@thecb/components 10.5.0-beta.1 → 10.5.0-beta.3

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thecb/components",
3
- "version": "10.5.0-beta.1",
3
+ "version": "10.5.0-beta.3",
4
4
  "description": "Common lib for CityBase react components",
5
5
  "main": "dist/index.cjs.js",
6
6
  "typings": "dist/index.d.ts",
@@ -24,22 +24,23 @@ export const modalV1 = () => (
24
24
  Modal Link
25
25
  </Text>
26
26
  )}
27
- hideModal={noop}
28
- continueAction={noop}
29
- modalOpen={boolean("modalOpen", true, groupId)}
30
- modalHeaderText={text("modalHeaderText", "Modal Header Text", groupId)}
31
- modalBodyText={text("modalBodyText", "Modal Body Text", groupId)}
27
+ blurUnderlay={boolean("blurUnderlay", true, groupId)}
32
28
  cancelButtonText={text("cancelButtonText", "Cancel", groupId)}
29
+ continueAction={noop}
33
30
  continueButtonText={text("continueButtonText", "Continue", groupId)}
34
- modalHeaderBg={text("modalHeaderBg", "white", groupId)}
35
- modalBodyBg={text("modalBodyBg", "#F6F6F9", groupId)}
36
- useDangerButton={boolean("useDangerButton", false, groupId)}
37
31
  defaultWrapper={boolean("defaultWrapper", true, groupId)}
32
+ hideModal={noop}
33
+ maxHeight={text("maxHeight", null, groupId)}
34
+ modalBodyBg={text("modalBodyBg", "#F6F6F9", groupId)}
35
+ modalBodyText={text("modalBodyText", "Modal Body Text", groupId)}
36
+ modalHeaderBg={text("modalHeaderBg", "white", groupId)}
37
+ modalHeaderText={text("modalHeaderText", "Modal Header Text", groupId)}
38
+ modalOpen={boolean("modalOpen", true, groupId)}
38
39
  noBorder={boolean("noBorder", true, groupId)}
40
+ noButtons={boolean("noButtons", false, groupId)}
39
41
  onlyCloseButton={boolean("onlyCloseButton", false, groupId)}
40
42
  onlyContinueButton={boolean("onlyContinueButton", false, groupId)}
41
- maxHeight={text("maxHeight", undefined, groupId)}
42
- blurUnderlay={boolean("blurUnderlay", true, groupId)}
43
+ useDangerButton={boolean("useDangerButton", false, groupId)}
43
44
  />
44
45
  );
45
46
 
@@ -66,7 +67,7 @@ export const modalV2 = () => (
66
67
  continueURL={text("continueURL", "", groupId)}
67
68
  defaultWrapper={boolean("defaultWrapper", true, groupId)}
68
69
  hideModal={noop}
69
- maxHeight={text("maxHeight", undefined, groupId)}
70
+ maxHeight={text("maxHeight", null, groupId)}
70
71
  modalBodyBg={text("modalBodyBg", "#F6F6F9", groupId)}
71
72
  modalBodyText={text("modalBodyText", "Modal Body Text", groupId)}
72
73
  modalHeaderBg={text("modalHeaderBg", "white", groupId)}
@@ -121,7 +122,7 @@ export const modalWithoutButtons = () => {
121
122
  modalBodyBg={text("modalBodyBg", "#F6F6F9", groupId)}
122
123
  defaultWrapper={boolean("defaultWrapper", true, groupId)}
123
124
  noButtons={boolean("noButtons", true, groupId)}
124
- maxHeight={text("maxHeight", undefined, groupId)}
125
+ maxHeight={text("maxHeight", null, groupId)}
125
126
  blurUnderlay={boolean("blurUnderlay", false, groupId)}
126
127
  />
127
128
  );
@@ -1,4 +1,4 @@
1
- import React, { Fragment, useContext, useRef } from "react";
1
+ import React, { Fragment, useContext, useEffect, useRef } from "react";
2
2
  import { ThemeContext } from "styled-components";
3
3
  import AriaModal from "react-aria-modal";
4
4
  import { WHITE, ATHENS_GREY, SILVER_GREY } from "../../../constants/colors";
@@ -50,14 +50,27 @@ const Modal = ({
50
50
  blurUnderlay = true
51
51
  }) => {
52
52
  const { isMobile } = useContext(ThemeContext);
53
- const modalContainerRef = useRef(null);
53
+
54
+ // `AriaModal` uses `focus-trap` as a transient dependency. It automatically looks
55
+ // for a tabbable node when the modal mounts. When it doesn't find one, it looks to
56
+ // the `fallbackFocus` option. However, React does not guarantee the ref supplied to
57
+ // this option will be populated on initial render when `focus-trap` is checking
58
+ // these option. When there are no buttons in the modal, this causes an error.
59
+ // Because `focus-trap` cannot be disabled, the ref itself requires a default value
60
+ // to satisfy `focus-trap` until React populates it with the real ref value.
61
+ //
62
+ // See:
63
+ // - https://react.dev/reference/react/useRef#caveats
64
+ // - https://github.com/davidtheclark/react-aria-modal/pull/103
65
+ // - https://github.com/focus-trap/focus-trap?tab=readme-ov-file#createoptions
66
+ const modalContainerRef = useRef("[id='react-aria-modal-dialog']");
54
67
 
55
68
  return (
56
- <div ref={modalContainerRef} data-qa={dataQa}>
69
+ <div ref={modalContainerRef} tabIndex="-1" data-qa={dataQa}>
57
70
  {modalOpen && (
58
71
  <AriaModal
59
- // fallback to resolve Jest unit test errors when tabbable doesn't exist in jsdom https://github.com/focus-trap/focus-trap-react/issues/91
60
72
  focusTrapOptions={{
73
+ // fallback to resolve Jest unit test errors when tabbable doesn't exist in jsdom https://github.com/focus-trap/focus-trap-react/issues/91
61
74
  fallbackFocus: modalContainerRef?.current
62
75
  }}
63
76
  onExit={onExit}
@@ -64,7 +64,20 @@ const Modal = ({
64
64
  useDangerButton = false
65
65
  }) => {
66
66
  const { isMobile } = useContext(ThemeContext);
67
- const modalContainerRef = useRef(null);
67
+
68
+ // `AriaModal` uses `focus-trap` as a transient dependency. It automatically looks
69
+ // for a tabbable node when the modal mounts. When it doesn't find one, it looks to
70
+ // the `fallbackFocus` option. However, React does not guarantee the ref supplied to
71
+ // this option will be populated on initial render when `focus-trap` is checking
72
+ // these option. When there are no buttons in the modal, this causes an error.
73
+ // Because `focus-trap` cannot be disabled, the ref itself requires a default value
74
+ // to satisfy `focus-trap` until React populates it with the real ref value.
75
+ //
76
+ // See:
77
+ // - https://react.dev/reference/react/useRef#caveats
78
+ // - https://github.com/davidtheclark/react-aria-modal/pull/103
79
+ // - https://github.com/focus-trap/focus-trap?tab=readme-ov-file#createoptions
80
+ const modalContainerRef = useRef("[id='react-aria-modal-dialog']");
68
81
 
69
82
  const hasCloseButton = onlyCloseButton && !noButtons;
70
83
  const hasCancelButton = !onlyContinueButton && !onlyCloseButton && !noButtons;
@@ -72,11 +85,11 @@ const Modal = ({
72
85
  (onlyContinueButton && !noButtons) || (!onlyCloseButton && !noButtons);
73
86
 
74
87
  return (
75
- <div ref={modalContainerRef} data-qa={dataQa}>
88
+ <div ref={modalContainerRef} tabIndex="-1" data-qa={dataQa}>
76
89
  {modalOpen && (
77
90
  <AriaModal
78
- // fallback to resolve Jest unit test errors when tabbable doesn't exist in jsdom https://github.com/focus-trap/focus-trap-react/issues/91
79
91
  focusTrapOptions={{
92
+ // fallback to resolve Jest unit test errors when tabbable doesn't exist in jsdom https://github.com/focus-trap/focus-trap-react/issues/91
80
93
  fallbackFocus: modalContainerRef?.current
81
94
  }}
82
95
  onExit={onExit}
@@ -143,6 +156,7 @@ const Modal = ({
143
156
  cancelButtonText={cancelButtonText}
144
157
  hideModal={hideModal}
145
158
  isMobile={isMobile}
159
+ key="cancel"
146
160
  />
147
161
  ),
148
162
  hasContinueButton && (
@@ -154,6 +168,7 @@ const Modal = ({
154
168
  isContinueActionDisabled={isContinueActionDisabled}
155
169
  isLoading={isLoading}
156
170
  isMobile={isMobile}
171
+ key="continue"
157
172
  useDangerButton={useDangerButton}
158
173
  />
159
174
  ),
@@ -163,6 +178,7 @@ const Modal = ({
163
178
  closeButtonText={closeButtonText}
164
179
  hideModal={hideModal}
165
180
  isMobile={isMobile}
181
+ key="close"
166
182
  />
167
183
  )
168
184
  ].filter(button => button)}