Skip to content

Commit cebfc83

Browse files
authored
feat: add Alert component
1 parent 028c91e commit cebfc83

File tree

5 files changed

+359
-0
lines changed

5 files changed

+359
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2+
3+
exports[`Button component > renders correctly for variant default 1`] = `
4+
<div>
5+
<div
6+
class="relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground bg-background text-foreground"
7+
role="alert"
8+
>
9+
<svg
10+
class="lucide lucide-info"
11+
fill="none"
12+
height="24"
13+
stroke="currentColor"
14+
stroke-linecap="round"
15+
stroke-linejoin="round"
16+
stroke-width="2"
17+
viewBox="0 0 24 24"
18+
width="24"
19+
xmlns="http://www.w3.org/2000/svg"
20+
>
21+
<circle
22+
cx="12"
23+
cy="12"
24+
r="10"
25+
/>
26+
<path
27+
d="M12 16v-4"
28+
/>
29+
<path
30+
d="M12 8h.01"
31+
/>
32+
</svg>
33+
<h5
34+
class="mb-1 font-medium leading-none tracking-tight"
35+
>
36+
default
37+
Alert
38+
</h5>
39+
<div
40+
class="text-sm [&_p]:leading-relaxed"
41+
>
42+
Description of altert of variant
43+
default
44+
</div>
45+
</div>
46+
</div>
47+
`;
48+
49+
exports[`Button component > renders correctly for variant destructive 1`] = `
50+
<div>
51+
<div
52+
class="relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive"
53+
role="alert"
54+
>
55+
<svg
56+
class="lucide lucide-info"
57+
fill="none"
58+
height="24"
59+
stroke="currentColor"
60+
stroke-linecap="round"
61+
stroke-linejoin="round"
62+
stroke-width="2"
63+
viewBox="0 0 24 24"
64+
width="24"
65+
xmlns="http://www.w3.org/2000/svg"
66+
>
67+
<circle
68+
cx="12"
69+
cy="12"
70+
r="10"
71+
/>
72+
<path
73+
d="M12 16v-4"
74+
/>
75+
<path
76+
d="M12 8h.01"
77+
/>
78+
</svg>
79+
<h5
80+
class="mb-1 font-medium leading-none tracking-tight"
81+
>
82+
destructive
83+
Alert
84+
</h5>
85+
<div
86+
class="text-sm [&_p]:leading-relaxed"
87+
>
88+
Description of altert of variant
89+
destructive
90+
</div>
91+
</div>
92+
</div>
93+
`;
94+
95+
exports[`Button component > renders correctly for variant error 1`] = `
96+
<div>
97+
<div
98+
class="relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 border-red-500 bg-red-100 [&>svg]:text-red-500"
99+
role="alert"
100+
>
101+
<svg
102+
class="lucide lucide-info"
103+
fill="none"
104+
height="24"
105+
stroke="currentColor"
106+
stroke-linecap="round"
107+
stroke-linejoin="round"
108+
stroke-width="2"
109+
viewBox="0 0 24 24"
110+
width="24"
111+
xmlns="http://www.w3.org/2000/svg"
112+
>
113+
<circle
114+
cx="12"
115+
cy="12"
116+
r="10"
117+
/>
118+
<path
119+
d="M12 16v-4"
120+
/>
121+
<path
122+
d="M12 8h.01"
123+
/>
124+
</svg>
125+
<h5
126+
class="mb-1 font-medium leading-none tracking-tight"
127+
>
128+
error
129+
Alert
130+
</h5>
131+
<div
132+
class="text-sm [&_p]:leading-relaxed"
133+
>
134+
Description of altert of variant
135+
error
136+
</div>
137+
</div>
138+
</div>
139+
`;
140+
141+
exports[`Button component > renders correctly for variant warning 1`] = `
142+
<div>
143+
<div
144+
class="relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 border-yellow-500 bg-yellow-100 [&>svg]:text-yellow-500"
145+
role="alert"
146+
>
147+
<svg
148+
class="lucide lucide-info"
149+
fill="none"
150+
height="24"
151+
stroke="currentColor"
152+
stroke-linecap="round"
153+
stroke-linejoin="round"
154+
stroke-width="2"
155+
viewBox="0 0 24 24"
156+
width="24"
157+
xmlns="http://www.w3.org/2000/svg"
158+
>
159+
<circle
160+
cx="12"
161+
cy="12"
162+
r="10"
163+
/>
164+
<path
165+
d="M12 16v-4"
166+
/>
167+
<path
168+
d="M12 8h.01"
169+
/>
170+
</svg>
171+
<h5
172+
class="mb-1 font-medium leading-none tracking-tight"
173+
>
174+
warning
175+
Alert
176+
</h5>
177+
<div
178+
class="text-sm [&_p]:leading-relaxed"
179+
>
180+
Description of altert of variant
181+
warning
182+
</div>
183+
</div>
184+
</div>
185+
`;

src/components/alert.spec.tsx

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { render } from "@testing-library/react";
2+
import { InfoIcon } from "lucide-react";
3+
import { describe, expect, it } from "vitest";
4+
import { Alert, AlertDescription, AlertProps, AlertTitle } from "./alert";
5+
6+
type Variant = NonNullable<AlertProps["variant"]>;
7+
8+
const variants: Variant[] = ["default", "destructive", "error", "warning"];
9+
10+
describe("Button component", () => {
11+
it.each(variants)("renders correctly for variant %s", (variant) => {
12+
const { container } = render(
13+
<Alert variant={variant}>
14+
<InfoIcon />
15+
<AlertTitle>{variant} Alert</AlertTitle>
16+
<AlertDescription>
17+
Description of altert of variant {variant}
18+
</AlertDescription>
19+
</Alert>
20+
);
21+
expect(container).toMatchSnapshot();
22+
});
23+
});

src/components/alert.stories.tsx

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import type { Meta, StoryObj } from "@storybook/react";
2+
import { fn } from "@storybook/test";
3+
4+
import {
5+
AlertCircle,
6+
AlertCircleIcon,
7+
AlertTriangleIcon,
8+
InfoIcon,
9+
} from "lucide-react";
10+
import { Alert, AlertDescription, AlertTitle } from "./alert";
11+
12+
const meta = {
13+
title: "Alert",
14+
component: Alert,
15+
parameters: {
16+
layout: "centered",
17+
},
18+
argTypes: {
19+
variant: {
20+
options: ["default", "destructive", "error", "warning"],
21+
control: "select",
22+
},
23+
},
24+
args: { onClick: fn() },
25+
} satisfies Meta<typeof Alert>;
26+
27+
export default meta;
28+
type Story = StoryObj<typeof meta>;
29+
30+
export const Example: Story = {
31+
render(props) {
32+
return (
33+
<Alert {...props}>
34+
<InfoIcon className="h-4 w-4" />
35+
<AlertTitle>Heads up!</AlertTitle>
36+
<AlertDescription>
37+
Message explaining why the heads up.
38+
</AlertDescription>
39+
</Alert>
40+
);
41+
},
42+
};
43+
44+
export const Destructive: Story = {
45+
args: {
46+
variant: "destructive",
47+
},
48+
render(props) {
49+
return (
50+
<Alert {...props}>
51+
<AlertCircle className="h-4 w-4" />
52+
<AlertTitle>Error</AlertTitle>
53+
<AlertDescription>Message explaining the error.</AlertDescription>
54+
</Alert>
55+
);
56+
},
57+
};
58+
59+
export const Error: Story = {
60+
args: {
61+
variant: "error",
62+
},
63+
render(props) {
64+
return (
65+
<Alert {...props}>
66+
<AlertCircleIcon className="h-4 w-4" />
67+
<AlertTitle>Error</AlertTitle>
68+
<AlertDescription>Message explaining the error.</AlertDescription>
69+
</Alert>
70+
);
71+
},
72+
};
73+
74+
export const Warning: Story = {
75+
args: {
76+
variant: "warning",
77+
},
78+
render(props) {
79+
return (
80+
<Alert {...props}>
81+
<AlertTriangleIcon className="h-4 w-4" />
82+
<AlertTitle>Warning</AlertTitle>
83+
<AlertDescription>Message explaining the warning.</AlertDescription>
84+
</Alert>
85+
);
86+
},
87+
};

src/components/alert.tsx

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import * as React from "react";
2+
import { cva, type VariantProps } from "class-variance-authority";
3+
4+
import { cn } from "@/lib/utils";
5+
6+
export type AlertProps = React.HTMLAttributes<HTMLDivElement> &
7+
VariantProps<typeof alertVariants>;
8+
9+
const alertVariants = cva(
10+
"relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
11+
{
12+
variants: {
13+
variant: {
14+
default: "bg-background text-foreground",
15+
destructive:
16+
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
17+
error: "border-red-500 bg-red-100 [&>svg]:text-red-500",
18+
warning: "border-yellow-500 bg-yellow-100 [&>svg]:text-yellow-500",
19+
},
20+
},
21+
defaultVariants: {
22+
variant: "default",
23+
},
24+
}
25+
);
26+
27+
const Alert = React.forwardRef<HTMLDivElement, AlertProps>(
28+
({ className, variant, ...props }, ref) => (
29+
<div
30+
ref={ref}
31+
role="alert"
32+
className={cn(alertVariants({ variant }), className)}
33+
{...props}
34+
/>
35+
)
36+
);
37+
Alert.displayName = "Alert";
38+
39+
const AlertTitle = React.forwardRef<
40+
HTMLParagraphElement,
41+
React.HTMLAttributes<HTMLHeadingElement>
42+
>(({ className, ...props }, ref) => (
43+
<h5
44+
ref={ref}
45+
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
46+
{...props}
47+
/>
48+
));
49+
AlertTitle.displayName = "AlertTitle";
50+
51+
const AlertDescription = React.forwardRef<
52+
HTMLParagraphElement,
53+
React.HTMLAttributes<HTMLParagraphElement>
54+
>(({ className, ...props }, ref) => (
55+
<div
56+
ref={ref}
57+
className={cn("text-sm [&_p]:leading-relaxed", className)}
58+
{...props}
59+
/>
60+
));
61+
AlertDescription.displayName = "AlertDescription";
62+
63+
export { Alert, AlertTitle, AlertDescription };

src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ export * from "@/components/form";
44
export * from "@/components/input";
55
export * from "@/components/label";
66
export * from "@/components/skeleton";
7+
export * from "@/components/alert";
78
export { cn } from "@/lib/utils";

0 commit comments

Comments
 (0)