تُرجمت هذه الصفحة آليًا وقد تحتوي على أخطاء. اقرأ الأصل الإنجليزي

العناصر

Tooltip

التلميح (Tooltip) إشارة قصيرة بجوار العنصر الذي تتعلق به: ماذا يفعل زر أيقونة، أو النص الكامل لتسمية مقتطعة. على أجهزة سطح المكتب ومتصفحاتها يظهر بعد أن يستقر المؤشر على العنصر المُحفِّز لمدة delay (500 مللي ثانية)، ويختفي عندما يغادر المؤشر العنصر أو يضغط عليه. على الهواتف والأجهزة اللوحية لا يظهر التلميح إلا عندما يفتحه التطبيق (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(|| ..)اختفى التلميح بعد انتهاء خروجه؛ وكذلك عندما يخفيه التطبيق.

#إمكانية الوصول

يكرر التلميح ما يقوله العنصر المُحفِّز لقارئات الشاشة: امنح العنصر المُحفِّز accessibilityLabel أيضًا، ليكون التلميح متاحًا للجميع.