@smicolon/fasla-ui 0.2.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.
Files changed (74) hide show
  1. package/README.md +141 -0
  2. package/bin/fasla-ui.mjs +54 -0
  3. package/dist/index.d.ts +5 -0
  4. package/dist/index.js +241 -0
  5. package/dist/lib/utils.d.ts +9 -0
  6. package/dist/lib/utils.js +9 -0
  7. package/dist/motion/index.d.ts +33 -0
  8. package/dist/motion/index.js +59 -0
  9. package/dist/registry/index.d.ts +17 -0
  10. package/dist/registry/index.js +145 -0
  11. package/dist/tokens/index.d.ts +55 -0
  12. package/dist/tokens/index.js +34 -0
  13. package/package.json +97 -0
  14. package/registry/blocks/app-shell/AppShell.tsx +66 -0
  15. package/registry/blocks/app-shell/index.ts +1 -0
  16. package/registry/blocks/data-table/DataTable.tsx +292 -0
  17. package/registry/blocks/data-table/index.ts +7 -0
  18. package/registry/blocks/empty-state/EmptyState.test.tsx +120 -0
  19. package/registry/blocks/empty-state/EmptyState.tsx +164 -0
  20. package/registry/blocks/empty-state/index.ts +6 -0
  21. package/registry/blocks/form-section/FormSection.tsx +140 -0
  22. package/registry/blocks/form-section/index.ts +8 -0
  23. package/registry/blocks/navbar/Navbar.tsx +190 -0
  24. package/registry/blocks/navbar/index.ts +16 -0
  25. package/registry/blocks/page-header/PageHeader.tsx +64 -0
  26. package/registry/blocks/page-header/index.ts +1 -0
  27. package/registry/blocks/sidebar/Sidebar.tsx +202 -0
  28. package/registry/blocks/sidebar/index.ts +16 -0
  29. package/registry/blocks/stats-card/StatsCard.tsx +134 -0
  30. package/registry/blocks/stats-card/index.ts +6 -0
  31. package/registry/effects/animated-gradient/animated-gradient.tsx +136 -0
  32. package/registry/effects/animated-gradient/index.ts +6 -0
  33. package/registry/effects/border-beam/border-beam.tsx +132 -0
  34. package/registry/effects/border-beam/index.ts +6 -0
  35. package/registry/effects/glow-card/glow-card.tsx +145 -0
  36. package/registry/effects/glow-card/index.ts +6 -0
  37. package/registry/effects/shimmer-button/index.ts +1 -0
  38. package/registry/effects/shimmer-button/shimmer-button.tsx +91 -0
  39. package/registry/effects/spotlight/index.ts +6 -0
  40. package/registry/effects/spotlight/spotlight.tsx +162 -0
  41. package/registry/effects/text-reveal/index.ts +6 -0
  42. package/registry/effects/text-reveal/text-reveal.tsx +139 -0
  43. package/registry/effects/typewriter-text/index.ts +6 -0
  44. package/registry/effects/typewriter-text/typewriter-text.tsx +220 -0
  45. package/registry/ui/avatar/avatar.tsx +135 -0
  46. package/registry/ui/avatar/index.ts +1 -0
  47. package/registry/ui/badge/badge.test.tsx +51 -0
  48. package/registry/ui/badge/badge.tsx +39 -0
  49. package/registry/ui/badge/index.ts +1 -0
  50. package/registry/ui/button/button.test.tsx +87 -0
  51. package/registry/ui/button/button.tsx +88 -0
  52. package/registry/ui/button/index.ts +1 -0
  53. package/registry/ui/card/card.test.tsx +119 -0
  54. package/registry/ui/card/card.tsx +75 -0
  55. package/registry/ui/card/index.ts +8 -0
  56. package/registry/ui/checkbox/checkbox.tsx +113 -0
  57. package/registry/ui/checkbox/index.ts +1 -0
  58. package/registry/ui/combobox/combobox.tsx +410 -0
  59. package/registry/ui/combobox/index.ts +1 -0
  60. package/registry/ui/input/index.ts +1 -0
  61. package/registry/ui/input/input.test.tsx +78 -0
  62. package/registry/ui/input/input.tsx +76 -0
  63. package/registry/ui/select/index.ts +1 -0
  64. package/registry/ui/select/select.tsx +94 -0
  65. package/registry/ui/skeleton/index.ts +7 -0
  66. package/registry/ui/skeleton/skeleton.test.tsx +104 -0
  67. package/registry/ui/skeleton/skeleton.tsx +88 -0
  68. package/registry/ui/switch/index.ts +1 -0
  69. package/registry/ui/switch/switch.tsx +89 -0
  70. package/registry/ui/tabs/index.ts +10 -0
  71. package/registry/ui/tabs/tabs.tsx +150 -0
  72. package/registry/ui/textarea/index.ts +1 -0
  73. package/registry/ui/textarea/textarea.tsx +73 -0
  74. package/registry.json +247 -0
@@ -0,0 +1,64 @@
1
+ import * as React from "react"
2
+ import { cn } from "../../../src/lib/utils"
3
+
4
+ export interface PageHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
5
+ /** Page title */
6
+ title: string
7
+ /** Optional description below the title */
8
+ description?: string
9
+ /** Action buttons/controls on the right side */
10
+ actions?: React.ReactNode
11
+ /** Breadcrumb navigation above the title */
12
+ breadcrumb?: React.ReactNode
13
+ /** Whether to show the bottom border */
14
+ bordered?: boolean
15
+ /** Semantic heading level for the title */
16
+ headingLevel?: 1 | 2 | 3
17
+ }
18
+
19
+ /**
20
+ * Page header with title, breadcrumb, description, and actions.
21
+ * Use at the top of page content to provide context and primary actions.
22
+ */
23
+ export function PageHeader({
24
+ title,
25
+ description,
26
+ actions,
27
+ breadcrumb,
28
+ bordered = true,
29
+ headingLevel = 1,
30
+ className,
31
+ ...props
32
+ }: PageHeaderProps) {
33
+ const Heading = `h${headingLevel}` as const
34
+
35
+ return (
36
+ <div
37
+ className={cn(
38
+ "flex flex-col gap-3 bg-background px-4 py-4 md:px-6",
39
+ bordered && "border-b",
40
+ className
41
+ )}
42
+ {...props}
43
+ >
44
+ <div className="flex items-start justify-between gap-4">
45
+ <div className="min-w-0 flex-1">
46
+ {breadcrumb && (
47
+ <nav className="mb-2 text-sm text-muted-foreground" aria-label="Breadcrumb">
48
+ {breadcrumb}
49
+ </nav>
50
+ )}
51
+ <Heading className="truncate text-xl font-semibold tracking-tight md:text-2xl">
52
+ {title}
53
+ </Heading>
54
+ {description && (
55
+ <p className="mt-1 text-sm text-muted-foreground">{description}</p>
56
+ )}
57
+ </div>
58
+ {actions && (
59
+ <div className="flex shrink-0 items-center gap-2">{actions}</div>
60
+ )}
61
+ </div>
62
+ </div>
63
+ )
64
+ }
@@ -0,0 +1 @@
1
+ export { PageHeader, type PageHeaderProps } from "./PageHeader"
@@ -0,0 +1,202 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cn } from "../../../src/lib/utils"
5
+
6
+ export interface SidebarProps extends React.HTMLAttributes<HTMLElement> {
7
+ /** Whether the sidebar is collapsed */
8
+ collapsed?: boolean
9
+ /** Called when collapse state changes */
10
+ onCollapsedChange?: (collapsed: boolean) => void
11
+ /** Sidebar width when expanded */
12
+ width?: "sm" | "default" | "lg"
13
+ children: React.ReactNode
14
+ }
15
+
16
+ const widthClasses = {
17
+ sm: "w-56",
18
+ default: "w-64",
19
+ lg: "w-72",
20
+ }
21
+
22
+ export function Sidebar({
23
+ collapsed = false,
24
+ onCollapsedChange,
25
+ width = "default",
26
+ className,
27
+ children,
28
+ ...props
29
+ }: SidebarProps) {
30
+ return (
31
+ <aside
32
+ className={cn(
33
+ "flex h-full flex-col border-r bg-background transition-all duration-200",
34
+ collapsed ? "w-16" : widthClasses[width],
35
+ className
36
+ )}
37
+ {...props}
38
+ >
39
+ {children}
40
+ </aside>
41
+ )
42
+ }
43
+
44
+ export interface SidebarHeaderProps
45
+ extends React.HTMLAttributes<HTMLDivElement> {
46
+ children: React.ReactNode
47
+ }
48
+
49
+ export function SidebarHeader({
50
+ className,
51
+ children,
52
+ ...props
53
+ }: SidebarHeaderProps) {
54
+ return (
55
+ <div
56
+ className={cn("flex h-14 items-center border-b px-4", className)}
57
+ {...props}
58
+ >
59
+ {children}
60
+ </div>
61
+ )
62
+ }
63
+
64
+ export interface SidebarContentProps
65
+ extends React.HTMLAttributes<HTMLDivElement> {
66
+ children: React.ReactNode
67
+ }
68
+
69
+ export function SidebarContent({
70
+ className,
71
+ children,
72
+ ...props
73
+ }: SidebarContentProps) {
74
+ return (
75
+ <div
76
+ className={cn("flex-1 overflow-auto p-2", className)}
77
+ {...props}
78
+ >
79
+ {children}
80
+ </div>
81
+ )
82
+ }
83
+
84
+ export interface SidebarFooterProps
85
+ extends React.HTMLAttributes<HTMLDivElement> {
86
+ children: React.ReactNode
87
+ }
88
+
89
+ export function SidebarFooter({
90
+ className,
91
+ children,
92
+ ...props
93
+ }: SidebarFooterProps) {
94
+ return (
95
+ <div
96
+ className={cn("border-t p-2", className)}
97
+ {...props}
98
+ >
99
+ {children}
100
+ </div>
101
+ )
102
+ }
103
+
104
+ export interface SidebarGroupProps extends React.HTMLAttributes<HTMLDivElement> {
105
+ /** Group label */
106
+ label?: string
107
+ children: React.ReactNode
108
+ }
109
+
110
+ export function SidebarGroup({
111
+ label,
112
+ className,
113
+ children,
114
+ ...props
115
+ }: SidebarGroupProps) {
116
+ return (
117
+ <div className={cn("py-2", className)} {...props}>
118
+ {label && (
119
+ <div className="mb-2 px-2 text-xs font-semibold uppercase tracking-wider text-muted-foreground">
120
+ {label}
121
+ </div>
122
+ )}
123
+ <div className="space-y-1">{children}</div>
124
+ </div>
125
+ )
126
+ }
127
+
128
+ export interface SidebarItemProps
129
+ extends React.ButtonHTMLAttributes<HTMLButtonElement> {
130
+ /** Icon element */
131
+ icon?: React.ReactNode
132
+ /** Whether item is active */
133
+ active?: boolean
134
+ /** Whether sidebar is collapsed (hides label) */
135
+ collapsed?: boolean
136
+ children: React.ReactNode
137
+ }
138
+
139
+ export function SidebarItem({
140
+ icon,
141
+ active,
142
+ collapsed,
143
+ className,
144
+ children,
145
+ ...props
146
+ }: SidebarItemProps) {
147
+ return (
148
+ <button
149
+ className={cn(
150
+ "flex w-full items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-colors",
151
+ "hover:bg-accent hover:text-accent-foreground",
152
+ active && "bg-accent text-accent-foreground",
153
+ collapsed && "justify-center px-2",
154
+ className
155
+ )}
156
+ {...props}
157
+ >
158
+ {icon && <span className="shrink-0">{icon}</span>}
159
+ {!collapsed && <span className="truncate">{children}</span>}
160
+ </button>
161
+ )
162
+ }
163
+
164
+ export interface SidebarCollapseButtonProps
165
+ extends React.ButtonHTMLAttributes<HTMLButtonElement> {
166
+ collapsed: boolean
167
+ onCollapsedChange: (collapsed: boolean) => void
168
+ }
169
+
170
+ export function SidebarCollapseButton({
171
+ collapsed,
172
+ onCollapsedChange,
173
+ className,
174
+ ...props
175
+ }: SidebarCollapseButtonProps) {
176
+ return (
177
+ <button
178
+ onClick={() => onCollapsedChange(!collapsed)}
179
+ className={cn(
180
+ "flex h-8 w-8 items-center justify-center rounded-md hover:bg-accent",
181
+ className
182
+ )}
183
+ aria-label={collapsed ? "Expand sidebar" : "Collapse sidebar"}
184
+ {...props}
185
+ >
186
+ <svg
187
+ width="16"
188
+ height="16"
189
+ viewBox="0 0 24 24"
190
+ fill="none"
191
+ stroke="currentColor"
192
+ strokeWidth="2"
193
+ strokeLinecap="round"
194
+ strokeLinejoin="round"
195
+ className={cn("transition-transform", collapsed && "rotate-180")}
196
+ >
197
+ <polyline points="11 17 6 12 11 7" />
198
+ <polyline points="18 17 13 12 18 7" />
199
+ </svg>
200
+ </button>
201
+ )
202
+ }
@@ -0,0 +1,16 @@
1
+ export {
2
+ Sidebar,
3
+ SidebarHeader,
4
+ SidebarContent,
5
+ SidebarFooter,
6
+ SidebarGroup,
7
+ SidebarItem,
8
+ SidebarCollapseButton,
9
+ type SidebarProps,
10
+ type SidebarHeaderProps,
11
+ type SidebarContentProps,
12
+ type SidebarFooterProps,
13
+ type SidebarGroupProps,
14
+ type SidebarItemProps,
15
+ type SidebarCollapseButtonProps,
16
+ } from "./Sidebar"
@@ -0,0 +1,134 @@
1
+ import * as React from "react"
2
+ import { cn } from "../../../src/lib/utils"
3
+
4
+ export interface StatsCardProps extends React.HTMLAttributes<HTMLDivElement> {
5
+ /** The main title/label */
6
+ title: string
7
+ /** The primary value to display */
8
+ value: string | number
9
+ /** Optional description or subtitle */
10
+ description?: string
11
+ /** Optional icon */
12
+ icon?: React.ReactNode
13
+ /** Trend direction and value */
14
+ trend?: {
15
+ value: number
16
+ direction: "up" | "down" | "neutral"
17
+ }
18
+ /** Whether the card is loading */
19
+ loading?: boolean
20
+ }
21
+
22
+ export function StatsCard({
23
+ title,
24
+ value,
25
+ description,
26
+ icon,
27
+ trend,
28
+ loading = false,
29
+ className,
30
+ ...props
31
+ }: StatsCardProps) {
32
+ return (
33
+ <div
34
+ className={cn(
35
+ "rounded-xl border bg-card p-6 shadow-sm",
36
+ className
37
+ )}
38
+ {...props}
39
+ >
40
+ <div className="flex items-start justify-between">
41
+ <div className="space-y-1">
42
+ <p className="text-sm font-medium text-muted-foreground">{title}</p>
43
+ {loading ? (
44
+ <div className="h-8 w-24 animate-pulse rounded bg-muted" />
45
+ ) : (
46
+ <p className="text-2xl font-bold tracking-tight">{value}</p>
47
+ )}
48
+ </div>
49
+ {icon && (
50
+ <div className="rounded-md bg-primary/10 p-2 text-primary">
51
+ {icon}
52
+ </div>
53
+ )}
54
+ </div>
55
+
56
+ {(description || trend) && (
57
+ <div className="mt-4 flex items-center gap-2 text-sm">
58
+ {trend && (
59
+ <span
60
+ className={cn(
61
+ "inline-flex items-center gap-1 font-medium",
62
+ trend.direction === "up" && "text-green-600 dark:text-green-400",
63
+ trend.direction === "down" && "text-red-600 dark:text-red-400",
64
+ trend.direction === "neutral" && "text-muted-foreground"
65
+ )}
66
+ >
67
+ {trend.direction === "up" && (
68
+ <svg
69
+ width="16"
70
+ height="16"
71
+ viewBox="0 0 24 24"
72
+ fill="none"
73
+ stroke="currentColor"
74
+ strokeWidth="2"
75
+ strokeLinecap="round"
76
+ strokeLinejoin="round"
77
+ >
78
+ <polyline points="18 15 12 9 6 15" />
79
+ </svg>
80
+ )}
81
+ {trend.direction === "down" && (
82
+ <svg
83
+ width="16"
84
+ height="16"
85
+ viewBox="0 0 24 24"
86
+ fill="none"
87
+ stroke="currentColor"
88
+ strokeWidth="2"
89
+ strokeLinecap="round"
90
+ strokeLinejoin="round"
91
+ >
92
+ <polyline points="6 9 12 15 18 9" />
93
+ </svg>
94
+ )}
95
+ {trend.value > 0 ? "+" : ""}
96
+ {trend.value}%
97
+ </span>
98
+ )}
99
+ {description && (
100
+ <span className="text-muted-foreground">{description}</span>
101
+ )}
102
+ </div>
103
+ )}
104
+ </div>
105
+ )
106
+ }
107
+
108
+ export interface StatsGridProps extends React.HTMLAttributes<HTMLDivElement> {
109
+ /** Number of columns */
110
+ columns?: 2 | 3 | 4
111
+ children: React.ReactNode
112
+ }
113
+
114
+ export function StatsGrid({
115
+ columns = 4,
116
+ className,
117
+ children,
118
+ ...props
119
+ }: StatsGridProps) {
120
+ return (
121
+ <div
122
+ className={cn(
123
+ "grid gap-4",
124
+ columns === 2 && "grid-cols-1 sm:grid-cols-2",
125
+ columns === 3 && "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3",
126
+ columns === 4 && "grid-cols-1 sm:grid-cols-2 lg:grid-cols-4",
127
+ className
128
+ )}
129
+ {...props}
130
+ >
131
+ {children}
132
+ </div>
133
+ )
134
+ }
@@ -0,0 +1,6 @@
1
+ export {
2
+ StatsCard,
3
+ StatsGrid,
4
+ type StatsCardProps,
5
+ type StatsGridProps,
6
+ } from "./StatsCard"
@@ -0,0 +1,136 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { motion, useReducedMotion } from "framer-motion"
5
+ import { cn } from "../../../src/lib/utils"
6
+
7
+ export interface AnimatedGradientProps
8
+ extends React.HTMLAttributes<HTMLDivElement> {
9
+ /** Gradient colors */
10
+ colors?: string[]
11
+ /** Animation speed in seconds */
12
+ speed?: number
13
+ /** Blur amount */
14
+ blur?: "sm" | "md" | "lg" | "xl" | "2xl" | "3xl"
15
+ }
16
+
17
+ const blurValues = {
18
+ sm: "blur-sm",
19
+ md: "blur-md",
20
+ lg: "blur-lg",
21
+ xl: "blur-xl",
22
+ "2xl": "blur-2xl",
23
+ "3xl": "blur-3xl",
24
+ }
25
+
26
+ /**
27
+ * Animated gradient background with moving color blobs.
28
+ * Respects prefers-reduced-motion by stopping animation.
29
+ */
30
+ export function AnimatedGradient({
31
+ colors = [
32
+ "hsl(var(--primary))",
33
+ "hsl(var(--secondary))",
34
+ "hsl(280 100% 70%)",
35
+ "hsl(200 100% 70%)",
36
+ ],
37
+ speed = 10,
38
+ blur = "3xl",
39
+ className,
40
+ children,
41
+ ...props
42
+ }: AnimatedGradientProps) {
43
+ const prefersReducedMotion = useReducedMotion()
44
+
45
+ return (
46
+ <div
47
+ className={cn("relative overflow-hidden", className)}
48
+ {...props}
49
+ >
50
+ {/* Gradient blobs */}
51
+ <div className="absolute inset-0 -z-10">
52
+ {colors.map((color, i) => (
53
+ <motion.div
54
+ key={i}
55
+ className={cn(
56
+ "absolute h-[50%] w-[50%] rounded-full opacity-60",
57
+ blurValues[blur]
58
+ )}
59
+ style={{
60
+ background: color,
61
+ left: `${(i % 2) * 50}%`,
62
+ top: `${Math.floor(i / 2) * 50}%`,
63
+ }}
64
+ animate={
65
+ prefersReducedMotion
66
+ ? {}
67
+ : {
68
+ x: [0, 30, -20, 0],
69
+ y: [0, -30, 20, 0],
70
+ scale: [1, 1.1, 0.9, 1],
71
+ }
72
+ }
73
+ transition={{
74
+ duration: speed + i * 2,
75
+ repeat: Infinity,
76
+ ease: "easeInOut",
77
+ }}
78
+ />
79
+ ))}
80
+ </div>
81
+
82
+ {/* Content */}
83
+ {children}
84
+ </div>
85
+ )
86
+ }
87
+
88
+ /**
89
+ * Animated gradient text effect.
90
+ */
91
+ export interface AnimatedGradientTextProps {
92
+ /** Gradient colors for text */
93
+ colors?: string[]
94
+ /** Animation speed in seconds */
95
+ speed?: number
96
+ /** Additional class names */
97
+ className?: string
98
+ /** Text content */
99
+ children: React.ReactNode
100
+ }
101
+
102
+ export function AnimatedGradientText({
103
+ colors = ["hsl(var(--primary))", "hsl(280 100% 70%)", "hsl(var(--primary))"],
104
+ speed = 3,
105
+ className,
106
+ children,
107
+ }: AnimatedGradientTextProps) {
108
+ const prefersReducedMotion = useReducedMotion()
109
+
110
+ return (
111
+ <motion.span
112
+ className={cn(
113
+ "inline-block bg-clip-text text-transparent",
114
+ className
115
+ )}
116
+ style={{
117
+ backgroundImage: `linear-gradient(90deg, ${colors.join(", ")})`,
118
+ backgroundSize: "200% auto",
119
+ }}
120
+ animate={
121
+ prefersReducedMotion
122
+ ? {}
123
+ : {
124
+ backgroundPosition: ["0% center", "200% center"],
125
+ }
126
+ }
127
+ transition={{
128
+ duration: speed,
129
+ repeat: Infinity,
130
+ ease: "linear",
131
+ }}
132
+ >
133
+ {children}
134
+ </motion.span>
135
+ )
136
+ }
@@ -0,0 +1,6 @@
1
+ export {
2
+ AnimatedGradient,
3
+ AnimatedGradientText,
4
+ type AnimatedGradientProps,
5
+ type AnimatedGradientTextProps,
6
+ } from "./animated-gradient"
@@ -0,0 +1,132 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { motion, useReducedMotion } from "framer-motion"
5
+ import { cn } from "../../../src/lib/utils"
6
+
7
+ export interface BorderBeamProps extends React.HTMLAttributes<HTMLDivElement> {
8
+ /** Duration of the animation in seconds */
9
+ duration?: number
10
+ /** Border width */
11
+ borderWidth?: number
12
+ /** Color of the beam */
13
+ colorFrom?: string
14
+ /** End color of the beam */
15
+ colorTo?: string
16
+ /** Delay before animation starts */
17
+ delay?: number
18
+ }
19
+
20
+ /**
21
+ * Animated border beam effect that travels around the element.
22
+ * Respects prefers-reduced-motion.
23
+ */
24
+ export function BorderBeam({
25
+ duration = 4,
26
+ borderWidth = 2,
27
+ colorFrom = "hsl(var(--primary))",
28
+ colorTo = "transparent",
29
+ delay = 0,
30
+ className,
31
+ children,
32
+ ...props
33
+ }: BorderBeamProps) {
34
+ const prefersReducedMotion = useReducedMotion()
35
+
36
+ return (
37
+ <div
38
+ className={cn("relative overflow-hidden rounded-lg", className)}
39
+ {...props}
40
+ >
41
+ {/* Border beam */}
42
+ {!prefersReducedMotion && (
43
+ <motion.div
44
+ className="pointer-events-none absolute inset-0 rounded-[inherit]"
45
+ style={{
46
+ padding: borderWidth,
47
+ background: `linear-gradient(90deg, ${colorFrom}, ${colorTo})`,
48
+ mask: "linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)",
49
+ maskComposite: "exclude",
50
+ WebkitMaskComposite: "xor",
51
+ }}
52
+ animate={{
53
+ rotate: [0, 360],
54
+ }}
55
+ transition={{
56
+ duration,
57
+ delay,
58
+ repeat: Infinity,
59
+ ease: "linear",
60
+ }}
61
+ />
62
+ )}
63
+
64
+ {/* Static border fallback for reduced motion */}
65
+ {prefersReducedMotion && (
66
+ <div
67
+ className="pointer-events-none absolute inset-0 rounded-[inherit]"
68
+ style={{
69
+ padding: borderWidth,
70
+ background: colorFrom,
71
+ mask: "linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)",
72
+ maskComposite: "exclude",
73
+ WebkitMaskComposite: "xor",
74
+ opacity: 0.5,
75
+ }}
76
+ />
77
+ )}
78
+
79
+ {/* Content */}
80
+ <div className="relative z-10 rounded-[inherit] bg-background">
81
+ {children}
82
+ </div>
83
+ </div>
84
+ )
85
+ }
86
+
87
+ export interface GlowingBorderProps extends React.HTMLAttributes<HTMLDivElement> {
88
+ /** Glow color */
89
+ glowColor?: string
90
+ /** Border radius */
91
+ borderRadius?: string
92
+ /** Glow intensity */
93
+ intensity?: "sm" | "md" | "lg"
94
+ }
95
+
96
+ const intensityValues = {
97
+ sm: "0 0 10px 2px",
98
+ md: "0 0 20px 4px",
99
+ lg: "0 0 30px 6px",
100
+ }
101
+
102
+ /**
103
+ * Static glowing border effect.
104
+ */
105
+ export function GlowingBorder({
106
+ glowColor = "hsl(var(--primary) / 0.5)",
107
+ borderRadius = "0.5rem",
108
+ intensity = "md",
109
+ className,
110
+ children,
111
+ ...props
112
+ }: GlowingBorderProps) {
113
+ return (
114
+ <div
115
+ className={cn("relative", className)}
116
+ style={{
117
+ borderRadius,
118
+ }}
119
+ {...props}
120
+ >
121
+ {/* Glow */}
122
+ <div
123
+ className="absolute inset-0 -z-10"
124
+ style={{
125
+ borderRadius,
126
+ boxShadow: `${intensityValues[intensity]} ${glowColor}`,
127
+ }}
128
+ />
129
+ {children}
130
+ </div>
131
+ )
132
+ }
@@ -0,0 +1,6 @@
1
+ export {
2
+ BorderBeam,
3
+ GlowingBorder,
4
+ type BorderBeamProps,
5
+ type GlowingBorderProps,
6
+ } from "./border-beam"