@jimmy_harika/websitekit 0.5.2 → 0.5.4
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/package.json +1 -1
- package/src/editor/BlockLibrary.tsx +82 -54
- package/src/editor/Canvas.tsx +1 -1
- package/src/editor/EditorDock.tsx +107 -0
- package/src/editor/PageBuilder.tsx +264 -111
- package/src/editor/Toolbar.tsx +47 -65
- package/src/editor/index.tsx +7 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jimmy_harika/websitekit",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.4",
|
|
4
4
|
"description": "Snow Labs website builder engine. Block-agnostic, curated-section. One shared builder across every Snow Labs app — engine + design-system-agnostic blocks + per-industry catalogs (author, photographer, …). Each app plugs in a catalog + theme + persistence adapter.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"private": false,
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
* previews
|
|
6
|
-
*
|
|
7
|
-
* you see is what you get.
|
|
4
|
+
* Add Block library — Pixieset-style: category chips + visual grid of live
|
|
5
|
+
* mini-previews. Renders INSIDE the unified right EditorDock (not a random
|
|
6
|
+
* portal side). Click inserts at the open library index.
|
|
8
7
|
*/
|
|
9
|
-
import { createElement,
|
|
10
|
-
import { createPortal } from "react-dom";
|
|
11
|
-
import { X } from "lucide-react";
|
|
8
|
+
import { createElement, useMemo, useState } from "react";
|
|
12
9
|
import {
|
|
13
10
|
type Catalog,
|
|
14
11
|
type SectionDefinition,
|
|
@@ -20,16 +17,20 @@ import { useBuilder } from "../store";
|
|
|
20
17
|
import { useEditorUi } from "./ui-context";
|
|
21
18
|
import { cn } from "../lib/cn";
|
|
22
19
|
|
|
23
|
-
export function
|
|
20
|
+
export function BlockLibraryBody({ catalog }: { catalog: Catalog }) {
|
|
24
21
|
const ui = useEditorUi();
|
|
25
22
|
const theme = useBuilder((s) => s.doc.theme);
|
|
26
23
|
const insertSection = useBuilder((s) => s.insertSection);
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
const categories = catalog.categories.length
|
|
25
|
+
? catalog.categories
|
|
26
|
+
: ["All"];
|
|
27
|
+
const [active, setActive] = useState(categories[0] ?? "All");
|
|
30
28
|
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
const items = useMemo(() => {
|
|
30
|
+
const all = Object.values(catalog.sections);
|
|
31
|
+
if (active === "All" || !catalog.categories.length) return all;
|
|
32
|
+
return all.filter((d) => d.category === active);
|
|
33
|
+
}, [catalog, active]);
|
|
33
34
|
|
|
34
35
|
function add(def: SectionDefinition, variant?: string) {
|
|
35
36
|
insertSection(
|
|
@@ -44,67 +45,94 @@ export function BlockLibrary({ catalog }: { catalog: Catalog }) {
|
|
|
44
45
|
ui.closeLibrary();
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
return
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
<
|
|
58
|
-
|
|
48
|
+
if (ui.libraryIndex === null) return null;
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<div className="flex h-full min-h-0 flex-col">
|
|
52
|
+
<div className="shrink-0 space-y-2 border-b border-border/40 px-3 py-3">
|
|
53
|
+
<p className="text-xs text-muted-foreground">
|
|
54
|
+
Click a block to insert it on the page.
|
|
55
|
+
</p>
|
|
56
|
+
<div className="flex flex-wrap gap-1.5">
|
|
57
|
+
{categories.map((c) => (
|
|
58
|
+
<button
|
|
59
|
+
key={c}
|
|
60
|
+
type="button"
|
|
61
|
+
onClick={() => setActive(c)}
|
|
62
|
+
className={cn(
|
|
63
|
+
"rounded-full px-2.5 py-1 text-[11px] font-medium transition",
|
|
64
|
+
active === c
|
|
65
|
+
? "bg-foreground text-background"
|
|
66
|
+
: "bg-muted text-muted-foreground hover:text-foreground",
|
|
67
|
+
)}
|
|
68
|
+
>
|
|
69
|
+
{c}
|
|
70
|
+
</button>
|
|
71
|
+
))}
|
|
59
72
|
</div>
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
? "bg-secondary font-medium text-secondary-foreground"
|
|
70
|
-
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
71
|
-
)}
|
|
72
|
-
>
|
|
73
|
-
{c}
|
|
74
|
-
</button>
|
|
75
|
-
))}
|
|
76
|
-
</nav>
|
|
77
|
-
<div className="grid flex-1 grid-cols-1 content-start gap-4 overflow-auto p-4 sm:grid-cols-2">
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
<div className="min-h-0 flex-1 overflow-auto p-3">
|
|
76
|
+
{items.length === 0 ? (
|
|
77
|
+
<p className="py-8 text-center text-sm text-muted-foreground">
|
|
78
|
+
No blocks in this category.
|
|
79
|
+
</p>
|
|
80
|
+
) : (
|
|
81
|
+
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
|
78
82
|
{items.flatMap((def) => {
|
|
79
83
|
const variants =
|
|
80
84
|
def.variants && def.variants.length
|
|
81
85
|
? def.variants
|
|
82
|
-
: [{ id: undefined as string | undefined, label:
|
|
86
|
+
: [{ id: undefined as string | undefined, label: "Default" }];
|
|
83
87
|
return variants.map((v) => (
|
|
84
88
|
<button
|
|
85
|
-
key={def.type + (v.id ?? "")}
|
|
89
|
+
key={def.type + (v.id ?? "default")}
|
|
90
|
+
type="button"
|
|
86
91
|
onClick={() => add(def, v.id)}
|
|
87
|
-
className="group overflow-hidden rounded-
|
|
92
|
+
className="group overflow-hidden rounded-xl border border-border/60 bg-muted/40 text-left transition hover:border-foreground/30 hover:shadow-md"
|
|
88
93
|
>
|
|
89
|
-
<div className="relative h-
|
|
94
|
+
<div className="relative h-28 overflow-hidden bg-background">
|
|
90
95
|
<div
|
|
91
96
|
className="pointer-events-none absolute left-0 top-0 origin-top-left"
|
|
92
|
-
style={{
|
|
97
|
+
style={{
|
|
98
|
+
...themeVars(theme),
|
|
99
|
+
width: 960,
|
|
100
|
+
transform: "scale(0.28)",
|
|
101
|
+
}}
|
|
93
102
|
>
|
|
94
103
|
{createElement(def.Component, defaultPropsFor(def, v.id))}
|
|
95
104
|
</div>
|
|
105
|
+
<div className="pointer-events-none absolute inset-0 bg-gradient-to-t from-background/40 to-transparent opacity-0 transition group-hover:opacity-100" />
|
|
96
106
|
</div>
|
|
97
|
-
<div className="border-t px-3 py-2
|
|
98
|
-
|
|
99
|
-
|
|
107
|
+
<div className="border-t border-border/40 px-3 py-2">
|
|
108
|
+
<div className="text-xs font-semibold text-foreground">
|
|
109
|
+
{def.label}
|
|
110
|
+
{variants.length > 1 ? (
|
|
111
|
+
<span className="font-normal text-muted-foreground">
|
|
112
|
+
{" "}
|
|
113
|
+
· {v.label}
|
|
114
|
+
</span>
|
|
115
|
+
) : null}
|
|
116
|
+
</div>
|
|
117
|
+
{def.description && (
|
|
118
|
+
<p className="mt-0.5 line-clamp-2 text-[10px] leading-snug text-muted-foreground">
|
|
119
|
+
{def.description}
|
|
120
|
+
</p>
|
|
121
|
+
)}
|
|
100
122
|
</div>
|
|
101
123
|
</button>
|
|
102
124
|
));
|
|
103
125
|
})}
|
|
104
126
|
</div>
|
|
105
|
-
|
|
127
|
+
)}
|
|
106
128
|
</div>
|
|
107
|
-
</div
|
|
108
|
-
document.body,
|
|
129
|
+
</div>
|
|
109
130
|
);
|
|
110
131
|
}
|
|
132
|
+
|
|
133
|
+
/** @deprecated portal-based library — kept export name for any external import. */
|
|
134
|
+
export function BlockLibrary({ catalog }: { catalog: Catalog }) {
|
|
135
|
+
// Dock hosts BlockLibraryBody; this no-op keeps the export stable.
|
|
136
|
+
void catalog;
|
|
137
|
+
return null;
|
|
138
|
+
}
|
package/src/editor/Canvas.tsx
CHANGED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Single consistent right-side dock for ALL editor panels (sections, design,
|
|
5
|
+
* inspector, block library, host tools). Same position, size, chrome — only
|
|
6
|
+
* the body swaps. Host apps theme via [data-websitekit-editor] tokens.
|
|
7
|
+
*/
|
|
8
|
+
import type { ReactNode } from "react";
|
|
9
|
+
import { X } from "lucide-react";
|
|
10
|
+
import { cn } from "../lib/cn";
|
|
11
|
+
|
|
12
|
+
export function EditorDock({
|
|
13
|
+
title,
|
|
14
|
+
subtitle,
|
|
15
|
+
onClose,
|
|
16
|
+
children,
|
|
17
|
+
wide,
|
|
18
|
+
className,
|
|
19
|
+
}: {
|
|
20
|
+
title: string;
|
|
21
|
+
subtitle?: string;
|
|
22
|
+
onClose?: () => void;
|
|
23
|
+
children: ReactNode;
|
|
24
|
+
/** Wider dock for Add Block library (~28rem default, ~36rem wide). */
|
|
25
|
+
wide?: boolean;
|
|
26
|
+
className?: string;
|
|
27
|
+
}) {
|
|
28
|
+
return (
|
|
29
|
+
<aside
|
|
30
|
+
data-editor-dock
|
|
31
|
+
className={cn(
|
|
32
|
+
"flex h-full shrink-0 flex-col border-l border-border/50 bg-card/90 text-foreground shadow-[-12px_0_40px_-20px_rgba(0,0,0,0.45)] backdrop-blur-2xl",
|
|
33
|
+
wide ? "w-[min(36rem,100vw)]" : "w-[min(22rem,100vw)]",
|
|
34
|
+
"max-md:fixed max-md:inset-x-0 max-md:bottom-0 max-md:top-auto max-md:z-50 max-md:h-[min(78vh,100%)] max-md:w-full max-md:rounded-t-2xl max-md:border-l-0 max-md:border-t max-md:shadow-2xl",
|
|
35
|
+
className,
|
|
36
|
+
)}
|
|
37
|
+
>
|
|
38
|
+
<header className="flex shrink-0 items-start justify-between gap-3 border-b border-border/40 px-4 py-3">
|
|
39
|
+
<div className="min-w-0">
|
|
40
|
+
<h2 className="text-[11px] font-semibold uppercase tracking-[0.18em] text-muted-foreground">
|
|
41
|
+
{title}
|
|
42
|
+
</h2>
|
|
43
|
+
{subtitle && (
|
|
44
|
+
<p className="mt-0.5 truncate text-sm font-medium text-foreground">{subtitle}</p>
|
|
45
|
+
)}
|
|
46
|
+
</div>
|
|
47
|
+
{onClose && (
|
|
48
|
+
<button
|
|
49
|
+
type="button"
|
|
50
|
+
onClick={onClose}
|
|
51
|
+
className="rounded-lg p-1.5 text-muted-foreground transition hover:bg-muted hover:text-foreground"
|
|
52
|
+
aria-label="Close panel"
|
|
53
|
+
>
|
|
54
|
+
<X className="size-4" />
|
|
55
|
+
</button>
|
|
56
|
+
)}
|
|
57
|
+
</header>
|
|
58
|
+
<div className="min-h-0 flex-1 overflow-auto">{children}</div>
|
|
59
|
+
</aside>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Thin left tool rail with icon + label so tools are discoverable. */
|
|
64
|
+
export function EditorRail({
|
|
65
|
+
items,
|
|
66
|
+
className,
|
|
67
|
+
}: {
|
|
68
|
+
items: {
|
|
69
|
+
id: string;
|
|
70
|
+
label: string;
|
|
71
|
+
icon: ReactNode;
|
|
72
|
+
active?: boolean;
|
|
73
|
+
onClick: () => void;
|
|
74
|
+
}[];
|
|
75
|
+
className?: string;
|
|
76
|
+
}) {
|
|
77
|
+
return (
|
|
78
|
+
<nav
|
|
79
|
+
data-editor-rail
|
|
80
|
+
aria-label="Editor tools"
|
|
81
|
+
className={cn(
|
|
82
|
+
"z-20 hidden w-[4.25rem] shrink-0 flex-col items-stretch gap-0.5 border-r border-border/40 bg-card/50 py-2 backdrop-blur-xl md:flex",
|
|
83
|
+
className,
|
|
84
|
+
)}
|
|
85
|
+
>
|
|
86
|
+
{items.map((item) => (
|
|
87
|
+
<button
|
|
88
|
+
key={item.id}
|
|
89
|
+
type="button"
|
|
90
|
+
title={item.label}
|
|
91
|
+
onClick={item.onClick}
|
|
92
|
+
className={cn(
|
|
93
|
+
"mx-1 flex flex-col items-center gap-0.5 rounded-xl px-1 py-2 text-[9px] font-medium uppercase tracking-[0.08em] transition",
|
|
94
|
+
item.active
|
|
95
|
+
? "bg-foreground text-background shadow-sm"
|
|
96
|
+
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
97
|
+
)}
|
|
98
|
+
>
|
|
99
|
+
<span className="flex size-5 items-center justify-center [&_svg]:size-4">
|
|
100
|
+
{item.icon}
|
|
101
|
+
</span>
|
|
102
|
+
<span className="max-w-full truncate leading-none">{item.label}</span>
|
|
103
|
+
</button>
|
|
104
|
+
))}
|
|
105
|
+
</nav>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
@@ -1,42 +1,70 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* optional `templates`). Owns the store, editor UI state, and the layout:
|
|
7
|
-
* top toolbar · [design panel] · canvas · [inspector] · overlays (library,
|
|
8
|
-
* templates).
|
|
4
|
+
* Editor shell — canvas-first with ONE consistent right dock for every panel
|
|
5
|
+
* (sections, design, inspect, add block, host tools). Left rail is labeled.
|
|
9
6
|
*/
|
|
10
7
|
import { useEffect, useMemo, useRef, useState, type ReactNode } from "react";
|
|
11
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
LayoutTemplate,
|
|
10
|
+
List,
|
|
11
|
+
Palette,
|
|
12
|
+
PanelRight,
|
|
13
|
+
Plus,
|
|
14
|
+
Sliders,
|
|
15
|
+
} from "lucide-react";
|
|
12
16
|
import type { PageDocument } from "../model";
|
|
13
17
|
import type { Catalog } from "../registry";
|
|
14
18
|
import { BuilderProvider, useBuilder, useCreateBuilderStore } from "../store";
|
|
15
19
|
import { EditorCapsContext } from "./edit-primitives";
|
|
16
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
EditorUiContext,
|
|
22
|
+
type Device,
|
|
23
|
+
type EditorUi,
|
|
24
|
+
type MobilePanel,
|
|
25
|
+
useEditorUi,
|
|
26
|
+
} from "./ui-context";
|
|
17
27
|
import { Toolbar } from "./Toolbar";
|
|
18
28
|
import { Canvas } from "./Canvas";
|
|
19
29
|
import { Inspector } from "./Inspector";
|
|
20
|
-
import {
|
|
30
|
+
import { BlockLibraryBody } from "./BlockLibrary";
|
|
21
31
|
import { ThemePanel } from "./ThemePanel";
|
|
22
32
|
import { SectionsPanel } from "./SectionsPanel";
|
|
23
33
|
import { TemplateGallery, type TemplateMeta } from "./TemplateGallery";
|
|
24
34
|
import { useEditorShortcuts } from "./shortcuts";
|
|
35
|
+
import { EditorDock, EditorRail } from "./EditorDock";
|
|
36
|
+
|
|
37
|
+
export type DockMode =
|
|
38
|
+
| "sections"
|
|
39
|
+
| "design"
|
|
40
|
+
| "inspector"
|
|
41
|
+
| "library"
|
|
42
|
+
| "host"
|
|
43
|
+
| null;
|
|
44
|
+
|
|
45
|
+
export interface HostRailItem {
|
|
46
|
+
id: string;
|
|
47
|
+
label: string;
|
|
48
|
+
icon: ReactNode;
|
|
49
|
+
active?: boolean;
|
|
50
|
+
onClick: () => void;
|
|
51
|
+
}
|
|
25
52
|
|
|
26
53
|
export interface PageBuilderProps {
|
|
27
54
|
catalog: Catalog;
|
|
28
55
|
initialDocument: PageDocument;
|
|
29
|
-
/** Fires (debounced upstream) whenever the document changes — for autosave. */
|
|
30
56
|
onChange?: (doc: PageDocument) => void;
|
|
31
57
|
onPublish?: (doc: PageDocument) => void | Promise<void>;
|
|
32
|
-
/** Upload a picked file, return its public URL (R2 / Convex storage). */
|
|
33
58
|
uploadImage?: (file: File) => Promise<string>;
|
|
34
59
|
templates?: TemplateMeta[];
|
|
35
60
|
saving?: "idle" | "saving" | "saved";
|
|
36
|
-
/** Header gate content (e.g. exit link + site name), pinned far left. */
|
|
37
61
|
leading?: ReactNode;
|
|
38
|
-
/**
|
|
62
|
+
/** Host tools in the labeled left rail (SEO, Domain, …). */
|
|
63
|
+
hostRail?: HostRailItem[];
|
|
64
|
+
/** When set, dock shows this host body (same right dock as all other panels). */
|
|
65
|
+
hostDock?: { title: string; subtitle?: string; body: ReactNode } | null;
|
|
39
66
|
viewUrl?: string;
|
|
67
|
+
layout?: "floating" | "classic";
|
|
40
68
|
}
|
|
41
69
|
|
|
42
70
|
export function PageBuilder(props: PageBuilderProps) {
|
|
@@ -59,22 +87,53 @@ function PageBuilderInner({
|
|
|
59
87
|
saving,
|
|
60
88
|
leading,
|
|
61
89
|
viewUrl,
|
|
90
|
+
hostRail,
|
|
91
|
+
hostDock,
|
|
92
|
+
layout = "floating",
|
|
62
93
|
}: PageBuilderProps) {
|
|
63
94
|
const doc = useBuilder((s) => s.doc);
|
|
95
|
+
const selectedId = useBuilder((s) => s.selectedId);
|
|
96
|
+
const select = useBuilder((s) => s.select);
|
|
64
97
|
const [device, setDevice] = useState<Device>("desktop");
|
|
65
98
|
const [libraryIndex, setLibraryIndex] = useState<number | null>(null);
|
|
66
|
-
const [inspectorOpen, setInspectorOpen] = useState(
|
|
99
|
+
const [inspectorOpen, setInspectorOpen] = useState(false);
|
|
67
100
|
const [templatesOpen, setTemplatesOpen] = useState(false);
|
|
68
|
-
const [
|
|
101
|
+
const [dock, setDock] = useState<DockMode>("sections");
|
|
69
102
|
const [mobilePanel, setMobilePanel] = useState<MobilePanel>(null);
|
|
70
103
|
|
|
104
|
+
// Library open → force library dock. Close library → restore sections.
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
if (libraryIndex !== null) setDock("library");
|
|
107
|
+
}, [libraryIndex]);
|
|
108
|
+
|
|
109
|
+
// Selecting a section opens inspector in the same dock.
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
if (selectedId && libraryIndex === null && !hostDock) {
|
|
112
|
+
setDock("inspector");
|
|
113
|
+
setInspectorOpen(true);
|
|
114
|
+
}
|
|
115
|
+
}, [selectedId, libraryIndex, hostDock]);
|
|
116
|
+
|
|
117
|
+
// Host dock takes over the unified dock when provided.
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
if (hostDock) setDock("host");
|
|
120
|
+
else if (dock === "host") setDock("sections");
|
|
121
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
122
|
+
}, [hostDock]);
|
|
123
|
+
|
|
71
124
|
const ui: EditorUi = useMemo(
|
|
72
125
|
() => ({
|
|
73
126
|
device,
|
|
74
127
|
setDevice,
|
|
75
128
|
libraryIndex,
|
|
76
|
-
openLibrary:
|
|
77
|
-
|
|
129
|
+
openLibrary: (index: number | null) => {
|
|
130
|
+
setLibraryIndex(index);
|
|
131
|
+
if (index !== null) setDock("library");
|
|
132
|
+
},
|
|
133
|
+
closeLibrary: () => {
|
|
134
|
+
setLibraryIndex(null);
|
|
135
|
+
setDock((d) => (d === "library" ? "sections" : d));
|
|
136
|
+
},
|
|
78
137
|
inspectorOpen,
|
|
79
138
|
setInspectorOpen,
|
|
80
139
|
templatesOpen,
|
|
@@ -85,7 +144,6 @@ function PageBuilderInner({
|
|
|
85
144
|
[device, libraryIndex, inspectorOpen, templatesOpen, mobilePanel],
|
|
86
145
|
);
|
|
87
146
|
|
|
88
|
-
// Autosave: emit document changes (skip the initial render).
|
|
89
147
|
const onChangeRef = useRef(onChange);
|
|
90
148
|
onChangeRef.current = onChange;
|
|
91
149
|
const firstRender = useRef(true);
|
|
@@ -97,121 +155,216 @@ function PageBuilderInner({
|
|
|
97
155
|
onChangeRef.current?.(doc);
|
|
98
156
|
}, [doc]);
|
|
99
157
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
158
|
+
useEditorShortcuts({
|
|
159
|
+
ui,
|
|
160
|
+
onPublish: onPublish ? () => onPublish(doc) : undefined,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const openDock = (mode: DockMode) => {
|
|
164
|
+
if (mode === "library") {
|
|
165
|
+
ui.openLibrary(doc.sections.length);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
// Toggle off if same dock re-clicked
|
|
169
|
+
if (mode !== null && mode === dock) {
|
|
170
|
+
setDock(null);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
// Leaving library
|
|
174
|
+
if (libraryIndex !== null) {
|
|
175
|
+
setLibraryIndex(null);
|
|
176
|
+
}
|
|
177
|
+
setDock(mode);
|
|
178
|
+
if (mode === "inspector") setInspectorOpen(true);
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const closeDock = () => {
|
|
182
|
+
if (libraryIndex !== null) ui.closeLibrary();
|
|
183
|
+
setDock(null);
|
|
184
|
+
select(null);
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const railItems = [
|
|
188
|
+
{
|
|
189
|
+
id: "sections",
|
|
190
|
+
label: "Pages",
|
|
191
|
+
icon: <List />,
|
|
192
|
+
active: dock === "sections",
|
|
193
|
+
onClick: () => openDock("sections"),
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
id: "add",
|
|
197
|
+
label: "Add",
|
|
198
|
+
icon: <Plus />,
|
|
199
|
+
active: dock === "library",
|
|
200
|
+
onClick: () => openDock("library"),
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
id: "design",
|
|
204
|
+
label: "Design",
|
|
205
|
+
icon: <Palette />,
|
|
206
|
+
active: dock === "design",
|
|
207
|
+
onClick: () => openDock("design"),
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
id: "inspect",
|
|
211
|
+
label: "Edit",
|
|
212
|
+
icon: <PanelRight />,
|
|
213
|
+
active: dock === "inspector",
|
|
214
|
+
onClick: () => openDock("inspector"),
|
|
215
|
+
},
|
|
216
|
+
...(templates && templates.length
|
|
217
|
+
? [
|
|
218
|
+
{
|
|
219
|
+
id: "templates",
|
|
220
|
+
label: "Looks",
|
|
221
|
+
icon: <LayoutTemplate />,
|
|
222
|
+
active: templatesOpen,
|
|
223
|
+
onClick: () => ui.setTemplatesOpen(true),
|
|
224
|
+
},
|
|
225
|
+
]
|
|
226
|
+
: []),
|
|
227
|
+
...(hostRail ?? []).map((h) => ({
|
|
228
|
+
id: h.id,
|
|
229
|
+
label: h.label,
|
|
230
|
+
icon: h.icon,
|
|
231
|
+
active: !!h.active || (dock === "host" && hostDock != null),
|
|
232
|
+
onClick: () => {
|
|
233
|
+
h.onClick();
|
|
234
|
+
},
|
|
235
|
+
})),
|
|
236
|
+
];
|
|
237
|
+
|
|
238
|
+
const dockTitle =
|
|
239
|
+
dock === "library"
|
|
240
|
+
? "Add block"
|
|
241
|
+
: dock === "design"
|
|
242
|
+
? "Design"
|
|
243
|
+
: dock === "inspector"
|
|
244
|
+
? "Edit section"
|
|
245
|
+
: dock === "host"
|
|
246
|
+
? (hostDock?.title ?? "Site")
|
|
247
|
+
: dock === "sections"
|
|
248
|
+
? "Page structure"
|
|
249
|
+
: "";
|
|
250
|
+
|
|
251
|
+
const dockSubtitle =
|
|
252
|
+
dock === "host"
|
|
253
|
+
? hostDock?.subtitle
|
|
254
|
+
: dock === "inspector"
|
|
255
|
+
? selectedId
|
|
256
|
+
? "Selected block"
|
|
257
|
+
: "Select a block on the page"
|
|
258
|
+
: dock === "library"
|
|
259
|
+
? "Browse layouts"
|
|
260
|
+
: undefined;
|
|
261
|
+
|
|
262
|
+
const showDock = dock !== null;
|
|
104
263
|
|
|
105
264
|
return (
|
|
106
265
|
<EditorUiContext.Provider value={ui}>
|
|
107
|
-
<div
|
|
266
|
+
<div
|
|
267
|
+
data-websitekit-editor
|
|
268
|
+
data-layout={layout}
|
|
269
|
+
className="relative flex h-full min-h-0 flex-col bg-transparent"
|
|
270
|
+
>
|
|
108
271
|
<Toolbar
|
|
109
272
|
onPublish={onPublish ? () => onPublish(doc) : undefined}
|
|
110
273
|
saving={saving}
|
|
111
|
-
designOpen={
|
|
112
|
-
onToggleDesign={() =>
|
|
274
|
+
designOpen={dock === "design"}
|
|
275
|
+
onToggleDesign={() => openDock("design")}
|
|
113
276
|
hasTemplates={!!templates && templates.length > 0}
|
|
114
277
|
leading={leading}
|
|
115
278
|
viewUrl={viewUrl}
|
|
279
|
+
floating
|
|
280
|
+
compact
|
|
281
|
+
onAddBlock={() => openDock("library")}
|
|
116
282
|
/>
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
283
|
+
|
|
284
|
+
<div className="relative flex min-h-0 flex-1">
|
|
285
|
+
<EditorRail items={railItems} />
|
|
286
|
+
|
|
287
|
+
<div className="relative min-w-0 flex-1">
|
|
288
|
+
<Canvas catalog={catalog} />
|
|
289
|
+
{/* On-canvas Add Block CTA */}
|
|
290
|
+
<div className="pointer-events-none absolute inset-x-0 bottom-4 z-10 flex justify-center">
|
|
291
|
+
<button
|
|
292
|
+
type="button"
|
|
293
|
+
onClick={() => openDock("library")}
|
|
294
|
+
className="pointer-events-auto inline-flex items-center gap-2 rounded-full border border-border/60 bg-card/90 px-4 py-2 text-xs font-semibold uppercase tracking-[0.14em] text-foreground shadow-xl backdrop-blur-xl transition hover:bg-card"
|
|
295
|
+
>
|
|
296
|
+
<Plus className="size-3.5" />
|
|
297
|
+
Add block
|
|
298
|
+
</button>
|
|
299
|
+
</div>
|
|
300
|
+
</div>
|
|
301
|
+
|
|
302
|
+
{showDock && (
|
|
303
|
+
<EditorDock
|
|
304
|
+
title={dockTitle}
|
|
305
|
+
subtitle={dockSubtitle}
|
|
306
|
+
onClose={closeDock}
|
|
307
|
+
wide={dock === "library"}
|
|
308
|
+
>
|
|
309
|
+
{dock === "sections" && <SectionsPanel catalog={catalog} />}
|
|
310
|
+
{dock === "design" && <ThemePanel />}
|
|
311
|
+
{dock === "inspector" && (
|
|
312
|
+
<div className="p-1">
|
|
313
|
+
{selectedId ? (
|
|
314
|
+
<Inspector catalog={catalog} />
|
|
315
|
+
) : (
|
|
316
|
+
<p className="p-4 text-sm text-muted-foreground">
|
|
317
|
+
Click a section on the page to edit its content and layout.
|
|
318
|
+
</p>
|
|
319
|
+
)}
|
|
126
320
|
</div>
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
<Canvas catalog={catalog} />
|
|
133
|
-
{/* Desktop inspector rail */}
|
|
134
|
-
<aside className="hidden w-80 shrink-0 flex-col border-l border-border bg-card md:flex">
|
|
135
|
-
<Inspector catalog={catalog} />
|
|
136
|
-
</aside>
|
|
321
|
+
)}
|
|
322
|
+
{dock === "library" && <BlockLibraryBody catalog={catalog} />}
|
|
323
|
+
{dock === "host" && hostDock?.body}
|
|
324
|
+
</EditorDock>
|
|
325
|
+
)}
|
|
137
326
|
</div>
|
|
138
|
-
|
|
139
|
-
<
|
|
140
|
-
|
|
141
|
-
|
|
327
|
+
|
|
328
|
+
<MobileBar onOpenDock={openDock} />
|
|
329
|
+
{templates && templates.length > 0 && (
|
|
330
|
+
<TemplateGallery templates={templates} />
|
|
331
|
+
)}
|
|
142
332
|
<style>{`[data-websitekit-editor] [data-pb-field]:empty::before{content:attr(data-pb-placeholder);opacity:.4;pointer-events:none;}`}</style>
|
|
143
333
|
</div>
|
|
144
334
|
</EditorUiContext.Provider>
|
|
145
335
|
);
|
|
146
336
|
}
|
|
147
337
|
|
|
148
|
-
|
|
149
|
-
|
|
338
|
+
function MobileBar({
|
|
339
|
+
onOpenDock,
|
|
340
|
+
}: {
|
|
341
|
+
onOpenDock: (m: DockMode) => void;
|
|
342
|
+
}) {
|
|
150
343
|
const ui = useEditorUi();
|
|
151
344
|
const sections = useBuilder((s) => s.doc.sections);
|
|
152
|
-
const tab = (p: MobilePanel, label: string, icon: ReactNode) => (
|
|
153
|
-
<button
|
|
154
|
-
type="button"
|
|
155
|
-
onClick={() => {
|
|
156
|
-
if (p === "inspector") ui.setInspectorOpen(true);
|
|
157
|
-
ui.setMobilePanel(ui.mobilePanel === p ? null : p);
|
|
158
|
-
}}
|
|
159
|
-
className={`flex flex-1 flex-col items-center gap-0.5 py-1.5 text-[10px] ${ui.mobilePanel === p ? "text-foreground" : "text-muted-foreground"}`}
|
|
160
|
-
>
|
|
161
|
-
{icon}
|
|
162
|
-
<span>{label}</span>
|
|
163
|
-
</button>
|
|
164
|
-
);
|
|
165
345
|
return (
|
|
166
|
-
<nav className="flex shrink-0 items-stretch border-t border-border bg-card px-1 pb-[env(safe-area-inset-bottom)] md:hidden">
|
|
167
|
-
{
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
346
|
+
<nav className="flex shrink-0 items-stretch border-t border-border/50 bg-card/80 px-1 pb-[env(safe-area-inset-bottom)] backdrop-blur-xl md:hidden">
|
|
347
|
+
{(
|
|
348
|
+
[
|
|
349
|
+
["sections", "Pages", <List className="size-5" key="l" />],
|
|
350
|
+
["library", "Add", <Plus className="size-5" key="p" />],
|
|
351
|
+
["design", "Design", <Palette className="size-5" key="d" />],
|
|
352
|
+
["inspector", "Edit", <Sliders className="size-5" key="i" />],
|
|
353
|
+
] as const
|
|
354
|
+
).map(([mode, label, icon]) => (
|
|
355
|
+
<button
|
|
356
|
+
key={mode}
|
|
357
|
+
type="button"
|
|
358
|
+
onClick={() => {
|
|
359
|
+
if (mode === "library") ui.openLibrary(sections.length);
|
|
360
|
+
else onOpenDock(mode);
|
|
361
|
+
}}
|
|
362
|
+
className="flex flex-1 flex-col items-center gap-0.5 py-1.5 text-[10px] text-muted-foreground"
|
|
363
|
+
>
|
|
364
|
+
{icon}
|
|
365
|
+
<span>{label}</span>
|
|
366
|
+
</button>
|
|
367
|
+
))}
|
|
178
368
|
</nav>
|
|
179
369
|
);
|
|
180
370
|
}
|
|
181
|
-
|
|
182
|
-
/** Bottom-sheet overlay rendering the active mobile panel. */
|
|
183
|
-
function MobileSheet({ catalog, designOpen }: { catalog: Catalog; designOpen: boolean }) {
|
|
184
|
-
const ui = useEditorUi();
|
|
185
|
-
if (ui.mobilePanel === null) return null;
|
|
186
|
-
return (
|
|
187
|
-
<div className="fixed inset-0 z-50 flex flex-col justify-end md:hidden">
|
|
188
|
-
<button
|
|
189
|
-
aria-label="Close panel"
|
|
190
|
-
onClick={() => ui.setMobilePanel(null)}
|
|
191
|
-
className="absolute inset-0 bg-black/40"
|
|
192
|
-
/>
|
|
193
|
-
<div className="relative max-h-[70vh] overflow-auto rounded-t-2xl border-t border-border bg-card pb-[env(safe-area-inset-bottom)]">
|
|
194
|
-
<div className="flex items-center justify-between border-b border-border px-4 py-2.5">
|
|
195
|
-
<span className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
|
196
|
-
{ui.mobilePanel === "sections" ? "Sections" : ui.mobilePanel === "design" ? "Design" : "Section"}
|
|
197
|
-
</span>
|
|
198
|
-
<button
|
|
199
|
-
onClick={() => ui.setMobilePanel(null)}
|
|
200
|
-
className="rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground"
|
|
201
|
-
>
|
|
202
|
-
<X className="size-4" />
|
|
203
|
-
</button>
|
|
204
|
-
</div>
|
|
205
|
-
<div className="min-h-[30vh]">
|
|
206
|
-
{ui.mobilePanel === "sections" && <SectionsPanel catalog={catalog} />}
|
|
207
|
-
{ui.mobilePanel === "design" && <ThemePanel />}
|
|
208
|
-
{ui.mobilePanel === "inspector" && (
|
|
209
|
-
<div className="p-4">
|
|
210
|
-
<Inspector catalog={catalog} />
|
|
211
|
-
</div>
|
|
212
|
-
)}
|
|
213
|
-
</div>
|
|
214
|
-
</div>
|
|
215
|
-
</div>
|
|
216
|
-
);
|
|
217
|
-
}
|
package/src/editor/Toolbar.tsx
CHANGED
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* device preview · undo/redo · view · save status · publish. Theme-optimized
|
|
5
|
-
* (bg-card surface, semantic tokens). */
|
|
3
|
+
/** Clean editor header: leading · devices · undo · preview · publish. */
|
|
6
4
|
import type { ReactNode } from "react";
|
|
7
5
|
import {
|
|
8
6
|
ExternalLink,
|
|
9
|
-
LayoutGrid,
|
|
10
7
|
Monitor,
|
|
11
|
-
|
|
8
|
+
Plus,
|
|
12
9
|
Redo2,
|
|
13
10
|
Smartphone,
|
|
14
11
|
Tablet,
|
|
@@ -27,22 +24,29 @@ const DEVICES: { id: Device; icon: typeof Monitor; label: string }[] = [
|
|
|
27
24
|
export function Toolbar({
|
|
28
25
|
onPublish,
|
|
29
26
|
saving = "idle",
|
|
30
|
-
designOpen,
|
|
31
|
-
onToggleDesign,
|
|
32
|
-
hasTemplates,
|
|
27
|
+
designOpen: _designOpen,
|
|
28
|
+
onToggleDesign: _onToggleDesign,
|
|
29
|
+
hasTemplates: _hasTemplates,
|
|
33
30
|
leading,
|
|
34
31
|
viewUrl,
|
|
32
|
+
floating,
|
|
33
|
+
compact,
|
|
34
|
+
onAddBlock,
|
|
35
35
|
}: {
|
|
36
36
|
onPublish?: () => void;
|
|
37
37
|
saving?: "idle" | "saving" | "saved";
|
|
38
|
-
designOpen
|
|
39
|
-
onToggleDesign
|
|
38
|
+
designOpen?: boolean;
|
|
39
|
+
onToggleDesign?: () => void;
|
|
40
40
|
hasTemplates?: boolean;
|
|
41
|
-
/** Gate content (exit + site name), pinned far left. */
|
|
42
41
|
leading?: ReactNode;
|
|
43
|
-
/** "View live site" link target. */
|
|
44
42
|
viewUrl?: string;
|
|
43
|
+
floating?: boolean;
|
|
44
|
+
compact?: boolean;
|
|
45
|
+
onAddBlock?: () => void;
|
|
45
46
|
}) {
|
|
47
|
+
void _designOpen;
|
|
48
|
+
void _onToggleDesign;
|
|
49
|
+
void _hasTemplates;
|
|
46
50
|
const ui = useEditorUi();
|
|
47
51
|
const undo = useTemporal((s) => s.undo);
|
|
48
52
|
const redo = useTemporal((s) => s.redo);
|
|
@@ -50,26 +54,22 @@ export function Toolbar({
|
|
|
50
54
|
const canRedo = useTemporal((s) => s.futureStates.length > 0);
|
|
51
55
|
|
|
52
56
|
return (
|
|
53
|
-
<header
|
|
57
|
+
<header
|
|
58
|
+
className={cn(
|
|
59
|
+
"relative z-40 flex h-12 shrink-0 items-center gap-2 px-2 text-foreground sm:px-3",
|
|
60
|
+
floating
|
|
61
|
+
? "border-b border-border/40 bg-card/50 backdrop-blur-xl"
|
|
62
|
+
: "border-b border-border bg-card",
|
|
63
|
+
)}
|
|
64
|
+
>
|
|
54
65
|
{leading && (
|
|
55
|
-
<div className="flex min-w-0 items-center gap-1.5
|
|
66
|
+
<div className="flex min-w-0 flex-1 items-center gap-1.5 overflow-hidden">
|
|
56
67
|
{leading}
|
|
57
68
|
</div>
|
|
58
69
|
)}
|
|
59
|
-
<div className="flex
|
|
60
|
-
<ToolButton active={designOpen} onClick={onToggleDesign} title="Design">
|
|
61
|
-
<Palette className="size-4" />
|
|
62
|
-
<span className="hidden text-xs md:inline">Design</span>
|
|
63
|
-
</ToolButton>
|
|
64
|
-
{hasTemplates && (
|
|
65
|
-
<ToolButton onClick={() => ui.setTemplatesOpen(true)} title="Templates">
|
|
66
|
-
<LayoutGrid className="size-4" />
|
|
67
|
-
<span className="hidden text-xs md:inline">Templates</span>
|
|
68
|
-
</ToolButton>
|
|
69
|
-
)}
|
|
70
|
-
</div>
|
|
70
|
+
{!leading && <div className="flex-1" />}
|
|
71
71
|
|
|
72
|
-
<div className="absolute left-1/2 flex -translate-x-1/2 items-center rounded-
|
|
72
|
+
<div className="absolute left-1/2 flex -translate-x-1/2 items-center rounded-xl border border-border/50 bg-background/70 p-0.5 backdrop-blur-md">
|
|
73
73
|
{DEVICES.map((d) => {
|
|
74
74
|
const Icon = d.icon;
|
|
75
75
|
return (
|
|
@@ -78,9 +78,9 @@ export function Toolbar({
|
|
|
78
78
|
title={d.label}
|
|
79
79
|
onClick={() => ui.setDevice(d.id)}
|
|
80
80
|
className={cn(
|
|
81
|
-
"rounded-
|
|
81
|
+
"rounded-lg p-1.5 transition",
|
|
82
82
|
ui.device === d.id
|
|
83
|
-
? "bg-secondary text-secondary-foreground"
|
|
83
|
+
? "bg-secondary text-secondary-foreground shadow-sm"
|
|
84
84
|
: "text-muted-foreground hover:bg-muted",
|
|
85
85
|
)}
|
|
86
86
|
>
|
|
@@ -90,12 +90,21 @@ export function Toolbar({
|
|
|
90
90
|
})}
|
|
91
91
|
</div>
|
|
92
92
|
|
|
93
|
-
<div className="ml-auto flex items-center gap-0.5">
|
|
93
|
+
<div className="ml-auto flex shrink-0 items-center gap-0.5">
|
|
94
|
+
{onAddBlock && !compact && (
|
|
95
|
+
<button
|
|
96
|
+
type="button"
|
|
97
|
+
onClick={onAddBlock}
|
|
98
|
+
className="mr-1 hidden items-center gap-1 rounded-xl border border-border/50 px-2.5 py-1.5 text-xs font-medium text-foreground hover:bg-muted sm:inline-flex"
|
|
99
|
+
>
|
|
100
|
+
<Plus className="size-3.5" /> Add
|
|
101
|
+
</button>
|
|
102
|
+
)}
|
|
94
103
|
<button
|
|
95
104
|
onClick={() => undo()}
|
|
96
105
|
disabled={!canUndo}
|
|
97
106
|
title="Undo"
|
|
98
|
-
className="rounded-
|
|
107
|
+
className="rounded-lg p-1.5 text-muted-foreground hover:bg-muted disabled:opacity-30"
|
|
99
108
|
>
|
|
100
109
|
<Undo2 className="size-4" />
|
|
101
110
|
</button>
|
|
@@ -103,7 +112,7 @@ export function Toolbar({
|
|
|
103
112
|
onClick={() => redo()}
|
|
104
113
|
disabled={!canRedo}
|
|
105
114
|
title="Redo"
|
|
106
|
-
className="rounded-
|
|
115
|
+
className="rounded-lg p-1.5 text-muted-foreground hover:bg-muted disabled:opacity-30"
|
|
107
116
|
>
|
|
108
117
|
<Redo2 className="size-4" />
|
|
109
118
|
</button>
|
|
@@ -112,19 +121,19 @@ export function Toolbar({
|
|
|
112
121
|
href={viewUrl}
|
|
113
122
|
target="_blank"
|
|
114
123
|
rel="noreferrer"
|
|
115
|
-
title="
|
|
116
|
-
className="ml-
|
|
124
|
+
title="Preview live site"
|
|
125
|
+
className="ml-0.5 hidden items-center gap-1 rounded-lg px-2 py-1.5 text-xs text-muted-foreground hover:bg-muted hover:text-foreground sm:flex"
|
|
117
126
|
>
|
|
118
|
-
<ExternalLink className="size-3.5" />
|
|
127
|
+
<ExternalLink className="size-3.5" />
|
|
119
128
|
</a>
|
|
120
129
|
)}
|
|
121
|
-
<span className="mx-1 w-
|
|
122
|
-
{saving === "saving" ? "
|
|
130
|
+
<span className="mx-1 hidden w-10 text-right text-[10px] text-muted-foreground/70 sm:block">
|
|
131
|
+
{saving === "saving" ? "…" : saving === "saved" ? "Saved" : ""}
|
|
123
132
|
</span>
|
|
124
133
|
{onPublish && (
|
|
125
134
|
<button
|
|
126
135
|
onClick={onPublish}
|
|
127
|
-
className="rounded-
|
|
136
|
+
className="rounded-xl bg-primary px-3.5 py-1.5 text-sm font-semibold text-primary-foreground shadow-sm transition hover:opacity-90"
|
|
128
137
|
>
|
|
129
138
|
Publish
|
|
130
139
|
</button>
|
|
@@ -133,30 +142,3 @@ export function Toolbar({
|
|
|
133
142
|
</header>
|
|
134
143
|
);
|
|
135
144
|
}
|
|
136
|
-
|
|
137
|
-
function ToolButton({
|
|
138
|
-
active,
|
|
139
|
-
onClick,
|
|
140
|
-
title,
|
|
141
|
-
children,
|
|
142
|
-
}: {
|
|
143
|
-
active?: boolean;
|
|
144
|
-
onClick: () => void;
|
|
145
|
-
title: string;
|
|
146
|
-
children: React.ReactNode;
|
|
147
|
-
}) {
|
|
148
|
-
return (
|
|
149
|
-
<button
|
|
150
|
-
onClick={onClick}
|
|
151
|
-
title={title}
|
|
152
|
-
className={cn(
|
|
153
|
-
"flex items-center gap-1.5 rounded-md px-2 py-1.5 sm:px-2.5",
|
|
154
|
-
active
|
|
155
|
-
? "bg-secondary text-secondary-foreground"
|
|
156
|
-
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
157
|
-
)}
|
|
158
|
-
>
|
|
159
|
-
{children}
|
|
160
|
-
</button>
|
|
161
|
-
);
|
|
162
|
-
}
|
package/src/editor/index.tsx
CHANGED
|
@@ -2,7 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
/** Client editor entry. Production rendering lives at
|
|
4
4
|
* "@jimmy_harika/websitekit/renderer"; the model/registry at the package root. */
|
|
5
|
-
export {
|
|
5
|
+
export {
|
|
6
|
+
PageBuilder,
|
|
7
|
+
type PageBuilderProps,
|
|
8
|
+
type HostRailItem,
|
|
9
|
+
type DockMode,
|
|
10
|
+
} from "./PageBuilder";
|
|
11
|
+
export { EditorDock, EditorRail } from "./EditorDock";
|
|
6
12
|
export { type TemplateMeta } from "./TemplateGallery";
|
|
7
13
|
export { type Device } from "./ui-context";
|
|
8
14
|
export { type EditorCapabilities } from "./edit-primitives";
|