@bernierllc/site-preview-ui
v0.1.1
Published
React components and hooks for live site config preview: brand swatches, completeness progress, and iframe embedding.
Downloads
440
Readme
@bernierllc/site-preview-ui
React components and hooks for live site configuration preview — brand swatches, completeness progress, and iframe embedding.
Overview
This package provides headless-friendly React components for previewing a SiteConfig as it is being built by an onboarding agent or admin UI. The centrepiece is <SitePreviewCard>, which generalises the nevarPro/components/ai/site-card.tsx auto-refresh pattern into a reusable, framework-agnostic component.
Key features:
<SitePreviewCard>— live summary card (logo, name, tagline, color swatches, section list, completeness bar)<SiteFramePreview>— iframe wrapper for the rendered live site, with open-in-new-tab control<ThemeSwatches>— standalone primary / secondary / accent color display<CompletenessBar>— 6-checkpoint progress bar driven byCompletenessResultfromsite-config-coreuseSitePreviewhook — fetches aSiteRenderModelvia a consumer-provided fetch function; re-fetches onrefreshTriggerchange
All data is prop-injected or fetched via a consumer-provided function — no built-in network calls, no server coupling, browser-safe.
Installation
npm install @bernierllc/site-preview-ui
# Peer dependencies (if not already installed):
npm install react react-domComponents
<SitePreviewCard>
The main preview card. Accepts either a raw SiteConfig (computed locally) or a pre-fetched SiteRenderModel. Automatically swaps to <SiteFramePreview> when the site status is ready or published and a livePreviewUrl is provided.
Props
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| config | SiteConfig | No | Raw site config. Computes CSS variables locally. |
| renderModel | SiteRenderModel | No | Pre-computed render model. Takes precedence over config. |
| livePreviewUrl | string | No | When provided and status is ready/published, renders an iframe instead of the card. |
| refreshTrigger | number | No | Increment this to call onRefresh. See refreshTrigger pattern. |
| onRefresh | () => void \| Promise<void> | No | Called when refreshTrigger increments. |
| className | string | No | CSS class added to the root element. |
Example
import { SitePreviewCard } from '@bernierllc/site-preview-ui';
function OnboardingPanel({ config, agentTurnCount, onReload, liveUrl }) {
return (
<SitePreviewCard
config={config}
livePreviewUrl={liveUrl}
refreshTrigger={agentTurnCount}
onRefresh={onReload}
className="panel__preview"
/>
);
}<SiteFramePreview>
Iframe wrapper for the rendered live site. Shows an accessible iframe and an open-in-new-tab anchor. Renders an error fallback when src is empty.
Props
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| src | string | Yes | — | URL to embed. |
| title | string | No | "Site preview" | Accessible iframe title. |
| height | string \| number | No | 600 | Iframe height. |
| openInNewTabLabel | string | No | "Open in new tab" | Label for the open-in-new-tab anchor. |
| className | string | No | — | CSS class added to the wrapper. |
Example
import { SiteFramePreview } from '@bernierllc/site-preview-ui';
<SiteFramePreview
src="https://acme.myapp.com"
title="Acme Plumbing — live site"
height={700}
openInNewTabLabel="View your site"
/><ThemeSwatches>
Renders primary, secondary, and accent color swatches from a SiteConfig['theme'] object.
Props
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| theme | SiteConfig['theme'] | Yes | — | The theme sub-object from a SiteConfig. |
| size | 'sm' \| 'md' \| 'lg' | No | 'md' | Swatch circle size. |
| showLabels | boolean | No | false | Show "Primary", "Secondary", "Accent" labels. |
| className | string | No | — | CSS class on the container. |
Example
import { ThemeSwatches } from '@bernierllc/site-preview-ui';
<ThemeSwatches theme={config.theme} showLabels size="lg" /><CompletenessBar>
6-checkpoint progress indicator driven by a CompletenessResult from @bernierllc/site-config-core.
Props
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| completeness | CompletenessResult | Yes | — | Result of completenessScore(config). |
| showLabels | boolean | No | false | Show checkpoint label text beside each dot. |
| showScore | boolean | No | false | Show aggregate percentage (e.g. "67%"). |
| className | string | No | — | CSS class on the container. |
Example
import { completenessScore } from '@bernierllc/site-config-core';
import { CompletenessBar } from '@bernierllc/site-preview-ui';
const result = completenessScore(config);
<CompletenessBar completeness={result} showScore showLabels />Hook
useSitePreview
Fetches a SiteRenderModel via a consumer-provided async function and exposes loading / error / refresh state. Re-fetches when refreshTrigger changes or when refresh() is called imperatively.
Options
| Option | Type | Required | Description |
|--------|------|----------|-------------|
| fetchFn | () => Promise<SiteRenderModel> | Yes | Async function that resolves the render model (e.g. a server action or fetch call). Memoize with useCallback to avoid unnecessary re-fetches. |
| refreshTrigger | number | No | Re-fetches when this value changes. |
| pollingIntervalMs | number | No | Poll on a fixed interval (milliseconds). |
Result
| Field | Type | Description |
|-------|------|-------------|
| renderModel | SiteRenderModel \| null | Most recent resolved model, or null before first fetch. |
| loading | boolean | true while a fetch is in-flight. |
| error | Error \| null | Last fetch error, wrapped in SitePreviewUIError, or null. |
| refresh | () => void | Imperatively trigger a re-fetch. |
Example
import { useSitePreview, SitePreviewCard } from '@bernierllc/site-preview-ui';
import { useCallback } from 'react';
function SiteDashboard({ siteId }: { siteId: string }) {
const fetchFn = useCallback(
() => fetch(`/api/sites/${siteId}/render-model`).then((r) => r.json()),
[siteId]
);
const { renderModel, loading, error } = useSitePreview({ fetchFn });
if (loading) return <div>Loading preview...</div>;
if (error) return <div>Error: {error.message}</div>;
if (!renderModel) return null;
return <SitePreviewCard renderModel={renderModel} />;
}refreshTrigger Pattern
The refreshTrigger prop (on <SitePreviewCard>) and option (on useSitePreview) implement the counter-increment refresh pattern used in the nevarPro onboarding agent: the parent tracks a counter and increments it after each agent turn; the component detects the change and calls onRefresh (or re-fetches). This avoids prop-drilling a boolean flag that needs to be reset.
import { useState, useCallback } from 'react';
import { SitePreviewCard, useSitePreview } from '@bernierllc/site-preview-ui';
function OnboardingView({ siteId }: { siteId: string }) {
const [agentTurnCount, setAgentTurnCount] = useState(0);
// Called after each agent streaming turn completes
const onAgentTurnComplete = useCallback(() => {
setAgentTurnCount((c) => c + 1);
}, []);
const fetchFn = useCallback(
() => fetch(`/api/sites/${siteId}/render`).then((r) => r.json()),
[siteId]
);
const { renderModel, refresh } = useSitePreview({
fetchFn,
refreshTrigger: agentTurnCount, // re-fetches when counter increments
});
return (
<SitePreviewCard
renderModel={renderModel ?? undefined}
refreshTrigger={agentTurnCount}
onRefresh={refresh} // card calls this when trigger increments
livePreviewUrl={`https://${siteId}.myapp.com`}
/>
);
}Behaviour:
refreshTrigger={0}on initial render — noonRefreshcall.refreshTrigger={1}on rerender —onRefreshis called once.refreshTrigger={2}on next rerender —onRefreshcalled again.
Error Handling
Fetch errors from useSitePreview are wrapped in SitePreviewUIError:
import { SitePreviewUIError } from '@bernierllc/site-preview-ui';
const { error } = useSitePreview({ fetchFn });
if (error instanceof SitePreviewUIError) {
console.error(error.code); // 'FETCH_FAILED'
console.error(error.cause); // original Error
console.error(error.context); // optional key-value context
}The error class follows the ES2022 Error.cause chaining pattern used across the BernierLLC package suite.
SiteRenderModel Type
Since SiteRenderModel is not defined in site-config-core (which only ships SiteConfig / CompletenessResult), this package defines and exports it:
export interface SiteRenderModel {
config: SiteConfig;
cssVariables: Record<string, string>; // from toCssVariables(config.theme)
}The cssVariables shape matches the output of toCssVariables from @bernierllc/site-config-core:
--color-primary, --color-secondary, --color-accent, --font-heading, --font-bodyStyling
Components ship with minimal inline styles (for swatch sizes and iframe dimensions only). All layout and colour classes follow BEM naming (site-preview-card__name, completeness-checkpoint--passed, etc.) and can be overridden via CSS. Pass className to any component for custom targeting.
CSS custom properties from SiteRenderModel.cssVariables are applied as inline styles on <SitePreviewCard>'s root element, so child elements can consume var(--color-primary) etc. without additional configuration.
Browser Safety
This package imports no Node.js built-ins and performs no built-in network requests. All data access is delegated to the consumer via prop injection or the fetchFn option. Safe to bundle for browser-only applications.
License
Copyright (c) 2025 Bernier LLC. Licensed to the client under a limited-use license. See LICENSE for details.
