featuredrop 2.6.1 → 2.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,540 @@
1
+ import { RefObject, ReactNode } from 'react';
2
+
3
+ /** Entry type label — determines default icon/color in UI */
4
+ type FeatureType = "feature" | "improvement" | "fix" | "breaking";
5
+ /** Priority level for announcements */
6
+ type FeaturePriority = "critical" | "normal" | "low";
7
+ /** Motion preset for built-in component transitions */
8
+ type FeatureDropAnimationPreset = "none" | "subtle" | "normal" | "playful";
9
+ /** Call-to-action for a feature entry */
10
+ interface FeatureCTA {
11
+ /** Button/link label */
12
+ label: string;
13
+ /** URL to navigate to */
14
+ url: string;
15
+ }
16
+ /** Variant-level overrides for A/B announcement testing */
17
+ interface FeatureVariant {
18
+ /** Optional variant-specific label override */
19
+ label?: string;
20
+ /** Optional variant-specific description override */
21
+ description?: string;
22
+ /** Optional variant-specific image override */
23
+ image?: string;
24
+ /** Optional variant-specific CTA override */
25
+ cta?: FeatureCTA;
26
+ /** Optional variant-specific metadata overrides */
27
+ meta?: Record<string, unknown>;
28
+ }
29
+ /** Audience targeting rule — determines which user segments see a feature */
30
+ interface AudienceRule {
31
+ /** Plans that should see this feature (e.g. ["pro", "enterprise"]) */
32
+ plan?: string[];
33
+ /** Roles that should see this feature (e.g. ["admin", "editor"]) */
34
+ role?: string[];
35
+ /** Regions that should see this feature (e.g. ["us", "eu"]) */
36
+ region?: string[];
37
+ /** Arbitrary key-value pairs for custom matching logic */
38
+ custom?: Record<string, unknown>;
39
+ }
40
+ /** Dependency gates for progressive feature discovery */
41
+ interface FeatureDependencies {
42
+ /** Features the user must have seen before this one can surface */
43
+ seen?: string[];
44
+ /** Features the user must have clicked before this one can surface */
45
+ clicked?: string[];
46
+ /** Features the user must have dismissed before this one can surface */
47
+ dismissed?: string[];
48
+ }
49
+ /** Runtime context used by trigger evaluation */
50
+ interface TriggerContext {
51
+ /** Current app route/path */
52
+ path?: string;
53
+ /** Named events observed in this session */
54
+ events?: ReadonlySet<string>;
55
+ /** Named milestone flags reached in this session */
56
+ milestones?: ReadonlySet<string>;
57
+ /** Usage counters keyed by event/pattern name */
58
+ usage?: Record<string, number>;
59
+ /** Session elapsed time in milliseconds */
60
+ elapsedMs?: number;
61
+ /** Scroll completion percentage (0-100) */
62
+ scrollPercent?: number;
63
+ /** Optional additional trigger context */
64
+ metadata?: Record<string, unknown>;
65
+ }
66
+ type FeatureTrigger = {
67
+ type: "page";
68
+ match: string | RegExp;
69
+ } | {
70
+ type: "usage";
71
+ event: string;
72
+ minActions?: number;
73
+ } | {
74
+ type: "time";
75
+ minSeconds: number;
76
+ } | {
77
+ type: "milestone";
78
+ event: string;
79
+ } | {
80
+ type: "frustration";
81
+ pattern: string;
82
+ threshold?: number;
83
+ } | {
84
+ type: "scroll";
85
+ minPercent?: number;
86
+ } | {
87
+ type: "custom";
88
+ evaluate: (context: TriggerContext) => boolean;
89
+ };
90
+ /** A single feature entry in the manifest */
91
+ interface FeatureEntry {
92
+ /** Unique identifier for the feature */
93
+ id: string;
94
+ /** Human-readable label (e.g. "Decision Journal") */
95
+ label: string;
96
+ /** Optional longer description (supports markdown in UI components) */
97
+ description?: string;
98
+ /**
99
+ * Semantic version targeting.
100
+ * If provided as an object, requires `appVersion` to be supplied to the provider/helpers.
101
+ * - introduced: earliest app version that includes this feature
102
+ * - showNewUntil: stop showing "new" once appVersion reaches this
103
+ * - deprecatedAt: hide feature for app versions at or above this (optional safety)
104
+ * - showIn: range string, e.g. ">=2.5.0 <3.0.0"
105
+ */
106
+ version?: string | {
107
+ introduced?: string;
108
+ showNewUntil?: string;
109
+ deprecatedAt?: string;
110
+ showIn?: string;
111
+ };
112
+ /** ISO date when this feature was released */
113
+ releasedAt: string;
114
+ /** ISO date after which the "new" badge should stop showing */
115
+ showNewUntil: string;
116
+ /** Optional key to match navigation items (e.g. "/journal", "settings") */
117
+ sidebarKey?: string;
118
+ /** Optional grouping category (e.g. "ai", "billing", "core") */
119
+ category?: string;
120
+ /** Optional product scope (`"*"`, `"askverdict"`, etc.) for multi-product manifests */
121
+ product?: string;
122
+ /** Optional URL to link to (e.g. docs page, changelog entry) */
123
+ url?: string;
124
+ /** Optional feature flag key; requires a flag bridge to evaluate */
125
+ flagKey?: string;
126
+ /** Entry type — determines default icon/color in UI components */
127
+ type?: FeatureType;
128
+ /** Priority level — critical entries get special treatment in UI */
129
+ priority?: FeaturePriority;
130
+ /** Optional image/screenshot URL */
131
+ image?: string;
132
+ /** Optional call-to-action button */
133
+ cta?: FeatureCTA;
134
+ /** ISO date — entry is hidden until this date (scheduled publishing) */
135
+ publishAt?: string;
136
+ /** Optional arbitrary metadata */
137
+ meta?: Record<string, unknown>;
138
+ /** A/B variants keyed by variant name (e.g. control, treatment_a) */
139
+ variants?: Record<string, FeatureVariant>;
140
+ /** Percentage split per variant (same order as variants object keys) */
141
+ variantSplit?: number[];
142
+ /** Audience targeting — if set, only matching users see this feature */
143
+ audience?: AudienceRule;
144
+ /** Dependency requirements (progressive disclosure sequencing) */
145
+ dependsOn?: FeatureDependencies;
146
+ /** Contextual trigger rule */
147
+ trigger?: FeatureTrigger;
148
+ }
149
+ /** Display format hint from the engine */
150
+ type DisplayFormat = "badge" | "toast" | "modal" | "banner" | "inline" | "spotlight";
151
+ /** Interaction type tracked by the engine */
152
+ type InteractionType = "seen" | "dismissed" | "clicked" | "completed" | "snoozed" | "hovered" | "expanded";
153
+ /** Timing decision returned by the engine */
154
+ interface TimingDecision {
155
+ /** Whether to show the announcement now */
156
+ show: boolean;
157
+ /** Reason for the decision */
158
+ reason: string;
159
+ /** Suggested delay in ms if not showing now */
160
+ delayMs?: number;
161
+ /** Confidence level (0-1) */
162
+ confidence: number;
163
+ }
164
+ /** Format recommendation from the engine */
165
+ interface FormatRecommendation {
166
+ /** Recommended display format */
167
+ primary: DisplayFormat;
168
+ /** Fallback format if primary component isn't used */
169
+ fallback: DisplayFormat;
170
+ /** Reason for the recommendation */
171
+ reason: string;
172
+ }
173
+ /** Adoption score breakdown */
174
+ interface AdoptionScore {
175
+ /** Overall score (0-100) */
176
+ score: number;
177
+ /** Letter grade */
178
+ grade: "A" | "B" | "C" | "D" | "F";
179
+ /** Score breakdown */
180
+ breakdown: {
181
+ /** % of features the user has explored */
182
+ featuresExplored: number;
183
+ /** Rate of dismissals (lower is better) */
184
+ dismissRate: number;
185
+ /** Rate of tour/checklist completion */
186
+ completionRate: number;
187
+ /** Whether engagement is rising, stable, or declining */
188
+ engagementTrend: "rising" | "stable" | "declining";
189
+ };
190
+ /** Actionable recommendations */
191
+ recommendations: string[];
192
+ }
193
+ /** Per-feature adoption status */
194
+ interface FeatureAdoptionStatus {
195
+ featureId: string;
196
+ status: "unseen" | "seen" | "explored" | "adopted" | "dismissed";
197
+ firstSeen?: string;
198
+ lastInteraction?: string;
199
+ interactionCount: number;
200
+ }
201
+ /** Delivery context passed to the engine for timing decisions */
202
+ interface DeliveryContext {
203
+ /** Current route/path */
204
+ currentPath: string;
205
+ /** Seconds since session start */
206
+ sessionAge: number;
207
+ /** Dismissals in last 5 minutes */
208
+ recentDismissals: number;
209
+ /** Feature priority */
210
+ featurePriority: FeaturePriority;
211
+ }
212
+ /**
213
+ * Plugin interface for the FeatureDrop engine.
214
+ *
215
+ * The open-source library defines this interface.
216
+ * The proprietary @featuredrop/engine implements it.
217
+ * Users can also build their own engine implementation.
218
+ *
219
+ * The free library works perfectly without any engine.
220
+ */
221
+ interface FeatureDropEngine {
222
+ /** Decide whether to show a feature announcement now */
223
+ shouldShow(featureId: string, context: DeliveryContext): TimingDecision;
224
+ /** Recommend the best display format for a feature */
225
+ recommendFormat(featureId: string): FormatRecommendation;
226
+ /** Get the user's overall adoption score */
227
+ getAdoptionScore(): AdoptionScore;
228
+ /** Track a user interaction with a feature */
229
+ trackInteraction(featureId: string, type: InteractionType): void;
230
+ /** Get adoption status for a specific feature */
231
+ getFeatureAdoption(featureId: string): FeatureAdoptionStatus;
232
+ /** Initialize the engine (called by provider on mount) */
233
+ initialize?(): void;
234
+ /** Cleanup resources (called by provider on unmount) */
235
+ destroy?(): void;
236
+ }
237
+
238
+ type AdoptionEventType = "feature_seen" | "feature_clicked" | "feature_dismissed" | "tour_started" | "tour_completed" | "tour_skipped" | "checklist_task_completed" | "checklist_completed" | "survey_submitted" | "feedback_submitted" | "announcement_shown" | "cta_clicked";
239
+ interface AdoptionEvent {
240
+ type: AdoptionEventType;
241
+ featureId?: string;
242
+ tourId?: string;
243
+ variant?: string;
244
+ timestamp: string;
245
+ sessionId?: string;
246
+ userId?: string;
247
+ metadata?: Record<string, unknown>;
248
+ }
249
+ type AdoptionEventInput = Omit<AdoptionEvent, "timestamp"> & {
250
+ timestamp?: string;
251
+ };
252
+ interface FeatureEngagementMetrics {
253
+ seen: number;
254
+ clicked: number;
255
+ dismissed: number;
256
+ }
257
+ interface AdoptionMetrics {
258
+ getAdoptionRate: (featureId: string) => number;
259
+ getTourCompletionRate: (tourId: string) => number;
260
+ getChecklistCompletionRate: (checklistId: string) => number;
261
+ getFeatureEngagement: (featureId: string) => FeatureEngagementMetrics;
262
+ getVariantPerformance: (featureId: string) => Record<string, number>;
263
+ }
264
+
265
+ interface FeatureDropTranslations {
266
+ newBadge: string;
267
+ whatsNewTitle: string;
268
+ markAllRead: string;
269
+ allCaughtUp: string;
270
+ close: string;
271
+ changelogTitle: string;
272
+ searchPlaceholder: string;
273
+ allCategories: string;
274
+ noUpdatesYet: string;
275
+ loadMore: string;
276
+ share: string;
277
+ skipToEntries: string;
278
+ newFeatureCount: (count: number) => string;
279
+ stepOf: (current: number, total: number) => string;
280
+ back: string;
281
+ next: string;
282
+ skip: string;
283
+ finish: string;
284
+ gotIt: string;
285
+ announcement: string;
286
+ feedbackTitle: string;
287
+ feedbackTrigger: string;
288
+ feedbackSubmitted: string;
289
+ submit: string;
290
+ cancel: string;
291
+ askLater: string;
292
+ }
293
+
294
+ interface FeatureDropContextValue {
295
+ /** Full manifest provided to the provider */
296
+ manifest: FeatureEntry[] | readonly FeatureEntry[];
297
+ /** All currently "new" features */
298
+ newFeatures: FeatureEntry[];
299
+ /** New features currently queued by throttling rules */
300
+ queuedFeatures: FeatureEntry[];
301
+ /** Count of new features */
302
+ newCount: number;
303
+ /** Count before throttling (all pending new features) */
304
+ totalNewCount: number;
305
+ /** All new features sorted by priority then release date */
306
+ newFeaturesSorted: FeatureEntry[];
307
+ /** Check if a sidebar key has any new features */
308
+ isNew: (sidebarKey: string) => boolean;
309
+ /** Dismiss a single feature by ID */
310
+ dismiss: (id: string) => void;
311
+ /** Dismiss all features (marks all as seen) */
312
+ dismissAll: () => Promise<void>;
313
+ /** Get the feature entry for a sidebar key (if it's new) */
314
+ getFeature: (sidebarKey: string) => FeatureEntry | undefined;
315
+ /** Whether quiet mode (Do Not Disturb) is enabled */
316
+ quietMode: boolean;
317
+ /** Enable/disable quiet mode */
318
+ setQuietMode: (enabled: boolean) => void;
319
+ /** Mark a feature as seen for dependency-chain resolution */
320
+ markFeatureSeen: (featureId: string) => void;
321
+ /** Mark a feature as clicked for dependency-chain resolution */
322
+ markFeatureClicked: (featureId: string) => void;
323
+ /** Remaining toasts allowed in this session under throttle rules */
324
+ getRemainingToastSlots: () => number;
325
+ /** Mark toasts as shown in this session */
326
+ markToastsShown: (featureIds: string[]) => void;
327
+ /** Whether a modal can open right now under throttle rules */
328
+ canShowModal: (priority?: FeaturePriority) => boolean;
329
+ /** Record a modal display timestamp */
330
+ markModalShown: () => void;
331
+ /** Whether a tour can start right now under throttle rules */
332
+ canShowTour: () => boolean;
333
+ /** Record a tour start timestamp */
334
+ markTourShown: () => void;
335
+ /** Acquire/release spotlight slots under maxSimultaneousSpotlights */
336
+ acquireSpotlightSlot: (id: string, priority?: FeaturePriority) => boolean;
337
+ releaseSpotlightSlot: (id: string) => void;
338
+ /** Number of currently active spotlight slots */
339
+ activeSpotlightCount: number;
340
+ /** Emit an adoption analytics event (collector-backed when configured) */
341
+ trackAdoptionEvent: (event: AdoptionEventInput) => void;
342
+ /** Report a component/runtime error to provider-level monitoring hooks */
343
+ reportError: (error: unknown, context?: {
344
+ component?: string;
345
+ componentStack?: string;
346
+ }) => void;
347
+ /** Active locale code used by built-in UI strings */
348
+ locale: string;
349
+ /** Text direction derived from locale */
350
+ direction: "ltr" | "rtl";
351
+ /** Active motion preset for built-in component transitions */
352
+ animation: FeatureDropAnimationPreset;
353
+ /** Resolved translation strings for built-in React components */
354
+ translations: FeatureDropTranslations;
355
+ /** Track a named usage event for trigger rules */
356
+ trackUsageEvent: (event: string, delta?: number) => void;
357
+ /** Track a named trigger event for trigger rules */
358
+ trackTriggerEvent: (event: string) => void;
359
+ /** Mark a milestone for trigger rules */
360
+ trackMilestone: (event: string) => void;
361
+ /** Manually override current path for page trigger rules */
362
+ setTriggerPath: (path: string) => void;
363
+ /** Optional engine instance for AI-powered delivery intelligence */
364
+ engine: FeatureDropEngine | null;
365
+ }
366
+
367
+ /**
368
+ * Access the full feature discovery context.
369
+ *
370
+ * Returns: `{ newFeatures, newCount, isNew, dismiss, dismissAll, getFeature }`
371
+ *
372
+ * @throws Error if used outside of `<FeatureDropProvider>`
373
+ */
374
+ declare function useFeatureDrop(): FeatureDropContextValue;
375
+
376
+ interface UseNewFeatureResult {
377
+ /** Whether this sidebar key has a new feature */
378
+ isNew: boolean;
379
+ /** The feature entry, if new */
380
+ feature: FeatureEntry | undefined;
381
+ /** Dismiss the feature for this sidebar key */
382
+ dismiss: () => void;
383
+ }
384
+ /**
385
+ * Check if a single navigation item has a new feature.
386
+ *
387
+ * @param sidebarKey - The key to check (e.g. "/journal", "settings")
388
+ * @returns `{ isNew, feature, dismiss }`
389
+ */
390
+ declare function useNewFeature(sidebarKey: string): UseNewFeatureResult;
391
+
392
+ /**
393
+ * Get the count of currently new features.
394
+ *
395
+ * Useful for rendering a badge count on a "What's New" button.
396
+ *
397
+ * @returns The number of new features
398
+ */
399
+ declare function useNewCount(): number;
400
+
401
+ interface UseTabNotificationOptions {
402
+ /** Whether tab notifications are enabled. Default: true */
403
+ enabled?: boolean;
404
+ /** Template string. `{count}` is replaced with the number. Default: "({count}) {title}" */
405
+ template?: string;
406
+ /** Enable flashing/blinking pattern for attention. Default: false */
407
+ flash?: boolean;
408
+ /** Flash interval in ms. Default: 1500 */
409
+ flashInterval?: number;
410
+ }
411
+ /**
412
+ * Updates the browser tab title with the unread feature count.
413
+ *
414
+ * Shows "(3) My App" when there are new features, restores the original
415
+ * title when all features are read. Optional flash/blink pattern for attention.
416
+ *
417
+ * @example
418
+ * ```tsx
419
+ * function App() {
420
+ * useTabNotification();
421
+ * return <div>...</div>;
422
+ * }
423
+ * ```
424
+ *
425
+ * @example With flash
426
+ * ```tsx
427
+ * useTabNotification({ flash: true, template: "[{count} new] {title}" });
428
+ * ```
429
+ */
430
+ declare function useTabNotification(options?: UseTabNotificationOptions): void;
431
+
432
+ declare function useAdoptionAnalytics(events: AdoptionEvent[]): AdoptionMetrics;
433
+
434
+ interface TourStep {
435
+ id: string;
436
+ target: string | RefObject<HTMLElement | null>;
437
+ title: string;
438
+ content: string | ReactNode;
439
+ placement?: "top" | "bottom" | "left" | "right" | "auto";
440
+ action?: "click" | "input" | "custom";
441
+ advanceOn?: {
442
+ selector: string;
443
+ event: string;
444
+ };
445
+ highlightTarget?: boolean;
446
+ beforeStep?: () => Promise<void>;
447
+ afterStep?: () => Promise<void>;
448
+ skipable?: boolean;
449
+ }
450
+
451
+ interface TourSnapshot {
452
+ isActive: boolean;
453
+ currentStepIndex: number;
454
+ currentStep: TourStep | null;
455
+ totalSteps: number;
456
+ }
457
+
458
+ interface UseTourResult {
459
+ startTour: () => void;
460
+ nextStep: () => void;
461
+ prevStep: () => void;
462
+ skipTour: () => void;
463
+ closeTour: () => void;
464
+ currentStep: TourSnapshot["currentStep"];
465
+ currentStepIndex: number;
466
+ totalSteps: number;
467
+ isActive: boolean;
468
+ }
469
+ declare function useTour(id: string): UseTourResult;
470
+
471
+ interface TourSequenceItem {
472
+ featureId: string;
473
+ tourId: string;
474
+ }
475
+ interface UseTourSequencerResult {
476
+ nextTourId: string | null;
477
+ nextFeatureId: string | null;
478
+ remainingTours: number;
479
+ startNextTour: () => boolean;
480
+ }
481
+ declare function useTourSequencer(sequence: TourSequenceItem[]): UseTourSequencerResult;
482
+
483
+ interface UseChecklistResult {
484
+ completeTask: (taskId: string) => void;
485
+ resetChecklist: () => void;
486
+ dismissChecklist: () => void;
487
+ toggleCollapsed: () => void;
488
+ isComplete: boolean;
489
+ progress: {
490
+ completed: number;
491
+ total: number;
492
+ percent: number;
493
+ };
494
+ tasks: Array<{
495
+ id: string;
496
+ completed: boolean;
497
+ }>;
498
+ dismissed: boolean;
499
+ collapsed: boolean;
500
+ }
501
+ declare function useChecklist(id: string): UseChecklistResult;
502
+
503
+ type SurveyType = "nps" | "csat" | "ces" | "custom";
504
+
505
+ interface UseSurveyResult {
506
+ show: (options?: {
507
+ force?: boolean;
508
+ }) => boolean;
509
+ hide: () => void;
510
+ askLater: () => void;
511
+ isOpen: boolean;
512
+ submitted: boolean;
513
+ canShow: boolean;
514
+ type: SurveyType;
515
+ }
516
+ declare function useSurvey(id: string): UseSurveyResult;
517
+
518
+ interface UseChangelogResult {
519
+ /** All features from the manifest (including non-new) */
520
+ features: readonly FeatureEntry[];
521
+ /** Only features that are currently "new" (unread) */
522
+ newFeatures: readonly FeatureEntry[];
523
+ /** Count of new/unread features */
524
+ newCount: number;
525
+ /** Sorted new features (critical first, then by date) */
526
+ newFeaturesSorted: readonly FeatureEntry[];
527
+ /** Dismiss a single feature by ID */
528
+ dismiss: (id: string) => void;
529
+ /** Dismiss all features at once */
530
+ dismissAll: () => void;
531
+ /** Check if a specific feature is new */
532
+ isNew: (sidebarKey: string) => boolean;
533
+ /** Mark all currently visible features as seen (advances watermark) */
534
+ markAllSeen: () => void;
535
+ /** Get features filtered by category */
536
+ getByCategory: (category: string) => readonly FeatureEntry[];
537
+ }
538
+ declare function useChangelog(): UseChangelogResult;
539
+
540
+ export { type TourSequenceItem, type UseChangelogResult, type UseChecklistResult, type UseNewFeatureResult, type UseSurveyResult, type UseTabNotificationOptions, type UseTourResult, type UseTourSequencerResult, useAdoptionAnalytics, useChangelog, useChecklist, useFeatureDrop, useNewCount, useNewFeature, useSurvey, useTabNotification, useTour, useTourSequencer };