@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.
- package/dist/components/__internal__/statusIcon/statusIcon.types.d.ts +12 -0
- package/dist/components/__internal__/statusIcon/statusIcon.types.d.ts.map +1 -0
- package/dist/components/__internal__/statusIcon/statusIcon.utils.cjs +21 -0
- package/dist/components/__internal__/statusIcon/statusIcon.utils.cjs.map +1 -0
- package/dist/components/__internal__/statusIcon/statusIcon.utils.d.ts +5 -0
- package/dist/components/__internal__/statusIcon/statusIcon.utils.d.ts.map +1 -0
- package/dist/components/__internal__/statusIcon/statusIcon.utils.js +19 -0
- package/dist/components/__internal__/statusIcon/statusIcon.utils.js.map +1 -0
- package/dist/components/atoms/BackgroundPattern/components/grid-check.cjs +14 -7
- package/dist/components/atoms/BackgroundPattern/components/grid-check.cjs.map +1 -1
- package/dist/components/atoms/BackgroundPattern/components/grid-check.d.ts.map +1 -1
- package/dist/components/atoms/BackgroundPattern/components/grid-check.js +14 -7
- package/dist/components/atoms/BackgroundPattern/components/grid-check.js.map +1 -1
- package/dist/components/atoms/BackgroundPattern/index.d.ts.map +1 -1
- package/dist/components/molecules/Alert/index.cjs +3 -11
- package/dist/components/molecules/Alert/index.cjs.map +1 -1
- package/dist/components/molecules/Alert/index.d.ts +3 -2
- package/dist/components/molecules/Alert/index.d.ts.map +1 -1
- package/dist/components/molecules/Alert/index.js +3 -11
- package/dist/components/molecules/Alert/index.js.map +1 -1
- package/dist/components/molecules/Notification/index.cjs +3 -10
- package/dist/components/molecules/Notification/index.cjs.map +1 -1
- package/dist/components/molecules/Notification/index.d.ts +2 -1
- package/dist/components/molecules/Notification/index.d.ts.map +1 -1
- package/dist/components/molecules/Notification/index.js +3 -10
- package/dist/components/molecules/Notification/index.js.map +1 -1
- package/dist/components/molecules/Pagination/index.cjs +4 -4
- package/dist/components/molecules/Pagination/index.cjs.map +1 -1
- package/dist/components/molecules/Pagination/index.js +5 -5
- package/dist/components/molecules/Pagination/index.js.map +1 -1
- package/dist/components/organisms/Calendar/calendar.utils.cjs +41 -0
- package/dist/components/organisms/Calendar/calendar.utils.cjs.map +1 -1
- package/dist/components/organisms/Calendar/calendar.utils.d.ts +20 -0
- package/dist/components/organisms/Calendar/calendar.utils.d.ts.map +1 -1
- package/dist/components/organisms/Calendar/calendar.utils.js +37 -1
- package/dist/components/organisms/Calendar/calendar.utils.js.map +1 -1
- package/dist/components/organisms/Calendar/components/calendar-day-view.cjs +86 -0
- package/dist/components/organisms/Calendar/components/calendar-day-view.cjs.map +1 -0
- package/dist/components/organisms/Calendar/components/calendar-day-view.d.ts +20 -0
- package/dist/components/organisms/Calendar/components/calendar-day-view.d.ts.map +1 -0
- package/dist/components/organisms/Calendar/components/calendar-day-view.js +84 -0
- package/dist/components/organisms/Calendar/components/calendar-day-view.js.map +1 -0
- package/dist/components/organisms/Calendar/components/calendar-mobile-day-grid.cjs +21 -0
- package/dist/components/organisms/Calendar/components/calendar-mobile-day-grid.cjs.map +1 -0
- package/dist/components/organisms/Calendar/components/calendar-mobile-day-grid.d.ts +16 -0
- package/dist/components/organisms/Calendar/components/calendar-mobile-day-grid.d.ts.map +1 -0
- package/dist/components/organisms/Calendar/components/calendar-mobile-day-grid.js +19 -0
- package/dist/components/organisms/Calendar/components/calendar-mobile-day-grid.js.map +1 -0
- package/dist/components/organisms/Calendar/components/calendar-month-view.cjs +265 -0
- package/dist/components/organisms/Calendar/components/calendar-month-view.cjs.map +1 -0
- package/dist/components/organisms/Calendar/components/calendar-month-view.d.ts +18 -0
- package/dist/components/organisms/Calendar/components/calendar-month-view.d.ts.map +1 -0
- package/dist/components/organisms/Calendar/components/calendar-month-view.js +263 -0
- package/dist/components/organisms/Calendar/components/calendar-month-view.js.map +1 -0
- package/dist/components/organisms/Calendar/components/calendar-positioned-event.cjs +41 -0
- package/dist/components/organisms/Calendar/components/calendar-positioned-event.cjs.map +1 -0
- package/dist/components/organisms/Calendar/components/calendar-positioned-event.d.ts +15 -0
- package/dist/components/organisms/Calendar/components/calendar-positioned-event.d.ts.map +1 -0
- package/dist/components/organisms/Calendar/components/calendar-positioned-event.js +39 -0
- package/dist/components/organisms/Calendar/components/calendar-positioned-event.js.map +1 -0
- package/dist/components/organisms/Calendar/components/calendar-week-view-day.cjs +23 -0
- package/dist/components/organisms/Calendar/components/calendar-week-view-day.cjs.map +1 -0
- package/dist/components/organisms/Calendar/components/calendar-week-view-day.d.ts +15 -0
- package/dist/components/organisms/Calendar/components/calendar-week-view-day.d.ts.map +1 -0
- package/dist/components/organisms/Calendar/components/calendar-week-view-day.js +21 -0
- package/dist/components/organisms/Calendar/components/calendar-week-view-day.js.map +1 -0
- package/dist/components/organisms/Calendar/components/calendar-week-view.cjs +77 -0
- package/dist/components/organisms/Calendar/components/calendar-week-view.cjs.map +1 -0
- package/dist/components/organisms/Calendar/components/calendar-week-view.d.ts +20 -0
- package/dist/components/organisms/Calendar/components/calendar-week-view.d.ts.map +1 -0
- package/dist/components/organisms/Calendar/components/calendar-week-view.js +75 -0
- package/dist/components/organisms/Calendar/components/calendar-week-view.js.map +1 -0
- package/dist/components/organisms/Calendar/index.cjs +4 -504
- package/dist/components/organisms/Calendar/index.cjs.map +1 -1
- package/dist/components/organisms/Calendar/index.d.ts +1 -1
- package/dist/components/organisms/Calendar/index.d.ts.map +1 -1
- package/dist/components/organisms/Calendar/index.js +6 -506
- package/dist/components/organisms/Calendar/index.js.map +1 -1
- package/dist/components/organisms/Charts/components/activity-gauge.cjs +4 -0
- package/dist/components/organisms/Charts/components/activity-gauge.cjs.map +1 -1
- package/dist/components/organisms/Charts/components/activity-gauge.d.ts +4 -0
- package/dist/components/organisms/Charts/components/activity-gauge.d.ts.map +1 -1
- package/dist/components/organisms/Charts/components/activity-gauge.js +4 -0
- package/dist/components/organisms/Charts/components/activity-gauge.js.map +1 -1
- package/dist/components/organisms/Charts/components/bar-chart.cjs +1 -1
- package/dist/components/organisms/Charts/components/bar-chart.cjs.map +1 -1
- package/dist/components/organisms/Charts/components/bar-chart.d.ts +1 -2
- package/dist/components/organisms/Charts/components/bar-chart.d.ts.map +1 -1
- package/dist/components/organisms/Charts/components/bar-chart.js +2 -2
- package/dist/components/organisms/Charts/components/bar-chart.js.map +1 -1
- package/dist/components/organisms/Charts/components/charts-base.cjs +1 -2
- package/dist/components/organisms/Charts/components/charts-base.cjs.map +1 -1
- package/dist/components/organisms/Charts/components/charts-base.d.ts +13 -2
- package/dist/components/organisms/Charts/components/charts-base.d.ts.map +1 -1
- package/dist/components/organisms/Charts/components/charts-base.js +1 -2
- package/dist/components/organisms/Charts/components/charts-base.js.map +1 -1
- package/dist/components/organisms/Charts/components/line-chart.cjs +1 -1
- package/dist/components/organisms/Charts/components/line-chart.cjs.map +1 -1
- package/dist/components/organisms/Charts/components/line-chart.d.ts +1 -2
- package/dist/components/organisms/Charts/components/line-chart.d.ts.map +1 -1
- package/dist/components/organisms/Charts/components/line-chart.js +2 -2
- package/dist/components/organisms/Charts/components/line-chart.js.map +1 -1
- package/dist/components/organisms/Charts/components/pie-chart.cjs +5 -0
- package/dist/components/organisms/Charts/components/pie-chart.cjs.map +1 -1
- package/dist/components/organisms/Charts/components/pie-chart.d.ts +6 -2
- package/dist/components/organisms/Charts/components/pie-chart.d.ts.map +1 -1
- package/dist/components/organisms/Charts/components/pie-chart.js +5 -0
- package/dist/components/organisms/Charts/components/pie-chart.js.map +1 -1
- package/dist/components/organisms/Charts/components/radar-chart.cjs +3 -4
- package/dist/components/organisms/Charts/components/radar-chart.cjs.map +1 -1
- package/dist/components/organisms/Charts/components/radar-chart.d.ts.map +1 -1
- package/dist/components/organisms/Charts/components/radar-chart.js +3 -4
- package/dist/components/organisms/Charts/components/radar-chart.js.map +1 -1
- package/dist/components/organisms/DatePicker/components/range-calendar.cjs +1 -1
- package/dist/components/organisms/DatePicker/components/range-calendar.cjs.map +1 -1
- package/dist/components/organisms/DatePicker/components/range-calendar.js +2 -2
- package/dist/components/organisms/DatePicker/components/range-calendar.js.map +1 -1
- package/dist/components/organisms/GradientPicker/components/gradient-area.cjs +157 -0
- package/dist/components/organisms/GradientPicker/components/gradient-area.cjs.map +1 -0
- package/dist/components/organisms/GradientPicker/components/gradient-area.d.ts +17 -0
- package/dist/components/organisms/GradientPicker/components/gradient-area.d.ts.map +1 -0
- package/dist/components/organisms/GradientPicker/components/gradient-area.js +155 -0
- package/dist/components/organisms/GradientPicker/components/gradient-area.js.map +1 -0
- package/dist/components/organisms/GradientPicker/components/gradient-reverse-button.cjs +17 -0
- package/dist/components/organisms/GradientPicker/components/gradient-reverse-button.cjs.map +1 -0
- package/dist/components/organisms/GradientPicker/components/gradient-reverse-button.d.ts +3 -0
- package/dist/components/organisms/GradientPicker/components/gradient-reverse-button.d.ts.map +1 -0
- package/dist/components/organisms/GradientPicker/components/gradient-reverse-button.js +15 -0
- package/dist/components/organisms/GradientPicker/components/gradient-reverse-button.js.map +1 -0
- package/dist/components/organisms/GradientPicker/components/gradient-slider.cjs +71 -0
- package/dist/components/organisms/GradientPicker/components/gradient-slider.cjs.map +1 -0
- package/dist/components/organisms/GradientPicker/components/gradient-slider.d.ts +7 -0
- package/dist/components/organisms/GradientPicker/components/gradient-slider.d.ts.map +1 -0
- package/dist/components/organisms/GradientPicker/components/gradient-slider.js +69 -0
- package/dist/components/organisms/GradientPicker/components/gradient-slider.js.map +1 -0
- package/dist/components/organisms/GradientPicker/components/gradient-stop-list.cjs +18 -0
- package/dist/components/organisms/GradientPicker/components/gradient-stop-list.cjs.map +1 -0
- package/dist/components/organisms/GradientPicker/components/gradient-stop-list.d.ts +4 -0
- package/dist/components/organisms/GradientPicker/components/gradient-stop-list.d.ts.map +1 -0
- package/dist/components/organisms/GradientPicker/components/gradient-stop-list.js +16 -0
- package/dist/components/organisms/GradientPicker/components/gradient-stop-list.js.map +1 -0
- package/dist/components/organisms/GradientPicker/components/gradient-stop-row.cjs +49 -0
- package/dist/components/organisms/GradientPicker/components/gradient-stop-row.cjs.map +1 -0
- package/dist/components/organisms/GradientPicker/components/gradient-stop-row.d.ts +6 -0
- package/dist/components/organisms/GradientPicker/components/gradient-stop-row.d.ts.map +1 -0
- package/dist/components/organisms/GradientPicker/components/gradient-stop-row.js +47 -0
- package/dist/components/organisms/GradientPicker/components/gradient-stop-row.js.map +1 -0
- package/dist/components/organisms/GradientPicker/components/gradient-type-select.cjs +28 -0
- package/dist/components/organisms/GradientPicker/components/gradient-type-select.cjs.map +1 -0
- package/dist/components/organisms/GradientPicker/components/gradient-type-select.d.ts +3 -0
- package/dist/components/organisms/GradientPicker/components/gradient-type-select.d.ts.map +1 -0
- package/dist/components/organisms/GradientPicker/components/gradient-type-select.js +26 -0
- package/dist/components/organisms/GradientPicker/components/gradient-type-select.js.map +1 -0
- package/dist/components/organisms/GradientPicker/components/position-input.cjs +43 -0
- package/dist/components/organisms/GradientPicker/components/position-input.cjs.map +1 -0
- package/dist/components/organisms/GradientPicker/components/position-input.d.ts +6 -0
- package/dist/components/organisms/GradientPicker/components/position-input.d.ts.map +1 -0
- package/dist/components/organisms/GradientPicker/components/position-input.js +41 -0
- package/dist/components/organisms/GradientPicker/components/position-input.js.map +1 -0
- package/dist/components/organisms/GradientPicker/index.cjs +11 -327
- package/dist/components/organisms/GradientPicker/index.cjs.map +1 -1
- package/dist/components/organisms/GradientPicker/index.d.ts +7 -8
- package/dist/components/organisms/GradientPicker/index.d.ts.map +1 -1
- package/dist/components/organisms/GradientPicker/index.js +7 -323
- package/dist/components/organisms/GradientPicker/index.js.map +1 -1
- package/dist/components/organisms/HeaderNavigation/components/nav-account-card.cjs +1 -1
- package/dist/components/organisms/HeaderNavigation/components/nav-account-card.cjs.map +1 -1
- package/dist/components/organisms/HeaderNavigation/components/nav-account-card.js +2 -2
- package/dist/components/organisms/HeaderNavigation/components/nav-account-card.js.map +1 -1
- package/dist/components/organisms/HeaderNavigation/components/nav-button.cjs +1 -1
- package/dist/components/organisms/HeaderNavigation/components/nav-button.cjs.map +1 -1
- package/dist/components/organisms/HeaderNavigation/components/nav-button.d.ts.map +1 -1
- package/dist/components/organisms/HeaderNavigation/components/nav-button.js +1 -1
- package/dist/components/organisms/HeaderNavigation/components/nav-button.js.map +1 -1
- package/dist/components/organisms/SidebarNavigation/components/nav-account-card.cjs +1 -1
- package/dist/components/organisms/SidebarNavigation/components/nav-account-card.cjs.map +1 -1
- package/dist/components/organisms/SidebarNavigation/components/nav-account-card.js +2 -2
- package/dist/components/organisms/SidebarNavigation/components/nav-account-card.js.map +1 -1
- package/dist/components/organisms/SidebarNavigation/index.cjs +1 -1
- package/dist/components/organisms/SidebarNavigation/index.cjs.map +1 -1
- package/dist/components/organisms/SidebarNavigation/index.js +2 -2
- package/dist/components/organisms/SidebarNavigation/index.js.map +1 -1
- package/dist/components/organisms/VideoPlayer/hooks/useVideoPlayback.cjs +169 -0
- package/dist/components/organisms/VideoPlayer/hooks/useVideoPlayback.cjs.map +1 -0
- package/dist/components/organisms/VideoPlayer/hooks/useVideoPlayback.d.ts +34 -0
- package/dist/components/organisms/VideoPlayer/hooks/useVideoPlayback.d.ts.map +1 -0
- package/dist/components/organisms/VideoPlayer/hooks/useVideoPlayback.js +167 -0
- package/dist/components/organisms/VideoPlayer/hooks/useVideoPlayback.js.map +1 -0
- package/dist/components/organisms/VideoPlayer/hooks/useVideoVolume.cjs +75 -0
- package/dist/components/organisms/VideoPlayer/hooks/useVideoVolume.cjs.map +1 -0
- package/dist/components/organisms/VideoPlayer/hooks/useVideoVolume.d.ts +16 -0
- package/dist/components/organisms/VideoPlayer/hooks/useVideoVolume.d.ts.map +1 -0
- package/dist/components/organisms/VideoPlayer/hooks/useVideoVolume.js +73 -0
- package/dist/components/organisms/VideoPlayer/hooks/useVideoVolume.js.map +1 -0
- package/dist/components/organisms/VideoPlayer/index.cjs +9 -182
- package/dist/components/organisms/VideoPlayer/index.cjs.map +1 -1
- package/dist/components/organisms/VideoPlayer/index.d.ts.map +1 -1
- package/dist/components/organisms/VideoPlayer/index.js +10 -183
- package/dist/components/organisms/VideoPlayer/index.js.map +1 -1
- package/dist/hooks/useBreakpoints/index.cjs +4 -1
- package/dist/hooks/useBreakpoints/index.cjs.map +1 -1
- package/dist/hooks/useBreakpoints/index.d.ts +2 -0
- package/dist/hooks/useBreakpoints/index.d.ts.map +1 -1
- package/dist/hooks/useBreakpoints/index.js +4 -2
- package/dist/hooks/useBreakpoints/index.js.map +1 -1
- 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
|
|
8
|
-
import { getLocalTimeZone, today, now, parseAbsoluteToLocal, startOfWeek
|
|
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
|
|
11
|
-
import {
|
|
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 {
|
|
18
|
-
import {
|
|
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
|