Эта страница переведена машинным способом и может содержать ошибки. Читать оригинал на английском

Элементы

Tooltip

Tooltip — это короткая подсказка рядом с тем, к чему она относится: что делает кнопка-иконка, полный текст усечённой надписи. На десктопах и в десктопных браузерах она появляется, когда указатель задержался на своём триггере в течение delay (500 мс), и скрывается, когда указатель уходит с триггера или нажимает на него. На телефонах и планшетах Tooltip показывается, только когда приложение само открывает его (open). Она никогда не перехватывает указатель и нажатия, поэтому триггер работает точно так же, как и без неё.

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 из nsh/base передаётся как свойство tooltip, которое есть у каждого элемента (Общие свойства), как .tooltip(..) в GPUI: элемент — это то, на чём задерживается указатель и относительно чего размещается подсказка. Сама подсказка — это всплывающее окошко: стилизуйте его через class / style, а его дочерние элементы — это то, что оно сообщает.

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())),
        )
}

По умолчанию она располагается над своим триггером (placement), с другой стороны, если места нет, и остаётся внутри безопасной области. С open приложение само решает, когда её показывать: она следует за onOpenChange, который сообщает, что указатель задержался на триггере или покинул его, и может показываться на любом устройстве.

Она появляется и исчезает вместе с вашими переходами: startingClass — её вид в первом кадре, а endingClass — её вид после скрытия, который она сохраняет, пока выполняются классы transition-* её class; затем следует onClose.

<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>

#Свойства

СвойствоRustТипПо умолчаниюОписание
children.child(..)JSX.ElementнетЧто она сообщает.
class, styleметоды .app_class(..), Styledstring, StyleProp<ViewStyle>нетВид всплывающего окошка — самой подсказки.
startingClass, startingStyle.starting_class(..), .starting_style(..)string, StyleProp<ViewStyle>нетЕё вид в первом кадре: от него запускаются её переходы.
endingClass, endingStyle.ending_class(..), .ending_style(..)string, StyleProp<ViewStyle>нетЕё вид после скрытия: сохраняется, пока выполняются её переходы.
open.open(..)booleanнетПоказана, управляется приложением.
defaultOpen.default_open(..)booleanfalseПоказана изначально, без open.
delay.delay(..)number500Миллисекунды, в течение которых указатель должен задержаться на триггере, прежде чем она появится; Infinity: сама никогда, только когда её открывает приложение.
disabled.disabled(..)booleanfalseНикогда не показывается.
placement.placement(..)"top" | "bottom" | "left" | "right""top"Сторона триггера, с которой она располагается.
align.align(..)"start" | "center" | "end""center"Как она выравнивается относительно триггера вдоль этой стороны.
offset.offset(..)number4Зазор между триггером и подсказкой.
margin.margin(..)number4Расстояние, которое она выдерживает от краёв безопасной области.

#События

СобытиеRustОписание
onOpenChange.on_open_change(|e| ..)Указатель задержался на триггере, ушёл с него или нажал на него (reason "hover").
onOpen.on_open(|| ..)Она показана.
onClose.on_close(|| ..)Она исчезла, после завершения выхода; также когда её скрыло приложение.

#Доступность

Tooltip дублирует для программ чтения с экрана то, что сообщает её триггер: задайте триггеру также accessibilityLabel, чтобы подсказка была доступна всем.