@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
@@ -0,0 +1,154 @@
1
+ :root {
2
+ --sio-color-white: #ffffff;
3
+ --sio-color-black: #000000;
4
+ --sio-color-gray: #655f5d;
5
+ --sio-color-light-gray: #c6c6c6;
6
+ --sio-color-primary: #3B82F6;
7
+ --sio-color-success: #10B981;
8
+ --sio-color-error: #EF4444;
9
+ --sio-color-warning: #F59E0B;
10
+ --sio-color-info: #06B6D4;
11
+ --sio-datatable-border: var(--sio-color-light-gray);
12
+ --sio-datatable-striped: #f9fafb;
13
+ --sio-datatable-hover: #f1f5f9;
14
+ --sio-form-border: var(--sio-color-light-gray);
15
+ --sio-form-bg: var(--sio-color-white);
16
+ }
17
+
18
+ :root {
19
+ --sio-card-border-radius: 3px;
20
+ --sio-card-padding-x: 3px;
21
+ --sio-card-padding-y: 3px;
22
+ --sio-form-border-radius: 3px;
23
+ --sio-form-padding-x: 10px;
24
+ --sio-form-padding-y: 5px;
25
+ }
26
+
27
+ .datatable__controls {
28
+ display: flex;
29
+ justify-content: flex-end;
30
+ }
31
+ .datatable__wrapper {
32
+ width: 100%;
33
+ overflow-x: auto;
34
+ }
35
+ .datatable .form-field__control {
36
+ margin: 0 0 15px 0;
37
+ width: 250px;
38
+ border: 1px solid var(--sio-form-border);
39
+ background: var(--sio-form-bg);
40
+ position: relative;
41
+ font-size: 1em;
42
+ padding: 0;
43
+ display: flex;
44
+ justify-content: space-between;
45
+ border-radius: var(--sio-form-border-radius);
46
+ transition: color 300ms ease;
47
+ }
48
+ .datatable .form-field__control input {
49
+ display: block;
50
+ color: var(--sio-color-gray);
51
+ width: 100%;
52
+ background: transparent;
53
+ border: none;
54
+ padding: var(--sio-form-padding-y) var(--sio-form-padding-x);
55
+ resize: vertical;
56
+ transition: color 300ms ease;
57
+ }
58
+ .datatable .form-field__control input:focus {
59
+ outline: none;
60
+ box-shadow: none;
61
+ }
62
+ .datatable .form-field__control input::placeholder {
63
+ color: var(--sio-color-light-gray);
64
+ font-weight: 200;
65
+ font-size: 1em;
66
+ }
67
+ .datatable .form-field.form-field--has-value .form-field__control {
68
+ color: var(--sio-color-black);
69
+ border-color: var(--sio-color-primary);
70
+ }
71
+ .datatable .form-field.form-field--focused .form-field__control {
72
+ color: var(--sio-color-black);
73
+ border-color: var(--sio-color-primary);
74
+ box-shadow: inset 2px 0 10px 0 rgba(var(--sio-color-primary-rgb), 0.3);
75
+ }
76
+ .datatable table {
77
+ width: 100%;
78
+ border-collapse: collapse;
79
+ border: none;
80
+ border-spacing: 0;
81
+ }
82
+ .datatable table tr {
83
+ vertical-align: middle;
84
+ white-space: nowrap;
85
+ }
86
+ .datatable table tr:last-child td {
87
+ border-width: 2px;
88
+ }
89
+ .datatable table thead th {
90
+ border-width: 2px;
91
+ white-space: nowrap;
92
+ }
93
+ .datatable table td,
94
+ .datatable table th {
95
+ text-align: left;
96
+ padding: var(--sio-form-padding-y) var(--sio-form-padding-x);
97
+ border-bottom: 1px solid var(--sio-datatable-border);
98
+ }
99
+ .datatable table tfoot td,
100
+ .datatable table tfoot th {
101
+ border-top: 2px solid;
102
+ font-weight: bold;
103
+ }
104
+ .datatable table.striped tbody tr:nth-child(even) {
105
+ background-color: var(--sio-datatable-striped);
106
+ }
107
+ .datatable table.hover tbody tr:hover {
108
+ background-color: var(--sio-datatable-hover);
109
+ }
110
+ .datatable .sort {
111
+ cursor: pointer;
112
+ }
113
+ .datatable .sort > span {
114
+ display: flex;
115
+ align-items: center;
116
+ gap: 5px;
117
+ }
118
+ .datatable .sort > span .label {
119
+ display: inline-block;
120
+ overflow: hidden;
121
+ text-overflow: ellipsis;
122
+ max-width: 150px;
123
+ white-space: nowrap;
124
+ }
125
+ .datatable .sort > span .icons {
126
+ position: relative;
127
+ display: inline-flex;
128
+ flex-direction: column;
129
+ margin-left: 2.5px;
130
+ gap: 0;
131
+ line-height: 1;
132
+ font-size: 0.7em;
133
+ }
134
+ .datatable .center {
135
+ text-align: center;
136
+ }
137
+ .datatable .right {
138
+ text-align: right;
139
+ }
140
+ .datatable .linebreak {
141
+ white-space: pre-line;
142
+ }
143
+ .datatable .no-style {
144
+ text-decoration: none;
145
+ color: inherit;
146
+ }
147
+ .datatable .no-style:hover {
148
+ text-decoration: underline;
149
+ }
150
+ .datatable .empty-state {
151
+ margin: 25px;
152
+ }
153
+
154
+ /*# sourceMappingURL=index.css.map */
@@ -0,0 +1 @@
1
+ {"version":3,"sourceRoot":"","sources":["../../src/assets/scss/tokens/_color.scss","../../src/assets/scss/tokens/_datatable.scss","../../src/assets/scss/index.scss"],"names":[],"mappings":"AAAA;EACE;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;EACA;EAEA;EACA;EACA;EAEA;EACA;;;ACjBF;EACE;EAEA;EACA;EAEA;EACA;EACA;;;ACJA;EACE;EACA;;AAGF;EACE;EACA;;AAIA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAKN;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAEA;EACE;;AAKF;EACE;EACA;;AAIJ;AAAA;EAEE;EACA;EACA;;AAIA;AAAA;EAEE;EACA;;AAIJ;EACE;;AAGF;EACE;;AAIJ;EACE;;AAEA;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAKN;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;EACA;;AAEA;EACE;;AAIJ;EACE","file":"index.css"}
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@sio-group/ui-datatable",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "style": "./dist/sio-datatable-style.css",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "require": "./dist/index.cjs"
15
+ },
16
+ "./sio-datatable-style.css": "./dist/styles/index.css"
17
+ },
18
+ "scripts": {
19
+ "build": "npm run build:js && npm run build:css",
20
+ "build:css": "sass src/assets/scss:dist/styles dist",
21
+ "build:js": "tsup src/index.ts --format cjs,esm --dts --tsconfig tsconfig.json",
22
+ "test": "vitest",
23
+ "test:ui": "vitest --ui"
24
+ },
25
+ "keywords": [],
26
+ "author": "",
27
+ "license": "ISC",
28
+ "description": "",
29
+ "peerDependencies": {
30
+ "react": "^19",
31
+ "react-dom": "^19"
32
+ },
33
+ "dependencies": {
34
+ "@sio-group/ui-core": "0.4.0",
35
+ "@sio-group/ui-pagination": "0.1.1",
36
+ "@sio-group/ui-modal": "0.4.0"
37
+ },
38
+ "devDependencies": {
39
+ "@vitejs/plugin-react": "^4",
40
+ "sass": "^1.97.3",
41
+ "typescript": "^5",
42
+ "vite": "^5"
43
+ }
44
+ }
@@ -0,0 +1,170 @@
1
+ @forward "tokens/color";
2
+ @forward "tokens/datatable";
3
+
4
+ .datatable {
5
+ &__controls {
6
+ display: flex;
7
+ justify-content: flex-end;
8
+ }
9
+
10
+ &__wrapper {
11
+ width: 100%;
12
+ overflow-x: auto;
13
+ }
14
+
15
+ .form-field {
16
+ &__control {
17
+ margin: 0 0 15px 0;
18
+ width: 250px;
19
+ border: 1px solid var(--sio-form-border);
20
+ background: var(--sio-form-bg);
21
+ position: relative;
22
+ font-size: 1em;
23
+ padding: 0;
24
+ display: flex;
25
+ justify-content: space-between;
26
+ border-radius: var(--sio-form-border-radius);
27
+ transition: color 300ms ease;
28
+
29
+ input {
30
+ display: block;
31
+ color: var(--sio-color-gray);
32
+ width: 100%;
33
+ background: transparent;
34
+ border: none;
35
+ padding: var(--sio-form-padding-y) var(--sio-form-padding-x);
36
+ resize: vertical;
37
+ transition: color 300ms ease;
38
+
39
+ &:focus {
40
+ outline: none;
41
+ box-shadow: none;
42
+ }
43
+
44
+ &::placeholder {
45
+ color: var(--sio-color-light-gray);
46
+ font-weight: 200;
47
+ font-size: 1em;
48
+ }
49
+ }
50
+ }
51
+
52
+ &.form-field--has-value .form-field__control {
53
+ color: var(--sio-color-black);
54
+ border-color: var(--sio-color-primary);
55
+ }
56
+
57
+ &.form-field--focused .form-field__control {
58
+ color: var(--sio-color-black);
59
+ border-color: var(--sio-color-primary);
60
+ box-shadow: inset 2px 0 10px 0 rgba(var(--sio-color-primary-rgb), 0.3);
61
+ }
62
+ }
63
+
64
+ table {
65
+ width: 100%;
66
+ border-collapse: collapse;
67
+ border: none;
68
+ border-spacing: 0;
69
+
70
+ tr {
71
+ vertical-align: middle;
72
+ white-space: nowrap;
73
+
74
+ &:last-child td {
75
+ border-width: 2px;
76
+ }
77
+ }
78
+
79
+ thead {
80
+ th {
81
+ border-width: 2px;
82
+ white-space: nowrap;
83
+ }
84
+ }
85
+
86
+ td,
87
+ th {
88
+ text-align: left;
89
+ padding: var(--sio-form-padding-y) var(--sio-form-padding-x);
90
+ border-bottom: 1px solid var(--sio-datatable-border);
91
+ }
92
+
93
+ tfoot {
94
+ td,
95
+ th {
96
+ border-top: 2px solid;
97
+ font-weight: bold;
98
+ }
99
+ }
100
+
101
+ &.striped tbody tr:nth-child(even) {
102
+ background-color: var(--sio-datatable-striped);
103
+ }
104
+
105
+ &.hover tbody tr:hover {
106
+ background-color: var(--sio-datatable-hover);
107
+ }
108
+ }
109
+
110
+ .sort {
111
+ cursor: pointer;
112
+
113
+ > span {
114
+ display: flex;
115
+ align-items: center;
116
+ gap: 5px;
117
+
118
+ .label {
119
+ display: inline-block;
120
+ overflow: hidden;
121
+ text-overflow: ellipsis;
122
+ max-width: 150px;
123
+ white-space: nowrap;
124
+ }
125
+
126
+ .icons {
127
+ position: relative;
128
+ display: inline-flex;
129
+ flex-direction: column;
130
+ margin-left: 2.5px;
131
+ gap: 0;
132
+ line-height: 1;
133
+ font-size: .7em;
134
+ }
135
+ }
136
+ }
137
+
138
+ .center {
139
+ text-align: center;
140
+ }
141
+
142
+ .right {
143
+ text-align: right;
144
+ }
145
+
146
+ .linebreak {
147
+ white-space: pre-line;
148
+ }
149
+
150
+ .no-style {
151
+ text-decoration: none;
152
+ color: inherit;
153
+
154
+ &:hover {
155
+ text-decoration: underline;
156
+ }
157
+ }
158
+
159
+ .empty-state {
160
+ margin: 25px;
161
+ }
162
+
163
+ .action-cell {
164
+ .action-group {}
165
+
166
+ .action-dropdown {
167
+ &__menu {}
168
+ }
169
+ }
170
+ }
@@ -0,0 +1,19 @@
1
+ :root {
2
+ --sio-color-white: #ffffff;
3
+ --sio-color-black: #000000;
4
+ --sio-color-gray: #655f5d;
5
+ --sio-color-light-gray: #c6c6c6;
6
+
7
+ --sio-color-primary: #3B82F6;
8
+ --sio-color-success: #10B981;
9
+ --sio-color-error: #EF4444;
10
+ --sio-color-warning: #F59E0B;
11
+ --sio-color-info: #06B6D4;
12
+
13
+ --sio-datatable-border: var(--sio-color-light-gray);
14
+ --sio-datatable-striped: #f9fafb;
15
+ --sio-datatable-hover: #f1f5f9;
16
+
17
+ --sio-form-border: var(--sio-color-light-gray);
18
+ --sio-form-bg: var(--sio-color-white);
19
+ }
@@ -0,0 +1,10 @@
1
+ :root {
2
+ --sio-card-border-radius: 3px;
3
+
4
+ --sio-card-padding-x: 3px;
5
+ --sio-card-padding-y: 3px;
6
+
7
+ --sio-form-border-radius: 3px;
8
+ --sio-form-padding-x: 10px;
9
+ --sio-form-padding-y: 5px;
10
+ }
@@ -0,0 +1,88 @@
1
+ import {Action, ActionCellProps} from "../types";
2
+ import {Button} from "@sio-group/ui-core";
3
+ import {useEffect, useRef, useState} from "react";
4
+
5
+ export const ActionCell = <T extends { id: string | number }> ({
6
+ actionMenu,
7
+ item,
8
+ renderMenuIcon,
9
+ }: ActionCellProps<T>) => {
10
+ const dropdownRef = useRef<HTMLDivElement>(null);
11
+ const triggerRef = useRef<HTMLElement>(null);
12
+ const [isOpen, setIsOpen] = useState(false);
13
+
14
+ const positionDropdown = () => {
15
+ if (dropdownRef.current && triggerRef.current) {
16
+ const rect: DOMRect = triggerRef.current.getBoundingClientRect();
17
+ dropdownRef.current.style.left = `${rect.left}px`;
18
+ dropdownRef.current.style.top = `${rect.top}px`;
19
+ }
20
+ }
21
+
22
+ useEffect(() => {
23
+ if (!isOpen) return;
24
+
25
+ const handleOutside = (e: MouseEvent | Event) => {
26
+ if (dropdownRef.current?.contains(e.target as Node)) return;
27
+ setIsOpen(false);
28
+ }
29
+
30
+ document.addEventListener('mousedown', handleOutside, false);
31
+ document.addEventListener('wheel', handleOutside, false);
32
+
33
+ return () => {
34
+ document.removeEventListener('mousedown', handleOutside, false);
35
+ document.removeEventListener('wheel', handleOutside, false);
36
+ }
37
+ }, [isOpen]);
38
+
39
+ const renderButton = ({ label, name, icon, onClick }: Action<T>, showLabel: boolean) => (
40
+ <Button
41
+ onClick={() => onClick(item)}
42
+ variant="secondary"
43
+ className=""
44
+ ariaLabel={label}
45
+ key={name}
46
+ >
47
+ {icon && <span>{icon}</span>}
48
+ {showLabel && <span>{label}</span>}
49
+ </Button>
50
+ )
51
+
52
+ return (
53
+ <td className="action-cel">
54
+ {actionMenu?.type === 'inline' ? (
55
+ <div className="action-group">
56
+ {actionMenu.actions.map((action: Action<T>) => renderButton(action, false))}
57
+ </div>
58
+ ) : (
59
+ <section
60
+ className="action-dropdown"
61
+ ref={triggerRef}
62
+ >
63
+ <button
64
+ className="btn--link btn--default"
65
+ aria-label="Acties"
66
+ aria-expanded={isOpen}
67
+ aria-haspopup="menu"
68
+ onClick={() => {
69
+ setIsOpen(!isOpen);
70
+ positionDropdown();
71
+ }}
72
+ >
73
+ {renderMenuIcon ? renderMenuIcon() : '⋮'}
74
+ </button>
75
+ {isOpen && (
76
+ <div
77
+ className="action-dropdown__menu"
78
+ ref={dropdownRef}
79
+ role="menu"
80
+ >
81
+ {actionMenu?.actions.map((action: Action<T>) => renderButton(action, true))}
82
+ </div>
83
+ )}
84
+ </section>
85
+ )}
86
+ </td>
87
+ );
88
+ }
@@ -0,0 +1,85 @@
1
+ import {DataTableHeader} from "./DataTableHeader";
2
+ import {useDataTable} from "../hooks/useDataTable";
3
+ import {DataTableProps} from "../types";
4
+ import {Pagination} from "@sio-group/ui-pagination";
5
+ import {DataTableBody} from "./DataTableBody";
6
+ import {DataTableControls} from "./DataTableControls";
7
+
8
+ export const DataTable = <T extends { id: string | number }>({
9
+ columns,
10
+ entity,
11
+ actionMenu,
12
+ renderMenuIcon,
13
+ onUpdate,
14
+ formFields,
15
+ renderSortIcon,
16
+ emptyMessage = 'Nog geen data',
17
+ striped = false,
18
+ hover = false,
19
+ style,
20
+ ...props
21
+ }: DataTableProps<T>) => {
22
+ const {
23
+ pagedData,
24
+ paginationMeta,
25
+ showPagination,
26
+ showSearch,
27
+ handleSearch,
28
+ handleSort,
29
+ handlePaginate,
30
+ currentSort,
31
+ currentSearch,
32
+ } = useDataTable(props);
33
+
34
+ return (
35
+ <div className="datatable" style={style}>
36
+ {showSearch && (
37
+ <DataTableControls
38
+ currentSearch={currentSearch}
39
+ handleSearch={handleSearch}
40
+ entity={entity}
41
+ />
42
+ )}
43
+
44
+ <div className="datatable__wrapper">
45
+ <table className={[striped && 'striped', hover && 'hover'].filter(Boolean).join(' ')}>
46
+ <DataTableHeader
47
+ columns={columns}
48
+ onSort={handleSort}
49
+ sortValue={currentSort}
50
+ hasActionMenu={!!actionMenu?.actions?.length}
51
+ renderSortIcon={renderSortIcon}
52
+ />
53
+ <tbody>
54
+ {pagedData.length
55
+ ? pagedData.map((item) => (
56
+ <DataTableBody
57
+ item={item}
58
+ columns={columns}
59
+ actionMenu={actionMenu}
60
+ formFields={formFields}
61
+ updateData={onUpdate}
62
+ renderMenuIcon={renderMenuIcon}
63
+ key={item.id}
64
+ />
65
+ ))
66
+ : (
67
+ <tr>
68
+ <td colSpan={columns.length + (actionMenu?.actions?.length ? 1 : 0)}>
69
+ <div className="empty-state">{emptyMessage}</div>
70
+ </td>
71
+ </tr>
72
+ )}
73
+ </tbody>
74
+ </table>
75
+ </div>
76
+
77
+ {(showPagination && paginationMeta) && (
78
+ <Pagination
79
+ {...paginationMeta}
80
+ onPaginate={handlePaginate}
81
+ />
82
+ )}
83
+ </div>
84
+ )
85
+ }
@@ -0,0 +1,34 @@
1
+ import {DataTableBodyProps} from "../types/data-table-body-props";
2
+ import {Column} from "../types";
3
+ import {ActionCell} from "./ActionCell";
4
+ import {TableCell} from "./TableCell";
5
+
6
+ export const DataTableBody = <T extends { id: string | number }> ({
7
+ item,
8
+ columns,
9
+ actionMenu,
10
+ formFields,
11
+ updateData,
12
+ renderMenuIcon,
13
+ }: DataTableBodyProps<T>) => {
14
+ return (
15
+ <tr>
16
+ {actionMenu && (
17
+ <ActionCell
18
+ actionMenu={actionMenu}
19
+ item={item}
20
+ renderMenuIcon={renderMenuIcon}
21
+ />
22
+ )}
23
+ {columns.map((column: Column<T>) => (
24
+ <TableCell
25
+ column={column}
26
+ item={item}
27
+ formFields={formFields}
28
+ updateData={updateData}
29
+ key={`${String(column.name)}-${item.id}`}
30
+ />
31
+ ))}
32
+ </tr>
33
+ )
34
+ }
@@ -0,0 +1,35 @@
1
+ import {Entity} from "../types";
2
+ import {useState} from "react";
3
+
4
+ interface DataTableControlsProps {
5
+ currentSearch?: string | null;
6
+ handleSearch: (query: string) => void;
7
+ entity?: Entity
8
+ }
9
+
10
+ export const DataTableControls = ({
11
+ currentSearch,
12
+ handleSearch,
13
+ entity,
14
+ }: DataTableControlsProps) => {
15
+ const [focused, setFocused] = useState(false);
16
+
17
+ return (
18
+ <div className="datatable__controls">
19
+ <div className={`form-field${currentSearch ? ' form-field--has-value' : ''}${focused ? ' form-field--focused' : ''}`}>
20
+ <div className="form-field__control">
21
+ <input
22
+ className="search"
23
+ type="search"
24
+ value={currentSearch ?? ''}
25
+ onChange={(e) => handleSearch(e.target.value)}
26
+ placeholder={`Zoeken in ${entity?.label.toLowerCase() ?? 'tabel'}`}
27
+ aria-label={`Zoeken in ${entity?.label.toLowerCase() ?? 'tabel'}`}
28
+ onFocus={() => setFocused(true)}
29
+ onBlur={() => setFocused(false)}
30
+ />
31
+ </div>
32
+ </div>
33
+ </div>
34
+ )
35
+ }