Elementos
Dock
Un área de dock es un espacio de trabajo que el usuario organiza: paneles en grupos de pestañas, grupos divididos lado a lado o apilados, y docks en los bordes izquierdo, derecho e inferior que se abren, se cierran y cambian de tamaño. Una pestaña arrastrada a otro grupo se une a él, arrastrada al borde de un grupo lo divide, y cada panel conserva lo que contiene al moverse. DockArea se encarga del comportamiento: arrastrar y soltar pestañas, que se ejecuta de forma nativa fotograma a fotograma, redimensionar divisiones y docks, abrir y cerrar docks, el zoom y el bloqueo. La apariencia es de la app: las pestañas, las barras de pestañas, los tiradores, el indicador de destino y el contenido de cada panel los renderiza y les da estilo la app, y el área no dibuja nada por sí misma.
import { mount, signal, Text, View } from "nsh";
import { Button, DockArea, type DockAreaLayout, type DockAreaRef } from "nsh/base";
import { button, buttonText, desktop, focusRing, Log, Panel, Split, ui, wide } from "./palette";
// The panels' keys and what each shows. The layout lists the keys; the app
// renders a panel's content from its key, and the content moves with it.
const titles: Record<string, string> = {
files: "Files",
readme: "README.md",
main: "main.rs",
preview: "Preview",
terminal: "Terminal",
problems: "Problems",
};
const lines: Record<string, string[]> = {
files: ["src/", " main.rs", " lib.rs", "README.md", "Cargo.toml"],
readme: ["# Hopper", "", "A small app with a dockable workspace."],
main: ["fn main() {", " hopper::run();", "}"],
preview: ["Hopper", "A small app with a dockable workspace."],
terminal: ["$ cargo run", " Compiling hopper v0.1.0", " Finished dev in 1.92s"],
problems: ["No problems"],
};
const initial: DockAreaLayout = {
center: {
type: "split",
axis: "horizontal",
children: [
{ type: "tabs", panels: ["readme", "main"], activeIndex: 1 },
{ type: "tabs", panels: ["preview"] },
],
sizes: [null, 260],
},
left: { layout: { type: "tabs", panels: ["files"] }, size: 180 },
bottom: { layout: { type: "tabs", panels: ["terminal", "problems"] }, size: 130 },
};
function App() {
const layout = signal<DockAreaLayout>(initial);
const locked = signal(false);
// What shows now, in either presentation: the layout's docks when the area
// is wide, the drawer or sheet presented when it is narrow. It starts from
// the layout and follows onDockOpenChange.
const shown = signal<Record<string, boolean>>({ left: true, bottom: true });
const events = signal<string[]>([]);
const log = (line: string) => events.set([line, ...events()].slice(0, 4));
let area!: DockAreaRef;
const toolbarButton = (label: string, onPress: () => void, on?: () => boolean) => (
<Button onPress={onPress} selected={on?.() ?? false} {...button("secondary", { size: "sm", on })}>
<Text style={buttonText("secondary", { on })}>{label}</Text>
</Button>
);
return (
<View style={{ flex: 1, backgroundColor: ui().page, padding: 20, paddingTop: 24, gap: 14 }}>
<Text style={{ fontSize: 22, fontWeight: "700", color: ui().text }}>dock</Text>
<Text style={{ color: ui().muted }}>
{desktop
? "Drag a tab onto another group, or to a group's edge to split it"
: "Tap Files or Terminal to open a dock; hold a tab to reorder it"}
</Text>
<Split
grow
width={720}
inspector={
<>
<Panel title="docks">
<View style={{ flexDirection: "row", flexWrap: "wrap", gap: 8 }}>
{toolbarButton("Files", () => area.toggleDock("left"), () => shown().left)}
{toolbarButton("Terminal", () => area.toggleDock("bottom"), () => shown().bottom)}
{toolbarButton("Lock", () => locked.set(!locked()), locked)}
{toolbarButton("Reset", () => layout.set(initial))}
</View>
</Panel>
<Log title="events" lines={events()} minHeight={96} />
</>
}
>
<View style={{ height: wide() ? 420 : 460, ...ui().card, borderRadius: 14, overflow: "hidden" }}>
<DockArea
ref={area}
class="flex-1"
layout={layout()}
onLayoutChange={({ layout: next }) => {
layout.set(next);
log("onLayoutChange");
}}
onDockOpenChange={({ placement, open }) => {
shown.set({ ...shown(), [placement]: open });
log(`${placement} ${open ? "shown" : "hidden"}`);
}}
onPanelActiveChange={({ panel, active }) => active && log(`${titles[panel]} shown`)}
locked={locked()}
// The demo's two layouts: the full workspace beside the inspector in a
// wide window, the phone presentation in one column. Without `compact`
// the area decides by its own width (under 600 points: compact).
compact={!wide()}
groupClass="flex-1"
tabBarClass="flex-row border-b border-border"
tabClass="group px-3 py-1.5 selected:bg-muted"
contentClass="flex-1"
handleClass="bg-border group-hover:bg-muted-foreground group-active:bg-primary"
handleAccessibilityLabel="Resize"
dockClass="bg-muted/40"
dropIndicatorClass="rounded-(--radius) border-2 border-primary bg-primary/10"
drawerClass="bg-background border-r border-border"
sheetClass="rounded-t-(--radius-lg) bg-background"
backdropClass="bg-black/30"
renderTab={(tab) => (
// One view inside the tab: a row holding the title and the close button.
<View class="flex-row items-center gap-1.5">
<Text class="text-[13px] text-muted-foreground group-selected:text-foreground">{titles[tab.panel]}</Text>
{tab.closable && desktop ? (
<Button
onPress={() => area.closePanel(tab.panel)}
accessibilityLabel={`Close ${titles[tab.panel]}`}
class="rounded-sm px-1 hover:bg-muted active:opacity-70"
focusStyle={focusRing()}
>
<Text class="text-xs text-muted-foreground">×</Text>
</Button>
) : null}
</View>
)}
renderDragPreview={(tab) => (
<View class="rounded-(--radius) border border-border bg-background px-3 py-1.5 shadow-md">
<Text class="text-[13px] text-foreground">{titles[tab.panel]}</Text>
</View>
)}
renderPanel={(panel) => (
<View class="flex-1 gap-1 p-3">
{(lines[panel.id] ?? []).map((line) => (
<Text class="font-mono text-xs text-foreground">{line || " "}</Text>
))}
</View>
)}
renderEmpty={() => (
<View class="flex-1 items-center justify-center">
<Text class="text-sm text-muted-foreground">Every panel is closed</Text>
</View>
)}
/>
</View>
</Split>
</View>
);
}
mount(App);use nsh_base::{
dock::{DockAreaLayout, DockLayout, DockPanel, DockPlacement, DockRegion, DockTab},
dock_area, theme,
};
use nsh_core::prelude::*;
fn workspace() -> impl IntoElement {
let layout = signal(
DockAreaLayout::new(
DockLayout::h_split()
.child(DockLayout::tabs().panel("readme").panel("main"), None)
.child(DockLayout::tabs().panel("preview"), Some(px(320.0))),
)
.with_dock(DockPlacement::Left, DockRegion::new(DockLayout::tabs().panel("files")))
.with_dock(DockPlacement::Bottom, DockRegion::new(DockLayout::tabs().panel("terminal"))),
);
dock_area()
.layout(move || layout.get())
.on_layout_change(move |next: &DockAreaLayout| layout.set(next.clone()))
.handle_style(|s| s.bg(move || theme().border()))
.tab_bar_style(|s| s.flex_row().border_b_1().border_color(move || theme().border()))
.tab_style(|s| s.px_3().py_1().selected(|s| s.bg(move || theme().muted())))
.render_tab(|tab: &DockTab| text(tab.panel.to_string()).text_sm().text_color(move || theme().foreground()))
.render_panel(|panel: &DockPanel| {
text(format!("{} content", panel.id)).p_4().text_color(move || theme().foreground())
})
}import { mount, signal, Text } from "nsh";
import { DockArea, type DockAreaLayout } from "nsh/base";
const titles: Record<string, string> = {
files: "Files", readme: "README.md", main: "main.rs", preview: "Preview", terminal: "Terminal",
};
function App() {
const layout = signal<DockAreaLayout>({
center: {
type: "split",
axis: "horizontal",
children: [
{ type: "tabs", panels: ["readme", "main"] },
{ type: "tabs", panels: ["preview"] },
],
sizes: [null, 320],
},
left: { layout: { type: "tabs", panels: ["files"] }, size: 240 },
bottom: { layout: { type: "tabs", panels: ["terminal"] }, size: 180 },
});
return (
<DockArea
class="size-full bg-background"
layout={layout()}
onLayoutChange={(e) => layout.set(e.layout)}
handleClass="bg-border group-active:bg-primary"
tabBarClass="flex-row border-b border-border"
tabClass="px-3 py-1.5 selected:bg-muted"
renderTab={(tab) => <Text class="text-sm text-foreground">{titles[tab.panel]}</Text>}
renderPanel={(panel) => <Text class="p-4 text-foreground">{titles[panel.id]} content</Text>}
/>
);
}
mount(App);DockArea y los valores que toma provienen de nsh/base, el crate nsh-base de Rust
(nsh_base::dock_area(), los valores en nsh_base::dock). En Rust, dock_area() lo crea, con un setter por
prop, un listener por evento y los métodos de estilo tipados; DockLayout describe una disposición mediante las claves de los paneles
(h_split(), v_split(), tabs(), .child(layout, size), .panel(key)).
#La disposición
La disposición es un valor, DockAreaLayout: el centro y un dock en cada uno de los bordes izquierdo, derecho e inferior, todos
opcionales. Cada región es un árbol con dos tipos de nodo:
| Nodo | Contiene | Campos |
|---|---|---|
{ type: "split" } |
Otros nodos, lado a lado (axis: "horizontal") o apilados ("vertical") |
children y sizes: la longitud de cada hijo a lo largo del eje en puntos, null para repartir el resto |
{ type: "tabs" } |
Paneles, por clave, uno visible a la vez | panels (las claves, en orden de pestañas), activeIndex (la visible, 0 por defecto) |
Un panel siempre vive en un grupo de pestañas: una región con un solo panel es un grupo de pestañas de uno. La clave de un panel es la cadena que la app usa para identificarlo ("files", el id de un documento), y una clave aparece una sola vez en toda la disposición. Un dock (DockRegion) es su árbol
(layout), su tamaño a lo ancho de su borde (size, 200 por defecto, mínimo 100), si está abierto (open, por defecto true)
y si arrastrar su borde por debajo de su tamaño mínimo lo cierra (collapsible, por defecto true).
Todo nodo tiene un id. Una disposición que escribe la app puede omitir los ids: el área los asigna, y toda disposición que informa los incluye. Al devolver una disposición informada se conserva la identidad de cada grupo, que es como los métodos del ref nombran los grupos. El área también ordena lo que recibe: un grupo o una división vacíos desaparecen, una división de un solo hijo se convierte en ese hijo, una división dentro de otra división del mismo eje se une a su padre, activeIndex permanece dentro del rango, y una clave listada dos veces conserva su primer lugar. Las disposiciones que informa siempre tienen esa única forma canónica.
La disposición es de la app (layout): cada cambio que hace el usuario (una pestaña seleccionada, arrastrada o cerrada, una división o un dock redimensionados, un dock abierto o cerrado) se informa como la nueva disposición completa (onLayoutChange), y el área muestra la
disposición que la app le devuelve. La app cambia la disposición por sí misma pasando un nuevo valor: una disposición guardada que se restaura, un
comando que reorganiza el espacio de trabajo. Un área sin layout mantiene la suya propia, a partir de defaultLayout, y
sigue informando cada cambio.
#Contenido de los paneles
Lo que muestra un panel es de la app: renderPanel lo renderiza a partir de la clave del panel (panel.id). Todos los paneles de un grupo se colocan y solo se muestra el visible, de modo que al cambiar de pestaña cada panel conserva su contenido; y cuando un panel se mueve (a otro grupo, a un dock, a un zoom y de vuelta), el mismo contenido se mueve con él: las signals que creó, el texto de un input, una posición de scroll, la página de un web view, todo permanece. El área renderiza un panel una vez, cuando aparece por primera vez, y libera su contenido cuando el panel sale de la disposición (onPanelRemove).
<DockArea
layout={layout()}
onLayoutChange={(e) => layout.set(e.layout)}
renderPanel={(panel) => (panel.id === "terminal" ? <Terminal /> : <Editor file={panel.id} />)}
onPanelActiveChange={({ panel, active }) => active && setTitle(panel)}
onPanelRemove={({ panel }) => closeFile(panel)}
/>
panels define lo que permite cada panel, por clave; un panel que no figura en la lista lo permite todo:
| Campo | Por defecto | Descripción |
|---|---|---|
id |
La clave del panel. | |
closable |
true |
Puede cerrarse. |
zoomable |
true |
Su grupo puede hacer zoom mientras el panel está visible. |
visible |
true |
Se muestra. Un panel oculto conserva su lugar y vuelve a él; un grupo o una división cuyos paneles están todos ocultos no ocupa espacio. |
#Pestañas
Cada grupo tiene una barra de pestañas con una pestaña por cada panel visible, en el orden de su panels. renderTab renderiza lo que muestra una pestaña (su título, un icono, un botón de cierre propio de la app); sin él, una pestaña muestra la clave del panel. El área la convierte en la pestaña: una pulsación la selecciona, un arrastre mueve su panel, los lectores de pantalla la anuncian como una pestaña con su estado de selección, y las clases selected: de tabClass dan estilo a la visible. El valor de una pestaña indica lo que la app necesita para dibujarla:
| Campo | Tipo | Descripción |
|---|---|---|
panel |
string |
La clave del panel. |
group |
number |
El id del grupo de pestañas en el que está. |
index |
number |
Su lugar entre las pestañas visibles del grupo. |
active |
boolean |
Es la pestaña visible del grupo. |
closable |
boolean |
El área la cerraría: es cerrable, el área no está bloqueada y su grupo puede cederla. |
zoomed |
boolean |
Su grupo tiene zoom. |
let area!: DockAreaRef;
<DockArea
ref={area}
layout={layout()}
onLayoutChange={(e) => layout.set(e.layout)}
renderTab={(tab) => (
<View class="flex-row items-center gap-2">
<Text class="text-sm">{titles[tab.panel]}</Text>
<If when={tab.closable}>
<Button onPress={() => area.closePanel(tab.panel)} accessibilityLabel="Close" class="rounded-sm px-1 hover:bg-muted">
<Text class="text-xs">×</Text>
</Button>
</If>
</View>
)}
renderPanel={(panel) => <Editor file={panel.id} />}
/>
#Arrastrar y soltar
Una pestaña arrastrada sobre un grupo muestra dónde aterrizaría, y al soltarla se coloca allí:
| Se suelta sobre | El panel |
|---|---|
| Una barra de pestañas | Se une a ese grupo, entre las pestañas junto a las que se suelta |
| El centro del contenido de un grupo | Se une a ese grupo |
| Un borde del contenido de un grupo (su 35 % exterior en cada lado) | Divide ese grupo: un nuevo grupo propio en ese lado |
Mientras un arrastre pasa por encima, el área muestra el indicador de destino sobre la parte del grupo que ocuparía el panel (todo su contenido si se une, la mitad si divide): una parte a la que la
app da estilo (dropIndicatorClass), con contenido propio si la app lo renderiza (renderDropIndicator, que recibe el
group, el side que separa o la barra de pestañas index). Se mueve a cada nuevo lugar de forma nativa, deslizándose hasta allí y apareciendo gradualmente sobre otro grupo mientras el anterior se desvanece (de inmediato cuando el usuario pide menos movimiento). Lo que sigue al puntero o al dedo también es de la app
(renderDragPreview). Un soltar es un único cambio de la disposición, informado una sola vez; un soltar que no cambiaría nada no cambia nada.
Un área bloqueada, un área con zoom y el último panel de un grupo cuando no hay nada junto a ese grupo no permiten arrastrar nada.
#Docks
Un dock se abre y se cierra sin perder sus paneles ni su tamaño: al cerrarlo conserva su árbol, y al abrirlo muestra los mismos grupos con el mismo tamaño. El borde entre un dock y el centro es un tirador que el usuario arrastra para redimensionar el dock; se mantiene en al menos 100 puntos y el centro conserva al menos 100 puntos, y un dock colapsable que se suelta por debajo de 50 puntos se cierra y conserva su tamaño para cuando se abra. El área siempre se ajusta a sus límites: un dock se muestra con su size dentro de lo que el área le deja, y cuando ni así cabe, los docks se reducen junto con el resto y los tamaños mínimos de las divisiones ceden. renderDock dibuja el chrome propio de un dock antes de su disposición (una franja de título, un botón de colapso), y recibe su
placement, open, size y collapsible; dockClass da estilo a su marco.
Abre, cierra y redimensiona docks desde la app mediante el ref (toggleDock, setDockOpen, setDockSize), que funcionan
en ambas presentaciones, o escribiendo los campos open y size del dock en la disposición que pasa (open es el de la
presentación completa). onDockOpenChange informa
cada vez que un dock se muestra o se oculta, medido respecto al open de la disposición (un área que es compacta desde el inicio informa
como oculto cada dock abierto en la disposición), de modo que un interruptor que parte del open de la disposición y sigue a onDockOpenChange siempre
muestra el estado del dock:
const terminalOpen = signal(true);
<DockArea
ref={area}
layout={layout()}
onLayoutChange={(e) => layout.set(e.layout)}
onDockOpenChange={({ placement, open }) => placement === "bottom" && terminalOpen.set(open)}
renderPanel={(panel) => <Editor file={panel.id} />}
/>
<Button selected={terminalOpen()} onPress={() => area.toggleDock("bottom")} class="px-3 py-1.5 selected:bg-muted">
<Text>Terminal</Text>
</Button>
#Zoom
Un grupo con zoom llena toda el área hasta que se le quita el zoom o sale de la disposición (area.toggleZoom(group), y area.toggleZoom() para quitar
el zoom), y onZoomChange informa el panel que muestra, o null. El zoom pertenece al grupo, por lo que sigue con zoom
cuando se cierra su panel visible y el siguiente toma su lugar. Un panel con zoomable: false impide que su grupo haga
zoom mientras está visible.
#Bloqueo
locked mantiene la organización tal como está: sin arrastrar pestañas, sin soltar, sin cerrar. Seleccionar pestañas, redimensionar y abrir y
cerrar docks siguen funcionando, de modo que un espacio de trabajo bloqueado sigue siendo un espacio de trabajo utilizable.
#Métodos
ref={area} proporciona los métodos del área (DockAreaRef), para los comandos, atajos y menús de la app. Cada edición se
informa con onLayoutChange igual que las del usuario. Un método al que se le pasa un panel, un grupo, un índice de pestaña o un dock que la disposición
no tiene deja la disposición como está y registra una advertencia con el nombre del método y el valor.
| Método | Descripción |
|---|---|
layout() |
Promise<DockAreaLayout>: la disposición mostrada. |
addPanel(panel, placement?, size?) |
Añade un panel a una región ("center", el valor por defecto, "left", "right", "bottom"), en su primer grupo de pestañas, como visible; un dock inexistente se crea, con size de ancho. Un panel que el área ya contiene se mueve allí. |
removePanel(panel) |
Elimina un panel, sin importar lo que digan sus opciones. |
closePanel(panel) |
Cierra un panel como lo haría su pestaña: salvo que el área esté bloqueada, el panel no sea cerrable o sea el último panel de un grupo sin nada junto a él. |
movePanel(panel, target) |
Mueve un panel a target: { group, side?, index?, activate?, size? }, al grupo de pestañas group (en index) o a un nuevo grupo en su side (size puntos a lo largo de la nueva división). |
splitAt(group, panel, side) |
Coloca un panel en un nuevo grupo de pestañas en un lado ("left", "right", "top", "bottom") de un contenedor. |
selectPanel(panel) |
Muestra un panel en su grupo. |
selectTab(group, index) |
Muestra una pestaña de un grupo. |
toggleDock(placement), setDockOpen(placement, open) |
Abre o cierra un dock. |
setDockSize(placement, size) |
Define el tamaño de un dock a lo ancho de su borde (mínimo 100). |
toggleZoom(group?) |
Aplica zoom a un grupo, o se lo quita cuando ya lo tiene; sin group, quita el zoom al grupo ampliado. |
zoomedGroup() |
Promise<number | null>: el id del grupo con zoom. |
groupOf(panel) |
Promise<number | null>: el id del grupo de pestañas que contiene un panel. |
En Rust, component_ref::<DockArea>() y .component_ref(&r) en el área proporcionan un ref cuyo r.api::<DockAreaApi>() tiene
los mismos métodos en snake case.
#Guardar y restaurar
Una disposición informada son datos simples, así que guardarla es JSON.stringify(layout) en el almacenamiento,
y restaurarla es pasar el valor analizado como layout. onLayoutChange informa cada cambio, incluidos un redimensionamiento y una selección de
pestaña, así que escribe en el almacenamiento cuando los cambios se hayan asentado (un temporizador corto que se reinicia con cada informe).
En Rust, DockAreaLayout::to_state y from_state leen y escriben DockAreaState, el formato de archivo de disposición
que también usa el dock de gpui-base.
#Teléfonos y ventanas estrechas
El dock se adapta a su propio ancho. Un área de 600 puntos de ancho o más (en una ventana de escritorio, en la web, en una tablet, un iPad a pantalla completa, un plegable abierto) es el espacio de trabajo completo: divisiones, grupos y docks lado a lado, como las apps multipanel de una tablet. Con un dedo en un teléfono o una tablet, el arrastre de una pestaña comienza con una pulsación larga, como en el arrastrar y soltar de iPadOS, de modo que una barra de pestañas sigue desplazándose con un deslizamiento, y los tiradores tienen un área táctil más amplia con el mismo aspecto.
Un área de menos de 600 puntos (en un teléfono, un iPad en Slide Over, en una ventana de escritorio o de navegador estrecha, o en una columna estrecha de una ancha) presenta la misma disposición para una sola pantalla, igual que las vistas divididas del iPad colapsan sus columnas y el panel de apoyo de Material Design se convierte en una hoja inferior:
- El centro muestra un grupo a la vez, a tamaño completo. Su barra de pestañas lista todos los paneles visibles del centro, y al seleccionar un panel de otro grupo ese grupo pasa a ser el que se muestra.
- Los docks izquierdo y derecho son cajones sobre el centro desde su borde, tan anchos como el dock, y el dock inferior es una
hoja tan alta como el dock. Se muestra uno a la vez, cuando la app lo presenta (
toggleDock,setDockOpen, que descartan el otro); se desliza al entrar y al salir, y una pulsación en su fondo, Escape o atrás lo descartan. Al entrar en la presentación compacta no se muestra ninguno. - Una pestaña arrastrada (con un dedo tras una pulsación larga, con un puntero de inmediato) se mueve dentro de la barra de pestañas para reordenarla.
Todos los paneles permanecen colocados en ambas presentaciones, de modo que al cambiar se conserva su contenido. El cajón o la hoja mostrados son
propios de la presentación compacta: el open de la disposición pertenece a la presentación completa, y presentar o descartar un
cajón no informa ningún onLayoutChange, de modo que un área que vuelve a ensancharse muestra los docks exactamente como estaban.
onDockOpenChange informa ambos: un dock cuyo open de la disposición cambia en la presentación completa, un cajón o una hoja mostrados
o descartados en la compacta, y cada dock cuyo estado de visibilidad cambia cuando cambia la presentación. En
renderDock, open indica si el dock se muestra en la presentación actual. La disposición sigue siendo el mismo valor,
de modo que un área que cruza los 600 puntos (un plegable que se abre, un iPad que entra en Split View, una ventana o una columna que cambia de tamaño)
mantiene cada panel donde estaba, y la app guarda una sola disposición. El ancho del área decide una vez que está maquetada (el
ancho de la ventana lo sustituye antes de eso), y compact elige la presentación en lugar del ancho. Los
cajones, la hoja y el fondo son partes a las que la app da estilo (drawerClass, sheetClass, backdropClass), igual que el
resto del área.
<DockArea
layout={layout()}
onLayoutChange={(e) => layout.set(e.layout)}
drawerClass="bg-background border-r border-border"
sheetClass="rounded-t-(--radius-lg) bg-background"
backdropClass="bg-black/40"
renderTab={(tab) => <Text>{titles[tab.panel]}</Text>}
renderPanel={(panel) => <Editor file={panel.id} />}
/>
#Props
| Prop | Rust | Tipo | Predeterminado | Descripción |
|---|---|---|---|---|
layout |
.layout(..) |
DockAreaLayout |
ninguno | La disposición mostrada (controlada): devuelve lo que informa onLayoutChange. |
defaultLayout |
.default_layout(..) |
DockAreaLayout |
un centro vacío | La disposición inicial, cuando no se define layout; a partir de entonces el área conserva la suya. |
locked |
.locked(..) |
boolean |
false |
Ninguna pestaña se arrastra, se suelta ni se cierra. |
compact |
.compact(..) |
boolean |
según el ancho | La presentación para teléfono (true) o la completa (false); sin esta prop, compacta mientras el área sea más estrecha que 600 puntos. |
panels |
.panels(..) |
DockPanelOptions[] |
[] |
Lo que permite cada panel, por clave: closable, zoomable, visible. |
renderPanel |
.render_panel(|panel| ..) |
(panel: DockPanel) => JSX.Element |
ninguno | El contenido de un panel, por su clave (panel.id). |
renderTab |
.render_tab(|tab| ..) |
(tab: DockTab) => JSX.Element |
la clave | Lo que muestra una pestaña. |
renderDropIndicator |
.render_drop_indicator(|drop| ..) |
(drop: DockDropIndicator) => JSX.Element |
ninguno | Lo que va dentro del indicador de colocación: group, side, index. |
renderDock |
.render_dock(|dock| ..) |
(dock: DockInfo) => JSX.Element |
ninguno | La estructura visual propia de un dock, antes de su disposición: placement, open (se muestra en la presentación actual), size, collapsible. |
renderEmpty |
.render_empty(|placement| ..) |
(placement: DockPlacement) => JSX.Element |
ninguno | Lo que muestra un centro vacío. |
renderDragPreview |
.render_drag_preview(|tab| ..) |
(tab: DockTab) => JSX.Element |
ninguno | Lo que sigue al puntero o al dedo mientras se arrastra una pestaña. |
groupClass, groupStyle |
.group_style(|s| ..) |
string, StyleProp<ViewStyle> |
ninguno | El marco de cada grupo de pestañas. |
tabBarClass, tabBarStyle |
.tab_bar_style(|s| ..) |
string, StyleProp<ViewStyle> |
ninguno | Cada barra de pestañas (flex-row para una fila). |
tabClass, tabStyle |
.tab_style(|s| ..) |
string, StyleProp<ViewStyle> |
ninguno | Cada pestaña; selected: para la que se muestra. |
contentClass, contentStyle |
.content_style(|s| ..) |
string, StyleProp<ViewStyle> |
ninguno | El marco de contenido de cada grupo, donde se muestran sus paneles. |
dropIndicatorClass, dropIndicatorStyle |
.drop_indicator_style(|s| ..) |
string, StyleProp<ViewStyle> |
ninguno | El indicador de colocación. |
handleClass, handleStyle |
.handle_style(|s| ..) |
string, StyleProp<ViewStyle> |
ninguno | El indicador de cada tirador, entre los grupos de una división y en el borde de cada dock; group-hover: y group-active: siguen al tirador. Mide 1 punto a lo ancho del tirador y recorre toda su longitud salvo que se indique otra cosa, de modo que bg-border es una línea fina en ambos casos; w-[3px] / h-[3px] la engrosan. |
handleThumbClass, handleThumbStyle |
.handle_thumb_class(..), .handle_thumb_style(|s| ..) |
string, StyleProp<ViewStyle> |
ninguno | El pulgar de cada tirador: un agarre centrado en la línea, dibujado en vertical como en una línea vertical y girado un cuarto de vuelta en una horizontal, de modo que una sola clase sirve para ambos ejes. Sigue siendo la misma vista mientras el tirador tiene el cursor encima, está pulsado o se arrastra, así que las clases group-hover: y group-active: lo hacen transitar (h-11 w-[3px] rounded-full bg-muted-foreground opacity-0 scale-y-0 transition group-hover:opacity-40 group-hover:scale-y-[0.45] group-active:opacity-90 group-active:scale-y-100). |
dockClass, dockStyle |
.dock_style(|s| ..) |
string, StyleProp<ViewStyle> |
ninguno | El marco de cada dock. |
drawerClass, drawerStyle |
.drawer_style(|s| ..) |
string, StyleProp<ViewStyle> |
ninguno | Compacta: los cajones de los docks laterales. |
sheetClass, sheetStyle |
.sheet_style(|s| ..) |
string, StyleProp<ViewStyle> |
ninguno | Compacta: la hoja del dock inferior. |
backdropClass, backdropStyle |
.backdrop_style(|s| ..) |
string, StyleProp<ViewStyle> |
ninguno | Compacta: el fondo detrás de un cajón o una hoja abiertos, cuya pulsación lo cierra. |
handleAccessibilityLabel |
.handle_accessibility_label(..) |
string |
ninguno | Lo que dicen los lectores de pantalla para cada tirador, entre los grupos de una división y en el borde de cada dock. |
ref |
.component_ref(&r) (component_ref::<DockArea>()) |
DockAreaRef |
Los métodos del área. | |
class, style |
métodos de estilo | string, StyleProp<ViewStyle> |
ninguno | El área en sí: su tamaño dentro de su padre (flex-1) y su fondo. Se mantiene dentro de sus límites y no toma ningún tamaño mínimo de su contenido. |
#Eventos
| Evento | Rust | Descripción |
|---|---|---|
onLayoutChange |
.on_layout_change(|layout: &DockAreaLayout| ..) |
La disposición cambió (un arrastre, una selección, un redimensionado, un dock abierto o cerrado, un método): { layout }, la nueva disposición completa, con todos sus ids. |
onPanelActiveChange |
.on_panel_active_change(|e: &DockAreaEvent| ..) (PanelActiveChange(panel, active)) |
{ panel, active }: un panel pasó a ser, o dejó de ser, la pestaña mostrada de su grupo (un dock cerrado no muestra ninguna). Una vez por cambio; a un panel que se va no se le avisa con false: onPanelRemove es su final. |
onPanelRemove |
.on_panel_remove(|panel: &PanelId| ..) |
{ panel }: un panel salió del área para siempre: se cerró, se eliminó o falta en una disposición nueva. Un panel que se mueve nunca lo recibe. |
onZoomChange |
.on_zoom_change(|panel: &Option<PanelId>| ..) |
Un grupo se amplió o se redujo: { panel }, el panel que muestra, o null. |
onDockOpenChange |
.on_dock_open_change(|e: &DockAreaEvent| ..) (DockOpenChange(placement, open)) |
{ placement, open }: un dock se mostró o se ocultó: cambió su disposición open en la presentación completa, se mostró o se descartó su cajón u hoja en la compacta, o cambió la presentación. |
#Partes
| Parte | Dentro de | Estilizada con | Estados |
|---|---|---|---|
| El área | class, style |
||
| Un grupo de pestañas | una región | groupClass, groupStyle |
|
| Una barra de pestañas | un grupo | tabBarClass, tabBarStyle |
|
| Una pestaña | una barra de pestañas | tabClass, tabStyle; renderTab |
selected:, active:, hover: |
| El contenido de un grupo | un grupo | contentClass, contentStyle; renderPanel |
|
| El indicador de un tirador | entre los grupos de una división, en el borde de un dock | handleClass, handleStyle |
group-hover:, group-active: |
| El pulgar de un tirador | centrado en la línea de un tirador | handleThumbClass, handleThumbStyle |
group-hover:, group-active: (pulsado hasta que termina el arrastre) |
| Un dock | un borde | dockClass, dockStyle; renderDock |
|
| El indicador de colocación | sobre un grupo durante un arrastre | dropIndicatorClass, dropIndicatorStyle; renderDropIndicator |
|
| La vista previa del arrastre | bajo el puntero o el dedo | renderDragPreview |
|
| El centro vacío | el centro | renderEmpty |
|
| Un cajón, la hoja, el fondo | sobre el centro, en la presentación compacta | drawerClass, sheetClass, backdropClass (y sus estilos) |
#Accesibilidad
- Cada pestaña es una pestaña con su estado de selección para los lectores de pantalla, igual que
Tab, y activarla la selecciona. Su etiqueta es el texto que dibujarenderTab. - Cada tirador, entre los grupos de una división y en el borde de cada dock, es un elemento ajustable con la etiqueta
handleAccessibilityLabel. Los tiradores entre los grupos de una división son los de Resizable: un lector de pantalla los mueve de 10 en 10 puntos, y lo informa cononLayoutChange. - En la presentación compacta, Escape y atrás descartan el cajón o la hoja mostrados.
Consulta Accesibilidad para ver etiquetas, roles y anuncios en general.
#Notas de plataforma
- Un puntero (un ratón, un trackpad, un lápiz que flota sobre la pantalla) arrastra una pestaña cuando se mueve 4 puntos con el botón pulsado, en todas las plataformas: un iPad con trackpad y una ventana de navegador estrecha manejada con el arrastre de un ratón se comportan como un escritorio. En macOS, Windows, Linux y GPUI toda pulsación cuenta como de puntero, incluida la de un dedo en un portátil con pantalla táctil.
- Un dedo en iOS, iPadOS y Android arrastra una pestaña tras una pulsación larga, como lo hace el arrastrar y soltar de iPadOS, de modo que un deslizamiento sobre la barra de pestañas la desplaza.
- Escritorios y web: los tiradores muestran el cursor de redimensionado de su eje.
- Teléfonos y áreas estrechas: la presentación compacta, con cajones y una hoja inferior.
- Todas las plataformas dibujan lo que la app estiliza: pestañas, barras de pestañas, tiradores, el indicador de colocación, docks, cajones y la hoja.