diff --git a/package.json b/package.json index 1e00488..4501442 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "./dropdown-menu": "./src/components/dropdown-menu.tsx", "./empty": "./src/components/empty.tsx", "./field": "./src/components/field.tsx", + "./glow-follow": "./src/components/glow-follow.ts", "./hover-card": "./src/components/hover-card.tsx", "./input": "./src/components/input.tsx", "./item": "./src/components/item.tsx", @@ -61,7 +62,8 @@ "./utils": "./src/utils.ts" }, "sideEffects": [ - "**/*.css" + "**/*.css", + "./src/components/glow-follow.ts" ], "dependencies": { "@base-ui/react": "^1.3.0", diff --git a/src/components/accordion.tsx b/src/components/accordion.tsx index a95706e..ae5513d 100644 --- a/src/components/accordion.tsx +++ b/src/components/accordion.tsx @@ -24,7 +24,7 @@ function AccordionItem({ return ( ) @@ -40,7 +40,7 @@ function AccordionTrigger({ 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", + "group/alert relative grid w-full gap-0.5 rounded-xl border-l-[3px] border border-transparent px-3.5 py-3 text-left text-sm shadow-[0_10px_30px_rgba(0,0,0,0.28),0_0_0_1px_rgba(255,255,255,0.03)_inset] 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", { variants: { variant: { default: - "border-border border-l-border bg-card text-card-foreground", + "border-white/10 border-l-white/20 bg-white/[0.035] text-card-foreground [backdrop-filter:blur(12px)] [-webkit-backdrop-filter:blur(12px)]", info: - "border-blue-500/20 border-l-blue-500 bg-blue-500/5 text-foreground *:data-[slot=alert-description]:text-foreground/80 *:[svg]:text-blue-400", + "border-blue-500/20 border-l-blue-500 bg-blue-500/5 text-foreground shadow-[0_10px_30px_rgba(0,0,0,0.28),0_0_18px_rgba(59,130,246,0.08)] *:data-[slot=alert-description]:text-foreground/80 *:[svg]:text-blue-400", success: - "border-emerald-500/20 border-l-emerald-500 bg-emerald-500/5 text-foreground *:data-[slot=alert-description]:text-foreground/80 *:[svg]:text-emerald-400", + "border-emerald-500/20 border-l-emerald-500 bg-emerald-500/5 text-foreground shadow-[0_10px_30px_rgba(0,0,0,0.28),0_0_18px_rgba(16,185,129,0.08)] *:data-[slot=alert-description]:text-foreground/80 *:[svg]:text-emerald-400", warning: - "border-amber-400/20 border-l-amber-400 bg-amber-400/5 text-foreground *:data-[slot=alert-description]:text-foreground/80 *:[svg]:text-amber-400", + "border-amber-400/20 border-l-amber-400 bg-amber-400/5 text-foreground shadow-[0_10px_30px_rgba(0,0,0,0.28),0_0_18px_rgba(245,181,23,0.10)] *:data-[slot=alert-description]:text-foreground/80 *:[svg]:text-amber-400", destructive: - "border-destructive/20 border-l-destructive bg-destructive/5 text-destructive *:data-[slot=alert-description]:text-destructive/80 *:[svg]:text-current", + "border-destructive/20 border-l-destructive bg-destructive/5 text-destructive shadow-[0_10px_30px_rgba(0,0,0,0.28),0_0_18px_oklch(0.63_0.24_27_/_0.10)] *:data-[slot=alert-description]:text-destructive/80 *:[svg]:text-current", }, }, defaultVariants: { diff --git a/src/components/avatar.tsx b/src/components/avatar.tsx index 683526f..d43326b 100644 --- a/src/components/avatar.tsx +++ b/src/components/avatar.tsx @@ -15,7 +15,7 @@ function Avatar({ data-slot="avatar" data-size={size} className={cn( - "group/avatar relative flex size-8 shrink-0 rounded-full select-none after:absolute after:inset-0 after:rounded-full after:border after:border-border after:mix-blend-darken data-[size=lg]:size-10 data-[size=sm]:size-6 dark:after:mix-blend-lighten", + "group/avatar relative flex size-8 shrink-0 rounded-full select-none shadow-[0_0_14px_rgba(255,255,255,0.04)] after:absolute after:inset-0 after:rounded-full after:border after:border-white/[0.12] after:mix-blend-lighten data-[size=lg]:size-10 data-[size=sm]:size-6", className )} {...props} @@ -47,7 +47,7 @@ function AvatarFallback({ svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3", + "relative flex size-8 shrink-0 items-center justify-center rounded-full bg-white/[0.08] text-sm text-white/60 ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3", className )} {...props} diff --git a/src/components/badge.tsx b/src/components/badge.tsx index 1f94568..bf7a2c1 100644 --- a/src/components/badge.tsx +++ b/src/components/badge.tsx @@ -3,19 +3,19 @@ import { cva, type VariantProps } from "class-variance-authority"; import { cn } from "../utils"; const badgeVariants = cva( - "inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all [&>svg]:size-3", + "inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all shadow-[0_0_10px_rgba(255,255,255,0.03)] [&>svg]:size-3", { variants: { variant: { - default: "bg-primary text-primary-foreground", - secondary: "bg-secondary text-secondary-foreground", - destructive: "bg-destructive/10 text-destructive", - outline: "border-border text-foreground", - ghost: "hover:bg-muted hover:text-muted-foreground", - beta: "bg-amber-500/15 text-amber-300 border-amber-500/30", - stable: "bg-emerald-500/15 text-emerald-300 border-emerald-500/30", + default: "border-primary/25 bg-primary/[0.15] text-primary shadow-[0_0_12px_rgba(245,181,23,0.14)]", + secondary: "border-white/10 bg-white/10 text-white/80", + destructive: "border-destructive/25 bg-destructive/10 text-destructive shadow-[0_0_12px_oklch(0.63_0.24_27_/_0.13)]", + outline: "border-white/10 bg-white/[0.03] text-white/80", + ghost: "text-white/70 hover:bg-white/[0.06] hover:text-white", + beta: "bg-amber-500/[0.15] text-amber-300 border-amber-500/30 shadow-[0_0_12px_rgba(245,181,23,0.12)]", + stable: "bg-emerald-500/[0.15] text-emerald-300 border-emerald-500/30 shadow-[0_0_12px_rgba(16,185,129,0.12)]", deprecated: "bg-destructive/10 text-destructive border-destructive/25", - new: "bg-blue-500/15 text-blue-300 border-blue-500/30", + new: "bg-blue-500/[0.15] text-blue-300 border-blue-500/30 shadow-[0_0_12px_rgba(59,130,246,0.12)]", }, }, defaultVariants: { variant: "default" }, diff --git a/src/components/button-group.tsx b/src/components/button-group.tsx index e2fc67d..732e8bc 100644 --- a/src/components/button-group.tsx +++ b/src/components/button-group.tsx @@ -49,7 +49,7 @@ function ButtonGroupText({ return ( & { + frosted?: boolean; + focus?: boolean; + spotlight?: boolean; + size?: "default" | "sm"; + "data-frosted"?: "true" | "false"; + "data-focus"?: "true" | "false"; + "data-spotlight"?: "true" | "false"; +}; + function Card({ className, + frosted = false, + focus = false, + spotlight = false, size = "default", + "data-frosted": dataFrosted, + "data-focus": dataFocus, + "data-spotlight": dataSpotlight, + onMouseEnter, + onMouseLeave, + onMouseMove, + onPointerEnter, + onPointerLeave, + onPointerMove, ...props -}: React.ComponentProps<"div"> & { size?: "default" | "sm" }) { +}: CardProps) { + const glassState = frosted ? "true" : dataFrosted === "true" || dataFrosted === "false" ? dataFrosted : undefined; + const focusState = focus ? "true" : dataFocus === "true" || dataFocus === "false" ? dataFocus : undefined; + const spotlightState = spotlight ? "true" : dataSpotlight === "true" || dataSpotlight === "false" ? dataSpotlight : undefined; + const handleMouseMove = (event: React.MouseEvent) => { + onMouseMove?.(event); + + setGlowFollowPosition(event.currentTarget, event.clientX, event.clientY); + }; + const handleMouseEnter = (event: React.MouseEvent) => { + onMouseEnter?.(event); + + setGlowFollowPosition(event.currentTarget, event.clientX, event.clientY); + }; + const handleMouseLeave = (event: React.MouseEvent) => { + onMouseLeave?.(event); + + clearGlowFollowPosition(event.currentTarget); + }; + const handlePointerMove = (event: React.PointerEvent) => { + onPointerMove?.(event); + + setGlowFollowPosition(event.currentTarget, event.clientX, event.clientY); + }; + const handlePointerEnter = (event: React.PointerEvent) => { + onPointerEnter?.(event); + + setGlowFollowPosition(event.currentTarget, event.clientX, event.clientY); + }; + const handlePointerLeave = (event: React.PointerEvent) => { + onPointerLeave?.(event); + + clearGlowFollowPosition(event.currentTarget); + }; + return (
img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl", + glassState ? "overview-glass-card" : "rounded-xl border border-border bg-card", + "group/card flex flex-col gap-4 py-4 text-sm text-card-foreground has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-2xl *:[img:last-child]:rounded-b-2xl", className )} + onMouseEnter={spotlightState === "true" ? handleMouseEnter : onMouseEnter} + onMouseLeave={spotlightState === "true" ? handleMouseLeave : onMouseLeave} + onMouseMove={spotlightState === "true" ? handleMouseMove : onMouseMove} + onPointerEnter={spotlightState === "true" ? handlePointerEnter : onPointerEnter} + onPointerLeave={spotlightState === "true" ? handlePointerLeave : onPointerLeave} + onPointerMove={spotlightState === "true" ? handlePointerMove : onPointerMove} {...props} /> ); @@ -80,7 +150,7 @@ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
diff --git a/src/components/checkbox.tsx b/src/components/checkbox.tsx index 3c1cb90..6b909ce 100644 --- a/src/components/checkbox.tsx +++ b/src/components/checkbox.tsx @@ -12,7 +12,7 @@ function Checkbox({ -
+
{children} diff --git a/src/components/empty.tsx b/src/components/empty.tsx index 8e17b87..f4e6c7d 100644 --- a/src/components/empty.tsx +++ b/src/components/empty.tsx @@ -7,7 +7,7 @@ function Empty({ className, ...props }: React.ComponentProps<"div">) {
{ + const element = getGlowFollowElement(event.target); + + if (element) { + setGlowFollowPosition(element, event.clientX, event.clientY); + } + }, + { passive: true }, + ); + + document.addEventListener( + "mousemove", + (event) => { + const element = getGlowFollowElement(event.target); + + if (element) { + setGlowFollowPosition(element, event.clientX, event.clientY); + } + }, + { passive: true }, + ); + + document.addEventListener( + "pointerout", + (event) => { + const element = getGlowFollowElement(event.target); + + if (!element || (event.relatedTarget instanceof Node && element.contains(event.relatedTarget))) { + return; + } + + clearGlowFollowPosition(element); + }, + { passive: true }, + ); + + document.addEventListener( + "mouseout", + (event) => { + const element = getGlowFollowElement(event.target); + + if (!element || (event.relatedTarget instanceof Node && element.contains(event.relatedTarget))) { + return; + } + + clearGlowFollowPosition(element); + }, + { passive: true }, + ); +} + +installGlowFollow(); diff --git a/src/components/hover-card.tsx b/src/components/hover-card.tsx index 776a665..c3042a3 100644 --- a/src/components/hover-card.tsx +++ b/src/components/hover-card.tsx @@ -30,7 +30,7 @@ function HoverCardContent({ align={align} sideOffset={sideOffset} className={cn( - "z-50 w-64 origin-(--radix-hover-card-content-transform-origin) rounded-lg bg-popover p-2.5 text-sm text-popover-foreground shadow-md ring-1 ring-foreground/10 outline-hidden duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95", + "glass-dropdown z-50 w-64 origin-(--radix-hover-card-content-transform-origin) rounded-xl p-2.5 text-sm text-popover-foreground outline-hidden duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95", className )} {...props} diff --git a/src/components/input.tsx b/src/components/input.tsx index a768eee..1b0cd9c 100644 --- a/src/components/input.tsx +++ b/src/components/input.tsx @@ -7,7 +7,7 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) { type={type} data-slot="input" className={cn( - "flex h-8 w-full rounded-lg border border-white/[0.09] bg-white/[0.04] px-3 py-1 text-sm transition-all placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:border-primary/40 focus-visible:bg-white/[0.06] focus-visible:shadow-[0_0_0_4px_oklch(0.80_0.17_90_/_0.08)] disabled:cursor-not-allowed disabled:opacity-50", + "glass-surface flex h-9 w-full rounded-lg border-white/10 px-3 py-1 text-sm text-white/90 shadow-[0_0_15px_rgba(255,255,255,0.02)] transition-all placeholder:text-white/[0.35] focus-visible:border-primary/[0.45] focus-visible:bg-white/[0.055] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/20 focus-visible:shadow-[0_0_0_4px_rgba(245,181,23,0.08),0_0_22px_rgba(245,181,23,0.06)] disabled:cursor-not-allowed disabled:opacity-50", className )} {...props} diff --git a/src/components/item.tsx b/src/components/item.tsx index 53d0aaa..8b62bbe 100644 --- a/src/components/item.tsx +++ b/src/components/item.tsx @@ -34,13 +34,13 @@ function ItemSeparator({ } const itemVariants = cva( - "group/item flex w-full flex-wrap items-center rounded-lg border text-sm transition-colors duration-100 outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 [a]:transition-colors [a]:hover:bg-muted", + "group/item flex w-full flex-wrap items-center rounded-lg border text-sm transition-colors duration-100 outline-none focus-visible:border-primary/[0.45] focus-visible:ring-[3px] focus-visible:ring-primary/20 [a]:transition-colors [a]:hover:bg-white/[0.04]", { variants: { variant: { - default: "border-transparent", - outline: "border-border", - muted: "border-transparent bg-muted/50", + default: "border-transparent hover:bg-white/[0.035]", + outline: "border-white/[0.08] bg-white/[0.02]", + muted: "border-white/[0.06] bg-white/[0.04]", }, size: { default: "gap-2.5 px-3 py-2.5", diff --git a/src/components/metric-card.tsx b/src/components/metric-card.tsx index 79cbc03..2702de7 100644 --- a/src/components/metric-card.tsx +++ b/src/components/metric-card.tsx @@ -1,34 +1,54 @@ import * as React from "react"; +import { clearGlowFollowPosition, installGlowFollow, setGlowFollowPosition } from "./glow-follow"; import { cn } from "../utils"; +// Ensure global glow-follow listeners are installed when this module loads. +installGlowFollow(); + interface MetricCardProps { label: string; value: string | number; icon?: React.ReactNode; delta?: { value: string; direction: "up" | "down" | "neutral" }; + frosted?: boolean; + focus?: boolean; + spotlight?: boolean; className?: string; } -const DELTA_STYLES = { up: "text-emerald-400", down: "text-red-400", neutral: "text-zinc-400" }; +const DELTA_STYLES = { up: "text-emerald-400", down: "text-red-400", neutral: "text-white/[0.45]" }; const DELTA_ARROWS = { up: "↑", down: "↓", neutral: "→" }; -export function MetricCard({ label, value, icon, delta, className }: MetricCardProps) { +export function MetricCard({ label, value, icon, delta, frosted = true, focus = false, spotlight = false, className }: MetricCardProps) { return (
setGlowFollowPosition(event.currentTarget, event.clientX, event.clientY)} + onMouseLeave={(event) => clearGlowFollowPosition(event.currentTarget)} + onMouseMove={(event) => setGlowFollowPosition(event.currentTarget, event.clientX, event.clientY)} + onPointerEnter={(event) => setGlowFollowPosition(event.currentTarget, event.clientX, event.clientY)} + onPointerLeave={(event) => clearGlowFollowPosition(event.currentTarget)} + onPointerMove={(event) => setGlowFollowPosition(event.currentTarget, event.clientX, event.clientY)} >
- {label} - {icon && {icon}} + {label} + {icon && ( + + {icon} + + )}
-
- {value} +
+ {value} {delta && ( - + {DELTA_ARROWS[delta.direction]} {delta.value} )} diff --git a/src/components/popover.tsx b/src/components/popover.tsx index 3228a60..42b3cac 100644 --- a/src/components/popover.tsx +++ b/src/components/popover.tsx @@ -28,7 +28,7 @@ function PopoverContent({ align={align} sideOffset={sideOffset} className={cn( - "z-50 flex w-72 origin-(--radix-popover-content-transform-origin) flex-col gap-2.5 rounded-xl border border-white/[0.1] bg-[rgba(14,14,16,0.88)] p-2.5 text-sm text-popover-foreground [backdrop-filter:blur(20px)_saturate(1.5)] [-webkit-backdrop-filter:blur(20px)_saturate(1.5)] shadow-[0_0_0_1px_rgba(255,255,255,0.04)_inset,0_8px_32px_rgba(0,0,0,0.6)] outline-hidden duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95", + "glass-dropdown z-50 flex w-72 origin-(--radix-popover-content-transform-origin) flex-col gap-2.5 rounded-xl p-2.5 text-sm text-popover-foreground outline-hidden duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95", className )} {...props} diff --git a/src/components/progress.tsx b/src/components/progress.tsx index 59fac6c..d7666ad 100644 --- a/src/components/progress.tsx +++ b/src/components/progress.tsx @@ -1,27 +1,105 @@ +"use client" + import * as React from "react" import { Progress as ProgressPrimitive } from "radix-ui" import { cn } from "../utils" +const PARTICLES = [ + { x: 13, y: -4, delay: -40, scale: 1 }, + { x: 24, y: 3, delay: -220, scale: 0.75 }, + { x: 9, y: 4, delay: -400, scale: 0.9 }, + { x: 18, y: -3, delay: -580, scale: 0.65 }, + { x: 29, y: -5, delay: -760, scale: 0.8 }, + { x: 34, y: 5, delay: -940, scale: 0.7 }, + { x: 16, y: 6, delay: -1120, scale: 0.58 }, + { x: 38, y: -2, delay: -1300, scale: 0.72 }, + { x: 27, y: 1, delay: -1480, scale: 0.52 }, +] + +const PARTICLE_DECAY_MS = 1400 + +function particleStyle({ x, y, delay, scale }: (typeof PARTICLES)[number]) { + return { + "--progress-particle-x": `${x}px`, + "--progress-particle-y": `${y}px`, + "--progress-particle-delay": `${delay}ms`, + "--progress-particle-scale": scale, + } as React.CSSProperties +} + function Progress({ className, value, ...props }: React.ComponentProps) { + const progressValue = typeof value === "number" ? Math.max(0, Math.min(100, value)) : 0 + const [particlesActive, setParticlesActive] = React.useState(false) + const previousProgressValue = React.useRef(progressValue) + const particleTimeout = React.useRef(null) + + React.useEffect(() => { + if (previousProgressValue.current === progressValue) { + return + } + + previousProgressValue.current = progressValue + + if (particleTimeout.current) { + window.clearTimeout(particleTimeout.current) + } + + if (progressValue <= 0) { + setParticlesActive(false) + return + } + + setParticlesActive(true) + particleTimeout.current = window.setTimeout(() => { + setParticlesActive(false) + }, PARTICLE_DECAY_MS) + }, [progressValue]) + + React.useEffect(() => { + return () => { + if (particleTimeout.current) { + window.clearTimeout(particleTimeout.current) + } + } + }, []) + return ( - + + + ) } diff --git a/src/components/radio-group.tsx b/src/components/radio-group.tsx index 389b492..bc407b4 100644 --- a/src/components/radio-group.tsx +++ b/src/components/radio-group.tsx @@ -24,7 +24,7 @@ function RadioGroupItem({ ) { return (
); diff --git a/src/components/slider.tsx b/src/components/slider.tsx index d4beb8c..0d5602b 100644 --- a/src/components/slider.tsx +++ b/src/components/slider.tsx @@ -36,18 +36,18 @@ function Slider({ > {Array.from({ length: _values.length }, (_, index) => ( ))} diff --git a/src/components/switch.tsx b/src/components/switch.tsx index 1e5b290..a142f3c 100644 --- a/src/components/switch.tsx +++ b/src/components/switch.tsx @@ -15,14 +15,14 @@ function Switch({ data-slot="switch" data-size={size} className={cn( - "peer group/switch relative inline-flex shrink-0 items-center rounded-full border border-transparent transition-all outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=default]:h-[18.4px] data-[size=default]:w-[32px] data-[size=sm]:h-[14px] data-[size=sm]:w-[24px] dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input dark:data-[state=unchecked]:bg-input/80 data-disabled:cursor-not-allowed data-disabled:opacity-50", + "peer group/switch relative inline-flex shrink-0 items-center rounded-full border border-white/10 shadow-[0_0_12px_rgba(255,255,255,0.03)] transition-all outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-primary/[0.45] focus-visible:ring-3 focus-visible:ring-primary/20 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=default]:h-[18.4px] data-[size=default]:w-[32px] data-[size=sm]:h-[14px] data-[size=sm]:w-[24px] data-[state=checked]:border-primary/40 data-[state=checked]:bg-primary data-[state=checked]:shadow-[0_0_16px_rgba(245,181,23,0.25)] data-[state=unchecked]:bg-white/[0.07] data-disabled:cursor-not-allowed data-disabled:opacity-50", className )} {...props} > ) diff --git a/src/components/table.tsx b/src/components/table.tsx index ffac59a..0d5145d 100644 --- a/src/components/table.tsx +++ b/src/components/table.tsx @@ -31,7 +31,7 @@ function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) { return ( tr]:last:border-b-0", className)} + className={cn("border-t border-white/[0.06] bg-white/[0.035] font-medium [&>tr]:last:border-b-0", className)} {...props} /> ); @@ -42,7 +42,7 @@ function TableRow({ className, ...props }: React.ComponentProps<"tr">) { ) { ) {