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.
Files changed (109) hide show
  1. package/AGENTS.md +308 -29
  2. package/README.md +48 -6
  3. package/package.json +2 -1
  4. package/scripts/lib/host-config.example.sh +1 -1
  5. package/scripts/verify-prototype-preview-states.mjs +57 -0
  6. package/src/components/platform-ui/controls-panel-select.tsx +4 -3
  7. package/src/components/platform-ui/prototype-tool-dialog.tsx +117 -0
  8. package/src/components/platform-ui/sidebar.tsx +34 -16
  9. package/src/components/prototype-gallery-client.tsx +77 -37
  10. package/src/components/prototypes/design-exploration-variant-preview-shell.tsx +43 -0
  11. package/src/components/prototypes/prototype-brief-field.tsx +33 -0
  12. package/src/components/prototypes/prototype-change-log-panel.tsx +59 -55
  13. package/src/components/prototypes/prototype-comment-provider.tsx +0 -1
  14. package/src/components/prototypes/prototype-comment-storage-empty-state.tsx +41 -0
  15. package/src/components/prototypes/prototype-create-exploration-modal.tsx +41 -64
  16. package/src/components/prototypes/prototype-create-prototype-modal.tsx +102 -0
  17. package/src/components/prototypes/prototype-create-state-modal.tsx +31 -70
  18. package/src/components/prototypes/prototype-default-state-map-page.tsx +4 -4
  19. package/src/components/prototypes/prototype-design-brief-modal.tsx +10 -10
  20. package/src/components/prototypes/prototype-design-brief-panel.tsx +261 -231
  21. package/src/components/prototypes/prototype-floating-pill.module.scss +16 -9
  22. package/src/components/prototypes/prototype-mobbin-gallery.tsx +18 -2
  23. package/src/components/prototypes/prototype-review-chrome.tsx +21 -2
  24. package/src/components/prototypes/prototype-review-sidebar.tsx +7 -37
  25. package/src/components/prototypes/prototype-spec-panel-content.tsx +5 -5
  26. package/src/components/prototypes/prototype-spec-panel-empty-state.tsx +53 -0
  27. package/src/components/prototypes/prototype-state-canvas-overlay.module.scss +20 -47
  28. package/src/components/prototypes/prototype-state-canvas-overlay.tsx +4 -42
  29. package/src/components/prototypes/prototype-variant-explorer.tsx +32 -78
  30. package/src/components/prototypes/prototype-vercel-preview-modal.tsx +4 -3
  31. package/src/components/prototypes/state-map-annotation-tooltip.module.scss +7 -7
  32. package/src/components/shell/prototype-component-library-empty-state.tsx +59 -0
  33. package/src/components/shell/prototype-gallery-nav.tsx +124 -0
  34. package/src/components/shell/prototype-gallery-shell.tsx +45 -13
  35. package/src/components/shell/prototype-link-source-modal.tsx +100 -0
  36. package/src/components/shell/prototype-plugin-update-popup.tsx +74 -0
  37. package/src/components/ui/button.tsx +4 -2
  38. package/src/components/ui/dialog.tsx +44 -5
  39. package/src/components/ui/dropdown-menu.tsx +2 -2
  40. package/src/components/ui/input.tsx +1 -1
  41. package/src/components/ui/label.tsx +1 -1
  42. package/src/components/ui/option-select.tsx +3 -3
  43. package/src/components/ui/scroll-area.tsx +1 -1
  44. package/src/components/ui/separator.tsx +1 -1
  45. package/src/components/ui/slider.tsx +1 -1
  46. package/src/components/ui/switch.tsx +1 -1
  47. package/src/components/ui/tabs.tsx +1 -1
  48. package/src/components/ui/toggle-group.tsx +1 -1
  49. package/src/components/ui/tooltip.tsx +2 -2
  50. package/src/config/create-prototype-component-library-page.tsx +74 -0
  51. package/src/config/create-prototype-gallery-page.tsx +26 -23
  52. package/src/config/create-prototype-host-app.tsx +3 -15
  53. package/src/config/create-prototype-site-layout.tsx +14 -1
  54. package/src/config/create-prototype-state-map-page.tsx +44 -0
  55. package/src/config/prototype-site-layout-client.tsx +23 -6
  56. package/src/config/with-prototype.ts +54 -0
  57. package/src/index.ts +71 -11
  58. package/src/lib/copy-text-with-attachments.ts +35 -0
  59. package/src/lib/format-source-directory-name.ts +12 -0
  60. package/src/lib/platform-ui/sidebar-interactive-cursor.ts +19 -0
  61. package/src/lib/pr-split/build-pr-split-plan-prompt.ts +81 -0
  62. package/src/lib/pr-split/pr-split-highlight.ts +3 -0
  63. package/src/lib/prototype-comments/core/annotation-target.ts +12 -3
  64. package/src/lib/prototype-comments/core/comment-capture-blocked.ts +24 -0
  65. package/src/lib/prototype-comments/core/screenshot.ts +68 -31
  66. package/src/lib/prototype-comments/core/storage.ts +3 -6
  67. package/src/lib/prototype-comments/core/types.ts +2 -0
  68. package/src/lib/prototype-comments/hooks/useLayoutShift.ts +9 -2
  69. package/src/lib/prototype-comments/react/CommentCaptureToolbar.tsx +45 -10
  70. package/src/lib/prototype-comments/react/CommentProvider.tsx +19 -0
  71. package/src/lib/prototype-comments/ui/CommentModeToggleButton.tsx +34 -0
  72. package/src/lib/prototype-comments/ui/CommentsGrid.tsx +8 -16
  73. package/src/lib/prototype-comments/ui/CommentsSidebar.tsx +5 -19
  74. package/src/lib/prototype-comments/ui/capture-styles.module.scss +5 -1
  75. package/src/lib/prototype-comments/ui/comments-grid-card.module.scss +0 -7
  76. package/src/lib/prototypes/build-design-exploration-renderers.ts +44 -0
  77. package/src/lib/prototypes/changelog-description-prompt.ts +58 -0
  78. package/src/lib/prototypes/comment-storage-setup-prompt.ts +124 -0
  79. package/src/lib/prototypes/create-prototype-prompt.ts +96 -0
  80. package/src/lib/prototypes/create-prototype-registry.ts +5 -0
  81. package/src/lib/prototypes/design-exploration-types.ts +261 -10
  82. package/src/lib/prototypes/link-source-prompt.ts +93 -0
  83. package/src/lib/prototypes/populate-component-library-prompt.ts +64 -0
  84. package/src/lib/prototypes/proto-plugin-version.ts +19 -0
  85. package/src/lib/prototypes/prototype-config-types.ts +2 -0
  86. package/src/lib/prototypes/prototype-layout-classes.ts +19 -0
  87. package/src/lib/prototypes/prototype-preview-state-registry.ts +5 -2
  88. package/src/lib/prototypes/prototype-review-context.tsx +4 -4
  89. package/src/lib/prototypes/prototype-state-canvas-constants.ts +1 -1
  90. package/src/lib/prototypes/prototype-state-canvas-types.ts +0 -2
  91. package/src/lib/prototypes/prototype-storage-status.ts +31 -0
  92. package/src/lib/prototypes/sort-prototypes-by-creation-date.ts +43 -0
  93. package/src/lib/prototypes/upgrade-proto-plugin-prompt.ts +40 -0
  94. package/src/lib/prototypes/use-proto-plugin-version-check.ts +67 -0
  95. package/src/lib/prototypes/use-prototype-tool-theme.tsx +1 -1
  96. package/src/lib/prototypes/use-toolbar-hover-panel.ts +12 -1
  97. package/src/lib/prototypes/variant-set-lucide-icon.tsx +24 -0
  98. package/src/lib/tool-portal.ts +24 -0
  99. package/src/lib/use-copy-to-clipboard.ts +7 -13
  100. package/src/server/create-host-api-route.ts +14 -16
  101. package/src/server/create-prototype-api-routes.ts +0 -115
  102. package/src/server/proto-plugin-version-route.ts +145 -0
  103. package/src/server/prototype-storage-status-route.ts +29 -0
  104. package/src/server.ts +2 -8
  105. package/src/styles/globals.css +357 -69
  106. package/src/lib/prototypes/design-exploration-default-storage.ts +0 -68
  107. package/src/lib/prototypes/design-exploration-default.ts +0 -51
  108. package/src/lib/prototypes/use-design-exploration-default.ts +0 -114
  109. 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&apos;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 "@prototype/lib/utils";
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 { getPrototypePortalContainer } from "@prototype/lib/tool-portal";
8
- import { cn } from "@prototype/lib/utils";
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 container={getPrototypePortalContainer()}>
40
- <DialogOverlay />
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 "@prototype/lib/tool-portal";
8
- import { cn } from "@prototype/lib/utils";
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;
@@ -1,6 +1,6 @@
1
1
  import * as React from "react";
2
2
 
3
- import { cn } from "@prototype/lib/utils";
3
+ import { cn } from "../../lib/utils";
4
4
 
5
5
  const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
6
6
  ({ className, type, ...props }, ref) => {
@@ -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 "@prototype/lib/utils";
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 "@prototype/components/ui/button";
6
+ import { Button } from "./button";
7
7
  import {
8
8
  DropdownMenu,
9
9
  DropdownMenuContent,
10
10
  DropdownMenuItem,
11
11
  DropdownMenuTrigger,
12
- } from "@prototype/components/ui/dropdown-menu";
13
- import { cn } from "@prototype/lib/utils";
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 "@prototype/lib/utils";
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 "@prototype/lib/utils";
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 "@prototype/lib/utils";
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 "@prototype/lib/utils";
6
+ import { cn } from "../../lib/utils";
7
7
 
8
8
  const Switch = React.forwardRef<
9
9
  React.ComponentRef<typeof SwitchPrimitives.Root>,
@@ -3,7 +3,7 @@
3
3
  import * as TabsPrimitive from "@radix-ui/react-tabs";
4
4
  import * as React from "react";
5
5
 
6
- import { cn } from "@prototype/lib/utils";
6
+ import { cn } from "../../lib/utils";
7
7
 
8
8
  const Tabs = TabsPrimitive.Root;
9
9
 
@@ -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 "@prototype/lib/utils";
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 "@prototype/lib/tool-portal";
7
- import { cn } from "@prototype/lib/utils";
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 { PrototypeGalleryHeader } from "@prototype/components/shell/prototype-gallery-shell";
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 = registry.getAllPrototypes();
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={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
- <div className="px-10 lg:px-16">
54
- <PrototypeGalleryHeader
55
- eyebrow={eyebrow}
56
- title={title}
57
- description={description}
58
- />
59
- <PrototypeGallery />
60
- </div>
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
- const prototype = registry.getPrototype(slug);
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<{ children: ReactNode }>}
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}