@trycompai/design-system 1.0.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.
Files changed (71) hide show
  1. package/README.md +110 -0
  2. package/components.json +21 -0
  3. package/hooks/use-mobile.tsx +19 -0
  4. package/lib/utils.ts +6 -0
  5. package/package.json +103 -0
  6. package/postcss.config.mjs +8 -0
  7. package/src/components/ui/accordion.tsx +60 -0
  8. package/src/components/ui/alert-dialog.tsx +161 -0
  9. package/src/components/ui/alert.tsx +109 -0
  10. package/src/components/ui/aspect-ratio.tsx +21 -0
  11. package/src/components/ui/avatar.tsx +74 -0
  12. package/src/components/ui/badge.tsx +48 -0
  13. package/src/components/ui/breadcrumb.tsx +254 -0
  14. package/src/components/ui/button-group.tsx +89 -0
  15. package/src/components/ui/button.tsx +122 -0
  16. package/src/components/ui/calendar.tsx +190 -0
  17. package/src/components/ui/card.tsx +155 -0
  18. package/src/components/ui/carousel.tsx +216 -0
  19. package/src/components/ui/chart.tsx +325 -0
  20. package/src/components/ui/checkbox.tsx +22 -0
  21. package/src/components/ui/collapsible.tsx +17 -0
  22. package/src/components/ui/combobox.tsx +248 -0
  23. package/src/components/ui/command.tsx +189 -0
  24. package/src/components/ui/container.tsx +34 -0
  25. package/src/components/ui/context-menu.tsx +235 -0
  26. package/src/components/ui/dialog.tsx +122 -0
  27. package/src/components/ui/drawer.tsx +102 -0
  28. package/src/components/ui/dropdown-menu.tsx +242 -0
  29. package/src/components/ui/empty.tsx +94 -0
  30. package/src/components/ui/field.tsx +215 -0
  31. package/src/components/ui/grid.tsx +135 -0
  32. package/src/components/ui/heading.tsx +56 -0
  33. package/src/components/ui/hover-card.tsx +46 -0
  34. package/src/components/ui/index.ts +61 -0
  35. package/src/components/ui/input-group.tsx +128 -0
  36. package/src/components/ui/input-otp.tsx +84 -0
  37. package/src/components/ui/input.tsx +15 -0
  38. package/src/components/ui/item.tsx +188 -0
  39. package/src/components/ui/kbd.tsx +26 -0
  40. package/src/components/ui/label.tsx +15 -0
  41. package/src/components/ui/menubar.tsx +163 -0
  42. package/src/components/ui/navigation-menu.tsx +147 -0
  43. package/src/components/ui/page-header.tsx +51 -0
  44. package/src/components/ui/page-layout.tsx +65 -0
  45. package/src/components/ui/pagination.tsx +104 -0
  46. package/src/components/ui/popover.tsx +57 -0
  47. package/src/components/ui/progress.tsx +61 -0
  48. package/src/components/ui/radio-group.tsx +37 -0
  49. package/src/components/ui/resizable.tsx +41 -0
  50. package/src/components/ui/scroll-area.tsx +48 -0
  51. package/src/components/ui/section.tsx +64 -0
  52. package/src/components/ui/select.tsx +166 -0
  53. package/src/components/ui/separator.tsx +17 -0
  54. package/src/components/ui/sheet.tsx +104 -0
  55. package/src/components/ui/sidebar.tsx +707 -0
  56. package/src/components/ui/skeleton.tsx +5 -0
  57. package/src/components/ui/slider.tsx +51 -0
  58. package/src/components/ui/sonner.tsx +43 -0
  59. package/src/components/ui/spinner.tsx +14 -0
  60. package/src/components/ui/stack.tsx +72 -0
  61. package/src/components/ui/switch.tsx +26 -0
  62. package/src/components/ui/table.tsx +65 -0
  63. package/src/components/ui/tabs.tsx +69 -0
  64. package/src/components/ui/text.tsx +59 -0
  65. package/src/components/ui/textarea.tsx +13 -0
  66. package/src/components/ui/toggle-group.tsx +87 -0
  67. package/src/components/ui/toggle.tsx +42 -0
  68. package/src/components/ui/tooltip.tsx +52 -0
  69. package/src/index.ts +3 -0
  70. package/src/styles/globals.css +122 -0
  71. package/tailwind.config.ts +59 -0
package/README.md ADDED
@@ -0,0 +1,110 @@
1
+ # @trycompai/design-system
2
+
3
+ A shadcn-style design system with Tailwind CSS v4. Ships raw TypeScript/TSX source files that your app compiles directly.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @trycompai/design-system
9
+ # or
10
+ bun add @trycompai/design-system
11
+ ```
12
+
13
+ ## Setup in Next.js
14
+
15
+ ### 1. Configure Tailwind CSS
16
+
17
+ Add the design system to your `tailwind.config.ts` content paths:
18
+
19
+ ```ts
20
+ // tailwind.config.ts
21
+ import type { Config } from 'tailwindcss';
22
+
23
+ const config: Config = {
24
+ content: [
25
+ './src/**/*.{js,ts,jsx,tsx,mdx}',
26
+ // Include the design system components
27
+ './node_modules/@trycompai/design-system/src/**/*.{js,ts,jsx,tsx}',
28
+ ],
29
+ // ... rest of your config
30
+ };
31
+
32
+ export default config;
33
+ ```
34
+
35
+ ### 2. Import Global Styles
36
+
37
+ Import the design system's CSS in your root layout:
38
+
39
+ ```tsx
40
+ // app/layout.tsx
41
+ import '@trycompai/design-system/globals.css';
42
+ // or import alongside your own globals
43
+ import './globals.css';
44
+ ```
45
+
46
+ ### 3. Configure Next.js to Transpile
47
+
48
+ Add the package to `transpilePackages` in your `next.config.ts`:
49
+
50
+ ```ts
51
+ // next.config.ts
52
+ import type { NextConfig } from 'next';
53
+
54
+ const nextConfig: NextConfig = {
55
+ transpilePackages: ['@trycompai/design-system'],
56
+ };
57
+
58
+ export default nextConfig;
59
+ ```
60
+
61
+ ## Usage
62
+
63
+ Import components directly:
64
+
65
+ ```tsx
66
+ import { Button, Card, CardHeader, CardContent } from '@trycompai/design-system';
67
+
68
+ export function MyComponent() {
69
+ return (
70
+ <Card>
71
+ <CardHeader>Hello</CardHeader>
72
+ <CardContent>
73
+ <Button variant="outline">Click me</Button>
74
+ </CardContent>
75
+ </Card>
76
+ );
77
+ }
78
+ ```
79
+
80
+ ## Available Exports
81
+
82
+ | Export | Description |
83
+ | ------------------------------------------ | ---------------------------------- |
84
+ | `@trycompai/design-system` | All components |
85
+ | `@trycompai/design-system/cn` | `cn()` utility for merging classes |
86
+ | `@trycompai/design-system/globals.css` | Global CSS with theme variables |
87
+ | `@trycompai/design-system/tailwind.config` | Tailwind configuration |
88
+
89
+ ## Components
90
+
91
+ The design system includes:
92
+
93
+ - **Layout**: `Container`, `Stack`, `Grid`, `PageLayout`, `Section`
94
+ - **Typography**: `Heading`, `Text`
95
+ - **Forms**: `Button`, `Input`, `Textarea`, `Select`, `Checkbox`, `RadioGroup`, `Switch`, `Label`, `Field`
96
+ - **Data Display**: `Card`, `Badge`, `Avatar`, `Table`, `Progress`
97
+ - **Feedback**: `Alert`, `AlertDialog`, `Dialog`, `Sheet`, `Drawer`, `Tooltip`, `Popover`
98
+ - **Navigation**: `Tabs`, `Breadcrumb`, `Pagination`, `NavigationMenu`, `DropdownMenu`
99
+ - **Utility**: `Separator`, `Skeleton`, `Spinner`, `ScrollArea`, `Collapsible`
100
+
101
+ ## Design Principles
102
+
103
+ - **No className prop**: Components use variants and props, not className overrides
104
+ - **Semantic tokens**: Uses CSS variables for consistent theming
105
+ - **Dark mode**: Full dark mode support via `next-themes`
106
+ - **Accessible**: Built on Radix UI primitives
107
+
108
+ ## License
109
+
110
+ MIT
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "base-vega",
4
+ "rsc": false,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "",
8
+ "css": "src/styles/globals.css",
9
+ "baseColor": "neutral",
10
+ "cssVariables": true,
11
+ "prefix": ""
12
+ },
13
+ "aliases": {
14
+ "components": "@/components",
15
+ "utils": "@/lib/utils",
16
+ "ui": "@/components/ui",
17
+ "lib": "@/lib",
18
+ "hooks": "@/hooks"
19
+ },
20
+ "iconLibrary": "lucide"
21
+ }
@@ -0,0 +1,19 @@
1
+ import * as React from 'react';
2
+
3
+ const MOBILE_BREAKPOINT = 768;
4
+
5
+ export function useIsMobile() {
6
+ const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined);
7
+
8
+ React.useEffect(() => {
9
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
10
+ const onChange = () => {
11
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
12
+ };
13
+ mql.addEventListener('change', onChange);
14
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
15
+ return () => mql.removeEventListener('change', onChange);
16
+ }, []);
17
+
18
+ return !!isMobile;
19
+ }
package/lib/utils.ts ADDED
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from 'clsx';
2
+ import { twMerge } from 'tailwind-merge';
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
package/package.json ADDED
@@ -0,0 +1,103 @@
1
+ {
2
+ "name": "@trycompai/design-system",
3
+ "version": "1.0.0",
4
+ "description": "Design system for Comp AI - shadcn-style components with Tailwind CSS",
5
+ "type": "module",
6
+ "main": "./src/index.ts",
7
+ "module": "./src/index.ts",
8
+ "types": "./src/index.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./src/index.ts",
12
+ "import": "./src/index.ts",
13
+ "default": "./src/index.ts"
14
+ },
15
+ "./components/*": {
16
+ "types": "./src/components/ui/*.tsx",
17
+ "import": "./src/components/ui/*.tsx",
18
+ "default": "./src/components/ui/*.tsx"
19
+ },
20
+ "./cn": {
21
+ "types": "./lib/utils.ts",
22
+ "import": "./lib/utils.ts",
23
+ "default": "./lib/utils.ts"
24
+ },
25
+ "./globals.css": "./src/styles/globals.css",
26
+ "./tailwind.config": {
27
+ "types": "./tailwind.config.ts",
28
+ "import": "./tailwind.config.ts",
29
+ "default": "./tailwind.config.ts"
30
+ },
31
+ "./postcss.config": "./postcss.config.mjs"
32
+ },
33
+ "files": [
34
+ "src",
35
+ "lib",
36
+ "hooks",
37
+ "tailwind.config.ts",
38
+ "postcss.config.mjs",
39
+ "components.json",
40
+ "README.md"
41
+ ],
42
+ "sideEffects": [
43
+ "**/*.css"
44
+ ],
45
+ "publishConfig": {
46
+ "access": "public"
47
+ },
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "git+https://github.com/trycompai/comp.git",
51
+ "directory": "packages/ui-shadcn"
52
+ },
53
+ "license": "MIT",
54
+ "keywords": [
55
+ "design-system",
56
+ "components",
57
+ "react",
58
+ "tailwindcss",
59
+ "shadcn",
60
+ "ui"
61
+ ],
62
+ "scripts": {
63
+ "clean": "rm -rf .turbo node_modules",
64
+ "format": "prettier --write .",
65
+ "lint": "prettier --check .",
66
+ "typecheck": "tsc --noEmit"
67
+ },
68
+ "dependencies": {
69
+ "@base-ui/react": "^1.0.0",
70
+ "class-variance-authority": "^0.7.1",
71
+ "clsx": "^2.1.1",
72
+ "cmdk": "^1.1.1",
73
+ "date-fns": "^4.1.0",
74
+ "embla-carousel-react": "^8.6.0",
75
+ "input-otp": "^1.4.2",
76
+ "lucide-react": "^0.562.0",
77
+ "next-themes": "^0.4.6",
78
+ "react-day-picker": "^9.13.0",
79
+ "react-resizable-panels": "^4.2.0",
80
+ "recharts": "2.15.4",
81
+ "shadcn": "^3.6.2",
82
+ "sonner": "^2.0.7",
83
+ "tailwind-merge": "^3.4.0",
84
+ "tw-animate-css": "^1.4.0",
85
+ "vaul": "^1.1.2"
86
+ },
87
+ "peerDependencies": {
88
+ "react": "^19.0.0",
89
+ "react-dom": "^19.0.0",
90
+ "tailwindcss": "^4.0.0"
91
+ },
92
+ "devDependencies": {
93
+ "@tailwindcss/postcss": "^4.1.10",
94
+ "@types/node": "^22.18.0",
95
+ "@types/react": "^19.2.7",
96
+ "@types/react-dom": "^19.2.3",
97
+ "postcss": "^8.5.4",
98
+ "react": "^19.1.1",
99
+ "react-dom": "^19.1.0",
100
+ "tailwindcss": "^4.1.8",
101
+ "typescript": "^5.9.3"
102
+ }
103
+ }
@@ -0,0 +1,8 @@
1
+ /** @type {import('postcss-load-config').Config} */
2
+ const config = {
3
+ plugins: {
4
+ '@tailwindcss/postcss': {},
5
+ },
6
+ };
7
+
8
+ export default config;
@@ -0,0 +1,60 @@
1
+ 'use client';
2
+
3
+ import { Accordion as AccordionPrimitive } from '@base-ui/react/accordion';
4
+ import { ChevronDownIcon, ChevronUpIcon } from 'lucide-react';
5
+
6
+ function Accordion({ ...props }: Omit<AccordionPrimitive.Root.Props, 'className'>) {
7
+ return (
8
+ <AccordionPrimitive.Root data-slot="accordion" className="flex w-full flex-col" {...props} />
9
+ );
10
+ }
11
+
12
+ function AccordionItem({ ...props }: Omit<AccordionPrimitive.Item.Props, 'className'>) {
13
+ return (
14
+ <AccordionPrimitive.Item data-slot="accordion-item" className="not-last:border-b" {...props} />
15
+ );
16
+ }
17
+
18
+ function AccordionTrigger({
19
+ children,
20
+ ...props
21
+ }: Omit<AccordionPrimitive.Trigger.Props, 'className'>) {
22
+ return (
23
+ <AccordionPrimitive.Header className="flex">
24
+ <AccordionPrimitive.Trigger
25
+ data-slot="accordion-trigger"
26
+ className="focus-visible:ring-ring/50 focus-visible:border-ring focus-visible:after:border-ring **:data-[slot=accordion-trigger-icon]:text-muted-foreground rounded-md py-4 text-left text-sm font-medium hover:underline focus-visible:ring-[3px] **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:size-4 group/accordion-trigger relative flex flex-1 items-start justify-between border border-transparent transition-all outline-none disabled:pointer-events-none disabled:opacity-50"
27
+ {...props}
28
+ >
29
+ {children}
30
+ <ChevronDownIcon
31
+ data-slot="accordion-trigger-icon"
32
+ className="pointer-events-none shrink-0 group-aria-expanded/accordion-trigger:hidden"
33
+ />
34
+ <ChevronUpIcon
35
+ data-slot="accordion-trigger-icon"
36
+ className="pointer-events-none hidden shrink-0 group-aria-expanded/accordion-trigger:inline"
37
+ />
38
+ </AccordionPrimitive.Trigger>
39
+ </AccordionPrimitive.Header>
40
+ );
41
+ }
42
+
43
+ function AccordionContent({
44
+ children,
45
+ ...props
46
+ }: Omit<AccordionPrimitive.Panel.Props, 'className'>) {
47
+ return (
48
+ <AccordionPrimitive.Panel
49
+ data-slot="accordion-content"
50
+ className="data-open:animate-accordion-down data-closed:animate-accordion-up text-sm overflow-hidden"
51
+ {...props}
52
+ >
53
+ <div className="pt-0 pb-4 [&_a]:hover:text-foreground h-(--accordion-panel-height) data-ending-style:h-0 data-starting-style:h-0 [&_a]:underline [&_a]:underline-offset-3 [&_p:not(:last-child)]:mb-4">
54
+ {children}
55
+ </div>
56
+ </AccordionPrimitive.Panel>
57
+ );
58
+ }
59
+
60
+ export { Accordion, AccordionContent, AccordionItem, AccordionTrigger };
@@ -0,0 +1,161 @@
1
+ import { AlertDialog as AlertDialogPrimitive } from '@base-ui/react/alert-dialog';
2
+ import * as React from 'react';
3
+
4
+ import { cn } from '../../../lib/utils';
5
+ import { Button } from './button';
6
+
7
+ function AlertDialog({ ...props }: AlertDialogPrimitive.Root.Props) {
8
+ return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />;
9
+ }
10
+
11
+ function AlertDialogTrigger({ ...props }: AlertDialogPrimitive.Trigger.Props) {
12
+ return <AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />;
13
+ }
14
+
15
+ function AlertDialogPortal({ ...props }: AlertDialogPrimitive.Portal.Props) {
16
+ return <AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />;
17
+ }
18
+
19
+ function AlertDialogOverlay({ className, ...props }: AlertDialogPrimitive.Backdrop.Props) {
20
+ return (
21
+ <AlertDialogPrimitive.Backdrop
22
+ data-slot="alert-dialog-overlay"
23
+ className={cn(
24
+ 'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 isolate z-50',
25
+ className,
26
+ )}
27
+ {...props}
28
+ />
29
+ );
30
+ }
31
+
32
+ function AlertDialogContent({
33
+ className,
34
+ size = 'default',
35
+ ...props
36
+ }: AlertDialogPrimitive.Popup.Props & {
37
+ size?: 'default' | 'sm';
38
+ }) {
39
+ return (
40
+ <AlertDialogPortal>
41
+ <AlertDialogOverlay />
42
+ <AlertDialogPrimitive.Popup
43
+ data-slot="alert-dialog-content"
44
+ data-size={size}
45
+ className={cn(
46
+ 'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 bg-background ring-foreground/10 gap-4 rounded-xl p-4 ring-1 duration-100 data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-sm group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 outline-none',
47
+ className,
48
+ )}
49
+ {...props}
50
+ />
51
+ </AlertDialogPortal>
52
+ );
53
+ }
54
+
55
+ function AlertDialogHeader({ className, ...props }: React.ComponentProps<'div'>) {
56
+ return (
57
+ <div
58
+ data-slot="alert-dialog-header"
59
+ className={cn(
60
+ 'grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-4 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]',
61
+ className,
62
+ )}
63
+ {...props}
64
+ />
65
+ );
66
+ }
67
+
68
+ function AlertDialogFooter({ className, ...props }: React.ComponentProps<'div'>) {
69
+ return (
70
+ <div
71
+ data-slot="alert-dialog-footer"
72
+ className={cn(
73
+ 'bg-muted/50 -mx-4 -mb-4 rounded-b-xl border-t p-4 flex flex-col-reverse gap-2 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end',
74
+ className,
75
+ )}
76
+ {...props}
77
+ />
78
+ );
79
+ }
80
+
81
+ function AlertDialogMedia({ className, ...props }: React.ComponentProps<'div'>) {
82
+ return (
83
+ <div
84
+ data-slot="alert-dialog-media"
85
+ className={cn(
86
+ "bg-muted mb-2 inline-flex size-10 items-center justify-center rounded-md sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-6",
87
+ className,
88
+ )}
89
+ {...props}
90
+ />
91
+ );
92
+ }
93
+
94
+ function AlertDialogTitle({
95
+ className,
96
+ ...props
97
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
98
+ return (
99
+ <AlertDialogPrimitive.Title
100
+ data-slot="alert-dialog-title"
101
+ className={cn(
102
+ 'text-sm font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2',
103
+ className,
104
+ )}
105
+ {...props}
106
+ />
107
+ );
108
+ }
109
+
110
+ function AlertDialogDescription({
111
+ className,
112
+ ...props
113
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {
114
+ return (
115
+ <AlertDialogPrimitive.Description
116
+ data-slot="alert-dialog-description"
117
+ className={cn(
118
+ 'text-muted-foreground *:[a]:hover:text-foreground text-sm text-balance md:text-pretty *:[a]:underline *:[a]:underline-offset-3',
119
+ className,
120
+ )}
121
+ {...props}
122
+ />
123
+ );
124
+ }
125
+
126
+ function AlertDialogAction({
127
+ variant = 'default',
128
+ ...props
129
+ }: Omit<React.ComponentProps<typeof Button>, 'className'>) {
130
+ return <Button data-slot="alert-dialog-action" variant={variant} {...props} />;
131
+ }
132
+
133
+ function AlertDialogCancel({
134
+ variant = 'secondary',
135
+ size = 'default',
136
+ ...props
137
+ }: Omit<AlertDialogPrimitive.Close.Props, 'className'> &
138
+ Pick<React.ComponentProps<typeof Button>, 'variant' | 'size'>) {
139
+ return (
140
+ <AlertDialogPrimitive.Close
141
+ data-slot="alert-dialog-cancel"
142
+ render={<Button variant={variant} size={size} />}
143
+ {...props}
144
+ />
145
+ );
146
+ }
147
+
148
+ export {
149
+ AlertDialog,
150
+ AlertDialogAction,
151
+ AlertDialogCancel,
152
+ AlertDialogContent,
153
+ AlertDialogDescription,
154
+ AlertDialogFooter,
155
+ AlertDialogHeader,
156
+ AlertDialogMedia,
157
+ AlertDialogOverlay,
158
+ AlertDialogPortal,
159
+ AlertDialogTitle,
160
+ AlertDialogTrigger,
161
+ };
@@ -0,0 +1,109 @@
1
+ import { cva, type VariantProps } from 'class-variance-authority';
2
+ import { AlertCircle, AlertTriangle, CheckCircle2, Info } from 'lucide-react';
3
+ import * as React from 'react';
4
+
5
+ const alertVariants = cva(
6
+ 'grid gap-0.5 rounded-lg border px-4 py-3 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.5 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*="size-"])]:size-4 w-full relative group/alert',
7
+ {
8
+ variants: {
9
+ variant: {
10
+ default: 'bg-card text-card-foreground',
11
+ info: 'border-blue-200 bg-blue-50 text-blue-800 dark:border-blue-800 dark:bg-blue-950/50 dark:text-blue-200 *:data-[slot=alert-description]:text-blue-700 dark:*:data-[slot=alert-description]:text-blue-300',
12
+ success:
13
+ 'border-green-200 bg-green-50 text-green-800 dark:border-green-800 dark:bg-green-950/50 dark:text-green-200 *:data-[slot=alert-description]:text-green-700 dark:*:data-[slot=alert-description]:text-green-300',
14
+ warning:
15
+ 'border-yellow-200 bg-yellow-50 text-yellow-800 dark:border-yellow-800 dark:bg-yellow-950/50 dark:text-yellow-200 *:data-[slot=alert-description]:text-yellow-700 dark:*:data-[slot=alert-description]:text-yellow-300',
16
+ destructive:
17
+ 'border-red-200 bg-red-50 text-red-800 dark:border-red-800 dark:bg-red-950/50 dark:text-red-200 *:data-[slot=alert-description]:text-red-700 dark:*:data-[slot=alert-description]:text-red-300',
18
+ },
19
+ },
20
+ defaultVariants: {
21
+ variant: 'default',
22
+ },
23
+ },
24
+ );
25
+
26
+ const variantIcons = {
27
+ default: null,
28
+ info: Info,
29
+ success: CheckCircle2,
30
+ warning: AlertTriangle,
31
+ destructive: AlertCircle,
32
+ } as const;
33
+
34
+ type AlertVariant = NonNullable<VariantProps<typeof alertVariants>['variant']>;
35
+
36
+ interface AlertProps
37
+ extends
38
+ Omit<React.ComponentProps<'div'>, 'className' | 'title'>,
39
+ VariantProps<typeof alertVariants> {
40
+ /** Alert title - renders AlertTitle component */
41
+ title?: React.ReactNode;
42
+ /** Alert description - renders AlertDescription component */
43
+ description?: React.ReactNode;
44
+ /** Custom icon - if not provided, uses default icon for the variant */
45
+ icon?: React.ReactNode;
46
+ /** Hide the default variant icon */
47
+ hideIcon?: boolean;
48
+ }
49
+
50
+ function Alert({
51
+ variant = 'default',
52
+ title,
53
+ description,
54
+ icon,
55
+ hideIcon,
56
+ children,
57
+ ...props
58
+ }: AlertProps) {
59
+ // Determine the icon to show
60
+ const showIcon = !hideIcon && (icon !== undefined || variant !== 'default');
61
+ const IconComponent = variantIcons[variant as AlertVariant];
62
+ const renderedIcon = icon ?? (IconComponent ? <IconComponent /> : null);
63
+
64
+ // If title/description are provided, render the simple API
65
+ if (title || description) {
66
+ return (
67
+ <div data-slot="alert" role="alert" className={alertVariants({ variant })} {...props}>
68
+ {showIcon && renderedIcon}
69
+ {title && <AlertTitle>{title}</AlertTitle>}
70
+ {description && <AlertDescription>{description}</AlertDescription>}
71
+ {children}
72
+ </div>
73
+ );
74
+ }
75
+
76
+ // Otherwise, render children (compound component pattern)
77
+ return (
78
+ <div data-slot="alert" role="alert" className={alertVariants({ variant })} {...props}>
79
+ {showIcon && renderedIcon}
80
+ {children}
81
+ </div>
82
+ );
83
+ }
84
+
85
+ function AlertTitle({ ...props }: Omit<React.ComponentProps<'div'>, 'className'>) {
86
+ return (
87
+ <div
88
+ data-slot="alert-title"
89
+ className="font-medium group-has-[>svg]/alert:col-start-2 [&_a]:hover:text-foreground [&_a]:underline [&_a]:underline-offset-3"
90
+ {...props}
91
+ />
92
+ );
93
+ }
94
+
95
+ function AlertDescription({ ...props }: Omit<React.ComponentProps<'div'>, 'className'>) {
96
+ return (
97
+ <div
98
+ data-slot="alert-description"
99
+ className="text-sm text-balance md:text-pretty [&_p:not(:last-child)]:mb-4 [&_a]:hover:text-foreground [&_a]:underline [&_a]:underline-offset-3"
100
+ {...props}
101
+ />
102
+ );
103
+ }
104
+
105
+ function AlertAction({ ...props }: Omit<React.ComponentProps<'div'>, 'className'>) {
106
+ return <div data-slot="alert-action" className="absolute top-2.5 right-3" {...props} />;
107
+ }
108
+
109
+ export { Alert, AlertAction, AlertDescription, AlertTitle, alertVariants };
@@ -0,0 +1,21 @@
1
+ import * as React from 'react';
2
+
3
+ function AspectRatio({
4
+ ratio,
5
+ ...props
6
+ }: Omit<React.ComponentProps<'div'>, 'className'> & { ratio: number }) {
7
+ return (
8
+ <div
9
+ data-slot="aspect-ratio"
10
+ style={
11
+ {
12
+ '--ratio': ratio,
13
+ } as React.CSSProperties
14
+ }
15
+ className="relative aspect-(--ratio)"
16
+ {...props}
17
+ />
18
+ );
19
+ }
20
+
21
+ export { AspectRatio };