proto-plugin 0.1.1 → 0.1.3
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 +48 -6
- package/package.json +2 -1
- package/scripts/lib/host-config.example.sh +1 -1
- 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 +59 -55
- package/src/components/prototypes/prototype-comment-provider.tsx +0 -1
- 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-chrome.tsx +21 -2
- package/src/components/prototypes/prototype-review-sidebar.tsx +7 -37
- 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/shell/prototype-plugin-update-popup.tsx +74 -0
- package/src/components/ui/button.tsx +4 -2
- package/src/components/ui/dialog.tsx +44 -5
- package/src/components/ui/dropdown-menu.tsx +2 -2
- package/src/components/ui/input.tsx +1 -1
- package/src/components/ui/label.tsx +1 -1
- package/src/components/ui/option-select.tsx +3 -3
- package/src/components/ui/scroll-area.tsx +1 -1
- package/src/components/ui/separator.tsx +1 -1
- package/src/components/ui/slider.tsx +1 -1
- package/src/components/ui/switch.tsx +1 -1
- package/src/components/ui/tabs.tsx +1 -1
- package/src/components/ui/toggle-group.tsx +1 -1
- package/src/components/ui/tooltip.tsx +2 -2
- 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 +23 -6
- package/src/config/with-prototype.ts +54 -0
- package/src/index.ts +71 -11
- 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/changelog-description-prompt.ts +58 -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/proto-plugin-version.ts +19 -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/upgrade-proto-plugin-prompt.ts +40 -0
- package/src/lib/prototypes/use-proto-plugin-version-check.ts +67 -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 +14 -16
- package/src/server/create-prototype-api-routes.ts +0 -115
- package/src/server/proto-plugin-version-route.ts +145 -0
- package/src/server/prototype-storage-status-route.ts +29 -0
- package/src/server.ts +2 -8
- package/src/styles/globals.css +357 -69
- 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
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Button } from "@prototype/components/ui/button";
|
|
4
|
+
import { buildUpgradeProtoPluginCopyText } from "@prototype/lib/prototypes/upgrade-proto-plugin-prompt";
|
|
5
|
+
import { useProtoPluginVersionCheck } from "@prototype/lib/prototypes/use-proto-plugin-version-check";
|
|
6
|
+
import useCopyToClipboard from "@prototype/lib/use-copy-to-clipboard";
|
|
7
|
+
import { cn } from "@prototype/lib/utils";
|
|
8
|
+
import { X } from "lucide-react";
|
|
9
|
+
import { toast } from "sonner";
|
|
10
|
+
|
|
11
|
+
export function PrototypePluginUpdatePopup() {
|
|
12
|
+
const { status, shouldShowPopup, dismiss } = useProtoPluginVersionCheck();
|
|
13
|
+
const { copy, icon, isCopied } = useCopyToClipboard();
|
|
14
|
+
|
|
15
|
+
if (!shouldShowPopup || !status?.installed || !status.latest) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const handleCopy = async () => {
|
|
20
|
+
try {
|
|
21
|
+
await copy(
|
|
22
|
+
buildUpgradeProtoPluginCopyText({
|
|
23
|
+
installed: status.installed!,
|
|
24
|
+
latest: status.latest!,
|
|
25
|
+
}),
|
|
26
|
+
);
|
|
27
|
+
} catch {
|
|
28
|
+
toast.error("Copy error", {
|
|
29
|
+
description: "Failed to copy upgrade prompt",
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div
|
|
36
|
+
role="status"
|
|
37
|
+
aria-live="polite"
|
|
38
|
+
className={cn(
|
|
39
|
+
"border-border bg-card ring-border pointer-events-auto fixed bottom-4 left-4 z-[1050] flex w-[min(20rem,calc(100vw-2rem))] flex-col gap-3 rounded-xl border p-4 shadow-lg ring-[0.5px]",
|
|
40
|
+
)}
|
|
41
|
+
>
|
|
42
|
+
<div className="flex items-start justify-between gap-2">
|
|
43
|
+
<div className="min-w-0 flex-1">
|
|
44
|
+
<p className="text-sm font-medium text-foreground">Update proto-plugin</p>
|
|
45
|
+
<p className="mt-1 text-sm text-muted-foreground">
|
|
46
|
+
v{status.latest} is available. You're on v{status.installed}.
|
|
47
|
+
</p>
|
|
48
|
+
</div>
|
|
49
|
+
<button
|
|
50
|
+
type="button"
|
|
51
|
+
aria-label="Dismiss update notification"
|
|
52
|
+
className="text-muted-foreground hover:text-foreground shrink-0 rounded p-1 transition-colors duration-200 ease"
|
|
53
|
+
onClick={dismiss}
|
|
54
|
+
>
|
|
55
|
+
<X size={14} />
|
|
56
|
+
</button>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<Button
|
|
60
|
+
type="button"
|
|
61
|
+
variant="outline"
|
|
62
|
+
size="sm"
|
|
63
|
+
className="h-8 w-fit cursor-pointer gap-1.5"
|
|
64
|
+
onClick={() => {
|
|
65
|
+
void handleCopy();
|
|
66
|
+
}}
|
|
67
|
+
aria-label="Copy upgrade prompt"
|
|
68
|
+
>
|
|
69
|
+
{isCopied ? "Copied prompt" : "Copy prompt"}
|
|
70
|
+
{icon}
|
|
71
|
+
</Button>
|
|
72
|
+
</div>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
@@ -4,10 +4,10 @@ import { Slot } from "@radix-ui/react-slot";
|
|
|
4
4
|
import { cva, type VariantProps } from "class-variance-authority";
|
|
5
5
|
import * as React from "react";
|
|
6
6
|
|
|
7
|
-
import { cn } from "
|
|
7
|
+
import { cn } from "../../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,8 +4,12 @@ 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 {
|
|
8
|
-
import {
|
|
7
|
+
import { isCommentCaptureBlockedTarget } from "../../lib/prototype-comments/core/comment-capture-blocked";
|
|
8
|
+
import {
|
|
9
|
+
getPrototypeDialogPortalContainer,
|
|
10
|
+
getPrototypeToolDialogPortalContainer,
|
|
11
|
+
} from "../../lib/tool-portal";
|
|
12
|
+
import { cn } from "../../lib/utils";
|
|
9
13
|
|
|
10
14
|
import styles from "./dialog.module.scss";
|
|
11
15
|
|
|
@@ -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}
|
|
@@ -4,8 +4,8 @@ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
|
|
|
4
4
|
import { Check, ChevronRight, Circle } from "lucide-react";
|
|
5
5
|
import * as React from "react";
|
|
6
6
|
|
|
7
|
-
import { getPrototypePortalContainer } from "
|
|
8
|
-
import { cn } from "
|
|
7
|
+
import { getPrototypePortalContainer } from "../../lib/tool-portal";
|
|
8
|
+
import { cn } from "../../lib/utils";
|
|
9
9
|
|
|
10
10
|
const DropdownMenu = DropdownMenuPrimitive.Root;
|
|
11
11
|
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
|
|
@@ -4,7 +4,7 @@ import * as LabelPrimitive from "@radix-ui/react-label";
|
|
|
4
4
|
import { cva, type VariantProps } from "class-variance-authority";
|
|
5
5
|
import * as React from "react";
|
|
6
6
|
|
|
7
|
-
import { cn } from "
|
|
7
|
+
import { cn } from "../../lib/utils";
|
|
8
8
|
|
|
9
9
|
const labelVariants = cva(
|
|
10
10
|
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
|
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
import { ChevronDown } from "lucide-react";
|
|
4
4
|
import type { ReactNode } from "react";
|
|
5
5
|
|
|
6
|
-
import { Button } from "
|
|
6
|
+
import { Button } from "./button";
|
|
7
7
|
import {
|
|
8
8
|
DropdownMenu,
|
|
9
9
|
DropdownMenuContent,
|
|
10
10
|
DropdownMenuItem,
|
|
11
11
|
DropdownMenuTrigger,
|
|
12
|
-
} from "
|
|
13
|
-
import { cn } from "
|
|
12
|
+
} from "./dropdown-menu";
|
|
13
|
+
import { cn } from "../../lib/utils";
|
|
14
14
|
|
|
15
15
|
export type OptionSelectItem<T extends string = string> = {
|
|
16
16
|
value: T;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
|
|
4
4
|
import * as React from "react";
|
|
5
5
|
|
|
6
|
-
import { cn } from "
|
|
6
|
+
import { cn } from "../../lib/utils";
|
|
7
7
|
|
|
8
8
|
const ScrollArea = React.forwardRef<
|
|
9
9
|
React.ComponentRef<typeof ScrollAreaPrimitive.Root>,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import * as SeparatorPrimitive from "@radix-ui/react-separator";
|
|
4
4
|
import * as React from "react";
|
|
5
5
|
|
|
6
|
-
import { cn } from "
|
|
6
|
+
import { cn } from "../../lib/utils";
|
|
7
7
|
|
|
8
8
|
const Separator = React.forwardRef<
|
|
9
9
|
React.ComponentRef<typeof SeparatorPrimitive.Root>,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import * as SliderPrimitive from "@radix-ui/react-slider";
|
|
4
4
|
import * as React from "react";
|
|
5
5
|
|
|
6
|
-
import { cn } from "
|
|
6
|
+
import { cn } from "../../lib/utils";
|
|
7
7
|
|
|
8
8
|
const Slider = React.forwardRef<
|
|
9
9
|
React.ComponentRef<typeof SliderPrimitive.Root>,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import * as SwitchPrimitives from "@radix-ui/react-switch";
|
|
4
4
|
import * as React from "react";
|
|
5
5
|
|
|
6
|
-
import { cn } from "
|
|
6
|
+
import { cn } from "../../lib/utils";
|
|
7
7
|
|
|
8
8
|
const Switch = React.forwardRef<
|
|
9
9
|
React.ComponentRef<typeof SwitchPrimitives.Root>,
|
|
@@ -4,7 +4,7 @@ import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
|
|
|
4
4
|
import { type VariantProps, cva } from "class-variance-authority";
|
|
5
5
|
import * as React from "react";
|
|
6
6
|
|
|
7
|
-
import { cn } from "
|
|
7
|
+
import { cn } from "../../lib/utils";
|
|
8
8
|
|
|
9
9
|
const toggleVariants = cva(
|
|
10
10
|
"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground",
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
|
4
4
|
import * as React from "react";
|
|
5
5
|
|
|
6
|
-
import { getPrototypePortalContainer } from "
|
|
7
|
-
import { cn } from "
|
|
6
|
+
import { getPrototypePortalContainer } from "../../lib/tool-portal";
|
|
7
|
+
import { cn } from "../../lib/utils";
|
|
8
8
|
|
|
9
9
|
const TooltipProvider = TooltipPrimitive.Provider;
|
|
10
10
|
|
|
@@ -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}
|