@reactberry/system 2.0.0-beta

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 (165) hide show
  1. package/README.md +48 -0
  2. package/package.json +74 -0
  3. package/src/blocks/Accordion/index.tsx +158 -0
  4. package/src/blocks/AnimatedCarousel/index.tsx +188 -0
  5. package/src/blocks/AppleGlow/index.tsx +144 -0
  6. package/src/blocks/Avatar/index.tsx +167 -0
  7. package/src/blocks/Await/index.tsx +45 -0
  8. package/src/blocks/Cards/AnimatedCard/index.tsx +175 -0
  9. package/src/blocks/Cards/FluorescentCard/index.tsx +180 -0
  10. package/src/blocks/Cards/InfoCard/index.tsx +206 -0
  11. package/src/blocks/Cards/TickerCard/index.tsx +125 -0
  12. package/src/blocks/Carousel/index.tsx +216 -0
  13. package/src/blocks/Checkbox/index.tsx +101 -0
  14. package/src/blocks/Collection/index.tsx +59 -0
  15. package/src/blocks/Container/index.tsx +55 -0
  16. package/src/blocks/Controls/Control.tsx +67 -0
  17. package/src/blocks/Controls/index.tsx +11 -0
  18. package/src/blocks/CyclingNumber/index.tsx +78 -0
  19. package/src/blocks/DisplaySet/index.tsx +42 -0
  20. package/src/blocks/Divider/index.tsx +14 -0
  21. package/src/blocks/Draggable/index.tsx +266 -0
  22. package/src/blocks/Drawer/index.tsx +136 -0
  23. package/src/blocks/DynamicIsland/DynamicIsland.tsx +89 -0
  24. package/src/blocks/DynamicIsland/index.tsx +2 -0
  25. package/src/blocks/Fader/index.tsx +145 -0
  26. package/src/blocks/FamilyDrawer/README.md +116 -0
  27. package/src/blocks/FamilyDrawer/example.tsx +108 -0
  28. package/src/blocks/FamilyDrawer/index.tsx +119 -0
  29. package/src/blocks/FamilyDrawer/views/DefaultView.tsx +93 -0
  30. package/src/blocks/FamilyDrawer/views/KeyView.tsx +129 -0
  31. package/src/blocks/FamilyDrawer/views/PhraseView.tsx +129 -0
  32. package/src/blocks/FamilyDrawer/views/RemoveView.tsx +81 -0
  33. package/src/blocks/FieldSet/index.tsx +173 -0
  34. package/src/blocks/Filesystem/index.tsx +198 -0
  35. package/src/blocks/Gallery/Carousel/index.tsx +257 -0
  36. package/src/blocks/Gallery/Modal/index.tsx +83 -0
  37. package/src/blocks/Gallery/index.tsx +57 -0
  38. package/src/blocks/Gallery/utils/animationVariants.ts +18 -0
  39. package/src/blocks/Gallery/utils/aspectRatio.ts +14 -0
  40. package/src/blocks/Gallery/utils/downloadPhoto.ts +24 -0
  41. package/src/blocks/Gallery/utils/range.ts +11 -0
  42. package/src/blocks/GradientMesh/index.tsx +106 -0
  43. package/src/blocks/Group/index.tsx +152 -0
  44. package/src/blocks/Heading/index.tsx +111 -0
  45. package/src/blocks/HorizontalScroller/index.tsx +135 -0
  46. package/src/blocks/Icon/index.tsx +45 -0
  47. package/src/blocks/Indicator/index.tsx +27 -0
  48. package/src/blocks/InlineEditor/index.tsx +216 -0
  49. package/src/blocks/List/index.tsx +657 -0
  50. package/src/blocks/Main/index.tsx +17 -0
  51. package/src/blocks/Marquee/index.tsx +116 -0
  52. package/src/blocks/MaskedField/index.tsx +199 -0
  53. package/src/blocks/Menu/MenuContent.tsx +246 -0
  54. package/src/blocks/Menu/MenuContext.tsx +34 -0
  55. package/src/blocks/Menu/MenuItem.tsx +104 -0
  56. package/src/blocks/Menu/index.tsx +60 -0
  57. package/src/blocks/Modal/index.tsx +268 -0
  58. package/src/blocks/MorphingPopover/index.tsx +294 -0
  59. package/src/blocks/Overlay/Backdrop.tsx +48 -0
  60. package/src/blocks/Overlay/OverscrollGuard.tsx +36 -0
  61. package/src/blocks/Overlay/index.ts +2 -0
  62. package/src/blocks/Parallax/index.tsx +117 -0
  63. package/src/blocks/ParallaxSection/index.tsx +61 -0
  64. package/src/blocks/Placeholder/index.tsx +48 -0
  65. package/src/blocks/Popover/index.tsx +402 -0
  66. package/src/blocks/Progress/getProgressColor.ts +61 -0
  67. package/src/blocks/Progress/index.tsx +179 -0
  68. package/src/blocks/ProgressiveBlur/index.tsx +75 -0
  69. package/src/blocks/README.md +15 -0
  70. package/src/blocks/RenderAsset/index.tsx +18 -0
  71. package/src/blocks/ScrollContainer/index.tsx +93 -0
  72. package/src/blocks/ShinyText/index.tsx +72 -0
  73. package/src/blocks/Skeleton/index.tsx +71 -0
  74. package/src/blocks/Slider/SliderControls.tsx +119 -0
  75. package/src/blocks/Slider/index.tsx +140 -0
  76. package/src/blocks/Slider/useSlider.ts +126 -0
  77. package/src/blocks/Slideshow/index.tsx +177 -0
  78. package/src/blocks/Spotlight/index.tsx +144 -0
  79. package/src/blocks/Steps/StepIndicator.tsx +149 -0
  80. package/src/blocks/Steps/StepProgress.tsx +164 -0
  81. package/src/blocks/Steps/Steps.tsx +197 -0
  82. package/src/blocks/Steps/StepsNav.tsx +30 -0
  83. package/src/blocks/Steps/StepsTracker.tsx +80 -0
  84. package/src/blocks/Steps/hooks.ts +71 -0
  85. package/src/blocks/Steps/index.tsx +16 -0
  86. package/src/blocks/Steps/types.ts +71 -0
  87. package/src/blocks/StickySectionStack/index.tsx +136 -0
  88. package/src/blocks/Switch/index.tsx +85 -0
  89. package/src/blocks/SystemNotice/index.tsx +81 -0
  90. package/src/blocks/Table/README.md +251 -0
  91. package/src/blocks/Table/Table.tsx +207 -0
  92. package/src/blocks/Table/TablePagination.tsx +189 -0
  93. package/src/blocks/Table/index.ts +33 -0
  94. package/src/blocks/Table/useTableControls.ts +331 -0
  95. package/src/blocks/Tag/index.tsx +27 -0
  96. package/src/blocks/TextBreak/index.tsx +96 -0
  97. package/src/blocks/TextReveal/index.tsx +104 -0
  98. package/src/blocks/Thumbnail/index.tsx +26 -0
  99. package/src/blocks/Ticker/index.tsx +112 -0
  100. package/src/blocks/Toast/index.tsx +77 -0
  101. package/src/blocks/Tooltip/index.tsx +174 -0
  102. package/src/blocks/Underlay/index.tsx +104 -0
  103. package/src/blocks/Upload/Dropzone.tsx +92 -0
  104. package/src/blocks/Upload/UploadBtn.tsx +38 -0
  105. package/src/blocks/Upload/index.tsx +61 -0
  106. package/src/blocks/Upload/types.ts +37 -0
  107. package/src/blocks/VideoMarquee/index.tsx +511 -0
  108. package/src/blocks/index.ts +119 -0
  109. package/src/blocks/pagination/Pagination.tsx +148 -0
  110. package/src/blocks/pagination/PaginationList.tsx +41 -0
  111. package/src/blocks/pagination/index.ts +2 -0
  112. package/src/charts/BarChart.tsx +63 -0
  113. package/src/charts/PieChart.tsx +39 -0
  114. package/src/charts/index.ts +3 -0
  115. package/src/charts/utils.ts +103 -0
  116. package/src/docs/README.md +373 -0
  117. package/src/docs/reference/README.md +299 -0
  118. package/src/elements/box.ts +163 -0
  119. package/src/elements/button.ts +49 -0
  120. package/src/elements/field.ts +129 -0
  121. package/src/elements/index.ts +8 -0
  122. package/src/elements/text.ts +47 -0
  123. package/src/elements/utils.js +97 -0
  124. package/src/hooks/use-copy-to-clipboard.tsx +33 -0
  125. package/src/hooks/use-enter-submit.tsx +23 -0
  126. package/src/hooks/use-local-storage.ts +42 -0
  127. package/src/hooks/use-sidebar.tsx +109 -0
  128. package/src/hooks/useAnimatedText.ts +32 -0
  129. package/src/hooks/useAutosizeTextArea.ts +45 -0
  130. package/src/hooks/useBreakpoint.tsx +123 -0
  131. package/src/hooks/useClickOutside.tsx +38 -0
  132. package/src/hooks/useHover.tsx +33 -0
  133. package/src/hooks/useHoverList.tsx +17 -0
  134. package/src/hooks/useKeyboardShortcuts.ts +91 -0
  135. package/src/hooks/useKeypress.ts +27 -0
  136. package/src/hooks/useOverlay.ts +32 -0
  137. package/src/hooks/useReducedMotion.ts +25 -0
  138. package/src/hooks/useStandaloneMode.ts +35 -0
  139. package/src/hooks/useTouchDevice.ts +34 -0
  140. package/src/icons/index.tsx +129 -0
  141. package/src/index.ts +12 -0
  142. package/src/providers/DesignSystemProvider.tsx +35 -0
  143. package/src/providers/StyledComponentsRegistry.tsx +30 -0
  144. package/src/providers/index.ts +2 -0
  145. package/src/themes/README.md +30 -0
  146. package/src/themes/default/assets/badge-avatar.tsx +45 -0
  147. package/src/themes/default/assets/logo.tsx +42 -0
  148. package/src/themes/default/global.ts +138 -0
  149. package/src/themes/default/modes/dark/config.js +49 -0
  150. package/src/themes/default/modes/dark/skins.js +631 -0
  151. package/src/themes/default/modes/dark/theme.js +87 -0
  152. package/src/themes/default/modes/light/config.js +48 -0
  153. package/src/themes/default/modes/light/skins.js +1026 -0
  154. package/src/themes/default/modes/light/theme.js +74 -0
  155. package/src/themes/default/tokens/controls.js +53 -0
  156. package/src/themes/default/tokens/shadows.js +63 -0
  157. package/src/themes/default/tokens/shapes.js +37 -0
  158. package/src/themes/default/tokens/space.js +143 -0
  159. package/src/themes/default/tokens/spectre.js +16 -0
  160. package/src/themes/default/utils.js +523 -0
  161. package/src/themes/index.ts +11 -0
  162. package/src/types.ts +394 -0
  163. package/src/utils/overlayTheme.ts +61 -0
  164. package/src/utils/pickColor.ts +15 -0
  165. package/tsconfig.json +24 -0
@@ -0,0 +1,657 @@
1
+ "use client";
2
+ import { Box, Text } from "@/design-system/elements";
3
+ import React, { useRef, useEffect, useState, useCallback } from "react";
4
+
5
+ /**
6
+ * List Component with Column Grouping
7
+ *
8
+ * A flexible data grid component that supports column grouping functionality.
9
+ * Columns can be grouped under common headers for better organization and visual hierarchy.
10
+ *
11
+ * ## Basic Usage
12
+ * ```tsx
13
+ * <List data={myData} grid={myColumns} />
14
+ * ```
15
+ *
16
+ * ## Column Grouping Methods
17
+ *
18
+ * ### Method 1: Direct Group Property (Simple)
19
+ * Add a `group` property to any GridColumn to group it under a header:
20
+ *
21
+ * ```tsx
22
+ * const groupedColumns = [
23
+ * {
24
+ * label: "Name",
25
+ * width: "200px",
26
+ * key: "name",
27
+ * component: (props) => <Text>{props.name}</Text>
28
+ * // No group - this will be standalone
29
+ * },
30
+ * {
31
+ * label: "Street",
32
+ * width: "150px",
33
+ * key: "street",
34
+ * group: "Address", // Grouped under "Address"
35
+ * component: (props) => <Text>{props.street}</Text>
36
+ * },
37
+ * {
38
+ * label: "City",
39
+ * width: "100px",
40
+ * key: "city",
41
+ * group: "Address", // Also grouped under "Address"
42
+ * component: (props) => <Text>{props.city}</Text>
43
+ * }
44
+ * ];
45
+ * ```
46
+ *
47
+ * ### Method 2: createColumnGroup Helper (Recommended)
48
+ * Use `createColumnGroup()` to create grouped columns directly in your grid array:
49
+ *
50
+ * ```tsx
51
+ * const addressGroup = createColumnGroup("Address Info", [
52
+ * { label: "Street", width: "150px", key: "street", component: StreetComponent },
53
+ * { label: "City", width: "100px", key: "city", component: CityComponent }
54
+ * ]);
55
+ *
56
+ * const contactGroup = createColumnGroup("Contact Info", [
57
+ * { label: "Phone", width: "120px", key: "phone", component: PhoneComponent },
58
+ * { label: "Email", width: "180px", key: "email", component: EmailComponent }
59
+ * ]);
60
+ *
61
+ * // Use directly in grid array
62
+ * const grid = [
63
+ * { label: "Name", width: "200px", key: "name", component: NameComponent },
64
+ * addressGroup, // Direct usage!
65
+ * contactGroup // Direct usage!
66
+ * ];
67
+ *
68
+ * <List data={myData} grid={grid} />
69
+ * ```
70
+ *
71
+ * ### Rendering Result:
72
+ * ```
73
+ * Name | Address Info | Contact Info
74
+ * | Street | City | Phone | Email
75
+ * --------|--------|------|-------|-------
76
+ * John | 123 | NY | 555-1 | j@...
77
+ * Jane | 456 | LA | 555-2 | jane@...
78
+ * ```
79
+ *
80
+ * ## Advanced Usage
81
+ *
82
+ * ### Custom Group Styling
83
+ * ```tsx
84
+ * const styledGroup = createColumnGroup(
85
+ * "Financial Data",
86
+ * [...columns],
87
+ * { bg: "palette.blues.1", color: "palette.blues.8" } // Custom group header props
88
+ * );
89
+ * ```
90
+ *
91
+ * ### Mixed Arrays
92
+ * You can mix standalone columns with grouped columns:
93
+ * ```tsx
94
+ * const grid = [
95
+ * standaloneColumn,
96
+ * groupedColumns1,
97
+ * anotherStandaloneColumn,
98
+ * groupedColumns2
99
+ * ];
100
+ * ```
101
+ */
102
+
103
+ function debounce<T extends (...args: any[]) => any>(
104
+ fn: T,
105
+ ms: number
106
+ ): (...args: Parameters<T>) => void {
107
+ let timer: NodeJS.Timeout;
108
+ return (...args: Parameters<T>) => {
109
+ clearTimeout(timer);
110
+ timer = setTimeout(() => {
111
+ fn(...args);
112
+ }, ms);
113
+ };
114
+ }
115
+
116
+ export interface DataItem {
117
+ [key: string]: any;
118
+ }
119
+
120
+ export interface ListProps {
121
+ data: DataItem[];
122
+ grid?: (GridColumn | GroupedColumn)[];
123
+ gap?: string;
124
+ selected?: string[];
125
+ onSelect?: (id: string) => void;
126
+ /**
127
+ * Called when a row is clicked. Receives the full item.
128
+ */
129
+ onRowClick?: (item: DataItem) => void;
130
+ /**
131
+ * Fixed height for each row. Default is "2rem".
132
+ */
133
+ rowHeight?: string;
134
+ [key: string]: any;
135
+ }
136
+
137
+ export interface GridColumn {
138
+ /** Column header label - can be text or React component */
139
+ label: string | React.ReactNode;
140
+ /** Column width (CSS width value) */
141
+ width: string;
142
+ /** Data key(s) to extract from row data */
143
+ key: string | string[];
144
+ /** Column alignment */
145
+ align?: any;
146
+ /** Minimum column width */
147
+ minWidth?: string;
148
+ /** Maximum column width */
149
+ maxWidth?: string;
150
+ /** Enable auto-width calculation based on content */
151
+ autoWidth?: boolean;
152
+ /** Component to render cell content */
153
+ component: React.ComponentType<any>;
154
+ /** Props passed to column header */
155
+ columnProps?: any;
156
+ /** Props passed to individual cells */
157
+ cellProps?: any;
158
+ /**
159
+ * Group identifier - columns with same group value will be grouped together.
160
+ * If undefined, column will not be grouped.
161
+ *
162
+ * @example
163
+ * ```tsx
164
+ * { group: "Personal Info" } // Groups under "Personal Info" header
165
+ * ```
166
+ */
167
+ group?: string;
168
+ [key: string]: any;
169
+ }
170
+
171
+ /**
172
+ * Represents a group of columns under a common header
173
+ */
174
+ export interface GroupedColumn {
175
+ /** Header text/component displayed above the grouped columns */
176
+ groupHeader: string | React.ReactNode;
177
+ /** Array of columns belonging to this group */
178
+ columns: GridColumn[];
179
+ /** Props passed to the group header element */
180
+ groupProps?: any;
181
+ }
182
+
183
+ const DEFAULT_GRID_SETUP: GridColumn[] = [
184
+ {
185
+ label: "id",
186
+ width: "4rem",
187
+ key: "id",
188
+ align: "center",
189
+ component: (props: DataItem) => <Text fontWeight={600}>{props.id}</Text>,
190
+ },
191
+ {
192
+ label: "name",
193
+ width: "1fr",
194
+ key: "name",
195
+ align: "start",
196
+ component: (props: DataItem) => <Text fontWeight={600}>{props.name}</Text>,
197
+ },
198
+ {
199
+ label: "",
200
+ width: "1fr",
201
+ key: "empty",
202
+ align: "start",
203
+ component: () => <></>,
204
+ },
205
+ ];
206
+
207
+ /**
208
+ * Utility function to create a grouped column structure
209
+ *
210
+ * @param groupHeader - The header text/component for the group
211
+ * @param columns - Array of columns to include in this group
212
+ * @param groupProps - Optional props for the group header styling
213
+ * @returns GroupedColumn object
214
+ *
215
+ * @example
216
+ * ```tsx
217
+ * const contactGroup = createColumnGroup(
218
+ * "Contact Information",
219
+ * [
220
+ * { label: "Email", width: "200px", key: "email", component: EmailCell },
221
+ * { label: "Phone", width: "150px", key: "phone", component: PhoneCell }
222
+ * ],
223
+ * { bg: "palette.blues.1" } // Custom group header styling
224
+ * );
225
+ * ```
226
+ */
227
+ export const createColumnGroup = (
228
+ groupHeader: string | React.ReactNode,
229
+ columns: GridColumn[],
230
+ groupProps?: any
231
+ ): GroupedColumn => ({
232
+ groupHeader,
233
+ columns,
234
+ groupProps,
235
+ });
236
+
237
+ /**
238
+ * Internal utility function to process mixed grid input.
239
+ * Handles both flat arrays of columns with group properties and
240
+ * arrays containing GroupedColumn objects.
241
+ *
242
+ * @param grid - Array of GridColumn and/or GroupedColumn objects
243
+ * @returns Array containing both individual columns and GroupedColumn objects
244
+ */
245
+ const processGridInput = (
246
+ grid: (GridColumn | GroupedColumn)[]
247
+ ): (GridColumn | GroupedColumn)[] => {
248
+ const result: (GridColumn | GroupedColumn)[] = [];
249
+
250
+ grid.forEach((item) => {
251
+ if ("columns" in item) {
252
+ // It's already a GroupedColumn, add it directly
253
+ result.push(item);
254
+ } else {
255
+ // It's a GridColumn, check if it has a group property
256
+ if (item.group) {
257
+ // Find if we already have a group with this name
258
+ const existingGroupIndex = result.findIndex(
259
+ (r) => "columns" in r && r.groupHeader === item.group
260
+ );
261
+
262
+ if (existingGroupIndex >= 0) {
263
+ // Add to existing group
264
+ (result[existingGroupIndex] as GroupedColumn).columns.push(item);
265
+ } else {
266
+ // Create new group
267
+ result.push({
268
+ groupHeader: item.group,
269
+ columns: [item],
270
+ });
271
+ }
272
+ } else {
273
+ // No group, add as standalone column
274
+ result.push(item);
275
+ }
276
+ }
277
+ });
278
+
279
+ return result;
280
+ };
281
+
282
+ /**
283
+ * Internal utility function to flatten grouped columns back to regular columns.
284
+ * Used for data processing where the flat structure is needed.
285
+ *
286
+ * @param groupedColumns - Mixed array of columns and groups
287
+ * @returns Flat array of GridColumn objects
288
+ */
289
+ const flattenColumns = (
290
+ groupedColumns: (GridColumn | GroupedColumn)[]
291
+ ): GridColumn[] => {
292
+ const flattened: GridColumn[] = [];
293
+
294
+ groupedColumns.forEach((item) => {
295
+ if ("columns" in item) {
296
+ flattened.push(...item.columns);
297
+ } else {
298
+ flattened.push(item);
299
+ }
300
+ });
301
+
302
+ return flattened;
303
+ };
304
+
305
+ // Utility function to filter data items based on grid keys
306
+ const filterDataByGridKeys = (data: DataItem[], grid: GridColumn[]) => {
307
+ const gridKeys = grid.map((col) => col.key).flat();
308
+ return data.map((item) => {
309
+ const filteredItem: Partial<DataItem> = {};
310
+ gridKeys.forEach((key) => {
311
+ if (typeof key === "string" && key in item) {
312
+ filteredItem[key] = item[key as keyof DataItem];
313
+ }
314
+ });
315
+ return filteredItem as DataItem;
316
+ });
317
+ };
318
+
319
+ // Helper function to get value from item using key or array of keys
320
+ const getItemValue = (item: DataItem, key: string | string[]) => {
321
+ if (typeof key === "string") {
322
+ return item[key];
323
+ }
324
+ // If key is an array, return an object with all specified keys
325
+ return key.reduce(
326
+ (acc, k) => ({
327
+ ...acc,
328
+ [k]: item[k],
329
+ }),
330
+ {}
331
+ );
332
+ };
333
+
334
+ const List: React.FC<ListProps> = ({
335
+ data = [],
336
+ grid = DEFAULT_GRID_SETUP,
337
+ selected = [],
338
+ gap = "xxxsmall",
339
+ rowHeight = "2rem",
340
+ onSelect,
341
+ onRowClick,
342
+ ...rest
343
+ }) => {
344
+ // Process grid input and flatten for processing
345
+ const groupedColumns = processGridInput(grid);
346
+ const flattenedGrid = flattenColumns(groupedColumns);
347
+
348
+ // Create a ref for each column that will hold calculated widths
349
+ const [columnWidths, setColumnWidths] = useState<string[]>([]);
350
+ const columnRefs = useRef<(HTMLDivElement | null)[]>([]);
351
+ const headerRefs = useRef<(HTMLDivElement | null)[]>([]);
352
+ const hasAutoWidthColumns = flattenedGrid.some((col) => col.autoWidth);
353
+ const [measuredOnce, setMeasuredOnce] = useState(false);
354
+
355
+ // Create initial grid template based on fixed widths
356
+ const initialGridTemplate = flattenedGrid
357
+ .map((col: GridColumn) => (col.autoWidth ? "auto" : col.width))
358
+ .join(" ");
359
+
360
+ // Filter the data based on the grid keys
361
+ const filteredData = filterDataByGridKeys(data, flattenedGrid);
362
+
363
+ // Memoize grid structure to prevent unnecessary recalculations
364
+ const gridStructureKey = JSON.stringify(
365
+ flattenedGrid.map((col) => ({
366
+ width: col.width,
367
+ autoWidth: col.autoWidth,
368
+ }))
369
+ );
370
+
371
+ const measureColumnWidths = useCallback(() => {
372
+ if (filteredData.length === 0 || !hasAutoWidthColumns) return;
373
+
374
+ // Initialize or resize the refs array to match grid length
375
+ columnRefs.current = columnRefs.current.slice(0, flattenedGrid.length);
376
+ headerRefs.current = headerRefs.current.slice(0, flattenedGrid.length);
377
+
378
+ const widths = flattenedGrid.map((col, index) => {
379
+ if (!col.autoWidth) return col.width;
380
+
381
+ // Get width of the column content and header
382
+ const columnElements = document.querySelectorAll(`.column-${index}`);
383
+ let maxWidth = 0;
384
+
385
+ // Include header width in calculation
386
+ const headerEl = headerRefs.current[index];
387
+ if (headerEl) {
388
+ const headerWidth = headerEl.getBoundingClientRect().width;
389
+ maxWidth = Math.max(maxWidth, headerWidth);
390
+ }
391
+
392
+ // Find the max width among all cells in this column
393
+ // Only measure a reasonable number of rows for performance
394
+ const maxRowsToMeasure = Math.min(columnElements.length, 20);
395
+ for (let i = 0; i < maxRowsToMeasure; i++) {
396
+ const el = columnElements[i];
397
+ const width = el.getBoundingClientRect().width;
398
+ maxWidth = Math.max(maxWidth, width);
399
+ }
400
+
401
+ // Add padding + buffer to prevent layout shifts
402
+ return `${maxWidth + 24}px`;
403
+ });
404
+
405
+ // Only update if there's a significant change
406
+ const hasSignificantChange = widths.some((width, i) => {
407
+ const current = columnWidths[i];
408
+ if (!current || current === "auto") return true;
409
+
410
+ // Extract numeric values for comparison
411
+ const currentVal = parseFloat(current);
412
+ const newVal = parseFloat(width);
413
+ return Math.abs(currentVal - newVal) > 5; // 5px threshold
414
+ });
415
+
416
+ if (hasSignificantChange || !measuredOnce) {
417
+ setColumnWidths(widths);
418
+ setMeasuredOnce(true);
419
+ }
420
+ }, [
421
+ filteredData.length,
422
+ flattenedGrid,
423
+ hasAutoWidthColumns,
424
+ columnWidths,
425
+ measuredOnce,
426
+ ]);
427
+ // Use a layout effect to measure sizes after DOM update but before paint
428
+ useEffect(() => {
429
+ if (!hasAutoWidthColumns) return;
430
+
431
+ // Only measure if we haven't measured yet or if grid structure changed
432
+ if (!measuredOnce || columnWidths.length !== flattenedGrid.length) {
433
+ // Use requestAnimationFrame to ensure DOM is ready
434
+ const timeoutId = setTimeout(() => {
435
+ measureColumnWidths();
436
+ }, 0);
437
+
438
+ return () => clearTimeout(timeoutId);
439
+ }
440
+ }, [
441
+ gridStructureKey,
442
+ measureColumnWidths,
443
+ measuredOnce,
444
+ hasAutoWidthColumns,
445
+ flattenedGrid.length,
446
+ columnWidths.length,
447
+ ]);
448
+
449
+ // Remeasure when window resizes
450
+ useEffect(() => {
451
+ if (!hasAutoWidthColumns) return;
452
+
453
+ // Use a debounced resize handler that only triggers on width changes
454
+ let lastWidth = window.innerWidth;
455
+ const handleResize = () => {
456
+ // Only remeasure on horizontal resizes
457
+ if (window.innerWidth !== lastWidth) {
458
+ lastWidth = window.innerWidth;
459
+ measureColumnWidths();
460
+ }
461
+ };
462
+
463
+ const debouncedResize = debounce(handleResize, 250);
464
+ window.addEventListener("resize", debouncedResize);
465
+
466
+ return () => {
467
+ window.removeEventListener("resize", debouncedResize);
468
+ };
469
+ }, [hasAutoWidthColumns, measureColumnWidths]);
470
+
471
+ // Determine which grid template to use (calculated or initial)
472
+ const gridTemplateColumns =
473
+ columnWidths.length === flattenedGrid.length
474
+ ? columnWidths.join(" ")
475
+ : initialGridTemplate;
476
+
477
+ return (
478
+ <Box
479
+ width="100%"
480
+ overflow="hidden"
481
+ shape="rounded"
482
+ border="1px solid"
483
+ skin="surface"
484
+ >
485
+ {/* Group Headers */}
486
+ {groupedColumns.some((item) => "columns" in item) && (
487
+ <Box
488
+ display={"grid"}
489
+ gridTemplateColumns={gridTemplateColumns}
490
+ pb={gap}
491
+ border="none"
492
+ gap={gap}
493
+ backgroundColor="palette.neutrals.0"
494
+ borderTopLeftRadius="rounded"
495
+ borderTopRightRadius="rounded"
496
+ width="100%"
497
+ {...rest}
498
+ >
499
+ {(() => {
500
+ let colIndex = 0;
501
+ return groupedColumns.map((item, groupIndex) => {
502
+ if ("columns" in item) {
503
+ const groupSpan = item.columns.length;
504
+ const groupHeader = (
505
+ <Text
506
+ key={`group-${groupIndex}`}
507
+ as="div"
508
+ skin="row.group"
509
+ shape="roundedSmall"
510
+ fontSize="small"
511
+ fontWeight={600}
512
+ textAlign="center"
513
+ display={"flex"}
514
+ alignItems="center"
515
+ justifyContent="center"
516
+ width="100%"
517
+ height="2.5rem"
518
+ backgroundColor="palette.neutrals.1"
519
+ borderBottom="1px solid"
520
+ borderColor="border"
521
+ padding="xxsmall"
522
+ gridColumn={`${colIndex + 1} / span ${groupSpan}`}
523
+ {...item.columns[0].groupProps}
524
+ >
525
+ {item.groupHeader}
526
+ </Text>
527
+ );
528
+ colIndex += groupSpan;
529
+ return groupHeader;
530
+ } else {
531
+ colIndex += 1;
532
+ return <Box key={`empty-${groupIndex}`} />;
533
+ }
534
+ });
535
+ })()}
536
+ </Box>
537
+ )}
538
+
539
+ {/* Column Headers */}
540
+ <Box
541
+ display={"grid"}
542
+ gridTemplateColumns={gridTemplateColumns}
543
+ border="none"
544
+ //gap={gap}
545
+ borderBottom="1px solid"
546
+ borderColor="neutral"
547
+ skin="base"
548
+ p="xxxs"
549
+ >
550
+ {flattenedGrid.map((column: GridColumn, i: number) => {
551
+ const align = column?.align || "start";
552
+ return (
553
+ <Text
554
+ as={React.isValidElement(column.label) ? "div" : "span"}
555
+ key={i}
556
+ ref={(el: any) => (headerRefs.current[i] = el)}
557
+ // shape="roundedSmall"
558
+ fontSize="xs"
559
+ fontWeight={600}
560
+ textAlign={align}
561
+ width="100%"
562
+ justifyContent={align}
563
+ px="xxxs"
564
+ color="primary"
565
+ {...column.columnProps}
566
+ lineClamp={1}
567
+ >
568
+ {React.isValidElement(column.label) ? column.label : column.label}
569
+ </Text>
570
+ );
571
+ })}
572
+ </Box>
573
+ <Box display="grid" overflow="hidden" width="100%">
574
+ {filteredData?.map((item, rowIndex) => {
575
+ const isSelected = selected.includes(item.id);
576
+ return (
577
+ <Text
578
+ key={rowIndex}
579
+ as="div"
580
+ display="grid"
581
+ gridTemplateColumns={gridTemplateColumns}
582
+ alignItems="center"
583
+ // py="xxxsmall"
584
+ fontSize={"small"}
585
+ borderBottom={
586
+ rowIndex !== filteredData.length - 1 ? "1px solid" : "0"
587
+ }
588
+ height={rowHeight}
589
+ maxHeight={rowHeight}
590
+ // skin={rowIndex % 2 === 1 ? "row.alt" : "row"}
591
+ borderColor="neutral"
592
+ onClick={() => {
593
+ if (onSelect) onSelect(item.id);
594
+ if (onRowClick) onRowClick(item);
595
+ }}
596
+ cursor={onSelect || onRowClick ? "pointer" : "default"}
597
+ // gap={gap}
598
+ >
599
+ {flattenedGrid.map((column, colIndex) => {
600
+ const Component = column.component;
601
+ const align = column?.align || "start";
602
+ const itemValue = getItemValue(item, column.key);
603
+ return (
604
+ <Text
605
+ as="div"
606
+ key={colIndex}
607
+ className={`column-${colIndex}`}
608
+ ref={
609
+ rowIndex === 0
610
+ ? (el: any) => (columnRefs.current[colIndex] = el)
611
+ : null
612
+ }
613
+ display="flex"
614
+ alignItems="center"
615
+ justifyContent={align}
616
+ width="100%"
617
+ height={"100%"}
618
+ // maxHeight={rowHeight}
619
+ // whiteSpace="nowrap"
620
+ overflow="hidden"
621
+ textOverflow="ellipsis"
622
+ borderRight={
623
+ colIndex !== flattenedGrid.length - 1 ? "1px solid" : "0"
624
+ }
625
+ //skin={rowIndex % 2 === 1 ? "row.alt" : "row"}
626
+ borderColor="neutral"
627
+ // px={colIndex === 0 ? "small" : "xxsmall"}
628
+ minWidth={column.minWidth || "initial"}
629
+ maxWidth={column.maxWidth || "initial"}
630
+ {...column.cellProps}
631
+ >
632
+ {isSelected && <>✅</>}
633
+ <Box
634
+ width="100%"
635
+ overflow="hidden"
636
+ display="flex"
637
+ alignItems="center"
638
+ justifyContent={align}
639
+ maxHeight="100%"
640
+ style={{
641
+ textOverflow: "ellipsis",
642
+ }}
643
+ >
644
+ <Component {...item} value={itemValue} />
645
+ </Box>
646
+ </Text>
647
+ );
648
+ })}
649
+ </Text>
650
+ );
651
+ })}
652
+ </Box>
653
+ </Box>
654
+ );
655
+ };
656
+
657
+ export default List;
@@ -0,0 +1,17 @@
1
+ "use client";
2
+ import Box, { BoxProps } from "@/design-system/elements/box";
3
+ import { ThemeContext } from "styled-components";
4
+ import React, { useContext } from "react";
5
+
6
+ export default function Main({
7
+ children,
8
+ ...props
9
+ }: { children: React.ReactNode; props?: any } & BoxProps) {
10
+ const theme: any = useContext(ThemeContext);
11
+
12
+ return (
13
+ <Box {...theme?.main} {...props}>
14
+ {children}
15
+ </Box>
16
+ );
17
+ }