@instructure/ui-drilldown 10.0.1-snapshot-11 → 10.0.1-snapshot-13

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.
@@ -166,67 +166,204 @@ type: example
166
166
 
167
167
  Additionally, the `renderLabelInfo` prop can render text or other elements next to the label.
168
168
 
169
- ```js
170
- ---
171
- type: example
172
- ---
169
+ - ```js
170
+ class VideoSettingsExample extends React.Component {
171
+ state = {
172
+ selectedCaption: 'English',
173
+ selectedSpeed: 'Normal',
174
+ selectedQuality: '720p',
175
+ isCommentsOn: true
176
+ }
173
177
 
174
- class VideoSettingsExample extends React.Component {
175
- state = {
176
- selectedCaption: 'English',
177
- selectedSpeed: 'Normal',
178
- selectedQuality: '720p',
179
- isCommentsOn: true
180
- }
178
+ renderTrigger() {
179
+ return <Button renderIcon={IconSettingsSolid}>Video Settings</Button>
180
+ }
181
181
 
182
- renderTrigger() {
183
- return (
184
- <Button renderIcon={IconSettingsSolid}>
185
- Video Settings
186
- </Button>
187
- )
188
- }
182
+ renderSelected(props, value) {
183
+ return (
184
+ <span
185
+ style={{ color: props.variant === 'default' ? '#6B7780' : undefined }}
186
+ >
187
+ {value}
188
+ </span>
189
+ )
190
+ }
189
191
 
190
- renderSelected(props, value) {
191
- return (
192
- <span style={{ color: props.variant === 'default' ? '#6B7780' : undefined }}>
193
- {value}
194
- </span>
195
- )
196
- }
192
+ renderSelectGroup(options, stateName) {
193
+ return (
194
+ <Drilldown.Group
195
+ id={`${stateName}Group`}
196
+ selectableType="single"
197
+ defaultSelected={[this.state[stateName]]}
198
+ onSelect={(_event, { value }) => {
199
+ this.setState({ [stateName]: value[0] })
200
+ }}
201
+ >
202
+ {options.map((option, idx) => (
203
+ <Drilldown.Option
204
+ key={idx}
205
+ id={option}
206
+ value={option}
207
+ onOptionClick={(_event, { goToPreviousPage }) => {
208
+ goToPreviousPage()
209
+ }}
210
+ >
211
+ {option}
212
+ </Drilldown.Option>
213
+ ))}
214
+ </Drilldown.Group>
215
+ )
216
+ }
197
217
 
198
- renderSelectGroup(options, stateName) {
199
- return (
200
- <Drilldown.Group
201
- id={`${stateName}Group`}
202
- selectableType="single"
203
- defaultSelected={[this.state[stateName]]}
204
- onSelect={(_event, { value }) => {
205
- this.setState({ [stateName]: value[0] })
206
- }}
207
- >
208
- {options.map((option, idx) => (
209
- <Drilldown.Option
210
- key={idx}
211
- id={option}
212
- value={option}
213
- onOptionClick={(_event, { goToPreviousPage }) => {
214
- goToPreviousPage()
215
- }}
218
+ render() {
219
+ const { captionOptions, speedOptions, qualityOptions } = this.props
220
+
221
+ return (
222
+ <Drilldown
223
+ rootPageId="videoSettings"
224
+ width="18.5rem"
225
+ maxHeight="26.5rem"
226
+ shouldHideOnSelect={false}
227
+ trigger={this.renderTrigger()}
228
+ onToggle={(_event, { shown, goToPage }) => {
229
+ shown && goToPage('videoSettings')
230
+ }}
231
+ >
232
+ <Drilldown.Page
233
+ id="videoSettings"
234
+ renderTitle="Video Settings"
235
+ withoutHeaderSeparator
216
236
  >
217
- {option}
218
- </Drilldown.Option>
219
- ))}
220
- </Drilldown.Group>
221
- )
237
+ <Drilldown.Option
238
+ id="captions"
239
+ subPageId="captions"
240
+ renderLabelInfo={(props) =>
241
+ this.renderSelected(props, this.state.selectedCaption)
242
+ }
243
+ >
244
+ Captions
245
+ </Drilldown.Option>
246
+ <Drilldown.Option
247
+ id="speed"
248
+ subPageId="speed"
249
+ renderLabelInfo={(props) =>
250
+ this.renderSelected(props, this.state.selectedSpeed)
251
+ }
252
+ >
253
+ Speed
254
+ </Drilldown.Option>
255
+ <Drilldown.Option
256
+ id="quality"
257
+ subPageId="quality"
258
+ renderLabelInfo={(props) =>
259
+ this.renderSelected(props, this.state.selectedQuality)
260
+ }
261
+ >
262
+ Quality
263
+ </Drilldown.Option>
264
+ <Drilldown.Option
265
+ id="comments"
266
+ themeOverride={{ padding: '0.25rem 0.75rem' }}
267
+ afterLabelContentVAlign="center"
268
+ onOptionClick={() => {
269
+ this.setState((state) => ({
270
+ isCommentsOn: !state.isCommentsOn
271
+ }))
272
+ }}
273
+ role="checkbox"
274
+ aria-checked={this.state.isCommentsOn ? 'true' : 'false'}
275
+ // prevents reading the label of the checkbox too (duplicated)
276
+ aria-describedby={['']}
277
+ renderLabelInfo={
278
+ <Checkbox
279
+ label={<ScreenReaderContent>Comments</ScreenReaderContent>}
280
+ variant="toggle"
281
+ readOnly
282
+ checked={this.state.isCommentsOn}
283
+ onChange={() => {
284
+ // needed for controlled Checkbox,
285
+ // but the state is handled on the Drilldown.Option
286
+ }}
287
+ labelPlacement="start"
288
+ tabIndex={-1}
289
+ />
290
+ }
291
+ >
292
+ Comments
293
+ </Drilldown.Option>
294
+ </Drilldown.Page>
295
+
296
+ <Drilldown.Page id="captions" renderTitle="Captions">
297
+ {this.renderSelectGroup(captionOptions, 'selectedCaption')}
298
+ </Drilldown.Page>
299
+
300
+ <Drilldown.Page id="speed" renderTitle="Speed">
301
+ {this.renderSelectGroup(speedOptions, 'selectedSpeed')}
302
+ </Drilldown.Page>
303
+
304
+ <Drilldown.Page id="quality" renderTitle="Quality">
305
+ {this.renderSelectGroup(qualityOptions, 'selectedQuality')}
306
+ </Drilldown.Page>
307
+ </Drilldown>
308
+ )
309
+ }
222
310
  }
223
311
 
224
- render() {
225
- const {
226
- captionOptions,
227
- speedOptions,
228
- qualityOptions
229
- } = this.props
312
+ render(
313
+ <VideoSettingsExample
314
+ captionOptions={['Off', 'English', 'German', 'Spanish', 'Durtch']}
315
+ speedOptions={['0.5x', 'Normal', '1.25x', '1.5x', '2x']}
316
+ qualityOptions={['Auto', '360p', '480p', '720p', '1080p']}
317
+ />
318
+ )
319
+ ```
320
+
321
+ - ```js
322
+ const VideoSettingsExample = (props) => {
323
+ const [selectedCaption, setSelectedCaption] = useState('English')
324
+ const [selectedSpeed, setSelectedSpeed] = useState('Normal')
325
+ const [selectedQuality, setSelectedQuality] = useState('720p')
326
+ const [isCommentsOn, setIsCommentsOn] = useState(true)
327
+
328
+ const renderTrigger = () => {
329
+ return <Button renderIcon={IconSettingsSolid}>Video Settings</Button>
330
+ }
331
+
332
+ const renderSelected = (props, value) => {
333
+ return (
334
+ <span
335
+ style={{ color: props.variant === 'default' ? '#6B7780' : undefined }}
336
+ >
337
+ {value}
338
+ </span>
339
+ )
340
+ }
341
+
342
+ const renderSelectGroup = (options, selectedState, setSelectedState) => {
343
+ return (
344
+ <Drilldown.Group
345
+ id={`${selectedState}Group`}
346
+ selectableType="single"
347
+ defaultSelected={[selectedState]}
348
+ onSelect={(_event, { value }) => {
349
+ setSelectedState(value[0])
350
+ }}
351
+ >
352
+ {options.map((option, idx) => (
353
+ <Drilldown.Option
354
+ key={idx}
355
+ id={option}
356
+ value={option}
357
+ onOptionClick={(_event, { goToPreviousPage }) => {
358
+ goToPreviousPage()
359
+ }}
360
+ >
361
+ {option}
362
+ </Drilldown.Option>
363
+ ))}
364
+ </Drilldown.Group>
365
+ )
366
+ }
230
367
 
231
368
  return (
232
369
  <Drilldown
@@ -234,7 +371,7 @@ class VideoSettingsExample extends React.Component {
234
371
  width="18.5rem"
235
372
  maxHeight="26.5rem"
236
373
  shouldHideOnSelect={false}
237
- trigger={this.renderTrigger()}
374
+ trigger={renderTrigger()}
238
375
  onToggle={(_event, { shown, goToPage }) => {
239
376
  shown && goToPage('videoSettings')
240
377
  }}
@@ -247,27 +384,21 @@ class VideoSettingsExample extends React.Component {
247
384
  <Drilldown.Option
248
385
  id="captions"
249
386
  subPageId="captions"
250
- renderLabelInfo={
251
- (props) => this.renderSelected(props, this.state.selectedCaption)
252
- }
387
+ renderLabelInfo={(props) => renderSelected(props, selectedCaption)}
253
388
  >
254
389
  Captions
255
390
  </Drilldown.Option>
256
391
  <Drilldown.Option
257
392
  id="speed"
258
393
  subPageId="speed"
259
- renderLabelInfo={
260
- (props) => this.renderSelected(props, this.state.selectedSpeed)
261
- }
394
+ renderLabelInfo={(props) => renderSelected(props, selectedSpeed)}
262
395
  >
263
396
  Speed
264
397
  </Drilldown.Option>
265
398
  <Drilldown.Option
266
399
  id="quality"
267
400
  subPageId="quality"
268
- renderLabelInfo={
269
- (props) => this.renderSelected(props, this.state.selectedQuality)
270
- }
401
+ renderLabelInfo={(props) => renderSelected(props, selectedQuality)}
271
402
  >
272
403
  Quality
273
404
  </Drilldown.Option>
@@ -276,19 +407,18 @@ class VideoSettingsExample extends React.Component {
276
407
  themeOverride={{ padding: '0.25rem 0.75rem' }}
277
408
  afterLabelContentVAlign="center"
278
409
  onOptionClick={() => {
279
- this.setState((state) => ({ isCommentsOn: !state.isCommentsOn }))
410
+ setIsCommentsOn((state) => !state)
280
411
  }}
281
412
  role="checkbox"
282
- aria-checked={this.state.isCommentsOn ? 'true' : 'false'}
413
+ aria-checked={isCommentsOn ? 'true' : 'false'}
283
414
  // prevents reading the label of the checkbox too (duplicated)
284
415
  aria-describedby={['']}
285
-
286
416
  renderLabelInfo={
287
417
  <Checkbox
288
418
  label={<ScreenReaderContent>Comments</ScreenReaderContent>}
289
- variant='toggle'
419
+ variant="toggle"
290
420
  readOnly
291
- checked={this.state.isCommentsOn}
421
+ checked={isCommentsOn}
292
422
  onChange={() => {
293
423
  // needed for controlled Checkbox,
294
424
  // but the state is handled on the Drilldown.Option
@@ -302,221 +432,388 @@ class VideoSettingsExample extends React.Component {
302
432
  </Drilldown.Option>
303
433
  </Drilldown.Page>
304
434
 
305
- <Drilldown.Page
306
- id="captions"
307
- renderTitle="Captions"
308
- >
309
- {this.renderSelectGroup(captionOptions, 'selectedCaption')}
435
+ <Drilldown.Page id="captions" renderTitle="Captions">
436
+ {renderSelectGroup(
437
+ props.captionOptions,
438
+ selectedCaption,
439
+ setSelectedCaption
440
+ )}
310
441
  </Drilldown.Page>
311
442
 
312
- <Drilldown.Page
313
- id="speed"
314
- renderTitle="Speed"
315
- >
316
- {this.renderSelectGroup(speedOptions, 'selectedSpeed')}
443
+ <Drilldown.Page id="speed" renderTitle="Speed">
444
+ {renderSelectGroup(
445
+ props.speedOptions,
446
+ selectedSpeed,
447
+ setSelectedSpeed
448
+ )}
317
449
  </Drilldown.Page>
318
450
 
319
- <Drilldown.Page
320
- id="quality"
321
- renderTitle="Quality"
322
- >
323
- {this.renderSelectGroup(qualityOptions, 'selectedQuality')}
451
+ <Drilldown.Page id="quality" renderTitle="Quality">
452
+ {renderSelectGroup(
453
+ props.qualityOptions,
454
+ selectedQuality,
455
+ setSelectedQuality
456
+ )}
324
457
  </Drilldown.Page>
325
458
  </Drilldown>
326
459
  )
327
460
  }
328
- }
329
461
 
330
- render(<VideoSettingsExample
331
- captionOptions={['Off', 'English', 'German', 'Spanish', 'Durtch']}
332
- speedOptions={['0.5x', 'Normal', '1.25x', '1.5x', '2x']}
333
- qualityOptions={['Auto', '360p', '480p', '720p', '1080p']}
334
- />)
335
- ```
462
+ render(
463
+ <VideoSettingsExample
464
+ captionOptions={['Off', 'English', 'German', 'Spanish', 'Durtch']}
465
+ speedOptions={['0.5x', 'Normal', '1.25x', '1.5x', '2x']}
466
+ qualityOptions={['Auto', '360p', '480p', '720p', '1080p']}
467
+ />
468
+ )
469
+ ```
336
470
 
337
471
  ### Displaying Drilldown in a Popover
338
472
 
339
473
  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.).
340
474
 
341
- ```js
342
- ---
343
- type: example
344
- ---
475
+ - ```js
476
+ class SelectContactsExample extends React.Component {
477
+ state = {
478
+ selected: []
479
+ }
345
480
 
346
- class SelectContactsExample extends React.Component {
347
- state = {
348
- selected: []
349
- }
481
+ getCategoryById(id) {
482
+ return this.props.contactsData.find((cat) => cat.id === id)
483
+ }
350
484
 
351
- getCategoryById(id) {
352
- return this.props.contactsData.find(cat => cat.id === id)
353
- }
485
+ selectContacts(values) {
486
+ this.setState({ selected: values })
487
+ }
354
488
 
355
- selectContacts(values) {
356
- this.setState({ selected: values })
357
- }
489
+ renderContacts(contacts) {
490
+ return contacts.map((contact, idx) => {
491
+ const { id, name, email } = contact
358
492
 
359
- renderContacts(contacts) {
360
- return contacts.map((contact, idx) => {
361
- const { id, name, email } = contact
493
+ return (
494
+ <Drilldown.Option
495
+ key={idx}
496
+ id={id}
497
+ value={name}
498
+ description={
499
+ <TruncateText>
500
+ #{idx + 1} | {email}
501
+ </TruncateText>
502
+ }
503
+ onOptionClick={() => {
504
+ this.selectContacts([contact])
505
+ }}
506
+ >
507
+ {name}
508
+ </Drilldown.Option>
509
+ )
510
+ })
511
+ }
512
+
513
+ renderSubCategoryOptions(subCategories = []) {
514
+ return subCategories.map((subCatId, idx) => {
515
+ const category = this.getCategoryById(subCatId)
516
+ const { id, title } = category
517
+
518
+ return (
519
+ <Drilldown.Option key={idx} id={id} subPageId={id}>
520
+ {title}
521
+ </Drilldown.Option>
522
+ )
523
+ })
524
+ }
525
+
526
+ renderPage(category, key) {
527
+ const { id, title, subCategories = [], options = [] } = category
528
+
529
+ const allContacts = this.getAllContactsFromCategory(category)
362
530
 
363
531
  return (
364
- <Drilldown.Option
365
- key={idx}
532
+ <Drilldown.Page
533
+ key={key}
366
534
  id={id}
367
- value={name}
368
- description={
369
- <TruncateText>
370
- #{idx + 1} | {email}
371
- </TruncateText>
372
- }
373
- onOptionClick={() => {
374
- this.selectContacts([contact])
535
+ renderTitle={title}
536
+ renderActionLabel={`Send to All (${allContacts.length})`}
537
+ onHeaderActionClicked={() => {
538
+ this.selectContacts(allContacts)
375
539
  }}
376
540
  >
377
- {name}
378
- </Drilldown.Option>
541
+ {[
542
+ ...this.renderSubCategoryOptions(subCategories),
543
+ ...this.renderContacts(options)
544
+ ]}
545
+ </Drilldown.Page>
379
546
  )
380
- })
381
- }
547
+ }
548
+
549
+ getAllContactsFromCategory(category) {
550
+ let allContacts = []
382
551
 
383
- renderSubCategoryOptions(subCategories = []) {
384
- return subCategories.map((subCatId, idx) => {
385
- const category = this.getCategoryById(subCatId)
386
- const { id, title } = category
552
+ const addOptions = (cat) => {
553
+ const { subCategories = [], options = [] } = cat
554
+ allContacts.push(...options)
555
+
556
+ subCategories.forEach((subCatId) => {
557
+ addOptions(this.getCategoryById(subCatId))
558
+ })
559
+ }
560
+
561
+ addOptions(category)
562
+ return allContacts
563
+ }
564
+
565
+ render() {
566
+ const { contactsData } = this.props
567
+ const { selected = [] } = this.state
387
568
 
388
569
  return (
389
- <Drilldown.Option key={idx} id={id} subPageId={id}>
390
- {title}
391
- </Drilldown.Option>
570
+ <Flex alignItems="start">
571
+ <Flex.Item width="20rem" textAlign="center" padding="x-small 0 0">
572
+ <Drilldown
573
+ rootPageId="contacts"
574
+ width="18.5rem"
575
+ maxHeight="26.5rem"
576
+ trigger={<Button>Select Contacts</Button>}
577
+ onToggle={(_event, args) => {
578
+ args.shown && selectContacts([])
579
+ }}
580
+ >
581
+ {contactsData.map((page, idx) => this.renderPage(page, idx))}
582
+ </Drilldown>
583
+ </Flex.Item>
584
+
585
+ <Flex.Item padding="0 0 0 large" shouldGrow shouldShrink>
586
+ <View as="div" padding="small" background="primary">
587
+ <div>
588
+ <Text weight="bold">
589
+ Selected ({selected ? selected.length : 0}):
590
+ </Text>
591
+ </div>
592
+ <div>{selected.map((a) => a.name).join(', ')}</div>
593
+ </View>
594
+ </Flex.Item>
595
+ </Flex>
392
596
  )
393
- })
597
+ }
394
598
  }
395
599
 
396
- renderPage(category, key) {
397
- const { id, title, subCategories = [], options = [] } = category
600
+ const generateCategory = (name, count) => {
601
+ return Array(count)
602
+ .fill(name)
603
+ .map((category, idx) => {
604
+ const id = category.replace(/(-|\s)/g, '').toLowerCase()
605
+ return {
606
+ id: `${id}${idx + 1}`,
607
+ category,
608
+ name: `${category} ${idx + 1}`,
609
+ email: `${id}${idx + 1}@randommail.com`
610
+ }
611
+ })
612
+ }
398
613
 
399
- const allContacts = this.getAllContactsFromCategory(category)
614
+ const contactsData = [
615
+ {
616
+ id: 'contacts',
617
+ title: 'Contacts',
618
+ subCategories: ['administrators', 'teachers', 'students']
619
+ },
620
+ {
621
+ id: 'administrators',
622
+ title: 'Administrators',
623
+ subCategories: ['accountAdmins', 'subAccountAdmins']
624
+ },
625
+ {
626
+ id: 'accountAdmins',
627
+ title: 'Account Admins',
628
+ options: generateCategory('Account Admin', 8)
629
+ },
630
+ {
631
+ id: 'subAccountAdmins',
632
+ title: 'Sub-Account Admins',
633
+ options: generateCategory('Sub-Account Admin', 12)
634
+ },
635
+ {
636
+ id: 'teachers',
637
+ title: 'Teachers',
638
+ options: generateCategory('Teacher', 23)
639
+ },
640
+ {
641
+ id: 'students',
642
+ title: 'Students',
643
+ options: generateCategory('Student', 34)
644
+ }
645
+ ]
400
646
 
401
- return (
402
- <Drilldown.Page
403
- key={key}
404
- id={id}
405
- renderTitle={title}
406
- renderActionLabel={`Send to All (${allContacts.length})`}
407
- onHeaderActionClicked={() => {
408
- this.selectContacts(allContacts)
409
- }}
410
- >
411
- {[
412
- ...this.renderSubCategoryOptions(subCategories),
413
- ...this.renderContacts(options)
414
- ]}
415
- </Drilldown.Page>
416
- )
417
- }
647
+ render(<SelectContactsExample contactsData={contactsData} />)
648
+ ```
649
+
650
+ - ```js
651
+ const SelectContactsExample = (props) => {
652
+ const [selected, setSelected] = useState([])
418
653
 
419
- getAllContactsFromCategory(category) {
420
- let allContacts = []
654
+ const getCategoryById = (id) => {
655
+ return props.contactsData.find((cat) => cat.id === id)
656
+ }
657
+
658
+ const selectContacts = (values) => {
659
+ setSelected(values)
660
+ }
421
661
 
422
- const addOptions = (cat) => {
423
- const { subCategories = [], options = [] } = cat
424
- allContacts.push(...options)
662
+ const renderContacts = (contacts) => {
663
+ return contacts.map((contact, idx) => {
664
+ const { id, name, email } = contact
665
+
666
+ return (
667
+ <Drilldown.Option
668
+ key={idx}
669
+ id={id}
670
+ value={name}
671
+ description={
672
+ <TruncateText>
673
+ #{idx + 1} | {email}
674
+ </TruncateText>
675
+ }
676
+ onOptionClick={() => {
677
+ selectContacts([contact])
678
+ }}
679
+ >
680
+ {name}
681
+ </Drilldown.Option>
682
+ )
683
+ })
684
+ }
425
685
 
686
+ const renderSubCategoryOptions = (subCategories = []) => {
687
+ return subCategories.map((subCatId, idx) => {
688
+ const category = getCategoryById(subCatId)
689
+ const { id, title } = category
426
690
 
427
- subCategories.forEach((subCatId) => {
428
- addOptions(this.getCategoryById(subCatId))
691
+ return (
692
+ <Drilldown.Option key={idx} id={id} subPageId={id}>
693
+ {title}
694
+ </Drilldown.Option>
695
+ )
429
696
  })
430
697
  }
431
698
 
432
- addOptions(category)
433
- return allContacts
434
- }
699
+ const renderPage = (category, key) => {
700
+ const { id, title, subCategories = [], options = [] } = category
701
+
702
+ const allContacts = getAllContactsFromCategory(category)
703
+
704
+ return (
705
+ <Drilldown.Page
706
+ key={key}
707
+ id={id}
708
+ renderTitle={title}
709
+ renderActionLabel={`Send to All (${allContacts.length})`}
710
+ onHeaderActionClicked={() => {
711
+ selectContacts(allContacts)
712
+ }}
713
+ >
714
+ {[
715
+ ...renderSubCategoryOptions(subCategories),
716
+ ...renderContacts(options)
717
+ ]}
718
+ </Drilldown.Page>
719
+ )
720
+ }
721
+
722
+ const getAllContactsFromCategory = (category) => {
723
+ let allContacts = []
724
+
725
+ const addOptions = (cat) => {
726
+ const { subCategories = [], options = [] } = cat
727
+ allContacts.push(...options)
728
+
729
+ subCategories.forEach((subCatId) => {
730
+ addOptions(getCategoryById(subCatId))
731
+ })
732
+ }
435
733
 
436
- render() {
437
- const { contactsData } = this.props
438
- const { selected = [] } = this.state
734
+ addOptions(category)
735
+ return allContacts
736
+ }
439
737
 
440
738
  return (
441
- <Flex alignItems='start'>
442
- <Flex.Item width="20rem" textAlign="center" padding='x-small 0 0'>
739
+ <Flex alignItems="start">
740
+ <Flex.Item width="20rem" textAlign="center" padding="x-small 0 0">
443
741
  <Drilldown
444
742
  rootPageId="contacts"
445
743
  width="18.5rem"
446
744
  maxHeight="26.5rem"
447
745
  trigger={<Button>Select Contacts</Button>}
448
746
  onToggle={(_event, args) => {
449
- args.shown && this.selectContacts([])
747
+ args.shown && selectContacts([])
450
748
  }}
451
749
  >
452
- {contactsData.map((page, idx) => this.renderPage(page, idx))}
750
+ {props.contactsData.map((page, idx) => renderPage(page, idx))}
453
751
  </Drilldown>
454
752
  </Flex.Item>
455
753
 
456
- <Flex.Item padding='0 0 0 large' shouldGrow shouldShrink>
457
- <View as="div" padding='small' background='primary'>
458
- <div>
459
- <Text weight='bold'>Selected ({selected ? selected.length : 0}):</Text>
460
- </div>
754
+ <Flex.Item padding="0 0 0 large" shouldGrow shouldShrink>
755
+ <View as="div" padding="small" background="primary">
461
756
  <div>
462
- {selected.map((a) => a.name).join(', ')}
757
+ <Text weight="bold">
758
+ Selected ({selected ? selected.length : 0}):
759
+ </Text>
463
760
  </div>
761
+ <div>{selected.map((a) => a.name).join(', ')}</div>
464
762
  </View>
465
763
  </Flex.Item>
466
764
  </Flex>
467
765
  )
468
766
  }
469
- }
470
-
471
- const generateCategory = (name, count) => {
472
- return Array(count)
473
- .fill(name)
474
- .map((category, idx) => {
475
- const id = category.replace(/(-|\s)/g, '').toLowerCase()
476
- return {
477
- id: `${id}${idx + 1}`,
478
- category,
479
- name: `${category} ${idx + 1}`,
480
- email: `${id}${idx + 1}@randommail.com`
481
- }
482
- })
483
- }
484
-
485
- const contactsData = [
486
- {
487
- id: 'contacts',
488
- title: 'Contacts',
489
- subCategories: ['administrators', 'teachers', 'students']
490
- },
491
- {
492
- id: 'administrators',
493
- title: 'Administrators',
494
- subCategories: ['accountAdmins', 'subAccountAdmins']
495
- },
496
- {
497
- id: 'accountAdmins',
498
- title: 'Account Admins',
499
- options: generateCategory('Account Admin', 8)
500
- },
501
- {
502
- id: 'subAccountAdmins',
503
- title: 'Sub-Account Admins',
504
- options: generateCategory('Sub-Account Admin', 12)
505
- },
506
- {
507
- id: 'teachers',
508
- title: 'Teachers',
509
- options: generateCategory('Teacher', 23)
510
- },
511
- {
512
- id: 'students',
513
- title: 'Students',
514
- options: generateCategory('Student', 34)
767
+
768
+ const generateCategory = (name, count) => {
769
+ return Array(count)
770
+ .fill(name)
771
+ .map((category, idx) => {
772
+ const id = category.replace(/(-|\s)/g, '').toLowerCase()
773
+ return {
774
+ id: `${id}${idx + 1}`,
775
+ category,
776
+ name: `${category} ${idx + 1}`,
777
+ email: `${id}${idx + 1}@randommail.com`
778
+ }
779
+ })
515
780
  }
516
- ]
517
781
 
518
- render(<SelectContactsExample contactsData={contactsData} />)
519
- ```
782
+ const contactsData = [
783
+ {
784
+ id: 'contacts',
785
+ title: 'Contacts',
786
+ subCategories: ['administrators', 'teachers', 'students']
787
+ },
788
+ {
789
+ id: 'administrators',
790
+ title: 'Administrators',
791
+ subCategories: ['accountAdmins', 'subAccountAdmins']
792
+ },
793
+ {
794
+ id: 'accountAdmins',
795
+ title: 'Account Admins',
796
+ options: generateCategory('Account Admin', 8)
797
+ },
798
+ {
799
+ id: 'subAccountAdmins',
800
+ title: 'Sub-Account Admins',
801
+ options: generateCategory('Sub-Account Admin', 12)
802
+ },
803
+ {
804
+ id: 'teachers',
805
+ title: 'Teachers',
806
+ options: generateCategory('Teacher', 23)
807
+ },
808
+ {
809
+ id: 'students',
810
+ title: 'Students',
811
+ options: generateCategory('Student', 34)
812
+ }
813
+ ]
814
+
815
+ render(<SelectContactsExample contactsData={contactsData} />)
816
+ ```
520
817
 
521
818
  ### Page header
522
819
 
@@ -538,36 +835,137 @@ The label can be changed with `renderBackButtonLabel` prop (defaults to "Back").
538
835
 
539
836
  This option will always display on the page when needed and cannot be disabled.
540
837
 
541
- ```js
542
- ---
543
- type: example
544
- ---
545
- class BackNavigationExample extends React.Component {
546
- state = {
547
- showTitle: true,
548
- showAction: true,
549
- showAlternativeBackLabel: false
838
+ - ```js
839
+ class BackNavigationExample extends React.Component {
840
+ state = {
841
+ showTitle: true,
842
+ showAction: true,
843
+ showAlternativeBackLabel: false
844
+ }
845
+
846
+ render() {
847
+ const { showTitle, showAction, showAlternativeBackLabel } = this.state
848
+
849
+ return (
850
+ <Flex alignItems="start">
851
+ <Flex.Item>
852
+ <Drilldown rootPageId="root" width="20rem" maxHeight="30rem">
853
+ <Drilldown.Page
854
+ id="root"
855
+ renderTitle={showTitle && 'Root page'}
856
+ renderActionLabel={showAction && 'Action!'}
857
+ >
858
+ <Drilldown.Option
859
+ id="BackNavigationRoot1"
860
+ subPageId="secondPage"
861
+ >
862
+ Option
863
+ </Drilldown.Option>
864
+ <Drilldown.Option
865
+ id="BackNavigationRoot2"
866
+ subPageId="secondPage"
867
+ >
868
+ Option
869
+ </Drilldown.Option>
870
+ <Drilldown.Option
871
+ id="BackNavigationRoot3"
872
+ subPageId="secondPage"
873
+ >
874
+ Option
875
+ </Drilldown.Option>
876
+ </Drilldown.Page>
877
+
878
+ <Drilldown.Page
879
+ id="secondPage"
880
+ renderTitle={showTitle && 'Second page'}
881
+ renderActionLabel={showAction && 'Action!'}
882
+ renderBackButtonLabel={
883
+ showAlternativeBackLabel
884
+ ? (prevPageTitle) =>
885
+ prevPageTitle ? `Back to ${prevPageTitle}` : 'Back'
886
+ : undefined
887
+ }
888
+ >
889
+ <Drilldown.Option id="BackNavigationSecondPage1">
890
+ Option
891
+ </Drilldown.Option>
892
+ <Drilldown.Option id="BackNavigationSecondPage2">
893
+ Option
894
+ </Drilldown.Option>
895
+ <Drilldown.Option id="BackNavigationSecondPage3">
896
+ Option
897
+ </Drilldown.Option>
898
+ </Drilldown.Page>
899
+ </Drilldown>
900
+ </Flex.Item>
901
+
902
+ <Flex.Item padding="0 0 0 large" shouldGrow shouldShrink>
903
+ <FormFieldGroup description="Drilldown Group example settings">
904
+ <Checkbox
905
+ checked={
906
+ !this.state.showTitle
907
+ ? false
908
+ : this.state.showAlternativeBackLabel
909
+ }
910
+ disabled={!this.state.showTitle}
911
+ label='Display previous page title in back navigation ("Show page title" needs to be turned on)'
912
+ variant="toggle"
913
+ onChange={() => {
914
+ this.setState({
915
+ showAlternativeBackLabel:
916
+ !this.state.showAlternativeBackLabel
917
+ })
918
+ }}
919
+ />
920
+ <Checkbox
921
+ checked={this.state.showTitle}
922
+ label="Show page title"
923
+ variant="toggle"
924
+ onChange={() => {
925
+ this.setState({ showTitle: !this.state.showTitle })
926
+ }}
927
+ />
928
+ <Checkbox
929
+ checked={this.state.showAction}
930
+ label="Show action label"
931
+ variant="toggle"
932
+ onChange={() => {
933
+ this.setState({ showAction: !this.state.showAction })
934
+ }}
935
+ />
936
+ </FormFieldGroup>
937
+ </Flex.Item>
938
+ </Flex>
939
+ )
940
+ }
550
941
  }
551
942
 
552
- render() {
553
- const { showTitle, showAction, showAlternativeBackLabel } = this.state
943
+ render(<BackNavigationExample />)
944
+ ```
945
+
946
+ - ```js
947
+ const BackNavigationExample = () => {
948
+ const [showTitle, setShowTitle] = useState(true)
949
+ const [showAction, setShowAction] = useState(true)
950
+ const [showAlternativeBackLabel, setShowAlternativeBackLabel] =
951
+ useState(false)
554
952
 
555
953
  return (
556
- <Flex alignItems='start'>
954
+ <Flex alignItems="start">
557
955
  <Flex.Item>
558
- <Drilldown rootPageId='root' width="20rem" maxHeight="30rem">
956
+ <Drilldown rootPageId="root" width="20rem" maxHeight="30rem">
559
957
  <Drilldown.Page
560
958
  id="root"
561
959
  renderTitle={showTitle && 'Root page'}
562
960
  renderActionLabel={showAction && 'Action!'}
563
961
  >
564
- <Drilldown.Option id="BackNavigationRoot1" subPageId='secondPage'>
962
+ <Drilldown.Option id="BackNavigationRoot1" subPageId="secondPage">
565
963
  Option
566
964
  </Drilldown.Option>
567
- <Drilldown.Option id="BackNavigationRoot2" subPageId='secondPage'>
965
+ <Drilldown.Option id="BackNavigationRoot2" subPageId="secondPage">
568
966
  Option
569
967
  </Drilldown.Option>
570
- <Drilldown.Option id="BackNavigationRoot3" subPageId='secondPage'>
968
+ <Drilldown.Option id="BackNavigationRoot3" subPageId="secondPage">
571
969
  Option
572
970
  </Drilldown.Option>
573
971
  </Drilldown.Page>
@@ -576,9 +974,12 @@ class BackNavigationExample extends React.Component {
576
974
  id="secondPage"
577
975
  renderTitle={showTitle && 'Second page'}
578
976
  renderActionLabel={showAction && 'Action!'}
579
- renderBackButtonLabel={showAlternativeBackLabel
580
- ? ((prevPageTitle) => prevPageTitle ? `Back to ${prevPageTitle}` : 'Back')
581
- : undefined}
977
+ renderBackButtonLabel={
978
+ showAlternativeBackLabel
979
+ ? (prevPageTitle) =>
980
+ prevPageTitle ? `Back to ${prevPageTitle}` : 'Back'
981
+ : undefined
982
+ }
582
983
  >
583
984
  <Drilldown.Option id="BackNavigationSecondPage1">
584
985
  Option
@@ -596,70 +997,128 @@ class BackNavigationExample extends React.Component {
596
997
  <Flex.Item padding="0 0 0 large" shouldGrow shouldShrink>
597
998
  <FormFieldGroup description="Drilldown Group example settings">
598
999
  <Checkbox
599
- checked={!this.state.showTitle ? false : this.state.showAlternativeBackLabel}
600
- disabled={!this.state.showTitle}
1000
+ checked={!showTitle ? false : showAlternativeBackLabel}
1001
+ disabled={!showTitle}
601
1002
  label='Display previous page title in back navigation ("Show page title" needs to be turned on)'
602
1003
  variant="toggle"
603
- onChange={
604
- () => {
605
- this.setState({ showAlternativeBackLabel: !this.state.showAlternativeBackLabel })
606
- }
607
- }
1004
+ onChange={() => {
1005
+ setShowAlternativeBackLabel(!showAlternativeBackLabel)
1006
+ }}
608
1007
  />
609
1008
  <Checkbox
610
- checked={this.state.showTitle}
1009
+ checked={showTitle}
611
1010
  label="Show page title"
612
1011
  variant="toggle"
613
- onChange={
614
- () => {
615
- this.setState({ showTitle: !this.state.showTitle })
616
- }
617
- }
1012
+ onChange={() => {
1013
+ setShowTitle(!showTitle)
1014
+ }}
618
1015
  />
619
1016
  <Checkbox
620
- checked={this.state.showAction}
1017
+ checked={showAction}
621
1018
  label="Show action label"
622
1019
  variant="toggle"
623
- onChange={
624
- () => {
625
- this.setState({ showAction: !this.state.showAction })
626
- }
627
- }
1020
+ onChange={() => {
1021
+ setShowAction(!showAction)
1022
+ }}
628
1023
  />
629
1024
  </FormFieldGroup>
630
1025
  </Flex.Item>
631
1026
  </Flex>
632
1027
  )
633
1028
  }
634
- }
635
1029
 
636
- render(<BackNavigationExample />)
637
- ```
1030
+ render(<BackNavigationExample />)
1031
+ ```
638
1032
 
639
1033
  ### Option Groups
640
1034
 
641
1035
  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.
642
1036
 
643
- ```js
644
- ---
645
- type: example
646
- ---
647
- class GroupsExample extends React.Component {
648
- state = {
649
- showSeparators: true,
650
- showTitles: true
1037
+ - ```js
1038
+ class GroupsExample extends React.Component {
1039
+ state = {
1040
+ showSeparators: true,
1041
+ showTitles: true
1042
+ }
1043
+
1044
+ render() {
1045
+ return (
1046
+ <Flex alignItems="start">
1047
+ <Flex.Item>
1048
+ <Drilldown rootPageId="root" width="20rem" maxHeight="30rem">
1049
+ <Drilldown.Page id="root">
1050
+ <Drilldown.Group
1051
+ id="italy"
1052
+ renderGroupTitle={this.state.showTitles && 'Italy'}
1053
+ withoutSeparators={!this.state.showSeparators}
1054
+ >
1055
+ <Drilldown.Option id="item11">Milano</Drilldown.Option>
1056
+ <Drilldown.Option id="item12">Florence</Drilldown.Option>
1057
+ </Drilldown.Group>
1058
+
1059
+ <Drilldown.Group
1060
+ id="france"
1061
+ renderGroupTitle={this.state.showTitles && 'France'}
1062
+ withoutSeparators={!this.state.showSeparators}
1063
+ >
1064
+ <Drilldown.Option id="item21">Lyon</Drilldown.Option>
1065
+ <Drilldown.Option id="item22">Bordeaux</Drilldown.Option>
1066
+ </Drilldown.Group>
1067
+
1068
+ <Drilldown.Group
1069
+ id="germany"
1070
+ renderGroupTitle={this.state.showTitles && 'Germany'}
1071
+ withoutSeparators={!this.state.showSeparators}
1072
+ >
1073
+ <Drilldown.Option id="item31">Frankfurt</Drilldown.Option>
1074
+ <Drilldown.Option id="item32">Dortmund</Drilldown.Option>
1075
+ </Drilldown.Group>
1076
+ </Drilldown.Page>
1077
+ </Drilldown>
1078
+ </Flex.Item>
1079
+
1080
+ <Flex.Item padding="0 0 0 large">
1081
+ <FormFieldGroup description="Drilldown Group example settings">
1082
+ <Checkbox
1083
+ checked={this.state.showSeparators}
1084
+ label="Show separators"
1085
+ variant="toggle"
1086
+ onChange={() => {
1087
+ this.setState({ showSeparators: !this.state.showSeparators })
1088
+ }}
1089
+ />
1090
+ <Checkbox
1091
+ checked={this.state.showTitles}
1092
+ label="Show group titles"
1093
+ variant="toggle"
1094
+ onChange={() => {
1095
+ this.setState({ showTitles: !this.state.showTitles })
1096
+ }}
1097
+ />
1098
+ </FormFieldGroup>
1099
+ </Flex.Item>
1100
+ </Flex>
1101
+ )
1102
+ }
651
1103
  }
652
1104
 
653
- render() {
1105
+ render(<GroupsExample />)
1106
+ ```
1107
+
1108
+ - ```js
1109
+ const GroupsExample = () => {
1110
+ const [showSeparators, setShowSeparators] = useState(true)
1111
+ const [showTitles, setShowTitles] = useState(true)
1112
+
654
1113
  return (
655
- <Flex alignItems='start'>
1114
+ <Flex alignItems="start">
656
1115
  <Flex.Item>
657
- <Drilldown rootPageId='root' width="20rem" maxHeight="30rem">
1116
+ <Drilldown rootPageId="root" width="20rem" maxHeight="30rem">
658
1117
  <Drilldown.Page id="root">
659
1118
  <Drilldown.Group
660
1119
  id="italy"
661
- renderGroupTitle={this.state.showTitles && 'Italy'}
662
- withoutSeparators={!this.state.showSeparators}
1120
+ renderGroupTitle={showTitles && 'Italy'}
1121
+ withoutSeparators={!showSeparators}
663
1122
  >
664
1123
  <Drilldown.Option id="item11">Milano</Drilldown.Option>
665
1124
  <Drilldown.Option id="item12">Florence</Drilldown.Option>
@@ -667,8 +1126,8 @@ class GroupsExample extends React.Component {
667
1126
 
668
1127
  <Drilldown.Group
669
1128
  id="france"
670
- renderGroupTitle={this.state.showTitles && 'France'}
671
- withoutSeparators={!this.state.showSeparators}
1129
+ renderGroupTitle={showTitles && 'France'}
1130
+ withoutSeparators={!showSeparators}
672
1131
  >
673
1132
  <Drilldown.Option id="item21">Lyon</Drilldown.Option>
674
1133
  <Drilldown.Option id="item22">Bordeaux</Drilldown.Option>
@@ -676,8 +1135,8 @@ class GroupsExample extends React.Component {
676
1135
 
677
1136
  <Drilldown.Group
678
1137
  id="germany"
679
- renderGroupTitle={this.state.showTitles && 'Germany'}
680
- withoutSeparators={!this.state.showSeparators}
1138
+ renderGroupTitle={showTitles && 'Germany'}
1139
+ withoutSeparators={!showSeparators}
681
1140
  >
682
1141
  <Drilldown.Option id="item31">Frankfurt</Drilldown.Option>
683
1142
  <Drilldown.Option id="item32">Dortmund</Drilldown.Option>
@@ -689,34 +1148,29 @@ class GroupsExample extends React.Component {
689
1148
  <Flex.Item padding="0 0 0 large">
690
1149
  <FormFieldGroup description="Drilldown Group example settings">
691
1150
  <Checkbox
692
- checked={this.state.showSeparators}
1151
+ checked={showSeparators}
693
1152
  label="Show separators"
694
1153
  variant="toggle"
695
- onChange={
696
- () => {
697
- this.setState({ showSeparators: !this.state.showSeparators })
698
- }
699
- }
1154
+ onChange={() => {
1155
+ setShowSeparators(!showSeparators)
1156
+ }}
700
1157
  />
701
1158
  <Checkbox
702
- checked={this.state.showTitles}
1159
+ checked={showTitles}
703
1160
  label="Show group titles"
704
1161
  variant="toggle"
705
- onChange={
706
- () => {
707
- this.setState({ showTitles: !this.state.showTitles })
708
- }
709
- }
1162
+ onChange={() => {
1163
+ setShowTitles(!showTitles)
1164
+ }}
710
1165
  />
711
1166
  </FormFieldGroup>
712
1167
  </Flex.Item>
713
1168
  </Flex>
714
1169
  )
715
1170
  }
716
- }
717
1171
 
718
- render(<GroupsExample />)
719
- ```
1172
+ render(<GroupsExample />)
1173
+ ```
720
1174
 
721
1175
  #### Selectable Groups
722
1176
 
@@ -759,80 +1213,263 @@ type: example
759
1213
 
760
1214
  ##### Multi-select Group
761
1215
 
762
- ```js
763
- ---
764
- type: example
765
- ---
1216
+ - ```js
1217
+ class SelectMembersExample extends React.Component {
1218
+ state = {
1219
+ selected: {}
1220
+ }
766
1221
 
767
- class SelectMembersExample extends React.Component {
768
- state = {
769
- selected: {}
770
- }
1222
+ selectMembers(groupValues) {
1223
+ this.setState(({ selected }) => {
1224
+ return { selected: { ...selected, ...groupValues } }
1225
+ })
1226
+ }
771
1227
 
772
- selectMembers(groupValues) {
773
- this.setState(({ selected }) => {
774
- return { selected: { ...selected, ...groupValues }}
775
- })
776
- }
1228
+ renderGroups(groups) {
1229
+ return groups.map((group, idx) => {
1230
+ const { id, label, members, selectableType } = group
777
1231
 
778
- renderGroups(groups) {
779
- return groups.map((group, idx) => {
780
- const { id, label, members, selectableType } = group
1232
+ return (
1233
+ <Drilldown.Group
1234
+ id={id}
1235
+ key={idx}
1236
+ renderGroupTitle={label}
1237
+ selectableType={selectableType}
1238
+ onSelect={(_e, { value }) => {
1239
+ this.selectMembers({ [id]: value })
1240
+ }}
1241
+ >
1242
+ {members.map((member, idx) => {
1243
+ const { id, name } = member
1244
+ return (
1245
+ <Drilldown.Option key={idx} id={id} value={name}>
1246
+ {name}
1247
+ </Drilldown.Option>
1248
+ )
1249
+ })}
1250
+ </Drilldown.Group>
1251
+ )
1252
+ })
1253
+ }
781
1254
 
782
- return (
783
- <Drilldown.Group
784
- id={id}
785
- key={idx}
786
- renderGroupTitle={label}
787
- selectableType={selectableType}
788
- onSelect={(_e, { value }) => {
789
- this.selectMembers({ [id]: value })
790
- }}
791
- >
792
- {members.map((member, idx) => {
793
- const { id, name } = member
794
- return (
795
- <Drilldown.Option key={idx} id={id} value={name}>
796
- {name}
797
- </Drilldown.Option>
798
- )
799
- })}
1255
+ renderSubPageOptions(subPages = []) {
1256
+ return subPages.map((subPage, idx) => {
1257
+ const { id, label } = subPage
800
1258
 
801
- </Drilldown.Group>
1259
+ return (
1260
+ <Drilldown.Option key={idx} id={id} subPageId={id}>
1261
+ {label}
1262
+ </Drilldown.Option>
1263
+ )
1264
+ })
1265
+ }
1266
+
1267
+ renderPage(category, key) {
1268
+ const { id, subPages = [], groups = [] } = category
1269
+
1270
+ return (
1271
+ <Drilldown.Page key={key} id={id}>
1272
+ {subPages.length > 0 && this.renderSubPageOptions(subPages)}
1273
+ {groups.length > 0 && this.renderGroups(groups)}
1274
+ </Drilldown.Page>
802
1275
  )
803
- })
804
- }
1276
+ }
805
1277
 
806
- renderSubPageOptions(subPages = []) {
807
- return subPages.map((subPage, idx) => {
808
- const { id, label } = subPage
1278
+ render() {
1279
+ const { pages } = this.props
1280
+ const { selected = {} } = this.state
809
1281
 
810
1282
  return (
811
- <Drilldown.Option key={idx} id={id} subPageId={id}>
812
- {label}
813
- </Drilldown.Option>
1283
+ <Flex alignItems="start">
1284
+ <Flex.Item width="20rem" textAlign="center" padding="x-small 0 0">
1285
+ <Drilldown
1286
+ rootPageId="root"
1287
+ width="18.5rem"
1288
+ maxHeight="26.5rem"
1289
+ trigger={<Button>Select Members</Button>}
1290
+ shouldHideOnSelect={false}
1291
+ onToggle={(_event, { shown }) => {
1292
+ shown && this.selectMembers({})
1293
+ }}
1294
+ >
1295
+ {pages.map((page, idx) => this.renderPage(page, idx))}
1296
+ </Drilldown>
1297
+ </Flex.Item>
1298
+
1299
+ <Flex.Item padding="0 0 0 large" shouldGrow shouldShrink>
1300
+ <View as="div" padding="small" background="primary">
1301
+ <div>
1302
+ <Text weight="bold">Selected members:</Text>
1303
+ </div>
1304
+ <div>
1305
+ <List>
1306
+ {Object.entries(selected).map(([groupId, values], idx) => {
1307
+ return values.length > 0 ? (
1308
+ <List.Item key={idx}>
1309
+ <b>{groupId}</b>: {values.join(', ')}
1310
+ </List.Item>
1311
+ ) : null
1312
+ })}
1313
+ </List>
1314
+ </div>
1315
+ </View>
1316
+ </Flex.Item>
1317
+ </Flex>
814
1318
  )
815
- })
1319
+ }
816
1320
  }
817
1321
 
818
- renderPage(category, key) {
819
- const { id, subPages = [], groups = [] } = category
1322
+ const pages = [
1323
+ {
1324
+ id: 'root',
1325
+ subPages: [
1326
+ { id: 'courses', label: 'Courses' },
1327
+ { id: 'groups', label: 'Groups' },
1328
+ { id: 'consortiums', label: 'Consortiums' }
1329
+ ]
1330
+ },
1331
+ {
1332
+ id: 'courses',
1333
+ groups: [
1334
+ {
1335
+ id: 'course1',
1336
+ label: 'Course 1',
1337
+ selectableType: 'multiple',
1338
+ members: [
1339
+ { id: 'course1_1', name: 'Hanna Septimus' },
1340
+ { id: 'course1_2', name: 'Kadin Press' },
1341
+ { id: 'course1_3', name: 'Kaiya Botosh' }
1342
+ ]
1343
+ },
1344
+ {
1345
+ id: 'course2',
1346
+ label: 'Course 2',
1347
+ selectableType: 'multiple',
1348
+ members: [
1349
+ { id: 'course2_1', name: 'Leo Calzoni' },
1350
+ { id: 'course2_2', name: 'Gretchen Gouse' }
1351
+ ]
1352
+ }
1353
+ ]
1354
+ },
1355
+ {
1356
+ id: 'groups',
1357
+ groups: [
1358
+ {
1359
+ id: 'group1',
1360
+ label: 'Group 1',
1361
+ selectableType: 'multiple',
1362
+ members: [
1363
+ { id: 'groups1_1', name: 'Penka Okabe' },
1364
+ { id: 'groups1_2', name: 'Ausma Meggyesfalvi' },
1365
+ { id: 'groups1_3', name: 'Endrit Höfler' },
1366
+ { id: 'groups1_4', name: 'Ryuu Carey' },
1367
+ { id: 'groups1_5', name: 'Daphne Dioli' }
1368
+ ]
1369
+ }
1370
+ ]
1371
+ },
1372
+ {
1373
+ id: 'consortiums',
1374
+ groups: [
1375
+ {
1376
+ id: 'consortium1',
1377
+ label: 'Consortium 1',
1378
+ selectableType: 'multiple',
1379
+ members: [
1380
+ { id: 'consortium1_1', name: 'Brahim Gustavsson' },
1381
+ { id: 'consortium1_2', name: 'Elodia Dreschner' }
1382
+ ]
1383
+ },
1384
+ {
1385
+ id: 'consortium2',
1386
+ label: 'Consortium 2',
1387
+ selectableType: 'multiple',
1388
+ members: [
1389
+ { id: 'consortium2_1', name: 'Darwin Peter' },
1390
+ { id: 'consortium2_2', name: 'Sukhrab Burnham' }
1391
+ ]
1392
+ },
1393
+ {
1394
+ id: 'consortium3',
1395
+ label: 'Consortium 3',
1396
+ selectableType: 'multiple',
1397
+ members: [
1398
+ { id: 'consortium3_1', name: 'Jeffry Antonise' },
1399
+ { id: 'consortium3_2', name: 'Bia Regenbogen' }
1400
+ ]
1401
+ }
1402
+ ]
1403
+ }
1404
+ ]
820
1405
 
821
- return (
822
- <Drilldown.Page key={key} id={id}>
823
- {subPages.length > 0 && this.renderSubPageOptions(subPages)}
824
- {groups.length > 0 && this.renderGroups(groups)}
825
- </Drilldown.Page>
826
- )
827
- }
1406
+ render(<SelectMembersExample pages={pages} />)
1407
+ ```
1408
+
1409
+ - ```js
1410
+ const SelectMembersExample = (props) => {
1411
+ const [selected, setSelected] = useState({})
1412
+
1413
+ const selectMembers = (groupValues) => {
1414
+ setSelected((selected) => ({
1415
+ ...selected,
1416
+ ...groupValues
1417
+ }))
1418
+ }
1419
+
1420
+ const renderGroups = (groups) => {
1421
+ return groups.map((group, idx) => {
1422
+ const { id, label, members, selectableType } = group
1423
+
1424
+ return (
1425
+ <Drilldown.Group
1426
+ id={id}
1427
+ key={idx}
1428
+ renderGroupTitle={label}
1429
+ selectableType={selectableType}
1430
+ onSelect={(_e, { value }) => {
1431
+ selectMembers({ [id]: value })
1432
+ }}
1433
+ >
1434
+ {members.map((member, idx) => {
1435
+ const { id, name } = member
1436
+ return (
1437
+ <Drilldown.Option key={idx} id={id} value={name}>
1438
+ {name}
1439
+ </Drilldown.Option>
1440
+ )
1441
+ })}
1442
+ </Drilldown.Group>
1443
+ )
1444
+ })
1445
+ }
1446
+
1447
+ const renderSubPageOptions = (subPages = []) => {
1448
+ return subPages.map((subPage, idx) => {
1449
+ const { id, label } = subPage
1450
+
1451
+ return (
1452
+ <Drilldown.Option key={idx} id={id} subPageId={id}>
1453
+ {label}
1454
+ </Drilldown.Option>
1455
+ )
1456
+ })
1457
+ }
1458
+
1459
+ const renderPage = (category, key) => {
1460
+ const { id, subPages = [], groups = [] } = category
828
1461
 
829
- render() {
830
- const { pages } = this.props
831
- const { selected = {} } = this.state
1462
+ return (
1463
+ <Drilldown.Page key={key} id={id}>
1464
+ {subPages.length > 0 && renderSubPageOptions(subPages)}
1465
+ {groups.length > 0 && renderGroups(groups)}
1466
+ </Drilldown.Page>
1467
+ )
1468
+ }
832
1469
 
833
1470
  return (
834
- <Flex alignItems='start'>
835
- <Flex.Item width="20rem" textAlign="center" padding='x-small 0 0'>
1471
+ <Flex alignItems="start">
1472
+ <Flex.Item width="20rem" textAlign="center" padding="x-small 0 0">
836
1473
  <Drilldown
837
1474
  rootPageId="root"
838
1475
  width="18.5rem"
@@ -840,21 +1477,21 @@ class SelectMembersExample extends React.Component {
840
1477
  trigger={<Button>Select Members</Button>}
841
1478
  shouldHideOnSelect={false}
842
1479
  onToggle={(_event, { shown }) => {
843
- shown && this.selectMembers({})
1480
+ shown && selectMembers({})
844
1481
  }}
845
1482
  >
846
- {pages.map((page, idx) => this.renderPage(page, idx))}
1483
+ {props.pages.map((page, idx) => renderPage(page, idx))}
847
1484
  </Drilldown>
848
1485
  </Flex.Item>
849
1486
 
850
- <Flex.Item padding='0 0 0 large' shouldGrow shouldShrink>
851
- <View as="div" padding='small' background='primary'>
1487
+ <Flex.Item padding="0 0 0 large" shouldGrow shouldShrink>
1488
+ <View as="div" padding="small" background="primary">
852
1489
  <div>
853
- <Text weight='bold'>Selected members:</Text>
1490
+ <Text weight="bold">Selected members:</Text>
854
1491
  </div>
855
1492
  <div>
856
1493
  <List>
857
- {Object.entries(selected).map(([ groupId, values ], idx) => {
1494
+ {Object.entries(selected).map(([groupId, values], idx) => {
858
1495
  return values.length > 0 ? (
859
1496
  <List.Item key={idx}>
860
1497
  <b>{groupId}</b>: {values.join(', ')}
@@ -868,111 +1505,228 @@ class SelectMembersExample extends React.Component {
868
1505
  </Flex>
869
1506
  )
870
1507
  }
871
- }
872
-
873
- const pages = [
874
- {
875
- id: 'root',
876
- subPages: [
877
- { id: 'courses', label: 'Courses' },
878
- { id: 'groups', label: 'Groups' },
879
- { id: 'consortiums', label: 'Consortiums' }
880
- ]
881
- },
882
- {
883
- id: 'courses',
884
- groups: [
885
- {
886
- id: 'course1',
887
- label: 'Course 1',
888
- selectableType: 'multiple',
889
- members: [
890
- { id: 'course1_1', name: 'Hanna Septimus' },
891
- { id: 'course1_2', name: 'Kadin Press' },
892
- { id: 'course1_3', name: 'Kaiya Botosh' },
893
- ]
894
- },
895
- {
896
- id: 'course2',
897
- label: 'Course 2',
898
- selectableType: 'multiple',
899
- members: [
900
- { id: 'course2_1', name: 'Leo Calzoni' },
901
- { id: 'course2_2', name: 'Gretchen Gouse' },
902
- ]
903
- }
904
- ]
905
- },
906
- {
907
- id: 'groups',
908
- groups: [
909
- {
910
- id: 'group1',
911
- label: 'Group 1',
912
- selectableType: 'multiple',
913
- members: [
914
- { id: 'groups1_1', name: 'Penka Okabe' },
915
- { id: 'groups1_2', name: 'Ausma Meggyesfalvi' },
916
- { id: 'groups1_3', name: 'Endrit Höfler' },
917
- { id: 'groups1_4', name: 'Ryuu Carey' },
918
- { id: 'groups1_5', name: 'Daphne Dioli' },
919
- ]
920
- }
921
- ]
922
- },
923
- {
924
- id: 'consortiums',
925
- groups: [
926
- {
927
- id: 'consortium1',
928
- label: 'Consortium 1',
929
- selectableType: 'multiple',
930
- members: [
931
- { id: 'consortium1_1', name: 'Brahim Gustavsson' },
932
- { id: 'consortium1_2', name: 'Elodia Dreschner' },
933
- ]
934
- },
935
- {
936
- id: 'consortium2',
937
- label: 'Consortium 2',
938
- selectableType: 'multiple',
939
- members: [
940
- { id: 'consortium2_1', name: 'Darwin Peter' },
941
- { id: 'consortium2_2', name: 'Sukhrab Burnham' },
942
- ]
943
- },
944
- {
945
- id: 'consortium3',
946
- label: 'Consortium 3',
947
- selectableType: 'multiple',
948
- members: [
949
- { id: 'consortium3_1', name: 'Jeffry Antonise' },
950
- { id: 'consortium3_2', name: 'Bia Regenbogen' },
951
- ]
952
- }
953
- ]
954
- }
955
- ]
956
1508
 
957
- render(<SelectMembersExample pages={pages} />)
958
- ```
1509
+ const pages = [
1510
+ {
1511
+ id: 'root',
1512
+ subPages: [
1513
+ { id: 'courses', label: 'Courses' },
1514
+ { id: 'groups', label: 'Groups' },
1515
+ { id: 'consortiums', label: 'Consortiums' }
1516
+ ]
1517
+ },
1518
+ {
1519
+ id: 'courses',
1520
+ groups: [
1521
+ {
1522
+ id: 'course1',
1523
+ label: 'Course 1',
1524
+ selectableType: 'multiple',
1525
+ members: [
1526
+ { id: 'course1_1', name: 'Hanna Septimus' },
1527
+ { id: 'course1_2', name: 'Kadin Press' },
1528
+ { id: 'course1_3', name: 'Kaiya Botosh' }
1529
+ ]
1530
+ },
1531
+ {
1532
+ id: 'course2',
1533
+ label: 'Course 2',
1534
+ selectableType: 'multiple',
1535
+ members: [
1536
+ { id: 'course2_1', name: 'Leo Calzoni' },
1537
+ { id: 'course2_2', name: 'Gretchen Gouse' }
1538
+ ]
1539
+ }
1540
+ ]
1541
+ },
1542
+ {
1543
+ id: 'groups',
1544
+ groups: [
1545
+ {
1546
+ id: 'group1',
1547
+ label: 'Group 1',
1548
+ selectableType: 'multiple',
1549
+ members: [
1550
+ { id: 'groups1_1', name: 'Penka Okabe' },
1551
+ { id: 'groups1_2', name: 'Ausma Meggyesfalvi' },
1552
+ { id: 'groups1_3', name: 'Endrit Höfler' },
1553
+ { id: 'groups1_4', name: 'Ryuu Carey' },
1554
+ { id: 'groups1_5', name: 'Daphne Dioli' }
1555
+ ]
1556
+ }
1557
+ ]
1558
+ },
1559
+ {
1560
+ id: 'consortiums',
1561
+ groups: [
1562
+ {
1563
+ id: 'consortium1',
1564
+ label: 'Consortium 1',
1565
+ selectableType: 'multiple',
1566
+ members: [
1567
+ { id: 'consortium1_1', name: 'Brahim Gustavsson' },
1568
+ { id: 'consortium1_2', name: 'Elodia Dreschner' }
1569
+ ]
1570
+ },
1571
+ {
1572
+ id: 'consortium2',
1573
+ label: 'Consortium 2',
1574
+ selectableType: 'multiple',
1575
+ members: [
1576
+ { id: 'consortium2_1', name: 'Darwin Peter' },
1577
+ { id: 'consortium2_2', name: 'Sukhrab Burnham' }
1578
+ ]
1579
+ },
1580
+ {
1581
+ id: 'consortium3',
1582
+ label: 'Consortium 3',
1583
+ selectableType: 'multiple',
1584
+ members: [
1585
+ { id: 'consortium3_1', name: 'Jeffry Antonise' },
1586
+ { id: 'consortium3_2', name: 'Bia Regenbogen' }
1587
+ ]
1588
+ }
1589
+ ]
1590
+ }
1591
+ ]
1592
+
1593
+ render(<SelectMembersExample pages={pages} />)
1594
+ ```
959
1595
 
960
1596
  ### Disabled prop
961
1597
 
962
1598
  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.
963
1599
 
964
- ```js
965
- ---
966
- type: example
967
- ---
968
- class DisabledExample extends React.Component {
969
- state = {
970
- disabled: 'None',
971
- withTrigger: false
1600
+ - ```js
1601
+ class DisabledExample extends React.Component {
1602
+ state = {
1603
+ disabled: 'None',
1604
+ withTrigger: false
1605
+ }
1606
+
1607
+ render() {
1608
+ const { disabled, withTrigger } = this.state
1609
+
1610
+ const disabledDrilldown = disabled === 'Drilldown'
1611
+ const disabledPages = disabled === 'Pages'
1612
+ const disabledGroups = disabled === 'Groups'
1613
+ const disabledOptions = disabled === 'Options'
1614
+
1615
+ return (
1616
+ <Flex alignItems="start">
1617
+ <Flex.Item width="20rem" textAlign="center" padding="x-small 0 0">
1618
+ <View textAlign="start">
1619
+ <Drilldown
1620
+ rootPageId="root"
1621
+ width="20rem"
1622
+ maxHeight="30rem"
1623
+ trigger={withTrigger && <Button>Toggle button</Button>}
1624
+ disabled={disabledDrilldown}
1625
+ >
1626
+ <Drilldown.Page
1627
+ id="root"
1628
+ renderTitle="Root page"
1629
+ renderActionLabel="Action"
1630
+ disabled={disabledPages}
1631
+ >
1632
+ <Drilldown.Option
1633
+ id="disabledOption1"
1634
+ subPageId="secondPage"
1635
+ disabled={disabledOptions}
1636
+ >
1637
+ Option with subPage navigation
1638
+ </Drilldown.Option>
1639
+ <Drilldown.Option
1640
+ id="disabledOption2"
1641
+ disabled={disabledOptions}
1642
+ >
1643
+ Option
1644
+ </Drilldown.Option>
1645
+ <Drilldown.Group
1646
+ id="group1"
1647
+ renderGroupTitle="Selectable Group"
1648
+ selectableType="multiple"
1649
+ disabled={disabledGroups}
1650
+ >
1651
+ {['Apple', 'Orange', 'Banana', 'Strawberry'].map((item) => (
1652
+ <Drilldown.Option
1653
+ id={item}
1654
+ key={item}
1655
+ value={item}
1656
+ defaultSelected={item === 'Apple'}
1657
+ disabled={disabledOptions}
1658
+ >
1659
+ {item}
1660
+ </Drilldown.Option>
1661
+ ))}
1662
+ </Drilldown.Group>
1663
+ </Drilldown.Page>
1664
+
1665
+ <Drilldown.Page
1666
+ id="secondPage"
1667
+ renderTitle="Second page"
1668
+ disabled={disabledPages}
1669
+ >
1670
+ {['Option 1', 'Option 2', 'Option 3', 'Option 4'].map(
1671
+ (item) => (
1672
+ <Drilldown.Option
1673
+ id={item}
1674
+ key={item}
1675
+ disabled={disabledOptions}
1676
+ >
1677
+ {item}
1678
+ </Drilldown.Option>
1679
+ )
1680
+ )}
1681
+ </Drilldown.Page>
1682
+ </Drilldown>
1683
+ </View>
1684
+ </Flex.Item>
1685
+
1686
+ <Flex.Item padding="0 0 0 large" shouldGrow shouldShrink>
1687
+ <FormFieldGroup
1688
+ description={<ScreenReaderContent>Settings</ScreenReaderContent>}
1689
+ colSpacing="medium"
1690
+ layout="columns"
1691
+ vAlign="top"
1692
+ >
1693
+ <RadioInputGroup
1694
+ onChange={(_event, value) => {
1695
+ this.setState({ disabled: value })
1696
+ }}
1697
+ defaultValue="None"
1698
+ name="disabledPart"
1699
+ description="Select disabled"
1700
+ >
1701
+ {['None', 'Drilldown', 'Pages', 'Groups', 'Options'].map(
1702
+ (part) => (
1703
+ <RadioInput key={part} value={part} label={part} />
1704
+ )
1705
+ )}
1706
+ </RadioInputGroup>
1707
+
1708
+ <Checkbox
1709
+ checked={this.state.withTrigger}
1710
+ label="With toggle button"
1711
+ variant="toggle"
1712
+ onChange={() => {
1713
+ this.setState({ withTrigger: !this.state.withTrigger })
1714
+ }}
1715
+ />
1716
+ </FormFieldGroup>
1717
+ </Flex.Item>
1718
+ </Flex>
1719
+ )
1720
+ }
972
1721
  }
973
1722
 
974
- render() {
975
- const { disabled, withTrigger } = this.state
1723
+ render(<DisabledExample />)
1724
+ ```
1725
+
1726
+ - ```js
1727
+ const DisabledExample = () => {
1728
+ const [disabled, setDisabled] = useState('None')
1729
+ const [withTrigger, setWithTrigger] = useState(false)
976
1730
 
977
1731
  const disabledDrilldown = disabled === 'Drilldown'
978
1732
  const disabledPages = disabled === 'Pages'
@@ -980,11 +1734,11 @@ class DisabledExample extends React.Component {
980
1734
  const disabledOptions = disabled === 'Options'
981
1735
 
982
1736
  return (
983
- <Flex alignItems='start'>
984
- <Flex.Item width="20rem" textAlign='center' padding='x-small 0 0'>
985
- <View textAlign='start'>
1737
+ <Flex alignItems="start">
1738
+ <Flex.Item width="20rem" textAlign="center" padding="x-small 0 0">
1739
+ <View textAlign="start">
986
1740
  <Drilldown
987
- rootPageId='root'
1741
+ rootPageId="root"
988
1742
  width="20rem"
989
1743
  maxHeight="30rem"
990
1744
  trigger={withTrigger && <Button>Toggle button</Button>}
@@ -998,33 +1752,34 @@ class DisabledExample extends React.Component {
998
1752
  >
999
1753
  <Drilldown.Option
1000
1754
  id="disabledOption1"
1001
- subPageId='secondPage'
1755
+ subPageId="secondPage"
1002
1756
  disabled={disabledOptions}
1003
1757
  >
1004
1758
  Option with subPage navigation
1005
1759
  </Drilldown.Option>
1006
- <Drilldown.Option id="disabledOption2" disabled={disabledOptions}>
1760
+ <Drilldown.Option
1761
+ id="disabledOption2"
1762
+ disabled={disabledOptions}
1763
+ >
1007
1764
  Option
1008
1765
  </Drilldown.Option>
1009
1766
  <Drilldown.Group
1010
1767
  id="group1"
1011
1768
  renderGroupTitle="Selectable Group"
1012
- selectableType='multiple'
1769
+ selectableType="multiple"
1013
1770
  disabled={disabledGroups}
1014
1771
  >
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
- )}
1772
+ {['Apple', 'Orange', 'Banana', 'Strawberry'].map((item) => (
1773
+ <Drilldown.Option
1774
+ id={item}
1775
+ key={item}
1776
+ value={item}
1777
+ defaultSelected={item === 'Apple'}
1778
+ disabled={disabledOptions}
1779
+ >
1780
+ {item}
1781
+ </Drilldown.Option>
1782
+ ))}
1028
1783
  </Drilldown.Group>
1029
1784
  </Drilldown.Page>
1030
1785
 
@@ -1034,7 +1789,7 @@ class DisabledExample extends React.Component {
1034
1789
  disabled={disabledPages}
1035
1790
  >
1036
1791
  {['Option 1', 'Option 2', 'Option 3', 'Option 4'].map(
1037
- item => (
1792
+ (item) => (
1038
1793
  <Drilldown.Option
1039
1794
  id={item}
1040
1795
  key={item}
@@ -1058,34 +1813,35 @@ class DisabledExample extends React.Component {
1058
1813
  >
1059
1814
  <RadioInputGroup
1060
1815
  onChange={(_event, value) => {
1061
- this.setState({ disabled: value })
1816
+ setDisabled(value)
1062
1817
  }}
1063
1818
  defaultValue="None"
1064
1819
  name="disabledPart"
1065
1820
  description="Select disabled"
1066
1821
  >
1067
- {['None', 'Drilldown', 'Pages', 'Groups', 'Options'].map(part => <RadioInput key={part} value={part} label={part} />)}
1822
+ {['None', 'Drilldown', 'Pages', 'Groups', 'Options'].map(
1823
+ (part) => (
1824
+ <RadioInput key={part} value={part} label={part} />
1825
+ )
1826
+ )}
1068
1827
  </RadioInputGroup>
1069
1828
 
1070
1829
  <Checkbox
1071
- checked={this.state.withTrigger}
1830
+ checked={withTrigger}
1072
1831
  label="With toggle button"
1073
1832
  variant="toggle"
1074
- onChange={
1075
- () => {
1076
- this.setState({ withTrigger: !this.state.withTrigger })
1077
- }
1078
- }
1833
+ onChange={() => {
1834
+ setWithTrigger(!withTrigger)
1835
+ }}
1079
1836
  />
1080
1837
  </FormFieldGroup>
1081
1838
  </Flex.Item>
1082
1839
  </Flex>
1083
1840
  )
1084
1841
  }
1085
- }
1086
1842
 
1087
- render(<DisabledExample />)
1088
- ```
1843
+ render(<DisabledExample />)
1844
+ ```
1089
1845
 
1090
1846
  ### shouldHideOnSelect
1091
1847
 
@@ -1232,179 +1988,400 @@ type: example
1232
1988
 
1233
1989
  The following example showcases an editable drilldown that can add or delete options.
1234
1990
 
1235
- ```js
1236
- ---
1237
- type: example
1238
- ---
1991
+ - ```js
1992
+ class EditableStructureExample extends React.Component {
1993
+ constructor(props) {
1994
+ super(props)
1995
+
1996
+ this.state = {
1997
+ districtsData: {
1998
+ d1: {
1999
+ label: 'District 1',
2000
+ schools: ['s1']
2001
+ }
2002
+ },
2003
+ schoolsData: {
2004
+ s1: {
2005
+ label: 'School 1',
2006
+ districtId: 'd1',
2007
+ isSelected: false
2008
+ }
2009
+ }
2010
+ }
1239
2011
 
1240
- class EditableStructureExample extends React.Component {
1241
- constructor(props) {
1242
- super(props)
2012
+ this.districtCounter = Object.keys(this.state.districtsData).length
2013
+ this.schoolCounter = Object.keys(this.state.schoolsData).length
2014
+ }
1243
2015
 
1244
- this.state = {
1245
- districtsData: {
1246
- d1: {
1247
- label: 'District 1',
1248
- schools: ['s1']
2016
+ toggleSelectedSchool(id, school) {
2017
+ this.setState({
2018
+ schoolsData: {
2019
+ ...this.state.schoolsData,
2020
+ [id]: { ...school, isSelected: !school.isSelected }
1249
2021
  }
1250
- },
1251
- schoolsData: {
1252
- s1: {
1253
- label: 'School 1',
1254
- districtId: 'd1',
1255
- isSelected: false
2022
+ })
2023
+ }
2024
+
2025
+ renderSelectedIcon(isSelected) {
2026
+ return <IconCheckSolid style={{ opacity: isSelected ? 1 : 0 }} />
2027
+ }
2028
+
2029
+ renderAddAction(label) {
2030
+ return (
2031
+ <span>
2032
+ <IconAddSolid />
2033
+ <View as="span" margin="0 0 0 x-small">
2034
+ New {label}
2035
+ </View>
2036
+ </span>
2037
+ )
2038
+ }
2039
+
2040
+ addDistrict() {
2041
+ const { districtsData } = this.state
2042
+ this.districtCounter++
2043
+ const districtNumber = this.districtCounter
2044
+ const districtId = `d${districtNumber}`
2045
+
2046
+ this.setState({
2047
+ districtsData: {
2048
+ ...districtsData,
2049
+ [districtId]: {
2050
+ label: `District ${districtNumber}`,
2051
+ schools: []
2052
+ }
1256
2053
  }
1257
- }
2054
+ })
1258
2055
  }
1259
2056
 
1260
- this.districtCounter = Object.keys(this.state.districtsData).length
1261
- this.schoolCounter = Object.keys(this.state.schoolsData).length
1262
- }
2057
+ addSchool(districtId) {
2058
+ const { districtsData, schoolsData } = this.state
2059
+ const district = districtsData[districtId]
1263
2060
 
1264
- toggleSelectedSchool(id, school) {
1265
- this.setState({ schoolsData: {
1266
- ...this.state.schoolsData,
1267
- [id]: { ...school, isSelected: !school.isSelected }
1268
- } })
1269
- }
2061
+ this.schoolCounter++
2062
+ const schoolNumber = this.schoolCounter
2063
+ const schoolId = `s${schoolNumber}`
1270
2064
 
1271
- renderSelectedIcon(isSelected) {
1272
- return (
1273
- <IconCheckSolid
1274
- style={{ opacity: isSelected ? 1 : 0 }}
1275
- />
1276
- )
2065
+ this.setState({
2066
+ districtsData: {
2067
+ ...districtsData,
2068
+ [districtId]: {
2069
+ ...district,
2070
+ schools: [...district.schools, schoolId]
2071
+ }
2072
+ },
2073
+ schoolsData: {
2074
+ ...schoolsData,
2075
+ [schoolId]: {
2076
+ label: `School ${schoolNumber}`,
2077
+ districtId,
2078
+ isSelected: false
2079
+ }
2080
+ }
2081
+ })
2082
+ }
2083
+
2084
+ renderDeleteOption(type, label, idToDelete) {
2085
+ const id = type === 'school' ? 'deleteSchool' : 'deleteDistrict'
2086
+ const callback =
2087
+ type === 'school' ? this.deleteSchool : this.deleteDistrict
2088
+ const separatorId = `${idToDelete}__separator`
2089
+
2090
+ return [
2091
+ <Drilldown.Separator id={separatorId} key={separatorId} />,
2092
+ <Drilldown.Option
2093
+ id={id}
2094
+ key={`${idToDelete}__${id}`}
2095
+ onOptionClick={() => {
2096
+ callback(idToDelete)
2097
+ }}
2098
+ themeOverride={(_componentTheme, currentTheme) => {
2099
+ return { color: currentTheme.colors.textDanger }
2100
+ }}
2101
+ >
2102
+ <IconTrashLine />
2103
+ <View as="span" margin="0 0 0 x-small">
2104
+ Delete {label}
2105
+ </View>
2106
+ </Drilldown.Option>
2107
+ ]
2108
+ }
2109
+
2110
+ deleteSchool = (id) => {
2111
+ const { districtsData, schoolsData } = this.state
2112
+ const { [id]: school, ...restSchools } = schoolsData
2113
+ const { districtId } = school
2114
+ const district = districtsData[districtId]
2115
+
2116
+ this.setState({
2117
+ schoolsData: restSchools,
2118
+ districtsData: {
2119
+ ...districtsData,
2120
+ [districtId]: {
2121
+ ...district,
2122
+ schools: district.schools.filter((schoolId) => schoolId !== id)
2123
+ }
2124
+ }
2125
+ })
2126
+ }
2127
+
2128
+ deleteDistrict = (id) => {
2129
+ const { districtsData, schoolsData } = this.state
2130
+ const { [id]: district, ...restDistricts } = districtsData
2131
+
2132
+ const filteredSchools = {}
2133
+
2134
+ Object.entries(schoolsData).forEach(([schoolId, school]) => {
2135
+ if (school.districtId !== id) {
2136
+ filteredSchools[schoolId] = school
2137
+ }
2138
+ })
2139
+
2140
+ this.setState({
2141
+ districtsData: restDistricts,
2142
+ schoolsData: filteredSchools
2143
+ })
2144
+ }
2145
+
2146
+ render() {
2147
+ const { districtsData, schoolsData } = this.state
2148
+ const districts = Object.entries(districtsData)
2149
+ const schools = Object.entries(schoolsData)
2150
+
2151
+ return (
2152
+ <Drilldown rootPageId="root" width="18.5rem" maxHeight="26.5rem">
2153
+ <Drilldown.Page
2154
+ id="root"
2155
+ renderTitle="Districts"
2156
+ renderActionLabel={this.renderAddAction('District')}
2157
+ onHeaderActionClicked={() => {
2158
+ this.addDistrict()
2159
+ }}
2160
+ >
2161
+ {districts.map(([id, district]) => {
2162
+ const { label } = district
2163
+ return (
2164
+ <Drilldown.Option
2165
+ key={`districtOption__${id}`}
2166
+ id={id}
2167
+ subPageId={id}
2168
+ >
2169
+ {label}
2170
+ </Drilldown.Option>
2171
+ )
2172
+ })}
2173
+ </Drilldown.Page>
2174
+
2175
+ {districts.map(([districtId, district]) => {
2176
+ const { label, schools } = district
2177
+ return (
2178
+ <Drilldown.Page
2179
+ key={`districtPage__${districtId}`}
2180
+ id={districtId}
2181
+ renderTitle={label}
2182
+ renderActionLabel={this.renderAddAction('School')}
2183
+ onHeaderActionClicked={() => {
2184
+ this.addSchool(districtId)
2185
+ }}
2186
+ >
2187
+ {schools.map((id) => {
2188
+ const { label, isSelected } = this.state.schoolsData[id]
2189
+ return (
2190
+ <Drilldown.Option
2191
+ key={`schoolOption__${districtId}--${id}`}
2192
+ id={id}
2193
+ subPageId={id}
2194
+ renderBeforeLabel={this.renderSelectedIcon(isSelected)}
2195
+ >
2196
+ {label}
2197
+ </Drilldown.Option>
2198
+ )
2199
+ })}
2200
+ {this.renderDeleteOption('district', label, districtId)}
2201
+ </Drilldown.Page>
2202
+ )
2203
+ })}
2204
+
2205
+ {schools.map(([id, school]) => {
2206
+ const { label, isSelected } = school
2207
+ return (
2208
+ <Drilldown.Page
2209
+ key={`schoolPage__${id}`}
2210
+ id={id}
2211
+ withoutHeaderSeparator
2212
+ >
2213
+ <Drilldown.Option
2214
+ id="toggleSchool"
2215
+ renderBeforeLabel={this.renderSelectedIcon(isSelected)}
2216
+ onOptionClick={() => {
2217
+ this.toggleSelectedSchool(id, school)
2218
+ }}
2219
+ >
2220
+ {isSelected ? 'Deselect' : 'Select'} {label}
2221
+ </Drilldown.Option>
2222
+ {this.renderDeleteOption('school', label, id)}
2223
+ </Drilldown.Page>
2224
+ )
2225
+ })}
2226
+ </Drilldown>
2227
+ )
2228
+ }
1277
2229
  }
1278
2230
 
1279
- renderAddAction(label) {
1280
- return (
1281
- <span>
1282
- <IconAddSolid />
1283
- <View as="span" margin="0 0 0 x-small">New {label}</View>
1284
- </span>
2231
+ render(<EditableStructureExample />)
2232
+ ```
2233
+
2234
+ - ```js
2235
+ const EditableStructureExample = () => {
2236
+ const [districtsData, setDistrictsData] = useState({
2237
+ d1: {
2238
+ label: 'District 1',
2239
+ schools: ['s1']
2240
+ }
2241
+ })
2242
+ const [schoolsData, setSchoolsData] = useState({
2243
+ s1: {
2244
+ label: 'School 1',
2245
+ districtId: 'd1',
2246
+ isSelected: false
2247
+ }
2248
+ })
2249
+
2250
+ const [districtCounter, setDistrictCounter] = useState(
2251
+ Object.keys(districtsData).length
1285
2252
  )
1286
- }
2253
+ const [schoolCounter, setSchoolCounter] = useState(
2254
+ Object.keys(schoolsData).length
2255
+ )
2256
+
2257
+ const districts = Object.entries(districtsData)
2258
+ const schools = Object.entries(schoolsData)
2259
+
2260
+ const toggleSelectedSchool = (id, school) => {
2261
+ setSchoolsData({
2262
+ ...schoolsData,
2263
+ [id]: { ...school, isSelected: !school.isSelected }
2264
+ })
2265
+ }
2266
+
2267
+ const renderSelectedIcon = (isSelected) => {
2268
+ return <IconCheckSolid style={{ opacity: isSelected ? 1 : 0 }} />
2269
+ }
2270
+
2271
+ const renderAddAction = (label) => {
2272
+ return (
2273
+ <span>
2274
+ <IconAddSolid />
2275
+ <View as="span" margin="0 0 0 x-small">
2276
+ New {label}
2277
+ </View>
2278
+ </span>
2279
+ )
2280
+ }
1287
2281
 
1288
- addDistrict() {
1289
- const { districtsData } = this.state
1290
- this.districtCounter++
1291
- const districtNumber = this.districtCounter
1292
- const districtId = `d${districtNumber}`
2282
+ const addDistrict = () => {
2283
+ const newDistrictCounter = districtCounter + 1
2284
+ const districtNumber = newDistrictCounter
2285
+ const districtId = `d${newDistrictCounter}`
2286
+ setDistrictCounter(newDistrictCounter)
1293
2287
 
1294
- this.setState({
1295
- districtsData: {
2288
+ setDistrictsData((districtsData) => ({
1296
2289
  ...districtsData,
1297
2290
  [districtId]: {
1298
2291
  label: `District ${districtNumber}`,
1299
2292
  schools: []
1300
2293
  }
1301
- }
1302
- })
1303
- }
1304
-
1305
- addSchool(districtId) {
1306
- const { districtsData, schoolsData } = this.state
1307
- const district = districtsData[districtId]
2294
+ }))
2295
+ }
1308
2296
 
1309
- this.schoolCounter++
1310
- const schoolNumber = this.schoolCounter
1311
- const schoolId = `s${schoolNumber}`
2297
+ const addSchool = (districtId) => {
2298
+ const newSchoolCounter = schoolCounter + 1
2299
+ const district = districtsData[districtId]
2300
+ const schoolNumber = newSchoolCounter
2301
+ const schoolId = `s${newSchoolCounter}`
2302
+ setSchoolCounter(newSchoolCounter)
1312
2303
 
1313
- this.setState({
1314
- districtsData: {
2304
+ setDistrictsData((districtsData) => ({
1315
2305
  ...districtsData,
1316
2306
  [districtId]: {
1317
- ...district,
2307
+ ...districtsData[districtId],
1318
2308
  schools: [...district.schools, schoolId]
1319
2309
  }
1320
- },
1321
- schoolsData: {
2310
+ }))
2311
+
2312
+ setSchoolsData((schoolsData) => ({
1322
2313
  ...schoolsData,
1323
2314
  [schoolId]: {
1324
2315
  label: `School ${schoolNumber}`,
1325
2316
  districtId,
1326
2317
  isSelected: false
1327
2318
  }
1328
- }
1329
- })
1330
- }
2319
+ }))
2320
+ }
1331
2321
 
1332
- renderDeleteOption(type, label, idToDelete) {
1333
- const id = type === 'school' ? "deleteSchool" : "deleteDistrict"
1334
- const callback = type === 'school' ? this.deleteSchool : this.deleteDistrict
1335
- const separatorId = `${idToDelete}__separator`
1336
-
1337
- return [
1338
- <Drilldown.Separator id={separatorId} key={separatorId} />,
1339
- <Drilldown.Option
1340
- id={id}
1341
- key={`${idToDelete}__${id}`}
1342
- onOptionClick={() => { callback(idToDelete) }}
1343
- themeOverride={(_componentTheme, currentTheme) => {
1344
- return { color: currentTheme.colors.textDanger }
1345
- }}
1346
- >
1347
- <IconTrashLine />
1348
- <View as='span' margin='0 0 0 x-small'>
1349
- Delete {label}
1350
- </View>
1351
- </Drilldown.Option>
1352
- ]
1353
- }
2322
+ const renderDeleteOption = (type, label, idToDelete) => {
2323
+ const id = type === 'school' ? 'deleteSchool' : 'deleteDistrict'
2324
+ const callback = type === 'school' ? deleteSchool : deleteDistrict
2325
+ const separatorId = `${idToDelete}__separator`
2326
+
2327
+ return [
2328
+ <Drilldown.Separator id={separatorId} key={separatorId} />,
2329
+ <Drilldown.Option
2330
+ id={id}
2331
+ key={`${idToDelete}__${id}`}
2332
+ onOptionClick={() => {
2333
+ callback(idToDelete)
2334
+ }}
2335
+ themeOverride={(_componentTheme, currentTheme) => {
2336
+ return { color: currentTheme.colors.textDanger }
2337
+ }}
2338
+ >
2339
+ <IconTrashLine />
2340
+ <View as="span" margin="0 0 0 x-small">
2341
+ Delete {label}
2342
+ </View>
2343
+ </Drilldown.Option>
2344
+ ]
2345
+ }
1354
2346
 
1355
- deleteSchool = (id) => {
1356
- const { districtsData, schoolsData } = this.state
1357
- const { [id]: school, ...restSchools } = schoolsData
1358
- const { districtId } = school
1359
- const district = districtsData[districtId]
2347
+ const deleteSchool = (id) => {
2348
+ const { [id]: school, ...restSchools } = schoolsData
2349
+ const { districtId } = school
2350
+ const district = districtsData[districtId]
1360
2351
 
1361
- this.setState({
1362
- schoolsData: restSchools,
1363
- districtsData: {
2352
+ setSchoolsData(restSchools)
2353
+ setDistrictsData({
1364
2354
  ...districtsData,
1365
2355
  [districtId]: {
1366
2356
  ...district,
1367
- schools: district.schools.filter(schoolId => schoolId !== id)
2357
+ schools: district.schools.filter((schoolId) => schoolId !== id)
1368
2358
  }
1369
- }
1370
- })
1371
- }
1372
-
1373
- deleteDistrict = (id) => {
1374
- const { districtsData, schoolsData } = this.state
1375
- const { [id]: district, ...restDistricts } = districtsData
2359
+ })
2360
+ }
1376
2361
 
1377
- const filteredSchools = {}
2362
+ const deleteDistrict = (id) => {
2363
+ const { [id]: district, ...restDistricts } = districtsData
1378
2364
 
1379
- Object.entries(schoolsData).forEach(([schoolId, school]) => {
1380
- if (school.districtId !== id) {
1381
- filteredSchools[schoolId] = school
1382
- }
1383
- })
2365
+ const filteredSchools = {}
1384
2366
 
1385
- this.setState({
1386
- districtsData: restDistricts,
1387
- schoolsData: filteredSchools
1388
- })
1389
- }
2367
+ Object.entries(schoolsData).forEach(([schoolId, school]) => {
2368
+ if (school.districtId !== id) {
2369
+ filteredSchools[schoolId] = school
2370
+ }
2371
+ })
1390
2372
 
1391
- render() {
1392
- const { districtsData, schoolsData } = this.state
1393
- const districts = Object.entries(districtsData)
1394
- const schools = Object.entries(schoolsData)
2373
+ setDistrictsData(restDistricts)
2374
+ setSchoolsData(filteredSchools)
2375
+ }
1395
2376
 
1396
2377
  return (
1397
- <Drilldown
1398
- rootPageId="root"
1399
- width="18.5rem"
1400
- maxHeight="26.5rem"
1401
- >
2378
+ <Drilldown rootPageId="root" width="18.5rem" maxHeight="26.5rem">
1402
2379
  <Drilldown.Page
1403
2380
  id="root"
1404
2381
  renderTitle="Districts"
1405
- renderActionLabel={this.renderAddAction('District')}
2382
+ renderActionLabel={renderAddAction('District')}
1406
2383
  onHeaderActionClicked={() => {
1407
- this.addDistrict()
2384
+ addDistrict()
1408
2385
  }}
1409
2386
  >
1410
2387
  {districts.map(([id, district]) => {
@@ -1421,32 +2398,32 @@ class EditableStructureExample extends React.Component {
1421
2398
  })}
1422
2399
  </Drilldown.Page>
1423
2400
 
1424
- {districts.map(([districtId , district]) => {
2401
+ {districts.map(([districtId, district]) => {
1425
2402
  const { label, schools } = district
1426
2403
  return (
1427
2404
  <Drilldown.Page
1428
2405
  key={`districtPage__${districtId}`}
1429
2406
  id={districtId}
1430
2407
  renderTitle={label}
1431
- renderActionLabel={this.renderAddAction('School')}
2408
+ renderActionLabel={renderAddAction('School')}
1432
2409
  onHeaderActionClicked={() => {
1433
- this.addSchool(districtId)
2410
+ addSchool(districtId)
1434
2411
  }}
1435
2412
  >
1436
2413
  {schools.map((id) => {
1437
- const { label, isSelected } = this.state.schoolsData[id]
2414
+ const { label, isSelected } = schoolsData[id]
1438
2415
  return (
1439
2416
  <Drilldown.Option
1440
2417
  key={`schoolOption__${districtId}--${id}`}
1441
2418
  id={id}
1442
2419
  subPageId={id}
1443
- renderBeforeLabel={this.renderSelectedIcon(isSelected)}
2420
+ renderBeforeLabel={renderSelectedIcon(isSelected)}
1444
2421
  >
1445
2422
  {label}
1446
2423
  </Drilldown.Option>
1447
2424
  )
1448
2425
  })}
1449
- {this.renderDeleteOption('district', label, districtId)}
2426
+ {renderDeleteOption('district', label, districtId)}
1450
2427
  </Drilldown.Page>
1451
2428
  )
1452
2429
  })}
@@ -1461,24 +2438,23 @@ class EditableStructureExample extends React.Component {
1461
2438
  >
1462
2439
  <Drilldown.Option
1463
2440
  id="toggleSchool"
1464
- renderBeforeLabel={this.renderSelectedIcon(isSelected)}
2441
+ renderBeforeLabel={renderSelectedIcon(isSelected)}
1465
2442
  onOptionClick={() => {
1466
- this.toggleSelectedSchool(id, school)
2443
+ toggleSelectedSchool(id, school)
1467
2444
  }}
1468
2445
  >
1469
2446
  {isSelected ? 'Deselect' : 'Select'} {label}
1470
2447
  </Drilldown.Option>
1471
- {this.renderDeleteOption('school', label, id)}
2448
+ {renderDeleteOption('school', label, id)}
1472
2449
  </Drilldown.Page>
1473
2450
  )
1474
2451
  })}
1475
2452
  </Drilldown>
1476
2453
  )
1477
2454
  }
1478
- }
1479
2455
 
1480
- render(<EditableStructureExample />)
1481
- ```
2456
+ render(<EditableStructureExample />)
2457
+ ```
1482
2458
 
1483
2459
  ### Guidelines
1484
2460