@7shifts/sous-chef 4.2.0 → 4.3.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/dist/assets/SevenShiftsLogo/SevenShiftsLogo.d.ts +3 -0
- package/dist/assets/SevenShiftsLogo/index.d.ts +1 -0
- package/dist/assets/SevenShiftsShortLogo/SevenShiftsShortLogo.d.ts +3 -0
- package/dist/assets/SevenShiftsShortLogo/index.d.ts +1 -0
- package/dist/foundation/tokens/color/color-constants.d.ts +1 -0
- package/dist/foundation/tokens/color/color-types.d.ts +1 -1
- package/dist/hooks/useScrollDetector.d.ts +5 -0
- package/dist/index.css +253 -1
- package/dist/index.css.map +1 -1
- package/dist/index.js +2553 -2160
- package/dist/index.js.map +1 -1
- package/dist/index.modern.js +2748 -2348
- package/dist/index.modern.js.map +1 -1
- package/dist/navigation/PrimaryNav/PrimaryNav.d.ts +13 -0
- package/dist/navigation/PrimaryNav/PrimaryNavContext.d.ts +16 -0
- package/dist/navigation/PrimaryNav/PrimaryNavHeader/PrimaryNavHeader.d.ts +8 -0
- package/dist/navigation/PrimaryNav/PrimaryNavHeader/index.d.ts +1 -0
- package/dist/navigation/PrimaryNav/index.d.ts +1 -0
- package/dist/navigation/PrimaryNavDivider/PrimaryNavDivider.d.ts +3 -0
- package/dist/navigation/PrimaryNavDivider/index.d.ts +1 -0
- package/dist/navigation/PrimaryNavFooter/PrimaryNavFooter.d.ts +6 -0
- package/dist/navigation/PrimaryNavFooter/index.d.ts +1 -0
- package/dist/navigation/PrimaryNavItem/PrimaryNavItem.d.ts +12 -0
- package/dist/navigation/PrimaryNavItem/PrimaryNavItemBadge/PrimaryNavItemBadge.d.ts +9 -0
- package/dist/navigation/PrimaryNavItem/PrimaryNavItemBadge/index.d.ts +1 -0
- package/dist/navigation/PrimaryNavItem/index.d.ts +1 -0
- package/dist/navigation/PrimaryNavSubItem/PrimaryNavSubItem.d.ts +11 -0
- package/dist/navigation/PrimaryNavSubItem/index.d.ts +1 -0
- package/dist/navigation/index.d.ts +6 -1
- package/dist/utils/actions.d.ts +1 -1
- package/llms-instructions/llms-components.md +425 -0
- package/llms-instructions/llms-composing-components.md +502 -0
- package/llms-instructions/llms-icons-and-illustrations.md +1039 -0
- package/llms-instructions/llms-tokens.md +397 -0
- package/llms-instructions/llms.md +78 -0
- package/package.json +3 -2
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
# Sous Chef Design System - Composing Components
|
|
2
|
+
|
|
3
|
+
In this document, you will find common examples and use cases for composing Sous Chef components to create user interfaces.
|
|
4
|
+
|
|
5
|
+
## Pages
|
|
6
|
+
|
|
7
|
+
In Sous Chef, there is currently no component to build the full app layout structure as a whole, such as the navbar, top bar, and center area. However, it offers the `PageLayout` component and the `Page` component, which can be used together.
|
|
8
|
+
The `Page` component can be used without the `PageLayout` component.
|
|
9
|
+
|
|
10
|
+
Use the `PageLayout` only when you have a group of pages, linked by a menu on the left side (it is not the app left side nav).
|
|
11
|
+
|
|
12
|
+
Example:
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
const MENU = [
|
|
16
|
+
{
|
|
17
|
+
to: '/employees',
|
|
18
|
+
label: 'Employees',
|
|
19
|
+
badge: '1'
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
to: '/inactive-employees',
|
|
23
|
+
label: 'Inactive Employees',
|
|
24
|
+
badge: '2'
|
|
25
|
+
}
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
// I'm using the MemoryRouter but it could be any other react router
|
|
30
|
+
<MemoryRouter
|
|
31
|
+
initialEntries={['/active-employees', '/inactive-employees']}
|
|
32
|
+
initialIndex={0}
|
|
33
|
+
>
|
|
34
|
+
<PageLayout title="Employees" menu={MENU}>
|
|
35
|
+
<Routes>
|
|
36
|
+
<Route
|
|
37
|
+
path="/active-employees"
|
|
38
|
+
element={<ActiveEmployeesPage />}
|
|
39
|
+
/>
|
|
40
|
+
<Route
|
|
41
|
+
path="/inactive-employees"
|
|
42
|
+
element={<InactiveEmployeesPage />}
|
|
43
|
+
/>
|
|
44
|
+
</Routes>
|
|
45
|
+
</PageLayout>
|
|
46
|
+
</MemoryRouter>
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
// This is the example for the ActiveEmployeesPage
|
|
50
|
+
|
|
51
|
+
const ActiveEmployeesPage = () => {
|
|
52
|
+
return <Page title="Active employees">// Content goes here</Page>;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// This is the example for the InactiveEmployeesPage
|
|
56
|
+
|
|
57
|
+
const ActiveEmployeesPage = () => {
|
|
58
|
+
return <Page title="Inactive employees">// Content goes here</Page>;
|
|
59
|
+
};
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Notice that the `PageLayout` component is a container that holds a group of pages, and its children are where the `Page` component will be rendered and orchestrated by a router.
|
|
63
|
+
|
|
64
|
+
## Forms
|
|
65
|
+
|
|
66
|
+
When it comes to forms in Sous Chef, we need to be clear on three concepts:
|
|
67
|
+
|
|
68
|
+
### Form UI
|
|
69
|
+
|
|
70
|
+
By default, the `<Form>` component wraps all children in a `Stack` component. Because of that, all form fields will have the proper spacing of `20px`:
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
<Form>
|
|
74
|
+
<TextField name="firstName" label="First Name" />
|
|
75
|
+
<TextField name="lastName" label="Last Name" />
|
|
76
|
+
<FormFooter actions={{
|
|
77
|
+
primary: <Button>Save</Button>,
|
|
78
|
+
secondary: <Button>Cancel</Button>
|
|
79
|
+
}}
|
|
80
|
+
/>
|
|
81
|
+
</Form>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
On the example above, it will add the proper spacing for the form fields, however, for the form footer by design it has a larger space from the form fields. The `FormFooter` adds that space automatically.
|
|
85
|
+
Because of that, NEVER build your own form actions:
|
|
86
|
+
|
|
87
|
+
NEVER DO THIS:
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
<Form>
|
|
91
|
+
<TextField name="firstName" label="First Name" />
|
|
92
|
+
<TextField name="lastName" label="Last Name" />
|
|
93
|
+
// This won't add the proper spacing from the form fields
|
|
94
|
+
<Inline>
|
|
95
|
+
<Button theme="primary">Save</Button>
|
|
96
|
+
<Button>Cancel</Button>
|
|
97
|
+
</Inline>
|
|
98
|
+
</Form>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Notice, the `FormFooter` also adds the proper theme to the buttons as well as controlling the position of the buttons.
|
|
102
|
+
|
|
103
|
+
#### Form in a Modal
|
|
104
|
+
|
|
105
|
+
The tricky part to understand when adding form in a modal is that the `Form` component needs to wrap the `ModalBody` and the `ModalFooter` so the form is accessible (user can hit the ENTER key to submit the form).
|
|
106
|
+
|
|
107
|
+
Example:
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
111
|
+
|
|
112
|
+
<>
|
|
113
|
+
<Button onClick={() => setIsOpen(true)}>Show modal</Button>
|
|
114
|
+
{isOpen && (
|
|
115
|
+
<Modal
|
|
116
|
+
header="Add Location"
|
|
117
|
+
onClose={() => setIsOpen(false)}
|
|
118
|
+
header="Add Location"
|
|
119
|
+
>
|
|
120
|
+
<Form
|
|
121
|
+
onSubmit={() => {
|
|
122
|
+
console.log('Will submit form!');
|
|
123
|
+
setIsOpen(false);
|
|
124
|
+
}}
|
|
125
|
+
stackContent={false}
|
|
126
|
+
>
|
|
127
|
+
<ModalBody>
|
|
128
|
+
<Stack>
|
|
129
|
+
<TextField name="firstName" label="First Name" />
|
|
130
|
+
<TextField name="lastName" label="First Name" />
|
|
131
|
+
</Stack>
|
|
132
|
+
</ModalBody>
|
|
133
|
+
<ModalFooter
|
|
134
|
+
actions={{
|
|
135
|
+
primary: <Button type="submit">Create location</Button>,
|
|
136
|
+
secondary: (
|
|
137
|
+
<Button onClick={() => setIsOpen(false)}>
|
|
138
|
+
Cancel
|
|
139
|
+
</Button>
|
|
140
|
+
)
|
|
141
|
+
}}
|
|
142
|
+
/>
|
|
143
|
+
</Form>
|
|
144
|
+
</Modal>
|
|
145
|
+
)}
|
|
146
|
+
</>;
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Notice that `stackContent={false}` is passed to the `Form` component. That is necessary so it does not add a `Stack` around the form children, because we don't want to add extra space to `ModalBody` and `ModalFooter`. However, inside the `ModalBody`, we still need to add a `Stack` around the form fields to guarantee proper spacing.
|
|
150
|
+
|
|
151
|
+
DON'T DO THIS:
|
|
152
|
+
|
|
153
|
+
```tsx
|
|
154
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
155
|
+
|
|
156
|
+
<>
|
|
157
|
+
<Button onClick={() => setIsOpen(true)}>Show modal</Button>
|
|
158
|
+
{isOpen && (
|
|
159
|
+
<Modal
|
|
160
|
+
header="Add Location"
|
|
161
|
+
onClose={() => setIsOpen(false)}
|
|
162
|
+
header="Add Location"
|
|
163
|
+
>
|
|
164
|
+
<ModalBody>
|
|
165
|
+
// The Form component is not wrapping the ModalFooter
|
|
166
|
+
<Form
|
|
167
|
+
onSubmit={() => {
|
|
168
|
+
console.log('Will submit form!');
|
|
169
|
+
setIsOpen(false);
|
|
170
|
+
}}
|
|
171
|
+
stackContent={false}
|
|
172
|
+
>
|
|
173
|
+
<TextField name="firstName" label="First Name" />
|
|
174
|
+
<TextField name="lastName" label="First Name" />
|
|
175
|
+
</Form>
|
|
176
|
+
</ModalBody>
|
|
177
|
+
<ModalFooter
|
|
178
|
+
actions={{
|
|
179
|
+
primary: <Button type="submit">Create location</Button>,
|
|
180
|
+
secondary: (
|
|
181
|
+
<Button onClick={() => setIsOpen(false)}>Cancel</Button>
|
|
182
|
+
)
|
|
183
|
+
}}
|
|
184
|
+
/>
|
|
185
|
+
</Modal>
|
|
186
|
+
)}
|
|
187
|
+
</>;
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
The example above produces a good interface at first glance; however, it is NOT accessible. The user CAN'T press ENTER to submit the form.
|
|
191
|
+
|
|
192
|
+
### Form state
|
|
193
|
+
|
|
194
|
+
The Sous Chef form components are state-agnostic, which means you can keep form state wherever you want. However, we recommend two approaches:
|
|
195
|
+
|
|
196
|
+
#### React useState
|
|
197
|
+
|
|
198
|
+
Recommended for smaller forms when there is not much validation.
|
|
199
|
+
|
|
200
|
+
Example:
|
|
201
|
+
|
|
202
|
+
```tsx
|
|
203
|
+
const [firstName, setFirstName] = useState('');
|
|
204
|
+
|
|
205
|
+
return (
|
|
206
|
+
<Form onSubmit={() => console.log(firstName)}>
|
|
207
|
+
<TextField
|
|
208
|
+
name="firstName"
|
|
209
|
+
label="First Name"
|
|
210
|
+
value={firstName}
|
|
211
|
+
onChange={setFirstName}
|
|
212
|
+
/>
|
|
213
|
+
</Form>
|
|
214
|
+
);
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Notice that in this case you need to pass the `value` and the `onChange` properties to make the form field controllable.
|
|
218
|
+
If there is any validation you can use the `error` prop:
|
|
219
|
+
|
|
220
|
+
Example:
|
|
221
|
+
|
|
222
|
+
```tsx
|
|
223
|
+
const [firstName, setFirstName] = useState('');
|
|
224
|
+
|
|
225
|
+
return (
|
|
226
|
+
<Form onSubmit={() => console.log(firstName)}>
|
|
227
|
+
<TextField
|
|
228
|
+
name="firstName"
|
|
229
|
+
label="First Name"
|
|
230
|
+
value={firstName}
|
|
231
|
+
onChange={setFirstName}
|
|
232
|
+
error={!firstName && 'First name required!'}
|
|
233
|
+
/>
|
|
234
|
+
</Form>
|
|
235
|
+
);
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Notice that in this case the validation is manual, and we would need to add other checks to make it a good user experience, for example, checking whether the field was touched before showing the error message. That is why, if you have validations, we recommend using Formik.
|
|
239
|
+
|
|
240
|
+
### Formik
|
|
241
|
+
|
|
242
|
+
Keeping the state in Formik is recommended in most cases because it is robust and has great support for validations.
|
|
243
|
+
|
|
244
|
+
Example:
|
|
245
|
+
|
|
246
|
+
```tsx
|
|
247
|
+
const formik = useFormik({
|
|
248
|
+
initialValues: {
|
|
249
|
+
firstName: ''
|
|
250
|
+
},
|
|
251
|
+
onSubmit: (values) => console.log(values.firstName)
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
return (
|
|
255
|
+
<Form formik={formik}>
|
|
256
|
+
<TextField name="firstName" label="First Name" />
|
|
257
|
+
</Form>
|
|
258
|
+
);
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Notice it is way simpler to build the UI using Formik. We just need to pass the `formik` prop to the Sous Chef `Form` component, then it will match the Formik state with the form fields by the `name` prop.
|
|
262
|
+
So, in this case, the `name="firstName"` will be matched to the `firstName` value in formik. The same applies to all other Sous Chef form fields.
|
|
263
|
+
|
|
264
|
+
### Form validation
|
|
265
|
+
|
|
266
|
+
For form validation, we recommend using `Formik` with `Yup`.
|
|
267
|
+
|
|
268
|
+
Example:
|
|
269
|
+
|
|
270
|
+
```tsx
|
|
271
|
+
import { useFormik } from 'formik';
|
|
272
|
+
import * as Yup from 'yup';
|
|
273
|
+
|
|
274
|
+
const schema = Yup.object().shape({
|
|
275
|
+
firstName: Yup.string()
|
|
276
|
+
.min(2, 'Too Short!')
|
|
277
|
+
.max(50, 'Too Long!')
|
|
278
|
+
.required('Required'),
|
|
279
|
+
lastName: Yup.string()
|
|
280
|
+
.min(2, 'Too Short!')
|
|
281
|
+
.max(50, 'Too Long!')
|
|
282
|
+
.required('Required'),
|
|
283
|
+
email: Yup.string().email('Invalid email').required('Required'),
|
|
284
|
+
birthdate: Yup.date().required('Required')
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
const formik: any = useFormik({
|
|
288
|
+
initialValues: {
|
|
289
|
+
firstName: '',
|
|
290
|
+
lastName: '',
|
|
291
|
+
email: '',
|
|
292
|
+
birthdate: null
|
|
293
|
+
},
|
|
294
|
+
validationSchema: schema,
|
|
295
|
+
onSubmit: (submittedValues) => action('onSubmit')(submittedValues)
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
return (
|
|
299
|
+
<Form formik={formik}>
|
|
300
|
+
<FormRow>
|
|
301
|
+
<TextField name="firstName" label="First Name" />
|
|
302
|
+
<TextField name="lastName" label="Last Name" />
|
|
303
|
+
</FormRow>
|
|
304
|
+
<TextField name="email" label="Email" />
|
|
305
|
+
<DateField name="birthdate" label="birthdate" />
|
|
306
|
+
<FormFooter
|
|
307
|
+
actions={{
|
|
308
|
+
primary: <Button disabled={!formik.isValid}>Save</Button>
|
|
309
|
+
}}
|
|
310
|
+
/>
|
|
311
|
+
</Form>
|
|
312
|
+
);
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Notice that in this example, we define the validation schema with `Yup`, then send it to `formik` through `validationSchema`. If there is any error, it will display automatically in the form field WITHOUT the need to pass the `error` prop.
|
|
316
|
+
Also, you can make use of `formik.isValid` and `formik.touched`, in this case the `formik.isValid` was used in the submit button.
|
|
317
|
+
|
|
318
|
+
## Data Tables
|
|
319
|
+
|
|
320
|
+
Data tables can go from simple tables to more complex use-cases with pagination and editable cells.
|
|
321
|
+
|
|
322
|
+
### Simple tables
|
|
323
|
+
|
|
324
|
+
Example of simple table:
|
|
325
|
+
|
|
326
|
+
```tsx
|
|
327
|
+
const ITEMS = [
|
|
328
|
+
{
|
|
329
|
+
date: 'Jun 22, 2019',
|
|
330
|
+
employeeName: 'Steve Lawrence',
|
|
331
|
+
hours: 15
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
date: 'Jan 15, 2020',
|
|
335
|
+
employeeName: 'Alex Andrade',
|
|
336
|
+
hours: 8
|
|
337
|
+
}
|
|
338
|
+
];
|
|
339
|
+
|
|
340
|
+
<DataTable items={ITEMS} />;
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
However, in most cases, you will need columns:
|
|
344
|
+
|
|
345
|
+
Example using columns:
|
|
346
|
+
|
|
347
|
+
```tsx
|
|
348
|
+
const COLUMNS = [
|
|
349
|
+
{
|
|
350
|
+
label: 'Employee Name',
|
|
351
|
+
name: 'employeeName'
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
label: 'Date',
|
|
355
|
+
name: 'date'
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
label: 'Hours',
|
|
359
|
+
name: 'hours',
|
|
360
|
+
isRightAligned: true
|
|
361
|
+
}
|
|
362
|
+
];
|
|
363
|
+
|
|
364
|
+
const ITEMS = [
|
|
365
|
+
{
|
|
366
|
+
date: 'Jun 22, 2019',
|
|
367
|
+
employeeName: 'Steve Lawrence',
|
|
368
|
+
hours: 15
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
date: 'Jan 15, 2020',
|
|
372
|
+
employeeName: 'Alex Andrade',
|
|
373
|
+
hours: <Text color="red">8</Text>
|
|
374
|
+
}
|
|
375
|
+
];
|
|
376
|
+
|
|
377
|
+
<DataTable columns={COLUMNS} items={ITEMS} />;
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
Notice in this example, when there is no use of the `itemComponent` prop, the cells will be matched using the column `name` and the object property in each `ITEMS` element.
|
|
381
|
+
Also notice, the `Hours` column is right-aligned. That means all the SORTING, ALIGNMENT AND COLUMN SIZE is done by the columns.
|
|
382
|
+
|
|
383
|
+
### Custom item elements
|
|
384
|
+
|
|
385
|
+
It is recommended to use the `itemComponent` prop to fully customize how each row and cell will look:
|
|
386
|
+
|
|
387
|
+
```tsx
|
|
388
|
+
import { DataTable, DataTableRow, Inline, Avatar } from '@7shifts/sous-chef';
|
|
389
|
+
import type { DataTableCustomComponent } from '@7shifts/sous-chef';
|
|
390
|
+
|
|
391
|
+
const COLUMNS = [
|
|
392
|
+
{
|
|
393
|
+
name: 'employeeName',
|
|
394
|
+
label: 'Employee',
|
|
395
|
+
size: 2
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
name: 'date',
|
|
399
|
+
label: 'Date'
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
name: 'hours',
|
|
403
|
+
label: 'Hours'
|
|
404
|
+
}
|
|
405
|
+
];
|
|
406
|
+
|
|
407
|
+
type Item = {
|
|
408
|
+
employeeName: string;
|
|
409
|
+
date: string;
|
|
410
|
+
hours: number;
|
|
411
|
+
image: string;
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
const ITEMS: Item[] = [
|
|
415
|
+
{
|
|
416
|
+
employeeName: 'Steve Lawrence',
|
|
417
|
+
date: 'Jun 22, 2019',
|
|
418
|
+
hours: 15,
|
|
419
|
+
image: IMAGE_URL
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
employeeName: 'Alex Andrade',
|
|
423
|
+
date: 'Jan 15, 2020',
|
|
424
|
+
hours: 8,
|
|
425
|
+
image: 'https://cdn.sanity.io/images/6she4yvt/production/d013e0d710594033d7396bc39b6b58f974a718d3-376x376.png?w=70&h=70&q=75&fit=max&auto=format&dpr=2'
|
|
426
|
+
}
|
|
427
|
+
];
|
|
428
|
+
|
|
429
|
+
const CustomItemComponent = (props: DataTableCustomComponent<Item>) => {
|
|
430
|
+
const { employeeName, date, hours, image } = props.item;
|
|
431
|
+
return (
|
|
432
|
+
<DataTableRow>
|
|
433
|
+
<DataTableCell columnIndex={0}>
|
|
434
|
+
<Inline alignItems="center">
|
|
435
|
+
<Avatar url={image} />
|
|
436
|
+
{employeeName}
|
|
437
|
+
</Inline>
|
|
438
|
+
</DataTableCell>
|
|
439
|
+
<DataTableCell columnIndex={1}>{date}</DataTableCell>
|
|
440
|
+
<DataTableCell columnIndex={2}>{hours}</DataTableCell>
|
|
441
|
+
</DataTableRow>
|
|
442
|
+
);
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
export default function App() {
|
|
446
|
+
return (
|
|
447
|
+
<DataTable
|
|
448
|
+
columns={COLUMNS}
|
|
449
|
+
items={ITEMS}
|
|
450
|
+
itemComponent={CustomItemComponent}
|
|
451
|
+
/>
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
In the example above, the `itemComponent` prop is passed as a React component. The `CustomItemComponent` uses the `DataTableCustomComponent` type to create the component properly.
|
|
457
|
+
With this approach, you need to construct the row and cells using `DataTableRow` and `DataTableCell`. `DataTableCell` requires the `columnIndex` prop, which maps it to the proper column.
|
|
458
|
+
In this case, the item property name no longer needs to match the `COLUMNS` item name.
|
|
459
|
+
|
|
460
|
+
### Pagination
|
|
461
|
+
|
|
462
|
+
In order to use pagination, you need to specify the `onPreviousClick` and `onNextClick` handlers. Optionally, you can define whether there is a next or previous page with `hasNext` or `hasPrevious`.
|
|
463
|
+
With this, it will render the Pagination Controls at the bottom of the table.
|
|
464
|
+
|
|
465
|
+
```tsx
|
|
466
|
+
const COLUMNS = [
|
|
467
|
+
{
|
|
468
|
+
label: 'Employee',
|
|
469
|
+
name: 'employeeName'
|
|
470
|
+
},
|
|
471
|
+
{
|
|
472
|
+
label: 'Date',
|
|
473
|
+
name: 'date'
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
label: 'Hours',
|
|
477
|
+
name: 'hours'
|
|
478
|
+
}
|
|
479
|
+
];
|
|
480
|
+
|
|
481
|
+
const ITEMS = [
|
|
482
|
+
{
|
|
483
|
+
date: 'Jun 22, 2019',
|
|
484
|
+
employeeName: 'Steve Lawrence',
|
|
485
|
+
hours: 15
|
|
486
|
+
},
|
|
487
|
+
{
|
|
488
|
+
date: 'Jan 15, 2020',
|
|
489
|
+
employeeName: 'Alex Andrade',
|
|
490
|
+
hours: 8
|
|
491
|
+
}
|
|
492
|
+
];
|
|
493
|
+
|
|
494
|
+
<DataTable
|
|
495
|
+
columns={COLUMNS}
|
|
496
|
+
items={ITEMS}
|
|
497
|
+
hasNext={true}
|
|
498
|
+
hasPrevious={false}
|
|
499
|
+
onNextClick={() => console.log('Fetch next page')}
|
|
500
|
+
onPreviousClick={() => console.log('Fetch previous page')}
|
|
501
|
+
/>;
|
|
502
|
+
```
|