@fanfare-io/fanfare-sdk-react 0.1.0

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.
Files changed (102) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +58 -0
  3. package/dist/components/auth/access-code-input.d.ts +24 -0
  4. package/dist/components/auth/auth-form.d.ts +39 -0
  5. package/dist/components/auth/auth-input.d.ts +28 -0
  6. package/dist/components/auth/email-input.d.ts +21 -0
  7. package/dist/components/auth/index.d.ts +19 -0
  8. package/dist/components/auth/otp-input.d.ts +32 -0
  9. package/dist/components/auth/phone-input.d.ts +22 -0
  10. package/dist/components/compositions/access-code-form.d.ts +29 -0
  11. package/dist/components/compositions/challenge-gate.d.ts +23 -0
  12. package/dist/components/compositions/ended-module.d.ts +26 -0
  13. package/dist/components/compositions/error-view.d.ts +58 -0
  14. package/dist/components/compositions/granted-panel.d.ts +35 -0
  15. package/dist/components/compositions/index.d.ts +15 -0
  16. package/dist/components/compositions/journey-gate.d.ts +48 -0
  17. package/dist/components/compositions/outcome-panel.d.ts +11 -0
  18. package/dist/components/index.d.ts +15 -0
  19. package/dist/components/journey/experience-journey-controller.d.ts +14 -0
  20. package/dist/components/journey/experience-journey-skeleton.d.ts +21 -0
  21. package/dist/components/module/alert-panel.d.ts +13 -0
  22. package/dist/components/module/index.d.ts +19 -0
  23. package/dist/components/module/info-panel.d.ts +9 -0
  24. package/dist/components/module/outcome-reason.d.ts +27 -0
  25. package/dist/components/module/panel-heading.d.ts +34 -0
  26. package/dist/components/module/panel-layout.d.ts +28 -0
  27. package/dist/components/primitives/badge.d.ts +18 -0
  28. package/dist/components/primitives/button.d.ts +23 -0
  29. package/dist/components/primitives/card.d.ts +20 -0
  30. package/dist/components/primitives/countdown.d.ts +42 -0
  31. package/dist/components/primitives/fade-presence.d.ts +28 -0
  32. package/dist/components/primitives/fanfare-logo.d.ts +12 -0
  33. package/dist/components/primitives/index.d.ts +30 -0
  34. package/dist/components/primitives/input.d.ts +46 -0
  35. package/dist/components/primitives/progress.d.ts +16 -0
  36. package/dist/components/primitives/skeleton.d.ts +9 -0
  37. package/dist/components/primitives/spinner.d.ts +10 -0
  38. package/dist/components/primitives/transition.d.ts +34 -0
  39. package/dist/components/shadow-dom-preview.d.ts +43 -0
  40. package/dist/components/widgets/appointment/appointment-booking-details.d.ts +18 -0
  41. package/dist/components/widgets/appointment/appointment-checked-in-view.d.ts +12 -0
  42. package/dist/components/widgets/appointment/appointment-day-tabs.d.ts +21 -0
  43. package/dist/components/widgets/appointment/appointment-slot-grid.d.ts +12 -0
  44. package/dist/components/widgets/appointment/appointment-slot-item.d.ts +12 -0
  45. package/dist/components/widgets/appointment/appointment-slot-picker.d.ts +16 -0
  46. package/dist/components/widgets/auction/auction-actions.d.ts +38 -0
  47. package/dist/components/widgets/auction/auction-bid-display.d.ts +35 -0
  48. package/dist/components/widgets/auction/index.d.ts +7 -0
  49. package/dist/components/widgets/draw/draw-actions.d.ts +40 -0
  50. package/dist/components/widgets/draw/index.d.ts +6 -0
  51. package/dist/components/widgets/experience-widget.d.ts +141 -0
  52. package/dist/components/widgets/index.d.ts +11 -0
  53. package/dist/components/widgets/internal/appointment-module.d.ts +28 -0
  54. package/dist/components/widgets/internal/auction-module.d.ts +33 -0
  55. package/dist/components/widgets/internal/draw-module.d.ts +43 -0
  56. package/dist/components/widgets/internal/index.d.ts +15 -0
  57. package/dist/components/widgets/internal/loading-view.d.ts +17 -0
  58. package/dist/components/widgets/internal/queue-module.d.ts +27 -0
  59. package/dist/components/widgets/internal/start-view.d.ts +25 -0
  60. package/dist/components/widgets/internal/timed-release-module.d.ts +27 -0
  61. package/dist/components/widgets/internal/upcoming-module.d.ts +29 -0
  62. package/dist/components/widgets/internal/waitlist-view.d.ts +18 -0
  63. package/dist/components/widgets/queue/index.d.ts +6 -0
  64. package/dist/components/widgets/queue/queue-actions.d.ts +36 -0
  65. package/dist/components/widgets/timed-release/index.d.ts +6 -0
  66. package/dist/components/widgets/timed-release/timed-release-actions.d.ts +38 -0
  67. package/dist/components/widgets/waitlist/index.d.ts +6 -0
  68. package/dist/components/widgets/waitlist/waitlist-actions.d.ts +40 -0
  69. package/dist/context.d.ts +5 -0
  70. package/dist/hooks/use-appointment-slots.d.ts +13 -0
  71. package/dist/hooks/use-auth.d.ts +22 -0
  72. package/dist/hooks/use-experience-journey.d.ts +20 -0
  73. package/dist/hooks/use-fanfare.d.ts +11 -0
  74. package/dist/hooks/use-journey-snapshot.d.ts +8 -0
  75. package/dist/hooks/use-nanostore.d.ts +6 -0
  76. package/dist/i18n/i18n-context.d.ts +73 -0
  77. package/dist/i18n/i18n-provider.d.ts +67 -0
  78. package/dist/i18n/index.d.ts +13 -0
  79. package/dist/index.d.ts +53 -0
  80. package/dist/index.js +4918 -0
  81. package/dist/internal-sdk.d.ts +2 -0
  82. package/dist/lib/animations.d.ts +94 -0
  83. package/dist/lib/appointment-format.d.ts +35 -0
  84. package/dist/lib/confetti.d.ts +17 -0
  85. package/dist/lib/currency.d.ts +9 -0
  86. package/dist/lib/index.d.ts +5 -0
  87. package/dist/lib/phone-formatter-lite.d.ts +26 -0
  88. package/dist/lib/use-grant-celebration.d.ts +12 -0
  89. package/dist/lib/utils.d.ts +13 -0
  90. package/dist/provider.d.ts +99 -0
  91. package/dist/styles/base.css +2254 -0
  92. package/dist/styles/index.d.ts +2 -0
  93. package/dist/styles/theme.css +143 -0
  94. package/dist/theme/index.d.ts +16 -0
  95. package/dist/theme/theme-context.d.ts +20 -0
  96. package/dist/theme/theme-provider.d.ts +20 -0
  97. package/dist/theme/theme-utils.d.ts +18 -0
  98. package/dist/theme/theme.types.d.ts +47 -0
  99. package/dist/theme/use-theme.d.ts +44 -0
  100. package/docs/SLOTS.md +89 -0
  101. package/docs/STYLING.md +61 -0
  102. package/package.json +145 -0
@@ -0,0 +1,2 @@
1
+ export { default as baseStyles } from './base.css?inline';
2
+ export { default as themeStyles } from './theme.css?inline';
@@ -0,0 +1,143 @@
1
+ @theme {
2
+ /* Core colors - global defaults; ThemeProvider only emits inline overrides.
3
+ * Color semantics (aligned with messaging templates):
4
+ * - surface: Outer/surrounding area (transparent by default)
5
+ * - background: Main content area (white by default)
6
+ */
7
+ --color-primary: #1d1f27;
8
+ --color-primary-foreground: #ffffff;
9
+ --color-secondary: #f4f4f5;
10
+ --color-background: #ffffff;
11
+ --color-surface: transparent;
12
+ --color-surface-foreground: #1d1f27;
13
+ --color-foreground: #1d1f27;
14
+ --color-muted: #4b4e56cc;
15
+ --color-muted-foreground: #4b4e5699;
16
+ --color-border: #4b4e5666;
17
+
18
+ /* Status colors */
19
+ --color-success: oklch(0.77 0.18 130);
20
+ --color-success-foreground: #111111;
21
+ --color-warning: oklch(0.72 0.18 65);
22
+ --color-warning-foreground: #111111;
23
+ --color-danger: oklch(0.63 0.24 25);
24
+ --color-danger-foreground: #ffffff;
25
+
26
+ /* Typography */
27
+ --font-sans: "Inter", system-ui, sans-serif;
28
+ --font-heading: "Inter", system-ui, sans-serif;
29
+ /*
30
+ * --font-mono is intentionally not declared globally. The clean and retro
31
+ * variant blocks below set `--ff-font-mono` to the Inconsolata stack so the
32
+ * variant-scoped `font-mono` utility resolves correctly. The `default`
33
+ * variant has no mono-aesthetic surface today, so we let Tailwind's
34
+ * built-in default mono stack apply for compact component surfaces (access-code
35
+ * input, draw-module entry numbers, debug stories).
36
+ */
37
+
38
+ /* Extension below Tailwind's default `text-xs` (12px) for badge / pill / micro-label copy. */
39
+ --text-xxs: 0.625rem;
40
+ --text-xxs--line-height: 0.875rem;
41
+ }
42
+
43
+ /**
44
+ * Per-variant default tokens. Higher specificity than :root @theme defaults,
45
+ * lower than ThemeProvider inline overrides — so a customer overriding only
46
+ * `primary` does not reset the variant's other token defaults.
47
+ *
48
+ * Each block declares `--ff-variant: "<name>"` so the rule survives Tailwind
49
+ * v4 / Lightning CSS compilation (empty rules are stripped). ENG-464 will
50
+ * populate variant-specific design tokens here.
51
+ */
52
+ [data-fanfare-variant="default"] {
53
+ --ff-variant: "default";
54
+ }
55
+
56
+ [data-fanfare-variant="retro"] {
57
+ --ff-variant: "retro";
58
+ --ff-color-background: #f4f1ea;
59
+ --ff-color-foreground: #000000;
60
+ --ff-color-muted: #444444;
61
+ --ff-color-muted-foreground: #444444;
62
+ --ff-color-border: #000000;
63
+ /*
64
+ * Note: retro intentionally does NOT override --ff-color-danger. The
65
+ * brutalist `#ff3a00` orange is a Countdown-critical-urgency accent only
66
+ * (applied directly in the countdown cva compound), not a global semantic
67
+ * destructive color. Destructive buttons / error views keep the customer's
68
+ * (or @theme default) danger token under retro.
69
+ */
70
+ --ff-font-sans: "Inconsolata", "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
71
+ --ff-font-heading: "Inconsolata", "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
72
+ --ff-font-mono: "Inconsolata", "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
73
+ }
74
+
75
+ [data-fanfare-variant="rounded"] {
76
+ --ff-variant: "rounded";
77
+ }
78
+
79
+ [data-fanfare-variant="clean"] {
80
+ --ff-variant: "clean";
81
+ --ff-color-background: #ffffff;
82
+ --ff-color-foreground: #0a0a0a;
83
+ --ff-color-muted: #737373;
84
+ --ff-color-muted-foreground: #737373;
85
+ --ff-color-border: #e8e8e8;
86
+ --ff-font-sans: "Figtree", system-ui, sans-serif;
87
+ --ff-font-heading: "Figtree", system-ui, sans-serif;
88
+ --ff-font-mono: "Inconsolata", "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
89
+ }
90
+
91
+ @layer base {
92
+ /**
93
+ * Default theme values for .fanfare-themed elements.
94
+ * ThemeProvider overrides these via inline styles.
95
+ * Uses surface (outer area) as the root background color.
96
+ */
97
+ .fanfare-themed {
98
+ font-family: var(--ff-font-sans);
99
+ color: var(--ff-color-foreground);
100
+ background-color: var(--ff-color-surface);
101
+ }
102
+
103
+ /**
104
+ * Ensure all descendants inherit border and outline colors
105
+ */
106
+ .fanfare-themed *,
107
+ .fanfare-themed ::after,
108
+ .fanfare-themed ::before,
109
+ .fanfare-themed ::backdrop,
110
+ .fanfare-themed ::file-selector-button {
111
+ border-color: var(--ff-color-border);
112
+ outline-color: var(--ff-color-border);
113
+ }
114
+
115
+ /**
116
+ * Interactive elements
117
+ */
118
+ .fanfare-themed button:not(:disabled),
119
+ .fanfare-themed [role="button"]:not(:disabled) {
120
+ cursor: pointer;
121
+ }
122
+
123
+ /**
124
+ * Focus styles
125
+ */
126
+ .fanfare-themed :focus-visible {
127
+ outline: 2px solid var(--ff-color-primary);
128
+ outline-offset: 2px;
129
+ }
130
+
131
+ /**
132
+ * Respect reduced motion preferences
133
+ */
134
+ @media (prefers-reduced-motion: reduce) {
135
+ .fanfare-themed *,
136
+ .fanfare-themed ::before,
137
+ .fanfare-themed ::after {
138
+ animation-duration: 0.01ms !important;
139
+ animation-iteration-count: 1 !important;
140
+ transition-duration: 0.01ms !important;
141
+ }
142
+ }
143
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Theme System
3
+ *
4
+ * Provides theming infrastructure for Fanfare SDK components.
5
+ *
6
+ * @packageDocumentation
7
+ */
8
+ export { THEME_CSS_VARS } from './theme.types';
9
+ export type { ResolvedTheme, ThemeCssVar, WidgetThemeConfig, WidgetVariant } from './theme.types';
10
+ export type { BrandTheme } from '@fanfare-io/fanfare-sdk-core/theme';
11
+ export { ThemeContext, useMergedTheme, useThemeContext } from './theme-context';
12
+ export type { ThemeContextValue } from './theme-context';
13
+ export { ThemeProvider } from './theme-provider';
14
+ export type { ThemeProviderProps } from './theme-provider';
15
+ export { useTheme, useThemeAndVariant, useVariant } from './use-theme';
16
+ export { buildThemeCssVars } from './theme-utils';
@@ -0,0 +1,20 @@
1
+ import { BrandTheme } from '@fanfare-io/fanfare-sdk-core/theme';
2
+ import { ResolvedTheme, WidgetVariant } from './theme.types';
3
+ /**
4
+ * Context value for theme state.
5
+ */
6
+ export interface ThemeContextValue extends ResolvedTheme {
7
+ /** Whether this is the root theme provider */
8
+ isRoot: boolean;
9
+ }
10
+ export declare const ThemeContext: import('react').Context<ThemeContextValue>;
11
+ /**
12
+ * Hook to access the current theme context.
13
+ * Returns the merged theme and variant from the nearest ThemeProvider.
14
+ */
15
+ export declare function useThemeContext(): ThemeContextValue;
16
+ /**
17
+ * Hook to create a merged theme value for a nested provider.
18
+ * Shallow merges the new theme over the parent theme.
19
+ */
20
+ export declare function useMergedTheme(theme?: BrandTheme | null, variant?: WidgetVariant): ThemeContextValue;
@@ -0,0 +1,20 @@
1
+ import { BrandTheme } from '@fanfare-io/fanfare-sdk-core/theme';
2
+ import { ReactNode } from 'react';
3
+ import { WidgetVariant } from './theme.types';
4
+ export interface ThemeProviderProps {
5
+ /** Theme overrides to merge with parent theme */
6
+ theme?: BrandTheme | null;
7
+ /** Widget variant (layout/density) */
8
+ variant?: WidgetVariant;
9
+ /** HTML element to render as wrapper */
10
+ as?: keyof JSX.IntrinsicElements;
11
+ /** Additional CSS classes */
12
+ className?: string;
13
+ /** Child components */
14
+ children: ReactNode;
15
+ }
16
+ /**
17
+ * Provides theme context to descendants and applies CSS variables.
18
+ * Supports nesting - child providers shallow-merge over parent theme.
19
+ */
20
+ export declare function ThemeProvider({ theme, variant, as: Component, className, children }: ThemeProviderProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,18 @@
1
+ import { BrandTheme } from '@fanfare-io/fanfare-sdk-core/theme';
2
+ /**
3
+ * Builds CSS custom property values from a theme override.
4
+ *
5
+ * Only emits CSS vars for fields the customer explicitly configured plus any
6
+ * vars derived from those (computed foregrounds, mirrored mutedForeground,
7
+ * heading font fallback). Defaults for unset fields come from the SDK's CSS
8
+ * layer (:root @theme + [data-fanfare-variant] selectors), so per-variant
9
+ * defaults survive single-token overrides.
10
+ *
11
+ * Color semantics (aligned with messaging templates):
12
+ * - surface: Outer/surrounding area (transparent by default)
13
+ * - background: Main content area (white by default)
14
+ *
15
+ * Note: border is intentionally NOT mirrored from `muted` — see the comment
16
+ * inside the muted branch.
17
+ */
18
+ export declare function buildThemeCssVars(theme: BrandTheme): Record<string, string>;
@@ -0,0 +1,47 @@
1
+ import { BrandTheme, WidgetVariant } from '@fanfare-io/fanfare-sdk-core/theme';
2
+ export type { WidgetVariant } from '@fanfare-io/fanfare-sdk-core/theme';
3
+ /**
4
+ * Extended theme configuration for widgets.
5
+ * Builds on BrandTheme from fanfare-sdk-core.
6
+ */
7
+ export interface WidgetThemeConfig {
8
+ /** Base theme from experience configuration */
9
+ theme?: BrandTheme;
10
+ /** Layout/density variant (not related to colors) */
11
+ variant?: WidgetVariant;
12
+ }
13
+ /**
14
+ * Resolved theme with all defaults applied.
15
+ * This is what components receive from the theme context.
16
+ */
17
+ export interface ResolvedTheme {
18
+ /** The fully-merged BrandTheme with defaults */
19
+ theme: BrandTheme;
20
+ /** The active variant */
21
+ variant: WidgetVariant;
22
+ }
23
+ /**
24
+ * CSS custom property names used by the theme system.
25
+ * Maps to Tailwind V4 variables with ff- prefix (--ff-color-*, --ff-font-*).
26
+ */
27
+ export declare const THEME_CSS_VARS: {
28
+ readonly primary: "--ff-color-primary";
29
+ readonly primaryForeground: "--ff-color-primary-foreground";
30
+ readonly secondary: "--ff-color-secondary";
31
+ readonly background: "--ff-color-background";
32
+ readonly surface: "--ff-color-surface";
33
+ readonly surfaceForeground: "--ff-color-surface-foreground";
34
+ readonly foreground: "--ff-color-foreground";
35
+ readonly muted: "--ff-color-muted";
36
+ readonly mutedForeground: "--ff-color-muted-foreground";
37
+ readonly border: "--ff-color-border";
38
+ readonly success: "--ff-color-success";
39
+ readonly successForeground: "--ff-color-success-foreground";
40
+ readonly warning: "--ff-color-warning";
41
+ readonly warningForeground: "--ff-color-warning-foreground";
42
+ readonly danger: "--ff-color-danger";
43
+ readonly dangerForeground: "--ff-color-danger-foreground";
44
+ readonly fontFamily: "--ff-font-sans";
45
+ readonly fontHeading: "--ff-font-heading";
46
+ };
47
+ export type ThemeCssVar = (typeof THEME_CSS_VARS)[keyof typeof THEME_CSS_VARS];
@@ -0,0 +1,44 @@
1
+ import { BrandTheme } from '@fanfare-io/fanfare-sdk-core/theme';
2
+ import { WidgetVariant } from './theme.types';
3
+ /**
4
+ * Access the current theme from the nearest ThemeProvider.
5
+ *
6
+ * @example
7
+ * ```tsx
8
+ * function MyComponent() {
9
+ * const theme = useTheme();
10
+ * return <div style={{ color: theme.primary }}>Themed content</div>;
11
+ * }
12
+ * ```
13
+ */
14
+ export declare function useTheme(): BrandTheme;
15
+ /**
16
+ * Access the current widget variant from the nearest ThemeProvider.
17
+ *
18
+ * @example
19
+ * ```tsx
20
+ * function MyComponent() {
21
+ * const variant = useVariant();
22
+ * if (variant === "compact") {
23
+ * return <CompactView />;
24
+ * }
25
+ * return <DefaultView />;
26
+ * }
27
+ * ```
28
+ */
29
+ export declare function useVariant(): WidgetVariant;
30
+ /**
31
+ * Access both theme and variant from the nearest ThemeProvider.
32
+ *
33
+ * @example
34
+ * ```tsx
35
+ * function MyComponent() {
36
+ * const { theme, variant } = useThemeAndVariant();
37
+ * // ...
38
+ * }
39
+ * ```
40
+ */
41
+ export declare function useThemeAndVariant(): {
42
+ theme: BrandTheme;
43
+ variant: WidgetVariant;
44
+ };
package/docs/SLOTS.md ADDED
@@ -0,0 +1,89 @@
1
+ # ExperienceWidget Slots
2
+
3
+ `ExperienceWidget` slots are render props for targeted UI overrides. Each slot is called only when the matching journey or sequence state is active; all other states keep the default Fanfare UI.
4
+
5
+ The slot surface is derived from the same model as `JourneyView`: `journeyStage` -> `sequence.phase` -> `sequence.mechanism`. Actions are not serialized as a separate source of truth, and the server stays authoritative over what is permitted.
6
+
7
+ Use `children` or `useExperienceJourney` when you need to replace the full widget. Use `slots` when the default flow is correct and a specific state needs custom UI.
8
+
9
+ ## Shared Props
10
+
11
+ Every slot receives:
12
+
13
+ - `snapshot`: the current `JourneySnapshot | null`.
14
+ - `view`: the current `JourneyView | null`.
15
+ - `error`: the current widget error string, or `null`.
16
+
17
+ ## Journey Slots
18
+
19
+ | Slot | Meaningful state | Extra props |
20
+ | ------------ | ------------------------------------------------------------- | --------------------------------------------------------- |
21
+ | `start` | `journeyStage: "ready"` | `onStart`, `isStarting` |
22
+ | `loading` | `journeyStage: "routing"`, gated loading, or fallback loading | `message?` |
23
+ | `auth` | `journeyStage: "gated"` with an auth gate | `onSubmit`, `onVerify`, `onSkip?` |
24
+ | `accessCode` | `journeyStage: "gated"` with an access-code gate | `onSubmit`, `onSkip?` |
25
+ | `challenge` | `journeyStage: "gated"` with a bot-check gate | `challenge`, `botMitigation?`, `onVerifyToken`, `onRetry` |
26
+ | `error` | Widget action or loading error | `error`, `onRetry` |
27
+
28
+ ## Routed Sequence Slots
29
+
30
+ | Slot | Meaningful state | Mechanisms | Extra props |
31
+ | --------------- | ----------------------------------------------- | ---------------------------------------------------------- | ------------------------------------------------ |
32
+ | `upcoming` | `phase: "scheduled"` | `queue`, `draw`, `auction`, `timed_release`, `appointment` | `startsAt?`, `canEnter`, `onEnter`, `isEntering` |
33
+ | `upcoming` | `phase: "enterable"` | `waitlist` | `startsAt?`, `canEnter`, `onEnter`, `isEntering` |
34
+ | `waitlist` | `phase: "participating"` | `waitlist` | `startsAt?`, `onLeave`, `isLeaving` |
35
+ | `enterable` | `phase: "enterable"` | `queue`, `draw`, `auction`, `timed_release`, `appointment` | `sequence`, `participationType` |
36
+ | `participating` | `phase: "participating"` | `queue`, `draw`, `auction`, `timed_release`, `appointment` | `sequence`, `participationType`, `position?` |
37
+ | `granted` | `phase: "granted"` | `queue`, `draw`, `auction`, `timed_release` | `sequence`, `grant?`, `expiresAt?` |
38
+ | `expired` | `phase: "ended"` with `outcome.type: "expired"` | any ended mechanism | `reason`, `endedAt?` |
39
+ | `ended` | `phase: "unavailable"` or `phase: "ended"` | any ended mechanism | `reason`, `endedAt?` |
40
+
41
+ There is no slot for `phase: "settling"`; `draw` and `auction` render their default settling modules. There is no `granted` state for `appointment` or `waitlist`. The `ended` slot also renders `phase: "unavailable"` for convenience; `unavailable` is not a terminal phase.
42
+
43
+ ## `slots.enterable`
44
+
45
+ `slots.enterable` receives the typed `sequence` view. Call the method exposed for that exact `phase` and `mechanism`.
46
+
47
+ ```tsx
48
+ <ExperienceWidget
49
+ experienceId="exp_123"
50
+ slots={{
51
+ enterable: ({ sequence, participationType }) => {
52
+ if (sequence.mechanism === "auction") {
53
+ return <button onClick={() => void sequence.bid("125.00")}>Bid</button>;
54
+ }
55
+ if (sequence.mechanism === "appointment") {
56
+ return <button onClick={() => void sequence.book("slot_123")}>Book</button>;
57
+ }
58
+ return <button onClick={() => void sequence.enter()}>Enter {participationType}</button>;
59
+ },
60
+ }}
61
+ />
62
+ ```
63
+
64
+ `participationType` is the slot prop carrying `sequence.mechanism` for the five non-waitlist mechanisms: `queue`, `draw`, `auction`, `timed_release`, or `appointment`. See the Mechanisms section of the core SDK `JOURNEY_MODEL.md`.
65
+
66
+ ## `slots.participating`
67
+
68
+ `slots.participating` receives the typed `sequence` view for `queue`, `draw`, `auction`, `timed_release`, and `appointment`.
69
+
70
+ - `queue`, `draw`: `state$`, `leave()`.
71
+ - `auction`: `state$`, `bid(amount)`, `leave()`.
72
+ - `timed_release`: `state$`, `leave()`, `complete()`.
73
+ - `appointment`: `state$`, `cancel(reason?)`, `reschedule(newSlotId, newLocationId?)`.
74
+
75
+ `position` is populated when queue display state includes a queue position.
76
+
77
+ ## Default Rendering
78
+
79
+ Without a slot:
80
+
81
+ - `ready` uses `StartView`.
82
+ - `routing` and loading fallbacks use `LoadingView`.
83
+ - `gated` uses `JourneyGate` or `ChallengeGate`.
84
+ - `scheduled` and enterable `waitlist` use `UpcomingModule`.
85
+ - participating `waitlist` uses `WaitlistView`.
86
+ - `queue`, `draw`, `auction`, `timed_release`, and `appointment` use their default modules for supported phases.
87
+ - `granted` uses the distribution module's granted panel.
88
+ - `ended` uses the distribution module's outcome UI for `queue`, `draw`, `auction`, `timed_release`, and `appointment`; `unavailable` uses `EndedModule`.
89
+ - ended `waitlist` can be handled with `slots.ended` or full custom rendering.
@@ -0,0 +1,61 @@
1
+ # Styling Reference
2
+
3
+ `@fanfare-io/fanfare-sdk-react` ships dressed components — `ExperienceWidget`, the
4
+ sequence modules, and slot helpers — that read from a single theme context.
5
+ Most customers should rely on the defaults, then override at four levels of
6
+ increasing specificity:
7
+
8
+ 1. `theme` for brand colors, typography, and imagery
9
+ 2. `variant` for packaged presentation styles
10
+ 3. `slots` for swapping specific journey or sequence states
11
+ 4. `children` or `useExperienceJourney` for full custom rendering
12
+
13
+ The rendered surface follows the SDK model: `journeyStage`, then
14
+ `sequence.phase`, then `sequence.mechanism`. Styling changes presentation only;
15
+ the server stays authoritative over which actions are permitted.
16
+
17
+ ## Theme
18
+
19
+ Pass a `BrandTheme` to `ThemeProvider` or directly to `ExperienceWidget`.
20
+
21
+ ```tsx
22
+ <ThemeProvider theme={{ primary: "#0f766e", fontHeading: "Instrument Serif, serif" }}>
23
+ <ExperienceWidget experienceId="exp_123" />
24
+ </ThemeProvider>
25
+ ```
26
+
27
+ Per-widget overrides are shallow-merged over the current theme.
28
+
29
+ ## Variant
30
+
31
+ `ExperienceWidget` supports packaged variants:
32
+
33
+ - `default`
34
+ - `retro`
35
+ - `rounded`
36
+ - `clean`
37
+
38
+ Use variants for presentation differences, not for business logic.
39
+
40
+ ## Slots
41
+
42
+ Replace specific sections with slots — the slot's render prop is called when
43
+ that state is active. All other states keep their default rendering.
44
+
45
+ ```tsx
46
+ <ExperienceWidget
47
+ experienceId="exp_123"
48
+ slots={{
49
+ upcoming: ({ startsAt, onEnter }) => <YourUpcomingCard startsAt={startsAt} onCta={onEnter} />,
50
+ }}
51
+ />
52
+ ```
53
+
54
+ See [`docs/SLOTS.md`](./SLOTS.md) for the full slot inventory.
55
+
56
+ ## Custom rendering
57
+
58
+ For full control, pass `children` to `ExperienceWidget` (a render prop
59
+ receiving `{ journey, view, snapshot, error, start, isStarting }`) or assemble
60
+ your own UI from `useExperienceJourney`. This is the lowest-level integration
61
+ and bypasses slots entirely.
package/package.json ADDED
@@ -0,0 +1,145 @@
1
+ {
2
+ "name": "@fanfare-io/fanfare-sdk-react",
3
+ "version": "0.1.0",
4
+ "description": "React adapter for Fanfare SDK",
5
+ "publishConfig": {
6
+ "registry": "https://registry.npmjs.org",
7
+ "access": "public"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/fanfare-io/fanfare-sdk.git"
12
+ },
13
+ "homepage": "https://docs.fanfare.io/sdk/components/react",
14
+ "bugs": {
15
+ "url": "https://github.com/fanfare-io/fanfare-sdk/issues"
16
+ },
17
+ "type": "module",
18
+ "files": [
19
+ "dist/",
20
+ "docs/",
21
+ "README.md"
22
+ ],
23
+ "module": "./dist/index.js",
24
+ "types": "./dist/index.d.ts",
25
+ "exports": {
26
+ ".": {
27
+ "types": "./dist/index.d.ts",
28
+ "import": "./dist/index.js"
29
+ },
30
+ "./styles": {
31
+ "import": "./dist/styles/base.css"
32
+ },
33
+ "./package.json": "./package.json"
34
+ },
35
+ "peerDependencies": {
36
+ "motion": "^12.23.12",
37
+ "react": "^18.0.0 || ^19.1.1",
38
+ "react-dom": "^18.0.0 || ^19.1.1"
39
+ },
40
+ "dependencies": {
41
+ "canvas-confetti": "^1.9.4",
42
+ "class-variance-authority": "^0.7.1",
43
+ "clsx": "^2.1.1",
44
+ "nanostores": "^1.1.0",
45
+ "tailwind-merge": "^2.6.0",
46
+ "valibot": "^1.1.0",
47
+ "@fanfare-io/fanfare-sdk-core": "0.1.0",
48
+ "@fanfare-io/fanfare-sdk-i18n": "0.1.0"
49
+ },
50
+ "devDependencies": {
51
+ "@iconify-json/heroicons": "^1.2.3",
52
+ "@radix-ui/react-dialog": "^1.1.7",
53
+ "@radix-ui/react-label": "^2.1.8",
54
+ "@radix-ui/react-slot": "^1.2.4",
55
+ "@radix-ui/react-switch": "^1.2.4",
56
+ "@size-limit/preset-small-lib": "^11.1.8",
57
+ "@storybook/addon-a11y": "^10.4.0",
58
+ "@storybook/addon-docs": "^10.4.0",
59
+ "@storybook/addon-themes": "^10.4.0",
60
+ "@storybook/addon-vitest": "~10.2.16",
61
+ "@storybook/react-vite": "^10.4.0",
62
+ "@tailwindcss/cli": "^4.1.16",
63
+ "@tailwindcss/vite": "^4.1.3",
64
+ "@testing-library/jest-dom": "^6.6.4",
65
+ "@testing-library/react": "^16.3.2",
66
+ "@testing-library/user-event": "^14.6.1",
67
+ "@types/canvas-confetti": "^1.9.0",
68
+ "@types/react": "^18.0.0",
69
+ "@types/react-dom": "^18.0.0",
70
+ "@vitejs/plugin-react": "^5.0.2",
71
+ "@vitest/browser": "^3.2.4",
72
+ "@vitest/ui": "^3.2.4",
73
+ "jsdom": "^26.1.0",
74
+ "motion": "^12.23.12",
75
+ "playwright": "^1.51.1",
76
+ "prettier": "^3.5.3",
77
+ "prettier-plugin-organize-imports": "^4.1.0",
78
+ "react": "^18.0.0",
79
+ "react-dom": "^18.0.0",
80
+ "react-hook-form": "^7.56.2",
81
+ "rimraf": "^6.0.1",
82
+ "size-limit": "^11.1.8",
83
+ "storybook": "^10.4.0",
84
+ "tailwindcss": "^4.1.3",
85
+ "tailwindcss-animate": "^1.0.7",
86
+ "typescript": "^5.9.2",
87
+ "unplugin-icons": "^22.1.0",
88
+ "vite": "^7.3.1",
89
+ "vite-plugin-dts": "^4.5.4",
90
+ "vite-tsconfig-paths": "^5.1.4",
91
+ "vitest": "^3.2.4",
92
+ "@fanfare-io/fanfare-sdk-core": "0.1.0",
93
+ "@fanfare-io/fanfare-sdk-solid": "0.1.0"
94
+ },
95
+ "sideEffects": [
96
+ "**/*.css"
97
+ ],
98
+ "keywords": [
99
+ "fanfare",
100
+ "react",
101
+ "hooks",
102
+ "sdk",
103
+ "queue",
104
+ "ecommerce"
105
+ ],
106
+ "license": "Apache-2.0",
107
+ "size-limit": [
108
+ {
109
+ "name": "React adapter",
110
+ "path": "dist/index.js",
111
+ "limit": "50 KB",
112
+ "ignore": [
113
+ "react",
114
+ "react-dom",
115
+ "react/jsx-runtime",
116
+ "motion",
117
+ "motion/react",
118
+ "@fanfare-io/fanfare-sdk-core",
119
+ "@fanfare-io/fanfare-sdk-core/internals",
120
+ "@fanfare-io/fanfare-sdk-core/test-utils",
121
+ "@fanfare-io/fanfare-sdk-i18n"
122
+ ]
123
+ }
124
+ ],
125
+ "scripts": {
126
+ "prebuild": "rimraf dist && pnpm -F @fanfare-io/fanfare-sdk-i18n build:deps && pnpm -F @fanfare-io/fanfare-sdk-core build:deps",
127
+ "pretest": "pnpm -F @fanfare-io/fanfare-sdk-i18n build:deps && pnpm -F @fanfare-io/fanfare-sdk-core build:deps && pnpm -F @fanfare-io/fanfare-sdk-solid build:incremental",
128
+ "build": "vite build && tailwindcss -i src/styles/base.css -o dist/styles/base.css && pnpm size",
129
+ "build:incremental": "vite build --emptyOutDir false",
130
+ "build:strict": "pnpm typecheck && pnpm build",
131
+ "build-storybook": "storybook build",
132
+ "dev": "vite build --watch",
133
+ "fmt": "prettier src --write",
134
+ "lint": "oxlint src",
135
+ "lint:fix": "oxlint --fix src",
136
+ "size": "size-limit",
137
+ "storybook": "storybook dev -p 6006 --no-open",
138
+ "test": "vitest run --project unit",
139
+ "test:browser": "vitest run --project storybook",
140
+ "test:watch": "vitest --project unit",
141
+ "test:unit": "vitest run --project unit --passWithNoTests",
142
+ "test:component": "vitest run .test.tsx --passWithNoTests",
143
+ "typecheck": "tsc --noEmit"
144
+ }
145
+ }