@thecb/components 11.8.1-beta.0 → 11.8.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.8.1-beta.0",
3
+ "version": "11.8.1",
4
4
  "description": "Common lib for CityBase react components",
5
5
  "main": "dist/index.cjs.js",
6
6
  "typings": "dist/index.d.ts",
@@ -1,6 +1,6 @@
1
1
  import React from "react";
2
2
 
3
- const IconAdd = ({ strokeWidth = 1 }) => (
3
+ const IconAdd = ({ stroke = "none", strokeWidth = 1 }) => (
4
4
  <svg
5
5
  xmlns="http://www.w3.org/2000/svg"
6
6
  xmlnsXlink="http://www.w3.org/1999/xlink"
@@ -15,7 +15,7 @@ const IconAdd = ({ strokeWidth = 1 }) => (
15
15
  d="M7.91666623 4.78508747L4.78508747 4.78508747 4.78508747 7.91666623 3.74122788 7.91666623 3.74122788 4.78508747 0.609649123 4.78508747 0.609649123 3.74122788 3.74122788 3.74122788 3.74122788 0.609649123 4.78508747 0.609649123 4.78508747 3.74122788 7.91666623 3.74122788z"
16
16
  ></path>
17
17
  </defs>
18
- <g fill="none" fillRule="evenodd" stroke="none" strokeWidth={strokeWidth}>
18
+ <g fill="none" fillRule="evenodd" stroke={stroke} strokeWidth={strokeWidth}>
19
19
  <g transform="translate(-407 -563)">
20
20
  <g transform="translate(408 562)">
21
21
  <g transform="translate(0 2)">
@@ -13,7 +13,6 @@ export { default as SortableTableHeading } from "./sortable-table-heading";
13
13
  export { default as CountryDropdown } from "./country-dropdown";
14
14
  export { default as Detail } from "./detail";
15
15
  export { default as DisplayBox } from "./display-box";
16
- export { default as DisplayCard } from "./display-card";
17
16
  export { default as Dropdown } from "./dropdown";
18
17
  export * from "./form-layouts";
19
18
  export { default as FormSelect } from "./form-select";
@@ -16,7 +16,7 @@ import {
16
16
  import {
17
17
  MINESHAFT_GREY,
18
18
  ATHENS_GREY,
19
- FOREST_GREEN,
19
+ SEA_GREEN,
20
20
  RAZZMATAZZ_RED
21
21
  } from "../../../constants/colors";
22
22
 
@@ -31,8 +31,8 @@ const PasswordRequirements = ({ password, isMobile }) => {
31
31
  color: RAZZMATAZZ_RED
32
32
  },
33
33
  [INPUT_STATE_VALID]: {
34
- icon: <IconValid margin="0 0.5rem 0 0" />,
35
- color: FOREST_GREEN
34
+ icon: <IconValid margin="0 0.5rem 0 0" bgFill={SEA_GREEN} />,
35
+ color: SEA_GREEN
36
36
  }
37
37
  };
38
38
 
@@ -47,3 +47,4 @@ export { default as MultipleSelectFilter } from "./multiple-select-filter";
47
47
  export { default as ContactCard } from "./contact-card";
48
48
  export { default as HeroImage } from "./hero-image";
49
49
  export { default as TurnstileWidget } from "./turnstile-widget/TurnstileWidget";
50
+ export { default as Tooltip } from "./tooltip";
@@ -5,24 +5,10 @@ import Paragraph from "../../atoms/paragraph";
5
5
  import { Box } from "../../atoms/layouts";
6
6
  import ButtonWithAction from "../../atoms/button-with-action";
7
7
  import { useOutsideClick } from "../../../hooks";
8
- import { noop } from "../../../util/general";
8
+ import { noop, arrowBorder } from "../../../util/general";
9
9
  import { ESCAPE } from "../../../constants/keyboard";
10
10
  import { fallbackValues } from "./Popover.theme";
11
11
 
12
- const arrowBorder = (borderColor, direction, width = "8px") => {
13
- const angle = `${width} solid transparent`;
14
- const straight = `${width} solid ${borderColor}`;
15
- if (direction == "down") {
16
- return `border-left: ${angle}; border-right: ${angle}; border-top: ${straight}`;
17
- } else if (direction == "up") {
18
- return `border-left: ${angle}; border-right: ${angle}; border-bottom: ${straight}`;
19
- } else if (direction == "left") {
20
- return `border-top: ${angle}; border-bottom: ${angle}; border-right: ${straight}`;
21
- } else if (direction == "right") {
22
- return `border-top: ${angle}; border-bottom: ${angle}; border-left: ${straight}`;
23
- }
24
- };
25
-
26
12
  const Popover = ({
27
13
  themeValues,
28
14
  triggerText = "",
@@ -0,0 +1,202 @@
1
+ import React, { useContext, useEffect, useRef, useState } from "react";
2
+ import { createThemeValues } from "../../../util/themeUtils";
3
+ import { ThemeContext } from "styled-components";
4
+ import Text from "../../atoms/text";
5
+ import Paragraph from "../../atoms/paragraph";
6
+ import { Box } from "../../atoms/layouts";
7
+ import ButtonWithAction from "../../atoms/button-with-action";
8
+ 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
+ };
25
+
26
+ const Tooltip = ({
27
+ 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.",
33
+ contentPosition = {
34
+ top: "-110px",
35
+ right: "auto",
36
+ bottom: "auto",
37
+ left: "-225px"
38
+ },
39
+ arrowDirection = "down",
40
+ arrowPosition = {
41
+ arrowTop: "auto",
42
+ arrowRight: "10px",
43
+ arrowBottom: "-8px",
44
+ arrowLeft: "auto"
45
+ },
46
+ minWidth = "250px",
47
+ maxWidth = "300px",
48
+ height = "auto",
49
+ containerExtraStyles = "",
50
+ triggerExtraStyles = "",
51
+ triggerButtonVariant = "smallGhost",
52
+ contentExtraStyles = "",
53
+ contentBackgroundColor = WHITE
54
+ }) => {
55
+ const closeTimeoutRef = useRef(null);
56
+ const [tooltipOpen, setTooltipOpen] = useState(false);
57
+ const themeContext = useContext(ThemeContext);
58
+ const themeValues = createThemeValues(
59
+ themeContext,
60
+ fallbackValues,
61
+ TOOLTIP_THEME_SOURCE
62
+ );
63
+
64
+ const {
65
+ borderColor,
66
+ popoverTriggerColor: tooltipTriggerColor,
67
+ hoverColor,
68
+ activeColor
69
+ } = themeValues;
70
+
71
+ const { top, right, bottom, left } = contentPosition;
72
+ const { arrowTop, arrowRight, arrowBottom, arrowLeft } = arrowPosition;
73
+
74
+ const handleToggleTooltip = desiredTooltipState => {
75
+ if (tooltipOpen !== desiredTooltipState) {
76
+ setTooltipOpen(desiredTooltipState);
77
+ }
78
+ };
79
+
80
+ const handleKeyboardEvent = e => {
81
+ if (e.key === "Escape") {
82
+ handleToggleTooltip(false);
83
+ }
84
+ };
85
+
86
+ const handleMouseEnter = () => {
87
+ if (closeTimeoutRef.current) {
88
+ clearTimeout(closeTimeoutRef.current);
89
+ closeTimeoutRef.current = null;
90
+ }
91
+ handleToggleTooltip(true);
92
+ };
93
+
94
+ const handleMouseLeave = () => {
95
+ closeTimeoutRef.current = setTimeout(() => {
96
+ handleToggleTooltip(false);
97
+ }, 300);
98
+ };
99
+
100
+ useEffect(() => {
101
+ return () => {
102
+ if (closeTimeoutRef.current) {
103
+ clearTimeout(closeTimeoutRef.current);
104
+ }
105
+ };
106
+ }, []);
107
+
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
+ >
117
+ <ButtonWithAction
118
+ aria-describedby={tooltipID}
119
+ onKeyDown={handleKeyboardEvent}
120
+ variant={triggerButtonVariant}
121
+ onFocus={() => handleToggleTooltip(true)}
122
+ onBlur={() => handleToggleTooltip(false)}
123
+ 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>
159
+ <Box
160
+ role="tooltip"
161
+ id={tooltipID}
162
+ aria-hidden={!tooltipOpen}
163
+ background={contentBackgroundColor}
164
+ data-qa="tooltip-contents"
165
+ extraStyles={`
166
+ position: absolute;
167
+ display: ${tooltipOpen ? "block" : "none"};
168
+ top: ${top};
169
+ right: ${right};
170
+ bottom: ${bottom};
171
+ left: ${left};
172
+ height: ${height};
173
+ ${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`}
177
+ borderRadius="4px"
178
+ minWidth={minWidth}
179
+ maxWidth={maxWidth}
180
+ >
181
+ <Paragraph>{tooltipContent}</Paragraph>
182
+ <Box
183
+ padding="0"
184
+ extraStyles={`
185
+ position: absolute;
186
+ content: "";
187
+ width: 0;
188
+ height: 0;
189
+ ${arrowBorder(borderColor, arrowDirection, "8px")};
190
+ filter: drop-shadow(2px 8px 14px black);
191
+ bottom: ${arrowBottom};
192
+ right: ${arrowRight};
193
+ top: ${arrowTop};
194
+ left: ${arrowLeft};
195
+ `}
196
+ />
197
+ </Box>
198
+ </Box>
199
+ );
200
+ };
201
+
202
+ export default Tooltip;
@@ -0,0 +1,15 @@
1
+ import { Canvas, Meta, Title, Story, Controls } from '@storybook/blocks';
2
+
3
+ import * as TooltipStories from './Tooltip.stories.js';
4
+
5
+ <Meta of={TooltipStories} />
6
+
7
+ <Title />
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.
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.
12
+
13
+ <Controls />
14
+
15
+ <Story />
@@ -0,0 +1,237 @@
1
+ import React from "react";
2
+ import Tooltip from "./Tooltip";
3
+ import WarningIconXS from "../../atoms/icons/WarningIconXS";
4
+
5
+ const meta = {
6
+ title: "Molecules/Tooltip",
7
+ component: Tooltip,
8
+ parameters: {
9
+ layout: "centered"
10
+ },
11
+ tags: ["!autodocs"],
12
+ decorators: [
13
+ Story => (
14
+ <div style={{ padding: "120px 300px" }}>
15
+ <Story />
16
+ </div>
17
+ )
18
+ ],
19
+ args: {
20
+ tooltipID: "tooltip-id",
21
+ hasIconTrigger: false,
22
+ IconTrigger: WarningIconXS,
23
+ iconHelpText: "Open the tooltip",
24
+ triggerText: "Hover me",
25
+ tooltipContent: "The contents of the tooltip go here.",
26
+ contentPosition: {
27
+ top: "-110px",
28
+ right: "auto",
29
+ bottom: "auto",
30
+ left: "-225px"
31
+ },
32
+ arrowDirection: "down",
33
+ arrowPosition: {
34
+ arrowTop: "auto",
35
+ arrowRight: "10px",
36
+ arrowBottom: "-8px",
37
+ arrowLeft: "auto"
38
+ },
39
+ minWidth: "250px",
40
+ maxWidth: "300px",
41
+ height: "auto",
42
+ containerExtraStyles: "",
43
+ triggerExtraStyles: "",
44
+ triggerButtonVariant: "smallGhost",
45
+ contentBackgroundColor: "",
46
+ contentExtraStyles: ""
47
+ },
48
+ argTypes: {
49
+ tooltipID: {
50
+ description:
51
+ "Unique ID linking the trigger to the tooltip content for accessibility",
52
+ table: {
53
+ type: { summary: "string" },
54
+ defaultValue: { summary: undefined }
55
+ }
56
+ },
57
+ hasIconTrigger: {
58
+ description:
59
+ "When true, renders an icon as the tooltip trigger instead of text",
60
+ control: { type: "boolean" },
61
+ table: {
62
+ type: { summary: "boolean" },
63
+ defaultValue: { summary: false }
64
+ }
65
+ },
66
+ IconTrigger: {
67
+ description:
68
+ "Icon component rendered as the trigger when hasIconTrigger is true",
69
+ table: {
70
+ type: { summary: "React Component" },
71
+ defaultValue: { summary: "WarningIconXS" }
72
+ }
73
+ },
74
+ iconHelpText: {
75
+ description: "Screen reader text for the icon trigger",
76
+ table: {
77
+ type: { summary: "string" },
78
+ defaultValue: { summary: "Open the tooltip" }
79
+ }
80
+ },
81
+ triggerText: {
82
+ description:
83
+ "Text rendered as the tooltip trigger when hasIconTrigger is false",
84
+ table: {
85
+ type: { summary: "string | JSX.Element" },
86
+ defaultValue: { summary: "Open the tooltip" }
87
+ }
88
+ },
89
+ tooltipContent: {
90
+ description: "The content displayed inside the tooltip",
91
+ table: {
92
+ type: { summary: "string" },
93
+ defaultValue: { summary: "The contents of the tooltip go here." }
94
+ }
95
+ },
96
+ contentPosition: {
97
+ description:
98
+ "CSS position values (top, right, bottom, left) for the tooltip content box relative to the trigger",
99
+ table: {
100
+ type: { summary: "object" },
101
+ defaultValue: {
102
+ summary: `{ top: "-110px", right: "auto", bottom: "auto", left: "-225px" }`
103
+ }
104
+ }
105
+ },
106
+ arrowDirection: {
107
+ description: "Direction the tooltip arrow points (up, down, left, right)",
108
+ control: { type: "select" },
109
+ options: ["up", "down", "left", "right"],
110
+ table: {
111
+ type: { summary: "string" },
112
+ defaultValue: { summary: "down" }
113
+ }
114
+ },
115
+ arrowPosition: {
116
+ description:
117
+ "CSS position values (arrowTop, arrowRight, arrowBottom, arrowLeft) for the arrow element",
118
+ table: {
119
+ type: { summary: "object" },
120
+ defaultValue: {
121
+ summary: `{ arrowTop: "auto", arrowRight: "10px", arrowBottom: "-8px", arrowLeft: "auto" }`
122
+ }
123
+ }
124
+ },
125
+ minWidth: {
126
+ description: "Minimum width of the tooltip content box",
127
+ table: {
128
+ type: { summary: "string" },
129
+ defaultValue: { summary: "250px" }
130
+ }
131
+ },
132
+ maxWidth: {
133
+ description: "Maximum width of the tooltip content box",
134
+ table: {
135
+ type: { summary: "string" },
136
+ defaultValue: { summary: "300px" }
137
+ }
138
+ },
139
+ height: {
140
+ description: "Height of the tooltip content box",
141
+ table: {
142
+ type: { summary: "string" },
143
+ defaultValue: { summary: "auto" }
144
+ }
145
+ },
146
+ containerExtraStyles: {
147
+ description:
148
+ "Additional CSS string applied to the tooltip container element",
149
+ table: {
150
+ type: { summary: "string" },
151
+ defaultValue: { summary: '""' }
152
+ }
153
+ },
154
+ triggerExtraStyles: {
155
+ description: "Additional CSS string applied to the text trigger element",
156
+ table: {
157
+ type: { summary: "string" },
158
+ defaultValue: { summary: '""' }
159
+ }
160
+ },
161
+ triggerButtonVariant: {
162
+ description:
163
+ "Button variant applied to the trigger ButtonWithAction element",
164
+ table: {
165
+ type: { summary: "string" },
166
+ defaultValue: { summary: "smallGhost" }
167
+ }
168
+ }
169
+ }
170
+ };
171
+
172
+ export default meta;
173
+
174
+ export const Basic = {
175
+ args: {
176
+ tooltipID: "tooltip-basic",
177
+ triggerText: "How basic is this?",
178
+ tooltipContent:
179
+ "This is a detailed explanation of a feature or term that the user may need more context about."
180
+ }
181
+ };
182
+
183
+ export const TooltipBelow = {
184
+ args: {
185
+ tooltipID: "tooltip-below",
186
+ contentPosition: {
187
+ top: "50px",
188
+ right: "auto",
189
+ bottom: "auto",
190
+ left: "-225px"
191
+ },
192
+ arrowDirection: "up",
193
+ arrowPosition: {
194
+ arrowTop: "-8px",
195
+ arrowRight: "10px",
196
+ arrowBottom: "auto",
197
+ arrowLeft: "auto"
198
+ }
199
+ }
200
+ };
201
+
202
+ export const TooltipRight = {
203
+ args: {
204
+ tooltipID: "tooltip-right",
205
+ contentPosition: {
206
+ top: "-40px",
207
+ right: "auto",
208
+ bottom: "auto",
209
+ left: "calc(100% + 12px)"
210
+ },
211
+ arrowDirection: "left",
212
+ arrowPosition: {
213
+ arrowTop: "50%",
214
+ arrowRight: "auto",
215
+ arrowBottom: "auto",
216
+ arrowLeft: "-8px"
217
+ }
218
+ }
219
+ };
220
+
221
+ export const CustomContent = {
222
+ args: {
223
+ tooltipID: "tooltip-custom-content",
224
+ triggerText: "What is this?",
225
+ tooltipContent:
226
+ "This is a detailed explanation of a feature or term that the user may need more context about."
227
+ }
228
+ };
229
+
230
+ export const CustomWidth = {
231
+ args: {
232
+ tooltipID: "tooltip-custom-width",
233
+ minWidth: "150px",
234
+ maxWidth: "200px",
235
+ tooltipContent: "A narrower tooltip."
236
+ }
237
+ };
@@ -0,0 +1,9 @@
1
+ const hoverColor = "#116285";
2
+ const activeColor = "#0E506D";
3
+ const tooltipTriggerColor = "#15749D";
4
+
5
+ export const fallbackValues = {
6
+ hoverColor,
7
+ activeColor,
8
+ tooltipTriggerColor
9
+ };
@@ -0,0 +1,34 @@
1
+ import React from "react";
2
+ import Expand from "../../../util/expand";
3
+ export interface TooltipProps {
4
+ tooltipID: string;
5
+ hasIconTrigger?: boolean;
6
+ IconTrigger?: React.FC<React.SVGProps<SVGSVGElement>>;
7
+ iconHelpText?: string;
8
+ triggerText?: string | JSX.Element;
9
+ tooltipContent: string;
10
+ contentPosition?: {
11
+ top: string;
12
+ right: string;
13
+ bottom: string;
14
+ left: string;
15
+ };
16
+ arrowDirection?: string;
17
+ arrowPosition?: {
18
+ arrowTop: string;
19
+ arrowRight: string;
20
+ arrowBottom: string;
21
+ arrowLeft: string;
22
+ };
23
+ minWidth?: string;
24
+ maxWidth?: string;
25
+ height?: string;
26
+ containerExtraStyles?: string;
27
+ triggerExtraStyles?: string;
28
+ triggerButtonVariant?: string;
29
+ contentExtraStyles?: string;
30
+ contentBackgroundColor?: string;
31
+ }
32
+
33
+ export const Tooltip: React.FC<Expand<TooltipProps> &
34
+ React.HTMLAttributes<HTMLElement>>;
@@ -0,0 +1,3 @@
1
+ import Tooltip from "./Tooltip";
2
+
3
+ export default Tooltip;
@@ -222,3 +222,17 @@ export const adjustHexColor = (hex, percent, action) => {
222
222
  .slice(1)
223
223
  .padStart(6, "0")}`;
224
224
  };
225
+
226
+ export const arrowBorder = (borderColor, direction, width = "8px") => {
227
+ const angle = `${width} solid transparent`;
228
+ const straight = `${width} solid ${borderColor}`;
229
+ if (direction == "down") {
230
+ return `border-left: ${angle}; border-right: ${angle}; border-top: ${straight}`;
231
+ } else if (direction == "up") {
232
+ return `border-left: ${angle}; border-right: ${angle}; border-bottom: ${straight}`;
233
+ } else if (direction == "left") {
234
+ return `border-top: ${angle}; border-bottom: ${angle}; border-right: ${straight}`;
235
+ } else if (direction == "right") {
236
+ return `border-top: ${angle}; border-bottom: ${angle}; border-left: ${straight}`;
237
+ }
238
+ };