React Adapter
The @presage-kit/react package provides React components and hooks for building adaptive interfaces.
Installation
Section intitulée « Installation »pnpm add @presage-kit/core @presage-kit/reactAdaptiveProvider
Section intitulée « AdaptiveProvider »Provides the AdaptiveClient to the entire component tree via React context.
import { AdaptiveProvider } from '@presage-kit/react'import { createAdaptiveClient, createLocalStorageDriver } from '@presage-kit/core'
const client = createAdaptiveClient({ rules: [/* ... */], persistence: { driver: createLocalStorageDriver('my-app') },})
function App() { return ( <AdaptiveProvider client={client}> <YourApp /> </AdaptiveProvider> )}Props:
| Prop | Type | Description |
|---|---|---|
client | AdaptiveClient | The client instance from createAdaptiveClient() |
children | ReactNode | Your application tree |
<Adaptive> Component
Section intitulée « <Adaptive> Component »Declares an adaptation point and renders the matching variant.
import { Adaptive, Variant } from '@presage-kit/react'
function Dashboard() { return ( <Adaptive id="dashboard-layout" defaultVariant="standard"> <Variant id="compact"> <CompactDashboard /> </Variant> <Variant id="standard"> <StandardDashboard /> </Variant> <Variant id="advanced"> <AdvancedDashboard /> </Variant> </Adaptive> )}<Adaptive> props:
| Prop | Type | Description |
|---|---|---|
id | string | Unique adaptation point identifier |
defaultVariant | string | Variant to show when no rule matches |
children | ReactNode | Must contain <Variant> components |
<Variant> props:
| Prop | Type | Description |
|---|---|---|
id | string | Variant identifier (referenced in rule actions) |
children | ReactNode | Content to render when this variant is selected |
The <Adaptive> component internally uses useAdaptive() and automatically tracks impressions when the selected variant changes.
<AdaptiveOrder> Component
Section intitulée « <AdaptiveOrder> Component »A generic ordering component for any list of items. Reorders, hides, or preserves the original order based on adaptation rules. Use this when you need adaptive ordering for non-navigation content such as feature cards, pricing tiers, or tab panels.
import { AdaptiveOrder, type Orderable } from '@presage-kit/react'
interface FeatureCard extends Orderable { title: string description: string}
const features: FeatureCard[] = [ { id: 'analytics', title: 'Analytics', description: 'Track user behavior' }, { id: 'automation', title: 'Automation', description: 'Automate repetitive tasks' }, { id: 'integrations', title: 'Integrations', description: 'Connect your tools' },]
function FeatureGrid() { return ( <AdaptiveOrder id="feature-highlights" items={features} renderItem={(item, index) => ( <div key={item.id} className="feature-card"> <h3>{item.title}</h3> <p>{item.description}</p> </div> )} /> )}With a reorder rule, items are rearranged based on user context:
{ id: 'power-user-features', adaptationId: 'feature-highlights', priority: 10, conditions: { all: [{ field: 'maturity', operator: 'eq', value: 'power' }], }, action: { type: 'reorder', order: ['automation', 'integrations', 'analytics'] },}With a hide rule, the entire list is hidden:
{ id: 'hide-features-for-new-users', adaptationId: 'feature-highlights', priority: 20, conditions: { all: [{ field: 'maturity', operator: 'eq', value: 'new' }], }, action: { type: 'hide' },}Props:
| Prop | Type | Description |
|---|---|---|
id | string | Adaptation point identifier |
items | T[] (extends Orderable) | Array of items, each with at least an id field |
renderItem | (item: T, index: number) => ReactNode | Render function for each item |
Orderable interface:
interface Orderable { id: string [key: string]: unknown}<AdaptiveNav> Component
Section intitulée « <AdaptiveNav> Component »A thin wrapper around <AdaptiveOrder> that provides the same API, specialized for navigation lists. Internally delegates to <AdaptiveOrder>.
Renders a list of navigation items that can be reordered by adaptation rules.
import { AdaptiveNav, type NavItem } from '@presage-kit/react'
interface MenuItem extends NavItem { label: string href: string icon: string}
const menuItems: MenuItem[] = [ { id: 'home', label: 'Home', href: '/', icon: 'home' }, { id: 'analytics', label: 'Analytics', href: '/analytics', icon: 'chart' }, { id: 'settings', label: 'Settings', href: '/settings', icon: 'gear' },]
function Sidebar() { return ( <AdaptiveNav id="sidebar-nav" items={menuItems} renderItem={(item, index) => ( <a key={item.id} href={item.href}> {item.icon} {item.label} </a> )} /> )}With a reorder rule, the navigation order changes based on user context:
{ id: 'power-user-nav-order', adaptationId: 'sidebar-nav', priority: 10, conditions: { all: [{ field: 'maturity', operator: 'eq', value: 'power' }], }, action: { type: 'reorder', order: ['analytics', 'home', 'settings'] },}With a hide rule, the entire navigation is hidden:
{ id: 'hide-nav-for-new-users', adaptationId: 'sidebar-nav', priority: 20, conditions: { all: [{ field: 'maturity', operator: 'eq', value: 'new' }], }, action: { type: 'hide' },}Props:
| Prop | Type | Description |
|---|---|---|
id | string | Adaptation point identifier |
items | T[] (extends NavItem) | Array of items, each with at least an id field |
renderItem | (item: T, index: number) => ReactNode | Render function for each item |
NavItem interface:
interface NavItem { id: string [key: string]: unknown}useAdaptive() Hook
Section intitulée « useAdaptive() Hook »The primary hook for resolving adaptation points. Returns the selected variant, full resolution details, context, and a scoped track function.
import { useAdaptive } from '@presage-kit/react'
function FeatureDiscovery() { const { selectedVariant, resolution, context, track } = useAdaptive('feature-discovery', { variants: ['tooltip', 'banner', 'modal'] as const, defaultVariant: 'tooltip', })
// Track a conversion event scoped to this adaptation point function handleDismiss() { track('feature_discovery_dismissed') }
switch (selectedVariant) { case 'tooltip': return <Tooltip onDismiss={handleDismiss} /> case 'banner': return <Banner onDismiss={handleDismiss} /> case 'modal': return <Modal onDismiss={handleDismiss} /> }}Parameters:
| Param | Type | Description |
|---|---|---|
adaptationId | string | Unique identifier for the adaptation point |
config.variants | readonly string[] | Available variant IDs |
config.defaultVariant | string | Fallback variant |
config.strategy | Strategy | Optional. Defaults to { type: 'rules' } |
Return type:
| Field | Type | Description |
|---|---|---|
selectedVariant | V | The selected variant (type-narrowed to your variant union) |
resolution | ResolvedAdaptation | Full resolution details (strategy, reason, timestamp) |
context | UserContext | Current user context |
track | (event: string, properties?: Record<string, unknown>) => void | Track function scoped to this adaptation point |
TypeScript generics: The hook infers the variant type from variants:
// selectedVariant is typed as 'tooltip' | 'banner' | 'modal'const { selectedVariant } = useAdaptive('feature-discovery', { variants: ['tooltip', 'banner', 'modal'] as const, defaultVariant: 'tooltip',})Automatic impression tracking: When the selected variant changes, useAdaptive() automatically tracks an presage:impression event with the adaptation ID, variant, and strategy.
useVariant() Hook
Section intitulée « useVariant() Hook »A lighter hook that returns only the selected variant string. Use this when you don’t need the full resolution details.
import { useVariant } from '@presage-kit/react'
function CTAButton() { const variant = useVariant('cta-style', { variants: ['primary', 'secondary', 'subtle'] as const, defaultVariant: 'primary', })
return <button className={`cta cta--${variant}`}>Get Started</button>}Parameters: Same as useAdaptive().
Return type: V — The selected variant string.
useAdaptiveClient() Hook
Section intitulée « useAdaptiveClient() Hook »Returns the AdaptiveClient instance from context. Useful for imperative operations like tracking and identifying.
import { useAdaptiveClient } from '@presage-kit/react'
function TrackingButton() { const client = useAdaptiveClient()
function handleClick() { client.track('feature_used', { featureId: 'export' }) }
return <button onClick={handleClick}>Export</button>}Return type: AdaptiveClient
Throws an error if called outside of <AdaptiveProvider>.
Full Example: SaaS Dashboard
Section intitulée « Full Example: SaaS Dashboard »This example shows an adaptive sidebar and onboarding flow for a SaaS dashboard.
import { createAdaptiveClient, createLocalStorageDriver } from '@presage-kit/core'
export const client = createAdaptiveClient({ rules: [ // Sidebar: prioritize analytics for power users { id: 'power-user-sidebar', adaptationId: 'sidebar', priority: 10, conditions: { all: [{ field: 'maturity', operator: 'eq', value: 'power' }], }, action: { type: 'reorder', order: ['analytics', 'dashboards', 'settings', 'home'] }, }, // Onboarding: guided tour for new users { id: 'new-user-tour', adaptationId: 'onboarding', priority: 10, conditions: { all: [{ field: 'maturity', operator: 'eq', value: 'new' }], }, action: { type: 'show', variantId: 'guided-tour' }, }, // Onboarding: hide for experienced users { id: 'experienced-no-onboarding', adaptationId: 'onboarding', priority: 5, conditions: { any: [ { field: 'maturity', operator: 'eq', value: 'active' }, { field: 'maturity', operator: 'eq', value: 'power' }, ], }, action: { type: 'hide' }, }, ], persistence: { driver: createLocalStorageDriver('saas-demo'), },})import { AdaptiveProvider } from '@presage-kit/react'import { client } from './lib/adaptive-client'import { Sidebar } from './components/Sidebar'import { Onboarding } from './components/Onboarding'import { Dashboard } from './components/Dashboard'
export function App() { return ( <AdaptiveProvider client={client}> <div style={{ display: 'flex' }}> <Sidebar /> <main> <Onboarding /> <Dashboard /> </main> </div> </AdaptiveProvider> )}import { AdaptiveNav, type NavItem } from '@presage-kit/react'
interface SidebarItem extends NavItem { label: string href: string}
const items: SidebarItem[] = [ { id: 'home', label: 'Home', href: '/' }, { id: 'dashboards', label: 'Dashboards', href: '/dashboards' }, { id: 'analytics', label: 'Analytics', href: '/analytics' }, { id: 'settings', label: 'Settings', href: '/settings' },]
export function Sidebar() { return ( <nav> <AdaptiveNav id="sidebar" items={items} renderItem={(item) => ( <a key={item.id} href={item.href}> {item.label} </a> )} /> </nav> )}import { Adaptive, Variant } from '@presage-kit/react'
export function Onboarding() { return ( <Adaptive id="onboarding" defaultVariant="standard"> <Variant id="guided-tour"> <div> <h2>Welcome! Let us show you around.</h2> <p>Follow the steps to get set up.</p> </div> </Variant> <Variant id="standard"> <div> <h2>What is new</h2> <p>Check out the latest features.</p> </div> </Variant> </Adaptive> )}