Documentation Index
Fetch the complete documentation index at: https://grantmaster.dev/llms.txt
Use this file to discover all available pages before exploring further.
Component API Reference
Reusable UI components from src/components/ui/. These are the building blocks of GrantMaster’s interface — use them instead of writing inline markup.
Convention: All components support dark mode via Tailwind dark: variants. Colour tokens follow the project palette: slate-* (neutral), primary-* (brand), emerald-* (success), rose-* (error), amber-* (warning).
Layout
PageLayout
Source: src/components/ui/PageLayout.tsx
Top-level content wrapper that constrains width and applies consistent padding.
<PageLayout maxWidth="xl" padding="default">
<PageHeader title="Projects" />
{/* page content */}
</PageLayout>
Props
| Prop | Type | Default | Description |
|---|
children | ReactNode | — | Page content |
maxWidth | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'full' | 'full' | Maximum content width (sm = max-w-3xl, md = max-w-5xl, lg = max-w-6xl, xl = max-w-7xl, 2xl = max-w-screen-2xl) |
padding | 'default' | 'compact' | 'none' | 'default' | Inner padding (compact = p-4, none / default = p-0) |
className | string? | — | Additional CSS classes |
Source: src/components/ui/PageHeader.tsx
Standard page title bar with optional description, icon, and trailing action buttons. Always use this instead of manual <h1> elements.
<PageHeader
title="Expense Reports"
description="Track and manage project expenditures"
icon={Receipt}
actions={<Button onClick={handleAdd}>New Expense</Button>}
/>
Props
| Prop | Type | Default | Description |
|---|
title | string | ReactNode | — | Page heading (rendered as <h1>) |
description | string? | — | Subtitle text below the heading |
actions | ReactNode? | — | Buttons or controls rendered at the trailing edge |
icon | ComponentType? | — | Lucide icon rendered before the title |
className | string? | — | Additional CSS classes on the outer container |
PageTabs
Source: src/components/ui/PageTabs.tsx
Horizontal tab bar using {@link animated-tabs}. Content is rendered externally — do not use the content prop on tab objects.
const [activeTab, setActiveTab] = useState('overview');
<PageTabs
ariaLabel="Overview sections"
tabs={[
{ title: 'Overview', value: 'overview', icon: LayoutDashboard },
{ title: 'Settings', value: 'settings', icon: Settings },
]}
activeTab={activeTab}
onTabChange={setActiveTab}
idBase="overview-tabs"
/>
<div
id={`overview-tabs-panel-${activeTab}`}
role="tabpanel"
aria-labelledby={`overview-tabs-tab-${activeTab}`}
tabIndex={0}
>
{activeTab === 'overview' && <OverviewContent />}
{activeTab === 'settings' && <SettingsContent />}
</div>
For route-driven pages, derive activeTab from the router and have onTabChange push the canonical tab URL. Do not preserve tab selection in shared state across routes.
Props
| Prop | Type | Default | Description |
|---|
tabs | Tab[] | — | Tab definitions (see Tab type below) |
activeTab | string | — | Currently selected tab value |
onTabChange | (value: string) => void | — | Callback when a tab is selected |
idBase | string? | auto-generated | Stable prefix for tab / tabpanel IDs when the panel is rendered externally |
ariaLabel | string? | 'Page sections' | Accessible label announced for the tablist |
className | string? | — | Additional CSS classes |
Tab Type
| Field | Type | Description |
|---|
title | ReactNode | Tab label |
value | string | Unique identifier |
icon | ComponentType<{ className?: string }>? | Lucide icon component |
Source: src/components/ui/SecondarySidebar.tsx
Left-side navigation panel for tab pages and sub-sections. Supports flat items or grouped items with collapsible sub-menus. Renders a mobile-friendly <Select> dropdown on small screens.
<div className="flex gap-6">
<SecondarySidebar
items={[
{ id: 'general', label: 'General', icon: Settings },
{ id: 'billing', label: 'Billing', icon: CreditCard },
]}
activeId={activeSection}
onSelect={setActiveSection}
searchable
/>
<div className="flex-1">{/* section content */}</div>
</div>
Props
| Prop | Type | Default | Description |
|---|
items | SidebarItem[]? | — | Flat list of navigation items (mutually exclusive with groups) |
groups | SidebarGroup[]? | — | Grouped items with collapsible sub-menus |
activeId | string | — | Currently selected item id |
onSelect | (id: string) => void | — | Callback when an item is selected |
searchable | boolean? | false | Show a search input that filters items by label and keywords |
searchPlaceholder | string? | 'Search...' | Placeholder for the search input |
hasPermission | (permission: string) => boolean? | — | Permission gate function. Items with a permission field are hidden when this returns false. |
className | string? | — | Additional CSS classes |
| Field | Type | Description |
|---|
id | string | Unique identifier |
label | string | Display text |
icon | ComponentType? | Lucide icon |
description | string? | Subtitle shown below the label |
keywords | string[]? | Additional terms for search filtering |
permission | string? | Required permission key (checked via hasPermission) |
badge | string | number? | Badge rendered at the trailing edge |
| Field | Type | Description |
|---|
id | string | Unique identifier |
label | string | Group heading |
icon | ComponentType? | Icon for the group header |
items | SidebarItem[] | Items within this group |
defaultExpanded | boolean? | Whether the group starts expanded |
Behavior
- Active state:
primary-600 background with white text on the selected item.
- Mobile: Renders a native
<Select> dropdown instead of the sidebar panel.
- Permission gating: Items with a
permission field are filtered out when hasPermission(permission) returns false.
- Default width:
w-64.
Data Display
StatusBadge
Source: src/components/ui/StatusBadge.tsx
Pill-shaped status indicator with icon, label, and themed colours. Use mapDomainStatus() to convert domain-specific strings to one of the 6 base status types.
import { StatusBadge, mapDomainStatus } from '@/components/ui/StatusBadge';
// Direct usage
<StatusBadge status="success" label="Approved" />
// Domain mapping
<StatusBadge status={mapDomainStatus('draft')} label="Draft" dot />
Props
| Prop | Type | Default | Description |
|---|
status | StatusType | — | Visual status category (see table below) |
label | string | — | Badge text |
icon | LucideIcon? | — | Override the default icon for the status |
dot | boolean? | false | Render a small coloured dot instead of an icon |
className | string? | — | Additional CSS classes |
StatusType Values
| Value | Colour | Default Icon |
|---|
success | emerald | CheckCircle2 |
warning | amber | AlertTriangle |
error | rose | AlertCircle |
info | primary | Info |
pending | slate | Clock |
neutral | slate | Info |
mapDomainStatus(domainStatus: string): StatusType
Case-insensitive mapping of 70+ domain strings to base StatusType. Falls back to 'neutral' for unknown values.
Examples: 'approved' → success, 'pending' → warning, 'rejected' → error, 'draft' → pending, 'cancelled' → neutral.
ConfidenceBadge
Source: src/components/ui/ConfidenceBadge.tsx
AI confidence score indicator with colour-coded thresholds.
<ConfidenceBadge confidence={92} />
<ConfidenceBadge confidence={45} size="sm" />
Props
| Prop | Type | Default | Description |
|---|
confidence | number | — | AI confidence score (0–100) |
size | 'sm' | 'md' | 'lg' | 'md' | Badge size preset |
showLabel | boolean? | true | Show label text (e.g. “High”) beside the percentage. Hidden at 'sm' size. |
Thresholds
| Range | Label | Colour |
|---|
| 85–100 | High | emerald |
| 70–84 | Good | primary |
| 50–69 | Medium | amber |
| 0–49 | Low | rose |
CitationList
Source: src/components/ui/CitationList.tsx
Renders a list of document citations with optional excerpts and navigation links. Returns null when citations are empty.
<CitationList
citations={ragResult.citations}
title="Sources"
compact
onDocumentClick={(id, page) => openDocViewer(id, page)}
/>
Props
| Prop | Type | Default | Description |
|---|
citations | DocumentCitation[] | — | Array of citation objects to render |
title | string? | 'Sources' | Heading shown above the list |
compact | boolean? | false | Smaller text and icon sizes |
onDocumentClick | (documentId: string, page?: number, section?: string) => void? | — | Custom click handler. When omitted, navigates to /documents/:id. |
Feedback & Overlays
Modal
Source: src/components/ui/Modal.tsx
Animated dialog overlay built on Radix DialogPrimitive with motion/react transitions.
<Modal isOpen={showModal} onClose={() => setShowModal(false)} title="Confirm Action" size="md">
<p>Are you sure?</p>
<Modal.Footer>
<Button variant="outline" onClick={() => setShowModal(false)}>Cancel</Button>
<Button onClick={handleConfirm}>Confirm</Button>
</Modal.Footer>
</Modal>
Props
| Prop | Type | Default | Description |
|---|
isOpen | boolean | — | Controls visibility |
onClose | () => void | — | Called when the modal is dismissed (overlay click, Escape, or close button) |
title | ReactNode | — | Modal heading |
children | ReactNode | — | Modal body content |
footer | ReactNode? | — | Content rendered in the footer area |
size | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'full' | 'lg' | Width preset (sm = max-w-md, md = max-w-xl, lg = max-w-3xl, xl = max-w-5xl, 2xl = max-w-7xl, full = max-w-[95vw]) |
BottomSheet
Source: src/components/ui/BottomSheet.tsx
Mobile-friendly bottom sheet with snap points and drag-to-dismiss.
<BottomSheet isOpen={open} onClose={() => setOpen(false)} snapPoints={[40, 90]}>
<div className="p-4">Sheet content</div>
</BottomSheet>
Props
| Prop | Type | Default | Description |
|---|
isOpen | boolean | — | Controls visibility |
onClose | () => void | — | Called on dismiss |
children | ReactNode | — | Sheet content |
snapPoints | number[]? | [40, 90] | Snap heights as viewport-height percentages |
initialSnap | number? | 0 | Index into snapPoints for the initial position |
onSnapChange | (index: number) => void? | — | Called when snap position changes |
showHandle | boolean? | true | Show the drag handle bar |
className | string? | — | Additional CSS classes |
Source: src/components/ui/Tooltip.tsx
Lightweight tooltip wrapper. Uses the custom GrantMaster implementation — not the multi-part shadcn Tooltip pattern.
<Tooltip content="Save changes" side="top" shortcut="Ctrl+S">
<Button>Save</Button>
</Tooltip>
Props
| Prop | Type | Default | Description |
|---|
children | ReactNode | — | Trigger element |
content | ReactNode | — | Tooltip content |
side | 'top' | 'bottom' | 'left' | 'right'? | 'top' | Tooltip placement |
align | 'start' | 'center' | 'end'? | 'center' | Alignment relative to the trigger |
delayDuration | number? | 200 | Delay in ms before showing |
disabled | boolean? | false | Suppress the tooltip |
shortcut | string? | — | Keyboard shortcut hint (e.g. 'Ctrl+S') |
className | string? | — | Additional CSS classes on the content |
Variants
| Component | Purpose | Key Props |
|---|
ActionTooltip | Shorthand for icon buttons | label, shortcut?, side? |
HelpTooltip | Informational popover with title/description | title, description, learnMoreUrl? |
InfoIconTooltip | Self-contained (i) icon with HelpTooltip | Same as HelpTooltip |
WarningBanner
Source: src/components/ui/WarningBanner.tsx
Full-width alert banner with type-based colouring.
<WarningBanner
type="warning"
title="Budget Alert"
message="Project spending has reached 80% of the allocated budget."
details={['Marketing: $4,200 over', 'Travel: approaching limit']}
dismissible
onDismiss={() => setDismissed(true)}
/>
Props
| Prop | Type | Default | Description |
|---|
type | 'warning' | 'error' | 'info' | 'critical' | — | Banner severity |
title | string? | — | Bold heading text |
message | string | — | Body message |
details | string[]? | — | Bullet-point detail lines |
onDismiss | () => void? | — | Callback when dismissed |
dismissible | boolean? | false | Show a dismiss button |
Type Colours
| Type | Background | Icon |
|---|
warning | amber | AlertTriangle |
error | rose | AlertCircle |
info | primary | Info |
critical | rose (darker) | ShieldAlert |
Source: src/components/ui/FloatingActionButton.tsx
Fixed-position action button, typically for mobile “add” actions.
<FloatingActionButton onClick={handleAdd} icon={Plus} label="New Expense" hideOnScroll />
Props
| Prop | Type | Default | Description |
|---|
onClick | () => void | — | Click handler |
icon | ComponentType? | — | Lucide icon |
label | string? | 'Add' | Button text |
position | string? | 'bottom-right' | Screen position |
hideOnScroll | boolean? | false | Auto-hide when the user scrolls down |
Data State
DataStateWrapper
Source: src/components/ui/DataStateWrapper.tsx
Declarative loading / error / empty state container. Wraps content and renders the appropriate fallback UI based on state flags.
<DataStateWrapper
loading={isLoading}
error={error}
isEmpty={items.length === 0}
onRetry={refetch}
emptyState={{ icon: Inbox, title: 'No items', description: 'Get started by adding one.' }}
skeleton={<SkeletonTable rows={5} cols={4} />}
>
<ItemList items={items} />
</DataStateWrapper>
Props
| Prop | Type | Default | Description |
|---|
loading | boolean | — | Show loading state |
error | Error | string | null | — | Show error state with message |
isEmpty | boolean | — | Show empty state when no data |
onRetry | () => void? | — | Retry button handler for error state |
skeleton | ReactNode? | — | Custom loading skeleton |
emptyState | EmptyStateConfig? | — | Configuration for the empty state (see below) |
renderError | (error, onRetry) => ReactNode? | — | Custom error renderer |
renderLoading | () => ReactNode? | — | Custom loading renderer |
renderEmpty | () => ReactNode? | — | Custom empty state renderer |
children | ReactNode | — | Content rendered when not loading, no error, and not empty |
className | string? | — | Outer container class |
minLoadingMs | number? | 0 | Minimum time (ms) to show loading state to prevent flicker |
EmptyStateConfig
| Field | Type | Description |
|---|
icon | ComponentType | Lucide icon |
title | string | Heading text |
description | string | Body text |
actionLabel | string? | CTA button label |
onAction | () => void? | CTA button handler |
EmptyState
Source: src/components/ui/EmptyState.tsx
Standalone empty state display with illustrations, actions, and suggestion chips.
<EmptyState
icon={FolderOpen}
title="No projects yet"
description="Create your first project to get started."
illustrationType="getting-started"
actions={[
{ label: 'Create Project', onClick: handleCreate, variant: 'default' },
{ label: 'Import', onClick: handleImport, variant: 'outline' },
]}
suggestions={[
{ label: 'View templates', onClick: openTemplates },
]}
/>
Props
| Prop | Type | Default | Description |
|---|
icon | ComponentType | — | Primary icon |
title | string | — | Heading text |
description | string | — | Body text |
actionLabel | string? | — | Single CTA button label (shorthand for one action) |
onAction | () => void? | — | Single CTA button handler |
actions | EmptyStateAction[]? | — | Multiple action buttons |
suggestions | EmptyStateSuggestion[]? | — | Suggestion chip links |
compact | boolean? | false | Smaller layout for inline contexts |
illustration | ReactNode? | — | Custom illustration element |
illustrationType | IllustrationType? | — | Built-in SVG illustration key |
className | string? | — | Additional CSS classes |
IllustrationType
'no-data' | 'no-results' | 'empty-folder' | 'getting-started' | 'error' | 'no-access'
ErrorBoundary
Source: src/components/ui/ErrorBoundary.tsx
React class component that catches render errors, reports to Sentry, and shows a recovery UI.
<ErrorBoundary context="User Profile Form" onReset={handleReset}>
<UserProfileForm />
</ErrorBoundary>
Props
| Prop | Type | Default | Description |
|---|
children | ReactNode | — | Component tree to protect |
fallback | ReactNode? | — | Custom fallback UI (overrides the built-in error card) |
onError | (error: Error, errorInfo: ErrorInfo) => void? | — | Additional error callback (Sentry reporting is automatic) |
context | string? | — | Label sent to Sentry and shown in the error card title |
showDetails | boolean? | false | Show error details in production (always shown in dev) |
onReset | () => void? | — | Custom handler for the “Try Again” button |
Behavior
- Sentry integration: Automatically captures the exception with
context as a tag.
- Recovery: “Try Again” clears the error state and re-renders children. Shows a warning if the error recurs.
- Development mode: Always shows error details and component stack.
Source: src/components/ui/FormComponents.tsx
Groups related form fields under a heading with optional description.
<FormSection title="Basic Information" description="Required fields for project setup">
<FormField label="Name" required error={errors.name}>
<Input {...register('name')} />
</FormField>
</FormSection>
Props
| Prop | Type | Default | Description |
|---|
title | string | — | Section heading |
description | string? | — | Subtitle text |
children | ReactNode | — | Form fields |
className | string? | — | Additional CSS classes |
Source: src/components/ui/FormComponents.tsx
Labelled wrapper for a single form input with error and hint display.
<FormField label="Email" required error={errors.email} hint="We'll never share your email">
<Input type="email" {...register('email')} />
</FormField>
Props
| Prop | Type | Default | Description |
|---|
label | string | — | Field label text |
required | boolean? | false | Show required asterisk |
error | string? | — | Error message (shown in red below the input) |
hint | string? | — | Help text below the input |
children | ReactNode | — | The input element |
className | string? | — | Additional CSS classes |
fullWidth | boolean? | false | Expand to full container width |
id | string? | — | HTML id for the label htmlFor |
Source: src/components/ui/FormComponents.tsx
Action button container for form submit/cancel buttons.
<FormActions align="right">
<Button variant="outline" onClick={handleCancel}>Cancel</Button>
<Button type="submit" isLoading={isSubmitting}>Save</Button>
</FormActions>
Props
| Prop | Type | Default | Description |
|---|
children | ReactNode | — | Action buttons |
align | 'left' | 'right' | 'center' | 'between' | 'right' | Button alignment |
className | string? | — | Additional CSS classes |
SearchFilterBar
Source: src/components/ui/SearchFilterBar.tsx
Search input with built-in debounce, filter slot, and action slot.
<SearchFilterBar
searchValue={searchTerm}
onSearchChange={setSearchTerm}
searchPlaceholder="Search projects..."
debounceMs={300}
filters={<Select value={status} onValueChange={setStatus}>...</Select>}
actions={<Button onClick={handleAdd}><Plus className="w-4 h-4 mr-2" />Add</Button>}
/>
Props
| Prop | Type | Default | Description |
|---|
searchValue | string | — | Controlled search value |
onSearchChange | (value: string) => void | — | Debounced search callback |
searchPlaceholder | string? | 'Search...' | Input placeholder |
debounceMs | number? | 300 | Debounce delay (0 to disable) |
filters | ReactNode? | — | Filter controls rendered beside the input |
actions | ReactNode? | — | Action buttons at the trailing edge |
className | string? | — | Additional CSS classes |
Behavior
- Immediate visual feedback: The input updates instantly; only
onSearchChange is debounced.
- Clear button: Fires
onSearchChange('') immediately (no debounce).
Lists & Animation
VirtualList<T>
Source: src/components/ui/VirtualList.tsx
Virtualized scrollable list for rendering large datasets efficiently. Built on @tanstack/react-virtual.
<VirtualList
items={filteredItems}
height={600}
estimateSize={() => 64}
renderItem={(item, index) => <ItemRow key={item.id} item={item} />}
gap={8}
loading={isLoading}
renderSkeleton={() => <SkeletonRow />}
loadingItemCount={10}
/>
Props
| Prop | Type | Default | Description |
|---|
items | T[] | — | Data array |
height | number | — | Container height in pixels |
estimateSize | (index: number) => number | — | Estimated row height function |
renderItem | (item: T, index: number) => ReactNode | — | Row renderer |
overscan | number? | 5 | Number of items to render outside the visible area |
measureElement | boolean? | — | Enable dynamic measurement |
className | string? | — | Container CSS classes |
contentClassName | string? | — | Inner scroll content CSS classes |
onScroll | (offset: number) => void? | — | Scroll position callback |
getItemKey | (index: number) => string | number? | — | Stable key function |
gap | number? | — | Gap between items in pixels |
loading | boolean? | — | Show loading skeletons instead of items |
loadingItemCount | number? | — | Number of skeleton rows |
renderSkeleton | () => ReactNode? | — | Skeleton row renderer |
renderEmpty | () => ReactNode? | — | Empty state renderer |
horizontal | boolean? | false | Horizontal scroll mode |
width | number? | — | Container width (for horizontal mode) |
aria-label | string? | — | Accessible label |
StaggeredList
Source: src/components/ui/StaggeredList.tsx
Animated list that staggers child entrance animations using motion/react.
<StaggeredList direction="up" staggerDelay={0.05}>
{items.map(item => (
<StaggeredItem key={item.id}>
<ItemCard item={item} />
</StaggeredItem>
))}
</StaggeredList>
Props (StaggeredList)
| Prop | Type | Default | Description |
|---|
children | ReactNode | — | StaggeredItem children |
className | string? | — | Container CSS classes |
direction | StaggerDirection? | 'up' | Animation direction |
staggerDelay | number? | 0.05 | Delay between items (seconds) |
duration | number? | 0.3 | Animation duration (seconds) |
initialDelay | number? | 0 | Delay before the first animation |
isLoading | boolean? | — | Show loading skeletons |
loadingSkeleton | ReactNode? | — | Skeleton element template |
skeletonCount | number? | — | Number of skeleton rows |
animateOnExit | boolean? | false | Animate items when they leave |
variants | Variants? | — | Custom motion/react variants |
disabled | boolean? | false | Disable animations |
listKey | string? | — | Key to reset animations when content changes |
StaggerDirection
'up' | 'down' | 'left' | 'right' | 'fade' | 'scale'
Props (StaggeredItem)
| Prop | Type | Default | Description |
|---|
children | ReactNode | — | Item content |
className | string? | — | Additional CSS classes |
index | number? | — | Override auto-calculated index |
variants | Variants? | — | Custom motion/react variants |
motionProps | MotionProps? | — | Additional motion/react props |
Navigation
Breadcrumbs
Source: src/components/ui/Breadcrumbs.tsx
Auto-generated breadcrumb trail derived from the current URL path via react-router-dom. UUID path segments are automatically hidden. Route labels are resolved from routeLabels then title-cased as a fallback.
<Breadcrumbs /> // no props required — reads from useLocation()
Always renders a home icon as the first crumb. Renders nothing when on the root / path.
Source: src/components/ui/BackButton.tsx
A ghost variant button that calls navigate(-1). Use in page headers when drill-down navigation requires an explicit back affordance.
<BackButton />
<BackButton label="Back to Projects" />
Data Display
CardVariants
Source: src/components/ui/CardVariants.tsx
Utility object exporting pre-composed Tailwind class strings for common card surface styles. Avoids repeating bg-white rounded-lg border border-slate-200 dark:bg-slate-900 dark:border-slate-800 across every component.
import { CardVariants } from '@/components/ui/CardVariants';
<div className={CardVariants.default}>...</div>
<div className={CardVariants.elevated}>...</div>
<div className={CardVariants.flat}>...</div>
Use CardVariants for simple styling presets. Use the composable Card/CardHeader/CardContent pattern from card.tsx for structured layouts with headers and footers.
MetricCard
Source: src/components/ui/MetricCard.tsx
Displays a single KPI metric with an icon, label, value, optional trend indicator, and a semantic colour tone. Use in dashboards and summary panels instead of building metric cards inline.
<MetricCard
label="Active Projects"
value={12}
icon={FolderOpen}
tone="info"
trend={{ direction: 'up', value: '3 this month' }}
/>
Props
| Prop | Type | Default | Description |
|---|
label | string | — | Metric name |
value | string | number | — | Displayed value |
icon | LucideIcon | — | Icon component |
tone | MetricTone | 'neutral' | Colour tone: neutral | info | success | warning | danger |
trend | { direction: 'up' | 'down' | 'flat'; value: string }? | — | Optional trend label |
loading | boolean? | false | Show skeleton |
className | string? | — | Additional CSS classes |
AnimatedIcon
Source: src/components/ui/AnimatedIcon.tsx
Wraps any LucideIcon with a configurable motion/react entrance or pulse animation. Use for status indicators and empty state illustrations.
<AnimatedIcon icon={CheckCircle} animation="bounce" size="lg" tone="success" />
Props
| Prop | Type | Default | Description |
|---|
icon | LucideIcon | — | Icon to render |
animation | 'bounce' | 'pulse' | 'spin' | 'none' | 'none' | Animation variant |
size | 'sm' | 'md' | 'lg' | 'xl' | 'md' | Icon size |
tone | MetricTone | 'neutral' | Colour tone |
className | string? | — | Additional CSS classes |
Canonical Event Status
CanonicalFreshnessBadge
Source: src/components/ui/CanonicalFreshnessBadge.tsx
Displays the freshness state of a canonical status projection — fresh (green), stale (amber warning), or unknown. Optionally renders a staleActionLabel button to trigger a refresh.
<CanonicalFreshnessBadge
freshness={projection.freshness}
staleActionLabel="Refresh"
onStaleAction={handleRefresh}
/>
Props
| Prop | Type | Default | Description |
|---|
freshness | CanonicalProjectionFreshness | — | Freshness object from shared/events |
staleActionLabel | string? | — | Button label shown when stale |
onStaleAction | () => void? | — | Called when stale action button clicked |
CanonicalProjectionHealthPanel
Source: src/components/ui/CanonicalProjectionHealthPanel.tsx
Displays a debug/admin panel summarising the health of all canonical status projections: total count, stale count, error count, and last-checked timestamp. Use in SuperAdmin and developer tooling pages.
<CanonicalProjectionHealthPanel health={canonicalHealth} />
Props
| Prop | Type | Default | Description |
|---|
health | CanonicalProjectionHealth | — | Health summary from shared/events |
Utilities
AsyncBoundary
Source: src/components/ui/AsyncBoundary.tsx
Composes React Suspense + ErrorBoundary into a single boundary component. Wrap any async/lazy component to handle both loading and error states declaratively.
<AsyncBoundary
fallback={<SkeletonLoader rows={3} />}
errorFallback={<p>Failed to load.</p>}
>
<LazyComponent />
</AsyncBoundary>
Props
| Prop | Type | Default | Description |
|---|
children | ReactNode | — | Content to render |
fallback | ReactNode? | <LoadingScreen /> | Suspense fallback |
errorFallback | ReactNode? | Default error UI | Error boundary fallback |
RouteErrorBoundary
Source: src/components/ui/RouteErrorBoundary.tsx
React Router v6 error boundary component used as the errorElement on route definitions. Catches loader/action errors and renders a user-friendly error page with a “go back” action.
// In routeConfig.tsx:
{ path: '/projects', element: <Projects />, errorElement: <RouteErrorBoundary /> }
Source: src/components/ui/ScrollToTop.tsx
Effect-only component that scrolls window to (0, 0) on every route change. Mount once inside the router, outside any <Routes>.
<Router>
<ScrollToTop />
<Routes>...</Routes>
</Router>
No props.
LoadingScreen
Source: src/components/ui/LoadingScreen.tsx
Full-page centred loading indicator. Used as the default Suspense fallback for lazy-loaded routes and as the AsyncBoundary default.
<LoadingScreen message="Loading projects…" />
Props
| Prop | Type | Default | Description |
|---|
message | string? | 'Loading…' | Descriptive loading text |
KeyboardShortcutsModal
Source: src/components/ui/KeyboardShortcutsModal.tsx
Modal listing all registered keyboard shortcuts from the ActionRegistry. Opened via ⌘? / Ctrl+? by the command palette feature.
<KeyboardShortcutsModal open={open} onOpenChange={setOpen} />
Props
| Prop | Type | Default | Description |
|---|
open | boolean | — | Modal open state |
onOpenChange | (open: boolean) => void | — | State change handler |
PremiumTabs
Source: src/components/ui/PremiumTabs.tsx
Tab bar variant that locks individual tabs behind subscription tier requirements. Locked tabs render a 🔒 icon and redirect to the billing page on click instead of switching content.
<PremiumTabs
tabs={[
{ label: 'Overview', value: 'overview' },
{ label: 'Intelligence', value: 'intelligence', requiredTier: 'Professional' },
]}
value={activeTab}
onValueChange={setActiveTab}
/>
Props
| Prop | Type | Default | Description |
|---|
tabs | PremiumTab[] | — | Tab definitions with optional requiredTier |
value | string | — | Active tab value |
onValueChange | (value: string) => void | — | Tab change handler |