@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.
- package/dist/cjs/bundle.css +513 -67
- package/dist/cjs/bundle.js +589 -589
- package/dist/cjs/bundle.js.map +1 -1
- package/dist/cjs/types/components/Avatar/Avatar.d.ts +1 -1
- package/dist/cjs/types/components/Avatar/Avatar.stories.d.ts +1 -1
- package/dist/cjs/types/components/Avatar/Avatar.styles.d.ts +1 -0
- package/dist/cjs/types/components/DataTable/DataTable.d.ts +195 -4
- package/dist/cjs/types/components/DataTable/DataTable.editing.d.ts +20 -0
- package/dist/cjs/types/components/DataTable/DataTable.editing.types.d.ts +145 -0
- package/dist/cjs/types/components/DataTable/DataTable.stories.d.ts +268 -6
- package/dist/cjs/types/components/Dropdown/Dropdown.d.ts +22 -0
- package/dist/cjs/types/components/Dropdown/Dropdown.stories.d.ts +4 -0
- package/dist/cjs/types/components/ScrollArea/ScrollArea.d.ts +3 -3
- package/dist/cjs/types/components/ScrollArea/ScrollArea.stories.d.ts +4 -0
- package/dist/cjs/types/components/Table/Table.d.ts +33 -3
- package/dist/cjs/types/components/Table/Table.stories.d.ts +86 -4
- package/dist/cjs/types/components/TextInput/TextInput.stories.d.ts +8 -0
- package/dist/cjs/types/components/TextInput/TextInput.styles.d.ts +1 -0
- package/dist/components/Avatar/Avatar.js +2 -1
- package/dist/components/Avatar/Avatar.styles.js +3 -0
- package/dist/components/Avatar/AvatarBase.js +1 -1
- package/dist/components/DataTable/DataTable.editing.js +385 -0
- package/dist/components/DataTable/DataTable.editing.types.js +1 -0
- package/dist/components/DataTable/DataTable.js +983 -50
- package/dist/components/DataTable/DataTable.stories.js +1077 -25
- package/dist/components/Dropdown/Dropdown.js +8 -6
- package/dist/components/ScrollArea/ScrollArea.js +2 -2
- package/dist/components/ScrollArea/ScrollArea.stories.js +68 -2
- package/dist/components/Table/Table.js +103 -13
- package/dist/components/Table/Table.stories.js +226 -9
- package/dist/components/TextInput/TextInput.js +6 -4
- package/dist/components/TextInput/TextInput.stories.js +8 -0
- package/dist/components/TextInput/TextInput.styles.js +7 -1
- package/dist/esm/bundle.css +513 -67
- package/dist/esm/bundle.js +1545 -1545
- package/dist/esm/bundle.js.map +1 -1
- package/dist/esm/types/components/Avatar/Avatar.d.ts +1 -1
- package/dist/esm/types/components/Avatar/Avatar.stories.d.ts +1 -1
- package/dist/esm/types/components/Avatar/Avatar.styles.d.ts +1 -0
- package/dist/esm/types/components/DataTable/DataTable.d.ts +195 -4
- package/dist/esm/types/components/DataTable/DataTable.editing.d.ts +20 -0
- package/dist/esm/types/components/DataTable/DataTable.editing.types.d.ts +145 -0
- package/dist/esm/types/components/DataTable/DataTable.stories.d.ts +268 -6
- package/dist/esm/types/components/Dropdown/Dropdown.d.ts +22 -0
- package/dist/esm/types/components/Dropdown/Dropdown.stories.d.ts +4 -0
- package/dist/esm/types/components/ScrollArea/ScrollArea.d.ts +3 -3
- package/dist/esm/types/components/ScrollArea/ScrollArea.stories.d.ts +4 -0
- package/dist/esm/types/components/Table/Table.d.ts +33 -3
- package/dist/esm/types/components/Table/Table.stories.d.ts +86 -4
- package/dist/esm/types/components/TextInput/TextInput.stories.d.ts +8 -0
- package/dist/esm/types/components/TextInput/TextInput.styles.d.ts +1 -0
- package/dist/index.d.ts +493 -122
- package/dist/src/theme/global.css +762 -96
- package/package.json +14 -2
- package/src/components/Avatar/Avatar.styles.ts +4 -1
- package/src/components/Avatar/Avatar.tsx +3 -2
- package/src/components/Avatar/AvatarBase.tsx +3 -3
- package/src/components/DataTable/DataTable.editing.tsx +861 -0
- package/src/components/DataTable/DataTable.editing.types.ts +192 -0
- package/src/components/DataTable/DataTable.stories.tsx +2169 -31
- package/src/components/DataTable/DataTable.test.tsx +696 -0
- package/src/components/DataTable/DataTable.tsx +2260 -94
- package/src/components/Dropdown/Dropdown.tsx +22 -6
- package/src/components/ScrollArea/ScrollArea.stories.tsx +146 -3
- package/src/components/ScrollArea/ScrollArea.tsx +6 -6
- package/src/components/Table/Table.stories.tsx +789 -44
- package/src/components/Table/Table.tsx +294 -28
- package/src/components/TextInput/TextInput.stories.tsx +80 -0
- package/src/components/TextInput/TextInput.styles.ts +7 -1
- package/src/components/TextInput/TextInput.tsx +21 -14
- package/src/test/setup.ts +50 -0
- package/src/theme/global.css +81 -42
- package/src/theme/presets/colors.js +12 -0
- package/src/theme/themes/variable.css +27 -28
- package/src/theme/tokens/baseline.css +2 -1
- package/src/theme/tokens/components/scrollbar.css +9 -4
- 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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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,
|
|
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
|
|
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 = "
|
|
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-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
|
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("
|
|
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("
|
|
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-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
+
};
|