componente · @solu30/ui-kit/navigation
TabbedPanel
Panel con pestañas armado por piezas: raíz, barra de pestañas, barra de herramientas y contenido.
35 sitios de import en 4 de 15 verticales — un archivo que la importa cuenta una vez, monte una instancia o veinte.
- TheCharterPanel
- carlosmartinpavon
- cmp-packages
- elliot
La pieza, con lugar de pantalla
Cargando el espécimen…
export function TabbedPanelMatrix() {
const [seccionFlota, setSeccionFlota] = useState('boats')
const [topTab, setTopTab] = useState('costos')
const [breakdownTab, setBreakdownTab] = useState('vertical')
/** `tabs` de `boats/index.tsx:78-87`. */
const tabsFlota: TabbedPanelTabItem[] = [
{ key: 'boats', label: 'Barcos', icon: <IconAnchor className="h-4 w-4" /> },
{ key: 'itineraries', label: 'Itinerarios', icon: <IconMap className="h-4 w-4" /> },
{ key: 'extras', label: 'Productos y servicios', icon: <IconGrid className="h-4 w-4" /> },
{ key: 'reviews', label: 'Reseñas', icon: <IconStar className="h-4 w-4" /> },
{ key: 'informacion', label: 'Información', icon: <IconLightbulb className="h-4 w-4" /> },
]
/** `topTabItems` y `breakdownTabItems` de `AiUsageDashboard.tsx:120-131`. */
const topTabItems: TabbedPanelTabItem[] = [
{ key: 'costos', label: 'Costos' },
{ key: 'uso', label: 'Uso' },
{ key: 'calidad', label: 'Calidad' },
{ key: 'desglose', label: 'Desglose' },
]
const breakdownTabItems: TabbedPanelTabItem[] = [
{ key: 'vertical', label: 'Por vertical' },
{ key: 'model', label: 'Por modelo' },
{ key: 'action', label: 'Por acción' },
]
// `boats/index.tsx:130-137`: las secciones que traen su propio encabezado
// interno devuelven `[]`, y entonces el Toolbar no se monta.
const accionesDeSeccion = seccionFlota === 'boats' || seccionFlota === 'itineraries'
return (
<Stage label="TabbedPanel · las dos formas reales del compuesto — Root+Tabs+Toolbar (TCP) y Root+Tabs+Content anidado (CMP)" tall wide>
<div className="flex w-full flex-col gap-6">
<div className="flex flex-col gap-1.5">
<span className="text-[10px] uppercase tracking-wide text-content/50">
Root + Tabs (underline) + Toolbar condicional — la flota de TheCharterPanel
</span>
<div className="rounded-xl border border-content/10 bg-container p-4">
<TabbedPanel.Root activeTab={seccionFlota} onChange={setSeccionFlota}>
<TabbedPanel.Tabs
tabs={tabsFlota}
activeTab={seccionFlota}
onChange={setSeccionFlota}
variant="underline"
/>
{accionesDeSeccion && (
<TabbedPanel.Toolbar className="mt-2 flex justify-end gap-2">
<Button variant="primary" size="sm" icon={IconPlus}>
{seccionFlota === 'boats' ? 'Nuevo barco' : 'Nuevo itinerario'}
</Button>
</TabbedPanel.Toolbar>
)}
</TabbedPanel.Root>
<p className="mt-3 text-[11px] text-content/60">
«Reseñas» e «Información» traen su propio encabezado con botones, así que ahí el
Toolbar no se monta: por eso desaparece al cambiar de pestaña.
</p>
</div>
</div>
<div className="flex flex-col gap-1.5">
<span className="text-[10px] uppercase tracking-wide text-content/50">
Root + Tabs (pills) + Content, con un segundo compuesto anidado adentro — el panel de
consumo de IA de carlosmartinpavon
</span>
<div className="rounded-xl border border-content/10 bg-container p-4">
<TabbedPanel.Root activeTab={topTab} onChange={setTopTab}>
<TabbedPanel.Tabs
tabs={topTabItems}
activeTab={topTab}
onChange={setTopTab}
variant="pills"
ariaLabel="AI usage sections"
/>
<TabbedPanel.Content>
{topTab === 'costos' && (
<p className="text-sm text-content/70">Costo real del período: USD 41,80.</p>
)}
{topTab === 'uso' && (
<p className="text-sm text-content/70">1,2 M de tokens en 3.140 turnos.</p>
)}
{topTab === 'calidad' && (
<p className="text-sm text-content/70">Tasa de error 0,6 % · latencia media 1.840 ms.</p>
)}
{topTab === 'desglose' && (
<TabbedPanel.Root activeTab={breakdownTab} onChange={setBreakdownTab}>
<TabbedPanel.Tabs
tabs={breakdownTabItems}
activeTab={breakdownTab}
onChange={setBreakdownTab}
variant="pills"
ariaLabel="AI usage breakdown"
/>
<TabbedPanel.Content>
<p className="text-sm text-content/70">
Desglose {breakdownTab === 'vertical' ? 'por vertical' : breakdownTab === 'model' ? 'por modelo' : 'por acción'} —
acá el vertical monta un `DataTable`.
</p>
</TabbedPanel.Content>
</TabbedPanel.Root>
)}
</TabbedPanel.Content>
</TabbedPanel.Root>
</div>
</div>
</div>
</Stage>
)
}De specimens/navigation.tsx · TabbedPanelMatrix: 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 { TabbedPanel } from '@solu30/ui-kit/navigation'Props
El catálogo no extrajo props para este símbolo, y eso admite dos lecturas: que el tipo no declare ninguna propia —heredarlas de un elemento HTML es lo normal en las piezas de composición— o que su interfaz se llame distinto y el extractor no la siga. Los filtros de columna son el segundo caso: declaran cuatro props requeridas en SelectFilterProps.
Ejemplo del paquete
// Compuesto por partes: la raíz sostiene el estado y las partes se apoyan en ella.
<TabbedPanel.Root value={activo} onValueChange={setActivo}>
<TabbedPanel.Tabs tabs={[{ key: 'datos', label: 'Datos' }]} activeTab={activo} onChange={setActivo} />
<TabbedPanel.Content value="datos"><DatosCliente /></TabbedPanel.Content>
</TabbedPanel.Root>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.