@open-mercato/ui 0.5.1-develop.2856.35de414092 → 0.5.1-develop.2874.77704bccbd

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 (246) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +204 -121
  3. package/dist/backend/AppShell.js +25 -28
  4. package/dist/backend/AppShell.js.map +2 -2
  5. package/dist/backend/ContextHelp.js +1 -1
  6. package/dist/backend/ContextHelp.js.map +1 -1
  7. package/dist/backend/CrudForm.js +12 -15
  8. package/dist/backend/CrudForm.js.map +2 -2
  9. package/dist/backend/DataTable.js +9 -10
  10. package/dist/backend/DataTable.js.map +2 -2
  11. package/dist/backend/FilterBar.js +6 -8
  12. package/dist/backend/FilterBar.js.map +2 -2
  13. package/dist/backend/FilterOverlay.js +10 -10
  14. package/dist/backend/FilterOverlay.js.map +2 -2
  15. package/dist/backend/FlashMessages.js +1 -1
  16. package/dist/backend/FlashMessages.js.map +2 -2
  17. package/dist/backend/JsonBuilder.js +6 -6
  18. package/dist/backend/JsonBuilder.js.map +1 -1
  19. package/dist/backend/NextStepCallout.js +1 -1
  20. package/dist/backend/NextStepCallout.js.map +1 -1
  21. package/dist/backend/PerspectiveSidebar.js +2 -2
  22. package/dist/backend/PerspectiveSidebar.js.map +2 -2
  23. package/dist/backend/ProfileDropdown.js +1 -1
  24. package/dist/backend/ProfileDropdown.js.map +1 -1
  25. package/dist/backend/RowActions.js +1 -1
  26. package/dist/backend/RowActions.js.map +1 -1
  27. package/dist/backend/UserMenu.js +2 -2
  28. package/dist/backend/UserMenu.js.map +1 -1
  29. package/dist/backend/WebhookSetupGuide.js +11 -11
  30. package/dist/backend/WebhookSetupGuide.js.map +2 -2
  31. package/dist/backend/charts/KpiCard.js +3 -3
  32. package/dist/backend/charts/KpiCard.js.map +1 -1
  33. package/dist/backend/columns/ColumnChooserPanel.js +1 -1
  34. package/dist/backend/columns/ColumnChooserPanel.js.map +2 -2
  35. package/dist/backend/custom-fields/FieldDefinitionsEditor.js +3 -3
  36. package/dist/backend/custom-fields/FieldDefinitionsEditor.js.map +2 -2
  37. package/dist/backend/dashboard/DashboardScreen.js +1 -1
  38. package/dist/backend/dashboard/DashboardScreen.js.map +1 -1
  39. package/dist/backend/date-range/DateRangeSelect.js +1 -1
  40. package/dist/backend/date-range/DateRangeSelect.js.map +1 -1
  41. package/dist/backend/date-range/InlineDateRangeSelect.js +1 -1
  42. package/dist/backend/date-range/InlineDateRangeSelect.js.map +1 -1
  43. package/dist/backend/detail/AccessDeniedMessage.js +1 -1
  44. package/dist/backend/detail/AccessDeniedMessage.js.map +1 -1
  45. package/dist/backend/detail/ActivitiesSection.js +5 -5
  46. package/dist/backend/detail/ActivitiesSection.js.map +1 -1
  47. package/dist/backend/detail/AddressEditor.js +3 -3
  48. package/dist/backend/detail/AddressEditor.js.map +2 -2
  49. package/dist/backend/detail/AddressTiles.js +3 -3
  50. package/dist/backend/detail/AddressTiles.js.map +2 -2
  51. package/dist/backend/detail/AttachmentMetadataDialog.js +1 -1
  52. package/dist/backend/detail/AttachmentMetadataDialog.js.map +1 -1
  53. package/dist/backend/detail/CustomDataSection.js +1 -1
  54. package/dist/backend/detail/CustomDataSection.js.map +1 -1
  55. package/dist/backend/detail/InlineEditors.js +5 -5
  56. package/dist/backend/detail/InlineEditors.js.map +1 -1
  57. package/dist/backend/detail/NotesSection.js +6 -6
  58. package/dist/backend/detail/NotesSection.js.map +1 -1
  59. package/dist/backend/detail/TagsSection.js +1 -1
  60. package/dist/backend/detail/TagsSection.js.map +1 -1
  61. package/dist/backend/devtools/UmesDevToolsPanel.js +6 -6
  62. package/dist/backend/devtools/UmesDevToolsPanel.js.map +2 -2
  63. package/dist/backend/devtools/components/ConflictWarnings.js +3 -3
  64. package/dist/backend/devtools/components/ConflictWarnings.js.map +2 -2
  65. package/dist/backend/devtools/components/EnricherTiming.js +2 -2
  66. package/dist/backend/devtools/components/EnricherTiming.js.map +2 -2
  67. package/dist/backend/devtools/components/EventFlow.js +5 -5
  68. package/dist/backend/devtools/components/EventFlow.js.map +2 -2
  69. package/dist/backend/devtools/components/ExtensionPointList.js +3 -3
  70. package/dist/backend/devtools/components/ExtensionPointList.js.map +2 -2
  71. package/dist/backend/devtools/components/InterceptorActivity.js +6 -6
  72. package/dist/backend/devtools/components/InterceptorActivity.js.map +2 -2
  73. package/dist/backend/forms/ActionsDropdown.js +1 -1
  74. package/dist/backend/forms/ActionsDropdown.js.map +1 -1
  75. package/dist/backend/forms/FormActionButtons.js +2 -3
  76. package/dist/backend/forms/FormActionButtons.js.map +2 -2
  77. package/dist/backend/indexes/PartialIndexBanner.js +8 -8
  78. package/dist/backend/indexes/PartialIndexBanner.js.map +2 -2
  79. package/dist/backend/inputs/ComboboxInput.js +1 -1
  80. package/dist/backend/inputs/ComboboxInput.js.map +2 -2
  81. package/dist/backend/inputs/DatePicker.js +3 -3
  82. package/dist/backend/inputs/DatePicker.js.map +1 -1
  83. package/dist/backend/inputs/DateTimePicker.js +3 -3
  84. package/dist/backend/inputs/DateTimePicker.js.map +1 -1
  85. package/dist/backend/inputs/EventSelect.js +1 -1
  86. package/dist/backend/inputs/EventSelect.js.map +2 -2
  87. package/dist/backend/inputs/LookupSelect.js +1 -1
  88. package/dist/backend/inputs/LookupSelect.js.map +1 -1
  89. package/dist/backend/inputs/SwitchableMarkdownInput.js +1 -1
  90. package/dist/backend/inputs/SwitchableMarkdownInput.js.map +1 -1
  91. package/dist/backend/inputs/TagsInput.js +2 -2
  92. package/dist/backend/inputs/TagsInput.js.map +2 -2
  93. package/dist/backend/inputs/TimeInput.js +1 -1
  94. package/dist/backend/inputs/TimeInput.js.map +1 -1
  95. package/dist/backend/inputs/TimePicker.js +3 -3
  96. package/dist/backend/inputs/TimePicker.js.map +1 -1
  97. package/dist/backend/messages/MessageObjectDetail.js +1 -1
  98. package/dist/backend/messages/MessageObjectDetail.js.map +1 -1
  99. package/dist/backend/messages/MessageObjectPreview.js +1 -1
  100. package/dist/backend/messages/MessageObjectPreview.js.map +1 -1
  101. package/dist/backend/messages/message-compose-form-groups.js +3 -3
  102. package/dist/backend/messages/message-compose-form-groups.js.map +1 -1
  103. package/dist/backend/notifications/NotificationCountBadge.js +1 -1
  104. package/dist/backend/notifications/NotificationCountBadge.js.map +2 -2
  105. package/dist/backend/notifications/NotificationPanel.js +3 -3
  106. package/dist/backend/notifications/NotificationPanel.js.map +1 -1
  107. package/dist/backend/progress/ProgressTopBar.js +4 -4
  108. package/dist/backend/progress/ProgressTopBar.js.map +2 -2
  109. package/dist/backend/schedule/ScheduleAgenda.js +1 -1
  110. package/dist/backend/schedule/ScheduleAgenda.js.map +2 -2
  111. package/dist/backend/schedule/ScheduleCalendar.js +1 -1
  112. package/dist/backend/schedule/ScheduleCalendar.js.map +1 -1
  113. package/dist/backend/schedule/ScheduleGrid.js +1 -1
  114. package/dist/backend/schedule/ScheduleGrid.js.map +2 -2
  115. package/dist/backend/version-history/VersionHistoryPanel.js +4 -4
  116. package/dist/backend/version-history/VersionHistoryPanel.js.map +2 -2
  117. package/dist/frontend/AuthFooter.js +1 -1
  118. package/dist/frontend/AuthFooter.js.map +1 -1
  119. package/dist/frontend/LanguageSwitcher.js +1 -1
  120. package/dist/frontend/LanguageSwitcher.js.map +1 -1
  121. package/dist/frontend/Layout.js +2 -2
  122. package/dist/frontend/Layout.js.map +1 -1
  123. package/dist/index.js +5 -0
  124. package/dist/index.js.map +2 -2
  125. package/dist/portal/PortalShell.js +15 -15
  126. package/dist/portal/PortalShell.js.map +2 -2
  127. package/dist/portal/components/PortalCard.js +2 -2
  128. package/dist/portal/components/PortalCard.js.map +2 -2
  129. package/dist/portal/components/PortalNotificationPanel.js +18 -18
  130. package/dist/portal/components/PortalNotificationPanel.js.map +2 -2
  131. package/dist/portal/components/PortalPageHeader.js +1 -1
  132. package/dist/portal/components/PortalPageHeader.js.map +2 -2
  133. package/dist/primitives/avatar.js +11 -1
  134. package/dist/primitives/avatar.js.map +2 -2
  135. package/dist/primitives/badge.js +1 -1
  136. package/dist/primitives/badge.js.map +1 -1
  137. package/dist/primitives/button.js +9 -5
  138. package/dist/primitives/button.js.map +2 -2
  139. package/dist/primitives/calendar.js +1 -1
  140. package/dist/primitives/calendar.js.map +1 -1
  141. package/dist/primitives/checkbox-field.js +63 -0
  142. package/dist/primitives/checkbox-field.js.map +7 -0
  143. package/dist/primitives/checkbox.js +31 -17
  144. package/dist/primitives/checkbox.js.map +2 -2
  145. package/dist/primitives/dialog.js +4 -4
  146. package/dist/primitives/dialog.js.map +1 -1
  147. package/dist/primitives/fancy-button.js +72 -0
  148. package/dist/primitives/fancy-button.js.map +7 -0
  149. package/dist/primitives/icon-button.js +20 -4
  150. package/dist/primitives/icon-button.js.map +2 -2
  151. package/dist/primitives/kbd.js +27 -0
  152. package/dist/primitives/kbd.js.map +7 -0
  153. package/dist/primitives/link-button.js +56 -0
  154. package/dist/primitives/link-button.js.map +7 -0
  155. package/dist/primitives/popover.js +1 -1
  156. package/dist/primitives/popover.js.map +1 -1
  157. package/dist/primitives/social-button.js +61 -0
  158. package/dist/primitives/social-button.js.map +7 -0
  159. package/dist/primitives/tabs.js +1 -1
  160. package/dist/primitives/tabs.js.map +1 -1
  161. package/dist/primitives/tag.js +45 -0
  162. package/dist/primitives/tag.js.map +7 -0
  163. package/dist/primitives/tooltip.js +1 -1
  164. package/dist/primitives/tooltip.js.map +1 -1
  165. package/package.json +3 -3
  166. package/src/backend/AppShell.tsx +25 -28
  167. package/src/backend/ContextHelp.tsx +1 -1
  168. package/src/backend/CrudForm.tsx +12 -15
  169. package/src/backend/DataTable.tsx +9 -10
  170. package/src/backend/FilterBar.tsx +6 -5
  171. package/src/backend/FilterOverlay.tsx +10 -10
  172. package/src/backend/FlashMessages.tsx +1 -1
  173. package/src/backend/JsonBuilder.tsx +6 -6
  174. package/src/backend/NextStepCallout.tsx +1 -1
  175. package/src/backend/PerspectiveSidebar.tsx +2 -2
  176. package/src/backend/ProfileDropdown.tsx +1 -1
  177. package/src/backend/RowActions.tsx +1 -1
  178. package/src/backend/UserMenu.tsx +2 -2
  179. package/src/backend/WebhookSetupGuide.tsx +11 -11
  180. package/src/backend/charts/KpiCard.tsx +3 -3
  181. package/src/backend/columns/ColumnChooserPanel.tsx +1 -1
  182. package/src/backend/custom-fields/FieldDefinitionsEditor.tsx +3 -3
  183. package/src/backend/dashboard/DashboardScreen.tsx +1 -1
  184. package/src/backend/date-range/DateRangeSelect.tsx +1 -1
  185. package/src/backend/date-range/InlineDateRangeSelect.tsx +1 -1
  186. package/src/backend/detail/AccessDeniedMessage.tsx +1 -1
  187. package/src/backend/detail/ActivitiesSection.tsx +5 -5
  188. package/src/backend/detail/AddressEditor.tsx +3 -3
  189. package/src/backend/detail/AddressTiles.tsx +3 -3
  190. package/src/backend/detail/AttachmentMetadataDialog.tsx +1 -1
  191. package/src/backend/detail/CustomDataSection.tsx +1 -1
  192. package/src/backend/detail/InlineEditors.tsx +5 -5
  193. package/src/backend/detail/NotesSection.tsx +6 -6
  194. package/src/backend/detail/TagsSection.tsx +1 -1
  195. package/src/backend/devtools/UmesDevToolsPanel.tsx +6 -6
  196. package/src/backend/devtools/components/ConflictWarnings.tsx +4 -4
  197. package/src/backend/devtools/components/EnricherTiming.tsx +2 -2
  198. package/src/backend/devtools/components/EventFlow.tsx +5 -5
  199. package/src/backend/devtools/components/ExtensionPointList.tsx +3 -3
  200. package/src/backend/devtools/components/InterceptorActivity.tsx +6 -6
  201. package/src/backend/forms/ActionsDropdown.tsx +1 -1
  202. package/src/backend/forms/FormActionButtons.tsx +4 -5
  203. package/src/backend/indexes/PartialIndexBanner.tsx +8 -8
  204. package/src/backend/inputs/ComboboxInput.tsx +1 -1
  205. package/src/backend/inputs/DatePicker.tsx +3 -3
  206. package/src/backend/inputs/DateTimePicker.tsx +3 -3
  207. package/src/backend/inputs/EventSelect.tsx +1 -1
  208. package/src/backend/inputs/LookupSelect.tsx +1 -1
  209. package/src/backend/inputs/SwitchableMarkdownInput.tsx +1 -1
  210. package/src/backend/inputs/TagsInput.tsx +2 -2
  211. package/src/backend/inputs/TimeInput.tsx +1 -1
  212. package/src/backend/inputs/TimePicker.tsx +3 -3
  213. package/src/backend/messages/MessageObjectDetail.tsx +1 -1
  214. package/src/backend/messages/MessageObjectPreview.tsx +1 -1
  215. package/src/backend/messages/message-compose-form-groups.tsx +3 -3
  216. package/src/backend/notifications/NotificationCountBadge.tsx +1 -1
  217. package/src/backend/notifications/NotificationPanel.tsx +3 -3
  218. package/src/backend/progress/ProgressTopBar.tsx +4 -4
  219. package/src/backend/schedule/ScheduleAgenda.tsx +1 -1
  220. package/src/backend/schedule/ScheduleCalendar.tsx +1 -1
  221. package/src/backend/schedule/ScheduleGrid.tsx +1 -1
  222. package/src/backend/version-history/VersionHistoryPanel.tsx +4 -4
  223. package/src/frontend/AuthFooter.tsx +1 -1
  224. package/src/frontend/LanguageSwitcher.tsx +1 -1
  225. package/src/frontend/Layout.tsx +2 -2
  226. package/src/index.ts +6 -1
  227. package/src/portal/PortalShell.tsx +15 -15
  228. package/src/portal/components/PortalCard.tsx +2 -2
  229. package/src/portal/components/PortalNotificationPanel.tsx +18 -18
  230. package/src/portal/components/PortalPageHeader.tsx +1 -1
  231. package/src/primitives/avatar.tsx +22 -0
  232. package/src/primitives/badge.tsx +1 -1
  233. package/src/primitives/button.tsx +12 -5
  234. package/src/primitives/calendar.tsx +1 -1
  235. package/src/primitives/checkbox-field.tsx +85 -0
  236. package/src/primitives/checkbox.tsx +44 -18
  237. package/src/primitives/dialog.tsx +4 -4
  238. package/src/primitives/fancy-button.tsx +89 -0
  239. package/src/primitives/icon-button.tsx +19 -2
  240. package/src/primitives/kbd.tsx +38 -0
  241. package/src/primitives/link-button.tsx +55 -0
  242. package/src/primitives/popover.tsx +1 -1
  243. package/src/primitives/social-button.tsx +80 -0
  244. package/src/primitives/tabs.tsx +1 -1
  245. package/src/primitives/tag.tsx +66 -0
  246. package/src/primitives/tooltip.tsx +1 -1
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/inputs/DateTimePicker.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { format } from 'date-fns'\nimport type { Locale } from 'date-fns'\nimport { CalendarIcon } 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 { Calendar } from '../../primitives/calendar'\nimport { TimeInput } from './TimeInput'\n\nexport type DateTimePickerProps = {\n value?: Date | null\n onChange: (date: Date | null) => void\n placeholder?: string\n disabled?: boolean\n readOnly?: boolean\n className?: string\n locale?: Locale\n displayFormat?: string\n minuteStep?: number\n showTodayButton?: boolean\n showClearButton?: boolean\n minDate?: Date\n maxDate?: Date\n}\n\nconst DAY_FIRST_LOCALE_CODES = new Set([\n 'pl', 'de', 'fr', 'es', 'it', 'pt', 'nl', 'ru', 'cs', 'sk', 'hu', 'ro',\n])\n\nfunction deriveDisplayFormat(locale?: Locale): string {\n if (!locale) return 'MMM d, yyyy HH:mm'\n const code = locale.code?.split('-')[0]?.toLowerCase() ?? ''\n return DAY_FIRST_LOCALE_CODES.has(code) ? 'd MMM yyyy HH:mm' : 'MMM d, yyyy HH:mm'\n}\n\nfunction extractTime(date: Date): string {\n const hour = String(date.getHours()).padStart(2, '0')\n const minute = String(date.getMinutes()).padStart(2, '0')\n return `${hour}:${minute}`\n}\n\nfunction applyTimeToDate(base: Date, time: string): Date {\n const parts = time.split(':')\n const hour = parseInt(parts[0] ?? '0', 10)\n const minute = parseInt(parts[1] ?? '0', 10)\n const next = new Date(base)\n next.setHours(isNaN(hour) ? 0 : hour)\n next.setMinutes(isNaN(minute) ? 0 : minute)\n next.setSeconds(0)\n next.setMilliseconds(0)\n return next\n}\n\nexport function DateTimePicker({\n value,\n onChange,\n placeholder,\n disabled = false,\n readOnly = false,\n className,\n locale,\n displayFormat,\n minuteStep = 1,\n showTodayButton = true,\n showClearButton = true,\n minDate,\n maxDate,\n}: DateTimePickerProps) {\n const t = useT()\n const [open, setOpen] = React.useState(false)\n\n const resolvedFormat = displayFormat ?? deriveDisplayFormat(locale)\n const placeholderText = placeholder ?? t('ui.dateTimePicker.placeholder', 'Pick date and time')\n const timeLabelText = t('ui.dateTimePicker.timeLabel', 'Time')\n const todayText = t('ui.dateTimePicker.todayButton', 'Today')\n const clearText = t('ui.dateTimePicker.clearButton', 'Clear')\n\n const formattedValue = React.useMemo(() => {\n if (!value) return null\n try {\n return format(value, resolvedFormat, locale ? { locale } : undefined)\n } catch {\n return null\n }\n }, [value, resolvedFormat, locale])\n\n const handleDaySelect = React.useCallback(\n (day: Date | undefined) => {\n if (!day) return\n const currentTime = value ? extractTime(value) : '00:00'\n onChange(applyTimeToDate(day, currentTime))\n },\n [onChange, value]\n )\n\n const handleTimeChange = React.useCallback(\n (time: string) => {\n const base = value ?? null\n if (!base) return\n onChange(applyTimeToDate(base, time))\n },\n [onChange, value]\n )\n\n const handleToday = React.useCallback(() => {\n const now = new Date()\n const currentTime = value ? extractTime(value) : extractTime(now)\n onChange(applyTimeToDate(now, currentTime))\n setOpen(false)\n }, [onChange, value])\n\n const handleClear = React.useCallback(() => {\n onChange(null)\n setOpen(false)\n }, [onChange])\n\n const isInteractive = !disabled && !readOnly\n\n const disabledMatcher = React.useMemo(() => {\n if (!minDate && !maxDate) return undefined\n const matchers: import('react-day-picker').Matcher[] = []\n if (minDate) matchers.push({ before: minDate })\n if (maxDate) matchers.push({ after: maxDate })\n return matchers\n }, [minDate, maxDate])\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:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-1',\n 'disabled:bg-muted disabled:text-muted-foreground disabled:cursor-not-allowed',\n readOnly && 'cursor-default opacity-70',\n !formattedValue && 'text-muted-foreground',\n className\n )}\n onClick={isInteractive ? undefined : (e) => e.preventDefault()}\n >\n <CalendarIcon className=\"h-4 w-4 shrink-0 text-muted-foreground\" />\n <span className=\"flex-1 truncate\">\n {formattedValue ?? placeholderText}\n </span>\n </button>\n </PopoverTrigger>\n <PopoverContent className=\"p-0 w-auto\">\n <Calendar\n mode=\"single\"\n selected={value ?? undefined}\n onSelect={handleDaySelect}\n locale={locale}\n disabled={disabledMatcher}\n initialFocus\n />\n <div className=\"border-t px-3 py-2 space-y-2\">\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm text-muted-foreground shrink-0\">{timeLabelText}:</span>\n <TimeInput\n value={value ? extractTime(value) : undefined}\n onChange={handleTimeChange}\n minuteStep={minuteStep}\n />\n </div>\n {(showTodayButton || showClearButton) && (\n <div className=\"flex items-center justify-between gap-2\">\n {showTodayButton && (\n <button\n type=\"button\"\n onClick={handleToday}\n className=\"text-sm text-primary hover:underline focus:outline-none\"\n >\n {todayText}\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:outline-none ml-auto\"\n >\n {clearText}\n </button>\n )}\n </div>\n )}\n </div>\n </PopoverContent>\n </Popover>\n )\n}\n"],
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { format } from 'date-fns'\nimport type { Locale } from 'date-fns'\nimport { CalendarIcon } 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 { Calendar } from '../../primitives/calendar'\nimport { TimeInput } from './TimeInput'\n\nexport type DateTimePickerProps = {\n value?: Date | null\n onChange: (date: Date | null) => void\n placeholder?: string\n disabled?: boolean\n readOnly?: boolean\n className?: string\n locale?: Locale\n displayFormat?: string\n minuteStep?: number\n showTodayButton?: boolean\n showClearButton?: boolean\n minDate?: Date\n maxDate?: Date\n}\n\nconst DAY_FIRST_LOCALE_CODES = new Set([\n 'pl', 'de', 'fr', 'es', 'it', 'pt', 'nl', 'ru', 'cs', 'sk', 'hu', 'ro',\n])\n\nfunction deriveDisplayFormat(locale?: Locale): string {\n if (!locale) return 'MMM d, yyyy HH:mm'\n const code = locale.code?.split('-')[0]?.toLowerCase() ?? ''\n return DAY_FIRST_LOCALE_CODES.has(code) ? 'd MMM yyyy HH:mm' : 'MMM d, yyyy HH:mm'\n}\n\nfunction extractTime(date: Date): string {\n const hour = String(date.getHours()).padStart(2, '0')\n const minute = String(date.getMinutes()).padStart(2, '0')\n return `${hour}:${minute}`\n}\n\nfunction applyTimeToDate(base: Date, time: string): Date {\n const parts = time.split(':')\n const hour = parseInt(parts[0] ?? '0', 10)\n const minute = parseInt(parts[1] ?? '0', 10)\n const next = new Date(base)\n next.setHours(isNaN(hour) ? 0 : hour)\n next.setMinutes(isNaN(minute) ? 0 : minute)\n next.setSeconds(0)\n next.setMilliseconds(0)\n return next\n}\n\nexport function DateTimePicker({\n value,\n onChange,\n placeholder,\n disabled = false,\n readOnly = false,\n className,\n locale,\n displayFormat,\n minuteStep = 1,\n showTodayButton = true,\n showClearButton = true,\n minDate,\n maxDate,\n}: DateTimePickerProps) {\n const t = useT()\n const [open, setOpen] = React.useState(false)\n\n const resolvedFormat = displayFormat ?? deriveDisplayFormat(locale)\n const placeholderText = placeholder ?? t('ui.dateTimePicker.placeholder', 'Pick date and time')\n const timeLabelText = t('ui.dateTimePicker.timeLabel', 'Time')\n const todayText = t('ui.dateTimePicker.todayButton', 'Today')\n const clearText = t('ui.dateTimePicker.clearButton', 'Clear')\n\n const formattedValue = React.useMemo(() => {\n if (!value) return null\n try {\n return format(value, resolvedFormat, locale ? { locale } : undefined)\n } catch {\n return null\n }\n }, [value, resolvedFormat, locale])\n\n const handleDaySelect = React.useCallback(\n (day: Date | undefined) => {\n if (!day) return\n const currentTime = value ? extractTime(value) : '00:00'\n onChange(applyTimeToDate(day, currentTime))\n },\n [onChange, value]\n )\n\n const handleTimeChange = React.useCallback(\n (time: string) => {\n const base = value ?? null\n if (!base) return\n onChange(applyTimeToDate(base, time))\n },\n [onChange, value]\n )\n\n const handleToday = React.useCallback(() => {\n const now = new Date()\n const currentTime = value ? extractTime(value) : extractTime(now)\n onChange(applyTimeToDate(now, currentTime))\n setOpen(false)\n }, [onChange, value])\n\n const handleClear = React.useCallback(() => {\n onChange(null)\n setOpen(false)\n }, [onChange])\n\n const isInteractive = !disabled && !readOnly\n\n const disabledMatcher = React.useMemo(() => {\n if (!minDate && !maxDate) return undefined\n const matchers: import('react-day-picker').Matcher[] = []\n if (minDate) matchers.push({ before: minDate })\n if (maxDate) matchers.push({ after: maxDate })\n return matchers\n }, [minDate, maxDate])\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 !formattedValue && 'text-muted-foreground',\n className\n )}\n onClick={isInteractive ? undefined : (e) => e.preventDefault()}\n >\n <CalendarIcon className=\"h-4 w-4 shrink-0 text-muted-foreground\" />\n <span className=\"flex-1 truncate\">\n {formattedValue ?? placeholderText}\n </span>\n </button>\n </PopoverTrigger>\n <PopoverContent className=\"p-0 w-auto\">\n <Calendar\n mode=\"single\"\n selected={value ?? undefined}\n onSelect={handleDaySelect}\n locale={locale}\n disabled={disabledMatcher}\n initialFocus\n />\n <div className=\"border-t px-3 py-2 space-y-2\">\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm text-muted-foreground shrink-0\">{timeLabelText}:</span>\n <TimeInput\n value={value ? extractTime(value) : undefined}\n onChange={handleTimeChange}\n minuteStep={minuteStep}\n />\n </div>\n {(showTodayButton || showClearButton) && (\n <div className=\"flex items-center justify-between gap-2\">\n {showTodayButton && (\n <button\n type=\"button\"\n onClick={handleToday}\n className=\"text-sm text-primary hover:underline focus-visible:outline-none\"\n >\n {todayText}\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 </div>\n </PopoverContent>\n </Popover>\n )\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -55,7 +55,7 @@ function EventSelect({
55
55
  {
56
56
  value,
57
57
  onChange: (e) => onChange(e.target.value),
58
- className: `h-10 rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 ${className || ""}`,
58
+ className: `h-10 rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 ${className || ""}`,
59
59
  disabled: disabled || isLoading,
60
60
  children: [
61
61
  /* @__PURE__ */ jsx("option", { value: "", disabled: true, children: isLoading ? "Loading..." : isEmpty ? "No events available" : placeholder }),
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/inputs/EventSelect.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useMemo } from 'react'\nimport { useQuery } from '@tanstack/react-query'\nimport { apiCall } from '../utils/apiCall'\n\n/**\n * Event definition returned by the API\n */\nexport interface EventDefinition {\n id: string\n label: string\n description?: string\n category?: 'crud' | 'lifecycle' | 'system' | 'custom'\n module?: string\n entity?: string\n excludeFromTriggers?: boolean\n}\n\nexport interface EventSelectProps {\n /** Current selected event ID */\n value: string\n /** Called when event is selected */\n onChange: (eventId: string) => void\n /** Placeholder text when no event selected */\n placeholder?: string\n /** Additional CSS classes */\n className?: string\n /** Whether the select is disabled */\n disabled?: boolean\n /** Filter events by category */\n categories?: Array<'crud' | 'lifecycle' | 'system' | 'custom'>\n /** Filter events by module */\n modules?: string[]\n /** Whether to exclude events marked as excludeFromTriggers (default: true) */\n excludeTriggerExcluded?: boolean\n}\n\n/**\n * EventSelect - A reusable select component for choosing declared events\n *\n * Fetches available events from the API and groups them by module.\n */\nexport function EventSelect({\n value,\n onChange,\n placeholder = 'Select an event...',\n className,\n disabled,\n categories,\n modules,\n excludeTriggerExcluded = true,\n}: EventSelectProps) {\n // Fetch events from the API\n const { data: allEvents = [], isLoading } = useQuery({\n queryKey: ['declared-events', excludeTriggerExcluded],\n queryFn: async () => {\n const result = await apiCall<{ data: EventDefinition[]; total: number }>(\n `/api/events?excludeTriggerExcluded=${excludeTriggerExcluded}`\n )\n if (!result.ok) return []\n return result.result?.data || []\n },\n staleTime: 5 * 60 * 1000, // Cache for 5 minutes\n })\n\n // Filter events based on props\n const filteredEvents = useMemo(() => {\n let events = allEvents\n\n if (categories?.length) {\n events = events.filter(e => e.category && categories.includes(e.category))\n }\n if (modules?.length) {\n events = events.filter(e => e.module && modules.includes(e.module))\n }\n\n return events\n }, [allEvents, categories, modules])\n\n // Group events by module for better UX\n const eventsByModule = useMemo(() => {\n const grouped: Record<string, EventDefinition[]> = {}\n for (const event of filteredEvents) {\n const module = event.module || 'other'\n if (!grouped[module]) grouped[module] = []\n grouped[module].push(event)\n }\n // Sort modules alphabetically\n return Object.fromEntries(\n Object.entries(grouped).sort(([a], [b]) => a.localeCompare(b))\n )\n }, [filteredEvents])\n\n // Format module name for display\n const formatModuleName = (module: string): string => {\n return module.charAt(0).toUpperCase() + module.slice(1).replace(/_/g, ' ')\n }\n\n const isEmpty = !isLoading && filteredEvents.length === 0\n\n return (\n <select\n value={value}\n onChange={(e) => onChange(e.target.value)}\n className={`h-10 rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 ${className || ''}`}\n disabled={disabled || isLoading}\n >\n <option value=\"\" disabled>\n {isLoading ? 'Loading...' : isEmpty ? 'No events available' : placeholder}\n </option>\n {Object.entries(eventsByModule).map(([module, moduleEvents]) => (\n <optgroup key={module} label={formatModuleName(module)}>\n {moduleEvents.map(event => (\n <option key={event.id} value={event.id}>\n {event.label}\n </option>\n ))}\n </optgroup>\n ))}\n </select>\n )\n}\n\n/**\n * Hook for getting available events\n */\nexport function useAvailableEvents(options?: {\n categories?: Array<'crud' | 'lifecycle' | 'system' | 'custom'>\n modules?: string[]\n excludeTriggerExcluded?: boolean\n}) {\n const excludeTriggerExcluded = options?.excludeTriggerExcluded !== false\n\n const { data: allEvents = [], isLoading, error, refetch } = useQuery({\n queryKey: ['declared-events', excludeTriggerExcluded],\n queryFn: async () => {\n const result = await apiCall<{ data: EventDefinition[]; total: number }>(\n `/api/events?excludeTriggerExcluded=${excludeTriggerExcluded}`\n )\n if (!result.ok) return []\n return result.result?.data || []\n },\n staleTime: 5 * 60 * 1000,\n })\n\n const filteredEvents = useMemo(() => {\n let events = allEvents\n\n if (options?.categories?.length) {\n events = events.filter(e => e.category && options.categories!.includes(e.category))\n }\n if (options?.modules?.length) {\n events = events.filter(e => e.module && options.modules!.includes(e.module))\n }\n\n return events\n }, [allEvents, options])\n\n // Group by module\n const eventsByModule = useMemo(() => {\n const grouped: Record<string, EventDefinition[]> = {}\n for (const event of filteredEvents) {\n const module = event.module || 'other'\n if (!grouped[module]) grouped[module] = []\n grouped[module].push(event)\n }\n return Object.fromEntries(\n Object.entries(grouped).sort(([a], [b]) => a.localeCompare(b))\n )\n }, [filteredEvents])\n\n return {\n events: filteredEvents,\n eventsByModule,\n isLoading,\n error,\n refetch,\n }\n}\n\nexport default EventSelect\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useMemo } from 'react'\nimport { useQuery } from '@tanstack/react-query'\nimport { apiCall } from '../utils/apiCall'\n\n/**\n * Event definition returned by the API\n */\nexport interface EventDefinition {\n id: string\n label: string\n description?: string\n category?: 'crud' | 'lifecycle' | 'system' | 'custom'\n module?: string\n entity?: string\n excludeFromTriggers?: boolean\n}\n\nexport interface EventSelectProps {\n /** Current selected event ID */\n value: string\n /** Called when event is selected */\n onChange: (eventId: string) => void\n /** Placeholder text when no event selected */\n placeholder?: string\n /** Additional CSS classes */\n className?: string\n /** Whether the select is disabled */\n disabled?: boolean\n /** Filter events by category */\n categories?: Array<'crud' | 'lifecycle' | 'system' | 'custom'>\n /** Filter events by module */\n modules?: string[]\n /** Whether to exclude events marked as excludeFromTriggers (default: true) */\n excludeTriggerExcluded?: boolean\n}\n\n/**\n * EventSelect - A reusable select component for choosing declared events\n *\n * Fetches available events from the API and groups them by module.\n */\nexport function EventSelect({\n value,\n onChange,\n placeholder = 'Select an event...',\n className,\n disabled,\n categories,\n modules,\n excludeTriggerExcluded = true,\n}: EventSelectProps) {\n // Fetch events from the API\n const { data: allEvents = [], isLoading } = useQuery({\n queryKey: ['declared-events', excludeTriggerExcluded],\n queryFn: async () => {\n const result = await apiCall<{ data: EventDefinition[]; total: number }>(\n `/api/events?excludeTriggerExcluded=${excludeTriggerExcluded}`\n )\n if (!result.ok) return []\n return result.result?.data || []\n },\n staleTime: 5 * 60 * 1000, // Cache for 5 minutes\n })\n\n // Filter events based on props\n const filteredEvents = useMemo(() => {\n let events = allEvents\n\n if (categories?.length) {\n events = events.filter(e => e.category && categories.includes(e.category))\n }\n if (modules?.length) {\n events = events.filter(e => e.module && modules.includes(e.module))\n }\n\n return events\n }, [allEvents, categories, modules])\n\n // Group events by module for better UX\n const eventsByModule = useMemo(() => {\n const grouped: Record<string, EventDefinition[]> = {}\n for (const event of filteredEvents) {\n const module = event.module || 'other'\n if (!grouped[module]) grouped[module] = []\n grouped[module].push(event)\n }\n // Sort modules alphabetically\n return Object.fromEntries(\n Object.entries(grouped).sort(([a], [b]) => a.localeCompare(b))\n )\n }, [filteredEvents])\n\n // Format module name for display\n const formatModuleName = (module: string): string => {\n return module.charAt(0).toUpperCase() + module.slice(1).replace(/_/g, ' ')\n }\n\n const isEmpty = !isLoading && filteredEvents.length === 0\n\n return (\n <select\n value={value}\n onChange={(e) => onChange(e.target.value)}\n className={`h-10 rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 ${className || ''}`}\n disabled={disabled || isLoading}\n >\n <option value=\"\" disabled>\n {isLoading ? 'Loading...' : isEmpty ? 'No events available' : placeholder}\n </option>\n {Object.entries(eventsByModule).map(([module, moduleEvents]) => (\n <optgroup key={module} label={formatModuleName(module)}>\n {moduleEvents.map(event => (\n <option key={event.id} value={event.id}>\n {event.label}\n </option>\n ))}\n </optgroup>\n ))}\n </select>\n )\n}\n\n/**\n * Hook for getting available events\n */\nexport function useAvailableEvents(options?: {\n categories?: Array<'crud' | 'lifecycle' | 'system' | 'custom'>\n modules?: string[]\n excludeTriggerExcluded?: boolean\n}) {\n const excludeTriggerExcluded = options?.excludeTriggerExcluded !== false\n\n const { data: allEvents = [], isLoading, error, refetch } = useQuery({\n queryKey: ['declared-events', excludeTriggerExcluded],\n queryFn: async () => {\n const result = await apiCall<{ data: EventDefinition[]; total: number }>(\n `/api/events?excludeTriggerExcluded=${excludeTriggerExcluded}`\n )\n if (!result.ok) return []\n return result.result?.data || []\n },\n staleTime: 5 * 60 * 1000,\n })\n\n const filteredEvents = useMemo(() => {\n let events = allEvents\n\n if (options?.categories?.length) {\n events = events.filter(e => e.category && options.categories!.includes(e.category))\n }\n if (options?.modules?.length) {\n events = events.filter(e => e.module && options.modules!.includes(e.module))\n }\n\n return events\n }, [allEvents, options])\n\n // Group by module\n const eventsByModule = useMemo(() => {\n const grouped: Record<string, EventDefinition[]> = {}\n for (const event of filteredEvents) {\n const module = event.module || 'other'\n if (!grouped[module]) grouped[module] = []\n grouped[module].push(event)\n }\n return Object.fromEntries(\n Object.entries(grouped).sort(([a], [b]) => a.localeCompare(b))\n )\n }, [filteredEvents])\n\n return {\n events: filteredEvents,\n eventsByModule,\n isLoading,\n error,\n refetch,\n }\n}\n\nexport default EventSelect\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -124,7 +124,7 @@ function LookupSelect({
124
124
  "div",
125
125
  {
126
126
  className: cn(
127
- "flex gap-3 rounded border bg-card p-3 transition-colors cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background",
127
+ "flex gap-3 rounded border bg-card p-3 transition-colors cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background",
128
128
  isSelected ? "border-primary/70 bg-primary/5" : "hover:border-primary/50"
129
129
  ),
130
130
  role: "button",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/inputs/LookupSelect.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Loader2, Search, X } from 'lucide-react'\nimport { Button } from '../../primitives/button'\nimport { cn } from '@open-mercato/shared/lib/utils'\n\nexport type LookupSelectItem = {\n id: string\n title: string\n subtitle?: string | null\n badge?: string | null\n icon?: React.ReactNode\n disabled?: boolean\n rightLabel?: string | null\n description?: string | null\n}\n\ntype LookupSelectProps = {\n value: string | null\n onChange: (next: string | null) => void\n fetchItems?: (query: string) => Promise<LookupSelectItem[]>\n fetchOptions?: (query?: string) => Promise<LookupSelectItem[]>\n options?: LookupSelectItem[]\n minQuery?: number\n actionSlot?: React.ReactNode\n onReady?: (controls: { setQuery: (value: string) => void }) => void\n searchPlaceholder?: string\n placeholder?: string\n clearLabel?: string\n emptyLabel?: string\n loadingLabel?: string\n selectLabel?: string\n selectedLabel?: string\n minQueryHintLabel?: string\n startTypingLabel?: string\n selectedHintLabel?: (id: string) => string\n disabled?: boolean\n loading?: boolean\n defaultOpen?: boolean\n}\n\nexport function LookupSelect({\n value,\n onChange,\n fetchItems,\n fetchOptions,\n options,\n minQuery = 2,\n actionSlot,\n onReady,\n placeholder,\n searchPlaceholder = placeholder ?? 'Search\u2026',\n clearLabel = 'Clear selection',\n emptyLabel = 'No results',\n loadingLabel = 'Searching\u2026',\n selectLabel = 'Select',\n selectedLabel = 'Selected',\n minQueryHintLabel,\n startTypingLabel = 'Start typing to search.',\n selectedHintLabel,\n disabled = false,\n loading: loadingProp = false,\n defaultOpen = false,\n}: LookupSelectProps) {\n const [query, setQuery] = React.useState('')\n const [items, setItems] = React.useState<LookupSelectItem[]>(options ?? [])\n const [loading, setLoading] = React.useState(false)\n const [hasTyped, setHasTyped] = React.useState(defaultOpen)\n const [error, setError] = React.useState<string | null>(null)\n const [fetchKey, setFetchKey] = React.useState(0)\n const fetchItemsRef = React.useRef(fetchItems ?? fetchOptions)\n const setQueryRef = React.useRef(setQuery)\n const optionsWasArrayRef = React.useRef(Array.isArray(options))\n\n React.useEffect(() => {\n fetchItemsRef.current = fetchItems ?? fetchOptions\n }, [fetchItems, fetchOptions])\n\n React.useEffect(() => {\n if (Array.isArray(options)) {\n optionsWasArrayRef.current = true\n setItems(options)\n } else if (optionsWasArrayRef.current) {\n optionsWasArrayRef.current = false\n setFetchKey((k) => k + 1)\n }\n }, [options])\n\n React.useEffect(() => {\n setQueryRef.current = setQuery\n if (onReady) onReady({ setQuery })\n }, [onReady, setQuery])\n\n const shouldSearch =\n defaultOpen || query.trim().length >= minQuery || Boolean(value && (options?.length ?? 0) > 0)\n React.useEffect(() => {\n if (disabled) {\n setItems(options ?? [])\n setLoading(false)\n return\n }\n let cancelled = false\n let timer: ReturnType<typeof setTimeout> | null = null\n if (!shouldSearch) {\n setItems(options ?? [])\n setLoading(false)\n setError(null)\n return () => { cancelled = true }\n }\n setLoading(true)\n setError(null)\n timer = setTimeout(() => {\n const requestId = Date.now()\n const fetcher = fetchItemsRef.current\n const loader = fetcher ?? (() => Promise.resolve(options ?? []))\n loader(query.trim())\n .then((result) => {\n if (cancelled) return\n setItems(result)\n })\n .catch((err) => {\n if (cancelled) return\n console.error('LookupSelect.fetchItems', err)\n setError('error')\n })\n .finally(() => {\n if (!cancelled) setLoading(false)\n })\n return requestId\n }, 220)\n return () => {\n cancelled = true\n if (timer) clearTimeout(timer)\n }\n }, [query, shouldSearch, fetchKey])\n\n return (\n <div className=\"space-y-3\">\n <div className=\"flex flex-col gap-2 sm:flex-row sm:items-center sm:gap-3\">\n <div className=\"relative flex-1\">\n <Search className=\"pointer-events-none absolute left-2 top-2.5 h-4 w-4 text-muted-foreground\" />\n <input\n className=\"w-full rounded border pl-8 pr-2 py-2 text-sm\"\n value={query}\n onChange={(event) => {\n setQuery(event.target.value)\n setHasTyped(true)\n }}\n placeholder={searchPlaceholder}\n disabled={disabled}\n />\n </div>\n {actionSlot ? <div className=\"sm:self-start\">{actionSlot}</div> : null}\n </div>\n {shouldSearch ? (\n <div className=\"space-y-2\">\n {loading || loadingProp ? (\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n {loadingLabel}\n </div>\n ) : null}\n {!loading && !loadingProp && !items.length ? (\n <p className=\"text-xs text-muted-foreground\">{emptyLabel}</p>\n ) : null}\n <div className=\"space-y-2 max-h-80 overflow-y-auto\">\n {items.map((item) => {\n const isSelected = value === item.id\n return (\n <div\n key={item.id}\n className={cn(\n 'flex gap-3 rounded border bg-card p-3 transition-colors cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background',\n isSelected ? 'border-primary/70 bg-primary/5' : 'hover:border-primary/50'\n )}\n role=\"button\"\n tabIndex={item.disabled ? -1 : 0}\n onClick={() => {\n if (item.disabled && !isSelected) return\n onChange(item.id)\n }}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n if (item.disabled && !isSelected) return\n onChange(item.id)\n }\n }}\n aria-pressed={isSelected}\n >\n <div className=\"flex h-10 w-10 sm:h-12 sm:w-12 shrink-0 items-center justify-center overflow-hidden rounded border bg-muted\">\n {item.icon ?? <span className=\"text-muted-foreground\">\u2022</span>}\n </div>\n <div className=\"flex min-w-0 flex-1 flex-col gap-1\">\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"min-w-0\">\n <div className=\"truncate text-sm font-medium\">{item.title}</div>\n {item.subtitle ? (\n <div className=\"text-xs text-muted-foreground truncate\">{item.subtitle}</div>\n ) : null}\n {item.description ? (\n <div className=\"text-xs text-muted-foreground truncate\">{item.description}</div>\n ) : null}\n </div>\n {item.rightLabel ? (\n <div className=\"shrink-0 text-xs font-medium text-muted-foreground\">{item.rightLabel}</div>\n ) : null}\n </div>\n <div className=\"flex justify-end\">\n <Button\n type=\"button\"\n variant={isSelected ? 'secondary' : 'outline'}\n size=\"sm\"\n className=\"shrink-0\"\n onClick={(event) => {\n event.stopPropagation()\n if (item.disabled && !isSelected) return\n onChange(item.id)\n }}\n disabled={item.disabled && !isSelected}\n >\n {isSelected ? selectedLabel : selectLabel}\n </Button>\n </div>\n </div>\n </div>\n )\n })}\n </div>\n {value ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"w-fit gap-1 text-sm font-normal\"\n onClick={() => onChange(null)}\n >\n <X className=\"h-4 w-4\" />\n {clearLabel}\n </Button>\n ) : null}\n </div>\n ) : hasTyped ? (\n <p className=\"text-xs text-muted-foreground\">\n {minQueryHintLabel ?? `Type at least ${minQuery} characters or paste an id to search.`}\n </p>\n ) : (\n <p className=\"text-xs text-muted-foreground\">{startTypingLabel}</p>\n )}\n {error ? <p className=\"text-xs text-destructive\">{emptyLabel}</p> : null}\n </div>\n )\n}\n"],
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Loader2, Search, X } from 'lucide-react'\nimport { Button } from '../../primitives/button'\nimport { cn } from '@open-mercato/shared/lib/utils'\n\nexport type LookupSelectItem = {\n id: string\n title: string\n subtitle?: string | null\n badge?: string | null\n icon?: React.ReactNode\n disabled?: boolean\n rightLabel?: string | null\n description?: string | null\n}\n\ntype LookupSelectProps = {\n value: string | null\n onChange: (next: string | null) => void\n fetchItems?: (query: string) => Promise<LookupSelectItem[]>\n fetchOptions?: (query?: string) => Promise<LookupSelectItem[]>\n options?: LookupSelectItem[]\n minQuery?: number\n actionSlot?: React.ReactNode\n onReady?: (controls: { setQuery: (value: string) => void }) => void\n searchPlaceholder?: string\n placeholder?: string\n clearLabel?: string\n emptyLabel?: string\n loadingLabel?: string\n selectLabel?: string\n selectedLabel?: string\n minQueryHintLabel?: string\n startTypingLabel?: string\n selectedHintLabel?: (id: string) => string\n disabled?: boolean\n loading?: boolean\n defaultOpen?: boolean\n}\n\nexport function LookupSelect({\n value,\n onChange,\n fetchItems,\n fetchOptions,\n options,\n minQuery = 2,\n actionSlot,\n onReady,\n placeholder,\n searchPlaceholder = placeholder ?? 'Search\u2026',\n clearLabel = 'Clear selection',\n emptyLabel = 'No results',\n loadingLabel = 'Searching\u2026',\n selectLabel = 'Select',\n selectedLabel = 'Selected',\n minQueryHintLabel,\n startTypingLabel = 'Start typing to search.',\n selectedHintLabel,\n disabled = false,\n loading: loadingProp = false,\n defaultOpen = false,\n}: LookupSelectProps) {\n const [query, setQuery] = React.useState('')\n const [items, setItems] = React.useState<LookupSelectItem[]>(options ?? [])\n const [loading, setLoading] = React.useState(false)\n const [hasTyped, setHasTyped] = React.useState(defaultOpen)\n const [error, setError] = React.useState<string | null>(null)\n const [fetchKey, setFetchKey] = React.useState(0)\n const fetchItemsRef = React.useRef(fetchItems ?? fetchOptions)\n const setQueryRef = React.useRef(setQuery)\n const optionsWasArrayRef = React.useRef(Array.isArray(options))\n\n React.useEffect(() => {\n fetchItemsRef.current = fetchItems ?? fetchOptions\n }, [fetchItems, fetchOptions])\n\n React.useEffect(() => {\n if (Array.isArray(options)) {\n optionsWasArrayRef.current = true\n setItems(options)\n } else if (optionsWasArrayRef.current) {\n optionsWasArrayRef.current = false\n setFetchKey((k) => k + 1)\n }\n }, [options])\n\n React.useEffect(() => {\n setQueryRef.current = setQuery\n if (onReady) onReady({ setQuery })\n }, [onReady, setQuery])\n\n const shouldSearch =\n defaultOpen || query.trim().length >= minQuery || Boolean(value && (options?.length ?? 0) > 0)\n React.useEffect(() => {\n if (disabled) {\n setItems(options ?? [])\n setLoading(false)\n return\n }\n let cancelled = false\n let timer: ReturnType<typeof setTimeout> | null = null\n if (!shouldSearch) {\n setItems(options ?? [])\n setLoading(false)\n setError(null)\n return () => { cancelled = true }\n }\n setLoading(true)\n setError(null)\n timer = setTimeout(() => {\n const requestId = Date.now()\n const fetcher = fetchItemsRef.current\n const loader = fetcher ?? (() => Promise.resolve(options ?? []))\n loader(query.trim())\n .then((result) => {\n if (cancelled) return\n setItems(result)\n })\n .catch((err) => {\n if (cancelled) return\n console.error('LookupSelect.fetchItems', err)\n setError('error')\n })\n .finally(() => {\n if (!cancelled) setLoading(false)\n })\n return requestId\n }, 220)\n return () => {\n cancelled = true\n if (timer) clearTimeout(timer)\n }\n }, [query, shouldSearch, fetchKey])\n\n return (\n <div className=\"space-y-3\">\n <div className=\"flex flex-col gap-2 sm:flex-row sm:items-center sm:gap-3\">\n <div className=\"relative flex-1\">\n <Search className=\"pointer-events-none absolute left-2 top-2.5 h-4 w-4 text-muted-foreground\" />\n <input\n className=\"w-full rounded border pl-8 pr-2 py-2 text-sm\"\n value={query}\n onChange={(event) => {\n setQuery(event.target.value)\n setHasTyped(true)\n }}\n placeholder={searchPlaceholder}\n disabled={disabled}\n />\n </div>\n {actionSlot ? <div className=\"sm:self-start\">{actionSlot}</div> : null}\n </div>\n {shouldSearch ? (\n <div className=\"space-y-2\">\n {loading || loadingProp ? (\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n {loadingLabel}\n </div>\n ) : null}\n {!loading && !loadingProp && !items.length ? (\n <p className=\"text-xs text-muted-foreground\">{emptyLabel}</p>\n ) : null}\n <div className=\"space-y-2 max-h-80 overflow-y-auto\">\n {items.map((item) => {\n const isSelected = value === item.id\n return (\n <div\n key={item.id}\n className={cn(\n 'flex gap-3 rounded border bg-card p-3 transition-colors cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background',\n isSelected ? 'border-primary/70 bg-primary/5' : 'hover:border-primary/50'\n )}\n role=\"button\"\n tabIndex={item.disabled ? -1 : 0}\n onClick={() => {\n if (item.disabled && !isSelected) return\n onChange(item.id)\n }}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n if (item.disabled && !isSelected) return\n onChange(item.id)\n }\n }}\n aria-pressed={isSelected}\n >\n <div className=\"flex h-10 w-10 sm:h-12 sm:w-12 shrink-0 items-center justify-center overflow-hidden rounded border bg-muted\">\n {item.icon ?? <span className=\"text-muted-foreground\">\u2022</span>}\n </div>\n <div className=\"flex min-w-0 flex-1 flex-col gap-1\">\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"min-w-0\">\n <div className=\"truncate text-sm font-medium\">{item.title}</div>\n {item.subtitle ? (\n <div className=\"text-xs text-muted-foreground truncate\">{item.subtitle}</div>\n ) : null}\n {item.description ? (\n <div className=\"text-xs text-muted-foreground truncate\">{item.description}</div>\n ) : null}\n </div>\n {item.rightLabel ? (\n <div className=\"shrink-0 text-xs font-medium text-muted-foreground\">{item.rightLabel}</div>\n ) : null}\n </div>\n <div className=\"flex justify-end\">\n <Button\n type=\"button\"\n variant={isSelected ? 'secondary' : 'outline'}\n size=\"sm\"\n className=\"shrink-0\"\n onClick={(event) => {\n event.stopPropagation()\n if (item.disabled && !isSelected) return\n onChange(item.id)\n }}\n disabled={item.disabled && !isSelected}\n >\n {isSelected ? selectedLabel : selectLabel}\n </Button>\n </div>\n </div>\n </div>\n )\n })}\n </div>\n {value ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"w-fit gap-1 text-sm font-normal\"\n onClick={() => onChange(null)}\n >\n <X className=\"h-4 w-4\" />\n {clearLabel}\n </Button>\n ) : null}\n </div>\n ) : hasTyped ? (\n <p className=\"text-xs text-muted-foreground\">\n {minQueryHintLabel ?? `Type at least ${minQuery} characters or paste an id to search.`}\n </p>\n ) : (\n <p className=\"text-xs text-muted-foreground\">{startTypingLabel}</p>\n )}\n {error ? <p className=\"text-xs text-destructive\">{emptyLabel}</p> : null}\n </div>\n )\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -43,7 +43,7 @@ function SwitchableMarkdownInput({
43
43
  const { resolvedTheme } = useTheme();
44
44
  const editorWrapperClasses = editorWrapperClassName ?? "w-full rounded-lg border border-muted-foreground/20 bg-background p-2";
45
45
  const editorClasses = editorClassName ?? "w-full";
46
- const textareaClasses = textareaClassName ?? "w-full resize-none overflow-hidden rounded-lg border border-muted-foreground/20 bg-background px-3 py-2 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary";
46
+ const textareaClasses = textareaClassName ?? "w-full resize-none overflow-hidden rounded-lg border border-muted-foreground/20 bg-background px-3 py-2 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring";
47
47
  if (isMarkdownEnabled && !disableMarkdown) {
48
48
  return /* @__PURE__ */ jsx("div", { className: editorWrapperClasses, children: /* @__PURE__ */ jsx("div", { "data-color-mode": resolvedTheme, className: editorClasses, children: /* @__PURE__ */ jsx(
49
49
  UiMarkdownEditor,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/inputs/SwitchableMarkdownInput.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport dynamic from 'next/dynamic'\nimport type { PluggableList } from 'unified'\nimport { LoadingMessage } from '../detail/LoadingMessage'\nimport { useMarkdownRemarkPlugins } from '../markdown/useMarkdownRemarkPlugins'\nimport { useTheme } from '../../theme'\n\nexport type SwitchableMarkdownInputProps = {\n value: string\n onChange: (value: string) => void\n isMarkdownEnabled: boolean\n disableMarkdown?: boolean\n height?: number\n placeholder?: string\n rows?: number\n textareaRef?: React.Ref<HTMLTextAreaElement>\n onTextareaInput?: React.FormEventHandler<HTMLTextAreaElement>\n textareaClassName?: string\n editorWrapperClassName?: string\n editorClassName?: string\n disabled?: boolean\n remarkPlugins?: PluggableList\n}\n\ntype UiMarkdownEditorProps = {\n value?: string\n height?: number\n onChange?: (value?: string) => void\n previewOptions?: { remarkPlugins?: unknown[] }\n}\n\nconst isTestEnv =\n typeof process !== 'undefined' &&\n (process.env.NODE_ENV === 'test' || typeof process.env.JEST_WORKER_ID !== 'undefined')\n\nconst MarkdownEditorTestStub: React.ComponentType<UiMarkdownEditorProps> = ({ value, onChange }) => (\n <textarea\n className=\"min-h-[160px] w-full rounded border px-3 py-2 text-sm\"\n value={value ?? ''}\n onChange={(event) => onChange?.(event.target.value)}\n />\n)\n\nconst UiMarkdownEditor = isTestEnv\n ? MarkdownEditorTestStub\n : (dynamic(() => import('@uiw/react-md-editor'), {\n ssr: false,\n loading: () => (\n <LoadingMessage\n label=\"Loading editor...\"\n className=\"min-h-[220px] justify-center\"\n />\n ),\n }) as unknown as React.ComponentType<UiMarkdownEditorProps>)\n\nexport function SwitchableMarkdownInput({\n value,\n onChange,\n isMarkdownEnabled,\n disableMarkdown,\n height = 220,\n placeholder,\n rows = 3,\n textareaRef,\n onTextareaInput,\n textareaClassName,\n editorWrapperClassName,\n editorClassName,\n disabled,\n remarkPlugins,\n}: SwitchableMarkdownInputProps) {\n const resolvedPlugins = useMarkdownRemarkPlugins(remarkPlugins)\n const { resolvedTheme } = useTheme()\n const editorWrapperClasses =\n editorWrapperClassName ?? 'w-full rounded-lg border border-muted-foreground/20 bg-background p-2'\n const editorClasses = editorClassName ?? 'w-full'\n const textareaClasses =\n textareaClassName\n ?? 'w-full resize-none overflow-hidden rounded-lg border border-muted-foreground/20 bg-background px-3 py-2 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary'\n\n if (isMarkdownEnabled && !disableMarkdown) {\n return (\n <div className={editorWrapperClasses}>\n <div data-color-mode={resolvedTheme} className={editorClasses}>\n <UiMarkdownEditor\n value={value}\n height={height}\n onChange={(nextValue) => onChange(typeof nextValue === 'string' ? nextValue : '')}\n previewOptions={resolvedPlugins.length ? { remarkPlugins: resolvedPlugins } : undefined}\n />\n </div>\n </div>\n )\n }\n\n return (\n <textarea\n ref={textareaRef}\n rows={rows}\n className={textareaClasses}\n placeholder={placeholder}\n value={value}\n onChange={(event) => onChange(event.target.value)}\n onInput={onTextareaInput}\n disabled={disabled}\n />\n )\n}\n"],
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport dynamic from 'next/dynamic'\nimport type { PluggableList } from 'unified'\nimport { LoadingMessage } from '../detail/LoadingMessage'\nimport { useMarkdownRemarkPlugins } from '../markdown/useMarkdownRemarkPlugins'\nimport { useTheme } from '../../theme'\n\nexport type SwitchableMarkdownInputProps = {\n value: string\n onChange: (value: string) => void\n isMarkdownEnabled: boolean\n disableMarkdown?: boolean\n height?: number\n placeholder?: string\n rows?: number\n textareaRef?: React.Ref<HTMLTextAreaElement>\n onTextareaInput?: React.FormEventHandler<HTMLTextAreaElement>\n textareaClassName?: string\n editorWrapperClassName?: string\n editorClassName?: string\n disabled?: boolean\n remarkPlugins?: PluggableList\n}\n\ntype UiMarkdownEditorProps = {\n value?: string\n height?: number\n onChange?: (value?: string) => void\n previewOptions?: { remarkPlugins?: unknown[] }\n}\n\nconst isTestEnv =\n typeof process !== 'undefined' &&\n (process.env.NODE_ENV === 'test' || typeof process.env.JEST_WORKER_ID !== 'undefined')\n\nconst MarkdownEditorTestStub: React.ComponentType<UiMarkdownEditorProps> = ({ value, onChange }) => (\n <textarea\n className=\"min-h-[160px] w-full rounded border px-3 py-2 text-sm\"\n value={value ?? ''}\n onChange={(event) => onChange?.(event.target.value)}\n />\n)\n\nconst UiMarkdownEditor = isTestEnv\n ? MarkdownEditorTestStub\n : (dynamic(() => import('@uiw/react-md-editor'), {\n ssr: false,\n loading: () => (\n <LoadingMessage\n label=\"Loading editor...\"\n className=\"min-h-[220px] justify-center\"\n />\n ),\n }) as unknown as React.ComponentType<UiMarkdownEditorProps>)\n\nexport function SwitchableMarkdownInput({\n value,\n onChange,\n isMarkdownEnabled,\n disableMarkdown,\n height = 220,\n placeholder,\n rows = 3,\n textareaRef,\n onTextareaInput,\n textareaClassName,\n editorWrapperClassName,\n editorClassName,\n disabled,\n remarkPlugins,\n}: SwitchableMarkdownInputProps) {\n const resolvedPlugins = useMarkdownRemarkPlugins(remarkPlugins)\n const { resolvedTheme } = useTheme()\n const editorWrapperClasses =\n editorWrapperClassName ?? 'w-full rounded-lg border border-muted-foreground/20 bg-background p-2'\n const editorClasses = editorClassName ?? 'w-full'\n const textareaClasses =\n textareaClassName\n ?? 'w-full resize-none overflow-hidden rounded-lg border border-muted-foreground/20 bg-background px-3 py-2 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring'\n\n if (isMarkdownEnabled && !disableMarkdown) {\n return (\n <div className={editorWrapperClasses}>\n <div data-color-mode={resolvedTheme} className={editorClasses}>\n <UiMarkdownEditor\n value={value}\n height={height}\n onChange={(nextValue) => onChange(typeof nextValue === 'string' ? nextValue : '')}\n previewOptions={resolvedPlugins.length ? { remarkPlugins: resolvedPlugins } : undefined}\n />\n </div>\n </div>\n )\n }\n\n return (\n <textarea\n ref={textareaRef}\n rows={rows}\n className={textareaClasses}\n placeholder={placeholder}\n value={value}\n onChange={(event) => onChange(event.target.value)}\n onInput={onTextareaInput}\n disabled={disabled}\n />\n )\n}\n"],
5
5
  "mappings": ";AAsCE;AAnCF,OAAO,aAAa;AAEpB,SAAS,sBAAsB;AAC/B,SAAS,gCAAgC;AACzC,SAAS,gBAAgB;AA0BzB,MAAM,YACJ,OAAO,YAAY,gBAClB,QAAQ,IAAI,aAAa,UAAU,OAAO,QAAQ,IAAI,mBAAmB;AAE5E,MAAM,yBAAqE,CAAC,EAAE,OAAO,SAAS,MAC5F;AAAA,EAAC;AAAA;AAAA,IACC,WAAU;AAAA,IACV,OAAO,SAAS;AAAA,IAChB,UAAU,CAAC,UAAU,WAAW,MAAM,OAAO,KAAK;AAAA;AACpD;AAGF,MAAM,mBAAmB,YACrB,yBACC,QAAQ,MAAM,OAAO,sBAAsB,GAAG;AAAA,EAC7C,KAAK;AAAA,EACL,SAAS,MACP;AAAA,IAAC;AAAA;AAAA,MACC,OAAM;AAAA,MACN,WAAU;AAAA;AAAA,EACZ;AAEJ,CAAC;AAEE,SAAS,wBAAwB;AAAA,EACtC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,SAAS;AAAA,EACT;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAiC;AAC/B,QAAM,kBAAkB,yBAAyB,aAAa;AAC9D,QAAM,EAAE,cAAc,IAAI,SAAS;AACnC,QAAM,uBACJ,0BAA0B;AAC5B,QAAM,gBAAgB,mBAAmB;AACzC,QAAM,kBACJ,qBACG;AAEL,MAAI,qBAAqB,CAAC,iBAAiB;AACzC,WACE,oBAAC,SAAI,WAAW,sBACd,8BAAC,SAAI,mBAAiB,eAAe,WAAW,eAC9C;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA,UAAU,CAAC,cAAc,SAAS,OAAO,cAAc,WAAW,YAAY,EAAE;AAAA,QAChF,gBAAgB,gBAAgB,SAAS,EAAE,eAAe,gBAAgB,IAAI;AAAA;AAAA,IAChF,GACF,GACF;AAAA,EAEJ;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL;AAAA,MACA,WAAW;AAAA,MACX;AAAA,MACA;AAAA,MACA,UAAU,CAAC,UAAU,SAAS,MAAM,OAAO,KAAK;AAAA,MAChD,SAAS;AAAA,MACT;AAAA;AAAA,EACF;AAEJ;",
6
6
  "names": []
7
7
  }
@@ -165,7 +165,7 @@ function TagsInput({
165
165
  return /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-2 rounded-sm bg-muted px-2 py-0.5 text-xs", children: [
166
166
  /* @__PURE__ */ jsxs("span", { className: "flex flex-col items-start leading-tight", children: [
167
167
  /* @__PURE__ */ jsx("span", { className: "whitespace-nowrap", children: label }),
168
- description ? /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground", children: description }) : null
168
+ description ? /* @__PURE__ */ jsx("span", { className: "text-overline text-muted-foreground", children: description }) : null
169
169
  ] }),
170
170
  /* @__PURE__ */ jsx(
171
171
  IconButton,
@@ -243,7 +243,7 @@ function TagsInput({
243
243
  },
244
244
  children: [
245
245
  /* @__PURE__ */ jsx("span", { children: option.label }),
246
- option.description ? /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground", children: option.description }) : null
246
+ option.description ? /* @__PURE__ */ jsx("span", { className: "text-overline text-muted-foreground", children: option.description }) : null
247
247
  ]
248
248
  },
249
249
  option.value
@@ -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}\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}: 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 valueRef = React.useRef(value)\n\n React.useEffect(() => {\n valueRef.current = value\n }, [value])\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-[10px] 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 (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-[10px] 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 { 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}\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}: 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 valueRef = React.useRef(value)\n\n React.useEffect(() => {\n valueRef.current = value\n }, [value])\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 (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
  );
89
89
  const inputClass = cn(
90
90
  "w-14 h-9 rounded border text-center text-sm tabular-nums",
91
- "focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-1",
91
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1",
92
92
  disabled && "bg-muted text-muted-foreground cursor-not-allowed",
93
93
  "disabled:bg-muted disabled:text-muted-foreground disabled:cursor-not-allowed"
94
94
  );
@@ -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'\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 inputClass = cn(\n 'w-14 h-9 rounded border text-center text-sm tabular-nums',\n 'focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-1',\n disabled && 'bg-muted text-muted-foreground cursor-not-allowed',\n 'disabled:bg-muted disabled:text-muted-foreground disabled:cursor-not-allowed'\n )\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={inputClass}\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={inputClass}\n />\n </div>\n )\n}\n"],
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'\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 inputClass = cn(\n 'w-14 h-9 rounded border text-center text-sm tabular-nums',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1',\n disabled && 'bg-muted text-muted-foreground cursor-not-allowed',\n 'disabled:bg-muted disabled:text-muted-foreground disabled:cursor-not-allowed'\n )\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={inputClass}\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={inputClass}\n />\n </div>\n )\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -54,7 +54,7 @@ function TimePicker({
54
54
  className: cn(
55
55
  "w-full h-9 flex items-center gap-2 rounded border px-3 text-sm text-left",
56
56
  "bg-background transition-colors",
57
- "focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-1",
57
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1",
58
58
  "disabled:bg-muted disabled:text-muted-foreground disabled:cursor-not-allowed",
59
59
  readOnly && "cursor-default opacity-70",
60
60
  !value && "text-muted-foreground",
@@ -82,7 +82,7 @@ function TimePicker({
82
82
  {
83
83
  type: "button",
84
84
  onClick: handleNow,
85
- className: "text-sm text-primary hover:underline focus:outline-none",
85
+ className: "text-sm text-primary hover:underline focus-visible:outline-none",
86
86
  children: nowText
87
87
  }
88
88
  ),
@@ -91,7 +91,7 @@ function TimePicker({
91
91
  {
92
92
  type: "button",
93
93
  onClick: handleClear,
94
- className: "text-sm text-muted-foreground hover:text-foreground hover:underline focus:outline-none ml-auto",
94
+ className: "text-sm text-muted-foreground hover:text-foreground hover:underline focus-visible:outline-none ml-auto",
95
95
  children: clearText
96
96
  }
97
97
  )
@@ -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:outline-none focus:ring-2 focus:ring-ring focus: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: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:outline-none ml-auto\"\n >\n {clearText}\n </button>\n )}\n </div>\n )}\n </PopoverContent>\n </Popover>\n )\n}\n"],
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
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;",
6
6
  "names": []
7
7
  }
@@ -32,7 +32,7 @@ function MessageObjectDetail(props) {
32
32
  Link,
33
33
  {
34
34
  href: resolveActionHref(viewAction.href, props.entityId),
35
- className: "block rounded-md transition-opacity hover:opacity-75",
35
+ className: "block rounded-md transition-opacity hover:opacity-80",
36
36
  children: preview
37
37
  }
38
38
  ) : preview,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/messages/MessageObjectDetail.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { ObjectDetailProps } from '@open-mercato/shared/modules/messages/types'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { MessageObjectPreview } from './MessageObjectPreview'\n\nfunction resolveActionHref(template: string, entityId: string): string {\n return template.replace('{entityId}', encodeURIComponent(entityId))\n}\n\nexport function MessageObjectDetail(props: ObjectDetailProps) {\n const t = useT()\n const [executingActionId, setExecutingActionId] = React.useState<string | null>(null)\n\n const viewAction = props.actions.find((a) => a.id === 'view')\n const otherActions = props.actions.filter((a) => a.id !== 'view')\n\n const preview = (\n <MessageObjectPreview\n entityId={props.entityId}\n entityModule={props.entityModule}\n entityType={props.entityType}\n snapshot={props.snapshot}\n previewData={props.previewData}\n actionRequired={props.actionRequired}\n actionType={props.actionType}\n actionLabel={props.actionLabel}\n icon={props.icon}\n />\n )\n\n return (\n <div className=\"space-y-3 rounded border p-3\">\n {viewAction?.href ? (\n <Link\n href={resolveActionHref(viewAction.href, props.entityId)}\n className=\"block rounded-md transition-opacity hover:opacity-75\"\n >\n {preview}\n </Link>\n ) : (\n preview\n )}\n\n {otherActions.length > 0 ? (\n <div className=\"flex flex-wrap gap-2\">\n {otherActions.map((action) => {\n if (action.href) {\n return (\n <Button\n key={action.id}\n type=\"button\"\n size=\"sm\"\n variant={action.variant ?? 'default'}\n asChild\n >\n <Link href={resolveActionHref(action.href, props.entityId)}>\n {t(action.labelKey ?? action.id, action.id)}\n </Link>\n </Button>\n )\n }\n return (\n <Button\n key={action.id}\n type=\"button\"\n size=\"sm\"\n variant={action.variant ?? 'default'}\n disabled={executingActionId !== null}\n onClick={async () => {\n if (executingActionId) return\n setExecutingActionId(action.id)\n try {\n await props.onAction(action.id, { id: props.entityId })\n } finally {\n setExecutingActionId(null)\n }\n }}\n >\n {executingActionId === action.id\n ? t('messages.actions.executing', 'Executing...')\n : t(action.labelKey ?? action.id, action.id)}\n </Button>\n )\n })}\n </div>\n ) : null}\n </div>\n )\n}\n\nexport default MessageObjectDetail\n"],
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { ObjectDetailProps } from '@open-mercato/shared/modules/messages/types'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { MessageObjectPreview } from './MessageObjectPreview'\n\nfunction resolveActionHref(template: string, entityId: string): string {\n return template.replace('{entityId}', encodeURIComponent(entityId))\n}\n\nexport function MessageObjectDetail(props: ObjectDetailProps) {\n const t = useT()\n const [executingActionId, setExecutingActionId] = React.useState<string | null>(null)\n\n const viewAction = props.actions.find((a) => a.id === 'view')\n const otherActions = props.actions.filter((a) => a.id !== 'view')\n\n const preview = (\n <MessageObjectPreview\n entityId={props.entityId}\n entityModule={props.entityModule}\n entityType={props.entityType}\n snapshot={props.snapshot}\n previewData={props.previewData}\n actionRequired={props.actionRequired}\n actionType={props.actionType}\n actionLabel={props.actionLabel}\n icon={props.icon}\n />\n )\n\n return (\n <div className=\"space-y-3 rounded border p-3\">\n {viewAction?.href ? (\n <Link\n href={resolveActionHref(viewAction.href, props.entityId)}\n className=\"block rounded-md transition-opacity hover:opacity-80\"\n >\n {preview}\n </Link>\n ) : (\n preview\n )}\n\n {otherActions.length > 0 ? (\n <div className=\"flex flex-wrap gap-2\">\n {otherActions.map((action) => {\n if (action.href) {\n return (\n <Button\n key={action.id}\n type=\"button\"\n size=\"sm\"\n variant={action.variant ?? 'default'}\n asChild\n >\n <Link href={resolveActionHref(action.href, props.entityId)}>\n {t(action.labelKey ?? action.id, action.id)}\n </Link>\n </Button>\n )\n }\n return (\n <Button\n key={action.id}\n type=\"button\"\n size=\"sm\"\n variant={action.variant ?? 'default'}\n disabled={executingActionId !== null}\n onClick={async () => {\n if (executingActionId) return\n setExecutingActionId(action.id)\n try {\n await props.onAction(action.id, { id: props.entityId })\n } finally {\n setExecutingActionId(null)\n }\n }}\n >\n {executingActionId === action.id\n ? t('messages.actions.executing', 'Executing...')\n : t(action.labelKey ?? action.id, action.id)}\n </Button>\n )\n })}\n </div>\n ) : null}\n </div>\n )\n}\n\nexport default MessageObjectDetail\n"],
5
5
  "mappings": ";AAqBI,cAcA,YAdA;AAnBJ,YAAY,WAAW;AACvB,OAAO,UAAU;AACjB,SAAS,YAAY;AAErB,SAAS,cAAc;AACvB,SAAS,4BAA4B;AAErC,SAAS,kBAAkB,UAAkB,UAA0B;AACrE,SAAO,SAAS,QAAQ,cAAc,mBAAmB,QAAQ,CAAC;AACpE;AAEO,SAAS,oBAAoB,OAA0B;AAC5D,QAAM,IAAI,KAAK;AACf,QAAM,CAAC,mBAAmB,oBAAoB,IAAI,MAAM,SAAwB,IAAI;AAEpF,QAAM,aAAa,MAAM,QAAQ,KAAK,CAAC,MAAM,EAAE,OAAO,MAAM;AAC5D,QAAM,eAAe,MAAM,QAAQ,OAAO,CAAC,MAAM,EAAE,OAAO,MAAM;AAEhE,QAAM,UACJ;AAAA,IAAC;AAAA;AAAA,MACC,UAAU,MAAM;AAAA,MAChB,cAAc,MAAM;AAAA,MACpB,YAAY,MAAM;AAAA,MAClB,UAAU,MAAM;AAAA,MAChB,aAAa,MAAM;AAAA,MACnB,gBAAgB,MAAM;AAAA,MACtB,YAAY,MAAM;AAAA,MAClB,aAAa,MAAM;AAAA,MACnB,MAAM,MAAM;AAAA;AAAA,EACd;AAGF,SACE,qBAAC,SAAI,WAAU,gCACZ;AAAA,gBAAY,OACX;AAAA,MAAC;AAAA;AAAA,QACC,MAAM,kBAAkB,WAAW,MAAM,MAAM,QAAQ;AAAA,QACvD,WAAU;AAAA,QAET;AAAA;AAAA,IACH,IAEA;AAAA,IAGD,aAAa,SAAS,IACrB,oBAAC,SAAI,WAAU,wBACZ,uBAAa,IAAI,CAAC,WAAW;AAC5B,UAAI,OAAO,MAAM;AACf,eACE;AAAA,UAAC;AAAA;AAAA,YAEC,MAAK;AAAA,YACL,MAAK;AAAA,YACL,SAAS,OAAO,WAAW;AAAA,YAC3B,SAAO;AAAA,YAEP,8BAAC,QAAK,MAAM,kBAAkB,OAAO,MAAM,MAAM,QAAQ,GACtD,YAAE,OAAO,YAAY,OAAO,IAAI,OAAO,EAAE,GAC5C;AAAA;AAAA,UARK,OAAO;AAAA,QASd;AAAA,MAEJ;AACA,aACE;AAAA,QAAC;AAAA;AAAA,UAEC,MAAK;AAAA,UACL,MAAK;AAAA,UACL,SAAS,OAAO,WAAW;AAAA,UAC3B,UAAU,sBAAsB;AAAA,UAChC,SAAS,YAAY;AACnB,gBAAI,kBAAmB;AACvB,iCAAqB,OAAO,EAAE;AAC9B,gBAAI;AACF,oBAAM,MAAM,SAAS,OAAO,IAAI,EAAE,IAAI,MAAM,SAAS,CAAC;AAAA,YACxD,UAAE;AACA,mCAAqB,IAAI;AAAA,YAC3B;AAAA,UACF;AAAA,UAEC,gCAAsB,OAAO,KAC1B,EAAE,8BAA8B,cAAc,IAC9C,EAAE,OAAO,YAAY,OAAO,IAAI,OAAO,EAAE;AAAA;AAAA,QAjBxC,OAAO;AAAA,MAkBd;AAAA,IAEJ,CAAC,GACH,IACE;AAAA,KACN;AAEJ;AAEA,IAAO,8BAAQ;",
6
6
  "names": []
7
7
  }
@@ -15,7 +15,7 @@ function MessageObjectPreview({
15
15
  }) {
16
16
  const t = useT();
17
17
  const Icon = resolveIcon(icon);
18
- return /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3 rounded-md border bg-muted/20 p-3", children: [
18
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3 rounded-md border bg-muted/30 p-3", children: [
19
19
  /* @__PURE__ */ jsx(Icon, { className: "mt-0.5 h-4 w-4 text-muted-foreground" }),
20
20
  /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-1", children: [
21
21
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/messages/MessageObjectPreview.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport { Box, type LucideIcon } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { ObjectPreviewProps } from '@open-mercato/shared/modules/messages/types'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { resolveRegisteredLucideIcon } from '../icons/lucideRegistry'\n\nfunction resolveIcon(name: string | undefined): LucideIcon {\n return resolveRegisteredLucideIcon(name) ?? Box\n}\n\nexport function MessageObjectPreview({\n previewData,\n actionRequired,\n actionLabel,\n icon,\n}: ObjectPreviewProps) {\n const t = useT()\n const Icon = resolveIcon(icon)\n\n return (\n <div className=\"flex items-start gap-3 rounded-md border bg-muted/20 p-3\">\n <Icon className=\"mt-0.5 h-4 w-4 text-muted-foreground\" />\n <div className=\"min-w-0 flex-1 space-y-1\">\n <div className=\"flex items-center gap-2\">\n <p className=\"truncate text-sm font-medium\">{previewData?.title || ''}</p>\n {actionRequired ? (\n <Badge variant=\"secondary\" className=\"text-xs\">\n {actionLabel || t('messages.composer.objectActionRequired', 'Action required')}\n </Badge>\n ) : null}\n </div>\n {previewData?.subtitle ? (\n <p className=\"truncate text-xs text-muted-foreground\">{previewData.subtitle}</p>\n ) : null}\n {previewData?.status ? (\n <Badge variant=\"outline\" className=\"text-xs\">{previewData.status}</Badge>\n ) : null}\n {previewData?.metadata && Object.keys(previewData.metadata).length > 0 ? (\n <dl className=\"space-y-1 pt-1\">\n {Object.entries(previewData.metadata).map(([key, value]) => (\n <div key={key} className=\"flex items-start gap-2 text-xs text-muted-foreground\">\n <dt className=\"font-medium capitalize\">{key}:</dt>\n <dd className=\"truncate\">{value}</dd>\n </div>\n ))}\n </dl>\n ) : null}\n </div>\n </div>\n )\n}\n\nexport default MessageObjectPreview\n"],
4
+ "sourcesContent": ["\"use client\"\n\nimport { Box, type LucideIcon } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { ObjectPreviewProps } from '@open-mercato/shared/modules/messages/types'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { resolveRegisteredLucideIcon } from '../icons/lucideRegistry'\n\nfunction resolveIcon(name: string | undefined): LucideIcon {\n return resolveRegisteredLucideIcon(name) ?? Box\n}\n\nexport function MessageObjectPreview({\n previewData,\n actionRequired,\n actionLabel,\n icon,\n}: ObjectPreviewProps) {\n const t = useT()\n const Icon = resolveIcon(icon)\n\n return (\n <div className=\"flex items-start gap-3 rounded-md border bg-muted/30 p-3\">\n <Icon className=\"mt-0.5 h-4 w-4 text-muted-foreground\" />\n <div className=\"min-w-0 flex-1 space-y-1\">\n <div className=\"flex items-center gap-2\">\n <p className=\"truncate text-sm font-medium\">{previewData?.title || ''}</p>\n {actionRequired ? (\n <Badge variant=\"secondary\" className=\"text-xs\">\n {actionLabel || t('messages.composer.objectActionRequired', 'Action required')}\n </Badge>\n ) : null}\n </div>\n {previewData?.subtitle ? (\n <p className=\"truncate text-xs text-muted-foreground\">{previewData.subtitle}</p>\n ) : null}\n {previewData?.status ? (\n <Badge variant=\"outline\" className=\"text-xs\">{previewData.status}</Badge>\n ) : null}\n {previewData?.metadata && Object.keys(previewData.metadata).length > 0 ? (\n <dl className=\"space-y-1 pt-1\">\n {Object.entries(previewData.metadata).map(([key, value]) => (\n <div key={key} className=\"flex items-start gap-2 text-xs text-muted-foreground\">\n <dt className=\"font-medium capitalize\">{key}:</dt>\n <dd className=\"truncate\">{value}</dd>\n </div>\n ))}\n </dl>\n ) : null}\n </div>\n </div>\n )\n}\n\nexport default MessageObjectPreview\n"],
5
5
  "mappings": ";AAuBM,cAEE,YAFF;AArBN,SAAS,WAA4B;AACrC,SAAS,YAAY;AAErB,SAAS,aAAa;AACtB,SAAS,mCAAmC;AAE5C,SAAS,YAAY,MAAsC;AACzD,SAAO,4BAA4B,IAAI,KAAK;AAC9C;AAEO,SAAS,qBAAqB;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAuB;AACrB,QAAM,IAAI,KAAK;AACf,QAAM,OAAO,YAAY,IAAI;AAE7B,SACE,qBAAC,SAAI,WAAU,4DACb;AAAA,wBAAC,QAAK,WAAU,wCAAuC;AAAA,IACvD,qBAAC,SAAI,WAAU,4BACb;AAAA,2BAAC,SAAI,WAAU,2BACb;AAAA,4BAAC,OAAE,WAAU,gCAAgC,uBAAa,SAAS,IAAG;AAAA,QACrE,iBACC,oBAAC,SAAM,SAAQ,aAAY,WAAU,WAClC,yBAAe,EAAE,0CAA0C,iBAAiB,GAC/E,IACE;AAAA,SACN;AAAA,MACC,aAAa,WACZ,oBAAC,OAAE,WAAU,0CAA0C,sBAAY,UAAS,IAC1E;AAAA,MACH,aAAa,SACZ,oBAAC,SAAM,SAAQ,WAAU,WAAU,WAAW,sBAAY,QAAO,IAC/D;AAAA,MACH,aAAa,YAAY,OAAO,KAAK,YAAY,QAAQ,EAAE,SAAS,IACnE,oBAAC,QAAG,WAAU,kBACX,iBAAO,QAAQ,YAAY,QAAQ,EAAE,IAAI,CAAC,CAAC,KAAK,KAAK,MACpD,qBAAC,SAAc,WAAU,wDACvB;AAAA,6BAAC,QAAG,WAAU,0BAA0B;AAAA;AAAA,UAAI;AAAA,WAAC;AAAA,QAC7C,oBAAC,QAAG,WAAU,YAAY,iBAAM;AAAA,WAFxB,GAGV,CACD,GACH,IACE;AAAA,OACN;AAAA,KACF;AAEJ;AAEA,IAAO,+BAAQ;",
6
6
  "names": []
7
7
  }
@@ -194,7 +194,7 @@ function ComposeModeFields({ compose }) {
194
194
  placeholder: compose.t("messages.placeholders.body", "Write your message..."),
195
195
  inputId: "messages-compose-body",
196
196
  rows: 8,
197
- textareaClassName: "min-h-[180px] w-full rounded-md border bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
197
+ textareaClassName: "min-h-[180px] w-full rounded-md border bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
198
198
  }
199
199
  ),
200
200
  /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
@@ -224,7 +224,7 @@ function ReplyModeFields({ compose }) {
224
224
  placeholder: compose.t("messages.placeholders.replyBody", "Write your reply..."),
225
225
  inputId: "messages-compose-body",
226
226
  rows: 8,
227
- textareaClassName: "min-h-[180px] w-full rounded-md border bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
227
+ textareaClassName: "min-h-[180px] w-full rounded-md border bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
228
228
  }
229
229
  ),
230
230
  /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
@@ -258,7 +258,7 @@ function ForwardModeFields({ compose }) {
258
258
  placeholder: compose.t("messages.placeholders.forwardContent", "Review and edit forwarded content..."),
259
259
  inputId: "messages-forward-note",
260
260
  rows: 6,
261
- textareaClassName: "min-h-[140px] w-full rounded-md border bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
261
+ textareaClassName: "min-h-[140px] w-full rounded-md border bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
262
262
  }
263
263
  )
264
264
  ] });