Elemente
Tooltip
Ein Tooltip ist ein kurzer Hinweis neben dem Element, auf das er sich bezieht: was ein Icon-Button tut oder der vollständige Text eines abgeschnittenen Labels.
Auf Desktops und in Desktop-Browsern erscheint er, sobald der Zeiger delay (500 ms) auf seinem Trigger verweilt hat, und verschwindet wieder, wenn der Zeiger den Trigger verlässt oder darauf drückt. Smartphones und Tablets zeigen einen Tooltip nur, wenn die App ihn öffnet
(open). Er fängt weder den Zeiger noch Klicks ab, sodass der Trigger genau so funktioniert wie ohne ihn.
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 aus nsh/base wird als Prop tooltip übergeben, die jedes Element hat
(Gemeinsame Props), als GPUIs .tooltip(..): Das Element ist das, worauf der Zeiger verweilt und woran der Tooltip ausgerichtet wird. Der Tooltip selbst ist die Sprechblase: Style sie mit class / style, und ihre Kinder sind das, was sie sagt.
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())),
)
}import { Text, View } from "nsh";
import { Tooltip } from "nsh/base";
function SaveButton(props: { onSave: () => void }) {
return (
<View
onPress={props.onSave}
accessibilityLabel="Save"
class="p-2"
tooltip={
<Tooltip placement="bottom" class="rounded-md bg-gray-900 px-2 py-1">
<Text class="text-white">Save the document</Text>
</Tooltip>
}
>
<Text>💾</Text>
</View>
);
}Standardmäßig steht er über seinem Trigger (placement), bei zu wenig Platz auf der gegenüberliegenden Seite, und bleibt innerhalb der Safe Area. Mit open entscheidet die App, wann er erscheint. Dazu dient onOpenChange: Es meldet, wenn der Zeiger auf dem Trigger verweilt oder ihn verlässt, und der Tooltip lässt sich auf jedem Gerät anzeigen.
Er erscheint und verschwindet mit den Transitions: startingClass ist sein Aussehen im ersten Frame und endingClass sein Aussehen im ausgeblendeten Zustand, das er beibehält, solange die transition-*-Klassen seines class laufen; danach folgt 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>
#Props
| Prop | Rust | Typ | Standard | Beschreibung |
|---|---|---|---|---|
children |
.child(..) |
JSX.Element |
keiner | Was er sagt. |
class, style |
.app_class(..), Styled Methoden |
string, StyleProp<ViewStyle> |
keiner | Das Aussehen der Sprechblase: der Tooltip selbst. |
startingClass, startingStyle |
.starting_class(..), .starting_style(..) |
string, StyleProp<ViewStyle> |
keiner | Sein Aussehen im ersten Frame: Seine Transitions laufen von dort aus. |
endingClass, endingStyle |
.ending_class(..), .ending_style(..) |
string, StyleProp<ViewStyle> |
keiner | Sein Aussehen im ausgeblendeten Zustand: Es bleibt bestehen, solange seine Transitions laufen. |
open |
.open(..) |
boolean |
keiner | Angezeigt, von der App gesteuert. |
defaultOpen |
.default_open(..) |
boolean |
false |
Anfangs angezeigt, ohne open. |
delay |
.delay(..) |
number |
500 |
Millisekunden, die der Zeiger auf dem Trigger verweilt, bevor er erscheint; Infinity: nie von selbst, nur wenn die App ihn öffnet. |
disabled |
.disabled(..) |
boolean |
false |
Er wird nie angezeigt. |
placement |
.placement(..) |
"top" | "bottom" | "left" | "right" |
"top" |
Die Seite des Triggers, auf der er steht. |
align |
.align(..) |
"start" | "center" | "end" |
"center" |
Wie er sich entlang dieser Seite am Trigger ausrichtet. |
offset |
.offset(..) |
number |
4 |
Der Abstand zwischen Trigger und Tooltip. |
margin |
.margin(..) |
number |
4 |
Der Abstand, den er zu den Rändern der Safe Area einhält. |
#Ereignisse
| Ereignis | Rust | Beschreibung |
|---|---|---|
onOpenChange |
.on_open_change(|e| ..) |
Der Zeiger verweilt auf dem Trigger, hat ihn verlassen oder darauf gedrückt (reason "hover"). |
onOpen |
.on_open(|| ..) |
Er wird angezeigt. |
onClose |
.on_close(|| ..) |
Er ist verschwunden, nach seiner Exit-Transition; auch wenn die App ihn ausgeblendet hat. |
#Barrierefreiheit
Ein Tooltip wiederholt gegenüber Screenreadern, was sein Trigger aussagt: Gib dem Trigger zusätzlich accessibilityLabel, damit der Hinweis für alle verfügbar ist.