11"use client" ;
22
3+ import { type VariantProps , cva } from "class-variance-authority" ;
34import {
45 AlertCircle ,
56 AlertTriangle ,
@@ -15,97 +16,98 @@ import { memo, type ReactNode } from "react";
1516
1617import { cn } from "@/lib/utils" ;
1718
18- export type AlertBoxType =
19- | "info"
20- | "warning"
21- | "danger"
22- | "tip"
23- | "success"
24- | "note"
25- | "question"
26- | "important"
27- | "example" ;
19+ const alertContainerVariants = cva (
20+ "my-6 rounded-lg p-4 shadow-xs backdrop-blur-xs transition-colors duration-200" ,
21+ {
22+ variants : {
23+ type : {
24+ info : "border border-blue-500/20 bg-blue-500/10 dark:border-blue-500/20 dark:bg-blue-500/10" ,
25+ warning :
26+ "border border-yellow-500/20 bg-yellow-500/10 dark:border-yellow-500/20 dark:bg-yellow-500/10" ,
27+ danger : "border border-red-500/20 bg-red-500/10 dark:border-red-500/20 dark:bg-red-500/10" ,
28+ tip : "border border-green-500/20 bg-green-500/10 dark:border-green-500/20 dark:bg-green-500/10" ,
29+ success :
30+ "border border-emerald-500/20 bg-emerald-500/10 dark:border-emerald-500/20 dark:bg-emerald-500/10" ,
31+ note : "border border-purple-500/20 bg-purple-500/10 dark:border-purple-500/20 dark:bg-purple-500/10" ,
32+ question :
33+ "border border-indigo-500/20 bg-indigo-500/10 dark:border-indigo-500/20 dark:bg-indigo-500/10" ,
34+ important :
35+ "border border-amber-500/20 bg-amber-500/10 dark:border-amber-500/20 dark:bg-amber-500/10" ,
36+ example :
37+ "border border-cyan-500/20 bg-cyan-500/10 dark:border-cyan-500/20 dark:bg-cyan-500/10" ,
38+ } ,
39+ } ,
40+ defaultVariants : {
41+ type : "info" ,
42+ } ,
43+ }
44+ ) ;
2845
29- export type AlertBoxProps = {
30- type : AlertBoxType ; // Alert type (affects style and icon)
31- children : ReactNode ; // Main alert content
32- title ?: ReactNode ; // Optional alert title
33- className ?: string ; // Optional extra CSS classes
34- } ;
35-
36- const alertStyles = {
37- info : {
38- container :
39- "bg-blue-500/10 border border-blue-500/20 dark:bg-blue-500/10 dark:border-blue-500/20" ,
40- icon : "text-blue-500 dark:text-blue-400" ,
41- text : "text-blue-800 dark:text-blue-200" ,
42- title : "text-blue-900 dark:text-blue-100" ,
43- role : "status" ,
44- } ,
45- warning : {
46- container :
47- "bg-yellow-500/10 border border-yellow-500/20 dark:bg-yellow-500/10 dark:border-yellow-500/20" ,
48- icon : "text-yellow-500 dark:text-yellow-400" ,
49- text : "text-yellow-800 dark:text-yellow-200" ,
50- title : "text-yellow-900 dark:text-yellow-100" ,
51- role : "alert" ,
52- } ,
53- danger : {
54- container : "bg-red-500/10 border border-red-500/20 dark:bg-red-500/10 dark:border-red-500/20" ,
55- icon : "text-red-500 dark:text-red-400" ,
56- text : "text-red-800 dark:text-red-200" ,
57- title : "text-red-900 dark:text-red-100" ,
58- role : "alert" ,
46+ const alertIconVariants = cva ( "h-5 w-5" , {
47+ variants : {
48+ type : {
49+ info : "text-blue-500 dark:text-blue-400" ,
50+ warning : "text-yellow-500 dark:text-yellow-400" ,
51+ danger : "text-red-500 dark:text-red-400" ,
52+ tip : "text-green-500 dark:text-green-400" ,
53+ success : "text-emerald-500 dark:text-emerald-400" ,
54+ note : "text-purple-500 dark:text-purple-400" ,
55+ question : "text-indigo-500 dark:text-indigo-400" ,
56+ important : "text-amber-500 dark:text-amber-400" ,
57+ example : "text-cyan-500 dark:text-cyan-400" ,
58+ } ,
5959 } ,
60- tip : {
61- container :
62- "bg-green-500/10 border border-green-500/20 dark:bg-green-500/10 dark:border-green-500/20" ,
63- icon : "text-green-500 dark:text-green-400" ,
64- text : "text-green-800 dark:text-green-200" ,
65- title : "text-green-900 dark:text-green-100" ,
66- role : "status" ,
60+ defaultVariants : {
61+ type : "info" ,
6762 } ,
68- success : {
69- container :
70- "bg-emerald-500/10 border border-emerald-500/20 dark:bg-emerald-500/10 dark:border-emerald-500/20" ,
71- icon : "text-emerald-500 dark:text-emerald-400" ,
72- text : "text-emerald-800 dark:text-emerald-200" ,
73- title : "text-emerald-900 dark:text-emerald-100 " ,
74- role : "status " ,
75- } ,
76- note : {
77- container :
78- "bg -purple-500/10 border border-purple-500/20 dark:bg -purple-500/10 dark:border-purple-500/20 ",
79- icon : "text-purple-500 dark:text-purple-400 " ,
80- text : "text-purple -800 dark:text-purple -200" ,
81- title : "text-purple-900 dark:text-purple-100 " ,
82- role : "note" ,
63+ } ) ;
64+
65+ const alertTextVariants = cva ( "prose-sm wrap-break-word md:prose-base max-w-full overflow-x-auto" , {
66+ variants : {
67+ type : {
68+ info : "text-blue-800 dark:text-blue-200 " ,
69+ warning : "text-yellow-800 dark:text-yellow-200 " ,
70+ danger : "text-red-800 dark:text-red-200" ,
71+ tip : "text-green-800 dark:text-green-200" ,
72+ success : "text-emerald-800 dark:text-emerald-200" ,
73+ note : "text -purple-800 dark:text -purple-200 ",
74+ question : "text-indigo-800 dark:text-indigo-200 " ,
75+ important : "text-amber -800 dark:text-amber -200" ,
76+ example : "text-cyan-800 dark:text-cyan-200 " ,
77+ } ,
8378 } ,
84- question : {
85- container :
86- "bg-indigo-500/10 border border-indigo-500/20 dark:bg-indigo-500/10 dark:border-indigo-500/20" ,
87- icon : "text-indigo-500 dark:text-indigo-400" ,
88- text : "text-indigo-800 dark:text-indigo-200" ,
89- title : "text-indigo-900 dark:text-indigo-100" ,
90- role : "note" ,
79+ defaultVariants : {
80+ type : "info" ,
9181 } ,
92- important : {
93- container :
94- "bg-amber-500/10 border border-amber-500/20 dark:bg-amber-500/10 dark:border-amber-500/20" ,
95- icon : "text-amber-500 dark:text-amber-400" ,
96- text : "text-amber-800 dark:text-amber-200" ,
97- title : "text-amber-900 dark:text-amber-100" ,
98- role : "alert" ,
82+ } ) ;
83+
84+ const alertTitleVariants = cva ( "mb-1 font-semibold text-sm md:text-base" , {
85+ variants : {
86+ type : {
87+ info : "text-blue-900 dark:text-blue-100" ,
88+ warning : "text-yellow-900 dark:text-yellow-100" ,
89+ danger : "text-red-900 dark:text-red-100" ,
90+ tip : "text-green-900 dark:text-green-100" ,
91+ success : "text-emerald-900 dark:text-emerald-100" ,
92+ note : "text-purple-900 dark:text-purple-100" ,
93+ question : "text-indigo-900 dark:text-indigo-100" ,
94+ important : "text-amber-900 dark:text-amber-100" ,
95+ example : "text-cyan-900 dark:text-cyan-100" ,
96+ } ,
9997 } ,
100- example : {
101- container :
102- "bg-cyan-500/10 border border-cyan-500/20 dark:bg-cyan-500/10 dark:border-cyan-500/20" ,
103- icon : "text-cyan-500 dark:text-cyan-400" ,
104- text : "text-cyan-800 dark:text-cyan-200" ,
105- title : "text-cyan-900 dark:text-cyan-100" ,
106- role : "note" ,
98+ defaultVariants : {
99+ type : "info" ,
107100 } ,
108- } as const ;
101+ } ) ;
102+
103+ export type AlertBoxType = NonNullable < VariantProps < typeof alertContainerVariants > [ "type" ] > ;
104+
105+ export type AlertBoxProps = {
106+ type : AlertBoxType ;
107+ children : ReactNode ;
108+ title ?: ReactNode ;
109+ className ?: string ;
110+ } ;
109111
110112const icons = {
111113 info : Info ,
@@ -119,7 +121,7 @@ const icons = {
119121 example : BookOpen ,
120122} as const ;
121123
122- const defaultTitles = {
124+ const defaultTitles : Record < AlertBoxType , string > = {
123125 info : "INFO!" ,
124126 warning : "Read this!" ,
125127 danger : "Danger!" ,
@@ -129,7 +131,19 @@ const defaultTitles = {
129131 question : "Question" ,
130132 important : "Important" ,
131133 example : "Example" ,
132- } as const ;
134+ } ;
135+
136+ const roles : Record < AlertBoxType , string > = {
137+ info : "status" ,
138+ warning : "alert" ,
139+ danger : "alert" ,
140+ tip : "status" ,
141+ success : "status" ,
142+ note : "note" ,
143+ question : "note" ,
144+ important : "alert" ,
145+ example : "note" ,
146+ } ;
133147
134148export const AlertBox = memo ( function AlertBoxComponent ( {
135149 type,
@@ -138,8 +152,8 @@ export const AlertBox = memo(function AlertBoxComponent({
138152 className,
139153} : AlertBoxProps ) {
140154 const Icon = icons [ type ] ;
141- const styles = alertStyles [ type ] ;
142155 const defaultTitle = defaultTitles [ type ] ;
156+ const role = roles [ type ] ;
143157
144158 const ariaLabel =
145159 typeof title === "string"
@@ -148,32 +162,22 @@ export const AlertBox = memo(function AlertBoxComponent({
148162
149163 return (
150164 < div
151- role = { styles . role }
165+ role = { role }
152166 { ...( ariaLabel ? { "aria-label" : ariaLabel } : { } ) }
153- className = { cn (
154- "my-6 rounded-lg p-4 shadow-xs backdrop-blur-xs transition-colors duration-200" ,
155- styles . container ,
156- className
157- ) }
167+ className = { cn ( alertContainerVariants ( { type } ) , className ) }
158168 >
159169 < div className = "flex flex-wrap items-start gap-3" >
160170 { /* Alert icon */ }
161171 < div aria-hidden = "true" className = "mt-0.5 shrink-0" >
162- < Icon className = { `h-5 w-5 ${ styles . icon } ` } />
172+ < Icon className = { alertIconVariants ( { type } ) } />
163173 </ div >
164174
165175 { /* Alert content */ }
166176 < div className = "min-w-0 flex-1" >
167177 { ! ! ( title || defaultTitle ) && (
168- < h5 className = { `mb-1 font-semibold text-sm md:text-base ${ styles . title } ` } >
169- { title || defaultTitle }
170- </ h5 >
178+ < h5 className = { alertTitleVariants ( { type } ) } > { title || defaultTitle } </ h5 >
171179 ) }
172- < div
173- className = { `prose-sm wrap-break-word md:prose-base max-w-full overflow-x-auto ${ styles . text } ` }
174- >
175- { children }
176- </ div >
180+ < div className = { alertTextVariants ( { type } ) } > { children } </ div >
177181 </ div >
178182 </ div >
179183 </ div >
0 commit comments