proto-plugin 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 (209) hide show
  1. package/AGENTS.md +350 -0
  2. package/README.md +279 -0
  3. package/components.json +18 -0
  4. package/package.json +73 -0
  5. package/scripts/download-mobbin-reference-images.mjs +112 -0
  6. package/scripts/lib/host-config.example.sh +48 -0
  7. package/scripts/lib/host-config.sh +25 -0
  8. package/scripts/lib/resolve-host-root.mjs +18 -0
  9. package/scripts/lib/source-path.sh +49 -0
  10. package/scripts/link-source-db.sh +128 -0
  11. package/scripts/link-source.sh +40 -0
  12. package/scripts/publish-package.sh +147 -0
  13. package/scripts/share-prototype.sh +279 -0
  14. package/scripts/sync-from-source.sh +74 -0
  15. package/scripts/verify-prototype-component-ids.mjs +151 -0
  16. package/scripts/verify-prototype-preview-states.mjs +135 -0
  17. package/src/app.ts +4 -0
  18. package/src/components/platform-ui/code-block.tsx +21 -0
  19. package/src/components/platform-ui/controls-panel-options.tsx +71 -0
  20. package/src/components/platform-ui/controls-panel-select.tsx +438 -0
  21. package/src/components/platform-ui/empty-state.tsx +15 -0
  22. package/src/components/platform-ui/icon-button.tsx +18 -0
  23. package/src/components/platform-ui/panel-select.tsx +123 -0
  24. package/src/components/platform-ui/panel.tsx +97 -0
  25. package/src/components/platform-ui/review-dropdown-menu.tsx +35 -0
  26. package/src/components/platform-ui/sidebar.tsx +293 -0
  27. package/src/components/platform-ui/toolbar-icon-button.tsx +34 -0
  28. package/src/components/platform-ui/toolbar.tsx +37 -0
  29. package/src/components/prototype-gallery-client.tsx +57 -0
  30. package/src/components/prototype-provider.tsx +20 -0
  31. package/src/components/prototypes/mini-pill-label.tsx +37 -0
  32. package/src/components/prototypes/prototype-change-log-panel.tsx +207 -0
  33. package/src/components/prototypes/prototype-comment-provider.tsx +61 -0
  34. package/src/components/prototypes/prototype-comment-toolbar.tsx +65 -0
  35. package/src/components/prototypes/prototype-control.tsx +31 -0
  36. package/src/components/prototypes/prototype-controls.tsx +553 -0
  37. package/src/components/prototypes/prototype-create-exploration-modal.tsx +117 -0
  38. package/src/components/prototypes/prototype-create-state-modal.tsx +117 -0
  39. package/src/components/prototypes/prototype-default-state-map-page.tsx +66 -0
  40. package/src/components/prototypes/prototype-design-brief-modal.tsx +128 -0
  41. package/src/components/prototypes/prototype-design-brief-panel.tsx +525 -0
  42. package/src/components/prototypes/prototype-design-context-panel.tsx +65 -0
  43. package/src/components/prototypes/prototype-floating-pill.module.scss +1341 -0
  44. package/src/components/prototypes/prototype-mobbin-gallery.tsx +330 -0
  45. package/src/components/prototypes/prototype-reference-docs.tsx +222 -0
  46. package/src/components/prototypes/prototype-review-chrome.module.scss +630 -0
  47. package/src/components/prototypes/prototype-review-chrome.tsx +1241 -0
  48. package/src/components/prototypes/prototype-review-dropdown-menu-item.tsx +1 -0
  49. package/src/components/prototypes/prototype-review-dropdown-menu-separator.tsx +1 -0
  50. package/src/components/prototypes/prototype-review-panel-menu-item.tsx +1 -0
  51. package/src/components/prototypes/prototype-review-panel-menu-separator.tsx +1 -0
  52. package/src/components/prototypes/prototype-review-panel-select.tsx +4 -0
  53. package/src/components/prototypes/prototype-review-sidebar.tsx +167 -0
  54. package/src/components/prototypes/prototype-share-link-restore.tsx +75 -0
  55. package/src/components/prototypes/prototype-share-mode.tsx +206 -0
  56. package/src/components/prototypes/prototype-spec-panel-content.tsx +538 -0
  57. package/src/components/prototypes/prototype-state-canvas-overlay.module.scss +336 -0
  58. package/src/components/prototypes/prototype-state-canvas-overlay.tsx +1023 -0
  59. package/src/components/prototypes/prototype-state-canvas.tsx +38 -0
  60. package/src/components/prototypes/prototype-state-screenshot-preview.tsx +47 -0
  61. package/src/components/prototypes/prototype-target.tsx +120 -0
  62. package/src/components/prototypes/prototype-tweak-highlight.tsx +132 -0
  63. package/src/components/prototypes/prototype-tweaks.tsx +48 -0
  64. package/src/components/prototypes/prototype-variant-explorer.tsx +347 -0
  65. package/src/components/prototypes/prototype-variant-rationale-panel.tsx +49 -0
  66. package/src/components/prototypes/prototype-variant-tabs.tsx +207 -0
  67. package/src/components/prototypes/prototype-vercel-preview-modal.tsx +120 -0
  68. package/src/components/prototypes/state-map-annotation-tooltip.module.scss +89 -0
  69. package/src/components/prototypes/state-map-annotation-tooltip.tsx +137 -0
  70. package/src/components/prototypes/state-map-wireframe-highlight.module.scss +25 -0
  71. package/src/components/prototypes/state-map-wireframe-highlight.tsx +82 -0
  72. package/src/components/shell/prototype-gallery-shell.tsx +79 -0
  73. package/src/components/shell/prototype-screenshot-capture.tsx +45 -0
  74. package/src/components/shell/prototype-shell.module.scss +194 -0
  75. package/src/components/shell/prototype-shell.tsx +160 -0
  76. package/src/components/shell/prototype-state-screenshot-capture.tsx +68 -0
  77. package/src/components/ui/button.tsx +58 -0
  78. package/src/components/ui/dialog.module.scss +81 -0
  79. package/src/components/ui/dialog.tsx +127 -0
  80. package/src/components/ui/dropdown-menu.tsx +200 -0
  81. package/src/components/ui/input.tsx +22 -0
  82. package/src/components/ui/label.tsx +26 -0
  83. package/src/components/ui/option-select.tsx +81 -0
  84. package/src/components/ui/scroll-area.tsx +48 -0
  85. package/src/components/ui/separator.tsx +31 -0
  86. package/src/components/ui/slider.tsx +28 -0
  87. package/src/components/ui/switch.tsx +29 -0
  88. package/src/components/ui/tabs.tsx +55 -0
  89. package/src/components/ui/toggle-group.tsx +80 -0
  90. package/src/components/ui/tooltip.tsx +32 -0
  91. package/src/config/create-prototype-gallery-page.tsx +65 -0
  92. package/src/config/create-prototype-host-app.tsx +121 -0
  93. package/src/config/create-prototype-page.tsx +41 -0
  94. package/src/config/create-prototype-site-layout.tsx +28 -0
  95. package/src/config/define-prototype-config.ts +7 -0
  96. package/src/config/prototype-icon.tsx +25 -0
  97. package/src/config/prototype-site-layout-client.tsx +35 -0
  98. package/src/config/with-prototype.ts +20 -0
  99. package/src/index.ts +303 -0
  100. package/src/lib/pr-split/build-pr-agent-prompt.ts +68 -0
  101. package/src/lib/pr-split/build-pr-split-prototype-url.ts +12 -0
  102. package/src/lib/pr-split/pr-split-highlight.ts +6 -0
  103. package/src/lib/pr-split/pr-split-types.ts +58 -0
  104. package/src/lib/pr-split/resolve-source-preview-url.ts +45 -0
  105. package/src/lib/pr-split/use-pr-vercel-previews.ts +69 -0
  106. package/src/lib/prototype-comments/core/annotation-channel.ts +41 -0
  107. package/src/lib/prototype-comments/core/annotation-status.ts +60 -0
  108. package/src/lib/prototype-comments/core/annotation-target.ts +567 -0
  109. package/src/lib/prototype-comments/core/capture-theme.ts +41 -0
  110. package/src/lib/prototype-comments/core/capture.ts +11 -0
  111. package/src/lib/prototype-comments/core/comment-capture-blocked.ts +47 -0
  112. package/src/lib/prototype-comments/core/comment-threads.ts +52 -0
  113. package/src/lib/prototype-comments/core/element-identification.ts +615 -0
  114. package/src/lib/prototype-comments/core/export.ts +35 -0
  115. package/src/lib/prototype-comments/core/freeze-animations.ts +266 -0
  116. package/src/lib/prototype-comments/core/identify-element.ts +76 -0
  117. package/src/lib/prototype-comments/core/normalize-viewport.ts +35 -0
  118. package/src/lib/prototype-comments/core/react-detection.ts +704 -0
  119. package/src/lib/prototype-comments/core/restore.ts +7 -0
  120. package/src/lib/prototype-comments/core/screenshot.ts +428 -0
  121. package/src/lib/prototype-comments/core/source-location.ts +906 -0
  122. package/src/lib/prototype-comments/core/storage.ts +80 -0
  123. package/src/lib/prototype-comments/core/types.ts +79 -0
  124. package/src/lib/prototype-comments/core/validation.ts +17 -0
  125. package/src/lib/prototype-comments/hooks/useLayoutShift.ts +61 -0
  126. package/src/lib/prototype-comments/index.ts +58 -0
  127. package/src/lib/prototype-comments/react/CommentCaptureToolbar.tsx +1398 -0
  128. package/src/lib/prototype-comments/react/CommentProvider.tsx +298 -0
  129. package/src/lib/prototype-comments/scss.d.ts +10 -0
  130. package/src/lib/prototype-comments/ui/CommentAnnotationOverlay.tsx +253 -0
  131. package/src/lib/prototype-comments/ui/CommentDetailPage.tsx +56 -0
  132. package/src/lib/prototype-comments/ui/CommentHeaderCount.tsx +28 -0
  133. package/src/lib/prototype-comments/ui/CommentThread.tsx +331 -0
  134. package/src/lib/prototype-comments/ui/CommentsGrid.tsx +780 -0
  135. package/src/lib/prototype-comments/ui/CommentsListPage.tsx +25 -0
  136. package/src/lib/prototype-comments/ui/CommentsSidebar.tsx +77 -0
  137. package/src/lib/prototype-comments/ui/annotation-marker/index.tsx +200 -0
  138. package/src/lib/prototype-comments/ui/annotation-marker/styles.module.scss +194 -0
  139. package/src/lib/prototype-comments/ui/annotation-popup/index.tsx +335 -0
  140. package/src/lib/prototype-comments/ui/annotation-popup/styles.module.scss +469 -0
  141. package/src/lib/prototype-comments/ui/capture-styles.module.scss +2225 -0
  142. package/src/lib/prototype-comments/ui/comment-highlight.ts +48 -0
  143. package/src/lib/prototype-comments/ui/comment-thread.module.scss +203 -0
  144. package/src/lib/prototype-comments/ui/comments-grid-card.module.scss +253 -0
  145. package/src/lib/prototype-comments/ui/comments-sidebar.module.scss +123 -0
  146. package/src/lib/prototype-comments/ui/icon-transitions.module.scss +51 -0
  147. package/src/lib/prototype-comments/ui/icons.tsx +1086 -0
  148. package/src/lib/prototype-comments/ui/is-comment-detail-path.ts +7 -0
  149. package/src/lib/prototypes/capture-prototype-viewport.ts +32 -0
  150. package/src/lib/prototypes/changelog-meta-context.tsx +162 -0
  151. package/src/lib/prototypes/changelog-meta-storage.ts +62 -0
  152. package/src/lib/prototypes/changelog-meta.ts +95 -0
  153. package/src/lib/prototypes/create-prototype-registry.ts +74 -0
  154. package/src/lib/prototypes/create-state-screenshot-thumbnail.ts +46 -0
  155. package/src/lib/prototypes/design-exploration-default-storage.ts +68 -0
  156. package/src/lib/prototypes/design-exploration-default.ts +51 -0
  157. package/src/lib/prototypes/design-exploration-types.ts +203 -0
  158. package/src/lib/prototypes/prototype-comment-registry.tsx +145 -0
  159. package/src/lib/prototypes/prototype-comment-review-bridge.tsx +40 -0
  160. package/src/lib/prototypes/prototype-comment-review-state.ts +57 -0
  161. package/src/lib/prototypes/prototype-component-registry.ts +40 -0
  162. package/src/lib/prototypes/prototype-config-types.ts +34 -0
  163. package/src/lib/prototypes/prototype-preview-state-registry.ts +443 -0
  164. package/src/lib/prototypes/prototype-preview-state-types.ts +89 -0
  165. package/src/lib/prototypes/prototype-review-context.tsx +905 -0
  166. package/src/lib/prototypes/prototype-review-url.ts +134 -0
  167. package/src/lib/prototypes/prototype-share-link.ts +175 -0
  168. package/src/lib/prototypes/prototype-state-canvas-constants.ts +118 -0
  169. package/src/lib/prototypes/prototype-state-canvas-types.ts +159 -0
  170. package/src/lib/prototypes/prototype-state-screenshot.ts +39 -0
  171. package/src/lib/prototypes/prototype-tweak-registry.ts +96 -0
  172. package/src/lib/prototypes/prototype-tweak-types.ts +31 -0
  173. package/src/lib/prototypes/reference-docs.ts +137 -0
  174. package/src/lib/prototypes/screenshot-manifest.ts +33 -0
  175. package/src/lib/prototypes/screenshot-src.ts +7 -0
  176. package/src/lib/prototypes/share-command.ts +8 -0
  177. package/src/lib/prototypes/state-map-annotation.ts +70 -0
  178. package/src/lib/prototypes/use-design-exploration-default.ts +114 -0
  179. package/src/lib/prototypes/use-persisted-local-state.ts +140 -0
  180. package/src/lib/prototypes/use-prototype-comments.ts +63 -0
  181. package/src/lib/prototypes/use-prototype-mobile-viewport-frame.ts +46 -0
  182. package/src/lib/prototypes/use-prototype-state-screenshot-src.ts +71 -0
  183. package/src/lib/prototypes/use-prototype-tool-theme.tsx +115 -0
  184. package/src/lib/prototypes/use-prototype-viewport-frame.ts +34 -0
  185. package/src/lib/prototypes/use-register-prototype-reference-docs.ts +16 -0
  186. package/src/lib/prototypes/use-snap-corner-position.ts +417 -0
  187. package/src/lib/prototypes/use-toolbar-hover-panel.ts +98 -0
  188. package/src/lib/prototypes/variant-set-lucide-icon.tsx +55 -0
  189. package/src/lib/tool-portal.ts +33 -0
  190. package/src/lib/use-copy-to-clipboard.ts +81 -0
  191. package/src/lib/utils.ts +6 -0
  192. package/src/lib/vercel-preview/parse-vercel-github-comment.ts +81 -0
  193. package/src/server/create-host-api-route.ts +100 -0
  194. package/src/server/create-prototype-api-routes.ts +428 -0
  195. package/src/server/gallery/create-gallery-api.ts +56 -0
  196. package/src/server/gallery/folders-route.ts +156 -0
  197. package/src/server/gallery/gallery-env.ts +39 -0
  198. package/src/server/gallery/gallery-route.ts +150 -0
  199. package/src/server/gallery/gallery-types.ts +15 -0
  200. package/src/server/gallery/image-route.ts +59 -0
  201. package/src/server/gallery/item-route.ts +92 -0
  202. package/src/server/pr-vercel-preview-route.ts +162 -0
  203. package/src/server/redis/client.ts +55 -0
  204. package/src/server/redis/prototype-changelog-meta.ts +31 -0
  205. package/src/server/redis/prototype-comments.ts +92 -0
  206. package/src/server/redis/prototype-design-exploration-default.ts +32 -0
  207. package/src/server.ts +51 -0
  208. package/src/styles/globals.css +624 -0
  209. package/tsconfig.json +19 -0
@@ -0,0 +1,905 @@
1
+ "use client";
2
+
3
+ import {
4
+ createContext,
5
+ useCallback,
6
+ useContext,
7
+ useEffect,
8
+ useLayoutEffect,
9
+ useMemo,
10
+ useRef,
11
+ useState,
12
+ type MutableRefObject,
13
+ type ReactNode,
14
+ } from "react";
15
+
16
+ import type { AnnotationChannel } from "@prototype/lib/prototype-comments/core/annotation-channel";
17
+ import { scrollPrototypeTargetIntoView } from "@prototype/lib/prototypes/prototype-share-link";
18
+ import {
19
+ hasReviewUrlParams,
20
+ parseReviewStateFromSearchParams,
21
+ syncReviewStateToUrl,
22
+ type PrototypeReviewSidebarPanel,
23
+ type PrototypeReviewTab,
24
+ } from "@prototype/lib/prototypes/prototype-review-url";
25
+ import { focusPrototypeTweak } from "@prototype/lib/prototypes/prototype-tweak-registry";
26
+ import type { PrototypeStateCanvasConfig } from "@prototype/lib/prototypes/prototype-state-canvas-types";
27
+ import type { PrototypePreviewState } from "@prototype/lib/prototypes/prototype-preview-state-types";
28
+ import {
29
+ defaultReferenceDocsConfigPath,
30
+ type PrototypeReferenceDoc,
31
+ } from "@prototype/lib/prototypes/reference-docs";
32
+ import {
33
+ prototypeReviewPreferenceKey,
34
+ usePersistedLocalString,
35
+ } from "@prototype/lib/prototypes/use-persisted-local-state";
36
+
37
+ export type { PrototypeReviewSidebarPanel, PrototypeReviewTab };
38
+
39
+ export type PrototypeViewportLayout = "desktop" | "mobile";
40
+
41
+ /** iPhone 14/15 logical width */
42
+ export const PROTOTYPE_MOBILE_VIEWPORT_WIDTH_PX = 390;
43
+
44
+ /** iPhone 14/15 logical height */
45
+ export const PROTOTYPE_MOBILE_VIEWPORT_HEIGHT_PX = 844;
46
+
47
+ export type PrototypeVariantSet = {
48
+ id: string;
49
+ label: string;
50
+ onActivate?: () => void;
51
+ };
52
+
53
+ function hasPrototypeTweaksContent(content: ReactNode | null | undefined): boolean {
54
+ return content != null && content !== false && content !== true;
55
+ }
56
+
57
+ type PrototypeReviewContextValue = {
58
+ slug: string;
59
+ open: boolean;
60
+ sidebarPanel: PrototypeReviewSidebarPanel | null;
61
+ activeTab: PrototypeReviewTab;
62
+ captureChannel: AnnotationChannel;
63
+ setCaptureChannel: (channel: AnnotationChannel) => void;
64
+ prodReferenceAvailable: boolean;
65
+ showProdReference: boolean;
66
+ setShowProdReference: (show: boolean) => void;
67
+ toggleProdReference: () => void;
68
+ registerProdReference: (available: boolean) => void;
69
+ /** Latest prototype controls panel content — read via ref after `setTweaksContent`. */
70
+ tweaksContentRef: MutableRefObject<ReactNode | null>;
71
+ /** Whether prototype controls content is registered (drives States toolbar button). */
72
+ hasTweaksContent: boolean;
73
+ setTweaksContent: (content: ReactNode | null) => void;
74
+ /** Latest spec panel content — read via ref after `setSpecContent`. */
75
+ specContentRef: MutableRefObject<ReactNode | null>;
76
+ /** Whether spec content is registered (drives Spec toolbar button). */
77
+ hasSpecContent: boolean;
78
+ setSpecContent: (content: ReactNode | null) => void;
79
+ /** Latest state canvas config — read via ref after `setStateCanvasConfig`. */
80
+ stateCanvasConfigRef: MutableRefObject<PrototypeStateCanvasConfig | null>;
81
+ /** Whether a state canvas is registered (drives Canvas toolbar button). */
82
+ hasStateCanvas: boolean;
83
+ setStateCanvasConfig: (config: PrototypeStateCanvasConfig | null) => void;
84
+ stateCanvasPagePath: string | null;
85
+ setStateCanvasPagePath: (path: string | null) => void;
86
+ /** Preview states from the controls panel select (read via ref for New state modal). */
87
+ previewStatesRef: MutableRefObject<PrototypePreviewState[]>;
88
+ registerPreviewStates: (states: PrototypePreviewState[]) => void;
89
+ referenceDocs: PrototypeReferenceDoc[];
90
+ referenceDocsConfigFilePath: string | undefined;
91
+ registerReferenceDocs: (
92
+ docs: PrototypeReferenceDoc[],
93
+ configFilePath?: string,
94
+ ) => void;
95
+ variantSets: PrototypeVariantSet[];
96
+ variantSetPopoverTargets: Record<string, HTMLElement | null>;
97
+ /** Bumped when variant sidebar panel content updates (read via getters). */
98
+ variantSidebarRevision: number;
99
+ legacyVariantsAvailable: boolean;
100
+ legacyVariantsPopoverTarget: HTMLElement | null;
101
+ activeVariantSetId: string | null;
102
+ highlightTargetId: string | null;
103
+ tweaksOpen: boolean;
104
+ variantsOpen: boolean;
105
+ settingsOpen: boolean;
106
+ shareModeActive: boolean;
107
+ openSidebar: (panel?: PrototypeReviewSidebarPanel) => void;
108
+ closeSidebar: () => void;
109
+ toggleSidebar: () => void;
110
+ setActiveTab: (tab: PrototypeReviewTab) => void;
111
+ registerVariantSet: (set: {
112
+ id: string;
113
+ label: string;
114
+ onActivate?: () => void;
115
+ }) => void;
116
+ registerVariantSetPopover: (id: string, element: HTMLElement | null) => void;
117
+ registerVariantSidebarContent: (id: string, content: ReactNode | null) => void;
118
+ updateVariantSidebarContent: (id: string, content: ReactNode) => void;
119
+ getVariantSidebarContent: (id: string) => ReactNode | null;
120
+ registerLegacyVariantSidebarContent: (content: ReactNode | null) => void;
121
+ updateLegacyVariantSidebarContent: (content: ReactNode) => void;
122
+ getLegacyVariantSidebarContent: () => ReactNode | null;
123
+ unregisterVariantSet: (id: string) => void;
124
+ setLegacyVariantsAvailable: (available: boolean) => void;
125
+ registerLegacyVariantsPopover: (element: HTMLElement | null) => void;
126
+ focusTweak: (tweakId: string) => void;
127
+ clearHighlight: () => void;
128
+ openTweaks: () => void;
129
+ closeTweaks: () => void;
130
+ toggleTweaks: () => void;
131
+ openVariantSet: (id: string) => void;
132
+ closeVariants: () => void;
133
+ toggleVariantSet: (id: string) => void;
134
+ /** @deprecated Use toggleVariantSet instead. */
135
+ openVariants: () => void;
136
+ /** @deprecated Use closeVariants instead. */
137
+ toggleVariants: () => void;
138
+ openSettings: () => void;
139
+ closeSettings: () => void;
140
+ toggleSettings: () => void;
141
+ enterShareMode: () => void;
142
+ exitShareMode: () => void;
143
+ focusShareTarget: (targetId: string) => void;
144
+ viewportLayout: PrototypeViewportLayout;
145
+ setViewportLayout: (layout: PrototypeViewportLayout) => void;
146
+ toggleViewportLayout: () => void;
147
+ };
148
+
149
+ const PrototypeReviewContext = createContext<PrototypeReviewContextValue | null>(
150
+ null,
151
+ );
152
+
153
+ export function PrototypeReviewProvider({
154
+ slug,
155
+ children,
156
+ }: {
157
+ slug: string;
158
+ children: ReactNode;
159
+ }) {
160
+ const [open, setOpen] = useState(false);
161
+ const [sidebarPanel, setSidebarPanel] =
162
+ useState<PrototypeReviewSidebarPanel | null>(null);
163
+ const [activeTab, setActiveTabState] = useState<PrototypeReviewTab>("tweaks");
164
+ const tweaksContentRef = useRef<ReactNode | null>(null);
165
+ const [hasTweaksContent, setHasTweaksContent] = useState(false);
166
+ const specContentRef = useRef<ReactNode | null>(null);
167
+ const [hasSpecContent, setHasSpecContent] = useState(false);
168
+ const stateCanvasConfigRef = useRef<PrototypeStateCanvasConfig | null>(null);
169
+ const [hasStateCanvas, setHasStateCanvas] = useState(false);
170
+ const [stateCanvasPagePath, setStateCanvasPagePathState] = useState<
171
+ string | null
172
+ >(null);
173
+ const [variantSetMap, setVariantSetMap] = useState<
174
+ Record<string, PrototypeVariantSet>
175
+ >({});
176
+ const [variantSetPopoverTargets, setVariantSetPopoverTargets] = useState<
177
+ Record<string, HTMLElement | null>
178
+ >({});
179
+ const [legacyVariantsAvailable, setLegacyVariantsAvailable] = useState(false);
180
+ const [legacyVariantsPopoverTarget, setLegacyVariantsPopoverTarget] =
181
+ useState<HTMLElement | null>(null);
182
+ const variantSidebarContentRef = useRef<Record<string, ReactNode>>({});
183
+ const legacyVariantSidebarContentRef = useRef<ReactNode | null>(null);
184
+ const [registeredVariantSidebarIds, setRegisteredVariantSidebarIds] =
185
+ useState<Record<string, true>>({});
186
+ const [legacyVariantSidebarRegistered, setLegacyVariantSidebarRegistered] =
187
+ useState(false);
188
+ const [variantSidebarRevision, setVariantSidebarRevision] = useState(0);
189
+ const [variantSetOrder, setVariantSetOrder] = useState<string[]>([]);
190
+ const previewStatesRef = useRef<PrototypePreviewState[]>([]);
191
+ const [referenceDocs, setReferenceDocs] = useState<PrototypeReferenceDoc[]>(
192
+ [],
193
+ );
194
+ const [referenceDocsConfigFilePath, setReferenceDocsConfigFilePath] =
195
+ useState<string | undefined>(undefined);
196
+ const [activeVariantSetId, setActiveVariantSetId] = useState<string | null>(
197
+ null,
198
+ );
199
+ const [highlightTargetId, setHighlightTargetId] = useState<string | null>(
200
+ null,
201
+ );
202
+ const [tweaksOpen, setTweaksOpen] = useState(false);
203
+ const [variantsOpen, setVariantsOpen] = useState(false);
204
+ const [settingsOpen, setSettingsOpen] = useState(false);
205
+ const [shareModeActive, setShareModeActive] = useState(false);
206
+ const [captureChannel, setCaptureChannelState] =
207
+ useState<AnnotationChannel>("comment");
208
+ const [prodReferenceAvailable, setProdReferenceAvailable] = useState(false);
209
+ const [showProdReference, setShowProdReference] = useState(false);
210
+ const { value: viewportLayoutRaw, updateValue: persistViewportLayout } =
211
+ usePersistedLocalString(
212
+ prototypeReviewPreferenceKey(slug, "viewport-layout"),
213
+ "desktop",
214
+ );
215
+ const viewportLayout: PrototypeViewportLayout =
216
+ viewportLayoutRaw === "mobile" ? "mobile" : "desktop";
217
+ const closePanelTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
218
+ const pendingUrlVariantSetRef = useRef<string | null>(null);
219
+ const urlHydratingRef = useRef(false);
220
+ const urlSyncReadyRef = useRef(false);
221
+
222
+ const setCaptureChannel = useCallback((channel: AnnotationChannel) => {
223
+ setCaptureChannelState(channel);
224
+ }, []);
225
+
226
+ const registerProdReference = useCallback((available: boolean) => {
227
+ setProdReferenceAvailable(available);
228
+ if (!available) {
229
+ setShowProdReference(false);
230
+ }
231
+ }, []);
232
+
233
+ const setShowProdReferenceState = useCallback((show: boolean) => {
234
+ setShowProdReference(show);
235
+ }, []);
236
+
237
+ const toggleProdReference = useCallback(() => {
238
+ setShowProdReference((current) => !current);
239
+ }, []);
240
+
241
+ const variantSets = useMemo(
242
+ () => variantSetOrder.map((id) => variantSetMap[id]).filter(Boolean),
243
+ [variantSetMap, variantSetOrder],
244
+ );
245
+
246
+ const setTweaksContent = useCallback((content: ReactNode | null) => {
247
+ tweaksContentRef.current = content;
248
+ const nextHasContent = hasPrototypeTweaksContent(content);
249
+ setHasTweaksContent((current) =>
250
+ current === nextHasContent ? current : nextHasContent,
251
+ );
252
+ }, []);
253
+
254
+ const registerPreviewStates = useCallback((states: PrototypePreviewState[]) => {
255
+ previewStatesRef.current = states;
256
+ }, []);
257
+
258
+ const registerReferenceDocs = useCallback(
259
+ (docs: PrototypeReferenceDoc[], configFilePath?: string) => {
260
+ setReferenceDocs(docs);
261
+ setReferenceDocsConfigFilePath(
262
+ configFilePath ?? defaultReferenceDocsConfigPath(slug),
263
+ );
264
+ },
265
+ [slug],
266
+ );
267
+
268
+ const setSpecContent = useCallback((content: ReactNode | null) => {
269
+ specContentRef.current = content;
270
+ const nextHasContent = content != null;
271
+ setHasSpecContent((current) =>
272
+ current === nextHasContent ? current : nextHasContent,
273
+ );
274
+ }, []);
275
+
276
+ const setStateCanvasConfig = useCallback(
277
+ (config: PrototypeStateCanvasConfig | null) => {
278
+ stateCanvasConfigRef.current = config;
279
+ const nextHasCanvas = config != null;
280
+ setHasStateCanvas((current) =>
281
+ current === nextHasCanvas ? current : nextHasCanvas,
282
+ );
283
+ },
284
+ [],
285
+ );
286
+
287
+ const setStateCanvasPagePath = useCallback((path: string | null) => {
288
+ setStateCanvasPagePathState(path);
289
+ setHasStateCanvas((current) => {
290
+ const next = path != null || stateCanvasConfigRef.current != null;
291
+ return current === next ? current : next;
292
+ });
293
+ }, []);
294
+
295
+ const closeVariants = useCallback(() => {
296
+ setVariantsOpen(false);
297
+ setActiveVariantSetId(null);
298
+ setSidebarPanel((current) => {
299
+ if (current === "variants") {
300
+ setOpen(false);
301
+ // Keep sidebarPanel as "variants" during the 250ms close animation,
302
+ // then clear it so the sidebar doesn't flash comments content.
303
+ if (closePanelTimerRef.current) clearTimeout(closePanelTimerRef.current);
304
+ closePanelTimerRef.current = setTimeout(() => {
305
+ setSidebarPanel((c) => (c === "variants" ? null : c));
306
+ closePanelTimerRef.current = null;
307
+ }, 280);
308
+ return current;
309
+ }
310
+ return current;
311
+ });
312
+ }, []);
313
+
314
+ const openSidebar = useCallback(
315
+ (panel?: PrototypeReviewSidebarPanel) => {
316
+ const nextPanel = panel ?? "comments";
317
+ if (nextPanel === "comments" || nextPanel === "change-log") {
318
+ setVariantsOpen(false);
319
+ setActiveVariantSetId(null);
320
+ }
321
+ if (closePanelTimerRef.current) {
322
+ clearTimeout(closePanelTimerRef.current);
323
+ closePanelTimerRef.current = null;
324
+ }
325
+ setSidebarPanel(nextPanel);
326
+ setOpen(true);
327
+ if (nextPanel === "comments") {
328
+ setActiveTabState(tweaksContentRef.current ? "tweaks" : "comments");
329
+ }
330
+ },
331
+ [],
332
+ );
333
+
334
+ const closeSidebar = useCallback(() => {
335
+ if (closePanelTimerRef.current) {
336
+ clearTimeout(closePanelTimerRef.current);
337
+ closePanelTimerRef.current = null;
338
+ }
339
+ setOpen(false);
340
+ setSidebarPanel(null);
341
+ setVariantsOpen(false);
342
+ setActiveVariantSetId(null);
343
+ }, []);
344
+
345
+ const toggleSidebar = useCallback(() => {
346
+ setOpen((current) => {
347
+ if (current) {
348
+ setSidebarPanel(null);
349
+ setVariantsOpen(false);
350
+ setActiveVariantSetId(null);
351
+ return false;
352
+ }
353
+ setSidebarPanel("comments");
354
+ setActiveTabState(tweaksContentRef.current ? "tweaks" : "comments");
355
+ return true;
356
+ });
357
+ }, []);
358
+
359
+ const setActiveTab = useCallback((tab: PrototypeReviewTab) => {
360
+ setActiveTabState(tab);
361
+ }, []);
362
+
363
+ const clearHighlight = useCallback(() => {
364
+ setHighlightTargetId(null);
365
+ }, []);
366
+
367
+ const focusTweak = useCallback((tweakId: string) => {
368
+ const targetId = focusPrototypeTweak(tweakId);
369
+ if (targetId) {
370
+ setHighlightTargetId(targetId);
371
+ }
372
+ }, []);
373
+
374
+ const variantSetActivateHandlersRef = useRef<Record<string, (() => void) | undefined>>(
375
+ {},
376
+ );
377
+
378
+ const registerVariantSet = useCallback(
379
+ (set: { id: string; label: string; onActivate?: () => void }) => {
380
+ variantSetActivateHandlersRef.current[set.id] = set.onActivate;
381
+
382
+ setVariantSetMap((current) => {
383
+ const existing = current[set.id];
384
+ if (existing?.label === set.label) return current;
385
+ return {
386
+ ...current,
387
+ [set.id]: {
388
+ id: set.id,
389
+ label: set.label,
390
+ },
391
+ };
392
+ });
393
+ setVariantSetOrder((current) =>
394
+ current.includes(set.id) ? current : [...current, set.id],
395
+ );
396
+ },
397
+ [],
398
+ );
399
+
400
+ const registerVariantSetPopover = useCallback(
401
+ (id: string, element: HTMLElement | null) => {
402
+ setVariantSetPopoverTargets((current) => {
403
+ if (current[id] === element) return current;
404
+ return { ...current, [id]: element };
405
+ });
406
+ },
407
+ [],
408
+ );
409
+
410
+ const registerLegacyVariantsPopover = useCallback(
411
+ (element: HTMLElement | null) => {
412
+ setLegacyVariantsPopoverTarget((current) =>
413
+ current === element ? current : element,
414
+ );
415
+ },
416
+ [],
417
+ );
418
+
419
+ const registerVariantSidebarContent = useCallback(
420
+ (id: string, content: ReactNode | null) => {
421
+ if (content == null) {
422
+ if (!(id in variantSidebarContentRef.current)) return;
423
+ delete variantSidebarContentRef.current[id];
424
+ setRegisteredVariantSidebarIds((current) => {
425
+ if (!(id in current)) return current;
426
+ const next = { ...current };
427
+ delete next[id];
428
+ return next;
429
+ });
430
+ setVariantSidebarRevision((revision) => revision + 1);
431
+ return;
432
+ }
433
+
434
+ variantSidebarContentRef.current[id] = content;
435
+ setRegisteredVariantSidebarIds((current) => {
436
+ if (id in current) return current;
437
+ return { ...current, [id]: true };
438
+ });
439
+ },
440
+ [],
441
+ );
442
+
443
+ const updateVariantSidebarContent = useCallback(
444
+ (id: string, content: ReactNode) => {
445
+ if (!(id in variantSidebarContentRef.current)) return;
446
+ variantSidebarContentRef.current[id] = content;
447
+ setVariantSidebarRevision((revision) => revision + 1);
448
+ },
449
+ [],
450
+ );
451
+
452
+ const getVariantSidebarContent = useCallback((id: string) => {
453
+ return variantSidebarContentRef.current[id] ?? null;
454
+ }, []);
455
+
456
+ const registerLegacyVariantSidebarContent = useCallback(
457
+ (content: ReactNode | null) => {
458
+ if (content == null) {
459
+ legacyVariantSidebarContentRef.current = null;
460
+ setLegacyVariantSidebarRegistered((current) =>
461
+ current ? false : current,
462
+ );
463
+ setVariantSidebarRevision((revision) => revision + 1);
464
+ return;
465
+ }
466
+
467
+ legacyVariantSidebarContentRef.current = content;
468
+ setLegacyVariantSidebarRegistered((current) => (current ? current : true));
469
+ },
470
+ [],
471
+ );
472
+
473
+ const updateLegacyVariantSidebarContent = useCallback((content: ReactNode) => {
474
+ if (legacyVariantSidebarContentRef.current == null) return;
475
+ legacyVariantSidebarContentRef.current = content;
476
+ setVariantSidebarRevision((revision) => revision + 1);
477
+ }, []);
478
+
479
+ const getLegacyVariantSidebarContent = useCallback(
480
+ () => legacyVariantSidebarContentRef.current,
481
+ [],
482
+ );
483
+
484
+ const unregisterVariantSet = useCallback((id: string) => {
485
+ delete variantSetActivateHandlersRef.current[id];
486
+
487
+ setVariantSetMap((current) => {
488
+ if (!(id in current)) return current;
489
+ const next = { ...current };
490
+ delete next[id];
491
+ return next;
492
+ });
493
+ setVariantSetOrder((current) => current.filter((entry) => entry !== id));
494
+ setActiveVariantSetId((current) => (current === id ? null : current));
495
+ setVariantSetPopoverTargets((current) => {
496
+ if (!(id in current)) return current;
497
+ const next = { ...current };
498
+ delete next[id];
499
+ return next;
500
+ });
501
+ delete variantSidebarContentRef.current[id];
502
+ setRegisteredVariantSidebarIds((current) => {
503
+ if (!(id in current)) return current;
504
+ const next = { ...current };
505
+ delete next[id];
506
+ return next;
507
+ });
508
+ }, []);
509
+
510
+ const openVariantSet = useCallback((id: string) => {
511
+ variantSetActivateHandlersRef.current[id]?.();
512
+ setSettingsOpen(false);
513
+ setTweaksOpen(false);
514
+ if (closePanelTimerRef.current) {
515
+ clearTimeout(closePanelTimerRef.current);
516
+ closePanelTimerRef.current = null;
517
+ }
518
+ setActiveVariantSetId(id);
519
+ setVariantsOpen(true);
520
+ setSidebarPanel("variants");
521
+ setOpen(true);
522
+ }, []);
523
+
524
+ const toggleVariantSet = useCallback(
525
+ (id: string) => {
526
+ if (variantsOpen && activeVariantSetId === id) {
527
+ closeVariants();
528
+ return;
529
+ }
530
+ openVariantSet(id);
531
+ },
532
+ [activeVariantSetId, closeVariants, openVariantSet, variantsOpen],
533
+ );
534
+
535
+ const openVariants = useCallback(() => {
536
+ setSettingsOpen(false);
537
+ setTweaksOpen(false);
538
+ if (closePanelTimerRef.current) {
539
+ clearTimeout(closePanelTimerRef.current);
540
+ closePanelTimerRef.current = null;
541
+ }
542
+ setVariantsOpen(true);
543
+ setSidebarPanel("variants");
544
+ setOpen(true);
545
+ }, []);
546
+
547
+ const toggleVariants = useCallback(() => {
548
+ openVariants();
549
+ }, [openVariants]);
550
+
551
+ const openTweaks = useCallback(() => {
552
+ setSettingsOpen(false);
553
+ setTweaksOpen(true);
554
+ }, []);
555
+ const closeTweaks = useCallback(() => setTweaksOpen(false), []);
556
+ const toggleTweaks = useCallback(() => {
557
+ setTweaksOpen((current) => {
558
+ const next = !current;
559
+ if (next) {
560
+ setSettingsOpen(false);
561
+ }
562
+ return next;
563
+ });
564
+ }, []);
565
+
566
+ const openSettings = useCallback(() => {
567
+ setTweaksOpen(false);
568
+ setSettingsOpen(true);
569
+ }, []);
570
+ const closeSettings = useCallback(() => setSettingsOpen(false), []);
571
+ const toggleSettings = useCallback(() => {
572
+ setSettingsOpen((current) => {
573
+ const next = !current;
574
+ if (next) {
575
+ setTweaksOpen(false);
576
+ }
577
+ return next;
578
+ });
579
+ }, []);
580
+
581
+ const enterShareMode = useCallback(() => {
582
+ setSettingsOpen(false);
583
+ setTweaksOpen(false);
584
+ setShareModeActive(true);
585
+ }, []);
586
+
587
+ const exitShareMode = useCallback(() => {
588
+ setShareModeActive(false);
589
+ }, []);
590
+
591
+ const focusShareTarget = useCallback((targetId: string) => {
592
+ scrollPrototypeTargetIntoView(targetId);
593
+ setHighlightTargetId(targetId);
594
+ }, []);
595
+
596
+ const setViewportLayout = useCallback(
597
+ (layout: PrototypeViewportLayout) => {
598
+ persistViewportLayout(layout);
599
+ },
600
+ [persistViewportLayout],
601
+ );
602
+
603
+ const toggleViewportLayout = useCallback(() => {
604
+ persistViewportLayout(viewportLayout === "desktop" ? "mobile" : "desktop");
605
+ }, [persistViewportLayout, viewportLayout]);
606
+
607
+ const applyReviewStateFromUrl = useCallback(
608
+ (parsed: ReturnType<typeof parseReviewStateFromSearchParams>) => {
609
+ setShowProdReference(parsed.prodReference);
610
+
611
+ if (parsed.settingsOpen) {
612
+ setSettingsOpen(true);
613
+ setTweaksOpen(false);
614
+ } else if (parsed.tweaksOpen) {
615
+ setTweaksOpen(true);
616
+ setSettingsOpen(false);
617
+ } else {
618
+ setTweaksOpen(false);
619
+ setSettingsOpen(false);
620
+ }
621
+
622
+ if (!parsed.panel) {
623
+ if (closePanelTimerRef.current) {
624
+ clearTimeout(closePanelTimerRef.current);
625
+ closePanelTimerRef.current = null;
626
+ }
627
+ setOpen(false);
628
+ setSidebarPanel(null);
629
+ setVariantsOpen(false);
630
+ setActiveVariantSetId(null);
631
+ return;
632
+ }
633
+
634
+ if (closePanelTimerRef.current) {
635
+ clearTimeout(closePanelTimerRef.current);
636
+ closePanelTimerRef.current = null;
637
+ }
638
+
639
+ if (parsed.panel === "variants") {
640
+ setTweaksOpen(false);
641
+ setSettingsOpen(false);
642
+ setVariantsOpen(true);
643
+ setSidebarPanel("variants");
644
+ setOpen(true);
645
+
646
+ if (parsed.variantSetId) {
647
+ pendingUrlVariantSetRef.current = parsed.variantSetId;
648
+ setActiveVariantSetId(parsed.variantSetId);
649
+ } else {
650
+ pendingUrlVariantSetRef.current = null;
651
+ setActiveVariantSetId(null);
652
+ }
653
+ return;
654
+ }
655
+
656
+ pendingUrlVariantSetRef.current = null;
657
+ setVariantsOpen(false);
658
+ setActiveVariantSetId(null);
659
+ setSidebarPanel(parsed.panel);
660
+ setOpen(true);
661
+
662
+ if (parsed.panel === "comments") {
663
+ setActiveTabState(
664
+ parsed.activeTab === "comments" && !tweaksContentRef.current
665
+ ? "comments"
666
+ : parsed.activeTab,
667
+ );
668
+ }
669
+ },
670
+ [],
671
+ );
672
+
673
+ const hydrateReviewStateFromUrl = useCallback(() => {
674
+ if (typeof window === "undefined") return;
675
+
676
+ const searchParams = new URLSearchParams(window.location.search);
677
+ if (!hasReviewUrlParams(searchParams)) {
678
+ urlSyncReadyRef.current = true;
679
+ return;
680
+ }
681
+
682
+ urlHydratingRef.current = true;
683
+ applyReviewStateFromUrl(parseReviewStateFromSearchParams(searchParams));
684
+ urlHydratingRef.current = false;
685
+ urlSyncReadyRef.current = true;
686
+ }, [applyReviewStateFromUrl]);
687
+
688
+ useEffect(() => {
689
+ hydrateReviewStateFromUrl();
690
+ }, [hydrateReviewStateFromUrl]);
691
+
692
+ useEffect(() => {
693
+ const onPopState = () => {
694
+ hydrateReviewStateFromUrl();
695
+ };
696
+ window.addEventListener("popstate", onPopState);
697
+ return () => window.removeEventListener("popstate", onPopState);
698
+ }, [hydrateReviewStateFromUrl]);
699
+
700
+ useLayoutEffect(() => {
701
+ const pendingId = pendingUrlVariantSetRef.current;
702
+ if (!pendingId) return;
703
+
704
+ const handler = variantSetActivateHandlersRef.current[pendingId];
705
+ if (!handler) return;
706
+
707
+ handler();
708
+ pendingUrlVariantSetRef.current = null;
709
+ }, [variantSetOrder, variantSets]);
710
+
711
+ useEffect(() => {
712
+ if (!urlSyncReadyRef.current || urlHydratingRef.current) return;
713
+
714
+ syncReviewStateToUrl({
715
+ open,
716
+ sidebarPanel,
717
+ activeVariantSetId,
718
+ activeTab,
719
+ tweaksOpen,
720
+ settingsOpen,
721
+ prodReferenceAvailable,
722
+ showProdReference,
723
+ });
724
+ }, [
725
+ open,
726
+ sidebarPanel,
727
+ activeVariantSetId,
728
+ activeTab,
729
+ tweaksOpen,
730
+ settingsOpen,
731
+ prodReferenceAvailable,
732
+ showProdReference,
733
+ ]);
734
+
735
+ const value = useMemo(
736
+ () => ({
737
+ slug,
738
+ open,
739
+ sidebarPanel,
740
+ activeTab,
741
+ captureChannel,
742
+ setCaptureChannel,
743
+ prodReferenceAvailable,
744
+ showProdReference,
745
+ setShowProdReference: setShowProdReferenceState,
746
+ toggleProdReference,
747
+ registerProdReference,
748
+ tweaksContentRef,
749
+ hasTweaksContent,
750
+ setTweaksContent,
751
+ specContentRef,
752
+ hasSpecContent,
753
+ setSpecContent,
754
+ stateCanvasConfigRef,
755
+ hasStateCanvas,
756
+ setStateCanvasConfig,
757
+ stateCanvasPagePath,
758
+ setStateCanvasPagePath,
759
+ previewStatesRef,
760
+ registerPreviewStates,
761
+ referenceDocs,
762
+ referenceDocsConfigFilePath,
763
+ registerReferenceDocs,
764
+ variantSets,
765
+ variantSetPopoverTargets,
766
+ variantSidebarRevision,
767
+ legacyVariantsAvailable,
768
+ legacyVariantsPopoverTarget,
769
+ activeVariantSetId,
770
+ highlightTargetId,
771
+ tweaksOpen,
772
+ variantsOpen,
773
+ settingsOpen,
774
+ shareModeActive,
775
+ openSidebar,
776
+ closeSidebar,
777
+ toggleSidebar,
778
+ setActiveTab,
779
+ registerVariantSet,
780
+ registerVariantSetPopover,
781
+ registerVariantSidebarContent,
782
+ updateVariantSidebarContent,
783
+ getVariantSidebarContent,
784
+ registerLegacyVariantSidebarContent,
785
+ updateLegacyVariantSidebarContent,
786
+ getLegacyVariantSidebarContent,
787
+ unregisterVariantSet,
788
+ setLegacyVariantsAvailable,
789
+ registerLegacyVariantsPopover,
790
+ focusTweak,
791
+ clearHighlight,
792
+ openTweaks,
793
+ closeTweaks,
794
+ toggleTweaks,
795
+ openVariantSet,
796
+ closeVariants,
797
+ toggleVariantSet,
798
+ openVariants,
799
+ toggleVariants,
800
+ openSettings,
801
+ closeSettings,
802
+ toggleSettings,
803
+ enterShareMode,
804
+ exitShareMode,
805
+ focusShareTarget,
806
+ viewportLayout,
807
+ setViewportLayout,
808
+ toggleViewportLayout,
809
+ }),
810
+ [
811
+ slug,
812
+ open,
813
+ sidebarPanel,
814
+ activeTab,
815
+ captureChannel,
816
+ setCaptureChannel,
817
+ prodReferenceAvailable,
818
+ showProdReference,
819
+ setShowProdReferenceState,
820
+ toggleProdReference,
821
+ registerProdReference,
822
+ tweaksContentRef,
823
+ hasTweaksContent,
824
+ setTweaksContent,
825
+ specContentRef,
826
+ hasSpecContent,
827
+ setSpecContent,
828
+ stateCanvasConfigRef,
829
+ hasStateCanvas,
830
+ setStateCanvasConfig,
831
+ stateCanvasPagePath,
832
+ setStateCanvasPagePath,
833
+ previewStatesRef,
834
+ registerPreviewStates,
835
+ referenceDocs,
836
+ referenceDocsConfigFilePath,
837
+ registerReferenceDocs,
838
+ variantSets,
839
+ variantSetPopoverTargets,
840
+ variantSidebarRevision,
841
+ legacyVariantsAvailable,
842
+ legacyVariantsPopoverTarget,
843
+ activeVariantSetId,
844
+ highlightTargetId,
845
+ tweaksOpen,
846
+ variantsOpen,
847
+ settingsOpen,
848
+ shareModeActive,
849
+ openSidebar,
850
+ closeSidebar,
851
+ toggleSidebar,
852
+ setActiveTab,
853
+ focusTweak,
854
+ clearHighlight,
855
+ registerVariantSet,
856
+ registerVariantSetPopover,
857
+ registerVariantSidebarContent,
858
+ updateVariantSidebarContent,
859
+ getVariantSidebarContent,
860
+ registerLegacyVariantSidebarContent,
861
+ updateLegacyVariantSidebarContent,
862
+ getLegacyVariantSidebarContent,
863
+ unregisterVariantSet,
864
+ setLegacyVariantsAvailable,
865
+ registerLegacyVariantsPopover,
866
+ openTweaks,
867
+ closeTweaks,
868
+ toggleTweaks,
869
+ openVariantSet,
870
+ closeVariants,
871
+ toggleVariantSet,
872
+ openVariants,
873
+ toggleVariants,
874
+ openSettings,
875
+ closeSettings,
876
+ toggleSettings,
877
+ enterShareMode,
878
+ exitShareMode,
879
+ focusShareTarget,
880
+ viewportLayout,
881
+ setViewportLayout,
882
+ toggleViewportLayout,
883
+ ],
884
+ );
885
+
886
+ return (
887
+ <PrototypeReviewContext.Provider value={value}>
888
+ {children}
889
+ </PrototypeReviewContext.Provider>
890
+ );
891
+ }
892
+
893
+ export function usePrototypeReview(): PrototypeReviewContextValue {
894
+ const context = useContext(PrototypeReviewContext);
895
+ if (!context) {
896
+ throw new Error(
897
+ "usePrototypeReview must be used within PrototypeReviewProvider",
898
+ );
899
+ }
900
+ return context;
901
+ }
902
+
903
+ export function usePrototypeReviewOptional(): PrototypeReviewContextValue | null {
904
+ return useContext(PrototypeReviewContext);
905
+ }