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

Sign up to get free protection for your applications and to get access to all the features.
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