@jimmy_harika/websitekit 0.5.3 → 0.5.5
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/InsertPoint.tsx +3 -3
- package/src/editor/Inspector.tsx +39 -21
- package/src/editor/PageBuilder.tsx +238 -276
- package/src/editor/SectionFrame.tsx +6 -6
- package/src/editor/SectionsPanel.tsx +22 -8
- package/src/editor/ThemePanel.tsx +1 -1
- package/src/editor/Toolbar.tsx +33 -82
- package/src/editor/edit-primitives.tsx +6 -6
- package/src/editor/index.tsx +7 -1
|
@@ -1,26 +1,54 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* preview-first). Host apps theme via [data-websitekit-editor] + CSS vars.
|
|
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.
|
|
7
6
|
*/
|
|
8
7
|
import { useEffect, useMemo, useRef, useState, type ReactNode } from "react";
|
|
9
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
LayoutTemplate,
|
|
10
|
+
List,
|
|
11
|
+
Palette,
|
|
12
|
+
PanelRight,
|
|
13
|
+
Plus,
|
|
14
|
+
Sliders,
|
|
15
|
+
} from "lucide-react";
|
|
10
16
|
import type { PageDocument } from "../model";
|
|
11
17
|
import type { Catalog } from "../registry";
|
|
12
18
|
import { BuilderProvider, useBuilder, useCreateBuilderStore } from "../store";
|
|
13
19
|
import { EditorCapsContext } from "./edit-primitives";
|
|
14
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
EditorUiContext,
|
|
22
|
+
type Device,
|
|
23
|
+
type EditorUi,
|
|
24
|
+
type MobilePanel,
|
|
25
|
+
useEditorUi,
|
|
26
|
+
} from "./ui-context";
|
|
15
27
|
import { Toolbar } from "./Toolbar";
|
|
16
28
|
import { Canvas } from "./Canvas";
|
|
17
29
|
import { Inspector } from "./Inspector";
|
|
18
|
-
import {
|
|
30
|
+
import { BlockLibraryBody } from "./BlockLibrary";
|
|
19
31
|
import { ThemePanel } from "./ThemePanel";
|
|
20
32
|
import { SectionsPanel } from "./SectionsPanel";
|
|
21
33
|
import { TemplateGallery, type TemplateMeta } from "./TemplateGallery";
|
|
22
34
|
import { useEditorShortcuts } from "./shortcuts";
|
|
23
|
-
import {
|
|
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
|
+
}
|
|
24
52
|
|
|
25
53
|
export interface PageBuilderProps {
|
|
26
54
|
catalog: Catalog;
|
|
@@ -31,15 +59,11 @@ export interface PageBuilderProps {
|
|
|
31
59
|
templates?: TemplateMeta[];
|
|
32
60
|
saving?: "idle" | "saving" | "saved";
|
|
33
61
|
leading?: ReactNode;
|
|
34
|
-
/**
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
|
|
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;
|
|
38
66
|
viewUrl?: string;
|
|
39
|
-
/**
|
|
40
|
-
* floating (default) = max canvas, panels on-call.
|
|
41
|
-
* classic = permanent 3-column rails.
|
|
42
|
-
*/
|
|
43
67
|
layout?: "floating" | "classic";
|
|
44
68
|
}
|
|
45
69
|
|
|
@@ -55,8 +79,6 @@ export function PageBuilder(props: PageBuilderProps) {
|
|
|
55
79
|
);
|
|
56
80
|
}
|
|
57
81
|
|
|
58
|
-
type LeftPanel = "sections" | "design" | null;
|
|
59
|
-
|
|
60
82
|
function PageBuilderInner({
|
|
61
83
|
catalog,
|
|
62
84
|
onChange,
|
|
@@ -65,31 +87,53 @@ function PageBuilderInner({
|
|
|
65
87
|
saving,
|
|
66
88
|
leading,
|
|
67
89
|
viewUrl,
|
|
68
|
-
|
|
69
|
-
|
|
90
|
+
hostRail,
|
|
91
|
+
hostDock,
|
|
70
92
|
layout = "floating",
|
|
71
93
|
}: PageBuilderProps) {
|
|
72
94
|
const doc = useBuilder((s) => s.doc);
|
|
73
95
|
const selectedId = useBuilder((s) => s.selectedId);
|
|
96
|
+
const select = useBuilder((s) => s.select);
|
|
74
97
|
const [device, setDevice] = useState<Device>("desktop");
|
|
75
98
|
const [libraryIndex, setLibraryIndex] = useState<number | null>(null);
|
|
76
99
|
const [inspectorOpen, setInspectorOpen] = useState(false);
|
|
77
100
|
const [templatesOpen, setTemplatesOpen] = useState(false);
|
|
78
|
-
const [
|
|
101
|
+
const [dock, setDock] = useState<DockMode>("sections");
|
|
79
102
|
const [mobilePanel, setMobilePanel] = useState<MobilePanel>(null);
|
|
80
103
|
|
|
81
|
-
//
|
|
104
|
+
// Library open → force library dock. Close library → restore sections.
|
|
82
105
|
useEffect(() => {
|
|
83
|
-
if (
|
|
84
|
-
}, [
|
|
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]);
|
|
85
123
|
|
|
86
124
|
const ui: EditorUi = useMemo(
|
|
87
125
|
() => ({
|
|
88
126
|
device,
|
|
89
127
|
setDevice,
|
|
90
128
|
libraryIndex,
|
|
91
|
-
openLibrary:
|
|
92
|
-
|
|
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
|
+
},
|
|
93
137
|
inspectorOpen,
|
|
94
138
|
setInspectorOpen,
|
|
95
139
|
templatesOpen,
|
|
@@ -111,295 +155,213 @@ function PageBuilderInner({
|
|
|
111
155
|
onChangeRef.current?.(doc);
|
|
112
156
|
}, [doc]);
|
|
113
157
|
|
|
114
|
-
useEditorShortcuts({
|
|
158
|
+
useEditorShortcuts({
|
|
159
|
+
ui,
|
|
160
|
+
onPublish: onPublish ? () => onPublish(doc) : undefined,
|
|
161
|
+
});
|
|
115
162
|
|
|
116
|
-
const
|
|
117
|
-
|
|
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
|
+
};
|
|
118
180
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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;
|
|
161
263
|
|
|
162
|
-
// ── Floating layout (default): max canvas, panels on-call ──────────────
|
|
163
264
|
return (
|
|
164
265
|
<EditorUiContext.Provider value={ui}>
|
|
165
266
|
<div
|
|
166
267
|
data-websitekit-editor
|
|
167
|
-
data-layout=
|
|
268
|
+
data-layout={layout}
|
|
168
269
|
className="relative flex h-full min-h-0 flex-col bg-transparent"
|
|
169
270
|
>
|
|
170
271
|
<Toolbar
|
|
171
272
|
onPublish={onPublish ? () => onPublish(doc) : undefined}
|
|
172
273
|
saving={saving}
|
|
173
|
-
designOpen={
|
|
174
|
-
onToggleDesign={() =>
|
|
274
|
+
designOpen={dock === "design"}
|
|
275
|
+
onToggleDesign={() => openDock("design")}
|
|
175
276
|
hasTemplates={!!templates && templates.length > 0}
|
|
176
277
|
leading={leading}
|
|
177
278
|
viewUrl={viewUrl}
|
|
178
279
|
floating
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
sectionsOpen={leftPanel === "sections"}
|
|
182
|
-
onToggleSections={() => toggleLeft("sections")}
|
|
280
|
+
compact
|
|
281
|
+
onAddBlock={() => openDock("library")}
|
|
183
282
|
/>
|
|
184
283
|
|
|
185
284
|
<div className="relative flex min-h-0 flex-1">
|
|
186
|
-
{
|
|
187
|
-
<nav
|
|
188
|
-
className="z-30 hidden w-12 shrink-0 flex-col items-center gap-1 border-r border-border/40 bg-card/40 py-2 backdrop-blur-xl md:flex"
|
|
189
|
-
aria-label="Editor tools"
|
|
190
|
-
>
|
|
191
|
-
<RailBtn
|
|
192
|
-
active={leftPanel === "sections"}
|
|
193
|
-
onClick={() => toggleLeft("sections")}
|
|
194
|
-
title="Sections"
|
|
195
|
-
>
|
|
196
|
-
<List className="size-4" />
|
|
197
|
-
</RailBtn>
|
|
198
|
-
<RailBtn
|
|
199
|
-
active={leftPanel === "design"}
|
|
200
|
-
onClick={() => toggleLeft("design")}
|
|
201
|
-
title="Design"
|
|
202
|
-
>
|
|
203
|
-
<Palette className="size-4" />
|
|
204
|
-
</RailBtn>
|
|
205
|
-
<RailBtn
|
|
206
|
-
active={false}
|
|
207
|
-
onClick={() => ui.openLibrary(doc.sections.length)}
|
|
208
|
-
title="Add block"
|
|
209
|
-
>
|
|
210
|
-
<Plus className="size-4" />
|
|
211
|
-
</RailBtn>
|
|
212
|
-
<RailBtn
|
|
213
|
-
active={inspectorOpen}
|
|
214
|
-
onClick={() => setInspectorOpen((v) => !v)}
|
|
215
|
-
title="Inspector"
|
|
216
|
-
>
|
|
217
|
-
<PanelRight className="size-4" />
|
|
218
|
-
</RailBtn>
|
|
219
|
-
{railExtra}
|
|
220
|
-
</nav>
|
|
285
|
+
<EditorRail items={railItems} />
|
|
221
286
|
|
|
222
|
-
{/* Canvas fills everything else */}
|
|
223
287
|
<div className="relative min-w-0 flex-1">
|
|
224
288
|
<Canvas catalog={catalog} />
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
{
|
|
230
|
-
|
|
231
|
-
)}
|
|
232
|
-
|
|
233
|
-
{/* Host panel (SEO / Domain / …) */}
|
|
234
|
-
{hostPanel}
|
|
235
|
-
|
|
236
|
-
{/* Floating inspector — only when open */}
|
|
237
|
-
{inspectorOpen && (
|
|
238
|
-
<FloatingCard
|
|
239
|
-
side="right"
|
|
240
|
-
onClose={() => setInspectorOpen(false)}
|
|
241
|
-
title="Inspector"
|
|
242
|
-
wide
|
|
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"
|
|
243
295
|
>
|
|
244
|
-
<
|
|
245
|
-
|
|
246
|
-
|
|
296
|
+
<Plus className="size-3.5" />
|
|
297
|
+
Add block
|
|
298
|
+
</button>
|
|
299
|
+
</div>
|
|
247
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} embedded />}
|
|
310
|
+
{dock === "design" && <ThemePanel />}
|
|
311
|
+
{dock === "inspector" &&
|
|
312
|
+
(selectedId ? (
|
|
313
|
+
<Inspector catalog={catalog} embedded />
|
|
314
|
+
) : (
|
|
315
|
+
<p className="p-4 text-sm text-muted-foreground">
|
|
316
|
+
Click a section on the page to edit its content and layout.
|
|
317
|
+
</p>
|
|
318
|
+
))}
|
|
319
|
+
{dock === "library" && <BlockLibraryBody catalog={catalog} />}
|
|
320
|
+
{dock === "host" && hostDock?.body}
|
|
321
|
+
</EditorDock>
|
|
322
|
+
)}
|
|
248
323
|
</div>
|
|
249
324
|
|
|
250
|
-
<MobileBar />
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
<
|
|
325
|
+
<MobileBar onOpenDock={openDock} />
|
|
326
|
+
{templates && templates.length > 0 && (
|
|
327
|
+
<TemplateGallery templates={templates} />
|
|
328
|
+
)}
|
|
329
|
+
<style>{`[data-websitekit-editor] [data-pb-field]:empty::before{content:attr(data-pb-placeholder);opacity:.4;pointer-events:none;}`}</style>
|
|
255
330
|
</div>
|
|
256
331
|
</EditorUiContext.Provider>
|
|
257
332
|
);
|
|
258
333
|
}
|
|
259
334
|
|
|
260
|
-
function
|
|
261
|
-
|
|
262
|
-
onClick,
|
|
263
|
-
title,
|
|
264
|
-
children,
|
|
335
|
+
function MobileBar({
|
|
336
|
+
onOpenDock,
|
|
265
337
|
}: {
|
|
266
|
-
|
|
267
|
-
onClick: () => void;
|
|
268
|
-
title: string;
|
|
269
|
-
children: ReactNode;
|
|
270
|
-
}) {
|
|
271
|
-
return (
|
|
272
|
-
<button
|
|
273
|
-
type="button"
|
|
274
|
-
title={title}
|
|
275
|
-
onClick={onClick}
|
|
276
|
-
className={cn(
|
|
277
|
-
"flex size-9 items-center justify-center rounded-xl transition",
|
|
278
|
-
active
|
|
279
|
-
? "bg-foreground text-background shadow-sm"
|
|
280
|
-
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
281
|
-
)}
|
|
282
|
-
>
|
|
283
|
-
{children}
|
|
284
|
-
</button>
|
|
285
|
-
);
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
function FloatingCard({
|
|
289
|
-
side,
|
|
290
|
-
title,
|
|
291
|
-
onClose,
|
|
292
|
-
children,
|
|
293
|
-
wide,
|
|
294
|
-
}: {
|
|
295
|
-
side: "left" | "right";
|
|
296
|
-
title: string;
|
|
297
|
-
onClose: () => void;
|
|
298
|
-
children: ReactNode;
|
|
299
|
-
wide?: boolean;
|
|
338
|
+
onOpenDock: (m: DockMode) => void;
|
|
300
339
|
}) {
|
|
340
|
+
const ui = useEditorUi();
|
|
341
|
+
const sections = useBuilder((s) => s.doc.sections);
|
|
301
342
|
return (
|
|
302
|
-
<
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
<span className="text-[11px] font-semibold uppercase tracking-[0.16em] text-muted-foreground">
|
|
312
|
-
{title}
|
|
313
|
-
</span>
|
|
343
|
+
<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">
|
|
344
|
+
{(
|
|
345
|
+
[
|
|
346
|
+
["sections", "Pages", <List className="size-5" key="l" />],
|
|
347
|
+
["library", "Add", <Plus className="size-5" key="p" />],
|
|
348
|
+
["design", "Design", <Palette className="size-5" key="d" />],
|
|
349
|
+
["inspector", "Edit", <Sliders className="size-5" key="i" />],
|
|
350
|
+
] as const
|
|
351
|
+
).map(([mode, label, icon]) => (
|
|
314
352
|
<button
|
|
353
|
+
key={mode}
|
|
315
354
|
type="button"
|
|
316
|
-
onClick={
|
|
317
|
-
|
|
318
|
-
|
|
355
|
+
onClick={() => {
|
|
356
|
+
if (mode === "library") ui.openLibrary(sections.length);
|
|
357
|
+
else onOpenDock(mode);
|
|
358
|
+
}}
|
|
359
|
+
className="flex flex-1 flex-col items-center gap-0.5 py-1.5 text-[10px] text-muted-foreground"
|
|
319
360
|
>
|
|
320
|
-
|
|
361
|
+
{icon}
|
|
362
|
+
<span>{label}</span>
|
|
321
363
|
</button>
|
|
322
|
-
|
|
323
|
-
<div className="min-h-0 flex-1 overflow-auto">{children}</div>
|
|
324
|
-
</div>
|
|
325
|
-
);
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
function EditorStyle() {
|
|
329
|
-
return (
|
|
330
|
-
<style>{`[data-websitekit-editor] [data-pb-field]:empty::before{content:attr(data-pb-placeholder);opacity:.4;pointer-events:none;}`}</style>
|
|
331
|
-
);
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
function MobileBar() {
|
|
335
|
-
const ui = useEditorUi();
|
|
336
|
-
const sections = useBuilder((s) => s.doc.sections);
|
|
337
|
-
const tab = (p: MobilePanel, label: string, icon: ReactNode) => (
|
|
338
|
-
<button
|
|
339
|
-
type="button"
|
|
340
|
-
onClick={() => {
|
|
341
|
-
if (p === "inspector") ui.setInspectorOpen(true);
|
|
342
|
-
ui.setMobilePanel(ui.mobilePanel === p ? null : p);
|
|
343
|
-
}}
|
|
344
|
-
className={`flex flex-1 flex-col items-center gap-0.5 py-1.5 text-[10px] ${ui.mobilePanel === p ? "text-foreground" : "text-muted-foreground"}`}
|
|
345
|
-
>
|
|
346
|
-
{icon}
|
|
347
|
-
<span>{label}</span>
|
|
348
|
-
</button>
|
|
349
|
-
);
|
|
350
|
-
return (
|
|
351
|
-
<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">
|
|
352
|
-
{tab("sections", "Sections", <List className="size-5" />)}
|
|
353
|
-
<button
|
|
354
|
-
type="button"
|
|
355
|
-
onClick={() => ui.openLibrary(sections.length)}
|
|
356
|
-
className="flex flex-1 flex-col items-center gap-0.5 py-1.5 text-[10px] text-muted-foreground"
|
|
357
|
-
>
|
|
358
|
-
<Plus className="size-5" />
|
|
359
|
-
<span>Add</span>
|
|
360
|
-
</button>
|
|
361
|
-
{tab("design", "Design", <Palette className="size-5" />)}
|
|
362
|
-
{tab("inspector", "Section", <Sliders className="size-5" />)}
|
|
364
|
+
))}
|
|
363
365
|
</nav>
|
|
364
366
|
);
|
|
365
367
|
}
|
|
366
|
-
|
|
367
|
-
function MobileSheet({ catalog }: { catalog: Catalog }) {
|
|
368
|
-
const ui = useEditorUi();
|
|
369
|
-
if (ui.mobilePanel === null) return null;
|
|
370
|
-
return (
|
|
371
|
-
<div className="fixed inset-0 z-50 flex flex-col justify-end md:hidden">
|
|
372
|
-
<button
|
|
373
|
-
aria-label="Close panel"
|
|
374
|
-
onClick={() => ui.setMobilePanel(null)}
|
|
375
|
-
className="absolute inset-0 bg-black/40"
|
|
376
|
-
/>
|
|
377
|
-
<div className="relative max-h-[70vh] overflow-auto rounded-t-2xl border-t border-border bg-card/95 pb-[env(safe-area-inset-bottom)] backdrop-blur-2xl">
|
|
378
|
-
<div className="flex items-center justify-between border-b border-border px-4 py-2.5">
|
|
379
|
-
<span className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
|
380
|
-
{ui.mobilePanel === "sections"
|
|
381
|
-
? "Sections"
|
|
382
|
-
: ui.mobilePanel === "design"
|
|
383
|
-
? "Design"
|
|
384
|
-
: "Section"}
|
|
385
|
-
</span>
|
|
386
|
-
<button
|
|
387
|
-
onClick={() => ui.setMobilePanel(null)}
|
|
388
|
-
className="rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground"
|
|
389
|
-
>
|
|
390
|
-
<X className="size-4" />
|
|
391
|
-
</button>
|
|
392
|
-
</div>
|
|
393
|
-
<div className="min-h-[30vh]">
|
|
394
|
-
{ui.mobilePanel === "sections" && <SectionsPanel catalog={catalog} />}
|
|
395
|
-
{ui.mobilePanel === "design" && <ThemePanel />}
|
|
396
|
-
{ui.mobilePanel === "inspector" && (
|
|
397
|
-
<div className="p-4">
|
|
398
|
-
<Inspector catalog={catalog} />
|
|
399
|
-
</div>
|
|
400
|
-
)}
|
|
401
|
-
</div>
|
|
402
|
-
</div>
|
|
403
|
-
</div>
|
|
404
|
-
);
|
|
405
|
-
}
|
|
@@ -61,14 +61,14 @@ export function SectionFrame({
|
|
|
61
61
|
className={cn(
|
|
62
62
|
"pointer-events-none absolute inset-0 z-10 ring-inset transition",
|
|
63
63
|
selected
|
|
64
|
-
? "ring-2 ring-
|
|
65
|
-
: "ring-1 ring-transparent group-hover/sec:ring-2 group-hover/sec:ring-
|
|
66
|
-
over && "ring-2 ring-
|
|
64
|
+
? "ring-2 ring-primary"
|
|
65
|
+
: "ring-1 ring-transparent group-hover/sec:ring-2 group-hover/sec:ring-primary/40",
|
|
66
|
+
over && "ring-2 ring-primary",
|
|
67
67
|
)}
|
|
68
68
|
/>
|
|
69
69
|
<div
|
|
70
70
|
className={cn(
|
|
71
|
-
"absolute right-3 top-3 z-20 flex items-center gap-0.5 rounded-lg border border-
|
|
71
|
+
"absolute right-3 top-3 z-20 flex items-center gap-0.5 rounded-lg border border-border bg-card/95 p-1 text-foreground shadow-sm backdrop-blur transition",
|
|
72
72
|
selected
|
|
73
73
|
? "opacity-100"
|
|
74
74
|
: "pointer-events-none opacity-0 group-hover/sec:pointer-events-auto group-hover/sec:opacity-100",
|
|
@@ -103,7 +103,7 @@ export function SectionFrame({
|
|
|
103
103
|
<button
|
|
104
104
|
title="Delete"
|
|
105
105
|
onClick={stop(() => remove(section.id))}
|
|
106
|
-
className="rounded p-1.5 text-
|
|
106
|
+
className="rounded p-1.5 text-destructive hover:bg-destructive/10"
|
|
107
107
|
>
|
|
108
108
|
<Trash2 className="size-4" />
|
|
109
109
|
</button>
|
|
@@ -111,7 +111,7 @@ export function SectionFrame({
|
|
|
111
111
|
{section.hideOn && section.hideOn.length > 0 && (
|
|
112
112
|
<div
|
|
113
113
|
title={`Hidden on ${section.hideOn.join(", ")}`}
|
|
114
|
-
className="absolute left-3 top-3 z-20 flex items-center gap-1 rounded-md border border-
|
|
114
|
+
className="absolute left-3 top-3 z-20 flex items-center gap-1 rounded-md border border-border bg-background/95 px-1.5 py-1 text-foreground/70 shadow-sm backdrop-blur"
|
|
115
115
|
>
|
|
116
116
|
<EyeOff className="size-3.5" />
|
|
117
117
|
{section.hideOn.map((bp) => {
|