npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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 by CompletenessResult from site-config-core
  • useSitePreview hook — fetches a SiteRenderModel via a consumer-provided fetch function; re-fetches on refreshTrigger change

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-dom

Components

<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 — no onRefresh call.
  • refreshTrigger={1} on rerender — onRefresh is called once.
  • refreshTrigger={2} on next rerender — onRefresh called 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-body

Styling

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.