'use client' import React from 'react' import { Label } from './label' import { cn } from '../../utils/cn' export interface TabSelectorItem { /** Unique identifier for the tab */ id: string /** Display label. Optional — omit for icon-only tabs (provide `ariaLabel` for accessibility). */ label?: string /** Accessible name. Required when `label` is omitted. */ ariaLabel?: string /** Optional icon (ReactNode) displayed before the label */ icon?: React.ReactNode /** Whether this tab is disabled */ disabled?: boolean /** Optional badge element displayed after the label */ badge?: React.ReactNode } export interface TabSelectorProps { /** Currently selected tab id */ value: string /** Callback when tab selection changes */ onValueChange: (value: string) => void /** Tab items to display */ items: TabSelectorItem[] /** Visual variant: primary (accent bg) or secondary (soft grey bg) */ variant?: 'primary' | 'secondary' /** Optional label displayed above the selector */ label?: string /** Disable the entire selector (overrides per-item enabled state) */ disabled?: boolean /** * Horizontal-scroll mode for unbounded/dynamic tab sets (e.g. data-derived * purposes). The default layout divides the row width equally (`flex-1` * items in a `w-full` row). Use `scrollable` when labels may exceed the * available width; it sizes tabs to their labels (`flex-none`) and lets * the row scroll (`overflow-x-auto`). */ scrollable?: boolean /** Additional CSS classes for the root container */ className?: string } export function TabSelector({ value, onValueChange, items, variant = 'primary', label, disabled, scrollable = false, className, }: TabSelectorProps) { return (
{label && ( // Label for family/color; "large" (text-h4) is this component's // long-standing label scale — neighbours opt into the same scale via // their `labelVariant="large"` when a design puts them side by side. )}
{items.map((item) => { const isActive = value === item.id const isDisabled = disabled || item.disabled return ( ) })}
) }