{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "textarea-validation",
  "title": "Validation textarea",
  "description": "Styled textarea: the Validation set. 5 decorative textarea variations (ValidTextarea, InvalidTextarea, WarningTextarea, HintTextarea, LiveTextarea) 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/textarea-validation.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { AlertTriangle, Check, Info, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Validation textarea family: 5 multi-line fields that show a validation state.\n   In this family the border sits directly on the <textarea>, so the invalid\n   appearance stays byte-for-byte identical to the project standard:\n   aria-invalid:border-danger aria-invalid:ring-danger/20 dark:aria-invalid:ring-danger/40\n   Color comes ONLY from tokens (success/warning/danger + shadcn); ai2 danger is\n   used, NEVER shadcn destructive. The state visuals are driven by CSS + a small\n   piece of React state; there is no animation, so motion/react is not needed. Every\n   field has a visible label (the ids are produced with React.useId). Each one wraps\n   a real <textarea> and passes through all the native props. Size = min-height. */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\nconst height: Record<StyledSize, string> = {\n  sm: \"min-h-16 text-sm\",\n  md: \"min-h-20 text-sm\",\n  lg: \"min-h-24 text-base\",\n  xl: \"min-h-28 text-base\",\n}\n\n/* Kenarlik alanin kendisinde: aria-invalid standardi dogrudan uygulanabilsin. */\nconst areaBase =\n  \"w-full resize-y rounded-lg border bg-transparent px-3 py-2 text-foreground outline-none transition-colors placeholder:text-muted-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-danger aria-invalid:ring-danger/20 dark:aria-invalid:ring-danger/40\"\n\nconst rootBase = \"flex w-72 max-w-full flex-col gap-1.5\"\n\nconst labelBase = \"px-0.5 text-xs font-medium text-foreground\"\n\nconst noteBase =\n  \"flex items-start gap-1.5 px-0.5 text-xs [&>svg]:mt-px [&>svg]:size-3.5 [&>svg]:shrink-0 [&>i]:mt-px [&>i]:text-xs [&>i]:leading-none [&>i]:shrink-0\"\n\ntype Props = React.ComponentProps<\"textarea\"> & {\n  size?: StyledSize\n  /** Gorunur etiket metni. */\n  label?: string\n}\n\n/* Kontrollu/kontrolsuz fark etmeksizin girilen degeri izler. */\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<HTMLTextAreaElement>) => {\n    setInternal(e.target.value)\n    props.onChange?.(e)\n  }\n  return { value, onChange }\n}\n\n/* Valid: success kenarlik + altta onay notu. */\nexport function ValidTextarea({ className, size = \"md\", label = \"Message\", ...props }: Props) {\n  const id = React.useId()\n  const noteId = `${id}-note`\n  return (\n    <span data-slot=\"styled-textarea\" data-tone=\"success\" className={rootBase}>\n      <label htmlFor={id} className={labelBase}>\n        {label}\n      </label>\n      <textarea\n        id={id}\n        aria-describedby={noteId}\n        {...props}\n        className={cn(\n          areaBase,\n          \"border-success focus-visible:border-success focus-visible:ring-success/25\",\n          height[size],\n          className\n        )}\n      />\n      <span id={noteId} className={cn(noteBase, \"text-success\")}>\n        <Check aria-hidden=\"true\" />\n        Looks good.\n      </span>\n    </span>\n  )\n}\n\n/* Invalid: proje standardi gecersiz gorunum (aria-invalid) + danger mesaji. */\nexport function InvalidTextarea({\n  className,\n  size = \"md\",\n  label = \"Message\",\n  message = \"This field is required.\",\n  ...props\n}: Props & { message?: string }) {\n  const id = React.useId()\n  const msgId = `${id}-msg`\n  return (\n    <span data-slot=\"styled-textarea\" data-tone=\"danger\" className={rootBase}>\n      <label htmlFor={id} className={labelBase}>\n        {label}\n      </label>\n      <textarea\n        id={id}\n        aria-invalid=\"true\"\n        aria-describedby={msgId}\n        {...props}\n        className={cn(areaBase, \"border-field-border\", height[size], className)}\n      />\n      <span id={msgId} className={cn(noteBase, \"text-danger\")}>\n        <X aria-hidden=\"true\" />\n        {message}\n      </span>\n    </span>\n  )\n}\n\n/* Warning: a non-blocking warning; a warning border + a warning note. This is NOT an\n   error state, so it does not carry aria-invalid and data-tone stays neutral. */\nexport function WarningTextarea({\n  className,\n  size = \"md\",\n  label = \"Message\",\n  message = \"This may be too short to be useful.\",\n  ...props\n}: Props & { message?: string }) {\n  const id = React.useId()\n  const msgId = `${id}-msg`\n  return (\n    <span data-slot=\"styled-textarea\" data-tone=\"neutral\" className={rootBase}>\n      <label htmlFor={id} className={labelBase}>\n        {label}\n      </label>\n      <textarea\n        id={id}\n        aria-describedby={msgId}\n        {...props}\n        className={cn(\n          areaBase,\n          \"border-warning focus-visible:border-warning focus-visible:ring-warning/25\",\n          height[size],\n          className\n        )}\n      />\n      <span id={msgId} className={cn(noteBase, \"text-warning\")}>\n        <AlertTriangle aria-hidden=\"true\" />\n        {message}\n      </span>\n    </span>\n  )\n}\n\n/* Hint: a neutral border + help text below (guidance, not an error). */\nexport function HintTextarea({\n  className,\n  size = \"md\",\n  label = \"Message\",\n  message = \"Tell us what happened, in your own words.\",\n  ...props\n}: Props & { message?: string }) {\n  const id = React.useId()\n  const msgId = `${id}-msg`\n  return (\n    <span data-slot=\"styled-textarea\" data-tone=\"neutral\" className={rootBase}>\n      <label htmlFor={id} className={labelBase}>\n        {label}\n      </label>\n      <textarea\n        id={id}\n        aria-describedby={msgId}\n        {...props}\n        className={cn(areaBase, \"border-field-border focus-visible:border-ring\", height[size], className)}\n      />\n      <span id={msgId} className={cn(noteBase, \"text-muted-foreground\")}>\n        <Info aria-hidden=\"true\" />\n        {message}\n      </span>\n    </span>\n  )\n}\n\n/* Live: yazdikca karakter ve kelime sayisini dogrular. Bos iken notr; minLength\n   altinda veya maxLength ustunde gecersiz (aria-invalid), arada gecerli. Sayac\n   politede duyurulur. */\nexport function LiveTextarea({\n  className,\n  size = \"md\",\n  label = \"Message\",\n  minLength = 20,\n  maxLength = 160,\n  ...props\n}: Props) {\n  const id = React.useId()\n  const statusId = `${id}-status`\n  const { value, onChange } = useTrackedValue(props)\n  const chars = value.length\n  const words = value.trim() === \"\" ? 0 : value.trim().split(/\\s+/).length\n  const state: \"idle\" | \"valid\" | \"invalid\" =\n    chars === 0 ? \"idle\" : chars < minLength || chars > maxLength ? \"invalid\" : \"valid\"\n\n  const tone =\n    state === \"valid\"\n      ? \"border-success focus-visible:border-success focus-visible:ring-success/25\"\n      : state === \"invalid\"\n        ? \"border-field-border\"\n        : \"border-field-border focus-visible:border-ring\"\n\n  const note =\n    state === \"idle\"\n      ? `Write at least ${minLength} characters.`\n      : state === \"invalid\"\n        ? chars < minLength\n          ? `${minLength - chars} more characters needed.`\n          : `${chars - maxLength} characters over the limit.`\n        : `${words} ${words === 1 ? \"word\" : \"words\"}, ${chars}/${maxLength} characters.`\n\n  return (\n    <span data-slot=\"styled-textarea\" data-tone={state === \"idle\" ? \"neutral\" : state === \"valid\" ? \"success\" : \"danger\"} className={rootBase}>\n      <label htmlFor={id} className={labelBase}>\n        {label}\n      </label>\n      <textarea\n        id={id}\n        minLength={minLength}\n        aria-invalid={state === \"invalid\" ? \"true\" : undefined}\n        aria-describedby={statusId}\n        {...props}\n        onChange={onChange}\n        className={cn(areaBase, tone, height[size], className)}\n      />\n      <span\n        id={statusId}\n        role=\"status\"\n        aria-live=\"polite\"\n        className={cn(\n          noteBase,\n          \"justify-between tabular-nums\",\n          state === \"invalid\" ? \"text-danger\" : state === \"valid\" ? \"text-success\" : \"text-muted-foreground\"\n        )}\n      >\n        {note}\n      </span>\n    </span>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/textarea-validation.tsx"
    }
  ],
  "categories": [
    "styled",
    "textarea"
  ],
  "type": "registry:component"
}