{
  "schemaVersion": 1,
  "target": "react",
  "name": "sliding-tabs",
  "version": "0.1.1",
  "category": "core",
  "title": "Sliding Tabs",
  "description": "Segmented tabs whose active pill slides between options with a spring.",
  "tags": [
    "tabs",
    "segmented-control",
    "navigation",
    "motion",
    "base-ui"
  ],
  "requires": {
    "projectTypes": [
      "next",
      "vite-react"
    ],
    "styling": "tailwind-v4"
  },
  "npmDependencies": {
    "@base-ui/react": "^1.9.0",
    "motion": "^14.14.0"
  },
  "registryDependencies": [],
  "docsPath": "/components/sliding-tabs",
  "props": [
    {
      "name": "SlidingTabs.defaultValue",
      "type": "any",
      "default": "0",
      "description": "Value of the tab that is active initially, when uncontrolled."
    },
    {
      "name": "SlidingTabs.value",
      "type": "any",
      "description": "Value of the active tab, when controlled. Pair with onValueChange."
    },
    {
      "name": "SlidingTabs.onValueChange",
      "type": "(value: any, details) => void",
      "description": "Called when the active tab changes."
    },
    {
      "name": "SlidingTabs.orientation",
      "type": "\"horizontal\" | \"vertical\"",
      "default": "\"horizontal\"",
      "description": "Lays the tabs out in a row or a column and sets which arrow keys move between them."
    },
    {
      "name": "SlidingTabs.transition",
      "type": "Transition",
      "default": "{ type: \"spring\", bounce: 0.2, duration: 0.4 }",
      "description": "Motion transition used when the pill slides."
    },
    {
      "name": "SlidingTabsList.activateOnFocus",
      "type": "boolean",
      "default": "false",
      "description": "Activates a tab as soon as arrow keys focus it, instead of on Enter or Space."
    },
    {
      "name": "SlidingTabsTab.value",
      "type": "any",
      "description": "Identifies the tab. Must match a SlidingTabsPanel value."
    },
    {
      "name": "SlidingTabsTab.disabled",
      "type": "boolean",
      "default": "false",
      "description": "Skips the tab during keyboard navigation and ignores clicks."
    },
    {
      "name": "SlidingTabsPanel.value",
      "type": "any",
      "description": "Shown while the tab with the same value is active."
    },
    {
      "name": "SlidingTabsPanel.keepMounted",
      "type": "boolean",
      "default": "false",
      "description": "Keeps the panel in the DOM, hidden, while another tab is active."
    }
  ],
  "accessibility": [
    "Base UI renders the tablist, tab, and tabpanel roles and links each tab to its panel with aria-controls and aria-labelledby.",
    "Arrow keys move focus between tabs; Home and End jump to the first and last tab.",
    "The active tab is marked with aria-selected and a raised pill behind its label, not only a color change.",
    "The pill is decorative (aria-hidden) and sits behind the label, so it never changes the accessible name.",
    "The tree is wrapped in MotionConfig reducedMotion=\"user\": when reduced motion is preferred, the pill jumps to the new tab instead of sliding.",
    "Give SlidingTabsList an aria-label that names the group."
  ],
  "states": [],
  "files": [
    {
      "path": "ui/sliding-tabs.tsx",
      "content": "\"use client\";\n\nimport { Tabs as BaseTabs } from \"@base-ui/react/tabs\";\nimport { MotionConfig, motion, type Transition } from \"motion/react\";\nimport { createContext, useContext, useId } from \"react\";\n\nfunction cx(...classes: Array<string | false | null | undefined>) {\n  return classes.filter(Boolean).join(\" \");\n}\n\nconst defaultTransition: Transition = { type: \"spring\", bounce: 0.2, duration: 0.4 };\n\n// Each SlidingTabs instance gets its own layoutId so pills never travel between separate tab groups.\nconst PillContext = createContext<{ layoutId: string; transition: Transition }>({\n  layoutId: \"seamless-sliding-tab-pill\",\n  transition: defaultTransition,\n});\n\nexport interface SlidingTabsProps extends Omit<BaseTabs.Root.Props, \"className\"> {\n  className?: string;\n  /** Motion transition for the sliding pill. Defaults to a soft spring. */\n  transition?: Transition;\n}\n\n/**\n * Tabs whose active pill slides between tabs with a spring.\n * With reduced motion preferred, the pill moves without animating.\n */\nexport function SlidingTabs({ className, transition = defaultTransition, ...props }: SlidingTabsProps) {\n  const layoutId = `sliding-tab-pill-${useId()}`;\n\n  return (\n    <MotionConfig reducedMotion=\"user\">\n      <PillContext.Provider value={{ layoutId, transition }}>\n        <BaseTabs.Root\n          className={cx(\"flex flex-col gap-4 data-[orientation=vertical]:flex-row\", className)}\n          {...props}\n        />\n      </PillContext.Provider>\n    </MotionConfig>\n  );\n}\n\nexport interface SlidingTabsListProps extends Omit<BaseTabs.List.Props, \"className\"> {\n  className?: string;\n}\n\n/** The track that holds the tabs and the sliding pill. */\nexport function SlidingTabsList({ className, ...props }: SlidingTabsListProps) {\n  return (\n    <BaseTabs.List\n      className={cx(\n        \"inline-flex w-fit gap-1 rounded-lg bg-zinc-100 p-1 dark:bg-zinc-900\",\n        \"data-[orientation=vertical]:flex-col\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport interface SlidingTabsTabProps extends Omit<BaseTabs.Tab.Props, \"className\" | \"render\"> {\n  className?: string;\n}\n\n/** A single tab. Its `value` must match a `SlidingTabsPanel`'s `value`. */\nexport function SlidingTabsTab({ className, ...props }: SlidingTabsTabProps) {\n  const { layoutId, transition } = useContext(PillContext);\n\n  return (\n    <BaseTabs.Tab\n      className={cx(\n        \"relative inline-flex h-8 cursor-pointer items-center justify-center gap-2 rounded-md px-3 text-sm font-medium whitespace-nowrap select-none\",\n        \"text-zinc-600 transition-colors duration-150 motion-reduce:transition-none\",\n        \"hover:text-zinc-900 data-active:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-100 dark:data-active:text-white\",\n        \"focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-sky-500\",\n        \"data-disabled:pointer-events-none data-disabled:opacity-50\",\n        \"data-[orientation=vertical]:justify-start [&_svg]:size-4 [&_svg]:shrink-0\",\n        className,\n      )}\n      render={(renderProps, state) => (\n        <button {...renderProps}>\n          {state.active ? (\n            <motion.span\n              aria-hidden=\"true\"\n              layoutId={layoutId}\n              transition={transition}\n              className=\"absolute inset-0 rounded-md bg-white shadow-sm ring-1 ring-zinc-900/10 dark:bg-zinc-700 dark:ring-white/10\"\n            />\n          ) : null}\n          <span className=\"relative inline-flex items-center gap-2\">{renderProps.children}</span>\n        </button>\n      )}\n      {...props}\n    />\n  );\n}\n\nexport interface SlidingTabsPanelProps extends Omit<BaseTabs.Panel.Props, \"className\"> {\n  className?: string;\n}\n\n/** Content shown while the tab with the same `value` is active. */\nexport function SlidingTabsPanel({ className, ...props }: SlidingTabsPanelProps) {\n  return (\n    <BaseTabs.Panel\n      className={cx(\n        \"rounded-md text-sm text-zinc-700 dark:text-zinc-300\",\n        \"focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-sky-500\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n"
    }
  ],
  "example": {
    "content": "import { SlidingTabs, SlidingTabsList, SlidingTabsPanel, SlidingTabsTab } from \"@/components/seamless/ui/sliding-tabs\";\n\nexport default function SlidingTabsExample() {\n  return (\n    <SlidingTabs defaultValue=\"week\" className=\"w-full max-w-sm items-center\">\n      <SlidingTabsList aria-label=\"Time range\">\n        <SlidingTabsTab value=\"day\">Day</SlidingTabsTab>\n        <SlidingTabsTab value=\"week\">Week</SlidingTabsTab>\n        <SlidingTabsTab value=\"month\">Month</SlidingTabsTab>\n      </SlidingTabsList>\n      <SlidingTabsPanel value=\"day\" className=\"text-center tabular-nums\">1,204 requests today.</SlidingTabsPanel>\n      <SlidingTabsPanel value=\"week\" className=\"text-center tabular-nums\">8,930 requests this week.</SlidingTabsPanel>\n      <SlidingTabsPanel value=\"month\" className=\"text-center tabular-nums\">36,512 requests this month.</SlidingTabsPanel>\n    </SlidingTabs>\n  );\n}\n"
  }
}
