@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.
Files changed (36) hide show
  1. package/dist/assets/SevenShiftsLogo/SevenShiftsLogo.d.ts +3 -0
  2. package/dist/assets/SevenShiftsLogo/index.d.ts +1 -0
  3. package/dist/assets/SevenShiftsShortLogo/SevenShiftsShortLogo.d.ts +3 -0
  4. package/dist/assets/SevenShiftsShortLogo/index.d.ts +1 -0
  5. package/dist/foundation/tokens/color/color-constants.d.ts +1 -0
  6. package/dist/foundation/tokens/color/color-types.d.ts +1 -1
  7. package/dist/hooks/useScrollDetector.d.ts +5 -0
  8. package/dist/index.css +253 -1
  9. package/dist/index.css.map +1 -1
  10. package/dist/index.js +2553 -2160
  11. package/dist/index.js.map +1 -1
  12. package/dist/index.modern.js +2748 -2348
  13. package/dist/index.modern.js.map +1 -1
  14. package/dist/navigation/PrimaryNav/PrimaryNav.d.ts +13 -0
  15. package/dist/navigation/PrimaryNav/PrimaryNavContext.d.ts +16 -0
  16. package/dist/navigation/PrimaryNav/PrimaryNavHeader/PrimaryNavHeader.d.ts +8 -0
  17. package/dist/navigation/PrimaryNav/PrimaryNavHeader/index.d.ts +1 -0
  18. package/dist/navigation/PrimaryNav/index.d.ts +1 -0
  19. package/dist/navigation/PrimaryNavDivider/PrimaryNavDivider.d.ts +3 -0
  20. package/dist/navigation/PrimaryNavDivider/index.d.ts +1 -0
  21. package/dist/navigation/PrimaryNavFooter/PrimaryNavFooter.d.ts +6 -0
  22. package/dist/navigation/PrimaryNavFooter/index.d.ts +1 -0
  23. package/dist/navigation/PrimaryNavItem/PrimaryNavItem.d.ts +12 -0
  24. package/dist/navigation/PrimaryNavItem/PrimaryNavItemBadge/PrimaryNavItemBadge.d.ts +9 -0
  25. package/dist/navigation/PrimaryNavItem/PrimaryNavItemBadge/index.d.ts +1 -0
  26. package/dist/navigation/PrimaryNavItem/index.d.ts +1 -0
  27. package/dist/navigation/PrimaryNavSubItem/PrimaryNavSubItem.d.ts +11 -0
  28. package/dist/navigation/PrimaryNavSubItem/index.d.ts +1 -0
  29. package/dist/navigation/index.d.ts +6 -1
  30. package/dist/utils/actions.d.ts +1 -1
  31. package/llms-instructions/llms-components.md +425 -0
  32. package/llms-instructions/llms-composing-components.md +502 -0
  33. package/llms-instructions/llms-icons-and-illustrations.md +1039 -0
  34. package/llms-instructions/llms-tokens.md +397 -0
  35. package/llms-instructions/llms.md +78 -0
  36. 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
+ ```