@neasg/design-system 0.4.10 → 0.4.12

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 (45) hide show
  1. package/README.md +2 -2
  2. package/dist/avatar-profile-popover.d.ts +34 -0
  3. package/dist/avatar-profile-popover.js +27 -0
  4. package/dist/badge.d.ts +1 -1
  5. package/dist/button.d.ts +1 -1
  6. package/dist/card.d.ts +2 -0
  7. package/dist/card.js +72 -3
  8. package/dist/command-search.d.ts +47 -2
  9. package/dist/command-search.js +173 -24
  10. package/dist/command.d.ts +2 -0
  11. package/dist/command.js +2 -2
  12. package/dist/date-input.d.ts +2 -0
  13. package/dist/date-input.js +44 -4
  14. package/dist/dialog-primitive.js +1 -1
  15. package/dist/draggable-tabs.d.ts +3 -0
  16. package/dist/draggable-tabs.js +4 -0
  17. package/dist/editable-table.js +2 -2
  18. package/dist/filter-popover.d.ts +2 -1
  19. package/dist/filter-popover.js +2 -2
  20. package/dist/guidance-tip.d.ts +26 -0
  21. package/dist/guidance-tip.js +162 -0
  22. package/dist/index.d.ts +13 -5
  23. package/dist/index.js +5 -1
  24. package/dist/jump-target-highlight.d.ts +13 -0
  25. package/dist/jump-target-highlight.js +95 -0
  26. package/dist/layout.d.ts +27 -5
  27. package/dist/layout.js +128 -34
  28. package/dist/message-item.js +9 -10
  29. package/dist/notification.js +1 -1
  30. package/dist/page-layout.d.ts +22 -0
  31. package/dist/page-layout.js +38 -0
  32. package/dist/page-section.js +1 -1
  33. package/dist/popover-menu.js +2 -2
  34. package/dist/routing-timeline.js +1 -1
  35. package/dist/table-column-visibility.d.ts +3 -1
  36. package/dist/table-column-visibility.js +74 -27
  37. package/dist/table-toolbar.d.ts +2 -1
  38. package/dist/table-toolbar.js +7 -2
  39. package/dist/table.d.ts +32 -3
  40. package/dist/table.js +318 -29
  41. package/dist/tabs.d.ts +2 -1
  42. package/dist/tabs.js +8 -5
  43. package/dist/theme-switcher.d.ts +1 -1
  44. package/dist/theme-switcher.js +4 -6
  45. package/package.json +2 -2
package/dist/table.js CHANGED
@@ -11,8 +11,11 @@ const TableRoot = React.forwardRef(({ className, wrapperClassName: _wrapperClass
11
11
  TableRoot.displayName = "TableRoot";
12
12
  const TableContainer = React.forwardRef(({ className, ...props }, ref) => (_jsx("div", { ref: ref, className: cn("min-h-0 overflow-auto rounded-lg border border-[hsl(var(--table-border))] bg-card text-card-foreground", className), ...props })));
13
13
  TableContainer.displayName = "TableContainer";
14
- function getTableColumnSizingStyle({ style, width, minWidth, maxWidth, }) {
15
- const resolvedMinWidth = minWidth !== null && minWidth !== void 0 ? minWidth : width;
14
+ const DEFAULT_COLUMN_RESIZE_MIN_WIDTH = 64;
15
+ const DEFAULT_COLUMN_RESIZE_START_WIDTH = 160;
16
+ const COLUMN_RESIZE_KEYBOARD_STEP = 16;
17
+ function getTableColumnSizingStyle({ style, width, minWidth, maxWidth, treatWidthAsMinWidth = true, }) {
18
+ const resolvedMinWidth = minWidth !== null && minWidth !== void 0 ? minWidth : (treatWidthAsMinWidth ? width : undefined);
16
19
  return {
17
20
  ...style,
18
21
  ...(width ? { width } : {}),
@@ -20,6 +23,94 @@ function getTableColumnSizingStyle({ style, width, minWidth, maxWidth, }) {
20
23
  ...(maxWidth ? { maxWidth } : {}),
21
24
  };
22
25
  }
26
+ function overflowContainmentClassName({ containOverflow, wrap, }) {
27
+ if (!containOverflow) {
28
+ return undefined;
29
+ }
30
+ return wrap
31
+ ? "break-words [overflow-wrap:anywhere]"
32
+ : "overflow-hidden text-ellipsis";
33
+ }
34
+ function parsePixelSize(value) {
35
+ if (typeof value === "number") {
36
+ return Number.isFinite(value) ? value : undefined;
37
+ }
38
+ if (!value) {
39
+ return undefined;
40
+ }
41
+ const match = /^(\d+(?:\.\d+)?)px$/.exec(value.trim());
42
+ return match ? Number(match[1]) : undefined;
43
+ }
44
+ function clampColumnWidth(column, width) {
45
+ var _a;
46
+ const minWidth = (_a = parsePixelSize(column.minWidth)) !== null && _a !== void 0 ? _a : DEFAULT_COLUMN_RESIZE_MIN_WIDTH;
47
+ const maxWidth = parsePixelSize(column.maxWidth);
48
+ const roundedWidth = Math.round(width);
49
+ const clampedWidth = Math.max(minWidth, roundedWidth);
50
+ return maxWidth !== undefined
51
+ ? Math.min(clampedWidth, maxWidth)
52
+ : clampedWidth;
53
+ }
54
+ function getColumnResizeMinWidth(column) {
55
+ var _a, _b;
56
+ return ((_b = (_a = parsePixelSize(column.resizeMinWidth)) !== null && _a !== void 0 ? _a : parsePixelSize(column.minWidth)) !== null && _b !== void 0 ? _b : DEFAULT_COLUMN_RESIZE_MIN_WIDTH);
57
+ }
58
+ function getColumnResizeMaxWidth(column) {
59
+ var _a;
60
+ return (_a = parsePixelSize(column.resizeMaxWidth)) !== null && _a !== void 0 ? _a : Number.POSITIVE_INFINITY;
61
+ }
62
+ function getColumnResizeMaxWidthStyle(column) {
63
+ const maxWidth = getColumnResizeMaxWidth(column);
64
+ return Number.isFinite(maxWidth) ? `${maxWidth}px` : undefined;
65
+ }
66
+ function clampColumnResizeWidth(column, width) {
67
+ const minWidth = getColumnResizeMinWidth(column);
68
+ const maxWidth = getColumnResizeMaxWidth(column);
69
+ const roundedWidth = Math.round(width);
70
+ const clampedWidth = Math.max(minWidth, roundedWidth);
71
+ return Number.isFinite(maxWidth)
72
+ ? Math.min(clampedWidth, maxWidth)
73
+ : clampedWidth;
74
+ }
75
+ function clampNumber(value, min, max) {
76
+ return Math.min(Math.max(value, min), max);
77
+ }
78
+ function getResolvedColumnWidth(column, columnWidths) {
79
+ const resizedWidth = columnWidths[column.key];
80
+ if (resizedWidth === undefined) {
81
+ return column.width;
82
+ }
83
+ return `${clampColumnWidth(column, resizedWidth)}px`;
84
+ }
85
+ function getResolvedResizableColumnWidth(column, columnWidths) {
86
+ return clampColumnResizeWidth(column, getColumnResizeBaseWidth(column, columnWidths));
87
+ }
88
+ function getRenderedColumnWidth({ column, columnWidths, resizing, }) {
89
+ if (resizing) {
90
+ return `${getResolvedResizableColumnWidth(column, columnWidths)}px`;
91
+ }
92
+ return getResolvedColumnWidth(column, columnWidths);
93
+ }
94
+ function getRenderedColumnMinWidth(column, resizing) {
95
+ return resizing ? `${getColumnResizeMinWidth(column)}px` : column.minWidth;
96
+ }
97
+ function getRenderedColumnMaxWidth(column, resizing) {
98
+ return resizing ? getColumnResizeMaxWidthStyle(column) : column.maxWidth;
99
+ }
100
+ function getColumnResizeBaseWidth(column, columnWidths, elementWidth) {
101
+ var _a, _b, _c, _d;
102
+ return ((_d = (_c = (_b = (_a = columnWidths[column.key]) !== null && _a !== void 0 ? _a : parsePixelSize(column.width)) !== null && _b !== void 0 ? _b : (elementWidth && elementWidth > 0 ? elementWidth : undefined)) !== null && _c !== void 0 ? _c : parsePixelSize(column.minWidth)) !== null && _d !== void 0 ? _d : DEFAULT_COLUMN_RESIZE_START_WIDTH);
103
+ }
104
+ function getColumnResizeLabel(column) {
105
+ if (column.visibilityLabel) {
106
+ return column.visibilityLabel;
107
+ }
108
+ if (typeof column.header === "string" ||
109
+ typeof column.header === "number") {
110
+ return String(column.header);
111
+ }
112
+ return column.key;
113
+ }
23
114
  const TableHeader = React.forwardRef(({ className, ...props }, ref) => (_jsx("thead", { ref: ref, className: cn("sticky top-0 z-10 bg-[hsl(var(--table-header-background))] [&_tr]:border-b [&_tr]:border-[hsl(var(--table-border))] [&>tr]:bg-inherit", className), ...props })));
24
115
  TableHeader.displayName = "TableHeader";
25
116
  const TableBody = React.forwardRef(({ className, ...props }, ref) => (_jsx("tbody", { ref: ref, className: cn("[&_tr:last-child]:border-0", className), ...props })));
@@ -28,7 +119,7 @@ const TableFooter = React.forwardRef(({ className, ...props }, ref) => (_jsx("tf
28
119
  TableFooter.displayName = "TableFooter";
29
120
  const TableRow = React.forwardRef(({ className, ...props }, ref) => (_jsx("tr", { ref: ref, className: cn("border-b border-[hsl(var(--table-border))] bg-card text-card-foreground transition-colors data-[state=selected]:bg-muted", className), ...props })));
30
121
  TableRow.displayName = "TableRow";
31
- const TableHead = React.forwardRef(({ className, style, width, minWidth, maxWidth, wrap = false, ...props }, ref) => (_jsx("th", { ref: ref, className: cn("h-11 px-4 py-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0", wrap ? "whitespace-normal" : "whitespace-nowrap", className), style: getTableColumnSizingStyle({
122
+ const TableHead = React.forwardRef(({ className, style, width, minWidth, maxWidth, wrap = false, treatWidthAsMinWidth, containOverflow, ...props }, ref) => (_jsx("th", { ref: ref, className: cn("h-11 px-4 py-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0", wrap ? "whitespace-normal" : "whitespace-nowrap", overflowContainmentClassName({ containOverflow, wrap }), className), style: getTableColumnSizingStyle({
32
123
  style: {
33
124
  ...style,
34
125
  fontWeight: 500,
@@ -36,24 +127,43 @@ const TableHead = React.forwardRef(({ className, style, width, minWidth, maxWidt
36
127
  width,
37
128
  minWidth,
38
129
  maxWidth,
130
+ treatWidthAsMinWidth,
39
131
  }), ...props })));
40
132
  TableHead.displayName = "TableHead";
41
- const TableCell = React.forwardRef(({ className, style, width, minWidth, maxWidth, wrap = false, ...props }, ref) => (_jsx("td", { ref: ref, className: cn("h-12 px-4 py-2 align-middle text-card-foreground [&:has([role=checkbox])]:pr-0", wrap ? "whitespace-normal" : "whitespace-nowrap", className), style: getTableColumnSizingStyle({ style, width, minWidth, maxWidth }), ...props })));
133
+ const TableCell = React.forwardRef(({ className, style, width, minWidth, maxWidth, wrap = false, treatWidthAsMinWidth, containOverflow, ...props }, ref) => (_jsx("td", { ref: ref, className: cn("h-12 px-4 py-2 align-middle text-card-foreground [&:has([role=checkbox])]:pr-0", wrap ? "whitespace-normal" : "whitespace-nowrap", overflowContainmentClassName({ containOverflow, wrap }), className), style: getTableColumnSizingStyle({
134
+ style,
135
+ width,
136
+ minWidth,
137
+ maxWidth,
138
+ treatWidthAsMinWidth,
139
+ }), ...props })));
42
140
  TableCell.displayName = "TableCell";
43
141
  const TableCaption = React.forwardRef(({ className, ...props }, ref) => (_jsx("caption", { ref: ref, className: cn("mt-4 text-sm text-muted-foreground", className), ...props })));
44
142
  TableCaption.displayName = "TableCaption";
45
- function stickyClassName(sticky, kind) {
143
+ const STICKY_SCROLL_EDGE_THRESHOLD = 1;
144
+ function stickyClassName(sticky, kind, showShadow = false) {
46
145
  if (!sticky)
47
146
  return undefined;
48
147
  const side = sticky === "left" ? "left-0" : "right-0";
49
- const shadow = sticky === "left"
50
- ? "shadow-[inset_-1px_0_0_0_var(--color-border)]"
51
- : "shadow-[inset_1px_0_0_0_var(--color-border)]";
148
+ const shadow = showShadow
149
+ ? sticky === "left"
150
+ ? "shadow-[inset_-1px_0_0_0_hsl(var(--table-border)),_8px_0_12px_-10px_hsl(var(--foreground)_/_0.35)]"
151
+ : "shadow-[inset_1px_0_0_0_hsl(var(--table-border)),_-8px_0_12px_-10px_hsl(var(--foreground)_/_0.35)]"
152
+ : undefined;
52
153
  const background = kind === "head" ? "bg-[hsl(var(--table-header-background))]" : "bg-card";
53
154
  // z-20 on header cells so they layer above sticky body cells on the other axis.
54
155
  const z = kind === "head" ? "z-20" : "z-10";
55
156
  return cn("sticky", background, side, z, shadow);
56
157
  }
158
+ function getStickyShadowVisibility(sticky, scrollState) {
159
+ if (sticky === "left") {
160
+ return scrollState.left;
161
+ }
162
+ if (sticky === "right") {
163
+ return scrollState.right;
164
+ }
165
+ return false;
166
+ }
57
167
  function alignClassName(align) {
58
168
  switch (align) {
59
169
  case "center":
@@ -107,12 +217,32 @@ export const TABLE_SKELETON_PRESETS = {
107
217
  })),
108
218
  }),
109
219
  };
110
- function SortableTableHead({ field, children, currentSortField, currentSortDirection, onSort, className, allowWrap = false, width, minWidth, maxWidth, align, sortLabel, }) {
220
+ function SortableTableHead({ field, children, currentSortField, currentSortDirection, onSort, className, allowWrap = false, width, minWidth, maxWidth, align, sortLabel, resizeHandle, treatWidthAsMinWidth, containOverflow, }) {
111
221
  const isSorted = currentSortField === field;
112
- return (_jsx(TableHead, { className: cn(alignClassName(align), className), wrap: allowWrap, width: width, minWidth: minWidth, maxWidth: maxWidth, children: _jsxs("button", { type: "button", onClick: () => onSort(field), className: cn("-ml-1.5 flex h-7 cursor-pointer items-center gap-1 rounded px-1.5 py-0 font-medium transition-colors hover:text-foreground", allowWrap
113
- ? "max-w-full whitespace-normal text-left"
114
- : "whitespace-nowrap", align === "right" && "ml-auto justify-end", align === "center" && "mx-auto justify-center"), style: { font: "inherit", color: "inherit", fontWeight: 500 }, "aria-label": `Sort by ${sortLabel !== null && sortLabel !== void 0 ? sortLabel : field}`, children: [children, _jsx("span", { className: "inline-flex h-3.5 w-3.5 shrink-0 items-center justify-center", children: isSorted ? (currentSortDirection === "asc" ? (_jsx(ArrowUpIcon, { className: "shrink-0", size: 14 })) : (_jsx(ArrowDownIcon, { className: "shrink-0", size: 14 }))) : (_jsx(ArrowUpDown, { className: "h-3.5 w-3.5 shrink-0 opacity-50" })) })] }) }));
222
+ return (_jsxs(TableHead, { className: cn(resizeHandle && "relative", alignClassName(align), className), wrap: allowWrap, width: width, minWidth: minWidth, maxWidth: maxWidth, treatWidthAsMinWidth: treatWidthAsMinWidth, containOverflow: containOverflow, children: [_jsxs("button", { type: "button", onClick: () => onSort(field), className: cn("-ml-1.5 flex h-7 cursor-pointer items-center gap-1 rounded px-1.5 py-0 font-medium transition-colors hover:text-foreground", containOverflow && "max-w-full overflow-hidden", allowWrap
223
+ ? "max-w-full whitespace-normal text-left"
224
+ : "whitespace-nowrap", align === "right" && "ml-auto justify-end", align === "center" && "mx-auto justify-center"), style: { font: "inherit", color: "inherit", fontWeight: 500 }, "aria-label": `Sort by ${sortLabel !== null && sortLabel !== void 0 ? sortLabel : field}`, children: [_jsx("span", { className: cn("min-w-0", containOverflow && !allowWrap && "truncate", containOverflow && allowWrap && "break-words [overflow-wrap:anywhere]"), children: children }), _jsx("span", { className: "inline-flex h-3.5 w-3.5 shrink-0 items-center justify-center", children: isSorted ? (currentSortDirection === "asc" ? (_jsx(ArrowUpIcon, { className: "shrink-0", size: 14 })) : (_jsx(ArrowDownIcon, { className: "shrink-0", size: 14 }))) : (_jsx(ArrowUpDown, { className: "h-3.5 w-3.5 shrink-0 opacity-50" })) })] }), resizeHandle] }));
115
225
  }
226
+ const TableColumnResizeHandle = React.forwardRef(function TableColumnResizeHandle({ column, nextColumn, currentWidth, minWidth, maxWidth, onResizeStart, onResizeByKeyboard, className, onClick, ...props }, ref) {
227
+ const label = getColumnResizeLabel(column);
228
+ return (_jsx("button", { ...props, ref: ref, type: "button", "aria-label": `Resize ${label} column`, title: `Resize ${label} column`, "aria-valuenow": currentWidth, "aria-valuemin": minWidth, "aria-valuemax": maxWidth, onPointerDown: (event) => onResizeStart(column, nextColumn, event), onClick: (event) => {
229
+ onClick === null || onClick === void 0 ? void 0 : onClick(event);
230
+ if (event.defaultPrevented) {
231
+ return;
232
+ }
233
+ event.preventDefault();
234
+ event.stopPropagation();
235
+ }, onKeyDown: (event) => {
236
+ if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") {
237
+ return;
238
+ }
239
+ event.preventDefault();
240
+ event.stopPropagation();
241
+ onResizeByKeyboard(column, nextColumn, event.key === "ArrowLeft"
242
+ ? -COLUMN_RESIZE_KEYBOARD_STEP
243
+ : COLUMN_RESIZE_KEYBOARD_STEP);
244
+ }, className: cn("group absolute right-0 top-0 z-[1] flex h-full w-4 cursor-col-resize touch-none items-center justify-center focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", className), children: _jsx("span", { "aria-hidden": "true", className: "h-6 w-px rounded-full bg-[hsl(var(--table-border))] transition-colors group-hover:bg-primary group-focus-visible:bg-primary" }) }));
245
+ });
116
246
  function getDefaultSkeleton(column) {
117
247
  var _a, _b;
118
248
  return {
@@ -130,28 +260,182 @@ function tableRowToneClassName(tone) {
130
260
  return undefined;
131
261
  }
132
262
  }
133
- function Table({ columns, rows, getRowKey, emptyState = (_jsx(EmptyState, { message: "No data available", description: "There are no rows to display in this table yet.", className: "border-0 bg-transparent px-0 py-6" })), toolbar, caption, className, containerClassName, tableClassName, rowClassName, rowTone, maxHeight, sorting, pagination, loading = false, loadingRows = 5, visibleColumnKeys, striped = false, onRowClick, style, ...props }) {
134
- // Use auto layout so the browser can grow a column to contain its (nowrap)
135
- // header. Caller can opt back into fixed layout via `tableClassName="table-fixed"`.
136
- const shouldUseFixedLayout = false;
137
- const visibleColumnSet = visibleColumnKeys
138
- ? new Set(visibleColumnKeys)
139
- : undefined;
140
- const visibleColumns = visibleColumnSet
141
- ? columns.filter((column) => visibleColumnSet.has(column.key))
142
- : columns;
143
- const container = (_jsx(TableContainer, { className: cn("w-full", loading && "pointer-events-none select-none", className, containerClassName), style: {
263
+ function getVisibleColumns(columns, visibleColumnKeys) {
264
+ if (!visibleColumnKeys) {
265
+ return columns;
266
+ }
267
+ const columnsByKey = new Map(columns.map((column) => [column.key, column]));
268
+ const seenColumnKeys = new Set();
269
+ const visibleColumns = [];
270
+ visibleColumnKeys.forEach((columnKey) => {
271
+ if (seenColumnKeys.has(columnKey)) {
272
+ return;
273
+ }
274
+ const column = columnsByKey.get(columnKey);
275
+ if (column) {
276
+ visibleColumns.push(column);
277
+ seenColumnKeys.add(columnKey);
278
+ }
279
+ });
280
+ return visibleColumns;
281
+ }
282
+ function isStickyResizeBoundary(column, nextColumn) {
283
+ return Boolean(column.sticky === "right" || nextColumn.sticky);
284
+ }
285
+ function Table({ columns, rows, getRowKey, emptyState = (_jsx(EmptyState, { message: "No data available", description: "There are no rows to display in this table yet.", className: "border-0 bg-transparent px-0 py-6" })), toolbar, caption, className, containerClassName, tableClassName, rowClassName, rowTone, maxHeight, sorting, pagination, loading = false, loadingRows = 5, visibleColumnKeys, resizableColumns = false, columnWidths, defaultColumnWidths, onColumnWidthsChange, renderColumnResizeHandle, striped = false, onRowClick, onScroll, style, ...props }) {
286
+ const visibleColumns = getVisibleColumns(columns, visibleColumnKeys);
287
+ const isColumnResizingEnabled = resizableColumns || visibleColumns.some((column) => column.resizable);
288
+ // Use auto layout by default so the browser can grow a column to contain its
289
+ // nowrap header. Resizable columns need fixed layout so neighbour columns do
290
+ // not redistribute width while a handle is dragged.
291
+ const shouldUseFixedLayout = isColumnResizingEnabled;
292
+ const [uncontrolledColumnWidths, setUncontrolledColumnWidths] = React.useState(() => ({ ...(defaultColumnWidths !== null && defaultColumnWidths !== void 0 ? defaultColumnWidths : {}) }));
293
+ const resolvedColumnWidths = columnWidths !== null && columnWidths !== void 0 ? columnWidths : uncontrolledColumnWidths;
294
+ const columnWidthsRef = React.useRef(resolvedColumnWidths);
295
+ const resizeCleanupRef = React.useRef(null);
296
+ const tableContainerRef = React.useRef(null);
297
+ const [stickyScrollState, setStickyScrollState] = React.useState({
298
+ left: false,
299
+ right: false,
300
+ });
301
+ const updateStickyScrollState = React.useCallback((element = tableContainerRef.current) => {
302
+ if (!element) {
303
+ return;
304
+ }
305
+ const maxScrollLeft = Math.max(0, element.scrollWidth - element.clientWidth);
306
+ const scrollLeft = Math.max(0, element.scrollLeft);
307
+ const nextScrollState = {
308
+ left: scrollLeft > STICKY_SCROLL_EDGE_THRESHOLD,
309
+ right: scrollLeft < maxScrollLeft - STICKY_SCROLL_EDGE_THRESHOLD,
310
+ };
311
+ setStickyScrollState((currentScrollState) => currentScrollState.left === nextScrollState.left &&
312
+ currentScrollState.right === nextScrollState.right
313
+ ? currentScrollState
314
+ : nextScrollState);
315
+ }, []);
316
+ React.useEffect(() => {
317
+ columnWidthsRef.current = resolvedColumnWidths;
318
+ }, [resolvedColumnWidths]);
319
+ React.useEffect(() => {
320
+ updateStickyScrollState();
321
+ });
322
+ React.useEffect(() => {
323
+ const element = tableContainerRef.current;
324
+ if (!element || typeof ResizeObserver === "undefined") {
325
+ return;
326
+ }
327
+ const observer = new ResizeObserver(() => updateStickyScrollState(element));
328
+ observer.observe(element);
329
+ return () => observer.disconnect();
330
+ }, [updateStickyScrollState]);
331
+ React.useEffect(() => () => {
332
+ var _a;
333
+ (_a = resizeCleanupRef.current) === null || _a === void 0 ? void 0 : _a.call(resizeCleanupRef);
334
+ }, []);
335
+ const commitColumnWidths = React.useCallback((updater) => {
336
+ const nextColumnWidths = updater(columnWidthsRef.current);
337
+ columnWidthsRef.current = nextColumnWidths;
338
+ if (!columnWidths) {
339
+ setUncontrolledColumnWidths(nextColumnWidths);
340
+ }
341
+ onColumnWidthsChange === null || onColumnWidthsChange === void 0 ? void 0 : onColumnWidthsChange(nextColumnWidths);
342
+ }, [columnWidths, onColumnWidthsChange]);
343
+ const resizeColumnPairByDelta = React.useCallback((column, nextColumn, delta, startColumnWidths = columnWidthsRef.current) => {
344
+ const currentWidth = getResolvedResizableColumnWidth(column, startColumnWidths);
345
+ const nextWidth = getResolvedResizableColumnWidth(nextColumn, startColumnWidths);
346
+ const minDelta = Math.max(getColumnResizeMinWidth(column) - currentWidth, nextWidth - getColumnResizeMaxWidth(nextColumn));
347
+ const maxDelta = Math.min(getColumnResizeMaxWidth(column) - currentWidth, nextWidth - getColumnResizeMinWidth(nextColumn));
348
+ const resolvedDelta = clampNumber(delta, minDelta, maxDelta);
349
+ commitColumnWidths((currentColumnWidths) => ({
350
+ ...currentColumnWidths,
351
+ [column.key]: Math.round(currentWidth + resolvedDelta),
352
+ [nextColumn.key]: Math.round(nextWidth - resolvedDelta),
353
+ }));
354
+ }, [commitColumnWidths]);
355
+ const resizeColumnByKeyboard = React.useCallback((column, nextColumn, delta) => {
356
+ resizeColumnPairByDelta(column, nextColumn, delta);
357
+ }, [resizeColumnPairByDelta]);
358
+ const startColumnResize = React.useCallback((column, nextColumn, event) => {
359
+ var _a;
360
+ event.preventDefault();
361
+ event.stopPropagation();
362
+ (_a = resizeCleanupRef.current) === null || _a === void 0 ? void 0 : _a.call(resizeCleanupRef);
363
+ const ownerDocument = event.currentTarget.ownerDocument;
364
+ const startX = event.clientX;
365
+ const startColumnWidths = columnWidthsRef.current;
366
+ const previousCursor = ownerDocument.documentElement.style.cursor;
367
+ ownerDocument.documentElement.style.cursor = "col-resize";
368
+ const handlePointerMove = (pointerEvent) => {
369
+ resizeColumnPairByDelta(column, nextColumn, pointerEvent.clientX - startX, startColumnWidths);
370
+ };
371
+ const handlePointerUp = () => {
372
+ var _a;
373
+ (_a = resizeCleanupRef.current) === null || _a === void 0 ? void 0 : _a.call(resizeCleanupRef);
374
+ };
375
+ const cleanup = () => {
376
+ ownerDocument.removeEventListener("pointermove", handlePointerMove);
377
+ ownerDocument.removeEventListener("pointerup", handlePointerUp);
378
+ ownerDocument.documentElement.style.cursor = previousCursor;
379
+ resizeCleanupRef.current = null;
380
+ };
381
+ resizeCleanupRef.current = cleanup;
382
+ ownerDocument.addEventListener("pointermove", handlePointerMove);
383
+ ownerDocument.addEventListener("pointerup", handlePointerUp);
384
+ }, [resizeColumnPairByDelta]);
385
+ const renderResizeHandle = React.useCallback((column, columnIndex) => {
386
+ var _a, _b;
387
+ const nextColumn = visibleColumns[columnIndex + 1];
388
+ const isResizable = (_a = column.resizable) !== null && _a !== void 0 ? _a : resizableColumns;
389
+ const isNextResizable = nextColumn && ((_b = nextColumn.resizable) !== null && _b !== void 0 ? _b : resizableColumns);
390
+ if (!isResizable || !nextColumn || !isNextResizable) {
391
+ return null;
392
+ }
393
+ const stickyResizeBoundary = isStickyResizeBoundary(column, nextColumn);
394
+ if (stickyResizeBoundary) {
395
+ return null;
396
+ }
397
+ const handle = (_jsx(TableColumnResizeHandle, { column: column, nextColumn: nextColumn, currentWidth: getResolvedResizableColumnWidth(column, resolvedColumnWidths), minWidth: getColumnResizeMinWidth(column), maxWidth: Number.isFinite(getColumnResizeMaxWidth(column))
398
+ ? getColumnResizeMaxWidth(column)
399
+ : undefined, onResizeStart: startColumnResize, onResizeByKeyboard: resizeColumnByKeyboard }));
400
+ if (renderColumnResizeHandle) {
401
+ return renderColumnResizeHandle({ column, nextColumn, handle });
402
+ }
403
+ return handle;
404
+ }, [
405
+ resizableColumns,
406
+ renderColumnResizeHandle,
407
+ resizeColumnByKeyboard,
408
+ resolvedColumnWidths,
409
+ startColumnResize,
410
+ visibleColumns,
411
+ ]);
412
+ const handleTableContainerScroll = React.useCallback((event) => {
413
+ onScroll === null || onScroll === void 0 ? void 0 : onScroll(event);
414
+ updateStickyScrollState(event.currentTarget);
415
+ }, [onScroll, updateStickyScrollState]);
416
+ const container = (_jsx(TableContainer, { ref: tableContainerRef, onScroll: handleTableContainerScroll, className: cn("w-full", loading && "pointer-events-none select-none", className, containerClassName), style: {
144
417
  ...style,
145
418
  ...(maxHeight !== undefined ? { maxHeight } : {}),
146
419
  }, ...props, children: _jsxs(TableRoot, { className: cn(shouldUseFixedLayout && "table-fixed", tableClassName), children: [caption ? _jsx(TableCaption, { children: caption }) : null, _jsx("colgroup", { children: visibleColumns.map((column) => (_jsx("col", { style: getTableColumnSizingStyle({
147
- width: column.width,
148
- minWidth: column.minWidth,
149
- maxWidth: column.maxWidth,
150
- }) }, column.key))) }), _jsx(TableHeader, { children: _jsx(TableRow, { children: visibleColumns.map((column) => {
420
+ width: getRenderedColumnWidth({
421
+ column,
422
+ columnWidths: resolvedColumnWidths,
423
+ resizing: isColumnResizingEnabled,
424
+ }),
425
+ minWidth: getRenderedColumnMinWidth(column, isColumnResizingEnabled),
426
+ maxWidth: getRenderedColumnMaxWidth(column, isColumnResizingEnabled),
427
+ treatWidthAsMinWidth: !isColumnResizingEnabled,
428
+ }) }, column.key))) }), _jsx(TableHeader, { children: _jsx(TableRow, { children: visibleColumns.map((column, columnIndex) => {
151
429
  var _a;
152
430
  const sortField = (_a = column.sortKey) !== null && _a !== void 0 ? _a : column.key;
153
431
  const isSortable = column.sortable && sorting;
154
- return (isSortable ? (_jsx(SortableTableHead, { field: sortField, currentSortField: sorting.field, currentSortDirection: sorting.direction, onSort: sorting.onSort, className: cn(stickyClassName(column.sticky, "head"), column.headerClassName), allowWrap: column.wrap, width: column.width, minWidth: column.minWidth, maxWidth: column.maxWidth, align: column.align, sortLabel: column.sortLabel, children: column.header }, column.key)) : (_jsx(TableHead, { width: column.width, minWidth: column.minWidth, maxWidth: column.maxWidth, wrap: column.wrap, className: cn(alignClassName(column.align), stickyClassName(column.sticky, "head"), column.headerClassName), children: column.header }, column.key)));
432
+ const columnWidth = getRenderedColumnWidth({
433
+ column,
434
+ columnWidths: resolvedColumnWidths,
435
+ resizing: isColumnResizingEnabled,
436
+ });
437
+ const resizeHandle = renderResizeHandle(column, columnIndex);
438
+ return (isSortable ? (_jsx(SortableTableHead, { field: sortField, currentSortField: sorting.field, currentSortDirection: sorting.direction, onSort: sorting.onSort, className: cn(stickyClassName(column.sticky, "head", getStickyShadowVisibility(column.sticky, stickyScrollState)), column.headerClassName), allowWrap: column.wrap, width: columnWidth, minWidth: getRenderedColumnMinWidth(column, isColumnResizingEnabled), maxWidth: getRenderedColumnMaxWidth(column, isColumnResizingEnabled), align: column.align, sortLabel: column.sortLabel, resizeHandle: resizeHandle, treatWidthAsMinWidth: !isColumnResizingEnabled, containOverflow: isColumnResizingEnabled, children: column.header }, column.key)) : (_jsxs(TableHead, { width: columnWidth, minWidth: getRenderedColumnMinWidth(column, isColumnResizingEnabled), maxWidth: getRenderedColumnMaxWidth(column, isColumnResizingEnabled), wrap: column.wrap, treatWidthAsMinWidth: !isColumnResizingEnabled, containOverflow: isColumnResizingEnabled, className: cn(resizeHandle && "relative", alignClassName(column.align), stickyClassName(column.sticky, "head", getStickyShadowVisibility(column.sticky, stickyScrollState)), column.headerClassName), children: [column.header, resizeHandle] }, column.key)));
155
439
  }) }) }), _jsx(TableBody, { children: loading ? (_jsx(TableRowSkeleton, { rows: loadingRows, columns: visibleColumns.map((column) => { var _a; return (_a = column.skeleton) !== null && _a !== void 0 ? _a : getDefaultSkeleton(column); }) })) : rows.length ? (rows.map((row, rowIndex) => {
156
440
  const resolvedTone = typeof rowTone === "function"
157
441
  ? rowTone(row, rowIndex)
@@ -164,10 +448,15 @@ function Table({ columns, rows, getRowKey, emptyState = (_jsx(EmptyState, { mess
164
448
  ? rowClassName(row, rowIndex)
165
449
  : rowClassName), children: visibleColumns.map((column) => {
166
450
  var _a, _b;
451
+ const columnWidth = getRenderedColumnWidth({
452
+ column,
453
+ columnWidths: resolvedColumnWidths,
454
+ resizing: isColumnResizingEnabled,
455
+ });
167
456
  const value = (_b = (_a = column.cell) === null || _a === void 0 ? void 0 : _a.call(column, row, rowIndex)) !== null && _b !== void 0 ? _b : (column.accessorKey !== undefined
168
457
  ? row[column.accessorKey]
169
458
  : null);
170
- return (_jsx(TableCell, { width: column.width, minWidth: column.minWidth, maxWidth: column.maxWidth, wrap: column.wrap, className: cn(alignClassName(column.align), stickyClassName(column.sticky, "cell"), column.cellClassName), children: value }, column.key));
459
+ return (_jsx(TableCell, { width: columnWidth, minWidth: getRenderedColumnMinWidth(column, isColumnResizingEnabled), maxWidth: getRenderedColumnMaxWidth(column, isColumnResizingEnabled), wrap: column.wrap, treatWidthAsMinWidth: !isColumnResizingEnabled, containOverflow: isColumnResizingEnabled, className: cn(alignClassName(column.align), stickyClassName(column.sticky, "cell", getStickyShadowVisibility(column.sticky, stickyScrollState)), column.cellClassName), children: value }, column.key));
171
460
  }) }, getRowKey(row, rowIndex)));
172
461
  })) : (_jsx(TableRow, { className: "cursor-default hover:bg-card", children: _jsx(TableCell, { colSpan: Math.max(visibleColumns.length, 1), className: "p-6", children: emptyState }) })) })] }) }));
173
462
  if (!pagination) {
package/dist/tabs.d.ts CHANGED
@@ -55,8 +55,9 @@ export interface TabWithDropdownProps {
55
55
  onSectionSelect?: (sectionId: string) => void;
56
56
  activeSection?: string;
57
57
  className?: string;
58
+ contentClassName?: string;
58
59
  }
59
- declare function TabWithDropdown({ value, label, sections, disabled, scrollOffset, onSectionSelect, activeSection, className, }: TabWithDropdownProps): import("react/jsx-runtime").JSX.Element;
60
+ declare function TabWithDropdown({ value, label, sections, disabled, scrollOffset, onSectionSelect, activeSection, className, contentClassName, }: TabWithDropdownProps): import("react/jsx-runtime").JSX.Element;
60
61
  export interface TabsProps extends Omit<React.ComponentPropsWithoutRef<typeof TabsPrimitive.Root>, "children" | "value" | "defaultValue" | "onValueChange"> {
61
62
  items: TabsItem[];
62
63
  defaultValue?: string;
package/dist/tabs.js CHANGED
@@ -2,11 +2,12 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import * as TabsPrimitive from "@radix-ui/react-tabs";
5
+ import { Check } from "lucide-react";
5
6
  import { ChevronDownIcon } from "./animated-icons/chevron-down";
6
7
  import { XIcon } from "./animated-icons/x";
7
8
  import { cn } from "./lib/utils";
8
9
  import { Popover } from "./popover";
9
- import { PopoverMenuItem, PopoverMenuSection } from "./popover-menu";
10
+ import { PopoverMenuSection } from "./popover-menu";
10
11
  const TabsContext = React.createContext(null);
11
12
  const getTabValueSelector = (value) => {
12
13
  const escapedValue = typeof CSS !== "undefined" && typeof CSS.escape === "function"
@@ -40,7 +41,7 @@ TabsContent.displayName = TabsPrimitive.Content.displayName;
40
41
  function renderTabsItemLabel(label, active) {
41
42
  return typeof label === "function" ? label({ active }) : label;
42
43
  }
43
- function TabTriggerWithDropdown({ item, triggerClassName, }) {
44
+ function TabTriggerWithDropdown({ item, triggerClassName, contentClassName, }) {
44
45
  var _a, _b, _c, _d, _e;
45
46
  const [open, setOpen] = React.useState(false);
46
47
  const sections = (_a = item.sections) !== null && _a !== void 0 ? _a : [];
@@ -81,9 +82,11 @@ function TabTriggerWithDropdown({ item, triggerClassName, }) {
81
82
  setInternalActiveSection((_b = (_a = sections[0]) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : null);
82
83
  }
83
84
  }, [isTabSelected, item.activeSection, sections]);
84
- return (_jsx(Popover, { open: open, onOpenChange: setOpen, surface: "menu", align: "start", contentClassName: "min-w-[12rem]", trigger: _jsxs(TabsTrigger, { value: item.value, disabled: item.disabled, "data-state": isTabSelected ? "active" : "inactive", "data-tab-value": item.value, className: cn("flex items-center gap-1", triggerClassName), children: [renderTabsItemLabel(item.label, isTabSelected), _jsx(ChevronDownIcon, { size: 12, className: cn("transition-transform duration-200", open && "rotate-180") })] }), children: _jsx(PopoverMenuSection, { children: sections.map((section) => (_jsx(PopoverMenuItem, { active: isTabSelected && activeSection === section.id, className: "py-2 pl-2 pr-8", onClick: () => handleSectionClick(section.id), children: section.label }, section.id))) }) }));
85
+ return (_jsx(Popover, { open: open, onOpenChange: setOpen, surface: "menu", align: "start", contentClassName: cn("min-w-[12rem]", contentClassName), trigger: _jsxs(TabsTrigger, { value: item.value, disabled: item.disabled, "data-state": isTabSelected ? "active" : "inactive", "data-tab-value": item.value, className: cn("flex items-center gap-1", triggerClassName), children: [renderTabsItemLabel(item.label, isTabSelected), _jsx(ChevronDownIcon, { size: 12, className: cn("transition-transform duration-200", open && "rotate-180") })] }), children: _jsx(PopoverMenuSection, { className: "space-y-1", children: sections.map((section) => (_jsxs("button", { type: "button", className: cn("flex min-h-fit w-full cursor-pointer items-start gap-2 rounded-sm px-3 py-2 text-left text-sm font-normal leading-5 text-popover-foreground outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:bg-accent focus-visible:text-accent-foreground focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-ring", isTabSelected &&
86
+ activeSection === section.id &&
87
+ "bg-accent text-accent-foreground hover:bg-accent hover:text-accent-foreground"), onClick: () => handleSectionClick(section.id), children: [_jsx("span", { className: "block min-w-0 flex-1 whitespace-normal break-words leading-5", children: section.label }), isTabSelected && activeSection === section.id ? (_jsx(Check, { "aria-hidden": "true", className: "mt-0.5 h-4 w-4 shrink-0" })) : null] }, section.id))) }) }));
85
88
  }
86
- function TabWithDropdown({ value, label, sections, disabled, scrollOffset, onSectionSelect, activeSection, className, }) {
89
+ function TabWithDropdown({ value, label, sections, disabled, scrollOffset, onSectionSelect, activeSection, className, contentClassName, }) {
87
90
  return (_jsx(TabTriggerWithDropdown, { item: {
88
91
  value,
89
92
  label,
@@ -93,7 +96,7 @@ function TabWithDropdown({ value, label, sections, disabled, scrollOffset, onSec
93
96
  scrollOffset,
94
97
  onSectionSelect,
95
98
  activeSection,
96
- }, triggerClassName: className }));
99
+ }, triggerClassName: className, contentClassName: contentClassName }));
97
100
  }
98
101
  function TabTriggerItem({ item, triggerClassName }) {
99
102
  var _a;
@@ -21,7 +21,7 @@ export interface ThemeSwitcherProps extends Omit<SwitchProps, "checked" | "defau
21
21
  useLightLabel?: string;
22
22
  className?: string;
23
23
  }
24
- declare function ThemeSwitcher({ checked, defaultChecked, onCheckedChange, isCollapsed, label, lightLabel, darkLabel, useDarkLabel, useLightLabel, className, disabled, ...props }: ThemeSwitcherProps): import("react/jsx-runtime").JSX.Element;
24
+ declare function ThemeSwitcher({ checked, defaultChecked, onCheckedChange, isCollapsed, label, lightLabel, darkLabel, useDarkLabel, useLightLabel, className, disabled, tabIndex, ...props }: ThemeSwitcherProps): import("react/jsx-runtime").JSX.Element;
25
25
  declare namespace ThemeSwitcher {
26
26
  var displayName: string;
27
27
  }
@@ -5,23 +5,21 @@ import { Moon, Sun } from "lucide-react";
5
5
  import { Button } from "./button";
6
6
  import { cn } from "./lib/utils";
7
7
  import { Switch } from "./switch";
8
- function ThemeSwitcher({ checked, defaultChecked = false, onCheckedChange, isCollapsed = false, label, lightLabel = "Light theme", darkLabel = "Dark theme", useDarkLabel = "Use dark theme", useLightLabel = "Use light theme", className, disabled, ...props }) {
8
+ function ThemeSwitcher({ checked, defaultChecked = false, onCheckedChange, isCollapsed = false, label, lightLabel = "Light theme", darkLabel = "Dark theme", useDarkLabel = "Use dark theme", useLightLabel = "Use light theme", className, disabled, tabIndex, ...props }) {
9
9
  const isControlled = checked !== undefined;
10
10
  const [uncontrolledChecked, setUncontrolledChecked] = React.useState(defaultChecked);
11
11
  const isDark = isControlled ? checked : uncontrolledChecked;
12
12
  const activeLabel = label !== null && label !== void 0 ? label : (isDark ? darkLabel : lightLabel);
13
13
  const ActiveIcon = isDark ? Moon : Sun;
14
- const CollapsedIcon = isDark ? Sun : Moon;
15
14
  const handleCheckedChange = React.useCallback((nextChecked) => {
16
15
  if (!isControlled) {
17
16
  setUncontrolledChecked(nextChecked);
18
17
  }
19
18
  onCheckedChange === null || onCheckedChange === void 0 ? void 0 : onCheckedChange(nextChecked);
20
19
  }, [isControlled, onCheckedChange]);
21
- if (isCollapsed) {
22
- return (_jsx("div", { "data-slot": "theme-switcher", className: cn("flex w-full justify-center py-1", className), children: _jsx(Button, { type: "button", variant: "ghost", size: "icon", disabled: disabled, "aria-label": isDark ? useLightLabel : useDarkLabel, "aria-pressed": isDark, onClick: () => handleCheckedChange(!isDark), children: _jsx(CollapsedIcon, { "aria-hidden": "true", className: "h-4 w-4" }) }) }));
23
- }
24
- return (_jsx("div", { "data-slot": "theme-switcher", className: "px-1.5 py-1", children: _jsx(Switch, { checked: isDark, onCheckedChange: handleCheckedChange, disabled: disabled, label: _jsxs("span", { className: "inline-flex min-w-0 items-center gap-2", children: [_jsx(ActiveIcon, { "aria-hidden": "true", className: "h-4 w-4 shrink-0" }), _jsx("span", { className: "truncate", children: activeLabel })] }), wrapperClassName: cn("w-full flex-row-reverse justify-between rounded-control px-3 py-2 text-muted-foreground transition-colors hover:bg-card hover:text-foreground", className), ...props }) }));
20
+ return (_jsxs("div", { "data-slot": "theme-switcher", className: cn("relative w-full overflow-hidden py-1 transition-[padding] duration-300 ease-in-out", isCollapsed ? "px-0" : "px-1.5", className), children: [_jsx(Button, { type: "button", variant: "ghost", size: "icon", disabled: disabled, "aria-hidden": isCollapsed ? undefined : true, "aria-label": isDark ? useLightLabel : useDarkLabel, "aria-pressed": isDark, tabIndex: isCollapsed ? tabIndex : -1, className: cn("absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 transition-opacity duration-150", isCollapsed ? "opacity-100" : "pointer-events-none opacity-0"), onClick: () => handleCheckedChange(!isDark), children: _jsx(ActiveIcon, { "aria-hidden": "true", className: "h-4 w-4" }) }), _jsx("div", { "aria-hidden": isCollapsed ? true : undefined, className: cn("transition-[opacity,transform] duration-150", isCollapsed
21
+ ? "pointer-events-none translate-x-1 opacity-0"
22
+ : "translate-x-0 opacity-100"), children: _jsx(Switch, { checked: isDark, onCheckedChange: handleCheckedChange, disabled: disabled, tabIndex: isCollapsed ? -1 : tabIndex, label: _jsxs("span", { className: "inline-flex min-w-0 items-center gap-2", children: [_jsx(ActiveIcon, { "aria-hidden": "true", className: "h-4 w-4 shrink-0" }), _jsx("span", { className: "truncate", children: activeLabel })] }), wrapperClassName: "w-full flex-row-reverse justify-between rounded-control px-3 py-2 text-muted-foreground transition-colors hover:bg-card hover:text-foreground", ...props }) })] }));
25
23
  }
26
24
  ThemeSwitcher.displayName = "ThemeSwitcher";
27
25
  export { ThemeSwitcher };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neasg/design-system",
3
- "version": "0.4.10",
3
+ "version": "0.4.12",
4
4
  "description": "NEA shared design system primitives, theme tokens, and Storybook docs.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -23,7 +23,7 @@
23
23
  "build-storybook": "storybook build -c .storybook",
24
24
  "test": "vitest run",
25
25
  "prepublishOnly": "npm test && npm run build",
26
- "chromatic": "chromatic --storybook-build-dir storybook-static --exit-once-uploaded"
26
+ "chromatic": "chromatic --build-script-name build-storybook"
27
27
  },
28
28
  "publishConfig": {
29
29
  "access": "public",