@stack-spot/citric-react 0.37.1 → 0.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/dist/citric.css +2844 -2844
- package/dist/components/Accordion.d.ts +1 -1
- package/dist/components/Accordion.js +1 -1
- package/dist/components/Alert.d.ts +1 -1
- package/dist/components/Alert.js +1 -1
- package/dist/components/AsyncContent.d.ts +1 -1
- package/dist/components/AsyncContent.js +1 -1
- package/dist/components/Avatar.d.ts +1 -1
- package/dist/components/Avatar.js +1 -1
- package/dist/components/AvatarGroup.d.ts +1 -1
- package/dist/components/AvatarGroup.js +1 -1
- package/dist/components/Badge.d.ts +1 -1
- package/dist/components/Badge.js +1 -1
- package/dist/components/Blockquote.d.ts +1 -1
- package/dist/components/Blockquote.js +1 -1
- package/dist/components/Breadcrumb.d.ts +1 -1
- package/dist/components/Breadcrumb.js +1 -1
- package/dist/components/Button.d.ts +1 -1
- package/dist/components/Button.js +1 -1
- package/dist/components/ButtonLink.d.ts +1 -1
- package/dist/components/ButtonLink.js +1 -1
- package/dist/components/Card.d.ts +1 -1
- package/dist/components/Card.js +1 -1
- package/dist/components/Checkbox.d.ts +1 -1
- package/dist/components/Checkbox.js +1 -1
- package/dist/components/CheckboxGroup.d.ts +1 -1
- package/dist/components/CheckboxGroup.d.ts.map +1 -1
- package/dist/components/CheckboxGroup.js +2 -2
- package/dist/components/CheckboxGroup.js.map +1 -1
- package/dist/components/Circle.d.ts +1 -1
- package/dist/components/Circle.js +1 -1
- package/dist/components/Divider.d.ts +1 -1
- package/dist/components/Divider.js +1 -1
- package/dist/components/ErrorBoundary.d.ts +1 -1
- package/dist/components/ErrorBoundary.js +1 -1
- package/dist/components/ErrorMessage.d.ts +1 -1
- package/dist/components/ErrorMessage.js +1 -1
- package/dist/components/FallbackBoundary.d.ts +1 -1
- package/dist/components/FallbackBoundary.js +1 -1
- package/dist/components/Favorite.d.ts +1 -1
- package/dist/components/Favorite.js +1 -1
- package/dist/components/FieldGroup.d.ts +1 -1
- package/dist/components/FieldGroup.js +1 -1
- package/dist/components/Form.d.ts +2 -2
- package/dist/components/Form.js +1 -1
- package/dist/components/FormGroup.d.ts +1 -1
- package/dist/components/FormGroup.js +1 -1
- package/dist/components/Icon.d.ts +1 -1
- package/dist/components/Icon.js +1 -1
- package/dist/components/IconBox.d.ts +3 -3
- package/dist/components/IconBox.js +1 -1
- package/dist/components/ImageBox.d.ts +3 -3
- package/dist/components/ImageBox.js +1 -1
- package/dist/components/ImageWithFallback.d.ts +1 -1
- package/dist/components/ImageWithFallback.js +1 -1
- package/dist/components/Input.d.ts +1 -1
- package/dist/components/Input.js +1 -1
- package/dist/components/Link.d.ts +1 -1
- package/dist/components/Link.js +1 -1
- package/dist/components/LoadingPanel.d.ts +1 -1
- package/dist/components/LoadingPanel.js +1 -1
- package/dist/components/MenuOverlay/Menu.d.ts +1 -1
- package/dist/components/MenuOverlay/Menu.js +1 -1
- package/dist/components/MenuOverlay/index.d.ts +1 -1
- package/dist/components/MenuOverlay/index.js +1 -1
- package/dist/components/Overlay/index.d.ts +4 -1
- package/dist/components/Overlay/index.d.ts.map +1 -1
- package/dist/components/Overlay/index.js +4 -1
- package/dist/components/Overlay/index.js.map +1 -1
- package/dist/components/Pagination.d.ts +1 -1
- package/dist/components/Pagination.js +1 -1
- package/dist/components/ProgressBar.d.ts +1 -1
- package/dist/components/ProgressBar.js +1 -1
- package/dist/components/ProgressCircular.d.ts +1 -1
- package/dist/components/ProgressCircular.js +1 -1
- package/dist/components/RadioGroup.d.ts +1 -1
- package/dist/components/RadioGroup.js +1 -1
- package/dist/components/Rating.d.ts +1 -1
- package/dist/components/Rating.js +1 -1
- package/dist/components/Select/MultiSelect.d.ts +1 -1
- package/dist/components/Select/MultiSelect.js +1 -1
- package/dist/components/Select/RichSelect.d.ts +1 -1
- package/dist/components/Select/RichSelect.js +1 -1
- package/dist/components/Select/SimpleSelect.d.ts +1 -1
- package/dist/components/Select/SimpleSelect.js +1 -1
- package/dist/components/Select/index.d.ts +1 -1
- package/dist/components/Select/index.js +1 -1
- package/dist/components/SelectBox.d.ts +1 -1
- package/dist/components/SelectBox.js +1 -1
- package/dist/components/Skeleton.d.ts +1 -1
- package/dist/components/Skeleton.js +1 -1
- package/dist/components/Slider.d.ts +1 -1
- package/dist/components/Slider.js +1 -1
- package/dist/components/SmartTable.d.ts +1 -1
- package/dist/components/SmartTable.js +1 -1
- package/dist/components/Stepper.d.ts +1 -1
- package/dist/components/Stepper.js +1 -1
- package/dist/components/Table.d.ts +3 -3
- package/dist/components/Table.js +1 -1
- package/dist/components/Tabs/index.d.ts +1 -1
- package/dist/components/Tabs/index.js +1 -1
- package/dist/components/Textarea.d.ts +1 -1
- package/dist/components/Textarea.js +1 -1
- package/dist/components/Tooltip.d.ts +1 -1
- package/dist/components/Tooltip.js +1 -1
- package/dist/context/CitricProvider.d.ts +1 -1
- package/dist/context/CitricProvider.js +1 -1
- package/dist/overlay.js +1 -1
- package/dist/theme.css +415 -415
- package/package.json +2 -1
- package/scripts/build-css.ts +49 -49
- package/src/components/Accordion.tsx +130 -130
- package/src/components/Alert.tsx +24 -24
- package/src/components/AsyncContent.tsx +70 -70
- package/src/components/Avatar.tsx +45 -45
- package/src/components/AvatarGroup.tsx +49 -49
- package/src/components/Badge.tsx +47 -47
- package/src/components/Blockquote.tsx +18 -18
- package/src/components/Breadcrumb.tsx +33 -33
- package/src/components/Button.tsx +105 -105
- package/src/components/ButtonLink.tsx +45 -45
- package/src/components/Card.tsx +68 -68
- package/src/components/Checkbox.tsx +51 -51
- package/src/components/CheckboxGroup.tsx +153 -152
- package/src/components/Circle.tsx +43 -43
- package/src/components/CitricComponent.ts +47 -47
- package/src/components/Divider.tsx +24 -24
- package/src/components/ErrorBoundary.tsx +75 -75
- package/src/components/ErrorMessage.tsx +11 -11
- package/src/components/FallbackBoundary.tsx +40 -40
- package/src/components/Favorite.tsx +57 -57
- package/src/components/FieldGroup.tsx +46 -46
- package/src/components/Form.tsx +36 -36
- package/src/components/FormGroup.tsx +57 -57
- package/src/components/Icon.tsx +35 -35
- package/src/components/IconBox.tsx +134 -134
- package/src/components/ImageBox.tsx +125 -125
- package/src/components/ImageWithFallback.tsx +65 -65
- package/src/components/Input.tsx +49 -49
- package/src/components/Link.tsx +55 -55
- package/src/components/LoadingPanel.tsx +8 -8
- package/src/components/MenuOverlay/Menu.tsx +158 -158
- package/src/components/MenuOverlay/context.ts +20 -20
- package/src/components/MenuOverlay/index.tsx +55 -55
- package/src/components/MenuOverlay/keyboard.ts +60 -60
- package/src/components/MenuOverlay/types.ts +171 -171
- package/src/components/Overlay/context.ts +10 -10
- package/src/components/Overlay/index.tsx +167 -164
- package/src/components/Overlay/types.ts +70 -70
- package/src/components/Pagination.tsx +133 -133
- package/src/components/ProgressBar.tsx +45 -45
- package/src/components/ProgressCircular.tsx +45 -45
- package/src/components/RadioGroup.tsx +146 -146
- package/src/components/Rating.tsx +98 -98
- package/src/components/Select/MultiSelect.tsx +217 -217
- package/src/components/Select/RichSelect.tsx +128 -128
- package/src/components/Select/SimpleSelect.tsx +73 -73
- package/src/components/Select/hooks.ts +133 -133
- package/src/components/Select/index.tsx +35 -35
- package/src/components/Select/types.ts +134 -134
- package/src/components/SelectBox.tsx +167 -167
- package/src/components/Skeleton.tsx +53 -53
- package/src/components/Slider.tsx +89 -89
- package/src/components/SmartTable.tsx +227 -227
- package/src/components/Stepper.tsx +163 -163
- package/src/components/Table.tsx +234 -234
- package/src/components/Tabs/TabController.ts +54 -54
- package/src/components/Tabs/index.tsx +87 -87
- package/src/components/Tabs/types.ts +54 -54
- package/src/components/Tabs/utils.ts +6 -6
- package/src/components/Text.ts +111 -111
- package/src/components/Textarea.tsx +27 -27
- package/src/components/Tooltip.tsx +72 -72
- package/src/components/layout.tsx +101 -101
- package/src/context/CitricContext.tsx +4 -4
- package/src/context/CitricProvider.tsx +14 -14
- package/src/context/hooks.ts +6 -6
- package/src/index.ts +58 -58
- package/src/overlay.ts +341 -341
- package/src/types.ts +216 -216
- package/src/utils/ValueController.ts +28 -28
- package/src/utils/acessibility.ts +92 -92
- package/src/utils/checkbox.ts +121 -121
- package/src/utils/css.ts +119 -119
- package/src/utils/options.ts +9 -9
- package/src/utils/radio.ts +93 -93
- package/src/utils/react.ts +6 -6
- package/tsconfig.json +10 -10
|
@@ -1,227 +1,227 @@
|
|
|
1
|
-
import { useMemo } from 'react'
|
|
2
|
-
import { withRef } from '../utils/react'
|
|
3
|
-
import { BaseTableProps, SortingDirection, Table, TableProps, Th, Tr } from './Table'
|
|
4
|
-
|
|
5
|
-
export type TableColumn<T> = {
|
|
6
|
-
/**
|
|
7
|
-
* A unique identifier for the column.
|
|
8
|
-
*/
|
|
9
|
-
key: string,
|
|
10
|
-
/**
|
|
11
|
-
* A label to use as the column's header.
|
|
12
|
-
*
|
|
13
|
-
* If not provided, the key will be used.
|
|
14
|
-
*/
|
|
15
|
-
label?: React.ReactNode,
|
|
16
|
-
/**
|
|
17
|
-
* A custom render function. It receives the item (row) and must return a React node.
|
|
18
|
-
*
|
|
19
|
-
* If not provided, the value of `item[key]` will be converted to a string and used. If it's undefined, an empty string is rendered.
|
|
20
|
-
*
|
|
21
|
-
* @param item the current item.
|
|
22
|
-
* @returns the React Node to render.
|
|
23
|
-
*/
|
|
24
|
-
render?: (item: T) => React.ReactNode,
|
|
25
|
-
/**
|
|
26
|
-
* What to do when the header is clicked (sorting)
|
|
27
|
-
*/
|
|
28
|
-
onSort?: (value: SortingDirection | undefined) => void,
|
|
29
|
-
/**
|
|
30
|
-
* Which is the current direction of the sorting? Only relevant if 'onSort' is set.
|
|
31
|
-
*
|
|
32
|
-
* @default 'desc'
|
|
33
|
-
*/
|
|
34
|
-
direction?: SortingDirection,
|
|
35
|
-
/**
|
|
36
|
-
* Additional properties for the "th" corresponding to this column.
|
|
37
|
-
*/
|
|
38
|
-
th?: Omit<JSX.IntrinsicElements['th'], 'children'>,
|
|
39
|
-
/**
|
|
40
|
-
* Additional properties for the "td" corresponding to this column.
|
|
41
|
-
*/
|
|
42
|
-
td?: Omit<JSX.IntrinsicElements['td'], 'children'>,
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
interface ExtraSmartTableProps<T extends Record<string, any>> {
|
|
46
|
-
/**
|
|
47
|
-
* An array where each item is a row in the table.
|
|
48
|
-
*/
|
|
49
|
-
data: T[],
|
|
50
|
-
/**
|
|
51
|
-
* A function to generate a unique key for each item. If not provided, the item's index will be used.
|
|
52
|
-
* @param item
|
|
53
|
-
* @returns a unique key
|
|
54
|
-
*/
|
|
55
|
-
keygen?: (item: T) => React.Key,
|
|
56
|
-
/**
|
|
57
|
-
* The columns to display in the table.
|
|
58
|
-
*
|
|
59
|
-
* Attention: always save your columns with `useMemo` to avoid unnecessary rerenders.
|
|
60
|
-
*/
|
|
61
|
-
columns: TableColumn<T>[],
|
|
62
|
-
/**
|
|
63
|
-
* If set, an extra row and column for each item is rendered. The extra row is collapsed by default and can be expanded through a button
|
|
64
|
-
* that is placed in the extra column.
|
|
65
|
-
*
|
|
66
|
-
* The extra row is, effectively, an accordion. The content of this accordion is defined by whatever this function returns.
|
|
67
|
-
*
|
|
68
|
-
* @param item the current item.
|
|
69
|
-
* @returns the React Node to render.
|
|
70
|
-
*/
|
|
71
|
-
renderAccordion?: (item: T) => React.ReactNode,
|
|
72
|
-
/**
|
|
73
|
-
* What should make the accordions expand or contract? A click on a button in the last column or a click anywhere in the row?
|
|
74
|
-
*
|
|
75
|
-
* Only valid if `renderAccordion` is set.
|
|
76
|
-
*
|
|
77
|
-
* @default 'button'
|
|
78
|
-
*/
|
|
79
|
-
accordionTrigger?: 'button' | 'row',
|
|
80
|
-
/**
|
|
81
|
-
* The maximum height for accordion rows.
|
|
82
|
-
*
|
|
83
|
-
* @default '200px'
|
|
84
|
-
*/
|
|
85
|
-
accordionMaxHeight?: string,
|
|
86
|
-
/**
|
|
87
|
-
* If true, no header is rendered for the table.
|
|
88
|
-
*
|
|
89
|
-
* @default false
|
|
90
|
-
*/
|
|
91
|
-
headless?: boolean,
|
|
92
|
-
/**
|
|
93
|
-
* Making a row clickable is not accessible, i.e. it creates an unfriendly UI for people who rely on assistive technologies.
|
|
94
|
-
*
|
|
95
|
-
* Instead you should place the action in an element inside the row, e.g. an anchor or a button.
|
|
96
|
-
*
|
|
97
|
-
* For users who don't rely on assistive technologies we may still want the click on the row to mimic the behavior of the element with
|
|
98
|
-
* the actual action. This property does exactly this!
|
|
99
|
-
*
|
|
100
|
-
* The value of this property must be a CSS selector relative to this row, it must refer to the element with the click listener that
|
|
101
|
-
* should be triggered when this row is clicked.
|
|
102
|
-
*
|
|
103
|
-
* When this property is set, the row cursor becomes a pointer.
|
|
104
|
-
*
|
|
105
|
-
* @example
|
|
106
|
-
*
|
|
107
|
-
* ```
|
|
108
|
-
* rowClickElement="a" // the first anchor element inside the row will be triggered when the row is clicked.
|
|
109
|
-
* ```
|
|
110
|
-
*/
|
|
111
|
-
rowClickElement?: string,
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export type BaseSmartTableProps<T extends Record<string, any>> = BaseTableProps & ExtraSmartTableProps<T>
|
|
115
|
-
|
|
116
|
-
export type SmartTableProps<T extends Record<string, any>> = Omit<TableProps, 'children'> & ExtraSmartTableProps<T>
|
|
117
|
-
|
|
118
|
-
function createRowClickListener(elementQuerySelector: string) {
|
|
119
|
-
return (event: React.MouseEvent) => {
|
|
120
|
-
const target = event.target instanceof HTMLElement ? event.target : undefined
|
|
121
|
-
const row = target instanceof HTMLTableRowElement ? target : target?.closest('tr')
|
|
122
|
-
if (row) {
|
|
123
|
-
const element = row.querySelector(elementQuerySelector)
|
|
124
|
-
if (element instanceof HTMLElement) element.click()
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Renders a table. Always prefer using this component over the raw Table component.
|
|
131
|
-
*
|
|
132
|
-
* This receives a dataset and renders each item as a row. The columns are determined by the property "columns".
|
|
133
|
-
*
|
|
134
|
-
* To change the overall table style, use the property "appearance".
|
|
135
|
-
*
|
|
136
|
-
* To check more complex table examples, like tables with accordions and sorting, check the storybook.
|
|
137
|
-
*
|
|
138
|
-
* @example
|
|
139
|
-
*
|
|
140
|
-
* ```
|
|
141
|
-
* const data = useMemo(() => [
|
|
142
|
-
* { id: 1, name: 'Yuri Tyson', phone: '1-801-475-4561', country: 'Poland', age: 21 },
|
|
143
|
-
* { id: 2, name: 'Amal Mcclure', phone: '(695) 948-4102', country: 'Ukraine', age: 32 },
|
|
144
|
-
* { id: 3, name: 'Levi Glass', phone: '1-976-544-4872', country: 'Colombia', age: 45 },
|
|
145
|
-
* ], [])
|
|
146
|
-
* const columns = useMemo(() => [
|
|
147
|
-
* { key: 'name', label: 'Name', render: item => `${item.name} (${item.age})` },
|
|
148
|
-
* { key: 'phone', label: 'Phone' },
|
|
149
|
-
* { key: 'country', label: 'Country' },
|
|
150
|
-
* {
|
|
151
|
-
* key: 'settings',
|
|
152
|
-
* render: item => (
|
|
153
|
-
* <Row gap={2}>
|
|
154
|
-
* <IconButton icon="ExternalLink" title="View" />
|
|
155
|
-
* <IconButton icon="Pencil" title="Edit" />
|
|
156
|
-
* <IconButton icon="Trash" title="Delete" colorScheme="danger" />
|
|
157
|
-
* </Row>
|
|
158
|
-
* ),
|
|
159
|
-
* },
|
|
160
|
-
* ], [])
|
|
161
|
-
* return <SmartTable data={data} columns={columns} />
|
|
162
|
-
* ```
|
|
163
|
-
*/
|
|
164
|
-
export const SmartTable = withRef(
|
|
165
|
-
function SmartTable<T extends Record<string, any>>({
|
|
166
|
-
data, keygen, columns, renderAccordion, accordionTrigger, accordionMaxHeight, id, rowClickElement, headless, ...props
|
|
167
|
-
}: SmartTableProps<T>) {
|
|
168
|
-
id = useMemo(() => id || `${Math.random()}`, [id])
|
|
169
|
-
|
|
170
|
-
const headers = useMemo(
|
|
171
|
-
() => headless ? [] : columns.map((c) => (
|
|
172
|
-
<Th {...c.th} key={c.key} onSort={c.onSort} direction={c.direction} tabIndex={c.onSort ? 0 : undefined}>
|
|
173
|
-
{c.label ?? c.key}
|
|
174
|
-
</Th>
|
|
175
|
-
)),
|
|
176
|
-
[columns, headless],
|
|
177
|
-
)
|
|
178
|
-
|
|
179
|
-
const rows = useMemo(
|
|
180
|
-
() => data.map((item, index) => {
|
|
181
|
-
const key = keygen ? keygen(item) : index
|
|
182
|
-
const accordionContent = renderAccordion?.(item)
|
|
183
|
-
const row = (
|
|
184
|
-
<Tr
|
|
185
|
-
key={key}
|
|
186
|
-
accordionTrigger={accordionContent ? accordionTrigger : undefined}
|
|
187
|
-
onClick={rowClickElement ? createRowClickListener(rowClickElement) : undefined}
|
|
188
|
-
>
|
|
189
|
-
{columns.map(c => <td {...c.td} key={c.key}>{c.render ? c.render(item) : (item[c.key] || '')}</td>)}
|
|
190
|
-
{renderAccordion && (
|
|
191
|
-
<td>
|
|
192
|
-
{accordionContent && (
|
|
193
|
-
<input
|
|
194
|
-
type="checkbox"
|
|
195
|
-
aria-controls={`${id}-${key}`}
|
|
196
|
-
onKeyDown={e => e.key === 'Enter' && e.target instanceof HTMLElement && e.target.click()}
|
|
197
|
-
/>
|
|
198
|
-
)}
|
|
199
|
-
</td>
|
|
200
|
-
)}
|
|
201
|
-
</Tr>
|
|
202
|
-
)
|
|
203
|
-
return renderAccordion ? (
|
|
204
|
-
<tbody key={`${key}-group`}>
|
|
205
|
-
{row}
|
|
206
|
-
<Tr id={`${id}-${key}`} accordion accordionMaxHeight={accordionMaxHeight}>
|
|
207
|
-
<td colSpan={columns.length + 1}><div>{accordionContent}</div></td>
|
|
208
|
-
</Tr>
|
|
209
|
-
</tbody>
|
|
210
|
-
) : row
|
|
211
|
-
}),
|
|
212
|
-
[columns, data, !!renderAccordion, accordionTrigger],
|
|
213
|
-
)
|
|
214
|
-
|
|
215
|
-
return (
|
|
216
|
-
<Table id={id} accordionRows={!!renderAccordion} {...props}>
|
|
217
|
-
{!headless && <thead>
|
|
218
|
-
<tr>
|
|
219
|
-
{headers}
|
|
220
|
-
{renderAccordion && <th></th>}
|
|
221
|
-
</tr>
|
|
222
|
-
</thead>}
|
|
223
|
-
{renderAccordion ? rows : <tbody>{rows}</tbody>}
|
|
224
|
-
</Table>
|
|
225
|
-
)
|
|
226
|
-
},
|
|
227
|
-
)
|
|
1
|
+
import { useMemo } from 'react'
|
|
2
|
+
import { withRef } from '../utils/react'
|
|
3
|
+
import { BaseTableProps, SortingDirection, Table, TableProps, Th, Tr } from './Table'
|
|
4
|
+
|
|
5
|
+
export type TableColumn<T> = {
|
|
6
|
+
/**
|
|
7
|
+
* A unique identifier for the column.
|
|
8
|
+
*/
|
|
9
|
+
key: string,
|
|
10
|
+
/**
|
|
11
|
+
* A label to use as the column's header.
|
|
12
|
+
*
|
|
13
|
+
* If not provided, the key will be used.
|
|
14
|
+
*/
|
|
15
|
+
label?: React.ReactNode,
|
|
16
|
+
/**
|
|
17
|
+
* A custom render function. It receives the item (row) and must return a React node.
|
|
18
|
+
*
|
|
19
|
+
* If not provided, the value of `item[key]` will be converted to a string and used. If it's undefined, an empty string is rendered.
|
|
20
|
+
*
|
|
21
|
+
* @param item the current item.
|
|
22
|
+
* @returns the React Node to render.
|
|
23
|
+
*/
|
|
24
|
+
render?: (item: T) => React.ReactNode,
|
|
25
|
+
/**
|
|
26
|
+
* What to do when the header is clicked (sorting)
|
|
27
|
+
*/
|
|
28
|
+
onSort?: (value: SortingDirection | undefined) => void,
|
|
29
|
+
/**
|
|
30
|
+
* Which is the current direction of the sorting? Only relevant if 'onSort' is set.
|
|
31
|
+
*
|
|
32
|
+
* @default 'desc'
|
|
33
|
+
*/
|
|
34
|
+
direction?: SortingDirection,
|
|
35
|
+
/**
|
|
36
|
+
* Additional properties for the "th" corresponding to this column.
|
|
37
|
+
*/
|
|
38
|
+
th?: Omit<JSX.IntrinsicElements['th'], 'children'>,
|
|
39
|
+
/**
|
|
40
|
+
* Additional properties for the "td" corresponding to this column.
|
|
41
|
+
*/
|
|
42
|
+
td?: Omit<JSX.IntrinsicElements['td'], 'children'>,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface ExtraSmartTableProps<T extends Record<string, any>> {
|
|
46
|
+
/**
|
|
47
|
+
* An array where each item is a row in the table.
|
|
48
|
+
*/
|
|
49
|
+
data: T[],
|
|
50
|
+
/**
|
|
51
|
+
* A function to generate a unique key for each item. If not provided, the item's index will be used.
|
|
52
|
+
* @param item
|
|
53
|
+
* @returns a unique key
|
|
54
|
+
*/
|
|
55
|
+
keygen?: (item: T) => React.Key,
|
|
56
|
+
/**
|
|
57
|
+
* The columns to display in the table.
|
|
58
|
+
*
|
|
59
|
+
* Attention: always save your columns with `useMemo` to avoid unnecessary rerenders.
|
|
60
|
+
*/
|
|
61
|
+
columns: TableColumn<T>[],
|
|
62
|
+
/**
|
|
63
|
+
* If set, an extra row and column for each item is rendered. The extra row is collapsed by default and can be expanded through a button
|
|
64
|
+
* that is placed in the extra column.
|
|
65
|
+
*
|
|
66
|
+
* The extra row is, effectively, an accordion. The content of this accordion is defined by whatever this function returns.
|
|
67
|
+
*
|
|
68
|
+
* @param item the current item.
|
|
69
|
+
* @returns the React Node to render.
|
|
70
|
+
*/
|
|
71
|
+
renderAccordion?: (item: T) => React.ReactNode,
|
|
72
|
+
/**
|
|
73
|
+
* What should make the accordions expand or contract? A click on a button in the last column or a click anywhere in the row?
|
|
74
|
+
*
|
|
75
|
+
* Only valid if `renderAccordion` is set.
|
|
76
|
+
*
|
|
77
|
+
* @default 'button'
|
|
78
|
+
*/
|
|
79
|
+
accordionTrigger?: 'button' | 'row',
|
|
80
|
+
/**
|
|
81
|
+
* The maximum height for accordion rows.
|
|
82
|
+
*
|
|
83
|
+
* @default '200px'
|
|
84
|
+
*/
|
|
85
|
+
accordionMaxHeight?: string,
|
|
86
|
+
/**
|
|
87
|
+
* If true, no header is rendered for the table.
|
|
88
|
+
*
|
|
89
|
+
* @default false
|
|
90
|
+
*/
|
|
91
|
+
headless?: boolean,
|
|
92
|
+
/**
|
|
93
|
+
* Making a row clickable is not accessible, i.e. it creates an unfriendly UI for people who rely on assistive technologies.
|
|
94
|
+
*
|
|
95
|
+
* Instead you should place the action in an element inside the row, e.g. an anchor or a button.
|
|
96
|
+
*
|
|
97
|
+
* For users who don't rely on assistive technologies we may still want the click on the row to mimic the behavior of the element with
|
|
98
|
+
* the actual action. This property does exactly this!
|
|
99
|
+
*
|
|
100
|
+
* The value of this property must be a CSS selector relative to this row, it must refer to the element with the click listener that
|
|
101
|
+
* should be triggered when this row is clicked.
|
|
102
|
+
*
|
|
103
|
+
* When this property is set, the row cursor becomes a pointer.
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
*
|
|
107
|
+
* ```
|
|
108
|
+
* rowClickElement="a" // the first anchor element inside the row will be triggered when the row is clicked.
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
rowClickElement?: string,
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export type BaseSmartTableProps<T extends Record<string, any>> = BaseTableProps & ExtraSmartTableProps<T>
|
|
115
|
+
|
|
116
|
+
export type SmartTableProps<T extends Record<string, any>> = Omit<TableProps, 'children'> & ExtraSmartTableProps<T>
|
|
117
|
+
|
|
118
|
+
function createRowClickListener(elementQuerySelector: string) {
|
|
119
|
+
return (event: React.MouseEvent) => {
|
|
120
|
+
const target = event.target instanceof HTMLElement ? event.target : undefined
|
|
121
|
+
const row = target instanceof HTMLTableRowElement ? target : target?.closest('tr')
|
|
122
|
+
if (row) {
|
|
123
|
+
const element = row.querySelector(elementQuerySelector)
|
|
124
|
+
if (element instanceof HTMLElement) element.click()
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Renders a table. Always prefer using this component over the raw Table component.
|
|
131
|
+
*
|
|
132
|
+
* This receives a dataset and renders each item as a row. The columns are determined by the property "columns".
|
|
133
|
+
*
|
|
134
|
+
* To change the overall table style, use the property "appearance".
|
|
135
|
+
*
|
|
136
|
+
* To check more complex table examples, like tables with accordions and sorting, check the storybook.
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
*
|
|
140
|
+
* ```
|
|
141
|
+
* const data = useMemo(() => [
|
|
142
|
+
* { id: 1, name: 'Yuri Tyson', phone: '1-801-475-4561', country: 'Poland', age: 21 },
|
|
143
|
+
* { id: 2, name: 'Amal Mcclure', phone: '(695) 948-4102', country: 'Ukraine', age: 32 },
|
|
144
|
+
* { id: 3, name: 'Levi Glass', phone: '1-976-544-4872', country: 'Colombia', age: 45 },
|
|
145
|
+
* ], [])
|
|
146
|
+
* const columns = useMemo(() => [
|
|
147
|
+
* { key: 'name', label: 'Name', render: item => `${item.name} (${item.age})` },
|
|
148
|
+
* { key: 'phone', label: 'Phone' },
|
|
149
|
+
* { key: 'country', label: 'Country' },
|
|
150
|
+
* {
|
|
151
|
+
* key: 'settings',
|
|
152
|
+
* render: item => (
|
|
153
|
+
* <Row gap={2}>
|
|
154
|
+
* <IconButton icon="ExternalLink" title="View" />
|
|
155
|
+
* <IconButton icon="Pencil" title="Edit" />
|
|
156
|
+
* <IconButton icon="Trash" title="Delete" colorScheme="danger" />
|
|
157
|
+
* </Row>
|
|
158
|
+
* ),
|
|
159
|
+
* },
|
|
160
|
+
* ], [])
|
|
161
|
+
* return <SmartTable data={data} columns={columns} />
|
|
162
|
+
* ```
|
|
163
|
+
*/
|
|
164
|
+
export const SmartTable = withRef(
|
|
165
|
+
function SmartTable<T extends Record<string, any>>({
|
|
166
|
+
data, keygen, columns, renderAccordion, accordionTrigger, accordionMaxHeight, id, rowClickElement, headless, ...props
|
|
167
|
+
}: SmartTableProps<T>) {
|
|
168
|
+
id = useMemo(() => id || `${Math.random()}`, [id])
|
|
169
|
+
|
|
170
|
+
const headers = useMemo(
|
|
171
|
+
() => headless ? [] : columns.map((c) => (
|
|
172
|
+
<Th {...c.th} key={c.key} onSort={c.onSort} direction={c.direction} tabIndex={c.onSort ? 0 : undefined}>
|
|
173
|
+
{c.label ?? c.key}
|
|
174
|
+
</Th>
|
|
175
|
+
)),
|
|
176
|
+
[columns, headless],
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
const rows = useMemo(
|
|
180
|
+
() => data.map((item, index) => {
|
|
181
|
+
const key = keygen ? keygen(item) : index
|
|
182
|
+
const accordionContent = renderAccordion?.(item)
|
|
183
|
+
const row = (
|
|
184
|
+
<Tr
|
|
185
|
+
key={key}
|
|
186
|
+
accordionTrigger={accordionContent ? accordionTrigger : undefined}
|
|
187
|
+
onClick={rowClickElement ? createRowClickListener(rowClickElement) : undefined}
|
|
188
|
+
>
|
|
189
|
+
{columns.map(c => <td {...c.td} key={c.key}>{c.render ? c.render(item) : (item[c.key] || '')}</td>)}
|
|
190
|
+
{renderAccordion && (
|
|
191
|
+
<td>
|
|
192
|
+
{accordionContent && (
|
|
193
|
+
<input
|
|
194
|
+
type="checkbox"
|
|
195
|
+
aria-controls={`${id}-${key}`}
|
|
196
|
+
onKeyDown={e => e.key === 'Enter' && e.target instanceof HTMLElement && e.target.click()}
|
|
197
|
+
/>
|
|
198
|
+
)}
|
|
199
|
+
</td>
|
|
200
|
+
)}
|
|
201
|
+
</Tr>
|
|
202
|
+
)
|
|
203
|
+
return renderAccordion ? (
|
|
204
|
+
<tbody key={`${key}-group`}>
|
|
205
|
+
{row}
|
|
206
|
+
<Tr id={`${id}-${key}`} accordion accordionMaxHeight={accordionMaxHeight}>
|
|
207
|
+
<td colSpan={columns.length + 1}><div>{accordionContent}</div></td>
|
|
208
|
+
</Tr>
|
|
209
|
+
</tbody>
|
|
210
|
+
) : row
|
|
211
|
+
}),
|
|
212
|
+
[columns, data, !!renderAccordion, accordionTrigger],
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
return (
|
|
216
|
+
<Table id={id} accordionRows={!!renderAccordion} {...props}>
|
|
217
|
+
{!headless && <thead>
|
|
218
|
+
<tr>
|
|
219
|
+
{headers}
|
|
220
|
+
{renderAccordion && <th></th>}
|
|
221
|
+
</tr>
|
|
222
|
+
</thead>}
|
|
223
|
+
{renderAccordion ? rows : <tbody>{rows}</tbody>}
|
|
224
|
+
</Table>
|
|
225
|
+
)
|
|
226
|
+
},
|
|
227
|
+
)
|