@demokit-ai/react 0.0.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.
- package/dist/index.cjs +231 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +235 -0
- package/dist/index.d.ts +235 -0
- package/dist/index.js +223 -0
- package/dist/index.js.map +1 -0
- package/package.json +61 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var core = require('@demokit-ai/core');
|
|
5
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
6
|
+
|
|
7
|
+
// src/provider.tsx
|
|
8
|
+
var DemoModeContext = react.createContext(void 0);
|
|
9
|
+
DemoModeContext.displayName = "DemoModeContext";
|
|
10
|
+
function DemoKitProvider({
|
|
11
|
+
children,
|
|
12
|
+
fixtures,
|
|
13
|
+
storageKey = "demokit-mode",
|
|
14
|
+
initialEnabled = false,
|
|
15
|
+
onDemoModeChange,
|
|
16
|
+
baseUrl
|
|
17
|
+
}) {
|
|
18
|
+
const [isDemoMode, setIsDemoMode] = react.useState(initialEnabled);
|
|
19
|
+
const [isHydrated, setIsHydrated] = react.useState(false);
|
|
20
|
+
const interceptorRef = react.useRef(null);
|
|
21
|
+
const initializedRef = react.useRef(false);
|
|
22
|
+
react.useEffect(() => {
|
|
23
|
+
if (initializedRef.current) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
initializedRef.current = true;
|
|
27
|
+
interceptorRef.current = core.createDemoInterceptor({
|
|
28
|
+
fixtures,
|
|
29
|
+
storageKey,
|
|
30
|
+
initialEnabled,
|
|
31
|
+
baseUrl,
|
|
32
|
+
onEnable: () => {
|
|
33
|
+
setIsDemoMode(true);
|
|
34
|
+
onDemoModeChange?.(true);
|
|
35
|
+
},
|
|
36
|
+
onDisable: () => {
|
|
37
|
+
setIsDemoMode(false);
|
|
38
|
+
onDemoModeChange?.(false);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
const storedState = interceptorRef.current.isEnabled();
|
|
42
|
+
setIsDemoMode(storedState);
|
|
43
|
+
setIsHydrated(true);
|
|
44
|
+
return () => {
|
|
45
|
+
interceptorRef.current?.destroy();
|
|
46
|
+
interceptorRef.current = null;
|
|
47
|
+
initializedRef.current = false;
|
|
48
|
+
};
|
|
49
|
+
}, []);
|
|
50
|
+
react.useEffect(() => {
|
|
51
|
+
if (interceptorRef.current && isHydrated) {
|
|
52
|
+
interceptorRef.current.setFixtures(fixtures);
|
|
53
|
+
}
|
|
54
|
+
}, [fixtures, isHydrated]);
|
|
55
|
+
const enable = react.useCallback(() => {
|
|
56
|
+
interceptorRef.current?.enable();
|
|
57
|
+
}, []);
|
|
58
|
+
const disable = react.useCallback(() => {
|
|
59
|
+
interceptorRef.current?.disable();
|
|
60
|
+
}, []);
|
|
61
|
+
const toggle = react.useCallback(() => {
|
|
62
|
+
interceptorRef.current?.toggle();
|
|
63
|
+
}, []);
|
|
64
|
+
const setDemoMode = react.useCallback((enabled) => {
|
|
65
|
+
if (enabled) {
|
|
66
|
+
interceptorRef.current?.enable();
|
|
67
|
+
} else {
|
|
68
|
+
interceptorRef.current?.disable();
|
|
69
|
+
}
|
|
70
|
+
}, []);
|
|
71
|
+
const resetSession = react.useCallback(() => {
|
|
72
|
+
interceptorRef.current?.resetSession();
|
|
73
|
+
}, []);
|
|
74
|
+
const getSession = react.useCallback(() => {
|
|
75
|
+
return interceptorRef.current?.getSession() ?? null;
|
|
76
|
+
}, []);
|
|
77
|
+
const value = react.useMemo(
|
|
78
|
+
() => ({
|
|
79
|
+
isDemoMode,
|
|
80
|
+
isHydrated,
|
|
81
|
+
enable,
|
|
82
|
+
disable,
|
|
83
|
+
toggle,
|
|
84
|
+
setDemoMode,
|
|
85
|
+
resetSession,
|
|
86
|
+
getSession
|
|
87
|
+
}),
|
|
88
|
+
[isDemoMode, isHydrated, enable, disable, toggle, setDemoMode, resetSession, getSession]
|
|
89
|
+
);
|
|
90
|
+
return /* @__PURE__ */ jsxRuntime.jsx(DemoModeContext.Provider, { value, children });
|
|
91
|
+
}
|
|
92
|
+
function useDemoMode() {
|
|
93
|
+
const context = react.useContext(DemoModeContext);
|
|
94
|
+
if (context === void 0) {
|
|
95
|
+
throw new Error(
|
|
96
|
+
"useDemoMode must be used within a DemoKitProvider. Make sure to wrap your app with <DemoKitProvider>."
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
return context;
|
|
100
|
+
}
|
|
101
|
+
function useIsDemoMode() {
|
|
102
|
+
return useDemoMode().isDemoMode;
|
|
103
|
+
}
|
|
104
|
+
function useIsHydrated() {
|
|
105
|
+
return useDemoMode().isHydrated;
|
|
106
|
+
}
|
|
107
|
+
function useDemoSession() {
|
|
108
|
+
return useDemoMode().getSession();
|
|
109
|
+
}
|
|
110
|
+
function EyeIcon() {
|
|
111
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
112
|
+
"svg",
|
|
113
|
+
{
|
|
114
|
+
width: "20",
|
|
115
|
+
height: "20",
|
|
116
|
+
viewBox: "0 0 24 24",
|
|
117
|
+
fill: "none",
|
|
118
|
+
stroke: "currentColor",
|
|
119
|
+
strokeWidth: "2",
|
|
120
|
+
strokeLinecap: "round",
|
|
121
|
+
strokeLinejoin: "round",
|
|
122
|
+
"aria-hidden": "true",
|
|
123
|
+
children: [
|
|
124
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" }),
|
|
125
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "3" })
|
|
126
|
+
]
|
|
127
|
+
}
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
var defaultStyles = {
|
|
131
|
+
container: {
|
|
132
|
+
display: "flex",
|
|
133
|
+
alignItems: "center",
|
|
134
|
+
justifyContent: "space-between",
|
|
135
|
+
padding: "8px 16px",
|
|
136
|
+
backgroundColor: "#fef3c7",
|
|
137
|
+
borderBottom: "1px solid rgba(217, 119, 6, 0.2)",
|
|
138
|
+
fontSize: "14px",
|
|
139
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif'
|
|
140
|
+
},
|
|
141
|
+
content: {
|
|
142
|
+
display: "flex",
|
|
143
|
+
alignItems: "center",
|
|
144
|
+
gap: "8px"
|
|
145
|
+
},
|
|
146
|
+
icon: {
|
|
147
|
+
color: "#d97706",
|
|
148
|
+
flexShrink: 0
|
|
149
|
+
},
|
|
150
|
+
label: {
|
|
151
|
+
fontWeight: 600,
|
|
152
|
+
color: "#78350f"
|
|
153
|
+
},
|
|
154
|
+
description: {
|
|
155
|
+
color: "rgba(120, 53, 15, 0.7)",
|
|
156
|
+
fontSize: "12px"
|
|
157
|
+
},
|
|
158
|
+
button: {
|
|
159
|
+
padding: "4px 12px",
|
|
160
|
+
fontSize: "14px",
|
|
161
|
+
backgroundColor: "transparent",
|
|
162
|
+
border: "1px solid rgba(217, 119, 6, 0.3)",
|
|
163
|
+
borderRadius: "4px",
|
|
164
|
+
cursor: "pointer",
|
|
165
|
+
color: "#78350f",
|
|
166
|
+
fontFamily: "inherit",
|
|
167
|
+
transition: "background-color 0.15s ease"
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
function DemoModeBanner({
|
|
171
|
+
className = "",
|
|
172
|
+
exitLabel = "Exit Demo Mode",
|
|
173
|
+
demoLabel = "Demo Mode Active",
|
|
174
|
+
description = "Changes are simulated and not saved",
|
|
175
|
+
showIcon = true,
|
|
176
|
+
style,
|
|
177
|
+
onExit
|
|
178
|
+
}) {
|
|
179
|
+
const { isDemoMode, isHydrated, disable } = useDemoMode();
|
|
180
|
+
if (!isHydrated || !isDemoMode) {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
const handleExit = () => {
|
|
184
|
+
if (onExit) {
|
|
185
|
+
onExit();
|
|
186
|
+
} else {
|
|
187
|
+
disable();
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
191
|
+
"div",
|
|
192
|
+
{
|
|
193
|
+
className: `demokit-banner ${className}`.trim(),
|
|
194
|
+
style: { ...defaultStyles.container, ...style },
|
|
195
|
+
role: "status",
|
|
196
|
+
"aria-live": "polite",
|
|
197
|
+
children: [
|
|
198
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: defaultStyles.content, children: [
|
|
199
|
+
showIcon && /* @__PURE__ */ jsxRuntime.jsx("span", { style: defaultStyles.icon, children: /* @__PURE__ */ jsxRuntime.jsx(EyeIcon, {}) }),
|
|
200
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: defaultStyles.label, children: demoLabel }),
|
|
201
|
+
description && /* @__PURE__ */ jsxRuntime.jsx("span", { style: defaultStyles.description, children: description })
|
|
202
|
+
] }),
|
|
203
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
204
|
+
"button",
|
|
205
|
+
{
|
|
206
|
+
onClick: handleExit,
|
|
207
|
+
style: defaultStyles.button,
|
|
208
|
+
onMouseOver: (e) => {
|
|
209
|
+
e.currentTarget.style.backgroundColor = "rgba(217, 119, 6, 0.1)";
|
|
210
|
+
},
|
|
211
|
+
onMouseOut: (e) => {
|
|
212
|
+
e.currentTarget.style.backgroundColor = "transparent";
|
|
213
|
+
},
|
|
214
|
+
type: "button",
|
|
215
|
+
children: exitLabel
|
|
216
|
+
}
|
|
217
|
+
)
|
|
218
|
+
]
|
|
219
|
+
}
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
exports.DemoKitProvider = DemoKitProvider;
|
|
224
|
+
exports.DemoModeBanner = DemoModeBanner;
|
|
225
|
+
exports.DemoModeContext = DemoModeContext;
|
|
226
|
+
exports.useDemoMode = useDemoMode;
|
|
227
|
+
exports.useDemoSession = useDemoSession;
|
|
228
|
+
exports.useIsDemoMode = useIsDemoMode;
|
|
229
|
+
exports.useIsHydrated = useIsHydrated;
|
|
230
|
+
//# sourceMappingURL=index.cjs.map
|
|
231
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/context.ts","../src/provider.tsx","../src/hooks.ts","../src/banner.tsx"],"names":["createContext","useState","useRef","useEffect","createDemoInterceptor","useCallback","useMemo","jsx","useContext","jsxs"],"mappings":"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","file":"index.cjs","sourcesContent":["import { createContext } from 'react'\nimport type { DemoModeContextValue } from './types'\n\n/**\n * React context for demo mode state\n * @internal\n */\nexport const DemoModeContext = createContext<DemoModeContextValue | undefined>(undefined)\n\nDemoModeContext.displayName = 'DemoModeContext'\n","'use client'\n\nimport { useState, useEffect, useCallback, useMemo, useRef } from 'react'\nimport { createDemoInterceptor, type DemoInterceptor, type SessionState } from '@demokit-ai/core'\nimport { DemoModeContext } from './context'\nimport type { DemoKitProviderProps, DemoModeContextValue } from './types'\n\n/**\n * Provider component that enables demo mode functionality\n *\n * Wraps your app to provide demo mode state and controls.\n * Handles SSR hydration safely and persists state to localStorage.\n *\n * @example\n * const fixtures = {\n * 'GET /api/users': () => [{ id: '1', name: 'Demo User' }],\n * 'GET /api/users/:id': ({ params }) => ({ id: params.id, name: 'Demo User' }),\n * }\n *\n * function App() {\n * return (\n * <DemoKitProvider\n * fixtures={fixtures}\n * onDemoModeChange={(enabled) => console.log('Demo mode:', enabled)}\n * >\n * <YourApp />\n * </DemoKitProvider>\n * )\n * }\n */\nexport function DemoKitProvider({\n children,\n fixtures,\n storageKey = 'demokit-mode',\n initialEnabled = false,\n onDemoModeChange,\n baseUrl,\n}: DemoKitProviderProps) {\n // Start with initialEnabled for SSR to avoid hydration mismatch\n const [isDemoMode, setIsDemoMode] = useState(initialEnabled)\n const [isHydrated, setIsHydrated] = useState(false)\n\n // Keep a ref to the interceptor instance\n const interceptorRef = useRef<DemoInterceptor | null>(null)\n\n // Track if we've initialized\n const initializedRef = useRef(false)\n\n // Initialize interceptor on mount (client-side only)\n useEffect(() => {\n if (initializedRef.current) {\n return\n }\n initializedRef.current = true\n\n interceptorRef.current = createDemoInterceptor({\n fixtures,\n storageKey,\n initialEnabled,\n baseUrl,\n onEnable: () => {\n setIsDemoMode(true)\n onDemoModeChange?.(true)\n },\n onDisable: () => {\n setIsDemoMode(false)\n onDemoModeChange?.(false)\n },\n })\n\n // Sync state from storage after hydration\n const storedState = interceptorRef.current.isEnabled()\n setIsDemoMode(storedState)\n\n // Mark as hydrated\n setIsHydrated(true)\n\n return () => {\n interceptorRef.current?.destroy()\n interceptorRef.current = null\n initializedRef.current = false\n }\n }, []) // Empty deps - only run once on mount\n\n // Update fixtures if they change\n useEffect(() => {\n if (interceptorRef.current && isHydrated) {\n interceptorRef.current.setFixtures(fixtures)\n }\n }, [fixtures, isHydrated])\n\n const enable = useCallback(() => {\n interceptorRef.current?.enable()\n }, [])\n\n const disable = useCallback(() => {\n interceptorRef.current?.disable()\n }, [])\n\n const toggle = useCallback(() => {\n interceptorRef.current?.toggle()\n }, [])\n\n const setDemoMode = useCallback((enabled: boolean) => {\n if (enabled) {\n interceptorRef.current?.enable()\n } else {\n interceptorRef.current?.disable()\n }\n }, [])\n\n const resetSession = useCallback(() => {\n interceptorRef.current?.resetSession()\n }, [])\n\n const getSession = useCallback((): SessionState | null => {\n return interceptorRef.current?.getSession() ?? null\n }, [])\n\n const value = useMemo<DemoModeContextValue>(\n () => ({\n isDemoMode,\n isHydrated,\n enable,\n disable,\n toggle,\n setDemoMode,\n resetSession,\n getSession,\n }),\n [isDemoMode, isHydrated, enable, disable, toggle, setDemoMode, resetSession, getSession]\n )\n\n return (\n <DemoModeContext.Provider value={value}>\n {children}\n </DemoModeContext.Provider>\n )\n}\n","'use client'\n\nimport { useContext } from 'react'\nimport { DemoModeContext } from './context'\nimport type { DemoModeContextValue } from './types'\n\n/**\n * Hook to access demo mode state and controls\n *\n * @returns Demo mode context value with state and control methods\n * @throws Error if used outside of DemoKitProvider\n *\n * @example\n * function MyComponent() {\n * const { isDemoMode, isHydrated, toggle } = useDemoMode()\n *\n * // Wait for hydration before rendering demo-dependent UI\n * if (!isHydrated) {\n * return <Loading />\n * }\n *\n * return (\n * <div>\n * <p>Demo mode: {isDemoMode ? 'ON' : 'OFF'}</p>\n * <button onClick={toggle}>Toggle</button>\n * </div>\n * )\n * }\n */\nexport function useDemoMode(): DemoModeContextValue {\n const context = useContext(DemoModeContext)\n\n if (context === undefined) {\n throw new Error(\n 'useDemoMode must be used within a DemoKitProvider. ' +\n 'Make sure to wrap your app with <DemoKitProvider>.'\n )\n }\n\n return context\n}\n\n/**\n * Hook to check if demo mode is enabled\n * Shorthand for useDemoMode().isDemoMode\n *\n * @returns Whether demo mode is enabled\n */\nexport function useIsDemoMode(): boolean {\n return useDemoMode().isDemoMode\n}\n\n/**\n * Hook to check if the component has hydrated\n * Shorthand for useDemoMode().isHydrated\n *\n * @returns Whether the component has hydrated\n */\nexport function useIsHydrated(): boolean {\n return useDemoMode().isHydrated\n}\n\n/**\n * Hook to access the session state\n * Shorthand for useDemoMode().getSession()\n *\n * @returns The session state, or null if not yet initialized\n *\n * @example\n * function MyComponent() {\n * const session = useDemoSession()\n *\n * const cart = session?.get<CartItem[]>('cart') || []\n * const addToCart = (item: CartItem) => {\n * session?.set('cart', [...cart, item])\n * }\n *\n * return <CartView items={cart} onAdd={addToCart} />\n * }\n */\nexport function useDemoSession() {\n return useDemoMode().getSession()\n}\n","'use client'\n\nimport { useDemoMode } from './hooks'\nimport type { DemoModeBannerProps } from './types'\n\n/**\n * Eye icon SVG component\n */\nfunction EyeIcon() {\n return (\n <svg\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z\" />\n <circle cx=\"12\" cy=\"12\" r=\"3\" />\n </svg>\n )\n}\n\n/**\n * Default styles for the banner\n */\nconst defaultStyles = {\n container: {\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n padding: '8px 16px',\n backgroundColor: '#fef3c7',\n borderBottom: '1px solid rgba(217, 119, 6, 0.2)',\n fontSize: '14px',\n fontFamily:\n '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif',\n } as React.CSSProperties,\n content: {\n display: 'flex',\n alignItems: 'center',\n gap: '8px',\n } as React.CSSProperties,\n icon: {\n color: '#d97706',\n flexShrink: 0,\n } as React.CSSProperties,\n label: {\n fontWeight: 600,\n color: '#78350f',\n } as React.CSSProperties,\n description: {\n color: 'rgba(120, 53, 15, 0.7)',\n fontSize: '12px',\n } as React.CSSProperties,\n button: {\n padding: '4px 12px',\n fontSize: '14px',\n backgroundColor: 'transparent',\n border: '1px solid rgba(217, 119, 6, 0.3)',\n borderRadius: '4px',\n cursor: 'pointer',\n color: '#78350f',\n fontFamily: 'inherit',\n transition: 'background-color 0.15s ease',\n } as React.CSSProperties,\n}\n\n/**\n * A ready-to-use banner component that shows when demo mode is active\n *\n * Displays a prominent amber banner with a label, description, and exit button.\n * Automatically hides when demo mode is disabled or before hydration.\n *\n * @example\n * function App() {\n * return (\n * <DemoKitProvider fixtures={fixtures}>\n * <DemoModeBanner />\n * <YourApp />\n * </DemoKitProvider>\n * )\n * }\n *\n * @example Custom labels\n * <DemoModeBanner\n * demoLabel=\"Preview Mode\"\n * description=\"You're viewing sample data\"\n * exitLabel=\"Exit Preview\"\n * />\n */\nexport function DemoModeBanner({\n className = '',\n exitLabel = 'Exit Demo Mode',\n demoLabel = 'Demo Mode Active',\n description = 'Changes are simulated and not saved',\n showIcon = true,\n style,\n onExit,\n}: DemoModeBannerProps) {\n const { isDemoMode, isHydrated, disable } = useDemoMode()\n\n // Don't render until hydrated to avoid hydration mismatch\n if (!isHydrated || !isDemoMode) {\n return null\n }\n\n const handleExit = () => {\n if (onExit) {\n onExit()\n } else {\n disable()\n }\n }\n\n return (\n <div\n className={`demokit-banner ${className}`.trim()}\n style={{ ...defaultStyles.container, ...style }}\n role=\"status\"\n aria-live=\"polite\"\n >\n <div style={defaultStyles.content}>\n {showIcon && (\n <span style={defaultStyles.icon}>\n <EyeIcon />\n </span>\n )}\n <span style={defaultStyles.label}>{demoLabel}</span>\n {description && <span style={defaultStyles.description}>{description}</span>}\n </div>\n <button\n onClick={handleExit}\n style={defaultStyles.button}\n onMouseOver={(e) => {\n e.currentTarget.style.backgroundColor = 'rgba(217, 119, 6, 0.1)'\n }}\n onMouseOut={(e) => {\n e.currentTarget.style.backgroundColor = 'transparent'\n }}\n type=\"button\"\n >\n {exitLabel}\n </button>\n </div>\n )\n}\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import * as _demokit_ai_core from '@demokit-ai/core';
|
|
3
|
+
import { FixtureMap, SessionState } from '@demokit-ai/core';
|
|
4
|
+
export { FixtureHandler, FixtureMap, RequestContext, SessionState } from '@demokit-ai/core';
|
|
5
|
+
import * as react from 'react';
|
|
6
|
+
import { ReactNode } from 'react';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Props for the DemoKitProvider component
|
|
10
|
+
*/
|
|
11
|
+
interface DemoKitProviderProps {
|
|
12
|
+
/**
|
|
13
|
+
* Child components to render
|
|
14
|
+
*/
|
|
15
|
+
children: ReactNode;
|
|
16
|
+
/**
|
|
17
|
+
* Map of URL patterns to fixture handlers
|
|
18
|
+
*/
|
|
19
|
+
fixtures: FixtureMap;
|
|
20
|
+
/**
|
|
21
|
+
* localStorage key for persisting demo mode state
|
|
22
|
+
* @default 'demokit-mode'
|
|
23
|
+
*/
|
|
24
|
+
storageKey?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Whether demo mode should be initially enabled
|
|
27
|
+
* If not provided, will read from localStorage
|
|
28
|
+
* @default false
|
|
29
|
+
*/
|
|
30
|
+
initialEnabled?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Callback invoked when demo mode state changes
|
|
33
|
+
*/
|
|
34
|
+
onDemoModeChange?: (enabled: boolean) => void;
|
|
35
|
+
/**
|
|
36
|
+
* Base URL to use for relative URL parsing
|
|
37
|
+
* @default 'http://localhost'
|
|
38
|
+
*/
|
|
39
|
+
baseUrl?: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Value provided by the DemoMode context
|
|
43
|
+
*/
|
|
44
|
+
interface DemoModeContextValue {
|
|
45
|
+
/**
|
|
46
|
+
* Whether demo mode is currently enabled
|
|
47
|
+
*/
|
|
48
|
+
isDemoMode: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Whether the component has hydrated (for SSR safety)
|
|
51
|
+
* Always check this before rendering demo-dependent UI
|
|
52
|
+
*/
|
|
53
|
+
isHydrated: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Enable demo mode
|
|
56
|
+
*/
|
|
57
|
+
enable(): void;
|
|
58
|
+
/**
|
|
59
|
+
* Disable demo mode
|
|
60
|
+
*/
|
|
61
|
+
disable(): void;
|
|
62
|
+
/**
|
|
63
|
+
* Toggle demo mode and return the new state
|
|
64
|
+
*/
|
|
65
|
+
toggle(): void;
|
|
66
|
+
/**
|
|
67
|
+
* Set demo mode to a specific state
|
|
68
|
+
*/
|
|
69
|
+
setDemoMode(enabled: boolean): void;
|
|
70
|
+
/**
|
|
71
|
+
* Reset the session state, clearing all stored data
|
|
72
|
+
* Call this to manually reset the demo session without page refresh
|
|
73
|
+
*/
|
|
74
|
+
resetSession(): void;
|
|
75
|
+
/**
|
|
76
|
+
* Get the current session state instance
|
|
77
|
+
* Useful for inspecting or manipulating session state directly
|
|
78
|
+
* Returns null if the interceptor hasn't been initialized yet
|
|
79
|
+
*/
|
|
80
|
+
getSession(): SessionState | null;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Props for the DemoModeBanner component
|
|
84
|
+
*/
|
|
85
|
+
interface DemoModeBannerProps {
|
|
86
|
+
/**
|
|
87
|
+
* Additional CSS class name
|
|
88
|
+
*/
|
|
89
|
+
className?: string;
|
|
90
|
+
/**
|
|
91
|
+
* Label for the exit button
|
|
92
|
+
* @default 'Exit Demo Mode'
|
|
93
|
+
*/
|
|
94
|
+
exitLabel?: string;
|
|
95
|
+
/**
|
|
96
|
+
* Label shown when demo mode is active
|
|
97
|
+
* @default 'Demo Mode Active'
|
|
98
|
+
*/
|
|
99
|
+
demoLabel?: string;
|
|
100
|
+
/**
|
|
101
|
+
* Description shown in the banner
|
|
102
|
+
* @default 'Changes are simulated and not saved'
|
|
103
|
+
*/
|
|
104
|
+
description?: string;
|
|
105
|
+
/**
|
|
106
|
+
* Whether to show the eye icon
|
|
107
|
+
* @default true
|
|
108
|
+
*/
|
|
109
|
+
showIcon?: boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Custom styles for the banner container
|
|
112
|
+
*/
|
|
113
|
+
style?: React.CSSProperties;
|
|
114
|
+
/**
|
|
115
|
+
* Callback when exit button is clicked
|
|
116
|
+
* If not provided, will call disable() from context
|
|
117
|
+
*/
|
|
118
|
+
onExit?: () => void;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Provider component that enables demo mode functionality
|
|
123
|
+
*
|
|
124
|
+
* Wraps your app to provide demo mode state and controls.
|
|
125
|
+
* Handles SSR hydration safely and persists state to localStorage.
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* const fixtures = {
|
|
129
|
+
* 'GET /api/users': () => [{ id: '1', name: 'Demo User' }],
|
|
130
|
+
* 'GET /api/users/:id': ({ params }) => ({ id: params.id, name: 'Demo User' }),
|
|
131
|
+
* }
|
|
132
|
+
*
|
|
133
|
+
* function App() {
|
|
134
|
+
* return (
|
|
135
|
+
* <DemoKitProvider
|
|
136
|
+
* fixtures={fixtures}
|
|
137
|
+
* onDemoModeChange={(enabled) => console.log('Demo mode:', enabled)}
|
|
138
|
+
* >
|
|
139
|
+
* <YourApp />
|
|
140
|
+
* </DemoKitProvider>
|
|
141
|
+
* )
|
|
142
|
+
* }
|
|
143
|
+
*/
|
|
144
|
+
declare function DemoKitProvider({ children, fixtures, storageKey, initialEnabled, onDemoModeChange, baseUrl, }: DemoKitProviderProps): react_jsx_runtime.JSX.Element;
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Hook to access demo mode state and controls
|
|
148
|
+
*
|
|
149
|
+
* @returns Demo mode context value with state and control methods
|
|
150
|
+
* @throws Error if used outside of DemoKitProvider
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* function MyComponent() {
|
|
154
|
+
* const { isDemoMode, isHydrated, toggle } = useDemoMode()
|
|
155
|
+
*
|
|
156
|
+
* // Wait for hydration before rendering demo-dependent UI
|
|
157
|
+
* if (!isHydrated) {
|
|
158
|
+
* return <Loading />
|
|
159
|
+
* }
|
|
160
|
+
*
|
|
161
|
+
* return (
|
|
162
|
+
* <div>
|
|
163
|
+
* <p>Demo mode: {isDemoMode ? 'ON' : 'OFF'}</p>
|
|
164
|
+
* <button onClick={toggle}>Toggle</button>
|
|
165
|
+
* </div>
|
|
166
|
+
* )
|
|
167
|
+
* }
|
|
168
|
+
*/
|
|
169
|
+
declare function useDemoMode(): DemoModeContextValue;
|
|
170
|
+
/**
|
|
171
|
+
* Hook to check if demo mode is enabled
|
|
172
|
+
* Shorthand for useDemoMode().isDemoMode
|
|
173
|
+
*
|
|
174
|
+
* @returns Whether demo mode is enabled
|
|
175
|
+
*/
|
|
176
|
+
declare function useIsDemoMode(): boolean;
|
|
177
|
+
/**
|
|
178
|
+
* Hook to check if the component has hydrated
|
|
179
|
+
* Shorthand for useDemoMode().isHydrated
|
|
180
|
+
*
|
|
181
|
+
* @returns Whether the component has hydrated
|
|
182
|
+
*/
|
|
183
|
+
declare function useIsHydrated(): boolean;
|
|
184
|
+
/**
|
|
185
|
+
* Hook to access the session state
|
|
186
|
+
* Shorthand for useDemoMode().getSession()
|
|
187
|
+
*
|
|
188
|
+
* @returns The session state, or null if not yet initialized
|
|
189
|
+
*
|
|
190
|
+
* @example
|
|
191
|
+
* function MyComponent() {
|
|
192
|
+
* const session = useDemoSession()
|
|
193
|
+
*
|
|
194
|
+
* const cart = session?.get<CartItem[]>('cart') || []
|
|
195
|
+
* const addToCart = (item: CartItem) => {
|
|
196
|
+
* session?.set('cart', [...cart, item])
|
|
197
|
+
* }
|
|
198
|
+
*
|
|
199
|
+
* return <CartView items={cart} onAdd={addToCart} />
|
|
200
|
+
* }
|
|
201
|
+
*/
|
|
202
|
+
declare function useDemoSession(): _demokit_ai_core.SessionState | null;
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* A ready-to-use banner component that shows when demo mode is active
|
|
206
|
+
*
|
|
207
|
+
* Displays a prominent amber banner with a label, description, and exit button.
|
|
208
|
+
* Automatically hides when demo mode is disabled or before hydration.
|
|
209
|
+
*
|
|
210
|
+
* @example
|
|
211
|
+
* function App() {
|
|
212
|
+
* return (
|
|
213
|
+
* <DemoKitProvider fixtures={fixtures}>
|
|
214
|
+
* <DemoModeBanner />
|
|
215
|
+
* <YourApp />
|
|
216
|
+
* </DemoKitProvider>
|
|
217
|
+
* )
|
|
218
|
+
* }
|
|
219
|
+
*
|
|
220
|
+
* @example Custom labels
|
|
221
|
+
* <DemoModeBanner
|
|
222
|
+
* demoLabel="Preview Mode"
|
|
223
|
+
* description="You're viewing sample data"
|
|
224
|
+
* exitLabel="Exit Preview"
|
|
225
|
+
* />
|
|
226
|
+
*/
|
|
227
|
+
declare function DemoModeBanner({ className, exitLabel, demoLabel, description, showIcon, style, onExit, }: DemoModeBannerProps): react_jsx_runtime.JSX.Element | null;
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* React context for demo mode state
|
|
231
|
+
* @internal
|
|
232
|
+
*/
|
|
233
|
+
declare const DemoModeContext: react.Context<DemoModeContextValue | undefined>;
|
|
234
|
+
|
|
235
|
+
export { DemoKitProvider, type DemoKitProviderProps, DemoModeBanner, type DemoModeBannerProps, DemoModeContext, type DemoModeContextValue, useDemoMode, useDemoSession, useIsDemoMode, useIsHydrated };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import * as _demokit_ai_core from '@demokit-ai/core';
|
|
3
|
+
import { FixtureMap, SessionState } from '@demokit-ai/core';
|
|
4
|
+
export { FixtureHandler, FixtureMap, RequestContext, SessionState } from '@demokit-ai/core';
|
|
5
|
+
import * as react from 'react';
|
|
6
|
+
import { ReactNode } from 'react';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Props for the DemoKitProvider component
|
|
10
|
+
*/
|
|
11
|
+
interface DemoKitProviderProps {
|
|
12
|
+
/**
|
|
13
|
+
* Child components to render
|
|
14
|
+
*/
|
|
15
|
+
children: ReactNode;
|
|
16
|
+
/**
|
|
17
|
+
* Map of URL patterns to fixture handlers
|
|
18
|
+
*/
|
|
19
|
+
fixtures: FixtureMap;
|
|
20
|
+
/**
|
|
21
|
+
* localStorage key for persisting demo mode state
|
|
22
|
+
* @default 'demokit-mode'
|
|
23
|
+
*/
|
|
24
|
+
storageKey?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Whether demo mode should be initially enabled
|
|
27
|
+
* If not provided, will read from localStorage
|
|
28
|
+
* @default false
|
|
29
|
+
*/
|
|
30
|
+
initialEnabled?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Callback invoked when demo mode state changes
|
|
33
|
+
*/
|
|
34
|
+
onDemoModeChange?: (enabled: boolean) => void;
|
|
35
|
+
/**
|
|
36
|
+
* Base URL to use for relative URL parsing
|
|
37
|
+
* @default 'http://localhost'
|
|
38
|
+
*/
|
|
39
|
+
baseUrl?: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Value provided by the DemoMode context
|
|
43
|
+
*/
|
|
44
|
+
interface DemoModeContextValue {
|
|
45
|
+
/**
|
|
46
|
+
* Whether demo mode is currently enabled
|
|
47
|
+
*/
|
|
48
|
+
isDemoMode: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Whether the component has hydrated (for SSR safety)
|
|
51
|
+
* Always check this before rendering demo-dependent UI
|
|
52
|
+
*/
|
|
53
|
+
isHydrated: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Enable demo mode
|
|
56
|
+
*/
|
|
57
|
+
enable(): void;
|
|
58
|
+
/**
|
|
59
|
+
* Disable demo mode
|
|
60
|
+
*/
|
|
61
|
+
disable(): void;
|
|
62
|
+
/**
|
|
63
|
+
* Toggle demo mode and return the new state
|
|
64
|
+
*/
|
|
65
|
+
toggle(): void;
|
|
66
|
+
/**
|
|
67
|
+
* Set demo mode to a specific state
|
|
68
|
+
*/
|
|
69
|
+
setDemoMode(enabled: boolean): void;
|
|
70
|
+
/**
|
|
71
|
+
* Reset the session state, clearing all stored data
|
|
72
|
+
* Call this to manually reset the demo session without page refresh
|
|
73
|
+
*/
|
|
74
|
+
resetSession(): void;
|
|
75
|
+
/**
|
|
76
|
+
* Get the current session state instance
|
|
77
|
+
* Useful for inspecting or manipulating session state directly
|
|
78
|
+
* Returns null if the interceptor hasn't been initialized yet
|
|
79
|
+
*/
|
|
80
|
+
getSession(): SessionState | null;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Props for the DemoModeBanner component
|
|
84
|
+
*/
|
|
85
|
+
interface DemoModeBannerProps {
|
|
86
|
+
/**
|
|
87
|
+
* Additional CSS class name
|
|
88
|
+
*/
|
|
89
|
+
className?: string;
|
|
90
|
+
/**
|
|
91
|
+
* Label for the exit button
|
|
92
|
+
* @default 'Exit Demo Mode'
|
|
93
|
+
*/
|
|
94
|
+
exitLabel?: string;
|
|
95
|
+
/**
|
|
96
|
+
* Label shown when demo mode is active
|
|
97
|
+
* @default 'Demo Mode Active'
|
|
98
|
+
*/
|
|
99
|
+
demoLabel?: string;
|
|
100
|
+
/**
|
|
101
|
+
* Description shown in the banner
|
|
102
|
+
* @default 'Changes are simulated and not saved'
|
|
103
|
+
*/
|
|
104
|
+
description?: string;
|
|
105
|
+
/**
|
|
106
|
+
* Whether to show the eye icon
|
|
107
|
+
* @default true
|
|
108
|
+
*/
|
|
109
|
+
showIcon?: boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Custom styles for the banner container
|
|
112
|
+
*/
|
|
113
|
+
style?: React.CSSProperties;
|
|
114
|
+
/**
|
|
115
|
+
* Callback when exit button is clicked
|
|
116
|
+
* If not provided, will call disable() from context
|
|
117
|
+
*/
|
|
118
|
+
onExit?: () => void;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Provider component that enables demo mode functionality
|
|
123
|
+
*
|
|
124
|
+
* Wraps your app to provide demo mode state and controls.
|
|
125
|
+
* Handles SSR hydration safely and persists state to localStorage.
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* const fixtures = {
|
|
129
|
+
* 'GET /api/users': () => [{ id: '1', name: 'Demo User' }],
|
|
130
|
+
* 'GET /api/users/:id': ({ params }) => ({ id: params.id, name: 'Demo User' }),
|
|
131
|
+
* }
|
|
132
|
+
*
|
|
133
|
+
* function App() {
|
|
134
|
+
* return (
|
|
135
|
+
* <DemoKitProvider
|
|
136
|
+
* fixtures={fixtures}
|
|
137
|
+
* onDemoModeChange={(enabled) => console.log('Demo mode:', enabled)}
|
|
138
|
+
* >
|
|
139
|
+
* <YourApp />
|
|
140
|
+
* </DemoKitProvider>
|
|
141
|
+
* )
|
|
142
|
+
* }
|
|
143
|
+
*/
|
|
144
|
+
declare function DemoKitProvider({ children, fixtures, storageKey, initialEnabled, onDemoModeChange, baseUrl, }: DemoKitProviderProps): react_jsx_runtime.JSX.Element;
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Hook to access demo mode state and controls
|
|
148
|
+
*
|
|
149
|
+
* @returns Demo mode context value with state and control methods
|
|
150
|
+
* @throws Error if used outside of DemoKitProvider
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* function MyComponent() {
|
|
154
|
+
* const { isDemoMode, isHydrated, toggle } = useDemoMode()
|
|
155
|
+
*
|
|
156
|
+
* // Wait for hydration before rendering demo-dependent UI
|
|
157
|
+
* if (!isHydrated) {
|
|
158
|
+
* return <Loading />
|
|
159
|
+
* }
|
|
160
|
+
*
|
|
161
|
+
* return (
|
|
162
|
+
* <div>
|
|
163
|
+
* <p>Demo mode: {isDemoMode ? 'ON' : 'OFF'}</p>
|
|
164
|
+
* <button onClick={toggle}>Toggle</button>
|
|
165
|
+
* </div>
|
|
166
|
+
* )
|
|
167
|
+
* }
|
|
168
|
+
*/
|
|
169
|
+
declare function useDemoMode(): DemoModeContextValue;
|
|
170
|
+
/**
|
|
171
|
+
* Hook to check if demo mode is enabled
|
|
172
|
+
* Shorthand for useDemoMode().isDemoMode
|
|
173
|
+
*
|
|
174
|
+
* @returns Whether demo mode is enabled
|
|
175
|
+
*/
|
|
176
|
+
declare function useIsDemoMode(): boolean;
|
|
177
|
+
/**
|
|
178
|
+
* Hook to check if the component has hydrated
|
|
179
|
+
* Shorthand for useDemoMode().isHydrated
|
|
180
|
+
*
|
|
181
|
+
* @returns Whether the component has hydrated
|
|
182
|
+
*/
|
|
183
|
+
declare function useIsHydrated(): boolean;
|
|
184
|
+
/**
|
|
185
|
+
* Hook to access the session state
|
|
186
|
+
* Shorthand for useDemoMode().getSession()
|
|
187
|
+
*
|
|
188
|
+
* @returns The session state, or null if not yet initialized
|
|
189
|
+
*
|
|
190
|
+
* @example
|
|
191
|
+
* function MyComponent() {
|
|
192
|
+
* const session = useDemoSession()
|
|
193
|
+
*
|
|
194
|
+
* const cart = session?.get<CartItem[]>('cart') || []
|
|
195
|
+
* const addToCart = (item: CartItem) => {
|
|
196
|
+
* session?.set('cart', [...cart, item])
|
|
197
|
+
* }
|
|
198
|
+
*
|
|
199
|
+
* return <CartView items={cart} onAdd={addToCart} />
|
|
200
|
+
* }
|
|
201
|
+
*/
|
|
202
|
+
declare function useDemoSession(): _demokit_ai_core.SessionState | null;
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* A ready-to-use banner component that shows when demo mode is active
|
|
206
|
+
*
|
|
207
|
+
* Displays a prominent amber banner with a label, description, and exit button.
|
|
208
|
+
* Automatically hides when demo mode is disabled or before hydration.
|
|
209
|
+
*
|
|
210
|
+
* @example
|
|
211
|
+
* function App() {
|
|
212
|
+
* return (
|
|
213
|
+
* <DemoKitProvider fixtures={fixtures}>
|
|
214
|
+
* <DemoModeBanner />
|
|
215
|
+
* <YourApp />
|
|
216
|
+
* </DemoKitProvider>
|
|
217
|
+
* )
|
|
218
|
+
* }
|
|
219
|
+
*
|
|
220
|
+
* @example Custom labels
|
|
221
|
+
* <DemoModeBanner
|
|
222
|
+
* demoLabel="Preview Mode"
|
|
223
|
+
* description="You're viewing sample data"
|
|
224
|
+
* exitLabel="Exit Preview"
|
|
225
|
+
* />
|
|
226
|
+
*/
|
|
227
|
+
declare function DemoModeBanner({ className, exitLabel, demoLabel, description, showIcon, style, onExit, }: DemoModeBannerProps): react_jsx_runtime.JSX.Element | null;
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* React context for demo mode state
|
|
231
|
+
* @internal
|
|
232
|
+
*/
|
|
233
|
+
declare const DemoModeContext: react.Context<DemoModeContextValue | undefined>;
|
|
234
|
+
|
|
235
|
+
export { DemoKitProvider, type DemoKitProviderProps, DemoModeBanner, type DemoModeBannerProps, DemoModeContext, type DemoModeContextValue, useDemoMode, useDemoSession, useIsDemoMode, useIsHydrated };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { createContext, useState, useRef, useEffect, useCallback, useMemo, useContext } from 'react';
|
|
2
|
+
import { createDemoInterceptor } from '@demokit-ai/core';
|
|
3
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
// src/provider.tsx
|
|
6
|
+
var DemoModeContext = createContext(void 0);
|
|
7
|
+
DemoModeContext.displayName = "DemoModeContext";
|
|
8
|
+
function DemoKitProvider({
|
|
9
|
+
children,
|
|
10
|
+
fixtures,
|
|
11
|
+
storageKey = "demokit-mode",
|
|
12
|
+
initialEnabled = false,
|
|
13
|
+
onDemoModeChange,
|
|
14
|
+
baseUrl
|
|
15
|
+
}) {
|
|
16
|
+
const [isDemoMode, setIsDemoMode] = useState(initialEnabled);
|
|
17
|
+
const [isHydrated, setIsHydrated] = useState(false);
|
|
18
|
+
const interceptorRef = useRef(null);
|
|
19
|
+
const initializedRef = useRef(false);
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (initializedRef.current) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
initializedRef.current = true;
|
|
25
|
+
interceptorRef.current = createDemoInterceptor({
|
|
26
|
+
fixtures,
|
|
27
|
+
storageKey,
|
|
28
|
+
initialEnabled,
|
|
29
|
+
baseUrl,
|
|
30
|
+
onEnable: () => {
|
|
31
|
+
setIsDemoMode(true);
|
|
32
|
+
onDemoModeChange?.(true);
|
|
33
|
+
},
|
|
34
|
+
onDisable: () => {
|
|
35
|
+
setIsDemoMode(false);
|
|
36
|
+
onDemoModeChange?.(false);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
const storedState = interceptorRef.current.isEnabled();
|
|
40
|
+
setIsDemoMode(storedState);
|
|
41
|
+
setIsHydrated(true);
|
|
42
|
+
return () => {
|
|
43
|
+
interceptorRef.current?.destroy();
|
|
44
|
+
interceptorRef.current = null;
|
|
45
|
+
initializedRef.current = false;
|
|
46
|
+
};
|
|
47
|
+
}, []);
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (interceptorRef.current && isHydrated) {
|
|
50
|
+
interceptorRef.current.setFixtures(fixtures);
|
|
51
|
+
}
|
|
52
|
+
}, [fixtures, isHydrated]);
|
|
53
|
+
const enable = useCallback(() => {
|
|
54
|
+
interceptorRef.current?.enable();
|
|
55
|
+
}, []);
|
|
56
|
+
const disable = useCallback(() => {
|
|
57
|
+
interceptorRef.current?.disable();
|
|
58
|
+
}, []);
|
|
59
|
+
const toggle = useCallback(() => {
|
|
60
|
+
interceptorRef.current?.toggle();
|
|
61
|
+
}, []);
|
|
62
|
+
const setDemoMode = useCallback((enabled) => {
|
|
63
|
+
if (enabled) {
|
|
64
|
+
interceptorRef.current?.enable();
|
|
65
|
+
} else {
|
|
66
|
+
interceptorRef.current?.disable();
|
|
67
|
+
}
|
|
68
|
+
}, []);
|
|
69
|
+
const resetSession = useCallback(() => {
|
|
70
|
+
interceptorRef.current?.resetSession();
|
|
71
|
+
}, []);
|
|
72
|
+
const getSession = useCallback(() => {
|
|
73
|
+
return interceptorRef.current?.getSession() ?? null;
|
|
74
|
+
}, []);
|
|
75
|
+
const value = useMemo(
|
|
76
|
+
() => ({
|
|
77
|
+
isDemoMode,
|
|
78
|
+
isHydrated,
|
|
79
|
+
enable,
|
|
80
|
+
disable,
|
|
81
|
+
toggle,
|
|
82
|
+
setDemoMode,
|
|
83
|
+
resetSession,
|
|
84
|
+
getSession
|
|
85
|
+
}),
|
|
86
|
+
[isDemoMode, isHydrated, enable, disable, toggle, setDemoMode, resetSession, getSession]
|
|
87
|
+
);
|
|
88
|
+
return /* @__PURE__ */ jsx(DemoModeContext.Provider, { value, children });
|
|
89
|
+
}
|
|
90
|
+
function useDemoMode() {
|
|
91
|
+
const context = useContext(DemoModeContext);
|
|
92
|
+
if (context === void 0) {
|
|
93
|
+
throw new Error(
|
|
94
|
+
"useDemoMode must be used within a DemoKitProvider. Make sure to wrap your app with <DemoKitProvider>."
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
return context;
|
|
98
|
+
}
|
|
99
|
+
function useIsDemoMode() {
|
|
100
|
+
return useDemoMode().isDemoMode;
|
|
101
|
+
}
|
|
102
|
+
function useIsHydrated() {
|
|
103
|
+
return useDemoMode().isHydrated;
|
|
104
|
+
}
|
|
105
|
+
function useDemoSession() {
|
|
106
|
+
return useDemoMode().getSession();
|
|
107
|
+
}
|
|
108
|
+
function EyeIcon() {
|
|
109
|
+
return /* @__PURE__ */ jsxs(
|
|
110
|
+
"svg",
|
|
111
|
+
{
|
|
112
|
+
width: "20",
|
|
113
|
+
height: "20",
|
|
114
|
+
viewBox: "0 0 24 24",
|
|
115
|
+
fill: "none",
|
|
116
|
+
stroke: "currentColor",
|
|
117
|
+
strokeWidth: "2",
|
|
118
|
+
strokeLinecap: "round",
|
|
119
|
+
strokeLinejoin: "round",
|
|
120
|
+
"aria-hidden": "true",
|
|
121
|
+
children: [
|
|
122
|
+
/* @__PURE__ */ jsx("path", { d: "M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" }),
|
|
123
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "3" })
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
var defaultStyles = {
|
|
129
|
+
container: {
|
|
130
|
+
display: "flex",
|
|
131
|
+
alignItems: "center",
|
|
132
|
+
justifyContent: "space-between",
|
|
133
|
+
padding: "8px 16px",
|
|
134
|
+
backgroundColor: "#fef3c7",
|
|
135
|
+
borderBottom: "1px solid rgba(217, 119, 6, 0.2)",
|
|
136
|
+
fontSize: "14px",
|
|
137
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif'
|
|
138
|
+
},
|
|
139
|
+
content: {
|
|
140
|
+
display: "flex",
|
|
141
|
+
alignItems: "center",
|
|
142
|
+
gap: "8px"
|
|
143
|
+
},
|
|
144
|
+
icon: {
|
|
145
|
+
color: "#d97706",
|
|
146
|
+
flexShrink: 0
|
|
147
|
+
},
|
|
148
|
+
label: {
|
|
149
|
+
fontWeight: 600,
|
|
150
|
+
color: "#78350f"
|
|
151
|
+
},
|
|
152
|
+
description: {
|
|
153
|
+
color: "rgba(120, 53, 15, 0.7)",
|
|
154
|
+
fontSize: "12px"
|
|
155
|
+
},
|
|
156
|
+
button: {
|
|
157
|
+
padding: "4px 12px",
|
|
158
|
+
fontSize: "14px",
|
|
159
|
+
backgroundColor: "transparent",
|
|
160
|
+
border: "1px solid rgba(217, 119, 6, 0.3)",
|
|
161
|
+
borderRadius: "4px",
|
|
162
|
+
cursor: "pointer",
|
|
163
|
+
color: "#78350f",
|
|
164
|
+
fontFamily: "inherit",
|
|
165
|
+
transition: "background-color 0.15s ease"
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
function DemoModeBanner({
|
|
169
|
+
className = "",
|
|
170
|
+
exitLabel = "Exit Demo Mode",
|
|
171
|
+
demoLabel = "Demo Mode Active",
|
|
172
|
+
description = "Changes are simulated and not saved",
|
|
173
|
+
showIcon = true,
|
|
174
|
+
style,
|
|
175
|
+
onExit
|
|
176
|
+
}) {
|
|
177
|
+
const { isDemoMode, isHydrated, disable } = useDemoMode();
|
|
178
|
+
if (!isHydrated || !isDemoMode) {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
const handleExit = () => {
|
|
182
|
+
if (onExit) {
|
|
183
|
+
onExit();
|
|
184
|
+
} else {
|
|
185
|
+
disable();
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
return /* @__PURE__ */ jsxs(
|
|
189
|
+
"div",
|
|
190
|
+
{
|
|
191
|
+
className: `demokit-banner ${className}`.trim(),
|
|
192
|
+
style: { ...defaultStyles.container, ...style },
|
|
193
|
+
role: "status",
|
|
194
|
+
"aria-live": "polite",
|
|
195
|
+
children: [
|
|
196
|
+
/* @__PURE__ */ jsxs("div", { style: defaultStyles.content, children: [
|
|
197
|
+
showIcon && /* @__PURE__ */ jsx("span", { style: defaultStyles.icon, children: /* @__PURE__ */ jsx(EyeIcon, {}) }),
|
|
198
|
+
/* @__PURE__ */ jsx("span", { style: defaultStyles.label, children: demoLabel }),
|
|
199
|
+
description && /* @__PURE__ */ jsx("span", { style: defaultStyles.description, children: description })
|
|
200
|
+
] }),
|
|
201
|
+
/* @__PURE__ */ jsx(
|
|
202
|
+
"button",
|
|
203
|
+
{
|
|
204
|
+
onClick: handleExit,
|
|
205
|
+
style: defaultStyles.button,
|
|
206
|
+
onMouseOver: (e) => {
|
|
207
|
+
e.currentTarget.style.backgroundColor = "rgba(217, 119, 6, 0.1)";
|
|
208
|
+
},
|
|
209
|
+
onMouseOut: (e) => {
|
|
210
|
+
e.currentTarget.style.backgroundColor = "transparent";
|
|
211
|
+
},
|
|
212
|
+
type: "button",
|
|
213
|
+
children: exitLabel
|
|
214
|
+
}
|
|
215
|
+
)
|
|
216
|
+
]
|
|
217
|
+
}
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export { DemoKitProvider, DemoModeBanner, DemoModeContext, useDemoMode, useDemoSession, useIsDemoMode, useIsHydrated };
|
|
222
|
+
//# sourceMappingURL=index.js.map
|
|
223
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/context.ts","../src/provider.tsx","../src/hooks.ts","../src/banner.tsx"],"names":["jsx"],"mappings":"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","file":"index.js","sourcesContent":["import { createContext } from 'react'\nimport type { DemoModeContextValue } from './types'\n\n/**\n * React context for demo mode state\n * @internal\n */\nexport const DemoModeContext = createContext<DemoModeContextValue | undefined>(undefined)\n\nDemoModeContext.displayName = 'DemoModeContext'\n","'use client'\n\nimport { useState, useEffect, useCallback, useMemo, useRef } from 'react'\nimport { createDemoInterceptor, type DemoInterceptor, type SessionState } from '@demokit-ai/core'\nimport { DemoModeContext } from './context'\nimport type { DemoKitProviderProps, DemoModeContextValue } from './types'\n\n/**\n * Provider component that enables demo mode functionality\n *\n * Wraps your app to provide demo mode state and controls.\n * Handles SSR hydration safely and persists state to localStorage.\n *\n * @example\n * const fixtures = {\n * 'GET /api/users': () => [{ id: '1', name: 'Demo User' }],\n * 'GET /api/users/:id': ({ params }) => ({ id: params.id, name: 'Demo User' }),\n * }\n *\n * function App() {\n * return (\n * <DemoKitProvider\n * fixtures={fixtures}\n * onDemoModeChange={(enabled) => console.log('Demo mode:', enabled)}\n * >\n * <YourApp />\n * </DemoKitProvider>\n * )\n * }\n */\nexport function DemoKitProvider({\n children,\n fixtures,\n storageKey = 'demokit-mode',\n initialEnabled = false,\n onDemoModeChange,\n baseUrl,\n}: DemoKitProviderProps) {\n // Start with initialEnabled for SSR to avoid hydration mismatch\n const [isDemoMode, setIsDemoMode] = useState(initialEnabled)\n const [isHydrated, setIsHydrated] = useState(false)\n\n // Keep a ref to the interceptor instance\n const interceptorRef = useRef<DemoInterceptor | null>(null)\n\n // Track if we've initialized\n const initializedRef = useRef(false)\n\n // Initialize interceptor on mount (client-side only)\n useEffect(() => {\n if (initializedRef.current) {\n return\n }\n initializedRef.current = true\n\n interceptorRef.current = createDemoInterceptor({\n fixtures,\n storageKey,\n initialEnabled,\n baseUrl,\n onEnable: () => {\n setIsDemoMode(true)\n onDemoModeChange?.(true)\n },\n onDisable: () => {\n setIsDemoMode(false)\n onDemoModeChange?.(false)\n },\n })\n\n // Sync state from storage after hydration\n const storedState = interceptorRef.current.isEnabled()\n setIsDemoMode(storedState)\n\n // Mark as hydrated\n setIsHydrated(true)\n\n return () => {\n interceptorRef.current?.destroy()\n interceptorRef.current = null\n initializedRef.current = false\n }\n }, []) // Empty deps - only run once on mount\n\n // Update fixtures if they change\n useEffect(() => {\n if (interceptorRef.current && isHydrated) {\n interceptorRef.current.setFixtures(fixtures)\n }\n }, [fixtures, isHydrated])\n\n const enable = useCallback(() => {\n interceptorRef.current?.enable()\n }, [])\n\n const disable = useCallback(() => {\n interceptorRef.current?.disable()\n }, [])\n\n const toggle = useCallback(() => {\n interceptorRef.current?.toggle()\n }, [])\n\n const setDemoMode = useCallback((enabled: boolean) => {\n if (enabled) {\n interceptorRef.current?.enable()\n } else {\n interceptorRef.current?.disable()\n }\n }, [])\n\n const resetSession = useCallback(() => {\n interceptorRef.current?.resetSession()\n }, [])\n\n const getSession = useCallback((): SessionState | null => {\n return interceptorRef.current?.getSession() ?? null\n }, [])\n\n const value = useMemo<DemoModeContextValue>(\n () => ({\n isDemoMode,\n isHydrated,\n enable,\n disable,\n toggle,\n setDemoMode,\n resetSession,\n getSession,\n }),\n [isDemoMode, isHydrated, enable, disable, toggle, setDemoMode, resetSession, getSession]\n )\n\n return (\n <DemoModeContext.Provider value={value}>\n {children}\n </DemoModeContext.Provider>\n )\n}\n","'use client'\n\nimport { useContext } from 'react'\nimport { DemoModeContext } from './context'\nimport type { DemoModeContextValue } from './types'\n\n/**\n * Hook to access demo mode state and controls\n *\n * @returns Demo mode context value with state and control methods\n * @throws Error if used outside of DemoKitProvider\n *\n * @example\n * function MyComponent() {\n * const { isDemoMode, isHydrated, toggle } = useDemoMode()\n *\n * // Wait for hydration before rendering demo-dependent UI\n * if (!isHydrated) {\n * return <Loading />\n * }\n *\n * return (\n * <div>\n * <p>Demo mode: {isDemoMode ? 'ON' : 'OFF'}</p>\n * <button onClick={toggle}>Toggle</button>\n * </div>\n * )\n * }\n */\nexport function useDemoMode(): DemoModeContextValue {\n const context = useContext(DemoModeContext)\n\n if (context === undefined) {\n throw new Error(\n 'useDemoMode must be used within a DemoKitProvider. ' +\n 'Make sure to wrap your app with <DemoKitProvider>.'\n )\n }\n\n return context\n}\n\n/**\n * Hook to check if demo mode is enabled\n * Shorthand for useDemoMode().isDemoMode\n *\n * @returns Whether demo mode is enabled\n */\nexport function useIsDemoMode(): boolean {\n return useDemoMode().isDemoMode\n}\n\n/**\n * Hook to check if the component has hydrated\n * Shorthand for useDemoMode().isHydrated\n *\n * @returns Whether the component has hydrated\n */\nexport function useIsHydrated(): boolean {\n return useDemoMode().isHydrated\n}\n\n/**\n * Hook to access the session state\n * Shorthand for useDemoMode().getSession()\n *\n * @returns The session state, or null if not yet initialized\n *\n * @example\n * function MyComponent() {\n * const session = useDemoSession()\n *\n * const cart = session?.get<CartItem[]>('cart') || []\n * const addToCart = (item: CartItem) => {\n * session?.set('cart', [...cart, item])\n * }\n *\n * return <CartView items={cart} onAdd={addToCart} />\n * }\n */\nexport function useDemoSession() {\n return useDemoMode().getSession()\n}\n","'use client'\n\nimport { useDemoMode } from './hooks'\nimport type { DemoModeBannerProps } from './types'\n\n/**\n * Eye icon SVG component\n */\nfunction EyeIcon() {\n return (\n <svg\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z\" />\n <circle cx=\"12\" cy=\"12\" r=\"3\" />\n </svg>\n )\n}\n\n/**\n * Default styles for the banner\n */\nconst defaultStyles = {\n container: {\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n padding: '8px 16px',\n backgroundColor: '#fef3c7',\n borderBottom: '1px solid rgba(217, 119, 6, 0.2)',\n fontSize: '14px',\n fontFamily:\n '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif',\n } as React.CSSProperties,\n content: {\n display: 'flex',\n alignItems: 'center',\n gap: '8px',\n } as React.CSSProperties,\n icon: {\n color: '#d97706',\n flexShrink: 0,\n } as React.CSSProperties,\n label: {\n fontWeight: 600,\n color: '#78350f',\n } as React.CSSProperties,\n description: {\n color: 'rgba(120, 53, 15, 0.7)',\n fontSize: '12px',\n } as React.CSSProperties,\n button: {\n padding: '4px 12px',\n fontSize: '14px',\n backgroundColor: 'transparent',\n border: '1px solid rgba(217, 119, 6, 0.3)',\n borderRadius: '4px',\n cursor: 'pointer',\n color: '#78350f',\n fontFamily: 'inherit',\n transition: 'background-color 0.15s ease',\n } as React.CSSProperties,\n}\n\n/**\n * A ready-to-use banner component that shows when demo mode is active\n *\n * Displays a prominent amber banner with a label, description, and exit button.\n * Automatically hides when demo mode is disabled or before hydration.\n *\n * @example\n * function App() {\n * return (\n * <DemoKitProvider fixtures={fixtures}>\n * <DemoModeBanner />\n * <YourApp />\n * </DemoKitProvider>\n * )\n * }\n *\n * @example Custom labels\n * <DemoModeBanner\n * demoLabel=\"Preview Mode\"\n * description=\"You're viewing sample data\"\n * exitLabel=\"Exit Preview\"\n * />\n */\nexport function DemoModeBanner({\n className = '',\n exitLabel = 'Exit Demo Mode',\n demoLabel = 'Demo Mode Active',\n description = 'Changes are simulated and not saved',\n showIcon = true,\n style,\n onExit,\n}: DemoModeBannerProps) {\n const { isDemoMode, isHydrated, disable } = useDemoMode()\n\n // Don't render until hydrated to avoid hydration mismatch\n if (!isHydrated || !isDemoMode) {\n return null\n }\n\n const handleExit = () => {\n if (onExit) {\n onExit()\n } else {\n disable()\n }\n }\n\n return (\n <div\n className={`demokit-banner ${className}`.trim()}\n style={{ ...defaultStyles.container, ...style }}\n role=\"status\"\n aria-live=\"polite\"\n >\n <div style={defaultStyles.content}>\n {showIcon && (\n <span style={defaultStyles.icon}>\n <EyeIcon />\n </span>\n )}\n <span style={defaultStyles.label}>{demoLabel}</span>\n {description && <span style={defaultStyles.description}>{description}</span>}\n </div>\n <button\n onClick={handleExit}\n style={defaultStyles.button}\n onMouseOver={(e) => {\n e.currentTarget.style.backgroundColor = 'rgba(217, 119, 6, 0.1)'\n }}\n onMouseOut={(e) => {\n e.currentTarget.style.backgroundColor = 'transparent'\n }}\n type=\"button\"\n >\n {exitLabel}\n </button>\n </div>\n )\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@demokit-ai/react",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "React bindings for DemoKit - provider, hooks, and components",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/index.d.cts",
|
|
17
|
+
"default": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsup",
|
|
26
|
+
"dev": "tsup --watch",
|
|
27
|
+
"test": "vitest",
|
|
28
|
+
"typecheck": "tsc --noEmit"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@demokit-ai/core": "*"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/react": "^19.0.0",
|
|
35
|
+
"@types/react-dom": "^19.0.0",
|
|
36
|
+
"react": "^19.0.0",
|
|
37
|
+
"react-dom": "^19.0.0",
|
|
38
|
+
"tsup": "^8.3.5",
|
|
39
|
+
"typescript": "^5.7.2",
|
|
40
|
+
"vitest": "^2.1.8"
|
|
41
|
+
},
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"react": ">=17.0.0",
|
|
44
|
+
"react-dom": ">=17.0.0"
|
|
45
|
+
},
|
|
46
|
+
"keywords": [
|
|
47
|
+
"demo",
|
|
48
|
+
"mock",
|
|
49
|
+
"react",
|
|
50
|
+
"provider",
|
|
51
|
+
"hooks",
|
|
52
|
+
"saas"
|
|
53
|
+
],
|
|
54
|
+
"license": "MIT",
|
|
55
|
+
"repository": {
|
|
56
|
+
"type": "git",
|
|
57
|
+
"url": "https://github.com/your-org/demokit",
|
|
58
|
+
"directory": "packages/react"
|
|
59
|
+
},
|
|
60
|
+
"sideEffects": false
|
|
61
|
+
}
|