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
|
@@ -1,19 +1,27 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { Button } from "@prototype/components/ui/button";
|
|
4
|
-
import {
|
|
4
|
+
import { Switch } from "@prototype/components/ui/switch";
|
|
5
|
+
import {
|
|
6
|
+
PrototypeComponent,
|
|
7
|
+
usePrototypeSlug,
|
|
8
|
+
} from "@prototype/components/prototypes/prototype-target";
|
|
9
|
+
import { PrototypeBriefTextarea } from "@prototype/components/prototypes/prototype-brief-field";
|
|
5
10
|
import { PrototypeDesignContextPanel } from "@prototype/components/prototypes/prototype-design-context-panel";
|
|
6
11
|
import { PrototypeMobbinGallery } from "@prototype/components/prototypes/prototype-mobbin-gallery";
|
|
7
|
-
import { PrototypeVariantRationalePanel } from "@prototype/components/prototypes/prototype-variant-rationale-panel";
|
|
8
12
|
import {
|
|
13
|
+
buildMoreDesignExplorationVariantsCopyText,
|
|
9
14
|
buildResetBriefDefaultsPrompt,
|
|
15
|
+
buildSetDefaultVariantPrompt,
|
|
16
|
+
getDesignExplorationDisplayOptions,
|
|
17
|
+
getDesignExplorationVariantOptions,
|
|
18
|
+
resolveDesignExplorationBaselineOption,
|
|
19
|
+
type DesignExplorationBaselineOption,
|
|
10
20
|
type DesignExplorationBrief,
|
|
11
21
|
type DesignExplorationContext,
|
|
12
22
|
type DesignExplorationMobbin,
|
|
13
|
-
type DesignExplorationRationale,
|
|
14
23
|
type DesignExplorationVariantOption,
|
|
15
24
|
type DesignExplorationVariantsSection,
|
|
16
|
-
type MobbinReference,
|
|
17
25
|
} from "@prototype/lib/prototypes/design-exploration-types";
|
|
18
26
|
import {
|
|
19
27
|
usePersistedLocalBoolean,
|
|
@@ -23,61 +31,6 @@ import useCopyToClipboard from "@prototype/lib/use-copy-to-clipboard";
|
|
|
23
31
|
import { cn } from "@prototype/lib/utils";
|
|
24
32
|
import { useEffect, useState } from "react";
|
|
25
33
|
|
|
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
34
|
const MODAL_VARIANT_PREVIEW_SCALE = 0.75;
|
|
82
35
|
const SIDEBAR_VARIANT_PREVIEW_SCALE = 0.42;
|
|
83
36
|
|
|
@@ -89,12 +42,8 @@ const MODAL_OVERVIEW_DESCRIPTION_FIELD_CLASS =
|
|
|
89
42
|
|
|
90
43
|
const MODAL_GALLERY_SECTION_TITLE_CLASS = "tool-heading";
|
|
91
44
|
|
|
92
|
-
const MODAL_GALLERY_SECTION_DESC_CLASS = "tool-body max-w-2xl";
|
|
93
|
-
|
|
94
45
|
const MODAL_GALLERY_CARD_TITLE_CLASS = "text-foreground font-medium";
|
|
95
46
|
|
|
96
|
-
const MODAL_GALLERY_CARD_BODY_CLASS = "text-sm text-muted-foreground leading-relaxed";
|
|
97
|
-
|
|
98
47
|
const SIDEBAR_OVERVIEW_TITLE_FIELD_CLASS =
|
|
99
48
|
"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
49
|
|
|
@@ -103,12 +52,8 @@ const SIDEBAR_OVERVIEW_DESCRIPTION_FIELD_CLASS =
|
|
|
103
52
|
|
|
104
53
|
const SIDEBAR_GALLERY_SECTION_TITLE_CLASS = "tool-section-label";
|
|
105
54
|
|
|
106
|
-
const SIDEBAR_GALLERY_SECTION_DESC_CLASS = "tool-body";
|
|
107
|
-
|
|
108
55
|
const SIDEBAR_GALLERY_CARD_TITLE_CLASS = "text-sm text-foreground font-medium";
|
|
109
56
|
|
|
110
|
-
const SIDEBAR_GALLERY_CARD_BODY_CLASS = "text-sm text-muted-foreground leading-relaxed";
|
|
111
|
-
|
|
112
57
|
export type PrototypeDesignBriefPanelLayout = "default" | "sidebar";
|
|
113
58
|
|
|
114
59
|
export type PrototypeDesignBriefPanelProps<TVariant extends string> = {
|
|
@@ -122,57 +67,62 @@ export type PrototypeDesignBriefPanelProps<TVariant extends string> = {
|
|
|
122
67
|
variant: TVariant;
|
|
123
68
|
onVariantChange: (variant: TVariant) => void;
|
|
124
69
|
options: DesignExplorationVariantOption<TVariant>[];
|
|
125
|
-
rationale: Record<TVariant, DesignExplorationRationale>;
|
|
126
70
|
renderers: Record<TVariant, () => React.ReactNode>;
|
|
127
|
-
/**
|
|
71
|
+
/** Pre-exploration UI — pinned at the bottom of the variant list. */
|
|
72
|
+
baseline: DesignExplorationBaselineOption<TVariant>;
|
|
73
|
+
/**
|
|
74
|
+
* Marked "Default" in the brief — the codebase-stored default variant
|
|
75
|
+
* (`config.defaultVariant`). Other options offer a "Make default" prompt.
|
|
76
|
+
*/
|
|
128
77
|
defaultVariantValue?: TVariant;
|
|
129
|
-
/**
|
|
130
|
-
onSetDefaultVariant?: (value: TVariant) => void;
|
|
131
|
-
/** Path to design exploration config — included in overview copy prompt. */
|
|
78
|
+
/** Path to design exploration config — included in copy prompts. */
|
|
132
79
|
briefConfigFilePath?: string;
|
|
80
|
+
/** Sidebar prompt copy — label from variant set or brief title. */
|
|
81
|
+
explorationLabel?: string;
|
|
82
|
+
/** Sidebar prompt copy — variant set id when registered. */
|
|
83
|
+
explorationId?: string;
|
|
133
84
|
layout?: PrototypeDesignBriefPanelLayout;
|
|
134
85
|
className?: string;
|
|
135
86
|
};
|
|
136
87
|
|
|
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
88
|
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";
|
|
89
|
+
"cursor-pointer 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
90
|
|
|
146
|
-
function
|
|
147
|
-
|
|
91
|
+
function MakeDefaultButton({
|
|
92
|
+
componentId,
|
|
93
|
+
optionLabel,
|
|
148
94
|
optionValue,
|
|
149
|
-
|
|
150
|
-
rationale,
|
|
151
|
-
layout,
|
|
152
|
-
bodyClassName,
|
|
95
|
+
configFilePath,
|
|
153
96
|
}: {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
layout: PrototypeDesignBriefPanelLayout;
|
|
159
|
-
bodyClassName: string;
|
|
97
|
+
componentId: string;
|
|
98
|
+
optionLabel: string;
|
|
99
|
+
optionValue: string;
|
|
100
|
+
configFilePath?: string;
|
|
160
101
|
}) {
|
|
161
|
-
const {
|
|
162
|
-
usePersistedLocalBoolean(
|
|
163
|
-
`${storageKeyPrefix}-variant-rationale-expanded-${optionValue}`,
|
|
164
|
-
false,
|
|
165
|
-
);
|
|
102
|
+
const { copy, isCopied } = useCopyToClipboard();
|
|
166
103
|
|
|
167
104
|
return (
|
|
168
|
-
<
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
105
|
+
<PrototypeComponent id={componentId}>
|
|
106
|
+
<Button
|
|
107
|
+
type="button"
|
|
108
|
+
variant="chrome"
|
|
109
|
+
size="sm"
|
|
110
|
+
className="h-6 shrink-0 rounded px-2 text-[11px]"
|
|
111
|
+
onClick={(event) => {
|
|
112
|
+
event.stopPropagation();
|
|
113
|
+
copy(
|
|
114
|
+
buildSetDefaultVariantPrompt({
|
|
115
|
+
variantLabel: optionLabel,
|
|
116
|
+
variantValue: optionValue,
|
|
117
|
+
configFilePath,
|
|
118
|
+
}),
|
|
119
|
+
);
|
|
120
|
+
}}
|
|
121
|
+
aria-label={`Copy prompt to make ${optionLabel} the default`}
|
|
122
|
+
>
|
|
123
|
+
{isCopied ? "Copied prompt" : "Make default"}
|
|
124
|
+
</Button>
|
|
125
|
+
</PrototypeComponent>
|
|
176
126
|
);
|
|
177
127
|
}
|
|
178
128
|
|
|
@@ -187,15 +137,17 @@ export function PrototypeDesignBriefPanel<TVariant extends string>({
|
|
|
187
137
|
variant,
|
|
188
138
|
onVariantChange,
|
|
189
139
|
options,
|
|
190
|
-
rationale,
|
|
191
140
|
renderers,
|
|
141
|
+
baseline,
|
|
192
142
|
defaultVariantValue,
|
|
193
|
-
onSetDefaultVariant,
|
|
194
143
|
briefConfigFilePath,
|
|
144
|
+
explorationLabel,
|
|
145
|
+
explorationId,
|
|
195
146
|
layout = "default",
|
|
196
147
|
className,
|
|
197
148
|
}: PrototypeDesignBriefPanelProps<TVariant>) {
|
|
198
149
|
const isSidebar = layout === "sidebar";
|
|
150
|
+
const slug = usePrototypeSlug();
|
|
199
151
|
const [variantPreviewsReady, setVariantPreviewsReady] = useState(false);
|
|
200
152
|
|
|
201
153
|
useEffect(() => {
|
|
@@ -204,6 +156,11 @@ export function PrototypeDesignBriefPanel<TVariant extends string>({
|
|
|
204
156
|
|
|
205
157
|
const { copy: copyOverviewDefaultsPrompt, icon: copyOverviewDefaultsIcon } =
|
|
206
158
|
useCopyToClipboard();
|
|
159
|
+
const {
|
|
160
|
+
copy: copyMoreVariantsPrompt,
|
|
161
|
+
icon: copyMoreVariantsIcon,
|
|
162
|
+
isCopied: isMoreVariantsCopied,
|
|
163
|
+
} = useCopyToClipboard();
|
|
207
164
|
const { value: title, updateValue: updateTitle } = usePersistedLocalString(
|
|
208
165
|
`${storageKeyPrefix}-overview-title`,
|
|
209
166
|
brief.titleDefault,
|
|
@@ -220,11 +177,18 @@ export function PrototypeDesignBriefPanel<TVariant extends string>({
|
|
|
220
177
|
`${storageKeyPrefix}-context-expanded`,
|
|
221
178
|
context?.defaultExpanded ?? false,
|
|
222
179
|
);
|
|
180
|
+
const { value: moreVariantsBrief, updateValue: updateMoreVariantsBrief } =
|
|
181
|
+
usePersistedLocalString(`${storageKeyPrefix}-more-variants-brief`, "");
|
|
182
|
+
const {
|
|
183
|
+
value: multipleVersions,
|
|
184
|
+
updateValue: updateMultipleVersions,
|
|
185
|
+
} = usePersistedLocalBoolean(
|
|
186
|
+
`${storageKeyPrefix}-more-variants-multiple-versions`,
|
|
187
|
+
true,
|
|
188
|
+
);
|
|
189
|
+
const canCopyMoreVariants = moreVariantsBrief.trim().length > 0 && Boolean(slug);
|
|
223
190
|
|
|
224
191
|
const variantsTitle = variantsSection?.title ?? "Design variants";
|
|
225
|
-
const variantsDescription =
|
|
226
|
-
variantsSection?.description ??
|
|
227
|
-
"Compare layout, copy, and CTAs side by side.";
|
|
228
192
|
|
|
229
193
|
const effectiveTitle = title.trim() || brief.titleDefault;
|
|
230
194
|
const effectiveDescription =
|
|
@@ -242,15 +206,9 @@ export function PrototypeDesignBriefPanel<TVariant extends string>({
|
|
|
242
206
|
const sectionTitleClass = isSidebar
|
|
243
207
|
? SIDEBAR_GALLERY_SECTION_TITLE_CLASS
|
|
244
208
|
: MODAL_GALLERY_SECTION_TITLE_CLASS;
|
|
245
|
-
const sectionDescClass = isSidebar
|
|
246
|
-
? SIDEBAR_GALLERY_SECTION_DESC_CLASS
|
|
247
|
-
: MODAL_GALLERY_SECTION_DESC_CLASS;
|
|
248
209
|
const cardTitleClass = isSidebar
|
|
249
210
|
? SIDEBAR_GALLERY_CARD_TITLE_CLASS
|
|
250
211
|
: MODAL_GALLERY_CARD_TITLE_CLASS;
|
|
251
|
-
const cardBodyClass = isSidebar
|
|
252
|
-
? SIDEBAR_GALLERY_CARD_BODY_CLASS
|
|
253
|
-
: MODAL_GALLERY_CARD_BODY_CLASS;
|
|
254
212
|
const variantPreviewScale = isSidebar
|
|
255
213
|
? SIDEBAR_VARIANT_PREVIEW_SCALE
|
|
256
214
|
: MODAL_VARIANT_PREVIEW_SCALE;
|
|
@@ -318,6 +276,188 @@ export function PrototypeDesignBriefPanel<TVariant extends string>({
|
|
|
318
276
|
/>
|
|
319
277
|
) : null;
|
|
320
278
|
|
|
279
|
+
const resolvedExplorationLabel = explorationLabel ?? brief.titleDefault;
|
|
280
|
+
|
|
281
|
+
const handleCopyMoreVariantsPrompt = () => {
|
|
282
|
+
if (!slug || !canCopyMoreVariants) return;
|
|
283
|
+
|
|
284
|
+
const origin =
|
|
285
|
+
typeof window !== "undefined" ? window.location.origin : undefined;
|
|
286
|
+
|
|
287
|
+
copyMoreVariantsPrompt(
|
|
288
|
+
buildMoreDesignExplorationVariantsCopyText({
|
|
289
|
+
slug,
|
|
290
|
+
explorationLabel: resolvedExplorationLabel,
|
|
291
|
+
explorationId,
|
|
292
|
+
options: explorationOptions,
|
|
293
|
+
brief: {
|
|
294
|
+
titleDefault: effectiveTitle,
|
|
295
|
+
descriptionDefault: effectiveDescription,
|
|
296
|
+
},
|
|
297
|
+
configFilePath: briefConfigFilePath,
|
|
298
|
+
origin,
|
|
299
|
+
briefText: moreVariantsBrief,
|
|
300
|
+
multipleVersions,
|
|
301
|
+
}),
|
|
302
|
+
);
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
const moreVariantsPromptSection = isSidebar ? (
|
|
306
|
+
<div className="flex flex-col gap-2">
|
|
307
|
+
<PrototypeComponent id={`${componentIdPrefix}.more-variants-prompt`}>
|
|
308
|
+
<PrototypeBriefTextarea
|
|
309
|
+
value={moreVariantsBrief}
|
|
310
|
+
onChange={(event) => updateMoreVariantsBrief(event.target.value)}
|
|
311
|
+
minHeightClass="min-h-[5rem]"
|
|
312
|
+
placeholder={
|
|
313
|
+
multipleVersions
|
|
314
|
+
? "Describe more variations to explore…"
|
|
315
|
+
: "Describe one variation to explore…"
|
|
316
|
+
}
|
|
317
|
+
aria-label="More variations brief"
|
|
318
|
+
/>
|
|
319
|
+
</PrototypeComponent>
|
|
320
|
+
<div className="flex items-center justify-between gap-4">
|
|
321
|
+
<PrototypeComponent id={`${componentIdPrefix}.more-variants-multiple-versions`}>
|
|
322
|
+
<label className="flex cursor-pointer items-center gap-2">
|
|
323
|
+
<Switch
|
|
324
|
+
checked={multipleVersions}
|
|
325
|
+
onCheckedChange={updateMultipleVersions}
|
|
326
|
+
aria-label="Multiple versions"
|
|
327
|
+
className="data-[state=checked]:bg-foreground"
|
|
328
|
+
/>
|
|
329
|
+
<span className="text-sm text-[var(--text-secondary)]">Multiple versions</span>
|
|
330
|
+
</label>
|
|
331
|
+
</PrototypeComponent>
|
|
332
|
+
<PrototypeComponent id={`${componentIdPrefix}.more-variants-copy-prompt`}>
|
|
333
|
+
<Button
|
|
334
|
+
type="button"
|
|
335
|
+
variant="chrome"
|
|
336
|
+
size="sm"
|
|
337
|
+
className="h-8 gap-1.5"
|
|
338
|
+
disabled={!canCopyMoreVariants}
|
|
339
|
+
onClick={handleCopyMoreVariantsPrompt}
|
|
340
|
+
aria-label="Copy more variations prompt"
|
|
341
|
+
>
|
|
342
|
+
{isMoreVariantsCopied ? "Copied prompt" : "Copy prompt"}
|
|
343
|
+
{copyMoreVariantsIcon}
|
|
344
|
+
</Button>
|
|
345
|
+
</PrototypeComponent>
|
|
346
|
+
</div>
|
|
347
|
+
</div>
|
|
348
|
+
) : null;
|
|
349
|
+
|
|
350
|
+
const explorationOptions = getDesignExplorationVariantOptions(options, baseline);
|
|
351
|
+
const resolvedBaseline = resolveDesignExplorationBaselineOption(baseline);
|
|
352
|
+
|
|
353
|
+
const renderVariantCard = (
|
|
354
|
+
option: DesignExplorationVariantOption<TVariant>,
|
|
355
|
+
{
|
|
356
|
+
isBaseline = false,
|
|
357
|
+
badgeSuffix,
|
|
358
|
+
}: { isBaseline?: boolean; badgeSuffix?: string } = {},
|
|
359
|
+
) => {
|
|
360
|
+
const VariantPreview = renderers[option.value];
|
|
361
|
+
const isActive = option.value === variant;
|
|
362
|
+
const isDefaultOption =
|
|
363
|
+
!isBaseline &&
|
|
364
|
+
defaultVariantValue !== undefined &&
|
|
365
|
+
option.value === defaultVariantValue;
|
|
366
|
+
const optionIdSuffix = badgeSuffix ?? option.value;
|
|
367
|
+
|
|
368
|
+
return (
|
|
369
|
+
<PrototypeComponent
|
|
370
|
+
key={option.value}
|
|
371
|
+
id={`${componentIdPrefix}.overview-option.${optionIdSuffix}`}
|
|
372
|
+
>
|
|
373
|
+
<div className="flex flex-col gap-2">
|
|
374
|
+
<div className="flex items-start justify-between gap-2 px-0.5">
|
|
375
|
+
<div className="flex min-w-0 flex-1 items-center gap-2">
|
|
376
|
+
<p className={cn(cardTitleClass, "min-w-0 truncate")}>
|
|
377
|
+
{option.label}
|
|
378
|
+
</p>
|
|
379
|
+
{isBaseline ? (
|
|
380
|
+
<PrototypeComponent
|
|
381
|
+
id={`${componentIdPrefix}.overview-option.${optionIdSuffix}.baseline`}
|
|
382
|
+
className="tool-variant-original-badge shrink-0"
|
|
383
|
+
>
|
|
384
|
+
<span aria-label="Original state before exploration">
|
|
385
|
+
ORIGINAL
|
|
386
|
+
</span>
|
|
387
|
+
</PrototypeComponent>
|
|
388
|
+
) : null}
|
|
389
|
+
</div>
|
|
390
|
+
|
|
391
|
+
<div className="flex shrink-0 items-center gap-3">
|
|
392
|
+
{isDefaultOption ? (
|
|
393
|
+
<PrototypeComponent
|
|
394
|
+
id={`${componentIdPrefix}.overview-option.${optionIdSuffix}.default`}
|
|
395
|
+
className="tool-variant-default-badge shrink-0"
|
|
396
|
+
>
|
|
397
|
+
<span aria-label="Current default variant">DEFAULT</span>
|
|
398
|
+
</PrototypeComponent>
|
|
399
|
+
) : null}
|
|
400
|
+
{!isBaseline && !isDefaultOption ? (
|
|
401
|
+
<MakeDefaultButton
|
|
402
|
+
componentId={`${componentIdPrefix}.overview-option.${optionIdSuffix}.set-default`}
|
|
403
|
+
optionLabel={option.label}
|
|
404
|
+
optionValue={option.value}
|
|
405
|
+
configFilePath={briefConfigFilePath}
|
|
406
|
+
/>
|
|
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
|
+
{option.rationale ? (
|
|
445
|
+
<p
|
|
446
|
+
className={cn(
|
|
447
|
+
"px-0.5 leading-relaxed",
|
|
448
|
+
isSidebar
|
|
449
|
+
? "text-sm text-[var(--text-secondary)]"
|
|
450
|
+
: "text-muted-foreground text-xs",
|
|
451
|
+
)}
|
|
452
|
+
>
|
|
453
|
+
{option.rationale.good} {option.rationale.bad}
|
|
454
|
+
</p>
|
|
455
|
+
) : null}
|
|
456
|
+
</div>
|
|
457
|
+
</PrototypeComponent>
|
|
458
|
+
);
|
|
459
|
+
};
|
|
460
|
+
|
|
321
461
|
const variantsSectionContent = (
|
|
322
462
|
<section
|
|
323
463
|
className={cn(
|
|
@@ -333,125 +473,13 @@ export function PrototypeDesignBriefPanel<TVariant extends string>({
|
|
|
333
473
|
)}
|
|
334
474
|
>
|
|
335
475
|
<p className={sectionTitleClass}>{variantsTitle}</p>
|
|
336
|
-
{!isSidebar && <p className={sectionDescClass}>{variantsDescription}</p>}
|
|
337
476
|
</div>
|
|
338
477
|
|
|
339
478
|
<div className={cn("flex flex-col", isSidebar ? "gap-6" : "gap-10")}>
|
|
340
|
-
{
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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
|
-
);
|
|
479
|
+
{explorationOptions.map((option) => renderVariantCard(option))}
|
|
480
|
+
{renderVariantCard(resolvedBaseline, {
|
|
481
|
+
isBaseline: true,
|
|
482
|
+
badgeSuffix: "baseline",
|
|
455
483
|
})}
|
|
456
484
|
</div>
|
|
457
485
|
</section>
|
|
@@ -469,6 +497,8 @@ export function PrototypeDesignBriefPanel<TVariant extends string>({
|
|
|
469
497
|
>
|
|
470
498
|
{isSidebar ? (
|
|
471
499
|
<>
|
|
500
|
+
{moreVariantsPromptSection}
|
|
501
|
+
<hr className="border-border" />
|
|
472
502
|
{variantsSectionContent}
|
|
473
503
|
{mobbinSection ? (
|
|
474
504
|
<>
|
|
@@ -276,12 +276,6 @@ $enter-duration: 150ms;
|
|
|
276
276
|
background: rgba(255, 255, 255, 0.08) !important;
|
|
277
277
|
color: rgba(255, 255, 255, 0.55) !important;
|
|
278
278
|
|
|
279
|
-
> :first-child {
|
|
280
|
-
border-radius: 0.375rem !important;
|
|
281
|
-
background: rgba(255, 255, 255, 0.14) !important;
|
|
282
|
-
box-shadow: none !important;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
279
|
[role="tab"] {
|
|
286
280
|
flex: 0 0 auto !important;
|
|
287
281
|
height: 26px !important;
|
|
@@ -293,6 +287,10 @@ $enter-duration: 150ms;
|
|
|
293
287
|
box-shadow: none !important;
|
|
294
288
|
font-size: 0.8125rem !important;
|
|
295
289
|
line-height: 1 !important;
|
|
290
|
+
transition:
|
|
291
|
+
background-color 0.15s ease,
|
|
292
|
+
color 0.15s ease,
|
|
293
|
+
box-shadow 0.15s ease;
|
|
296
294
|
}
|
|
297
295
|
}
|
|
298
296
|
|
|
@@ -305,14 +303,17 @@ $enter-duration: 150ms;
|
|
|
305
303
|
color: rgba(255, 255, 255, 0.55) !important;
|
|
306
304
|
|
|
307
305
|
@media (hover: hover) and (pointer: fine) {
|
|
308
|
-
&:hover:not([data-active]) {
|
|
306
|
+
&:hover:not([data-state="active"]) {
|
|
309
307
|
background: rgba(255, 255, 255, 0.06) !important;
|
|
310
308
|
color: rgba(255, 255, 255, 0.75) !important;
|
|
311
309
|
}
|
|
312
310
|
}
|
|
313
311
|
|
|
314
|
-
&[data-active] {
|
|
315
|
-
|
|
312
|
+
&[data-state="active"] {
|
|
313
|
+
background: rgba(0, 151, 254, 0.22) !important;
|
|
314
|
+
color: #fff !important;
|
|
315
|
+
box-shadow: inset 0 0 0 1px rgba(0, 151, 254, 0.55) !important;
|
|
316
|
+
font-weight: 600 !important;
|
|
316
317
|
}
|
|
317
318
|
}
|
|
318
319
|
|
|
@@ -1338,4 +1339,10 @@ $enter-duration: 150ms;
|
|
|
1338
1339
|
.rationaleLabel {
|
|
1339
1340
|
color: rgba(0, 0, 0, 0.92);
|
|
1340
1341
|
}
|
|
1342
|
+
|
|
1343
|
+
.variantsTab[data-state="active"] {
|
|
1344
|
+
background: rgba(0, 120, 215, 0.12) !important;
|
|
1345
|
+
box-shadow: inset 0 0 0 1px rgba(0, 120, 215, 0.35) !important;
|
|
1346
|
+
color: rgba(0, 0, 0, 0.92) !important;
|
|
1347
|
+
}
|
|
1341
1348
|
}
|