Skip to content

Add Alert component #10

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 185 additions & 0 deletions src/components/__snapshots__/alert.spec.tsx.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`Button component > renders correctly for variant default 1`] = `
<div>
<div
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"
role="alert"
>
<svg
class="lucide lucide-info"
fill="none"
height="24"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<circle
cx="12"
cy="12"
r="10"
/>
<path
d="M12 16v-4"
/>
<path
d="M12 8h.01"
/>
</svg>
<h5
class="mb-1 font-medium leading-none tracking-tight"
>
default
Alert
</h5>
<div
class="text-sm [&_p]:leading-relaxed"
>
Description of altert of variant
default
</div>
</div>
</div>
`;

exports[`Button component > renders correctly for variant destructive 1`] = `
<div>
<div
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"
role="alert"
>
<svg
class="lucide lucide-info"
fill="none"
height="24"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<circle
cx="12"
cy="12"
r="10"
/>
<path
d="M12 16v-4"
/>
<path
d="M12 8h.01"
/>
</svg>
<h5
class="mb-1 font-medium leading-none tracking-tight"
>
destructive
Alert
</h5>
<div
class="text-sm [&_p]:leading-relaxed"
>
Description of altert of variant
destructive
</div>
</div>
</div>
`;

exports[`Button component > renders correctly for variant error 1`] = `
<div>
<div
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"
role="alert"
>
<svg
class="lucide lucide-info"
fill="none"
height="24"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<circle
cx="12"
cy="12"
r="10"
/>
<path
d="M12 16v-4"
/>
<path
d="M12 8h.01"
/>
</svg>
<h5
class="mb-1 font-medium leading-none tracking-tight"
>
error
Alert
</h5>
<div
class="text-sm [&_p]:leading-relaxed"
>
Description of altert of variant
error
</div>
</div>
</div>
`;

exports[`Button component > renders correctly for variant warning 1`] = `
<div>
<div
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"
role="alert"
>
<svg
class="lucide lucide-info"
fill="none"
height="24"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<circle
cx="12"
cy="12"
r="10"
/>
<path
d="M12 16v-4"
/>
<path
d="M12 8h.01"
/>
</svg>
<h5
class="mb-1 font-medium leading-none tracking-tight"
>
warning
Alert
</h5>
<div
class="text-sm [&_p]:leading-relaxed"
>
Description of altert of variant
warning
</div>
</div>
</div>
`;
23 changes: 23 additions & 0 deletions src/components/alert.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { render } from "@testing-library/react";
import { InfoIcon } from "lucide-react";
import { describe, expect, it } from "vitest";
import { Alert, AlertDescription, AlertProps, AlertTitle } from "./alert";

type Variant = NonNullable<AlertProps["variant"]>;

const variants: Variant[] = ["default", "destructive", "error", "warning"];

describe("Button component", () => {
it.each(variants)("renders correctly for variant %s", (variant) => {
const { container } = render(
<Alert variant={variant}>
<InfoIcon />
<AlertTitle>{variant} Alert</AlertTitle>
<AlertDescription>
Description of altert of variant {variant}
</AlertDescription>
</Alert>
);
expect(container).toMatchSnapshot();
});
});
87 changes: 87 additions & 0 deletions src/components/alert.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import type { Meta, StoryObj } from "@storybook/react";
import { fn } from "@storybook/test";

import {
AlertCircle,
AlertCircleIcon,
AlertTriangleIcon,
InfoIcon,
} from "lucide-react";
import { Alert, AlertDescription, AlertTitle } from "./alert";

const meta = {
title: "Alert",
component: Alert,
parameters: {
layout: "centered",
},
argTypes: {
variant: {
options: ["default", "destructive", "error", "warning"],
control: "select",
},
},
args: { onClick: fn() },
} satisfies Meta<typeof Alert>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Example: Story = {
render(props) {
return (
<Alert {...props}>
<InfoIcon className="h-4 w-4" />
<AlertTitle>Heads up!</AlertTitle>
<AlertDescription>
Message explaining why the heads up.
</AlertDescription>
</Alert>
);
},
};

export const Destructive: Story = {
args: {
variant: "destructive",
},
render(props) {
return (
<Alert {...props}>
<AlertCircle className="h-4 w-4" />
<AlertTitle>Error</AlertTitle>
<AlertDescription>Message explaining the error.</AlertDescription>
</Alert>
);
},
};

export const Error: Story = {
args: {
variant: "error",
},
render(props) {
return (
<Alert {...props}>
<AlertCircleIcon className="h-4 w-4" />
<AlertTitle>Error</AlertTitle>
<AlertDescription>Message explaining the error.</AlertDescription>
</Alert>
);
},
};

export const Warning: Story = {
args: {
variant: "warning",
},
render(props) {
return (
<Alert {...props}>
<AlertTriangleIcon className="h-4 w-4" />
<AlertTitle>Warning</AlertTitle>
<AlertDescription>Message explaining the warning.</AlertDescription>
</Alert>
);
},
};
63 changes: 63 additions & 0 deletions src/components/alert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";

import { cn } from "@/lib/utils";

export type AlertProps = React.HTMLAttributes<HTMLDivElement> &
VariantProps<typeof alertVariants>;

const alertVariants = cva(
"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",
{
variants: {
variant: {
default: "bg-background text-foreground",
destructive:
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
error: "border-red-500 bg-red-100 [&>svg]:text-red-500",
warning: "border-yellow-500 bg-yellow-100 [&>svg]:text-yellow-500",
},
},
defaultVariants: {
variant: "default",
},
}
);

const Alert = React.forwardRef<HTMLDivElement, AlertProps>(
({ className, variant, ...props }, ref) => (
<div
ref={ref}
role="alert"
className={cn(alertVariants({ variant }), className)}
{...props}
/>
)
);
Alert.displayName = "Alert";

const AlertTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h5
ref={ref}
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
{...props}
/>
));
AlertTitle.displayName = "AlertTitle";

const AlertDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("text-sm [&_p]:leading-relaxed", className)}
{...props}
/>
));
AlertDescription.displayName = "AlertDescription";

export { Alert, AlertTitle, AlertDescription };
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export * from "@/components/form";
export * from "@/components/input";
export * from "@/components/label";
export * from "@/components/skeleton";
export * from "@/components/alert";
export { cn } from "@/lib/utils";