foss-design 0.3.6 → 0.4.1
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/viewer/assets/index-CUXYA4jJ.js +158 -0
- package/dist/viewer/assets/index-DG4Nv6cf.css +2 -0
- package/dist/viewer/index.html +2 -2
- package/dist/viewer-lib/index.js +747 -60
- package/dist/viewer-lib/styles.css +101 -0
- package/dist/viewer-lib/types/viewer/src/lib/phone.d.ts +1 -0
- package/dist/viewer-lib/types/viewer/src/lib/viewer.d.ts +4 -1
- package/dist/viewer-lib/types/viewer/src/library.d.ts +2 -0
- 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/package.json +1 -1
- package/dist/viewer/assets/index-DmtHtusH.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;
|
|
@@ -6501,7 +6505,10 @@ function Menu({ trigger, children, width = 220, align = "start", side = "bottom"
|
|
|
6501
6505
|
sideOffset,
|
|
6502
6506
|
collisionPadding: 12,
|
|
6503
6507
|
className: menuSurface,
|
|
6504
|
-
style: {
|
|
6508
|
+
style: {
|
|
6509
|
+
width,
|
|
6510
|
+
maxWidth: "calc(100vw - 24px)"
|
|
6511
|
+
},
|
|
6505
6512
|
children
|
|
6506
6513
|
}) })]
|
|
6507
6514
|
});
|
|
@@ -6717,24 +6724,60 @@ function AppTopBar() {
|
|
|
6717
6724
|
const project = useProject().data;
|
|
6718
6725
|
const { slots } = useViewer();
|
|
6719
6726
|
const [location] = useLocation();
|
|
6720
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
6721
|
-
|
|
6722
|
-
|
|
6723
|
-
|
|
6724
|
-
|
|
6725
|
-
|
|
6726
|
-
|
|
6727
|
-
|
|
6728
|
-
|
|
6729
|
-
|
|
6730
|
-
|
|
6731
|
-
|
|
6732
|
-
|
|
6733
|
-
|
|
6734
|
-
|
|
6735
|
-
|
|
6727
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
6728
|
+
/* @__PURE__ */ jsxs(Bar, { children: [
|
|
6729
|
+
slots.barStart,
|
|
6730
|
+
/* @__PURE__ */ jsxs("div", {
|
|
6731
|
+
className: "flex min-w-0 items-center gap-6",
|
|
6732
|
+
children: [slots.brand ?? /* @__PURE__ */ jsx(Wordmark, {
|
|
6733
|
+
name: project?.name,
|
|
6734
|
+
version: project?.version
|
|
6735
|
+
}), /* @__PURE__ */ jsx(Tabs, { location })]
|
|
6736
|
+
}),
|
|
6737
|
+
/* @__PURE__ */ jsxs("div", {
|
|
6738
|
+
className: "ml-auto flex items-center gap-2",
|
|
6739
|
+
children: [
|
|
6740
|
+
/* @__PURE__ */ jsx(SearchButton, {}),
|
|
6741
|
+
/* @__PURE__ */ jsx("span", {
|
|
6742
|
+
className: cn("flex", slots.barEnd ? "max-md:hidden" : void 0),
|
|
6743
|
+
children: /* @__PURE__ */ jsx(ThemeMenu, {})
|
|
6744
|
+
}),
|
|
6745
|
+
slots.barEnd
|
|
6746
|
+
]
|
|
6747
|
+
})
|
|
6748
|
+
] }),
|
|
6749
|
+
/* @__PURE__ */ jsx(PhoneTabs, { location }),
|
|
6750
|
+
slots.belowBar
|
|
6751
|
+
] });
|
|
6752
|
+
}
|
|
6753
|
+
/** The section tabs on a phone, where the bar has no room for them: a row under it. */
|
|
6754
|
+
function PhoneTabs({ location }) {
|
|
6755
|
+
return /* @__PURE__ */ jsx("nav", {
|
|
6756
|
+
className: "sticky top-[52px] z-30 hidden h-10 shrink-0 items-stretch border-b border-rule bg-surface px-2 max-md:flex",
|
|
6757
|
+
children: TABS.map((tab) => {
|
|
6758
|
+
const on = tab.match(location);
|
|
6759
|
+
return /* @__PURE__ */ jsxs(Link, {
|
|
6760
|
+
href: tab.to,
|
|
6761
|
+
"aria-current": on ? "page" : void 0,
|
|
6762
|
+
className: cn("relative flex grow items-center justify-center text-[13.5px] no-underline", on ? "font-medium text-ink" : "text-ink2"),
|
|
6763
|
+
children: [tab.label, on ? /* @__PURE__ */ jsx("span", {
|
|
6764
|
+
"aria-hidden": true,
|
|
6765
|
+
className: "absolute inset-x-6 bottom-0 h-[2px] rounded-full bg-action"
|
|
6766
|
+
}) : null]
|
|
6767
|
+
}, tab.to);
|
|
6736
6768
|
})
|
|
6737
|
-
|
|
6769
|
+
});
|
|
6770
|
+
}
|
|
6771
|
+
//#endregion
|
|
6772
|
+
//#region viewer/src/lib/phone.ts
|
|
6773
|
+
/** Phones: under Tailwind's `md`. The canvas gives way to one screen at a time there. */
|
|
6774
|
+
var media = window.matchMedia("(max-width: 767px)");
|
|
6775
|
+
var subscribe = (onChange) => {
|
|
6776
|
+
media.addEventListener("change", onChange);
|
|
6777
|
+
return () => media.removeEventListener("change", onChange);
|
|
6778
|
+
};
|
|
6779
|
+
function useIsPhone() {
|
|
6780
|
+
return useSyncExternalStore(subscribe, () => media.matches);
|
|
6738
6781
|
}
|
|
6739
6782
|
//#endregion
|
|
6740
6783
|
//#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
|
|
@@ -7836,7 +7879,7 @@ function KV({ label, children, labelWidth = 180, minH = 40, end = false }) {
|
|
|
7836
7879
|
return /* @__PURE__ */ jsxs("div", {
|
|
7837
7880
|
className: "grid content-center items-baseline gap-x-4 border-t border-rule px-4 py-2",
|
|
7838
7881
|
style: {
|
|
7839
|
-
gridTemplateColumns:
|
|
7882
|
+
gridTemplateColumns: `min(${labelWidth}px, 42%) minmax(0, 1fr)`,
|
|
7840
7883
|
minHeight: minH
|
|
7841
7884
|
},
|
|
7842
7885
|
children: [/* @__PURE__ */ jsx("span", {
|
|
@@ -7996,7 +8039,7 @@ function PageHead({ title, sub, right, badge, crumbs }) {
|
|
|
7996
8039
|
}), badge]
|
|
7997
8040
|
}),
|
|
7998
8041
|
sub ? /* @__PURE__ */ jsx("div", {
|
|
7999
|
-
className: "text-[13px] text-muted",
|
|
8042
|
+
className: "text-[13px] [overflow-wrap:anywhere] text-muted",
|
|
8000
8043
|
children: sub
|
|
8001
8044
|
}) : null
|
|
8002
8045
|
]
|
|
@@ -11949,7 +11992,7 @@ function isTyping(target) {
|
|
|
11949
11992
|
//#endregion
|
|
11950
11993
|
//#region viewer/src/pages/canvas/canvas-page.tsx
|
|
11951
11994
|
var query = new URLSearchParams(window.location.search);
|
|
11952
|
-
var CAPTURE = query.has("capture");
|
|
11995
|
+
var CAPTURE$1 = query.has("capture");
|
|
11953
11996
|
var cameraKey = (canvas, page) => `foss-design.camera.${canvas}.${page}`;
|
|
11954
11997
|
function loadCamera(key) {
|
|
11955
11998
|
try {
|
|
@@ -11985,7 +12028,7 @@ function CanvasView({ canvas, page }) {
|
|
|
11985
12028
|
const camera = useMemo(() => new CameraStore(), []);
|
|
11986
12029
|
const apiRef = useRef(null);
|
|
11987
12030
|
const [theme, setTheme] = useScreenTheme(canvas);
|
|
11988
|
-
const [sidebar, setSidebar] = useState(() => !CAPTURE && window.innerWidth > 900);
|
|
12031
|
+
const [sidebar, setSidebar] = useState(() => !CAPTURE$1 && window.innerWidth > 900);
|
|
11989
12032
|
const [inspectOn, setInspect] = useState(false);
|
|
11990
12033
|
const sizes = useStore(store, (state) => state.sizes);
|
|
11991
12034
|
const layout = useMemo(() => page ? layoutPage(page, sizes) : {
|
|
@@ -12029,7 +12072,7 @@ function CanvasView({ canvas, page }) {
|
|
|
12029
12072
|
if (placed.current || !layout.bounds.w) return;
|
|
12030
12073
|
placed.current = true;
|
|
12031
12074
|
const focus = takeFocus(canvas.id);
|
|
12032
|
-
const saved = CAPTURE ? null : loadCamera(key);
|
|
12075
|
+
const saved = CAPTURE$1 ? null : loadCamera(key);
|
|
12033
12076
|
if (focus) {
|
|
12034
12077
|
store.set((state) => ({
|
|
12035
12078
|
...state,
|
|
@@ -12037,7 +12080,7 @@ function CanvasView({ canvas, page }) {
|
|
|
12037
12080
|
}));
|
|
12038
12081
|
requestAnimationFrame(() => apiRef.current?.fitItem(focus, false));
|
|
12039
12082
|
} else if (saved && saved.z >= camera.minZoom) camera.set(saved);
|
|
12040
|
-
else requestAnimationFrame(() => CAPTURE ? apiRef.current?.fitAll(false) : apiRef.current?.openView());
|
|
12083
|
+
else requestAnimationFrame(() => CAPTURE$1 ? apiRef.current?.fitAll(false) : apiRef.current?.openView());
|
|
12041
12084
|
}, [
|
|
12042
12085
|
layout,
|
|
12043
12086
|
camera,
|
|
@@ -12046,7 +12089,7 @@ function CanvasView({ canvas, page }) {
|
|
|
12046
12089
|
store
|
|
12047
12090
|
]);
|
|
12048
12091
|
useEffect(() => {
|
|
12049
|
-
if (CAPTURE) return;
|
|
12092
|
+
if (CAPTURE$1) return;
|
|
12050
12093
|
let timer;
|
|
12051
12094
|
const off = camera.subscribe((c) => {
|
|
12052
12095
|
clearTimeout(timer);
|
|
@@ -12139,7 +12182,7 @@ function CanvasView({ canvas, page }) {
|
|
|
12139
12182
|
navigate
|
|
12140
12183
|
]);
|
|
12141
12184
|
useEffect(() => {
|
|
12142
|
-
if (!CAPTURE) return;
|
|
12185
|
+
if (!CAPTURE$1) return;
|
|
12143
12186
|
const started = Date.now();
|
|
12144
12187
|
const timer = setInterval(() => {
|
|
12145
12188
|
const frames = store.get().frames;
|
|
@@ -12222,7 +12265,7 @@ function CanvasView({ canvas, page }) {
|
|
|
12222
12265
|
inspectOn,
|
|
12223
12266
|
inspection
|
|
12224
12267
|
]);
|
|
12225
|
-
if (CAPTURE) return /* @__PURE__ */ jsx("div", {
|
|
12268
|
+
if (CAPTURE$1) return /* @__PURE__ */ jsx("div", {
|
|
12226
12269
|
className: "flex h-dvh",
|
|
12227
12270
|
children: /* @__PURE__ */ jsx(Viewport, {
|
|
12228
12271
|
layout,
|
|
@@ -12438,6 +12481,79 @@ function useScreenFrames(frameEls, layout) {
|
|
|
12438
12481
|
}, [frameEls, layout]);
|
|
12439
12482
|
}
|
|
12440
12483
|
//#endregion
|
|
12484
|
+
//#region viewer/src/ui/sheet.tsx
|
|
12485
|
+
/**
|
|
12486
|
+
* A panel sliding up from the bottom edge, for phones: lists to pick from and actions. Like
|
|
12487
|
+
* Dialog, focus stays inside, and Escape, a tap on the dimmed page or the close button close it.
|
|
12488
|
+
*/
|
|
12489
|
+
function Sheet({ open, onOpenChange, title, children }) {
|
|
12490
|
+
return /* @__PURE__ */ jsx(Dialog$1, {
|
|
12491
|
+
open,
|
|
12492
|
+
onOpenChange,
|
|
12493
|
+
children: /* @__PURE__ */ jsx(AnimatePresence, { children: open ? /* @__PURE__ */ jsxs(DialogPortal, {
|
|
12494
|
+
forceMount: true,
|
|
12495
|
+
children: [/* @__PURE__ */ jsx(DialogOverlay, {
|
|
12496
|
+
asChild: true,
|
|
12497
|
+
forceMount: true,
|
|
12498
|
+
children: /* @__PURE__ */ jsx(motion.div, {
|
|
12499
|
+
initial: { opacity: 0 },
|
|
12500
|
+
animate: { opacity: 1 },
|
|
12501
|
+
exit: { opacity: 0 },
|
|
12502
|
+
transition: { duration: .16 },
|
|
12503
|
+
className: "fixed inset-0 z-50 bg-overlay"
|
|
12504
|
+
})
|
|
12505
|
+
}), /* @__PURE__ */ jsx(DialogContent, {
|
|
12506
|
+
asChild: true,
|
|
12507
|
+
forceMount: true,
|
|
12508
|
+
"aria-describedby": void 0,
|
|
12509
|
+
children: /* @__PURE__ */ jsxs(motion.div, {
|
|
12510
|
+
initial: { y: "100%" },
|
|
12511
|
+
animate: { y: 0 },
|
|
12512
|
+
exit: {
|
|
12513
|
+
y: "100%",
|
|
12514
|
+
transition: {
|
|
12515
|
+
duration: .18,
|
|
12516
|
+
ease: "easeIn"
|
|
12517
|
+
}
|
|
12518
|
+
},
|
|
12519
|
+
transition: {
|
|
12520
|
+
type: "spring",
|
|
12521
|
+
stiffness: 420,
|
|
12522
|
+
damping: 40
|
|
12523
|
+
},
|
|
12524
|
+
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",
|
|
12525
|
+
children: [
|
|
12526
|
+
/* @__PURE__ */ jsx("div", {
|
|
12527
|
+
className: "flex justify-center pt-2",
|
|
12528
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
12529
|
+
"aria-hidden": true,
|
|
12530
|
+
className: "h-1 w-9 rounded-full bg-rule-strong"
|
|
12531
|
+
})
|
|
12532
|
+
}),
|
|
12533
|
+
/* @__PURE__ */ jsxs("div", {
|
|
12534
|
+
className: "flex min-h-[48px] items-center justify-between gap-3 pr-2 pl-4",
|
|
12535
|
+
children: [/* @__PURE__ */ jsx(DialogTitle, {
|
|
12536
|
+
className: "m-0 truncate text-[15px] font-semibold",
|
|
12537
|
+
children: title
|
|
12538
|
+
}), /* @__PURE__ */ jsx(DialogClose, {
|
|
12539
|
+
asChild: true,
|
|
12540
|
+
children: /* @__PURE__ */ jsx(IconButton, {
|
|
12541
|
+
icon: "x",
|
|
12542
|
+
label: "Close"
|
|
12543
|
+
})
|
|
12544
|
+
})]
|
|
12545
|
+
}),
|
|
12546
|
+
/* @__PURE__ */ jsx("div", {
|
|
12547
|
+
className: "min-h-0 overflow-y-auto overscroll-contain border-t border-rule",
|
|
12548
|
+
children
|
|
12549
|
+
})
|
|
12550
|
+
]
|
|
12551
|
+
})
|
|
12552
|
+
})]
|
|
12553
|
+
}) : null })
|
|
12554
|
+
});
|
|
12555
|
+
}
|
|
12556
|
+
//#endregion
|
|
12441
12557
|
//#region viewer/src/pages/canvas/play-page.tsx
|
|
12442
12558
|
function framesOf(canvas, pageId) {
|
|
12443
12559
|
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) => ({
|
|
@@ -12743,6 +12859,441 @@ function Player({ canvas, itemId }) {
|
|
|
12743
12859
|
});
|
|
12744
12860
|
}
|
|
12745
12861
|
//#endregion
|
|
12862
|
+
//#region viewer/src/pages/canvas/phone-view.tsx
|
|
12863
|
+
/** Room around the screen on the stage, per side. */
|
|
12864
|
+
var GUTTER = 12;
|
|
12865
|
+
/**
|
|
12866
|
+
* A canvas on a phone: no board to pan, one screen at a time as in play mode. The screen fills
|
|
12867
|
+
* the width and scrolls; the bottom bar steps through every screen of the canvas and opens the
|
|
12868
|
+
* list of them. Serves `/c/:canvas`, its pages and `/c/:canvas/play/:item` under 768px.
|
|
12869
|
+
*/
|
|
12870
|
+
function PhoneCanvas({ canvasId, pageId, itemId }) {
|
|
12871
|
+
const { data: canvas, error, isLoading } = useCanvas(canvasId);
|
|
12872
|
+
if (error) return /* @__PURE__ */ jsx(NotFound, {
|
|
12873
|
+
title: "Canvas not found",
|
|
12874
|
+
text: error.message
|
|
12875
|
+
});
|
|
12876
|
+
if (isLoading || !canvas) return /* @__PURE__ */ jsx("div", { className: "canvas-dots h-dvh" });
|
|
12877
|
+
return /* @__PURE__ */ jsx(PhoneViewer, {
|
|
12878
|
+
canvas,
|
|
12879
|
+
pageId,
|
|
12880
|
+
itemId
|
|
12881
|
+
});
|
|
12882
|
+
}
|
|
12883
|
+
function PhoneViewer({ canvas, pageId, itemId }) {
|
|
12884
|
+
const [, navigate] = useLocation();
|
|
12885
|
+
const { frameSandbox, slots, scope } = useViewer();
|
|
12886
|
+
const appTheme = useTheme();
|
|
12887
|
+
const all = useMemo(() => framesOf(canvas, null), [canvas]);
|
|
12888
|
+
const index = Math.max(0, itemId ? all.findIndex((entry) => entry.item.id === itemId) : pageId ? all.findIndex((entry) => entry.pageId === pageId) : 0);
|
|
12889
|
+
const entry = all[index];
|
|
12890
|
+
const item = entry?.item;
|
|
12891
|
+
const [theme, setTheme] = useState(canvas.theme ?? appTheme);
|
|
12892
|
+
const [listOpen, setListOpen] = useState(false);
|
|
12893
|
+
const [errors, setErrors] = useState([]);
|
|
12894
|
+
const [stageWidth, setStageWidth] = useState(0);
|
|
12895
|
+
const [autoHeight, setAutoHeight] = useState(null);
|
|
12896
|
+
const [direction, setDirection] = useState(0);
|
|
12897
|
+
const [actual, setActual] = useState(false);
|
|
12898
|
+
const stage = useRef(null);
|
|
12899
|
+
const frame = useRef(null);
|
|
12900
|
+
const [frameEl, setFrameEl] = useState(null);
|
|
12901
|
+
const setFrame = useCallback((el) => {
|
|
12902
|
+
if (!el) return;
|
|
12903
|
+
frame.current = el;
|
|
12904
|
+
setFrameEl(el);
|
|
12905
|
+
}, []);
|
|
12906
|
+
const show = useCallback((target, step = 0) => {
|
|
12907
|
+
setDirection(step);
|
|
12908
|
+
navigate(playHref(canvas.id, target.item.id, target.pageId), { replace: true });
|
|
12909
|
+
}, [canvas.id, navigate]);
|
|
12910
|
+
const move = (step) => {
|
|
12911
|
+
if (all.length < 2) return;
|
|
12912
|
+
show(all[(index + step + all.length) % all.length], step);
|
|
12913
|
+
};
|
|
12914
|
+
const goTo = (id) => {
|
|
12915
|
+
const target = all.find((e) => e.item.id === id);
|
|
12916
|
+
if (target) show(target, all.indexOf(target) > index ? 1 : -1);
|
|
12917
|
+
};
|
|
12918
|
+
useLayoutEffect(() => {
|
|
12919
|
+
const el = stage.current;
|
|
12920
|
+
if (!el) return;
|
|
12921
|
+
const observer = new ResizeObserver(() => setStageWidth(el.clientWidth));
|
|
12922
|
+
observer.observe(el);
|
|
12923
|
+
return () => observer.disconnect();
|
|
12924
|
+
}, []);
|
|
12925
|
+
const frameTheme = item?.kind === "screen" ? item.theme ?? theme : theme;
|
|
12926
|
+
const [openTheme] = useState(frameTheme);
|
|
12927
|
+
const src = item ? item.kind === "screen" ? frameSrc(item.url, openTheme) : item.url : "";
|
|
12928
|
+
const openUrl = useOpenUrl(canvas.id, item, frameTheme);
|
|
12929
|
+
useEffect(() => {
|
|
12930
|
+
setErrors([]);
|
|
12931
|
+
setAutoHeight(item?.kind === "screen" ? item.measuredHeight ?? null : null);
|
|
12932
|
+
stage.current?.scrollTo({ top: 0 });
|
|
12933
|
+
const el = frame.current;
|
|
12934
|
+
if (!el || item?.kind !== "screen") return;
|
|
12935
|
+
return listenToFrame(el, (message) => {
|
|
12936
|
+
if (message.type === "size") setAutoHeight(message.height);
|
|
12937
|
+
else if (message.type === "error") setErrors((list) => list.includes(message.message) ? list : [...list, message.message]);
|
|
12938
|
+
else if (message.type === "updated") setErrors([]);
|
|
12939
|
+
else if (message.type === "go") goTo(message.target.includes("/") ? message.target.split("/")[1] : message.target);
|
|
12940
|
+
else if (message.type === "link") followLink(message, canvas, goTo);
|
|
12941
|
+
});
|
|
12942
|
+
}, [frameEl, src]);
|
|
12943
|
+
useEffect(() => {
|
|
12944
|
+
if (item?.kind === "screen") sendTheme(frame.current, frameTheme);
|
|
12945
|
+
}, [frameTheme, item]);
|
|
12946
|
+
useEffect(() => {
|
|
12947
|
+
const onKey = (event) => {
|
|
12948
|
+
if (document.querySelector("[role=\"dialog\"]")) return;
|
|
12949
|
+
if (event.key === "ArrowRight") move(1);
|
|
12950
|
+
else if (event.key === "ArrowLeft") move(-1);
|
|
12951
|
+
};
|
|
12952
|
+
window.addEventListener("keydown", onKey);
|
|
12953
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
12954
|
+
});
|
|
12955
|
+
const swipe = useRef(null);
|
|
12956
|
+
const onTouchStart = (event) => {
|
|
12957
|
+
const touch = event.touches[0];
|
|
12958
|
+
swipe.current = touch ? {
|
|
12959
|
+
x: touch.clientX,
|
|
12960
|
+
y: touch.clientY
|
|
12961
|
+
} : null;
|
|
12962
|
+
};
|
|
12963
|
+
const onTouchEnd = (event) => {
|
|
12964
|
+
const start = swipe.current;
|
|
12965
|
+
const touch = event.changedTouches[0];
|
|
12966
|
+
swipe.current = null;
|
|
12967
|
+
if (!start || !touch) return;
|
|
12968
|
+
const dx = touch.clientX - start.x;
|
|
12969
|
+
const dy = touch.clientY - start.y;
|
|
12970
|
+
if (Math.abs(dx) > 56 && Math.abs(dx) > Math.abs(dy) * 1.5) move(dx < 0 ? 1 : -1);
|
|
12971
|
+
};
|
|
12972
|
+
if (!item || !entry) return /* @__PURE__ */ jsxs("div", {
|
|
12973
|
+
className: "flex h-dvh flex-col",
|
|
12974
|
+
children: [/* @__PURE__ */ jsx(PhoneBar, {
|
|
12975
|
+
canvas,
|
|
12976
|
+
title: canvas.title,
|
|
12977
|
+
sub: null
|
|
12978
|
+
}), /* @__PURE__ */ jsx(NotFound, {
|
|
12979
|
+
title: "No screens here",
|
|
12980
|
+
text: "This canvas has no screens to show yet."
|
|
12981
|
+
})]
|
|
12982
|
+
});
|
|
12983
|
+
const width = item.frame.width;
|
|
12984
|
+
const fitScale = stageWidth ? Math.min(1, (stageWidth - 24) / width) : 0;
|
|
12985
|
+
const wide = fitScale > 0 && fitScale < .8;
|
|
12986
|
+
const scale = wide && actual ? 1 : fitScale;
|
|
12987
|
+
const height = item.frame.height === "auto" ? Math.max(autoHeight ?? 0, 480) : item.frame.height;
|
|
12988
|
+
const canTheme = item.kind === "screen" && !item.theme;
|
|
12989
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
12990
|
+
className: "flex h-dvh flex-col",
|
|
12991
|
+
children: [
|
|
12992
|
+
/* @__PURE__ */ jsx(PhoneBar, {
|
|
12993
|
+
canvas,
|
|
12994
|
+
title: item.title || item.id,
|
|
12995
|
+
sub: [canvas.title, entry.where].filter(Boolean).join(" · "),
|
|
12996
|
+
back: scope ? void 0 : "/",
|
|
12997
|
+
end: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
12998
|
+
wide ? /* @__PURE__ */ jsx(IconButton, {
|
|
12999
|
+
icon: actual ? "zoom-out" : "zoom-in",
|
|
13000
|
+
label: actual ? "Fit to width" : "Actual size",
|
|
13001
|
+
onClick: () => setActual((value) => !value)
|
|
13002
|
+
}) : null,
|
|
13003
|
+
/* @__PURE__ */ jsxs(Menu, {
|
|
13004
|
+
align: "end",
|
|
13005
|
+
width: 220,
|
|
13006
|
+
trigger: /* @__PURE__ */ jsx(IconButton, {
|
|
13007
|
+
icon: "more",
|
|
13008
|
+
label: "Screen options"
|
|
13009
|
+
}),
|
|
13010
|
+
children: [
|
|
13011
|
+
canTheme ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
13012
|
+
/* @__PURE__ */ jsx(MenuLabel, { children: "Screen theme" }),
|
|
13013
|
+
/* @__PURE__ */ jsx(ThemeItem, {
|
|
13014
|
+
value: "light",
|
|
13015
|
+
current: theme,
|
|
13016
|
+
onPick: setTheme
|
|
13017
|
+
}),
|
|
13018
|
+
/* @__PURE__ */ jsx(ThemeItem, {
|
|
13019
|
+
value: "dark",
|
|
13020
|
+
current: theme,
|
|
13021
|
+
onPick: setTheme
|
|
13022
|
+
}),
|
|
13023
|
+
/* @__PURE__ */ jsx(MenuSeparator, {})
|
|
13024
|
+
] }) : null,
|
|
13025
|
+
/* @__PURE__ */ jsxs(MenuItem, {
|
|
13026
|
+
onSelect: () => reloadFrame(frame.current),
|
|
13027
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
13028
|
+
name: "refresh",
|
|
13029
|
+
size: 15,
|
|
13030
|
+
className: "text-ink2"
|
|
13031
|
+
}), "Reload screen"]
|
|
13032
|
+
}),
|
|
13033
|
+
openUrl ? /* @__PURE__ */ jsxs(MenuItem, {
|
|
13034
|
+
onSelect: () => window.open(openUrl, "_blank", "noopener"),
|
|
13035
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
13036
|
+
name: "external",
|
|
13037
|
+
size: 15,
|
|
13038
|
+
className: "text-ink2"
|
|
13039
|
+
}), "Open in a new tab"]
|
|
13040
|
+
}) : null,
|
|
13041
|
+
/* @__PURE__ */ jsxs(MenuItem, {
|
|
13042
|
+
onSelect: () => setThemePref(appTheme === "dark" ? "light" : "dark"),
|
|
13043
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
13044
|
+
name: appTheme === "dark" ? "sun" : "moon",
|
|
13045
|
+
size: 15,
|
|
13046
|
+
className: "text-ink2"
|
|
13047
|
+
}), appTheme === "dark" ? "Light appearance" : "Dark appearance"]
|
|
13048
|
+
})
|
|
13049
|
+
]
|
|
13050
|
+
}),
|
|
13051
|
+
slots.barEnd
|
|
13052
|
+
] })
|
|
13053
|
+
}),
|
|
13054
|
+
slots.belowBar,
|
|
13055
|
+
/* @__PURE__ */ jsxs("div", {
|
|
13056
|
+
ref: stage,
|
|
13057
|
+
onTouchStart,
|
|
13058
|
+
onTouchEnd,
|
|
13059
|
+
className: cn("canvas-dots relative min-h-0 grow overflow-y-auto overscroll-contain", wide && actual ? "overflow-x-auto" : "overflow-x-hidden"),
|
|
13060
|
+
children: [errors.length ? /* @__PURE__ */ jsxs("div", {
|
|
13061
|
+
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",
|
|
13062
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
13063
|
+
name: "alert",
|
|
13064
|
+
size: 14,
|
|
13065
|
+
className: "mt-px shrink-0"
|
|
13066
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
13067
|
+
className: "min-w-0 break-words",
|
|
13068
|
+
children: errors.join("\n")
|
|
13069
|
+
})]
|
|
13070
|
+
}) : null, /* @__PURE__ */ jsx(AnimatePresence, {
|
|
13071
|
+
initial: false,
|
|
13072
|
+
mode: "popLayout",
|
|
13073
|
+
custom: direction,
|
|
13074
|
+
children: /* @__PURE__ */ jsx(motion.div, {
|
|
13075
|
+
custom: direction,
|
|
13076
|
+
initial: {
|
|
13077
|
+
opacity: 0,
|
|
13078
|
+
x: direction * 40
|
|
13079
|
+
},
|
|
13080
|
+
animate: {
|
|
13081
|
+
opacity: 1,
|
|
13082
|
+
x: 0
|
|
13083
|
+
},
|
|
13084
|
+
exit: {
|
|
13085
|
+
opacity: 0,
|
|
13086
|
+
x: direction * -40,
|
|
13087
|
+
transition: { duration: .12 }
|
|
13088
|
+
},
|
|
13089
|
+
transition: {
|
|
13090
|
+
type: "spring",
|
|
13091
|
+
stiffness: 420,
|
|
13092
|
+
damping: 38
|
|
13093
|
+
},
|
|
13094
|
+
className: cn("flex", wide && actual ? "w-max" : "justify-center"),
|
|
13095
|
+
style: { padding: GUTTER },
|
|
13096
|
+
children: scale ? /* @__PURE__ */ jsx("div", {
|
|
13097
|
+
className: "shrink-0 overflow-hidden rounded-[6px] bg-surface",
|
|
13098
|
+
style: {
|
|
13099
|
+
width: width * scale,
|
|
13100
|
+
height: height * scale,
|
|
13101
|
+
boxShadow: "var(--shadow-frame), 0 0 0 1px var(--rule)"
|
|
13102
|
+
},
|
|
13103
|
+
children: /* @__PURE__ */ jsx("iframe", {
|
|
13104
|
+
ref: setFrame,
|
|
13105
|
+
src,
|
|
13106
|
+
title: item.title,
|
|
13107
|
+
allow: "clipboard-read; clipboard-write; fullscreen",
|
|
13108
|
+
sandbox: frameSandbox,
|
|
13109
|
+
className: "block border-0",
|
|
13110
|
+
style: {
|
|
13111
|
+
width,
|
|
13112
|
+
height,
|
|
13113
|
+
transform: `scale(${scale})`,
|
|
13114
|
+
transformOrigin: "0 0",
|
|
13115
|
+
colorScheme: frameTheme
|
|
13116
|
+
},
|
|
13117
|
+
onLoad: () => item.kind === "screen" && sendTheme(frame.current, frameTheme)
|
|
13118
|
+
}, `${item.id}:${item.kind === "screen" ? item.rev : item.url}`)
|
|
13119
|
+
}) : null
|
|
13120
|
+
}, item.id)
|
|
13121
|
+
})]
|
|
13122
|
+
}),
|
|
13123
|
+
/* @__PURE__ */ jsxs("nav", {
|
|
13124
|
+
"aria-label": "Screens",
|
|
13125
|
+
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))]",
|
|
13126
|
+
children: [
|
|
13127
|
+
/* @__PURE__ */ jsx(IconButton, {
|
|
13128
|
+
icon: "chevron-left",
|
|
13129
|
+
label: "Previous screen",
|
|
13130
|
+
onClick: () => move(-1),
|
|
13131
|
+
disabled: all.length < 2
|
|
13132
|
+
}),
|
|
13133
|
+
/* @__PURE__ */ jsxs("button", {
|
|
13134
|
+
type: "button",
|
|
13135
|
+
onClick: () => setListOpen(true),
|
|
13136
|
+
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",
|
|
13137
|
+
children: [
|
|
13138
|
+
/* @__PURE__ */ jsx(Icon, {
|
|
13139
|
+
name: "list",
|
|
13140
|
+
size: 15,
|
|
13141
|
+
className: "shrink-0 text-muted"
|
|
13142
|
+
}),
|
|
13143
|
+
/* @__PURE__ */ jsxs("span", {
|
|
13144
|
+
className: "tabular shrink-0 text-muted",
|
|
13145
|
+
children: [
|
|
13146
|
+
index + 1,
|
|
13147
|
+
" / ",
|
|
13148
|
+
all.length
|
|
13149
|
+
]
|
|
13150
|
+
}),
|
|
13151
|
+
/* @__PURE__ */ jsx("span", {
|
|
13152
|
+
className: "min-w-0 truncate font-medium",
|
|
13153
|
+
children: item.title || item.id
|
|
13154
|
+
})
|
|
13155
|
+
]
|
|
13156
|
+
}),
|
|
13157
|
+
/* @__PURE__ */ jsx(IconButton, {
|
|
13158
|
+
icon: "chevron-right",
|
|
13159
|
+
label: "Next screen",
|
|
13160
|
+
onClick: () => move(1),
|
|
13161
|
+
disabled: all.length < 2
|
|
13162
|
+
})
|
|
13163
|
+
]
|
|
13164
|
+
}),
|
|
13165
|
+
/* @__PURE__ */ jsx(Sheet, {
|
|
13166
|
+
open: listOpen,
|
|
13167
|
+
onOpenChange: setListOpen,
|
|
13168
|
+
title: canvas.title,
|
|
13169
|
+
children: /* @__PURE__ */ jsx(ScreenList, {
|
|
13170
|
+
canvas,
|
|
13171
|
+
current: item.id,
|
|
13172
|
+
onPick: (id) => {
|
|
13173
|
+
setListOpen(false);
|
|
13174
|
+
goTo(id);
|
|
13175
|
+
}
|
|
13176
|
+
})
|
|
13177
|
+
})
|
|
13178
|
+
]
|
|
13179
|
+
});
|
|
13180
|
+
}
|
|
13181
|
+
function ThemeItem({ value, current, onPick }) {
|
|
13182
|
+
return /* @__PURE__ */ jsxs(MenuItem, {
|
|
13183
|
+
active: current === value,
|
|
13184
|
+
onSelect: () => onPick(value),
|
|
13185
|
+
children: [
|
|
13186
|
+
/* @__PURE__ */ jsx(Icon, {
|
|
13187
|
+
name: value === "dark" ? "moon" : "sun",
|
|
13188
|
+
size: 15,
|
|
13189
|
+
className: "text-ink2"
|
|
13190
|
+
}),
|
|
13191
|
+
value === "dark" ? "Dark" : "Light",
|
|
13192
|
+
current === value ? /* @__PURE__ */ jsx(Icon, {
|
|
13193
|
+
name: "check",
|
|
13194
|
+
size: 15,
|
|
13195
|
+
className: "ml-auto text-ink2"
|
|
13196
|
+
}) : null
|
|
13197
|
+
]
|
|
13198
|
+
});
|
|
13199
|
+
}
|
|
13200
|
+
/** The phone's top bar: a way back, the screen and where it sits, and the screen's actions. */
|
|
13201
|
+
function PhoneBar({ canvas, title, sub, back, end }) {
|
|
13202
|
+
const { slots } = useViewer();
|
|
13203
|
+
return /* @__PURE__ */ jsxs("header", {
|
|
13204
|
+
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)]",
|
|
13205
|
+
children: [
|
|
13206
|
+
slots.barStart,
|
|
13207
|
+
back ? /* @__PURE__ */ jsx(Link, {
|
|
13208
|
+
href: back,
|
|
13209
|
+
"aria-label": `All canvases, leaving ${canvas.title}`,
|
|
13210
|
+
className: "inline-flex size-10 shrink-0 items-center justify-center rounded-[8px] text-ink2 active:bg-soft2",
|
|
13211
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
13212
|
+
name: "arrow-left",
|
|
13213
|
+
size: 18
|
|
13214
|
+
})
|
|
13215
|
+
}) : /* @__PURE__ */ jsx("span", {
|
|
13216
|
+
className: "flex size-10 shrink-0 items-center justify-center",
|
|
13217
|
+
children: /* @__PURE__ */ jsx(Mark, {})
|
|
13218
|
+
}),
|
|
13219
|
+
/* @__PURE__ */ jsxs("div", {
|
|
13220
|
+
className: "flex min-w-0 grow flex-col leading-tight",
|
|
13221
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
13222
|
+
className: "truncate text-[14.5px] font-semibold",
|
|
13223
|
+
children: title
|
|
13224
|
+
}), sub ? /* @__PURE__ */ jsx("span", {
|
|
13225
|
+
className: "truncate text-[12px] text-muted",
|
|
13226
|
+
children: sub
|
|
13227
|
+
}) : null]
|
|
13228
|
+
}),
|
|
13229
|
+
/* @__PURE__ */ jsx("div", {
|
|
13230
|
+
className: "flex shrink-0 items-center gap-1",
|
|
13231
|
+
children: end
|
|
13232
|
+
})
|
|
13233
|
+
]
|
|
13234
|
+
});
|
|
13235
|
+
}
|
|
13236
|
+
var KIND_ICON = (entry) => entry.item.kind === "url" ? "globe" : entry.item.frame.device === "phone" ? "smartphone" : entry.item.frame.device === "tablet" ? "tablet" : "monitor";
|
|
13237
|
+
/** Every screen of the canvas by page and section, the current one marked. */
|
|
13238
|
+
function ScreenList({ canvas, current, onPick }) {
|
|
13239
|
+
const activeRef = useRef(null);
|
|
13240
|
+
useEffect(() => {
|
|
13241
|
+
activeRef.current?.scrollIntoView({ block: "center" });
|
|
13242
|
+
}, []);
|
|
13243
|
+
const multi = canvas.pages.length > 1;
|
|
13244
|
+
return /* @__PURE__ */ jsx("div", {
|
|
13245
|
+
className: "flex flex-col pb-2",
|
|
13246
|
+
children: canvas.pages.map((page) => {
|
|
13247
|
+
const entries = framesOf(canvas, page.id);
|
|
13248
|
+
if (!entries.length) return null;
|
|
13249
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
13250
|
+
className: "flex flex-col",
|
|
13251
|
+
children: [multi ? /* @__PURE__ */ jsx("h3", {
|
|
13252
|
+
className: "sticky top-0 z-[1] m-0 border-b border-rule bg-soft px-4 py-2 text-[12px] font-semibold text-ink2",
|
|
13253
|
+
children: page.title
|
|
13254
|
+
}) : null, page.sections.map((section) => {
|
|
13255
|
+
const items = entries.filter((entry) => section.items.includes(entry.item));
|
|
13256
|
+
if (!items.length) return null;
|
|
13257
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
13258
|
+
className: "flex flex-col",
|
|
13259
|
+
children: [section.title ? /* @__PURE__ */ jsx("div", {
|
|
13260
|
+
className: "px-4 pt-3 pb-1 text-[12px] text-muted",
|
|
13261
|
+
children: section.title
|
|
13262
|
+
}) : null, items.map((entry) => {
|
|
13263
|
+
const on = entry.item.id === current;
|
|
13264
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
13265
|
+
ref: on ? activeRef : void 0,
|
|
13266
|
+
type: "button",
|
|
13267
|
+
"aria-current": on ? "true" : void 0,
|
|
13268
|
+
onClick: () => onPick(entry.item.id),
|
|
13269
|
+
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"),
|
|
13270
|
+
children: [
|
|
13271
|
+
/* @__PURE__ */ jsx("span", {
|
|
13272
|
+
className: cn("flex size-8 shrink-0 items-center justify-center rounded-[8px]", on ? "bg-action-soft text-action" : "bg-soft2 text-muted"),
|
|
13273
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
13274
|
+
name: KIND_ICON(entry),
|
|
13275
|
+
size: 15
|
|
13276
|
+
})
|
|
13277
|
+
}),
|
|
13278
|
+
/* @__PURE__ */ jsx("span", {
|
|
13279
|
+
className: "min-w-0 grow truncate",
|
|
13280
|
+
children: entry.item.title || entry.item.id
|
|
13281
|
+
}),
|
|
13282
|
+
on ? /* @__PURE__ */ jsx(Icon, {
|
|
13283
|
+
name: "check",
|
|
13284
|
+
size: 16,
|
|
13285
|
+
className: "shrink-0 text-action"
|
|
13286
|
+
}) : null
|
|
13287
|
+
]
|
|
13288
|
+
}, entry.item.id);
|
|
13289
|
+
})]
|
|
13290
|
+
}, section.id);
|
|
13291
|
+
})]
|
|
13292
|
+
}, page.id);
|
|
13293
|
+
})
|
|
13294
|
+
});
|
|
13295
|
+
}
|
|
13296
|
+
//#endregion
|
|
12746
13297
|
//#region viewer/src/lib/format.ts
|
|
12747
13298
|
var units = [
|
|
12748
13299
|
["year", 31536e3],
|
|
@@ -12843,6 +13394,7 @@ function Foot({ children }) {
|
|
|
12843
13394
|
}
|
|
12844
13395
|
function CanvasList({ project }) {
|
|
12845
13396
|
const { canvasRowActions: rowActions, canvasesFoot } = useViewer().slots;
|
|
13397
|
+
const phone = useIsPhone();
|
|
12846
13398
|
if (!project.canvases.length) return /* @__PURE__ */ jsxs(Panel, { children: [
|
|
12847
13399
|
/* @__PURE__ */ jsx(PanelHead, {
|
|
12848
13400
|
title: "Canvases",
|
|
@@ -12873,7 +13425,31 @@ function CanvasList({ project }) {
|
|
|
12873
13425
|
const actions = rowActions?.(canvas.id);
|
|
12874
13426
|
return /* @__PURE__ */ jsx(WithActions, {
|
|
12875
13427
|
actions,
|
|
12876
|
-
children: /* @__PURE__ */ jsxs(RowLink, {
|
|
13428
|
+
children: phone ? /* @__PURE__ */ jsxs(RowLink, {
|
|
13429
|
+
to: `/c/${encodeURIComponent(canvas.id)}`,
|
|
13430
|
+
cols: "86px minmax(0,1fr)",
|
|
13431
|
+
align: "ll",
|
|
13432
|
+
pad: "10px 16px",
|
|
13433
|
+
minH: 74,
|
|
13434
|
+
first: !!actions,
|
|
13435
|
+
children: [/* @__PURE__ */ jsx(Cover, { canvas }), /* @__PURE__ */ jsx(Two, {
|
|
13436
|
+
top: canvas.title,
|
|
13437
|
+
bottom: /* @__PURE__ */ jsxs("span", {
|
|
13438
|
+
className: "flex min-w-0 items-center gap-2",
|
|
13439
|
+
children: [canvas.issues ? /* @__PURE__ */ jsx(Badge, {
|
|
13440
|
+
tone: "danger",
|
|
13441
|
+
children: plural(canvas.issues, "problem")
|
|
13442
|
+
}) : null, /* @__PURE__ */ jsxs("span", {
|
|
13443
|
+
className: "truncate",
|
|
13444
|
+
children: [
|
|
13445
|
+
plural(canvas.screens, "screen"),
|
|
13446
|
+
" · ",
|
|
13447
|
+
relativeTime(canvas.updatedAt)
|
|
13448
|
+
]
|
|
13449
|
+
})]
|
|
13450
|
+
})
|
|
13451
|
+
})]
|
|
13452
|
+
}) : /* @__PURE__ */ jsxs(RowLink, {
|
|
12877
13453
|
to: `/c/${encodeURIComponent(canvas.id)}`,
|
|
12878
13454
|
cols: "86px minmax(0,1fr) auto auto",
|
|
12879
13455
|
align: "llrr",
|
|
@@ -12991,6 +13567,7 @@ function HomeSkeleton() {
|
|
|
12991
13567
|
function HomePage() {
|
|
12992
13568
|
const { data: project, error, isLoading } = useProject();
|
|
12993
13569
|
const { slots } = useViewer();
|
|
13570
|
+
const phone = useIsPhone();
|
|
12994
13571
|
if (error) return /* @__PURE__ */ jsx(Notice, {
|
|
12995
13572
|
title: "Could not load the project",
|
|
12996
13573
|
text: error.message
|
|
@@ -12999,13 +13576,19 @@ function HomePage() {
|
|
|
12999
13576
|
return /* @__PURE__ */ jsxs(Content, { children: [
|
|
13000
13577
|
/* @__PURE__ */ jsx(PageHead, {
|
|
13001
13578
|
title: project.name,
|
|
13002
|
-
right: slots.homeActions,
|
|
13579
|
+
right: phone ? void 0 : slots.homeActions,
|
|
13580
|
+
badge: phone && slots.homeActions ? /* @__PURE__ */ jsx("span", {
|
|
13581
|
+
className: "ml-auto flex",
|
|
13582
|
+
children: slots.homeActions
|
|
13583
|
+
}) : void 0,
|
|
13003
13584
|
sub: slots.homeSub ? slots.homeSub : project.static ? `Snapshot built with foss-design ${project.version}` : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
13004
13585
|
/* @__PURE__ */ jsxs(Mono, {
|
|
13005
13586
|
size: 12.5,
|
|
13587
|
+
wrap: true,
|
|
13006
13588
|
children: [project.root, "/.design"]
|
|
13007
13589
|
}),
|
|
13008
|
-
"
|
|
13590
|
+
" ",
|
|
13591
|
+
"· foss-design ",
|
|
13009
13592
|
project.version
|
|
13010
13593
|
] })
|
|
13011
13594
|
}),
|
|
@@ -16666,6 +17249,7 @@ function MotionPage({ system }) {
|
|
|
16666
17249
|
//#region viewer/src/pages/system/nav.tsx
|
|
16667
17250
|
/** Grouped links of the system section; a wrapping row above the content on narrow screens. */
|
|
16668
17251
|
function SystemNav({ system }) {
|
|
17252
|
+
const phone = useIsPhone();
|
|
16669
17253
|
const kinds = new Set(system.tokens.map((token) => token.kind));
|
|
16670
17254
|
const foundations = [
|
|
16671
17255
|
{
|
|
@@ -16707,6 +17291,33 @@ function SystemNav({ system }) {
|
|
|
16707
17291
|
groups.set(component.group, list);
|
|
16708
17292
|
}
|
|
16709
17293
|
const guidelines = system.guidelines.filter((guide) => guide.slug !== "overview");
|
|
17294
|
+
if (phone) {
|
|
17295
|
+
const sections = [
|
|
17296
|
+
{
|
|
17297
|
+
label: "",
|
|
17298
|
+
items: [{
|
|
17299
|
+
to: "/system",
|
|
17300
|
+
label: "Overview"
|
|
17301
|
+
}]
|
|
17302
|
+
},
|
|
17303
|
+
{
|
|
17304
|
+
label: "Foundations",
|
|
17305
|
+
items: foundations
|
|
17306
|
+
},
|
|
17307
|
+
{
|
|
17308
|
+
label: "Guidelines",
|
|
17309
|
+
items: guidelines.map((guide) => ({
|
|
17310
|
+
to: `/system/guidelines/${encodeURIComponent(guide.slug)}`,
|
|
17311
|
+
label: guide.title
|
|
17312
|
+
}))
|
|
17313
|
+
},
|
|
17314
|
+
...[...groups].map(([label, items]) => ({
|
|
17315
|
+
label,
|
|
17316
|
+
items
|
|
17317
|
+
}))
|
|
17318
|
+
];
|
|
17319
|
+
return /* @__PURE__ */ jsx(PhoneSystemNav, { sections: sections.filter((section) => section.items.length) });
|
|
17320
|
+
}
|
|
16710
17321
|
return /* @__PURE__ */ jsxs("div", {
|
|
16711
17322
|
className: "flex flex-col max-lg:gap-1 max-lg:[&_nav]:flex-row max-lg:[&_nav]:flex-wrap",
|
|
16712
17323
|
children: [
|
|
@@ -16742,6 +17353,70 @@ function SystemNav({ system }) {
|
|
|
16742
17353
|
function count(system, kind) {
|
|
16743
17354
|
return system.tokens.filter((token) => token.kind === kind).length || void 0;
|
|
16744
17355
|
}
|
|
17356
|
+
/** On a phone: the current section on a full-width button that lists them all in a sheet. */
|
|
17357
|
+
function PhoneSystemNav({ sections }) {
|
|
17358
|
+
const [location] = useLocation();
|
|
17359
|
+
const [open, setOpen] = useState(false);
|
|
17360
|
+
const current = sections.flatMap((section) => section.items).find((item) => item.to === location);
|
|
17361
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("button", {
|
|
17362
|
+
type: "button",
|
|
17363
|
+
onClick: () => setOpen(true),
|
|
17364
|
+
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",
|
|
17365
|
+
children: [
|
|
17366
|
+
/* @__PURE__ */ jsx("span", {
|
|
17367
|
+
className: "text-[12.5px] text-muted",
|
|
17368
|
+
children: "Section"
|
|
17369
|
+
}),
|
|
17370
|
+
/* @__PURE__ */ jsx("span", {
|
|
17371
|
+
className: "min-w-0 grow truncate font-medium",
|
|
17372
|
+
children: current?.label ?? "Overview"
|
|
17373
|
+
}),
|
|
17374
|
+
/* @__PURE__ */ jsx(Icon, {
|
|
17375
|
+
name: "chevron-down",
|
|
17376
|
+
size: 16,
|
|
17377
|
+
className: "shrink-0 text-muted"
|
|
17378
|
+
})
|
|
17379
|
+
]
|
|
17380
|
+
}), /* @__PURE__ */ jsx(Sheet, {
|
|
17381
|
+
open,
|
|
17382
|
+
onOpenChange: setOpen,
|
|
17383
|
+
title: "Design system",
|
|
17384
|
+
children: /* @__PURE__ */ jsx("nav", {
|
|
17385
|
+
"aria-label": "Design system",
|
|
17386
|
+
className: "flex flex-col pb-2",
|
|
17387
|
+
children: sections.map((section) => /* @__PURE__ */ jsxs("div", {
|
|
17388
|
+
className: "flex flex-col",
|
|
17389
|
+
children: [section.label ? /* @__PURE__ */ jsx("div", {
|
|
17390
|
+
className: "px-4 pt-3 pb-1 text-[12px] text-muted",
|
|
17391
|
+
children: section.label
|
|
17392
|
+
}) : null, section.items.map((item) => {
|
|
17393
|
+
const on = item.to === location;
|
|
17394
|
+
return /* @__PURE__ */ jsxs(Link, {
|
|
17395
|
+
href: item.to,
|
|
17396
|
+
onClick: () => setOpen(false),
|
|
17397
|
+
"aria-current": on ? "page" : void 0,
|
|
17398
|
+
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"),
|
|
17399
|
+
children: [
|
|
17400
|
+
/* @__PURE__ */ jsx("span", {
|
|
17401
|
+
className: "min-w-0 grow truncate",
|
|
17402
|
+
children: item.label
|
|
17403
|
+
}),
|
|
17404
|
+
item.hint ? /* @__PURE__ */ jsx("span", {
|
|
17405
|
+
className: "text-[12.5px] text-muted",
|
|
17406
|
+
children: item.hint
|
|
17407
|
+
}) : null,
|
|
17408
|
+
on ? /* @__PURE__ */ jsx(Icon, {
|
|
17409
|
+
name: "check",
|
|
17410
|
+
size: 16,
|
|
17411
|
+
className: "shrink-0 text-action"
|
|
17412
|
+
}) : null
|
|
17413
|
+
]
|
|
17414
|
+
}, item.to);
|
|
17415
|
+
})]
|
|
17416
|
+
}, section.label || "overview"))
|
|
17417
|
+
})
|
|
17418
|
+
})] });
|
|
17419
|
+
}
|
|
16745
17420
|
//#endregion
|
|
16746
17421
|
//#region viewer/src/pages/system/other-tokens.tsx
|
|
16747
17422
|
function OtherTokensPage({ system }) {
|
|
@@ -17437,7 +18112,30 @@ function Title() {
|
|
|
17437
18112
|
}, [name]);
|
|
17438
18113
|
return null;
|
|
17439
18114
|
}
|
|
18115
|
+
/** Screenshots (`?capture`) always get the canvas, whatever the window. */
|
|
18116
|
+
var CAPTURE = new URLSearchParams(window.location.search).has("capture");
|
|
18117
|
+
/** The canvas routes' pages; on a phone the canvas is one screen at a time. */
|
|
18118
|
+
function useCanvasPages() {
|
|
18119
|
+
const phone = useIsPhone() && !CAPTURE;
|
|
18120
|
+
return {
|
|
18121
|
+
play: (canvas, item) => phone ? /* @__PURE__ */ jsx(PhoneCanvas, {
|
|
18122
|
+
canvasId: canvas,
|
|
18123
|
+
itemId: item
|
|
18124
|
+
}) : /* @__PURE__ */ jsx(PlayPage, {
|
|
18125
|
+
canvasId: canvas,
|
|
18126
|
+
itemId: item
|
|
18127
|
+
}),
|
|
18128
|
+
canvas: (canvas, page) => phone ? /* @__PURE__ */ jsx(PhoneCanvas, {
|
|
18129
|
+
canvasId: canvas,
|
|
18130
|
+
pageId: page
|
|
18131
|
+
}) : /* @__PURE__ */ jsx(CanvasPage, {
|
|
18132
|
+
canvasId: canvas,
|
|
18133
|
+
pageId: page
|
|
18134
|
+
})
|
|
18135
|
+
};
|
|
18136
|
+
}
|
|
17440
18137
|
function Routes() {
|
|
18138
|
+
const pages = useCanvasPages();
|
|
17441
18139
|
return /* @__PURE__ */ jsxs(Switch$1, { children: [
|
|
17442
18140
|
/* @__PURE__ */ jsx(Route, {
|
|
17443
18141
|
path: "/c/:canvas/full/:item",
|
|
@@ -17448,21 +18146,15 @@ function Routes() {
|
|
|
17448
18146
|
}),
|
|
17449
18147
|
/* @__PURE__ */ jsx(Route, {
|
|
17450
18148
|
path: "/c/:canvas/play/:item",
|
|
17451
|
-
children: (params) =>
|
|
17452
|
-
canvasId: params.canvas,
|
|
17453
|
-
itemId: params.item
|
|
17454
|
-
})
|
|
18149
|
+
children: (params) => pages.play(params.canvas, params.item)
|
|
17455
18150
|
}),
|
|
17456
18151
|
/* @__PURE__ */ jsx(Route, {
|
|
17457
18152
|
path: "/c/:canvas/p/:page",
|
|
17458
|
-
children: (params) =>
|
|
17459
|
-
canvasId: params.canvas,
|
|
17460
|
-
pageId: params.page
|
|
17461
|
-
})
|
|
18153
|
+
children: (params) => pages.canvas(params.canvas, params.page)
|
|
17462
18154
|
}),
|
|
17463
18155
|
/* @__PURE__ */ jsx(Route, {
|
|
17464
18156
|
path: "/c/:canvas",
|
|
17465
|
-
children: (params) =>
|
|
18157
|
+
children: (params) => pages.canvas(params.canvas)
|
|
17466
18158
|
}),
|
|
17467
18159
|
/* @__PURE__ */ jsxs(Route, {
|
|
17468
18160
|
path: "/system/*?",
|
|
@@ -17482,6 +18174,7 @@ function ScopedRoutes({ canvas }) {
|
|
|
17482
18174
|
to: home,
|
|
17483
18175
|
replace: true
|
|
17484
18176
|
});
|
|
18177
|
+
const pages = useCanvasPages();
|
|
17485
18178
|
return /* @__PURE__ */ jsxs(Switch$1, { children: [
|
|
17486
18179
|
/* @__PURE__ */ jsx(Route, {
|
|
17487
18180
|
path: "/c/:canvas/full/:item",
|
|
@@ -17492,21 +18185,15 @@ function ScopedRoutes({ canvas }) {
|
|
|
17492
18185
|
}),
|
|
17493
18186
|
/* @__PURE__ */ jsx(Route, {
|
|
17494
18187
|
path: "/c/:canvas/play/:item",
|
|
17495
|
-
children: (params) => guard(params.canvas,
|
|
17496
|
-
canvasId: canvas,
|
|
17497
|
-
itemId: params.item
|
|
17498
|
-
}))
|
|
18188
|
+
children: (params) => guard(params.canvas, pages.play(canvas, params.item))
|
|
17499
18189
|
}),
|
|
17500
18190
|
/* @__PURE__ */ jsx(Route, {
|
|
17501
18191
|
path: "/c/:canvas/p/:page",
|
|
17502
|
-
children: (params) => guard(params.canvas,
|
|
17503
|
-
canvasId: canvas,
|
|
17504
|
-
pageId: params.page
|
|
17505
|
-
}))
|
|
18192
|
+
children: (params) => guard(params.canvas, pages.canvas(canvas, params.page))
|
|
17506
18193
|
}),
|
|
17507
18194
|
/* @__PURE__ */ jsx(Route, {
|
|
17508
18195
|
path: "/c/:canvas",
|
|
17509
|
-
children: (params) => guard(params.canvas,
|
|
18196
|
+
children: (params) => guard(params.canvas, pages.canvas(canvas))
|
|
17510
18197
|
}),
|
|
17511
18198
|
/* @__PURE__ */ jsx(Route, { children: /* @__PURE__ */ jsx(Redirect, {
|
|
17512
18199
|
to: home,
|
|
@@ -17808,4 +18495,4 @@ function TextArea({ label, hint, error, className, id, disabled, rows = 4, ...re
|
|
|
17808
18495
|
});
|
|
17809
18496
|
}
|
|
17810
18497
|
//#endregion
|
|
17811
|
-
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 };
|
|
18498
|
+
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 };
|