@neo4j-ndl/react 4.16.8 → 4.16.9
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/package.json +5 -2
- package/skills/ndl-react/SKILL.md +152 -0
- package/skills/ndl-react/components/accordion.md +386 -0
- package/skills/ndl-react/components/ai/code-preview.md +128 -0
- package/skills/ndl-react/components/ai/file-tag.md +90 -0
- package/skills/ndl-react/components/ai/image-tag.md +91 -0
- package/skills/ndl-react/components/ai/more-files.md +73 -0
- package/skills/ndl-react/components/ai/presence.md +54 -0
- package/skills/ndl-react/components/ai/preview.md +498 -0
- package/skills/ndl-react/components/ai/prompt.md +362 -0
- package/skills/ndl-react/components/ai/reasoning.md +95 -0
- package/skills/ndl-react/components/ai/response.md +1109 -0
- package/skills/ndl-react/components/ai/suggestion.md +84 -0
- package/skills/ndl-react/components/ai/thinking.md +43 -0
- package/skills/ndl-react/components/ai/tool-call.md +43 -0
- package/skills/ndl-react/components/ai/user-bubble.md +187 -0
- package/skills/ndl-react/components/avatar.md +169 -0
- package/skills/ndl-react/components/banner.md +225 -0
- package/skills/ndl-react/components/box.md +39 -0
- package/skills/ndl-react/components/breadcrumbs.md +245 -0
- package/skills/ndl-react/components/checkbox.md +269 -0
- package/skills/ndl-react/components/clean-icon-button.md +294 -0
- package/skills/ndl-react/components/clipboard-button.md +154 -0
- package/skills/ndl-react/components/code-block.md +276 -0
- package/skills/ndl-react/components/code.md +68 -0
- package/skills/ndl-react/components/color-picker.md +131 -0
- package/skills/ndl-react/components/conditional-wrap.md +46 -0
- package/skills/ndl-react/components/data-grid.md +3121 -0
- package/skills/ndl-react/components/date-picker.md +652 -0
- package/skills/ndl-react/components/dialog.md +485 -0
- package/skills/ndl-react/components/dismissible-tag.md +136 -0
- package/skills/ndl-react/components/divider.md +96 -0
- package/skills/ndl-react/components/drawer.md +543 -0
- package/skills/ndl-react/components/dropdown-button.md +161 -0
- package/skills/ndl-react/components/dropzone.md +371 -0
- package/skills/ndl-react/components/filled-button.md +26 -0
- package/skills/ndl-react/components/flex.md +82 -0
- package/skills/ndl-react/components/graph-label.md +167 -0
- package/skills/ndl-react/components/icon-button-array.md +187 -0
- package/skills/ndl-react/components/icon-button.md +193 -0
- package/skills/ndl-react/components/inline-edit.md +209 -0
- package/skills/ndl-react/components/kbd.md +63 -0
- package/skills/ndl-react/components/loading-bar.md +43 -0
- package/skills/ndl-react/components/loading-spinner.md +57 -0
- package/skills/ndl-react/components/logo.md +57 -0
- package/skills/ndl-react/components/menu.md +993 -0
- package/skills/ndl-react/components/modal.md +27 -0
- package/skills/ndl-react/components/next/tree-view.md +966 -0
- package/skills/ndl-react/components/outlined-button.md +26 -0
- package/skills/ndl-react/components/popover.md +283 -0
- package/skills/ndl-react/components/progress-bar.md +70 -0
- package/skills/ndl-react/components/radio.md +150 -0
- package/skills/ndl-react/components/read-only-tag.md +96 -0
- package/skills/ndl-react/components/segmented-control.md +200 -0
- package/skills/ndl-react/components/select-icon-button.md +183 -0
- package/skills/ndl-react/components/select.md +530 -0
- package/skills/ndl-react/components/selectable-tag.md +203 -0
- package/skills/ndl-react/components/side-navigation.md +652 -0
- package/skills/ndl-react/components/skeleton.md +274 -0
- package/skills/ndl-react/components/slider.md +308 -0
- package/skills/ndl-react/components/spotlight.md +796 -0
- package/skills/ndl-react/components/status-indicator.md +57 -0
- package/skills/ndl-react/components/status-label.md +132 -0
- package/skills/ndl-react/components/switch.md +167 -0
- package/skills/ndl-react/components/tabs.md +433 -0
- package/skills/ndl-react/components/tag.md +91 -0
- package/skills/ndl-react/components/text-area.md +178 -0
- package/skills/ndl-react/components/text-button.md +25 -0
- package/skills/ndl-react/components/text-input.md +475 -0
- package/skills/ndl-react/components/text-link.md +119 -0
- package/skills/ndl-react/components/time-picker.md +137 -0
- package/skills/ndl-react/components/timezone-picker.md +496 -0
- package/skills/ndl-react/components/tooltip.md +478 -0
- package/skills/ndl-react/components/tree-view.md +545 -0
- package/skills/ndl-react/components/typography.md +72 -0
- package/skills/ndl-react/components/wizard.md +152 -0
|
@@ -0,0 +1,3121 @@
|
|
|
1
|
+
# DataGrid
|
|
2
|
+
|
|
3
|
+
Import: `import { DataGrid } from '@neo4j-ndl/react'`
|
|
4
|
+
|
|
5
|
+
## Props
|
|
6
|
+
|
|
7
|
+
### DataGrid
|
|
8
|
+
|
|
9
|
+
| Prop | Type | Required | Default | Description |
|
|
10
|
+
|------|------|----------|---------|-------------|
|
|
11
|
+
| `components` | `DataGridComponentsInterface<T>` | | | Table sub-component overrides |
|
|
12
|
+
| `isAutoResizingColumns` | `boolean` | | `true` | Enables automatic resizing of columns. If `isResizable` is `true`, the columns will only be automatically resized on initial render of the table. If `isResizable` is `false`, the columns will be resized automatically to always fit the table width (assuming that column maxWidths allow for it). Note: Action columns are a fixed size of 40px, and Pinned columns are not resized automatically. @defaultValue true |
|
|
13
|
+
| `isKeyboardNavigable` | `boolean` | | `true` | Enables keyboard navigation. To control keyboard navigation programmatically for edge cases, pass a `ref` to `tableNavRef` |
|
|
14
|
+
| `isLoading` | `boolean` | | `false` | Enables table's loading state |
|
|
15
|
+
| `isResizable` | `boolean` | | `true` | Resizing the width of columns |
|
|
16
|
+
| `isSkeletonLoading` | `boolean` | | `false` | |
|
|
17
|
+
| `portalTarget` | `HTMLElement \| null` | | `document.body` | The target element for portaled elements to be rendered in, e.g. the modal root if the data grid is inside a modal. This is used in for example the DropdownCell, RowsPerPage, and ColumnControls. |
|
|
18
|
+
| `ref` | `Ref<HTMLDivElement>` | | | A ref to apply to the root element. |
|
|
19
|
+
| `rootProps` | `HtmlAttributes<"div">` | | | @deprecated Use htmlAttributes, className and style on the root element instead |
|
|
20
|
+
| `skeletonProps` | `(SkeletonProps & { as?: ElementType<any, keyof IntrinsicElements>; } & BaseProps<ElementType<any, keyof IntrinsicElements>>)` | | | |
|
|
21
|
+
| `styleRules` | `{ apply: { color?: { value: string; isDisabled?: boolean; }; colorRange?: { maxColor: string; maxValue: number; minColor: string; minValue: number; onColumn: string; isDisabled?: boolean \| undefined; midColor?: string \| undefined; midValue?: number \| undefined; } \| undefined; textColor?: { .....` | | | Rule-based conditional styling for cells and rows. |
|
|
22
|
+
| `styleRuleValueConverters` | `DataGridStyleRuleValueConverters` | | | Per-column converters that teach the style-rule engine how to interpret complex cell values. |
|
|
23
|
+
| `styling` | `Partial<{ hasZebraStriping: boolean; borderStyle: "all-sides" \| "horizontal" \| "vertical" \| "none"; headerStyle: "filled" \| "clean"; hasHoverEffects: boolean; isCompact: boolean; }>` | | `{}` | |
|
|
24
|
+
| `tableInstance` | `Table<T>` | ✅ | | Table instance created from `useTable` |
|
|
25
|
+
| `tableNavRef` | `RefObject<any>` | | | Reference to the tableNav instance for programmatic control |
|
|
26
|
+
|
|
27
|
+
### DataGrid.ActionButton
|
|
28
|
+
|
|
29
|
+
| Prop | Type | Required | Default | Description |
|
|
30
|
+
|------|------|----------|---------|-------------|
|
|
31
|
+
| `action` | `MenuItemProps` | ✅ | | |
|
|
32
|
+
| `as` | `ElementType<any, keyof IntrinsicElements>` | | | An override of the default HTML tag of the root of the component. Can also be another React component. |
|
|
33
|
+
| `onClose` | `() => void` | ✅ | | |
|
|
34
|
+
| `ref` | `any` | | | A ref to apply to the root element. |
|
|
35
|
+
|
|
36
|
+
### DataGrid.ActionCell
|
|
37
|
+
|
|
38
|
+
| Prop | Type | Required | Default | Description |
|
|
39
|
+
|------|------|----------|---------|-------------|
|
|
40
|
+
| `cell` | `CellContext<T, unknown>` | | | |
|
|
41
|
+
| `innerCleanIconButtonProps` | `Partial<Omit<CleanIconButtonButtonProps & { as?: ElementType<any, keyof IntrinsicElements>; } & BaseProps<ElementType<any, keyof IntrinsicElements>>, "ref">>` | | | |
|
|
42
|
+
| `innerMenuProps` | `Partial<MenuProps & { as?: ElementType<any, keyof IntrinsicElements>; } & BaseProps<ElementType<any, keyof IntrinsicElements>>>` | | | |
|
|
43
|
+
|
|
44
|
+
### DataGrid.Body
|
|
45
|
+
|
|
46
|
+
| Prop | Type | Required | Default | Description |
|
|
47
|
+
|------|------|----------|---------|-------------|
|
|
48
|
+
| `children` | `ReactNode` | | | |
|
|
49
|
+
| `ref` | `Ref<HTMLDivElement>` | | | A ref to apply to the root element. |
|
|
50
|
+
|
|
51
|
+
### DataGrid.BodyCell
|
|
52
|
+
|
|
53
|
+
| Prop | Type | Required | Default | Description |
|
|
54
|
+
|------|------|----------|---------|-------------|
|
|
55
|
+
| `cell` | `Cell<T, unknown>` | ✅ | | |
|
|
56
|
+
| `children` | `ReactNode` | | | |
|
|
57
|
+
| `ref` | `Ref<HTMLDivElement>` | | | A ref to apply to the root element. |
|
|
58
|
+
|
|
59
|
+
### DataGrid.BodyRow
|
|
60
|
+
|
|
61
|
+
| Prop | Type | Required | Default | Description |
|
|
62
|
+
|------|------|----------|---------|-------------|
|
|
63
|
+
| `children` | `ReactNode` | | | |
|
|
64
|
+
| `ref` | `Ref<HTMLDivElement>` | | | A ref to apply to the root element. |
|
|
65
|
+
| `row` | `Row<T>` | ✅ | | |
|
|
66
|
+
|
|
67
|
+
### DataGrid.ColumnControls
|
|
68
|
+
|
|
69
|
+
| Prop | Type | Required | Default | Description |
|
|
70
|
+
|------|------|----------|---------|-------------|
|
|
71
|
+
| `cell` | `Header<T, unknown>` | ✅ | | |
|
|
72
|
+
| `children` | `ReactNode` | | | |
|
|
73
|
+
| `ref` | `Ref<HTMLDivElement>` | | | A ref to apply to the root element. |
|
|
74
|
+
|
|
75
|
+
### DataGrid.DropDownCell
|
|
76
|
+
|
|
77
|
+
| Prop | Type | Required | Default | Description |
|
|
78
|
+
|------|------|----------|---------|-------------|
|
|
79
|
+
| `ariaLabel` | `string` | ✅ | | Aria label of the select component. Required for accessibility. |
|
|
80
|
+
| `cell` | `CellContext<T, unknown>` | ✅ | | |
|
|
81
|
+
| `components` | `Partial<SelectComponents<DropDownCellOption, boolean, GroupBase<DropDownCellOption>>>` | | | Override the underlying react-select components (e.g. `Option`, `SingleValue`, `MultiValue`). Merged over the Needle defaults, so only the components you pass are replaced. |
|
|
82
|
+
| `errorText` | `ReactNode` | | | Error message. If it exists the select will be in error state. |
|
|
83
|
+
| `helpText` | `ReactNode` | | | Help text of the select component. |
|
|
84
|
+
| `isDisabled` | `boolean` | | | Whether the select component is disabled |
|
|
85
|
+
| `options` | `{ value: string; label: string; }[]` | ✅ | | |
|
|
86
|
+
| `portalTarget` | `HTMLElement` | | `document.body` | The target element to portal the menu of the select component to, e.g. the modal root if the data grid is inside a modal. Defaults to the portalTarget prop of the DataGrid root component. |
|
|
87
|
+
| `ref` | `Ref<HTMLDivElement>` | | | A ref to apply to the root element. |
|
|
88
|
+
|
|
89
|
+
### DataGrid.Header
|
|
90
|
+
|
|
91
|
+
| Prop | Type | Required | Default | Description |
|
|
92
|
+
|------|------|----------|---------|-------------|
|
|
93
|
+
| `children` | `ReactNode` | | | |
|
|
94
|
+
| `ref` | `Ref<HTMLDivElement>` | | | A ref to apply to the root element. |
|
|
95
|
+
|
|
96
|
+
### DataGrid.HeaderCell
|
|
97
|
+
|
|
98
|
+
| Prop | Type | Required | Default | Description |
|
|
99
|
+
|------|------|----------|---------|-------------|
|
|
100
|
+
| `cell` | `Header<T, unknown>` | ✅ | | |
|
|
101
|
+
| `children` | `ReactNode` | | | |
|
|
102
|
+
| `ref` | `Ref<HTMLDivElement>` | | | A ref to apply to the root element. |
|
|
103
|
+
|
|
104
|
+
### DataGrid.HeaderTitle
|
|
105
|
+
|
|
106
|
+
| Prop | Type | Required | Default | Description |
|
|
107
|
+
|------|------|----------|---------|-------------|
|
|
108
|
+
| `cell` | `Header<T, unknown>` | ✅ | | |
|
|
109
|
+
| `children` | `ReactNode` | | | |
|
|
110
|
+
| `ref` | `Ref<HTMLDivElement>` | | | A ref to apply to the root element. |
|
|
111
|
+
|
|
112
|
+
### DataGrid.InlineEditCell
|
|
113
|
+
|
|
114
|
+
| Prop | Type | Required | Default | Description |
|
|
115
|
+
|------|------|----------|---------|-------------|
|
|
116
|
+
| `ariaLabel` | `string` | ✅ | | |
|
|
117
|
+
| `cell` | `CellContext<T, unknown>` | ✅ | | |
|
|
118
|
+
| `ref` | `Ref<HTMLInputElement>` | | | A ref to apply to the root element. |
|
|
119
|
+
| `value` | `string` | ✅ | | |
|
|
120
|
+
|
|
121
|
+
### DataGrid.LoadingPlaceholder
|
|
122
|
+
|
|
123
|
+
| Prop | Type | Required | Default | Description |
|
|
124
|
+
|------|------|----------|---------|-------------|
|
|
125
|
+
| `children` | `ReactNode` | | | |
|
|
126
|
+
| `ref` | `Ref<HTMLDivElement>` | | | A ref to apply to the root element. |
|
|
127
|
+
|
|
128
|
+
### DataGrid.Navigation
|
|
129
|
+
|
|
130
|
+
| Prop | Type | Required | Default | Description |
|
|
131
|
+
|------|------|----------|---------|-------------|
|
|
132
|
+
| `children` | `ReactNode` | | | |
|
|
133
|
+
| `ref` | `Ref<HTMLDivElement>` | | | A ref to apply to the root element. |
|
|
134
|
+
|
|
135
|
+
### DataGrid.NoDataIcon
|
|
136
|
+
|
|
137
|
+
_No props documented._
|
|
138
|
+
### DataGrid.NoDataPlaceholder
|
|
139
|
+
|
|
140
|
+
| Prop | Type | Required | Default | Description |
|
|
141
|
+
|------|------|----------|---------|-------------|
|
|
142
|
+
| `children` | `ReactNode` | | | |
|
|
143
|
+
| `ref` | `Ref<HTMLDivElement>` | | | A ref to apply to the root element. |
|
|
144
|
+
|
|
145
|
+
### DataGrid.NoDataPlaceholderContentWrapper
|
|
146
|
+
|
|
147
|
+
| Prop | Type | Required | Default | Description |
|
|
148
|
+
|------|------|----------|---------|-------------|
|
|
149
|
+
| `children` | `ReactNode` | | | |
|
|
150
|
+
| `ref` | `Ref<HTMLDivElement>` | | | A ref to apply to the root element. |
|
|
151
|
+
|
|
152
|
+
### DataGrid.Pagination
|
|
153
|
+
|
|
154
|
+
| Prop | Type | Required | Default | Description |
|
|
155
|
+
|------|------|----------|---------|-------------|
|
|
156
|
+
| `children` | `ReactNode` | | | |
|
|
157
|
+
| `ref` | `Ref<HTMLElement>` | | | A ref to apply to the root element. |
|
|
158
|
+
|
|
159
|
+
### DataGrid.PaginationArrowButton
|
|
160
|
+
|
|
161
|
+
| Prop | Type | Required | Default | Description |
|
|
162
|
+
|------|------|----------|---------|-------------|
|
|
163
|
+
| `action` | `'next' \| 'previous'` | ✅ | | |
|
|
164
|
+
| `children` | `ReactNode` | | | |
|
|
165
|
+
| `onClick` | `(e: MouseEvent<HTMLButtonElement, MouseEvent>) => void` | | | |
|
|
166
|
+
| `ref` | `Ref<HTMLButtonElement>` | | | A ref to apply to the root element. |
|
|
167
|
+
|
|
168
|
+
### DataGrid.PaginationNumericButton
|
|
169
|
+
|
|
170
|
+
| Prop | Type | Required | Default | Description |
|
|
171
|
+
|------|------|----------|---------|-------------|
|
|
172
|
+
| `children` | `ReactNode` | | | |
|
|
173
|
+
| `currentIndex` | `string \| number` | ✅ | | |
|
|
174
|
+
| `isSelected` | `boolean` | ✅ | | |
|
|
175
|
+
| `onClick` | `(e: MouseEvent<HTMLButtonElement, MouseEvent>) => void` | | | |
|
|
176
|
+
| `ref` | `Ref<HTMLButtonElement>` | | | A ref to apply to the root element. |
|
|
177
|
+
|
|
178
|
+
### DataGrid.ResizingBar
|
|
179
|
+
|
|
180
|
+
| Prop | Type | Required | Default | Description |
|
|
181
|
+
|------|------|----------|---------|-------------|
|
|
182
|
+
| `header` | `Header<T, unknown>` | ✅ | | |
|
|
183
|
+
| `ref` | `Ref<HTMLButtonElement>` | | | A ref to apply to the root element. |
|
|
184
|
+
|
|
185
|
+
### DataGrid.RowsPerPage
|
|
186
|
+
|
|
187
|
+
| Prop | Type | Required | Default | Description |
|
|
188
|
+
|------|------|----------|---------|-------------|
|
|
189
|
+
| `children` | `ReactNode` | | | |
|
|
190
|
+
| `portalTarget` | `HTMLElement` | | `document.body` | The target element to portal the menu of the select component to. Should be document.body normally, or the modal root if the select is inside a modal. Defaults to the portalTarget prop of the DataGrid root component. |
|
|
191
|
+
| `ref` | `Ref<HTMLDivElement>` | | | A ref to apply to the root element. |
|
|
192
|
+
|
|
193
|
+
### DataGrid.Scrollable
|
|
194
|
+
|
|
195
|
+
| Prop | Type | Required | Default | Description |
|
|
196
|
+
|------|------|----------|---------|-------------|
|
|
197
|
+
| `children` | `ReactNode` | | | |
|
|
198
|
+
| `ref` | `Ref<HTMLDivElement> & RefObject<HTMLDivElement>` | | | A ref to apply to the root element. |
|
|
199
|
+
|
|
200
|
+
### DataGrid.TableResults
|
|
201
|
+
|
|
202
|
+
| Prop | Type | Required | Default | Description |
|
|
203
|
+
|------|------|----------|---------|-------------|
|
|
204
|
+
| `children` | `ReactNode` | | | |
|
|
205
|
+
| `manualPagination` | `{ from: number; to: number; totalRows: number; }` | | | |
|
|
206
|
+
| `ref` | `Ref<HTMLSpanElement>` | | | A ref to apply to the root element. |
|
|
207
|
+
| `resultsLabel` | `string` | | | The label to use for the results, meaning the word "results" in the sentence "Showing X-Y of Z results". @defaultValue 'results' |
|
|
208
|
+
|
|
209
|
+
## Examples
|
|
210
|
+
|
|
211
|
+
### Datagrid Column Filtering
|
|
212
|
+
|
|
213
|
+
```tsx
|
|
214
|
+
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';
|
|
215
|
+
|
|
216
|
+
import { DataGrid } from '@neo4j-ndl/react';
|
|
217
|
+
import {
|
|
218
|
+
createColumnHelper,
|
|
219
|
+
getCoreRowModel,
|
|
220
|
+
getFilteredRowModel,
|
|
221
|
+
getPaginationRowModel,
|
|
222
|
+
useReactTable,
|
|
223
|
+
} from '@tanstack/react-table';
|
|
224
|
+
|
|
225
|
+
import { TABLE_DEMO_DATA } from './staticData';
|
|
226
|
+
|
|
227
|
+
type TestDataFormat = {
|
|
228
|
+
name: string;
|
|
229
|
+
age: number;
|
|
230
|
+
cypherCommand: string;
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const Component = () => {
|
|
234
|
+
const columnHelper = createColumnHelper<TestDataFormat>();
|
|
235
|
+
|
|
236
|
+
const columns = [
|
|
237
|
+
columnHelper.accessor('name', {
|
|
238
|
+
cell: (info) => info.getValue(),
|
|
239
|
+
footer: (info) => info.column.id,
|
|
240
|
+
header: () => 'Name',
|
|
241
|
+
maxSize: 300,
|
|
242
|
+
minSize: 180,
|
|
243
|
+
size: 220,
|
|
244
|
+
}),
|
|
245
|
+
columnHelper.accessor('age', {
|
|
246
|
+
cell: (info) => info.renderValue(),
|
|
247
|
+
footer: (info) => info.column.id,
|
|
248
|
+
header: () => 'Age',
|
|
249
|
+
minSize: 80,
|
|
250
|
+
}),
|
|
251
|
+
columnHelper.accessor('cypherCommand', {
|
|
252
|
+
cell: (info) => info.getValue(),
|
|
253
|
+
footer: (info) => info.column.id,
|
|
254
|
+
header: () => 'Cypher Command',
|
|
255
|
+
minSize: 400,
|
|
256
|
+
size: 400,
|
|
257
|
+
}),
|
|
258
|
+
];
|
|
259
|
+
|
|
260
|
+
const table = useReactTable({
|
|
261
|
+
columnResizeMode: 'onChange',
|
|
262
|
+
columns,
|
|
263
|
+
data: TABLE_DEMO_DATA,
|
|
264
|
+
debugColumns: true,
|
|
265
|
+
debugHeaders: true,
|
|
266
|
+
debugTable: true,
|
|
267
|
+
defaultColumn: {
|
|
268
|
+
maxSize: 800,
|
|
269
|
+
minSize: 80,
|
|
270
|
+
},
|
|
271
|
+
enableSorting: false,
|
|
272
|
+
getCoreRowModel: getCoreRowModel(),
|
|
273
|
+
getFilteredRowModel: getFilteredRowModel(),
|
|
274
|
+
getPaginationRowModel: getPaginationRowModel(),
|
|
275
|
+
initialState: {
|
|
276
|
+
pagination: {
|
|
277
|
+
pageSize: 5,
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
return (
|
|
283
|
+
<div className="n-w-full n-bg-light-neutral-text-weakest">
|
|
284
|
+
<DataGrid
|
|
285
|
+
isResizable={true}
|
|
286
|
+
tableInstance={table}
|
|
287
|
+
isKeyboardNavigable={true}
|
|
288
|
+
/>
|
|
289
|
+
</div>
|
|
290
|
+
);
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
export default Component;
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Datagrid Compact Mode
|
|
297
|
+
|
|
298
|
+
```tsx
|
|
299
|
+
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';
|
|
300
|
+
|
|
301
|
+
import { DataGrid } from '@neo4j-ndl/react';
|
|
302
|
+
import {
|
|
303
|
+
createColumnHelper,
|
|
304
|
+
getCoreRowModel,
|
|
305
|
+
getPaginationRowModel,
|
|
306
|
+
getSortedRowModel,
|
|
307
|
+
useReactTable,
|
|
308
|
+
} from '@tanstack/react-table';
|
|
309
|
+
|
|
310
|
+
import { TABLE_DEMO_DATA } from './staticData';
|
|
311
|
+
|
|
312
|
+
type TestDataFormat = {
|
|
313
|
+
name: string;
|
|
314
|
+
age: number;
|
|
315
|
+
cypherCommand: string;
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
const Component = () => {
|
|
319
|
+
const columnHelper = createColumnHelper<TestDataFormat>();
|
|
320
|
+
|
|
321
|
+
const columns = [
|
|
322
|
+
columnHelper.accessor('name', {
|
|
323
|
+
cell: (info) => info.getValue(),
|
|
324
|
+
footer: (info) => info.column.id,
|
|
325
|
+
header: () => 'Name',
|
|
326
|
+
maxSize: 300,
|
|
327
|
+
minSize: 180,
|
|
328
|
+
size: 220,
|
|
329
|
+
}),
|
|
330
|
+
columnHelper.accessor('age', {
|
|
331
|
+
cell: (info) => info.renderValue(),
|
|
332
|
+
footer: (info) => info.column.id,
|
|
333
|
+
header: () => 'Age',
|
|
334
|
+
minSize: 80,
|
|
335
|
+
}),
|
|
336
|
+
columnHelper.accessor('cypherCommand', {
|
|
337
|
+
cell: (info) => info.getValue(),
|
|
338
|
+
footer: (info) => info.column.id,
|
|
339
|
+
header: () => 'Cypher Command',
|
|
340
|
+
minSize: 400,
|
|
341
|
+
size: 400,
|
|
342
|
+
}),
|
|
343
|
+
];
|
|
344
|
+
|
|
345
|
+
const table = useReactTable({
|
|
346
|
+
columnResizeMode: 'onChange',
|
|
347
|
+
columns,
|
|
348
|
+
data: TABLE_DEMO_DATA,
|
|
349
|
+
debugColumns: true,
|
|
350
|
+
debugHeaders: true,
|
|
351
|
+
debugTable: true,
|
|
352
|
+
defaultColumn: {
|
|
353
|
+
maxSize: 800,
|
|
354
|
+
minSize: 80,
|
|
355
|
+
},
|
|
356
|
+
enableSorting: true,
|
|
357
|
+
getCoreRowModel: getCoreRowModel(),
|
|
358
|
+
getPaginationRowModel: getPaginationRowModel(),
|
|
359
|
+
getSortedRowModel: getSortedRowModel(),
|
|
360
|
+
initialState: {
|
|
361
|
+
pagination: {
|
|
362
|
+
pageSize: 5,
|
|
363
|
+
},
|
|
364
|
+
},
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
return (
|
|
368
|
+
<div className="n-w-full n-bg-light-neutral-text-weakest">
|
|
369
|
+
<DataGrid
|
|
370
|
+
isResizable={false}
|
|
371
|
+
tableInstance={table}
|
|
372
|
+
isKeyboardNavigable={false}
|
|
373
|
+
styling={{
|
|
374
|
+
borderStyle: 'all-sides',
|
|
375
|
+
hasHoverEffects: true,
|
|
376
|
+
hasZebraStriping: false,
|
|
377
|
+
isCompact: true,
|
|
378
|
+
}}
|
|
379
|
+
/>
|
|
380
|
+
</div>
|
|
381
|
+
);
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
export default Component;
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### Datagrid Component Overriding
|
|
388
|
+
|
|
389
|
+
```tsx
|
|
390
|
+
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';
|
|
391
|
+
|
|
392
|
+
import { DataGrid, Tooltip, Typography } from '@neo4j-ndl/react';
|
|
393
|
+
import {
|
|
394
|
+
createColumnHelper,
|
|
395
|
+
getCoreRowModel,
|
|
396
|
+
getPaginationRowModel,
|
|
397
|
+
getSortedRowModel,
|
|
398
|
+
useReactTable,
|
|
399
|
+
} from '@tanstack/react-table';
|
|
400
|
+
|
|
401
|
+
import { TABLE_DEMO_DATA } from './staticData';
|
|
402
|
+
|
|
403
|
+
type TestDataFormat = {
|
|
404
|
+
name: string;
|
|
405
|
+
age: number;
|
|
406
|
+
cypherCommand: string;
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
const Component = () => {
|
|
410
|
+
const columnHelper = createColumnHelper<TestDataFormat>();
|
|
411
|
+
|
|
412
|
+
const columns = [
|
|
413
|
+
columnHelper.accessor('name', {
|
|
414
|
+
cell: (info) => info.getValue(),
|
|
415
|
+
footer: (info) => info.column.id,
|
|
416
|
+
header: () => 'Name',
|
|
417
|
+
maxSize: 300,
|
|
418
|
+
minSize: 180,
|
|
419
|
+
size: 220,
|
|
420
|
+
}),
|
|
421
|
+
columnHelper.accessor('age', {
|
|
422
|
+
cell: (info) => info.renderValue(),
|
|
423
|
+
footer: (info) => info.column.id,
|
|
424
|
+
header: () => 'Age',
|
|
425
|
+
minSize: 80,
|
|
426
|
+
}),
|
|
427
|
+
columnHelper.accessor('cypherCommand', {
|
|
428
|
+
cell: (info) => info.getValue(),
|
|
429
|
+
footer: (info) => info.column.id,
|
|
430
|
+
header: () => 'Cypher Command',
|
|
431
|
+
minSize: 400,
|
|
432
|
+
size: 400,
|
|
433
|
+
}),
|
|
434
|
+
];
|
|
435
|
+
|
|
436
|
+
const table = useReactTable({
|
|
437
|
+
columnResizeMode: 'onChange',
|
|
438
|
+
columns,
|
|
439
|
+
data: TABLE_DEMO_DATA,
|
|
440
|
+
debugColumns: true,
|
|
441
|
+
debugHeaders: true,
|
|
442
|
+
debugTable: true,
|
|
443
|
+
defaultColumn: {
|
|
444
|
+
maxSize: 800,
|
|
445
|
+
minSize: 80,
|
|
446
|
+
},
|
|
447
|
+
enableSorting: true,
|
|
448
|
+
getCoreRowModel: getCoreRowModel(),
|
|
449
|
+
getPaginationRowModel: getPaginationRowModel(),
|
|
450
|
+
getSortedRowModel: getSortedRowModel(),
|
|
451
|
+
initialState: {
|
|
452
|
+
pagination: {
|
|
453
|
+
pageSize: 5,
|
|
454
|
+
},
|
|
455
|
+
},
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
return (
|
|
459
|
+
<div className="n-flex n-flex-col n-gap-token-8">
|
|
460
|
+
<Typography
|
|
461
|
+
variant="body-medium"
|
|
462
|
+
as="div"
|
|
463
|
+
className="n-flex n-flex-col n-gap-token-8 n-p-token-24 n-pb-token-16"
|
|
464
|
+
>
|
|
465
|
+
<div>Current container height: 400px</div>
|
|
466
|
+
<div>Try updating height with Storybook' s story controls👇</div>
|
|
467
|
+
</Typography>
|
|
468
|
+
|
|
469
|
+
<div
|
|
470
|
+
style={{
|
|
471
|
+
height: '400px',
|
|
472
|
+
}}
|
|
473
|
+
>
|
|
474
|
+
<DataGrid
|
|
475
|
+
className="n-h-full"
|
|
476
|
+
isResizable={true}
|
|
477
|
+
tableInstance={table}
|
|
478
|
+
components={{
|
|
479
|
+
Body: (props) => <DataGrid.Body {...props} />,
|
|
480
|
+
HeaderTitle: ({ cell, children }) => {
|
|
481
|
+
const { column, id } = cell;
|
|
482
|
+
const title =
|
|
483
|
+
typeof column.columnDef.header === 'string'
|
|
484
|
+
? column.columnDef.header
|
|
485
|
+
: id;
|
|
486
|
+
return (
|
|
487
|
+
<Tooltip type="simple">
|
|
488
|
+
<Tooltip.Trigger hasButtonWrapper>
|
|
489
|
+
<span>{children}</span>
|
|
490
|
+
</Tooltip.Trigger>
|
|
491
|
+
<Tooltip.Content>{title}</Tooltip.Content>
|
|
492
|
+
</Tooltip>
|
|
493
|
+
);
|
|
494
|
+
},
|
|
495
|
+
PaginationNumericButton: ({ isSelected, style, ...restProps }) => {
|
|
496
|
+
return (
|
|
497
|
+
<DataGrid.PaginationNumericButton
|
|
498
|
+
{...restProps}
|
|
499
|
+
isSelected={isSelected}
|
|
500
|
+
style={{
|
|
501
|
+
...style,
|
|
502
|
+
...(isSelected && {
|
|
503
|
+
backgroundImage:
|
|
504
|
+
'linear-gradient(90deg, #00C0FF 0%, #FFCF00 20%, #FC4F4F 40%, #00C0FF 100%)',
|
|
505
|
+
backgroundSize: '200% auto',
|
|
506
|
+
borderRadius: '10px',
|
|
507
|
+
boxShadow: '0 0 20px #eee',
|
|
508
|
+
}),
|
|
509
|
+
}}
|
|
510
|
+
/>
|
|
511
|
+
);
|
|
512
|
+
},
|
|
513
|
+
TableResults: () => (
|
|
514
|
+
<DataGrid.TableResults>
|
|
515
|
+
A table with a fixed height of 100px, custom result section and
|
|
516
|
+
custom pagination button
|
|
517
|
+
</DataGrid.TableResults>
|
|
518
|
+
),
|
|
519
|
+
}}
|
|
520
|
+
/>
|
|
521
|
+
</div>
|
|
522
|
+
</div>
|
|
523
|
+
);
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
export default Component;
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
### Datagrid Custom Cell With Style Rules
|
|
530
|
+
|
|
531
|
+
```tsx
|
|
532
|
+
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';
|
|
533
|
+
|
|
534
|
+
import {
|
|
535
|
+
DataGrid,
|
|
536
|
+
type DataGridStyleRule,
|
|
537
|
+
useDataGridCellStyle,
|
|
538
|
+
} from '@neo4j-ndl/react';
|
|
539
|
+
import {
|
|
540
|
+
type Cell,
|
|
541
|
+
createColumnHelper,
|
|
542
|
+
flexRender,
|
|
543
|
+
getCoreRowModel,
|
|
544
|
+
getPaginationRowModel,
|
|
545
|
+
getSortedRowModel,
|
|
546
|
+
type RowData,
|
|
547
|
+
useReactTable,
|
|
548
|
+
} from '@tanstack/react-table';
|
|
549
|
+
|
|
550
|
+
import { TABLE_DEMO_DATA, type TestDataFormat } from './staticData';
|
|
551
|
+
|
|
552
|
+
const styleRules: DataGridStyleRule[] = [
|
|
553
|
+
// Row coloring: all cells styled when name contains "Alicia"
|
|
554
|
+
{
|
|
555
|
+
apply: {
|
|
556
|
+
color: { value: '#000000' },
|
|
557
|
+
textColor: { value: '#ffffff' },
|
|
558
|
+
},
|
|
559
|
+
column: null,
|
|
560
|
+
priority: 3,
|
|
561
|
+
where: { contains: [{ column: 'name' }, 'Alicia'] },
|
|
562
|
+
},
|
|
563
|
+
// Cell coloring: cells in the "age" column with value 51 get green
|
|
564
|
+
{
|
|
565
|
+
apply: { color: { value: '#00bb00' } },
|
|
566
|
+
column: 'age',
|
|
567
|
+
priority: 3,
|
|
568
|
+
where: { equal: [{ column: 'age' }, 51] },
|
|
569
|
+
},
|
|
570
|
+
// Dual color ranges on the "age" column: independent gradients for background
|
|
571
|
+
// (driven by age, full 0-100 range) and text color (driven by age too, but
|
|
572
|
+
// with a narrower 20-80 band so the text contrast is calibrated separately).
|
|
573
|
+
{
|
|
574
|
+
apply: {
|
|
575
|
+
colorRange: {
|
|
576
|
+
maxColor: '#f00000',
|
|
577
|
+
maxValue: 100,
|
|
578
|
+
midColor: '#a00000',
|
|
579
|
+
midValue: 50,
|
|
580
|
+
minColor: '#800000',
|
|
581
|
+
minValue: 0,
|
|
582
|
+
onColumn: 'age',
|
|
583
|
+
},
|
|
584
|
+
textColorRange: {
|
|
585
|
+
maxColor: '#aaaaaa',
|
|
586
|
+
maxValue: 80,
|
|
587
|
+
minColor: '#ffffff',
|
|
588
|
+
minValue: 20,
|
|
589
|
+
onColumn: 'age',
|
|
590
|
+
},
|
|
591
|
+
},
|
|
592
|
+
column: 'age',
|
|
593
|
+
priority: 2, // Lower priority so explicit green rule overrides
|
|
594
|
+
},
|
|
595
|
+
// Multi-column rule: all cells styled when age > 50 AND name contains "o"
|
|
596
|
+
{
|
|
597
|
+
apply: {
|
|
598
|
+
color: { value: '#fef3c7' },
|
|
599
|
+
textColor: { value: '#000000' },
|
|
600
|
+
},
|
|
601
|
+
column: null,
|
|
602
|
+
priority: 0, // Lower priority so other rules take precedence
|
|
603
|
+
where: {
|
|
604
|
+
and: [
|
|
605
|
+
{ greaterThan: [{ column: 'age' }, 50] },
|
|
606
|
+
{ contains: [{ column: 'name' }, 'o'] },
|
|
607
|
+
],
|
|
608
|
+
},
|
|
609
|
+
},
|
|
610
|
+
];
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* A custom BodyCell that uses the `useDataGridCellStyle` hook to manually apply style rules to the cell.
|
|
614
|
+
*/
|
|
615
|
+
const CustomBodyCell = <T extends RowData>({
|
|
616
|
+
cell,
|
|
617
|
+
}: {
|
|
618
|
+
cell: Cell<T, unknown>;
|
|
619
|
+
}) => {
|
|
620
|
+
const ruleStyle = useDataGridCellStyle(cell);
|
|
621
|
+
|
|
622
|
+
return (
|
|
623
|
+
<div
|
|
624
|
+
className="ndl-data-grid-td"
|
|
625
|
+
role="cell"
|
|
626
|
+
style={{
|
|
627
|
+
...ruleStyle,
|
|
628
|
+
width: cell.column.getSize(),
|
|
629
|
+
}}
|
|
630
|
+
>
|
|
631
|
+
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
632
|
+
</div>
|
|
633
|
+
);
|
|
634
|
+
};
|
|
635
|
+
|
|
636
|
+
const Component = () => {
|
|
637
|
+
const columnHelper = createColumnHelper<TestDataFormat>();
|
|
638
|
+
|
|
639
|
+
const columns = [
|
|
640
|
+
columnHelper.accessor('name', {
|
|
641
|
+
cell: (info) => info.getValue(),
|
|
642
|
+
header: () => 'Name',
|
|
643
|
+
minSize: 180,
|
|
644
|
+
size: 220,
|
|
645
|
+
}),
|
|
646
|
+
columnHelper.accessor('age', {
|
|
647
|
+
cell: (info) => info.renderValue(),
|
|
648
|
+
header: () => 'Age',
|
|
649
|
+
minSize: 80,
|
|
650
|
+
}),
|
|
651
|
+
columnHelper.accessor('cypherCommand', {
|
|
652
|
+
cell: (info) => info.getValue(),
|
|
653
|
+
header: () => 'Cypher Command',
|
|
654
|
+
minSize: 400,
|
|
655
|
+
size: 400,
|
|
656
|
+
}),
|
|
657
|
+
];
|
|
658
|
+
|
|
659
|
+
const table = useReactTable({
|
|
660
|
+
columnResizeMode: 'onChange',
|
|
661
|
+
columns,
|
|
662
|
+
data: TABLE_DEMO_DATA,
|
|
663
|
+
defaultColumn: {
|
|
664
|
+
maxSize: 800,
|
|
665
|
+
minSize: 80,
|
|
666
|
+
},
|
|
667
|
+
enableSorting: true,
|
|
668
|
+
getCoreRowModel: getCoreRowModel(),
|
|
669
|
+
getPaginationRowModel: getPaginationRowModel(),
|
|
670
|
+
getSortedRowModel: getSortedRowModel(),
|
|
671
|
+
initialState: {
|
|
672
|
+
pagination: {
|
|
673
|
+
pageSize: 10,
|
|
674
|
+
},
|
|
675
|
+
},
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
return (
|
|
679
|
+
<div className="n-w-full n-bg-light-neutral-text-weakest">
|
|
680
|
+
<DataGrid
|
|
681
|
+
isResizable={false}
|
|
682
|
+
tableInstance={table}
|
|
683
|
+
isKeyboardNavigable={false}
|
|
684
|
+
styleRules={styleRules}
|
|
685
|
+
components={{
|
|
686
|
+
BodyCell: CustomBodyCell,
|
|
687
|
+
}}
|
|
688
|
+
styling={{
|
|
689
|
+
borderStyle: 'all-sides',
|
|
690
|
+
hasHoverEffects: true,
|
|
691
|
+
hasZebraStriping: false,
|
|
692
|
+
}}
|
|
693
|
+
/>
|
|
694
|
+
</div>
|
|
695
|
+
);
|
|
696
|
+
};
|
|
697
|
+
|
|
698
|
+
export default Component;
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
### Datagrid In Dialog
|
|
702
|
+
|
|
703
|
+
```tsx
|
|
704
|
+
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';
|
|
705
|
+
|
|
706
|
+
import {
|
|
707
|
+
DataGrid,
|
|
708
|
+
type DataGridNav,
|
|
709
|
+
Dialog,
|
|
710
|
+
FilledButton,
|
|
711
|
+
Select,
|
|
712
|
+
useDataGridContext,
|
|
713
|
+
} from '@neo4j-ndl/react';
|
|
714
|
+
import {
|
|
715
|
+
type ColumnDef,
|
|
716
|
+
type ColumnMeta,
|
|
717
|
+
createColumnHelper,
|
|
718
|
+
getCoreRowModel,
|
|
719
|
+
getPaginationRowModel,
|
|
720
|
+
getSortedRowModel,
|
|
721
|
+
useReactTable,
|
|
722
|
+
} from '@tanstack/react-table';
|
|
723
|
+
import { useRef, useState } from 'react';
|
|
724
|
+
|
|
725
|
+
import { TABLE_DEMO_DATA } from './staticData';
|
|
726
|
+
|
|
727
|
+
type TestDataFormat = {
|
|
728
|
+
name: string;
|
|
729
|
+
age: number;
|
|
730
|
+
cypherCommand: string;
|
|
731
|
+
};
|
|
732
|
+
|
|
733
|
+
const columnHelper = createColumnHelper<TestDataFormat>();
|
|
734
|
+
|
|
735
|
+
const actionsColumn: (tableNav: DataGridNav) => ColumnDef<TestDataFormat> = (
|
|
736
|
+
tableNav,
|
|
737
|
+
) => ({
|
|
738
|
+
cell: ({ row }) => {
|
|
739
|
+
const options = ['Edit', 'Delete', 'View'];
|
|
740
|
+
const dropdownOptions = options.map((o) => ({ label: o, value: o }));
|
|
741
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
742
|
+
const [selected, setSelected] = useState(dropdownOptions[0]);
|
|
743
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
744
|
+
const { portalTarget } = useDataGridContext();
|
|
745
|
+
|
|
746
|
+
return (
|
|
747
|
+
<div
|
|
748
|
+
data-testid={`ndl-actions-cell-row-${row.index}`}
|
|
749
|
+
className="n-flex n-flex-row n-gap-token-8"
|
|
750
|
+
>
|
|
751
|
+
<Select
|
|
752
|
+
ariaLabel={`actions-select-row-${row.index}`}
|
|
753
|
+
type="select"
|
|
754
|
+
selectProps={{
|
|
755
|
+
menuPortalTarget: portalTarget,
|
|
756
|
+
menuPosition: 'fixed',
|
|
757
|
+
onChange: (e) => {
|
|
758
|
+
if (!e) {
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
setSelected(e);
|
|
762
|
+
},
|
|
763
|
+
onKeyDown: (e) => {
|
|
764
|
+
// Stop propagation of arrow down to the table, to avoid moving to the next item in the cell
|
|
765
|
+
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
|
|
766
|
+
e.stopPropagation();
|
|
767
|
+
}
|
|
768
|
+
},
|
|
769
|
+
onMenuClose: () => {
|
|
770
|
+
console.info(
|
|
771
|
+
'Start listening to keyboard events for table navigation',
|
|
772
|
+
);
|
|
773
|
+
tableNav.enable();
|
|
774
|
+
},
|
|
775
|
+
onMenuOpen: () => {
|
|
776
|
+
console.info(
|
|
777
|
+
'Stop listening to keyboard events for table navigation',
|
|
778
|
+
);
|
|
779
|
+
tableNav.disable();
|
|
780
|
+
},
|
|
781
|
+
options: dropdownOptions,
|
|
782
|
+
value: selected,
|
|
783
|
+
}}
|
|
784
|
+
/>
|
|
785
|
+
</div>
|
|
786
|
+
);
|
|
787
|
+
},
|
|
788
|
+
header: 'Custom Select',
|
|
789
|
+
id: 'custom-select',
|
|
790
|
+
minSize: 150,
|
|
791
|
+
});
|
|
792
|
+
|
|
793
|
+
const Component = () => {
|
|
794
|
+
const [data, setData] = useState(TABLE_DEMO_DATA);
|
|
795
|
+
const [isAgeFilterApplied, setIsAgeFilterApplied] = useState(false);
|
|
796
|
+
|
|
797
|
+
const ColumnActions: ColumnMeta<TestDataFormat, unknown> = {
|
|
798
|
+
columnActions: {
|
|
799
|
+
actions: [
|
|
800
|
+
{
|
|
801
|
+
onClick: () => {
|
|
802
|
+
if (isAgeFilterApplied) {
|
|
803
|
+
setData(TABLE_DEMO_DATA);
|
|
804
|
+
setIsAgeFilterApplied(false);
|
|
805
|
+
return;
|
|
806
|
+
} else {
|
|
807
|
+
const filteredData = data.filter((item) => item.age >= 18);
|
|
808
|
+
setData(filteredData);
|
|
809
|
+
setIsAgeFilterApplied(true);
|
|
810
|
+
}
|
|
811
|
+
},
|
|
812
|
+
title: 'Age >= 18',
|
|
813
|
+
},
|
|
814
|
+
],
|
|
815
|
+
},
|
|
816
|
+
};
|
|
817
|
+
|
|
818
|
+
const regularColumns = [
|
|
819
|
+
columnHelper.accessor('name', {
|
|
820
|
+
cell: (cell) => (
|
|
821
|
+
<DataGrid.InlineEditCell
|
|
822
|
+
cell={cell}
|
|
823
|
+
value={cell.getValue()}
|
|
824
|
+
ariaLabel="edit name"
|
|
825
|
+
/>
|
|
826
|
+
),
|
|
827
|
+
maxSize: 300,
|
|
828
|
+
meta: {
|
|
829
|
+
isInlineEditCell: {
|
|
830
|
+
onEditChange: (newValue, cell) => {
|
|
831
|
+
setData((oldData) => {
|
|
832
|
+
return oldData.map((row) => {
|
|
833
|
+
if (row.name === cell.row.original.name) {
|
|
834
|
+
return {
|
|
835
|
+
...row,
|
|
836
|
+
name: newValue,
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
return row;
|
|
840
|
+
});
|
|
841
|
+
});
|
|
842
|
+
},
|
|
843
|
+
},
|
|
844
|
+
},
|
|
845
|
+
minSize: 180,
|
|
846
|
+
size: 220,
|
|
847
|
+
}),
|
|
848
|
+
columnHelper.accessor('age', {
|
|
849
|
+
cell: (info) => {
|
|
850
|
+
return (
|
|
851
|
+
<DataGrid.DropDownCell
|
|
852
|
+
cell={info}
|
|
853
|
+
options={[
|
|
854
|
+
{
|
|
855
|
+
label: '12',
|
|
856
|
+
value: '12',
|
|
857
|
+
},
|
|
858
|
+
{
|
|
859
|
+
label: '32',
|
|
860
|
+
value: '32',
|
|
861
|
+
},
|
|
862
|
+
{
|
|
863
|
+
label: '37',
|
|
864
|
+
value: '37',
|
|
865
|
+
},
|
|
866
|
+
{
|
|
867
|
+
label: '39',
|
|
868
|
+
value: '39',
|
|
869
|
+
},
|
|
870
|
+
{
|
|
871
|
+
label: '51',
|
|
872
|
+
value: '51',
|
|
873
|
+
},
|
|
874
|
+
]}
|
|
875
|
+
ariaLabel="select age"
|
|
876
|
+
portalTarget={document.getElementById('default-menu')}
|
|
877
|
+
/>
|
|
878
|
+
);
|
|
879
|
+
},
|
|
880
|
+
meta: {
|
|
881
|
+
isDropDownCell: {
|
|
882
|
+
onChange: (newValue, cell) => {
|
|
883
|
+
if (newValue === undefined) {
|
|
884
|
+
// No change
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
const parsedValue = parseInt(newValue, 10);
|
|
889
|
+
|
|
890
|
+
if (isNaN(parsedValue)) {
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
setData((oldData) => {
|
|
895
|
+
return oldData.map((row) => {
|
|
896
|
+
if (row.name === cell.row.original.name) {
|
|
897
|
+
return {
|
|
898
|
+
...row,
|
|
899
|
+
age: parsedValue,
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
return row;
|
|
903
|
+
});
|
|
904
|
+
});
|
|
905
|
+
},
|
|
906
|
+
},
|
|
907
|
+
...ColumnActions,
|
|
908
|
+
},
|
|
909
|
+
minSize: 120,
|
|
910
|
+
}),
|
|
911
|
+
columnHelper.accessor('cypherCommand', {
|
|
912
|
+
cell: (info) => info.getValue(),
|
|
913
|
+
minSize: 400,
|
|
914
|
+
size: 400,
|
|
915
|
+
}),
|
|
916
|
+
];
|
|
917
|
+
|
|
918
|
+
const allColumns = (tableNav: DataGridNav) => [
|
|
919
|
+
...regularColumns,
|
|
920
|
+
actionsColumn(tableNav),
|
|
921
|
+
];
|
|
922
|
+
|
|
923
|
+
const tableNavRef = useRef<DataGridNav>(null!);
|
|
924
|
+
const table = useReactTable({
|
|
925
|
+
autoResetPageIndex: false,
|
|
926
|
+
columnResizeMode: 'onChange',
|
|
927
|
+
columns: allColumns(tableNavRef.current),
|
|
928
|
+
data: data,
|
|
929
|
+
debugColumns: true,
|
|
930
|
+
debugHeaders: true,
|
|
931
|
+
debugTable: true,
|
|
932
|
+
defaultColumn: {
|
|
933
|
+
maxSize: 800,
|
|
934
|
+
minSize: 80,
|
|
935
|
+
},
|
|
936
|
+
enableSorting: true,
|
|
937
|
+
getCoreRowModel: getCoreRowModel(),
|
|
938
|
+
getPaginationRowModel: getPaginationRowModel(),
|
|
939
|
+
getSortedRowModel: getSortedRowModel(),
|
|
940
|
+
initialState: {
|
|
941
|
+
pagination: {
|
|
942
|
+
pageSize: 5,
|
|
943
|
+
},
|
|
944
|
+
},
|
|
945
|
+
});
|
|
946
|
+
|
|
947
|
+
// Modal code
|
|
948
|
+
const [isOpen, setIsOpen] = useState<boolean>(false);
|
|
949
|
+
const handleClick = () => setIsOpen((prev) => !prev);
|
|
950
|
+
const handleClose = () => setIsOpen(false);
|
|
951
|
+
|
|
952
|
+
// DataGrid code
|
|
953
|
+
return (
|
|
954
|
+
<div className="n-w-full">
|
|
955
|
+
<div className="n-flex n-justify-center">
|
|
956
|
+
<FilledButton onClick={handleClick}>Open Dialog</FilledButton>
|
|
957
|
+
</div>
|
|
958
|
+
<Dialog
|
|
959
|
+
className="n-p-token-8 n-bg-neutral-10 n-max-w-max n-rounded-lg"
|
|
960
|
+
htmlAttributes={{
|
|
961
|
+
id: 'default-menu',
|
|
962
|
+
}}
|
|
963
|
+
onClose={handleClose}
|
|
964
|
+
isOpen={isOpen}
|
|
965
|
+
hasDisabledCloseButton={true}
|
|
966
|
+
>
|
|
967
|
+
<Dialog.Content>
|
|
968
|
+
<DataGrid
|
|
969
|
+
portalTarget={document.getElementById('default-menu')}
|
|
970
|
+
isResizable={true}
|
|
971
|
+
tableInstance={table}
|
|
972
|
+
tableNavRef={tableNavRef}
|
|
973
|
+
isKeyboardNavigable={true}
|
|
974
|
+
styling={{
|
|
975
|
+
borderStyle: 'horizontal',
|
|
976
|
+
hasZebraStriping: false,
|
|
977
|
+
}}
|
|
978
|
+
/>
|
|
979
|
+
</Dialog.Content>
|
|
980
|
+
</Dialog>
|
|
981
|
+
</div>
|
|
982
|
+
);
|
|
983
|
+
};
|
|
984
|
+
|
|
985
|
+
export default Component;
|
|
986
|
+
```
|
|
987
|
+
|
|
988
|
+
### Datagrid Keyboard Navigation
|
|
989
|
+
|
|
990
|
+
```tsx
|
|
991
|
+
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';
|
|
992
|
+
|
|
993
|
+
import {
|
|
994
|
+
Checkbox,
|
|
995
|
+
DataGrid,
|
|
996
|
+
type DataGridNav,
|
|
997
|
+
IconButton,
|
|
998
|
+
Select,
|
|
999
|
+
useDataGridContext,
|
|
1000
|
+
} from '@neo4j-ndl/react';
|
|
1001
|
+
import { MegaphoneIconOutline } from '@neo4j-ndl/react/icons';
|
|
1002
|
+
import {
|
|
1003
|
+
type ColumnDef,
|
|
1004
|
+
createColumnHelper,
|
|
1005
|
+
getCoreRowModel,
|
|
1006
|
+
getPaginationRowModel,
|
|
1007
|
+
useReactTable,
|
|
1008
|
+
} from '@tanstack/react-table';
|
|
1009
|
+
import { useRef, useState } from 'react';
|
|
1010
|
+
|
|
1011
|
+
import { TABLE_DEMO_DATA } from './staticData';
|
|
1012
|
+
|
|
1013
|
+
type TestDataFormat = {
|
|
1014
|
+
name: string;
|
|
1015
|
+
age: number;
|
|
1016
|
+
cypherCommand: string;
|
|
1017
|
+
};
|
|
1018
|
+
|
|
1019
|
+
const selectableColumn: ColumnDef<TestDataFormat> = {
|
|
1020
|
+
cell: ({ row }) => (
|
|
1021
|
+
<Checkbox
|
|
1022
|
+
ariaLabel={`row-${row.index}`}
|
|
1023
|
+
isChecked={row.getIsSelected() || false}
|
|
1024
|
+
onChange={row.getToggleSelectedHandler()}
|
|
1025
|
+
/>
|
|
1026
|
+
),
|
|
1027
|
+
header: ({ table }) => (
|
|
1028
|
+
<Checkbox
|
|
1029
|
+
ariaLabel="Select all rows"
|
|
1030
|
+
isChecked={table.getIsAllRowsSelected() || false}
|
|
1031
|
+
onChange={table.getToggleAllRowsSelectedHandler()}
|
|
1032
|
+
isIndeterminate={table.getIsSomeRowsSelected()}
|
|
1033
|
+
/>
|
|
1034
|
+
),
|
|
1035
|
+
id: 'select',
|
|
1036
|
+
maxSize: 80,
|
|
1037
|
+
size: 80,
|
|
1038
|
+
};
|
|
1039
|
+
|
|
1040
|
+
const actionsColumn: (tableNav: DataGridNav) => ColumnDef<TestDataFormat> = (
|
|
1041
|
+
tableNav,
|
|
1042
|
+
) => ({
|
|
1043
|
+
cell: ({ row }) => {
|
|
1044
|
+
const options = ['Edit', 'Delete', 'View'];
|
|
1045
|
+
const dropdownOptions = options.map((o) => ({ label: o, value: o }));
|
|
1046
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
1047
|
+
const [selected, setSelected] = useState(dropdownOptions[0]);
|
|
1048
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
1049
|
+
const { portalTarget } = useDataGridContext();
|
|
1050
|
+
|
|
1051
|
+
return (
|
|
1052
|
+
<div
|
|
1053
|
+
data-testid={`ndl-actions-cell-row-${row.index}`}
|
|
1054
|
+
className="n-flex n-flex-row n-gap-token-8"
|
|
1055
|
+
>
|
|
1056
|
+
<IconButton
|
|
1057
|
+
description="Megaphone"
|
|
1058
|
+
onClick={() => {
|
|
1059
|
+
alert("Let's get loud 📣");
|
|
1060
|
+
}}
|
|
1061
|
+
htmlAttributes={{
|
|
1062
|
+
type: 'button',
|
|
1063
|
+
}}
|
|
1064
|
+
>
|
|
1065
|
+
<MegaphoneIconOutline />
|
|
1066
|
+
</IconButton>
|
|
1067
|
+
<Select
|
|
1068
|
+
ariaLabel={`actions-select-row-${row.index}`}
|
|
1069
|
+
type="select"
|
|
1070
|
+
selectProps={{
|
|
1071
|
+
menuPortalTarget: portalTarget,
|
|
1072
|
+
onChange: (e) => {
|
|
1073
|
+
if (!e) {
|
|
1074
|
+
return;
|
|
1075
|
+
}
|
|
1076
|
+
setSelected(e);
|
|
1077
|
+
},
|
|
1078
|
+
onKeyDown: (e) => {
|
|
1079
|
+
// Stop propagation of arrow down to the table, to avoid moving to the next item in the cell
|
|
1080
|
+
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
|
|
1081
|
+
e.stopPropagation();
|
|
1082
|
+
}
|
|
1083
|
+
},
|
|
1084
|
+
onMenuClose: () => {
|
|
1085
|
+
console.info(
|
|
1086
|
+
'Start listening to keyboard events for table navigation',
|
|
1087
|
+
);
|
|
1088
|
+
tableNav.enable();
|
|
1089
|
+
},
|
|
1090
|
+
onMenuOpen: () => {
|
|
1091
|
+
console.info(
|
|
1092
|
+
'Stop listening to keyboard events for table navigation',
|
|
1093
|
+
);
|
|
1094
|
+
tableNav.disable();
|
|
1095
|
+
},
|
|
1096
|
+
options: dropdownOptions,
|
|
1097
|
+
value: selected,
|
|
1098
|
+
}}
|
|
1099
|
+
/>
|
|
1100
|
+
</div>
|
|
1101
|
+
);
|
|
1102
|
+
},
|
|
1103
|
+
header: 'actions',
|
|
1104
|
+
id: 'actions',
|
|
1105
|
+
});
|
|
1106
|
+
|
|
1107
|
+
const allColumns = (tableNav: DataGridNav) => {
|
|
1108
|
+
const columnHelper = createColumnHelper<TestDataFormat>();
|
|
1109
|
+
|
|
1110
|
+
const commonColumns = [
|
|
1111
|
+
columnHelper.accessor('name', {
|
|
1112
|
+
cell: (info) => info.getValue(),
|
|
1113
|
+
footer: (info) => info.column.id,
|
|
1114
|
+
header: () => 'Name',
|
|
1115
|
+
maxSize: 300,
|
|
1116
|
+
minSize: 180,
|
|
1117
|
+
size: 220,
|
|
1118
|
+
}),
|
|
1119
|
+
columnHelper.accessor('age', {
|
|
1120
|
+
cell: (info) => info.renderValue(),
|
|
1121
|
+
footer: (info) => info.column.id,
|
|
1122
|
+
header: () => 'Age',
|
|
1123
|
+
minSize: 80,
|
|
1124
|
+
}),
|
|
1125
|
+
];
|
|
1126
|
+
|
|
1127
|
+
return [selectableColumn, ...commonColumns, actionsColumn(tableNav)];
|
|
1128
|
+
};
|
|
1129
|
+
|
|
1130
|
+
const Component = () => {
|
|
1131
|
+
const [rowSelection, setRowSelection] = useState({});
|
|
1132
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
1133
|
+
const tableNavRef = useRef<DataGridNav>(null!);
|
|
1134
|
+
|
|
1135
|
+
const table = useReactTable({
|
|
1136
|
+
columnResizeMode: 'onChange',
|
|
1137
|
+
columns: allColumns(tableNavRef.current),
|
|
1138
|
+
data: TABLE_DEMO_DATA,
|
|
1139
|
+
enableSorting: false,
|
|
1140
|
+
getCoreRowModel: getCoreRowModel(),
|
|
1141
|
+
getPaginationRowModel: getPaginationRowModel(),
|
|
1142
|
+
initialState: {
|
|
1143
|
+
pagination: {
|
|
1144
|
+
pageSize: 5,
|
|
1145
|
+
},
|
|
1146
|
+
},
|
|
1147
|
+
onRowSelectionChange: setRowSelection,
|
|
1148
|
+
state: {
|
|
1149
|
+
rowSelection,
|
|
1150
|
+
},
|
|
1151
|
+
});
|
|
1152
|
+
|
|
1153
|
+
return (
|
|
1154
|
+
<>
|
|
1155
|
+
<DataGrid
|
|
1156
|
+
isResizable={false}
|
|
1157
|
+
isKeyboardNavigable={true}
|
|
1158
|
+
styling={{
|
|
1159
|
+
borderStyle: 'all-sides',
|
|
1160
|
+
}}
|
|
1161
|
+
tableNavRef={tableNavRef}
|
|
1162
|
+
tableInstance={table}
|
|
1163
|
+
ref={ref}
|
|
1164
|
+
portalTarget={document.body}
|
|
1165
|
+
/>
|
|
1166
|
+
</>
|
|
1167
|
+
);
|
|
1168
|
+
};
|
|
1169
|
+
|
|
1170
|
+
export default Component;
|
|
1171
|
+
```
|
|
1172
|
+
|
|
1173
|
+
### Datagrid Kitchen Sink
|
|
1174
|
+
|
|
1175
|
+
```tsx
|
|
1176
|
+
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';
|
|
1177
|
+
|
|
1178
|
+
import { DataGrid, Menu } from '@neo4j-ndl/react';
|
|
1179
|
+
import { ChevronDownIconSolid } from '@neo4j-ndl/react/icons';
|
|
1180
|
+
import {
|
|
1181
|
+
type ColumnDef,
|
|
1182
|
+
type ColumnMeta,
|
|
1183
|
+
createColumnHelper,
|
|
1184
|
+
getCoreRowModel,
|
|
1185
|
+
getPaginationRowModel,
|
|
1186
|
+
getSortedRowModel,
|
|
1187
|
+
type TableOptions,
|
|
1188
|
+
useReactTable,
|
|
1189
|
+
} from '@tanstack/react-table';
|
|
1190
|
+
import { Leva, useControls } from 'leva';
|
|
1191
|
+
|
|
1192
|
+
import { type BorderStyle, type HeaderStyle } from '../DataGrid';
|
|
1193
|
+
import { TABLE_DEMO_DATA } from './staticData';
|
|
1194
|
+
|
|
1195
|
+
type TestDataFormat = {
|
|
1196
|
+
name: string;
|
|
1197
|
+
age: number;
|
|
1198
|
+
cypherCommand: string;
|
|
1199
|
+
};
|
|
1200
|
+
|
|
1201
|
+
const columnHelper = createColumnHelper<TestDataFormat>();
|
|
1202
|
+
|
|
1203
|
+
const ColumnActions: ColumnMeta<TestDataFormat, unknown> = {
|
|
1204
|
+
columnActions: {
|
|
1205
|
+
actions: [
|
|
1206
|
+
{
|
|
1207
|
+
onClick: () => {
|
|
1208
|
+
alert('Action triggered');
|
|
1209
|
+
},
|
|
1210
|
+
title: 'Example Column Action',
|
|
1211
|
+
},
|
|
1212
|
+
],
|
|
1213
|
+
},
|
|
1214
|
+
};
|
|
1215
|
+
|
|
1216
|
+
type ColumnGeneratorOptions = {
|
|
1217
|
+
shouldIncludeRowActionColumn: boolean;
|
|
1218
|
+
};
|
|
1219
|
+
const COMMON_COLUMNS = (options: ColumnGeneratorOptions) => {
|
|
1220
|
+
// oxlint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1221
|
+
const columns: ColumnDef<TestDataFormat, any>[] = [
|
|
1222
|
+
columnHelper.accessor('name', {
|
|
1223
|
+
cell: (info) => info.getValue(),
|
|
1224
|
+
maxSize: 300,
|
|
1225
|
+
minSize: 180,
|
|
1226
|
+
size: 220,
|
|
1227
|
+
}),
|
|
1228
|
+
columnHelper.accessor('age', {
|
|
1229
|
+
cell: (info) => info.getValue(),
|
|
1230
|
+
header: () => <div>Age</div>,
|
|
1231
|
+
minSize: 80,
|
|
1232
|
+
...(options.shouldIncludeRowActionColumn
|
|
1233
|
+
? {
|
|
1234
|
+
meta: {
|
|
1235
|
+
...ColumnActions,
|
|
1236
|
+
},
|
|
1237
|
+
}
|
|
1238
|
+
: {}),
|
|
1239
|
+
}),
|
|
1240
|
+
columnHelper.accessor('cypherCommand', {
|
|
1241
|
+
cell: (info) => info.getValue(),
|
|
1242
|
+
minSize: 400,
|
|
1243
|
+
size: 400,
|
|
1244
|
+
}),
|
|
1245
|
+
];
|
|
1246
|
+
|
|
1247
|
+
if (options.shouldIncludeRowActionColumn) {
|
|
1248
|
+
columns.push(
|
|
1249
|
+
columnHelper.display({
|
|
1250
|
+
cell: (cell) => {
|
|
1251
|
+
const isSpecialRow =
|
|
1252
|
+
cell.row.original.name === TABLE_DEMO_DATA[3].name || undefined;
|
|
1253
|
+
return (
|
|
1254
|
+
<DataGrid.ActionCell
|
|
1255
|
+
cell={cell}
|
|
1256
|
+
innerCleanIconButtonProps={{
|
|
1257
|
+
children: isSpecialRow && <ChevronDownIconSolid />,
|
|
1258
|
+
description: `Actions for ${cell.row.original.name}`,
|
|
1259
|
+
}}
|
|
1260
|
+
innerMenuProps={
|
|
1261
|
+
isSpecialRow && {
|
|
1262
|
+
children: <Menu.Item title="Custom icon and menu props" />,
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
/>
|
|
1266
|
+
);
|
|
1267
|
+
},
|
|
1268
|
+
id: 'actions',
|
|
1269
|
+
meta: {
|
|
1270
|
+
isActionCell: {
|
|
1271
|
+
actions: [
|
|
1272
|
+
{
|
|
1273
|
+
onClick: (_event, cell) => {
|
|
1274
|
+
alert(
|
|
1275
|
+
`Row Action triggered for row with index: ${
|
|
1276
|
+
cell.row.index
|
|
1277
|
+
}`,
|
|
1278
|
+
);
|
|
1279
|
+
},
|
|
1280
|
+
title: 'Example Row Action',
|
|
1281
|
+
},
|
|
1282
|
+
],
|
|
1283
|
+
onOpenChange: (open) => console.info(`onOpenChange: ${open}`),
|
|
1284
|
+
},
|
|
1285
|
+
},
|
|
1286
|
+
}),
|
|
1287
|
+
);
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
return columns;
|
|
1291
|
+
};
|
|
1292
|
+
|
|
1293
|
+
const COMMON_TABLE_PROPS = {
|
|
1294
|
+
columnResizeMode: 'onChange',
|
|
1295
|
+
data: TABLE_DEMO_DATA,
|
|
1296
|
+
// https://tanstack.com/table/v8/docs/guide/column-sizing#overview
|
|
1297
|
+
defaultColumn: {
|
|
1298
|
+
minSize: 80,
|
|
1299
|
+
},
|
|
1300
|
+
enableSorting: false,
|
|
1301
|
+
getCoreRowModel: getCoreRowModel(),
|
|
1302
|
+
getPaginationRowModel: getPaginationRowModel(),
|
|
1303
|
+
initialState: {
|
|
1304
|
+
pagination: {
|
|
1305
|
+
pageSize: 5,
|
|
1306
|
+
},
|
|
1307
|
+
},
|
|
1308
|
+
meta: {
|
|
1309
|
+
actions: [
|
|
1310
|
+
{
|
|
1311
|
+
onClick: () => {
|
|
1312
|
+
alert('wowowow');
|
|
1313
|
+
},
|
|
1314
|
+
title: "Let's party",
|
|
1315
|
+
},
|
|
1316
|
+
],
|
|
1317
|
+
},
|
|
1318
|
+
} as Omit<TableOptions<TestDataFormat>, 'columns'>;
|
|
1319
|
+
|
|
1320
|
+
const Component = () => {
|
|
1321
|
+
const {
|
|
1322
|
+
keyboard: isKeyboardNavigable,
|
|
1323
|
+
zebraStriping: hasZebraStriping,
|
|
1324
|
+
borderStyle,
|
|
1325
|
+
headerStyle,
|
|
1326
|
+
includeRowActionColumn: shouldIncludeRowActionColumn,
|
|
1327
|
+
} = useControls({
|
|
1328
|
+
borderStyle: {
|
|
1329
|
+
options: ['all-sides', 'horizontal', 'vertical', 'none'],
|
|
1330
|
+
value: 'all-sides',
|
|
1331
|
+
},
|
|
1332
|
+
headerStyle: {
|
|
1333
|
+
options: ['filled', 'clean'],
|
|
1334
|
+
value: 'filled',
|
|
1335
|
+
},
|
|
1336
|
+
includeRowActionColumn: false,
|
|
1337
|
+
keyboard: false,
|
|
1338
|
+
zebraStriping: false,
|
|
1339
|
+
});
|
|
1340
|
+
|
|
1341
|
+
const table = useReactTable({
|
|
1342
|
+
...COMMON_TABLE_PROPS,
|
|
1343
|
+
columns: COMMON_COLUMNS({
|
|
1344
|
+
shouldIncludeRowActionColumn,
|
|
1345
|
+
}),
|
|
1346
|
+
enableSorting: true,
|
|
1347
|
+
getSortedRowModel: getSortedRowModel(),
|
|
1348
|
+
});
|
|
1349
|
+
|
|
1350
|
+
return (
|
|
1351
|
+
<div className="n-h-[1000px] n-w-full n-bg-primary-bg-weak n-p-token-32">
|
|
1352
|
+
<Leva isRoot />
|
|
1353
|
+
<DataGrid
|
|
1354
|
+
isResizable={true}
|
|
1355
|
+
tableInstance={table}
|
|
1356
|
+
isKeyboardNavigable={isKeyboardNavigable}
|
|
1357
|
+
styling={{
|
|
1358
|
+
borderStyle: borderStyle as BorderStyle,
|
|
1359
|
+
hasZebraStriping,
|
|
1360
|
+
headerStyle: headerStyle as HeaderStyle,
|
|
1361
|
+
}}
|
|
1362
|
+
/>
|
|
1363
|
+
</div>
|
|
1364
|
+
);
|
|
1365
|
+
};
|
|
1366
|
+
|
|
1367
|
+
export default Component;
|
|
1368
|
+
```
|
|
1369
|
+
|
|
1370
|
+
### Datagrid No Pagination Table
|
|
1371
|
+
|
|
1372
|
+
```tsx
|
|
1373
|
+
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';
|
|
1374
|
+
|
|
1375
|
+
import { DataGrid } from '@neo4j-ndl/react';
|
|
1376
|
+
import {
|
|
1377
|
+
createColumnHelper,
|
|
1378
|
+
getCoreRowModel,
|
|
1379
|
+
useReactTable,
|
|
1380
|
+
} from '@tanstack/react-table';
|
|
1381
|
+
|
|
1382
|
+
import { PRUNED_TABLE_DEMO_DATA } from './staticData';
|
|
1383
|
+
|
|
1384
|
+
type TestDataFormat = {
|
|
1385
|
+
name: string;
|
|
1386
|
+
age: number;
|
|
1387
|
+
cypherCommand: string;
|
|
1388
|
+
};
|
|
1389
|
+
|
|
1390
|
+
const Component = () => {
|
|
1391
|
+
const columnHelper = createColumnHelper<TestDataFormat>();
|
|
1392
|
+
|
|
1393
|
+
const columns = [
|
|
1394
|
+
columnHelper.accessor('name', {
|
|
1395
|
+
cell: (info) => info.getValue(),
|
|
1396
|
+
footer: (info) => info.column.id,
|
|
1397
|
+
header: () => 'Name',
|
|
1398
|
+
maxSize: 300,
|
|
1399
|
+
minSize: 180,
|
|
1400
|
+
size: 220,
|
|
1401
|
+
}),
|
|
1402
|
+
columnHelper.accessor('age', {
|
|
1403
|
+
cell: (info) => info.renderValue(),
|
|
1404
|
+
footer: (info) => info.column.id,
|
|
1405
|
+
header: () => 'Age',
|
|
1406
|
+
minSize: 80,
|
|
1407
|
+
}),
|
|
1408
|
+
columnHelper.accessor('cypherCommand', {
|
|
1409
|
+
cell: (info) => info.getValue(),
|
|
1410
|
+
footer: (info) => info.column.id,
|
|
1411
|
+
header: () => 'Cypher Command',
|
|
1412
|
+
minSize: 400,
|
|
1413
|
+
size: 400,
|
|
1414
|
+
}),
|
|
1415
|
+
];
|
|
1416
|
+
|
|
1417
|
+
const table = useReactTable({
|
|
1418
|
+
columns,
|
|
1419
|
+
data: PRUNED_TABLE_DEMO_DATA,
|
|
1420
|
+
enableSorting: false,
|
|
1421
|
+
getCoreRowModel: getCoreRowModel(),
|
|
1422
|
+
});
|
|
1423
|
+
|
|
1424
|
+
return (
|
|
1425
|
+
<DataGrid
|
|
1426
|
+
components={{ Navigation: null }}
|
|
1427
|
+
isResizable={false}
|
|
1428
|
+
tableInstance={table}
|
|
1429
|
+
/>
|
|
1430
|
+
);
|
|
1431
|
+
};
|
|
1432
|
+
|
|
1433
|
+
export default Component;
|
|
1434
|
+
```
|
|
1435
|
+
|
|
1436
|
+
### Datagrid Percentage Width Columns
|
|
1437
|
+
|
|
1438
|
+
```tsx
|
|
1439
|
+
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';
|
|
1440
|
+
|
|
1441
|
+
import { DataGrid } from '@neo4j-ndl/react';
|
|
1442
|
+
import {
|
|
1443
|
+
createColumnHelper,
|
|
1444
|
+
getCoreRowModel,
|
|
1445
|
+
getSortedRowModel,
|
|
1446
|
+
useReactTable,
|
|
1447
|
+
} from '@tanstack/react-table';
|
|
1448
|
+
|
|
1449
|
+
import { TABLE_DEMO_DATA } from './staticData';
|
|
1450
|
+
|
|
1451
|
+
type TestDataFormat = {
|
|
1452
|
+
name: string;
|
|
1453
|
+
age: number;
|
|
1454
|
+
cypherCommand: string;
|
|
1455
|
+
};
|
|
1456
|
+
|
|
1457
|
+
const Component = () => {
|
|
1458
|
+
const columnHelper = createColumnHelper<TestDataFormat>();
|
|
1459
|
+
|
|
1460
|
+
const columns = [
|
|
1461
|
+
columnHelper.accessor('name', {
|
|
1462
|
+
cell: (info) => info.getValue(),
|
|
1463
|
+
footer: (info) => info.column.id,
|
|
1464
|
+
header: () => 'Name',
|
|
1465
|
+
size: 200, // 200 / 1000 = 20% of the table width
|
|
1466
|
+
}),
|
|
1467
|
+
columnHelper.accessor('age', {
|
|
1468
|
+
cell: (info) => info.renderValue(),
|
|
1469
|
+
footer: (info) => info.column.id,
|
|
1470
|
+
header: () => 'Age',
|
|
1471
|
+
minSize: 100, // 100 / 1000 = 10% of the table width
|
|
1472
|
+
}),
|
|
1473
|
+
columnHelper.accessor('cypherCommand', {
|
|
1474
|
+
cell: (info) => info.getValue(),
|
|
1475
|
+
footer: (info) => info.column.id,
|
|
1476
|
+
header: () => 'Cypher Command',
|
|
1477
|
+
size: 700, // 700 / 1000 = 70% of the table width
|
|
1478
|
+
}),
|
|
1479
|
+
];
|
|
1480
|
+
|
|
1481
|
+
const table = useReactTable({
|
|
1482
|
+
columnResizeMode: 'onChange',
|
|
1483
|
+
columns: columns,
|
|
1484
|
+
data: TABLE_DEMO_DATA.slice(0, 5),
|
|
1485
|
+
debugColumns: true,
|
|
1486
|
+
debugHeaders: true,
|
|
1487
|
+
debugTable: true,
|
|
1488
|
+
enableSorting: false,
|
|
1489
|
+
getCoreRowModel: getCoreRowModel(),
|
|
1490
|
+
getSortedRowModel: getSortedRowModel(),
|
|
1491
|
+
});
|
|
1492
|
+
|
|
1493
|
+
// Disable resizing of columns and enable automatic resizing of columns, to allow for percentage width columns
|
|
1494
|
+
const isResizable = false;
|
|
1495
|
+
const isAutoResizingColumns = true;
|
|
1496
|
+
|
|
1497
|
+
return (
|
|
1498
|
+
<div className="n-w-full n-bg-light-neutral-text-weakest">
|
|
1499
|
+
<DataGrid
|
|
1500
|
+
components={{
|
|
1501
|
+
Navigation: null,
|
|
1502
|
+
}}
|
|
1503
|
+
isAutoResizingColumns={isAutoResizingColumns}
|
|
1504
|
+
isKeyboardNavigable={false}
|
|
1505
|
+
isResizable={isResizable}
|
|
1506
|
+
styling={{
|
|
1507
|
+
borderStyle: 'all-sides',
|
|
1508
|
+
hasHoverEffects: false,
|
|
1509
|
+
hasZebraStriping: false,
|
|
1510
|
+
}}
|
|
1511
|
+
tableInstance={table}
|
|
1512
|
+
/>
|
|
1513
|
+
</div>
|
|
1514
|
+
);
|
|
1515
|
+
};
|
|
1516
|
+
|
|
1517
|
+
export default Component;
|
|
1518
|
+
```
|
|
1519
|
+
|
|
1520
|
+
### Datagrid Placeholder States
|
|
1521
|
+
|
|
1522
|
+
```tsx
|
|
1523
|
+
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';
|
|
1524
|
+
|
|
1525
|
+
import { DataGrid } from '@neo4j-ndl/react';
|
|
1526
|
+
import {
|
|
1527
|
+
createColumnHelper,
|
|
1528
|
+
getCoreRowModel,
|
|
1529
|
+
getPaginationRowModel,
|
|
1530
|
+
useReactTable,
|
|
1531
|
+
} from '@tanstack/react-table';
|
|
1532
|
+
|
|
1533
|
+
import { TABLE_DEMO_DATA } from './staticData';
|
|
1534
|
+
|
|
1535
|
+
type TestDataFormat = {
|
|
1536
|
+
name: string;
|
|
1537
|
+
age: number;
|
|
1538
|
+
cypherCommand: string;
|
|
1539
|
+
};
|
|
1540
|
+
|
|
1541
|
+
const Component = () => {
|
|
1542
|
+
const columnHelper = createColumnHelper<TestDataFormat>();
|
|
1543
|
+
|
|
1544
|
+
const columns = [
|
|
1545
|
+
columnHelper.accessor('name', {
|
|
1546
|
+
cell: (info) => info.getValue(),
|
|
1547
|
+
footer: (info) => info.column.id,
|
|
1548
|
+
header: () => 'Name',
|
|
1549
|
+
maxSize: 300,
|
|
1550
|
+
minSize: 180,
|
|
1551
|
+
size: 220,
|
|
1552
|
+
}),
|
|
1553
|
+
columnHelper.accessor('age', {
|
|
1554
|
+
cell: (info) => info.renderValue(),
|
|
1555
|
+
footer: (info) => info.column.id,
|
|
1556
|
+
header: () => 'Age',
|
|
1557
|
+
minSize: 80,
|
|
1558
|
+
}),
|
|
1559
|
+
columnHelper.accessor('cypherCommand', {
|
|
1560
|
+
cell: (info) => info.getValue(),
|
|
1561
|
+
footer: (info) => info.column.id,
|
|
1562
|
+
header: () => 'Cypher Command',
|
|
1563
|
+
minSize: 400,
|
|
1564
|
+
size: 400,
|
|
1565
|
+
}),
|
|
1566
|
+
];
|
|
1567
|
+
|
|
1568
|
+
const table = useReactTable({
|
|
1569
|
+
columnResizeMode: 'onChange',
|
|
1570
|
+
columns,
|
|
1571
|
+
data: TABLE_DEMO_DATA,
|
|
1572
|
+
debugColumns: true,
|
|
1573
|
+
debugHeaders: true,
|
|
1574
|
+
debugTable: true,
|
|
1575
|
+
defaultColumn: {
|
|
1576
|
+
maxSize: 800,
|
|
1577
|
+
minSize: 80,
|
|
1578
|
+
},
|
|
1579
|
+
enableSorting: false,
|
|
1580
|
+
getCoreRowModel: getCoreRowModel(),
|
|
1581
|
+
getPaginationRowModel: getPaginationRowModel(),
|
|
1582
|
+
initialState: {
|
|
1583
|
+
pagination: {
|
|
1584
|
+
pageSize: 5,
|
|
1585
|
+
},
|
|
1586
|
+
},
|
|
1587
|
+
});
|
|
1588
|
+
|
|
1589
|
+
return (
|
|
1590
|
+
<div className="n-w-full n-bg-light-neutral-text-weakest">
|
|
1591
|
+
<DataGrid
|
|
1592
|
+
isResizable={true}
|
|
1593
|
+
tableInstance={table}
|
|
1594
|
+
isKeyboardNavigable={true}
|
|
1595
|
+
/>
|
|
1596
|
+
</div>
|
|
1597
|
+
);
|
|
1598
|
+
};
|
|
1599
|
+
|
|
1600
|
+
export default Component;
|
|
1601
|
+
```
|
|
1602
|
+
|
|
1603
|
+
### Datagrid Resizable Column
|
|
1604
|
+
|
|
1605
|
+
```tsx
|
|
1606
|
+
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';
|
|
1607
|
+
|
|
1608
|
+
import { DataGrid } from '@neo4j-ndl/react';
|
|
1609
|
+
import {
|
|
1610
|
+
createColumnHelper,
|
|
1611
|
+
getCoreRowModel,
|
|
1612
|
+
getPaginationRowModel,
|
|
1613
|
+
useReactTable,
|
|
1614
|
+
} from '@tanstack/react-table';
|
|
1615
|
+
|
|
1616
|
+
import { TABLE_DEMO_DATA } from './staticData';
|
|
1617
|
+
|
|
1618
|
+
type TestDataFormat = {
|
|
1619
|
+
name: string;
|
|
1620
|
+
age: number;
|
|
1621
|
+
cypherCommand: string;
|
|
1622
|
+
};
|
|
1623
|
+
|
|
1624
|
+
const Component = ({
|
|
1625
|
+
isKeyboardNavigable = true,
|
|
1626
|
+
components,
|
|
1627
|
+
}: {
|
|
1628
|
+
isKeyboardNavigable?: boolean;
|
|
1629
|
+
components?: React.ComponentProps<typeof DataGrid>['components'];
|
|
1630
|
+
} = {}) => {
|
|
1631
|
+
const columnHelper = createColumnHelper<TestDataFormat>();
|
|
1632
|
+
|
|
1633
|
+
const columns = [
|
|
1634
|
+
columnHelper.accessor('name', {
|
|
1635
|
+
cell: (info) => info.getValue(),
|
|
1636
|
+
footer: (info) => info.column.id,
|
|
1637
|
+
header: () => 'Name',
|
|
1638
|
+
maxSize: 300,
|
|
1639
|
+
minSize: 180,
|
|
1640
|
+
size: 220,
|
|
1641
|
+
}),
|
|
1642
|
+
columnHelper.accessor('age', {
|
|
1643
|
+
cell: (info) => info.renderValue(),
|
|
1644
|
+
footer: (info) => info.column.id,
|
|
1645
|
+
header: () => 'Age',
|
|
1646
|
+
minSize: 80,
|
|
1647
|
+
}),
|
|
1648
|
+
columnHelper.accessor('cypherCommand', {
|
|
1649
|
+
cell: (info) => info.getValue(),
|
|
1650
|
+
footer: (info) => info.column.id,
|
|
1651
|
+
header: () => 'Cypher Command',
|
|
1652
|
+
minSize: 400,
|
|
1653
|
+
size: 400,
|
|
1654
|
+
}),
|
|
1655
|
+
];
|
|
1656
|
+
|
|
1657
|
+
const table = useReactTable({
|
|
1658
|
+
columnResizeMode: 'onChange',
|
|
1659
|
+
columns,
|
|
1660
|
+
data: TABLE_DEMO_DATA,
|
|
1661
|
+
debugColumns: true,
|
|
1662
|
+
debugHeaders: true,
|
|
1663
|
+
debugTable: true,
|
|
1664
|
+
defaultColumn: {
|
|
1665
|
+
maxSize: 800,
|
|
1666
|
+
minSize: 80,
|
|
1667
|
+
},
|
|
1668
|
+
enableSorting: false,
|
|
1669
|
+
getCoreRowModel: getCoreRowModel(),
|
|
1670
|
+
getPaginationRowModel: getPaginationRowModel(),
|
|
1671
|
+
initialState: {
|
|
1672
|
+
pagination: {
|
|
1673
|
+
pageSize: 5,
|
|
1674
|
+
},
|
|
1675
|
+
},
|
|
1676
|
+
});
|
|
1677
|
+
|
|
1678
|
+
return (
|
|
1679
|
+
<div className="n-w-full n-bg-light-neutral-text-weakest">
|
|
1680
|
+
<DataGrid<TestDataFormat>
|
|
1681
|
+
isResizable={true}
|
|
1682
|
+
tableInstance={table}
|
|
1683
|
+
isKeyboardNavigable={isKeyboardNavigable}
|
|
1684
|
+
// @ts-expect-error limitations of storybooks makes it hard to handle generics
|
|
1685
|
+
components={components}
|
|
1686
|
+
/>
|
|
1687
|
+
</div>
|
|
1688
|
+
);
|
|
1689
|
+
};
|
|
1690
|
+
|
|
1691
|
+
export default Component;
|
|
1692
|
+
```
|
|
1693
|
+
|
|
1694
|
+
### Datagrid Rule Based Styling
|
|
1695
|
+
|
|
1696
|
+
```tsx
|
|
1697
|
+
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';
|
|
1698
|
+
|
|
1699
|
+
import { DataGrid } from '@neo4j-ndl/react';
|
|
1700
|
+
import {
|
|
1701
|
+
createColumnHelper,
|
|
1702
|
+
getCoreRowModel,
|
|
1703
|
+
getPaginationRowModel,
|
|
1704
|
+
getSortedRowModel,
|
|
1705
|
+
useReactTable,
|
|
1706
|
+
} from '@tanstack/react-table';
|
|
1707
|
+
|
|
1708
|
+
import { type DataGridStyleRule } from '../style-rules/types';
|
|
1709
|
+
import { TABLE_DEMO_DATA, type TestDataFormat } from './staticData';
|
|
1710
|
+
|
|
1711
|
+
const styleRules: DataGridStyleRule[] = [
|
|
1712
|
+
// Row coloring: all cells styled when name contains "Alicia"
|
|
1713
|
+
{
|
|
1714
|
+
apply: {
|
|
1715
|
+
color: { value: '#000000' },
|
|
1716
|
+
textColor: { value: '#ffffff' },
|
|
1717
|
+
},
|
|
1718
|
+
column: null,
|
|
1719
|
+
priority: 3,
|
|
1720
|
+
where: { contains: [{ column: 'name' }, 'Alicia'] },
|
|
1721
|
+
},
|
|
1722
|
+
// Cell coloring: cells in the "age" column with value 51 get green
|
|
1723
|
+
{
|
|
1724
|
+
apply: { color: { value: '#00bb00' } },
|
|
1725
|
+
column: 'age',
|
|
1726
|
+
priority: 3,
|
|
1727
|
+
where: { equal: [{ column: 'age' }, 51] },
|
|
1728
|
+
},
|
|
1729
|
+
// Dual color ranges on the "age" column: independent gradients for background
|
|
1730
|
+
// (driven by age, full 0-100 range) and text color (driven by age too, but
|
|
1731
|
+
// with a narrower 20-80 band so the text contrast is calibrated separately).
|
|
1732
|
+
{
|
|
1733
|
+
apply: {
|
|
1734
|
+
colorRange: {
|
|
1735
|
+
maxColor: '#f00000',
|
|
1736
|
+
maxValue: 100,
|
|
1737
|
+
midColor: '#a00000',
|
|
1738
|
+
midValue: 50,
|
|
1739
|
+
minColor: '#800000',
|
|
1740
|
+
minValue: 0,
|
|
1741
|
+
onColumn: 'age',
|
|
1742
|
+
},
|
|
1743
|
+
textColorRange: {
|
|
1744
|
+
maxColor: '#aaaaaa',
|
|
1745
|
+
maxValue: 80,
|
|
1746
|
+
minColor: '#ffffff',
|
|
1747
|
+
minValue: 20,
|
|
1748
|
+
onColumn: 'age',
|
|
1749
|
+
},
|
|
1750
|
+
},
|
|
1751
|
+
column: 'age',
|
|
1752
|
+
priority: 2, // Lower priority so explicit green rule overrides
|
|
1753
|
+
},
|
|
1754
|
+
// Multi-column rule: all cells styled when age > 50 AND name contains "o"
|
|
1755
|
+
{
|
|
1756
|
+
apply: {
|
|
1757
|
+
color: { value: '#fef3c7' },
|
|
1758
|
+
textColor: { value: '#000000' },
|
|
1759
|
+
},
|
|
1760
|
+
column: null,
|
|
1761
|
+
priority: 0, // Lower priority so other rules take precedence
|
|
1762
|
+
where: {
|
|
1763
|
+
and: [
|
|
1764
|
+
{ greaterThan: [{ column: 'age' }, 50] },
|
|
1765
|
+
{ contains: [{ column: 'name' }, 'o'] },
|
|
1766
|
+
],
|
|
1767
|
+
},
|
|
1768
|
+
},
|
|
1769
|
+
];
|
|
1770
|
+
|
|
1771
|
+
const Component = () => {
|
|
1772
|
+
const columnHelper = createColumnHelper<TestDataFormat>();
|
|
1773
|
+
|
|
1774
|
+
const columns = [
|
|
1775
|
+
columnHelper.accessor('name', {
|
|
1776
|
+
cell: (info) => info.getValue(),
|
|
1777
|
+
header: () => 'Name',
|
|
1778
|
+
minSize: 180,
|
|
1779
|
+
size: 220,
|
|
1780
|
+
}),
|
|
1781
|
+
columnHelper.accessor('age', {
|
|
1782
|
+
cell: (info) => info.renderValue(),
|
|
1783
|
+
header: () => 'Age',
|
|
1784
|
+
minSize: 80,
|
|
1785
|
+
}),
|
|
1786
|
+
columnHelper.accessor('cypherCommand', {
|
|
1787
|
+
cell: (info) => info.getValue(),
|
|
1788
|
+
header: () => 'Cypher Command',
|
|
1789
|
+
minSize: 400,
|
|
1790
|
+
size: 400,
|
|
1791
|
+
}),
|
|
1792
|
+
];
|
|
1793
|
+
|
|
1794
|
+
const table = useReactTable({
|
|
1795
|
+
columnResizeMode: 'onChange',
|
|
1796
|
+
columns,
|
|
1797
|
+
data: TABLE_DEMO_DATA,
|
|
1798
|
+
defaultColumn: {
|
|
1799
|
+
maxSize: 800,
|
|
1800
|
+
minSize: 80,
|
|
1801
|
+
},
|
|
1802
|
+
enableSorting: true,
|
|
1803
|
+
getCoreRowModel: getCoreRowModel(),
|
|
1804
|
+
getPaginationRowModel: getPaginationRowModel(),
|
|
1805
|
+
getSortedRowModel: getSortedRowModel(),
|
|
1806
|
+
initialState: {
|
|
1807
|
+
pagination: {
|
|
1808
|
+
pageSize: 10,
|
|
1809
|
+
},
|
|
1810
|
+
},
|
|
1811
|
+
});
|
|
1812
|
+
|
|
1813
|
+
return (
|
|
1814
|
+
<div className="n-w-full n-bg-light-neutral-text-weakest">
|
|
1815
|
+
<DataGrid
|
|
1816
|
+
isResizable={false}
|
|
1817
|
+
tableInstance={table}
|
|
1818
|
+
isKeyboardNavigable={false}
|
|
1819
|
+
styleRules={styleRules}
|
|
1820
|
+
styling={{
|
|
1821
|
+
borderStyle: 'all-sides',
|
|
1822
|
+
hasHoverEffects: true,
|
|
1823
|
+
hasZebraStriping: false,
|
|
1824
|
+
}}
|
|
1825
|
+
/>
|
|
1826
|
+
</div>
|
|
1827
|
+
);
|
|
1828
|
+
};
|
|
1829
|
+
|
|
1830
|
+
export default Component;
|
|
1831
|
+
```
|
|
1832
|
+
|
|
1833
|
+
### Datagrid Rule Based Styling Datetime
|
|
1834
|
+
|
|
1835
|
+
```tsx
|
|
1836
|
+
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';
|
|
1837
|
+
|
|
1838
|
+
import { DataGrid } from '@neo4j-ndl/react';
|
|
1839
|
+
import {
|
|
1840
|
+
createColumnHelper,
|
|
1841
|
+
getCoreRowModel,
|
|
1842
|
+
getPaginationRowModel,
|
|
1843
|
+
getSortedRowModel,
|
|
1844
|
+
useReactTable,
|
|
1845
|
+
} from '@tanstack/react-table';
|
|
1846
|
+
|
|
1847
|
+
import { type DataGridStyleRule } from '../style-rules/types';
|
|
1848
|
+
|
|
1849
|
+
type EventRow = {
|
|
1850
|
+
event: string;
|
|
1851
|
+
/**
|
|
1852
|
+
* JS Date coerced from the event's local ISO string. Natively converted to
|
|
1853
|
+
* Unix ms by the engine and compared against { datetime: "..." } literals.
|
|
1854
|
+
*/
|
|
1855
|
+
scheduledDate: Date;
|
|
1856
|
+
/**
|
|
1857
|
+
* UTC milliseconds since midnight. Compared against { time: "..." } literals
|
|
1858
|
+
* that are defined in Stockholm / CEST (+02:00) time — the engine normalises
|
|
1859
|
+
* both sides to UTC before comparing.
|
|
1860
|
+
*/
|
|
1861
|
+
scheduledTimeMs: number;
|
|
1862
|
+
/** Human-readable local time shown in the cell, e.g. "09:00 CEST (+02:00)". */
|
|
1863
|
+
localTimeDisplay: string;
|
|
1864
|
+
status: 'confirmed' | 'delayed' | 'cancelled';
|
|
1865
|
+
};
|
|
1866
|
+
|
|
1867
|
+
const data: EventRow[] = [
|
|
1868
|
+
// ── Past events (before start of 2026-06-25 in Stockholm / CEST) ──────────
|
|
1869
|
+
{
|
|
1870
|
+
event: 'Quarterly review',
|
|
1871
|
+
localTimeDisplay: '09:00 CEST (+02:00)',
|
|
1872
|
+
// 09:00 Stockholm CEST = 07:00 UTC
|
|
1873
|
+
scheduledDate: new Date('2026-06-10T09:00:00+02:00'),
|
|
1874
|
+
scheduledTimeMs: 7 * 3_600_000,
|
|
1875
|
+
status: 'confirmed',
|
|
1876
|
+
},
|
|
1877
|
+
{
|
|
1878
|
+
event: 'Security audit',
|
|
1879
|
+
localTimeDisplay: '07:30 BST (+01:00)',
|
|
1880
|
+
// 07:30 London BST (+01:00) = 06:30 UTC — before Stockholm opens
|
|
1881
|
+
scheduledDate: new Date('2026-06-18T07:30:00+01:00'),
|
|
1882
|
+
scheduledTimeMs: 6 * 3_600_000 + 30 * 60_000,
|
|
1883
|
+
status: 'delayed',
|
|
1884
|
+
},
|
|
1885
|
+
// ── Today (2026-06-25 in Stockholm / CEST) ─────────────────────────────────
|
|
1886
|
+
{
|
|
1887
|
+
event: 'Customer demo',
|
|
1888
|
+
localTimeDisplay: '09:00 EDT (-04:00)',
|
|
1889
|
+
// 09:00 New York EDT (-04:00) = 13:00 UTC
|
|
1890
|
+
scheduledDate: new Date('2026-06-25T09:00:00-04:00'),
|
|
1891
|
+
scheduledTimeMs: 13 * 3_600_000,
|
|
1892
|
+
status: 'confirmed',
|
|
1893
|
+
},
|
|
1894
|
+
{
|
|
1895
|
+
event: 'Platform migration',
|
|
1896
|
+
localTimeDisplay: '22:00 CEST (+02:00)',
|
|
1897
|
+
// 22:00 Stockholm CEST (+02:00) = 20:00 UTC — after Stockholm closes
|
|
1898
|
+
scheduledDate: new Date('2026-06-25T22:00:00+02:00'),
|
|
1899
|
+
scheduledTimeMs: 20 * 3_600_000,
|
|
1900
|
+
status: 'confirmed',
|
|
1901
|
+
},
|
|
1902
|
+
// ── Near future (up to and including 2026-07-14 in Stockholm) ─────────────
|
|
1903
|
+
{
|
|
1904
|
+
event: 'Board presentation',
|
|
1905
|
+
localTimeDisplay: '10:00 SGT (+08:00)',
|
|
1906
|
+
// 10:00 Singapore SGT (+08:00) = 02:00 UTC — before Stockholm opens
|
|
1907
|
+
scheduledDate: new Date('2026-07-02T10:00:00+08:00'),
|
|
1908
|
+
scheduledTimeMs: 2 * 3_600_000,
|
|
1909
|
+
status: 'confirmed',
|
|
1910
|
+
},
|
|
1911
|
+
{
|
|
1912
|
+
event: 'Partner onboarding',
|
|
1913
|
+
localTimeDisplay: '13:15 CEST (+02:00)',
|
|
1914
|
+
// 13:15 Paris CEST (+02:00) = 11:15 UTC
|
|
1915
|
+
scheduledDate: new Date('2026-07-08T13:15:00+02:00'),
|
|
1916
|
+
scheduledTimeMs: 11 * 3_600_000 + 15 * 60_000,
|
|
1917
|
+
status: 'cancelled',
|
|
1918
|
+
},
|
|
1919
|
+
// ── Far future (after 2026-07-15T00:00 Stockholm / CEST = 2026-07-14T22:00Z)
|
|
1920
|
+
{
|
|
1921
|
+
event: 'Architecture summit',
|
|
1922
|
+
localTimeDisplay: '08:00 CEST (+02:00)',
|
|
1923
|
+
// 08:00 Amsterdam CEST (+02:00) = 06:00 UTC.
|
|
1924
|
+
// Although the calendar date is "July 15", 08:00 CEST is still within the
|
|
1925
|
+
// same UTC day as July 14 22:00Z, so it crosses the far-future boundary.
|
|
1926
|
+
scheduledDate: new Date('2026-07-15T08:00:00+02:00'),
|
|
1927
|
+
scheduledTimeMs: 6 * 3_600_000,
|
|
1928
|
+
status: 'confirmed',
|
|
1929
|
+
},
|
|
1930
|
+
{
|
|
1931
|
+
event: 'Annual conference',
|
|
1932
|
+
localTimeDisplay: '09:00 PDT (-07:00)',
|
|
1933
|
+
// 09:00 San Francisco PDT (-07:00) = 16:00 UTC — after Stockholm closes
|
|
1934
|
+
scheduledDate: new Date('2026-08-03T09:00:00-07:00'),
|
|
1935
|
+
scheduledTimeMs: 16 * 3_600_000,
|
|
1936
|
+
status: 'confirmed',
|
|
1937
|
+
},
|
|
1938
|
+
{
|
|
1939
|
+
event: 'Year-end planning',
|
|
1940
|
+
localTimeDisplay: '09:00 JST (+09:00)',
|
|
1941
|
+
// 09:00 Tokyo JST (+09:00) = 00:00 UTC — midnight, well before Stockholm opens
|
|
1942
|
+
scheduledDate: new Date('2026-11-20T09:00:00+09:00'),
|
|
1943
|
+
scheduledTimeMs: 0,
|
|
1944
|
+
status: 'confirmed',
|
|
1945
|
+
},
|
|
1946
|
+
];
|
|
1947
|
+
|
|
1948
|
+
/**
|
|
1949
|
+
* All date and time boundaries are anchored to Stockholm in June–July 2026.
|
|
1950
|
+
* Date literals use RFC 9557 IANA bracket notation — the UTC offset is inferred
|
|
1951
|
+
* from the named zone (CEST = +02:00 in this period); writing `+02:00` explicitly
|
|
1952
|
+
* is optional. Time literals use UTC-offset notation.
|
|
1953
|
+
*
|
|
1954
|
+
* "Today" from a Stockholm perspective:
|
|
1955
|
+
* 2026-06-25T00:00:00[Europe/Stockholm] = 2026-06-24T22:00:00Z
|
|
1956
|
+
* 2026-06-26T00:00:00[Europe/Stockholm] = 2026-06-25T22:00:00Z
|
|
1957
|
+
*
|
|
1958
|
+
* "Far future" threshold:
|
|
1959
|
+
* 2026-07-15T00:00:00[Europe/Stockholm] = 2026-07-14T22:00:00Z
|
|
1960
|
+
*
|
|
1961
|
+
* Business hours (Stockholm):
|
|
1962
|
+
* { time: '09:00:00+02:00' } resolves to 07:00 UTC (25 200 000 ms)
|
|
1963
|
+
* { time: '17:00:00+02:00' } resolves to 15:00 UTC (54 000 000 ms)
|
|
1964
|
+
*/
|
|
1965
|
+
const styleRules: DataGridStyleRule[] = [
|
|
1966
|
+
// Past events — entire row in muted red
|
|
1967
|
+
{
|
|
1968
|
+
apply: {
|
|
1969
|
+
color: { value: '#fee2e2' },
|
|
1970
|
+
textColor: { value: '#7f1d1d' },
|
|
1971
|
+
},
|
|
1972
|
+
column: null,
|
|
1973
|
+
priority: 1,
|
|
1974
|
+
where: {
|
|
1975
|
+
lessThan: [
|
|
1976
|
+
{ column: 'scheduledDate' },
|
|
1977
|
+
{ datetime: '2026-06-25T00:00:00[Europe/Stockholm]' },
|
|
1978
|
+
],
|
|
1979
|
+
},
|
|
1980
|
+
},
|
|
1981
|
+
// Today (in Stockholm timezone) — entire row in amber
|
|
1982
|
+
{
|
|
1983
|
+
apply: {
|
|
1984
|
+
color: { value: '#fef9c3' },
|
|
1985
|
+
textColor: { value: '#713f12' },
|
|
1986
|
+
},
|
|
1987
|
+
column: null,
|
|
1988
|
+
priority: 1,
|
|
1989
|
+
where: {
|
|
1990
|
+
and: [
|
|
1991
|
+
{
|
|
1992
|
+
greaterThanOrEqual: [
|
|
1993
|
+
{ column: 'scheduledDate' },
|
|
1994
|
+
{ datetime: '2026-06-25T00:00:00[Europe/Stockholm]' },
|
|
1995
|
+
],
|
|
1996
|
+
},
|
|
1997
|
+
{
|
|
1998
|
+
lessThan: [
|
|
1999
|
+
{ column: 'scheduledDate' },
|
|
2000
|
+
{ datetime: '2026-06-26T00:00:00[Europe/Stockholm]' },
|
|
2001
|
+
],
|
|
2002
|
+
},
|
|
2003
|
+
],
|
|
2004
|
+
},
|
|
2005
|
+
},
|
|
2006
|
+
// Far future (after 2026-07-15 midnight Stockholm time) — entire row in green
|
|
2007
|
+
{
|
|
2008
|
+
apply: {
|
|
2009
|
+
color: { value: '#dcfce7' },
|
|
2010
|
+
textColor: { value: '#14532d' },
|
|
2011
|
+
},
|
|
2012
|
+
column: null,
|
|
2013
|
+
priority: 1,
|
|
2014
|
+
where: {
|
|
2015
|
+
greaterThanOrEqual: [
|
|
2016
|
+
{ column: 'scheduledDate' },
|
|
2017
|
+
{ datetime: '2026-07-15T00:00:00[Europe/Stockholm]' },
|
|
2018
|
+
],
|
|
2019
|
+
},
|
|
2020
|
+
},
|
|
2021
|
+
// Outside Stockholm business hours (before 09:00 CEST or from 17:00 CEST) —
|
|
2022
|
+
// time cell in orange. The { time: "..." } literals carry an offset so the
|
|
2023
|
+
// engine normalises them to UTC before comparing against scheduledTimeMs.
|
|
2024
|
+
{
|
|
2025
|
+
apply: {
|
|
2026
|
+
color: { value: '#ffedd5' },
|
|
2027
|
+
textColor: { value: '#7c2d12' },
|
|
2028
|
+
},
|
|
2029
|
+
column: 'scheduledTimeMs',
|
|
2030
|
+
priority: 2,
|
|
2031
|
+
where: {
|
|
2032
|
+
or: [
|
|
2033
|
+
{
|
|
2034
|
+
lessThan: [{ column: 'scheduledTimeMs' }, { time: '09:00:00+02:00' }],
|
|
2035
|
+
},
|
|
2036
|
+
{
|
|
2037
|
+
greaterThanOrEqual: [
|
|
2038
|
+
{ column: 'scheduledTimeMs' },
|
|
2039
|
+
{ time: '17:00:00+02:00' },
|
|
2040
|
+
],
|
|
2041
|
+
},
|
|
2042
|
+
],
|
|
2043
|
+
},
|
|
2044
|
+
},
|
|
2045
|
+
// Cancelled events — all cells grayed out at highest priority
|
|
2046
|
+
{
|
|
2047
|
+
apply: {
|
|
2048
|
+
color: { value: '#f3f4f6' },
|
|
2049
|
+
textColor: { value: '#9ca3af' },
|
|
2050
|
+
},
|
|
2051
|
+
column: null,
|
|
2052
|
+
priority: 5,
|
|
2053
|
+
where: { equal: [{ column: 'status' }, 'cancelled'] },
|
|
2054
|
+
},
|
|
2055
|
+
];
|
|
2056
|
+
|
|
2057
|
+
const Component = () => {
|
|
2058
|
+
const columnHelper = createColumnHelper<EventRow>();
|
|
2059
|
+
|
|
2060
|
+
const columns = [
|
|
2061
|
+
columnHelper.accessor('event', {
|
|
2062
|
+
cell: (info) => info.getValue(),
|
|
2063
|
+
header: () => 'Event',
|
|
2064
|
+
minSize: 180,
|
|
2065
|
+
size: 220,
|
|
2066
|
+
}),
|
|
2067
|
+
columnHelper.accessor('scheduledDate', {
|
|
2068
|
+
cell: (info) =>
|
|
2069
|
+
info.getValue().toLocaleDateString('en-GB', {
|
|
2070
|
+
day: 'numeric',
|
|
2071
|
+
month: 'short',
|
|
2072
|
+
timeZone: 'Europe/Stockholm',
|
|
2073
|
+
year: 'numeric',
|
|
2074
|
+
}),
|
|
2075
|
+
header: () => 'Date (Stockholm)',
|
|
2076
|
+
minSize: 150,
|
|
2077
|
+
size: 170,
|
|
2078
|
+
}),
|
|
2079
|
+
columnHelper.accessor('localTimeDisplay', {
|
|
2080
|
+
cell: (info) => info.getValue(),
|
|
2081
|
+
header: () => 'Local time',
|
|
2082
|
+
minSize: 160,
|
|
2083
|
+
size: 180,
|
|
2084
|
+
}),
|
|
2085
|
+
columnHelper.accessor('scheduledTimeMs', {
|
|
2086
|
+
cell: (info) => {
|
|
2087
|
+
const ms = info.getValue();
|
|
2088
|
+
const h = Math.floor(ms / 3_600_000)
|
|
2089
|
+
.toString()
|
|
2090
|
+
.padStart(2, '0');
|
|
2091
|
+
const m = Math.floor((ms % 3_600_000) / 60_000)
|
|
2092
|
+
.toString()
|
|
2093
|
+
.padStart(2, '0');
|
|
2094
|
+
return `${h}:${m} UTC`;
|
|
2095
|
+
},
|
|
2096
|
+
header: () => 'Time (UTC)',
|
|
2097
|
+
minSize: 110,
|
|
2098
|
+
size: 130,
|
|
2099
|
+
}),
|
|
2100
|
+
columnHelper.accessor('status', {
|
|
2101
|
+
cell: (info) => info.getValue(),
|
|
2102
|
+
header: () => 'Status',
|
|
2103
|
+
minSize: 100,
|
|
2104
|
+
size: 120,
|
|
2105
|
+
}),
|
|
2106
|
+
];
|
|
2107
|
+
|
|
2108
|
+
const table = useReactTable({
|
|
2109
|
+
columnResizeMode: 'onChange',
|
|
2110
|
+
columns,
|
|
2111
|
+
data,
|
|
2112
|
+
defaultColumn: {
|
|
2113
|
+
maxSize: 800,
|
|
2114
|
+
minSize: 80,
|
|
2115
|
+
},
|
|
2116
|
+
enableSorting: true,
|
|
2117
|
+
getCoreRowModel: getCoreRowModel(),
|
|
2118
|
+
getPaginationRowModel: getPaginationRowModel(),
|
|
2119
|
+
getSortedRowModel: getSortedRowModel(),
|
|
2120
|
+
initialState: {
|
|
2121
|
+
pagination: { pageSize: 10 },
|
|
2122
|
+
},
|
|
2123
|
+
});
|
|
2124
|
+
|
|
2125
|
+
return (
|
|
2126
|
+
<div className="n-w-full n-bg-light-neutral-text-weakest">
|
|
2127
|
+
<DataGrid
|
|
2128
|
+
isResizable={false}
|
|
2129
|
+
isKeyboardNavigable={false}
|
|
2130
|
+
tableInstance={table}
|
|
2131
|
+
styleRules={styleRules}
|
|
2132
|
+
styling={{
|
|
2133
|
+
borderStyle: 'all-sides',
|
|
2134
|
+
hasHoverEffects: true,
|
|
2135
|
+
hasZebraStriping: false,
|
|
2136
|
+
}}
|
|
2137
|
+
/>
|
|
2138
|
+
</div>
|
|
2139
|
+
);
|
|
2140
|
+
};
|
|
2141
|
+
|
|
2142
|
+
export default Component;
|
|
2143
|
+
```
|
|
2144
|
+
|
|
2145
|
+
### Datagrid Selectable
|
|
2146
|
+
|
|
2147
|
+
```tsx
|
|
2148
|
+
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';
|
|
2149
|
+
|
|
2150
|
+
import { Checkbox, DataGrid } from '@neo4j-ndl/react';
|
|
2151
|
+
import {
|
|
2152
|
+
type ColumnDef,
|
|
2153
|
+
createColumnHelper,
|
|
2154
|
+
getCoreRowModel,
|
|
2155
|
+
getPaginationRowModel,
|
|
2156
|
+
useReactTable,
|
|
2157
|
+
} from '@tanstack/react-table';
|
|
2158
|
+
import { useState } from 'react';
|
|
2159
|
+
|
|
2160
|
+
import { TABLE_DEMO_DATA } from './staticData';
|
|
2161
|
+
|
|
2162
|
+
type TestDataFormat = {
|
|
2163
|
+
name: string;
|
|
2164
|
+
age: number;
|
|
2165
|
+
cypherCommand: string;
|
|
2166
|
+
};
|
|
2167
|
+
|
|
2168
|
+
const selectableColumn: ColumnDef<TestDataFormat> = {
|
|
2169
|
+
cell: ({ row }) => (
|
|
2170
|
+
<div style={{ padding: '4px 0' }}>
|
|
2171
|
+
<Checkbox
|
|
2172
|
+
ariaLabel={`row-${row.index}`}
|
|
2173
|
+
isChecked={row.getIsSelected() || false}
|
|
2174
|
+
onChange={row.getToggleSelectedHandler()}
|
|
2175
|
+
/>
|
|
2176
|
+
</div>
|
|
2177
|
+
),
|
|
2178
|
+
header: ({ table }) => (
|
|
2179
|
+
<Checkbox
|
|
2180
|
+
ariaLabel="Select all rows"
|
|
2181
|
+
isChecked={table.getIsAllRowsSelected() || false}
|
|
2182
|
+
// If you want to select all pages from the current page use:
|
|
2183
|
+
// https://tanstack.com/table/v8/docs/api/features/row-selection#gettoggleallpagerowsselectedhandler
|
|
2184
|
+
onChange={table.getToggleAllRowsSelectedHandler()}
|
|
2185
|
+
isIndeterminate={table.getIsSomeRowsSelected()}
|
|
2186
|
+
/>
|
|
2187
|
+
),
|
|
2188
|
+
id: 'select',
|
|
2189
|
+
maxSize: 80,
|
|
2190
|
+
minSize: 48,
|
|
2191
|
+
size: 80,
|
|
2192
|
+
};
|
|
2193
|
+
const columnHelper = createColumnHelper<TestDataFormat>();
|
|
2194
|
+
|
|
2195
|
+
const commonColumns = [
|
|
2196
|
+
columnHelper.accessor('name', {
|
|
2197
|
+
cell: (info) => info.getValue(),
|
|
2198
|
+
footer: (info) => info.column.id,
|
|
2199
|
+
header: () => 'Name',
|
|
2200
|
+
maxSize: 300,
|
|
2201
|
+
minSize: 180,
|
|
2202
|
+
size: 220,
|
|
2203
|
+
}),
|
|
2204
|
+
columnHelper.accessor('age', {
|
|
2205
|
+
cell: (info) => info.renderValue(),
|
|
2206
|
+
footer: (info) => info.column.id,
|
|
2207
|
+
header: () => 'Age',
|
|
2208
|
+
minSize: 80,
|
|
2209
|
+
}),
|
|
2210
|
+
columnHelper.accessor('cypherCommand', {
|
|
2211
|
+
cell: (info) => info.getValue(),
|
|
2212
|
+
footer: (info) => info.column.id,
|
|
2213
|
+
header: () => 'Cypher Command',
|
|
2214
|
+
minSize: 400,
|
|
2215
|
+
size: 400,
|
|
2216
|
+
}),
|
|
2217
|
+
];
|
|
2218
|
+
|
|
2219
|
+
const allColumns = [selectableColumn, ...commonColumns];
|
|
2220
|
+
|
|
2221
|
+
const Component = () => {
|
|
2222
|
+
const [rowSelection, setRowSelection] = useState({});
|
|
2223
|
+
|
|
2224
|
+
const table = useReactTable({
|
|
2225
|
+
columnResizeMode: 'onChange',
|
|
2226
|
+
columns: allColumns,
|
|
2227
|
+
data: TABLE_DEMO_DATA,
|
|
2228
|
+
enableSorting: false,
|
|
2229
|
+
getCoreRowModel: getCoreRowModel(),
|
|
2230
|
+
getPaginationRowModel: getPaginationRowModel(),
|
|
2231
|
+
initialState: {
|
|
2232
|
+
pagination: {
|
|
2233
|
+
pageSize: 5,
|
|
2234
|
+
},
|
|
2235
|
+
},
|
|
2236
|
+
onRowSelectionChange: setRowSelection,
|
|
2237
|
+
state: {
|
|
2238
|
+
rowSelection,
|
|
2239
|
+
},
|
|
2240
|
+
});
|
|
2241
|
+
|
|
2242
|
+
return (
|
|
2243
|
+
<DataGrid
|
|
2244
|
+
isResizable={false}
|
|
2245
|
+
tableInstance={table}
|
|
2246
|
+
isKeyboardNavigable={false}
|
|
2247
|
+
/>
|
|
2248
|
+
);
|
|
2249
|
+
};
|
|
2250
|
+
|
|
2251
|
+
export default Component;
|
|
2252
|
+
```
|
|
2253
|
+
|
|
2254
|
+
### Datagrid Simple
|
|
2255
|
+
|
|
2256
|
+
```tsx
|
|
2257
|
+
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';
|
|
2258
|
+
|
|
2259
|
+
import { DataGrid } from '@neo4j-ndl/react';
|
|
2260
|
+
import {
|
|
2261
|
+
createColumnHelper,
|
|
2262
|
+
getCoreRowModel,
|
|
2263
|
+
getPaginationRowModel,
|
|
2264
|
+
getSortedRowModel,
|
|
2265
|
+
useReactTable,
|
|
2266
|
+
} from '@tanstack/react-table';
|
|
2267
|
+
|
|
2268
|
+
import { TABLE_DEMO_DATA } from './staticData';
|
|
2269
|
+
|
|
2270
|
+
type TestDataFormat = {
|
|
2271
|
+
name: string;
|
|
2272
|
+
age: number;
|
|
2273
|
+
cypherCommand: string;
|
|
2274
|
+
};
|
|
2275
|
+
|
|
2276
|
+
const Component = () => {
|
|
2277
|
+
const columnHelper = createColumnHelper<TestDataFormat>();
|
|
2278
|
+
|
|
2279
|
+
const columns = [
|
|
2280
|
+
columnHelper.accessor('name', {
|
|
2281
|
+
cell: (info) => info.getValue(),
|
|
2282
|
+
footer: (info) => info.column.id,
|
|
2283
|
+
header: () => 'Name',
|
|
2284
|
+
maxSize: 300,
|
|
2285
|
+
minSize: 180,
|
|
2286
|
+
size: 220,
|
|
2287
|
+
}),
|
|
2288
|
+
columnHelper.accessor('age', {
|
|
2289
|
+
cell: (info) => info.renderValue(),
|
|
2290
|
+
footer: (info) => info.column.id,
|
|
2291
|
+
header: () => 'Age',
|
|
2292
|
+
minSize: 80,
|
|
2293
|
+
}),
|
|
2294
|
+
columnHelper.accessor('cypherCommand', {
|
|
2295
|
+
cell: (info) => info.getValue(),
|
|
2296
|
+
footer: (info) => info.column.id,
|
|
2297
|
+
header: () => 'Cypher Command',
|
|
2298
|
+
minSize: 400,
|
|
2299
|
+
size: 400,
|
|
2300
|
+
}),
|
|
2301
|
+
];
|
|
2302
|
+
|
|
2303
|
+
const table = useReactTable({
|
|
2304
|
+
columnResizeMode: 'onChange',
|
|
2305
|
+
columns,
|
|
2306
|
+
data: TABLE_DEMO_DATA,
|
|
2307
|
+
debugColumns: true,
|
|
2308
|
+
debugHeaders: true,
|
|
2309
|
+
debugTable: true,
|
|
2310
|
+
defaultColumn: {
|
|
2311
|
+
maxSize: 800,
|
|
2312
|
+
minSize: 80,
|
|
2313
|
+
},
|
|
2314
|
+
enableSorting: true,
|
|
2315
|
+
getCoreRowModel: getCoreRowModel(),
|
|
2316
|
+
getPaginationRowModel: getPaginationRowModel(),
|
|
2317
|
+
getSortedRowModel: getSortedRowModel(),
|
|
2318
|
+
initialState: {
|
|
2319
|
+
pagination: {
|
|
2320
|
+
pageSize: 5,
|
|
2321
|
+
},
|
|
2322
|
+
},
|
|
2323
|
+
});
|
|
2324
|
+
|
|
2325
|
+
return (
|
|
2326
|
+
<div className="n-w-full n-bg-light-neutral-text-weakest">
|
|
2327
|
+
<DataGrid
|
|
2328
|
+
isResizable={false}
|
|
2329
|
+
tableInstance={table}
|
|
2330
|
+
isKeyboardNavigable={false}
|
|
2331
|
+
styling={{
|
|
2332
|
+
borderStyle: 'all-sides',
|
|
2333
|
+
hasHoverEffects: true,
|
|
2334
|
+
hasZebraStriping: false,
|
|
2335
|
+
}}
|
|
2336
|
+
/>
|
|
2337
|
+
</div>
|
|
2338
|
+
);
|
|
2339
|
+
};
|
|
2340
|
+
|
|
2341
|
+
export default Component;
|
|
2342
|
+
```
|
|
2343
|
+
|
|
2344
|
+
### Datagrid Single Selectable
|
|
2345
|
+
|
|
2346
|
+
```tsx
|
|
2347
|
+
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';
|
|
2348
|
+
|
|
2349
|
+
import { DataGrid, Radio, useDataGridContext } from '@neo4j-ndl/react';
|
|
2350
|
+
import {
|
|
2351
|
+
createColumnHelper,
|
|
2352
|
+
getCoreRowModel,
|
|
2353
|
+
getPaginationRowModel,
|
|
2354
|
+
useReactTable,
|
|
2355
|
+
} from '@tanstack/react-table';
|
|
2356
|
+
|
|
2357
|
+
type PricingEntry = {
|
|
2358
|
+
cpuCount: number;
|
|
2359
|
+
id: number;
|
|
2360
|
+
memory: string;
|
|
2361
|
+
storage: string;
|
|
2362
|
+
};
|
|
2363
|
+
|
|
2364
|
+
const columnHelper = createColumnHelper<PricingEntry>();
|
|
2365
|
+
|
|
2366
|
+
const DEMO_PRICING: PricingEntry[] = [
|
|
2367
|
+
{
|
|
2368
|
+
cpuCount: 1,
|
|
2369
|
+
id: 1,
|
|
2370
|
+
memory: '1 GB',
|
|
2371
|
+
storage: '10 GB',
|
|
2372
|
+
},
|
|
2373
|
+
{
|
|
2374
|
+
cpuCount: 2,
|
|
2375
|
+
id: 2,
|
|
2376
|
+
memory: '2 GB',
|
|
2377
|
+
storage: '20 GB',
|
|
2378
|
+
},
|
|
2379
|
+
{
|
|
2380
|
+
cpuCount: 3,
|
|
2381
|
+
id: 3,
|
|
2382
|
+
memory: '3 GB',
|
|
2383
|
+
storage: '30 GB',
|
|
2384
|
+
},
|
|
2385
|
+
];
|
|
2386
|
+
|
|
2387
|
+
const columns = [
|
|
2388
|
+
columnHelper.accessor('id', {
|
|
2389
|
+
cell: ({ cell, row }) => (
|
|
2390
|
+
<Radio
|
|
2391
|
+
ariaLabel={`value-${cell.getValue()}`}
|
|
2392
|
+
isChecked={row.getIsSelected()}
|
|
2393
|
+
/>
|
|
2394
|
+
),
|
|
2395
|
+
header: () => null,
|
|
2396
|
+
maxSize: 50,
|
|
2397
|
+
size: 50,
|
|
2398
|
+
}),
|
|
2399
|
+
columnHelper.accessor('memory', {
|
|
2400
|
+
cell: (info) => info.getValue(),
|
|
2401
|
+
minSize: 120,
|
|
2402
|
+
}),
|
|
2403
|
+
columnHelper.accessor('cpuCount', {
|
|
2404
|
+
cell: (info) => info.getValue(),
|
|
2405
|
+
}),
|
|
2406
|
+
columnHelper.accessor('storage', {
|
|
2407
|
+
cell: (info) => info.getValue(),
|
|
2408
|
+
}),
|
|
2409
|
+
];
|
|
2410
|
+
|
|
2411
|
+
const CustomBodyRow = ({
|
|
2412
|
+
row,
|
|
2413
|
+
}: React.ComponentProps<typeof DataGrid.BodyRow<PricingEntry>>) => {
|
|
2414
|
+
const { components } = useDataGridContext<PricingEntry>();
|
|
2415
|
+
|
|
2416
|
+
return (
|
|
2417
|
+
<div key={row.id} role="row" className="ndl-data-grid-tr">
|
|
2418
|
+
{row.getVisibleCells().map(
|
|
2419
|
+
(cell) =>
|
|
2420
|
+
components.BodyCell && (
|
|
2421
|
+
<components.BodyCell
|
|
2422
|
+
key={cell.id}
|
|
2423
|
+
cell={cell}
|
|
2424
|
+
htmlAttributes={{
|
|
2425
|
+
onClick: () => row.toggleSelected(!row.getIsSelected()),
|
|
2426
|
+
}}
|
|
2427
|
+
/>
|
|
2428
|
+
),
|
|
2429
|
+
)}
|
|
2430
|
+
</div>
|
|
2431
|
+
);
|
|
2432
|
+
};
|
|
2433
|
+
|
|
2434
|
+
const Component = () => {
|
|
2435
|
+
const table = useReactTable({
|
|
2436
|
+
columnResizeMode: 'onChange',
|
|
2437
|
+
columns,
|
|
2438
|
+
data: DEMO_PRICING,
|
|
2439
|
+
enableMultiRowSelection: false,
|
|
2440
|
+
enableSorting: false,
|
|
2441
|
+
getCoreRowModel: getCoreRowModel(),
|
|
2442
|
+
getPaginationRowModel: getPaginationRowModel(),
|
|
2443
|
+
initialState: {
|
|
2444
|
+
rowSelection: {
|
|
2445
|
+
0: true,
|
|
2446
|
+
},
|
|
2447
|
+
},
|
|
2448
|
+
});
|
|
2449
|
+
|
|
2450
|
+
return (
|
|
2451
|
+
<div className="n-w-full">
|
|
2452
|
+
<DataGrid
|
|
2453
|
+
components={{
|
|
2454
|
+
BodyRow: CustomBodyRow,
|
|
2455
|
+
Navigation: null,
|
|
2456
|
+
}}
|
|
2457
|
+
isKeyboardNavigable={false}
|
|
2458
|
+
isResizable={false}
|
|
2459
|
+
tableInstance={table}
|
|
2460
|
+
/>
|
|
2461
|
+
</div>
|
|
2462
|
+
);
|
|
2463
|
+
};
|
|
2464
|
+
|
|
2465
|
+
export default Component;
|
|
2466
|
+
```
|
|
2467
|
+
|
|
2468
|
+
### Datagrid Skeleton Loading
|
|
2469
|
+
|
|
2470
|
+
```tsx
|
|
2471
|
+
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';
|
|
2472
|
+
|
|
2473
|
+
import { DataGrid } from '@neo4j-ndl/react';
|
|
2474
|
+
import {
|
|
2475
|
+
createColumnHelper,
|
|
2476
|
+
getCoreRowModel,
|
|
2477
|
+
getPaginationRowModel,
|
|
2478
|
+
useReactTable,
|
|
2479
|
+
} from '@tanstack/react-table';
|
|
2480
|
+
|
|
2481
|
+
import { TABLE_DEMO_DATA } from './staticData';
|
|
2482
|
+
|
|
2483
|
+
type TestDataFormat = {
|
|
2484
|
+
name: string;
|
|
2485
|
+
age: number;
|
|
2486
|
+
cypherCommand: string;
|
|
2487
|
+
};
|
|
2488
|
+
|
|
2489
|
+
const Component = () => {
|
|
2490
|
+
const columnHelper = createColumnHelper<TestDataFormat>();
|
|
2491
|
+
|
|
2492
|
+
const columns = [
|
|
2493
|
+
columnHelper.accessor('name', {
|
|
2494
|
+
cell: (info) => info.getValue(),
|
|
2495
|
+
footer: (info) => info.column.id,
|
|
2496
|
+
header: () => 'Name',
|
|
2497
|
+
maxSize: 300,
|
|
2498
|
+
minSize: 180,
|
|
2499
|
+
size: 220,
|
|
2500
|
+
}),
|
|
2501
|
+
columnHelper.accessor('age', {
|
|
2502
|
+
cell: (info) => info.renderValue(),
|
|
2503
|
+
footer: (info) => info.column.id,
|
|
2504
|
+
header: () => 'Age',
|
|
2505
|
+
minSize: 80,
|
|
2506
|
+
}),
|
|
2507
|
+
columnHelper.accessor('cypherCommand', {
|
|
2508
|
+
cell: (info) => info.getValue(),
|
|
2509
|
+
footer: (info) => info.column.id,
|
|
2510
|
+
header: () => 'Cypher Command',
|
|
2511
|
+
minSize: 400,
|
|
2512
|
+
size: 400,
|
|
2513
|
+
}),
|
|
2514
|
+
];
|
|
2515
|
+
|
|
2516
|
+
const table = useReactTable({
|
|
2517
|
+
columnResizeMode: 'onChange',
|
|
2518
|
+
columns,
|
|
2519
|
+
data: TABLE_DEMO_DATA,
|
|
2520
|
+
debugColumns: true,
|
|
2521
|
+
debugHeaders: true,
|
|
2522
|
+
debugTable: true,
|
|
2523
|
+
defaultColumn: {
|
|
2524
|
+
maxSize: 800,
|
|
2525
|
+
minSize: 80,
|
|
2526
|
+
},
|
|
2527
|
+
enableSorting: false,
|
|
2528
|
+
getCoreRowModel: getCoreRowModel(),
|
|
2529
|
+
getPaginationRowModel: getPaginationRowModel(),
|
|
2530
|
+
initialState: {
|
|
2531
|
+
pagination: {
|
|
2532
|
+
pageSize: 5,
|
|
2533
|
+
},
|
|
2534
|
+
},
|
|
2535
|
+
});
|
|
2536
|
+
|
|
2537
|
+
return (
|
|
2538
|
+
<div className="n-w-full n-bg-light-neutral-text-weakest">
|
|
2539
|
+
<DataGrid
|
|
2540
|
+
isResizable={true}
|
|
2541
|
+
tableInstance={table}
|
|
2542
|
+
isKeyboardNavigable={true}
|
|
2543
|
+
isSkeletonLoading={true}
|
|
2544
|
+
/>
|
|
2545
|
+
</div>
|
|
2546
|
+
);
|
|
2547
|
+
};
|
|
2548
|
+
|
|
2549
|
+
export default Component;
|
|
2550
|
+
```
|
|
2551
|
+
|
|
2552
|
+
### Datagrid Sortable
|
|
2553
|
+
|
|
2554
|
+
```tsx
|
|
2555
|
+
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';
|
|
2556
|
+
|
|
2557
|
+
import { DataGrid } from '@neo4j-ndl/react';
|
|
2558
|
+
import {
|
|
2559
|
+
createColumnHelper,
|
|
2560
|
+
getCoreRowModel,
|
|
2561
|
+
getPaginationRowModel,
|
|
2562
|
+
getSortedRowModel,
|
|
2563
|
+
useReactTable,
|
|
2564
|
+
} from '@tanstack/react-table';
|
|
2565
|
+
|
|
2566
|
+
import { TABLE_DEMO_DATA } from './staticData';
|
|
2567
|
+
|
|
2568
|
+
type TestDataFormat = {
|
|
2569
|
+
name: string;
|
|
2570
|
+
age: number;
|
|
2571
|
+
cypherCommand: string;
|
|
2572
|
+
};
|
|
2573
|
+
|
|
2574
|
+
const Component = () => {
|
|
2575
|
+
const columnHelper = createColumnHelper<TestDataFormat>();
|
|
2576
|
+
|
|
2577
|
+
const columns = [
|
|
2578
|
+
columnHelper.accessor('name', {
|
|
2579
|
+
cell: (info) => info.getValue(),
|
|
2580
|
+
footer: (info) => info.column.id,
|
|
2581
|
+
header: () => 'Name',
|
|
2582
|
+
maxSize: 300,
|
|
2583
|
+
minSize: 180,
|
|
2584
|
+
size: 220,
|
|
2585
|
+
}),
|
|
2586
|
+
columnHelper.accessor('age', {
|
|
2587
|
+
cell: (info) => info.renderValue(),
|
|
2588
|
+
footer: (info) => info.column.id,
|
|
2589
|
+
header: () => 'Age',
|
|
2590
|
+
minSize: 80,
|
|
2591
|
+
}),
|
|
2592
|
+
columnHelper.accessor('cypherCommand', {
|
|
2593
|
+
cell: (info) => info.getValue(),
|
|
2594
|
+
footer: (info) => info.column.id,
|
|
2595
|
+
header: () => 'Cypher Command',
|
|
2596
|
+
minSize: 400,
|
|
2597
|
+
size: 400,
|
|
2598
|
+
}),
|
|
2599
|
+
];
|
|
2600
|
+
|
|
2601
|
+
const table = useReactTable({
|
|
2602
|
+
columnResizeMode: 'onChange',
|
|
2603
|
+
columns,
|
|
2604
|
+
data: TABLE_DEMO_DATA,
|
|
2605
|
+
debugColumns: true,
|
|
2606
|
+
debugHeaders: true,
|
|
2607
|
+
debugTable: true,
|
|
2608
|
+
defaultColumn: {
|
|
2609
|
+
maxSize: 800,
|
|
2610
|
+
minSize: 80,
|
|
2611
|
+
},
|
|
2612
|
+
enableSorting: true,
|
|
2613
|
+
getCoreRowModel: getCoreRowModel(),
|
|
2614
|
+
getPaginationRowModel: getPaginationRowModel(),
|
|
2615
|
+
getSortedRowModel: getSortedRowModel(),
|
|
2616
|
+
initialState: {
|
|
2617
|
+
pagination: {
|
|
2618
|
+
pageSize: 5,
|
|
2619
|
+
},
|
|
2620
|
+
},
|
|
2621
|
+
});
|
|
2622
|
+
|
|
2623
|
+
return (
|
|
2624
|
+
<div className="n-w-full n-bg-light-neutral-text-weakest">
|
|
2625
|
+
<DataGrid
|
|
2626
|
+
isResizable={true}
|
|
2627
|
+
tableInstance={table}
|
|
2628
|
+
isKeyboardNavigable={true}
|
|
2629
|
+
/>
|
|
2630
|
+
</div>
|
|
2631
|
+
);
|
|
2632
|
+
};
|
|
2633
|
+
|
|
2634
|
+
export default Component;
|
|
2635
|
+
```
|
|
2636
|
+
|
|
2637
|
+
### Datagrid Using Editable Cells
|
|
2638
|
+
|
|
2639
|
+
```tsx
|
|
2640
|
+
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';
|
|
2641
|
+
|
|
2642
|
+
import { tokens } from '@neo4j-ndl/base';
|
|
2643
|
+
import { DataGrid, GraphLabel } from '@neo4j-ndl/react';
|
|
2644
|
+
import { ExclamationTriangleIconSolid } from '@neo4j-ndl/react/icons';
|
|
2645
|
+
import {
|
|
2646
|
+
createColumnHelper,
|
|
2647
|
+
getCoreRowModel,
|
|
2648
|
+
getPaginationRowModel,
|
|
2649
|
+
getSortedRowModel,
|
|
2650
|
+
useReactTable,
|
|
2651
|
+
} from '@tanstack/react-table';
|
|
2652
|
+
import { useState } from 'react';
|
|
2653
|
+
import { type MultiValueProps } from 'react-select';
|
|
2654
|
+
|
|
2655
|
+
type TestDataFormat = {
|
|
2656
|
+
name: string;
|
|
2657
|
+
role: string;
|
|
2658
|
+
permissions: string[];
|
|
2659
|
+
labels: string[];
|
|
2660
|
+
status: string;
|
|
2661
|
+
};
|
|
2662
|
+
|
|
2663
|
+
const ROLE_OPTIONS = [
|
|
2664
|
+
{ label: 'Admin', value: 'admin' },
|
|
2665
|
+
{ label: 'Editor', value: 'editor' },
|
|
2666
|
+
{ label: 'Viewer', value: 'viewer' },
|
|
2667
|
+
];
|
|
2668
|
+
|
|
2669
|
+
const PERMISSION_OPTIONS = [
|
|
2670
|
+
{ label: 'Read', value: 'read' },
|
|
2671
|
+
{ label: 'Write', value: 'write' },
|
|
2672
|
+
{ label: 'Delete', value: 'delete' },
|
|
2673
|
+
];
|
|
2674
|
+
|
|
2675
|
+
const LABEL_OPTIONS = [
|
|
2676
|
+
{ label: 'Person', value: 'person' },
|
|
2677
|
+
{ label: 'Movie', value: 'movie' },
|
|
2678
|
+
{ label: 'Director', value: 'director' },
|
|
2679
|
+
{ label: 'Actor', value: 'actor' },
|
|
2680
|
+
];
|
|
2681
|
+
|
|
2682
|
+
const STATUS_OPTIONS = [
|
|
2683
|
+
{ label: 'Active', value: 'active' },
|
|
2684
|
+
{ label: 'Inactive', value: 'inactive' },
|
|
2685
|
+
{ label: 'Pending', value: 'pending' },
|
|
2686
|
+
];
|
|
2687
|
+
|
|
2688
|
+
const VALID_STATUSES = STATUS_OPTIONS.map((option) => option.value);
|
|
2689
|
+
|
|
2690
|
+
type DropDownOption = { value: string; label: string };
|
|
2691
|
+
|
|
2692
|
+
const LABEL_COLORS: Record<string, string> = {
|
|
2693
|
+
actor: tokens.graph['7'],
|
|
2694
|
+
director: tokens.graph['4'],
|
|
2695
|
+
movie: tokens.graph['3'],
|
|
2696
|
+
person: tokens.graph['1'],
|
|
2697
|
+
};
|
|
2698
|
+
|
|
2699
|
+
// Render each selected value as a non-interactive GraphLabel (as a span)
|
|
2700
|
+
// instead of the default Tag chip, demonstrating the components override.
|
|
2701
|
+
const LabelMultiValue = (props: MultiValueProps<DropDownOption, boolean>) => (
|
|
2702
|
+
<GraphLabel
|
|
2703
|
+
as="span"
|
|
2704
|
+
type="node"
|
|
2705
|
+
size="small"
|
|
2706
|
+
color={LABEL_COLORS[props.data.value]}
|
|
2707
|
+
>
|
|
2708
|
+
{props.data.label}
|
|
2709
|
+
</GraphLabel>
|
|
2710
|
+
);
|
|
2711
|
+
|
|
2712
|
+
// The "Dallas Kuhn" row has a status that is not part of the available
|
|
2713
|
+
// options, so its dropdown cell renders in the error state.
|
|
2714
|
+
const DEMO_DATA: TestDataFormat[] = [
|
|
2715
|
+
{
|
|
2716
|
+
labels: ['person', 'actor'],
|
|
2717
|
+
name: 'Alicia Sanford',
|
|
2718
|
+
permissions: ['read', 'write'],
|
|
2719
|
+
role: 'admin',
|
|
2720
|
+
status: 'active',
|
|
2721
|
+
},
|
|
2722
|
+
{
|
|
2723
|
+
labels: ['movie'],
|
|
2724
|
+
name: 'Dallas Kuhn',
|
|
2725
|
+
permissions: ['read'],
|
|
2726
|
+
role: 'editor',
|
|
2727
|
+
status: 'unknown',
|
|
2728
|
+
},
|
|
2729
|
+
{
|
|
2730
|
+
labels: ['director'],
|
|
2731
|
+
name: 'Susan Daugherty MD',
|
|
2732
|
+
permissions: ['read', 'write', 'delete'],
|
|
2733
|
+
role: 'viewer',
|
|
2734
|
+
status: 'pending',
|
|
2735
|
+
},
|
|
2736
|
+
{
|
|
2737
|
+
labels: ['person'],
|
|
2738
|
+
name: 'Karen Huel',
|
|
2739
|
+
permissions: ['read'],
|
|
2740
|
+
role: 'editor',
|
|
2741
|
+
status: 'inactive',
|
|
2742
|
+
},
|
|
2743
|
+
{
|
|
2744
|
+
labels: ['movie', 'director'],
|
|
2745
|
+
name: 'Ken Kessler',
|
|
2746
|
+
permissions: ['write', 'delete'],
|
|
2747
|
+
role: 'admin',
|
|
2748
|
+
status: 'active',
|
|
2749
|
+
},
|
|
2750
|
+
{
|
|
2751
|
+
labels: [],
|
|
2752
|
+
name: 'Courtney McClure',
|
|
2753
|
+
permissions: [],
|
|
2754
|
+
role: 'viewer',
|
|
2755
|
+
status: 'pending',
|
|
2756
|
+
},
|
|
2757
|
+
];
|
|
2758
|
+
|
|
2759
|
+
const columnHelper = createColumnHelper<TestDataFormat>();
|
|
2760
|
+
|
|
2761
|
+
const Component = () => {
|
|
2762
|
+
const [data, setData] = useState(DEMO_DATA);
|
|
2763
|
+
|
|
2764
|
+
const updateRow = (name: string, update: Partial<TestDataFormat>): void => {
|
|
2765
|
+
setData((oldData) =>
|
|
2766
|
+
oldData.map((row) => (row.name === name ? { ...row, ...update } : row)),
|
|
2767
|
+
);
|
|
2768
|
+
};
|
|
2769
|
+
|
|
2770
|
+
const COMMON_COLUMNS = [
|
|
2771
|
+
// Editable inline text cell.
|
|
2772
|
+
columnHelper.accessor('name', {
|
|
2773
|
+
cell: (cell) => (
|
|
2774
|
+
<DataGrid.InlineEditCell
|
|
2775
|
+
cell={cell}
|
|
2776
|
+
value={cell.getValue()}
|
|
2777
|
+
ariaLabel="edit name"
|
|
2778
|
+
/>
|
|
2779
|
+
),
|
|
2780
|
+
meta: {
|
|
2781
|
+
isInlineEditCell: {
|
|
2782
|
+
onEditChange: (newValue, cell) => {
|
|
2783
|
+
updateRow(cell.row.original.name, { name: newValue });
|
|
2784
|
+
},
|
|
2785
|
+
},
|
|
2786
|
+
},
|
|
2787
|
+
minSize: 180,
|
|
2788
|
+
size: 220,
|
|
2789
|
+
}),
|
|
2790
|
+
// Default single-select dropdown.
|
|
2791
|
+
columnHelper.accessor('role', {
|
|
2792
|
+
cell: (info) => (
|
|
2793
|
+
<DataGrid.DropDownCell
|
|
2794
|
+
cell={info}
|
|
2795
|
+
options={ROLE_OPTIONS}
|
|
2796
|
+
ariaLabel="select role"
|
|
2797
|
+
/>
|
|
2798
|
+
),
|
|
2799
|
+
meta: {
|
|
2800
|
+
isDropDownCell: {
|
|
2801
|
+
onChange: (newValue, cell) => {
|
|
2802
|
+
if (newValue === undefined) {
|
|
2803
|
+
return;
|
|
2804
|
+
}
|
|
2805
|
+
updateRow(cell.row.original.name, { role: newValue });
|
|
2806
|
+
},
|
|
2807
|
+
},
|
|
2808
|
+
},
|
|
2809
|
+
minSize: 160,
|
|
2810
|
+
size: 180,
|
|
2811
|
+
}),
|
|
2812
|
+
// Default multi-select dropdown (default Tag chips).
|
|
2813
|
+
columnHelper.accessor('permissions', {
|
|
2814
|
+
cell: (info) => (
|
|
2815
|
+
<DataGrid.DropDownCell
|
|
2816
|
+
cell={info}
|
|
2817
|
+
options={PERMISSION_OPTIONS}
|
|
2818
|
+
ariaLabel="select permissions"
|
|
2819
|
+
/>
|
|
2820
|
+
),
|
|
2821
|
+
enableSorting: false,
|
|
2822
|
+
meta: {
|
|
2823
|
+
isDropDownCell: {
|
|
2824
|
+
isMulti: true,
|
|
2825
|
+
onChange: (newValue, cell) => {
|
|
2826
|
+
updateRow(cell.row.original.name, { permissions: newValue });
|
|
2827
|
+
},
|
|
2828
|
+
},
|
|
2829
|
+
},
|
|
2830
|
+
minSize: 220,
|
|
2831
|
+
size: 260,
|
|
2832
|
+
}),
|
|
2833
|
+
// Multi-select dropdown with a custom MultiValue (GraphLabel chips).
|
|
2834
|
+
columnHelper.accessor('labels', {
|
|
2835
|
+
cell: (info) => (
|
|
2836
|
+
<DataGrid.DropDownCell
|
|
2837
|
+
cell={info}
|
|
2838
|
+
options={LABEL_OPTIONS}
|
|
2839
|
+
ariaLabel="select labels"
|
|
2840
|
+
components={{ MultiValue: LabelMultiValue }}
|
|
2841
|
+
/>
|
|
2842
|
+
),
|
|
2843
|
+
enableSorting: false,
|
|
2844
|
+
meta: {
|
|
2845
|
+
isDropDownCell: {
|
|
2846
|
+
isMulti: true,
|
|
2847
|
+
onChange: (newValue, cell) => {
|
|
2848
|
+
updateRow(cell.row.original.name, { labels: newValue });
|
|
2849
|
+
},
|
|
2850
|
+
},
|
|
2851
|
+
},
|
|
2852
|
+
minSize: 240,
|
|
2853
|
+
size: 280,
|
|
2854
|
+
}),
|
|
2855
|
+
// Single-select dropdown in an error state.
|
|
2856
|
+
columnHelper.accessor('status', {
|
|
2857
|
+
cell: (info) => {
|
|
2858
|
+
const isInvalid = !VALID_STATUSES.includes(info.getValue());
|
|
2859
|
+
|
|
2860
|
+
const isPending = info.getValue() === 'pending';
|
|
2861
|
+
const isInactive = info.getValue() === 'inactive';
|
|
2862
|
+
|
|
2863
|
+
const warningHelpText = (
|
|
2864
|
+
<span className="n-flex n-items-center n-gap-token-2 n-text-warning-text">
|
|
2865
|
+
<ExclamationTriangleIconSolid className="n-size-token-16 n-text-warning-icon" />
|
|
2866
|
+
Inactive users cannot sign in
|
|
2867
|
+
</span>
|
|
2868
|
+
);
|
|
2869
|
+
|
|
2870
|
+
return (
|
|
2871
|
+
<DataGrid.DropDownCell
|
|
2872
|
+
cell={info}
|
|
2873
|
+
options={STATUS_OPTIONS}
|
|
2874
|
+
ariaLabel="select status"
|
|
2875
|
+
errorText={isInvalid ? 'Please select a valid status' : undefined}
|
|
2876
|
+
helpText={
|
|
2877
|
+
isInactive
|
|
2878
|
+
? warningHelpText
|
|
2879
|
+
: isPending
|
|
2880
|
+
? 'Waiting for admin approval'
|
|
2881
|
+
: undefined
|
|
2882
|
+
}
|
|
2883
|
+
/>
|
|
2884
|
+
);
|
|
2885
|
+
},
|
|
2886
|
+
meta: {
|
|
2887
|
+
isDropDownCell: {
|
|
2888
|
+
onChange: (newValue, cell) => {
|
|
2889
|
+
if (newValue === undefined) {
|
|
2890
|
+
return;
|
|
2891
|
+
}
|
|
2892
|
+
updateRow(cell.row.original.name, { status: newValue });
|
|
2893
|
+
},
|
|
2894
|
+
},
|
|
2895
|
+
},
|
|
2896
|
+
minSize: 200,
|
|
2897
|
+
size: 240,
|
|
2898
|
+
}),
|
|
2899
|
+
];
|
|
2900
|
+
const table = useReactTable({
|
|
2901
|
+
autoResetPageIndex: false,
|
|
2902
|
+
columnResizeMode: 'onChange',
|
|
2903
|
+
columns: COMMON_COLUMNS,
|
|
2904
|
+
data: data,
|
|
2905
|
+
defaultColumn: {
|
|
2906
|
+
maxSize: 800,
|
|
2907
|
+
minSize: 80,
|
|
2908
|
+
},
|
|
2909
|
+
enableSorting: true,
|
|
2910
|
+
getCoreRowModel: getCoreRowModel(),
|
|
2911
|
+
getPaginationRowModel: getPaginationRowModel(),
|
|
2912
|
+
getSortedRowModel: getSortedRowModel(),
|
|
2913
|
+
initialState: {
|
|
2914
|
+
pagination: {
|
|
2915
|
+
pageSize: 5,
|
|
2916
|
+
},
|
|
2917
|
+
},
|
|
2918
|
+
});
|
|
2919
|
+
|
|
2920
|
+
return (
|
|
2921
|
+
<div className="n-w-full n-bg-light-neutral-text-weakest">
|
|
2922
|
+
<DataGrid
|
|
2923
|
+
isKeyboardNavigable={true}
|
|
2924
|
+
isResizable={true}
|
|
2925
|
+
styling={{
|
|
2926
|
+
borderStyle: 'all-sides',
|
|
2927
|
+
hasZebraStriping: false,
|
|
2928
|
+
}}
|
|
2929
|
+
tableInstance={table}
|
|
2930
|
+
/>
|
|
2931
|
+
</div>
|
|
2932
|
+
);
|
|
2933
|
+
};
|
|
2934
|
+
|
|
2935
|
+
export default Component;
|
|
2936
|
+
```
|
|
2937
|
+
|
|
2938
|
+
### Datagrid Virtualization
|
|
2939
|
+
|
|
2940
|
+
```tsx
|
|
2941
|
+
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';
|
|
2942
|
+
|
|
2943
|
+
import { DataGrid } from '@neo4j-ndl/react';
|
|
2944
|
+
import {
|
|
2945
|
+
createColumnHelper,
|
|
2946
|
+
getCoreRowModel,
|
|
2947
|
+
getPaginationRowModel,
|
|
2948
|
+
getSortedRowModel,
|
|
2949
|
+
type Table as DataGridType,
|
|
2950
|
+
useReactTable,
|
|
2951
|
+
} from '@tanstack/react-table';
|
|
2952
|
+
import { useVirtualizer, type Virtualizer } from '@tanstack/react-virtual';
|
|
2953
|
+
import { type RefObject, useMemo, useRef } from 'react';
|
|
2954
|
+
|
|
2955
|
+
import { type CommonProps } from '../../_common/types';
|
|
2956
|
+
|
|
2957
|
+
type TestDataFormat = {
|
|
2958
|
+
name: string;
|
|
2959
|
+
age: number;
|
|
2960
|
+
cypherCommand: string;
|
|
2961
|
+
};
|
|
2962
|
+
|
|
2963
|
+
const TABLE_DEMO_DATA: TestDataFormat[] = Array.from(
|
|
2964
|
+
{ length: 1000 },
|
|
2965
|
+
(_, i) => ({
|
|
2966
|
+
age: Math.floor(Math.random() * 100),
|
|
2967
|
+
cypherCommand: `MATCH (n) RETURN n LIMIT ${i + 1}`,
|
|
2968
|
+
name: `Person ${i + 1}`,
|
|
2969
|
+
}),
|
|
2970
|
+
);
|
|
2971
|
+
|
|
2972
|
+
const BodyCurryingFunction =
|
|
2973
|
+
(
|
|
2974
|
+
rowVirtualizer: Virtualizer<HTMLDivElement, Element>,
|
|
2975
|
+
ref: RefObject<HTMLDivElement | null>,
|
|
2976
|
+
table: DataGridType<TestDataFormat>,
|
|
2977
|
+
) =>
|
|
2978
|
+
// eslint-disable-next-line react/display-name
|
|
2979
|
+
({
|
|
2980
|
+
style,
|
|
2981
|
+
className,
|
|
2982
|
+
...props
|
|
2983
|
+
}: CommonProps<'div', { children?: React.ReactNode }>) => {
|
|
2984
|
+
const { rows } = table.getSortedRowModel();
|
|
2985
|
+
rowVirtualizer.setOptions({
|
|
2986
|
+
...rowVirtualizer.options,
|
|
2987
|
+
count: rows.length,
|
|
2988
|
+
});
|
|
2989
|
+
const items = rowVirtualizer.getVirtualItems();
|
|
2990
|
+
|
|
2991
|
+
return (
|
|
2992
|
+
<div ref={ref} className="n-overflow-auto n-h-[290px]">
|
|
2993
|
+
<DataGrid.Body
|
|
2994
|
+
{...props}
|
|
2995
|
+
className={className}
|
|
2996
|
+
style={{
|
|
2997
|
+
...style,
|
|
2998
|
+
height: `${rowVirtualizer.getTotalSize()}px`,
|
|
2999
|
+
position: 'relative',
|
|
3000
|
+
}}
|
|
3001
|
+
>
|
|
3002
|
+
{items.map((row: { index: number; size: number; start: number }) => {
|
|
3003
|
+
const foundRow = rows[row.index];
|
|
3004
|
+
if (foundRow === undefined) {
|
|
3005
|
+
return null;
|
|
3006
|
+
}
|
|
3007
|
+
|
|
3008
|
+
return (
|
|
3009
|
+
<DataGrid.BodyRow
|
|
3010
|
+
key={`table-row-${row.index}`}
|
|
3011
|
+
row={foundRow}
|
|
3012
|
+
style={{
|
|
3013
|
+
height: `${row.size}px`,
|
|
3014
|
+
left: 0,
|
|
3015
|
+
position: 'absolute',
|
|
3016
|
+
top: 0,
|
|
3017
|
+
transform: `translateY(${row.start}px)`,
|
|
3018
|
+
width: '100%',
|
|
3019
|
+
}}
|
|
3020
|
+
/>
|
|
3021
|
+
);
|
|
3022
|
+
})}
|
|
3023
|
+
</DataGrid.Body>
|
|
3024
|
+
</div>
|
|
3025
|
+
);
|
|
3026
|
+
};
|
|
3027
|
+
|
|
3028
|
+
/** Repeats array `n` times */
|
|
3029
|
+
const repeat = (arr: unknown[], n: number) => Array(n).fill(arr).flat();
|
|
3030
|
+
|
|
3031
|
+
const columnHelper = createColumnHelper<TestDataFormat>();
|
|
3032
|
+
|
|
3033
|
+
const columns = [
|
|
3034
|
+
columnHelper.accessor('name', {
|
|
3035
|
+
cell: (info) => info.getValue(),
|
|
3036
|
+
footer: (info) => info.column.id,
|
|
3037
|
+
header: () => 'Name',
|
|
3038
|
+
maxSize: 300,
|
|
3039
|
+
minSize: 180,
|
|
3040
|
+
size: 220,
|
|
3041
|
+
}),
|
|
3042
|
+
columnHelper.accessor('age', {
|
|
3043
|
+
cell: (info) => info.renderValue(),
|
|
3044
|
+
footer: (info) => info.column.id,
|
|
3045
|
+
header: () => 'Age',
|
|
3046
|
+
minSize: 80,
|
|
3047
|
+
}),
|
|
3048
|
+
columnHelper.accessor('cypherCommand', {
|
|
3049
|
+
cell: (info) => info.getValue(),
|
|
3050
|
+
footer: (info) => info.column.id,
|
|
3051
|
+
header: () => 'Cypher Command',
|
|
3052
|
+
minSize: 400,
|
|
3053
|
+
size: 400,
|
|
3054
|
+
}),
|
|
3055
|
+
];
|
|
3056
|
+
|
|
3057
|
+
const ExtendedProps = {
|
|
3058
|
+
columnResizeMode: 'onChange' as const,
|
|
3059
|
+
columns,
|
|
3060
|
+
data: [...repeat(TABLE_DEMO_DATA, 500)],
|
|
3061
|
+
debugColumns: true,
|
|
3062
|
+
debugHeaders: true,
|
|
3063
|
+
debugTable: true,
|
|
3064
|
+
defaultColumn: {
|
|
3065
|
+
maxSize: 800,
|
|
3066
|
+
minSize: 80,
|
|
3067
|
+
},
|
|
3068
|
+
enableSorting: false,
|
|
3069
|
+
getCoreRowModel: getCoreRowModel(),
|
|
3070
|
+
getPaginationRowModel: getPaginationRowModel(),
|
|
3071
|
+
};
|
|
3072
|
+
|
|
3073
|
+
const Component = () => {
|
|
3074
|
+
const scrollingParentRef = useRef<HTMLDivElement | null>(null);
|
|
3075
|
+
|
|
3076
|
+
const rowVirtualizer = useVirtualizer({
|
|
3077
|
+
count: ExtendedProps.data.length,
|
|
3078
|
+
estimateSize: () => 40,
|
|
3079
|
+
getScrollElement: () => scrollingParentRef.current,
|
|
3080
|
+
// 40 is the size for a basic data type row so fallback to that
|
|
3081
|
+
overscan: 15,
|
|
3082
|
+
});
|
|
3083
|
+
|
|
3084
|
+
const table = useReactTable({
|
|
3085
|
+
...ExtendedProps,
|
|
3086
|
+
enableSorting: true,
|
|
3087
|
+
getSortedRowModel: getSortedRowModel(),
|
|
3088
|
+
initialState: {
|
|
3089
|
+
pagination: {
|
|
3090
|
+
pageSize: ExtendedProps.data.length,
|
|
3091
|
+
},
|
|
3092
|
+
},
|
|
3093
|
+
});
|
|
3094
|
+
|
|
3095
|
+
const CustomBody = useMemo(() => {
|
|
3096
|
+
return BodyCurryingFunction(rowVirtualizer, scrollingParentRef, table);
|
|
3097
|
+
}, [rowVirtualizer, scrollingParentRef, table]);
|
|
3098
|
+
|
|
3099
|
+
return (
|
|
3100
|
+
<div
|
|
3101
|
+
className="n-w-full n-bg-light-neutral-text-weakest"
|
|
3102
|
+
style={{ height: '400px' }}
|
|
3103
|
+
>
|
|
3104
|
+
<DataGrid
|
|
3105
|
+
components={{
|
|
3106
|
+
Body: CustomBody,
|
|
3107
|
+
PaginationNumericButton: null,
|
|
3108
|
+
RowsPerPage: null,
|
|
3109
|
+
}}
|
|
3110
|
+
isResizable={false}
|
|
3111
|
+
tableInstance={table}
|
|
3112
|
+
rootProps={{
|
|
3113
|
+
className: 'n-h-full',
|
|
3114
|
+
}}
|
|
3115
|
+
/>
|
|
3116
|
+
</div>
|
|
3117
|
+
);
|
|
3118
|
+
};
|
|
3119
|
+
|
|
3120
|
+
export default Component;
|
|
3121
|
+
```
|