@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.
Files changed (263) hide show
  1. package/README.md +35 -0
  2. package/dist/chunk-27KIIUAR.js +59 -0
  3. package/dist/chunk-2G2E2JMA.js +123 -0
  4. package/dist/chunk-4C66DLIJ.js +51 -0
  5. package/dist/chunk-4RD5ZF2V.js +55 -0
  6. package/dist/chunk-4RJKB7LC.js +14 -0
  7. package/dist/chunk-4T7F5BZZ.js +26 -0
  8. package/dist/chunk-5GOBP2JS.js +53 -0
  9. package/dist/chunk-6ZY524ID.js +42 -0
  10. package/dist/chunk-AWQSSKCK.js +32 -0
  11. package/dist/chunk-BNHSAFMP.js +93 -0
  12. package/dist/chunk-BWRHL2AG.js +439 -0
  13. package/dist/chunk-DKKYR6DS.js +132 -0
  14. package/dist/chunk-E5ALT5W7.js +182 -0
  15. package/dist/chunk-FY7PTP6E.js +322 -0
  16. package/dist/chunk-GTCSRHPF.js +119 -0
  17. package/dist/chunk-I2UVVKQI.js +12 -0
  18. package/dist/chunk-IGQVA7SC.js +41 -0
  19. package/dist/chunk-K3IKUSZW.js +59 -0
  20. package/dist/chunk-KENSVWOY.js +151 -0
  21. package/dist/chunk-KX3O6GJ6.js +138 -0
  22. package/dist/chunk-L4UM372R.js +253 -0
  23. package/dist/chunk-ORMEWXMH.js +37 -0
  24. package/dist/chunk-Q3FKEKIN.js +23 -0
  25. package/dist/chunk-SEKKGFM6.js +28 -0
  26. package/dist/chunk-SY3HT54E.js +91 -0
  27. package/dist/chunk-TAW5ZZ4Z.js +346 -0
  28. package/dist/chunk-TRUPPHBQ.js +109 -0
  29. package/dist/chunk-TU55CHXU.js +30 -0
  30. package/dist/chunk-TWZZB4WO.js +114 -0
  31. package/dist/chunk-TYI74BSP.js +62 -0
  32. package/dist/chunk-U42SKNR6.js +104 -0
  33. package/dist/chunk-UU3FA6LV.js +72 -0
  34. package/dist/chunk-WVUIIBRR.js +51 -0
  35. package/dist/chunk-XUIPGYP5.js +39 -0
  36. package/dist/chunk-Z4UCFUF7.js +299 -0
  37. package/dist/components/Breadcrumbs.cjs +376 -0
  38. package/dist/components/Breadcrumbs.js +90 -0
  39. package/dist/components/Button.cjs +319 -0
  40. package/dist/components/Button.js +8 -0
  41. package/dist/components/CalendarRange.cjs +520 -0
  42. package/dist/components/CalendarRange.js +13 -0
  43. package/dist/components/Caption.cjs +283 -0
  44. package/dist/components/Caption.js +80 -0
  45. package/dist/components/Checkbox.cjs +378 -0
  46. package/dist/components/Checkbox.js +11 -0
  47. package/dist/components/ContentTab.cjs +382 -0
  48. package/dist/components/ContentTab.js +10 -0
  49. package/dist/components/ContentTabs.cjs +472 -0
  50. package/dist/components/ContentTabs.js +98 -0
  51. package/dist/components/DMSiLogo.cjs +79 -0
  52. package/dist/components/DMSiLogo.js +56 -0
  53. package/dist/components/DataGrid.cjs +3113 -0
  54. package/dist/components/DataGrid.js +758 -0
  55. package/dist/components/DataGridCell.cjs +1907 -0
  56. package/dist/components/DataGridCell.js +24 -0
  57. package/dist/components/DataTable.cjs +791 -0
  58. package/dist/components/DataTable.js +720 -0
  59. package/dist/components/DateInput.cjs +1130 -0
  60. package/dist/components/DateInput.js +170 -0
  61. package/dist/components/DateRangeInput.cjs +1131 -0
  62. package/dist/components/DateRangeInput.js +171 -0
  63. package/dist/components/DebugJson.cjs +50 -0
  64. package/dist/components/DebugJson.js +27 -0
  65. package/dist/components/Display.cjs +234 -0
  66. package/dist/components/Display.js +12 -0
  67. package/dist/components/EditingContext.cjs +73 -0
  68. package/dist/components/EditingContext.js +35 -0
  69. package/dist/components/FilterGroup.cjs +1431 -0
  70. package/dist/components/FilterGroup.js +231 -0
  71. package/dist/components/FullViewportBox.cjs +35 -0
  72. package/dist/components/FullViewportBox.js +12 -0
  73. package/dist/components/Grid.cjs +69 -0
  74. package/dist/components/Grid.js +36 -0
  75. package/dist/components/GridContainer.cjs +125 -0
  76. package/dist/components/GridContainer.js +92 -0
  77. package/dist/components/Heading.cjs +238 -0
  78. package/dist/components/Heading.js +14 -0
  79. package/dist/components/HorizontalDivider.cjs +33 -0
  80. package/dist/components/HorizontalDivider.js +10 -0
  81. package/dist/components/Icon.cjs +98 -0
  82. package/dist/components/Icon.js +7 -0
  83. package/dist/components/Input.cjs +672 -0
  84. package/dist/components/Input.js +21 -0
  85. package/dist/components/InputGroup.cjs +270 -0
  86. package/dist/components/InputGroup.js +60 -0
  87. package/dist/components/Label.cjs +223 -0
  88. package/dist/components/Label.js +8 -0
  89. package/dist/components/Link.cjs +262 -0
  90. package/dist/components/Link.js +8 -0
  91. package/dist/components/List.cjs +37 -0
  92. package/dist/components/List.js +14 -0
  93. package/dist/components/LiveChatComponent.cjs +63 -0
  94. package/dist/components/LiveChatComponent.js +40 -0
  95. package/dist/components/LogoAgilityTopBar.cjs +115 -0
  96. package/dist/components/LogoAgilityTopBar.js +92 -0
  97. package/dist/components/LogoDMSiTopBar.cjs +79 -0
  98. package/dist/components/LogoDMSiTopBar.js +7 -0
  99. package/dist/components/LogoMillworkTopBar.cjs +221 -0
  100. package/dist/components/LogoMillworkTopBar.js +198 -0
  101. package/dist/components/MainBar.cjs +211 -0
  102. package/dist/components/MainBar.js +65 -0
  103. package/dist/components/Menu.cjs +437 -0
  104. package/dist/components/Menu.js +11 -0
  105. package/dist/components/MenuOption.cjs +483 -0
  106. package/dist/components/MenuOption.js +13 -0
  107. package/dist/components/MobileDataGrid.cjs +658 -0
  108. package/dist/components/MobileDataGrid.js +125 -0
  109. package/dist/components/Modal.cjs +783 -0
  110. package/dist/components/Modal.js +245 -0
  111. package/dist/components/ModalButtons.cjs +385 -0
  112. package/dist/components/ModalButtons.js +10 -0
  113. package/dist/components/ModalContent.cjs +57 -0
  114. package/dist/components/ModalContent.js +7 -0
  115. package/dist/components/ModalHeader.cjs +426 -0
  116. package/dist/components/ModalHeader.js +11 -0
  117. package/dist/components/ModalScrim.cjs +64 -0
  118. package/dist/components/ModalScrim.js +7 -0
  119. package/dist/components/NavigationTab.cjs +431 -0
  120. package/dist/components/NavigationTab.js +10 -0
  121. package/dist/components/NavigationTabs.cjs +477 -0
  122. package/dist/components/NavigationTabs.js +56 -0
  123. package/dist/components/Notification.cjs +640 -0
  124. package/dist/components/Notification.js +117 -0
  125. package/dist/components/OptionPill.cjs +478 -0
  126. package/dist/components/OptionPill.js +11 -0
  127. package/dist/components/Paragraph.cjs +231 -0
  128. package/dist/components/Paragraph.js +8 -0
  129. package/dist/components/Password.cjs +700 -0
  130. package/dist/components/Password.js +53 -0
  131. package/dist/components/ProjectBar.cjs +242 -0
  132. package/dist/components/ProjectBar.js +63 -0
  133. package/dist/components/Radio.cjs +349 -0
  134. package/dist/components/Radio.js +131 -0
  135. package/dist/components/Search.cjs +767 -0
  136. package/dist/components/Search.js +12 -0
  137. package/dist/components/Select.cjs +758 -0
  138. package/dist/components/Select.js +12 -0
  139. package/dist/components/SideMenu.cjs +54 -0
  140. package/dist/components/SideMenu.js +21 -0
  141. package/dist/components/SideMenuGroup.cjs +422 -0
  142. package/dist/components/SideMenuGroup.js +83 -0
  143. package/dist/components/SideMenuItem.cjs +388 -0
  144. package/dist/components/SideMenuItem.js +70 -0
  145. package/dist/components/Stack.cjs +138 -0
  146. package/dist/components/Stack.js +7 -0
  147. package/dist/components/StatusPill.cjs +265 -0
  148. package/dist/components/StatusPill.js +52 -0
  149. package/dist/components/Stepper.cjs +885 -0
  150. package/dist/components/Stepper.js +105 -0
  151. package/dist/components/Subheader.cjs +226 -0
  152. package/dist/components/Subheader.js +8 -0
  153. package/dist/components/Surface.cjs +98 -0
  154. package/dist/components/Surface.js +40 -0
  155. package/dist/components/Swatch.cjs +1728 -0
  156. package/dist/components/Swatch.js +1319 -0
  157. package/dist/components/Textarea.cjs +269 -0
  158. package/dist/components/Textarea.js +96 -0
  159. package/dist/components/Theme.cjs +36 -0
  160. package/dist/components/Theme.js +7 -0
  161. package/dist/components/Time.cjs +1118 -0
  162. package/dist/components/Time.js +353 -0
  163. package/dist/components/Toast.cjs +644 -0
  164. package/dist/components/Toast.js +218 -0
  165. package/dist/components/Tooltip.cjs +273 -0
  166. package/dist/components/Tooltip.js +9 -0
  167. package/dist/components/TopBar.cjs +352 -0
  168. package/dist/components/TopBar.js +132 -0
  169. package/dist/components/useInfiniteScroll.cjs +57 -0
  170. package/dist/components/useInfiniteScroll.js +8 -0
  171. package/dist/components/useMatchesMedia.cjs +53 -0
  172. package/dist/components/useMatchesMedia.js +9 -0
  173. package/dist/components/useMenuSystem.cjs +358 -0
  174. package/dist/components/useMenuSystem.js +11 -0
  175. package/dist/components/useMounted.cjs +39 -0
  176. package/dist/components/useMounted.js +8 -0
  177. package/dist/fonts.css +21 -0
  178. package/dist/icons-light[FILL]-PPZXOLWS.woff2 +0 -0
  179. package/dist/icons-normal[FILL]-PPZXOLWS.woff2 +0 -0
  180. package/dist/index.css +4401 -0
  181. package/dist/open-sans-55T6A4JE.woff2 +0 -0
  182. package/dist/types.cjs +18 -0
  183. package/dist/types.js +0 -0
  184. package/package.json +66 -0
  185. package/src/brand.css +125 -0
  186. package/src/classNames.ts +144 -0
  187. package/src/components/Breadcrumbs.tsx +116 -0
  188. package/src/components/Button.tsx +210 -0
  189. package/src/components/CalendarRange.tsx +429 -0
  190. package/src/components/Caption.tsx +101 -0
  191. package/src/components/Checkbox.tsx +196 -0
  192. package/src/components/ContentTab.tsx +66 -0
  193. package/src/components/ContentTabs.tsx +103 -0
  194. package/src/components/DMSiLogo.tsx +32 -0
  195. package/src/components/DataGrid.tsx +948 -0
  196. package/src/components/DataGridCell.tsx +384 -0
  197. package/src/components/DataTable.tsx +835 -0
  198. package/src/components/DateInput.tsx +188 -0
  199. package/src/components/DateRangeInput.tsx +179 -0
  200. package/src/components/DebugJson.tsx +24 -0
  201. package/src/components/Display.tsx +60 -0
  202. package/src/components/EditingContext.tsx +40 -0
  203. package/src/components/FilterGroup.tsx +234 -0
  204. package/src/components/FullViewportBox.tsx +11 -0
  205. package/src/components/Grid.tsx +75 -0
  206. package/src/components/GridContainer.tsx +124 -0
  207. package/src/components/Heading.tsx +66 -0
  208. package/src/components/HorizontalDivider.tsx +3 -0
  209. package/src/components/Icon.tsx +36 -0
  210. package/src/components/Input.tsx +511 -0
  211. package/src/components/InputGroup.tsx +51 -0
  212. package/src/components/Label.tsx +40 -0
  213. package/src/components/Link.tsx +106 -0
  214. package/src/components/List.tsx +10 -0
  215. package/src/components/LiveChatComponent.tsx +56 -0
  216. package/src/components/LogoAgilityTopBar.tsx +53 -0
  217. package/src/components/LogoDMSiTopBar.tsx +32 -0
  218. package/src/components/LogoMillworkTopBar.tsx +118 -0
  219. package/src/components/MainBar.tsx +83 -0
  220. package/src/components/Menu.tsx +286 -0
  221. package/src/components/MenuOption.tsx +275 -0
  222. package/src/components/MobileDataGrid.tsx +135 -0
  223. package/src/components/Modal.tsx +271 -0
  224. package/src/components/ModalButtons.tsx +44 -0
  225. package/src/components/ModalContent.tsx +23 -0
  226. package/src/components/ModalHeader.tsx +41 -0
  227. package/src/components/ModalScrim.tsx +35 -0
  228. package/src/components/NavigationTab.tsx +89 -0
  229. package/src/components/NavigationTabs.tsx +63 -0
  230. package/src/components/Notification.tsx +120 -0
  231. package/src/components/OptionPill.tsx +114 -0
  232. package/src/components/Paragraph.tsx +49 -0
  233. package/src/components/Password.tsx +46 -0
  234. package/src/components/ProjectBar.tsx +76 -0
  235. package/src/components/Radio.tsx +140 -0
  236. package/src/components/Search.tsx +129 -0
  237. package/src/components/Select.tsx +104 -0
  238. package/src/components/SideMenu.tsx +21 -0
  239. package/src/components/SideMenuGroup.tsx +81 -0
  240. package/src/components/SideMenuItem.tsx +90 -0
  241. package/src/components/Stack.tsx +179 -0
  242. package/src/components/StatusPill.tsx +51 -0
  243. package/src/components/Stepper.tsx +91 -0
  244. package/src/components/Subheader.tsx +44 -0
  245. package/src/components/Surface.tsx +34 -0
  246. package/src/components/Swatch.tsx +1066 -0
  247. package/src/components/Textarea.tsx +101 -0
  248. package/src/components/Theme.tsx +13 -0
  249. package/src/components/Time.tsx +438 -0
  250. package/src/components/Toast.tsx +244 -0
  251. package/src/components/Tooltip.tsx +137 -0
  252. package/src/components/TopBar.tsx +124 -0
  253. package/src/components/useInfiniteScroll.tsx +40 -0
  254. package/src/components/useMatchesMedia.tsx +28 -0
  255. package/src/components/useMenuSystem.tsx +367 -0
  256. package/src/components/useMounted.tsx +14 -0
  257. package/src/darkmode.css +140 -0
  258. package/src/fonts.css +23 -0
  259. package/src/index.css +509 -0
  260. package/src/index.tsx +2 -0
  261. package/src/types.ts +149 -0
  262. package/src/utils/formatting.tsx +81 -0
  263. 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";