"use client"; /** * Base design system for Smart Organization. * * Rules encoded here (from PLAN.md §12): * - dark-first, flat surfaces, generous whitespace * - no gradients, no drop shadows * - every async surface has empty / loading / error states * - the customer never sees a provider or model name */ import * as React from "react"; export function cx(...parts: Array): string { return parts.filter(Boolean).join(" "); } /* -------------------------------------------------------------------------- */ /* Button */ /* -------------------------------------------------------------------------- */ type ButtonVariant = "primary" | "secondary" | "ghost" | "danger"; type ButtonSize = "sm" | "md"; const BUTTON_VARIANTS: Record = { primary: "bg-accent text-white hover:bg-accent-strong border-transparent", secondary: "bg-surface-overlay text-slate-100 hover:bg-surface-border border-surface-border", ghost: "bg-transparent text-slate-300 hover:bg-surface-overlay border-transparent", danger: "bg-danger text-white hover:opacity-90 border-transparent", }; const BUTTON_SIZES: Record = { sm: "h-8 px-3 text-xs", md: "h-10 px-4 text-sm", }; export interface ButtonProps extends React.ButtonHTMLAttributes { variant?: ButtonVariant; size?: ButtonSize; loading?: boolean; } export function Button({ variant = "primary", size = "md", loading = false, className, children, disabled, ...rest }: ButtonProps) { return ( ); } /* -------------------------------------------------------------------------- */ /* Card / Badge / Stat */ /* -------------------------------------------------------------------------- */ export function Card({ className, children, ...rest }: React.HTMLAttributes) { return (
{children}
); } export function CardTitle({ children }: { children: React.ReactNode }) { return

{children}

; } export function CardHint({ children }: { children: React.ReactNode }) { return

{children}

; } type Tone = "neutral" | "info" | "success" | "warning" | "danger"; const TONES: Record = { neutral: "bg-surface-overlay text-slate-300 border-surface-border", info: "bg-accent-soft text-accent border-accent/40", success: "bg-positive/10 text-positive border-positive/40", warning: "bg-warning/10 text-warning border-warning/40", danger: "bg-danger/10 text-danger border-danger/40", }; export function Badge({ tone = "neutral", children, className, }: { tone?: Tone; children: React.ReactNode; className?: string; }) { return ( {children} ); } export function Stat({ label, value, hint, tone = "neutral", }: { label: string; value: React.ReactNode; hint?: string; tone?: Tone; }) { return (

{label}

{value}

{hint &&

{hint}

}
); } /* -------------------------------------------------------------------------- */ /* Form fields */ /* -------------------------------------------------------------------------- */ export interface FieldProps extends React.InputHTMLAttributes { label: string; hint?: string; error?: string; } export function Field({ label, hint, error, className, id, ...rest }: FieldProps) { const inputId = id ?? `field-${label.replace(/\s+/g, "-")}`; return (
{error ? (

{error}

) : hint ? (

{hint}

) : null}
); } export interface SelectProps extends React.SelectHTMLAttributes { label: string; hint?: string; error?: string; options: { value: string; label: string }[]; } export function Select({ label, hint, error, options, className, id, ...rest }: SelectProps) { const selectId = id ?? `select-${label.replace(/\s+/g, "-")}`; return (
{label ? ( ) : null} {error ? (

{error}

) : hint ? (

{hint}

) : null}
); } export function Checkbox({ label, className, id, ...rest }: React.InputHTMLAttributes & { label: string }) { const boxId = id ?? `check-${label.replace(/\s+/g, "-")}`; return ( ); } /* -------------------------------------------------------------------------- */ /* Async states */ /* -------------------------------------------------------------------------- */ export function LoadingState({ label = "در حال بارگذاری…" }: { label?: string }) { return (

{label}

); } export function EmptyState({ title, description, action, }: { title: string; description?: string; action?: React.ReactNode; }) { return (

{title}

{description &&

{description}

} {action}
); } export function ErrorState({ title = "خطا در دریافت داده", message, onRetry, }: { title?: string; message?: string; onRetry?: () => void; }) { return (

{title}

{message &&

{message}

} {onRetry && ( )}
); } /* -------------------------------------------------------------------------- */ /* Domain-specific surfaces */ /* -------------------------------------------------------------------------- */ export function UsageBar({ used, total, label, }: { used: number; total: number; label?: string; }) { const pct = total > 0 ? Math.min(100, Math.round((used / total) * 100)) : 0; return (
{label && (
{label} ٪{pct}
)}
90 ? "bg-danger" : pct > 70 ? "bg-warning" : "bg-accent", )} style={{ width: `${pct}%` }} />
); } export interface TimelineItem { id: string; title: string; meta?: string; tone?: Tone; } export function Timeline({ items }: { items: TimelineItem[] }) { if (items.length === 0) { return

رویدادی ثبت نشده است.

; } return (
    {items.map((item) => (
  1. {item.title} {item.meta && {item.meta}}
  2. ))}
); } export function EmployeeCard({ name, role, status, activeTasks, todayUsage, onOpen, }: { name: string; role: string; status: string; activeTasks?: number; todayUsage?: string; onOpen?: () => void; }) { const tone: Tone = status === "active" ? "success" : status === "paused" ? "warning" : "neutral"; return (

{name}

{role}

{status === "active" ? "فعال" : status === "paused" ? "متوقف" : "بایگانی"}
وظایف فعال: {activeTasks ?? 0} مصرف امروز: {todayUsage ?? "—"}
{onOpen && ( )}
); } export function TaskCard({ title, status, employeeName, onOpen, }: { title: string; status: string; employeeName?: string; onOpen?: () => void; }) { const tone: Tone = status === "done" ? "success" : status === "failed" ? "danger" : status === "running" ? "info" : status === "waiting_approval" ? "warning" : "neutral"; return (

{title}

{status}
{employeeName &&

{employeeName}

} {onOpen && ( )}
); } export function ConfirmDialog({ open, title, description, confirmLabel = "تأیید", onConfirm, onCancel, }: { open: boolean; title: string; description?: string; confirmLabel?: string; onConfirm: () => void; onCancel: () => void; }) { if (!open) return null; return (

{title}

{description &&

{description}

}
); } /* -------------------------------------------------------------------------- */ /* Modal / Drawer */ /* -------------------------------------------------------------------------- */ const MODAL_SIZES: Record<"sm" | "md" | "lg", string> = { sm: "max-w-sm", md: "max-w-lg", lg: "max-w-3xl", }; export function Modal({ open, title, description, onClose, footer, size = "md", children, }: { open: boolean; title: string; description?: string; onClose: () => void; footer?: React.ReactNode; size?: "sm" | "md" | "lg"; children?: React.ReactNode; }) { if (!open) return null; return (
event.stopPropagation()} className={cx( "anim-scale-in w-full rounded-xl border border-surface-border bg-surface-raised p-5", MODAL_SIZES[size], )} >

{title}

{description &&

{description}

}
{children &&
{children}
} {footer &&
{footer}
}
); } export function Drawer({ open, title, onClose, side = "start", children, }: { open: boolean; title: string; onClose: () => void; /** ``start`` is the right-hand edge in an RTL layout. */ side?: "start" | "end"; children: React.ReactNode; }) { if (!open) return null; return (
); } /* -------------------------------------------------------------------------- */ /* Table */ /* -------------------------------------------------------------------------- */ export interface TableColumn { key: string; header: string; render: (row: T) => React.ReactNode; align?: "start" | "center" | "end"; className?: string; } const ALIGN: Record<"start" | "center" | "end", string> = { start: "text-start", center: "text-center", end: "text-end", }; /** * A table that owns its own empty / loading / error presentation. * * Passing those states in as props rather than leaving every caller to * remember them is what keeps the three states consistent across the app. */ export function Table({ columns, rows, rowKey, loading = false, error, onRetry, empty, caption, }: { columns: Array>; rows: T[]; rowKey: (row: T) => string; loading?: boolean; error?: string | null; onRetry?: () => void; empty?: React.ReactNode; caption?: string; }) { if (loading) return ; if (error) return ; if (rows.length === 0) { return ( <> {empty ?? } ); } return (
{caption && } {columns.map((column) => ( ))} {rows.map((row) => ( {columns.map((column) => ( ))} ))}
{caption}
{column.header}
{column.render(row)}
); } /* -------------------------------------------------------------------------- */ /* Tabs */ /* -------------------------------------------------------------------------- */ export interface TabItem { key: string; label: string; badge?: React.ReactNode; } export function Tabs({ items, active, onChange, }: { items: TabItem[]; active: string; onChange: (key: string) => void; }) { return (
{items.map((item) => { const selected = item.key === active; return ( ); })}
); } /* -------------------------------------------------------------------------- */ /* Toast */ /* -------------------------------------------------------------------------- */ export interface ToastMessage { id: string; title: string; description?: string; tone?: Tone; } export function ToastStack({ toasts, onDismiss, }: { toasts: ToastMessage[]; onDismiss: (id: string) => void; }) { if (toasts.length === 0) return null; return (
{toasts.map((toast) => (
{toast.title} {toast.description && ( {toast.description} )}
))}
); } /* -------------------------------------------------------------------------- */ /* ArtifactViewer */ /* -------------------------------------------------------------------------- */ export function ArtifactViewer({ name, kind, content, sizeBytes, createdAt, }: { name: string; kind: string; content: string; sizeBytes?: number; createdAt?: string; }) { return (
{name} {kind}
{sizeBytes !== undefined ? `${sizeBytes} بایت` : ""} {createdAt ? ` · ${createdAt}` : ""}
        {content || "محتوایی ثبت نشده است."}
      
); }