@flipdish/ui-library 0.21.0 → 0.22.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 (206) hide show
  1. package/dist/components/__internal__/statusIcon/statusIcon.types.d.ts +12 -0
  2. package/dist/components/__internal__/statusIcon/statusIcon.types.d.ts.map +1 -0
  3. package/dist/components/__internal__/statusIcon/statusIcon.utils.cjs +21 -0
  4. package/dist/components/__internal__/statusIcon/statusIcon.utils.cjs.map +1 -0
  5. package/dist/components/__internal__/statusIcon/statusIcon.utils.d.ts +5 -0
  6. package/dist/components/__internal__/statusIcon/statusIcon.utils.d.ts.map +1 -0
  7. package/dist/components/__internal__/statusIcon/statusIcon.utils.js +19 -0
  8. package/dist/components/__internal__/statusIcon/statusIcon.utils.js.map +1 -0
  9. package/dist/components/atoms/BackgroundPattern/components/grid-check.cjs +14 -7
  10. package/dist/components/atoms/BackgroundPattern/components/grid-check.cjs.map +1 -1
  11. package/dist/components/atoms/BackgroundPattern/components/grid-check.d.ts.map +1 -1
  12. package/dist/components/atoms/BackgroundPattern/components/grid-check.js +14 -7
  13. package/dist/components/atoms/BackgroundPattern/components/grid-check.js.map +1 -1
  14. package/dist/components/atoms/BackgroundPattern/index.d.ts.map +1 -1
  15. package/dist/components/molecules/Alert/index.cjs +3 -11
  16. package/dist/components/molecules/Alert/index.cjs.map +1 -1
  17. package/dist/components/molecules/Alert/index.d.ts +3 -2
  18. package/dist/components/molecules/Alert/index.d.ts.map +1 -1
  19. package/dist/components/molecules/Alert/index.js +3 -11
  20. package/dist/components/molecules/Alert/index.js.map +1 -1
  21. package/dist/components/molecules/Notification/index.cjs +3 -10
  22. package/dist/components/molecules/Notification/index.cjs.map +1 -1
  23. package/dist/components/molecules/Notification/index.d.ts +2 -1
  24. package/dist/components/molecules/Notification/index.d.ts.map +1 -1
  25. package/dist/components/molecules/Notification/index.js +3 -10
  26. package/dist/components/molecules/Notification/index.js.map +1 -1
  27. package/dist/components/molecules/Pagination/index.cjs +4 -4
  28. package/dist/components/molecules/Pagination/index.cjs.map +1 -1
  29. package/dist/components/molecules/Pagination/index.js +5 -5
  30. package/dist/components/molecules/Pagination/index.js.map +1 -1
  31. package/dist/components/organisms/Calendar/calendar.utils.cjs +41 -0
  32. package/dist/components/organisms/Calendar/calendar.utils.cjs.map +1 -1
  33. package/dist/components/organisms/Calendar/calendar.utils.d.ts +20 -0
  34. package/dist/components/organisms/Calendar/calendar.utils.d.ts.map +1 -1
  35. package/dist/components/organisms/Calendar/calendar.utils.js +37 -1
  36. package/dist/components/organisms/Calendar/calendar.utils.js.map +1 -1
  37. package/dist/components/organisms/Calendar/components/calendar-day-view.cjs +86 -0
  38. package/dist/components/organisms/Calendar/components/calendar-day-view.cjs.map +1 -0
  39. package/dist/components/organisms/Calendar/components/calendar-day-view.d.ts +20 -0
  40. package/dist/components/organisms/Calendar/components/calendar-day-view.d.ts.map +1 -0
  41. package/dist/components/organisms/Calendar/components/calendar-day-view.js +84 -0
  42. package/dist/components/organisms/Calendar/components/calendar-day-view.js.map +1 -0
  43. package/dist/components/organisms/Calendar/components/calendar-mobile-day-grid.cjs +21 -0
  44. package/dist/components/organisms/Calendar/components/calendar-mobile-day-grid.cjs.map +1 -0
  45. package/dist/components/organisms/Calendar/components/calendar-mobile-day-grid.d.ts +16 -0
  46. package/dist/components/organisms/Calendar/components/calendar-mobile-day-grid.d.ts.map +1 -0
  47. package/dist/components/organisms/Calendar/components/calendar-mobile-day-grid.js +19 -0
  48. package/dist/components/organisms/Calendar/components/calendar-mobile-day-grid.js.map +1 -0
  49. package/dist/components/organisms/Calendar/components/calendar-month-view.cjs +265 -0
  50. package/dist/components/organisms/Calendar/components/calendar-month-view.cjs.map +1 -0
  51. package/dist/components/organisms/Calendar/components/calendar-month-view.d.ts +18 -0
  52. package/dist/components/organisms/Calendar/components/calendar-month-view.d.ts.map +1 -0
  53. package/dist/components/organisms/Calendar/components/calendar-month-view.js +263 -0
  54. package/dist/components/organisms/Calendar/components/calendar-month-view.js.map +1 -0
  55. package/dist/components/organisms/Calendar/components/calendar-positioned-event.cjs +41 -0
  56. package/dist/components/organisms/Calendar/components/calendar-positioned-event.cjs.map +1 -0
  57. package/dist/components/organisms/Calendar/components/calendar-positioned-event.d.ts +15 -0
  58. package/dist/components/organisms/Calendar/components/calendar-positioned-event.d.ts.map +1 -0
  59. package/dist/components/organisms/Calendar/components/calendar-positioned-event.js +39 -0
  60. package/dist/components/organisms/Calendar/components/calendar-positioned-event.js.map +1 -0
  61. package/dist/components/organisms/Calendar/components/calendar-week-view-day.cjs +23 -0
  62. package/dist/components/organisms/Calendar/components/calendar-week-view-day.cjs.map +1 -0
  63. package/dist/components/organisms/Calendar/components/calendar-week-view-day.d.ts +15 -0
  64. package/dist/components/organisms/Calendar/components/calendar-week-view-day.d.ts.map +1 -0
  65. package/dist/components/organisms/Calendar/components/calendar-week-view-day.js +21 -0
  66. package/dist/components/organisms/Calendar/components/calendar-week-view-day.js.map +1 -0
  67. package/dist/components/organisms/Calendar/components/calendar-week-view.cjs +77 -0
  68. package/dist/components/organisms/Calendar/components/calendar-week-view.cjs.map +1 -0
  69. package/dist/components/organisms/Calendar/components/calendar-week-view.d.ts +20 -0
  70. package/dist/components/organisms/Calendar/components/calendar-week-view.d.ts.map +1 -0
  71. package/dist/components/organisms/Calendar/components/calendar-week-view.js +75 -0
  72. package/dist/components/organisms/Calendar/components/calendar-week-view.js.map +1 -0
  73. package/dist/components/organisms/Calendar/index.cjs +4 -504
  74. package/dist/components/organisms/Calendar/index.cjs.map +1 -1
  75. package/dist/components/organisms/Calendar/index.d.ts +1 -1
  76. package/dist/components/organisms/Calendar/index.d.ts.map +1 -1
  77. package/dist/components/organisms/Calendar/index.js +6 -506
  78. package/dist/components/organisms/Calendar/index.js.map +1 -1
  79. package/dist/components/organisms/Charts/components/activity-gauge.cjs +4 -0
  80. package/dist/components/organisms/Charts/components/activity-gauge.cjs.map +1 -1
  81. package/dist/components/organisms/Charts/components/activity-gauge.d.ts +4 -0
  82. package/dist/components/organisms/Charts/components/activity-gauge.d.ts.map +1 -1
  83. package/dist/components/organisms/Charts/components/activity-gauge.js +4 -0
  84. package/dist/components/organisms/Charts/components/activity-gauge.js.map +1 -1
  85. package/dist/components/organisms/Charts/components/bar-chart.cjs +1 -1
  86. package/dist/components/organisms/Charts/components/bar-chart.cjs.map +1 -1
  87. package/dist/components/organisms/Charts/components/bar-chart.d.ts +1 -2
  88. package/dist/components/organisms/Charts/components/bar-chart.d.ts.map +1 -1
  89. package/dist/components/organisms/Charts/components/bar-chart.js +2 -2
  90. package/dist/components/organisms/Charts/components/bar-chart.js.map +1 -1
  91. package/dist/components/organisms/Charts/components/charts-base.cjs +1 -2
  92. package/dist/components/organisms/Charts/components/charts-base.cjs.map +1 -1
  93. package/dist/components/organisms/Charts/components/charts-base.d.ts +13 -2
  94. package/dist/components/organisms/Charts/components/charts-base.d.ts.map +1 -1
  95. package/dist/components/organisms/Charts/components/charts-base.js +1 -2
  96. package/dist/components/organisms/Charts/components/charts-base.js.map +1 -1
  97. package/dist/components/organisms/Charts/components/line-chart.cjs +1 -1
  98. package/dist/components/organisms/Charts/components/line-chart.cjs.map +1 -1
  99. package/dist/components/organisms/Charts/components/line-chart.d.ts +1 -2
  100. package/dist/components/organisms/Charts/components/line-chart.d.ts.map +1 -1
  101. package/dist/components/organisms/Charts/components/line-chart.js +2 -2
  102. package/dist/components/organisms/Charts/components/line-chart.js.map +1 -1
  103. package/dist/components/organisms/Charts/components/pie-chart.cjs +5 -0
  104. package/dist/components/organisms/Charts/components/pie-chart.cjs.map +1 -1
  105. package/dist/components/organisms/Charts/components/pie-chart.d.ts +6 -2
  106. package/dist/components/organisms/Charts/components/pie-chart.d.ts.map +1 -1
  107. package/dist/components/organisms/Charts/components/pie-chart.js +5 -0
  108. package/dist/components/organisms/Charts/components/pie-chart.js.map +1 -1
  109. package/dist/components/organisms/Charts/components/radar-chart.cjs +3 -4
  110. package/dist/components/organisms/Charts/components/radar-chart.cjs.map +1 -1
  111. package/dist/components/organisms/Charts/components/radar-chart.d.ts.map +1 -1
  112. package/dist/components/organisms/Charts/components/radar-chart.js +3 -4
  113. package/dist/components/organisms/Charts/components/radar-chart.js.map +1 -1
  114. package/dist/components/organisms/DatePicker/components/range-calendar.cjs +1 -1
  115. package/dist/components/organisms/DatePicker/components/range-calendar.cjs.map +1 -1
  116. package/dist/components/organisms/DatePicker/components/range-calendar.js +2 -2
  117. package/dist/components/organisms/DatePicker/components/range-calendar.js.map +1 -1
  118. package/dist/components/organisms/GradientPicker/components/gradient-area.cjs +157 -0
  119. package/dist/components/organisms/GradientPicker/components/gradient-area.cjs.map +1 -0
  120. package/dist/components/organisms/GradientPicker/components/gradient-area.d.ts +17 -0
  121. package/dist/components/organisms/GradientPicker/components/gradient-area.d.ts.map +1 -0
  122. package/dist/components/organisms/GradientPicker/components/gradient-area.js +155 -0
  123. package/dist/components/organisms/GradientPicker/components/gradient-area.js.map +1 -0
  124. package/dist/components/organisms/GradientPicker/components/gradient-reverse-button.cjs +17 -0
  125. package/dist/components/organisms/GradientPicker/components/gradient-reverse-button.cjs.map +1 -0
  126. package/dist/components/organisms/GradientPicker/components/gradient-reverse-button.d.ts +3 -0
  127. package/dist/components/organisms/GradientPicker/components/gradient-reverse-button.d.ts.map +1 -0
  128. package/dist/components/organisms/GradientPicker/components/gradient-reverse-button.js +15 -0
  129. package/dist/components/organisms/GradientPicker/components/gradient-reverse-button.js.map +1 -0
  130. package/dist/components/organisms/GradientPicker/components/gradient-slider.cjs +71 -0
  131. package/dist/components/organisms/GradientPicker/components/gradient-slider.cjs.map +1 -0
  132. package/dist/components/organisms/GradientPicker/components/gradient-slider.d.ts +7 -0
  133. package/dist/components/organisms/GradientPicker/components/gradient-slider.d.ts.map +1 -0
  134. package/dist/components/organisms/GradientPicker/components/gradient-slider.js +69 -0
  135. package/dist/components/organisms/GradientPicker/components/gradient-slider.js.map +1 -0
  136. package/dist/components/organisms/GradientPicker/components/gradient-stop-list.cjs +18 -0
  137. package/dist/components/organisms/GradientPicker/components/gradient-stop-list.cjs.map +1 -0
  138. package/dist/components/organisms/GradientPicker/components/gradient-stop-list.d.ts +4 -0
  139. package/dist/components/organisms/GradientPicker/components/gradient-stop-list.d.ts.map +1 -0
  140. package/dist/components/organisms/GradientPicker/components/gradient-stop-list.js +16 -0
  141. package/dist/components/organisms/GradientPicker/components/gradient-stop-list.js.map +1 -0
  142. package/dist/components/organisms/GradientPicker/components/gradient-stop-row.cjs +49 -0
  143. package/dist/components/organisms/GradientPicker/components/gradient-stop-row.cjs.map +1 -0
  144. package/dist/components/organisms/GradientPicker/components/gradient-stop-row.d.ts +6 -0
  145. package/dist/components/organisms/GradientPicker/components/gradient-stop-row.d.ts.map +1 -0
  146. package/dist/components/organisms/GradientPicker/components/gradient-stop-row.js +47 -0
  147. package/dist/components/organisms/GradientPicker/components/gradient-stop-row.js.map +1 -0
  148. package/dist/components/organisms/GradientPicker/components/gradient-type-select.cjs +28 -0
  149. package/dist/components/organisms/GradientPicker/components/gradient-type-select.cjs.map +1 -0
  150. package/dist/components/organisms/GradientPicker/components/gradient-type-select.d.ts +3 -0
  151. package/dist/components/organisms/GradientPicker/components/gradient-type-select.d.ts.map +1 -0
  152. package/dist/components/organisms/GradientPicker/components/gradient-type-select.js +26 -0
  153. package/dist/components/organisms/GradientPicker/components/gradient-type-select.js.map +1 -0
  154. package/dist/components/organisms/GradientPicker/components/position-input.cjs +43 -0
  155. package/dist/components/organisms/GradientPicker/components/position-input.cjs.map +1 -0
  156. package/dist/components/organisms/GradientPicker/components/position-input.d.ts +6 -0
  157. package/dist/components/organisms/GradientPicker/components/position-input.d.ts.map +1 -0
  158. package/dist/components/organisms/GradientPicker/components/position-input.js +41 -0
  159. package/dist/components/organisms/GradientPicker/components/position-input.js.map +1 -0
  160. package/dist/components/organisms/GradientPicker/index.cjs +11 -327
  161. package/dist/components/organisms/GradientPicker/index.cjs.map +1 -1
  162. package/dist/components/organisms/GradientPicker/index.d.ts +7 -8
  163. package/dist/components/organisms/GradientPicker/index.d.ts.map +1 -1
  164. package/dist/components/organisms/GradientPicker/index.js +7 -323
  165. package/dist/components/organisms/GradientPicker/index.js.map +1 -1
  166. package/dist/components/organisms/HeaderNavigation/components/nav-account-card.cjs +1 -1
  167. package/dist/components/organisms/HeaderNavigation/components/nav-account-card.cjs.map +1 -1
  168. package/dist/components/organisms/HeaderNavigation/components/nav-account-card.js +2 -2
  169. package/dist/components/organisms/HeaderNavigation/components/nav-account-card.js.map +1 -1
  170. package/dist/components/organisms/HeaderNavigation/components/nav-button.cjs +1 -1
  171. package/dist/components/organisms/HeaderNavigation/components/nav-button.cjs.map +1 -1
  172. package/dist/components/organisms/HeaderNavigation/components/nav-button.d.ts.map +1 -1
  173. package/dist/components/organisms/HeaderNavigation/components/nav-button.js +1 -1
  174. package/dist/components/organisms/HeaderNavigation/components/nav-button.js.map +1 -1
  175. package/dist/components/organisms/SidebarNavigation/components/nav-account-card.cjs +1 -1
  176. package/dist/components/organisms/SidebarNavigation/components/nav-account-card.cjs.map +1 -1
  177. package/dist/components/organisms/SidebarNavigation/components/nav-account-card.js +2 -2
  178. package/dist/components/organisms/SidebarNavigation/components/nav-account-card.js.map +1 -1
  179. package/dist/components/organisms/SidebarNavigation/index.cjs +1 -1
  180. package/dist/components/organisms/SidebarNavigation/index.cjs.map +1 -1
  181. package/dist/components/organisms/SidebarNavigation/index.js +2 -2
  182. package/dist/components/organisms/SidebarNavigation/index.js.map +1 -1
  183. package/dist/components/organisms/VideoPlayer/hooks/useVideoPlayback.cjs +169 -0
  184. package/dist/components/organisms/VideoPlayer/hooks/useVideoPlayback.cjs.map +1 -0
  185. package/dist/components/organisms/VideoPlayer/hooks/useVideoPlayback.d.ts +34 -0
  186. package/dist/components/organisms/VideoPlayer/hooks/useVideoPlayback.d.ts.map +1 -0
  187. package/dist/components/organisms/VideoPlayer/hooks/useVideoPlayback.js +167 -0
  188. package/dist/components/organisms/VideoPlayer/hooks/useVideoPlayback.js.map +1 -0
  189. package/dist/components/organisms/VideoPlayer/hooks/useVideoVolume.cjs +75 -0
  190. package/dist/components/organisms/VideoPlayer/hooks/useVideoVolume.cjs.map +1 -0
  191. package/dist/components/organisms/VideoPlayer/hooks/useVideoVolume.d.ts +16 -0
  192. package/dist/components/organisms/VideoPlayer/hooks/useVideoVolume.d.ts.map +1 -0
  193. package/dist/components/organisms/VideoPlayer/hooks/useVideoVolume.js +73 -0
  194. package/dist/components/organisms/VideoPlayer/hooks/useVideoVolume.js.map +1 -0
  195. package/dist/components/organisms/VideoPlayer/index.cjs +9 -182
  196. package/dist/components/organisms/VideoPlayer/index.cjs.map +1 -1
  197. package/dist/components/organisms/VideoPlayer/index.d.ts.map +1 -1
  198. package/dist/components/organisms/VideoPlayer/index.js +10 -183
  199. package/dist/components/organisms/VideoPlayer/index.js.map +1 -1
  200. package/dist/hooks/useBreakpoints/index.cjs +4 -1
  201. package/dist/hooks/useBreakpoints/index.cjs.map +1 -1
  202. package/dist/hooks/useBreakpoints/index.d.ts +2 -0
  203. package/dist/hooks/useBreakpoints/index.d.ts.map +1 -1
  204. package/dist/hooks/useBreakpoints/index.js +4 -2
  205. package/dist/hooks/useBreakpoints/index.js.map +1 -1
  206. package/package.json +1 -1
@@ -1,519 +1,19 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
- import { ChevronLeft, ChevronRight, Copy01, Trash01, Edit01, Calendar as Calendar$2, Clock, BellRinging01 } from '@flipdish/ui-icons';
3
- import { Avatar, AvatarAddButton } from '@flipdish/ui-library/components/atoms/Avatar';
4
- import { Button } from '@flipdish/ui-library/components/atoms/Button';
5
- import { ButtonUtility } from '@flipdish/ui-library/components/atoms/UtilityButton';
6
2
  import { cx } from '@flipdish/ui-library/utilities/cx';
7
- import { useResolvedTestId, TestIdProvider, useTestIdPrefix, joinTestId, slugifyTestId } from '@flipdish/ui-library/utilities/testIdUtils';
8
- import { getLocalTimeZone, today, now, parseAbsoluteToLocal, startOfWeek, startOfMonth, endOfMonth, endOfWeek, toCalendarDate, isSameMonth, toZoned, isSameDay, isToday, CalendarDateTime, CalendarDate } from '@internationalized/date';
3
+ import { useResolvedTestId, TestIdProvider } from '@flipdish/ui-library/utilities/testIdUtils';
4
+ import { getLocalTimeZone, today, now, parseAbsoluteToLocal, startOfWeek } from '@internationalized/date';
9
5
  import { useLocale, useDateFormatter } from '@react-aria/i18n';
10
- import { useMemo, useState, useEffect, useCallback, useRef, useLayoutEffect } from 'react';
11
- import { Calendar as Calendar$1, Heading, CalendarGrid, CalendarGridHeader, CalendarHeaderCell, CalendarGridBody } from 'react-aria-components';
12
- import { CalendarCell } from '../DatePicker/components/cell.js';
13
- import { CalendarColumnHeader } from './components/calendar-column-header.js';
14
- import { CalendarDwViewCell } from './components/calendar-dw-view-cell.js';
15
- import { CalendarDwViewEvent } from './components/calendar-dw-view-event.js';
6
+ import { useMemo, useState, useEffect } from 'react';
7
+ import { DayView } from './components/calendar-day-view.js';
16
8
  import { CalendarHeader } from './components/calendar-header.js';
17
- import { CalendarMonthViewCell } from './components/calendar-month-view-cell.js';
18
- import { CalendarMonthViewEvent } from './components/calendar-month-view-event.js';
19
- import { CalendarRowLabel } from './components/calendar-row-label.js';
20
- import { CalendarTimeMarker } from './components/calendar-time-marker.js';
9
+ import { MonthView } from './components/calendar-month-view.js';
10
+ import { WeekView } from './components/calendar-week-view.js';
21
11
 
22
- const SLOT_HEIGHT = 48;
23
12
  const viewOptions = [
24
13
  { value: 'day', label: 'Day view', addon: '⌘D' },
25
14
  { value: 'week', label: 'Week view', addon: '⌘W' },
26
15
  { value: 'month', label: 'Month view', addon: '⌘M' },
27
16
  ];
28
- const getStartOfDay = (date, timeZone) => {
29
- const zoned = date instanceof CalendarDate ? toZoned(date, timeZone) : date;
30
- return zoned.set({ hour: 0, minute: 0, second: 0, millisecond: 0 });
31
- };
32
- const getEndOfDay = (date, timeZone) => {
33
- const zoned = date instanceof CalendarDate ? toZoned(date, timeZone) : date;
34
- return zoned.set({ hour: 23, minute: 59, second: 59, millisecond: 999 });
35
- };
36
- const getDaysInVisibleMonthGrid = (anchorDate, locale) => {
37
- const monthStart = startOfMonth(anchorDate);
38
- const monthEnd = endOfMonth(anchorDate);
39
- const weekStart = startOfWeek(monthStart, locale);
40
- const weekEnd = endOfWeek(monthEnd, locale);
41
- const days = [];
42
- let day = weekStart;
43
- while (day.compare(weekEnd) <= 0) {
44
- days.push(day);
45
- day = day.add({ days: 1 });
46
- }
47
- return days;
48
- };
49
- const getEventsForDay = (allEvents, targetDate, timeZone) => {
50
- const dayStart = getStartOfDay(targetDate, timeZone);
51
- const dayEnd = getEndOfDay(targetDate, timeZone);
52
- return allEvents
53
- .filter((event) => {
54
- // Check if event interval overlaps with the day interval
55
- return event.start.compare(dayEnd) < 0 && event.end.compare(dayStart) > 0;
56
- })
57
- .sort((a, b) => a.start.compare(b.start));
58
- };
59
- const PositionedEvent = ({ event, dayStart, timeZone, slotHeight, overlapIndex, setSelectedDate, timeFormatter }) => {
60
- const prefix = useTestIdPrefix();
61
- const eventTestId = prefix ? joinTestId(prefix, `event-${slugifyTestId(event.id)}`) : undefined;
62
- const formatTime = (dateTime) => timeFormatter.format(dateTime.toDate());
63
- const startZoned = event.start;
64
- const endZoned = event.end;
65
- // Clamp event start/end times to the visible day boundaries for height calculation
66
- const dayEnd = getEndOfDay(dayStart, timeZone); // Calculate dayEnd based on dayStart
67
- const clampedStart = startZoned.compare(dayStart) < 0 ? dayStart : startZoned;
68
- const clampedEnd = endZoned.compare(dayEnd) > 0 ? dayEnd : endZoned;
69
- const startMinutes = clampedStart.hour * 60 + clampedStart.minute;
70
- const endMinutes = clampedEnd.hour * 60 + clampedEnd.minute;
71
- const durationMinutes = Math.max(15, endMinutes - startMinutes);
72
- // Position based on 30-min slots (slotHeight = 24px)
73
- const top = (startMinutes / 30) * slotHeight;
74
- const height = Math.max(slotHeight / 2, (durationMinutes / 30) * slotHeight); // Min height 15min
75
- // Basic overlap handling - simple horizontal offset
76
- // const horizontalOffset = overlapIndex * 10;
77
- const horizontalOffset = 0;
78
- const displayTime = durationMinutes > 30;
79
- const supportingText = displayTime ? formatTime(startZoned) : undefined;
80
- return (
81
- // biome-ignore lint/a11y/noStaticElementInteractions: This is a static element that is used to position the event
82
- // biome-ignore lint/a11y/useKeyWithClickEvents: This is a static element that is used to position the event
83
- jsx("div", { className: "absolute w-full px-1.5 py-1.5", style: {
84
- top: `${top}px`,
85
- height: `${height}px`,
86
- left: `${horizontalOffset}px`,
87
- zIndex: overlapIndex,
88
- }, onClick: () => setSelectedDate(toCalendarDate(startZoned)), children: jsx(CalendarDwViewEvent, { label: event.title, supportingText: supportingText, color: event.color, withDot: event.dot, "data-testid": eventTestId }) }, event.id));
89
- };
90
- const MonthView = ({ currentMonthDate, selectedDate, zonedEvents, locale, timeZone, setSelectedDate, fullDateFormatter, shortWeekdayFormatter, timeFormatter, // Needed for formatTime inside
91
- className, }) => {
92
- const prefix = useTestIdPrefix();
93
- const eventTestId = (eventId) => (prefix ? joinTestId(prefix, `event-${slugifyTestId(eventId)}`) : undefined);
94
- const monthStart = startOfMonth(currentMonthDate);
95
- const monthEnd = endOfMonth(currentMonthDate);
96
- const weekStart = startOfWeek(monthStart, locale);
97
- const weekEnd = endOfWeek(monthEnd, locale);
98
- const formatTimeForMonth = (dateTime) => timeFormatter.format(dateTime.toDate());
99
- const days = useMemo(() => getDaysInVisibleMonthGrid(currentMonthDate, locale), [currentMonthDate, locale]);
100
- const weekdays = useMemo(() => {
101
- const firstDay = startOfWeek(today(timeZone), locale);
102
- return Array.from({ length: 7 }).map((_, i) => shortWeekdayFormatter.format(firstDay.add({ days: i }).toDate(timeZone)));
103
- }, [locale, timeZone, shortWeekdayFormatter]); // Dependencies are passed in
104
- const visibleEvents = zonedEvents.filter((event) => {
105
- const eventStartDay = toCalendarDate(event.start);
106
- const eventEndDay = toCalendarDate(event.end);
107
- return eventStartDay.compare(weekEnd) <= 0 && eventEndDay.compare(weekStart) >= 0;
108
- });
109
- // Pre-calculate events for each day in the visible grid
110
- const eventsByDay = useMemo(() => {
111
- const map = new Map();
112
- days.forEach((day) => {
113
- const dateKey = day.toString();
114
- const dayEvents = getEventsForDay(visibleEvents, day, timeZone);
115
- map.set(dateKey, dayEvents);
116
- });
117
- return map;
118
- }, [days, visibleEvents, timeZone]); // Dependencies: days array, visible events, timezone
119
- const MAX_EVENTS_PER_CELL = 3;
120
- const todayCalDate = today(timeZone);
121
- const targetDateForFooter = selectedDate || (isSameMonth(currentMonthDate, todayCalDate) ? todayCalDate : null);
122
- let eventsForFooter = [];
123
- if (targetDateForFooter) {
124
- const targetZoned = toZoned(targetDateForFooter, timeZone);
125
- const targetZonedStart = getStartOfDay(targetZoned, timeZone);
126
- const targetZonedEnd = getEndOfDay(targetZoned, timeZone);
127
- eventsForFooter = zonedEvents
128
- .filter((event) => {
129
- const eventStartDay = toCalendarDate(event.start);
130
- const eventEndDay = toCalendarDate(event.end);
131
- return (isSameDay(eventStartDay, targetDateForFooter) ||
132
- isSameDay(eventEndDay, targetDateForFooter) ||
133
- (event.start.compare(targetZonedEnd) < 0 && event.end.compare(targetZonedStart) > 0));
134
- })
135
- .sort((a, b) => a.start.compare(b.start));
136
- }
137
- /**
138
- * Calculates how many days an event should visually span from a given start date.
139
- *
140
- * Multi-day events are rendered as a single element that spans multiple cells.
141
- * To avoid duplicate rendering:
142
- * - Events render only on their start day
143
- * - Multi-week events re-render at the start of each new week they span
144
- *
145
- * @returns Number of days to span, or null if event should not render on this day
146
- */
147
- const calculateEventSpan = useCallback((event, currentDate) => {
148
- const eventStartDay = toCalendarDate(event.start);
149
- const eventEndDay = toCalendarDate(event.end);
150
- const startsOnThisDay = isSameDay(currentDate, eventStartDay);
151
- // Determine if we should render this event on the current date
152
- if (!startsOnThisDay && eventStartDay.compare(currentDate) < 0) {
153
- // Event started on a previous day
154
- const currentDayIndex = days.findIndex((d) => isSameDay(d, currentDate));
155
- const weekStartIndex = Math.floor(currentDayIndex / 7) * 7;
156
- const weekStartDate = days[weekStartIndex];
157
- if (!weekStartDate)
158
- return null;
159
- const isFirstDayOfWeek = isSameDay(currentDate, weekStartDate);
160
- // Only render at week boundaries for multi-week events
161
- if (!isFirstDayOfWeek) {
162
- return null; // Already rendered earlier in this week
163
- }
164
- }
165
- // Calculate span: count days from current date until event ends or week ends
166
- const currentDayIndex = days.findIndex((d) => isSameDay(d, currentDate));
167
- const weekEndIndex = Math.floor(currentDayIndex / 7) * 7 + 6;
168
- let spanDays = 1;
169
- let checkDay = currentDate.add({ days: 1 });
170
- let checkDayIndex = currentDayIndex + 1;
171
- while (checkDayIndex <= weekEndIndex && checkDay.compare(eventEndDay) <= 0) {
172
- spanDays++;
173
- checkDay = checkDay.add({ days: 1 });
174
- checkDayIndex++;
175
- }
176
- return spanDays;
177
- }, [days]);
178
- /**
179
- * Creates an optimized layout for multi-day events using a row-packing algorithm.
180
- *
181
- * Key concepts:
182
- * 1. Each event is assigned to a "row" (vertical position within a day cell)
183
- * 2. Events spanning multiple days create invisible "placeholder" cells to reserve space
184
- * 3. Row assignment uses a greedy algorithm to minimize wasted vertical space
185
- * 4. Multi-week events can use different rows in each week for optimal packing
186
- *
187
- * Example: If Event A (Mon-Wed) is in row 0, Event B (Thu-Fri) can also use row 0
188
- * because they don't overlap, preventing wasted space.
189
- */
190
- const eventRenderMap = useMemo(() => {
191
- const renderMap = new Map();
192
- // Initialize empty arrays for each day
193
- // biome-ignore lint/suspicious/useIterableCallbackReturn: This is a side effect
194
- days.forEach((day) => renderMap.set(day.toString(), []));
195
- // Tracking state for the algorithm
196
- const eventRowAssignments = new Map(); // eventId -> row number
197
- const rowOccupancyByDay = new Map(); // dateKey -> [row0: Set<eventId>, row1: Set<eventId>, ...]
198
- // biome-ignore lint/suspicious/useIterableCallbackReturn: This is a side effect
199
- days.forEach((day) => rowOccupancyByDay.set(day.toString(), []));
200
- // Checks if an event is starting at a week boundary (for multi-week events)
201
- const isWeekBoundaryStart = (dayIndex, eventStartDay, currentDay) => {
202
- const startsOnCurrentDay = isSameDay(currentDay, eventStartDay);
203
- if (startsOnCurrentDay)
204
- return false;
205
- const weekStartIndex = Math.floor(dayIndex / 7) * 7;
206
- const weekStartDate = days[weekStartIndex];
207
- if (!weekStartDate)
208
- return false;
209
- return isSameDay(currentDay, weekStartDate);
210
- };
211
- // Finds the first available row for an event across all days it spans
212
- const findAvailableRow = (startDayIndex, span) => {
213
- let row = 0;
214
- while (true) {
215
- let isAvailable = true;
216
- // Check if this row is free for all days in the span
217
- for (let offset = 0; offset < span; offset++) {
218
- const dayIndex = startDayIndex + offset;
219
- const day = days[dayIndex];
220
- if (!day)
221
- break;
222
- const dayKey = day.toString();
223
- const occupancy = rowOccupancyByDay.get(dayKey);
224
- if ((occupancy?.[row]?.size ?? 0) > 0) {
225
- isAvailable = false;
226
- break;
227
- }
228
- }
229
- if (isAvailable)
230
- return row;
231
- row++;
232
- }
233
- };
234
- // Marks a row as occupied for all days an event spans
235
- const markRowAsOccupied = (startDayIndex, span, row, eventId) => {
236
- for (let offset = 0; offset < span; offset++) {
237
- const dayIndex = startDayIndex + offset;
238
- const day = days[dayIndex];
239
- if (!day)
240
- break;
241
- const dayKey = day.toString();
242
- const occupancy = rowOccupancyByDay.get(dayKey);
243
- if (!occupancy)
244
- break;
245
- let cell = occupancy[row];
246
- if (!cell) {
247
- cell = new Set();
248
- occupancy[row] = cell;
249
- }
250
- cell.add(eventId);
251
- }
252
- };
253
- // Main logic: Process each day and assign rows to events
254
- days.forEach((day, dayIndex) => {
255
- const dateKey = day.toString();
256
- const eventsOnThisDay = eventsByDay.get(dateKey) || [];
257
- eventsOnThisDay.forEach((event) => {
258
- const span = calculateEventSpan(event, day);
259
- if (span === null)
260
- return; // Event doesn't render on this day
261
- const eventStartDay = toCalendarDate(event.start);
262
- const isNewWeekSegment = isWeekBoundaryStart(dayIndex, eventStartDay, day);
263
- const hasExistingRow = eventRowAssignments.has(event.id);
264
- // Determine row: reuse existing row within same week, or find new row for new week segments
265
- let row;
266
- if (hasExistingRow && !isNewWeekSegment) {
267
- // biome-ignore lint/style/noNonNullAssertion: guarded by hasExistingRow (eventRowAssignments.has(event.id)) above
268
- row = eventRowAssignments.get(event.id);
269
- }
270
- else {
271
- row = findAvailableRow(dayIndex, span);
272
- eventRowAssignments.set(event.id, row);
273
- }
274
- // Mark occupancy and add event cell
275
- markRowAsOccupied(dayIndex, span, row, event.id);
276
- renderMap.get(dateKey)?.push({ event, span, isPlaceholder: false, row });
277
- // Add invisible placeholder cells for subsequent days
278
- for (let offset = 1; offset < span; offset++) {
279
- const nextDayIndex = dayIndex + offset;
280
- const nextDay = days[nextDayIndex];
281
- if (!nextDay)
282
- break;
283
- const nextDayKey = nextDay.toString();
284
- renderMap.get(nextDayKey)?.push({ event, span: 0, isPlaceholder: true, row });
285
- }
286
- });
287
- });
288
- // Sort events by row for consistent rendering order
289
- // biome-ignore lint/suspicious/useIterableCallbackReturn: This is a side effect
290
- renderMap.forEach((events) => events.sort((a, b) => a.row - b.row));
291
- return renderMap;
292
- }, [calculateEventSpan, days, eventsByDay]);
293
- return (jsxs("div", { className: cx('flex flex-1 flex-col', className), children: [jsx("div", { className: "grid grid-cols-7", children: weekdays.map((weekday, index) => (
294
- // biome-ignore lint/suspicious/noArrayIndexKey: This is a key for the column header
295
- jsx(CalendarColumnHeader, { weekDay: weekday, className: "before:border-b" }, index))) }), jsx("div", { className: cx('grid flex-1 grid-cols-7',
296
- // 6 rows for >35 days (6 weeks), 5 rows for >28 days (5 weeks), 4 rows for <=28 days (4 weeks)
297
- days.length > 35 ? 'grid-rows-6' : days.length > 28 ? 'grid-rows-5' : 'grid-rows-4'), children: days.map((date, index) => {
298
- const dateKey = date.toString();
299
- const isCurrentMonthFlag = isSameMonth(date, currentMonthDate);
300
- const isTodayFlag = isToday(date, timeZone);
301
- const isSelectedFlag = selectedDate ? isSameDay(date, selectedDate) : false;
302
- // Determine if it's the last row based on the number of weeks
303
- const isLastRow = days.length > 35 ? index >= 35 : days.length > 28 ? index >= 28 : index >= 21;
304
- const isLastColumn = (index + 1) % 7 === 0;
305
- // Retrieve events to render for this day
306
- const eventsToRender = eventRenderMap.get(dateKey) || [];
307
- const dayEvents = eventsByDay.get(dateKey) || [];
308
- const eventsToShow = eventsToRender.slice(0, MAX_EVENTS_PER_CELL);
309
- const remainingEventsCount = Math.max(0, dayEvents.length - MAX_EVENTS_PER_CELL);
310
- return (jsxs(CalendarMonthViewCell, { day: date.day, isDisabled: !isCurrentMonthFlag, state: isSelectedFlag ? 'selected' : isTodayFlag ? 'current' : 'default', className: cx(isLastRow && 'before:border-b-0', isLastColumn && 'before:border-r-0'), onClick: () => isCurrentMonthFlag && setSelectedDate(date), "data-testid": prefix ? joinTestId(prefix, `cell-${slugifyTestId(dateKey)}`) : undefined, children: [jsx("div", { className: "flex gap-1 max-md:pl-1 md:flex-col", children: eventsToShow.map(({ event, span, isPlaceholder }) => {
311
- // If this is a placeholder, render an invisible element to take up space
312
- if (isPlaceholder) {
313
- return (jsx("div", { className: "pointer-events-none opacity-0", children: jsx(CalendarMonthViewEvent, { label: event.title, collapseOnMobile: true, color: event.color, withDot: event.dot }) }, `${event.id}-placeholder`));
314
- }
315
- // Regular event rendering
316
- const eventStartDay = toCalendarDate(event.start);
317
- const eventEndDay = toCalendarDate(event.end);
318
- const startsToday = isSameDay(date, eventStartDay);
319
- const endsToday = isSameDay(date, eventEndDay);
320
- const continuesPrior = eventStartDay.compare(date) < 0;
321
- const continuesAfter = eventEndDay.compare(date) > 0;
322
- // Boundary checks compare times, not calendar days: an event starts "at day start"
323
- // only when it begins at midnight, and ends "at day end" when it ends at midnight
324
- // (exclusive-end convention) or at 23:59:59.999 (inclusive-end convention).
325
- const eventStartsAtDayStart = event.start.compare(getStartOfDay(event.start, timeZone)) === 0;
326
- const eventEndsAtDayEnd = event.end.compare(getStartOfDay(event.end, timeZone)) === 0 || event.end.compare(getEndOfDay(event.end, timeZone)) >= 0;
327
- const isAllDayForCell = (continuesPrior && continuesAfter) ||
328
- (startsToday && continuesAfter && eventStartsAtDayStart) ||
329
- (continuesPrior && endsToday && eventEndsAtDayEnd) ||
330
- (startsToday && endsToday && eventStartsAtDayStart && eventEndsAtDayEnd);
331
- const supportingText = isAllDayForCell ? undefined : formatTimeForMonth(event.start);
332
- // Apply custom width style for multi-day events
333
- const spanStyle = span > 1
334
- ? {
335
- zIndex: 1,
336
- width: `calc(${span * 100}% + ${(span - 1) * 16}px)`,
337
- }
338
- : undefined;
339
- return (jsx("div", { style: spanStyle, className: cx(!isCurrentMonthFlag && 'opacity-50'), children: jsx(CalendarMonthViewEvent, { label: event.title, collapseOnMobile: true, supportingText: supportingText, color: event.color, withDot: event.dot, "data-testid": eventTestId(event.id) }) }, event.id));
340
- }) }), remainingEventsCount > 0 && (jsx("div", { className: "truncate text-xs font-semibold text-utility-neutral-500 max-md:pl-1", children: `${remainingEventsCount} more...` }))] }, dateKey));
341
- }) }), targetDateForFooter && (jsxs("div", { className: "border-t border-secondary px-4 py-5 md:hidden", children: [jsx("h3", { className: "text-sm font-semibold text-primary", children: fullDateFormatter.format(targetDateForFooter.toDate(timeZone)) }), eventsForFooter.length > 0 && (jsx("div", { className: "mt-4 flex flex-col gap-1.5", children: eventsForFooter.slice(0, 3).map((event) => {
342
- // "All day" when the event covers the entire displayed day. Comparing against the
343
- // footer's target day (not the event's own end day) handles exclusive-end events
344
- // (end = next midnight) and multi-day events spanning this day.
345
- const dayStart = getStartOfDay(targetDateForFooter, timeZone);
346
- const dayEnd = getEndOfDay(targetDateForFooter, timeZone);
347
- const isAllDay = event.start.compare(dayStart) <= 0 && event.end.compare(dayEnd) >= 0;
348
- const supportingText = isAllDay ? 'All day' : formatTimeForMonth(event.start);
349
- return (jsx(CalendarMonthViewEvent, { label: event.title, supportingText: supportingText, color: event.color, withDot: event.dot, "data-testid": eventTestId(event.id) }, `footer-${event.id}`));
350
- }) })), eventsForFooter.length === 0 && jsx("p", { className: "mt-4 text-xs font-semibold text-quaternary", children: "No events for this day." }), eventsForFooter.length > 3 && jsx("p", { className: "mt-4 text-xs font-semibold text-quaternary", children: `${eventsForFooter.length - 3} more...` })] }))] }));
351
- };
352
- const MobileSingleDayGrid = ({ dayToDisplay, dayEvents, timeZone, setSelectedDate, timeFormatter, className }) => {
353
- const dayStart = useMemo(() => getStartOfDay(dayToDisplay, timeZone), [dayToDisplay, timeZone]); // Calculate once
354
- return (jsxs("div", { className: cx('relative flex-1', className), children: [Array.from({ length: 48 }).map((_, slotIndex) => (jsx(CalendarDwViewCell
355
- // biome-ignore lint/suspicious/noArrayIndexKey: This is a key for the slot
356
- , { className: cx('before:border-r-0', slotIndex === 47 && 'before:border-b-0') }, `mobile-slot-${dayToDisplay.toString()}-${slotIndex}`))), dayEvents.map((event, index) => {
357
- const overlapIndex = dayEvents.filter((e, i) => i < index && event.start.compare(e.end) < 0 && event.end.compare(e.start) > 0).length;
358
- const totalOverlapping = dayEvents.filter((e) => event.start.compare(e.end) < 0 && event.end.compare(e.start) > 0).length;
359
- return (jsx(PositionedEvent, { event: event, dayStart: dayStart, timeZone: timeZone, slotHeight: SLOT_HEIGHT, overlapIndex: overlapIndex, totalOverlapping: totalOverlapping, setSelectedDate: setSelectedDate, timeFormatter: timeFormatter }, event.id));
360
- })] }));
361
- };
362
- const WeekViewDay = ({ day, isLastDay, visibleEvents, timeZone, slotHeight, setSelectedDate, timeFormatter }) => {
363
- const dateKey = day.toString();
364
- const dayEvents = useMemo(() => getEventsForDay(visibleEvents, day, timeZone), [visibleEvents, day, timeZone]);
365
- const dayStart = useMemo(() => getStartOfDay(day, timeZone), [day, timeZone]);
366
- return (jsx("div", { className: "flex flex-col border-secondary", children: jsxs("div", { className: "relative flex-1 bg-primary", style: { minHeight: `${48 * slotHeight}px` }, children: [Array.from({ length: 48 }).map((_, slotIndex) => (jsx(CalendarDwViewCell
367
- // biome-ignore lint/suspicious/noArrayIndexKey: fixed 48 half-hour slots — the index is a stable slot position
368
- , { className: cx('last:before:border-b-0', isLastDay && 'before:border-r-0') }, `slot-${dateKey}-${slotIndex}`))), dayEvents.map((event, index) => {
369
- const overlapIndex = dayEvents.filter((e, i) => i < index && event.start.compare(e.end) < 0 && event.end.compare(e.start) > 0).length;
370
- const totalOverlapping = dayEvents.filter((e) => event.start.compare(e.end) < 0 && event.end.compare(e.start) > 0).length;
371
- return (jsx(PositionedEvent, { event: event, dayStart: dayStart, timeZone: timeZone, slotHeight: slotHeight, overlapIndex: overlapIndex, totalOverlapping: totalOverlapping, setSelectedDate: setSelectedDate, timeFormatter: timeFormatter }, event.id));
372
- })] }) }));
373
- };
374
- const WeekView = ({ currentMonthDate, selectedDate, zonedEvents, locale, timeZone, currentTime, setSelectedDate, shortWeekdayFormatter, timeFormatter, hourOnlyFormatter, className, view, }) => {
375
- const currentWeekStart = startOfWeek(currentMonthDate, locale);
376
- const currentWeekEnd = endOfWeek(currentMonthDate, locale);
377
- const localCurrentTime = currentTime;
378
- const scrollContainerRef = useRef(null);
379
- const formatTime = (dateTime) => timeFormatter.format(dateTime.toDate());
380
- const showTimeMarker = localCurrentTime.compare(toZoned(currentWeekStart, timeZone)) >= 0 &&
381
- localCurrentTime.compare(toZoned(currentWeekEnd, timeZone).add({ days: 1 })) < 0;
382
- const days = [];
383
- let dayIterator = currentWeekStart;
384
- while (dayIterator.compare(currentWeekEnd) <= 0) {
385
- days.push(dayIterator);
386
- dayIterator = dayIterator.add({ days: 1 });
387
- }
388
- const hours = Array.from({ length: 24 }, (_, i) => i);
389
- let timeMarkerTop = 0;
390
- if (showTimeMarker) {
391
- const startOfVisibleDay = getStartOfDay(localCurrentTime, timeZone);
392
- const minutesFromTop = localCurrentTime.hour * 60 + localCurrentTime.minute - (startOfVisibleDay.hour * 60 + startOfVisibleDay.minute);
393
- timeMarkerTop = (minutesFromTop / 30) * SLOT_HEIGHT;
394
- }
395
- const visibleEvents = zonedEvents.filter((event) => {
396
- const eventStartDay = toCalendarDate(event.start);
397
- const eventEndDay = toCalendarDate(event.end);
398
- return eventStartDay.compare(currentWeekEnd) <= 0 && eventEndDay.compare(currentWeekStart) >= 0;
399
- });
400
- // Calculate earliest event time for the entire week or default to 8 AM
401
- const earliestEventTimeInWeek = useMemo(() => {
402
- if (visibleEvents.length === 0) {
403
- // Default scroll target: 8 AM of the week start day
404
- return toZoned(currentWeekStart, timeZone).set({ hour: 8 });
405
- }
406
- // Find the earliest start time across all visible events in the week
407
- return visibleEvents.reduce((earliest, current) => {
408
- return current.start.compare(earliest.start) < 0 ? current : earliest;
409
- }).start;
410
- }, [visibleEvents, currentWeekStart, timeZone]);
411
- // Effect to scroll to the earliest event - Use useLayoutEffect
412
- // biome-ignore lint/correctness/useExhaustiveDependencies: We need to include currentMonthDate in the dependency array to recalculate the earliest event time when the week changes
413
- useLayoutEffect(() => {
414
- // Only scroll if the view is 'week' and the ref is attached
415
- if (view === 'week' && scrollContainerRef.current && earliestEventTimeInWeek) {
416
- const startMinutes = earliestEventTimeInWeek.hour * 60 + earliestEventTimeInWeek.minute;
417
- // Calculate target scroll, ensuring it's not negative
418
- let targetScrollTop = Math.max(0, (startMinutes / 30) * SLOT_HEIGHT);
419
- // Optional: Add an offset
420
- targetScrollTop = Math.max(0, targetScrollTop - SLOT_HEIGHT);
421
- // No need for setTimeout with useLayoutEffect for initial scroll
422
- if (scrollContainerRef.current) {
423
- scrollContainerRef.current.scrollTop = targetScrollTop;
424
- }
425
- }
426
- // Dependency array includes view and earliestEventTimeInWeek
427
- // currentMonthDate determines the week, which affects earliestEventTimeInWeek calculation
428
- }, [view, earliestEventTimeInWeek, currentMonthDate]);
429
- return (jsxs("div", { className: cx('flex flex-1 flex-col overflow-auto', className), children: [jsx("div", { className: "sticky top-0 z-10 grid grid-cols-7 bg-primary pl-18 shadow-sm dark:border-b dark:border-secondary", children: days.map((day) => {
430
- const isTodayFlag = isToday(day, timeZone);
431
- const dayToHighlight = selectedDate && day.compare(currentWeekStart) >= 0 && day.compare(currentWeekEnd) <= 0 ? selectedDate : currentWeekStart;
432
- const isSelectedFlag = isSameDay(day, dayToHighlight);
433
- const weekDayShort = shortWeekdayFormatter.format(day.toDate(timeZone));
434
- return (jsx(CalendarColumnHeader, { onClick: () => setSelectedDate(day), weekDay: weekDayShort, day: day.day, state: isSelectedFlag ? 'selected' : isTodayFlag ? 'current' : 'default', className: "cursor-pointer first:before:-left-px first:before:border-l" }, `mobile-header-${day.toString()}`));
435
- }) }), jsxs("div", { ref: scrollContainerRef, tabIndex: 0, className: "relative flex flex-1 overflow-y-auto", children: [jsx("div", { className: "flex h-max w-18 flex-col border-r border-secondary", children: hours.map((hour) => {
436
- const time = new CalendarDateTime(currentWeekStart.year, currentWeekStart.month, currentWeekStart.day, hour);
437
- const timeString = hourOnlyFormatter.format(toZoned(time, timeZone).toDate());
438
- return jsx(CalendarRowLabel, { children: timeString }, `time-${hour}`);
439
- }) }), jsx("div", { className: "grid flex-1 grid-cols-7", children: days.map((day, index) => {
440
- const isLastDay = index === days.length - 1;
441
- return (jsx(WeekViewDay, { day: day, isLastDay: isLastDay, visibleEvents: visibleEvents, timeZone: timeZone, slotHeight: SLOT_HEIGHT, setSelectedDate: setSelectedDate, timeFormatter: timeFormatter }, day.toString()));
442
- }) }), showTimeMarker && jsx(CalendarTimeMarker, { style: { top: `${timeMarkerTop}px` }, children: formatTime(localCurrentTime) })] })] }));
443
- };
444
- const DayView = ({ dayToDisplay, zonedEvents, locale, timeZone, currentTime, setSelectedDate, shortWeekdayFormatter, timeFormatter, hourOnlyFormatter, selectedDate, className, view, }) => {
445
- const currentWeekStart = startOfWeek(dayToDisplay, locale);
446
- const currentWeekEnd = endOfWeek(dayToDisplay, locale);
447
- const localCurrentTime = currentTime;
448
- const formatTime = (dateTime) => timeFormatter.format(dateTime.toDate());
449
- const showTimeMarker = isSameDay(toCalendarDate(localCurrentTime), dayToDisplay);
450
- const hours = Array.from({ length: 24 }, (_, i) => i);
451
- let timeMarkerTop = 0;
452
- if (showTimeMarker) {
453
- const minutesFromDayStart = localCurrentTime.hour * 60 + localCurrentTime.minute;
454
- timeMarkerTop = (minutesFromDayStart / 30) * SLOT_HEIGHT;
455
- }
456
- const visibleEvents = useMemo(() => getEventsForDay(zonedEvents, dayToDisplay, timeZone), [zonedEvents, dayToDisplay, timeZone]);
457
- const highlightedDates = useMemo(() => {
458
- const datesWithEvents = new Set();
459
- zonedEvents.forEach((event) => {
460
- let currentDate = toCalendarDate(event.start);
461
- const endDate = toCalendarDate(event.end);
462
- // Iterate through each day the event spans
463
- while (currentDate.compare(endDate) <= 0) {
464
- datesWithEvents.add(currentDate.toString());
465
- currentDate = currentDate.add({ days: 1 });
466
- }
467
- });
468
- return datesWithEvents;
469
- }, [zonedEvents]);
470
- const days = [];
471
- let dayIterator = currentWeekStart;
472
- while (dayIterator.compare(currentWeekEnd) <= 0) {
473
- days.push(dayIterator);
474
- dayIterator = dayIterator.add({ days: 1 });
475
- }
476
- const scrollContainerRef = useRef(null);
477
- // Calculate earliest event time or default to 8 AM
478
- const earliestEventTime = useMemo(() => {
479
- if (visibleEvents.length === 0) {
480
- // Default scroll target: 8 AM of the displayed day
481
- return toZoned(dayToDisplay, timeZone).set({ hour: 8 });
482
- }
483
- // Find the earliest start time among visible events
484
- return visibleEvents.reduce((earliest, current) => {
485
- return current.start.compare(earliest.start) < 0 ? current : earliest;
486
- }).start;
487
- }, [visibleEvents, dayToDisplay, timeZone]);
488
- // Effect to scroll to the earliest event - Use useLayoutEffect
489
- // biome-ignore lint/correctness/useExhaustiveDependencies: We need to include view, earliestEventTime, and dayToDisplay in the dependency array to re-trigger on changes
490
- useLayoutEffect(() => {
491
- // Only scroll if the view is 'day' and the ref is attached
492
- if (view === 'day' && scrollContainerRef.current && earliestEventTime) {
493
- const startMinutes = earliestEventTime.hour * 60 + earliestEventTime.minute;
494
- // Calculate target scroll, ensuring it's not negative
495
- let targetScrollTop = Math.max(0, (startMinutes / 30) * SLOT_HEIGHT);
496
- // Optional: Add an offset to show some context before the first event (e.g., scroll up by one slot height)
497
- targetScrollTop = Math.max(0, targetScrollTop - SLOT_HEIGHT);
498
- // No need for setTimeout with useLayoutEffect for initial scroll
499
- if (scrollContainerRef.current) {
500
- scrollContainerRef.current.scrollTop = targetScrollTop;
501
- }
502
- }
503
- // Dependency array includes view, earliestEventTime, and dayToDisplay to re-trigger on changes
504
- }, [view, earliestEventTime, dayToDisplay]);
505
- return (jsxs("div", { className: cx('flex flex-1 flex-col overflow-auto', className), children: [jsx("div", { className: "sticky top-0 z-20 grid grid-cols-7 bg-primary shadow-sm lg:hidden dark:border-b dark:border-secondary", children: days.map((day) => {
506
- const isTodayFlag = isToday(day, timeZone);
507
- const dayToHighlight = selectedDate && day.compare(currentWeekStart) >= 0 && day.compare(currentWeekEnd) <= 0 ? selectedDate : currentWeekStart;
508
- const isSelectedFlag = isSameDay(day, dayToHighlight);
509
- const weekDayShort = shortWeekdayFormatter.format(day.toDate(timeZone));
510
- return (jsx(CalendarColumnHeader, { onClick: () => setSelectedDate(day), weekDay: weekDayShort, day: day.day, state: isSelectedFlag ? 'selected' : isTodayFlag ? 'current' : 'default', className: "cursor-pointer" }, `mobile-header-${day.toString()}`));
511
- }) }), jsxs("div", { className: "flex h-full flex-1", children: [jsxs("div", { ref: scrollContainerRef, tabIndex: 0, className: "relative flex h-full flex-1 overflow-auto", children: [jsx("div", { className: "flex h-max w-14 flex-col border-r border-secondary md:w-18", children: hours.map((hour) => {
512
- const time = new CalendarDateTime(dayToDisplay.year, dayToDisplay.month, dayToDisplay.day, hour);
513
- const timeString = hourOnlyFormatter.format(toZoned(time, timeZone).toDate());
514
- return jsx(CalendarRowLabel, { children: timeString }, `time-${hour}`);
515
- }) }), jsx(MobileSingleDayGrid, { dayToDisplay: dayToDisplay, dayEvents: visibleEvents, timeZone: timeZone, locale: locale, currentTime: currentTime, setSelectedDate: setSelectedDate, timeFormatter: timeFormatter }), showTimeMarker && jsx(CalendarTimeMarker, { style: { top: `${timeMarkerTop}px` }, children: formatTime(localCurrentTime) })] }), jsxs("div", { className: "sticky top-0 hidden h-full w-82 flex-col overflow-auto border-l border-secondary mask-b-from-94% lg:flex", children: [jsxs(Calendar$1, { "aria-label": "Calendar", className: "px-6 py-5", value: selectedDate, onChange: (value) => setSelectedDate(value), children: [jsxs("div", { className: "mb-3 flex items-center justify-between", children: [jsx(Button, { slot: "previous", iconLeading: ChevronLeft, size: "sm", color: "tertiary", className: "size-8" }), jsx(Heading, { className: "text-sm font-semibold text-fg-secondary" }), jsx(Button, { slot: "next", iconLeading: ChevronRight, size: "sm", color: "tertiary", className: "size-8" })] }), jsxs(CalendarGrid, { weekdayStyle: "short", className: "w-max", children: [jsx(CalendarGridHeader, { className: "border-b-4 border-transparent", children: (day) => (jsx(CalendarHeaderCell, { className: "p-0", children: jsx("div", { className: "flex size-10 items-center justify-center text-sm font-medium text-secondary", children: day.slice(0, 2) }) })) }), jsx(CalendarGridBody, { className: "[&_td]:p-0 [&_tr]:border-b-4 [&_tr]:border-transparent [&_tr:last-of-type]:border-none", children: (date) => jsx(CalendarCell, { date: date, isHighlighted: highlightedDates.has(date.toString()) }) })] })] }), jsxs("div", { className: "flex flex-col gap-5 border-t border-secondary px-6 py-5", children: [jsxs("div", { className: "flex flex-col gap-2", children: [jsxs("section", { className: "flex w-full justify-between", children: [jsx("p", { className: "text-md font-semibold text-primary", children: "Product demo" }), jsxs("div", { className: "-mt-2 -mr-2 flex gap-0.5", children: [jsx(ButtonUtility, { size: "xs", color: "tertiary", tooltip: "Copy link", icon: Copy01 }), jsx(ButtonUtility, { size: "xs", color: "tertiary", tooltip: "Delete", icon: Trash01 }), jsx(ButtonUtility, { size: "xs", color: "tertiary", tooltip: "Edit", icon: Edit01 })] })] }), jsxs("section", { className: "flex flex-col gap-1.5", children: [jsxs("div", { className: "flex items-center gap-1.5", children: [jsx(Calendar$2, { className: "size-4 stroke-[2.25px] text-fg-quaternary" }), jsx("p", { className: "text-sm text-tertiary", children: "Friday, Jan 10, 2027" })] }), jsxs("div", { className: "flex items-center gap-1.5", children: [jsx(Clock, { className: "size-4 stroke-[2.25px] text-fg-quaternary" }), jsx("p", { className: "text-sm text-tertiary", children: "1:30 PM - 3:30 PM" })] }), jsxs("div", { className: "flex items-center gap-1.5", children: [jsx(BellRinging01, { className: "size-4 stroke-[2.25px] text-fg-quaternary" }), jsx("p", { className: "text-sm text-tertiary", children: "10 min before" })] })] })] }), jsxs("div", { className: "flex flex-col gap-3", children: [jsxs("section", { className: "flex gap-2", children: [jsxs("section", { className: "flex flex-row -space-x-2", children: [jsx(Avatar, { className: "ring-[1.5px] ring-bg-primary", src: "https://www.untitledui.com/images/avatars/sienna-hewitt?fm=webp&q=80", alt: "Sienna Hewitt", size: "sm" }), jsx(Avatar, { className: "ring-[1.5px] ring-bg-primary", src: "https://www.untitledui.com/images/avatars/ammar-foley?fm=webp&q=80", alt: "Ammar Foley", size: "sm" }), jsx(Avatar, { className: "ring-[1.5px] ring-bg-primary", src: "https://www.untitledui.com/images/avatars/pippa-wilkinson?fm=webp&q=80", alt: "Pippa Wilkinson", size: "sm" }), jsx(Avatar, { className: "ring-[1.5px] ring-bg-primary", src: "https://www.untitledui.com/images/avatars/olly-schroeder?fm=webp&q=80", alt: "Olly Schroeder", size: "sm" }), jsx(Avatar, { className: "ring-[1.5px] ring-bg-primary", src: "https://www.untitledui.com/images/avatars/mathilde-lewis?fm=webp&q=80", alt: "Mathilde Lewis", size: "sm" }), jsx(Avatar, { className: "ring-[1.5px] ring-bg-primary", initials: "OR", size: "sm" })] }), jsx(AvatarAddButton, { size: "sm" })] }), jsxs("section", { className: "flex items-center gap-2", children: [jsx("p", { className: "text-sm font-semibold text-primary", children: "6 guests" }), jsx("span", { className: "h-[13px] border-l border-primary" }), jsx("p", { className: "text-sm text-tertiary", children: "5 yes" }), jsx("span", { className: "h-[13px] border-l border-primary" }), jsx("p", { className: "text-sm text-tertiary", children: "1 awaiting" })] })] }), jsxs("section", { className: "flex flex-col gap-2", children: [jsx("p", { className: "text-sm font-semibold text-primary", children: "About this event" }), jsxs("div", { className: "text-sm text-tertiary", children: [jsx("p", { children: "Sienna is inviting you to a scheduled Zoom meeting." }), jsx("br", {}), jsx("p", { children: "Topic: Product demo for the new dashboard and Q&A session." }), jsx("br", {}), jsxs("p", { className: "wrap-break-word whitespace-normal", children: ["Join Zoom Meeting:\u00A0", jsx("span", { className: "break-all underline", children: "https://us02web.zoom.us/j/86341969512" }), "\u00A0"] }), jsx("br", {}), jsx("p", { children: "Meeting ID: 863 4196 9512" })] })] })] })] })] })] }));
516
- };
517
17
  /**
518
18
  * A scheduling calendar that renders a list of events across month, week, and
519
19
  * day views, with built-in navigation, a current-time marker, and a responsive