@open-mercato/ui 0.6.7-develop.6785.1.1dd7cfac55 → 0.6.7-develop.6795.1.8a3f27921c

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 (28) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/build.mjs +2 -54
  3. package/dist/backend/NextStepCallout.js +4 -4
  4. package/dist/backend/NextStepCallout.js.map +1 -1
  5. package/dist/backend/confirm-dialog/ConfirmDialog.js +12 -0
  6. package/dist/backend/confirm-dialog/ConfirmDialog.js.map +2 -2
  7. package/dist/backend/detail/AddressTiles.js +4 -5
  8. package/dist/backend/detail/AddressTiles.js.map +2 -2
  9. package/dist/backend/icons/lucideRegistry.generated.js +1 -25
  10. package/dist/backend/icons/lucideRegistry.generated.js.map +2 -2
  11. package/dist/backend/icons/lucideRegistry.js +3 -3
  12. package/dist/backend/icons/lucideRegistry.js.map +2 -2
  13. package/dist/backend/icons/lucideRegistryRuntime.js +28 -0
  14. package/dist/backend/icons/lucideRegistryRuntime.js.map +7 -0
  15. package/dist/backend/schedule/ScheduleToolbar.js +87 -70
  16. package/dist/backend/schedule/ScheduleToolbar.js.map +2 -2
  17. package/jest.config.cjs +2 -1
  18. package/package.json +17 -16
  19. package/scripts/lucideRegistrySource.cjs +133 -0
  20. package/src/backend/NextStepCallout.tsx +4 -4
  21. package/src/backend/confirm-dialog/ConfirmDialog.tsx +15 -0
  22. package/src/backend/confirm-dialog/__tests__/ConfirmDialog.test.tsx +33 -1
  23. package/src/backend/detail/AddressTiles.tsx +3 -6
  24. package/src/backend/icons/__tests__/lucideRegistryGenerator.test.ts +177 -0
  25. package/src/backend/icons/lucideRegistry.generated.tsx +3 -37
  26. package/src/backend/icons/lucideRegistry.ts +2 -2
  27. package/src/backend/icons/lucideRegistryRuntime.tsx +36 -0
  28. package/src/backend/schedule/ScheduleToolbar.tsx +89 -68
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/backend/icons/lucideRegistryRuntime.tsx"],
4
+ "sourcesContent": ["import type * as React from 'react'\nimport type { LucideIcon } from 'lucide-react'\nimport { LUCIDE_ICON_REGISTRY } from './lucideRegistry.generated'\n\nfunction normalizeKebabIconName(input: string): string {\n const trimmed = input.trim()\n if (!trimmed) return ''\n const withoutPrefix = trimmed.startsWith('lucide:') ? trimmed.slice('lucide:'.length) : trimmed\n if (!withoutPrefix) return ''\n if (!withoutPrefix.includes('-') && !withoutPrefix.includes('_') && !withoutPrefix.includes(' ') && /[A-Z]/.test(withoutPrefix)) {\n return withoutPrefix\n .replace(/([a-z0-9])([A-Z])/g, '$1-$2')\n .replace(/([A-Z])([A-Z][a-z])/g, '$1-$2')\n .toLowerCase()\n }\n return withoutPrefix\n .replace(/[_\\s]+/g, '-')\n .replace(/-+/g, '-')\n .toLowerCase()\n}\n\nexport function resolveRegisteredLucideIcon(name: string | undefined): LucideIcon | null {\n if (!name) return null\n const normalized = normalizeKebabIconName(name)\n if (!normalized) return null\n return (LUCIDE_ICON_REGISTRY as Record<string, LucideIcon | undefined>)[normalized] ?? null\n}\n\nexport function resolveRegisteredLucideIconNode(\n name: string | undefined,\n className: string\n): React.ReactNode | null {\n const Icon = resolveRegisteredLucideIcon(name)\n if (!Icon) return null\n return <Icon className={className} />\n}\n"],
5
+ "mappings": "AAkCS;AAhCT,SAAS,4BAA4B;AAErC,SAAS,uBAAuB,OAAuB;AACrD,QAAM,UAAU,MAAM,KAAK;AAC3B,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,gBAAgB,QAAQ,WAAW,SAAS,IAAI,QAAQ,MAAM,UAAU,MAAM,IAAI;AACxF,MAAI,CAAC,cAAe,QAAO;AAC3B,MAAI,CAAC,cAAc,SAAS,GAAG,KAAK,CAAC,cAAc,SAAS,GAAG,KAAK,CAAC,cAAc,SAAS,GAAG,KAAK,QAAQ,KAAK,aAAa,GAAG;AAC/H,WAAO,cACJ,QAAQ,sBAAsB,OAAO,EACrC,QAAQ,wBAAwB,OAAO,EACvC,YAAY;AAAA,EACjB;AACA,SAAO,cACJ,QAAQ,WAAW,GAAG,EACtB,QAAQ,OAAO,GAAG,EAClB,YAAY;AACjB;AAEO,SAAS,4BAA4B,MAA6C;AACvF,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,aAAa,uBAAuB,IAAI;AAC9C,MAAI,CAAC,WAAY,QAAO;AACxB,SAAQ,qBAAgE,UAAU,KAAK;AACzF;AAEO,SAAS,gCACd,MACA,WACwB;AACxB,QAAM,OAAO,4BAA4B,IAAI;AAC7C,MAAI,CAAC,KAAM,QAAO;AAClB,SAAO,oBAAC,QAAK,WAAsB;AACrC;",
6
+ "names": []
7
+ }
@@ -1,8 +1,19 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
- import { Button } from "../../primitives/button.js";
5
- import { Input } from "../../primitives/input.js";
4
+ import { DateRangePicker } from "../../primitives/date-range-picker.js";
5
+ import { IconButton } from "../../primitives/icon-button.js";
6
+ import {
7
+ SegmentedControl,
8
+ SegmentedControlItem
9
+ } from "../../primitives/segmented-control.js";
10
+ import {
11
+ Select,
12
+ SelectContent,
13
+ SelectItem,
14
+ SelectTrigger,
15
+ SelectValue
16
+ } from "../../primitives/select.js";
6
17
  import { cn } from "@open-mercato/shared/lib/utils";
7
18
  import { useT } from "@open-mercato/shared/lib/i18n/context";
8
19
  import { addDays } from "date-fns/addDays";
@@ -24,16 +35,19 @@ const VIEW_OPTIONS = [
24
35
  { id: "month", labelKey: "schedule.view.month", fallback: "Month" },
25
36
  { id: "agenda", labelKey: "schedule.view.agenda", fallback: "Agenda" }
26
37
  ];
27
- function formatDateInputValue(value) {
28
- const year = value.getFullYear();
29
- const month = String(value.getMonth() + 1).padStart(2, "0");
30
- const day = String(value.getDate()).padStart(2, "0");
31
- return `${year}-${month}-${day}`;
32
- }
33
- function parseDateInputValue(value, fallback) {
34
- if (!value) return fallback;
35
- const next = /* @__PURE__ */ new Date(`${value}T00:00:00`);
36
- return Number.isNaN(next.getTime()) ? fallback : next;
38
+ function getTimezoneOptions(current) {
39
+ const options = /* @__PURE__ */ new Set();
40
+ if (current) options.add(current);
41
+ options.add("UTC");
42
+ const resolved = Intl.DateTimeFormat().resolvedOptions().timeZone;
43
+ if (resolved) options.add(resolved);
44
+ const intlWithSupportedValues = Intl;
45
+ if (typeof intlWithSupportedValues.supportedValuesOf === "function") {
46
+ intlWithSupportedValues.supportedValuesOf("timeZone").forEach((timezone) => {
47
+ if (timezone) options.add(timezone);
48
+ });
49
+ }
50
+ return Array.from(options).sort((a, b) => a.localeCompare(b));
37
51
  }
38
52
  function ScheduleToolbar({
39
53
  view,
@@ -103,70 +117,73 @@ function ScheduleToolbar({
103
117
  const nextStart = startOfDay(addDays(range.start, multiplier * rangeLength));
104
118
  onRangeChange({ start: nextStart, end: endOfDay(addDays(nextStart, rangeLength - 1)) });
105
119
  }, [onRangeChange, range.start, rangeLength, view]);
106
- return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-3 rounded-xl border bg-card p-4 md:flex-row md:items-center md:justify-between", className), children: [
107
- /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-2", children: VIEW_OPTIONS.map((option) => /* @__PURE__ */ jsx(
108
- Button,
120
+ const timezoneOptions = React.useMemo(() => getTimezoneOptions(timezone), [timezone]);
121
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-wrap items-center gap-x-4 gap-y-2 rounded-xl border bg-card p-3", className), children: [
122
+ /* @__PURE__ */ jsx(
123
+ SegmentedControl,
109
124
  {
110
- variant: view === option.id ? "default" : "outline",
111
- size: "sm",
112
- onClick: () => {
113
- if (option.id === view) return;
114
- onViewChange(option.id);
115
- onRangeChange(deriveRangeForView(/* @__PURE__ */ new Date(), option.id));
125
+ value: view,
126
+ onValueChange: (value) => {
127
+ const nextView = value;
128
+ if (nextView === view) return;
129
+ onViewChange(nextView);
130
+ onRangeChange(deriveRangeForView(/* @__PURE__ */ new Date(), nextView));
116
131
  },
117
- children: t(option.labelKey, option.fallback)
118
- },
119
- option.id
120
- )) }),
121
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
122
- /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => shiftRange("prev"), "aria-label": t("schedule.range.prev", "Previous"), children: /* @__PURE__ */ jsx(ChevronLeft, { className: "size-4", "aria-hidden": true }) }),
123
- /* @__PURE__ */ jsx("div", { className: "text-sm font-medium text-foreground", children: rangeLabel }),
124
- /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => shiftRange("next"), "aria-label": t("schedule.range.next", "Next"), children: /* @__PURE__ */ jsx(ChevronRight, { className: "size-4", "aria-hidden": true }) })
132
+ "aria-label": t("schedule.view.label", "Schedule view"),
133
+ className: "shrink-0",
134
+ children: VIEW_OPTIONS.map((option) => /* @__PURE__ */ jsx(SegmentedControlItem, { value: option.id, children: t(option.labelKey, option.fallback) }, option.id))
135
+ }
136
+ ),
137
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [
138
+ /* @__PURE__ */ jsx(
139
+ IconButton,
140
+ {
141
+ type: "button",
142
+ variant: "outline",
143
+ onClick: () => shiftRange("prev"),
144
+ "aria-label": t("schedule.range.prev", "Previous"),
145
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "size-4", "aria-hidden": true })
146
+ }
147
+ ),
148
+ /* @__PURE__ */ jsx("div", { className: "min-w-0 whitespace-nowrap px-1 text-sm font-medium text-foreground", children: rangeLabel }),
149
+ /* @__PURE__ */ jsx(
150
+ IconButton,
151
+ {
152
+ type: "button",
153
+ variant: "outline",
154
+ onClick: () => shiftRange("next"),
155
+ "aria-label": t("schedule.range.next", "Next"),
156
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "size-4", "aria-hidden": true })
157
+ }
158
+ )
125
159
  ] }),
126
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [
127
- /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
128
- /* @__PURE__ */ jsx("span", { children: t("schedule.range.start", "Start") }),
129
- /* @__PURE__ */ jsx(
130
- Input,
131
- {
132
- type: "date",
133
- value: formatDateInputValue(range.start),
134
- onChange: (event) => {
135
- const nextStart = parseDateInputValue(event.target.value, range.start);
136
- onRangeChange({ start: nextStart, end: range.end });
137
- },
138
- className: "h-8 w-full sm:w-[140px]"
139
- }
140
- )
141
- ] }),
142
- /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
143
- /* @__PURE__ */ jsx("span", { children: t("schedule.range.end", "End") }),
144
- /* @__PURE__ */ jsx(
145
- Input,
146
- {
147
- type: "date",
148
- value: formatDateInputValue(range.end),
149
- onChange: (event) => {
150
- const nextEnd = parseDateInputValue(event.target.value, range.end);
151
- onRangeChange({ start: range.start, end: nextEnd });
152
- },
153
- className: "h-8 w-full sm:w-[140px]"
154
- }
155
- )
156
- ] }),
157
- /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
158
- /* @__PURE__ */ jsx("span", { children: t("schedule.range.timezone", "Timezone") }),
160
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-x-3 gap-y-2 md:ml-auto", children: [
161
+ /* @__PURE__ */ jsx(
162
+ DateRangePicker,
163
+ {
164
+ value: range,
165
+ onChange: (next) => {
166
+ if (!next) return;
167
+ onRangeChange({ start: startOfDay(next.start), end: endOfDay(next.end) });
168
+ },
169
+ size: "sm",
170
+ showPresets: false,
171
+ numberOfMonths: 2,
172
+ "aria-label": t("schedule.range.label", "Date range")
173
+ }
174
+ ),
175
+ onTimezoneChange ? /* @__PURE__ */ jsxs(Select, { value: timezone ?? void 0, onValueChange: onTimezoneChange, children: [
159
176
  /* @__PURE__ */ jsx(
160
- Input,
177
+ SelectTrigger,
161
178
  {
162
- type: "text",
163
- value: timezone ?? "",
164
- onChange: (event) => onTimezoneChange?.(event.target.value),
165
- className: "h-8 w-full sm:w-[180px]",
166
- placeholder: t("schedule.range.timezone.placeholder", "UTC")
179
+ size: "sm",
180
+ className: "w-auto min-w-[10rem]",
181
+ "aria-label": t("schedule.range.timezone", "Timezone"),
182
+ children: /* @__PURE__ */ jsx(SelectValue, { placeholder: t("schedule.range.timezone.placeholder", "UTC") })
167
183
  }
168
- )
169
- ] })
184
+ ),
185
+ /* @__PURE__ */ jsx(SelectContent, { children: timezoneOptions.map((option) => /* @__PURE__ */ jsx(SelectItem, { value: option, children: option }, option)) })
186
+ ] }) : null
170
187
  ] })
171
188
  ] });
172
189
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/schedule/ScheduleToolbar.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Button } from '../../primitives/button'\nimport { Input } from '../../primitives/input'\nimport type { ScheduleRange, ScheduleViewMode } from './types'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { addDays } from 'date-fns/addDays'\nimport { addMonths } from 'date-fns/addMonths'\nimport { addWeeks } from 'date-fns/addWeeks'\nimport { differenceInCalendarDays } from 'date-fns/differenceInCalendarDays'\nimport { endOfDay } from 'date-fns/endOfDay'\nimport { endOfMonth } from 'date-fns/endOfMonth'\nimport { endOfWeek } from 'date-fns/endOfWeek'\nimport { format } from 'date-fns/format'\nimport { startOfDay } from 'date-fns/startOfDay'\nimport { startOfMonth } from 'date-fns/startOfMonth'\nimport { startOfWeek } from 'date-fns/startOfWeek'\nimport { enUS } from 'date-fns/locale/en-US'\nimport { ChevronLeft, ChevronRight } from 'lucide-react'\n\nconst VIEW_OPTIONS: Array<{ id: ScheduleViewMode; labelKey: string; fallback: string }> = [\n { id: 'day', labelKey: 'schedule.view.day', fallback: 'Day' },\n { id: 'week', labelKey: 'schedule.view.week', fallback: 'Week' },\n { id: 'month', labelKey: 'schedule.view.month', fallback: 'Month' },\n { id: 'agenda', labelKey: 'schedule.view.agenda', fallback: 'Agenda' },\n]\n\nfunction formatDateInputValue(value: Date): string {\n const year = value.getFullYear()\n const month = String(value.getMonth() + 1).padStart(2, '0')\n const day = String(value.getDate()).padStart(2, '0')\n return `${year}-${month}-${day}`\n}\n\nfunction parseDateInputValue(value: string, fallback: Date): Date {\n if (!value) return fallback\n const next = new Date(`${value}T00:00:00`)\n return Number.isNaN(next.getTime()) ? fallback : next\n}\n\nexport type ScheduleToolbarProps = {\n view: ScheduleViewMode\n range: ScheduleRange\n timezone?: string\n onViewChange: (view: ScheduleViewMode) => void\n onRangeChange: (range: ScheduleRange) => void\n onTimezoneChange?: (timezone: string) => void\n className?: string\n}\n\nexport function ScheduleToolbar({\n view,\n range,\n timezone,\n onViewChange,\n onRangeChange,\n onTimezoneChange,\n className,\n}: ScheduleToolbarProps) {\n const t = useT()\n const rangeLength = React.useMemo(\n () => Math.max(1, differenceInCalendarDays(range.end, range.start) + 1),\n [range.end, range.start],\n )\n const deriveRangeForView = React.useCallback((base: Date, nextView: ScheduleViewMode): ScheduleRange => {\n if (nextView === 'day') {\n const start = startOfDay(base)\n return { start, end: endOfDay(start) }\n }\n if (nextView === 'week') {\n return { start: startOfWeek(base, { locale: enUS }), end: endOfWeek(base, { locale: enUS }) }\n }\n if (nextView === 'month') {\n return { start: startOfMonth(base), end: endOfMonth(base) }\n }\n const start = startOfDay(base)\n return { start, end: endOfDay(addDays(start, rangeLength - 1)) }\n }, [rangeLength])\n const rangeLabel = React.useMemo(() => {\n if (view === 'day') {\n return format(range.start, 'EEE, MMM d')\n }\n if (view === 'week') {\n const startLabel = format(range.start, 'MMM d')\n const endLabel = format(range.end, 'MMM d')\n const yearLabel = format(range.start, 'yyyy')\n return `${startLabel} - ${endLabel}, ${yearLabel}`\n }\n if (view === 'month') {\n return format(range.start, 'MMMM yyyy')\n }\n const startLabel = format(range.start, 'MMM d')\n const endLabel = format(range.end, 'MMM d, yyyy')\n return `${startLabel} - ${endLabel}`\n }, [range.end, range.start, view])\n\n const shiftRange = React.useCallback((direction: 'prev' | 'next') => {\n const multiplier = direction === 'prev' ? -1 : 1\n if (view === 'day') {\n const nextStart = startOfDay(addDays(range.start, multiplier))\n onRangeChange({ start: nextStart, end: endOfDay(nextStart) })\n return\n }\n if (view === 'week') {\n const base = addWeeks(range.start, multiplier)\n onRangeChange({\n start: startOfWeek(base, { locale: enUS }),\n end: endOfWeek(base, { locale: enUS }),\n })\n return\n }\n if (view === 'month') {\n const base = addMonths(range.start, multiplier)\n onRangeChange({ start: startOfMonth(base), end: endOfMonth(base) })\n return\n }\n const nextStart = startOfDay(addDays(range.start, multiplier * rangeLength))\n onRangeChange({ start: nextStart, end: endOfDay(addDays(nextStart, rangeLength - 1)) })\n }, [onRangeChange, range.start, rangeLength, view])\n\n return (\n <div className={cn('flex flex-col gap-3 rounded-xl border bg-card p-4 md:flex-row md:items-center md:justify-between', className)}>\n <div className=\"flex flex-wrap items-center gap-2\">\n {VIEW_OPTIONS.map((option) => (\n <Button\n key={option.id}\n variant={view === option.id ? 'default' : 'outline'}\n size=\"sm\"\n onClick={() => {\n if (option.id === view) return\n onViewChange(option.id)\n onRangeChange(deriveRangeForView(new Date(), option.id))\n }}\n >\n {t(option.labelKey, option.fallback)}\n </Button>\n ))}\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => shiftRange('prev')} aria-label={t('schedule.range.prev', 'Previous')}>\n <ChevronLeft className=\"size-4\" aria-hidden />\n </Button>\n <div className=\"text-sm font-medium text-foreground\">{rangeLabel}</div>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => shiftRange('next')} aria-label={t('schedule.range.next', 'Next')}>\n <ChevronRight className=\"size-4\" aria-hidden />\n </Button>\n </div>\n <div className=\"flex flex-wrap items-center gap-3\">\n <label className=\"flex items-center gap-2 text-xs text-muted-foreground\">\n <span>{t('schedule.range.start', 'Start')}</span>\n <Input\n type=\"date\"\n value={formatDateInputValue(range.start)}\n onChange={(event) => {\n const nextStart = parseDateInputValue(event.target.value, range.start)\n onRangeChange({ start: nextStart, end: range.end })\n }}\n className=\"h-8 w-full sm:w-[140px]\"\n />\n </label>\n <label className=\"flex items-center gap-2 text-xs text-muted-foreground\">\n <span>{t('schedule.range.end', 'End')}</span>\n <Input\n type=\"date\"\n value={formatDateInputValue(range.end)}\n onChange={(event) => {\n const nextEnd = parseDateInputValue(event.target.value, range.end)\n onRangeChange({ start: range.start, end: nextEnd })\n }}\n className=\"h-8 w-full sm:w-[140px]\"\n />\n </label>\n <label className=\"flex items-center gap-2 text-xs text-muted-foreground\">\n <span>{t('schedule.range.timezone', 'Timezone')}</span>\n <Input\n type=\"text\"\n value={timezone ?? ''}\n onChange={(event) => onTimezoneChange?.(event.target.value)}\n className=\"h-8 w-full sm:w-[180px]\"\n placeholder={t('schedule.range.timezone.placeholder', 'UTC')}\n />\n </label>\n </div>\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { DateRangePicker } from '../../primitives/date-range-picker'\nimport { IconButton } from '../../primitives/icon-button'\nimport {\n SegmentedControl,\n SegmentedControlItem,\n} from '../../primitives/segmented-control'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '../../primitives/select'\nimport type { ScheduleRange, ScheduleViewMode } from './types'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { addDays } from 'date-fns/addDays'\nimport { addMonths } from 'date-fns/addMonths'\nimport { addWeeks } from 'date-fns/addWeeks'\nimport { differenceInCalendarDays } from 'date-fns/differenceInCalendarDays'\nimport { endOfDay } from 'date-fns/endOfDay'\nimport { endOfMonth } from 'date-fns/endOfMonth'\nimport { endOfWeek } from 'date-fns/endOfWeek'\nimport { format } from 'date-fns/format'\nimport { startOfDay } from 'date-fns/startOfDay'\nimport { startOfMonth } from 'date-fns/startOfMonth'\nimport { startOfWeek } from 'date-fns/startOfWeek'\nimport { enUS } from 'date-fns/locale/en-US'\nimport { ChevronLeft, ChevronRight } from 'lucide-react'\n\nconst VIEW_OPTIONS: Array<{ id: ScheduleViewMode; labelKey: string; fallback: string }> = [\n { id: 'day', labelKey: 'schedule.view.day', fallback: 'Day' },\n { id: 'week', labelKey: 'schedule.view.week', fallback: 'Week' },\n { id: 'month', labelKey: 'schedule.view.month', fallback: 'Month' },\n { id: 'agenda', labelKey: 'schedule.view.agenda', fallback: 'Agenda' },\n]\n\nfunction getTimezoneOptions(current?: string): string[] {\n const options = new Set<string>()\n if (current) options.add(current)\n options.add('UTC')\n const resolved = Intl.DateTimeFormat().resolvedOptions().timeZone\n if (resolved) options.add(resolved)\n const intlWithSupportedValues = Intl as typeof Intl & { supportedValuesOf?: (input: 'timeZone') => string[] }\n if (typeof intlWithSupportedValues.supportedValuesOf === 'function') {\n intlWithSupportedValues.supportedValuesOf('timeZone').forEach((timezone) => {\n if (timezone) options.add(timezone)\n })\n }\n return Array.from(options).sort((a, b) => a.localeCompare(b))\n}\n\nexport type ScheduleToolbarProps = {\n view: ScheduleViewMode\n range: ScheduleRange\n timezone?: string\n onViewChange: (view: ScheduleViewMode) => void\n onRangeChange: (range: ScheduleRange) => void\n onTimezoneChange?: (timezone: string) => void\n className?: string\n}\n\nexport function ScheduleToolbar({\n view,\n range,\n timezone,\n onViewChange,\n onRangeChange,\n onTimezoneChange,\n className,\n}: ScheduleToolbarProps) {\n const t = useT()\n const rangeLength = React.useMemo(\n () => Math.max(1, differenceInCalendarDays(range.end, range.start) + 1),\n [range.end, range.start],\n )\n const deriveRangeForView = React.useCallback((base: Date, nextView: ScheduleViewMode): ScheduleRange => {\n if (nextView === 'day') {\n const start = startOfDay(base)\n return { start, end: endOfDay(start) }\n }\n if (nextView === 'week') {\n return { start: startOfWeek(base, { locale: enUS }), end: endOfWeek(base, { locale: enUS }) }\n }\n if (nextView === 'month') {\n return { start: startOfMonth(base), end: endOfMonth(base) }\n }\n const start = startOfDay(base)\n return { start, end: endOfDay(addDays(start, rangeLength - 1)) }\n }, [rangeLength])\n const rangeLabel = React.useMemo(() => {\n if (view === 'day') {\n return format(range.start, 'EEE, MMM d')\n }\n if (view === 'week') {\n const startLabel = format(range.start, 'MMM d')\n const endLabel = format(range.end, 'MMM d')\n const yearLabel = format(range.start, 'yyyy')\n return `${startLabel} - ${endLabel}, ${yearLabel}`\n }\n if (view === 'month') {\n return format(range.start, 'MMMM yyyy')\n }\n const startLabel = format(range.start, 'MMM d')\n const endLabel = format(range.end, 'MMM d, yyyy')\n return `${startLabel} - ${endLabel}`\n }, [range.end, range.start, view])\n\n const shiftRange = React.useCallback((direction: 'prev' | 'next') => {\n const multiplier = direction === 'prev' ? -1 : 1\n if (view === 'day') {\n const nextStart = startOfDay(addDays(range.start, multiplier))\n onRangeChange({ start: nextStart, end: endOfDay(nextStart) })\n return\n }\n if (view === 'week') {\n const base = addWeeks(range.start, multiplier)\n onRangeChange({\n start: startOfWeek(base, { locale: enUS }),\n end: endOfWeek(base, { locale: enUS }),\n })\n return\n }\n if (view === 'month') {\n const base = addMonths(range.start, multiplier)\n onRangeChange({ start: startOfMonth(base), end: endOfMonth(base) })\n return\n }\n const nextStart = startOfDay(addDays(range.start, multiplier * rangeLength))\n onRangeChange({ start: nextStart, end: endOfDay(addDays(nextStart, rangeLength - 1)) })\n }, [onRangeChange, range.start, rangeLength, view])\n\n const timezoneOptions = React.useMemo(() => getTimezoneOptions(timezone), [timezone])\n\n return (\n <div className={cn('flex flex-wrap items-center gap-x-4 gap-y-2 rounded-xl border bg-card p-3', className)}>\n <SegmentedControl\n value={view}\n onValueChange={(value) => {\n const nextView = value as ScheduleViewMode\n if (nextView === view) return\n onViewChange(nextView)\n onRangeChange(deriveRangeForView(new Date(), nextView))\n }}\n aria-label={t('schedule.view.label', 'Schedule view')}\n className=\"shrink-0\"\n >\n {VIEW_OPTIONS.map((option) => (\n <SegmentedControlItem key={option.id} value={option.id}>\n {t(option.labelKey, option.fallback)}\n </SegmentedControlItem>\n ))}\n </SegmentedControl>\n <div className=\"flex shrink-0 items-center gap-1\">\n <IconButton\n type=\"button\"\n variant=\"outline\"\n onClick={() => shiftRange('prev')}\n aria-label={t('schedule.range.prev', 'Previous')}\n >\n <ChevronLeft className=\"size-4\" aria-hidden />\n </IconButton>\n <div className=\"min-w-0 whitespace-nowrap px-1 text-sm font-medium text-foreground\">{rangeLabel}</div>\n <IconButton\n type=\"button\"\n variant=\"outline\"\n onClick={() => shiftRange('next')}\n aria-label={t('schedule.range.next', 'Next')}\n >\n <ChevronRight className=\"size-4\" aria-hidden />\n </IconButton>\n </div>\n <div className=\"flex min-w-0 flex-wrap items-center gap-x-3 gap-y-2 md:ml-auto\">\n <DateRangePicker\n value={range}\n onChange={(next) => {\n if (!next) return\n onRangeChange({ start: startOfDay(next.start), end: endOfDay(next.end) })\n }}\n size=\"sm\"\n showPresets={false}\n numberOfMonths={2}\n aria-label={t('schedule.range.label', 'Date range')}\n />\n {onTimezoneChange ? (\n <Select value={timezone ?? undefined} onValueChange={onTimezoneChange}>\n <SelectTrigger\n size=\"sm\"\n className=\"w-auto min-w-[10rem]\"\n aria-label={t('schedule.range.timezone', 'Timezone')}\n >\n <SelectValue placeholder={t('schedule.range.timezone.placeholder', 'UTC')} />\n </SelectTrigger>\n <SelectContent>\n {timezoneOptions.map((option) => (\n <SelectItem key={option} value={option}>\n {option}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n ) : null}\n </div>\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["start", "startLabel", "endLabel", "nextStart"]
7
7
  }
package/jest.config.cjs CHANGED
@@ -16,7 +16,8 @@ module.exports = {
16
16
  '^remark-gfm$': '<rootDir>/jest.markdown-mock.tsx',
17
17
  },
18
18
  transform: {
19
- '^.+\\.(t|j)sx?$': [
19
+ // `.cjs` is included so the build-time source emitters under scripts/ are testable.
20
+ '^.+\\.(cjs|(t|j)sx?)$': [
20
21
  '<rootDir>/../../scripts/jest-mikroorm-transformer.cjs',
21
22
  {
22
23
  tsconfig: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/ui",
3
- "version": "0.6.7-develop.6785.1.1dd7cfac55",
3
+ "version": "0.6.7-develop.6795.1.8a3f27921c",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -137,31 +137,31 @@
137
137
  "@dnd-kit/sortable": "^10.0.0",
138
138
  "@dnd-kit/utilities": "^3.2.2",
139
139
  "@mdxeditor/editor": "^4.0.4",
140
- "@radix-ui/react-accordion": "^1.2.16",
141
- "@radix-ui/react-dialog": "^1.1.19",
142
- "@radix-ui/react-popover": "^1.1.19",
143
- "@radix-ui/react-radio-group": "^1.4.3",
144
- "@radix-ui/react-scroll-area": "^1.2.14",
145
- "@radix-ui/react-select": "^2.3.3",
146
- "@radix-ui/react-slider": "^1.4.3",
147
- "@radix-ui/react-tooltip": "^1.2.12",
140
+ "@radix-ui/react-accordion": "^1.2.20",
141
+ "@radix-ui/react-dialog": "^1.1.23",
142
+ "@radix-ui/react-popover": "^1.1.23",
143
+ "@radix-ui/react-radio-group": "^1.4.7",
144
+ "@radix-ui/react-scroll-area": "^1.2.18",
145
+ "@radix-ui/react-select": "^2.3.7",
146
+ "@radix-ui/react-slider": "^1.4.7",
147
+ "@radix-ui/react-tooltip": "^1.2.16",
148
148
  "@tanstack/react-virtual": "^3.14.6",
149
149
  "cmdk": "^1.0.0",
150
150
  "date-fns": "4.4.0",
151
151
  "react-big-calendar": "^1.20.0",
152
152
  "react-day-picker": "^10.0.1",
153
153
  "react-markdown": "^10.1.0",
154
- "recharts": "^3.9.2",
154
+ "recharts": "^3.10.1",
155
155
  "remark-gfm": "^4.0.1"
156
156
  },
157
157
  "peerDependencies": {
158
- "@open-mercato/shared": "0.6.7-develop.6785.1.1dd7cfac55",
158
+ "@open-mercato/shared": "0.6.7-develop.6795.1.8a3f27921c",
159
159
  "react": ">=18.0.0",
160
160
  "react-dom": ">=18.0.0",
161
161
  "react-is": ">=18.0.0"
162
162
  },
163
163
  "devDependencies": {
164
- "@open-mercato/shared": "0.6.7-develop.6785.1.1dd7cfac55",
164
+ "@open-mercato/shared": "0.6.7-develop.6795.1.8a3f27921c",
165
165
  "@testing-library/dom": "^10.4.1",
166
166
  "@testing-library/jest-dom": "^6.9.1",
167
167
  "@testing-library/react": "^16.3.1",
@@ -170,10 +170,11 @@
170
170
  "@types/react-dom": "^19.2.3",
171
171
  "jest": "^30.4.2",
172
172
  "jest-environment-jsdom": "^30.4.1",
173
- "react": "19.2.7",
174
- "react-dom": "19.2.7",
175
- "react-is": "^19.2.7",
176
- "ts-jest": "^29.4.11",
173
+ "react": "19.2.8",
174
+ "react-dom": "19.2.8",
175
+ "react-is": "^19.2.8",
176
+ "ts-jest": "^29.4.12",
177
+ "ts-morph": "^28.0.0",
177
178
  "typescript": "7.0.2"
178
179
  },
179
180
  "publishConfig": {
@@ -0,0 +1,133 @@
1
+ // Pure source emitter for src/backend/icons/lucideRegistry.generated.tsx.
2
+ // Kept side-effect free, and CommonJS so both build.mjs and the Jest suite can load it.
3
+
4
+ const {
5
+ IndentationText,
6
+ Project,
7
+ QuoteKind,
8
+ ScriptKind,
9
+ StructureKind,
10
+ VariableDeclarationKind,
11
+ } = require('ts-morph')
12
+
13
+ const GENERATED_FILE_NAME = 'lucideRegistry.generated.tsx'
14
+ const LUCIDE_MODULE_SPECIFIER = 'lucide-react'
15
+ const BANNER = `/* eslint-disable */
16
+ // AUTO-GENERATED by @open-mercato/ui build. Do not edit by hand.
17
+ // Generated from discovered icon string usages across the repo.
18
+
19
+ `
20
+
21
+ let sharedProject = null
22
+
23
+ function getProject() {
24
+ if (!sharedProject) {
25
+ sharedProject = new Project({
26
+ useInMemoryFileSystem: true,
27
+ manipulationSettings: {
28
+ indentationText: IndentationText.TwoSpaces,
29
+ quoteKind: QuoteKind.Single,
30
+ useTrailingCommas: true,
31
+ },
32
+ })
33
+ }
34
+
35
+ return sharedProject
36
+ }
37
+
38
+ function sortResolvedIcons(resolvedIcons) {
39
+ return [...resolvedIcons].sort((a, b) => a.kebab.localeCompare(b.kebab))
40
+ }
41
+
42
+ function collectUniqueExports(resolvedIcons) {
43
+ return [...new Set(resolvedIcons.map((icon) => icon.exportName))].sort((a, b) => a.localeCompare(b))
44
+ }
45
+
46
+ function writeNamedImports(exportNames) {
47
+ return (writer) => {
48
+ writer.newLine()
49
+ for (const name of exportNames) {
50
+ writer.write(`${name},`)
51
+ writer.newLine()
52
+ }
53
+ }
54
+ }
55
+
56
+ function writeRegistryLiteral(entries) {
57
+ return (writer) => {
58
+ if (entries.length === 0) {
59
+ writer.write('{}')
60
+ return
61
+ }
62
+
63
+ writer.write('{')
64
+ writer.newLine()
65
+ for (const icon of entries) {
66
+ writer.write(`'${icon.kebab}': ${icon.exportName},`)
67
+ writer.newLine()
68
+ }
69
+ writer.write('}')
70
+ }
71
+ }
72
+
73
+ function assertNoSyntacticDiagnostics(project, sourceFile) {
74
+ const diagnostics = project.getProgram().getSyntacticDiagnostics(sourceFile)
75
+ if (diagnostics.length === 0) return
76
+
77
+ const details = diagnostics
78
+ .map((diagnostic) => ` line ${diagnostic.getLineNumber() ?? 0}: ${diagnostic.getMessageText()}`)
79
+ .join('\n')
80
+ throw new Error(`[internal] Generated ${GENERATED_FILE_NAME} is not syntactically valid:\n${details}`)
81
+ }
82
+
83
+ function normalizeSourceText(text) {
84
+ const normalized = text.replace(/\r\n/g, '\n')
85
+ return normalized.endsWith('\n') ? normalized : `${normalized}\n`
86
+ }
87
+
88
+ function buildLucideRegistrySource(resolvedIcons) {
89
+ const entries = sortResolvedIcons(resolvedIcons)
90
+ const uniqueExports = collectUniqueExports(entries)
91
+
92
+ const project = getProject()
93
+ const sourceFile = project.createSourceFile(GENERATED_FILE_NAME, '', {
94
+ overwrite: true,
95
+ scriptKind: ScriptKind.TSX,
96
+ })
97
+
98
+ sourceFile.addImportDeclaration({
99
+ kind: StructureKind.ImportDeclaration,
100
+ isTypeOnly: true,
101
+ namedImports: [{ name: 'LucideIcon' }],
102
+ moduleSpecifier: LUCIDE_MODULE_SPECIFIER,
103
+ })
104
+
105
+ if (uniqueExports.length > 0) {
106
+ sourceFile.addImportDeclaration({
107
+ kind: StructureKind.ImportDeclaration,
108
+ namedImports: writeNamedImports(uniqueExports),
109
+ moduleSpecifier: LUCIDE_MODULE_SPECIFIER,
110
+ })
111
+ }
112
+
113
+ sourceFile.addVariableStatement({
114
+ kind: StructureKind.VariableStatement,
115
+ isExported: true,
116
+ declarationKind: VariableDeclarationKind.Const,
117
+ declarations: [
118
+ {
119
+ name: 'LUCIDE_ICON_REGISTRY',
120
+ type: 'Record<string, LucideIcon>',
121
+ initializer: writeRegistryLiteral(entries),
122
+ },
123
+ ],
124
+ })
125
+
126
+ sourceFile.formatText({ indentSize: 2, convertTabsToSpaces: true })
127
+ sourceFile.insertText(0, BANNER)
128
+ assertNoSyntacticDiagnostics(project, sourceFile)
129
+
130
+ return normalizeSourceText(sourceFile.getFullText())
131
+ }
132
+
133
+ module.exports = { buildLucideRegistrySource }
@@ -42,16 +42,16 @@ type NextStepCalloutProps = {
42
42
  const STEP_STYLES: Record<NonNullable<NextStepCalloutStep['state']>, string> = {
43
43
  pending: '',
44
44
  active: 'border-primary/40 bg-primary/10 text-primary',
45
- completed: 'border-emerald-500/30 bg-emerald-500/10 text-emerald-200',
45
+ completed: 'border-status-success-border bg-status-success-bg text-status-success-text',
46
46
  }
47
47
 
48
48
  const STATUS_STYLES: Record<NextStepCalloutStatusTone, string> = {
49
49
  default: 'border-border bg-background/80 text-foreground',
50
50
  info: 'border-primary/20 bg-primary/5 text-primary',
51
- success: 'border-emerald-500/20 bg-emerald-500/5 text-emerald-200',
52
- warning: 'border-amber-500/20 bg-amber-500/5 text-amber-200',
51
+ success: 'border-status-success-border bg-status-success-bg text-status-success-text',
52
+ warning: 'border-status-warning-border bg-status-warning-bg text-status-warning-text',
53
53
  danger: 'border-destructive/20 bg-destructive/5 text-destructive',
54
- muted: 'border-zinc-500/20 bg-zinc-500/5 text-zinc-200',
54
+ muted: 'border-status-neutral-border bg-status-neutral-bg text-status-neutral-text',
55
55
  }
56
56
 
57
57
  export function NextStepCallout({
@@ -136,6 +136,21 @@ export function ConfirmDialog({
136
136
  return () => dialog.removeEventListener("cancel", handleCancel);
137
137
  }, [loading, requestClose]);
138
138
 
139
+ React.useEffect(() => {
140
+ if (!open) return;
141
+
142
+ const handleEscape = (event: KeyboardEvent) => {
143
+ if (event.key !== "Escape") return;
144
+ if (!(event.target instanceof Node) || !dialogRef.current?.contains(event.target)) return;
145
+ event.preventDefault();
146
+ event.stopPropagation();
147
+ if (!loading) requestClose();
148
+ };
149
+
150
+ window.addEventListener("keydown", handleEscape, true);
151
+ return () => window.removeEventListener("keydown", handleEscape, true);
152
+ }, [loading, open, requestClose]);
153
+
139
154
  // Handle backdrop click
140
155
  const handleBackdropClick = (e: React.MouseEvent<HTMLDialogElement>) => {
141
156
  // Only close if clicking directly on the dialog (backdrop), not its children
@@ -2,7 +2,7 @@
2
2
  * @jest-environment jsdom
3
3
  */
4
4
  import * as React from 'react'
5
- import { screen } from '@testing-library/react'
5
+ import { fireEvent, screen, within } from '@testing-library/react'
6
6
  import { renderWithProviders } from '@open-mercato/shared/lib/testing/renderWithProviders'
7
7
  import { ConfirmDialog } from '../ConfirmDialog'
8
8
 
@@ -60,4 +60,36 @@ describe('ConfirmDialog', () => {
60
60
  expect(container.contains(dialog)).toBe(false)
61
61
  expect(dialog.parentElement).toBe(document.body)
62
62
  })
63
+
64
+ it('closes only the confirmation that owns an Escape event', () => {
65
+ const firstOpenChange = jest.fn()
66
+ const secondOpenChange = jest.fn()
67
+
68
+ renderWithProviders(
69
+ <>
70
+ <ConfirmDialog
71
+ open
72
+ onOpenChange={firstOpenChange}
73
+ onConfirm={() => undefined}
74
+ title="First confirmation"
75
+ confirmText="Confirm first"
76
+ cancelText="Cancel first"
77
+ />
78
+ <ConfirmDialog
79
+ open
80
+ onOpenChange={secondOpenChange}
81
+ onConfirm={() => undefined}
82
+ title="Second confirmation"
83
+ confirmText="Confirm second"
84
+ cancelText="Cancel second"
85
+ />
86
+ </>,
87
+ )
88
+
89
+ const secondDialog = screen.getByRole('alertdialog', { name: 'Second confirmation' })
90
+ fireEvent.keyDown(within(secondDialog).getByRole('button', { name: 'Cancel second' }), { key: 'Escape' })
91
+
92
+ expect(firstOpenChange).not.toHaveBeenCalled()
93
+ expect(secondOpenChange).toHaveBeenCalledWith(false)
94
+ })
63
95
  })
@@ -7,7 +7,7 @@ import { flash } from '@open-mercato/ui/backend/FlashMessages'
7
7
  import { TabEmptyState } from '@open-mercato/ui/backend/detail'
8
8
  import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
9
9
  import { normalizeCoordinateInput, validateCoordinateInput } from '@open-mercato/shared/lib/location/coordinates'
10
- import { AddressView, formatAddressString, type AddressFormatStrategy } from './addressFormat'
10
+ import { AddressView, type AddressFormatStrategy } from './addressFormat'
11
11
  import AddressEditor, { type AddressTypesAdapter } from './AddressEditor'
12
12
  import {
13
13
  Dialog,
@@ -176,7 +176,7 @@ export function AddressTiles<C = unknown>({
176
176
  t,
177
177
  emptyLabel,
178
178
  isSubmitting = false,
179
- gridClassName = 'grid grid-cols-1 gap-2 sm:gap-4 sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4',
179
+ gridClassName = 'grid grid-cols-[repeat(auto-fill,minmax(240px,1fr))] gap-2 sm:gap-4',
180
180
  hideAddButton = false,
181
181
  onAddActionChange,
182
182
  emptyStateTitle,
@@ -480,7 +480,7 @@ export function AddressTiles<C = unknown>({
480
480
  addressTypesAdapter={addressTypesAdapter}
481
481
  addressTypesContext={addressTypesContext}
482
482
  />
483
- {generalError ? <p className="text-xs text-red-600">{generalError}</p> : null}
483
+ {generalError ? <p className="text-xs text-status-error-text">{generalError}</p> : null}
484
484
  <div className="flex flex-wrap justify-end gap-2">
485
485
  <Button type="button" variant="outline" onClick={handleCancel} disabled={disableActions}>
486
486
  {label('cancel', 'Cancel')}
@@ -569,9 +569,6 @@ export function AddressTiles<C = unknown>({
569
569
  </p>
570
570
  ) : null}
571
571
  <AddressView address={address} format={format} className="text-sm text-foreground" />
572
- <p className="text-xs text-muted-foreground">
573
- {formatAddressString(address, format)}
574
- </p>
575
572
  </div>
576
573
  <div className="flex items-center gap-1 opacity-100 md:opacity-0 transition-opacity md:group-hover:opacity-100 focus-within:opacity-100">
577
574
  <Button