{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "swap",
  "type": "registry:ui",
  "title": "Swap",
  "description": "Animated hand-off between the states of a button or label: copied, pending, done.",
  "dependencies": [],
  "registryDependencies": [
    "https://control-ui.dev/r/core.json"
  ],
  "files": [
    {
      "path": "src/registry/knob-contracts/swap-knobs.ts",
      "target": "@components/control-ui/knob-contracts/swap-knobs.ts",
      "type": "registry:component",
      "content": "// Generated from src/registry/sources/control-ui/recipes/swap.css by scripts/gen-knob-contracts.ts — run `bun run sync:knobs`.\nexport const swapKnobs = [\"--cui-swap-animation-duration\", \"--cui-swap-easing\", \"--cui-swap-label-inset\", \"--cui-swap-icon-scale\"] as const;\nexport type SwapKnobStyle = Partial<Record<(typeof swapKnobs)[number], string>>;\n"
    },
    {
      "path": "src/registry/sources/control-ui/recipes/swap.css",
      "target": "@components/control-ui/styles/recipes/swap.css",
      "type": "registry:file",
      "content": "@layer components {\n  :where([data-control-family=\"swap\"][data-slot=\"root\"]) {\n    --cui-swap-animation-duration: var(--duration-slow);\n    --cui-swap-easing: var(--ease-emphasized);\n    --cui-swap-label-inset: 60%;\n    --cui-swap-icon-scale: 0.5;\n    position: relative;\n    display: inline-flex;\n    align-items: center;\n    gap: inherit;\n    inline-size: var(--_swap-inline-size, auto);\n    transition: inline-size var(--cui-swap-animation-duration) var(--cui-swap-easing);\n  }\n\n  :where([data-control-family=\"swap\"][data-slot=\"item\"]) {\n    display: inline-flex;\n    flex: none;\n    align-items: center;\n    gap: inherit;\n    white-space: nowrap;\n  }\n\n  :where([data-control-family=\"swap\"][data-slot=\"item\"]:not([data-state=\"active\"])) {\n    position: absolute;\n    inset-block: 0;\n    inset-inline-start: 0;\n    opacity: 0;\n    pointer-events: none;\n  }\n\n  :where([data-control-family=\"swap\"][data-slot=\"item\"][data-state=\"inactive\"]) {\n    visibility: hidden;\n  }\n\n  :where([data-control-family=\"swap\"][data-slot=\"label\"]) {\n    display: inline-block;\n  }\n\n  :where([data-control-family=\"swap\"][data-swapped] > [data-slot=\"item\"][data-state=\"active\"]) {\n    animation: cui-swap-item-enter var(--cui-swap-animation-duration) var(--cui-swap-easing) both;\n  }\n\n  :where([data-control-family=\"swap\"][data-swapped] > [data-slot=\"item\"][data-state=\"exiting\"]) {\n    animation: cui-swap-item-exit var(--cui-swap-animation-duration) var(--cui-swap-easing) both;\n  }\n\n  :where(\n    [data-control-family=\"swap\"][data-swapped]\n      > [data-slot=\"item\"][data-state=\"active\"]\n      > :not(svg, [data-slot=\"icon\"], [data-control-family=\"spinner\"])\n  ) {\n    animation: cui-swap-label-enter var(--cui-swap-animation-duration) var(--cui-swap-easing) both;\n  }\n\n  :where(\n    [data-control-family=\"swap\"][data-swapped]\n      > [data-slot=\"item\"][data-state=\"exiting\"]\n      > :not(svg, [data-slot=\"icon\"], [data-control-family=\"spinner\"])\n  ) {\n    animation: cui-swap-label-exit var(--cui-swap-animation-duration) var(--cui-swap-easing) both;\n  }\n\n  :where(\n    [data-control-family=\"swap\"][data-swapped]\n      > [data-slot=\"item\"][data-state=\"active\"]\n      > :is(svg, [data-slot=\"icon\"], [data-control-family=\"spinner\"])\n  ) {\n    animation: cui-swap-icon-enter var(--cui-swap-animation-duration) var(--cui-swap-easing) both;\n  }\n\n  :where(\n    [data-control-family=\"swap\"][data-swapped]\n      > [data-slot=\"item\"][data-state=\"exiting\"]\n      > :is(svg, [data-slot=\"icon\"], [data-control-family=\"spinner\"])\n  ) {\n    animation: cui-swap-icon-exit var(--cui-swap-animation-duration) var(--cui-swap-easing) both;\n  }\n}\n\n@keyframes cui-swap-item-enter {\n  from {\n    opacity: 0;\n    filter: blur(var(--blur-xs));\n  }\n}\n\n@keyframes cui-swap-item-exit {\n  from {\n    opacity: 1;\n    filter: blur(0);\n  }\n  to {\n    opacity: 0;\n    filter: blur(var(--blur-xs));\n  }\n}\n\n@keyframes cui-swap-label-enter {\n  from {\n    translate: 0 var(--cui-swap-label-inset);\n  }\n}\n\n@keyframes cui-swap-label-exit {\n  to {\n    translate: 0 calc(var(--cui-swap-label-inset) * -1);\n  }\n}\n\n@keyframes cui-swap-icon-enter {\n  from {\n    scale: var(--cui-swap-icon-scale);\n  }\n}\n\n@keyframes cui-swap-icon-exit {\n  to {\n    scale: var(--cui-swap-icon-scale);\n  }\n}\n\n@property --cui-swap-animation-duration {\n  syntax: \"<time>\";\n  inherits: true;\n  initial-value: 0s;\n}\n\n@property --cui-swap-easing {\n  syntax: \"*\";\n  inherits: true;\n}\n\n@property --cui-swap-label-inset {\n  syntax: \"<length-percentage>\";\n  inherits: true;\n  initial-value: 0px;\n}\n\n@property --cui-swap-icon-scale {\n  syntax: \"<number>\";\n  inherits: true;\n  initial-value: 1;\n}\n"
    },
    {
      "path": "src/registry/sources/control-ui/ui/swap.tsx",
      "target": "@components/control-ui/ui/swap.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n\nimport type { ComponentProps, CSSProperties, ReactNode } from \"react\";\nimport { Children, createContext, useContext, useState } from \"react\";\nimport type { SwapKnobStyle } from \"@/components/control-ui/knob-contracts/swap-knobs\";\n\nexport type SwapItemState = \"active\" | \"exiting\" | \"inactive\";\n\ntype SwapTransition = { value: string; previous: string | null };\n\nconst SwapContext = createContext<SwapTransition | null>(null);\n\nfunction useSwap() {\n  const context = useContext(SwapContext);\n  if (!context) throw new Error(\"SwapItem must be rendered inside <Swap>.\");\n  return context;\n}\n\nfunction fitRootToItem(item: HTMLSpanElement) {\n  const root = item.parentElement;\n  if (!root) return;\n  const observer = new ResizeObserver(([entry]) => {\n    const [borderBox] = entry.borderBoxSize;\n    root.style.setProperty(\"--_swap-inline-size\", `${borderBox.inlineSize}px`);\n  });\n  observer.observe(item);\n  return () => observer.disconnect();\n}\n\nexport type SwapProps = Omit<ComponentProps<\"span\">, \"style\"> & {\n  value: string;\n  style?: CSSProperties & SwapKnobStyle;\n};\n\nexport function Swap({ value, children, ...props }: SwapProps) {\n  const [transition, setTransition] = useState<SwapTransition>({ value, previous: null });\n  if (transition.value !== value) setTransition({ value, previous: transition.value });\n\n  return (\n    <SwapContext.Provider value={transition}>\n      <span\n        data-control-ui=\"swap\"\n        data-control-family=\"swap\"\n        data-slot=\"root\"\n        data-swapped={transition.previous === null ? undefined : \"\"}\n        {...props}\n      >\n        {children}\n      </span>\n    </SwapContext.Provider>\n  );\n}\n\nfunction itemState({ value, previous }: SwapTransition, itemValue: string): SwapItemState {\n  if (itemValue === value) return \"active\";\n  if (itemValue === previous) return \"exiting\";\n  return \"inactive\";\n}\n\nfunction wrapLabelText(children: ReactNode): ReactNode {\n  return Children.map(children, (child) =>\n    typeof child === \"string\" || typeof child === \"number\" ? (\n      <span data-control-ui=\"swap\" data-control-family=\"swap\" data-slot=\"label\">\n        {child}\n      </span>\n    ) : (\n      child\n    ),\n  );\n}\n\nexport type SwapItemProps = Omit<ComponentProps<\"span\">, \"ref\"> & {\n  value: string;\n};\n\nexport function SwapItem({ value, children, ...props }: SwapItemProps) {\n  const state = itemState(useSwap(), value);\n\n  return (\n    <span\n      data-control-ui=\"swap\"\n      data-control-family=\"swap\"\n      data-slot=\"item\"\n      data-state={state}\n      aria-hidden={state === \"active\" ? undefined : true}\n      {...props}\n      ref={state === \"active\" ? fitRootToItem : undefined}\n    >\n      {wrapLabelText(children)}\n    </span>\n  );\n}\n"
    }
  ],
  "css": {
    "@import \"../components/control-ui/styles/recipes/swap.css\"": {}
  }
}
