Elementos
Tooltip
Un tooltip es una pista breve junto a aquello a lo que se refiere: lo que hace un botón de icono, el texto completo de una etiqueta truncada.
En escritorio y en navegadores de escritorio aparece cuando el puntero ha permanecido sobre su disparador durante delay (500 ms), y
se oculta cuando el puntero sale o pulsa el disparador. Los teléfonos y las tabletas solo muestran un tooltip cuando la app lo abre
(open). Nunca captura el puntero ni las pulsaciones, por lo que el disparador funciona exactamente igual que sin él.
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 se pasa como la prop tooltip que tiene todo elemento
(Props comunes), como el .tooltip(..) de GPUI: el elemento es sobre el que descansa el puntero y aquel respecto al cual
se coloca. El tooltip en sí es la burbuja: se estiliza con class / style, y sus hijos son lo que dice.
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 defecto se coloca encima de su disparador (placement), en el lado opuesto cuando no hay espacio, y permanece dentro del área
segura. Con open la app decide cuándo se muestra: sigue a onOpenChange, que informa de que el puntero descansa sobre el
disparador o lo abandona, y puede mostrarse en cualquier dispositivo.
Aparece y desaparece con tus transiciones: startingClass es su aspecto en el primer fotograma y endingClass su aspecto
una vez oculto, que conserva mientras se ejecutan las clases transition-* de su class; a continuación viene 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 | Tipo | Valor por defecto | Descripción |
|---|---|---|---|---|
children |
.child(..) |
JSX.Element |
ninguno | Lo que dice. |
class, style |
métodos .app_class(..), Styled |
string, StyleProp<ViewStyle> |
ninguno | El aspecto de la burbuja: el propio tooltip. |
startingClass, startingStyle |
.starting_class(..), .starting_style(..) |
string, StyleProp<ViewStyle> |
ninguno | Su aspecto en el primer fotograma: sus transiciones parten de él. |
endingClass, endingStyle |
.ending_class(..), .ending_style(..) |
string, StyleProp<ViewStyle> |
ninguno | Su aspecto una vez oculto: se mantiene mientras se ejecutan sus transiciones. |
open |
.open(..) |
boolean |
ninguno | Mostrado, controlado por la app. |
defaultOpen |
.default_open(..) |
boolean |
false |
Mostrado al principio, sin open. |
delay |
.delay(..) |
number |
500 |
Milisegundos que el puntero descansa sobre el disparador antes de que se muestre; Infinity: nunca por sí mismo, solo cuando la app lo abre. |
disabled |
.disabled(..) |
boolean |
false |
Nunca se muestra. |
placement |
.placement(..) |
"top" | "bottom" | "left" | "right" |
"top" |
El lado del disparador en el que se coloca. |
align |
.align(..) |
"start" | "center" | "end" |
"center" |
Cómo se alinea con el disparador a lo largo de ese lado. |
offset |
.offset(..) |
number |
4 |
El espacio entre el disparador y el tooltip. |
margin |
.margin(..) |
number |
4 |
La distancia que mantiene respecto a los bordes del área segura. |
#Eventos
| Evento | Rust | Descripción |
|---|---|---|
onOpenChange |
.on_open_change(|e| ..) |
El puntero descansó sobre el disparador, o lo abandonó o lo pulsó (reason "hover"). |
onOpen |
.on_open(|| ..) |
Se muestra. |
onClose |
.on_close(|| ..) |
Ha desaparecido, tras su salida; también cuando la app lo ocultó. |
#Accesibilidad
Un tooltip repite para los lectores de pantalla lo que dice su disparador: dale también un accessibilityLabel al disparador, de modo que la
pista esté disponible para todos.