@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,275 @@
1
+ "use client";
2
+
3
+ import clsx from "clsx";
4
+ import { PropsWithChildren, ReactNode, RefObject, useId, useRef } from "react";
5
+ import { baseTransition, componentGap, componentPadding } from "../classNames";
6
+ import { AsProps } from "../types";
7
+ import { Label, Tags } from "./Label";
8
+ import { Paragraph } from "./Paragraph";
9
+ import { Icon } from "./Icon";
10
+ import { MenuPosition } from "./Menu";
11
+ import { useMatchesMobile } from "./useMatchesMedia";
12
+
13
+ type BaseProps = PropsWithChildren<{
14
+ disabled?: boolean;
15
+ value?: string;
16
+ before?: ReactNode;
17
+ after?: ReactNode;
18
+ onClick?: (id: string, value?: string) => void;
19
+ ref?: React.Ref<HTMLDivElement> | undefined;
20
+ mobilePositionTo?: RefObject<HTMLElement | null>;
21
+ selected?: boolean;
22
+ currentSubMenuLevel?: number | null;
23
+ }> & (
24
+ | {
25
+ subMenu: (props: {
26
+ menuId: string,
27
+ positionTo?: RefObject<HTMLDivElement>,
28
+ mobilePositionTo?: RefObject<HTMLElement | null>,
29
+ position?: MenuPosition,
30
+ subMenuLevel: number,
31
+ mobileBackMenuOption?: () => ReactNode;
32
+ mobileHide?: boolean;
33
+ }) => ReactNode;
34
+ subMenuLevel?: number;
35
+ onSubMenuHover: (menuId: string, level: number) => void;
36
+ onSubMenuLeave: (level: number) => void;
37
+ onSubMenuEnter: () => void;
38
+ toggleMenu: (id: string, level: number) => void;
39
+ activeMenu: string;
40
+ closeSubMenuLevel: (level: number) => void;
41
+ }
42
+ | {
43
+ subMenu?: undefined;
44
+ subMenuLevel?: number;
45
+ onSubMenuHover?: (menuId: string, level: number) => void;
46
+ onSubMenuLeave?: (level: number) => void;
47
+ onSubMenuEnter?: () => void;
48
+ toggleMenu?: (id: string, level: number) => void;
49
+ closeSubMenuLevel?: (level: number) => void;
50
+ activeMenu?: string;
51
+ }
52
+ ) & (
53
+ | {
54
+ highlightMatchingText: true;
55
+ menuValue: string;
56
+ }
57
+ | {
58
+ highlightMatchingText?: false | undefined;
59
+ menuValue?: string;
60
+ }
61
+ );
62
+
63
+ type ActionProps = BaseProps & {
64
+ variant: "action";
65
+ } & AsProps<Tags>;
66
+
67
+ type NormalProps = BaseProps & {
68
+ variant?: "normal";
69
+ };
70
+
71
+ type MenuOptionProps = ActionProps | NormalProps;
72
+
73
+ export const MenuOption = ({
74
+ children,
75
+ disabled = false,
76
+ variant = "normal",
77
+ value,
78
+ before,
79
+ after,
80
+ subMenu,
81
+ onClick,
82
+ selected,
83
+ ref,
84
+ onSubMenuHover,
85
+ onSubMenuLeave,
86
+ onSubMenuEnter,
87
+ toggleMenu,
88
+ subMenuLevel = 1,
89
+ currentSubMenuLevel,
90
+ closeSubMenuLevel,
91
+ activeMenu,
92
+ mobilePositionTo,
93
+ highlightMatchingText = false,
94
+ menuValue
95
+ }: MenuOptionProps) => {
96
+ const uniqueId = useId();
97
+ const internalRef = useRef(null);
98
+ const actualRef = (ref || internalRef) as RefObject<HTMLDivElement>;
99
+ const menuId = useRef(`menu-${uniqueId}`);
100
+ const isMobile = useMatchesMobile();
101
+
102
+ const handleMouseEnter = () => {
103
+ if (subMenu && onSubMenuHover && !disabled) {
104
+ onSubMenuHover(menuId.current, subMenuLevel);
105
+ }
106
+ };
107
+
108
+ const handleMouseLeave = () => {
109
+ if (subMenu && onSubMenuLeave && !disabled) {
110
+ onSubMenuLeave(subMenuLevel);
111
+ }
112
+ };
113
+
114
+ const handleSubMenuEnter = () => {
115
+ if (onSubMenuEnter) {
116
+ onSubMenuEnter();
117
+ }
118
+ };
119
+
120
+ const additionalAttributes = {
121
+ "data-selected": selected || null,
122
+ };
123
+
124
+ const svgStyles = clsx(
125
+ "[&>svg]:shrink-0 [&>svg]:fill-icon-action-primary-normal",
126
+ );
127
+
128
+ const textLabelStyles = clsx("w-full whitespace-nowrap !leading-6");
129
+
130
+ const normalStyles =
131
+ variant === "normal" &&
132
+ !disabled &&
133
+ clsx(
134
+ "bg-transparent text-text-primary-normal",
135
+ "hover:bg-background-action-secondary-hover",
136
+ "focus:bg-background-action-secondary-hover",
137
+ "data-selected:bg-background-action-secondary-hover",
138
+ "active:bg-background-action-secondary-active",
139
+ );
140
+
141
+ const normalDisabledStyles =
142
+ variant === "normal" && disabled && clsx("text-text-primary-disabled");
143
+
144
+ const actionStyles =
145
+ variant === "action" &&
146
+ !disabled &&
147
+ clsx(
148
+ "text-action-400 bg-transparent",
149
+ "hover:bg-background-action-secondary-hover hover:text-text-action-hover",
150
+ "focus:bg-background-action-secondary-hover focus:text-text-action-hover",
151
+ "data-selected:bg-background-action-secondary-active data-selected:text-text-action-active",
152
+ "active:bg-background-action-secondary-active active:text-text-action-active",
153
+ );
154
+
155
+ const actionDisabledStyles =
156
+ variant === "action" && disabled && clsx("text-text-action-disabled");
157
+
158
+ const disabledStyles =
159
+ disabled && clsx("bg-transparent cursor-default pointer-events-none");
160
+
161
+ const renderChildren = typeof children === 'string' && highlightMatchingText
162
+ ? (
163
+ highlightMatch(children, menuValue)
164
+ ) : children
165
+
166
+ return (
167
+ <>
168
+ <div
169
+ ref={actualRef}
170
+ className={clsx(
171
+ "flex items-center cursor-pointer w-full text-left relative outline-none",
172
+ svgStyles,
173
+ componentGap,
174
+ componentPadding,
175
+ baseTransition,
176
+ normalStyles,
177
+ normalDisabledStyles,
178
+ actionStyles,
179
+ actionDisabledStyles,
180
+ disabledStyles,
181
+ )}
182
+ data-value={value || children}
183
+ onClick={() => {
184
+ onClick?.(menuId.current, (value || children) as string);
185
+ if (subMenu) {
186
+ toggleMenu(menuId.current, subMenuLevel)
187
+ }
188
+ }}
189
+ onMouseEnter={handleMouseEnter}
190
+ onMouseLeave={handleMouseLeave}
191
+ {...additionalAttributes}
192
+ tabIndex={-1}
193
+ role="menuitem"
194
+ aria-haspopup={subMenu ? "menu" : undefined}
195
+ >
196
+
197
+ {before && (
198
+ <div className="shrink-0 flex items-center">{before}</div>
199
+ )}
200
+
201
+ {variant === "action" ? (
202
+ <Label padded className={textLabelStyles}>
203
+ {renderChildren}
204
+ </Label>
205
+ ) : (
206
+ <Paragraph padded className={textLabelStyles}>
207
+ {renderChildren}
208
+ </Paragraph>
209
+ )}
210
+
211
+ {renderAfterProp()}
212
+ </div>
213
+
214
+ {subMenu && (
215
+ <div
216
+ onMouseEnter={handleSubMenuEnter}
217
+ onMouseLeave={handleMouseLeave}
218
+ data-submenu-parent={menuId.current}
219
+ data-menu-level={subMenuLevel + 1}
220
+ >
221
+ {subMenu({ menuId: menuId.current, positionTo: actualRef, mobilePositionTo, position: "right", subMenuLevel, mobileBackMenuOption, mobileHide: isMobile && activeMenu !== menuId.current })}
222
+ </div>
223
+ )}
224
+ </>
225
+ );
226
+
227
+ function renderAfterProp() {
228
+ if (after) {
229
+ return after;
230
+ }
231
+
232
+ if (subMenu && after !== null) {
233
+ return <Icon name="chevron_right" />
234
+ }
235
+ }
236
+
237
+ function mobileBackMenuOption() {
238
+ if (!isMobile) {
239
+ return;
240
+ }
241
+
242
+ return (
243
+ <MenuOption
244
+ onClick={() => {
245
+ closeSubMenuLevel?.(currentSubMenuLevel ?? 0)
246
+ }}
247
+ variant="action"
248
+ before={<Icon name="chevron_left" />}
249
+ >
250
+ Back
251
+ </MenuOption>
252
+ )
253
+ }
254
+ };
255
+
256
+ MenuOption.displayName = "MenuOption";
257
+
258
+ function highlightMatch(text: string, searchValue?: string): React.ReactNode {
259
+ if (!searchValue || !searchValue.trim()) {
260
+ return text;
261
+ }
262
+
263
+ const regex = new RegExp(`(${searchValue.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
264
+ const parts = text.split(regex);
265
+
266
+ return parts.map((part, index) =>
267
+ regex.test(part) ? (
268
+ <span key={index} className="font-bold">
269
+ {part}
270
+ </span>
271
+ ) : (
272
+ part
273
+ )
274
+ );
275
+ }
@@ -0,0 +1,135 @@
1
+ import clsx from "clsx";
2
+ import { Icon } from "./Icon";
3
+ import { ComponentProps } from "react";
4
+ import { Stack } from "./Stack";
5
+ import { Button } from "./Button";
6
+ import { Theme } from "./Theme";
7
+ import { Heading3 } from "./Heading";
8
+ import { Paragraph } from "./Paragraph";
9
+
10
+ export type RowData = Record<string, unknown>;
11
+
12
+ export type MobileColumnDef<T extends RowData> = {
13
+ header: string;
14
+ accessorKey: keyof T;
15
+ show?: boolean;
16
+
17
+ cell?: (info: { row: T }) => React.ReactNode;
18
+ };
19
+
20
+ export function MobileDataGrid<T extends RowData>({
21
+ columns,
22
+ data,
23
+ header,
24
+ getId,
25
+ renderLink,
26
+ renderChevron,
27
+ }: {
28
+ columns: MobileColumnDef<T>[];
29
+ data: T[];
30
+ header: string;
31
+ getId: (data: T) => string | number | undefined;
32
+ renderLink?: (data: T) => React.ReactNode;
33
+ renderChevron?: (data: T) => React.ReactNode;
34
+ } & ComponentProps<"div">) {
35
+ return (
36
+ <div
37
+ className={clsx(
38
+ "rounded",
39
+ "overflow-hidden",
40
+ "divide-y divide-border-primary-normal",
41
+ "border border-border-primary-normal",
42
+ "overflow-y-scroll scrollbar-thin",
43
+ )}
44
+ >
45
+ <MobileDataGridHeader header={header} />
46
+ {data.map((item) => (
47
+ <MobileDataGridCard
48
+ data={item}
49
+ key={getId(item)}
50
+ columns={columns}
51
+ renderLink={renderLink}
52
+ renderChevron={renderChevron}
53
+ />
54
+ ))}
55
+ </div>
56
+ );
57
+ }
58
+
59
+ function MobileDataGridHeader({ header }: { header: string }) {
60
+ return (
61
+ <div className="sticky top-0">
62
+ <Theme theme="brand">
63
+ <Stack
64
+ horizontal
65
+ items="start"
66
+ justify="center"
67
+ backgroundColor="background-primary-normal"
68
+ padding
69
+ sizing="component"
70
+ >
71
+ <Heading3 as="span" color="text-primary-normal">
72
+ {header}
73
+ </Heading3>
74
+ </Stack>
75
+ </Theme>
76
+ </div>
77
+ );
78
+ }
79
+
80
+ function MobileDataGridCard<T extends RowData>({
81
+ data,
82
+ columns,
83
+ renderLink,
84
+ renderChevron,
85
+ }: {
86
+ data: T;
87
+ columns: MobileColumnDef<T>[];
88
+ renderLink?: (data: T) => React.ReactNode;
89
+ renderChevron?: (data: T) => React.ReactNode;
90
+ }) {
91
+ return (
92
+ <div>
93
+ <Stack sizing="component" padding>
94
+ <Stack horizontal horizontalMobile items="center" justify="between">
95
+ {renderLink && renderLink(data)}
96
+ <Stack horizontal horizontalMobile items="center" justify="end">
97
+ <Button
98
+ iconOnly
99
+ variant="tertiary"
100
+ onClick={() => console.log("Edit", data.id)}
101
+ leftIcon={<Icon name="docs" />}
102
+ ></Button>
103
+ <Button
104
+ iconOnly
105
+ variant="tertiary"
106
+ onClick={() => console.log("Edit", data.id)}
107
+ leftIcon={<Icon name="swap_vert" />}
108
+ ></Button>
109
+ </Stack>
110
+ </Stack>
111
+
112
+ {columns
113
+ .filter((x) => x.show)
114
+ .map((column) => (
115
+ <div
116
+ key={String(column.accessorKey)}
117
+ className="mb-2 grid grid-cols-2"
118
+ >
119
+ <Paragraph color="text-secondary-normal" className="text-left">
120
+ {column.header}:
121
+ </Paragraph>{" "}
122
+ {column.cell
123
+ ? column.cell({ row: data })
124
+ : String(data[column.accessorKey])}
125
+ </div>
126
+ ))}
127
+ </Stack>
128
+ {renderChevron && (
129
+ <Stack items="center" justify="center" horizontal horizontalMobile>
130
+ {renderChevron(data)}
131
+ </Stack>
132
+ )}
133
+ </div>
134
+ );
135
+ }
@@ -0,0 +1,271 @@
1
+ "use client";
2
+
3
+ import clsx from "clsx";
4
+ import { PropsWithChildren, useCallback, useEffect, useRef } from "react";
5
+ import { ModalHeader } from "./ModalHeader";
6
+ import { ModalContent } from "./ModalContent";
7
+ import { ModalButtons } from "./ModalButtons";
8
+ import { ModalScrim } from "./ModalScrim";
9
+ import { createPortal } from "react-dom";
10
+ import { findDocumentRoot } from "../utils";
11
+ import { usePrevious } from "react-use";
12
+ import { useMatchesMobile } from "./useMatchesMedia";
13
+ import { useMounted } from "./useMounted";
14
+
15
+ type ModalProps = PropsWithChildren<{
16
+ title?: string;
17
+ open?: boolean;
18
+ size?: "small" | "medium" | "large" | "x-large";
19
+ className?: string;
20
+ onClose?: () => void;
21
+ onContinue?: () => void;
22
+ closeOnBackdropClick?: boolean;
23
+ showButtons?: boolean;
24
+ hideCloseIcon?: boolean;
25
+ headerIcon?: React.ReactNode;
26
+ fixedHeightScrolling?: boolean;
27
+ customActions?: React.ReactNode;
28
+ }>;
29
+
30
+ const fadeInScale = (element: HTMLElement, duration = 300) =>
31
+ element.animate(
32
+ [
33
+ { opacity: 0, transform: "scale(0.95)" },
34
+ { opacity: 1, transform: "scale(1)" },
35
+ ],
36
+ {
37
+ duration,
38
+ easing: "cubic-bezier(0.4, 0, 0.2, 1)",
39
+ fill: "both",
40
+ },
41
+ );
42
+
43
+ const fadeOutScale = (element: HTMLElement, duration = 200) =>
44
+ element.animate(
45
+ [
46
+ { opacity: 1, transform: "scale(1)" },
47
+ { opacity: 0, transform: "scale(0.95)" },
48
+ ],
49
+ {
50
+ duration,
51
+ easing: "ease-in-out",
52
+ fill: "both",
53
+ },
54
+ );
55
+
56
+ const bgFadeIn = (element: HTMLElement, duration = 300) =>
57
+ element.animate([{ opacity: 0 }, { opacity: 1 }], {
58
+ duration,
59
+ easing: "cubic-bezier(0.4, 0, 0.2, 1)",
60
+ fill: "both",
61
+ });
62
+
63
+ const bgFadeOut = (element: HTMLElement, duration = 200) =>
64
+ element.animate([{ opacity: 1 }, { opacity: 0 }], {
65
+ duration,
66
+ easing: "ease-in-out",
67
+ fill: "both",
68
+ });
69
+
70
+ const whenAllAnimationsFinish = (
71
+ animations: Animation[],
72
+ callback: () => void,
73
+ ) => {
74
+ let finishedCount = 0;
75
+
76
+ animations.forEach((animation) => {
77
+ animation.onfinish = () => {
78
+ finishedCount += 1;
79
+ if (finishedCount === animations.length) {
80
+ callback();
81
+ }
82
+ };
83
+ });
84
+ };
85
+
86
+ const sizes = {
87
+ small: {
88
+ sizeClass: "max-h-screen desktop:max-w-120 rounded-sm",
89
+ },
90
+ medium: {
91
+ sizeClass:
92
+ "desktop:max-w-180 w-screen desktop:h-fit h-screen desktop:w-full max-w-240",
93
+ },
94
+ large: {
95
+ sizeClass:
96
+ "desktop:max-w-240 w-screen desktop:h-fit h-screen desktop:w-full max-w-240",
97
+ },
98
+ "x-large": {
99
+ sizeClass:
100
+ "desktop:max-w-300 w-screen desktop:h-fit h-screen desktop:w-full max-w-240",
101
+ },
102
+ };
103
+
104
+ export const Modal = ({
105
+ title,
106
+ open = false,
107
+ size = "small",
108
+ className,
109
+ children,
110
+ onClose,
111
+ onContinue,
112
+ closeOnBackdropClick = true,
113
+ showButtons = false,
114
+ hideCloseIcon = false,
115
+ headerIcon,
116
+ fixedHeightScrolling = false,
117
+ customActions,
118
+ }: ModalProps) => {
119
+ const mounted = useMounted();
120
+
121
+ const modalRef = useRef<HTMLDivElement>(null);
122
+ const bgRef = useRef<HTMLDivElement>(null);
123
+
124
+ const wasOpen = usePrevious(open);
125
+
126
+ const isMobile = useMatchesMobile();
127
+
128
+ const computedFixedHeightScrolling = isMobile || fixedHeightScrolling;
129
+
130
+ useEffect(() => {
131
+ if (!mounted) return;
132
+ if (!modalRef.current || !bgRef.current) {
133
+ console.error("Modal or background reference is not set.");
134
+ return;
135
+ }
136
+ if (wasOpen === undefined) return;
137
+ if (wasOpen && !open) {
138
+ const modalAnimation = fadeOutScale(modalRef.current);
139
+ const bgAnimation = bgFadeOut(bgRef.current);
140
+ whenAllAnimationsFinish([modalAnimation, bgAnimation], () => {
141
+ if (onClose) {
142
+ onClose();
143
+ }
144
+ });
145
+ } else if (!wasOpen && open) {
146
+ fadeInScale(modalRef.current);
147
+ bgFadeIn(bgRef.current);
148
+ }
149
+ }, [mounted, open]);
150
+
151
+ const handleKeyDown = useCallback(
152
+ (e: KeyboardEvent) => {
153
+ if (e.key === "Escape") {
154
+ if (onClose) {
155
+ e.preventDefault();
156
+ onClose();
157
+ }
158
+ }
159
+ },
160
+ [onClose, bgRef, modalRef],
161
+ );
162
+
163
+ const handleClose = useCallback(() => {
164
+ if (onClose) {
165
+ onClose();
166
+ }
167
+ }, [onClose]);
168
+
169
+ useEffect(() => {
170
+ if (open) {
171
+ document.addEventListener("keyup", handleKeyDown);
172
+ }
173
+
174
+ return () => {
175
+ document.removeEventListener("keyup", handleKeyDown);
176
+ };
177
+ }, [open, handleKeyDown]);
178
+
179
+ // Effect to lock scroll when modal is open
180
+ useEffect(() => {
181
+ if (!open) return;
182
+
183
+ const scrollY = window.scrollY;
184
+ const body = document.body;
185
+
186
+ // Lock scroll
187
+ body.style.position = "fixed";
188
+ body.style.top = `-${scrollY}px`;
189
+ body.style.left = "0";
190
+ body.style.right = "0";
191
+ body.style.overflow = "hidden";
192
+ body.style.width = "100%";
193
+
194
+ return () => {
195
+ // Restore scroll
196
+ body.style.position = "";
197
+ body.style.top = "";
198
+ body.style.left = "";
199
+ body.style.right = "";
200
+ body.style.overflow = "";
201
+ body.style.width = "";
202
+
203
+ window.scrollTo(0, scrollY);
204
+ };
205
+ }, [open]);
206
+
207
+ const { sizeClass } = sizes[size] ?? sizes.small;
208
+
209
+ const backgroundClickHandler = useCallback(
210
+ (e: React.MouseEvent<HTMLElement>) => {
211
+ const target = e.target as HTMLElement;
212
+ const currentTarget = e.currentTarget as HTMLElement;
213
+ if (currentTarget.contains(target) && currentTarget !== target) {
214
+ e.stopPropagation();
215
+ return;
216
+ }
217
+
218
+ if (open && closeOnBackdropClick) {
219
+ handleClose();
220
+ }
221
+ },
222
+ [open, closeOnBackdropClick, handleClose],
223
+ );
224
+
225
+ if (!mounted) {
226
+ return null;
227
+ }
228
+
229
+ return createPortal(
230
+ <ModalScrim
231
+ size={size}
232
+ ref={bgRef}
233
+ show={open}
234
+ onClick={backgroundClickHandler}
235
+ >
236
+ <div
237
+ ref={modalRef}
238
+ className={clsx(
239
+ "bg-white shadow-md rounded-sm flex flex-col overflow-hidden w-full opacity-0 h-fit",
240
+ computedFixedHeightScrolling &&
241
+ "desktop:max-h-[calc(100vh-32px)] desktop:h-auto",
242
+ className,
243
+ sizeClass,
244
+ )}
245
+ onClick={(e) => e.stopPropagation()}
246
+ >
247
+ <ModalHeader
248
+ title={title}
249
+ onClose={handleClose}
250
+ hideCloseIcon={hideCloseIcon}
251
+ headerIcon={headerIcon}
252
+ />
253
+ {children && (
254
+ <ModalContent fixedHeightScrolling={computedFixedHeightScrolling}>
255
+ {children}
256
+ </ModalContent>
257
+ )}
258
+ {showButtons && (
259
+ <ModalButtons
260
+ onClose={handleClose}
261
+ onContinue={onContinue}
262
+ customActions={customActions}
263
+ />
264
+ )}
265
+ </div>
266
+ </ModalScrim>,
267
+ findDocumentRoot(bgRef.current),
268
+ );
269
+ };
270
+
271
+ Modal.displayName = "Modal";