AnnouncementsProvider
AnnouncementsProvider: configure announcement storage, queue behavior, frequency rules, and audience targeting at app level
AnnouncementsProvider
The root provider that manages announcement state, queue, persistence, and configuration. All announcement components and hooks must be used within this provider.
Why Use AnnouncementsProvider?
The provider centralizes announcement management:
- State management - Tracks views, dismissals, and active announcements
- Queue management - Automatically orders by priority and shows next in queue
- Persistence - Saves state to localStorage/sessionStorage
- Audience targeting - Filters announcements based on user context
- Lifecycle callbacks - Global event handlers for all announcements
Basic Usage
import { AnnouncementsProvider, AnnouncementModal } from '@tour-kit/announcements';
function App() {
return (
<AnnouncementsProvider
announcements={[
{
id: 'welcome',
variant: 'modal',
title: 'Welcome!',
description: 'Thanks for joining us.',
},
]}
>
<AnnouncementModal id="welcome" />
<YourApp />
</AnnouncementsProvider>
);
}Props
Prop
Type
Announcement Configuration
Each announcement in the announcements array requires:
const announcements: AnnouncementConfig[] = [
{
id: 'unique-id', // Required: unique identifier
variant: 'modal', // Required: 'modal' | 'slideout' | 'banner' | 'toast' | 'spotlight'
priority: 'high', // Optional: 'low' | 'normal' | 'high' | 'critical'
title: 'Announcement Title',
description: 'Announcement description',
// Actions
primaryAction: {
label: 'Get Started',
onClick: () => console.log('Primary action'),
},
secondaryAction: {
label: 'Learn More',
onClick: () => console.log('Secondary action'),
},
// Display rules
frequency: 'once', // 'once' | 'session' | 'always' | object
audience: [], // Targeting rules
schedule: {}, // From @tour-kit/scheduling
// Variant-specific options
modalOptions: { size: 'md' },
slideoutOptions: { position: 'right' },
bannerOptions: { sticky: true },
toastOptions: { autoDismiss: true },
spotlightOptions: { targetSelector: '#element' },
// Callbacks
onShow: () => console.log('Shown'),
onDismiss: (reason) => console.log('Dismissed', reason),
onComplete: () => console.log('Completed'),
// Metadata
metadata: { campaign: 'spring-2024' },
},
];Queue Configuration
Control how announcements are queued and displayed:
<AnnouncementsProvider
queueConfig={{
maxConcurrent: 1, // Only show 1 announcement at a time
autoShow: true, // Automatically show next in queue
delayBetween: 1000, // Wait 1s between announcements
respectPriority: true, // Order by priority (default: true)
}}
/>QueueConfig Options
Prop
Type
User Context for Targeting
Provide user data to enable audience targeting:
<AnnouncementsProvider
userContext={{
userId: '123',
plan: 'pro',
role: 'admin',
signupDate: '2024-01-15',
features: ['export', 'api'],
preferences: {
theme: 'dark',
},
}}
announcements={[
{
id: 'pro-feature',
variant: 'modal',
title: 'New Pro Feature',
audience: [
{ field: 'plan', operator: 'equals', value: 'pro' },
],
},
{
id: 'admin-notice',
variant: 'banner',
title: 'Admin Update',
audience: [
{ field: 'role', operator: 'equals', value: 'admin' },
],
},
]}
/>See Audience Targeting for detailed targeting rules.
Storage Options
localStorage (Default)
Persist across browser sessions:
<AnnouncementsProvider
storage="localStorage"
storageKey="my-app:announcements"
/>sessionStorage
Persist only for current session:
<AnnouncementsProvider
storage="sessionStorage"
storageKey="my-app:announcements"
/>Memory
No persistence (resets on page reload):
<AnnouncementsProvider storage="memory" />Using storage="memory" means announcement state will reset on page reload. Users will see dismissed announcements again.
Global Callbacks
Track announcement events across your app:
<AnnouncementsProvider
announcements={announcements}
onShow={(id) => {
console.log(`Announcement shown: ${id}`);
analytics.track('announcement_shown', { announcementId: id });
}}
onDismiss={(id, reason) => {
console.log(`Announcement dismissed: ${id}, reason: ${reason}`);
analytics.track('announcement_dismissed', {
announcementId: id,
dismissalReason: reason,
});
}}
onComplete={(id) => {
console.log(`Announcement completed: ${id}`);
analytics.track('announcement_completed', { announcementId: id });
}}
/>Dismissal Reasons
The onDismiss callback receives one of these reasons:
'close_button'- User clicked close button'overlay_click'- User clicked overlay (modals/slideouts)'escape_key'- User pressed Escape key'primary_action'- User clicked primary action button'secondary_action'- User clicked secondary action button'auto_dismiss'- Toast auto-dismissed after delay'programmatic'- Dismissed via hook/API call
Complete Example
import {
AnnouncementsProvider,
AnnouncementModal,
AnnouncementBanner,
AnnouncementToast,
} from '@tour-kit/announcements';
const announcements = [
{
id: 'critical-update',
variant: 'modal',
priority: 'critical',
title: 'Security Update Required',
description: 'Please update your password immediately.',
frequency: 'always',
primaryAction: {
label: 'Update Now',
onClick: () => router.push('/settings/security'),
},
modalOptions: {
size: 'md',
closeOnOverlayClick: false,
closeOnEscape: false,
},
},
{
id: 'new-export',
variant: 'modal',
priority: 'high',
title: 'New Export Feature',
description: 'Export your data to CSV, PDF, or Excel.',
frequency: 'once',
audience: [
{ field: 'plan', operator: 'in', value: ['pro', 'enterprise'] },
],
media: {
type: 'image',
src: '/images/export-feature.png',
alt: 'Export feature preview',
},
primaryAction: {
label: 'Try It Now',
onClick: () => router.push('/export'),
},
secondaryAction: {
label: 'Learn More',
onClick: () => window.open('/docs/export', '_blank'),
},
},
{
id: 'maintenance',
variant: 'banner',
priority: 'normal',
title: 'Scheduled maintenance tonight at 2 AM EST',
frequency: 'session',
bannerOptions: {
position: 'top',
sticky: true,
intent: 'warning',
},
},
{
id: 'tip-of-day',
variant: 'toast',
priority: 'low',
title: 'Pro Tip: Use keyboard shortcuts!',
frequency: { type: 'interval', days: 7 },
toastOptions: {
position: 'bottom-right',
autoDismiss: true,
autoDismissDelay: 5000,
},
},
];
function App() {
const [user, setUser] = useState(null);
useEffect(() => {
// Fetch user data
fetchUser().then(setUser);
}, []);
if (!user) return <Loading />;
return (
<AnnouncementsProvider
announcements={announcements}
userContext={{
userId: user.id,
plan: user.plan,
role: user.role,
signupDate: user.createdAt,
}}
queueConfig={{
maxConcurrent: 1,
autoShow: true,
delayBetween: 500,
}}
storage="localStorage"
storageKey="myapp:announcements"
onShow={(id) => {
console.log('Shown:', id);
analytics.track('announcement_shown', { id });
}}
onDismiss={(id, reason) => {
console.log('Dismissed:', id, reason);
analytics.track('announcement_dismissed', { id, reason });
}}
onComplete={(id) => {
console.log('Completed:', id);
analytics.track('announcement_completed', { id });
}}
>
{/* Render announcement UI components */}
<AnnouncementModal id="critical-update" />
<AnnouncementModal id="new-export" />
<AnnouncementBanner id="maintenance" />
<AnnouncementToast id="tip-of-day" />
{/* Your app content */}
<MainApp />
</AnnouncementsProvider>
);
}Dynamic Announcements
Add or update announcements after initial render:
function DynamicAnnouncementsExample() {
const [announcements, setAnnouncements] = useState([]);
useEffect(() => {
// Fetch announcements from API
fetch('/api/announcements')
.then(res => res.json())
.then(data => setAnnouncements(data));
}, []);
return (
<AnnouncementsProvider announcements={announcements}>
<App />
</AnnouncementsProvider>
);
}When the announcements prop changes, the provider will re-evaluate which announcements should be shown based on frequency rules and user context.
Nested Providers
You can nest providers for different sections of your app:
function App() {
return (
<AnnouncementsProvider
announcements={globalAnnouncements}
storageKey="app:global"
>
<Dashboard />
<AdminPanel>
<AnnouncementsProvider
announcements={adminAnnouncements}
storageKey="app:admin"
>
<AdminContent />
</AnnouncementsProvider>
</AdminPanel>
</AnnouncementsProvider>
);
}Each provider maintains its own state. Nested providers won't share announcement state with parent providers.
TypeScript
The provider is fully typed:
import type {
AnnouncementConfig,
QueueConfig,
DismissalReason,
} from '@tour-kit/announcements';
const announcements: AnnouncementConfig[] = [
{
id: 'welcome',
variant: 'modal',
title: 'Welcome!',
},
];
const queueConfig: QueueConfig = {
maxConcurrent: 1,
autoShow: true,
};
<AnnouncementsProvider
announcements={announcements}
queueConfig={queueConfig}
onDismiss={(id: string, reason: DismissalReason) => {
console.log(id, reason);
}}
>
<App />
</AnnouncementsProvider>Related
- useAnnouncements - Access provider state
- Queue Configuration - Detailed queue config
- Audience Targeting - Targeting rules
- Frequency Rules - Display frequency