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,525 @@
1
+ "use client";
2
+
3
+ import { Button } from "@prototype/components/ui/button";
4
+ import { PrototypeComponent } from "@prototype/components/prototypes/prototype-target";
5
+ import { PrototypeDesignContextPanel } from "@prototype/components/prototypes/prototype-design-context-panel";
6
+ import { PrototypeMobbinGallery } from "@prototype/components/prototypes/prototype-mobbin-gallery";
7
+ import { PrototypeVariantRationalePanel } from "@prototype/components/prototypes/prototype-variant-rationale-panel";
8
+ import {
9
+ buildResetBriefDefaultsPrompt,
10
+ type DesignExplorationBrief,
11
+ type DesignExplorationContext,
12
+ type DesignExplorationMobbin,
13
+ type DesignExplorationRationale,
14
+ type DesignExplorationVariantOption,
15
+ type DesignExplorationVariantsSection,
16
+ type MobbinReference,
17
+ } from "@prototype/lib/prototypes/design-exploration-types";
18
+ import {
19
+ usePersistedLocalBoolean,
20
+ usePersistedLocalString,
21
+ } from "@prototype/lib/prototypes/use-persisted-local-state";
22
+ import useCopyToClipboard from "@prototype/lib/use-copy-to-clipboard";
23
+ import { cn } from "@prototype/lib/utils";
24
+ import { useEffect, useState } from "react";
25
+
26
+ function scrollToMobbinReference(
27
+ componentIdPrefix: string,
28
+ referenceId: string,
29
+ ) {
30
+ const el = document.querySelector(
31
+ `[data-prototype-target*="${componentIdPrefix}.mobbin-reference.${referenceId}"]`,
32
+ );
33
+ if (el) {
34
+ el.scrollIntoView({ behavior: "smooth", block: "nearest" });
35
+ }
36
+ }
37
+
38
+ function VariantHint({
39
+ hint,
40
+ references,
41
+ componentIdPrefix,
42
+ }: {
43
+ hint: string;
44
+ references: MobbinReference[];
45
+ componentIdPrefix: string;
46
+ }) {
47
+ const parts = hint.split(/,\s*/);
48
+
49
+ return (
50
+ <>
51
+ {parts.map((part, i) => {
52
+ const trimmed = part.trim();
53
+ const matchedRef = references.find(
54
+ (r) => r.appName.toLowerCase() === trimmed.toLowerCase(),
55
+ );
56
+ const isLast = i === parts.length - 1;
57
+
58
+ return (
59
+ <span key={i}>
60
+ {matchedRef ? (
61
+ <button
62
+ type="button"
63
+ onClick={() =>
64
+ scrollToMobbinReference(componentIdPrefix, matchedRef.id)
65
+ }
66
+ className="cursor-pointer underline decoration-dotted underline-offset-2 transition-colors duration-200 ease hover:text-foreground"
67
+ >
68
+ {trimmed}
69
+ </button>
70
+ ) : (
71
+ trimmed
72
+ )}
73
+ {!isLast ? ", " : ""}
74
+ </span>
75
+ );
76
+ })}
77
+ </>
78
+ );
79
+ }
80
+
81
+ const MODAL_VARIANT_PREVIEW_SCALE = 0.75;
82
+ const SIDEBAR_VARIANT_PREVIEW_SCALE = 0.42;
83
+
84
+ const MODAL_OVERVIEW_TITLE_FIELD_CLASS =
85
+ "tool-heading field-sizing-content w-full min-h-0 resize-none border-0 bg-transparent p-0 shadow-none outline-none focus-visible:ring-0 focus-visible:ring-offset-0";
86
+
87
+ const MODAL_OVERVIEW_DESCRIPTION_FIELD_CLASS =
88
+ "tool-body field-sizing-content w-full min-h-0 resize-none border-0 bg-transparent p-0 shadow-none outline-none focus-visible:ring-0 focus-visible:ring-offset-0";
89
+
90
+ const MODAL_GALLERY_SECTION_TITLE_CLASS = "tool-heading";
91
+
92
+ const MODAL_GALLERY_SECTION_DESC_CLASS = "tool-body max-w-2xl";
93
+
94
+ const MODAL_GALLERY_CARD_TITLE_CLASS = "text-foreground font-medium";
95
+
96
+ const MODAL_GALLERY_CARD_BODY_CLASS = "text-sm text-muted-foreground leading-relaxed";
97
+
98
+ const SIDEBAR_OVERVIEW_TITLE_FIELD_CLASS =
99
+ "text-sm text-foreground field-sizing-content w-full min-h-0 resize-none border-0 bg-transparent p-0 font-medium leading-snug shadow-none outline-none focus-visible:ring-0 focus-visible:ring-offset-0";
100
+
101
+ const SIDEBAR_OVERVIEW_DESCRIPTION_FIELD_CLASS =
102
+ "text-sm text-muted-foreground field-sizing-content w-full min-h-0 resize-none border-0 bg-transparent p-0 leading-relaxed shadow-none outline-none focus-visible:ring-0 focus-visible:ring-offset-0";
103
+
104
+ const SIDEBAR_GALLERY_SECTION_TITLE_CLASS = "tool-section-label";
105
+
106
+ const SIDEBAR_GALLERY_SECTION_DESC_CLASS = "tool-body";
107
+
108
+ const SIDEBAR_GALLERY_CARD_TITLE_CLASS = "text-sm text-foreground font-medium";
109
+
110
+ const SIDEBAR_GALLERY_CARD_BODY_CLASS = "text-sm text-muted-foreground leading-relaxed";
111
+
112
+ export type PrototypeDesignBriefPanelLayout = "default" | "sidebar";
113
+
114
+ export type PrototypeDesignBriefPanelProps<TVariant extends string> = {
115
+ componentIdPrefix: string;
116
+ galleryId: string;
117
+ storageKeyPrefix: string;
118
+ brief: DesignExplorationBrief;
119
+ context?: DesignExplorationContext;
120
+ mobbin?: DesignExplorationMobbin;
121
+ variantsSection?: DesignExplorationVariantsSection;
122
+ variant: TVariant;
123
+ onVariantChange: (variant: TVariant) => void;
124
+ options: DesignExplorationVariantOption<TVariant>[];
125
+ rationale: Record<TVariant, DesignExplorationRationale>;
126
+ renderers: Record<TVariant, () => React.ReactNode>;
127
+ /** Shown with a "Default" pill when this option matches the KV default. */
128
+ defaultVariantValue?: TVariant;
129
+ /** Persists the selected default variant to KV storage. */
130
+ onSetDefaultVariant?: (value: TVariant) => void;
131
+ /** Path to design exploration config — included in overview copy prompt. */
132
+ briefConfigFilePath?: string;
133
+ layout?: PrototypeDesignBriefPanelLayout;
134
+ className?: string;
135
+ };
136
+
137
+ const VARIANT_DEFAULT_CONTROL_CLASS =
138
+ "h-6 shrink-0 border border-border-solid bg-bg-top px-2 text-[11px] font-medium text-foreground shadow-xs hover:border-border-medium hover:bg-bg-layered";
139
+
140
+ const VARIANT_DEFAULT_ACTIVE_CLASS =
141
+ "border-[color-mix(in_srgb,var(--primary)_35%,var(--border-solid))] bg-[var(--tool-chrome-blue-highlight)] text-primary-dark hover:border-[color-mix(in_srgb,var(--primary)_35%,var(--border-solid))] hover:bg-[var(--tool-chrome-blue-highlight)]";
142
+
143
+ const SIDEBAR_OVERVIEW_COPY_BUTTON_CLASS =
144
+ "text-[var(--tool-chrome-icon)] hover:text-[var(--tool-chrome-icon-hover)] inline-flex size-7 shrink-0 items-center justify-center rounded-sm border-0 bg-transparent p-0 transition-colors duration-200 ease outline-none focus:outline-hidden focus-visible:ring-2 focus-visible:ring-focus-ring focus-visible:ring-offset-0 disabled:pointer-events-none disabled:opacity-40 motion-reduce:transition-none [&_svg]:pointer-events-none [&_svg]:shrink-0";
145
+
146
+ function VariantRationaleSection<TVariant extends string>({
147
+ componentIdPrefix,
148
+ optionValue,
149
+ storageKeyPrefix,
150
+ rationale,
151
+ layout,
152
+ bodyClassName,
153
+ }: {
154
+ componentIdPrefix: string;
155
+ optionValue: TVariant;
156
+ storageKeyPrefix: string;
157
+ rationale: DesignExplorationRationale;
158
+ layout: PrototypeDesignBriefPanelLayout;
159
+ bodyClassName: string;
160
+ }) {
161
+ const { value: expanded, updateValue: updateExpanded } =
162
+ usePersistedLocalBoolean(
163
+ `${storageKeyPrefix}-variant-rationale-expanded-${optionValue}`,
164
+ false,
165
+ );
166
+
167
+ return (
168
+ <PrototypeVariantRationalePanel
169
+ componentId={`${componentIdPrefix}.overview-option.${optionValue}.rationale`}
170
+ rationale={rationale}
171
+ expanded={expanded}
172
+ onExpandedChange={updateExpanded}
173
+ layout={layout}
174
+ bodyClassName={bodyClassName}
175
+ />
176
+ );
177
+ }
178
+
179
+ export function PrototypeDesignBriefPanel<TVariant extends string>({
180
+ componentIdPrefix,
181
+ galleryId,
182
+ storageKeyPrefix,
183
+ brief,
184
+ context,
185
+ mobbin,
186
+ variantsSection,
187
+ variant,
188
+ onVariantChange,
189
+ options,
190
+ rationale,
191
+ renderers,
192
+ defaultVariantValue,
193
+ onSetDefaultVariant,
194
+ briefConfigFilePath,
195
+ layout = "default",
196
+ className,
197
+ }: PrototypeDesignBriefPanelProps<TVariant>) {
198
+ const isSidebar = layout === "sidebar";
199
+ const [variantPreviewsReady, setVariantPreviewsReady] = useState(false);
200
+
201
+ useEffect(() => {
202
+ setVariantPreviewsReady(true);
203
+ }, []);
204
+
205
+ const { copy: copyOverviewDefaultsPrompt, icon: copyOverviewDefaultsIcon } =
206
+ useCopyToClipboard();
207
+ const { value: title, updateValue: updateTitle } = usePersistedLocalString(
208
+ `${storageKeyPrefix}-overview-title`,
209
+ brief.titleDefault,
210
+ );
211
+ const { value: description, updateValue: updateDescription } =
212
+ usePersistedLocalString(
213
+ `${storageKeyPrefix}-overview-description`,
214
+ brief.descriptionDefault,
215
+ );
216
+ const {
217
+ value: contextExpanded,
218
+ updateValue: updateContextExpanded,
219
+ } = usePersistedLocalBoolean(
220
+ `${storageKeyPrefix}-context-expanded`,
221
+ context?.defaultExpanded ?? false,
222
+ );
223
+
224
+ const variantsTitle = variantsSection?.title ?? "Design variants";
225
+ const variantsDescription =
226
+ variantsSection?.description ??
227
+ "Compare layout, copy, and CTAs side by side.";
228
+
229
+ const effectiveTitle = title.trim() || brief.titleDefault;
230
+ const effectiveDescription =
231
+ description.trim() || brief.descriptionDefault;
232
+ const overviewMatchesDefaults =
233
+ effectiveTitle === brief.titleDefault &&
234
+ effectiveDescription === brief.descriptionDefault;
235
+
236
+ const overviewTitleClass = isSidebar
237
+ ? SIDEBAR_OVERVIEW_TITLE_FIELD_CLASS
238
+ : MODAL_OVERVIEW_TITLE_FIELD_CLASS;
239
+ const overviewDescriptionClass = isSidebar
240
+ ? SIDEBAR_OVERVIEW_DESCRIPTION_FIELD_CLASS
241
+ : MODAL_OVERVIEW_DESCRIPTION_FIELD_CLASS;
242
+ const sectionTitleClass = isSidebar
243
+ ? SIDEBAR_GALLERY_SECTION_TITLE_CLASS
244
+ : MODAL_GALLERY_SECTION_TITLE_CLASS;
245
+ const sectionDescClass = isSidebar
246
+ ? SIDEBAR_GALLERY_SECTION_DESC_CLASS
247
+ : MODAL_GALLERY_SECTION_DESC_CLASS;
248
+ const cardTitleClass = isSidebar
249
+ ? SIDEBAR_GALLERY_CARD_TITLE_CLASS
250
+ : MODAL_GALLERY_CARD_TITLE_CLASS;
251
+ const cardBodyClass = isSidebar
252
+ ? SIDEBAR_GALLERY_CARD_BODY_CLASS
253
+ : MODAL_GALLERY_CARD_BODY_CLASS;
254
+ const variantPreviewScale = isSidebar
255
+ ? SIDEBAR_VARIANT_PREVIEW_SCALE
256
+ : MODAL_VARIANT_PREVIEW_SCALE;
257
+
258
+ const overviewFields = (
259
+ <div
260
+ className={cn(
261
+ "text-left",
262
+ isSidebar ? "flex flex-col gap-2" : undefined,
263
+ )}
264
+ >
265
+ <PrototypeComponent id={`${componentIdPrefix}.overview-title`}>
266
+ <input
267
+ value={title}
268
+ onChange={(event) => updateTitle(event.target.value)}
269
+ aria-label="Overview title"
270
+ className={overviewTitleClass}
271
+ />
272
+ </PrototypeComponent>
273
+ <PrototypeComponent id={`${componentIdPrefix}.overview-description`}>
274
+ <textarea
275
+ value={description}
276
+ onChange={(event) => updateDescription(event.target.value)}
277
+ rows={isSidebar ? 3 : 1}
278
+ aria-label="Overview description"
279
+ className={cn(overviewDescriptionClass, !isSidebar && "mt-2")}
280
+ />
281
+ </PrototypeComponent>
282
+ </div>
283
+ );
284
+
285
+ const contextSection = context ? (
286
+ <PrototypeDesignContextPanel
287
+ componentId={`${componentIdPrefix}.user-stats`}
288
+ context={context}
289
+ expanded={contextExpanded}
290
+ onExpandedChange={updateContextExpanded}
291
+ layout={layout}
292
+ />
293
+ ) : null;
294
+
295
+ const overviewHeader = (
296
+ <div
297
+ className={cn(
298
+ "text-left",
299
+ isSidebar ? "flex flex-col gap-2 pt-1" : "px-4 py-4 sm:px-5 sm:py-5",
300
+ )}
301
+ >
302
+ {overviewFields}
303
+ {!isSidebar && contextSection ? (
304
+ <div className="mt-3">{contextSection}</div>
305
+ ) : null}
306
+ </div>
307
+ );
308
+
309
+ const mobbinSection = mobbin ? (
310
+ <PrototypeMobbinGallery
311
+ componentIdPrefix={componentIdPrefix}
312
+ galleryId={galleryId}
313
+ references={mobbin.references}
314
+ title={mobbin.title}
315
+ description={mobbin.description}
316
+ imagePathForReference={mobbin.imagePathForReference}
317
+ layout={layout}
318
+ />
319
+ ) : null;
320
+
321
+ const variantsSectionContent = (
322
+ <section
323
+ className={cn(
324
+ isSidebar
325
+ ? "flex flex-col gap-3"
326
+ : "border-border border-t px-4 py-6 sm:px-5 sm:py-8",
327
+ )}
328
+ >
329
+ <div
330
+ className={cn(
331
+ "flex flex-col gap-2",
332
+ !isSidebar && "mb-6",
333
+ )}
334
+ >
335
+ <p className={sectionTitleClass}>{variantsTitle}</p>
336
+ {!isSidebar && <p className={sectionDescClass}>{variantsDescription}</p>}
337
+ </div>
338
+
339
+ <div className={cn("flex flex-col", isSidebar ? "gap-6" : "gap-10")}>
340
+ {options.map((option) => {
341
+ const optionRationale = rationale[option.value];
342
+ const VariantPreview = renderers[option.value];
343
+ const isActive = option.value === variant;
344
+ const isDefaultOption =
345
+ defaultVariantValue !== undefined &&
346
+ option.value === defaultVariantValue;
347
+
348
+ return (
349
+ <PrototypeComponent
350
+ key={option.value}
351
+ id={`${componentIdPrefix}.overview-option.${option.value}`}
352
+ >
353
+ <div className="flex flex-col gap-2">
354
+ <div className="flex items-start justify-between gap-2 px-0.5">
355
+ <p className={cn(cardBodyClass, "min-w-0 flex-1")}>
356
+ <span className={cardTitleClass}>{option.label}</span>
357
+ {option.hint ? (
358
+ <>
359
+ {" · "}
360
+ <VariantHint
361
+ hint={option.hint}
362
+ references={mobbin?.references ?? []}
363
+ componentIdPrefix={componentIdPrefix}
364
+ />
365
+ </>
366
+ ) : null}
367
+ </p>
368
+
369
+ <div className="flex shrink-0 items-center gap-3">
370
+ {isDefaultOption ? (
371
+ <PrototypeComponent
372
+ id={`${componentIdPrefix}.overview-option.${option.value}.default`}
373
+ >
374
+ <Button
375
+ type="button"
376
+ variant="secondary"
377
+ size="sm"
378
+ className={cn(
379
+ VARIANT_DEFAULT_CONTROL_CLASS,
380
+ VARIANT_DEFAULT_ACTIVE_CLASS,
381
+ "pointer-events-none disabled:opacity-100",
382
+ )}
383
+ disabled
384
+ aria-label="Current default variant"
385
+ >
386
+ Default
387
+ </Button>
388
+ </PrototypeComponent>
389
+ ) : onSetDefaultVariant ? (
390
+ <PrototypeComponent
391
+ id={`${componentIdPrefix}.overview-option.${option.value}.set-default`}
392
+ >
393
+ <Button
394
+ type="button"
395
+ variant="secondary"
396
+ size="sm"
397
+ className={VARIANT_DEFAULT_CONTROL_CLASS}
398
+ onClick={(event) => {
399
+ event.stopPropagation();
400
+ onSetDefaultVariant(option.value);
401
+ }}
402
+ aria-label={`Set ${option.label} as default`}
403
+ >
404
+ Make default
405
+ </Button>
406
+ </PrototypeComponent>
407
+ ) : null}
408
+ </div>
409
+ </div>
410
+
411
+ <div
412
+ role="button"
413
+ tabIndex={0}
414
+ aria-pressed={isActive}
415
+ aria-label={`Select ${option.label} variant`}
416
+ onClick={() => onVariantChange(option.value)}
417
+ onKeyDown={(event) => {
418
+ if (event.key === "Enter" || event.key === " ") {
419
+ event.preventDefault();
420
+ onVariantChange(option.value);
421
+ }
422
+ }}
423
+ className={cn(
424
+ "tool-variant-card",
425
+ isActive && "is-active",
426
+ )}
427
+ >
428
+ <div
429
+ style={{ zoom: variantPreviewScale }}
430
+ className="w-full"
431
+ data-prototype-screenshot
432
+ >
433
+ {variantPreviewsReady ? (
434
+ VariantPreview()
435
+ ) : (
436
+ <div
437
+ className="bg-bg-subtle min-h-32 w-full"
438
+ aria-hidden
439
+ />
440
+ )}
441
+ </div>
442
+ </div>
443
+
444
+ <VariantRationaleSection
445
+ componentIdPrefix={componentIdPrefix}
446
+ optionValue={option.value}
447
+ storageKeyPrefix={storageKeyPrefix}
448
+ rationale={optionRationale}
449
+ layout={layout}
450
+ bodyClassName={cardBodyClass}
451
+ />
452
+ </div>
453
+ </PrototypeComponent>
454
+ );
455
+ })}
456
+ </div>
457
+ </section>
458
+ );
459
+
460
+ return (
461
+ <PrototypeComponent
462
+ id={`${componentIdPrefix}.overview-modal`}
463
+ className={cn(
464
+ "min-h-0 flex-1",
465
+ isSidebar && "flex flex-col gap-4",
466
+ !isSidebar && "overflow-y-auto overscroll-contain",
467
+ className,
468
+ )}
469
+ >
470
+ {isSidebar ? (
471
+ <>
472
+ {variantsSectionContent}
473
+ {mobbinSection ? (
474
+ <>
475
+ <hr className="border-border" />
476
+ {mobbinSection}
477
+ </>
478
+ ) : null}
479
+ {contextSection ? (
480
+ <>
481
+ <hr className="border-border" />
482
+ {contextSection}
483
+ </>
484
+ ) : null}
485
+ <hr className="border-border" />
486
+ <div className="flex flex-col gap-2">
487
+ <div className="flex items-center justify-between gap-2">
488
+ <p className={SIDEBAR_GALLERY_SECTION_TITLE_CLASS}>Overview</p>
489
+ <PrototypeComponent
490
+ id={`${componentIdPrefix}.overview-copy-default-brief-prompt`}
491
+ >
492
+ <button
493
+ type="button"
494
+ className={SIDEBAR_OVERVIEW_COPY_BUTTON_CLASS}
495
+ disabled={overviewMatchesDefaults}
496
+ onClick={() =>
497
+ copyOverviewDefaultsPrompt(
498
+ buildResetBriefDefaultsPrompt({
499
+ brief: {
500
+ titleDefault: effectiveTitle,
501
+ descriptionDefault: effectiveDescription,
502
+ },
503
+ configFilePath: briefConfigFilePath,
504
+ }),
505
+ )
506
+ }
507
+ aria-label="Copy prompt to set overview as default"
508
+ >
509
+ {copyOverviewDefaultsIcon}
510
+ </button>
511
+ </PrototypeComponent>
512
+ </div>
513
+ {overviewFields}
514
+ </div>
515
+ </>
516
+ ) : (
517
+ <>
518
+ {overviewHeader}
519
+ {mobbinSection}
520
+ {variantsSectionContent}
521
+ </>
522
+ )}
523
+ </PrototypeComponent>
524
+ );
525
+ }
@@ -0,0 +1,65 @@
1
+ "use client";
2
+
3
+ import { CodeBlock } from "@prototype/components/platform-ui/code-block";
4
+ import { Panel, PanelSection } from "@prototype/components/platform-ui/panel";
5
+ import { PrototypeComponent } from "@prototype/components/prototypes/prototype-target";
6
+ import { cn } from "@prototype/lib/utils";
7
+
8
+ import type { DesignExplorationContext } from "@prototype/lib/prototypes/design-exploration-types";
9
+
10
+ function DefaultContextJsonView({
11
+ data,
12
+ compact = false,
13
+ }: {
14
+ data: unknown;
15
+ compact?: boolean;
16
+ }) {
17
+ const formatted =
18
+ typeof data === "string" ? data : JSON.stringify(data, null, 2);
19
+
20
+ return <CodeBlock compact={compact}>{formatted}</CodeBlock>;
21
+ }
22
+
23
+ type PrototypeDesignContextPanelProps = {
24
+ componentId: string;
25
+ context: DesignExplorationContext;
26
+ expanded: boolean;
27
+ onExpandedChange: (expanded: boolean) => void;
28
+ layout?: "default" | "sidebar";
29
+ };
30
+
31
+ export function PrototypeDesignContextPanel({
32
+ componentId,
33
+ context,
34
+ expanded,
35
+ onExpandedChange,
36
+ layout = "default",
37
+ }: PrototypeDesignContextPanelProps) {
38
+ const isSidebar = layout === "sidebar";
39
+ const panelId = context.panelId ?? `${componentId}-context-panel`;
40
+
41
+ return (
42
+ <PrototypeComponent id={componentId} className={isSidebar ? undefined : "mt-4"}>
43
+ <Panel className={cn("overflow-hidden rounded-lg bg-muted", isSidebar && "rounded-md")}>
44
+ <PanelSection
45
+ title={context.label}
46
+ expanded={expanded}
47
+ onExpandedChange={onExpandedChange}
48
+ headerClassName={isSidebar ? "px-2.5 py-2 normal-case" : undefined}
49
+ className="border-t-0"
50
+ >
51
+ <div id={panelId} className={cn(isSidebar && "text-sm")}>
52
+ {context.render ? (
53
+ context.render(context.data)
54
+ ) : (
55
+ <DefaultContextJsonView
56
+ data={context.data}
57
+ compact={isSidebar}
58
+ />
59
+ )}
60
+ </div>
61
+ </PanelSection>
62
+ </Panel>
63
+ </PrototypeComponent>
64
+ );
65
+ }