{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "country-select",
  "title": "Country Select",
  "description": "A country picker: all 249 ISO 3166-1 countries, named in the reader's own language, sorted the way that language sorts, and searchable by local name, English name or two-letter code. Reach for it wherever a form asks where someone is or where something is going: sign-up and onboarding; billing, shipping and delivery addresses; the country on a tax, VAT or GST field; the list of countries you actually ship to; the residence or citizenship question in KYC, AML and identity verification; the country ahead of a phone number's dial code; nationality and place-of-birth fields on visa, travel and immigration forms; the market or region a product, price or licence is available in; bank-account, payout and remittance destinations; customs declarations; and the \"country\" row in profile, account and organisation settings. Common asks it answers: \"country select\", \"country picker\", \"country dropdown\", \"country selector react\", \"select country component\", \"list of countries react\", \"country combobox\", \"searchable country select\", \"country select with flags\", \"ISO country code select\", \"shadcn country select\", \"shadcn country picker\", \"react-select-country-list alternative\", \"country autocomplete\", \"country field for address form\", \"nationality select\". Official shadcn/ui has no country, region, locale or flag item of any kind — its select, native-select and combobox are empty controls that know nothing about the world — so the data is on you, and the data is where hand-rolled versions go wrong. This one carries no name table at all: it holds 249 two-letter codes and asks `Intl.DisplayNames` for the names, which means every country arrives already translated into the reader's language (JP is \"Japan\" in English and \"日本\" in Japanese) and nothing has to be re-translated or re-checked when a country is renamed upstream. The curation that matters is what is left out: `Intl.DisplayNames` will just as happily name EU (European Union), UN (United Nations), EZ (Eurozone), QO (Outlying Oceania) and 001 (world), none of which is a place a parcel can go, so the code list is explicit rather than generated. Sorting goes through `Intl.Collator`, the difference between a usable list and a broken one: a plain `sort()` orders by code point, which drops every country whose name begins with an accent — Åland Islands, Österreich — below Zimbabwe at the very bottom, exactly where nobody scrolls, and it does it in every language that has accents. The filter reads three names per country, because a person has three ways to name one: the local name, the English name (typed constantly on non-English sites, because it is what the passport and the shipping label say) and the code itself — so a Japanese page finds 日本 when someone types \"japan\" or \"JP\", and an exact two-letter code wins outright so \"in\" reaches India rather than burying it under Indonesia. Accents and punctuation are folded away, so \"cote divoire\" reaches Côte d'Ivoire despite the typographic apostrophe no keyboard produces. A real combobox, not a styled div: the trigger is a `type=\"button\"` with `role=\"combobox\"` and `aria-expanded`, the panel is a `listbox` driven by `aria-activedescendant`, arrow keys, Home, End, Enter and Escape all work, the highlight scrolls itself into view through 249 rows, opening a field that already says Japan starts on Japan, and an outside press closes it. Works controlled (`value` + `onValueChange`) or uncontrolled (`defaultValue`), always emitting the alpha-2 code and never a display name, so what you store survives the reader switching language; `name` adds a hidden input so it submits with a native form; `countries` narrows the list to the places you ship to, and a stored code outside that narrowed list still shows its own name instead of silently reading as \"nothing chosen\"; `priority` pins the two or three countries most sign-ups come from above the alphabet; `flags` adds the flag emoji as a hint next to the name — off by default, because Windows ships no flag glyphs and renders every one of them as two bare letters; and `getCountryName()` is exported so an order summary or confirmation email spells the country exactly the way the picker did. Styled entirely with shadcn tokens (input, ring, accent, popover, muted-foreground), so it follows light and dark mode, and it ships zero dependencies — no country-data package, no icon package, one file.",
  "dependencies": [],
  "files": [
    {
      "path": "registry/ui/country-select.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/**\n * Every officially assigned ISO 3166-1 alpha-2 code (249 of them), and nothing else.\n *\n * Codes only, deliberately. The names come from `Intl.DisplayNames`, which every current runtime\n * carries, so this component ships a 249-entry list of two-letter strings rather than a table of\n * 249 country names that would have to be re-translated for every language and re-checked every\n * time a country renames itself.\n *\n * The list is curated rather than generated at runtime because `Intl.DisplayNames` answers for far\n * more than countries: it will happily name `EU` (European Union), `UN` (United Nations), `EZ`\n * (Eurozone), `QO` (Outlying Oceania) and `001` (world). None of those belong in a field that asks\n * where someone lives or where a parcel is going, and the only place to draw that line is here.\n *\n * Territories with no permanent population — `AQ`, `BV`, `HM`, `TF`, `UM`, `GS` — are included\n * because they are officially assigned and some address and customs forms do accept them. Narrow\n * the list with `countries` rather than forking the file.\n */\nexport const COUNTRY_CODES = [\n  \"AD\", \"AE\", \"AF\", \"AG\", \"AI\", \"AL\", \"AM\", \"AO\", \"AQ\", \"AR\", \"AS\", \"AT\", \"AU\", \"AW\", \"AX\", \"AZ\",\n  \"BA\", \"BB\", \"BD\", \"BE\", \"BF\", \"BG\", \"BH\", \"BI\", \"BJ\", \"BL\", \"BM\", \"BN\", \"BO\", \"BQ\", \"BR\", \"BS\",\n  \"BT\", \"BV\", \"BW\", \"BY\", \"BZ\", \"CA\", \"CC\", \"CD\", \"CF\", \"CG\", \"CH\", \"CI\", \"CK\", \"CL\", \"CM\", \"CN\",\n  \"CO\", \"CR\", \"CU\", \"CV\", \"CW\", \"CX\", \"CY\", \"CZ\", \"DE\", \"DJ\", \"DK\", \"DM\", \"DO\", \"DZ\", \"EC\", \"EE\",\n  \"EG\", \"EH\", \"ER\", \"ES\", \"ET\", \"FI\", \"FJ\", \"FK\", \"FM\", \"FO\", \"FR\", \"GA\", \"GB\", \"GD\", \"GE\", \"GF\",\n  \"GG\", \"GH\", \"GI\", \"GL\", \"GM\", \"GN\", \"GP\", \"GQ\", \"GR\", \"GS\", \"GT\", \"GU\", \"GW\", \"GY\", \"HK\", \"HM\",\n  \"HN\", \"HR\", \"HT\", \"HU\", \"ID\", \"IE\", \"IL\", \"IM\", \"IN\", \"IO\", \"IQ\", \"IR\", \"IS\", \"IT\", \"JE\", \"JM\",\n  \"JO\", \"JP\", \"KE\", \"KG\", \"KH\", \"KI\", \"KM\", \"KN\", \"KP\", \"KR\", \"KW\", \"KY\", \"KZ\", \"LA\", \"LB\", \"LC\",\n  \"LI\", \"LK\", \"LR\", \"LS\", \"LT\", \"LU\", \"LV\", \"LY\", \"MA\", \"MC\", \"MD\", \"ME\", \"MF\", \"MG\", \"MH\", \"MK\",\n  \"ML\", \"MM\", \"MN\", \"MO\", \"MP\", \"MQ\", \"MR\", \"MS\", \"MT\", \"MU\", \"MV\", \"MW\", \"MX\", \"MY\", \"MZ\", \"NA\",\n  \"NC\", \"NE\", \"NF\", \"NG\", \"NI\", \"NL\", \"NO\", \"NP\", \"NR\", \"NU\", \"NZ\", \"OM\", \"PA\", \"PE\", \"PF\", \"PG\",\n  \"PH\", \"PK\", \"PL\", \"PM\", \"PN\", \"PR\", \"PS\", \"PT\", \"PW\", \"PY\", \"QA\", \"RE\", \"RO\", \"RS\", \"RU\", \"RW\",\n  \"SA\", \"SB\", \"SC\", \"SD\", \"SE\", \"SG\", \"SH\", \"SI\", \"SJ\", \"SK\", \"SL\", \"SM\", \"SN\", \"SO\", \"SR\", \"SS\",\n  \"ST\", \"SV\", \"SX\", \"SY\", \"SZ\", \"TC\", \"TD\", \"TF\", \"TG\", \"TH\", \"TJ\", \"TK\", \"TL\", \"TM\", \"TN\", \"TO\",\n  \"TR\", \"TT\", \"TV\", \"TW\", \"TZ\", \"UA\", \"UG\", \"UM\", \"US\", \"UY\", \"UZ\", \"VA\", \"VC\", \"VE\", \"VG\", \"VI\",\n  \"VN\", \"VU\", \"WF\", \"WS\", \"YE\", \"YT\", \"ZA\", \"ZM\", \"ZW\",\n] as const\n\n/**\n * A narrowed alpha-2 code, for callers building their own lists: `const SHIPPING: CountryCode[] =\n * [\"US\", \"CA\"]` catches a typo at compile time. The component's own props stay `string`, because a\n * value read back out of a database predates any type and still has to render.\n */\nexport type CountryCode = (typeof COUNTRY_CODES)[number]\n\nexport interface CountrySelectProps {\n  /** Controlled ISO 3166-1 alpha-2 code, e.g. \"JP\". Pair with `onValueChange`. */\n  value?: string\n  /** Starting code for an uncontrolled field. Ignored once `value` is passed. */\n  defaultValue?: string\n  /** Called with the chosen alpha-2 code. Never called with a display name. */\n  onValueChange?: (code: string) => void\n  /**\n   * Language the country names are shown in (default: the runtime's own). Passing this explicitly\n   * is what makes a server-rendered page deterministic — see the note on `mounted` below.\n   */\n  locale?: string\n  /**\n   * The codes to offer, in place of all 249. Pass the countries you actually ship to, or the ones\n   * a licence covers. Codes the runtime cannot name are dropped rather than shown raw.\n   */\n  countries?: readonly string[]\n  /**\n   * Codes pinned above the alphabet, in the order given — the two or three countries most of your\n   * signups come from. They stay in the main list too, so searching still finds them where a\n   * reader expects.\n   */\n  priority?: readonly string[]\n  /** Shown on the trigger while nothing is chosen. */\n  placeholder?: string\n  /** Shown in the filter box. Also its accessible name. */\n  searchPlaceholder?: string\n  /** Shown when the filter matches nothing. */\n  emptyMessage?: string\n  /** When set, a hidden input mirrors the code so it submits with a native form. */\n  name?: string\n  /**\n   * Render the flag emoji beside each name. Off by default: flag glyphs are absent on Windows,\n   * where every one of them falls back to the two letters of the code instead — fine as a hint,\n   * useless as the only label, which is why the name is always shown as well.\n   */\n  flags?: boolean\n  disabled?: boolean\n  /** Lands on the trigger, so a `<label htmlFor>` names the control. */\n  id?: string\n  className?: string\n  /** Give one of these, or an `id` paired with a visible `<label>`. */\n  \"aria-label\"?: string\n  \"aria-labelledby\"?: string\n}\n\n/**\n * The country's name in `locale`, or the code itself when the runtime has no name for it.\n *\n * Exported because the name is needed outside the field too — an order summary, a confirmation\n * email, a read-only profile row — and going through the same function keeps those spellings\n * identical to the one the user picked from.\n */\nexport function getCountryName(code: string, locale?: string): string {\n  try {\n    return new Intl.DisplayNames(locale, { type: \"region\" }).of(code) ?? code\n  } catch {\n    return code\n  }\n}\n\n/** The flag emoji for an alpha-2 code, built from the two regional indicator symbols. */\nexport function getCountryFlag(code: string): string {\n  if (!/^[A-Za-z]{2}$/.test(code)) return \"\"\n  return String.fromCodePoint(\n    ...[...code.toUpperCase()].map((c) => 0x1f1e6 + c.charCodeAt(0) - 65)\n  )\n}\n\n/**\n * Folded for searching: lower-cased, stripped of accents, and stripped of everything that is not a\n * letter or a digit.\n *\n * All three matter for countries specifically. Accents, because nobody types the circumflex in\n * \"Côte d'Ivoire\" and few keyboards make it easy. Punctuation, because that same name holds a\n * typographic apostrophe (U+2019) that no keyboard produces at all — searching \"cote divoire\" has\n * to reach it. Spaces, because \"unitedstates\" and \"United States\" should be the same query.\n */\nfunction fold(text: string): string {\n  return text\n    .normalize(\"NFD\")\n    .replace(/[̀-ͯ]/g, \"\")\n    .toLowerCase()\n    .replace(/[^\\p{Letter}\\p{Number}]/gu, \"\")\n}\n\ninterface CountryOption {\n  code: string\n  /** What the row renders, in the reader's language. */\n  name: string\n  /** `name`, folded. */\n  search: string\n  /** The English name, folded — see `matches`. */\n  searchEnglish: string\n}\n\n/**\n * How well `option` answers `query`, or -1 for no match at all. Higher is better.\n *\n * Three haystacks, because a country has three names a person might type. The one in front of them\n * (\"日本\"), the English one (\"Japan\" — typed constantly on non-English sites, because it is what\n * the passport and the shipping label say), and the code itself (\"JP\"). A picker that searches only\n * the rendered label fails a Japanese user typing \"japan\" and fails everyone typing the code.\n */\nfunction matches(option: CountryOption, query: string, rawQuery: string): number {\n  // An exact code is unambiguous and wins outright: \"in\" should not bury India under Indonesia.\n  if (rawQuery.length === 2 && option.code.toLowerCase() === rawQuery) return 4\n  if (option.search.startsWith(query)) return 3\n  if (option.searchEnglish.startsWith(query)) return 2\n  if (option.search.includes(query) || option.searchEnglish.includes(query)) return 1\n  return -1\n}\n\nconst ChevronIcon = ({ open }: { open: boolean }) => (\n  <svg\n    className={cn(\n      \"pointer-events-none absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground transition-transform\",\n      open && \"rotate-180\"\n    )}\n    width=\"16\"\n    height=\"16\"\n    viewBox=\"0 0 24 24\"\n    fill=\"none\"\n    stroke=\"currentColor\"\n    strokeWidth=\"2\"\n    strokeLinecap=\"round\"\n    strokeLinejoin=\"round\"\n    aria-hidden=\"true\"\n  >\n    <path d=\"m6 9 6 6 6-6\" />\n  </svg>\n)\n\nconst CheckIcon = () => (\n  <svg\n    className=\"h-4 w-4\"\n    width=\"16\"\n    height=\"16\"\n    viewBox=\"0 0 24 24\"\n    fill=\"none\"\n    stroke=\"currentColor\"\n    strokeWidth=\"2\"\n    strokeLinecap=\"round\"\n    strokeLinejoin=\"round\"\n    aria-hidden=\"true\"\n  >\n    <path d=\"M20 6 9 17l-5-5\" />\n  </svg>\n)\n\n/**\n * A country picker: all 249 ISO countries, named in the reader's language, sorted the way that\n * language sorts, and searchable by local name, English name or code.\n *\n * ```tsx\n * const [country, setCountry] = React.useState(\"\")\n *\n * return (\n *   <>\n *     <Label htmlFor=\"country\">Country</Label>\n *     <CountrySelect\n *       id=\"country\"\n *       name=\"country\"\n *       value={country}\n *       onValueChange={setCountry}\n *       priority={[\"US\", \"GB\", \"CA\"]}\n *       placeholder=\"Select a country\"\n *     />\n *   </>\n * )\n * ```\n *\n * Two things here are easy to get wrong by hand and are the reason this is a component rather than\n * a `<select>` with 249 hard-coded options. Sorting goes through `Intl.Collator`, because a plain\n * `sort()` orders by code point and drops every country whose name starts with an accent — Åland\n * Islands, Österreich — below Zimbabwe, at the very bottom where nobody scrolls. And the filter\n * reads three names per country, so a Japanese page still finds 日本 when someone types \"japan\".\n *\n * The value is always the alpha-2 code, never a display name, so what you store stays stable when\n * the reader's language changes or a country is renamed upstream.\n */\nexport function CountrySelect({\n  value: valueProp,\n  defaultValue,\n  onValueChange,\n  locale,\n  countries,\n  priority,\n  placeholder = \"Select a country\",\n  searchPlaceholder = \"Search countries…\",\n  emptyMessage = \"No country found.\",\n  name,\n  flags = false,\n  disabled = false,\n  id,\n  className,\n  \"aria-label\": ariaLabel,\n  \"aria-labelledby\": ariaLabelledby,\n}: CountrySelectProps) {\n  const isControlled = valueProp !== undefined\n  const [uncontrolled, setUncontrolled] = React.useState(defaultValue ?? \"\")\n  const value = isControlled ? valueProp : uncontrolled\n\n  const [open, setOpen] = React.useState(false)\n  const [query, setQuery] = React.useState(\"\")\n  const [active, setActive] = React.useState(0)\n\n  const generatedId = React.useId()\n  const triggerId = id ?? generatedId\n  const listboxId = `${generatedId}-listbox`\n  const rootRef = React.useRef<HTMLDivElement>(null)\n  const triggerRef = React.useRef<HTMLButtonElement>(null)\n  const searchRef = React.useRef<HTMLInputElement>(null)\n\n  /**\n   * Names are resolved after mount unless the caller pinned the language, and only then.\n   *\n   * `Intl.DisplayNames(undefined)` reads the runtime's own locale, and the server's locale is the\n   * server's — a page rendered in en-US on the server and read in ja-JP hydrates with a different\n   * word in the trigger, which is the classic mismatch. Passing `locale` removes the disagreement,\n   * so that case renders the real name on the first pass and never flashes. Left to the runtime,\n   * the trigger shows the code until hydration: present, correct, submittable, just terser.\n   */\n  const [mounted, setMounted] = React.useState(false)\n  React.useEffect(() => {\n    setMounted(true)\n  }, [])\n  const namesReady = mounted || locale !== undefined\n\n  const options = React.useMemo<CountryOption[]>(() => {\n    if (!namesReady) return []\n    const english = (code: string) => getCountryName(code, \"en\")\n    const collator = new Intl.Collator(locale)\n    return (countries ?? COUNTRY_CODES)\n      .map((code) => {\n        const name = getCountryName(code, locale)\n        return {\n          code,\n          name,\n          search: fold(name),\n          searchEnglish: fold(english(code)),\n        }\n      })\n      // A code the runtime cannot name comes back as the code itself. Offering it would put two\n      // raw letters in a list of country names, so it is dropped the way timezone-select drops a\n      // zone it cannot format.\n      .filter((option) => option.name !== option.code)\n      .sort((a, b) => collator.compare(a.name, b.name))\n  }, [countries, locale, namesReady])\n\n  const byCode = React.useMemo(() => {\n    const map = new Map<string, CountryOption>()\n    for (const option of options) map.set(option.code, option)\n    return map\n  }, [options])\n\n  /** The pinned rows, in the order the caller gave, skipping anything not on offer. */\n  const pinned = React.useMemo(() => {\n    if (!priority?.length) return []\n    return priority.map((code) => byCode.get(code)).filter(Boolean) as CountryOption[]\n  }, [priority, byCode])\n\n  /**\n   * The rows as drawn: pinned block first while the field is unfiltered, then the alphabet. Once\n   * there is a query the pinning is dropped — a search result ordered by anything other than how\n   * well it matched reads as a bug.\n   */\n  const rows = React.useMemo(() => {\n    const raw = query.trim().toLowerCase()\n    const folded = fold(query)\n    if (!folded) {\n      return {\n        pinned,\n        rest: options,\n        all: [...pinned, ...options],\n      }\n    }\n    const scored: Array<{ option: CountryOption; score: number }> = []\n    for (const option of options) {\n      const score = matches(option, folded, raw)\n      if (score >= 0) scored.push({ option, score })\n    }\n    // Stable within a score band: the collator already ordered `options`, and `sort` is stable, so\n    // equally good matches stay alphabetical instead of shuffling as the query grows.\n    scored.sort((a, b) => b.score - a.score)\n    const all = scored.map((s) => s.option)\n    return { pinned: [], rest: all, all }\n  }, [options, pinned, query])\n\n  const selected = value ? byCode.get(value) : undefined\n\n  /**\n   * What the trigger says. A stored code that is not on offer — a country dropped from a narrowed\n   * `countries`, or one saved before the list was narrowed — shows as its own name rather than\n   * falling back to the placeholder, which would read as \"nothing chosen\" and quietly lose the\n   * answer on the next save.\n   */\n  const triggerLabel = !value\n    ? placeholder\n    : selected\n      ? selected.name\n      : namesReady\n        ? getCountryName(value, locale)\n        : value\n\n  const openPanel = React.useCallback(() => {\n    if (disabled) return\n    setOpen(true)\n    setQuery(\"\")\n    setActive(0)\n  }, [disabled])\n\n  const closePanel = React.useCallback((refocus: boolean) => {\n    setOpen(false)\n    if (refocus) triggerRef.current?.focus()\n  }, [])\n\n  function choose(option: CountryOption) {\n    if (!isControlled) setUncontrolled(option.code)\n    onValueChange?.(option.code)\n    closePanel(true)\n  }\n\n  // Focus the filter box when the panel opens.\n  React.useEffect(() => {\n    if (!open) return\n    const timer = window.setTimeout(() => searchRef.current?.focus(), 0)\n    return () => window.clearTimeout(timer)\n  }, [open])\n\n  // Start on the chosen country, so opening a field that already says Japan lands on Japan rather\n  // than on Afghanistan 100 rows above it.\n  React.useEffect(() => {\n    if (!open || !value) return\n    const index = rows.all.findIndex((option) => option.code === value)\n    if (index >= 0) setActive(index)\n    // Only when the panel opens: re-running this as the query changes would drag the highlight\n    // back to the selected row after every keystroke.\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [open])\n\n  // Close on an outside pointer press (capture, so it beats focus moves).\n  React.useEffect(() => {\n    if (!open) return\n    function onPointerDown(event: PointerEvent) {\n      if (!rootRef.current?.contains(event.target as Node)) closePanel(false)\n    }\n    document.addEventListener(\"pointerdown\", onPointerDown, true)\n    return () => document.removeEventListener(\"pointerdown\", onPointerDown, true)\n  }, [open, closePanel])\n\n  // Clamp the highlight when the filter shrinks the list under it.\n  React.useEffect(() => {\n    setActive((current) => Math.min(current, Math.max(0, rows.all.length - 1)))\n  }, [rows.all.length])\n\n  // Keep the highlighted row on screen while arrowing through 249 of them. Looked up by id rather\n  // than queried off the list, because `useId` mints ids containing colons and a selector would\n  // have to be escaped before it parsed.\n  React.useEffect(() => {\n    if (!open) return\n    document.getElementById(`${generatedId}-opt-${active}`)?.scrollIntoView({ block: \"nearest\" })\n  }, [active, open, generatedId])\n\n  function handleTriggerKeyDown(event: React.KeyboardEvent) {\n    if (disabled) return\n    if (event.key === \"ArrowDown\" || event.key === \"ArrowUp\") {\n      event.preventDefault()\n      openPanel()\n    }\n  }\n\n  function handlePanelKeyDown(event: React.KeyboardEvent) {\n    switch (event.key) {\n      case \"ArrowDown\":\n        event.preventDefault()\n        setActive((a) => Math.max(0, Math.min(a + 1, rows.all.length - 1)))\n        break\n      case \"ArrowUp\":\n        event.preventDefault()\n        setActive((a) => Math.max(a - 1, 0))\n        break\n      case \"Home\":\n        event.preventDefault()\n        setActive(0)\n        break\n      case \"End\":\n        event.preventDefault()\n        setActive(Math.max(0, rows.all.length - 1))\n        break\n      case \"Enter\": {\n        event.preventDefault()\n        const option = rows.all[active]\n        if (option) choose(option)\n        break\n      }\n      case \"Escape\":\n        event.preventDefault()\n        closePanel(true)\n        break\n      case \"Tab\":\n        closePanel(false)\n        break\n    }\n  }\n\n  let index = -1\n  const renderRow = (option: CountryOption) => {\n    index += 1\n    const rowIndex = index\n    const isSelected = option.code === value\n    return (\n      <li\n        key={`${option.code}-${rowIndex}`}\n        id={`${generatedId}-opt-${rowIndex}`}\n        role=\"option\"\n        aria-selected={isSelected}\n        onPointerMove={() => setActive(rowIndex)}\n        // Keep focus in the filter box so the arrow keys still work after a click.\n        onPointerDown={(event) => event.preventDefault()}\n        onClick={() => choose(option)}\n        className={cn(\n          \"flex cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm\",\n          rowIndex === active && \"bg-accent text-accent-foreground\"\n        )}\n      >\n        <span className=\"flex h-4 w-4 shrink-0 items-center justify-center\">\n          {isSelected ? <CheckIcon /> : null}\n        </span>\n        {flags ? (\n          <span aria-hidden=\"true\" className=\"text-base leading-none\">\n            {getCountryFlag(option.code)}\n          </span>\n        ) : null}\n        <span className=\"truncate\">{option.name}</span>\n        <span className=\"ml-auto shrink-0 text-xs text-muted-foreground\">{option.code}</span>\n      </li>\n    )\n  }\n\n  return (\n    <div ref={rootRef} className={cn(\"relative\", className)}>\n      <button\n        ref={triggerRef}\n        id={triggerId}\n        // Never \"submit\": this control lives inside forms, and the browser's default would post\n        // the form the moment someone opened the country list.\n        type=\"button\"\n        role=\"combobox\"\n        aria-expanded={open}\n        aria-haspopup=\"listbox\"\n        aria-controls={open ? listboxId : undefined}\n        aria-label={ariaLabel}\n        aria-labelledby={ariaLabelledby}\n        disabled={disabled}\n        onClick={() => (open ? closePanel(false) : openPanel())}\n        onKeyDown={handleTriggerKeyDown}\n        className={cn(\n          \"flex h-9 w-full items-center gap-2 rounded-md border border-input bg-transparent py-1 pl-3 pr-8 text-left text-sm shadow-sm transition-colors\",\n          \"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring\",\n          \"disabled:cursor-not-allowed disabled:opacity-50\",\n          !value && \"text-muted-foreground\"\n        )}\n      >\n        {flags && value ? (\n          <span aria-hidden=\"true\" className=\"text-base leading-none\">\n            {getCountryFlag(value)}\n          </span>\n        ) : null}\n        <span className=\"truncate\">{triggerLabel}</span>\n        <ChevronIcon open={open} />\n      </button>\n\n      {open ? (\n        <div\n          onKeyDown={handlePanelKeyDown}\n          className=\"absolute z-50 mt-1 w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md\"\n        >\n          <input\n            ref={searchRef}\n            type=\"text\"\n            role=\"searchbox\"\n            autoComplete=\"off\"\n            value={query}\n            onChange={(event) => {\n              setQuery(event.target.value)\n              setActive(0)\n            }}\n            placeholder={searchPlaceholder}\n            aria-label={searchPlaceholder}\n            aria-controls={listboxId}\n            aria-activedescendant={\n              rows.all.length > 0 ? `${generatedId}-opt-${active}` : undefined\n            }\n            className=\"w-full border-b bg-transparent px-3 py-2 text-sm outline-none placeholder:text-muted-foreground\"\n          />\n          <ul\n            id={listboxId}\n            role=\"listbox\"\n            aria-label={ariaLabel ?? placeholder}\n            tabIndex={-1}\n            className=\"max-h-60 overflow-y-auto p-1 focus-visible:outline-none\"\n          >\n            {rows.all.length === 0 ? (\n              // Not an option, so it stays out of the listbox's owned children.\n              <li role=\"presentation\" className=\"px-2 py-4 text-center text-sm text-muted-foreground\">\n                {emptyMessage}\n              </li>\n            ) : null}\n            {rows.pinned.length > 0 ? (\n              <>\n                {rows.pinned.map(renderRow)}\n                <li role=\"presentation\" className=\"my-1 border-t\" />\n              </>\n            ) : null}\n            {rows.rest.map(renderRow)}\n          </ul>\n        </div>\n      ) : null}\n\n      {name ? <input type=\"hidden\" name={name} value={value} /> : null}\n    </div>\n  )\n}\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}