@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jimmy_harika/websitekit",
3
- "version": "0.5.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 (~28rem default, ~36rem wide). */
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
- 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",
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?: { title: string; subtitle?: string; body: ReactNode } | null;
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) setDock("library");
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
- setDock("inspector");
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) setDock("host");
120
- else if (dock === "host") setDock("sections");
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) setDock("library");
156
+ if (index !== null) {
157
+ setDock("library");
158
+ onDockChangeRef.current?.("library");
159
+ }
132
160
  },
133
161
  closeLibrary: () => {
134
162
  setLibraryIndex(null);
135
- setDock((d) => (d === "library" ? "sections" : 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
- setDock(null);
202
+ setDockMode(null);
171
203
  return;
172
204
  }
173
205
  // Leaving library
174
206
  if (libraryIndex !== null) {
175
207
  setLibraryIndex(null);
176
208
  }
177
- setDock(mode);
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
- setDock(null);
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
- active: !!h.active || (dock === "host" && hostDock != null),
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
- wide={dock === "library"}
340
+ size={
341
+ dock === "library"
342
+ ? "wide"
343
+ : dock === "host"
344
+ ? (hostDock?.size ?? "default")
345
+ : "default"
346
+ }
308
347
  >
309
- {dock === "sections" && <SectionsPanel catalog={catalog} embedded />}
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" && hostDock?.body}
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
- <button
353
- key={mode}
354
- type="button"
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"
360
- >
361
- {icon}
362
- <span>{label}</span>
363
- </button>
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-end" : "justify-between",
39
+ embedded ? "justify-between" : "justify-between",
34
40
  )}
35
41
  >
36
- {!embedded && (
37
- <span className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
38
- Sections
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 block
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">
@@ -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";