{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "inline-edit",
  "type": "registry:ui",
  "title": "InlineEdit",
  "description": "ویرایش درجا: کلیک یا Enter برای ویرایش، Enter ثبت، Esc انصراف",
  "registryDependencies": [
    "https://kashi.sepantapouya.com/r/cx.json",
    "https://kashi.sepantapouya.com/r/kashi-provider.json"
  ],
  "files": [
    {
      "path": "registry/kashi/InlineEdit.tsx",
      "type": "registry:ui",
      "target": "components/kashi/InlineEdit.tsx",
      "content": "\"use client\";\n\nimport { useEffect, useRef, useState, type ReactNode } from \"react\";\nimport { cx } from \"./cx\";\nimport { useKashi } from \"./KashiProvider\";\n\nexport interface InlineEditProps {\n  value?: string;\n  defaultValue?: string;\n  /** پس از ثبت؛ اگر Promise برگرداند تا پایانش منتظر می‌ماند و در خطا متن قبلی برمی‌گردد */\n  onSave?: (value: string) => void | Promise<void>;\n  /** نام فیلد برای صفحه‌خوان، مثل «نام پروژه» (الزامی) */\n  label: string;\n  placeholder?: string;\n  /** اعتبارسنجی؛ پیام خطا یا undefined */\n  validate?: (value: string) => string | undefined;\n  /** نمایش متن، مثلاً به‌شکل عنوان */\n  renderView?: (value: string) => ReactNode;\n  className?: string;\n  labels?: { edit?: string; save?: string; cancel?: string; empty?: string };\n}\n\nconst Pencil = () => <svg viewBox=\"0 0 20 20\" fill=\"none\" stroke=\"currentColor\" strokeWidth={1.7} strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\"><path d=\"m13.5 3.5 3 3L7 16H4v-3Z\" /></svg>;\nconst Check = () => <svg viewBox=\"0 0 20 20\" fill=\"none\" stroke=\"currentColor\" strokeWidth={2} strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\"><path d=\"m5 10.5 3.2 3L15 7\" /></svg>;\nconst X = () => <svg viewBox=\"0 0 20 20\" fill=\"none\" stroke=\"currentColor\" strokeWidth={1.8} strokeLinecap=\"round\" aria-hidden=\"true\"><path d=\"m6 6 8 8M14 6l-8 8\" /></svg>;\n\n/** ویرایش درجا: کلیک یا Enter برای ویرایش، Enter ثبت، Esc انصراف */\nexport function InlineEdit({ value, defaultValue = \"\", onSave, label, placeholder, validate, renderView, className, labels = {} }: InlineEditProps) {\n  const { messages: M } = useKashi();\n  const { edit = M.common.edit, save = M.common.save, cancel = M.common.cancel, empty = M.inlineEdit.empty } = labels;\n  const [inner, setInner] = useState(defaultValue);\n  const current = value ?? inner;\n  const [editing, setEditing] = useState(false);\n  const [draft, setDraft] = useState(current);\n  const [error, setError] = useState<string>();\n  const [busy, setBusy] = useState(false);\n  const input = useRef<HTMLInputElement>(null);\n  const view = useRef<HTMLButtonElement>(null);\n  const back = useRef(false);\n\n  useEffect(() => {\n    if (editing) input.current?.select();\n    else if (back.current) {\n      back.current = false;\n      view.current?.focus();\n    }\n  }, [editing]);\n\n  const start = () => {\n    setDraft(current);\n    setError(undefined);\n    setEditing(true);\n  };\n  const stop = () => {\n    back.current = true;\n    setEditing(false);\n  };\n  const commit = async () => {\n    const v = draft.trim();\n    const err = validate?.(v);\n    if (err) return setError(err);\n    if (v === current) return stop();\n    const prev = current;\n    if (value === undefined) setInner(v);\n    setBusy(true);\n    try {\n      await onSave?.(v);\n      stop();\n    } catch {\n      if (value === undefined) setInner(prev);\n      setError(M.inlineEdit.failed);\n    } finally {\n      setBusy(false);\n    }\n  };\n\n  if (!editing) {\n    return (\n      <span className={cx(\"k-inline-edit\", className)}>\n        <button ref={view} type=\"button\" className=\"k-inline-edit__view\" onClick={start} aria-label={`${label}: ${current || empty}${M.common.list}${edit}`}>\n          {current ? (renderView ? renderView(current) : current) : <span className=\"k-inline-edit__placeholder\">{placeholder ?? empty}</span>}\n          <Pencil />\n        </button>\n      </span>\n    );\n  }\n  const errId = `${label}-err`.replace(/\\s/g, \"-\");\n  return (\n    <span className={cx(\"k-inline-edit\", className)}>\n      <span className=\"k-field\" style={{ gap: 4 }}>\n        <span className=\"k-input\" data-state={error ? \"invalid\" : undefined}>\n          <input\n            ref={input}\n            className=\"k-input__control\"\n            aria-label={label}\n            value={draft}\n            placeholder={placeholder}\n            aria-invalid={error ? true : undefined}\n            aria-describedby={error ? errId : undefined}\n            disabled={busy}\n            onChange={(e) => {\n              setDraft(e.target.value);\n              setError(undefined);\n            }}\n            onKeyDown={(e) => {\n              if (e.key === \"Enter\") {\n                e.preventDefault();\n                commit();\n              } else if (e.key === \"Escape\") {\n                e.preventDefault();\n                stop();\n              }\n            }}\n          />\n        </span>\n        {error && <span className=\"k-hint k-hint--error\" id={errId}>{error}</span>}\n      </span>\n      <span className=\"k-inline-edit__actions\">\n        <button type=\"button\" className=\"k-btn k-btn--primary k-btn--icon k-btn--sm\" aria-label={save} aria-busy={busy || undefined} onClick={commit}><Check /></button>\n        <button type=\"button\" className=\"k-btn k-btn--secondary k-btn--icon k-btn--sm\" aria-label={cancel} onClick={stop}><X /></button>\n      </span>\n    </span>\n  );\n}\n"
    }
  ]
}