{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "tabs",
  "title": "Tabs",
  "description": "Tabs from the Get IT Done design system.",
  "dependencies": [
    "@radix-ui/react-tabs@^1.2.1",
    "class-variance-authority@^0.7.0"
  ],
  "registryDependencies": [
    "https://zero-design.itdone.de/r/labels-context.json",
    "https://zero-design.itdone.de/r/utils.json",
    "https://zero-design.itdone.de/r/theme.json"
  ],
  "files": [
    {
      "path": "registry/ui/tabs.tsx",
      "content": "'use client';\n\nimport * as TabsPrimitive from '@radix-ui/react-tabs';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport * as React from 'react';\n\nimport { cn } from '@/lib/utils';\nimport { usePreset } from '@/components/ui/labels-context';\n\n/**\n * `segmented` (default): pill-in-track look, the active trigger is a raised `bg-background` chip.\n * `line`: transparent track with a hairline baseline; the active trigger gets a 2px bottom bar.\n * `segment`: segmented control with control-height triggers, the `tab` type role and an `elevation-surface` active chip.\n */\nconst tabsListVariants = cva('inline-flex h-10 items-center justify-center text-muted-foreground', {\n  variants: {\n    variant: {\n      // Track padding is 3px outside zero (the 0.16 value, no spacing token exists for it) and the 2px micro token in zero (see TabsList).\n      segmented: 'rounded-control bg-muted p-[3px]',\n      // Segmented control (zero): 2px track padding and gap, control radius, auto height (trigger sets it).\n      segment: 'h-auto justify-start gap-micro rounded-control bg-muted p-micro',\n      line: 'h-auto max-w-full flex-wrap justify-start gap-x-4 rounded-none border-b border-solid border-hairline bg-transparent p-0',\n      // Vertical nav list (Figma \"Vertical tabs\"): stacked full-width triggers. Pair with `<Tabs orientation=\"vertical\">`.\n      vertical: 'h-auto flex-col items-stretch justify-start gap-1 rounded-none bg-transparent p-0',\n    },\n  },\n  defaultVariants: { variant: 'segmented' },\n});\n\n// Zero preset overrides of the line list, applied after the variant classes.\nconst LINE_LIST_ZERO = 'gap-x-5 border-border-default';\n\nconst tabsTriggerVariants = cva(\n  'inline-flex items-center justify-center whitespace-nowrap text-muted-foreground transition-[color,background-color,border-color,box-shadow] duration-150 hover:text-foreground focus-visible:outline-none focus-visible:ring-focus focus-visible:ring-offset-focus focus-visible:ring-offset-background focus-visible:ring-ring-default disabled:pointer-events-none disabled:opacity-disabled data-[state=active]:text-foreground',\n  {\n    variants: {\n      variant: {\n        segmented:\n          'px-3 py-[6px] text-sm leading-normal font-medium rounded-item data-[state=active]:bg-background data-[state=active]:shadow-xs',\n        segment:\n          'h-control-sm shrink-0 gap-gap-control rounded-item px-control-px-sm typography-tab data-[state=active]:bg-background data-[state=active]:shadow-elevation-surface',\n        // Vertical tab: left aligned, 32px high, `tab` type role, brand-subtle fill when active.\n        vertical:\n          'h-8 w-full justify-start gap-2 rounded-item px-3 typography-tab [&>svg]:h-icon-nav [&>svg]:w-icon-nav [&>svg]:shrink-0 data-[state=active]:bg-background-brand-subtle data-[state=active]:text-general-brand-on-container',\n        line: '-mb-px rounded-none border-b-2 border-solid border-transparent px-1 py-[6px] pb-2 data-[state=active]:border-foreground',\n      },\n      // Product preset (DesignSystemProvider `preset`). `zero` gives the line tab a 40px row with the tab role and the vertical tab the muted active row.\n      preset: { default: '', zero: '' },\n      size: { sm: '', md: '' },\n    },\n    compoundVariants: [\n      { variant: 'line', preset: 'default', className: 'text-sm leading-normal font-medium' },\n      // Outside zero the segmented tab is 32px, so the track (3px padding) is 38px like the segmented ToggleGroup.\n      { variant: 'segmented', preset: 'default', className: 'h-8 py-0' },\n      // Zero: the count span carries no margin, so the trigger gap (6px token) spaces it from the label in every variant and size.\n      { variant: ['segmented', 'segment', 'line'], preset: 'zero', className: 'gap-gap-control' },\n      { variant: 'line', preset: 'zero', className: 'h-10 px-0 py-0 typography-tab' },\n      // Segment tab height by list size: `sm` is a 28px list in zero (24px tabs), `md` keeps the control-sm tab.\n      { variant: 'segment', size: 'sm', className: 'h-control-xs' },\n      { variant: 'vertical', preset: 'zero', className: 'h-nav-item data-[state=active]:bg-muted data-[state=active]:text-foreground' },\n    ],\n    defaultVariants: { variant: 'segmented', preset: 'default', size: 'md' },\n  },\n);\n\ntype TabsVariantContextValue = NonNullable<VariantProps<typeof tabsListVariants>['variant']>;\n\n// The list owns the variant; triggers read it from context (and may override with their own prop).\nconst TabsVariantContext = React.createContext<TabsVariantContextValue>('segmented');\n// Size of the segment list, read by the triggers.\nconst TabsSizeContext = React.createContext<'sm' | 'md' | undefined>(undefined);\n\nconst Tabs = TabsPrimitive.Root;\n\nconst TabsList = React.forwardRef<\n  React.ElementRef<typeof TabsPrimitive.List>,\n  React.ComponentPropsWithoutRef<typeof TabsPrimitive.List> &\n    VariantProps<typeof tabsListVariants> & {\n      // Zero: `sm` is a 28px list (24px tabs inside 2px padding), `md` the control-sm tab, default `sm`.\n      // Outside zero, segmented and segment lists: `sm` is as tall as a Button sm (32px) and `md` as a default Button (40px), with 3px track padding. Without `size` the list keeps its legacy height (segmented 38px, segment 36px).\n      size?: 'sm' | 'md';\n    }\n>(({ className, variant, size, ...props }, ref) => {\n  const zero = usePreset() === 'zero';\n  // Zero: the plain list is the segmented control.\n  const resolved = variant ?? (zero ? 'segment' : 'segmented');\n  return (\n    <TabsVariantContext.Provider value={resolved}>\n      <TabsSizeContext.Provider value={size ?? (zero ? 'sm' : undefined)}>\n      <TabsPrimitive.List ref={ref} className={cn(tabsListVariants({ variant: resolved }), zero && resolved === 'segmented' && 'p-micro', !zero && resolved === 'segmented' && 'h-auto',\n          // Sized track outside zero: total height is the Button control token, the triggers fill it (see TabsTrigger).\n          !zero && size && (resolved === 'segmented' || resolved === 'segment') && cn('p-[3px]', size === 'sm' ? 'h-control-sm' : 'h-control-md'), zero && resolved === 'line' && LINE_LIST_ZERO, className)} {...props} />\n      </TabsSizeContext.Provider>\n    </TabsVariantContext.Provider>\n  );\n});\nTabsList.displayName = TabsPrimitive.List.displayName;\n\n/**\n * Count slot convention: `<TabsTrigger count={3}>Open</TabsTrigger>` renders a `TabsCount` after the\n * label. Use `TabsCount` directly for custom content (e.g. `99+`). The count is muted at rest and\n * follows the trigger on the active tab (`group-data-[state=active]`). Tabular figures keep the\n * trigger width stable while the number changes.\n */\nconst TabsCount = React.forwardRef<\n  HTMLSpanElement,\n  React.HTMLAttributes<HTMLSpanElement> & {\n    variant?: TabsVariantContextValue;\n    // `destructive` marks a count that needs attention, for example a selection or error count (Figma _Tab Badge=Count destructive).\n    tone?: 'default' | 'destructive';\n  }\n>(({ className, variant, tone = 'default', ...props }, ref) => {\n  const fromList = React.useContext(TabsVariantContext);\n  const resolved = variant ?? fromList;\n  const zero = usePreset() === 'zero';\n  // The segmented track, the segment track and the active vertical row are bg-muted surfaces, so the count uses the on-muted role there.\n  const onMuted = resolved === 'segmented' || resolved === 'segment' || resolved === 'vertical';\n  const subtleCount = onMuted ? 'text-text-subtle-on-muted' : 'text-text-subtle';\n  return (\n    <span\n      ref={ref}\n      className={cn(\n        zero && resolved !== 'vertical'\n          ? cn('typography-kpi-label-strong tabular-nums', subtleCount)\n          : resolved === 'vertical'\n          ? // Pushed to the row end like a nav counter.\n            cn('ml-auto typography-kpi-label-strong tabular-nums', subtleCount)\n          : resolved === 'segment'\n          ? // Plain-text count: the trigger gap spaces it, and it stays subtle on the active tab too.\n            cn('typography-kpi-label-strong tabular-nums', subtleCount)\n          : cn('ml-[6px] text-micro font-normal leading-none tabular-nums group-data-[state=active]:text-muted-foreground', subtleCount),\n        tone === 'destructive' && (onMuted ? 'text-text-destructive-on-subtle group-data-[state=active]:text-text-destructive-on-subtle' : 'text-text-destructive group-data-[state=active]:text-text-destructive'),\n        className,\n      )}\n      {...props}\n    />\n  );\n});\nTabsCount.displayName = 'TabsCount';\n\nconst TabsTrigger = React.forwardRef<\n  React.ElementRef<typeof TabsPrimitive.Trigger>,\n  React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger> &\n    Omit<VariantProps<typeof tabsTriggerVariants>, 'preset' | 'size'> & {\n      /** Optional count rendered in a `TabsCount` slot after the label. `0` is rendered; `undefined`/`null` is not. */\n      count?: number | string | null;\n      // Tone of the count slot. `destructive` uses the destructive text role.\n      countTone?: 'default' | 'destructive';\n    }\n>(({ className, variant, count, countTone, children, ...props }, ref) => {\n  const innerRef = React.useRef<HTMLButtonElement | null>(null);\n  // Tabs used as a plain switch (no TabsContent at all, for example the view tabs of an action bar) must not point aria-controls at a panel that does not exist.\n  // With panels, inactive tabs keep it: their panel is unmounted by design.\n  React.useEffect(() => {\n    const node = innerRef.current;\n    const target = node?.getAttribute('aria-controls');\n    if (!node || !target || node.ownerDocument.getElementById(target)) return;\n    const list = node.closest('[role=tablist]');\n    const root = list?.parentElement?.closest('[data-orientation]') ?? list?.parentElement;\n    if (!root?.querySelector('[role=tabpanel]')) node.removeAttribute('aria-controls');\n  });\n  const setRefs = React.useCallback(\n    (node: HTMLButtonElement | null) => {\n      innerRef.current = node;\n      if (typeof ref === 'function') ref(node);\n      else if (ref) (ref as React.MutableRefObject<HTMLButtonElement | null>).current = node;\n    },\n    [ref],\n  );\n  const fromList = React.useContext(TabsVariantContext);\n  const listSize = React.useContext(TabsSizeContext);\n  const zero = usePreset() === 'zero';\n  const resolvedVariant = variant ?? fromList;\n  // Sized list outside zero: the trigger fills the track height (control token minus the 3px padding).\n  const fillsTrack = !zero && listSize !== undefined && (resolvedVariant === 'segmented' || resolvedVariant === 'segment');\n  return (\n    <TabsPrimitive.Trigger\n      ref={setRefs}\n      className={cn(\n        'group',\n        tabsTriggerVariants({ variant: resolvedVariant, preset: zero ? 'zero' : 'default', size: zero ? listSize : undefined }),\n        fillsTrack && 'h-full py-0',\n        className,\n      )}\n      {...props}\n    >\n      {children}\n      {count != null && <TabsCount variant={variant ?? fromList} tone={countTone}>\n          {count}\n        </TabsCount>}\n    </TabsPrimitive.Trigger>\n  );\n});\nTabsTrigger.displayName = TabsPrimitive.Trigger.displayName;\n\nconst TabsContent = React.forwardRef<\n  React.ElementRef<typeof TabsPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>\n>(({ className, ...props }, ref) => (\n  <TabsPrimitive.Content\n    ref={ref}\n    className={cn(\n      'mt-2 rounded-item focus-visible:outline-none focus-visible:ring-focus focus-visible:ring-offset-focus focus-visible:ring-offset-background focus-visible:ring-ring-default',\n      className,\n    )}\n    {...props}\n  />\n));\nTabsContent.displayName = TabsPrimitive.Content.displayName;\n\nexport { Tabs, TabsContent, TabsCount, TabsList, tabsListVariants, TabsTrigger, tabsTriggerVariants };\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}