@thecb/components 11.10.1-beta.2 → 11.10.1

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": "11.10.1-beta.2",
3
+ "version": "11.10.1",
4
4
  "description": "Common lib for CityBase react components",
5
5
  "main": "dist/index.cjs.js",
6
6
  "typings": "dist/index.d.ts",
@@ -34,11 +34,19 @@ const cardBrands = {
34
34
  }
35
35
  };
36
36
 
37
+ const normalizeType = type => {
38
+ if (!type) return undefined;
39
+ const lower = type.toLowerCase();
40
+ if (lower === "mastercard") return "master_card";
41
+ return lower;
42
+ };
43
+
37
44
  const CardType = ({ type, size = "small" }) => {
45
+ const normalizedType = normalizeType(type);
38
46
  const { label, [size]: IconComponent } =
39
- cardBrands[type] || cardBrands.default;
47
+ cardBrands[normalizedType] || cardBrands.default;
40
48
  return (
41
- <span role="img" aria-label={label}>
49
+ <span>
42
50
  <IconComponent />
43
51
  </span>
44
52
  );
@@ -13,7 +13,6 @@ import "core-js/proposals/relative-indexing-method";
13
13
 
14
14
  import {
15
15
  ERROR_COLOR,
16
- GREY_CHATEAU,
17
16
  MINESHAFT_GREY,
18
17
  STORM_GREY,
19
18
  WHITE
@@ -35,7 +34,7 @@ const IconWrapper = styled.div`
35
34
 
36
35
  const DropdownContentWrapper = styled.div`
37
36
  transform-origin: 0 0;
38
- border: 1px solid ${GREY_CHATEAU};
37
+ border: 1px solid ${STORM_GREY};
39
38
  border-radius: 2px;
40
39
  background-color: ${WHITE};
41
40
  padding: 8px 0 8px;
@@ -349,7 +348,7 @@ const Dropdown = ({
349
348
  ? ERROR_COLOR
350
349
  : isOpen
351
350
  ? themeValues.selectedColor
352
- : GREY_CHATEAU
351
+ : STORM_GREY
353
352
  }
354
353
  dataQa={placeholder}
355
354
  extraStyles={
@@ -2,6 +2,7 @@ import React from "react";
2
2
 
3
3
  const DropdownIcon = () => (
4
4
  <svg
5
+ aria-hidden="true"
5
6
  version="1.2"
6
7
  xmlns="http://www.w3.org/2000/svg"
7
8
  overflow="visible"
@@ -7,6 +7,7 @@ const DropdownIconV2 = ({
7
7
  ...props
8
8
  }) => (
9
9
  <svg
10
+ aria-hidden="true"
10
11
  width={width}
11
12
  height={height}
12
13
  viewBox={`0 0 ${width} ${height}`}
@@ -148,7 +148,7 @@ const FormInput = ({
148
148
  <Stack childGap="0.25rem">
149
149
  <Box padding="0">
150
150
  {helperModal ? (
151
- <Cluster justify="space-between" align="center">
151
+ <Cluster justify="space-between" align="center" overflow>
152
152
  {labelDisplayOverride ? (
153
153
  labelDisplayOverride
154
154
  ) : (
@@ -1,5 +1,5 @@
1
- import React, { Fragment } from "react";
2
- import styled from "styled-components";
1
+ import React, { Fragment, useContext } from "react";
2
+ import styled, { ThemeContext } from "styled-components";
3
3
  import CardType from "../card-type";
4
4
  import Text from "../text";
5
5
  import { Box, Stack } from "../layouts";
@@ -15,7 +15,7 @@ export const CreditCardWrapper = styled.div`
15
15
 
16
16
  export const CCIconWrapper = styled.div`
17
17
  margin-right: 16px;
18
- width: 30px;
18
+ width: 36px;
19
19
  height: auto;
20
20
  display: flex;
21
21
  `;
@@ -27,16 +27,18 @@ const FormattedCreditCard = ({
27
27
  expireDate,
28
28
  expirationStatus,
29
29
  themeValues
30
- }) => (
30
+ }) => {
31
+ const { isMobile } = useContext(ThemeContext);
32
+ return (
31
33
  <CreditCardWrapper>
32
34
  <CCIconWrapper>
33
- <CardType type={type} />
35
+ <CardType type={type} size={isMobile ? "small" : "large"} />
34
36
  </CCIconWrapper>
35
37
  <Stack childGap="0">
36
38
  <Box padding="0">
37
39
  <Text
38
40
  variant="p"
39
- padding="0 0 0 8px"
41
+ padding="0"
40
42
  color={themeValues.textColor}
41
43
  textAlign="left"
42
44
  extraStyles={`display: inline-block;`}
@@ -58,7 +60,8 @@ const FormattedCreditCard = ({
58
60
  )}
59
61
  </Stack>
60
62
  </CreditCardWrapper>
61
- );
63
+ );
64
+ };
62
65
  export default themeComponent(
63
66
  FormattedCreditCard,
64
67
  "FormattedCreditCard",
@@ -8,6 +8,8 @@ const AmExSmallIcon = () => {
8
8
  viewBox="0 0 24 16"
9
9
  fill="none"
10
10
  xmlns="http://www.w3.org/2000/svg"
11
+ role="img"
12
+ aria-label="American Express"
11
13
  >
12
14
  <g clipPath="url(#clip0_3693_1095)">
13
15
  <g clipPath="url(#clip1_3693_1095)">
@@ -8,6 +8,8 @@ const DiscoverSmallIcon = () => {
8
8
  viewBox="0 0 24 16"
9
9
  fill="none"
10
10
  xmlns="http://www.w3.org/2000/svg"
11
+ role="img"
12
+ aria-label="Discover"
11
13
  >
12
14
  <g clipPath="url(#clip0_3818_267)">
13
15
  <path
@@ -9,7 +9,7 @@ const GenericCardLarge = () => {
9
9
  fill="none"
10
10
  xmlns="http://www.w3.org/2000/svg"
11
11
  role="img"
12
- aria-label="Card Payment"
12
+ aria-label="Credit card"
13
13
  >
14
14
  <rect width="36" height="24" rx="2" fill="#15749D" />
15
15
  <path
@@ -8,6 +8,8 @@ const GenericSmallIcon = () => {
8
8
  viewBox="0 0 24 16"
9
9
  fill="none"
10
10
  xmlns="http://www.w3.org/2000/svg"
11
+ role="img"
12
+ aria-label="Credit card"
11
13
  >
12
14
  <rect width="24" height="16" rx="2" fill="#15749D" />
13
15
  <path
@@ -8,6 +8,8 @@ const MasterCardSmallIcon = () => {
8
8
  viewBox="0 0 24 16"
9
9
  fill="none"
10
10
  xmlns="http://www.w3.org/2000/svg"
11
+ role="img"
12
+ aria-label="Mastercard"
11
13
  >
12
14
  <rect width="24" height="16" rx="1" fill="#F6F6F9" />
13
15
  <path
@@ -8,6 +8,8 @@ const VisaSmallIcon = () => {
8
8
  viewBox="0 0 24 16"
9
9
  fill="none"
10
10
  xmlns="http://www.w3.org/2000/svg"
11
+ role="img"
12
+ aria-label="Visa"
11
13
  >
12
14
  <path
13
15
  fillRule="evenodd"
@@ -69,8 +69,8 @@ const InnerRadioSection = ({
69
69
  `;
70
70
 
71
71
  const RightIcon = styled.img`
72
- height: ${({ isMobile }) => (isMobile ? "14px" : "18px")};
73
- width: ${({ isMobile }) => (isMobile ? "22px" : "28px")};
72
+ height: ${({ isMobile }) => (isMobile ? "16px" : "24px")};
73
+ width: ${({ isMobile }) => (isMobile ? "24px" : "36px")};
74
74
  ${({ fade }) => fade && "opacity: 0.4;"}
75
75
  transition: opacity 0.3s ease;
76
76
  `;
@@ -2,23 +2,34 @@ import React, { useContext, useEffect, useRef, useState } from "react";
2
2
  import { createThemeValues } from "../../../util/themeUtils";
3
3
  import { ThemeContext } from "styled-components";
4
4
  import Text from "../../atoms/text";
5
+ import Paragraph from "../../atoms/paragraph";
5
6
  import { Box } from "../../atoms/layouts";
6
7
  import ButtonWithAction from "../../atoms/button-with-action";
7
8
  import { noop, arrowBorder } from "../../../util/general";
8
- import { TOOLTIP_THEME_SOURCE, fallbackValues } from "./Tooltip.theme";
9
+ import WarningIconXS from "../../atoms/icons/WarningIconXS";
10
+ import {
11
+ MATISSE_BLUE,
12
+ PEACOCK_BLUE,
13
+ SAPPHIRE_BLUE,
14
+ WHITE
15
+ } from "../../../constants/colors";
16
+
17
+ const TOOLTIP_THEME_SOURCE = "Popover";
18
+
19
+ const fallbackValues = {
20
+ hoverColor: SAPPHIRE_BLUE,
21
+ activeColor: PEACOCK_BLUE,
22
+ popoverTriggerColor: MATISSE_BLUE,
23
+ borderColor: `rgba(255, 255, 255, 0.85)`
24
+ };
9
25
 
10
26
  const Tooltip = ({
11
27
  tooltipID,
12
- children,
13
- hasCustomTrigger = false,
14
- triggerText = "",
15
- containerExtraStyles = "",
16
- triggerButtonVariant = "smallGhost",
17
- content = "",
18
- contentExtraStyles = "",
19
- minWidth = "250px",
20
- maxWidth = "100%",
21
- height = "auto",
28
+ hasIconTrigger = false,
29
+ IconTrigger = WarningIconXS,
30
+ iconHelpText = "Open the tooltip",
31
+ triggerText = "Open the tooltip",
32
+ tooltipContent = "The contents of the tooltip go here.",
22
33
  contentPosition = {
23
34
  top: "-110px",
24
35
  right: "auto",
@@ -32,7 +43,14 @@ const Tooltip = ({
32
43
  arrowBottom: "-8px",
33
44
  arrowLeft: "auto"
34
45
  },
35
- arrowColor
46
+ minWidth = "250px",
47
+ maxWidth = "300px",
48
+ height = "auto",
49
+ containerExtraStyles = "",
50
+ triggerExtraStyles = "",
51
+ triggerButtonVariant = "smallGhost",
52
+ contentExtraStyles = "",
53
+ contentBackgroundColor = WHITE
36
54
  }) => {
37
55
  const closeTimeoutRef = useRef(null);
38
56
  const [tooltipOpen, setTooltipOpen] = useState(false);
@@ -43,16 +61,23 @@ const Tooltip = ({
43
61
  TOOLTIP_THEME_SOURCE
44
62
  );
45
63
 
64
+ const {
65
+ borderColor,
66
+ popoverTriggerColor: tooltipTriggerColor,
67
+ hoverColor,
68
+ activeColor
69
+ } = themeValues;
70
+
46
71
  const { top, right, bottom, left } = contentPosition;
47
72
  const { arrowTop, arrowRight, arrowBottom, arrowLeft } = arrowPosition;
48
73
 
49
- const handleToggleTooltip = desiredState => {
50
- if (tooltipOpen !== desiredState) {
51
- setTooltipOpen(desiredState);
74
+ const handleToggleTooltip = desiredTooltipState => {
75
+ if (tooltipOpen !== desiredTooltipState) {
76
+ setTooltipOpen(desiredTooltipState);
52
77
  }
53
78
  };
54
79
 
55
- const handleKeyDown = e => {
80
+ const handleKeyboardEvent = e => {
56
81
  if (e.key === "Escape") {
57
82
  handleToggleTooltip(false);
58
83
  }
@@ -80,73 +105,80 @@ const Tooltip = ({
80
105
  };
81
106
  }, []);
82
107
 
83
- const renderTrigger = () => {
84
- if (hasCustomTrigger && children) {
85
- return React.cloneElement(React.Children.only(children), {
86
- "aria-describedby": tooltipID,
87
- onFocus: () => handleToggleTooltip(true),
88
- onBlur: () => handleToggleTooltip(false),
89
- onKeyDown: handleKeyDown
90
- });
91
- }
92
-
93
- return (
108
+ return (
109
+ <Box
110
+ ref={closeTimeoutRef}
111
+ padding="0"
112
+ extraStyles={`position: relative; ${containerExtraStyles}`}
113
+ onMouseEnter={() => handleMouseEnter(true)}
114
+ onMouseLeave={() => handleMouseLeave(false)}
115
+ data-qa="tooltip-container"
116
+ >
94
117
  <ButtonWithAction
95
- action={noop}
96
118
  aria-describedby={tooltipID}
97
- onKeyDown={handleKeyDown}
119
+ onKeyDown={handleKeyboardEvent}
98
120
  variant={triggerButtonVariant}
99
121
  onFocus={() => handleToggleTooltip(true)}
100
122
  onBlur={() => handleToggleTooltip(false)}
101
123
  onTouchStart={() => handleToggleTooltip(true)}
102
- data-qa={`tooltip-trigger-${tooltipID}`}
103
- text={triggerText}
104
- extraStyles={`
105
- color: ${themeValues.linkColor};
106
- &:hover { color: ${themeValues.hoverColor}; text-decoration: none;}
107
- &:active, &:focus { color: ${themeValues.activeColor};text-decoration: none;}
108
- button, span, &:hover span { text-decoration: none; }
109
- `}
110
- />
111
- );
112
- };
113
-
114
- return (
115
- <Box
116
- padding="0"
117
- extraStyles={`position: relative; ${containerExtraStyles}`}
118
- onMouseEnter={handleMouseEnter}
119
- onMouseLeave={handleMouseLeave}
120
- data-qa={`${tooltipID}-container`}
121
- >
122
- {renderTrigger()}
124
+ data-qa="tooltip-trigger"
125
+ contentOverride={true}
126
+ >
127
+ {hasIconTrigger === true && (
128
+ <>
129
+ <Box aria-label="Open tooltip">
130
+ <IconTrigger color={tooltipTriggerColor} />
131
+ </Box>
132
+ <Box padding="0" srOnly>
133
+ <Text>{iconHelpText}</Text>
134
+ </Box>
135
+ </>
136
+ )}
137
+ {hasIconTrigger === false && (
138
+ <Text
139
+ color={tooltipTriggerColor}
140
+ extraStyles={`
141
+ color: ${tooltipTriggerColor};
142
+ &:visited {
143
+ color: ${tooltipTriggerColor};
144
+ }
145
+ &:hover {
146
+ color: ${hoverColor};
147
+ }
148
+ &:active,
149
+ &:focus {
150
+ color: ${activeColor};
151
+ }
152
+ ${triggerExtraStyles};
153
+ `}
154
+ >
155
+ {triggerText}
156
+ </Text>
157
+ )}
158
+ </ButtonWithAction>
123
159
  <Box
124
160
  role="tooltip"
125
161
  id={tooltipID}
126
162
  aria-hidden={!tooltipOpen}
127
- background={themeValues.borderColor}
163
+ background={contentBackgroundColor}
128
164
  data-qa="tooltip-contents"
129
165
  extraStyles={`
130
166
  position: absolute;
131
167
  display: ${tooltipOpen ? "block" : "none"};
132
- top: ${top};
168
+ top: ${top};
133
169
  right: ${right};
134
170
  bottom: ${bottom};
135
171
  left: ${left};
136
172
  height: ${height};
137
173
  ${contentExtraStyles}
138
- `}
139
- boxShadow="0px 2px 14px 0px rgb(246, 246, 249), 0px 3px 8px 0px rgb(202, 206, 216)"
140
- border="1px solid transparent"
174
+ `}
175
+ boxShadow={`0px 2px 14px 0px rgb(246, 246, 249), 0px 3px 8px 0px rgb(202, 206, 216)`}
176
+ border={`1px solid transparent`}
141
177
  borderRadius="4px"
142
178
  minWidth={minWidth}
143
179
  maxWidth={maxWidth}
144
180
  >
145
- {typeof content === "string" ? (
146
- <Text color={themeValues.linkColor}>{content}</Text>
147
- ) : (
148
- content
149
- )}
181
+ <Paragraph>{tooltipContent}</Paragraph>
150
182
  <Box
151
183
  padding="0"
152
184
  extraStyles={`
@@ -154,11 +186,7 @@ const Tooltip = ({
154
186
  content: "";
155
187
  width: 0;
156
188
  height: 0;
157
- ${arrowBorder(
158
- arrowColor || themeValues.borderColor,
159
- arrowDirection,
160
- "8px"
161
- )};
189
+ ${arrowBorder(borderColor, arrowDirection, "8px")};
162
190
  filter: drop-shadow(2px 8px 14px black);
163
191
  bottom: ${arrowBottom};
164
192
  right: ${arrowRight};
@@ -6,19 +6,9 @@ import * as TooltipStories from './Tooltip.stories.js';
6
6
 
7
7
  <Title />
8
8
 
9
- Tooltip is an accessible tooltip component implementing the [WAI-ARIA Tooltip Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/tooltip/). It displays supplemental information when the user hovers over or focuses on a trigger element.
9
+ The Tooltip is a fully accessible tooltip widget that displays additional information when a user hovers over or focuses on a specified trigger element. The trigger can either be text supplied using the `triggerText` prop, or a custom Icon component supplied using the `IconTrigger` prop. The trigger is rendered as a `ButtonWithAction` with the `smallGhost` variant.
10
10
 
11
- The trigger can be either:
12
- - **Custom children**: Pass any single React element as `children`. It will receive `aria-describedby`, focus, blur, and keyboard handlers automatically.
13
- - **Default button**: When no `children` are provided, a `ButtonWithAction` is rendered using the `triggerText` prop.
14
-
15
- The tooltip content (`content` prop) accepts a plain string or a React node for rich content. It is styled similarly to the Popover component, including an arrow pointing toward the trigger.
16
-
17
- ### Accessibility
18
- - The trigger element has `aria-describedby` referencing the tooltip.
19
- - The tooltip container has `role="tooltip"`.
20
- - Pressing **Escape** dismisses the tooltip.
21
- - Focus stays on the trigger while the tooltip is displayed.
11
+ The Tooltip uses the WAI-ARIA tooltip pattern (`role="tooltip"` and `aria-describedby`) for accessibility. It can be positioned anywhere around the trigger element using the position props. Content and style of the tooltip are customizable.
22
12
 
23
13
  <Controls />
24
14