@thecb/components 9.3.3-beta.20 → 9.3.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thecb/components",
3
- "version": "9.3.3-beta.20",
3
+ "version": "9.3.3",
4
4
  "description": "Common lib for CityBase react components",
5
5
  "main": "dist/index.cjs.js",
6
6
  "typings": "dist/index.d.ts",
@@ -50,6 +50,7 @@ const AccountAndRoutingModal = ({
50
50
  toggleAccepted(true);
51
51
  toggleOpen(false);
52
52
  }}
53
+ initialFocusSelector={"[name='Close']"}
53
54
  >
54
55
  <Text
55
56
  variant="pS"
@@ -60,6 +61,8 @@ const AccountAndRoutingModal = ({
60
61
  weight={themeValues.fontWeight}
61
62
  hoverStyles={themeValues.modalLinkHoverFocus}
62
63
  extraStyles={`cursor: pointer;`}
64
+ role="button"
65
+ className="modal-trigger"
63
66
  >
64
67
  {link}
65
68
  </Text>
@@ -45,10 +45,12 @@ const Modal = ({
45
45
  isLoading,
46
46
  buttonExtraStyles,
47
47
  children,
48
- dataQa = null
48
+ dataQa = null,
49
+ initialFocusSelector = ""
49
50
  }) => {
50
51
  const { isMobile } = useContext(ThemeContext);
51
52
  const modalContainerRef = useRef(null);
53
+
52
54
  return (
53
55
  <div ref={modalContainerRef} data-qa={dataQa}>
54
56
  {modalOpen && (
@@ -71,6 +73,9 @@ const Modal = ({
71
73
  overflow: "auto"
72
74
  }}
73
75
  underlayClickExits={underlayClickExits}
76
+ aria-modal={true}
77
+ initialFocus={initialFocusSelector}
78
+ focusDialog={!initialFocusSelector} // Focus the dialogue box itself if no selector for initial focus was provided
74
79
  >
75
80
  <Box
76
81
  padding="0"
@@ -125,6 +130,8 @@ const Modal = ({
125
130
  dataQa={cancelButtonText}
126
131
  extraStyles={buttonExtraStyles}
127
132
  className="modal-cancel-button"
133
+ role="button"
134
+ name={cancelButtonText}
128
135
  />
129
136
  </Box>
130
137
  <Box width="100%" padding="0">
@@ -139,6 +146,8 @@ const Modal = ({
139
146
  disabled={isContinueActionDisabled}
140
147
  extraStyles={buttonExtraStyles}
141
148
  className="modal-continue-button"
149
+ role="button"
150
+ name={continueButtonText}
142
151
  />
143
152
  </Box>
144
153
  </Stack>
@@ -157,6 +166,8 @@ const Modal = ({
157
166
  dataQa={cancelButtonText}
158
167
  extraStyles={buttonExtraStyles}
159
168
  className="modal-cancel-button"
169
+ role="button"
170
+ name={cancelButtonText}
160
171
  />
161
172
  <ButtonWithAction
162
173
  variant={
@@ -169,6 +180,8 @@ const Modal = ({
169
180
  disabled={isContinueActionDisabled}
170
181
  extraStyles={buttonExtraStyles}
171
182
  className="modal-continue-button"
183
+ role="button"
184
+ name={continueButtonText}
172
185
  />
173
186
  </Stack>
174
187
  )}
@@ -182,6 +195,8 @@ const Modal = ({
182
195
  dataQa={closeButtonText}
183
196
  extraStyles={buttonExtraStyles}
184
197
  className="modal-close-button"
198
+ role="button"
199
+ name={closeButtonText}
185
200
  />
186
201
  </Box>
187
202
  )}
@@ -70,19 +70,9 @@ const AutopayModal = ({
70
70
  : navigateToSettings
71
71
  };
72
72
 
73
- const hoverStyles = `
74
- &:hover {
75
- .autopayIcon { fill: ${themeValues.hoverColor}; text-decoration: underline; cursor: pointer; }
76
- }`;
73
+ const hoverStyles = "text-decoration: underline;";
74
+ const activeStyles = "text-decoration: underline;";
77
75
 
78
- const activeStyles = `
79
- &:active {
80
- .autopayIcon { fill: ${themeValues.activeColor}; text-decoration: underline; }
81
- }`;
82
-
83
- const defaultStyles = `
84
- .autopayIcon { fill: ${themeValues.color}; text-decoration: underline; }
85
- `;
86
76
  const renderAutoPayControl = () => {
87
77
  switch (controlType) {
88
78
  case "secondary": {
@@ -126,7 +116,7 @@ const AutopayModal = ({
126
116
  }}
127
117
  hoverStyles={hoverStyles}
128
118
  activeStyles={activeStyles}
129
- extraStyles={defaultStyles}
119
+ extraStyles={"cursor: pointer;"}
130
120
  >
131
121
  <Cluster
132
122
  justify={isMobile ? "flex-start" : "flex-end"}
@@ -188,6 +188,7 @@ const PaymentFormACH = ({
188
188
  showCheckbox={false}
189
189
  description="View"
190
190
  terms={termsContent}
191
+ initialFocusSelector={".modal-close-button"}
191
192
  />
192
193
  </Cover>
193
194
  )}
@@ -210,6 +210,7 @@ const PaymentFormCard = ({
210
210
  showCheckbox={false}
211
211
  description="View"
212
212
  terms={termsContent}
213
+ initialFocusSelector={".modal-close-button"}
213
214
  />
214
215
  </Cover>
215
216
  )}
@@ -6,6 +6,7 @@ import { Box } from "../../atoms/layouts";
6
6
  import ButtonWithAction from "../../atoms/button-with-action";
7
7
  import { useOutsideClick } from "../../../hooks";
8
8
  import { noop } from "../../../util/general";
9
+ import { ESCAPE } from "../../../constants/keyboard";
9
10
  import { fallbackValues } from "./Popover.theme";
10
11
 
11
12
  const arrowBorder = (borderColor, direction, width = "8px") => {
@@ -76,7 +77,7 @@ const Popover = ({
76
77
  handleTogglePopover(false);
77
78
  }}
78
79
  onKeyDown={e => {
79
- if (e.keyCode === 27) {
80
+ if (e.keyCode === ESCAPE) {
80
81
  handleTogglePopover(false);
81
82
  }
82
83
  }}
@@ -4,7 +4,6 @@ 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
- import SolidDivider from "../../atoms/solid-divider/SolidDivider";
8
7
  import { Box, Cluster, Stack, Motion } from "../../atoms/layouts";
9
8
  import { createIdFromString, noop, wrapIndex } from "../../../util/general";
10
9
  import Text from "../../atoms/text";
@@ -58,7 +57,6 @@ const RadioSection = ({
58
57
  containerStyles = "",
59
58
  ariaDescribedBy,
60
59
  isSectionRequired = false,
61
- groupedSections,
62
60
  ...rest
63
61
  }) => {
64
62
  const [focused, setFocused] = useState(null);
@@ -149,306 +147,144 @@ const RadioSection = ({
149
147
  aria-required={isSectionRequired}
150
148
  {...rest}
151
149
  >
152
- {!!sections &&
153
- sections
154
- .filter(section => !section.hidden)
155
- .map((section, i) => (
156
- <Motion
157
- tabIndex={
158
- section.hideRadioButton || section.disabled ? "-1" : "0"
159
- }
160
- ref={sectionRefs.current[i]}
161
- onBlur={() => !section.disabled && setFocused(null)}
162
- onFocus={() => !section.disabled && setFocused(section.id)}
163
- onKeyDown={e =>
164
- !section.disabled && handleKeyDown(section.id, e, i)
165
- }
166
- hoverStyles={themeValues.focusStyles}
167
- animate={openSection === section.id ? "open" : "closed"}
168
- initial={initiallyOpen ? "open" : "closed"}
169
- key={`item-${section.id}`}
170
- extraStyles={borderStyles}
171
- role="radio"
172
- aria-checked={openSection === section.id}
173
- aria-disabled={section.disabled}
174
- aria-required={section.required}
175
- aria-labelledby={section.id}
176
- aria-describedby={`right-icons-${idString(section)}`}
177
- >
178
- <Stack childGap="0">
179
- <Box
180
- padding={
181
- section.hideRadioButton ? "1.5rem" : "1.25rem 1.5rem"
182
- }
183
- background={
184
- section.disabled
185
- ? themeValues.headingDisabledColor
186
- : themeValues.headingBackgroundColor
187
- }
188
- onClick={
189
- (isMobile && supportsTouch) || section.disabled
190
- ? noop
191
- : () => toggleOpenSection(section.id)
192
- }
193
- onTouchEnd={
194
- isMobile && supportsTouch && !section.disabled
195
- ? () => toggleOpenSection(section.id)
196
- : noop
197
- }
198
- key={`header-${section.id}`}
199
- borderSize="0px"
200
- borderColor={themeValues.borderColor}
201
- borderWidthOverride={
202
- openSection === section.id && !!section.content
203
- ? `0px 0px 1px 0px`
204
- : ``
205
- }
206
- extraStyles={!section.disabled ? "cursor: pointer;" : ""}
207
- dataQa={section.dataQa ? section.dataQa : section.id}
150
+ {sections
151
+ .filter(section => !section.hidden)
152
+ .map((section, i) => (
153
+ <Motion
154
+ tabIndex={
155
+ section.hideRadioButton || section.disabled ? "-1" : "0"
156
+ }
157
+ ref={sectionRefs.current[i]}
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
+ }
163
+ hoverStyles={themeValues.focusStyles}
164
+ animate={openSection === section.id ? "open" : "closed"}
165
+ initial={initiallyOpen ? "open" : "closed"}
166
+ key={`item-${section.id}`}
167
+ extraStyles={borderStyles}
168
+ role="radio"
169
+ aria-checked={openSection === section.id}
170
+ aria-disabled={section.disabled}
171
+ aria-required={section.required}
172
+ aria-labelledby={section.id}
173
+ aria-describedby={`right-icons-${idString(section)}`}
174
+ >
175
+ <Stack childGap="0">
176
+ <Box
177
+ padding={
178
+ section.hideRadioButton ? "1.5rem" : "1.25rem 1.5rem"
179
+ }
180
+ background={
181
+ section.disabled
182
+ ? themeValues.headingDisabledColor
183
+ : themeValues.headingBackgroundColor
184
+ }
185
+ onClick={
186
+ (isMobile && supportsTouch) || section.disabled
187
+ ? noop
188
+ : () => toggleOpenSection(section.id)
189
+ }
190
+ onTouchEnd={
191
+ isMobile && supportsTouch && !section.disabled
192
+ ? () => toggleOpenSection(section.id)
193
+ : noop
194
+ }
195
+ key={`header-${section.id}`}
196
+ borderSize="0px"
197
+ borderColor={themeValues.borderColor}
198
+ borderWidthOverride={
199
+ openSection === section.id && !!section.content
200
+ ? `0px 0px 1px 0px`
201
+ : ``
202
+ }
203
+ extraStyles={!section.disabled ? "cursor: pointer;" : ""}
204
+ dataQa={section.dataQa ? section.dataQa : section.id}
205
+ >
206
+ <Cluster
207
+ justify="space-between"
208
+ align="center"
209
+ childGap="1px"
210
+ nowrap
208
211
  >
209
- <Cluster
210
- justify="space-between"
211
- align="center"
212
- childGap="1px"
213
- nowrap
214
- >
215
- <Cluster justify="flex-start" align="center" nowrap>
216
- {!section.hideRadioButton && (
217
- <Box padding="0">
218
- <RadioButton
219
- id={`radio-input-${idString(section)}`}
220
- name={idString(section)}
221
- ariaDescribedBy={ariaDescribedBy}
222
- radioOn={openSection === section.id}
223
- radioFocused={focused === section.id}
224
- toggleRadio={
225
- section.disabled
226
- ? noop
227
- : () => toggleOpenSection(section.id)
228
- }
229
- tabIndex="-1"
230
- isRequired={section.required}
231
- />
232
- </Box>
233
- )}
234
- {section.titleIcon && (
235
- <Cluster align="center">{section.titleIcon}</Cluster>
236
- )}
237
- <Box padding={section.titleIcon ? "0 0 0 8px" : "0"}>
238
- <Text
239
- as="label"
240
- htmlFor={`radio-input-${idString(section)}`}
241
- color={CHARADE_GREY}
242
- >
243
- {section.title}
244
- </Text>
212
+ <Cluster justify="flex-start" align="center" nowrap>
213
+ {!section.hideRadioButton && (
214
+ <Box padding="0">
215
+ <RadioButton
216
+ id={`radio-input-${idString(section)}`}
217
+ name={idString(section)}
218
+ ariaDescribedBy={ariaDescribedBy}
219
+ radioOn={openSection === section.id}
220
+ radioFocused={focused === section.id}
221
+ toggleRadio={
222
+ section.disabled
223
+ ? noop
224
+ : () => toggleOpenSection(section.id)
225
+ }
226
+ tabIndex="-1"
227
+ isRequired={section.required}
228
+ />
245
229
  </Box>
246
- </Cluster>
247
- {section.rightIcons && (
248
- <Cluster
249
- id={`right-icons-${idString(section)}`}
250
- childGap="0.5rem"
251
- aria-label={section.rightIconsLabel || null}
252
- role={section.rightIconsRole || null}
253
- >
254
- {section.rightIcons.map(icon => (
255
- <RightIcon
256
- src={icon.img}
257
- key={icon.img}
258
- fade={!icon.enabled}
259
- isMobile={isMobile}
260
- alt={icon.altText}
261
- aria-disabled={!icon.enabled}
262
- />
263
- ))}
264
- </Cluster>
265
230
  )}
266
- {section.rightTitleContent && (
267
- <Fragment>{section.rightTitleContent}</Fragment>
231
+ {section.titleIcon && (
232
+ <Cluster align="center">{section.titleIcon}</Cluster>
268
233
  )}
234
+ <Box padding={section.titleIcon ? "0 0 0 8px" : "0"}>
235
+ <Text
236
+ as="label"
237
+ htmlFor={`radio-input-${idString(section)}`}
238
+ color={CHARADE_GREY}
239
+ >
240
+ {section.title}
241
+ </Text>
242
+ </Box>
269
243
  </Cluster>
270
- </Box>
271
- <AnimatePresence initial={false}>
272
- {openSection === section.id && (
273
- <Motion
274
- key={`content-${section.id}`}
275
- padding="0"
276
- background={themeValues.bodyBackgroundColor}
277
- layoutTransition
278
- initial="closed"
279
- animate="open"
280
- exit="closed"
281
- variants={wrapper}
282
- extraStyles={`transform-origin: 100% 0;`}
244
+ {section.rightIcons && (
245
+ <Cluster
246
+ id={`right-icons-${idString(section)}`}
247
+ childGap="0.5rem"
248
+ aria-label={section.rightIconsLabel || null}
249
+ role={section.rightIconsRole || null}
283
250
  >
284
- {section.content}
285
- </Motion>
251
+ {section.rightIcons.map(icon => (
252
+ <RightIcon
253
+ src={icon.img}
254
+ key={icon.img}
255
+ fade={!icon.enabled}
256
+ isMobile={isMobile}
257
+ alt={icon.altText}
258
+ aria-disabled={!icon.enabled}
259
+ />
260
+ ))}
261
+ </Cluster>
286
262
  )}
287
- </AnimatePresence>
288
- </Stack>
289
- </Motion>
290
- ))}
291
- {!!groupedSections &&
292
- groupedSections.map(sectionGroup =>
293
- sectionGroup
294
- .filter(unfilteredSection => !unfilteredSection.hidden)
295
- .map((section, i) => (
296
- <>
297
- <Motion
298
- tabIndex={
299
- section.hideRadioButton || section.disabled ? "-1" : "0"
300
- }
301
- ref={sectionRefs.current[i]}
302
- onBlur={() => !section.disabled && setFocused(null)}
303
- onFocus={() => !section.disabled && setFocused(section.id)}
304
- onKeyDown={e =>
305
- !section.disabled && handleKeyDown(section.id, e, i)
306
- }
307
- hoverStyles={themeValues.focusStyles}
308
- animate={openSection === section.id ? "open" : "closed"}
309
- initial={initiallyOpen ? "open" : "closed"}
310
- key={`item-${section.id}`}
311
- extraStyles={borderStyles}
312
- role="radio"
313
- aria-checked={openSection === section.id}
314
- aria-disabled={section.disabled}
315
- aria-required={section?.required}
316
- >
317
- <Stack childGap="0">
318
- <Box
319
- padding={
320
- section.hideRadioButton ? "1.5rem" : "1.25rem 1.5rem"
321
- }
322
- background={
323
- section.disabled
324
- ? themeValues.headingDisabledColor
325
- : themeValues.headingBackgroundColor
326
- }
327
- onClick={
328
- (isMobile && supportsTouch) || section.disabled
329
- ? noop
330
- : () => toggleOpenSection(section.id)
331
- }
332
- onTouchEnd={
333
- isMobile && supportsTouch && !section.disabled
334
- ? () => toggleOpenSection(section.id)
335
- : noop
336
- }
337
- key={`header-${section.id}`}
338
- borderSize="0px"
339
- borderColor={themeValues.borderColor}
340
- borderWidthOverride={
341
- openSection === section.id && !!section.content
342
- ? `0px 0px 1px 0px`
343
- : ``
344
- }
345
- extraStyles={
346
- !section.disabled ? "cursor: pointer;" : ""
347
- }
348
- dataQa={section.dataQa ? section.dataQa : section.id}
349
- role="radio"
350
- aria-checked={openSection === section.id}
351
- aria-disabled={section.disabled}
352
- aria-required={section.required}
353
- aria-labelledby={section.id}
354
- aria-describedby={`right-icons-${idString(section)}`}
355
- >
356
- <Cluster
357
- justify="space-between"
358
- align="center"
359
- childGap="1px"
360
- nowrap
361
- >
362
- <Cluster justify="flex-start" align="center" nowrap>
363
- {!section.hideRadioButton && (
364
- <Box padding="0">
365
- <RadioButton
366
- id={`radio-input-${idString(section)}`}
367
- name={idString(section)}
368
- ariaDescribedBy={ariaDescribedBy}
369
- radioOn={openSection === section.id}
370
- radioFocused={focused === section.id}
371
- toggleRadio={
372
- section.disabled
373
- ? noop
374
- : () => toggleOpenSection(section.id)
375
- }
376
- tabIndex="-1"
377
- isRequired={section.required}
378
- />
379
- </Box>
380
- )}
381
- {section.titleIcon && (
382
- <Cluster align="center">
383
- {section.titleIcon}
384
- </Cluster>
385
- )}
386
- <Box
387
- padding={section.titleIcon ? "0 0 0 8px" : "0"}
388
- >
389
- <Text
390
- as="label"
391
- htmlFor={`radio-input-${idString(section)}`}
392
- color={CHARADE_GREY}
393
- >
394
- {section.title}
395
- </Text>
396
- </Box>
397
- </Cluster>
398
- {section.rightIcons && (
399
- <Cluster
400
- id={`right-icons-${idString(section)}`}
401
- childGap="0.5rem"
402
- aria-label={section.rightIconsLabel || null}
403
- role={section.rightIconsRole || null}
404
- >
405
- {section.rightIcons.map(icon => (
406
- <RightIcon
407
- src={icon.img}
408
- key={icon.img}
409
- fade={!icon.enabled}
410
- isMobile={isMobile}
411
- alt={icon.altText}
412
- aria-disabled={!icon.enabled}
413
- />
414
- ))}
415
- </Cluster>
416
- )}
417
- {section.rightTitleContent && (
418
- <Fragment>{section.rightTitleContent}</Fragment>
419
- )}
420
- </Cluster>
421
- </Box>
422
- <AnimatePresence initial={false}>
423
- {openSection === section.id && (
424
- <Motion
425
- key={`content-${section.id}`}
426
- padding="0"
427
- background={themeValues.bodyBackgroundColor}
428
- layoutTransition
429
- initial="closed"
430
- animate="open"
431
- exit="closed"
432
- variants={wrapper}
433
- extraStyles={`transform-origin: 100% 0;`}
434
- >
435
- {section.content}
436
- </Motion>
437
- )}
438
- </AnimatePresence>
439
- </Stack>
440
- </Motion>
441
- {sectionGroup.indexOf(section) === sectionGroup.length - 1 &&
442
- groupedSections.indexOf(sectionGroup) !==
443
- groupedSections.length - 1 && (
444
- <SolidDivider
445
- borderSize="2px"
446
- color={themeValues.borderColor}
447
- />
263
+ {section.rightTitleContent && (
264
+ <Fragment>{section.rightTitleContent}</Fragment>
448
265
  )}
449
- </>
450
- ))
451
- )}
266
+ </Cluster>
267
+ </Box>
268
+ <AnimatePresence initial={false}>
269
+ {openSection === section.id && (
270
+ <Motion
271
+ key={`content-${section.id}`}
272
+ padding="0"
273
+ background={themeValues.bodyBackgroundColor}
274
+ layoutTransition
275
+ initial="closed"
276
+ animate="open"
277
+ exit="closed"
278
+ variants={wrapper}
279
+ extraStyles={`transform-origin: 100% 0;`}
280
+ >
281
+ {section.content}
282
+ </Motion>
283
+ )}
284
+ </AnimatePresence>
285
+ </Stack>
286
+ </Motion>
287
+ ))}
452
288
  </Stack>
453
289
  </Box>
454
290
  );