{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "city-select",
  "type": "registry:ui",
  "title": "CitySelect",
  "description": "انتخاب شهر با جستجو؛ با استان فقط شهرهای همان استان، بدون آن همهٔ ۱۲۰۰+ شهر ایران با نام استان",
  "dependencies": [
    "@kashi-ui/persian@^1.7.0"
  ],
  "registryDependencies": [
    "https://kashi.sepantapouya.com/r/combobox.json",
    "https://kashi.sepantapouya.com/r/cx.json",
    "https://kashi.sepantapouya.com/r/kashi-provider.json"
  ],
  "files": [
    {
      "path": "registry/kashi/CitySelect.tsx",
      "type": "registry:ui",
      "target": "components/kashi/CitySelect.tsx",
      "content": "\"use client\";\n\nimport { forwardRef, useMemo, useState } from \"react\";\nimport { CITIES, getCities } from \"@kashi-ui/persian\";\nimport { Combobox, searchText, type ComboboxOption, type ComboboxProps } from \"./Combobox\";\nimport { useKashi } from \"./KashiProvider\";\n\nexport interface CitySelectProps extends Omit<ComboboxProps, \"options\" | \"value\" | \"defaultValue\" | \"onChange\"> {\n  /** فقط شهرهای این استان؛ بدون استان، همهٔ شهرها با نام استانشان */\n  province?: string | null;\n  /** نام شهر */\n  value?: string | null;\n  defaultValue?: string | null;\n  /** استان هم برمی‌گردد تا بدون انتخاب استان، از روی شهر پر شود؛ برای شهر خارج از فهرست null */\n  onChange?: (city: string | null, province: string | null) => void;\n  /** وقتی شهر در فهرست نیست، همان متن تایپ‌شده پذیرفته شود (شهرهای تازه‌تأسیس) @default true */\n  allowCustom?: boolean;\n}\n\nconst SEP = \"|\";\n// «علی آباد»، «علی‌آباد» و «علیآباد» یکی‌اند\nconst loose = (s: string) => searchText(s).replace(/[\\s‌]/g, \"\");\nconst filter = (o: ComboboxOption, q: string) =>\n  o.value.startsWith(`custom${SEP}`) || [o.label, o.description ?? \"\"].some((t) => loose(t).includes(loose(q)));\n\n/** انتخاب شهر با جستجو؛ با استان فقط شهرهای همان استان، بدون آن همهٔ ۱۲۰۰+ شهر ایران با نام استان */\nexport const CitySelect = forwardRef<HTMLInputElement, CitySelectProps>(function CitySelect(\n  { province, value, defaultValue = null, onChange, allowCustom = true, label, placeholder, onInputChange, ...rest },\n  ref,\n) {\n  const { messages: M } = useKashi();\n  label ??= M.city.label;\n  const [query, setQuery] = useState(\"\");\n  const [inner, setInner] = useState<string | null>(defaultValue);\n  const city = value !== undefined ? value : inner;\n\n  const options = useMemo<ComboboxOption[]>(() => {\n    const list: ComboboxOption[] = province\n      ? getCities(province).map((c) => ({ value: `${province}${SEP}${c}`, label: c }))\n      : Object.entries(CITIES).flatMap(([p, cities]) => cities.map((c) => ({ value: `${p}${SEP}${c}`, label: c, description: p })));\n    const q = query.trim();\n    const known = list.some((o) => o.label === city);\n    // شهر انتخاب‌شده‌ای که در فهرست نیست هم باید در فیلد بماند\n    if (city && !known) list.unshift({ value: `custom${SEP}${city}`, label: city, description: M.city.outside });\n    if (allowCustom && q && q !== city && !list.some((o) => loose(o.label) === loose(q)))\n      list.push({ value: `custom${SEP}${q}`, label: q, description: M.city.custom });\n    return list;\n  }, [province, query, city, allowCustom]);\n\n  const selected = city ? options.find((o) => o.label === city && (!province || o.value.startsWith(`${province}${SEP}`) || o.value.startsWith(`custom${SEP}`)))?.value ?? null : null;\n\n  return (\n    <Combobox\n      ref={ref}\n      label={label}\n      placeholder={placeholder ?? (province ? M.city.search : M.city.hint)}\n      options={options}\n      filter={filter}\n      value={selected}\n      onInputChange={(q) => {\n        setQuery(q);\n        onInputChange?.(q);\n      }}\n      onChange={(v) => {\n        const [p, c] = v ? v.split(SEP) : [null, null];\n        const next = c ?? null;\n        if (value === undefined) setInner(next);\n        onChange?.(next, p === \"custom\" ? null : p);\n      }}\n      {...rest}\n    />\n  );\n});\n"
    }
  ]
}