@vibeflow-tools/prototyping 0.2.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.
- package/CHANGELOG.md +12 -0
- package/README.md +341 -0
- package/dist/index.d.ts +292 -0
- package/dist/index.js +890 -0
- package/dist/prototype-bundle.iife.js +1 -0
- package/dist/prototype-bundle.js +1 -0
- package/package.json +70 -0
- package/skills/SKILL.md +384 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# @vibeflow-tools/prototyping
|
|
2
|
+
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Add `@vibeflow-tools/prototyping` React package for in-app variant switching. Provides `useVariant`, `useActiveVariant`, `VariantProvider`, `PageVariantSwitcher`, `VariantSwitcher`, and `VariantDevToolbar` components with URL param and localStorage persistence, keyboard shortcuts (Alt+H / Ctrl+Shift+V, configurable), overlay integration (MutationObserver + polling for bookmarklet injection), and zero runtime dependencies.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- c525ae7: Make `VariantSwitcher` draggable — same hold-to-drag behaviour as the vibeflow corner trigger. Hold the indicator dot for 300ms, then drag to reposition anywhere on the viewport. Position is persisted to `localStorage` per scope name (`vf-variant-pos-<name>`), so it survives page reloads. Cursor changes to `grab` on hold and `grabbing` during drag for clear affordance.
|
|
12
|
+
- Add production-ready README with full API reference, quick start, persistence docs, keyboard shortcuts, and Vibeflow overlay integration guide.
|
package/README.md
ADDED
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
# @vibeflow-tools/prototyping
|
|
2
|
+
|
|
3
|
+
> **In-app variant switching for React** — page-level and component-level prototyping with URL persistence and zero runtime dependencies.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@vibeflow-tools/prototyping)
|
|
6
|
+
[](https://www.vibeflow.tools/ui-prototyping.html)
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install @vibeflow-tools/prototyping
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Built for Coding Agents
|
|
15
|
+
|
|
16
|
+
`ui-prototyping` was built to enable coding agents to propose UI variants directly in your running app — without screenshots, back-and-forth prompts, or external design tools.
|
|
17
|
+
|
|
18
|
+
An agent can define multiple named variants (layouts, colour schemes, component densities) and register them with a few lines of code. You switch between them with a click, see the result immediately in your real app, and tell the agent which one to keep. **No Figma. No mockups. No rebuild cycle.**
|
|
19
|
+
|
|
20
|
+
This makes `ui-prototyping` one of the most efficient ways for developers to give and receive UI feedback when working with AI coding assistants.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Why It Matters
|
|
25
|
+
|
|
26
|
+
Designing and reviewing UI variations is slow when you have to change code, rebuild, and refresh every time you want to see a different state. `ui-prototyping` brings the switch directly into your running app:
|
|
27
|
+
|
|
28
|
+
- **Click to switch** — numbered dot on each component, floating toolbar for all scopes at once
|
|
29
|
+
- **URL-persisted** — share a URL and your reviewer sees the exact variant you're showing them
|
|
30
|
+
- **TypeScript-first** — variant keys are narrowed to their literal types, config objects are fully typed
|
|
31
|
+
- **Zero runtime deps** — peer-deps only (`react`, `react-dom`); nothing extra in your bundle
|
|
32
|
+
- **Vibeflow overlay integration** — when the Vibeflow overlay is detected, the toolbar is accessible via the right-click context menu ("Prototyping" option)
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Quick Start
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
import { VariantProvider, useVariant, PageVariantSwitcher } from '@vibeflow-tools/prototyping'
|
|
40
|
+
|
|
41
|
+
// 1. Define your variants
|
|
42
|
+
const heroVariants = {
|
|
43
|
+
default: {},
|
|
44
|
+
compact: { spacing: 'tight', titleSize: 'md' },
|
|
45
|
+
expanded: { spacing: 'loose', titleSize: 'xl' },
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 2. Read the active variant
|
|
49
|
+
function HeroSection() {
|
|
50
|
+
const v = useVariant('Hero', heroVariants)
|
|
51
|
+
return (
|
|
52
|
+
<section className={v.spacing === 'tight' ? 'py-4' : 'py-12'}>
|
|
53
|
+
<PageVariantSwitcher name="Hero" variants={heroVariants} />
|
|
54
|
+
<h1 className={v.titleSize === 'xl' ? 'text-4xl' : 'text-2xl'}>Welcome</h1>
|
|
55
|
+
</section>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 3. Wrap your app
|
|
60
|
+
function App() {
|
|
61
|
+
return (
|
|
62
|
+
<VariantProvider>
|
|
63
|
+
<HeroSection />
|
|
64
|
+
</VariantProvider>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Installation
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
npm install @vibeflow-tools/prototyping
|
|
75
|
+
# or
|
|
76
|
+
pnpm add @vibeflow-tools/prototyping
|
|
77
|
+
# or
|
|
78
|
+
yarn add @vibeflow-tools/prototyping
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Requirements:** React 18+, Node.js 18+.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Core API
|
|
86
|
+
|
|
87
|
+
### `<VariantProvider>`
|
|
88
|
+
|
|
89
|
+
Wrap your app (or a subtree) with `VariantProvider` to enable the variant switching system. All hooks and switcher components below it share state via this provider.
|
|
90
|
+
|
|
91
|
+
```tsx
|
|
92
|
+
<VariantProvider>
|
|
93
|
+
<App />
|
|
94
|
+
</VariantProvider>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
#### Props
|
|
98
|
+
|
|
99
|
+
| Prop | Type | Default | Description |
|
|
100
|
+
|------|------|---------|-------------|
|
|
101
|
+
| `mode` | `"dev" \| "always"` | `"dev"` | `"dev"` hides switcher UI in production (`NODE_ENV === "production"`). `"always"` always shows it — useful for A/B testing demos. |
|
|
102
|
+
| `defaultVisible` | `boolean` | `true` | Initial UI visibility. Persisted in `localStorage` so users can hide/show across sessions. |
|
|
103
|
+
| `shortcuts` | `KeyboardShortcut[] \| false` | default shortcuts | Custom keyboard shortcuts for toggling the variant UI. Pass `false` to disable. |
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
### `useVariant(name, variants)`
|
|
108
|
+
|
|
109
|
+
Core hook. Registers the scope, resolves the active variant from URL → localStorage → default, and returns the matching config object.
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
const variants = {
|
|
113
|
+
default: {},
|
|
114
|
+
compact: { compact: true, density: 'low' },
|
|
115
|
+
detailed: { showMeta: true, showComments: true },
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function TaskCard() {
|
|
119
|
+
const v = useVariant('TaskCard', variants)
|
|
120
|
+
// v is typed as typeof variants[keyof typeof variants]
|
|
121
|
+
return <div className={v.compact ? 'p-2' : 'p-4'}>…</div>
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
The first key is always the default variant (applied when no URL param or storage entry is present).
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
### `<VariantSwitcher>`
|
|
130
|
+
|
|
131
|
+
A subtle indicator dot positioned on the edge of the parent element. Clicking it expands a numbered-dots picker.
|
|
132
|
+
|
|
133
|
+
```tsx
|
|
134
|
+
function TaskCard() {
|
|
135
|
+
const v = useVariant('TaskCard', variants)
|
|
136
|
+
return (
|
|
137
|
+
<div style={{ position: 'relative' }}>
|
|
138
|
+
<VariantSwitcher name="TaskCard" variants={variants} />
|
|
139
|
+
{v.compact ? <CompactView /> : <FullView />}
|
|
140
|
+
</div>
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**The parent must have `position: relative` for correct placement.**
|
|
146
|
+
|
|
147
|
+
Deduplicates per scope — only the first `VariantSwitcher` for a given scope renders, even if the component is used multiple times on the page.
|
|
148
|
+
|
|
149
|
+
#### Props
|
|
150
|
+
|
|
151
|
+
| Prop | Type | Default | Description |
|
|
152
|
+
|------|------|---------|-------------|
|
|
153
|
+
| `name` | `string` | required | Scope name — must match the `useVariant` call |
|
|
154
|
+
| `variants` | `Record<string, object>` | required | Same variant definitions passed to `useVariant` |
|
|
155
|
+
| `position` | `"right" \| "left"` | `"right"` | Which side the dot appears on |
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
### `<PageVariantSwitcher>`
|
|
160
|
+
|
|
161
|
+
A dark segmented control fixed to the top-left of the viewport — ideal for page-level layout or theme switching.
|
|
162
|
+
|
|
163
|
+
```tsx
|
|
164
|
+
function DashboardPage() {
|
|
165
|
+
const layout = useVariant('DashboardLayout', layoutVariants)
|
|
166
|
+
return (
|
|
167
|
+
<>
|
|
168
|
+
<PageVariantSwitcher name="DashboardLayout" variants={layoutVariants} />
|
|
169
|
+
<main className={layout.sidebar ? 'with-sidebar' : ''}>…</main>
|
|
170
|
+
</>
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
#### Props
|
|
176
|
+
|
|
177
|
+
| Prop | Type | Default | Description |
|
|
178
|
+
|------|------|---------|-------------|
|
|
179
|
+
| `name` | `string` | required | Scope name — must match the `useVariant` call |
|
|
180
|
+
| `variants` | `Record<string, object>` | required | Same variant definitions passed to `useVariant` |
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
### `<VariantDevToolbar>`
|
|
185
|
+
|
|
186
|
+
An optional floating toolbar that shows **all registered variant scopes** at once in a single panel. Use this when you have many scopes and want a central control panel.
|
|
187
|
+
|
|
188
|
+
```tsx
|
|
189
|
+
function App() {
|
|
190
|
+
return (
|
|
191
|
+
<VariantProvider>
|
|
192
|
+
<VariantDevToolbar /> {/* ← add once near the root */}
|
|
193
|
+
<MainContent />
|
|
194
|
+
</VariantProvider>
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
The toolbar button (vibeflow icon) appears bottom-right of the page. When the Vibeflow overlay is detected, the button is hidden — access the toolbar instead via the overlay's right-click context menu.
|
|
200
|
+
|
|
201
|
+
**Keyboard shortcuts:**
|
|
202
|
+
- `Alt+H` — toggle all switchers on/off
|
|
203
|
+
- `Ctrl+Shift+V` — toggle all switchers on/off
|
|
204
|
+
- `Escape` — close the toolbar panel
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
### `registerVariant(name, variants)`
|
|
209
|
+
|
|
210
|
+
Module-level registration. Use this to centralise all variant definitions in one file instead of re-passing them to every component.
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
// variants.ts
|
|
214
|
+
import { registerVariant } from '@vibeflow-tools/prototyping'
|
|
215
|
+
|
|
216
|
+
registerVariant('TaskCard', {
|
|
217
|
+
default: {},
|
|
218
|
+
minimal: { compact: true },
|
|
219
|
+
detailed: { showMeta: true, showComments: true },
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
registerVariant('KanbanBoard', {
|
|
223
|
+
default: {},
|
|
224
|
+
dense: { rowHeight: 'sm' },
|
|
225
|
+
})
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
```tsx
|
|
229
|
+
// TaskCard.tsx
|
|
230
|
+
function TaskCard() {
|
|
231
|
+
// Pass an empty object — registered variants are merged automatically
|
|
232
|
+
const v = useVariant('TaskCard', {})
|
|
233
|
+
return <div className={v.compact ? 'p-2' : 'p-4'}>…</div>
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Inline variants always win over registered ones when both are provided.
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## Persistence
|
|
242
|
+
|
|
243
|
+
Active variants are persisted in two places:
|
|
244
|
+
|
|
245
|
+
| Layer | Storage | Scope |
|
|
246
|
+
|-------|---------|-------|
|
|
247
|
+
| URL param | `?vf-<name>=<variant>` | Shareable — copy the URL to share a specific variant |
|
|
248
|
+
| localStorage | `vf-<name>` | Session — survives page reloads in the same browser |
|
|
249
|
+
|
|
250
|
+
URL takes precedence over localStorage.
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## Keyboard Shortcuts
|
|
255
|
+
|
|
256
|
+
| Shortcut | Action |
|
|
257
|
+
|----------|--------|
|
|
258
|
+
| `Alt+H` | Toggle all variant switchers on/off |
|
|
259
|
+
| `Ctrl+Shift+V` | Toggle all variant switchers on/off |
|
|
260
|
+
|
|
261
|
+
Both shortcuts call `toggleUiVisible()` on the `VariantProvider`. To open the `VariantDevToolbar` panel, click the vibeflow icon button in the bottom-right corner.
|
|
262
|
+
|
|
263
|
+
Customise or disable shortcuts via the `shortcuts` prop on `VariantProvider`:
|
|
264
|
+
|
|
265
|
+
```tsx
|
|
266
|
+
// Custom shortcuts
|
|
267
|
+
<VariantProvider shortcuts={[{ key: 'v', alt: true }]}>
|
|
268
|
+
<App />
|
|
269
|
+
</VariantProvider>
|
|
270
|
+
|
|
271
|
+
// Disable all shortcuts
|
|
272
|
+
<VariantProvider shortcuts={false}>
|
|
273
|
+
<App />
|
|
274
|
+
</VariantProvider>
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## Vibeflow Overlay Integration
|
|
280
|
+
|
|
281
|
+
When the [Vibeflow](https://vibeflow.tools) overlay is injected into the page, `VariantDevToolbar` automatically hides its standalone vibeflow icon button and registers itself with the overlay. The toolbar is then accessible via the overlay's right-click context menu under **"Prototyping"** — both on the bottom-right trigger and when right-clicking any element on the page.
|
|
282
|
+
|
|
283
|
+
No configuration needed — detection is automatic.
|
|
284
|
+
|
|
285
|
+
> **Tip for AI coding agents:** Combine `ui-prototyping` with the [Vibeflow overlay](https://vibeflow.tools) so variants are instantly accessible via right-click anywhere on the page. The agent defines the variants; you switch and approve. See the [ui-prototyping webpage](https://www.vibeflow.tools/ui-prototyping.html) for a live demo.
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## Advanced: Power User Utilities
|
|
290
|
+
|
|
291
|
+
The following URL/localStorage utilities are exported for cases where you need manual control:
|
|
292
|
+
|
|
293
|
+
```ts
|
|
294
|
+
import {
|
|
295
|
+
readVariantFromUrl,
|
|
296
|
+
writeVariantToUrl,
|
|
297
|
+
removeVariantFromUrl,
|
|
298
|
+
readVariantFromStorage,
|
|
299
|
+
writeVariantToStorage,
|
|
300
|
+
removeVariantFromStorage,
|
|
301
|
+
resolveActiveVariant,
|
|
302
|
+
readUiVisibleFromStorage,
|
|
303
|
+
writeUiVisibleToStorage,
|
|
304
|
+
} from '@vibeflow-tools/prototyping'
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
## TypeScript
|
|
310
|
+
|
|
311
|
+
All exports are fully typed. The `useVariant` hook preserves key-literal types so your IDE autocompletes variant config properties:
|
|
312
|
+
|
|
313
|
+
```tsx
|
|
314
|
+
const variants = {
|
|
315
|
+
default: {},
|
|
316
|
+
compact: { compact: true as const, rows: 3 as const },
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function MyComponent() {
|
|
320
|
+
const v = useVariant('MyComponent', variants)
|
|
321
|
+
// v.compact is typed as `true | undefined`
|
|
322
|
+
// v.rows is typed as `3 | undefined`
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
## Contributing
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
pnpm install
|
|
332
|
+
pnpm --filter @vibeflow-tools/prototyping run build
|
|
333
|
+
pnpm --filter @vibeflow-tools/prototyping run test
|
|
334
|
+
pnpm --filter @vibeflow-tools/prototyping run test:coverage
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
## License
|
|
340
|
+
|
|
341
|
+
[Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0) — see [NOTICE](https://github.com/zorcec/vibeflow/blob/main/NOTICE) for third-party attributions.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
/** Default keyboard shortcuts for toggling the variant UI. */
|
|
6
|
+
interface KeyboardShortcut {
|
|
7
|
+
/** The key value (e.g. "h", "V"). Case-sensitive for Ctrl+Shift combos. */
|
|
8
|
+
key: string;
|
|
9
|
+
/** Whether Alt must be held. */
|
|
10
|
+
alt?: boolean;
|
|
11
|
+
/** Whether Ctrl must be held. */
|
|
12
|
+
ctrl?: boolean;
|
|
13
|
+
/** Whether Shift must be held. */
|
|
14
|
+
shift?: boolean;
|
|
15
|
+
/** Whether Meta (Cmd/Win) must be held. */
|
|
16
|
+
meta?: boolean;
|
|
17
|
+
}
|
|
18
|
+
interface KeyboardShortcutsOptions {
|
|
19
|
+
/** Called when a toggle shortcut is pressed. */
|
|
20
|
+
onToggleUi: () => void;
|
|
21
|
+
/**
|
|
22
|
+
* Custom shortcut definitions. Defaults to [Alt+H, Ctrl+Shift+V].
|
|
23
|
+
* Set to `false` to disable all keyboard shortcuts.
|
|
24
|
+
*/
|
|
25
|
+
shortcuts?: KeyboardShortcut[] | false;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Registers global keyboard shortcuts for the prototyping system.
|
|
29
|
+
*
|
|
30
|
+
* | Default Shortcut | Action |
|
|
31
|
+
* |------------------|----------------------|
|
|
32
|
+
* | Alt + H | Toggle all switchers |
|
|
33
|
+
* | Ctrl+Shift+V | Toggle all switchers |
|
|
34
|
+
*
|
|
35
|
+
* Shortcuts can be customized via the `shortcuts` option, or disabled
|
|
36
|
+
* entirely by passing `false`.
|
|
37
|
+
*/
|
|
38
|
+
declare function useKeyboardShortcuts({ onToggleUi, shortcuts, }: KeyboardShortcutsOptions): void;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Core TypeScript types for @vibeflow-tools/prototyping.
|
|
42
|
+
*
|
|
43
|
+
* VariantDefinitions maps variant names to arbitrary config objects.
|
|
44
|
+
* The generic V type preserves key-literal types for TypeScript consumers.
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
/** A map of variant name → arbitrary config object. */
|
|
48
|
+
type VariantDefinitions<V extends Record<string, Record<string, unknown>>> = V;
|
|
49
|
+
/** The mode controls when variant switcher UI is visible. */
|
|
50
|
+
type VariantMode = "dev" | "always";
|
|
51
|
+
/** Internal state stored per named scope. */
|
|
52
|
+
interface VariantState {
|
|
53
|
+
/** Currently active variant name. */
|
|
54
|
+
activeVariant: string;
|
|
55
|
+
/** All registered variant names for this scope. */
|
|
56
|
+
variantNames: string[];
|
|
57
|
+
}
|
|
58
|
+
/** The shape of the context value exposed by VariantProvider. */
|
|
59
|
+
interface VariantContextValue {
|
|
60
|
+
/** Get the active variant name for a given scope. */
|
|
61
|
+
getActiveVariant: (name: string) => string;
|
|
62
|
+
/** Set the active variant for a scope. */
|
|
63
|
+
setActiveVariant: (name: string, variant: string) => void;
|
|
64
|
+
/** Register a scope with its variant names (called by useVariant / VariantSwitcher). */
|
|
65
|
+
registerScope: (name: string, variantNames: string[]) => void;
|
|
66
|
+
/**
|
|
67
|
+
* Register a VariantSwitcher instance for a scope.
|
|
68
|
+
* Returns true if this is the first instance (should render), false if duplicate (should skip).
|
|
69
|
+
*/
|
|
70
|
+
registerSwitcher: (name: string) => boolean;
|
|
71
|
+
/** Unregister a VariantSwitcher instance (called on unmount). */
|
|
72
|
+
unregisterSwitcher: (name: string) => void;
|
|
73
|
+
/** All registered scopes and their states. */
|
|
74
|
+
scopes: Record<string, VariantState>;
|
|
75
|
+
/** Whether switcher UI should be visible. */
|
|
76
|
+
uiVisible: boolean;
|
|
77
|
+
/** Toggle switcher UI visibility. */
|
|
78
|
+
toggleUiVisible: () => void;
|
|
79
|
+
/** The current mode ("dev" | "always"). */
|
|
80
|
+
mode: VariantMode;
|
|
81
|
+
}
|
|
82
|
+
/** Props for VariantProvider. */
|
|
83
|
+
interface VariantProviderProps {
|
|
84
|
+
children: React.ReactNode;
|
|
85
|
+
/**
|
|
86
|
+
* "dev" (default) — switcher UI is hidden unless NODE_ENV !== "production".
|
|
87
|
+
* "always" — switcher UI is always visible (useful for A/B testing / user-facing demos).
|
|
88
|
+
*/
|
|
89
|
+
mode?: VariantMode;
|
|
90
|
+
/**
|
|
91
|
+
* Initial UI visibility state.
|
|
92
|
+
* Defaults to true when mode allows it.
|
|
93
|
+
*/
|
|
94
|
+
defaultVisible?: boolean;
|
|
95
|
+
/**
|
|
96
|
+
* Custom keyboard shortcuts for toggling the variant UI.
|
|
97
|
+
* Defaults to [Alt+H, Ctrl+Shift+V]. Pass `false` to disable shortcuts.
|
|
98
|
+
*/
|
|
99
|
+
shortcuts?: KeyboardShortcut[] | false;
|
|
100
|
+
}
|
|
101
|
+
/** Props shared by switcher components. */
|
|
102
|
+
interface SwitcherProps {
|
|
103
|
+
/** Scope name — must match the first argument of useVariant. */
|
|
104
|
+
name: string;
|
|
105
|
+
/** Variant definitions object — same as passed to useVariant. */
|
|
106
|
+
variants: Record<string, Record<string, unknown>>;
|
|
107
|
+
/** Preferred side for floating placement. Default: "right". */
|
|
108
|
+
position?: "right" | "left";
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
declare const VariantContext: react.Context<VariantContextValue | null>;
|
|
112
|
+
/** Access the variant context. Throws if used outside VariantProvider. */
|
|
113
|
+
declare function useVariantContext(): VariantContextValue;
|
|
114
|
+
/**
|
|
115
|
+
* VariantProvider wraps your app (or a subtree) and provides the variant
|
|
116
|
+
* switching system. All useVariant hooks and switcher components below it
|
|
117
|
+
* share state via this provider.
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* <VariantProvider>
|
|
121
|
+
* <App />
|
|
122
|
+
* </VariantProvider>
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* // Always visible — for A/B testing or user-facing demos
|
|
126
|
+
* <VariantProvider mode="always">
|
|
127
|
+
* <App />
|
|
128
|
+
* </VariantProvider>
|
|
129
|
+
*/
|
|
130
|
+
declare function VariantProvider({ children, mode, defaultVisible, shortcuts, }: VariantProviderProps): ReactNode;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Core hook for reading the active variant config.
|
|
134
|
+
*
|
|
135
|
+
* Registers the scope with the VariantProvider, resolves the active variant
|
|
136
|
+
* from URL → localStorage → default, and returns the corresponding config.
|
|
137
|
+
*
|
|
138
|
+
* @param name - Unique scope name (e.g. "TaskCard", "KanbanBoard")
|
|
139
|
+
* @param variants - Variant definitions object; first key is the default
|
|
140
|
+
* @returns The config object for the active variant
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* const variants = {
|
|
144
|
+
* default: {},
|
|
145
|
+
* minimal: { compact: true },
|
|
146
|
+
* detailed: { showMeta: true, showComments: true },
|
|
147
|
+
* }
|
|
148
|
+
*
|
|
149
|
+
* function TaskCard() {
|
|
150
|
+
* const variant = useVariant('TaskCard', variants)
|
|
151
|
+
* return <div className={variant.compact ? 'compact' : ''}>…</div>
|
|
152
|
+
* }
|
|
153
|
+
*/
|
|
154
|
+
declare function useVariant<V extends Record<string, Record<string, unknown>>>(name: string, variants: V): V[keyof V];
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Resolves the active variant key for a scope.
|
|
158
|
+
*
|
|
159
|
+
* Checks context first, then falls back to URL → localStorage → default.
|
|
160
|
+
* Used by useVariant, VariantSwitcher, and PageVariantSwitcher to avoid
|
|
161
|
+
* duplicating the same resolution logic.
|
|
162
|
+
*/
|
|
163
|
+
declare function useActiveVariant(name: string, variantKeys: string[]): string;
|
|
164
|
+
|
|
165
|
+
interface PageVariantSwitcherProps {
|
|
166
|
+
/** Scope name — must match the first argument of useVariant. */
|
|
167
|
+
name: string;
|
|
168
|
+
/** Variant definitions — same as passed to useVariant. */
|
|
169
|
+
variants: Record<string, Record<string, unknown>>;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Dark segmented bar floating top-left of the page.
|
|
173
|
+
* Renders automatically via VariantProvider for page-level variant switching.
|
|
174
|
+
*
|
|
175
|
+
* Place this component anywhere inside VariantProvider — typically at the
|
|
176
|
+
* top of the page component alongside useVariant.
|
|
177
|
+
*
|
|
178
|
+
* @example
|
|
179
|
+
* function App() {
|
|
180
|
+
* const layout = useVariant('Layout', layoutVariants)
|
|
181
|
+
* return (
|
|
182
|
+
* <>
|
|
183
|
+
* <PageVariantSwitcher name="Layout" variants={layoutVariants} />
|
|
184
|
+
* <main>…</main>
|
|
185
|
+
* </>
|
|
186
|
+
* )
|
|
187
|
+
* }
|
|
188
|
+
*/
|
|
189
|
+
declare function PageVariantSwitcher({ name, variants, }: PageVariantSwitcherProps): react_jsx_runtime.JSX.Element | null;
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Component variant switcher with a subtle indicator dot.
|
|
193
|
+
*
|
|
194
|
+
* Shows a small, non-intrusive dot on the right (or left) side of the parent.
|
|
195
|
+
* Clicking the dot expands the full numbered-dots picker.
|
|
196
|
+
* Clicking outside or pressing Escape collapses it back.
|
|
197
|
+
*
|
|
198
|
+
* The dot is draggable — hold for 300ms, then drag to reposition anywhere on
|
|
199
|
+
* the viewport. The new position is persisted to localStorage per scope name.
|
|
200
|
+
*
|
|
201
|
+
* Deduplicates per scope — only the first VariantSwitcher for a given
|
|
202
|
+
* scope renders. Multiple components using the same scope share one switcher.
|
|
203
|
+
*
|
|
204
|
+
* The parent element must have `position: relative` for correct placement.
|
|
205
|
+
*
|
|
206
|
+
* @example
|
|
207
|
+
* function TaskCard({ task }) {
|
|
208
|
+
* const variant = useVariant('TaskCard', taskCardVariants)
|
|
209
|
+
* return (
|
|
210
|
+
* <div style={{ position: 'relative' }}>
|
|
211
|
+
* <VariantSwitcher name="TaskCard" variants={taskCardVariants} />
|
|
212
|
+
* {variant.compact ? <CompactView /> : <FullView />}
|
|
213
|
+
* </div>
|
|
214
|
+
* )
|
|
215
|
+
* }
|
|
216
|
+
*/
|
|
217
|
+
declare function VariantSwitcher({ name, variants, position, }: SwitcherProps): react_jsx_runtime.JSX.Element | null;
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Optional floating toolbar that shows all registered variant scopes.
|
|
221
|
+
* Toggled via Ctrl+Shift+V keyboard shortcut or programmatically.
|
|
222
|
+
*
|
|
223
|
+
* **Vibeflow overlay integration:** When the Vibeflow overlay is detected,
|
|
224
|
+
* the standalone vibeflow icon button is hidden. Instead, the toolbar is accessible
|
|
225
|
+
* via the overlay's right-click context menu ("Prototyping" option).
|
|
226
|
+
*
|
|
227
|
+
* Place this once near the root of your app inside VariantProvider.
|
|
228
|
+
*
|
|
229
|
+
* @example
|
|
230
|
+
* function App() {
|
|
231
|
+
* return (
|
|
232
|
+
* <VariantProvider>
|
|
233
|
+
* <VariantDevToolbar />
|
|
234
|
+
* <MainContent />
|
|
235
|
+
* </VariantProvider>
|
|
236
|
+
* )
|
|
237
|
+
* }
|
|
238
|
+
*/
|
|
239
|
+
declare function VariantDevToolbar(): react_jsx_runtime.JSX.Element | null;
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Global variant registry.
|
|
243
|
+
*
|
|
244
|
+
* Optional module-level registration of variant definitions so they don't
|
|
245
|
+
* have to be re-passed to every useVariant call in deeply nested trees.
|
|
246
|
+
* Registered variants are merged with inline variant definitions — inline
|
|
247
|
+
* always wins.
|
|
248
|
+
*/
|
|
249
|
+
type VariantDefs = Record<string, Record<string, unknown>>;
|
|
250
|
+
/**
|
|
251
|
+
* Register a variant definition at module level.
|
|
252
|
+
* Useful when you want to centralise all variant registrations in one file.
|
|
253
|
+
*
|
|
254
|
+
* @example
|
|
255
|
+
* registerVariant('TaskCard', {
|
|
256
|
+
* default: {},
|
|
257
|
+
* minimal: { compact: true },
|
|
258
|
+
* detailed: { showMeta: true },
|
|
259
|
+
* })
|
|
260
|
+
*/
|
|
261
|
+
declare function registerVariant(name: string, variants: VariantDefs): void;
|
|
262
|
+
/**
|
|
263
|
+
* Retrieve previously registered variant definitions for a scope.
|
|
264
|
+
* Returns undefined if the scope was never registered.
|
|
265
|
+
*/
|
|
266
|
+
declare function getRegisteredVariant(name: string): VariantDefs | undefined;
|
|
267
|
+
/** Clear all registrations. Primarily used in tests. */
|
|
268
|
+
declare function clearVariantRegistry(): void;
|
|
269
|
+
|
|
270
|
+
/** Read the active variant for a scope from the current URL search params. */
|
|
271
|
+
declare function readVariantFromUrl(name: string): string | null;
|
|
272
|
+
/** Write the active variant for a scope into the URL (pushState — no reload). */
|
|
273
|
+
declare function writeVariantToUrl(name: string, variant: string): void;
|
|
274
|
+
/** Remove a scope's variant from the URL. */
|
|
275
|
+
declare function removeVariantFromUrl(name: string): void;
|
|
276
|
+
/** Read the active variant for a scope from localStorage. */
|
|
277
|
+
declare function readVariantFromStorage(name: string): string | null;
|
|
278
|
+
/** Write the active variant for a scope to localStorage. */
|
|
279
|
+
declare function writeVariantToStorage(name: string, variant: string): void;
|
|
280
|
+
/** Remove a scope's variant from localStorage. */
|
|
281
|
+
declare function removeVariantFromStorage(name: string): void;
|
|
282
|
+
/** Persist the UI visibility flag. */
|
|
283
|
+
declare function writeUiVisibleToStorage(visible: boolean): void;
|
|
284
|
+
/** Read the persisted UI visibility flag. Returns null when not set. */
|
|
285
|
+
declare function readUiVisibleFromStorage(): boolean | null;
|
|
286
|
+
/**
|
|
287
|
+
* Resolve the active variant for a scope.
|
|
288
|
+
* Priority: URL param → localStorage → provided default key.
|
|
289
|
+
*/
|
|
290
|
+
declare function resolveActiveVariant(name: string, variantKeys: string[], defaultKey: string): string;
|
|
291
|
+
|
|
292
|
+
export { type KeyboardShortcut, PageVariantSwitcher, type SwitcherProps, VariantContext, type VariantContextValue, type VariantDefinitions, VariantDevToolbar, type VariantMode, VariantProvider, type VariantProviderProps, type VariantState, VariantSwitcher, clearVariantRegistry, getRegisteredVariant, readUiVisibleFromStorage, readVariantFromStorage, readVariantFromUrl, registerVariant, removeVariantFromStorage, removeVariantFromUrl, resolveActiveVariant, useActiveVariant, useKeyboardShortcuts, useVariant, useVariantContext, writeUiVisibleToStorage, writeVariantToStorage, writeVariantToUrl };
|