Aller au contenu

Vue Adapter

The @presage-kit/vue package provides Vue 3 composables and a plugin for building adaptive interfaces.

Fenêtre de terminal
pnpm add @presage-kit/core @presage-kit/vue

Install the plugin to provide the AdaptiveClient to all components via Vue’s dependency injection.

src/main.ts
import { createApp } from 'vue'
import { AdaptivePlugin } from '@presage-kit/vue'
import { createAdaptiveClient, createLocalStorageDriver } from '@presage-kit/core'
import App from './App.vue'
const client = createAdaptiveClient({
rules: [/* ... */],
persistence: { driver: createLocalStorageDriver('my-app') },
})
const app = createApp(App)
app.use(AdaptivePlugin, { client })
app.mount('#app')

Options:

OptionTypeDescription
clientAdaptiveClientThe client instance from createAdaptiveClient()

The primary composable for resolving adaptation points. Returns reactive getters for the selected variant, resolution, and context.

<script setup lang="ts">
import { useAdaptive } from '@presage-kit/vue'
const { selectedVariant, resolution, context, track } = useAdaptive('onboarding', {
variants: ['guided-tour', 'standard', 'minimal'] as const,
defaultVariant: 'standard',
})
function handleComplete() {
track('onboarding_completed')
}
</script>
<template>
<div v-if="selectedVariant === 'guided-tour'">
<h2>Welcome! Let us show you around.</h2>
<button @click="handleComplete">Complete tour</button>
</div>
<div v-else-if="selectedVariant === 'standard'">
<h2>Welcome</h2>
</div>
<div v-else>
<p>Good to see you again.</p>
</div>
</template>

Parameters:

ParamTypeDescription
adaptationIdstringUnique identifier for the adaptation point
config.variantsreadonly string[]Available variant IDs
config.defaultVariantstringFallback variant
config.strategyStrategyOptional. Defaults to { type: 'rules' }

Return type:

The return object uses reactive getters, so values update automatically when the user context changes.

FieldTypeDescription
selectedVariantV (getter)The selected variant string
resolutionResolvedAdaptation (getter)Full resolution details
contextUserContext (getter)Current user context
track(event: string, properties?: Record<string, unknown>) => voidScoped track function

Automatic impression tracking: Like the React hook, useAdaptive() watches for variant changes and automatically tracks presage:impression events.

A lighter composable that returns only the selected variant string.

<script setup lang="ts">
import { useVariant } from '@presage-kit/vue'
const variant = useVariant('cta-style', {
variants: ['primary', 'secondary', 'subtle'] as const,
defaultVariant: 'primary',
})
</script>
<template>
<button :class="`cta cta--${variant}`">Get Started</button>
</template>

Parameters: Same as useAdaptive().

Return type: V — The selected variant string (reactive getter).

Returns the AdaptiveClient instance from the plugin injection. Use for imperative operations.

<script setup lang="ts">
import { useAdaptiveClient } from '@presage-kit/vue'
const client = useAdaptiveClient()
function handleExport() {
client.track('feature_used', { featureId: 'export' })
}
</script>
<template>
<button @click="handleExport">Export Data</button>
</template>

Throws an error if the AdaptivePlugin has not been installed.

Declarative components for rendering variant-based adaptations. Works the same as the React adapter.

<script setup lang="ts">
import { Adaptive, Variant } from '@presage-kit/vue'
</script>
<template>
<Adaptive id="dashboard-layout" default-variant="standard">
<Variant id="compact">
<CompactDashboard />
</Variant>
<Variant id="standard">
<StandardDashboard />
</Variant>
<Variant id="advanced">
<AdvancedDashboard />
</Variant>
</Adaptive>
</template>

<Adaptive> props:

PropTypeDescription
idstringUnique adaptation point identifier
default-variantstringVariant to show when no rule matches

<Variant> props:

PropTypeDescription
idstringVariant identifier (referenced in rule actions)

The <Adaptive> component internally uses useAdaptive() and automatically tracks impressions when the selected variant changes.

A generic ordering component for any list of items. Reorders, hides, or preserves the original order based on adaptation rules. Uses Vue scoped slots for rendering each item. Use this when you need adaptive ordering for non-navigation content such as feature cards, pricing tiers, or tab panels.

<script setup lang="ts">
import { AdaptiveOrder } from '@presage-kit/vue'
const features = [
{ id: 'analytics', title: 'Analytics', description: 'Track user behavior' },
{ id: 'automation', title: 'Automation', description: 'Automate repetitive tasks' },
{ id: 'integrations', title: 'Integrations', description: 'Connect your tools' },
]
</script>
<template>
<div class="feature-grid">
<AdaptiveOrder id="feature-highlights" :items="features" v-slot="{ item, index }">
<div class="feature-card">
<h3>{{ item.title }}</h3>
<p>{{ item.description }}</p>
</div>
</AdaptiveOrder>
</div>
</template>

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'] },
}

Props:

PropTypeDescription
idstringAdaptation point identifier
itemsT[] (extends Orderable)Array of items, each with at least an id field

Scoped slot:

FieldTypeDescription
itemTThe current item
indexnumberThe current item index in the ordered list

A thin wrapper around <AdaptiveOrder>, specialized for navigation lists. Internally delegates all logic to <AdaptiveOrder>.

Renders a list of navigation items that can be reordered or hidden by adaptation rules. Uses Vue scoped slots for rendering each item.

<script setup lang="ts">
import { AdaptiveNav } from '@presage-kit/vue'
const items = [
{ id: 'home', label: 'Home', href: '/' },
{ id: 'analytics', label: 'Analytics', href: '/analytics' },
{ id: 'settings', label: 'Settings', href: '/settings' },
]
</script>
<template>
<nav>
<AdaptiveNav id="sidebar-nav" :items="items" v-slot="{ item }">
<a :href="item.href">{{ item.label }}</a>
</AdaptiveNav>
</nav>
</template>

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'] },
}

Props:

PropTypeDescription
idstringAdaptation point identifier
itemsT[] (extends NavItem)Array of items, each with at least an id field

Scoped slot:

FieldTypeDescription
itemTThe current navigation item

Alternative: Manual AdaptiveNav with evaluateAction()

Section intitulée « Alternative: Manual AdaptiveNav with evaluateAction() »

You can also use evaluateAction() directly for full control over reorder and hide logic.

<script setup lang="ts">
import { computed } from 'vue'
import { useAdaptiveClient } from '@presage-kit/vue'
interface NavItem {
id: string
label: string
href: string
}
const client = useAdaptiveClient()
const baseItems: NavItem[] = [
{ id: 'home', label: 'Home', href: '/' },
{ id: 'analytics', label: 'Analytics', href: '/analytics' },
{ id: 'settings', label: 'Settings', href: '/settings' },
]
const navItems = computed(() => {
const action = client.evaluateAction('sidebar-nav')
if (action?.type === 'hide') return []
if (action?.type === 'reorder') {
return reorder(baseItems, action.order)
}
return baseItems
})
function reorder(items: NavItem[], order: string[]): NavItem[] {
const map = new Map(items.map((item) => [item.id, item]))
const ordered: NavItem[] = []
for (const id of order) {
const item = map.get(id)
if (item) {
ordered.push(item)
map.delete(id)
}
}
// Append items not in the order list
for (const item of map.values()) {
ordered.push(item)
}
return ordered
}
</script>
<template>
<nav>
<a v-for="item in navItems" :key="item.id" :href="item.href">
{{ item.label }}
</a>
</nav>
</template>
src/lib/adaptive-client.ts
import { createAdaptiveClient, createLocalStorageDriver } from '@presage-kit/core'
export const client = createAdaptiveClient({
rules: [
{
id: 'power-user-sidebar',
adaptationId: 'sidebar-nav',
priority: 10,
conditions: {
all: [{ field: 'maturity', operator: 'eq', value: 'power' }],
},
action: { type: 'reorder', order: ['analytics', 'dashboards', 'settings', 'home'] },
},
{
id: 'new-user-tour',
adaptationId: 'onboarding',
priority: 10,
conditions: {
all: [{ field: 'maturity', operator: 'eq', value: 'new' }],
},
action: { type: 'show', variantId: 'guided-tour' },
},
{
id: 'experienced-skip',
adaptationId: 'onboarding',
priority: 5,
conditions: {
any: [
{ field: 'maturity', operator: 'eq', value: 'active' },
{ field: 'maturity', operator: 'eq', value: 'power' },
],
},
action: { type: 'show', variantId: 'minimal' },
},
],
persistence: {
driver: createLocalStorageDriver('vue-demo'),
},
})
src/main.ts
import { createApp } from 'vue'
import { AdaptivePlugin } from '@presage-kit/vue'
import { client } from './lib/adaptive-client'
import App from './App.vue'
const app = createApp(App)
app.use(AdaptivePlugin, { client })
app.mount('#app')
src/App.vue
<script setup lang="ts">
import SidebarNav from './components/SidebarNav.vue'
import Onboarding from './components/Onboarding.vue'
</script>
<template>
<div class="layout">
<SidebarNav />
<main>
<Onboarding />
<router-view />
</main>
</div>
</template>
src/components/Onboarding.vue
<script setup lang="ts">
import { Adaptive, Variant } from '@presage-kit/vue'
</script>
<template>
<Adaptive id="onboarding" default-variant="standard">
<Variant id="guided-tour">
<div class="onboarding">
<h2>Welcome! Let us show you around.</h2>
<button>Complete Tour</button>
</div>
</Variant>
<Variant id="standard">
<div class="onboarding">
<h2>What is new</h2>
<p>Check out the latest features.</p>
</div>
</Variant>
<Variant id="minimal">
<div class="onboarding">
<p>Welcome back!</p>
</div>
</Variant>
</Adaptive>
</template>
src/components/SidebarNav.vue
<script setup lang="ts">
import { AdaptiveNav } from '@presage-kit/vue'
const items = [
{ id: 'home', label: 'Home', href: '/' },
{ id: 'dashboards', label: 'Dashboards', href: '/dashboards' },
{ id: 'analytics', label: 'Analytics', href: '/analytics' },
{ id: 'settings', label: 'Settings', href: '/settings' },
]
</script>
<template>
<nav class="sidebar">
<AdaptiveNav id="sidebar-nav" :items="items" v-slot="{ item }">
<a :href="item.href">{{ item.label }}</a>
</AdaptiveNav>
</nav>
</template>