@sio-group/ui-datatable 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +429 -0
  3. package/dist/index.cjs +647 -0
  4. package/dist/index.d.cts +83 -0
  5. package/dist/index.d.ts +83 -0
  6. package/dist/index.js +620 -0
  7. package/dist/styles/index.css +154 -0
  8. package/dist/styles/index.css.map +1 -0
  9. package/package.json +44 -0
  10. package/src/assets/scss/index.scss +170 -0
  11. package/src/assets/scss/tokens/_color.scss +19 -0
  12. package/src/assets/scss/tokens/_datatable.scss +10 -0
  13. package/src/components/ActionCell.tsx +88 -0
  14. package/src/components/DataTable.tsx +85 -0
  15. package/src/components/DataTableBody.tsx +34 -0
  16. package/src/components/DataTableControls.tsx +35 -0
  17. package/src/components/DataTableHeader.tsx +59 -0
  18. package/src/components/DefaultSortIcon.tsx +13 -0
  19. package/src/components/TableCell.tsx +17 -0
  20. package/src/components/cell-types/BooleanCell.tsx +29 -0
  21. package/src/components/cell-types/DateCell.tsx +28 -0
  22. package/src/components/cell-types/EmptyCell.tsx +3 -0
  23. package/src/components/cell-types/InlineInputCell.tsx +129 -0
  24. package/src/hooks/useDataTable.ts +113 -0
  25. package/src/index.ts +14 -0
  26. package/src/types/action-cell-props.d.ts +9 -0
  27. package/src/types/action-menu.d.ts +15 -0
  28. package/src/types/column.d.ts +10 -0
  29. package/src/types/data-table-body-props.d.ts +16 -0
  30. package/src/types/data-table-header-props.d.ts +11 -0
  31. package/src/types/data-table-props.d.ts +32 -0
  32. package/src/types/entity.d.ts +4 -0
  33. package/src/types/form-field.d.ts +8 -0
  34. package/src/types/index.ts +11 -0
  35. package/src/types/pagination-meta.d.ts +7 -0
  36. package/src/types/sort-state.d.ts +6 -0
  37. package/src/types/table-cell-props.d.ts +9 -0
  38. package/src/types/use-data-table-props.d.ts +14 -0
  39. package/src/types/use-data-table-return.d.ts +14 -0
  40. package/src/utils/is-pill-value.ts +7 -0
  41. package/src/utils/render-object.tsx +18 -0
  42. package/src/utils/render-value.tsx +89 -0
  43. package/tsconfig.json +17 -0
  44. package/tsup.config.ts +8 -0
  45. package/vitest.config.ts +9 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,16 @@
1
+ # @sio-group/ui-datatable
2
+
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - e606fc1: Feat: added datatable
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [430252c]
12
+ - Updated dependencies [a7139b5]
13
+ - Updated dependencies [e606fc1]
14
+ - @sio-group/ui-core@0.4.0
15
+ - @sio-group/ui-modal@0.4.0
16
+ - @sio-group/ui-pagination@0.1.1
package/README.md ADDED
@@ -0,0 +1,429 @@
1
+ # @sio-group/ui-datatable
2
+
3
+ [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/ISC)
4
+ ![npm](https://img.shields.io/npm/v/@sio-group/ui-datatable)
5
+ ![TypeScript](https://img.shields.io/badge/types-Yes-brightgreen)
6
+
7
+ A flexible and accessible datatable component for React applications.
8
+ Supports both client-side and server-side data handling through a single unified API.
9
+
10
+ ---
11
+
12
+ ## Features
13
+
14
+ * 🔄 **Client-side & server-side** – one component for both local and API-driven data
15
+ * 🔍 **Search** – built-in search bar, client-side filtering or server-side delegation
16
+ * ↕️ **Sorting** – per-column sort with custom sort icons
17
+ * 📄 **Pagination** – via `@sio-group/ui-pagination`
18
+ * ✏️ **Inline editing** – editable cells with text input and select support
19
+ * 🎯 **Action menu** – inline or dropdown row actions, fully controlled by the consumer
20
+ * 🎨 **Custom styling** – supports custom classes and inline styles
21
+ * ♿ **Accessible** – ARIA attributes on sort controls and search input
22
+ * 📦 **TypeScript** – full generic type support
23
+
24
+ ---
25
+
26
+ ## Installation
27
+
28
+ ```bash
29
+ npm install @sio-group/ui-datatable
30
+ ```
31
+
32
+ ### Peer dependencies
33
+
34
+ This package requires:
35
+
36
+ * `react` ^19.0.0
37
+ * `react-dom` ^19.0.0
38
+
39
+ ### Dependencies
40
+
41
+ This package includes:
42
+
43
+ * `@sio-group/ui-pagination`
44
+ * `@sio-group/ui-core`
45
+
46
+ ---
47
+
48
+ ## Styling
49
+
50
+ ```js
51
+ import "@sio-group/ui-datatable/sio-datatable-style.css";
52
+ import "@sio-group/ui-core/sio-core-style.css";
53
+ import "@sio-group/ui-pagination/sio-pagination-style.css";
54
+ ```
55
+
56
+ ---
57
+
58
+ ## Quick Example
59
+
60
+ ```tsx
61
+ import { DataTable } from "@sio-group/ui-datatable";
62
+
63
+ interface User {
64
+ id: number;
65
+ name: string;
66
+ email: string;
67
+ }
68
+
69
+ const columns = [
70
+ { name: 'name', label: 'Naam', sort: true },
71
+ { name: 'email', label: 'E-mail', format: 'email' as const },
72
+ ];
73
+
74
+ function Example() {
75
+ return (
76
+ <DataTable<User>
77
+ columns={columns}
78
+ data={users}
79
+ clientPageSize={20}
80
+ clientSearchKeys={['name', 'email']}
81
+ />
82
+ );
83
+ }
84
+ ```
85
+
86
+ ---
87
+
88
+ ## Client-side vs Server-side
89
+
90
+ The datatable has two modes. The mode is determined automatically based on the props you provide — there is no explicit `mode` prop.
91
+
92
+ | | Client-side | Server-side |
93
+ |-----------------|-------------------------|---------------------------|
94
+ | Data | Full dataset via `data` | Current page via `data` |
95
+ | Pagination info | Calculated internally | Provided via `pagination` |
96
+ | Search | Filtered internally | Delegated via `onSearch` |
97
+ | Sort | Sorted internally | Delegated via `onSort` |
98
+
99
+ ### Client-side
100
+
101
+ Pass the full dataset via `data`. The datatable handles filtering, sorting and pagination internally.
102
+
103
+ ```tsx
104
+ <DataTable<User>
105
+ columns={columns}
106
+ data={allUsers}
107
+ clientPageSize={20}
108
+ clientSearchKeys={['name', 'email']}
109
+ />
110
+ ```
111
+
112
+ - **Search** is shown when `clientSearchKeys` is provided.
113
+ - **Pagination** is shown when `clientPageSize` is provided.
114
+ - **Sorting** is active per column when `column.sort` is `true`.
115
+
116
+ ### Server-side
117
+
118
+ Pass the current page of data via `data` and provide a `pagination` object with the metadata from your API response. The datatable delegates search, sort and pagination back to you via callbacks.
119
+
120
+ ```tsx
121
+ <DataTable<User>
122
+ columns={columns}
123
+ data={pagedUsers}
124
+ pagination={{
125
+ currentPage: 1,
126
+ pageCount: 12,
127
+ total: 240,
128
+ from: 1,
129
+ to: 20,
130
+ }}
131
+ onPaginate={(page) => fetchPage(page)}
132
+ onSearch={(query) => setSearch(query)}
133
+ onSort={(sort) => setSort(sort)}
134
+ searchValue={search}
135
+ sortValue={sort}
136
+ />
137
+ ```
138
+
139
+ - **Search** is shown when `onSearch` is provided.
140
+ - **Pagination** is shown when `onPaginate` is provided.
141
+ - **Sorting** is active per column when `column.sort` is `true`.
142
+
143
+ ---
144
+
145
+ ## Action Menu
146
+
147
+ Row actions are fully controlled by the consumer via `onClick`. The datatable has no knowledge of navigation, deletion or confirmation dialogs.
148
+
149
+ ```tsx
150
+ const actionMenu = {
151
+ type: 'dropdown' as const,
152
+ actions: [
153
+ {
154
+ name: 'detail',
155
+ label: 'Bekijken',
156
+ icon: <EyeIcon />,
157
+ onClick: (item) => navigate(`/users/${item.id}`),
158
+ },
159
+ {
160
+ name: 'edit',
161
+ label: 'Wijzigen',
162
+ icon: <PencilIcon />,
163
+ onClick: (item) => navigate(`/users/${item.id}/wijzigen`),
164
+ },
165
+ {
166
+ name: 'delete',
167
+ label: 'Verwijderen',
168
+ icon: <TrashIcon />,
169
+ onClick: (item) => setConfirmItem(item),
170
+ },
171
+ ],
172
+ };
173
+
174
+ <DataTable<User>
175
+ columns={columns}
176
+ data={users}
177
+ actionMenu={actionMenu}
178
+ renderMenuIcon={() => <EllipsisIcon />}
179
+ />
180
+ ```
181
+
182
+ The `type` prop controls how the actions are rendered:
183
+
184
+ | `type` | Description |
185
+ |--------------|----------------------------------------------|
186
+ | `'inline'` | All action buttons shown directly in the row |
187
+ | `'dropdown'` | Actions hidden behind a trigger button |
188
+
189
+ ---
190
+
191
+ ## Inline Editing
192
+
193
+ Mark columns as editable by providing a `formFields` array. Each entry maps a column by `name` to an input type.
194
+
195
+ ```tsx
196
+ const formFields = [
197
+ { name: 'name', type: 'text', required: true },
198
+ {
199
+ name: 'status',
200
+ type: 'select',
201
+ options: [
202
+ { label: 'Actief', value: 'active' },
203
+ { label: 'Inactief', value: 'inactive' },
204
+ ],
205
+ },
206
+ ];
207
+
208
+ <DataTable<User>
209
+ columns={columns}
210
+ data={users}
211
+ formFields={formFields}
212
+ onUpdate={(id, values) => updateUser(id, values)}
213
+ />
214
+ ```
215
+
216
+ An edit icon appears next to the cell value. Clicking it opens an input. Changes are confirmed with ✓ or cancelled with ✗. Pressing `Escape` also cancels the edit.
217
+
218
+ `onUpdate` is called with the row `id` and a partial object containing the changed field:
219
+
220
+ ```
221
+ onUpdate={(id, values) => {
222
+ // id: 1
223
+ // values: { name: 'Sophia' }
224
+ }}
225
+ ```
226
+
227
+ ---
228
+
229
+ ## Column Formats
230
+
231
+ The `format` prop on a column controls how the cell value is rendered.
232
+
233
+ | `format` | Input type | Renders |
234
+ |-------------------|------------------------------------|----------------------------------------------------|
235
+ | `'date'` | `string \| Date` | Localized date (`nl-BE`) |
236
+ | `'datetime'` | `string \| Date` | Localized date + time (`nl-BE`) |
237
+ | `'boolean'` | `boolean` | Clickable ✓/✗, calls `onUpdate` |
238
+ | `'button'` | `boolean` | Same as boolean, rendered as a primary button |
239
+ | `'pill'` | `{ status: Color, label: string }` | `<Pill>` from `@sio-group/ui-core` |
240
+ | `'email'` | `string` | `mailto:` link |
241
+ | `{ key: string }` | `object \| object[]` | Renders the value of the given key from the object |
242
+ | _(none)_ | `string \| number` | Raw value |
243
+
244
+ For objects and arrays without a `format`, all key-value pairs are rendered stacked.
245
+
246
+ ```
247
+ // Render only the `name` key from an object or array of objects
248
+ { name: 'role', label: 'Rol', format: { key: 'name' } }
249
+ ```
250
+
251
+ ---
252
+
253
+ ## Sort Icons
254
+
255
+ Provide a custom sort icon via `renderSortIcon`:
256
+
257
+ ```
258
+ <DataTable
259
+ ...
260
+ renderSortIcon={(direction, active) => (
261
+ <MyIcon
262
+ name={direction === 'asc' ? 'sort-up' : 'sort-down'}
263
+ style={{ opacity: active ? 1 : 0.3 }}
264
+ />
265
+ )}
266
+ />
267
+ ```
268
+
269
+ ---
270
+
271
+ ## API Reference
272
+
273
+ ### DataTable Props
274
+
275
+ | Prop | Type | Default | Description |
276
+ |--------------------|------------------------------------------------------------|-------------------|----------------------------------------------------------------------------|
277
+ | `columns` | `Column<T>[]` | — | Column definitions |
278
+ | `data` | `T[]` | — | Data to display — full dataset (client-side) or current page (server-side) |
279
+ | `pagination` | `PaginationMeta` | — | Server pagination metadata. Presence determines server-side mode |
280
+ | `onPaginate` | `(page: number) => void` | — | Called on page change. Shows pagination when provided (server-side) |
281
+ | `onSearch` | `(query: string) => void` | — | Called on search input. Shows search bar when provided (server-side) |
282
+ | `onSort` | `(sort: SortState \| null) => void` | — | Called on column sort (server-side) |
283
+ | `searchValue` | `string` | — | Controlled search value (server-side) |
284
+ | `sortValue` | `SortState \| null` | — | Controlled sort state (server-side) |
285
+ | `clientPageSize` | `number` | — | Page size for client-side pagination. Shows pagination when provided |
286
+ | `clientSearchKeys` | `(keyof T)[]` | — | Fields to search on client-side. Shows search bar when provided |
287
+ | `entity` | `Entity` | — | Used for search bar placeholder text |
288
+ | `actionMenu` | `ActionMenu<T>` | — | Row action configuration |
289
+ | `renderMenuIcon` | `() => ReactNode` | — | Custom dropdown trigger icon |
290
+ | `onUpdate` | `(id: string \| number, values: Partial<T>) => void` | — | Called on inline edit save or boolean toggle |
291
+ | `formFields` | `FormField[]` | — | Makes columns inline-editable |
292
+ | `renderSortIcon` | `(direction: SortDirection, active: boolean) => ReactNode` | — | Custom sort indicator |
293
+ | `emptyMessage` | `string` | `'Nog geen data'` | Message shown when data is empty |
294
+ | `striped` | `boolean` | `false` | Alternating row background color |
295
+ | `hover` | `boolean` | `false` | Highlight row on mouse hover |
296
+ | `style` | `CSSProperties` | — | Inline styles for the table wrapper |
297
+
298
+ ---
299
+
300
+ ### Column
301
+
302
+ | Prop | Type | Default | Description |
303
+ |-------------|-----------------------------------------------------------------------------------------|---------|-------------------------------------------|
304
+ | `name` | `keyof T` | — | Maps to a key on the data object |
305
+ | `label` | `string` | — | Column header label |
306
+ | `sort` | `boolean` | — | Enables sorting for this column |
307
+ | `format` | `'boolean' \| 'button' \| 'date' \| 'datetime' \| 'pill' \| 'email' \| { key: string }` | — | Cell render format |
308
+ | `className` | `string` | — | Additional CSS class on `<th>` and `<td>` |
309
+ | `style` | `CSSProperties` | — | Inline styles on `<th>` and `<td>` |
310
+
311
+ ---
312
+
313
+ ## Utility Classes
314
+
315
+ The following CSS classes can be applied to a column via the `className` prop.
316
+
317
+ | Class | Description |
318
+ |-------------|----------------------------------------------------------------|
319
+ | `center` | Centers the cell content |
320
+ | `right` | Aligns the cell content to the right |
321
+ | `linebreak` | Renders newlines in the cell value (`\n` becomes a line break) |
322
+ | `no-style` | Removes link styling — underline on hover only |
323
+
324
+ ```tsx
325
+ const columns = [
326
+ { name: 'amount', label: 'Bedrag', className: 'right' },
327
+ { name: 'notes', label: 'Notities', className: 'linebreak' },
328
+ ];
329
+ ```
330
+
331
+ `no-style` is useful on columns with `format: 'email' or other link-based formats where you want to suppress the default link appearance.
332
+
333
+ ### ActionMenu
334
+
335
+ | Prop | Type | Description |
336
+ |-----------|--------------------------|--------------------------|
337
+ | `type` | `'inline' \| 'dropdown'` | How actions are rendered |
338
+ | `actions` | `Action<T>[]` | List of row actions |
339
+
340
+ ### Action
341
+
342
+ | Prop | Type | Description |
343
+ |-----------|---------------------|-------------------------------|
344
+ | `name` | `string` | Identifier for the action |
345
+ | `label` | `string` | Display label |
346
+ | `icon` | `ReactNode` | Optional icon |
347
+ | `onClick` | `(item: T) => void` | Called with the full row item |
348
+
349
+ ---
350
+
351
+ ### FormField
352
+
353
+ | Prop | Type | Default | Description |
354
+ |------------|--------------------------------------------------|---------|--------------------------------|
355
+ | `name` | `string` | — | Maps to a column by name |
356
+ | `type` | `'text' \| 'select' \| 'radio'` | — | Input type |
357
+ | `options` | `string[] \| { label: string; value: string }[]` | — | Options for select and radio |
358
+ | `required` | `boolean` | — | Prevents saving an empty value |
359
+
360
+ ---
361
+
362
+ ### PaginationMeta
363
+
364
+ | Prop | Type | Description |
365
+ |---------------|----------|-----------------------------|
366
+ | `currentPage` | `number` | Active page (1-based) |
367
+ | `pageCount` | `number` | Total number of pages |
368
+ | `total` | `number` | Total number of items |
369
+ | `from` | `number` | First item on current page |
370
+ | `to` | `number` | Last item on current page |
371
+
372
+ ---
373
+
374
+ ### SortState
375
+
376
+ | Prop | Type | Description |
377
+ |-------------|-------------------|---------------------|
378
+ | `name` | `keyof T` | Column being sorted |
379
+ | `direction` | `'asc' \| 'desc'` | Sort direction |
380
+
381
+ ---
382
+
383
+ ### Entity
384
+
385
+ | Prop | Type | Description |
386
+ |---------|----------|----------------------------------------------------------------|
387
+ | `name` | `string` | Machine name, e.g. `'users'` |
388
+ | `label` | `string` | Display name, e.g. `'Gebruikers'` — used in search placeholder |
389
+
390
+ ---
391
+
392
+ ## TypeScript
393
+
394
+ This package includes full TypeScript definitions. All main types are exported.
395
+
396
+ ```ts
397
+ import { DataTable } from "@sio-group/ui-datatable";
398
+ import type {
399
+ DataTableProps,
400
+ Column,
401
+ Entity,
402
+ Action,
403
+ ActionMenu,
404
+ ActionMenuType,
405
+ FormField,
406
+ FormFieldType,
407
+ SortState,
408
+ SortDirection,
409
+ } from "@sio-group/ui-datatable";
410
+ ```
411
+
412
+ ---
413
+
414
+ ## Browser Support
415
+
416
+ Supports all modern browsers that support:
417
+
418
+ * ES6 modules
419
+ * React 19+
420
+
421
+ ---
422
+
423
+ ## Contributing
424
+
425
+ Please read [CONTRIBUTING.md](../../CONTRIBUTING.md) for details on our code of conduct and the process for submitting pull requests.
426
+
427
+ ## License
428
+
429
+ This project is licensed under the ISC License - see the [LICENSE](../../LICENSE) file for details.