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
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { PrototypeToolDialog } from "@prototype/components/platform-ui/prototype-tool-dialog";
|
|
4
|
+
import { Button } from "@prototype/components/ui/button";
|
|
5
|
+
import { Input } from "@prototype/components/ui/input";
|
|
6
|
+
import { Label } from "@prototype/components/ui/label";
|
|
7
|
+
import { PROTOTYPE_BRIEF_FIELD_BORDER_CLASS } from "@prototype/components/prototypes/prototype-brief-field";
|
|
8
|
+
import { buildLinkSourceCopyText } from "@prototype/lib/prototypes/link-source-prompt";
|
|
9
|
+
import useCopyToClipboard from "@prototype/lib/use-copy-to-clipboard";
|
|
10
|
+
import { useEffect, useId, useState } from "react";
|
|
11
|
+
import { toast } from "sonner";
|
|
12
|
+
|
|
13
|
+
export type PrototypeLinkSourceModalProps = {
|
|
14
|
+
open: boolean;
|
|
15
|
+
onOpenChange: (open: boolean) => void;
|
|
16
|
+
currentSourcePath?: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function PrototypeLinkSourceModal({
|
|
20
|
+
open,
|
|
21
|
+
onOpenChange,
|
|
22
|
+
currentSourcePath,
|
|
23
|
+
}: PrototypeLinkSourceModalProps) {
|
|
24
|
+
const pathFieldId = useId();
|
|
25
|
+
const [sourcePath, setSourcePath] = useState("");
|
|
26
|
+
const [isCopying, setIsCopying] = useState(false);
|
|
27
|
+
const { copy, icon, isCopied } = useCopyToClipboard();
|
|
28
|
+
const canCopy = sourcePath.trim().length > 0;
|
|
29
|
+
const isUpdate = Boolean(currentSourcePath?.trim());
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (!open) {
|
|
33
|
+
setSourcePath("");
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
setSourcePath(currentSourcePath?.trim() ?? "");
|
|
38
|
+
}, [open, currentSourcePath]);
|
|
39
|
+
|
|
40
|
+
const handleCopy = async () => {
|
|
41
|
+
if (!canCopy || isCopying) return;
|
|
42
|
+
|
|
43
|
+
setIsCopying(true);
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
await copy(
|
|
47
|
+
buildLinkSourceCopyText({
|
|
48
|
+
sourcePath,
|
|
49
|
+
currentSourcePath,
|
|
50
|
+
}),
|
|
51
|
+
);
|
|
52
|
+
} catch {
|
|
53
|
+
toast.error("Copy error", {
|
|
54
|
+
description: "Failed to copy link source prompt",
|
|
55
|
+
});
|
|
56
|
+
} finally {
|
|
57
|
+
setIsCopying(false);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<PrototypeToolDialog
|
|
63
|
+
open={open}
|
|
64
|
+
onOpenChange={onOpenChange}
|
|
65
|
+
title={isUpdate ? "Change source app" : "Link source app"}
|
|
66
|
+
description="Set the codebase for which you're making prototypes."
|
|
67
|
+
bodyClassName="gap-4"
|
|
68
|
+
footerClassName="justify-end"
|
|
69
|
+
footer={
|
|
70
|
+
<Button
|
|
71
|
+
type="button"
|
|
72
|
+
variant="chrome"
|
|
73
|
+
size="sm"
|
|
74
|
+
className="h-8 gap-1.5"
|
|
75
|
+
disabled={!canCopy || isCopying}
|
|
76
|
+
onClick={() => {
|
|
77
|
+
void handleCopy();
|
|
78
|
+
}}
|
|
79
|
+
aria-label="Copy link source prompt"
|
|
80
|
+
>
|
|
81
|
+
{isCopied ? "Copied prompt" : isCopying ? "Copying…" : "Copy prompt"}
|
|
82
|
+
{icon}
|
|
83
|
+
</Button>
|
|
84
|
+
}
|
|
85
|
+
>
|
|
86
|
+
<div className="flex flex-col gap-1.5">
|
|
87
|
+
<Label htmlFor={pathFieldId} className="text-foreground">
|
|
88
|
+
Source path, description of folder, or folder name
|
|
89
|
+
</Label>
|
|
90
|
+
<Input
|
|
91
|
+
id={pathFieldId}
|
|
92
|
+
value={sourcePath}
|
|
93
|
+
onChange={(event) => setSourcePath(event.target.value)}
|
|
94
|
+
placeholder="ex. ../cal.diy, cal.diy, or sibling folder to this host app"
|
|
95
|
+
className={PROTOTYPE_BRIEF_FIELD_BORDER_CLASS}
|
|
96
|
+
/>
|
|
97
|
+
</div>
|
|
98
|
+
</PrototypeToolDialog>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
@@ -7,7 +7,7 @@ import * as React from "react";
|
|
|
7
7
|
import { cn } from "@prototype/lib/utils";
|
|
8
8
|
|
|
9
9
|
const buttonVariants = cva(
|
|
10
|
-
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
|
10
|
+
"inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
|
11
11
|
{
|
|
12
12
|
variants: {
|
|
13
13
|
variant: {
|
|
@@ -18,6 +18,8 @@ const buttonVariants = cva(
|
|
|
18
18
|
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
|
19
19
|
secondary:
|
|
20
20
|
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
21
|
+
chrome:
|
|
22
|
+
"border-0 bg-[var(--tool-chrome-button-bg)] text-[var(--tool-chrome-button-text)] shadow-none hover:bg-[var(--tool-chrome-button-bg-hover)] hover:text-[var(--tool-chrome-button-text-hover)]",
|
|
21
23
|
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
22
24
|
link: "text-primary underline-offset-4 hover:underline",
|
|
23
25
|
},
|
|
@@ -4,7 +4,11 @@ import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
|
4
4
|
import { X } from "lucide-react";
|
|
5
5
|
import * as React from "react";
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { isCommentCaptureBlockedTarget } from "@prototype/lib/prototype-comments/core/comment-capture-blocked";
|
|
8
|
+
import {
|
|
9
|
+
getPrototypeDialogPortalContainer,
|
|
10
|
+
getPrototypeToolDialogPortalContainer,
|
|
11
|
+
} from "@prototype/lib/tool-portal";
|
|
8
12
|
import { cn } from "@prototype/lib/utils";
|
|
9
13
|
|
|
10
14
|
import styles from "./dialog.module.scss";
|
|
@@ -14,6 +18,19 @@ const DialogTrigger = DialogPrimitive.Trigger;
|
|
|
14
18
|
const DialogClose = DialogPrimitive.Close;
|
|
15
19
|
const DialogPortal = DialogPrimitive.Portal;
|
|
16
20
|
|
|
21
|
+
function isPrototypeToolOutsideEventTarget(target: EventTarget | null): boolean {
|
|
22
|
+
return target instanceof HTMLElement && isCommentCaptureBlockedTarget(target);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function ignorePrototypeToolOutsideEvent(event: {
|
|
26
|
+
preventDefault: () => void;
|
|
27
|
+
target: EventTarget | null;
|
|
28
|
+
}): void {
|
|
29
|
+
if (isPrototypeToolOutsideEventTarget(event.target)) {
|
|
30
|
+
event.preventDefault();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
17
34
|
const DialogOverlay = React.forwardRef<
|
|
18
35
|
React.ComponentRef<typeof DialogPrimitive.Overlay>,
|
|
19
36
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
|
@@ -30,14 +47,24 @@ const DialogOverlay = React.forwardRef<
|
|
|
30
47
|
));
|
|
31
48
|
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
|
|
32
49
|
|
|
50
|
+
export type DialogPortalScope = "viewport" | "tool";
|
|
51
|
+
|
|
33
52
|
const DialogContent = React.forwardRef<
|
|
34
53
|
React.ComponentRef<typeof DialogPrimitive.Content>,
|
|
35
54
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & {
|
|
36
55
|
showCloseButton?: boolean;
|
|
56
|
+
overlayClassName?: string;
|
|
57
|
+
portalScope?: DialogPortalScope;
|
|
37
58
|
}
|
|
38
|
-
>(({ className, children, showCloseButton = true, ...props }, ref) => (
|
|
39
|
-
<DialogPortal
|
|
40
|
-
|
|
59
|
+
>(({ className, children, showCloseButton = true, overlayClassName, portalScope = "viewport", onPointerDownOutside, onInteractOutside, onFocusOutside, ...props }, ref) => (
|
|
60
|
+
<DialogPortal
|
|
61
|
+
container={
|
|
62
|
+
portalScope === "tool"
|
|
63
|
+
? getPrototypeToolDialogPortalContainer()
|
|
64
|
+
: getPrototypeDialogPortalContainer()
|
|
65
|
+
}
|
|
66
|
+
>
|
|
67
|
+
<DialogOverlay className={overlayClassName} />
|
|
41
68
|
<DialogPrimitive.Content
|
|
42
69
|
ref={ref}
|
|
43
70
|
className={cn(
|
|
@@ -45,6 +72,18 @@ const DialogContent = React.forwardRef<
|
|
|
45
72
|
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg gap-4 border bg-background p-6 shadow-lg sm:rounded-lg",
|
|
46
73
|
className,
|
|
47
74
|
)}
|
|
75
|
+
onPointerDownOutside={(event) => {
|
|
76
|
+
ignorePrototypeToolOutsideEvent(event);
|
|
77
|
+
onPointerDownOutside?.(event);
|
|
78
|
+
}}
|
|
79
|
+
onInteractOutside={(event) => {
|
|
80
|
+
ignorePrototypeToolOutsideEvent(event);
|
|
81
|
+
onInteractOutside?.(event);
|
|
82
|
+
}}
|
|
83
|
+
onFocusOutside={(event) => {
|
|
84
|
+
ignorePrototypeToolOutsideEvent(event);
|
|
85
|
+
onFocusOutside?.(event);
|
|
86
|
+
}}
|
|
48
87
|
{...props}
|
|
49
88
|
>
|
|
50
89
|
{children}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PrototypeGalleryHeader,
|
|
3
|
+
PrototypeGalleryPageLayout,
|
|
4
|
+
} from "@prototype/components/shell/prototype-gallery-shell";
|
|
5
|
+
import { PrototypeComponentLibraryEmptyState } from "@prototype/components/shell/prototype-component-library-empty-state";
|
|
6
|
+
import { formatSourceDirectoryName } from "@prototype/lib/format-source-directory-name";
|
|
7
|
+
import { PROTOTYPE_SOURCE_SURFACE_ATTR } from "@prototype/lib/tool-portal";
|
|
8
|
+
import type { ReactNode } from "react";
|
|
9
|
+
|
|
10
|
+
type PrototypeComponentLibraryPageOptions = {
|
|
11
|
+
title?: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
eyebrow?: string;
|
|
14
|
+
children?: ReactNode;
|
|
15
|
+
sourcePath?: string;
|
|
16
|
+
syncConfigPath?: string;
|
|
17
|
+
componentLibraryPagePath?: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export function createPrototypeComponentLibraryPage(
|
|
21
|
+
options: PrototypeComponentLibraryPageOptions = {},
|
|
22
|
+
) {
|
|
23
|
+
const {
|
|
24
|
+
title = "Component Library",
|
|
25
|
+
description = "Shared UI primitives and building blocks used across prototypes.",
|
|
26
|
+
eyebrow,
|
|
27
|
+
children,
|
|
28
|
+
sourcePath = process.env.SOURCE_PATH,
|
|
29
|
+
syncConfigPath,
|
|
30
|
+
componentLibraryPagePath,
|
|
31
|
+
} = options;
|
|
32
|
+
|
|
33
|
+
const sourceDirectoryName = formatSourceDirectoryName(sourcePath);
|
|
34
|
+
const isEmpty = !children;
|
|
35
|
+
|
|
36
|
+
function ComponentLibraryPage() {
|
|
37
|
+
return (
|
|
38
|
+
<PrototypeGalleryPageLayout
|
|
39
|
+
header={
|
|
40
|
+
<PrototypeGalleryHeader
|
|
41
|
+
eyebrow={eyebrow}
|
|
42
|
+
title={title}
|
|
43
|
+
description={description}
|
|
44
|
+
className="mb-0"
|
|
45
|
+
/>
|
|
46
|
+
}
|
|
47
|
+
>
|
|
48
|
+
{isEmpty ? (
|
|
49
|
+
<div className="px-10 pt-8 pb-16 lg:px-16">
|
|
50
|
+
<PrototypeComponentLibraryEmptyState
|
|
51
|
+
sourcePath={sourceDirectoryName ?? sourcePath}
|
|
52
|
+
syncConfigPath={syncConfigPath}
|
|
53
|
+
componentLibraryPagePath={componentLibraryPagePath}
|
|
54
|
+
/>
|
|
55
|
+
</div>
|
|
56
|
+
) : (
|
|
57
|
+
// Isolate synced host design-system components from the tool chrome
|
|
58
|
+
// theme scoped to [data-prototype-root]. This surface pins neutral
|
|
59
|
+
// product tokens (see packages/prototype/src/styles/globals.css), so
|
|
60
|
+
// previews never inherit tool-chrome --primary/--font-sans. Hosts layer
|
|
61
|
+
// their brand token values by scoping to [data-prototype-source-surface].
|
|
62
|
+
<div
|
|
63
|
+
className="px-10 pt-8 pb-16 lg:px-16"
|
|
64
|
+
{...{ [PROTOTYPE_SOURCE_SURFACE_ATTR]: "" }}
|
|
65
|
+
>
|
|
66
|
+
{children}
|
|
67
|
+
</div>
|
|
68
|
+
)}
|
|
69
|
+
</PrototypeGalleryPageLayout>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return ComponentLibraryPage;
|
|
74
|
+
}
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import "server-only";
|
|
2
2
|
|
|
3
3
|
import { PrototypeGalleryClient } from "@prototype/components/prototype-gallery-client";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
PrototypeGalleryHeader,
|
|
6
|
+
PrototypeGalleryPageLayout,
|
|
7
|
+
} from "@prototype/components/shell/prototype-gallery-shell";
|
|
5
8
|
import { createPrototypeRegistry } from "@prototype/lib/prototypes/create-prototype-registry";
|
|
6
9
|
import type { PrototypeConfig } from "@prototype/lib/prototypes/prototype-config-types";
|
|
7
10
|
|
|
8
11
|
import { readScreenshotManifest } from "../lib/prototypes/screenshot-manifest";
|
|
12
|
+
import { sortPrototypesByCreationDate } from "../lib/prototypes/sort-prototypes-by-creation-date";
|
|
9
13
|
|
|
10
14
|
type PrototypeGalleryPageOptions = {
|
|
11
15
|
title?: string;
|
|
@@ -25,24 +29,17 @@ export function createPrototypeGalleryPage(
|
|
|
25
29
|
} = options;
|
|
26
30
|
|
|
27
31
|
async function PrototypeGallery() {
|
|
28
|
-
const prototypes =
|
|
32
|
+
const [prototypes, screenshotManifest] = await Promise.all([
|
|
33
|
+
Promise.resolve(registry.getAllPrototypes()),
|
|
34
|
+
readScreenshotManifest(),
|
|
35
|
+
]);
|
|
36
|
+
const sortedPrototypes = await sortPrototypesByCreationDate(prototypes);
|
|
29
37
|
const screenshotVersions =
|
|
30
|
-
process.env.NODE_ENV === "development"
|
|
31
|
-
? await readScreenshotManifest()
|
|
32
|
-
: {};
|
|
33
|
-
|
|
34
|
-
if (prototypes.length === 0) {
|
|
35
|
-
return (
|
|
36
|
-
<p className="text-muted-foreground py-12 text-sm">
|
|
37
|
-
No prototypes yet. Add one in{" "}
|
|
38
|
-
<code className="text-foreground">prototype.config.ts</code>.
|
|
39
|
-
</p>
|
|
40
|
-
);
|
|
41
|
-
}
|
|
38
|
+
process.env.NODE_ENV === "development" ? screenshotManifest : {};
|
|
42
39
|
|
|
43
40
|
return (
|
|
44
41
|
<PrototypeGalleryClient
|
|
45
|
-
prototypes={
|
|
42
|
+
prototypes={sortedPrototypes}
|
|
46
43
|
screenshotVersions={screenshotVersions}
|
|
47
44
|
/>
|
|
48
45
|
);
|
|
@@ -50,14 +47,20 @@ export function createPrototypeGalleryPage(
|
|
|
50
47
|
|
|
51
48
|
function HomePage() {
|
|
52
49
|
return (
|
|
53
|
-
<
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
50
|
+
<PrototypeGalleryPageLayout
|
|
51
|
+
header={
|
|
52
|
+
<PrototypeGalleryHeader
|
|
53
|
+
eyebrow={eyebrow}
|
|
54
|
+
title={title}
|
|
55
|
+
description={description}
|
|
56
|
+
className="mb-0"
|
|
57
|
+
/>
|
|
58
|
+
}
|
|
59
|
+
>
|
|
60
|
+
<div className="px-10 pt-8 pb-16 lg:px-16">
|
|
61
|
+
<PrototypeGallery />
|
|
62
|
+
</div>
|
|
63
|
+
</PrototypeGalleryPageLayout>
|
|
61
64
|
);
|
|
62
65
|
}
|
|
63
66
|
|
|
@@ -4,7 +4,6 @@ import { notFound } from "next/navigation";
|
|
|
4
4
|
import { Suspense } from "react";
|
|
5
5
|
|
|
6
6
|
import { PrototypeShell } from "@prototype/components/shell/prototype-shell";
|
|
7
|
-
import { PrototypeDefaultStateMapPage } from "@prototype/components/prototypes/prototype-default-state-map-page";
|
|
8
7
|
import { createPrototypeRegistry } from "@prototype/lib/prototypes/create-prototype-registry";
|
|
9
8
|
import type {
|
|
10
9
|
PrototypeConfig,
|
|
@@ -12,6 +11,7 @@ import type {
|
|
|
12
11
|
} from "@prototype/lib/prototypes/prototype-config-types";
|
|
13
12
|
|
|
14
13
|
import { createPrototypePage } from "./create-prototype-page";
|
|
14
|
+
import { createPrototypeStateMapPage } from "./create-prototype-state-map-page";
|
|
15
15
|
|
|
16
16
|
function matchExtraRoute(
|
|
17
17
|
path: string[],
|
|
@@ -51,6 +51,7 @@ function findExtraRoute(
|
|
|
51
51
|
export function createPrototypeCatchAllPage(config: PrototypeConfig) {
|
|
52
52
|
const registry = createPrototypeRegistry(config);
|
|
53
53
|
const prototypePage = createPrototypePage(config);
|
|
54
|
+
const stateMapPage = createPrototypeStateMapPage(config);
|
|
54
55
|
|
|
55
56
|
function generateStaticParams() {
|
|
56
57
|
const prototypePaths = prototypePage.generateStaticParams().map(({ slug }) => ({
|
|
@@ -98,20 +99,7 @@ export function createPrototypeCatchAllPage(config: PrototypeConfig) {
|
|
|
98
99
|
|
|
99
100
|
if (path.length === 3 && path[0] === "prototypes" && path[2] === "states") {
|
|
100
101
|
const slug = path[1]!;
|
|
101
|
-
|
|
102
|
-
const componentRegistry = registry.getPrototypeComponentRegistryForSlug(slug);
|
|
103
|
-
|
|
104
|
-
if (!prototype) {
|
|
105
|
-
notFound();
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return (
|
|
109
|
-
<Suspense fallback={null}>
|
|
110
|
-
<PrototypeShell slug={slug} componentRegistry={componentRegistry}>
|
|
111
|
-
<PrototypeDefaultStateMapPage slug={slug} />
|
|
112
|
-
</PrototypeShell>
|
|
113
|
-
</Suspense>
|
|
114
|
-
);
|
|
102
|
+
return stateMapPage.default({ params: Promise.resolve({ slug }) });
|
|
115
103
|
}
|
|
116
104
|
|
|
117
105
|
notFound();
|
|
@@ -2,15 +2,22 @@ import type { ComponentType, ReactNode } from "react";
|
|
|
2
2
|
|
|
3
3
|
import { PrototypeGalleryShell } from "@prototype/components/shell/prototype-gallery-shell";
|
|
4
4
|
|
|
5
|
+
import { formatSourceDirectoryName } from "@prototype/lib/format-source-directory-name";
|
|
6
|
+
|
|
5
7
|
import { PrototypeSiteLayoutClient } from "./prototype-site-layout-client";
|
|
6
8
|
|
|
7
9
|
type PrototypeSiteLayoutOptions = {
|
|
8
10
|
toaster?: ReactNode;
|
|
11
|
+
sourceDirectoryName?: string;
|
|
9
12
|
};
|
|
10
13
|
|
|
11
14
|
export function createPrototypeSiteLayout(
|
|
12
15
|
options: PrototypeSiteLayoutOptions = {},
|
|
13
16
|
) {
|
|
17
|
+
const sourcePath = process.env.SOURCE_PATH;
|
|
18
|
+
const sourceDirectoryName =
|
|
19
|
+
options.sourceDirectoryName ?? formatSourceDirectoryName(sourcePath);
|
|
20
|
+
|
|
14
21
|
return function PrototypeSiteLayout({
|
|
15
22
|
children,
|
|
16
23
|
}: Readonly<{
|
|
@@ -18,7 +25,13 @@ export function createPrototypeSiteLayout(
|
|
|
18
25
|
}>) {
|
|
19
26
|
return (
|
|
20
27
|
<PrototypeSiteLayoutClient
|
|
21
|
-
galleryShell={PrototypeGalleryShell as ComponentType<{
|
|
28
|
+
galleryShell={PrototypeGalleryShell as ComponentType<{
|
|
29
|
+
children: ReactNode;
|
|
30
|
+
sourceDirectoryName?: string;
|
|
31
|
+
sourcePath?: string;
|
|
32
|
+
}>}
|
|
33
|
+
sourceDirectoryName={sourceDirectoryName}
|
|
34
|
+
sourcePath={sourcePath}
|
|
22
35
|
toaster={options.toaster}
|
|
23
36
|
>
|
|
24
37
|
{children}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { notFound } from "next/navigation";
|
|
2
|
+
import { Suspense } from "react";
|
|
3
|
+
|
|
4
|
+
import { PrototypeDefaultStateMapPage } from "@prototype/components/prototypes/prototype-default-state-map-page";
|
|
5
|
+
import { PrototypeShell } from "@prototype/components/shell/prototype-shell";
|
|
6
|
+
import { createPrototypeRegistry } from "@prototype/lib/prototypes/create-prototype-registry";
|
|
7
|
+
import type { PrototypeConfig } from "@prototype/lib/prototypes/prototype-config-types";
|
|
8
|
+
|
|
9
|
+
type PrototypeStateMapPageProps = {
|
|
10
|
+
params: Promise<{ slug: string }>;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function createPrototypeStateMapPage(config: PrototypeConfig) {
|
|
14
|
+
const registry = createPrototypeRegistry(config);
|
|
15
|
+
|
|
16
|
+
function generateStaticParams() {
|
|
17
|
+
return registry.getAllPrototypes().map(({ slug }) => ({ slug }));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function PrototypeStateMapPage({ params }: PrototypeStateMapPageProps) {
|
|
21
|
+
const { slug } = await params;
|
|
22
|
+
const prototype = registry.getPrototype(slug);
|
|
23
|
+
const StateMapComponent = registry.getPrototypeStateMapComponent(slug);
|
|
24
|
+
const componentRegistry = registry.getPrototypeComponentRegistryForSlug(slug);
|
|
25
|
+
|
|
26
|
+
if (!prototype) {
|
|
27
|
+
notFound();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<Suspense fallback={null}>
|
|
32
|
+
<PrototypeShell slug={slug} componentRegistry={componentRegistry}>
|
|
33
|
+
{StateMapComponent ? (
|
|
34
|
+
<StateMapComponent />
|
|
35
|
+
) : (
|
|
36
|
+
<PrototypeDefaultStateMapPage slug={slug} />
|
|
37
|
+
)}
|
|
38
|
+
</PrototypeShell>
|
|
39
|
+
</Suspense>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return { default: PrototypeStateMapPage, generateStaticParams };
|
|
44
|
+
}
|
|
@@ -5,24 +5,38 @@ import type { ComponentType, ReactNode } from "react";
|
|
|
5
5
|
|
|
6
6
|
type PrototypeSiteLayoutClientProps = {
|
|
7
7
|
children: ReactNode;
|
|
8
|
-
galleryShell: ComponentType<{
|
|
8
|
+
galleryShell: ComponentType<{
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
sourceDirectoryName?: string;
|
|
11
|
+
sourcePath?: string;
|
|
12
|
+
}>;
|
|
13
|
+
sourceDirectoryName?: string;
|
|
14
|
+
sourcePath?: string;
|
|
9
15
|
toaster: ReactNode;
|
|
10
16
|
};
|
|
11
17
|
|
|
12
18
|
export function PrototypeSiteLayoutClient({
|
|
13
19
|
children,
|
|
14
20
|
galleryShell: GalleryShell,
|
|
21
|
+
sourceDirectoryName,
|
|
22
|
+
sourcePath,
|
|
15
23
|
toaster,
|
|
16
24
|
}: PrototypeSiteLayoutClientProps) {
|
|
17
25
|
const pathname = usePathname();
|
|
18
|
-
const
|
|
26
|
+
const isGalleryRoute =
|
|
27
|
+
pathname === "/" || pathname.startsWith("/component-library");
|
|
19
28
|
|
|
20
|
-
if (
|
|
29
|
+
if (isGalleryRoute) {
|
|
21
30
|
return (
|
|
22
|
-
|
|
23
|
-
<GalleryShell
|
|
31
|
+
<div className="bg-[var(--bg-main)] flex h-svh min-h-svh w-full flex-col overflow-hidden">
|
|
32
|
+
<GalleryShell
|
|
33
|
+
sourceDirectoryName={sourceDirectoryName}
|
|
34
|
+
sourcePath={sourcePath}
|
|
35
|
+
>
|
|
36
|
+
{children}
|
|
37
|
+
</GalleryShell>
|
|
24
38
|
{toaster}
|
|
25
|
-
|
|
39
|
+
</div>
|
|
26
40
|
);
|
|
27
41
|
}
|
|
28
42
|
|
|
@@ -12,9 +12,30 @@ export function withPrototype(
|
|
|
12
12
|
|
|
13
13
|
return {
|
|
14
14
|
...nextConfig,
|
|
15
|
+
// Next.js 16 blocks cross-origin dev asset/HMR requests by default.
|
|
16
|
+
// Include ngrok (mobile testing) and common local host aliases (Glass browser, 127.0.0.1).
|
|
17
|
+
allowedDevOrigins: [
|
|
18
|
+
...(nextConfig.allowedDevOrigins ?? []),
|
|
19
|
+
"127.0.0.1",
|
|
20
|
+
"[::1]",
|
|
21
|
+
"*.ngrok-free.app",
|
|
22
|
+
"*.ngrok-free.dev",
|
|
23
|
+
"*.ngrok.app",
|
|
24
|
+
"*.ngrok.io",
|
|
25
|
+
"*.ngrok.dev",
|
|
26
|
+
],
|
|
15
27
|
transpilePackages: [
|
|
16
28
|
...(nextConfig.transpilePackages ?? []),
|
|
17
29
|
"proto-plugin",
|
|
18
30
|
],
|
|
31
|
+
images: {
|
|
32
|
+
...nextConfig.images,
|
|
33
|
+
localPatterns: [
|
|
34
|
+
{
|
|
35
|
+
pathname: "/prototypes/screenshots/**",
|
|
36
|
+
},
|
|
37
|
+
...(nextConfig.images?.localPatterns ?? []),
|
|
38
|
+
],
|
|
39
|
+
},
|
|
19
40
|
};
|
|
20
41
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export { definePrototypeConfig } from "./config/define-prototype-config";
|
|
2
|
+
export { createPrototypeGalleryPage } from "./config/create-prototype-gallery-page";
|
|
3
|
+
export { createPrototypeComponentLibraryPage } from "./config/create-prototype-component-library-page";
|
|
2
4
|
export { createPrototypePage } from "./config/create-prototype-page";
|
|
5
|
+
export { createPrototypeStateMapPage } from "./config/create-prototype-state-map-page";
|
|
3
6
|
export { createPrototypeSiteLayout } from "./config/create-prototype-site-layout";
|
|
4
7
|
export { withPrototype } from "./config/with-prototype";
|
|
5
8
|
|
|
@@ -44,6 +47,12 @@ export type { PrototypeVercelPreviewModalProps } from "./components/prototypes/p
|
|
|
44
47
|
export { PrototypeTweaks } from "./components/prototypes/prototype-tweaks";
|
|
45
48
|
export type { PrototypeTweakConfig } from "./components/prototypes/prototype-tweaks";
|
|
46
49
|
|
|
50
|
+
export {
|
|
51
|
+
PROTOTYPE_PAGE_CLASS,
|
|
52
|
+
PROTOTYPE_SCROLL_CONTAINER_CLASS,
|
|
53
|
+
PROTOTYPE_VIEW_SHELL_CLASS,
|
|
54
|
+
} from "./lib/prototypes/prototype-layout-classes";
|
|
55
|
+
|
|
47
56
|
export { usePrototypeComments } from "./lib/prototypes/use-prototype-comments";
|
|
48
57
|
|
|
49
58
|
export {
|
|
@@ -51,19 +60,51 @@ export {
|
|
|
51
60
|
readPersistedPrototypeLiveState,
|
|
52
61
|
} from "./lib/prototypes/use-persisted-local-state";
|
|
53
62
|
|
|
54
|
-
export type {
|
|
63
|
+
export type {
|
|
64
|
+
DesignExplorationBaselineOption,
|
|
65
|
+
DesignExplorationConfig,
|
|
66
|
+
} from "./lib/prototypes/design-exploration-types";
|
|
55
67
|
|
|
56
68
|
export {
|
|
57
69
|
buildCreateDesignExplorationCopyText,
|
|
58
70
|
buildCreateDesignExplorationPrompt,
|
|
71
|
+
buildMoreDesignExplorationVariantsCopyText,
|
|
72
|
+
buildMoreDesignExplorationVariantsPrompt,
|
|
59
73
|
buildResetBriefDefaultsPrompt,
|
|
74
|
+
DESIGN_EXPLORATION_BASELINE_LABEL,
|
|
60
75
|
getAdjacentDesignExplorationVariant,
|
|
76
|
+
getDesignExplorationDisplayOptions,
|
|
77
|
+
getDesignExplorationVariantOptions,
|
|
78
|
+
resolveDesignExplorationBaselineOption,
|
|
61
79
|
} from "./lib/prototypes/design-exploration-types";
|
|
62
80
|
|
|
81
|
+
export { buildDesignExplorationRenderers } from "./lib/prototypes/build-design-exploration-renderers";
|
|
82
|
+
|
|
83
|
+
export {
|
|
84
|
+
DesignExplorationVariantPreviewShell,
|
|
85
|
+
type DesignExplorationVariantPreviewLayout,
|
|
86
|
+
type DesignExplorationVariantPreviewShellProps,
|
|
87
|
+
} from "./components/prototypes/design-exploration-variant-preview-shell";
|
|
88
|
+
|
|
63
89
|
export {
|
|
64
90
|
buildCreatePreviewStateCopyText,
|
|
65
91
|
buildCreatePreviewStatePrompt,
|
|
66
92
|
} from "./lib/prototypes/prototype-preview-state-types";
|
|
93
|
+
|
|
94
|
+
export {
|
|
95
|
+
buildCreatePrototypeCopyText,
|
|
96
|
+
buildCreatePrototypePrompt,
|
|
97
|
+
suggestPrototypeSlug,
|
|
98
|
+
} from "./lib/prototypes/create-prototype-prompt";
|
|
99
|
+
|
|
100
|
+
export {
|
|
101
|
+
buildPopulateComponentLibraryCopyText,
|
|
102
|
+
buildPopulateComponentLibraryPrompt,
|
|
103
|
+
} from "./lib/prototypes/populate-component-library-prompt";
|
|
104
|
+
export {
|
|
105
|
+
buildLinkSourceCopyText,
|
|
106
|
+
buildLinkSourcePrompt,
|
|
107
|
+
} from "./lib/prototypes/link-source-prompt";
|
|
67
108
|
export type { PrototypePreviewState } from "./lib/prototypes/prototype-preview-state-types";
|
|
68
109
|
|
|
69
110
|
export {
|
|
@@ -113,6 +154,7 @@ export {
|
|
|
113
154
|
PrototypeGalleryShell,
|
|
114
155
|
PrototypeGalleryHeader,
|
|
115
156
|
PrototypeGalleryPage,
|
|
157
|
+
PrototypeGalleryPageLayout,
|
|
116
158
|
} from "./components/shell/prototype-gallery-shell";
|
|
117
159
|
|
|
118
160
|
export { screenshotSrc } from "./lib/prototypes/screenshot-src";
|
|
@@ -225,8 +267,11 @@ export {
|
|
|
225
267
|
PROTOTYPE_PREVIEW_STAGE_ID,
|
|
226
268
|
PROTOTYPE_ROOT_ID,
|
|
227
269
|
PROTOTYPE_SCREENSHOT_ATTR,
|
|
270
|
+
PROTOTYPE_SOURCE_SURFACE_ATTR,
|
|
228
271
|
PROTOTYPE_VIEWPORT_ID,
|
|
229
272
|
getPrototypeChromeRoot,
|
|
273
|
+
getPrototypeDialogPortalContainer,
|
|
274
|
+
getPrototypeToolDialogPortalContainer,
|
|
230
275
|
getPrototypePreviewStage,
|
|
231
276
|
getPrototypeScreenshotRoot,
|
|
232
277
|
} from "./lib/tool-portal";
|
|
@@ -246,15 +291,6 @@ export {
|
|
|
246
291
|
useChangelogMetaOptional,
|
|
247
292
|
} from "./lib/prototypes/changelog-meta-context";
|
|
248
293
|
|
|
249
|
-
export {
|
|
250
|
-
prototypeDesignExplorationDefaultRedisKey,
|
|
251
|
-
parseDesignExplorationDefault,
|
|
252
|
-
isValidDesignExplorationDefaultRecord,
|
|
253
|
-
} from "./lib/prototypes/design-exploration-default";
|
|
254
|
-
export type { DesignExplorationDefaultRecord } from "./lib/prototypes/design-exploration-default";
|
|
255
|
-
export { createDesignExplorationDefaultStorageAdapter } from "./lib/prototypes/design-exploration-default-storage";
|
|
256
|
-
export { useDesignExplorationDefault } from "./lib/prototypes/use-design-exploration-default";
|
|
257
|
-
|
|
258
294
|
export {
|
|
259
295
|
filterAnnotationsByChannel,
|
|
260
296
|
getChangelogRootAnnotations,
|
|
@@ -276,10 +312,18 @@ export type { PrototypeSpecPanelContentProps } from "./components/prototypes/pro
|
|
|
276
312
|
export {
|
|
277
313
|
PR_TARGET_HIGHLIGHT_BORDER,
|
|
278
314
|
PR_TARGET_HIGHLIGHT_FILL,
|
|
315
|
+
PR_SPLIT_WIREFRAME_FRAME_CLASS,
|
|
279
316
|
} from "./lib/pr-split/pr-split-highlight";
|
|
280
317
|
|
|
281
318
|
export { buildPrSplitPrototypeUrl } from "./lib/pr-split/build-pr-split-prototype-url";
|
|
282
319
|
export { buildPrAgentPrompt } from "./lib/pr-split/build-pr-agent-prompt";
|
|
320
|
+
export {
|
|
321
|
+
buildPrSplitPlanPrompt,
|
|
322
|
+
buildPrSplitPlanCopyText,
|
|
323
|
+
} from "./lib/pr-split/build-pr-split-plan-prompt";
|
|
324
|
+
export type { PrSplitPlanPromptOptions } from "./lib/pr-split/build-pr-split-plan-prompt";
|
|
325
|
+
|
|
326
|
+
export { PrototypeSpecPanelEmptyState } from "./components/prototypes/prototype-spec-panel-empty-state";
|
|
283
327
|
|
|
284
328
|
export type {
|
|
285
329
|
PrSplitEntry,
|