@dmsi/wedgekit-react 0.0.551 → 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 (137) hide show
  1. package/package.json +2 -3
  2. package/src/brand.css +0 -125
  3. package/src/classNames.ts +0 -174
  4. package/src/components/AccessChangerTabItem.tsx +0 -71
  5. package/src/components/Accordion.tsx +0 -108
  6. package/src/components/Alert.tsx +0 -81
  7. package/src/components/Breadcrumbs.tsx +0 -142
  8. package/src/components/Button.tsx +0 -216
  9. package/src/components/CalendarRange.tsx +0 -628
  10. package/src/components/Caption.tsx +0 -144
  11. package/src/components/Card.tsx +0 -88
  12. package/src/components/Checkbox.tsx +0 -206
  13. package/src/components/CompactImagesPreview.tsx +0 -135
  14. package/src/components/ContentTab.tsx +0 -84
  15. package/src/components/ContentTabs.tsx +0 -136
  16. package/src/components/DMSiLogo.tsx +0 -33
  17. package/src/components/DataGrid/ColumnSelectorHeaderCell/ColumnSelectorMenuOption.tsx +0 -35
  18. package/src/components/DataGrid/ColumnSelectorHeaderCell/index.tsx +0 -74
  19. package/src/components/DataGrid/PinnedColumns.tsx +0 -183
  20. package/src/components/DataGrid/TableBody/LoadingCell.tsx +0 -44
  21. package/src/components/DataGrid/TableBody/TableBodyRow.tsx +0 -157
  22. package/src/components/DataGrid/TableBody/index.tsx +0 -185
  23. package/src/components/DataGrid/index.tsx +0 -756
  24. package/src/components/DataGrid/types.ts +0 -98
  25. package/src/components/DataGrid/utils.tsx +0 -15
  26. package/src/components/DataGridCell.tsx +0 -526
  27. package/src/components/DataTable.tsx +0 -881
  28. package/src/components/DateInput.tsx +0 -306
  29. package/src/components/DateRangeInput.tsx +0 -758
  30. package/src/components/DebugJson.tsx +0 -28
  31. package/src/components/Display.tsx +0 -66
  32. package/src/components/EditingContext.tsx +0 -43
  33. package/src/components/EmptyCartIcon.tsx +0 -18
  34. package/src/components/FilterGroup.tsx +0 -264
  35. package/src/components/FullViewportBox.tsx +0 -19
  36. package/src/components/Grid.tsx +0 -97
  37. package/src/components/Heading.tsx +0 -72
  38. package/src/components/HorizontalDivider.tsx +0 -22
  39. package/src/components/Icon.tsx +0 -39
  40. package/src/components/ImagePlaceholder.tsx +0 -22
  41. package/src/components/Input.tsx +0 -609
  42. package/src/components/InputGroup.tsx +0 -59
  43. package/src/components/Label.tsx +0 -46
  44. package/src/components/Link.tsx +0 -117
  45. package/src/components/List.tsx +0 -18
  46. package/src/components/ListGroup.tsx +0 -82
  47. package/src/components/LiveChatComponent.tsx +0 -56
  48. package/src/components/LoadingScrim.tsx +0 -33
  49. package/src/components/LogoAgilityTopBar.tsx +0 -54
  50. package/src/components/LogoDMSiTopBar.tsx +0 -33
  51. package/src/components/LogoMillworkTopBar.tsx +0 -119
  52. package/src/components/MainBar.tsx +0 -91
  53. package/src/components/MaxViewportBox.tsx +0 -19
  54. package/src/components/Menu.tsx +0 -316
  55. package/src/components/MenuOption.tsx +0 -330
  56. package/src/components/MobileDataGrid/ColumnList.tsx +0 -66
  57. package/src/components/MobileDataGrid/ColumnSelector/index.tsx +0 -97
  58. package/src/components/MobileDataGrid/GridContextProvider/GridContext.tsx +0 -25
  59. package/src/components/MobileDataGrid/GridContextProvider/index.tsx +0 -132
  60. package/src/components/MobileDataGrid/GridContextProvider/useGridContext.ts +0 -10
  61. package/src/components/MobileDataGrid/MobileDataGridCard/MobileDataGridColumn.tsx +0 -27
  62. package/src/components/MobileDataGrid/MobileDataGridCard/index.tsx +0 -138
  63. package/src/components/MobileDataGrid/MobileDataGridHeader.tsx +0 -81
  64. package/src/components/MobileDataGrid/RowDetailModalProvider/ModalContent.tsx +0 -42
  65. package/src/components/MobileDataGrid/RowDetailModalProvider/index.tsx +0 -68
  66. package/src/components/MobileDataGrid/dataGridReducer.ts +0 -55
  67. package/src/components/MobileDataGrid/index.tsx +0 -92
  68. package/src/components/MobileDataGrid/types.ts +0 -4
  69. package/src/components/Modal.tsx +0 -312
  70. package/src/components/ModalButtons.tsx +0 -62
  71. package/src/components/ModalContent.tsx +0 -31
  72. package/src/components/ModalHeader.tsx +0 -78
  73. package/src/components/ModalScrim.tsx +0 -42
  74. package/src/components/NavigationTab.tsx +0 -95
  75. package/src/components/NavigationTabs.tsx +0 -70
  76. package/src/components/NestedMenu.tsx +0 -131
  77. package/src/components/Notification.tsx +0 -128
  78. package/src/components/OptionPill.tsx +0 -139
  79. package/src/components/OrderCheckIcon.tsx +0 -19
  80. package/src/components/PDFViewer/DownloadIcon.tsx +0 -25
  81. package/src/components/PDFViewer/PDFElement.tsx +0 -90
  82. package/src/components/PDFViewer/PDFNavigation.tsx +0 -68
  83. package/src/components/PDFViewer/PDFPage.tsx +0 -34
  84. package/src/components/PDFViewer/index.tsx +0 -128
  85. package/src/components/Pagination.tsx +0 -182
  86. package/src/components/Paragraph.tsx +0 -55
  87. package/src/components/Password.tsx +0 -62
  88. package/src/components/ProductImagePreview/CarouselPagination.tsx +0 -54
  89. package/src/components/ProductImagePreview/MobileImageCarousel.tsx +0 -226
  90. package/src/components/ProductImagePreview/ProductPrimaryImage.tsx +0 -219
  91. package/src/components/ProductImagePreview/Thumbnail.tsx +0 -55
  92. package/src/components/ProductImagePreview/ZoomWindow.tsx +0 -136
  93. package/src/components/ProductImagePreview/index.tsx +0 -182
  94. package/src/components/ProductImagePreview/useProductImagePreview.ts +0 -211
  95. package/src/components/ProjectBar.tsx +0 -82
  96. package/src/components/Radio.tsx +0 -146
  97. package/src/components/Search.tsx +0 -152
  98. package/src/components/SearchResultImage/index.tsx +0 -39
  99. package/src/components/Select.tsx +0 -114
  100. package/src/components/SideMenu.tsx +0 -30
  101. package/src/components/SideMenuGroup.tsx +0 -95
  102. package/src/components/SideMenuItem.tsx +0 -109
  103. package/src/components/SimpleTable.tsx +0 -77
  104. package/src/components/SkeletonParagraph.tsx +0 -31
  105. package/src/components/Spinner.tsx +0 -32
  106. package/src/components/Stack.tsx +0 -347
  107. package/src/components/StatusPill.tsx +0 -59
  108. package/src/components/Stepper.tsx +0 -128
  109. package/src/components/Subheader.tsx +0 -50
  110. package/src/components/Surface.tsx +0 -37
  111. package/src/components/Swatch.tsx +0 -1341
  112. package/src/components/Textarea.tsx +0 -102
  113. package/src/components/Theme.tsx +0 -27
  114. package/src/components/Time.tsx +0 -460
  115. package/src/components/Toast.tsx +0 -268
  116. package/src/components/Tooltip.tsx +0 -159
  117. package/src/components/TopBar.tsx +0 -139
  118. package/src/components/Upload.tsx +0 -107
  119. package/src/components/WorldpayIframe.tsx +0 -7
  120. package/src/components/index.ts +0 -34
  121. package/src/components/useMenuSystem.tsx +0 -456
  122. package/src/components/useMounted.tsx +0 -14
  123. package/src/darkmode.css +0 -278
  124. package/src/fonts.css +0 -23
  125. package/src/hooks/index.ts +0 -4
  126. package/src/hooks/useInfiniteScroll.tsx +0 -40
  127. package/src/hooks/useKeydown.ts +0 -42
  128. package/src/hooks/useMatchesMedia.ts +0 -18
  129. package/src/hooks/useTableLayout.ts +0 -106
  130. package/src/index.css +0 -800
  131. package/src/index.tsx +0 -5
  132. package/src/types.ts +0 -150
  133. package/src/utils/date.ts +0 -236
  134. package/src/utils/formatting.tsx +0 -81
  135. package/src/utils/index.ts +0 -4
  136. package/src/utils/mergeObjectArrays.ts +0 -18
  137. package/src/utils.ts +0 -24
@@ -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;