@rovula/ui 0.1.28 → 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 +501 -67
- package/dist/cjs/bundle.js +589 -589
- package/dist/cjs/bundle.js.map +1 -1
- 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/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 +501 -67
- package/dist/esm/bundle.js +1545 -1545
- package/dist/esm/bundle.js.map +1 -1
- 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 +747 -96
- package/package.json +14 -2
- 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
|
@@ -3,10 +3,11 @@ import type { Meta, StoryObj } from "@storybook/react";
|
|
|
3
3
|
import {
|
|
4
4
|
Table,
|
|
5
5
|
TableBody,
|
|
6
|
-
TableCaption,
|
|
7
6
|
TableCell,
|
|
7
|
+
TableFooter,
|
|
8
8
|
TableHead,
|
|
9
9
|
TableHeader,
|
|
10
|
+
TablePagination,
|
|
10
11
|
TableRow,
|
|
11
12
|
} from "./Table";
|
|
12
13
|
|
|
@@ -14,12 +15,17 @@ const meta = {
|
|
|
14
15
|
title: "Components/Table",
|
|
15
16
|
component: Table,
|
|
16
17
|
tags: ["autodocs"],
|
|
17
|
-
parameters: {
|
|
18
|
-
|
|
18
|
+
parameters: { layout: "fullscreen" },
|
|
19
|
+
argTypes: {
|
|
20
|
+
bordered: {
|
|
21
|
+
control: "boolean",
|
|
22
|
+
description:
|
|
23
|
+
"Outer `rounded-md` + `border-table-c-border`, inner scroll (`Table.tsx`).",
|
|
24
|
+
},
|
|
19
25
|
},
|
|
20
26
|
decorators: [
|
|
21
27
|
(Story) => (
|
|
22
|
-
<div className="p-
|
|
28
|
+
<div className="p-8 flex flex-col gap-10 bg-page-bg-main min-h-screen w-full">
|
|
23
29
|
<Story />
|
|
24
30
|
</div>
|
|
25
31
|
),
|
|
@@ -28,52 +34,791 @@ const meta = {
|
|
|
28
34
|
|
|
29
35
|
export default meta;
|
|
30
36
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
const rows = [
|
|
38
|
+
{ id: "INV001", status: "Paid", method: "Credit Card", amount: "$250.00" },
|
|
39
|
+
{ id: "INV002", status: "Pending", method: "PayPal", amount: "$150.00" },
|
|
40
|
+
{ id: "INV003", status: "Paid", method: "Bank Transfer", amount: "$350.00" },
|
|
41
|
+
{
|
|
42
|
+
id: "INV004",
|
|
43
|
+
status: "Cancelled",
|
|
44
|
+
method: "Credit Card",
|
|
45
|
+
amount: "$450.00",
|
|
46
|
+
},
|
|
47
|
+
{ id: "INV005", status: "Pending", method: "PayPal", amount: "$90.00" },
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
// ── Shared helpers ────────────────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
const Headers = ({ colDivided = false }: { colDivided?: boolean }) => (
|
|
53
|
+
<TableHeader>
|
|
54
|
+
{/* divided={false}: TableHeader already applies border-b via [&_tr>th]:border-b */}
|
|
55
|
+
<TableRow divided={false} colDivided={colDivided}>
|
|
56
|
+
<TableHead className="w-[120px]">Invoice</TableHead>
|
|
57
|
+
<TableHead>Status</TableHead>
|
|
58
|
+
<TableHead>Method</TableHead>
|
|
59
|
+
<TableHead className="text-right">Amount</TableHead>
|
|
60
|
+
</TableRow>
|
|
61
|
+
</TableHeader>
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const Rows = ({
|
|
65
|
+
divided = true,
|
|
66
|
+
colDivided = false,
|
|
67
|
+
selectedIndex,
|
|
68
|
+
}: {
|
|
69
|
+
divided?: boolean;
|
|
70
|
+
colDivided?: boolean;
|
|
71
|
+
selectedIndex?: number;
|
|
72
|
+
}) => (
|
|
73
|
+
<>
|
|
74
|
+
{rows.map((r, i) => (
|
|
75
|
+
<TableRow
|
|
76
|
+
key={r.id}
|
|
77
|
+
divided={divided}
|
|
78
|
+
colDivided={colDivided}
|
|
79
|
+
data-state={i === selectedIndex ? "selected" : undefined}
|
|
80
|
+
>
|
|
81
|
+
<TableCell className="font-medium">{r.id}</TableCell>
|
|
82
|
+
<TableCell>{r.status}</TableCell>
|
|
83
|
+
<TableCell>{r.method}</TableCell>
|
|
84
|
+
<TableCell className="text-right">{r.amount}</TableCell>
|
|
85
|
+
</TableRow>
|
|
86
|
+
))}
|
|
87
|
+
</>
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
// ── Stories ───────────────────────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
/** NON-STRIPED — horizontal row strokes, no column dividers */
|
|
93
|
+
export const Default: StoryObj = {
|
|
94
|
+
render: () => (
|
|
95
|
+
<Table>
|
|
96
|
+
<Headers />
|
|
97
|
+
<TableBody>
|
|
98
|
+
<Rows />
|
|
99
|
+
</TableBody>
|
|
100
|
+
</Table>
|
|
101
|
+
),
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/** NON-STRIPED + COL DIVIDED — horizontal row strokes + vertical column dividers */
|
|
105
|
+
export const NonStripedDivided: StoryObj = {
|
|
106
|
+
render: () => (
|
|
107
|
+
<Table bordered>
|
|
108
|
+
<Headers colDivided />
|
|
109
|
+
<TableBody>
|
|
110
|
+
<Rows colDivided />
|
|
111
|
+
</TableBody>
|
|
112
|
+
</Table>
|
|
113
|
+
),
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/** STRIPED — alternating bg-a / bg-b, NO row strokes, NO column dividers */
|
|
117
|
+
export const Striped: StoryObj = {
|
|
118
|
+
render: () => (
|
|
119
|
+
<Table bordered>
|
|
120
|
+
<Headers />
|
|
121
|
+
<TableBody striped>
|
|
122
|
+
<Rows divided={false} />
|
|
123
|
+
</TableBody>
|
|
124
|
+
</Table>
|
|
125
|
+
),
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* STRIPED + COL DIVIDED — alternating bg, NO row strokes, WITH column dividers.
|
|
130
|
+
* Primary Xspector table style.
|
|
131
|
+
*/
|
|
132
|
+
export const StripedAndDivided: StoryObj = {
|
|
133
|
+
render: () => (
|
|
134
|
+
<Table bordered>
|
|
135
|
+
<Headers colDivided />
|
|
136
|
+
<TableBody striped>
|
|
137
|
+
<Rows divided={false} colDivided />
|
|
138
|
+
</TableBody>
|
|
139
|
+
</Table>
|
|
140
|
+
),
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
/** SELECTED ROW — data-[state=selected] applies transparent-primary-12. Row 1 pre-selected. */
|
|
144
|
+
export const WithSelectedRow: StoryObj = {
|
|
145
|
+
render: () => (
|
|
146
|
+
<Table bordered>
|
|
147
|
+
<Headers colDivided />
|
|
148
|
+
<TableBody striped>
|
|
149
|
+
<Rows divided={false} colDivided selectedIndex={1} />
|
|
150
|
+
</TableBody>
|
|
151
|
+
</Table>
|
|
152
|
+
),
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* WITH FOOTER — tfoot summary row with bg-table-bg-main + top border.
|
|
157
|
+
* TableFooter is inside Table so bordered works naturally.
|
|
158
|
+
*/
|
|
159
|
+
export const WithFooter: StoryObj = {
|
|
160
|
+
render: () => (
|
|
161
|
+
<Table bordered>
|
|
162
|
+
<Headers colDivided />
|
|
163
|
+
<TableBody>
|
|
164
|
+
<Rows colDivided />
|
|
165
|
+
</TableBody>
|
|
166
|
+
<TableFooter>
|
|
167
|
+
<TableRow divided={false} colDivided>
|
|
168
|
+
<TableCell className="font-medium">5 invoices</TableCell>
|
|
169
|
+
<TableCell />
|
|
170
|
+
<TableCell />
|
|
171
|
+
<TableCell className="text-right font-medium">$1,290.00</TableCell>
|
|
172
|
+
</TableRow>
|
|
173
|
+
</TableFooter>
|
|
174
|
+
</Table>
|
|
175
|
+
),
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* WITH PAGINATION — TablePagination is a sibling of Table so needs an explicit
|
|
180
|
+
* outer wrapper (not covered by Table's bordered prop).
|
|
181
|
+
*/
|
|
182
|
+
export const WithPagination: StoryObj = {
|
|
183
|
+
render: () => {
|
|
184
|
+
const [pageIndex, setPageIndex] = React.useState(0);
|
|
185
|
+
const [pageSize, setPageSize] = React.useState(2);
|
|
186
|
+
const paged = rows.slice(pageIndex * pageSize, (pageIndex + 1) * pageSize);
|
|
37
187
|
return (
|
|
38
|
-
|
|
188
|
+
// Explicit outer wrapper — required because TablePagination is a sibling of Table
|
|
189
|
+
<div className="rounded-md overflow-hidden border border-table-c-border w-full">
|
|
39
190
|
<Table>
|
|
40
|
-
<
|
|
41
|
-
<TableHeader>
|
|
42
|
-
<TableRow>
|
|
43
|
-
<TableHead className="w-[100px]">Invoice</TableHead>
|
|
44
|
-
<TableHead>Status</TableHead>
|
|
45
|
-
<TableHead>Method</TableHead>
|
|
46
|
-
<TableHead className="text-right">Amount</TableHead>
|
|
47
|
-
</TableRow>
|
|
48
|
-
</TableHeader>
|
|
191
|
+
<Headers colDivided />
|
|
49
192
|
<TableBody>
|
|
50
|
-
|
|
51
|
-
<
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
193
|
+
{paged.map((r) => (
|
|
194
|
+
<TableRow key={r.id} divided={true} colDivided>
|
|
195
|
+
<TableCell className="font-medium">{r.id}</TableCell>
|
|
196
|
+
<TableCell>{r.status}</TableCell>
|
|
197
|
+
<TableCell>{r.method}</TableCell>
|
|
198
|
+
<TableCell className="text-right">{r.amount}</TableCell>
|
|
199
|
+
</TableRow>
|
|
200
|
+
))}
|
|
201
|
+
</TableBody>
|
|
202
|
+
</Table>
|
|
203
|
+
<TablePagination
|
|
204
|
+
pageIndex={pageIndex}
|
|
205
|
+
pageSize={pageSize}
|
|
206
|
+
totalCount={rows.length}
|
|
207
|
+
pageSizeOptions={[2, 3, 5]}
|
|
208
|
+
onPageChange={setPageIndex}
|
|
209
|
+
onPageSizeChange={(s) => {
|
|
210
|
+
setPageSize(s);
|
|
211
|
+
setPageIndex(0);
|
|
212
|
+
}}
|
|
213
|
+
/>
|
|
214
|
+
</div>
|
|
215
|
+
);
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
/** EMPTY STATE — single full-width row with centered message */
|
|
220
|
+
export const Empty: StoryObj = {
|
|
221
|
+
render: () => (
|
|
222
|
+
<Table bordered>
|
|
223
|
+
<Headers colDivided />
|
|
224
|
+
<TableBody>
|
|
225
|
+
<TableRow divided={false} className="hover:bg-transparent">
|
|
226
|
+
<TableCell
|
|
227
|
+
colSpan={4}
|
|
228
|
+
className="h-32 text-center text-text-g-contrast-medium"
|
|
229
|
+
>
|
|
230
|
+
No data found.
|
|
231
|
+
</TableCell>
|
|
232
|
+
</TableRow>
|
|
233
|
+
</TableBody>
|
|
234
|
+
</Table>
|
|
235
|
+
),
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
/** ALL STATES SIDE-BY-SIDE — reference sheet for design review */
|
|
239
|
+
export const AllStates: StoryObj = {
|
|
240
|
+
render: () => (
|
|
241
|
+
<div className="flex flex-col gap-6 w-full">
|
|
242
|
+
<p className="typography-subtitle4 text-text-g-contrast-high">
|
|
243
|
+
Non-striped — row stroke only
|
|
244
|
+
</p>
|
|
245
|
+
<Table bordered>
|
|
246
|
+
<Headers />
|
|
247
|
+
<TableBody>
|
|
248
|
+
<Rows />
|
|
249
|
+
</TableBody>
|
|
250
|
+
</Table>
|
|
251
|
+
|
|
252
|
+
<p className="typography-subtitle4 text-text-g-contrast-high">
|
|
253
|
+
Non-striped + col divided — row stroke + column dividers
|
|
254
|
+
</p>
|
|
255
|
+
<Table bordered>
|
|
256
|
+
<Headers colDivided />
|
|
257
|
+
<TableBody>
|
|
258
|
+
<Rows colDivided />
|
|
259
|
+
</TableBody>
|
|
260
|
+
</Table>
|
|
261
|
+
|
|
262
|
+
<p className="typography-subtitle4 text-text-g-contrast-high">
|
|
263
|
+
Striped — alternating bg, no row stroke, no column dividers
|
|
264
|
+
</p>
|
|
265
|
+
<Table bordered>
|
|
266
|
+
<Headers />
|
|
267
|
+
<TableBody striped>
|
|
268
|
+
<Rows divided={false} />
|
|
269
|
+
</TableBody>
|
|
270
|
+
</Table>
|
|
271
|
+
|
|
272
|
+
<p className="typography-subtitle4 text-text-g-contrast-high">
|
|
273
|
+
Striped + col divided — alternating bg, no row stroke, column dividers
|
|
274
|
+
(primary style)
|
|
275
|
+
</p>
|
|
276
|
+
<Table bordered>
|
|
277
|
+
<Headers colDivided />
|
|
278
|
+
<TableBody striped>
|
|
279
|
+
<Rows divided={false} colDivided />
|
|
280
|
+
</TableBody>
|
|
281
|
+
</Table>
|
|
282
|
+
|
|
283
|
+
<p className="typography-subtitle4 text-text-g-contrast-high">
|
|
284
|
+
Selected row (index 2) — transparent-primary-12 overlay
|
|
285
|
+
</p>
|
|
286
|
+
<Table bordered>
|
|
287
|
+
<Headers colDivided />
|
|
288
|
+
<TableBody striped>
|
|
289
|
+
<Rows divided={false} colDivided selectedIndex={2} />
|
|
290
|
+
</TableBody>
|
|
291
|
+
</Table>
|
|
292
|
+
|
|
293
|
+
<p className="typography-subtitle4 text-text-g-contrast-high">
|
|
294
|
+
With footer — summary row in tfoot
|
|
295
|
+
</p>
|
|
296
|
+
<Table bordered>
|
|
297
|
+
<Headers colDivided />
|
|
298
|
+
<TableBody>
|
|
299
|
+
<Rows colDivided />
|
|
300
|
+
</TableBody>
|
|
301
|
+
<TableFooter>
|
|
302
|
+
<TableRow divided={false} colDivided>
|
|
303
|
+
<TableCell className="font-medium">5 invoices</TableCell>
|
|
304
|
+
<TableCell />
|
|
305
|
+
<TableCell />
|
|
306
|
+
<TableCell className="text-right font-medium">$1,290.00</TableCell>
|
|
307
|
+
</TableRow>
|
|
308
|
+
</TableFooter>
|
|
309
|
+
</Table>
|
|
310
|
+
</div>
|
|
311
|
+
),
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
// ── Scroll ────────────────────────────────────────────────────────────────────
|
|
315
|
+
|
|
316
|
+
const scrollCols = [
|
|
317
|
+
"ID",
|
|
318
|
+
"Column name",
|
|
319
|
+
"Category",
|
|
320
|
+
"Data type",
|
|
321
|
+
"Format",
|
|
322
|
+
"Source",
|
|
323
|
+
"Default value",
|
|
324
|
+
"Required",
|
|
325
|
+
"Nullable",
|
|
326
|
+
"Min length",
|
|
327
|
+
"Max length",
|
|
328
|
+
"Pattern",
|
|
329
|
+
"Unit",
|
|
330
|
+
"Precision",
|
|
331
|
+
"Encoding",
|
|
332
|
+
"Description",
|
|
333
|
+
];
|
|
334
|
+
|
|
335
|
+
const scrollRows = Array.from({ length: 40 }, (_, i) => [
|
|
336
|
+
`COL-${String(i + 1).padStart(3, "0")}`,
|
|
337
|
+
`column_field_${i + 1}`,
|
|
338
|
+
["Identifier", "Asset", "Timestamp", "Status", "User", "Metric", "Tag"][
|
|
339
|
+
i % 7
|
|
340
|
+
],
|
|
341
|
+
["String", "Number", "Date", "Boolean", "Enum", "Array", "Object"][i % 7],
|
|
342
|
+
["UUID", "Free text", "ISO 8601", "true/false", "1–5", "JSON", "Base64"][
|
|
343
|
+
i % 7
|
|
344
|
+
],
|
|
345
|
+
["System", "Manual", "Device", "Auth", "Sensor", "Import", "Computed"][i % 7],
|
|
346
|
+
["-", "auto", "now()", "true", "0", "[]", "{}"][i % 7],
|
|
347
|
+
i % 3 === 0 ? "Yes" : "No",
|
|
348
|
+
i % 2 === 0 ? "Yes" : "No",
|
|
349
|
+
["-", "1", "3", "8", "16", "36", "64"][i % 7],
|
|
350
|
+
["255", "128", "1024", "36", "64", "512", "-"][i % 7],
|
|
351
|
+
["-", "^[A-Z]+$", "^\\d+$", ".*", "^[a-z_]+$", "-", "-"][i % 7],
|
|
352
|
+
["m", "kg", "°C", "-", "%", "px", "ms"][i % 7],
|
|
353
|
+
["-", "2", "4", "6", "0", "3", "8"][i % 7],
|
|
354
|
+
["UTF-8", "ASCII", "Base64", "UTF-16", "-", "UTF-8", "UTF-8"][i % 7],
|
|
355
|
+
`Description text for column ${i + 1}`,
|
|
356
|
+
]);
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* WITH SCROLL (Figma 9637-10817)
|
|
360
|
+
*
|
|
361
|
+
* Demonstrates both axes of overflow:
|
|
362
|
+
* - Vertical: fixed-height container → body rows overflow and scroll vertically
|
|
363
|
+
* - Horizontal: 10 columns wider than the viewport → native h-scroll bar appears
|
|
364
|
+
*
|
|
365
|
+
* Implementation notes:
|
|
366
|
+
* - Outer div sets the fixed height and owns the rounded border
|
|
367
|
+
* (TablePagination must sit outside the scrollable Table)
|
|
368
|
+
* - Table gets rootClassName="flex-1 min-h-0" so it grows to fill flex space
|
|
369
|
+
* and allows overflow-auto to kick in
|
|
370
|
+
* - TableHeader gets className="sticky top-0 z-10" for a frozen header row
|
|
371
|
+
*/
|
|
372
|
+
export const WithScroll: StoryObj = {
|
|
373
|
+
render: () => {
|
|
374
|
+
const [pageIndex, setPageIndex] = React.useState(0);
|
|
375
|
+
const [pageSize, setPageSize] = React.useState(5);
|
|
376
|
+
const totalCount = scrollRows.length;
|
|
377
|
+
const paged = scrollRows.slice(
|
|
378
|
+
pageIndex * pageSize,
|
|
379
|
+
(pageIndex + 1) * pageSize,
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
return (
|
|
383
|
+
// Outer wrapper: fixed height + border owns the rounded corners.
|
|
384
|
+
// TablePagination lives here as a sibling (outside the scroll area).
|
|
385
|
+
<div className="flex flex-col h-[320px] w-full">
|
|
386
|
+
{/* Table fills remaining height; overflow-auto on rootDiv handles both scroll axes */}
|
|
387
|
+
<Table rootClassName="flex-1 min-h-0">
|
|
388
|
+
{/* Sticky header — stays visible during vertical scroll */}
|
|
389
|
+
<TableHeader className="sticky top-0 z-10">
|
|
390
|
+
<TableRow divided={false} colDivided>
|
|
391
|
+
{scrollCols.map((col) => (
|
|
392
|
+
<TableHead key={col} className="whitespace-nowrap">
|
|
393
|
+
{col}
|
|
394
|
+
</TableHead>
|
|
395
|
+
))}
|
|
73
396
|
</TableRow>
|
|
397
|
+
</TableHeader>
|
|
398
|
+
|
|
399
|
+
<TableBody striped>
|
|
400
|
+
{paged.map((cells, i) => (
|
|
401
|
+
<TableRow key={i} divided={false} colDivided>
|
|
402
|
+
{cells.map((cell, j) => (
|
|
403
|
+
<TableCell key={j} className="whitespace-nowrap">
|
|
404
|
+
{cell}
|
|
405
|
+
</TableCell>
|
|
406
|
+
))}
|
|
407
|
+
</TableRow>
|
|
408
|
+
))}
|
|
74
409
|
</TableBody>
|
|
75
410
|
</Table>
|
|
411
|
+
|
|
412
|
+
{/* Pagination sits outside the scroll container — always visible */}
|
|
413
|
+
<TablePagination
|
|
414
|
+
pageIndex={pageIndex}
|
|
415
|
+
pageSize={pageSize}
|
|
416
|
+
totalCount={totalCount}
|
|
417
|
+
pageSizeOptions={[5, 10, 20]}
|
|
418
|
+
onPageChange={setPageIndex}
|
|
419
|
+
onPageSizeChange={(s) => {
|
|
420
|
+
setPageSize(s);
|
|
421
|
+
setPageIndex(0);
|
|
422
|
+
}}
|
|
423
|
+
/>
|
|
76
424
|
</div>
|
|
77
425
|
);
|
|
78
426
|
},
|
|
79
|
-
}
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* SCROLLBAR SIZES — per-axis demo
|
|
431
|
+
*
|
|
432
|
+
* Combine ui-scrollbar-x-{size} + ui-scrollbar-y-{size} independently.
|
|
433
|
+
* CSS: height → horizontal bar thickness | width → vertical bar thickness
|
|
434
|
+
*/
|
|
435
|
+
export const ScrollbarSizes: StoryObj = {
|
|
436
|
+
render: () => {
|
|
437
|
+
const makeTable = (label: string, xClass: string, yClass: string) => (
|
|
438
|
+
<div key={label} className="flex flex-col gap-2">
|
|
439
|
+
<p className="typography-subtitle4 text-text-g-contrast-high">
|
|
440
|
+
{label}
|
|
441
|
+
</p>
|
|
442
|
+
<div className="rounded-xl overflow-hidden border border-table-bg-line h-[180px] flex flex-col">
|
|
443
|
+
<Table
|
|
444
|
+
rootClassName={`flex-1 min-h-0 ui-scrollbar ${xClass} ${yClass}`}
|
|
445
|
+
>
|
|
446
|
+
<TableHeader className="sticky top-0 z-10">
|
|
447
|
+
<TableRow divided={false} colDivided>
|
|
448
|
+
{scrollCols.map((c) => (
|
|
449
|
+
<TableHead key={c} className="whitespace-nowrap">
|
|
450
|
+
{c}
|
|
451
|
+
</TableHead>
|
|
452
|
+
))}
|
|
453
|
+
</TableRow>
|
|
454
|
+
</TableHeader>
|
|
455
|
+
<TableBody striped>
|
|
456
|
+
{scrollRows.slice(0, 12).map((cells, i) => (
|
|
457
|
+
<TableRow key={i} divided={false} colDivided>
|
|
458
|
+
{cells.map((cell, j) => (
|
|
459
|
+
<TableCell key={j} className="whitespace-nowrap">
|
|
460
|
+
{cell}
|
|
461
|
+
</TableCell>
|
|
462
|
+
))}
|
|
463
|
+
</TableRow>
|
|
464
|
+
))}
|
|
465
|
+
</TableBody>
|
|
466
|
+
</Table>
|
|
467
|
+
</div>
|
|
468
|
+
</div>
|
|
469
|
+
);
|
|
470
|
+
|
|
471
|
+
return (
|
|
472
|
+
<div className="flex flex-col gap-8 w-full">
|
|
473
|
+
{makeTable(
|
|
474
|
+
"X = M (12px) + Y = S (6px) ← Table default",
|
|
475
|
+
"ui-scrollbar-x-m",
|
|
476
|
+
"ui-scrollbar-y-s",
|
|
477
|
+
)}
|
|
478
|
+
{makeTable(
|
|
479
|
+
"X = S (6px) + Y = M (12px)",
|
|
480
|
+
"ui-scrollbar-x-s",
|
|
481
|
+
"ui-scrollbar-y-m",
|
|
482
|
+
)}
|
|
483
|
+
{makeTable(
|
|
484
|
+
"X = M (12px) + Y = M (12px)",
|
|
485
|
+
"ui-scrollbar-x-m",
|
|
486
|
+
"ui-scrollbar-y-m",
|
|
487
|
+
)}
|
|
488
|
+
{makeTable(
|
|
489
|
+
"X = S (6px) + Y = S (6px)",
|
|
490
|
+
"ui-scrollbar-x-s",
|
|
491
|
+
"ui-scrollbar-y-s",
|
|
492
|
+
)}
|
|
493
|
+
{makeTable(
|
|
494
|
+
"X = XS (2px) + Y = XS (2px)",
|
|
495
|
+
"ui-scrollbar-x-xs",
|
|
496
|
+
"ui-scrollbar-y-xs",
|
|
497
|
+
)}
|
|
498
|
+
{makeTable(
|
|
499
|
+
"X = M (12px) + Y = XS (2px)",
|
|
500
|
+
"ui-scrollbar-x-m",
|
|
501
|
+
"ui-scrollbar-y-xs",
|
|
502
|
+
)}
|
|
503
|
+
</div>
|
|
504
|
+
);
|
|
505
|
+
},
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
// ── Panel (on Modal / Drawer / Panel surface) ─────────────────────────────────
|
|
509
|
+
//
|
|
510
|
+
// The Panel wrapper carries data-surface="panel" which overrides --table-c-*
|
|
511
|
+
// tokens via CSS cascade — no variant prop on Table or TablePagination needed.
|
|
512
|
+
// Every story below mirrors its non-panel counterpart exactly.
|
|
513
|
+
|
|
514
|
+
/** Simulates a Modal / Drawer / Panel surface wrapper.
|
|
515
|
+
* data-surface="panel" triggers the CSS token overrides in table.css. */
|
|
516
|
+
const PanelDecorator = ({ children }: { children: React.ReactNode }) => (
|
|
517
|
+
<div
|
|
518
|
+
data-surface="panel"
|
|
519
|
+
className=" bg-modal-surface p-6 w-full max-w-3xl mx-auto"
|
|
520
|
+
>
|
|
521
|
+
<p className="typography-subtitle4 text-text-g-contrast-medium mb-4">
|
|
522
|
+
Modal / Panel surface
|
|
523
|
+
</p>
|
|
524
|
+
{children}
|
|
525
|
+
</div>
|
|
526
|
+
);
|
|
527
|
+
|
|
528
|
+
/** PANEL DEFAULT — transparent rows + panel-sub-line row dividers */
|
|
529
|
+
export const PanelDefault: StoryObj = {
|
|
530
|
+
render: () => (
|
|
531
|
+
<PanelDecorator>
|
|
532
|
+
<Table>
|
|
533
|
+
<Headers />
|
|
534
|
+
<TableBody>
|
|
535
|
+
<Rows />
|
|
536
|
+
</TableBody>
|
|
537
|
+
</Table>
|
|
538
|
+
</PanelDecorator>
|
|
539
|
+
),
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
/** PANEL NON-STRIPED DIVIDED — row dividers + vertical column dividers */
|
|
543
|
+
export const PanelNonStripedDivided: StoryObj = {
|
|
544
|
+
render: () => (
|
|
545
|
+
<PanelDecorator>
|
|
546
|
+
<Table>
|
|
547
|
+
<Headers colDivided />
|
|
548
|
+
<TableBody>
|
|
549
|
+
<Rows colDivided />
|
|
550
|
+
</TableBody>
|
|
551
|
+
</Table>
|
|
552
|
+
</PanelDecorator>
|
|
553
|
+
),
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
/** PANEL STRIPED — alternating rows (both transparent in panel mode), no row strokes */
|
|
557
|
+
export const PanelStriped: StoryObj = {
|
|
558
|
+
render: () => (
|
|
559
|
+
<PanelDecorator>
|
|
560
|
+
<Table bordered>
|
|
561
|
+
<Headers />
|
|
562
|
+
<TableBody striped>
|
|
563
|
+
<Rows divided={false} />
|
|
564
|
+
</TableBody>
|
|
565
|
+
</Table>
|
|
566
|
+
</PanelDecorator>
|
|
567
|
+
),
|
|
568
|
+
};
|
|
569
|
+
|
|
570
|
+
/** PANEL STRIPED + COL DIVIDED */
|
|
571
|
+
export const PanelStripedAndDivided: StoryObj = {
|
|
572
|
+
render: () => (
|
|
573
|
+
<PanelDecorator>
|
|
574
|
+
<Table bordered>
|
|
575
|
+
<Headers colDivided />
|
|
576
|
+
<TableBody striped>
|
|
577
|
+
<Rows divided={false} colDivided />
|
|
578
|
+
</TableBody>
|
|
579
|
+
</Table>
|
|
580
|
+
</PanelDecorator>
|
|
581
|
+
),
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
/** PANEL SELECTED ROW — table-panel-selected (yellow/olive) */
|
|
585
|
+
export const PanelWithSelectedRow: StoryObj = {
|
|
586
|
+
render: () => (
|
|
587
|
+
<PanelDecorator>
|
|
588
|
+
<Table bordered>
|
|
589
|
+
<Headers colDivided />
|
|
590
|
+
<TableBody striped>
|
|
591
|
+
<Rows divided={false} colDivided selectedIndex={1} />
|
|
592
|
+
</TableBody>
|
|
593
|
+
</Table>
|
|
594
|
+
</PanelDecorator>
|
|
595
|
+
),
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
/** PANEL WITH FOOTER */
|
|
599
|
+
export const PanelWithFooter: StoryObj = {
|
|
600
|
+
render: () => (
|
|
601
|
+
<PanelDecorator>
|
|
602
|
+
<Table bordered>
|
|
603
|
+
<Headers colDivided />
|
|
604
|
+
<TableBody>
|
|
605
|
+
<Rows colDivided />
|
|
606
|
+
</TableBody>
|
|
607
|
+
<TableFooter>
|
|
608
|
+
<TableRow divided={false} colDivided>
|
|
609
|
+
<TableCell className="font-medium">5 invoices</TableCell>
|
|
610
|
+
<TableCell />
|
|
611
|
+
<TableCell />
|
|
612
|
+
<TableCell className="text-right font-medium">$1,290.00</TableCell>
|
|
613
|
+
</TableRow>
|
|
614
|
+
</TableFooter>
|
|
615
|
+
</Table>
|
|
616
|
+
</PanelDecorator>
|
|
617
|
+
),
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* PANEL WITH PAGINATION
|
|
622
|
+
* TablePagination sits inside the same data-surface="panel" scope so it
|
|
623
|
+
* automatically picks up the panel tokens — no variant prop needed.
|
|
624
|
+
*/
|
|
625
|
+
export const PanelWithPagination: StoryObj = {
|
|
626
|
+
render: () => {
|
|
627
|
+
const [pageIndex, setPageIndex] = React.useState(0);
|
|
628
|
+
const [pageSize, setPageSize] = React.useState(3);
|
|
629
|
+
const paged = rows.slice(pageIndex * pageSize, (pageIndex + 1) * pageSize);
|
|
630
|
+
return (
|
|
631
|
+
<PanelDecorator>
|
|
632
|
+
<div className="rounded-lg overflow-hidden border border-table-c-border w-full">
|
|
633
|
+
<Table>
|
|
634
|
+
<Headers colDivided />
|
|
635
|
+
<TableBody>
|
|
636
|
+
{paged.map((r) => (
|
|
637
|
+
<TableRow key={r.id} divided colDivided>
|
|
638
|
+
<TableCell className="font-medium">{r.id}</TableCell>
|
|
639
|
+
<TableCell>{r.status}</TableCell>
|
|
640
|
+
<TableCell>{r.method}</TableCell>
|
|
641
|
+
<TableCell className="text-right">{r.amount}</TableCell>
|
|
642
|
+
</TableRow>
|
|
643
|
+
))}
|
|
644
|
+
</TableBody>
|
|
645
|
+
</Table>
|
|
646
|
+
<TablePagination
|
|
647
|
+
pageIndex={pageIndex}
|
|
648
|
+
pageSize={pageSize}
|
|
649
|
+
totalCount={rows.length}
|
|
650
|
+
pageSizeOptions={[2, 3, 5]}
|
|
651
|
+
onPageChange={setPageIndex}
|
|
652
|
+
onPageSizeChange={(s) => {
|
|
653
|
+
setPageSize(s);
|
|
654
|
+
setPageIndex(0);
|
|
655
|
+
}}
|
|
656
|
+
/>
|
|
657
|
+
</div>
|
|
658
|
+
</PanelDecorator>
|
|
659
|
+
);
|
|
660
|
+
},
|
|
661
|
+
};
|
|
662
|
+
|
|
663
|
+
/** PANEL EMPTY STATE */
|
|
664
|
+
export const PanelEmpty: StoryObj = {
|
|
665
|
+
render: () => (
|
|
666
|
+
<PanelDecorator>
|
|
667
|
+
<Table bordered>
|
|
668
|
+
<Headers colDivided />
|
|
669
|
+
<TableBody>
|
|
670
|
+
<TableRow divided={false} className="hover:bg-transparent">
|
|
671
|
+
<TableCell
|
|
672
|
+
colSpan={4}
|
|
673
|
+
className="h-32 text-center text-text-g-contrast-medium"
|
|
674
|
+
>
|
|
675
|
+
No data found.
|
|
676
|
+
</TableCell>
|
|
677
|
+
</TableRow>
|
|
678
|
+
</TableBody>
|
|
679
|
+
</Table>
|
|
680
|
+
</PanelDecorator>
|
|
681
|
+
),
|
|
682
|
+
};
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* PANEL WITH SCROLL — sticky header + both scroll axes inside a fixed-height panel.
|
|
686
|
+
*/
|
|
687
|
+
export const PanelWithScroll: StoryObj = {
|
|
688
|
+
render: () => {
|
|
689
|
+
const [pageIndex, setPageIndex] = React.useState(0);
|
|
690
|
+
const [pageSize, setPageSize] = React.useState(5);
|
|
691
|
+
const paged = scrollRows.slice(
|
|
692
|
+
pageIndex * pageSize,
|
|
693
|
+
(pageIndex + 1) * pageSize,
|
|
694
|
+
);
|
|
695
|
+
return (
|
|
696
|
+
<PanelDecorator>
|
|
697
|
+
<div className="flex flex-col h-[280px] w-full rounded-lg overflow-hidden border border-table-c-border">
|
|
698
|
+
<Table rootClassName="flex-1 min-h-0">
|
|
699
|
+
<TableHeader className="sticky top-0 z-10">
|
|
700
|
+
<TableRow divided={false} colDivided>
|
|
701
|
+
{scrollCols.map((col) => (
|
|
702
|
+
<TableHead key={col} className="whitespace-nowrap">
|
|
703
|
+
{col}
|
|
704
|
+
</TableHead>
|
|
705
|
+
))}
|
|
706
|
+
</TableRow>
|
|
707
|
+
</TableHeader>
|
|
708
|
+
<TableBody>
|
|
709
|
+
{paged.map((cells, i) => (
|
|
710
|
+
<TableRow key={i} divided colDivided>
|
|
711
|
+
{cells.map((cell, j) => (
|
|
712
|
+
<TableCell key={j} className="whitespace-nowrap">
|
|
713
|
+
{cell}
|
|
714
|
+
</TableCell>
|
|
715
|
+
))}
|
|
716
|
+
</TableRow>
|
|
717
|
+
))}
|
|
718
|
+
</TableBody>
|
|
719
|
+
</Table>
|
|
720
|
+
<TablePagination
|
|
721
|
+
pageIndex={pageIndex}
|
|
722
|
+
pageSize={pageSize}
|
|
723
|
+
totalCount={scrollRows.length}
|
|
724
|
+
pageSizeOptions={[5, 10, 20]}
|
|
725
|
+
onPageChange={setPageIndex}
|
|
726
|
+
onPageSizeChange={(s) => {
|
|
727
|
+
setPageSize(s);
|
|
728
|
+
setPageIndex(0);
|
|
729
|
+
}}
|
|
730
|
+
/>
|
|
731
|
+
</div>
|
|
732
|
+
</PanelDecorator>
|
|
733
|
+
);
|
|
734
|
+
},
|
|
735
|
+
};
|
|
736
|
+
|
|
737
|
+
/** PANEL ALL STATES — all variants side-by-side for design review */
|
|
738
|
+
export const PanelAllStates: StoryObj = {
|
|
739
|
+
render: () => (
|
|
740
|
+
<div className="flex flex-col gap-6 w-full">
|
|
741
|
+
<p className="typography-subtitle4 text-text-g-contrast-high">
|
|
742
|
+
Non-striped — row stroke only
|
|
743
|
+
</p>
|
|
744
|
+
<PanelDecorator>
|
|
745
|
+
<Table bordered>
|
|
746
|
+
<Headers />
|
|
747
|
+
<TableBody>
|
|
748
|
+
<Rows />
|
|
749
|
+
</TableBody>
|
|
750
|
+
</Table>
|
|
751
|
+
</PanelDecorator>
|
|
752
|
+
|
|
753
|
+
<p className="typography-subtitle4 text-text-g-contrast-high">
|
|
754
|
+
Non-striped + col divided
|
|
755
|
+
</p>
|
|
756
|
+
<PanelDecorator>
|
|
757
|
+
<Table bordered>
|
|
758
|
+
<Headers colDivided />
|
|
759
|
+
<TableBody>
|
|
760
|
+
<Rows colDivided />
|
|
761
|
+
</TableBody>
|
|
762
|
+
</Table>
|
|
763
|
+
</PanelDecorator>
|
|
764
|
+
|
|
765
|
+
<p className="typography-subtitle4 text-text-g-contrast-high">
|
|
766
|
+
Striped — alternating bg, no row stroke
|
|
767
|
+
</p>
|
|
768
|
+
<PanelDecorator>
|
|
769
|
+
<Table bordered>
|
|
770
|
+
<Headers />
|
|
771
|
+
<TableBody striped>
|
|
772
|
+
<Rows divided={false} />
|
|
773
|
+
</TableBody>
|
|
774
|
+
</Table>
|
|
775
|
+
</PanelDecorator>
|
|
776
|
+
|
|
777
|
+
<p className="typography-subtitle4 text-text-g-contrast-high">
|
|
778
|
+
Striped + col divided (primary style)
|
|
779
|
+
</p>
|
|
780
|
+
<PanelDecorator>
|
|
781
|
+
<Table bordered>
|
|
782
|
+
<Headers colDivided />
|
|
783
|
+
<TableBody striped>
|
|
784
|
+
<Rows divided={false} colDivided />
|
|
785
|
+
</TableBody>
|
|
786
|
+
</Table>
|
|
787
|
+
</PanelDecorator>
|
|
788
|
+
|
|
789
|
+
<p className="typography-subtitle4 text-text-g-contrast-high">
|
|
790
|
+
Selected row (index 2) — table-panel-selected overlay
|
|
791
|
+
</p>
|
|
792
|
+
<PanelDecorator>
|
|
793
|
+
<Table bordered>
|
|
794
|
+
<Headers colDivided />
|
|
795
|
+
<TableBody striped>
|
|
796
|
+
<Rows divided={false} colDivided selectedIndex={2} />
|
|
797
|
+
</TableBody>
|
|
798
|
+
</Table>
|
|
799
|
+
</PanelDecorator>
|
|
800
|
+
|
|
801
|
+
<p className="typography-subtitle4 text-text-g-contrast-high">
|
|
802
|
+
With footer
|
|
803
|
+
</p>
|
|
804
|
+
<PanelDecorator>
|
|
805
|
+
<Table bordered>
|
|
806
|
+
<Headers colDivided />
|
|
807
|
+
<TableBody>
|
|
808
|
+
<Rows colDivided />
|
|
809
|
+
</TableBody>
|
|
810
|
+
<TableFooter>
|
|
811
|
+
<TableRow divided={false} colDivided>
|
|
812
|
+
<TableCell className="font-medium">5 invoices</TableCell>
|
|
813
|
+
<TableCell />
|
|
814
|
+
<TableCell />
|
|
815
|
+
<TableCell className="text-right font-medium">
|
|
816
|
+
$1,290.00
|
|
817
|
+
</TableCell>
|
|
818
|
+
</TableRow>
|
|
819
|
+
</TableFooter>
|
|
820
|
+
</Table>
|
|
821
|
+
</PanelDecorator>
|
|
822
|
+
</div>
|
|
823
|
+
),
|
|
824
|
+
};
|