@dmsi/wedgekit-react 0.0.550 → 0.0.552

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 (180) hide show
  1. package/dist/{chunk-U3QGZAVS.js → chunk-JADOJNBI.js} +4 -4
  2. package/dist/{chunk-N2KPADIL.js → chunk-WNGFRQ4Y.js} +7 -7
  3. package/dist/{chunk-ZVY3TLXL.js → chunk-ZIPJMN2E.js} +4 -4
  4. package/dist/components/Alert.js +2 -2
  5. package/dist/components/CalendarRange.js +10 -10
  6. package/dist/components/DataGrid/ColumnSelectorHeaderCell/ColumnSelectorMenuOption.js +10 -10
  7. package/dist/components/DataGrid/ColumnSelectorHeaderCell/index.js +10 -10
  8. package/dist/components/DataGrid/PinnedColumns.js +10 -10
  9. package/dist/components/DataGrid/TableBody/LoadingCell.js +10 -10
  10. package/dist/components/DataGrid/TableBody/TableBodyRow.js +10 -10
  11. package/dist/components/DataGrid/TableBody/index.js +10 -10
  12. package/dist/components/DataGrid/index.js +10 -10
  13. package/dist/components/DataGrid/utils.js +10 -10
  14. package/dist/components/DateInput.js +10 -10
  15. package/dist/components/DateRangeInput.js +10 -10
  16. package/dist/components/FilterGroup.js +5 -5
  17. package/dist/components/MobileDataGrid/ColumnSelector/index.js +10 -10
  18. package/dist/components/MobileDataGrid/MobileDataGridHeader.js +10 -10
  19. package/dist/components/MobileDataGrid/RowDetailModalProvider/index.js +5 -5
  20. package/dist/components/MobileDataGrid/index.js +10 -10
  21. package/dist/components/Modal.js +4 -4
  22. package/dist/components/ModalButtons.js +2 -2
  23. package/dist/components/ModalHeader.js +2 -2
  24. package/dist/components/NavigationTab.js +2 -2
  25. package/dist/components/NavigationTabs.js +2 -2
  26. package/dist/components/NestedMenu.js +3 -3
  27. package/dist/components/Notification.js +3 -3
  28. package/dist/components/OptionPill.js +2 -2
  29. package/dist/components/PDFViewer/DownloadIcon.js +2 -2
  30. package/dist/components/PDFViewer/PDFNavigation.js +2 -2
  31. package/dist/components/PDFViewer/index.js +6 -6
  32. package/dist/components/ProductImagePreview/index.js +1 -1
  33. package/dist/components/Stepper.js +3 -3
  34. package/dist/components/Toast.js +3 -3
  35. package/dist/components/Upload.js +3 -3
  36. package/dist/components/index.js +16 -16
  37. package/package.json +8 -9
  38. package/src/brand.css +0 -125
  39. package/src/classNames.ts +0 -174
  40. package/src/components/AccessChangerTabItem.tsx +0 -71
  41. package/src/components/Accordion.tsx +0 -108
  42. package/src/components/Alert.tsx +0 -81
  43. package/src/components/Breadcrumbs.tsx +0 -142
  44. package/src/components/Button.tsx +0 -216
  45. package/src/components/CalendarRange.tsx +0 -628
  46. package/src/components/Caption.tsx +0 -144
  47. package/src/components/Card.tsx +0 -88
  48. package/src/components/Checkbox.tsx +0 -206
  49. package/src/components/CompactImagesPreview.tsx +0 -135
  50. package/src/components/ContentTab.tsx +0 -84
  51. package/src/components/ContentTabs.tsx +0 -136
  52. package/src/components/DMSiLogo.tsx +0 -33
  53. package/src/components/DataGrid/ColumnSelectorHeaderCell/ColumnSelectorMenuOption.tsx +0 -35
  54. package/src/components/DataGrid/ColumnSelectorHeaderCell/index.tsx +0 -74
  55. package/src/components/DataGrid/PinnedColumns.tsx +0 -183
  56. package/src/components/DataGrid/TableBody/LoadingCell.tsx +0 -44
  57. package/src/components/DataGrid/TableBody/TableBodyRow.tsx +0 -157
  58. package/src/components/DataGrid/TableBody/index.tsx +0 -185
  59. package/src/components/DataGrid/index.tsx +0 -756
  60. package/src/components/DataGrid/types.ts +0 -98
  61. package/src/components/DataGrid/utils.tsx +0 -15
  62. package/src/components/DataGridCell.tsx +0 -526
  63. package/src/components/DataTable.tsx +0 -881
  64. package/src/components/DateInput.tsx +0 -306
  65. package/src/components/DateRangeInput.tsx +0 -758
  66. package/src/components/DebugJson.tsx +0 -28
  67. package/src/components/Display.tsx +0 -66
  68. package/src/components/EditingContext.tsx +0 -43
  69. package/src/components/EmptyCartIcon.tsx +0 -18
  70. package/src/components/FilterGroup.tsx +0 -264
  71. package/src/components/FullViewportBox.tsx +0 -19
  72. package/src/components/Grid.tsx +0 -97
  73. package/src/components/Heading.tsx +0 -72
  74. package/src/components/HorizontalDivider.tsx +0 -22
  75. package/src/components/Icon.tsx +0 -39
  76. package/src/components/ImagePlaceholder.tsx +0 -22
  77. package/src/components/Input.tsx +0 -609
  78. package/src/components/InputGroup.tsx +0 -59
  79. package/src/components/Label.tsx +0 -46
  80. package/src/components/Link.tsx +0 -117
  81. package/src/components/List.tsx +0 -18
  82. package/src/components/ListGroup.tsx +0 -82
  83. package/src/components/LiveChatComponent.tsx +0 -56
  84. package/src/components/LoadingScrim.tsx +0 -33
  85. package/src/components/LogoAgilityTopBar.tsx +0 -54
  86. package/src/components/LogoDMSiTopBar.tsx +0 -33
  87. package/src/components/LogoMillworkTopBar.tsx +0 -119
  88. package/src/components/MainBar.tsx +0 -91
  89. package/src/components/MaxViewportBox.tsx +0 -19
  90. package/src/components/Menu.tsx +0 -316
  91. package/src/components/MenuOption.tsx +0 -330
  92. package/src/components/MobileDataGrid/ColumnList.tsx +0 -66
  93. package/src/components/MobileDataGrid/ColumnSelector/index.tsx +0 -97
  94. package/src/components/MobileDataGrid/GridContextProvider/GridContext.tsx +0 -25
  95. package/src/components/MobileDataGrid/GridContextProvider/index.tsx +0 -132
  96. package/src/components/MobileDataGrid/GridContextProvider/useGridContext.ts +0 -10
  97. package/src/components/MobileDataGrid/MobileDataGridCard/MobileDataGridColumn.tsx +0 -27
  98. package/src/components/MobileDataGrid/MobileDataGridCard/index.tsx +0 -138
  99. package/src/components/MobileDataGrid/MobileDataGridHeader.tsx +0 -81
  100. package/src/components/MobileDataGrid/RowDetailModalProvider/ModalContent.tsx +0 -42
  101. package/src/components/MobileDataGrid/RowDetailModalProvider/index.tsx +0 -68
  102. package/src/components/MobileDataGrid/dataGridReducer.ts +0 -55
  103. package/src/components/MobileDataGrid/index.tsx +0 -92
  104. package/src/components/MobileDataGrid/types.ts +0 -4
  105. package/src/components/Modal.tsx +0 -312
  106. package/src/components/ModalButtons.tsx +0 -62
  107. package/src/components/ModalContent.tsx +0 -31
  108. package/src/components/ModalHeader.tsx +0 -78
  109. package/src/components/ModalScrim.tsx +0 -42
  110. package/src/components/NavigationTab.tsx +0 -95
  111. package/src/components/NavigationTabs.tsx +0 -70
  112. package/src/components/NestedMenu.tsx +0 -131
  113. package/src/components/Notification.tsx +0 -128
  114. package/src/components/OptionPill.tsx +0 -139
  115. package/src/components/OrderCheckIcon.tsx +0 -19
  116. package/src/components/PDFViewer/DownloadIcon.tsx +0 -25
  117. package/src/components/PDFViewer/PDFElement.tsx +0 -90
  118. package/src/components/PDFViewer/PDFNavigation.tsx +0 -68
  119. package/src/components/PDFViewer/PDFPage.tsx +0 -34
  120. package/src/components/PDFViewer/index.tsx +0 -128
  121. package/src/components/Pagination.tsx +0 -182
  122. package/src/components/Paragraph.tsx +0 -55
  123. package/src/components/Password.tsx +0 -62
  124. package/src/components/ProductImagePreview/CarouselPagination.tsx +0 -54
  125. package/src/components/ProductImagePreview/MobileImageCarousel.tsx +0 -226
  126. package/src/components/ProductImagePreview/ProductPrimaryImage.tsx +0 -219
  127. package/src/components/ProductImagePreview/Thumbnail.tsx +0 -55
  128. package/src/components/ProductImagePreview/ZoomWindow.tsx +0 -136
  129. package/src/components/ProductImagePreview/index.tsx +0 -182
  130. package/src/components/ProductImagePreview/useProductImagePreview.ts +0 -211
  131. package/src/components/ProjectBar.tsx +0 -82
  132. package/src/components/Radio.tsx +0 -146
  133. package/src/components/Search.tsx +0 -152
  134. package/src/components/SearchResultImage/index.tsx +0 -39
  135. package/src/components/Select.tsx +0 -114
  136. package/src/components/SideMenu.tsx +0 -30
  137. package/src/components/SideMenuGroup.tsx +0 -95
  138. package/src/components/SideMenuItem.tsx +0 -109
  139. package/src/components/SimpleTable.tsx +0 -77
  140. package/src/components/SkeletonParagraph.tsx +0 -31
  141. package/src/components/Spinner.tsx +0 -32
  142. package/src/components/Stack.tsx +0 -347
  143. package/src/components/StatusPill.tsx +0 -59
  144. package/src/components/Stepper.tsx +0 -128
  145. package/src/components/Subheader.tsx +0 -50
  146. package/src/components/Surface.tsx +0 -37
  147. package/src/components/Swatch.tsx +0 -1341
  148. package/src/components/Textarea.tsx +0 -102
  149. package/src/components/Theme.tsx +0 -27
  150. package/src/components/Time.tsx +0 -460
  151. package/src/components/Toast.tsx +0 -268
  152. package/src/components/Tooltip.tsx +0 -159
  153. package/src/components/TopBar.tsx +0 -139
  154. package/src/components/Upload.tsx +0 -107
  155. package/src/components/WorldpayIframe.tsx +0 -7
  156. package/src/components/index.ts +0 -34
  157. package/src/components/useMenuSystem.tsx +0 -456
  158. package/src/components/useMounted.tsx +0 -14
  159. package/src/darkmode.css +0 -278
  160. package/src/fonts.css +0 -23
  161. package/src/hooks/index.ts +0 -4
  162. package/src/hooks/useInfiniteScroll.tsx +0 -40
  163. package/src/hooks/useKeydown.ts +0 -42
  164. package/src/hooks/useMatchesMedia.ts +0 -18
  165. package/src/hooks/useTableLayout.ts +0 -106
  166. package/src/index.css +0 -800
  167. package/src/index.tsx +0 -5
  168. package/src/types.ts +0 -150
  169. package/src/utils/date.ts +0 -236
  170. package/src/utils/formatting.tsx +0 -81
  171. package/src/utils/index.ts +0 -4
  172. package/src/utils/mergeObjectArrays.ts +0 -18
  173. package/src/utils.ts +0 -24
  174. package/dist/{chunk-7FQ7PGUF.js → chunk-7COWXCPA.js} +3 -3
  175. package/dist/{chunk-NKCFYM7A.js → chunk-7SFFUICM.js} +3 -3
  176. package/dist/{chunk-25RZP3VR.js → chunk-AKJUBFJK.js} +3 -3
  177. package/dist/{chunk-TAPYQBQU.js → chunk-CMMQTIVM.js} +3 -3
  178. package/dist/{chunk-GYEXSNFP.js → chunk-FWCVZWE6.js} +3 -3
  179. package/dist/{chunk-MV6W7OMC.js → chunk-QMMPHXVE.js} +3 -3
  180. package/dist/{chunk-GG5OZTI5.js → chunk-XRE52QTN.js} +3 -3
@@ -1,628 +0,0 @@
1
- import clsx from "clsx";
2
- import {
3
- typography,
4
- componentPadding,
5
- layoutPaddding,
6
- layoutGap,
7
- } from "../classNames";
8
- import { Icon } from "./Icon";
9
- import React, { useEffect, useRef, useState } from "react";
10
- import { Temporal } from "@js-temporal/polyfill";
11
- import { Menu, MenuOption } from ".";
12
-
13
- export interface CalendarRangeProps {
14
- from?: string | number;
15
- to?: string | number;
16
- onChange?: (from: string, to: string) => void;
17
- onPendingFromChange?: (from: string) => void;
18
- isDateAvailable?: (date: Temporal.PlainDate) => boolean;
19
- /**
20
- * Display mode: 'single' for one month, 'double' for two months side by side (default)
21
- */
22
- mode?: "single" | "double";
23
- cardStyle?: boolean;
24
- /**
25
- * Disable range selection in single mode. When true, single mode will only allow single date selection.
26
- * Only applies when mode is "single".
27
- */
28
- disableRange?: boolean;
29
- id?: string;
30
- testid?: string;
31
- }
32
-
33
- /**
34
- * Returns true if the given date is a weekend (Saturday or Sunday).
35
- * @param date Temporal.PlainDate
36
- */
37
- export function isWeekend(date: Temporal.PlainDate): boolean {
38
- // 6 = Saturday, 7 = Sunday (Temporal: 1=Monday, 7=Sunday)
39
- return date.dayOfWeek === 6 || date.dayOfWeek === 7;
40
- }
41
-
42
- // DateCell component for rendering a single day
43
- interface DateCellProps {
44
- date: Temporal.PlainDate;
45
- isInMonth: boolean;
46
- isToday: boolean;
47
- isSelected: boolean;
48
- inRange: boolean;
49
- isDisabled: boolean;
50
- onClick: () => void;
51
- onMouseEnter: () => void;
52
- onMouseLeave: () => void;
53
- /** True if this cell is the start of the selected range */
54
- isRangeStart?: boolean;
55
- /** True if this cell is the end of the selected range */
56
- isRangeEnd?: boolean;
57
- /** True if range selection is disabled in single mode */
58
- isRangeDisabled?: boolean;
59
- id?: string;
60
- testid?: string;
61
- }
62
-
63
- function DateCell({
64
- date,
65
- isInMonth,
66
- isToday,
67
- isSelected,
68
- inRange,
69
- isDisabled,
70
- isRangeStart,
71
- isRangeEnd,
72
- onClick,
73
- onMouseEnter,
74
- onMouseLeave,
75
- cellPadding = "",
76
- isRangeDisabled = false,
77
- id,
78
- testid,
79
- ...props
80
- }: DateCellProps & { cellPadding?: string }) {
81
- return (
82
- <span
83
- {...props}
84
- id={id}
85
- data-testid={testid}
86
- className={clsx(
87
- "flex items-center justify-center aspect-square select-none transition-colors border duration-100 font-medium",
88
- typography.caption,
89
- cellPadding,
90
- !isToday &&
91
- !isSelected &&
92
- !inRange &&
93
- !isDisabled &&
94
- !isRangeStart &&
95
- !isRangeEnd &&
96
- "border-transparent",
97
- !isInMonth && "border-transparent",
98
- // Today: subtle border ring
99
- isToday &&
100
- !isSelected &&
101
- !inRange &&
102
- "rounded-full border-border-primary-normal ",
103
- // Selected: Figma blue, white text, strong shadow
104
- isSelected && "bg-action-400 text-white border-action-400 z-10",
105
- !isSelected && !inRange && "rounded-base",
106
- // When range is disabled OR when only 'from' is selected (no range yet), apply rounded corners
107
- (isRangeDisabled || (!inRange && isSelected)) && "rounded-base",
108
- inRange && isSelected && "hover:border-action-500",
109
- // In range: Figma light blue background
110
- inRange &&
111
- !isSelected &&
112
- "bg-action-100 text-text-primary-normal border-y-action-400 border-x-0 ",
113
- // Disabled: Figma gray, no pointer, no hover
114
- isDisabled && !inRange
115
- ? "text-text-primary-disabled bg-transparent pointer-events-none opacity-40 border-transparent"
116
- : [
117
- "text-text-primary-normal cursor-pointer",
118
- // Figma hover: blue bg, blue text (or red text if selected)
119
- isSelected
120
- ? "hover:bg-background-action-primary-hover hover:text-white"
121
- : "hover:bg-action-100 hover:text-text-action-primary-hover",
122
- // Figma active: darker blue bg, white text
123
- "active:bg-action-300 active:text-white",
124
- // Figma focus: ring
125
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-action-400",
126
- ],
127
- isRangeStart && "rounded-l",
128
- isRangeEnd && "rounded-r",
129
- )}
130
- tabIndex={isDisabled ? -1 : 0}
131
- aria-disabled={isDisabled}
132
- onClick={() => !isDisabled && isInMonth && onClick()}
133
- onMouseEnter={() => isInMonth && onMouseEnter()}
134
- onMouseLeave={() => isInMonth && onMouseLeave()}
135
- >
136
- {isInMonth ? date.day : ""}
137
- </span>
138
- );
139
- }
140
-
141
- export function CalendarRange({
142
- from,
143
- to,
144
- onChange,
145
- onPendingFromChange,
146
- isDateAvailable,
147
- mode = "double",
148
- cardStyle = false,
149
- disableRange = false,
150
- id,
151
- testid,
152
- }: CalendarRangeProps) {
153
- const weekDays = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
154
- // Parse from/to props
155
- const parseDate = (d?: string | number) => {
156
- if (!d) {
157
- return undefined;
158
- }
159
-
160
- try {
161
- if (typeof d === "number") {
162
- return Temporal.PlainDate.from(new Date(d).toISOString().slice(0, 10));
163
- }
164
-
165
- if (typeof d === "string") {
166
- return Temporal.PlainDate.from(d);
167
- }
168
-
169
- return undefined;
170
- } catch (error) {
171
- // If parsing fails, return current date instead of throwing
172
- console.error("Invalid date format:", d, error);
173
- return Temporal.Now.plainDateISO();
174
- }
175
- };
176
- const fromDate = parseDate(from);
177
- const toDate = parseDate(to);
178
-
179
- // Internal state for visible months and selection
180
- const today = Temporal.Now.plainDateISO();
181
- const [baseMonth, setBaseMonth] = useState(
182
- fromDate ?? today.with({ day: 1 }),
183
- );
184
- const [selecting, setSelecting] = useState<"from" | "to">("from");
185
- const [pendingFrom, setPendingFrom] = useState<
186
- Temporal.PlainDate | undefined
187
- >(undefined);
188
- const [hoveredDate, setHoveredDate] = useState<
189
- Temporal.PlainDate | undefined
190
- >(undefined);
191
-
192
- // Update base month when from/to props change
193
- useEffect(() => {
194
- if (fromDate) {
195
- setBaseMonth(fromDate.with({ day: 1 }));
196
- } else if (toDate) {
197
- setBaseMonth(toDate.with({ day: 1 }));
198
- }
199
- }, [from, to]);
200
-
201
- // Reset selection state when from/to props change externally
202
- useEffect(() => {
203
- if (fromDate && toDate) {
204
- setSelecting("from");
205
- setPendingFrom(undefined);
206
- setHoveredDate(undefined);
207
- }
208
- }, [from, to]);
209
-
210
- // Helper to get month data
211
- function getMonthData(monthOffset: number) {
212
- const monthDate = baseMonth.add({ months: monthOffset }).with({ day: 1 });
213
- const days = monthDate.daysInMonth;
214
- const firstDayOffset = monthDate.dayOfWeek % 7;
215
- return {
216
- name: monthDate.toLocaleString("en-US", { month: "long" }),
217
- year: monthDate.year,
218
- days,
219
- firstDayOffset,
220
- date: monthDate,
221
- };
222
- }
223
-
224
- function getMonthDataWith(monthOffset: number) {
225
- const monthDate = baseMonth.with({ month: monthOffset }).with({ day: 1 });
226
- const days = monthDate.daysInMonth;
227
- const firstDayOffset = monthDate.dayOfWeek % 7;
228
- return {
229
- name: monthDate.toLocaleString("en-US", { month: "long" }),
230
- year: monthDate.year,
231
- days,
232
- firstDayOffset,
233
- date: monthDate,
234
- };
235
- }
236
-
237
- // Click handlers
238
- function handleDayClick(date: Temporal.PlainDate) {
239
- if (isDateAvailable && !isDateAvailable(date)) return;
240
-
241
- // Single mode with range disabled: just select the date directly
242
- if (mode === "single" && disableRange) {
243
- if (onChange) {
244
- onChange(date.toString(), date.toString());
245
- }
246
- return;
247
- }
248
-
249
- // Range mode (or single mode with range enabled): handle two-step selection
250
- if (selecting === "from") {
251
- setPendingFrom(date);
252
- setSelecting("to");
253
- setHoveredDate(undefined);
254
- onPendingFromChange?.(date.toString());
255
- // Hide current from/to selection when starting a new selection
256
- } else if (pendingFrom) {
257
- if (onChange) {
258
- const [start, end] =
259
- Temporal.PlainDate.compare(date, pendingFrom) < 0
260
- ? [date, pendingFrom]
261
- : [pendingFrom, date];
262
- onChange(start.toString(), end.toString());
263
- }
264
- setPendingFrom(undefined);
265
- setSelecting("from");
266
- setHoveredDate(undefined);
267
- }
268
- }
269
-
270
- // Range highlighting
271
- function isInRange(date: Temporal.PlainDate) {
272
- // No range highlighting in single mode when range is disabled
273
- if (mode === "single" && disableRange) {
274
- return false;
275
- }
276
-
277
- if (pendingFrom && selecting === "to" && hoveredDate) {
278
- const [start, end] =
279
- Temporal.PlainDate.compare(hoveredDate, pendingFrom) < 0
280
- ? [hoveredDate, pendingFrom]
281
- : [pendingFrom, hoveredDate];
282
- return (
283
- Temporal.PlainDate.compare(date, start) >= 0 &&
284
- Temporal.PlainDate.compare(date, end) <= 0
285
- );
286
- }
287
- if (!pendingFrom && fromDate && toDate) {
288
- return (
289
- Temporal.PlainDate.compare(date, fromDate) >= 0 &&
290
- Temporal.PlainDate.compare(date, toDate) <= 0
291
- );
292
- }
293
- return false;
294
- }
295
-
296
- // Render
297
- return (
298
- <div
299
- id={id}
300
- data-testid={testid}
301
- className={clsx(
302
- "relative bg-background-grouped-primary-normal rounded-base w-fit",
303
- layoutPaddding,
304
- layoutGap,
305
- cardStyle && "shadow-4",
306
- // baseTransition,
307
- "overflow-hidden",
308
- )}
309
- >
310
- <div
311
- className={clsx(
312
- "flex flex-row items-start justify-start bg-background-primary-normal overflow-clip",
313
- layoutGap,
314
- )}
315
- >
316
- {(mode === "double" ? [0, 1] : [0]).map((offset, idx) => {
317
- return (
318
- <CalendarPane
319
- key={idx}
320
- getMonthData={getMonthData}
321
- getMonthDataWith={getMonthDataWith}
322
- offset={offset}
323
- idx={idx}
324
- id={id}
325
- testid={testid}
326
- baseMonth={baseMonth}
327
- setBaseMonth={setBaseMonth}
328
- mode={mode}
329
- pendingFrom={pendingFrom}
330
- weekDays={weekDays}
331
- fromDate={fromDate}
332
- toDate={toDate}
333
- isDateAvailable={isDateAvailable}
334
- disableRange={disableRange}
335
- hoveredDate={hoveredDate}
336
- isInRange={isInRange}
337
- today={today}
338
- setHoveredDate={setHoveredDate}
339
- handleDayClick={handleDayClick}
340
- />
341
- );
342
- })}
343
- </div>
344
- </div>
345
- );
346
- }
347
- function CalendarPane({
348
- getMonthData,
349
- getMonthDataWith,
350
- offset,
351
- idx,
352
- id,
353
- testid,
354
- baseMonth,
355
- setBaseMonth,
356
- mode,
357
- pendingFrom,
358
- weekDays,
359
- fromDate,
360
- toDate,
361
- isDateAvailable,
362
- disableRange,
363
- hoveredDate,
364
- isInRange,
365
- today,
366
- setHoveredDate,
367
- handleDayClick,
368
- }: {
369
- getMonthData: (offset: number) => {
370
- name: string;
371
- year: number;
372
- days: number;
373
- firstDayOffset: number;
374
- date: Temporal.PlainDate;
375
- };
376
- getMonthDataWith: (offset: number) => {
377
- name: string;
378
- year: number;
379
- days: number;
380
- firstDayOffset: number;
381
- date: Temporal.PlainDate;
382
- };
383
- offset: number;
384
- idx: number;
385
- id: string | undefined;
386
- testid: string | undefined;
387
- baseMonth: Temporal.PlainDate;
388
- setBaseMonth: (date: Temporal.PlainDate) => void;
389
- mode: "single" | "range" | "double";
390
- pendingFrom: Temporal.PlainDate | undefined;
391
- weekDays: string[];
392
- fromDate: Temporal.PlainDate | undefined;
393
- toDate: Temporal.PlainDate | undefined;
394
- isDateAvailable?: (date: Temporal.PlainDate) => boolean;
395
- disableRange: boolean;
396
- hoveredDate: Temporal.PlainDate | undefined;
397
- isInRange: (date: Temporal.PlainDate) => boolean;
398
- today: Temporal.PlainDate;
399
- setHoveredDate: (date: Temporal.PlainDate | undefined) => void;
400
- handleDayClick: (date: Temporal.PlainDate) => void;
401
- }) {
402
- const months = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
403
- const years = Array.from({ length: 100 }).map(
404
- (_, i) => baseMonth.year - 50 + i,
405
- );
406
- const [monthMenuOpen, setMonthMenuOpen] = useState(false);
407
- const [yearMenuOpen, setYearMenuOpen] = useState(false);
408
-
409
- const monthMenuRef = useRef<HTMLButtonElement | null>(null);
410
- const yearMenuRef = useRef<HTMLButtonElement | null>(null);
411
- const month = getMonthData(offset);
412
- // Always show 6 weeks (42 days)
413
- const totalCells = 42;
414
- const emptyCells = month.firstDayOffset;
415
- return (
416
- <React.Fragment key={month.name + month.year}>
417
- <div
418
- // key={month.name + month.year}
419
- className={clsx("flex flex-col")}
420
- >
421
- <div
422
- className={clsx(
423
- "flex flex-row items-center justify-between",
424
- typography.label,
425
- "text-text-action-primary-normal",
426
- )}
427
- >
428
- {idx === 0 ? (
429
- <button
430
- id={id ? `${id}-prev-month-button` : undefined}
431
- data-testid={testid ? `${testid}-prev-month-button` : undefined}
432
- type="button"
433
- className={clsx(
434
- "flex items-center justify-center rounded-base hover:bg-action-100 active:bg-action-300 text-icon-action-primary-normal",
435
- componentPadding,
436
- )}
437
- aria-label="Previous month"
438
- onClick={() => setBaseMonth(baseMonth.subtract({ months: 1 }))}
439
- >
440
- <Icon name="chevron_left" size={24} />
441
- </button>
442
- ) : (
443
- <span className={clsx(componentPadding, "mr-1")} />
444
- )}
445
- <div className="flex gap-desktop-compact-component-padding">
446
- <button
447
- ref={(el) => {
448
- monthMenuRef.current = el;
449
- }}
450
- type="button"
451
- onClick={() => {
452
- setMonthMenuOpen(true);
453
- setYearMenuOpen(false);
454
- }}
455
- className="font-semibold text-text-action-primary-normal text-[14px] py-[2px] truncate"
456
- >
457
- {month.name}
458
- </button>
459
- <Menu
460
- show={monthMenuOpen}
461
- positionTo={monthMenuRef}
462
- setShow={() => setMonthMenuOpen(false)}
463
- >
464
- {months
465
- .map((x) => [x, getMonthDataWith(x + 1)] as const)
466
- .map(([x, m]) => (
467
- <MenuOption
468
- key={m.name}
469
- selected={baseMonth.month === x + 1}
470
- onClick={() => {
471
- setBaseMonth(baseMonth.with({ month: x + 1 }));
472
- setMonthMenuOpen(false);
473
- }}
474
- >
475
- {m.name}
476
- </MenuOption>
477
- ))}
478
- </Menu>
479
- <button
480
- ref={(el) => {
481
- yearMenuRef.current = el;
482
- }}
483
- type="button"
484
- onClick={() => {
485
- setYearMenuOpen(true);
486
- setMonthMenuOpen(false);
487
- }}
488
- className="font-semibold text-text-action-primary-normal text-[14px] py-[2px] truncate"
489
- >
490
- {month.year}
491
- </button>
492
- <Menu
493
- show={yearMenuOpen}
494
- positionTo={yearMenuRef}
495
- setShow={() => setYearMenuOpen(false)}
496
- >
497
- {years.map((y) => (
498
- <MenuOption
499
- key={y}
500
- selected={baseMonth.year === y}
501
- onClick={() => {
502
- setBaseMonth(baseMonth.with({ year: y }));
503
- setYearMenuOpen(false);
504
- }}
505
- >
506
- {y}
507
- </MenuOption>
508
- ))}
509
- </Menu>
510
- </div>
511
- {(mode === "double" ? idx === 1 : true) ? (
512
- <button
513
- id={id ? `${id}-next-month-button` : undefined}
514
- data-testid={testid ? `${testid}-next-month-button` : undefined}
515
- type="button"
516
- className={clsx(
517
- "flex items-center justify-center rounded-base hover:bg-action-100 active:bg-action-300 text-icon-action-primary-normal",
518
- componentPadding,
519
- )}
520
- aria-label="Next month"
521
- onClick={() => setBaseMonth(baseMonth.add({ months: 1 }))}
522
- >
523
- <Icon name="chevron_right" size={24} />
524
- </button>
525
- ) : (
526
- <span className={clsx(componentPadding, "ml-1")} />
527
- )}
528
- </div>
529
- <div className={clsx("grid grid-cols-7")}>
530
- {weekDays.map((d) => (
531
- <span
532
- key={d}
533
- className={clsx(
534
- typography.caption,
535
- "text-text-secondary-normal text-center",
536
- "w-10",
537
- )}
538
- >
539
- {d}
540
- </span>
541
- ))}
542
- </div>
543
- <div className={clsx("grid grid-cols-7")}>
544
- {Array.from({ length: totalCells }).map((_, i) => {
545
- const day = i - emptyCells + 1;
546
- const date = month.date.with({ day: 1 }).add({
547
- days: i - emptyCells,
548
- });
549
- const isInMonth = day > 0 && day <= month.days;
550
- const isToday = isInMonth && date.equals(today);
551
- const isSelected =
552
- isInMonth &&
553
- ((!pendingFrom && fromDate && date.equals(fromDate)) ||
554
- (!pendingFrom && toDate && date.equals(toDate)) ||
555
- (pendingFrom && date.equals(pendingFrom)));
556
- const inRange = isInMonth && isInRange(date);
557
- const isDisabled =
558
- !isInMonth || (isDateAvailable ? !isDateAvailable(date) : false);
559
- // New: determine if this is the start or end of the range
560
-
561
- const hoverDateIsBeforePendingFrom =
562
- hoveredDate &&
563
- pendingFrom &&
564
- Temporal.PlainDate.compare(hoveredDate, pendingFrom) < 0;
565
-
566
- const hoverDateIsAfterPendingFrom =
567
- hoveredDate &&
568
- pendingFrom &&
569
- Temporal.PlainDate.compare(hoveredDate, pendingFrom) >= 0;
570
-
571
- const isRangeStart =
572
- mode === "single" && disableRange
573
- ? false
574
- : (!pendingFrom &&
575
- isInMonth &&
576
- fromDate &&
577
- date.equals(fromDate)) ||
578
- (hoverDateIsAfterPendingFrom && date.equals(pendingFrom));
579
-
580
- const isRangeEnd =
581
- mode === "single" && disableRange
582
- ? false
583
- : (!pendingFrom &&
584
- isInMonth &&
585
- toDate &&
586
- date.equals(toDate)) ||
587
- (hoverDateIsBeforePendingFrom && date.equals(pendingFrom));
588
- return (
589
- <DateCell
590
- key={i}
591
- id={id ? `${id}-date-${date.toString()}` : undefined}
592
- testid={
593
- testid ? `${testid}-date-${date.toString()}` : undefined
594
- }
595
- date={date}
596
- isInMonth={!!isInMonth}
597
- isToday={!!isToday}
598
- isSelected={!!isSelected}
599
- inRange={!!inRange}
600
- isDisabled={!!isDisabled}
601
- onClick={() => handleDayClick(date)}
602
- onMouseEnter={() => setHoveredDate(date)}
603
- onMouseLeave={() => setHoveredDate(undefined)}
604
- isRangeStart={!!isRangeStart}
605
- isRangeEnd={!!isRangeEnd}
606
- isRangeDisabled={mode === "single" && disableRange}
607
- // Add cell padding for spacing
608
- cellPadding={componentPadding}
609
- />
610
- );
611
- })}
612
- </div>
613
- </div>
614
- {mode === "double" && idx === 0 && (
615
- <div
616
- className={clsx(
617
- "self-stretch bg-border-primary-normal rounded-base",
618
-
619
- // 1px width, full height, matches Figma divider
620
- "w-px",
621
- )}
622
- />
623
- )}
624
- </React.Fragment>
625
- );
626
- }
627
-
628
- export default CalendarRange;