diff --git a/src/components/EmptyState.tsx b/src/components/EmptyState.tsx new file mode 100644 index 0000000..003139c --- /dev/null +++ b/src/components/EmptyState.tsx @@ -0,0 +1,134 @@ +import type { ComponentType, ReactNode } from 'react'; +import { Link } from 'react-router-dom'; +import { cn } from '@/lib/utils'; + +/** + * Shared empty-state component. + * + * Before this existed the app had ~39 ad-hoc empty states, each free to drift + * in spacing, tone and — crucially — whether they offered a way *out*. The + * `action` slot is the valuable part: "No crews nearby → Create one" turns a + * dead-end into a loop. Prefer this component over hand-rolling new ones. + */ + +export interface EmptyStateAction { + /** Button label. */ + label: string; + /** Internal route to navigate to (renders a ). */ + to?: string; + /** Click handler (renders a + + ); + } + + return ( + + ); +} + +export function EmptyState({ + icon: Icon, + emoji, + title, + hint, + actions, + footer, + className, +}: EmptyStateProps) { + return ( +
+ {(Icon || emoji) && ( +
+ {emoji ? ( + + ) : ( + Icon && + )} +
+ )} + +

{title}

+ + {hint && ( +

{hint}

+ )} + + {actions && actions.length > 0 && ( +
+ {actions.map((action) => ( + + ))} +
+ )} + + {footer && ( + + {footer.label} + + )} +
+ ); +} + +export default EmptyState; diff --git a/src/index.css b/src/index.css index f5d1c3a..54756ad 100644 --- a/src/index.css +++ b/src/index.css @@ -24,9 +24,11 @@ --muted: 0 0% 96%; --muted-foreground: 0 0% 40%; - /* Darker neon lime for light mode */ + /* Darker neon lime for light mode. + Foreground is near-black (~9.5:1 on lime) — white (2.35:1) fails WCAG AA, + and lime is the "go"/CTA color, so its filled state must stay readable. */ --accent: 75 80% 40%; - --accent-foreground: 0 0% 100%; + --accent-foreground: 0 0% 10%; --destructive: 0 84% 60%; --destructive-foreground: 0 0% 100%;