/** * Admin UI Primitives * * Single source of truth for admin panel styling. * Every input, select, button, badge, card, and modal in /admin should use these. */ import { type ComponentPropsWithoutRef, forwardRef } from "react"; import { X } from "lucide-react"; import { useFocusTrap } from "@/hooks/useFocusTrap"; /* ============================== */ /* Style tokens */ /* ============================== */ export const adminStyles = { /** Standard input — full width, rounded-lg */ input: "w-full rounded-lg border border-neutral-200 bg-neutral-100 px-4 py-2.5 text-neutral-900 placeholder-neutral-400 outline-none hover:border-gold/30 focus:border-gold transition-colors dark:border-white/10 dark:bg-neutral-800 dark:text-white dark:placeholder-neutral-500", /** Compact input — smaller padding, text-sm */ inputSm: "rounded-md border border-neutral-200 bg-neutral-100 px-2.5 py-1.5 text-sm text-neutral-900 placeholder-neutral-400 outline-none hover:border-gold/30 focus:border-gold transition-colors dark:border-white/10 dark:bg-neutral-800 dark:text-white dark:placeholder-neutral-600", /** Dashed input — for "add new" fields */ inputDashed: "flex-1 rounded-lg border border-dashed border-neutral-200 bg-neutral-100/50 px-4 py-2 text-sm text-neutral-900 placeholder-neutral-400 outline-none hover:border-gold/30 hover:placeholder-neutral-500 focus:border-gold/50 transition-colors dark:border-white/10 dark:bg-neutral-800/50 dark:text-white dark:placeholder-neutral-600", /** Textarea — same as input + resize-none */ textarea: "w-full rounded-lg border border-neutral-200 bg-neutral-100 px-4 py-2.5 text-neutral-900 placeholder-neutral-400 outline-none hover:border-gold/30 focus:border-gold transition-colors resize-none dark:border-white/10 dark:bg-neutral-800 dark:text-white dark:placeholder-neutral-500", /** Native select */ select: "w-full rounded-lg border border-neutral-200 bg-neutral-100 px-3 py-2 text-sm text-neutral-900 outline-none focus:border-gold/40 transition-colors [color-scheme:light] dark:border-white/10 dark:bg-neutral-800 dark:text-white dark:[color-scheme:dark]", /** Select option */ option: "bg-white dark:bg-neutral-900", /** Primary button — gold solid */ btnPrimary: "inline-flex items-center justify-center gap-2 rounded-lg bg-gold px-4 py-2.5 text-sm font-medium text-black transition-all hover:bg-gold-light hover:opacity-90 disabled:opacity-50 disabled:cursor-not-allowed", /** Secondary button — outline */ btnSecondary: "inline-flex items-center justify-center gap-2 rounded-lg border border-neutral-200 bg-neutral-100 px-4 py-2.5 text-sm font-medium text-neutral-700 transition-colors hover:bg-neutral-200 dark:border-white/10 dark:bg-neutral-800 dark:text-neutral-300 dark:hover:bg-neutral-700", /** Small gold accent button */ btnGoldSm: "rounded-md bg-gold/20 border border-gold/30 px-3 py-1 text-xs font-medium text-amber-700 hover:bg-gold/30 transition-colors disabled:opacity-30 disabled:cursor-not-allowed dark:text-gold", /** Cancel/muted small button */ btnCancelSm: "rounded-md border border-neutral-200 px-3 py-1 text-xs text-neutral-500 hover:text-neutral-900 hover:border-neutral-300 transition-colors dark:border-white/10 dark:text-neutral-400 dark:hover:text-white dark:hover:border-white/25", /** Danger button */ btnDanger: "inline-flex items-center justify-center gap-2 rounded-lg bg-red-600 px-4 py-2.5 text-sm font-medium text-white transition-colors hover:bg-red-500", /** Card container */ card: "rounded-xl border border-neutral-200 bg-white p-5 dark:border-white/10 dark:bg-neutral-900", /** Modal overlay */ modalOverlay: "fixed inset-0 z-50 flex items-center justify-center p-4", /** Modal backdrop */ modalBackdrop: "absolute inset-0 bg-black/70 backdrop-blur-sm", /** Modal content */ modalContent: "relative w-full rounded-2xl border border-neutral-200 bg-white p-6 shadow-2xl dark:border-white/[0.08] dark:bg-[#0a0a0a]", /** Modal close button */ modalClose: "absolute right-3 top-3 flex h-8 w-8 items-center justify-center rounded-full text-neutral-500 hover:bg-neutral-100 hover:text-neutral-900 transition-colors cursor-pointer dark:hover:bg-white/[0.06] dark:hover:text-white", /** Label */ label: "block text-xs font-medium uppercase tracking-wider text-neutral-500 dark:text-neutral-400", /** Section heading in admin */ sectionTitle: "text-lg font-bold text-neutral-900 dark:text-white", /** Dashed add-item button */ addButton: "flex items-center gap-2 rounded-lg border border-dashed border-neutral-300 px-4 py-2.5 text-sm text-neutral-500 hover:text-neutral-900 hover:border-neutral-400 transition-colors dark:border-white/20 dark:text-neutral-400 dark:hover:text-white dark:hover:border-white/40", } as const; /* ============================== */ /* Input components */ /* ============================== */ interface AdminInputProps extends ComponentPropsWithoutRef<"input"> { variant?: "default" | "sm" | "dashed"; } export const AdminInput = forwardRef( function AdminInput({ variant = "default", className = "", ...props }, ref) { const base = variant === "sm" ? adminStyles.inputSm : variant === "dashed" ? adminStyles.inputDashed : adminStyles.input; return ; }, ); interface AdminTextareaProps extends ComponentPropsWithoutRef<"textarea"> { autoResize?: boolean; } export const AdminTextarea = forwardRef( function AdminTextarea({ autoResize, className = "", ...props }, ref) { function handleInput(e: React.FormEvent) { if (autoResize) { const el = e.currentTarget; el.style.height = "auto"; el.style.height = el.scrollHeight + "px"; } } return (