@rttui/skin-anocca 1.0.14 → 1.0.16

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 CHANGED
@@ -1,5 +1,19 @@
1
1
  # @rttui/skin-anocca
2
2
 
3
+ ## 1.0.16
4
+
5
+ ### Patch Changes
6
+
7
+ - fix invalid build
8
+
9
+ ## 1.0.15
10
+
11
+ ### Patch Changes
12
+
13
+ - Improve column sizing API
14
+ - Updated dependencies
15
+ - @rttui/core@1.0.15
16
+
3
17
  ## 1.0.14
4
18
 
5
19
  ### Patch Changes
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@rttui/skin-anocca",
3
- "version": "1.0.14",
3
+ "version": "1.0.16",
4
4
  "main": "./dist/cjs/index.cjs",
5
5
  "dependencies": {
6
- "@rttui/core": "^1.0.14"
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
- function TableHeaderCell({
371
- headerId,
372
- isPinned,
373
- width,
374
- header,
375
- type,
376
- isLast,
377
- isLastPinned,
378
- isLastCenter,
379
- }: VirtualHeader & {
380
- type: "header" | "footer";
381
- isLast: boolean;
382
- isFirst: boolean;
383
- isLastPinned: boolean;
384
- isFirstPinned: boolean;
385
- isFirstCenter: boolean;
386
- isLastCenter: boolean;
387
- }) {
388
- const { table } = useTableContext();
389
- return (
390
- <TableCell
391
- component="div"
392
- className="th"
393
- data-header-id={headerId}
394
- data-is-pinned={isPinned}
395
- sx={{
396
- transition: "background-color 0.2s ease",
397
- whiteSpace: "nowrap",
398
- zIndex: isPinned ? 1 : 0,
399
- display: "flex",
400
- overflow: "hidden",
401
- height: "var(--header-row-height)",
402
- width,
403
- position: "relative",
404
- flexShrink: 0,
405
- alignItems: "center",
406
- gap: "8px",
407
- justifyContent: "space-between",
408
- padding: "6px 12px",
409
- boxSizing: "border-box",
410
- fontWeight: 600,
411
- backgroundColor: isPinned
412
- ? (theme) => theme.palette.background.paper
413
- : "transparent",
414
- borderRight:
415
- ((isPinned === "start" && !isLastPinned) || !isPinned) &&
416
- !isLast &&
417
- !(isLastCenter && table.getIsSomeColumnsPinned("right"))
418
- ? (theme) => `1px solid ${theme.palette.divider}`
419
- : undefined,
420
- borderLeft:
421
- isPinned === "end" && !isLastPinned
422
- ? (theme) => `1px solid ${theme.palette.divider}`
423
- : undefined,
424
- }}
425
- >
426
- <div style={{ flex: 1, display: "flex", justifyContent: "flex-start" }}>
427
- {header && !header.isPlaceholder
428
- ? flexRender(header.column.columnDef[type], header.getContext())
429
- : null}
430
- </div>
431
- </TableCell>
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,147 @@
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 largest header in the column.
23
+
24
+ ```tsx
25
+ <ReactTanstackTableUi
26
+ autoCrushColumns
27
+ />
28
+ ```
29
+
30
+ To prevent a particular 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
+ ### Crush behavior
42
+
43
+ By default the crush behavior is to crush the column based on the largest header.
44
+ You can change this by setting the `crushMinSizeBy` prop to `header`, `cell` or `both`.
45
+
46
+ ```tsx
47
+ <ReactTanstackTableUi
48
+ autoCrushColumns
49
+ crushMinSizeBy="cell"
50
+ />
51
+ ```
52
+
53
+ <Story of={RttuiStories.SizeByLargestHeader} />
54
+
55
+ You can also use meta to configure the crush behavior for a particular column.
56
+
57
+ ```tsx
58
+ columnHelper.accessor("age", {
59
+ header: "Age",
60
+ meta: { crushMinSizeBy: "cell" },
61
+ });
62
+
63
+ <ReactTanstackTableUi
64
+ autoCrushColumns
65
+ enableColumnPinning
66
+ crushMinSizeBy="header"
67
+ />
68
+ ```
69
+
70
+ <Story of={RttuiStories.SizeByLargestHeaderWithMeta} />
71
+
72
+ <br /><br />
73
+
74
+
75
+ ### Fill available space after crush
76
+
77
+ If you don't have so many columns and you want the columns to fill all the available space,
78
+ you can set the `autoCrushColumns` prop to `true` and set the `fillAvailableSpaceAfterCrush` prop to `true`.
79
+ [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.
80
+
81
+
82
+ ```tsx
83
+ <ReactTanstackTableUi
84
+ autoCrushColumns
85
+ fillAvailableSpaceAfterCrush
86
+ scrollbarWidth={16}
87
+ />
88
+ ```
89
+
90
+ To prevent a particular column from being expanded, you can set the `fillAvailableSpaceAfterCrush` meta option to `false`.
91
+
92
+ ```ts
93
+ columnHelper.accessor("name", {
94
+ header: "Name",
95
+ meta: { fillAvailableSpaceAfterCrush: false },
96
+ })
97
+ ```
98
+
99
+ <Canvas of={RttuiStories.FillAvailableSpaceAfterCrushExceptName} />
100
+
101
+ # Column Pinning
102
+
103
+ By default columns are pinned relative to the other columns:
104
+
105
+ <Story of={RttuiStories.PinRelativeToCols} />
106
+
107
+ ```tsx
108
+ <ReactTanstackTableUi
109
+ autoCrushColumns
110
+ pinColsRelativeTo="cols"
111
+ />
112
+ ```
113
+
114
+ You can also pin columns relative to the table:
115
+
116
+ <Story of={RttuiStories.PinRelativeToTable} />
117
+
118
+ ```tsx
119
+ <ReactTanstackTableUi
120
+ autoCrushColumns
121
+ pinColsRelativeTo="table"
122
+ />
123
+ ```
124
+
125
+ # Row Pinning
126
+
127
+ By default rows are pinned relative to the other rows:
128
+
129
+ <Story of={RttuiStories.CanPinRowsRelativeToRows} />
130
+
131
+ ```tsx
132
+ <ReactTanstackTableUi
133
+ enableRowPinning
134
+ pinRowsRelativeTo="rows"
135
+ />
136
+ ```
137
+
138
+ You can also pin rows relative to the table:
139
+
140
+ <Story of={RttuiStories.CanPinRowsRelativeToTable} />
141
+
142
+ ```tsx
143
+ <ReactTanstackTableUi
144
+ enableRowPinning
145
+ pinRowsRelativeTo="table"
146
+ />
147
+ ```
@@ -1,31 +1,9 @@
1
1
  import type { Meta, StoryObj } from "@storybook/react";
2
2
 
3
- import { Box } from "@mui/material";
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,193 @@ const meta = {
37
15
  },
38
16
  // More on argTypes: https://storybook.js.org/docs/api/argtypes
39
17
  argTypes: {
40
- tableOptions: { control: "object" },
41
- uiOptions: { control: "object" },
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
- uiOptions: {
46
- width: 600,
47
- height: 400,
48
- skin: AnoccaSkin,
49
- autoSizeColumns: true,
50
- },
51
- tableOptions: {
52
- columns: [],
53
- data: [],
54
- getCoreRowModel: getCoreRowModel(),
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
+ scrollbarWidth: 16,
38
+ getRowId: (row) => row.id,
56
39
  },
57
40
  } satisfies Meta<typeof ReactTanstackTableUi>;
58
41
 
59
42
  export default meta;
60
43
  type Story = StoryObj<typeof meta>;
61
44
 
62
- const columnHelper = decorateColumnHelper(createColumnHelper<Person>(), {
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 = {
45
+ export const Basic: Story = {
85
46
  args: {
86
- tableOptions: {
87
- columns,
88
- data,
89
- getCoreRowModel: getCoreRowModel(),
90
- enableColumnResizing: true,
91
- enableColumnPinning: true,
47
+ enableColumnPinning: true,
48
+ autoCrushColumns: false,
49
+ fillAvailableSpaceAfterCrush: false,
50
+ pinColsRelativeTo: "cols",
51
+ },
52
+ parameters: {
53
+ docs: {
54
+ source: { language: "tsx", code: createSourceCode() },
92
55
  },
93
- uiOptions: {
94
- width: 600,
95
- height: 400,
96
- skin: AnoccaSkin,
97
- autoSizeColumns: true,
56
+ },
57
+ };
58
+
59
+ export const AutoCrushColumns: Story = {
60
+ args: {
61
+ autoCrushColumns: true,
62
+ },
63
+ parameters: {
64
+ docs: {
65
+ source: {
66
+ language: "tsx",
67
+ code: createSourceCode({ props: " autoCrushColumns" }),
68
+ },
98
69
  },
99
70
  },
100
71
  };
101
72
 
102
- // More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
103
- export const AutoSizeColumnsPinRelativeToCols: Story = {
73
+ export const AutoCrushColumnsExceptName: Story = {
104
74
  args: {
105
- tableOptions: {
106
- columns,
107
- data,
108
- getCoreRowModel: getCoreRowModel(),
109
- enableColumnResizing: true,
110
- enableColumnPinning: true,
75
+ autoCrushColumns: true,
76
+ enableColumnPinning: true,
77
+ meta: {
78
+ name: {
79
+ autoCrush: false,
80
+ },
111
81
  },
112
- uiOptions: {
113
- width: 600,
114
- height: 400,
115
- skin: AnoccaSkin,
116
- autoSizeColumns: true,
117
- pinColsRelativeTo: "cols",
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
- tableOptions: {
126
- columns,
127
- data,
128
- getCoreRowModel: getCoreRowModel(),
129
- enableColumnResizing: true,
130
- enableColumnPinning: true,
131
- initialState: {
132
- columnPinning: {
133
- right: ["city"],
134
- },
98
+ autoCrushColumns: true,
99
+ pinColsRelativeTo: "cols",
100
+ enableColumnPinning: true,
101
+ initialState: {
102
+ columnPinning: {
103
+ right: ["city"],
135
104
  },
136
105
  },
137
- uiOptions: {
138
- width: 600,
139
- height: 400,
140
- skin: AnoccaSkin,
141
- },
142
106
  },
143
107
  };
144
108
 
145
109
  export const PinRelativeToTable: Story = {
146
110
  args: {
147
- tableOptions: {
148
- columns,
149
- data,
150
- getCoreRowModel: getCoreRowModel(),
151
- enableColumnResizing: true,
152
- enableColumnPinning: true,
153
- initialState: {
154
- columnPinning: {
155
- right: ["city"],
156
- },
111
+ autoCrushColumns: true,
112
+ pinColsRelativeTo: "table",
113
+ enableColumnPinning: true,
114
+ initialState: {
115
+ columnPinning: {
116
+ right: ["city"],
157
117
  },
158
118
  },
159
- uiOptions: {
160
- width: 600,
161
- height: 400,
162
- skin: AnoccaSkin,
163
- pinColsRelativeTo: "table",
119
+ },
120
+ };
121
+
122
+ export const FillAvailableSpaceAfterCrush: Story = {
123
+ args: {
124
+ autoCrushColumns: true,
125
+ fillAvailableSpaceAfterCrush: true,
126
+ },
127
+ };
128
+
129
+ export const FillAvailableSpaceAfterCrushExceptName: Story = {
130
+ args: {
131
+ autoCrushColumns: true,
132
+ fillAvailableSpaceAfterCrush: true,
133
+ meta: {
134
+ name: {
135
+ fillAvailableSpaceAfterCrush: false,
136
+ },
137
+ },
138
+ },
139
+ parameters: {
140
+ docs: {
141
+ source: {
142
+ language: "tsx",
143
+ code: createSourceCode({
144
+ props: " autoCrushColumns\n fillAvailableSpaceAfterCrush",
145
+ nameMeta: " fillAvailableSpaceAfterCrush: false,",
146
+ }),
147
+ },
148
+ },
149
+ },
150
+ };
151
+
152
+ export const FillAvailableSpaceAfterCrushWithoutSpecifiedScrollbarWidth: Story =
153
+ {
154
+ args: {
155
+ autoCrushColumns: true,
156
+ fillAvailableSpaceAfterCrush: true,
157
+ scrollbarWidth: 0,
158
+ },
159
+ };
160
+
161
+ export const CanPinRowsRelativeToRows: Story = {
162
+ args: {
163
+ enableRowPinning: true,
164
+ enableColumnPinning: true,
165
+ data: "small",
166
+ pinRowsRelativeTo: "rows",
167
+ initialState: {
168
+ rowPinning: {
169
+ bottom: ["3"],
170
+ },
164
171
  },
165
172
  },
166
173
  };
167
174
 
168
- export const PinRelativeToTableWithBigData: Story = {
175
+ export const CanPinRowsRelativeToTable: Story = {
169
176
  args: {
170
- tableOptions: {
171
- columns,
172
- data: bigData,
173
- getCoreRowModel: getCoreRowModel(),
174
- enableColumnResizing: true,
175
- enableColumnPinning: true,
176
- initialState: {
177
- columnPinning: {
178
- right: ["city"],
179
- },
177
+ enableRowPinning: true,
178
+ enableColumnPinning: true,
179
+ data: "small",
180
+ pinRowsRelativeTo: "table",
181
+ initialState: {
182
+ rowPinning: {
183
+ bottom: ["3"],
180
184
  },
181
185
  },
182
- uiOptions: {
183
- width: 600,
184
- height: 400,
185
- skin: AnoccaSkin,
186
- pinColsRelativeTo: "table",
186
+ },
187
+ };
188
+
189
+ export const SizeByLargestHeader: Story = {
190
+ args: {
191
+ autoCrushColumns: true,
192
+ crushMinSizeBy: "cell",
193
+ },
194
+ };
195
+
196
+ export const SizeByLargestHeaderWithMeta: Story = {
197
+ args: {
198
+ autoCrushColumns: true,
199
+ crushMinSizeBy: "header",
200
+ enableColumnPinning: true,
201
+ meta: {
202
+ age: {
203
+ crushMinSizeBy: 'cell',
204
+ },
187
205
  },
188
206
  },
189
207
  };
@@ -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 { ReactTanstackTableUi as Table } from "@rttui/core";
4
- import { TableOptions, useReactTable } from "@tanstack/react-table";
5
-
6
- export const ReactTanstackTableUi = ({
7
- tableOptions,
8
- uiOptions,
9
- }: {
10
- tableOptions: TableOptions<any>;
11
- uiOptions: Omit<React.ComponentProps<typeof Table>, "table">;
12
- }) => {
13
- const table = useReactTable(tableOptions);
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
+ {props.enableRowPinning && <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
- <Table table={table} {...uiOptions} />
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 'vite'
2
- import react from '@vitejs/plugin-react'
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
+ });