@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.
Files changed (54) hide show
  1. package/dist/index.cjs.js +346 -245
  2. package/dist/index.cjs.js.map +1 -1
  3. package/dist/index.d.ts +3 -1
  4. package/dist/index.esm.js +346 -246
  5. package/dist/index.esm.js.map +1 -1
  6. package/package.json +1 -1
  7. package/src/components/atoms/button-with-action/ButtonWithAction.js +76 -70
  8. package/src/components/atoms/checkbox/Checkbox.js +10 -2
  9. package/src/components/atoms/checkbox/Checkbox.stories.js +1 -0
  10. package/src/components/atoms/country-dropdown/CountryDropdown.js +2 -0
  11. package/src/components/atoms/dropdown/Dropdown.js +11 -8
  12. package/src/components/atoms/form-layouts/FormInput.js +3 -0
  13. package/src/components/atoms/form-select/FormSelect.js +3 -1
  14. package/src/components/atoms/icons/AccountNumberImage.js +2 -0
  15. package/src/components/atoms/icons/BankIcon.js +2 -0
  16. package/src/components/atoms/icons/CheckmarkIcon.js +2 -0
  17. package/src/components/atoms/icons/GenericCard.js +2 -0
  18. package/src/components/atoms/icons/GenericCardLarge.js +2 -0
  19. package/src/components/atoms/icons/KebabMenuIcon.d.ts +1 -0
  20. package/src/components/atoms/icons/KebabMenuIcon.js +38 -0
  21. package/src/components/atoms/icons/RoutingNumberImage.js +2 -0
  22. package/src/components/atoms/icons/TrashIcon.js +42 -40
  23. package/src/components/atoms/icons/icons.stories.js +3 -1
  24. package/src/components/atoms/icons/index.d.ts +1 -0
  25. package/src/components/atoms/icons/index.js +3 -1
  26. package/src/components/atoms/layouts/Motion.js +7 -10
  27. package/src/components/atoms/state-province-dropdown/StateProvinceDropdown.js +3 -1
  28. package/src/components/atoms/toggle-switch/ToggleSwitch.js +2 -1
  29. package/src/components/molecules/address-form/AddressForm.js +5 -0
  30. package/src/components/molecules/collapsible-section/CollapsibleSection.js +2 -1
  31. package/src/components/molecules/email-form/EmailForm.js +3 -1
  32. package/src/components/molecules/modal/Modal.js +2 -1
  33. package/src/components/molecules/obligation/modules/AutopayModalModule.js +3 -13
  34. package/src/components/molecules/payment-form-ach/PaymentFormACH.js +6 -0
  35. package/src/components/molecules/payment-form-card/PaymentFormCard.js +5 -0
  36. package/src/components/molecules/phone-form/PhoneForm.js +3 -1
  37. package/src/components/molecules/popover/Popover.js +2 -1
  38. package/src/components/molecules/popup-menu/PopupMenu.js +152 -0
  39. package/src/components/molecules/popup-menu/PopupMenu.stories.js +40 -0
  40. package/src/components/molecules/popup-menu/PopupMenu.styled.js +20 -0
  41. package/src/components/molecules/popup-menu/PopupMenu.theme.js +11 -0
  42. package/src/components/molecules/popup-menu/index.d.ts +25 -0
  43. package/src/components/molecules/popup-menu/index.js +3 -0
  44. package/src/components/molecules/popup-menu/popup-menu-item/PopupMenuItem.js +79 -0
  45. package/src/components/molecules/popup-menu/popup-menu-item/PopupMenuItem.styled.js +27 -0
  46. package/src/components/molecules/popup-menu/popup-menu-item/PopupMenuItem.theme.js +23 -0
  47. package/src/components/molecules/radio-section/RadioSection.js +62 -13
  48. package/src/components/molecules/radio-section/RadioSection.stories.js +4 -2
  49. package/src/components/molecules/radio-section/radio-button/RadioButton.js +4 -2
  50. package/src/components/molecules/terms-and-conditions/TermsAndConditions.stories.js +3 -1
  51. package/src/components/molecules/terms-and-conditions/TermsAndConditionsControlV2.js +4 -0
  52. package/src/constants/keyboard.js +7 -0
  53. package/src/util/general.js +10 -0
  54. /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 handleKeyDown = (id, e) => {
53
- if (e?.keyCode === 13 || e?.keyCode === 32) {
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 childGap="0" role="radiogroup">
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
- onKeyDown={e => !section.disabled && handleKeyDown(section.id, e)}
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?.rightIconsLabel || null}
200
- role={section?.rightIconsRole || null}
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 && (
@@ -0,0 +1,7 @@
1
+ export const ARROW_LEFT = 37;
2
+ export const ARROW_UP = 38;
3
+ export const ARROW_RIGHT = 39;
4
+ export const ARROW_DOWN = 40;
5
+ export const ENTER = 13;
6
+ export const ESCAPE = 27;
7
+ export const SPACEBAR = 32;
@@ -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
+ };