@open-mercato/ui 0.6.1-develop.3256.1.fe3dec2464 → 0.6.1-develop.3276.1.1fcd145078

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 (137) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/backend/CrudForm.js +46 -129
  3. package/dist/backend/CrudForm.js.map +3 -3
  4. package/dist/backend/DataTable.js +10 -3
  5. package/dist/backend/DataTable.js.map +2 -2
  6. package/dist/backend/EmptyState.js +1 -47
  7. package/dist/backend/EmptyState.js.map +2 -2
  8. package/dist/backend/FilterOverlay.js +31 -26
  9. package/dist/backend/FilterOverlay.js.map +2 -2
  10. package/dist/backend/FlashMessages.js +23 -25
  11. package/dist/backend/FlashMessages.js.map +2 -2
  12. package/dist/backend/JsonBuilder.js +4 -2
  13. package/dist/backend/JsonBuilder.js.map +2 -2
  14. package/dist/backend/LogList.js +49 -0
  15. package/dist/backend/LogList.js.map +7 -0
  16. package/dist/backend/crud/CollapsibleGroup.js +13 -3
  17. package/dist/backend/crud/CollapsibleGroup.js.map +2 -2
  18. package/dist/backend/detail/InlineEditors.js +80 -32
  19. package/dist/backend/detail/InlineEditors.js.map +2 -2
  20. package/dist/backend/inputs/ComboboxInput.js +60 -6
  21. package/dist/backend/inputs/ComboboxInput.js.map +2 -2
  22. package/dist/backend/inputs/DatePicker.js +6 -142
  23. package/dist/backend/inputs/DatePicker.js.map +2 -2
  24. package/dist/backend/inputs/DateTimePicker.js +6 -182
  25. package/dist/backend/inputs/DateTimePicker.js.map +2 -2
  26. package/dist/backend/inputs/TagsInput.js +16 -19
  27. package/dist/backend/inputs/TagsInput.js.map +2 -2
  28. package/dist/backend/inputs/TimeInput.js +1 -1
  29. package/dist/backend/inputs/TimeInput.js.map +2 -2
  30. package/dist/backend/inputs/TimePicker.js +83 -72
  31. package/dist/backend/inputs/TimePicker.js.map +2 -2
  32. package/dist/backend/utils/customFieldForms.js +2 -2
  33. package/dist/backend/utils/customFieldForms.js.map +1 -1
  34. package/dist/portal/components/PortalEmptyState.js +13 -9
  35. package/dist/portal/components/PortalEmptyState.js.map +2 -2
  36. package/dist/primitives/accordion.js +137 -0
  37. package/dist/primitives/accordion.js.map +7 -0
  38. package/dist/primitives/alert.js +180 -12
  39. package/dist/primitives/alert.js.map +2 -2
  40. package/dist/primitives/calendar.js +122 -32
  41. package/dist/primitives/calendar.js.map +2 -2
  42. package/dist/primitives/compact-select.js +47 -0
  43. package/dist/primitives/compact-select.js.map +7 -0
  44. package/dist/primitives/counter-input.js +174 -0
  45. package/dist/primitives/counter-input.js.map +7 -0
  46. package/dist/primitives/date-picker-helpers.js +79 -0
  47. package/dist/primitives/date-picker-helpers.js.map +7 -0
  48. package/dist/primitives/date-picker.js +216 -0
  49. package/dist/primitives/date-picker.js.map +7 -0
  50. package/dist/primitives/date-range-picker.js +239 -0
  51. package/dist/primitives/date-range-picker.js.map +7 -0
  52. package/dist/primitives/digit-input.js +157 -0
  53. package/dist/primitives/digit-input.js.map +7 -0
  54. package/dist/primitives/empty-state.js +93 -0
  55. package/dist/primitives/empty-state.js.map +7 -0
  56. package/dist/primitives/inline-input.js +29 -0
  57. package/dist/primitives/inline-input.js.map +7 -0
  58. package/dist/primitives/inline-select.js +41 -0
  59. package/dist/primitives/inline-select.js.map +7 -0
  60. package/dist/primitives/notification-stack.js +116 -0
  61. package/dist/primitives/notification-stack.js.map +7 -0
  62. package/dist/primitives/notification.js +62 -0
  63. package/dist/primitives/notification.js.map +7 -0
  64. package/dist/primitives/rich-editor.js +1230 -0
  65. package/dist/primitives/rich-editor.js.map +7 -0
  66. package/dist/primitives/select.js +1 -0
  67. package/dist/primitives/select.js.map +2 -2
  68. package/dist/primitives/skeleton.js +55 -0
  69. package/dist/primitives/skeleton.js.map +7 -0
  70. package/dist/primitives/tag-input.js +186 -0
  71. package/dist/primitives/tag-input.js.map +7 -0
  72. package/dist/primitives/tag.js +66 -13
  73. package/dist/primitives/tag.js.map +2 -2
  74. package/dist/primitives/time-picker.js +793 -0
  75. package/dist/primitives/time-picker.js.map +7 -0
  76. package/package.json +4 -3
  77. package/src/backend/CrudForm.tsx +61 -113
  78. package/src/backend/DataTable.tsx +17 -5
  79. package/src/backend/EmptyState.tsx +13 -63
  80. package/src/backend/FilterOverlay.tsx +38 -22
  81. package/src/backend/FlashMessages.tsx +23 -24
  82. package/src/backend/JsonBuilder.tsx +4 -2
  83. package/src/backend/LogList.tsx +109 -0
  84. package/src/backend/__tests__/CrudForm.datetime.test.tsx +12 -7
  85. package/src/backend/__tests__/FilterOverlay.dateRange.test.tsx +89 -0
  86. package/src/backend/__tests__/LogList.test.tsx +163 -0
  87. package/src/backend/crud/CollapsibleGroup.tsx +17 -3
  88. package/src/backend/detail/InlineEditors.tsx +78 -25
  89. package/src/backend/detail/__tests__/InlineEditors.test.tsx +81 -1
  90. package/src/backend/inputs/ComboboxInput.tsx +81 -9
  91. package/src/backend/inputs/DatePicker.tsx +28 -155
  92. package/src/backend/inputs/DateTimePicker.tsx +25 -193
  93. package/src/backend/inputs/TagsInput.tsx +10 -13
  94. package/src/backend/inputs/TimeInput.tsx +2 -1
  95. package/src/backend/inputs/TimePicker.tsx +99 -70
  96. package/src/backend/inputs/__tests__/ComboboxInput.test.tsx +133 -0
  97. package/src/backend/inputs/__tests__/TimePicker.test.tsx +2 -2
  98. package/src/backend/utils/__tests__/richTextSanitizer.test.ts +5 -2
  99. package/src/backend/utils/customFieldForms.ts +2 -2
  100. package/src/portal/components/PortalEmptyState.tsx +27 -22
  101. package/src/primitives/__tests__/accordion.test.tsx +197 -0
  102. package/src/primitives/__tests__/alert.test.tsx +204 -0
  103. package/src/primitives/__tests__/compact-select.test.tsx +98 -0
  104. package/src/primitives/__tests__/counter-input.test.tsx +165 -0
  105. package/src/primitives/__tests__/date-picker.test.tsx +221 -0
  106. package/src/primitives/__tests__/date-range-picker.test.tsx +173 -0
  107. package/src/primitives/__tests__/digit-input.test.tsx +168 -0
  108. package/src/primitives/__tests__/empty-state.test.tsx +154 -0
  109. package/src/primitives/__tests__/inline-input.test.tsx +100 -0
  110. package/src/primitives/__tests__/inline-select.test.tsx +103 -0
  111. package/src/primitives/__tests__/notification-stack.test.tsx +174 -0
  112. package/src/primitives/__tests__/notification.test.tsx +119 -0
  113. package/src/primitives/__tests__/rich-editor.test.tsx +411 -0
  114. package/src/primitives/__tests__/skeleton.test.tsx +76 -0
  115. package/src/primitives/__tests__/tag-input.test.tsx +198 -0
  116. package/src/primitives/__tests__/tag.test.tsx +123 -0
  117. package/src/primitives/__tests__/time-picker.test.tsx +392 -0
  118. package/src/primitives/accordion.tsx +225 -0
  119. package/src/primitives/alert.tsx +234 -24
  120. package/src/primitives/calendar.tsx +146 -38
  121. package/src/primitives/compact-select.tsx +59 -0
  122. package/src/primitives/counter-input.tsx +211 -0
  123. package/src/primitives/date-picker-helpers.ts +96 -0
  124. package/src/primitives/date-picker.tsx +290 -0
  125. package/src/primitives/date-range-picker.tsx +281 -0
  126. package/src/primitives/digit-input.tsx +208 -0
  127. package/src/primitives/empty-state.tsx +140 -0
  128. package/src/primitives/inline-input.tsx +55 -0
  129. package/src/primitives/inline-select.tsx +74 -0
  130. package/src/primitives/notification-stack.tsx +185 -0
  131. package/src/primitives/notification.tsx +136 -0
  132. package/src/primitives/rich-editor.tsx +1578 -0
  133. package/src/primitives/select.tsx +1 -0
  134. package/src/primitives/skeleton.tsx +70 -0
  135. package/src/primitives/tag-input.tsx +223 -0
  136. package/src/primitives/tag.tsx +69 -10
  137. package/src/primitives/time-picker.tsx +1059 -0
@@ -3,7 +3,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { useT } from "@open-mercato/shared/lib/i18n/context";
5
5
  import { Button } from "../../primitives/button.js";
6
- import { IconButton } from "../../primitives/icon-button.js";
6
+ import { Tag } from "../../primitives/tag.js";
7
7
  function normalizeOptions(input) {
8
8
  if (!Array.isArray(input)) return [];
9
9
  return input.map((option) => {
@@ -167,24 +167,21 @@ function TagsInput({
167
167
  const option = optionMap.get(tag);
168
168
  const label = option?.label ?? tag;
169
169
  const description = option?.description;
170
- return /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-2 rounded-sm bg-muted px-2 py-0.5 text-xs", children: [
171
- /* @__PURE__ */ jsxs("span", { className: "flex flex-col items-start leading-tight", children: [
172
- /* @__PURE__ */ jsx("span", { className: "whitespace-nowrap", children: label }),
173
- description ? /* @__PURE__ */ jsx("span", { className: "text-overline text-muted-foreground", children: description }) : null
174
- ] }),
175
- /* @__PURE__ */ jsx(
176
- IconButton,
177
- {
178
- type: "button",
179
- variant: "ghost",
180
- size: "xs",
181
- className: "opacity-60 hover:opacity-100",
182
- onClick: () => removeTag(tag),
183
- disabled,
184
- children: "\xD7"
185
- }
186
- )
187
- ] }, tag);
170
+ return /* @__PURE__ */ jsx(
171
+ Tag,
172
+ {
173
+ shape: "square",
174
+ variant: "default",
175
+ disabled,
176
+ onRemove: () => removeTag(tag),
177
+ removeAriaLabel: t("ui.inputs.tagsInput.removeTag", "Remove {label}", { label }),
178
+ children: /* @__PURE__ */ jsxs("span", { className: "flex flex-col items-start leading-tight", children: [
179
+ /* @__PURE__ */ jsx("span", { className: "whitespace-nowrap", children: label }),
180
+ description ? /* @__PURE__ */ jsx("span", { className: "text-overline text-muted-foreground", children: description }) : null
181
+ ] })
182
+ },
183
+ tag
184
+ );
188
185
  }),
189
186
  /* @__PURE__ */ jsx(
190
187
  "input",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/inputs/TagsInput.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '../../primitives/button'\nimport { IconButton } from '../../primitives/icon-button'\n\nexport type TagsInputOption = {\n value: string\n label: string\n description?: string | null\n}\n\nexport type TagsInputProps = {\n value: string[]\n onChange: (next: string[]) => void\n placeholder?: string\n suggestions?: Array<string | TagsInputOption>\n loadSuggestions?: (query?: string) => Promise<Array<string | TagsInputOption>>\n selectedOptions?: TagsInputOption[]\n resolveLabel?: (value: string) => string\n resolveDescription?: (value: string) => string | null | undefined\n autoFocus?: boolean\n disabled?: boolean\n allowCustomValues?: boolean\n showSuggestionsOnFocus?: boolean\n suppressInitialSuggestionsOnFocus?: boolean\n}\n\nfunction normalizeOptions(input?: Array<string | TagsInputOption>): TagsInputOption[] {\n if (!Array.isArray(input)) return []\n return input\n .map((option) => {\n if (typeof option === 'string') {\n const trimmed = option.trim()\n if (!trimmed) return null\n return { value: trimmed, label: trimmed }\n }\n const value = typeof option.value === 'string' ? option.value.trim() : ''\n if (!value) return null\n return {\n value,\n label: option.label?.trim() || value,\n description: option.description ?? null,\n }\n })\n .filter((option): option is TagsInputOption => !!option)\n}\n\nexport function TagsInput({\n value,\n onChange,\n placeholder,\n suggestions,\n loadSuggestions,\n selectedOptions,\n resolveLabel,\n resolveDescription,\n autoFocus,\n disabled = false,\n allowCustomValues = true,\n showSuggestionsOnFocus = true,\n suppressInitialSuggestionsOnFocus = false,\n}: TagsInputProps) {\n const t = useT()\n const [input, setInput] = React.useState('')\n const [asyncOptions, setAsyncOptions] = React.useState<TagsInputOption[]>([])\n const [loading, setLoading] = React.useState(false)\n const [touched, setTouched] = React.useState(false)\n const suppressBlurCommitRef = React.useRef(false)\n const suppressSuggestionsOnFocusRef = React.useRef(Boolean(autoFocus && suppressInitialSuggestionsOnFocus && !disabled))\n const valueRef = React.useRef(value)\n\n React.useEffect(() => {\n valueRef.current = value\n }, [value])\n\n React.useEffect(() => {\n suppressSuggestionsOnFocusRef.current = Boolean(autoFocus && suppressInitialSuggestionsOnFocus && !disabled)\n }, [autoFocus, disabled, suppressInitialSuggestionsOnFocus])\n\n const staticOptions = React.useMemo(() => normalizeOptions(suggestions), [suggestions])\n const selectedOptionList = React.useMemo(\n () => normalizeOptions(selectedOptions),\n [selectedOptions]\n )\n\n const optionMap = React.useMemo(() => {\n const map = new Map<string, TagsInputOption>()\n const register = (option: TagsInputOption) => {\n if (!map.has(option.value)) {\n map.set(option.value, option)\n }\n }\n staticOptions.forEach(register)\n asyncOptions.forEach(register)\n selectedOptionList.forEach(register)\n value.forEach((val) => {\n if (map.has(val)) return\n map.set(val, {\n value: val,\n label: resolveLabel?.(val) ?? val,\n description: resolveDescription?.(val) ?? null,\n })\n })\n return map\n }, [asyncOptions, resolveDescription, resolveLabel, selectedOptionList, staticOptions, value])\n\n const availableOptions = React.useMemo(() => {\n return Array.from(optionMap.values()).filter((option) => !value.includes(option.value))\n }, [optionMap, value])\n\n const filteredSuggestions = React.useMemo(() => {\n const query = input.toLowerCase().trim()\n if (!query) return availableOptions.slice(0, 8)\n return availableOptions.filter((option) => {\n const labelMatch = option.label.toLowerCase().includes(query)\n const descMatch = option.description?.toLowerCase().includes(query)\n return labelMatch || Boolean(descMatch)\n })\n }, [availableOptions, input])\n\n React.useEffect(() => {\n if (!loadSuggestions || !touched || disabled) return\n const query = input.trim()\n let cancelled = false\n const handle = window.setTimeout(async () => {\n setLoading(true)\n try {\n const items = await loadSuggestions(query)\n if (!cancelled) {\n setAsyncOptions(normalizeOptions(items))\n }\n } finally {\n if (!cancelled) setLoading(false)\n }\n }, 200)\n return () => {\n cancelled = true\n window.clearTimeout(handle)\n }\n }, [disabled, input, loadSuggestions, touched])\n\n const addValue = React.useCallback(\n (nextValue: string) => {\n if (disabled) return\n const trimmed = nextValue.trim()\n if (!trimmed) return\n const currentValue = valueRef.current\n if (currentValue.includes(trimmed)) return\n const next = [...currentValue, trimmed]\n valueRef.current = next\n onChange(next)\n },\n [disabled, onChange]\n )\n\n const findOptionForInput = React.useCallback(\n (raw: string): TagsInputOption | null => {\n const query = raw.trim().toLowerCase()\n if (!query) return null\n for (const option of optionMap.values()) {\n if (option.value === raw.trim()) return option\n if (option.label.toLowerCase() === query) return option\n }\n return null\n },\n [optionMap]\n )\n\n const addTag = React.useCallback(\n (raw: string) => {\n if (disabled) return\n const option = findOptionForInput(raw)\n if (option) {\n addValue(option.value)\n return\n }\n if (!allowCustomValues) return\n addValue(raw)\n },\n [addValue, allowCustomValues, disabled, findOptionForInput]\n )\n\n const removeTag = React.useCallback(\n (tag: string) => {\n if (disabled) return\n const next = valueRef.current.filter((candidate) => candidate !== tag)\n valueRef.current = next\n onChange(next)\n },\n [disabled, onChange]\n )\n\n return (\n <div\n className={[\n 'w-full rounded border px-2 py-1',\n disabled ? 'bg-muted text-muted-foreground/80 cursor-not-allowed' : '',\n ]\n .filter(Boolean)\n .join(' ')}\n aria-disabled={disabled || undefined}\n >\n <div className=\"flex flex-wrap gap-1\">\n {value.map((tag) => {\n const option = optionMap.get(tag)\n const label = option?.label ?? tag\n const description = option?.description\n return (\n <span key={tag} className=\"inline-flex items-center gap-2 rounded-sm bg-muted px-2 py-0.5 text-xs\">\n <span className=\"flex flex-col items-start leading-tight\">\n <span className=\"whitespace-nowrap\">{label}</span>\n {description ? (\n <span className=\"text-overline text-muted-foreground\">{description}</span>\n ) : null}\n </span>\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"xs\"\n className=\"opacity-60 hover:opacity-100\"\n onClick={() => removeTag(tag)}\n disabled={disabled}\n >\n \u00D7\n </IconButton>\n </span>\n )\n })}\n <input\n className=\"flex-1 min-w-[80px] sm:min-w-[120px] border-0 py-1 text-sm outline-none disabled:bg-transparent\"\n value={input}\n placeholder={placeholder || t('ui.inputs.tagsInput.placeholder', 'Add tag and press Enter')}\n autoFocus={autoFocus}\n data-crud-focus-target=\"\"\n disabled={disabled}\n onFocus={() => {\n if (suppressSuggestionsOnFocusRef.current) {\n suppressSuggestionsOnFocusRef.current = false\n return\n }\n if (showSuggestionsOnFocus) {\n setTouched(true)\n }\n }}\n onMouseDown={() => {\n setTouched(true)\n }}\n onChange={(event) => {\n setTouched(true)\n setInput(event.target.value)\n }}\n onKeyDown={(event) => {\n if (disabled) return\n if (event.key === 'Enter' || event.key === ',') {\n event.preventDefault()\n addTag(input)\n setInput('')\n } else if (event.key === 'Backspace' && input === '' && value.length > 0) {\n removeTag(value[value.length - 1])\n }\n }}\n onBlur={() => {\n if (disabled) return\n if (suppressBlurCommitRef.current) {\n suppressBlurCommitRef.current = false\n setInput('')\n return\n }\n addTag(input)\n setInput('')\n }}\n />\n {loading && touched ? (\n <div className=\"basis-full mt-1 text-xs text-muted-foreground\">Loading suggestions\u2026</div>\n ) : null}\n {!loading && filteredSuggestions.length ? (\n <div className=\"basis-full mt-1 flex flex-col gap-1\">\n {filteredSuggestions.map((option) => (\n <Button\n key={option.value}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"w-full justify-start font-normal flex flex-col items-start text-xs px-1.5 py-1\"\n onMouseDown={(event) => {\n suppressBlurCommitRef.current = true\n event.preventDefault()\n }}\n onClick={() => {\n suppressBlurCommitRef.current = false\n addValue(option.value)\n setInput('')\n }}\n >\n <span>{option.label}</span>\n {option.description ? (\n <span className=\"text-overline text-muted-foreground\">{option.description}</span>\n ) : null}\n </Button>\n ))}\n </div>\n ) : null}\n </div>\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '../../primitives/button'\nimport { Tag } from '../../primitives/tag'\n\nexport type TagsInputOption = {\n value: string\n label: string\n description?: string | null\n}\n\nexport type TagsInputProps = {\n value: string[]\n onChange: (next: string[]) => void\n placeholder?: string\n suggestions?: Array<string | TagsInputOption>\n loadSuggestions?: (query?: string) => Promise<Array<string | TagsInputOption>>\n selectedOptions?: TagsInputOption[]\n resolveLabel?: (value: string) => string\n resolveDescription?: (value: string) => string | null | undefined\n autoFocus?: boolean\n disabled?: boolean\n allowCustomValues?: boolean\n showSuggestionsOnFocus?: boolean\n suppressInitialSuggestionsOnFocus?: boolean\n}\n\nfunction normalizeOptions(input?: Array<string | TagsInputOption>): TagsInputOption[] {\n if (!Array.isArray(input)) return []\n return input\n .map((option) => {\n if (typeof option === 'string') {\n const trimmed = option.trim()\n if (!trimmed) return null\n return { value: trimmed, label: trimmed }\n }\n const value = typeof option.value === 'string' ? option.value.trim() : ''\n if (!value) return null\n return {\n value,\n label: option.label?.trim() || value,\n description: option.description ?? null,\n }\n })\n .filter((option): option is TagsInputOption => !!option)\n}\n\nexport function TagsInput({\n value,\n onChange,\n placeholder,\n suggestions,\n loadSuggestions,\n selectedOptions,\n resolveLabel,\n resolveDescription,\n autoFocus,\n disabled = false,\n allowCustomValues = true,\n showSuggestionsOnFocus = true,\n suppressInitialSuggestionsOnFocus = false,\n}: TagsInputProps) {\n const t = useT()\n const [input, setInput] = React.useState('')\n const [asyncOptions, setAsyncOptions] = React.useState<TagsInputOption[]>([])\n const [loading, setLoading] = React.useState(false)\n const [touched, setTouched] = React.useState(false)\n const suppressBlurCommitRef = React.useRef(false)\n const suppressSuggestionsOnFocusRef = React.useRef(Boolean(autoFocus && suppressInitialSuggestionsOnFocus && !disabled))\n const valueRef = React.useRef(value)\n\n React.useEffect(() => {\n valueRef.current = value\n }, [value])\n\n React.useEffect(() => {\n suppressSuggestionsOnFocusRef.current = Boolean(autoFocus && suppressInitialSuggestionsOnFocus && !disabled)\n }, [autoFocus, disabled, suppressInitialSuggestionsOnFocus])\n\n const staticOptions = React.useMemo(() => normalizeOptions(suggestions), [suggestions])\n const selectedOptionList = React.useMemo(\n () => normalizeOptions(selectedOptions),\n [selectedOptions]\n )\n\n const optionMap = React.useMemo(() => {\n const map = new Map<string, TagsInputOption>()\n const register = (option: TagsInputOption) => {\n if (!map.has(option.value)) {\n map.set(option.value, option)\n }\n }\n staticOptions.forEach(register)\n asyncOptions.forEach(register)\n selectedOptionList.forEach(register)\n value.forEach((val) => {\n if (map.has(val)) return\n map.set(val, {\n value: val,\n label: resolveLabel?.(val) ?? val,\n description: resolveDescription?.(val) ?? null,\n })\n })\n return map\n }, [asyncOptions, resolveDescription, resolveLabel, selectedOptionList, staticOptions, value])\n\n const availableOptions = React.useMemo(() => {\n return Array.from(optionMap.values()).filter((option) => !value.includes(option.value))\n }, [optionMap, value])\n\n const filteredSuggestions = React.useMemo(() => {\n const query = input.toLowerCase().trim()\n if (!query) return availableOptions.slice(0, 8)\n return availableOptions.filter((option) => {\n const labelMatch = option.label.toLowerCase().includes(query)\n const descMatch = option.description?.toLowerCase().includes(query)\n return labelMatch || Boolean(descMatch)\n })\n }, [availableOptions, input])\n\n React.useEffect(() => {\n if (!loadSuggestions || !touched || disabled) return\n const query = input.trim()\n let cancelled = false\n const handle = window.setTimeout(async () => {\n setLoading(true)\n try {\n const items = await loadSuggestions(query)\n if (!cancelled) {\n setAsyncOptions(normalizeOptions(items))\n }\n } finally {\n if (!cancelled) setLoading(false)\n }\n }, 200)\n return () => {\n cancelled = true\n window.clearTimeout(handle)\n }\n }, [disabled, input, loadSuggestions, touched])\n\n const addValue = React.useCallback(\n (nextValue: string) => {\n if (disabled) return\n const trimmed = nextValue.trim()\n if (!trimmed) return\n const currentValue = valueRef.current\n if (currentValue.includes(trimmed)) return\n const next = [...currentValue, trimmed]\n valueRef.current = next\n onChange(next)\n },\n [disabled, onChange]\n )\n\n const findOptionForInput = React.useCallback(\n (raw: string): TagsInputOption | null => {\n const query = raw.trim().toLowerCase()\n if (!query) return null\n for (const option of optionMap.values()) {\n if (option.value === raw.trim()) return option\n if (option.label.toLowerCase() === query) return option\n }\n return null\n },\n [optionMap]\n )\n\n const addTag = React.useCallback(\n (raw: string) => {\n if (disabled) return\n const option = findOptionForInput(raw)\n if (option) {\n addValue(option.value)\n return\n }\n if (!allowCustomValues) return\n addValue(raw)\n },\n [addValue, allowCustomValues, disabled, findOptionForInput]\n )\n\n const removeTag = React.useCallback(\n (tag: string) => {\n if (disabled) return\n const next = valueRef.current.filter((candidate) => candidate !== tag)\n valueRef.current = next\n onChange(next)\n },\n [disabled, onChange]\n )\n\n return (\n <div\n className={[\n 'w-full rounded border px-2 py-1',\n disabled ? 'bg-muted text-muted-foreground/80 cursor-not-allowed' : '',\n ]\n .filter(Boolean)\n .join(' ')}\n aria-disabled={disabled || undefined}\n >\n <div className=\"flex flex-wrap gap-1\">\n {value.map((tag) => {\n const option = optionMap.get(tag)\n const label = option?.label ?? tag\n const description = option?.description\n return (\n <Tag\n key={tag}\n shape=\"square\"\n variant=\"default\"\n disabled={disabled}\n onRemove={() => removeTag(tag)}\n removeAriaLabel={t('ui.inputs.tagsInput.removeTag', 'Remove {label}', { label })}\n >\n <span className=\"flex flex-col items-start leading-tight\">\n <span className=\"whitespace-nowrap\">{label}</span>\n {description ? (\n <span className=\"text-overline text-muted-foreground\">{description}</span>\n ) : null}\n </span>\n </Tag>\n )\n })}\n <input\n className=\"flex-1 min-w-[80px] sm:min-w-[120px] border-0 py-1 text-sm outline-none disabled:bg-transparent\"\n value={input}\n placeholder={placeholder || t('ui.inputs.tagsInput.placeholder', 'Add tag and press Enter')}\n autoFocus={autoFocus}\n data-crud-focus-target=\"\"\n disabled={disabled}\n onFocus={() => {\n if (suppressSuggestionsOnFocusRef.current) {\n suppressSuggestionsOnFocusRef.current = false\n return\n }\n if (showSuggestionsOnFocus) {\n setTouched(true)\n }\n }}\n onMouseDown={() => {\n setTouched(true)\n }}\n onChange={(event) => {\n setTouched(true)\n setInput(event.target.value)\n }}\n onKeyDown={(event) => {\n if (disabled) return\n if (event.key === 'Enter' || event.key === ',') {\n event.preventDefault()\n addTag(input)\n setInput('')\n } else if (event.key === 'Backspace' && input === '' && value.length > 0) {\n removeTag(value[value.length - 1])\n }\n }}\n onBlur={() => {\n if (disabled) return\n if (suppressBlurCommitRef.current) {\n suppressBlurCommitRef.current = false\n setInput('')\n return\n }\n addTag(input)\n setInput('')\n }}\n />\n {loading && touched ? (\n <div className=\"basis-full mt-1 text-xs text-muted-foreground\">Loading suggestions\u2026</div>\n ) : null}\n {!loading && filteredSuggestions.length ? (\n <div className=\"basis-full mt-1 flex flex-col gap-1\">\n {filteredSuggestions.map((option) => (\n <Button\n key={option.value}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"w-full justify-start font-normal flex flex-col items-start text-xs px-1.5 py-1\"\n onMouseDown={(event) => {\n suppressBlurCommitRef.current = true\n event.preventDefault()\n }}\n onClick={() => {\n suppressBlurCommitRef.current = false\n addValue(option.value)\n setInput('')\n }}\n >\n <span>{option.label}</span>\n {option.description ? (\n <span className=\"text-overline text-muted-foreground\">{option.description}</span>\n ) : null}\n </Button>\n ))}\n </div>\n ) : null}\n </div>\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -88,7 +88,7 @@ function TimeInput({
88
88
  [disabled, emitChange, hour, minuteStep]
89
89
  );
90
90
  const wrapperClass = "w-14";
91
- const innerInputClass = "text-center tabular-nums";
91
+ const innerInputClass = "text-center tabular-nums leading-none [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:m-0 [&::-webkit-outer-spin-button]:m-0";
92
92
  return /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-1", className), children: [
93
93
  /* @__PURE__ */ jsx(
94
94
  Input,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/inputs/TimeInput.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Input } from '../../primitives/input'\n\nexport type TimeInputProps = {\n value?: string | null\n onChange: (time: string) => void\n disabled?: boolean\n className?: string\n minuteStep?: number\n hourLabel?: string\n minuteLabel?: string\n}\n\nfunction padTwo(n: number): string {\n return String(n).padStart(2, '0')\n}\n\nfunction parseTime(value: string | null | undefined): { hour: number; minute: number } {\n if (!value) return { hour: 0, minute: 0 }\n const parts = value.split(':')\n const hour = parseInt(parts[0] ?? '0', 10)\n const minute = parseInt(parts[1] ?? '0', 10)\n return {\n hour: isNaN(hour) ? 0 : Math.max(0, Math.min(23, hour)),\n minute: isNaN(minute) ? 0 : Math.max(0, Math.min(59, minute)),\n }\n}\n\nfunction snapMinute(minute: number, step: number): number {\n if (step <= 1) return minute\n return Math.round(minute / step) * step % 60\n}\n\nexport function TimeInput({\n value,\n onChange,\n disabled = false,\n className,\n minuteStep = 1,\n hourLabel: hourLabelProp,\n minuteLabel: minuteLabelProp,\n}: TimeInputProps) {\n const t = useT()\n const hourLabel = hourLabelProp ?? t('ui.timePicker.hourLabel', 'Hour')\n const minuteLabel = minuteLabelProp ?? t('ui.timePicker.minuteLabel', 'Minute')\n\n const { hour, minute } = parseTime(value)\n\n const emitChange = React.useCallback(\n (nextHour: number, nextMinute: number) => {\n onChange(`${padTwo(nextHour)}:${padTwo(nextMinute)}`)\n },\n [onChange]\n )\n\n const handleHourKeyDown = React.useCallback(\n (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (disabled) return\n if (e.key === 'ArrowUp') {\n e.preventDefault()\n emitChange((hour + 1) % 24, minute)\n } else if (e.key === 'ArrowDown') {\n e.preventDefault()\n emitChange((hour + 23) % 24, minute)\n }\n },\n [disabled, emitChange, hour, minute]\n )\n\n const handleMinuteKeyDown = React.useCallback(\n (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (disabled) return\n if (e.key === 'ArrowUp') {\n e.preventDefault()\n const step = minuteStep > 1 ? minuteStep : 1\n emitChange(hour, (minute + step) % 60)\n } else if (e.key === 'ArrowDown') {\n e.preventDefault()\n const step = minuteStep > 1 ? minuteStep : 1\n emitChange(hour, (minute + 60 - step) % 60)\n }\n },\n [disabled, emitChange, hour, minute, minuteStep]\n )\n\n const handleHourChange = React.useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n if (disabled) return\n const raw = parseInt(e.target.value, 10)\n if (isNaN(raw)) return\n emitChange(Math.max(0, Math.min(23, raw)), minute)\n },\n [disabled, emitChange, minute]\n )\n\n const handleMinuteChange = React.useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n if (disabled) return\n const raw = parseInt(e.target.value, 10)\n if (isNaN(raw)) return\n const snapped = minuteStep > 1 ? snapMinute(raw, minuteStep) : Math.max(0, Math.min(59, raw))\n emitChange(hour, snapped)\n },\n [disabled, emitChange, hour, minuteStep]\n )\n\n const wrapperClass = 'w-14'\n const innerInputClass = 'text-center tabular-nums'\n\n return (\n <div className={cn('flex items-center gap-1', className)}>\n <Input\n type=\"number\"\n min={0}\n max={23}\n value={padTwo(hour)}\n onChange={handleHourChange}\n onKeyDown={handleHourKeyDown}\n disabled={disabled}\n aria-label={hourLabel}\n data-crud-focus-target=\"\"\n className={wrapperClass}\n inputClassName={innerInputClass}\n />\n <span className=\"text-sm font-medium select-none\">:</span>\n <Input\n type=\"number\"\n min={0}\n max={59}\n step={minuteStep}\n value={padTwo(minute)}\n onChange={handleMinuteChange}\n onKeyDown={handleMinuteKeyDown}\n disabled={disabled}\n aria-label={minuteLabel}\n className={wrapperClass}\n inputClassName={innerInputClass}\n />\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Input } from '../../primitives/input'\n\nexport type TimeInputProps = {\n value?: string | null\n onChange: (time: string) => void\n disabled?: boolean\n className?: string\n minuteStep?: number\n hourLabel?: string\n minuteLabel?: string\n}\n\nfunction padTwo(n: number): string {\n return String(n).padStart(2, '0')\n}\n\nfunction parseTime(value: string | null | undefined): { hour: number; minute: number } {\n if (!value) return { hour: 0, minute: 0 }\n const parts = value.split(':')\n const hour = parseInt(parts[0] ?? '0', 10)\n const minute = parseInt(parts[1] ?? '0', 10)\n return {\n hour: isNaN(hour) ? 0 : Math.max(0, Math.min(23, hour)),\n minute: isNaN(minute) ? 0 : Math.max(0, Math.min(59, minute)),\n }\n}\n\nfunction snapMinute(minute: number, step: number): number {\n if (step <= 1) return minute\n return Math.round(minute / step) * step % 60\n}\n\nexport function TimeInput({\n value,\n onChange,\n disabled = false,\n className,\n minuteStep = 1,\n hourLabel: hourLabelProp,\n minuteLabel: minuteLabelProp,\n}: TimeInputProps) {\n const t = useT()\n const hourLabel = hourLabelProp ?? t('ui.timePicker.hourLabel', 'Hour')\n const minuteLabel = minuteLabelProp ?? t('ui.timePicker.minuteLabel', 'Minute')\n\n const { hour, minute } = parseTime(value)\n\n const emitChange = React.useCallback(\n (nextHour: number, nextMinute: number) => {\n onChange(`${padTwo(nextHour)}:${padTwo(nextMinute)}`)\n },\n [onChange]\n )\n\n const handleHourKeyDown = React.useCallback(\n (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (disabled) return\n if (e.key === 'ArrowUp') {\n e.preventDefault()\n emitChange((hour + 1) % 24, minute)\n } else if (e.key === 'ArrowDown') {\n e.preventDefault()\n emitChange((hour + 23) % 24, minute)\n }\n },\n [disabled, emitChange, hour, minute]\n )\n\n const handleMinuteKeyDown = React.useCallback(\n (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (disabled) return\n if (e.key === 'ArrowUp') {\n e.preventDefault()\n const step = minuteStep > 1 ? minuteStep : 1\n emitChange(hour, (minute + step) % 60)\n } else if (e.key === 'ArrowDown') {\n e.preventDefault()\n const step = minuteStep > 1 ? minuteStep : 1\n emitChange(hour, (minute + 60 - step) % 60)\n }\n },\n [disabled, emitChange, hour, minute, minuteStep]\n )\n\n const handleHourChange = React.useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n if (disabled) return\n const raw = parseInt(e.target.value, 10)\n if (isNaN(raw)) return\n emitChange(Math.max(0, Math.min(23, raw)), minute)\n },\n [disabled, emitChange, minute]\n )\n\n const handleMinuteChange = React.useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n if (disabled) return\n const raw = parseInt(e.target.value, 10)\n if (isNaN(raw)) return\n const snapped = minuteStep > 1 ? snapMinute(raw, minuteStep) : Math.max(0, Math.min(59, raw))\n emitChange(hour, snapped)\n },\n [disabled, emitChange, hour, minuteStep]\n )\n\n const wrapperClass = 'w-14'\n const innerInputClass =\n 'text-center tabular-nums leading-none [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:m-0 [&::-webkit-outer-spin-button]:m-0'\n\n return (\n <div className={cn('flex items-center gap-1', className)}>\n <Input\n type=\"number\"\n min={0}\n max={23}\n value={padTwo(hour)}\n onChange={handleHourChange}\n onKeyDown={handleHourKeyDown}\n disabled={disabled}\n aria-label={hourLabel}\n data-crud-focus-target=\"\"\n className={wrapperClass}\n inputClassName={innerInputClass}\n />\n <span className=\"text-sm font-medium select-none\">:</span>\n <Input\n type=\"number\"\n min={0}\n max={59}\n step={minuteStep}\n value={padTwo(minute)}\n onChange={handleMinuteChange}\n onKeyDown={handleMinuteKeyDown}\n disabled={disabled}\n aria-label={minuteLabel}\n className={wrapperClass}\n inputClassName={innerInputClass}\n />\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -4,8 +4,10 @@ import * as React from "react";
4
4
  import { ClockIcon } from "lucide-react";
5
5
  import { cn } from "@open-mercato/shared/lib/utils";
6
6
  import { useT } from "@open-mercato/shared/lib/i18n/context";
7
- import { Popover, PopoverContent, PopoverTrigger } from "../../primitives/popover.js";
8
- import { TimeInput } from "./TimeInput.js";
7
+ import {
8
+ TimePicker as TimePickerPrimitive,
9
+ formatTimePickerDisplay
10
+ } from "../../primitives/time-picker.js";
9
11
  function currentHHMM() {
10
12
  const now = /* @__PURE__ */ new Date();
11
13
  const hour = String(now.getHours()).padStart(2, "0");
@@ -19,85 +21,94 @@ function TimePicker({
19
21
  disabled = false,
20
22
  readOnly = false,
21
23
  className,
22
- minuteStep = 1,
24
+ minuteStep = 30,
23
25
  showNowButton = true,
24
- showClearButton = true
26
+ // Default changed 2026-05-11 from `true` → `false`: the new primitive's Cancel
27
+ // button already exits the popover without committing, which is what users mean
28
+ // by "Clear" in most flows. Pass `showClearButton={true}` to opt back in when
29
+ // you need an explicit "set value to null" action distinct from "dismiss".
30
+ showClearButton = false
25
31
  }) {
26
32
  const t = useT();
27
33
  const [open, setOpen] = React.useState(false);
28
34
  const placeholderText = placeholder ?? t("ui.timePicker.placeholder", "Pick a time");
29
35
  const nowText = t("ui.timePicker.nowButton", "Now");
30
36
  const clearText = t("ui.timePicker.clearButton", "Clear");
31
- const handleTimeChange = React.useCallback(
32
- (time) => {
33
- onChange(time);
34
- },
35
- [onChange]
36
- );
37
- const handleNow = React.useCallback(() => {
38
- onChange(currentHHMM());
39
- setOpen(false);
40
- }, [onChange]);
41
- const handleClear = React.useCallback(() => {
42
- onChange(null);
43
- setOpen(false);
44
- }, [onChange]);
45
37
  const isInteractive = !disabled && !readOnly;
46
- return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: isInteractive ? setOpen : void 0, children: [
47
- /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
48
- "button",
49
- {
50
- type: "button",
51
- "data-crud-focus-target": "",
52
- disabled,
53
- "aria-haspopup": "dialog",
54
- className: cn(
55
- "w-full h-9 flex items-center gap-2 rounded border px-3 text-sm text-left",
56
- "bg-background transition-colors",
57
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1",
58
- "disabled:bg-muted disabled:text-muted-foreground disabled:cursor-not-allowed",
59
- readOnly && "cursor-default opacity-70",
60
- !value && "text-muted-foreground",
61
- className
62
- ),
63
- onClick: isInteractive ? void 0 : (e) => e.preventDefault(),
64
- children: [
65
- /* @__PURE__ */ jsx(ClockIcon, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
66
- /* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: value ?? placeholderText })
67
- ]
68
- }
69
- ) }),
70
- /* @__PURE__ */ jsxs(PopoverContent, { className: "p-3 w-auto min-w-[180px]", children: [
71
- /* @__PURE__ */ jsx(
72
- TimeInput,
73
- {
74
- value,
75
- onChange: handleTimeChange,
76
- minuteStep
77
- }
38
+ const displayValue = value ? (() => {
39
+ const { main, suffix } = formatTimePickerDisplay(value, "12h");
40
+ return suffix ? `${main} ${suffix}` : main;
41
+ })() : null;
42
+ const triggerButton = /* @__PURE__ */ jsxs(
43
+ "button",
44
+ {
45
+ type: "button",
46
+ "data-crud-focus-target": "",
47
+ disabled,
48
+ "aria-haspopup": "dialog",
49
+ className: cn(
50
+ "w-full h-10 flex items-center gap-2 rounded-md border bg-background px-3 text-sm text-left transition-colors",
51
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1",
52
+ "disabled:bg-muted disabled:text-muted-foreground disabled:cursor-not-allowed",
53
+ readOnly && "cursor-default opacity-70",
54
+ !value && "text-muted-foreground",
55
+ className
78
56
  ),
79
- (showNowButton || showClearButton) && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 mt-3 pt-2 border-t", children: [
80
- showNowButton && /* @__PURE__ */ jsx(
81
- "button",
82
- {
83
- type: "button",
84
- onClick: handleNow,
85
- className: "text-sm text-primary hover:underline focus-visible:outline-none",
86
- children: nowText
87
- }
88
- ),
89
- showClearButton && /* @__PURE__ */ jsx(
90
- "button",
91
- {
92
- type: "button",
93
- onClick: handleClear,
94
- className: "text-sm text-muted-foreground hover:text-foreground hover:underline focus-visible:outline-none ml-auto",
95
- children: clearText
96
- }
97
- )
98
- ] })
99
- ] })
100
- ] });
57
+ onClick: !isInteractive ? (event) => event.preventDefault() : void 0,
58
+ children: [
59
+ /* @__PURE__ */ jsx(ClockIcon, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
60
+ /* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: displayValue ?? placeholderText })
61
+ ]
62
+ }
63
+ );
64
+ if (!isInteractive) {
65
+ return triggerButton;
66
+ }
67
+ const pinnedTopActions = [];
68
+ if (showNowButton) {
69
+ const nowValue = currentHHMM();
70
+ const { main: nowMain, suffix: nowSuffix } = formatTimePickerDisplay(nowValue, "12h");
71
+ pinnedTopActions.push({
72
+ label: nowText,
73
+ icon: /* @__PURE__ */ jsx(ClockIcon, { "aria-hidden": "true" }),
74
+ rightText: nowSuffix ? `${nowMain} ${nowSuffix}` : nowMain,
75
+ onClick: () => {
76
+ onChange(nowValue);
77
+ setOpen(false);
78
+ }
79
+ });
80
+ }
81
+ const legacyFooterActions = [];
82
+ if (showClearButton) {
83
+ legacyFooterActions.push({
84
+ label: clearText,
85
+ onClick: () => {
86
+ onChange(null);
87
+ setOpen(false);
88
+ },
89
+ variant: "muted"
90
+ });
91
+ }
92
+ return /* @__PURE__ */ jsx(
93
+ TimePickerPrimitive,
94
+ {
95
+ value: value ?? null,
96
+ onChange: (next) => onChange(next),
97
+ onApply: (next) => {
98
+ onChange(next);
99
+ },
100
+ intervalMinutes: Math.max(1, minuteStep),
101
+ showHeader: false,
102
+ showFooter: true,
103
+ headerPlaceholder: placeholderText,
104
+ pinnedTopActions: pinnedTopActions.length > 0 ? pinnedTopActions : void 0,
105
+ legacyFooterActions: legacyFooterActions.length > 0 ? legacyFooterActions : void 0,
106
+ trigger: triggerButton,
107
+ open,
108
+ onOpenChange: setOpen,
109
+ disabled
110
+ }
111
+ );
101
112
  }
102
113
  export {
103
114
  TimePicker
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/inputs/TimePicker.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { ClockIcon } from 'lucide-react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Popover, PopoverContent, PopoverTrigger } from '../../primitives/popover'\nimport { TimeInput } from './TimeInput'\n\nexport type TimePickerProps = {\n value?: string | null\n onChange: (time: string | null) => void\n placeholder?: string\n disabled?: boolean\n readOnly?: boolean\n className?: string\n minuteStep?: number\n showNowButton?: boolean\n showClearButton?: boolean\n}\n\nfunction currentHHMM(): string {\n const now = new Date()\n const hour = String(now.getHours()).padStart(2, '0')\n const minute = String(now.getMinutes()).padStart(2, '0')\n return `${hour}:${minute}`\n}\n\nexport function TimePicker({\n value,\n onChange,\n placeholder,\n disabled = false,\n readOnly = false,\n className,\n minuteStep = 1,\n showNowButton = true,\n showClearButton = true,\n}: TimePickerProps) {\n const t = useT()\n const [open, setOpen] = React.useState(false)\n\n const placeholderText = placeholder ?? t('ui.timePicker.placeholder', 'Pick a time')\n const nowText = t('ui.timePicker.nowButton', 'Now')\n const clearText = t('ui.timePicker.clearButton', 'Clear')\n\n const handleTimeChange = React.useCallback(\n (time: string) => {\n onChange(time)\n },\n [onChange]\n )\n\n const handleNow = React.useCallback(() => {\n onChange(currentHHMM())\n setOpen(false)\n }, [onChange])\n\n const handleClear = React.useCallback(() => {\n onChange(null)\n setOpen(false)\n }, [onChange])\n\n const isInteractive = !disabled && !readOnly\n\n return (\n <Popover open={open} onOpenChange={isInteractive ? setOpen : undefined}>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n data-crud-focus-target=\"\"\n disabled={disabled}\n aria-haspopup=\"dialog\"\n className={cn(\n 'w-full h-9 flex items-center gap-2 rounded border px-3 text-sm text-left',\n 'bg-background transition-colors',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1',\n 'disabled:bg-muted disabled:text-muted-foreground disabled:cursor-not-allowed',\n readOnly && 'cursor-default opacity-70',\n !value && 'text-muted-foreground',\n className\n )}\n onClick={isInteractive ? undefined : (e) => e.preventDefault()}\n >\n <ClockIcon className=\"h-4 w-4 shrink-0 text-muted-foreground\" />\n <span className=\"flex-1 truncate\">{value ?? placeholderText}</span>\n </button>\n </PopoverTrigger>\n <PopoverContent className=\"p-3 w-auto min-w-[180px]\">\n <TimeInput\n value={value}\n onChange={handleTimeChange}\n minuteStep={minuteStep}\n />\n {(showNowButton || showClearButton) && (\n <div className=\"flex items-center justify-between gap-2 mt-3 pt-2 border-t\">\n {showNowButton && (\n <button\n type=\"button\"\n onClick={handleNow}\n className=\"text-sm text-primary hover:underline focus-visible:outline-none\"\n >\n {nowText}\n </button>\n )}\n {showClearButton && (\n <button\n type=\"button\"\n onClick={handleClear}\n className=\"text-sm text-muted-foreground hover:text-foreground hover:underline focus-visible:outline-none ml-auto\"\n >\n {clearText}\n </button>\n )}\n </div>\n )}\n </PopoverContent>\n </Popover>\n )\n}\n"],
5
- "mappings": ";AAoEQ,SAgBE,KAhBF;AAlER,YAAY,WAAW;AACvB,SAAS,iBAAiB;AAC1B,SAAS,UAAU;AACnB,SAAS,YAAY;AACrB,SAAS,SAAS,gBAAgB,sBAAsB;AACxD,SAAS,iBAAiB;AAc1B,SAAS,cAAsB;AAC7B,QAAM,MAAM,oBAAI,KAAK;AACrB,QAAM,OAAO,OAAO,IAAI,SAAS,CAAC,EAAE,SAAS,GAAG,GAAG;AACnD,QAAM,SAAS,OAAO,IAAI,WAAW,CAAC,EAAE,SAAS,GAAG,GAAG;AACvD,SAAO,GAAG,IAAI,IAAI,MAAM;AAC1B;AAEO,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,WAAW;AAAA,EACX;AAAA,EACA,aAAa;AAAA,EACb,gBAAgB;AAAA,EAChB,kBAAkB;AACpB,GAAoB;AAClB,QAAM,IAAI,KAAK;AACf,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAS,KAAK;AAE5C,QAAM,kBAAkB,eAAe,EAAE,6BAA6B,aAAa;AACnF,QAAM,UAAU,EAAE,2BAA2B,KAAK;AAClD,QAAM,YAAY,EAAE,6BAA6B,OAAO;AAExD,QAAM,mBAAmB,MAAM;AAAA,IAC7B,CAAC,SAAiB;AAChB,eAAS,IAAI;AAAA,IACf;AAAA,IACA,CAAC,QAAQ;AAAA,EACX;AAEA,QAAM,YAAY,MAAM,YAAY,MAAM;AACxC,aAAS,YAAY,CAAC;AACtB,YAAQ,KAAK;AAAA,EACf,GAAG,CAAC,QAAQ,CAAC;AAEb,QAAM,cAAc,MAAM,YAAY,MAAM;AAC1C,aAAS,IAAI;AACb,YAAQ,KAAK;AAAA,EACf,GAAG,CAAC,QAAQ,CAAC;AAEb,QAAM,gBAAgB,CAAC,YAAY,CAAC;AAEpC,SACE,qBAAC,WAAQ,MAAY,cAAc,gBAAgB,UAAU,QAC3D;AAAA,wBAAC,kBAAe,SAAO,MACrB;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,0BAAuB;AAAA,QACvB;AAAA,QACA,iBAAc;AAAA,QACd,WAAW;AAAA,UACT;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA,YAAY;AAAA,UACZ,CAAC,SAAS;AAAA,UACV;AAAA,QACF;AAAA,QACA,SAAS,gBAAgB,SAAY,CAAC,MAAM,EAAE,eAAe;AAAA,QAE7D;AAAA,8BAAC,aAAU,WAAU,0CAAyC;AAAA,UAC9D,oBAAC,UAAK,WAAU,mBAAmB,mBAAS,iBAAgB;AAAA;AAAA;AAAA,IAC9D,GACF;AAAA,IACA,qBAAC,kBAAe,WAAU,4BACxB;AAAA;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA,UAAU;AAAA,UACV;AAAA;AAAA,MACF;AAAA,OACE,iBAAiB,oBACjB,qBAAC,SAAI,WAAU,8DACZ;AAAA,yBACC;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS;AAAA,YACT,WAAU;AAAA,YAET;AAAA;AAAA,QACH;AAAA,QAED,mBACC;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS;AAAA,YACT,WAAU;AAAA,YAET;AAAA;AAAA,QACH;AAAA,SAEJ;AAAA,OAEJ;AAAA,KACF;AAEJ;",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { ClockIcon } from 'lucide-react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport {\n TimePicker as TimePickerPrimitive,\n formatTimePickerDisplay,\n} from '../../primitives/time-picker'\n\nexport type TimePickerProps = {\n value?: string | null\n onChange: (time: string | null) => void\n placeholder?: string\n disabled?: boolean\n readOnly?: boolean\n className?: string\n minuteStep?: number\n showNowButton?: boolean\n showClearButton?: boolean\n}\n\nfunction currentHHMM(): string {\n const now = new Date()\n const hour = String(now.getHours()).padStart(2, '0')\n const minute = String(now.getMinutes()).padStart(2, '0')\n return `${hour}:${minute}`\n}\n\n/**\n * Legacy popover-anchored time picker. Preserved for backward compatibility with\n * existing CrudForm `type: 'time'` consumers and DateTimePicker. Internally a shim\n * over the new `<TimePicker>` primitive from `@open-mercato/ui/primitives/time-picker`.\n *\n * `minuteStep` maps to `intervalMinutes` (default 30 for sane slot list size).\n * `showNowButton` / `showClearButton` render as `legacyFooterActions`.\n */\nexport function TimePicker({\n value,\n onChange,\n placeholder,\n disabled = false,\n readOnly = false,\n className,\n minuteStep = 30,\n showNowButton = true,\n // Default changed 2026-05-11 from `true` \u2192 `false`: the new primitive's Cancel\n // button already exits the popover without committing, which is what users mean\n // by \"Clear\" in most flows. Pass `showClearButton={true}` to opt back in when\n // you need an explicit \"set value to null\" action distinct from \"dismiss\".\n showClearButton = false,\n}: TimePickerProps) {\n const t = useT()\n const [open, setOpen] = React.useState(false)\n\n const placeholderText = placeholder ?? t('ui.timePicker.placeholder', 'Pick a time')\n const nowText = t('ui.timePicker.nowButton', 'Now')\n const clearText = t('ui.timePicker.clearButton', 'Clear')\n const isInteractive = !disabled && !readOnly\n\n // Render the trigger label in the same 12h \"HH:MM AM/PM\" format as the slot list\n // inside the popover so users see one consistent representation.\n const displayValue = value\n ? (() => {\n const { main, suffix } = formatTimePickerDisplay(value, '12h')\n return suffix ? `${main} ${suffix}` : main\n })()\n : null\n\n const triggerButton = (\n <button\n type=\"button\"\n data-crud-focus-target=\"\"\n disabled={disabled}\n aria-haspopup=\"dialog\"\n className={cn(\n 'w-full h-10 flex items-center gap-2 rounded-md border bg-background px-3 text-sm text-left transition-colors',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1',\n 'disabled:bg-muted disabled:text-muted-foreground disabled:cursor-not-allowed',\n readOnly && 'cursor-default opacity-70',\n !value && 'text-muted-foreground',\n className,\n )}\n onClick={!isInteractive ? (event) => event.preventDefault() : undefined}\n >\n <ClockIcon className=\"h-4 w-4 shrink-0 text-muted-foreground\" />\n <span className=\"flex-1 truncate\">{displayValue ?? placeholderText}</span>\n </button>\n )\n\n if (!isInteractive) {\n return triggerButton\n }\n\n const pinnedTopActions: Array<{\n label: string\n onClick: () => void\n icon?: React.ReactNode\n rightText?: string\n }> = []\n if (showNowButton) {\n const nowValue = currentHHMM()\n const { main: nowMain, suffix: nowSuffix } = formatTimePickerDisplay(nowValue, '12h')\n pinnedTopActions.push({\n label: nowText,\n icon: <ClockIcon aria-hidden=\"true\" />,\n rightText: nowSuffix ? `${nowMain} ${nowSuffix}` : nowMain,\n onClick: () => {\n onChange(nowValue)\n setOpen(false)\n },\n })\n }\n\n const legacyFooterActions: Array<{ label: string; onClick: () => void; variant: 'link' | 'muted' }> = []\n if (showClearButton) {\n legacyFooterActions.push({\n label: clearText,\n onClick: () => {\n onChange(null)\n setOpen(false)\n },\n variant: 'muted',\n })\n }\n\n return (\n <TimePickerPrimitive\n value={value ?? null}\n onChange={(next) => onChange(next)}\n onApply={(next) => {\n onChange(next)\n }}\n intervalMinutes={Math.max(1, minuteStep)}\n // Trigger button already displays current value \u2014 don't duplicate it\n // inside the popover header.\n showHeader={false}\n showFooter\n headerPlaceholder={placeholderText}\n pinnedTopActions={pinnedTopActions.length > 0 ? pinnedTopActions : undefined}\n legacyFooterActions={legacyFooterActions.length > 0 ? legacyFooterActions : undefined}\n trigger={triggerButton}\n open={open}\n onOpenChange={setOpen}\n disabled={disabled}\n />\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -66,7 +66,7 @@ function buildFormFieldFromCustomFieldDef(def, opts) {
66
66
  case "datetime":
67
67
  return { id, label, type: "datetime", ...baseProps };
68
68
  case "multiline": {
69
- let editor = "uiw";
69
+ let editor = "html";
70
70
  if (def.editor === "simpleMarkdown") editor = "simple";
71
71
  else if (def.editor === "htmlRichText") editor = "html";
72
72
  return { id, label, type: "richtext", ...baseProps, editor };
@@ -113,7 +113,7 @@ function buildFormFieldFromCustomFieldDef(def, opts) {
113
113
  return base;
114
114
  }
115
115
  if (def.kind === "text" && typeof def.editor === "string" && def.editor) {
116
- let editor = "uiw";
116
+ let editor = "html";
117
117
  if (def.editor === "simpleMarkdown") editor = "simple";
118
118
  else if (def.editor === "htmlRichText") editor = "html";
119
119
  return { id, label, type: "richtext", ...baseProps, editor };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/utils/customFieldForms.ts"],
4
- "sourcesContent": ["import type { CrudField } from '../CrudForm'\nimport type {\n CustomFieldDefDto,\n CustomFieldDefinitionsPayload,\n} from './customFieldDefs'\nimport {\n filterCustomFieldDefs,\n fetchCustomFieldDefs,\n fetchCustomFieldDefinitionsPayload,\n} from './customFieldDefs'\nimport { FieldRegistry, loadGeneratedFieldRegistrations } from '../fields/registry'\nimport { apiCall } from './apiCall'\nimport { normalizeCustomFieldOptions } from '@open-mercato/shared/modules/entities/options'\nimport { CURRENCY_OPTIONS_URL } from '@open-mercato/shared/modules/entities/kinds'\n\nlet registryReady: Promise<void> | null = null\n\nasync function ensureFieldRegistryReady() {\n if (!registryReady) {\n registryReady = loadGeneratedFieldRegistrations().catch((err) => {\n registryReady = null\n throw err\n })\n }\n await registryReady\n}\n\nfunction buildOptionsUrl(base: string, query?: string): string {\n if (!query) return base\n try {\n const isAbsolute = /^([a-z][a-z\\d+\\-.]*:)?\\/\\//i.test(base)\n const origin = typeof window !== 'undefined' ? window.location.origin : 'http://localhost'\n const url = isAbsolute ? new URL(base) : new URL(base, origin)\n if (!url.searchParams.has('query')) url.searchParams.append('query', query)\n if (!url.searchParams.has('q')) url.searchParams.append('q', query)\n if (isAbsolute) return url.toString()\n return `${url.pathname}${url.search}`\n } catch {\n const sep = base.includes('?') ? '&' : '?'\n if (base.includes('query=')) return `${base}${sep}q=${encodeURIComponent(query)}`\n return `${base}${sep}query=${encodeURIComponent(query)}`\n }\n}\n\ntype OptionsResponse = { items?: unknown[] }\n\nasync function loadRemoteOptions(url: string): Promise<Array<{ value: string; label: string }>> {\n try {\n const call = await apiCall<OptionsResponse>(url, undefined, { fallback: { items: [] } })\n if (!call.ok) return []\n const payload = call.result ?? { items: [] }\n const items = Array.isArray(payload?.items) ? payload.items : []\n return items.map((it: any) => ({\n value: String(it?.value ?? it),\n label: String(it?.label ?? it?.value ?? it),\n }))\n } catch {\n return []\n }\n}\n\nexport function buildFormFieldFromCustomFieldDef(\n def: CustomFieldDefDto,\n opts?: { bareIds?: boolean }\n): CrudField | null {\n const id = opts?.bareIds ? def.key : `cf_${def.key}`\n const label = def.label || def.key\n const required = Array.isArray((def as any).validation)\n ? ((def as any).validation as any[]).some((rule) => rule && rule.rule === 'required')\n : false\n const baseProps = {\n description: def.description,\n required,\n ...(def.defaultValue !== undefined && def.defaultValue !== null ? { defaultValue: def.defaultValue } : {}),\n }\n\n switch (def.kind) {\n case 'boolean':\n return { id, label, type: 'checkbox', ...baseProps }\n case 'integer':\n case 'float':\n return { id, label, type: 'number', ...baseProps }\n case 'date':\n return { id, label, type: 'datepicker', ...baseProps }\n case 'datetime':\n return { id, label, type: 'datetime', ...baseProps }\n case 'multiline': {\n let editor: 'simple' | 'uiw' | 'html' = 'uiw'\n if (def.editor === 'simpleMarkdown') editor = 'simple'\n else if (def.editor === 'htmlRichText') editor = 'html'\n return { id, label, type: 'richtext', ...baseProps, editor }\n }\n case 'select':\n case 'currency':\n case 'relation':\n return {\n id,\n label,\n type: 'select',\n ...baseProps,\n options: normalizeCustomFieldOptions(def.options || []).map((option) => ({\n value: option.value,\n label: option.label,\n })),\n multiple: !!def.multi,\n ...(def.kind === 'currency'\n ? {\n loadOptions: async (query?: string) => {\n const url = buildOptionsUrl(CURRENCY_OPTIONS_URL, query)\n return loadRemoteOptions(url)\n },\n }\n : def.optionsUrl\n ? {\n loadOptions: async (query?: string) => {\n const url = buildOptionsUrl(def.optionsUrl!, query)\n return loadRemoteOptions(url)\n },\n }\n : {}),\n ...(def.multi && def.input === 'listbox' ? ({ listbox: true } as any) : {}),\n }\n default: {\n if (def.kind === 'text' && def.multi) {\n const base: any = { id, label, type: 'tags', ...baseProps }\n const resolvedOptions = normalizeCustomFieldOptions(def.options || [])\n if (resolvedOptions.length > 0) {\n base.options = resolvedOptions.map((option) => ({ value: option.value, label: option.label }))\n }\n if (def.optionsUrl) {\n base.loadOptions = async (query?: string) => {\n const url = buildOptionsUrl(def.optionsUrl!, query)\n return loadRemoteOptions(url)\n }\n }\n return base\n }\n if (def.kind === 'text' && typeof def.editor === 'string' && def.editor) {\n let editor: 'simple' | 'uiw' | 'html' = 'uiw'\n if (def.editor === 'simpleMarkdown') editor = 'simple'\n else if (def.editor === 'htmlRichText') editor = 'html'\n return { id, label, type: 'richtext', ...baseProps, editor }\n }\n const input = FieldRegistry.getInput(def.kind)\n if (input) {\n return {\n id,\n label,\n type: 'custom',\n ...baseProps,\n component: (props) => input({ ...props, def }),\n }\n }\n return { id, label, type: 'text', ...baseProps }\n }\n }\n}\n\nexport function buildFormFieldsFromCustomFields(\n defs: CustomFieldDefDto[],\n opts?: { bareIds?: boolean }\n): CrudField[] {\n const fields: CrudField[] = []\n const visible = filterCustomFieldDefs(defs, 'form')\n const seenKeys = new Set<string>()\n for (const def of visible) {\n const keyLower = String(def.key).toLowerCase()\n if (seenKeys.has(keyLower)) continue\n seenKeys.add(keyLower)\n const field = buildFormFieldFromCustomFieldDef(def, opts)\n if (field) fields.push(field)\n }\n return fields\n}\n\nexport async function fetchCustomFieldFormStructure(\n entityIds: string | string[],\n fetchImpl?: typeof fetch,\n options?: { bareIds?: boolean },\n): Promise<{ fields: CrudField[]; definitions: CustomFieldDefDto[]; metadata: CustomFieldDefinitionsPayload }> {\n await ensureFieldRegistryReady()\n const metadata = await fetchCustomFieldDefinitionsPayload(entityIds, fetchImpl)\n const definitions = Array.isArray(metadata.items) ? metadata.items : []\n const fields = buildFormFieldsFromCustomFields(definitions, options)\n return { fields, definitions, metadata }\n}\n\nexport async function fetchCustomFieldFormFields(\n entityIds: string | string[],\n fetchImpl?: typeof fetch,\n options?: { bareIds?: boolean },\n): Promise<CrudField[]> {\n const { fields } = await fetchCustomFieldFormStructure(entityIds, fetchImpl, options)\n return fields\n}\n\nexport async function fetchCustomFieldFormFieldsWithDefinitions(\n entityIds: string | string[],\n fetchImpl?: typeof fetch,\n options?: { bareIds?: boolean },\n): Promise<{ fields: CrudField[]; definitions: CustomFieldDefDto[]; metadata: CustomFieldDefinitionsPayload }> {\n return fetchCustomFieldFormStructure(entityIds, fetchImpl, options)\n}\n"],
4
+ "sourcesContent": ["import type { CrudField } from '../CrudForm'\nimport type {\n CustomFieldDefDto,\n CustomFieldDefinitionsPayload,\n} from './customFieldDefs'\nimport {\n filterCustomFieldDefs,\n fetchCustomFieldDefs,\n fetchCustomFieldDefinitionsPayload,\n} from './customFieldDefs'\nimport { FieldRegistry, loadGeneratedFieldRegistrations } from '../fields/registry'\nimport { apiCall } from './apiCall'\nimport { normalizeCustomFieldOptions } from '@open-mercato/shared/modules/entities/options'\nimport { CURRENCY_OPTIONS_URL } from '@open-mercato/shared/modules/entities/kinds'\n\nlet registryReady: Promise<void> | null = null\n\nasync function ensureFieldRegistryReady() {\n if (!registryReady) {\n registryReady = loadGeneratedFieldRegistrations().catch((err) => {\n registryReady = null\n throw err\n })\n }\n await registryReady\n}\n\nfunction buildOptionsUrl(base: string, query?: string): string {\n if (!query) return base\n try {\n const isAbsolute = /^([a-z][a-z\\d+\\-.]*:)?\\/\\//i.test(base)\n const origin = typeof window !== 'undefined' ? window.location.origin : 'http://localhost'\n const url = isAbsolute ? new URL(base) : new URL(base, origin)\n if (!url.searchParams.has('query')) url.searchParams.append('query', query)\n if (!url.searchParams.has('q')) url.searchParams.append('q', query)\n if (isAbsolute) return url.toString()\n return `${url.pathname}${url.search}`\n } catch {\n const sep = base.includes('?') ? '&' : '?'\n if (base.includes('query=')) return `${base}${sep}q=${encodeURIComponent(query)}`\n return `${base}${sep}query=${encodeURIComponent(query)}`\n }\n}\n\ntype OptionsResponse = { items?: unknown[] }\n\nasync function loadRemoteOptions(url: string): Promise<Array<{ value: string; label: string }>> {\n try {\n const call = await apiCall<OptionsResponse>(url, undefined, { fallback: { items: [] } })\n if (!call.ok) return []\n const payload = call.result ?? { items: [] }\n const items = Array.isArray(payload?.items) ? payload.items : []\n return items.map((it: any) => ({\n value: String(it?.value ?? it),\n label: String(it?.label ?? it?.value ?? it),\n }))\n } catch {\n return []\n }\n}\n\nexport function buildFormFieldFromCustomFieldDef(\n def: CustomFieldDefDto,\n opts?: { bareIds?: boolean }\n): CrudField | null {\n const id = opts?.bareIds ? def.key : `cf_${def.key}`\n const label = def.label || def.key\n const required = Array.isArray((def as any).validation)\n ? ((def as any).validation as any[]).some((rule) => rule && rule.rule === 'required')\n : false\n const baseProps = {\n description: def.description,\n required,\n ...(def.defaultValue !== undefined && def.defaultValue !== null ? { defaultValue: def.defaultValue } : {}),\n }\n\n switch (def.kind) {\n case 'boolean':\n return { id, label, type: 'checkbox', ...baseProps }\n case 'integer':\n case 'float':\n return { id, label, type: 'number', ...baseProps }\n case 'date':\n return { id, label, type: 'datepicker', ...baseProps }\n case 'datetime':\n return { id, label, type: 'datetime', ...baseProps }\n case 'multiline': {\n let editor: 'simple' | 'uiw' | 'html' = 'html'\n if (def.editor === 'simpleMarkdown') editor = 'simple'\n else if (def.editor === 'htmlRichText') editor = 'html'\n return { id, label, type: 'richtext', ...baseProps, editor }\n }\n case 'select':\n case 'currency':\n case 'relation':\n return {\n id,\n label,\n type: 'select',\n ...baseProps,\n options: normalizeCustomFieldOptions(def.options || []).map((option) => ({\n value: option.value,\n label: option.label,\n })),\n multiple: !!def.multi,\n ...(def.kind === 'currency'\n ? {\n loadOptions: async (query?: string) => {\n const url = buildOptionsUrl(CURRENCY_OPTIONS_URL, query)\n return loadRemoteOptions(url)\n },\n }\n : def.optionsUrl\n ? {\n loadOptions: async (query?: string) => {\n const url = buildOptionsUrl(def.optionsUrl!, query)\n return loadRemoteOptions(url)\n },\n }\n : {}),\n ...(def.multi && def.input === 'listbox' ? ({ listbox: true } as any) : {}),\n }\n default: {\n if (def.kind === 'text' && def.multi) {\n const base: any = { id, label, type: 'tags', ...baseProps }\n const resolvedOptions = normalizeCustomFieldOptions(def.options || [])\n if (resolvedOptions.length > 0) {\n base.options = resolvedOptions.map((option) => ({ value: option.value, label: option.label }))\n }\n if (def.optionsUrl) {\n base.loadOptions = async (query?: string) => {\n const url = buildOptionsUrl(def.optionsUrl!, query)\n return loadRemoteOptions(url)\n }\n }\n return base\n }\n if (def.kind === 'text' && typeof def.editor === 'string' && def.editor) {\n let editor: 'simple' | 'uiw' | 'html' = 'html'\n if (def.editor === 'simpleMarkdown') editor = 'simple'\n else if (def.editor === 'htmlRichText') editor = 'html'\n return { id, label, type: 'richtext', ...baseProps, editor }\n }\n const input = FieldRegistry.getInput(def.kind)\n if (input) {\n return {\n id,\n label,\n type: 'custom',\n ...baseProps,\n component: (props) => input({ ...props, def }),\n }\n }\n return { id, label, type: 'text', ...baseProps }\n }\n }\n}\n\nexport function buildFormFieldsFromCustomFields(\n defs: CustomFieldDefDto[],\n opts?: { bareIds?: boolean }\n): CrudField[] {\n const fields: CrudField[] = []\n const visible = filterCustomFieldDefs(defs, 'form')\n const seenKeys = new Set<string>()\n for (const def of visible) {\n const keyLower = String(def.key).toLowerCase()\n if (seenKeys.has(keyLower)) continue\n seenKeys.add(keyLower)\n const field = buildFormFieldFromCustomFieldDef(def, opts)\n if (field) fields.push(field)\n }\n return fields\n}\n\nexport async function fetchCustomFieldFormStructure(\n entityIds: string | string[],\n fetchImpl?: typeof fetch,\n options?: { bareIds?: boolean },\n): Promise<{ fields: CrudField[]; definitions: CustomFieldDefDto[]; metadata: CustomFieldDefinitionsPayload }> {\n await ensureFieldRegistryReady()\n const metadata = await fetchCustomFieldDefinitionsPayload(entityIds, fetchImpl)\n const definitions = Array.isArray(metadata.items) ? metadata.items : []\n const fields = buildFormFieldsFromCustomFields(definitions, options)\n return { fields, definitions, metadata }\n}\n\nexport async function fetchCustomFieldFormFields(\n entityIds: string | string[],\n fetchImpl?: typeof fetch,\n options?: { bareIds?: boolean },\n): Promise<CrudField[]> {\n const { fields } = await fetchCustomFieldFormStructure(entityIds, fetchImpl, options)\n return fields\n}\n\nexport async function fetchCustomFieldFormFieldsWithDefinitions(\n entityIds: string | string[],\n fetchImpl?: typeof fetch,\n options?: { bareIds?: boolean },\n): Promise<{ fields: CrudField[]; definitions: CustomFieldDefDto[]; metadata: CustomFieldDefinitionsPayload }> {\n return fetchCustomFieldFormStructure(entityIds, fetchImpl, options)\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,14 +1,18 @@
1
1
  "use client";
2
- import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { EmptyState } from "../../primitives/empty-state.js";
3
4
  function PortalEmptyState({ icon, title, description, action }) {
4
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center gap-3 rounded-xl border border-dashed py-12 text-center", children: [
5
- icon ? /* @__PURE__ */ jsx("div", { className: "flex size-10 items-center justify-center rounded-lg bg-muted text-muted-foreground", children: icon }) : null,
6
- /* @__PURE__ */ jsxs("div", { children: [
7
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-foreground", children: title }),
8
- description ? /* @__PURE__ */ jsx("p", { className: "mt-0.5 max-w-sm text-sm text-muted-foreground", children: description }) : null
9
- ] }),
10
- action ?? null
11
- ] });
5
+ return /* @__PURE__ */ jsx(
6
+ EmptyState,
7
+ {
8
+ variant: "subtle",
9
+ size: "lg",
10
+ icon,
11
+ title,
12
+ description,
13
+ actions: action
14
+ }
15
+ );
12
16
  }
13
17
  export {
14
18
  PortalEmptyState
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/portal/components/PortalEmptyState.tsx"],
4
- "sourcesContent": ["\"use client\"\nimport type { ReactNode } from 'react'\n\ntype PortalEmptyStateProps = {\n icon?: ReactNode\n title: string\n description?: string\n action?: ReactNode\n}\n\n/**\n * Empty state placeholder for portal sections with no content.\n * Dashed border, centered layout, optional icon and CTA.\n */\nexport function PortalEmptyState({ icon, title, description, action }: PortalEmptyStateProps) {\n return (\n <div className=\"flex flex-col items-center justify-center gap-3 rounded-xl border border-dashed py-12 text-center\">\n {icon ? (\n <div className=\"flex size-10 items-center justify-center rounded-lg bg-muted text-muted-foreground\">\n {icon}\n </div>\n ) : null}\n <div>\n <p className=\"text-sm font-medium text-foreground\">{title}</p>\n {description ? (\n <p className=\"mt-0.5 max-w-sm text-sm text-muted-foreground\">{description}</p>\n ) : null}\n </div>\n {action ?? null}\n </div>\n )\n}\n"],
5
- "mappings": ";AAkBQ,cAIF,YAJE;AAJD,SAAS,iBAAiB,EAAE,MAAM,OAAO,aAAa,OAAO,GAA0B;AAC5F,SACE,qBAAC,SAAI,WAAU,qGACZ;AAAA,WACC,oBAAC,SAAI,WAAU,sFACZ,gBACH,IACE;AAAA,IACJ,qBAAC,SACC;AAAA,0BAAC,OAAE,WAAU,uCAAuC,iBAAM;AAAA,MACzD,cACC,oBAAC,OAAE,WAAU,iDAAiD,uBAAY,IACxE;AAAA,OACN;AAAA,IACC,UAAU;AAAA,KACb;AAEJ;",
4
+ "sourcesContent": ["/**\n * @deprecated Import `EmptyState` from `@open-mercato/ui/primitives/empty-state`\n * directly with `variant=\"subtle\"` and `size=\"lg\"` (matches the previous\n * portal layout). The standalone `PortalEmptyState` is replaced by the\n * unified primitive in DS Foundation v3.\n *\n * This shim preserves the legacy import path\n * `@open-mercato/ui/portal/components/PortalEmptyState` for existing\n * portal consumers \u2014 it forwards to the primitive with the portal-style\n * variant + size pre-set.\n */\n\n\"use client\"\n\nimport * as React from 'react'\nimport { EmptyState } from '../../primitives/empty-state'\n\nexport type PortalEmptyStateProps = {\n icon?: React.ReactNode\n title: string\n description?: string\n action?: React.ReactNode\n}\n\n/** @deprecated Use `EmptyState` from `@open-mercato/ui/primitives/empty-state` with `variant=\"subtle\"` and `size=\"lg\"`. */\nexport function PortalEmptyState({ icon, title, description, action }: PortalEmptyStateProps) {\n return (\n <EmptyState\n variant=\"subtle\"\n size=\"lg\"\n icon={icon}\n title={title}\n description={description}\n actions={action}\n />\n )\n}\n"],
5
+ "mappings": ";AA2BI;AAZJ,SAAS,kBAAkB;AAUpB,SAAS,iBAAiB,EAAE,MAAM,OAAO,aAAa,OAAO,GAA0B;AAC5F,SACE;AAAA,IAAC;AAAA;AAAA,MACC,SAAQ;AAAA,MACR,MAAK;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA,SAAS;AAAA;AAAA,EACX;AAEJ;",
6
6
  "names": []
7
7
  }