이 페이지는 아직 번역되지 않았습니다. 영어 원문 보기

요소

Tooltip

A tooltip is a short hint next to what it is about: what an icon button does, the full text of a truncated label. On the desktops and in desktop browsers it shows once the pointer has rested on its trigger for delay (500 ms), and hides when the pointer leaves or presses the trigger. Phones and tablets show a tooltip only when the app opens it (open). It never takes the pointer or presses, so the trigger works exactly as it does without it.

el-tooltip.tsx
el-tooltip.tsxLive
import { mount, signal, Text, View } from "nsh";
import { type OpenChangeReason, Tooltip } from "nsh/base";
import { button, buttonText, createPresence, desktop, Inspector, Log, Panel, Stage, ui, wide } from "./palette";

/** An icon button with a hint of the app's look, above or below it. */
function Tool(props: {
  icon: string;
  label: string;
  hint: string;
  placement: "top" | "bottom";
  pinned: boolean;
  onPress: () => void;
  onOpenChange: (e: { open: boolean; reason: OpenChangeReason }) => void;
}) {
  const hovered = signal(false);
  // The bubble stays made between showings: it comes in from its side as it shows.
  const presence = createPresence(props.placement);
  return (
    <View style={{ alignItems: "center", gap: 6 }}>
      <View
        onPress={props.onPress}
        accessibilityLabel={props.label}
        {...button("secondary", { align: "center", style: { width: desktop ? 36 : 52, height: desktop ? 36 : 52, paddingHorizontal: 0 } })}
        tooltip={
          <Tooltip
            open={props.pinned || hovered()}
            placement={props.placement}
            offset={8}
            margin={8}
            delay={400}
            onOpenChange={(e) => {
              hovered.set(e.open);
              props.onOpenChange(e);
            }}
            onOpen={presence.show}
            onClose={presence.hide}
          >
            <View style={{ backgroundColor: ui().text, borderRadius: 7, paddingHorizontal: 10, paddingVertical: 6, ...presence.style }}>
              <Text style={{ fontSize: 13, color: ui().page }}>{props.hint}</Text>
            </View>
          </Tooltip>
        }
      >
        <Text style={{ fontSize: desktop ? 17 : 22, color: ui().text }}>{props.icon}</Text>
      </View>
      <Text style={{ fontSize: 11, color: ui().faint }}>{props.placement}</Text>
    </View>
  );
}

function App() {
  const events = signal<string[]>([]);
  const pressed = signal("—");
  const pinned = signal(false);
  const log = (name: string) => ({ open, reason }: { open: boolean; reason: OpenChangeReason }) =>
    events.set([`${name} ${open ? "shown" : "hidden"} · ${reason}`, ...events()].slice(0, 6));
  const tools = () => (
    <>
      <Tool icon="★" label="Star" hint="Add to favorites" placement="top" pinned={pinned()} onPress={() => pressed.set("Star")} onOpenChange={log("Star")} />
      <Tool icon="↗" label="Share" hint="Share with your team" placement="bottom" pinned={pinned()} onPress={() => pressed.set("Share")} onOpenChange={log("Share")} />
      <Tool icon="✕" label="Delete" hint="Move to the trash" placement="top" pinned={pinned()} onPress={() => pressed.set("Delete")} onOpenChange={log("Delete")} />
    </>
  );
  // The `tooltip` prop of any element: a `Tooltip` with the app's look.
  const pin = () => (
    <View
      tooltip={
        <Tooltip style={{ backgroundColor: ui().text, borderRadius: 7, paddingHorizontal: 10, paddingVertical: 6 }}>
          <Text style={{ fontSize: 13, color: ui().page }}>Shows every tooltip without a pointer</Text>
        </Tooltip>
      }
      onPress={() => pinned.set(!pinned())}
      {...button("secondary", { align: "center", style: wide() ? {} : { flex: 1 } })}
    >
      <Text style={buttonText()}>
        {wide() ? (pinned() ? "Unpin the tooltips" : "Pin the tooltips") : pinned() ? "Unpin" : "Pin"}
      </Text>
    </View>
  );
  const onPress = () => (
    <Panel title="onPress">
      <Text style={{ fontSize: 13, color: ui().text }}>{pressed()}</Text>
    </Panel>
  );
  const changes = () => <Log title="onOpenChange({ open, reason })" lines={events()} minHeight={130} />;

  return (
    <View style={{ flex: 1, backgroundColor: ui().page, padding: 20, paddingTop: 24, gap: 14 }}>
      <Text style={{ fontSize: 22, fontWeight: "700", color: ui().text }}>tooltip</Text>
      <Text style={{ color: ui().muted }}>
        {desktop ? "Rest the pointer on a button; a click still presses it" : "Tooltips follow a pointer: pin them to see them here"}
      </Text>

      {() =>
        wide() ? (
          <View style={{ flex: 1, flexDirection: "row", gap: 16 }}>
            <Stage gap={40}>
              <View style={{ flexDirection: "row", justifyContent: "center", gap: 64 }}>{tools()}</View>
              {pin()}
            </Stage>
            <Inspector>
              {onPress()}
              {changes()}
            </Inspector>
          </View>
        ) : (
          <>
            <View style={{ flex: 1, minHeight: 200, flexDirection: "row", alignItems: "center", justifyContent: "center", gap: 28 }}>{tools()}</View>
            <View style={{ flexDirection: "row", gap: 8 }}>{pin()}</View>
            {onPress()}
            {changes()}
          </>
        )
      }
    </View>
  );
}

mount(App);

Tooltip from nsh/base is given as the tooltip prop every element has (Common props), as GPUI’s .tooltip(..): the element is what the pointer rests on and what it is placed against. The tooltip itself is the bubble: style it with class / style, and its children are what it says.

use nsh_base::tooltip;
use nsh_core::prelude::*;

fn save_button(on_save: impl Fn() + 'static) -> impl IntoElement {
    view()
        .on_press(on_save)
        .accessibility_label("Save")
        .p_2()
        .child(text("💾"))
        .tooltip(
            tooltip()
                .placement(Placement::Bottom)
                .rounded_md()
                .bg(rgb(0x111827))
                .px_2()
                .py_1()
                .child(text("Save the document").text_color(white())),
        )
}

It goes above its trigger by default (placement), on the other side when there is no room, and stays inside the safe area. With open the app decides when it shows: it follows onOpenChange, which reports the pointer resting on the trigger or leaving it, and can show it on any device.

It comes and goes with your transitions: startingClass is its look in its first frame and endingClass its look once hidden, which it keeps while the transition-* classes of its class run; onClose follows.

<Tooltip
  class="rounded-md bg-gray-900 px-2 py-1 transition-opacity duration-100"
  startingClass="opacity-0"
  endingClass="opacity-0"
>
  <Text class="text-white">Save the document</Text>
</Tooltip>

#Props

PropRustTypeDefaultDescription
children.child(..)JSX.ElementnoneWhat it says.
class, style.app_class(..), Styled methodsstring, StyleProp<ViewStyle>noneThe look of the bubble: the tooltip itself.
startingClass, startingStyle.starting_class(..), .starting_style(..)string, StyleProp<ViewStyle>noneIts look in its first frame: its transitions run from it.
endingClass, endingStyle.ending_class(..), .ending_style(..)string, StyleProp<ViewStyle>noneIts look once hidden: it stays while its transitions run.
open.open(..)booleannoneShown, controlled by the app.
defaultOpen.default_open(..)booleanfalseShown at first, without open.
delay.delay(..)number500Milliseconds the pointer rests on the trigger before it shows; Infinity: never by itself, only when the app opens it.
disabled.disabled(..)booleanfalseIt never shows.
placement.placement(..)"top" | "bottom" | "left" | "right""top"The side of the trigger it goes on.
align.align(..)"start" | "center" | "end""center"How it lines up with the trigger along that side.
offset.offset(..)number4The gap between the trigger and the tooltip.
margin.margin(..)number4The distance it keeps from the edges of the safe area.

#Events

EventRustDescription
onOpenChange.on_open_change(|e| ..)The pointer rested on the trigger, or left or pressed it (reason "hover").
onOpen.on_open(|| ..)It is shown.
onClose.on_close(|| ..)It is gone, after its exit; also when the app hid it.

#Accessibility

A tooltip repeats what its trigger says to screen readers: give the trigger an accessibilityLabel too, so the hint is there for everyone.