Skip to content

Commit 31defba

Browse files
feact(alert/callout): design update (#4060)
* feat(design-tokens): updated tokens to support Alert and Callout updates * feat(callout): add dismissible funcitonality to the component * chore(pr): changeset * chore(alert): updated styling * chore(callout): updated styling * chore(pr): changeset * chore(pr): typedocs * chore(pr): linting issues * chore(pr): peer dependencies * chore(pr): peer dependencies * feat(website): added new example for dismissible callout * chore(pr): cleanup changesets * chore(pr): cleanup changesets * chore(pr): pr cleanup * chore(pr): typedocs * chore(pr): fix stlying issues * chore(pr): remove icon padding * fix(callout): correct row gap size * fix(callout): correct row gap size * fix(callout): fix double margin * fix(callout): close and icon on same line * fix(callout): issue with additonal pixel added --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
1 parent 7ae14af commit 31defba

File tree

18 files changed

+268
-61
lines changed

18 files changed

+268
-61
lines changed

.changeset/mean-houses-learn.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@twilio-paste/design-tokens": minor
3+
"@twilio-paste/core": minor
4+
---
5+
6+
[Design Tokens] added a new design token for color-border-new-weak and made changes to color-text-icon-warning in the Twilio theme to support design updates to Alert and Callout components

.changeset/neat-weeks-rhyme.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@twilio-paste/callout": minor
3+
"@twilio-paste/core": minor
4+
---
5+
6+
[Callout] added dismissible functionality to the component

.changeset/plenty-rockets-study.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@twilio-paste/alert": patch
3+
"@twilio-paste/callout": patch
4+
"@twilio-paste/core": patch
5+
---
6+
7+
[Callout, Alert] updated styling

packages/paste-core/components/alert/src/Alert.tsx

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,17 @@ export const AlertVariants = {
2222
NEUTRAL: "neutral",
2323
WARNING: "warning",
2424
} as const;
25-
export const AlertBackgroundColors = {
25+
export const AlertBackgroundColors: BoxProps["backgroundColor"] = {
2626
ERROR: "colorBackgroundErrorWeakest",
2727
NEUTRAL: "colorBackgroundNeutralWeakest",
2828
WARNING: "colorBackgroundWarningWeakest",
2929
} as const;
30-
export const AlertTextColors = {
30+
export const AlertBorderColors: BoxProps["borderColor"] = {
31+
ERROR: "colorBorderErrorWeaker",
32+
NEUTRAL: "colorBorderNeutralWeaker",
33+
WARNING: "colorBorderWarningWeaker",
34+
} as const;
35+
export const AlertTextColors: BoxProps["color"] = {
3136
ERROR: "colorTextError",
3237
NEUTRAL: "colorTextNeutral",
3338
WARNING: "colorTextWarningStrong",
@@ -169,10 +174,11 @@ const Alert = React.forwardRef<HTMLDivElement, AlertProps>(
169174
<Box
170175
{...safelySpreadBoxProps(props)}
171176
backgroundColor={AlertBackgroundColors[variant.toUpperCase() as AlertVariantKeys]}
172-
paddingLeft="space60"
173-
paddingRight="space60"
174-
paddingTop="space60"
175-
paddingBottom="space60"
177+
borderColor={AlertBorderColors[variant.toUpperCase() as AlertVariantKeys]}
178+
borderStyle="solid"
179+
borderWidth="borderWidth10"
180+
borderRadius="borderRadius30"
181+
padding="space50"
176182
element={element}
177183
variant={variant}
178184
ref={ref}
@@ -188,7 +194,7 @@ const Alert = React.forwardRef<HTMLDivElement, AlertProps>(
188194
{onDismiss && typeof onDismiss === "function" && (
189195
<MediaFigure align="end" spacing="space60">
190196
<Button onClick={onDismiss} variant="secondary_icon" size="reset" element={`${element}_DISMISS_BUTTON`}>
191-
<CloseIcon element={`${element}_DISMISS_ICON`} decorative size="sizeIcon20" />
197+
<CloseIcon element={`${element}_DISMISS_ICON`} color="colorTextIcon" decorative size="sizeIcon20" />
192198
<ScreenReaderOnly>{i18nDismissLabel}</ScreenReaderOnly>
193199
</Button>
194200
</MediaFigure>

packages/paste-core/components/callout/__tests__/index.spec.tsx

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { render, screen } from "@testing-library/react";
1+
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
22
import * as React from "react";
33

44
import { Callout, CalloutHeading, CalloutList, CalloutListItem, CalloutText } from "../src";
@@ -179,3 +179,47 @@ describe("i18n", () => {
179179
expect(icon?.textContent).toEqual("(nouveau)");
180180
});
181181
});
182+
183+
describe("Callout dismiss", () => {
184+
it("should not render close button if no dismiss funciton passed", () => {
185+
render(
186+
<Callout variant="neutral" data-testid="callout">
187+
<CalloutText>This is some text.</CalloutText>
188+
</Callout>,
189+
);
190+
191+
const callout = screen.getByTestId("callout");
192+
193+
const text = screen.getByText("This is some text.");
194+
expect(text).toBeTruthy();
195+
196+
const dismissButton = callout.querySelector('[data-paste-element="CALLOUT_DISMISS_BUTTON"]');
197+
expect(dismissButton).toBeNull();
198+
});
199+
200+
it("should render close button if dismiss function passed", async () => {
201+
const closeSpy = jest.fn();
202+
203+
render(
204+
<Callout variant="neutral" data-testid="callout" onDismiss={closeSpy}>
205+
<CalloutText>This is some text.</CalloutText>
206+
</Callout>,
207+
);
208+
209+
const callout = screen.getByTestId("callout");
210+
211+
const text = screen.getByText("This is some text.");
212+
expect(text).toBeTruthy();
213+
214+
const dismissButton = callout.querySelector('[data-paste-element="CALLOUT_DISMISS_BUTTON"]');
215+
expect(dismissButton).toBeTruthy();
216+
217+
expect(closeSpy).not.toHaveBeenCalled();
218+
219+
await waitFor(() => {
220+
fireEvent.click(dismissButton as HTMLElement);
221+
});
222+
223+
expect(closeSpy).toHaveBeenCalled();
224+
});
225+
});

packages/paste-core/components/callout/package.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,17 @@
2525
"tsc": "tsc"
2626
},
2727
"peerDependencies": {
28+
"@twilio-paste/anchor": "^12.0.0",
2829
"@twilio-paste/animation-library": "^2.0.0",
2930
"@twilio-paste/box": "^10.0.0",
31+
"@twilio-paste/button": "^14.0.0",
3032
"@twilio-paste/color-contrast-utils": "^5.0.0",
3133
"@twilio-paste/customization": "^8.0.0",
3234
"@twilio-paste/design-tokens": "^10.0.0",
3335
"@twilio-paste/icons": "^12.0.0",
3436
"@twilio-paste/screen-reader-only": "^13.0.0",
37+
"@twilio-paste/spinner": "^14.0.0",
38+
"@twilio-paste/stack": "^8.0.0",
3539
"@twilio-paste/style-props": "^9.0.0",
3640
"@twilio-paste/styling-library": "^3.0.0",
3741
"@twilio-paste/text": "^10.0.0",
@@ -44,13 +48,17 @@
4448
"react-dom": "^16.8.6 || ^17.0.2 || ^18.0.0"
4549
},
4650
"devDependencies": {
51+
"@twilio-paste/anchor": "^12.0.0",
4752
"@twilio-paste/animation-library": "^2.0.0",
4853
"@twilio-paste/box": "^10.1.0",
54+
"@twilio-paste/button": "^14.1.0",
4955
"@twilio-paste/color-contrast-utils": "^5.0.0",
5056
"@twilio-paste/customization": "^8.1.0",
5157
"@twilio-paste/design-tokens": "^10.2.0",
5258
"@twilio-paste/icons": "^12.2.0",
5359
"@twilio-paste/screen-reader-only": "^13.1.0",
60+
"@twilio-paste/spinner": "^14.0.0",
61+
"@twilio-paste/stack": "^8.0.0",
5462
"@twilio-paste/style-props": "^9.1.0",
5563
"@twilio-paste/styling-library": "^3.0.0",
5664
"@twilio-paste/text": "^10.1.0",

packages/paste-core/components/callout/src/Callout.tsx

Lines changed: 59 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { Box, safelySpreadBoxProps } from "@twilio-paste/box";
22
import type { BoxProps, BoxStyleProps } from "@twilio-paste/box";
3+
import { Button } from "@twilio-paste/button";
4+
import { CloseIcon } from "@twilio-paste/icons/esm/CloseIcon";
35
import { ErrorIcon } from "@twilio-paste/icons/esm/ErrorIcon";
46
import { NeutralIcon } from "@twilio-paste/icons/esm/NeutralIcon";
57
import { NewIcon } from "@twilio-paste/icons/esm/NewIcon";
@@ -43,33 +45,48 @@ export interface CalloutProps extends HTMLPasteProps<"div"> {
4345
* @memberof CalloutProps
4446
*/
4547
marginY?: BoxStyleProps["marginY"];
48+
/**
49+
* Function to run on dismiss of the Callout. Adds a close button.
50+
*
51+
* @default null
52+
* @memberof CalloutProps
53+
*/
54+
onDismiss?: () => void;
55+
/**
56+
* Title for dismiss label. Only necessary when using onDismiss.
57+
*
58+
* @default 'Dismiss callout'
59+
* @memberof CalloutProps
60+
* @type {string}
61+
*/
62+
i18nDismissLabel?: string;
4663
}
4764

4865
const variantStyles: Record<CalloutVariants, BoxStyleProps> = {
4966
success: {
50-
backgroundColor: "colorBackgroundSuccessWeakest",
67+
backgroundColor: "colorBackgroundWeak",
5168
color: "colorTextSuccess",
52-
borderColor: "colorBorderSuccessWeaker",
69+
borderColor: "colorBorderSuccessWeak",
5370
},
5471
error: {
5572
backgroundColor: "colorBackgroundErrorWeakest",
5673
color: "colorTextError",
57-
borderColor: "colorBorderErrorWeaker",
74+
borderColor: "colorBorderErrorWeak",
5875
},
5976
warning: {
60-
backgroundColor: "colorBackgroundWarningWeakest",
77+
backgroundColor: "colorBackgroundWeak",
6178
color: "colorTextWarningStrong",
62-
borderColor: "colorBorderWarningWeaker",
79+
borderColor: "colorBorderWarningWeak",
6380
},
6481
new: {
65-
backgroundColor: "colorBackgroundNewWeakest",
82+
backgroundColor: "colorBackgroundWeak",
6683
color: "colorTextNew",
67-
borderColor: "colorBorderNewWeaker",
84+
borderColor: "colorBorderNewWeak",
6885
},
6986
neutral: {
70-
backgroundColor: "colorBackgroundNeutralWeakest",
87+
backgroundColor: "colorBackgroundWeak",
7188
color: "colorTextNeutral",
72-
borderColor: "colorBorderNeutralWeaker",
89+
borderColor: "colorBorderNeutralWeak",
7390
},
7491
};
7592

@@ -90,7 +107,19 @@ const defaultIconLabels: Record<CalloutVariants, string> = {
90107
};
91108

92109
export const Callout = React.forwardRef<HTMLDivElement, CalloutProps>(
93-
({ children, variant, element = "CALLOUT", i18nLabel, marginY, ...props }, ref) => {
110+
(
111+
{
112+
children,
113+
variant,
114+
element = "CALLOUT",
115+
i18nLabel,
116+
marginY,
117+
onDismiss,
118+
i18nDismissLabel = "Dismiss callout",
119+
...props
120+
},
121+
ref,
122+
) => {
94123
const IconComponent = variantIcons[variant];
95124
const iconLabel = i18nLabel ? i18nLabel : defaultIconLabels[variant];
96125

@@ -100,19 +129,31 @@ export const Callout = React.forwardRef<HTMLDivElement, CalloutProps>(
100129
ref={ref}
101130
element={element}
102131
display="flex"
132+
flexDirection="column"
103133
marginY={marginY}
104-
padding="space60"
105-
borderStyle="solid"
106-
borderWidth="borderWidth10"
107-
borderRadius="borderRadius30"
134+
rowGap="space50"
135+
paddingTop="space70"
136+
paddingLeft="space70"
137+
paddingRight="space70"
138+
paddingBottom="space90"
139+
borderLeftStyle="solid"
140+
borderLeftWidth="borderWidth20"
108141
variant={variant}
109142
{...variantStyles[variant]}
110143
>
111-
<Box marginRight="space60" paddingTop="space10" element={`${element}_ICON`}>
112-
{IconComponent}
113-
<ScreenReaderOnly>{iconLabel}</ScreenReaderOnly>
144+
<Box display="flex" justifyContent="space-between">
145+
<Box element={`${element}_ICON`}>
146+
{IconComponent}
147+
<ScreenReaderOnly>{iconLabel}</ScreenReaderOnly>
148+
</Box>
149+
{onDismiss && typeof onDismiss === "function" && (
150+
<Button onClick={onDismiss} variant="secondary_icon" size="reset" element={`${element}_DISMISS_BUTTON`}>
151+
<CloseIcon element={`${element}_DISMISS_ICON`} decorative size="sizeIcon20" />
152+
<ScreenReaderOnly>{i18nDismissLabel}</ScreenReaderOnly>
153+
</Button>
154+
)}
114155
</Box>
115-
<Box display="flex" flexDirection="column" rowGap="space50" flex="1">
156+
<Box display="flex" flexDirection="column" rowGap="space30" flex="1">
116157
{children}
117158
</Box>
118159
</Box>

packages/paste-core/components/callout/stories/index.stories.tsx

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,46 @@ export const CalloutWithMargin: StoryFn = () => (
8585
</Callout>
8686
</>
8787
);
88+
89+
export const DismissibleVsNonDismissibleVariantsWithDetailedBody: StoryFn = () => {
90+
return (
91+
<Box display="flex" flexDirection="column" rowGap="space60">
92+
{/* eslint-disable-next-line no-console */}
93+
<Callout variant="new" onDismiss={() => console.log("dismissed!")}>
94+
<CalloutHeading as="h3">New callout</CalloutHeading>
95+
<CalloutText>Take a look at this list:</CalloutText>
96+
<ExampleList as="ol" />
97+
</Callout>
98+
{/* eslint-disable-next-line no-console */}
99+
<Callout variant="neutral" onDismiss={() => console.log("dismissed!")}>
100+
<CalloutHeading as="h3">Neutral callout</CalloutHeading>
101+
<CalloutText>
102+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas bibendum lectus in congue dignissim. Donec
103+
id blandit justo. Nam dignissim eros sem, sit amet commodo enim dapibus nec. Pellentesque at euismod mi.
104+
Aenean dignissim fringilla ipsum, ac mattis justo vehicula sed. Nulla non vestibulum sapien, ut mollis leo.
105+
Aliquam iaculis urna vel efficitur suscipit. Maecenas id mi vel est elementum varius. Maecenas lobortis, nisi
106+
ut pulvinar accumsan, ante felis consequat neque, id mollis mi eros non eros. Quisque sagittis euismod enim ut
107+
lobortis. Etiam ac diam efficitur, dapibus ligula sit amet, auctor ex. Cras lacinia, lacus id consequat
108+
sollicitudin, augue ex laoreet felis, ac blandit tellus erat vel felis.
109+
</CalloutText>
110+
</Callout>
111+
<Callout variant="new">
112+
<CalloutHeading as="h3">New callout</CalloutHeading>
113+
<CalloutText>Take a look at this list:</CalloutText>
114+
<ExampleList as="ol" />
115+
</Callout>
116+
<Callout variant="neutral">
117+
<CalloutHeading as="h3">Neutral callout</CalloutHeading>
118+
<CalloutText>
119+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas bibendum lectus in congue dignissim. Donec
120+
id blandit justo. Nam dignissim eros sem, sit amet commodo enim dapibus nec. Pellentesque at euismod mi.
121+
Aenean dignissim fringilla ipsum, ac mattis justo vehicula sed. Nulla non vestibulum sapien, ut mollis leo.
122+
Aliquam iaculis urna vel efficitur suscipit. Maecenas id mi vel est elementum varius. Maecenas lobortis, nisi
123+
ut pulvinar accumsan, ante felis consequat neque, id mollis mi eros non eros. Quisque sagittis euismod enim ut
124+
lobortis. Etiam ac diam efficitur, dapibus ligula sit amet, auctor ex. Cras lacinia, lacus id consequat
125+
sollicitudin, augue ex laoreet felis, ac blandit tellus erat vel felis.
126+
</CalloutText>
127+
</Callout>
128+
</Box>
129+
);
130+
};

packages/paste-core/components/callout/type-docs.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,13 @@
433433
"required": false,
434434
"externalProp": true
435435
},
436+
"i18nDismissLabel": {
437+
"type": "string",
438+
"defaultValue": "'Dismiss callout'",
439+
"required": false,
440+
"externalProp": false,
441+
"description": "Title for dismiss label. Only necessary when using onDismiss."
442+
},
436443
"i18nLabel": {
437444
"type": "string",
438445
"defaultValue": "'(neutral)' | '(warning)' | '(error)' | '(success)' | '(new)'",
@@ -724,6 +731,13 @@
724731
"required": false,
725732
"externalProp": true
726733
},
734+
"onDismiss": {
735+
"type": "() => void",
736+
"defaultValue": "null",
737+
"required": false,
738+
"externalProp": false,
739+
"description": "Function to run on dismiss of the Callout. Adds a close button."
740+
},
727741
"onDoubleClick": {
728742
"type": "MouseEventHandler<HTMLDivElement>",
729743
"defaultValue": null,

0 commit comments

Comments
 (0)