@jusankar/moon 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -0
- package/dist/icons.d.ts +2 -0
- package/dist/icons.js +30051 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3389 -0
- package/dist/src/components/alert/alert.d.ts +11 -0
- package/dist/src/components/alert/alert.story.d.ts +9 -0
- package/dist/src/components/alert/alert.test.d.ts +1 -0
- package/dist/src/components/alert/index.d.ts +1 -0
- package/dist/src/components/badge/badge.d.ts +10 -0
- package/dist/src/components/badge/badge.story.d.ts +10 -0
- package/dist/src/components/badge/badge.test.d.ts +1 -0
- package/dist/src/components/badge/index.d.ts +1 -0
- package/dist/src/components/card/card.d.ts +11 -0
- package/dist/src/components/card/card.story.d.ts +9 -0
- package/dist/src/components/card/card.test.d.ts +1 -0
- package/dist/src/components/card/index.d.ts +1 -0
- package/dist/src/icons.d.ts +1 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/tests/vitest.setup.d.ts +7 -0
- package/dist/src/utils.d.ts +2 -0
- package/dist/vite.config.d.ts +3 -0
- package/package.json +132 -0
- package/src/components/alert/alert.story.tsx +58 -0
- package/src/components/alert/alert.test.tsx +299 -0
- package/src/components/alert/alert.tsx +65 -0
- package/src/components/alert/index.ts +1 -0
- package/src/components/badge/badge.story.tsx +82 -0
- package/src/components/badge/badge.test.tsx +189 -0
- package/src/components/badge/badge.tsx +43 -0
- package/src/components/badge/index.ts +1 -0
- package/src/components/card/card.story.tsx +123 -0
- package/src/components/card/card.test.tsx +231 -0
- package/src/components/card/card.tsx +85 -0
- package/src/components/card/index.ts +1 -0
- package/src/icons.ts +1 -0
- package/src/index.ts +3 -0
- package/src/styles/index.css +123 -0
- package/src/styles/storybook-only.css +20 -0
- package/src/tests/vitest.setup.ts +76 -0
- package/src/utils.ts +6 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { VariantProps } from 'class-variance-authority';
|
|
2
|
+
import { ClassProp } from 'class-variance-authority/types';
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
declare const alertVariants: (props?: ({
|
|
5
|
+
variant?: "default" | "destructive" | null | undefined;
|
|
6
|
+
} & ClassProp) | undefined) => string;
|
|
7
|
+
declare function Alert({ className, variant, ...props }: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
declare function AlertTitle({ className, ...props }: React.ComponentProps<"div">): import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
declare function AlertDescription({ className, ...props }: React.ComponentProps<"div">): import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
declare function AlertAction({ className, ...props }: React.ComponentProps<"div">): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export { Alert, AlertTitle, AlertDescription, AlertAction };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import { Alert } from '.';
|
|
3
|
+
declare const meta: Meta<typeof Alert>;
|
|
4
|
+
export default meta;
|
|
5
|
+
type Story = StoryObj<typeof Alert>;
|
|
6
|
+
export declare const Basic: Story;
|
|
7
|
+
export declare const Destructive: Story;
|
|
8
|
+
export declare const Action: Story;
|
|
9
|
+
export declare const Colors: Story;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './alert';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { VariantProps } from 'class-variance-authority';
|
|
2
|
+
import { ClassProp } from 'class-variance-authority/types';
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
declare const badgeVariants: (props?: ({
|
|
5
|
+
variant?: "default" | "destructive" | "link" | "secondary" | "outline" | "ghost" | null | undefined;
|
|
6
|
+
} & ClassProp) | undefined) => string;
|
|
7
|
+
declare function Badge({ className, variant, asChild, ...props }: React.ComponentProps<"span"> & VariantProps<typeof badgeVariants> & {
|
|
8
|
+
asChild?: boolean;
|
|
9
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
export { Badge, badgeVariants };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import { Badge } from '.';
|
|
3
|
+
declare const meta: Meta<typeof Badge>;
|
|
4
|
+
export default meta;
|
|
5
|
+
type Story = StoryObj<typeof Badge>;
|
|
6
|
+
export declare const Variants: Story;
|
|
7
|
+
export declare const WithIcon: Story;
|
|
8
|
+
export declare const WithSpinner: Story;
|
|
9
|
+
export declare const AsLink: Story;
|
|
10
|
+
export declare const CustomColors: Story;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './badge';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
declare function Card({ className, size, ...props }: React.ComponentProps<"div"> & {
|
|
3
|
+
size?: "default" | "sm";
|
|
4
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
5
|
+
declare function CardHeader({ className, ...props }: React.ComponentProps<"div">): import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
declare function CardTitle({ className, ...props }: React.ComponentProps<"div">): import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
declare function CardDescription({ className, ...props }: React.ComponentProps<"div">): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
declare function CardAction({ className, ...props }: React.ComponentProps<"div">): import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
declare function CardContent({ className, ...props }: React.ComponentProps<"div">): import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
declare function CardFooter({ className, ...props }: React.ComponentProps<"div">): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent, };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import { Card } from '.';
|
|
3
|
+
declare const meta: Meta<typeof Card>;
|
|
4
|
+
export default meta;
|
|
5
|
+
type Story = StoryObj<typeof Card>;
|
|
6
|
+
export declare const Usage: Story;
|
|
7
|
+
export declare const Size: Story;
|
|
8
|
+
export declare const Image: Story;
|
|
9
|
+
export declare const Demo: Story;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './card';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '@tabler/icons-react';
|
package/package.json
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jusankar/moon",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./icons": {
|
|
14
|
+
"types": "./dist/icons.d.ts",
|
|
15
|
+
"import": "./dist/icons.js",
|
|
16
|
+
"require": "./dist/icons.js"
|
|
17
|
+
},
|
|
18
|
+
"./styles.css": "./src/styles/index.css"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"src"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"dev": "bun run storybook",
|
|
26
|
+
"storybook": "storybook dev -p 6006",
|
|
27
|
+
"build-storybook": "storybook build",
|
|
28
|
+
"build": "bunx --bun vite build",
|
|
29
|
+
"test": "vitest",
|
|
30
|
+
"test:ui": "vitest --ui",
|
|
31
|
+
"test:coverage": "vitest --coverage",
|
|
32
|
+
"test:smoke": "vitest render-all",
|
|
33
|
+
"check": "bunx --bun tsc",
|
|
34
|
+
"lint": "bunx --bun eslint . --ext .ts,.tsx",
|
|
35
|
+
"lint:fix": "bunx --bun eslint . --ext .ts,.tsx --fix",
|
|
36
|
+
"format": "bunx --bun prettier --write ."
|
|
37
|
+
},
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"react": "^19.1.0"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@storybook/addon-docs": "^10.1.7",
|
|
43
|
+
"@storybook/addon-onboarding": "^10.1.7",
|
|
44
|
+
"@tabler/icons-react": "^3.34.1",
|
|
45
|
+
"lucide-react": "^0.560.0",
|
|
46
|
+
"react-textarea-autosize": "^8.5.9"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@eslint/js": "^9.27.0",
|
|
50
|
+
"@hookform/resolvers": "^5.2.2",
|
|
51
|
+
"@radix-ui/react-accordion": "^1.2.12",
|
|
52
|
+
"@radix-ui/react-alert-dialog": "^1.1.15",
|
|
53
|
+
"@radix-ui/react-aspect-ratio": "^1.1.8",
|
|
54
|
+
"@radix-ui/react-avatar": "^1.1.11",
|
|
55
|
+
"@radix-ui/react-checkbox": "^1.3.3",
|
|
56
|
+
"@radix-ui/react-collapsible": "^1.1.12",
|
|
57
|
+
"@radix-ui/react-context-menu": "^2.2.16",
|
|
58
|
+
"@radix-ui/react-dialog": "^1.1.15",
|
|
59
|
+
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
|
60
|
+
"@radix-ui/react-hover-card": "^1.1.15",
|
|
61
|
+
"@radix-ui/react-label": "^2.1.8",
|
|
62
|
+
"@radix-ui/react-menubar": "^1.1.16",
|
|
63
|
+
"@radix-ui/react-navigation-menu": "^1.2.14",
|
|
64
|
+
"@radix-ui/react-popover": "^1.1.15",
|
|
65
|
+
"@radix-ui/react-progress": "^1.1.8",
|
|
66
|
+
"@radix-ui/react-radio-group": "^1.3.8",
|
|
67
|
+
"@radix-ui/react-scroll-area": "^1.2.10",
|
|
68
|
+
"@radix-ui/react-select": "^2.2.6",
|
|
69
|
+
"@radix-ui/react-separator": "^1.1.8",
|
|
70
|
+
"@radix-ui/react-slider": "^1.3.6",
|
|
71
|
+
"@radix-ui/react-slot": "^1.2.4",
|
|
72
|
+
"@radix-ui/react-switch": "^1.2.6",
|
|
73
|
+
"@radix-ui/react-tabs": "^1.1.13",
|
|
74
|
+
"@radix-ui/react-toggle": "^1.1.10",
|
|
75
|
+
"@radix-ui/react-toggle-group": "^1.1.11",
|
|
76
|
+
"@radix-ui/react-tooltip": "^1.2.8",
|
|
77
|
+
"@storybook/react-vite": "^10.1.7",
|
|
78
|
+
"@tailwindcss/vite": "^4.1.18",
|
|
79
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
80
|
+
"@testing-library/react": "^16.3.1",
|
|
81
|
+
"@testing-library/user-event": "^14.6.1",
|
|
82
|
+
"@types/bun": "^1.2.15",
|
|
83
|
+
"@types/node": "^22.9.0",
|
|
84
|
+
"@types/react": "19.1.0",
|
|
85
|
+
"@types/react-dom": "19.1.0",
|
|
86
|
+
"@typescript-eslint/eslint-plugin": "^8.49.0",
|
|
87
|
+
"@typescript-eslint/parser": "^8.49.0",
|
|
88
|
+
"@vitejs/plugin-react": "^5.1.2",
|
|
89
|
+
"@vitest/coverage-v8": "4.0.17",
|
|
90
|
+
"@vitest/ui": "4.0.17",
|
|
91
|
+
"ajv": "^8.17.1",
|
|
92
|
+
"bun-plugin-tailwind": "^0.0.15",
|
|
93
|
+
"class-variance-authority": "^0.7.1",
|
|
94
|
+
"clsx": "^2.1.1",
|
|
95
|
+
"cmdk": "^1.1.1",
|
|
96
|
+
"date-fns": "^4.1.0",
|
|
97
|
+
"embla-carousel-react": "^8.6.0",
|
|
98
|
+
"eslint": "^9.39.1",
|
|
99
|
+
"eslint-plugin-hooks": "^0.4.3",
|
|
100
|
+
"eslint-plugin-react": "^7.37.5",
|
|
101
|
+
"eslint-plugin-react-hooks": "^5.2.0",
|
|
102
|
+
"eslint-plugin-storybook": "^9.0.13",
|
|
103
|
+
"husky": "^9.1.7",
|
|
104
|
+
"input-otp": "^1.4.2",
|
|
105
|
+
"jsdom": "^27.4.0",
|
|
106
|
+
"lint-staged": "^16.1.4",
|
|
107
|
+
"next-themes": "^0.4.6",
|
|
108
|
+
"prettier": "^3.7.4",
|
|
109
|
+
"prettier-plugin-tailwindcss": "^0.6.11",
|
|
110
|
+
"react-day-picker": "^9.12.0",
|
|
111
|
+
"react-dom": "^19.1.0",
|
|
112
|
+
"react-hook-form": "^7.68.0",
|
|
113
|
+
"react-resizable-panels": "^3.0.6",
|
|
114
|
+
"recharts": "2.15.4",
|
|
115
|
+
"semver": "^7.7.2",
|
|
116
|
+
"sonner": "^2.0.7",
|
|
117
|
+
"storybook": "^10.1.7",
|
|
118
|
+
"tailwind-merge": "^3.4.0",
|
|
119
|
+
"tailwindcss": "^4.1.18",
|
|
120
|
+
"tsx": "^4.19.2",
|
|
121
|
+
"tw-animate-css": "^1.4.0",
|
|
122
|
+
"typescript": "^5.9.3",
|
|
123
|
+
"typescript-eslint": "^8.32.1",
|
|
124
|
+
"vaul": "^1.1.2",
|
|
125
|
+
"vite": "^7.2.7",
|
|
126
|
+
"vite-plugin-dts": "^4.5.3",
|
|
127
|
+
"vite-tsconfig-paths": "^4.2.1",
|
|
128
|
+
"vitest": "4.0.17",
|
|
129
|
+
"vitest-axe": "^0.1.0",
|
|
130
|
+
"zod": "^4.1.13"
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { Alert, AlertDescription, AlertTitle, AlertAction } from '~/src/components/alert'
|
|
3
|
+
import { CheckCircle2Icon, InfoIcon, AlertCircleIcon, AlertTriangleIcon } from 'lucide-react'
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof Alert> = {
|
|
6
|
+
title: 'Components/Alert',
|
|
7
|
+
component: Alert,
|
|
8
|
+
}
|
|
9
|
+
export default meta
|
|
10
|
+
type Story = StoryObj<typeof Alert>
|
|
11
|
+
|
|
12
|
+
export const Basic: Story = {
|
|
13
|
+
render: () => (
|
|
14
|
+
<Alert className="max-w-md">
|
|
15
|
+
<CheckCircle2Icon />
|
|
16
|
+
<AlertTitle>Account updated successfully</AlertTitle>
|
|
17
|
+
<AlertDescription>
|
|
18
|
+
Your profile information has been saved. Changes will be reflected immediately.
|
|
19
|
+
</AlertDescription>
|
|
20
|
+
</Alert>
|
|
21
|
+
),
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const Destructive: Story = {
|
|
25
|
+
render: () => (
|
|
26
|
+
<Alert variant="destructive" className="max-w-md">
|
|
27
|
+
<AlertCircleIcon />
|
|
28
|
+
<AlertTitle>Payment failed</AlertTitle>
|
|
29
|
+
<AlertDescription>
|
|
30
|
+
Your payment could not be processed. Please check your payment method and try again.
|
|
31
|
+
</AlertDescription>
|
|
32
|
+
</Alert>
|
|
33
|
+
),
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const Action: Story = {
|
|
37
|
+
render: () => (
|
|
38
|
+
<Alert className="max-w-md">
|
|
39
|
+
<AlertTitle>Dark mode is now available</AlertTitle>
|
|
40
|
+
<AlertDescription>Enable it under your profile settings to get started.</AlertDescription>
|
|
41
|
+
<AlertAction>
|
|
42
|
+
Enable
|
|
43
|
+
</AlertAction>
|
|
44
|
+
</Alert>
|
|
45
|
+
),
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const Colors: Story = {
|
|
49
|
+
render: () => (
|
|
50
|
+
<Alert className="max-w-md border-amber-200 bg-amber-50 text-amber-900 dark:border-amber-900 dark:bg-amber-950 dark:text-amber-50">
|
|
51
|
+
<AlertTriangleIcon />
|
|
52
|
+
<AlertTitle>Your subscription will expire in 3 days.</AlertTitle>
|
|
53
|
+
<AlertDescription>
|
|
54
|
+
Renew now to avoid service interruption or upgrade to a paid plan to continue using the service.
|
|
55
|
+
</AlertDescription>
|
|
56
|
+
</Alert>
|
|
57
|
+
),
|
|
58
|
+
}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { describe, it, expect, vi } from "vitest"
|
|
3
|
+
import { render, screen } from "@testing-library/react"
|
|
4
|
+
import userEvent from "@testing-library/user-event"
|
|
5
|
+
import { axe } from "vitest-axe"
|
|
6
|
+
import {
|
|
7
|
+
Alert,
|
|
8
|
+
AlertDescription,
|
|
9
|
+
AlertTitle,
|
|
10
|
+
AlertAction,
|
|
11
|
+
} from "src/components/alert"
|
|
12
|
+
import { CheckCircle2Icon, InfoIcon, AlertCircleIcon } from "lucide-react"
|
|
13
|
+
|
|
14
|
+
describe("Alert Component", () => {
|
|
15
|
+
describe("rendering", () => {
|
|
16
|
+
it("renders Alert with title and description", () => {
|
|
17
|
+
render(
|
|
18
|
+
<Alert>
|
|
19
|
+
<CheckCircle2Icon data-slot="icon" />
|
|
20
|
+
<AlertTitle data-slot="title">Payment successful</AlertTitle>
|
|
21
|
+
<AlertDescription data-slot="description">
|
|
22
|
+
Your payment of $29.99 has been processed.
|
|
23
|
+
</AlertDescription>
|
|
24
|
+
</Alert>,
|
|
25
|
+
)
|
|
26
|
+
expect(screen.getByText("Payment successful")).toBeVisible()
|
|
27
|
+
expect(screen.getByText(/has been processed/i)).toBeVisible()
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it("renders icon as an svg element", () => {
|
|
31
|
+
const { container } = render(
|
|
32
|
+
<Alert>
|
|
33
|
+
<InfoIcon data-slot="icon" />
|
|
34
|
+
<AlertTitle>New feature</AlertTitle>
|
|
35
|
+
<AlertDescription>Feature description</AlertDescription>
|
|
36
|
+
</Alert>,
|
|
37
|
+
)
|
|
38
|
+
const icon = container.querySelector('[data-slot="icon"]')
|
|
39
|
+
expect(icon?.tagName.toLowerCase()).toBe("svg")
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it("renders AlertTitle and AlertDescription elements", () => {
|
|
43
|
+
const { container } = render(
|
|
44
|
+
<Alert>
|
|
45
|
+
<CheckCircle2Icon />
|
|
46
|
+
<AlertTitle data-slot="title">Title text</AlertTitle>
|
|
47
|
+
<AlertDescription data-slot="description">Desc text</AlertDescription>
|
|
48
|
+
</Alert>,
|
|
49
|
+
)
|
|
50
|
+
expect(container.querySelector('[data-slot="title"]')).toBeInTheDocument()
|
|
51
|
+
expect(
|
|
52
|
+
container.querySelector('[data-slot="description"]'),
|
|
53
|
+
).toBeInTheDocument()
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
describe("components", () => {
|
|
58
|
+
it("renders AlertTitle displaying the provided text", () => {
|
|
59
|
+
render(<AlertTitle data-slot="title">Alert Title</AlertTitle>)
|
|
60
|
+
expect(screen.getByText("Alert Title")).toBeVisible()
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it("renders AlertDescription displaying the provided text", () => {
|
|
64
|
+
render(<AlertDescription data-slot="description">Description</AlertDescription>)
|
|
65
|
+
expect(screen.getByText("Description")).toBeVisible()
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it("renders AlertAction as a container for action elements", () => {
|
|
69
|
+
render(
|
|
70
|
+
<AlertAction data-slot="action">
|
|
71
|
+
<button>Click me</button>
|
|
72
|
+
</AlertAction>,
|
|
73
|
+
)
|
|
74
|
+
expect(screen.getByRole("button", { name: "Click me" })).toBeVisible()
|
|
75
|
+
})
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
describe("variants", () => {
|
|
79
|
+
it("applies default variant class when no variant prop provided", () => {
|
|
80
|
+
const { container } = render(
|
|
81
|
+
<Alert>
|
|
82
|
+
<AlertTitle>Default variant</AlertTitle>
|
|
83
|
+
</Alert>,
|
|
84
|
+
)
|
|
85
|
+
expect(container.firstChild).toHaveClass("alert-default")
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it("applies destructive variant class when variant prop set to destructive", () => {
|
|
89
|
+
const { container } = render(
|
|
90
|
+
<Alert variant="destructive">
|
|
91
|
+
<AlertTitle>Destructive variant</AlertTitle>
|
|
92
|
+
</Alert>,
|
|
93
|
+
)
|
|
94
|
+
expect(container.firstChild).toHaveClass("alert-destructive")
|
|
95
|
+
})
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
describe("size", () => {
|
|
99
|
+
// The component does not appear to have a size prop documented or usage.
|
|
100
|
+
it("does not support size prop and ignores it gracefully", () => {
|
|
101
|
+
// TypeScript will block unsupported size prop; testing that passing it does nothing
|
|
102
|
+
const { container } = render(
|
|
103
|
+
// @ts-expect-error testing unknown prop
|
|
104
|
+
<Alert size="lg">
|
|
105
|
+
<AlertTitle>Size ignored</AlertTitle>
|
|
106
|
+
</Alert>,
|
|
107
|
+
)
|
|
108
|
+
expect(container.firstChild).toBeInTheDocument()
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
describe("subcomponents", () => {
|
|
113
|
+
it("renders AlertTitle and AlertDescription correctly", () => {
|
|
114
|
+
const { container } = render(
|
|
115
|
+
<Alert>
|
|
116
|
+
<AlertTitle data-slot="title">My title</AlertTitle>
|
|
117
|
+
<AlertDescription data-slot="description">My description</AlertDescription>
|
|
118
|
+
</Alert>,
|
|
119
|
+
)
|
|
120
|
+
expect(container.querySelector('[data-slot="title"]')).toBeInTheDocument()
|
|
121
|
+
expect(container.querySelector('[data-slot="description"]')).toBeInTheDocument()
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it("renders AlertAction in the top-right corner slot", () => {
|
|
125
|
+
const { container } = render(
|
|
126
|
+
<Alert>
|
|
127
|
+
<AlertTitle>Title</AlertTitle>
|
|
128
|
+
<AlertDescription>Description</AlertDescription>
|
|
129
|
+
<AlertAction data-slot="action">
|
|
130
|
+
<button>Action</button>
|
|
131
|
+
</AlertAction>
|
|
132
|
+
</Alert>,
|
|
133
|
+
)
|
|
134
|
+
expect(container.querySelector('[data-slot="action"]')).toBeInTheDocument()
|
|
135
|
+
expect(screen.getByRole("button", { name: "Action" })).toBeVisible()
|
|
136
|
+
})
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
describe("state", () => {
|
|
140
|
+
// No state variants like disabled or loading appear documented.
|
|
141
|
+
it("renders normally without explicit state props", () => {
|
|
142
|
+
render(
|
|
143
|
+
<Alert>
|
|
144
|
+
<AlertTitle>No state prop</AlertTitle>
|
|
145
|
+
</Alert>,
|
|
146
|
+
)
|
|
147
|
+
expect(screen.getByText("No state prop")).toBeVisible()
|
|
148
|
+
})
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
describe("props", () => {
|
|
152
|
+
it("accepts and applies className to Alert", () => {
|
|
153
|
+
const { container } = render(
|
|
154
|
+
<Alert className="custom-class">
|
|
155
|
+
<AlertTitle>Test</AlertTitle>
|
|
156
|
+
</Alert>,
|
|
157
|
+
)
|
|
158
|
+
expect(container.firstChild).toHaveClass("custom-class")
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
it("accepts and applies className to AlertTitle", () => {
|
|
162
|
+
const { container } = render(
|
|
163
|
+
<AlertTitle className="title-class" data-slot="title">
|
|
164
|
+
Title
|
|
165
|
+
</AlertTitle>,
|
|
166
|
+
)
|
|
167
|
+
expect(container.querySelector('[data-slot="title"]')).toHaveClass("title-class")
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
it("accepts and applies className to AlertDescription", () => {
|
|
171
|
+
const { container } = render(
|
|
172
|
+
<AlertDescription className="desc-class" data-slot="description">
|
|
173
|
+
Desc
|
|
174
|
+
</AlertDescription>,
|
|
175
|
+
)
|
|
176
|
+
expect(container.querySelector('[data-slot="description"]')).toHaveClass("desc-class")
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
it("accepts and applies className to AlertAction", () => {
|
|
180
|
+
const { container } = render(
|
|
181
|
+
<AlertAction className="action-class" data-slot="action">
|
|
182
|
+
<button>Btn</button>
|
|
183
|
+
</AlertAction>,
|
|
184
|
+
)
|
|
185
|
+
expect(container.querySelector('[data-slot="action"]')).toHaveClass("action-class")
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
it("accepts variant prop on Alert", () => {
|
|
189
|
+
const { container } = render(
|
|
190
|
+
<Alert variant="destructive">
|
|
191
|
+
<AlertTitle>Destructive</AlertTitle>
|
|
192
|
+
</Alert>,
|
|
193
|
+
)
|
|
194
|
+
expect(container.firstChild).toHaveClass("alert-destructive")
|
|
195
|
+
})
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
describe("interactions", () => {
|
|
199
|
+
// Alert does not handle direct interactions like button clicks internally besides delegated children.
|
|
200
|
+
it("does not handle internal interactions", () => {
|
|
201
|
+
render(
|
|
202
|
+
<Alert>
|
|
203
|
+
<AlertTitle>Interaction test</AlertTitle>
|
|
204
|
+
</Alert>,
|
|
205
|
+
)
|
|
206
|
+
// No interactive elements inside alert by default
|
|
207
|
+
expect(screen.queryByRole("button")).not.toBeInTheDocument()
|
|
208
|
+
})
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
describe("accessibility", () => {
|
|
212
|
+
it("has no accessibility violations", async () => {
|
|
213
|
+
const { container } = render(
|
|
214
|
+
<Alert>
|
|
215
|
+
<CheckCircle2Icon />
|
|
216
|
+
<AlertTitle>Accessible Title</AlertTitle>
|
|
217
|
+
<AlertDescription>This is an accessible alert description.</AlertDescription>
|
|
218
|
+
</Alert>,
|
|
219
|
+
)
|
|
220
|
+
const results = await axe(container)
|
|
221
|
+
expect(results).toHaveNoViolations()
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
it("has appropriate roles and aria attributes", () => {
|
|
225
|
+
render(
|
|
226
|
+
<Alert>
|
|
227
|
+
<CheckCircle2Icon />
|
|
228
|
+
<AlertTitle>Role Test</AlertTitle>
|
|
229
|
+
<AlertDescription>Description</AlertDescription>
|
|
230
|
+
</Alert>,
|
|
231
|
+
)
|
|
232
|
+
const alert = screen.getByRole("alert")
|
|
233
|
+
expect(alert).toBeInTheDocument()
|
|
234
|
+
expect(screen.getByText("Role Test")).toBeVisible()
|
|
235
|
+
})
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
describe("error handling", () => {
|
|
239
|
+
it("renders gracefully with no children", () => {
|
|
240
|
+
const { container } = render(<Alert />)
|
|
241
|
+
expect(container.firstChild).toBeInTheDocument()
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
it("renders gracefully with minimal props", () => {
|
|
245
|
+
render(
|
|
246
|
+
<Alert>
|
|
247
|
+
<AlertTitle>Only title</AlertTitle>
|
|
248
|
+
</Alert>,
|
|
249
|
+
)
|
|
250
|
+
expect(screen.getByText("Only title")).toBeVisible()
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
it("handles invalid variant prop without crashing", () => {
|
|
254
|
+
// @ts-expect-error testing invalid prop
|
|
255
|
+
const { container } = render(<Alert variant="invalid" />)
|
|
256
|
+
expect(container.firstChild).toBeInTheDocument()
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
it("renders AlertAction without children gracefully", () => {
|
|
260
|
+
const { container } = render(<AlertAction />)
|
|
261
|
+
expect(container.firstChild).toBeInTheDocument()
|
|
262
|
+
})
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
describe("exports", () => {
|
|
266
|
+
it("exports Alert component", () => {
|
|
267
|
+
expect(Alert).toBeDefined()
|
|
268
|
+
const { container } = render(<Alert />)
|
|
269
|
+
expect(container.firstChild).toBeInTheDocument()
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
it("exports AlertTitle component", () => {
|
|
273
|
+
expect(AlertTitle).toBeDefined()
|
|
274
|
+
const { container } = render(<AlertTitle>Title Export</AlertTitle>)
|
|
275
|
+
expect(screen.getByText("Title Export")).toBeVisible()
|
|
276
|
+
expect(container.firstChild).toBeInTheDocument()
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
it("exports AlertDescription component", () => {
|
|
280
|
+
expect(AlertDescription).toBeDefined()
|
|
281
|
+
const { container } = render(
|
|
282
|
+
<AlertDescription>Description Export</AlertDescription>,
|
|
283
|
+
)
|
|
284
|
+
expect(screen.getByText("Description Export")).toBeVisible()
|
|
285
|
+
expect(container.firstChild).toBeInTheDocument()
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
it("exports AlertAction component", () => {
|
|
289
|
+
expect(AlertAction).toBeDefined()
|
|
290
|
+
const { container } = render(
|
|
291
|
+
<AlertAction>
|
|
292
|
+
<button>Btn Export</button>
|
|
293
|
+
</AlertAction>,
|
|
294
|
+
)
|
|
295
|
+
expect(screen.getByRole("button", { name: "Btn Export" })).toBeVisible()
|
|
296
|
+
expect(container.firstChild).toBeInTheDocument()
|
|
297
|
+
})
|
|
298
|
+
})
|
|
299
|
+
})
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
import { cn } from "~/src/utils"
|
|
4
|
+
const alertVariants = cva("grid gap-0.5 rounded-lg border px-2.5 py-2 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*=\'size-\'])]:size-4 w-full relative group/alert", {
|
|
5
|
+
variants: {
|
|
6
|
+
variant: {
|
|
7
|
+
default: "bg-card text-card-foreground",
|
|
8
|
+
destructive: "text-destructive bg-card *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current",
|
|
9
|
+
},
|
|
10
|
+
},
|
|
11
|
+
defaultVariants: {
|
|
12
|
+
variant: "default",
|
|
13
|
+
},
|
|
14
|
+
})
|
|
15
|
+
function Alert({
|
|
16
|
+
className,
|
|
17
|
+
variant,
|
|
18
|
+
...props
|
|
19
|
+
}: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
|
|
20
|
+
return (
|
|
21
|
+
<div
|
|
22
|
+
data-slot="alert"
|
|
23
|
+
role="alert"
|
|
24
|
+
className={cn(alertVariants({ variant }), className)}
|
|
25
|
+
{...props}
|
|
26
|
+
/>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
30
|
+
return (
|
|
31
|
+
<div
|
|
32
|
+
data-slot="alert-title"
|
|
33
|
+
className={cn(
|
|
34
|
+
"font-medium group-has-[>svg]/alert:col-start-2 [&_a]:hover:text-foreground [&_a]:underline [&_a]:underline-offset-3",
|
|
35
|
+
className
|
|
36
|
+
)}
|
|
37
|
+
{...props}
|
|
38
|
+
/>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
function AlertDescription({
|
|
42
|
+
className,
|
|
43
|
+
...props
|
|
44
|
+
}: React.ComponentProps<"div">) {
|
|
45
|
+
return (
|
|
46
|
+
<div
|
|
47
|
+
data-slot="alert-description"
|
|
48
|
+
className={cn(
|
|
49
|
+
"text-muted-foreground text-sm text-balance md:text-pretty [&_p:not(:last-child)]:mb-4 [&_a]:hover:text-foreground [&_a]:underline [&_a]:underline-offset-3",
|
|
50
|
+
className
|
|
51
|
+
)}
|
|
52
|
+
{...props}
|
|
53
|
+
/>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
function AlertAction({ className, ...props }: React.ComponentProps<"div">) {
|
|
57
|
+
return (
|
|
58
|
+
<div
|
|
59
|
+
data-slot="alert-action"
|
|
60
|
+
className={cn("absolute top-2 right-2", className)}
|
|
61
|
+
{...props}
|
|
62
|
+
/>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
export { Alert, AlertTitle, AlertDescription, AlertAction }
|