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

@potion5/react

v1.0.0

Published

Embeddable React components for the Potion API - AI-powered beverage formulation platform

Downloads

97

Readme

@potion5/react

Embeddable React components for the Potion API. Build beverage formulation features into your application with pre-built, customizable components.

Installation

npm install @potion5/react @potion5/sdk
# or
yarn add @potion5/react @potion5/sdk
# or
pnpm add @potion5/react @potion5/sdk

Quick Start

React Applications

import { PotionProvider, FormulationBuilder } from '@potion5/react';
import '@potion5/react/styles';

function App() {
  return (
    <PotionProvider apiKey="pk_live_your_api_key">
      <FormulationBuilder
        enableAI={true}
        onComplete={(formulation) => {
          console.log('Created formulation:', formulation);
        }}
      />
    </PotionProvider>
  );
}

Vanilla JavaScript (Non-React Apps)

<script src="https://cdn.potion.com/embed.js"></script>
<div id="potion-container"></div>
<script>
  PotionEmbed.create({
    apiKey: 'pk_live_your_api_key',
    container: '#potion-container',
    component: 'FormulationBuilder',
    props: {
      enableAI: true,
      onComplete: function(formulation) {
        console.log('Created:', formulation);
      }
    },
    theme: {
      mode: 'light',
      primaryColor: '#6366f1'
    }
  });
</script>

Components

FormulationBuilder

Create and edit beverage formulations with AI assistance.

<FormulationBuilder
  mode="create" // 'create' | 'edit'
  formulationId="..." // Required for edit mode
  category="energy-drink"
  enableAI={true}
  onComplete={(formulation) => void}
  onCancel={() => void}
  onChange={(formulation) => void}
  showProperties={true}
/>

IngredientSearch

Smart ingredient finder with allergen warnings and GRAS status.

<IngredientSearch
  onSelect={(ingredient) => void}
  grasOnly={false}
  excludeAllergens={['peanuts', 'milk']}
  maxResults={20}
  showDetails={true}
  placeholder="Search ingredients..."
  compact={false}
/>

ComplianceChecker

Comprehensive compliance checking with checklist generation.

<ComplianceChecker
  formulationId="..."
  generateChecklist={true}
  generateLabelingRequirements={true}
  includeRegulatoryCitations={true}
  jurisdictions={['CA', 'NY']}
  onChecklistGenerated={(items) => void}
  onLabelingGenerated={(requirements) => void}
  onExport={(format, data) => void}
/>

SOPEditor

Full SOP lifecycle management - create, view, and edit.

<SOPEditor
  mode="create" // 'create' | 'view' | 'edit'
  formulationId="..."
  sopId="..." // Required for view/edit
  scale="pilot" // 'lab' | 'pilot' | 'commercial'
  generateWithAI={true}
  showCriticalControlPoints={true}
  onSave={(sop) => void}
  onPublish={(sop) => void}
  onStepComplete={(stepNumber) => void}
  onExport={(format, blob) => void}
/>

PotionAssistant

Unified AI chat interface for formulation assistance.

<PotionAssistant
  formulationId="..." // Optional context
  position="inline" // 'bottom-right' | 'bottom-left' | 'inline' | 'fullscreen'
  defaultExpanded={true}
  placeholder="Ask anything..."
  welcomeMessage="Hi! I'm your formulation assistant."
  suggestedPrompts={[
    "What's the shelf life?",
    "Check compliance issues"
  ]}
  enableVisualization={true}
  enableActions={true}
  onAction={(action, data) => void}
  height="600px"
/>

Theming

CSS Variables

Override CSS custom properties to match your brand:

:root {
  --potion-color-primary: #your-brand-color;
  --potion-color-accent: #your-accent-color;
  --potion-font-family: 'Your Font', sans-serif;
  --potion-radius-default: 8px;
}

Theme Provider

<PotionProvider
  apiKey="pk_live_xxx"
  theme={{
    mode: 'light', // 'light' | 'dark' | 'system'
    primaryColor: '#6366f1',
    accentColor: '#8b5cf6',
    borderRadius: 'md', // 'none' | 'sm' | 'md' | 'lg' | 'full'
    fontFamily: 'Inter, sans-serif',
  }}
>
  {/* Your components */}
</PotionProvider>

Dynamic Theme Updates

function ThemeSwitcher() {
  const { theme, setTheme } = usePotionTheme();

  return (
    <button onClick={() => setTheme({ mode: 'dark' })}>
      Toggle Dark Mode
    </button>
  );
}

Hooks

useFormulation

const { formulation, isLoading, error, refetch, update, remove } = useFormulation({
  id: 'formulation-id',
  autoFetch: true,
});

useFormulationGenerate

const { generate, isGenerating, error, generatedFormulation } = useFormulationGenerate();

await generate({
  category: 'energy-drink',
  description: 'A refreshing citrus energy drink',
  constraints: {
    max_ingredients: 10,
    exclude_allergens: ['milk'],
  },
});

useIngredientSearch

const { query, setQuery, results, isSearching, clear } = useIngredientSearch({
  debounce: 300,
  limit: 20,
  grasOnly: true,
});

useComplianceCheck

const { result, isChecking, error, check } = useComplianceCheck({
  formulationId: 'xxx',
  autoCheck: false,
});

useComplianceChecklist

const { checklist, isGenerating, generate, updateItem, exportChecklist } = useComplianceChecklist({
  formulationId: 'xxx',
  includeFederal: true,
  states: ['CA', 'NY'],
});

useLabelingRequirements

const { requirements, isLoading, isGenerating, fetch, generate } = useLabelingRequirements({
  formulationId: 'xxx',
  autoFetch: false,
});

Vanilla JS Embed API

Create Instance

const instance = PotionEmbed.create({
  apiKey: 'pk_live_xxx',
  container: '#my-container',
  component: 'FormulationBuilder',
  props: { enableAI: true },
  theme: { mode: 'dark' },
  onReady: () => console.log('Ready!'),
  onError: (error) => console.error(error),
});

Update Props

instance.updateProps({
  formulationId: 'new-id',
  mode: 'edit',
});

Destroy Instance

instance.destroy();
// or
PotionEmbed.destroy('#my-container');
// or destroy all
PotionEmbed.destroyAll();

Available Components

console.log(PotionEmbed.components);
// ['FormulationBuilder', 'IngredientSearch', 'ComplianceChecker', 'SOPEditor', 'PotionAssistant']

Error Handling

PotionErrorBoundary

Wrap components with error boundary for production-ready error handling:

import { PotionErrorBoundary } from '@potion5/react';

<PotionErrorBoundary
  fallback={<CustomErrorUI />}
  onError={(error, errorInfo) => {
    // Log to error reporting service
    logError(error, errorInfo);
  }}
  showDetails={process.env.NODE_ENV === 'development'}
  reportErrors={true}
  reportEndpoint="/api/errors"
>
  <FormulationBuilder />
</PotionErrorBoundary>

Custom Fallback with Error Details

<PotionErrorBoundary
  fallback={(details) => (
    <div>
      <h3>Error: {details.error.message}</h3>
      <p>Component: {details.componentName}</p>
      <p>Time: {details.timestamp}</p>
      {details.errorInfo?.componentStack && (
        <pre>{details.errorInfo.componentStack}</pre>
      )}
    </div>
  )}
  onRetry={() => console.log('Retrying...')}
>
  <FormulationBuilder />
</PotionErrorBoundary>

Note: The default fallback UI includes "Try Again" and "Reset" buttons automatically. For custom fallbacks, use the onRetry and onReset props on the error boundary.

Testing

MockPotionProvider

Test your integrations without hitting real APIs:

import { MockPotionProvider, mockFormulation } from '@potion5/react/testing';
import { render, screen } from '@testing-library/react';

test('renders formulation builder', async () => {
  render(
    <MockPotionProvider
      mockData={{
        formulations: [mockFormulation({ name: 'Test Drink' })],
      }}
      config={{
        delay: 0, // No delay for tests
        debug: false,
      }}
    >
      <FormulationBuilder />
    </MockPotionProvider>
  );

  expect(await screen.findByText('Create Formulation')).toBeInTheDocument();
});

Mock Factories

Create test data with customizable factories:

import {
  mockFormulation,
  mockIngredient,
  mockComplianceCheckResult,
  mockSOPDocument,
  mockAllergenIngredient,
} from '@potion5/react/testing';

// Basic mock
const formulation = mockFormulation();

// With overrides
const customFormulation = mockFormulation({
  name: 'Custom Energy Drink',
  category: 'energy-drink',
  status: 'approved',
});

// Allergen ingredient
const milkIngredient = mockAllergenIngredient('milk', {
  ingredient_name: 'Milk Protein Isolate',
});

Test Utilities

import {
  createPotionWrapper,
  waitForMockDelay,
  createMockHandler,
} from '@potion5/react/testing';

// Create wrapper for multiple tests
const wrapper = createPotionWrapper({
  mockData: { formulations: [] },
});

render(<FormulationBuilder />, { wrapper });

// Wait for async operations
await waitForMockDelay(100);

// Track callbacks
const onComplete = createMockHandler<Formulation>();
expect(onComplete.calls).toHaveLength(1);

Simulating Errors

<MockPotionProvider
  config={{
    errors: {
      formulations: new Error('Network error'),
      compliance: 'Service unavailable',
    },
  }}
>
  <FormulationBuilder />
</MockPotionProvider>

Iframe Embedding

For non-technical teams or complete isolation, embed via iframe:

Basic Iframe

<iframe
  id="potion-embed"
  src="https://embed.potion.com/iframe?apiKey=pk_live_xxx&component=FormulationBuilder"
  style="width: 100%; height: 600px; border: none;"
></iframe>

With Configuration

<iframe
  src="https://embed.potion.com/iframe?apiKey=pk_live_xxx&component=FormulationBuilder&themeMode=dark&primaryColor=%236366f1"
></iframe>

PostMessage Communication

Listen for events from the iframe:

import { PotionIframeClient } from '@potion5/react/iframe';

// Wait for iframe to be ready
const iframe = document.getElementById('potion-embed');
await PotionIframeClient.waitForReady(iframe);

// Listen for events
const unsubscribe = PotionIframeClient.listen((message) => {
  switch (message.type) {
    case 'potion:formulation:complete':
      console.log('Formulation created:', message.payload);
      break;
    case 'potion:error':
      console.error('Error:', message.payload);
      break;
  }
});

// Send commands to iframe
PotionIframeClient.updateConfig(iframe, {
  props: { mode: 'edit', formulationId: 'xxx' },
});

// Clean up
unsubscribe();

URL Parameters

| Parameter | Description | Example | |-----------|-------------|---------| | apiKey | API key (required) | pk_live_xxx | | component | Component name (required) | FormulationBuilder | | props | JSON props object | %7B%22enableAI%22%3Atrue%7D | | themeMode | Theme mode | light, dark, system | | primaryColor | Hex color | %236366f1 | | debug | Enable debug mode | 1 |

Building Iframe URL

import { buildIframeUrl } from '@potion5/react/iframe';

const url = buildIframeUrl('https://embed.potion.com/iframe', {
  apiKey: 'pk_live_xxx',
  component: 'FormulationBuilder',
  props: { enableAI: true },
  theme: { mode: 'dark', primaryColor: '#6366f1' },
});

TypeScript Support

All components and hooks are fully typed. Import types as needed:

import type {
  Formulation,
  Ingredient,
  ComplianceCheckResult,
  SOPDocument,
  FormulationBuilderProps,
  PotionErrorBoundaryProps,
  ErrorDetails,
} from '@potion5/react';

// Testing types
import type {
  MockData,
  MockConfig,
  MockPotionProviderProps,
} from '@potion5/react/testing';

// Iframe types
import type {
  IframeConfig,
  PotionMessage,
  PotionMessageType,
} from '@potion5/react/iframe';

Browser Support

  • Chrome (latest)
  • Firefox (latest)
  • Safari (latest)
  • Edge (latest)

License

MIT