@rovula/ui 0.1.27 → 0.1.29

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 (77) hide show
  1. package/dist/cjs/bundle.css +513 -67
  2. package/dist/cjs/bundle.js +589 -589
  3. package/dist/cjs/bundle.js.map +1 -1
  4. package/dist/cjs/types/components/Avatar/Avatar.d.ts +1 -1
  5. package/dist/cjs/types/components/Avatar/Avatar.stories.d.ts +1 -1
  6. package/dist/cjs/types/components/Avatar/Avatar.styles.d.ts +1 -0
  7. package/dist/cjs/types/components/DataTable/DataTable.d.ts +195 -4
  8. package/dist/cjs/types/components/DataTable/DataTable.editing.d.ts +20 -0
  9. package/dist/cjs/types/components/DataTable/DataTable.editing.types.d.ts +145 -0
  10. package/dist/cjs/types/components/DataTable/DataTable.stories.d.ts +268 -6
  11. package/dist/cjs/types/components/Dropdown/Dropdown.d.ts +22 -0
  12. package/dist/cjs/types/components/Dropdown/Dropdown.stories.d.ts +4 -0
  13. package/dist/cjs/types/components/ScrollArea/ScrollArea.d.ts +3 -3
  14. package/dist/cjs/types/components/ScrollArea/ScrollArea.stories.d.ts +4 -0
  15. package/dist/cjs/types/components/Table/Table.d.ts +33 -3
  16. package/dist/cjs/types/components/Table/Table.stories.d.ts +86 -4
  17. package/dist/cjs/types/components/TextInput/TextInput.stories.d.ts +8 -0
  18. package/dist/cjs/types/components/TextInput/TextInput.styles.d.ts +1 -0
  19. package/dist/components/Avatar/Avatar.js +2 -1
  20. package/dist/components/Avatar/Avatar.styles.js +3 -0
  21. package/dist/components/Avatar/AvatarBase.js +1 -1
  22. package/dist/components/DataTable/DataTable.editing.js +385 -0
  23. package/dist/components/DataTable/DataTable.editing.types.js +1 -0
  24. package/dist/components/DataTable/DataTable.js +983 -50
  25. package/dist/components/DataTable/DataTable.stories.js +1077 -25
  26. package/dist/components/Dropdown/Dropdown.js +8 -6
  27. package/dist/components/ScrollArea/ScrollArea.js +2 -2
  28. package/dist/components/ScrollArea/ScrollArea.stories.js +68 -2
  29. package/dist/components/Table/Table.js +103 -13
  30. package/dist/components/Table/Table.stories.js +226 -9
  31. package/dist/components/TextInput/TextInput.js +6 -4
  32. package/dist/components/TextInput/TextInput.stories.js +8 -0
  33. package/dist/components/TextInput/TextInput.styles.js +7 -1
  34. package/dist/esm/bundle.css +513 -67
  35. package/dist/esm/bundle.js +1545 -1545
  36. package/dist/esm/bundle.js.map +1 -1
  37. package/dist/esm/types/components/Avatar/Avatar.d.ts +1 -1
  38. package/dist/esm/types/components/Avatar/Avatar.stories.d.ts +1 -1
  39. package/dist/esm/types/components/Avatar/Avatar.styles.d.ts +1 -0
  40. package/dist/esm/types/components/DataTable/DataTable.d.ts +195 -4
  41. package/dist/esm/types/components/DataTable/DataTable.editing.d.ts +20 -0
  42. package/dist/esm/types/components/DataTable/DataTable.editing.types.d.ts +145 -0
  43. package/dist/esm/types/components/DataTable/DataTable.stories.d.ts +268 -6
  44. package/dist/esm/types/components/Dropdown/Dropdown.d.ts +22 -0
  45. package/dist/esm/types/components/Dropdown/Dropdown.stories.d.ts +4 -0
  46. package/dist/esm/types/components/ScrollArea/ScrollArea.d.ts +3 -3
  47. package/dist/esm/types/components/ScrollArea/ScrollArea.stories.d.ts +4 -0
  48. package/dist/esm/types/components/Table/Table.d.ts +33 -3
  49. package/dist/esm/types/components/Table/Table.stories.d.ts +86 -4
  50. package/dist/esm/types/components/TextInput/TextInput.stories.d.ts +8 -0
  51. package/dist/esm/types/components/TextInput/TextInput.styles.d.ts +1 -0
  52. package/dist/index.d.ts +493 -122
  53. package/dist/src/theme/global.css +762 -96
  54. package/package.json +14 -2
  55. package/src/components/Avatar/Avatar.styles.ts +4 -1
  56. package/src/components/Avatar/Avatar.tsx +3 -2
  57. package/src/components/Avatar/AvatarBase.tsx +3 -3
  58. package/src/components/DataTable/DataTable.editing.tsx +861 -0
  59. package/src/components/DataTable/DataTable.editing.types.ts +192 -0
  60. package/src/components/DataTable/DataTable.stories.tsx +2169 -31
  61. package/src/components/DataTable/DataTable.test.tsx +696 -0
  62. package/src/components/DataTable/DataTable.tsx +2260 -94
  63. package/src/components/Dropdown/Dropdown.tsx +22 -6
  64. package/src/components/ScrollArea/ScrollArea.stories.tsx +146 -3
  65. package/src/components/ScrollArea/ScrollArea.tsx +6 -6
  66. package/src/components/Table/Table.stories.tsx +789 -44
  67. package/src/components/Table/Table.tsx +294 -28
  68. package/src/components/TextInput/TextInput.stories.tsx +80 -0
  69. package/src/components/TextInput/TextInput.styles.ts +7 -1
  70. package/src/components/TextInput/TextInput.tsx +21 -14
  71. package/src/test/setup.ts +50 -0
  72. package/src/theme/global.css +81 -42
  73. package/src/theme/presets/colors.js +12 -0
  74. package/src/theme/themes/variable.css +27 -28
  75. package/src/theme/tokens/baseline.css +2 -1
  76. package/src/theme/tokens/components/scrollbar.css +9 -4
  77. package/src/theme/tokens/components/table.css +63 -0
@@ -26,7 +26,7 @@ export const menuItemBaseStyles = cn("relative flex gap-1 cursor-pointer select-
26
26
  // Dropdown
27
27
  // ---------------------------------------------------------------------------
28
28
  const Dropdown = forwardRef((_a, ref) => {
29
- var { id, options = [], value, label, size = "md", rounded = "normal", variant = "outline", helperText, errorMessage, fullwidth = true, disabled = false, error = false, filterMode = false, required = true, modal: _modal, onChangeText, onSelect, renderOptions: customRenderOptions, optionContainerClassName, optionItemClassName, optionNotFoundItemClassName, segmentedInput = true } = _a, props = __rest(_a, ["id", "options", "value", "label", "size", "rounded", "variant", "helperText", "errorMessage", "fullwidth", "disabled", "error", "filterMode", "required", "modal", "onChangeText", "onSelect", "renderOptions", "optionContainerClassName", "optionItemClassName", "optionNotFoundItemClassName", "segmentedInput"]);
29
+ var { id, options = [], value, label, size = "md", rounded = "normal", variant = "outline", helperText, errorMessage, fullwidth = true, disabled = false, error = false, filterMode = false, required = true, modal: _modal, onChangeText, onSelect, renderOptions: customRenderOptions, optionContainerClassName, optionItemClassName, optionNotFoundItemClassName, segmentedInput = true, keepOpenAfterSelect = false, portal = false } = _a, props = __rest(_a, ["id", "options", "value", "label", "size", "rounded", "variant", "helperText", "errorMessage", "fullwidth", "disabled", "error", "filterMode", "required", "modal", "onChangeText", "onSelect", "renderOptions", "optionContainerClassName", "optionItemClassName", "optionNotFoundItemClassName", "segmentedInput", "keepOpenAfterSelect", "portal"]);
30
30
  const _id = id || `${label}-select`;
31
31
  const [selectedOption, setSelectedOption] = useState(null);
32
32
  const [query, setQuery] = useState("");
@@ -47,11 +47,11 @@ const Dropdown = forwardRef((_a, ref) => {
47
47
  setQuery("");
48
48
  if (option) {
49
49
  onSelect === null || onSelect === void 0 ? void 0 : onSelect(option);
50
- // After selection Headless UI keeps focus on the input (via rAF refocus).
51
- // Blur after that rAF so the next click triggers onFocus immediate open.
52
- setTimeout(() => { var _a; return (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.blur(); }, 0);
50
+ if (!keepOpenAfterSelect) {
51
+ setTimeout(() => { var _a; return (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.blur(); }, 0);
52
+ }
53
53
  }
54
- }, [onSelect]);
54
+ }, [onSelect, keepOpenAfterSelect]);
55
55
  const handleInputChange = useCallback((e) => {
56
56
  if (filterMode)
57
57
  setQuery(e.target.value);
@@ -83,7 +83,9 @@ const Dropdown = forwardRef((_a, ref) => {
83
83
  "bg-[var(--dropdown-menu-hover-bg)] text-[var(--dropdown-menu-hover-text)]", optionItemClassName), children: ({ selected }) => (_jsxs(_Fragment, { children: [_jsx("span", { className: "shrink-0 size-4 flex items-center justify-center", children: (selected || (selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.value) === option.value) && (_jsx(Icon, { type: "heroicons", name: "check", className: "size-4 text-[var(--dropdown-menu-selected-text)]" })) }), option.label] })) }, option.value));
84
84
  });
85
85
  };
86
- return (_jsx(Combobox, { value: selectedOption, onChange: handleSelect, immediate: true, by: "value", disabled: disabled, children: ({ open }) => (_jsxs("div", { className: cn("relative", fullwidth && "w-full"), children: [_jsx(ComboboxInput, Object.assign({ as: TextInput, ref: inputRef, hasClearIcon: false, endIcon: _jsx(ChevronDownIcon, { className: dropdownIconVariant({ isFocus: open }) }), label: label, placeholder: " ", autoComplete: "off", rounded: rounded, variant: variant, helperText: helperText, errorMessage: errorMessage, fullwidth: fullwidth, error: error, required: required, id: _id, disabled: disabled, size: size, className: segmentedInput ? customInputVariant({ size }) : undefined, displayValue: (opt) => { var _a; return (_a = opt === null || opt === void 0 ? void 0 : opt.label) !== null && _a !== void 0 ? _a : ""; }, readOnly: !filterMode, onChange: handleInputChange }, props)), _jsx(ComboboxOptions, { className: cn("absolute top-full left-0 w-full -mt-3 z-[51]", "min-w-[154px] max-h-60 overflow-y-auto", "rounded-md bg-modal-dropdown-surface border border-bg-stroke3 text-text-g-contrast-high", optionContainerClassName), style: { boxShadow: "var(--dropdown-menu-shadow)" }, children: renderOptionList() })] })) }));
86
+ return (_jsx(Combobox, { value: selectedOption, onChange: handleSelect, immediate: true, by: "value", disabled: disabled, children: ({ open }) => (_jsxs("div", { className: cn("relative", fullwidth && "w-full"), children: [_jsx(ComboboxInput, Object.assign({ as: TextInput, ref: inputRef, hasClearIcon: false, endIcon: _jsx(ChevronDownIcon, { className: dropdownIconVariant({ isFocus: open }) }), label: label, autoComplete: "off", rounded: rounded, variant: variant, helperText: helperText, errorMessage: errorMessage, fullwidth: fullwidth, error: error, required: required, id: _id, disabled: disabled, size: size, className: segmentedInput ? customInputVariant({ size }) : undefined, displayValue: (opt) => { var _a; return (_a = opt === null || opt === void 0 ? void 0 : opt.label) !== null && _a !== void 0 ? _a : ""; }, readOnly: !filterMode, onChange: handleInputChange }, props)), _jsx(ComboboxOptions, Object.assign({}, (portal
87
+ ? { portal: true, anchor: { to: "bottom start", gap: 4 } }
88
+ : {}), { className: cn(!portal && "absolute top-full left-0 w-full -mt-3 z-[51]", portal && "z-[51] !max-h-60 w-[var(--input-width)]", "min-w-[154px] max-h-60 overflow-y-auto", "rounded-md bg-modal-dropdown-surface border border-bg-stroke3 text-text-g-contrast-high", optionContainerClassName), style: { boxShadow: "var(--dropdown-menu-shadow)" }, children: renderOptionList() }))] })) }));
87
89
  });
88
90
  Dropdown.displayName = "Dropdown";
89
91
  export default Dropdown;
@@ -13,7 +13,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
13
13
  import * as React from "react";
14
14
  import { cn } from "@/utils/cn";
15
15
  const sizeClass = {
16
- m: "ui-scrollbar ui-scrollbar-m",
16
+ m: "ui-scrollbar", // default = M, no extra class needed
17
17
  s: "ui-scrollbar ui-scrollbar-s",
18
18
  xs: "ui-scrollbar ui-scrollbar-xs",
19
19
  };
@@ -44,7 +44,7 @@ const directionClass = {
44
44
  * ```
45
45
  */
46
46
  export const ScrollArea = React.forwardRef((_a, ref) => {
47
- var { className, scrollbarSize = "s", direction = "vertical", children } = _a, props = __rest(_a, ["className", "scrollbarSize", "direction", "children"]);
47
+ var { className, scrollbarSize = "m", direction = "vertical", children } = _a, props = __rest(_a, ["className", "scrollbarSize", "direction", "children"]);
48
48
  return (_jsx("div", Object.assign({ ref: ref, className: cn(directionClass[direction], sizeClass[scrollbarSize], className) }, props, { children: children })));
49
49
  });
50
50
  ScrollArea.displayName = "ScrollArea";
@@ -7,7 +7,7 @@ const meta = {
7
7
  layout: "fullscreen",
8
8
  },
9
9
  decorators: [
10
- (Story) => (_jsx("div", { className: "p-10 flex gap-8 flex-wrap bg-workspace-surface min-h-screen", children: _jsx(Story, {}) })),
10
+ (Story) => (_jsx("div", { className: "p-10 flex gap-8 flex-wrap bg-page-bg-main min-h-screen", children: _jsx(Story, {}) })),
11
11
  ],
12
12
  };
13
13
  export default meta;
@@ -17,7 +17,7 @@ const ITEMS = Array.from({ length: 12 }, (_, i) => `Option Description ${i + 1}`
17
17
  // ---------------------------------------------------------------------------
18
18
  export const Sizes = {
19
19
  name: "Size Variants",
20
- render: () => (_jsx("div", { className: "flex gap-10 items-start", children: ["m", "s", "xs"].map((size) => (_jsxs("div", { children: [_jsxs("p", { className: "typography-small4 text-text-g-contrast-medium mb-2 uppercase", children: ["Size ", size] }), _jsx(ScrollArea, { scrollbarSize: size, className: "max-h-[270px] w-[200px] rounded-lg bg-modal-surface", children: ITEMS.map((label) => (_jsx("div", { className: "px-4 py-3 typography-subtitle4 text-text-g-contrast-high", children: label }, label))) })] }, size))) })),
20
+ render: () => (_jsx("div", { className: "flex gap-10 items-start", children: ["m", "s", "xs"].map((size) => (_jsxs("div", { children: [_jsxs("p", { className: "typography-small4 text-text-g-contrast-medium mb-2 uppercase", children: ["Size ", size, size === "m" ? " (default)" : ""] }), _jsx(ScrollArea, { scrollbarSize: size, className: "max-h-[270px] w-[200px] rounded-lg bg-modal-surface", children: ITEMS.map((label) => (_jsx("div", { className: "px-4 py-3 typography-subtitle4 text-text-g-contrast-high", children: label }, label))) })] }, size))) })),
21
21
  };
22
22
  // ---------------------------------------------------------------------------
23
23
  // Vertical scroll (Figma: Can Scroll)
@@ -54,3 +54,69 @@ export const WithDropdownMenu = {
54
54
  return (_jsxs("div", { className: "flex gap-8 items-start flex-wrap", children: [_jsxs("div", { children: [_jsx("p", { className: "typography-small4 text-text-g-contrast-medium mb-2", children: "Can Scroll" }), _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "outline", children: "Open Menu" }) }), _jsx(DropdownMenuContent, { children: _jsx(ScrollArea, { className: "max-h-[270px]", children: ITEMS.map((label) => (_jsx(DropdownMenuItem, { children: label }, label))) }) })] })] }), _jsxs("div", { children: [_jsx("p", { className: "typography-small4 text-text-g-contrast-medium mb-2", children: "Double Scroll" }), _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "outline", children: "Open Menu" }) }), _jsxs(DropdownMenuContent, { children: [_jsx(DropdownMenuLabel, { children: "Section A" }), _jsx(ScrollArea, { className: "max-h-[160px]", children: ITEMS.slice(0, 8).map((label) => (_jsx(DropdownMenuItem, { children: label }, label))) }), _jsx(DropdownMenuSeparator, {}), _jsx(DropdownMenuLabel, { children: "Section B" }), _jsx(ScrollArea, { className: "max-h-[160px]", children: ITEMS.slice(0, 8).map((label) => (_jsx(DropdownMenuItem, { children: label }, label))) })] })] })] })] }));
55
55
  },
56
56
  };
57
+ // ---------------------------------------------------------------------------
58
+ // Both axes — vertical + horizontal scroll simultaneously
59
+ // ---------------------------------------------------------------------------
60
+ const WIDE_COLS = [
61
+ "ID",
62
+ "Name",
63
+ "Category",
64
+ "Type",
65
+ "Format",
66
+ "Source",
67
+ "Default",
68
+ "Required",
69
+ "Nullable",
70
+ "Description",
71
+ ];
72
+ const WIDE_ROWS = Array.from({ length: 30 }, (_, i) => ({
73
+ id: `ROW-${String(i + 1).padStart(3, "0")}`,
74
+ name: `item_name_${i + 1}`,
75
+ category: ["Alpha", "Beta", "Gamma", "Delta", "Epsilon"][i % 5],
76
+ type: ["String", "Number", "Boolean", "Date", "Enum"][i % 5],
77
+ format: ["Text", "Integer", "true/false", "ISO 8601", "1–5"][i % 5],
78
+ source: ["System", "Manual", "Device", "Auth", "Import"][i % 5],
79
+ defaultVal: ["-", "auto", "true", "now()", "0"][i % 5],
80
+ required: i % 3 === 0 ? "Yes" : "No",
81
+ nullable: i % 2 === 0 ? "Yes" : "No",
82
+ description: `Description text for row ${i + 1}`,
83
+ }));
84
+ const WideContent = () => (_jsxs("div", { className: "min-w-[800px]", children: [_jsx("div", { className: "flex sticky top-0 z-10 bg-modal-surface border-b border-[var(--dropdown-menu-seperator-bg)]", children: WIDE_COLS.map((col) => (_jsx("div", { className: "shrink-0 w-[120px] px-3 py-2 typography-small2 text-text-g-contrast-medium whitespace-nowrap", children: col }, col))) }), WIDE_ROWS.map((row) => (_jsxs("div", { className: "flex border-b border-[var(--dropdown-menu-seperator-bg)] hover:bg-[var(--transparent-grey2-8,rgba(145,158,171,0.08))] transition-colors", children: [_jsx("div", { className: "shrink-0 w-[120px] px-3 py-2 typography-subtitle4 text-text-g-contrast-high whitespace-nowrap font-medium", children: row.id }), _jsx("div", { className: "shrink-0 w-[120px] px-3 py-2 typography-subtitle4 text-text-g-contrast-high whitespace-nowrap", children: row.name }), _jsx("div", { className: "shrink-0 w-[120px] px-3 py-2 typography-subtitle4 text-text-g-contrast-high whitespace-nowrap", children: row.category }), _jsx("div", { className: "shrink-0 w-[120px] px-3 py-2 typography-subtitle4 text-text-g-contrast-high whitespace-nowrap", children: row.type }), _jsx("div", { className: "shrink-0 w-[120px] px-3 py-2 typography-subtitle4 text-text-g-contrast-high whitespace-nowrap", children: row.format }), _jsx("div", { className: "shrink-0 w-[120px] px-3 py-2 typography-subtitle4 text-text-g-contrast-high whitespace-nowrap", children: row.source }), _jsx("div", { className: "shrink-0 w-[120px] px-3 py-2 typography-subtitle4 text-text-g-contrast-high whitespace-nowrap", children: row.defaultVal }), _jsx("div", { className: "shrink-0 w-[120px] px-3 py-2 typography-subtitle4 text-text-g-contrast-high whitespace-nowrap", children: row.required }), _jsx("div", { className: "shrink-0 w-[120px] px-3 py-2 typography-subtitle4 text-text-g-contrast-high whitespace-nowrap", children: row.nullable }), _jsx("div", { className: "shrink-0 w-[120px] px-3 py-2 typography-subtitle4 text-text-g-contrast-high whitespace-nowrap", children: row.description })] }, row.id)))] }));
85
+ export const BothAxes = {
86
+ name: "Both Axes",
87
+ render: () => {
88
+ const cases = [
89
+ {
90
+ label: "X = M (16px) + Y = M (16px) — same",
91
+ xSize: "ui-scrollbar-x-m",
92
+ ySize: "ui-scrollbar-y-m",
93
+ },
94
+ {
95
+ label: "X = S (10px) + Y = S (10px) — same",
96
+ xSize: "ui-scrollbar-x-s",
97
+ ySize: "ui-scrollbar-y-s",
98
+ },
99
+ {
100
+ label: "X = XS (4px) + Y = XS (4px) — same",
101
+ xSize: "ui-scrollbar-x-xs",
102
+ ySize: "ui-scrollbar-y-xs",
103
+ },
104
+ {
105
+ label: "X = M (16px) + Y = S (10px) — mixed",
106
+ xSize: "ui-scrollbar-x-m",
107
+ ySize: "ui-scrollbar-y-s",
108
+ },
109
+ {
110
+ label: "X = S (10px) + Y = M (16px) — mixed",
111
+ xSize: "ui-scrollbar-x-s",
112
+ ySize: "ui-scrollbar-y-m",
113
+ },
114
+ {
115
+ label: "X = M (16px) + Y = XS (4px) — mixed",
116
+ xSize: "ui-scrollbar-x-m",
117
+ ySize: "ui-scrollbar-y-xs",
118
+ },
119
+ ];
120
+ return (_jsx("div", { className: "flex flex-col gap-8 w-full", children: cases.map(({ label, xSize, ySize }) => (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("p", { className: "typography-subtitle4 text-text-g-contrast-high", children: label }), _jsx(ScrollArea, { direction: "both", className: `h-[220px] w-[480px] rounded-lg bg-modal-surface ${xSize} ${ySize}`, children: _jsx(WideContent, {}) })] }, label))) }));
121
+ },
122
+ };
@@ -9,47 +9,137 @@ var __rest = (this && this.__rest) || function (s, e) {
9
9
  }
10
10
  return t;
11
11
  };
12
- import { jsx as _jsx } from "react/jsx-runtime";
12
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
13
13
  import * as React from "react";
14
+ import { ChevronDown, ChevronLeft, ChevronRight, ChevronFirst, ChevronLast, } from "lucide-react";
14
15
  import { cn } from "@/utils/cn";
16
+ import ActionButton from "@/components/ActionButton/ActionButton";
17
+ // ---------------------------------------------------------------------------
18
+ // Table — root scroll container + <table>
19
+ // Uses border-separate / border-spacing-0 so border-r on cells works cleanly.
20
+ //
21
+ // Colours adapt automatically when an ancestor carries data-surface="panel".
22
+ // See src/theme/tokens/components/table.css for the token definitions.
23
+ // ---------------------------------------------------------------------------
15
24
  const Table = React.forwardRef((_a, ref) => {
16
- var { rootClassName, className, rootRef } = _a, props = __rest(_a, ["rootClassName", "className", "rootRef"]);
17
- return (_jsx("div", { className: cn("relative h-full w-full overflow-auto", rootClassName), ref: rootRef, children: _jsx("table", Object.assign({ ref: ref, className: cn("w-full caption-bottom text-sm border-collapse", className) }, props)) }));
25
+ var { rootClassName, className, rootRef, bordered = false, scrollableWrapper = true } = _a, props = __rest(_a, ["rootClassName", "className", "rootRef", "bordered", "scrollableWrapper"]);
26
+ const scrollClassName = cn("relative h-full w-full min-h-0 overflow-auto", "ui-scrollbar ui-scrollbar-x-m ui-scrollbar-y-s");
27
+ const tableClassName = cn("min-w-full caption-bottom border-separate border-spacing-0", className);
28
+ const tableEl = (_jsx("table", Object.assign({ ref: ref, className: tableClassName }, props)));
29
+ if (!bordered && scrollableWrapper === false) {
30
+ return (_jsx("table", Object.assign({ ref: ref, className: cn(tableClassName, rootClassName) }, props)));
31
+ }
32
+ if (bordered) {
33
+ return (_jsx("div", { className: cn("relative flex h-full w-full min-h-0 flex-col overflow-hidden rounded-md border border-table-c-border", rootClassName), ref: rootRef, children: _jsx("div", { className: scrollClassName, children: tableEl }) }));
34
+ }
35
+ return (_jsx("div", { className: cn(scrollClassName, rootClassName), ref: rootRef, children: tableEl }));
18
36
  });
19
37
  Table.displayName = "Table";
38
+ // ---------------------------------------------------------------------------
39
+ // TableHeader
40
+ // The header-to-body separator resolves from --table-c-header-line:
41
+ // default → transparent (bg contrast between table-bg-main and row-bg
42
+ // provides visual separation — no explicit line needed)
43
+ // panel → table-panel-main-line (all bgs are transparent, explicit
44
+ // line required)
45
+ // ---------------------------------------------------------------------------
20
46
  const TableHeader = React.forwardRef((_a, ref) => {
21
47
  var { className } = _a, props = __rest(_a, ["className"]);
22
- return (_jsx("thead", Object.assign({ ref: ref, className: cn("[&_tr]:border-b bg-secondary-80", className) }, props)));
48
+ return (_jsx("thead", Object.assign({ ref: ref, className: cn("[&_tr>th]:border-b [&_tr>th]:border-b-table-c-header-line", className) }, props)));
23
49
  });
24
50
  TableHeader.displayName = "TableHeader";
51
+ // ---------------------------------------------------------------------------
52
+ // TableBody
53
+ // striped=true → alternating bg colours (odd=row-bg, even=row-bg-even),
54
+ // NO horizontal row strokes
55
+ // striped=false → single row-bg colour, row strokes via TableRow divided prop
56
+ //
57
+ // Hover, selected, and bg colours all resolve from table-c-* tokens so they
58
+ // automatically switch when inside a [data-surface="panel"] ancestor.
59
+ // ---------------------------------------------------------------------------
25
60
  const TableBody = React.forwardRef((_a, ref) => {
26
- var { className } = _a, props = __rest(_a, ["className"]);
27
- return (_jsx("tbody", Object.assign({ ref: ref, className: cn("[&_tr:last-child]:border-0", className) }, props)));
61
+ var { className, striped = false } = _a, props = __rest(_a, ["className", "striped"]);
62
+ return (_jsx("tbody", Object.assign({ ref: ref, className: cn(
63
+ // Remove the bottom border on the very last row's cells in every mode.
64
+ "[&_tr:last-child>td]:border-b-0 [&_tr:last-child>th]:border-b-0", "[&_tr]:bg-table-c-row-bg",
65
+ // !important beats nth-child specificity so hover always wins.
66
+ "[&_tr:hover]:!bg-table-c-hover", "[&_tr[data-state=selected]]:!bg-table-c-selected", "[&_tr[data-highlighted=true]]:!bg-table-c-selected", striped && [
67
+ "[&_tr:nth-child(odd)]:bg-table-c-row-bg",
68
+ "[&_tr:nth-child(even)]:bg-table-c-row-bg-even",
69
+ // Remove row stroke — colour alternation provides separation.
70
+ "[&_tr>td]:border-b-0",
71
+ ], className) }, props)));
28
72
  });
29
73
  TableBody.displayName = "TableBody";
74
+ // ---------------------------------------------------------------------------
75
+ // TableFooter
76
+ // ---------------------------------------------------------------------------
30
77
  const TableFooter = React.forwardRef((_a, ref) => {
31
78
  var { className } = _a, props = __rest(_a, ["className"]);
32
- return (_jsx("tfoot", Object.assign({ ref: ref, className: cn("border-t bg-transparent-grey2-8 font-medium [&>tr]:last:border-b-0", className) }, props)));
79
+ return (_jsx("tfoot", Object.assign({ ref: ref, className: cn("bg-table-c-header-bg border-t border-t-table-c-header-line font-medium [&>tr]:last:border-b-0", className) }, props)));
33
80
  });
34
81
  TableFooter.displayName = "TableFooter";
82
+ // ---------------------------------------------------------------------------
83
+ // TableRow
84
+ // divided=true → horizontal separator on child td/th cells (border-b)
85
+ // NOTE: with border-separate tables, borders on <tr> are
86
+ // invisible — must target cells directly via CSS selectors.
87
+ // divided=false → no separator (striped rows / empty row)
88
+ // colDivided=true → vertical column dividers on every non-last td / th child
89
+ // ---------------------------------------------------------------------------
35
90
  const TableRow = React.forwardRef((_a, ref) => {
36
- var { className } = _a, props = __rest(_a, ["className"]);
37
- return (_jsx("tr", Object.assign({ ref: ref, className: cn("border-b transition-colors hover:bg-transparent-grey2-8 data-[state=selected]:bg-grey-20", className) }, props)));
91
+ var { className, divided = true, colDivided = false } = _a, props = __rest(_a, ["className", "divided", "colDivided"]);
92
+ return (_jsx("tr", Object.assign({ ref: ref, className: cn("transition-colors",
93
+ // Row separator — applied on cells because border-separate ignores tr borders
94
+ divided && [
95
+ "[&>td]:border-b [&>td]:border-b-table-c-row-line",
96
+ "[&>th]:border-b [&>th]:border-b-table-c-row-line",
97
+ ],
98
+ // Column dividers on every non-last cell
99
+ colDivided && [
100
+ "[&>td:not(:last-child)]:border-r [&>td:not(:last-child)]:border-r-table-c-col-line",
101
+ "[&>th:not(:last-child)]:border-r [&>th:not(:last-child)]:border-r-table-c-col-line",
102
+ ], className) }, props)));
38
103
  });
39
104
  TableRow.displayName = "TableRow";
105
+ // ---------------------------------------------------------------------------
106
+ // TableHead (th)
107
+ // ---------------------------------------------------------------------------
40
108
  const TableHead = React.forwardRef((_a, ref) => {
41
109
  var { className } = _a, props = __rest(_a, ["className"]);
42
- return (_jsx("th", Object.assign({ ref: ref, className: cn(" h-12 py-3 px-6 text-left align-middle typography-body2 text-text-g-contrast-low [&:has([role=checkbox])]:pr-4 [&:has([role=checkbox])]:w-4", className) }, props)));
110
+ return (_jsx("th", Object.assign({ ref: ref, className: cn("h-[44px] box-border py-3 px-3 text-left align-middle", "typography-body2 text-text-contrast-max", "bg-table-c-header-bg",
111
+ // Prefer min-width only so callers (e.g. DataTable exactWidth) can set
112
+ // a different width via inline style without fighting a fixed `w-10`.
113
+ "[&:has([role=checkbox])]:px-3 [&:has([role=checkbox])]:min-w-10", className) }, props)));
43
114
  });
44
115
  TableHead.displayName = "TableHead";
116
+ // ---------------------------------------------------------------------------
117
+ // TableCell (td)
118
+ // ---------------------------------------------------------------------------
45
119
  const TableCell = React.forwardRef((_a, ref) => {
46
120
  var { className } = _a, props = __rest(_a, ["className"]);
47
- return (_jsx("td", Object.assign({ ref: ref, className: cn(" py-3 px-6 text-left align-middle typography-body3 text-text-g-contrast-low [&:has([role=checkbox])]:pr-4 [&:has([role=checkbox])]:w-4", className) }, props)));
121
+ return (_jsx("td", Object.assign({ ref: ref, className: cn("h-[42px] box-border py-3 px-3 text-left align-middle", "typography-body3 text-text-contrast-max",
122
+ // Inherit row background from <tr> so every cell paints the same fill
123
+ // (some table layouts / browsers leave the last column visually “empty”).
124
+ "bg-inherit", "[&:has([role=checkbox])]:px-3 [&:has([role=checkbox])]:min-w-10", className) }, props)));
48
125
  });
49
126
  TableCell.displayName = "TableCell";
127
+ // ---------------------------------------------------------------------------
128
+ // TableCaption
129
+ // ---------------------------------------------------------------------------
50
130
  const TableCaption = React.forwardRef((_a, ref) => {
51
131
  var { className } = _a, props = __rest(_a, ["className"]);
52
- return (_jsx("caption", Object.assign({ ref: ref, className: cn("mt-4 text-sm text-text-g-contrast-medium", className) }, props)));
132
+ return (_jsx("caption", Object.assign({ ref: ref, className: cn("mt-4 pb-3 text-sm text-text-g-contrast-medium", className) }, props)));
53
133
  });
54
134
  TableCaption.displayName = "TableCaption";
55
- export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption, };
135
+ const TablePagination = React.forwardRef(({ pageIndex, pageSize, totalCount, pageSizeOptions = [10, 20, 50, 100], onPageChange, onPageSizeChange, className, }, ref) => {
136
+ const totalPages = Math.max(1, Math.ceil(totalCount / pageSize));
137
+ const from = totalCount === 0 ? 0 : pageIndex * pageSize + 1;
138
+ const to = Math.min((pageIndex + 1) * pageSize, totalCount);
139
+ return (_jsxs("div", { ref: ref, className: cn("flex items-center justify-end gap-8 px-6 py-2", "bg-table-c-header-bg", "border-t border-t-table-c-header-line", className), children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("span", { className: "typography-subtitle4 text-text-g-contrast-high whitespace-nowrap", children: "items per page" }), _jsxs("div", { className: "relative", children: [_jsx("select", { value: pageSize, onChange: (e) => {
140
+ onPageSizeChange(Number(e.target.value));
141
+ onPageChange(0);
142
+ }, className: cn("appearance-none w-[72px] rounded-md border border-input-default-stroke", "bg-table-c-header-bg p-2 pr-7", "typography-small2 text-text-g-contrast-high", "cursor-pointer focus:outline-none focus:ring-1 focus:ring-input-active-stroke"), children: pageSizeOptions.map((size) => (_jsx("option", { value: size, children: size }, size))) }), _jsx(ChevronDown, { className: "pointer-events-none absolute right-2 top-1/2 -translate-y-1/2 size-[14px] text-text-g-contrast-high", "aria-hidden": true })] })] }), _jsxs("span", { className: "typography-subtitle4 text-text-g-contrast-high whitespace-nowrap tabular-nums", children: [from, "\u2013", to, " of ", totalCount, " items"] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(ActionButton, { variant: "icon", size: "sm", onClick: () => onPageChange(0), disabled: pageIndex === 0, "aria-label": "First page", children: _jsx(ChevronFirst, {}) }), _jsx(ActionButton, { variant: "icon", size: "sm", onClick: () => onPageChange(pageIndex - 1), disabled: pageIndex === 0, "aria-label": "Previous page", children: _jsx(ChevronLeft, {}) }), _jsx(ActionButton, { variant: "icon", size: "sm", onClick: () => onPageChange(pageIndex + 1), disabled: pageIndex >= totalPages - 1, "aria-label": "Next page", children: _jsx(ChevronRight, {}) }), _jsx(ActionButton, { variant: "icon", size: "sm", onClick: () => onPageChange(totalPages - 1), disabled: pageIndex >= totalPages - 1, "aria-label": "Last page", children: _jsx(ChevronLast, {}) })] })] }));
143
+ });
144
+ TablePagination.displayName = "TablePagination";
145
+ export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption, TablePagination, };
@@ -1,21 +1,238 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow, } from "./Table";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import React from "react";
3
+ import { Table, TableBody, TableCell, TableFooter, TableHead, TableHeader, TablePagination, TableRow, } from "./Table";
3
4
  const meta = {
4
5
  title: "Components/Table",
5
6
  component: Table,
6
7
  tags: ["autodocs"],
7
- parameters: {
8
- layout: "fullscreen",
8
+ parameters: { layout: "fullscreen" },
9
+ argTypes: {
10
+ bordered: {
11
+ control: "boolean",
12
+ description: "Outer `rounded-md` + `border-table-c-border`, inner scroll (`Table.tsx`).",
13
+ },
9
14
  },
10
15
  decorators: [
11
- (Story) => (_jsx("div", { className: "p-5 flex w-full", children: _jsx(Story, {}) })),
16
+ (Story) => (_jsx("div", { className: "p-8 flex flex-col gap-10 bg-page-bg-main min-h-screen w-full", children: _jsx(Story, {}) })),
12
17
  ],
13
18
  };
14
19
  export default meta;
20
+ const rows = [
21
+ { id: "INV001", status: "Paid", method: "Credit Card", amount: "$250.00" },
22
+ { id: "INV002", status: "Pending", method: "PayPal", amount: "$150.00" },
23
+ { id: "INV003", status: "Paid", method: "Bank Transfer", amount: "$350.00" },
24
+ {
25
+ id: "INV004",
26
+ status: "Cancelled",
27
+ method: "Credit Card",
28
+ amount: "$450.00",
29
+ },
30
+ { id: "INV005", status: "Pending", method: "PayPal", amount: "$90.00" },
31
+ ];
32
+ // ── Shared helpers ────────────────────────────────────────────────────────────
33
+ const Headers = ({ colDivided = false }) => (_jsx(TableHeader, { children: _jsxs(TableRow, { divided: false, colDivided: colDivided, children: [_jsx(TableHead, { className: "w-[120px]", children: "Invoice" }), _jsx(TableHead, { children: "Status" }), _jsx(TableHead, { children: "Method" }), _jsx(TableHead, { className: "text-right", children: "Amount" })] }) }));
34
+ const Rows = ({ divided = true, colDivided = false, selectedIndex, }) => (_jsx(_Fragment, { children: rows.map((r, i) => (_jsxs(TableRow, { divided: divided, colDivided: colDivided, "data-state": i === selectedIndex ? "selected" : undefined, children: [_jsx(TableCell, { className: "font-medium", children: r.id }), _jsx(TableCell, { children: r.status }), _jsx(TableCell, { children: r.method }), _jsx(TableCell, { className: "text-right", children: r.amount })] }, r.id))) }));
35
+ // ── Stories ───────────────────────────────────────────────────────────────────
36
+ /** NON-STRIPED — horizontal row strokes, no column dividers */
15
37
  export const Default = {
16
- args: {},
17
- render: (args) => {
18
- const props = Object.assign({}, args);
19
- return (_jsx("div", { className: "flex flex-row gap-4 w-full", children: _jsxs(Table, { children: [_jsx(TableCaption, { children: "A list of your recent invoices." }), _jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { className: "w-[100px]", children: "Invoice" }), _jsx(TableHead, { children: "Status" }), _jsx(TableHead, { children: "Method" }), _jsx(TableHead, { className: "text-right", children: "Amount" })] }) }), _jsxs(TableBody, { children: [_jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-medium", children: "INV001" }), _jsx(TableCell, { children: "Paid" }), _jsx(TableCell, { children: "Credit Card" }), _jsx(TableCell, { className: "text-right", children: "$250.00" })] }), _jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-medium", children: "INV001" }), _jsx(TableCell, { children: "Paid" }), _jsx(TableCell, { children: "Credit Card" }), _jsx(TableCell, { className: "text-right", children: "$250.00" })] }), _jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-medium", children: "INV001" }), _jsx(TableCell, { children: "Paid" }), _jsx(TableCell, { children: "Credit Card" }), _jsx(TableCell, { className: "text-right", children: "$250.00" })] }), _jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-medium", children: "INV001" }), _jsx(TableCell, { children: "Paid" }), _jsx(TableCell, { children: "Credit Card" }), _jsx(TableCell, { className: "text-right", children: "$250.00" })] })] })] }) }));
38
+ render: () => (_jsxs(Table, { children: [_jsx(Headers, {}), _jsx(TableBody, { children: _jsx(Rows, {}) })] })),
39
+ };
40
+ /** NON-STRIPED + COL DIVIDED — horizontal row strokes + vertical column dividers */
41
+ export const NonStripedDivided = {
42
+ render: () => (_jsxs(Table, { bordered: true, children: [_jsx(Headers, { colDivided: true }), _jsx(TableBody, { children: _jsx(Rows, { colDivided: true }) })] })),
43
+ };
44
+ /** STRIPED — alternating bg-a / bg-b, NO row strokes, NO column dividers */
45
+ export const Striped = {
46
+ render: () => (_jsxs(Table, { bordered: true, children: [_jsx(Headers, {}), _jsx(TableBody, { striped: true, children: _jsx(Rows, { divided: false }) })] })),
47
+ };
48
+ /**
49
+ * STRIPED + COL DIVIDED — alternating bg, NO row strokes, WITH column dividers.
50
+ * Primary Xspector table style.
51
+ */
52
+ export const StripedAndDivided = {
53
+ render: () => (_jsxs(Table, { bordered: true, children: [_jsx(Headers, { colDivided: true }), _jsx(TableBody, { striped: true, children: _jsx(Rows, { divided: false, colDivided: true }) })] })),
54
+ };
55
+ /** SELECTED ROW — data-[state=selected] applies transparent-primary-12. Row 1 pre-selected. */
56
+ export const WithSelectedRow = {
57
+ render: () => (_jsxs(Table, { bordered: true, children: [_jsx(Headers, { colDivided: true }), _jsx(TableBody, { striped: true, children: _jsx(Rows, { divided: false, colDivided: true, selectedIndex: 1 }) })] })),
58
+ };
59
+ /**
60
+ * WITH FOOTER — tfoot summary row with bg-table-bg-main + top border.
61
+ * TableFooter is inside Table so bordered works naturally.
62
+ */
63
+ export const WithFooter = {
64
+ render: () => (_jsxs(Table, { bordered: true, children: [_jsx(Headers, { colDivided: true }), _jsx(TableBody, { children: _jsx(Rows, { colDivided: true }) }), _jsx(TableFooter, { children: _jsxs(TableRow, { divided: false, colDivided: true, children: [_jsx(TableCell, { className: "font-medium", children: "5 invoices" }), _jsx(TableCell, {}), _jsx(TableCell, {}), _jsx(TableCell, { className: "text-right font-medium", children: "$1,290.00" })] }) })] })),
65
+ };
66
+ /**
67
+ * WITH PAGINATION — TablePagination is a sibling of Table so needs an explicit
68
+ * outer wrapper (not covered by Table's bordered prop).
69
+ */
70
+ export const WithPagination = {
71
+ render: () => {
72
+ const [pageIndex, setPageIndex] = React.useState(0);
73
+ const [pageSize, setPageSize] = React.useState(2);
74
+ const paged = rows.slice(pageIndex * pageSize, (pageIndex + 1) * pageSize);
75
+ return (
76
+ // Explicit outer wrapper — required because TablePagination is a sibling of Table
77
+ _jsxs("div", { className: "rounded-md overflow-hidden border border-table-c-border w-full", children: [_jsxs(Table, { children: [_jsx(Headers, { colDivided: true }), _jsx(TableBody, { children: paged.map((r) => (_jsxs(TableRow, { divided: true, colDivided: true, children: [_jsx(TableCell, { className: "font-medium", children: r.id }), _jsx(TableCell, { children: r.status }), _jsx(TableCell, { children: r.method }), _jsx(TableCell, { className: "text-right", children: r.amount })] }, r.id))) })] }), _jsx(TablePagination, { pageIndex: pageIndex, pageSize: pageSize, totalCount: rows.length, pageSizeOptions: [2, 3, 5], onPageChange: setPageIndex, onPageSizeChange: (s) => {
78
+ setPageSize(s);
79
+ setPageIndex(0);
80
+ } })] }));
81
+ },
82
+ };
83
+ /** EMPTY STATE — single full-width row with centered message */
84
+ export const Empty = {
85
+ render: () => (_jsxs(Table, { bordered: true, children: [_jsx(Headers, { colDivided: true }), _jsx(TableBody, { children: _jsx(TableRow, { divided: false, className: "hover:bg-transparent", children: _jsx(TableCell, { colSpan: 4, className: "h-32 text-center text-text-g-contrast-medium", children: "No data found." }) }) })] })),
86
+ };
87
+ /** ALL STATES SIDE-BY-SIDE — reference sheet for design review */
88
+ export const AllStates = {
89
+ render: () => (_jsxs("div", { className: "flex flex-col gap-6 w-full", children: [_jsx("p", { className: "typography-subtitle4 text-text-g-contrast-high", children: "Non-striped \u2014 row stroke only" }), _jsxs(Table, { bordered: true, children: [_jsx(Headers, {}), _jsx(TableBody, { children: _jsx(Rows, {}) })] }), _jsx("p", { className: "typography-subtitle4 text-text-g-contrast-high", children: "Non-striped + col divided \u2014 row stroke + column dividers" }), _jsxs(Table, { bordered: true, children: [_jsx(Headers, { colDivided: true }), _jsx(TableBody, { children: _jsx(Rows, { colDivided: true }) })] }), _jsx("p", { className: "typography-subtitle4 text-text-g-contrast-high", children: "Striped \u2014 alternating bg, no row stroke, no column dividers" }), _jsxs(Table, { bordered: true, children: [_jsx(Headers, {}), _jsx(TableBody, { striped: true, children: _jsx(Rows, { divided: false }) })] }), _jsx("p", { className: "typography-subtitle4 text-text-g-contrast-high", children: "Striped + col divided \u2014 alternating bg, no row stroke, column dividers (primary style)" }), _jsxs(Table, { bordered: true, children: [_jsx(Headers, { colDivided: true }), _jsx(TableBody, { striped: true, children: _jsx(Rows, { divided: false, colDivided: true }) })] }), _jsx("p", { className: "typography-subtitle4 text-text-g-contrast-high", children: "Selected row (index 2) \u2014 transparent-primary-12 overlay" }), _jsxs(Table, { bordered: true, children: [_jsx(Headers, { colDivided: true }), _jsx(TableBody, { striped: true, children: _jsx(Rows, { divided: false, colDivided: true, selectedIndex: 2 }) })] }), _jsx("p", { className: "typography-subtitle4 text-text-g-contrast-high", children: "With footer \u2014 summary row in tfoot" }), _jsxs(Table, { bordered: true, children: [_jsx(Headers, { colDivided: true }), _jsx(TableBody, { children: _jsx(Rows, { colDivided: true }) }), _jsx(TableFooter, { children: _jsxs(TableRow, { divided: false, colDivided: true, children: [_jsx(TableCell, { className: "font-medium", children: "5 invoices" }), _jsx(TableCell, {}), _jsx(TableCell, {}), _jsx(TableCell, { className: "text-right font-medium", children: "$1,290.00" })] }) })] })] })),
90
+ };
91
+ // ── Scroll ────────────────────────────────────────────────────────────────────
92
+ const scrollCols = [
93
+ "ID",
94
+ "Column name",
95
+ "Category",
96
+ "Data type",
97
+ "Format",
98
+ "Source",
99
+ "Default value",
100
+ "Required",
101
+ "Nullable",
102
+ "Min length",
103
+ "Max length",
104
+ "Pattern",
105
+ "Unit",
106
+ "Precision",
107
+ "Encoding",
108
+ "Description",
109
+ ];
110
+ const scrollRows = Array.from({ length: 40 }, (_, i) => [
111
+ `COL-${String(i + 1).padStart(3, "0")}`,
112
+ `column_field_${i + 1}`,
113
+ ["Identifier", "Asset", "Timestamp", "Status", "User", "Metric", "Tag"][i % 7],
114
+ ["String", "Number", "Date", "Boolean", "Enum", "Array", "Object"][i % 7],
115
+ ["UUID", "Free text", "ISO 8601", "true/false", "1–5", "JSON", "Base64"][i % 7],
116
+ ["System", "Manual", "Device", "Auth", "Sensor", "Import", "Computed"][i % 7],
117
+ ["-", "auto", "now()", "true", "0", "[]", "{}"][i % 7],
118
+ i % 3 === 0 ? "Yes" : "No",
119
+ i % 2 === 0 ? "Yes" : "No",
120
+ ["-", "1", "3", "8", "16", "36", "64"][i % 7],
121
+ ["255", "128", "1024", "36", "64", "512", "-"][i % 7],
122
+ ["-", "^[A-Z]+$", "^\\d+$", ".*", "^[a-z_]+$", "-", "-"][i % 7],
123
+ ["m", "kg", "°C", "-", "%", "px", "ms"][i % 7],
124
+ ["-", "2", "4", "6", "0", "3", "8"][i % 7],
125
+ ["UTF-8", "ASCII", "Base64", "UTF-16", "-", "UTF-8", "UTF-8"][i % 7],
126
+ `Description text for column ${i + 1}`,
127
+ ]);
128
+ /**
129
+ * WITH SCROLL (Figma 9637-10817)
130
+ *
131
+ * Demonstrates both axes of overflow:
132
+ * - Vertical: fixed-height container → body rows overflow and scroll vertically
133
+ * - Horizontal: 10 columns wider than the viewport → native h-scroll bar appears
134
+ *
135
+ * Implementation notes:
136
+ * - Outer div sets the fixed height and owns the rounded border
137
+ * (TablePagination must sit outside the scrollable Table)
138
+ * - Table gets rootClassName="flex-1 min-h-0" so it grows to fill flex space
139
+ * and allows overflow-auto to kick in
140
+ * - TableHeader gets className="sticky top-0 z-10" for a frozen header row
141
+ */
142
+ export const WithScroll = {
143
+ render: () => {
144
+ const [pageIndex, setPageIndex] = React.useState(0);
145
+ const [pageSize, setPageSize] = React.useState(5);
146
+ const totalCount = scrollRows.length;
147
+ const paged = scrollRows.slice(pageIndex * pageSize, (pageIndex + 1) * pageSize);
148
+ return (
149
+ // Outer wrapper: fixed height + border owns the rounded corners.
150
+ // TablePagination lives here as a sibling (outside the scroll area).
151
+ _jsxs("div", { className: "flex flex-col h-[320px] w-full", children: [_jsxs(Table, { rootClassName: "flex-1 min-h-0", children: [_jsx(TableHeader, { className: "sticky top-0 z-10", children: _jsx(TableRow, { divided: false, colDivided: true, children: scrollCols.map((col) => (_jsx(TableHead, { className: "whitespace-nowrap", children: col }, col))) }) }), _jsx(TableBody, { striped: true, children: paged.map((cells, i) => (_jsx(TableRow, { divided: false, colDivided: true, children: cells.map((cell, j) => (_jsx(TableCell, { className: "whitespace-nowrap", children: cell }, j))) }, i))) })] }), _jsx(TablePagination, { pageIndex: pageIndex, pageSize: pageSize, totalCount: totalCount, pageSizeOptions: [5, 10, 20], onPageChange: setPageIndex, onPageSizeChange: (s) => {
152
+ setPageSize(s);
153
+ setPageIndex(0);
154
+ } })] }));
155
+ },
156
+ };
157
+ /**
158
+ * SCROLLBAR SIZES — per-axis demo
159
+ *
160
+ * Combine ui-scrollbar-x-{size} + ui-scrollbar-y-{size} independently.
161
+ * CSS: height → horizontal bar thickness | width → vertical bar thickness
162
+ */
163
+ export const ScrollbarSizes = {
164
+ render: () => {
165
+ const makeTable = (label, xClass, yClass) => (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("p", { className: "typography-subtitle4 text-text-g-contrast-high", children: label }), _jsx("div", { className: "rounded-xl overflow-hidden border border-table-bg-line h-[180px] flex flex-col", children: _jsxs(Table, { rootClassName: `flex-1 min-h-0 ui-scrollbar ${xClass} ${yClass}`, children: [_jsx(TableHeader, { className: "sticky top-0 z-10", children: _jsx(TableRow, { divided: false, colDivided: true, children: scrollCols.map((c) => (_jsx(TableHead, { className: "whitespace-nowrap", children: c }, c))) }) }), _jsx(TableBody, { striped: true, children: scrollRows.slice(0, 12).map((cells, i) => (_jsx(TableRow, { divided: false, colDivided: true, children: cells.map((cell, j) => (_jsx(TableCell, { className: "whitespace-nowrap", children: cell }, j))) }, i))) })] }) })] }, label));
166
+ return (_jsxs("div", { className: "flex flex-col gap-8 w-full", children: [makeTable("X = M (12px) + Y = S (6px) ← Table default", "ui-scrollbar-x-m", "ui-scrollbar-y-s"), makeTable("X = S (6px) + Y = M (12px)", "ui-scrollbar-x-s", "ui-scrollbar-y-m"), makeTable("X = M (12px) + Y = M (12px)", "ui-scrollbar-x-m", "ui-scrollbar-y-m"), makeTable("X = S (6px) + Y = S (6px)", "ui-scrollbar-x-s", "ui-scrollbar-y-s"), makeTable("X = XS (2px) + Y = XS (2px)", "ui-scrollbar-x-xs", "ui-scrollbar-y-xs"), makeTable("X = M (12px) + Y = XS (2px)", "ui-scrollbar-x-m", "ui-scrollbar-y-xs")] }));
20
167
  },
21
168
  };
169
+ // ── Panel (on Modal / Drawer / Panel surface) ─────────────────────────────────
170
+ //
171
+ // The Panel wrapper carries data-surface="panel" which overrides --table-c-*
172
+ // tokens via CSS cascade — no variant prop on Table or TablePagination needed.
173
+ // Every story below mirrors its non-panel counterpart exactly.
174
+ /** Simulates a Modal / Drawer / Panel surface wrapper.
175
+ * data-surface="panel" triggers the CSS token overrides in table.css. */
176
+ const PanelDecorator = ({ children }) => (_jsxs("div", { "data-surface": "panel", className: " bg-modal-surface p-6 w-full max-w-3xl mx-auto", children: [_jsx("p", { className: "typography-subtitle4 text-text-g-contrast-medium mb-4", children: "Modal / Panel surface" }), children] }));
177
+ /** PANEL DEFAULT — transparent rows + panel-sub-line row dividers */
178
+ export const PanelDefault = {
179
+ render: () => (_jsx(PanelDecorator, { children: _jsxs(Table, { children: [_jsx(Headers, {}), _jsx(TableBody, { children: _jsx(Rows, {}) })] }) })),
180
+ };
181
+ /** PANEL NON-STRIPED DIVIDED — row dividers + vertical column dividers */
182
+ export const PanelNonStripedDivided = {
183
+ render: () => (_jsx(PanelDecorator, { children: _jsxs(Table, { children: [_jsx(Headers, { colDivided: true }), _jsx(TableBody, { children: _jsx(Rows, { colDivided: true }) })] }) })),
184
+ };
185
+ /** PANEL STRIPED — alternating rows (both transparent in panel mode), no row strokes */
186
+ export const PanelStriped = {
187
+ render: () => (_jsx(PanelDecorator, { children: _jsxs(Table, { bordered: true, children: [_jsx(Headers, {}), _jsx(TableBody, { striped: true, children: _jsx(Rows, { divided: false }) })] }) })),
188
+ };
189
+ /** PANEL STRIPED + COL DIVIDED */
190
+ export const PanelStripedAndDivided = {
191
+ render: () => (_jsx(PanelDecorator, { children: _jsxs(Table, { bordered: true, children: [_jsx(Headers, { colDivided: true }), _jsx(TableBody, { striped: true, children: _jsx(Rows, { divided: false, colDivided: true }) })] }) })),
192
+ };
193
+ /** PANEL SELECTED ROW — table-panel-selected (yellow/olive) */
194
+ export const PanelWithSelectedRow = {
195
+ render: () => (_jsx(PanelDecorator, { children: _jsxs(Table, { bordered: true, children: [_jsx(Headers, { colDivided: true }), _jsx(TableBody, { striped: true, children: _jsx(Rows, { divided: false, colDivided: true, selectedIndex: 1 }) })] }) })),
196
+ };
197
+ /** PANEL WITH FOOTER */
198
+ export const PanelWithFooter = {
199
+ render: () => (_jsx(PanelDecorator, { children: _jsxs(Table, { bordered: true, children: [_jsx(Headers, { colDivided: true }), _jsx(TableBody, { children: _jsx(Rows, { colDivided: true }) }), _jsx(TableFooter, { children: _jsxs(TableRow, { divided: false, colDivided: true, children: [_jsx(TableCell, { className: "font-medium", children: "5 invoices" }), _jsx(TableCell, {}), _jsx(TableCell, {}), _jsx(TableCell, { className: "text-right font-medium", children: "$1,290.00" })] }) })] }) })),
200
+ };
201
+ /**
202
+ * PANEL WITH PAGINATION
203
+ * TablePagination sits inside the same data-surface="panel" scope so it
204
+ * automatically picks up the panel tokens — no variant prop needed.
205
+ */
206
+ export const PanelWithPagination = {
207
+ render: () => {
208
+ const [pageIndex, setPageIndex] = React.useState(0);
209
+ const [pageSize, setPageSize] = React.useState(3);
210
+ const paged = rows.slice(pageIndex * pageSize, (pageIndex + 1) * pageSize);
211
+ return (_jsx(PanelDecorator, { children: _jsxs("div", { className: "rounded-lg overflow-hidden border border-table-c-border w-full", children: [_jsxs(Table, { children: [_jsx(Headers, { colDivided: true }), _jsx(TableBody, { children: paged.map((r) => (_jsxs(TableRow, { divided: true, colDivided: true, children: [_jsx(TableCell, { className: "font-medium", children: r.id }), _jsx(TableCell, { children: r.status }), _jsx(TableCell, { children: r.method }), _jsx(TableCell, { className: "text-right", children: r.amount })] }, r.id))) })] }), _jsx(TablePagination, { pageIndex: pageIndex, pageSize: pageSize, totalCount: rows.length, pageSizeOptions: [2, 3, 5], onPageChange: setPageIndex, onPageSizeChange: (s) => {
212
+ setPageSize(s);
213
+ setPageIndex(0);
214
+ } })] }) }));
215
+ },
216
+ };
217
+ /** PANEL EMPTY STATE */
218
+ export const PanelEmpty = {
219
+ render: () => (_jsx(PanelDecorator, { children: _jsxs(Table, { bordered: true, children: [_jsx(Headers, { colDivided: true }), _jsx(TableBody, { children: _jsx(TableRow, { divided: false, className: "hover:bg-transparent", children: _jsx(TableCell, { colSpan: 4, className: "h-32 text-center text-text-g-contrast-medium", children: "No data found." }) }) })] }) })),
220
+ };
221
+ /**
222
+ * PANEL WITH SCROLL — sticky header + both scroll axes inside a fixed-height panel.
223
+ */
224
+ export const PanelWithScroll = {
225
+ render: () => {
226
+ const [pageIndex, setPageIndex] = React.useState(0);
227
+ const [pageSize, setPageSize] = React.useState(5);
228
+ const paged = scrollRows.slice(pageIndex * pageSize, (pageIndex + 1) * pageSize);
229
+ return (_jsx(PanelDecorator, { children: _jsxs("div", { className: "flex flex-col h-[280px] w-full rounded-lg overflow-hidden border border-table-c-border", children: [_jsxs(Table, { rootClassName: "flex-1 min-h-0", children: [_jsx(TableHeader, { className: "sticky top-0 z-10", children: _jsx(TableRow, { divided: false, colDivided: true, children: scrollCols.map((col) => (_jsx(TableHead, { className: "whitespace-nowrap", children: col }, col))) }) }), _jsx(TableBody, { children: paged.map((cells, i) => (_jsx(TableRow, { divided: true, colDivided: true, children: cells.map((cell, j) => (_jsx(TableCell, { className: "whitespace-nowrap", children: cell }, j))) }, i))) })] }), _jsx(TablePagination, { pageIndex: pageIndex, pageSize: pageSize, totalCount: scrollRows.length, pageSizeOptions: [5, 10, 20], onPageChange: setPageIndex, onPageSizeChange: (s) => {
230
+ setPageSize(s);
231
+ setPageIndex(0);
232
+ } })] }) }));
233
+ },
234
+ };
235
+ /** PANEL ALL STATES — all variants side-by-side for design review */
236
+ export const PanelAllStates = {
237
+ render: () => (_jsxs("div", { className: "flex flex-col gap-6 w-full", children: [_jsx("p", { className: "typography-subtitle4 text-text-g-contrast-high", children: "Non-striped \u2014 row stroke only" }), _jsx(PanelDecorator, { children: _jsxs(Table, { bordered: true, children: [_jsx(Headers, {}), _jsx(TableBody, { children: _jsx(Rows, {}) })] }) }), _jsx("p", { className: "typography-subtitle4 text-text-g-contrast-high", children: "Non-striped + col divided" }), _jsx(PanelDecorator, { children: _jsxs(Table, { bordered: true, children: [_jsx(Headers, { colDivided: true }), _jsx(TableBody, { children: _jsx(Rows, { colDivided: true }) })] }) }), _jsx("p", { className: "typography-subtitle4 text-text-g-contrast-high", children: "Striped \u2014 alternating bg, no row stroke" }), _jsx(PanelDecorator, { children: _jsxs(Table, { bordered: true, children: [_jsx(Headers, {}), _jsx(TableBody, { striped: true, children: _jsx(Rows, { divided: false }) })] }) }), _jsx("p", { className: "typography-subtitle4 text-text-g-contrast-high", children: "Striped + col divided (primary style)" }), _jsx(PanelDecorator, { children: _jsxs(Table, { bordered: true, children: [_jsx(Headers, { colDivided: true }), _jsx(TableBody, { striped: true, children: _jsx(Rows, { divided: false, colDivided: true }) })] }) }), _jsx("p", { className: "typography-subtitle4 text-text-g-contrast-high", children: "Selected row (index 2) \u2014 table-panel-selected overlay" }), _jsx(PanelDecorator, { children: _jsxs(Table, { bordered: true, children: [_jsx(Headers, { colDivided: true }), _jsx(TableBody, { striped: true, children: _jsx(Rows, { divided: false, colDivided: true, selectedIndex: 2 }) })] }) }), _jsx("p", { className: "typography-subtitle4 text-text-g-contrast-high", children: "With footer" }), _jsx(PanelDecorator, { children: _jsxs(Table, { bordered: true, children: [_jsx(Headers, { colDivided: true }), _jsx(TableBody, { children: _jsx(Rows, { colDivided: true }) }), _jsx(TableFooter, { children: _jsxs(TableRow, { divided: false, colDivided: true, children: [_jsx(TableCell, { className: "font-medium", children: "5 invoices" }), _jsx(TableCell, {}), _jsx(TableCell, {}), _jsx(TableCell, { className: "text-right font-medium", children: "$1,290.00" })] }) })] }) })] })),
238
+ };