@open-mercato/ui 0.6.1-develop.3278.1.a4d26475e4 → 0.6.1-develop.3291.1.6fad645fd0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +23 -14
  3. package/dist/backend/AppShell.js +8 -32
  4. package/dist/backend/AppShell.js.map +2 -2
  5. package/dist/backend/CrudForm.js +18 -0
  6. package/dist/backend/CrudForm.js.map +2 -2
  7. package/dist/backend/FilterBar.js +11 -14
  8. package/dist/backend/FilterBar.js.map +2 -2
  9. package/dist/backend/columns/ColumnChooserPanel.js +4 -6
  10. package/dist/backend/columns/ColumnChooserPanel.js.map +2 -2
  11. package/dist/backend/inputs/ComboboxInput.js +11 -7
  12. package/dist/backend/inputs/ComboboxInput.js.map +2 -2
  13. package/dist/backend/inputs/EventPatternInput.js +5 -2
  14. package/dist/backend/inputs/EventPatternInput.js.map +2 -2
  15. package/dist/backend/inputs/EventSelect.js +10 -4
  16. package/dist/backend/inputs/EventSelect.js.map +2 -2
  17. package/dist/backend/inputs/LookupSelect.js +51 -47
  18. package/dist/backend/inputs/LookupSelect.js.map +2 -2
  19. package/dist/backend/inputs/PhoneNumberField.js +196 -45
  20. package/dist/backend/inputs/PhoneNumberField.js.map +2 -2
  21. package/dist/backend/inputs/SwitchableMarkdownInput.js.map +2 -2
  22. package/dist/backend/messages/message-compose-form-groups.js +2 -2
  23. package/dist/backend/messages/message-compose-form-groups.js.map +2 -2
  24. package/dist/backend/utils/serverErrors.js +6 -2
  25. package/dist/backend/utils/serverErrors.js.map +2 -2
  26. package/dist/portal/utils/nav.js +17 -0
  27. package/dist/portal/utils/nav.js.map +2 -2
  28. package/dist/primitives/amount-input.js +128 -0
  29. package/dist/primitives/amount-input.js.map +7 -0
  30. package/dist/primitives/button-input.js +47 -0
  31. package/dist/primitives/button-input.js.map +7 -0
  32. package/dist/primitives/card-input.js +133 -0
  33. package/dist/primitives/card-input.js.map +7 -0
  34. package/dist/primitives/email-input.js +29 -0
  35. package/dist/primitives/email-input.js.map +7 -0
  36. package/dist/primitives/password-input.js +64 -0
  37. package/dist/primitives/password-input.js.map +7 -0
  38. package/dist/primitives/search-input.js +57 -0
  39. package/dist/primitives/search-input.js.map +7 -0
  40. package/dist/primitives/select.js +43 -5
  41. package/dist/primitives/select.js.map +2 -2
  42. package/dist/primitives/website-input.js +56 -0
  43. package/dist/primitives/website-input.js.map +7 -0
  44. package/package.json +3 -3
  45. package/src/backend/AppShell.tsx +7 -30
  46. package/src/backend/CrudForm.tsx +17 -0
  47. package/src/backend/FilterBar.tsx +5 -6
  48. package/src/backend/__tests__/serverErrors.test.ts +79 -1
  49. package/src/backend/columns/ColumnChooserPanel.tsx +4 -5
  50. package/src/backend/inputs/ComboboxInput.tsx +32 -26
  51. package/src/backend/inputs/EventPatternInput.tsx +11 -2
  52. package/src/backend/inputs/EventSelect.tsx +13 -4
  53. package/src/backend/inputs/LookupSelect.tsx +75 -53
  54. package/src/backend/inputs/PhoneNumberField.tsx +237 -47
  55. package/src/backend/inputs/SwitchableMarkdownInput.tsx +30 -0
  56. package/src/backend/inputs/__tests__/ComboboxInput.test.tsx +4 -0
  57. package/src/backend/inputs/__tests__/PhoneNumberField.test.tsx +4 -0
  58. package/src/backend/messages/message-compose-form-groups.tsx +2 -2
  59. package/src/backend/utils/serverErrors.ts +15 -8
  60. package/src/portal/utils/__tests__/nav.test.ts +80 -0
  61. package/src/portal/utils/nav.ts +23 -1
  62. package/src/primitives/__tests__/password-input.test.tsx +93 -0
  63. package/src/primitives/__tests__/search-input.test.tsx +95 -0
  64. package/src/primitives/__tests__/zindex-overlay.test.tsx +4 -0
  65. package/src/primitives/amount-input.tsx +184 -0
  66. package/src/primitives/button-input.tsx +68 -0
  67. package/src/primitives/card-input.tsx +193 -0
  68. package/src/primitives/email-input.tsx +36 -0
  69. package/src/primitives/password-input.tsx +100 -0
  70. package/src/primitives/search-input.tsx +87 -0
  71. package/src/primitives/select.tsx +98 -11
  72. package/src/primitives/website-input.tsx +72 -0
@@ -1,7 +1,8 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
- import { Loader2, Search, X } from "lucide-react";
4
+ import { Check, Loader2, Search, X } from "lucide-react";
5
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
5
6
  import { Button } from "../../primitives/button.js";
6
7
  import { cn } from "@open-mercato/shared/lib/utils";
7
8
  function LookupSelect({
@@ -14,19 +15,32 @@ function LookupSelect({
14
15
  actionSlot,
15
16
  onReady,
16
17
  placeholder,
17
- searchPlaceholder = placeholder ?? "Search\u2026",
18
- clearLabel = "Clear selection",
19
- emptyLabel = "No results",
20
- loadingLabel = "Searching\u2026",
21
- selectLabel = "Select",
22
- selectedLabel = "Selected",
18
+ searchPlaceholder,
19
+ clearLabel,
20
+ emptyLabel,
21
+ loadingLabel,
22
+ selectLabel,
23
+ selectedLabel,
23
24
  minQueryHintLabel,
24
- startTypingLabel = "Start typing to search.",
25
+ startTypingLabel,
25
26
  selectedHintLabel,
26
27
  disabled = false,
27
28
  loading: loadingProp = false,
28
29
  defaultOpen = false
29
30
  }) {
31
+ const t = useT();
32
+ const resolvedSearchPlaceholder = searchPlaceholder ?? placeholder ?? t("ui.lookupSelect.searchPlaceholder", "Search\u2026");
33
+ const resolvedClearLabel = clearLabel ?? t("ui.lookupSelect.clearSelection", "Clear selection");
34
+ const resolvedEmptyLabel = emptyLabel ?? t("ui.lookupSelect.noResults", "No results");
35
+ const resolvedLoadingLabel = loadingLabel ?? t("ui.lookupSelect.searching", "Searching\u2026");
36
+ const resolvedSelectLabel = selectLabel ?? t("ui.lookupSelect.select", "Select");
37
+ const resolvedSelectedLabel = selectedLabel ?? t("ui.lookupSelect.selected", "Selected");
38
+ const resolvedStartTypingLabel = startTypingLabel ?? t("ui.lookupSelect.startTyping", "Start typing to search.");
39
+ const resolvedMinQueryHintLabel = minQueryHintLabel ?? t(
40
+ "ui.lookupSelect.minQueryHint",
41
+ "Type at least {minQuery} characters or paste an id to search.",
42
+ { minQuery: String(minQuery) }
43
+ );
30
44
  const [query, setQuery] = React.useState("");
31
45
  const [items, setItems] = React.useState(options ?? []);
32
46
  const [loading, setLoading] = React.useState(false);
@@ -95,17 +109,17 @@ function LookupSelect({
95
109
  return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
96
110
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 sm:flex-row sm:items-center sm:gap-3", children: [
97
111
  /* @__PURE__ */ jsxs("div", { className: "relative flex-1", children: [
98
- /* @__PURE__ */ jsx(Search, { className: "pointer-events-none absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" }),
112
+ /* @__PURE__ */ jsx(Search, { className: "pointer-events-none absolute left-3.5 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" }),
99
113
  /* @__PURE__ */ jsx(
100
114
  "input",
101
115
  {
102
- className: "w-full rounded border pl-8 pr-2 py-2 text-sm",
116
+ className: "w-full h-10 rounded-lg border border-input bg-background pl-10 pr-3 text-sm shadow-xs transition-colors outline-none placeholder:text-muted-foreground hover:border-foreground/20 focus-visible:shadow-focus focus-visible:border-brand-violet disabled:bg-bg-disabled disabled:border-border-disabled disabled:text-muted-foreground disabled:cursor-not-allowed",
103
117
  value: query,
104
118
  onChange: (event) => {
105
119
  setQuery(event.target.value);
106
120
  setHasTyped(true);
107
121
  },
108
- placeholder: searchPlaceholder,
122
+ placeholder: resolvedSearchPlaceholder,
109
123
  disabled
110
124
  }
111
125
  )
@@ -115,60 +129,50 @@ function LookupSelect({
115
129
  shouldSearch ? /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
116
130
  loading || loadingProp ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
117
131
  /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }),
118
- loadingLabel
132
+ resolvedLoadingLabel
119
133
  ] }) : null,
120
- !loading && !loadingProp && !items.length ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: emptyLabel }) : null,
121
- /* @__PURE__ */ jsx("div", { className: "space-y-2 max-h-80 overflow-y-auto", children: items.map((item) => {
134
+ !loading && !loadingProp && !items.length ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: resolvedEmptyLabel }) : null,
135
+ /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-1.5 max-h-80 overflow-y-auto -mx-0.5 px-0.5 py-0.5", children: items.map((item) => {
122
136
  const isSelected = value === item.id;
137
+ const isInteractive = !item.disabled || isSelected;
123
138
  return /* @__PURE__ */ jsxs(
124
139
  "div",
125
140
  {
126
141
  className: cn(
127
- "flex gap-3 rounded border bg-card p-3 transition-colors cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background",
128
- isSelected ? "border-primary/70 bg-primary/5" : "hover:border-primary/50"
142
+ "group flex items-center gap-4 rounded-xl border p-4 transition-all duration-150 focus-visible:outline-none focus-visible:shadow-focus",
143
+ isInteractive ? "cursor-pointer" : "cursor-not-allowed opacity-60",
144
+ isSelected ? "border-brand-violet bg-brand-violet/5 shadow-sm" : "border-input bg-card hover:border-foreground/20 hover:bg-muted/30 hover:shadow-sm"
129
145
  ),
130
146
  role: "button",
131
147
  tabIndex: item.disabled ? -1 : 0,
132
148
  onClick: () => {
133
- if (item.disabled && !isSelected) return;
149
+ if (!isInteractive) return;
134
150
  onChange(item.id);
135
151
  },
136
152
  onKeyDown: (event) => {
137
153
  if (event.key === "Enter" || event.key === " ") {
138
154
  event.preventDefault();
139
- if (item.disabled && !isSelected) return;
155
+ if (!isInteractive) return;
140
156
  onChange(item.id);
141
157
  }
142
158
  },
143
159
  "aria-pressed": isSelected,
160
+ "aria-disabled": item.disabled && !isSelected ? true : void 0,
161
+ title: isSelected ? resolvedSelectedLabel : resolvedSelectLabel,
144
162
  children: [
145
- /* @__PURE__ */ jsx("div", { className: "flex h-10 w-10 sm:h-12 sm:w-12 shrink-0 items-center justify-center overflow-hidden rounded border bg-muted", children: item.icon ?? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\u2022" }) }),
146
- /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [
147
- /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-2", children: [
148
- /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
149
- /* @__PURE__ */ jsx("div", { className: "truncate text-sm font-medium", children: item.title }),
150
- item.subtitle ? /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground truncate", children: item.subtitle }) : null,
151
- item.description ? /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground truncate", children: item.description }) : null
152
- ] }),
153
- item.rightLabel ? /* @__PURE__ */ jsx("div", { className: "shrink-0 text-xs font-medium text-muted-foreground", children: item.rightLabel }) : null
163
+ item.icon ? /* @__PURE__ */ jsx("div", { className: "flex h-12 w-12 shrink-0 items-center justify-center overflow-hidden [&>svg]:size-6 [&_svg]:text-muted-foreground", children: item.icon }) : /* @__PURE__ */ jsx("div", { className: cn(
164
+ "flex h-12 w-12 shrink-0 items-center justify-center overflow-hidden rounded-lg border transition-colors",
165
+ isSelected ? "border-brand-violet/40 bg-brand-violet/10 text-brand-violet" : "border-input bg-muted text-muted-foreground group-hover:border-foreground/20"
166
+ ), children: /* @__PURE__ */ jsx("span", { className: "text-base font-semibold uppercase", children: item.title.slice(0, 1) }) }),
167
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [
168
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3", children: [
169
+ /* @__PURE__ */ jsx("div", { className: "truncate text-sm font-semibold text-foreground", children: item.title }),
170
+ item.rightLabel ? /* @__PURE__ */ jsx("div", { className: "shrink-0 text-overline font-medium uppercase tracking-wider text-muted-foreground", children: item.rightLabel }) : null
154
171
  ] }),
155
- /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx(
156
- Button,
157
- {
158
- type: "button",
159
- variant: isSelected ? "secondary" : "outline",
160
- size: "sm",
161
- className: "shrink-0",
162
- onClick: (event) => {
163
- event.stopPropagation();
164
- if (item.disabled && !isSelected) return;
165
- onChange(item.id);
166
- },
167
- disabled: item.disabled && !isSelected,
168
- children: isSelected ? selectedLabel : selectLabel
169
- }
170
- ) })
171
- ] })
172
+ item.subtitle ? /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground truncate", children: item.subtitle }) : null,
173
+ item.description ? /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground/70 truncate", children: item.description }) : null
174
+ ] }),
175
+ /* @__PURE__ */ jsx("div", { className: "flex shrink-0 items-center justify-center", children: isSelected ? /* @__PURE__ */ jsx(Check, { className: "size-5 text-brand-violet", "aria-hidden": "true" }) : /* @__PURE__ */ jsx("div", { className: "size-5", "aria-hidden": "true" }) })
172
176
  ]
173
177
  },
174
178
  item.id
@@ -184,12 +188,12 @@ function LookupSelect({
184
188
  onClick: () => onChange(null),
185
189
  children: [
186
190
  /* @__PURE__ */ jsx(X, { className: "h-4 w-4" }),
187
- clearLabel
191
+ resolvedClearLabel
188
192
  ]
189
193
  }
190
194
  ) : null
191
- ] }) : hasTyped ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: minQueryHintLabel ?? `Type at least ${minQuery} characters or paste an id to search.` }) : /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: startTypingLabel }),
192
- error ? /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: emptyLabel }) : null
195
+ ] }) : hasTyped ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: resolvedMinQueryHintLabel }) : /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: resolvedStartTypingLabel }),
196
+ error ? /* @__PURE__ */ jsx("p", { className: "text-xs text-status-error-text", role: "alert", children: resolvedEmptyLabel }) : null
193
197
  ] });
194
198
  }
195
199
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/inputs/LookupSelect.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Loader2, Search, X } from 'lucide-react'\nimport { Button } from '../../primitives/button'\nimport { cn } from '@open-mercato/shared/lib/utils'\n\nexport type LookupSelectItem = {\n id: string\n title: string\n subtitle?: string | null\n badge?: string | null\n icon?: React.ReactNode\n disabled?: boolean\n rightLabel?: string | null\n description?: string | null\n}\n\ntype LookupSelectProps = {\n value: string | null\n onChange: (next: string | null) => void\n fetchItems?: (query: string) => Promise<LookupSelectItem[]>\n fetchOptions?: (query?: string) => Promise<LookupSelectItem[]>\n options?: LookupSelectItem[]\n minQuery?: number\n actionSlot?: React.ReactNode\n onReady?: (controls: { setQuery: (value: string) => void }) => void\n searchPlaceholder?: string\n placeholder?: string\n clearLabel?: string\n emptyLabel?: string\n loadingLabel?: string\n selectLabel?: string\n selectedLabel?: string\n minQueryHintLabel?: string\n startTypingLabel?: string\n selectedHintLabel?: (id: string) => string\n disabled?: boolean\n loading?: boolean\n defaultOpen?: boolean\n}\n\nexport function LookupSelect({\n value,\n onChange,\n fetchItems,\n fetchOptions,\n options,\n minQuery = 2,\n actionSlot,\n onReady,\n placeholder,\n searchPlaceholder = placeholder ?? 'Search\u2026',\n clearLabel = 'Clear selection',\n emptyLabel = 'No results',\n loadingLabel = 'Searching\u2026',\n selectLabel = 'Select',\n selectedLabel = 'Selected',\n minQueryHintLabel,\n startTypingLabel = 'Start typing to search.',\n selectedHintLabel,\n disabled = false,\n loading: loadingProp = false,\n defaultOpen = false,\n}: LookupSelectProps) {\n const [query, setQuery] = React.useState('')\n const [items, setItems] = React.useState<LookupSelectItem[]>(options ?? [])\n const [loading, setLoading] = React.useState(false)\n const [hasTyped, setHasTyped] = React.useState(defaultOpen)\n const [error, setError] = React.useState<string | null>(null)\n const [fetchKey, setFetchKey] = React.useState(0)\n const fetchItemsRef = React.useRef(fetchItems ?? fetchOptions)\n const setQueryRef = React.useRef(setQuery)\n const optionsWasArrayRef = React.useRef(Array.isArray(options))\n\n React.useEffect(() => {\n fetchItemsRef.current = fetchItems ?? fetchOptions\n }, [fetchItems, fetchOptions])\n\n React.useEffect(() => {\n if (Array.isArray(options)) {\n optionsWasArrayRef.current = true\n setItems(options)\n } else if (optionsWasArrayRef.current) {\n optionsWasArrayRef.current = false\n setFetchKey((k) => k + 1)\n }\n }, [options])\n\n React.useEffect(() => {\n setQueryRef.current = setQuery\n if (onReady) onReady({ setQuery })\n }, [onReady, setQuery])\n\n const shouldSearch =\n defaultOpen || query.trim().length >= minQuery || Boolean(value && (options?.length ?? 0) > 0)\n React.useEffect(() => {\n if (disabled) {\n setItems(options ?? [])\n setLoading(false)\n return\n }\n let cancelled = false\n let timer: ReturnType<typeof setTimeout> | null = null\n if (!shouldSearch) {\n setItems(options ?? [])\n setLoading(false)\n setError(null)\n return () => { cancelled = true }\n }\n setLoading(true)\n setError(null)\n timer = setTimeout(() => {\n const requestId = Date.now()\n const fetcher = fetchItemsRef.current\n const loader = fetcher ?? (() => Promise.resolve(options ?? []))\n loader(query.trim())\n .then((result) => {\n if (cancelled) return\n setItems(result)\n })\n .catch((err) => {\n if (cancelled) return\n console.error('LookupSelect.fetchItems', err)\n setError('error')\n })\n .finally(() => {\n if (!cancelled) setLoading(false)\n })\n return requestId\n }, 220)\n return () => {\n cancelled = true\n if (timer) clearTimeout(timer)\n }\n }, [query, shouldSearch, fetchKey])\n\n return (\n <div className=\"space-y-3\">\n <div className=\"flex flex-col gap-2 sm:flex-row sm:items-center sm:gap-3\">\n <div className=\"relative flex-1\">\n <Search className=\"pointer-events-none absolute left-2 top-2.5 h-4 w-4 text-muted-foreground\" />\n <input\n className=\"w-full rounded border pl-8 pr-2 py-2 text-sm\"\n value={query}\n onChange={(event) => {\n setQuery(event.target.value)\n setHasTyped(true)\n }}\n placeholder={searchPlaceholder}\n disabled={disabled}\n />\n </div>\n {actionSlot ? <div className=\"sm:self-start\">{actionSlot}</div> : null}\n </div>\n {shouldSearch ? (\n <div className=\"space-y-2\">\n {loading || loadingProp ? (\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n {loadingLabel}\n </div>\n ) : null}\n {!loading && !loadingProp && !items.length ? (\n <p className=\"text-xs text-muted-foreground\">{emptyLabel}</p>\n ) : null}\n <div className=\"space-y-2 max-h-80 overflow-y-auto\">\n {items.map((item) => {\n const isSelected = value === item.id\n return (\n <div\n key={item.id}\n className={cn(\n 'flex gap-3 rounded border bg-card p-3 transition-colors cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background',\n isSelected ? 'border-primary/70 bg-primary/5' : 'hover:border-primary/50'\n )}\n role=\"button\"\n tabIndex={item.disabled ? -1 : 0}\n onClick={() => {\n if (item.disabled && !isSelected) return\n onChange(item.id)\n }}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n if (item.disabled && !isSelected) return\n onChange(item.id)\n }\n }}\n aria-pressed={isSelected}\n >\n <div className=\"flex h-10 w-10 sm:h-12 sm:w-12 shrink-0 items-center justify-center overflow-hidden rounded border bg-muted\">\n {item.icon ?? <span className=\"text-muted-foreground\">\u2022</span>}\n </div>\n <div className=\"flex min-w-0 flex-1 flex-col gap-1\">\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"min-w-0\">\n <div className=\"truncate text-sm font-medium\">{item.title}</div>\n {item.subtitle ? (\n <div className=\"text-xs text-muted-foreground truncate\">{item.subtitle}</div>\n ) : null}\n {item.description ? (\n <div className=\"text-xs text-muted-foreground truncate\">{item.description}</div>\n ) : null}\n </div>\n {item.rightLabel ? (\n <div className=\"shrink-0 text-xs font-medium text-muted-foreground\">{item.rightLabel}</div>\n ) : null}\n </div>\n <div className=\"flex justify-end\">\n <Button\n type=\"button\"\n variant={isSelected ? 'secondary' : 'outline'}\n size=\"sm\"\n className=\"shrink-0\"\n onClick={(event) => {\n event.stopPropagation()\n if (item.disabled && !isSelected) return\n onChange(item.id)\n }}\n disabled={item.disabled && !isSelected}\n >\n {isSelected ? selectedLabel : selectLabel}\n </Button>\n </div>\n </div>\n </div>\n )\n })}\n </div>\n {value ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"w-fit gap-1 text-sm font-normal\"\n onClick={() => onChange(null)}\n >\n <X className=\"h-4 w-4\" />\n {clearLabel}\n </Button>\n ) : null}\n </div>\n ) : hasTyped ? (\n <p className=\"text-xs text-muted-foreground\">\n {minQueryHintLabel ?? `Type at least ${minQuery} characters or paste an id to search.`}\n </p>\n ) : (\n <p className=\"text-xs text-muted-foreground\">{startTypingLabel}</p>\n )}\n {error ? <p className=\"text-xs text-destructive\">{emptyLabel}</p> : null}\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Check, Loader2, Search, X } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '../../primitives/button'\nimport { cn } from '@open-mercato/shared/lib/utils'\n\nexport type LookupSelectItem = {\n id: string\n title: string\n subtitle?: string | null\n badge?: string | null\n icon?: React.ReactNode\n disabled?: boolean\n rightLabel?: string | null\n description?: string | null\n}\n\ntype LookupSelectProps = {\n value: string | null\n onChange: (next: string | null) => void\n fetchItems?: (query: string) => Promise<LookupSelectItem[]>\n fetchOptions?: (query?: string) => Promise<LookupSelectItem[]>\n options?: LookupSelectItem[]\n minQuery?: number\n actionSlot?: React.ReactNode\n onReady?: (controls: { setQuery: (value: string) => void }) => void\n searchPlaceholder?: string\n placeholder?: string\n clearLabel?: string\n emptyLabel?: string\n loadingLabel?: string\n selectLabel?: string\n selectedLabel?: string\n minQueryHintLabel?: string\n startTypingLabel?: string\n selectedHintLabel?: (id: string) => string\n disabled?: boolean\n loading?: boolean\n defaultOpen?: boolean\n}\n\nexport function LookupSelect({\n value,\n onChange,\n fetchItems,\n fetchOptions,\n options,\n minQuery = 2,\n actionSlot,\n onReady,\n placeholder,\n searchPlaceholder,\n clearLabel,\n emptyLabel,\n loadingLabel,\n selectLabel,\n selectedLabel,\n minQueryHintLabel,\n startTypingLabel,\n selectedHintLabel,\n disabled = false,\n loading: loadingProp = false,\n defaultOpen = false,\n}: LookupSelectProps) {\n const t = useT()\n const resolvedSearchPlaceholder = searchPlaceholder ?? placeholder ?? t('ui.lookupSelect.searchPlaceholder', 'Search\u2026')\n const resolvedClearLabel = clearLabel ?? t('ui.lookupSelect.clearSelection', 'Clear selection')\n const resolvedEmptyLabel = emptyLabel ?? t('ui.lookupSelect.noResults', 'No results')\n const resolvedLoadingLabel = loadingLabel ?? t('ui.lookupSelect.searching', 'Searching\u2026')\n const resolvedSelectLabel = selectLabel ?? t('ui.lookupSelect.select', 'Select')\n const resolvedSelectedLabel = selectedLabel ?? t('ui.lookupSelect.selected', 'Selected')\n const resolvedStartTypingLabel = startTypingLabel ?? t('ui.lookupSelect.startTyping', 'Start typing to search.')\n const resolvedMinQueryHintLabel = minQueryHintLabel ?? t(\n 'ui.lookupSelect.minQueryHint',\n 'Type at least {minQuery} characters or paste an id to search.',\n { minQuery: String(minQuery) }\n )\n const [query, setQuery] = React.useState('')\n const [items, setItems] = React.useState<LookupSelectItem[]>(options ?? [])\n const [loading, setLoading] = React.useState(false)\n const [hasTyped, setHasTyped] = React.useState(defaultOpen)\n const [error, setError] = React.useState<string | null>(null)\n const [fetchKey, setFetchKey] = React.useState(0)\n const fetchItemsRef = React.useRef(fetchItems ?? fetchOptions)\n const setQueryRef = React.useRef(setQuery)\n const optionsWasArrayRef = React.useRef(Array.isArray(options))\n\n React.useEffect(() => {\n fetchItemsRef.current = fetchItems ?? fetchOptions\n }, [fetchItems, fetchOptions])\n\n React.useEffect(() => {\n if (Array.isArray(options)) {\n optionsWasArrayRef.current = true\n setItems(options)\n } else if (optionsWasArrayRef.current) {\n optionsWasArrayRef.current = false\n setFetchKey((k) => k + 1)\n }\n }, [options])\n\n React.useEffect(() => {\n setQueryRef.current = setQuery\n if (onReady) onReady({ setQuery })\n }, [onReady, setQuery])\n\n const shouldSearch =\n defaultOpen || query.trim().length >= minQuery || Boolean(value && (options?.length ?? 0) > 0)\n React.useEffect(() => {\n if (disabled) {\n setItems(options ?? [])\n setLoading(false)\n return\n }\n let cancelled = false\n let timer: ReturnType<typeof setTimeout> | null = null\n if (!shouldSearch) {\n setItems(options ?? [])\n setLoading(false)\n setError(null)\n return () => { cancelled = true }\n }\n setLoading(true)\n setError(null)\n timer = setTimeout(() => {\n const requestId = Date.now()\n const fetcher = fetchItemsRef.current\n const loader = fetcher ?? (() => Promise.resolve(options ?? []))\n loader(query.trim())\n .then((result) => {\n if (cancelled) return\n setItems(result)\n })\n .catch((err) => {\n if (cancelled) return\n console.error('LookupSelect.fetchItems', err)\n setError('error')\n })\n .finally(() => {\n if (!cancelled) setLoading(false)\n })\n return requestId\n }, 220)\n return () => {\n cancelled = true\n if (timer) clearTimeout(timer)\n }\n }, [query, shouldSearch, fetchKey])\n\n return (\n <div className=\"space-y-3\">\n <div className=\"flex flex-col gap-2 sm:flex-row sm:items-center sm:gap-3\">\n <div className=\"relative flex-1\">\n <Search className=\"pointer-events-none absolute left-3.5 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground\" />\n <input\n className=\"w-full h-10 rounded-lg border border-input bg-background pl-10 pr-3 text-sm shadow-xs transition-colors outline-none placeholder:text-muted-foreground hover:border-foreground/20 focus-visible:shadow-focus focus-visible:border-brand-violet disabled:bg-bg-disabled disabled:border-border-disabled disabled:text-muted-foreground disabled:cursor-not-allowed\"\n value={query}\n onChange={(event) => {\n setQuery(event.target.value)\n setHasTyped(true)\n }}\n placeholder={resolvedSearchPlaceholder}\n disabled={disabled}\n />\n </div>\n {actionSlot ? <div className=\"sm:self-start\">{actionSlot}</div> : null}\n </div>\n {shouldSearch ? (\n <div className=\"space-y-2\">\n {loading || loadingProp ? (\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n {resolvedLoadingLabel}\n </div>\n ) : null}\n {!loading && !loadingProp && !items.length ? (\n <p className=\"text-xs text-muted-foreground\">{resolvedEmptyLabel}</p>\n ) : null}\n <div className=\"flex flex-col gap-1.5 max-h-80 overflow-y-auto -mx-0.5 px-0.5 py-0.5\">\n {items.map((item) => {\n const isSelected = value === item.id\n const isInteractive = !item.disabled || isSelected\n return (\n <div\n key={item.id}\n className={cn(\n 'group flex items-center gap-4 rounded-xl border p-4 transition-all duration-150 focus-visible:outline-none focus-visible:shadow-focus',\n isInteractive ? 'cursor-pointer' : 'cursor-not-allowed opacity-60',\n isSelected\n ? 'border-brand-violet bg-brand-violet/5 shadow-sm'\n : 'border-input bg-card hover:border-foreground/20 hover:bg-muted/30 hover:shadow-sm'\n )}\n role=\"button\"\n tabIndex={item.disabled ? -1 : 0}\n onClick={() => {\n if (!isInteractive) return\n onChange(item.id)\n }}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n if (!isInteractive) return\n onChange(item.id)\n }\n }}\n aria-pressed={isSelected}\n aria-disabled={item.disabled && !isSelected ? true : undefined}\n title={isSelected ? resolvedSelectedLabel : resolvedSelectLabel}\n >\n {item.icon ? (\n <div className=\"flex h-12 w-12 shrink-0 items-center justify-center overflow-hidden [&>svg]:size-6 [&_svg]:text-muted-foreground\">\n {item.icon}\n </div>\n ) : (\n <div className={cn(\n 'flex h-12 w-12 shrink-0 items-center justify-center overflow-hidden rounded-lg border transition-colors',\n isSelected\n ? 'border-brand-violet/40 bg-brand-violet/10 text-brand-violet'\n : 'border-input bg-muted text-muted-foreground group-hover:border-foreground/20'\n )}>\n <span className=\"text-base font-semibold uppercase\">{item.title.slice(0, 1)}</span>\n </div>\n )}\n <div className=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n <div className=\"flex items-center justify-between gap-3\">\n <div className=\"truncate text-sm font-semibold text-foreground\">{item.title}</div>\n {item.rightLabel ? (\n <div className=\"shrink-0 text-overline font-medium uppercase tracking-wider text-muted-foreground\">\n {item.rightLabel}\n </div>\n ) : null}\n </div>\n {item.subtitle ? (\n <div className=\"text-xs text-muted-foreground truncate\">{item.subtitle}</div>\n ) : null}\n {item.description ? (\n <div className=\"text-xs text-muted-foreground/70 truncate\">{item.description}</div>\n ) : null}\n </div>\n <div className=\"flex shrink-0 items-center justify-center\">\n {isSelected ? (\n <Check className=\"size-5 text-brand-violet\" aria-hidden=\"true\" />\n ) : (\n <div className=\"size-5\" aria-hidden=\"true\" />\n )}\n </div>\n </div>\n )\n })}\n </div>\n {value ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"w-fit gap-1 text-sm font-normal\"\n onClick={() => onChange(null)}\n >\n <X className=\"h-4 w-4\" />\n {resolvedClearLabel}\n </Button>\n ) : null}\n </div>\n ) : hasTyped ? (\n <p className=\"text-xs text-muted-foreground\">\n {resolvedMinQueryHintLabel}\n </p>\n ) : (\n <p className=\"text-xs text-muted-foreground\">{resolvedStartTypingLabel}</p>\n )}\n {error ? <p className=\"text-xs text-status-error-text\" role=\"alert\">{resolvedEmptyLabel}</p> : null}\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,10 +1,64 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
4
5
  import { extractPhoneDigits, validatePhoneNumber } from "@open-mercato/shared/lib/phone";
5
- import { Input } from "../../primitives/input.js";
6
+ import { cn } from "@open-mercato/shared/lib/utils";
7
+ import {
8
+ Select,
9
+ SelectContent,
10
+ SelectItem,
11
+ SelectItemLeading,
12
+ SelectTrigger
13
+ } from "../../primitives/select.js";
14
+ const PHONE_COUNTRIES = [
15
+ { iso2: "US", dialCode: "+1", label: "United States", flag: "\u{1F1FA}\u{1F1F8}" },
16
+ { iso2: "CA", dialCode: "+1", label: "Canada", flag: "\u{1F1E8}\u{1F1E6}" },
17
+ { iso2: "GB", dialCode: "+44", label: "United Kingdom", flag: "\u{1F1EC}\u{1F1E7}" },
18
+ { iso2: "PL", dialCode: "+48", label: "Poland", flag: "\u{1F1F5}\u{1F1F1}" },
19
+ { iso2: "DE", dialCode: "+49", label: "Germany", flag: "\u{1F1E9}\u{1F1EA}" },
20
+ { iso2: "FR", dialCode: "+33", label: "France", flag: "\u{1F1EB}\u{1F1F7}" },
21
+ { iso2: "ES", dialCode: "+34", label: "Spain", flag: "\u{1F1EA}\u{1F1F8}" },
22
+ { iso2: "IT", dialCode: "+39", label: "Italy", flag: "\u{1F1EE}\u{1F1F9}" },
23
+ { iso2: "NL", dialCode: "+31", label: "Netherlands", flag: "\u{1F1F3}\u{1F1F1}" },
24
+ { iso2: "SE", dialCode: "+46", label: "Sweden", flag: "\u{1F1F8}\u{1F1EA}" },
25
+ { iso2: "AT", dialCode: "+43", label: "Austria", flag: "\u{1F1E6}\u{1F1F9}" },
26
+ { iso2: "CH", dialCode: "+41", label: "Switzerland", flag: "\u{1F1E8}\u{1F1ED}" },
27
+ { iso2: "PT", dialCode: "+351", label: "Portugal", flag: "\u{1F1F5}\u{1F1F9}" },
28
+ { iso2: "CZ", dialCode: "+420", label: "Czechia", flag: "\u{1F1E8}\u{1F1FF}" },
29
+ { iso2: "RO", dialCode: "+40", label: "Romania", flag: "\u{1F1F7}\u{1F1F4}" },
30
+ { iso2: "UA", dialCode: "+380", label: "Ukraine", flag: "\u{1F1FA}\u{1F1E6}" }
31
+ ];
32
+ const DEFAULT_COUNTRY = PHONE_COUNTRIES[0];
33
+ const COUNTRIES_BY_DIAL_LENGTH = [...PHONE_COUNTRIES].sort(
34
+ (a, b) => b.dialCode.length - a.dialCode.length
35
+ );
36
+ function findCountryByIso(iso2) {
37
+ return PHONE_COUNTRIES.find((c) => c.iso2 === iso2);
38
+ }
39
+ function parseCountryFromValue(value) {
40
+ if (!value) return null;
41
+ const trimmed = value.trim();
42
+ if (!trimmed.startsWith("+")) return null;
43
+ for (const country of COUNTRIES_BY_DIAL_LENGTH) {
44
+ if (trimmed.startsWith(country.dialCode)) return country;
45
+ }
46
+ return null;
47
+ }
48
+ function extractLocalNumber(value, dialCode) {
49
+ if (!value) return "";
50
+ const trimmed = value.trim();
51
+ if (!trimmed.startsWith(dialCode)) return trimmed;
52
+ return trimmed.slice(dialCode.length).trim();
53
+ }
54
+ function composeValue(country, localNumber) {
55
+ const trimmed = localNumber.trim();
56
+ if (!trimmed) return "";
57
+ if (trimmed.startsWith("+")) return trimmed;
58
+ return `${country.dialCode} ${trimmed}`;
59
+ }
6
60
  const DEFAULT_MIN_DIGITS = 6;
7
- const DEFAULT_INVALID_LABEL = "Enter a valid phone number with country code (e.g. +1 212 555 1234)";
61
+ const DEFAULT_PLACEHOLDER = "(555) 000-0000";
8
62
  function PhoneNumberField({
9
63
  id,
10
64
  value,
@@ -21,38 +75,68 @@ function PhoneNumberField({
21
75
  duplicateLabel,
22
76
  duplicateLinkLabel,
23
77
  invalidLabel,
24
- onDuplicateLookup
78
+ onDuplicateLookup,
79
+ countries: countriesProp,
80
+ defaultCountryIso2
25
81
  }) {
26
- const [local, setLocal] = React.useState(() => {
27
- if (value == null || value === "") return "";
28
- return String(value);
29
- });
82
+ const t = useT();
83
+ const resolvedInvalidLabel = invalidLabel ?? t(
84
+ "ui.inputs.phoneNumberField.invalid",
85
+ "Enter a valid phone number with country code (e.g. +1 212 555 1234)"
86
+ );
87
+ const resolvedCheckingLabel = checkingLabel ?? t(
88
+ "ui.inputs.phoneNumberField.checking",
89
+ "Checking for duplicates\u2026"
90
+ );
91
+ const resolvedDuplicateLinkLabel = duplicateLinkLabel ?? t(
92
+ "ui.inputs.phoneNumberField.duplicateLink",
93
+ "Open record"
94
+ );
95
+ const resolvedPlaceholder = placeholder ?? DEFAULT_PLACEHOLDER;
96
+ const countries = countriesProp ?? PHONE_COUNTRIES;
97
+ const fallbackCountry = React.useMemo(
98
+ () => defaultCountryIso2 && findCountryByIso(defaultCountryIso2) || DEFAULT_COUNTRY,
99
+ [defaultCountryIso2]
100
+ );
101
+ const initialCountry = React.useMemo(
102
+ () => parseCountryFromValue(value) ?? fallbackCountry,
103
+ // eslint-disable-next-line react-hooks/exhaustive-deps
104
+ []
105
+ );
106
+ const [country, setCountry] = React.useState(initialCountry);
107
+ const [localNumber, setLocalNumber] = React.useState(
108
+ () => extractLocalNumber(value, initialCountry.dialCode)
109
+ );
30
110
  const [duplicate, setDuplicate] = React.useState(null);
31
111
  const [checking, setChecking] = React.useState(false);
32
112
  const [validationHint, setValidationHint] = React.useState(null);
113
+ const [focused, setFocused] = React.useState(false);
33
114
  const userEditingRef = React.useRef(false);
34
115
  const externalFieldError = externalError && externalError.trim().length > 0 ? externalError : null;
35
116
  const errorMessage = externalFieldError ?? validationHint;
36
117
  const errorId = errorMessage && id ? `${id}-error` : void 0;
37
118
  const describedBy = [ariaDescribedBy, errorId].filter((part) => Boolean(part)).join(" ") || void 0;
119
+ const composedValue = composeValue(country, localNumber);
38
120
  React.useEffect(() => {
39
121
  if (userEditingRef.current) return;
40
122
  if (value == null || value === "") {
41
- setLocal("");
123
+ setLocalNumber("");
42
124
  onDigitsChange?.(null);
43
125
  return;
44
126
  }
45
- const nextValue = String(value);
46
- setLocal(nextValue);
47
- onDigitsChange?.(extractPhoneDigits(nextValue) || null);
48
- }, [value, onDigitsChange]);
127
+ const parsed = parseCountryFromValue(value);
128
+ if (parsed) setCountry(parsed);
129
+ const next = extractLocalNumber(value, parsed?.dialCode ?? country.dialCode);
130
+ setLocalNumber(next);
131
+ onDigitsChange?.(extractPhoneDigits(String(value)) || null);
132
+ }, [value]);
49
133
  React.useEffect(() => {
50
134
  if (!onDuplicateLookup || disabled) {
51
135
  setDuplicate(null);
52
136
  setChecking(false);
53
137
  return;
54
138
  }
55
- const digits = extractPhoneDigits(local);
139
+ const digits = extractPhoneDigits(composedValue);
56
140
  if (!digits || digits.length < minDigits) {
57
141
  setDuplicate(null);
58
142
  setChecking(false);
@@ -74,70 +158,137 @@ function PhoneNumberField({
74
158
  cancelled = true;
75
159
  window.clearTimeout(handle);
76
160
  };
77
- }, [local, disabled, minDigits, onDuplicateLookup]);
78
- const handleChange = React.useCallback(
161
+ }, [composedValue, disabled, minDigits, onDuplicateLookup]);
162
+ const emit = React.useCallback(
163
+ (nextCountry, nextLocal) => {
164
+ const composed = composeValue(nextCountry, nextLocal);
165
+ onValueChange(composed.length ? composed : void 0);
166
+ onDigitsChange?.(extractPhoneDigits(composed) || null);
167
+ },
168
+ [onDigitsChange, onValueChange]
169
+ );
170
+ const handleCountryChange = React.useCallback(
171
+ (iso2) => {
172
+ const next = findCountryByIso(iso2);
173
+ if (!next) return;
174
+ userEditingRef.current = true;
175
+ setCountry(next);
176
+ setValidationHint(null);
177
+ emit(next, localNumber);
178
+ },
179
+ [emit, localNumber]
180
+ );
181
+ const handleLocalChange = React.useCallback(
79
182
  (event) => {
80
183
  const next = event.target.value;
81
- const cleanDigits = extractPhoneDigits(next);
82
184
  userEditingRef.current = true;
83
- setLocal(next);
185
+ setLocalNumber(next);
84
186
  setValidationHint(null);
85
- onValueChange(next.length ? next : void 0);
86
- onDigitsChange?.(cleanDigits.length ? cleanDigits : null);
187
+ emit(country, next);
87
188
  },
88
- [onValueChange, onDigitsChange]
189
+ [country, emit]
89
190
  );
90
191
  const handleBlur = React.useCallback(() => {
91
192
  userEditingRef.current = false;
92
- const trimmed = local.trim();
193
+ setFocused(false);
194
+ const trimmed = localNumber.trim();
93
195
  if (!trimmed) {
94
- setLocal("");
196
+ setLocalNumber("");
95
197
  setValidationHint(null);
96
198
  onValueChange(void 0);
97
199
  onDigitsChange?.(null);
98
200
  return;
99
201
  }
100
- const result = validatePhoneNumber(trimmed);
202
+ const composed = composeValue(country, trimmed);
203
+ const result = validatePhoneNumber(composed);
101
204
  if (result.valid) {
102
- setLocal(result.normalized ?? "");
205
+ const normalizedLocal = extractLocalNumber(result.normalized ?? composed, country.dialCode);
206
+ setLocalNumber(normalizedLocal);
103
207
  setValidationHint(null);
104
208
  onValueChange(result.normalized || void 0);
105
209
  onDigitsChange?.(result.digits || null);
106
210
  } else {
107
- setLocal(trimmed);
108
- setValidationHint(invalidLabel ?? DEFAULT_INVALID_LABEL);
109
- onValueChange(trimmed);
211
+ setValidationHint(resolvedInvalidLabel);
212
+ onValueChange(composed);
110
213
  onDigitsChange?.(result.digits || null);
111
214
  }
112
- }, [invalidLabel, local, onDigitsChange, onValueChange]);
215
+ }, [country, localNumber, onDigitsChange, onValueChange, resolvedInvalidLabel]);
216
+ const containerErrorBorder = errorMessage ? "border-status-error-icon" : "border-input";
217
+ const containerFocusBorder = errorMessage ? "border-status-error-icon shadow-focus" : "border-brand-violet shadow-focus";
113
218
  return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
114
- /* @__PURE__ */ jsx(
115
- Input,
219
+ /* @__PURE__ */ jsxs(
220
+ "div",
116
221
  {
117
- type: "tel",
118
- value: local,
119
- onChange: handleChange,
120
- onBlur: handleBlur,
121
- placeholder,
122
- autoFocus,
123
- disabled,
124
- id,
125
- "aria-label": ariaLabel,
126
- "aria-describedby": describedBy,
222
+ className: cn(
223
+ "flex items-stretch w-full rounded-md border bg-background shadow-xs transition-colors",
224
+ disabled ? "bg-bg-disabled border-border-disabled cursor-not-allowed" : focused ? containerFocusBorder : `${containerErrorBorder} hover:border-foreground/30`
225
+ ),
127
226
  "aria-invalid": errorMessage ? "true" : void 0,
128
- "data-crud-focus-target": ""
227
+ children: [
228
+ /* @__PURE__ */ jsxs(Select, { value: country.iso2, onValueChange: handleCountryChange, disabled, children: [
229
+ /* @__PURE__ */ jsxs(
230
+ SelectTrigger,
231
+ {
232
+ "aria-label": ariaLabel ? `${ariaLabel} country` : "Country code",
233
+ className: cn(
234
+ "h-auto w-auto shrink-0 gap-1.5 rounded-none rounded-l-md border-0 bg-transparent px-2.5 py-2 shadow-none",
235
+ "hover:bg-muted/40 focus:bg-muted/40 focus-visible:shadow-none focus-visible:border-0",
236
+ "disabled:bg-transparent disabled:hover:bg-transparent"
237
+ ),
238
+ onFocus: () => setFocused(true),
239
+ onBlur: () => setFocused(false),
240
+ children: [
241
+ /* @__PURE__ */ jsx("span", { className: "text-base leading-none", "aria-hidden": "true", children: country.flag }),
242
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-foreground tabular-nums", children: country.dialCode })
243
+ ]
244
+ }
245
+ ),
246
+ /* @__PURE__ */ jsx(SelectContent, { align: "start", children: countries.map((c) => /* @__PURE__ */ jsxs(SelectItem, { value: c.iso2, children: [
247
+ /* @__PURE__ */ jsx(SelectItemLeading, { children: /* @__PURE__ */ jsx("span", { className: "text-base leading-none", children: c.flag }) }),
248
+ /* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: c.label }),
249
+ /* @__PURE__ */ jsx("span", { className: "ml-2 text-xs text-muted-foreground tabular-nums", children: c.dialCode })
250
+ ] }, `${c.iso2}-${c.dialCode}`)) })
251
+ ] }),
252
+ /* @__PURE__ */ jsx("div", { "aria-hidden": "true", className: "w-px self-stretch bg-input" }),
253
+ /* @__PURE__ */ jsx(
254
+ "input",
255
+ {
256
+ type: "tel",
257
+ inputMode: "tel",
258
+ autoComplete: "tel-national",
259
+ value: localNumber,
260
+ onChange: handleLocalChange,
261
+ onBlur: handleBlur,
262
+ onFocus: () => setFocused(true),
263
+ placeholder: resolvedPlaceholder,
264
+ autoFocus,
265
+ disabled,
266
+ id,
267
+ "aria-label": ariaLabel,
268
+ "aria-describedby": describedBy,
269
+ "aria-invalid": errorMessage ? "true" : void 0,
270
+ "data-crud-focus-target": "",
271
+ className: cn(
272
+ "flex-1 min-w-0 bg-transparent px-3 py-2 text-sm leading-5 outline-none",
273
+ "placeholder:text-muted-foreground",
274
+ "disabled:cursor-not-allowed disabled:text-muted-foreground"
275
+ )
276
+ }
277
+ )
278
+ ]
129
279
  }
130
280
  ),
131
- errorMessage ? /* @__PURE__ */ jsx("p", { id: errorId, className: "text-xs text-destructive", children: errorMessage }) : null,
132
- !disabled && duplicate && duplicateLabel && duplicateLinkLabel ? /* @__PURE__ */ jsxs("p", { className: "text-xs text-amber-600", children: [
281
+ errorMessage ? /* @__PURE__ */ jsx("p", { id: errorId, role: "alert", className: "text-xs text-status-error-text", children: errorMessage }) : null,
282
+ !disabled && duplicate && duplicateLabel ? /* @__PURE__ */ jsxs("p", { className: "text-xs text-status-warning-text", children: [
133
283
  duplicateLabel(duplicate),
134
284
  " ",
135
- /* @__PURE__ */ jsx("a", { className: "font-medium text-primary underline underline-offset-2", href: duplicate.href, children: duplicateLinkLabel })
285
+ /* @__PURE__ */ jsx("a", { className: "font-medium text-brand-violet underline underline-offset-2", href: duplicate.href, children: resolvedDuplicateLinkLabel })
136
286
  ] }) : null,
137
- !disabled && !duplicate && checking && checkingLabel ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: checkingLabel }) : null
287
+ !disabled && !duplicate && checking ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: resolvedCheckingLabel }) : null
138
288
  ] });
139
289
  }
140
290
  export {
291
+ PHONE_COUNTRIES,
141
292
  PhoneNumberField
142
293
  };
143
294
  //# sourceMappingURL=PhoneNumberField.js.map