@open-mercato/ui 0.6.2-develop.3461.1.605f31c2c9 → 0.6.2

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 (86) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/ai/AiChat.js +130 -22
  3. package/dist/ai/AiChat.js.map +2 -2
  4. package/dist/ai/AiChatSessions.js +73 -3
  5. package/dist/ai/AiChatSessions.js.map +2 -2
  6. package/dist/ai/conversation-store.js +149 -0
  7. package/dist/ai/conversation-store.js.map +7 -0
  8. package/dist/ai/parts/MutationPreviewCard.js +4 -3
  9. package/dist/ai/parts/MutationPreviewCard.js.map +2 -2
  10. package/dist/ai/parts/MutationResultCard.js +9 -16
  11. package/dist/ai/parts/MutationResultCard.js.map +2 -2
  12. package/dist/ai/useAiChat.js +80 -4
  13. package/dist/ai/useAiChat.js.map +2 -2
  14. package/dist/backend/AuthSessionGuard.js +99 -0
  15. package/dist/backend/AuthSessionGuard.js.map +7 -0
  16. package/dist/backend/CrudForm.js +2 -1
  17. package/dist/backend/CrudForm.js.map +2 -2
  18. package/dist/backend/FilterOverlay.js +1 -1
  19. package/dist/backend/FilterOverlay.js.map +1 -1
  20. package/dist/backend/dashboard/widgetRegistry.js +2 -1
  21. package/dist/backend/dashboard/widgetRegistry.js.map +2 -2
  22. package/dist/backend/date-range/dateRanges.js +16 -18
  23. package/dist/backend/date-range/dateRanges.js.map +2 -2
  24. package/dist/backend/detail/AttachmentMetadataDialog.js +3 -1
  25. package/dist/backend/detail/AttachmentMetadataDialog.js.map +2 -2
  26. package/dist/backend/detail/InlineEditors.js +1 -1
  27. package/dist/backend/detail/InlineEditors.js.map +1 -1
  28. package/dist/backend/injection/useAppEvent.js +2 -7
  29. package/dist/backend/injection/useAppEvent.js.map +2 -2
  30. package/dist/backend/injection/widgetRegistry.js +2 -1
  31. package/dist/backend/injection/widgetRegistry.js.map +2 -2
  32. package/dist/backend/messages/useMessageCompose.js +62 -5
  33. package/dist/backend/messages/useMessageCompose.js.map +2 -2
  34. package/dist/backend/messages/useMessageComposeOperations.js +64 -5
  35. package/dist/backend/messages/useMessageComposeOperations.js.map +2 -2
  36. package/dist/backend/schedule/ScheduleCalendar.js +11 -1
  37. package/dist/backend/schedule/ScheduleCalendar.js.map +2 -2
  38. package/dist/backend/schedule/ScheduleToolbar.js +11 -1
  39. package/dist/backend/schedule/ScheduleToolbar.js.map +2 -2
  40. package/dist/primitives/calendar.js +1 -1
  41. package/dist/primitives/calendar.js.map +1 -1
  42. package/dist/primitives/date-format.js +1 -1
  43. package/dist/primitives/date-format.js.map +1 -1
  44. package/dist/primitives/date-picker-helpers.js +7 -9
  45. package/dist/primitives/date-picker-helpers.js.map +2 -2
  46. package/dist/primitives/date-picker.js +1 -1
  47. package/dist/primitives/date-picker.js.map +2 -2
  48. package/dist/primitives/date-range-picker.js +1 -1
  49. package/dist/primitives/date-range-picker.js.map +2 -2
  50. package/jest.config.cjs +4 -1
  51. package/package.json +6 -7
  52. package/src/ai/AiChat.tsx +138 -22
  53. package/src/ai/AiChatSessions.tsx +89 -9
  54. package/src/ai/__tests__/AiAssistantLauncher.test.tsx +1 -1
  55. package/src/ai/__tests__/AiChat.conversation.test.tsx +86 -0
  56. package/src/ai/__tests__/AiChat.test.tsx +17 -2
  57. package/src/ai/__tests__/conversation-store.test.ts +29 -0
  58. package/src/ai/conversation-store.ts +246 -0
  59. package/src/ai/parts/MutationPreviewCard.tsx +25 -20
  60. package/src/ai/parts/MutationResultCard.tsx +15 -27
  61. package/src/ai/parts/__tests__/MutationPreviewCard.test.tsx +26 -1
  62. package/src/ai/parts/__tests__/MutationResultCard.test.tsx +16 -1
  63. package/src/ai/useAiChat.ts +97 -8
  64. package/src/backend/AuthSessionGuard.tsx +114 -0
  65. package/src/backend/CrudForm.tsx +3 -2
  66. package/src/backend/FilterOverlay.tsx +1 -1
  67. package/src/backend/__tests__/AppShell.test.tsx +1 -1
  68. package/src/backend/__tests__/AuthSessionGuard.test.tsx +150 -0
  69. package/src/backend/__tests__/SidebarCustomizationEditor.test.tsx +8 -1
  70. package/src/backend/dashboard/widgetRegistry.ts +2 -1
  71. package/src/backend/date-range/dateRanges.ts +16 -18
  72. package/src/backend/detail/AttachmentMetadataDialog.tsx +1 -1
  73. package/src/backend/detail/InlineEditors.tsx +1 -1
  74. package/src/backend/injection/__tests__/useAppEvent.test.ts +16 -0
  75. package/src/backend/injection/useAppEvent.ts +2 -9
  76. package/src/backend/injection/widgetRegistry.ts +2 -1
  77. package/src/backend/messages/__tests__/MessageComposer.test.tsx +77 -0
  78. package/src/backend/messages/useMessageCompose.ts +64 -5
  79. package/src/backend/messages/useMessageComposeOperations.ts +70 -4
  80. package/src/backend/schedule/ScheduleCalendar.tsx +11 -1
  81. package/src/backend/schedule/ScheduleToolbar.tsx +11 -1
  82. package/src/primitives/calendar.tsx +2 -2
  83. package/src/primitives/date-format.ts +2 -2
  84. package/src/primitives/date-picker-helpers.ts +7 -9
  85. package/src/primitives/date-picker.tsx +2 -2
  86. package/src/primitives/date-range-picker.tsx +2 -2
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/primitives/calendar.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { DayPicker, useDayPicker } from 'react-day-picker'\nimport type { DayPickerProps, CalendarMonth } from 'react-day-picker'\nimport { ChevronLeft, ChevronRight } from 'lucide-react'\nimport { format } from 'date-fns'\nimport type { Locale } from 'date-fns'\nimport { cn } from '@open-mercato/shared/lib/utils'\n\nexport type CalendarProps = DayPickerProps\n\nfunction MonthNavButton({\n direction,\n locale,\n}: {\n direction: 'prev' | 'next'\n locale?: Locale\n}) {\n const dayPicker = useDayPicker() as unknown as {\n previousMonth?: Date\n nextMonth?: Date\n goToMonth?: (month: Date) => void\n }\n const target = direction === 'prev' ? dayPicker.previousMonth : dayPicker.nextMonth\n const Icon = direction === 'prev' ? ChevronLeft : ChevronRight\n const targetLabel = format(target ?? new Date(), 'MMMM yyyy', locale ? { locale } : undefined)\n const ariaLabel = `Go to ${direction === 'prev' ? 'previous' : 'next'} month: ${targetLabel}`\n return (\n <button\n type=\"button\"\n disabled={!target}\n aria-label={ariaLabel}\n onClick={() => {\n if (target && dayPicker.goToMonth) dayPicker.goToMonth(target)\n }}\n className={cn(\n 'h-9 w-9 inline-flex items-center justify-center rounded-md shrink-0',\n 'border border-border bg-background text-muted-foreground transition-colors',\n 'hover:bg-accent hover:text-accent-foreground hover:border-input',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',\n 'disabled:opacity-30 disabled:pointer-events-none disabled:hover:bg-background',\n )}\n >\n <Icon className=\"h-4 w-4\" aria-hidden=\"true\" />\n </button>\n )\n}\n\nfunction buildMonthCaption(locale: Locale | undefined, totalMonths: number) {\n return function MonthCaption({\n calendarMonth,\n displayIndex,\n }: {\n calendarMonth: CalendarMonth\n displayIndex?: number\n }) {\n const label = format(calendarMonth.date, 'MMMM yyyy', locale ? { locale } : undefined)\n const index = typeof displayIndex === 'number' ? displayIndex : 0\n // For multi-month layouts (e.g. range pickers) only the leftmost month\n // exposes the previous-month chevron and only the rightmost exposes the\n // next-month chevron. Navigation is always global across all visible\n // months, so showing both arrows on every month is confusing.\n const showPrev = index === 0\n const showNext = index === totalMonths - 1\n return (\n <div className=\"flex items-center justify-between gap-2 mb-3\">\n {showPrev ? (\n <MonthNavButton direction=\"prev\" locale={locale} />\n ) : (\n <div className=\"h-9 w-9 shrink-0\" aria-hidden=\"true\" />\n )}\n <div\n className=\"flex-1 flex items-center justify-center h-9 rounded-md bg-muted px-3 text-sm font-medium\"\n aria-live=\"polite\"\n >\n {label}\n </div>\n {showNext ? (\n <MonthNavButton direction=\"next\" locale={locale} />\n ) : (\n <div className=\"h-9 w-9 shrink-0\" aria-hidden=\"true\" />\n )}\n </div>\n )\n }\n}\n\nexport function Calendar({\n className,\n classNames,\n showOutsideDays = true,\n fixedWeeks = true,\n locale,\n components,\n numberOfMonths = 1,\n pagedNavigation = true,\n ...props\n}: CalendarProps) {\n const monthCaption = React.useMemo(\n () => buildMonthCaption(locale as Locale | undefined, numberOfMonths),\n [locale, numberOfMonths],\n )\n return (\n <DayPicker\n showOutsideDays={showOutsideDays}\n fixedWeeks={fixedWeeks}\n pagedNavigation={pagedNavigation}\n locale={locale}\n numberOfMonths={numberOfMonths}\n className={cn('p-3', className)}\n classNames={{\n months: 'flex flex-col sm:flex-row gap-4',\n month: 'space-y-2',\n month_caption: '',\n caption_label: 'sr-only',\n nav: 'sr-only',\n month_grid: 'w-full border-collapse',\n weekdays: 'flex',\n weekday: 'text-muted-foreground rounded-md w-9 font-normal text-xs',\n weeks: 'w-full border-collapse',\n week: 'flex w-full mt-1',\n day: 'h-9 w-9 text-center text-sm p-0 relative focus-within:relative focus-within:z-20',\n day_button: cn(\n 'h-9 w-9 p-0 font-normal aria-selected:opacity-100',\n 'inline-flex items-center justify-center rounded-md text-sm',\n 'transition-colors focus:outline-none focus-visible:outline-none disabled:pointer-events-none',\n // Focus indicator is a soft accent fill instead of a ring overlay \u2014 keyboard\n // users get a visible cue, but mouse-click focus does not leave a stuck ring\n // on top of the selected cell.\n 'hover:bg-accent hover:text-accent-foreground',\n 'focus-visible:bg-accent focus-visible:text-accent-foreground',\n ),\n // React-day-picker v9 applies `classNames.selected` / `range_*` to the day\n // CELL (`<td>`) wrapper, not to the inner `<button>`. To keep the parent\n // fill visible through interaction, we (a) paint the cell with the desired\n // bg/text, and (b) force the inner button to render transparent \u2014 so the\n // button's own hover/focus-visible bg overrides cannot cover the cell fill.\n selected: cn(\n '!bg-primary !text-primary-foreground rounded-md',\n '[&_button]:!bg-transparent [&_button]:!text-primary-foreground',\n '[&_button:hover]:!bg-transparent [&_button:hover]:!text-primary-foreground',\n '[&_button:focus-visible]:!bg-transparent [&_button:focus-visible]:!text-primary-foreground',\n ),\n range_start: cn(\n '!bg-primary !text-primary-foreground rounded-l-md !rounded-r-none',\n '[&_button]:!bg-transparent [&_button]:!text-primary-foreground',\n '[&_button:hover]:!bg-transparent [&_button:hover]:!text-primary-foreground',\n '[&_button:focus-visible]:!bg-transparent [&_button:focus-visible]:!text-primary-foreground',\n ),\n range_end: cn(\n '!bg-primary !text-primary-foreground rounded-r-md !rounded-l-none',\n '[&_button]:!bg-transparent [&_button]:!text-primary-foreground',\n '[&_button:hover]:!bg-transparent [&_button:hover]:!text-primary-foreground',\n '[&_button:focus-visible]:!bg-transparent [&_button:focus-visible]:!text-primary-foreground',\n ),\n range_middle: cn(\n '!bg-accent !text-accent-foreground !rounded-none',\n '[&_button]:!bg-transparent [&_button]:!text-accent-foreground',\n '[&_button:hover]:!bg-transparent [&_button:hover]:!text-accent-foreground',\n '[&_button:focus-visible]:!bg-transparent [&_button:focus-visible]:!text-accent-foreground',\n ),\n today: 'font-semibold text-primary rounded-md',\n outside:\n 'day-outside text-muted-foreground opacity-40 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30',\n disabled: 'text-muted-foreground opacity-50',\n hidden: 'invisible',\n ...classNames,\n }}\n components={{\n MonthCaption: monthCaption,\n ...components,\n }}\n {...props}\n />\n )\n}\n"],
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { DayPicker, useDayPicker } from 'react-day-picker'\nimport type { DayPickerProps, CalendarMonth } from 'react-day-picker'\nimport { ChevronLeft, ChevronRight } from 'lucide-react'\nimport { format } from 'date-fns/format'\nimport type { Locale } from 'date-fns/locale'\nimport { cn } from '@open-mercato/shared/lib/utils'\n\nexport type CalendarProps = DayPickerProps\n\nfunction MonthNavButton({\n direction,\n locale,\n}: {\n direction: 'prev' | 'next'\n locale?: Locale\n}) {\n const dayPicker = useDayPicker() as unknown as {\n previousMonth?: Date\n nextMonth?: Date\n goToMonth?: (month: Date) => void\n }\n const target = direction === 'prev' ? dayPicker.previousMonth : dayPicker.nextMonth\n const Icon = direction === 'prev' ? ChevronLeft : ChevronRight\n const targetLabel = format(target ?? new Date(), 'MMMM yyyy', locale ? { locale } : undefined)\n const ariaLabel = `Go to ${direction === 'prev' ? 'previous' : 'next'} month: ${targetLabel}`\n return (\n <button\n type=\"button\"\n disabled={!target}\n aria-label={ariaLabel}\n onClick={() => {\n if (target && dayPicker.goToMonth) dayPicker.goToMonth(target)\n }}\n className={cn(\n 'h-9 w-9 inline-flex items-center justify-center rounded-md shrink-0',\n 'border border-border bg-background text-muted-foreground transition-colors',\n 'hover:bg-accent hover:text-accent-foreground hover:border-input',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',\n 'disabled:opacity-30 disabled:pointer-events-none disabled:hover:bg-background',\n )}\n >\n <Icon className=\"h-4 w-4\" aria-hidden=\"true\" />\n </button>\n )\n}\n\nfunction buildMonthCaption(locale: Locale | undefined, totalMonths: number) {\n return function MonthCaption({\n calendarMonth,\n displayIndex,\n }: {\n calendarMonth: CalendarMonth\n displayIndex?: number\n }) {\n const label = format(calendarMonth.date, 'MMMM yyyy', locale ? { locale } : undefined)\n const index = typeof displayIndex === 'number' ? displayIndex : 0\n // For multi-month layouts (e.g. range pickers) only the leftmost month\n // exposes the previous-month chevron and only the rightmost exposes the\n // next-month chevron. Navigation is always global across all visible\n // months, so showing both arrows on every month is confusing.\n const showPrev = index === 0\n const showNext = index === totalMonths - 1\n return (\n <div className=\"flex items-center justify-between gap-2 mb-3\">\n {showPrev ? (\n <MonthNavButton direction=\"prev\" locale={locale} />\n ) : (\n <div className=\"h-9 w-9 shrink-0\" aria-hidden=\"true\" />\n )}\n <div\n className=\"flex-1 flex items-center justify-center h-9 rounded-md bg-muted px-3 text-sm font-medium\"\n aria-live=\"polite\"\n >\n {label}\n </div>\n {showNext ? (\n <MonthNavButton direction=\"next\" locale={locale} />\n ) : (\n <div className=\"h-9 w-9 shrink-0\" aria-hidden=\"true\" />\n )}\n </div>\n )\n }\n}\n\nexport function Calendar({\n className,\n classNames,\n showOutsideDays = true,\n fixedWeeks = true,\n locale,\n components,\n numberOfMonths = 1,\n pagedNavigation = true,\n ...props\n}: CalendarProps) {\n const monthCaption = React.useMemo(\n () => buildMonthCaption(locale as Locale | undefined, numberOfMonths),\n [locale, numberOfMonths],\n )\n return (\n <DayPicker\n showOutsideDays={showOutsideDays}\n fixedWeeks={fixedWeeks}\n pagedNavigation={pagedNavigation}\n locale={locale}\n numberOfMonths={numberOfMonths}\n className={cn('p-3', className)}\n classNames={{\n months: 'flex flex-col sm:flex-row gap-4',\n month: 'space-y-2',\n month_caption: '',\n caption_label: 'sr-only',\n nav: 'sr-only',\n month_grid: 'w-full border-collapse',\n weekdays: 'flex',\n weekday: 'text-muted-foreground rounded-md w-9 font-normal text-xs',\n weeks: 'w-full border-collapse',\n week: 'flex w-full mt-1',\n day: 'h-9 w-9 text-center text-sm p-0 relative focus-within:relative focus-within:z-20',\n day_button: cn(\n 'h-9 w-9 p-0 font-normal aria-selected:opacity-100',\n 'inline-flex items-center justify-center rounded-md text-sm',\n 'transition-colors focus:outline-none focus-visible:outline-none disabled:pointer-events-none',\n // Focus indicator is a soft accent fill instead of a ring overlay \u2014 keyboard\n // users get a visible cue, but mouse-click focus does not leave a stuck ring\n // on top of the selected cell.\n 'hover:bg-accent hover:text-accent-foreground',\n 'focus-visible:bg-accent focus-visible:text-accent-foreground',\n ),\n // React-day-picker v9 applies `classNames.selected` / `range_*` to the day\n // CELL (`<td>`) wrapper, not to the inner `<button>`. To keep the parent\n // fill visible through interaction, we (a) paint the cell with the desired\n // bg/text, and (b) force the inner button to render transparent \u2014 so the\n // button's own hover/focus-visible bg overrides cannot cover the cell fill.\n selected: cn(\n '!bg-primary !text-primary-foreground rounded-md',\n '[&_button]:!bg-transparent [&_button]:!text-primary-foreground',\n '[&_button:hover]:!bg-transparent [&_button:hover]:!text-primary-foreground',\n '[&_button:focus-visible]:!bg-transparent [&_button:focus-visible]:!text-primary-foreground',\n ),\n range_start: cn(\n '!bg-primary !text-primary-foreground rounded-l-md !rounded-r-none',\n '[&_button]:!bg-transparent [&_button]:!text-primary-foreground',\n '[&_button:hover]:!bg-transparent [&_button:hover]:!text-primary-foreground',\n '[&_button:focus-visible]:!bg-transparent [&_button:focus-visible]:!text-primary-foreground',\n ),\n range_end: cn(\n '!bg-primary !text-primary-foreground rounded-r-md !rounded-l-none',\n '[&_button]:!bg-transparent [&_button]:!text-primary-foreground',\n '[&_button:hover]:!bg-transparent [&_button:hover]:!text-primary-foreground',\n '[&_button:focus-visible]:!bg-transparent [&_button:focus-visible]:!text-primary-foreground',\n ),\n range_middle: cn(\n '!bg-accent !text-accent-foreground !rounded-none',\n '[&_button]:!bg-transparent [&_button]:!text-accent-foreground',\n '[&_button:hover]:!bg-transparent [&_button:hover]:!text-accent-foreground',\n '[&_button:focus-visible]:!bg-transparent [&_button:focus-visible]:!text-accent-foreground',\n ),\n today: 'font-semibold text-primary rounded-md',\n outside:\n 'day-outside text-muted-foreground opacity-40 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30',\n disabled: 'text-muted-foreground opacity-50',\n hidden: 'invisible',\n ...classNames,\n }}\n components={{\n MonthCaption: monthCaption,\n ...components,\n }}\n {...props}\n />\n )\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,4 +1,4 @@
1
- import { format as formatDateFns } from "date-fns";
1
+ import { format as formatDateFns } from "date-fns/format";
2
2
  const DAY_FIRST_LOCALE_CODES = /* @__PURE__ */ new Set([
3
3
  "pl",
4
4
  "de",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/primitives/date-format.ts"],
4
- "sourcesContent": ["import { format as formatDateFns } from 'date-fns'\nimport type { Locale } from 'date-fns'\n\ntype LocaleLike = Locale | string | null | undefined\n\nconst DAY_FIRST_LOCALE_CODES = new Set([\n 'pl', 'de', 'fr', 'es', 'it', 'pt', 'nl', 'ru', 'cs', 'sk', 'hu', 'ro',\n])\n\nconst SYSTEM_FORMAT_VALUES = new Set(['auto', 'default', 'locale', 'system'])\n\nfunction getLocaleCode(locale?: LocaleLike): string {\n if (!locale) return ''\n if (typeof locale === 'string') return locale.split('-')[0]?.toLowerCase() ?? ''\n return locale.code?.split('-')[0]?.toLowerCase() ?? ''\n}\n\nexport function normalizeDateFormatPattern(value: string | null | undefined): string | null {\n const trimmed = value?.trim()\n if (!trimmed) return null\n if (SYSTEM_FORMAT_VALUES.has(trimmed.toLowerCase())) return null\n return trimmed\n .replace(/YYYY/g, 'yyyy')\n .replace(/YY/g, 'yy')\n .replace(/DD/g, 'dd')\n}\n\nexport function deriveDateDisplayFormat(locale?: LocaleLike): string {\n const code = getLocaleCode(locale)\n return code && DAY_FIRST_LOCALE_CODES.has(code) ? 'd MMM yyyy' : 'MMM d, yyyy'\n}\n\nexport function resolvePublicDateFormat(locale?: LocaleLike, explicitFormat?: string | null): string {\n return (\n normalizeDateFormatPattern(explicitFormat)\n ?? normalizeDateFormatPattern(process.env.NEXT_PUBLIC_OM_DATE_FORMAT)\n ?? normalizeDateFormatPattern(process.env.NEXT_PUBLIC_DATE_FORMAT)\n ?? deriveDateDisplayFormat(locale)\n )\n}\n\nexport function resolvePublicDateTimeFormat(locale?: LocaleLike, explicitFormat?: string | null): string {\n const dateFormat = deriveDateDisplayFormat(locale)\n return (\n normalizeDateFormatPattern(explicitFormat)\n ?? normalizeDateFormatPattern(process.env.NEXT_PUBLIC_OM_DATE_TIME_FORMAT)\n ?? normalizeDateFormatPattern(process.env.NEXT_PUBLIC_DATE_TIME_FORMAT)\n ?? normalizeDateFormatPattern(process.env.NEXT_PUBLIC_OM_DATE_FORMAT)\n ?? normalizeDateFormatPattern(process.env.NEXT_PUBLIC_DATE_FORMAT)\n ?? `${dateFormat} HH:mm`\n )\n}\n\nexport function formatWithPublicDateFormat(date: Date, format: string, locale?: Locale): string | null {\n try {\n return formatDateFns(date, format, locale ? { locale } : undefined)\n } catch {\n return null\n }\n}\n"],
4
+ "sourcesContent": ["import { format as formatDateFns } from 'date-fns/format'\nimport type { Locale } from 'date-fns/locale'\n\ntype LocaleLike = Locale | string | null | undefined\n\nconst DAY_FIRST_LOCALE_CODES = new Set([\n 'pl', 'de', 'fr', 'es', 'it', 'pt', 'nl', 'ru', 'cs', 'sk', 'hu', 'ro',\n])\n\nconst SYSTEM_FORMAT_VALUES = new Set(['auto', 'default', 'locale', 'system'])\n\nfunction getLocaleCode(locale?: LocaleLike): string {\n if (!locale) return ''\n if (typeof locale === 'string') return locale.split('-')[0]?.toLowerCase() ?? ''\n return locale.code?.split('-')[0]?.toLowerCase() ?? ''\n}\n\nexport function normalizeDateFormatPattern(value: string | null | undefined): string | null {\n const trimmed = value?.trim()\n if (!trimmed) return null\n if (SYSTEM_FORMAT_VALUES.has(trimmed.toLowerCase())) return null\n return trimmed\n .replace(/YYYY/g, 'yyyy')\n .replace(/YY/g, 'yy')\n .replace(/DD/g, 'dd')\n}\n\nexport function deriveDateDisplayFormat(locale?: LocaleLike): string {\n const code = getLocaleCode(locale)\n return code && DAY_FIRST_LOCALE_CODES.has(code) ? 'd MMM yyyy' : 'MMM d, yyyy'\n}\n\nexport function resolvePublicDateFormat(locale?: LocaleLike, explicitFormat?: string | null): string {\n return (\n normalizeDateFormatPattern(explicitFormat)\n ?? normalizeDateFormatPattern(process.env.NEXT_PUBLIC_OM_DATE_FORMAT)\n ?? normalizeDateFormatPattern(process.env.NEXT_PUBLIC_DATE_FORMAT)\n ?? deriveDateDisplayFormat(locale)\n )\n}\n\nexport function resolvePublicDateTimeFormat(locale?: LocaleLike, explicitFormat?: string | null): string {\n const dateFormat = deriveDateDisplayFormat(locale)\n return (\n normalizeDateFormatPattern(explicitFormat)\n ?? normalizeDateFormatPattern(process.env.NEXT_PUBLIC_OM_DATE_TIME_FORMAT)\n ?? normalizeDateFormatPattern(process.env.NEXT_PUBLIC_DATE_TIME_FORMAT)\n ?? normalizeDateFormatPattern(process.env.NEXT_PUBLIC_OM_DATE_FORMAT)\n ?? normalizeDateFormatPattern(process.env.NEXT_PUBLIC_DATE_FORMAT)\n ?? `${dateFormat} HH:mm`\n )\n}\n\nexport function formatWithPublicDateFormat(date: Date, format: string, locale?: Locale): string | null {\n try {\n return formatDateFns(date, format, locale ? { locale } : undefined)\n } catch {\n return null\n }\n}\n"],
5
5
  "mappings": "AAAA,SAAS,UAAU,qBAAqB;AAKxC,MAAM,yBAAyB,oBAAI,IAAI;AAAA,EACrC;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AACpE,CAAC;AAED,MAAM,uBAAuB,oBAAI,IAAI,CAAC,QAAQ,WAAW,UAAU,QAAQ,CAAC;AAE5E,SAAS,cAAc,QAA6B;AAClD,MAAI,CAAC,OAAQ,QAAO;AACpB,MAAI,OAAO,WAAW,SAAU,QAAO,OAAO,MAAM,GAAG,EAAE,CAAC,GAAG,YAAY,KAAK;AAC9E,SAAO,OAAO,MAAM,MAAM,GAAG,EAAE,CAAC,GAAG,YAAY,KAAK;AACtD;AAEO,SAAS,2BAA2B,OAAiD;AAC1F,QAAM,UAAU,OAAO,KAAK;AAC5B,MAAI,CAAC,QAAS,QAAO;AACrB,MAAI,qBAAqB,IAAI,QAAQ,YAAY,CAAC,EAAG,QAAO;AAC5D,SAAO,QACJ,QAAQ,SAAS,MAAM,EACvB,QAAQ,OAAO,IAAI,EACnB,QAAQ,OAAO,IAAI;AACxB;AAEO,SAAS,wBAAwB,QAA6B;AACnE,QAAM,OAAO,cAAc,MAAM;AACjC,SAAO,QAAQ,uBAAuB,IAAI,IAAI,IAAI,eAAe;AACnE;AAEO,SAAS,wBAAwB,QAAqB,gBAAwC;AACnG,SACE,2BAA2B,cAAc,KACtC,2BAA2B,QAAQ,IAAI,0BAA0B,KACjE,2BAA2B,QAAQ,IAAI,uBAAuB,KAC9D,wBAAwB,MAAM;AAErC;AAEO,SAAS,4BAA4B,QAAqB,gBAAwC;AACvG,QAAM,aAAa,wBAAwB,MAAM;AACjD,SACE,2BAA2B,cAAc,KACtC,2BAA2B,QAAQ,IAAI,+BAA+B,KACtE,2BAA2B,QAAQ,IAAI,4BAA4B,KACnE,2BAA2B,QAAQ,IAAI,0BAA0B,KACjE,2BAA2B,QAAQ,IAAI,uBAAuB,KAC9D,GAAG,UAAU;AAEpB;AAEO,SAAS,2BAA2B,MAAY,QAAgB,QAAgC;AACrG,MAAI;AACF,WAAO,cAAc,MAAM,QAAQ,SAAS,EAAE,OAAO,IAAI,MAAS;AAAA,EACpE,QAAQ;AACN,WAAO;AAAA,EACT;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,12 +1,10 @@
1
- import {
2
- startOfDay,
3
- endOfDay,
4
- startOfMonth,
5
- startOfYear,
6
- subDays,
7
- subMonths,
8
- subYears
9
- } from "date-fns";
1
+ import { endOfDay } from "date-fns/endOfDay";
2
+ import { startOfDay } from "date-fns/startOfDay";
3
+ import { startOfMonth } from "date-fns/startOfMonth";
4
+ import { startOfYear } from "date-fns/startOfYear";
5
+ import { subDays } from "date-fns/subDays";
6
+ import { subMonths } from "date-fns/subMonths";
7
+ import { subYears } from "date-fns/subYears";
10
8
  function defaultDateRangePresets() {
11
9
  return [
12
10
  {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/primitives/date-picker-helpers.ts"],
4
- "sourcesContent": ["import {\n startOfDay,\n endOfDay,\n startOfMonth,\n startOfYear,\n subDays,\n subMonths,\n subYears,\n} from 'date-fns'\nimport type { DateRange } from '../backend/date-range/dateRanges'\n\nexport type DateRangePresetItem = {\n id: string\n labelKey: string\n range: (referenceDate?: Date) => DateRange\n}\n\n/**\n * Returns the canonical 8 date-range presets aligned with the Figma\n * Range Picker spec (`446:7412` Period Range track).\n *\n * The list is intentionally shorter than `DATE_RANGE_OPTIONS` from\n * `@open-mercato/ui/backend/date-range`: that constant powers\n * dashboard/analytics modules with 13 presets (today, yesterday,\n * this/last week/month/quarter/year, last 7/30/90 days), but the Figma\n * Range Picker UI exposes 8 options optimised for filter UX (today plus\n * sliding windows and month/year-to-date).\n *\n * Used by `<DateRangePicker>` as the default `presets` value.\n */\nexport function defaultDateRangePresets(): DateRangePresetItem[] {\n return [\n {\n id: 'today',\n labelKey: 'ui.dateRangePicker.presets.today',\n range: (ref = new Date()) => ({ start: startOfDay(ref), end: endOfDay(ref) }),\n },\n {\n id: 'last_7_days',\n labelKey: 'ui.dateRangePicker.presets.last7Days',\n range: (ref = new Date()) => ({\n start: startOfDay(subDays(ref, 6)),\n end: endOfDay(ref),\n }),\n },\n {\n id: 'last_30_days',\n labelKey: 'ui.dateRangePicker.presets.last30Days',\n range: (ref = new Date()) => ({\n start: startOfDay(subDays(ref, 29)),\n end: endOfDay(ref),\n }),\n },\n {\n id: 'last_3_months',\n labelKey: 'ui.dateRangePicker.presets.last3Months',\n range: (ref = new Date()) => ({\n start: startOfDay(subMonths(ref, 3)),\n end: endOfDay(ref),\n }),\n },\n {\n id: 'last_12_months',\n labelKey: 'ui.dateRangePicker.presets.last12Months',\n range: (ref = new Date()) => ({\n start: startOfDay(subYears(ref, 1)),\n end: endOfDay(ref),\n }),\n },\n {\n id: 'month_to_date',\n labelKey: 'ui.dateRangePicker.presets.monthToDate',\n range: (ref = new Date()) => ({\n start: startOfMonth(ref),\n end: endOfDay(ref),\n }),\n },\n {\n id: 'year_to_date',\n labelKey: 'ui.dateRangePicker.presets.yearToDate',\n range: (ref = new Date()) => ({\n start: startOfYear(ref),\n end: endOfDay(ref),\n }),\n },\n {\n id: 'all_time',\n labelKey: 'ui.dateRangePicker.presets.allTime',\n range: (ref = new Date()) => ({\n // Symbolic \"all time\": from epoch to end of reference day.\n start: new Date(0),\n end: endOfDay(ref),\n }),\n },\n ]\n}\n"],
5
- "mappings": "AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAsBA,SAAS,0BAAiD;AAC/D,SAAO;AAAA,IACL;AAAA,MACE,IAAI;AAAA,MACJ,UAAU;AAAA,MACV,OAAO,CAAC,MAAM,oBAAI,KAAK,OAAO,EAAE,OAAO,WAAW,GAAG,GAAG,KAAK,SAAS,GAAG,EAAE;AAAA,IAC7E;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,UAAU;AAAA,MACV,OAAO,CAAC,MAAM,oBAAI,KAAK,OAAO;AAAA,QAC5B,OAAO,WAAW,QAAQ,KAAK,CAAC,CAAC;AAAA,QACjC,KAAK,SAAS,GAAG;AAAA,MACnB;AAAA,IACF;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,UAAU;AAAA,MACV,OAAO,CAAC,MAAM,oBAAI,KAAK,OAAO;AAAA,QAC5B,OAAO,WAAW,QAAQ,KAAK,EAAE,CAAC;AAAA,QAClC,KAAK,SAAS,GAAG;AAAA,MACnB;AAAA,IACF;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,UAAU;AAAA,MACV,OAAO,CAAC,MAAM,oBAAI,KAAK,OAAO;AAAA,QAC5B,OAAO,WAAW,UAAU,KAAK,CAAC,CAAC;AAAA,QACnC,KAAK,SAAS,GAAG;AAAA,MACnB;AAAA,IACF;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,UAAU;AAAA,MACV,OAAO,CAAC,MAAM,oBAAI,KAAK,OAAO;AAAA,QAC5B,OAAO,WAAW,SAAS,KAAK,CAAC,CAAC;AAAA,QAClC,KAAK,SAAS,GAAG;AAAA,MACnB;AAAA,IACF;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,UAAU;AAAA,MACV,OAAO,CAAC,MAAM,oBAAI,KAAK,OAAO;AAAA,QAC5B,OAAO,aAAa,GAAG;AAAA,QACvB,KAAK,SAAS,GAAG;AAAA,MACnB;AAAA,IACF;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,UAAU;AAAA,MACV,OAAO,CAAC,MAAM,oBAAI,KAAK,OAAO;AAAA,QAC5B,OAAO,YAAY,GAAG;AAAA,QACtB,KAAK,SAAS,GAAG;AAAA,MACnB;AAAA,IACF;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,UAAU;AAAA,MACV,OAAO,CAAC,MAAM,oBAAI,KAAK,OAAO;AAAA;AAAA,QAE5B,OAAO,oBAAI,KAAK,CAAC;AAAA,QACjB,KAAK,SAAS,GAAG;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import { endOfDay } from 'date-fns/endOfDay'\nimport { startOfDay } from 'date-fns/startOfDay'\nimport { startOfMonth } from 'date-fns/startOfMonth'\nimport { startOfYear } from 'date-fns/startOfYear'\nimport { subDays } from 'date-fns/subDays'\nimport { subMonths } from 'date-fns/subMonths'\nimport { subYears } from 'date-fns/subYears'\nimport type { DateRange } from '../backend/date-range/dateRanges'\n\nexport type DateRangePresetItem = {\n id: string\n labelKey: string\n range: (referenceDate?: Date) => DateRange\n}\n\n/**\n * Returns the canonical 8 date-range presets aligned with the Figma\n * Range Picker spec (`446:7412` Period Range track).\n *\n * The list is intentionally shorter than `DATE_RANGE_OPTIONS` from\n * `@open-mercato/ui/backend/date-range`: that constant powers\n * dashboard/analytics modules with 13 presets (today, yesterday,\n * this/last week/month/quarter/year, last 7/30/90 days), but the Figma\n * Range Picker UI exposes 8 options optimised for filter UX (today plus\n * sliding windows and month/year-to-date).\n *\n * Used by `<DateRangePicker>` as the default `presets` value.\n */\nexport function defaultDateRangePresets(): DateRangePresetItem[] {\n return [\n {\n id: 'today',\n labelKey: 'ui.dateRangePicker.presets.today',\n range: (ref = new Date()) => ({ start: startOfDay(ref), end: endOfDay(ref) }),\n },\n {\n id: 'last_7_days',\n labelKey: 'ui.dateRangePicker.presets.last7Days',\n range: (ref = new Date()) => ({\n start: startOfDay(subDays(ref, 6)),\n end: endOfDay(ref),\n }),\n },\n {\n id: 'last_30_days',\n labelKey: 'ui.dateRangePicker.presets.last30Days',\n range: (ref = new Date()) => ({\n start: startOfDay(subDays(ref, 29)),\n end: endOfDay(ref),\n }),\n },\n {\n id: 'last_3_months',\n labelKey: 'ui.dateRangePicker.presets.last3Months',\n range: (ref = new Date()) => ({\n start: startOfDay(subMonths(ref, 3)),\n end: endOfDay(ref),\n }),\n },\n {\n id: 'last_12_months',\n labelKey: 'ui.dateRangePicker.presets.last12Months',\n range: (ref = new Date()) => ({\n start: startOfDay(subYears(ref, 1)),\n end: endOfDay(ref),\n }),\n },\n {\n id: 'month_to_date',\n labelKey: 'ui.dateRangePicker.presets.monthToDate',\n range: (ref = new Date()) => ({\n start: startOfMonth(ref),\n end: endOfDay(ref),\n }),\n },\n {\n id: 'year_to_date',\n labelKey: 'ui.dateRangePicker.presets.yearToDate',\n range: (ref = new Date()) => ({\n start: startOfYear(ref),\n end: endOfDay(ref),\n }),\n },\n {\n id: 'all_time',\n labelKey: 'ui.dateRangePicker.presets.allTime',\n range: (ref = new Date()) => ({\n // Symbolic \"all time\": from epoch to end of reference day.\n start: new Date(0),\n end: endOfDay(ref),\n }),\n },\n ]\n}\n"],
5
+ "mappings": "AAAA,SAAS,gBAAgB;AACzB,SAAS,kBAAkB;AAC3B,SAAS,oBAAoB;AAC7B,SAAS,mBAAmB;AAC5B,SAAS,eAAe;AACxB,SAAS,iBAAiB;AAC1B,SAAS,gBAAgB;AAsBlB,SAAS,0BAAiD;AAC/D,SAAO;AAAA,IACL;AAAA,MACE,IAAI;AAAA,MACJ,UAAU;AAAA,MACV,OAAO,CAAC,MAAM,oBAAI,KAAK,OAAO,EAAE,OAAO,WAAW,GAAG,GAAG,KAAK,SAAS,GAAG,EAAE;AAAA,IAC7E;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,UAAU;AAAA,MACV,OAAO,CAAC,MAAM,oBAAI,KAAK,OAAO;AAAA,QAC5B,OAAO,WAAW,QAAQ,KAAK,CAAC,CAAC;AAAA,QACjC,KAAK,SAAS,GAAG;AAAA,MACnB;AAAA,IACF;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,UAAU;AAAA,MACV,OAAO,CAAC,MAAM,oBAAI,KAAK,OAAO;AAAA,QAC5B,OAAO,WAAW,QAAQ,KAAK,EAAE,CAAC;AAAA,QAClC,KAAK,SAAS,GAAG;AAAA,MACnB;AAAA,IACF;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,UAAU;AAAA,MACV,OAAO,CAAC,MAAM,oBAAI,KAAK,OAAO;AAAA,QAC5B,OAAO,WAAW,UAAU,KAAK,CAAC,CAAC;AAAA,QACnC,KAAK,SAAS,GAAG;AAAA,MACnB;AAAA,IACF;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,UAAU;AAAA,MACV,OAAO,CAAC,MAAM,oBAAI,KAAK,OAAO;AAAA,QAC5B,OAAO,WAAW,SAAS,KAAK,CAAC,CAAC;AAAA,QAClC,KAAK,SAAS,GAAG;AAAA,MACnB;AAAA,IACF;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,UAAU;AAAA,MACV,OAAO,CAAC,MAAM,oBAAI,KAAK,OAAO;AAAA,QAC5B,OAAO,aAAa,GAAG;AAAA,QACvB,KAAK,SAAS,GAAG;AAAA,MACnB;AAAA,IACF;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,UAAU;AAAA,MACV,OAAO,CAAC,MAAM,oBAAI,KAAK,OAAO;AAAA,QAC5B,OAAO,YAAY,GAAG;AAAA,QACtB,KAAK,SAAS,GAAG;AAAA,MACnB;AAAA,IACF;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,UAAU;AAAA,MACV,OAAO,CAAC,MAAM,oBAAI,KAAK,OAAO;AAAA;AAAA,QAE5B,OAAO,oBAAI,KAAK,CAAC;AAAA,QACjB,KAAK,SAAS,GAAG;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -158,7 +158,7 @@ function DatePicker({
158
158
  onSelect: handleDaySelect,
159
159
  locale,
160
160
  disabled: disabledMatcher,
161
- initialFocus: true
161
+ autoFocus: true
162
162
  }
163
163
  ),
164
164
  withTime && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-t px-3 py-2", children: [
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/primitives/date-picker.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\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 './popover'\nimport { Calendar } from './calendar'\nimport { Button } from './button'\nimport { LinkButton } from './link-button'\nimport { TimeInput } from '../backend/inputs/TimeInput'\nimport { formatWithPublicDateFormat, resolvePublicDateFormat, resolvePublicDateTimeFormat } from './date-format'\n\nexport type DatePickerFooter = 'apply-cancel' | 'today-clear' | 'none'\n\nexport type DatePickerProps = {\n value?: Date | null\n onChange: (value: Date | null) => void\n placeholder?: string\n size?: 'sm' | 'default'\n disabled?: boolean\n readOnly?: boolean\n /**\n * Footer mode in the popover.\n * - `'apply-cancel'` (default, Figma-aligned): selecting a day stages a draft;\n * Apply commits and closes; Cancel reverts and closes.\n * - `'today-clear'`: legacy behavior \u2014 Today commits today, Clear sets null.\n * - `'none'`: no footer; selecting a day commits immediately and closes.\n */\n footer?: DatePickerFooter\n /**\n * Whether selecting a day commits the value and closes the popover.\n * - In `footer='apply-cancel'`: ignored (commit happens via Apply).\n * - In `footer='today-clear'`: default `true` for backward compatibility.\n * - In `footer='none'`: always `true` (popover always closes after selection).\n */\n closeOnSelect?: boolean\n /**\n * Whether to show the \"Today\" link in `footer='today-clear'` mode. Default `true`.\n * Only meaningful when `footer='today-clear'`.\n */\n showTodayButton?: boolean\n /**\n * Whether to show the \"Clear\" link in `footer='today-clear'` mode. Default `true`.\n * Only meaningful when `footer='today-clear'`.\n */\n showClearButton?: boolean\n withTime?: boolean\n minuteStep?: number\n align?: 'start' | 'center' | 'end'\n minDate?: Date\n maxDate?: Date\n locale?: Locale\n displayFormat?: string\n className?: string\n popoverClassName?: string\n id?: string\n name?: string\n required?: boolean\n 'aria-label'?: string\n 'aria-describedby'?: string\n}\n\nfunction extractTime(date: Date): string {\n return `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`\n}\n\nfunction applyTimeToDate(base: Date, time: string): Date {\n const [hourStr = '0', minuteStr = '0'] = time.split(':')\n const hour = parseInt(hourStr, 10)\n const minute = parseInt(minuteStr, 10)\n const next = new Date(base)\n next.setHours(isNaN(hour) ? 0 : hour, isNaN(minute) ? 0 : minute, 0, 0)\n return next\n}\n\nfunction startOfDay(date: Date): Date {\n const next = new Date(date)\n next.setHours(0, 0, 0, 0)\n return next\n}\n\nexport function DatePicker({\n value,\n onChange,\n placeholder,\n size = 'default',\n disabled = false,\n readOnly = false,\n footer = 'apply-cancel',\n closeOnSelect,\n showTodayButton = true,\n showClearButton = true,\n withTime = false,\n minuteStep = 1,\n align = 'start',\n minDate,\n maxDate,\n locale,\n displayFormat,\n className,\n popoverClassName,\n id,\n name,\n required,\n 'aria-label': ariaLabel,\n 'aria-describedby': ariaDescribedBy,\n}: DatePickerProps) {\n const resolvedCloseOnSelect = closeOnSelect ?? footer === 'today-clear'\n const t = useT()\n const [open, setOpen] = React.useState(false)\n const [draft, setDraft] = React.useState<Date | null>(value ?? null)\n\n const isInteractive = !disabled && !readOnly\n const useDraft = footer === 'apply-cancel'\n\n React.useEffect(() => {\n if (open) setDraft(value ?? null)\n }, [open, value])\n\n const resolvedFormat = withTime\n ? resolvePublicDateTimeFormat(locale, displayFormat)\n : resolvePublicDateFormat(locale, displayFormat)\n const placeholderText = placeholder ?? (\n withTime\n ? t('ui.dateTimePicker.placeholder', 'Pick date and time')\n : t('ui.datePicker.placeholder', 'Pick a date')\n )\n\n const formattedValue = React.useMemo(() => {\n if (!value) return null\n return formatWithPublicDateFormat(value, resolvedFormat, locale)\n }, [value, resolvedFormat, locale])\n\n const handleDaySelect = React.useCallback(\n (day: Date | undefined) => {\n if (!day || !isInteractive) return\n const reference = withTime ? (useDraft ? draft : value) : null\n const base = withTime\n ? applyTimeToDate(day, reference ? extractTime(reference) : '00:00')\n : startOfDay(day)\n if (useDraft) {\n setDraft(base)\n } else {\n onChange(base)\n if (footer === 'none' || resolvedCloseOnSelect) setOpen(false)\n }\n },\n [isInteractive, withTime, useDraft, draft, value, footer, resolvedCloseOnSelect, onChange],\n )\n\n const handleTimeChange = React.useCallback(\n (time: string) => {\n const target = useDraft ? draft : value\n if (!target) return\n const next = applyTimeToDate(target, time)\n if (useDraft) setDraft(next)\n else onChange(next)\n },\n [useDraft, draft, value, onChange],\n )\n\n const handleApply = React.useCallback(() => {\n onChange(draft)\n setOpen(false)\n }, [draft, onChange])\n\n const handleCancel = React.useCallback(() => {\n setDraft(value ?? null)\n setOpen(false)\n }, [value])\n\n const handleToday = React.useCallback(() => {\n const today = withTime ? new Date() : startOfDay(new Date())\n onChange(today)\n setOpen(false)\n }, [withTime, onChange])\n\n const handleClear = React.useCallback(() => {\n onChange(null)\n setOpen(false)\n }, [onChange])\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 const heightClass = size === 'sm' ? 'h-8 px-2.5 text-xs' : 'h-9 px-3 text-sm'\n const selectedDate = useDraft ? draft : value\n\n return (\n <Popover open={open} onOpenChange={isInteractive ? setOpen : undefined}>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n id={id}\n name={name}\n aria-label={ariaLabel}\n aria-describedby={ariaDescribedBy}\n aria-haspopup=\"dialog\"\n aria-required={required ? true : undefined}\n data-crud-focus-target=\"\"\n data-slot=\"date-picker-trigger\"\n disabled={disabled}\n className={cn(\n 'w-full inline-flex items-center gap-2 rounded-md border border-input bg-background shadow-xs transition-colors text-left',\n 'focus-visible:outline-none focus-visible:shadow-focus focus-visible:border-foreground',\n 'hover:bg-muted/40',\n 'disabled:bg-bg-disabled disabled:border-border-disabled disabled:shadow-none disabled:hover:bg-bg-disabled disabled:cursor-not-allowed',\n 'aria-invalid:border-destructive',\n heightClass,\n readOnly && 'cursor-default opacity-70',\n !formattedValue && 'text-muted-foreground',\n className,\n )}\n onClick={isInteractive ? undefined : (event) => event.preventDefault()}\n >\n <CalendarIcon className=\"h-4 w-4 shrink-0 text-muted-foreground\" aria-hidden=\"true\" />\n <span className=\"flex-1 truncate\">\n {formattedValue ?? placeholderText}\n </span>\n </button>\n </PopoverTrigger>\n <PopoverContent align={align} className={cn('w-auto p-0', popoverClassName)}>\n <Calendar\n mode=\"single\"\n selected={selectedDate ?? undefined}\n onSelect={handleDaySelect}\n locale={locale}\n disabled={disabledMatcher}\n initialFocus\n />\n {withTime && (\n <div className=\"flex items-center gap-2 border-t px-3 py-2\">\n <span className=\"text-sm text-muted-foreground shrink-0\">\n {t('ui.datePicker.timeLabel', 'Time')}:\n </span>\n <TimeInput\n value={selectedDate ? extractTime(selectedDate) : undefined}\n onChange={handleTimeChange}\n minuteStep={minuteStep}\n disabled={!isInteractive || !selectedDate}\n />\n </div>\n )}\n {footer === 'apply-cancel' && (\n <div className=\"flex items-center justify-end gap-2 border-t px-3 py-2\">\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={handleCancel}>\n {t('ui.datePicker.cancelButton', 'Cancel')}\n </Button>\n <Button type=\"button\" size=\"sm\" onClick={handleApply}>\n {t('ui.datePicker.applyButton', 'Apply')}\n </Button>\n </div>\n )}\n {footer === 'today-clear' && (showTodayButton || showClearButton) && (\n <div className=\"flex items-center justify-between gap-2 border-t px-3 py-2\">\n {showTodayButton ? (\n <LinkButton type=\"button\" variant=\"primary\" onClick={handleToday}>\n {t('ui.datePicker.todayButton', 'Today')}\n </LinkButton>\n ) : null}\n {showClearButton ? (\n <LinkButton type=\"button\" variant=\"gray\" onClick={handleClear} className=\"ml-auto\">\n {t('ui.datePicker.clearButton', 'Clear')}\n </LinkButton>\n ) : null}\n </div>\n )}\n </PopoverContent>\n </Popover>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { Locale } from 'date-fns/locale'\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 './popover'\nimport { Calendar } from './calendar'\nimport { Button } from './button'\nimport { LinkButton } from './link-button'\nimport { TimeInput } from '../backend/inputs/TimeInput'\nimport { formatWithPublicDateFormat, resolvePublicDateFormat, resolvePublicDateTimeFormat } from './date-format'\n\nexport type DatePickerFooter = 'apply-cancel' | 'today-clear' | 'none'\n\nexport type DatePickerProps = {\n value?: Date | null\n onChange: (value: Date | null) => void\n placeholder?: string\n size?: 'sm' | 'default'\n disabled?: boolean\n readOnly?: boolean\n /**\n * Footer mode in the popover.\n * - `'apply-cancel'` (default, Figma-aligned): selecting a day stages a draft;\n * Apply commits and closes; Cancel reverts and closes.\n * - `'today-clear'`: legacy behavior \u2014 Today commits today, Clear sets null.\n * - `'none'`: no footer; selecting a day commits immediately and closes.\n */\n footer?: DatePickerFooter\n /**\n * Whether selecting a day commits the value and closes the popover.\n * - In `footer='apply-cancel'`: ignored (commit happens via Apply).\n * - In `footer='today-clear'`: default `true` for backward compatibility.\n * - In `footer='none'`: always `true` (popover always closes after selection).\n */\n closeOnSelect?: boolean\n /**\n * Whether to show the \"Today\" link in `footer='today-clear'` mode. Default `true`.\n * Only meaningful when `footer='today-clear'`.\n */\n showTodayButton?: boolean\n /**\n * Whether to show the \"Clear\" link in `footer='today-clear'` mode. Default `true`.\n * Only meaningful when `footer='today-clear'`.\n */\n showClearButton?: boolean\n withTime?: boolean\n minuteStep?: number\n align?: 'start' | 'center' | 'end'\n minDate?: Date\n maxDate?: Date\n locale?: Locale\n displayFormat?: string\n className?: string\n popoverClassName?: string\n id?: string\n name?: string\n required?: boolean\n 'aria-label'?: string\n 'aria-describedby'?: string\n}\n\nfunction extractTime(date: Date): string {\n return `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`\n}\n\nfunction applyTimeToDate(base: Date, time: string): Date {\n const [hourStr = '0', minuteStr = '0'] = time.split(':')\n const hour = parseInt(hourStr, 10)\n const minute = parseInt(minuteStr, 10)\n const next = new Date(base)\n next.setHours(isNaN(hour) ? 0 : hour, isNaN(minute) ? 0 : minute, 0, 0)\n return next\n}\n\nfunction startOfDay(date: Date): Date {\n const next = new Date(date)\n next.setHours(0, 0, 0, 0)\n return next\n}\n\nexport function DatePicker({\n value,\n onChange,\n placeholder,\n size = 'default',\n disabled = false,\n readOnly = false,\n footer = 'apply-cancel',\n closeOnSelect,\n showTodayButton = true,\n showClearButton = true,\n withTime = false,\n minuteStep = 1,\n align = 'start',\n minDate,\n maxDate,\n locale,\n displayFormat,\n className,\n popoverClassName,\n id,\n name,\n required,\n 'aria-label': ariaLabel,\n 'aria-describedby': ariaDescribedBy,\n}: DatePickerProps) {\n const resolvedCloseOnSelect = closeOnSelect ?? footer === 'today-clear'\n const t = useT()\n const [open, setOpen] = React.useState(false)\n const [draft, setDraft] = React.useState<Date | null>(value ?? null)\n\n const isInteractive = !disabled && !readOnly\n const useDraft = footer === 'apply-cancel'\n\n React.useEffect(() => {\n if (open) setDraft(value ?? null)\n }, [open, value])\n\n const resolvedFormat = withTime\n ? resolvePublicDateTimeFormat(locale, displayFormat)\n : resolvePublicDateFormat(locale, displayFormat)\n const placeholderText = placeholder ?? (\n withTime\n ? t('ui.dateTimePicker.placeholder', 'Pick date and time')\n : t('ui.datePicker.placeholder', 'Pick a date')\n )\n\n const formattedValue = React.useMemo(() => {\n if (!value) return null\n return formatWithPublicDateFormat(value, resolvedFormat, locale)\n }, [value, resolvedFormat, locale])\n\n const handleDaySelect = React.useCallback(\n (day: Date | undefined) => {\n if (!day || !isInteractive) return\n const reference = withTime ? (useDraft ? draft : value) : null\n const base = withTime\n ? applyTimeToDate(day, reference ? extractTime(reference) : '00:00')\n : startOfDay(day)\n if (useDraft) {\n setDraft(base)\n } else {\n onChange(base)\n if (footer === 'none' || resolvedCloseOnSelect) setOpen(false)\n }\n },\n [isInteractive, withTime, useDraft, draft, value, footer, resolvedCloseOnSelect, onChange],\n )\n\n const handleTimeChange = React.useCallback(\n (time: string) => {\n const target = useDraft ? draft : value\n if (!target) return\n const next = applyTimeToDate(target, time)\n if (useDraft) setDraft(next)\n else onChange(next)\n },\n [useDraft, draft, value, onChange],\n )\n\n const handleApply = React.useCallback(() => {\n onChange(draft)\n setOpen(false)\n }, [draft, onChange])\n\n const handleCancel = React.useCallback(() => {\n setDraft(value ?? null)\n setOpen(false)\n }, [value])\n\n const handleToday = React.useCallback(() => {\n const today = withTime ? new Date() : startOfDay(new Date())\n onChange(today)\n setOpen(false)\n }, [withTime, onChange])\n\n const handleClear = React.useCallback(() => {\n onChange(null)\n setOpen(false)\n }, [onChange])\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 const heightClass = size === 'sm' ? 'h-8 px-2.5 text-xs' : 'h-9 px-3 text-sm'\n const selectedDate = useDraft ? draft : value\n\n return (\n <Popover open={open} onOpenChange={isInteractive ? setOpen : undefined}>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n id={id}\n name={name}\n aria-label={ariaLabel}\n aria-describedby={ariaDescribedBy}\n aria-haspopup=\"dialog\"\n aria-required={required ? true : undefined}\n data-crud-focus-target=\"\"\n data-slot=\"date-picker-trigger\"\n disabled={disabled}\n className={cn(\n 'w-full inline-flex items-center gap-2 rounded-md border border-input bg-background shadow-xs transition-colors text-left',\n 'focus-visible:outline-none focus-visible:shadow-focus focus-visible:border-foreground',\n 'hover:bg-muted/40',\n 'disabled:bg-bg-disabled disabled:border-border-disabled disabled:shadow-none disabled:hover:bg-bg-disabled disabled:cursor-not-allowed',\n 'aria-invalid:border-destructive',\n heightClass,\n readOnly && 'cursor-default opacity-70',\n !formattedValue && 'text-muted-foreground',\n className,\n )}\n onClick={isInteractive ? undefined : (event) => event.preventDefault()}\n >\n <CalendarIcon className=\"h-4 w-4 shrink-0 text-muted-foreground\" aria-hidden=\"true\" />\n <span className=\"flex-1 truncate\">\n {formattedValue ?? placeholderText}\n </span>\n </button>\n </PopoverTrigger>\n <PopoverContent align={align} className={cn('w-auto p-0', popoverClassName)}>\n <Calendar\n mode=\"single\"\n selected={selectedDate ?? undefined}\n onSelect={handleDaySelect}\n locale={locale}\n disabled={disabledMatcher}\n autoFocus\n />\n {withTime && (\n <div className=\"flex items-center gap-2 border-t px-3 py-2\">\n <span className=\"text-sm text-muted-foreground shrink-0\">\n {t('ui.datePicker.timeLabel', 'Time')}:\n </span>\n <TimeInput\n value={selectedDate ? extractTime(selectedDate) : undefined}\n onChange={handleTimeChange}\n minuteStep={minuteStep}\n disabled={!isInteractive || !selectedDate}\n />\n </div>\n )}\n {footer === 'apply-cancel' && (\n <div className=\"flex items-center justify-end gap-2 border-t px-3 py-2\">\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={handleCancel}>\n {t('ui.datePicker.cancelButton', 'Cancel')}\n </Button>\n <Button type=\"button\" size=\"sm\" onClick={handleApply}>\n {t('ui.datePicker.applyButton', 'Apply')}\n </Button>\n </div>\n )}\n {footer === 'today-clear' && (showTodayButton || showClearButton) && (\n <div className=\"flex items-center justify-between gap-2 border-t px-3 py-2\">\n {showTodayButton ? (\n <LinkButton type=\"button\" variant=\"primary\" onClick={handleToday}>\n {t('ui.datePicker.todayButton', 'Today')}\n </LinkButton>\n ) : null}\n {showClearButton ? (\n <LinkButton type=\"button\" variant=\"gray\" onClick={handleClear} className=\"ml-auto\">\n {t('ui.datePicker.clearButton', 'Clear')}\n </LinkButton>\n ) : null}\n </div>\n )}\n </PopoverContent>\n </Popover>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -198,7 +198,7 @@ function DateRangePicker({
198
198
  locale,
199
199
  disabled: disabledMatcher,
200
200
  numberOfMonths,
201
- initialFocus: true
201
+ autoFocus: true
202
202
  }
203
203
  ) })
204
204
  ] }),
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/primitives/date-range-picker.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\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 './popover'\nimport { Calendar } from './calendar'\nimport { Button } from './button'\nimport type { DateRange as RDPRange } from 'react-day-picker'\nimport type { DateRange } from '../backend/date-range/dateRanges'\nimport {\n defaultDateRangePresets,\n type DateRangePresetItem,\n} from './date-picker-helpers'\nimport { formatWithPublicDateFormat, resolvePublicDateFormat } from './date-format'\n\nexport type { DateRangePresetItem } from './date-picker-helpers'\n\nexport type DateRangePickerProps = {\n value?: DateRange | null\n onChange: (value: DateRange | null) => void\n presets?: DateRangePresetItem[]\n showPresets?: boolean\n placeholder?: string\n size?: 'sm' | 'default'\n disabled?: boolean\n readOnly?: boolean\n withFooter?: boolean\n align?: 'start' | 'center' | 'end'\n minDate?: Date\n maxDate?: Date\n numberOfMonths?: 1 | 2\n locale?: Locale\n formatRange?: (value: DateRange, locale?: Locale) => string\n className?: string\n popoverClassName?: string\n id?: string\n name?: string\n required?: boolean\n 'aria-label'?: string\n 'aria-describedby'?: string\n}\n\nfunction defaultFormatRange(range: DateRange, locale?: Locale): string {\n const fmt = resolvePublicDateFormat(locale)\n return `${formatWithPublicDateFormat(range.start, fmt, locale) ?? ''} \u2013 ${formatWithPublicDateFormat(range.end, fmt, locale) ?? ''}`\n}\n\nfunction toRDPRange(range: DateRange | null | undefined): RDPRange | undefined {\n if (!range) return undefined\n return { from: range.start, to: range.end }\n}\n\nfunction fromRDPRange(rdp: RDPRange | undefined): DateRange | null {\n if (!rdp || !rdp.from) return null\n return { start: rdp.from, end: rdp.to ?? rdp.from }\n}\n\nfunction rangesEqual(a: DateRange | null, b: DateRange | null): boolean {\n if (!a && !b) return true\n if (!a || !b) return false\n return a.start.getTime() === b.start.getTime() && a.end.getTime() === b.end.getTime()\n}\n\nexport function DateRangePicker({\n value,\n onChange,\n presets,\n showPresets = true,\n placeholder,\n size = 'default',\n disabled = false,\n readOnly = false,\n withFooter = true,\n align = 'start',\n minDate,\n maxDate,\n numberOfMonths = 2,\n locale,\n formatRange,\n className,\n popoverClassName,\n id,\n name,\n required,\n 'aria-label': ariaLabel,\n 'aria-describedby': ariaDescribedBy,\n}: DateRangePickerProps) {\n const t = useT()\n const [open, setOpen] = React.useState(false)\n const [draft, setDraft] = React.useState<DateRange | null>(value ?? null)\n const [activePresetId, setActivePresetId] = React.useState<string | null>(null)\n\n const isInteractive = !disabled && !readOnly\n const useDraft = withFooter\n\n React.useEffect(() => {\n if (open) {\n setDraft(value ?? null)\n setActivePresetId(null)\n }\n }, [open, value])\n\n const resolvedPresets = React.useMemo<DateRangePresetItem[]>(\n () => presets ?? defaultDateRangePresets(),\n [presets],\n )\n\n const placeholderText = placeholder ?? t('ui.dateRangePicker.placeholder', 'Pick a date range')\n const formatter = formatRange ?? defaultFormatRange\n const formattedValue = React.useMemo(() => {\n if (!value) return null\n return formatter(value, locale)\n }, [value, formatter, locale])\n\n const selectedDate = useDraft ? draft : value\n const rdpSelected = toRDPRange(selectedDate)\n\n const handleRangeSelect = React.useCallback(\n (rdpRange: RDPRange | undefined) => {\n if (!isInteractive) return\n const next = fromRDPRange(rdpRange)\n setActivePresetId(null)\n if (useDraft) {\n setDraft(next)\n } else {\n onChange(next)\n if (next?.start && next?.end) setOpen(false)\n }\n },\n [isInteractive, useDraft, onChange],\n )\n\n const handlePresetClick = React.useCallback(\n (preset: DateRangePresetItem) => {\n if (!isInteractive) return\n const range = preset.range()\n setActivePresetId(preset.id)\n if (useDraft) {\n setDraft(range)\n } else {\n onChange(range)\n setOpen(false)\n }\n },\n [isInteractive, useDraft, onChange],\n )\n\n const handleApply = React.useCallback(() => {\n onChange(draft)\n setOpen(false)\n }, [draft, onChange])\n\n const handleCancel = React.useCallback(() => {\n setDraft(value ?? null)\n setActivePresetId(null)\n setOpen(false)\n }, [value])\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 const heightClass = size === 'sm' ? 'h-8 px-2.5 text-xs' : 'h-9 px-3 text-sm'\n\n return (\n <Popover open={open} onOpenChange={isInteractive ? setOpen : undefined}>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n id={id}\n name={name}\n aria-label={ariaLabel}\n aria-describedby={ariaDescribedBy}\n aria-haspopup=\"dialog\"\n aria-required={required ? true : undefined}\n data-crud-focus-target=\"\"\n data-slot=\"date-range-picker-trigger\"\n disabled={disabled}\n className={cn(\n 'w-full inline-flex items-center gap-2 rounded-md border border-input bg-background shadow-xs transition-colors text-left',\n 'focus-visible:outline-none focus-visible:shadow-focus focus-visible:border-foreground',\n 'hover:bg-muted/40',\n 'disabled:bg-bg-disabled disabled:border-border-disabled disabled:shadow-none disabled:hover:bg-bg-disabled disabled:cursor-not-allowed',\n 'aria-invalid:border-destructive',\n heightClass,\n readOnly && 'cursor-default opacity-70',\n !formattedValue && 'text-muted-foreground',\n className,\n )}\n onClick={isInteractive ? undefined : (event) => event.preventDefault()}\n >\n <CalendarIcon className=\"h-4 w-4 shrink-0 text-muted-foreground\" aria-hidden=\"true\" />\n <span className=\"flex-1 truncate\">\n {formattedValue ?? placeholderText}\n </span>\n </button>\n </PopoverTrigger>\n <PopoverContent\n align={align}\n className={cn(\n 'w-auto p-0 flex flex-col overflow-hidden',\n 'max-h-[var(--radix-popover-content-available-height)]',\n popoverClassName,\n )}\n >\n <div className=\"flex min-h-0 flex-1 overflow-auto\">\n {showPresets ? (\n <div\n data-slot=\"date-range-presets\"\n className=\"flex w-[200px] shrink-0 flex-col gap-0.5 overflow-y-auto border-r p-2\"\n >\n {resolvedPresets.map((preset) => {\n const isActive = activePresetId === preset.id\n return (\n <button\n key={preset.id}\n type=\"button\"\n onClick={() => handlePresetClick(preset)}\n className={cn(\n 'inline-flex h-8 items-center rounded-md px-3 text-left text-sm transition-colors shrink-0',\n 'hover:bg-accent hover:text-accent-foreground',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',\n isActive && 'bg-muted font-medium text-foreground',\n )}\n >\n {t(preset.labelKey, preset.id.replace(/_/g, ' '))}\n </button>\n )\n })}\n </div>\n ) : null}\n <div className=\"p-3\">\n <Calendar\n mode=\"range\"\n selected={rdpSelected}\n onSelect={handleRangeSelect}\n locale={locale}\n disabled={disabledMatcher}\n numberOfMonths={numberOfMonths}\n initialFocus\n />\n </div>\n </div>\n {withFooter ? (\n <div className=\"flex shrink-0 items-center justify-between gap-3 border-t bg-popover px-3 py-2\">\n <span className=\"truncate text-xs text-muted-foreground\">\n {selectedDate\n ? `${t('ui.dateRangePicker.rangeLabel', 'Range')}: ${formatter(selectedDate, locale)}`\n : t('ui.dateRangePicker.rangeEmpty', 'No range selected')}\n </span>\n <div className=\"flex shrink-0 items-center gap-2\">\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={handleCancel}>\n {t('ui.dateRangePicker.cancelButton', 'Cancel')}\n </Button>\n <Button type=\"button\" size=\"sm\" onClick={handleApply}>\n {t('ui.dateRangePicker.applyButton', 'Apply')}\n </Button>\n </div>\n </div>\n ) : null}\n </PopoverContent>\n </Popover>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { Locale } from 'date-fns/locale'\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 './popover'\nimport { Calendar } from './calendar'\nimport { Button } from './button'\nimport type { DateRange as RDPRange } from 'react-day-picker'\nimport type { DateRange } from '../backend/date-range/dateRanges'\nimport {\n defaultDateRangePresets,\n type DateRangePresetItem,\n} from './date-picker-helpers'\nimport { formatWithPublicDateFormat, resolvePublicDateFormat } from './date-format'\n\nexport type { DateRangePresetItem } from './date-picker-helpers'\n\nexport type DateRangePickerProps = {\n value?: DateRange | null\n onChange: (value: DateRange | null) => void\n presets?: DateRangePresetItem[]\n showPresets?: boolean\n placeholder?: string\n size?: 'sm' | 'default'\n disabled?: boolean\n readOnly?: boolean\n withFooter?: boolean\n align?: 'start' | 'center' | 'end'\n minDate?: Date\n maxDate?: Date\n numberOfMonths?: 1 | 2\n locale?: Locale\n formatRange?: (value: DateRange, locale?: Locale) => string\n className?: string\n popoverClassName?: string\n id?: string\n name?: string\n required?: boolean\n 'aria-label'?: string\n 'aria-describedby'?: string\n}\n\nfunction defaultFormatRange(range: DateRange, locale?: Locale): string {\n const fmt = resolvePublicDateFormat(locale)\n return `${formatWithPublicDateFormat(range.start, fmt, locale) ?? ''} \u2013 ${formatWithPublicDateFormat(range.end, fmt, locale) ?? ''}`\n}\n\nfunction toRDPRange(range: DateRange | null | undefined): RDPRange | undefined {\n if (!range) return undefined\n return { from: range.start, to: range.end }\n}\n\nfunction fromRDPRange(rdp: RDPRange | undefined): DateRange | null {\n if (!rdp || !rdp.from) return null\n return { start: rdp.from, end: rdp.to ?? rdp.from }\n}\n\nfunction rangesEqual(a: DateRange | null, b: DateRange | null): boolean {\n if (!a && !b) return true\n if (!a || !b) return false\n return a.start.getTime() === b.start.getTime() && a.end.getTime() === b.end.getTime()\n}\n\nexport function DateRangePicker({\n value,\n onChange,\n presets,\n showPresets = true,\n placeholder,\n size = 'default',\n disabled = false,\n readOnly = false,\n withFooter = true,\n align = 'start',\n minDate,\n maxDate,\n numberOfMonths = 2,\n locale,\n formatRange,\n className,\n popoverClassName,\n id,\n name,\n required,\n 'aria-label': ariaLabel,\n 'aria-describedby': ariaDescribedBy,\n}: DateRangePickerProps) {\n const t = useT()\n const [open, setOpen] = React.useState(false)\n const [draft, setDraft] = React.useState<DateRange | null>(value ?? null)\n const [activePresetId, setActivePresetId] = React.useState<string | null>(null)\n\n const isInteractive = !disabled && !readOnly\n const useDraft = withFooter\n\n React.useEffect(() => {\n if (open) {\n setDraft(value ?? null)\n setActivePresetId(null)\n }\n }, [open, value])\n\n const resolvedPresets = React.useMemo<DateRangePresetItem[]>(\n () => presets ?? defaultDateRangePresets(),\n [presets],\n )\n\n const placeholderText = placeholder ?? t('ui.dateRangePicker.placeholder', 'Pick a date range')\n const formatter = formatRange ?? defaultFormatRange\n const formattedValue = React.useMemo(() => {\n if (!value) return null\n return formatter(value, locale)\n }, [value, formatter, locale])\n\n const selectedDate = useDraft ? draft : value\n const rdpSelected = toRDPRange(selectedDate)\n\n const handleRangeSelect = React.useCallback(\n (rdpRange: RDPRange | undefined) => {\n if (!isInteractive) return\n const next = fromRDPRange(rdpRange)\n setActivePresetId(null)\n if (useDraft) {\n setDraft(next)\n } else {\n onChange(next)\n if (next?.start && next?.end) setOpen(false)\n }\n },\n [isInteractive, useDraft, onChange],\n )\n\n const handlePresetClick = React.useCallback(\n (preset: DateRangePresetItem) => {\n if (!isInteractive) return\n const range = preset.range()\n setActivePresetId(preset.id)\n if (useDraft) {\n setDraft(range)\n } else {\n onChange(range)\n setOpen(false)\n }\n },\n [isInteractive, useDraft, onChange],\n )\n\n const handleApply = React.useCallback(() => {\n onChange(draft)\n setOpen(false)\n }, [draft, onChange])\n\n const handleCancel = React.useCallback(() => {\n setDraft(value ?? null)\n setActivePresetId(null)\n setOpen(false)\n }, [value])\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 const heightClass = size === 'sm' ? 'h-8 px-2.5 text-xs' : 'h-9 px-3 text-sm'\n\n return (\n <Popover open={open} onOpenChange={isInteractive ? setOpen : undefined}>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n id={id}\n name={name}\n aria-label={ariaLabel}\n aria-describedby={ariaDescribedBy}\n aria-haspopup=\"dialog\"\n aria-required={required ? true : undefined}\n data-crud-focus-target=\"\"\n data-slot=\"date-range-picker-trigger\"\n disabled={disabled}\n className={cn(\n 'w-full inline-flex items-center gap-2 rounded-md border border-input bg-background shadow-xs transition-colors text-left',\n 'focus-visible:outline-none focus-visible:shadow-focus focus-visible:border-foreground',\n 'hover:bg-muted/40',\n 'disabled:bg-bg-disabled disabled:border-border-disabled disabled:shadow-none disabled:hover:bg-bg-disabled disabled:cursor-not-allowed',\n 'aria-invalid:border-destructive',\n heightClass,\n readOnly && 'cursor-default opacity-70',\n !formattedValue && 'text-muted-foreground',\n className,\n )}\n onClick={isInteractive ? undefined : (event) => event.preventDefault()}\n >\n <CalendarIcon className=\"h-4 w-4 shrink-0 text-muted-foreground\" aria-hidden=\"true\" />\n <span className=\"flex-1 truncate\">\n {formattedValue ?? placeholderText}\n </span>\n </button>\n </PopoverTrigger>\n <PopoverContent\n align={align}\n className={cn(\n 'w-auto p-0 flex flex-col overflow-hidden',\n 'max-h-[var(--radix-popover-content-available-height)]',\n popoverClassName,\n )}\n >\n <div className=\"flex min-h-0 flex-1 overflow-auto\">\n {showPresets ? (\n <div\n data-slot=\"date-range-presets\"\n className=\"flex w-[200px] shrink-0 flex-col gap-0.5 overflow-y-auto border-r p-2\"\n >\n {resolvedPresets.map((preset) => {\n const isActive = activePresetId === preset.id\n return (\n <button\n key={preset.id}\n type=\"button\"\n onClick={() => handlePresetClick(preset)}\n className={cn(\n 'inline-flex h-8 items-center rounded-md px-3 text-left text-sm transition-colors shrink-0',\n 'hover:bg-accent hover:text-accent-foreground',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',\n isActive && 'bg-muted font-medium text-foreground',\n )}\n >\n {t(preset.labelKey, preset.id.replace(/_/g, ' '))}\n </button>\n )\n })}\n </div>\n ) : null}\n <div className=\"p-3\">\n <Calendar\n mode=\"range\"\n selected={rdpSelected}\n onSelect={handleRangeSelect}\n locale={locale}\n disabled={disabledMatcher}\n numberOfMonths={numberOfMonths}\n autoFocus\n />\n </div>\n </div>\n {withFooter ? (\n <div className=\"flex shrink-0 items-center justify-between gap-3 border-t bg-popover px-3 py-2\">\n <span className=\"truncate text-xs text-muted-foreground\">\n {selectedDate\n ? `${t('ui.dateRangePicker.rangeLabel', 'Range')}: ${formatter(selectedDate, locale)}`\n : t('ui.dateRangePicker.rangeEmpty', 'No range selected')}\n </span>\n <div className=\"flex shrink-0 items-center gap-2\">\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={handleCancel}>\n {t('ui.dateRangePicker.cancelButton', 'Cancel')}\n </Button>\n <Button type=\"button\" size=\"sm\" onClick={handleApply}>\n {t('ui.dateRangePicker.applyButton', 'Apply')}\n </Button>\n </div>\n </div>\n ) : null}\n </PopoverContent>\n </Popover>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/jest.config.cjs CHANGED
@@ -1,6 +1,7 @@
1
1
  /** @type {import('jest').Config} */
2
2
  module.exports = {
3
3
  testEnvironment: 'jsdom',
4
+ testTimeout: 30000,
4
5
  watchman: false,
5
6
  rootDir: '.',
6
7
  maxWorkers: 4,
@@ -18,13 +19,15 @@ module.exports = {
18
19
  {
19
20
  tsconfig: {
20
21
  jsx: 'react-jsx',
22
+ rootDir: '.',
23
+ ignoreDeprecations: '6.0',
21
24
  },
22
25
  },
23
26
  ],
24
27
  },
25
28
  setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
26
29
  transformIgnorePatterns: [
27
- 'node_modules/(?!(@mikro-orm)/)',
30
+ 'node_modules/(?!(@mikro-orm|kysely)/)',
28
31
  ],
29
32
  testMatch: [
30
33
  '<rootDir>/src/**/__tests__/**/*.test.(ts|tsx)',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/ui",
3
- "version": "0.6.2-develop.3461.1.605f31c2c9",
3
+ "version": "0.6.2",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {
@@ -133,21 +133,21 @@
133
133
  "@radix-ui/react-select": "^2.1.6",
134
134
  "@radix-ui/react-tooltip": "^1.2.8",
135
135
  "@tanstack/react-virtual": "^3.13.24",
136
- "date-fns": "4.1.0",
136
+ "date-fns": "4.2.1",
137
137
  "react-big-calendar": "^1.19.4",
138
- "react-day-picker": "^9.14.0",
138
+ "react-day-picker": "^10.0.1",
139
139
  "react-markdown": "^10.1.0",
140
140
  "recharts": "^3.8.1",
141
141
  "remark-gfm": "^4.0.1"
142
142
  },
143
143
  "peerDependencies": {
144
- "@open-mercato/shared": "0.6.2-develop.3461.1.605f31c2c9",
144
+ "@open-mercato/shared": "0.6.2",
145
145
  "react": ">=18.0.0",
146
146
  "react-dom": ">=18.0.0",
147
147
  "react-is": ">=18.0.0"
148
148
  },
149
149
  "devDependencies": {
150
- "@open-mercato/shared": "0.6.2-develop.3461.1.605f31c2c9",
150
+ "@open-mercato/shared": "0.6.2",
151
151
  "@testing-library/dom": "^10.4.1",
152
152
  "@testing-library/jest-dom": "^6.9.1",
153
153
  "@testing-library/react": "^16.3.1",
@@ -168,6 +168,5 @@
168
168
  "type": "git",
169
169
  "url": "https://github.com/open-mercato/open-mercato",
170
170
  "directory": "packages/ui"
171
- },
172
- "stableVersion": "0.6.1"
171
+ }
173
172
  }
package/src/ai/AiChat.tsx CHANGED
@@ -16,12 +16,14 @@ import {
16
16
  CircleDashed,
17
17
  Copy,
18
18
  Database,
19
+ Download,
19
20
  FileQuestion,
20
21
  GitBranch,
21
22
  Handshake,
22
23
  Lightbulb,
23
24
  ListChecks,
24
25
  Loader2,
26
+ Maximize2,
25
27
  Paperclip,
26
28
  Plus,
27
29
  Search,
@@ -38,6 +40,13 @@ import { useT } from '@open-mercato/shared/lib/i18n/context'
38
40
  import { cn } from '@open-mercato/shared/lib/utils'
39
41
  import { Alert, AlertDescription, AlertTitle } from '../primitives/alert'
40
42
  import { Button } from '../primitives/button'
43
+ import {
44
+ Dialog,
45
+ DialogContent,
46
+ DialogDescription,
47
+ DialogHeader,
48
+ DialogTitle,
49
+ } from '../primitives/dialog'
41
50
  import { IconButton } from '../primitives/icon-button'
42
51
  import { Label } from '../primitives/label'
43
52
  import { Textarea } from '../primitives/textarea'
@@ -638,6 +647,128 @@ function ReasoningPanel({ text, streaming }: { text: string; streaming: boolean
638
647
  )
639
648
  }
640
649
 
650
+ function getAttachmentImageUrl(file: AiChatMessageFile): string | null {
651
+ if (file.id) return `/api/attachments/image/${encodeURIComponent(file.id)}`
652
+ return file.previewUrl ?? null
653
+ }
654
+
655
+ function getAttachmentDownloadUrl(file: AiChatMessageFile): string | null {
656
+ if (file.id) return `/api/attachments/file/${encodeURIComponent(file.id)}?download=1`
657
+ return file.previewUrl ?? null
658
+ }
659
+
660
+ function MessageFileAttachment({ file }: { file: AiChatMessageFile }) {
661
+ const t = useT()
662
+ const [zoomOpen, setZoomOpen] = React.useState(false)
663
+ const imageUrl = file.previewUrl ? getAttachmentImageUrl(file) : null
664
+ const downloadUrl = getAttachmentDownloadUrl(file)
665
+ const downloadLabel = t('ai_assistant.chat.downloadFile', 'Download {name}').replace(
666
+ '{name}',
667
+ file.name,
668
+ )
669
+ const zoomLabel = t('ai_assistant.chat.zoomImage', 'Open {name} preview').replace(
670
+ '{name}',
671
+ file.name,
672
+ )
673
+
674
+ if (!file.previewUrl) {
675
+ return (
676
+ <span className="inline-flex max-w-full items-center gap-1 rounded-full border border-border bg-muted px-2 py-0.5 text-xs">
677
+ <Paperclip className="size-3 shrink-0" aria-hidden />
678
+ <span className="max-w-[180px] truncate">{file.name}</span>
679
+ {downloadUrl ? (
680
+ <IconButton
681
+ asChild
682
+ variant="ghost"
683
+ size="xs"
684
+ className="-mr-1 size-5 shrink-0"
685
+ >
686
+ <a href={downloadUrl} download={file.name} aria-label={downloadLabel}>
687
+ <Download className="size-3" aria-hidden />
688
+ </a>
689
+ </IconButton>
690
+ ) : null}
691
+ </span>
692
+ )
693
+ }
694
+
695
+ return (
696
+ <>
697
+ <div className="group/file relative max-w-full">
698
+ <button
699
+ type="button"
700
+ className="block max-w-full rounded-md border border-border bg-muted/30 focus-visible:outline-none focus-visible:shadow-focus"
701
+ onClick={() => setZoomOpen(true)}
702
+ aria-label={zoomLabel}
703
+ data-ai-chat-image-preview=""
704
+ >
705
+ <img
706
+ src={file.previewUrl}
707
+ alt={file.name}
708
+ className="max-h-32 max-w-[200px] rounded-md object-cover"
709
+ />
710
+ <span className="pointer-events-none absolute right-1 top-1 inline-flex size-6 items-center justify-center rounded-md bg-background/90 text-muted-foreground opacity-0 shadow-xs transition-opacity group-hover/file:opacity-100 group-focus-within/file:opacity-100">
711
+ <Maximize2 className="size-3.5" aria-hidden />
712
+ </span>
713
+ </button>
714
+ {downloadUrl ? (
715
+ <IconButton
716
+ asChild
717
+ variant="white"
718
+ size="xs"
719
+ className="absolute bottom-1 right-1 opacity-0 shadow-xs transition-opacity group-hover/file:opacity-100 group-focus-within/file:opacity-100"
720
+ >
721
+ <a
722
+ href={downloadUrl}
723
+ download={file.name}
724
+ aria-label={downloadLabel}
725
+ onClick={(event) => event.stopPropagation()}
726
+ data-ai-chat-file-download=""
727
+ >
728
+ <Download className="size-3.5" aria-hidden />
729
+ </a>
730
+ </IconButton>
731
+ ) : null}
732
+ </div>
733
+
734
+ <Dialog open={zoomOpen} onOpenChange={setZoomOpen}>
735
+ <DialogContent className="gap-3 p-4 sm:max-w-[min(96vw,960px)]">
736
+ <DialogHeader className="min-w-0 pr-16">
737
+ <DialogTitle className="truncate text-sm">{file.name}</DialogTitle>
738
+ <DialogDescription className="sr-only">
739
+ {t('ai_assistant.chat.imagePreviewDialogDescription', 'Image preview')}
740
+ </DialogDescription>
741
+ </DialogHeader>
742
+ <div className="relative flex min-h-0 items-center justify-center overflow-auto rounded-md bg-muted/30 p-2">
743
+ <img
744
+ src={imageUrl ?? file.previewUrl}
745
+ alt={file.name}
746
+ className="max-h-[75vh] max-w-full object-contain"
747
+ />
748
+ {downloadUrl ? (
749
+ <IconButton
750
+ asChild
751
+ variant="white"
752
+ size="sm"
753
+ className="absolute bottom-3 right-3 shadow-xs"
754
+ >
755
+ <a
756
+ href={downloadUrl}
757
+ download={file.name}
758
+ aria-label={downloadLabel}
759
+ data-ai-chat-file-download=""
760
+ >
761
+ <Download className="size-4" aria-hidden />
762
+ </a>
763
+ </IconButton>
764
+ ) : null}
765
+ </div>
766
+ </DialogContent>
767
+ </Dialog>
768
+ </>
769
+ )
770
+ }
771
+
641
772
  function MessageRow({
642
773
  message,
643
774
  registry,
@@ -737,24 +868,9 @@ function MessageRow({
737
868
  </div>
738
869
  {message.files && message.files.length > 0 ? (
739
870
  <div className="flex flex-wrap gap-2 py-1">
740
- {message.files.map((file, i) =>
741
- file.previewUrl ? (
742
- <img
743
- key={i}
744
- src={file.previewUrl}
745
- alt={file.name}
746
- className="max-h-32 max-w-[200px] rounded-md border border-border object-cover"
747
- />
748
- ) : (
749
- <span
750
- key={i}
751
- className="inline-flex items-center gap-1 rounded-full border border-border bg-muted px-2 py-0.5 text-xs"
752
- >
753
- <Paperclip className="size-3" aria-hidden />
754
- {file.name}
755
- </span>
756
- ),
757
- )}
871
+ {message.files.map((file, i) => (
872
+ <MessageFileAttachment key={`${file.id ?? file.name}:${i}`} file={file} />
873
+ ))}
758
874
  </div>
759
875
  ) : null}
760
876
  {isAssistant && message.reasoning ? (
@@ -1030,10 +1146,9 @@ export function AiChat({
1030
1146
  attachmentId?: string
1031
1147
  /**
1032
1148
  * Base64 data URL of the image (capped by PREVIEW_DATA_URL_MAX_BYTES).
1033
- * Stored on the message so the preview survives a reload — durable
1034
- * server URLs were intentionally avoided because the LLM provider can
1035
- * never reach a localhost dev URL anyway, and HTTP fetches add latency
1036
- * the chat doesn't need.
1149
+ * Stored on the local message for immediate previews; once the transcript
1150
+ * is loaded from server storage the attachment id is used to rebuild a
1151
+ * same-origin thumbnail URL.
1037
1152
  */
1038
1153
  previewDataUrl?: string
1039
1154
  error?: string
@@ -1255,6 +1370,7 @@ export function AiChat({
1255
1370
  const isImage = entry.file.type.startsWith('image/')
1256
1371
  const fallback = isImage ? URL.createObjectURL(entry.file) : undefined
1257
1372
  return {
1373
+ id: entry.attachmentId,
1258
1374
  name: entry.file.name,
1259
1375
  type: entry.file.type,
1260
1376
  previewUrl: isImage ? (entry.previewDataUrl ?? fallback) : undefined,