@thecb/components 10.4.9-beta.0 → 10.5.0-beta.1

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,180 @@
1
+ import React, { useContext, useRef } from "react";
2
+ import AriaModal from "react-aria-modal";
3
+ import { ThemeContext } from "styled-components";
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 { noop } from "../../../util/general";
13
+ import Paragraph from "../../atoms/paragraph";
14
+ import Title from "../../atoms/title";
15
+ import { Box, Cluster } from "../../atoms/layouts";
16
+ import withWindowSize from "../../withWindowSize";
17
+ import {
18
+ ButtonLayoutWrapper,
19
+ CancelButton,
20
+ CloseButton,
21
+ ContinueButton
22
+ } from "./__private__";
23
+
24
+ /*
25
+ Default Modal molecule
26
+ Uses react-aria-modal behind the scenes for a11y purposes
27
+ Styling accomplished with our atoms / layout primitives
28
+
29
+ Cancel button will (for now) always use hideModal as its action
30
+ Continue button takes an action, if you want to navigate to
31
+ a different route (as with a link) connect() and use "push" from @thecb/connected-react-router
32
+ */
33
+
34
+ const getApplicationNode = () => document.getElementById("root");
35
+
36
+ const Modal = ({
37
+ blurUnderlay = true,
38
+ buttonExtraStyles = "",
39
+ cancelAction = noop,
40
+ cancelButtonText = "Cancel",
41
+ children = [],
42
+ closeButtonText = "Close",
43
+ continueAction = noop,
44
+ continueButtonText = "Continue",
45
+ continueURL = "",
46
+ customWidth = "",
47
+ dataQa = null,
48
+ defaultWrapper = true,
49
+ hideModal = noop,
50
+ initialFocusSelector = "",
51
+ isContinueActionDisabled = false,
52
+ isLoading = false,
53
+ maxHeight = "",
54
+ modalBodyBg = ATHENS_GREY,
55
+ modalBodyText = "",
56
+ modalHeaderBg = WHITE,
57
+ modalHeaderText = "",
58
+ modalOpen = false,
59
+ noButtons = false, // for instances where modal is closed automatically
60
+ onExit = hideModal,
61
+ onlyCloseButton = false,
62
+ onlyContinueButton = false,
63
+ underlayClickExits = true,
64
+ useDangerButton = false
65
+ }) => {
66
+ const { isMobile } = useContext(ThemeContext);
67
+ const modalContainerRef = useRef(null);
68
+
69
+ const hasCloseButton = onlyCloseButton && !noButtons;
70
+ const hasCancelButton = !onlyContinueButton && !onlyCloseButton && !noButtons;
71
+ const hasContinueButton =
72
+ (onlyContinueButton && !noButtons) || (!onlyCloseButton && !noButtons);
73
+
74
+ return (
75
+ <div ref={modalContainerRef} data-qa={dataQa}>
76
+ {modalOpen && (
77
+ <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
+ focusTrapOptions={{
80
+ fallbackFocus: modalContainerRef?.current
81
+ }}
82
+ onExit={onExit}
83
+ getApplicationNode={getApplicationNode}
84
+ titleText={modalHeaderText}
85
+ underlayStyle={{
86
+ display: "flex",
87
+ flexDirection: "column",
88
+ justifyContent: "center",
89
+ alignItems: "center",
90
+ background: "rgba(41, 42, 51, 0.45)",
91
+ backdropFilter: blurUnderlay ? "blur(4px)" : "none",
92
+ WebkitBackdropFilter: blurUnderlay ? "blur(4px)" : "none"
93
+ }}
94
+ dialogStyle={{
95
+ borderRadius: CORNER_STANDARD,
96
+ width: isMobile ? "" : customWidth || "615px",
97
+ overflow: "auto"
98
+ }}
99
+ underlayClickExits={underlayClickExits}
100
+ aria-modal={true}
101
+ initialFocus={initialFocusSelector}
102
+ focusDialog={!initialFocusSelector} // Focus the dialogue box itself if no selector for initial focus was provided
103
+ >
104
+ <Box padding="0" boxShadow="inset 0px -2px 0px 0px rgb(0, 80, 149)">
105
+ <Box
106
+ background={modalHeaderBg}
107
+ borderColor={SILVER_GREY}
108
+ borderWidthOverride={`0 0 ${BORDER_THIN} 0`}
109
+ padding={`${SPACING_XS} ${SPACING_NORMAL}`}
110
+ >
111
+ <Cluster justify="flex-start" align="center">
112
+ <Title as="h2" weight={FONT_WEIGHT_SEMIBOLD} fontSize="1.25rem">
113
+ {modalHeaderText}
114
+ </Title>
115
+ </Cluster>
116
+ </Box>
117
+ <Box background={modalBodyBg} padding="0">
118
+ <Box
119
+ padding={SPACING_NORMAL}
120
+ borderWidthOverride={!noButtons && `0 0 ${BORDER_THIN} 0`}
121
+ borderColor={!noButtons && SILVER_GREY}
122
+ extraStyles={
123
+ maxHeight ? `max-height: ${maxHeight}; overflow: auto;` : ``
124
+ }
125
+ >
126
+ {defaultWrapper ? (
127
+ <Paragraph variant="p">{modalBodyText}</Paragraph>
128
+ ) : (
129
+ <Box padding={maxHeight ? `0 0 ${SPACING_XS} 0` : "0"}>
130
+ {modalBodyText}
131
+ </Box>
132
+ )}
133
+ </Box>
134
+ {noButtons ? (
135
+ <></>
136
+ ) : (
137
+ <ButtonLayoutWrapper isMobile={isMobile}>
138
+ {[
139
+ hasCancelButton && (
140
+ <CancelButton
141
+ buttonExtraStyles={buttonExtraStyles}
142
+ cancelAction={cancelAction}
143
+ cancelButtonText={cancelButtonText}
144
+ hideModal={hideModal}
145
+ isMobile={isMobile}
146
+ />
147
+ ),
148
+ hasContinueButton && (
149
+ <ContinueButton
150
+ buttonExtraStyles={buttonExtraStyles}
151
+ continueAction={continueAction}
152
+ continueButtonText={continueButtonText}
153
+ continueURL={continueURL}
154
+ isContinueActionDisabled={isContinueActionDisabled}
155
+ isLoading={isLoading}
156
+ isMobile={isMobile}
157
+ useDangerButton={useDangerButton}
158
+ />
159
+ ),
160
+ hasCloseButton && (
161
+ <CloseButton
162
+ buttonExtraStyles={buttonExtraStyles}
163
+ closeButtonText={closeButtonText}
164
+ hideModal={hideModal}
165
+ isMobile={isMobile}
166
+ />
167
+ )
168
+ ].filter(button => button)}
169
+ </ButtonLayoutWrapper>
170
+ )}
171
+ </Box>
172
+ </Box>
173
+ </AriaModal>
174
+ )}
175
+ {children}
176
+ </div>
177
+ );
178
+ };
179
+
180
+ export default withWindowSize(Modal);
@@ -0,0 +1,24 @@
1
+ import React from "react";
2
+ import { SPACING_NORMAL } from "../../../../constants/style_constants";
3
+ import { Box, Stack } from "../../../atoms/layouts";
4
+
5
+ export const ButtonLayoutWrapper = ({ children = [], isMobile = false }) => {
6
+ const safeChildren = Array.isArray(children) ? children : [children];
7
+ const flexGrow = isMobile ? "flex-grow: 1;" : "";
8
+
9
+ return (
10
+ <Box padding={SPACING_NORMAL}>
11
+ <Stack childGap="1rem" direction="row" justify="flex-end">
12
+ {safeChildren.map((child, index) => {
13
+ return (
14
+ <Box padding="0" extraStyles={flexGrow} key={index}>
15
+ {child}
16
+ </Box>
17
+ );
18
+ })}
19
+ </Stack>
20
+ </Box>
21
+ );
22
+ };
23
+
24
+ export default ButtonLayoutWrapper;
@@ -0,0 +1,30 @@
1
+ import React from "react";
2
+ import { CORNER_STANDARD } from "../../../../constants/style_constants";
3
+ import { noop } from "../../../../util/general";
4
+ import ButtonWithAction from "../../../atoms/button-with-action/ButtonWithAction";
5
+
6
+ export const CancelButton = ({
7
+ buttonExtraStyles = "",
8
+ cancelAction = noop,
9
+ cancelButtonText = "",
10
+ hideModal = noop,
11
+ isMobile = false
12
+ }) => {
13
+ const fullWidth = isMobile ? "width: 100%;" : "";
14
+
15
+ return (
16
+ <ButtonWithAction
17
+ action={cancelAction ? cancelAction : hideModal}
18
+ borderRadius={CORNER_STANDARD}
19
+ className="modal-cancel-button"
20
+ dataQa={cancelButtonText}
21
+ extraStyles={`${buttonExtraStyles}; margin: 0; ${fullWidth}`}
22
+ name={cancelButtonText}
23
+ role="button"
24
+ text={cancelButtonText}
25
+ variant="secondary"
26
+ />
27
+ );
28
+ };
29
+
30
+ export default CancelButton;
@@ -0,0 +1,29 @@
1
+ import React from "react";
2
+ import { CORNER_STANDARD } from "../../../../constants/style_constants";
3
+ import { noop } from "../../../../util/general";
4
+ import ButtonWithAction from "../../../atoms/button-with-action/ButtonWithAction";
5
+
6
+ export const CloseButton = ({
7
+ buttonExtraStyles = "",
8
+ closeButtonText = "",
9
+ hideModal = noop,
10
+ isMobile = false
11
+ }) => {
12
+ const fullWidth = isMobile ? "width: 100%;" : "";
13
+
14
+ return (
15
+ <ButtonWithAction
16
+ action={hideModal}
17
+ borderRadius={CORNER_STANDARD}
18
+ className="modal-close-button"
19
+ dataQa={closeButtonText}
20
+ extraStyles={`${buttonExtraStyles}; margin: 0; ${fullWidth}`}
21
+ name={closeButtonText}
22
+ role="button"
23
+ text={closeButtonText}
24
+ variant="primary"
25
+ />
26
+ );
27
+ };
28
+
29
+ export default CloseButton;
@@ -0,0 +1,39 @@
1
+ import React from "react";
2
+ import { CORNER_STANDARD } from "../../../../constants/style_constants";
3
+ import { noop } from "../../../../util/general";
4
+ import ButtonWithAction from "../../../atoms/button-with-action/ButtonWithAction";
5
+ import ButtonWithLink from "../../../atoms/button-with-link/ButtonWithLink";
6
+
7
+ export const ContinueButton = ({
8
+ buttonExtraStyles = "",
9
+ continueAction = noop,
10
+ continueButtonText = "",
11
+ continueURL = "",
12
+ isContinueActionDisabled = false,
13
+ isLoading = false,
14
+ isMobile = false,
15
+ useDangerButton = false
16
+ }) => {
17
+ const ContinueButtonAtom = continueURL ? ButtonWithLink : ButtonWithAction;
18
+ const fullWidth = isMobile ? "width: 100%;" : "";
19
+
20
+ return (
21
+ <ContinueButtonAtom
22
+ action={continueAction}
23
+ borderRadius={CORNER_STANDARD}
24
+ className="modal-continue-button"
25
+ dataQa={continueButtonText}
26
+ disabled={isContinueActionDisabled}
27
+ extraStyles={`${buttonExtraStyles}; margin: 0; ${fullWidth}`}
28
+ isLoading={isLoading}
29
+ linkExtraStyles={`display: inline-block; ${fullWidth}`}
30
+ name={continueButtonText}
31
+ role="button"
32
+ text={continueButtonText}
33
+ url={continueURL}
34
+ variant={useDangerButton ? "danger" : "primary"}
35
+ />
36
+ );
37
+ };
38
+
39
+ export default ContinueButton;
@@ -0,0 +1,46 @@
1
+ import React from "react";
2
+ import Expand from "../../../../util/expand";
3
+
4
+ export interface ButtonLayoutWrapperProps {
5
+ isMobile?: boolean;
6
+ }
7
+
8
+ export declare const ButtonLayoutWrapper: React.FC<Expand<
9
+ ButtonLayoutWrapperProps
10
+ > &
11
+ React.HTMLAttributes<HTMLElement>>;
12
+
13
+ export interface CancelButtonProps {
14
+ buttonExtraStyles?: string;
15
+ cancelAction?: Function;
16
+ cancelButtonText?: string;
17
+ hideModal?: Function;
18
+ isMobile?: boolean;
19
+ }
20
+
21
+ export declare const CancelButton: React.FC<Expand<CancelButtonProps> &
22
+ React.HTMLAttributes<HTMLElement>>;
23
+
24
+ export interface CloseButtonProps {
25
+ buttonExtraStyles?: string;
26
+ closeButtonText?: string;
27
+ hideModal?: Function;
28
+ isMobile?: boolean;
29
+ }
30
+
31
+ export declare const CloseButton: React.FC<Expand<CloseButtonProps> &
32
+ React.HTMLAttributes<HTMLElement>>;
33
+
34
+ export interface ContinueButtonProps {
35
+ buttonExtraStyles?: string;
36
+ continueAction?: Function;
37
+ continueButtonText?: string;
38
+ continueURL?: string;
39
+ isContinueActionDisabled?: boolean;
40
+ isLoading?: boolean;
41
+ isMobile?: boolean;
42
+ useDangerButton?: boolean;
43
+ }
44
+
45
+ export declare const ContinueButton: React.FC<Expand<ContinueButtonProps> &
46
+ React.HTMLAttributes<HTMLElement>>;
@@ -0,0 +1,4 @@
1
+ export { default as ButtonLayoutWrapper } from "./ButtonLayoutWrapper";
2
+ export { default as CancelButton } from "./CancelButton";
3
+ export { default as CloseButton } from "./CloseButton";
4
+ export { default as ContinueButton } from "./ContinueButton";
@@ -29,8 +29,7 @@ const ToastNotification = ({
29
29
  maxWidth = "350px",
30
30
  height = "56px",
31
31
  childGap = "1rem",
32
- backgroundColor,
33
- role = "alert"
32
+ backgroundColor
34
33
  }) => (
35
34
  <Box
36
35
  onClick={closeToastNotification}
@@ -56,21 +55,18 @@ const ToastNotification = ({
56
55
  ${extraStyles};
57
56
  cursor: pointer;
58
57
  `}
59
- role={role}
60
58
  >
61
- <Cluster align="center" childGap={childGap} justify="space-between">
62
- <Cluster align="center" childGap={childGap}>
63
- {variant === VARIANTS.SUCCESS && <SuccessfulIconMedium />}
64
- {variant === VARIANTS.ERROR && <ErroredIcon />}
65
- <Box padding="1rem 0" maxWidth={maxWidth}>
66
- <Paragraph
67
- weight={FONT_WEIGHT_SEMIBOLD}
68
- extraStyles={"word-break: break-word;"}
69
- >
70
- {message}
71
- </Paragraph>
72
- </Box>
73
- </Cluster>
59
+ <Cluster align="center" childGap={childGap}>
60
+ {variant === VARIANTS.SUCCESS && <SuccessfulIconMedium />}
61
+ {variant === VARIANTS.ERROR && <ErroredIcon />}
62
+ <Box padding="1rem 0" maxWidth={maxWidth}>
63
+ <Paragraph
64
+ weight={FONT_WEIGHT_SEMIBOLD}
65
+ extraStyles={"word-break: break-word;"}
66
+ >
67
+ {message}
68
+ </Paragraph>
69
+ </Box>
74
70
  <IconQuitLarge />
75
71
  </Cluster>
76
72
  </Box>
@@ -3,9 +3,6 @@ import ToastNotification from "./ToastNotification";
3
3
  import page from "../../../../.storybook/page";
4
4
  import { useToastNotification } from "../../../hooks";
5
5
  import { ToastVariants } from "../../../types/common";
6
- import { text } from "@storybook/addon-knobs";
7
-
8
- const groupId = "props";
9
6
 
10
7
  export const toastNotificationSuccess = () => {
11
8
  const {
@@ -29,9 +26,6 @@ export const toastNotificationSuccess = () => {
29
26
  message={toastMessage}
30
27
  toastOpen={isToastOpen}
31
28
  closeToastNotification={() => hideToast()}
32
- role={text("role", "alert", groupId)}
33
- minWidth={text("minWidth", null, groupId)}
34
- maxWidth={text("maxWidth", null, groupId)}
35
29
  />
36
30
  );
37
31
  };
@@ -60,43 +54,11 @@ export const toastNotificationError = () => {
60
54
  message={toastMessage}
61
55
  toastOpen={isToastOpen}
62
56
  closeToastNotification={() => hideToast()}
63
- role={text("role", "alert", groupId)}
64
- minWidth={text("minWidth", null, groupId)}
65
- maxWidth={text("maxWidth", null, groupId)}
66
57
  />
67
58
  );
68
59
  };
69
60
  toastNotificationError.storyName = "Error Toast";
70
61
 
71
- export const toastNotificationNoAutoHide = () => {
72
- const {
73
- isToastOpen,
74
- toastMessage,
75
- showToast,
76
- hideToast
77
- } = useToastNotification({ timeout: 0 });
78
-
79
- useEffect(() => {
80
- showToast({
81
- message: "Success!",
82
- variant: ToastVariants.SUCCESS
83
- });
84
- }, []);
85
-
86
- return (
87
- <ToastNotification
88
- message={toastMessage}
89
- toastOpen={isToastOpen}
90
- closeToastNotification={() => hideToast()}
91
- role={text("role", "alert", groupId)}
92
- minWidth={text("minWidth", null, groupId)}
93
- maxWidth={text("maxWidth", null, groupId)}
94
- />
95
- );
96
- };
97
-
98
- toastNotificationNoAutoHide.storyName = "No auto-hide";
99
-
100
62
  const story = page({
101
63
  title: "Components|Molecules/ToastNotification",
102
64
  Component: ToastNotification
@@ -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";
@@ -10,7 +10,7 @@ const useToastNotification = ({ timeout = 5000 } = {}) => {
10
10
  const [toastState, setToastState] = useState(initialToastState);
11
11
 
12
12
  useEffect(() => {
13
- if (toastState.isOpen && timeout > 0) {
13
+ if (toastState.isOpen) {
14
14
  setTimeout(() => {
15
15
  setToastState(initialToastState);
16
16
  }, timeout);
package/src/.DS_Store DELETED
Binary file