@thecb/components 9.3.4-beta.0 → 9.3.4-beta.11

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 (34) hide show
  1. package/dist/index.cjs.js +3075 -2962
  2. package/dist/index.cjs.js.map +1 -1
  3. package/dist/index.esm.js +3075 -2962
  4. package/dist/index.esm.js.map +1 -1
  5. package/dist/src/apps/checkout/pages/payment/sub-pages/payment-amount/PaymentAmount_old.js +49322 -0
  6. package/package.json +1 -1
  7. package/src/.DS_Store +0 -0
  8. package/src/components/.DS_Store +0 -0
  9. package/src/components/atoms/.DS_Store +0 -0
  10. package/src/components/atoms/checkbox/Checkbox.js +2 -1
  11. package/src/components/atoms/dropdown/Dropdown.js +3 -3
  12. package/src/components/atoms/icons/.DS_Store +0 -0
  13. package/src/components/atoms/layouts/Motion.js +7 -10
  14. package/src/components/atoms/toggle-switch/ToggleSwitch.js +2 -1
  15. package/src/components/molecules/account-and-routing-modal/AccountAndRoutingModal.js +3 -0
  16. package/src/components/molecules/collapsible-section/CollapsibleSection.js +2 -1
  17. package/src/components/molecules/modal/Modal.js +16 -1
  18. package/src/components/molecules/obligation/modules/AutopayModalModule.js +3 -13
  19. package/src/components/molecules/payment-form-ach/PaymentFormACH.js +1 -0
  20. package/src/components/molecules/payment-form-card/PaymentFormCard.js +1 -0
  21. package/src/components/molecules/popover/Popover.js +2 -1
  22. package/src/components/molecules/radio-section/InnerRadioSection.js +207 -0
  23. package/src/components/molecules/radio-section/InnerRadioSection.theme.js +15 -0
  24. package/src/components/molecules/radio-section/RadioSection.js +120 -202
  25. package/src/components/molecules/radio-section/RadioSection.stories.js +85 -15
  26. package/src/components/molecules/radio-section/RadioSection.theme.js +2 -2
  27. package/src/components/molecules/radio-section/radio-button/RadioButton.js +1 -1
  28. package/src/components/molecules/terms-and-conditions/TermsAndConditions.stories.js +19 -2
  29. package/src/components/molecules/terms-and-conditions/TermsAndConditionsControlV1.js +3 -1
  30. package/src/components/molecules/terms-and-conditions/TermsAndConditionsControlV2.js +3 -1
  31. package/src/components/molecules/terms-and-conditions-modal/TermsAndConditionsModal.js +5 -1
  32. package/src/components/molecules/terms-and-conditions-modal/TermsAndConditionsModal.stories.js +8 -3
  33. package/src/constants/keyboard.js +7 -0
  34. package/src/util/general.js +10 -0
@@ -1,34 +1,40 @@
1
- import React, { Fragment, useState } from "react";
2
- import styled from "styled-components";
1
+ import React, { createRef, Fragment, useRef, useState } from "react";
3
2
  import { themeComponent } from "../../../util/themeUtils";
4
3
  import { fallbackValues } from "./RadioSection.theme";
5
- import { AnimatePresence } from "framer-motion";
6
- import RadioButton from "./radio-button/RadioButton";
7
- import { Box, Cluster, Stack, Motion } from "../../atoms/layouts";
8
- import { createIdFromString, noop } from "../../../util/general";
9
- import Text from "../../atoms/text";
10
- import { CHARADE_GREY } from "../../../constants/colors";
11
- /*
12
- Takes an array of section objects, each object should look like:
13
- {
14
- title: <React Component(s)>,
15
- id: <String> "identifier of section",
16
- disabled: boolean, (displays section and grayed out radio but disables interaction)
17
- hideRadioButton: boolean, (keeps section displayed but hides radio and disables open/close function),
18
- hidden: boolean, (hides section entirely)
19
- dataQa: string,
20
- content: <React Component(s)> e.g.: <Box><Stack>cool content stuff</Stack></Box> (any collection of components will work),
21
- rightTitleContent: <React Component(s)> (rendered on the very right of the title section, use to supplement "rightIcons" with text, as in expired CC status, or render other custom content)
22
- }
23
-
24
- Also takes an "openSection" which should equal the id of the section that should be open
25
- And a toggleOpenSection. RadioSection will call this function with the id of the section
26
- that it is in. It is up to the user to store the open section value in state up from the component
27
- using a useState() hook, or reducer.
28
-
29
- The section itself comes with some motion to open/close. To add more motion to the content,
30
- wrap your content with a Motion layout primitive and provide appropriate props.
31
-
4
+ import SolidDivider from "../../atoms/solid-divider/SolidDivider";
5
+ import { Box, Stack } from "../../atoms/layouts";
6
+ import { createIdFromString, wrapIndex } from "../../../util/general";
7
+ import {
8
+ ARROW_DOWN,
9
+ ARROW_LEFT,
10
+ ARROW_RIGHT,
11
+ ARROW_UP,
12
+ ENTER,
13
+ SPACEBAR
14
+ } from "../../../constants/keyboard";
15
+ import InnerRadioSection from "./InnerRadioSection";
16
+ /**
17
+ - The RadioSection component takes either a flat array (via the 'sections'
18
+ prop) of section objects or a multidimensional array (via the 'groupedSections' prop) of section objects. Note that if using a multidimensional array, the nesting cannot exceed 2 levels deep.
19
+ - Each 'section' object should look like:
20
+ {
21
+ title: <React Component(s)>,
22
+ id: <String> "identifier of section",
23
+ disabled: boolean, (displays section and grayed out radio but disables interaction)
24
+ hideRadioButton: boolean, (keeps section displayed but hides radio and disables open/close function),
25
+ hidden: boolean, (hides section entirely)
26
+ dataQa: string,
27
+ content: <React Component(s)> e.g.: <Box><Stack>cool content stuff</Stack></Box> (any collection of components will work),
28
+ rightTitleContent: <React Component(s)> (rendered on the very right of the title section, use to supplement "rightIcons" with text, as in expired CC status, or render other custom content)
29
+ }
30
+ - It also takes an "openSection" which should equal the id of the section that
31
+ should be open, along with "toggleOpenSection"
32
+ - RadioSection will call "toggleOpenSection" with the id of the section
33
+ that it is in.
34
+ - It is up to the engineer to store the open section value in state up from the
35
+ component using a useState() hook or a reducer.
36
+ - The section itself comes with some motion to open/close. To add more motion
37
+ to the content, wrap your content with a Motion layout primitive and provide appropriate props.
32
38
  */
33
39
 
34
40
  const idString = section =>
@@ -48,56 +54,44 @@ const RadioSection = ({
48
54
  openHeight = "auto",
49
55
  containerStyles = "",
50
56
  ariaDescribedBy,
51
- isSectionRequired = false
57
+ isSectionRequired = false,
58
+ groupedSections,
59
+ ...rest
52
60
  }) => {
53
- const handleKeyDown = (id, e) => {
54
- if (e?.keyCode === 13 || e?.keyCode === 32) {
55
- toggleOpenSection(id);
56
- }
57
- };
61
+ const [focused, setFocused] = useState(null);
58
62
 
59
- const wrapper = {
60
- open: {
61
- height: openHeight,
62
- opacity: 1,
63
- transition: {
64
- duration: 0.3,
65
- ease: [0.04, 0.62, 0.23, 0.98],
66
- staggerChildren: staggeredAnimation ? 0.15 : 0
67
- }
68
- },
69
- closed: {
70
- height: 0,
71
- opacity: 0,
72
- transition: {
73
- duration: 0.3,
74
- ease: [0.04, 0.62, 0.23, 0.98],
75
- staggerChildren: staggeredAnimation ? 0.15 : 0,
76
- staggerDirection: -1
77
- }
78
- }
79
- };
63
+ const sectionRefs = useRef(
64
+ [...Array(sections.length)].map(() => createRef())
65
+ );
80
66
 
81
- const borderStyles = `
82
- border-width: 0 0 1px 0;
83
- border-color: ${themeValues.borderColor};
84
- border-style: solid;
85
- border-radius: 0px;
86
- transform-origin: 100% 0;
67
+ const handleKeyDown = (id, e, i) => {
68
+ if (e.currentTarget !== e.target) {
69
+ return;
70
+ }
87
71
 
88
- &:last-child {
89
- border-width: 0;
72
+ // Allow Enter and Space to select a section
73
+ if (e.keyCode === ENTER || e.keyCode === SPACEBAR) {
74
+ e.preventDefault();
75
+ toggleOpenSection(id);
90
76
  }
91
- `;
92
77
 
93
- const RightIcon = styled.img`
94
- height: ${({ isMobile }) => (isMobile ? "14px" : "18px")};
95
- width: ${({ isMobile }) => (isMobile ? "22px" : "28px")};
96
- ${({ fade }) => fade && "opacity: 0.4;"}
97
- transition: opacity 0.3s ease;
98
- `;
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);
99
89
 
100
- const [focused, setFocused] = useState(null);
90
+ sectionRefs?.current[nextIndex]?.current?.focus();
91
+ setFocused(sections[nextIndex]?.id);
92
+ toggleOpenSection(sections[nextIndex]?.id);
93
+ }
94
+ };
101
95
 
102
96
  return (
103
97
  <Box
@@ -108,141 +102,65 @@ const RadioSection = ({
108
102
  >
109
103
  <Stack
110
104
  childGap="0"
111
- aria-role="radiogroup"
105
+ role="radiogroup"
112
106
  aria-required={isSectionRequired}
107
+ {...rest}
113
108
  >
114
- {sections
115
- .filter(section => !section.hidden)
116
- .map(section => (
117
- <Motion
118
- tabIndex={
119
- section.hideRadioButton || section.disabled ? "-1" : "0"
120
- }
121
- onKeyDown={e => !section.disabled && handleKeyDown(section.id, e)}
122
- onFocus={() => !section.disabled && setFocused(section.id)}
123
- onBlur={() => !section.disabled && setFocused(null)}
124
- hoverStyles={themeValues.focusStyles}
125
- animate={openSection === section.id ? "open" : "closed"}
126
- initial={initiallyOpen ? "open" : "closed"}
127
- key={`item-${section.id}`}
128
- extraStyles={borderStyles}
129
- role="radio"
130
- aria-checked={openSection === section.id}
131
- aria-disabled={section.disabled}
132
- aria-required={section?.required}
133
- >
134
- <Stack childGap="0">
135
- <Box
136
- padding={
137
- section.hideRadioButton ? "1.5rem" : "1.25rem 1.5rem"
109
+ {!!sections &&
110
+ sections
111
+ .filter(section => !section.hidden)
112
+ .map((section, i) => (
113
+ <Fragment key={`radio-section-${sections.indexOf(section)}`}>
114
+ <InnerRadioSection
115
+ sectionIndex={i}
116
+ section={section}
117
+ sectionRefs={sectionRefs}
118
+ focused={focused}
119
+ setFocused={setFocused}
120
+ openHeight={openHeight}
121
+ openSection={openSection}
122
+ toggleOpenSection={toggleOpenSection}
123
+ onKeyDown={e =>
124
+ !section.disabled && handleKeyDown(section.id, e, i)
138
125
  }
139
- background={
140
- section.disabled
141
- ? themeValues.headingDisabledColor
142
- : themeValues.headingBackgroundColor
143
- }
144
- onClick={
145
- (isMobile && supportsTouch) || section.disabled
146
- ? noop
147
- : () => toggleOpenSection(section.id)
148
- }
149
- onTouchEnd={
150
- isMobile && supportsTouch && !section.disabled
151
- ? () => toggleOpenSection(section.id)
152
- : noop
153
- }
154
- key={`header-${section.id}`}
155
- borderSize="0px"
156
- borderColor={themeValues.borderColor}
157
- borderWidthOverride={
158
- openSection === section.id && !!section.content
159
- ? `0px 0px 1px 0px`
160
- : ``
161
- }
162
- extraStyles={!section.disabled ? "cursor: pointer;" : ""}
163
- dataQa={section.dataQa ? section.dataQa : section.id}
126
+ ariaLabelledBy={section.id}
127
+ ariaDescribedBy={`right-icons-${idString(section)}`}
128
+ />
129
+ </Fragment>
130
+ ))}
131
+ {!!groupedSections &&
132
+ groupedSections.map(sectionGroup =>
133
+ sectionGroup
134
+ .filter(unfilteredSection => !unfilteredSection.hidden)
135
+ .map((section, i) => (
136
+ <Fragment
137
+ key={`radio-section-${groupedSections.indexOf(
138
+ sectionGroup
139
+ )}-${sectionGroup.indexOf(section)}}`}
164
140
  >
165
- <Cluster
166
- justify="space-between"
167
- align="center"
168
- childGap="1px"
169
- nowrap
170
- >
171
- <Cluster justify="flex-start" align="center" nowrap>
172
- {!section.hideRadioButton && (
173
- <Box padding="0">
174
- <RadioButton
175
- id={`radio-input-${idString(section)}`}
176
- name={idString(section)}
177
- ariaDescribedBy={ariaDescribedBy}
178
- radioOn={openSection === section.id}
179
- radioFocused={focused === section.id}
180
- toggleRadio={
181
- section.disabled
182
- ? noop
183
- : () => toggleOpenSection(section.id)
184
- }
185
- tabIndex="-1"
186
- isRequired={section?.required}
187
- />
188
- </Box>
189
- )}
190
- {section.titleIcon && (
191
- <Cluster align="center">{section.titleIcon}</Cluster>
192
- )}
193
- <Box padding={section.titleIcon ? "0 0 0 8px" : "0"}>
194
- <Text
195
- as="label"
196
- htmlFor={`radio-input-${idString(section)}`}
197
- color={CHARADE_GREY}
198
- >
199
- {section.title}
200
- </Text>
201
- </Box>
202
- </Cluster>
203
- {section.rightIcons && (
204
- <Cluster
205
- childGap="0.5rem"
206
- aria-label={section?.rightIconsLabel || null}
207
- role={section?.rightIconsRole || null}
208
- >
209
- {section.rightIcons.map(icon => (
210
- <RightIcon
211
- src={icon.img}
212
- key={icon.img}
213
- fade={!icon.enabled}
214
- isMobile={isMobile}
215
- alt={icon.altText}
216
- aria-disabled={!icon.enabled}
217
- />
218
- ))}
219
- </Cluster>
220
- )}
221
- {section.rightTitleContent && (
222
- <Fragment>{section.rightTitleContent}</Fragment>
141
+ <InnerRadioSection
142
+ sectionIndex={i}
143
+ section={section}
144
+ sectionRefs={sectionRefs}
145
+ focused={focused}
146
+ setFocused={setFocused}
147
+ openHeight={openHeight}
148
+ ariaLabelledBy={section.id}
149
+ ariaDescribedBy={`right-icons-${idString(section)}`}
150
+ openSection={openSection}
151
+ toggleOpenSection={toggleOpenSection}
152
+ />
153
+ {sectionGroup.indexOf(section) === sectionGroup.length - 1 &&
154
+ groupedSections.indexOf(sectionGroup) !==
155
+ groupedSections.length - 1 && (
156
+ <SolidDivider
157
+ borderSize="1px" // Resulting line is 2px thick due to border from other element
158
+ color={themeValues.borderColor}
159
+ />
223
160
  )}
224
- </Cluster>
225
- </Box>
226
- <AnimatePresence initial={false}>
227
- {openSection === section.id && (
228
- <Motion
229
- key={`content-${section.id}`}
230
- padding="0"
231
- background={themeValues.bodyBackgroundColor}
232
- layoutTransition
233
- initial="closed"
234
- animate="open"
235
- exit="closed"
236
- variants={wrapper}
237
- extraStyles={`transform-origin: 100% 0;`}
238
- >
239
- {section.content}
240
- </Motion>
241
- )}
242
- </AnimatePresence>
243
- </Stack>
244
- </Motion>
245
- ))}
161
+ </Fragment>
162
+ ))
163
+ )}
246
164
  </Stack>
247
165
  </Box>
248
166
  );
@@ -1,8 +1,9 @@
1
1
  import React, { useState } from "react";
2
- import { text, boolean } from "@storybook/addon-knobs";
2
+ import { boolean } from "@storybook/addon-knobs";
3
3
 
4
4
  import RadioSection from "./RadioSection";
5
5
  import page from "../../../../.storybook/page";
6
+ import { Box } from "../../atoms/layouts";
6
7
 
7
8
  const story = page({
8
9
  title: "Components|Molecules/RadioSection",
@@ -43,30 +44,99 @@ const cardIconsLabel = `Accepting ${cardIcons
43
44
  ? ` and ${cardIcon.altText}.`
44
45
  : ` ` + cardIcon.altText
45
46
  )}`;
47
+ const groupedSections = [
48
+ [
49
+ {
50
+ id: "new-card-section",
51
+ title: "Group 1: New Card",
52
+ content: <p>The form to add a credit card would go here.</p>,
53
+ rightIconsLabel: cardIconsLabel,
54
+ rightIcons: cardIcons,
55
+ required: true
56
+ },
57
+ {
58
+ id: "new-bank-section",
59
+ title: "Group 1: New Bank Account",
60
+ content: <p>The form to add a credit card would go here.</p>,
61
+ required: true
62
+ }
63
+ ],
64
+ [
65
+ {
66
+ id: "bar",
67
+ title: "Group 2: Bar",
68
+ content: <div>Content 1</div>,
69
+ required: true
70
+ }
71
+ ],
72
+ [
73
+ {
74
+ id: "bar2",
75
+ title: "Group 3: Bar",
76
+ content: <div>Content 1</div>,
77
+ required: true
78
+ },
79
+ {
80
+ id: "baz",
81
+ title: "Group 3: Baz",
82
+ content: <div>Content 1</div>,
83
+ required: true
84
+ }
85
+ ]
86
+ ];
46
87
  const sections = [
47
88
  {
48
- id: "new-card-section",
49
- title: "New Card",
50
- content: <p>The form to add a credit card would go here.</p>,
89
+ id: "new-card-section-2",
90
+ title: "Section 1: New Card",
91
+ content: <Box>The form to add a credit card would go here.</Box>,
51
92
  rightIconsLabel: cardIconsLabel,
52
93
  rightIcons: cardIcons,
53
94
  required: true
54
95
  },
55
- { id: "bar", title: "Bar", content: <div>Content 1</div>, required: true },
56
- { id: "baz", title: "Baz", content: <div>Content 2</div> }
96
+ {
97
+ id: "bar3",
98
+ title: "Section 1: Bar",
99
+ content: <div>Content 1</div>,
100
+ required: true
101
+ },
102
+ { id: "baz2", title: "Section 1: Baz", content: <div>Content 2</div> }
57
103
  ];
58
104
 
59
105
  export const radioSection = () => {
60
106
  const [openSection, setOpenSection] = useState("");
107
+ const [openGroupedSection, setOpenGroupedSection] = useState("");
61
108
  return (
62
- <RadioSection
63
- isMobile={boolean("isMobile", false, "props")}
64
- supportsTouch={boolean("isMobile", false, "props")}
65
- toggleOpenSection={setOpenSection}
66
- openSection={openSection}
67
- staggeredAnimation={boolean("staggeredAnimation", false, "props")}
68
- sections={sections}
69
- isSectionRequired={true}
70
- />
109
+ <>
110
+ <Box padding="0 1rem" extraStyles="text-align: center;">
111
+ <p>
112
+ Using <b>sections</b>, a flat array of section objects.
113
+ </p>
114
+ </Box>
115
+ <RadioSection
116
+ isMobile={boolean("isMobile", false, "props")}
117
+ supportsTouch={boolean("isMobile", false, "props")}
118
+ toggleOpenSection={setOpenSection}
119
+ openSection={openSection}
120
+ staggeredAnimation={boolean("staggeredAnimation", false, "props")}
121
+ sections={sections}
122
+ isSectionRequired={true}
123
+ />
124
+ <Box />
125
+ <Box padding="0 1rem" extraStyles="text-align: center;">
126
+ <p>
127
+ Using <b>groupedSections</b>, a multidimensional array of sections,
128
+ instead of the <b>sections</b> prop.
129
+ </p>
130
+ </Box>
131
+ <RadioSection
132
+ isMobile={boolean("isMobile", false, "props")}
133
+ supportsTouch={boolean("isMobile", false, "props")}
134
+ toggleOpenSection={setOpenGroupedSection}
135
+ openSection={openGroupedSection}
136
+ staggeredAnimation={boolean("staggeredAnimation", false, "props")}
137
+ groupedSections={groupedSections}
138
+ isSectionRequired={true}
139
+ />
140
+ </>
71
141
  );
72
142
  };
@@ -1,9 +1,9 @@
1
- import { WHITE, GREY_CHATEAU, ATHENS_GREY } from "../../../constants/colors";
1
+ import { WHITE, GHOST_GREY, ATHENS_GREY } from "../../../constants/colors";
2
2
 
3
3
  const headingBackgroundColor = `${WHITE}`;
4
4
  const headingDisabledColor = `${ATHENS_GREY}`;
5
5
  const bodyBackgroundColor = "#eeeeee";
6
- const borderColor = `${GREY_CHATEAU}`;
6
+ const borderColor = `${GHOST_GREY}`;
7
7
  const focusStyles = `outline: none;`;
8
8
 
9
9
  export const fallbackValues = {
@@ -16,7 +16,7 @@ const RadioButton = ({
16
16
  toggleRadio,
17
17
  name,
18
18
  disabled = false,
19
- ariaDescribedBy = "",
19
+ ariaDescribedBy,
20
20
  themeValues,
21
21
  ariaLabelledBy = "",
22
22
  ariaLabel = null,
@@ -7,14 +7,31 @@ import { noop } from "../../../util/general";
7
7
 
8
8
  const groupId = "props";
9
9
 
10
+ const Terms = () => (
11
+ <p>
12
+ Modal Content: Elit voluptate cupidatat in pariatur anim in excepteur non.{" "}
13
+ <a href="#modal-bottom" id="focus-me">
14
+ Laboris elit laboris labore pariatur incididunt
15
+ </a>{" "}
16
+ proident occaecat laboris sit elit. Dolor irure enim adipisicing irure
17
+ consectetur dolor enim eiusmod elit eiusmod ut. Pariatur ut quis et pariatur
18
+ nulla nostrud sit esse. Veniam est occaecat cupidatat est nulla dolor minim
19
+ nostrud anim ea voluptate. modal content. Elit voluptate cupidatat in
20
+ pariatur anim in excepteur non. Laboris elit laboris labore pariatur
21
+ incididunt proident occaecat laboris sit elit. Dolor irure enim adipisicing
22
+ irure consectetur dolor enim eiusmod elit eiusmod ut. Pariatur ut quis et
23
+ pariatur nulla nostrud sit esse.
24
+ </p>
25
+ );
26
+
10
27
  export const termsAndConditions = () => (
11
28
  <TermsAndConditions
12
29
  version={select("version", ["v1", "v2"], "v1", groupId)}
13
30
  onCheck={noop}
14
31
  isChecked={boolean("isChecked", false, groupId)}
15
- html={text("html", "terms and conditions summary", groupId)}
16
- terms={text("terms", "terms and conditions modal text", groupId)}
32
+ terms={<Terms />}
17
33
  error={boolean("error", false, groupId)}
34
+ initialFocusSelector={text("initialFocusSelector", "#focus-me", groupId)}
18
35
  description={text("description", "I definitely agree to the", groupId)}
19
36
  />
20
37
  );
@@ -10,7 +10,8 @@ const TermsAndConditionsControlV1 = ({
10
10
  html,
11
11
  terms,
12
12
  error = false,
13
- linkVariant
13
+ linkVariant,
14
+ initialFocusSelector = ""
14
15
  }) => {
15
16
  const [showTerms, toggleShowTerms] = useState(false);
16
17
  return (
@@ -33,6 +34,7 @@ const TermsAndConditionsControlV1 = ({
33
34
  isOpen={showTerms}
34
35
  toggleOpen={toggleShowTerms}
35
36
  linkVariant={linkVariant}
37
+ initialFocusSelector={initialFocusSelector}
36
38
  />
37
39
  )}
38
40
  </Stack>
@@ -28,7 +28,8 @@ const TermsAndConditionsControlV2 = ({
28
28
  modalVariant = "default",
29
29
  containerBackground = ATHENS_GREY,
30
30
  checkboxMargin = "4px 8px 4px 4px",
31
- modalTitle = "Terms and Conditions"
31
+ modalTitle = "Terms and Conditions",
32
+ initialFocusSelector = ""
32
33
  }) => {
33
34
  const [showTerms, toggleShowTerms] = useState(false);
34
35
  const standardBoxShadow = generateShadows().standard.base;
@@ -79,6 +80,7 @@ const TermsAndConditionsControlV2 = ({
79
80
  toggleOpen={toggleTerms}
80
81
  linkVariant={modalVariant}
81
82
  title={modalTitle}
83
+ initialFocusSelector={initialFocusSelector}
82
84
  />
83
85
  )}
84
86
  </Cluster>
@@ -15,7 +15,8 @@ const TermsAndConditionsModal = ({
15
15
  terms,
16
16
  variant,
17
17
  linkVariant = "p",
18
- themeValues
18
+ themeValues,
19
+ initialFocusSelector = ""
19
20
  }) => (
20
21
  <Modal
21
22
  modalOpen={isOpen}
@@ -42,6 +43,7 @@ const TermsAndConditionsModal = ({
42
43
  toggleAccepted(true);
43
44
  toggleOpen(false);
44
45
  }}
46
+ initialFocusSelector={initialFocusSelector}
45
47
  >
46
48
  <Text
47
49
  variant={linkVariant}
@@ -52,6 +54,8 @@ const TermsAndConditionsModal = ({
52
54
  weight={themeValues.fontWeight}
53
55
  hoverStyles={themeValues.modalLinkHoverFocus}
54
56
  extraStyles={`display: inline-block; width: fit-content; cursor: pointer`}
57
+ role="button" // This should always be a "button" since it opens a modal
58
+ className="modal-trigger"
55
59
  >
56
60
  {link}
57
61
  </Text>
@@ -20,17 +20,22 @@ const linkVariants = {
20
20
  pXL: "PXL"
21
21
  };
22
22
 
23
+ const Terms = () => (
24
+ <p>
25
+ Terms content with a <a href="#">link</a> that should NOT get initial focus.
26
+ Instead, the <code>Cancel</code> button below should.
27
+ </p>
28
+ );
23
29
  export const termsAndConditionsModal = () => (
24
30
  <TermsAndConditionsModal
25
31
  link={text("text", "Show modal", groupId)}
26
32
  title={text("title", "Title", groupId)}
27
33
  isOpen={boolean("isOpen", false, groupId)}
28
- // toggleOpen={setShowTerms}
29
- // toggleAccepted={toggleTermsAccepted}
30
34
  acceptText={text("acceptText", "Accept", groupId)}
31
- terms={text("terms", "terms and conditions", groupId)}
35
+ terms={<Terms />}
32
36
  variant={select("variants", variants, "default", groupId)}
33
37
  linkVariant={select("linkVariants", linkVariants, groupId)}
38
+ initialFocusSelector="[name='Cancel']"
34
39
  />
35
40
  );
36
41
 
@@ -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
+ };