foss-design 0.3.6 → 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/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 +734 -58
- 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/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;
|
|
@@ -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
|
|
@@ -7836,7 +7873,7 @@ function KV({ label, children, labelWidth = 180, minH = 40, end = false }) {
|
|
|
7836
7873
|
return /* @__PURE__ */ jsxs("div", {
|
|
7837
7874
|
className: "grid content-center items-baseline gap-x-4 border-t border-rule px-4 py-2",
|
|
7838
7875
|
style: {
|
|
7839
|
-
gridTemplateColumns:
|
|
7876
|
+
gridTemplateColumns: `min(${labelWidth}px, 42%) minmax(0, 1fr)`,
|
|
7840
7877
|
minHeight: minH
|
|
7841
7878
|
},
|
|
7842
7879
|
children: [/* @__PURE__ */ jsx("span", {
|
|
@@ -7996,7 +8033,7 @@ function PageHead({ title, sub, right, badge, crumbs }) {
|
|
|
7996
8033
|
}), badge]
|
|
7997
8034
|
}),
|
|
7998
8035
|
sub ? /* @__PURE__ */ jsx("div", {
|
|
7999
|
-
className: "text-[13px] text-muted",
|
|
8036
|
+
className: "text-[13px] [overflow-wrap:anywhere] text-muted",
|
|
8000
8037
|
children: sub
|
|
8001
8038
|
}) : null
|
|
8002
8039
|
]
|
|
@@ -11949,7 +11986,7 @@ function isTyping(target) {
|
|
|
11949
11986
|
//#endregion
|
|
11950
11987
|
//#region viewer/src/pages/canvas/canvas-page.tsx
|
|
11951
11988
|
var query = new URLSearchParams(window.location.search);
|
|
11952
|
-
var CAPTURE = query.has("capture");
|
|
11989
|
+
var CAPTURE$1 = query.has("capture");
|
|
11953
11990
|
var cameraKey = (canvas, page) => `foss-design.camera.${canvas}.${page}`;
|
|
11954
11991
|
function loadCamera(key) {
|
|
11955
11992
|
try {
|
|
@@ -11985,7 +12022,7 @@ function CanvasView({ canvas, page }) {
|
|
|
11985
12022
|
const camera = useMemo(() => new CameraStore(), []);
|
|
11986
12023
|
const apiRef = useRef(null);
|
|
11987
12024
|
const [theme, setTheme] = useScreenTheme(canvas);
|
|
11988
|
-
const [sidebar, setSidebar] = useState(() => !CAPTURE && window.innerWidth > 900);
|
|
12025
|
+
const [sidebar, setSidebar] = useState(() => !CAPTURE$1 && window.innerWidth > 900);
|
|
11989
12026
|
const [inspectOn, setInspect] = useState(false);
|
|
11990
12027
|
const sizes = useStore(store, (state) => state.sizes);
|
|
11991
12028
|
const layout = useMemo(() => page ? layoutPage(page, sizes) : {
|
|
@@ -12029,7 +12066,7 @@ function CanvasView({ canvas, page }) {
|
|
|
12029
12066
|
if (placed.current || !layout.bounds.w) return;
|
|
12030
12067
|
placed.current = true;
|
|
12031
12068
|
const focus = takeFocus(canvas.id);
|
|
12032
|
-
const saved = CAPTURE ? null : loadCamera(key);
|
|
12069
|
+
const saved = CAPTURE$1 ? null : loadCamera(key);
|
|
12033
12070
|
if (focus) {
|
|
12034
12071
|
store.set((state) => ({
|
|
12035
12072
|
...state,
|
|
@@ -12037,7 +12074,7 @@ function CanvasView({ canvas, page }) {
|
|
|
12037
12074
|
}));
|
|
12038
12075
|
requestAnimationFrame(() => apiRef.current?.fitItem(focus, false));
|
|
12039
12076
|
} else if (saved && saved.z >= camera.minZoom) camera.set(saved);
|
|
12040
|
-
else requestAnimationFrame(() => CAPTURE ? apiRef.current?.fitAll(false) : apiRef.current?.openView());
|
|
12077
|
+
else requestAnimationFrame(() => CAPTURE$1 ? apiRef.current?.fitAll(false) : apiRef.current?.openView());
|
|
12041
12078
|
}, [
|
|
12042
12079
|
layout,
|
|
12043
12080
|
camera,
|
|
@@ -12046,7 +12083,7 @@ function CanvasView({ canvas, page }) {
|
|
|
12046
12083
|
store
|
|
12047
12084
|
]);
|
|
12048
12085
|
useEffect(() => {
|
|
12049
|
-
if (CAPTURE) return;
|
|
12086
|
+
if (CAPTURE$1) return;
|
|
12050
12087
|
let timer;
|
|
12051
12088
|
const off = camera.subscribe((c) => {
|
|
12052
12089
|
clearTimeout(timer);
|
|
@@ -12139,7 +12176,7 @@ function CanvasView({ canvas, page }) {
|
|
|
12139
12176
|
navigate
|
|
12140
12177
|
]);
|
|
12141
12178
|
useEffect(() => {
|
|
12142
|
-
if (!CAPTURE) return;
|
|
12179
|
+
if (!CAPTURE$1) return;
|
|
12143
12180
|
const started = Date.now();
|
|
12144
12181
|
const timer = setInterval(() => {
|
|
12145
12182
|
const frames = store.get().frames;
|
|
@@ -12222,7 +12259,7 @@ function CanvasView({ canvas, page }) {
|
|
|
12222
12259
|
inspectOn,
|
|
12223
12260
|
inspection
|
|
12224
12261
|
]);
|
|
12225
|
-
if (CAPTURE) return /* @__PURE__ */ jsx("div", {
|
|
12262
|
+
if (CAPTURE$1) return /* @__PURE__ */ jsx("div", {
|
|
12226
12263
|
className: "flex h-dvh",
|
|
12227
12264
|
children: /* @__PURE__ */ jsx(Viewport, {
|
|
12228
12265
|
layout,
|
|
@@ -12438,6 +12475,79 @@ function useScreenFrames(frameEls, layout) {
|
|
|
12438
12475
|
}, [frameEls, layout]);
|
|
12439
12476
|
}
|
|
12440
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
|
|
12441
12551
|
//#region viewer/src/pages/canvas/play-page.tsx
|
|
12442
12552
|
function framesOf(canvas, pageId) {
|
|
12443
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) => ({
|
|
@@ -12743,6 +12853,441 @@ function Player({ canvas, itemId }) {
|
|
|
12743
12853
|
});
|
|
12744
12854
|
}
|
|
12745
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
|
|
12746
13291
|
//#region viewer/src/lib/format.ts
|
|
12747
13292
|
var units = [
|
|
12748
13293
|
["year", 31536e3],
|
|
@@ -12843,6 +13388,7 @@ function Foot({ children }) {
|
|
|
12843
13388
|
}
|
|
12844
13389
|
function CanvasList({ project }) {
|
|
12845
13390
|
const { canvasRowActions: rowActions, canvasesFoot } = useViewer().slots;
|
|
13391
|
+
const phone = useIsPhone();
|
|
12846
13392
|
if (!project.canvases.length) return /* @__PURE__ */ jsxs(Panel, { children: [
|
|
12847
13393
|
/* @__PURE__ */ jsx(PanelHead, {
|
|
12848
13394
|
title: "Canvases",
|
|
@@ -12873,7 +13419,31 @@ function CanvasList({ project }) {
|
|
|
12873
13419
|
const actions = rowActions?.(canvas.id);
|
|
12874
13420
|
return /* @__PURE__ */ jsx(WithActions, {
|
|
12875
13421
|
actions,
|
|
12876
|
-
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, {
|
|
12877
13447
|
to: `/c/${encodeURIComponent(canvas.id)}`,
|
|
12878
13448
|
cols: "86px minmax(0,1fr) auto auto",
|
|
12879
13449
|
align: "llrr",
|
|
@@ -13003,9 +13573,11 @@ function HomePage() {
|
|
|
13003
13573
|
sub: slots.homeSub ? slots.homeSub : project.static ? `Snapshot built with foss-design ${project.version}` : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
13004
13574
|
/* @__PURE__ */ jsxs(Mono, {
|
|
13005
13575
|
size: 12.5,
|
|
13576
|
+
wrap: true,
|
|
13006
13577
|
children: [project.root, "/.design"]
|
|
13007
13578
|
}),
|
|
13008
|
-
"
|
|
13579
|
+
" ",
|
|
13580
|
+
"· foss-design ",
|
|
13009
13581
|
project.version
|
|
13010
13582
|
] })
|
|
13011
13583
|
}),
|
|
@@ -16666,6 +17238,7 @@ function MotionPage({ system }) {
|
|
|
16666
17238
|
//#region viewer/src/pages/system/nav.tsx
|
|
16667
17239
|
/** Grouped links of the system section; a wrapping row above the content on narrow screens. */
|
|
16668
17240
|
function SystemNav({ system }) {
|
|
17241
|
+
const phone = useIsPhone();
|
|
16669
17242
|
const kinds = new Set(system.tokens.map((token) => token.kind));
|
|
16670
17243
|
const foundations = [
|
|
16671
17244
|
{
|
|
@@ -16707,6 +17280,33 @@ function SystemNav({ system }) {
|
|
|
16707
17280
|
groups.set(component.group, list);
|
|
16708
17281
|
}
|
|
16709
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
|
+
}
|
|
16710
17310
|
return /* @__PURE__ */ jsxs("div", {
|
|
16711
17311
|
className: "flex flex-col max-lg:gap-1 max-lg:[&_nav]:flex-row max-lg:[&_nav]:flex-wrap",
|
|
16712
17312
|
children: [
|
|
@@ -16742,6 +17342,70 @@ function SystemNav({ system }) {
|
|
|
16742
17342
|
function count(system, kind) {
|
|
16743
17343
|
return system.tokens.filter((token) => token.kind === kind).length || void 0;
|
|
16744
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
|
+
}
|
|
16745
17409
|
//#endregion
|
|
16746
17410
|
//#region viewer/src/pages/system/other-tokens.tsx
|
|
16747
17411
|
function OtherTokensPage({ system }) {
|
|
@@ -17437,7 +18101,30 @@ function Title() {
|
|
|
17437
18101
|
}, [name]);
|
|
17438
18102
|
return null;
|
|
17439
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
|
+
}
|
|
17440
18126
|
function Routes() {
|
|
18127
|
+
const pages = useCanvasPages();
|
|
17441
18128
|
return /* @__PURE__ */ jsxs(Switch$1, { children: [
|
|
17442
18129
|
/* @__PURE__ */ jsx(Route, {
|
|
17443
18130
|
path: "/c/:canvas/full/:item",
|
|
@@ -17448,21 +18135,15 @@ function Routes() {
|
|
|
17448
18135
|
}),
|
|
17449
18136
|
/* @__PURE__ */ jsx(Route, {
|
|
17450
18137
|
path: "/c/:canvas/play/:item",
|
|
17451
|
-
children: (params) =>
|
|
17452
|
-
canvasId: params.canvas,
|
|
17453
|
-
itemId: params.item
|
|
17454
|
-
})
|
|
18138
|
+
children: (params) => pages.play(params.canvas, params.item)
|
|
17455
18139
|
}),
|
|
17456
18140
|
/* @__PURE__ */ jsx(Route, {
|
|
17457
18141
|
path: "/c/:canvas/p/:page",
|
|
17458
|
-
children: (params) =>
|
|
17459
|
-
canvasId: params.canvas,
|
|
17460
|
-
pageId: params.page
|
|
17461
|
-
})
|
|
18142
|
+
children: (params) => pages.canvas(params.canvas, params.page)
|
|
17462
18143
|
}),
|
|
17463
18144
|
/* @__PURE__ */ jsx(Route, {
|
|
17464
18145
|
path: "/c/:canvas",
|
|
17465
|
-
children: (params) =>
|
|
18146
|
+
children: (params) => pages.canvas(params.canvas)
|
|
17466
18147
|
}),
|
|
17467
18148
|
/* @__PURE__ */ jsxs(Route, {
|
|
17468
18149
|
path: "/system/*?",
|
|
@@ -17482,6 +18163,7 @@ function ScopedRoutes({ canvas }) {
|
|
|
17482
18163
|
to: home,
|
|
17483
18164
|
replace: true
|
|
17484
18165
|
});
|
|
18166
|
+
const pages = useCanvasPages();
|
|
17485
18167
|
return /* @__PURE__ */ jsxs(Switch$1, { children: [
|
|
17486
18168
|
/* @__PURE__ */ jsx(Route, {
|
|
17487
18169
|
path: "/c/:canvas/full/:item",
|
|
@@ -17492,21 +18174,15 @@ function ScopedRoutes({ canvas }) {
|
|
|
17492
18174
|
}),
|
|
17493
18175
|
/* @__PURE__ */ jsx(Route, {
|
|
17494
18176
|
path: "/c/:canvas/play/:item",
|
|
17495
|
-
children: (params) => guard(params.canvas,
|
|
17496
|
-
canvasId: canvas,
|
|
17497
|
-
itemId: params.item
|
|
17498
|
-
}))
|
|
18177
|
+
children: (params) => guard(params.canvas, pages.play(canvas, params.item))
|
|
17499
18178
|
}),
|
|
17500
18179
|
/* @__PURE__ */ jsx(Route, {
|
|
17501
18180
|
path: "/c/:canvas/p/:page",
|
|
17502
|
-
children: (params) => guard(params.canvas,
|
|
17503
|
-
canvasId: canvas,
|
|
17504
|
-
pageId: params.page
|
|
17505
|
-
}))
|
|
18181
|
+
children: (params) => guard(params.canvas, pages.canvas(canvas, params.page))
|
|
17506
18182
|
}),
|
|
17507
18183
|
/* @__PURE__ */ jsx(Route, {
|
|
17508
18184
|
path: "/c/:canvas",
|
|
17509
|
-
children: (params) => guard(params.canvas,
|
|
18185
|
+
children: (params) => guard(params.canvas, pages.canvas(canvas))
|
|
17510
18186
|
}),
|
|
17511
18187
|
/* @__PURE__ */ jsx(Route, { children: /* @__PURE__ */ jsx(Redirect, {
|
|
17512
18188
|
to: home,
|
|
@@ -17808,4 +18484,4 @@ function TextArea({ label, hint, error, className, id, disabled, rows = 4, ...re
|
|
|
17808
18484
|
});
|
|
17809
18485
|
}
|
|
17810
18486
|
//#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 };
|
|
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 };
|