@react-ui-org/react-ui 0.50.2 → 0.52.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/dist/lib.development.js +157 -49
  2. package/dist/lib.js +1 -1
  3. package/package.json +1 -1
  4. package/src/lib/components/Alert/Alert.jsx +1 -3
  5. package/src/lib/components/Alert/Alert.scss +1 -9
  6. package/src/lib/components/Alert/README.mdx +0 -20
  7. package/src/lib/components/Alert/_settings.scss +1 -1
  8. package/src/lib/components/Alert/_theme.scss +0 -10
  9. package/src/lib/components/Badge/Badge.jsx +1 -3
  10. package/src/lib/components/Badge/Badge.scss +25 -44
  11. package/src/lib/components/Badge/README.mdx +6 -14
  12. package/src/lib/components/Button/Button.jsx +20 -10
  13. package/src/lib/components/Button/README.mdx +8 -3
  14. package/src/lib/components/Button/_base.scss +21 -12
  15. package/src/lib/components/Button/_priorities.scss +13 -18
  16. package/src/lib/components/Button/_settings.scss +1 -1
  17. package/src/lib/components/Button/_theme.scss +0 -10
  18. package/src/lib/components/ButtonGroup/ButtonGroup.jsx +5 -3
  19. package/src/lib/components/ButtonGroup/ButtonGroup.scss +26 -1
  20. package/src/lib/components/ButtonGroup/README.mdx +85 -59
  21. package/src/lib/components/ButtonGroup/_theme.scss +13 -0
  22. package/src/lib/components/Card/Card.jsx +1 -3
  23. package/src/lib/components/Card/Card.scss +0 -9
  24. package/src/lib/components/Card/README.mdx +0 -16
  25. package/src/lib/components/Card/_theme.scss +0 -10
  26. package/src/lib/components/FormLayout/README.mdx +22 -8
  27. package/src/lib/components/Grid/_helpers/generateResponsiveCustomProperties.js +1 -1
  28. package/src/lib/components/InputGroup/InputGroup.jsx +170 -0
  29. package/src/lib/components/InputGroup/InputGroup.scss +92 -0
  30. package/src/lib/components/InputGroup/InputGroupContext.js +3 -0
  31. package/src/lib/components/InputGroup/README.mdx +278 -0
  32. package/src/lib/components/InputGroup/_theme.scss +2 -0
  33. package/src/lib/components/InputGroup/index.js +2 -0
  34. package/src/lib/components/Modal/Modal.jsx +58 -97
  35. package/src/lib/components/Modal/ModalCloseButton.scss +2 -2
  36. package/src/lib/components/Modal/README.mdx +392 -128
  37. package/src/lib/components/Modal/_helpers/getPositionClassName.js +7 -0
  38. package/src/lib/components/Modal/_helpers/getSizeClassName.js +19 -0
  39. package/src/lib/components/Modal/_hooks/useModalFocus.js +126 -0
  40. package/src/lib/components/Modal/_hooks/useModalScrollPrevention.js +35 -0
  41. package/src/lib/components/Modal/_settings.scss +2 -2
  42. package/src/lib/components/Popover/README.mdx +7 -4
  43. package/src/lib/components/Radio/README.mdx +9 -1
  44. package/src/lib/components/Radio/Radio.jsx +39 -31
  45. package/src/lib/components/Radio/Radio.scss +11 -1
  46. package/src/lib/components/ScrollView/README.mdx +2 -2
  47. package/src/lib/components/SelectField/SelectField.jsx +21 -8
  48. package/src/lib/components/SelectField/SelectField.scss +5 -0
  49. package/src/lib/components/Table/_components/TableCell.scss +6 -5
  50. package/src/lib/components/Table/_components/TableHeaderCell/TableHeaderCell.jsx +8 -5
  51. package/src/lib/components/Table/_settings.scss +5 -6
  52. package/src/lib/components/Text/README.mdx +14 -8
  53. package/src/lib/components/TextField/TextField.jsx +21 -8
  54. package/src/lib/components/TextField/TextField.scss +5 -0
  55. package/src/lib/components/TextLink/README.mdx +8 -6
  56. package/src/lib/components/TextLink/TextLink.scss +5 -0
  57. package/src/lib/components/TextLink/_theme.scss +2 -0
  58. package/src/lib/components/Toolbar/README.mdx +19 -11
  59. package/src/lib/components/_helpers/getRootColorClassName.js +4 -0
  60. package/src/lib/index.js +1 -0
  61. package/src/lib/styles/elements/_code.scss +1 -3
  62. package/src/lib/styles/elements/_page.scss +1 -0
  63. package/src/lib/styles/elements/_rulers.scss +1 -3
  64. package/src/lib/styles/elements/_small.scss +1 -1
  65. package/src/lib/styles/settings/_form-fields.scss +1 -1
  66. package/src/lib/styles/settings/_utilities.scss +46 -14
  67. package/src/lib/styles/theme/_accessibility.scss +4 -4
  68. package/src/lib/styles/theme/_borders.scss +3 -2
  69. package/src/lib/styles/theme/_code.scss +2 -2
  70. package/src/lib/styles/theme/_links.scss +6 -4
  71. package/src/lib/styles/theme/_lists.scss +1 -1
  72. package/src/lib/styles/theme/_page.scss +2 -2
  73. package/src/lib/styles/theme/_spacing.scss +11 -11
  74. package/src/lib/styles/theme/_typography.scss +19 -18
  75. package/src/lib/styles/theme-constants/_colors.scss +23 -23
  76. package/src/lib/styles/tools/_spacing.scss +1 -1
  77. package/src/lib/styles/tools/form-fields/_box-field-elements.scss +19 -2
  78. package/src/lib/styles/tools/form-fields/_box-field-sizes.scss +11 -8
  79. package/src/lib/styles/tools/form-fields/_foundation.scss +7 -0
  80. package/src/lib/theme.scss +650 -567
  81. package/src/lib/styles/theme/_colors.scss +0 -65
  82. /package/src/lib/components/{Button/helpers → _helpers}/getRootPriorityClassName.js +0 -0
@@ -14,6 +14,7 @@ import {
14
14
  } from 'docz'
15
15
  import {
16
16
  Button,
17
+ CheckboxField,
17
18
  FormLayout,
18
19
  Modal,
19
20
  ModalBody,
@@ -22,7 +23,9 @@ import {
22
23
  ModalFooter,
23
24
  ModalHeader,
24
25
  ModalTitle,
26
+ Radio,
25
27
  ScrollView,
28
+ TextArea,
26
29
  TextField,
27
30
  Toolbar,
28
31
  ToolbarGroup,
@@ -55,11 +58,7 @@ And use it:
55
58
  const modalCloseButtonRef = React.useRef();
56
59
  return (
57
60
  <>
58
- <Button
59
- label="Launch modal"
60
- onClick={() => setModalOpen(true)}
61
- priority="outline"
62
- />
61
+ <Button label="Launch modal" onClick={() => setModalOpen(true)} />
63
62
  <div>
64
63
  {modalOpen && (
65
64
  <Modal
@@ -86,6 +85,7 @@ And use it:
86
85
  ref={modalPrimaryButtonRef}
87
86
  />
88
87
  <Button
88
+ color="secondary"
89
89
  label="Close"
90
90
  onClick={() => setModalOpen(false)}
91
91
  priority="outline"
@@ -117,12 +117,15 @@ See [API](#api) for all available options.
117
117
 
118
118
  - Modal **automatically focuses the first non-disabled form field** by default
119
119
  which allows users to confirm the modal by hitting the enter key. When no
120
- field is found then the primary button (in the footer) is focused. To turn
121
- this feature off, set the `autofocus` prop to `false`.
120
+ field is found then the primary button (in the footer) is focused. If there
121
+ are neither, it tries to focus any other focusable elements. In case there
122
+ are none, or [autoFocus](#autoFocus) is disabled, Modal itself is focused.
122
123
 
123
124
  - **Avoid stacking** of modals. While it may technically work, the modal is just
124
125
  not designed for that.
125
126
 
127
+ 📖 [Read more about modals at Nielsen Norman Group.][nng-modal]
128
+
126
129
  ## Composition
127
130
 
128
131
  Modal is decomposed into the following components:
@@ -152,7 +155,6 @@ e.g. dialog modal, blocking modal, scrollable modal, etc.
152
155
  setModalOpen(1);
153
156
  setTimeout(() => setModalOpen(null), 2500);
154
157
  }}
155
- priority="outline"
156
158
  />
157
159
  <Button
158
160
  label="Launch blocking modal with title"
@@ -160,17 +162,14 @@ e.g. dialog modal, blocking modal, scrollable modal, etc.
160
162
  setModalOpen(2);
161
163
  setTimeout(() => setModalOpen(null), 3500);
162
164
  }}
163
- priority="outline"
164
165
  />
165
166
  <Button
166
167
  label="Launch modal as dialog"
167
168
  onClick={() => setModalOpen(3)}
168
- priority="outline"
169
169
  />
170
170
  <Button
171
171
  label="Launch modal as form"
172
172
  onClick={() => setModalOpen(4)}
173
- priority="outline"
174
173
  />
175
174
  <div>
176
175
  {modalOpen === 1 && (
@@ -227,6 +226,7 @@ e.g. dialog modal, blocking modal, scrollable modal, etc.
227
226
  ref={modalPrimaryButtonRef}
228
227
  />
229
228
  <Button
229
+ color="secondary"
230
230
  label="Close"
231
231
  onClick={() => setModalOpen(false)}
232
232
  priority="outline"
@@ -259,6 +259,7 @@ e.g. dialog modal, blocking modal, scrollable modal, etc.
259
259
  ref={modalPrimaryButtonRef}
260
260
  />
261
261
  <Button
262
+ color="secondary"
262
263
  label="Close"
263
264
  onClick={() => setModalOpen(false)}
264
265
  priority="outline"
@@ -305,7 +306,6 @@ There are two ways how to position elements within the ModalHeader:
305
306
  setModalOpen(true);
306
307
  setVariant(1);
307
308
  }}
308
- priority="outline"
309
309
  />
310
310
  <Button
311
311
  label="Launch without close button"
@@ -313,7 +313,6 @@ There are two ways how to position elements within the ModalHeader:
313
313
  setModalOpen(true);
314
314
  setVariant(2);
315
315
  }}
316
- priority="outline"
317
316
  />
318
317
  <Button
319
318
  label="Launch without close button and with centered title"
@@ -321,7 +320,6 @@ There are two ways how to position elements within the ModalHeader:
321
320
  setModalOpen(true);
322
321
  setVariant(3);
323
322
  }}
324
- priority="outline"
325
323
  />
326
324
  <Button
327
325
  label="Launch with custom layout"
@@ -329,7 +327,6 @@ There are two ways how to position elements within the ModalHeader:
329
327
  setModalOpen(true);
330
328
  setVariant(4);
331
329
  }}
332
- priority="outline"
333
330
  />
334
331
  <div>
335
332
  {modalOpen && (
@@ -384,6 +381,7 @@ There are two ways how to position elements within the ModalHeader:
384
381
  ref={modalPrimaryButtonRef}
385
382
  />
386
383
  <Button
384
+ color="secondary"
387
385
  label="Close"
388
386
  onClick={() => setModalOpen(false)}
389
387
  priority="outline"
@@ -432,44 +430,8 @@ There are two ways to position buttons within the ModalFooter:
432
430
  return (
433
431
  <>
434
432
  <Button
435
- label="Launch with footer justified to start"
436
- onClick={() => {
437
- setModalOpen(true);
438
- setModalJustify('start');
439
- }}
440
- priority="outline"
441
- />
442
- <Button
443
- label="Launch with footer justified to center"
444
- onClick={() => {
445
- setModalOpen(true);
446
- setModalJustify('center');
447
- }}
448
- priority="outline"
449
- />
450
- <Button
451
- label="Launch with footer justified to end"
452
- onClick={() => {
453
- setModalOpen(true);
454
- setModalJustify('end');
455
- }}
456
- priority="outline"
457
- />
458
- <Button
459
- label="Launch with footer justified with space between"
460
- onClick={() => {
461
- setModalOpen(true);
462
- setModalJustify('space-between');
463
- }}
464
- priority="outline"
465
- />
466
- <Button
467
- label="Launch with footer with custom layout"
468
- onClick={() => {
469
- setModalOpen(true);
470
- setModalJustify('stretch');
471
- }}
472
- priority="outline"
433
+ label="Launch modal with footer variants"
434
+ onClick={() => setModalOpen(true)}
473
435
  />
474
436
  <div>
475
437
  {modalOpen && (
@@ -478,15 +440,38 @@ There are two ways to position buttons within the ModalFooter:
478
440
  primaryButtonRef={modalPrimaryButtonRef}
479
441
  >
480
442
  <ModalHeader>
481
- <ModalTitle>Delete the user?</ModalTitle>
443
+ <ModalTitle>Footer justification</ModalTitle>
482
444
  <ModalCloseButton onClick={() => setModalOpen(false)} />
483
445
  </ModalHeader>
484
446
  <ModalBody>
485
447
  <ModalContent>
486
- <p>
487
- Do you really want to delete the user <code>admin</code>?
488
- This cannot be undone.
489
- </p>
448
+ <Radio
449
+ label="Footer justification"
450
+ onChange={(e) => setModalJustify(e.target.value)}
451
+ options={[
452
+ {
453
+ label: 'start',
454
+ value: 'start',
455
+ },
456
+ {
457
+ label: 'center',
458
+ value: 'center',
459
+ },
460
+ {
461
+ label: 'end',
462
+ value: 'end',
463
+ },
464
+ {
465
+ label: 'space-between',
466
+ value: 'space-between',
467
+ },
468
+ {
469
+ label: 'stretch (with a custom layout)',
470
+ value: 'stretch',
471
+ },
472
+ ]}
473
+ value={modalJustify}
474
+ />
490
475
  </ModalContent>
491
476
  </ModalBody>
492
477
  <ModalFooter justify={modalJustify}>
@@ -514,6 +499,7 @@ There are two ways to position buttons within the ModalFooter:
514
499
  </ToolbarGroup>
515
500
  <ToolbarItem>
516
501
  <Button
502
+ color="secondary"
517
503
  label="Close"
518
504
  onClick={() => setModalOpen(false)}
519
505
  priority="outline"
@@ -524,12 +510,12 @@ There are two ways to position buttons within the ModalFooter:
524
510
  ) : (
525
511
  <>
526
512
  <Button
527
- color="danger"
528
- label="Delete"
513
+ label="OK"
529
514
  onClick={() => setModalOpen(false)}
530
515
  ref={modalPrimaryButtonRef}
531
516
  />
532
517
  <Button
518
+ color="secondary"
533
519
  label="Close"
534
520
  onClick={() => setModalOpen(false)}
535
521
  priority="outline"
@@ -566,7 +552,6 @@ Modals of any size automatically shrink when they cannot fit the screen width.
566
552
  setModalSize('small');
567
553
  setModalOpen(true);
568
554
  }}
569
- priority="outline"
570
555
  />
571
556
  <Button
572
557
  label="Launch medium modal"
@@ -574,7 +559,6 @@ Modals of any size automatically shrink when they cannot fit the screen width.
574
559
  setModalSize('medium');
575
560
  setModalOpen(true);
576
561
  }}
577
- priority="outline"
578
562
  />
579
563
  <Button
580
564
  label="Launch large modal"
@@ -582,7 +566,6 @@ Modals of any size automatically shrink when they cannot fit the screen width.
582
566
  setModalSize('large');
583
567
  setModalOpen(true);
584
568
  }}
585
- priority="outline"
586
569
  />
587
570
  <Button
588
571
  label="Launch fullscreen modal"
@@ -590,7 +573,6 @@ Modals of any size automatically shrink when they cannot fit the screen width.
590
573
  setModalSize('fullscreen');
591
574
  setModalOpen(true);
592
575
  }}
593
- priority="outline"
594
576
  />
595
577
  <div>
596
578
  {modalOpen && (
@@ -619,6 +601,7 @@ Modals of any size automatically shrink when they cannot fit the screen width.
619
601
  ref={modalPrimaryButtonRef}
620
602
  />
621
603
  <Button
604
+ color="secondary"
622
605
  label="Close"
623
606
  onClick={() => setModalOpen(false)}
624
607
  priority="outline"
@@ -645,7 +628,6 @@ On top of that, the modal can adjust to the width of its content.
645
628
  <Button
646
629
  label="Launch auto-width modal"
647
630
  onClick={() => setModalOpen(true)}
648
- priority="outline"
649
631
  />
650
632
  <div>
651
633
  {modalOpen && (
@@ -674,6 +656,7 @@ On top of that, the modal can adjust to the width of its content.
674
656
  ref={modalPrimaryButtonRef}
675
657
  />
676
658
  <Button
659
+ color="secondary"
677
660
  label="Close"
678
661
  onClick={() => setModalOpen(false)}
679
662
  priority="outline"
@@ -688,57 +671,61 @@ On top of that, the modal can adjust to the width of its content.
688
671
  }}
689
672
  </Playground>
690
673
 
691
- 👉 Please note the auto width may not function correctly in combination with
692
- other auto-layout mechanisms, e.g. the auto-width
693
- [FormLayout](/components/form-layout#label-width). It's just too much
694
- magic that doesn't work together (yet?) 🎩.
674
+ ## Position
695
675
 
696
- 👉 Beware of horizontal FormLayout inside `small` modals. While automatic
697
- overflow handling comes to the rescue in this kind of scenario, you will be
698
- better off with the combination of auto-sized modal and horizontal FormLayout
699
- with a fixed label width (i.e. any other than `auto`, see the previous note).
676
+ Modal can be aligned either to the top or center of the screen.
700
677
 
701
678
  <Playground>
702
679
  {() => {
703
680
  const [modalOpen, setModalOpen] = React.useState(false);
681
+ const [modalPosition, setModalPosition] = React.useState('center');
704
682
  const modalPrimaryButtonRef = React.useRef();
705
683
  const modalCloseButtonRef = React.useRef();
706
684
  return (
707
685
  <>
708
686
  <Button
709
- label="Launch auto-with modal with a form"
710
- onClick={() => setModalOpen(true)}
711
- priority="outline"
687
+ label="Launch modal at center"
688
+ onClick={() => {
689
+ setModalPosition('center');
690
+ setModalOpen(true);
691
+ }}
692
+ />
693
+ <Button
694
+ label="Launch modal at top"
695
+ onClick={() => {
696
+ setModalPosition('top');
697
+ setModalOpen(true);
698
+ }}
712
699
  />
713
700
  <div>
714
701
  {modalOpen && (
715
702
  <Modal
716
703
  closeButtonRef={modalCloseButtonRef}
704
+ position={modalPosition}
717
705
  primaryButtonRef={modalPrimaryButtonRef}
718
- size="auto"
719
706
  >
720
707
  <ModalHeader>
721
- <ModalTitle>Form inside modal</ModalTitle>
708
+ <ModalTitle>Delete the user?</ModalTitle>
722
709
  <ModalCloseButton onClick={() => setModalOpen(false)} />
723
710
  </ModalHeader>
724
711
  <ModalBody>
725
712
  <ModalContent>
726
- <FormLayout fieldLayout="horizontal">
727
- <TextField label="A form element" />
728
- <TextField label="Another form element" />
729
- <TextField label="Yet another one" />
730
- </FormLayout>
713
+ <p>
714
+ Do you really want to delete the user <code>admin</code>?
715
+ This cannot be undone.
716
+ </p>
731
717
  </ModalContent>
732
718
  </ModalBody>
733
719
  <ModalFooter>
734
720
  <Button
735
- color="primary"
736
- label="Save"
721
+ color="danger"
722
+ label="Delete"
737
723
  onClick={() => setModalOpen(false)}
738
724
  ref={modalPrimaryButtonRef}
739
725
  />
740
726
  <Button
741
- label="Cancel"
727
+ color="secondary"
728
+ label="Close"
742
729
  onClick={() => setModalOpen(false)}
743
730
  priority="outline"
744
731
  ref={modalCloseButtonRef}
@@ -752,62 +739,64 @@ with a fixed label width (i.e. any other than `auto`, see the previous note).
752
739
  }}
753
740
  </Playground>
754
741
 
755
- ## Position
742
+ ## Forms in Modals
756
743
 
757
- Modal can be aligned either to the top or center of the screen.
744
+ You can safely place a FormLayout into a Modal of any size, including the
745
+ auto-width Modal.
758
746
 
759
747
  <Playground>
760
748
  {() => {
761
749
  const [modalOpen, setModalOpen] = React.useState(false);
762
- const [modalPosition, setModalPosition] = React.useState('center');
750
+ const [agree, setAgree] = React.useState(true);
763
751
  const modalPrimaryButtonRef = React.useRef();
764
752
  const modalCloseButtonRef = React.useRef();
765
753
  return (
766
754
  <>
767
755
  <Button
768
- label="Launch modal at center"
769
- onClick={() => {
770
- setModalPosition('center');
771
- setModalOpen(true);
772
- }}
773
- priority="outline"
774
- />
775
- <Button
776
- label="Launch modal at top"
777
- onClick={() => {
778
- setModalPosition('top');
779
- setModalOpen(true);
780
- }}
781
- priority="outline"
756
+ label="Launch auto-width modal with auto-width form"
757
+ onClick={() => setModalOpen(true)}
782
758
  />
783
759
  <div>
784
760
  {modalOpen && (
785
761
  <Modal
786
762
  closeButtonRef={modalCloseButtonRef}
787
- position={modalPosition}
788
763
  primaryButtonRef={modalPrimaryButtonRef}
764
+ size="auto"
789
765
  >
790
766
  <ModalHeader>
791
- <ModalTitle>Delete the user?</ModalTitle>
767
+ <ModalTitle>Auto-width form inside auto-width modal</ModalTitle>
792
768
  <ModalCloseButton onClick={() => setModalOpen(false)} />
793
769
  </ModalHeader>
794
770
  <ModalBody>
795
771
  <ModalContent>
796
- <p>
797
- Do you really want to delete the user <code>admin</code>?
798
- This cannot be undone.
799
- </p>
772
+ <FormLayout autoWidth fieldLayout="horizontal">
773
+ <TextField
774
+ label="A form element"
775
+ validationState="warning"
776
+ validationText={`Account with this name already exists,
777
+ pick a different one.`
778
+ }
779
+ />
780
+ <TextField label="Another form element" />
781
+ <TextField label="Yet another one" />
782
+ <CheckboxField
783
+ checked={agree}
784
+ label="I agree"
785
+ onChange={() => setAgree(!agree)}
786
+ />
787
+ </FormLayout>
800
788
  </ModalContent>
801
789
  </ModalBody>
802
790
  <ModalFooter>
803
791
  <Button
804
- color="danger"
805
- label="Delete"
792
+ color="primary"
793
+ label="Save"
806
794
  onClick={() => setModalOpen(false)}
807
795
  ref={modalPrimaryButtonRef}
808
796
  />
809
797
  <Button
810
- label="Close"
798
+ color="secondary"
799
+ label="Cancel"
811
800
  onClick={() => setModalOpen(false)}
812
801
  priority="outline"
813
802
  ref={modalCloseButtonRef}
@@ -821,6 +810,14 @@ Modal can be aligned either to the top or center of the screen.
821
810
  }}
822
811
  </Playground>
823
812
 
813
+ 👉 Inside Modal, we recommend using the `autoWidth` option of FormLayout. This
814
+ prevents the Modal from unwanted horizontal expansion when a long validation
815
+ text pops up during user's interaction with the form.
816
+
817
+ 👉 Beware of horizontal FormLayout inside `small` modals. While automatic
818
+ overflow handling comes to the rescue in this kind of scenario, you will be
819
+ better off with the combination of auto-sized modal and horizontal FormLayout.
820
+
824
821
  ## Keyboard Control
825
822
 
826
823
  Modal can be controlled either by mouse or keyboard. To enhance user
@@ -829,24 +826,147 @@ can be closed by pressing the `Escape` key.
829
826
 
830
827
  To enable it, you just need to pass a reference to the buttons using
831
828
  `primaryButtonRef` and `closeButtonRef` props on Modal. The advantage of passing
832
- a reference to the button is that if the button is disabled, the key press will
833
- not fire the event.
834
-
835
- 👉 We strongly recommend using this feature together with Autofocus for a better
836
- user experience.
829
+ the reference to the button is that if the button is disabled, the key press
830
+ will not fire the event.
837
831
 
838
832
  ## Autofocus
839
833
 
840
834
  Autofocus is implemented to enhance the user experience by automatically
841
- focussing an element within the modal.
835
+ focusing an element within the Modal.
842
836
 
843
837
  How does it work? It tries to find `input`, `textarea`, and `select` elements
844
838
  inside of Modal and moves focus onto the first non-disabled one. If none is
845
839
  found and the `primaryButtonRef` prop on Modal is set, then the primary button
846
- is focused.
840
+ is focused. If there are neither, it tries to focus any other focusable elements.
841
+ In case there are none or `autoFocus` is disabled, Modal itself is focused.
847
842
 
848
- Autofocus is enabled by default, so if you want to control the focus of
849
- elements manually, set the `autoFocus` prop on Modal to `false`.
843
+ <Playground>
844
+ {() => {
845
+ const [modalOpen, setModalOpen] = React.useState(null);
846
+ const modalPrimaryButtonRef = React.useRef();
847
+ const modalCloseButtonRef = React.useRef();
848
+ return (
849
+ <>
850
+ <Button
851
+ label="Launch modal with autofocus and form"
852
+ onClick={() => setModalOpen(1)}
853
+ />
854
+ <Button
855
+ label="Launch modal with autofocus"
856
+ onClick={() => setModalOpen(2)}
857
+ />
858
+ <Button
859
+ label="Launch modal with autofocus disabled"
860
+ onClick={() => setModalOpen(3)}
861
+ />
862
+ <div>
863
+ {modalOpen === 1 && (
864
+ <Modal
865
+ closeButtonRef={modalCloseButtonRef}
866
+ primaryButtonRef={modalPrimaryButtonRef}
867
+ >
868
+ <ModalHeader>
869
+ <ModalTitle>Modal with autoFocus and form</ModalTitle>
870
+ <ModalCloseButton onClick={() => setModalOpen(null)} />
871
+ </ModalHeader>
872
+ <ModalBody>
873
+ <ModalContent>
874
+ <FormLayout autoWidth fieldLayout="horizontal">
875
+ <TextField
876
+ disabled
877
+ label="A form element"
878
+ />
879
+ <TextField label="Another form element" />
880
+ <TextArea label="Yet another one" />
881
+ </FormLayout>
882
+ </ModalContent>
883
+ </ModalBody>
884
+ <ModalFooter>
885
+ <Button
886
+ label="Submit"
887
+ onClick={() => setModalOpen(null)}
888
+ ref={modalPrimaryButtonRef}
889
+ />
890
+ <Button
891
+ color="secondary"
892
+ label="Close"
893
+ onClick={() => setModalOpen(null)}
894
+ priority="outline"
895
+ ref={modalCloseButtonRef}
896
+ />
897
+ </ModalFooter>
898
+ </Modal>
899
+ )}
900
+ {modalOpen === 2 && (
901
+ <Modal
902
+ closeButtonRef={modalCloseButtonRef}
903
+ primaryButtonRef={modalPrimaryButtonRef}
904
+ >
905
+ <ModalHeader>
906
+ <ModalTitle>Modal with autoFocus enabled with no form</ModalTitle>
907
+ <ModalCloseButton onClick={() => setModalOpen(null)} />
908
+ </ModalHeader>
909
+ <ModalBody>
910
+ <ModalContent>
911
+ <p>
912
+ This Modal autofocuses the primary button or any other
913
+ focusable element.
914
+ </p>
915
+ </ModalContent>
916
+ </ModalBody>
917
+ <ModalFooter>
918
+ <Button
919
+ label="Acknowledge"
920
+ onClick={() => setModalOpen(null)}
921
+ ref={modalPrimaryButtonRef}
922
+ />
923
+ <Button
924
+ color="secondary"
925
+ label="Close"
926
+ onClick={() => setModalOpen(null)}
927
+ priority="outline"
928
+ ref={modalCloseButtonRef}
929
+ />
930
+ </ModalFooter>
931
+ </Modal>
932
+ )}
933
+ {modalOpen === 3 && (
934
+ <Modal
935
+ autoFocus={false}
936
+ closeButtonRef={modalCloseButtonRef}
937
+ primaryButtonRef={modalPrimaryButtonRef}
938
+ >
939
+ <ModalHeader>
940
+ <ModalTitle>Modal with autoFocus disabled</ModalTitle>
941
+ </ModalHeader>
942
+ <ModalBody>
943
+ <ModalContent>
944
+ <p>
945
+ This Modal focuses the Modal element itself.
946
+ </p>
947
+ </ModalContent>
948
+ </ModalBody>
949
+ <ModalFooter>
950
+ <Button
951
+ label="Acknowledge"
952
+ onClick={() => setModalOpen(null)}
953
+ ref={modalPrimaryButtonRef}
954
+ />
955
+ <Button
956
+ color="secondary"
957
+ label="Close"
958
+ onClick={() => setModalOpen(null)}
959
+ priority="outline"
960
+ ref={modalCloseButtonRef}
961
+ />
962
+ </ModalFooter>
963
+ </Modal>
964
+ )}
965
+ </div>
966
+ </>
967
+ );
968
+ }}
969
+ </Playground>
850
970
 
851
971
  ## Scrolling Long Content
852
972
 
@@ -948,7 +1068,6 @@ independent of the page itself. This can be done in three ways using the
948
1068
  setModalScrolling('auto');
949
1069
  setModalOpen(true);
950
1070
  }}
951
- priority="outline"
952
1071
  />
953
1072
  <Button
954
1073
  label="Launch modal with ScrollView"
@@ -956,7 +1075,6 @@ independent of the page itself. This can be done in three ways using the
956
1075
  setModalScrolling('custom');
957
1076
  setModalOpen(true);
958
1077
  }}
959
- priority="outline"
960
1078
  />
961
1079
  <Button
962
1080
  label="Launch modal with non-scrolling body"
@@ -964,12 +1082,11 @@ independent of the page itself. This can be done in three ways using the
964
1082
  setModalScrolling('none');
965
1083
  setModalOpen(true);
966
1084
  }}
967
- priority="outline"
968
1085
  />
969
1086
  <div>
970
1087
  {modalOpen && (
971
1088
  <Modal
972
- autoFocus={false}
1089
+ autoFocus={modalScrolling !== 'none'}
973
1090
  closeButtonRef={modalCloseButtonRef}
974
1091
  primaryButtonRef={modalPrimaryButtonRef}
975
1092
  size="small"
@@ -996,6 +1113,7 @@ independent of the page itself. This can be done in three ways using the
996
1113
  ref={modalPrimaryButtonRef}
997
1114
  />
998
1115
  <Button
1116
+ color="secondary"
999
1117
  label="Close"
1000
1118
  onClick={() => setModalOpen(false)}
1001
1119
  priority="outline"
@@ -1012,9 +1130,154 @@ independent of the page itself. This can be done in three ways using the
1012
1130
 
1013
1131
  ### Long Content and Autofocus
1014
1132
 
1015
- 👉 If you wrap ModalContent with ScrollView, you may want to turn `autoFocus`
1016
- off to prevent the modal from scrolling to the end immediately after being
1017
- opened.
1133
+ 👉 If you have a Modal with `scrolling` set to `none`, you may want to disable
1134
+ `autoFocus` to prevent the modal from scrolling to the end immediately after
1135
+ being opened.
1136
+
1137
+ ## Prevent Scrolling Underneath the Modal
1138
+
1139
+ You can choose the mode in which Modal prevents the scroll of the page underneath.
1140
+ Default mode prevents scrolling on `<body>` element and accounts for the scrollbar
1141
+ width. If you choose `off`, there will be no scroll prevention. If you need more
1142
+ flexibility, define your methods `start` (called on Modal's mount) and `reset`
1143
+ (called on Modal unmount) wrapped by an object and handle scroll prevention
1144
+ yourself.
1145
+
1146
+ <Playground>
1147
+ {() => {
1148
+ const [modalOpen, setModalOpen] = React.useState(null);
1149
+ const modalPrimaryButtonRef = React.useRef();
1150
+ const modalCloseButtonRef = React.useRef();
1151
+ const customScrollPreventionObject = {
1152
+ start: () => {
1153
+ // YOUR CUSTOM SCROLL PREVENTING LOGIC GOES HERE
1154
+ window.document.body.style.overflowY = 'hidden'
1155
+ },
1156
+ reset: () => {
1157
+ // YOUR CUSTOM SCROLL RE-ENABLING LOGIC GOES HERE
1158
+ window.document.body.style.overflowY = 'auto'
1159
+ },
1160
+ };
1161
+ return (
1162
+ <>
1163
+ <Button
1164
+ label="Launch modal with default scroll prevention"
1165
+ onClick={() => setModalOpen(1)}
1166
+ />
1167
+ <Button
1168
+ label="Launch modal with no scroll prevention"
1169
+ onClick={() => setModalOpen(2)}
1170
+ />
1171
+ <Button
1172
+ label="Launch modal with custom scroll prevention"
1173
+ onClick={() => setModalOpen(3)}
1174
+ />
1175
+ <div>
1176
+ {modalOpen === 1 && (
1177
+ <Modal
1178
+ closeButtonRef={modalCloseButtonRef}
1179
+ primaryButtonRef={modalPrimaryButtonRef}
1180
+ >
1181
+ <ModalHeader>
1182
+ <ModalTitle>Modal with default scroll prevention</ModalTitle>
1183
+ <ModalCloseButton onClick={() => setModalOpen(null)} />
1184
+ </ModalHeader>
1185
+ <ModalBody>
1186
+ <ModalContent>
1187
+ <p>
1188
+ This Modal uses default scroll prevention on the document's
1189
+ <code>body</code> element.
1190
+ </p>
1191
+ </ModalContent>
1192
+ </ModalBody>
1193
+ <ModalFooter>
1194
+ <Button
1195
+ label="Acknowledge"
1196
+ onClick={() => setModalOpen(null)}
1197
+ ref={modalPrimaryButtonRef}
1198
+ />
1199
+ <Button
1200
+ color="secondary"
1201
+ label="Close"
1202
+ onClick={() => setModalOpen(null)}
1203
+ priority="outline"
1204
+ ref={modalCloseButtonRef}
1205
+ />
1206
+ </ModalFooter>
1207
+ </Modal>
1208
+ )}
1209
+ {modalOpen === 2 && (
1210
+ <Modal
1211
+ closeButtonRef={modalCloseButtonRef}
1212
+ preventScrollUnderneath="off"
1213
+ primaryButtonRef={modalPrimaryButtonRef}
1214
+ >
1215
+ <ModalHeader>
1216
+ <ModalTitle>Modal with no scroll prevention</ModalTitle>
1217
+ <ModalCloseButton onClick={() => setModalOpen(null)} />
1218
+ </ModalHeader>
1219
+ <ModalBody>
1220
+ <ModalContent>
1221
+ <p>
1222
+ This Modal does not prevent scrolling.
1223
+ </p>
1224
+ </ModalContent>
1225
+ </ModalBody>
1226
+ <ModalFooter>
1227
+ <Button
1228
+ label="Acknowledge"
1229
+ onClick={() => setModalOpen(null)}
1230
+ ref={modalPrimaryButtonRef}
1231
+ />
1232
+ <Button
1233
+ color="secondary"
1234
+ label="Close"
1235
+ onClick={() => setModalOpen(null)}
1236
+ priority="outline"
1237
+ ref={modalCloseButtonRef}
1238
+ />
1239
+ </ModalFooter>
1240
+ </Modal>
1241
+ )}
1242
+ {modalOpen === 3 && (
1243
+ <Modal
1244
+ closeButtonRef={modalCloseButtonRef}
1245
+ preventScrollUnderneath={customScrollPreventionObject}
1246
+ primaryButtonRef={modalPrimaryButtonRef}
1247
+ >
1248
+ <ModalHeader>
1249
+ <ModalTitle>Modal with custom scroll prevention</ModalTitle>
1250
+ <ModalCloseButton onClick={() => setModalOpen(null)} />
1251
+ </ModalHeader>
1252
+ <ModalBody>
1253
+ <ModalContent>
1254
+ <p>
1255
+ This Modal uses provided custom functions to prevent scrolling
1256
+ and reset it on unmount.
1257
+ </p>
1258
+ </ModalContent>
1259
+ </ModalBody>
1260
+ <ModalFooter>
1261
+ <Button
1262
+ label="Acknowledge"
1263
+ onClick={() => setModalOpen(null)}
1264
+ ref={modalPrimaryButtonRef}
1265
+ />
1266
+ <Button
1267
+ color="secondary"
1268
+ label="Close"
1269
+ onClick={() => setModalOpen(null)}
1270
+ priority="outline"
1271
+ ref={modalCloseButtonRef}
1272
+ />
1273
+ </ModalFooter>
1274
+ </Modal>
1275
+ )}
1276
+ </div>
1277
+ </>
1278
+ );
1279
+ }}
1280
+ </Playground>
1018
1281
 
1019
1282
  <!-- markdownlint-disable MD024 -->
1020
1283
 
@@ -1090,6 +1353,7 @@ accessibility.
1090
1353
  | `--rui-Modal--fullscreen__width` | Width of fullscreen modal |
1091
1354
  | `--rui-Modal--fullscreen__height` | Height of fullscreen modal |
1092
1355
 
1356
+ [nng-modal]: https://www.nngroup.com/articles/modal-nonmodal-dialog/
1093
1357
  [React synthetic events]: https://reactjs.org/docs/events.html
1094
1358
  [div]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div#attributes
1095
1359
  [heading]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Heading_Elements#attributes