componente · @solu30/ui-kit/navigation
LiquidTabBar
Barra de navegación líquida: la pestaña elegida es una gota y el borde superior se hunde debajo de ella, inclinándose hacia donde viaja.
Seguir leyendo — 2 párrafos más
Se posiciona sola donde la pongas: no es fixed. Para clavarla al pie —que es su lugar natural en móvil— la envuelve el consumidor:
<div className="fixed inset-x-0 bottom-0 z-40 flex justify-center pb-[env(safe-area-inset-bottom)]">
<LiquidTabBar items={items} currentPath={pathname} renderLink={renderNextLink} />
</div>Todavía no la importa ninguno de los 15 verticales medidos.
El componente, montado
Cargando el espécimen…
export function LiquidTabBarSpecimen() {
const [ruta, setRuta] = useState('/inicio')
const items = [
{ id: 'inicio', label: 'Inicio', icon: IconHome, href: '/inicio', accent: 'var(--color-primary)' },
{ id: 'agenda', label: 'Agenda', icon: IconClock, href: '/agenda', accent: 'var(--color-info)' },
{ id: 'equipo', label: 'Equipo', icon: IconTeam, href: '/equipo', accent: 'var(--color-success)' },
{ id: 'cuenta', label: 'Cuenta', icon: IconUser, href: '/cuenta', accent: 'var(--color-warning)' },
]
return (
<Stage label="LiquidTabBar · cuatro destinos con acento propio — el bead se puede arrastrar" tall>
<div className="flex flex-col items-center gap-3 px-1 py-1">
<PhoneFrame className="w-[300px]">
<div className="flex h-[380px] flex-col overflow-hidden bg-bkg">
<div className="flex-1 space-y-2 p-3">
<div className="h-3 w-24 rounded bg-container" />
<div className="h-20 rounded-lg bg-container" />
<div className="h-20 rounded-lg bg-container" />
</div>
<div className="flex justify-center px-2 pb-3">
<LiquidTabBar
items={items}
currentPath={ruta}
onSelect={(id) => setRuta(`/${id}`)}
aria-label="Navegación del panel"
renderLink={({ href, className, children, ...resto }) => (
<span
{...resto}
role="link"
tabIndex={0}
className={className}
onClick={() => setRuta(href)}
onKeyDown={(e) => e.key === 'Enter' && setRuta(href)}
>
{children}
</span>
)}
/>
</div>
</div>
</PhoneFrame>
<p className="max-w-[400px] text-center text-[11px] text-content/60">
Arrastrá el bead a lo largo de la barra: al soltar calza en la pestaña más cercana y la
elige, igual que un click. El color viaja interpolado entre el acento que deja y el que
toma. Sin uso real todavía en ningún vertical — la procedencia es ADR-365.
</p>
</div>
</Stage>
)
}De specimens/navigation.tsx · LiquidTabBarSpecimen: el espécimen del Muestrario, no un ejemplo oficial del paquete. Muestra cómo se usa la pieza de verdad, montada contra los tipos instalados —eso es lo que vale— pero no es documentación del kit.
Cómo se usa
import { LiquidTabBar } from '@solu30/ui-kit/navigation'Props
| Prop | Requerida | Tipo | Qué hace |
|---|---|---|---|
| items | sí | LiquidTabBarItem[] | Los destinos. Pensado para 2 a 6; con más, la barra pierde el sentido (ver el bloque de arriba). |
| activeId | opcional | string | Item elegido, para usarlo controlado. |
| defaultActiveId | opcional | string | Item elegido en el primer render, para usarlo no controlado. @default el primero |
| currentPath | opcional | string | Path actual, para resolver el activo por `href`: un item queda activo si `currentPath === href` o si `currentPath` arranca con `href + '/'`. `activeId` le gana. |
| onSelect | opcional | (id: string, index: number) => void | Se dispara al elegir una pestaña, sea por click o al soltar el drag. |
| renderLink | opcional | (props: { href: string; className: string; children: React__default.ReactNode; 'aria-current'?: 'page'; }) => React__default.ReactNode | Renderer de links para el router del host (el `Link` de Next, etc.). |
| draggable | opcional | boolean | Arrastrar el bead a lo largo de la barra. Al soltar calza en la pestaña más cercana y la elige, igual que un click. @default true |
| aria-label | opcional | string | Nombre accesible de la navegación. @default 'Navegación principal' |
| className | opcional | string | — |
Ejemplo del paquete
<LiquidTabBar
items={[
{ id: 'inicio', label: 'Inicio', icon: Home, href: '/' },
{ id: 'agenda', label: 'Agenda', icon: Calendar, href: '/agenda', accent: '#f59e0b' },
{ id: 'cuenta', label: 'Cuenta', icon: User, href: '/cuenta' },
]}
currentPath={pathname}
/>
Accesibilidad
- `<nav>` con links o botones de verdad: el tabulador los recorre en orden y el
activo lleva `aria-current="page"`. No es un `tablist` —no hay paneles que
controlar— así que tampoco secuestra las flechas.
- `prefers-reduced-motion`: el bead aparece en su lugar nuevo en vez de viajar.
El movimiento acá es decorativo; sin él la barra sigue diciendo cuál está
elegida (ADR-310).
- La piel y el bead son `aria-hidden`: son la puesta en escena de un estado que
ya está dicho en el DOM.
Tokens que consume
- `--ui-bg-raised` / `--ui-bg-base`: la plancha, con su degradé de arriba abajo.
- `--ui-border` y `--ui-content-primary`: el filo superior, que es lo único que
le da silueta a una plancha oscura sobre una sala oscura — y, de paso, es el
trazo que dibuja el menisco.
- `--ui-accent`: el bead y la etiqueta, salvo que el item traiga su `accent`.
- `--color-primary-foreground`: la tinta del ícono cuando va montado en el bead.
Cada tema lo fija JUNTO a su accent (ADR-367), así que el contraste ahí
adentro no es una decisión de este componente.
- `--ui-content-muted`: el ícono mientras descansa sobre la plancha.Del @example del JSDoc de @solu30/ui-kit, sin editar: son 65 de los 541 símbolos. La sangría y los renglones en blanco son los del paquete.