{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "inputs-validation",
  "title": "Validation inputs",
  "description": "Styled inputs: the Validation set. 5 decorative inputs variations (SuccessInput, ErrorInput, LiveInput, StrengthInput, CharCountInput) on the ai2 token system, driven by CSS token transitions and sized sm to xl. Part of the free styled layer.",
  "dependencies": [
    "lucide-react@^1.23.0"
  ],
  "registryDependencies": [
    "@ai2/tokens"
  ],
  "files": [
    {
      "path": "registry/ai2/styled/inputs-validation.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Check, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Validation input family: 5 text inputs that show a validation state. Color comes\n   ONLY from tokens (success/warning/danger + shadcn). The state visuals are driven by\n   CSS + a small piece of React state; there is no animation, so motion/react is not\n   needed. Each one wraps a real <input> and passes through all the native props. */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\nconst height: Record<StyledSize, string> = {\n  sm: \"h-8 text-sm\",\n  md: \"h-9 text-sm\",\n  lg: \"h-10 text-base\",\n  xl: \"h-12 text-base\",\n}\n\nconst fieldBase =\n  \"w-full bg-transparent text-foreground outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50\"\n\nconst wrapBase =\n  \"relative inline-flex w-56 max-w-full items-center rounded-lg border px-3 transition-colors focus-within:ring-[3px]\"\n\ntype Props = Omit<React.ComponentProps<\"input\">, \"size\"> & { size?: StyledSize }\n\n/* Tracks the entered value whether controlled or uncontrolled (for the length calculation). */\nfunction useTrackedValue(props: Pick<Props, \"value\" | \"defaultValue\" | \"onChange\">) {\n  const [internal, setInternal] = React.useState(\n    props.defaultValue != null ? String(props.defaultValue) : \"\"\n  )\n  const value = props.value != null ? String(props.value) : internal\n  const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n    setInternal(e.target.value)\n    props.onChange?.(e)\n  }\n  return { value, onChange }\n}\n\n/* Success: gecerli iken yesil success kenarlik + saga hizali onay ikonu. */\nexport function SuccessInput({ className, size = \"md\", valid = true, ...props }: Props & { valid?: boolean }) {\n  return (\n    <span\n      data-slot=\"styled-input\"\n      data-tone={valid ? \"success\" : \"neutral\"}\n      className={cn(\n        wrapBase,\n        valid\n          ? \"border-success text-success-foreground focus-within:border-success focus-within:ring-success/25\"\n          : \"border-field-border focus-within:border-ring focus-within:ring-ring/50\",\n        height[size],\n        className\n      )}\n    >\n      <input {...props} className={cn(fieldBase, \"h-full\")} />\n      {valid ? <Check aria-hidden=\"true\" className=\"ml-2 size-4 shrink-0 text-success\" /> : null}\n    </span>\n  )\n}\n\n/* Error: danger kenarlik + altta mesaj slotu (message prop). */\nexport function ErrorInput({\n  className,\n  size = \"md\",\n  message,\n  ...props\n}: Props & { message?: React.ReactNode }) {\n  const autoId = React.useId()\n  const msgId = props[\"aria-describedby\"] ?? (message ? `${autoId}-msg` : undefined)\n  return (\n    <span data-slot=\"styled-input\" data-tone=\"danger\" className=\"inline-flex w-56 max-w-full flex-col gap-1\">\n      <span\n        className={cn(\n          wrapBase,\n          \"border-danger focus-within:border-danger focus-within:ring-danger/25\",\n          height[size],\n          className\n        )}\n      >\n        <input {...props} aria-invalid=\"true\" aria-describedby={msgId} className={cn(fieldBase, \"h-full\")} />\n        <X aria-hidden=\"true\" className=\"ml-2 size-4 shrink-0 text-danger\" />\n      </span>\n      {message ? (\n        <span id={msgId} className=\"px-1 text-xs text-danger\">\n          {message}\n        </span>\n      ) : null}\n    </span>\n  )\n}\n\n/* Live: kenarlik rengi state prop'unu (idle/valid/invalid) izler. */\nexport function LiveInput({\n  className,\n  size = \"md\",\n  state = \"idle\",\n  ...props\n}: Props & { state?: \"idle\" | \"valid\" | \"invalid\" }) {\n  const tone =\n    state === \"valid\"\n      ? \"border-success focus-within:border-success focus-within:ring-success/25\"\n      : state === \"invalid\"\n        ? \"border-danger focus-within:border-danger focus-within:ring-danger/25\"\n        : \"border-field-border focus-within:border-ring focus-within:ring-ring/50\"\n  return (\n    <span data-slot=\"styled-input\" data-tone={state} className={cn(wrapBase, tone, height[size], className)}>\n      <input\n        {...props}\n        aria-invalid={state === \"invalid\" ? \"true\" : undefined}\n        className={cn(fieldBase, \"h-full\")}\n      />\n    </span>\n  )\n}\n\n/* Strength: a 4-segment strength bar underneath, filling with the value length. */\nexport function StrengthInput({ className, size = \"md\", ...props }: Props) {\n  const { value, onChange } = useTrackedValue(props)\n  const hasLower = /[a-z]/.test(value)\n  const hasUpper = /[A-Z]/.test(value)\n  const hasDigit = /[0-9]/.test(value)\n  const hasSymbol = /[^A-Za-z0-9]/.test(value)\n  const variety = [hasLower, hasUpper, hasDigit, hasSymbol].filter(Boolean).length\n  const score =\n    value.length === 0 ? 0 : Math.min(4, Math.max(value.length >= 8 ? 2 : 1, variety))\n  const barTone =\n    score <= 1 ? \"bg-danger\" : score === 2 ? \"bg-warning\" : score === 3 ? \"bg-info\" : \"bg-success\"\n  return (\n    <span data-slot=\"styled-input\" className=\"inline-flex w-56 max-w-full flex-col gap-1.5\">\n      <span\n        className={cn(\n          wrapBase,\n          \"border-field-border focus-within:border-ring focus-within:ring-ring/50\",\n          height[size],\n          className\n        )}\n      >\n        <input type=\"password\" {...props} onChange={onChange} className={cn(fieldBase, \"h-full\")} />\n      </span>\n      <span aria-hidden=\"true\" className=\"flex gap-1\">\n        {[0, 1, 2, 3].map((i) => (\n          <span\n            key={i}\n            className={cn(\n              \"h-1 flex-1 rounded-full transition-colors duration-(--motion-base) motion-reduce:transition-none\",\n              i < score ? barTone : \"bg-muted\"\n            )}\n          />\n        ))}\n      </span>\n    </span>\n  )\n}\n\n/* CharCount: it accepts maxLength and shows a live character counter below. */\nexport function CharCountInput({ className, size = \"md\", maxLength, ...props }: Props) {\n  const { value, onChange } = useTrackedValue(props)\n  const count = value.length\n  const near = maxLength != null && count >= maxLength * 0.9\n  return (\n    <span data-slot=\"styled-input\" className=\"inline-flex w-56 max-w-full flex-col gap-1\">\n      <span\n        className={cn(\n          wrapBase,\n          \"border-field-border focus-within:border-ring focus-within:ring-ring/50\",\n          height[size],\n          className\n        )}\n      >\n        <input {...props} maxLength={maxLength} onChange={onChange} className={cn(fieldBase, \"h-full\")} />\n      </span>\n      <span\n        className={cn(\n          \"self-end px-1 text-xs tabular-nums\",\n          near ? \"text-warning\" : \"text-muted-foreground\"\n        )}\n      >\n        {count}\n        {maxLength != null ? `/${maxLength}` : null}\n      </span>\n    </span>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/inputs-validation.tsx"
    }
  ],
  "categories": [
    "styled",
    "inputs"
  ],
  "type": "registry:component"
}