@ordinatio/ui
v1.1.0
Published
Reusable React components and hooks for Ordinatio applications
Readme
@ordinatio/ui
Battle-tested React components and hooks extracted from System 1701. Zero external UI library dependencies — works with any React setup (Tailwind, shadcn, CSS modules, vanilla CSS).
Quick Start
npm install @ordinatio/uiimport {
ErrorBoundary,
EmailBodyRenderer,
EmailThreadView,
useKeyboardShortcuts,
cachedFetch,
prefetch,
} from '@ordinatio/ui';Peer dependency: React 18+
Components
<ErrorBoundary>
Catches React rendering crashes, generates a unique timestamped error reference, and displays a user-friendly error card with a copy button. Zero external dependencies — uses inline styles.
import { ErrorBoundary, withErrorBoundary } from '@ordinatio/ui';
// Wrap any component tree
<ErrorBoundary
title="Something went wrong"
message="An error occurred while loading this section."
refHint="Copy this code and send to support."
onError={(error, errorInfo) => logToService(error)}
renderActions={(reset) => (
<>
<button onClick={reset}>Try Again</button>
<button onClick={() => window.location.href = '/'}>Go Home</button>
</>
)}
>
<YourComponent />
</ErrorBoundary>
// Or use the HOC
const SafeComponent = withErrorBoundary(DangerousComponent, {
title: 'Widget Error',
});Features:
- Unique error ref:
RENDER_100-20260409T120000(copyable with one click) - Component stack trace (collapsed by default)
- Custom title, message, and hint text
- Custom action buttons via
renderActions onErrorcallback for external error tracking- Fallback UI prop for fully custom error display
- Clipboard copy with 2-second confirmation feedback
- Works in
'use client'components (Next.js App Router)
Props:
| Prop | Type | Description |
|------|------|-------------|
| children | ReactNode | Content to protect |
| fallback | ReactNode | Custom fallback UI (replaces default error card) |
| onError | (error, info) => void | Error callback |
| errorCode | string | Override error code (default: RENDER_100) |
| title | string | Error card title |
| message | string | Error card message |
| refHint | string | Text below the error ref |
| renderActions | (reset) => ReactNode | Custom action buttons |
| showDashboardButton | boolean | Show "Back to Dashboard" link |
<EmailBodyRenderer>
Renders email HTML in an isolated iframe to prevent CSS bleed into the host app. Auto-resizes, strips <style> tags, and opens all links in new tabs.
import { EmailBodyRenderer } from '@ordinatio/ui';
<EmailBodyRenderer
bodyHtml={email.bodyHtml}
bodyText={email.bodyText}
snippet={email.snippet}
minHeight="300px"
iframeStyles="body { font-family: Georgia, serif; }"
className="my-custom-class"
/>Features:
- Strips
<style>tags (baseline CSS bleed defense) <base target="_blank">— all links open in new tabs- Auto-resize via
onLoad(fits content height + 32px padding) - Sandboxed:
allow-same-origin allow-popups referrerPolicy="no-referrer"for privacy- Falls back to
<pre>for plain text, paragraph for snippet - Returns
nullwhen no content
<EmailThreadView>
Gmail-style email thread with expand/collapse. Shows sender avatars, names, date, and snippet preview. Generic — works with any message type.
import { EmailThreadView } from '@ordinatio/ui';
import type { ThreadMessage } from '@ordinatio/ui';
const messages: ThreadMessage[] = [...];
<EmailThreadView
messages={messages}
initialExpanded={['msg-latest']}
renderMessage={(msg, isExpanded) => (
<CustomMessageRenderer message={msg} />
)}
/>Features:
- Auto-expands last message by default
- Click header to toggle expand/collapse
- Sender avatar initial (from name or email)
- Date formatting:
Apr 7, 10:30 AM - Snippet preview on collapsed messages (80 char truncation)
- Custom
renderMessagecallback for full control - Generic type:
EmailThreadView<T extends ThreadMessage> - Single message: renders directly without thread UI
Hooks
useKeyboardShortcuts()
Declarative keyboard shortcuts that respect form inputs and support modifier keys.
import { useKeyboardShortcuts } from '@ordinatio/ui';
useKeyboardShortcuts({
shortcuts: [
{ key: 'r', handler: () => handleReply() },
{ key: 'e', handler: () => handleArchive() },
{ key: 'Escape', handler: () => closeModal() },
{ key: 'k', handler: () => openSearch(), modifiers: { meta: true } },
{ key: 's', handler: () => save(), modifiers: { meta: true } },
],
enabled: !isModalOpen, // Disable when modal is active
});Features:
- Case-insensitive key matching
- Auto-ignores when focused in
INPUT,TEXTAREA,SELECT, orcontentEditable - Modifier key support:
meta,ctrl,alt,shift - Without modifiers specified: ignores if any modifier is pressed (prevents conflicts)
enabledflag for conditional activation- Custom
ignoreTagsarray - Cleanup on unmount
Cache Utilities
cachedFetch()
Stale-while-revalidate caching for API responses. Returns cached data instantly, revalidates in background.
import { cachedFetch, invalidateCache, prefetch, clearCache } from '@ordinatio/ui';
// Fetch with caching
const data = await cachedFetch<InboxResponse>('/api/email/messages');
// 1st call: fetches from server, caches
// 2nd call (within 30s): returns cache instantly
// 2nd call (30s-5min): returns cache, revalidates in background
// With callback (fires twice if revalidating)
await cachedFetch('/api/data', undefined, (data, fromCache) => {
if (!fromCache) setData(data); // Update UI with fresh data
});
// Custom timing
await cachedFetch('/api/data', { staleTime: 60_000, maxAge: 10 * 60_000 });
// After a mutation, invalidate
invalidateCache('/api/email/messages');
// Prefetch on app load (fire-and-forget)
prefetch('/api/email/messages');
prefetch('/api/tasks');
// Clear everything
clearCache();Defaults:
staleTime: 30 seconds (data is "fresh")maxAge: 5 minutes (data is evicted)
Features:
- In-flight deduplication (concurrent requests for same URL share one fetch)
onDatacallback fires twice when revalidating: once with cache, once with freshinvalidateCache()forces next fetch to go to serverprefetch()warms cache without blocking- Type-safe with generics:
cachedFetch<T>(url)
Error System
import { renderError, RENDER_ERRORS } from '@ordinatio/ui';
const { code, ref, timestamp, module, context } = renderError('RENDER_100', {
message: error.message,
componentName: 'OrderForm',
});
// → { code: 'RENDER_100', ref: 'RENDER_100-20260409T120000', module: 'RENDER', ... }| Code | Description | |------|-------------| | RENDER_100 | Generic component rendering failure | | RENDER_101 | Page failed to render (route-level) | | RENDER_102 | Data display crashed (null/undefined) |
Testing
pnpm --filter @ordinatio/ui test62 tests across 6 test files:
| File | Tests | Coverage |
|------|-------|----------|
| error-boundary.test.tsx | 13 | Catch, display, reset, HOC, fallback, callbacks |
| email-body-renderer.test.tsx | 10 | Iframe, style strip, fallbacks, custom styles |
| email-thread-view.test.tsx | 12 | Expand/collapse, avatars, custom renderer |
| use-keyboard-shortcuts.test.ts | 9 | Keys, modifiers, input ignore, cleanup |
| use-cached-fetch.test.ts | 9 | Cache, revalidation, dedup, prefetch |
| render-errors.test.ts | 9 | Ref format, context, registry |
Design Decisions
- Zero UI library dependency — Inline styles, no Tailwind/shadcn requirement. Works anywhere React runs.
- className + style props — Components accept both for Tailwind/CSS module layering.
- Generic types —
EmailThreadView<T>accepts any message shape extendingThreadMessage. - Pure cache —
cachedFetchis a module-level singleton (survives SPA navigation).
License
MIT — part of the Ordinatio platform.
