@chrisflippen/blueprint-document-assembly 3.1.1 → 3.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,8 +9,15 @@ An animation-heavy React component library for visualizing multi-step processes
9
9
 
10
10
  - **Cinematic Animations** — Smooth, professional animations powered by Motion (Framer Motion) with 4 built-in presets
11
11
  - **Progressive Document Assembly** — Watch documents build piece-by-piece as steps complete with typewriter effects
12
+ - **External Mode** — Disable the internal timer loop and drive all state via imperative ref methods for real API integration
13
+ - **Document Export** — Extract rendered HTML via `getDocumentHTML()` for PDF generation or downstream processing
14
+ - **Error Handling** — Steps can error and retry, with `ErrorBoundary` wrapping and `onError` callbacks
15
+ - **Portal Support** — Render the completion modal at `document.body` or a custom target to avoid z-index conflicts
16
+ - **Panel Visibility** — Hide/show header, left panel, right panel, metrics footer, and progress bar independently
17
+ - **Render Slots** — Replace the header or footer with custom render functions
12
18
  - **ThemeProvider** — Full React Context theming; change all colors by passing a single config
13
19
  - **Animation Presets** — Smooth, Snappy, Cinematic, and Minimal presets with configurable timings
20
+ - **SSR Safe** — `'use client'` directives on all components/hooks, guarded `document`/`window` access
14
21
  - **Accessibility** — `prefers-reduced-motion` support, ARIA roles (`progressbar`, `log`, `dialog`), focus trap in modal, keyboard navigation
15
22
  - **Mobile Responsive** — Automatic vertical stacking on small screens via `useResponsiveLayout`
16
23
  - **Headless Hook** — `useDocumentAssembly()` for full control without any UI
@@ -27,37 +34,40 @@ npm install @chrisflippen/blueprint-document-assembly motion lucide-react
27
34
 
28
35
  ## Styling Requirements
29
36
 
30
- This component requires **Tailwind CSS v4**. Your theme should include these CSS variables:
37
+ 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.
38
+
39
+ If your host app defines these CSS variables, the component will inherit them automatically:
31
40
 
32
41
  ```css
33
- @theme {
34
- --color-background: #ffffff;
35
- --color-foreground: #0a0a0a;
36
- --color-muted: #f4f4f5;
37
- --color-muted-foreground: #71717a;
38
- --color-border: #e4e4e7;
42
+ :root {
43
+ --background: #ffffff;
44
+ --foreground: #0a0a0a;
45
+ --muted: #f4f4f5;
46
+ --muted-foreground: #71717a;
47
+ --border: #e4e4e7;
39
48
  }
40
49
 
41
50
  @media (prefers-color-scheme: dark) {
42
- @theme {
43
- --color-background: #0a0a0a;
44
- --color-foreground: #fafafa;
45
- --color-muted: #27272a;
46
- --color-muted-foreground: #a1a1aa;
47
- --color-border: #27272a;
51
+ :root {
52
+ --background: #0a0a0a;
53
+ --foreground: #fafafa;
54
+ --muted: #27272a;
55
+ --muted-foreground: #a1a1aa;
56
+ --border: #27272a;
48
57
  }
49
58
  }
50
59
  ```
51
60
 
52
- ### Tailwind Safelist (required when using ThemeProvider)
61
+ If these variables are not defined, sensible defaults (white background, dark text) are used automatically.
53
62
 
54
- Dynamic theme colors generate Tailwind classes at runtime. Add them to your safelist:
63
+ ### Embedding in an Existing App
55
64
 
56
- ```ts
57
- import { getThemeSafelist } from '@chrisflippen/blueprint-document-assembly';
65
+ The root component uses `h-full` instead of `h-screen`, so it fills its parent container. Ensure the parent has a defined height:
58
66
 
59
- // In your Tailwind config
60
- safelist: getThemeSafelist({ primary: 'blue', success: 'green' })
67
+ ```tsx
68
+ <div style={{ height: '600px' }}> {/* or h-full with a sized parent */}
69
+ <BlueprintDocumentAssembly />
70
+ </div>
61
71
  ```
62
72
 
63
73
  ## Quick Start
@@ -90,29 +100,69 @@ function App() {
90
100
  }
91
101
  ```
92
102
 
93
- ### ThemeProvider — Custom Colors
103
+ ### Theme — Custom Colors
94
104
 
95
- Pass a `theme` prop to recolor all components. Colors map to Tailwind color names.
105
+ Pass a `theme` prop to recolor all components. Accepts CSS color values or Tailwind color names (backward compatible):
96
106
 
97
107
  ```tsx
108
+ // CSS color values (recommended)
109
+ <BlueprintDocumentAssembly
110
+ theme={{ primary: '#3b82f6', accent: '#6366f1', success: '#22c55e' }}
111
+ />
112
+
113
+ // Tailwind color names (still works via built-in lookup)
98
114
  <BlueprintDocumentAssembly
99
115
  theme={{ primary: 'blue', success: 'green', processing: 'indigo' }}
100
116
  />
101
117
  ```
102
118
 
103
- Under the hood this wraps the component tree in a `<ThemeProvider>` that resolves color names into Tailwind class strings. Components read from context via `useThemeOptional()` and fall back to hardcoded styles when no provider is present.
119
+ 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.
104
120
 
105
- You can also use the provider directly for sub-components:
121
+ #### CSS Variable Override
106
122
 
107
- ```tsx
108
- import { ThemeProvider, useTheme, resolveTheme } from '@chrisflippen/blueprint-document-assembly';
123
+ You can also override colors directly in CSS without using the `theme` prop:
109
124
 
110
- function CustomUI() {
111
- const theme = useTheme(); // throws if no provider
112
- return <div className={theme.primary.bg}>Themed content</div>;
125
+ ```css
126
+ /* Override in your app's CSS */
127
+ .my-container {
128
+ --bda-primary: #3b82f6;
129
+ --bda-accent: #6366f1;
130
+ --bda-success: #22c55e;
131
+ --bda-processing: #8b5cf6;
132
+ --bda-secondary: #06b6d4;
133
+ --bda-warning: #f59e0b;
113
134
  }
114
135
  ```
115
136
 
137
+ #### Available CSS Variables
138
+
139
+ | Variable | Default | Description |
140
+ |----------|---------|-------------|
141
+ | `--bda-primary` | `#f97316` (orange) | Primary accent color |
142
+ | `--bda-accent` | `#ef4444` (red) | Gradient endpoint, active state |
143
+ | `--bda-success` | `#10b981` (emerald) | Completed states |
144
+ | `--bda-processing` | `#8b5cf6` (purple) | Processing/in-progress states |
145
+ | `--bda-secondary` | `#06b6d4` (cyan) | Document/info elements |
146
+ | `--bda-warning` | `#f59e0b` (amber) | Warning log level |
147
+ | `--bda-bg` | `var(--background, #ffffff)` | Background color |
148
+ | `--bda-fg` | `var(--foreground, #0a0a0a)` | Foreground text color |
149
+ | `--bda-muted` | `var(--muted, #f4f4f5)` | Muted background |
150
+ | `--bda-muted-fg` | `var(--muted-foreground, #71717a)` | Muted text color |
151
+ | `--bda-border` | `var(--border, #e4e4e7)` | Border color |
152
+
153
+ #### Programmatic Access
154
+
155
+ ```tsx
156
+ import { resolveTheme, buildCssVars, resolveColorValue, colorMix } from '@chrisflippen/blueprint-document-assembly';
157
+
158
+ const theme = resolveTheme({ primary: 'blue' });
159
+ // theme.primary.color === '#3b82f6'
160
+ // theme.cssVars === { '--bda-primary': '#3b82f6', ... }
161
+
162
+ resolveColorValue('blue'); // '#3b82f6'
163
+ colorMix('#3b82f6', 10); // 'color-mix(in srgb, #3b82f6 10%, transparent)'
164
+ ```
165
+
116
166
  ### Animation Presets
117
167
 
118
168
  Four built-in presets control all animation timings, springs, stagger delays, and typewriter speeds:
@@ -214,6 +264,196 @@ function App() {
214
264
  }
215
265
  ```
216
266
 
267
+ ### External Mode — Real API Integration
268
+
269
+ 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.
270
+
271
+ ```tsx
272
+ import { useRef, useEffect } from 'react';
273
+ import { BlueprintDocumentAssembly } from '@chrisflippen/blueprint-document-assembly';
274
+ import type { BlueprintDocumentAssemblyRef } from '@chrisflippen/blueprint-document-assembly';
275
+
276
+ function App() {
277
+ const ref = useRef<BlueprintDocumentAssemblyRef>(null);
278
+
279
+ useEffect(() => {
280
+ async function runAssembly() {
281
+ ref.current?.start();
282
+
283
+ // Step 0: start processing
284
+ ref.current?.updateStep(0, { status: 'active' });
285
+ ref.current?.addLog(0, null, 'info', 'Connecting to API...');
286
+
287
+ try {
288
+ const result = await fetch('/api/process-step-0');
289
+ const data = await result.json();
290
+
291
+ ref.current?.addLog(0, null, 'success', `Received ${data.tokens} tokens`);
292
+ ref.current?.updateMetrics({ tokens: data.tokens, cost: data.cost });
293
+ ref.current?.completeStep(0);
294
+ } catch (err) {
295
+ ref.current?.setStepError(0, err.message);
296
+ }
297
+ }
298
+ runAssembly();
299
+ }, []);
300
+
301
+ return (
302
+ <BlueprintDocumentAssembly
303
+ ref={ref}
304
+ externalMode
305
+ autoStart={false}
306
+ onError={(err) => console.error(`Step ${err.stepIndex} failed:`, err.message)}
307
+ />
308
+ );
309
+ }
310
+ ```
311
+
312
+ #### External Mode Ref Methods
313
+
314
+ | Method | Description |
315
+ |--------|-------------|
316
+ | `updateStep(index, { status, errorMessage })` | Merge partial state into a step |
317
+ | `completeStep(index)` | Mark step completed, reveal its document section, advance `currentStep` |
318
+ | `completeSubstep(index, substepId)` | Mark a substep completed |
319
+ | `setStepError(index, message)` | Set step to `'error'` status with a message |
320
+ | `retryStep(index)` | Reset step back to `'pending'`, clear error |
321
+ | `addLog(index, substepId, level, message)` | Add a log entry (`'info'` \| `'success'` \| `'warning'` \| `'processing'`) |
322
+ | `updateMetrics(partial \| updater)` | Update metrics with a partial object or updater function |
323
+ | `getDocumentContentRef()` | Get the DOM element containing the rendered document |
324
+ | `getDocumentHTML()` | Get the `innerHTML` of the rendered document for PDF generation |
325
+
326
+ #### State Snapshots
327
+
328
+ The headless hook also exposes `getSnapshot()` and `restoreSnapshot()` for persisting state:
329
+
330
+ ```tsx
331
+ const { getSnapshot, restoreSnapshot } = useDocumentAssembly({ externalMode: true });
332
+
333
+ // Save to localStorage
334
+ const snapshot = getSnapshot();
335
+ localStorage.setItem('assembly-state', JSON.stringify(snapshot));
336
+
337
+ // Restore later
338
+ const saved = JSON.parse(localStorage.getItem('assembly-state')!);
339
+ restoreSnapshot(saved);
340
+ ```
341
+
342
+ ### Document Export
343
+
344
+ Extract rendered HTML for PDF generation or downstream processing:
345
+
346
+ ```tsx
347
+ const ref = useRef<BlueprintDocumentAssemblyRef>(null);
348
+
349
+ // Get raw HTML string
350
+ const html = ref.current?.getDocumentHTML();
351
+
352
+ // Or get the DOM element directly
353
+ const el = ref.current?.getDocumentContentRef();
354
+ ```
355
+
356
+ ### Error Handling
357
+
358
+ 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.
359
+
360
+ ```tsx
361
+ <BlueprintDocumentAssembly
362
+ onError={(err) => reportError(err)}
363
+ errorFallback={(error, reset) => (
364
+ <div>
365
+ <p>Assembly crashed: {error.message}</p>
366
+ <button onClick={reset}>Retry</button>
367
+ </div>
368
+ )}
369
+ onRenderError={(error, info) => logToSentry(error, info)}
370
+ />
371
+ ```
372
+
373
+ The `ErrorBoundary` component is also exported for standalone use:
374
+
375
+ ```tsx
376
+ import { ErrorBoundary } from '@chrisflippen/blueprint-document-assembly';
377
+
378
+ <ErrorBoundary
379
+ fallback={(error, reset) => <p>{error.message} <button onClick={reset}>Retry</button></p>}
380
+ onError={(error, info) => console.error(error)}
381
+ >
382
+ <MyComponent />
383
+ </ErrorBoundary>
384
+ ```
385
+
386
+ ### Portal Support
387
+
388
+ 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:
389
+
390
+ ```tsx
391
+ // Portal to document.body
392
+ <BlueprintDocumentAssembly portalTarget />
393
+
394
+ // Portal to a specific CSS selector
395
+ <BlueprintDocumentAssembly portalTarget="#modal-root" />
396
+
397
+ // Portal to a specific element
398
+ <BlueprintDocumentAssembly portalTarget={myContainerRef.current} />
399
+ ```
400
+
401
+ When portaled, the modal uses `fixed` positioning instead of `absolute`.
402
+
403
+ ### Panel Visibility
404
+
405
+ Hide or show individual UI sections:
406
+
407
+ ```tsx
408
+ // Hide the left panel (steps) — show only the document preview
409
+ <BlueprintDocumentAssembly visibility={{ leftPanel: false }} />
410
+
411
+ // Hide metrics footer and progress bar
412
+ <BlueprintDocumentAssembly visibility={{ metricsFooter: false, progressBar: false }} />
413
+
414
+ // Hide everything except the document
415
+ <BlueprintDocumentAssembly
416
+ visibility={{ leftPanel: false, header: false, metricsFooter: false, progressBar: false }}
417
+ />
418
+ ```
419
+
420
+ When only one panel is visible, it automatically takes full width.
421
+
422
+ ### Custom Render Slots
423
+
424
+ Replace the header or footer with custom UI:
425
+
426
+ ```tsx
427
+ <BlueprintDocumentAssembly
428
+ renderSlots={{
429
+ header: ({ currentStep, totalSteps, progress }) => (
430
+ <div className="mb-8">
431
+ <h2>Custom Header — Step {currentStep + 1}/{totalSteps}</h2>
432
+ <progress value={progress} max={100} />
433
+ </div>
434
+ ),
435
+ footer: ({ metrics }) => (
436
+ <div className="p-4 text-center text-sm">
437
+ {metrics.tokens} tokens &middot; ${metrics.cost.toFixed(4)}
438
+ </div>
439
+ ),
440
+ }}
441
+ />
442
+ ```
443
+
444
+ ### SSR / Next.js Compatibility
445
+
446
+ 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.
447
+
448
+ ```tsx
449
+ // Safe to import in Next.js App Router
450
+ import { BlueprintDocumentAssembly } from '@chrisflippen/blueprint-document-assembly';
451
+
452
+ // The injectStyle utility is also exported for custom use
453
+ import { injectStyle } from '@chrisflippen/blueprint-document-assembly';
454
+ injectStyle('my-styles', '.my-class { color: red; }');
455
+ ```
456
+
217
457
  ## Accessibility
218
458
 
219
459
  ### Reduced Motion
@@ -311,14 +551,21 @@ Or override via the `layout` prop:
311
551
  | `onPause` | `() => void` | - | Paused callback |
312
552
  | `onResume` | `() => void` | - | Resumed callback |
313
553
  | `onReset` | `() => void` | - | Reset callback |
554
+ | `externalMode` | `boolean` | `false` | Disable internal timer; drive state via ref methods |
555
+ | `onError` | `(error) => void` | - | Step error callback (external mode) |
556
+ | `portalTarget` | `boolean \| string \| HTMLElement` | - | Portal target for completion modal |
557
+ | `visibility` | `VisibilityConfig` | all `true` | Show/hide header, panels, footer, progress bar |
558
+ | `renderSlots` | `RenderSlots` | - | Custom header/footer render functions |
559
+ | `errorFallback` | `ReactNode \| (error, reset) => ReactNode` | - | Error boundary fallback UI |
560
+ | `onRenderError` | `(error, errorInfo) => void` | - | Render error callback |
314
561
 
315
562
  ### Exports
316
563
 
317
564
  **Components:**
318
- `BlueprintDocumentAssembly`, `StepItem`, `SubStepItem`, `LogLine`, `LogContainer`, `DocumentPreview`, `DocumentLine`, `WholeDocumentView`, `WholeDocumentContent`
565
+ `BlueprintDocumentAssembly`, `StepItem`, `SubStepItem`, `LogLine`, `LogContainer`, `DocumentPreview`, `DocumentLine`, `WholeDocumentView`, `WholeDocumentContent`, `ErrorBoundary`
319
566
 
320
567
  **Theme:**
321
- `ThemeProvider`, `useTheme`, `useThemeOptional`, `resolveTheme`, `getThemeSafelist`
568
+ `ThemeProvider`, `useTheme`, `useThemeOptional`, `resolveTheme`, `buildCssVars`, `resolveColorValue`, `colorMix`, `TAILWIND_COLOR_MAP`, ~~`getThemeSafelist`~~ (deprecated)
322
569
 
323
570
  **Animation:**
324
571
  `AnimationProvider`, `useAnimation`, `useAnimationOptional`, `SMOOTH_PRESET`, `SNAPPY_PRESET`, `CINEMATIC_PRESET`, `MINIMAL_PRESET`
@@ -333,10 +580,10 @@ Or override via the `layout` prop:
333
580
  `DEFAULT_STEPS`, `DEFAULT_STEP_LOGS`, `DEFAULT_SUBSTEP_LOGS`, `DEFAULT_LABELS`, `DEFAULT_THEME`, `STEP_ICON_MAP`
334
581
 
335
582
  **Utilities:**
336
- `createStep`, `createSubStep`, `createDocumentSection`, `resetStepCounter`, `resolveContent`
583
+ `createStep`, `createSubStep`, `createDocumentSection`, `resetStepCounter`, `resolveContent`, `injectStyle`
337
584
 
338
585
  **Types:**
339
- `Step`, `SubStep`, `LogEntry`, `DocumentMetrics`, `DocumentIds`, `DocumentSection`, `DocumentSubsection`, `ClassNameSlots`, `ThemeConfig`, `AnimationTimings`, `AnimationPreset`, `ResolvedTheme`, `ResolvedThemeColors`, `LabelConfig`, `LayoutConfig`, `UseDocumentAssemblyOptions`, `UseDocumentAssemblyReturn`, `BlueprintDocumentAssemblyRef`, `BlueprintDocumentAssemblyProps`, and all component prop types
586
+ `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
340
587
 
341
588
  ## Architecture
342
589
 
@@ -344,22 +591,24 @@ Or override via the `layout` prop:
344
591
  src/
345
592
  ├── components/
346
593
  │ ├── BlueprintDocumentAssembly.tsx # Main component (providers, responsive, ARIA)
347
- │ ├── StepItem.tsx # Step card with theme context
594
+ │ ├── StepItem.tsx # Step card with error state support
348
595
  │ ├── SubStepItem.tsx # Substep with ARIA listitem
349
596
  │ ├── LogComponents.tsx # Log display with ARIA log role
350
597
  │ ├── DocumentPreview.tsx # Progressive document builder
351
598
  │ ├── DocumentLine.tsx # Typewriter text (reduced motion aware)
352
- │ ├── WholeDocumentView.tsx # Completion overlay (GPU-safe glow)
353
- └── WholeDocumentContent.tsx # Static document renderer
599
+ │ ├── WholeDocumentView.tsx # Completion overlay with portal support
600
+ ├── WholeDocumentContent.tsx # Static document renderer with contentRef
601
+ │ └── ErrorBoundary.tsx # React error boundary with custom fallback
354
602
  ├── theme/
355
- │ ├── ThemeContext.tsx # ThemeProvider, resolveTheme, safelist
603
+ │ ├── ThemeContext.tsx # ThemeProvider, resolveTheme (incl. error status)
604
+ │ ├── css-vars.ts # CSS variable helpers, Tailwind color lookup
356
605
  │ └── index.ts
357
606
  ├── animation/
358
607
  │ ├── presets.ts # SMOOTH, SNAPPY, CINEMATIC, MINIMAL
359
608
  │ ├── AnimationContext.tsx # AnimationProvider, useAnimation
360
609
  │ └── index.ts
361
610
  ├── hooks/
362
- │ ├── useDocumentAssembly.ts # Headless assembly hook
611
+ │ ├── useDocumentAssembly.ts # Headless hook with external mode + snapshots
363
612
  │ ├── useReducedMotion.ts # prefers-reduced-motion detection
364
613
  │ ├── useResponsiveLayout.ts # Breakpoint detection
365
614
  │ └── index.ts
@@ -372,11 +621,12 @@ src/
372
621
  ├── utils/
373
622
  │ ├── factories.ts # createStep, createSubStep, etc.
374
623
  │ ├── content-resolvers.ts # Shared resolveContent utility
624
+ │ ├── inject-style.ts # SSR-safe style injection utility
375
625
  │ └── index.ts
376
626
  ├── constants/
377
627
  │ ├── default-labels.ts
378
628
  │ └── default-theme.ts
379
- ├── types.ts
629
+ ├── types.ts # All types incl. AssemblySnapshot, VisibilityConfig, RenderSlots
380
630
  ├── constants.ts
381
631
  ├── default-steps.ts
382
632
  └── index.tsx # Public API