"use client"; import { type ButtonHTMLAttributes, type InputHTMLAttributes, type ReactNode, type SelectHTMLAttributes, type TextareaHTMLAttributes, useEffect, } from "react"; import { cn, toFa } from "@/lib/utils"; import { CloseIcon, SpinnerIcon } from "./icons"; /* ------------------------------- Button -------------------------------- */ type ButtonVariant = "primary" | "secondary" | "danger" | "ghost"; interface ButtonProps extends ButtonHTMLAttributes { variant?: ButtonVariant; loading?: boolean; icon?: ReactNode; } const buttonVariants: Record = { primary: "bg-primary text-white hover:bg-primary-hover", secondary: "bg-surface-muted text-foreground hover:bg-surface-hover border border-border", danger: "bg-danger text-white hover:opacity-90", ghost: "text-muted hover:bg-surface-muted", }; export function Button({ variant = "primary", loading, icon, className, children, disabled, ...props }: ButtonProps) { return ( {loading ? : icon} {children} ); } /* ------------------------------- Fields -------------------------------- */ export function Field({ label, hint, required, children, }: { label: string; hint?: string; required?: boolean; children: ReactNode; }) { return ( {label} {required && *} {children} {hint && {hint}} ); } const fieldClass = "w-full rounded-xl border border-border bg-surface px-3 py-2 text-sm outline-none transition focus:border-primary focus:ring-4 focus:ring-[var(--ring)] disabled:opacity-60"; export function Input(props: InputHTMLAttributes) { return ; } export function Textarea(props: TextareaHTMLAttributes) { return ( ); } export function Select(props: SelectHTMLAttributes) { return ; } export function Switch({ checked, onChange, label, }: { checked: boolean; onChange: (v: boolean) => void; label?: string; }) { return ( onChange(!checked)} className="inline-flex items-center gap-2 text-sm" > {label} ); } /* ----------------------------- Containers ------------------------------ */ export function Card({ className, children, }: { className?: string; children: ReactNode; }) { return ( {children} ); } export function PageHeader({ title, subtitle, action, }: { title: string; subtitle?: string; action?: ReactNode; }) { return ( {title} {subtitle && {subtitle}} {action} ); } export function Badge({ children, tone = "neutral", }: { children: ReactNode; tone?: "neutral" | "success" | "danger" | "primary"; }) { return ( {children} ); } export function Pagination({ page, lastPage, total, onChange, }: { page: number; lastPage: number; total?: number; onChange: (p: number) => void; }) { if (lastPage <= 1) return null; return ( {total != null && `${toFa(total)} مورد · `} صفحه {toFa(page)} از {toFa(lastPage)} onChange(page - 1)} disabled={page <= 1} > قبلی onChange(page + 1)} disabled={page >= lastPage} > بعدی ); } export function EmptyState({ title, message, icon, action, }: { title?: string; message: string; icon?: ReactNode; action?: ReactNode; }) { return ( {icon ?? "🌙"} {title && {title}} {message} {action && {action}} ); } export function Spinner({ className }: { className?: string }) { return ; } /* ------------------------------- Modal --------------------------------- */ export function Modal({ open, onClose, title, children, footer, }: { open: boolean; onClose: () => void; title: string; children: ReactNode; footer?: ReactNode; }) { useEffect(() => { if (!open) return; const onKey = (e: KeyboardEvent) => e.key === "Escape" && onClose(); document.addEventListener("keydown", onKey); return () => document.removeEventListener("keydown", onKey); }, [open, onClose]); if (!open) return null; return ( {title} {children} {footer && ( {footer} )} ); } export function ConfirmDialog({ open, title = "حذف مورد", message, confirmText = "حذف", loading, onConfirm, onClose, }: { open: boolean; title?: string; message: string; confirmText?: string; loading?: boolean; onConfirm: () => void; onClose: () => void; }) { return ( انصراف {confirmText} > } > {message} ); }
{subtitle}
{title}
{message}