@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 CHANGED
@@ -1,5 +1,13 @@
1
1
  # @rttui/skin-anocca
2
2
 
3
+ ## 1.0.15
4
+
5
+ ### Patch Changes
6
+
7
+ - Improve column sizing API
8
+ - Updated dependencies
9
+ - @rttui/core@1.0.15
10
+
3
11
  ## 1.0.14
4
12
 
5
13
  ### 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.15",
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,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 { 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,192 @@ 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
+ 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 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 = {
46
+ export const Basic: Story = {
85
47
  args: {
86
- tableOptions: {
87
- columns,
88
- data,
89
- getCoreRowModel: getCoreRowModel(),
90
- enableColumnResizing: true,
91
- enableColumnPinning: true,
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
- uiOptions: {
94
- width: 600,
95
- height: 400,
96
- skin: AnoccaSkin,
97
- autoSizeColumns: true,
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
- // More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
103
- export const AutoSizeColumnsPinRelativeToCols: Story = {
74
+ export const AutoCrushColumnsExceptName: Story = {
104
75
  args: {
105
- tableOptions: {
106
- columns,
107
- data,
108
- getCoreRowModel: getCoreRowModel(),
109
- enableColumnResizing: true,
110
- enableColumnPinning: true,
76
+ autoCrushColumns: 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
+ 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
- tableOptions: {
148
- columns,
149
- data,
150
- getCoreRowModel: getCoreRowModel(),
151
- enableColumnResizing: true,
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
- uiOptions: {
160
- width: 600,
161
- height: 400,
162
- skin: AnoccaSkin,
163
- pinColsRelativeTo: "table",
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 PinRelativeToTableWithBigData: Story = {
173
+ export const CanPinRowsRelativeToTable: Story = {
169
174
  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
- },
175
+ enableRowPinning: true,
176
+ enableColumnPinning: true,
177
+ data: "small",
178
+ pinRowsRelativeTo: "table",
179
+ initialState: {
180
+ rowPinning: {
181
+ bottom: ["3"],
180
182
  },
181
183
  },
182
- uiOptions: {
183
- width: 600,
184
- height: 400,
185
- skin: AnoccaSkin,
186
- pinColsRelativeTo: "table",
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 { 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
+ <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
+ });