{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "field-validation",
  "title": "Validation field",
  "description": "Styled field: the Validation set. 5 decorative field variations (ValidField, InvalidField, WarningField, HintField, LiveField) 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/field-validation.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { AlertTriangle, CheckCircle2, Info, XCircle } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Validation field family: 5 form-field wrappers that share the same layout but\n   each carry a different validation state (valid, invalid, warning, hint, live).\n   Each export is a complete field: a label + a real input + a state message; the\n   ids are produced with useId, the message is bound to the control with\n   aria-describedby, and in the invalid state aria-invalid is written ONTO THE\n   CONTROL. Color comes ONLY from tokens: success/danger/warning/muted. A static\n   layout - no animation. Renders with no props too. */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\nconst gap: Record<StyledSize, string> = {\n  sm: \"gap-1\",\n  md: \"gap-1.5\",\n  lg: \"gap-2\",\n  xl: \"gap-2.5\",\n}\n\nconst labelText: Record<StyledSize, string> = {\n  sm: \"text-xs\",\n  md: \"text-sm\",\n  lg: \"text-sm\",\n  xl: \"text-base\",\n}\n\nconst helpText: Record<StyledSize, string> = {\n  sm: \"text-xs\",\n  md: \"text-xs\",\n  lg: \"text-sm\",\n  xl: \"text-sm\",\n}\n\nconst controlHeight: 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\n/* Ortak kontrol stili. Gecersiz gorunum TEK standart: aria-invalid utility'leri. */\nconst controlBase =\n  \"w-full rounded-md border border-field-border bg-transparent px-3 py-1 text-foreground outline-none transition-colors placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-danger aria-invalid:ring-danger/20 dark:aria-invalid:ring-danger/40\"\n\n/* Message row: icon plus text. Both svg and i are targeted for icon compatibility. */\nconst messageRow =\n  \"flex items-center gap-1.5 leading-snug [&>svg]:size-3.5 [&>svg]:shrink-0 [&>i]:text-xs [&>i]:leading-none\"\n\nconst DEFAULT_LABEL = \"Email\"\nconst DEFAULT_PLACEHOLDER = \"you@example.com\"\n\ninterface FieldProps {\n  className?: string\n  size?: StyledSize\n  label?: React.ReactNode\n  description?: React.ReactNode\n  placeholder?: string\n}\n\n/* Shared shell: label plus control plus message; the ids and aria are wired here. */\nfunction ValidationShell({\n  className,\n  size = \"md\",\n  label = DEFAULT_LABEL,\n  description,\n  placeholder = DEFAULT_PLACEHOLDER,\n  invalid,\n  message,\n  messageTone,\n  borderClass,\n  value,\n  onValueChange,\n}: FieldProps & {\n  invalid?: boolean\n  message?: React.ReactNode\n  messageTone: string\n  borderClass?: string\n  value?: string\n  onValueChange?: (v: string) => void\n}) {\n  const base = React.useId()\n  const controlId = `${base}-control`\n  const descId = description != null && description !== false ? `${base}-desc` : undefined\n  const msgId = message != null && message !== false ? `${base}-msg` : undefined\n  const describedBy = [descId, msgId].filter(Boolean).join(\" \") || undefined\n\n  return (\n    <div data-slot=\"styled-field\" className={cn(\"flex w-full max-w-xs flex-col\", gap[size], className)}>\n      <label\n        htmlFor={controlId}\n        data-slot=\"styled-field-label\"\n        className={cn(\"w-fit font-medium leading-none text-foreground select-none\", labelText[size])}\n      >\n        {label}\n      </label>\n      <input\n        id={controlId}\n        placeholder={placeholder}\n        aria-describedby={describedBy}\n        aria-invalid={invalid ? true : undefined}\n        value={value}\n        onChange={onValueChange ? (e) => onValueChange(e.target.value) : undefined}\n        className={cn(controlBase, controlHeight[size], borderClass)}\n      />\n      {description != null && description !== false ? (\n        <p id={descId} data-slot=\"styled-field-description\" className={cn(\"leading-snug text-muted-foreground\", helpText[size])}>\n          {description}\n        </p>\n      ) : null}\n      {message != null && message !== false ? (\n        <p id={msgId} data-slot=\"styled-field-message\" className={cn(messageRow, helpText[size], messageTone)}>\n          {message}\n        </p>\n      ) : null}\n    </div>\n  )\n}\n\n/* Valid: dogrulanmis alan, success tonlu kenar + onay mesaji. */\nexport function ValidField({ className, size = \"md\", label = DEFAULT_LABEL, description, placeholder }: FieldProps) {\n  return (\n    <ValidationShell\n      className={className}\n      size={size}\n      label={label}\n      description={description}\n      placeholder={placeholder}\n      borderClass=\"border-success focus-visible:border-success focus-visible:ring-success/30\"\n      messageTone=\"font-medium text-success\"\n      message={\n        <>\n          <CheckCircle2 />\n          <span>This address looks good.</span>\n        </>\n      }\n    />\n  )\n}\n\n/* Invalid: aria-invalid on the control, with the danger message below. */\nexport function InvalidField({ className, size = \"md\", label = DEFAULT_LABEL, description, placeholder }: FieldProps) {\n  return (\n    <ValidationShell\n      className={className}\n      size={size}\n      label={label}\n      description={description}\n      placeholder={placeholder}\n      invalid\n      messageTone=\"font-medium text-danger\"\n      message={\n        <>\n          <XCircle />\n          <span>Enter a valid email address.</span>\n        </>\n      }\n    />\n  )\n}\n\n/* Warning: a valid entry that still needs attention; the warning tone, NO aria-invalid. */\nexport function WarningField({ className, size = \"md\", label = DEFAULT_LABEL, description, placeholder }: FieldProps) {\n  return (\n    <ValidationShell\n      className={className}\n      size={size}\n      label={label}\n      description={description}\n      placeholder={placeholder}\n      borderClass=\"border-warning focus-visible:border-warning focus-visible:ring-warning/30\"\n      messageTone=\"font-medium text-warning\"\n      message={\n        <>\n          <AlertTriangle />\n          <span>This domain is not usually allowed.</span>\n        </>\n      }\n    />\n  )\n}\n\n/* Hint: a neutral help message, carrying no state color at all. */\nexport function HintField({ className, size = \"md\", label = DEFAULT_LABEL, description, placeholder }: FieldProps) {\n  return (\n    <ValidationShell\n      className={className}\n      size={size}\n      label={label}\n      description={description}\n      placeholder={placeholder}\n      messageTone=\"text-muted-foreground\"\n      message={\n        <>\n          <Info />\n          <span>We only use this to send receipts.</span>\n        </>\n      }\n    />\n  )\n}\n\n/* Basit, deterministik email kontrolu. */\nfunction isEmail(v: string) {\n  const at = v.indexOf(\"@\")\n  if (at <= 0) return false\n  const dot = v.indexOf(\".\", at + 2)\n  return dot > at + 1 && dot < v.length - 1\n}\n\n/* Live: validates as you type. State is held at the top, not in the unmounting subtree. */\nexport function LiveField({ className, size = \"md\", label = DEFAULT_LABEL, description, placeholder }: FieldProps) {\n  const [value, setValue] = React.useState(\"\")\n  const touched = value.length > 0\n  const valid = isEmail(value)\n\n  return (\n    <ValidationShell\n      className={className}\n      size={size}\n      label={label}\n      description={description}\n      placeholder={placeholder}\n      value={value}\n      onValueChange={setValue}\n      invalid={touched && !valid}\n      borderClass={touched && valid ? \"border-success focus-visible:border-success focus-visible:ring-success/30\" : undefined}\n      messageTone={touched ? (valid ? \"font-medium text-success\" : \"font-medium text-danger\") : \"text-muted-foreground\"}\n      message={\n        touched ? (\n          valid ? (\n            <>\n              <CheckCircle2 />\n              <span>Looks like a valid address.</span>\n            </>\n          ) : (\n            <>\n              <XCircle />\n              <span>Keep typing a full email address.</span>\n            </>\n          )\n        ) : (\n          <>\n            <Info />\n            <span>Validation runs as you type.</span>\n          </>\n        )\n      }\n    />\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/field-validation.tsx"
    }
  ],
  "categories": [
    "styled",
    "field"
  ],
  "type": "registry:component"
}