@thecb/components 9.3.2 → 9.3.3-beta.20

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.2",
3
+ "version": "9.3.3-beta.20",
4
4
  "description": "Common lib for CityBase react components",
5
5
  "main": "dist/index.cjs.js",
6
6
  "typings": "dist/index.d.ts",
package/src/.DS_Store ADDED
Binary file
Binary file
Binary file
@@ -70,9 +70,19 @@ const AutopayModal = ({
70
70
  : navigateToSettings
71
71
  };
72
72
 
73
- const hoverStyles = "text-decoration: underline;";
74
- const activeStyles = "text-decoration: underline;";
73
+ const hoverStyles = `
74
+ &:hover {
75
+ .autopayIcon { fill: ${themeValues.hoverColor}; text-decoration: underline; cursor: pointer; }
76
+ }`;
75
77
 
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
+ `;
76
86
  const renderAutoPayControl = () => {
77
87
  switch (controlType) {
78
88
  case "secondary": {
@@ -116,7 +126,7 @@ const AutopayModal = ({
116
126
  }}
117
127
  hoverStyles={hoverStyles}
118
128
  activeStyles={activeStyles}
119
- extraStyles={"cursor: pointer;"}
129
+ extraStyles={defaultStyles}
120
130
  >
121
131
  <Cluster
122
132
  justify={isMobile ? "flex-start" : "flex-end"}
@@ -6,7 +6,6 @@ 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";
10
9
  import { fallbackValues } from "./Popover.theme";
11
10
 
12
11
  const arrowBorder = (borderColor, direction, width = "8px") => {
@@ -77,7 +76,7 @@ const Popover = ({
77
76
  handleTogglePopover(false);
78
77
  }}
79
78
  onKeyDown={e => {
80
- if (e.keyCode === ESCAPE) {
79
+ if (e.keyCode === 27) {
81
80
  handleTogglePopover(false);
82
81
  }
83
82
  }}
@@ -4,6 +4,7 @@ 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";
7
8
  import { Box, Cluster, Stack, Motion } from "../../atoms/layouts";
8
9
  import { createIdFromString, noop, wrapIndex } from "../../../util/general";
9
10
  import Text from "../../atoms/text";
@@ -57,6 +58,7 @@ const RadioSection = ({
57
58
  containerStyles = "",
58
59
  ariaDescribedBy,
59
60
  isSectionRequired = false,
61
+ groupedSections,
60
62
  ...rest
61
63
  }) => {
62
64
  const [focused, setFocused] = useState(null);
@@ -147,144 +149,306 @@ const RadioSection = ({
147
149
  aria-required={isSectionRequired}
148
150
  {...rest}
149
151
  >
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
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}
211
208
  >
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
- />
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>
229
245
  </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>
230
265
  )}
231
- {section.titleIcon && (
232
- <Cluster align="center">{section.titleIcon}</Cluster>
266
+ {section.rightTitleContent && (
267
+ <Fragment>{section.rightTitleContent}</Fragment>
233
268
  )}
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>
243
269
  </Cluster>
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}
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;`}
250
283
  >
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>
284
+ {section.content}
285
+ </Motion>
262
286
  )}
263
- {section.rightTitleContent && (
264
- <Fragment>{section.rightTitleContent}</Fragment>
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
+ />
265
448
  )}
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
- ))}
449
+ </>
450
+ ))
451
+ )}
288
452
  </Stack>
289
453
  </Box>
290
454
  );
@@ -3,6 +3,7 @@ import { text, 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,6 +44,46 @@ 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: "bar",
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
89
  id: "new-card-section",
@@ -58,15 +99,35 @@ const sections = [
58
99
 
59
100
  export const radioSection = () => {
60
101
  const [openSection, setOpenSection] = useState("");
102
+ const [openGroupedSection, setOpenGroupedSection] = useState("");
61
103
  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
- />
104
+ <>
105
+ <p>
106
+ Using <code>sections</code>, a flat array of sections.
107
+ </p>
108
+ <RadioSection
109
+ isMobile={boolean("isMobile", false, "props")}
110
+ supportsTouch={boolean("isMobile", false, "props")}
111
+ toggleOpenSection={setOpenSection}
112
+ openSection={openSection}
113
+ staggeredAnimation={boolean("staggeredAnimation", false, "props")}
114
+ sections={sections}
115
+ isSectionRequired={true}
116
+ />
117
+ <Box />
118
+ <p>
119
+ Using <code>groupedSections</code>, a multidimensional array of
120
+ sections, instead of <code>sections</code> prop.
121
+ </p>
122
+ <RadioSection
123
+ isMobile={boolean("isMobile", false, "props")}
124
+ supportsTouch={boolean("isMobile", false, "props")}
125
+ toggleOpenSection={setOpenGroupedSection}
126
+ openSection={openGroupedSection}
127
+ staggeredAnimation={boolean("staggeredAnimation", false, "props")}
128
+ groupedSections={groupedSections}
129
+ isSectionRequired={true}
130
+ />
131
+ </>
71
132
  );
72
133
  };
@@ -1,4 +1,5 @@
1
1
  import * as colors from "./colors";
2
2
  import * as fontWeights from "./style_constants";
3
+ import * as keyboard from "./keyboard.js";
3
4
 
4
- export { colors, fontWeights };
5
+ export { colors, fontWeights, keyboard };
@@ -1,7 +1,6 @@
1
+ export const ARROW_DOWN = 40;
1
2
  export const ARROW_LEFT = 37;
2
- export const ARROW_UP = 38;
3
3
  export const ARROW_RIGHT = 39;
4
- export const ARROW_DOWN = 40;
4
+ export const ARROW_UP = 38;
5
5
  export const ENTER = 13;
6
- export const ESCAPE = 27;
7
6
  export const SPACEBAR = 32;