@jimmy_harika/websitekit 0.5.2 → 0.5.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jimmy_harika/websitekit",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
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,
@@ -74,7 +74,7 @@ export function Canvas({ catalog }: { catalog: Catalog }) {
74
74
 
75
75
  return (
76
76
  <div
77
- className="flex-1 overflow-auto bg-muted p-4 sm:p-6"
77
+ className="flex-1 overflow-auto bg-transparent p-3 sm:p-5 md:p-6"
78
78
  onClick={() => select(null)}
79
79
  >
80
80
  <div
@@ -1,14 +1,12 @@
1
1
  "use client";
2
2
 
3
3
  /**
4
- * Top-level editor. The app wires it with a `catalog`, an `initialDocument`,
5
- * and capability callbacks (autosave via `onChange`, `onPublish`, `uploadImage`,
6
- * optional `templates`). Owns the store, editor UI state, and the layout:
7
- * top toolbar · [design panel] · canvas · [inspector] · overlays (library,
8
- * templates).
4
+ * Top-level editor. Floating-panel layout: canvas owns the space; left/right
5
+ * tools open as on-call glass cards so the live page stays large (Pixieset-style
6
+ * preview-first). Host apps theme via [data-websitekit-editor] + CSS vars.
9
7
  */
10
8
  import { useEffect, useMemo, useRef, useState, type ReactNode } from "react";
11
- import { List, Palette, Plus, Sliders, X } from "lucide-react";
9
+ import { List, Palette, PanelRight, Plus, Sliders, X } from "lucide-react";
12
10
  import type { PageDocument } from "../model";
13
11
  import type { Catalog } from "../registry";
14
12
  import { BuilderProvider, useBuilder, useCreateBuilderStore } from "../store";
@@ -22,21 +20,27 @@ import { ThemePanel } from "./ThemePanel";
22
20
  import { SectionsPanel } from "./SectionsPanel";
23
21
  import { TemplateGallery, type TemplateMeta } from "./TemplateGallery";
24
22
  import { useEditorShortcuts } from "./shortcuts";
23
+ import { cn } from "../lib/cn";
25
24
 
26
25
  export interface PageBuilderProps {
27
26
  catalog: Catalog;
28
27
  initialDocument: PageDocument;
29
- /** Fires (debounced upstream) whenever the document changes — for autosave. */
30
28
  onChange?: (doc: PageDocument) => void;
31
29
  onPublish?: (doc: PageDocument) => void | Promise<void>;
32
- /** Upload a picked file, return its public URL (R2 / Convex storage). */
33
30
  uploadImage?: (file: File) => Promise<string>;
34
31
  templates?: TemplateMeta[];
35
32
  saving?: "idle" | "saving" | "saved";
36
- /** Header gate content (e.g. exit link + site name), pinned far left. */
37
33
  leading?: ReactNode;
38
- /** "View live site" link target. */
34
+ /** Extra tools in the left icon rail (host app: SEO, Domain, …). */
35
+ railExtra?: ReactNode;
36
+ /** Floating host panel body (when host manages its own panel open state). */
37
+ hostPanel?: ReactNode;
39
38
  viewUrl?: string;
39
+ /**
40
+ * floating (default) = max canvas, panels on-call.
41
+ * classic = permanent 3-column rails.
42
+ */
43
+ layout?: "floating" | "classic";
40
44
  }
41
45
 
42
46
  export function PageBuilder(props: PageBuilderProps) {
@@ -51,6 +55,8 @@ export function PageBuilder(props: PageBuilderProps) {
51
55
  );
52
56
  }
53
57
 
58
+ type LeftPanel = "sections" | "design" | null;
59
+
54
60
  function PageBuilderInner({
55
61
  catalog,
56
62
  onChange,
@@ -59,15 +65,24 @@ function PageBuilderInner({
59
65
  saving,
60
66
  leading,
61
67
  viewUrl,
68
+ railExtra,
69
+ hostPanel,
70
+ layout = "floating",
62
71
  }: PageBuilderProps) {
63
72
  const doc = useBuilder((s) => s.doc);
73
+ const selectedId = useBuilder((s) => s.selectedId);
64
74
  const [device, setDevice] = useState<Device>("desktop");
65
75
  const [libraryIndex, setLibraryIndex] = useState<number | null>(null);
66
- const [inspectorOpen, setInspectorOpen] = useState(true);
76
+ const [inspectorOpen, setInspectorOpen] = useState(false);
67
77
  const [templatesOpen, setTemplatesOpen] = useState(false);
68
- const [designOpen, setDesignOpen] = useState(false);
78
+ const [leftPanel, setLeftPanel] = useState<LeftPanel>("sections");
69
79
  const [mobilePanel, setMobilePanel] = useState<MobilePanel>(null);
70
80
 
81
+ // Open inspector when a section is selected (floating mode); keep open in classic.
82
+ useEffect(() => {
83
+ if (layout === "floating" && selectedId) setInspectorOpen(true);
84
+ }, [selectedId, layout]);
85
+
71
86
  const ui: EditorUi = useMemo(
72
87
  () => ({
73
88
  device,
@@ -85,7 +100,6 @@ function PageBuilderInner({
85
100
  [device, libraryIndex, inspectorOpen, templatesOpen, mobilePanel],
86
101
  );
87
102
 
88
- // Autosave: emit document changes (skip the initial render).
89
103
  const onChangeRef = useRef(onChange);
90
104
  onChangeRef.current = onChange;
91
105
  const firstRender = useRef(true);
@@ -97,55 +111,226 @@ function PageBuilderInner({
97
111
  onChangeRef.current?.(doc);
98
112
  }, [doc]);
99
113
 
100
- // Keyboard shortcuts (⌘Z/⌘⇧Z, Delete, ⌘D, Esc, ⌘/, ⌘⇧P, ↑/↓).
101
- // `ui` is passed in (not read from context) because this hook runs in
102
- // PageBuilderInner, which sits ABOVE its own <EditorUiContext.Provider>.
103
114
  useEditorShortcuts({ ui, onPublish: onPublish ? () => onPublish(doc) : undefined });
104
115
 
116
+ const toggleLeft = (p: LeftPanel) =>
117
+ setLeftPanel((cur) => (cur === p ? null : p));
118
+
119
+ if (layout === "classic") {
120
+ return (
121
+ <EditorUiContext.Provider value={ui}>
122
+ <div data-websitekit-editor data-layout="classic" className="flex h-full min-h-0 flex-col bg-muted">
123
+ <Toolbar
124
+ onPublish={onPublish ? () => onPublish(doc) : undefined}
125
+ saving={saving}
126
+ designOpen={leftPanel === "design"}
127
+ onToggleDesign={() => toggleLeft("design")}
128
+ hasTemplates={!!templates && templates.length > 0}
129
+ leading={leading}
130
+ viewUrl={viewUrl}
131
+ />
132
+ <div className="flex min-h-0 flex-1">
133
+ <aside className="hidden w-64 shrink-0 flex-col border-r border-border bg-card md:flex">
134
+ {leftPanel === "design" ? (
135
+ <>
136
+ <div className="border-b border-border px-3 py-2.5 text-xs font-semibold uppercase tracking-wide text-muted-foreground">
137
+ Design
138
+ </div>
139
+ <div className="min-h-0 flex-1 overflow-auto">
140
+ <ThemePanel />
141
+ </div>
142
+ </>
143
+ ) : (
144
+ <SectionsPanel catalog={catalog} />
145
+ )}
146
+ </aside>
147
+ <Canvas catalog={catalog} />
148
+ <aside className="hidden w-80 shrink-0 flex-col border-l border-border bg-card md:flex">
149
+ <Inspector catalog={catalog} />
150
+ </aside>
151
+ </div>
152
+ <MobileBar />
153
+ <MobileSheet catalog={catalog} />
154
+ <BlockLibrary catalog={catalog} />
155
+ {templates && templates.length > 0 && <TemplateGallery templates={templates} />}
156
+ <EditorStyle />
157
+ </div>
158
+ </EditorUiContext.Provider>
159
+ );
160
+ }
161
+
162
+ // ── Floating layout (default): max canvas, panels on-call ──────────────
105
163
  return (
106
164
  <EditorUiContext.Provider value={ui}>
107
- <div data-websitekit-editor className="flex h-full min-h-0 flex-col bg-muted">
165
+ <div
166
+ data-websitekit-editor
167
+ data-layout="floating"
168
+ className="relative flex h-full min-h-0 flex-col bg-transparent"
169
+ >
108
170
  <Toolbar
109
171
  onPublish={onPublish ? () => onPublish(doc) : undefined}
110
172
  saving={saving}
111
- designOpen={designOpen}
112
- onToggleDesign={() => setDesignOpen((v) => !v)}
173
+ designOpen={leftPanel === "design"}
174
+ onToggleDesign={() => toggleLeft("design")}
113
175
  hasTemplates={!!templates && templates.length > 0}
114
176
  leading={leading}
115
177
  viewUrl={viewUrl}
178
+ floating
179
+ inspectorOpen={inspectorOpen}
180
+ onToggleInspector={() => setInspectorOpen((v) => !v)}
181
+ sectionsOpen={leftPanel === "sections"}
182
+ onToggleSections={() => toggleLeft("sections")}
116
183
  />
117
- <div className="flex min-h-0 flex-1">
118
- <aside className="hidden w-64 shrink-0 flex-col border-r border-border bg-card md:flex">
119
- {designOpen ? (
120
- <>
121
- <div className="border-b border-border px-3 py-2.5 text-xs font-semibold uppercase tracking-wide text-muted-foreground">
122
- Design
123
- </div>
124
- <div className="min-h-0 flex-1 overflow-auto">
125
- <ThemePanel />
126
- </div>
127
- </>
128
- ) : (
129
- <SectionsPanel catalog={catalog} />
184
+
185
+ <div className="relative flex min-h-0 flex-1">
186
+ {/* Icon rail — always present, thin */}
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>
221
+
222
+ {/* Canvas fills everything else */}
223
+ <div className="relative min-w-0 flex-1">
224
+ <Canvas catalog={catalog} />
225
+
226
+ {/* Floating left panel */}
227
+ {leftPanel && (
228
+ <FloatingCard side="left" onClose={() => setLeftPanel(null)} title={leftPanel === "design" ? "Design" : "Sections"}>
229
+ {leftPanel === "design" ? <ThemePanel /> : <SectionsPanel catalog={catalog} />}
230
+ </FloatingCard>
130
231
  )}
131
- </aside>
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>
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
243
+ >
244
+ <Inspector catalog={catalog} />
245
+ </FloatingCard>
246
+ )}
247
+ </div>
137
248
  </div>
249
+
138
250
  <MobileBar />
139
- <MobileSheet catalog={catalog} designOpen={designOpen} />
251
+ <MobileSheet catalog={catalog} />
140
252
  <BlockLibrary catalog={catalog} />
141
253
  {templates && templates.length > 0 && <TemplateGallery templates={templates} />}
142
- <style>{`[data-websitekit-editor] [data-pb-field]:empty::before{content:attr(data-pb-placeholder);opacity:.4;pointer-events:none;}`}</style>
254
+ <EditorStyle />
143
255
  </div>
144
256
  </EditorUiContext.Provider>
145
257
  );
146
258
  }
147
259
 
148
- /** Mobile bottom tab bar — the only way to reach panels on < md. */
260
+ function RailBtn({
261
+ active,
262
+ onClick,
263
+ title,
264
+ children,
265
+ }: {
266
+ active: boolean;
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;
300
+ }) {
301
+ return (
302
+ <div
303
+ className={cn(
304
+ "absolute top-3 bottom-3 z-20 flex flex-col overflow-hidden rounded-2xl border border-border/50 bg-card/85 shadow-2xl backdrop-blur-2xl",
305
+ wide ? "w-[min(22rem,calc(100%-1.5rem))]" : "w-[min(18rem,calc(100%-1.5rem))]",
306
+ side === "left" ? "left-3" : "right-3",
307
+ )}
308
+ data-floating-panel={side}
309
+ >
310
+ <div className="flex items-center justify-between border-b border-border/40 px-3 py-2">
311
+ <span className="text-[11px] font-semibold uppercase tracking-[0.16em] text-muted-foreground">
312
+ {title}
313
+ </span>
314
+ <button
315
+ type="button"
316
+ onClick={onClose}
317
+ className="rounded-lg p-1 text-muted-foreground transition hover:bg-muted hover:text-foreground"
318
+ aria-label="Close panel"
319
+ >
320
+ <X className="size-3.5" />
321
+ </button>
322
+ </div>
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
+
149
334
  function MobileBar() {
150
335
  const ui = useEditorUi();
151
336
  const sections = useBuilder((s) => s.doc.sections);
@@ -163,7 +348,7 @@ function MobileBar() {
163
348
  </button>
164
349
  );
165
350
  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">
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">
167
352
  {tab("sections", "Sections", <List className="size-5" />)}
168
353
  <button
169
354
  type="button"
@@ -179,8 +364,7 @@ function MobileBar() {
179
364
  );
180
365
  }
181
366
 
182
- /** Bottom-sheet overlay rendering the active mobile panel. */
183
- function MobileSheet({ catalog, designOpen }: { catalog: Catalog; designOpen: boolean }) {
367
+ function MobileSheet({ catalog }: { catalog: Catalog }) {
184
368
  const ui = useEditorUi();
185
369
  if (ui.mobilePanel === null) return null;
186
370
  return (
@@ -190,10 +374,14 @@ function MobileSheet({ catalog, designOpen }: { catalog: Catalog; designOpen: bo
190
374
  onClick={() => ui.setMobilePanel(null)}
191
375
  className="absolute inset-0 bg-black/40"
192
376
  />
193
- <div className="relative max-h-[70vh] overflow-auto rounded-t-2xl border-t border-border bg-card pb-[env(safe-area-inset-bottom)]">
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">
194
378
  <div className="flex items-center justify-between border-b border-border px-4 py-2.5">
195
379
  <span className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
196
- {ui.mobilePanel === "sections" ? "Sections" : ui.mobilePanel === "design" ? "Design" : "Section"}
380
+ {ui.mobilePanel === "sections"
381
+ ? "Sections"
382
+ : ui.mobilePanel === "design"
383
+ ? "Design"
384
+ : "Section"}
197
385
  </span>
198
386
  <button
199
387
  onClick={() => ui.setMobilePanel(null)}
@@ -1,14 +1,14 @@
1
1
  "use client";
2
2
 
3
- /** Single cohesive editor header: gate (exit + site name) · design · templates ·
4
- * device preview · undo/redo · view · save status · publish. Theme-optimized
5
- * (bg-card surface, semantic tokens). */
3
+ /** Editor header glass-friendly surface, device switcher, undo/redo, publish. */
6
4
  import type { ReactNode } from "react";
7
5
  import {
8
6
  ExternalLink,
9
7
  LayoutGrid,
8
+ List,
10
9
  Monitor,
11
10
  Palette,
11
+ PanelRight,
12
12
  Redo2,
13
13
  Smartphone,
14
14
  Tablet,
@@ -32,16 +32,24 @@ export function Toolbar({
32
32
  hasTemplates,
33
33
  leading,
34
34
  viewUrl,
35
+ floating,
36
+ inspectorOpen,
37
+ onToggleInspector,
38
+ sectionsOpen,
39
+ onToggleSections,
35
40
  }: {
36
41
  onPublish?: () => void;
37
42
  saving?: "idle" | "saving" | "saved";
38
43
  designOpen: boolean;
39
44
  onToggleDesign: () => void;
40
45
  hasTemplates?: boolean;
41
- /** Gate content (exit + site name), pinned far left. */
42
46
  leading?: ReactNode;
43
- /** "View live site" link target. */
44
47
  viewUrl?: string;
48
+ floating?: boolean;
49
+ inspectorOpen?: boolean;
50
+ onToggleInspector?: () => void;
51
+ sectionsOpen?: boolean;
52
+ onToggleSections?: () => void;
45
53
  }) {
46
54
  const ui = useEditorUi();
47
55
  const undo = useTemporal((s) => s.undo);
@@ -50,26 +58,49 @@ export function Toolbar({
50
58
  const canRedo = useTemporal((s) => s.futureStates.length > 0);
51
59
 
52
60
  return (
53
- <header className="relative z-40 flex h-12 shrink-0 items-center gap-1 border-b border-border bg-card px-2 text-foreground sm:px-3">
61
+ <header
62
+ className={cn(
63
+ "relative z-40 flex h-12 shrink-0 items-center gap-1 px-2 text-foreground sm:px-3",
64
+ floating
65
+ ? "border-b border-border/40 bg-card/50 backdrop-blur-xl"
66
+ : "border-b border-border bg-card",
67
+ )}
68
+ >
54
69
  {leading && (
55
- <div className="flex min-w-0 items-center gap-1.5 pr-1.5 sm:border-r sm:border-border">
70
+ <div className="flex min-w-0 items-center gap-1.5 pr-1.5 sm:border-r sm:border-border/40">
56
71
  {leading}
57
72
  </div>
58
73
  )}
59
74
  <div className="flex items-center gap-0.5 pl-0.5">
75
+ {floating && onToggleSections && (
76
+ <ToolButton active={!!sectionsOpen} onClick={onToggleSections} title="Sections">
77
+ <List className="size-4" />
78
+ <span className="hidden text-xs lg:inline">Sections</span>
79
+ </ToolButton>
80
+ )}
60
81
  <ToolButton active={designOpen} onClick={onToggleDesign} title="Design">
61
82
  <Palette className="size-4" />
62
- <span className="hidden text-xs md:inline">Design</span>
83
+ <span className="hidden text-xs lg:inline">Design</span>
63
84
  </ToolButton>
64
85
  {hasTemplates && (
65
86
  <ToolButton onClick={() => ui.setTemplatesOpen(true)} title="Templates">
66
87
  <LayoutGrid className="size-4" />
67
- <span className="hidden text-xs md:inline">Templates</span>
88
+ <span className="hidden text-xs lg:inline">Templates</span>
89
+ </ToolButton>
90
+ )}
91
+ {floating && onToggleInspector && (
92
+ <ToolButton
93
+ active={!!inspectorOpen}
94
+ onClick={onToggleInspector}
95
+ title="Inspector"
96
+ >
97
+ <PanelRight className="size-4" />
98
+ <span className="hidden text-xs lg:inline">Inspect</span>
68
99
  </ToolButton>
69
100
  )}
70
101
  </div>
71
102
 
72
- <div className="absolute left-1/2 flex -translate-x-1/2 items-center rounded-lg border border-border bg-background p-0.5">
103
+ <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
104
  {DEVICES.map((d) => {
74
105
  const Icon = d.icon;
75
106
  return (
@@ -78,9 +109,9 @@ export function Toolbar({
78
109
  title={d.label}
79
110
  onClick={() => ui.setDevice(d.id)}
80
111
  className={cn(
81
- "rounded-md p-1.5 transition",
112
+ "rounded-lg p-1.5 transition",
82
113
  ui.device === d.id
83
- ? "bg-secondary text-secondary-foreground"
114
+ ? "bg-secondary text-secondary-foreground shadow-sm"
84
115
  : "text-muted-foreground hover:bg-muted",
85
116
  )}
86
117
  >
@@ -95,7 +126,7 @@ export function Toolbar({
95
126
  onClick={() => undo()}
96
127
  disabled={!canUndo}
97
128
  title="Undo"
98
- className="rounded-md p-1.5 text-muted-foreground hover:bg-muted disabled:opacity-30"
129
+ className="rounded-lg p-1.5 text-muted-foreground hover:bg-muted disabled:opacity-30"
99
130
  >
100
131
  <Undo2 className="size-4" />
101
132
  </button>
@@ -103,7 +134,7 @@ export function Toolbar({
103
134
  onClick={() => redo()}
104
135
  disabled={!canRedo}
105
136
  title="Redo"
106
- className="rounded-md p-1.5 text-muted-foreground hover:bg-muted disabled:opacity-30"
137
+ className="rounded-lg p-1.5 text-muted-foreground hover:bg-muted disabled:opacity-30"
107
138
  >
108
139
  <Redo2 className="size-4" />
109
140
  </button>
@@ -113,9 +144,9 @@ export function Toolbar({
113
144
  target="_blank"
114
145
  rel="noreferrer"
115
146
  title="View live site"
116
- className="ml-1 hidden items-center gap-1 rounded-md px-2 py-1.5 text-xs text-muted-foreground hover:bg-muted hover:text-foreground lg:flex"
147
+ className="ml-1 hidden items-center gap-1 rounded-lg px-2 py-1.5 text-xs text-muted-foreground hover:bg-muted hover:text-foreground lg:flex"
117
148
  >
118
- <ExternalLink className="size-3.5" /> View
149
+ <ExternalLink className="size-3.5" /> Preview
119
150
  </a>
120
151
  )}
121
152
  <span className="mx-1 w-12 text-right text-[11px] text-muted-foreground/70">
@@ -124,7 +155,7 @@ export function Toolbar({
124
155
  {onPublish && (
125
156
  <button
126
157
  onClick={onPublish}
127
- className="rounded-lg bg-primary px-3.5 py-1.5 text-sm font-medium text-primary-foreground shadow-sm transition hover:opacity-90 sm:px-4"
158
+ className="rounded-xl bg-primary px-3.5 py-1.5 text-sm font-semibold text-primary-foreground shadow-sm transition hover:opacity-90 sm:px-4"
128
159
  >
129
160
  Publish
130
161
  </button>
@@ -150,7 +181,7 @@ function ToolButton({
150
181
  onClick={onClick}
151
182
  title={title}
152
183
  className={cn(
153
- "flex items-center gap-1.5 rounded-md px-2 py-1.5 sm:px-2.5",
184
+ "flex items-center gap-1.5 rounded-xl px-2 py-1.5 sm:px-2.5",
154
185
  active
155
186
  ? "bg-secondary text-secondary-foreground"
156
187
  : "text-muted-foreground hover:bg-muted hover:text-foreground",