import type { LucideIcon } from 'lucide-react'; import type { ReactNode } from 'react'; import { Loader2, TriangleAlert } from 'lucide-react'; import { cn } from 'helpers/cn'; import { useTranslation } from '@/components/locales'; // Loading, failed, empty. See docs/design-language-interface.md § 6. // // These are the states a new user meets FIRST — before a single record exists, before a token is // pasted, when something is misconfigured. Treating them as filler is how an app ends up saying "No // results" to someone who cannot tell whether that means "nothing here" or "it is broken". // // One implementation, so all three agree on size, spacing and centring across every app. They fill // their container rather than sitting at the top, because a half-empty pane with a line of grey text // stuck to the ceiling reads as a rendering bug. const Shell = ({ children, className }: { children: ReactNode; className?: string }) => (
{children}
); export const LoadingBlock = ({ label }: { label?: string }) => { const { t } = useTranslation(); return (

{label ?? t('state.loading')}

); }; /** * The real message is shown, not a friendly substitute for it. On a self-hosted platform the person * reading an error is almost always the person who can fix it, and "Something went wrong" costs them * the one clue they needed. */ export const ErrorBlock = ({ title, message, action }: { title: string; message?: ReactNode; action?: ReactNode }) => (

{title}

{message &&

{message}

}
{action}
); /** * `hint` is not decoration — it is where the state says what WOULD be here and why it is not, which * is the difference between an empty state and a shrug. */ export const EmptyBlock = ({ icon: Icon, title, hint, action, }: { icon?: LucideIcon; title: string; hint?: ReactNode; action?: ReactNode; }) => ( {Icon && (
)}

{title}

{hint &&

{hint}

}
{action}
);