{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "tabs",
  "type": "registry:ui",
  "title": "Tabs",
  "description": "Tabs navigation primitives for Tile UI.",
  "devDependencies": [
    "sass"
  ],
  "registryDependencies": [
    "@tile-ui/core",
    "@tile-ui/styles"
  ],
  "files": [
    {
      "path": "packages/react/src/components/tabs/tabs.tsx",
      "type": "registry:ui",
      "target": "components/ui/tabs/tabs.tsx",
      "content": "import React, { createContext, useContext, useEffect, useId, useLayoutEffect, useRef, useState } from 'react';\nimport { getTabsListVariantKey, getTabsState, observeTabsIndicator, tabsStyleKeys } from '../lib/core';\nimport type { TabsBaseProps, TabsListBaseProps, TabsTriggerBaseProps, TabsContentBaseProps } from '../lib/core';\nimport styles from './tabs.module.scss';\n\ninterface TabsContextValue {\n\tvalue: string;\n\torientation: 'horizontal' | 'vertical';\n\tbaseId: string;\n\tselect: (value: string) => void;\n}\n\nconst TabsContext = createContext<TabsContextValue | null>(null);\nconst useBrowserLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect;\n\nfunction useTabsContext(): TabsContextValue {\n\tconst context = useContext(TabsContext);\n\tif (!context) {\n\t\tthrow new Error('Tabs sub-components must be used within <Tabs>.');\n\t}\n\treturn context;\n}\n\nexport interface TabsProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'value' | 'defaultValue'>, TabsBaseProps {}\n\nconst Tabs = React.forwardRef<HTMLDivElement, TabsProps>(({ className = '', value, defaultValue = '', orientation = 'horizontal', onValueChange, children, ...props }, ref) => {\n\tconst [internalValue, setInternalValue] = useState(defaultValue);\n\tconst currentValue = value !== undefined ? value : internalValue;\n\tconst baseId = useId();\n\n\tfunction select(next: string) {\n\t\tif (value === undefined) {\n\t\t\tsetInternalValue(next);\n\t\t}\n\t\tonValueChange?.(next);\n\t}\n\n\treturn (\n\t\t<TabsContext.Provider value={{ value: currentValue, orientation, baseId, select }}>\n\t\t\t<div ref={ref} data-orientation={orientation} className={`${styles[tabsStyleKeys.root]} ${className}`} {...props}>\n\t\t\t\t{children}\n\t\t\t</div>\n\t\t</TabsContext.Provider>\n\t);\n});\nTabs.displayName = 'Tabs';\n\nexport interface TabsListProps extends React.HTMLAttributes<HTMLDivElement>, TabsListBaseProps {}\n\nconst TabsList = React.forwardRef<HTMLDivElement, TabsListProps>(({ className = '', variant = 'default', children, onKeyDown, ...props }, ref) => {\n\tconst context = useTabsContext();\n\tconst variantKey = getTabsListVariantKey(variant);\n\tconst isVertical = context.orientation === 'vertical';\n\tconst listRef = useRef<HTMLDivElement>(null);\n\tconst indicatorRef = useRef<HTMLSpanElement>(null);\n\tconst measurement = useRef<ReturnType<typeof observeTabsIndicator> | null>(null);\n\n\tuseBrowserLayoutEffect(() => {\n\t\tif (!listRef.current || !indicatorRef.current) return;\n\t\tconst observer = observeTabsIndicator(listRef.current, indicatorRef.current);\n\t\tmeasurement.current = observer;\n\t\treturn () => {\n\t\t\tobserver.disconnect();\n\t\t\tmeasurement.current = null;\n\t\t};\n\t}, []);\n\tuseBrowserLayoutEffect(() => measurement.current?.update());\n\n\tfunction handleKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {\n\t\tonKeyDown?.(event);\n\n\t\tconst nextKey = isVertical ? 'ArrowDown' : 'ArrowRight';\n\t\tconst prevKey = isVertical ? 'ArrowUp' : 'ArrowLeft';\n\t\tconst isNext = event.key === nextKey;\n\t\tconst isPrev = event.key === prevKey;\n\t\tconst isHome = event.key === 'Home';\n\t\tconst isEnd = event.key === 'End';\n\n\t\tif (!isNext && !isPrev && !isHome && !isEnd) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst triggers = Array.from(event.currentTarget.querySelectorAll<HTMLButtonElement>('button[role=\"tab\"]')).filter((trigger) => !trigger.disabled);\n\n\t\tif (triggers.length === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst currentIndex = triggers.indexOf(document.activeElement as HTMLButtonElement);\n\t\tlet nextIndex: number;\n\t\tif (isHome) {\n\t\t\tnextIndex = 0;\n\t\t} else if (isEnd) {\n\t\t\tnextIndex = triggers.length - 1;\n\t\t} else {\n\t\t\tconst direction = isNext ? 1 : -1;\n\t\t\tnextIndex = (currentIndex + direction + triggers.length) % triggers.length;\n\t\t}\n\n\t\tconst nextTrigger = triggers[nextIndex];\n\t\tnextTrigger?.focus();\n\n\t\t// 自动激活：方向键移动焦点时同步选中对应标签页。\n\t\tconst nextValue = nextTrigger?.getAttribute('data-value');\n\t\tif (nextValue !== null && nextValue !== undefined) {\n\t\t\tcontext.select(nextValue);\n\t\t}\n\n\t\tevent.preventDefault();\n\t}\n\n\treturn (\n\t\t<div\n\t\t\tref={(element) => {\n\t\t\t\tlistRef.current = element;\n\t\t\t\tif (typeof ref === 'function') return ref(element);\n\t\t\t\tif (ref) ref.current = element;\n\t\t\t}}\n\t\t\trole=\"tablist\"\n\t\t\tdata-orientation={context.orientation}\n\t\t\tdata-variant={variant}\n\t\t\tonKeyDown={handleKeyDown}\n\t\t\tclassName={`${styles[tabsStyleKeys.list]} ${styles[variantKey]} ${className}`}\n\t\t\t{...props}>\n\t\t\t{children}\n\t\t\t<span ref={indicatorRef} aria-hidden=\"true\" data-slot=\"tabs-indicator\" data-state=\"hidden\" className={styles.indicator} />\n\t\t</div>\n\t);\n});\nTabsList.displayName = 'TabsList';\n\nexport interface TabsTriggerProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'value'>, TabsTriggerBaseProps {\n\tvalue: string;\n}\n\nconst TabsTrigger = React.forwardRef<HTMLButtonElement, TabsTriggerProps>(({ className = '', value, disabled = false, children, onClick, ...props }, ref) => {\n\tconst context = useTabsContext();\n\tconst active = context.value === value;\n\tconst state = getTabsState(active);\n\tconst triggerId = `${context.baseId}-trigger-${value}`;\n\tconst contentId = `${context.baseId}-content-${value}`;\n\n\tfunction handleClick(event: React.MouseEvent<HTMLButtonElement>) {\n\t\tcontext.select(value);\n\t\tonClick?.(event);\n\t}\n\n\treturn (\n\t\t<button\n\t\t\tref={ref}\n\t\t\ttype=\"button\"\n\t\t\tid={triggerId}\n\t\t\trole=\"tab\"\n\t\t\taria-selected={active}\n\t\t\taria-controls={contentId}\n\t\t\tdata-value={value}\n\t\t\ttabIndex={active ? 0 : -1}\n\t\t\tdisabled={disabled}\n\t\t\tdata-state={state}\n\t\t\tdata-orientation={context.orientation}\n\t\t\tclassName={`${styles[tabsStyleKeys.trigger]} ${className}`}\n\t\t\tonClick={handleClick}\n\t\t\t{...props}>\n\t\t\t{children}\n\t\t</button>\n\t);\n});\nTabsTrigger.displayName = 'TabsTrigger';\n\nexport interface TabsContentProps extends React.HTMLAttributes<HTMLDivElement>, TabsContentBaseProps {\n\tvalue: string;\n}\n\nconst TabsContent = React.forwardRef<HTMLDivElement, TabsContentProps>(({ className = '', value, children, ...props }, ref) => {\n\tconst context = useTabsContext();\n\tconst active = context.value === value;\n\tconst state = getTabsState(active);\n\tconst triggerId = `${context.baseId}-trigger-${value}`;\n\tconst contentId = `${context.baseId}-content-${value}`;\n\n\treturn (\n\t\t<div\n\t\t\t{...props}\n\t\t\tref={ref}\n\t\t\tid={contentId}\n\t\t\trole=\"tabpanel\"\n\t\t\taria-labelledby={triggerId}\n\t\t\tdata-state={state}\n\t\t\tdata-orientation={context.orientation}\n\t\t\thidden={!active}\n\t\t\tclassName={`${styles[tabsStyleKeys.content]} ${className}`}>\n\t\t\t{children}\n\t\t</div>\n\t);\n});\nTabsContent.displayName = 'TabsContent';\n\nexport { Tabs, TabsList, TabsTrigger, TabsContent };\nexport default Tabs;\n"
    },
    {
      "path": "packages/styles/scss/components/tabs.module.scss",
      "type": "registry:file",
      "target": "components/ui/tabs/tabs.module.scss",
      "content": "@use '../../../styles/variables/colors' as *;\n\n.root {\n\tdisplay: flex;\n\tgap: var(--tile-tabs-gap, $spacing-2);\n\tfont-weight: var(--tile-tabs-root-font-weight, inherit);\n\n\t&[data-orientation='horizontal'] {\n\t\tflex-direction: column;\n\t}\n\n\t&[data-orientation='vertical'] {\n\t\tflex-direction: row;\n\t}\n}\n\n.list {\n\tposition: var(--tile-tabs-list-position, static);\n\tisolation: var(--tile-tabs-list-isolation, auto);\n\tdisplay: inline-flex;\n\twidth: fit-content;\n\talign-items: var(--tile-tabs-list-align, center);\n\tjustify-content: center;\n\tborder-radius: var(--tile-tabs-list-radius, $radius-lg);\n\tpadding: var(--tile-tabs-list-padding, 3px);\n\tbackground-color: var(--tile-tabs-list-background, $muted);\n\tcolor: var(--tile-tabs-list-foreground, var(--tile-tabs-foreground, $muted-foreground));\n\tbox-shadow: var(--tile-tabs-list-shadow, none);\n\ttransition: background-color $transition-fast;\n\toutline: none;\n\n\t&::before {\n\t\tcontent: var(--tile-tabs-track-ring-content, none);\n\t\tposition: absolute;\n\t\tinset: 0;\n\t\tborder-radius: inherit;\n\t\tbox-shadow: var(--tile-tabs-track-ring-shadow, none);\n\t\tpointer-events: none;\n\t}\n\n\t&:focus-visible {\n\t\tbox-shadow: var(--tile-tabs-focus-shadow, 0 0 0 2px $background, 0 0 0 4px $ring);\n\t}\n\n\t&[data-orientation='horizontal'] {\n\t\theight: var(--tile-tabs-list-height, 2.25rem);\n\t}\n\n\t&[data-orientation='vertical'] {\n\t\theight: fit-content;\n\t\tflex-direction: column;\n\t}\n\n\t// 变体\n\t&.variantLine {\n\t\tgap: var(--tile-tabs-line-gap, $spacing-1);\n\t\tbackground-color: transparent;\n\t\tborder-radius: 0;\n\t\tpadding: var(--tile-tabs-line-padding, 3px);\n\t\tbox-shadow: var(--tile-tabs-line-shadow, none);\n\t\tborder-bottom: var(--tile-tabs-line-border-bottom, 0 solid transparent);\n\t\tborder-color: var(--tile-tabs-line-border-color, currentcolor);\n\n\t\t&::before {\n\t\t\tcontent: none;\n\t\t}\n\n\t\t&[data-orientation='horizontal'] {\n\t\t\theight: var(--tile-tabs-line-height, 2.25rem);\n\t\t}\n\n\t\t> .trigger[data-state='active'] {\n\t\t\tbackground-color: transparent;\n\t\t\tbox-shadow: none;\n\n\t\t\t&::after {\n\t\t\t\tdisplay: var(--tile-tabs-active-pseudo-display, block);\n\t\t\t\tcontent: '';\n\t\t\t\tposition: absolute;\n\t\t\t\tbackground-color: var(--tile-tabs-line-color, $foreground);\n\t\t\t}\n\t\t}\n\n\t\t&[data-orientation='horizontal'] > .trigger[data-state='active']::after {\n\t\t\tleft: 0;\n\t\t\tright: 0;\n\t\t\tbottom: -5px;\n\t\t\theight: 2px;\n\t\t}\n\n\t\t&[data-orientation='vertical'] > .trigger[data-state='active']::after {\n\t\t\ttop: 0;\n\t\t\tbottom: 0;\n\t\t\tright: -4px;\n\t\t\twidth: 2px;\n\t\t}\n\t}\n}\n\n.trigger {\n\tposition: relative;\n\tz-index: var(--tile-tabs-trigger-z-index, auto);\n\tdisplay: inline-flex;\n\tflex: var(--tile-tabs-trigger-flex, 1);\n\talign-items: center;\n\tjustify-content: center;\n\tgap: $spacing-1_5;\n\tborder: var(--tile-tabs-trigger-border-width, 1px) solid transparent;\n\tborder-radius: var(--tile-tabs-trigger-radius, $radius-md);\n\tpadding: var(--tile-tabs-trigger-padding, $spacing-1 $spacing-2);\n\tmargin: var(--tile-tabs-trigger-margin, 0);\n\tfont-size: var(--tile-tabs-font-size, $text-sm);\n\tfont-weight: var(--tile-tabs-font-weight, $font-medium);\n\twhite-space: nowrap;\n\tcolor: var(--tile-tabs-foreground, $muted-foreground);\n\ttransition:\n\t\tcolor $transition-fast,\n\t\tbox-shadow $transition-fast;\n\toutline: none;\n\tuser-select: none;\n\n\t&:hover {\n\t\tcolor: var(--tile-tabs-active-foreground, $foreground);\n\t}\n\n\t&:focus-visible {\n\t\tborder-color: var(--tile-tabs-focus-border, $ring);\n\t\tbox-shadow: var(--tile-tabs-trigger-focus-shadow, 0 0 0 3px color-mix(in srgb, $ring 50%, transparent));\n\t}\n\n\t&:disabled {\n\t\tpointer-events: none;\n\t\topacity: 0.5;\n\t}\n\n\t&[data-state='active'] {\n\t\tbackground-color: var(--tile-tabs-active-background, $background);\n\t\tcolor: var(--tile-tabs-active-foreground, $foreground);\n\t\tbox-shadow: var(--tile-tabs-active-shadow, $shadow-sm);\n\t}\n\n\t&[data-orientation='vertical'] {\n\t\twidth: 100%;\n\t\tjustify-content: flex-start;\n\t}\n\n\tsvg {\n\t\tpointer-events: none;\n\t\twidth: 1rem;\n\t\theight: 1rem;\n\t\tflex-shrink: 0;\n\t}\n}\n\n.content {\n\tflex: 1;\n\tborder-radius: var(--tile-tabs-content-radius, $radius);\n\toutline: none;\n\n\t&:focus-visible {\n\t\tbox-shadow: var(--tile-tabs-trigger-focus-shadow, 0 0 0 3px color-mix(in srgb, $ring 50%, transparent));\n\t}\n}\n\n// One independent indicator per list. It is inert in the original Tile theme.\n.indicator {\n\tdisplay: var(--tile-tabs-indicator-display, none);\n\tposition: absolute;\n\tpointer-events: none;\n\tz-index: 0;\n\tleft: 0;\n\ttop: 0;\n\twidth: var(--tile-tabs-indicator-width, 0px);\n\theight: var(--tile-tabs-indicator-height, 0px);\n\ttransform: translate(var(--tile-tabs-indicator-left, 0px), var(--tile-tabs-indicator-top, 0px));\n\tborder-radius: var(--tile-tabs-indicator-radius, $radius-md);\n\tbackground-color: var(--tile-tabs-indicator-background, $background);\n\tbox-shadow: var(--tile-tabs-indicator-shadow, $shadow-sm);\n\ttransition:\n\t\ttransform 200ms ease,\n\t\twidth 200ms ease,\n\t\theight 200ms ease;\n\n\t&[data-state='hidden'] {\n\t\tvisibility: hidden;\n\t}\n}\n\n.variantLine {\n\t> .trigger {\n\t\tpadding: var(--tile-tabs-line-trigger-padding, $spacing-1 $spacing-2);\n\t\tmargin: var(--tile-tabs-line-trigger-margin, 0);\n\t\tborder-radius: var(--tile-tabs-line-trigger-radius, $radius-md);\n\n\t\t&:hover {\n\t\t\tbackground-color: var(--tile-tabs-line-hover-background, transparent);\n\t\t}\n\t}\n\n\t&[data-orientation='horizontal'] > .indicator {\n\t\ttop: auto;\n\t\tbottom: 0;\n\t\theight: 2px;\n\t\ttransform: translateX(var(--tile-tabs-indicator-left, 0px));\n\t\tborder-radius: 0;\n\t\tbackground-color: var(--tile-tabs-line-color, $foreground);\n\t\tbox-shadow: none;\n\t}\n\n\t&[data-orientation='vertical'] > .indicator {\n\t\tleft: auto;\n\t\tinset-inline-end: 0;\n\t\twidth: 2px;\n\t\ttransform: translateY(var(--tile-tabs-indicator-top, 0px));\n\t\tborder-radius: 0;\n\t\tbackground-color: var(--tile-tabs-line-color, $foreground);\n\t\tbox-shadow: none;\n\t}\n}\n\n// Selected-state shadows otherwise override keyboard focus, especially in line lists.\n.trigger[data-state='active']:focus-visible {\n\tbox-shadow: var(--tile-tabs-trigger-focus-shadow, var(--tile-tabs-active-shadow, $shadow-sm));\n}\n\n.list.variantLine > .trigger[data-state='active']:focus-visible {\n\tbox-shadow: var(--tile-tabs-trigger-focus-shadow, none);\n}\n\n@media (prefers-reduced-motion: reduce) {\n\t.indicator {\n\t\ttransition: none;\n\t}\n}\n"
    },
    {
      "path": "packages/react/src/components/tabs/index.ts",
      "type": "registry:ui",
      "target": "components/ui/tabs/index.ts",
      "content": "export { Tabs, TabsList, TabsTrigger, TabsContent, type TabsProps, type TabsListProps, type TabsTriggerProps, type TabsContentProps } from './tabs';\n"
    }
  ]
}
