@czi-sds/mcp 0.16.0 → 0.18.0
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/README.md +2 -1
- package/data/component-docs/Accordion.md +291 -0
- package/data/component-docs/Autocomplete.md +525 -0
- package/data/component-docs/Banner.md +231 -0
- package/data/component-docs/Button.md +224 -0
- package/data/component-docs/ButtonDropdown.md +256 -0
- package/data/component-docs/ButtonGroup.md +320 -0
- package/data/component-docs/ButtonToggle.md +397 -0
- package/data/component-docs/Callout.md +268 -0
- package/data/component-docs/CellBasic.md +122 -0
- package/data/component-docs/CellComponent.md +63 -0
- package/data/component-docs/CellHeader.md +128 -0
- package/data/component-docs/ComplexFilter.md +294 -0
- package/data/component-docs/ContentCard.md +406 -0
- package/data/component-docs/Dialog.md +651 -0
- package/data/component-docs/Dropdown.md +155 -0
- package/data/component-docs/DropdownMenu.md +949 -0
- package/data/component-docs/HeatmapChart.md +782 -0
- package/data/component-docs/Hero.md +363 -0
- package/data/component-docs/Icon.md +283 -0
- package/data/component-docs/InputCheckbox.md +205 -0
- package/data/component-docs/InputDropdown.md +482 -0
- package/data/component-docs/InputRadio.md +173 -0
- package/data/component-docs/InputSearch.md +81 -0
- package/data/component-docs/InputSlider.md +189 -0
- package/data/component-docs/InputText.md +110 -0
- package/data/component-docs/InputToggle.md +141 -0
- package/data/component-docs/IntentMessage.md +293 -0
- package/data/component-docs/Legend.md +442 -0
- package/data/component-docs/Link.md +23 -0
- package/data/component-docs/List.md +300 -0
- package/data/component-docs/LoadingIndicator.md +198 -0
- package/data/component-docs/Menu.md +298 -0
- package/data/component-docs/MenuItem.md +100 -0
- package/data/component-docs/NavigationFooter.md +165 -0
- package/data/component-docs/NavigationHeader.md +500 -0
- package/data/component-docs/NavigationJumpTo.md +414 -0
- package/data/component-docs/Notification.md +288 -0
- package/data/component-docs/Pagination.md +67 -0
- package/data/component-docs/Panel.md +636 -0
- package/data/component-docs/Popover.md +448 -0
- package/data/component-docs/PreComposedTable.md +2947 -0
- package/data/component-docs/ProteinStructureViewer.md +1044 -0
- package/data/component-docs/SegmentedControl.md +396 -0
- package/data/component-docs/StackedBarChart.md +475 -0
- package/data/component-docs/Table.md +2000 -0
- package/data/component-docs/TableHeader.md +53 -0
- package/data/component-docs/TableRow.md +74 -0
- package/data/component-docs/Tabs.md +455 -0
- package/data/component-docs/Tag.md +647 -0
- package/data/component-docs/TagFilter.md +131 -0
- package/data/component-docs/Tooltip.md +561 -0
- package/data/component-docs/TooltipCondensed.md +203 -0
- package/data/component-docs/TooltipTable.md +145 -0
- package/data/component-docs/index.json +267 -0
- package/data/component-list.json +2 -5
- package/data/component-props/Accordion.json +31 -11
- package/data/component-props/Alert.json +3 -3
- package/data/component-props/Autocomplete.json +150 -15
- package/data/component-props/Banner.json +31 -17
- package/data/component-props/Button.json +45 -20
- package/data/component-props/ButtonDropdown.json +36 -17
- package/data/component-props/ButtonDropdownLegacy.json +11 -11
- package/data/component-props/ButtonGroup.json +32 -11
- package/data/component-props/ButtonIcon.json +10 -10
- package/data/component-props/ButtonLegacy.json +19 -19
- package/data/component-props/ButtonToggle.json +32 -19
- package/data/component-props/ButtonToggleLegacy.json +11 -11
- package/data/component-props/Callout.json +58 -17
- package/data/component-props/CellBasic.json +94 -41
- package/data/component-props/CellComponent.json +23 -7
- package/data/component-props/CellHeader.json +62 -19
- package/data/component-props/ComplexFilter.json +52 -21
- package/data/component-props/ContentCard.json +70 -47
- package/data/component-props/Dialog.json +47 -14
- package/data/component-props/Dropdown.json +68 -31
- package/data/component-props/DropdownMenu.json +128 -23
- package/data/component-props/HeatmapChart.json +88 -12
- package/data/component-props/Hero.json +80 -39
- package/data/component-props/Icon.json +20 -9
- package/data/component-props/InputCheckbox.json +36 -14
- package/data/component-props/InputDropdown.json +75 -27
- package/data/component-props/InputRadio.json +31 -13
- package/data/component-props/InputSearch.json +41 -18
- package/data/component-props/InputSlider.json +46 -19
- package/data/component-props/InputText.json +26 -19
- package/data/component-props/InputToggle.json +26 -9
- package/data/component-props/IntentMessage.json +38 -9
- package/data/component-props/Link.json +13 -7
- package/data/component-props/LoadingIndicator.json +11 -3
- package/data/component-props/MenuItem.json +38 -19
- package/data/component-props/MenuSelect.json +8 -8
- package/data/component-props/NavigationFooter.json +49 -19
- package/data/component-props/NavigationHeader.json +150 -37
- package/data/component-props/NavigationJumpTo.json +33 -7
- package/data/component-props/Notification.json +39 -21
- package/data/component-props/Pagination.json +39 -11
- package/data/component-props/Panel.json +62 -9
- package/data/component-props/Popover.json +80 -7
- package/data/component-props/PreComposedTable.json +99 -27
- package/data/component-props/ProteinStructureViewer.json +117 -0
- package/data/component-props/SegmentedControl.json +42 -3
- package/data/component-props/StackedBarChart.json +116 -42
- package/data/component-props/TableRow.json +38 -19
- package/data/component-props/Tabs.json +66 -9
- package/data/component-props/Tag.json +34 -17
- package/data/component-props/TagFilter.json +10 -3
- package/data/component-props/Tooltip.json +49 -18
- package/data/component-props/TooltipCondensed.json +18 -9
- package/data/component-props/TooltipTable.json +17 -7
- package/data/resources/{zeroheight-rules.md → documentation-rules.md} +43 -30
- package/data/resources/figma-rules.md +2 -2
- package/data/tailwind.json +20 -14
- package/dist/stdio.js +112 -208
- package/package.json +9 -5
- package/data/component-props/ButtonDropdownV2.json +0 -34
- package/data/component-props/ButtonNew.json +0 -47
- package/data/component-props/ButtonToggleV2.json +0 -44
- package/data/component-props/ButtonV2.json +0 -43
- package/data/zeroheight-cache.json +0 -10259
- package/data/zeroheight-exports/Accordion.md +0 -774
- package/data/zeroheight-exports/Banner.md +0 -691
- package/data/zeroheight-exports/Borders.md +0 -117
- package/data/zeroheight-exports/Buttons.md +0 -1854
- package/data/zeroheight-exports/Callout.md +0 -931
- package/data/zeroheight-exports/Colors.md +0 -1755
- package/data/zeroheight-exports/Content-Card.md +0 -309
- package/data/zeroheight-exports/Contributing-to-SDS.md +0 -98
- package/data/zeroheight-exports/Control-Inputs.md +0 -2818
- package/data/zeroheight-exports/Corners.md +0 -104
- package/data/zeroheight-exports/Cover-page.md +0 -3
- package/data/zeroheight-exports/Dialog.md +0 -1367
- package/data/zeroheight-exports/Downloads.md +0 -236
- package/data/zeroheight-exports/Drop-Shadows.md +0 -79
- package/data/zeroheight-exports/Dropdown-Input.md +0 -1776
- package/data/zeroheight-exports/Dropdown-Menus.md +0 -913
- package/data/zeroheight-exports/Element-Status-Tracker.md +0 -267
- package/data/zeroheight-exports/Field-Inputs.md +0 -1405
- package/data/zeroheight-exports/Filters.md +0 -1111
- package/data/zeroheight-exports/Getting-Started.md +0 -1157
- package/data/zeroheight-exports/Hero.md +0 -414
- package/data/zeroheight-exports/Icons.md +0 -2067
- package/data/zeroheight-exports/Intent.md +0 -86
- package/data/zeroheight-exports/Introduction-to-Bases.md +0 -35
- package/data/zeroheight-exports/Introduction-to-Cells.md +0 -35
- package/data/zeroheight-exports/Introduction-to-DNA.md +0 -35
- package/data/zeroheight-exports/Introduction-to-Genes.md +0 -35
- package/data/zeroheight-exports/Introduction.md +0 -115
- package/data/zeroheight-exports/Link.md +0 -432
- package/data/zeroheight-exports/Lists.md +0 -986
- package/data/zeroheight-exports/Loading-Indicator.md +0 -41
- package/data/zeroheight-exports/Navigation.md +0 -1190
- package/data/zeroheight-exports/Notification.md +0 -1038
- package/data/zeroheight-exports/Panel.md +0 -1030
- package/data/zeroheight-exports/README.md +0 -122
- package/data/zeroheight-exports/Responsive-Design.md +0 -330
- package/data/zeroheight-exports/Search-Input.md +0 -91
- package/data/zeroheight-exports/Search.md +0 -822
- package/data/zeroheight-exports/Segmented-Control.md +0 -609
- package/data/zeroheight-exports/Spacing.md +0 -297
- package/data/zeroheight-exports/Table.md +0 -5345
- package/data/zeroheight-exports/Tabs.md +0 -1324
- package/data/zeroheight-exports/Tags.md +0 -1545
- package/data/zeroheight-exports/Text-Link.md +0 -268
- package/data/zeroheight-exports/Theming.md +0 -362
- package/data/zeroheight-exports/Tooltips.md +0 -2015
- package/data/zeroheight-exports/Typography.md +0 -800
|
@@ -0,0 +1,2947 @@
|
|
|
1
|
+
# PreComposedTable
|
|
2
|
+
|
|
3
|
+
This component is a fully-featured, composable table built on top of TanStack Table with built-in support for sorting, pagination, row selection, filtering, column pinning, and responsive sizing
|
|
4
|
+
|
|
5
|
+
## Source Code
|
|
6
|
+
|
|
7
|
+
The component's source code in the SDS codebase can be found [here](https://github.com/chanzuckerberg/sci-components/blob/main/packages/components/src/core/PreComposedTable/index.tsx).
|
|
8
|
+
|
|
9
|
+
## Import
|
|
10
|
+
|
|
11
|
+
**React TypeScript**
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { PreComposedTable } from "@czi-sds/components";
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Code examples
|
|
18
|
+
|
|
19
|
+
> **Note:** PreComposedTable runs on [TanStack Table](https://tanstack.com/table/latest), which SDS takes as a peer dependency. Install `@tanstack/react-table` alongside it. Column definitions are TanStack's own, so anything its docs describe applies here too.
|
|
20
|
+
|
|
21
|
+
### Default
|
|
22
|
+
|
|
23
|
+
Data, columns, and the features you want switched on (here sorting, row selection and pagination), with Name pinned to the left edge and Actions to the right. A column left as a plain accessor is drawn as a CellHeader and a CellBasic, while the cells that render something else forward the element and width they are handed. Note that `data`, `columns` and `paginationConfig` sit outside the component so their identity is stable: a new `paginationConfig` object sends the table back to its first page.
|
|
24
|
+
|
|
25
|
+
**Example: DefaultPreComposedTable**
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
// The Default story: sorting, row selection and pagination switched on over the
|
|
29
|
+
// full column set, with Name pinned to the left edge and Actions to the right.
|
|
30
|
+
//
|
|
31
|
+
// A column left as a plain accessor is drawn as a CellHeader and a CellBasic.
|
|
32
|
+
// A renderer of your own is handed the element and the width to draw with as
|
|
33
|
+
// `as` and `style`, and forwards both. Without `as` it renders a second <td>
|
|
34
|
+
// inside the one the table already drew. A header renderer is handed
|
|
35
|
+
// `data-pinned-edge` too, which is what tells a pinned header to draw its
|
|
36
|
+
// gradient.
|
|
37
|
+
//
|
|
38
|
+
// data, columns and paginationConfig sit outside the component so their
|
|
39
|
+
// identity is stable: a new paginationConfig object sends the table back to its
|
|
40
|
+
// first page.
|
|
41
|
+
|
|
42
|
+
import {
|
|
43
|
+
Button,
|
|
44
|
+
CellBasic,
|
|
45
|
+
CellComponent,
|
|
46
|
+
CellHeader,
|
|
47
|
+
Icon,
|
|
48
|
+
PreComposedTable,
|
|
49
|
+
Tag,
|
|
50
|
+
} from "@czi-sds/components";
|
|
51
|
+
import type {
|
|
52
|
+
CellContext,
|
|
53
|
+
ColumnDef,
|
|
54
|
+
HeaderContext,
|
|
55
|
+
} from "@tanstack/react-table";
|
|
56
|
+
import * as React from "react";
|
|
57
|
+
|
|
58
|
+
type Employee = {
|
|
59
|
+
department: string;
|
|
60
|
+
email: string;
|
|
61
|
+
lastActive: string;
|
|
62
|
+
location: string;
|
|
63
|
+
name: string;
|
|
64
|
+
projects: number;
|
|
65
|
+
role: string;
|
|
66
|
+
salary: number;
|
|
67
|
+
startDate: string;
|
|
68
|
+
status: "Active" | "Inactive";
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const DATA: Employee[] = [
|
|
72
|
+
{
|
|
73
|
+
department: "Engineering",
|
|
74
|
+
email: "john.doe@example.com",
|
|
75
|
+
lastActive: "2024-01-15",
|
|
76
|
+
location: "San Francisco",
|
|
77
|
+
name: "John Doe",
|
|
78
|
+
projects: 5,
|
|
79
|
+
role: "Software Engineer",
|
|
80
|
+
salary: 85000,
|
|
81
|
+
startDate: "2022-03-15",
|
|
82
|
+
status: "Active",
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
department: "Product",
|
|
86
|
+
email: "jane.smith@example.com",
|
|
87
|
+
lastActive: "2024-01-14",
|
|
88
|
+
location: "New York",
|
|
89
|
+
name: "Jane Smith",
|
|
90
|
+
projects: 3,
|
|
91
|
+
role: "Product Manager",
|
|
92
|
+
salary: 95000,
|
|
93
|
+
startDate: "2021-08-20",
|
|
94
|
+
status: "Active",
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
department: "Design",
|
|
98
|
+
email: "bob.johnson@example.com",
|
|
99
|
+
lastActive: "2024-01-10",
|
|
100
|
+
location: "Austin",
|
|
101
|
+
name: "Bob Johnson",
|
|
102
|
+
projects: 2,
|
|
103
|
+
role: "Designer",
|
|
104
|
+
salary: 75000,
|
|
105
|
+
startDate: "2023-01-10",
|
|
106
|
+
status: "Inactive",
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
department: "Data",
|
|
110
|
+
email: "alice.brown@example.com",
|
|
111
|
+
lastActive: "2024-01-16",
|
|
112
|
+
location: "Seattle",
|
|
113
|
+
name: "Alice Brown",
|
|
114
|
+
projects: 7,
|
|
115
|
+
role: "Data Scientist",
|
|
116
|
+
salary: 90000,
|
|
117
|
+
startDate: "2022-11-05",
|
|
118
|
+
status: "Active",
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
department: "Engineering",
|
|
122
|
+
email: "charlie.wilson@example.com",
|
|
123
|
+
lastActive: "2024-01-15",
|
|
124
|
+
location: "Remote",
|
|
125
|
+
name: "Charlie Wilson",
|
|
126
|
+
projects: 4,
|
|
127
|
+
role: "DevOps Engineer",
|
|
128
|
+
salary: 88000,
|
|
129
|
+
startDate: "2023-06-12",
|
|
130
|
+
status: "Active",
|
|
131
|
+
},
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
/** The cell context, plus how PreComposedTable wants the cell drawn. */
|
|
135
|
+
type CellProps = CellContext<Employee, unknown> & {
|
|
136
|
+
as?: React.ElementType;
|
|
137
|
+
style?: React.CSSProperties;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
/** The header context, plus which edge a pinned header sits against. */
|
|
141
|
+
type HeaderProps = HeaderContext<Employee, unknown> & {
|
|
142
|
+
"data-pinned-edge"?: "left" | "right";
|
|
143
|
+
style?: React.CSSProperties;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
function textCell({ as, getValue, style }: CellProps) {
|
|
147
|
+
return (
|
|
148
|
+
<CellBasic
|
|
149
|
+
as={as}
|
|
150
|
+
primaryText={String(getValue())}
|
|
151
|
+
shouldShowTooltipOnHover={false}
|
|
152
|
+
style={style}
|
|
153
|
+
/>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function numberCell({ as, column, getValue, style }: CellProps) {
|
|
158
|
+
return (
|
|
159
|
+
<CellBasic
|
|
160
|
+
as={as}
|
|
161
|
+
// Room for the sort chevron, which would otherwise sit on the number.
|
|
162
|
+
extraRightPadding={column.getCanSort() ? 20 : 0}
|
|
163
|
+
horizontalAlign="right"
|
|
164
|
+
primaryText={String(getValue())}
|
|
165
|
+
shouldShowTooltipOnHover={false}
|
|
166
|
+
style={style}
|
|
167
|
+
tabularNums
|
|
168
|
+
/>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const COLUMNS: ColumnDef<Employee>[] = [
|
|
173
|
+
{
|
|
174
|
+
accessorKey: "name",
|
|
175
|
+
cell: ({ as, getValue, row, style }: CellProps) => (
|
|
176
|
+
<CellBasic
|
|
177
|
+
as={as}
|
|
178
|
+
link={`mailto:${row.original.email}`}
|
|
179
|
+
linkComponent="a"
|
|
180
|
+
linkProps={{ target: "_blank" }}
|
|
181
|
+
primaryText={getValue() as string}
|
|
182
|
+
secondaryText={row.original.role}
|
|
183
|
+
shouldShowTooltipOnHover={false}
|
|
184
|
+
shouldShowUnderlineOnHover
|
|
185
|
+
style={style}
|
|
186
|
+
/>
|
|
187
|
+
),
|
|
188
|
+
enableSorting: false,
|
|
189
|
+
header: ({ "data-pinned-edge": pinnedEdge, style }: HeaderProps) => (
|
|
190
|
+
<CellHeader
|
|
191
|
+
data-pinned-edge={pinnedEdge}
|
|
192
|
+
hideSortIcon
|
|
193
|
+
hover
|
|
194
|
+
shouldShowTooltipOnHover
|
|
195
|
+
style={style}
|
|
196
|
+
tooltipProps={{ placement: "top", textAlign: "left", width: "wide" }}
|
|
197
|
+
tooltipSubtitle="Each name links to the person's email address."
|
|
198
|
+
tooltipText="Name"
|
|
199
|
+
>
|
|
200
|
+
Name
|
|
201
|
+
</CellHeader>
|
|
202
|
+
),
|
|
203
|
+
meta: { isGrow: true, pinning: "left" },
|
|
204
|
+
minSize: 150,
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
accessorKey: "email",
|
|
208
|
+
cell: ({ as, getValue, style }: CellProps) => (
|
|
209
|
+
<CellBasic
|
|
210
|
+
as={as}
|
|
211
|
+
link={`mailto:${getValue() as string}`}
|
|
212
|
+
linkProps={{ target: "_blank" }}
|
|
213
|
+
primaryText={getValue() as string}
|
|
214
|
+
primaryTextWrapLineCount={1}
|
|
215
|
+
shouldShowTooltipOnHover={false}
|
|
216
|
+
shouldShowUnderlineOnHover
|
|
217
|
+
style={style}
|
|
218
|
+
/>
|
|
219
|
+
),
|
|
220
|
+
header: "Email",
|
|
221
|
+
meta: { isGrow: true },
|
|
222
|
+
minSize: 200,
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
accessorKey: "role",
|
|
226
|
+
cell: textCell,
|
|
227
|
+
header: "Role",
|
|
228
|
+
meta: { verticalAlign: "middle" },
|
|
229
|
+
minSize: 150,
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
accessorKey: "department",
|
|
233
|
+
cell: textCell,
|
|
234
|
+
header: "Department",
|
|
235
|
+
minSize: 120,
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
accessorKey: "status",
|
|
239
|
+
cell: ({ as, getValue, style }: CellProps) => (
|
|
240
|
+
<CellComponent as={as} style={style}>
|
|
241
|
+
<Tag
|
|
242
|
+
color={getValue() === "Active" ? "positive" : "negative"}
|
|
243
|
+
hover={false}
|
|
244
|
+
label={getValue() as string}
|
|
245
|
+
sdsStyle="rounded"
|
|
246
|
+
sdsType="secondary"
|
|
247
|
+
/>
|
|
248
|
+
</CellComponent>
|
|
249
|
+
),
|
|
250
|
+
header: "Status",
|
|
251
|
+
minSize: 100,
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
accessorKey: "lastActive",
|
|
255
|
+
cell: numberCell,
|
|
256
|
+
header: "Last Active",
|
|
257
|
+
meta: { headerCellProps: { horizontalAlign: "right" } },
|
|
258
|
+
minSize: 120,
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
accessorKey: "projects",
|
|
262
|
+
cell: numberCell,
|
|
263
|
+
header: "Projects",
|
|
264
|
+
meta: { headerCellProps: { horizontalAlign: "right" } },
|
|
265
|
+
minSize: 100,
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
accessorKey: "salary",
|
|
269
|
+
cell: ({ as, column, getValue, style }: CellProps) => (
|
|
270
|
+
<CellBasic
|
|
271
|
+
as={as}
|
|
272
|
+
extraRightPadding={column.getCanSort() ? 20 : 0}
|
|
273
|
+
horizontalAlign="right"
|
|
274
|
+
primaryText={`${(getValue() as number).toLocaleString()}`}
|
|
275
|
+
shouldShowTooltipOnHover={false}
|
|
276
|
+
style={style}
|
|
277
|
+
tabularNums
|
|
278
|
+
/>
|
|
279
|
+
),
|
|
280
|
+
header: "Salary",
|
|
281
|
+
meta: { headerCellProps: { horizontalAlign: "right" } },
|
|
282
|
+
minSize: 120,
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
accessorKey: "location",
|
|
286
|
+
cell: textCell,
|
|
287
|
+
header: "Location",
|
|
288
|
+
minSize: 150,
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
accessorKey: "startDate",
|
|
292
|
+
cell: numberCell,
|
|
293
|
+
header: "Start Date",
|
|
294
|
+
meta: { headerCellProps: { horizontalAlign: "right" } },
|
|
295
|
+
minSize: 120,
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
cell: ({ as, row, style }: CellProps) => (
|
|
299
|
+
<CellComponent as={as} horizontalAlign="right" style={style}>
|
|
300
|
+
<Button
|
|
301
|
+
aria-label={`Edit ${row.original.name}`}
|
|
302
|
+
onClick={() => console.log("Edit", row.original)}
|
|
303
|
+
sdsStyle="minimal"
|
|
304
|
+
sdsType="secondary"
|
|
305
|
+
>
|
|
306
|
+
<Icon sdsIcon="Edit" sdsSize="s" />
|
|
307
|
+
</Button>
|
|
308
|
+
<Button
|
|
309
|
+
aria-label={`Download ${row.original.name}`}
|
|
310
|
+
onClick={() => console.log("Download", row.original)}
|
|
311
|
+
sdsStyle="minimal"
|
|
312
|
+
sdsType="secondary"
|
|
313
|
+
>
|
|
314
|
+
<Icon sdsIcon="Download" sdsSize="s" />
|
|
315
|
+
</Button>
|
|
316
|
+
</CellComponent>
|
|
317
|
+
),
|
|
318
|
+
enableSorting: false,
|
|
319
|
+
header: "Actions",
|
|
320
|
+
id: "actions",
|
|
321
|
+
meta: {
|
|
322
|
+
headerCellProps: { horizontalAlign: "right" },
|
|
323
|
+
pinning: "right",
|
|
324
|
+
verticalAlign: "middle",
|
|
325
|
+
},
|
|
326
|
+
size: 100,
|
|
327
|
+
},
|
|
328
|
+
];
|
|
329
|
+
|
|
330
|
+
const PAGINATION = { pageSize: 10 };
|
|
331
|
+
|
|
332
|
+
function App() {
|
|
333
|
+
return (
|
|
334
|
+
<div className="app">
|
|
335
|
+
<PreComposedTable<Employee>
|
|
336
|
+
columns={COLUMNS}
|
|
337
|
+
data={DATA}
|
|
338
|
+
enablePagination
|
|
339
|
+
enableRowSelection
|
|
340
|
+
enableSorting
|
|
341
|
+
paginationConfig={PAGINATION}
|
|
342
|
+
/>
|
|
343
|
+
</div>
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export default App;
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### With global filtering
|
|
351
|
+
|
|
352
|
+
`enableGlobalFiltering` puts a search input above the table that matches across every column at once. The table holds the search term itself, so there is nothing to wire up.
|
|
353
|
+
|
|
354
|
+
**Example: PreComposedTableWithGlobalFiltering**
|
|
355
|
+
|
|
356
|
+
```tsx
|
|
357
|
+
// The WithGlobalFiltering story: enableGlobalFiltering puts a search input
|
|
358
|
+
// above the table that matches across every column at once. The table holds the
|
|
359
|
+
// search term itself, so there is nothing to wire up, and sorting and selection
|
|
360
|
+
// keep working on whatever rows are left.
|
|
361
|
+
//
|
|
362
|
+
// A column left as a plain accessor is drawn as a CellHeader and a CellBasic.
|
|
363
|
+
// A renderer of your own is handed the element and the width to draw with as
|
|
364
|
+
// `as` and `style`, and forwards both. Without `as` it renders a second <td>
|
|
365
|
+
// inside the one the table already drew. A header renderer is handed
|
|
366
|
+
// `data-pinned-edge` too, which is what tells a pinned header to draw its
|
|
367
|
+
// gradient.
|
|
368
|
+
//
|
|
369
|
+
// data and columns sit outside the component so their identity is stable.
|
|
370
|
+
|
|
371
|
+
import {
|
|
372
|
+
Button,
|
|
373
|
+
CellBasic,
|
|
374
|
+
CellComponent,
|
|
375
|
+
CellHeader,
|
|
376
|
+
Icon,
|
|
377
|
+
PreComposedTable,
|
|
378
|
+
Tag,
|
|
379
|
+
} from "@czi-sds/components";
|
|
380
|
+
import type {
|
|
381
|
+
CellContext,
|
|
382
|
+
ColumnDef,
|
|
383
|
+
HeaderContext,
|
|
384
|
+
} from "@tanstack/react-table";
|
|
385
|
+
import * as React from "react";
|
|
386
|
+
|
|
387
|
+
type Employee = {
|
|
388
|
+
department: string;
|
|
389
|
+
email: string;
|
|
390
|
+
lastActive: string;
|
|
391
|
+
location: string;
|
|
392
|
+
name: string;
|
|
393
|
+
projects: number;
|
|
394
|
+
role: string;
|
|
395
|
+
salary: number;
|
|
396
|
+
startDate: string;
|
|
397
|
+
status: "Active" | "Inactive";
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
const DATA: Employee[] = [
|
|
401
|
+
{
|
|
402
|
+
department: "Engineering",
|
|
403
|
+
email: "john.doe@example.com",
|
|
404
|
+
lastActive: "2024-01-15",
|
|
405
|
+
location: "San Francisco",
|
|
406
|
+
name: "John Doe",
|
|
407
|
+
projects: 5,
|
|
408
|
+
role: "Software Engineer",
|
|
409
|
+
salary: 85000,
|
|
410
|
+
startDate: "2022-03-15",
|
|
411
|
+
status: "Active",
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
department: "Product",
|
|
415
|
+
email: "jane.smith@example.com",
|
|
416
|
+
lastActive: "2024-01-14",
|
|
417
|
+
location: "New York",
|
|
418
|
+
name: "Jane Smith",
|
|
419
|
+
projects: 3,
|
|
420
|
+
role: "Product Manager",
|
|
421
|
+
salary: 95000,
|
|
422
|
+
startDate: "2021-08-20",
|
|
423
|
+
status: "Active",
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
department: "Design",
|
|
427
|
+
email: "bob.johnson@example.com",
|
|
428
|
+
lastActive: "2024-01-10",
|
|
429
|
+
location: "Austin",
|
|
430
|
+
name: "Bob Johnson",
|
|
431
|
+
projects: 2,
|
|
432
|
+
role: "Designer",
|
|
433
|
+
salary: 75000,
|
|
434
|
+
startDate: "2023-01-10",
|
|
435
|
+
status: "Inactive",
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
department: "Data",
|
|
439
|
+
email: "alice.brown@example.com",
|
|
440
|
+
lastActive: "2024-01-16",
|
|
441
|
+
location: "Seattle",
|
|
442
|
+
name: "Alice Brown",
|
|
443
|
+
projects: 7,
|
|
444
|
+
role: "Data Scientist",
|
|
445
|
+
salary: 90000,
|
|
446
|
+
startDate: "2022-11-05",
|
|
447
|
+
status: "Active",
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
department: "Engineering",
|
|
451
|
+
email: "charlie.wilson@example.com",
|
|
452
|
+
lastActive: "2024-01-15",
|
|
453
|
+
location: "Remote",
|
|
454
|
+
name: "Charlie Wilson",
|
|
455
|
+
projects: 4,
|
|
456
|
+
role: "DevOps Engineer",
|
|
457
|
+
salary: 88000,
|
|
458
|
+
startDate: "2023-06-12",
|
|
459
|
+
status: "Active",
|
|
460
|
+
},
|
|
461
|
+
];
|
|
462
|
+
|
|
463
|
+
/** The cell context, plus how PreComposedTable wants the cell drawn. */
|
|
464
|
+
type CellProps = CellContext<Employee, unknown> & {
|
|
465
|
+
as?: React.ElementType;
|
|
466
|
+
style?: React.CSSProperties;
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
/** The header context, plus which edge a pinned header sits against. */
|
|
470
|
+
type HeaderProps = HeaderContext<Employee, unknown> & {
|
|
471
|
+
"data-pinned-edge"?: "left" | "right";
|
|
472
|
+
style?: React.CSSProperties;
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
function textCell({ as, getValue, style }: CellProps) {
|
|
476
|
+
return (
|
|
477
|
+
<CellBasic
|
|
478
|
+
as={as}
|
|
479
|
+
primaryText={String(getValue())}
|
|
480
|
+
shouldShowTooltipOnHover={false}
|
|
481
|
+
style={style}
|
|
482
|
+
/>
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function numberCell({ as, column, getValue, style }: CellProps) {
|
|
487
|
+
return (
|
|
488
|
+
<CellBasic
|
|
489
|
+
as={as}
|
|
490
|
+
// Room for the sort chevron, which would otherwise sit on the number.
|
|
491
|
+
extraRightPadding={column.getCanSort() ? 20 : 0}
|
|
492
|
+
horizontalAlign="right"
|
|
493
|
+
primaryText={String(getValue())}
|
|
494
|
+
shouldShowTooltipOnHover={false}
|
|
495
|
+
style={style}
|
|
496
|
+
tabularNums
|
|
497
|
+
/>
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
const COLUMNS: ColumnDef<Employee>[] = [
|
|
502
|
+
{
|
|
503
|
+
accessorKey: "name",
|
|
504
|
+
cell: ({ as, getValue, row, style }: CellProps) => (
|
|
505
|
+
<CellBasic
|
|
506
|
+
as={as}
|
|
507
|
+
link={`mailto:${row.original.email}`}
|
|
508
|
+
linkComponent="a"
|
|
509
|
+
linkProps={{ target: "_blank" }}
|
|
510
|
+
primaryText={getValue() as string}
|
|
511
|
+
secondaryText={row.original.role}
|
|
512
|
+
shouldShowTooltipOnHover={false}
|
|
513
|
+
shouldShowUnderlineOnHover
|
|
514
|
+
style={style}
|
|
515
|
+
/>
|
|
516
|
+
),
|
|
517
|
+
enableSorting: false,
|
|
518
|
+
header: ({ "data-pinned-edge": pinnedEdge, style }: HeaderProps) => (
|
|
519
|
+
<CellHeader
|
|
520
|
+
data-pinned-edge={pinnedEdge}
|
|
521
|
+
hideSortIcon
|
|
522
|
+
hover
|
|
523
|
+
shouldShowTooltipOnHover
|
|
524
|
+
style={style}
|
|
525
|
+
tooltipProps={{ placement: "top", textAlign: "left", width: "wide" }}
|
|
526
|
+
tooltipSubtitle="Each name links to the person's email address."
|
|
527
|
+
tooltipText="Name"
|
|
528
|
+
>
|
|
529
|
+
Name
|
|
530
|
+
</CellHeader>
|
|
531
|
+
),
|
|
532
|
+
meta: { isGrow: true, pinning: "left" },
|
|
533
|
+
minSize: 150,
|
|
534
|
+
},
|
|
535
|
+
{
|
|
536
|
+
accessorKey: "email",
|
|
537
|
+
cell: ({ as, getValue, style }: CellProps) => (
|
|
538
|
+
<CellBasic
|
|
539
|
+
as={as}
|
|
540
|
+
link={`mailto:${getValue() as string}`}
|
|
541
|
+
linkProps={{ target: "_blank" }}
|
|
542
|
+
primaryText={getValue() as string}
|
|
543
|
+
primaryTextWrapLineCount={1}
|
|
544
|
+
shouldShowTooltipOnHover={false}
|
|
545
|
+
shouldShowUnderlineOnHover
|
|
546
|
+
style={style}
|
|
547
|
+
/>
|
|
548
|
+
),
|
|
549
|
+
header: "Email",
|
|
550
|
+
meta: { isGrow: true },
|
|
551
|
+
minSize: 200,
|
|
552
|
+
},
|
|
553
|
+
{
|
|
554
|
+
accessorKey: "role",
|
|
555
|
+
cell: textCell,
|
|
556
|
+
header: "Role",
|
|
557
|
+
meta: { verticalAlign: "middle" },
|
|
558
|
+
minSize: 150,
|
|
559
|
+
},
|
|
560
|
+
{
|
|
561
|
+
accessorKey: "department",
|
|
562
|
+
cell: textCell,
|
|
563
|
+
header: "Department",
|
|
564
|
+
minSize: 120,
|
|
565
|
+
},
|
|
566
|
+
{
|
|
567
|
+
accessorKey: "status",
|
|
568
|
+
cell: ({ as, getValue, style }: CellProps) => (
|
|
569
|
+
<CellComponent as={as} style={style}>
|
|
570
|
+
<Tag
|
|
571
|
+
color={getValue() === "Active" ? "positive" : "negative"}
|
|
572
|
+
hover={false}
|
|
573
|
+
label={getValue() as string}
|
|
574
|
+
sdsStyle="rounded"
|
|
575
|
+
sdsType="secondary"
|
|
576
|
+
/>
|
|
577
|
+
</CellComponent>
|
|
578
|
+
),
|
|
579
|
+
header: "Status",
|
|
580
|
+
minSize: 100,
|
|
581
|
+
},
|
|
582
|
+
{
|
|
583
|
+
accessorKey: "lastActive",
|
|
584
|
+
cell: numberCell,
|
|
585
|
+
header: "Last Active",
|
|
586
|
+
meta: { headerCellProps: { horizontalAlign: "right" } },
|
|
587
|
+
minSize: 120,
|
|
588
|
+
},
|
|
589
|
+
{
|
|
590
|
+
accessorKey: "projects",
|
|
591
|
+
cell: numberCell,
|
|
592
|
+
header: "Projects",
|
|
593
|
+
meta: { headerCellProps: { horizontalAlign: "right" } },
|
|
594
|
+
minSize: 100,
|
|
595
|
+
},
|
|
596
|
+
{
|
|
597
|
+
accessorKey: "salary",
|
|
598
|
+
cell: ({ as, column, getValue, style }: CellProps) => (
|
|
599
|
+
<CellBasic
|
|
600
|
+
as={as}
|
|
601
|
+
extraRightPadding={column.getCanSort() ? 20 : 0}
|
|
602
|
+
horizontalAlign="right"
|
|
603
|
+
primaryText={`${(getValue() as number).toLocaleString()}`}
|
|
604
|
+
shouldShowTooltipOnHover={false}
|
|
605
|
+
style={style}
|
|
606
|
+
tabularNums
|
|
607
|
+
/>
|
|
608
|
+
),
|
|
609
|
+
header: "Salary",
|
|
610
|
+
meta: { headerCellProps: { horizontalAlign: "right" } },
|
|
611
|
+
minSize: 120,
|
|
612
|
+
},
|
|
613
|
+
{
|
|
614
|
+
accessorKey: "location",
|
|
615
|
+
cell: textCell,
|
|
616
|
+
header: "Location",
|
|
617
|
+
minSize: 150,
|
|
618
|
+
},
|
|
619
|
+
{
|
|
620
|
+
accessorKey: "startDate",
|
|
621
|
+
cell: numberCell,
|
|
622
|
+
header: "Start Date",
|
|
623
|
+
meta: { headerCellProps: { horizontalAlign: "right" } },
|
|
624
|
+
minSize: 120,
|
|
625
|
+
},
|
|
626
|
+
{
|
|
627
|
+
cell: ({ as, row, style }: CellProps) => (
|
|
628
|
+
<CellComponent as={as} horizontalAlign="right" style={style}>
|
|
629
|
+
<Button
|
|
630
|
+
aria-label={`Edit ${row.original.name}`}
|
|
631
|
+
onClick={() => console.log("Edit", row.original)}
|
|
632
|
+
sdsStyle="minimal"
|
|
633
|
+
sdsType="secondary"
|
|
634
|
+
>
|
|
635
|
+
<Icon sdsIcon="Edit" sdsSize="s" />
|
|
636
|
+
</Button>
|
|
637
|
+
<Button
|
|
638
|
+
aria-label={`Download ${row.original.name}`}
|
|
639
|
+
onClick={() => console.log("Download", row.original)}
|
|
640
|
+
sdsStyle="minimal"
|
|
641
|
+
sdsType="secondary"
|
|
642
|
+
>
|
|
643
|
+
<Icon sdsIcon="Download" sdsSize="s" />
|
|
644
|
+
</Button>
|
|
645
|
+
</CellComponent>
|
|
646
|
+
),
|
|
647
|
+
enableSorting: false,
|
|
648
|
+
header: "Actions",
|
|
649
|
+
id: "actions",
|
|
650
|
+
meta: {
|
|
651
|
+
headerCellProps: { horizontalAlign: "right" },
|
|
652
|
+
pinning: "right",
|
|
653
|
+
verticalAlign: "middle",
|
|
654
|
+
},
|
|
655
|
+
size: 100,
|
|
656
|
+
},
|
|
657
|
+
];
|
|
658
|
+
|
|
659
|
+
function App() {
|
|
660
|
+
return (
|
|
661
|
+
<div className="app">
|
|
662
|
+
<PreComposedTable<Employee>
|
|
663
|
+
columns={COLUMNS}
|
|
664
|
+
data={DATA}
|
|
665
|
+
enableGlobalFiltering
|
|
666
|
+
enableRowSelection
|
|
667
|
+
enableSorting
|
|
668
|
+
/>
|
|
669
|
+
</div>
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
export default App;
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
### With pagination
|
|
677
|
+
|
|
678
|
+
The same table paged three rows at a time. `paginationConfig` takes Pagination's own props, so `pageSize` sets the size of every page and `placement` moves the controls, which sit on the right by default.
|
|
679
|
+
|
|
680
|
+
**Example: PreComposedTableWithPagination**
|
|
681
|
+
|
|
682
|
+
```tsx
|
|
683
|
+
// The WithPagination story: the same table paged three rows at a time.
|
|
684
|
+
// paginationConfig takes the Pagination component's own props, so pageSize sets
|
|
685
|
+
// both the initial page size and the size of every page after it, and
|
|
686
|
+
// placement moves the controls, which sit on the right by default.
|
|
687
|
+
//
|
|
688
|
+
// A column left as a plain accessor is drawn as a CellHeader and a CellBasic.
|
|
689
|
+
// A renderer of your own is handed the element and the width to draw with as
|
|
690
|
+
// `as` and `style`, and forwards both. Without `as` it renders a second <td>
|
|
691
|
+
// inside the one the table already drew. A header renderer is handed
|
|
692
|
+
// `data-pinned-edge` too, which is what tells a pinned header to draw its
|
|
693
|
+
// gradient.
|
|
694
|
+
//
|
|
695
|
+
// data, columns and paginationConfig sit outside the component so their
|
|
696
|
+
// identity is stable: a new paginationConfig object sends the table back to its
|
|
697
|
+
// first page.
|
|
698
|
+
|
|
699
|
+
import {
|
|
700
|
+
Button,
|
|
701
|
+
CellBasic,
|
|
702
|
+
CellComponent,
|
|
703
|
+
CellHeader,
|
|
704
|
+
Icon,
|
|
705
|
+
PreComposedTable,
|
|
706
|
+
Tag,
|
|
707
|
+
} from "@czi-sds/components";
|
|
708
|
+
import type {
|
|
709
|
+
CellContext,
|
|
710
|
+
ColumnDef,
|
|
711
|
+
HeaderContext,
|
|
712
|
+
} from "@tanstack/react-table";
|
|
713
|
+
import * as React from "react";
|
|
714
|
+
|
|
715
|
+
type Employee = {
|
|
716
|
+
department: string;
|
|
717
|
+
email: string;
|
|
718
|
+
lastActive: string;
|
|
719
|
+
location: string;
|
|
720
|
+
name: string;
|
|
721
|
+
projects: number;
|
|
722
|
+
role: string;
|
|
723
|
+
salary: number;
|
|
724
|
+
startDate: string;
|
|
725
|
+
status: "Active" | "Inactive";
|
|
726
|
+
};
|
|
727
|
+
|
|
728
|
+
const DATA: Employee[] = [
|
|
729
|
+
{
|
|
730
|
+
department: "Engineering",
|
|
731
|
+
email: "john.doe@example.com",
|
|
732
|
+
lastActive: "2024-01-15",
|
|
733
|
+
location: "San Francisco",
|
|
734
|
+
name: "John Doe",
|
|
735
|
+
projects: 5,
|
|
736
|
+
role: "Software Engineer",
|
|
737
|
+
salary: 85000,
|
|
738
|
+
startDate: "2022-03-15",
|
|
739
|
+
status: "Active",
|
|
740
|
+
},
|
|
741
|
+
{
|
|
742
|
+
department: "Product",
|
|
743
|
+
email: "jane.smith@example.com",
|
|
744
|
+
lastActive: "2024-01-14",
|
|
745
|
+
location: "New York",
|
|
746
|
+
name: "Jane Smith",
|
|
747
|
+
projects: 3,
|
|
748
|
+
role: "Product Manager",
|
|
749
|
+
salary: 95000,
|
|
750
|
+
startDate: "2021-08-20",
|
|
751
|
+
status: "Active",
|
|
752
|
+
},
|
|
753
|
+
{
|
|
754
|
+
department: "Design",
|
|
755
|
+
email: "bob.johnson@example.com",
|
|
756
|
+
lastActive: "2024-01-10",
|
|
757
|
+
location: "Austin",
|
|
758
|
+
name: "Bob Johnson",
|
|
759
|
+
projects: 2,
|
|
760
|
+
role: "Designer",
|
|
761
|
+
salary: 75000,
|
|
762
|
+
startDate: "2023-01-10",
|
|
763
|
+
status: "Inactive",
|
|
764
|
+
},
|
|
765
|
+
{
|
|
766
|
+
department: "Data",
|
|
767
|
+
email: "alice.brown@example.com",
|
|
768
|
+
lastActive: "2024-01-16",
|
|
769
|
+
location: "Seattle",
|
|
770
|
+
name: "Alice Brown",
|
|
771
|
+
projects: 7,
|
|
772
|
+
role: "Data Scientist",
|
|
773
|
+
salary: 90000,
|
|
774
|
+
startDate: "2022-11-05",
|
|
775
|
+
status: "Active",
|
|
776
|
+
},
|
|
777
|
+
{
|
|
778
|
+
department: "Engineering",
|
|
779
|
+
email: "charlie.wilson@example.com",
|
|
780
|
+
lastActive: "2024-01-15",
|
|
781
|
+
location: "Remote",
|
|
782
|
+
name: "Charlie Wilson",
|
|
783
|
+
projects: 4,
|
|
784
|
+
role: "DevOps Engineer",
|
|
785
|
+
salary: 88000,
|
|
786
|
+
startDate: "2023-06-12",
|
|
787
|
+
status: "Active",
|
|
788
|
+
},
|
|
789
|
+
];
|
|
790
|
+
|
|
791
|
+
/** The cell context, plus how PreComposedTable wants the cell drawn. */
|
|
792
|
+
type CellProps = CellContext<Employee, unknown> & {
|
|
793
|
+
as?: React.ElementType;
|
|
794
|
+
style?: React.CSSProperties;
|
|
795
|
+
};
|
|
796
|
+
|
|
797
|
+
/** The header context, plus which edge a pinned header sits against. */
|
|
798
|
+
type HeaderProps = HeaderContext<Employee, unknown> & {
|
|
799
|
+
"data-pinned-edge"?: "left" | "right";
|
|
800
|
+
style?: React.CSSProperties;
|
|
801
|
+
};
|
|
802
|
+
|
|
803
|
+
function textCell({ as, getValue, style }: CellProps) {
|
|
804
|
+
return (
|
|
805
|
+
<CellBasic
|
|
806
|
+
as={as}
|
|
807
|
+
primaryText={String(getValue())}
|
|
808
|
+
shouldShowTooltipOnHover={false}
|
|
809
|
+
style={style}
|
|
810
|
+
/>
|
|
811
|
+
);
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
function numberCell({ as, column, getValue, style }: CellProps) {
|
|
815
|
+
return (
|
|
816
|
+
<CellBasic
|
|
817
|
+
as={as}
|
|
818
|
+
// Room for the sort chevron, which would otherwise sit on the number.
|
|
819
|
+
extraRightPadding={column.getCanSort() ? 20 : 0}
|
|
820
|
+
horizontalAlign="right"
|
|
821
|
+
primaryText={String(getValue())}
|
|
822
|
+
shouldShowTooltipOnHover={false}
|
|
823
|
+
style={style}
|
|
824
|
+
tabularNums
|
|
825
|
+
/>
|
|
826
|
+
);
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
const COLUMNS: ColumnDef<Employee>[] = [
|
|
830
|
+
{
|
|
831
|
+
accessorKey: "name",
|
|
832
|
+
cell: ({ as, getValue, row, style }: CellProps) => (
|
|
833
|
+
<CellBasic
|
|
834
|
+
as={as}
|
|
835
|
+
link={`mailto:${row.original.email}`}
|
|
836
|
+
linkComponent="a"
|
|
837
|
+
linkProps={{ target: "_blank" }}
|
|
838
|
+
primaryText={getValue() as string}
|
|
839
|
+
secondaryText={row.original.role}
|
|
840
|
+
shouldShowTooltipOnHover={false}
|
|
841
|
+
shouldShowUnderlineOnHover
|
|
842
|
+
style={style}
|
|
843
|
+
/>
|
|
844
|
+
),
|
|
845
|
+
enableSorting: false,
|
|
846
|
+
header: ({ "data-pinned-edge": pinnedEdge, style }: HeaderProps) => (
|
|
847
|
+
<CellHeader
|
|
848
|
+
data-pinned-edge={pinnedEdge}
|
|
849
|
+
hideSortIcon
|
|
850
|
+
hover
|
|
851
|
+
shouldShowTooltipOnHover
|
|
852
|
+
style={style}
|
|
853
|
+
tooltipProps={{ placement: "top", textAlign: "left", width: "wide" }}
|
|
854
|
+
tooltipSubtitle="Each name links to the person's email address."
|
|
855
|
+
tooltipText="Name"
|
|
856
|
+
>
|
|
857
|
+
Name
|
|
858
|
+
</CellHeader>
|
|
859
|
+
),
|
|
860
|
+
meta: { isGrow: true, pinning: "left" },
|
|
861
|
+
minSize: 150,
|
|
862
|
+
},
|
|
863
|
+
{
|
|
864
|
+
accessorKey: "email",
|
|
865
|
+
cell: ({ as, getValue, style }: CellProps) => (
|
|
866
|
+
<CellBasic
|
|
867
|
+
as={as}
|
|
868
|
+
link={`mailto:${getValue() as string}`}
|
|
869
|
+
linkProps={{ target: "_blank" }}
|
|
870
|
+
primaryText={getValue() as string}
|
|
871
|
+
primaryTextWrapLineCount={1}
|
|
872
|
+
shouldShowTooltipOnHover={false}
|
|
873
|
+
shouldShowUnderlineOnHover
|
|
874
|
+
style={style}
|
|
875
|
+
/>
|
|
876
|
+
),
|
|
877
|
+
header: "Email",
|
|
878
|
+
meta: { isGrow: true },
|
|
879
|
+
minSize: 200,
|
|
880
|
+
},
|
|
881
|
+
{
|
|
882
|
+
accessorKey: "role",
|
|
883
|
+
cell: textCell,
|
|
884
|
+
header: "Role",
|
|
885
|
+
meta: { verticalAlign: "middle" },
|
|
886
|
+
minSize: 150,
|
|
887
|
+
},
|
|
888
|
+
{
|
|
889
|
+
accessorKey: "department",
|
|
890
|
+
cell: textCell,
|
|
891
|
+
header: "Department",
|
|
892
|
+
minSize: 120,
|
|
893
|
+
},
|
|
894
|
+
{
|
|
895
|
+
accessorKey: "status",
|
|
896
|
+
cell: ({ as, getValue, style }: CellProps) => (
|
|
897
|
+
<CellComponent as={as} style={style}>
|
|
898
|
+
<Tag
|
|
899
|
+
color={getValue() === "Active" ? "positive" : "negative"}
|
|
900
|
+
hover={false}
|
|
901
|
+
label={getValue() as string}
|
|
902
|
+
sdsStyle="rounded"
|
|
903
|
+
sdsType="secondary"
|
|
904
|
+
/>
|
|
905
|
+
</CellComponent>
|
|
906
|
+
),
|
|
907
|
+
header: "Status",
|
|
908
|
+
minSize: 100,
|
|
909
|
+
},
|
|
910
|
+
{
|
|
911
|
+
accessorKey: "lastActive",
|
|
912
|
+
cell: numberCell,
|
|
913
|
+
header: "Last Active",
|
|
914
|
+
meta: { headerCellProps: { horizontalAlign: "right" } },
|
|
915
|
+
minSize: 120,
|
|
916
|
+
},
|
|
917
|
+
{
|
|
918
|
+
accessorKey: "projects",
|
|
919
|
+
cell: numberCell,
|
|
920
|
+
header: "Projects",
|
|
921
|
+
meta: { headerCellProps: { horizontalAlign: "right" } },
|
|
922
|
+
minSize: 100,
|
|
923
|
+
},
|
|
924
|
+
{
|
|
925
|
+
accessorKey: "salary",
|
|
926
|
+
cell: ({ as, column, getValue, style }: CellProps) => (
|
|
927
|
+
<CellBasic
|
|
928
|
+
as={as}
|
|
929
|
+
extraRightPadding={column.getCanSort() ? 20 : 0}
|
|
930
|
+
horizontalAlign="right"
|
|
931
|
+
primaryText={`${(getValue() as number).toLocaleString()}`}
|
|
932
|
+
shouldShowTooltipOnHover={false}
|
|
933
|
+
style={style}
|
|
934
|
+
tabularNums
|
|
935
|
+
/>
|
|
936
|
+
),
|
|
937
|
+
header: "Salary",
|
|
938
|
+
meta: { headerCellProps: { horizontalAlign: "right" } },
|
|
939
|
+
minSize: 120,
|
|
940
|
+
},
|
|
941
|
+
{
|
|
942
|
+
accessorKey: "location",
|
|
943
|
+
cell: textCell,
|
|
944
|
+
header: "Location",
|
|
945
|
+
minSize: 150,
|
|
946
|
+
},
|
|
947
|
+
{
|
|
948
|
+
accessorKey: "startDate",
|
|
949
|
+
cell: numberCell,
|
|
950
|
+
header: "Start Date",
|
|
951
|
+
meta: { headerCellProps: { horizontalAlign: "right" } },
|
|
952
|
+
minSize: 120,
|
|
953
|
+
},
|
|
954
|
+
{
|
|
955
|
+
cell: ({ as, row, style }: CellProps) => (
|
|
956
|
+
<CellComponent as={as} horizontalAlign="right" style={style}>
|
|
957
|
+
<Button
|
|
958
|
+
aria-label={`Edit ${row.original.name}`}
|
|
959
|
+
onClick={() => console.log("Edit", row.original)}
|
|
960
|
+
sdsStyle="minimal"
|
|
961
|
+
sdsType="secondary"
|
|
962
|
+
>
|
|
963
|
+
<Icon sdsIcon="Edit" sdsSize="s" />
|
|
964
|
+
</Button>
|
|
965
|
+
<Button
|
|
966
|
+
aria-label={`Download ${row.original.name}`}
|
|
967
|
+
onClick={() => console.log("Download", row.original)}
|
|
968
|
+
sdsStyle="minimal"
|
|
969
|
+
sdsType="secondary"
|
|
970
|
+
>
|
|
971
|
+
<Icon sdsIcon="Download" sdsSize="s" />
|
|
972
|
+
</Button>
|
|
973
|
+
</CellComponent>
|
|
974
|
+
),
|
|
975
|
+
enableSorting: false,
|
|
976
|
+
header: "Actions",
|
|
977
|
+
id: "actions",
|
|
978
|
+
meta: {
|
|
979
|
+
headerCellProps: { horizontalAlign: "right" },
|
|
980
|
+
pinning: "right",
|
|
981
|
+
verticalAlign: "middle",
|
|
982
|
+
},
|
|
983
|
+
size: 100,
|
|
984
|
+
},
|
|
985
|
+
];
|
|
986
|
+
|
|
987
|
+
const PAGINATION = { pageSize: 3 };
|
|
988
|
+
|
|
989
|
+
function App() {
|
|
990
|
+
return (
|
|
991
|
+
<div className="app">
|
|
992
|
+
<PreComposedTable<Employee>
|
|
993
|
+
columns={COLUMNS}
|
|
994
|
+
data={DATA}
|
|
995
|
+
enablePagination
|
|
996
|
+
enableRowSelection
|
|
997
|
+
enableSorting
|
|
998
|
+
paginationConfig={PAGINATION}
|
|
999
|
+
/>
|
|
1000
|
+
</div>
|
|
1001
|
+
);
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
export default App;
|
|
1005
|
+
```
|
|
1006
|
+
|
|
1007
|
+
### Fixed width
|
|
1008
|
+
|
|
1009
|
+
`tableWidth` pins the table to 600px rather than letting it fill its container. The columns need more room than that, so the table scrolls sideways while the pinned Name and Actions columns hold their edges.
|
|
1010
|
+
|
|
1011
|
+
**Example: PreComposedTableFixedWidth**
|
|
1012
|
+
|
|
1013
|
+
```tsx
|
|
1014
|
+
// The FixedWidth story: tableWidth pins the table to 600px instead of filling
|
|
1015
|
+
// its container. The columns need more room than that, so the table scrolls
|
|
1016
|
+
// sideways and the pinned Name and Actions columns hold their edges, each
|
|
1017
|
+
// drawing a gradient once there is something hidden behind it.
|
|
1018
|
+
//
|
|
1019
|
+
// A column left as a plain accessor is drawn as a CellHeader and a CellBasic.
|
|
1020
|
+
// A renderer of your own is handed the element and the width to draw with as
|
|
1021
|
+
// `as` and `style`, and forwards both. Without `as` it renders a second <td>
|
|
1022
|
+
// inside the one the table already drew. A header renderer is handed
|
|
1023
|
+
// `data-pinned-edge` too, which is what tells a pinned header to draw its
|
|
1024
|
+
// gradient.
|
|
1025
|
+
//
|
|
1026
|
+
// data, columns and paginationConfig sit outside the component so their
|
|
1027
|
+
// identity is stable: a new paginationConfig object sends the table back to its
|
|
1028
|
+
// first page.
|
|
1029
|
+
|
|
1030
|
+
import {
|
|
1031
|
+
Button,
|
|
1032
|
+
CellBasic,
|
|
1033
|
+
CellComponent,
|
|
1034
|
+
CellHeader,
|
|
1035
|
+
Icon,
|
|
1036
|
+
PreComposedTable,
|
|
1037
|
+
Tag,
|
|
1038
|
+
} from "@czi-sds/components";
|
|
1039
|
+
import type {
|
|
1040
|
+
CellContext,
|
|
1041
|
+
ColumnDef,
|
|
1042
|
+
HeaderContext,
|
|
1043
|
+
} from "@tanstack/react-table";
|
|
1044
|
+
import * as React from "react";
|
|
1045
|
+
|
|
1046
|
+
type Employee = {
|
|
1047
|
+
department: string;
|
|
1048
|
+
email: string;
|
|
1049
|
+
lastActive: string;
|
|
1050
|
+
location: string;
|
|
1051
|
+
name: string;
|
|
1052
|
+
projects: number;
|
|
1053
|
+
role: string;
|
|
1054
|
+
salary: number;
|
|
1055
|
+
startDate: string;
|
|
1056
|
+
status: "Active" | "Inactive";
|
|
1057
|
+
};
|
|
1058
|
+
|
|
1059
|
+
const DATA: Employee[] = [
|
|
1060
|
+
{
|
|
1061
|
+
department: "Engineering",
|
|
1062
|
+
email: "john.doe@example.com",
|
|
1063
|
+
lastActive: "2024-01-15",
|
|
1064
|
+
location: "San Francisco",
|
|
1065
|
+
name: "John Doe",
|
|
1066
|
+
projects: 5,
|
|
1067
|
+
role: "Software Engineer",
|
|
1068
|
+
salary: 85000,
|
|
1069
|
+
startDate: "2022-03-15",
|
|
1070
|
+
status: "Active",
|
|
1071
|
+
},
|
|
1072
|
+
{
|
|
1073
|
+
department: "Product",
|
|
1074
|
+
email: "jane.smith@example.com",
|
|
1075
|
+
lastActive: "2024-01-14",
|
|
1076
|
+
location: "New York",
|
|
1077
|
+
name: "Jane Smith",
|
|
1078
|
+
projects: 3,
|
|
1079
|
+
role: "Product Manager",
|
|
1080
|
+
salary: 95000,
|
|
1081
|
+
startDate: "2021-08-20",
|
|
1082
|
+
status: "Active",
|
|
1083
|
+
},
|
|
1084
|
+
{
|
|
1085
|
+
department: "Design",
|
|
1086
|
+
email: "bob.johnson@example.com",
|
|
1087
|
+
lastActive: "2024-01-10",
|
|
1088
|
+
location: "Austin",
|
|
1089
|
+
name: "Bob Johnson",
|
|
1090
|
+
projects: 2,
|
|
1091
|
+
role: "Designer",
|
|
1092
|
+
salary: 75000,
|
|
1093
|
+
startDate: "2023-01-10",
|
|
1094
|
+
status: "Inactive",
|
|
1095
|
+
},
|
|
1096
|
+
{
|
|
1097
|
+
department: "Data",
|
|
1098
|
+
email: "alice.brown@example.com",
|
|
1099
|
+
lastActive: "2024-01-16",
|
|
1100
|
+
location: "Seattle",
|
|
1101
|
+
name: "Alice Brown",
|
|
1102
|
+
projects: 7,
|
|
1103
|
+
role: "Data Scientist",
|
|
1104
|
+
salary: 90000,
|
|
1105
|
+
startDate: "2022-11-05",
|
|
1106
|
+
status: "Active",
|
|
1107
|
+
},
|
|
1108
|
+
{
|
|
1109
|
+
department: "Engineering",
|
|
1110
|
+
email: "charlie.wilson@example.com",
|
|
1111
|
+
lastActive: "2024-01-15",
|
|
1112
|
+
location: "Remote",
|
|
1113
|
+
name: "Charlie Wilson",
|
|
1114
|
+
projects: 4,
|
|
1115
|
+
role: "DevOps Engineer",
|
|
1116
|
+
salary: 88000,
|
|
1117
|
+
startDate: "2023-06-12",
|
|
1118
|
+
status: "Active",
|
|
1119
|
+
},
|
|
1120
|
+
];
|
|
1121
|
+
|
|
1122
|
+
/** The cell context, plus how PreComposedTable wants the cell drawn. */
|
|
1123
|
+
type CellProps = CellContext<Employee, unknown> & {
|
|
1124
|
+
as?: React.ElementType;
|
|
1125
|
+
style?: React.CSSProperties;
|
|
1126
|
+
};
|
|
1127
|
+
|
|
1128
|
+
/** The header context, plus which edge a pinned header sits against. */
|
|
1129
|
+
type HeaderProps = HeaderContext<Employee, unknown> & {
|
|
1130
|
+
"data-pinned-edge"?: "left" | "right";
|
|
1131
|
+
style?: React.CSSProperties;
|
|
1132
|
+
};
|
|
1133
|
+
|
|
1134
|
+
function textCell({ as, getValue, style }: CellProps) {
|
|
1135
|
+
return (
|
|
1136
|
+
<CellBasic
|
|
1137
|
+
as={as}
|
|
1138
|
+
primaryText={String(getValue())}
|
|
1139
|
+
shouldShowTooltipOnHover={false}
|
|
1140
|
+
style={style}
|
|
1141
|
+
/>
|
|
1142
|
+
);
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
function numberCell({ as, column, getValue, style }: CellProps) {
|
|
1146
|
+
return (
|
|
1147
|
+
<CellBasic
|
|
1148
|
+
as={as}
|
|
1149
|
+
// Room for the sort chevron, which would otherwise sit on the number.
|
|
1150
|
+
extraRightPadding={column.getCanSort() ? 20 : 0}
|
|
1151
|
+
horizontalAlign="right"
|
|
1152
|
+
primaryText={String(getValue())}
|
|
1153
|
+
shouldShowTooltipOnHover={false}
|
|
1154
|
+
style={style}
|
|
1155
|
+
tabularNums
|
|
1156
|
+
/>
|
|
1157
|
+
);
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
const COLUMNS: ColumnDef<Employee>[] = [
|
|
1161
|
+
{
|
|
1162
|
+
accessorKey: "name",
|
|
1163
|
+
cell: ({ as, getValue, row, style }: CellProps) => (
|
|
1164
|
+
<CellBasic
|
|
1165
|
+
as={as}
|
|
1166
|
+
link={`mailto:${row.original.email}`}
|
|
1167
|
+
linkComponent="a"
|
|
1168
|
+
linkProps={{ target: "_blank" }}
|
|
1169
|
+
primaryText={getValue() as string}
|
|
1170
|
+
secondaryText={row.original.role}
|
|
1171
|
+
shouldShowTooltipOnHover={false}
|
|
1172
|
+
shouldShowUnderlineOnHover
|
|
1173
|
+
style={style}
|
|
1174
|
+
/>
|
|
1175
|
+
),
|
|
1176
|
+
enableSorting: false,
|
|
1177
|
+
header: ({ "data-pinned-edge": pinnedEdge, style }: HeaderProps) => (
|
|
1178
|
+
<CellHeader
|
|
1179
|
+
data-pinned-edge={pinnedEdge}
|
|
1180
|
+
hideSortIcon
|
|
1181
|
+
hover
|
|
1182
|
+
shouldShowTooltipOnHover
|
|
1183
|
+
style={style}
|
|
1184
|
+
tooltipProps={{ placement: "top", textAlign: "left", width: "wide" }}
|
|
1185
|
+
tooltipSubtitle="Each name links to the person's email address."
|
|
1186
|
+
tooltipText="Name"
|
|
1187
|
+
>
|
|
1188
|
+
Name
|
|
1189
|
+
</CellHeader>
|
|
1190
|
+
),
|
|
1191
|
+
meta: { isGrow: true, pinning: "left" },
|
|
1192
|
+
minSize: 150,
|
|
1193
|
+
},
|
|
1194
|
+
{
|
|
1195
|
+
accessorKey: "email",
|
|
1196
|
+
cell: ({ as, getValue, style }: CellProps) => (
|
|
1197
|
+
<CellBasic
|
|
1198
|
+
as={as}
|
|
1199
|
+
link={`mailto:${getValue() as string}`}
|
|
1200
|
+
linkProps={{ target: "_blank" }}
|
|
1201
|
+
primaryText={getValue() as string}
|
|
1202
|
+
primaryTextWrapLineCount={1}
|
|
1203
|
+
shouldShowTooltipOnHover={false}
|
|
1204
|
+
shouldShowUnderlineOnHover
|
|
1205
|
+
style={style}
|
|
1206
|
+
/>
|
|
1207
|
+
),
|
|
1208
|
+
header: "Email",
|
|
1209
|
+
meta: { isGrow: true },
|
|
1210
|
+
minSize: 200,
|
|
1211
|
+
},
|
|
1212
|
+
{
|
|
1213
|
+
accessorKey: "role",
|
|
1214
|
+
cell: textCell,
|
|
1215
|
+
header: "Role",
|
|
1216
|
+
meta: { verticalAlign: "middle" },
|
|
1217
|
+
minSize: 150,
|
|
1218
|
+
},
|
|
1219
|
+
{
|
|
1220
|
+
accessorKey: "department",
|
|
1221
|
+
cell: textCell,
|
|
1222
|
+
header: "Department",
|
|
1223
|
+
minSize: 120,
|
|
1224
|
+
},
|
|
1225
|
+
{
|
|
1226
|
+
accessorKey: "status",
|
|
1227
|
+
cell: ({ as, getValue, style }: CellProps) => (
|
|
1228
|
+
<CellComponent as={as} style={style}>
|
|
1229
|
+
<Tag
|
|
1230
|
+
color={getValue() === "Active" ? "positive" : "negative"}
|
|
1231
|
+
hover={false}
|
|
1232
|
+
label={getValue() as string}
|
|
1233
|
+
sdsStyle="rounded"
|
|
1234
|
+
sdsType="secondary"
|
|
1235
|
+
/>
|
|
1236
|
+
</CellComponent>
|
|
1237
|
+
),
|
|
1238
|
+
header: "Status",
|
|
1239
|
+
minSize: 100,
|
|
1240
|
+
},
|
|
1241
|
+
{
|
|
1242
|
+
accessorKey: "lastActive",
|
|
1243
|
+
cell: numberCell,
|
|
1244
|
+
header: "Last Active",
|
|
1245
|
+
meta: { headerCellProps: { horizontalAlign: "right" } },
|
|
1246
|
+
minSize: 120,
|
|
1247
|
+
},
|
|
1248
|
+
{
|
|
1249
|
+
accessorKey: "projects",
|
|
1250
|
+
cell: numberCell,
|
|
1251
|
+
header: "Projects",
|
|
1252
|
+
meta: { headerCellProps: { horizontalAlign: "right" } },
|
|
1253
|
+
minSize: 100,
|
|
1254
|
+
},
|
|
1255
|
+
{
|
|
1256
|
+
accessorKey: "salary",
|
|
1257
|
+
cell: ({ as, column, getValue, style }: CellProps) => (
|
|
1258
|
+
<CellBasic
|
|
1259
|
+
as={as}
|
|
1260
|
+
extraRightPadding={column.getCanSort() ? 20 : 0}
|
|
1261
|
+
horizontalAlign="right"
|
|
1262
|
+
primaryText={`${(getValue() as number).toLocaleString()}`}
|
|
1263
|
+
shouldShowTooltipOnHover={false}
|
|
1264
|
+
style={style}
|
|
1265
|
+
tabularNums
|
|
1266
|
+
/>
|
|
1267
|
+
),
|
|
1268
|
+
header: "Salary",
|
|
1269
|
+
meta: { headerCellProps: { horizontalAlign: "right" } },
|
|
1270
|
+
minSize: 120,
|
|
1271
|
+
},
|
|
1272
|
+
{
|
|
1273
|
+
accessorKey: "location",
|
|
1274
|
+
cell: textCell,
|
|
1275
|
+
header: "Location",
|
|
1276
|
+
minSize: 150,
|
|
1277
|
+
},
|
|
1278
|
+
{
|
|
1279
|
+
accessorKey: "startDate",
|
|
1280
|
+
cell: numberCell,
|
|
1281
|
+
header: "Start Date",
|
|
1282
|
+
meta: { headerCellProps: { horizontalAlign: "right" } },
|
|
1283
|
+
minSize: 120,
|
|
1284
|
+
},
|
|
1285
|
+
{
|
|
1286
|
+
cell: ({ as, row, style }: CellProps) => (
|
|
1287
|
+
<CellComponent as={as} horizontalAlign="right" style={style}>
|
|
1288
|
+
<Button
|
|
1289
|
+
aria-label={`Edit ${row.original.name}`}
|
|
1290
|
+
onClick={() => console.log("Edit", row.original)}
|
|
1291
|
+
sdsStyle="minimal"
|
|
1292
|
+
sdsType="secondary"
|
|
1293
|
+
>
|
|
1294
|
+
<Icon sdsIcon="Edit" sdsSize="s" />
|
|
1295
|
+
</Button>
|
|
1296
|
+
<Button
|
|
1297
|
+
aria-label={`Download ${row.original.name}`}
|
|
1298
|
+
onClick={() => console.log("Download", row.original)}
|
|
1299
|
+
sdsStyle="minimal"
|
|
1300
|
+
sdsType="secondary"
|
|
1301
|
+
>
|
|
1302
|
+
<Icon sdsIcon="Download" sdsSize="s" />
|
|
1303
|
+
</Button>
|
|
1304
|
+
</CellComponent>
|
|
1305
|
+
),
|
|
1306
|
+
enableSorting: false,
|
|
1307
|
+
header: "Actions",
|
|
1308
|
+
id: "actions",
|
|
1309
|
+
meta: {
|
|
1310
|
+
headerCellProps: { horizontalAlign: "right" },
|
|
1311
|
+
pinning: "right",
|
|
1312
|
+
verticalAlign: "middle",
|
|
1313
|
+
},
|
|
1314
|
+
size: 100,
|
|
1315
|
+
},
|
|
1316
|
+
];
|
|
1317
|
+
|
|
1318
|
+
const PAGINATION = { pageSize: 10 };
|
|
1319
|
+
|
|
1320
|
+
function App() {
|
|
1321
|
+
return (
|
|
1322
|
+
<div className="app">
|
|
1323
|
+
<PreComposedTable<Employee>
|
|
1324
|
+
columns={COLUMNS}
|
|
1325
|
+
data={DATA}
|
|
1326
|
+
enablePagination
|
|
1327
|
+
enableRowSelection
|
|
1328
|
+
enableSorting
|
|
1329
|
+
paginationConfig={PAGINATION}
|
|
1330
|
+
tableWidth="600px"
|
|
1331
|
+
/>
|
|
1332
|
+
</div>
|
|
1333
|
+
);
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
export default App;
|
|
1337
|
+
```
|
|
1338
|
+
|
|
1339
|
+
### With column filtering
|
|
1340
|
+
|
|
1341
|
+
`enableColumnFiltering` adds a row of search inputs under the header, one per column. The filters are held above the table here rather than inside it: `state.columnFilters` and `onColumnFiltersChange` passed through `tableOptions` override the state the component keeps, which is what makes them yours to read, persist or reset.
|
|
1342
|
+
|
|
1343
|
+
**Example: PreComposedTableWithColumnFiltering**
|
|
1344
|
+
|
|
1345
|
+
```tsx
|
|
1346
|
+
// The WithColumnFiltering story: enableColumnFiltering adds a row of search
|
|
1347
|
+
// inputs under the header, one per column.
|
|
1348
|
+
//
|
|
1349
|
+
// The filter state is held above the table here rather than inside it. A state
|
|
1350
|
+
// you pass through tableOptions overrides the one the component keeps, so
|
|
1351
|
+
// state.columnFilters plus onColumnFiltersChange hands you the filters to read,
|
|
1352
|
+
// persist or reset from your own code. TanStack sends change handlers an
|
|
1353
|
+
// updater that is either the next value or a function of the current one, so
|
|
1354
|
+
// resolve it against your state rather than storing it directly.
|
|
1355
|
+
//
|
|
1356
|
+
// A column left as a plain accessor is drawn as a CellHeader and a CellBasic.
|
|
1357
|
+
// A renderer of your own is handed the element and the width to draw with as
|
|
1358
|
+
// `as` and `style`, and forwards both. Without `as` it renders a second <td>
|
|
1359
|
+
// inside the one the table already drew. A header renderer is handed
|
|
1360
|
+
// `data-pinned-edge` too, which is what tells a pinned header to draw its
|
|
1361
|
+
// gradient.
|
|
1362
|
+
//
|
|
1363
|
+
// data, columns and paginationConfig sit outside the component so their
|
|
1364
|
+
// identity is stable: a new paginationConfig object sends the table back to its
|
|
1365
|
+
// first page.
|
|
1366
|
+
|
|
1367
|
+
import {
|
|
1368
|
+
Button,
|
|
1369
|
+
CellBasic,
|
|
1370
|
+
CellComponent,
|
|
1371
|
+
CellHeader,
|
|
1372
|
+
Icon,
|
|
1373
|
+
PreComposedTable,
|
|
1374
|
+
Tag,
|
|
1375
|
+
} from "@czi-sds/components";
|
|
1376
|
+
import type {
|
|
1377
|
+
CellContext,
|
|
1378
|
+
ColumnDef,
|
|
1379
|
+
ColumnFiltersState,
|
|
1380
|
+
HeaderContext,
|
|
1381
|
+
Updater,
|
|
1382
|
+
} from "@tanstack/react-table";
|
|
1383
|
+
import * as React from "react";
|
|
1384
|
+
|
|
1385
|
+
type Employee = {
|
|
1386
|
+
department: string;
|
|
1387
|
+
email: string;
|
|
1388
|
+
lastActive: string;
|
|
1389
|
+
location: string;
|
|
1390
|
+
name: string;
|
|
1391
|
+
projects: number;
|
|
1392
|
+
role: string;
|
|
1393
|
+
salary: number;
|
|
1394
|
+
startDate: string;
|
|
1395
|
+
status: "Active" | "Inactive";
|
|
1396
|
+
};
|
|
1397
|
+
|
|
1398
|
+
const DATA: Employee[] = [
|
|
1399
|
+
{
|
|
1400
|
+
department: "Engineering",
|
|
1401
|
+
email: "john.doe@example.com",
|
|
1402
|
+
lastActive: "2024-01-15",
|
|
1403
|
+
location: "San Francisco",
|
|
1404
|
+
name: "John Doe",
|
|
1405
|
+
projects: 5,
|
|
1406
|
+
role: "Software Engineer",
|
|
1407
|
+
salary: 85000,
|
|
1408
|
+
startDate: "2022-03-15",
|
|
1409
|
+
status: "Active",
|
|
1410
|
+
},
|
|
1411
|
+
{
|
|
1412
|
+
department: "Product",
|
|
1413
|
+
email: "jane.smith@example.com",
|
|
1414
|
+
lastActive: "2024-01-14",
|
|
1415
|
+
location: "New York",
|
|
1416
|
+
name: "Jane Smith",
|
|
1417
|
+
projects: 3,
|
|
1418
|
+
role: "Product Manager",
|
|
1419
|
+
salary: 95000,
|
|
1420
|
+
startDate: "2021-08-20",
|
|
1421
|
+
status: "Active",
|
|
1422
|
+
},
|
|
1423
|
+
{
|
|
1424
|
+
department: "Design",
|
|
1425
|
+
email: "bob.johnson@example.com",
|
|
1426
|
+
lastActive: "2024-01-10",
|
|
1427
|
+
location: "Austin",
|
|
1428
|
+
name: "Bob Johnson",
|
|
1429
|
+
projects: 2,
|
|
1430
|
+
role: "Designer",
|
|
1431
|
+
salary: 75000,
|
|
1432
|
+
startDate: "2023-01-10",
|
|
1433
|
+
status: "Inactive",
|
|
1434
|
+
},
|
|
1435
|
+
{
|
|
1436
|
+
department: "Data",
|
|
1437
|
+
email: "alice.brown@example.com",
|
|
1438
|
+
lastActive: "2024-01-16",
|
|
1439
|
+
location: "Seattle",
|
|
1440
|
+
name: "Alice Brown",
|
|
1441
|
+
projects: 7,
|
|
1442
|
+
role: "Data Scientist",
|
|
1443
|
+
salary: 90000,
|
|
1444
|
+
startDate: "2022-11-05",
|
|
1445
|
+
status: "Active",
|
|
1446
|
+
},
|
|
1447
|
+
{
|
|
1448
|
+
department: "Engineering",
|
|
1449
|
+
email: "charlie.wilson@example.com",
|
|
1450
|
+
lastActive: "2024-01-15",
|
|
1451
|
+
location: "Remote",
|
|
1452
|
+
name: "Charlie Wilson",
|
|
1453
|
+
projects: 4,
|
|
1454
|
+
role: "DevOps Engineer",
|
|
1455
|
+
salary: 88000,
|
|
1456
|
+
startDate: "2023-06-12",
|
|
1457
|
+
status: "Active",
|
|
1458
|
+
},
|
|
1459
|
+
];
|
|
1460
|
+
|
|
1461
|
+
/** The cell context, plus how PreComposedTable wants the cell drawn. */
|
|
1462
|
+
type CellProps = CellContext<Employee, unknown> & {
|
|
1463
|
+
as?: React.ElementType;
|
|
1464
|
+
style?: React.CSSProperties;
|
|
1465
|
+
};
|
|
1466
|
+
|
|
1467
|
+
/** The header context, plus which edge a pinned header sits against. */
|
|
1468
|
+
type HeaderProps = HeaderContext<Employee, unknown> & {
|
|
1469
|
+
"data-pinned-edge"?: "left" | "right";
|
|
1470
|
+
style?: React.CSSProperties;
|
|
1471
|
+
};
|
|
1472
|
+
|
|
1473
|
+
function textCell({ as, getValue, style }: CellProps) {
|
|
1474
|
+
return (
|
|
1475
|
+
<CellBasic
|
|
1476
|
+
as={as}
|
|
1477
|
+
primaryText={String(getValue())}
|
|
1478
|
+
shouldShowTooltipOnHover={false}
|
|
1479
|
+
style={style}
|
|
1480
|
+
/>
|
|
1481
|
+
);
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
function numberCell({ as, column, getValue, style }: CellProps) {
|
|
1485
|
+
return (
|
|
1486
|
+
<CellBasic
|
|
1487
|
+
as={as}
|
|
1488
|
+
// Room for the sort chevron, which would otherwise sit on the number.
|
|
1489
|
+
extraRightPadding={column.getCanSort() ? 20 : 0}
|
|
1490
|
+
horizontalAlign="right"
|
|
1491
|
+
primaryText={String(getValue())}
|
|
1492
|
+
shouldShowTooltipOnHover={false}
|
|
1493
|
+
style={style}
|
|
1494
|
+
tabularNums
|
|
1495
|
+
/>
|
|
1496
|
+
);
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
const COLUMNS: ColumnDef<Employee>[] = [
|
|
1500
|
+
{
|
|
1501
|
+
accessorKey: "name",
|
|
1502
|
+
cell: ({ as, getValue, row, style }: CellProps) => (
|
|
1503
|
+
<CellBasic
|
|
1504
|
+
as={as}
|
|
1505
|
+
link={`mailto:${row.original.email}`}
|
|
1506
|
+
linkComponent="a"
|
|
1507
|
+
linkProps={{ target: "_blank" }}
|
|
1508
|
+
primaryText={getValue() as string}
|
|
1509
|
+
secondaryText={row.original.role}
|
|
1510
|
+
shouldShowTooltipOnHover={false}
|
|
1511
|
+
shouldShowUnderlineOnHover
|
|
1512
|
+
style={style}
|
|
1513
|
+
/>
|
|
1514
|
+
),
|
|
1515
|
+
enableSorting: false,
|
|
1516
|
+
header: ({ "data-pinned-edge": pinnedEdge, style }: HeaderProps) => (
|
|
1517
|
+
<CellHeader
|
|
1518
|
+
data-pinned-edge={pinnedEdge}
|
|
1519
|
+
hideSortIcon
|
|
1520
|
+
hover
|
|
1521
|
+
shouldShowTooltipOnHover
|
|
1522
|
+
style={style}
|
|
1523
|
+
tooltipProps={{ placement: "top", textAlign: "left", width: "wide" }}
|
|
1524
|
+
tooltipSubtitle="Each name links to the person's email address."
|
|
1525
|
+
tooltipText="Name"
|
|
1526
|
+
>
|
|
1527
|
+
Name
|
|
1528
|
+
</CellHeader>
|
|
1529
|
+
),
|
|
1530
|
+
meta: { isGrow: true, pinning: "left" },
|
|
1531
|
+
minSize: 150,
|
|
1532
|
+
},
|
|
1533
|
+
{
|
|
1534
|
+
accessorKey: "email",
|
|
1535
|
+
cell: ({ as, getValue, style }: CellProps) => (
|
|
1536
|
+
<CellBasic
|
|
1537
|
+
as={as}
|
|
1538
|
+
link={`mailto:${getValue() as string}`}
|
|
1539
|
+
linkProps={{ target: "_blank" }}
|
|
1540
|
+
primaryText={getValue() as string}
|
|
1541
|
+
primaryTextWrapLineCount={1}
|
|
1542
|
+
shouldShowTooltipOnHover={false}
|
|
1543
|
+
shouldShowUnderlineOnHover
|
|
1544
|
+
style={style}
|
|
1545
|
+
/>
|
|
1546
|
+
),
|
|
1547
|
+
header: "Email",
|
|
1548
|
+
meta: { isGrow: true },
|
|
1549
|
+
minSize: 200,
|
|
1550
|
+
},
|
|
1551
|
+
{
|
|
1552
|
+
accessorKey: "role",
|
|
1553
|
+
cell: textCell,
|
|
1554
|
+
header: "Role",
|
|
1555
|
+
meta: { verticalAlign: "middle" },
|
|
1556
|
+
minSize: 150,
|
|
1557
|
+
},
|
|
1558
|
+
{
|
|
1559
|
+
accessorKey: "department",
|
|
1560
|
+
cell: textCell,
|
|
1561
|
+
header: "Department",
|
|
1562
|
+
minSize: 120,
|
|
1563
|
+
},
|
|
1564
|
+
{
|
|
1565
|
+
accessorKey: "status",
|
|
1566
|
+
cell: ({ as, getValue, style }: CellProps) => (
|
|
1567
|
+
<CellComponent as={as} style={style}>
|
|
1568
|
+
<Tag
|
|
1569
|
+
color={getValue() === "Active" ? "positive" : "negative"}
|
|
1570
|
+
hover={false}
|
|
1571
|
+
label={getValue() as string}
|
|
1572
|
+
sdsStyle="rounded"
|
|
1573
|
+
sdsType="secondary"
|
|
1574
|
+
/>
|
|
1575
|
+
</CellComponent>
|
|
1576
|
+
),
|
|
1577
|
+
header: "Status",
|
|
1578
|
+
minSize: 100,
|
|
1579
|
+
},
|
|
1580
|
+
{
|
|
1581
|
+
accessorKey: "lastActive",
|
|
1582
|
+
cell: numberCell,
|
|
1583
|
+
header: "Last Active",
|
|
1584
|
+
meta: { headerCellProps: { horizontalAlign: "right" } },
|
|
1585
|
+
minSize: 120,
|
|
1586
|
+
},
|
|
1587
|
+
{
|
|
1588
|
+
accessorKey: "projects",
|
|
1589
|
+
cell: numberCell,
|
|
1590
|
+
header: "Projects",
|
|
1591
|
+
meta: { headerCellProps: { horizontalAlign: "right" } },
|
|
1592
|
+
minSize: 100,
|
|
1593
|
+
},
|
|
1594
|
+
{
|
|
1595
|
+
accessorKey: "salary",
|
|
1596
|
+
cell: ({ as, column, getValue, style }: CellProps) => (
|
|
1597
|
+
<CellBasic
|
|
1598
|
+
as={as}
|
|
1599
|
+
extraRightPadding={column.getCanSort() ? 20 : 0}
|
|
1600
|
+
horizontalAlign="right"
|
|
1601
|
+
primaryText={`${(getValue() as number).toLocaleString()}`}
|
|
1602
|
+
shouldShowTooltipOnHover={false}
|
|
1603
|
+
style={style}
|
|
1604
|
+
tabularNums
|
|
1605
|
+
/>
|
|
1606
|
+
),
|
|
1607
|
+
header: "Salary",
|
|
1608
|
+
meta: { headerCellProps: { horizontalAlign: "right" } },
|
|
1609
|
+
minSize: 120,
|
|
1610
|
+
},
|
|
1611
|
+
{
|
|
1612
|
+
accessorKey: "location",
|
|
1613
|
+
cell: textCell,
|
|
1614
|
+
header: "Location",
|
|
1615
|
+
minSize: 150,
|
|
1616
|
+
},
|
|
1617
|
+
{
|
|
1618
|
+
accessorKey: "startDate",
|
|
1619
|
+
cell: numberCell,
|
|
1620
|
+
header: "Start Date",
|
|
1621
|
+
meta: { headerCellProps: { horizontalAlign: "right" } },
|
|
1622
|
+
minSize: 120,
|
|
1623
|
+
},
|
|
1624
|
+
{
|
|
1625
|
+
cell: ({ as, row, style }: CellProps) => (
|
|
1626
|
+
<CellComponent as={as} horizontalAlign="right" style={style}>
|
|
1627
|
+
<Button
|
|
1628
|
+
aria-label={`Edit ${row.original.name}`}
|
|
1629
|
+
onClick={() => console.log("Edit", row.original)}
|
|
1630
|
+
sdsStyle="minimal"
|
|
1631
|
+
sdsType="secondary"
|
|
1632
|
+
>
|
|
1633
|
+
<Icon sdsIcon="Edit" sdsSize="s" />
|
|
1634
|
+
</Button>
|
|
1635
|
+
<Button
|
|
1636
|
+
aria-label={`Download ${row.original.name}`}
|
|
1637
|
+
onClick={() => console.log("Download", row.original)}
|
|
1638
|
+
sdsStyle="minimal"
|
|
1639
|
+
sdsType="secondary"
|
|
1640
|
+
>
|
|
1641
|
+
<Icon sdsIcon="Download" sdsSize="s" />
|
|
1642
|
+
</Button>
|
|
1643
|
+
</CellComponent>
|
|
1644
|
+
),
|
|
1645
|
+
enableSorting: false,
|
|
1646
|
+
header: "Actions",
|
|
1647
|
+
id: "actions",
|
|
1648
|
+
meta: {
|
|
1649
|
+
headerCellProps: { horizontalAlign: "right" },
|
|
1650
|
+
pinning: "right",
|
|
1651
|
+
verticalAlign: "middle",
|
|
1652
|
+
},
|
|
1653
|
+
size: 100,
|
|
1654
|
+
},
|
|
1655
|
+
];
|
|
1656
|
+
|
|
1657
|
+
function App() {
|
|
1658
|
+
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
|
1659
|
+
[]
|
|
1660
|
+
);
|
|
1661
|
+
|
|
1662
|
+
const tableOptions = React.useMemo(
|
|
1663
|
+
() => ({
|
|
1664
|
+
onColumnFiltersChange: (updater: Updater<ColumnFiltersState>) =>
|
|
1665
|
+
setColumnFilters((current) =>
|
|
1666
|
+
typeof updater === "function" ? updater(current) : updater
|
|
1667
|
+
),
|
|
1668
|
+
state: { columnFilters },
|
|
1669
|
+
}),
|
|
1670
|
+
[columnFilters]
|
|
1671
|
+
);
|
|
1672
|
+
|
|
1673
|
+
return (
|
|
1674
|
+
<div className="app">
|
|
1675
|
+
<PreComposedTable<Employee>
|
|
1676
|
+
columns={COLUMNS}
|
|
1677
|
+
data={DATA}
|
|
1678
|
+
enableColumnFiltering
|
|
1679
|
+
enableSorting
|
|
1680
|
+
tableOptions={tableOptions}
|
|
1681
|
+
/>
|
|
1682
|
+
</div>
|
|
1683
|
+
);
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
export default App;
|
|
1687
|
+
```
|
|
1688
|
+
|
|
1689
|
+
### With selective column filtering
|
|
1690
|
+
|
|
1691
|
+
Not every column has to take part in the filter row. Status replaces its input with a Dropdown through `meta.filterComponent` and matches on equality rather than the default substring search, while Salary and Start Date opt out altogether with `enableColumnFilter: false`.
|
|
1692
|
+
|
|
1693
|
+
**Example: PreComposedTableWithSelectiveColumnFiltering**
|
|
1694
|
+
|
|
1695
|
+
```tsx
|
|
1696
|
+
// The WithSelectiveColumnFiltering story: enableColumnFiltering adds a filter
|
|
1697
|
+
// row under the header, but not every column has to take part in it.
|
|
1698
|
+
//
|
|
1699
|
+
// Status replaces its search input with a Dropdown through
|
|
1700
|
+
// meta.filterComponent, and because that writes a whole status rather than
|
|
1701
|
+
// something to search for, the column matches with a filterFn of equalsString.
|
|
1702
|
+
// Salary and Start Date opt out altogether with enableColumnFilter: false,
|
|
1703
|
+
// which leaves their filter cells empty.
|
|
1704
|
+
//
|
|
1705
|
+
// filterComponent is called as a plain function rather than mounted as a
|
|
1706
|
+
// component, so keep state out of it and read the current value back from
|
|
1707
|
+
// column.getFilterValue().
|
|
1708
|
+
|
|
1709
|
+
import {
|
|
1710
|
+
CellBasic,
|
|
1711
|
+
CellComponent,
|
|
1712
|
+
Dropdown,
|
|
1713
|
+
PreComposedTable,
|
|
1714
|
+
Tag,
|
|
1715
|
+
} from "@czi-sds/components";
|
|
1716
|
+
import type { CellContext, ColumnDef } from "@tanstack/react-table";
|
|
1717
|
+
import * as React from "react";
|
|
1718
|
+
|
|
1719
|
+
type Employee = {
|
|
1720
|
+
department: string;
|
|
1721
|
+
email: string;
|
|
1722
|
+
name: string;
|
|
1723
|
+
role: string;
|
|
1724
|
+
salary: number;
|
|
1725
|
+
startDate: string;
|
|
1726
|
+
status: "Active" | "Inactive";
|
|
1727
|
+
};
|
|
1728
|
+
|
|
1729
|
+
const DATA: Employee[] = [
|
|
1730
|
+
{
|
|
1731
|
+
department: "Engineering",
|
|
1732
|
+
email: "john.doe@example.com",
|
|
1733
|
+
name: "John Doe",
|
|
1734
|
+
role: "Software Engineer",
|
|
1735
|
+
salary: 85000,
|
|
1736
|
+
startDate: "2022-03-15",
|
|
1737
|
+
status: "Active",
|
|
1738
|
+
},
|
|
1739
|
+
{
|
|
1740
|
+
department: "Product",
|
|
1741
|
+
email: "jane.smith@example.com",
|
|
1742
|
+
name: "Jane Smith",
|
|
1743
|
+
role: "Product Manager",
|
|
1744
|
+
salary: 95000,
|
|
1745
|
+
startDate: "2021-08-20",
|
|
1746
|
+
status: "Active",
|
|
1747
|
+
},
|
|
1748
|
+
{
|
|
1749
|
+
department: "Design",
|
|
1750
|
+
email: "bob.johnson@example.com",
|
|
1751
|
+
name: "Bob Johnson",
|
|
1752
|
+
role: "Designer",
|
|
1753
|
+
salary: 75000,
|
|
1754
|
+
startDate: "2023-01-10",
|
|
1755
|
+
status: "Inactive",
|
|
1756
|
+
},
|
|
1757
|
+
{
|
|
1758
|
+
department: "Data",
|
|
1759
|
+
email: "alice.brown@example.com",
|
|
1760
|
+
name: "Alice Brown",
|
|
1761
|
+
role: "Data Scientist",
|
|
1762
|
+
salary: 90000,
|
|
1763
|
+
startDate: "2022-11-05",
|
|
1764
|
+
status: "Active",
|
|
1765
|
+
},
|
|
1766
|
+
{
|
|
1767
|
+
department: "Engineering",
|
|
1768
|
+
email: "charlie.wilson@example.com",
|
|
1769
|
+
name: "Charlie Wilson",
|
|
1770
|
+
role: "DevOps Engineer",
|
|
1771
|
+
salary: 88000,
|
|
1772
|
+
startDate: "2023-06-12",
|
|
1773
|
+
status: "Active",
|
|
1774
|
+
},
|
|
1775
|
+
];
|
|
1776
|
+
|
|
1777
|
+
const STATUS_OPTIONS = [
|
|
1778
|
+
{ name: "All" },
|
|
1779
|
+
{ name: "Active" },
|
|
1780
|
+
{ name: "Inactive" },
|
|
1781
|
+
];
|
|
1782
|
+
|
|
1783
|
+
/** The cell context, plus how PreComposedTable wants the cell drawn. */
|
|
1784
|
+
type CellProps = CellContext<Employee, unknown> & {
|
|
1785
|
+
as?: React.ElementType;
|
|
1786
|
+
style?: React.CSSProperties;
|
|
1787
|
+
};
|
|
1788
|
+
|
|
1789
|
+
function textCell({ as, getValue, style }: CellProps) {
|
|
1790
|
+
return (
|
|
1791
|
+
<CellBasic
|
|
1792
|
+
as={as}
|
|
1793
|
+
primaryText={String(getValue())}
|
|
1794
|
+
shouldShowTooltipOnHover={false}
|
|
1795
|
+
style={style}
|
|
1796
|
+
/>
|
|
1797
|
+
);
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
const COLUMNS: ColumnDef<Employee>[] = [
|
|
1801
|
+
{
|
|
1802
|
+
accessorKey: "name",
|
|
1803
|
+
cell: textCell,
|
|
1804
|
+
header: "Name",
|
|
1805
|
+
meta: { isGrow: true, pinning: "left" },
|
|
1806
|
+
minSize: 150,
|
|
1807
|
+
},
|
|
1808
|
+
{
|
|
1809
|
+
accessorKey: "email",
|
|
1810
|
+
cell: textCell,
|
|
1811
|
+
header: "Email",
|
|
1812
|
+
meta: { isGrow: true },
|
|
1813
|
+
minSize: 200,
|
|
1814
|
+
},
|
|
1815
|
+
{ accessorKey: "role", cell: textCell, header: "Role", minSize: 150 },
|
|
1816
|
+
{
|
|
1817
|
+
accessorKey: "department",
|
|
1818
|
+
cell: textCell,
|
|
1819
|
+
header: "Department",
|
|
1820
|
+
minSize: 120,
|
|
1821
|
+
},
|
|
1822
|
+
{
|
|
1823
|
+
accessorKey: "status",
|
|
1824
|
+
cell: ({ as, getValue, style }: CellProps) => (
|
|
1825
|
+
<CellComponent as={as} style={style}>
|
|
1826
|
+
<Tag
|
|
1827
|
+
color={getValue() === "Active" ? "positive" : "negative"}
|
|
1828
|
+
hover={false}
|
|
1829
|
+
label={getValue() as string}
|
|
1830
|
+
sdsStyle="rounded"
|
|
1831
|
+
sdsType="secondary"
|
|
1832
|
+
/>
|
|
1833
|
+
</CellComponent>
|
|
1834
|
+
),
|
|
1835
|
+
filterFn: "equalsString",
|
|
1836
|
+
header: "Status",
|
|
1837
|
+
meta: {
|
|
1838
|
+
filterComponent: ({ column }) => (
|
|
1839
|
+
<Dropdown<{ name: string }, false, false, false>
|
|
1840
|
+
InputDropdownProps={{ width: "100%" }}
|
|
1841
|
+
label="Status"
|
|
1842
|
+
onChange={(_event, value) =>
|
|
1843
|
+
column.setFilterValue(
|
|
1844
|
+
!value || value.name === "All" ? undefined : value.name
|
|
1845
|
+
)
|
|
1846
|
+
}
|
|
1847
|
+
options={STATUS_OPTIONS}
|
|
1848
|
+
/>
|
|
1849
|
+
),
|
|
1850
|
+
},
|
|
1851
|
+
minSize: 140,
|
|
1852
|
+
},
|
|
1853
|
+
{
|
|
1854
|
+
accessorKey: "salary",
|
|
1855
|
+
cell: ({ as, column, getValue, style }: CellProps) => (
|
|
1856
|
+
<CellBasic
|
|
1857
|
+
as={as}
|
|
1858
|
+
extraRightPadding={column.getCanSort() ? 20 : 0}
|
|
1859
|
+
horizontalAlign="right"
|
|
1860
|
+
primaryText={`${(getValue() as number).toLocaleString()}`}
|
|
1861
|
+
shouldShowTooltipOnHover={false}
|
|
1862
|
+
style={style}
|
|
1863
|
+
tabularNums
|
|
1864
|
+
/>
|
|
1865
|
+
),
|
|
1866
|
+
enableColumnFilter: false,
|
|
1867
|
+
header: "Salary",
|
|
1868
|
+
meta: { headerCellProps: { horizontalAlign: "right" } },
|
|
1869
|
+
minSize: 120,
|
|
1870
|
+
},
|
|
1871
|
+
{
|
|
1872
|
+
accessorKey: "startDate",
|
|
1873
|
+
cell: ({ as, column, getValue, style }: CellProps) => (
|
|
1874
|
+
<CellBasic
|
|
1875
|
+
as={as}
|
|
1876
|
+
extraRightPadding={column.getCanSort() ? 20 : 0}
|
|
1877
|
+
horizontalAlign="right"
|
|
1878
|
+
primaryText={String(getValue())}
|
|
1879
|
+
shouldShowTooltipOnHover={false}
|
|
1880
|
+
style={style}
|
|
1881
|
+
tabularNums
|
|
1882
|
+
/>
|
|
1883
|
+
),
|
|
1884
|
+
enableColumnFilter: false,
|
|
1885
|
+
header: "Start Date",
|
|
1886
|
+
meta: {
|
|
1887
|
+
headerCellProps: { horizontalAlign: "right" },
|
|
1888
|
+
pinning: "right",
|
|
1889
|
+
},
|
|
1890
|
+
minSize: 120,
|
|
1891
|
+
},
|
|
1892
|
+
];
|
|
1893
|
+
|
|
1894
|
+
function App() {
|
|
1895
|
+
return (
|
|
1896
|
+
<div className="app">
|
|
1897
|
+
<PreComposedTable<Employee>
|
|
1898
|
+
columns={COLUMNS}
|
|
1899
|
+
data={DATA}
|
|
1900
|
+
enableColumnFiltering
|
|
1901
|
+
enableRowSelection
|
|
1902
|
+
enableSorting
|
|
1903
|
+
/>
|
|
1904
|
+
</div>
|
|
1905
|
+
);
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
export default App;
|
|
1909
|
+
```
|
|
1910
|
+
|
|
1911
|
+
### With custom column filters
|
|
1912
|
+
|
|
1913
|
+
Every filter here is a component of its own: a Dropdown for Status, and a pair of number inputs for Salary and Projects matched with TanStack's `inNumberRange` filter. A filter component is called rather than mounted, so it holds no state of its own: it reads `column.getFilterValue()` and writes `column.setFilterValue()`.
|
|
1914
|
+
|
|
1915
|
+
**Example: PreComposedTableWithCustomColumnFilters**
|
|
1916
|
+
|
|
1917
|
+
```tsx
|
|
1918
|
+
// The WithCustomColumnFilters story: every filter in this table is a component
|
|
1919
|
+
// of its own, supplied through meta.filterComponent. Status filters through a
|
|
1920
|
+
// Dropdown against a filterFn of equalsString, while Salary and Projects use a
|
|
1921
|
+
// pair of number inputs against inNumberRange, which expects a [min, max] pair.
|
|
1922
|
+
//
|
|
1923
|
+
// A filter component is called as a plain function rather than mounted, so it
|
|
1924
|
+
// holds no state: it reads the current value from column.getFilterValue() and
|
|
1925
|
+
// writes the next one with column.setFilterValue(), which is also what lets
|
|
1926
|
+
// something outside the table (a Clear filters button, say) reset it.
|
|
1927
|
+
|
|
1928
|
+
import {
|
|
1929
|
+
CellBasic,
|
|
1930
|
+
CellComponent,
|
|
1931
|
+
Dropdown,
|
|
1932
|
+
InputText,
|
|
1933
|
+
PreComposedTable,
|
|
1934
|
+
Tag,
|
|
1935
|
+
getSpaces,
|
|
1936
|
+
type CommonThemeProps,
|
|
1937
|
+
} from "@czi-sds/components";
|
|
1938
|
+
import styled from "@emotion/styled";
|
|
1939
|
+
import type { CellContext, Column, ColumnDef } from "@tanstack/react-table";
|
|
1940
|
+
import * as React from "react";
|
|
1941
|
+
|
|
1942
|
+
type Employee = {
|
|
1943
|
+
name: string;
|
|
1944
|
+
projects: number;
|
|
1945
|
+
role: string;
|
|
1946
|
+
salary: number;
|
|
1947
|
+
status: "Active" | "Inactive";
|
|
1948
|
+
};
|
|
1949
|
+
|
|
1950
|
+
const DATA: Employee[] = [
|
|
1951
|
+
{
|
|
1952
|
+
name: "John Doe",
|
|
1953
|
+
projects: 5,
|
|
1954
|
+
role: "Software Engineer",
|
|
1955
|
+
salary: 85000,
|
|
1956
|
+
status: "Active",
|
|
1957
|
+
},
|
|
1958
|
+
{
|
|
1959
|
+
name: "Jane Smith",
|
|
1960
|
+
projects: 3,
|
|
1961
|
+
role: "Product Manager",
|
|
1962
|
+
salary: 95000,
|
|
1963
|
+
status: "Active",
|
|
1964
|
+
},
|
|
1965
|
+
{
|
|
1966
|
+
name: "Bob Johnson",
|
|
1967
|
+
projects: 2,
|
|
1968
|
+
role: "Designer",
|
|
1969
|
+
salary: 75000,
|
|
1970
|
+
status: "Inactive",
|
|
1971
|
+
},
|
|
1972
|
+
{
|
|
1973
|
+
name: "Alice Brown",
|
|
1974
|
+
projects: 7,
|
|
1975
|
+
role: "Data Scientist",
|
|
1976
|
+
salary: 90000,
|
|
1977
|
+
status: "Active",
|
|
1978
|
+
},
|
|
1979
|
+
{
|
|
1980
|
+
name: "Charlie Wilson",
|
|
1981
|
+
projects: 4,
|
|
1982
|
+
role: "DevOps Engineer",
|
|
1983
|
+
salary: 88000,
|
|
1984
|
+
status: "Active",
|
|
1985
|
+
},
|
|
1986
|
+
];
|
|
1987
|
+
|
|
1988
|
+
const STATUS_OPTIONS = [
|
|
1989
|
+
{ name: "All" },
|
|
1990
|
+
{ name: "Active" },
|
|
1991
|
+
{ name: "Inactive" },
|
|
1992
|
+
];
|
|
1993
|
+
|
|
1994
|
+
/** The cell context, plus how PreComposedTable wants the cell drawn. */
|
|
1995
|
+
type CellProps = CellContext<Employee, unknown> & {
|
|
1996
|
+
as?: React.ElementType;
|
|
1997
|
+
style?: React.CSSProperties;
|
|
1998
|
+
};
|
|
1999
|
+
|
|
2000
|
+
const RangeInputs = styled.div<CommonThemeProps>`
|
|
2001
|
+
${(props) => {
|
|
2002
|
+
const spaces = getSpaces(props);
|
|
2003
|
+
|
|
2004
|
+
return `
|
|
2005
|
+
align-items: center;
|
|
2006
|
+
display: flex;
|
|
2007
|
+
gap: ${spaces?.xxs}px;
|
|
2008
|
+
`;
|
|
2009
|
+
}}
|
|
2010
|
+
`;
|
|
2011
|
+
|
|
2012
|
+
function NumberRangeFilter({ column }: { column: Column<Employee, unknown> }) {
|
|
2013
|
+
const [min, max] = (column.getFilterValue() as [number?, number?]) ?? [];
|
|
2014
|
+
|
|
2015
|
+
const setBound = (index: 0 | 1, value: string) =>
|
|
2016
|
+
column.setFilterValue((old: [number?, number?] = []) => {
|
|
2017
|
+
const next: [number?, number?] = [old[0], old[1]];
|
|
2018
|
+
next[index] = value ? Number(value) : undefined;
|
|
2019
|
+
return next;
|
|
2020
|
+
});
|
|
2021
|
+
|
|
2022
|
+
return (
|
|
2023
|
+
<RangeInputs>
|
|
2024
|
+
<InputText
|
|
2025
|
+
hideLabel
|
|
2026
|
+
id={`${column.id}-min`}
|
|
2027
|
+
label="Min"
|
|
2028
|
+
onChange={(event) => setBound(0, event.target.value)}
|
|
2029
|
+
placeholder="Min"
|
|
2030
|
+
sdsType="textField"
|
|
2031
|
+
size="small"
|
|
2032
|
+
type="number"
|
|
2033
|
+
value={min ?? ""}
|
|
2034
|
+
/>
|
|
2035
|
+
<span>—</span>
|
|
2036
|
+
<InputText
|
|
2037
|
+
hideLabel
|
|
2038
|
+
id={`${column.id}-max`}
|
|
2039
|
+
label="Max"
|
|
2040
|
+
onChange={(event) => setBound(1, event.target.value)}
|
|
2041
|
+
placeholder="Max"
|
|
2042
|
+
sdsType="textField"
|
|
2043
|
+
size="small"
|
|
2044
|
+
type="number"
|
|
2045
|
+
value={max ?? ""}
|
|
2046
|
+
/>
|
|
2047
|
+
</RangeInputs>
|
|
2048
|
+
);
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
function numberCell({ as, column, getValue, style }: CellProps) {
|
|
2052
|
+
return (
|
|
2053
|
+
<CellBasic
|
|
2054
|
+
as={as}
|
|
2055
|
+
extraRightPadding={column.getCanSort() ? 20 : 0}
|
|
2056
|
+
horizontalAlign="right"
|
|
2057
|
+
primaryText={String(getValue())}
|
|
2058
|
+
shouldShowTooltipOnHover={false}
|
|
2059
|
+
style={style}
|
|
2060
|
+
tabularNums
|
|
2061
|
+
/>
|
|
2062
|
+
);
|
|
2063
|
+
}
|
|
2064
|
+
|
|
2065
|
+
const COLUMNS: ColumnDef<Employee>[] = [
|
|
2066
|
+
{
|
|
2067
|
+
accessorKey: "name",
|
|
2068
|
+
cell: ({ as, getValue, style }: CellProps) => (
|
|
2069
|
+
<CellBasic
|
|
2070
|
+
as={as}
|
|
2071
|
+
primaryText={getValue() as string}
|
|
2072
|
+
shouldShowTooltipOnHover={false}
|
|
2073
|
+
style={style}
|
|
2074
|
+
/>
|
|
2075
|
+
),
|
|
2076
|
+
header: "Name",
|
|
2077
|
+
meta: { isGrow: true, pinning: "left" },
|
|
2078
|
+
minSize: 150,
|
|
2079
|
+
},
|
|
2080
|
+
{
|
|
2081
|
+
accessorKey: "role",
|
|
2082
|
+
cell: ({ as, getValue, style }: CellProps) => (
|
|
2083
|
+
<CellBasic
|
|
2084
|
+
as={as}
|
|
2085
|
+
primaryText={getValue() as string}
|
|
2086
|
+
shouldShowTooltipOnHover={false}
|
|
2087
|
+
style={style}
|
|
2088
|
+
/>
|
|
2089
|
+
),
|
|
2090
|
+
header: "Role",
|
|
2091
|
+
minSize: 150,
|
|
2092
|
+
},
|
|
2093
|
+
{
|
|
2094
|
+
accessorKey: "status",
|
|
2095
|
+
cell: ({ as, getValue, style }: CellProps) => (
|
|
2096
|
+
<CellComponent as={as} style={style}>
|
|
2097
|
+
<Tag
|
|
2098
|
+
color={getValue() === "Active" ? "positive" : "negative"}
|
|
2099
|
+
hover={false}
|
|
2100
|
+
label={getValue() as string}
|
|
2101
|
+
sdsStyle="rounded"
|
|
2102
|
+
sdsType="secondary"
|
|
2103
|
+
/>
|
|
2104
|
+
</CellComponent>
|
|
2105
|
+
),
|
|
2106
|
+
filterFn: "equalsString",
|
|
2107
|
+
header: "Status",
|
|
2108
|
+
meta: {
|
|
2109
|
+
filterComponent: ({ column }) => (
|
|
2110
|
+
<Dropdown<{ name: string }, false, false, false>
|
|
2111
|
+
InputDropdownProps={{ width: "100%" }}
|
|
2112
|
+
label="Status"
|
|
2113
|
+
onChange={(_event, value) =>
|
|
2114
|
+
column.setFilterValue(
|
|
2115
|
+
!value || value.name === "All" ? undefined : value.name
|
|
2116
|
+
)
|
|
2117
|
+
}
|
|
2118
|
+
options={STATUS_OPTIONS}
|
|
2119
|
+
/>
|
|
2120
|
+
),
|
|
2121
|
+
},
|
|
2122
|
+
minSize: 140,
|
|
2123
|
+
},
|
|
2124
|
+
{
|
|
2125
|
+
accessorKey: "salary",
|
|
2126
|
+
cell: ({ as, column, getValue, style }: CellProps) => (
|
|
2127
|
+
<CellBasic
|
|
2128
|
+
as={as}
|
|
2129
|
+
extraRightPadding={column.getCanSort() ? 20 : 0}
|
|
2130
|
+
horizontalAlign="right"
|
|
2131
|
+
primaryText={`${(getValue() as number).toLocaleString()}`}
|
|
2132
|
+
shouldShowTooltipOnHover={false}
|
|
2133
|
+
style={style}
|
|
2134
|
+
tabularNums
|
|
2135
|
+
/>
|
|
2136
|
+
),
|
|
2137
|
+
filterFn: "inNumberRange",
|
|
2138
|
+
header: "Salary",
|
|
2139
|
+
meta: {
|
|
2140
|
+
filterComponent: NumberRangeFilter,
|
|
2141
|
+
headerCellProps: { horizontalAlign: "right" },
|
|
2142
|
+
},
|
|
2143
|
+
minSize: 180,
|
|
2144
|
+
},
|
|
2145
|
+
{
|
|
2146
|
+
accessorKey: "projects",
|
|
2147
|
+
cell: numberCell,
|
|
2148
|
+
filterFn: "inNumberRange",
|
|
2149
|
+
header: "Projects",
|
|
2150
|
+
meta: {
|
|
2151
|
+
filterComponent: NumberRangeFilter,
|
|
2152
|
+
headerCellProps: { horizontalAlign: "right" },
|
|
2153
|
+
},
|
|
2154
|
+
minSize: 180,
|
|
2155
|
+
},
|
|
2156
|
+
];
|
|
2157
|
+
|
|
2158
|
+
function App() {
|
|
2159
|
+
return (
|
|
2160
|
+
<div className="app">
|
|
2161
|
+
<PreComposedTable<Employee>
|
|
2162
|
+
columns={COLUMNS}
|
|
2163
|
+
data={DATA}
|
|
2164
|
+
enableColumnFiltering
|
|
2165
|
+
enableRowSelection
|
|
2166
|
+
enableSorting
|
|
2167
|
+
/>
|
|
2168
|
+
</div>
|
|
2169
|
+
);
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2172
|
+
export default App;
|
|
2173
|
+
```
|
|
2174
|
+
|
|
2175
|
+
### With table actions
|
|
2176
|
+
|
|
2177
|
+
The ref exposes the TanStack table as `ref.current.table`, which covers what the props do not: clearing filters, selecting rows from outside the table, paging programmatically. It is an imperative handle rather than state, so the toolbar calls into it from its click handlers.
|
|
2178
|
+
|
|
2179
|
+
**Example: PreComposedTableWithTableActions**
|
|
2180
|
+
|
|
2181
|
+
```tsx
|
|
2182
|
+
// The WithTableActions story: the ref exposes the underlying TanStack table as
|
|
2183
|
+
// ref.current.table, which is the escape hatch for everything the props do not
|
|
2184
|
+
// cover: clearing filters, selecting rows from outside the table, paging
|
|
2185
|
+
// programmatically, reading the filtered rows back out.
|
|
2186
|
+
//
|
|
2187
|
+
// It is an imperative handle rather than state, so reading from it during
|
|
2188
|
+
// render tells you nothing useful; call into it from an event handler, as the
|
|
2189
|
+
// toolbar does.
|
|
2190
|
+
//
|
|
2191
|
+
// A column left as a plain accessor is drawn as a CellHeader and a CellBasic.
|
|
2192
|
+
// A renderer of your own is handed the element and the width to draw with as
|
|
2193
|
+
// `as` and `style`, and forwards both. Without `as` it renders a second <td>
|
|
2194
|
+
// inside the one the table already drew. A header renderer is handed
|
|
2195
|
+
// `data-pinned-edge` too, which is what tells a pinned header to draw its
|
|
2196
|
+
// gradient.
|
|
2197
|
+
//
|
|
2198
|
+
// data, columns and paginationConfig sit outside the component so their
|
|
2199
|
+
// identity is stable: a new paginationConfig object sends the table back to its
|
|
2200
|
+
// first page.
|
|
2201
|
+
|
|
2202
|
+
import {
|
|
2203
|
+
Button,
|
|
2204
|
+
CellBasic,
|
|
2205
|
+
CellComponent,
|
|
2206
|
+
CellHeader,
|
|
2207
|
+
Icon,
|
|
2208
|
+
PreComposedTable,
|
|
2209
|
+
Tag,
|
|
2210
|
+
getSpaces,
|
|
2211
|
+
type CommonThemeProps,
|
|
2212
|
+
type PreComposedTableRef,
|
|
2213
|
+
} from "@czi-sds/components";
|
|
2214
|
+
import styled from "@emotion/styled";
|
|
2215
|
+
import type {
|
|
2216
|
+
CellContext,
|
|
2217
|
+
ColumnDef,
|
|
2218
|
+
HeaderContext,
|
|
2219
|
+
} from "@tanstack/react-table";
|
|
2220
|
+
import * as React from "react";
|
|
2221
|
+
|
|
2222
|
+
type Employee = {
|
|
2223
|
+
department: string;
|
|
2224
|
+
email: string;
|
|
2225
|
+
lastActive: string;
|
|
2226
|
+
location: string;
|
|
2227
|
+
name: string;
|
|
2228
|
+
projects: number;
|
|
2229
|
+
role: string;
|
|
2230
|
+
salary: number;
|
|
2231
|
+
startDate: string;
|
|
2232
|
+
status: "Active" | "Inactive";
|
|
2233
|
+
};
|
|
2234
|
+
|
|
2235
|
+
const DATA: Employee[] = [
|
|
2236
|
+
{
|
|
2237
|
+
department: "Engineering",
|
|
2238
|
+
email: "john.doe@example.com",
|
|
2239
|
+
lastActive: "2024-01-15",
|
|
2240
|
+
location: "San Francisco",
|
|
2241
|
+
name: "John Doe",
|
|
2242
|
+
projects: 5,
|
|
2243
|
+
role: "Software Engineer",
|
|
2244
|
+
salary: 85000,
|
|
2245
|
+
startDate: "2022-03-15",
|
|
2246
|
+
status: "Active",
|
|
2247
|
+
},
|
|
2248
|
+
{
|
|
2249
|
+
department: "Product",
|
|
2250
|
+
email: "jane.smith@example.com",
|
|
2251
|
+
lastActive: "2024-01-14",
|
|
2252
|
+
location: "New York",
|
|
2253
|
+
name: "Jane Smith",
|
|
2254
|
+
projects: 3,
|
|
2255
|
+
role: "Product Manager",
|
|
2256
|
+
salary: 95000,
|
|
2257
|
+
startDate: "2021-08-20",
|
|
2258
|
+
status: "Active",
|
|
2259
|
+
},
|
|
2260
|
+
{
|
|
2261
|
+
department: "Design",
|
|
2262
|
+
email: "bob.johnson@example.com",
|
|
2263
|
+
lastActive: "2024-01-10",
|
|
2264
|
+
location: "Austin",
|
|
2265
|
+
name: "Bob Johnson",
|
|
2266
|
+
projects: 2,
|
|
2267
|
+
role: "Designer",
|
|
2268
|
+
salary: 75000,
|
|
2269
|
+
startDate: "2023-01-10",
|
|
2270
|
+
status: "Inactive",
|
|
2271
|
+
},
|
|
2272
|
+
{
|
|
2273
|
+
department: "Data",
|
|
2274
|
+
email: "alice.brown@example.com",
|
|
2275
|
+
lastActive: "2024-01-16",
|
|
2276
|
+
location: "Seattle",
|
|
2277
|
+
name: "Alice Brown",
|
|
2278
|
+
projects: 7,
|
|
2279
|
+
role: "Data Scientist",
|
|
2280
|
+
salary: 90000,
|
|
2281
|
+
startDate: "2022-11-05",
|
|
2282
|
+
status: "Active",
|
|
2283
|
+
},
|
|
2284
|
+
{
|
|
2285
|
+
department: "Engineering",
|
|
2286
|
+
email: "charlie.wilson@example.com",
|
|
2287
|
+
lastActive: "2024-01-15",
|
|
2288
|
+
location: "Remote",
|
|
2289
|
+
name: "Charlie Wilson",
|
|
2290
|
+
projects: 4,
|
|
2291
|
+
role: "DevOps Engineer",
|
|
2292
|
+
salary: 88000,
|
|
2293
|
+
startDate: "2023-06-12",
|
|
2294
|
+
status: "Active",
|
|
2295
|
+
},
|
|
2296
|
+
];
|
|
2297
|
+
|
|
2298
|
+
/** The cell context, plus how PreComposedTable wants the cell drawn. */
|
|
2299
|
+
type CellProps = CellContext<Employee, unknown> & {
|
|
2300
|
+
as?: React.ElementType;
|
|
2301
|
+
style?: React.CSSProperties;
|
|
2302
|
+
};
|
|
2303
|
+
|
|
2304
|
+
/** The header context, plus which edge a pinned header sits against. */
|
|
2305
|
+
type HeaderProps = HeaderContext<Employee, unknown> & {
|
|
2306
|
+
"data-pinned-edge"?: "left" | "right";
|
|
2307
|
+
style?: React.CSSProperties;
|
|
2308
|
+
};
|
|
2309
|
+
|
|
2310
|
+
function textCell({ as, getValue, style }: CellProps) {
|
|
2311
|
+
return (
|
|
2312
|
+
<CellBasic
|
|
2313
|
+
as={as}
|
|
2314
|
+
primaryText={String(getValue())}
|
|
2315
|
+
shouldShowTooltipOnHover={false}
|
|
2316
|
+
style={style}
|
|
2317
|
+
/>
|
|
2318
|
+
);
|
|
2319
|
+
}
|
|
2320
|
+
|
|
2321
|
+
function numberCell({ as, column, getValue, style }: CellProps) {
|
|
2322
|
+
return (
|
|
2323
|
+
<CellBasic
|
|
2324
|
+
as={as}
|
|
2325
|
+
// Room for the sort chevron, which would otherwise sit on the number.
|
|
2326
|
+
extraRightPadding={column.getCanSort() ? 20 : 0}
|
|
2327
|
+
horizontalAlign="right"
|
|
2328
|
+
primaryText={String(getValue())}
|
|
2329
|
+
shouldShowTooltipOnHover={false}
|
|
2330
|
+
style={style}
|
|
2331
|
+
tabularNums
|
|
2332
|
+
/>
|
|
2333
|
+
);
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
const COLUMNS: ColumnDef<Employee>[] = [
|
|
2337
|
+
{
|
|
2338
|
+
accessorKey: "name",
|
|
2339
|
+
cell: ({ as, getValue, row, style }: CellProps) => (
|
|
2340
|
+
<CellBasic
|
|
2341
|
+
as={as}
|
|
2342
|
+
link={`mailto:${row.original.email}`}
|
|
2343
|
+
linkComponent="a"
|
|
2344
|
+
linkProps={{ target: "_blank" }}
|
|
2345
|
+
primaryText={getValue() as string}
|
|
2346
|
+
secondaryText={row.original.role}
|
|
2347
|
+
shouldShowTooltipOnHover={false}
|
|
2348
|
+
shouldShowUnderlineOnHover
|
|
2349
|
+
style={style}
|
|
2350
|
+
/>
|
|
2351
|
+
),
|
|
2352
|
+
enableSorting: false,
|
|
2353
|
+
header: ({ "data-pinned-edge": pinnedEdge, style }: HeaderProps) => (
|
|
2354
|
+
<CellHeader
|
|
2355
|
+
data-pinned-edge={pinnedEdge}
|
|
2356
|
+
hideSortIcon
|
|
2357
|
+
hover
|
|
2358
|
+
shouldShowTooltipOnHover
|
|
2359
|
+
style={style}
|
|
2360
|
+
tooltipProps={{ placement: "top", textAlign: "left", width: "wide" }}
|
|
2361
|
+
tooltipSubtitle="Each name links to the person's email address."
|
|
2362
|
+
tooltipText="Name"
|
|
2363
|
+
>
|
|
2364
|
+
Name
|
|
2365
|
+
</CellHeader>
|
|
2366
|
+
),
|
|
2367
|
+
meta: { isGrow: true, pinning: "left" },
|
|
2368
|
+
minSize: 150,
|
|
2369
|
+
},
|
|
2370
|
+
{
|
|
2371
|
+
accessorKey: "email",
|
|
2372
|
+
cell: ({ as, getValue, style }: CellProps) => (
|
|
2373
|
+
<CellBasic
|
|
2374
|
+
as={as}
|
|
2375
|
+
link={`mailto:${getValue() as string}`}
|
|
2376
|
+
linkProps={{ target: "_blank" }}
|
|
2377
|
+
primaryText={getValue() as string}
|
|
2378
|
+
primaryTextWrapLineCount={1}
|
|
2379
|
+
shouldShowTooltipOnHover={false}
|
|
2380
|
+
shouldShowUnderlineOnHover
|
|
2381
|
+
style={style}
|
|
2382
|
+
/>
|
|
2383
|
+
),
|
|
2384
|
+
header: "Email",
|
|
2385
|
+
meta: { isGrow: true },
|
|
2386
|
+
minSize: 200,
|
|
2387
|
+
},
|
|
2388
|
+
{
|
|
2389
|
+
accessorKey: "role",
|
|
2390
|
+
cell: textCell,
|
|
2391
|
+
header: "Role",
|
|
2392
|
+
meta: { verticalAlign: "middle" },
|
|
2393
|
+
minSize: 150,
|
|
2394
|
+
},
|
|
2395
|
+
{
|
|
2396
|
+
accessorKey: "department",
|
|
2397
|
+
cell: textCell,
|
|
2398
|
+
header: "Department",
|
|
2399
|
+
minSize: 120,
|
|
2400
|
+
},
|
|
2401
|
+
{
|
|
2402
|
+
accessorKey: "status",
|
|
2403
|
+
cell: ({ as, getValue, style }: CellProps) => (
|
|
2404
|
+
<CellComponent as={as} style={style}>
|
|
2405
|
+
<Tag
|
|
2406
|
+
color={getValue() === "Active" ? "positive" : "negative"}
|
|
2407
|
+
hover={false}
|
|
2408
|
+
label={getValue() as string}
|
|
2409
|
+
sdsStyle="rounded"
|
|
2410
|
+
sdsType="secondary"
|
|
2411
|
+
/>
|
|
2412
|
+
</CellComponent>
|
|
2413
|
+
),
|
|
2414
|
+
header: "Status",
|
|
2415
|
+
minSize: 100,
|
|
2416
|
+
},
|
|
2417
|
+
{
|
|
2418
|
+
accessorKey: "lastActive",
|
|
2419
|
+
cell: numberCell,
|
|
2420
|
+
header: "Last Active",
|
|
2421
|
+
meta: { headerCellProps: { horizontalAlign: "right" } },
|
|
2422
|
+
minSize: 120,
|
|
2423
|
+
},
|
|
2424
|
+
{
|
|
2425
|
+
accessorKey: "projects",
|
|
2426
|
+
cell: numberCell,
|
|
2427
|
+
header: "Projects",
|
|
2428
|
+
meta: { headerCellProps: { horizontalAlign: "right" } },
|
|
2429
|
+
minSize: 100,
|
|
2430
|
+
},
|
|
2431
|
+
{
|
|
2432
|
+
accessorKey: "salary",
|
|
2433
|
+
cell: ({ as, column, getValue, style }: CellProps) => (
|
|
2434
|
+
<CellBasic
|
|
2435
|
+
as={as}
|
|
2436
|
+
extraRightPadding={column.getCanSort() ? 20 : 0}
|
|
2437
|
+
horizontalAlign="right"
|
|
2438
|
+
primaryText={`${(getValue() as number).toLocaleString()}`}
|
|
2439
|
+
shouldShowTooltipOnHover={false}
|
|
2440
|
+
style={style}
|
|
2441
|
+
tabularNums
|
|
2442
|
+
/>
|
|
2443
|
+
),
|
|
2444
|
+
header: "Salary",
|
|
2445
|
+
meta: { headerCellProps: { horizontalAlign: "right" } },
|
|
2446
|
+
minSize: 120,
|
|
2447
|
+
},
|
|
2448
|
+
{
|
|
2449
|
+
accessorKey: "location",
|
|
2450
|
+
cell: textCell,
|
|
2451
|
+
header: "Location",
|
|
2452
|
+
minSize: 150,
|
|
2453
|
+
},
|
|
2454
|
+
{
|
|
2455
|
+
accessorKey: "startDate",
|
|
2456
|
+
cell: numberCell,
|
|
2457
|
+
header: "Start Date",
|
|
2458
|
+
meta: { headerCellProps: { horizontalAlign: "right" } },
|
|
2459
|
+
minSize: 120,
|
|
2460
|
+
},
|
|
2461
|
+
{
|
|
2462
|
+
cell: ({ as, row, style }: CellProps) => (
|
|
2463
|
+
<CellComponent as={as} horizontalAlign="right" style={style}>
|
|
2464
|
+
<Button
|
|
2465
|
+
aria-label={`Edit ${row.original.name}`}
|
|
2466
|
+
onClick={() => console.log("Edit", row.original)}
|
|
2467
|
+
sdsStyle="minimal"
|
|
2468
|
+
sdsType="secondary"
|
|
2469
|
+
>
|
|
2470
|
+
<Icon sdsIcon="Edit" sdsSize="s" />
|
|
2471
|
+
</Button>
|
|
2472
|
+
<Button
|
|
2473
|
+
aria-label={`Download ${row.original.name}`}
|
|
2474
|
+
onClick={() => console.log("Download", row.original)}
|
|
2475
|
+
sdsStyle="minimal"
|
|
2476
|
+
sdsType="secondary"
|
|
2477
|
+
>
|
|
2478
|
+
<Icon sdsIcon="Download" sdsSize="s" />
|
|
2479
|
+
</Button>
|
|
2480
|
+
</CellComponent>
|
|
2481
|
+
),
|
|
2482
|
+
enableSorting: false,
|
|
2483
|
+
header: "Actions",
|
|
2484
|
+
id: "actions",
|
|
2485
|
+
meta: {
|
|
2486
|
+
headerCellProps: { horizontalAlign: "right" },
|
|
2487
|
+
pinning: "right",
|
|
2488
|
+
verticalAlign: "middle",
|
|
2489
|
+
},
|
|
2490
|
+
size: 100,
|
|
2491
|
+
},
|
|
2492
|
+
];
|
|
2493
|
+
|
|
2494
|
+
const PAGINATION = { pageSize: 5 };
|
|
2495
|
+
|
|
2496
|
+
const Toolbar = styled.div<CommonThemeProps>`
|
|
2497
|
+
${(props) => {
|
|
2498
|
+
const spaces = getSpaces(props);
|
|
2499
|
+
|
|
2500
|
+
return `
|
|
2501
|
+
display: flex;
|
|
2502
|
+
flex-wrap: wrap;
|
|
2503
|
+
gap: ${spaces?.xs}px;
|
|
2504
|
+
margin-bottom: ${spaces?.l}px;
|
|
2505
|
+
`;
|
|
2506
|
+
}}
|
|
2507
|
+
`;
|
|
2508
|
+
|
|
2509
|
+
function App() {
|
|
2510
|
+
const tableRef = React.useRef<PreComposedTableRef<Employee>>(null);
|
|
2511
|
+
|
|
2512
|
+
return (
|
|
2513
|
+
<div className="app">
|
|
2514
|
+
<Toolbar>
|
|
2515
|
+
<Button
|
|
2516
|
+
onClick={() => tableRef.current?.table.resetColumnFilters()}
|
|
2517
|
+
sdsStyle="outline"
|
|
2518
|
+
sdsType="primary"
|
|
2519
|
+
>
|
|
2520
|
+
Clear all filters
|
|
2521
|
+
</Button>
|
|
2522
|
+
<Button
|
|
2523
|
+
onClick={() => tableRef.current?.table.resetSorting()}
|
|
2524
|
+
sdsStyle="outline"
|
|
2525
|
+
sdsType="primary"
|
|
2526
|
+
>
|
|
2527
|
+
Reset sorting
|
|
2528
|
+
</Button>
|
|
2529
|
+
<Button
|
|
2530
|
+
onClick={() => tableRef.current?.table.toggleAllRowsSelected(true)}
|
|
2531
|
+
sdsStyle="outline"
|
|
2532
|
+
sdsType="primary"
|
|
2533
|
+
>
|
|
2534
|
+
Select all rows
|
|
2535
|
+
</Button>
|
|
2536
|
+
<Button
|
|
2537
|
+
onClick={() => tableRef.current?.table.toggleAllRowsSelected(false)}
|
|
2538
|
+
sdsStyle="outline"
|
|
2539
|
+
sdsType="primary"
|
|
2540
|
+
>
|
|
2541
|
+
Deselect all rows
|
|
2542
|
+
</Button>
|
|
2543
|
+
<Button
|
|
2544
|
+
onClick={() => {
|
|
2545
|
+
tableRef.current?.table.resetColumnFilters();
|
|
2546
|
+
tableRef.current?.table.resetSorting();
|
|
2547
|
+
tableRef.current?.table.toggleAllRowsSelected(false);
|
|
2548
|
+
}}
|
|
2549
|
+
sdsStyle="outline"
|
|
2550
|
+
sdsType="secondary"
|
|
2551
|
+
>
|
|
2552
|
+
Reset everything
|
|
2553
|
+
</Button>
|
|
2554
|
+
</Toolbar>
|
|
2555
|
+
|
|
2556
|
+
<PreComposedTable<Employee>
|
|
2557
|
+
columns={COLUMNS}
|
|
2558
|
+
data={DATA}
|
|
2559
|
+
enableColumnFiltering
|
|
2560
|
+
enablePagination
|
|
2561
|
+
enableRowSelection
|
|
2562
|
+
enableSorting
|
|
2563
|
+
paginationConfig={PAGINATION}
|
|
2564
|
+
ref={tableRef}
|
|
2565
|
+
/>
|
|
2566
|
+
</div>
|
|
2567
|
+
);
|
|
2568
|
+
}
|
|
2569
|
+
|
|
2570
|
+
export default App;
|
|
2571
|
+
```
|
|
2572
|
+
|
|
2573
|
+
### With table options
|
|
2574
|
+
|
|
2575
|
+
`tableOptions` is merged into the underlying TanStack table's options, so anything `useReactTable` accepts can be set through it. This one passes `autoResetPageIndex: false`, so filtering leaves you on the page you were reading instead of returning you to the first.
|
|
2576
|
+
|
|
2577
|
+
**Example: PreComposedTableWithTableOptions**
|
|
2578
|
+
|
|
2579
|
+
```tsx
|
|
2580
|
+
// The WithTableOptions story: tableOptions is merged into the underlying
|
|
2581
|
+
// TanStack table's options, so anything useReactTable accepts can be set here:
|
|
2582
|
+
// meta, filter functions, debugTable, and the rest.
|
|
2583
|
+
//
|
|
2584
|
+
// This one sets autoResetPageIndex: false, so filtering the table leaves you on
|
|
2585
|
+
// the page you were reading instead of sending you back to the first one.
|
|
2586
|
+
//
|
|
2587
|
+
// A column left as a plain accessor is drawn as a CellHeader and a CellBasic.
|
|
2588
|
+
// A renderer of your own is handed the element and the width to draw with as
|
|
2589
|
+
// `as` and `style`, and forwards both. Without `as` it renders a second <td>
|
|
2590
|
+
// inside the one the table already drew. A header renderer is handed
|
|
2591
|
+
// `data-pinned-edge` too, which is what tells a pinned header to draw its
|
|
2592
|
+
// gradient.
|
|
2593
|
+
//
|
|
2594
|
+
// data, columns and paginationConfig sit outside the component so their
|
|
2595
|
+
// identity is stable: a new paginationConfig object sends the table back to its
|
|
2596
|
+
// first page.
|
|
2597
|
+
|
|
2598
|
+
import {
|
|
2599
|
+
Button,
|
|
2600
|
+
CellBasic,
|
|
2601
|
+
CellComponent,
|
|
2602
|
+
CellHeader,
|
|
2603
|
+
Icon,
|
|
2604
|
+
PreComposedTable,
|
|
2605
|
+
Tag,
|
|
2606
|
+
} from "@czi-sds/components";
|
|
2607
|
+
import type {
|
|
2608
|
+
CellContext,
|
|
2609
|
+
ColumnDef,
|
|
2610
|
+
HeaderContext,
|
|
2611
|
+
} from "@tanstack/react-table";
|
|
2612
|
+
import * as React from "react";
|
|
2613
|
+
|
|
2614
|
+
type Employee = {
|
|
2615
|
+
department: string;
|
|
2616
|
+
email: string;
|
|
2617
|
+
lastActive: string;
|
|
2618
|
+
location: string;
|
|
2619
|
+
name: string;
|
|
2620
|
+
projects: number;
|
|
2621
|
+
role: string;
|
|
2622
|
+
salary: number;
|
|
2623
|
+
startDate: string;
|
|
2624
|
+
status: "Active" | "Inactive";
|
|
2625
|
+
};
|
|
2626
|
+
|
|
2627
|
+
const DATA: Employee[] = [
|
|
2628
|
+
{
|
|
2629
|
+
department: "Engineering",
|
|
2630
|
+
email: "john.doe@example.com",
|
|
2631
|
+
lastActive: "2024-01-15",
|
|
2632
|
+
location: "San Francisco",
|
|
2633
|
+
name: "John Doe",
|
|
2634
|
+
projects: 5,
|
|
2635
|
+
role: "Software Engineer",
|
|
2636
|
+
salary: 85000,
|
|
2637
|
+
startDate: "2022-03-15",
|
|
2638
|
+
status: "Active",
|
|
2639
|
+
},
|
|
2640
|
+
{
|
|
2641
|
+
department: "Product",
|
|
2642
|
+
email: "jane.smith@example.com",
|
|
2643
|
+
lastActive: "2024-01-14",
|
|
2644
|
+
location: "New York",
|
|
2645
|
+
name: "Jane Smith",
|
|
2646
|
+
projects: 3,
|
|
2647
|
+
role: "Product Manager",
|
|
2648
|
+
salary: 95000,
|
|
2649
|
+
startDate: "2021-08-20",
|
|
2650
|
+
status: "Active",
|
|
2651
|
+
},
|
|
2652
|
+
{
|
|
2653
|
+
department: "Design",
|
|
2654
|
+
email: "bob.johnson@example.com",
|
|
2655
|
+
lastActive: "2024-01-10",
|
|
2656
|
+
location: "Austin",
|
|
2657
|
+
name: "Bob Johnson",
|
|
2658
|
+
projects: 2,
|
|
2659
|
+
role: "Designer",
|
|
2660
|
+
salary: 75000,
|
|
2661
|
+
startDate: "2023-01-10",
|
|
2662
|
+
status: "Inactive",
|
|
2663
|
+
},
|
|
2664
|
+
{
|
|
2665
|
+
department: "Data",
|
|
2666
|
+
email: "alice.brown@example.com",
|
|
2667
|
+
lastActive: "2024-01-16",
|
|
2668
|
+
location: "Seattle",
|
|
2669
|
+
name: "Alice Brown",
|
|
2670
|
+
projects: 7,
|
|
2671
|
+
role: "Data Scientist",
|
|
2672
|
+
salary: 90000,
|
|
2673
|
+
startDate: "2022-11-05",
|
|
2674
|
+
status: "Active",
|
|
2675
|
+
},
|
|
2676
|
+
{
|
|
2677
|
+
department: "Engineering",
|
|
2678
|
+
email: "charlie.wilson@example.com",
|
|
2679
|
+
lastActive: "2024-01-15",
|
|
2680
|
+
location: "Remote",
|
|
2681
|
+
name: "Charlie Wilson",
|
|
2682
|
+
projects: 4,
|
|
2683
|
+
role: "DevOps Engineer",
|
|
2684
|
+
salary: 88000,
|
|
2685
|
+
startDate: "2023-06-12",
|
|
2686
|
+
status: "Active",
|
|
2687
|
+
},
|
|
2688
|
+
];
|
|
2689
|
+
|
|
2690
|
+
/** The cell context, plus how PreComposedTable wants the cell drawn. */
|
|
2691
|
+
type CellProps = CellContext<Employee, unknown> & {
|
|
2692
|
+
as?: React.ElementType;
|
|
2693
|
+
style?: React.CSSProperties;
|
|
2694
|
+
};
|
|
2695
|
+
|
|
2696
|
+
/** The header context, plus which edge a pinned header sits against. */
|
|
2697
|
+
type HeaderProps = HeaderContext<Employee, unknown> & {
|
|
2698
|
+
"data-pinned-edge"?: "left" | "right";
|
|
2699
|
+
style?: React.CSSProperties;
|
|
2700
|
+
};
|
|
2701
|
+
|
|
2702
|
+
function textCell({ as, getValue, style }: CellProps) {
|
|
2703
|
+
return (
|
|
2704
|
+
<CellBasic
|
|
2705
|
+
as={as}
|
|
2706
|
+
primaryText={String(getValue())}
|
|
2707
|
+
shouldShowTooltipOnHover={false}
|
|
2708
|
+
style={style}
|
|
2709
|
+
/>
|
|
2710
|
+
);
|
|
2711
|
+
}
|
|
2712
|
+
|
|
2713
|
+
function numberCell({ as, column, getValue, style }: CellProps) {
|
|
2714
|
+
return (
|
|
2715
|
+
<CellBasic
|
|
2716
|
+
as={as}
|
|
2717
|
+
// Room for the sort chevron, which would otherwise sit on the number.
|
|
2718
|
+
extraRightPadding={column.getCanSort() ? 20 : 0}
|
|
2719
|
+
horizontalAlign="right"
|
|
2720
|
+
primaryText={String(getValue())}
|
|
2721
|
+
shouldShowTooltipOnHover={false}
|
|
2722
|
+
style={style}
|
|
2723
|
+
tabularNums
|
|
2724
|
+
/>
|
|
2725
|
+
);
|
|
2726
|
+
}
|
|
2727
|
+
|
|
2728
|
+
const COLUMNS: ColumnDef<Employee>[] = [
|
|
2729
|
+
{
|
|
2730
|
+
accessorKey: "name",
|
|
2731
|
+
cell: ({ as, getValue, row, style }: CellProps) => (
|
|
2732
|
+
<CellBasic
|
|
2733
|
+
as={as}
|
|
2734
|
+
link={`mailto:${row.original.email}`}
|
|
2735
|
+
linkComponent="a"
|
|
2736
|
+
linkProps={{ target: "_blank" }}
|
|
2737
|
+
primaryText={getValue() as string}
|
|
2738
|
+
secondaryText={row.original.role}
|
|
2739
|
+
shouldShowTooltipOnHover={false}
|
|
2740
|
+
shouldShowUnderlineOnHover
|
|
2741
|
+
style={style}
|
|
2742
|
+
/>
|
|
2743
|
+
),
|
|
2744
|
+
enableSorting: false,
|
|
2745
|
+
header: ({ "data-pinned-edge": pinnedEdge, style }: HeaderProps) => (
|
|
2746
|
+
<CellHeader
|
|
2747
|
+
data-pinned-edge={pinnedEdge}
|
|
2748
|
+
hideSortIcon
|
|
2749
|
+
hover
|
|
2750
|
+
shouldShowTooltipOnHover
|
|
2751
|
+
style={style}
|
|
2752
|
+
tooltipProps={{ placement: "top", textAlign: "left", width: "wide" }}
|
|
2753
|
+
tooltipSubtitle="Each name links to the person's email address."
|
|
2754
|
+
tooltipText="Name"
|
|
2755
|
+
>
|
|
2756
|
+
Name
|
|
2757
|
+
</CellHeader>
|
|
2758
|
+
),
|
|
2759
|
+
meta: { isGrow: true, pinning: "left" },
|
|
2760
|
+
minSize: 150,
|
|
2761
|
+
},
|
|
2762
|
+
{
|
|
2763
|
+
accessorKey: "email",
|
|
2764
|
+
cell: ({ as, getValue, style }: CellProps) => (
|
|
2765
|
+
<CellBasic
|
|
2766
|
+
as={as}
|
|
2767
|
+
link={`mailto:${getValue() as string}`}
|
|
2768
|
+
linkProps={{ target: "_blank" }}
|
|
2769
|
+
primaryText={getValue() as string}
|
|
2770
|
+
primaryTextWrapLineCount={1}
|
|
2771
|
+
shouldShowTooltipOnHover={false}
|
|
2772
|
+
shouldShowUnderlineOnHover
|
|
2773
|
+
style={style}
|
|
2774
|
+
/>
|
|
2775
|
+
),
|
|
2776
|
+
header: "Email",
|
|
2777
|
+
meta: { isGrow: true },
|
|
2778
|
+
minSize: 200,
|
|
2779
|
+
},
|
|
2780
|
+
{
|
|
2781
|
+
accessorKey: "role",
|
|
2782
|
+
cell: textCell,
|
|
2783
|
+
header: "Role",
|
|
2784
|
+
meta: { verticalAlign: "middle" },
|
|
2785
|
+
minSize: 150,
|
|
2786
|
+
},
|
|
2787
|
+
{
|
|
2788
|
+
accessorKey: "department",
|
|
2789
|
+
cell: textCell,
|
|
2790
|
+
header: "Department",
|
|
2791
|
+
minSize: 120,
|
|
2792
|
+
},
|
|
2793
|
+
{
|
|
2794
|
+
accessorKey: "status",
|
|
2795
|
+
cell: ({ as, getValue, style }: CellProps) => (
|
|
2796
|
+
<CellComponent as={as} style={style}>
|
|
2797
|
+
<Tag
|
|
2798
|
+
color={getValue() === "Active" ? "positive" : "negative"}
|
|
2799
|
+
hover={false}
|
|
2800
|
+
label={getValue() as string}
|
|
2801
|
+
sdsStyle="rounded"
|
|
2802
|
+
sdsType="secondary"
|
|
2803
|
+
/>
|
|
2804
|
+
</CellComponent>
|
|
2805
|
+
),
|
|
2806
|
+
header: "Status",
|
|
2807
|
+
minSize: 100,
|
|
2808
|
+
},
|
|
2809
|
+
{
|
|
2810
|
+
accessorKey: "lastActive",
|
|
2811
|
+
cell: numberCell,
|
|
2812
|
+
header: "Last Active",
|
|
2813
|
+
meta: { headerCellProps: { horizontalAlign: "right" } },
|
|
2814
|
+
minSize: 120,
|
|
2815
|
+
},
|
|
2816
|
+
{
|
|
2817
|
+
accessorKey: "projects",
|
|
2818
|
+
cell: numberCell,
|
|
2819
|
+
header: "Projects",
|
|
2820
|
+
meta: { headerCellProps: { horizontalAlign: "right" } },
|
|
2821
|
+
minSize: 100,
|
|
2822
|
+
},
|
|
2823
|
+
{
|
|
2824
|
+
accessorKey: "salary",
|
|
2825
|
+
cell: ({ as, column, getValue, style }: CellProps) => (
|
|
2826
|
+
<CellBasic
|
|
2827
|
+
as={as}
|
|
2828
|
+
extraRightPadding={column.getCanSort() ? 20 : 0}
|
|
2829
|
+
horizontalAlign="right"
|
|
2830
|
+
primaryText={`${(getValue() as number).toLocaleString()}`}
|
|
2831
|
+
shouldShowTooltipOnHover={false}
|
|
2832
|
+
style={style}
|
|
2833
|
+
tabularNums
|
|
2834
|
+
/>
|
|
2835
|
+
),
|
|
2836
|
+
header: "Salary",
|
|
2837
|
+
meta: { headerCellProps: { horizontalAlign: "right" } },
|
|
2838
|
+
minSize: 120,
|
|
2839
|
+
},
|
|
2840
|
+
{
|
|
2841
|
+
accessorKey: "location",
|
|
2842
|
+
cell: textCell,
|
|
2843
|
+
header: "Location",
|
|
2844
|
+
minSize: 150,
|
|
2845
|
+
},
|
|
2846
|
+
{
|
|
2847
|
+
accessorKey: "startDate",
|
|
2848
|
+
cell: numberCell,
|
|
2849
|
+
header: "Start Date",
|
|
2850
|
+
meta: { headerCellProps: { horizontalAlign: "right" } },
|
|
2851
|
+
minSize: 120,
|
|
2852
|
+
},
|
|
2853
|
+
{
|
|
2854
|
+
cell: ({ as, row, style }: CellProps) => (
|
|
2855
|
+
<CellComponent as={as} horizontalAlign="right" style={style}>
|
|
2856
|
+
<Button
|
|
2857
|
+
aria-label={`Edit ${row.original.name}`}
|
|
2858
|
+
onClick={() => console.log("Edit", row.original)}
|
|
2859
|
+
sdsStyle="minimal"
|
|
2860
|
+
sdsType="secondary"
|
|
2861
|
+
>
|
|
2862
|
+
<Icon sdsIcon="Edit" sdsSize="s" />
|
|
2863
|
+
</Button>
|
|
2864
|
+
<Button
|
|
2865
|
+
aria-label={`Download ${row.original.name}`}
|
|
2866
|
+
onClick={() => console.log("Download", row.original)}
|
|
2867
|
+
sdsStyle="minimal"
|
|
2868
|
+
sdsType="secondary"
|
|
2869
|
+
>
|
|
2870
|
+
<Icon sdsIcon="Download" sdsSize="s" />
|
|
2871
|
+
</Button>
|
|
2872
|
+
</CellComponent>
|
|
2873
|
+
),
|
|
2874
|
+
enableSorting: false,
|
|
2875
|
+
header: "Actions",
|
|
2876
|
+
id: "actions",
|
|
2877
|
+
meta: {
|
|
2878
|
+
headerCellProps: { horizontalAlign: "right" },
|
|
2879
|
+
pinning: "right",
|
|
2880
|
+
verticalAlign: "middle",
|
|
2881
|
+
},
|
|
2882
|
+
size: 100,
|
|
2883
|
+
},
|
|
2884
|
+
];
|
|
2885
|
+
|
|
2886
|
+
const PAGINATION = { pageSize: 3 };
|
|
2887
|
+
|
|
2888
|
+
const TABLE_OPTIONS = { autoResetPageIndex: false };
|
|
2889
|
+
|
|
2890
|
+
function App() {
|
|
2891
|
+
return (
|
|
2892
|
+
<div className="app">
|
|
2893
|
+
<PreComposedTable<Employee>
|
|
2894
|
+
columns={COLUMNS}
|
|
2895
|
+
data={DATA}
|
|
2896
|
+
enableColumnFiltering
|
|
2897
|
+
enablePagination
|
|
2898
|
+
enableRowSelection
|
|
2899
|
+
enableSorting
|
|
2900
|
+
paginationConfig={PAGINATION}
|
|
2901
|
+
tableOptions={TABLE_OPTIONS}
|
|
2902
|
+
/>
|
|
2903
|
+
</div>
|
|
2904
|
+
);
|
|
2905
|
+
}
|
|
2906
|
+
|
|
2907
|
+
export default App;
|
|
2908
|
+
```
|
|
2909
|
+
|
|
2910
|
+
## Props
|
|
2911
|
+
|
|
2912
|
+
PreComposedTable is generic over your row type, so `data` and `columns` are typed together. It owns the table state (sorting, selection, filtering, pagination, pinning), and every feature is off unless you switch it on.
|
|
2913
|
+
|
|
2914
|
+
| Name | Type | Default | Description |
|
|
2915
|
+
| -------------------------- | ------------------------------------------------------------------------------ | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
2916
|
+
| `data` | `TData[]` | - | Array of data objects to display in the table rows |
|
|
2917
|
+
| `columns` | `ColumnDef<TData>[]` | - | Column definitions that specify how data should be rendered and behave |
|
|
2918
|
+
| `border` | `boolean` | `true` | Draws the border around the scrolling table area |
|
|
2919
|
+
| `sdsStyle` | `"lined"` \| `"striped"` | `"lined"` | Whether rows are separated by dividers or by alternating backgrounds |
|
|
2920
|
+
| `enableSorting` | `boolean` | `false` | Makes column headers sortable, wiring each one's chevron and click handler for you |
|
|
2921
|
+
| `enableRowSelection` | `boolean` | `false` | Adds a checkbox column with select-all. To control that column yourself, include one with the `id` `"SdsTableSelectColumn"` and it will be used instead of the generated one |
|
|
2922
|
+
| `enableColumnFiltering` | `boolean` | `false` | Adds a filter row under the header. Each column gets a search input unless its `meta` supplies a `filterComponent` |
|
|
2923
|
+
| `shouldPinSelectRowToLeft` | `boolean` | `true` | Pins the row selection column to the left/right side of the table |
|
|
2924
|
+
| `enablePagination` | `boolean` | `false` | Enables pagination controls at the bottom of the table |
|
|
2925
|
+
| `enableGlobalFiltering` | `boolean` | `false` | Displays a search input that filters across all columns |
|
|
2926
|
+
| `paginationConfig` | `Partial<PaginationProps> & { placement?: "left" \| "center" \| "right" }` | - | Passed through to Pagination, plus a placement for the controls, which sit on the right by default. `pageSize` also sets the initial page size, which is `10` otherwise. Changing this object resets the table to the first page |
|
|
2927
|
+
| `className` | `string` | - | CSS class name to apply to the table container |
|
|
2928
|
+
| `style` | `React.CSSProperties` | - | Inline styles to apply to the table container |
|
|
2929
|
+
| `tableWidth` | `string` | `100%` | Sets the width of the table container |
|
|
2930
|
+
| `tableRowProps` | `Partial<TableRowProps>` | - | Additional props to pass to each table row component |
|
|
2931
|
+
| `onRowSelect` | `(selectedRows: TData[]) => void` | - | Callback triggered when row selection changes; receives array of selected row data |
|
|
2932
|
+
| `tableOptions` | `Partial<Omit<TableOptions<TData>, "data" \| "columns" \| "getCoreRowModel">>` | - | Escape hatch to the underlying TanStack table. Anything here is merged into its options, and a state you supply overrides the one the component keeps, which is how you drive sorting or pagination from outside. The row models and change handlers are always the component's own |
|
|
2933
|
+
|
|
2934
|
+
The component also takes a ref, which exposes the TanStack table instance as `ref.current.table` for anything the props do not cover.
|
|
2935
|
+
|
|
2936
|
+
## Column meta
|
|
2937
|
+
|
|
2938
|
+
Layout and pinning are set per column through the standard TanStack column definition's `meta` field, which SDS extends with the following. Everything else in a column definition (`accessorKey`, `header`, `cell`, `enableSorting`, `size`, `minSize`, `maxSize`, `filterFn`) behaves as TanStack documents it, and a header or cell given as a plain string is rendered as a CellHeader or CellBasic for you.
|
|
2939
|
+
|
|
2940
|
+
| Name | Type | Default | Description |
|
|
2941
|
+
| ----------------- | ---------------------------------------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
|
2942
|
+
| `pinning` | `"left"` \| `"right"` | - | Sticks the column to that edge while the table scrolls sideways, with a gradient at the edge once there is something hidden behind it |
|
|
2943
|
+
| `verticalAlign` | `"top"` \| `"middle"` \| `"bottom"` | - | Vertical alignment of the column's cells |
|
|
2944
|
+
| `widthPercentage` | `number` | - | The column's share of the table width, as a percentage rather than a fixed size |
|
|
2945
|
+
| `isGrow` | `boolean` | - | Lets this column absorb whatever width the others leave over |
|
|
2946
|
+
| `headerCellProps` | `Partial<CellHeaderProps>` | - | Props for the CellHeader generated from a string header, for tooltips, alignment or truncation |
|
|
2947
|
+
| `filterComponent` | `(props: { column: Column<TData>; table: Table<TData> }) => React.ReactNode` | - | Replaces the default search input in this column's filter cell, for a select or a range instead |
|