|
| 1 | +# Consistent UI State Design System |
| 2 | + |
| 3 | +This document outlines the UI state design system introduced in YieldVault RWA for handling loading, error, and empty states consistently across all components and pages. |
| 4 | + |
| 5 | +--- |
| 6 | + |
| 7 | +## Core Components Overview |
| 8 | + |
| 9 | +| Component | Responsibility | Default Role | Live Region | |
| 10 | +|---|---|---|---| |
| 11 | +| `StateWrapper` | Declarative state orchestrator (`isLoading` → `isError` → `isEmpty` → `children`) | Varies | Varies | |
| 12 | +| `LoadingState` | Standardized loading spinner and message with skeleton fallback options | `status` | `aria-live="polite"` | |
| 13 | +| `ErrorState` | Accessible error alert with retry triggers, severity levels, and optional detail toggle | `alert` | `aria-live="assertive"` | |
| 14 | +| `EmptyState` | Empty state cards with pre-configured kinds (`no-data`, `no-results`, `permission`, `search`, etc.) | `status` / `alert` | `aria-live="polite"` / `assertive` | |
| 15 | + |
| 16 | +--- |
| 17 | + |
| 18 | +## 1. `StateWrapper` |
| 19 | + |
| 20 | +`StateWrapper` simplifies state conditional logic in container components and page views. |
| 21 | + |
| 22 | +```tsx |
| 23 | +import { StateWrapper } from "@/components/ui"; |
| 24 | + |
| 25 | +function VaultMetricsSection({ data, isLoading, isError, error, refetch }) { |
| 26 | + return ( |
| 27 | + <StateWrapper |
| 28 | + isLoading={isLoading} |
| 29 | + isError={isError} |
| 30 | + isEmpty={!data || data.length === 0} |
| 31 | + error={error} |
| 32 | + onRetry={refetch} |
| 33 | + loadingMessage="Loading vault metrics..." |
| 34 | + emptyProps={{ |
| 35 | + title: "No Metrics Available", |
| 36 | + description: "Deposit funds to view yield telemetry.", |
| 37 | + kind: "no-data" |
| 38 | + }} |
| 39 | + > |
| 40 | + <MetricsGrid data={data} /> |
| 41 | + </StateWrapper> |
| 42 | + ); |
| 43 | +} |
| 44 | +``` |
| 45 | + |
| 46 | +### Props |
| 47 | + |
| 48 | +| Name | Type | Default | Description | |
| 49 | +|---|---|---|---| |
| 50 | +| `isLoading` | `boolean` | `false` | When true, renders `LoadingState` or `loadingFallback`. | |
| 51 | +| `isError` | `boolean` | `false` | When true, renders `ErrorState` or `errorFallback`. | |
| 52 | +| `isEmpty` | `boolean` | `false` | When true, renders `EmptyState` or `emptyFallback`. | |
| 53 | +| `error` | `Error \| string \| null` | `undefined` | Error object or error string for `ErrorState`. | |
| 54 | +| `onRetry` | `() => void` | `undefined` | Retry callback triggered on error state action click. | |
| 55 | +| `loadingMessage` | `string` | `"Loading..."` | Custom loading message text. | |
| 56 | +| `loadingFallback` | `ReactNode` | `undefined` | Complete custom JSX override for loading state (e.g. `DashboardCardSkeleton`). | |
| 57 | +| `errorFallback` | `ReactNode` | `undefined` | Complete custom JSX override for error state. | |
| 58 | +| `emptyFallback` | `ReactNode` | `undefined` | Complete custom JSX override for empty state. | |
| 59 | + |
| 60 | +--- |
| 61 | + |
| 62 | +## 2. `LoadingState` |
| 63 | + |
| 64 | +`LoadingState` provides a standardized spinner and message for section-level or full-page loading indicators. |
| 65 | + |
| 66 | +```tsx |
| 67 | +import { LoadingState } from "@/components/ui"; |
| 68 | + |
| 69 | +// Section loader |
| 70 | +<LoadingState message="Calculating projected yields..." size="md" /> |
| 71 | + |
| 72 | +// Full-page loader |
| 73 | +<LoadingState message="Connecting to Stellar network..." size="full" /> |
| 74 | +``` |
| 75 | + |
| 76 | +--- |
| 77 | + |
| 78 | +## 3. `ErrorState` |
| 79 | + |
| 80 | +`ErrorState` renders accessible error notices with tone styling (`error`, `warning`, `info`), retry triggers, and optional expandable detail blocks. |
| 81 | + |
| 82 | +```tsx |
| 83 | +import { ErrorState } from "@/components/ui"; |
| 84 | + |
| 85 | +<ErrorState |
| 86 | + title="RPC Node Timeout" |
| 87 | + description="Could not reach Horizon RPC endpoint. Please retry." |
| 88 | + tone="error" |
| 89 | + onRetry={() => refetch()} |
| 90 | + showDetailsToggle={true} |
| 91 | + error={error} |
| 92 | +/> |
| 93 | +``` |
| 94 | + |
| 95 | +--- |
| 96 | + |
| 97 | +## 4. `EmptyState` |
| 98 | + |
| 99 | +`EmptyState` displays standardized empty state messages when lists, tables, or search filters yield no data. |
| 100 | + |
| 101 | +```tsx |
| 102 | +import { EmptyState } from "@/components/ui"; |
| 103 | + |
| 104 | +<EmptyState |
| 105 | + kind="no-results" |
| 106 | + title="No Transactions Found" |
| 107 | + description="No deposit or withdrawal records match the selected filter." |
| 108 | + action={{ |
| 109 | + label: "Reset Filters", |
| 110 | + onClick: handleResetFilters |
| 111 | + }} |
| 112 | +/> |
| 113 | +``` |
| 114 | + |
| 115 | +--- |
| 116 | + |
| 117 | +## Accessibility Guidelines |
| 118 | + |
| 119 | +1. **Screen Readers**: |
| 120 | + - Loading states use `role="status"` and `aria-live="polite"` with `aria-busy="true"`. |
| 121 | + - Error states use `role="alert"` and `aria-live="assertive"`. |
| 122 | + - Decorative icons inside state containers set `aria-hidden="true"`. |
| 123 | +2. **Focus Management**: |
| 124 | + - Interactive retry buttons have clear, descriptive labels and contrast meeting WCAG AA standards. |
0 commit comments