@thecb/components 5.3.3 → 5.5.0

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": "5.3.3",
3
+ "version": "5.5.0",
4
4
  "description": "Common lib for CityBase react components",
5
5
  "main": "dist/index.cjs.js",
6
6
  "module": "dist/index.esm.js",
@@ -9,20 +9,28 @@ const Reel = ({
9
9
  padding = "1rem",
10
10
  justifyContent = "flex-start",
11
11
  disableScroll = false,
12
+ useOrderedList = false,
13
+ useUnorderedList = false,
12
14
  children,
13
15
  ...rest
14
- }) => (
15
- <ReelStyled
16
- childGap={childGap}
17
- height={height}
18
- childWidth={childWidth}
19
- padding={padding}
20
- justifyContent={justifyContent}
21
- disableScroll={disableScroll}
22
- {...rest}
23
- >
24
- {safeChildren(children, <Fragment />)}
25
- </ReelStyled>
26
- );
16
+ }) => {
17
+ const elementType = useOrderedList ? "ol" : useUnorderedList ? "ul" : "div";
18
+ return (
19
+ <ReelStyled
20
+ childGap={childGap}
21
+ height={height}
22
+ childWidth={childWidth}
23
+ padding={padding}
24
+ justifyContent={justifyContent}
25
+ disableScroll={disableScroll}
26
+ as={elementType}
27
+ useOrderedList={useOrderedList}
28
+ useUnorderedList={useUnorderedList}
29
+ {...rest}
30
+ >
31
+ {safeChildren(children, <Fragment />)}
32
+ </ReelStyled>
33
+ );
34
+ };
27
35
 
28
36
  export default Reel;
@@ -14,4 +14,15 @@ export const ReelStyled = styled.div`
14
14
  > * {
15
15
  flex: 0 0 ${({ childWidth }) => childWidth};
16
16
  }
17
+
18
+ ${({ useOrderedList, useUnorderedList }) =>
19
+ useOrderedList || useUnorderedList
20
+ ? `
21
+ margin: 0;
22
+ margin-block-start: 0;
23
+ margin-block-end: 0;
24
+ padding-inline-start: 0;
25
+ list-style-type: none;
26
+ `
27
+ : ``}
17
28
  `;
@@ -2,7 +2,7 @@ import React, { Fragment } from "react";
2
2
  import { fallbackValues } from "./CollapsibleSection.theme";
3
3
  import { AnimatePresence } from "framer-motion";
4
4
  import { themeComponent } from "../../../util/themeUtils";
5
- import Heading from "../../atoms/heading";
5
+ import Title from "../../atoms/title";
6
6
  import { FONT_WEIGHT_SEMIBOLD } from "../../../constants/style_constants";
7
7
  import { Box, Cluster, Stack, Motion } from "../../atoms/layouts";
8
8
  import { ChevronIcon } from "../../atoms/icons";
@@ -22,7 +22,9 @@ const CollapsibleSection = ({
22
22
  customTitle = false,
23
23
  stackTitle = false,
24
24
  stackTitleContent,
25
- sectionGap = "0.5rem"
25
+ sectionGap = "0.5rem",
26
+ name = "",
27
+ index = 1
26
28
  }) => {
27
29
  const handleKeyDown = e => {
28
30
  if (e.keyCode === 13) {
@@ -31,6 +33,9 @@ const CollapsibleSection = ({
31
33
  };
32
34
 
33
35
  const numChildren = typeof children === "Array" ? children.length : 1;
36
+ const label =
37
+ name !== "" ? name : !customTitle ? title : "collapsible section";
38
+ const id = `${label?.replaceAll(" ", "-")?.toLowerCase()}-${index}`;
34
39
 
35
40
  const wrapper = {
36
41
  open: {
@@ -71,39 +76,45 @@ const CollapsibleSection = ({
71
76
  >
72
77
  {stackTitle && <Fragment>{stackTitleContent}</Fragment>}
73
78
  <Stack childGap={isOpen && !isMobile ? sectionGap : "0rem"}>
74
- <Box
75
- padding={customPadding ? customPadding : "0"}
76
- background={themeValues.headingBackgroundColor}
77
- onClick={isMobile && supportsTouch ? noop : toggleSection}
78
- onTouchEnd={isMobile && supportsTouch ? toggleSection : noop}
79
- key="header"
80
- hoverStyles={`cursor: pointer;`}
81
- tabIndex="0"
82
- onKeyDown={handleKeyDown}
83
- extraStyles={`z-index: 25;`}
84
- >
85
- <Cluster justify="space-between" align="center">
86
- {customTitle ? (
87
- <Box width="calc(100% - 36px)" padding="0px">
88
- {title}
89
- </Box>
90
- ) : (
91
- <Heading
92
- variant="h6"
93
- weight={FONT_WEIGHT_SEMIBOLD}
94
- color={themeValues.titleColor}
95
- as="h6"
79
+ <Box padding="0" role="heading" aria-label={label} aria-level={3}>
80
+ <Box
81
+ padding={customPadding ? customPadding : "0"}
82
+ background={themeValues.headingBackgroundColor}
83
+ onClick={isMobile && supportsTouch ? noop : toggleSection}
84
+ onTouchEnd={isMobile && supportsTouch ? toggleSection : noop}
85
+ key="header"
86
+ hoverStyles={`cursor: pointer;`}
87
+ tabIndex="0"
88
+ onKeyDown={handleKeyDown}
89
+ extraStyles={`z-index: 25;`}
90
+ role="button"
91
+ aria-expanded={isOpen.toString()}
92
+ aria-controls={id}
93
+ id={`${id}-button`}
94
+ >
95
+ <Cluster justify="space-between" align="center">
96
+ {customTitle ? (
97
+ <Box width="calc(100% - 36px)" padding="0px">
98
+ {title}
99
+ </Box>
100
+ ) : (
101
+ <Title
102
+ weight={FONT_WEIGHT_SEMIBOLD}
103
+ color={themeValues.titleColor}
104
+ as="h6"
105
+ variant="small"
106
+ >
107
+ {title}
108
+ </Title>
109
+ )}
110
+ <Motion
111
+ variants={icon}
112
+ extraStyles={`display: flex; align-items: center;`}
96
113
  >
97
- {title}
98
- </Heading>
99
- )}
100
- <Motion
101
- variants={icon}
102
- extraStyles={`display: flex; align-items: center;`}
103
- >
104
- <ChevronIcon />
105
- </Motion>
106
- </Cluster>
114
+ <ChevronIcon />
115
+ </Motion>
116
+ </Cluster>
117
+ </Box>
107
118
  </Box>
108
119
  <AnimatePresence initial={false}>
109
120
  {isOpen && (
@@ -116,6 +127,9 @@ const CollapsibleSection = ({
116
127
  exit="closed"
117
128
  variants={wrapper}
118
129
  extraStyles={`transform-origin: 100% 0; overflow-y: hidden;`}
130
+ id={`${id}-content`}
131
+ role={"region"}
132
+ aria-labelledby={`${id}-button`}
119
133
  >
120
134
  {children}
121
135
  </Motion>
@@ -7,7 +7,7 @@ import { Box, Stack } from "../../atoms/layouts";
7
7
  import Placeholder from "../../atoms/placeholder";
8
8
  import ButtonWithAction from "../../atoms/button-with-action";
9
9
  import Text from "../../atoms/text";
10
- import Paragraph from "../../atoms/paragraph";
10
+ import Title from "../../atoms/title";
11
11
  import {
12
12
  STORM_GREY,
13
13
  BOSTON_BLUE,
@@ -35,115 +35,122 @@ const EditableList = ({
35
35
  modal: Modal,
36
36
  modalProps,
37
37
  autoPayMethods,
38
+ as = "p",
38
39
  qaPrefix
39
- }) => (
40
- <Box padding="0rem 0rem 1.5rem 0rem">
41
- <Stack childGap="0rem">
42
- {title !== "" && (
43
- <Box padding="0rem 0rem 0.5rem 0rem">
44
- <Paragraph
45
- variant="pL"
46
- weight={titleWeight}
47
- color={CHARADE_GREY}
48
- extraStyles="letter-spacing: 0.29px;"
49
- >
50
- {title}
51
- </Paragraph>
52
- </Box>
53
- )}
54
- <Box
55
- padding="0"
56
- borderRadius="4px"
57
- extraStyles={`box-shadow: 0px 2px 14px 0px rgb(246, 246, 249),
58
- 0px 3px 8px 0px rgb(202, 206, 216);`}
59
- >
60
- {items.map(item => {
61
- const [modalOpen, toggleModal] = useState(false);
62
- const expiredItem = item?.expirationStatus ?? ACTIVE;
63
- return (
64
- <EditableListItem
65
- listItemSize={
66
- !!item.id &&
67
- autoPayMethods?.some(methodID => methodID === item.id)
68
- ? "big"
69
- : listItemSize
70
- }
71
- key={item.id || item}
72
- disabled={expiredItem === EXPIRED}
40
+ }) => {
41
+ const addText = `Add a${
42
+ itemName[0].match(/[aieouAIEOU]/) ? "n" : ""
43
+ } ${itemName}`;
44
+ return (
45
+ <Box padding="0rem 0rem 1.5rem 0rem" as="section" aria-labelledby={`li`}>
46
+ <Stack childGap="0rem">
47
+ {title !== "" && (
48
+ <Box padding="0rem 0rem 0.5rem 0rem">
49
+ <Title
50
+ as={as}
51
+ weight={titleWeight}
52
+ color={CHARADE_GREY}
53
+ extraStyles="letter-spacing: 0.29px; font-size: 1.125rem;"
73
54
  >
74
- <Text variant="p" color={CHARADE_GREY}>
75
- {renderItem(item)}
76
- </Text>
77
- <EditableListItemControls>
78
- {item.isPrimary && (
79
- <Text
80
- variant="p"
81
- color={STORM_GREY}
82
- extraStyles={`font-style: italic;`}
83
- key={`Default ${itemName}`}
84
- >
85
- Default {itemName}
86
- </Text>
87
- )}
88
- {canRemove && (
89
- <Box
90
- padding="0 0.5rem"
91
- border="2px solid transparent"
92
- extraStyles={`:not(:first-child) { border-left: 2px solid ${BOSTON_BLUE};}`}
93
- dataQa={qaPrefix + " Remove"}
94
- key={`Remove ${item.id}`}
95
- >
96
- {useModal ? (
97
- <Modal
98
- item={{ ...item }}
99
- {...modalProps}
100
- modalOpen={modalOpen}
101
- toggleModal={toggleModal}
102
- />
103
- ) : (
55
+ {title}
56
+ </Title>
57
+ </Box>
58
+ )}
59
+ <Box
60
+ padding="0"
61
+ borderRadius="4px"
62
+ extraStyles={`box-shadow: 0px 2px 14px 0px rgb(246, 246, 249),
63
+ 0px 3px 8px 0px rgb(202, 206, 216);`}
64
+ >
65
+ {items.map(item => {
66
+ const [modalOpen, toggleModal] = useState(false);
67
+ const expiredItem = item?.expirationStatus ?? ACTIVE;
68
+ return (
69
+ <EditableListItem
70
+ listItemSize={
71
+ !!item.id &&
72
+ autoPayMethods?.some(methodID => methodID === item.id)
73
+ ? "big"
74
+ : listItemSize
75
+ }
76
+ key={item.id || item}
77
+ disabled={expiredItem === EXPIRED}
78
+ >
79
+ <Text variant="p" color={CHARADE_GREY}>
80
+ {renderItem(item)}
81
+ </Text>
82
+ <EditableListItemControls>
83
+ {item.isPrimary && (
84
+ <Text
85
+ variant="p"
86
+ color={STORM_GREY}
87
+ extraStyles={`font-style: italic;`}
88
+ key={`Default ${itemName}`}
89
+ >
90
+ Default {itemName}
91
+ </Text>
92
+ )}
93
+ {canRemove && (
94
+ <Box
95
+ padding="0 0.5rem"
96
+ border="2px solid transparent"
97
+ extraStyles={`:not(:first-child) { border-left: 2px solid ${BOSTON_BLUE};}`}
98
+ dataQa={qaPrefix + " Remove"}
99
+ key={`Remove ${item.id}`}
100
+ >
101
+ {useModal ? (
102
+ <Modal
103
+ item={{ ...item }}
104
+ {...modalProps}
105
+ modalOpen={modalOpen}
106
+ toggleModal={toggleModal}
107
+ />
108
+ ) : (
109
+ <ButtonWithAction
110
+ variant="smallGhost"
111
+ text="Remove"
112
+ action={() => removeItem(item.id)}
113
+ extraStyles={`min-width: 0;`}
114
+ aria-label={`Remove ${itemName}`}
115
+ />
116
+ )}
117
+ </Box>
118
+ )}
119
+ {canEdit && (
120
+ <Box
121
+ padding="0 0.5rem"
122
+ border="2px solid transparent"
123
+ extraStyles={`:not(:first-child) { border-left: 2px solid ${BOSTON_BLUE};}`}
124
+ dataQa={qaPrefix + " Edit"}
125
+ key={`Edit ${item.id}`}
126
+ >
104
127
  <ButtonWithAction
105
128
  variant="smallGhost"
106
- text="Remove"
107
- action={() => removeItem(item.id)}
129
+ text="Edit"
130
+ action={() => editItem(item.id)}
108
131
  extraStyles={`min-width: 0;`}
132
+ aria-label={`Edit ${itemName}`}
109
133
  />
110
- )}
111
- </Box>
112
- )}
113
- {canEdit && (
114
- <Box
115
- padding="0 0.5rem"
116
- border="2px solid transparent"
117
- extraStyles={`:not(:first-child) { border-left: 2px solid ${BOSTON_BLUE};}`}
118
- dataQa={qaPrefix + " Edit"}
119
- key={`Edit ${item.id}`}
120
- >
121
- <ButtonWithAction
122
- variant="smallGhost"
123
- text="Edit"
124
- action={() => editItem(item.id)}
125
- extraStyles={`min-width: 0;`}
126
- />
127
- </Box>
128
- )}
129
- </EditableListItemControls>
130
- </EditableListItem>
131
- );
132
- })}
133
- </Box>
134
- {canAdd && (!maxItems || items.length < maxItems) && (
135
- <Box padding={items.length === 0 ? "0" : "1rem 0 0"}>
136
- <Placeholder
137
- text={`Add a${
138
- itemName[0].match(/[aieouAIEOU]/) ? "n" : ""
139
- } ${itemName}`}
140
- action={addItem}
141
- dataQa={"Add " + qaPrefix}
142
- />
134
+ </Box>
135
+ )}
136
+ </EditableListItemControls>
137
+ </EditableListItem>
138
+ );
139
+ })}
143
140
  </Box>
144
- )}
145
- </Stack>
146
- </Box>
147
- );
141
+ {canAdd && (!maxItems || items.length < maxItems) && (
142
+ <Box padding={items.length === 0 ? "0" : "1rem 0 0"}>
143
+ <Placeholder
144
+ text={addText}
145
+ action={addItem}
146
+ dataQa={"Add " + qaPrefix}
147
+ aria-label={addText}
148
+ />
149
+ </Box>
150
+ )}
151
+ </Stack>
152
+ </Box>
153
+ );
154
+ };
148
155
 
149
156
  export default EditableList;
@@ -8,7 +8,14 @@ import Text from "../../atoms/text";
8
8
  import { FONT_WEIGHT_SEMIBOLD } from "../../../constants/style_constants";
9
9
 
10
10
  // this component needs some fix'n
11
- const HighlightTabRow = ({ tabs, highlightIndex, themeValues, isMobile }) => {
11
+ const HighlightTabRow = ({
12
+ tabs,
13
+ highlightIndex,
14
+ themeValues,
15
+ isMobile,
16
+ useOrderedList = true,
17
+ useUnorderedList = false
18
+ }) => {
12
19
  const itemsAfterIndex = tabs.slice(highlightIndex).length - 1;
13
20
  const itemsBeforeIndex = tabs.slice(0, highlightIndex).length;
14
21
  const boxesBefore =
@@ -35,6 +42,8 @@ const HighlightTabRow = ({ tabs, highlightIndex, themeValues, isMobile }) => {
35
42
  childWidth="11rem"
36
43
  justifyContent="space-evenly"
37
44
  disableScroll
45
+ useOrderedList={useOrderedList}
46
+ useUnorderedList={useUnorderedList}
38
47
  >
39
48
  {repeat(<Box />, boxesBefore)}
40
49
  {tabs.map((t, i) => (
@@ -46,6 +55,7 @@ const HighlightTabRow = ({ tabs, highlightIndex, themeValues, isMobile }) => {
46
55
  }
47
56
  borderWidthOverride="0 0 3px 0"
48
57
  extraStyles="text-align: center;"
58
+ as="li"
49
59
  >
50
60
  <Text
51
61
  variant="p"
@@ -2,8 +2,20 @@ import React, { Fragment, memo } from "react";
2
2
  import { themeComponent } from "../../../util/themeUtils";
3
3
  import { fallbackValues } from "./Module.theme";
4
4
  import Heading from "../../atoms/heading";
5
+ import Title from "../../atoms/title";
5
6
  import { Box } from "../../atoms/layouts";
6
7
 
8
+ /*
9
+ New (01/22) - updated <Module /> to use <Title /> atom
10
+ Because <Title /> decouples size from element, also gave <Module />
11
+ two new props: "as" and "fontSize"
12
+
13
+ When present, <Module /> will use those values to dictate element type and font size.
14
+
15
+ For backwards compatability, <Module /> still computes a themed font size and element type based on old
16
+ <Heading /> variants. If "fontSize" or "as" is undefined, <Module /> will use themed values.
17
+ */
18
+
7
19
  const Module = ({
8
20
  heading,
9
21
  spacing = "1rem",
@@ -11,34 +23,48 @@ const Module = ({
11
23
  spacingBottom = "2.5rem",
12
24
  themeValues,
13
25
  variant = "default",
26
+ fontSize,
27
+ as,
14
28
  children
15
- }) => (
16
- <Fragment>
17
- {heading && (
18
- <Heading
19
- variant={
20
- variant === "small" ? "h6" : variant === "default" ? "h5" : "h2"
21
- }
22
- weight={themeValues.fontWeight}
23
- color={themeValues.fontColor}
24
- margin={`${spacing} 0 ${themeValues.titleSpacing} 0`}
25
- textAlign={themeValues.textAlign}
26
- >
27
- {heading}
28
- </Heading>
29
- )}
30
- <Box padding={`0 0 ${spacingBottom}`}>
31
- <Box
32
- padding={padding}
33
- background={themeValues.backgroundColor}
34
- borderRadius={themeValues.borderRadius}
35
- boxShadow={themeValues.boxShadow}
36
- >
37
- {children}
29
+ }) => {
30
+ const themedFontSize =
31
+ variant === "small"
32
+ ? "1.25rem"
33
+ : variant === "default"
34
+ ? "1.375rem"
35
+ : "2rem";
36
+ const computedFontSize = fontSize || themedFontSize;
37
+ const themedElemType =
38
+ variant === "small" ? "h6" : variant === "default" ? "h5" : "h2";
39
+ const computedElemType = as || themedElemType;
40
+
41
+ return (
42
+ <Fragment>
43
+ {heading && (
44
+ <Title
45
+ weight={themeValues.fontWeight}
46
+ color={themeValues.fontColor}
47
+ margin={`${spacing} 0 ${themeValues.titleSpacing} 0`}
48
+ textAlign={themeValues.textAlign}
49
+ as={computedElemType}
50
+ extraStyles={`font-size: ${computedFontSize};`}
51
+ >
52
+ {heading}
53
+ </Title>
54
+ )}
55
+ <Box padding={`0 0 ${spacingBottom}`}>
56
+ <Box
57
+ padding={padding}
58
+ background={themeValues.backgroundColor}
59
+ borderRadius={themeValues.borderRadius}
60
+ boxShadow={themeValues.boxShadow}
61
+ >
62
+ {children}
63
+ </Box>
38
64
  </Box>
39
- </Box>
40
- </Fragment>
41
- );
65
+ </Fragment>
66
+ );
67
+ };
42
68
 
43
69
  export default memo(
44
70
  themeComponent(Module, "Module", fallbackValues, "default")
@@ -8,7 +8,7 @@ import CollapsibleSection from "../collapsible-section";
8
8
 
9
9
  import LabeledAmount from "../../atoms/labeled-amount";
10
10
  import LineItem from "../../atoms/line-item";
11
- import Heading from "../../atoms/heading";
11
+ import Title from "../../atoms/title";
12
12
  import SolidDivider from "../../atoms/solid-divider";
13
13
  import { FONT_WEIGHT_BOLD } from "../../../constants/style_constants";
14
14
 
@@ -51,6 +51,7 @@ const Collapsible = ({ content, title, supportsTouch, isOpen, setIsOpen }) => (
51
51
  initiallyOpen={false}
52
52
  toggleSection={() => setIsOpen(!isOpen)}
53
53
  customTitle={true}
54
+ name="Collapsed Payment Details"
54
55
  >
55
56
  <Motion
56
57
  variants={{
@@ -130,18 +131,33 @@ const PaymentDetails = ({
130
131
  ) : isCollapsible ? (
131
132
  <Box width="100%" padding="none">
132
133
  <Cluster justify="space-between" align="center">
133
- <Heading variant="h5" weight="700" as="h1">
134
+ <Title
135
+ weight={FONT_WEIGHT_BOLD}
136
+ as="h1"
137
+ extraStyles={`font-size: 1.375rem;`}
138
+ id="payment-details-title"
139
+ >
134
140
  {titleText}
135
- </Heading>
136
- <Heading variant="h5" weight="700" as="h1">
141
+ </Title>
142
+ <Title
143
+ weight={FONT_WEIGHT_BOLD}
144
+ as="p"
145
+ extraStyles={`font-size: 1.375rem;`}
146
+ >
137
147
  {displayCurrency(total)}
138
- </Heading>
148
+ </Title>
139
149
  </Cluster>
140
150
  </Box>
141
151
  ) : (
142
- <Heading as="h1" variant="h3" weight="700" margin="1rem 0 0 0">
152
+ <Title
153
+ as="h1"
154
+ weight={FONT_WEIGHT_BOLD}
155
+ margin="1rem 0 0 0"
156
+ extraStyles={`font-size: 1.75rem;`}
157
+ id="payment-details-title"
158
+ >
143
159
  {titleText}
144
- </Heading>
160
+ </Title>
145
161
  );
146
162
  return isCollapsible ? (
147
163
  <Collapsible