@smicolon/fasla-ui 0.2.4 → 0.4.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 +44 -20
- package/dist/index.cjs +319 -0
- package/dist/index.d.cts +5 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +92 -24
- package/dist/lib/utils.cjs +11 -0
- package/dist/lib/utils.d.cts +9 -0
- package/dist/motion/index.cjs +61 -0
- package/dist/motion/index.d.cts +33 -0
- package/dist/registry/index.cjs +179 -0
- package/dist/registry/index.d.cts +17 -0
- package/dist/registry/index.js +32 -3
- package/dist/tokens/index.cjs +77 -0
- package/dist/tokens/index.d.cts +124 -0
- package/dist/tokens/index.d.ts +75 -6
- package/dist/tokens/index.js +59 -20
- package/package.json +17 -7
- package/registry/blocks/app-shell/AppShell.tsx +1 -1
- package/registry/blocks/data-table/DataTable.test.tsx +61 -0
- package/registry/blocks/data-table/DataTable.tsx +53 -16
- package/registry/blocks/data-table/index.ts +1 -0
- package/registry/blocks/empty-state/EmptyState.test.tsx +23 -0
- package/registry/blocks/empty-state/EmptyState.tsx +32 -33
- package/registry/blocks/form-section/FormSection.tsx +1 -1
- package/registry/blocks/navbar/Navbar.test.tsx +12 -0
- package/registry/blocks/navbar/Navbar.tsx +8 -23
- package/registry/blocks/page-header/PageHeader.tsx +5 -2
- package/registry/blocks/sidebar/Sidebar.test.tsx +39 -0
- package/registry/blocks/sidebar/Sidebar.tsx +36 -26
- package/registry/blocks/stats-card/StatsCard.tsx +12 -29
- package/registry/effects/animated-gradient/animated-gradient.tsx +19 -8
- package/registry/effects/border-beam/border-beam.tsx +77 -53
- package/registry/effects/effects.test.tsx +266 -0
- package/registry/effects/glow-card/glow-card.tsx +68 -43
- package/registry/effects/shimmer-button/shimmer-button.tsx +62 -23
- package/registry/effects/spotlight/spotlight.tsx +73 -29
- package/registry/effects/text-reveal/index.ts +1 -0
- package/registry/effects/text-reveal/text-reveal.test.tsx +66 -0
- package/registry/effects/text-reveal/text-reveal.tsx +70 -21
- package/registry/effects/typewriter-text/typewriter-text.tsx +39 -20
- package/registry/next14-tsconfig.test.ts +43 -0
- package/registry/text-props-undefined.test.tsx +73 -0
- package/registry/ui/avatar/avatar.test.tsx +316 -0
- package/registry/ui/avatar/avatar.tsx +235 -108
- package/registry/ui/avatar/index.ts +1 -1
- package/registry/ui/badge/badge.test.tsx +267 -32
- package/registry/ui/badge/badge.tsx +233 -21
- package/registry/ui/button/button.test.tsx +6 -0
- package/registry/ui/button/button.tsx +6 -23
- package/registry/ui/card/card.tsx +1 -1
- package/registry/ui/checkbox/checkbox.test.tsx +23 -0
- package/registry/ui/checkbox/checkbox.tsx +16 -27
- package/registry/ui/combobox/combobox.test.tsx +72 -0
- package/registry/ui/combobox/combobox.tsx +34 -59
- package/registry/ui/input/input.test.tsx +3 -3
- package/registry/ui/input/input.tsx +5 -5
- package/registry/ui/radio/index.ts +1 -0
- package/registry/ui/radio/radio.test.tsx +248 -0
- package/registry/ui/radio/radio.tsx +208 -0
- package/registry/ui/select/select.tsx +4 -14
- package/registry/ui/skeleton/index.ts +1 -0
- package/registry/ui/skeleton/skeleton.test.tsx +113 -29
- package/registry/ui/skeleton/skeleton.tsx +101 -41
- package/registry/ui/status-indicator/index.ts +6 -0
- package/registry/ui/status-indicator/status-indicator.test.tsx +148 -0
- package/registry/ui/status-indicator/status-indicator.tsx +87 -0
- package/registry/ui/switch/index.ts +1 -1
- package/registry/ui/switch/switch.test.tsx +323 -0
- package/registry/ui/switch/switch.tsx +254 -57
- package/registry/ui/textarea/textarea.tsx +1 -1
- package/registry.json +79 -16
- package/tailwind-preset.ts +143 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
import * as React from "react"
|
|
4
|
+
import { ChevronsLeftIcon } from "lucide-react"
|
|
4
5
|
import { cn } from "../../../src/lib/utils"
|
|
5
6
|
|
|
6
7
|
export interface SidebarProps extends React.HTMLAttributes<HTMLElement> {
|
|
@@ -19,6 +20,11 @@ const widthClasses = {
|
|
|
19
20
|
lg: "w-72",
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
/** The enclosing Sidebar's onCollapsedChange, for SidebarCollapseButton. */
|
|
24
|
+
const SidebarCollapsedChangeContext = React.createContext<
|
|
25
|
+
((collapsed: boolean) => void) | undefined
|
|
26
|
+
>(undefined)
|
|
27
|
+
|
|
22
28
|
export function Sidebar({
|
|
23
29
|
collapsed = false,
|
|
24
30
|
onCollapsedChange,
|
|
@@ -28,16 +34,18 @@ export function Sidebar({
|
|
|
28
34
|
...props
|
|
29
35
|
}: SidebarProps) {
|
|
30
36
|
return (
|
|
31
|
-
<
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
<SidebarCollapsedChangeContext.Provider value={onCollapsedChange}>
|
|
38
|
+
<aside
|
|
39
|
+
className={cn(
|
|
40
|
+
"flex h-full flex-col border-e bg-background transition-all duration-200",
|
|
41
|
+
collapsed ? "w-16" : widthClasses[width],
|
|
42
|
+
className
|
|
43
|
+
)}
|
|
44
|
+
{...props}
|
|
45
|
+
>
|
|
46
|
+
{children}
|
|
47
|
+
</aside>
|
|
48
|
+
</SidebarCollapsedChangeContext.Provider>
|
|
41
49
|
)
|
|
42
50
|
}
|
|
43
51
|
|
|
@@ -116,7 +124,7 @@ export function SidebarGroup({
|
|
|
116
124
|
return (
|
|
117
125
|
<div className={cn("py-2", className)} {...props}>
|
|
118
126
|
{label && (
|
|
119
|
-
<div className="mb-2 px-2 text-xs font-semibold uppercase
|
|
127
|
+
<div className="mb-2 px-2 text-xs font-semibold uppercase text-muted-foreground">
|
|
120
128
|
{label}
|
|
121
129
|
</div>
|
|
122
130
|
)}
|
|
@@ -173,9 +181,19 @@ export function SidebarCollapseButton({
|
|
|
173
181
|
className,
|
|
174
182
|
...props
|
|
175
183
|
}: SidebarCollapseButtonProps) {
|
|
184
|
+
const onSidebarCollapsedChange = React.useContext(SidebarCollapsedChangeContext)
|
|
185
|
+
|
|
186
|
+
// Tells the enclosing Sidebar too, once if both were given the same handler
|
|
187
|
+
const toggle = () => {
|
|
188
|
+
onCollapsedChange(!collapsed)
|
|
189
|
+
if (onSidebarCollapsedChange && onSidebarCollapsedChange !== onCollapsedChange) {
|
|
190
|
+
onSidebarCollapsedChange(!collapsed)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
176
194
|
return (
|
|
177
195
|
<button
|
|
178
|
-
onClick={
|
|
196
|
+
onClick={toggle}
|
|
179
197
|
className={cn(
|
|
180
198
|
"flex h-8 w-8 items-center justify-center rounded-md hover:bg-accent",
|
|
181
199
|
className
|
|
@@ -183,20 +201,12 @@ export function SidebarCollapseButton({
|
|
|
183
201
|
aria-label={collapsed ? "Expand sidebar" : "Collapse sidebar"}
|
|
184
202
|
{...props}
|
|
185
203
|
>
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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>
|
|
204
|
+
{/* Points at the edge the sidebar collapses toward, so it mirrors in RTL. */}
|
|
205
|
+
<ChevronsLeftIcon
|
|
206
|
+
size={16}
|
|
207
|
+
strokeWidth={1.5}
|
|
208
|
+
className={cn("transition-transform motion-reduce:transition-none rtl:-scale-x-100", collapsed && "rotate-180")}
|
|
209
|
+
/>
|
|
200
210
|
</button>
|
|
201
211
|
)
|
|
202
212
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as React from "react"
|
|
2
|
+
import { ChevronDownIcon, ChevronUpIcon } from "lucide-react"
|
|
2
3
|
import { cn } from "../../../src/lib/utils"
|
|
3
4
|
|
|
4
5
|
export interface StatsCardProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
@@ -43,7 +44,7 @@ export function StatsCard({
|
|
|
43
44
|
{loading ? (
|
|
44
45
|
<div className="h-8 w-24 animate-pulse rounded bg-muted" />
|
|
45
46
|
) : (
|
|
46
|
-
<p className="text-2xl font-bold
|
|
47
|
+
<p className="text-2xl font-bold">{value}</p>
|
|
47
48
|
)}
|
|
48
49
|
</div>
|
|
49
50
|
{icon && (
|
|
@@ -59,41 +60,23 @@ export function StatsCard({
|
|
|
59
60
|
<span
|
|
60
61
|
className={cn(
|
|
61
62
|
"inline-flex items-center gap-1 font-medium",
|
|
62
|
-
|
|
63
|
-
trend.direction === "
|
|
63
|
+
// The tokens already change with the mode, so no dark: variants.
|
|
64
|
+
trend.direction === "up" && "text-success",
|
|
65
|
+
trend.direction === "down" && "text-destructive",
|
|
64
66
|
trend.direction === "neutral" && "text-muted-foreground"
|
|
65
67
|
)}
|
|
66
68
|
>
|
|
67
69
|
{trend.direction === "up" && (
|
|
68
|
-
<
|
|
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>
|
|
70
|
+
<ChevronUpIcon size={16} strokeWidth={1.5} />
|
|
80
71
|
)}
|
|
81
72
|
{trend.direction === "down" && (
|
|
82
|
-
<
|
|
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>
|
|
73
|
+
<ChevronDownIcon size={16} strokeWidth={1.5} />
|
|
94
74
|
)}
|
|
95
|
-
{
|
|
96
|
-
|
|
75
|
+
{/* One LTR unit: in RTL the sign would otherwise jump to the far end, "12.5%+". */}
|
|
76
|
+
<bdi dir="ltr">
|
|
77
|
+
{trend.value > 0 ? "+" : ""}
|
|
78
|
+
{trend.value}%
|
|
79
|
+
</bdi>
|
|
97
80
|
</span>
|
|
98
81
|
)}
|
|
99
82
|
{description && (
|
|
@@ -4,6 +4,20 @@ import * as React from "react"
|
|
|
4
4
|
import { motion, useReducedMotion } from "framer-motion"
|
|
5
5
|
import { cn } from "../../../src/lib/utils"
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* The colours, without blank entries (a cleared control, say); the fallback
|
|
9
|
+
* when none are left, since an empty list would draw nothing.
|
|
10
|
+
*/
|
|
11
|
+
const filledList = (colors: string[] | undefined, fallback: string[]) => {
|
|
12
|
+
const usable = (colors ?? []).filter((color) => typeof color === "string" && color.trim())
|
|
13
|
+
return usable.length ? usable : fallback
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Theme tokens only, so the gradient follows the project's palette and mode.
|
|
17
|
+
const GRADIENT_COLORS = ["var(--primary)", "var(--secondary)", "var(--chart-5)", "var(--info)"]
|
|
18
|
+
|
|
19
|
+
const TEXT_COLORS = ["var(--primary)", "var(--chart-5)", "var(--primary)"]
|
|
20
|
+
|
|
7
21
|
export interface AnimatedGradientProps
|
|
8
22
|
extends React.HTMLAttributes<HTMLDivElement> {
|
|
9
23
|
/** Gradient colors */
|
|
@@ -28,12 +42,7 @@ const blurValues = {
|
|
|
28
42
|
* Respects prefers-reduced-motion by stopping animation.
|
|
29
43
|
*/
|
|
30
44
|
export function AnimatedGradient({
|
|
31
|
-
colors
|
|
32
|
-
"hsl(var(--primary))",
|
|
33
|
-
"hsl(var(--secondary))",
|
|
34
|
-
"hsl(280 100% 70%)",
|
|
35
|
-
"hsl(200 100% 70%)",
|
|
36
|
-
],
|
|
45
|
+
colors: colorsProp,
|
|
37
46
|
speed = 10,
|
|
38
47
|
blur = "3xl",
|
|
39
48
|
className,
|
|
@@ -41,10 +50,11 @@ export function AnimatedGradient({
|
|
|
41
50
|
...props
|
|
42
51
|
}: AnimatedGradientProps) {
|
|
43
52
|
const prefersReducedMotion = useReducedMotion()
|
|
53
|
+
const colors = filledList(colorsProp, GRADIENT_COLORS)
|
|
44
54
|
|
|
45
55
|
return (
|
|
46
56
|
<div
|
|
47
|
-
className={cn("relative overflow-hidden", className)}
|
|
57
|
+
className={cn("relative isolate overflow-hidden", className)}
|
|
48
58
|
{...props}
|
|
49
59
|
>
|
|
50
60
|
{/* Gradient blobs */}
|
|
@@ -100,12 +110,13 @@ export interface AnimatedGradientTextProps {
|
|
|
100
110
|
}
|
|
101
111
|
|
|
102
112
|
export function AnimatedGradientText({
|
|
103
|
-
colors
|
|
113
|
+
colors: colorsProp,
|
|
104
114
|
speed = 3,
|
|
105
115
|
className,
|
|
106
116
|
children,
|
|
107
117
|
}: AnimatedGradientTextProps) {
|
|
108
118
|
const prefersReducedMotion = useReducedMotion()
|
|
119
|
+
const colors = filledList(colorsProp, TEXT_COLORS)
|
|
109
120
|
|
|
110
121
|
return (
|
|
111
122
|
<motion.span
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
import * as React from "react"
|
|
4
|
-
import { motion, useReducedMotion } from "framer-motion"
|
|
4
|
+
import { animate, motion, useMotionValue, useReducedMotion, useTransform } from "framer-motion"
|
|
5
5
|
import { cn } from "../../../src/lib/utils"
|
|
6
6
|
|
|
7
|
+
/** The value, or the fallback when it is missing or blank (a cleared control, say). */
|
|
8
|
+
const filled = (value: string | undefined, fallback: string) =>
|
|
9
|
+
value && value.trim() ? value : fallback
|
|
10
|
+
|
|
7
11
|
export interface BorderBeamProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
8
12
|
/** Duration of the animation in seconds */
|
|
9
13
|
duration?: number
|
|
@@ -24,62 +28,68 @@ export interface BorderBeamProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
24
28
|
export function BorderBeam({
|
|
25
29
|
duration = 4,
|
|
26
30
|
borderWidth = 2,
|
|
27
|
-
colorFrom
|
|
28
|
-
colorTo
|
|
31
|
+
colorFrom: colorFromProp,
|
|
32
|
+
colorTo: colorToProp,
|
|
29
33
|
delay = 0,
|
|
30
34
|
className,
|
|
31
35
|
children,
|
|
32
36
|
...props
|
|
33
37
|
}: BorderBeamProps) {
|
|
38
|
+
const colorFrom = filled(colorFromProp, "var(--primary)")
|
|
39
|
+
const colorTo = filled(colorToProp, "transparent")
|
|
34
40
|
const prefersReducedMotion = useReducedMotion()
|
|
35
41
|
|
|
42
|
+
// The beam is a conic gradient whose start angle turns, so it runs round
|
|
43
|
+
// the border of any shape; rotating a layer only works for a square.
|
|
44
|
+
const angle = useMotionValue(0)
|
|
45
|
+
React.useEffect(() => {
|
|
46
|
+
if (prefersReducedMotion) return
|
|
47
|
+
const controls = animate(angle, 360, {
|
|
48
|
+
duration,
|
|
49
|
+
delay,
|
|
50
|
+
ease: "linear",
|
|
51
|
+
repeat: Infinity,
|
|
52
|
+
})
|
|
53
|
+
return () => controls.stop()
|
|
54
|
+
}, [angle, duration, delay, prefersReducedMotion])
|
|
55
|
+
const background = useTransform(
|
|
56
|
+
angle,
|
|
57
|
+
(a) => `conic-gradient(from ${a}deg, transparent 0deg 270deg, ${colorTo} 270deg, ${colorFrom} 360deg)`
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
/** Paints only a borderWidth ring: the content box is masked out. */
|
|
61
|
+
const ring: React.CSSProperties = {
|
|
62
|
+
padding: borderWidth,
|
|
63
|
+
mask: "linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)",
|
|
64
|
+
maskComposite: "exclude",
|
|
65
|
+
WebkitMask: "linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)",
|
|
66
|
+
WebkitMaskComposite: "xor",
|
|
67
|
+
}
|
|
68
|
+
|
|
36
69
|
return (
|
|
37
70
|
<div
|
|
38
|
-
className={cn("relative overflow-hidden rounded-lg", className)}
|
|
71
|
+
className={cn("relative isolate overflow-hidden rounded-lg", className)}
|
|
39
72
|
{...props}
|
|
40
73
|
>
|
|
41
|
-
{/*
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
)}
|
|
74
|
+
{/* Content */}
|
|
75
|
+
<div className="relative rounded-[inherit] bg-background">
|
|
76
|
+
{children}
|
|
77
|
+
</div>
|
|
63
78
|
|
|
64
|
-
{/*
|
|
65
|
-
{prefersReducedMotion
|
|
79
|
+
{/* The ring sits above the content, over its outer edge, so the content cannot cover it */}
|
|
80
|
+
{prefersReducedMotion ? (
|
|
66
81
|
<div
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}}
|
|
82
|
+
aria-hidden="true"
|
|
83
|
+
className="pointer-events-none absolute inset-0 z-10 rounded-[inherit]"
|
|
84
|
+
style={{ ...ring, background: colorFrom, opacity: 0.5 }}
|
|
85
|
+
/>
|
|
86
|
+
) : (
|
|
87
|
+
<motion.div
|
|
88
|
+
aria-hidden="true"
|
|
89
|
+
className="pointer-events-none absolute inset-0 z-10 rounded-[inherit]"
|
|
90
|
+
style={{ ...ring, background }}
|
|
76
91
|
/>
|
|
77
92
|
)}
|
|
78
|
-
|
|
79
|
-
{/* Content */}
|
|
80
|
-
<div className="relative z-10 rounded-[inherit] bg-background">
|
|
81
|
-
{children}
|
|
82
|
-
</div>
|
|
83
93
|
</div>
|
|
84
94
|
)
|
|
85
95
|
}
|
|
@@ -93,40 +103,54 @@ export interface GlowingBorderProps extends React.HTMLAttributes<HTMLDivElement>
|
|
|
93
103
|
intensity?: "sm" | "md" | "lg"
|
|
94
104
|
}
|
|
95
105
|
|
|
106
|
+
/**
|
|
107
|
+
* The ring's width and the blur of the glow that hugs it, in px. The glow has
|
|
108
|
+
* no spread, so it stays close to the edge instead of casting a shadow.
|
|
109
|
+
*/
|
|
96
110
|
const intensityValues = {
|
|
97
|
-
sm:
|
|
98
|
-
md:
|
|
99
|
-
lg:
|
|
111
|
+
sm: { ring: 1, glow: 4 },
|
|
112
|
+
md: { ring: 1, glow: 8 },
|
|
113
|
+
lg: { ring: 2, glow: 12 },
|
|
100
114
|
}
|
|
101
115
|
|
|
102
116
|
/**
|
|
103
|
-
* Static glowing border
|
|
117
|
+
* Static glowing border: a coloured ring on the element's edge with a soft,
|
|
118
|
+
* tight glow around it. The ring is the border, so the content needs none.
|
|
104
119
|
*/
|
|
105
120
|
export function GlowingBorder({
|
|
106
|
-
glowColor
|
|
107
|
-
borderRadius
|
|
121
|
+
glowColor: glowColorProp,
|
|
122
|
+
borderRadius: borderRadiusProp,
|
|
108
123
|
intensity = "md",
|
|
109
124
|
className,
|
|
110
125
|
children,
|
|
111
126
|
...props
|
|
112
127
|
}: GlowingBorderProps) {
|
|
128
|
+
const glowColor = filled(glowColorProp, "color-mix(in oklch, var(--primary) 50%, transparent)")
|
|
129
|
+
const borderRadius = filled(borderRadiusProp, "0.5rem")
|
|
130
|
+
const { ring, glow } = intensityValues[intensity]
|
|
113
131
|
return (
|
|
114
132
|
<div
|
|
115
|
-
className={cn("relative", className)}
|
|
133
|
+
className={cn("relative isolate", className)}
|
|
116
134
|
style={{
|
|
117
135
|
borderRadius,
|
|
118
136
|
}}
|
|
119
137
|
{...props}
|
|
120
138
|
>
|
|
121
|
-
{
|
|
139
|
+
{children}
|
|
140
|
+
{/*
|
|
141
|
+
Above the content, so a card's own background cannot cover it. The inset
|
|
142
|
+
shadow draws the ring just inside the edge, over any neutral border the
|
|
143
|
+
content has; the outer ones are the glow.
|
|
144
|
+
*/}
|
|
122
145
|
<div
|
|
123
|
-
|
|
146
|
+
aria-hidden="true"
|
|
147
|
+
className="pointer-events-none absolute inset-0 z-10"
|
|
124
148
|
style={{
|
|
125
149
|
borderRadius,
|
|
126
|
-
|
|
150
|
+
// Two stacked blurs, no spread: a denser glow at the edge that fades fast.
|
|
151
|
+
boxShadow: `inset 0 0 0 ${ring}px ${glowColor}, 0 0 ${glow / 2}px 0 ${glowColor}, 0 0 ${glow}px 0 ${glowColor}`,
|
|
127
152
|
}}
|
|
128
153
|
/>
|
|
129
|
-
{children}
|
|
130
154
|
</div>
|
|
131
155
|
)
|
|
132
156
|
}
|