foss-design 0.3.5 → 0.4.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/dist/runtime/index.js +28 -0
- package/dist/viewer/assets/index-BYP2IITn.css +2 -0
- package/dist/viewer/assets/index-J92cmw_g.js +158 -0
- package/dist/viewer/index.html +2 -2
- package/dist/viewer-lib/index.js +774 -60
- package/dist/viewer-lib/styles.css +101 -0
- package/dist/viewer-lib/types/src/shared/types.d.ts +12 -0
- package/dist/viewer-lib/types/viewer/src/lib/frames.d.ts +9 -1
- package/dist/viewer-lib/types/viewer/src/lib/phone.d.ts +1 -0
- package/dist/viewer-lib/types/viewer/src/library.d.ts +2 -0
- package/dist/viewer-lib/types/viewer/src/pages/canvas/items.d.ts +4 -1
- package/dist/viewer-lib/types/viewer/src/pages/canvas/phone-view.d.ts +10 -0
- package/dist/viewer-lib/types/viewer/src/pages/canvas/play-page.d.ts +10 -0
- package/dist/viewer-lib/types/viewer/src/ui/sheet.d.ts +11 -0
- package/dist/viewer-lib/types/viewer/src/ui/toast.d.ts +1 -1
- package/package.json +1 -1
- package/dist/viewer/assets/index-Cbriu8my.js +0 -157
- package/dist/viewer/assets/index-YT1s8WdL.css +0 -2
package/dist/viewer-lib/index.js
CHANGED
|
@@ -2585,14 +2585,14 @@ function readPref() {
|
|
|
2585
2585
|
return "system";
|
|
2586
2586
|
}
|
|
2587
2587
|
var pref = new Store(readPref());
|
|
2588
|
-
var media = window.matchMedia("(prefers-color-scheme: dark)");
|
|
2588
|
+
var media$1 = window.matchMedia("(prefers-color-scheme: dark)");
|
|
2589
2589
|
function resolve(value) {
|
|
2590
|
-
return value === "system" ? media.matches ? "dark" : "light" : value;
|
|
2590
|
+
return value === "system" ? media$1.matches ? "dark" : "light" : value;
|
|
2591
2591
|
}
|
|
2592
2592
|
function apply() {
|
|
2593
2593
|
document.documentElement.dataset.theme = resolve(pref.get());
|
|
2594
2594
|
}
|
|
2595
|
-
media.addEventListener("change", apply);
|
|
2595
|
+
media$1.addEventListener("change", apply);
|
|
2596
2596
|
pref.subscribe(apply);
|
|
2597
2597
|
apply();
|
|
2598
2598
|
function setThemePref(value) {
|
|
@@ -2608,9 +2608,9 @@ function useThemePref() {
|
|
|
2608
2608
|
function useTheme() {
|
|
2609
2609
|
const value = useStore(pref);
|
|
2610
2610
|
const systemDark = useSyncExternalStore((listener) => {
|
|
2611
|
-
media.addEventListener("change", listener);
|
|
2612
|
-
return () => media.removeEventListener("change", listener);
|
|
2613
|
-
}, () => media.matches);
|
|
2611
|
+
media$1.addEventListener("change", listener);
|
|
2612
|
+
return () => media$1.removeEventListener("change", listener);
|
|
2613
|
+
}, () => media$1.matches);
|
|
2614
2614
|
return value === "system" ? systemDark ? "dark" : "light" : value;
|
|
2615
2615
|
}
|
|
2616
2616
|
//#endregion
|
|
@@ -3094,7 +3094,8 @@ function SearchButton({ width = 240, label = "Search and jump" }) {
|
|
|
3094
3094
|
type: "button",
|
|
3095
3095
|
onClick: openPalette,
|
|
3096
3096
|
style: { width },
|
|
3097
|
-
|
|
3097
|
+
"aria-label": label,
|
|
3098
|
+
className: "flex h-ctrl min-w-0 shrink cursor-pointer items-center gap-2 rounded-[6px] border border-rule bg-soft pr-1.5 pl-3 text-ctrl text-muted transition-colors hover:border-rule-strong max-lg:w-auto! max-md:w-ctrl! max-md:justify-center max-md:border-0 max-md:bg-transparent max-md:p-0",
|
|
3098
3099
|
children: [
|
|
3099
3100
|
/* @__PURE__ */ jsx(Icon, {
|
|
3100
3101
|
name: "search",
|
|
@@ -3104,7 +3105,10 @@ function SearchButton({ width = 240, label = "Search and jump" }) {
|
|
|
3104
3105
|
className: "min-w-0 grow truncate text-left max-lg:hidden",
|
|
3105
3106
|
children: label
|
|
3106
3107
|
}),
|
|
3107
|
-
/* @__PURE__ */ jsx(
|
|
3108
|
+
/* @__PURE__ */ jsx("span", {
|
|
3109
|
+
className: "flex max-md:hidden",
|
|
3110
|
+
children: /* @__PURE__ */ jsx(Kbd, { children: MOD_KEY === "⌘" ? "⌘K" : "Ctrl K" })
|
|
3111
|
+
})
|
|
3108
3112
|
]
|
|
3109
3113
|
});
|
|
3110
3114
|
}
|
|
@@ -5637,12 +5641,12 @@ function useIsHydrated() {
|
|
|
5637
5641
|
}
|
|
5638
5642
|
__name$6(useIsHydrated, "useIsHydrated");
|
|
5639
5643
|
var useReactSyncExternalStore = React[" useSyncExternalStore ".trim().toString()];
|
|
5640
|
-
function subscribe() {
|
|
5644
|
+
function subscribe$1() {
|
|
5641
5645
|
return () => {};
|
|
5642
5646
|
}
|
|
5643
|
-
__name$6(subscribe, "subscribe");
|
|
5647
|
+
__name$6(subscribe$1, "subscribe");
|
|
5644
5648
|
function useIsHydratedModern() {
|
|
5645
|
-
return useReactSyncExternalStore(subscribe, () => true, () => false);
|
|
5649
|
+
return useReactSyncExternalStore(subscribe$1, () => true, () => false);
|
|
5646
5650
|
}
|
|
5647
5651
|
__name$6(useIsHydratedModern, "useIsHydratedModern");
|
|
5648
5652
|
var useIsHydrated2 = typeof useReactSyncExternalStore === "function" ? useIsHydratedModern : useIsHydrated;
|
|
@@ -6717,24 +6721,57 @@ function AppTopBar() {
|
|
|
6717
6721
|
const project = useProject().data;
|
|
6718
6722
|
const { slots } = useViewer();
|
|
6719
6723
|
const [location] = useLocation();
|
|
6720
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
6721
|
-
|
|
6722
|
-
|
|
6723
|
-
|
|
6724
|
-
|
|
6725
|
-
|
|
6726
|
-
|
|
6727
|
-
|
|
6728
|
-
|
|
6729
|
-
|
|
6730
|
-
|
|
6731
|
-
|
|
6732
|
-
|
|
6733
|
-
|
|
6734
|
-
|
|
6735
|
-
|
|
6724
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
6725
|
+
/* @__PURE__ */ jsxs(Bar, { children: [
|
|
6726
|
+
slots.barStart,
|
|
6727
|
+
/* @__PURE__ */ jsxs("div", {
|
|
6728
|
+
className: "flex min-w-0 items-center gap-6",
|
|
6729
|
+
children: [slots.brand ?? /* @__PURE__ */ jsx(Wordmark, {
|
|
6730
|
+
name: project?.name,
|
|
6731
|
+
version: project?.version
|
|
6732
|
+
}), /* @__PURE__ */ jsx(Tabs, { location })]
|
|
6733
|
+
}),
|
|
6734
|
+
/* @__PURE__ */ jsxs("div", {
|
|
6735
|
+
className: "ml-auto flex items-center gap-2",
|
|
6736
|
+
children: [
|
|
6737
|
+
/* @__PURE__ */ jsx(SearchButton, {}),
|
|
6738
|
+
/* @__PURE__ */ jsx(ThemeMenu, {}),
|
|
6739
|
+
slots.barEnd
|
|
6740
|
+
]
|
|
6741
|
+
})
|
|
6742
|
+
] }),
|
|
6743
|
+
/* @__PURE__ */ jsx(PhoneTabs, { location }),
|
|
6744
|
+
slots.belowBar
|
|
6745
|
+
] });
|
|
6746
|
+
}
|
|
6747
|
+
/** The section tabs on a phone, where the bar has no room for them: a row under it. */
|
|
6748
|
+
function PhoneTabs({ location }) {
|
|
6749
|
+
return /* @__PURE__ */ jsx("nav", {
|
|
6750
|
+
className: "sticky top-[52px] z-30 hidden h-10 shrink-0 items-stretch border-b border-rule bg-surface px-2 max-md:flex",
|
|
6751
|
+
children: TABS.map((tab) => {
|
|
6752
|
+
const on = tab.match(location);
|
|
6753
|
+
return /* @__PURE__ */ jsxs(Link, {
|
|
6754
|
+
href: tab.to,
|
|
6755
|
+
"aria-current": on ? "page" : void 0,
|
|
6756
|
+
className: cn("relative flex grow items-center justify-center text-[13.5px] no-underline", on ? "font-medium text-ink" : "text-ink2"),
|
|
6757
|
+
children: [tab.label, on ? /* @__PURE__ */ jsx("span", {
|
|
6758
|
+
"aria-hidden": true,
|
|
6759
|
+
className: "absolute inset-x-6 bottom-0 h-[2px] rounded-full bg-action"
|
|
6760
|
+
}) : null]
|
|
6761
|
+
}, tab.to);
|
|
6736
6762
|
})
|
|
6737
|
-
|
|
6763
|
+
});
|
|
6764
|
+
}
|
|
6765
|
+
//#endregion
|
|
6766
|
+
//#region viewer/src/lib/phone.ts
|
|
6767
|
+
/** Phones: under Tailwind's `md`. The canvas gives way to one screen at a time there. */
|
|
6768
|
+
var media = window.matchMedia("(max-width: 767px)");
|
|
6769
|
+
var subscribe = (onChange) => {
|
|
6770
|
+
media.addEventListener("change", onChange);
|
|
6771
|
+
return () => media.removeEventListener("change", onChange);
|
|
6772
|
+
};
|
|
6773
|
+
function useIsPhone() {
|
|
6774
|
+
return useSyncExternalStore(subscribe, () => media.matches);
|
|
6738
6775
|
}
|
|
6739
6776
|
//#endregion
|
|
6740
6777
|
//#region ../../node_modules/.pnpm/@radix-ui+react-popover@1.1.23_@types+react-dom@19.3.0_@types+react@19.3.0__@types+reac_bdb8fe50570f35efe0254f5915b263fd/node_modules/@radix-ui/react-popover/dist/index.mjs
|
|
@@ -6999,6 +7036,10 @@ function Toaster() {
|
|
|
6999
7036
|
name: "alert",
|
|
7000
7037
|
size: 15,
|
|
7001
7038
|
className: "text-danger-text"
|
|
7039
|
+
}) : item.tone === "info" ? /* @__PURE__ */ jsx(Icon, {
|
|
7040
|
+
name: "info",
|
|
7041
|
+
size: 15,
|
|
7042
|
+
className: "text-muted"
|
|
7002
7043
|
}) : /* @__PURE__ */ jsx(Icon, {
|
|
7003
7044
|
name: "check",
|
|
7004
7045
|
size: 15,
|
|
@@ -7106,6 +7147,31 @@ function frameSrc(url, theme, extra) {
|
|
|
7106
7147
|
function absoluteUrl(url) {
|
|
7107
7148
|
return new URL(url, document.baseURI).toString();
|
|
7108
7149
|
}
|
|
7150
|
+
/**
|
|
7151
|
+
* A link or form the runtime kept from leaving its frame. A path whose last part names a screen
|
|
7152
|
+
* or URL item on the canvas (`/settings`, `settings.html`) goes there; a link to another site
|
|
7153
|
+
* opens in a new tab; anything else says where it pointed.
|
|
7154
|
+
*/
|
|
7155
|
+
function followLink(message, canvas, go) {
|
|
7156
|
+
if (message.form) {
|
|
7157
|
+
toast("Forms are not sent from a screen", void 0, "info");
|
|
7158
|
+
return;
|
|
7159
|
+
}
|
|
7160
|
+
if (message.path === null) {
|
|
7161
|
+
const tab = window.open(message.href, "_blank");
|
|
7162
|
+
if (tab) tab.opener = null;
|
|
7163
|
+
else toast("Link to another site", message.href, "info");
|
|
7164
|
+
return;
|
|
7165
|
+
}
|
|
7166
|
+
const path = message.path.split(/[?#]/)[0].replace(/\/+$/, "");
|
|
7167
|
+
let name = path.slice(path.lastIndexOf("/") + 1).replace(/\.html?$/, "");
|
|
7168
|
+
try {
|
|
7169
|
+
name = decodeURIComponent(name);
|
|
7170
|
+
} catch {}
|
|
7171
|
+
const ids = new Set(canvas.pages.flatMap((page) => page.sections.flatMap((section) => section.items.filter((item) => item.kind === "screen" || item.kind === "url").map((item) => item.id))));
|
|
7172
|
+
if (name && ids.has(name)) go(name);
|
|
7173
|
+
else toast("Not a screen on this canvas", message.path, "info");
|
|
7174
|
+
}
|
|
7109
7175
|
//#endregion
|
|
7110
7176
|
//#region viewer/src/components/inspector.tsx
|
|
7111
7177
|
/**
|
|
@@ -7807,7 +7873,7 @@ function KV({ label, children, labelWidth = 180, minH = 40, end = false }) {
|
|
|
7807
7873
|
return /* @__PURE__ */ jsxs("div", {
|
|
7808
7874
|
className: "grid content-center items-baseline gap-x-4 border-t border-rule px-4 py-2",
|
|
7809
7875
|
style: {
|
|
7810
|
-
gridTemplateColumns:
|
|
7876
|
+
gridTemplateColumns: `min(${labelWidth}px, 42%) minmax(0, 1fr)`,
|
|
7811
7877
|
minHeight: minH
|
|
7812
7878
|
},
|
|
7813
7879
|
children: [/* @__PURE__ */ jsx("span", {
|
|
@@ -7967,7 +8033,7 @@ function PageHead({ title, sub, right, badge, crumbs }) {
|
|
|
7967
8033
|
}), badge]
|
|
7968
8034
|
}),
|
|
7969
8035
|
sub ? /* @__PURE__ */ jsx("div", {
|
|
7970
|
-
className: "text-[13px] text-muted",
|
|
8036
|
+
className: "text-[13px] [overflow-wrap:anywhere] text-muted",
|
|
7971
8037
|
children: sub
|
|
7972
8038
|
}) : null
|
|
7973
8039
|
]
|
|
@@ -8779,7 +8845,7 @@ function FullPage({ canvasId, itemId }) {
|
|
|
8779
8845
|
else if (message.type === "go") {
|
|
8780
8846
|
const id = message.target.includes("/") ? message.target.split("/")[1] : message.target;
|
|
8781
8847
|
if (items.some((entry) => entry.id === id)) navigate(fullHref(canvasId, id, theme), { replace: true });
|
|
8782
|
-
}
|
|
8848
|
+
} else if (message.type === "link" && canvas) followLink(message, canvas, (id) => navigate(fullHref(canvasId, id, theme), { replace: true }));
|
|
8783
8849
|
});
|
|
8784
8850
|
}, [url, item?.kind]);
|
|
8785
8851
|
if (error) return /* @__PURE__ */ jsx(NotFound, {
|
|
@@ -11137,6 +11203,9 @@ var FrameItem = memo(function FrameItem({ placed, mode, thumb, theme, store, eve
|
|
|
11137
11203
|
case "go":
|
|
11138
11204
|
events.onGo(item.id, message.target);
|
|
11139
11205
|
break;
|
|
11206
|
+
case "link":
|
|
11207
|
+
events.onLink(item.id, message);
|
|
11208
|
+
break;
|
|
11140
11209
|
case "wheel":
|
|
11141
11210
|
events.onWheel(frame, message);
|
|
11142
11211
|
break;
|
|
@@ -11917,7 +11986,7 @@ function isTyping(target) {
|
|
|
11917
11986
|
//#endregion
|
|
11918
11987
|
//#region viewer/src/pages/canvas/canvas-page.tsx
|
|
11919
11988
|
var query = new URLSearchParams(window.location.search);
|
|
11920
|
-
var CAPTURE = query.has("capture");
|
|
11989
|
+
var CAPTURE$1 = query.has("capture");
|
|
11921
11990
|
var cameraKey = (canvas, page) => `foss-design.camera.${canvas}.${page}`;
|
|
11922
11991
|
function loadCamera(key) {
|
|
11923
11992
|
try {
|
|
@@ -11953,7 +12022,7 @@ function CanvasView({ canvas, page }) {
|
|
|
11953
12022
|
const camera = useMemo(() => new CameraStore(), []);
|
|
11954
12023
|
const apiRef = useRef(null);
|
|
11955
12024
|
const [theme, setTheme] = useScreenTheme(canvas);
|
|
11956
|
-
const [sidebar, setSidebar] = useState(() => !CAPTURE && window.innerWidth > 900);
|
|
12025
|
+
const [sidebar, setSidebar] = useState(() => !CAPTURE$1 && window.innerWidth > 900);
|
|
11957
12026
|
const [inspectOn, setInspect] = useState(false);
|
|
11958
12027
|
const sizes = useStore(store, (state) => state.sizes);
|
|
11959
12028
|
const layout = useMemo(() => page ? layoutPage(page, sizes) : {
|
|
@@ -11997,7 +12066,7 @@ function CanvasView({ canvas, page }) {
|
|
|
11997
12066
|
if (placed.current || !layout.bounds.w) return;
|
|
11998
12067
|
placed.current = true;
|
|
11999
12068
|
const focus = takeFocus(canvas.id);
|
|
12000
|
-
const saved = CAPTURE ? null : loadCamera(key);
|
|
12069
|
+
const saved = CAPTURE$1 ? null : loadCamera(key);
|
|
12001
12070
|
if (focus) {
|
|
12002
12071
|
store.set((state) => ({
|
|
12003
12072
|
...state,
|
|
@@ -12005,7 +12074,7 @@ function CanvasView({ canvas, page }) {
|
|
|
12005
12074
|
}));
|
|
12006
12075
|
requestAnimationFrame(() => apiRef.current?.fitItem(focus, false));
|
|
12007
12076
|
} else if (saved && saved.z >= camera.minZoom) camera.set(saved);
|
|
12008
|
-
else requestAnimationFrame(() => CAPTURE ? apiRef.current?.fitAll(false) : apiRef.current?.openView());
|
|
12077
|
+
else requestAnimationFrame(() => CAPTURE$1 ? apiRef.current?.fitAll(false) : apiRef.current?.openView());
|
|
12009
12078
|
}, [
|
|
12010
12079
|
layout,
|
|
12011
12080
|
camera,
|
|
@@ -12014,7 +12083,7 @@ function CanvasView({ canvas, page }) {
|
|
|
12014
12083
|
store
|
|
12015
12084
|
]);
|
|
12016
12085
|
useEffect(() => {
|
|
12017
|
-
if (CAPTURE) return;
|
|
12086
|
+
if (CAPTURE$1) return;
|
|
12018
12087
|
let timer;
|
|
12019
12088
|
const off = camera.subscribe((c) => {
|
|
12020
12089
|
clearTimeout(timer);
|
|
@@ -12078,6 +12147,9 @@ function CanvasView({ canvas, page }) {
|
|
|
12078
12147
|
}
|
|
12079
12148
|
console.warn(`[design] go("${target}"): no such screen`);
|
|
12080
12149
|
},
|
|
12150
|
+
onLink(from, message) {
|
|
12151
|
+
followLink(message, canvas, (id) => this.onGo(from, id));
|
|
12152
|
+
},
|
|
12081
12153
|
onWheel(frame, message) {
|
|
12082
12154
|
const root = frame.closest(".canvas-root");
|
|
12083
12155
|
if (!root) return;
|
|
@@ -12104,7 +12176,7 @@ function CanvasView({ canvas, page }) {
|
|
|
12104
12176
|
navigate
|
|
12105
12177
|
]);
|
|
12106
12178
|
useEffect(() => {
|
|
12107
|
-
if (!CAPTURE) return;
|
|
12179
|
+
if (!CAPTURE$1) return;
|
|
12108
12180
|
const started = Date.now();
|
|
12109
12181
|
const timer = setInterval(() => {
|
|
12110
12182
|
const frames = store.get().frames;
|
|
@@ -12187,7 +12259,7 @@ function CanvasView({ canvas, page }) {
|
|
|
12187
12259
|
inspectOn,
|
|
12188
12260
|
inspection
|
|
12189
12261
|
]);
|
|
12190
|
-
if (CAPTURE) return /* @__PURE__ */ jsx("div", {
|
|
12262
|
+
if (CAPTURE$1) return /* @__PURE__ */ jsx("div", {
|
|
12191
12263
|
className: "flex h-dvh",
|
|
12192
12264
|
children: /* @__PURE__ */ jsx(Viewport, {
|
|
12193
12265
|
layout,
|
|
@@ -12403,6 +12475,79 @@ function useScreenFrames(frameEls, layout) {
|
|
|
12403
12475
|
}, [frameEls, layout]);
|
|
12404
12476
|
}
|
|
12405
12477
|
//#endregion
|
|
12478
|
+
//#region viewer/src/ui/sheet.tsx
|
|
12479
|
+
/**
|
|
12480
|
+
* A panel sliding up from the bottom edge, for phones: lists to pick from and actions. Like
|
|
12481
|
+
* Dialog, focus stays inside, and Escape, a tap on the dimmed page or the close button close it.
|
|
12482
|
+
*/
|
|
12483
|
+
function Sheet({ open, onOpenChange, title, children }) {
|
|
12484
|
+
return /* @__PURE__ */ jsx(Dialog$1, {
|
|
12485
|
+
open,
|
|
12486
|
+
onOpenChange,
|
|
12487
|
+
children: /* @__PURE__ */ jsx(AnimatePresence, { children: open ? /* @__PURE__ */ jsxs(DialogPortal, {
|
|
12488
|
+
forceMount: true,
|
|
12489
|
+
children: [/* @__PURE__ */ jsx(DialogOverlay, {
|
|
12490
|
+
asChild: true,
|
|
12491
|
+
forceMount: true,
|
|
12492
|
+
children: /* @__PURE__ */ jsx(motion.div, {
|
|
12493
|
+
initial: { opacity: 0 },
|
|
12494
|
+
animate: { opacity: 1 },
|
|
12495
|
+
exit: { opacity: 0 },
|
|
12496
|
+
transition: { duration: .16 },
|
|
12497
|
+
className: "fixed inset-0 z-50 bg-overlay"
|
|
12498
|
+
})
|
|
12499
|
+
}), /* @__PURE__ */ jsx(DialogContent, {
|
|
12500
|
+
asChild: true,
|
|
12501
|
+
forceMount: true,
|
|
12502
|
+
"aria-describedby": void 0,
|
|
12503
|
+
children: /* @__PURE__ */ jsxs(motion.div, {
|
|
12504
|
+
initial: { y: "100%" },
|
|
12505
|
+
animate: { y: 0 },
|
|
12506
|
+
exit: {
|
|
12507
|
+
y: "100%",
|
|
12508
|
+
transition: {
|
|
12509
|
+
duration: .18,
|
|
12510
|
+
ease: "easeIn"
|
|
12511
|
+
}
|
|
12512
|
+
},
|
|
12513
|
+
transition: {
|
|
12514
|
+
type: "spring",
|
|
12515
|
+
stiffness: 420,
|
|
12516
|
+
damping: 40
|
|
12517
|
+
},
|
|
12518
|
+
className: "fixed inset-x-0 bottom-0 z-50 flex max-h-[82dvh] flex-col rounded-t-[14px] border-t border-rule bg-surface pb-[env(safe-area-inset-bottom)] shadow-pop outline-none",
|
|
12519
|
+
children: [
|
|
12520
|
+
/* @__PURE__ */ jsx("div", {
|
|
12521
|
+
className: "flex justify-center pt-2",
|
|
12522
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
12523
|
+
"aria-hidden": true,
|
|
12524
|
+
className: "h-1 w-9 rounded-full bg-rule-strong"
|
|
12525
|
+
})
|
|
12526
|
+
}),
|
|
12527
|
+
/* @__PURE__ */ jsxs("div", {
|
|
12528
|
+
className: "flex min-h-[48px] items-center justify-between gap-3 pr-2 pl-4",
|
|
12529
|
+
children: [/* @__PURE__ */ jsx(DialogTitle, {
|
|
12530
|
+
className: "m-0 truncate text-[15px] font-semibold",
|
|
12531
|
+
children: title
|
|
12532
|
+
}), /* @__PURE__ */ jsx(DialogClose, {
|
|
12533
|
+
asChild: true,
|
|
12534
|
+
children: /* @__PURE__ */ jsx(IconButton, {
|
|
12535
|
+
icon: "x",
|
|
12536
|
+
label: "Close"
|
|
12537
|
+
})
|
|
12538
|
+
})]
|
|
12539
|
+
}),
|
|
12540
|
+
/* @__PURE__ */ jsx("div", {
|
|
12541
|
+
className: "min-h-0 overflow-y-auto overscroll-contain border-t border-rule",
|
|
12542
|
+
children
|
|
12543
|
+
})
|
|
12544
|
+
]
|
|
12545
|
+
})
|
|
12546
|
+
})]
|
|
12547
|
+
}) : null })
|
|
12548
|
+
});
|
|
12549
|
+
}
|
|
12550
|
+
//#endregion
|
|
12406
12551
|
//#region viewer/src/pages/canvas/play-page.tsx
|
|
12407
12552
|
function framesOf(canvas, pageId) {
|
|
12408
12553
|
return (pageId ? canvas.pages.filter((p) => p.id === pageId) : canvas.pages).flatMap((page) => page.sections.flatMap((section) => section.items.filter((item) => item.kind === "screen" || item.kind === "url").map((item) => ({
|
|
@@ -12509,7 +12654,10 @@ function Player({ canvas, itemId }) {
|
|
|
12509
12654
|
const [pagePart, idPart] = message.target.includes("/") ? message.target.split("/") : [null, message.target];
|
|
12510
12655
|
const target = all.find((e) => e.item.id === idPart && (!pagePart || e.pageId === pagePart));
|
|
12511
12656
|
if (target) navigate(playHref(canvas.id, target.item.id, target.pageId));
|
|
12512
|
-
}
|
|
12657
|
+
} else if (message.type === "link") followLink(message, canvas, (id) => {
|
|
12658
|
+
const target = all.find((e) => e.item.id === id);
|
|
12659
|
+
if (target) navigate(playHref(canvas.id, target.item.id, target.pageId));
|
|
12660
|
+
});
|
|
12513
12661
|
});
|
|
12514
12662
|
}, [
|
|
12515
12663
|
item,
|
|
@@ -12705,6 +12853,441 @@ function Player({ canvas, itemId }) {
|
|
|
12705
12853
|
});
|
|
12706
12854
|
}
|
|
12707
12855
|
//#endregion
|
|
12856
|
+
//#region viewer/src/pages/canvas/phone-view.tsx
|
|
12857
|
+
/** Room around the screen on the stage, per side. */
|
|
12858
|
+
var GUTTER = 12;
|
|
12859
|
+
/**
|
|
12860
|
+
* A canvas on a phone: no board to pan, one screen at a time as in play mode. The screen fills
|
|
12861
|
+
* the width and scrolls; the bottom bar steps through every screen of the canvas and opens the
|
|
12862
|
+
* list of them. Serves `/c/:canvas`, its pages and `/c/:canvas/play/:item` under 768px.
|
|
12863
|
+
*/
|
|
12864
|
+
function PhoneCanvas({ canvasId, pageId, itemId }) {
|
|
12865
|
+
const { data: canvas, error, isLoading } = useCanvas(canvasId);
|
|
12866
|
+
if (error) return /* @__PURE__ */ jsx(NotFound, {
|
|
12867
|
+
title: "Canvas not found",
|
|
12868
|
+
text: error.message
|
|
12869
|
+
});
|
|
12870
|
+
if (isLoading || !canvas) return /* @__PURE__ */ jsx("div", { className: "canvas-dots h-dvh" });
|
|
12871
|
+
return /* @__PURE__ */ jsx(PhoneViewer, {
|
|
12872
|
+
canvas,
|
|
12873
|
+
pageId,
|
|
12874
|
+
itemId
|
|
12875
|
+
});
|
|
12876
|
+
}
|
|
12877
|
+
function PhoneViewer({ canvas, pageId, itemId }) {
|
|
12878
|
+
const [, navigate] = useLocation();
|
|
12879
|
+
const { frameSandbox, slots, scope } = useViewer();
|
|
12880
|
+
const appTheme = useTheme();
|
|
12881
|
+
const all = useMemo(() => framesOf(canvas, null), [canvas]);
|
|
12882
|
+
const index = Math.max(0, itemId ? all.findIndex((entry) => entry.item.id === itemId) : pageId ? all.findIndex((entry) => entry.pageId === pageId) : 0);
|
|
12883
|
+
const entry = all[index];
|
|
12884
|
+
const item = entry?.item;
|
|
12885
|
+
const [theme, setTheme] = useState(canvas.theme ?? appTheme);
|
|
12886
|
+
const [listOpen, setListOpen] = useState(false);
|
|
12887
|
+
const [errors, setErrors] = useState([]);
|
|
12888
|
+
const [stageWidth, setStageWidth] = useState(0);
|
|
12889
|
+
const [autoHeight, setAutoHeight] = useState(null);
|
|
12890
|
+
const [direction, setDirection] = useState(0);
|
|
12891
|
+
const [actual, setActual] = useState(false);
|
|
12892
|
+
const stage = useRef(null);
|
|
12893
|
+
const frame = useRef(null);
|
|
12894
|
+
const [frameEl, setFrameEl] = useState(null);
|
|
12895
|
+
const setFrame = useCallback((el) => {
|
|
12896
|
+
if (!el) return;
|
|
12897
|
+
frame.current = el;
|
|
12898
|
+
setFrameEl(el);
|
|
12899
|
+
}, []);
|
|
12900
|
+
const show = useCallback((target, step = 0) => {
|
|
12901
|
+
setDirection(step);
|
|
12902
|
+
navigate(playHref(canvas.id, target.item.id, target.pageId), { replace: true });
|
|
12903
|
+
}, [canvas.id, navigate]);
|
|
12904
|
+
const move = (step) => {
|
|
12905
|
+
if (all.length < 2) return;
|
|
12906
|
+
show(all[(index + step + all.length) % all.length], step);
|
|
12907
|
+
};
|
|
12908
|
+
const goTo = (id) => {
|
|
12909
|
+
const target = all.find((e) => e.item.id === id);
|
|
12910
|
+
if (target) show(target, all.indexOf(target) > index ? 1 : -1);
|
|
12911
|
+
};
|
|
12912
|
+
useLayoutEffect(() => {
|
|
12913
|
+
const el = stage.current;
|
|
12914
|
+
if (!el) return;
|
|
12915
|
+
const observer = new ResizeObserver(() => setStageWidth(el.clientWidth));
|
|
12916
|
+
observer.observe(el);
|
|
12917
|
+
return () => observer.disconnect();
|
|
12918
|
+
}, []);
|
|
12919
|
+
const frameTheme = item?.kind === "screen" ? item.theme ?? theme : theme;
|
|
12920
|
+
const [openTheme] = useState(frameTheme);
|
|
12921
|
+
const src = item ? item.kind === "screen" ? frameSrc(item.url, openTheme) : item.url : "";
|
|
12922
|
+
const openUrl = useOpenUrl(canvas.id, item, frameTheme);
|
|
12923
|
+
useEffect(() => {
|
|
12924
|
+
setErrors([]);
|
|
12925
|
+
setAutoHeight(item?.kind === "screen" ? item.measuredHeight ?? null : null);
|
|
12926
|
+
stage.current?.scrollTo({ top: 0 });
|
|
12927
|
+
const el = frame.current;
|
|
12928
|
+
if (!el || item?.kind !== "screen") return;
|
|
12929
|
+
return listenToFrame(el, (message) => {
|
|
12930
|
+
if (message.type === "size") setAutoHeight(message.height);
|
|
12931
|
+
else if (message.type === "error") setErrors((list) => list.includes(message.message) ? list : [...list, message.message]);
|
|
12932
|
+
else if (message.type === "updated") setErrors([]);
|
|
12933
|
+
else if (message.type === "go") goTo(message.target.includes("/") ? message.target.split("/")[1] : message.target);
|
|
12934
|
+
else if (message.type === "link") followLink(message, canvas, goTo);
|
|
12935
|
+
});
|
|
12936
|
+
}, [frameEl, src]);
|
|
12937
|
+
useEffect(() => {
|
|
12938
|
+
if (item?.kind === "screen") sendTheme(frame.current, frameTheme);
|
|
12939
|
+
}, [frameTheme, item]);
|
|
12940
|
+
useEffect(() => {
|
|
12941
|
+
const onKey = (event) => {
|
|
12942
|
+
if (document.querySelector("[role=\"dialog\"]")) return;
|
|
12943
|
+
if (event.key === "ArrowRight") move(1);
|
|
12944
|
+
else if (event.key === "ArrowLeft") move(-1);
|
|
12945
|
+
};
|
|
12946
|
+
window.addEventListener("keydown", onKey);
|
|
12947
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
12948
|
+
});
|
|
12949
|
+
const swipe = useRef(null);
|
|
12950
|
+
const onTouchStart = (event) => {
|
|
12951
|
+
const touch = event.touches[0];
|
|
12952
|
+
swipe.current = touch ? {
|
|
12953
|
+
x: touch.clientX,
|
|
12954
|
+
y: touch.clientY
|
|
12955
|
+
} : null;
|
|
12956
|
+
};
|
|
12957
|
+
const onTouchEnd = (event) => {
|
|
12958
|
+
const start = swipe.current;
|
|
12959
|
+
const touch = event.changedTouches[0];
|
|
12960
|
+
swipe.current = null;
|
|
12961
|
+
if (!start || !touch) return;
|
|
12962
|
+
const dx = touch.clientX - start.x;
|
|
12963
|
+
const dy = touch.clientY - start.y;
|
|
12964
|
+
if (Math.abs(dx) > 56 && Math.abs(dx) > Math.abs(dy) * 1.5) move(dx < 0 ? 1 : -1);
|
|
12965
|
+
};
|
|
12966
|
+
if (!item || !entry) return /* @__PURE__ */ jsxs("div", {
|
|
12967
|
+
className: "flex h-dvh flex-col",
|
|
12968
|
+
children: [/* @__PURE__ */ jsx(PhoneBar, {
|
|
12969
|
+
canvas,
|
|
12970
|
+
title: canvas.title,
|
|
12971
|
+
sub: null
|
|
12972
|
+
}), /* @__PURE__ */ jsx(NotFound, {
|
|
12973
|
+
title: "No screens here",
|
|
12974
|
+
text: "This canvas has no screens to show yet."
|
|
12975
|
+
})]
|
|
12976
|
+
});
|
|
12977
|
+
const width = item.frame.width;
|
|
12978
|
+
const fitScale = stageWidth ? Math.min(1, (stageWidth - 24) / width) : 0;
|
|
12979
|
+
const wide = fitScale > 0 && fitScale < .8;
|
|
12980
|
+
const scale = wide && actual ? 1 : fitScale;
|
|
12981
|
+
const height = item.frame.height === "auto" ? Math.max(autoHeight ?? 0, 480) : item.frame.height;
|
|
12982
|
+
const canTheme = item.kind === "screen" && !item.theme;
|
|
12983
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
12984
|
+
className: "flex h-dvh flex-col",
|
|
12985
|
+
children: [
|
|
12986
|
+
/* @__PURE__ */ jsx(PhoneBar, {
|
|
12987
|
+
canvas,
|
|
12988
|
+
title: item.title || item.id,
|
|
12989
|
+
sub: [canvas.title, entry.where].filter(Boolean).join(" · "),
|
|
12990
|
+
back: scope ? void 0 : "/",
|
|
12991
|
+
end: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
12992
|
+
wide ? /* @__PURE__ */ jsx(IconButton, {
|
|
12993
|
+
icon: actual ? "zoom-out" : "zoom-in",
|
|
12994
|
+
label: actual ? "Fit to width" : "Actual size",
|
|
12995
|
+
onClick: () => setActual((value) => !value)
|
|
12996
|
+
}) : null,
|
|
12997
|
+
/* @__PURE__ */ jsxs(Menu, {
|
|
12998
|
+
align: "end",
|
|
12999
|
+
width: 220,
|
|
13000
|
+
trigger: /* @__PURE__ */ jsx(IconButton, {
|
|
13001
|
+
icon: "more",
|
|
13002
|
+
label: "Screen options"
|
|
13003
|
+
}),
|
|
13004
|
+
children: [
|
|
13005
|
+
canTheme ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
13006
|
+
/* @__PURE__ */ jsx(MenuLabel, { children: "Screen theme" }),
|
|
13007
|
+
/* @__PURE__ */ jsx(ThemeItem, {
|
|
13008
|
+
value: "light",
|
|
13009
|
+
current: theme,
|
|
13010
|
+
onPick: setTheme
|
|
13011
|
+
}),
|
|
13012
|
+
/* @__PURE__ */ jsx(ThemeItem, {
|
|
13013
|
+
value: "dark",
|
|
13014
|
+
current: theme,
|
|
13015
|
+
onPick: setTheme
|
|
13016
|
+
}),
|
|
13017
|
+
/* @__PURE__ */ jsx(MenuSeparator, {})
|
|
13018
|
+
] }) : null,
|
|
13019
|
+
/* @__PURE__ */ jsxs(MenuItem, {
|
|
13020
|
+
onSelect: () => reloadFrame(frame.current),
|
|
13021
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
13022
|
+
name: "refresh",
|
|
13023
|
+
size: 15,
|
|
13024
|
+
className: "text-ink2"
|
|
13025
|
+
}), "Reload screen"]
|
|
13026
|
+
}),
|
|
13027
|
+
openUrl ? /* @__PURE__ */ jsxs(MenuItem, {
|
|
13028
|
+
onSelect: () => window.open(openUrl, "_blank", "noopener"),
|
|
13029
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
13030
|
+
name: "external",
|
|
13031
|
+
size: 15,
|
|
13032
|
+
className: "text-ink2"
|
|
13033
|
+
}), "Open in a new tab"]
|
|
13034
|
+
}) : null,
|
|
13035
|
+
/* @__PURE__ */ jsxs(MenuItem, {
|
|
13036
|
+
onSelect: () => setThemePref(appTheme === "dark" ? "light" : "dark"),
|
|
13037
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
13038
|
+
name: appTheme === "dark" ? "sun" : "moon",
|
|
13039
|
+
size: 15,
|
|
13040
|
+
className: "text-ink2"
|
|
13041
|
+
}), appTheme === "dark" ? "Light appearance" : "Dark appearance"]
|
|
13042
|
+
})
|
|
13043
|
+
]
|
|
13044
|
+
}),
|
|
13045
|
+
slots.barEnd
|
|
13046
|
+
] })
|
|
13047
|
+
}),
|
|
13048
|
+
slots.belowBar,
|
|
13049
|
+
/* @__PURE__ */ jsxs("div", {
|
|
13050
|
+
ref: stage,
|
|
13051
|
+
onTouchStart,
|
|
13052
|
+
onTouchEnd,
|
|
13053
|
+
className: cn("canvas-dots relative min-h-0 grow overflow-y-auto overscroll-contain", wide && actual ? "overflow-x-auto" : "overflow-x-hidden"),
|
|
13054
|
+
children: [errors.length ? /* @__PURE__ */ jsxs("div", {
|
|
13055
|
+
className: "sticky top-0 z-10 flex items-start gap-2 border-b border-rule bg-danger-soft px-4 py-2 font-mono text-[12px] whitespace-pre-wrap text-danger-text",
|
|
13056
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
13057
|
+
name: "alert",
|
|
13058
|
+
size: 14,
|
|
13059
|
+
className: "mt-px shrink-0"
|
|
13060
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
13061
|
+
className: "min-w-0 break-words",
|
|
13062
|
+
children: errors.join("\n")
|
|
13063
|
+
})]
|
|
13064
|
+
}) : null, /* @__PURE__ */ jsx(AnimatePresence, {
|
|
13065
|
+
initial: false,
|
|
13066
|
+
mode: "popLayout",
|
|
13067
|
+
custom: direction,
|
|
13068
|
+
children: /* @__PURE__ */ jsx(motion.div, {
|
|
13069
|
+
custom: direction,
|
|
13070
|
+
initial: {
|
|
13071
|
+
opacity: 0,
|
|
13072
|
+
x: direction * 40
|
|
13073
|
+
},
|
|
13074
|
+
animate: {
|
|
13075
|
+
opacity: 1,
|
|
13076
|
+
x: 0
|
|
13077
|
+
},
|
|
13078
|
+
exit: {
|
|
13079
|
+
opacity: 0,
|
|
13080
|
+
x: direction * -40,
|
|
13081
|
+
transition: { duration: .12 }
|
|
13082
|
+
},
|
|
13083
|
+
transition: {
|
|
13084
|
+
type: "spring",
|
|
13085
|
+
stiffness: 420,
|
|
13086
|
+
damping: 38
|
|
13087
|
+
},
|
|
13088
|
+
className: cn("flex", wide && actual ? "w-max" : "justify-center"),
|
|
13089
|
+
style: { padding: GUTTER },
|
|
13090
|
+
children: scale ? /* @__PURE__ */ jsx("div", {
|
|
13091
|
+
className: "shrink-0 overflow-hidden rounded-[6px] bg-surface",
|
|
13092
|
+
style: {
|
|
13093
|
+
width: width * scale,
|
|
13094
|
+
height: height * scale,
|
|
13095
|
+
boxShadow: "var(--shadow-frame), 0 0 0 1px var(--rule)"
|
|
13096
|
+
},
|
|
13097
|
+
children: /* @__PURE__ */ jsx("iframe", {
|
|
13098
|
+
ref: setFrame,
|
|
13099
|
+
src,
|
|
13100
|
+
title: item.title,
|
|
13101
|
+
allow: "clipboard-read; clipboard-write; fullscreen",
|
|
13102
|
+
sandbox: frameSandbox,
|
|
13103
|
+
className: "block border-0",
|
|
13104
|
+
style: {
|
|
13105
|
+
width,
|
|
13106
|
+
height,
|
|
13107
|
+
transform: `scale(${scale})`,
|
|
13108
|
+
transformOrigin: "0 0",
|
|
13109
|
+
colorScheme: frameTheme
|
|
13110
|
+
},
|
|
13111
|
+
onLoad: () => item.kind === "screen" && sendTheme(frame.current, frameTheme)
|
|
13112
|
+
}, `${item.id}:${item.kind === "screen" ? item.rev : item.url}`)
|
|
13113
|
+
}) : null
|
|
13114
|
+
}, item.id)
|
|
13115
|
+
})]
|
|
13116
|
+
}),
|
|
13117
|
+
/* @__PURE__ */ jsxs("nav", {
|
|
13118
|
+
"aria-label": "Screens",
|
|
13119
|
+
className: "flex shrink-0 items-center gap-1 border-t border-rule bg-surface px-2 pt-1.5 pb-[max(6px,env(safe-area-inset-bottom))]",
|
|
13120
|
+
children: [
|
|
13121
|
+
/* @__PURE__ */ jsx(IconButton, {
|
|
13122
|
+
icon: "chevron-left",
|
|
13123
|
+
label: "Previous screen",
|
|
13124
|
+
onClick: () => move(-1),
|
|
13125
|
+
disabled: all.length < 2
|
|
13126
|
+
}),
|
|
13127
|
+
/* @__PURE__ */ jsxs("button", {
|
|
13128
|
+
type: "button",
|
|
13129
|
+
onClick: () => setListOpen(true),
|
|
13130
|
+
className: "flex h-10 min-w-0 grow cursor-pointer items-center justify-center gap-2 rounded-[8px] border-0 bg-transparent px-3 text-[13.5px] text-ink active:bg-soft2",
|
|
13131
|
+
children: [
|
|
13132
|
+
/* @__PURE__ */ jsx(Icon, {
|
|
13133
|
+
name: "list",
|
|
13134
|
+
size: 15,
|
|
13135
|
+
className: "shrink-0 text-muted"
|
|
13136
|
+
}),
|
|
13137
|
+
/* @__PURE__ */ jsxs("span", {
|
|
13138
|
+
className: "tabular shrink-0 text-muted",
|
|
13139
|
+
children: [
|
|
13140
|
+
index + 1,
|
|
13141
|
+
" / ",
|
|
13142
|
+
all.length
|
|
13143
|
+
]
|
|
13144
|
+
}),
|
|
13145
|
+
/* @__PURE__ */ jsx("span", {
|
|
13146
|
+
className: "min-w-0 truncate font-medium",
|
|
13147
|
+
children: item.title || item.id
|
|
13148
|
+
})
|
|
13149
|
+
]
|
|
13150
|
+
}),
|
|
13151
|
+
/* @__PURE__ */ jsx(IconButton, {
|
|
13152
|
+
icon: "chevron-right",
|
|
13153
|
+
label: "Next screen",
|
|
13154
|
+
onClick: () => move(1),
|
|
13155
|
+
disabled: all.length < 2
|
|
13156
|
+
})
|
|
13157
|
+
]
|
|
13158
|
+
}),
|
|
13159
|
+
/* @__PURE__ */ jsx(Sheet, {
|
|
13160
|
+
open: listOpen,
|
|
13161
|
+
onOpenChange: setListOpen,
|
|
13162
|
+
title: canvas.title,
|
|
13163
|
+
children: /* @__PURE__ */ jsx(ScreenList, {
|
|
13164
|
+
canvas,
|
|
13165
|
+
current: item.id,
|
|
13166
|
+
onPick: (id) => {
|
|
13167
|
+
setListOpen(false);
|
|
13168
|
+
goTo(id);
|
|
13169
|
+
}
|
|
13170
|
+
})
|
|
13171
|
+
})
|
|
13172
|
+
]
|
|
13173
|
+
});
|
|
13174
|
+
}
|
|
13175
|
+
function ThemeItem({ value, current, onPick }) {
|
|
13176
|
+
return /* @__PURE__ */ jsxs(MenuItem, {
|
|
13177
|
+
active: current === value,
|
|
13178
|
+
onSelect: () => onPick(value),
|
|
13179
|
+
children: [
|
|
13180
|
+
/* @__PURE__ */ jsx(Icon, {
|
|
13181
|
+
name: value === "dark" ? "moon" : "sun",
|
|
13182
|
+
size: 15,
|
|
13183
|
+
className: "text-ink2"
|
|
13184
|
+
}),
|
|
13185
|
+
value === "dark" ? "Dark" : "Light",
|
|
13186
|
+
current === value ? /* @__PURE__ */ jsx(Icon, {
|
|
13187
|
+
name: "check",
|
|
13188
|
+
size: 15,
|
|
13189
|
+
className: "ml-auto text-ink2"
|
|
13190
|
+
}) : null
|
|
13191
|
+
]
|
|
13192
|
+
});
|
|
13193
|
+
}
|
|
13194
|
+
/** The phone's top bar: a way back, the screen and where it sits, and the screen's actions. */
|
|
13195
|
+
function PhoneBar({ canvas, title, sub, back, end }) {
|
|
13196
|
+
const { slots } = useViewer();
|
|
13197
|
+
return /* @__PURE__ */ jsxs("header", {
|
|
13198
|
+
className: "flex h-[52px] shrink-0 items-center gap-2 border-b border-rule bg-surface pr-2 pl-2 pt-[env(safe-area-inset-top)]",
|
|
13199
|
+
children: [
|
|
13200
|
+
slots.barStart,
|
|
13201
|
+
back ? /* @__PURE__ */ jsx(Link, {
|
|
13202
|
+
href: back,
|
|
13203
|
+
"aria-label": `All canvases, leaving ${canvas.title}`,
|
|
13204
|
+
className: "inline-flex size-10 shrink-0 items-center justify-center rounded-[8px] text-ink2 active:bg-soft2",
|
|
13205
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
13206
|
+
name: "arrow-left",
|
|
13207
|
+
size: 18
|
|
13208
|
+
})
|
|
13209
|
+
}) : /* @__PURE__ */ jsx("span", {
|
|
13210
|
+
className: "flex size-10 shrink-0 items-center justify-center",
|
|
13211
|
+
children: /* @__PURE__ */ jsx(Mark, {})
|
|
13212
|
+
}),
|
|
13213
|
+
/* @__PURE__ */ jsxs("div", {
|
|
13214
|
+
className: "flex min-w-0 grow flex-col leading-tight",
|
|
13215
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
13216
|
+
className: "truncate text-[14.5px] font-semibold",
|
|
13217
|
+
children: title
|
|
13218
|
+
}), sub ? /* @__PURE__ */ jsx("span", {
|
|
13219
|
+
className: "truncate text-[12px] text-muted",
|
|
13220
|
+
children: sub
|
|
13221
|
+
}) : null]
|
|
13222
|
+
}),
|
|
13223
|
+
/* @__PURE__ */ jsx("div", {
|
|
13224
|
+
className: "flex shrink-0 items-center gap-1",
|
|
13225
|
+
children: end
|
|
13226
|
+
})
|
|
13227
|
+
]
|
|
13228
|
+
});
|
|
13229
|
+
}
|
|
13230
|
+
var KIND_ICON = (entry) => entry.item.kind === "url" ? "globe" : entry.item.frame.device === "phone" ? "smartphone" : entry.item.frame.device === "tablet" ? "tablet" : "monitor";
|
|
13231
|
+
/** Every screen of the canvas by page and section, the current one marked. */
|
|
13232
|
+
function ScreenList({ canvas, current, onPick }) {
|
|
13233
|
+
const activeRef = useRef(null);
|
|
13234
|
+
useEffect(() => {
|
|
13235
|
+
activeRef.current?.scrollIntoView({ block: "center" });
|
|
13236
|
+
}, []);
|
|
13237
|
+
const multi = canvas.pages.length > 1;
|
|
13238
|
+
return /* @__PURE__ */ jsx("div", {
|
|
13239
|
+
className: "flex flex-col pb-2",
|
|
13240
|
+
children: canvas.pages.map((page) => {
|
|
13241
|
+
const entries = framesOf(canvas, page.id);
|
|
13242
|
+
if (!entries.length) return null;
|
|
13243
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
13244
|
+
className: "flex flex-col",
|
|
13245
|
+
children: [multi ? /* @__PURE__ */ jsx("h3", {
|
|
13246
|
+
className: "sticky top-0 z-[1] m-0 border-b border-rule bg-soft px-4 py-2 text-[12px] font-semibold text-ink2",
|
|
13247
|
+
children: page.title
|
|
13248
|
+
}) : null, page.sections.map((section) => {
|
|
13249
|
+
const items = entries.filter((entry) => section.items.includes(entry.item));
|
|
13250
|
+
if (!items.length) return null;
|
|
13251
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
13252
|
+
className: "flex flex-col",
|
|
13253
|
+
children: [section.title ? /* @__PURE__ */ jsx("div", {
|
|
13254
|
+
className: "px-4 pt-3 pb-1 text-[12px] text-muted",
|
|
13255
|
+
children: section.title
|
|
13256
|
+
}) : null, items.map((entry) => {
|
|
13257
|
+
const on = entry.item.id === current;
|
|
13258
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
13259
|
+
ref: on ? activeRef : void 0,
|
|
13260
|
+
type: "button",
|
|
13261
|
+
"aria-current": on ? "true" : void 0,
|
|
13262
|
+
onClick: () => onPick(entry.item.id),
|
|
13263
|
+
className: cn("flex min-h-[48px] cursor-pointer items-center gap-3 border-0 bg-transparent px-4 text-left text-[14px] active:bg-soft2", on ? "font-medium text-ink" : "text-ink2"),
|
|
13264
|
+
children: [
|
|
13265
|
+
/* @__PURE__ */ jsx("span", {
|
|
13266
|
+
className: cn("flex size-8 shrink-0 items-center justify-center rounded-[8px]", on ? "bg-action-soft text-action" : "bg-soft2 text-muted"),
|
|
13267
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
13268
|
+
name: KIND_ICON(entry),
|
|
13269
|
+
size: 15
|
|
13270
|
+
})
|
|
13271
|
+
}),
|
|
13272
|
+
/* @__PURE__ */ jsx("span", {
|
|
13273
|
+
className: "min-w-0 grow truncate",
|
|
13274
|
+
children: entry.item.title || entry.item.id
|
|
13275
|
+
}),
|
|
13276
|
+
on ? /* @__PURE__ */ jsx(Icon, {
|
|
13277
|
+
name: "check",
|
|
13278
|
+
size: 16,
|
|
13279
|
+
className: "shrink-0 text-action"
|
|
13280
|
+
}) : null
|
|
13281
|
+
]
|
|
13282
|
+
}, entry.item.id);
|
|
13283
|
+
})]
|
|
13284
|
+
}, section.id);
|
|
13285
|
+
})]
|
|
13286
|
+
}, page.id);
|
|
13287
|
+
})
|
|
13288
|
+
});
|
|
13289
|
+
}
|
|
13290
|
+
//#endregion
|
|
12708
13291
|
//#region viewer/src/lib/format.ts
|
|
12709
13292
|
var units = [
|
|
12710
13293
|
["year", 31536e3],
|
|
@@ -12805,6 +13388,7 @@ function Foot({ children }) {
|
|
|
12805
13388
|
}
|
|
12806
13389
|
function CanvasList({ project }) {
|
|
12807
13390
|
const { canvasRowActions: rowActions, canvasesFoot } = useViewer().slots;
|
|
13391
|
+
const phone = useIsPhone();
|
|
12808
13392
|
if (!project.canvases.length) return /* @__PURE__ */ jsxs(Panel, { children: [
|
|
12809
13393
|
/* @__PURE__ */ jsx(PanelHead, {
|
|
12810
13394
|
title: "Canvases",
|
|
@@ -12835,7 +13419,31 @@ function CanvasList({ project }) {
|
|
|
12835
13419
|
const actions = rowActions?.(canvas.id);
|
|
12836
13420
|
return /* @__PURE__ */ jsx(WithActions, {
|
|
12837
13421
|
actions,
|
|
12838
|
-
children: /* @__PURE__ */ jsxs(RowLink, {
|
|
13422
|
+
children: phone ? /* @__PURE__ */ jsxs(RowLink, {
|
|
13423
|
+
to: `/c/${encodeURIComponent(canvas.id)}`,
|
|
13424
|
+
cols: "86px minmax(0,1fr)",
|
|
13425
|
+
align: "ll",
|
|
13426
|
+
pad: "10px 16px",
|
|
13427
|
+
minH: 74,
|
|
13428
|
+
first: !!actions,
|
|
13429
|
+
children: [/* @__PURE__ */ jsx(Cover, { canvas }), /* @__PURE__ */ jsx(Two, {
|
|
13430
|
+
top: canvas.title,
|
|
13431
|
+
bottom: /* @__PURE__ */ jsxs("span", {
|
|
13432
|
+
className: "flex min-w-0 items-center gap-2",
|
|
13433
|
+
children: [canvas.issues ? /* @__PURE__ */ jsx(Badge, {
|
|
13434
|
+
tone: "danger",
|
|
13435
|
+
children: plural(canvas.issues, "problem")
|
|
13436
|
+
}) : null, /* @__PURE__ */ jsxs("span", {
|
|
13437
|
+
className: "truncate",
|
|
13438
|
+
children: [
|
|
13439
|
+
plural(canvas.screens, "screen"),
|
|
13440
|
+
" · ",
|
|
13441
|
+
relativeTime(canvas.updatedAt)
|
|
13442
|
+
]
|
|
13443
|
+
})]
|
|
13444
|
+
})
|
|
13445
|
+
})]
|
|
13446
|
+
}) : /* @__PURE__ */ jsxs(RowLink, {
|
|
12839
13447
|
to: `/c/${encodeURIComponent(canvas.id)}`,
|
|
12840
13448
|
cols: "86px minmax(0,1fr) auto auto",
|
|
12841
13449
|
align: "llrr",
|
|
@@ -12965,9 +13573,11 @@ function HomePage() {
|
|
|
12965
13573
|
sub: slots.homeSub ? slots.homeSub : project.static ? `Snapshot built with foss-design ${project.version}` : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
12966
13574
|
/* @__PURE__ */ jsxs(Mono, {
|
|
12967
13575
|
size: 12.5,
|
|
13576
|
+
wrap: true,
|
|
12968
13577
|
children: [project.root, "/.design"]
|
|
12969
13578
|
}),
|
|
12970
|
-
"
|
|
13579
|
+
" ",
|
|
13580
|
+
"· foss-design ",
|
|
12971
13581
|
project.version
|
|
12972
13582
|
] })
|
|
12973
13583
|
}),
|
|
@@ -16628,6 +17238,7 @@ function MotionPage({ system }) {
|
|
|
16628
17238
|
//#region viewer/src/pages/system/nav.tsx
|
|
16629
17239
|
/** Grouped links of the system section; a wrapping row above the content on narrow screens. */
|
|
16630
17240
|
function SystemNav({ system }) {
|
|
17241
|
+
const phone = useIsPhone();
|
|
16631
17242
|
const kinds = new Set(system.tokens.map((token) => token.kind));
|
|
16632
17243
|
const foundations = [
|
|
16633
17244
|
{
|
|
@@ -16669,6 +17280,33 @@ function SystemNav({ system }) {
|
|
|
16669
17280
|
groups.set(component.group, list);
|
|
16670
17281
|
}
|
|
16671
17282
|
const guidelines = system.guidelines.filter((guide) => guide.slug !== "overview");
|
|
17283
|
+
if (phone) {
|
|
17284
|
+
const sections = [
|
|
17285
|
+
{
|
|
17286
|
+
label: "",
|
|
17287
|
+
items: [{
|
|
17288
|
+
to: "/system",
|
|
17289
|
+
label: "Overview"
|
|
17290
|
+
}]
|
|
17291
|
+
},
|
|
17292
|
+
{
|
|
17293
|
+
label: "Foundations",
|
|
17294
|
+
items: foundations
|
|
17295
|
+
},
|
|
17296
|
+
{
|
|
17297
|
+
label: "Guidelines",
|
|
17298
|
+
items: guidelines.map((guide) => ({
|
|
17299
|
+
to: `/system/guidelines/${encodeURIComponent(guide.slug)}`,
|
|
17300
|
+
label: guide.title
|
|
17301
|
+
}))
|
|
17302
|
+
},
|
|
17303
|
+
...[...groups].map(([label, items]) => ({
|
|
17304
|
+
label,
|
|
17305
|
+
items
|
|
17306
|
+
}))
|
|
17307
|
+
];
|
|
17308
|
+
return /* @__PURE__ */ jsx(PhoneSystemNav, { sections: sections.filter((section) => section.items.length) });
|
|
17309
|
+
}
|
|
16672
17310
|
return /* @__PURE__ */ jsxs("div", {
|
|
16673
17311
|
className: "flex flex-col max-lg:gap-1 max-lg:[&_nav]:flex-row max-lg:[&_nav]:flex-wrap",
|
|
16674
17312
|
children: [
|
|
@@ -16704,6 +17342,70 @@ function SystemNav({ system }) {
|
|
|
16704
17342
|
function count(system, kind) {
|
|
16705
17343
|
return system.tokens.filter((token) => token.kind === kind).length || void 0;
|
|
16706
17344
|
}
|
|
17345
|
+
/** On a phone: the current section on a full-width button that lists them all in a sheet. */
|
|
17346
|
+
function PhoneSystemNav({ sections }) {
|
|
17347
|
+
const [location] = useLocation();
|
|
17348
|
+
const [open, setOpen] = useState(false);
|
|
17349
|
+
const current = sections.flatMap((section) => section.items).find((item) => item.to === location);
|
|
17350
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("button", {
|
|
17351
|
+
type: "button",
|
|
17352
|
+
onClick: () => setOpen(true),
|
|
17353
|
+
className: "flex h-11 w-full cursor-pointer items-center gap-2 rounded-[8px] border border-rule bg-surface px-3.5 text-left text-[14px] text-ink",
|
|
17354
|
+
children: [
|
|
17355
|
+
/* @__PURE__ */ jsx("span", {
|
|
17356
|
+
className: "text-[12.5px] text-muted",
|
|
17357
|
+
children: "Section"
|
|
17358
|
+
}),
|
|
17359
|
+
/* @__PURE__ */ jsx("span", {
|
|
17360
|
+
className: "min-w-0 grow truncate font-medium",
|
|
17361
|
+
children: current?.label ?? "Overview"
|
|
17362
|
+
}),
|
|
17363
|
+
/* @__PURE__ */ jsx(Icon, {
|
|
17364
|
+
name: "chevron-down",
|
|
17365
|
+
size: 16,
|
|
17366
|
+
className: "shrink-0 text-muted"
|
|
17367
|
+
})
|
|
17368
|
+
]
|
|
17369
|
+
}), /* @__PURE__ */ jsx(Sheet, {
|
|
17370
|
+
open,
|
|
17371
|
+
onOpenChange: setOpen,
|
|
17372
|
+
title: "Design system",
|
|
17373
|
+
children: /* @__PURE__ */ jsx("nav", {
|
|
17374
|
+
"aria-label": "Design system",
|
|
17375
|
+
className: "flex flex-col pb-2",
|
|
17376
|
+
children: sections.map((section) => /* @__PURE__ */ jsxs("div", {
|
|
17377
|
+
className: "flex flex-col",
|
|
17378
|
+
children: [section.label ? /* @__PURE__ */ jsx("div", {
|
|
17379
|
+
className: "px-4 pt-3 pb-1 text-[12px] text-muted",
|
|
17380
|
+
children: section.label
|
|
17381
|
+
}) : null, section.items.map((item) => {
|
|
17382
|
+
const on = item.to === location;
|
|
17383
|
+
return /* @__PURE__ */ jsxs(Link, {
|
|
17384
|
+
href: item.to,
|
|
17385
|
+
onClick: () => setOpen(false),
|
|
17386
|
+
"aria-current": on ? "page" : void 0,
|
|
17387
|
+
className: cn("flex min-h-[46px] items-center gap-2 px-4 text-[14px] no-underline active:bg-soft2", on ? "font-medium text-ink" : "text-ink2"),
|
|
17388
|
+
children: [
|
|
17389
|
+
/* @__PURE__ */ jsx("span", {
|
|
17390
|
+
className: "min-w-0 grow truncate",
|
|
17391
|
+
children: item.label
|
|
17392
|
+
}),
|
|
17393
|
+
item.hint ? /* @__PURE__ */ jsx("span", {
|
|
17394
|
+
className: "text-[12.5px] text-muted",
|
|
17395
|
+
children: item.hint
|
|
17396
|
+
}) : null,
|
|
17397
|
+
on ? /* @__PURE__ */ jsx(Icon, {
|
|
17398
|
+
name: "check",
|
|
17399
|
+
size: 16,
|
|
17400
|
+
className: "shrink-0 text-action"
|
|
17401
|
+
}) : null
|
|
17402
|
+
]
|
|
17403
|
+
}, item.to);
|
|
17404
|
+
})]
|
|
17405
|
+
}, section.label || "overview"))
|
|
17406
|
+
})
|
|
17407
|
+
})] });
|
|
17408
|
+
}
|
|
16707
17409
|
//#endregion
|
|
16708
17410
|
//#region viewer/src/pages/system/other-tokens.tsx
|
|
16709
17411
|
function OtherTokensPage({ system }) {
|
|
@@ -17399,7 +18101,30 @@ function Title() {
|
|
|
17399
18101
|
}, [name]);
|
|
17400
18102
|
return null;
|
|
17401
18103
|
}
|
|
18104
|
+
/** Screenshots (`?capture`) always get the canvas, whatever the window. */
|
|
18105
|
+
var CAPTURE = new URLSearchParams(window.location.search).has("capture");
|
|
18106
|
+
/** The canvas routes' pages; on a phone the canvas is one screen at a time. */
|
|
18107
|
+
function useCanvasPages() {
|
|
18108
|
+
const phone = useIsPhone() && !CAPTURE;
|
|
18109
|
+
return {
|
|
18110
|
+
play: (canvas, item) => phone ? /* @__PURE__ */ jsx(PhoneCanvas, {
|
|
18111
|
+
canvasId: canvas,
|
|
18112
|
+
itemId: item
|
|
18113
|
+
}) : /* @__PURE__ */ jsx(PlayPage, {
|
|
18114
|
+
canvasId: canvas,
|
|
18115
|
+
itemId: item
|
|
18116
|
+
}),
|
|
18117
|
+
canvas: (canvas, page) => phone ? /* @__PURE__ */ jsx(PhoneCanvas, {
|
|
18118
|
+
canvasId: canvas,
|
|
18119
|
+
pageId: page
|
|
18120
|
+
}) : /* @__PURE__ */ jsx(CanvasPage, {
|
|
18121
|
+
canvasId: canvas,
|
|
18122
|
+
pageId: page
|
|
18123
|
+
})
|
|
18124
|
+
};
|
|
18125
|
+
}
|
|
17402
18126
|
function Routes() {
|
|
18127
|
+
const pages = useCanvasPages();
|
|
17403
18128
|
return /* @__PURE__ */ jsxs(Switch$1, { children: [
|
|
17404
18129
|
/* @__PURE__ */ jsx(Route, {
|
|
17405
18130
|
path: "/c/:canvas/full/:item",
|
|
@@ -17410,21 +18135,15 @@ function Routes() {
|
|
|
17410
18135
|
}),
|
|
17411
18136
|
/* @__PURE__ */ jsx(Route, {
|
|
17412
18137
|
path: "/c/:canvas/play/:item",
|
|
17413
|
-
children: (params) =>
|
|
17414
|
-
canvasId: params.canvas,
|
|
17415
|
-
itemId: params.item
|
|
17416
|
-
})
|
|
18138
|
+
children: (params) => pages.play(params.canvas, params.item)
|
|
17417
18139
|
}),
|
|
17418
18140
|
/* @__PURE__ */ jsx(Route, {
|
|
17419
18141
|
path: "/c/:canvas/p/:page",
|
|
17420
|
-
children: (params) =>
|
|
17421
|
-
canvasId: params.canvas,
|
|
17422
|
-
pageId: params.page
|
|
17423
|
-
})
|
|
18142
|
+
children: (params) => pages.canvas(params.canvas, params.page)
|
|
17424
18143
|
}),
|
|
17425
18144
|
/* @__PURE__ */ jsx(Route, {
|
|
17426
18145
|
path: "/c/:canvas",
|
|
17427
|
-
children: (params) =>
|
|
18146
|
+
children: (params) => pages.canvas(params.canvas)
|
|
17428
18147
|
}),
|
|
17429
18148
|
/* @__PURE__ */ jsxs(Route, {
|
|
17430
18149
|
path: "/system/*?",
|
|
@@ -17444,6 +18163,7 @@ function ScopedRoutes({ canvas }) {
|
|
|
17444
18163
|
to: home,
|
|
17445
18164
|
replace: true
|
|
17446
18165
|
});
|
|
18166
|
+
const pages = useCanvasPages();
|
|
17447
18167
|
return /* @__PURE__ */ jsxs(Switch$1, { children: [
|
|
17448
18168
|
/* @__PURE__ */ jsx(Route, {
|
|
17449
18169
|
path: "/c/:canvas/full/:item",
|
|
@@ -17454,21 +18174,15 @@ function ScopedRoutes({ canvas }) {
|
|
|
17454
18174
|
}),
|
|
17455
18175
|
/* @__PURE__ */ jsx(Route, {
|
|
17456
18176
|
path: "/c/:canvas/play/:item",
|
|
17457
|
-
children: (params) => guard(params.canvas,
|
|
17458
|
-
canvasId: canvas,
|
|
17459
|
-
itemId: params.item
|
|
17460
|
-
}))
|
|
18177
|
+
children: (params) => guard(params.canvas, pages.play(canvas, params.item))
|
|
17461
18178
|
}),
|
|
17462
18179
|
/* @__PURE__ */ jsx(Route, {
|
|
17463
18180
|
path: "/c/:canvas/p/:page",
|
|
17464
|
-
children: (params) => guard(params.canvas,
|
|
17465
|
-
canvasId: canvas,
|
|
17466
|
-
pageId: params.page
|
|
17467
|
-
}))
|
|
18181
|
+
children: (params) => guard(params.canvas, pages.canvas(canvas, params.page))
|
|
17468
18182
|
}),
|
|
17469
18183
|
/* @__PURE__ */ jsx(Route, {
|
|
17470
18184
|
path: "/c/:canvas",
|
|
17471
|
-
children: (params) => guard(params.canvas,
|
|
18185
|
+
children: (params) => guard(params.canvas, pages.canvas(canvas))
|
|
17472
18186
|
}),
|
|
17473
18187
|
/* @__PURE__ */ jsx(Route, { children: /* @__PURE__ */ jsx(Redirect, {
|
|
17474
18188
|
to: home,
|
|
@@ -17770,4 +18484,4 @@ function TextArea({ label, hint, error, className, id, disabled, rows = 4, ...re
|
|
|
17770
18484
|
});
|
|
17771
18485
|
}
|
|
17772
18486
|
//#endregion
|
|
17773
|
-
export { ApiError, Avatar, Badge, Bar, Button, ButtonAnchor, ButtonLink, Content, Count, DesignViewer, Dialog, GroupRow, Icon, IconButton, KV, Kbd, Mark, Menu, MenuItem, MenuLabel, MenuSeparator, Meter, Mono, Muted, NavLabel, Notice, NoticeCard, PageHead, Panel, PanelBody, PanelFoot, PanelHead, PanelTabs, Row, RowLink, Segmented, Skeleton, SkeletonRows, SlidePanel, Strip, SubNav, Switch, THead, TextArea, TextField, ThemeMenu, Toaster, Tooltip, TooltipProvider, Two, Wordmark, buttonClass, cn, copyText, floatingSurface, iconButtonClass, initials, localSource, menuItemClass, menuSurface, resolveUrl, setThemePref, staticSource, toast, useTheme, useThemePref };
|
|
18487
|
+
export { ApiError, Avatar, Badge, Bar, Button, ButtonAnchor, ButtonLink, Content, Count, DesignViewer, Dialog, GroupRow, Icon, IconButton, KV, Kbd, Mark, Menu, MenuItem, MenuLabel, MenuSeparator, Meter, Mono, Muted, NavLabel, Notice, NoticeCard, PageHead, Panel, PanelBody, PanelFoot, PanelHead, PanelTabs, Row, RowLink, Segmented, Sheet, Skeleton, SkeletonRows, SlidePanel, Strip, SubNav, Switch, THead, TextArea, TextField, ThemeMenu, Toaster, Tooltip, TooltipProvider, Two, Wordmark, buttonClass, cn, copyText, floatingSurface, iconButtonClass, initials, localSource, menuItemClass, menuSurface, resolveUrl, setThemePref, staticSource, toast, useIsPhone, useTheme, useThemePref };
|