'use client'; import { useState, useEffect, useRef } from 'react'; import { createPortal } from 'react-dom'; import { XMarkIcon, EllipsisVerticalIcon } from '@heroicons/react/24/outline'; import clsx from 'clsx'; // ----- Skeleton loaders ----- export function TableSkeleton({ rows = 5 }: { rows?: number }) { return (
{Array.from({ length: rows }).map((_, i) => (
))}
); } export function CardSkeleton({ count = 3 }: { count?: number }) { return (
{Array.from({ length: count }).map((_, i) => (
))}
); } // ----- Dropdown component (portal-based to escape overflow:hidden) ----- export function Dropdown({ trigger, children, open, onOpenChange, align = 'right' }: { trigger: React.ReactNode; children: React.ReactNode; open: boolean; onOpenChange: (open: boolean) => void; align?: 'left' | 'right'; }) { const triggerRef = useRef(null); const menuRef = useRef(null); const [pos, setPos] = useState<{ top: number; left: number } | null>(null); useEffect(() => { if (open && triggerRef.current) { const rect = triggerRef.current.getBoundingClientRect(); const menuWidth = 192; let left = align === 'right' ? rect.right - menuWidth : rect.left; left = Math.max(8, Math.min(left, window.innerWidth - menuWidth - 8)); setPos({ top: rect.bottom + 4, left }); } }, [open, align]); useEffect(() => { if (!open) return; const handler = (e: MouseEvent) => { const target = e.target as Node; if ( triggerRef.current && !triggerRef.current.contains(target) && menuRef.current && !menuRef.current.contains(target) ) { onOpenChange(false); } }; document.addEventListener('mousedown', handler); return () => document.removeEventListener('mousedown', handler); }, [open, onOpenChange]); useEffect(() => { if (!open) return; const handler = () => onOpenChange(false); window.addEventListener('scroll', handler, true); return () => window.removeEventListener('scroll', handler, true); }, [open, onOpenChange]); return ( <>
onOpenChange(!open)}>{trigger}
{open && pos && createPortal(
{children}
, document.body )} ); } export function DropdownItem({ onClick, children, className }: { onClick: () => void; children: React.ReactNode; className?: string }) { return ( ); } // ----- Bottom Sheet (mobile) ----- export function BottomSheet({ open, onClose, title, children }: { open: boolean; onClose: () => void; title: string; children: React.ReactNode; }) { if (!open) return null; return (
e.stopPropagation()} >

{title}

{children}
); } // ----- More Menu (per-row) ----- export function MoreMenu({ children }: { children: React.ReactNode }) { const [open, setOpen] = useState(false); return ( } > {children} ); } // ----- Global CSS for animations ----- export function AdminMobileStyles() { return ( ); }