From a5dfb25dc753beaec34808a9cf529d56b335edce Mon Sep 17 00:00:00 2001 From: Brian McGee Date: Fri, 4 Jul 2025 10:20:58 +0100 Subject: [PATCH] feat(ui): alert component --- .../ui/src/components/v2/Alert/Alert.css | 39 +++++ .../src/components/v2/Alert/Alert.stories.tsx | 138 ++++++++++++++++++ .../ui/src/components/v2/Alert/Alert.tsx | 43 ++++++ 3 files changed, 220 insertions(+) create mode 100644 pkgs/clan-app/ui/src/components/v2/Alert/Alert.css create mode 100644 pkgs/clan-app/ui/src/components/v2/Alert/Alert.stories.tsx create mode 100644 pkgs/clan-app/ui/src/components/v2/Alert/Alert.tsx diff --git a/pkgs/clan-app/ui/src/components/v2/Alert/Alert.css b/pkgs/clan-app/ui/src/components/v2/Alert/Alert.css new file mode 100644 index 000000000..0fd90cc0d --- /dev/null +++ b/pkgs/clan-app/ui/src/components/v2/Alert/Alert.css @@ -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; + } +} diff --git a/pkgs/clan-app/ui/src/components/v2/Alert/Alert.stories.tsx b/pkgs/clan-app/ui/src/components/v2/Alert/Alert.stories.tsx new file mode 100644 index 000000000..5434becd7 --- /dev/null +++ b/pkgs/clan-app/ui/src/components/v2/Alert/Alert.stories.tsx @@ -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 = { + title: "Components/Alert", + component: Alert, + decorators: [ + (Story: StoryObj) => ( +
+ +
+ ), + ], +}; + +export default meta; + +type Story = StoryObj; + +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", + }, +}; diff --git a/pkgs/clan-app/ui/src/components/v2/Alert/Alert.tsx b/pkgs/clan-app/ui/src/components/v2/Alert/Alert.tsx new file mode 100644 index 000000000..7dc156a2f --- /dev/null +++ b/pkgs/clan-app/ui/src/components/v2/Alert/Alert.tsx @@ -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) => ( + + {props.icon && } +
+ + {props.title} + + + {props.description} + +
+ {props.onDismiss && ( + + )} +
+);