@jimmy_harika/websitekit 0.5.5 → 0.5.7
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/EditorDock.tsx +17 -4
- package/src/editor/PageBuilder.tsx +81 -29
- package/src/editor/SectionsPanel.tsx +12 -8
- package/src/editor/index.tsx +3 -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.7",
|
|
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,
|
|
@@ -9,29 +9,42 @@ import type { ReactNode } from "react";
|
|
|
9
9
|
import { X } from "lucide-react";
|
|
10
10
|
import { cn } from "../lib/cn";
|
|
11
11
|
|
|
12
|
+
export type EditorDockSize = "default" | "wide" | "xl";
|
|
13
|
+
|
|
14
|
+
const DOCK_WIDTH: Record<EditorDockSize, string> = {
|
|
15
|
+
default: "w-[min(22rem,100vw)]",
|
|
16
|
+
wide: "w-[min(32rem,100vw)]",
|
|
17
|
+
xl: "w-[min(40rem,100vw)]",
|
|
18
|
+
};
|
|
19
|
+
|
|
12
20
|
export function EditorDock({
|
|
13
21
|
title,
|
|
14
22
|
subtitle,
|
|
15
23
|
onClose,
|
|
16
24
|
children,
|
|
17
25
|
wide,
|
|
26
|
+
size,
|
|
18
27
|
className,
|
|
19
28
|
}: {
|
|
20
29
|
title: string;
|
|
21
30
|
subtitle?: string;
|
|
22
31
|
onClose?: () => void;
|
|
23
32
|
children: ReactNode;
|
|
24
|
-
/** Wider dock for Add Block library
|
|
33
|
+
/** @deprecated Prefer `size`. Wider dock for Add Block library. */
|
|
25
34
|
wide?: boolean;
|
|
35
|
+
/** default ~22rem · wide ~32rem · xl ~40rem (SEO / plan managers). */
|
|
36
|
+
size?: EditorDockSize;
|
|
26
37
|
className?: string;
|
|
27
38
|
}) {
|
|
39
|
+
const resolved: EditorDockSize = size ?? (wide ? "wide" : "default");
|
|
28
40
|
return (
|
|
29
41
|
<aside
|
|
30
42
|
data-editor-dock
|
|
43
|
+
data-size={resolved}
|
|
31
44
|
className={cn(
|
|
32
|
-
"flex h-full shrink-0 flex-col border-l border-border/50 bg-card/90 text-foreground shadow-none backdrop-blur-2xl",
|
|
33
|
-
|
|
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(
|
|
45
|
+
"flex h-full min-h-0 shrink-0 flex-col border-l border-border/50 bg-card/90 text-foreground shadow-none backdrop-blur-2xl",
|
|
46
|
+
DOCK_WIDTH[resolved],
|
|
47
|
+
"max-md:fixed max-md:inset-x-0 max-md:bottom-0 max-md:top-auto max-md:z-50 max-md:h-[min(85vh,100%)] max-md:w-full max-md:rounded-t-2xl max-md:border-l-0 max-md:border-t max-md:shadow-2xl",
|
|
35
48
|
className,
|
|
36
49
|
)}
|
|
37
50
|
>
|
|
@@ -50,6 +50,16 @@ export interface HostRailItem {
|
|
|
50
50
|
onClick: () => void;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
export type HostDockSize = "default" | "wide" | "xl";
|
|
54
|
+
|
|
55
|
+
export interface HostDock {
|
|
56
|
+
title: string;
|
|
57
|
+
subtitle?: string;
|
|
58
|
+
body: ReactNode;
|
|
59
|
+
/** Wide panels for dense host UIs (SEO, plan). Default fits inspector. */
|
|
60
|
+
size?: HostDockSize;
|
|
61
|
+
}
|
|
62
|
+
|
|
53
63
|
export interface PageBuilderProps {
|
|
54
64
|
catalog: Catalog;
|
|
55
65
|
initialDocument: PageDocument;
|
|
@@ -59,10 +69,14 @@ export interface PageBuilderProps {
|
|
|
59
69
|
templates?: TemplateMeta[];
|
|
60
70
|
saving?: "idle" | "saving" | "saved";
|
|
61
71
|
leading?: ReactNode;
|
|
72
|
+
/** Site pages switcher — rendered at the top of the Pages dock. */
|
|
73
|
+
pagesSlot?: ReactNode;
|
|
62
74
|
/** Host tools in the labeled left rail (SEO, Domain, …). */
|
|
63
75
|
hostRail?: HostRailItem[];
|
|
64
76
|
/** When set, dock shows this host body (same right dock as all other panels). */
|
|
65
|
-
hostDock?:
|
|
77
|
+
hostDock?: HostDock | null;
|
|
78
|
+
/** Fires when the dock mode changes so hosts can clear host tool selection. */
|
|
79
|
+
onDockChange?: (mode: DockMode) => void;
|
|
66
80
|
viewUrl?: string;
|
|
67
81
|
layout?: "floating" | "classic";
|
|
68
82
|
}
|
|
@@ -86,9 +100,11 @@ function PageBuilderInner({
|
|
|
86
100
|
templates,
|
|
87
101
|
saving,
|
|
88
102
|
leading,
|
|
103
|
+
pagesSlot,
|
|
89
104
|
viewUrl,
|
|
90
105
|
hostRail,
|
|
91
106
|
hostDock,
|
|
107
|
+
onDockChange,
|
|
92
108
|
layout = "floating",
|
|
93
109
|
}: PageBuilderProps) {
|
|
94
110
|
const doc = useBuilder((s) => s.doc);
|
|
@@ -100,24 +116,33 @@ function PageBuilderInner({
|
|
|
100
116
|
const [templatesOpen, setTemplatesOpen] = useState(false);
|
|
101
117
|
const [dock, setDock] = useState<DockMode>("sections");
|
|
102
118
|
const [mobilePanel, setMobilePanel] = useState<MobilePanel>(null);
|
|
119
|
+
const onDockChangeRef = useRef(onDockChange);
|
|
120
|
+
onDockChangeRef.current = onDockChange;
|
|
121
|
+
|
|
122
|
+
const setDockMode = (mode: DockMode) => {
|
|
123
|
+
setDock(mode);
|
|
124
|
+
onDockChangeRef.current?.(mode);
|
|
125
|
+
};
|
|
103
126
|
|
|
104
127
|
// Library open → force library dock. Close library → restore sections.
|
|
105
128
|
useEffect(() => {
|
|
106
|
-
if (libraryIndex !== null)
|
|
129
|
+
if (libraryIndex !== null) setDockMode("library");
|
|
130
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
107
131
|
}, [libraryIndex]);
|
|
108
132
|
|
|
109
133
|
// Selecting a section opens inspector in the same dock.
|
|
110
134
|
useEffect(() => {
|
|
111
135
|
if (selectedId && libraryIndex === null && !hostDock) {
|
|
112
|
-
|
|
136
|
+
setDockMode("inspector");
|
|
113
137
|
setInspectorOpen(true);
|
|
114
138
|
}
|
|
139
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
115
140
|
}, [selectedId, libraryIndex, hostDock]);
|
|
116
141
|
|
|
117
142
|
// Host dock takes over the unified dock when provided.
|
|
118
143
|
useEffect(() => {
|
|
119
|
-
if (hostDock)
|
|
120
|
-
else if (dock === "host")
|
|
144
|
+
if (hostDock) setDockMode("host");
|
|
145
|
+
else if (dock === "host") setDockMode("sections");
|
|
121
146
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
122
147
|
}, [hostDock]);
|
|
123
148
|
|
|
@@ -128,11 +153,18 @@ function PageBuilderInner({
|
|
|
128
153
|
libraryIndex,
|
|
129
154
|
openLibrary: (index: number | null) => {
|
|
130
155
|
setLibraryIndex(index);
|
|
131
|
-
if (index !== null)
|
|
156
|
+
if (index !== null) {
|
|
157
|
+
setDock("library");
|
|
158
|
+
onDockChangeRef.current?.("library");
|
|
159
|
+
}
|
|
132
160
|
},
|
|
133
161
|
closeLibrary: () => {
|
|
134
162
|
setLibraryIndex(null);
|
|
135
|
-
setDock((d) =>
|
|
163
|
+
setDock((d) => {
|
|
164
|
+
const next = d === "library" ? "sections" : d;
|
|
165
|
+
if (next !== d) onDockChangeRef.current?.(next);
|
|
166
|
+
return next;
|
|
167
|
+
});
|
|
136
168
|
},
|
|
137
169
|
inspectorOpen,
|
|
138
170
|
setInspectorOpen,
|
|
@@ -167,20 +199,20 @@ function PageBuilderInner({
|
|
|
167
199
|
}
|
|
168
200
|
// Toggle off if same dock re-clicked
|
|
169
201
|
if (mode !== null && mode === dock) {
|
|
170
|
-
|
|
202
|
+
setDockMode(null);
|
|
171
203
|
return;
|
|
172
204
|
}
|
|
173
205
|
// Leaving library
|
|
174
206
|
if (libraryIndex !== null) {
|
|
175
207
|
setLibraryIndex(null);
|
|
176
208
|
}
|
|
177
|
-
|
|
209
|
+
setDockMode(mode);
|
|
178
210
|
if (mode === "inspector") setInspectorOpen(true);
|
|
179
211
|
};
|
|
180
212
|
|
|
181
213
|
const closeDock = () => {
|
|
182
214
|
if (libraryIndex !== null) ui.closeLibrary();
|
|
183
|
-
|
|
215
|
+
setDockMode(null);
|
|
184
216
|
select(null);
|
|
185
217
|
};
|
|
186
218
|
|
|
@@ -228,7 +260,8 @@ function PageBuilderInner({
|
|
|
228
260
|
id: h.id,
|
|
229
261
|
label: h.label,
|
|
230
262
|
icon: h.icon,
|
|
231
|
-
|
|
263
|
+
// Only the clicked host tool is active — never light up every host item.
|
|
264
|
+
active: !!h.active,
|
|
232
265
|
onClick: () => {
|
|
233
266
|
h.onClick();
|
|
234
267
|
},
|
|
@@ -304,9 +337,17 @@ function PageBuilderInner({
|
|
|
304
337
|
title={dockTitle}
|
|
305
338
|
subtitle={dockSubtitle}
|
|
306
339
|
onClose={closeDock}
|
|
307
|
-
|
|
340
|
+
size={
|
|
341
|
+
dock === "library"
|
|
342
|
+
? "wide"
|
|
343
|
+
: dock === "host"
|
|
344
|
+
? (hostDock?.size ?? "default")
|
|
345
|
+
: "default"
|
|
346
|
+
}
|
|
308
347
|
>
|
|
309
|
-
{dock === "sections" &&
|
|
348
|
+
{dock === "sections" && (
|
|
349
|
+
<SectionsPanel catalog={catalog} embedded pagesSlot={pagesSlot} />
|
|
350
|
+
)}
|
|
310
351
|
{dock === "design" && <ThemePanel />}
|
|
311
352
|
{dock === "inspector" &&
|
|
312
353
|
(selectedId ? (
|
|
@@ -317,12 +358,14 @@ function PageBuilderInner({
|
|
|
317
358
|
</p>
|
|
318
359
|
))}
|
|
319
360
|
{dock === "library" && <BlockLibraryBody catalog={catalog} />}
|
|
320
|
-
{dock === "host" &&
|
|
361
|
+
{dock === "host" && (
|
|
362
|
+
<div className="p-3 sm:p-4">{hostDock?.body}</div>
|
|
363
|
+
)}
|
|
321
364
|
</EditorDock>
|
|
322
365
|
)}
|
|
323
366
|
</div>
|
|
324
367
|
|
|
325
|
-
<MobileBar onOpenDock={openDock} />
|
|
368
|
+
<MobileBar dock={dock} onOpenDock={openDock} />
|
|
326
369
|
{templates && templates.length > 0 && (
|
|
327
370
|
<TemplateGallery templates={templates} />
|
|
328
371
|
)}
|
|
@@ -333,8 +376,10 @@ function PageBuilderInner({
|
|
|
333
376
|
}
|
|
334
377
|
|
|
335
378
|
function MobileBar({
|
|
379
|
+
dock,
|
|
336
380
|
onOpenDock,
|
|
337
381
|
}: {
|
|
382
|
+
dock: DockMode;
|
|
338
383
|
onOpenDock: (m: DockMode) => void;
|
|
339
384
|
}) {
|
|
340
385
|
const ui = useEditorUi();
|
|
@@ -348,20 +393,27 @@ function MobileBar({
|
|
|
348
393
|
["design", "Design", <Palette className="size-5" key="d" />],
|
|
349
394
|
["inspector", "Edit", <Sliders className="size-5" key="i" />],
|
|
350
395
|
] as const
|
|
351
|
-
).map(([mode, label, icon]) =>
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
396
|
+
).map(([mode, label, icon]) => {
|
|
397
|
+
const active = dock === mode;
|
|
398
|
+
return (
|
|
399
|
+
<button
|
|
400
|
+
key={mode}
|
|
401
|
+
type="button"
|
|
402
|
+
onClick={() => {
|
|
403
|
+
if (mode === "library") ui.openLibrary(sections.length);
|
|
404
|
+
else onOpenDock(mode);
|
|
405
|
+
}}
|
|
406
|
+
className={
|
|
407
|
+
active
|
|
408
|
+
? "flex flex-1 flex-col items-center gap-0.5 py-1.5 text-[10px] font-semibold text-primary"
|
|
409
|
+
: "flex flex-1 flex-col items-center gap-0.5 py-1.5 text-[10px] text-muted-foreground"
|
|
410
|
+
}
|
|
411
|
+
>
|
|
412
|
+
{icon}
|
|
413
|
+
<span>{label}</span>
|
|
414
|
+
</button>
|
|
415
|
+
);
|
|
416
|
+
})}
|
|
365
417
|
</nav>
|
|
366
418
|
);
|
|
367
419
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* reorder; the Add button opens the block library. Reorder rides the same
|
|
7
7
|
* pragmatic-drag-and-drop monitor as the canvas (kind "section").
|
|
8
8
|
*/
|
|
9
|
-
import { useRef, useState } from "react";
|
|
9
|
+
import { useRef, useState, type ReactNode } from "react";
|
|
10
10
|
import { ChevronDown, ChevronUp, GripVertical, Plus, Trash2 } from "lucide-react";
|
|
11
11
|
import { type Catalog, getDefinition } from "../registry";
|
|
12
12
|
import { useBuilder } from "../store";
|
|
@@ -17,32 +17,36 @@ import { cn } from "../lib/cn";
|
|
|
17
17
|
export function SectionsPanel({
|
|
18
18
|
catalog,
|
|
19
19
|
embedded,
|
|
20
|
+
pagesSlot,
|
|
20
21
|
}: {
|
|
21
22
|
catalog: Catalog;
|
|
22
23
|
/** Dock owns the title — only show the Add control. */
|
|
23
24
|
embedded?: boolean;
|
|
25
|
+
/** Site page switcher (host). Renders above the block list. */
|
|
26
|
+
pagesSlot?: ReactNode;
|
|
24
27
|
}) {
|
|
25
28
|
const sections = useBuilder((s) => s.doc.sections);
|
|
26
29
|
const ui = useEditorUi();
|
|
27
30
|
|
|
28
31
|
return (
|
|
29
32
|
<div className="flex h-full min-h-0 flex-col">
|
|
33
|
+
{pagesSlot && (
|
|
34
|
+
<div className="shrink-0 border-b border-border/60 px-3 py-3">{pagesSlot}</div>
|
|
35
|
+
)}
|
|
30
36
|
<div
|
|
31
37
|
className={cn(
|
|
32
38
|
"flex items-center border-b border-border px-3 py-2.5",
|
|
33
|
-
embedded ? "justify-
|
|
39
|
+
embedded ? "justify-between" : "justify-between",
|
|
34
40
|
)}
|
|
35
41
|
>
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
</span>
|
|
40
|
-
)}
|
|
42
|
+
<span className="text-[10px] font-semibold uppercase tracking-[0.14em] text-muted-foreground">
|
|
43
|
+
Blocks on this page
|
|
44
|
+
</span>
|
|
41
45
|
<button
|
|
42
46
|
onClick={() => ui.openLibrary(sections.length)}
|
|
43
47
|
className="flex items-center gap-1 rounded-md bg-primary px-2 py-1 text-xs font-medium text-primary-foreground transition hover:opacity-90"
|
|
44
48
|
>
|
|
45
|
-
<Plus className="size-3.5" /> Add
|
|
49
|
+
<Plus className="size-3.5" /> Add
|
|
46
50
|
</button>
|
|
47
51
|
</div>
|
|
48
52
|
<div className="min-h-0 flex-1 overflow-auto p-2">
|
package/src/editor/index.tsx
CHANGED
|
@@ -6,9 +6,11 @@ export {
|
|
|
6
6
|
PageBuilder,
|
|
7
7
|
type PageBuilderProps,
|
|
8
8
|
type HostRailItem,
|
|
9
|
+
type HostDock,
|
|
10
|
+
type HostDockSize,
|
|
9
11
|
type DockMode,
|
|
10
12
|
} from "./PageBuilder";
|
|
11
|
-
export { EditorDock, EditorRail } from "./EditorDock";
|
|
13
|
+
export { EditorDock, EditorRail, type EditorDockSize } from "./EditorDock";
|
|
12
14
|
export { type TemplateMeta } from "./TemplateGallery";
|
|
13
15
|
export { type Device } from "./ui-context";
|
|
14
16
|
export { type EditorCapabilities } from "./edit-primitives";
|