@thecb/components 11.11.0-beta.5 → 11.11.0-beta.8
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/dist/index.cjs.js +130 -64
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +130 -64
- package/dist/index.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/components/atoms/formatted-bank-account/FormattedBankAccount.js +2 -2
- package/src/components/atoms/formatted-bank-account/FormattedBankAccount.theme.js +2 -2
- package/src/components/atoms/formatted-credit-card/FormattedCreditCard.js +2 -2
- package/src/components/atoms/formatted-credit-card/FormattedCreditCard.theme.js +2 -2
- package/src/components/molecules/editable-list/EditableList.js +3 -0
- package/src/components/molecules/radio-section/InnerRadioSection.js +1 -1
- package/src/components/molecules/radio-section/RadioSection.stories.js +142 -0
- package/src/components/molecules/terms-and-conditions-modal/TermsAndConditionsModal.js +1 -1
- package/src/components/molecules/terms-and-conditions-modal/TermsAndConditionsModal.stories.js +110 -0
- package/src/components/molecules/tooltip/Tooltip.js +112 -50
- package/src/components/molecules/tooltip/Tooltip.stories.js +25 -14
- package/src/components/molecules/tooltip/Tooltip.theme.js +10 -11
- package/src/components/molecules/tooltip/index.d.ts +3 -2
- package/src/util/formats.js +6 -3
package/package.json
CHANGED
|
@@ -50,8 +50,8 @@ const FormattedBankAccount = ({
|
|
|
50
50
|
<Text
|
|
51
51
|
variant="p"
|
|
52
52
|
color={themeValues.autopayTextColor}
|
|
53
|
-
extraStyles={`font-style: italic;`}
|
|
54
|
-
>{`Autopay
|
|
53
|
+
extraStyles={`font-style: italic; font-size: .75rem;`}
|
|
54
|
+
>{`Autopay On`}</Text>
|
|
55
55
|
)}
|
|
56
56
|
</Stack>
|
|
57
57
|
</BankItemWrapper>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { CHARADE_GREY,
|
|
1
|
+
import { CHARADE_GREY, SEA_GREEN } from "../../../constants/colors";
|
|
2
2
|
|
|
3
3
|
const textColor = `${CHARADE_GREY}`;
|
|
4
|
-
const autopayTextColor = `${
|
|
4
|
+
const autopayTextColor = `${SEA_GREEN}`;
|
|
5
5
|
|
|
6
6
|
export const fallbackValues = {
|
|
7
7
|
textColor,
|
|
@@ -55,8 +55,8 @@ const FormattedCreditCard = ({
|
|
|
55
55
|
<Text
|
|
56
56
|
variant="p"
|
|
57
57
|
color={themeValues.autopayTextColor}
|
|
58
|
-
extraStyles={`font-style: italic;`}
|
|
59
|
-
>{`Autopay
|
|
58
|
+
extraStyles={`font-style: italic; font-size: .75rem;`}
|
|
59
|
+
>{`Autopay On`}</Text>
|
|
60
60
|
)}
|
|
61
61
|
</Stack>
|
|
62
62
|
</CreditCardWrapper>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { CHARADE_GREY,
|
|
1
|
+
import { CHARADE_GREY, SEA_GREEN } from "../../../constants/colors";
|
|
2
2
|
|
|
3
3
|
const textColor = `${CHARADE_GREY}`;
|
|
4
|
-
const autopayTextColor = `${
|
|
4
|
+
const autopayTextColor = `${SEA_GREEN}`;
|
|
5
5
|
|
|
6
6
|
export const fallbackValues = {
|
|
7
7
|
textColor,
|
|
@@ -23,6 +23,7 @@ const EditableList = ({
|
|
|
23
23
|
titleWeight = "400",
|
|
24
24
|
canAdd = true,
|
|
25
25
|
addItem,
|
|
26
|
+
addItemDestination,
|
|
26
27
|
removeItem,
|
|
27
28
|
editItem,
|
|
28
29
|
itemName,
|
|
@@ -168,6 +169,8 @@ const EditableList = ({
|
|
|
168
169
|
<Box padding={items.length === 0 ? "0" : "1rem 0 0"}>
|
|
169
170
|
<Placeholder
|
|
170
171
|
text={addText}
|
|
172
|
+
isLink={!!addItemDestination}
|
|
173
|
+
destination={addItemDestination}
|
|
171
174
|
action={addItem}
|
|
172
175
|
dataQa={"Add " + qaPrefix}
|
|
173
176
|
aria-label={addText}
|
|
@@ -159,7 +159,7 @@ const InnerRadioSection = ({
|
|
|
159
159
|
<Box padding={section.titleIcon ? "0 0 0 8px" : "0"}>
|
|
160
160
|
<Text
|
|
161
161
|
as="label"
|
|
162
|
-
htmlFor={`radio
|
|
162
|
+
htmlFor={`radio-${idString(section)}`}
|
|
163
163
|
color={CHARADE_GREY}
|
|
164
164
|
>
|
|
165
165
|
{section.title}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import RadioSection from "./RadioSection";
|
|
3
|
+
import { Box } from "../../atoms/layouts";
|
|
4
|
+
|
|
5
|
+
const PaymentMethodSections = () => {
|
|
6
|
+
const [openSection, setOpenSection] = useState("");
|
|
7
|
+
return (
|
|
8
|
+
<RadioSection
|
|
9
|
+
toggleOpenSection={setOpenSection}
|
|
10
|
+
openSection={openSection}
|
|
11
|
+
isSectionRequired={true}
|
|
12
|
+
sections={[
|
|
13
|
+
{
|
|
14
|
+
id: "checking-1234",
|
|
15
|
+
title: "Checking Account ending in 1234",
|
|
16
|
+
content: (
|
|
17
|
+
<Box padding="1rem">
|
|
18
|
+
<p>Checking account payment details would appear here.</p>
|
|
19
|
+
</Box>
|
|
20
|
+
),
|
|
21
|
+
required: true,
|
|
22
|
+
dataQa: "Checking Account"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: "savings-5678",
|
|
26
|
+
title: "Savings Account ending in 5678",
|
|
27
|
+
content: (
|
|
28
|
+
<Box padding="1rem">
|
|
29
|
+
<p>Savings account payment details would appear here.</p>
|
|
30
|
+
</Box>
|
|
31
|
+
),
|
|
32
|
+
required: true,
|
|
33
|
+
dataQa: "Savings Account"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: "card-4321",
|
|
37
|
+
title: "Card ending in 4321",
|
|
38
|
+
content: (
|
|
39
|
+
<Box padding="1rem">
|
|
40
|
+
<p>Credit card payment details would appear here.</p>
|
|
41
|
+
</Box>
|
|
42
|
+
),
|
|
43
|
+
required: true,
|
|
44
|
+
dataQa: "Credit Card"
|
|
45
|
+
}
|
|
46
|
+
]}
|
|
47
|
+
/>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const BasicSections = () => {
|
|
52
|
+
const [openSection, setOpenSection] = useState("");
|
|
53
|
+
return (
|
|
54
|
+
<RadioSection
|
|
55
|
+
toggleOpenSection={setOpenSection}
|
|
56
|
+
openSection={openSection}
|
|
57
|
+
sections={[
|
|
58
|
+
{
|
|
59
|
+
id: "section-a",
|
|
60
|
+
title: "Section A",
|
|
61
|
+
content: (
|
|
62
|
+
<Box padding="1rem">
|
|
63
|
+
<p>Content for section A</p>
|
|
64
|
+
</Box>
|
|
65
|
+
)
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
id: "section-b",
|
|
69
|
+
title: "Section B",
|
|
70
|
+
content: (
|
|
71
|
+
<Box padding="1rem">
|
|
72
|
+
<p>Content for section B</p>
|
|
73
|
+
</Box>
|
|
74
|
+
)
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: "section-c",
|
|
78
|
+
title: "Section C",
|
|
79
|
+
content: (
|
|
80
|
+
<Box padding="1rem">
|
|
81
|
+
<p>Content for section C</p>
|
|
82
|
+
</Box>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
]}
|
|
86
|
+
/>
|
|
87
|
+
);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const WithDisabledSection = () => {
|
|
91
|
+
const [openSection, setOpenSection] = useState("");
|
|
92
|
+
return (
|
|
93
|
+
<RadioSection
|
|
94
|
+
toggleOpenSection={setOpenSection}
|
|
95
|
+
openSection={openSection}
|
|
96
|
+
sections={[
|
|
97
|
+
{
|
|
98
|
+
id: "enabled-section",
|
|
99
|
+
title: "Enabled Option",
|
|
100
|
+
content: (
|
|
101
|
+
<Box padding="1rem">
|
|
102
|
+
<p>This option is selectable.</p>
|
|
103
|
+
</Box>
|
|
104
|
+
)
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
id: "disabled-section",
|
|
108
|
+
title: "Disabled Option",
|
|
109
|
+
disabled: true,
|
|
110
|
+
content: (
|
|
111
|
+
<Box padding="1rem">
|
|
112
|
+
<p>This option is disabled.</p>
|
|
113
|
+
</Box>
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
]}
|
|
117
|
+
/>
|
|
118
|
+
);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const meta = {
|
|
122
|
+
title: "Molecules/RadioSection",
|
|
123
|
+
component: RadioSection,
|
|
124
|
+
parameters: {
|
|
125
|
+
layout: "centered"
|
|
126
|
+
},
|
|
127
|
+
tags: ["!autodocs"]
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export default meta;
|
|
131
|
+
|
|
132
|
+
export const PaymentMethods = {
|
|
133
|
+
render: () => <PaymentMethodSections />
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export const Basic = {
|
|
137
|
+
render: () => <BasicSections />
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
export const Disabled = {
|
|
141
|
+
render: () => <WithDisabledSection />
|
|
142
|
+
};
|
|
@@ -54,7 +54,7 @@ const TermsAndConditionsModal = ({
|
|
|
54
54
|
weight={themeValues.fontWeight}
|
|
55
55
|
hoverStyles={themeValues.modalLinkHoverFocus}
|
|
56
56
|
textDecoration={themeValues.modalLinkTextDecoration}
|
|
57
|
-
extraStyles={`display: inline-block; width: fit-content; cursor: pointer
|
|
57
|
+
extraStyles={`display: inline-block; width: fit-content; cursor: pointer; min-height: 24px; line-height: 24px;`}
|
|
58
58
|
role="button" // This should always be a "button" since it opens a modal
|
|
59
59
|
className="modal-trigger"
|
|
60
60
|
>
|
package/src/components/molecules/terms-and-conditions-modal/TermsAndConditionsModal.stories.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import TermsAndConditionsModal from "./TermsAndConditionsModal";
|
|
3
|
+
|
|
4
|
+
const TermsContent = () => (
|
|
5
|
+
<p>
|
|
6
|
+
By enrolling, you agree to the automatic payment terms. Payments will be
|
|
7
|
+
processed on the first of each month using your selected payment method. You
|
|
8
|
+
may cancel at any time by contacting support. See our{" "}
|
|
9
|
+
<a href="#">Privacy Policy</a> for more details.
|
|
10
|
+
</p>
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
const meta = {
|
|
14
|
+
title: "Molecules/TermsAndConditionsModal",
|
|
15
|
+
component: TermsAndConditionsModal,
|
|
16
|
+
parameters: {
|
|
17
|
+
layout: "centered"
|
|
18
|
+
},
|
|
19
|
+
tags: ["!autodocs"],
|
|
20
|
+
args: {
|
|
21
|
+
link: "Terms and Conditions",
|
|
22
|
+
title: "Terms and Conditions",
|
|
23
|
+
terms: <TermsContent />,
|
|
24
|
+
isOpen: false,
|
|
25
|
+
linkVariant: "p",
|
|
26
|
+
initialFocusSelector: "[name='Cancel']"
|
|
27
|
+
},
|
|
28
|
+
argTypes: {
|
|
29
|
+
link: {
|
|
30
|
+
description: "Text displayed for the modal trigger link",
|
|
31
|
+
table: {
|
|
32
|
+
type: { summary: "string" },
|
|
33
|
+
defaultValue: { summary: "Terms and Conditions" }
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
title: {
|
|
37
|
+
description: "Title displayed in the modal header",
|
|
38
|
+
table: {
|
|
39
|
+
type: { summary: "string" },
|
|
40
|
+
defaultValue: { summary: "Terms & Conditions" }
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
isOpen: {
|
|
44
|
+
description: "Whether the modal is currently open",
|
|
45
|
+
table: {
|
|
46
|
+
type: { summary: "boolean" },
|
|
47
|
+
defaultValue: { summary: false }
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
acceptText: {
|
|
51
|
+
description:
|
|
52
|
+
"Text for the accept button. If omitted, only a close button is shown.",
|
|
53
|
+
table: {
|
|
54
|
+
type: { summary: "string" },
|
|
55
|
+
defaultValue: { summary: undefined }
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
linkVariant: {
|
|
59
|
+
description: "Text size variant for the trigger link",
|
|
60
|
+
control: "select",
|
|
61
|
+
options: ["p", "pL", "pS", "pXS", "pXXS", "pXL"],
|
|
62
|
+
table: {
|
|
63
|
+
type: { summary: "string" },
|
|
64
|
+
defaultValue: { summary: "p" }
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
initialFocusSelector: {
|
|
68
|
+
description: "CSS selector for the element that receives initial focus",
|
|
69
|
+
table: {
|
|
70
|
+
type: { summary: "string" },
|
|
71
|
+
defaultValue: { summary: "" }
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export default meta;
|
|
78
|
+
|
|
79
|
+
export const Default = {};
|
|
80
|
+
|
|
81
|
+
export const SmallVariant = {
|
|
82
|
+
args: {
|
|
83
|
+
linkVariant: "pS"
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export const ExtraSmallVariant = {
|
|
88
|
+
args: {
|
|
89
|
+
linkVariant: "pXS"
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export const LargeVariant = {
|
|
94
|
+
args: {
|
|
95
|
+
linkVariant: "pL"
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export const WithAcceptButton = {
|
|
100
|
+
args: {
|
|
101
|
+
acceptText: "I Accept"
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export const CustomLinkText = {
|
|
106
|
+
args: {
|
|
107
|
+
link: "Learn More",
|
|
108
|
+
linkVariant: "pS"
|
|
109
|
+
}
|
|
110
|
+
};
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import React, { useContext, useEffect, useRef, useState } from "react";
|
|
2
2
|
import { createThemeValues } from "../../../util/themeUtils";
|
|
3
|
+
import { WHITE } from "../../../constants/colors";
|
|
3
4
|
import { ThemeContext } from "styled-components";
|
|
4
5
|
import Text from "../../atoms/text";
|
|
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 {
|
|
9
|
+
import { fallbackValues } from "./Tooltip.theme";
|
|
10
|
+
|
|
11
|
+
const TOOLTIP_THEME_SOURCE = "Popover";
|
|
9
12
|
|
|
10
13
|
const Tooltip = ({
|
|
11
14
|
tooltipID,
|
|
@@ -32,9 +35,12 @@ const Tooltip = ({
|
|
|
32
35
|
arrowBottom: "-8px",
|
|
33
36
|
arrowLeft: "auto"
|
|
34
37
|
},
|
|
35
|
-
|
|
38
|
+
customTriggerRole,
|
|
39
|
+
backgroundColor = WHITE
|
|
36
40
|
}) => {
|
|
37
41
|
const closeTimeoutRef = useRef(null);
|
|
42
|
+
const containerRef = useRef(null);
|
|
43
|
+
|
|
38
44
|
const [tooltipOpen, setTooltipOpen] = useState(false);
|
|
39
45
|
const themeContext = useContext(ThemeContext);
|
|
40
46
|
const themeValues = createThemeValues(
|
|
@@ -58,6 +64,87 @@ const Tooltip = ({
|
|
|
58
64
|
}
|
|
59
65
|
};
|
|
60
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Renders the tooltip trigger element.
|
|
69
|
+
*
|
|
70
|
+
* When `hasCustomTrigger` is true, the provided child element is cloned and
|
|
71
|
+
* injected with the event handlers needed to control tooltip visibility:
|
|
72
|
+
* - onFocus/onBlur: open and close for keyboard users
|
|
73
|
+
* - onKeyDown: allows Escape to dismiss the tooltip
|
|
74
|
+
* - onTouchStart: open on tap for touch/mobile users (onFocus is unreliable on touch)
|
|
75
|
+
*
|
|
76
|
+
* Mouse interactions (hover) are handled at the container level via
|
|
77
|
+
* onMouseEnter/onMouseLeave, so they do not need to be injected here.
|
|
78
|
+
*
|
|
79
|
+
* Any existing event handlers on the child are preserved and called first,
|
|
80
|
+
* so the child's own behavior is not overridden.
|
|
81
|
+
*
|
|
82
|
+
* When no custom trigger is provided, a default ButtonWithAction is rendered
|
|
83
|
+
* using `triggerText` and `triggerButtonVariant`.
|
|
84
|
+
*/
|
|
85
|
+
const renderTrigger = () => {
|
|
86
|
+
if (hasCustomTrigger && !children) {
|
|
87
|
+
console.warn(
|
|
88
|
+
"Tooltip: children prop is required when hasCustomTrigger is true"
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (hasCustomTrigger && children) {
|
|
93
|
+
const child = React.Children.only(children);
|
|
94
|
+
// Capture the child's existing handlers before overwriting
|
|
95
|
+
const {
|
|
96
|
+
onFocus: childOnFocus,
|
|
97
|
+
onBlur: childOnBlur,
|
|
98
|
+
onKeyDown: childOnKeyDown,
|
|
99
|
+
onTouchStart: childOnTouchStart
|
|
100
|
+
} = child.props ?? {};
|
|
101
|
+
|
|
102
|
+
return React.cloneElement(child, {
|
|
103
|
+
tabIndex: child.props?.tabIndex ?? 0,
|
|
104
|
+
style: { cursor: `pointer`, ...child.props?.style },
|
|
105
|
+
onFocus: e => {
|
|
106
|
+
childOnFocus?.(e);
|
|
107
|
+
handleToggleTooltip(true);
|
|
108
|
+
},
|
|
109
|
+
onBlur: e => {
|
|
110
|
+
childOnBlur?.(e);
|
|
111
|
+
handleToggleTooltip(false);
|
|
112
|
+
},
|
|
113
|
+
onKeyDown: e => {
|
|
114
|
+
childOnKeyDown?.(e);
|
|
115
|
+
handleKeyDown(e);
|
|
116
|
+
},
|
|
117
|
+
onTouchStart: e => {
|
|
118
|
+
childOnTouchStart?.(e);
|
|
119
|
+
handleToggleTooltip(true);
|
|
120
|
+
},
|
|
121
|
+
role: customTriggerRole || child.props?.role,
|
|
122
|
+
"aria-describedby": tooltipID,
|
|
123
|
+
"data-qa": `tooltip-trigger-${tooltipID}`
|
|
124
|
+
});
|
|
125
|
+
} else {
|
|
126
|
+
return (
|
|
127
|
+
<ButtonWithAction
|
|
128
|
+
action={noop}
|
|
129
|
+
onKeyDown={handleKeyDown}
|
|
130
|
+
variant={triggerButtonVariant}
|
|
131
|
+
text={triggerText}
|
|
132
|
+
tabIndex={0}
|
|
133
|
+
ariaDescribedby={tooltipID}
|
|
134
|
+
onFocus={() => handleToggleTooltip(true)}
|
|
135
|
+
onBlur={() => handleToggleTooltip(false)}
|
|
136
|
+
onTouchStart={() => handleToggleTooltip(true)}
|
|
137
|
+
dataQa={`tooltip-trigger-${tooltipID}`}
|
|
138
|
+
extraStyles={`
|
|
139
|
+
color: ${themeValues.linkColor};
|
|
140
|
+
&:hover { color: ${themeValues.hoverColor}; text-decoration: none;}
|
|
141
|
+
&:active, &:focus { color: ${themeValues.activeColor};text-decoration: none;}
|
|
142
|
+
button, span, &:hover span { text-decoration: none; }
|
|
143
|
+
`}
|
|
144
|
+
/>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
61
148
|
const handleMouseEnter = () => {
|
|
62
149
|
if (closeTimeoutRef.current) {
|
|
63
150
|
clearTimeout(closeTimeoutRef.current);
|
|
@@ -65,57 +152,35 @@ const Tooltip = ({
|
|
|
65
152
|
}
|
|
66
153
|
handleToggleTooltip(true);
|
|
67
154
|
};
|
|
68
|
-
|
|
69
155
|
const handleMouseLeave = () => {
|
|
70
156
|
closeTimeoutRef.current = setTimeout(() => {
|
|
71
157
|
handleToggleTooltip(false);
|
|
72
158
|
}, 300);
|
|
73
159
|
};
|
|
74
160
|
|
|
161
|
+
// Touch listener effect
|
|
75
162
|
useEffect(() => {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
163
|
+
if (!tooltipOpen) return;
|
|
164
|
+
|
|
165
|
+
const handleOutsideTouch = e => {
|
|
166
|
+
if (containerRef.current && !containerRef.current.contains(e.target)) {
|
|
167
|
+
setTooltipOpen(false);
|
|
79
168
|
}
|
|
80
169
|
};
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
tabIndex: "0",
|
|
91
|
-
style: { cursor: "pointer" }
|
|
92
|
-
});
|
|
93
|
-
}
|
|
170
|
+
document.addEventListener("touchstart", handleOutsideTouch);
|
|
171
|
+
return () => document.removeEventListener("touchstart", handleOutsideTouch);
|
|
172
|
+
}, [tooltipOpen]);
|
|
94
173
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
onFocus={() => handleToggleTooltip(true)}
|
|
102
|
-
onBlur={() => handleToggleTooltip(false)}
|
|
103
|
-
onTouchStart={() => handleToggleTooltip(true)}
|
|
104
|
-
data-qa={`tooltip-trigger-${tooltipID}`}
|
|
105
|
-
text={triggerText}
|
|
106
|
-
tabIndex="0"
|
|
107
|
-
extraStyles={`
|
|
108
|
-
color: ${themeValues.linkColor};
|
|
109
|
-
&:hover { color: ${themeValues.hoverColor}; text-decoration: none;}
|
|
110
|
-
&:active, &:focus { color: ${themeValues.activeColor};text-decoration: none;}
|
|
111
|
-
button, span, &:hover span { text-decoration: none; }
|
|
112
|
-
`}
|
|
113
|
-
/>
|
|
114
|
-
);
|
|
115
|
-
};
|
|
174
|
+
// Unmount cleanup only
|
|
175
|
+
useEffect(() => {
|
|
176
|
+
return () => {
|
|
177
|
+
if (closeTimeoutRef.current) clearTimeout(closeTimeoutRef.current);
|
|
178
|
+
};
|
|
179
|
+
}, []);
|
|
116
180
|
|
|
117
181
|
return (
|
|
118
182
|
<Box
|
|
183
|
+
ref={containerRef}
|
|
119
184
|
padding="0"
|
|
120
185
|
extraStyles={`position: relative; ${containerExtraStyles}`}
|
|
121
186
|
onMouseEnter={handleMouseEnter}
|
|
@@ -127,7 +192,7 @@ const Tooltip = ({
|
|
|
127
192
|
role="tooltip"
|
|
128
193
|
id={tooltipID}
|
|
129
194
|
aria-hidden={!tooltipOpen}
|
|
130
|
-
background={
|
|
195
|
+
background={backgroundColor}
|
|
131
196
|
data-qa="tooltip-contents"
|
|
132
197
|
extraStyles={`
|
|
133
198
|
position: absolute;
|
|
@@ -137,6 +202,7 @@ const Tooltip = ({
|
|
|
137
202
|
bottom: ${bottom};
|
|
138
203
|
left: ${left};
|
|
139
204
|
height: ${height};
|
|
205
|
+
color: ${themeValues.textColor};
|
|
140
206
|
${contentExtraStyles}
|
|
141
207
|
`}
|
|
142
208
|
boxShadow="0px 2px 14px 0px rgb(246, 246, 249), 0px 3px 8px 0px rgb(202, 206, 216)"
|
|
@@ -145,11 +211,11 @@ const Tooltip = ({
|
|
|
145
211
|
minWidth={minWidth}
|
|
146
212
|
maxWidth={maxWidth}
|
|
147
213
|
>
|
|
148
|
-
{typeof content === "string" ? (
|
|
149
|
-
<Text color={themeValues.
|
|
150
|
-
) : (
|
|
214
|
+
{typeof content === "string" && content !== "" ? (
|
|
215
|
+
<Text color={themeValues.textColor}>{content}</Text>
|
|
216
|
+
) : content !== undefined && content !== null ? (
|
|
151
217
|
content
|
|
152
|
-
)}
|
|
218
|
+
) : null}
|
|
153
219
|
<Box
|
|
154
220
|
padding="0"
|
|
155
221
|
extraStyles={`
|
|
@@ -157,11 +223,7 @@ const Tooltip = ({
|
|
|
157
223
|
content: "";
|
|
158
224
|
width: 0;
|
|
159
225
|
height: 0;
|
|
160
|
-
${arrowBorder(
|
|
161
|
-
arrowColor || themeValues.borderColor,
|
|
162
|
-
arrowDirection,
|
|
163
|
-
"8px"
|
|
164
|
-
)};
|
|
226
|
+
${arrowBorder(backgroundColor, arrowDirection, "8px")};
|
|
165
227
|
filter: drop-shadow(2px 8px 14px black);
|
|
166
228
|
bottom: ${arrowBottom};
|
|
167
229
|
right: ${arrowRight};
|
|
@@ -3,7 +3,7 @@ import Tooltip from "./Tooltip";
|
|
|
3
3
|
import Text from "../../atoms/text/Text";
|
|
4
4
|
import AutopayOnIcon from "../../atoms/icons/AutopayOnIcon";
|
|
5
5
|
import Box from "../../atoms/layouts/Box";
|
|
6
|
-
import { SEA_GREEN } from "../../../constants/colors";
|
|
6
|
+
import { SEA_GREEN, WHITE } from "../../../constants/colors";
|
|
7
7
|
|
|
8
8
|
const meta = {
|
|
9
9
|
title: "Molecules/Tooltip",
|
|
@@ -52,6 +52,15 @@ const meta = {
|
|
|
52
52
|
defaultValue: { summary: false }
|
|
53
53
|
}
|
|
54
54
|
},
|
|
55
|
+
customTriggerRole: {
|
|
56
|
+
description:
|
|
57
|
+
"Role for the custom trigger element for accessibility purposes. Defaults to undefined.",
|
|
58
|
+
control: { type: "text" },
|
|
59
|
+
table: {
|
|
60
|
+
type: { summary: "string" },
|
|
61
|
+
defaultValue: { summary: undefined }
|
|
62
|
+
}
|
|
63
|
+
},
|
|
55
64
|
children: {
|
|
56
65
|
description:
|
|
57
66
|
"Optional trigger element. When provided, it replaces the default ButtonWithAction trigger. The child element will receive aria-describedby, focus, blur, and keydown handlers.",
|
|
@@ -135,7 +144,7 @@ const meta = {
|
|
|
135
144
|
description: "Maximum width of the tooltip content box.",
|
|
136
145
|
table: {
|
|
137
146
|
type: { summary: "string" },
|
|
138
|
-
defaultValue: { summary: "
|
|
147
|
+
defaultValue: { summary: "100%" }
|
|
139
148
|
}
|
|
140
149
|
},
|
|
141
150
|
height: {
|
|
@@ -161,12 +170,12 @@ const meta = {
|
|
|
161
170
|
defaultValue: { summary: '""' }
|
|
162
171
|
}
|
|
163
172
|
},
|
|
164
|
-
|
|
173
|
+
backgroundColor: {
|
|
165
174
|
description:
|
|
166
|
-
"Override
|
|
175
|
+
"Override backgroundColor for the tooltip arrow. Defaults to WHITE when not provided.",
|
|
167
176
|
table: {
|
|
168
177
|
type: { summary: "string" },
|
|
169
|
-
defaultValue: { summary:
|
|
178
|
+
defaultValue: { summary: WHITE }
|
|
170
179
|
}
|
|
171
180
|
}
|
|
172
181
|
}
|
|
@@ -215,21 +224,23 @@ export const RichTooltipContent = {
|
|
|
215
224
|
tooltipID: "tooltip-node-content",
|
|
216
225
|
contentExtraStyles: "background-color: #000000; color: #ffffff;",
|
|
217
226
|
triggerText: "Rich content",
|
|
218
|
-
|
|
227
|
+
backgroundColor: "black",
|
|
219
228
|
contentPosition: {
|
|
220
229
|
top: "-126px",
|
|
221
230
|
right: "auto",
|
|
222
231
|
bottom: "auto",
|
|
223
232
|
left: "-225px"
|
|
224
233
|
},
|
|
225
|
-
content: (
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
234
|
+
content: React.createElement("div", { style: { padding: "8px" } }, [
|
|
235
|
+
React.createElement("strong", null, "Bold title"),
|
|
236
|
+
React.createElement(
|
|
237
|
+
"p",
|
|
238
|
+
null,
|
|
239
|
+
"With ",
|
|
240
|
+
React.createElement("em", null, "an italic text detail"),
|
|
241
|
+
" below."
|
|
242
|
+
)
|
|
243
|
+
])
|
|
233
244
|
}
|
|
234
245
|
};
|
|
235
246
|
|