@rttui/skin-anocca 1.0.14 → 1.0.15
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/CHANGELOG.md +8 -0
- package/package.json +2 -2
- package/src/RowPinButtons.tsx +40 -0
- package/src/index.tsx +81 -67
- package/src/stories/ReactTanstackTableUiStory.mdx +136 -0
- package/src/stories/ReactTanstackTableUiStory.stories.tsx +149 -132
- package/src/stories/ReactTanstackTableUiStoryComponent.tsx +103 -13
- package/src/stories/ScrollbarWidthSpecified.mdx +42 -0
- package/src/stories/createSourceCode.ts +53 -0
- package/vite.config.ts +19 -3
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
{
|
2
2
|
"name": "@rttui/skin-anocca",
|
3
|
-
"version": "1.0.
|
3
|
+
"version": "1.0.15",
|
4
4
|
"main": "./dist/cjs/index.cjs",
|
5
5
|
"dependencies": {
|
6
|
-
"@rttui/core": "^1.0.
|
6
|
+
"@rttui/core": "^1.0.15"
|
7
7
|
},
|
8
8
|
"module": "./dist/mjs/index.mjs",
|
9
9
|
"types": "./dist/types/index.d.ts",
|
@@ -0,0 +1,40 @@
|
|
1
|
+
import { Row } from "@tanstack/react-table";
|
2
|
+
import { IconButton, Stack } from "@mui/material";
|
3
|
+
import { FiX, FiChevronUp, FiChevronDown } from "react-icons/fi";
|
4
|
+
|
5
|
+
export function RowPinButtons({ row }: { row: Row<any> }) {
|
6
|
+
if (!row.getCanPin()) {
|
7
|
+
return null;
|
8
|
+
}
|
9
|
+
|
10
|
+
if (row.getIsPinned()) {
|
11
|
+
return (
|
12
|
+
<IconButton
|
13
|
+
onClick={() => row.pin(false, true, true)}
|
14
|
+
size="small"
|
15
|
+
sx={{ color: "text.secondary" }}
|
16
|
+
>
|
17
|
+
<FiX size={16} />
|
18
|
+
</IconButton>
|
19
|
+
);
|
20
|
+
}
|
21
|
+
|
22
|
+
return (
|
23
|
+
<Stack direction="row">
|
24
|
+
<IconButton
|
25
|
+
onClick={() => row.pin("top", true, true)}
|
26
|
+
size="small"
|
27
|
+
sx={{ color: "text.secondary" }}
|
28
|
+
>
|
29
|
+
<FiChevronUp size={16} />
|
30
|
+
</IconButton>
|
31
|
+
<IconButton
|
32
|
+
onClick={() => row.pin("bottom", true, true)}
|
33
|
+
size="small"
|
34
|
+
sx={{ color: "text.secondary" }}
|
35
|
+
>
|
36
|
+
<FiChevronDown size={16} />
|
37
|
+
</IconButton>
|
38
|
+
</Stack>
|
39
|
+
);
|
40
|
+
}
|
package/src/index.tsx
CHANGED
@@ -116,9 +116,9 @@ const AnoccaSkin: Skin = {
|
|
116
116
|
);
|
117
117
|
},
|
118
118
|
HeaderRow: TableHeaderRow,
|
119
|
-
HeaderCell: (props) => {
|
120
|
-
return <TableHeaderCell {...props} />;
|
121
|
-
},
|
119
|
+
HeaderCell: React.forwardRef((props, ref) => {
|
120
|
+
return <TableHeaderCell {...props} ref={ref} />;
|
121
|
+
}),
|
122
122
|
TableBody: ({ children }) => {
|
123
123
|
return (
|
124
124
|
<TableBody
|
@@ -127,6 +127,7 @@ const AnoccaSkin: Skin = {
|
|
127
127
|
sx={{
|
128
128
|
position: "relative",
|
129
129
|
width: "var(--table-width)",
|
130
|
+
height: "var(--table-height)",
|
130
131
|
display: "flex",
|
131
132
|
flexDirection: "column",
|
132
133
|
justifyContent: "flex-start",
|
@@ -300,6 +301,8 @@ const AnoccaSkin: Skin = {
|
|
300
301
|
whiteSpace: "nowrap",
|
301
302
|
zIndex: isPinned ? 5 : 0,
|
302
303
|
boxSizing: "border-box",
|
304
|
+
fontSize: "0.875rem",
|
305
|
+
color: "text.primary",
|
303
306
|
alignItems: "center",
|
304
307
|
gap: "8px",
|
305
308
|
display: "flex",
|
@@ -367,70 +370,81 @@ const AnoccaSkin: Skin = {
|
|
367
370
|
},
|
368
371
|
};
|
369
372
|
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
?
|
413
|
-
: "
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
isPinned
|
422
|
-
? (theme) =>
|
423
|
-
:
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
373
|
+
const TableHeaderCell = React.forwardRef<
|
374
|
+
HTMLDivElement,
|
375
|
+
VirtualHeader & {
|
376
|
+
type: "header" | "footer";
|
377
|
+
isLast: boolean;
|
378
|
+
isFirst: boolean;
|
379
|
+
isLastPinned: boolean;
|
380
|
+
isFirstPinned: boolean;
|
381
|
+
isLastCenter: boolean;
|
382
|
+
isFirstCenter: boolean;
|
383
|
+
isMeasuring: boolean;
|
384
|
+
}
|
385
|
+
>(
|
386
|
+
(
|
387
|
+
{
|
388
|
+
headerId,
|
389
|
+
isPinned,
|
390
|
+
width,
|
391
|
+
header,
|
392
|
+
type,
|
393
|
+
isLast,
|
394
|
+
isLastPinned,
|
395
|
+
isLastCenter,
|
396
|
+
isMeasuring,
|
397
|
+
},
|
398
|
+
ref,
|
399
|
+
) => {
|
400
|
+
const { table } = useTableContext();
|
401
|
+
return (
|
402
|
+
<TableCell
|
403
|
+
ref={ref}
|
404
|
+
component="div"
|
405
|
+
className="th"
|
406
|
+
data-header-id={headerId}
|
407
|
+
data-is-pinned={isPinned}
|
408
|
+
sx={{
|
409
|
+
transition: "background-color 0.2s ease",
|
410
|
+
whiteSpace: "nowrap",
|
411
|
+
zIndex: isPinned ? 1 : 0,
|
412
|
+
display: "flex",
|
413
|
+
overflow: "hidden",
|
414
|
+
height: "var(--header-row-height)",
|
415
|
+
width: isMeasuring ? "auto" : width,
|
416
|
+
position: "relative",
|
417
|
+
flexShrink: 0,
|
418
|
+
alignItems: "center",
|
419
|
+
gap: "8px",
|
420
|
+
justifyContent: "space-between",
|
421
|
+
padding: "6px 12px",
|
422
|
+
boxSizing: "border-box",
|
423
|
+
fontWeight: 600,
|
424
|
+
backgroundColor: isPinned
|
425
|
+
? (theme) => theme.palette.background.paper
|
426
|
+
: "transparent",
|
427
|
+
borderRight:
|
428
|
+
((isPinned === "start" && !isLastPinned) || !isPinned) &&
|
429
|
+
!isLast &&
|
430
|
+
!(isLastCenter && table.getIsSomeColumnsPinned("right"))
|
431
|
+
? (theme) => `1px solid ${theme.palette.divider}`
|
432
|
+
: undefined,
|
433
|
+
borderLeft:
|
434
|
+
isPinned === "end" && !isLastPinned
|
435
|
+
? (theme) => `1px solid ${theme.palette.divider}`
|
436
|
+
: undefined,
|
437
|
+
}}
|
438
|
+
>
|
439
|
+
<div style={{ flex: 1, display: "flex", justifyContent: "flex-start" }}>
|
440
|
+
{header && !header.isPlaceholder
|
441
|
+
? flexRender(header.column.columnDef[type], header.getContext())
|
442
|
+
: null}
|
443
|
+
</div>
|
444
|
+
</TableCell>
|
445
|
+
);
|
446
|
+
},
|
447
|
+
);
|
434
448
|
|
435
449
|
export { AnoccaSkin };
|
436
450
|
|
@@ -0,0 +1,136 @@
|
|
1
|
+
import { Canvas, Meta, Story, Source, Controls } from '@storybook/blocks';
|
2
|
+
|
3
|
+
import * as RttuiStories from './ReactTanstackTableUiStory.stories';
|
4
|
+
|
5
|
+
<Meta of={RttuiStories} />
|
6
|
+
|
7
|
+
# Column sizing
|
8
|
+
By default columns uses the size defined in the columns. By default that is 150px in tanstack table.
|
9
|
+
|
10
|
+
You can override this by setting the `size` prop on the column.
|
11
|
+
```tsx
|
12
|
+
columnHelper.accessor("age", {
|
13
|
+
header: "Age",
|
14
|
+
size: 50,
|
15
|
+
})
|
16
|
+
```
|
17
|
+
|
18
|
+
<Canvas of={RttuiStories.Basic} />
|
19
|
+
|
20
|
+
### Auto crush columns
|
21
|
+
|
22
|
+
You can also use the `autoCrushColumns` prop to automatically crush columns to fit the available space.
|
23
|
+
|
24
|
+
```tsx
|
25
|
+
<ReactTanstackTableUi
|
26
|
+
autoCrushColumns
|
27
|
+
/>
|
28
|
+
```
|
29
|
+
|
30
|
+
To prevent a perticular column from being crushed, you can set the `autoCrush` meta option to `false`.
|
31
|
+
|
32
|
+
```tsx
|
33
|
+
columnHelper.accessor("name", {
|
34
|
+
header: "Name",
|
35
|
+
meta: { autoCrush: false },
|
36
|
+
})
|
37
|
+
```
|
38
|
+
|
39
|
+
<Canvas of={RttuiStories.AutoCrushColumnsExceptName} />
|
40
|
+
|
41
|
+
### Fill available space after crush
|
42
|
+
|
43
|
+
If you don't have so many columns and you want the columns to fill all the available space,
|
44
|
+
you can set the `autoCrushColumns` prop to `true` and set the `fillAvailableSpaceAfterCrush` prop to `true`.
|
45
|
+
[If you don't want a horizontal scrollbar to appear](?path=/docs/reacttanstacktableui--scrollbarwidthspecified) you also need to specify the `scrollbarWidth` prop which is the width of the vertical scrollbar.
|
46
|
+
|
47
|
+
|
48
|
+
```tsx
|
49
|
+
<ReactTanstackTableUi
|
50
|
+
autoCrushColumns
|
51
|
+
fillAvailableSpaceAfterCrush
|
52
|
+
scrollbarWidth={16}
|
53
|
+
/>
|
54
|
+
```
|
55
|
+
|
56
|
+
To prevent a perticular column from being expanded, you can set the `fillAvailableSpaceAfterCrush` meta option to `false`.
|
57
|
+
|
58
|
+
```ts
|
59
|
+
columnHelper.accessor("name", {
|
60
|
+
header: "Name",
|
61
|
+
meta: { fillAvailableSpaceAfterCrush: false },
|
62
|
+
})
|
63
|
+
```
|
64
|
+
|
65
|
+
<Canvas of={RttuiStories.FillAvailableSpaceAfterCrushExceptName} />
|
66
|
+
|
67
|
+
### Size by largest header
|
68
|
+
|
69
|
+
If you want the columns to be sized by the largest header, you can set the `crushMinSizeBy` prop to `header`.
|
70
|
+
|
71
|
+
```tsx
|
72
|
+
<ReactTanstackTableUi crushMinSizeBy="header" />
|
73
|
+
```
|
74
|
+
|
75
|
+
<Story of={RttuiStories.SizeByLargestHeader} />
|
76
|
+
|
77
|
+
You can also use meta to configure the crush behavior for a perticular column.
|
78
|
+
|
79
|
+
```ts
|
80
|
+
columnHelper.accessor("age", {
|
81
|
+
header: "Age",
|
82
|
+
meta: { crushMinSizeBy: "cell" },
|
83
|
+
})
|
84
|
+
```
|
85
|
+
|
86
|
+
<Story of={RttuiStories.SizeByLargestHeaderWithMeta} />
|
87
|
+
|
88
|
+
<br /><br />
|
89
|
+
|
90
|
+
# Column Pinning
|
91
|
+
|
92
|
+
By default columns are pinned relative to the other columns:
|
93
|
+
|
94
|
+
<Story of={RttuiStories.PinRelativeToCols} />
|
95
|
+
|
96
|
+
```tsx
|
97
|
+
<ReactTanstackTableUi
|
98
|
+
autoCrushColumns
|
99
|
+
pinColsRelativeTo="cols"
|
100
|
+
/>
|
101
|
+
```
|
102
|
+
|
103
|
+
You can also pin columns relative to the table:
|
104
|
+
|
105
|
+
<Story of={RttuiStories.PinRelativeToTable} />
|
106
|
+
|
107
|
+
```tsx
|
108
|
+
<ReactTanstackTableUi
|
109
|
+
autoCrushColumns
|
110
|
+
pinColsRelativeTo="table"
|
111
|
+
/>
|
112
|
+
```
|
113
|
+
|
114
|
+
# Row Pinning
|
115
|
+
|
116
|
+
By default rows are pinned relative to the other rows:
|
117
|
+
|
118
|
+
<Story of={RttuiStories.CanPinRowsRelativeToRows} />
|
119
|
+
|
120
|
+
```tsx
|
121
|
+
<ReactTanstackTableUi
|
122
|
+
enableRowPinning
|
123
|
+
pinRowsRelativeTo="rows"
|
124
|
+
/>
|
125
|
+
```
|
126
|
+
|
127
|
+
You can also pin rows relative to the table:
|
128
|
+
|
129
|
+
<Story of={RttuiStories.CanPinRowsRelativeToTable} />
|
130
|
+
|
131
|
+
```tsx
|
132
|
+
<ReactTanstackTableUi
|
133
|
+
enableRowPinning
|
134
|
+
pinRowsRelativeTo="table"
|
135
|
+
/>
|
136
|
+
```
|
@@ -1,31 +1,9 @@
|
|
1
1
|
import type { Meta, StoryObj } from "@storybook/react";
|
2
2
|
|
3
|
-
import {
|
4
|
-
import { decorateColumnHelper } from "@rttui/core";
|
5
|
-
import { createColumnHelper, getCoreRowModel } from "@tanstack/react-table";
|
3
|
+
import { getCoreRowModel } from "@tanstack/react-table";
|
6
4
|
import { AnoccaSkin } from "..";
|
7
|
-
import { HeaderPinButtons } from "../HeaderPinButtons";
|
8
5
|
import { ReactTanstackTableUi } from "./ReactTanstackTableUiStoryComponent";
|
9
|
-
|
10
|
-
type Person = {
|
11
|
-
id: string;
|
12
|
-
name: string;
|
13
|
-
age: number;
|
14
|
-
city: string;
|
15
|
-
};
|
16
|
-
|
17
|
-
const data: Person[] = [
|
18
|
-
{ id: "1", name: "John", age: 20, city: "New York" },
|
19
|
-
{ id: "2", name: "Jane", age: 21, city: "Los Angeles" },
|
20
|
-
{ id: "3", name: "Jim", age: 22, city: "Chicago" },
|
21
|
-
];
|
22
|
-
|
23
|
-
const bigData: Person[] = Array.from({ length: 1000 }, (_, i) => ({
|
24
|
-
id: i.toString(),
|
25
|
-
name: `Person ${i}`,
|
26
|
-
age: 20 + i,
|
27
|
-
city: `City ${i}`,
|
28
|
-
}));
|
6
|
+
import { createSourceCode } from "./createSourceCode";
|
29
7
|
|
30
8
|
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
|
31
9
|
const meta = {
|
@@ -37,153 +15,192 @@ const meta = {
|
|
37
15
|
},
|
38
16
|
// More on argTypes: https://storybook.js.org/docs/api/argtypes
|
39
17
|
argTypes: {
|
40
|
-
|
41
|
-
|
18
|
+
data: { control: "select" },
|
19
|
+
columns: { control: "select" },
|
20
|
+
pinColsRelativeTo: { control: "select", options: ["cols", "table"] },
|
21
|
+
skin: {
|
22
|
+
control: "object",
|
23
|
+
table: { disable: true },
|
24
|
+
},
|
42
25
|
},
|
43
26
|
// Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args
|
44
27
|
args: {
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
28
|
+
width: 600,
|
29
|
+
height: 400,
|
30
|
+
skin: AnoccaSkin,
|
31
|
+
autoCrushColumns: false,
|
32
|
+
data: "big",
|
33
|
+
columns: "few",
|
34
|
+
getCoreRowModel: getCoreRowModel(),
|
35
|
+
pinColsRelativeTo: "cols",
|
36
|
+
fillAvailableSpaceAfterCrush: false,
|
37
|
+
enableColumnPinning: true,
|
38
|
+
scrollbarWidth: 16,
|
39
|
+
getRowId: (row) => row.id,
|
56
40
|
},
|
57
41
|
} satisfies Meta<typeof ReactTanstackTableUi>;
|
58
42
|
|
59
43
|
export default meta;
|
60
44
|
type Story = StoryObj<typeof meta>;
|
61
45
|
|
62
|
-
const
|
63
|
-
header: (original) => (
|
64
|
-
<Box sx={{ display: "flex", gap: 2 }}>
|
65
|
-
{original}
|
66
|
-
<HeaderPinButtons />
|
67
|
-
</Box>
|
68
|
-
),
|
69
|
-
});
|
70
|
-
|
71
|
-
const columns = [
|
72
|
-
columnHelper.accessor("name", {
|
73
|
-
header: "Name",
|
74
|
-
}),
|
75
|
-
columnHelper.accessor("age", {
|
76
|
-
header: "Age",
|
77
|
-
}),
|
78
|
-
columnHelper.accessor("city", {
|
79
|
-
header: "City",
|
80
|
-
}),
|
81
|
-
];
|
82
|
-
|
83
|
-
// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
|
84
|
-
export const AutoSizeColumns: Story = {
|
46
|
+
export const Basic: Story = {
|
85
47
|
args: {
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
48
|
+
enableColumnPinning: true,
|
49
|
+
autoCrushColumns: false,
|
50
|
+
fillAvailableSpaceAfterCrush: false,
|
51
|
+
pinColsRelativeTo: "cols",
|
52
|
+
},
|
53
|
+
parameters: {
|
54
|
+
docs: {
|
55
|
+
source: { language: "tsx", code: createSourceCode() },
|
92
56
|
},
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
57
|
+
},
|
58
|
+
};
|
59
|
+
|
60
|
+
export const AutoCrushColumns: Story = {
|
61
|
+
args: {
|
62
|
+
autoCrushColumns: true,
|
63
|
+
},
|
64
|
+
parameters: {
|
65
|
+
docs: {
|
66
|
+
source: {
|
67
|
+
language: "tsx",
|
68
|
+
code: createSourceCode({ props: " autoCrushColumns" }),
|
69
|
+
},
|
98
70
|
},
|
99
71
|
},
|
100
72
|
};
|
101
73
|
|
102
|
-
|
103
|
-
export const AutoSizeColumnsPinRelativeToCols: Story = {
|
74
|
+
export const AutoCrushColumnsExceptName: Story = {
|
104
75
|
args: {
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
enableColumnPinning: true,
|
76
|
+
autoCrushColumns: true,
|
77
|
+
meta: {
|
78
|
+
name: {
|
79
|
+
autoCrush: false,
|
80
|
+
},
|
111
81
|
},
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
82
|
+
},
|
83
|
+
parameters: {
|
84
|
+
docs: {
|
85
|
+
source: {
|
86
|
+
language: "tsx",
|
87
|
+
code: createSourceCode({
|
88
|
+
props: " autoCrushColumns",
|
89
|
+
nameMeta: " autoCrush: false,",
|
90
|
+
}),
|
91
|
+
},
|
118
92
|
},
|
119
93
|
},
|
120
94
|
};
|
121
95
|
|
122
|
-
// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
|
123
96
|
export const PinRelativeToCols: Story = {
|
124
97
|
args: {
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
enableColumnPinning: true,
|
131
|
-
initialState: {
|
132
|
-
columnPinning: {
|
133
|
-
right: ["city"],
|
134
|
-
},
|
98
|
+
autoCrushColumns: true,
|
99
|
+
pinColsRelativeTo: "cols",
|
100
|
+
initialState: {
|
101
|
+
columnPinning: {
|
102
|
+
right: ["city"],
|
135
103
|
},
|
136
104
|
},
|
137
|
-
uiOptions: {
|
138
|
-
width: 600,
|
139
|
-
height: 400,
|
140
|
-
skin: AnoccaSkin,
|
141
|
-
},
|
142
105
|
},
|
143
106
|
};
|
144
107
|
|
145
108
|
export const PinRelativeToTable: Story = {
|
146
109
|
args: {
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
enableColumnPinning: true,
|
153
|
-
initialState: {
|
154
|
-
columnPinning: {
|
155
|
-
right: ["city"],
|
156
|
-
},
|
110
|
+
autoCrushColumns: true,
|
111
|
+
pinColsRelativeTo: "table",
|
112
|
+
initialState: {
|
113
|
+
columnPinning: {
|
114
|
+
right: ["city"],
|
157
115
|
},
|
158
116
|
},
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
117
|
+
},
|
118
|
+
};
|
119
|
+
|
120
|
+
export const FillAvailableSpaceAfterCrush: Story = {
|
121
|
+
args: {
|
122
|
+
autoCrushColumns: true,
|
123
|
+
fillAvailableSpaceAfterCrush: true,
|
124
|
+
},
|
125
|
+
};
|
126
|
+
|
127
|
+
export const FillAvailableSpaceAfterCrushExceptName: Story = {
|
128
|
+
args: {
|
129
|
+
autoCrushColumns: true,
|
130
|
+
fillAvailableSpaceAfterCrush: true,
|
131
|
+
meta: {
|
132
|
+
name: {
|
133
|
+
fillAvailableSpaceAfterCrush: false,
|
134
|
+
},
|
135
|
+
},
|
136
|
+
},
|
137
|
+
parameters: {
|
138
|
+
docs: {
|
139
|
+
source: {
|
140
|
+
language: "tsx",
|
141
|
+
code: createSourceCode({
|
142
|
+
props: " autoCrushColumns\n fillAvailableSpaceAfterCrush",
|
143
|
+
nameMeta: " fillAvailableSpaceAfterCrush: false,",
|
144
|
+
}),
|
145
|
+
},
|
146
|
+
},
|
147
|
+
},
|
148
|
+
};
|
149
|
+
|
150
|
+
export const FillAvailableSpaceAfterCrushWithoutSpecifiedScrollbarWidth: Story =
|
151
|
+
{
|
152
|
+
args: {
|
153
|
+
autoCrushColumns: true,
|
154
|
+
fillAvailableSpaceAfterCrush: true,
|
155
|
+
scrollbarWidth: 0,
|
156
|
+
},
|
157
|
+
};
|
158
|
+
|
159
|
+
export const CanPinRowsRelativeToRows: Story = {
|
160
|
+
args: {
|
161
|
+
enableRowPinning: true,
|
162
|
+
enableColumnPinning: true,
|
163
|
+
data: "small",
|
164
|
+
pinRowsRelativeTo: "rows",
|
165
|
+
initialState: {
|
166
|
+
rowPinning: {
|
167
|
+
bottom: ["3"],
|
168
|
+
},
|
164
169
|
},
|
165
170
|
},
|
166
171
|
};
|
167
172
|
|
168
|
-
export const
|
173
|
+
export const CanPinRowsRelativeToTable: Story = {
|
169
174
|
args: {
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
columnPinning: {
|
178
|
-
right: ["city"],
|
179
|
-
},
|
175
|
+
enableRowPinning: true,
|
176
|
+
enableColumnPinning: true,
|
177
|
+
data: "small",
|
178
|
+
pinRowsRelativeTo: "table",
|
179
|
+
initialState: {
|
180
|
+
rowPinning: {
|
181
|
+
bottom: ["3"],
|
180
182
|
},
|
181
183
|
},
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
184
|
+
},
|
185
|
+
};
|
186
|
+
|
187
|
+
export const SizeByLargestHeader: Story = {
|
188
|
+
args: {
|
189
|
+
autoCrushColumns: true,
|
190
|
+
crushMinSizeBy: "header",
|
191
|
+
enableColumnPinning: true,
|
192
|
+
},
|
193
|
+
};
|
194
|
+
|
195
|
+
export const SizeByLargestHeaderWithMeta: Story = {
|
196
|
+
args: {
|
197
|
+
autoCrushColumns: true,
|
198
|
+
crushMinSizeBy: "header",
|
199
|
+
enableColumnPinning: true,
|
200
|
+
meta: {
|
201
|
+
age: {
|
202
|
+
crushMinSizeBy: 'cell',
|
203
|
+
},
|
187
204
|
},
|
188
205
|
},
|
189
206
|
};
|
@@ -1,22 +1,112 @@
|
|
1
|
-
import { ScopedCssBaseline, ThemeProvider } from "@mui/material";
|
1
|
+
import { Box, ScopedCssBaseline, ThemeProvider, Typography } from "@mui/material";
|
2
2
|
import { useTheme } from "@mui/material/styles";
|
3
|
-
import {
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
3
|
+
import {
|
4
|
+
decorateColumnHelper,
|
5
|
+
ReactTanstackTableUi as TableComponent,
|
6
|
+
} from "@rttui/core";
|
7
|
+
import {
|
8
|
+
ColumnDef,
|
9
|
+
ColumnMeta,
|
10
|
+
createColumnHelper,
|
11
|
+
Table,
|
12
|
+
TableOptions,
|
13
|
+
useReactTable,
|
14
|
+
} from "@tanstack/react-table";
|
15
|
+
import { HeaderPinButtons } from "../HeaderPinButtons";
|
16
|
+
import React from "react";
|
17
|
+
import { RowPinButtons } from "../RowPinButtons";
|
18
|
+
type Person = {
|
19
|
+
id: string;
|
20
|
+
name: string;
|
21
|
+
age: number;
|
22
|
+
city: string;
|
23
|
+
} & Record<`col${number}`, string>;
|
24
|
+
|
25
|
+
const smallData: Person[] = [
|
26
|
+
{ id: "1", name: "John", age: 20, city: "New York" },
|
27
|
+
{ id: "2", name: "Jane", age: 21, city: "Los Angeles" },
|
28
|
+
{ id: "3", name: "Jim", age: 22, city: "Chicago" },
|
29
|
+
];
|
30
|
+
|
31
|
+
const bigData: Person[] = Array.from({ length: 1000 }, (_, i) => ({
|
32
|
+
id: i.toString(),
|
33
|
+
name: `Person ${i}`,
|
34
|
+
age: 20 + i,
|
35
|
+
city: `City ${i}`,
|
36
|
+
...Object.fromEntries(
|
37
|
+
Array.from({ length: 100 }, (_, j) => [`col${j}`, `Value ${i}-${j}`]),
|
38
|
+
),
|
39
|
+
}));
|
40
|
+
|
41
|
+
export const ReactTanstackTableUi = (
|
42
|
+
props: {
|
43
|
+
data: "big" | "small";
|
44
|
+
columns: "many" | "few";
|
45
|
+
meta?: Record<string, ColumnMeta<Person, string>>;
|
46
|
+
} & Omit<TableOptions<Person>, "data" | "columns"> &
|
47
|
+
Omit<React.ComponentProps<typeof TableComponent>, "table">,
|
48
|
+
) => {
|
14
49
|
const theme = useTheme();
|
50
|
+
const data: Person[] = props.data === "big" ? bigData : smallData;
|
51
|
+
|
52
|
+
const columns: ColumnDef<Person>[] = React.useMemo(() => {
|
53
|
+
const columnHelper = decorateColumnHelper(createColumnHelper<Person>(), {
|
54
|
+
header: (original) => (
|
55
|
+
<Box sx={{ display: "flex", gap: 2 }}>
|
56
|
+
{original}
|
57
|
+
{props.enableColumnPinning && <HeaderPinButtons />}
|
58
|
+
</Box>
|
59
|
+
),
|
60
|
+
});
|
61
|
+
|
62
|
+
const fewColumns = [
|
63
|
+
columnHelper.accessor("name", {
|
64
|
+
header: "Name",
|
65
|
+
cell: (info) => (
|
66
|
+
<Box sx={{ display: "flex", gap: 2, flex: 1 }}>
|
67
|
+
<Typography variant="body2">{info.getValue()}</Typography>
|
68
|
+
<Box sx={{ flexGrow: 1 }} />
|
69
|
+
{props.enableRowPinning && <RowPinButtons row={info.row} />}
|
70
|
+
</Box>
|
71
|
+
),
|
72
|
+
meta: props.meta?.["name"],
|
73
|
+
}),
|
74
|
+
columnHelper.accessor("age", {
|
75
|
+
header: "Age",
|
76
|
+
meta: props.meta?.["age"],
|
77
|
+
cell: (info) => <Typography variant="body2">{info.getValue()}</Typography>,
|
78
|
+
size: 50,
|
79
|
+
}),
|
80
|
+
columnHelper.accessor("city", {
|
81
|
+
header: "City",
|
82
|
+
meta: props.meta?.["city"],
|
83
|
+
cell: (info) => <Typography variant="body2">{info.getValue()}</Typography>,
|
84
|
+
}),
|
85
|
+
];
|
86
|
+
|
87
|
+
const manyColumns = [
|
88
|
+
...fewColumns,
|
89
|
+
...Array.from({ length: 100 }, (_, i) =>
|
90
|
+
columnHelper.accessor(`col${i}`, {
|
91
|
+
header: `Column ${i}`,
|
92
|
+
cell: (info) => <Typography variant="body2">{info.getValue()}</Typography>,
|
93
|
+
}),
|
94
|
+
),
|
95
|
+
];
|
96
|
+
|
97
|
+
return props.columns === "few" ? fewColumns : manyColumns;
|
98
|
+
}, [props.columns, props.enableColumnPinning, props.enableRowPinning, props.meta]);
|
99
|
+
|
100
|
+
const table = useReactTable({
|
101
|
+
...props,
|
102
|
+
data,
|
103
|
+
columns,
|
104
|
+
});
|
15
105
|
|
16
106
|
return (
|
17
107
|
<ScopedCssBaseline>
|
18
108
|
<ThemeProvider theme={theme}>
|
19
|
-
<
|
109
|
+
<TableComponent {...props} table={table as Table<unknown>} />
|
20
110
|
</ThemeProvider>
|
21
111
|
</ScopedCssBaseline>
|
22
112
|
);
|
@@ -0,0 +1,42 @@
|
|
1
|
+
import { Canvas, Meta, Story, Source, Controls } from '@storybook/blocks';
|
2
|
+
import { Box } from '@mui/material';
|
3
|
+
|
4
|
+
import * as ColumnPinningStories from './ReactTanstackTableUiStory.stories';
|
5
|
+
|
6
|
+
<Meta of={ColumnPinningStories} />
|
7
|
+
|
8
|
+
# ⚠️ No scrollbar width specified
|
9
|
+
We get a horizontal scrollbar because we don't specify the width of the vertical scrollbar.
|
10
|
+
```tsx
|
11
|
+
<ReactTanstackTableUi
|
12
|
+
autoCrushColumns
|
13
|
+
fillAvailableSpaceAfterCrush
|
14
|
+
/>
|
15
|
+
```
|
16
|
+
<Box sx={{
|
17
|
+
'*::-webkit-scrollbar': { width: '16px' },
|
18
|
+
'*::-webkit-scrollbar-track': { backgroundColor: 'yellow' },
|
19
|
+
'*::-webkit-scrollbar-thumb': { backgroundColor: 'blue' },
|
20
|
+
}}
|
21
|
+
>
|
22
|
+
<Story of={ColumnPinningStories.FillAvailableSpaceAfterCrushWithoutSpecifiedScrollbarWidth} />
|
23
|
+
</Box>
|
24
|
+
<br /><br />
|
25
|
+
# ✅ Scrollbar width specified
|
26
|
+
We specify the width of the vertical scrollbar and we correctly only see a vertical scrollbar.
|
27
|
+
```tsx
|
28
|
+
<ReactTanstackTableUi
|
29
|
+
autoCrushColumns
|
30
|
+
fillAvailableSpaceAfterCrush
|
31
|
+
scrollbarWidth={16}
|
32
|
+
/>
|
33
|
+
```
|
34
|
+
<Box sx={{
|
35
|
+
'*::-webkit-scrollbar': { width: '16px' },
|
36
|
+
'*::-webkit-scrollbar-track': { backgroundColor: 'yellow' },
|
37
|
+
'*::-webkit-scrollbar-thumb': { backgroundColor: 'blue' },
|
38
|
+
}}
|
39
|
+
>
|
40
|
+
<Story of={ColumnPinningStories.FillAvailableSpaceAfterCrushExceptName} />
|
41
|
+
</Box>
|
42
|
+
|
@@ -0,0 +1,53 @@
|
|
1
|
+
export const createSourceCode = (opts?: { hookOptions?: string, props?: string, nameMeta?: string }) => {
|
2
|
+
return `import { createColumnHelper, useReactTable, getCoreRowModel } from "@tanstack/react-table";
|
3
|
+
import { Box } from "@mui/material";
|
4
|
+
import { HeaderPinButtons } from "@rttui/skin-anocca";
|
5
|
+
import { ReactTanstackTableUi, decorateColumnHelper } from "@rttui/core";
|
6
|
+
|
7
|
+
type Person = {
|
8
|
+
id: string;
|
9
|
+
name: string;
|
10
|
+
age: number;
|
11
|
+
city: string;
|
12
|
+
};
|
13
|
+
|
14
|
+
const data: Person[] = [
|
15
|
+
{ id: "1", name: "John", age: 20, city: "New York" },
|
16
|
+
{ id: "2", name: "Jane", age: 21, city: "Los Angeles" },
|
17
|
+
{ id: "3", name: "Jim", age: 22, city: "Chicago" },
|
18
|
+
];
|
19
|
+
|
20
|
+
const columnHelper = decorateColumnHelper(createColumnHelper<Person>(), {
|
21
|
+
header: (original) => (
|
22
|
+
<Box sx={{ display: "flex", gap: 2 }}>
|
23
|
+
{original}
|
24
|
+
{props.enableColumnPinning && <HeaderPinButtons />}
|
25
|
+
</Box>
|
26
|
+
),
|
27
|
+
});
|
28
|
+
|
29
|
+
const columns = [
|
30
|
+
columnHelper.accessor("name", {
|
31
|
+
header: "Name",${opts?.nameMeta ? '\n' + opts.nameMeta : ''}
|
32
|
+
}),
|
33
|
+
columnHelper.accessor("age", {
|
34
|
+
header: "Age",
|
35
|
+
size: 50,
|
36
|
+
}),
|
37
|
+
columnHelper.accessor("city", {
|
38
|
+
header: "City",
|
39
|
+
}),
|
40
|
+
];
|
41
|
+
|
42
|
+
const table = useReactTable({
|
43
|
+
data,
|
44
|
+
columns,
|
45
|
+
getCoreRowModel: getCoreRowModel()${opts?.hookOptions ? '\n' + opts.hookOptions : ''}
|
46
|
+
});
|
47
|
+
|
48
|
+
<ReactTanstackTableUi
|
49
|
+
table={table}
|
50
|
+
enableColumnPinning${opts?.props ? '\n' + opts.props : ''}
|
51
|
+
/>
|
52
|
+
`;
|
53
|
+
};
|
package/vite.config.ts
CHANGED
@@ -1,7 +1,23 @@
|
|
1
|
-
import { defineConfig } from
|
2
|
-
import react from
|
1
|
+
import { defineConfig } from "vite";
|
2
|
+
import react from "@vitejs/plugin-react";
|
3
|
+
import path from "path";
|
4
|
+
import fs from "fs";
|
5
|
+
let base: undefined | string;
|
6
|
+
|
7
|
+
try {
|
8
|
+
const packageJson = fs.readFileSync(
|
9
|
+
path.join(__dirname, "package.json"),
|
10
|
+
"utf-8",
|
11
|
+
);
|
12
|
+
const viteBase = JSON.parse(packageJson).viteBase;
|
13
|
+
if (viteBase) {
|
14
|
+
base = viteBase;
|
15
|
+
}
|
16
|
+
} catch (e) {
|
17
|
+
// ignore
|
18
|
+
}
|
3
19
|
|
4
20
|
// https://vite.dev/config/
|
5
21
|
export default defineConfig({
|
6
22
|
plugins: [react()],
|
7
|
-
})
|
23
|
+
});
|