SPB Git

Repository Public

misc.tsx 3.6 KB 63 lines tsx

1import * as React from 'react';2import { AlertTriangle } from 'lucide-react';3import { cn } from '@/lib/cn';4import { errorMessage } from '@/lib/api';56export function Kbd({ children, className }: { children: React.ReactNode; className?: string }) {7  return <kbd className={cn('inline-flex h-[18px] min-w-[18px] items-center justify-center rounded-[4px] border border-line-2 bg-surface-2 px-1 font-sans text-[10.5px] font-medium text-text-2', className)}>{children}</kbd>;8}9export function Skeleton({ className }: { className?: string }) {10  return <div aria-hidden className={cn('shimmer rounded-sm bg-surface-2', className)} />;11}12export function Spinner({ className }: { className?: string }) {13  return <span role="status" aria-label="Loading" className={cn('inline-block size-4 animate-spin rounded-full border-[1.5px] border-text-2 border-t-transparent', className)} />;14}15export function EmptyState({ icon, illustration, title, description, action, className }: { icon?: React.ReactNode; illustration?: React.ReactNode; title: string; description?: React.ReactNode; action?: React.ReactNode; className?: string }) {16  return (17    <div className={cn('flex flex-col items-center justify-center gap-2 px-6 py-12 text-center', className)}>18      {illustration ? <div className="mb-1">{illustration}</div> : icon && <div className="mb-1 flex size-11 items-center justify-center rounded-full bg-surface-2 text-muted [&>svg]:size-5">{icon}</div>}19      <p className="text-[15px] font-semibold tracking-tight text-text">{title}</p>20      {description && <p className="max-w-sm text-[13px] leading-relaxed text-text-2">{description}</p>}21      {action && <div className="mt-3">{action}</div>}22    </div>23  );24}25export function ErrorState({ error, onRetry, className }: { error: unknown; onRetry?: () => void; className?: string }) {26  return (27    <div role="alert" className={cn('flex flex-col items-center gap-2 px-6 py-10 text-center', className)}>28      <AlertTriangle className="size-5 text-error" />29      <p className="text-sm font-medium">Could not load</p>30      <p className="max-w-sm text-xs text-text-2">{errorMessage(error)}</p>31      {onRetry && (32        <button onClick={onRetry} className="mt-2 h-8 rounded-sm border border-line-2 px-3 text-xs text-text-2 hover:border-line-3 hover:text-text pointer-coarse:h-11">33          Retry34        </button>35      )}36    </div>37  );38}39export function PageHeader({ title, description, actions, meta }: { title: string; description?: React.ReactNode; actions?: React.ReactNode; meta?: React.ReactNode }) {40  return (41    <div className="flex flex-wrap items-start gap-x-4 gap-y-2 border-b border-line px-4 py-3 md:px-6 md:py-4">42      <div className="min-w-0 flex-1">43        {/* phones already show the title in the top bar */}44        <h1 className="hidden text-[15px] font-semibold tracking-tight text-text md:block">{title}</h1>45        {description && <p className="text-xs text-text-2 md:mt-0.5">{description}</p>}46        {meta && <div className="mt-1 text-[11px] text-muted">{meta}</div>}47      </div>48      {actions && <div className="flex flex-wrap items-center gap-2">{actions}</div>}49    </div>50  );51}52export function Section({ title, aside, children, className }: { title: string; aside?: React.ReactNode; children: React.ReactNode; className?: string }) {53  return (54    <section className={cn('min-w-0 overflow-hidden card', className)}>55      <header className="flex min-h-10 items-center gap-3 border-b border-line px-4 py-2">56        <h2 className="flex-1 text-xs font-semibold uppercase tracking-wide text-text-2">{title}</h2>57        {aside && <div className="text-[11px] text-muted">{aside}</div>}58      </header>59      {children}60    </section>61  );62}63
Files main
Menu

Theme