@oppulence/design-system 1.0.2
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 +115 -0
- package/components.json +21 -0
- package/hooks/use-mobile.tsx +21 -0
- package/lib/utils.ts +6 -0
- package/package.json +104 -0
- package/postcss.config.mjs +8 -0
- package/src/components/atoms/aspect-ratio.tsx +21 -0
- package/src/components/atoms/avatar.tsx +91 -0
- package/src/components/atoms/badge.tsx +47 -0
- package/src/components/atoms/button.tsx +128 -0
- package/src/components/atoms/checkbox.tsx +24 -0
- package/src/components/atoms/container.tsx +42 -0
- package/src/components/atoms/heading.tsx +56 -0
- package/src/components/atoms/index.ts +21 -0
- package/src/components/atoms/input.tsx +18 -0
- package/src/components/atoms/kbd.tsx +23 -0
- package/src/components/atoms/label.tsx +15 -0
- package/src/components/atoms/logo.tsx +52 -0
- package/src/components/atoms/progress.tsx +79 -0
- package/src/components/atoms/separator.tsx +17 -0
- package/src/components/atoms/skeleton.tsx +13 -0
- package/src/components/atoms/slider.tsx +56 -0
- package/src/components/atoms/spinner.tsx +14 -0
- package/src/components/atoms/stack.tsx +126 -0
- package/src/components/atoms/switch.tsx +26 -0
- package/src/components/atoms/text.tsx +69 -0
- package/src/components/atoms/textarea.tsx +19 -0
- package/src/components/atoms/toggle.tsx +40 -0
- package/src/components/molecules/accordion.tsx +72 -0
- package/src/components/molecules/ai-chat.tsx +251 -0
- package/src/components/molecules/alert.tsx +131 -0
- package/src/components/molecules/breadcrumb.tsx +301 -0
- package/src/components/molecules/button-group.tsx +96 -0
- package/src/components/molecules/card.tsx +184 -0
- package/src/components/molecules/collapsible.tsx +21 -0
- package/src/components/molecules/command-search.tsx +148 -0
- package/src/components/molecules/empty.tsx +98 -0
- package/src/components/molecules/field.tsx +217 -0
- package/src/components/molecules/grid.tsx +141 -0
- package/src/components/molecules/hover-card.tsx +45 -0
- package/src/components/molecules/index.ts +29 -0
- package/src/components/molecules/input-group.tsx +151 -0
- package/src/components/molecules/input-otp.tsx +74 -0
- package/src/components/molecules/item.tsx +194 -0
- package/src/components/molecules/page-header.tsx +89 -0
- package/src/components/molecules/pagination.tsx +130 -0
- package/src/components/molecules/popover.tsx +96 -0
- package/src/components/molecules/radio-group.tsx +37 -0
- package/src/components/molecules/resizable.tsx +52 -0
- package/src/components/molecules/scroll-area.tsx +45 -0
- package/src/components/molecules/section.tsx +108 -0
- package/src/components/molecules/select.tsx +201 -0
- package/src/components/molecules/settings.tsx +197 -0
- package/src/components/molecules/table.tsx +111 -0
- package/src/components/molecules/tabs.tsx +74 -0
- package/src/components/molecules/theme-switcher.tsx +187 -0
- package/src/components/molecules/toggle-group.tsx +89 -0
- package/src/components/molecules/tooltip.tsx +66 -0
- package/src/components/organisms/alert-dialog.tsx +152 -0
- package/src/components/organisms/app-shell.tsx +939 -0
- package/src/components/organisms/calendar.tsx +212 -0
- package/src/components/organisms/carousel.tsx +230 -0
- package/src/components/organisms/chart.tsx +333 -0
- package/src/components/organisms/combobox.tsx +274 -0
- package/src/components/organisms/command.tsx +200 -0
- package/src/components/organisms/context-menu.tsx +229 -0
- package/src/components/organisms/dialog.tsx +134 -0
- package/src/components/organisms/drawer.tsx +123 -0
- package/src/components/organisms/dropdown-menu.tsx +256 -0
- package/src/components/organisms/index.ts +17 -0
- package/src/components/organisms/menubar.tsx +203 -0
- package/src/components/organisms/navigation-menu.tsx +143 -0
- package/src/components/organisms/page-layout.tsx +105 -0
- package/src/components/organisms/sheet.tsx +126 -0
- package/src/components/organisms/sidebar.tsx +723 -0
- package/src/components/organisms/sonner.tsx +41 -0
- package/src/components/ui/index.ts +3 -0
- package/src/index.ts +3 -0
- package/src/styles/globals.css +297 -0
- package/tailwind.config.ts +77 -0
|
@@ -0,0 +1,72 @@
|
|
|
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({
|
|
7
|
+
...props
|
|
8
|
+
}: Omit<AccordionPrimitive.Root.Props, "className">) {
|
|
9
|
+
return (
|
|
10
|
+
<AccordionPrimitive.Root
|
|
11
|
+
data-slot="accordion"
|
|
12
|
+
className="flex w-full flex-col"
|
|
13
|
+
{...props}
|
|
14
|
+
/>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function AccordionItem({
|
|
19
|
+
...props
|
|
20
|
+
}: Omit<AccordionPrimitive.Item.Props, "className">) {
|
|
21
|
+
return (
|
|
22
|
+
<AccordionPrimitive.Item
|
|
23
|
+
data-slot="accordion-item"
|
|
24
|
+
className="not-last:border-b"
|
|
25
|
+
{...props}
|
|
26
|
+
/>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function AccordionTrigger({
|
|
31
|
+
children,
|
|
32
|
+
...props
|
|
33
|
+
}: Omit<AccordionPrimitive.Trigger.Props, "className">) {
|
|
34
|
+
return (
|
|
35
|
+
<AccordionPrimitive.Header className="flex">
|
|
36
|
+
<AccordionPrimitive.Trigger
|
|
37
|
+
data-slot="accordion-trigger"
|
|
38
|
+
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"
|
|
39
|
+
{...props}
|
|
40
|
+
>
|
|
41
|
+
{children}
|
|
42
|
+
<ChevronDownIcon
|
|
43
|
+
data-slot="accordion-trigger-icon"
|
|
44
|
+
className="pointer-events-none shrink-0 group-aria-expanded/accordion-trigger:hidden"
|
|
45
|
+
/>
|
|
46
|
+
<ChevronUpIcon
|
|
47
|
+
data-slot="accordion-trigger-icon"
|
|
48
|
+
className="pointer-events-none hidden shrink-0 group-aria-expanded/accordion-trigger:inline"
|
|
49
|
+
/>
|
|
50
|
+
</AccordionPrimitive.Trigger>
|
|
51
|
+
</AccordionPrimitive.Header>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function AccordionContent({
|
|
56
|
+
children,
|
|
57
|
+
...props
|
|
58
|
+
}: Omit<AccordionPrimitive.Panel.Props, "className">) {
|
|
59
|
+
return (
|
|
60
|
+
<AccordionPrimitive.Panel
|
|
61
|
+
data-slot="accordion-content"
|
|
62
|
+
className="data-open:animate-accordion-down data-closed:animate-accordion-up text-sm overflow-hidden"
|
|
63
|
+
{...props}
|
|
64
|
+
>
|
|
65
|
+
<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">
|
|
66
|
+
{children}
|
|
67
|
+
</div>
|
|
68
|
+
</AccordionPrimitive.Panel>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger };
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
|
+
import { MessageCircleIcon, SparklesIcon, XIcon } from "lucide-react";
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
|
|
7
|
+
const aiChatTriggerVariants = cva(
|
|
8
|
+
"fixed bottom-6 right-6 z-50 flex items-center justify-center rounded-full transition-all duration-200 cursor-pointer",
|
|
9
|
+
{
|
|
10
|
+
variants: {
|
|
11
|
+
size: {
|
|
12
|
+
default: "size-14",
|
|
13
|
+
sm: "size-12",
|
|
14
|
+
lg: "size-16",
|
|
15
|
+
},
|
|
16
|
+
variant: {
|
|
17
|
+
default:
|
|
18
|
+
"bg-primary text-primary-foreground hover:bg-primary/90 active:scale-95",
|
|
19
|
+
secondary:
|
|
20
|
+
"bg-foreground text-background hover:bg-foreground/90 active:scale-95",
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
defaultVariants: {
|
|
24
|
+
size: "default",
|
|
25
|
+
variant: "default",
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const aiChatPanelVariants = cva(
|
|
31
|
+
"fixed bottom-24 right-6 z-50 flex flex-col bg-background border border-border/40 rounded-2xl overflow-hidden transition-all duration-200 origin-bottom-right",
|
|
32
|
+
{
|
|
33
|
+
variants: {
|
|
34
|
+
size: {
|
|
35
|
+
default: "w-96 h-[500px]",
|
|
36
|
+
sm: "w-80 h-[400px]",
|
|
37
|
+
lg: "w-[450px] h-[600px]",
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
defaultVariants: {
|
|
41
|
+
size: "default",
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
interface AIChatProps extends VariantProps<typeof aiChatTriggerVariants> {
|
|
47
|
+
/** Whether the chat panel is open */
|
|
48
|
+
open?: boolean;
|
|
49
|
+
/** Default open state (uncontrolled) */
|
|
50
|
+
defaultOpen?: boolean;
|
|
51
|
+
/** Callback when open state changes */
|
|
52
|
+
onOpenChange?: (open: boolean) => void;
|
|
53
|
+
/** Custom trigger icon */
|
|
54
|
+
triggerIcon?: React.ReactNode;
|
|
55
|
+
/** Panel size */
|
|
56
|
+
panelSize?: "sm" | "default" | "lg";
|
|
57
|
+
/** Content to render inside the chat panel */
|
|
58
|
+
children?: React.ReactNode;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function AIChat({
|
|
62
|
+
open: openProp,
|
|
63
|
+
defaultOpen = false,
|
|
64
|
+
onOpenChange,
|
|
65
|
+
triggerIcon,
|
|
66
|
+
size = "default",
|
|
67
|
+
variant = "default",
|
|
68
|
+
panelSize = "default",
|
|
69
|
+
children,
|
|
70
|
+
}: AIChatProps) {
|
|
71
|
+
const [internalOpen, setInternalOpen] = React.useState(defaultOpen);
|
|
72
|
+
const isOpen = openProp ?? internalOpen;
|
|
73
|
+
|
|
74
|
+
const handleToggle = () => {
|
|
75
|
+
const newValue = !isOpen;
|
|
76
|
+
if (openProp === undefined) {
|
|
77
|
+
setInternalOpen(newValue);
|
|
78
|
+
}
|
|
79
|
+
onOpenChange?.(newValue);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<>
|
|
84
|
+
{/* Chat Panel */}
|
|
85
|
+
<div
|
|
86
|
+
data-slot="ai-chat-panel"
|
|
87
|
+
className={`${aiChatPanelVariants({ size: panelSize })} ${
|
|
88
|
+
isOpen
|
|
89
|
+
? "scale-100 opacity-100"
|
|
90
|
+
: "scale-95 opacity-0 pointer-events-none"
|
|
91
|
+
}`}
|
|
92
|
+
style={{
|
|
93
|
+
boxShadow:
|
|
94
|
+
"0 8px 32px -4px rgb(0 0 0 / 0.12), 0 4px 16px -2px rgb(0 0 0 / 0.08)",
|
|
95
|
+
}}
|
|
96
|
+
>
|
|
97
|
+
{children || <AIChatDefaultContent onClose={handleToggle} />}
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
{/* Floating Trigger Button */}
|
|
101
|
+
<button
|
|
102
|
+
type="button"
|
|
103
|
+
data-slot="ai-chat-trigger"
|
|
104
|
+
onClick={handleToggle}
|
|
105
|
+
className={aiChatTriggerVariants({ size, variant })}
|
|
106
|
+
style={{
|
|
107
|
+
boxShadow:
|
|
108
|
+
"0 4px 16px -2px rgb(0 0 0 / 0.15), 0 2px 8px -2px rgb(0 0 0 / 0.1)",
|
|
109
|
+
}}
|
|
110
|
+
aria-label={isOpen ? "Close chat" : "Open chat"}
|
|
111
|
+
>
|
|
112
|
+
<span
|
|
113
|
+
className={`absolute transition-all duration-200 ${
|
|
114
|
+
isOpen
|
|
115
|
+
? "scale-0 opacity-0 rotate-90"
|
|
116
|
+
: "scale-100 opacity-100 rotate-0"
|
|
117
|
+
}`}
|
|
118
|
+
>
|
|
119
|
+
{triggerIcon || <SparklesIcon className="size-6" />}
|
|
120
|
+
</span>
|
|
121
|
+
<span
|
|
122
|
+
className={`absolute transition-all duration-200 ${
|
|
123
|
+
isOpen
|
|
124
|
+
? "scale-100 opacity-100 rotate-0"
|
|
125
|
+
: "scale-0 opacity-0 -rotate-90"
|
|
126
|
+
}`}
|
|
127
|
+
>
|
|
128
|
+
<XIcon className="size-6" />
|
|
129
|
+
</span>
|
|
130
|
+
</button>
|
|
131
|
+
</>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Default content when no children provided
|
|
136
|
+
function AIChatDefaultContent({ onClose }: { onClose: () => void }) {
|
|
137
|
+
return (
|
|
138
|
+
<>
|
|
139
|
+
{/* Header */}
|
|
140
|
+
<div className="flex items-center justify-between px-4 py-3 border-b border-border/40">
|
|
141
|
+
<div className="flex items-center gap-2">
|
|
142
|
+
<span className="flex size-8 items-center justify-center rounded-full bg-primary/10 text-primary">
|
|
143
|
+
<SparklesIcon className="size-4" />
|
|
144
|
+
</span>
|
|
145
|
+
<div>
|
|
146
|
+
<div className="font-semibold text-sm">AI Assistant</div>
|
|
147
|
+
<div className="text-xs text-muted-foreground">Ask me anything</div>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
<button
|
|
151
|
+
type="button"
|
|
152
|
+
onClick={onClose}
|
|
153
|
+
className="size-8 flex items-center justify-center rounded-md hover:bg-muted/50 text-muted-foreground hover:text-foreground transition-colors"
|
|
154
|
+
>
|
|
155
|
+
<XIcon className="size-4" />
|
|
156
|
+
</button>
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
{/* Messages Area */}
|
|
160
|
+
<div className="flex-1 overflow-auto p-4">
|
|
161
|
+
<div className="flex flex-col gap-4">
|
|
162
|
+
{/* AI Welcome Message */}
|
|
163
|
+
<div className="flex gap-3">
|
|
164
|
+
<span className="flex size-7 shrink-0 items-center justify-center rounded-full bg-primary/10 text-primary">
|
|
165
|
+
<SparklesIcon className="size-3.5" />
|
|
166
|
+
</span>
|
|
167
|
+
<div className="flex-1 rounded-2xl rounded-tl-sm bg-muted/50 dark:bg-muted px-3 py-2 text-sm">
|
|
168
|
+
Hi! I'm your AI assistant. How can I help you today?
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
|
|
174
|
+
{/* Input Area */}
|
|
175
|
+
<div className="p-3 border-t border-border/40">
|
|
176
|
+
<div className="flex items-center gap-2 rounded-xl bg-muted/50 dark:bg-muted px-3 py-2">
|
|
177
|
+
<input
|
|
178
|
+
type="text"
|
|
179
|
+
placeholder="Ask a question..."
|
|
180
|
+
className="flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground"
|
|
181
|
+
/>
|
|
182
|
+
<button
|
|
183
|
+
type="button"
|
|
184
|
+
className="flex size-8 items-center justify-center rounded-lg bg-primary text-primary-foreground hover:bg-primary/90 transition-colors"
|
|
185
|
+
>
|
|
186
|
+
<MessageCircleIcon className="size-4" />
|
|
187
|
+
</button>
|
|
188
|
+
</div>
|
|
189
|
+
<div className="mt-2 text-center text-xs text-muted-foreground">
|
|
190
|
+
Powered by AI
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
</>
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Compound components for custom content
|
|
198
|
+
function AIChatHeader({
|
|
199
|
+
children,
|
|
200
|
+
...props
|
|
201
|
+
}: Omit<React.ComponentProps<"div">, "className">) {
|
|
202
|
+
return (
|
|
203
|
+
<div
|
|
204
|
+
data-slot="ai-chat-header"
|
|
205
|
+
className="flex items-center justify-between px-4 py-3 border-b border-border/40"
|
|
206
|
+
{...props}
|
|
207
|
+
>
|
|
208
|
+
{children}
|
|
209
|
+
</div>
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function AIChatBody({
|
|
214
|
+
children,
|
|
215
|
+
...props
|
|
216
|
+
}: Omit<React.ComponentProps<"div">, "className">) {
|
|
217
|
+
return (
|
|
218
|
+
<div
|
|
219
|
+
data-slot="ai-chat-body"
|
|
220
|
+
className="flex-1 overflow-auto p-4"
|
|
221
|
+
{...props}
|
|
222
|
+
>
|
|
223
|
+
{children}
|
|
224
|
+
</div>
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function AIChatFooter({
|
|
229
|
+
children,
|
|
230
|
+
...props
|
|
231
|
+
}: Omit<React.ComponentProps<"div">, "className">) {
|
|
232
|
+
return (
|
|
233
|
+
<div
|
|
234
|
+
data-slot="ai-chat-footer"
|
|
235
|
+
className="p-3 border-t border-border/40"
|
|
236
|
+
{...props}
|
|
237
|
+
>
|
|
238
|
+
{children}
|
|
239
|
+
</div>
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export {
|
|
244
|
+
AIChat,
|
|
245
|
+
AIChatHeader,
|
|
246
|
+
AIChatBody,
|
|
247
|
+
AIChatFooter,
|
|
248
|
+
aiChatTriggerVariants,
|
|
249
|
+
aiChatPanelVariants,
|
|
250
|
+
};
|
|
251
|
+
export type { AIChatProps };
|
|
@@ -0,0 +1,131 @@
|
|
|
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
|
|
68
|
+
data-slot="alert"
|
|
69
|
+
role="alert"
|
|
70
|
+
className={alertVariants({ variant })}
|
|
71
|
+
{...props}
|
|
72
|
+
>
|
|
73
|
+
{showIcon && renderedIcon}
|
|
74
|
+
{title && <AlertTitle>{title}</AlertTitle>}
|
|
75
|
+
{description && <AlertDescription>{description}</AlertDescription>}
|
|
76
|
+
{children}
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Otherwise, render children (compound component pattern)
|
|
82
|
+
return (
|
|
83
|
+
<div
|
|
84
|
+
data-slot="alert"
|
|
85
|
+
role="alert"
|
|
86
|
+
className={alertVariants({ variant })}
|
|
87
|
+
{...props}
|
|
88
|
+
>
|
|
89
|
+
{showIcon && renderedIcon}
|
|
90
|
+
{children}
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function AlertTitle({
|
|
96
|
+
...props
|
|
97
|
+
}: Omit<React.ComponentProps<"div">, "className">) {
|
|
98
|
+
return (
|
|
99
|
+
<div
|
|
100
|
+
data-slot="alert-title"
|
|
101
|
+
className="font-medium group-has-[>svg]/alert:col-start-2 [&_a]:hover:text-foreground [&_a]:underline [&_a]:underline-offset-3"
|
|
102
|
+
{...props}
|
|
103
|
+
/>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function AlertDescription({
|
|
108
|
+
...props
|
|
109
|
+
}: Omit<React.ComponentProps<"div">, "className">) {
|
|
110
|
+
return (
|
|
111
|
+
<div
|
|
112
|
+
data-slot="alert-description"
|
|
113
|
+
className="text-sm text-balance md:text-pretty [&_p:not(:last-child)]:mb-4 [&_a]:hover:text-foreground [&_a]:underline [&_a]:underline-offset-3"
|
|
114
|
+
{...props}
|
|
115
|
+
/>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function AlertAction({
|
|
120
|
+
...props
|
|
121
|
+
}: Omit<React.ComponentProps<"div">, "className">) {
|
|
122
|
+
return (
|
|
123
|
+
<div
|
|
124
|
+
data-slot="alert-action"
|
|
125
|
+
className="absolute top-2.5 right-3"
|
|
126
|
+
{...props}
|
|
127
|
+
/>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export { Alert, AlertAction, AlertDescription, AlertTitle, alertVariants };
|