@chrisflippen/blueprint-document-assembly
v3.3.2
Published
An animation-heavy React component for visualizing multi-step processes as a Blueprint/Technical Document Assembly system with streaming logs, progressive document construction, and cinematic completion effects
Maintainers
Readme
Blueprint Document Assembly
An animation-heavy React component library for visualizing multi-step processes as a Blueprint/Technical Document Assembly system. Features progressive document construction, streaming logs, cinematic completion effects, theming, animation presets, and accessibility support.
Features
- Cinematic Animations — Smooth, professional animations powered by Motion (Framer Motion) with 4 built-in presets
- Progressive Document Assembly — Watch documents build piece-by-piece as steps complete with typewriter effects
- External Mode — Disable the internal timer loop and drive all state via imperative ref methods for real API integration
- Document Export — Extract rendered HTML via
getDocumentHTML()for PDF generation or downstream processing - Error Handling — Steps can error and retry, with
ErrorBoundarywrapping andonErrorcallbacks - Portal Support — Render the completion modal at
document.bodyor a custom target to avoid z-index conflicts - Panel Visibility — Hide/show header, left panel, right panel, metrics footer, and progress bar independently
- Render Slots — Replace the header or footer with custom render functions
- ThemeProvider — Full React Context theming; change all colors by passing a single config
- Animation Presets — Smooth, Snappy, Cinematic, and Minimal presets with configurable timings
- SSR Safe —
'use client'directives on all components/hooks, guardeddocument/windowaccess - Accessibility —
prefers-reduced-motionsupport, ARIA roles (progressbar,log,dialog), focus trap in modal, keyboard navigation - Mobile Responsive — Automatic vertical stacking on small screens via
useResponsiveLayout - Headless Hook —
useDocumentAssembly()for full control without any UI - Generic Presets — Squiggle text presets (
~~~~~ ~~~ ~~~~~~~) for non-domain-specific demos - Real-time Metrics — Track tokens, costs, and elapsed time
- Dark Mode — Respects system theme preferences
- TypeScript — Fully typed with exported interfaces
Installation
npm install @chrisflippen/blueprint-document-assembly motion lucide-reactStyling Requirements
This component uses CSS Custom Properties for all theme colors. It works with Tailwind CSS v4 but does not require it — any CSS framework or plain CSS is fine.
If your host app defines these CSS variables, the component will inherit them automatically:
:root {
--background: #ffffff;
--foreground: #0a0a0a;
--muted: #f4f4f5;
--muted-foreground: #71717a;
--border: #e4e4e7;
}
@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #fafafa;
--muted: #27272a;
--muted-foreground: #a1a1aa;
--border: #27272a;
}
}If these variables are not defined, sensible defaults (white background, dark text) are used automatically.
Embedding in an Existing App
The root component uses h-full instead of h-screen, so it fills its parent container. Ensure the parent has a defined height:
<div style={{ height: '600px' }}> {/* or h-full with a sized parent */}
<BlueprintDocumentAssembly />
</div>Quick Start
import { BlueprintDocumentAssembly } from '@chrisflippen/blueprint-document-assembly';
function App() {
return <BlueprintDocumentAssembly />;
}Zero-config renders a sworn affidavit assembly with default steps, logs, and document sections.
Usage Examples
Callbacks
import { BlueprintDocumentAssembly } from '@chrisflippen/blueprint-document-assembly';
import type { DocumentMetrics, Step } from '@chrisflippen/blueprint-document-assembly';
function App() {
return (
<BlueprintDocumentAssembly
onComplete={(metrics: DocumentMetrics) => console.log('Done!', metrics)}
onStepComplete={(step: Step) => console.log(`Step ${step.number} done`)}
/>
);
}Theme — Custom Colors
Pass a theme prop to recolor all components. Accepts CSS color values or Tailwind color names (backward compatible):
// CSS color values (recommended)
<BlueprintDocumentAssembly
theme={{ primary: '#3b82f6', accent: '#6366f1', success: '#22c55e' }}
/>
// Tailwind color names (still works via built-in lookup)
<BlueprintDocumentAssembly
theme={{ primary: 'blue', success: 'green', processing: 'indigo' }}
/>Under the hood, the component injects --bda-* CSS variables on its root element. All children reference these variables via inline styles. No Tailwind safelist is needed.
CSS Variable Override
You can also override colors directly in CSS without using the theme prop:
/* Override in your app's CSS */
.my-container {
--bda-primary: #3b82f6;
--bda-accent: #6366f1;
--bda-success: #22c55e;
--bda-processing: #8b5cf6;
--bda-secondary: #06b6d4;
--bda-warning: #f59e0b;
}Available CSS Variables
| Variable | Default | Description |
|----------|---------|-------------|
| --bda-primary | #f97316 (orange) | Primary accent color |
| --bda-accent | #ef4444 (red) | Gradient endpoint, active state |
| --bda-success | #10b981 (emerald) | Completed states |
| --bda-processing | #8b5cf6 (purple) | Processing/in-progress states |
| --bda-secondary | #06b6d4 (cyan) | Document/info elements |
| --bda-warning | #f59e0b (amber) | Warning log level |
| --bda-bg | var(--background, #ffffff) | Background color |
| --bda-fg | var(--foreground, #0a0a0a) | Foreground text color |
| --bda-muted | var(--muted, #f4f4f5) | Muted background |
| --bda-muted-fg | var(--muted-foreground, #71717a) | Muted text color |
| --bda-border | var(--border, #e4e4e7) | Border color |
Programmatic Access
import { resolveTheme, buildCssVars, resolveColorValue, colorMix } from '@chrisflippen/blueprint-document-assembly';
const theme = resolveTheme({ primary: 'blue' });
// theme.primary.color === '#3b82f6'
// theme.cssVars === { '--bda-primary': '#3b82f6', ... }
resolveColorValue('blue'); // '#3b82f6'
colorMix('#3b82f6', 10); // 'color-mix(in srgb, #3b82f6 10%, transparent)'Animation Presets
Four built-in presets control all animation timings, springs, stagger delays, and typewriter speeds:
import {
BlueprintDocumentAssembly,
SMOOTH_PRESET, // Default — balanced
SNAPPY_PRESET, // Fast, responsive
CINEMATIC_PRESET, // Slow, dramatic
MINIMAL_PRESET, // Near-instant (used automatically for reduced motion)
} from '@chrisflippen/blueprint-document-assembly';
<BlueprintDocumentAssembly animationPreset={SNAPPY_PRESET} />You can also override individual timings without a full preset:
<BlueprintDocumentAssembly
animationTimings={{
stepActivation: 400,
substepDelay: 250,
typewriterSpeed: 15,
}}
/>Squiggle Text Presets
Generic document presets using tilde characters instead of real content — useful for demos, portfolios, and non-legal use cases:
import {
BlueprintDocumentAssembly,
SQUIGGLE_STEPS,
SQUIGGLE_STEP_LOGS,
SQUIGGLE_SUBSTEP_LOGS,
SQUIGGLE_DOCUMENT_SECTIONS,
} from '@chrisflippen/blueprint-document-assembly';
<BlueprintDocumentAssembly
steps={SQUIGGLE_STEPS}
stepLogs={SQUIGGLE_STEP_LOGS}
substepLogs={SQUIGGLE_SUBSTEP_LOGS}
documentSections={SQUIGGLE_DOCUMENT_SECTIONS}
/>Squiggle presets use the same section IDs and substep triggers as the legal preset, so they're a drop-in replacement.
Headless Hook
Use useDocumentAssembly() for full programmatic control without the built-in UI:
import { useDocumentAssembly } from '@chrisflippen/blueprint-document-assembly';
function CustomAssembly() {
const {
steps, progress, isRunning, isComplete, metrics,
completedSubsteps, completedSections,
start, pause, resume, reset, goToStep,
} = useDocumentAssembly({
autoStart: false,
animationTimings: { stepActivation: 500 },
onComplete: (m) => console.log('Done', m),
});
return (
<div>
<p>Progress: {progress}%</p>
<button onClick={start}>Start</button>
<button onClick={pause}>Pause</button>
<button onClick={resume}>Resume</button>
<button onClick={reset}>Reset</button>
</div>
);
}Imperative Control via Ref
import { useRef } from 'react';
import { BlueprintDocumentAssembly } from '@chrisflippen/blueprint-document-assembly';
import type { BlueprintDocumentAssemblyRef } from '@chrisflippen/blueprint-document-assembly';
function App() {
const ref = useRef<BlueprintDocumentAssemblyRef>(null);
return (
<>
<BlueprintDocumentAssembly ref={ref} autoStart={false} />
<button onClick={() => ref.current?.start()}>Start</button>
<button onClick={() => ref.current?.pause()}>Pause</button>
</>
);
}External Mode — Real API Integration
When externalMode={true}, the internal simulation loop is disabled. The host app drives all state transitions via imperative ref methods. This is the key unlock for production use.
import { useRef, useEffect } from 'react';
import { BlueprintDocumentAssembly } from '@chrisflippen/blueprint-document-assembly';
import type { BlueprintDocumentAssemblyRef } from '@chrisflippen/blueprint-document-assembly';
function App() {
const ref = useRef<BlueprintDocumentAssemblyRef>(null);
useEffect(() => {
async function runAssembly() {
ref.current?.start();
// Step 0: start processing
ref.current?.updateStep(0, { status: 'active' });
ref.current?.addLog(0, null, 'info', 'Connecting to API...');
try {
const result = await fetch('/api/process-step-0');
const data = await result.json();
ref.current?.addLog(0, null, 'success', `Received ${data.tokens} tokens`);
ref.current?.updateMetrics({ tokens: data.tokens, cost: data.cost });
ref.current?.completeStep(0);
} catch (err) {
ref.current?.setStepError(0, err.message);
}
}
runAssembly();
}, []);
return (
<BlueprintDocumentAssembly
ref={ref}
externalMode
autoStart={false}
onError={(err) => console.error(`Step ${err.stepIndex} failed:`, err.message)}
/>
);
}External Mode Ref Methods
| Method | Description |
|--------|-------------|
| updateStep(index, { status, errorMessage }) | Merge partial state into a step |
| completeStep(index) | Mark step completed, reveal its document section, advance currentStep |
| completeSubstep(index, substepId) | Mark a substep completed |
| setStepError(index, message) | Set step to 'error' status with a message |
| retryStep(index) | Reset step back to 'pending', clear error |
| addLog(index, substepId, level, message) | Add a log entry ('info' | 'success' | 'warning' | 'processing') |
| updateMetrics(partial \| updater) | Update metrics with a partial object or updater function |
| getDocumentContentRef() | Get the DOM element containing the rendered document |
| getDocumentHTML() | Get the innerHTML of the rendered document for PDF generation |
State Snapshots
The headless hook also exposes getSnapshot() and restoreSnapshot() for persisting state:
const { getSnapshot, restoreSnapshot } = useDocumentAssembly({ externalMode: true });
// Save to localStorage
const snapshot = getSnapshot();
localStorage.setItem('assembly-state', JSON.stringify(snapshot));
// Restore later
const saved = JSON.parse(localStorage.getItem('assembly-state')!);
restoreSnapshot(saved);Document Export
Extract rendered HTML for PDF generation or downstream processing:
const ref = useRef<BlueprintDocumentAssemblyRef>(null);
// Get raw HTML string
const html = ref.current?.getDocumentHTML();
// Or get the DOM element directly
const el = ref.current?.getDocumentContentRef();Error Handling
Steps support an 'error' status with visual feedback (warning-colored badge, AlertCircle icon, error message). The component is also wrapped in an ErrorBoundary for render errors.
<BlueprintDocumentAssembly
onError={(err) => reportError(err)}
errorFallback={(error, reset) => (
<div>
<p>Assembly crashed: {error.message}</p>
<button onClick={reset}>Retry</button>
</div>
)}
onRenderError={(error, info) => logToSentry(error, info)}
/>The ErrorBoundary component is also exported for standalone use:
import { ErrorBoundary } from '@chrisflippen/blueprint-document-assembly';
<ErrorBoundary
fallback={(error, reset) => <p>{error.message} <button onClick={reset}>Retry</button></p>}
onError={(error, info) => console.error(error)}
>
<MyComponent />
</ErrorBoundary>Portal Support
By default, the completion modal uses absolute positioning within the component. In host apps with their own modals, this can cause z-index conflicts. Use portalTarget to render it elsewhere:
// Portal to document.body
<BlueprintDocumentAssembly portalTarget />
// Portal to a specific CSS selector
<BlueprintDocumentAssembly portalTarget="#modal-root" />
// Portal to a specific element
<BlueprintDocumentAssembly portalTarget={myContainerRef.current} />When portaled, the modal uses fixed positioning instead of absolute.
Panel Visibility
Hide or show individual UI sections:
// Hide the left panel (steps) — show only the document preview
<BlueprintDocumentAssembly visibility={{ leftPanel: false }} />
// Hide metrics footer and progress bar
<BlueprintDocumentAssembly visibility={{ metricsFooter: false, progressBar: false }} />
// Hide everything except the document
<BlueprintDocumentAssembly
visibility={{ leftPanel: false, header: false, metricsFooter: false, progressBar: false }}
/>When only one panel is visible, it automatically takes full width.
Custom Render Slots
Replace the header or footer with custom UI:
<BlueprintDocumentAssembly
renderSlots={{
header: ({ currentStep, totalSteps, progress }) => (
<div className="mb-8">
<h2>Custom Header — Step {currentStep + 1}/{totalSteps}</h2>
<progress value={progress} max={100} />
</div>
),
footer: ({ metrics }) => (
<div className="p-4 text-center text-sm">
{metrics.tokens} tokens · ${metrics.cost.toFixed(4)}
</div>
),
}}
/>SSR / Next.js Compatibility
All component and hook files include 'use client' directives. Style injection uses an SSR-safe injectStyle() utility that checks for document availability before DOM access.
// Safe to import in Next.js App Router
import { BlueprintDocumentAssembly } from '@chrisflippen/blueprint-document-assembly';
// The injectStyle utility is also exported for custom use
import { injectStyle } from '@chrisflippen/blueprint-document-assembly';
injectStyle('my-styles', '.my-class { color: red; }');Accessibility
Reduced Motion
The library automatically detects prefers-reduced-motion: reduce and:
- Skips typewriter animations (shows full text immediately)
- Disables sparkle/celebration effects
- Uses
MINIMAL_PRESETtimings (near-instant transitions) - Removes entrance slide/scale animations
When using AnimationProvider, reduced motion detection is automatic:
import { AnimationProvider, useAnimation } from '@chrisflippen/blueprint-document-assembly';
function MyComponent() {
const { reducedMotion, preset } = useAnimation();
// reducedMotion === true when user prefers reduced motion
}You can also use the hook standalone:
import { useReducedMotion } from '@chrisflippen/blueprint-document-assembly';
const prefersReduced = useReducedMotion(); // booleanARIA Attributes
- Progress bar:
role="progressbar"witharia-valuenow,aria-valuemin,aria-valuemax - Log containers:
role="log"witharia-live="polite" - Log toggles:
aria-expandedandaria-label - Substep lists:
role="list"androle="listitem" - Root container:
aria-label="Document assembly" - Document lines:
aria-busywhile typewriter animation is active - Completion modal:
role="dialog",aria-modal="true", focus trap,Escapeto close
Keyboard Navigation
- Escape — closes the completion modal overlay
- Tab — cycles focus within the modal (trapped between close button and content)
- focus-visible ring styling on all interactive elements
Mobile Responsive
On screens narrower than 768px, the layout automatically switches from horizontal (side-by-side) to vertical (stacked). Use the hook directly:
import { useResponsiveLayout } from '@chrisflippen/blueprint-document-assembly';
const { isMobile, isTablet, direction } = useResponsiveLayout();
// direction: 'vertical' on mobile, 'horizontal' on desktopOr override via the layout prop:
<BlueprintDocumentAssembly layout={{ direction: 'vertical' }} />API Reference
BlueprintDocumentAssembly Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| steps | Step[] | Default affidavit steps | Steps to process |
| stepLogs | Record<string, string[][]> | Default logs | Log data per step |
| substepLogs | Record<string, string[][]> | Default logs | Log data per substep |
| documentSections | DocumentSection[] | Legal preset | Document content sections |
| autoStart | boolean | true | Start assembly on mount |
| autoScroll | boolean | true | Auto-scroll document |
| showWholeDocumentView | boolean | true | Show completion overlay |
| documentIds | DocumentIds | Auto-generated | Custom case/file numbers |
| animationSpeed | number | 1 | Speed multiplier |
| animationPreset | AnimationPreset | - | Animation preset (SMOOTH, SNAPPY, etc.) |
| animationTimings | AnimationTimings | - | Override individual timing values |
| theme | ThemeConfig | - | Theme color config |
| classNames | ClassNameSlots | - | CSS class overrides for internal slots |
| labels | LabelConfig | - | UI string overrides |
| layout | LayoutConfig | - | Layout direction and panel widths |
| className | string | - | Root container className |
| renderStep | (step, default) => ReactNode | - | Custom step renderer |
| renderSubstep | (substep, default) => ReactNode | - | Custom substep renderer |
| renderLog | (log, default) => ReactNode | - | Custom log renderer |
| renderDocumentSection | (section, substeps) => ReactNode | - | Custom section renderer |
| onComplete | (metrics) => void | - | Assembly complete callback |
| onStepStart | (step, index) => void | - | Step started callback |
| onStepComplete | (step, metrics) => void | - | Step complete callback |
| onSubstepComplete | (substepId, stepIndex) => void | - | Substep complete callback |
| onSectionReveal | (sectionId) => void | - | Section revealed callback |
| onLogEntry | (log) => void | - | Log entry added callback |
| onPause | () => void | - | Paused callback |
| onResume | () => void | - | Resumed callback |
| onReset | () => void | - | Reset callback |
| externalMode | boolean | false | Disable internal timer; drive state via ref methods |
| onError | (error) => void | - | Step error callback (external mode) |
| portalTarget | boolean \| string \| HTMLElement | - | Portal target for completion modal |
| visibility | VisibilityConfig | all true | Show/hide header, panels, footer, progress bar |
| renderSlots | RenderSlots | - | Custom header/footer render functions |
| errorFallback | ReactNode \| (error, reset) => ReactNode | - | Error boundary fallback UI |
| onRenderError | (error, errorInfo) => void | - | Render error callback |
Exports
Components:
BlueprintDocumentAssembly, StepItem, SubStepItem, LogLine, LogContainer, DocumentPreview, DocumentLine, WholeDocumentView, WholeDocumentContent, ErrorBoundary
Theme:
ThemeProvider, useTheme, useThemeOptional, resolveTheme, buildCssVars, resolveColorValue, colorMix, TAILWIND_COLOR_MAP, ~~getThemeSafelist~~ (deprecated)
Animation:
AnimationProvider, useAnimation, useAnimationOptional, SMOOTH_PRESET, SNAPPY_PRESET, CINEMATIC_PRESET, MINIMAL_PRESET
Hooks:
useDocumentAssembly, useReducedMotion, useResponsiveLayout
Presets:
LEGAL_DOCUMENT_SECTIONS, LEGAL_WHOLE_DOCUMENT_SECTIONS, SQUIGGLE_DOCUMENT_SECTIONS, SQUIGGLE_WHOLE_DOCUMENT_SECTIONS, SQUIGGLE_STEPS, SQUIGGLE_STEP_LOGS, SQUIGGLE_SUBSTEP_LOGS
Constants:
DEFAULT_STEPS, DEFAULT_STEP_LOGS, DEFAULT_SUBSTEP_LOGS, DEFAULT_LABELS, DEFAULT_THEME, STEP_ICON_MAP
Utilities:
createStep, createStepFactory, createSubStep, createDocumentSection, resetStepCounter, resolveContent, injectStyle
Types:
Step, SubStep, LogEntry, DocumentMetrics, DocumentIds, DocumentSection, DocumentSubsection, ClassNameSlots, ThemeConfig, AnimationTimings, AnimationPreset, ResolvedTheme, ResolvedThemeColors, LabelConfig, LayoutConfig, UseDocumentAssemblyOptions, UseDocumentAssemblyReturn, BlueprintDocumentAssemblyRef, BlueprintDocumentAssemblyProps, AssemblySnapshot, VisibilityConfig, RenderSlots, and all component prop types
Architecture
src/
├── components/
│ ├── BlueprintDocumentAssembly.tsx # Main component (providers, responsive, ARIA)
│ ├── StepItem.tsx # Step card with error state support
│ ├── SubStepItem.tsx # Substep with ARIA listitem
│ ├── LogComponents.tsx # Log display with ARIA log role
│ ├── DocumentPreview.tsx # Progressive document builder
│ ├── DocumentLine.tsx # Typewriter text (reduced motion aware)
│ ├── WholeDocumentView.tsx # Completion overlay with portal support
│ ├── WholeDocumentContent.tsx # Static document renderer with contentRef
│ └── ErrorBoundary.tsx # React error boundary with custom fallback
├── theme/
│ ├── ThemeContext.tsx # ThemeProvider, resolveTheme (incl. error status)
│ ├── css-vars.ts # CSS variable helpers, Tailwind color lookup
│ └── index.ts
├── animation/
│ ├── presets.ts # SMOOTH, SNAPPY, CINEMATIC, MINIMAL
│ ├── AnimationContext.tsx # AnimationProvider, useAnimation
│ └── index.ts
├── hooks/
│ ├── useDocumentAssembly.ts # Headless hook with external mode + snapshots
│ ├── useReducedMotion.ts # prefers-reduced-motion detection
│ ├── useResponsiveLayout.ts # Breakpoint detection
│ └── index.ts
├── presets/
│ ├── legal-document-sections.ts # Sworn affidavit sections
│ ├── legal-whole-document.ts # Legal completion view
│ ├── squiggle-document-sections.ts # Generic squiggle text sections
│ ├── squiggle-steps.ts # Generic steps + logs
│ └── index.ts
├── utils/
│ ├── factories.ts # createStep, createSubStep, etc.
│ ├── content-resolvers.ts # Shared resolveContent utility
│ ├── inject-style.ts # SSR-safe style injection utility
│ └── index.ts
├── constants/
│ ├── default-labels.ts
│ └── default-theme.ts
├── types.ts # All types incl. AssemblySnapshot, VisibilityConfig, RenderSlots
├── constants.ts
├── default-steps.ts
└── index.tsx # Public APILicense
MIT
Acknowledgments
- Built with Motion (Framer Motion)
- Icons by Lucide
- Styled with Tailwind CSS
