Elementos
Tooltip
Um tooltip é uma dica curta ao lado daquilo a que se refere: o que um botão de ícone faz, o texto completo de um rótulo truncado.
Em desktops e navegadores desktop, ele aparece depois que o ponteiro fica parado sobre o gatilho por delay (500 ms) e
some quando o ponteiro sai ou pressiona o gatilho. Celulares e tablets mostram um tooltip somente quando o app o abre
(open). Ele nunca captura o ponteiro nem os toques, então o gatilho funciona exatamente como funcionaria sem ele.
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 de nsh/base é passado como a prop tooltip que todo elemento tem
(Props comuns), como o .tooltip(..) do GPUI: o elemento é aquilo sobre o qual o ponteiro fica parado e em relação ao qual
o tooltip é posicionado. O tooltip em si é o balão: estilize-o com class / style, e seus filhos são o que ele diz.
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>
);
}Por padrão, ele fica acima do gatilho (placement), do outro lado quando não há espaço, e permanece dentro da área
segura. Com open, o app decide quando ele aparece: ele segue onOpenChange, que informa quando o ponteiro fica parado sobre o
gatilho ou sai dele, e pode mostrá-lo em qualquer dispositivo.
Ele entra e sai com as suas transições: startingClass é a sua aparência no primeiro frame e endingClass a sua aparência
depois de oculto, que ele mantém enquanto as classes transition-* do seu class executam; onClose vem em seguida.
<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 | Tipo | Padrão | Descrição |
|---|---|---|---|---|
children |
.child(..) |
JSX.Element |
nenhum | O que ele diz. |
class, style |
métodos .app_class(..), Styled |
string, StyleProp<ViewStyle> |
nenhum | A aparência do balão: o próprio tooltip. |
startingClass, startingStyle |
.starting_class(..), .starting_style(..) |
string, StyleProp<ViewStyle> |
nenhum | Sua aparência no primeiro frame: suas transições partem dela. |
endingClass, endingStyle |
.ending_class(..), .ending_style(..) |
string, StyleProp<ViewStyle> |
nenhum | Sua aparência depois de oculto: ela permanece enquanto suas transições executam. |
open |
.open(..) |
boolean |
nenhum | Exibido, controlado pelo app. |
defaultOpen |
.default_open(..) |
boolean |
false |
Exibido inicialmente, sem open. |
delay |
.delay(..) |
number |
500 |
Milissegundos em que o ponteiro fica parado sobre o gatilho antes de o tooltip aparecer; Infinity: nunca sozinho, somente quando o app o abre. |
disabled |
.disabled(..) |
boolean |
false |
Nunca é exibido. |
placement |
.placement(..) |
"top" | "bottom" | "left" | "right" |
"top" |
O lado do gatilho em que ele fica. |
align |
.align(..) |
"start" | "center" | "end" |
"center" |
Como ele se alinha ao gatilho ao longo desse lado. |
offset |
.offset(..) |
number |
4 |
O espaço entre o gatilho e o tooltip. |
margin |
.margin(..) |
number |
4 |
A distância que ele mantém das bordas da área segura. |
#Eventos
| Evento | Rust | Descrição |
|---|---|---|
onOpenChange |
.on_open_change(|e| ..) |
O ponteiro ficou parado sobre o gatilho, ou saiu dele ou o pressionou (reason "hover"). |
onOpen |
.on_open(|| ..) |
Ele está exibido. |
onClose |
.on_close(|| ..) |
Ele foi ocultado, após a sua saída; também quando o app o ocultou. |
#Acessibilidade
Um tooltip repete para os leitores de tela o que o seu gatilho diz: dê ao gatilho também um accessibilityLabel, para que a
dica esteja disponível para todos.