元素
Tooltip
工具提示是显示在所说明内容旁边的简短提示:图标按钮的作用、被截断的标签的完整文字。在桌面平台和桌面浏览器中,指针在触发元素上停留
delay(500 毫秒)后显示,指针离开或按下触发元素时隐藏。手机和平板只在应用打开它(open)时显示。它从不接收指针或按压,所以触发元素的行为与没有它时完全一样。
import { mount, signal, Text, View } from "nsh";
import { type OpenChangeReason, Tooltip } from "nsh/base";
import { desktop, Inspector, Log, Panel, size, 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: (open: boolean, reason: OpenChangeReason) => void;
}) {
const hovered = signal(false);
return (
<View style={{ alignItems: "center", gap: 6 }}>
<View
onPress={props.onPress}
accessibilityLabel={props.label}
activeStyle={{ opacity: 0.6 }}
hoverStyle={{ borderColor: ui().accent }}
style={{
...ui().raised,
width: desktop ? 36 : 52,
height: desktop ? 36 : 52,
borderRadius: desktop ? 8 : 14,
alignItems: "center",
justifyContent: "center",
}}
tooltip={
<Tooltip
open={props.pinned || hovered()}
placement={props.placement}
offset={8}
margin={8}
delay={400}
onOpenChange={(open, reason) => {
hovered.set(open);
props.onOpenChange(open, reason);
}}
style={{ backgroundColor: ui().text, borderRadius: 7, paddingHorizontal: 10, paddingVertical: 6 }}
>
<Text style={{ fontSize: 13, color: ui().page }}>{props.hint}</Text>
</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: 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 text in the theme's bubble.
const pin = () => (
<View
tooltip="Shows every tooltip without a pointer"
onPress={() => pinned.set(!pinned())}
activeStyle={{ opacity: 0.6 }}
style={
wide()
? { ...ui().raised, alignSelf: "center", height: size.control, paddingHorizontal: 12, borderRadius: 8, justifyContent: "center" }
: { ...ui().raised, flex: 1, height: 40, borderRadius: 10, alignItems: "center", justifyContent: "center" }
}
>
<Text style={{ fontSize: desktop ? 13 : 14, fontWeight: "500", color: ui().text }}>
{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 属性:文字显示在用主题 popover 令牌绘制的小气泡中,元素则按原样显示(见通用属性)。
view().on_press(save).tooltip("保存文档").child(text("💾"))<View onPress={save} tooltip="保存文档">
<Text>💾</Text>
</View>需要自定义样式、位置并跟踪状态时,把 nsh/base 的 Tooltip 作为元素的 tooltip 传入(与 GPUI 的 .tooltip(..) 相同):该元素就是指针停留的对象,也是它定位的参照。工具提示本身就是气泡:用 class / style 设置样式,子元素是它的内容。
use nsh_base::tooltip;
use nsh_core::ui::prelude::*;
fn save_button(on_save: impl Fn() + 'static) -> impl IntoElement {
view()
.on_press(on_save)
.accessibility_label("保存")
.p_2()
.child(text("💾"))
.tooltip(
tooltip()
.placement(Placement::Bottom)
.rounded_md()
.bg(rgb(0x111827))
.px_2()
.py_1()
.child(text("保存文档").text_color(white())),
)
}import { Text, View } from "nsh";
import { Tooltip } from "nsh/base";
function SaveButton(props: { onSave: () => void }) {
return (
<View
onPress={props.onSave}
accessibilityLabel="保存"
class="p-2"
tooltip={
<Tooltip placement="bottom" class="rounded-md bg-gray-900 px-2 py-1">
<Text class="text-white">保存文档</Text>
</Tooltip>
}
>
<Text>💾</Text>
</View>
);
}它默认显示在触发元素上方(placement),空间不够时换到另一侧,并始终位于安全区之内。设置 open 后由应用决定何时显示:应用跟随 onOpenChange(它报告指针在触发元素上停留或离开),并且可以在任何设备上显示它。
#属性
| 属性 | Rust | 类型 | 默认值 | 说明 |
|---|---|---|---|---|
children | .child(..) | JSX.Element | 无 | 提示的内容。 |
class、style | .app_class(..)、Styled 方法 | string、StyleProp<ViewStyle> | 无 | 气泡的外观:即工具提示本身。 |
open | .open(..) | boolean | 无 | 是否显示,由应用控制。 |
defaultOpen | .default_open(..) | boolean | false | 没有 open 时,初始是否显示。 |
delay | .delay(..) | number | 500 | 指针在触发元素上停留多少毫秒后显示。 |
disabled | .disabled(..) | boolean | false | 它从不显示。 |
placement | .placement(..) | "top" | "bottom" | "left" | "right" | "top" | 它位于触发元素的哪一侧。 |
align | .align(..) | "start" | "center" | "end" | "center" | 它沿这一侧如何与触发元素对齐。 |
offset | .offset(..) | number | 4 | 触发元素与工具提示之间的间距。 |
margin | .margin(..) | number | 4 | 它与安全区边缘保持的距离。 |
#事件
| 事件 | Rust | 说明 |
|---|---|---|
onOpenChange | .on_open_change(|e| ..) | 指针在触发元素上停留了,或离开、按下了它(reason 为 "hover")。 |
onOpen | .on_open(|| ..) | 它已显示。 |
onClose | .on_close(|| ..) | 它已消失,应用隐藏它时也会触发。 |
#无障碍
工具提示把触发元素的说明重复给屏幕阅读器:也给触发元素设置 accessibilityLabel,让每个人都能得到这条提示。