{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "input",
  "type": "registry:ui",
  "title": "Input، Textarea",
  "description": "ورودی",
  "dependencies": [
    "@kashi-ui/persian@^1.7.0"
  ],
  "registryDependencies": [
    "https://kashi.sepantapouya.com/r/cx.json",
    "https://kashi.sepantapouya.com/r/field.json",
    "https://kashi.sepantapouya.com/r/kashi-provider.json",
    "https://kashi.sepantapouya.com/r/utils.json"
  ],
  "files": [
    {
      "path": "registry/kashi/Input.tsx",
      "type": "registry:ui",
      "target": "components/kashi/Input.tsx",
      "content": "\"use client\";\n\nimport {\n  forwardRef, useId, useRef, useState,\n  type ChangeEvent, type InputHTMLAttributes, type ReactNode, type TextareaHTMLAttributes,\n} from \"react\";\nimport { toFaDigits } from \"@kashi-ui/persian\";\nimport { cx } from \"./cx\";\nimport { Field, describedBy, type FieldClassNames } from \"./Field\";\nimport { mergeRefs, useIsomorphicLayoutEffect } from \"./utils\";\nimport { useKashi } from \"./KashiProvider\";\n\nexport type InputSize = \"sm\" | \"md\" | \"lg\";\n\n/** کلاس دلخواه برای هر بخش داخلی فیلد */\nexport interface InputClassNames extends FieldClassNames {\n  /** کادر دور فیلد (k-input) */\n  box?: string;\n  /** خود input یا textarea */\n  control?: string;\n  addon?: string;\n}\n\ninterface FieldOwnProps {\n  /** برچسب بالای فیلد — اگر نمی‌دهید حتماً aria-label بدهید */\n  label?: ReactNode;\n  /** متن راهنمای زیر فیلد */\n  hint?: ReactNode;\n  /** پیام خطا؛ فیلد را aria-invalid می‌کند */\n  error?: ReactNode;\n  /** نشان «اختیاری» کنار برچسب؛ رشته برای متن دلخواه */\n  optional?: boolean | string;\n  /** محتوای انتهای ردیف برچسب، مثل لینک «فراموشی رمز» */\n  labelEnd?: ReactNode;\n  /** نمایش شمارندهٔ نویسه؛ با maxLength به شکل «۱۲ / ۱۰۰» */\n  showCount?: boolean;\n  /** کلاس روی ظرف بیرونی (k-field) */\n  fieldClassName?: string;\n  classNames?: InputClassNames;\n}\n\nexport interface InputProps extends FieldOwnProps, Omit<InputHTMLAttributes<HTMLInputElement>, \"size\"> {\n  /** @default \"md\" */\n  size?: InputSize;\n  /** آیکون یا متن ابتدای فیلد */\n  addonStart?: ReactNode;\n  /** آیکون یا متن انتهای فیلد */\n  addonEnd?: ReactNode;\n  /** دکمهٔ پاک کردن وقتی فیلد مقدار دارد */\n  clearable?: boolean;\n  /** پس از پاک کردن */\n  onClear?: () => void;\n  /** برای type=\"password\": دکمهٔ نمایش و پنهان کردن رمز @default true */\n  revealable?: boolean;\n  /** اسپینر در انتهای فیلد، مثلاً هنگام بررسی نام کاربری */\n  loading?: boolean;\n  /**\n   * «code» برای کد تخفیف، کلید لایسنس، شناسه و مقدارهای شبیه شبا: متن تایپ‌شده تک‌فاصله، چپ‌به‌راست و جدا از متن فارسی\n   * (unicode-bidi: isolate) با فاصلهٔ حروف بیشتر، ولی متن placeholder با همان فونت فارسی می‌ماند. غلط‌گیر و حروف بزرگ خودکار خاموش‌اند.\n   * @default \"default\"\n   */\n  variant?: \"default\" | \"code\";\n  labels?: { clear?: string; show?: string; hide?: string; loading?: string };\n}\n\nconst Eye = ({ off }: { off?: boolean }) => (\n  <svg viewBox=\"0 0 20 20\" fill=\"none\" stroke=\"currentColor\" strokeWidth={1.7} strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n    <path d=\"M1.8 10S4.8 4.5 10 4.5 18.2 10 18.2 10 15.2 15.5 10 15.5 1.8 10 1.8 10Z\" />\n    <circle cx=\"10\" cy=\"10\" r=\"2.5\" />\n    {off && <path d=\"m3 3 14 14\" />}\n  </svg>\n);\nconst X = () => (\n  <svg viewBox=\"0 0 20 20\" fill=\"none\" stroke=\"currentColor\" strokeWidth={1.7} strokeLinecap=\"round\" aria-hidden=\"true\"><path d=\"m6 6 8 8M14 6l-8 8\" /></svg>\n);\n\n/** طول متن فعلی، برای کنترل‌شده و کنترل‌نشده */\nfunction useLength(value: unknown, defaultValue: unknown) {\n  const [len, setLen] = useState(() => String(defaultValue ?? \"\").length);\n  const controlled = value !== undefined;\n  return [controlled ? String(value ?? \"\").length : len, (e: { target: { value: string } }) => !controlled && setLen(e.target.value.length)] as const;\n}\n\n/** مقدار input را از بیرون عوض می‌کند و رویداد input می‌فرستد تا onChange در React هم اجرا شود */\nfunction setNativeValue(el: HTMLInputElement | HTMLTextAreaElement, value: string) {\n  const proto = el instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;\n  Object.getOwnPropertyDescriptor(proto, \"value\")?.set?.call(el, value);\n  el.dispatchEvent(new Event(\"input\", { bubbles: true }));\n}\n\nfunction Counter({ id, length, max }: { id: string; length: number; max?: number }) {\n  return (\n    <span className=\"k-counter\" id={id} data-over={max !== undefined && length > max ? \"\" : undefined}>\n      {max !== undefined ? `${toFaDigits(length)} / ${toFaDigits(max)}` : toFaDigits(length)}\n    </span>\n  );\n}\n\nexport const Input = forwardRef<HTMLInputElement, InputProps>(function Input(\n  {\n    label, hint, error, optional, labelEnd, showCount, size = \"md\", addonStart, addonEnd, clearable, onClear, revealable = true,\n    loading, variant = \"default\", labels = {}, required, id, type = \"text\", className, fieldClassName, classNames = {},\n    \"aria-describedby\": ariaDescribedBy, onChange, value, defaultValue, maxLength, disabled, readOnly, ...rest\n  },\n  ref,\n) {\n  const autoId = useId();\n  const inputId = id ?? autoId;\n  const inner = useRef<HTMLInputElement>(null);\n  const [revealed, setRevealed] = useState(false);\n  const [length, trackLength] = useLength(value, defaultValue);\n  const isPassword = type === \"password\";\n  const { messages: M } = useKashi();\n  const { clear = M.input.clear, show = M.input.show, hide = M.input.hide, loading: loadingLabel = M.input.checking } = labels;\n  const counterId = `${inputId}-count`;\n  const canClear = clearable && length > 0 && !disabled && !readOnly;\n\n  const end = (\n    <>\n      {loading && <span className=\"k-input__addon\"><span className=\"k-spinner k-spinner--sm\" role=\"status\" aria-label={loadingLabel} /></span>}\n      {canClear && (\n        <button\n          type=\"button\"\n          className=\"k-input__action\"\n          aria-label={clear}\n          onClick={() => {\n            if (inner.current) setNativeValue(inner.current, \"\");\n            inner.current?.focus();\n            onClear?.();\n          }}\n        >\n          <X />\n        </button>\n      )}\n      {isPassword && revealable && (\n        <button type=\"button\" className=\"k-input__action\" aria-label={revealed ? hide : show} aria-pressed={revealed} onClick={() => setRevealed((r) => !r)}>\n          <Eye off={revealed} />\n        </button>\n      )}\n      {addonEnd && <span className={cx(\"k-input__addon\", classNames.addon)}>{addonEnd}</span>}\n    </>\n  );\n\n  return (\n    <Field\n      id={inputId} label={label} hint={hint} error={error} required={required} optional={optional} labelEnd={labelEnd}\n      hintEnd={showCount ? <Counter id={counterId} length={length} max={maxLength} /> : undefined}\n      className={fieldClassName} classNames={classNames}\n    >\n      <div\n        className={cx(\"k-input\", size !== \"md\" && `k-input--${size}`, variant === \"code\" && \"k-input--code\", className, classNames.box)}\n        data-size={size}\n        data-state={disabled ? \"disabled\" : error ? \"invalid\" : undefined}\n      >\n        {addonStart && <span className={cx(\"k-input__addon\", classNames.addon)}>{addonStart}</span>}\n        <input\n          ref={mergeRefs(ref, inner)}\n          id={inputId}\n          className={cx(\"k-input__control\", classNames.control)}\n          type={isPassword && revealed ? \"text\" : type}\n          required={required}\n          value={value}\n          defaultValue={defaultValue}\n          maxLength={maxLength}\n          disabled={disabled}\n          readOnly={readOnly}\n          aria-invalid={error ? true : undefined}\n          aria-describedby={describedBy(inputId, hint, error, [ariaDescribedBy, showCount ? counterId : undefined].filter(Boolean).join(\" \") || undefined)}\n          {...(variant === \"code\" ? { spellCheck: false, autoCapitalize: \"off\", autoCorrect: \"off\", autoComplete: \"off\" } : {})}\n          onChange={(e: ChangeEvent<HTMLInputElement>) => {\n            trackLength(e);\n            onChange?.(e);\n          }}\n          {...rest}\n        />\n        {end}\n      </div>\n    </Field>\n  );\n});\n\nexport interface TextareaProps extends FieldOwnProps, TextareaHTMLAttributes<HTMLTextAreaElement> {\n  /** ارتفاع خودکار بر اساس متن */\n  autoResize?: boolean;\n  /** بیشترین تعداد خط در autoResize */\n  maxRows?: number;\n}\n\nexport const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(function Textarea(\n  {\n    label, hint, error, optional, labelEnd, showCount, autoResize, maxRows, required, id, className, fieldClassName, classNames = {},\n    \"aria-describedby\": ariaDescribedBy, onChange, value, defaultValue, maxLength, rows = 4, style, ...rest\n  },\n  ref,\n) {\n  const autoId = useId();\n  const inputId = id ?? autoId;\n  const inner = useRef<HTMLTextAreaElement>(null);\n  const [length, trackLength] = useLength(value, defaultValue);\n  const counterId = `${inputId}-count`;\n\n  // ارتفاع خودکار: در مرورگرهای جدید با field-sizing، وگرنه با اندازه‌گیری scrollHeight\n  const resize = () => {\n    const el = inner.current;\n    if (!el || !autoResize || CSS.supports?.(\"field-sizing\", \"content\")) return;\n    el.style.blockSize = \"auto\";\n    el.style.blockSize = `${el.scrollHeight}px`;\n  };\n  useIsomorphicLayoutEffect(resize, [value, autoResize]);\n\n  return (\n    <Field\n      id={inputId} label={label} hint={hint} error={error} required={required} optional={optional} labelEnd={labelEnd}\n      hintEnd={showCount ? <Counter id={counterId} length={length} max={maxLength} /> : undefined}\n      className={fieldClassName} classNames={classNames}\n    >\n      <div className={cx(\"k-input\", \"k-input--textarea\", autoResize && \"k-input--autoresize\", className, classNames.box)} data-state={error ? \"invalid\" : undefined}>\n        <textarea\n          ref={mergeRefs(ref, inner)}\n          id={inputId}\n          className={cx(\"k-input__control\", classNames.control)}\n          required={required}\n          rows={rows}\n          value={value}\n          defaultValue={defaultValue}\n          maxLength={maxLength}\n          style={maxRows ? { ...style, maxBlockSize: `${maxRows * 1.75 + 0.25}em` } : style}\n          aria-invalid={error ? true : undefined}\n          aria-describedby={describedBy(inputId, hint, error, [ariaDescribedBy, showCount ? counterId : undefined].filter(Boolean).join(\" \") || undefined)}\n          onChange={(e) => {\n            trackLength(e);\n            onChange?.(e);\n            resize();\n          }}\n          {...rest}\n        />\n      </div>\n    </Field>\n  );\n});\n"
    }
  ]
}