Skip to content

Commit 8e31ed4

Browse files
refactor(site): Refactor alerts (#7587)
1 parent 63a9e34 commit 8e31ed4

File tree

45 files changed

+407
-543
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+407
-543
lines changed

site/.eslintrc.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,10 @@ rules:
128128
message:
129129
"You should use the Avatar component provided on
130130
components/Avatar/Avatar"
131+
- name: "@mui/material/Alert"
132+
message:
133+
"You should use the Alert component provided on
134+
components/Alert/Alert"
131135
no-unused-vars: "off"
132136
"object-curly-spacing": "off"
133137
react-hooks/exhaustive-deps: warn
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { Alert } from "./Alert"
2+
import Button from "@mui/material/Button"
3+
import Link from "@mui/material/Link"
4+
import type { Meta, StoryObj } from "@storybook/react"
5+
6+
const meta: Meta<typeof Alert> = {
7+
title: "components/Alert",
8+
component: Alert,
9+
}
10+
11+
export default meta
12+
type Story = StoryObj<typeof Alert>
13+
14+
const ExampleAction = (
15+
<Button onClick={() => null} size="small" variant="text">
16+
Button
17+
</Button>
18+
)
19+
20+
export const Warning: Story = {
21+
args: {
22+
children: "This is a warning",
23+
severity: "warning",
24+
},
25+
}
26+
27+
export const WarningWithDismiss: Story = {
28+
args: {
29+
children: "This is a warning",
30+
dismissible: true,
31+
severity: "warning",
32+
},
33+
}
34+
35+
export const WarningWithAction: Story = {
36+
args: {
37+
children: "This is a warning",
38+
actions: [ExampleAction],
39+
severity: "warning",
40+
},
41+
}
42+
43+
export const WarningWithActionAndDismiss: Story = {
44+
args: {
45+
children: "This is a warning",
46+
actions: [ExampleAction],
47+
dismissible: true,
48+
severity: "warning",
49+
},
50+
}
51+
52+
export const WithChildren: Story = {
53+
args: {
54+
severity: "warning",
55+
children: (
56+
<div>
57+
This is a message with a <Link href="#">link</Link>
58+
</div>
59+
),
60+
},
61+
}

site/src/components/Alert/Alert.tsx

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { useState, FC, ReactNode, PropsWithChildren } from "react"
2+
import Collapse from "@mui/material/Collapse"
3+
// eslint-disable-next-line no-restricted-imports -- It is the base component
4+
import MuiAlert, { AlertProps as MuiAlertProps } from "@mui/material/Alert"
5+
import Button from "@mui/material/Button"
6+
7+
export interface AlertProps extends PropsWithChildren {
8+
severity: MuiAlertProps["severity"]
9+
actions?: ReactNode[]
10+
dismissible?: boolean
11+
onRetry?: () => void
12+
onDismiss?: () => void
13+
}
14+
15+
export const Alert: FC<AlertProps> = ({
16+
children,
17+
actions = [],
18+
onRetry,
19+
dismissible,
20+
severity,
21+
onDismiss,
22+
}) => {
23+
const [open, setOpen] = useState(true)
24+
25+
return (
26+
<Collapse in={open}>
27+
<MuiAlert
28+
severity={severity}
29+
action={
30+
<>
31+
{/* CTAs passed in by the consumer */}
32+
{actions.length > 0 &&
33+
actions.map((action) => <div key={String(action)}>{action}</div>)}
34+
35+
{/* retry CTA */}
36+
{onRetry && (
37+
<Button variant="text" size="small" onClick={onRetry}>
38+
Retry
39+
</Button>
40+
)}
41+
42+
{/* close CTA */}
43+
{dismissible && (
44+
<Button
45+
variant="text"
46+
size="small"
47+
onClick={() => {
48+
setOpen(false)
49+
onDismiss && onDismiss()
50+
}}
51+
data-testid="dismiss-banner-btn"
52+
>
53+
Dismiss
54+
</Button>
55+
)}
56+
</>
57+
}
58+
>
59+
{children}
60+
</MuiAlert>
61+
</Collapse>
62+
)
63+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import Button from "@mui/material/Button"
2+
import { mockApiError } from "testHelpers/entities"
3+
import type { Meta, StoryObj } from "@storybook/react"
4+
import { action } from "@storybook/addon-actions"
5+
import { ErrorAlert } from "./ErrorAlert"
6+
7+
const mockError = mockApiError({
8+
message: "Email or password was invalid",
9+
detail: "Password is invalid",
10+
})
11+
12+
const meta: Meta<typeof ErrorAlert> = {
13+
title: "components/ErrorAlert",
14+
component: ErrorAlert,
15+
args: {
16+
error: mockError,
17+
dismissible: false,
18+
onRetry: undefined,
19+
},
20+
}
21+
22+
export default meta
23+
type Story = StoryObj<typeof ErrorAlert>
24+
25+
const ExampleAction = (
26+
<Button onClick={() => null} size="small" variant="text">
27+
Button
28+
</Button>
29+
)
30+
31+
export const WithOnlyMessage: Story = {
32+
args: {
33+
error: mockApiError({
34+
message: "Email or password was invalid",
35+
}),
36+
},
37+
}
38+
39+
export const WithDismiss: Story = {
40+
args: {
41+
dismissible: true,
42+
},
43+
}
44+
45+
export const WithAction: Story = {
46+
args: {
47+
actions: [ExampleAction],
48+
},
49+
}
50+
51+
export const WithActionAndDismiss: Story = {
52+
args: {
53+
actions: [ExampleAction],
54+
dismissible: true,
55+
},
56+
}
57+
58+
export const WithRetry: Story = {
59+
args: {
60+
onRetry: action("retry"),
61+
dismissible: true,
62+
},
63+
}
64+
65+
export const WithActionRetryAndDismiss: Story = {
66+
args: {
67+
actions: [ExampleAction],
68+
onRetry: action("retry"),
69+
dismissible: true,
70+
},
71+
}
72+
73+
export const WithNonApiError: Story = {
74+
args: {
75+
error: new Error("Non API error here"),
76+
},
77+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { AlertProps, Alert } from "./Alert"
2+
import AlertTitle from "@mui/material/AlertTitle"
3+
import Box from "@mui/material/Box"
4+
import { getErrorMessage, getErrorDetail } from "api/errors"
5+
import { FC } from "react"
6+
7+
export const ErrorAlert: FC<
8+
Omit<AlertProps, "severity" | "children"> & { error: unknown }
9+
> = ({ error, ...alertProps }) => {
10+
const message = getErrorMessage(error, "Something went wrong.")
11+
const detail = getErrorDetail(error)
12+
13+
return (
14+
<Alert severity="error" {...alertProps}>
15+
{detail ? (
16+
<>
17+
<AlertTitle>{message}</AlertTitle>
18+
<Box
19+
component="span"
20+
color={(theme) => theme.palette.text.secondary}
21+
fontSize={13}
22+
data-chromatic="ignore"
23+
>
24+
{detail}
25+
</Box>
26+
</>
27+
) : (
28+
message
29+
)}
30+
</Alert>
31+
)
32+
}

site/src/components/AlertBanner/AlertBanner.stories.tsx

Lines changed: 0 additions & 122 deletions
This file was deleted.

0 commit comments

Comments
 (0)