{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "tabs-accent",
  "title": "Accent tabs",
  "description": "Styled tabs: the Accent set. 5 decorative tabs variations (BarTabs, GlowTabs, FillTabs, RingTabs, EdgeTabs) on the ai2 token system, powered by framer-motion, reduced-motion aware and sized sm to xl. Part of the free styled layer.",
  "dependencies": [
    "motion@^12.42.2"
  ],
  "registryDependencies": [
    "@ai2/tokens"
  ],
  "files": [
    {
      "path": "registry/ai2/styled/tabs-accent.tsx",
      "content": "\"use client\"\r\n\r\nimport * as React from \"react\"\r\nimport { motion, useReducedMotion } from \"motion/react\"\r\n\r\nimport { cn } from \"@/lib/utils\"\r\n\r\n/* Accent tabs family: 5 tab strips where the only difference is the highlight\n   language of the active tab. A thick bar, a glowing halo, a filled background, a\n   ring frame and a left-edge strip.\n   Each export is a complete tabs component - internal active-tab state,\n   role=\"tablist\"/role=\"tab\" and a role=\"tabpanel\" panel. The ids are produced with\n   React.useId(), so two examples on the same page do not collide. Color comes ONLY\n   from tokens, via alpha color-mix. The indicator slides with a framer-motion\n   layoutId; it switches instantly under reduced motion. */\r\n\r\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\r\n\r\ntype TabItem = { value: string; label: React.ReactNode; panel: React.ReactNode }\r\n\r\ntype TabsProps = {\r\n  className?: string\r\n  size?: StyledSize\r\n  tabs?: TabItem[]\r\n  value?: string\r\n  defaultValue?: string\r\n  onValueChange?: (v: string) => void\r\n}\r\n\r\nconst DEFAULT_TABS: TabItem[] = [\r\n  { value: \"overview\", label: \"Overview\", panel: \"A summary of the workspace: recent activity, open items and the team.\" },\r\n  { value: \"activity\", label: \"Activity\", panel: \"Every event from the last seven days, newest first.\" },\r\n  { value: \"settings\", label: \"Settings\", panel: \"Workspace name, members and notification preferences.\" },\r\n]\r\n\r\nconst sizeTab: Record<StyledSize, string> = {\r\n  sm: \"h-8 px-3 text-xs\",\r\n  md: \"h-9 px-4 text-sm\",\r\n  lg: \"h-10 px-5 text-sm\",\r\n  xl: \"h-11 px-6 text-base\",\r\n}\r\n\r\nconst tabBase =\r\n  \"relative z-10 inline-flex select-none items-center justify-center gap-2 whitespace-nowrap font-medium outline-none transition-colors focus-visible:ring-[3px] focus-visible:ring-ring/50 [&_svg]:size-4 [&_svg]:shrink-0 [&>svg]:shrink-0 [&_i]:text-base [&_i]:leading-none [&>i]:shrink-0\"\r\n\r\nconst panelBase =\r\n  \"rounded-lg border border-border bg-card p-4 text-sm text-muted-foreground outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50\"\r\n\r\nfunction useTabsState(props: TabsProps, fallback: TabItem[]) {\r\n  const { tabs, value, defaultValue, onValueChange } = props\r\n  const list = React.useMemo(() => (tabs && tabs.length ? tabs : fallback), [tabs, fallback])\r\n  const [internal, setInternal] = React.useState<string>(() => defaultValue ?? list[0]?.value ?? \"\")\r\n  const active = value ?? internal\r\n  const refs = React.useRef<(HTMLButtonElement | null)[]>([])\r\n  const select = React.useCallback(\r\n    (v: string) => {\r\n      if (value === undefined) setInternal(v)\r\n      onValueChange?.(v)\r\n    },\r\n    [value, onValueChange]\r\n  )\r\n  const onKeyDown = (e: React.KeyboardEvent) => {\r\n    if (e.key !== \"ArrowLeft\" && e.key !== \"ArrowRight\") return\r\n    e.preventDefault()\r\n    const idx = list.findIndex((t) => t.value === active)\r\n    const dir = e.key === \"ArrowRight\" ? 1 : -1\r\n    const ni = (idx + dir + list.length) % list.length\r\n    select(list[ni].value)\r\n    refs.current[ni]?.focus()\r\n  }\r\n  return { list, active, select, refs, onKeyDown }\r\n}\r\n\r\nfunction useSlide() {\r\n  const reduce = useReducedMotion()\r\n  return reduce\r\n    ? { duration: 0 }\r\n    : { type: \"spring\" as const, stiffness: 420, damping: 34 }\r\n}\r\n\r\ntype AccentConfig = {\r\n  bar: string\r\n  tab: string\r\n  indicator: string\r\n  active: string\r\n  inactive: string\r\n}\r\n\r\n/* Shared shell: bar plus highlight indicator plus panel. */\r\nfunction AccentTabsShell({ props, config }: { props: TabsProps; config: AccentConfig }) {\r\n  const { className, size = \"md\" } = props\r\n  const { list, active, select, refs, onKeyDown } = useTabsState(props, DEFAULT_TABS)\r\n  const uid = React.useId()\r\n  const transition = useSlide()\r\n  const current = list.find((t) => t.value === active) ?? list[0]\r\n\r\n  return (\r\n    <div data-slot=\"styled-tabs\" className={cn(\"flex flex-col gap-3\", className)}>\r\n      {/* 200% text (WCAG 1.4.4): the strip is `w-fit`, so growing text widened\n          the page. Scrolling was moved inside the strip itself; the wrapper is\n          REQUIRED because `overflow-x-auto` clips vertically too and the focus\n          ring would be cut off. The `-m-1 p-1` pair opens room for the ring and\n          preserves the outer measurements. The same fix is documented in detail\n          in registry/ai2/ui/tabs.tsx. */}\r\n      <div\r\n        data-slot=\"styled-tabs-viewport\"\r\n        className=\"-m-1 flex max-w-full overflow-x-auto p-1 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden\"\r\n      >\r\n        <div\r\n          role=\"tablist\"\r\n          aria-orientation=\"horizontal\"\r\n          onKeyDown={onKeyDown}\r\n          className={cn(\"inline-flex w-fit items-center gap-1\", config.bar)}\r\n        >\r\n          {list.map((tab, i) => {\r\n            const on = tab.value === active\r\n            return (\r\n              <button\r\n                key={tab.value}\r\n                ref={(el) => {\r\n                  refs.current[i] = el\r\n                }}\r\n                type=\"button\"\r\n                role=\"tab\"\r\n                id={`${uid}-tab-${tab.value}`}\r\n                aria-selected={on}\r\n                aria-controls={`${uid}-panel-${tab.value}`}\r\n                tabIndex={on ? 0 : -1}\r\n                onClick={() => select(tab.value)}\r\n                className={cn(tabBase, sizeTab[size], config.tab, on ? config.active : config.inactive)}\r\n              >\r\n                {on ? (\r\n                  <motion.span\r\n                    layoutId={`${uid}-accent-indicator`}\r\n                    transition={transition}\r\n                    className={cn(\"absolute -z-10\", config.indicator)}\r\n                  />\r\n                ) : null}\r\n                {tab.label}\r\n              </button>\r\n            )\r\n          })}\r\n        </div>\r\n      </div>\r\n      {current ? (\r\n        <div\r\n          role=\"tabpanel\"\r\n          id={`${uid}-panel-${current.value}`}\r\n          aria-labelledby={`${uid}-tab-${current.value}`}\r\n          tabIndex={0}\r\n          className={panelBase}\r\n        >\r\n          {current.panel}\r\n        </div>\r\n      ) : null}\r\n    </div>\r\n  )\r\n}\r\n\r\n/* Bar: aktif sekmenin altinda kalin primary cubuk kayar. */\r\nexport function BarTabs(props: TabsProps) {\r\n  return (\r\n    <AccentTabsShell\r\n      props={props}\r\n      config={{\r\n        bar: \"border-b-2 border-border\",\r\n        tab: \"-mb-0.5 rounded-t-md\",\r\n        indicator: \"inset-x-1 -bottom-0.5 h-0.5 rounded-full bg-primary\",\r\n        active: \"text-primary\",\r\n        inactive: \"text-muted-foreground hover:text-foreground\",\r\n      }}\r\n    />\r\n  )\r\n}\r\n\r\n/* Glow: aktif sekme primary hale ve tintli zeminle isilar. */\r\nexport function GlowTabs(props: TabsProps) {\r\n  return (\r\n    <AccentTabsShell\r\n      props={props}\r\n      config={{\r\n        bar: \"rounded-lg border border-border bg-card p-1\",\r\n        tab: \"rounded-md\",\r\n        indicator:\r\n          \"inset-0 rounded-md bg-[color-mix(in_oklab,var(--color-primary)_14%,transparent)] shadow-[0_0_0_1px_color-mix(in_oklab,var(--color-primary)_35%,transparent),0_0_16px_-2px_color-mix(in_oklab,var(--color-primary)_45%,transparent)]\",\r\n        active: \"text-primary\",\r\n        inactive: \"text-muted-foreground hover:text-foreground\",\r\n      }}\r\n    />\r\n  )\r\n}\r\n\r\n/* Fill: aktif sekme tam primary dolgu alir. */\r\nexport function FillTabs(props: TabsProps) {\r\n  return (\r\n    <AccentTabsShell\r\n      props={props}\r\n      config={{\r\n        bar: \"rounded-lg bg-muted p-1\",\r\n        tab: \"rounded-md\",\r\n        indicator: \"inset-0 rounded-md bg-primary\",\r\n        active: \"text-primary-foreground\",\r\n        inactive: \"text-muted-foreground hover:text-foreground\",\r\n      }}\r\n    />\r\n  )\r\n}\r\n\r\n/* Ring: the active tab is unfilled and carries a primary ring frame. */\r\nexport function RingTabs(props: TabsProps) {\r\n  return (\r\n    <AccentTabsShell\r\n      props={props}\r\n      config={{\r\n        bar: \"inline-flex items-center gap-1.5\",\r\n        tab: \"rounded-full\",\r\n        indicator:\r\n          \"inset-0 rounded-full border border-[color-mix(in_oklab,var(--color-primary)_55%,transparent)] shadow-[0_0_0_3px_color-mix(in_oklab,var(--color-primary)_14%,transparent)]\",\r\n        active: \"text-primary\",\r\n        inactive: \"text-muted-foreground hover:text-foreground\",\r\n      }}\r\n    />\r\n  )\r\n}\r\n\r\n/* Edge: aktif sekmede sol kenarda dikey primary serit. */\r\nexport function EdgeTabs(props: TabsProps) {\r\n  return (\r\n    <AccentTabsShell\r\n      props={props}\r\n      config={{\r\n        bar: \"rounded-lg bg-muted p-1\",\r\n        tab: \"rounded-md pl-4\",\r\n        indicator: \"inset-y-0 left-0 right-0 rounded-md border-l-2 border-primary bg-[color-mix(in_oklab,var(--color-primary)_10%,transparent)]\",\r\n        active: \"text-foreground\",\r\n        inactive: \"text-muted-foreground hover:text-foreground\",\r\n      }}\r\n    />\r\n  )\r\n}\r\n",
      "type": "registry:component",
      "target": "components/ui/tabs-accent.tsx"
    }
  ],
  "categories": [
    "styled",
    "tabs"
  ],
  "type": "registry:component"
}