@realiizlabs/admin 0.14.0 → 0.15.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ Realiiz Studio License (source-available)
2
+
3
+ Copyright (c) 2026 Realiiz Labs LLC. All rights reserved.
4
+
5
+ This package (@realiizlabs/admin, "Studio") is published so that websites built
6
+ from the Realiiz site-starter-pro template can install and update it without
7
+ credentials. It is not open source.
8
+
9
+ You may install, run and modify this package as part of a website that you or
10
+ your organization built from a licensed copy of site-starter-pro, or that
11
+ Realiiz built for you. You may keep it installed for as long as that website
12
+ exists, including after your relationship with Realiiz ends.
13
+
14
+ You may not redistribute this package, sell it, offer it as part of another
15
+ template, product or service, or remove this notice.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND. In no event
18
+ shall Realiiz Labs LLC be liable for any claim, damages or other liability
19
+ arising from the use of the software.
20
+
21
+ Questions: hello@realiiz.com
@@ -23,7 +23,7 @@ interface ShellUser {
23
23
  }
24
24
  declare function firstNameOf(user: ShellUser): string;
25
25
  declare function initialsOf(user: ShellUser): string;
26
- declare function AccountMenu({ user, role, theme, onTheme, signOutPath, accountHref, helpHref }: {
26
+ declare function AccountMenu({ user, role, theme, onTheme, signOutPath, accountHref, helpHref, siteUrl }: {
27
27
  user: ShellUser;
28
28
  role: string;
29
29
  theme: Theme;
@@ -33,6 +33,8 @@ declare function AccountMenu({ user, role, theme, onTheme, signOutPath, accountH
33
33
  accountHref?: string;
34
34
  /** The Help center. Omit to hide the item. */
35
35
  helpHref?: string;
36
+ /** The live website — a "View website" item that opens in a new tab. Omit to hide it. */
37
+ siteUrl?: string | null;
36
38
  }): react.JSX.Element;
37
39
 
38
40
  /**
@@ -23,7 +23,7 @@ interface ShellUser {
23
23
  }
24
24
  declare function firstNameOf(user: ShellUser): string;
25
25
  declare function initialsOf(user: ShellUser): string;
26
- declare function AccountMenu({ user, role, theme, onTheme, signOutPath, accountHref, helpHref }: {
26
+ declare function AccountMenu({ user, role, theme, onTheme, signOutPath, accountHref, helpHref, siteUrl }: {
27
27
  user: ShellUser;
28
28
  role: string;
29
29
  theme: Theme;
@@ -33,6 +33,8 @@ declare function AccountMenu({ user, role, theme, onTheme, signOutPath, accountH
33
33
  accountHref?: string;
34
34
  /** The Help center. Omit to hide the item. */
35
35
  helpHref?: string;
36
+ /** The live website — a "View website" item that opens in a new tab. Omit to hide it. */
37
+ siteUrl?: string | null;
36
38
  }): react.JSX.Element;
37
39
 
38
40
  /**
@@ -0,0 +1,82 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var react = require('react');
5
+ var jsxRuntime = require('react/jsx-runtime');
6
+
7
+ // src/bar/StudioBar.tsx
8
+ var STUDIO_MARKER_COOKIE = "rz-studio";
9
+ var HIDE_KEY = "rz-bar-hidden";
10
+ function hasStudioMarker(cookie = typeof document === "undefined" ? "" : document.cookie) {
11
+ return cookie.split(";").some((c) => c.trim().startsWith(`${STUDIO_MARKER_COOKIE}=1`));
12
+ }
13
+ function editHrefFor(pathname, routes, adminPath = "/admin") {
14
+ for (const [prefix, typeId] of Object.entries(routes)) {
15
+ if (!pathname.startsWith(prefix)) continue;
16
+ const rest = pathname.slice(prefix.length).replace(/\/+$/, "");
17
+ if (!rest || rest.includes("/")) continue;
18
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(rest)) continue;
19
+ return `${adminPath}/${typeId}/${rest}`;
20
+ }
21
+ return null;
22
+ }
23
+ function StudioBar({ routes = {}, adminPath = "/admin", label = "Studio" }) {
24
+ const [state, setState] = react.useState({ show: false, path: "" });
25
+ react.useEffect(() => {
26
+ const path = window.location.pathname;
27
+ if (path === adminPath || path.startsWith(`${adminPath}/`)) return;
28
+ let hidden = false;
29
+ try {
30
+ hidden = sessionStorage.getItem(HIDE_KEY) === "1";
31
+ } catch {
32
+ }
33
+ if (!hidden && hasStudioMarker()) setState({ show: true, path });
34
+ }, [adminPath]);
35
+ if (!state.show) return null;
36
+ const edit = editHrefFor(state.path, routes, adminPath);
37
+ const close = () => {
38
+ try {
39
+ sessionStorage.setItem(HIDE_KEY, "1");
40
+ } catch {
41
+ }
42
+ setState({ show: false, path: "" });
43
+ };
44
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: S.wrap, role: "region", "aria-label": "Studio", "data-studio-bar": true, children: [
45
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: S.brand, children: [
46
+ /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.4", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 3l7 3v5c0 5-3.5 8.5-7 10-3.5-1.5-7-5-7-10V6z" }) }),
47
+ label
48
+ ] }),
49
+ edit && /* @__PURE__ */ jsxRuntime.jsx("a", { href: edit, style: S.link, children: "Edit this page" }),
50
+ /* @__PURE__ */ jsxRuntime.jsx("a", { href: adminPath, style: S.link, children: "Dashboard" }),
51
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: close, style: S.close, "aria-label": "Hide the Studio bar for this tab", children: "\xD7" })
52
+ ] });
53
+ }
54
+ var S = {
55
+ wrap: {
56
+ position: "fixed",
57
+ left: 16,
58
+ bottom: 16,
59
+ zIndex: 2147483e3,
60
+ display: "flex",
61
+ alignItems: "center",
62
+ gap: 2,
63
+ padding: "4px 6px 4px 10px",
64
+ borderRadius: 999,
65
+ background: "#0b1220",
66
+ color: "#e5e7eb",
67
+ boxShadow: "0 6px 24px rgba(0,0,0,.28)",
68
+ font: "500 13px/1 system-ui, -apple-system, Segoe UI, Roboto, sans-serif",
69
+ letterSpacing: ".01em",
70
+ WebkitFontSmoothing: "antialiased"
71
+ },
72
+ brand: { display: "inline-flex", alignItems: "center", gap: 6, paddingRight: 8, marginRight: 4, borderRight: "1px solid rgba(255,255,255,.18)", color: "#a5b4fc", fontWeight: 600 },
73
+ link: { color: "#e5e7eb", textDecoration: "none", padding: "6px 8px", borderRadius: 999 },
74
+ close: { marginLeft: 2, width: 26, height: 26, border: 0, borderRadius: 999, background: "transparent", color: "#9ca3af", fontSize: 16, lineHeight: 1, cursor: "pointer" }
75
+ };
76
+
77
+ exports.STUDIO_MARKER_COOKIE = STUDIO_MARKER_COOKIE;
78
+ exports.StudioBar = StudioBar;
79
+ exports.editHrefFor = editHrefFor;
80
+ exports.hasStudioMarker = hasStudioMarker;
81
+ //# sourceMappingURL=index.cjs.map
82
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/bar/StudioBar.tsx"],"names":["useState","useEffect","jsxs","jsx"],"mappings":"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","file":"index.cjs","sourcesContent":["\"use client\";\n\nimport { useEffect, useState, type CSSProperties } from \"react\";\n\nexport const STUDIO_MARKER_COOKIE = \"rz-studio\";\nconst HIDE_KEY = \"rz-bar-hidden\";\n\nexport interface StudioBarProps {\n /** Public URL prefix → content type id, e.g. { \"/blog/\": \"posts\", \"/events/\": \"events\" }. */\n routes?: Record<string, string>;\n /** Where Studio lives. Default \"/admin\". */\n adminPath?: string;\n /** Shown on the pill. Default \"Studio\". */\n label?: string;\n}\n\n/** True when Studio's marker cookie is present (someone signed in to Studio in this browser). */\nexport function hasStudioMarker(cookie: string = typeof document === \"undefined\" ? \"\" : document.cookie): boolean {\n return cookie.split(\";\").some((c) => c.trim().startsWith(`${STUDIO_MARKER_COOKIE}=1`));\n}\n\n/** The Studio edit page for a public path, or null when the path isn't an item Studio manages. */\nexport function editHrefFor(pathname: string, routes: Record<string, string>, adminPath = \"/admin\"): string | null {\n for (const [prefix, typeId] of Object.entries(routes)) {\n if (!pathname.startsWith(prefix)) continue;\n const rest = pathname.slice(prefix.length).replace(/\\/+$/, \"\");\n if (!rest || rest.includes(\"/\")) continue; // the index page, or something nested — not an item\n if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(rest)) continue;\n return `${adminPath}/${typeId}/${rest}`;\n }\n return null;\n}\n\nexport function StudioBar({ routes = {}, adminPath = \"/admin\", label = \"Studio\" }: StudioBarProps) {\n const [state, setState] = useState<{ show: boolean; path: string }>({ show: false, path: \"\" });\n\n useEffect(() => {\n const path = window.location.pathname;\n if (path === adminPath || path.startsWith(`${adminPath}/`)) return;\n let hidden = false;\n try { hidden = sessionStorage.getItem(HIDE_KEY) === \"1\"; } catch { /* private mode */ }\n if (!hidden && hasStudioMarker()) setState({ show: true, path });\n }, [adminPath]);\n\n if (!state.show) return null;\n const edit = editHrefFor(state.path, routes, adminPath);\n const close = () => { try { sessionStorage.setItem(HIDE_KEY, \"1\"); } catch { /* private mode */ } setState({ show: false, path: \"\" }); };\n\n return (\n <div style={S.wrap} role=\"region\" aria-label=\"Studio\" data-studio-bar>\n <span style={S.brand}>\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.4\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\"><path d=\"M12 3l7 3v5c0 5-3.5 8.5-7 10-3.5-1.5-7-5-7-10V6z\" /></svg>\n {label}\n </span>\n {edit && <a href={edit} style={S.link}>Edit this page</a>}\n <a href={adminPath} style={S.link}>Dashboard</a>\n <button type=\"button\" onClick={close} style={S.close} aria-label=\"Hide the Studio bar for this tab\">×</button>\n </div>\n );\n}\n\n/* Self-contained styles: no dependency on the site's CSS, fonts or theme. */\nconst S: Record<string, CSSProperties> = {\n wrap: {\n position: \"fixed\", left: 16, bottom: 16, zIndex: 2147483000,\n display: \"flex\", alignItems: \"center\", gap: 2,\n padding: \"4px 6px 4px 10px\", borderRadius: 999,\n background: \"#0b1220\", color: \"#e5e7eb\", boxShadow: \"0 6px 24px rgba(0,0,0,.28)\",\n font: \"500 13px/1 system-ui, -apple-system, Segoe UI, Roboto, sans-serif\", letterSpacing: \".01em\",\n WebkitFontSmoothing: \"antialiased\",\n },\n brand: { display: \"inline-flex\", alignItems: \"center\", gap: 6, paddingRight: 8, marginRight: 4, borderRight: \"1px solid rgba(255,255,255,.18)\", color: \"#a5b4fc\", fontWeight: 600 },\n link: { color: \"#e5e7eb\", textDecoration: \"none\", padding: \"6px 8px\", borderRadius: 999 },\n close: { marginLeft: 2, width: 26, height: 26, border: 0, borderRadius: 999, background: \"transparent\", color: \"#9ca3af\", fontSize: 16, lineHeight: 1, cursor: \"pointer\" },\n};\n"]}
@@ -0,0 +1,18 @@
1
+ import * as react from 'react';
2
+
3
+ declare const STUDIO_MARKER_COOKIE = "rz-studio";
4
+ interface StudioBarProps {
5
+ /** Public URL prefix → content type id, e.g. { "/blog/": "posts", "/events/": "events" }. */
6
+ routes?: Record<string, string>;
7
+ /** Where Studio lives. Default "/admin". */
8
+ adminPath?: string;
9
+ /** Shown on the pill. Default "Studio". */
10
+ label?: string;
11
+ }
12
+ /** True when Studio's marker cookie is present (someone signed in to Studio in this browser). */
13
+ declare function hasStudioMarker(cookie?: string): boolean;
14
+ /** The Studio edit page for a public path, or null when the path isn't an item Studio manages. */
15
+ declare function editHrefFor(pathname: string, routes: Record<string, string>, adminPath?: string): string | null;
16
+ declare function StudioBar({ routes, adminPath, label }: StudioBarProps): react.JSX.Element | null;
17
+
18
+ export { STUDIO_MARKER_COOKIE, StudioBar, type StudioBarProps, editHrefFor, hasStudioMarker };
@@ -0,0 +1,18 @@
1
+ import * as react from 'react';
2
+
3
+ declare const STUDIO_MARKER_COOKIE = "rz-studio";
4
+ interface StudioBarProps {
5
+ /** Public URL prefix → content type id, e.g. { "/blog/": "posts", "/events/": "events" }. */
6
+ routes?: Record<string, string>;
7
+ /** Where Studio lives. Default "/admin". */
8
+ adminPath?: string;
9
+ /** Shown on the pill. Default "Studio". */
10
+ label?: string;
11
+ }
12
+ /** True when Studio's marker cookie is present (someone signed in to Studio in this browser). */
13
+ declare function hasStudioMarker(cookie?: string): boolean;
14
+ /** The Studio edit page for a public path, or null when the path isn't an item Studio manages. */
15
+ declare function editHrefFor(pathname: string, routes: Record<string, string>, adminPath?: string): string | null;
16
+ declare function StudioBar({ routes, adminPath, label }: StudioBarProps): react.JSX.Element | null;
17
+
18
+ export { STUDIO_MARKER_COOKIE, StudioBar, type StudioBarProps, editHrefFor, hasStudioMarker };
@@ -0,0 +1,77 @@
1
+ "use client";
2
+ import { useState, useEffect } from 'react';
3
+ import { jsxs, jsx } from 'react/jsx-runtime';
4
+
5
+ // src/bar/StudioBar.tsx
6
+ var STUDIO_MARKER_COOKIE = "rz-studio";
7
+ var HIDE_KEY = "rz-bar-hidden";
8
+ function hasStudioMarker(cookie = typeof document === "undefined" ? "" : document.cookie) {
9
+ return cookie.split(";").some((c) => c.trim().startsWith(`${STUDIO_MARKER_COOKIE}=1`));
10
+ }
11
+ function editHrefFor(pathname, routes, adminPath = "/admin") {
12
+ for (const [prefix, typeId] of Object.entries(routes)) {
13
+ if (!pathname.startsWith(prefix)) continue;
14
+ const rest = pathname.slice(prefix.length).replace(/\/+$/, "");
15
+ if (!rest || rest.includes("/")) continue;
16
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(rest)) continue;
17
+ return `${adminPath}/${typeId}/${rest}`;
18
+ }
19
+ return null;
20
+ }
21
+ function StudioBar({ routes = {}, adminPath = "/admin", label = "Studio" }) {
22
+ const [state, setState] = useState({ show: false, path: "" });
23
+ useEffect(() => {
24
+ const path = window.location.pathname;
25
+ if (path === adminPath || path.startsWith(`${adminPath}/`)) return;
26
+ let hidden = false;
27
+ try {
28
+ hidden = sessionStorage.getItem(HIDE_KEY) === "1";
29
+ } catch {
30
+ }
31
+ if (!hidden && hasStudioMarker()) setState({ show: true, path });
32
+ }, [adminPath]);
33
+ if (!state.show) return null;
34
+ const edit = editHrefFor(state.path, routes, adminPath);
35
+ const close = () => {
36
+ try {
37
+ sessionStorage.setItem(HIDE_KEY, "1");
38
+ } catch {
39
+ }
40
+ setState({ show: false, path: "" });
41
+ };
42
+ return /* @__PURE__ */ jsxs("div", { style: S.wrap, role: "region", "aria-label": "Studio", "data-studio-bar": true, children: [
43
+ /* @__PURE__ */ jsxs("span", { style: S.brand, children: [
44
+ /* @__PURE__ */ jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.4", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M12 3l7 3v5c0 5-3.5 8.5-7 10-3.5-1.5-7-5-7-10V6z" }) }),
45
+ label
46
+ ] }),
47
+ edit && /* @__PURE__ */ jsx("a", { href: edit, style: S.link, children: "Edit this page" }),
48
+ /* @__PURE__ */ jsx("a", { href: adminPath, style: S.link, children: "Dashboard" }),
49
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: close, style: S.close, "aria-label": "Hide the Studio bar for this tab", children: "\xD7" })
50
+ ] });
51
+ }
52
+ var S = {
53
+ wrap: {
54
+ position: "fixed",
55
+ left: 16,
56
+ bottom: 16,
57
+ zIndex: 2147483e3,
58
+ display: "flex",
59
+ alignItems: "center",
60
+ gap: 2,
61
+ padding: "4px 6px 4px 10px",
62
+ borderRadius: 999,
63
+ background: "#0b1220",
64
+ color: "#e5e7eb",
65
+ boxShadow: "0 6px 24px rgba(0,0,0,.28)",
66
+ font: "500 13px/1 system-ui, -apple-system, Segoe UI, Roboto, sans-serif",
67
+ letterSpacing: ".01em",
68
+ WebkitFontSmoothing: "antialiased"
69
+ },
70
+ brand: { display: "inline-flex", alignItems: "center", gap: 6, paddingRight: 8, marginRight: 4, borderRight: "1px solid rgba(255,255,255,.18)", color: "#a5b4fc", fontWeight: 600 },
71
+ link: { color: "#e5e7eb", textDecoration: "none", padding: "6px 8px", borderRadius: 999 },
72
+ close: { marginLeft: 2, width: 26, height: 26, border: 0, borderRadius: 999, background: "transparent", color: "#9ca3af", fontSize: 16, lineHeight: 1, cursor: "pointer" }
73
+ };
74
+
75
+ export { STUDIO_MARKER_COOKIE, StudioBar, editHrefFor, hasStudioMarker };
76
+ //# sourceMappingURL=index.js.map
77
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/bar/StudioBar.tsx"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["\"use client\";\n\nimport { useEffect, useState, type CSSProperties } from \"react\";\n\nexport const STUDIO_MARKER_COOKIE = \"rz-studio\";\nconst HIDE_KEY = \"rz-bar-hidden\";\n\nexport interface StudioBarProps {\n /** Public URL prefix → content type id, e.g. { \"/blog/\": \"posts\", \"/events/\": \"events\" }. */\n routes?: Record<string, string>;\n /** Where Studio lives. Default \"/admin\". */\n adminPath?: string;\n /** Shown on the pill. Default \"Studio\". */\n label?: string;\n}\n\n/** True when Studio's marker cookie is present (someone signed in to Studio in this browser). */\nexport function hasStudioMarker(cookie: string = typeof document === \"undefined\" ? \"\" : document.cookie): boolean {\n return cookie.split(\";\").some((c) => c.trim().startsWith(`${STUDIO_MARKER_COOKIE}=1`));\n}\n\n/** The Studio edit page for a public path, or null when the path isn't an item Studio manages. */\nexport function editHrefFor(pathname: string, routes: Record<string, string>, adminPath = \"/admin\"): string | null {\n for (const [prefix, typeId] of Object.entries(routes)) {\n if (!pathname.startsWith(prefix)) continue;\n const rest = pathname.slice(prefix.length).replace(/\\/+$/, \"\");\n if (!rest || rest.includes(\"/\")) continue; // the index page, or something nested — not an item\n if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(rest)) continue;\n return `${adminPath}/${typeId}/${rest}`;\n }\n return null;\n}\n\nexport function StudioBar({ routes = {}, adminPath = \"/admin\", label = \"Studio\" }: StudioBarProps) {\n const [state, setState] = useState<{ show: boolean; path: string }>({ show: false, path: \"\" });\n\n useEffect(() => {\n const path = window.location.pathname;\n if (path === adminPath || path.startsWith(`${adminPath}/`)) return;\n let hidden = false;\n try { hidden = sessionStorage.getItem(HIDE_KEY) === \"1\"; } catch { /* private mode */ }\n if (!hidden && hasStudioMarker()) setState({ show: true, path });\n }, [adminPath]);\n\n if (!state.show) return null;\n const edit = editHrefFor(state.path, routes, adminPath);\n const close = () => { try { sessionStorage.setItem(HIDE_KEY, \"1\"); } catch { /* private mode */ } setState({ show: false, path: \"\" }); };\n\n return (\n <div style={S.wrap} role=\"region\" aria-label=\"Studio\" data-studio-bar>\n <span style={S.brand}>\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.4\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\"><path d=\"M12 3l7 3v5c0 5-3.5 8.5-7 10-3.5-1.5-7-5-7-10V6z\" /></svg>\n {label}\n </span>\n {edit && <a href={edit} style={S.link}>Edit this page</a>}\n <a href={adminPath} style={S.link}>Dashboard</a>\n <button type=\"button\" onClick={close} style={S.close} aria-label=\"Hide the Studio bar for this tab\">×</button>\n </div>\n );\n}\n\n/* Self-contained styles: no dependency on the site's CSS, fonts or theme. */\nconst S: Record<string, CSSProperties> = {\n wrap: {\n position: \"fixed\", left: 16, bottom: 16, zIndex: 2147483000,\n display: \"flex\", alignItems: \"center\", gap: 2,\n padding: \"4px 6px 4px 10px\", borderRadius: 999,\n background: \"#0b1220\", color: \"#e5e7eb\", boxShadow: \"0 6px 24px rgba(0,0,0,.28)\",\n font: \"500 13px/1 system-ui, -apple-system, Segoe UI, Roboto, sans-serif\", letterSpacing: \".01em\",\n WebkitFontSmoothing: \"antialiased\",\n },\n brand: { display: \"inline-flex\", alignItems: \"center\", gap: 6, paddingRight: 8, marginRight: 4, borderRight: \"1px solid rgba(255,255,255,.18)\", color: \"#a5b4fc\", fontWeight: 600 },\n link: { color: \"#e5e7eb\", textDecoration: \"none\", padding: \"6px 8px\", borderRadius: 999 },\n close: { marginLeft: 2, width: 26, height: 26, border: 0, borderRadius: 999, background: \"transparent\", color: \"#9ca3af\", fontSize: 16, lineHeight: 1, cursor: \"pointer\" },\n};\n"]}
@@ -138,6 +138,35 @@ function Sidebar({ items, pinned = [], activeHref, basePath }) {
138
138
  )
139
139
  ] });
140
140
  }
141
+ function buttonClass(variant = "secondary", size = "md", extra) {
142
+ return ["rz-btn", `rz-btn--${variant}`, `rz-btn--${size}`, extra].filter(Boolean).join(" ");
143
+ }
144
+ function buttonStyle(variant = "secondary", size = "md") {
145
+ return { className: buttonClass(variant, size) };
146
+ }
147
+ function Button({ variant = "secondary", size = "md", className, type = "button", ...rest }) {
148
+ return /* @__PURE__ */ jsxRuntime.jsx("button", { type, className: buttonClass(variant, size, className), ...rest });
149
+ }
150
+ function LinkButton({ variant = "secondary", size = "md", className, external, forward, children, ...rest }) {
151
+ const extra = external ? { target: "_blank", rel: "noreferrer", ...rest } : rest;
152
+ return /* @__PURE__ */ jsxRuntime.jsxs("a", { className: buttonClass(variant, size, className), ...extra, children: [
153
+ children,
154
+ external && /* @__PURE__ */ jsxRuntime.jsx(OutboundArrow, {}),
155
+ forward && !external && /* @__PURE__ */ jsxRuntime.jsx(ForwardArrow, {})
156
+ ] });
157
+ }
158
+ function ForwardArrow() {
159
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { "aria-hidden": "true", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "rz-arrow rz-arrow--fwd", children: [
160
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M5 12h14" }),
161
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m13 6 6 6-6 6" })
162
+ ] });
163
+ }
164
+ function OutboundArrow() {
165
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { "aria-hidden": "true", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "rz-arrow", children: [
166
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M7 17 17 7" }),
167
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M7 7h10v10" })
168
+ ] });
169
+ }
141
170
  var THEME_KEY = "rz-admin-theme";
142
171
  function useTheme(defaultTheme = "light") {
143
172
  const [theme, setTheme] = react.useState(defaultTheme);
@@ -189,12 +218,16 @@ var HelpIcon = () => /* @__PURE__ */ jsxRuntime.jsxs("svg", { ...ico, children:
189
218
  /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M2 4h6a4 4 0 0 1 4 4v12a3 3 0 0 0-3-3H2z" }),
190
219
  /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M22 4h-6a4 4 0 0 0-4 4v12a3 3 0 0 1 3-3h7z" })
191
220
  ] });
221
+ var GlobeIcon = () => /* @__PURE__ */ jsxRuntime.jsxs("svg", { ...ico, children: [
222
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "9" }),
223
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18" })
224
+ ] });
192
225
  var OutIcon = () => /* @__PURE__ */ jsxRuntime.jsxs("svg", { ...ico, children: [
193
226
  /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" }),
194
227
  /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M16 17l5-5-5-5" }),
195
228
  /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M21 12H9" })
196
229
  ] });
197
- function AccountMenu({ user, role, theme, onTheme, signOutPath, accountHref, helpHref }) {
230
+ function AccountMenu({ user, role, theme, onTheme, signOutPath, accountHref, helpHref, siteUrl }) {
198
231
  const [open, setOpen] = react.useState(false);
199
232
  const wrap = react.useRef(null);
200
233
  react.useEffect(() => {
@@ -229,7 +262,12 @@ function AccountMenu({ user, role, theme, onTheme, signOutPath, accountHref, hel
229
262
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Theme" }),
230
263
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "rz-menu__end", children: /* @__PURE__ */ jsxRuntime.jsx(ThemeToggle, { theme, onChange: onTheme }) })
231
264
  ] }),
232
- (accountHref || helpHref) && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rz-menu__sep" }),
265
+ (siteUrl || accountHref || helpHref) && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rz-menu__sep" }),
266
+ siteUrl && /* @__PURE__ */ jsxRuntime.jsxs("a", { href: siteUrl, target: "_blank", rel: "noreferrer", role: "menuitem", className: "rz-menu__item", children: [
267
+ /* @__PURE__ */ jsxRuntime.jsx(GlobeIcon, {}),
268
+ "View website",
269
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "rz-menu__end", children: /* @__PURE__ */ jsxRuntime.jsx(OutboundArrow, {}) })
270
+ ] }),
233
271
  accountHref && /* @__PURE__ */ jsxRuntime.jsxs("a", { href: accountHref, role: "menuitem", className: "rz-menu__item", children: [
234
272
  /* @__PURE__ */ jsxRuntime.jsx(UserIcon, {}),
235
273
  "Account"
@@ -246,19 +284,28 @@ function AccountMenu({ user, role, theme, onTheme, signOutPath, accountHref, hel
246
284
  ] })
247
285
  ] });
248
286
  }
249
- function TopBar({ siteName, productName, logoUrl, homeHref, user, role, theme, onTheme, signOutPath, accountHref, helpHref }) {
287
+ function TopBar({ siteName, productName, logoUrl, homeHref, siteUrl, user, role, theme, onTheme, signOutPath, accountHref, helpHref }) {
250
288
  return /* @__PURE__ */ jsxRuntime.jsxs("header", { className: "rz-topbar", children: [
251
- /* @__PURE__ */ jsxRuntime.jsxs("a", { href: homeHref, className: "rz-topbar__brand", children: [
252
- logoUrl && /* @__PURE__ */ jsxRuntime.jsx("img", { src: logoUrl, alt: "", className: "rz-topbar__logo" }),
253
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: siteName }),
254
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "rz-topbar__product", children: productName })
255
- ] }),
289
+ /* @__PURE__ */ jsxRuntime.jsxs(
290
+ "a",
291
+ {
292
+ href: siteUrl || homeHref,
293
+ className: "rz-topbar__brand",
294
+ ...siteUrl ? { target: "_blank", rel: "noreferrer", title: "Open the live website in a new tab" } : {},
295
+ children: [
296
+ logoUrl && /* @__PURE__ */ jsxRuntime.jsx("img", { src: logoUrl, alt: "", className: "rz-topbar__logo" }),
297
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: siteName }),
298
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "rz-topbar__product", children: productName }),
299
+ siteUrl && /* @__PURE__ */ jsxRuntime.jsx(OutboundArrow, {})
300
+ ]
301
+ }
302
+ ),
256
303
  user && role && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rz-topbar__right", children: [
257
304
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "rz-topbar__greeting", "data-testid": "rz-greeting", children: [
258
305
  "Welcome back, ",
259
306
  firstNameOf(user)
260
307
  ] }),
261
- /* @__PURE__ */ jsxRuntime.jsx(AccountMenu, { user, role, theme, onTheme, signOutPath, accountHref, helpHref })
308
+ /* @__PURE__ */ jsxRuntime.jsx(AccountMenu, { user, role, theme, onTheme, signOutPath, accountHref, helpHref, siteUrl })
262
309
  ] })
263
310
  ] });
264
311
  }
@@ -318,6 +365,8 @@ var SHELL_CSS = `
318
365
  .rz-topbar__brand { display: flex; align-items: center; gap: .6rem; font-weight: 600; font-size: .9375rem; text-decoration: none; }
319
366
  .rz-topbar__logo { height: 22px; width: auto; display: block; }
320
367
  .rz-topbar__product { font-weight: 400; color: var(--text-secondary); }
368
+ .rz-topbar__brand .rz-arrow { color: var(--text-muted); opacity: 0; transition: opacity .15s, transform .15s; }
369
+ .rz-topbar__brand:hover .rz-arrow { opacity: 1; }
321
370
  .rz-topbar__right { margin-left: auto; display: flex; align-items: center; gap: 1rem; }
322
371
  .rz-topbar__greeting { color: var(--text-secondary); font-size: .8rem; }
323
372
  .rz-body { display: flex; flex: 1; min-height: 0; }
@@ -529,6 +578,7 @@ function AdminShell({
529
578
  signOutPath,
530
579
  accountHref,
531
580
  helpHref,
581
+ siteUrl,
532
582
  branding,
533
583
  defaultTheme = "light",
534
584
  children
@@ -545,6 +595,7 @@ function AdminShell({
545
595
  siteName: shownName,
546
596
  productName,
547
597
  logoUrl: shownLogo,
598
+ siteUrl,
548
599
  homeHref: basePath,
549
600
  user,
550
601
  role,
@@ -561,35 +612,6 @@ function AdminShell({
561
612
  ] })
562
613
  ] });
563
614
  }
564
- function buttonClass(variant = "secondary", size = "md", extra) {
565
- return ["rz-btn", `rz-btn--${variant}`, `rz-btn--${size}`, extra].filter(Boolean).join(" ");
566
- }
567
- function buttonStyle(variant = "secondary", size = "md") {
568
- return { className: buttonClass(variant, size) };
569
- }
570
- function Button({ variant = "secondary", size = "md", className, type = "button", ...rest }) {
571
- return /* @__PURE__ */ jsxRuntime.jsx("button", { type, className: buttonClass(variant, size, className), ...rest });
572
- }
573
- function LinkButton({ variant = "secondary", size = "md", className, external, forward, children, ...rest }) {
574
- const extra = external ? { target: "_blank", rel: "noreferrer", ...rest } : rest;
575
- return /* @__PURE__ */ jsxRuntime.jsxs("a", { className: buttonClass(variant, size, className), ...extra, children: [
576
- children,
577
- external && /* @__PURE__ */ jsxRuntime.jsx(OutboundArrow, {}),
578
- forward && !external && /* @__PURE__ */ jsxRuntime.jsx(ForwardArrow, {})
579
- ] });
580
- }
581
- function ForwardArrow() {
582
- return /* @__PURE__ */ jsxRuntime.jsxs("svg", { "aria-hidden": "true", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "rz-arrow rz-arrow--fwd", children: [
583
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M5 12h14" }),
584
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m13 6 6 6-6 6" })
585
- ] });
586
- }
587
- function OutboundArrow() {
588
- return /* @__PURE__ */ jsxRuntime.jsxs("svg", { "aria-hidden": "true", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "rz-arrow", children: [
589
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M7 17 17 7" }),
590
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M7 7h10v10" })
591
- ] });
592
- }
593
615
  function Card({ title, meta, note, children, actions, className }) {
594
616
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: ["rz-card", className].filter(Boolean).join(" "), children: [
595
617
  title && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rz-card__title", children: title }),