{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "input",
  "title": "UI Input",
  "description": "A text input whose idle/hover/active/typing/invalid state is a pure function of the timeline; the focus ring, border, caret, and value reveal are keyframed presets.",
  "dependencies": [
    "remotion",
    "class-variance-authority"
  ],
  "registryDependencies": [
    "https://snapcn.dev/r/snap-cn-ui.json",
    "https://snapcn.dev/r/caret.json",
    "utils"
  ],
  "files": [
    {
      "path": "registry/snap-cn-ui/input/index.tsx",
      "content": "\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { Caret } from \"@/components/snap-cn/caret\";\nimport {\n  mixOklch,\n  revealedText,\n  type SnapCnTheme,\n  useSnapCnTheme,\n} from \"@/lib/snap-cn-ui\";\nimport { cn } from \"@/lib/utils\";\n\nexport type InputState =\n  | \"idle\"\n  | \"hover\"\n  | \"active\"\n  | \"typing\"\n  | \"blur\"\n  | \"invalid\";\n\ntype InputSize = \"sm\" | \"default\" | \"lg\";\n\nexport interface InputProps {\n  state?: InputState;\n  style?: InputStyle;\n  placeholder?: string;\n  value?: string;\n  size?: InputSize;\n  theme?: Partial<SnapCnTheme>;\n  primary?: string;\n  fullWidth?: boolean;\n  className?: string;\n}\n\n/**\n * The control, as classes.\n *\n * This is a shadcn field that happens to be driven by a frame clock, so it is\n * built the way shadcn builds one: `cva` for the size scale, `cn` for merging,\n * `data-slot` for targeting. What stays in `style` is *only* what is tweened —\n * see the note on the render below.\n */\nconst inputVariants = cva(\n  \"relative flex items-center border border-solid tracking-[-0.01em]\",\n  {\n    variants: {\n      size: {\n        sm: \"h-9 px-3 text-[13px]\",\n        default: \"h-10 px-3.5 text-[15px]\",\n        lg: \"h-12 px-4 text-[17px]\",\n      },\n      fullWidth: { true: \"w-full\", false: \"w-80\" },\n    },\n    defaultVariants: { size: \"default\", fullWidth: false },\n  },\n);\n\n/** The placeholder overlays the value, so it needs the field's own inset. */\nconst placeholderVariants = cva(\n  \"pointer-events-none absolute whitespace-nowrap\",\n  {\n    variants: { size: { sm: \"left-3\", default: \"left-3.5\", lg: \"left-4\" } },\n    defaultVariants: { size: \"default\" },\n  },\n);\n\n/** `Caret` takes a pixel height, not a class — so this one number stays a number. */\nconst CARET_HEIGHT: Record<InputSize, number> = { sm: 14, default: 17, lg: 19 };\n\nexport interface InputStyle {\n  borderColor: string;\n  ringColor: string;\n  ringWidth: number;\n  background: string;\n  caretOpacity: number;\n  valueReveal: number;\n  placeholderOpacity: number;\n}\n\nexport interface InputStyleContext {\n  idleBorder: string;\n  hoverBorder: string;\n  activeBorder: string;\n  invalidBorder: string;\n  ring: string;\n  invalidRing: string;\n  background: string;\n  hoverBackground: string;\n  foreground: string;\n  mutedForeground: string;\n}\n\nexport function inputStyleContext(theme: SnapCnTheme): InputStyleContext {\n  return {\n    idleBorder: theme.input,\n    hoverBorder: mixOklch(theme.input, theme.foreground, 0.18),\n    activeBorder: theme.ring,\n    invalidBorder: theme.destructive,\n    ring: mixOklch(theme.background, theme.ring, 0.5),\n    invalidRing: mixOklch(theme.background, theme.destructive, 0.4),\n    background: theme.background,\n    hoverBackground: mixOklch(theme.background, theme.muted, 0.4),\n    foreground: theme.foreground,\n    mutedForeground: theme.mutedForeground,\n  };\n}\n\nexport function inputStyle(\n  state: InputState,\n  ctx: InputStyleContext,\n): InputStyle {\n  switch (state) {\n    case \"hover\":\n      return {\n        borderColor: ctx.hoverBorder,\n        ringColor: ctx.ring,\n        ringWidth: 0,\n        background: ctx.hoverBackground,\n        caretOpacity: 0,\n        valueReveal: 0,\n        placeholderOpacity: 1,\n      };\n    case \"active\":\n      return {\n        borderColor: ctx.activeBorder,\n        ringColor: ctx.ring,\n        ringWidth: 3,\n        background: ctx.background,\n        caretOpacity: 1,\n        valueReveal: 0,\n        placeholderOpacity: 1,\n      };\n    case \"typing\":\n      return {\n        borderColor: ctx.activeBorder,\n        ringColor: ctx.ring,\n        ringWidth: 3,\n        background: ctx.background,\n        caretOpacity: 1,\n        valueReveal: 1,\n        placeholderOpacity: 0,\n      };\n    case \"blur\":\n      return {\n        borderColor: ctx.idleBorder,\n        ringColor: ctx.ring,\n        ringWidth: 0,\n        background: ctx.background,\n        caretOpacity: 0,\n        valueReveal: 1,\n        placeholderOpacity: 0,\n      };\n    case \"invalid\":\n      return {\n        borderColor: ctx.invalidBorder,\n        ringColor: ctx.invalidRing,\n        ringWidth: 3,\n        background: ctx.background,\n        caretOpacity: 0,\n        valueReveal: 1,\n        placeholderOpacity: 0,\n      };\n    default:\n      return {\n        borderColor: ctx.idleBorder,\n        ringColor: ctx.ring,\n        ringWidth: 0,\n        background: ctx.background,\n        caretOpacity: 0,\n        valueReveal: 0,\n        placeholderOpacity: 1,\n      };\n  }\n}\n\nexport function Input({\n  state = \"idle\",\n  style,\n  placeholder = \"you@example.com\",\n  value = \"remotion@snapcn.dev\",\n  size = \"default\",\n  theme: themeOverride,\n  primary,\n  fullWidth = false,\n  className,\n}: InputProps) {\n  const theme = useSnapCnTheme({\n    ...themeOverride,\n    ...(primary ? { primary } : {}),\n  });\n\n  const ctx = inputStyleContext(theme);\n  const v = style ?? inputStyle(state, ctx);\n  const revealed = revealedText(\n    value,\n    Math.round(value.length * v.valueReveal),\n  );\n\n  return (\n    // No font-family. A shadcn Input does not set one — it inherits, and the\n    // hard-coded `var(--font-geist-sans)` that used to be here resolved on the\n    // site and to nothing in a render, which is the bug this tier existed to\n    // avoid.\n    <div className=\"absolute inset-0 flex items-center justify-center\">\n      <div\n        data-slot=\"input\"\n        data-state={state}\n        className={cn(inputVariants({ size, fullWidth }), className)}\n        // Everything left in `style` is a value being *tweened between shadcn\n        // states* by `use-input-transition` — `mixOklch(idleBorder → ring)`, a\n        // ring growing 0→3px. A class is a binary state; this component's whole\n        // job is the 300ms in between, so these cannot be classes and are not a\n        // compromise. `borderRadius` follows the theme token.\n        style={{\n          background: v.background,\n          borderColor: v.borderColor,\n          borderRadius: theme.radius,\n          boxShadow: `0 0 0 ${v.ringWidth}px ${v.ringColor}`,\n        }}\n      >\n        <span\n          data-slot=\"input-placeholder\"\n          className={placeholderVariants({ size })}\n          style={{\n            color: ctx.mutedForeground,\n            opacity: v.valueReveal > 0 ? 0 : v.placeholderOpacity,\n          }}\n        >\n          {placeholder}\n        </span>\n\n        <div className=\"flex min-w-0 items-center\">\n          <span className=\"whitespace-nowrap\" style={{ color: ctx.foreground }}>\n            {revealed}\n          </span>\n          <Caret\n            color={ctx.foreground}\n            height={CARET_HEIGHT[size]}\n            radius={1}\n            opacity={v.caretOpacity}\n            marginLeft={revealed.length > 0 ? 4 : 0}\n          />\n        </div>\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/snap-cn/input.tsx"
    },
    {
      "path": "registry/snap-cn-ui/input/use-input-transition.ts",
      "content": "\"use client\";\n\nimport {\n  type InputState,\n  type InputStyle,\n  inputStyle,\n  inputStyleContext,\n} from \"@/components/snap-cn/input\";\nimport {\n  easings,\n  mixOklch,\n  type SnapCnTheme,\n  type Step,\n  useSnapCnTheme,\n  useStateTransition,\n} from \"@/lib/snap-cn-ui\";\n\nexport const DEFAULT_DURATION = 8;\n\nexport function tweenInputStyle(\n  a: InputStyle,\n  b: InputStyle,\n  t: number,\n): InputStyle {\n  return {\n    ringWidth: a.ringWidth + (b.ringWidth - a.ringWidth) * t,\n    caretOpacity: a.caretOpacity + (b.caretOpacity - a.caretOpacity) * t,\n    valueReveal: a.valueReveal + (b.valueReveal - a.valueReveal) * t,\n    placeholderOpacity:\n      a.placeholderOpacity + (b.placeholderOpacity - a.placeholderOpacity) * t,\n    borderColor: mixOklch(a.borderColor, b.borderColor, t),\n    ringColor: mixOklch(a.ringColor, b.ringColor, t),\n    background: mixOklch(a.background, b.background, t),\n  };\n}\n\nexport interface InputTransitionOptions {\n  theme?: Partial<SnapCnTheme>;\n  mode?: \"light\" | \"dark\";\n  primary?: string;\n  speed?: number;\n  defaultDuration?: number;\n}\n\nexport function useInputTransition(\n  steps: Step<InputState>[],\n  opts: InputTransitionOptions = {},\n): InputStyle {\n  const {\n    theme: themeOverride,\n    mode,\n    primary,\n    speed = 1,\n    defaultDuration = DEFAULT_DURATION,\n  } = opts;\n  const theme = useSnapCnTheme(\n    { ...themeOverride, ...(primary ? { primary } : {}) },\n    mode,\n  );\n  const ctx = inputStyleContext(theme);\n  const { from, to, progress } = useStateTransition(\n    steps,\n    \"idle\",\n    speed,\n    defaultDuration,\n  );\n  const t = easings.out(progress);\n  return tweenInputStyle(inputStyle(from, ctx), inputStyle(to, ctx), t);\n}\n",
      "type": "registry:component",
      "target": "components/snap-cn/use-input-transition.ts"
    }
  ],
  "type": "registry:component"
}