@thecb/components 11.10.1-beta.1 → 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.1",
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",
@@ -31,7 +42,15 @@ const Tooltip = ({
31
42
  arrowRight: "10px",
32
43
  arrowBottom: "-8px",
33
44
  arrowLeft: "auto"
34
- }
45
+ },
46
+ minWidth = "250px",
47
+ maxWidth = "300px",
48
+ height = "auto",
49
+ containerExtraStyles = "",
50
+ triggerExtraStyles = "",
51
+ triggerButtonVariant = "smallGhost",
52
+ contentExtraStyles = "",
53
+ contentBackgroundColor = WHITE
35
54
  }) => {
36
55
  const closeTimeoutRef = useRef(null);
37
56
  const [tooltipOpen, setTooltipOpen] = useState(false);
@@ -42,16 +61,23 @@ const Tooltip = ({
42
61
  TOOLTIP_THEME_SOURCE
43
62
  );
44
63
 
64
+ const {
65
+ borderColor,
66
+ popoverTriggerColor: tooltipTriggerColor,
67
+ hoverColor,
68
+ activeColor
69
+ } = themeValues;
70
+
45
71
  const { top, right, bottom, left } = contentPosition;
46
72
  const { arrowTop, arrowRight, arrowBottom, arrowLeft } = arrowPosition;
47
73
 
48
- const handleToggleTooltip = desiredState => {
49
- if (tooltipOpen !== desiredState) {
50
- setTooltipOpen(desiredState);
74
+ const handleToggleTooltip = desiredTooltipState => {
75
+ if (tooltipOpen !== desiredTooltipState) {
76
+ setTooltipOpen(desiredTooltipState);
51
77
  }
52
78
  };
53
79
 
54
- const handleKeyDown = e => {
80
+ const handleKeyboardEvent = e => {
55
81
  if (e.key === "Escape") {
56
82
  handleToggleTooltip(false);
57
83
  }
@@ -79,73 +105,80 @@ const Tooltip = ({
79
105
  };
80
106
  }, []);
81
107
 
82
- const renderTrigger = () => {
83
- if (hasCustomTrigger && children) {
84
- return React.cloneElement(React.Children.only(children), {
85
- "aria-describedby": tooltipID,
86
- onFocus: () => handleToggleTooltip(true),
87
- onBlur: () => handleToggleTooltip(false),
88
- onKeyDown: handleKeyDown
89
- });
90
- }
91
-
92
- 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
+ >
93
117
  <ButtonWithAction
94
- action={noop}
95
118
  aria-describedby={tooltipID}
96
- onKeyDown={handleKeyDown}
119
+ onKeyDown={handleKeyboardEvent}
97
120
  variant={triggerButtonVariant}
98
121
  onFocus={() => handleToggleTooltip(true)}
99
122
  onBlur={() => handleToggleTooltip(false)}
100
123
  onTouchStart={() => handleToggleTooltip(true)}
101
- data-qa={`tooltip-trigger-${tooltipID}`}
102
- text={triggerText}
103
- extraStyles={`
104
- color: ${themeValues.linkColor};
105
- &:hover { color: ${themeValues.hoverColor}; text-decoration: none;}
106
- &:active, &:focus { color: ${themeValues.activeColor};text-decoration: none;}
107
- button, span, &:hover span { text-decoration: none; }
108
- `}
109
- />
110
- );
111
- };
112
-
113
- return (
114
- <Box
115
- padding="0"
116
- extraStyles={`position: relative; ${containerExtraStyles}`}
117
- onMouseEnter={handleMouseEnter}
118
- onMouseLeave={handleMouseLeave}
119
- data-qa={`${tooltipID}-container`}
120
- >
121
- {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>
122
159
  <Box
123
160
  role="tooltip"
124
161
  id={tooltipID}
125
162
  aria-hidden={!tooltipOpen}
126
- background={themeValues.borderColor}
163
+ background={contentBackgroundColor}
127
164
  data-qa="tooltip-contents"
128
165
  extraStyles={`
129
166
  position: absolute;
130
167
  display: ${tooltipOpen ? "block" : "none"};
131
- top: ${top};
168
+ top: ${top};
132
169
  right: ${right};
133
170
  bottom: ${bottom};
134
171
  left: ${left};
135
172
  height: ${height};
136
173
  ${contentExtraStyles}
137
- `}
138
- boxShadow="0px 2px 14px 0px rgb(246, 246, 249), 0px 3px 8px 0px rgb(202, 206, 216)"
139
- 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`}
140
177
  borderRadius="4px"
141
178
  minWidth={minWidth}
142
179
  maxWidth={maxWidth}
143
180
  >
144
- {typeof content === "string" ? (
145
- <Text color={themeValues.linkColor}>{content}</Text>
146
- ) : (
147
- content
148
- )}
181
+ <Paragraph>{tooltipContent}</Paragraph>
149
182
  <Box
150
183
  padding="0"
151
184
  extraStyles={`
@@ -153,7 +186,7 @@ const Tooltip = ({
153
186
  content: "";
154
187
  width: 0;
155
188
  height: 0;
156
- ${arrowBorder(themeValues.borderColor, arrowDirection, "8px")};
189
+ ${arrowBorder(borderColor, arrowDirection, "8px")};
157
190
  filter: drop-shadow(2px 8px 14px black);
158
191
  bottom: ${arrowBottom};
159
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