"use client"; import * as React from "react"; import Link from "next/link"; import { AnimatePresence, motion } from "framer-motion"; import { X } from "lucide-react"; import { cn } from "@/lib/utils"; import { formatSC } from "@spinza/shared"; /* ------------------------------------------------------------------ Button */ type ButtonVariant = "primary" | "secondary" | "ghost" | "danger" | "accent" | "outline"; type ButtonSize = "sm" | "md" | "lg" | "xl" | "icon"; export interface ButtonProps extends React.ButtonHTMLAttributes { variant?: ButtonVariant; size?: ButtonSize; loading?: boolean; href?: string; } const variantClass: Record = { primary: "bg-fg text-bg hover:bg-white active:scale-[0.98] shadow-[0_10px_30px_-12px_rgba(255,255,255,0.35)]", accent: "bg-[linear-gradient(180deg,#e8cf8f,#c9a961)] text-[#1a1406] hover:brightness-110 active:scale-[0.98] shadow-glow", secondary: "surface-2 text-fg hover:bg-surface-3", outline: "border border-line-2 text-fg hover:bg-surface-2", ghost: "text-fg-2 hover:text-fg hover:bg-surface-2", danger: "bg-danger/15 text-danger border border-danger/30 hover:bg-danger/25", }; const sizeClass: Record = { sm: "h-9 px-3 text-[13px] rounded-sm gap-1.5", md: "h-11 px-4 text-sm rounded-md gap-2", lg: "h-12 px-6 text-[15px] rounded-md gap-2", xl: "h-14 px-8 text-base rounded-lg gap-2.5", icon: "h-11 w-11 rounded-md", }; export const Button = React.forwardRef(function Button({ className, variant = "primary", size = "md", loading, href, children, disabled, ...props }, ref) { const cls = cn("inline-flex items-center justify-center font-semibold tracking-tight transition-all duration-200 select-none focus-ring disabled:opacity-50 disabled:pointer-events-none whitespace-nowrap", variantClass[variant], sizeClass[size], className); if (href) { return ( {children} ); } return ( ); }); export function Spinner({ className }: { className?: string }) { return ( ); } /* -------------------------------------------------------------------- Card */ export function Card({ className, children, as: Tag = "div", ...props }: React.HTMLAttributes & { as?: React.ElementType }) { return ( {children} ); } /* ------------------------------------------------------------------- Badge */ export function Badge({ className, tone = "neutral", children }: { className?: string; tone?: "neutral" | "accent" | "success" | "danger" | "info" | "new"; children: React.ReactNode }) { const tones = { neutral: "bg-surface-2 text-fg-2 border-line", accent: "bg-accent-soft text-accent-2 border-accent/30", success: "bg-success/10 text-success border-success/30", danger: "bg-danger/10 text-danger border-danger/30", info: "bg-info/10 text-info border-info/30", new: "bg-[#ff5c7a]/15 text-[#ff8aa0] border-[#ff5c7a]/30", }; return {children}; } /* ------------------------------------------------------------------- Input */ export const Input = React.forwardRef & { label?: string; hint?: string; error?: string | null }>(function Input({ className, label, hint, error, id, ...props }, ref) { const generatedId = React.useId(); const inputId = id ?? generatedId; return ( ); }); /* ------------------------------------------------------------------ Switch */ export function Switch({ checked, onChange, label, description }: { checked: boolean; onChange: (v: boolean) => void; label: string; description?: string }) { return ( ); } /* ---------------------------------------------------------------- Progress */ export function Progress({ value, max = 1, className, tone = "accent" }: { value: number; max?: number; className?: string; tone?: "accent" | "success" | "info" | "credit" }) { const pct = Math.max(0, Math.min(100, (value / (max || 1)) * 100)); const tones = { accent: "bg-[linear-gradient(90deg,#c9a961,#e8cf8f)]", success: "bg-success", info: "bg-info", credit: "bg-credit" }; return (
); } /* -------------------------------------------------------------- Credits */ export function Credits({ amount, className, size = "md", sign }: { amount: number; className?: string; size?: "sm" | "md" | "lg" | "xl"; sign?: boolean }) { const s = { sm: "text-sm", md: "text-base", lg: "text-2xl", xl: "text-4xl sm:text-5xl" }[size]; return ( {sign && amount > 0 ? "+" : ""} {formatSC(amount, { unit: false })} SC ); } /* ------------------------------------------------------------------ Sheet */ export function Sheet({ open, onClose, title, children, side = "bottom", className }: { open: boolean; onClose: () => void; title?: string; children: React.ReactNode; side?: "bottom" | "right" | "center"; className?: string }) { React.useEffect(() => { if (!open) return; const onKey = (e: KeyboardEvent) => e.key === "Escape" && onClose(); window.addEventListener("keydown", onKey); document.body.style.overflow = "hidden"; return () => { window.removeEventListener("keydown", onKey); document.body.style.overflow = ""; }; }, [open, onClose]); const variants = { bottom: { initial: { y: "100%" }, animate: { y: 0 }, exit: { y: "100%" }, cls: "inset-x-0 bottom-0 rounded-t-xl max-h-[88dvh] sm:inset-x-auto sm:left-1/2 sm:-translate-x-1/2 sm:bottom-6 sm:w-[560px] sm:rounded-xl" }, right: { initial: { x: "100%" }, animate: { x: 0 }, exit: { x: "100%" }, cls: "inset-y-0 right-0 w-full max-w-md" }, center: { initial: { scale: 0.96, opacity: 0 }, animate: { scale: 1, opacity: 1 }, exit: { scale: 0.96, opacity: 0 }, cls: "left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[calc(100%-2rem)] max-w-lg rounded-xl" }, }[side]; return ( {open ? ( <>

{title}

{children}
) : null}
); } /* ------------------------------------------------------------------- Tabs */ export function Tabs({ value, onChange, items, className }: { value: T; onChange: (v: T) => void; items: { value: T; label: string }[]; className?: string }) { return (
{items.map((it) => ( ))}
); } /* ------------------------------------------------------------- Empty state */ export function Empty({ title, description, action, icon }: { title: string; description?: string; action?: React.ReactNode; icon?: React.ReactNode }) { return (
{icon ?
{icon}
: null}

{title}

{description ?

{description}

: null} {action ?
{action}
: null}
); } /* --------------------------------------------------------------- Skeleton */ export function Skeleton({ className }: { className?: string }) { return
; } /* ------------------------------------------------------------ SectionHead */ export function SectionHead({ title, eyebrow, action, className }: { title: string; eyebrow?: string; action?: React.ReactNode; className?: string }) { return (
{eyebrow ?
{eyebrow}
: null}

{title}

{action}
); } export { motion, AnimatePresence };