empty-state.tsx62 lines · main
| 1 | 'use client'; |
| 2 | |
| 3 | import { motion } from 'motion/react'; |
| 4 | |
| 5 | /** |
| 6 | * Friendly, spacious empty/error placeholder — replaces the old cramped |
| 7 | * one-liner. Give it an icon, a plain-words message, and (for error |
| 8 | * states) an action like a retry button. |
| 9 | */ |
| 10 | export function EmptyState({ |
| 11 | icon, |
| 12 | title, |
| 13 | message, |
| 14 | action, |
| 15 | }: { |
| 16 | icon?: React.ReactNode; |
| 17 | title: string; |
| 18 | message?: string; |
| 19 | action?: React.ReactNode; |
| 20 | }) { |
| 21 | return ( |
| 22 | <motion.div |
| 23 | initial={{ opacity: 0, y: 8 }} |
| 24 | animate={{ opacity: 1, y: 0 }} |
| 25 | transition={{ duration: 0.4, ease: 'easeOut' }} |
| 26 | className="flex flex-col items-center justify-center gap-4 rounded-xl border border-dashed border-[var(--color-border-subtle)] bg-[var(--color-surface)] px-8 py-16 text-center" |
| 27 | > |
| 28 | {icon ? <span className="text-[var(--color-text-muted)]">{icon}</span> : null} |
| 29 | <div className="flex flex-col gap-1.5"> |
| 30 | <p className="font-mono text-sm text-[var(--color-text)]">{title}</p> |
| 31 | {message ? ( |
| 32 | <p className="mx-auto max-w-md font-mono text-xs leading-relaxed text-[var(--color-text-subtle)]"> |
| 33 | {message} |
| 34 | </p> |
| 35 | ) : null} |
| 36 | </div> |
| 37 | {action ? <div className="mt-1">{action}</div> : null} |
| 38 | </motion.div> |
| 39 | ); |
| 40 | } |
| 41 | |
| 42 | /** House-styled action button for EmptyState (e.g. "retry"). */ |
| 43 | export function EmptyStateButton({ |
| 44 | onClick, |
| 45 | children, |
| 46 | disabled, |
| 47 | }: { |
| 48 | onClick: () => void; |
| 49 | children: React.ReactNode; |
| 50 | disabled?: boolean; |
| 51 | }) { |
| 52 | return ( |
| 53 | <button |
| 54 | type="button" |
| 55 | onClick={onClick} |
| 56 | disabled={disabled} |
| 57 | className="rounded-[var(--radius-md)] border border-[var(--color-border)] bg-[var(--color-surface-raised)] px-4 py-2 font-mono text-xs text-[var(--color-text)] transition hover:border-[var(--color-border-strong)] hover:text-[var(--color-primary)] disabled:opacity-50" |
| 58 | > |
| 59 | {children} |
| 60 | </button> |
| 61 | ); |
| 62 | } |