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

component-previewer

v0.2.0

Published

Render a single React Native component instantly in its app's real provider shell, without booting the app.

Readme

component-previewer

Render a single React Native component in isolation, in your app's real provider shell, with chosen props/state, without booting the rest of the app. Uses Storybook-compatible CSF (*.stories.tsx) with zero setup (no .rnstorybook/, no codegen manifest, no Metro wrapper), and runs on both surfaces: the iOS/Android simulator (true native) and a browser (via react-native-web).

The differentiator: your real AppShell wraps every story (single source of truth, can't drift), and CSF decorators run inside it. Edit a story's args live on-device with the built-in controls panel.

Install

pnpm add -D component-previewer        # or npm i -D / yarn add -D

Peer dependencies: react, react-native.

Use it in your own app (the contract)

  1. Expose your real provider shell as one component (theme, query client, i18n, safe-area, …):
    // AppProviders.tsx
    export function AppProviders({ children }) { /* your real providers */ }
  2. Write CSF stories (*.stories.tsx), the exact Storybook format, no import from this tool needed:
    // Button.stories.tsx
    import { Button } from './Button';
    export default { title: 'Button', component: Button, args: { label: 'Press me' } };
    export const Primary = { args: { label: 'Buy now' } };
    export const Disabled = { args: { disabled: true } };
    // optional per-story providers run INSIDE your real shell:
    // export const Boxed = { args: {...}, decorators: [(Story) => <View>…<Story/></View>] };
  3. Wire the boot flag at your app root (~10 lines). Discovery is zero-codegen:
    import { Previewer, fromRequireContext } from 'component-previewer';
    
    const ctx = require.context('./', true, /\.stories\.tsx$/);            // Metro / native + Expo web
    const PREVIEW = process.env.EXPO_PUBLIC_PREVIEW === '1';
    const Shell = ({ children }) => <AppProviders>{children}</AppProviders>;
    
    export default function App() {
      return PREVIEW
        ? <Previewer stories={fromRequireContext(ctx)} shell={Shell} />
        : <YourApp />;
    }
    On a pure-Vite host, use fromGlob(import.meta.glob('./**/*.stories.tsx', { eager: true })) instead of fromRequireContext.

Live props controls

Open any story and tap Controls to edit its args on-device and re-render live (the Storybook-controls equivalent). Control kinds are inferred from the arg values with zero config:

| value type | control | | ---------------------- | -------------------------------- | | string | text field | | string like #rrggbb| color swatch + hex field | | number | numeric field | | boolean | switch |

Functions, plain objects, arrays, null/undefined are skipped by inference (passed through untouched). Declare optional CSF argTypes to force a control — a dropdown, a range, an explicit color, or an editable object/array:

export default {
  title: 'Badge',
  component: Badge,
  args: { tone: 'neutral', accent: '#3366ff', data: { rows: 3 } },
  argTypes: {
    tone: { control: 'select', options: ['neutral', 'success', 'danger'] },
    size: { control: 'range', min: 16, max: 256, step: 8 }, // clamped number field
    accent: { control: 'color' },                            // swatch + hex (also auto-inferred for #hex values)
    data: { control: 'object' },                             // JSON editor (objects + arrays), commits on valid JSON
  },
};

A Reset button restores the story's declared args; Copy logs the current args as JSON (pass onCopyArgs to wire a real clipboard). Edits never leak between stories.

Args persistence

Edited args persist per story id across list ↔ stage navigation and Fast Refresh, with zero dependencies (an in-memory session store). To also survive a cold reload, pass a persistence adapter — the package never imports a storage lib itself:

import { Previewer, type ArgsPersistence } from 'component-previewer';

// any synchronous get/set/remove around AsyncStorage, MMKV, a file, etc.
const persistence: ArgsPersistence = { get, set, remove };

<Previewer stories={…} shell={Shell} persistence={persistence} />

Globals — parameterize the one shell (theme, locale, …)

Args are per-component props. Globals are toolbar-level params that vary the shell across every story — the canonical case is light/dark. Declare them on <Previewer globalTypes> and read them in your shell:

const Shell: ShellComponent = ({ children, globals }) => (
  <AppProviders mode={globals?.theme === 'light' ? 'light' : 'dark'}>{children}</AppProviders>
);

<Previewer
  stories={…}
  shell={Shell}
  globalTypes={{ theme: { options: ['dark', 'light'], default: 'dark' } }}
/>

A Globals section appears at the top of the Controls panel; switching theme re-renders the current story through the same shell. This is what removes the two classic sources of duplication:

  • No forked light/dark stories. One story, toggled — not export const Light + export const Dark with a hand-written dark decorator.
  • No separate "lab" component with its own switchers. Variant axes (a card's state, a chart's data) belong in args/argTypes and drive the Controls panel; theme/locale belong in globalTypes. There's nothing left for a bespoke lab harness to do.

Single source of truth (no shell drift)

The differentiator is that your real AppProviders is the shell, so the preview can't drift from the app. Globals exist specifically so you never have to fork that shell into a lighter "preview-only" copy to vary the theme — vary it with a global instead. The package deliberately can't auto-detect a forked shell (it can't see your app root), so the contract is the guard: import your real providers as shell; parameterize with globalTypes; never re-author.

Run the example

The repo ships an Expo example app.

Native (simulator / device):

cd example
EXPO_PUBLIC_PREVIEW=1 npx expo start   # press i, or scan with Expo Go

Boots into a searchable picker of discovered stories; selecting one renders it natively inside the real shell. This is the surface that shows Skia/glass/native modules (the example's BlurCard story renders a real native blur).

Browser (no simulator):

EXPO_PUBLIC_PREVIEW=1 npx expo start --web

Same picker via react-native-web. Native modules can't render here (documented cap).

Without the flag, the app boots its normal screen instead. Deep-link straight into one story with EXPO_PUBLIC_PREVIEW_STORY=button--primary.

API

  • fromRequireContext(ctx) / fromGlob(glob)StoryEntry[] — zero-codegen discovery.
  • <Previewer stories={…} shell={…} initialStoryId? /> — the picker + stage UI; initialStoryId (or EXPO_PUBLIC_PREVIEW_STORY) deep-links straight into one story.
  • inferControls(args, argTypes?)Control[] — pure; the control descriptors that drive the on-device panel.
  • composeStory(entry, Shell?, args?)Shell (outer) > decorators (inner) > story; pure. Pass args to render an override (the live-edit path).
  • parseCsfModule(id, module)StoryEntry[] — parse one CSF module to entries.

Exported types: StoryEntry, Meta, Story, Args, ArgType, ArgTypes, ControlKind, Control, Decorator, ShellComponent, RequireContext, GlobModules, CsfModule.

Develop the library

This is a pnpm workspace: the example/ app consumes the library source via Metro (bob's metro-config + the source export condition), so editing src/ hot-reloads in the example, no rebuild or repack.

pnpm install     # once — links the workspace
pnpm lint        # eslint (typescript-eslint)
pnpm typecheck   # tsc --noEmit
pnpm test        # vitest (CSF parsing, discovery, shell compose, control inference)
pnpm build       # react-native-builder-bob → lib/module (ESM) + lib/typescript (.d.ts)

Build/packaging: react-native-builder-bob (ESM module + typescript targets), exports map with a source condition, MIT licensed. See CONTRIBUTING.md.

Status

  • Core: CSF parsing, zero-codegen discovery (require.context / import.meta.glob), registry, real-shell compose. Pure and unit-tested.
  • Native backend: boot-flag entry swap + in-app picker + real-shell render + deep-link, verified on the iOS simulator (including a native expo-blur story).
  • Web backend: renders the same CSF stories via react-native-web (native-module cap documented).
  • Live props controls: value-inferred text/number/boolean/color + CSF argTypes select/range/color/object, on-device, live. Edits persist per story (session + optional cold-reload adapter); Copy/Reset.
  • Roadmap: TS-type-driven prop controls (auto-infer from prop types), VS Code extension, more CSF decorator edge cases.

License

MIT