proto-plugin 0.1.0 → 0.1.2
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/AGENTS.md +308 -29
- package/README.md +55 -13
- package/package.json +1 -1
- package/scripts/lib/host-config.example.sh +1 -1
- package/scripts/publish-package.sh +41 -0
- package/scripts/verify-prototype-preview-states.mjs +57 -0
- package/src/components/platform-ui/controls-panel-select.tsx +4 -3
- package/src/components/platform-ui/prototype-tool-dialog.tsx +117 -0
- package/src/components/platform-ui/sidebar.tsx +34 -16
- package/src/components/prototype-gallery-client.tsx +77 -37
- package/src/components/prototypes/design-exploration-variant-preview-shell.tsx +43 -0
- package/src/components/prototypes/prototype-brief-field.tsx +33 -0
- package/src/components/prototypes/prototype-change-log-panel.tsx +4 -0
- package/src/components/prototypes/prototype-comment-storage-empty-state.tsx +41 -0
- package/src/components/prototypes/prototype-create-exploration-modal.tsx +41 -64
- package/src/components/prototypes/prototype-create-prototype-modal.tsx +102 -0
- package/src/components/prototypes/prototype-create-state-modal.tsx +31 -70
- package/src/components/prototypes/prototype-default-state-map-page.tsx +4 -4
- package/src/components/prototypes/prototype-design-brief-modal.tsx +10 -10
- package/src/components/prototypes/prototype-design-brief-panel.tsx +261 -231
- package/src/components/prototypes/prototype-floating-pill.module.scss +16 -9
- package/src/components/prototypes/prototype-mobbin-gallery.tsx +18 -2
- package/src/components/prototypes/prototype-review-sidebar.tsx +11 -28
- package/src/components/prototypes/prototype-spec-panel-content.tsx +5 -5
- package/src/components/prototypes/prototype-spec-panel-empty-state.tsx +53 -0
- package/src/components/prototypes/prototype-state-canvas-overlay.module.scss +20 -47
- package/src/components/prototypes/prototype-state-canvas-overlay.tsx +4 -42
- package/src/components/prototypes/prototype-variant-explorer.tsx +32 -78
- package/src/components/prototypes/prototype-vercel-preview-modal.tsx +4 -3
- package/src/components/prototypes/state-map-annotation-tooltip.module.scss +7 -7
- package/src/components/shell/prototype-component-library-empty-state.tsx +59 -0
- package/src/components/shell/prototype-gallery-nav.tsx +124 -0
- package/src/components/shell/prototype-gallery-shell.tsx +45 -13
- package/src/components/shell/prototype-link-source-modal.tsx +100 -0
- package/src/components/ui/button.tsx +3 -1
- package/src/components/ui/dialog.tsx +43 -4
- package/src/config/create-prototype-component-library-page.tsx +74 -0
- package/src/config/create-prototype-gallery-page.tsx +26 -23
- package/src/config/create-prototype-host-app.tsx +3 -15
- package/src/config/create-prototype-site-layout.tsx +14 -1
- package/src/config/create-prototype-state-map-page.tsx +44 -0
- package/src/config/prototype-site-layout-client.tsx +20 -6
- package/src/config/with-prototype.ts +21 -0
- package/src/index.ts +54 -10
- package/src/lib/copy-text-with-attachments.ts +35 -0
- package/src/lib/format-source-directory-name.ts +12 -0
- package/src/lib/platform-ui/sidebar-interactive-cursor.ts +19 -0
- package/src/lib/pr-split/build-pr-split-plan-prompt.ts +81 -0
- package/src/lib/pr-split/pr-split-highlight.ts +3 -0
- package/src/lib/prototype-comments/core/annotation-target.ts +12 -3
- package/src/lib/prototype-comments/core/comment-capture-blocked.ts +24 -0
- package/src/lib/prototype-comments/core/screenshot.ts +68 -31
- package/src/lib/prototype-comments/core/storage.ts +3 -6
- package/src/lib/prototype-comments/core/types.ts +2 -0
- package/src/lib/prototype-comments/hooks/useLayoutShift.ts +9 -2
- package/src/lib/prototype-comments/react/CommentCaptureToolbar.tsx +45 -10
- package/src/lib/prototype-comments/react/CommentProvider.tsx +19 -0
- package/src/lib/prototype-comments/ui/CommentModeToggleButton.tsx +34 -0
- package/src/lib/prototype-comments/ui/CommentsGrid.tsx +8 -16
- package/src/lib/prototype-comments/ui/CommentsSidebar.tsx +5 -19
- package/src/lib/prototype-comments/ui/capture-styles.module.scss +5 -1
- package/src/lib/prototype-comments/ui/comments-grid-card.module.scss +0 -7
- package/src/lib/prototypes/build-design-exploration-renderers.ts +44 -0
- package/src/lib/prototypes/comment-storage-setup-prompt.ts +124 -0
- package/src/lib/prototypes/create-prototype-prompt.ts +96 -0
- package/src/lib/prototypes/create-prototype-registry.ts +5 -0
- package/src/lib/prototypes/design-exploration-types.ts +261 -10
- package/src/lib/prototypes/link-source-prompt.ts +93 -0
- package/src/lib/prototypes/populate-component-library-prompt.ts +64 -0
- package/src/lib/prototypes/prototype-config-types.ts +2 -0
- package/src/lib/prototypes/prototype-layout-classes.ts +19 -0
- package/src/lib/prototypes/prototype-preview-state-registry.ts +5 -2
- package/src/lib/prototypes/prototype-review-context.tsx +4 -4
- package/src/lib/prototypes/prototype-state-canvas-constants.ts +1 -1
- package/src/lib/prototypes/prototype-state-canvas-types.ts +0 -2
- package/src/lib/prototypes/prototype-storage-status.ts +31 -0
- package/src/lib/prototypes/sort-prototypes-by-creation-date.ts +43 -0
- package/src/lib/prototypes/use-prototype-tool-theme.tsx +1 -1
- package/src/lib/prototypes/use-toolbar-hover-panel.ts +12 -1
- package/src/lib/prototypes/variant-set-lucide-icon.tsx +24 -0
- package/src/lib/tool-portal.ts +24 -0
- package/src/lib/use-copy-to-clipboard.ts +7 -13
- package/src/server/create-host-api-route.ts +7 -16
- package/src/server/create-prototype-api-routes.ts +0 -115
- package/src/server/prototype-storage-status-route.ts +29 -0
- package/src/server.ts +0 -8
- package/src/styles/globals.css +359 -71
- package/src/lib/prototypes/design-exploration-default-storage.ts +0 -68
- package/src/lib/prototypes/design-exploration-default.ts +0 -51
- package/src/lib/prototypes/use-design-exploration-default.ts +0 -114
- package/src/server/redis/prototype-design-exploration-default.ts +0 -32
|
@@ -4,19 +4,16 @@ import useCopyToClipboard from "@prototype/lib/use-copy-to-clipboard";
|
|
|
4
4
|
import { PrototypeControlGroup } from "@prototype/components/prototypes/prototype-control";
|
|
5
5
|
import { PrototypeDesignBriefModal } from "@prototype/components/prototypes/prototype-design-brief-modal";
|
|
6
6
|
import { PrototypeDesignBriefPanel } from "@prototype/components/prototypes/prototype-design-brief-panel";
|
|
7
|
-
import {
|
|
8
|
-
PrototypeComponent,
|
|
9
|
-
usePrototypeSlug,
|
|
10
|
-
} from "@prototype/components/prototypes/prototype-target";
|
|
7
|
+
import { PrototypeComponent } from "@prototype/components/prototypes/prototype-target";
|
|
11
8
|
import { PrototypeVariantTabs } from "@prototype/components/prototypes/prototype-variant-tabs";
|
|
12
9
|
import {
|
|
13
10
|
buildBriefOverviewCopyText,
|
|
11
|
+
getDesignExplorationDisplayOptions,
|
|
14
12
|
type DesignExplorationConfig,
|
|
15
13
|
} from "@prototype/lib/prototypes/design-exploration-types";
|
|
16
14
|
import { usePrototypeReviewOptional } from "@prototype/lib/prototypes/prototype-review-context";
|
|
17
|
-
import { useDesignExplorationDefault } from "@prototype/lib/prototypes/use-design-exploration-default";
|
|
18
15
|
import { usePersistedLocalString } from "@prototype/lib/prototypes/use-persisted-local-state";
|
|
19
|
-
import {
|
|
16
|
+
import { LayoutGrid } from "lucide-react";
|
|
20
17
|
import { useEffect, useMemo, useState } from "react";
|
|
21
18
|
|
|
22
19
|
const OVERVIEW_CONTROL_BUTTON_CLASS = "tool-toolbar-button";
|
|
@@ -35,6 +32,12 @@ type PrototypeVariantExplorerProps<TVariant extends string> =
|
|
|
35
32
|
* design explorations available in the review sidebar.
|
|
36
33
|
*/
|
|
37
34
|
registerOnly?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* When true, skips rendering the active variant inline on the page while
|
|
37
|
+
* still using `renderers` in the design-brief sidebar/modal. Use when the
|
|
38
|
+
* live page already reflects the selected variant (e.g. a full-page canvas).
|
|
39
|
+
*/
|
|
40
|
+
hideInlinePreview?: boolean;
|
|
38
41
|
/** When set, registers this exploration in the review sidebar. */
|
|
39
42
|
variantSet?: {
|
|
40
43
|
id: string;
|
|
@@ -49,34 +52,26 @@ export function PrototypeVariantExplorer<TVariant extends string>({
|
|
|
49
52
|
variant,
|
|
50
53
|
onVariantChange,
|
|
51
54
|
options,
|
|
52
|
-
rationale,
|
|
53
55
|
renderers,
|
|
56
|
+
baseline,
|
|
54
57
|
brief,
|
|
55
58
|
context,
|
|
56
59
|
mobbin,
|
|
57
60
|
variantsSection,
|
|
61
|
+
defaultVariant,
|
|
58
62
|
variantTabAriaLabel = "variant",
|
|
59
63
|
mobbinGalleryId = "mobbin-inspiration-gallery",
|
|
60
64
|
wrapRoot = true,
|
|
61
65
|
registerOnly = false,
|
|
66
|
+
hideInlinePreview = false,
|
|
62
67
|
briefConfigFilePath,
|
|
63
68
|
variantSet,
|
|
64
69
|
}: PrototypeVariantExplorerProps<TVariant>) {
|
|
65
70
|
const review = usePrototypeReviewOptional();
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
);
|
|
71
|
-
const {
|
|
72
|
-
effectiveDefault,
|
|
73
|
-
setDefaultVariant,
|
|
74
|
-
} = useDesignExplorationDefault({
|
|
75
|
-
slug,
|
|
76
|
-
storageKeyPrefix,
|
|
77
|
-
validValues,
|
|
78
|
-
});
|
|
79
|
-
const [notesExpanded, setNotesExpanded] = useState(false);
|
|
71
|
+
// Default is stored in the codebase config — not KV. "Make default" copies a
|
|
72
|
+
// prompt to update `defaultVariant` in the exploration config file.
|
|
73
|
+
const effectiveDefault = defaultVariant ?? options[0]?.value;
|
|
74
|
+
const displayOptions = getDesignExplorationDisplayOptions(options, baseline);
|
|
80
75
|
const [overviewOpen, setOverviewOpen] = useState(false);
|
|
81
76
|
const { copy: copyOverview, icon: copyIcon } = useCopyToClipboard();
|
|
82
77
|
const { value: overviewTitle } = usePersistedLocalString(
|
|
@@ -88,9 +83,7 @@ export function PrototypeVariantExplorer<TVariant extends string>({
|
|
|
88
83
|
brief.descriptionDefault,
|
|
89
84
|
);
|
|
90
85
|
|
|
91
|
-
const fallbackVariant =
|
|
92
|
-
const activeRationale =
|
|
93
|
-
rationale[variant] ?? (fallbackVariant ? rationale[fallbackVariant] : undefined);
|
|
86
|
+
const fallbackVariant = displayOptions[0]?.value;
|
|
94
87
|
const ActiveVariantRenderer =
|
|
95
88
|
renderers[variant] ?? (fallbackVariant ? renderers[fallbackVariant] : null);
|
|
96
89
|
const hideInlineControls = Boolean(review);
|
|
@@ -107,11 +100,12 @@ export function PrototypeVariantExplorer<TVariant extends string>({
|
|
|
107
100
|
variant,
|
|
108
101
|
onVariantChange,
|
|
109
102
|
options,
|
|
110
|
-
rationale,
|
|
111
103
|
renderers,
|
|
104
|
+
baseline,
|
|
112
105
|
defaultVariantValue: effectiveDefault,
|
|
113
|
-
onSetDefaultVariant: setDefaultVariant,
|
|
114
106
|
briefConfigFilePath,
|
|
107
|
+
explorationLabel: variantSet?.label ?? brief.titleDefault,
|
|
108
|
+
explorationId: variantSet?.id,
|
|
115
109
|
}),
|
|
116
110
|
[
|
|
117
111
|
brief,
|
|
@@ -123,11 +117,12 @@ export function PrototypeVariantExplorer<TVariant extends string>({
|
|
|
123
117
|
mobbinGalleryId,
|
|
124
118
|
onVariantChange,
|
|
125
119
|
options,
|
|
126
|
-
rationale,
|
|
127
120
|
renderers,
|
|
128
|
-
|
|
121
|
+
baseline,
|
|
129
122
|
storageKeyPrefix,
|
|
130
123
|
variant,
|
|
124
|
+
variantSet?.id,
|
|
125
|
+
variantSet?.label,
|
|
131
126
|
variantsSection,
|
|
132
127
|
],
|
|
133
128
|
);
|
|
@@ -151,8 +146,9 @@ export function PrototypeVariantExplorer<TVariant extends string>({
|
|
|
151
146
|
overviewTitle,
|
|
152
147
|
overviewDescription,
|
|
153
148
|
options.map((option) => option.value).join(","),
|
|
149
|
+
baseline.value,
|
|
154
150
|
].join("|"),
|
|
155
|
-
[variant, effectiveDefault, overviewTitle, overviewDescription, options],
|
|
151
|
+
[variant, effectiveDefault, overviewTitle, overviewDescription, options, baseline.value],
|
|
156
152
|
);
|
|
157
153
|
|
|
158
154
|
const variantSetId = variantSet?.id ?? null;
|
|
@@ -205,7 +201,7 @@ export function PrototypeVariantExplorer<TVariant extends string>({
|
|
|
205
201
|
<div className="flex flex-wrap items-center gap-2">
|
|
206
202
|
<PrototypeVariantTabs
|
|
207
203
|
idPrefix={variantTabsIdPrefix}
|
|
208
|
-
options={
|
|
204
|
+
options={displayOptions}
|
|
209
205
|
value={variant}
|
|
210
206
|
onValueChange={onVariantChange}
|
|
211
207
|
keyboardScopeActive={!overviewOpen}
|
|
@@ -242,65 +238,21 @@ export function PrototypeVariantExplorer<TVariant extends string>({
|
|
|
242
238
|
<LayoutGrid className="size-3.5" />
|
|
243
239
|
</button>
|
|
244
240
|
</PrototypeComponent>
|
|
245
|
-
|
|
246
|
-
<button
|
|
247
|
-
type="button"
|
|
248
|
-
className={OVERVIEW_CONTROL_BUTTON_CLASS}
|
|
249
|
-
onClick={() => setNotesExpanded((expanded) => !expanded)}
|
|
250
|
-
aria-expanded={notesExpanded}
|
|
251
|
-
aria-controls={`${componentIdPrefix}-notes`}
|
|
252
|
-
aria-label={notesExpanded ? "Hide notes" : "Show notes"}
|
|
253
|
-
>
|
|
254
|
-
{notesExpanded ? (
|
|
255
|
-
<ChevronUp className="size-3.5" />
|
|
256
|
-
) : (
|
|
257
|
-
<ChevronDown className="size-3.5" />
|
|
258
|
-
)}
|
|
259
|
-
</button>
|
|
260
241
|
</div>
|
|
261
242
|
</div>
|
|
262
|
-
|
|
263
|
-
{notesExpanded && activeRationale ? (
|
|
264
|
-
<PrototypeComponent
|
|
265
|
-
id={`${componentIdPrefix}.rationale`}
|
|
266
|
-
className="border-border bg-muted absolute right-0 top-full z-10 mt-2 max-w-md rounded-lg border px-3 py-2.5 text-left"
|
|
267
|
-
>
|
|
268
|
-
<div
|
|
269
|
-
id={`${componentIdPrefix}-notes`}
|
|
270
|
-
className="flex flex-col gap-2"
|
|
271
|
-
>
|
|
272
|
-
<p className="text-sm leading-relaxed">
|
|
273
|
-
<span className="text-foreground font-medium">Good</span>
|
|
274
|
-
{" · "}
|
|
275
|
-
<span className="text-muted-foreground">
|
|
276
|
-
{activeRationale.good}
|
|
277
|
-
</span>
|
|
278
|
-
</p>
|
|
279
|
-
<p className="text-sm leading-relaxed">
|
|
280
|
-
<span className="text-foreground font-medium">Bad</span>
|
|
281
|
-
{" · "}
|
|
282
|
-
<span className="text-muted-foreground">
|
|
283
|
-
{activeRationale.bad}
|
|
284
|
-
</span>
|
|
285
|
-
</p>
|
|
286
|
-
</div>
|
|
287
|
-
</PrototypeComponent>
|
|
288
|
-
) : null}
|
|
289
243
|
</div>
|
|
290
244
|
</PrototypeControlGroup>
|
|
291
245
|
</div>
|
|
292
246
|
),
|
|
293
247
|
[
|
|
294
|
-
activeRationale,
|
|
295
248
|
brief.descriptionDefault,
|
|
296
249
|
brief.titleDefault,
|
|
297
250
|
componentIdPrefix,
|
|
298
251
|
copyIcon,
|
|
299
252
|
copyOverview,
|
|
300
253
|
effectiveDefault,
|
|
301
|
-
notesExpanded,
|
|
302
254
|
onVariantChange,
|
|
303
|
-
|
|
255
|
+
displayOptions,
|
|
304
256
|
overviewDescription,
|
|
305
257
|
overviewOpen,
|
|
306
258
|
overviewTitle,
|
|
@@ -334,14 +286,16 @@ export function PrototypeVariantExplorer<TVariant extends string>({
|
|
|
334
286
|
variant={variant}
|
|
335
287
|
onVariantChange={onVariantChange}
|
|
336
288
|
options={options}
|
|
337
|
-
rationale={rationale}
|
|
338
289
|
renderers={renderers}
|
|
290
|
+
baseline={baseline}
|
|
339
291
|
defaultVariantValue={effectiveDefault}
|
|
340
|
-
|
|
292
|
+
briefConfigFilePath={briefConfigFilePath}
|
|
341
293
|
/>
|
|
342
294
|
) : null}
|
|
343
295
|
|
|
344
|
-
{!registerOnly &&
|
|
296
|
+
{!registerOnly && !hideInlinePreview && ActiveVariantRenderer
|
|
297
|
+
? ActiveVariantRenderer()
|
|
298
|
+
: null}
|
|
345
299
|
</div>
|
|
346
300
|
);
|
|
347
301
|
}
|
|
@@ -6,11 +6,11 @@ import {
|
|
|
6
6
|
DialogContent,
|
|
7
7
|
DialogTitle,
|
|
8
8
|
} from "@prototype/components/ui/dialog";
|
|
9
|
+
import { PROTOTYPE_TOOL_MODAL_CLOSE_CLASS } from "@prototype/components/platform-ui/prototype-tool-dialog";
|
|
9
10
|
import { ExternalLink, Loader2, X } from "lucide-react";
|
|
10
11
|
import { useEffect, useState } from "react";
|
|
11
12
|
|
|
12
|
-
const MODAL_CHROME_BUTTON_CLASS =
|
|
13
|
-
"text-muted-foreground hover:text-foreground inline-flex size-8 shrink-0 items-center justify-center rounded-sm border-0 bg-transparent p-0 opacity-70 transition-opacity hover:opacity-100 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 disabled:hover:opacity-40 disabled:hover:text-muted-foreground motion-reduce:transition-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4";
|
|
13
|
+
const MODAL_CHROME_BUTTON_CLASS = PROTOTYPE_TOOL_MODAL_CLOSE_CLASS;
|
|
14
14
|
|
|
15
15
|
export type PrototypeVercelPreviewModalProps = {
|
|
16
16
|
open: boolean;
|
|
@@ -44,8 +44,9 @@ export function PrototypeVercelPreviewModal({
|
|
|
44
44
|
return (
|
|
45
45
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
46
46
|
<DialogContent
|
|
47
|
+
portalScope="tool"
|
|
47
48
|
showCloseButton={false}
|
|
48
|
-
className="flex h-[min(92svh,calc(100%-2rem))] max-h-[min(92svh,calc(100%-2rem))] w-[min(96rem,calc(100vw-2rem))] max-w-[min(96rem,calc(100vw-2rem))] flex-col gap-0 overflow-hidden p-0 sm:max-w-[min(96rem,calc(100vw-2rem))]"
|
|
49
|
+
className="tool-dialog-surface flex h-[min(92svh,calc(100%-2rem))] max-h-[min(92svh,calc(100%-2rem))] w-[min(96rem,calc(100vw-2rem))] max-w-[min(96rem,calc(100vw-2rem))] flex-col gap-0 overflow-hidden border-0 p-0 sm:max-w-[min(96rem,calc(100vw-2rem))]"
|
|
49
50
|
>
|
|
50
51
|
<div className="flex items-center justify-between gap-3 border-b border-border px-4 py-3">
|
|
51
52
|
<DialogTitle className="min-w-0 truncate text-sm font-medium">
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
$wireframe-bg:
|
|
2
|
-
$wireframe-border:
|
|
1
|
+
$wireframe-bg: var(--tool-chrome-surface);
|
|
2
|
+
$wireframe-border: var(--tool-chrome-border);
|
|
3
3
|
$enter-duration: 0.12s;
|
|
4
4
|
$enter-ease: cubic-bezier(0.215, 0.61, 0.355, 1);
|
|
5
5
|
|
|
@@ -38,7 +38,7 @@ $enter-ease: cubic-bezier(0.215, 0.61, 0.355, 1);
|
|
|
38
38
|
margin: 0;
|
|
39
39
|
font-size: 0.6875rem;
|
|
40
40
|
line-height: 1.45;
|
|
41
|
-
color: var(--
|
|
41
|
+
color: var(--tool-chrome-text-muted);
|
|
42
42
|
white-space: pre-wrap;
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -54,13 +54,13 @@ $enter-ease: cubic-bezier(0.215, 0.61, 0.355, 1);
|
|
|
54
54
|
padding: 0.1875rem 0 0.1875rem 0.875rem;
|
|
55
55
|
font-size: 0.6875rem;
|
|
56
56
|
line-height: 1.45;
|
|
57
|
-
color: var(--
|
|
57
|
+
color: var(--tool-chrome-text-muted);
|
|
58
58
|
|
|
59
59
|
&::before {
|
|
60
60
|
content: "•";
|
|
61
61
|
position: absolute;
|
|
62
62
|
left: 0;
|
|
63
|
-
color: var(--
|
|
63
|
+
color: var(--tool-chrome-icon);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
& + & {
|
|
@@ -78,12 +78,12 @@ $enter-ease: cubic-bezier(0.215, 0.61, 0.355, 1);
|
|
|
78
78
|
@media (hover: hover) and (pointer: fine) {
|
|
79
79
|
&:hover {
|
|
80
80
|
background: color-mix(in srgb, var(--tool-chrome-blue) 6%, $wireframe-bg);
|
|
81
|
-
color: var(--
|
|
81
|
+
color: var(--tool-chrome-text-heading);
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
.bulletItemActive {
|
|
87
87
|
background: color-mix(in srgb, var(--tool-chrome-blue) 8%, $wireframe-bg);
|
|
88
|
-
color: var(--
|
|
88
|
+
color: var(--tool-chrome-text-heading);
|
|
89
89
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Button } from "@prototype/components/ui/button";
|
|
4
|
+
import { buildPopulateComponentLibraryCopyText } from "@prototype/lib/prototypes/populate-component-library-prompt";
|
|
5
|
+
import useCopyToClipboard from "@prototype/lib/use-copy-to-clipboard";
|
|
6
|
+
|
|
7
|
+
type PrototypeComponentLibraryEmptyStateProps = {
|
|
8
|
+
sourcePath?: string;
|
|
9
|
+
syncConfigPath?: string;
|
|
10
|
+
componentLibraryPagePath?: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function PrototypeComponentLibraryEmptyState({
|
|
14
|
+
sourcePath,
|
|
15
|
+
syncConfigPath,
|
|
16
|
+
componentLibraryPagePath,
|
|
17
|
+
}: PrototypeComponentLibraryEmptyStateProps) {
|
|
18
|
+
const { copy, icon, isCopied } = useCopyToClipboard();
|
|
19
|
+
|
|
20
|
+
const handleCopy = () => {
|
|
21
|
+
const origin =
|
|
22
|
+
typeof window !== "undefined" ? window.location.origin : undefined;
|
|
23
|
+
|
|
24
|
+
copy(
|
|
25
|
+
buildPopulateComponentLibraryCopyText({
|
|
26
|
+
sourcePath,
|
|
27
|
+
syncConfigPath,
|
|
28
|
+
componentLibraryPagePath,
|
|
29
|
+
origin,
|
|
30
|
+
}),
|
|
31
|
+
);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div className="max-w-2xl rounded-xl border border-dashed border-[var(--tool-chrome-border)] bg-[var(--tool-chrome-surface-muted)] px-6 py-8 text-[var(--tool-chrome-text-muted)]">
|
|
36
|
+
<p className="text-sm font-medium text-[var(--tool-chrome-text-heading)]">
|
|
37
|
+
No components yet
|
|
38
|
+
</p>
|
|
39
|
+
<p className="mt-2 text-sm leading-relaxed">
|
|
40
|
+
Copy the prompt below into your agent. It will inspect the linked source
|
|
41
|
+
app and port over base styles, UI primitives, and shared building
|
|
42
|
+
blocks — err on the side of syncing more rather than less.
|
|
43
|
+
</p>
|
|
44
|
+
<div className="mt-5 flex items-center gap-3">
|
|
45
|
+
<Button
|
|
46
|
+
type="button"
|
|
47
|
+
variant="outline"
|
|
48
|
+
size="sm"
|
|
49
|
+
className="h-8 gap-1.5 border-[var(--tool-chrome-border-strong)] text-[var(--tool-chrome-text-heading)] hover:text-[var(--tool-chrome-text-heading)]"
|
|
50
|
+
onClick={handleCopy}
|
|
51
|
+
aria-label="Copy component library population prompt"
|
|
52
|
+
>
|
|
53
|
+
{isCopied ? "Copied prompt" : "Copy prompt"}
|
|
54
|
+
{icon}
|
|
55
|
+
</Button>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { IconButton } from "@prototype/components/platform-ui/icon-button";
|
|
4
|
+
import { Button } from "@prototype/components/ui/button";
|
|
5
|
+
import { PrototypeLinkSourceModal } from "@prototype/components/shell/prototype-link-source-modal";
|
|
6
|
+
import { cn } from "@prototype/lib/utils";
|
|
7
|
+
import { usePrototypeToolTheme } from "@prototype/lib/prototypes/use-prototype-tool-theme";
|
|
8
|
+
import { Moon, Pencil, Shapes, Sun, LayoutDashboard } from "lucide-react";
|
|
9
|
+
import Link from "next/link";
|
|
10
|
+
import { usePathname } from "next/navigation";
|
|
11
|
+
import { useState } from "react";
|
|
12
|
+
|
|
13
|
+
const GALLERY_NAV_ITEMS = [
|
|
14
|
+
{
|
|
15
|
+
href: "/",
|
|
16
|
+
label: "Prototypes",
|
|
17
|
+
icon: Shapes,
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
href: "/component-library",
|
|
21
|
+
label: "Component Library",
|
|
22
|
+
icon: LayoutDashboard,
|
|
23
|
+
},
|
|
24
|
+
] as const;
|
|
25
|
+
|
|
26
|
+
type PrototypeGalleryNavProps = {
|
|
27
|
+
sourceDirectoryName?: string;
|
|
28
|
+
sourcePath?: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export function PrototypeGalleryNav({
|
|
32
|
+
sourceDirectoryName,
|
|
33
|
+
sourcePath,
|
|
34
|
+
}: PrototypeGalleryNavProps) {
|
|
35
|
+
const pathname = usePathname();
|
|
36
|
+
const { theme, toggleTheme } = usePrototypeToolTheme();
|
|
37
|
+
const [linkSourceOpen, setLinkSourceOpen] = useState(false);
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<aside className="border-[var(--tool-chrome-border)] bg-[var(--bg-ground)] flex h-full min-h-0 w-56 shrink-0 flex-col self-stretch overflow-hidden border-r">
|
|
41
|
+
<nav aria-label="Gallery" className="flex flex-col gap-1 p-3">
|
|
42
|
+
{GALLERY_NAV_ITEMS.map(({ href, label, icon: Icon }) => {
|
|
43
|
+
const isActive =
|
|
44
|
+
href === "/"
|
|
45
|
+
? pathname === "/"
|
|
46
|
+
: pathname === href || pathname.startsWith(`${href}/`);
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<Link
|
|
50
|
+
key={href}
|
|
51
|
+
href={href}
|
|
52
|
+
aria-current={isActive ? "page" : undefined}
|
|
53
|
+
className={cn(
|
|
54
|
+
"flex items-center gap-2.5 rounded-md px-3 py-2 text-sm font-medium transition-colors",
|
|
55
|
+
isActive
|
|
56
|
+
? "bg-[var(--tool-chrome-gray-highlight)] text-[var(--tool-chrome-text-heading)]"
|
|
57
|
+
: "text-[var(--tool-chrome-text-muted)] hover:bg-[var(--tool-chrome-gray-highlight)] hover:text-[var(--tool-chrome-text-heading)]",
|
|
58
|
+
)}
|
|
59
|
+
>
|
|
60
|
+
<Icon className="size-4 shrink-0" aria-hidden />
|
|
61
|
+
{label}
|
|
62
|
+
</Link>
|
|
63
|
+
);
|
|
64
|
+
})}
|
|
65
|
+
</nav>
|
|
66
|
+
|
|
67
|
+
<div className="border-[var(--tool-chrome-border)] mt-auto border-t p-3">
|
|
68
|
+
<div className="flex min-w-0 items-center gap-2">
|
|
69
|
+
<IconButton
|
|
70
|
+
type="button"
|
|
71
|
+
variant="ghost"
|
|
72
|
+
size="icon"
|
|
73
|
+
onClick={toggleTheme}
|
|
74
|
+
aria-label={
|
|
75
|
+
theme === "light" ? "Switch to dark mode" : "Switch to light mode"
|
|
76
|
+
}
|
|
77
|
+
className="size-8 shrink-0 text-[var(--tool-chrome-text-muted)] hover:text-[var(--tool-chrome-text-heading)]"
|
|
78
|
+
>
|
|
79
|
+
{theme === "light" ? (
|
|
80
|
+
<Moon size={16} strokeWidth={2} aria-hidden />
|
|
81
|
+
) : (
|
|
82
|
+
<Sun size={16} strokeWidth={2} aria-hidden />
|
|
83
|
+
)}
|
|
84
|
+
</IconButton>
|
|
85
|
+
{sourceDirectoryName ? (
|
|
86
|
+
<>
|
|
87
|
+
<span
|
|
88
|
+
className="min-w-0 flex-1 truncate text-xs text-[var(--tool-chrome-text-muted)]"
|
|
89
|
+
title={sourcePath ?? sourceDirectoryName}
|
|
90
|
+
>
|
|
91
|
+
{sourceDirectoryName}
|
|
92
|
+
</span>
|
|
93
|
+
<IconButton
|
|
94
|
+
type="button"
|
|
95
|
+
variant="ghost"
|
|
96
|
+
size="icon"
|
|
97
|
+
onClick={() => setLinkSourceOpen(true)}
|
|
98
|
+
aria-label="Change linked source app"
|
|
99
|
+
className="size-7 shrink-0 text-[var(--tool-chrome-text-muted)] hover:text-[var(--tool-chrome-text-heading)]"
|
|
100
|
+
>
|
|
101
|
+
<Pencil size={14} strokeWidth={2} aria-hidden />
|
|
102
|
+
</IconButton>
|
|
103
|
+
</>
|
|
104
|
+
) : (
|
|
105
|
+
<Button
|
|
106
|
+
type="button"
|
|
107
|
+
variant="outline"
|
|
108
|
+
onClick={() => setLinkSourceOpen(true)}
|
|
109
|
+
className="h-9 min-w-0 flex-1 justify-center rounded-lg text-sm font-medium"
|
|
110
|
+
>
|
|
111
|
+
Link source
|
|
112
|
+
</Button>
|
|
113
|
+
)}
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
<PrototypeLinkSourceModal
|
|
118
|
+
open={linkSourceOpen}
|
|
119
|
+
onOpenChange={setLinkSourceOpen}
|
|
120
|
+
currentSourcePath={sourcePath}
|
|
121
|
+
/>
|
|
122
|
+
</aside>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
@@ -1,33 +1,64 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { PrototypeProvider } from "@prototype/components/prototype-provider";
|
|
4
|
+
import { PrototypeGalleryNav } from "@prototype/components/shell/prototype-gallery-nav";
|
|
4
5
|
import { cn } from "@prototype/lib/utils";
|
|
5
6
|
import type { ReactNode } from "react";
|
|
6
7
|
|
|
7
8
|
type PrototypeGalleryShellProps = {
|
|
8
9
|
children: ReactNode;
|
|
9
10
|
footer?: ReactNode;
|
|
11
|
+
sourceDirectoryName?: string;
|
|
12
|
+
sourcePath?: string;
|
|
10
13
|
};
|
|
11
14
|
|
|
12
15
|
export function PrototypeGalleryShell({
|
|
13
16
|
children,
|
|
14
17
|
footer,
|
|
18
|
+
sourceDirectoryName,
|
|
19
|
+
sourcePath,
|
|
15
20
|
}: PrototypeGalleryShellProps) {
|
|
16
21
|
return (
|
|
17
|
-
<PrototypeProvider className="bg-[var(--bg-
|
|
22
|
+
<PrototypeProvider className="bg-[var(--bg-main)] flex h-full min-h-0 w-full flex-1 flex-col overflow-hidden">
|
|
18
23
|
{footer ? (
|
|
19
|
-
<div className="border-border flex shrink-0 items-center justify-end border-b px-10 py-3 lg:px-16">
|
|
24
|
+
<div className="border-[var(--tool-chrome-border)] bg-[var(--bg-ground)] flex shrink-0 items-center justify-end border-b px-10 py-3 lg:px-16">
|
|
20
25
|
{footer}
|
|
21
26
|
</div>
|
|
22
27
|
) : null}
|
|
23
28
|
|
|
24
|
-
<
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
<div className="flex h-full min-h-0 flex-1 overflow-hidden">
|
|
30
|
+
<PrototypeGalleryNav
|
|
31
|
+
sourceDirectoryName={sourceDirectoryName}
|
|
32
|
+
sourcePath={sourcePath}
|
|
33
|
+
/>
|
|
34
|
+
<main className="bg-[var(--bg-main)] flex min-h-0 flex-1 flex-col overflow-hidden">
|
|
35
|
+
{children}
|
|
36
|
+
</main>
|
|
37
|
+
</div>
|
|
27
38
|
</PrototypeProvider>
|
|
28
39
|
);
|
|
29
40
|
}
|
|
30
41
|
|
|
42
|
+
type PrototypeGalleryPageLayoutProps = {
|
|
43
|
+
header: ReactNode;
|
|
44
|
+
children: ReactNode;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/** Gallery pages: header stays fixed; only body scrolls. */
|
|
48
|
+
export function PrototypeGalleryPageLayout({
|
|
49
|
+
header,
|
|
50
|
+
children,
|
|
51
|
+
}: PrototypeGalleryPageLayoutProps) {
|
|
52
|
+
return (
|
|
53
|
+
<div className="flex h-full min-h-0 flex-1 flex-col overflow-hidden">
|
|
54
|
+
{header}
|
|
55
|
+
<div className="bg-[var(--bg-main)] min-h-0 flex-1 overflow-y-auto overscroll-y-contain">
|
|
56
|
+
<div className="h-fit w-full">{children}</div>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
31
62
|
type PrototypeGalleryHeaderProps = {
|
|
32
63
|
eyebrow?: string;
|
|
33
64
|
title?: string;
|
|
@@ -44,17 +75,17 @@ export function PrototypeGalleryHeader({
|
|
|
44
75
|
return (
|
|
45
76
|
<div
|
|
46
77
|
className={cn(
|
|
47
|
-
"border-border -
|
|
78
|
+
"border-[var(--tool-chrome-border)] bg-[var(--bg-ground)] shrink-0 border-b px-10 py-6 lg:px-16",
|
|
48
79
|
className,
|
|
49
80
|
)}
|
|
50
81
|
>
|
|
51
82
|
{eyebrow ? (
|
|
52
|
-
<p className="text-xs text-muted
|
|
83
|
+
<p className="text-xs text-[var(--tool-chrome-text-muted)]">{eyebrow}</p>
|
|
53
84
|
) : null}
|
|
54
|
-
<h1 className="
|
|
85
|
+
<h1 className="mt-0 text-2xl font-medium tracking-tight text-[var(--tool-chrome-text-heading)]">
|
|
55
86
|
{title}
|
|
56
87
|
</h1>
|
|
57
|
-
<p className="
|
|
88
|
+
<p className="mt-2 max-w-2xl text-sm leading-relaxed text-[var(--tool-chrome-text-muted)]">
|
|
58
89
|
{description}
|
|
59
90
|
</p>
|
|
60
91
|
</div>
|
|
@@ -70,10 +101,11 @@ export function PrototypeGalleryPage({
|
|
|
70
101
|
}) {
|
|
71
102
|
return (
|
|
72
103
|
<PrototypeGalleryShell footer={footer}>
|
|
73
|
-
<
|
|
74
|
-
{header ?? <PrototypeGalleryHeader />}
|
|
75
|
-
|
|
76
|
-
|
|
104
|
+
<PrototypeGalleryPageLayout
|
|
105
|
+
header={header ?? <PrototypeGalleryHeader className="mb-0" />}
|
|
106
|
+
>
|
|
107
|
+
<div className="px-10 pt-8 pb-16 lg:px-16">{children}</div>
|
|
108
|
+
</PrototypeGalleryPageLayout>
|
|
77
109
|
</PrototypeGalleryShell>
|
|
78
110
|
);
|
|
79
111
|
}
|