Tabs
Pestañas en píldora cuya selección se anida en la barra y se desliza con ease-in-out.
This week · 112 orders
Instalación
$ pnpm dlx shadcn@latest add @ssntl/tabsUso
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
<Tabs defaultValue="semana">
<TabsList aria-label="Periodo del reporte">
<TabsTrigger value="dia">Día</TabsTrigger>
<TabsTrigger value="semana">Semana</TabsTrigger>
</TabsList>
<TabsContent value="semana">…</TabsContent>
</Tabs>Reglas
Lo que este componente espera de ti, tal como se instala en components/ui/tabs.md junto al código. El archivo completo agrega cuándo usarlo, teclado, los fundamentos de UI que aplica y cada regla compartida (en inglés).
## Reglas de Tabs
- **Pestañas + selección interna.** Pestañas en píldora a 4px dentro de una barra en píldora, así el radio de la selección es el de la barra menos el padding.
- **Entrada y salida suaves.** La selección se desliza en 220ms con `--ease-in-out`, porque ya está en pantalla; con movimiento reducido salta.
- La pestaña activa también cambia de peso y color, así el estado nunca depende del movimiento.
- Las pestañas miden 44px; las flechas, Inicio y Fin se mueven entre ellas (Radix).Código
"use client"
import * as React from "react"
import { Tabs as TabsPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
// Tabs (Radix): arrow keys, Home and End move between tabs, and only the
// active one is in the tab order.
// - Nested corners: a pill track with 4px of padding around pill tabs, so
// the selection's curve is the track's curve minus the padding.
// - Slow in, slow out: the selection slides to the new tab in 220ms (--duration-menu) on
// --ease-in-out, since it is already on screen moving to a new place. It
// jumps under reduced motion. The active tab also changes color, so the
// state never depends on the motion.
const Tabs = TabsPrimitive.Root
function TabsList({
className,
children,
...props
}: React.ComponentProps<typeof TabsPrimitive.List>) {
const list = React.useRef<HTMLDivElement>(null)
const [box, setBox] = React.useState<{ left: number; width: number } | null>(
null
)
React.useLayoutEffect(() => {
const el = list.current
if (!el) return
const measure = () => {
const active = el.querySelector<HTMLElement>("[data-state=active]")
if (active) setBox({ left: active.offsetLeft, width: active.offsetWidth })
}
measure()
const resize = new ResizeObserver(measure)
resize.observe(el)
const mutate = new MutationObserver(measure)
mutate.observe(el, {
attributes: true,
subtree: true,
attributeFilter: ["data-state"],
})
return () => {
resize.disconnect()
mutate.disconnect()
}
}, [])
return (
<TabsPrimitive.List
ref={list}
data-slot="tabs-list"
className={cn(
"relative isolate inline-flex max-w-full [scrollbar-width:none] items-center gap-1 overflow-x-auto rounded-full bg-secondary p-1",
className
)}
{...props}
>
{box && (
<span
aria-hidden
className="absolute inset-y-1 left-0 -z-10 rounded-full bg-card elevation-1 transition-[translate,width] duration-(--duration-menu) ease-in-out motion-reduce:transition-none"
style={{ translate: `${box.left}px 0`, width: box.width }}
/>
)}
{children}
</TabsPrimitive.List>
)
}
function TabsTrigger({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
return (
<TabsPrimitive.Trigger
data-slot="tabs-trigger"
className={cn(
"inline-flex h-11 shrink-0 cursor-pointer items-center justify-center gap-2 rounded-full px-4 text-label font-normal whitespace-nowrap text-muted-foreground",
"transition-[color] duration-(--duration-fast) ease-out hover:text-foreground data-[state=active]:font-medium data-[state=active]:text-foreground",
"focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-40",
className
)}
{...props}
/>
)
}
function TabsContent({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Content>) {
return (
<TabsPrimitive.Content
data-slot="tabs-content"
className={cn(
"rounded-xs pt-4 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring",
className
)}
{...props}
/>
)
}
export { Tabs, TabsContent, TabsList, TabsTrigger }