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.
- package/AGENTS.md +350 -0
- package/README.md +279 -0
- package/components.json +18 -0
- package/package.json +73 -0
- package/scripts/download-mobbin-reference-images.mjs +112 -0
- package/scripts/lib/host-config.example.sh +48 -0
- package/scripts/lib/host-config.sh +25 -0
- package/scripts/lib/resolve-host-root.mjs +18 -0
- package/scripts/lib/source-path.sh +49 -0
- package/scripts/link-source-db.sh +128 -0
- package/scripts/link-source.sh +40 -0
- package/scripts/publish-package.sh +147 -0
- package/scripts/share-prototype.sh +279 -0
- package/scripts/sync-from-source.sh +74 -0
- package/scripts/verify-prototype-component-ids.mjs +151 -0
- package/scripts/verify-prototype-preview-states.mjs +135 -0
- package/src/app.ts +4 -0
- package/src/components/platform-ui/code-block.tsx +21 -0
- package/src/components/platform-ui/controls-panel-options.tsx +71 -0
- package/src/components/platform-ui/controls-panel-select.tsx +438 -0
- package/src/components/platform-ui/empty-state.tsx +15 -0
- package/src/components/platform-ui/icon-button.tsx +18 -0
- package/src/components/platform-ui/panel-select.tsx +123 -0
- package/src/components/platform-ui/panel.tsx +97 -0
- package/src/components/platform-ui/review-dropdown-menu.tsx +35 -0
- package/src/components/platform-ui/sidebar.tsx +293 -0
- package/src/components/platform-ui/toolbar-icon-button.tsx +34 -0
- package/src/components/platform-ui/toolbar.tsx +37 -0
- package/src/components/prototype-gallery-client.tsx +57 -0
- package/src/components/prototype-provider.tsx +20 -0
- package/src/components/prototypes/mini-pill-label.tsx +37 -0
- package/src/components/prototypes/prototype-change-log-panel.tsx +207 -0
- package/src/components/prototypes/prototype-comment-provider.tsx +61 -0
- package/src/components/prototypes/prototype-comment-toolbar.tsx +65 -0
- package/src/components/prototypes/prototype-control.tsx +31 -0
- package/src/components/prototypes/prototype-controls.tsx +553 -0
- package/src/components/prototypes/prototype-create-exploration-modal.tsx +117 -0
- package/src/components/prototypes/prototype-create-state-modal.tsx +117 -0
- package/src/components/prototypes/prototype-default-state-map-page.tsx +66 -0
- package/src/components/prototypes/prototype-design-brief-modal.tsx +128 -0
- package/src/components/prototypes/prototype-design-brief-panel.tsx +525 -0
- package/src/components/prototypes/prototype-design-context-panel.tsx +65 -0
- package/src/components/prototypes/prototype-floating-pill.module.scss +1341 -0
- package/src/components/prototypes/prototype-mobbin-gallery.tsx +330 -0
- package/src/components/prototypes/prototype-reference-docs.tsx +222 -0
- package/src/components/prototypes/prototype-review-chrome.module.scss +630 -0
- package/src/components/prototypes/prototype-review-chrome.tsx +1241 -0
- package/src/components/prototypes/prototype-review-dropdown-menu-item.tsx +1 -0
- package/src/components/prototypes/prototype-review-dropdown-menu-separator.tsx +1 -0
- package/src/components/prototypes/prototype-review-panel-menu-item.tsx +1 -0
- package/src/components/prototypes/prototype-review-panel-menu-separator.tsx +1 -0
- package/src/components/prototypes/prototype-review-panel-select.tsx +4 -0
- package/src/components/prototypes/prototype-review-sidebar.tsx +167 -0
- package/src/components/prototypes/prototype-share-link-restore.tsx +75 -0
- package/src/components/prototypes/prototype-share-mode.tsx +206 -0
- package/src/components/prototypes/prototype-spec-panel-content.tsx +538 -0
- package/src/components/prototypes/prototype-state-canvas-overlay.module.scss +336 -0
- package/src/components/prototypes/prototype-state-canvas-overlay.tsx +1023 -0
- package/src/components/prototypes/prototype-state-canvas.tsx +38 -0
- package/src/components/prototypes/prototype-state-screenshot-preview.tsx +47 -0
- package/src/components/prototypes/prototype-target.tsx +120 -0
- package/src/components/prototypes/prototype-tweak-highlight.tsx +132 -0
- package/src/components/prototypes/prototype-tweaks.tsx +48 -0
- package/src/components/prototypes/prototype-variant-explorer.tsx +347 -0
- package/src/components/prototypes/prototype-variant-rationale-panel.tsx +49 -0
- package/src/components/prototypes/prototype-variant-tabs.tsx +207 -0
- package/src/components/prototypes/prototype-vercel-preview-modal.tsx +120 -0
- package/src/components/prototypes/state-map-annotation-tooltip.module.scss +89 -0
- package/src/components/prototypes/state-map-annotation-tooltip.tsx +137 -0
- package/src/components/prototypes/state-map-wireframe-highlight.module.scss +25 -0
- package/src/components/prototypes/state-map-wireframe-highlight.tsx +82 -0
- package/src/components/shell/prototype-gallery-shell.tsx +79 -0
- package/src/components/shell/prototype-screenshot-capture.tsx +45 -0
- package/src/components/shell/prototype-shell.module.scss +194 -0
- package/src/components/shell/prototype-shell.tsx +160 -0
- package/src/components/shell/prototype-state-screenshot-capture.tsx +68 -0
- package/src/components/ui/button.tsx +58 -0
- package/src/components/ui/dialog.module.scss +81 -0
- package/src/components/ui/dialog.tsx +127 -0
- package/src/components/ui/dropdown-menu.tsx +200 -0
- package/src/components/ui/input.tsx +22 -0
- package/src/components/ui/label.tsx +26 -0
- package/src/components/ui/option-select.tsx +81 -0
- package/src/components/ui/scroll-area.tsx +48 -0
- package/src/components/ui/separator.tsx +31 -0
- package/src/components/ui/slider.tsx +28 -0
- package/src/components/ui/switch.tsx +29 -0
- package/src/components/ui/tabs.tsx +55 -0
- package/src/components/ui/toggle-group.tsx +80 -0
- package/src/components/ui/tooltip.tsx +32 -0
- package/src/config/create-prototype-gallery-page.tsx +65 -0
- package/src/config/create-prototype-host-app.tsx +121 -0
- package/src/config/create-prototype-page.tsx +41 -0
- package/src/config/create-prototype-site-layout.tsx +28 -0
- package/src/config/define-prototype-config.ts +7 -0
- package/src/config/prototype-icon.tsx +25 -0
- package/src/config/prototype-site-layout-client.tsx +35 -0
- package/src/config/with-prototype.ts +20 -0
- package/src/index.ts +303 -0
- package/src/lib/pr-split/build-pr-agent-prompt.ts +68 -0
- package/src/lib/pr-split/build-pr-split-prototype-url.ts +12 -0
- package/src/lib/pr-split/pr-split-highlight.ts +6 -0
- package/src/lib/pr-split/pr-split-types.ts +58 -0
- package/src/lib/pr-split/resolve-source-preview-url.ts +45 -0
- package/src/lib/pr-split/use-pr-vercel-previews.ts +69 -0
- package/src/lib/prototype-comments/core/annotation-channel.ts +41 -0
- package/src/lib/prototype-comments/core/annotation-status.ts +60 -0
- package/src/lib/prototype-comments/core/annotation-target.ts +567 -0
- package/src/lib/prototype-comments/core/capture-theme.ts +41 -0
- package/src/lib/prototype-comments/core/capture.ts +11 -0
- package/src/lib/prototype-comments/core/comment-capture-blocked.ts +47 -0
- package/src/lib/prototype-comments/core/comment-threads.ts +52 -0
- package/src/lib/prototype-comments/core/element-identification.ts +615 -0
- package/src/lib/prototype-comments/core/export.ts +35 -0
- package/src/lib/prototype-comments/core/freeze-animations.ts +266 -0
- package/src/lib/prototype-comments/core/identify-element.ts +76 -0
- package/src/lib/prototype-comments/core/normalize-viewport.ts +35 -0
- package/src/lib/prototype-comments/core/react-detection.ts +704 -0
- package/src/lib/prototype-comments/core/restore.ts +7 -0
- package/src/lib/prototype-comments/core/screenshot.ts +428 -0
- package/src/lib/prototype-comments/core/source-location.ts +906 -0
- package/src/lib/prototype-comments/core/storage.ts +80 -0
- package/src/lib/prototype-comments/core/types.ts +79 -0
- package/src/lib/prototype-comments/core/validation.ts +17 -0
- package/src/lib/prototype-comments/hooks/useLayoutShift.ts +61 -0
- package/src/lib/prototype-comments/index.ts +58 -0
- package/src/lib/prototype-comments/react/CommentCaptureToolbar.tsx +1398 -0
- package/src/lib/prototype-comments/react/CommentProvider.tsx +298 -0
- package/src/lib/prototype-comments/scss.d.ts +10 -0
- package/src/lib/prototype-comments/ui/CommentAnnotationOverlay.tsx +253 -0
- package/src/lib/prototype-comments/ui/CommentDetailPage.tsx +56 -0
- package/src/lib/prototype-comments/ui/CommentHeaderCount.tsx +28 -0
- package/src/lib/prototype-comments/ui/CommentThread.tsx +331 -0
- package/src/lib/prototype-comments/ui/CommentsGrid.tsx +780 -0
- package/src/lib/prototype-comments/ui/CommentsListPage.tsx +25 -0
- package/src/lib/prototype-comments/ui/CommentsSidebar.tsx +77 -0
- package/src/lib/prototype-comments/ui/annotation-marker/index.tsx +200 -0
- package/src/lib/prototype-comments/ui/annotation-marker/styles.module.scss +194 -0
- package/src/lib/prototype-comments/ui/annotation-popup/index.tsx +335 -0
- package/src/lib/prototype-comments/ui/annotation-popup/styles.module.scss +469 -0
- package/src/lib/prototype-comments/ui/capture-styles.module.scss +2225 -0
- package/src/lib/prototype-comments/ui/comment-highlight.ts +48 -0
- package/src/lib/prototype-comments/ui/comment-thread.module.scss +203 -0
- package/src/lib/prototype-comments/ui/comments-grid-card.module.scss +253 -0
- package/src/lib/prototype-comments/ui/comments-sidebar.module.scss +123 -0
- package/src/lib/prototype-comments/ui/icon-transitions.module.scss +51 -0
- package/src/lib/prototype-comments/ui/icons.tsx +1086 -0
- package/src/lib/prototype-comments/ui/is-comment-detail-path.ts +7 -0
- package/src/lib/prototypes/capture-prototype-viewport.ts +32 -0
- package/src/lib/prototypes/changelog-meta-context.tsx +162 -0
- package/src/lib/prototypes/changelog-meta-storage.ts +62 -0
- package/src/lib/prototypes/changelog-meta.ts +95 -0
- package/src/lib/prototypes/create-prototype-registry.ts +74 -0
- package/src/lib/prototypes/create-state-screenshot-thumbnail.ts +46 -0
- package/src/lib/prototypes/design-exploration-default-storage.ts +68 -0
- package/src/lib/prototypes/design-exploration-default.ts +51 -0
- package/src/lib/prototypes/design-exploration-types.ts +203 -0
- package/src/lib/prototypes/prototype-comment-registry.tsx +145 -0
- package/src/lib/prototypes/prototype-comment-review-bridge.tsx +40 -0
- package/src/lib/prototypes/prototype-comment-review-state.ts +57 -0
- package/src/lib/prototypes/prototype-component-registry.ts +40 -0
- package/src/lib/prototypes/prototype-config-types.ts +34 -0
- package/src/lib/prototypes/prototype-preview-state-registry.ts +443 -0
- package/src/lib/prototypes/prototype-preview-state-types.ts +89 -0
- package/src/lib/prototypes/prototype-review-context.tsx +905 -0
- package/src/lib/prototypes/prototype-review-url.ts +134 -0
- package/src/lib/prototypes/prototype-share-link.ts +175 -0
- package/src/lib/prototypes/prototype-state-canvas-constants.ts +118 -0
- package/src/lib/prototypes/prototype-state-canvas-types.ts +159 -0
- package/src/lib/prototypes/prototype-state-screenshot.ts +39 -0
- package/src/lib/prototypes/prototype-tweak-registry.ts +96 -0
- package/src/lib/prototypes/prototype-tweak-types.ts +31 -0
- package/src/lib/prototypes/reference-docs.ts +137 -0
- package/src/lib/prototypes/screenshot-manifest.ts +33 -0
- package/src/lib/prototypes/screenshot-src.ts +7 -0
- package/src/lib/prototypes/share-command.ts +8 -0
- package/src/lib/prototypes/state-map-annotation.ts +70 -0
- package/src/lib/prototypes/use-design-exploration-default.ts +114 -0
- package/src/lib/prototypes/use-persisted-local-state.ts +140 -0
- package/src/lib/prototypes/use-prototype-comments.ts +63 -0
- package/src/lib/prototypes/use-prototype-mobile-viewport-frame.ts +46 -0
- package/src/lib/prototypes/use-prototype-state-screenshot-src.ts +71 -0
- package/src/lib/prototypes/use-prototype-tool-theme.tsx +115 -0
- package/src/lib/prototypes/use-prototype-viewport-frame.ts +34 -0
- package/src/lib/prototypes/use-register-prototype-reference-docs.ts +16 -0
- package/src/lib/prototypes/use-snap-corner-position.ts +417 -0
- package/src/lib/prototypes/use-toolbar-hover-panel.ts +98 -0
- package/src/lib/prototypes/variant-set-lucide-icon.tsx +55 -0
- package/src/lib/tool-portal.ts +33 -0
- package/src/lib/use-copy-to-clipboard.ts +81 -0
- package/src/lib/utils.ts +6 -0
- package/src/lib/vercel-preview/parse-vercel-github-comment.ts +81 -0
- package/src/server/create-host-api-route.ts +100 -0
- package/src/server/create-prototype-api-routes.ts +428 -0
- package/src/server/gallery/create-gallery-api.ts +56 -0
- package/src/server/gallery/folders-route.ts +156 -0
- package/src/server/gallery/gallery-env.ts +39 -0
- package/src/server/gallery/gallery-route.ts +150 -0
- package/src/server/gallery/gallery-types.ts +15 -0
- package/src/server/gallery/image-route.ts +59 -0
- package/src/server/gallery/item-route.ts +92 -0
- package/src/server/pr-vercel-preview-route.ts +162 -0
- package/src/server/redis/client.ts +55 -0
- package/src/server/redis/prototype-changelog-meta.ts +31 -0
- package/src/server/redis/prototype-comments.ts +92 -0
- package/src/server/redis/prototype-design-exploration-default.ts +32 -0
- package/src/server.ts +51 -0
- package/src/styles/globals.css +624 -0
- 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
|
+
}
|