@thecb/components 11.10.0-beta.2 → 11.10.1-beta.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.0-beta.2",
3
+ "version": "11.10.1-beta.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,17 +34,9 @@ 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
-
44
37
  const CardType = ({ type, size = "small" }) => {
45
- const normalizedType = normalizeType(type);
46
38
  const { label, [size]: IconComponent } =
47
- cardBrands[normalizedType] || cardBrands.default;
39
+ cardBrands[type] || cardBrands.default;
48
40
  return (
49
41
  <span role="img" aria-label={label}>
50
42
  <IconComponent />
@@ -1,5 +1,5 @@
1
- import React, { Fragment, useContext } from "react";
2
- import styled, { ThemeContext } from "styled-components";
1
+ import React, { Fragment } from "react";
2
+ import styled 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: 36px;
18
+ width: 30px;
19
19
  height: auto;
20
20
  display: flex;
21
21
  `;
@@ -27,18 +27,16 @@ const FormattedCreditCard = ({
27
27
  expireDate,
28
28
  expirationStatus,
29
29
  themeValues
30
- }) => {
31
- const { isMobile } = useContext(ThemeContext);
32
- return (
30
+ }) => (
33
31
  <CreditCardWrapper>
34
32
  <CCIconWrapper>
35
- <CardType type={type} size={isMobile ? "small" : "large"} />
33
+ <CardType type={type} />
36
34
  </CCIconWrapper>
37
35
  <Stack childGap="0">
38
36
  <Box padding="0">
39
37
  <Text
40
38
  variant="p"
41
- padding="0"
39
+ padding="0 0 0 8px"
42
40
  color={themeValues.textColor}
43
41
  textAlign="left"
44
42
  extraStyles={`display: inline-block;`}
@@ -60,8 +58,7 @@ const FormattedCreditCard = ({
60
58
  )}
61
59
  </Stack>
62
60
  </CreditCardWrapper>
63
- );
64
- };
61
+ );
65
62
  export default themeComponent(
66
63
  FormattedCreditCard,
67
64
  "FormattedCreditCard",
@@ -8,8 +8,6 @@ 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"
13
11
  >
14
12
  <g clipPath="url(#clip0_3693_1095)">
15
13
  <g clipPath="url(#clip1_3693_1095)">
@@ -8,8 +8,6 @@ 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"
13
11
  >
14
12
  <g clipPath="url(#clip0_3818_267)">
15
13
  <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="Credit card"
12
+ aria-label="Card Payment"
13
13
  >
14
14
  <rect width="36" height="24" rx="2" fill="#15749D" />
15
15
  <path
@@ -8,8 +8,6 @@ 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"
13
11
  >
14
12
  <rect width="24" height="16" rx="2" fill="#15749D" />
15
13
  <path
@@ -8,8 +8,6 @@ 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"
13
11
  >
14
12
  <rect width="24" height="16" rx="1" fill="#F6F6F9" />
15
13
  <path
@@ -8,8 +8,6 @@ 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"
13
11
  >
14
12
  <path
15
13
  fillRule="evenodd"
@@ -69,8 +69,8 @@ const InnerRadioSection = ({
69
69
  `;
70
70
 
71
71
  const RightIcon = styled.img`
72
- height: ${({ isMobile }) => (isMobile ? "16px" : "24px")};
73
- width: ${({ isMobile }) => (isMobile ? "24px" : "36px")};
72
+ height: ${({ isMobile }) => (isMobile ? "14px" : "18px")};
73
+ width: ${({ isMobile }) => (isMobile ? "22px" : "28px")};
74
74
  ${({ fade }) => fade && "opacity: 0.4;"}
75
75
  transition: opacity 0.3s ease;
76
76
  `;
@@ -2,34 +2,23 @@ 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";
6
5
  import { Box } from "../../atoms/layouts";
7
6
  import ButtonWithAction from "../../atoms/button-with-action";
8
7
  import { noop, arrowBorder } from "../../../util/general";
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
- };
8
+ import { TOOLTIP_THEME_SOURCE, fallbackValues } from "./Tooltip.theme";
25
9
 
26
10
  const Tooltip = ({
27
11
  tooltipID,
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.",
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",
33
22
  contentPosition = {
34
23
  top: "-110px",
35
24
  right: "auto",
@@ -42,15 +31,7 @@ const Tooltip = ({
42
31
  arrowRight: "10px",
43
32
  arrowBottom: "-8px",
44
33
  arrowLeft: "auto"
45
- },
46
- minWidth = "250px",
47
- maxWidth = "300px",
48
- height = "auto",
49
- containerExtraStyles = "",
50
- triggerExtraStyles = "",
51
- triggerButtonVariant = "smallGhost",
52
- contentExtraStyles = "",
53
- contentBackgroundColor = WHITE
34
+ }
54
35
  }) => {
55
36
  const closeTimeoutRef = useRef(null);
56
37
  const [tooltipOpen, setTooltipOpen] = useState(false);
@@ -61,23 +42,16 @@ const Tooltip = ({
61
42
  TOOLTIP_THEME_SOURCE
62
43
  );
63
44
 
64
- const {
65
- borderColor,
66
- popoverTriggerColor: tooltipTriggerColor,
67
- hoverColor,
68
- activeColor
69
- } = themeValues;
70
-
71
45
  const { top, right, bottom, left } = contentPosition;
72
46
  const { arrowTop, arrowRight, arrowBottom, arrowLeft } = arrowPosition;
73
47
 
74
- const handleToggleTooltip = desiredTooltipState => {
75
- if (tooltipOpen !== desiredTooltipState) {
76
- setTooltipOpen(desiredTooltipState);
48
+ const handleToggleTooltip = desiredState => {
49
+ if (tooltipOpen !== desiredState) {
50
+ setTooltipOpen(desiredState);
77
51
  }
78
52
  };
79
53
 
80
- const handleKeyboardEvent = e => {
54
+ const handleKeyDown = e => {
81
55
  if (e.key === "Escape") {
82
56
  handleToggleTooltip(false);
83
57
  }
@@ -105,80 +79,73 @@ const Tooltip = ({
105
79
  };
106
80
  }, []);
107
81
 
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
- >
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 (
117
93
  <ButtonWithAction
94
+ action={noop}
118
95
  aria-describedby={tooltipID}
119
- onKeyDown={handleKeyboardEvent}
96
+ onKeyDown={handleKeyDown}
120
97
  variant={triggerButtonVariant}
121
98
  onFocus={() => handleToggleTooltip(true)}
122
99
  onBlur={() => handleToggleTooltip(false)}
123
100
  onTouchStart={() => handleToggleTooltip(true)}
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>
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()}
159
122
  <Box
160
123
  role="tooltip"
161
124
  id={tooltipID}
162
125
  aria-hidden={!tooltipOpen}
163
- background={contentBackgroundColor}
126
+ background={themeValues.borderColor}
164
127
  data-qa="tooltip-contents"
165
128
  extraStyles={`
166
129
  position: absolute;
167
130
  display: ${tooltipOpen ? "block" : "none"};
168
- top: ${top};
131
+ top: ${top};
169
132
  right: ${right};
170
133
  bottom: ${bottom};
171
134
  left: ${left};
172
135
  height: ${height};
173
136
  ${contentExtraStyles}
174
- `}
175
- boxShadow={`0px 2px 14px 0px rgb(246, 246, 249), 0px 3px 8px 0px rgb(202, 206, 216)`}
176
- border={`1px solid transparent`}
137
+ `}
138
+ boxShadow="0px 2px 14px 0px rgb(246, 246, 249), 0px 3px 8px 0px rgb(202, 206, 216)"
139
+ border="1px solid transparent"
177
140
  borderRadius="4px"
178
141
  minWidth={minWidth}
179
142
  maxWidth={maxWidth}
180
143
  >
181
- <Paragraph>{tooltipContent}</Paragraph>
144
+ {typeof content === "string" ? (
145
+ <Text color={themeValues.linkColor}>{content}</Text>
146
+ ) : (
147
+ content
148
+ )}
182
149
  <Box
183
150
  padding="0"
184
151
  extraStyles={`
@@ -186,7 +153,7 @@ const Tooltip = ({
186
153
  content: "";
187
154
  width: 0;
188
155
  height: 0;
189
- ${arrowBorder(borderColor, arrowDirection, "8px")};
156
+ ${arrowBorder(themeValues.borderColor, arrowDirection, "8px")};
190
157
  filter: drop-shadow(2px 8px 14px black);
191
158
  bottom: ${arrowBottom};
192
159
  right: ${arrowRight};
@@ -6,9 +6,19 @@ import * as TooltipStories from './Tooltip.stories.js';
6
6
 
7
7
  <Title />
8
8
 
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.
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.
10
10
 
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.
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.
12
22
 
13
23
  <Controls />
14
24