Skip to content
Open
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
136 changes: 73 additions & 63 deletions packages/demo/src/content/components/alert.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@ heading: "Alert"
description: "Alert with sizes and variants"
---

import { Alert, AlertTitle, AlertDescription } from "@eqtylab/equality";
import { Alert } from "@eqtylab/equality";

## Overview

---

The Alert component displays important messages to users, usually as a result of an action they took. Use alerts to communicate status, warnings, errors, or success states.

- **Default:** General information.
- **Primary:** General information.
- **Neutral:** Low-emphasis, informational messages.
- **Success:** Positive outcomes or confirmations.
- **Warning:** Cautionary information that needs attention.
- **Danger:** Critical errors or destructive actions.
Expand All @@ -24,90 +25,99 @@ The Alert component displays important messages to users, usually as a result of
Import the component:

```tsx
import { Alert, AlertTitle, AlertDescription } from "@eqtylab/equality";
import { Alert } from "@eqtylab/equality";
```

Basic usage with title and description:

```tsx
<Alert>
<AlertTitle>Heads up!</AlertTitle>
<AlertDescription>This is an important message.</AlertDescription>
</Alert>
<Alert title="Heads up!" description="This is an important message." />
```

## Variants

---

<div className="flex flex-col gap-4">
<Alert>
<AlertTitle>Default</AlertTitle>
<AlertDescription>This is a default alert for general information.</AlertDescription>
</Alert>

<Alert variant="success">
<AlertTitle>Success</AlertTitle>
<AlertDescription>
Your changes have been saved successfully.
</AlertDescription>
</Alert>

<Alert variant="warning">
<AlertTitle>Warning</AlertTitle>
<AlertDescription>
Please review your input before continuing.
</AlertDescription>
</Alert>

<Alert variant="danger">
<AlertTitle>Danger</AlertTitle>
<AlertDescription>A serious error occurred.</AlertDescription>
</Alert>
The `success`, `warning`, and `danger` variants show a default icon. The `primary` and `neutral` variants have no icon by default.

<div className="mt-4 flex max-w-[50%] flex-col gap-4">
<Alert
variant="primary"
title="Primary"
description="This is a general notification for your information."
/>
<Alert
variant="neutral"
title="Neutral"
description="This is a low-emphasis, informational message."
/>
<Alert
variant="success"
title="Success"
description="Your changes have been saved successfully."
/>
<Alert
variant="warning"
title="Warning"
description="Please review your input before continuing."
/>
<Alert
variant="danger"
title="Danger"
description="A serious error occurred."
/>
</div>

### Usage

```tsx
<Alert variant="success">
<AlertTitle>Success</AlertTitle>
<AlertDescription>Your changes have been saved successfully.</AlertDescription>
</Alert>

<Alert variant="warning">
<AlertTitle>Warning</AlertTitle>
<AlertDescription>Please review your input before continuing.</AlertDescription>
</Alert>

<Alert variant="danger">
<AlertTitle>Danger</AlertTitle>
<AlertDescription>A serious error occurred.</AlertDescription>
</Alert>
<Alert variant="primary" title="Primary" description="This is a general notification for your information." />
<Alert variant="neutral" title="Neutral" description="This is a low-emphasis, informational message." />
<Alert variant="success" title="Success" description="Your changes have been saved successfully." />
<Alert variant="warning" title="Warning" description="Please review your input before continuing." />
<Alert variant="danger" title="Danger" description="A serious error occurred." />
```

## Slots

---
### Icons

The `success`, `warning`, and `danger` variants apply a default icon automatically. Override it with the `icon` prop by passing a [Lucide](https://lucide.dev/icons/) icon name or a React element. Pass `icon={null}` to remove a variant's default icon, or add an icon to a variant that has none.

<div className="my-4 flex max-w-[50%] flex-col gap-4">
<Alert
variant="primary"
icon="Info"
title="Custom icon"
description="A primary alert with an explicitly provided icon."
/>
<Alert
variant="success"
icon="PartyPopper"
title="Overridden icon"
description="A success alert with a custom icon instead of the default."
/>
<Alert
variant="danger"
icon={null}
title="No icon"
description="A danger alert with its default icon removed."
/>
</div>

| Name | Description |
| ------------------ | ---------------------------------------------------- |
| `AlertTitle` | Renders the alert heading as an `<h5>` element. |
| `AlertDescription` | Renders the alert body content as a `<div>` element. |
```tsx
<Alert variant="primary" icon="Info" title="Custom icon" description="A primary alert with an explicitly provided icon." />
<Alert variant="success" icon="PartyPopper" title="Overridden icon" description="A success alert with a custom icon instead of the default." />
<Alert variant="danger" icon={null} title="No icon" description="A danger alert with its default icon removed." />
```

## Props

---

### Alert

| Name | Description | Type | Default | Required |
| --------- | ----------------------------- | ----------------------------------------- | --------- | -------- |
| `variant` | The visual style of the alert | `default`, `success`, `warning`, `danger` | `default` | ❌ |

### AlertTitle

Accepts all standard HTML heading attributes.

### AlertDescription

Accepts all standard HTML paragraph attributes.
| Name | Description | Type | Default | Required |
| ------------- | ----------------------------------------------------------------------------- | ---------------------------------------------------- | --------------- | -------- |
| `title` | The alert heading, rendered as an `<h4>` | `string` | — | ✅ |
| `description` | The alert body text, rendered as a `<p>` | `string` | — | ✅ |
| `variant` | The visual style of the alert | `primary`, `neutral`, `success`, `warning`, `danger` | `primary` | ❌ |
| `icon` | Overrides the variant's default icon. Lucide name, element, or `null` to hide | `string`, `ReactElement`, `null` | Variant default | ❌ |
34 changes: 27 additions & 7 deletions packages/ui/src/components/alert/alert.module.css
Original file line number Diff line number Diff line change
@@ -1,38 +1,58 @@
@reference '../../theme/theme.module.css';

.alert {
@apply [&>svg]:text-text-primary [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg~*]:pl-7;
@apply relative w-full;
@apply rounded-lg border;
@apply px-4 py-3;
@apply text-sm;
@apply grid gap-y-1;
}

.alert--default {
@apply text-badge-text-neutral [&>svg]:text-badge-text-neutral;
.alert--primary {
@apply text-badge-text-primary;
@apply bg-badge-background-primary border-badge-text-primary;
}

.alert--neutral {
@apply text-badge-text-neutral;
@apply bg-badge-background-neutral border-badge-text-neutral;
}

.alert--success {
@apply text-badge-text-success [&>svg]:text-badge-text-success;
@apply text-badge-text-success;
@apply bg-badge-background-success border-badge-text-success;
}

.alert--warning {
@apply text-badge-text-warning [&>svg]:text-badge-text-warning;
@apply text-badge-text-warning;
@apply bg-badge-background-warning border-badge-text-warning;
}

.alert--danger {
@apply text-badge-text-danger [&>svg]:text-badge-text-danger;
@apply text-badge-text-danger;
@apply bg-badge-background-danger border-badge-text-danger;
}

.alert .alert-icon {
@apply text-inherit;
}

.alert-title {
@apply font-medium leading-none tracking-tight;
@apply mb-1;
}

.alert-description {
@apply text-sm [&_p]:leading-relaxed;
}

.alert--with-icon {
@apply grid-cols-[auto_1fr] gap-x-2;
}

.alert--with-icon .alert-title {
@apply col-start-2 self-center;
}

.alert--with-icon .alert-description {
@apply col-start-2;
}
73 changes: 50 additions & 23 deletions packages/ui/src/components/alert/alert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,70 @@ import * as React from 'react';
import { cva, type VariantProps } from 'class-variance-authority';

import styles from '@/components/alert/alert.module.css';
import { Icon } from '@/components/icon/icon';
import { cn } from '@/lib/utils';

const alertVariants = cva(styles['alert'], {
variants: {
variant: {
default: styles['alert--default'],
primary: styles['alert--primary'],
neutral: styles['alert--neutral'],
success: styles['alert--success'],
warning: styles['alert--warning'],
danger: styles['alert--danger'],
},
},
defaultVariants: {
variant: 'default',
variant: 'primary',
},
});

const Alert = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
>(({ className, variant, ...props }, ref) => (
<div ref={ref} role="alert" className={cn(alertVariants({ variant }), className)} {...props} />
));
Alert.displayName = 'Alert';
// Default icons for variants. Primary and neutral have no icon by default.
const defaultVariantIcons: Record<string, string> = {
success: 'Check',
warning: 'OctagonAlert',
danger: 'TriangleAlert',
};

const AlertTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
({ className, ...props }, ref) => (
<h5 ref={ref} className={cn(styles['alert-title'], className)} {...props} />
)
);
AlertTitle.displayName = 'AlertTitle';
interface AlertProps
extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof alertVariants> {
title: string;
description: string;
icon?: React.ReactElement | string | null;
}

const Alert = React.forwardRef<HTMLDivElement, AlertProps>(
({ className, variant, title, description, icon, ...props }, ref) => {
// Use the provided icon, otherwise fall back to the variant's default icon.
// Passing `icon={null}` explicitly opts out of the default icon.
const effectiveIcon =
icon === undefined ? (variant ? defaultVariantIcons[variant] : undefined) : icon;

const AlertDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn(styles['alert-description'], className)} {...props} />
));
AlertDescription.displayName = 'AlertDescription';
return (
<div
ref={ref}
role="alert"
className={cn(
alertVariants({ variant }),
effectiveIcon ? styles['alert--with-icon'] : '',
className
)}
{...props}
>
{effectiveIcon ? (
<Icon
icon={effectiveIcon}
size="sm"
background="transparent"
className={styles['alert-icon']}
/>
) : null}
<h4 className={styles['alert-title']}>{title}</h4>
<p className={styles['alert-description']}>{description}</p>
</div>
);
}
);
Alert.displayName = 'Alert';

export { Alert, AlertDescription, AlertTitle };
export { Alert };
Loading