@dmsi/wedgekit-react 0.0.2
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/README.md +35 -0
- package/dist/chunk-27KIIUAR.js +59 -0
- package/dist/chunk-2G2E2JMA.js +123 -0
- package/dist/chunk-4C66DLIJ.js +51 -0
- package/dist/chunk-4RD5ZF2V.js +55 -0
- package/dist/chunk-4RJKB7LC.js +14 -0
- package/dist/chunk-4T7F5BZZ.js +26 -0
- package/dist/chunk-5GOBP2JS.js +53 -0
- package/dist/chunk-6ZY524ID.js +42 -0
- package/dist/chunk-AWQSSKCK.js +32 -0
- package/dist/chunk-BNHSAFMP.js +93 -0
- package/dist/chunk-BWRHL2AG.js +439 -0
- package/dist/chunk-DKKYR6DS.js +132 -0
- package/dist/chunk-E5ALT5W7.js +182 -0
- package/dist/chunk-FY7PTP6E.js +322 -0
- package/dist/chunk-GTCSRHPF.js +119 -0
- package/dist/chunk-I2UVVKQI.js +12 -0
- package/dist/chunk-IGQVA7SC.js +41 -0
- package/dist/chunk-K3IKUSZW.js +59 -0
- package/dist/chunk-KENSVWOY.js +151 -0
- package/dist/chunk-KX3O6GJ6.js +138 -0
- package/dist/chunk-L4UM372R.js +253 -0
- package/dist/chunk-ORMEWXMH.js +37 -0
- package/dist/chunk-Q3FKEKIN.js +23 -0
- package/dist/chunk-SEKKGFM6.js +28 -0
- package/dist/chunk-SY3HT54E.js +91 -0
- package/dist/chunk-TAW5ZZ4Z.js +346 -0
- package/dist/chunk-TRUPPHBQ.js +109 -0
- package/dist/chunk-TU55CHXU.js +30 -0
- package/dist/chunk-TWZZB4WO.js +114 -0
- package/dist/chunk-TYI74BSP.js +62 -0
- package/dist/chunk-U42SKNR6.js +104 -0
- package/dist/chunk-UU3FA6LV.js +72 -0
- package/dist/chunk-WVUIIBRR.js +51 -0
- package/dist/chunk-XUIPGYP5.js +39 -0
- package/dist/chunk-Z4UCFUF7.js +299 -0
- package/dist/components/Breadcrumbs.cjs +376 -0
- package/dist/components/Breadcrumbs.js +90 -0
- package/dist/components/Button.cjs +319 -0
- package/dist/components/Button.js +8 -0
- package/dist/components/CalendarRange.cjs +520 -0
- package/dist/components/CalendarRange.js +13 -0
- package/dist/components/Caption.cjs +283 -0
- package/dist/components/Caption.js +80 -0
- package/dist/components/Checkbox.cjs +378 -0
- package/dist/components/Checkbox.js +11 -0
- package/dist/components/ContentTab.cjs +382 -0
- package/dist/components/ContentTab.js +10 -0
- package/dist/components/ContentTabs.cjs +472 -0
- package/dist/components/ContentTabs.js +98 -0
- package/dist/components/DMSiLogo.cjs +79 -0
- package/dist/components/DMSiLogo.js +56 -0
- package/dist/components/DataGrid.cjs +3113 -0
- package/dist/components/DataGrid.js +758 -0
- package/dist/components/DataGridCell.cjs +1907 -0
- package/dist/components/DataGridCell.js +24 -0
- package/dist/components/DataTable.cjs +791 -0
- package/dist/components/DataTable.js +720 -0
- package/dist/components/DateInput.cjs +1130 -0
- package/dist/components/DateInput.js +170 -0
- package/dist/components/DateRangeInput.cjs +1131 -0
- package/dist/components/DateRangeInput.js +171 -0
- package/dist/components/DebugJson.cjs +50 -0
- package/dist/components/DebugJson.js +27 -0
- package/dist/components/Display.cjs +234 -0
- package/dist/components/Display.js +12 -0
- package/dist/components/EditingContext.cjs +73 -0
- package/dist/components/EditingContext.js +35 -0
- package/dist/components/FilterGroup.cjs +1431 -0
- package/dist/components/FilterGroup.js +231 -0
- package/dist/components/FullViewportBox.cjs +35 -0
- package/dist/components/FullViewportBox.js +12 -0
- package/dist/components/Grid.cjs +69 -0
- package/dist/components/Grid.js +36 -0
- package/dist/components/GridContainer.cjs +125 -0
- package/dist/components/GridContainer.js +92 -0
- package/dist/components/Heading.cjs +238 -0
- package/dist/components/Heading.js +14 -0
- package/dist/components/HorizontalDivider.cjs +33 -0
- package/dist/components/HorizontalDivider.js +10 -0
- package/dist/components/Icon.cjs +98 -0
- package/dist/components/Icon.js +7 -0
- package/dist/components/Input.cjs +672 -0
- package/dist/components/Input.js +21 -0
- package/dist/components/InputGroup.cjs +270 -0
- package/dist/components/InputGroup.js +60 -0
- package/dist/components/Label.cjs +223 -0
- package/dist/components/Label.js +8 -0
- package/dist/components/Link.cjs +262 -0
- package/dist/components/Link.js +8 -0
- package/dist/components/List.cjs +37 -0
- package/dist/components/List.js +14 -0
- package/dist/components/LiveChatComponent.cjs +63 -0
- package/dist/components/LiveChatComponent.js +40 -0
- package/dist/components/LogoAgilityTopBar.cjs +115 -0
- package/dist/components/LogoAgilityTopBar.js +92 -0
- package/dist/components/LogoDMSiTopBar.cjs +79 -0
- package/dist/components/LogoDMSiTopBar.js +7 -0
- package/dist/components/LogoMillworkTopBar.cjs +221 -0
- package/dist/components/LogoMillworkTopBar.js +198 -0
- package/dist/components/MainBar.cjs +211 -0
- package/dist/components/MainBar.js +65 -0
- package/dist/components/Menu.cjs +437 -0
- package/dist/components/Menu.js +11 -0
- package/dist/components/MenuOption.cjs +483 -0
- package/dist/components/MenuOption.js +13 -0
- package/dist/components/MobileDataGrid.cjs +658 -0
- package/dist/components/MobileDataGrid.js +125 -0
- package/dist/components/Modal.cjs +783 -0
- package/dist/components/Modal.js +245 -0
- package/dist/components/ModalButtons.cjs +385 -0
- package/dist/components/ModalButtons.js +10 -0
- package/dist/components/ModalContent.cjs +57 -0
- package/dist/components/ModalContent.js +7 -0
- package/dist/components/ModalHeader.cjs +426 -0
- package/dist/components/ModalHeader.js +11 -0
- package/dist/components/ModalScrim.cjs +64 -0
- package/dist/components/ModalScrim.js +7 -0
- package/dist/components/NavigationTab.cjs +431 -0
- package/dist/components/NavigationTab.js +10 -0
- package/dist/components/NavigationTabs.cjs +477 -0
- package/dist/components/NavigationTabs.js +56 -0
- package/dist/components/Notification.cjs +640 -0
- package/dist/components/Notification.js +117 -0
- package/dist/components/OptionPill.cjs +478 -0
- package/dist/components/OptionPill.js +11 -0
- package/dist/components/Paragraph.cjs +231 -0
- package/dist/components/Paragraph.js +8 -0
- package/dist/components/Password.cjs +700 -0
- package/dist/components/Password.js +53 -0
- package/dist/components/ProjectBar.cjs +242 -0
- package/dist/components/ProjectBar.js +63 -0
- package/dist/components/Radio.cjs +349 -0
- package/dist/components/Radio.js +131 -0
- package/dist/components/Search.cjs +767 -0
- package/dist/components/Search.js +12 -0
- package/dist/components/Select.cjs +758 -0
- package/dist/components/Select.js +12 -0
- package/dist/components/SideMenu.cjs +54 -0
- package/dist/components/SideMenu.js +21 -0
- package/dist/components/SideMenuGroup.cjs +422 -0
- package/dist/components/SideMenuGroup.js +83 -0
- package/dist/components/SideMenuItem.cjs +388 -0
- package/dist/components/SideMenuItem.js +70 -0
- package/dist/components/Stack.cjs +138 -0
- package/dist/components/Stack.js +7 -0
- package/dist/components/StatusPill.cjs +265 -0
- package/dist/components/StatusPill.js +52 -0
- package/dist/components/Stepper.cjs +885 -0
- package/dist/components/Stepper.js +105 -0
- package/dist/components/Subheader.cjs +226 -0
- package/dist/components/Subheader.js +8 -0
- package/dist/components/Surface.cjs +98 -0
- package/dist/components/Surface.js +40 -0
- package/dist/components/Swatch.cjs +1728 -0
- package/dist/components/Swatch.js +1319 -0
- package/dist/components/Textarea.cjs +269 -0
- package/dist/components/Textarea.js +96 -0
- package/dist/components/Theme.cjs +36 -0
- package/dist/components/Theme.js +7 -0
- package/dist/components/Time.cjs +1118 -0
- package/dist/components/Time.js +353 -0
- package/dist/components/Toast.cjs +644 -0
- package/dist/components/Toast.js +218 -0
- package/dist/components/Tooltip.cjs +273 -0
- package/dist/components/Tooltip.js +9 -0
- package/dist/components/TopBar.cjs +352 -0
- package/dist/components/TopBar.js +132 -0
- package/dist/components/useInfiniteScroll.cjs +57 -0
- package/dist/components/useInfiniteScroll.js +8 -0
- package/dist/components/useMatchesMedia.cjs +53 -0
- package/dist/components/useMatchesMedia.js +9 -0
- package/dist/components/useMenuSystem.cjs +358 -0
- package/dist/components/useMenuSystem.js +11 -0
- package/dist/components/useMounted.cjs +39 -0
- package/dist/components/useMounted.js +8 -0
- package/dist/fonts.css +21 -0
- package/dist/icons-light[FILL]-PPZXOLWS.woff2 +0 -0
- package/dist/icons-normal[FILL]-PPZXOLWS.woff2 +0 -0
- package/dist/index.css +4401 -0
- package/dist/open-sans-55T6A4JE.woff2 +0 -0
- package/dist/types.cjs +18 -0
- package/dist/types.js +0 -0
- package/package.json +66 -0
- package/src/brand.css +125 -0
- package/src/classNames.ts +144 -0
- package/src/components/Breadcrumbs.tsx +116 -0
- package/src/components/Button.tsx +210 -0
- package/src/components/CalendarRange.tsx +429 -0
- package/src/components/Caption.tsx +101 -0
- package/src/components/Checkbox.tsx +196 -0
- package/src/components/ContentTab.tsx +66 -0
- package/src/components/ContentTabs.tsx +103 -0
- package/src/components/DMSiLogo.tsx +32 -0
- package/src/components/DataGrid.tsx +948 -0
- package/src/components/DataGridCell.tsx +384 -0
- package/src/components/DataTable.tsx +835 -0
- package/src/components/DateInput.tsx +188 -0
- package/src/components/DateRangeInput.tsx +179 -0
- package/src/components/DebugJson.tsx +24 -0
- package/src/components/Display.tsx +60 -0
- package/src/components/EditingContext.tsx +40 -0
- package/src/components/FilterGroup.tsx +234 -0
- package/src/components/FullViewportBox.tsx +11 -0
- package/src/components/Grid.tsx +75 -0
- package/src/components/GridContainer.tsx +124 -0
- package/src/components/Heading.tsx +66 -0
- package/src/components/HorizontalDivider.tsx +3 -0
- package/src/components/Icon.tsx +36 -0
- package/src/components/Input.tsx +511 -0
- package/src/components/InputGroup.tsx +51 -0
- package/src/components/Label.tsx +40 -0
- package/src/components/Link.tsx +106 -0
- package/src/components/List.tsx +10 -0
- package/src/components/LiveChatComponent.tsx +56 -0
- package/src/components/LogoAgilityTopBar.tsx +53 -0
- package/src/components/LogoDMSiTopBar.tsx +32 -0
- package/src/components/LogoMillworkTopBar.tsx +118 -0
- package/src/components/MainBar.tsx +83 -0
- package/src/components/Menu.tsx +286 -0
- package/src/components/MenuOption.tsx +275 -0
- package/src/components/MobileDataGrid.tsx +135 -0
- package/src/components/Modal.tsx +271 -0
- package/src/components/ModalButtons.tsx +44 -0
- package/src/components/ModalContent.tsx +23 -0
- package/src/components/ModalHeader.tsx +41 -0
- package/src/components/ModalScrim.tsx +35 -0
- package/src/components/NavigationTab.tsx +89 -0
- package/src/components/NavigationTabs.tsx +63 -0
- package/src/components/Notification.tsx +120 -0
- package/src/components/OptionPill.tsx +114 -0
- package/src/components/Paragraph.tsx +49 -0
- package/src/components/Password.tsx +46 -0
- package/src/components/ProjectBar.tsx +76 -0
- package/src/components/Radio.tsx +140 -0
- package/src/components/Search.tsx +129 -0
- package/src/components/Select.tsx +104 -0
- package/src/components/SideMenu.tsx +21 -0
- package/src/components/SideMenuGroup.tsx +81 -0
- package/src/components/SideMenuItem.tsx +90 -0
- package/src/components/Stack.tsx +179 -0
- package/src/components/StatusPill.tsx +51 -0
- package/src/components/Stepper.tsx +91 -0
- package/src/components/Subheader.tsx +44 -0
- package/src/components/Surface.tsx +34 -0
- package/src/components/Swatch.tsx +1066 -0
- package/src/components/Textarea.tsx +101 -0
- package/src/components/Theme.tsx +13 -0
- package/src/components/Time.tsx +438 -0
- package/src/components/Toast.tsx +244 -0
- package/src/components/Tooltip.tsx +137 -0
- package/src/components/TopBar.tsx +124 -0
- package/src/components/useInfiniteScroll.tsx +40 -0
- package/src/components/useMatchesMedia.tsx +28 -0
- package/src/components/useMenuSystem.tsx +367 -0
- package/src/components/useMounted.tsx +14 -0
- package/src/darkmode.css +140 -0
- package/src/fonts.css +23 -0
- package/src/index.css +509 -0
- package/src/index.tsx +2 -0
- package/src/types.ts +149 -0
- package/src/utils/formatting.tsx +81 -0
- package/src/utils.ts +23 -0
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
import clsx from "clsx";
|
|
2
|
+
import {
|
|
3
|
+
typography,
|
|
4
|
+
componentGap,
|
|
5
|
+
componentPadding,
|
|
6
|
+
// baseTransition,
|
|
7
|
+
paddingUsingComponentGap,
|
|
8
|
+
componentPaddingXUsingComponentGap,
|
|
9
|
+
componentPaddingYUsingComponentGap,
|
|
10
|
+
} from "../classNames";
|
|
11
|
+
import { Icon } from "./Icon";
|
|
12
|
+
import React, { useState } from "react";
|
|
13
|
+
import { Temporal } from "@js-temporal/polyfill";
|
|
14
|
+
|
|
15
|
+
export interface CalendarRangeProps {
|
|
16
|
+
from?: string | number;
|
|
17
|
+
to?: string | number;
|
|
18
|
+
onChange?: (from: string, to: string) => void;
|
|
19
|
+
isDateAvailable?: (date: Temporal.PlainDate) => boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Display mode: 'single' for one month, 'double' for two months side by side (default)
|
|
22
|
+
*/
|
|
23
|
+
mode?: "single" | "double";
|
|
24
|
+
cardStyle?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Disable range selection in single mode. When true, single mode will only allow single date selection.
|
|
27
|
+
* Only applies when mode is "single".
|
|
28
|
+
*/
|
|
29
|
+
disableRange?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Returns true if the given date is a weekend (Saturday or Sunday).
|
|
34
|
+
* @param date Temporal.PlainDate
|
|
35
|
+
*/
|
|
36
|
+
export function isWeekend(date: Temporal.PlainDate): boolean {
|
|
37
|
+
// 6 = Saturday, 7 = Sunday (Temporal: 1=Monday, 7=Sunday)
|
|
38
|
+
return date.dayOfWeek === 6 || date.dayOfWeek === 7;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// DateCell component for rendering a single day
|
|
42
|
+
interface DateCellProps {
|
|
43
|
+
date: Temporal.PlainDate;
|
|
44
|
+
isInMonth: boolean;
|
|
45
|
+
isToday: boolean;
|
|
46
|
+
isSelected: boolean;
|
|
47
|
+
inRange: boolean;
|
|
48
|
+
isDisabled: boolean;
|
|
49
|
+
onClick: () => void;
|
|
50
|
+
onMouseEnter: () => void;
|
|
51
|
+
onMouseLeave: () => void;
|
|
52
|
+
/** True if this cell is the start of the selected range */
|
|
53
|
+
isRangeStart?: boolean;
|
|
54
|
+
/** True if this cell is the end of the selected range */
|
|
55
|
+
isRangeEnd?: boolean;
|
|
56
|
+
/** True if range selection is disabled in single mode */
|
|
57
|
+
isRangeDisabled?: boolean;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function DateCell({
|
|
61
|
+
date,
|
|
62
|
+
isInMonth,
|
|
63
|
+
isToday,
|
|
64
|
+
isSelected,
|
|
65
|
+
inRange,
|
|
66
|
+
isDisabled,
|
|
67
|
+
isRangeStart,
|
|
68
|
+
isRangeEnd,
|
|
69
|
+
onClick,
|
|
70
|
+
onMouseEnter,
|
|
71
|
+
onMouseLeave,
|
|
72
|
+
cellPadding = "",
|
|
73
|
+
isRangeDisabled = false,
|
|
74
|
+
...props
|
|
75
|
+
}: DateCellProps & { cellPadding?: string }) {
|
|
76
|
+
return (
|
|
77
|
+
<span
|
|
78
|
+
{...props}
|
|
79
|
+
className={clsx(
|
|
80
|
+
"flex items-center justify-center aspect-square select-none transition-colors border duration-100 font-medium",
|
|
81
|
+
typography.caption,
|
|
82
|
+
cellPadding,
|
|
83
|
+
!isToday &&
|
|
84
|
+
!isSelected &&
|
|
85
|
+
!inRange &&
|
|
86
|
+
!isDisabled &&
|
|
87
|
+
!isRangeStart &&
|
|
88
|
+
!isRangeEnd &&
|
|
89
|
+
"border-transparent",
|
|
90
|
+
!isInMonth && "border-transparent",
|
|
91
|
+
// Today: subtle border ring
|
|
92
|
+
isToday &&
|
|
93
|
+
!isSelected &&
|
|
94
|
+
!inRange &&
|
|
95
|
+
"rounded-full border-border-primary-normal ",
|
|
96
|
+
// Selected: Figma blue, white text, strong shadow
|
|
97
|
+
isSelected && "bg-action-400 text-white border-action-400 z-10",
|
|
98
|
+
!isSelected && !inRange && "rounded-base",
|
|
99
|
+
// When range is disabled OR when only 'from' is selected (no range yet), apply rounded corners
|
|
100
|
+
(isRangeDisabled || (!inRange && isSelected)) && "rounded-base",
|
|
101
|
+
inRange && isSelected && "hover:border-action-500",
|
|
102
|
+
// In range: Figma light blue background
|
|
103
|
+
inRange &&
|
|
104
|
+
!isSelected &&
|
|
105
|
+
"bg-action-100 text-text-primary-normal border-y-action-400 border-x-0 ",
|
|
106
|
+
// Disabled: Figma gray, no pointer, no hover
|
|
107
|
+
isDisabled && !inRange
|
|
108
|
+
? "text-text-primary-disabled bg-transparent pointer-events-none opacity-40 border-transparent"
|
|
109
|
+
: [
|
|
110
|
+
"text-text-primary-normal cursor-pointer",
|
|
111
|
+
// Figma hover: blue bg, blue text (or red text if selected)
|
|
112
|
+
isSelected
|
|
113
|
+
? "hover:bg-background-action-primary-hover hover:text-white"
|
|
114
|
+
: "hover:bg-action-100 hover:text-text-action-primary-hover",
|
|
115
|
+
// Figma active: darker blue bg, white text
|
|
116
|
+
"active:bg-action-300 active:text-white",
|
|
117
|
+
// Figma focus: ring
|
|
118
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-action-400",
|
|
119
|
+
],
|
|
120
|
+
isRangeStart && "rounded-l",
|
|
121
|
+
isRangeEnd && "rounded-r",
|
|
122
|
+
)}
|
|
123
|
+
tabIndex={isDisabled ? -1 : 0}
|
|
124
|
+
aria-disabled={isDisabled}
|
|
125
|
+
onClick={() => !isDisabled && isInMonth && onClick()}
|
|
126
|
+
onMouseEnter={() => isInMonth && onMouseEnter()}
|
|
127
|
+
onMouseLeave={() => isInMonth && onMouseLeave()}
|
|
128
|
+
>
|
|
129
|
+
{isInMonth ? date.day : ""}
|
|
130
|
+
</span>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function CalendarRange({
|
|
135
|
+
from,
|
|
136
|
+
to,
|
|
137
|
+
onChange,
|
|
138
|
+
isDateAvailable,
|
|
139
|
+
mode = "double",
|
|
140
|
+
cardStyle = false,
|
|
141
|
+
disableRange = false,
|
|
142
|
+
}: CalendarRangeProps) {
|
|
143
|
+
const weekDays = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
|
|
144
|
+
// Parse from/to props
|
|
145
|
+
const parseDate = (d?: string | number) => {
|
|
146
|
+
if (!d) return undefined;
|
|
147
|
+
if (typeof d === "number")
|
|
148
|
+
return Temporal.PlainDate.from(new Date(d).toISOString().slice(0, 10));
|
|
149
|
+
if (typeof d === "string") return Temporal.PlainDate.from(d);
|
|
150
|
+
return undefined;
|
|
151
|
+
};
|
|
152
|
+
const fromDate = parseDate(from);
|
|
153
|
+
const toDate = parseDate(to);
|
|
154
|
+
|
|
155
|
+
// Internal state for visible months and selection
|
|
156
|
+
const today = Temporal.Now.plainDateISO();
|
|
157
|
+
const [baseMonth, setBaseMonth] = useState(
|
|
158
|
+
fromDate ?? today.with({ day: 1 }),
|
|
159
|
+
);
|
|
160
|
+
const [selecting, setSelecting] = useState<"from" | "to">("from");
|
|
161
|
+
const [pendingFrom, setPendingFrom] = useState<
|
|
162
|
+
Temporal.PlainDate | undefined
|
|
163
|
+
>(undefined);
|
|
164
|
+
const [hoveredDate, setHoveredDate] = useState<
|
|
165
|
+
Temporal.PlainDate | undefined
|
|
166
|
+
>(undefined);
|
|
167
|
+
|
|
168
|
+
// Helper to get month data
|
|
169
|
+
function getMonthData(monthOffset: number) {
|
|
170
|
+
const monthDate = baseMonth.add({ months: monthOffset }).with({ day: 1 });
|
|
171
|
+
const days = monthDate.daysInMonth;
|
|
172
|
+
const firstDayOffset = monthDate.dayOfWeek % 7;
|
|
173
|
+
return {
|
|
174
|
+
name: monthDate.toLocaleString("en-US", { month: "long" }),
|
|
175
|
+
year: monthDate.year,
|
|
176
|
+
days,
|
|
177
|
+
firstDayOffset,
|
|
178
|
+
date: monthDate,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Click handlers
|
|
183
|
+
function handleDayClick(date: Temporal.PlainDate) {
|
|
184
|
+
if (isDateAvailable && !isDateAvailable(date)) return;
|
|
185
|
+
|
|
186
|
+
// Single mode with range disabled: just select the date directly
|
|
187
|
+
if (mode === "single" && disableRange) {
|
|
188
|
+
if (onChange) {
|
|
189
|
+
onChange(date.toString(), date.toString());
|
|
190
|
+
}
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Range mode (or single mode with range enabled): handle two-step selection
|
|
195
|
+
if (selecting === "from") {
|
|
196
|
+
setPendingFrom(date);
|
|
197
|
+
setSelecting("to");
|
|
198
|
+
setHoveredDate(undefined);
|
|
199
|
+
// Hide current from/to selection when starting a new selection
|
|
200
|
+
} else if (pendingFrom) {
|
|
201
|
+
if (onChange) {
|
|
202
|
+
const [start, end] =
|
|
203
|
+
Temporal.PlainDate.compare(date, pendingFrom) < 0
|
|
204
|
+
? [date, pendingFrom]
|
|
205
|
+
: [pendingFrom, date];
|
|
206
|
+
onChange(start.toString(), end.toString());
|
|
207
|
+
}
|
|
208
|
+
setPendingFrom(undefined);
|
|
209
|
+
setSelecting("from");
|
|
210
|
+
setHoveredDate(undefined);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Range highlighting
|
|
215
|
+
function isInRange(date: Temporal.PlainDate) {
|
|
216
|
+
// No range highlighting in single mode when range is disabled
|
|
217
|
+
if (mode === "single" && disableRange) {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (pendingFrom && selecting === "to" && hoveredDate) {
|
|
222
|
+
const [start, end] =
|
|
223
|
+
Temporal.PlainDate.compare(hoveredDate, pendingFrom) < 0
|
|
224
|
+
? [hoveredDate, pendingFrom]
|
|
225
|
+
: [pendingFrom, hoveredDate];
|
|
226
|
+
return (
|
|
227
|
+
Temporal.PlainDate.compare(date, start) >= 0 &&
|
|
228
|
+
Temporal.PlainDate.compare(date, end) <= 0
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
if (!pendingFrom && fromDate && toDate) {
|
|
232
|
+
return (
|
|
233
|
+
Temporal.PlainDate.compare(date, fromDate) >= 0 &&
|
|
234
|
+
Temporal.PlainDate.compare(date, toDate) <= 0
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Render
|
|
241
|
+
return (
|
|
242
|
+
<div
|
|
243
|
+
className={clsx(
|
|
244
|
+
"relative bg-background-grouped-primary-normal rounded-base w-fit",
|
|
245
|
+
componentPadding,
|
|
246
|
+
componentGap,
|
|
247
|
+
cardStyle && "shadow-4",
|
|
248
|
+
// baseTransition,
|
|
249
|
+
"overflow-hidden",
|
|
250
|
+
)}
|
|
251
|
+
>
|
|
252
|
+
<div
|
|
253
|
+
className={clsx(
|
|
254
|
+
"flex flex-row items-start justify-start bg-background-primary-normal overflow-clip",
|
|
255
|
+
componentGap,
|
|
256
|
+
paddingUsingComponentGap,
|
|
257
|
+
)}
|
|
258
|
+
>
|
|
259
|
+
{(mode === "double" ? [0, 1] : [0]).map((offset, idx) => {
|
|
260
|
+
const month = getMonthData(offset);
|
|
261
|
+
// Always show 6 weeks (42 days)
|
|
262
|
+
const totalCells = 42;
|
|
263
|
+
const emptyCells = month.firstDayOffset;
|
|
264
|
+
return (
|
|
265
|
+
<React.Fragment key={month.name + month.year}>
|
|
266
|
+
<div
|
|
267
|
+
// key={month.name + month.year}
|
|
268
|
+
className={clsx(
|
|
269
|
+
"flex flex-col",
|
|
270
|
+
componentGap,
|
|
271
|
+
componentPadding,
|
|
272
|
+
)}
|
|
273
|
+
>
|
|
274
|
+
<div
|
|
275
|
+
className={clsx(
|
|
276
|
+
"flex flex-row items-center justify-between",
|
|
277
|
+
componentGap,
|
|
278
|
+
componentPaddingXUsingComponentGap,
|
|
279
|
+
componentPaddingYUsingComponentGap,
|
|
280
|
+
"mb-3",
|
|
281
|
+
typography.label,
|
|
282
|
+
"text-text-action-primary-normal",
|
|
283
|
+
)}
|
|
284
|
+
>
|
|
285
|
+
{idx === 0 ? (
|
|
286
|
+
<button
|
|
287
|
+
type="button"
|
|
288
|
+
className={clsx(
|
|
289
|
+
"flex items-center justify-center rounded-base hover:bg-action-100 active:bg-action-300 text-icon-primary-normal",
|
|
290
|
+
componentPadding,
|
|
291
|
+
"mr-1",
|
|
292
|
+
)}
|
|
293
|
+
aria-label="Previous month"
|
|
294
|
+
onClick={() =>
|
|
295
|
+
setBaseMonth(baseMonth.subtract({ months: 1 }))
|
|
296
|
+
}
|
|
297
|
+
>
|
|
298
|
+
<Icon name="chevron_left" size={24} />
|
|
299
|
+
</button>
|
|
300
|
+
) : (
|
|
301
|
+
<span className={clsx(componentPadding, "mr-1")} />
|
|
302
|
+
)}
|
|
303
|
+
<div className="flex gap-desktop-compact-component-padding">
|
|
304
|
+
<span className="font-semibold text-text-action-primary-normal text-[14px] leading-[1] truncate">
|
|
305
|
+
{month.name}
|
|
306
|
+
</span>
|
|
307
|
+
<span className="font-semibold text-text-action-primary-normal text-[14px] leading-[1] px-1 truncate">
|
|
308
|
+
{month.year}
|
|
309
|
+
</span>
|
|
310
|
+
</div>
|
|
311
|
+
{(mode === "double" ? idx === 1 : true) ? (
|
|
312
|
+
<button
|
|
313
|
+
type="button"
|
|
314
|
+
className={clsx(
|
|
315
|
+
"flex items-center justify-center rounded-base hover:bg-action-100 active:bg-action-300 text-icon-primary-normal",
|
|
316
|
+
componentPadding,
|
|
317
|
+
"ml-1",
|
|
318
|
+
)}
|
|
319
|
+
aria-label="Next month"
|
|
320
|
+
onClick={() => setBaseMonth(baseMonth.add({ months: 1 }))}
|
|
321
|
+
>
|
|
322
|
+
<Icon name="chevron_right" size={24} />
|
|
323
|
+
</button>
|
|
324
|
+
) : (
|
|
325
|
+
<span className={clsx(componentPadding, "ml-1")} />
|
|
326
|
+
)}
|
|
327
|
+
</div>
|
|
328
|
+
<div className={clsx("grid grid-cols-7 mb-1", componentGap)}>
|
|
329
|
+
{weekDays.map((d) => (
|
|
330
|
+
<span
|
|
331
|
+
key={d}
|
|
332
|
+
className={clsx(
|
|
333
|
+
typography.caption,
|
|
334
|
+
"text-text-secondary-normal text-center",
|
|
335
|
+
)}
|
|
336
|
+
>
|
|
337
|
+
{d}
|
|
338
|
+
</span>
|
|
339
|
+
))}
|
|
340
|
+
</div>
|
|
341
|
+
<div className={clsx("grid grid-cols-7")}>
|
|
342
|
+
{Array.from({ length: totalCells }).map((_, i) => {
|
|
343
|
+
const day = i - emptyCells + 1;
|
|
344
|
+
const date = month.date.with({ day: 1 }).add({
|
|
345
|
+
days: i - emptyCells,
|
|
346
|
+
});
|
|
347
|
+
const isInMonth = day > 0 && day <= month.days;
|
|
348
|
+
const isToday = isInMonth && date.equals(today);
|
|
349
|
+
const isSelected =
|
|
350
|
+
isInMonth &&
|
|
351
|
+
((!pendingFrom && fromDate && date.equals(fromDate)) ||
|
|
352
|
+
(!pendingFrom && toDate && date.equals(toDate)) ||
|
|
353
|
+
(pendingFrom && date.equals(pendingFrom)));
|
|
354
|
+
const inRange = isInMonth && isInRange(date);
|
|
355
|
+
const isDisabled =
|
|
356
|
+
!isInMonth ||
|
|
357
|
+
(isDateAvailable ? !isDateAvailable(date) : false);
|
|
358
|
+
// New: determine if this is the start or end of the range
|
|
359
|
+
|
|
360
|
+
const hoverDateIsBeforePendingFrom =
|
|
361
|
+
hoveredDate &&
|
|
362
|
+
pendingFrom &&
|
|
363
|
+
Temporal.PlainDate.compare(hoveredDate, pendingFrom) < 0;
|
|
364
|
+
|
|
365
|
+
const hoverDateIsAfterPendingFrom =
|
|
366
|
+
hoveredDate &&
|
|
367
|
+
pendingFrom &&
|
|
368
|
+
Temporal.PlainDate.compare(hoveredDate, pendingFrom) >= 0;
|
|
369
|
+
|
|
370
|
+
const isRangeStart =
|
|
371
|
+
mode === "single" && disableRange
|
|
372
|
+
? false
|
|
373
|
+
: (!pendingFrom &&
|
|
374
|
+
isInMonth &&
|
|
375
|
+
fromDate &&
|
|
376
|
+
date.equals(fromDate)) ||
|
|
377
|
+
(hoverDateIsAfterPendingFrom &&
|
|
378
|
+
date.equals(pendingFrom));
|
|
379
|
+
|
|
380
|
+
const isRangeEnd =
|
|
381
|
+
mode === "single" && disableRange
|
|
382
|
+
? false
|
|
383
|
+
: (!pendingFrom &&
|
|
384
|
+
isInMonth &&
|
|
385
|
+
toDate &&
|
|
386
|
+
date.equals(toDate)) ||
|
|
387
|
+
(hoverDateIsBeforePendingFrom &&
|
|
388
|
+
date.equals(pendingFrom));
|
|
389
|
+
return (
|
|
390
|
+
<DateCell
|
|
391
|
+
key={i}
|
|
392
|
+
date={date}
|
|
393
|
+
isInMonth={!!isInMonth}
|
|
394
|
+
isToday={!!isToday}
|
|
395
|
+
isSelected={!!isSelected}
|
|
396
|
+
inRange={!!inRange}
|
|
397
|
+
isDisabled={!!isDisabled}
|
|
398
|
+
onClick={() => handleDayClick(date)}
|
|
399
|
+
onMouseEnter={() => setHoveredDate(date)}
|
|
400
|
+
onMouseLeave={() => setHoveredDate(undefined)}
|
|
401
|
+
isRangeStart={!!isRangeStart}
|
|
402
|
+
isRangeEnd={!!isRangeEnd}
|
|
403
|
+
isRangeDisabled={mode === "single" && disableRange}
|
|
404
|
+
// Add cell padding for spacing
|
|
405
|
+
cellPadding="p-1.5 md:p-2 lg:p-2.5"
|
|
406
|
+
/>
|
|
407
|
+
);
|
|
408
|
+
})}
|
|
409
|
+
</div>
|
|
410
|
+
</div>
|
|
411
|
+
{mode === "double" && idx === 0 && (
|
|
412
|
+
<div
|
|
413
|
+
className={clsx(
|
|
414
|
+
"self-stretch bg-border-primary-normal rounded-base mx-mobile-component-padding desktop:mx-desktop-component-padding compact:mx-desktop-compact-component-padding",
|
|
415
|
+
|
|
416
|
+
// 1px width, full height, matches Figma divider
|
|
417
|
+
"w-px",
|
|
418
|
+
)}
|
|
419
|
+
/>
|
|
420
|
+
)}
|
|
421
|
+
</React.Fragment>
|
|
422
|
+
);
|
|
423
|
+
})}
|
|
424
|
+
</div>
|
|
425
|
+
</div>
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
export default CalendarRange;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import clsx from "clsx";
|
|
2
|
+
import { AsProps, IconSize, TextAttributes, TypographyProps } from "../types";
|
|
3
|
+
import { typography } from "../classNames";
|
|
4
|
+
import { Icon } from "./Icon";
|
|
5
|
+
|
|
6
|
+
type Tags = "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "span" | "p" | "li";
|
|
7
|
+
type Style = {
|
|
8
|
+
style?: "default" | "success" | "warning" | "error" | "info";
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
type CaptionProps = {
|
|
12
|
+
as?: Tags;
|
|
13
|
+
style?: Style["style"];
|
|
14
|
+
} & AsProps<Tags> &
|
|
15
|
+
TextAttributes &
|
|
16
|
+
TypographyProps;
|
|
17
|
+
|
|
18
|
+
export const Caption = ({
|
|
19
|
+
className,
|
|
20
|
+
children,
|
|
21
|
+
as = "span",
|
|
22
|
+
style = "default",
|
|
23
|
+
color,
|
|
24
|
+
align,
|
|
25
|
+
...props
|
|
26
|
+
}: CaptionProps) => {
|
|
27
|
+
const Element = as;
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div className="flex gap-1">
|
|
31
|
+
<WhichIcon style={style} size={16} className="mt-[3px] desktop:mt-0" />
|
|
32
|
+
|
|
33
|
+
<Element
|
|
34
|
+
className={clsx(
|
|
35
|
+
typography.caption.replace("text-text-primary-normal", ""),
|
|
36
|
+
(style === "default" || style === "info") &&
|
|
37
|
+
"text-text-secondary-normal",
|
|
38
|
+
style === "success" && "text-text-success-normal",
|
|
39
|
+
style === "warning" && "text-text-warning-normal",
|
|
40
|
+
style === "error" && "text-text-critical-normal",
|
|
41
|
+
align === "left" && "text-left",
|
|
42
|
+
align === "center" && "text-center",
|
|
43
|
+
align === "right" && "text-right",
|
|
44
|
+
className,
|
|
45
|
+
)}
|
|
46
|
+
{...props}
|
|
47
|
+
style={{
|
|
48
|
+
color: color ? `var(--color-${color})` : undefined,
|
|
49
|
+
}}
|
|
50
|
+
>
|
|
51
|
+
{children}
|
|
52
|
+
</Element>
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const WhichIcon = ({
|
|
58
|
+
style,
|
|
59
|
+
size,
|
|
60
|
+
className,
|
|
61
|
+
}: {
|
|
62
|
+
style: Style["style"];
|
|
63
|
+
size: IconSize;
|
|
64
|
+
className?: string;
|
|
65
|
+
}) => {
|
|
66
|
+
if (style === "success") {
|
|
67
|
+
return (
|
|
68
|
+
<span className="text-icon-success-normal contents">
|
|
69
|
+
<Icon className={className}name="check_circle" size={size} />
|
|
70
|
+
</span>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (style === "warning") {
|
|
75
|
+
return (
|
|
76
|
+
<span className="text-icon-warning-normal contents">
|
|
77
|
+
<Icon className={className}name="warning" size={size} />
|
|
78
|
+
</span>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (style === "error") {
|
|
83
|
+
return (
|
|
84
|
+
<span className="text-icon-critical-normal contents">
|
|
85
|
+
<Icon className={className}name="info" size={size} />
|
|
86
|
+
</span>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (style === "info") {
|
|
91
|
+
return (
|
|
92
|
+
<span className="text-icon-primary-normal contents">
|
|
93
|
+
<Icon className={className}name="info" size={size} />
|
|
94
|
+
</span>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return null;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
Caption.displayName = "Caption";
|