@thecb/components 9.3.2-beta.1 → 9.3.2
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/dist/index.cjs.js +346 -245
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.esm.js +346 -246
- package/dist/index.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/components/atoms/button-with-action/ButtonWithAction.js +76 -70
- package/src/components/atoms/checkbox/Checkbox.js +10 -2
- package/src/components/atoms/checkbox/Checkbox.stories.js +1 -0
- package/src/components/atoms/country-dropdown/CountryDropdown.js +2 -0
- package/src/components/atoms/dropdown/Dropdown.js +11 -8
- package/src/components/atoms/form-layouts/FormInput.js +3 -0
- package/src/components/atoms/form-select/FormSelect.js +3 -1
- package/src/components/atoms/icons/AccountNumberImage.js +2 -0
- package/src/components/atoms/icons/BankIcon.js +2 -0
- package/src/components/atoms/icons/CheckmarkIcon.js +2 -0
- package/src/components/atoms/icons/GenericCard.js +2 -0
- package/src/components/atoms/icons/GenericCardLarge.js +2 -0
- package/src/components/atoms/icons/KebabMenuIcon.d.ts +1 -0
- package/src/components/atoms/icons/KebabMenuIcon.js +38 -0
- package/src/components/atoms/icons/RoutingNumberImage.js +2 -0
- package/src/components/atoms/icons/TrashIcon.js +42 -40
- package/src/components/atoms/icons/icons.stories.js +3 -1
- package/src/components/atoms/icons/index.d.ts +1 -0
- package/src/components/atoms/icons/index.js +3 -1
- package/src/components/atoms/layouts/Motion.js +7 -10
- package/src/components/atoms/state-province-dropdown/StateProvinceDropdown.js +3 -1
- package/src/components/atoms/toggle-switch/ToggleSwitch.js +2 -1
- package/src/components/molecules/address-form/AddressForm.js +5 -0
- package/src/components/molecules/collapsible-section/CollapsibleSection.js +2 -1
- package/src/components/molecules/email-form/EmailForm.js +3 -1
- package/src/components/molecules/modal/Modal.js +2 -1
- package/src/components/molecules/obligation/modules/AutopayModalModule.js +3 -13
- package/src/components/molecules/payment-form-ach/PaymentFormACH.js +6 -0
- package/src/components/molecules/payment-form-card/PaymentFormCard.js +5 -0
- package/src/components/molecules/phone-form/PhoneForm.js +3 -1
- package/src/components/molecules/popover/Popover.js +2 -1
- package/src/components/molecules/popup-menu/PopupMenu.js +152 -0
- package/src/components/molecules/popup-menu/PopupMenu.stories.js +40 -0
- package/src/components/molecules/popup-menu/PopupMenu.styled.js +20 -0
- package/src/components/molecules/popup-menu/PopupMenu.theme.js +11 -0
- package/src/components/molecules/popup-menu/index.d.ts +25 -0
- package/src/components/molecules/popup-menu/index.js +3 -0
- package/src/components/molecules/popup-menu/popup-menu-item/PopupMenuItem.js +79 -0
- package/src/components/molecules/popup-menu/popup-menu-item/PopupMenuItem.styled.js +27 -0
- package/src/components/molecules/popup-menu/popup-menu-item/PopupMenuItem.theme.js +23 -0
- package/src/components/molecules/radio-section/RadioSection.js +62 -13
- package/src/components/molecules/radio-section/RadioSection.stories.js +4 -2
- package/src/components/molecules/radio-section/radio-button/RadioButton.js +4 -2
- package/src/components/molecules/terms-and-conditions/TermsAndConditions.stories.js +3 -1
- package/src/components/molecules/terms-and-conditions/TermsAndConditionsControlV2.js +4 -0
- package/src/constants/keyboard.js +7 -0
- package/src/util/general.js +10 -0
- /package/src/components/atoms/icons/{ExternalLinkIcon.js → ExternalLinkicon.js} +0 -0
|
@@ -1,13 +1,21 @@
|
|
|
1
|
-
import React, { Fragment, useState } from "react";
|
|
1
|
+
import React, { createRef, Fragment, useRef, useState } from "react";
|
|
2
2
|
import styled from "styled-components";
|
|
3
3
|
import { themeComponent } from "../../../util/themeUtils";
|
|
4
4
|
import { fallbackValues } from "./RadioSection.theme";
|
|
5
5
|
import { AnimatePresence } from "framer-motion";
|
|
6
6
|
import RadioButton from "./radio-button/RadioButton";
|
|
7
7
|
import { Box, Cluster, Stack, Motion } from "../../atoms/layouts";
|
|
8
|
-
import { createIdFromString, noop } from "../../../util/general";
|
|
8
|
+
import { createIdFromString, noop, wrapIndex } from "../../../util/general";
|
|
9
9
|
import Text from "../../atoms/text";
|
|
10
10
|
import { CHARADE_GREY } from "../../../constants/colors";
|
|
11
|
+
import {
|
|
12
|
+
ARROW_DOWN,
|
|
13
|
+
ARROW_LEFT,
|
|
14
|
+
ARROW_RIGHT,
|
|
15
|
+
ARROW_UP,
|
|
16
|
+
ENTER,
|
|
17
|
+
SPACEBAR
|
|
18
|
+
} from "../../../constants/keyboard";
|
|
11
19
|
/*
|
|
12
20
|
Takes an array of section objects, each object should look like:
|
|
13
21
|
{
|
|
@@ -47,12 +55,42 @@ const RadioSection = ({
|
|
|
47
55
|
initiallyOpen = true,
|
|
48
56
|
openHeight = "auto",
|
|
49
57
|
containerStyles = "",
|
|
50
|
-
ariaDescribedBy
|
|
58
|
+
ariaDescribedBy,
|
|
59
|
+
isSectionRequired = false,
|
|
60
|
+
...rest
|
|
51
61
|
}) => {
|
|
52
|
-
const
|
|
53
|
-
|
|
62
|
+
const [focused, setFocused] = useState(null);
|
|
63
|
+
const sectionRefs = useRef(
|
|
64
|
+
[...Array(sections.length)].map(() => createRef())
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const handleKeyDown = (id, e, i) => {
|
|
68
|
+
if (e.currentTarget !== e.target) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Allow Enter and Space to select a section
|
|
73
|
+
if (e.keyCode === ENTER || e.keyCode === SPACEBAR) {
|
|
74
|
+
e.preventDefault();
|
|
54
75
|
toggleOpenSection(id);
|
|
55
76
|
}
|
|
77
|
+
|
|
78
|
+
// Allow Up and Down arrow navigation between sections
|
|
79
|
+
if (
|
|
80
|
+
e.keyCode == ARROW_UP ||
|
|
81
|
+
e.keyCode == ARROW_DOWN ||
|
|
82
|
+
e.keyCode == ARROW_LEFT ||
|
|
83
|
+
e.keyCode == ARROW_RIGHT
|
|
84
|
+
) {
|
|
85
|
+
e.preventDefault();
|
|
86
|
+
const indexIncrement =
|
|
87
|
+
e.keyCode == ARROW_RIGHT || e.keyCode == ARROW_DOWN ? 1 : -1;
|
|
88
|
+
const nextIndex = wrapIndex(i + indexIncrement, sections.length);
|
|
89
|
+
|
|
90
|
+
sectionRefs?.current[nextIndex]?.current?.focus();
|
|
91
|
+
setFocused(sections[nextIndex]?.id);
|
|
92
|
+
toggleOpenSection(sections[nextIndex]?.id);
|
|
93
|
+
}
|
|
56
94
|
};
|
|
57
95
|
|
|
58
96
|
const wrapper = {
|
|
@@ -96,8 +134,6 @@ const RadioSection = ({
|
|
|
96
134
|
transition: opacity 0.3s ease;
|
|
97
135
|
`;
|
|
98
136
|
|
|
99
|
-
const [focused, setFocused] = useState(null);
|
|
100
|
-
|
|
101
137
|
return (
|
|
102
138
|
<Box
|
|
103
139
|
padding="1px"
|
|
@@ -105,17 +141,25 @@ const RadioSection = ({
|
|
|
105
141
|
borderRadius="4px"
|
|
106
142
|
extraStyles={containerStyles}
|
|
107
143
|
>
|
|
108
|
-
<Stack
|
|
144
|
+
<Stack
|
|
145
|
+
childGap="0"
|
|
146
|
+
role="radiogroup"
|
|
147
|
+
aria-required={isSectionRequired}
|
|
148
|
+
{...rest}
|
|
149
|
+
>
|
|
109
150
|
{sections
|
|
110
151
|
.filter(section => !section.hidden)
|
|
111
|
-
.map(section => (
|
|
152
|
+
.map((section, i) => (
|
|
112
153
|
<Motion
|
|
113
154
|
tabIndex={
|
|
114
155
|
section.hideRadioButton || section.disabled ? "-1" : "0"
|
|
115
156
|
}
|
|
116
|
-
|
|
117
|
-
onFocus={() => !section.disabled && setFocused(section.id)}
|
|
157
|
+
ref={sectionRefs.current[i]}
|
|
118
158
|
onBlur={() => !section.disabled && setFocused(null)}
|
|
159
|
+
onFocus={() => !section.disabled && setFocused(section.id)}
|
|
160
|
+
onKeyDown={e =>
|
|
161
|
+
!section.disabled && handleKeyDown(section.id, e, i)
|
|
162
|
+
}
|
|
119
163
|
hoverStyles={themeValues.focusStyles}
|
|
120
164
|
animate={openSection === section.id ? "open" : "closed"}
|
|
121
165
|
initial={initiallyOpen ? "open" : "closed"}
|
|
@@ -124,6 +168,9 @@ const RadioSection = ({
|
|
|
124
168
|
role="radio"
|
|
125
169
|
aria-checked={openSection === section.id}
|
|
126
170
|
aria-disabled={section.disabled}
|
|
171
|
+
aria-required={section.required}
|
|
172
|
+
aria-labelledby={section.id}
|
|
173
|
+
aria-describedby={`right-icons-${idString(section)}`}
|
|
127
174
|
>
|
|
128
175
|
<Stack childGap="0">
|
|
129
176
|
<Box
|
|
@@ -177,6 +224,7 @@ const RadioSection = ({
|
|
|
177
224
|
: () => toggleOpenSection(section.id)
|
|
178
225
|
}
|
|
179
226
|
tabIndex="-1"
|
|
227
|
+
isRequired={section.required}
|
|
180
228
|
/>
|
|
181
229
|
</Box>
|
|
182
230
|
)}
|
|
@@ -195,9 +243,10 @@ const RadioSection = ({
|
|
|
195
243
|
</Cluster>
|
|
196
244
|
{section.rightIcons && (
|
|
197
245
|
<Cluster
|
|
246
|
+
id={`right-icons-${idString(section)}`}
|
|
198
247
|
childGap="0.5rem"
|
|
199
|
-
aria-label={section
|
|
200
|
-
role={section
|
|
248
|
+
aria-label={section.rightIconsLabel || null}
|
|
249
|
+
role={section.rightIconsRole || null}
|
|
201
250
|
>
|
|
202
251
|
{section.rightIcons.map(icon => (
|
|
203
252
|
<RightIcon
|
|
@@ -49,9 +49,10 @@ const sections = [
|
|
|
49
49
|
title: "New Card",
|
|
50
50
|
content: <p>The form to add a credit card would go here.</p>,
|
|
51
51
|
rightIconsLabel: cardIconsLabel,
|
|
52
|
-
rightIcons: cardIcons
|
|
52
|
+
rightIcons: cardIcons,
|
|
53
|
+
required: true
|
|
53
54
|
},
|
|
54
|
-
{ id: "bar", title: "Bar", content: <div>Content 1</div
|
|
55
|
+
{ id: "bar", title: "Bar", content: <div>Content 1</div>, required: true },
|
|
55
56
|
{ id: "baz", title: "Baz", content: <div>Content 2</div> }
|
|
56
57
|
];
|
|
57
58
|
|
|
@@ -65,6 +66,7 @@ export const radioSection = () => {
|
|
|
65
66
|
openSection={openSection}
|
|
66
67
|
staggeredAnimation={boolean("staggeredAnimation", false, "props")}
|
|
67
68
|
sections={sections}
|
|
69
|
+
isSectionRequired={true}
|
|
68
70
|
/>
|
|
69
71
|
);
|
|
70
72
|
};
|
|
@@ -16,10 +16,11 @@ const RadioButton = ({
|
|
|
16
16
|
toggleRadio,
|
|
17
17
|
name,
|
|
18
18
|
disabled = false,
|
|
19
|
-
ariaDescribedBy
|
|
19
|
+
ariaDescribedBy,
|
|
20
20
|
themeValues,
|
|
21
21
|
ariaLabelledBy = "",
|
|
22
|
-
ariaLabel = null
|
|
22
|
+
ariaLabel = null,
|
|
23
|
+
isRequired = false
|
|
23
24
|
}) => {
|
|
24
25
|
const buttonBorder = {
|
|
25
26
|
onFocused: {
|
|
@@ -93,6 +94,7 @@ const RadioButton = ({
|
|
|
93
94
|
onClick={toggleRadio}
|
|
94
95
|
aria-describedby={ariaDescribedBy}
|
|
95
96
|
tabIndex="-1"
|
|
97
|
+
required={isRequired}
|
|
96
98
|
{...extraProps}
|
|
97
99
|
/>
|
|
98
100
|
<Motion
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { text, boolean } from "@storybook/addon-knobs";
|
|
2
|
+
import { text, boolean, select } from "@storybook/addon-knobs";
|
|
3
3
|
|
|
4
4
|
import TermsAndConditions from "./TermsAndConditions";
|
|
5
5
|
import page from "../../../../.storybook/page";
|
|
@@ -9,11 +9,13 @@ const groupId = "props";
|
|
|
9
9
|
|
|
10
10
|
export const termsAndConditions = () => (
|
|
11
11
|
<TermsAndConditions
|
|
12
|
+
version={select("version", ["v1", "v2"], "v1", groupId)}
|
|
12
13
|
onCheck={noop}
|
|
13
14
|
isChecked={boolean("isChecked", false, groupId)}
|
|
14
15
|
html={text("html", "terms and conditions summary", groupId)}
|
|
15
16
|
terms={text("terms", "terms and conditions modal text", groupId)}
|
|
16
17
|
error={boolean("error", false, groupId)}
|
|
18
|
+
description={text("description", "I definitely agree to the", groupId)}
|
|
17
19
|
/>
|
|
18
20
|
);
|
|
19
21
|
|
|
@@ -11,6 +11,8 @@ import {
|
|
|
11
11
|
import { generateShadows } from "../../../util/generateShadows";
|
|
12
12
|
import { useScrollTo } from "../../../hooks";
|
|
13
13
|
|
|
14
|
+
const TermsAndConditionsTitleDivId = "terms-and-conditions-title";
|
|
15
|
+
|
|
14
16
|
const TermsAndConditionsControlV2 = ({
|
|
15
17
|
showCheckbox = true,
|
|
16
18
|
onCheck,
|
|
@@ -57,6 +59,7 @@ const TermsAndConditionsControlV2 = ({
|
|
|
57
59
|
onChange={onCheck}
|
|
58
60
|
checkboxMargin={checkboxMargin}
|
|
59
61
|
extraStyles={`align-self: flex-start;`}
|
|
62
|
+
labelledById={TermsAndConditionsTitleDivId}
|
|
60
63
|
/>
|
|
61
64
|
)}
|
|
62
65
|
<Stack childGap="0.25rem" fullHeight>
|
|
@@ -65,6 +68,7 @@ const TermsAndConditionsControlV2 = ({
|
|
|
65
68
|
align="center"
|
|
66
69
|
nowrap
|
|
67
70
|
extraStyles={`padding-right: 2px; > div > * { margin: 4px 2px; };`}
|
|
71
|
+
id={TermsAndConditionsTitleDivId}
|
|
68
72
|
>
|
|
69
73
|
{description && <Text color={CHARADE_GREY}>{description}</Text>}
|
|
70
74
|
{terms && (
|
package/src/util/general.js
CHANGED
|
@@ -163,3 +163,13 @@ export const titleCaseString = string => {
|
|
|
163
163
|
export const kebabCaseString = string => {
|
|
164
164
|
return string?.replaceAll(" ", "-").toLowerCase();
|
|
165
165
|
};
|
|
166
|
+
|
|
167
|
+
export const wrapIndex = (index, length) => {
|
|
168
|
+
if (index <= -1) {
|
|
169
|
+
return length - 1;
|
|
170
|
+
} else if (index >= length) {
|
|
171
|
+
return 0;
|
|
172
|
+
} else {
|
|
173
|
+
return index;
|
|
174
|
+
}
|
|
175
|
+
};
|
|
File without changes
|