@thecb/components 10.4.7-beta.0 → 10.5.0-beta.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.
@@ -0,0 +1,221 @@
1
+ import React, { Fragment, useContext, useRef } from "react";
2
+ import { ThemeContext } from "styled-components";
3
+ import AriaModal from "react-aria-modal";
4
+ import { WHITE, ATHENS_GREY, SILVER_GREY } from "../../../constants/colors";
5
+ import { FONT_WEIGHT_SEMIBOLD } from "../../../constants/style_constants";
6
+ import Paragraph from "../../atoms/paragraph";
7
+ import Title from "../../atoms/title";
8
+ import ButtonWithAction from "../../atoms/button-with-action";
9
+ import { Box, Stack, Cluster } from "../../atoms/layouts";
10
+
11
+ /*
12
+ Default Modal molecule
13
+ Uses react-aria-modal behind the scenes for a11y purposes
14
+ Styling accomplished with our atoms / layout primitives
15
+
16
+ Cancel button will (for now) always use hideModal as its action
17
+ Continue button takes an action, if you want to navigate to
18
+ a different route (as with a link) connect() and use "push" from @thecb/connected-react-router
19
+ */
20
+
21
+ const getApplicationNode = () => document.getElementById("root");
22
+
23
+ const Modal = ({
24
+ hideModal,
25
+ onExit = hideModal,
26
+ continueAction,
27
+ isContinueActionDisabled = false,
28
+ cancelAction,
29
+ modalOpen,
30
+ modalHeaderText,
31
+ modalBodyText,
32
+ cancelButtonText = "Cancel",
33
+ continueButtonText = "Continue",
34
+ closeButtonText = "Close",
35
+ modalHeaderBg = WHITE,
36
+ modalBodyBg = ATHENS_GREY,
37
+ useDangerButton = false,
38
+ defaultWrapper = true,
39
+ onlyCloseButton = false,
40
+ noButtons = false, // for instances where modal is closed automatically
41
+ maxHeight,
42
+ underlayClickExits = true,
43
+ noBorder,
44
+ customWidth,
45
+ isLoading,
46
+ buttonExtraStyles,
47
+ children,
48
+ dataQa = null,
49
+ initialFocusSelector = "",
50
+ blurUnderlay = true
51
+ }) => {
52
+ const { isMobile } = useContext(ThemeContext);
53
+ const modalContainerRef = useRef(null);
54
+
55
+ return (
56
+ <div ref={modalContainerRef} data-qa={dataQa}>
57
+ {modalOpen && (
58
+ <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
+ focusTrapOptions={{
61
+ fallbackFocus: modalContainerRef?.current
62
+ }}
63
+ onExit={onExit}
64
+ getApplicationNode={getApplicationNode}
65
+ titleText={modalHeaderText}
66
+ underlayStyle={{
67
+ display: "flex",
68
+ flexDirection: "column",
69
+ justifyContent: "center",
70
+ alignItems: "center",
71
+ background: "rgba(41, 42, 51, 0.45)",
72
+ backdropFilter: blurUnderlay ? "blur(4px)" : "none",
73
+ WebkitBackdropFilter: blurUnderlay ? "blur(4px)" : "none"
74
+ }}
75
+ dialogStyle={{
76
+ width: customWidth || "615px",
77
+ overflow: "auto"
78
+ }}
79
+ underlayClickExits={underlayClickExits}
80
+ aria-modal={true}
81
+ initialFocus={initialFocusSelector}
82
+ focusDialog={!initialFocusSelector} // Focus the dialogue box itself if no selector for initial focus was provided
83
+ >
84
+ <Box
85
+ padding="0"
86
+ borderRadius="2px"
87
+ boxShadow="inset 0px -2px 0px 0px rgb(0, 80, 149)"
88
+ >
89
+ <Box background={modalHeaderBg} padding="1.5rem">
90
+ <Cluster justify="flex-start" align="center">
91
+ <Title as="h2" weight={FONT_WEIGHT_SEMIBOLD} fontSize="1.25rem">
92
+ {modalHeaderText}
93
+ </Title>
94
+ </Cluster>
95
+ </Box>
96
+ <Box background={modalBodyBg} padding="1.5rem">
97
+ <Stack childGap="1.5rem">
98
+ <Box
99
+ borderWidthOverride={noBorder && "0 0 2px 0"}
100
+ borderColor={!noBorder && SILVER_GREY}
101
+ padding={!noBorder && "0 0 1.5rem 0"}
102
+ extraStyles={
103
+ maxHeight ? `max-height: ${maxHeight}; overflow: auto;` : ``
104
+ }
105
+ >
106
+ {defaultWrapper ? (
107
+ <Paragraph variant="p">{modalBodyText}</Paragraph>
108
+ ) : (
109
+ <Box padding={maxHeight ? "0 0 1rem 0" : "0"}>
110
+ {modalBodyText}
111
+ </Box>
112
+ )}
113
+ </Box>
114
+ <Box padding="0">
115
+ <Stack
116
+ direction="row"
117
+ justify="flex-end"
118
+ align="center"
119
+ childGap="0rem"
120
+ >
121
+ {!noButtons && (
122
+ <>
123
+ {!onlyCloseButton ? (
124
+ <Fragment>
125
+ {isMobile ? (
126
+ <Stack childGap="1rem" direction="row">
127
+ <Box width="100%" padding="0">
128
+ <ButtonWithAction
129
+ variant="secondary"
130
+ action={
131
+ cancelAction ? cancelAction : hideModal
132
+ }
133
+ text={cancelButtonText}
134
+ dataQa={cancelButtonText}
135
+ extraStyles={buttonExtraStyles}
136
+ className="modal-cancel-button"
137
+ role="button"
138
+ name={cancelButtonText}
139
+ />
140
+ </Box>
141
+ <Box width="100%" padding="0">
142
+ <ButtonWithAction
143
+ variant={
144
+ useDangerButton ? "danger" : "primary"
145
+ }
146
+ action={continueAction}
147
+ text={continueButtonText}
148
+ dataQa={continueButtonText}
149
+ isLoading={isLoading}
150
+ disabled={isContinueActionDisabled}
151
+ extraStyles={buttonExtraStyles}
152
+ className="modal-continue-button"
153
+ role="button"
154
+ name={continueButtonText}
155
+ />
156
+ </Box>
157
+ </Stack>
158
+ ) : (
159
+ <Stack
160
+ childGap="1rem"
161
+ direction="row"
162
+ justify="flex-end"
163
+ >
164
+ <ButtonWithAction
165
+ variant="secondary"
166
+ action={
167
+ cancelAction ? cancelAction : hideModal
168
+ }
169
+ text={cancelButtonText}
170
+ dataQa={cancelButtonText}
171
+ extraStyles={buttonExtraStyles}
172
+ className="modal-cancel-button"
173
+ role="button"
174
+ name={cancelButtonText}
175
+ />
176
+ <ButtonWithAction
177
+ variant={
178
+ useDangerButton ? "danger" : "primary"
179
+ }
180
+ action={continueAction}
181
+ text={continueButtonText}
182
+ dataQa={continueButtonText}
183
+ isLoading={isLoading}
184
+ disabled={isContinueActionDisabled}
185
+ extraStyles={buttonExtraStyles}
186
+ className="modal-continue-button"
187
+ role="button"
188
+ name={continueButtonText}
189
+ />
190
+ </Stack>
191
+ )}
192
+ </Fragment>
193
+ ) : (
194
+ <Box padding="0.5rem">
195
+ <ButtonWithAction
196
+ action={hideModal}
197
+ variant="primary"
198
+ text={closeButtonText}
199
+ dataQa={closeButtonText}
200
+ extraStyles={buttonExtraStyles}
201
+ className="modal-close-button"
202
+ role="button"
203
+ name={closeButtonText}
204
+ />
205
+ </Box>
206
+ )}
207
+ </>
208
+ )}
209
+ </Stack>
210
+ </Box>
211
+ </Stack>
212
+ </Box>
213
+ </Box>
214
+ </AriaModal>
215
+ )}
216
+ {children}
217
+ </div>
218
+ );
219
+ };
220
+
221
+ export default Modal;
@@ -0,0 +1,249 @@
1
+ import React, { Fragment, forwardRef, useContext, useRef } from "react";
2
+ import { ThemeContext } from "styled-components";
3
+ import AriaModal from "react-aria-modal";
4
+ import { WHITE, ATHENS_GREY, SILVER_GREY } from "../../../constants/colors";
5
+ import {
6
+ BORDER_THIN,
7
+ CORNER_STANDARD,
8
+ FONT_WEIGHT_SEMIBOLD,
9
+ SPACING_NORMAL,
10
+ SPACING_XS
11
+ } from "../../../constants/style_constants";
12
+ import Paragraph from "../../atoms/paragraph";
13
+ import Title from "../../atoms/title";
14
+ import ButtonWithAction from "../../atoms/button-with-action";
15
+ import { Box, Stack, Cluster } from "../../atoms/layouts";
16
+
17
+ /*
18
+ Default Modal molecule
19
+ Uses react-aria-modal behind the scenes for a11y purposes
20
+ Styling accomplished with our atoms / layout primitives
21
+
22
+ Cancel button will (for now) always use hideModal as its action
23
+ Continue button takes an action, if you want to navigate to
24
+ a different route (as with a link) connect() and use "push" from @thecb/connected-react-router
25
+ */
26
+
27
+ const getApplicationNode = () => document.getElementById("root");
28
+
29
+ const Modal = ({
30
+ blurUnderlay = true,
31
+ buttonExtraStyles,
32
+ cancelAction,
33
+ cancelButtonText = "Cancel",
34
+ children,
35
+ closeButtonText = "Close",
36
+ continueAction,
37
+ continueButtonText = "Continue",
38
+ customWidth,
39
+ dataQa = null,
40
+ defaultWrapper = true,
41
+ hideModal,
42
+ initialFocusSelector = "",
43
+ isContinueActionDisabled = false,
44
+ isLoading,
45
+ maxHeight,
46
+ modalBodyBg = ATHENS_GREY,
47
+ modalBodyText,
48
+ modalHeaderBg = WHITE,
49
+ modalHeaderText,
50
+ modalOpen,
51
+ noButtons = false, // for instances where modal is closed automatically
52
+ onExit = hideModal,
53
+ onlyCloseButton = false,
54
+ onlyContinueButton = false,
55
+ underlayClickExits = true,
56
+ useDangerButton = false
57
+ }) => {
58
+ const { isMobile } = useContext(ThemeContext);
59
+ const modalContainerRef = useRef(null);
60
+ const noButtonSection = noButtons;
61
+
62
+ const AllButtons = () =>
63
+ isMobile ? (
64
+ <Stack childGap="1rem" direction="row">
65
+ <Box width="100%" padding="0">
66
+ <ButtonWithAction
67
+ variant="secondary"
68
+ action={cancelAction ? cancelAction : hideModal}
69
+ text={cancelButtonText}
70
+ dataQa={cancelButtonText}
71
+ extraStyles={buttonExtraStyles}
72
+ borderRadius={CORNER_STANDARD}
73
+ className="modal-cancel-button"
74
+ role="button"
75
+ name={cancelButtonText}
76
+ />
77
+ </Box>
78
+ <Box width="100%" padding="0">
79
+ <ButtonWithAction
80
+ variant={useDangerButton ? "danger" : "primary"}
81
+ action={continueAction}
82
+ text={continueButtonText}
83
+ dataQa={continueButtonText}
84
+ isLoading={isLoading}
85
+ disabled={isContinueActionDisabled}
86
+ extraStyles={buttonExtraStyles}
87
+ borderRadius={CORNER_STANDARD}
88
+ className="modal-continue-button"
89
+ role="button"
90
+ name={continueButtonText}
91
+ />
92
+ </Box>
93
+ </Stack>
94
+ ) : (
95
+ <Stack childGap="1rem" direction="row" justify="flex-end">
96
+ <ButtonWithAction
97
+ variant="secondary"
98
+ action={cancelAction ? cancelAction : hideModal}
99
+ text={cancelButtonText}
100
+ dataQa={cancelButtonText}
101
+ extraStyles={buttonExtraStyles}
102
+ borderRadius={CORNER_STANDARD}
103
+ className="modal-cancel-button"
104
+ role="button"
105
+ name={cancelButtonText}
106
+ />
107
+ <ButtonWithAction
108
+ variant={useDangerButton ? "danger" : "primary"}
109
+ action={continueAction}
110
+ text={continueButtonText}
111
+ dataQa={continueButtonText}
112
+ isLoading={isLoading}
113
+ disabled={isContinueActionDisabled}
114
+ extraStyles={buttonExtraStyles}
115
+ borderRadius={CORNER_STANDARD}
116
+ className="modal-continue-button"
117
+ role="button"
118
+ name={continueButtonText}
119
+ />
120
+ </Stack>
121
+ );
122
+
123
+ const CloseButton = () => (
124
+ <ButtonWithAction
125
+ action={hideModal}
126
+ variant="primary"
127
+ text={closeButtonText}
128
+ dataQa={closeButtonText}
129
+ extraStyles={buttonExtraStyles}
130
+ borderRadius={CORNER_STANDARD}
131
+ className="modal-close-button"
132
+ role="button"
133
+ name={closeButtonText}
134
+ />
135
+ );
136
+
137
+ const ContinueButton = () => (
138
+ <ButtonWithAction
139
+ variant={useDangerButton ? "danger" : "primary"}
140
+ action={continueAction}
141
+ text={continueButtonText}
142
+ dataQa={continueButtonText}
143
+ isLoading={isLoading}
144
+ disabled={isContinueActionDisabled}
145
+ extraStyles={buttonExtraStyles}
146
+ borderRadius={CORNER_STANDARD}
147
+ className="modal-continue-button"
148
+ role="button"
149
+ name={continueButtonText}
150
+ />
151
+ );
152
+
153
+ let MaybeButtons = forwardRef(() => <Fragment />);
154
+ if (!noButtons) {
155
+ if (onlyContinueButton) {
156
+ MaybeButtons = ContinueButton;
157
+ } else if (onlyCloseButton) {
158
+ MaybeButtons = CloseButton;
159
+ } else {
160
+ MaybeButtons = AllButtons;
161
+ }
162
+ }
163
+
164
+ return (
165
+ <div ref={modalContainerRef} data-qa={dataQa}>
166
+ {modalOpen && (
167
+ <AriaModal
168
+ // fallback to resolve Jest unit test errors when tabbable doesn't exist in jsdom https://github.com/focus-trap/focus-trap-react/issues/91
169
+ focusTrapOptions={{
170
+ fallbackFocus: modalContainerRef?.current
171
+ }}
172
+ onExit={onExit}
173
+ getApplicationNode={getApplicationNode}
174
+ titleText={modalHeaderText}
175
+ underlayStyle={{
176
+ display: "flex",
177
+ flexDirection: "column",
178
+ justifyContent: "center",
179
+ alignItems: "center",
180
+ background: "rgba(41, 42, 51, 0.45)",
181
+ backdropFilter: blurUnderlay ? "blur(4px)" : "none",
182
+ WebkitBackdropFilter: blurUnderlay ? "blur(4px)" : "none"
183
+ }}
184
+ dialogStyle={{
185
+ borderRadius: CORNER_STANDARD,
186
+ width: customWidth || "615px",
187
+ overflow: "auto"
188
+ }}
189
+ underlayClickExits={underlayClickExits}
190
+ aria-modal={true}
191
+ initialFocus={initialFocusSelector}
192
+ focusDialog={!initialFocusSelector} // Focus the dialogue box itself if no selector for initial focus was provided
193
+ >
194
+ <Box padding="0" boxShadow="inset 0px -2px 0px 0px rgb(0, 80, 149)">
195
+ <Box
196
+ background={modalHeaderBg}
197
+ borderColor={SILVER_GREY}
198
+ borderWidthOverride={`0 0 ${BORDER_THIN} 0`}
199
+ padding={`${SPACING_XS} ${SPACING_NORMAL}`}
200
+ >
201
+ <Cluster justify="flex-start" align="center">
202
+ <Title as="h2" weight={FONT_WEIGHT_SEMIBOLD} fontSize="1.25rem">
203
+ {modalHeaderText}
204
+ </Title>
205
+ </Cluster>
206
+ </Box>
207
+ <Box background={modalBodyBg} padding="0">
208
+ <Stack childGap={SPACING_NORMAL}>
209
+ <Box
210
+ padding={SPACING_NORMAL}
211
+ borderWidthOverride={!noButtons && `0 0 ${BORDER_THIN} 0`}
212
+ borderColor={!noButtons && SILVER_GREY}
213
+ extraStyles={
214
+ maxHeight ? `max-height: ${maxHeight}; overflow: auto;` : ``
215
+ }
216
+ >
217
+ {defaultWrapper ? (
218
+ <Paragraph variant="p">{modalBodyText}</Paragraph>
219
+ ) : (
220
+ <Box padding={maxHeight ? `0 0 ${SPACING_XS} 0` : "0"}>
221
+ {modalBodyText}
222
+ </Box>
223
+ )}
224
+ </Box>
225
+ {noButtons ? (
226
+ <MaybeButtons />
227
+ ) : (
228
+ <Box padding={`0 ${SPACING_NORMAL} ${SPACING_NORMAL}`}>
229
+ <Stack
230
+ direction="row"
231
+ justify="flex-end"
232
+ align="center"
233
+ childGap="0rem"
234
+ >
235
+ <MaybeButtons />
236
+ </Stack>
237
+ </Box>
238
+ )}
239
+ </Stack>
240
+ </Box>
241
+ </Box>
242
+ </AriaModal>
243
+ )}
244
+ {children}
245
+ </div>
246
+ );
247
+ };
248
+
249
+ export default Modal;
@@ -1,11 +1,15 @@
1
1
  type StyleDeclaration = string;
2
2
 
3
- export const HEADER_HEIGHT: StyleDeclaration;
4
- export const FOOTER_HEIGHT: StyleDeclaration;
5
- export const SPACER_HEIGHT: StyleDeclaration;
6
- export const JUMBO_HEIGHT: StyleDeclaration;
7
- export const COMPACT_JUMBO_HEIGHT: StyleDeclaration;
3
+ export const BORDER_THIN: StyleDeclaration;
4
+ export const CORNER_STANDARD: StyleDeclaration;
8
5
  export const FONT_WEIGHT_REGULAR: StyleDeclaration;
9
- export const FONT_WEIGHT_BOLD: StyleDeclaration;
10
6
  export const FONT_WEIGHT_SEMIBOLD: StyleDeclaration;
7
+ export const FONT_WEIGHT_BOLD: StyleDeclaration;
8
+ export const FOOTER_HEIGHT: StyleDeclaration;
9
+ export const HEADER_HEIGHT: StyleDeclaration;
10
+ export const COMPACT_JUMBO_HEIGHT: StyleDeclaration;
11
+ export const JUMBO_HEIGHT: StyleDeclaration;
12
+ export const SPACER_HEIGHT: StyleDeclaration;
11
13
  export const LINK_TEXT_DECORATION: StyleDeclaration;
14
+ export const SPACING_XS: StyleDeclaration;
15
+ export const SPACING_NORMAL: StyleDeclaration;
@@ -1,23 +1,15 @@
1
1
  /* These are constants used by nav frontend components */
2
2
 
3
- const HEADER_HEIGHT = "104px";
4
- const FOOTER_HEIGHT = "100px";
5
- const SPACER_HEIGHT = "65px";
6
- const JUMBO_HEIGHT = "300px";
7
- const COMPACT_JUMBO_HEIGHT = "65px";
8
- const FONT_WEIGHT_REGULAR = "400";
9
- const FONT_WEIGHT_BOLD = "700";
10
- const FONT_WEIGHT_SEMIBOLD = "600";
11
- const LINK_TEXT_DECORATION = "underline solid 1px";
12
-
13
- export {
14
- HEADER_HEIGHT,
15
- FOOTER_HEIGHT,
16
- SPACER_HEIGHT,
17
- JUMBO_HEIGHT,
18
- COMPACT_JUMBO_HEIGHT,
19
- FONT_WEIGHT_REGULAR,
20
- FONT_WEIGHT_BOLD,
21
- FONT_WEIGHT_SEMIBOLD,
22
- LINK_TEXT_DECORATION
23
- };
3
+ export const BORDER_THIN = "1px";
4
+ export const CORNER_STANDARD = "4px";
5
+ export const FONT_WEIGHT_REGULAR = "400";
6
+ export const FONT_WEIGHT_SEMIBOLD = "600";
7
+ export const FONT_WEIGHT_BOLD = "700";
8
+ export const FOOTER_HEIGHT = "100px";
9
+ export const HEADER_HEIGHT = "104px";
10
+ export const COMPACT_JUMBO_HEIGHT = "65px";
11
+ export const JUMBO_HEIGHT = "300px";
12
+ export const SPACER_HEIGHT = "65px";
13
+ export const LINK_TEXT_DECORATION = "underline solid 1px";
14
+ export const SPACING_XS = "1.0rem";
15
+ export const SPACING_NORMAL = "1.5rem";