@open-mercato/ui 0.8.1-develop.7296.1.2111d779db → 0.9.0

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 (85) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/backend/CrudForm.js +19 -6
  3. package/dist/backend/CrudForm.js.map +2 -2
  4. package/dist/backend/DataTable.js +62 -10
  5. package/dist/backend/DataTable.js.map +2 -2
  6. package/dist/backend/RowActions.js +54 -5
  7. package/dist/backend/RowActions.js.map +2 -2
  8. package/dist/backend/charts/KpiCard.js +2 -1
  9. package/dist/backend/charts/KpiCard.js.map +2 -2
  10. package/dist/backend/detail/AttachmentsSection.js +17 -5
  11. package/dist/backend/detail/AttachmentsSection.js.map +2 -2
  12. package/dist/backend/detail/NotesSection.js +38 -20
  13. package/dist/backend/detail/NotesSection.js.map +2 -2
  14. package/dist/backend/dev/DevRuntimeDiagnosticsBanner.js +11 -1
  15. package/dist/backend/dev/DevRuntimeDiagnosticsBanner.js.map +2 -2
  16. package/dist/backend/icons/lucideRegistry.generated.js +26 -0
  17. package/dist/backend/icons/lucideRegistry.generated.js.map +2 -2
  18. package/dist/backend/injection/eventBridge.js +33 -0
  19. package/dist/backend/injection/eventBridge.js.map +2 -2
  20. package/dist/backend/inputs/LookupSelect.js +20 -3
  21. package/dist/backend/inputs/LookupSelect.js.map +2 -2
  22. package/dist/backend/inputs/PhoneNumberField.js +1 -1
  23. package/dist/backend/inputs/PhoneNumberField.js.map +2 -2
  24. package/dist/backend/inputs/TagsInput.js +20 -2
  25. package/dist/backend/inputs/TagsInput.js.map +2 -2
  26. package/dist/backend/messages/EmailThreadsPanel.js +2 -1
  27. package/dist/backend/messages/EmailThreadsPanel.js.map +2 -2
  28. package/dist/backend/messages/MessageComposer.js.map +2 -2
  29. package/dist/backend/messages/index.js.map +2 -2
  30. package/dist/backend/messages/message-compose-form-groups.js +24 -0
  31. package/dist/backend/messages/message-compose-form-groups.js.map +2 -2
  32. package/dist/backend/messages/useMessageCompose.js +27 -0
  33. package/dist/backend/messages/useMessageCompose.js.map +2 -2
  34. package/dist/backend/messages/useMessageComposeOperations.js +1 -0
  35. package/dist/backend/messages/useMessageComposeOperations.js.map +2 -2
  36. package/dist/backend/notifications/NotificationItem.js +8 -1
  37. package/dist/backend/notifications/NotificationItem.js.map +2 -2
  38. package/dist/portal/PortalShell.js +2 -2
  39. package/dist/portal/PortalShell.js.map +2 -2
  40. package/dist/primitives/calendar.js +69 -14
  41. package/dist/primitives/calendar.js.map +2 -2
  42. package/package.json +4 -5
  43. package/src/backend/CrudForm.tsx +30 -8
  44. package/src/backend/DataTable.tsx +95 -20
  45. package/src/backend/RowActions.tsx +114 -6
  46. package/src/backend/__tests__/AttachmentsSection.upload.test.tsx +183 -0
  47. package/src/backend/__tests__/CrudForm.fieldInjection.test.tsx +139 -2
  48. package/src/backend/__tests__/CrudForm.hiddenGroups.test.tsx +1 -1
  49. package/src/backend/__tests__/DataTable.extensions.test.tsx +30 -0
  50. package/src/backend/__tests__/DataTable.perspectiveReconcile.test.tsx +320 -2
  51. package/src/backend/__tests__/DataTable.stickyResponsive.test.tsx +19 -0
  52. package/src/backend/__tests__/DevRuntimeDiagnosticsBanner.test.tsx +117 -0
  53. package/src/backend/__tests__/NotesSection.test.tsx +138 -0
  54. package/src/backend/__tests__/RowActions.test.tsx +202 -0
  55. package/src/backend/charts/KpiCard.tsx +1 -0
  56. package/src/backend/charts/__tests__/KpiCard.test.tsx +20 -0
  57. package/src/backend/detail/AttachmentsSection.tsx +17 -5
  58. package/src/backend/detail/NotesSection.tsx +122 -66
  59. package/src/backend/dev/DevRuntimeDiagnosticsBanner.tsx +24 -1
  60. package/src/backend/icons/lucideRegistry.generated.tsx +26 -0
  61. package/src/backend/injection/__tests__/eventBridge.test.tsx +47 -1
  62. package/src/backend/injection/eventBridge.ts +35 -0
  63. package/src/backend/inputs/LookupSelect.tsx +32 -3
  64. package/src/backend/inputs/PhoneNumberField.tsx +1 -1
  65. package/src/backend/inputs/TagsInput.tsx +24 -3
  66. package/src/backend/inputs/__tests__/LookupSelect.test.tsx +166 -1
  67. package/src/backend/inputs/__tests__/PhoneNumberField.typeahead.test.tsx +43 -0
  68. package/src/backend/inputs/__tests__/TagsInput.test.tsx +81 -0
  69. package/src/backend/messages/EmailThreadsPanel.tsx +21 -1
  70. package/src/backend/messages/MessageComposer.tsx +1 -0
  71. package/src/backend/messages/__tests__/EmailThreadsPanel.senderLabel.test.tsx +99 -0
  72. package/src/backend/messages/__tests__/MessageComposer.test.tsx +44 -0
  73. package/src/backend/messages/__tests__/useMessageCompose.test.tsx +69 -0
  74. package/src/backend/messages/__tests__/useMessageComposeSender.test.tsx +208 -0
  75. package/src/backend/messages/index.ts +1 -0
  76. package/src/backend/messages/message-compose-form-groups.tsx +41 -0
  77. package/src/backend/messages/message-composer.types.ts +26 -0
  78. package/src/backend/messages/useMessageCompose.ts +46 -0
  79. package/src/backend/messages/useMessageComposeOperations.ts +8 -0
  80. package/src/backend/notifications/NotificationItem.tsx +7 -0
  81. package/src/backend/notifications/__tests__/NotificationItem.customRenderer.test.tsx +139 -0
  82. package/src/portal/PortalShell.tsx +7 -4
  83. package/src/portal/__tests__/PortalShell.test.tsx +29 -1
  84. package/src/primitives/__tests__/calendar.test.tsx +174 -1
  85. package/src/primitives/calendar.tsx +168 -96
@@ -7,7 +7,9 @@ import { ChevronLeft, ChevronRight } from 'lucide-react'
7
7
  import { format } from 'date-fns/format'
8
8
  import type { Locale } from 'date-fns/locale'
9
9
  import { cn } from '@open-mercato/shared/lib/utils'
10
- import { useT } from '@open-mercato/shared/lib/i18n/context'
10
+ import { useOptionalT, useT } from '@open-mercato/shared/lib/i18n/context'
11
+ import type { TranslateFn } from '@open-mercato/shared/lib/i18n/context'
12
+ import { createTranslator } from '@open-mercato/shared/lib/i18n/translate'
11
13
  import { CompactButton } from './compact-button'
12
14
 
13
15
  export type CalendarProps = DayPickerProps & { daySize?: 36 | 40 }
@@ -46,6 +48,16 @@ export function CalendarMonthSelector({
46
48
  const STANDALONE_MONTH_YEAR = 'LLLL yyyy'
47
49
  const STANDALONE_MONTH_SHORT = 'LLL'
48
50
 
51
+ const fallbackTranslate = createTranslator({})
52
+
53
+ function useCalendarT(): TranslateFn {
54
+ return useOptionalT() ?? fallbackTranslate
55
+ }
56
+
57
+ type CalendarLabels = DayPickerProps['labels']
58
+
59
+ const CalendarLabelsContext = React.createContext<CalendarLabels>(undefined)
60
+
49
61
  const navButtonClassName = cn(
50
62
  'h-9 w-9 inline-flex items-center justify-center rounded-md shrink-0',
51
63
  'border border-border bg-background text-muted-foreground transition-colors',
@@ -61,6 +73,8 @@ function MonthNavButton({
61
73
  direction: 'prev' | 'next'
62
74
  locale?: Locale
63
75
  }) {
76
+ const t = useCalendarT()
77
+ const labels = React.useContext(CalendarLabelsContext)
64
78
  const dayPicker = useDayPicker() as unknown as {
65
79
  previousMonth?: Date
66
80
  nextMonth?: Date
@@ -69,7 +83,12 @@ function MonthNavButton({
69
83
  const target = direction === 'prev' ? dayPicker.previousMonth : dayPicker.nextMonth
70
84
  const Icon = direction === 'prev' ? ChevronLeft : ChevronRight
71
85
  const targetLabel = format(target ?? new Date(), STANDALONE_MONTH_YEAR, locale ? { locale } : undefined)
72
- const ariaLabel = `Go to ${direction === 'prev' ? 'previous' : 'next'} month: ${targetLabel}`
86
+ const customLabel = direction === 'prev' ? labels?.labelPrevious : labels?.labelNext
87
+ const ariaLabel = customLabel
88
+ ? customLabel(target)
89
+ : direction === 'prev'
90
+ ? t('ui.calendar.goToPreviousMonth', 'Go to previous month: {month}', { month: targetLabel })
91
+ : t('ui.calendar.goToNextMonth', 'Go to next month: {month}', { month: targetLabel })
73
92
  return (
74
93
  <button
75
94
  type="button"
@@ -89,6 +108,7 @@ function buildMonthCaption(
89
108
  locale: Locale | undefined,
90
109
  totalMonths: number,
91
110
  onOpenMonthGrid: (() => void) | null,
111
+ monthGridTriggerRef: React.Ref<HTMLButtonElement>,
92
112
  ) {
93
113
  return function MonthCaption({
94
114
  calendarMonth,
@@ -97,6 +117,7 @@ function buildMonthCaption(
97
117
  calendarMonth: CalendarMonth
98
118
  displayIndex?: number
99
119
  }) {
120
+ const t = useCalendarT()
100
121
  const label = format(calendarMonth.date, STANDALONE_MONTH_YEAR, locale ? { locale } : undefined)
101
122
  const index = typeof displayIndex === 'number' ? displayIndex : 0
102
123
  // For multi-month layouts (e.g. range pickers) only the leftmost month
@@ -117,9 +138,10 @@ function buildMonthCaption(
117
138
  )}
118
139
  {labelInteractive ? (
119
140
  <button
141
+ ref={monthGridTriggerRef}
120
142
  type="button"
121
143
  onClick={onOpenMonthGrid ?? undefined}
122
- aria-label={`${label} – open month and year navigation`}
144
+ aria-label={t('ui.calendar.openMonthYearNavigation', '{month} – open month and year navigation', { month: label })}
123
145
  className={cn(
124
146
  'flex-1 flex items-center justify-center h-9 rounded-md bg-muted px-3 text-sm font-medium',
125
147
  'transition-colors hover:bg-accent hover:text-accent-foreground',
@@ -152,14 +174,49 @@ function MonthGrid({
152
174
  locale,
153
175
  onSelectMonth,
154
176
  onClose,
177
+ onRestoreFocus,
155
178
  }: {
156
179
  initialYear: number
157
180
  selectedMonth: Date
158
181
  locale?: Locale
159
182
  onSelectMonth: (month: Date) => void
160
183
  onClose: () => void
184
+ onRestoreFocus: () => void
161
185
  }) {
186
+ const t = useCalendarT()
162
187
  const [year, setYear] = React.useState(initialYear)
188
+ const gridRef = React.useRef<HTMLDivElement>(null)
189
+ const selectedMonthRef = React.useRef<HTMLButtonElement>(null)
190
+ React.useEffect(() => {
191
+ selectedMonthRef.current?.focus()
192
+ return onRestoreFocus
193
+ }, [onRestoreFocus])
194
+
195
+ React.useEffect(() => {
196
+ const handleEscape = (event: KeyboardEvent) => {
197
+ if (event.key !== 'Escape' || !(event.target instanceof Node) || !gridRef.current?.contains(event.target)) return
198
+ event.preventDefault()
199
+ event.stopPropagation()
200
+ onClose()
201
+ }
202
+ window.addEventListener('keydown', handleEscape, true)
203
+ return () => window.removeEventListener('keydown', handleEscape, true)
204
+ }, [onClose])
205
+
206
+ const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
207
+ if (event.key !== 'Tab') return
208
+
209
+ const buttons = event.currentTarget.querySelectorAll<HTMLButtonElement>('button:not(:disabled)')
210
+ const firstButton = buttons[0]
211
+ const lastButton = buttons[buttons.length - 1]
212
+ if (event.shiftKey && document.activeElement === firstButton) {
213
+ event.preventDefault()
214
+ lastButton?.focus()
215
+ } else if (!event.shiftKey && document.activeElement === lastButton) {
216
+ event.preventDefault()
217
+ firstButton?.focus()
218
+ }
219
+ }
163
220
  const today = new Date()
164
221
  const monthLabels = React.useMemo(
165
222
  () =>
@@ -171,14 +228,16 @@ function MonthGrid({
171
228
  const yearLabel = format(new Date(year, 0, 1), 'yyyy', locale ? { locale } : undefined)
172
229
  return (
173
230
  <div
231
+ ref={gridRef}
174
232
  className="absolute inset-0 z-10 flex flex-col rounded-md bg-popover p-3"
175
233
  role="dialog"
176
- aria-label="Select month and year"
234
+ aria-label={t('ui.calendar.selectMonthAndYear', 'Select month and year')}
235
+ onKeyDown={handleKeyDown}
177
236
  >
178
237
  <div className="flex items-center justify-between gap-2 mb-3">
179
238
  <button
180
239
  type="button"
181
- aria-label={`Go to previous year: ${year - 1}`}
240
+ aria-label={t('ui.calendar.goToPreviousYear', 'Go to previous year: {year}', { year: year - 1 })}
182
241
  onClick={() => setYear((current) => current - 1)}
183
242
  className={navButtonClassName}
184
243
  >
@@ -187,7 +246,7 @@ function MonthGrid({
187
246
  <button
188
247
  type="button"
189
248
  onClick={onClose}
190
- aria-label={`${yearLabel} – back to day selection`}
249
+ aria-label={t('ui.calendar.backToDaySelection', '{year} – back to day selection', { year: yearLabel })}
191
250
  className={cn(
192
251
  'flex-1 flex items-center justify-center h-9 rounded-md bg-muted px-3 text-sm font-medium',
193
252
  'transition-colors hover:bg-accent hover:text-accent-foreground',
@@ -198,7 +257,7 @@ function MonthGrid({
198
257
  </button>
199
258
  <button
200
259
  type="button"
201
- aria-label={`Go to next year: ${year + 1}`}
260
+ aria-label={t('ui.calendar.goToNextYear', 'Go to next year: {year}', { year: year + 1 })}
202
261
  onClick={() => setYear((current) => current + 1)}
203
262
  className={navButtonClassName}
204
263
  >
@@ -214,6 +273,7 @@ function MonthGrid({
214
273
  return (
215
274
  <button
216
275
  key={monthIndex}
276
+ ref={monthIndex === selectedMonth.getMonth() ? selectedMonthRef : undefined}
217
277
  type="button"
218
278
  aria-pressed={isSelected}
219
279
  onClick={() => onSelectMonth(new Date(year, monthIndex, 1))}
@@ -248,6 +308,7 @@ export function Calendar({
248
308
  month,
249
309
  defaultMonth,
250
310
  onMonthChange,
311
+ labels,
251
312
  ...props
252
313
  }: CalendarProps) {
253
314
  // The month/year grid (fast navigation) is only meaningful for single-month
@@ -257,6 +318,10 @@ export function Calendar({
257
318
  () => month ?? defaultMonth ?? new Date(),
258
319
  )
259
320
  const [showMonthGrid, setShowMonthGrid] = React.useState(false)
321
+ const monthGridTriggerRef = React.useRef<HTMLButtonElement>(null)
322
+ const restoreDayViewFocus = React.useCallback(() => {
323
+ monthGridTriggerRef.current?.focus()
324
+ }, [])
260
325
 
261
326
  // Honor a controlled `month` prop when consumers drive navigation externally.
262
327
  React.useEffect(() => {
@@ -285,100 +350,107 @@ export function Calendar({
285
350
  locale as Locale | undefined,
286
351
  numberOfMonths,
287
352
  monthGridEnabled ? () => setShowMonthGrid(true) : null,
353
+ monthGridTriggerRef,
288
354
  ),
289
355
  [locale, numberOfMonths, monthGridEnabled],
290
356
  )
291
357
 
292
358
  return (
293
- <div className={monthGridEnabled ? 'relative' : 'contents'}>
294
- <div
295
- className={monthGridEnabled && showMonthGrid ? 'pointer-events-none' : 'contents'}
296
- aria-hidden={monthGridEnabled && showMonthGrid ? true : undefined}
297
- >
298
- <DayPicker
299
- showOutsideDays={showOutsideDays}
300
- fixedWeeks={fixedWeeks}
301
- pagedNavigation={pagedNavigation}
302
- locale={locale}
303
- numberOfMonths={numberOfMonths}
304
- month={displayMonth}
305
- onMonthChange={handleMonthChange}
306
- className={cn('p-3', className)}
307
- classNames={{
308
- months: 'flex flex-col sm:flex-row gap-4',
309
- month: 'space-y-2',
310
- month_caption: '',
311
- caption_label: 'sr-only',
312
- nav: 'sr-only',
313
- month_grid: 'w-full border-collapse',
314
- weekdays: 'flex',
315
- weekday: cn('text-muted-foreground rounded-md font-normal text-xs', daySize === 40 ? 'w-10' : 'w-9'),
316
- weeks: 'w-full border-collapse',
317
- week: 'flex w-full mt-1',
318
- day: cn('text-center text-sm p-0 relative focus-within:relative focus-within:z-20', daySize === 40 ? 'size-10' : 'size-9'),
319
- day_button: cn(
320
- 'p-0 font-normal aria-selected:opacity-100',
321
- daySize === 40 ? 'size-10' : 'size-9',
322
- 'inline-flex items-center justify-center rounded-md text-sm',
323
- 'transition-colors focus:outline-none focus-visible:outline-none disabled:pointer-events-none',
324
- // Focus indicator is a soft accent fill instead of a ring overlay — keyboard
325
- // users get a visible cue, but mouse-click focus does not leave a stuck ring
326
- // on top of the selected cell.
327
- 'hover:bg-accent hover:text-accent-foreground',
328
- 'focus-visible:bg-accent focus-visible:text-accent-foreground',
329
- ),
330
- // React-day-picker v9 applies `classNames.selected` / `range_*` to the day
331
- // CELL (`<td>`) wrapper, not to the inner `<button>`. To keep the parent
332
- // fill visible through interaction, we (a) paint the cell with the desired
333
- // bg/text, and (b) force the inner button to render transparent — so the
334
- // button's own hover/focus-visible bg overrides cannot cover the cell fill.
335
- selected: cn(
336
- '!bg-primary !text-primary-foreground rounded-md',
337
- '[&_button]:!bg-transparent [&_button]:!text-primary-foreground',
338
- '[&_button:hover]:!bg-transparent [&_button:hover]:!text-primary-foreground',
339
- '[&_button:focus-visible]:!bg-transparent [&_button:focus-visible]:!text-primary-foreground',
340
- ),
341
- range_start: cn(
342
- '!bg-primary !text-primary-foreground rounded-l-md !rounded-r-none',
343
- '[&_button]:!bg-transparent [&_button]:!text-primary-foreground',
344
- '[&_button:hover]:!bg-transparent [&_button:hover]:!text-primary-foreground',
345
- '[&_button:focus-visible]:!bg-transparent [&_button:focus-visible]:!text-primary-foreground',
346
- ),
347
- range_end: cn(
348
- '!bg-primary !text-primary-foreground rounded-r-md !rounded-l-none',
349
- '[&_button]:!bg-transparent [&_button]:!text-primary-foreground',
350
- '[&_button:hover]:!bg-transparent [&_button:hover]:!text-primary-foreground',
351
- '[&_button:focus-visible]:!bg-transparent [&_button:focus-visible]:!text-primary-foreground',
352
- ),
353
- range_middle: cn(
354
- '!bg-accent !text-accent-foreground !rounded-none',
355
- '[&_button]:!bg-transparent [&_button]:!text-accent-foreground',
356
- '[&_button:hover]:!bg-transparent [&_button:hover]:!text-accent-foreground',
357
- '[&_button:focus-visible]:!bg-transparent [&_button:focus-visible]:!text-accent-foreground',
358
- ),
359
- today: 'font-semibold text-primary rounded-md',
360
- outside:
361
- 'day-outside text-muted-foreground opacity-40 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30',
362
- disabled: 'text-muted-foreground opacity-50',
363
- hidden: 'invisible',
364
- ...classNames,
365
- }}
366
- components={{
367
- MonthCaption: monthCaption,
368
- ...components,
369
- }}
370
- {...props}
371
- />
359
+ <CalendarLabelsContext.Provider value={labels}>
360
+ <div className={monthGridEnabled ? 'relative' : 'contents'}>
361
+ <div
362
+ className={monthGridEnabled && showMonthGrid ? 'invisible pointer-events-none' : 'contents'}
363
+ aria-hidden={monthGridEnabled && showMonthGrid ? true : undefined}
364
+ inert={monthGridEnabled && showMonthGrid ? true : undefined}
365
+ >
366
+ <DayPicker
367
+ showOutsideDays={showOutsideDays}
368
+ fixedWeeks={fixedWeeks}
369
+ pagedNavigation={pagedNavigation}
370
+ hideNavigation
371
+ locale={locale}
372
+ labels={labels}
373
+ numberOfMonths={numberOfMonths}
374
+ month={displayMonth}
375
+ onMonthChange={handleMonthChange}
376
+ className={cn('p-3', className)}
377
+ classNames={{
378
+ months: 'flex flex-col sm:flex-row gap-4',
379
+ month: 'space-y-2',
380
+ month_caption: '',
381
+ caption_label: 'sr-only',
382
+ nav: 'sr-only',
383
+ month_grid: 'w-full border-collapse',
384
+ weekdays: 'flex',
385
+ weekday: cn('text-muted-foreground rounded-md font-normal text-xs', daySize === 40 ? 'w-10' : 'w-9'),
386
+ weeks: 'w-full border-collapse',
387
+ week: 'flex w-full mt-1',
388
+ day: cn('text-center text-sm p-0 relative focus-within:relative focus-within:z-20', daySize === 40 ? 'size-10' : 'size-9'),
389
+ day_button: cn(
390
+ 'p-0 font-normal aria-selected:opacity-100',
391
+ daySize === 40 ? 'size-10' : 'size-9',
392
+ 'inline-flex items-center justify-center rounded-md text-sm',
393
+ 'transition-colors focus:outline-none focus-visible:outline-none disabled:pointer-events-none',
394
+ // Focus indicator is a soft accent fill instead of a ring overlay — keyboard
395
+ // users get a visible cue, but mouse-click focus does not leave a stuck ring
396
+ // on top of the selected cell.
397
+ 'hover:bg-accent hover:text-accent-foreground',
398
+ 'focus-visible:bg-accent focus-visible:text-accent-foreground',
399
+ ),
400
+ // React-day-picker v9 applies `classNames.selected` / `range_*` to the day
401
+ // CELL (`<td>`) wrapper, not to the inner `<button>`. To keep the parent
402
+ // fill visible through interaction, we (a) paint the cell with the desired
403
+ // bg/text, and (b) force the inner button to render transparent — so the
404
+ // button's own hover/focus-visible bg overrides cannot cover the cell fill.
405
+ selected: cn(
406
+ '!bg-primary !text-primary-foreground rounded-md',
407
+ '[&_button]:!bg-transparent [&_button]:!text-primary-foreground',
408
+ '[&_button:hover]:!bg-transparent [&_button:hover]:!text-primary-foreground',
409
+ '[&_button:focus-visible]:!bg-transparent [&_button:focus-visible]:!text-primary-foreground',
410
+ ),
411
+ range_start: cn(
412
+ '!bg-primary !text-primary-foreground rounded-l-md !rounded-r-none',
413
+ '[&_button]:!bg-transparent [&_button]:!text-primary-foreground',
414
+ '[&_button:hover]:!bg-transparent [&_button:hover]:!text-primary-foreground',
415
+ '[&_button:focus-visible]:!bg-transparent [&_button:focus-visible]:!text-primary-foreground',
416
+ ),
417
+ range_end: cn(
418
+ '!bg-primary !text-primary-foreground rounded-r-md !rounded-l-none',
419
+ '[&_button]:!bg-transparent [&_button]:!text-primary-foreground',
420
+ '[&_button:hover]:!bg-transparent [&_button:hover]:!text-primary-foreground',
421
+ '[&_button:focus-visible]:!bg-transparent [&_button:focus-visible]:!text-primary-foreground',
422
+ ),
423
+ range_middle: cn(
424
+ '!bg-accent !text-accent-foreground !rounded-none',
425
+ '[&_button]:!bg-transparent [&_button]:!text-accent-foreground',
426
+ '[&_button:hover]:!bg-transparent [&_button:hover]:!text-accent-foreground',
427
+ '[&_button:focus-visible]:!bg-transparent [&_button:focus-visible]:!text-accent-foreground',
428
+ ),
429
+ today: 'font-semibold text-primary rounded-md',
430
+ outside:
431
+ 'day-outside text-muted-foreground opacity-40 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30',
432
+ disabled: 'text-muted-foreground opacity-50',
433
+ hidden: 'invisible',
434
+ ...classNames,
435
+ }}
436
+ components={{
437
+ MonthCaption: monthCaption,
438
+ ...components,
439
+ }}
440
+ {...props}
441
+ />
442
+ </div>
443
+ {monthGridEnabled && showMonthGrid ? (
444
+ <MonthGrid
445
+ initialYear={displayMonth.getFullYear()}
446
+ selectedMonth={displayMonth}
447
+ locale={locale as Locale | undefined}
448
+ onSelectMonth={handleSelectMonth}
449
+ onClose={() => setShowMonthGrid(false)}
450
+ onRestoreFocus={restoreDayViewFocus}
451
+ />
452
+ ) : null}
372
453
  </div>
373
- {monthGridEnabled && showMonthGrid ? (
374
- <MonthGrid
375
- initialYear={displayMonth.getFullYear()}
376
- selectedMonth={displayMonth}
377
- locale={locale as Locale | undefined}
378
- onSelectMonth={handleSelectMonth}
379
- onClose={() => setShowMonthGrid(false)}
380
- />
381
- ) : null}
382
- </div>
454
+ </CalendarLabelsContext.Provider>
383
455
  )
384
456
  }