@open-mercato/ui 0.5.1-develop.2953.6647bb2c43 → 0.5.1-develop.2964.d5ac4a6ebb

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 (96) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +8 -0
  3. package/dist/backend/CrudForm.js +57 -29
  4. package/dist/backend/CrudForm.js.map +2 -2
  5. package/dist/backend/DataTable.js +32 -14
  6. package/dist/backend/DataTable.js.map +2 -2
  7. package/dist/backend/FilterOverlay.js +23 -17
  8. package/dist/backend/FilterOverlay.js.map +2 -2
  9. package/dist/backend/JsonBuilder.js +32 -18
  10. package/dist/backend/JsonBuilder.js.map +2 -2
  11. package/dist/backend/columns/ColumnChooserPanel.js +12 -13
  12. package/dist/backend/columns/ColumnChooserPanel.js.map +2 -2
  13. package/dist/backend/custom-fields/FieldDefinitionsEditor.js +71 -62
  14. package/dist/backend/custom-fields/FieldDefinitionsEditor.js.map +2 -2
  15. package/dist/backend/date-range/DateRangeSelect.js +11 -10
  16. package/dist/backend/date-range/DateRangeSelect.js.map +2 -2
  17. package/dist/backend/date-range/InlineDateRangeSelect.js +10 -22
  18. package/dist/backend/date-range/InlineDateRangeSelect.js.map +2 -2
  19. package/dist/backend/detail/ActivitiesSection.js +20 -12
  20. package/dist/backend/detail/ActivitiesSection.js.map +2 -2
  21. package/dist/backend/detail/AddressEditor.js +24 -7
  22. package/dist/backend/detail/AddressEditor.js.map +2 -2
  23. package/dist/backend/detail/InlineEditors.js +12 -6
  24. package/dist/backend/detail/InlineEditors.js.map +2 -2
  25. package/dist/backend/detail/NotesSection.js +20 -14
  26. package/dist/backend/detail/NotesSection.js.map +2 -2
  27. package/dist/backend/filters/AdvancedFilterBuilder.js +52 -24
  28. package/dist/backend/filters/AdvancedFilterBuilder.js.map +2 -2
  29. package/dist/backend/injection/InjectedField.js +12 -7
  30. package/dist/backend/injection/InjectedField.js.map +2 -2
  31. package/dist/backend/inputs/ComboboxInput.js.map +2 -2
  32. package/dist/backend/inputs/EventSelect.js +22 -6
  33. package/dist/backend/inputs/EventSelect.js.map +2 -2
  34. package/dist/backend/inputs/PhoneNumberField.js +2 -2
  35. package/dist/backend/inputs/PhoneNumberField.js.map +2 -2
  36. package/dist/backend/inputs/TimeInput.js +9 -10
  37. package/dist/backend/inputs/TimeInput.js.map +2 -2
  38. package/dist/backend/messages/message-compose-form-groups.js +12 -7
  39. package/dist/backend/messages/message-compose-form-groups.js.map +2 -2
  40. package/dist/backend/messages/useMessageCompose.js +7 -1
  41. package/dist/backend/messages/useMessageCompose.js.map +2 -2
  42. package/dist/frontend/LanguageSwitcher.js +19 -14
  43. package/dist/frontend/LanguageSwitcher.js.map +2 -2
  44. package/dist/index.js +5 -0
  45. package/dist/index.js.map +2 -2
  46. package/dist/primitives/checkbox-field.js +17 -5
  47. package/dist/primitives/checkbox-field.js.map +2 -2
  48. package/dist/primitives/input.js +71 -14
  49. package/dist/primitives/input.js.map +2 -2
  50. package/dist/primitives/radio-field.js +74 -0
  51. package/dist/primitives/radio-field.js.map +7 -0
  52. package/dist/primitives/radio.js +37 -0
  53. package/dist/primitives/radio.js.map +7 -0
  54. package/dist/primitives/select.js +155 -0
  55. package/dist/primitives/select.js.map +7 -0
  56. package/dist/primitives/switch-field.js +76 -0
  57. package/dist/primitives/switch-field.js.map +7 -0
  58. package/dist/primitives/switch.js +17 -3
  59. package/dist/primitives/switch.js.map +2 -2
  60. package/dist/primitives/textarea.js +48 -12
  61. package/dist/primitives/textarea.js.map +2 -2
  62. package/dist/primitives/tooltip.js +44 -15
  63. package/dist/primitives/tooltip.js.map +2 -2
  64. package/package.json +5 -3
  65. package/src/backend/CrudForm.tsx +104 -37
  66. package/src/backend/DataTable.tsx +38 -20
  67. package/src/backend/FilterOverlay.tsx +35 -21
  68. package/src/backend/JsonBuilder.tsx +38 -20
  69. package/src/backend/__tests__/FieldDefinitionsEditor.test.tsx +23 -6
  70. package/src/backend/columns/ColumnChooserPanel.tsx +9 -10
  71. package/src/backend/custom-fields/FieldDefinitionsEditor.tsx +120 -87
  72. package/src/backend/date-range/DateRangeSelect.tsx +19 -12
  73. package/src/backend/date-range/InlineDateRangeSelect.tsx +16 -20
  74. package/src/backend/detail/ActivitiesSection.tsx +35 -23
  75. package/src/backend/detail/AddressEditor.tsx +30 -16
  76. package/src/backend/detail/InlineEditors.tsx +21 -11
  77. package/src/backend/detail/NotesSection.tsx +35 -25
  78. package/src/backend/filters/AdvancedFilterBuilder.tsx +60 -34
  79. package/src/backend/injection/InjectedField.tsx +21 -12
  80. package/src/backend/inputs/ComboboxInput.tsx +4 -0
  81. package/src/backend/inputs/EventSelect.tsx +30 -17
  82. package/src/backend/inputs/PhoneNumberField.tsx +2 -2
  83. package/src/backend/inputs/TimeInput.tsx +9 -10
  84. package/src/backend/messages/message-compose-form-groups.tsx +21 -12
  85. package/src/backend/messages/useMessageCompose.ts +20 -1
  86. package/src/frontend/LanguageSwitcher.tsx +20 -17
  87. package/src/index.ts +5 -0
  88. package/src/primitives/checkbox-field.tsx +10 -2
  89. package/src/primitives/input.tsx +73 -12
  90. package/src/primitives/radio-field.tsx +92 -0
  91. package/src/primitives/radio.tsx +42 -0
  92. package/src/primitives/select.tsx +200 -0
  93. package/src/primitives/switch-field.tsx +100 -0
  94. package/src/primitives/switch.tsx +17 -4
  95. package/src/primitives/textarea.tsx +67 -11
  96. package/src/primitives/tooltip.tsx +68 -24
@@ -3,6 +3,14 @@
3
3
  import * as React from 'react'
4
4
  import { Button } from '../primitives/button'
5
5
  import { IconButton } from '../primitives/icon-button'
6
+ import { Input } from '../primitives/input'
7
+ import {
8
+ Select,
9
+ SelectContent,
10
+ SelectItem,
11
+ SelectTrigger,
12
+ SelectValue,
13
+ } from '../primitives/select'
6
14
  import { Plus, Trash2, ChevronRight, ChevronDown, Code, LayoutList } from 'lucide-react'
7
15
 
8
16
  function cn(...classes: (string | undefined | null | false)[]) {
@@ -239,47 +247,57 @@ function JsonNode({ data, onChange, onDelete, readOnly, label, isRoot }: JsonNod
239
247
  <div className="flex-1 flex gap-2 items-center flex-wrap">
240
248
 
241
249
  {!readOnly && (
242
- <select
250
+ <Select
243
251
  value={type}
244
- onChange={(e) => handleTypeChange(e.target.value as JsonNodeType)}
245
- className="text-xs border rounded px-1 py-0.5 bg-muted text-foreground focus-visible:ring-1 focus-visible:ring-ring"
252
+ onValueChange={(next) => handleTypeChange(next as JsonNodeType)}
246
253
  >
247
- <option value="string">String</option>
248
- <option value="number">Number</option>
249
- <option value="boolean">Boolean</option>
250
- <option value="object">Object</option>
251
- <option value="array">Array</option>
252
- <option value="null">Null</option>
253
- </select>
254
+ <SelectTrigger size="sm" className="w-auto min-w-[6rem]">
255
+ <SelectValue />
256
+ </SelectTrigger>
257
+ <SelectContent>
258
+ <SelectItem value="string">String</SelectItem>
259
+ <SelectItem value="number">Number</SelectItem>
260
+ <SelectItem value="boolean">Boolean</SelectItem>
261
+ <SelectItem value="object">Object</SelectItem>
262
+ <SelectItem value="array">Array</SelectItem>
263
+ <SelectItem value="null">Null</SelectItem>
264
+ </SelectContent>
265
+ </Select>
254
266
  )}
255
267
 
256
268
  {type === 'string' && (
257
- <input
258
- className="flex-1 min-w-0 sm:min-w-[120px] text-sm border rounded px-2 py-0.5 focus-visible:outline-none focus-visible:border-ring"
269
+ <Input
270
+ size="sm"
271
+ className="flex-1 min-w-0 sm:min-w-[120px]"
259
272
  value={data}
260
273
  onChange={e => onChange(e.target.value)}
261
274
  disabled={readOnly}
262
275
  />
263
276
  )}
264
277
  {type === 'number' && (
265
- <input
278
+ <Input
266
279
  type="number"
267
- className="flex-1 w-full sm:w-[100px] text-sm border rounded px-2 py-0.5 focus-visible:outline-none focus-visible:border-ring"
280
+ size="sm"
281
+ className="flex-1 w-full sm:w-[100px]"
268
282
  value={data}
269
283
  onChange={e => onChange(parseFloat(e.target.value) || 0)}
270
284
  disabled={readOnly}
271
285
  />
272
286
  )}
273
287
  {type === 'boolean' && (
274
- <select
275
- className="flex-1 w-full sm:w-[100px] text-sm border rounded px-2 py-0.5 focus-visible:outline-none focus-visible:border-ring"
288
+ <Select
276
289
  value={String(data)}
277
- onChange={e => onChange(e.target.value === 'true')}
290
+ onValueChange={(next) => onChange(next === 'true')}
278
291
  disabled={readOnly}
279
292
  >
280
- <option value="true">true</option>
281
- <option value="false">false</option>
282
- </select>
293
+ <SelectTrigger size="sm" className="flex-1 w-full sm:w-[100px]">
294
+ <SelectValue />
295
+ </SelectTrigger>
296
+ <SelectContent>
297
+ <SelectItem value="true">true</SelectItem>
298
+ <SelectItem value="false">false</SelectItem>
299
+ </SelectContent>
300
+ </Select>
283
301
  )}
284
302
  {type === 'null' && <span className="text-xs text-muted-foreground">null</span>}
285
303
  {isContainer && (
@@ -5,6 +5,20 @@ import { fireEvent, screen } from '@testing-library/react'
5
5
  import { renderWithProviders } from '@open-mercato/shared/lib/testing/renderWithProviders'
6
6
  import { FieldDefinitionsEditor, type FieldDefinition } from '../custom-fields/FieldDefinitionsEditor'
7
7
 
8
+ // Radix Select uses pointer capture / scrollIntoView APIs that jsdom doesn't implement.
9
+ // Polyfill them so tests can interact with Radix-based comboboxes.
10
+ if (typeof window !== 'undefined') {
11
+ if (!Element.prototype.hasPointerCapture) {
12
+ Element.prototype.hasPointerCapture = () => false
13
+ }
14
+ if (!Element.prototype.releasePointerCapture) {
15
+ Element.prototype.releasePointerCapture = () => undefined
16
+ }
17
+ if (!Element.prototype.scrollIntoView) {
18
+ Element.prototype.scrollIntoView = () => undefined
19
+ }
20
+ }
21
+
8
22
  describe('FieldDefinitionsEditor', () => {
9
23
  it('assigns a field to a fieldset without triggering a render-time parent update warning', () => {
10
24
  const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
@@ -36,15 +50,18 @@ describe('FieldDefinitionsEditor', () => {
36
50
  />,
37
51
  )
38
52
 
39
- const assignFieldsetSelect = screen.getAllByRole('combobox').find((element) => {
40
- if (!(element instanceof HTMLSelectElement)) return false
41
- const optionLabels = Array.from(element.options).map((option) => option.text)
42
- return optionLabels.includes('Unassigned') && optionLabels.includes('New fieldset')
53
+ const assignFieldsetTrigger = screen.getAllByRole('combobox').find((element) => {
54
+ return element.textContent?.trim() === 'Unassigned'
43
55
  })
44
56
 
45
- expect(assignFieldsetSelect).toBeDefined()
57
+ expect(assignFieldsetTrigger).toBeDefined()
58
+
59
+ fireEvent.pointerDown(assignFieldsetTrigger!, { button: 0, ctrlKey: false })
60
+ fireEvent.click(assignFieldsetTrigger!)
46
61
 
47
- fireEvent.change(assignFieldsetSelect as HTMLSelectElement, { target: { value: 'fieldset_1' } })
62
+ const option = screen.getByRole('option', { name: 'New fieldset' })
63
+ fireEvent.pointerDown(option)
64
+ fireEvent.click(option)
48
65
 
49
66
  expect(handleDefinitionChange).toHaveBeenCalledWith(0, expect.objectContaining({
50
67
  key: 'buying_role',
@@ -4,6 +4,7 @@ import { Search, GripVertical, X, ChevronRight } from 'lucide-react'
4
4
  import { Button } from '../../primitives/button'
5
5
  import { IconButton } from '../../primitives/icon-button'
6
6
  import { Switch } from '../../primitives/switch'
7
+ import { Input } from '../../primitives/input'
7
8
  import { useT } from '@open-mercato/shared/lib/i18n/context'
8
9
  import {
9
10
  DndContext,
@@ -167,16 +168,14 @@ export function ColumnChooserSection({
167
168
  return (
168
169
  <div className="flex flex-col">
169
170
  <div className="px-4 py-3 border-t">
170
- <div className="relative">
171
- <Search className="absolute left-2 top-1/2 -translate-y-1/2 size-4 text-muted-foreground" />
172
- <input
173
- type="text"
174
- className="w-full rounded border bg-background pl-8 pr-2 py-2 text-sm"
175
- placeholder={t('ui.columnChooser.search', 'Search columns...')}
176
- value={searchQuery}
177
- onChange={(e) => setSearchQuery(e.target.value)}
178
- />
179
- </div>
171
+ <Input
172
+ type="text"
173
+ size="sm"
174
+ leftIcon={<Search />}
175
+ placeholder={t('ui.columnChooser.search', 'Search columns...')}
176
+ value={searchQuery}
177
+ onChange={(e) => setSearchQuery(e.target.value)}
178
+ />
180
179
  </div>
181
180
 
182
181
  <div>
@@ -4,6 +4,13 @@ import * as React from 'react'
4
4
  import { Cog, GripVertical, Languages, Pencil, Plus, Trash2, X } from 'lucide-react'
5
5
  import { Button } from '../../primitives/button'
6
6
  import { IconButton } from '../../primitives/icon-button'
7
+ import {
8
+ Select,
9
+ SelectContent,
10
+ SelectItem,
11
+ SelectTrigger,
12
+ SelectValue,
13
+ } from '../../primitives/select'
7
14
  import { CUSTOM_FIELD_KINDS } from '@open-mercato/shared/modules/entities/kinds'
8
15
  import { FieldRegistry } from '../fields/registry'
9
16
  import { slugify } from '@open-mercato/shared/lib/slugify'
@@ -289,18 +296,21 @@ export function FieldDefinitionsEditor({
289
296
  <div className="rounded border bg-card p-3 space-y-3">
290
297
  <div className="flex flex-wrap items-center gap-2">
291
298
  <label className="text-xs font-medium text-muted-foreground">Fieldset</label>
292
- <select
293
- className="border rounded px-2 py-1 text-sm"
294
- value={resolvedActiveFieldset ?? ''}
295
- onChange={(event) => handleActiveFieldsetChange(event.target.value)}
299
+ <Select
300
+ value={resolvedActiveFieldset || undefined}
301
+ onValueChange={(value) => handleActiveFieldsetChange(value ?? '')}
296
302
  >
297
- <option value="">Unassigned fields</option>
298
- {fieldsets.map((fs) => (
299
- <option key={fs.code} value={fs.code}>
300
- {fs.label || fs.code}
301
- </option>
302
- ))}
303
- </select>
303
+ <SelectTrigger size="sm" className="w-auto min-w-[10rem]">
304
+ <SelectValue placeholder="Unassigned fields" />
305
+ </SelectTrigger>
306
+ <SelectContent>
307
+ {fieldsets.map((fs) => (
308
+ <SelectItem key={fs.code} value={fs.code}>
309
+ {fs.label || fs.code}
310
+ </SelectItem>
311
+ ))}
312
+ </SelectContent>
313
+ </Select>
304
314
  <Button
305
315
  variant="outline"
306
316
  size="sm"
@@ -339,22 +349,25 @@ export function FieldDefinitionsEditor({
339
349
  </div>
340
350
  <div>
341
351
  <label className="text-xs">Icon</label>
342
- <select
343
- className="border rounded w-full px-2 py-1 text-sm"
344
- value={activeFieldsetConfig.icon ?? ''}
345
- onChange={(event) =>
352
+ <Select
353
+ value={activeFieldsetConfig.icon || undefined}
354
+ onValueChange={(value) =>
346
355
  handleFieldsetPatch(activeFieldsetConfig.code, {
347
- icon: event.target.value || undefined,
356
+ icon: value || undefined,
348
357
  })
349
358
  }
350
359
  >
351
- <option value="">Default</option>
352
- {FIELDSET_ICON_OPTIONS.map((option) => (
353
- <option key={option.value} value={option.value}>
354
- {option.label}
355
- </option>
356
- ))}
357
- </select>
360
+ <SelectTrigger size="sm">
361
+ <SelectValue placeholder="Default" />
362
+ </SelectTrigger>
363
+ <SelectContent>
364
+ {FIELDSET_ICON_OPTIONS.map((option) => (
365
+ <SelectItem key={option.value} value={option.value}>
366
+ {option.label}
367
+ </SelectItem>
368
+ ))}
369
+ </SelectContent>
370
+ </Select>
358
371
  </div>
359
372
  <div>
360
373
  <label className="text-xs">Description</label>
@@ -756,17 +769,21 @@ const FieldDefinitionCard = React.memo(function FieldDefinitionCard({
756
769
  </div>
757
770
  <div className="md:col-span-6">
758
771
  <label className="text-xs">Kind</label>
759
- <select
760
- className={`rounded w-full px-2 py-1 text-sm ${error?.kind ? 'border-red-500 border' : 'border'}`}
761
- value={local.kind}
762
- onChange={(event) => { apply({ kind: event.target.value }, true) }}
772
+ <Select
773
+ value={local.kind || undefined}
774
+ onValueChange={(value) => { apply({ kind: value ?? '' }, true) }}
763
775
  >
764
- {kindOptions.map((option) => (
765
- <option key={option.value} value={option.value}>
766
- {option.label}
767
- </option>
768
- ))}
769
- </select>
776
+ <SelectTrigger className={error?.kind ? 'border-destructive' : undefined}>
777
+ <SelectValue />
778
+ </SelectTrigger>
779
+ <SelectContent>
780
+ {kindOptions.map((option) => (
781
+ <SelectItem key={option.value} value={option.value}>
782
+ {option.label}
783
+ </SelectItem>
784
+ ))}
785
+ </SelectContent>
786
+ </Select>
770
787
  {error?.kind ? <div className="text-xs text-red-600 mt-1">{error.kind}</div> : null}
771
788
  </div>
772
789
  </div>
@@ -775,18 +792,21 @@ const FieldDefinitionCard = React.memo(function FieldDefinitionCard({
775
792
  <div className="mt-3 grid grid-cols-1 md:grid-cols-2 gap-3">
776
793
  <div>
777
794
  <label className="text-xs">Assign to fieldset</label>
778
- <select
779
- className="border rounded w-full px-2 py-1 text-sm"
780
- value={currentFieldsetValue}
781
- onChange={(event) => handleFieldsetSelect(event.target.value)}
795
+ <Select
796
+ value={currentFieldsetValue || undefined}
797
+ onValueChange={(value) => handleFieldsetSelect(value ?? '')}
782
798
  >
783
- <option value="">Unassigned</option>
784
- {(fieldsets || []).map((fs) => (
785
- <option key={fs.code} value={fs.code}>
786
- {fs.label || fs.code}
787
- </option>
788
- ))}
789
- </select>
799
+ <SelectTrigger>
800
+ <SelectValue placeholder="Unassigned" />
801
+ </SelectTrigger>
802
+ <SelectContent>
803
+ {(fieldsets || []).map((fs) => (
804
+ <SelectItem key={fs.code} value={fs.code}>
805
+ {fs.label || fs.code}
806
+ </SelectItem>
807
+ ))}
808
+ </SelectContent>
809
+ </Select>
790
810
  </div>
791
811
  {currentFieldsetValue ? (
792
812
  <div>
@@ -794,18 +814,21 @@ const FieldDefinitionCard = React.memo(function FieldDefinitionCard({
794
814
  <label className="text-xs">Group</label>
795
815
  </div>
796
816
  <div className="flex items-center gap-2 mt-1">
797
- <select
798
- className="flex-1 border rounded px-2 py-1 text-sm"
799
- value={currentGroup?.code ?? ''}
800
- onChange={(event) => handleGroupSelect(event.target.value)}
817
+ <Select
818
+ value={currentGroup?.code || undefined}
819
+ onValueChange={(value) => handleGroupSelect(value ?? '')}
801
820
  >
802
- <option value="">No group</option>
803
- {groupOptions.map((group) => (
804
- <option key={group.code} value={group.code}>
805
- {group.title || group.code}
806
- </option>
807
- ))}
808
- </select>
821
+ <SelectTrigger className="flex-1">
822
+ <SelectValue placeholder="No group" />
823
+ </SelectTrigger>
824
+ <SelectContent>
825
+ {groupOptions.map((group) => (
826
+ <SelectItem key={group.code} value={group.code}>
827
+ {group.title || group.code}
828
+ </SelectItem>
829
+ ))}
830
+ </SelectContent>
831
+ </Select>
809
832
  <IconButton
810
833
  variant="outline"
811
834
  className="text-muted-foreground"
@@ -852,16 +875,19 @@ const FieldDefinitionCard = React.memo(function FieldDefinitionCard({
852
875
  <>
853
876
  <div>
854
877
  <label className="text-xs">Editor</label>
855
- <select
856
- className="border rounded w-full px-2 py-1 text-sm"
857
- value={typeof local.configJson?.editor === 'string' ? local.configJson.editor : ''}
858
- onChange={(event) => { apply({ configJson: { ...(local.configJson || {}), editor: event.target.value || undefined } }, true) }}
878
+ <Select
879
+ value={typeof local.configJson?.editor === 'string' && local.configJson.editor ? local.configJson.editor : undefined}
880
+ onValueChange={(value) => { apply({ configJson: { ...(local.configJson || {}), editor: value || undefined } }, true) }}
859
881
  >
860
- <option value="">Default</option>
861
- <option value="markdown">Markdown (UIW)</option>
862
- <option value="simpleMarkdown">Simple Markdown</option>
863
- <option value="htmlRichText">HTML Rich Text</option>
864
- </select>
882
+ <SelectTrigger>
883
+ <SelectValue placeholder="Default" />
884
+ </SelectTrigger>
885
+ <SelectContent>
886
+ <SelectItem value="markdown">Markdown (UIW)</SelectItem>
887
+ <SelectItem value="simpleMarkdown">Simple Markdown</SelectItem>
888
+ <SelectItem value="htmlRichText">HTML Rich Text</SelectItem>
889
+ </SelectContent>
890
+ </Select>
865
891
  </div>
866
892
  {local.kind === 'text' && (
867
893
  <>
@@ -873,20 +899,23 @@ const FieldDefinitionCard = React.memo(function FieldDefinitionCard({
873
899
  {!!local.configJson?.multi && (
874
900
  <div className="md:col-span-2">
875
901
  <label className="text-xs">Multi-select input style</label>
876
- <select
877
- className="border rounded w-full px-2 py-1 text-sm"
902
+ <Select
878
903
  value={local.configJson?.input === 'listbox' ? 'listbox' : 'default'}
879
- onChange={(event) => {
880
- const { value } = event.target
904
+ onValueChange={(value) => {
881
905
  const nextConfig = { ...(local.configJson || {}) }
882
906
  if (value === 'listbox') nextConfig.input = 'listbox'
883
907
  else delete nextConfig.input
884
908
  apply({ configJson: nextConfig }, true)
885
909
  }}
886
910
  >
887
- <option value="default">Default</option>
888
- <option value="listbox">Listbox (searchable)</option>
889
- </select>
911
+ <SelectTrigger>
912
+ <SelectValue />
913
+ </SelectTrigger>
914
+ <SelectContent>
915
+ <SelectItem value="default">Default</SelectItem>
916
+ <SelectItem value="listbox">Listbox (searchable)</SelectItem>
917
+ </SelectContent>
918
+ </Select>
890
919
  </div>
891
920
  )}
892
921
  </>
@@ -1022,11 +1051,10 @@ const FieldDefinitionCard = React.memo(function FieldDefinitionCard({
1022
1051
  {(Array.isArray(local.configJson?.validation) ? local.configJson!.validation : []).map((rule: any, index: number) => (
1023
1052
  <div key={index} className="grid grid-cols-1 md:grid-cols-12 gap-2 items-center">
1024
1053
  <div className="md:col-span-3">
1025
- <select
1026
- className="border rounded w-full px-2 py-1 text-sm"
1054
+ <Select
1027
1055
  value={rule?.rule || 'required'}
1028
- onChange={(event) => {
1029
- const nextRule = event.target.value
1056
+ onValueChange={(value) => {
1057
+ const nextRule = value
1030
1058
  apply((current) => {
1031
1059
  const list = Array.isArray(current.configJson?.validation) ? [...current.configJson.validation] : []
1032
1060
  const existing = (list[index] as any) || {}
@@ -1035,18 +1063,23 @@ const FieldDefinitionCard = React.memo(function FieldDefinitionCard({
1035
1063
  }, true)
1036
1064
  }}
1037
1065
  >
1038
- <option value="required">required</option>
1039
- <option value="date">date</option>
1040
- <option value="integer">integer</option>
1041
- <option value="float">float</option>
1042
- <option value="lt">lt</option>
1043
- <option value="lte">lte</option>
1044
- <option value="gt">gt</option>
1045
- <option value="gte">gte</option>
1046
- <option value="eq">eq</option>
1047
- <option value="ne">ne</option>
1048
- <option value="regex">regex</option>
1049
- </select>
1066
+ <SelectTrigger>
1067
+ <SelectValue />
1068
+ </SelectTrigger>
1069
+ <SelectContent>
1070
+ <SelectItem value="required">required</SelectItem>
1071
+ <SelectItem value="date">date</SelectItem>
1072
+ <SelectItem value="integer">integer</SelectItem>
1073
+ <SelectItem value="float">float</SelectItem>
1074
+ <SelectItem value="lt">lt</SelectItem>
1075
+ <SelectItem value="lte">lte</SelectItem>
1076
+ <SelectItem value="gt">gt</SelectItem>
1077
+ <SelectItem value="gte">gte</SelectItem>
1078
+ <SelectItem value="eq">eq</SelectItem>
1079
+ <SelectItem value="ne">ne</SelectItem>
1080
+ <SelectItem value="regex">regex</SelectItem>
1081
+ </SelectContent>
1082
+ </Select>
1050
1083
  </div>
1051
1084
  <div className="md:col-span-4">
1052
1085
  <input
@@ -2,6 +2,13 @@
2
2
 
3
3
  import * as React from 'react'
4
4
  import { useT } from '@open-mercato/shared/lib/i18n/context'
5
+ import {
6
+ Select,
7
+ SelectContent,
8
+ SelectItem,
9
+ SelectTrigger,
10
+ SelectValue,
11
+ } from '../../primitives/select'
5
12
  import { DATE_RANGE_OPTIONS, type DateRangePreset } from './dateRanges'
6
13
 
7
14
  export type DateRangeSelectProps = {
@@ -31,18 +38,18 @@ export function DateRangeSelect({
31
38
  {label}
32
39
  </label>
33
40
  )}
34
- <select
35
- id={id}
36
- className="w-full rounded-md border bg-background px-2 py-1 text-sm text-foreground focus-visible:border-ring focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
37
- value={value}
38
- onChange={(e) => onChange(e.target.value as DateRangePreset)}
39
- >
40
- {DATE_RANGE_OPTIONS.map((option) => (
41
- <option key={option.value} value={option.value}>
42
- {t(option.labelKey, option.value.replace(/_/g, ' '))}
43
- </option>
44
- ))}
45
- </select>
41
+ <Select value={value} onValueChange={(next) => onChange(next as DateRangePreset)}>
42
+ <SelectTrigger id={id} size="sm">
43
+ <SelectValue />
44
+ </SelectTrigger>
45
+ <SelectContent>
46
+ {DATE_RANGE_OPTIONS.map((option) => (
47
+ <SelectItem key={option.value} value={option.value}>
48
+ {t(option.labelKey, option.value.replace(/_/g, ' '))}
49
+ </SelectItem>
50
+ ))}
51
+ </SelectContent>
52
+ </Select>
46
53
  </div>
47
54
  )
48
55
  }
@@ -2,6 +2,13 @@
2
2
 
3
3
  import * as React from 'react'
4
4
  import { useT } from '@open-mercato/shared/lib/i18n/context'
5
+ import {
6
+ Select,
7
+ SelectContent,
8
+ SelectItem,
9
+ SelectTrigger,
10
+ SelectValue,
11
+ } from '../../primitives/select'
5
12
  import { DATE_RANGE_OPTIONS, type DateRangePreset } from './dateRanges'
6
13
 
7
14
  export type InlineDateRangeSelectProps = {
@@ -23,29 +30,18 @@ export function InlineDateRangeSelect({
23
30
  : value.replace(/_/g, ' ')
24
31
 
25
32
  return (
26
- <div className={`relative inline-flex items-center ${className}`}>
27
- <select
28
- className="appearance-none rounded-md border border-border bg-background px-2 py-0.5 pr-6 text-xs text-foreground hover:border-primary/50 focus-visible:border-ring focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring cursor-pointer"
29
- value={value}
30
- onChange={(e) => onChange(e.target.value as DateRangePreset)}
31
- title={displayLabel}
32
- >
33
+ <Select value={value} onValueChange={(next) => onChange(next as DateRangePreset)}>
34
+ <SelectTrigger size="sm" className={className} title={displayLabel}>
35
+ <SelectValue />
36
+ </SelectTrigger>
37
+ <SelectContent>
33
38
  {DATE_RANGE_OPTIONS.map((option) => (
34
- <option key={option.value} value={option.value}>
39
+ <SelectItem key={option.value} value={option.value}>
35
40
  {t(option.labelKey, option.value.replace(/_/g, ' '))}
36
- </option>
41
+ </SelectItem>
37
42
  ))}
38
- </select>
39
- <svg
40
- className="pointer-events-none absolute right-1.5 h-3 w-3 text-muted-foreground"
41
- fill="none"
42
- viewBox="0 0 24 24"
43
- stroke="currentColor"
44
- strokeWidth={2}
45
- >
46
- <path strokeLinecap="round" strokeLinejoin="round" d="M19 9l-7 7-7-7" />
47
- </svg>
48
- </div>
43
+ </SelectContent>
44
+ </Select>
49
45
  )
50
46
  }
51
47