feat(ui): alert component

This commit is contained in:
Brian McGee
2025-07-04 10:20:58 +01:00
parent 1b34aa06a4
commit a5dfb25dc7
3 changed files with 220 additions and 0 deletions

View File

@@ -0,0 +1,39 @@
div.alert {
@apply flex gap-2.5 px-6 py-4 size-full rounded-md items-start;
&.has-icon {
@apply pl-4;
svg.icon {
@apply relative top-0.5;
}
}
&.has-dismiss {
@apply pr-4;
}
& > div.content {
@apply flex flex-col gap-2 size-full;
}
&.info {
@apply bg-semantic-info-1 border border-semantic-info-3 fg-semantic-info-3;
}
&.error {
@apply bg-semantic-error-2 border border-semantic-error-3 fg-semantic-error-3;
}
&.warning {
@apply bg-semantic-warning-2 border border-semantic-warning-3 fg-semantic-warning-3;
}
&.success {
@apply bg-semantic-success-1 border border-semantic-success-3 fg-semantic-success-3;
}
& > button.dismiss-trigger {
@apply relative top-0.5;
}
}

View File

@@ -0,0 +1,138 @@
import type { Meta, StoryObj } from "@kachurun/storybook-solid";
import { Alert, AlertProps } from "@/src/components/v2/Alert/Alert";
import { expect, fn } from "storybook/test";
import { StoryContext } from "@kachurun/storybook-solid-vite";
const meta: Meta<AlertProps> = {
title: "Components/Alert",
component: Alert,
decorators: [
(Story: StoryObj) => (
<div class="w-72">
<Story />
</div>
),
],
};
export default meta;
type Story = StoryObj<AlertProps>;
export const Info: Story = {
args: {
type: "info",
title: "Headline",
description:
"Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.",
},
};
export const Error: Story = {
args: {
...Info.args,
type: "error",
},
};
export const Warning: Story = {
args: {
...Info.args,
type: "warning",
},
};
export const Success: Story = {
args: {
...Info.args,
type: "success",
},
};
export const InfoIcon: Story = {
args: {
...Info.args,
icon: "Info",
},
};
export const ErrorIcon: Story = {
args: {
...Error.args,
icon: "WarningFilled",
},
};
export const WarningIcon: Story = {
args: {
...Warning.args,
icon: "WarningFilled",
},
};
export const SuccessIcon: Story = {
args: {
...Success.args,
icon: "Checkmark",
},
};
export const InfoDismiss: Story = {
args: {
...Info.args,
onDismiss: fn(),
play: async ({ canvas, step, userEvent, args }: StoryContext) => {
await userEvent.click(canvas.getByRole("button"));
await expect(args.onDismiss).toHaveBeenCalled();
},
},
};
export const ErrorDismiss: Story = {
args: {
...InfoDismiss.args,
type: "error",
},
};
export const WarningDismiss: Story = {
args: {
...InfoDismiss.args,
type: "warning",
},
};
export const SuccessDismiss: Story = {
args: {
...InfoDismiss.args,
type: "success",
},
};
export const InfoIconDismiss: Story = {
args: {
...InfoDismiss.args,
icon: "Info",
},
};
export const ErrorIconDismiss: Story = {
args: {
...ErrorDismiss.args,
icon: "WarningFilled",
},
};
export const WarningIconDismiss: Story = {
args: {
...WarningDismiss.args,
icon: "WarningFilled",
},
};
export const SuccessIconDismiss: Story = {
args: {
...SuccessDismiss.args,
icon: "Checkmark",
},
};

View File

@@ -0,0 +1,43 @@
import "./Alert.css";
import cx from "classnames";
import Icon, { IconVariant } from "@/src/components/v2/Icon/Icon";
import { Typography } from "@/src/components/v2/Typography/Typography";
import { Button } from "@kobalte/core/button";
import { Alert as KAlert } from "@kobalte/core/alert";
export interface AlertProps {
type: "success" | "error" | "warning" | "info";
title: string;
description: string;
icon?: IconVariant;
onDismiss?: () => void;
}
export const Alert = (props: AlertProps) => (
<KAlert
class={cx("alert", props.type, {
"has-icon": props.icon,
"has-dismiss": props.onDismiss,
})}
>
{props.icon && <Icon icon={props.icon} color="inherit" size="1rem" />}
<div class="content">
<Typography hierarchy="body" size="default" weight="bold" color="inherit">
{props.title}
</Typography>
<Typography hierarchy="body" size="xs" color="inherit">
{props.description}
</Typography>
</div>
{props.onDismiss && (
<Button
name="dismiss-alert"
class="dismiss-trigger"
onClick={props.onDismiss}
aria-label={`Dismiss ${props.type} alert`}
>
<Icon icon="Close" color="primary" size="0.75rem" />
</Button>
)}
</KAlert>
);