@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.
- package/README.md +110 -0
- package/components.json +21 -0
- package/hooks/use-mobile.tsx +19 -0
- package/lib/utils.ts +6 -0
- package/package.json +103 -0
- package/postcss.config.mjs +8 -0
- package/src/components/ui/accordion.tsx +60 -0
- package/src/components/ui/alert-dialog.tsx +161 -0
- package/src/components/ui/alert.tsx +109 -0
- package/src/components/ui/aspect-ratio.tsx +21 -0
- package/src/components/ui/avatar.tsx +74 -0
- package/src/components/ui/badge.tsx +48 -0
- package/src/components/ui/breadcrumb.tsx +254 -0
- package/src/components/ui/button-group.tsx +89 -0
- package/src/components/ui/button.tsx +122 -0
- package/src/components/ui/calendar.tsx +190 -0
- package/src/components/ui/card.tsx +155 -0
- package/src/components/ui/carousel.tsx +216 -0
- package/src/components/ui/chart.tsx +325 -0
- package/src/components/ui/checkbox.tsx +22 -0
- package/src/components/ui/collapsible.tsx +17 -0
- package/src/components/ui/combobox.tsx +248 -0
- package/src/components/ui/command.tsx +189 -0
- package/src/components/ui/container.tsx +34 -0
- package/src/components/ui/context-menu.tsx +235 -0
- package/src/components/ui/dialog.tsx +122 -0
- package/src/components/ui/drawer.tsx +102 -0
- package/src/components/ui/dropdown-menu.tsx +242 -0
- package/src/components/ui/empty.tsx +94 -0
- package/src/components/ui/field.tsx +215 -0
- package/src/components/ui/grid.tsx +135 -0
- package/src/components/ui/heading.tsx +56 -0
- package/src/components/ui/hover-card.tsx +46 -0
- package/src/components/ui/index.ts +61 -0
- package/src/components/ui/input-group.tsx +128 -0
- package/src/components/ui/input-otp.tsx +84 -0
- package/src/components/ui/input.tsx +15 -0
- package/src/components/ui/item.tsx +188 -0
- package/src/components/ui/kbd.tsx +26 -0
- package/src/components/ui/label.tsx +15 -0
- package/src/components/ui/menubar.tsx +163 -0
- package/src/components/ui/navigation-menu.tsx +147 -0
- package/src/components/ui/page-header.tsx +51 -0
- package/src/components/ui/page-layout.tsx +65 -0
- package/src/components/ui/pagination.tsx +104 -0
- package/src/components/ui/popover.tsx +57 -0
- package/src/components/ui/progress.tsx +61 -0
- package/src/components/ui/radio-group.tsx +37 -0
- package/src/components/ui/resizable.tsx +41 -0
- package/src/components/ui/scroll-area.tsx +48 -0
- package/src/components/ui/section.tsx +64 -0
- package/src/components/ui/select.tsx +166 -0
- package/src/components/ui/separator.tsx +17 -0
- package/src/components/ui/sheet.tsx +104 -0
- package/src/components/ui/sidebar.tsx +707 -0
- package/src/components/ui/skeleton.tsx +5 -0
- package/src/components/ui/slider.tsx +51 -0
- package/src/components/ui/sonner.tsx +43 -0
- package/src/components/ui/spinner.tsx +14 -0
- package/src/components/ui/stack.tsx +72 -0
- package/src/components/ui/switch.tsx +26 -0
- package/src/components/ui/table.tsx +65 -0
- package/src/components/ui/tabs.tsx +69 -0
- package/src/components/ui/text.tsx +59 -0
- package/src/components/ui/textarea.tsx +13 -0
- package/src/components/ui/toggle-group.tsx +87 -0
- package/src/components/ui/toggle.tsx +42 -0
- package/src/components/ui/tooltip.tsx +52 -0
- package/src/index.ts +3 -0
- package/src/styles/globals.css +122 -0
- 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
|
package/components.json
ADDED
|
@@ -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
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,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 };
|