{
  "$schema": "https://shadcn-svelte.com/schema/registry-item.json",
  "name": "animated-tabs",
  "type": "registry:ui",
  "title": "AnimatedTabs",
  "description": "Animated shadcn Tabs with spring-based sliding indicator via svelte-motion layoutId.",
  "dependencies": [
    "@humanspeak/svelte-motion",
    "bits-ui"
  ],
  "devDependencies": [],
  "registryDependencies": [],
  "files": [
    {
      "content": "import Content, { type TabsContentProps } from './animated-tabs-content.svelte'\nimport List, { type TabsListProps } from './animated-tabs-list.svelte'\nimport Trigger, { type TabsTriggerProps } from './animated-tabs-trigger.svelte'\nimport Root, { type TabsProps } from './animated-tabs.svelte'\n\nexport {\n    Content,\n    List,\n    Root,\n    Root as AnimatedTabs,\n    Trigger,\n    type TabsContentProps,\n    type TabsListProps,\n    type TabsProps,\n    type TabsTriggerProps\n}\n",
      "type": "registry:file",
      "target": "animated-tabs/index.ts"
    },
    {
      "content": "<!--\n  @component\n  Animated shadcn Tabs content panel.\n\n  Wraps bits-ui Tabs.Content. When animated, uses the child render prop\n  to keep the panel element always in the DOM (never `hidden`). Inactive\n  panels use `display:none` so they collapse. The active panel's children\n  are wrapped in a MotionDiv that replays a fade+slide entrance on each\n  tab switch — the panel container (and any styling on it) stays stable.\n-->\n<script lang=\"ts\">\n    import { cn, type WithoutChildrenOrChild } from '$UTILS$'\n    import { MotionDiv } from '@humanspeak/svelte-motion'\n    import { Tabs as TabsPrimitive, type TabsContentProps as BitsTabsContentProps } from 'bits-ui'\n    import { getContext } from 'svelte'\n    import { TABS_CTX, type TabsContext } from './animated-tabs.svelte'\n\n    export type TabsContentProps = WithoutChildrenOrChild<BitsTabsContentProps> & {\n        /** Override animation for this panel. Inherits from Root when unset. */\n        animated?: boolean\n        /** Override the content entrance initial state. Default: `{ opacity: 0, y: 8 }` */\n        initial?: Record<string, unknown>\n        /** Override the content entrance animate target. Default: `{ opacity: 1, y: 0 }` */\n        animate?: Record<string, unknown>\n        /** Override the content entrance transition. Default: `{ duration: 0.3, ease: 'easeOut' }` */\n        transition?: Record<string, unknown>\n    }\n\n    let {\n        class: className,\n        value,\n        animated,\n        initial,\n        animate,\n        transition,\n        ref = $bindable(null),\n        children,\n        ...restProps\n    }: TabsContentProps & { children?: import('svelte').Snippet } = $props()\n\n    const ctx = getContext<TabsContext>(TABS_CTX)\n    const isActive = $derived(ctx.value() === value)\n    const isAnimated = $derived(animated ?? ctx.animated)\n\n    const defaultInitial = { opacity: 0, y: 8 }\n    const defaultAnimate = { opacity: 1, y: 0 }\n    const defaultTransition = { duration: 0.3, ease: 'easeOut' }\n</script>\n\n{#if isAnimated}\n    <TabsPrimitive.Content bind:ref {value} {...restProps}>\n        {#snippet child({ props: panelProps })}\n            <!-- eslint-disable-next-line @typescript-eslint/no-unused-vars -- hidden must be stripped so the panel stays in the DOM -->\n            {@const { hidden: _hidden, style: panelStyle, ...safeProps } = panelProps}\n            <div\n                {...safeProps}\n                class={cn(\n                    'mt-2 ring-offset-background focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-none',\n                    className\n                )}\n                style=\"{panelStyle ?? ''}{isActive ? '' : ';display:none'}\"\n            >\n                {#key ctx.value()}\n                    <MotionDiv\n                        initial={initial ?? defaultInitial}\n                        animate={animate ?? defaultAnimate}\n                        transition={transition ?? defaultTransition}\n                    >\n                        {@render children?.()}\n                    </MotionDiv>\n                {/key}\n            </div>\n        {/snippet}\n    </TabsPrimitive.Content>\n{:else}\n    <TabsPrimitive.Content\n        bind:ref\n        {value}\n        class={cn(\n            'mt-2 ring-offset-background focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-none',\n            className\n        )}\n        {...restProps}\n    >\n        {@render children?.()}\n    </TabsPrimitive.Content>\n{/if}\n",
      "type": "registry:file",
      "target": "animated-tabs/animated-tabs-content.svelte"
    },
    {
      "content": "<!--\n  @component\n  Animated shadcn Tabs list component.\n\n  Wraps bits-ui Tabs.List for ARIA-compliant keyboard navigation\n  (arrow keys, Home/End, roving tabindex).\n-->\n<script lang=\"ts\">\n    import { cn, type WithoutChildrenOrChild } from '$UTILS$'\n    import { Tabs as TabsPrimitive, type TabsListProps as BitsTabsListProps } from 'bits-ui'\n\n    export type TabsListProps = WithoutChildrenOrChild<BitsTabsListProps>\n\n    let {\n        class: className,\n        ref = $bindable(null),\n        children,\n        ...restProps\n    }: TabsListProps & { children?: import('svelte').Snippet } = $props()\n</script>\n\n<TabsPrimitive.List\n    bind:ref\n    class={cn(\n        'inline-flex h-9 w-fit items-center justify-center rounded-lg bg-muted p-[3px] text-muted-foreground',\n        className\n    )}\n    {...restProps}\n>\n    {@render children?.()}\n</TabsPrimitive.List>\n",
      "type": "registry:file",
      "target": "animated-tabs/animated-tabs-list.svelte"
    },
    {
      "content": "<!--\n  @component\n  Animated shadcn Tabs trigger component.\n\n  When `animated=true` (from context), renders a spring-based sliding\n  indicator via svelte-motion layoutId. Falls back to standard CSS\n  background swap when `animated=false`.\n-->\n<script lang=\"ts\">\n    import { cn, type WithoutChildrenOrChild } from '$UTILS$'\n    import { AnimatePresence, MotionDiv } from '@humanspeak/svelte-motion'\n    import { Tabs as TabsPrimitive, type TabsTriggerProps as BitsTabsTriggerProps } from 'bits-ui'\n    import { getContext } from 'svelte'\n    import { TABS_CTX, type TabsContext } from './animated-tabs.svelte'\n\n    export type TabsTriggerProps = WithoutChildrenOrChild<BitsTabsTriggerProps> & {\n        /** Override animation for this trigger. Inherits from Root when unset. */\n        animated?: boolean\n        /** Override the indicator spring transition. Default: `{ type: 'spring', stiffness: 500, damping: 30 }` */\n        transition?: Record<string, unknown>\n    }\n\n    let {\n        class: className,\n        value,\n        animated,\n        transition,\n        ref = $bindable(null),\n        children,\n        ...restProps\n    }: TabsTriggerProps & { children?: import('svelte').Snippet } = $props()\n\n    const ctx = getContext<TabsContext>(TABS_CTX)\n    const isActive = $derived(ctx.value() === value)\n    const isAnimated = $derived(animated ?? ctx.animated)\n\n    const defaultTransition = { type: 'spring' as const, stiffness: 500, damping: 30 }\n</script>\n\n<TabsPrimitive.Trigger\n    bind:ref\n    {value}\n    class={cn(\n        'relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 dark:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\"size-\"])]:size-4',\n        !isAnimated &&\n            'text-foreground data-[state=active]:bg-background data-[state=active]:shadow-sm dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 dark:data-[state=active]:text-foreground',\n        className\n    )}\n    {...restProps}\n>\n    {#if isAnimated}\n        <AnimatePresence>\n            {#if isActive}\n                <MotionDiv\n                    key=\"indicator\"\n                    layoutId={ctx.layoutId}\n                    class=\"absolute inset-0 rounded-md bg-background shadow-sm dark:border dark:border-input dark:bg-input/30\"\n                    transition={transition ?? defaultTransition}\n                />\n            {/if}\n        </AnimatePresence>\n        <span\n            class=\"relative z-10 inline-flex items-center gap-1.5\"\n            class:text-foreground={isActive}\n        >\n            {@render children?.()}\n        </span>\n    {:else}\n        {@render children?.()}\n    {/if}\n</TabsPrimitive.Trigger>\n",
      "type": "registry:file",
      "target": "animated-tabs/animated-tabs-trigger.svelte"
    },
    {
      "content": "<!--\n  @component\n  Animated shadcn Tabs root component powered by svelte-motion.\n\n  Drop-in replacement for standard shadcn tabs with a spring-based sliding\n  indicator (via layoutId) that animates between triggers. Wraps bits-ui\n  Tabs primitives for full ARIA compliance.\n\n  Set `animated={false}` to disable motion and get vanilla shadcn behavior.\n-->\n<script lang=\"ts\" module>\n    /** Module-level counter to generate unique layoutIds per tab group. */\n    let instanceCounter = 0\n\n    /** Symbol key for the tabs context. */\n    export const TABS_CTX = Symbol('animated-tabs')\n\n    /** Context shape propagated to child components. */\n    export type TabsContext = {\n        animated: boolean\n        layoutId: string\n        value: () => string\n    }\n</script>\n\n<script lang=\"ts\">\n    import { cn, type WithoutChildrenOrChild } from '$UTILS$'\n    import { Tabs as TabsPrimitive, type TabsRootProps as BitsTabsRootProps } from 'bits-ui'\n    import { setContext } from 'svelte'\n\n    export type TabsProps = WithoutChildrenOrChild<BitsTabsRootProps> & {\n        /** Set to false to disable motion animations (vanilla shadcn behavior). */\n        animated?: boolean\n    }\n\n    let {\n        class: className,\n        value = $bindable(''),\n        onValueChange,\n        animated = true,\n        ref = $bindable(null),\n        children,\n        ...restProps\n    }: TabsProps & { children?: import('svelte').Snippet } = $props()\n\n    // eslint-disable-next-line no-useless-assignment -- counter is read on next instance mount\n    const layoutId = `animated-tabs-${instanceCounter++}`\n\n    setContext<TabsContext>(TABS_CTX, {\n        animated,\n        layoutId,\n        get value() {\n            return () => value\n        }\n    })\n</script>\n\n<TabsPrimitive.Root bind:ref bind:value {onValueChange} class={cn(className)} {...restProps}>\n    {@render children?.()}\n</TabsPrimitive.Root>\n",
      "type": "registry:file",
      "target": "animated-tabs/animated-tabs.svelte"
    }
  ],
  "categories": [
    "navigation"
  ],
  "author": "Humanspeak, Inc.",
  "docs": "Requires @humanspeak/svelte-motion and bits-ui. Set animated={false} on Root to disable."
}
