{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "author": "Lopifi (https://lopifi.com)",
  "categories": [
    "payments",
    "finance",
    "accounting"
  ],
  "dependencies": [
    "@base-ui-components/react"
  ],
  "description": "Searchable, with recents.",
  "files": [
    {
      "content": "\"use client\";\n\nimport { Popover } from \"@base-ui-components/react/popover\";\nimport * as React from \"react\";\nimport { cn } from \"@/lib/ledger/cn\";\nimport { CURRENCY_FLAGS } from \"@/components/ledger/currency-badge\";\nimport { CheckIcon, ChevronDownIcon, MagnifyingGlassIcon } from \"@/components/ledger/icons\";\n\nexport type CurrencyOption = { code: string; name: string; symbol: string };\n\nexport const COMMON_CURRENCIES: CurrencyOption[] = [\n  { code: \"USD\", name: \"US Dollar\", symbol: \"$\" },\n  { code: \"EUR\", name: \"Euro\", symbol: \"€\" },\n  { code: \"GBP\", name: \"British Pound\", symbol: \"£\" },\n  { code: \"JPY\", name: \"Japanese Yen\", symbol: \"¥\" },\n  { code: \"CHF\", name: \"Swiss Franc\", symbol: \"CHF\" },\n  { code: \"CAD\", name: \"Canadian Dollar\", symbol: \"$\" },\n  { code: \"AUD\", name: \"Australian Dollar\", symbol: \"$\" },\n  { code: \"SEK\", name: \"Swedish Krona\", symbol: \"kr\" },\n  { code: \"NOK\", name: \"Norwegian Krone\", symbol: \"kr\" },\n  { code: \"DKK\", name: \"Danish Krone\", symbol: \"kr\" },\n  { code: \"SGD\", name: \"Singapore Dollar\", symbol: \"$\" },\n  { code: \"HKD\", name: \"Hong Kong Dollar\", symbol: \"$\" },\n  { code: \"MXN\", name: \"Mexican Peso\", symbol: \"$\" },\n  { code: \"BRL\", name: \"Brazilian Real\", symbol: \"R$\" },\n  { code: \"INR\", name: \"Indian Rupee\", symbol: \"₹\" },\n  { code: \"ZAR\", name: \"South African Rand\", symbol: \"R\" },\n  { code: \"KWD\", name: \"Kuwaiti Dinar\", symbol: \"KD\" },\n  { code: \"PLN\", name: \"Polish Złoty\", symbol: \"zł\" },\n  { code: \"CZK\", name: \"Czech Koruna\", symbol: \"Kč\" },\n  { code: \"NZD\", name: \"New Zealand Dollar\", symbol: \"$\" },\n];\n\nexport interface CurrencySelectProps {\n  className?: string;\n  label?: string;\n  onValueChange: (code: string) => void;\n  options?: CurrencyOption[];\n  /** Pinned at the top — the shortest path for most users. */\n  recents?: string[];\n  value: string;\n}\n\n/** Searchable by code, name and symbol. The code is always shown. */\nexport function CurrencySelect({\n  value,\n  onValueChange,\n  recents = [],\n  options = COMMON_CURRENCIES,\n  className,\n  label = \"Currency\",\n}: CurrencySelectProps) {\n  const [open, setOpen] = React.useState(false);\n  const [query, setQuery] = React.useState(\"\");\n\n  const filtered = React.useMemo(() => {\n    const q = query.trim().toLowerCase();\n    if (!q) return options;\n    return options.filter(\n      (o) =>\n        o.code.toLowerCase().includes(q) ||\n        o.name.toLowerCase().includes(q) ||\n        o.symbol.toLowerCase().includes(q),\n    );\n  }, [options, query]);\n\n  const recentOptions = recents\n    .map((code) => options.find((o) => o.code === code))\n    .filter((o): o is CurrencyOption => Boolean(o));\n\n  return (\n    <Popover.Root onOpenChange={setOpen} open={open}>\n      <Popover.Trigger\n        aria-label={`${label}: ${value}`}\n        className={cn(\n          \"inline-flex h-10 items-center gap-2 rounded-sm border border-border bg-surface px-3\",\n          \"text-label font-medium transition-colors duration-1 ease-standard hover:bg-surface-hover\",\n          className,\n        )}\n      >\n        {CURRENCY_FLAGS[value] ? <span aria-hidden=\"true\">{CURRENCY_FLAGS[value]}</span> : null}\n        <span className=\"reference-numeric\">{value}</span>\n        <ChevronDownIcon aria-hidden=\"true\" className=\"size-3.5 text-text-tertiary\" />\n      </Popover.Trigger>\n      <Popover.Portal>\n        <Popover.Positioner align=\"end\" className=\"z-dropdown\" sideOffset={8}>\n          <Popover.Popup\n            className={cn(\n              \"w-[280px] overflow-hidden rounded-md border border-border bg-surface-raised shadow-elevation-3\",\n              \"data-[open]:ledger-enter\",\n            )}\n            style={{ transformOrigin: \"var(--transform-origin)\" }}\n          >\n            <div className=\"flex items-center gap-2 border-b border-border-subtle px-3\">\n              <MagnifyingGlassIcon aria-hidden=\"true\" className=\"size-4 text-text-tertiary\" />\n              <input\n                aria-label=\"Search currencies\"\n                autoFocus\n                className=\"h-10 flex-1 bg-transparent text-body outline-none placeholder:text-text-disabled\"\n                onChange={(e) => setQuery(e.target.value)}\n                placeholder=\"Search currencies\"\n                value={query}\n              />\n            </div>\n            <div className=\"max-h-[300px] overflow-y-auto py-1 scrollbar-thin\">\n              {!query && recentOptions.length > 0 ? (\n                <Group label=\"Recent\">\n                  {recentOptions.map((o) => (\n                    <Option\n                      key={`recent-${o.code}`}\n                      onSelect={() => {\n                        onValueChange(o.code);\n                        setOpen(false);\n                      }}\n                      option={o}\n                      selected={o.code === value}\n                    />\n                  ))}\n                </Group>\n              ) : null}\n              <Group label={query ? \"Results\" : \"All currencies\"}>\n                {filtered.map((o) => (\n                  <Option\n                    key={o.code}\n                    onSelect={() => {\n                      onValueChange(o.code);\n                      setOpen(false);\n                    }}\n                    option={o}\n                    selected={o.code === value}\n                  />\n                ))}\n                {filtered.length === 0 ? (\n                  <p className=\"px-3 py-4 text-caption text-text-secondary\">No currency matches “{query}”.</p>\n                ) : null}\n              </Group>\n            </div>\n          </Popover.Popup>\n        </Popover.Positioner>\n      </Popover.Portal>\n    </Popover.Root>\n  );\n}\n\nfunction Group({ label, children }: { label: string; children: React.ReactNode }) {\n  return (\n    <div className=\"py-1\">\n      <p className=\"px-3 pb-1 text-caption text-text-tertiary\">{label}</p>\n      {children}\n    </div>\n  );\n}\n\nfunction Option({\n  option,\n  selected,\n  onSelect,\n}: {\n  option: CurrencyOption;\n  selected: boolean;\n  onSelect: () => void;\n}) {\n  return (\n    <button\n      className={cn(\n        \"flex w-full items-center gap-2 px-3 py-2 text-left text-body\",\n        \"transition-colors duration-1 ease-standard hover:bg-surface-hover\",\n        selected && \"bg-surface-selected\",\n      )}\n      onClick={onSelect}\n      type=\"button\"\n    >\n      <span aria-hidden=\"true\" className=\"w-5\">\n        {CURRENCY_FLAGS[option.code] ?? \"\"}\n      </span>\n      <span className=\"reference-numeric w-10 font-medium\">{option.code}</span>\n      <span className=\"flex-1 truncate text-text-secondary\">{option.name}</span>\n      {selected ? <CheckIcon aria-hidden=\"true\" className=\"size-4 text-accent\" /> : null}\n    </button>\n  );\n}\n",
      "path": "components/ledger/currency-select.tsx",
      "target": "components/ledger/currency-select.tsx",
      "type": "registry:component"
    }
  ],
  "name": "currency-select",
  "registryDependencies": [
    "@lopifi/theme",
    "@lopifi/cn",
    "@lopifi/currency-badge",
    "@lopifi/icons"
  ],
  "title": "CurrencySelect",
  "type": "registry:component"
}
