Embeddable React components for the Potion API - AI-powered beverage formulation platform
npm install @potion5/reactEmbeddable React components for the Potion API. Build beverage formulation features into your application with pre-built, customizable components.
``bash`
npm install @potion5/react @potion5/sdkor
yarn add @potion5/react @potion5/sdkor
pnpm add @potion5/react @potion5/sdk
`tsx
import { PotionProvider, FormulationBuilder } from '@potion5/react';
import '@potion5/react/styles';
function App() {
return (
onComplete={(formulation) => {
console.log('Created formulation:', formulation);
}}
/>
);
}
`
`html`
Create and edit beverage formulations with AI assistance.
`tsx`
formulationId="..." // Required for edit mode
category="energy-drink"
enableAI={true}
onComplete={(formulation) => void}
onCancel={() => void}
onChange={(formulation) => void}
showProperties={true}
/>
Smart ingredient finder with allergen warnings and GRAS status.
`tsx`
grasOnly={false}
excludeAllergens={['peanuts', 'milk']}
maxResults={20}
showDetails={true}
placeholder="Search ingredients..."
compact={false}
/>
Comprehensive compliance checking with checklist generation.
`tsx`
generateChecklist={true}
generateLabelingRequirements={true}
includeRegulatoryCitations={true}
jurisdictions={['CA', 'NY']}
onChecklistGenerated={(items) => void}
onLabelingGenerated={(requirements) => void}
onExport={(format, data) => void}
/>
Full SOP lifecycle management - create, view, and edit.
`tsx`
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}
/>
Unified AI chat interface for formulation assistance.
`tsx`
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"
/>
Override CSS custom properties to match your brand:
`css`
:root {
--potion-color-primary: #your-brand-color;
--potion-color-accent: #your-accent-color;
--potion-font-family: 'Your Font', sans-serif;
--potion-radius-default: 8px;
}
`tsx`
theme={{
mode: 'light', // 'light' | 'dark' | 'system'
primaryColor: '#6366f1',
accentColor: '#8b5cf6',
borderRadius: 'md', // 'none' | 'sm' | 'md' | 'lg' | 'full'
fontFamily: 'Inter, sans-serif',
}}
>
{/ Your components /}
`tsx
function ThemeSwitcher() {
const { theme, setTheme } = usePotionTheme();
return (
);
}
`
`tsx`
const { formulation, isLoading, error, refetch, update, remove } = useFormulation({
id: 'formulation-id',
autoFetch: true,
});
`tsx
const { generate, isGenerating, error, generatedFormulation } = useFormulationGenerate();
await generate({
category: 'energy-drink',
description: 'A refreshing citrus energy drink',
constraints: {
max_ingredients: 10,
exclude_allergens: ['milk'],
},
});
`
`tsx`
const { query, setQuery, results, isSearching, clear } = useIngredientSearch({
debounce: 300,
limit: 20,
grasOnly: true,
});
`tsx`
const { result, isChecking, error, check } = useComplianceCheck({
formulationId: 'xxx',
autoCheck: false,
});
`tsx`
const { checklist, isGenerating, generate, updateItem, exportChecklist } = useComplianceChecklist({
formulationId: 'xxx',
includeFederal: true,
states: ['CA', 'NY'],
});
`tsx`
const { requirements, isLoading, isGenerating, fetch, generate } = useLabelingRequirements({
formulationId: 'xxx',
autoFetch: false,
});
`javascript`
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),
});
`javascript`
instance.updateProps({
formulationId: 'new-id',
mode: 'edit',
});
`javascript`
instance.destroy();
// or
PotionEmbed.destroy('#my-container');
// or destroy all
PotionEmbed.destroyAll();
`javascript`
console.log(PotionEmbed.components);
// ['FormulationBuilder', 'IngredientSearch', 'ComplianceChecker', 'SOPEditor', 'PotionAssistant']
Wrap components with error boundary for production-ready error handling:
`tsx
import { PotionErrorBoundary } from '@potion5/react';
onError={(error, errorInfo) => {
// Log to error reporting service
logError(error, errorInfo);
}}
showDetails={process.env.NODE_ENV === 'development'}
reportErrors={true}
reportEndpoint="/api/errors"
>
`
` Component: {details.componentName} Time: {details.timestamp}tsx`
Error: {details.error.message}
{details.errorInfo?.componentStack && (
{details.errorInfo.componentStack}
)}
)}
onRetry={() => console.log('Retrying...')}
>
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.
Test your integrations without hitting real APIs:
`tsx
import { MockPotionProvider, mockFormulation } from '@potion5/react/testing';
import { render, screen } from '@testing-library/react';
test('renders formulation builder', async () => {
render(
formulations: [mockFormulation({ name: 'Test Drink' })],
}}
config={{
delay: 0, // No delay for tests
debug: false,
}}
>
);
expect(await screen.findByText('Create Formulation')).toBeInTheDocument();
});
`
Create test data with customizable factories:
`tsx
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',
});
`
`tsx
import {
createPotionWrapper,
waitForMockDelay,
createMockHandler,
} from '@potion5/react/testing';
// Create wrapper for multiple tests
const wrapper = createPotionWrapper({
mockData: { formulations: [] },
});
render(
// Wait for async operations
await waitForMockDelay(100);
// Track callbacks
const onComplete = createMockHandler
expect(onComplete.calls).toHaveLength(1);
`
`tsx`
errors: {
formulations: new Error('Network error'),
compliance: 'Service unavailable',
},
}}
>
For non-technical teams or complete isolation, embed via iframe:
`html`
id="potion-embed"
src="https://embed.potion.com/iframe?apiKey=pk_live_xxx&component=FormulationBuilder"
style="width: 100%; height: 600px; border: none;"
>
`html`
src="https://embed.potion.com/iframe?apiKey=pk_live_xxx&component=FormulationBuilder&themeMode=dark&primaryColor=%236366f1"
>
Listen for events from the iframe:
`javascript
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();
`
| 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 |
`typescript
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' },
});
`
All components and hooks are fully typed. Import types as needed:
`tsx
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';
``
- Chrome (latest)
- Firefox (latest)
- Safari (latest)
- Edge (latest)
MIT