@instructure/ui-drilldown 8.21.1-snapshot.10

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 (89) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/LICENSE.md +27 -0
  3. package/README.md +30 -0
  4. package/es/Drilldown/DrilldownGroup/index.js +56 -0
  5. package/es/Drilldown/DrilldownGroup/props.js +42 -0
  6. package/es/Drilldown/DrilldownLocator.js +144 -0
  7. package/es/Drilldown/DrilldownOption/index.js +59 -0
  8. package/es/Drilldown/DrilldownOption/props.js +46 -0
  9. package/es/Drilldown/DrilldownPage/index.js +56 -0
  10. package/es/Drilldown/DrilldownPage/props.js +41 -0
  11. package/es/Drilldown/DrilldownSeparator/index.js +52 -0
  12. package/es/Drilldown/DrilldownSeparator/props.js +30 -0
  13. package/es/Drilldown/__examples__/Drilldown.examples.js +299 -0
  14. package/es/Drilldown/index.js +1241 -0
  15. package/es/Drilldown/locator.js +26 -0
  16. package/es/Drilldown/props.js +67 -0
  17. package/es/Drilldown/styles.js +82 -0
  18. package/es/Drilldown/theme.js +43 -0
  19. package/es/index.js +24 -0
  20. package/lib/Drilldown/DrilldownGroup/index.js +42 -0
  21. package/lib/Drilldown/DrilldownGroup/props.js +56 -0
  22. package/lib/Drilldown/DrilldownLocator.js +157 -0
  23. package/lib/Drilldown/DrilldownOption/index.js +45 -0
  24. package/lib/Drilldown/DrilldownOption/props.js +57 -0
  25. package/lib/Drilldown/DrilldownPage/index.js +66 -0
  26. package/lib/Drilldown/DrilldownPage/props.js +56 -0
  27. package/lib/Drilldown/DrilldownSeparator/index.js +38 -0
  28. package/lib/Drilldown/DrilldownSeparator/props.js +41 -0
  29. package/lib/Drilldown/__examples__/Drilldown.examples.js +289 -0
  30. package/lib/Drilldown/index.js +1253 -0
  31. package/lib/Drilldown/locator.js +40 -0
  32. package/lib/Drilldown/props.js +83 -0
  33. package/lib/Drilldown/styles.js +90 -0
  34. package/lib/Drilldown/theme.js +51 -0
  35. package/lib/index.js +13 -0
  36. package/lib/package.json +1 -0
  37. package/package.json +58 -0
  38. package/src/Drilldown/DrilldownGroup/index.tsx +64 -0
  39. package/src/Drilldown/DrilldownGroup/props.ts +149 -0
  40. package/src/Drilldown/DrilldownLocator.ts +101 -0
  41. package/src/Drilldown/DrilldownOption/index.tsx +68 -0
  42. package/src/Drilldown/DrilldownOption/props.ts +224 -0
  43. package/src/Drilldown/DrilldownPage/index.tsx +58 -0
  44. package/src/Drilldown/DrilldownPage/props.ts +129 -0
  45. package/src/Drilldown/DrilldownSeparator/index.tsx +60 -0
  46. package/src/Drilldown/DrilldownSeparator/props.ts +65 -0
  47. package/src/Drilldown/README.md +1515 -0
  48. package/src/Drilldown/__examples__/Drilldown.examples.tsx +318 -0
  49. package/src/Drilldown/index.tsx +1492 -0
  50. package/src/Drilldown/locator.ts +28 -0
  51. package/src/Drilldown/props.ts +378 -0
  52. package/src/Drilldown/styles.ts +95 -0
  53. package/src/Drilldown/theme.ts +47 -0
  54. package/src/index.ts +26 -0
  55. package/tsconfig.build.json +30 -0
  56. package/tsconfig.build.tsbuildinfo +1 -0
  57. package/tsconfig.json +4 -0
  58. package/types/Drilldown/DrilldownGroup/index.d.ts +59 -0
  59. package/types/Drilldown/DrilldownGroup/index.d.ts.map +1 -0
  60. package/types/Drilldown/DrilldownGroup/props.d.ts +65 -0
  61. package/types/Drilldown/DrilldownGroup/props.d.ts.map +1 -0
  62. package/types/Drilldown/DrilldownLocator.d.ts +652 -0
  63. package/types/Drilldown/DrilldownLocator.d.ts.map +1 -0
  64. package/types/Drilldown/DrilldownOption/index.d.ts +134 -0
  65. package/types/Drilldown/DrilldownOption/index.d.ts.map +1 -0
  66. package/types/Drilldown/DrilldownOption/props.d.ts +116 -0
  67. package/types/Drilldown/DrilldownOption/props.d.ts.map +1 -0
  68. package/types/Drilldown/DrilldownPage/index.d.ts +45 -0
  69. package/types/Drilldown/DrilldownPage/index.d.ts.map +1 -0
  70. package/types/Drilldown/DrilldownPage/props.d.ts +51 -0
  71. package/types/Drilldown/DrilldownPage/props.d.ts.map +1 -0
  72. package/types/Drilldown/DrilldownSeparator/index.d.ts +27 -0
  73. package/types/Drilldown/DrilldownSeparator/index.d.ts.map +1 -0
  74. package/types/Drilldown/DrilldownSeparator/props.d.ts +18 -0
  75. package/types/Drilldown/DrilldownSeparator/props.d.ts.map +1 -0
  76. package/types/Drilldown/__examples__/Drilldown.examples.d.ts +5 -0
  77. package/types/Drilldown/__examples__/Drilldown.examples.d.ts.map +1 -0
  78. package/types/Drilldown/index.d.ts +246 -0
  79. package/types/Drilldown/index.d.ts.map +1 -0
  80. package/types/Drilldown/locator.d.ts +4 -0
  81. package/types/Drilldown/locator.d.ts.map +1 -0
  82. package/types/Drilldown/props.d.ts +189 -0
  83. package/types/Drilldown/props.d.ts.map +1 -0
  84. package/types/Drilldown/styles.d.ts +15 -0
  85. package/types/Drilldown/styles.d.ts.map +1 -0
  86. package/types/Drilldown/theme.d.ts +10 -0
  87. package/types/Drilldown/theme.d.ts.map +1 -0
  88. package/types/index.d.ts +3 -0
  89. package/types/index.d.ts.map +1 -0
@@ -0,0 +1,1515 @@
1
+ ---
2
+ describes: Drilldown
3
+ ---
4
+
5
+ `Drilldown` is a diverse component that displays hierarchical data in a fashion that allows the users to "drill down" and dig deeper into the layers (pages) of the data structure. It has similar look and features to the [Menu](#Menu), [Select](#Select) and [TreeBrowser](#TreeBrowser) components.
6
+
7
+ The `Drilldown` component exists to support navigating and managing tree structures in compact spaces. With WCAG 2.1 requirements around small viewports, and also with general responsiveness standards the classic tree-navigation and flyout menu patterns got outdated.
8
+
9
+ Some of Drilldown's features include:
10
+
11
+ - options that navigate (drill) down in the structure is marked with an arrow;
12
+ - options that navigate back in the structure is always a “back” option at the top of the list;
13
+ - option groups can have titles that have the standard InstUI menu group title style;
14
+ - groups are divided by separator lines;
15
+ - items in groups have no indent unless the group is a checkbox or radio option group, in which case the selected items are marked with a "check" icon, all unselected items have an indent;
16
+ - secondary information blocks can be displayed both in-line with the option, or below the option with a dedicated text and color style;
17
+ - the component can be rendered both in-line and in a popover.
18
+
19
+ ### Pages
20
+
21
+ The main building blocks of Drilldown are the `Drilldown.Pages`. These represent the layers of the structure and can contain Options, Separators and Groups. Each page has a "header" that can contain a page title, the back navigation and a "page action" option (see [Page header section](/#Drilldown/#page-header)).
22
+
23
+ Each page needs an `id` prop that is used in the navigation. Options point to pages with their `subPageId` prop, and the Drilldown itself needs a `rootPageId` that indicates the first root level page, which renders first.
24
+
25
+ ```js
26
+ ---
27
+ example: true
28
+ ---
29
+ <Drilldown rootPageId='root' width="20rem" maxHeight="30rem">
30
+ <Drilldown.Page id="root" renderTitle='Groceries'>
31
+ <Drilldown.Option id="Produce" subPageId="Produce">
32
+ Produce
33
+ </Drilldown.Option>
34
+ <Drilldown.Option id="Grains and Bread" subPageId="Grains and Bread">
35
+ Grains and Bread
36
+ </Drilldown.Option>
37
+ </Drilldown.Page>
38
+
39
+ <Drilldown.Page id="Produce" renderTitle='Produce'>
40
+ <Drilldown.Option id="Fruits" subPageId="Fruits">
41
+ Fruits
42
+ </Drilldown.Option>
43
+ <Drilldown.Option id="Vegtables" subPageId="Vegtables">
44
+ Vegtables
45
+ </Drilldown.Option>
46
+ </Drilldown.Page>
47
+
48
+ <Drilldown.Page id="Grains and Bread" renderTitle='Grains and Bread'>
49
+ {['Pasta', 'Rice', 'Bread', 'Flour', 'Cereal', 'Oats'].map(
50
+ item => <Drilldown.Option id={item} key={item}>{item}</Drilldown.Option>
51
+ )}
52
+ </Drilldown.Page>
53
+
54
+ <Drilldown.Page id="Fruits" renderTitle='Fruits'>
55
+ {['Apple', 'Orange', 'Cherry', 'Grapefruit', 'Mango', 'Banana', 'Strawberry'].map(
56
+ item => <Drilldown.Option id={item} key={item}>{item}</Drilldown.Option>
57
+ )}
58
+ </Drilldown.Page>
59
+
60
+ <Drilldown.Page id="Vegtables" renderTitle='Vegtables'>
61
+ {['Tomato', 'Cucumber', 'Eggplant', 'Lettuce', 'Garlic', 'Onion', 'Corn', 'Carrot', 'Bell pepper'].map(
62
+ item => <Drilldown.Option id={item} key={item}>{item}</Drilldown.Option>
63
+ )}
64
+ </Drilldown.Page>
65
+ </Drilldown>
66
+ ```
67
+
68
+ ### Options
69
+
70
+ `Drilldown.Option` is the main child component of Drilldown. The content can be a ReactNode or a function returning a ReactNode. The function has an object as its parameter, containing the option's `id`, `variant` and `isSelected` state.
71
+
72
+ > Note: Drilldown is based on the [Options](#Options) component, so the Drilldown.Options are rendered as `Options.Item` components under the hood. This is why the `variant` parameter has the values of Options.Item's `variant` prop.
73
+
74
+ ```js
75
+ ---
76
+ example: true
77
+ ---
78
+ <Drilldown rootPageId='root' width="20rem" maxHeight="30rem">
79
+ <Drilldown.Page id="root">
80
+ <Drilldown.Option id="option1">
81
+ Option
82
+ </Drilldown.Option>
83
+ <Drilldown.Option id="option2">
84
+ {(props) => `Option ${props.variant === 'highlighted'
85
+ ? 'is highlighted'
86
+ : 'is not highlighted'}`}
87
+ </Drilldown.Option>
88
+ <Drilldown.Option id="option3">
89
+ <Pill>Pill</Pill> Option
90
+ </Drilldown.Option>
91
+ <Drilldown.Option id="option4" disabled>
92
+ Option
93
+ </Drilldown.Option>
94
+ </Drilldown.Page>
95
+ </Drilldown>
96
+ ```
97
+
98
+ Options can be links too. If the `href` prop is set, the option renders as an `<a>` element.
99
+
100
+ ```js
101
+ ---
102
+ example: true
103
+ ---
104
+ <Drilldown rootPageId='root' width="20rem" maxHeight="30rem">
105
+ <Drilldown.Page id="root">
106
+ <Drilldown.Option id="option1" href="/#Options">
107
+ Options component
108
+ </Drilldown.Option>
109
+ <Drilldown.Option id="option2"href="/#Menu">
110
+ Menu component
111
+ </Drilldown.Option>
112
+ <Drilldown.Option id="option3" href="/#Select">
113
+ Options component
114
+ </Drilldown.Option>
115
+ <Drilldown.Option id="option4" href="/#SimpleSelect" disabled>
116
+ SimpleSelect component
117
+ </Drilldown.Option>
118
+ <Drilldown.Option id="option5" href="/#TreeBrowser">
119
+ TreeBrowser component
120
+ </Drilldown.Option>
121
+ </Drilldown.Page>
122
+ </Drilldown>
123
+ ```
124
+
125
+ Just like `Options.Item`, `Drilldown.Option` can render a description under the label and icons before or after the label.
126
+
127
+ ```js
128
+ ---
129
+ example: true
130
+ ---
131
+ <Drilldown rootPageId='root' width="20rem" maxHeight="30rem">
132
+ <Drilldown.Page id="root">
133
+ <Drilldown.Option
134
+ id="option1"
135
+ description="Curabitur fringilla, urna ut efficitur molestie, nibh lacus tincidunt elit, ut tempor ipsum nunc sit amet massa."
136
+ renderBeforeLabel={IconCheckSolid}
137
+ renderAfterLabel={IconArrowOpenEndSolid}
138
+ beforeLabelContentVAlign="start"
139
+ afterLabelContentVAlign="start"
140
+ >
141
+ Option
142
+ </Drilldown.Option>
143
+ <Drilldown.Option
144
+ id="option2"
145
+ description="Curabitur fringilla, urna ut efficitur molestie, nibh lacus tincidunt elit, ut tempor ipsum nunc sit amet massa."
146
+ renderBeforeLabel={IconCheckSolid}
147
+ renderAfterLabel={IconArrowOpenEndSolid}
148
+ beforeLabelContentVAlign="center"
149
+ afterLabelContentVAlign="center"
150
+ >
151
+ Option
152
+ </Drilldown.Option>
153
+ <Drilldown.Option
154
+ id="option3"
155
+ description="Curabitur fringilla, urna ut efficitur molestie, nibh lacus tincidunt elit, ut tempor ipsum nunc sit amet massa."
156
+ renderBeforeLabel={IconCheckSolid}
157
+ renderAfterLabel={IconArrowOpenEndSolid}
158
+ beforeLabelContentVAlign="end"
159
+ afterLabelContentVAlign="end"
160
+ >
161
+ Option
162
+ </Drilldown.Option>
163
+ </Drilldown.Page>
164
+ </Drilldown>
165
+ ```
166
+
167
+ Additionally, the `renderLabelInfo` prop can render text or other elements next to the label.
168
+
169
+ ```js
170
+ ---
171
+ example: true
172
+ render: false
173
+ ---
174
+
175
+ class VideoSettingsExample extends React.Component {
176
+ state = {
177
+ selectedCaption: 'English',
178
+ selectedSpeed: 'Normal',
179
+ selectedQuality: '720p',
180
+ isCommentsOn: true
181
+ }
182
+
183
+ renderTrigger() {
184
+ return (
185
+ <Button renderIcon={IconSettingsSolid}>
186
+ Video Settings
187
+ </Button>
188
+ )
189
+ }
190
+
191
+ renderSelected(props, value) {
192
+ return (
193
+ <span style={{ color: props.variant === 'default' ? '#6B7780' : undefined }}>
194
+ {value}
195
+ </span>
196
+ )
197
+ }
198
+
199
+ renderSelectGroup(options, stateName) {
200
+ return (
201
+ <Drilldown.Group
202
+ id={`${stateName}Group`}
203
+ selectableType="single"
204
+ defaultSelected={[this.state[stateName]]}
205
+ onSelect={(_event, { value }) => {
206
+ this.setState({ [stateName]: value[0] })
207
+ }}
208
+ >
209
+ {options.map((option, idx) => (
210
+ <Drilldown.Option
211
+ key={idx}
212
+ id={option}
213
+ value={option}
214
+ onOptionClick={(_event, { goToPreviousPage }) => {
215
+ goToPreviousPage()
216
+ }}
217
+ >
218
+ {option}
219
+ </Drilldown.Option>
220
+ ))}
221
+ </Drilldown.Group>
222
+ )
223
+ }
224
+
225
+ render() {
226
+ const {
227
+ captionOptions,
228
+ speedOptions,
229
+ qualityOptions
230
+ } = this.props
231
+
232
+ return (
233
+ <Drilldown
234
+ rootPageId="videoSettings"
235
+ width="18.5rem"
236
+ maxHeight="26.5rem"
237
+ shouldHideOnSelect={false}
238
+ trigger={this.renderTrigger()}
239
+ onToggle={(_event, { shown, goToPage }) => {
240
+ shown && goToPage('videoSettings')
241
+ }}
242
+ >
243
+ <Drilldown.Page
244
+ id="videoSettings"
245
+ renderTitle="Video Settings"
246
+ withoutHeaderSeparator
247
+ >
248
+ <Drilldown.Option
249
+ id="captions"
250
+ subPageId="captions"
251
+ renderLabelInfo={
252
+ (props) => this.renderSelected(props, this.state.selectedCaption)
253
+ }
254
+ >
255
+ Captions
256
+ </Drilldown.Option>
257
+ <Drilldown.Option
258
+ id="speed"
259
+ subPageId="speed"
260
+ renderLabelInfo={
261
+ (props) => this.renderSelected(props, this.state.selectedSpeed)
262
+ }
263
+ >
264
+ Speed
265
+ </Drilldown.Option>
266
+ <Drilldown.Option
267
+ id="quality"
268
+ subPageId="quality"
269
+ renderLabelInfo={
270
+ (props) => this.renderSelected(props, this.state.selectedQuality)
271
+ }
272
+ >
273
+ Quality
274
+ </Drilldown.Option>
275
+ <Drilldown.Option
276
+ id="comments"
277
+ themeOverride={{ padding: '0.25rem 0.75rem' }}
278
+ afterLabelContentVAlign="center"
279
+ onOptionClick={() => {
280
+ this.setState((state) => ({ isCommentsOn: !state.isCommentsOn }))
281
+ }}
282
+ role="checkbox"
283
+ aria-checked={this.state.isCommentsOn ? 'true' : 'false'}
284
+ // prevents reading the label of the checkbox too (duplicated)
285
+ aria-describedby={['']}
286
+
287
+ renderLabelInfo={
288
+ <Checkbox
289
+ label={<ScreenReaderContent>Comments</ScreenReaderContent>}
290
+ variant='toggle'
291
+ readOnly
292
+ checked={this.state.isCommentsOn}
293
+ onChange={() => {
294
+ // needed for controlled Checkbox,
295
+ // but the state is handled on the Drilldown.Option
296
+ }}
297
+ labelPlacement="start"
298
+ tabIndex={-1}
299
+ />
300
+ }
301
+ >
302
+ Comments
303
+ </Drilldown.Option>
304
+ </Drilldown.Page>
305
+
306
+ <Drilldown.Page
307
+ id="captions"
308
+ renderTitle="Captions"
309
+ >
310
+ {this.renderSelectGroup(captionOptions, 'selectedCaption')}
311
+ </Drilldown.Page>
312
+
313
+ <Drilldown.Page
314
+ id="speed"
315
+ renderTitle="Speed"
316
+ >
317
+ {this.renderSelectGroup(speedOptions, 'selectedSpeed')}
318
+ </Drilldown.Page>
319
+
320
+ <Drilldown.Page
321
+ id="quality"
322
+ renderTitle="Quality"
323
+ >
324
+ {this.renderSelectGroup(qualityOptions, 'selectedQuality')}
325
+ </Drilldown.Page>
326
+ </Drilldown>
327
+ )
328
+ }
329
+ }
330
+
331
+ render(<VideoSettingsExample
332
+ captionOptions={['Off', 'English', 'German', 'Spanish', 'Durtch']}
333
+ speedOptions={['0.5x', 'Normal', '1.25x', '1.5x', '2x']}
334
+ qualityOptions={['Auto', '360p', '480p', '720p', '1080p']}
335
+ />)
336
+ ```
337
+
338
+ ### Displaying Drilldown in a Popover
339
+
340
+ Just like [Menu](#Menu), Drilldown accepts a `trigger` property: it will render a toggle button which, when clicked, shows or hides the Drilldown in a [Popover](#Popover). Drilldown passes many of its props to Popover in this case (`mountNode`, `shouldContainFocus`, `withArrow`, etc.).
341
+
342
+ ```js
343
+ ---
344
+ example: true
345
+ render: false
346
+ ---
347
+
348
+ class SelectContactsExample extends React.Component {
349
+ state = {
350
+ selected: []
351
+ }
352
+
353
+ getCategoryById(id) {
354
+ return this.props.contactsData.find(cat => cat.id === id)
355
+ }
356
+
357
+ selectContacts(values) {
358
+ this.setState({ selected: values })
359
+ }
360
+
361
+ renderContacts(contacts) {
362
+ return contacts.map((contact, idx) => {
363
+ const { id, name, email } = contact
364
+
365
+ return (
366
+ <Drilldown.Option
367
+ key={idx}
368
+ id={id}
369
+ value={name}
370
+ description={
371
+ <TruncateText>
372
+ #{idx + 1} | {email}
373
+ </TruncateText>
374
+ }
375
+ onOptionClick={() => {
376
+ this.selectContacts([contact])
377
+ }}
378
+ >
379
+ {name}
380
+ </Drilldown.Option>
381
+ )
382
+ })
383
+ }
384
+
385
+ renderSubCategoryOptions(subCategories = []) {
386
+ return subCategories.map((subCatId, idx) => {
387
+ const category = this.getCategoryById(subCatId)
388
+ const { id, title } = category
389
+
390
+ return (
391
+ <Drilldown.Option key={idx} id={id} subPageId={id}>
392
+ {title}
393
+ </Drilldown.Option>
394
+ )
395
+ })
396
+ }
397
+
398
+ renderPage(category, key) {
399
+ const { id, title, subCategories = [], options = [] } = category
400
+
401
+ const allContacts = this.getAllContactsFromCategory(category)
402
+
403
+ return (
404
+ <Drilldown.Page
405
+ key={key}
406
+ id={id}
407
+ renderTitle={title}
408
+ renderActionLabel={`Send to All (${allContacts.length})`}
409
+ onHeaderActionClicked={() => {
410
+ this.selectContacts(allContacts)
411
+ }}
412
+ >
413
+ {[
414
+ ...this.renderSubCategoryOptions(subCategories),
415
+ ...this.renderContacts(options)
416
+ ]}
417
+ </Drilldown.Page>
418
+ )
419
+ }
420
+
421
+ getAllContactsFromCategory(category) {
422
+ let allContacts = []
423
+
424
+ const addOptions = (cat) => {
425
+ const { subCategories = [], options = [] } = cat
426
+ allContacts.push(...options)
427
+
428
+
429
+ subCategories.forEach((subCatId) => {
430
+ addOptions(this.getCategoryById(subCatId))
431
+ })
432
+ }
433
+
434
+ addOptions(category)
435
+ return allContacts
436
+ }
437
+
438
+ render() {
439
+ const { contactsData } = this.props
440
+ const { selected = [] } = this.state
441
+
442
+ return (
443
+ <Flex alignItems='start'>
444
+ <Flex.Item width="20rem" textAlign="center" padding='x-small 0 0'>
445
+ <Drilldown
446
+ rootPageId="contacts"
447
+ width="18.5rem"
448
+ maxHeight="26.5rem"
449
+ trigger={<Button>Select Contacts</Button>}
450
+ onToggle={(_event, args) => {
451
+ args.shown && this.selectContacts([])
452
+ }}
453
+ >
454
+ {contactsData.map((page, idx) => this.renderPage(page, idx))}
455
+ </Drilldown>
456
+ </Flex.Item>
457
+
458
+ <Flex.Item padding='0 0 0 large' shouldGrow shouldShrink>
459
+ <View as="div" padding='small' background='primary'>
460
+ <div>
461
+ <Text weight='bold'>Selected ({selected ? selected.length : 0}):</Text>
462
+ </div>
463
+ <div>
464
+ {selected.map((a) => a.name).join(', ')}
465
+ </div>
466
+ </View>
467
+ </Flex.Item>
468
+ </Flex>
469
+ )
470
+ }
471
+ }
472
+
473
+ const generateCategory = (name, count) => {
474
+ return Array(count)
475
+ .fill(name)
476
+ .map((category, idx) => {
477
+ const id = category.replace(/(-|\s)/g, '').toLowerCase()
478
+ return {
479
+ id: `${id}${idx + 1}`,
480
+ category,
481
+ name: `${category} ${idx + 1}`,
482
+ email: `${id}${idx + 1}@randommail.com`
483
+ }
484
+ })
485
+ }
486
+
487
+ const contactsData = [
488
+ {
489
+ id: 'contacts',
490
+ title: 'Contacts',
491
+ subCategories: ['administrators', 'teachers', 'students']
492
+ },
493
+ {
494
+ id: 'administrators',
495
+ title: 'Administrators',
496
+ subCategories: ['accountAdmins', 'subAccountAdmins']
497
+ },
498
+ {
499
+ id: 'accountAdmins',
500
+ title: 'Account Admins',
501
+ options: generateCategory('Account Admin', 8)
502
+ },
503
+ {
504
+ id: 'subAccountAdmins',
505
+ title: 'Sub-Account Admins',
506
+ options: generateCategory('Sub-Account Admin', 12)
507
+ },
508
+ {
509
+ id: 'teachers',
510
+ title: 'Teachers',
511
+ options: generateCategory('Teacher', 23)
512
+ },
513
+ {
514
+ id: 'students',
515
+ title: 'Students',
516
+ options: generateCategory('Student', 34)
517
+ }
518
+ ]
519
+
520
+ render(<SelectContactsExample contactsData={contactsData} />)
521
+ ```
522
+
523
+ ### Page header
524
+
525
+ Each page can have a "header" with three optional items. The header and the Drilldown content are separated with a `<Drilldown.Separator>` (can be hidden with the `withoutHeaderSeparator` prop.)
526
+
527
+ ##### Title
528
+
529
+ The `renderTitle` prop sets the title of the Page.
530
+
531
+ ##### Action
532
+
533
+ The `renderActionLabel` displays an "action" option that can be used as e.g.: a "Select all" function. The action itself can be set with the `onHeaderActionClicked` prop.
534
+
535
+ ##### Back navigation
536
+
537
+ On every page (except the root page) a "Back" navigation option is displayed with an arrow.
538
+
539
+ The label can be changed with `renderBackButtonLabel` prop (defaults to "Back"). If a function is passed, it has a `prevPageTitle` parameter.
540
+
541
+ This option will always display on the page when needed and cannot be disabled.
542
+
543
+ ```js
544
+ ---
545
+ example: true
546
+ render: false
547
+ ---
548
+ class BackNavigationExample extends React.Component {
549
+ state = {
550
+ showTitle: true,
551
+ showAction: true,
552
+ showAlternativeBackLabel: false
553
+ }
554
+
555
+ render() {
556
+ const { showTitle, showAction, showAlternativeBackLabel } = this.state
557
+
558
+ return (
559
+ <Flex alignItems='start'>
560
+ <Flex.Item>
561
+ <Drilldown rootPageId='root' width="20rem" maxHeight="30rem">
562
+ <Drilldown.Page
563
+ id="root"
564
+ renderTitle={showTitle && 'Root page'}
565
+ renderActionLabel={showAction && 'Action!'}
566
+ >
567
+ <Drilldown.Option id="option1" subPageId='secondPage'>
568
+ Option
569
+ </Drilldown.Option>
570
+ <Drilldown.Option id="option2" subPageId='secondPage'>
571
+ Option
572
+ </Drilldown.Option>
573
+ <Drilldown.Option id="option3" subPageId='secondPage'>
574
+ Option
575
+ </Drilldown.Option>
576
+ </Drilldown.Page>
577
+
578
+ <Drilldown.Page
579
+ id="secondPage"
580
+ renderTitle={showTitle && 'Second page'}
581
+ renderActionLabel={showAction && 'Action!'}
582
+ renderBackButtonLabel={showAlternativeBackLabel
583
+ ? ((prevPageTitle) => prevPageTitle ? `Back to ${prevPageTitle}` : 'Back')
584
+ : undefined}
585
+ >
586
+ <Drilldown.Option id="option1">Option</Drilldown.Option>
587
+ <Drilldown.Option id="option2">Option</Drilldown.Option>
588
+ <Drilldown.Option id="option3">Option</Drilldown.Option>
589
+ </Drilldown.Page>
590
+ </Drilldown>
591
+ </Flex.Item>
592
+
593
+ <Flex.Item padding="0 0 0 large" shouldGrow shouldShrink>
594
+ <FormFieldGroup description="Drilldown Group example settings">
595
+ <Checkbox
596
+ checked={!this.state.showTitle ? false : this.state.showAlternativeBackLabel}
597
+ disabled={!this.state.showTitle}
598
+ label='Display previous page title in back navigation ("Show page title" needs to be turned on)'
599
+ variant="toggle"
600
+ onChange={
601
+ () => {
602
+ this.setState({ showAlternativeBackLabel: !this.state.showAlternativeBackLabel })
603
+ }
604
+ }
605
+ />
606
+ <Checkbox
607
+ checked={this.state.showTitle}
608
+ label="Show page title"
609
+ variant="toggle"
610
+ onChange={
611
+ () => {
612
+ this.setState({ showTitle: !this.state.showTitle })
613
+ }
614
+ }
615
+ />
616
+ <Checkbox
617
+ checked={this.state.showAction}
618
+ label="Show action label"
619
+ variant="toggle"
620
+ onChange={
621
+ () => {
622
+ this.setState({ showAction: !this.state.showAction })
623
+ }
624
+ }
625
+ />
626
+ </FormFieldGroup>
627
+ </Flex.Item>
628
+ </Flex>
629
+ )
630
+ }
631
+ }
632
+
633
+ render(<BackNavigationExample />)
634
+ ```
635
+
636
+ ### Option Groups
637
+
638
+ Wrapping the Options in `<Drilldown.Group>` will separate these options with separators. These separators can be hidden, and you can provide a label with the `renderGroupTitle` prop.
639
+
640
+ ```js
641
+ ---
642
+ example: true
643
+ render: false
644
+ ---
645
+ class GroupsExample extends React.Component {
646
+ state = {
647
+ showSeparators: true,
648
+ showTitles: true
649
+ }
650
+
651
+ render() {
652
+ return (
653
+ <Flex alignItems='start'>
654
+ <Flex.Item>
655
+ <Drilldown rootPageId='root' width="20rem" maxHeight="30rem">
656
+ <Drilldown.Page id="root">
657
+ <Drilldown.Group
658
+ id="italy"
659
+ renderGroupTitle={this.state.showTitles && 'Italy'}
660
+ withoutSeparators={!this.state.showSeparators}
661
+ >
662
+ <Drilldown.Option id="item11">Milano</Drilldown.Option>
663
+ <Drilldown.Option id="item12">Florence</Drilldown.Option>
664
+ </Drilldown.Group>
665
+
666
+ <Drilldown.Group
667
+ id="france"
668
+ renderGroupTitle={this.state.showTitles && 'France'}
669
+ withoutSeparators={!this.state.showSeparators}
670
+ >
671
+ <Drilldown.Option id="item21">Lyon</Drilldown.Option>
672
+ <Drilldown.Option id="item22">Bordeaux</Drilldown.Option>
673
+ </Drilldown.Group>
674
+
675
+ <Drilldown.Group
676
+ id="germany"
677
+ renderGroupTitle={this.state.showTitles && 'Germany'}
678
+ withoutSeparators={!this.state.showSeparators}
679
+ >
680
+ <Drilldown.Option id="item31">Frankfurt</Drilldown.Option>
681
+ <Drilldown.Option id="item32">Dortmund</Drilldown.Option>
682
+ </Drilldown.Group>
683
+ </Drilldown.Page>
684
+ </Drilldown>
685
+ </Flex.Item>
686
+
687
+ <Flex.Item padding="0 0 0 large">
688
+ <FormFieldGroup description="Drilldown Group example settings">
689
+ <Checkbox
690
+ checked={this.state.showSeparators}
691
+ label="Show separators"
692
+ variant="toggle"
693
+ onChange={
694
+ () => {
695
+ this.setState({ showSeparators: !this.state.showSeparators })
696
+ }
697
+ }
698
+ />
699
+ <Checkbox
700
+ checked={this.state.showTitles}
701
+ label="Show group titles"
702
+ variant="toggle"
703
+ onChange={
704
+ () => {
705
+ this.setState({ showTitles: !this.state.showTitles })
706
+ }
707
+ }
708
+ />
709
+ </FormFieldGroup>
710
+ </Flex.Item>
711
+ </Flex>
712
+ )
713
+ }
714
+ }
715
+
716
+ render(<GroupsExample />)
717
+ ```
718
+
719
+ #### Selectable Groups
720
+
721
+ The `selectableType` prop makes a group either a single-select (radio) or multi-select (checkbox) group. Selected options are indicated with a "check" icon.
722
+
723
+ It is recommended to set the `value` prop of the options in a group, because the `defaultSelected` and `onSelect` props are based on these values.
724
+
725
+ ##### Single-select Group
726
+
727
+ ```js
728
+ ---
729
+ example: true
730
+ ---
731
+ <Drilldown rootPageId='root' width="20rem" maxHeight="30rem">
732
+ <Drilldown.Page id="root">
733
+ <Drilldown.Group
734
+ id="group1"
735
+ renderGroupTitle="Select one option"
736
+ selectableType='single'
737
+ >
738
+ <Drilldown.Option id="+2" value="+2" defaultSelected>
739
+ Strongly agree
740
+ </Drilldown.Option>
741
+ <Drilldown.Option id="+1" value="+1">
742
+ Somewhat agree
743
+ </Drilldown.Option>
744
+ <Drilldown.Option id="0" value="0">
745
+ Neither agree nor disagree
746
+ </Drilldown.Option>
747
+ <Drilldown.Option id="-1" value="-1">
748
+ Somewhat disagree
749
+ </Drilldown.Option>
750
+ <Drilldown.Option id="-2" value="-2">
751
+ Strongly disagree
752
+ </Drilldown.Option>
753
+ </Drilldown.Group>
754
+ </Drilldown.Page>
755
+ </Drilldown>
756
+ ```
757
+
758
+ ##### Multi-select Group
759
+
760
+ ```js
761
+ ---
762
+ example: true
763
+ render: false
764
+ ---
765
+
766
+ class SelectMembersExample extends React.Component {
767
+ state = {
768
+ selected: {}
769
+ }
770
+
771
+ selectMembers(groupValues) {
772
+ this.setState(({ selected }) => {
773
+ return { selected: { ...selected, ...groupValues }}
774
+ })
775
+ }
776
+
777
+ renderGroups(groups) {
778
+ return groups.map((group, idx) => {
779
+ const { id, label, members, selectableType } = group
780
+
781
+ return (
782
+ <Drilldown.Group
783
+ id={id}
784
+ key={idx}
785
+ renderGroupTitle={label}
786
+ selectableType={selectableType}
787
+ onSelect={(_e, { value }) => {
788
+ this.selectMembers({ [id]: value })
789
+ }}
790
+ >
791
+ {members.map((member, idx) => {
792
+ const { id, name } = member
793
+ return (
794
+ <Drilldown.Option key={idx} id={id} value={name}>
795
+ {name}
796
+ </Drilldown.Option>
797
+ )
798
+ })}
799
+
800
+ </Drilldown.Group>
801
+ )
802
+ })
803
+ }
804
+
805
+ renderSubPageOptions(subPages = []) {
806
+ return subPages.map((subPage, idx) => {
807
+ const { id, label } = subPage
808
+
809
+ return (
810
+ <Drilldown.Option key={idx} id={id} subPageId={id}>
811
+ {label}
812
+ </Drilldown.Option>
813
+ )
814
+ })
815
+ }
816
+
817
+ renderPage(category, key) {
818
+ const { id, subPages = [], groups = [] } = category
819
+
820
+ return (
821
+ <Drilldown.Page key={key} id={id}>
822
+ {subPages.length > 0 && this.renderSubPageOptions(subPages)}
823
+ {groups.length > 0 && this.renderGroups(groups)}
824
+ </Drilldown.Page>
825
+ )
826
+ }
827
+
828
+ render() {
829
+ const { pages } = this.props
830
+ const { selected = {} } = this.state
831
+
832
+ return (
833
+ <Flex alignItems='start'>
834
+ <Flex.Item width="20rem" textAlign="center" padding='x-small 0 0'>
835
+ <Drilldown
836
+ rootPageId="root"
837
+ width="18.5rem"
838
+ maxHeight="26.5rem"
839
+ trigger={<Button>Select Members</Button>}
840
+ shouldHideOnSelect={false}
841
+ onToggle={(_event, { shown }) => {
842
+ shown && this.selectMembers({})
843
+ }}
844
+ >
845
+ {pages.map((page, idx) => this.renderPage(page, idx))}
846
+ </Drilldown>
847
+ </Flex.Item>
848
+
849
+ <Flex.Item padding='0 0 0 large' shouldGrow shouldShrink>
850
+ <View as="div" padding='small' background='primary'>
851
+ <div>
852
+ <Text weight='bold'>Selected members:</Text>
853
+ </div>
854
+ <div>
855
+ <List>
856
+ {Object.entries(selected).map(([ groupId, values ], idx) => {
857
+ return values.length > 0 ? (
858
+ <List.Item key={idx}>
859
+ <b>{groupId}</b>: {values.join(', ')}
860
+ </List.Item>
861
+ ) : null
862
+ })}
863
+ </List>
864
+ </div>
865
+ </View>
866
+ </Flex.Item>
867
+ </Flex>
868
+ )
869
+ }
870
+ }
871
+
872
+ const pages = [
873
+ {
874
+ id: 'root',
875
+ subPages: [
876
+ { id: 'courses', label: 'Courses' },
877
+ { id: 'groups', label: 'Groups' },
878
+ { id: 'consortiums', label: 'Consortiums' }
879
+ ]
880
+ },
881
+ {
882
+ id: 'courses',
883
+ groups: [
884
+ {
885
+ id: 'course1',
886
+ label: 'Course 1',
887
+ selectableType: 'multiple',
888
+ members: [
889
+ { id: 'course1_1', name: 'Hanna Septimus' },
890
+ { id: 'course1_2', name: 'Kadin Press' },
891
+ { id: 'course1_3', name: 'Kaiya Botosh' },
892
+ ]
893
+ },
894
+ {
895
+ id: 'course2',
896
+ label: 'Course 2',
897
+ selectableType: 'multiple',
898
+ members: [
899
+ { id: 'course2_1', name: 'Leo Calzoni' },
900
+ { id: 'course2_2', name: 'Gretchen Gouse' },
901
+ ]
902
+ }
903
+ ]
904
+ },
905
+ {
906
+ id: 'groups',
907
+ groups: [
908
+ {
909
+ id: 'group1',
910
+ label: 'Group 1',
911
+ selectableType: 'multiple',
912
+ members: [
913
+ { id: 'groups1_1', name: 'Penka Okabe' },
914
+ { id: 'groups1_2', name: 'Ausma Meggyesfalvi' },
915
+ { id: 'groups1_3', name: 'Endrit Höfler' },
916
+ { id: 'groups1_4', name: 'Ryuu Carey' },
917
+ { id: 'groups1_5', name: 'Daphne Dioli' },
918
+ ]
919
+ }
920
+ ]
921
+ },
922
+ {
923
+ id: 'consortiums',
924
+ groups: [
925
+ {
926
+ id: 'consortium1',
927
+ label: 'Consortium 1',
928
+ selectableType: 'multiple',
929
+ members: [
930
+ { id: 'consortium1_1', name: 'Brahim Gustavsson' },
931
+ { id: 'consortium1_2', name: 'Elodia Dreschner' },
932
+ ]
933
+ },
934
+ {
935
+ id: 'consortium2',
936
+ label: 'Consortium 2',
937
+ selectableType: 'multiple',
938
+ members: [
939
+ { id: 'consortium2_1', name: 'Darwin Peter' },
940
+ { id: 'consortium2_2', name: 'Sukhrab Burnham' },
941
+ ]
942
+ },
943
+ {
944
+ id: 'consortium3',
945
+ label: 'Consortium 3',
946
+ selectableType: 'multiple',
947
+ members: [
948
+ { id: 'consortium3_1', name: 'Jeffry Antonise' },
949
+ { id: 'consortium3_2', name: 'Bia Regenbogen' },
950
+ ]
951
+ }
952
+ ]
953
+ }
954
+ ]
955
+
956
+ render(<SelectMembersExample pages={pages} />)
957
+ ```
958
+
959
+ ### Disabled prop
960
+
961
+ You can disable the whole Drilldown or its sub-components with the `disabled` prop. The only option that can not be disabled is the Back navigation.
962
+
963
+ ```js
964
+ ---
965
+ example: true
966
+ render: false
967
+ ---
968
+ class DisabledExample extends React.Component {
969
+ state = {
970
+ disabled: 'None',
971
+ withTrigger: false
972
+ }
973
+
974
+ render() {
975
+ const { disabled, withTrigger } = this.state
976
+
977
+ const disabledDrilldown = disabled === 'Drilldown'
978
+ const disabledPages = disabled === 'Pages'
979
+ const disabledGroups = disabled === 'Groups'
980
+ const disabledOptions = disabled === 'Options'
981
+
982
+ return (
983
+ <Flex alignItems='start'>
984
+ <Flex.Item width="20rem" textAlign='center' padding='x-small 0 0'>
985
+ <View textAlign='start'>
986
+ <Drilldown
987
+ rootPageId='root'
988
+ width="20rem"
989
+ maxHeight="30rem"
990
+ trigger={withTrigger && <Button>Toggle button</Button>}
991
+ disabled={disabledDrilldown}
992
+ >
993
+ <Drilldown.Page
994
+ id="root"
995
+ renderTitle="Root page"
996
+ renderActionLabel="Action"
997
+ disabled={disabledPages}
998
+ >
999
+ <Drilldown.Option
1000
+ id="option1"
1001
+ subPageId='secondPage'
1002
+ disabled={disabledOptions}
1003
+ >
1004
+ Option with subPage navigation
1005
+ </Drilldown.Option>
1006
+ <Drilldown.Option id="option2" disabled={disabledOptions}>
1007
+ Option
1008
+ </Drilldown.Option>
1009
+ <Drilldown.Group
1010
+ id="group1"
1011
+ renderGroupTitle="Selectable Group"
1012
+ selectableType='multiple'
1013
+ disabled={disabledGroups}
1014
+ >
1015
+ {['Apple', 'Orange', 'Banana', 'Strawberry'].map(
1016
+ item => (
1017
+ <Drilldown.Option
1018
+ id={item}
1019
+ key={item}
1020
+ value={item}
1021
+ defaultSelected={item === 'Apple'}
1022
+ disabled={disabledOptions}
1023
+ >
1024
+ {item}
1025
+ </Drilldown.Option>
1026
+ )
1027
+ )}
1028
+ </Drilldown.Group>
1029
+ </Drilldown.Page>
1030
+
1031
+ <Drilldown.Page
1032
+ id="secondPage"
1033
+ renderTitle="Second page"
1034
+ disabled={disabledPages}
1035
+ >
1036
+ {['Option 1', 'Option 2', 'Option 3', 'Option 4'].map(
1037
+ item => (
1038
+ <Drilldown.Option
1039
+ id={item}
1040
+ key={item}
1041
+ disabled={disabledOptions}
1042
+ >
1043
+ {item}
1044
+ </Drilldown.Option>
1045
+ )
1046
+ )}
1047
+ </Drilldown.Page>
1048
+ </Drilldown>
1049
+ </View>
1050
+ </Flex.Item>
1051
+
1052
+ <Flex.Item padding="0 0 0 large" shouldGrow shouldShrink>
1053
+ <FormFieldGroup
1054
+ description={<ScreenReaderContent>Settings</ScreenReaderContent>}
1055
+ colSpacing="medium"
1056
+ layout="columns"
1057
+ vAlign="top"
1058
+ >
1059
+ <RadioInputGroup
1060
+ onChange={(_event, value) => {
1061
+ this.setState({ disabled: value })
1062
+ }}
1063
+ defaultValue="None"
1064
+ name="disabledPart"
1065
+ description="Select disabled"
1066
+ >
1067
+ {['None', 'Drilldown', 'Pages', 'Groups', 'Options'].map(part => <RadioInput key={part} value={part} label={part} />)}
1068
+ </RadioInputGroup>
1069
+
1070
+ <Checkbox
1071
+ checked={this.state.withTrigger}
1072
+ label="With toggle button"
1073
+ variant="toggle"
1074
+ onChange={
1075
+ () => {
1076
+ this.setState({ withTrigger: !this.state.withTrigger })
1077
+ }
1078
+ }
1079
+ />
1080
+ </FormFieldGroup>
1081
+ </Flex.Item>
1082
+ </Flex>
1083
+ )
1084
+ }
1085
+ }
1086
+
1087
+ render(<DisabledExample />)
1088
+ ```
1089
+
1090
+ ### shouldHideOnSelect
1091
+
1092
+ By default, if the Drilldown is in a Popover, it will hide if an option is selected (except on page navigation). You can disable this behavior by setting `shouldHideOnSelect={false}`.
1093
+
1094
+ You can still manually close the Popover on option click or select, calling the `.hide()` method on the `drilldown` parameter of the `onOptionClick` and `onSelect` callbacks.
1095
+
1096
+ ```js
1097
+ ---
1098
+ example: true
1099
+ ---
1100
+ <Drilldown
1101
+ rootPageId='root'
1102
+ width="20rem"
1103
+ maxHeight="30rem"
1104
+ trigger={<Button>Toggle button</Button>}
1105
+ shouldHideOnSelect={false}
1106
+ >
1107
+ <Drilldown.Page id="root" renderTitle='Should not hide on select'>
1108
+ <Drilldown.Option id="option1">
1109
+ Option
1110
+ </Drilldown.Option>
1111
+ <Drilldown.Option id="option2">
1112
+ Option
1113
+ </Drilldown.Option>
1114
+ <Drilldown.Option id="option3">
1115
+ Option
1116
+ </Drilldown.Option>
1117
+ <Drilldown.Option
1118
+ id="close"
1119
+ renderBeforeLabel={<IconXLine />}
1120
+ onOptionClick={(_event, data) => {
1121
+ data.drilldown.hide()
1122
+ }}
1123
+ themeOverride={(_componentTheme, currentTheme) => {
1124
+ return { color: currentTheme.colors.textDanger }
1125
+ }}
1126
+ >
1127
+ Close Popover
1128
+ </Drilldown.Option>
1129
+ </Drilldown.Page>
1130
+ </Drilldown>
1131
+ ```
1132
+
1133
+ ### Page navigation
1134
+
1135
+ The recommended way to navigate between pages is to add the `subaPageId` prop to the `<Drilldown.Option>`-s. This will make an arrow display on the option to indicate that it navigates to another page.
1136
+
1137
+ If you have to manually navigate to another page on click or on Popover toggle, the `onOptionClick` and `onToggle` callbacks expose methods for page navigation and the current page history.
1138
+
1139
+ - `pageHistory`: An array of the Page Id-s in "breadcrumbs" fashion.
1140
+ - `goToPreviousPage`: A method that navigates to the previous page (if not on root page).
1141
+ - `goToPage`: A method that navigates to a page, defined by the page id. If that page is already in the page history, it goes back to that level. If it is not in the history and the page exists, it adds it to the page history and goes there.
1142
+
1143
+ These two navigation methods are also available as public methods on the Drilldown component.
1144
+
1145
+ ```js
1146
+ ---
1147
+ example: true
1148
+ ---
1149
+ <Drilldown
1150
+ rootPageId='Root Page'
1151
+ width="20rem"
1152
+ maxHeight="30rem"
1153
+ >
1154
+ <Drilldown.Page id="Root Page" renderTitle='Root page'>
1155
+ <Drilldown.Option id="option1" subPageId='Page Two'>
1156
+ Go to Page Two
1157
+ </Drilldown.Option>
1158
+ <Drilldown.Option id="option2" subPageId='Page Three'>
1159
+ Go to Page Three
1160
+ </Drilldown.Option>
1161
+ <Drilldown.Option
1162
+ id="option3"
1163
+ onOptionClick={(_event, args) => {
1164
+ console.log(args.pageHistory)
1165
+ const nav = args.goToPage('Page Two')
1166
+ console.log(`Navigated from "${nav.prevPageId}" to "${nav.newPageId}"`)
1167
+ }}
1168
+ description='Navigates to Page Two on click and logs the pageHistory on the console.'
1169
+ >
1170
+ Manual navigation to Page Two
1171
+ </Drilldown.Option>
1172
+ </Drilldown.Page>
1173
+
1174
+ <Drilldown.Page id="Page Two" renderTitle='Page Two'>
1175
+ <Drilldown.Option id="option1" subPageId='Page Three'>
1176
+ Go to Page Three
1177
+ </Drilldown.Option>
1178
+ <Drilldown.Option
1179
+ id="option2"
1180
+ onOptionClick={(_event, args) => {
1181
+ console.log(args.pageHistory)
1182
+ const nav = args.goToPreviousPage()
1183
+ console.log(`Navigated back from "${nav.prevPageId}" to previous "${nav.newPageId}"`)
1184
+ }}
1185
+ description='Navigates to the previous page on click and logs the pageHistory on the console.'
1186
+ >
1187
+ Manual "Back" navigation
1188
+ </Drilldown.Option>
1189
+ <Drilldown.Option
1190
+ id="option3"
1191
+ onOptionClick={(_event, args) => {
1192
+ console.log(args.pageHistory)
1193
+ const nav = args.goToPage('Page Three')
1194
+ console.log(`Navigated from "${nav.prevPageId}" to "${nav.newPageId}"`)
1195
+ }}
1196
+ description='Navigates to Page Two on click and logs the pageHistory on the console.'
1197
+ >
1198
+ Manual navigation to Page Three
1199
+ </Drilldown.Option>
1200
+ </Drilldown.Page>
1201
+
1202
+ <Drilldown.Page id="Page Three" renderTitle='Page Three'>
1203
+ <Drilldown.Option
1204
+ id="option1"
1205
+ onOptionClick={(_event, args) => {
1206
+ console.log(args.pageHistory)
1207
+ const nav = args.goToPage(args.pageHistory[0])
1208
+ console.log(`Navigated from "${nav.prevPageId}" to "${nav.newPageId}"`)
1209
+ }}
1210
+ description='Navigates to the first page on click and logs the pageHistory on the console.'
1211
+ >
1212
+ Manual "Back" navigation
1213
+ </Drilldown.Option>
1214
+ <Drilldown.Option
1215
+ id="option2"
1216
+ onOptionClick={(_event, args) => {
1217
+ console.log(args.pageHistory)
1218
+ const nav = args.goToPreviousPage()
1219
+ console.log(`Navigated back from "${nav.prevPageId}" to previous "${nav.newPageId}"`)
1220
+ }}
1221
+ description='Navigates to the previous page on click and logs the pageHistory on the console.'
1222
+ >
1223
+ Manual "Back" navigation
1224
+ </Drilldown.Option>
1225
+ </Drilldown.Page>
1226
+ </Drilldown>
1227
+ ```
1228
+
1229
+ ### Editable structures
1230
+
1231
+ The following example showcases an editable drilldown that can add or delete options.
1232
+
1233
+ ```js
1234
+ ---
1235
+ example: true
1236
+ render: false
1237
+ ---
1238
+
1239
+ class EditableStructureExample extends React.Component {
1240
+ constructor(props) {
1241
+ super(props)
1242
+
1243
+ this.state = {
1244
+ districtsData: {
1245
+ d1: {
1246
+ label: 'District 1',
1247
+ schools: ['s1']
1248
+ }
1249
+ },
1250
+ schoolsData: {
1251
+ s1: {
1252
+ label: 'School 1',
1253
+ districtId: 'd1',
1254
+ isSelected: false
1255
+ }
1256
+ }
1257
+ }
1258
+
1259
+ this.districtCounter = Object.keys(this.state.districtsData).length
1260
+ this.schoolCounter = Object.keys(this.state.schoolsData).length
1261
+ }
1262
+
1263
+ toggleSelectedSchool(id, school) {
1264
+ this.setState({ schoolsData: {
1265
+ ...this.state.schoolsData,
1266
+ [id]: { ...school, isSelected: !school.isSelected }
1267
+ } })
1268
+ }
1269
+
1270
+ renderSelectedIcon(isSelected) {
1271
+ return (
1272
+ <IconCheckSolid
1273
+ style={{ opacity: isSelected ? 1 : 0 }}
1274
+ />
1275
+ )
1276
+ }
1277
+
1278
+ renderAddAction(label) {
1279
+ return (
1280
+ <span>
1281
+ <IconAddSolid />
1282
+ <View as="span" margin="0 0 0 x-small">New {label}</View>
1283
+ </span>
1284
+ )
1285
+ }
1286
+
1287
+ addDistrict() {
1288
+ const { districtsData } = this.state
1289
+ this.districtCounter++
1290
+ const districtNumber = this.districtCounter
1291
+ const districtId = `d${districtNumber}`
1292
+
1293
+ this.setState({
1294
+ districtsData: {
1295
+ ...districtsData,
1296
+ [districtId]: {
1297
+ label: `District ${districtNumber}`,
1298
+ schools: []
1299
+ }
1300
+ }
1301
+ })
1302
+ }
1303
+
1304
+ addSchool(districtId) {
1305
+ const { districtsData, schoolsData } = this.state
1306
+ const district = districtsData[districtId]
1307
+
1308
+ this.schoolCounter++
1309
+ const schoolNumber = this.schoolCounter
1310
+ const schoolId = `s${schoolNumber}`
1311
+
1312
+ this.setState({
1313
+ districtsData: {
1314
+ ...districtsData,
1315
+ [districtId]: {
1316
+ ...district,
1317
+ schools: [...district.schools, schoolId]
1318
+ }
1319
+ },
1320
+ schoolsData: {
1321
+ ...schoolsData,
1322
+ [schoolId]: {
1323
+ label: `School ${schoolNumber}`,
1324
+ districtId,
1325
+ isSelected: false
1326
+ }
1327
+ }
1328
+ })
1329
+ }
1330
+
1331
+ renderDeleteOption(type, label, idToDelete) {
1332
+ const id = type === 'school' ? "deleteSchool" : "deleteDistrict"
1333
+ const callback = type === 'school' ? this.deleteSchool : this.deleteDistrict
1334
+ const separatorId = `${idToDelete}__separator`
1335
+
1336
+ return [
1337
+ <Drilldown.Separator id={separatorId} key={separatorId} />,
1338
+ <Drilldown.Option
1339
+ id={id}
1340
+ key={`${idToDelete}__${id}`}
1341
+ onOptionClick={() => { callback(idToDelete) }}
1342
+ themeOverride={(_componentTheme, currentTheme) => {
1343
+ return { color: currentTheme.colors.textDanger }
1344
+ }}
1345
+ >
1346
+ <IconTrashLine />
1347
+ <View as='span' margin='0 0 0 x-small'>
1348
+ Delete {label}
1349
+ </View>
1350
+ </Drilldown.Option>
1351
+ ]
1352
+ }
1353
+
1354
+ deleteSchool = (id) => {
1355
+ const { districtsData, schoolsData } = this.state
1356
+ const { [id]: school, ...restSchools } = schoolsData
1357
+ const { districtId } = school
1358
+ const district = districtsData[districtId]
1359
+
1360
+ this.setState({
1361
+ schoolsData: restSchools,
1362
+ districtsData: {
1363
+ ...districtsData,
1364
+ [districtId]: {
1365
+ ...district,
1366
+ schools: district.schools.filter(schoolId => schoolId !== id)
1367
+ }
1368
+ }
1369
+ })
1370
+ }
1371
+
1372
+ deleteDistrict = (id) => {
1373
+ const { districtsData, schoolsData } = this.state
1374
+ const { [id]: district, ...restDistricts } = districtsData
1375
+
1376
+ const filteredSchools = {}
1377
+
1378
+ Object.entries(schoolsData).forEach(([schoolId, school]) => {
1379
+ if (school.districtId !== id) {
1380
+ filteredSchools[schoolId] = school
1381
+ }
1382
+ })
1383
+
1384
+ this.setState({
1385
+ districtsData: restDistricts,
1386
+ schoolsData: filteredSchools
1387
+ })
1388
+ }
1389
+
1390
+ render() {
1391
+ const { districtsData, schoolsData } = this.state
1392
+ const districts = Object.entries(districtsData)
1393
+ const schools = Object.entries(schoolsData)
1394
+
1395
+ return (
1396
+ <Drilldown
1397
+ rootPageId="root"
1398
+ width="18.5rem"
1399
+ maxHeight="26.5rem"
1400
+ >
1401
+ <Drilldown.Page
1402
+ id="root"
1403
+ renderTitle="Districts"
1404
+ renderActionLabel={this.renderAddAction('District')}
1405
+ onHeaderActionClicked={() => {
1406
+ this.addDistrict()
1407
+ }}
1408
+ >
1409
+ {districts.map(([id, district]) => {
1410
+ const { label } = district
1411
+ return (
1412
+ <Drilldown.Option
1413
+ key={`districtOption__${id}`}
1414
+ id={id}
1415
+ subPageId={id}
1416
+ >
1417
+ {label}
1418
+ </Drilldown.Option>
1419
+ )
1420
+ })}
1421
+ </Drilldown.Page>
1422
+
1423
+ {districts.map(([districtId , district]) => {
1424
+ const { label, schools } = district
1425
+ return (
1426
+ <Drilldown.Page
1427
+ key={`districtPage__${districtId}`}
1428
+ id={districtId}
1429
+ renderTitle={label}
1430
+ renderActionLabel={this.renderAddAction('School')}
1431
+ onHeaderActionClicked={() => {
1432
+ this.addSchool(districtId)
1433
+ }}
1434
+ >
1435
+ {schools.map((id) => {
1436
+ const { label, isSelected } = this.state.schoolsData[id]
1437
+ return (
1438
+ <Drilldown.Option
1439
+ key={`schoolOption__${districtId}--${id}`}
1440
+ id={id}
1441
+ subPageId={id}
1442
+ renderBeforeLabel={this.renderSelectedIcon(isSelected)}
1443
+ >
1444
+ {label}
1445
+ </Drilldown.Option>
1446
+ )
1447
+ })}
1448
+ {this.renderDeleteOption('district', label, districtId)}
1449
+ </Drilldown.Page>
1450
+ )
1451
+ })}
1452
+
1453
+ {schools.map(([id, school]) => {
1454
+ const { label, isSelected } = school
1455
+ return (
1456
+ <Drilldown.Page
1457
+ key={`schoolPage__${id}`}
1458
+ id={id}
1459
+ withoutHeaderSeparator
1460
+ >
1461
+ <Drilldown.Option
1462
+ id="toggleSchool"
1463
+ renderBeforeLabel={this.renderSelectedIcon(isSelected)}
1464
+ onOptionClick={() => {
1465
+ this.toggleSelectedSchool(id, school)
1466
+ }}
1467
+ >
1468
+ {isSelected ? 'Deselect' : 'Select'} {label}
1469
+ </Drilldown.Option>
1470
+ {this.renderDeleteOption('school', label, id)}
1471
+ </Drilldown.Page>
1472
+ )
1473
+ })}
1474
+ </Drilldown>
1475
+ )
1476
+ }
1477
+ }
1478
+
1479
+ render(<EditableStructureExample />)
1480
+ ```
1481
+
1482
+ ### Guidelines
1483
+
1484
+ ```js
1485
+ ---
1486
+ guidelines: true
1487
+ ---
1488
+ <Guidelines>
1489
+ <Figure recommendation="yes" title="Do">
1490
+ <Figure.Item>
1491
+ Use to display tree structures (instead of the TreeBrowser)
1492
+ </Figure.Item>
1493
+ <Figure.Item>
1494
+ Use instead of flyout menus
1495
+ </Figure.Item>
1496
+ <Figure.Item>
1497
+ Use for radio or checkbox type interactions
1498
+ </Figure.Item>
1499
+ <Figure.Item>
1500
+ Make the text within Drilldown options direct and self-evident so users can quickly decide on an action
1501
+ </Figure.Item>
1502
+ </Figure>
1503
+ <Figure recommendation="no" title="Don't">
1504
+ <Figure.Item>
1505
+ Include complex content
1506
+ </Figure.Item>
1507
+ <Figure.Item>
1508
+ Use content that is not a Drilldown.Option (eg. buttons)
1509
+ </Figure.Item>
1510
+ <Figure.Item>
1511
+ Use the “back” option to display group names
1512
+ </Figure.Item>
1513
+ </Figure>
1514
+ </Guidelines>
1515
+ ```