foss-design 0.3.1 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@ import { QueryClient, QueryClientProvider, useQuery } from "@tanstack/react-quer
2
2
  import { AnimatePresence, MotionConfig, motion } from "motion/react";
3
3
  import * as React from "react";
4
4
  import { Children, createContext, forwardRef, memo, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
5
- import { Link, Redirect, Route, Router, Switch as Switch$1, useLocation } from "wouter";
5
+ import { Link, Redirect, Route, Router, Switch as Switch$1, useLocation, useRouter } from "wouter";
6
6
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
7
  import * as ReactDOM from "react-dom";
8
8
  //#region \0rolldown/runtime.js
@@ -6507,11 +6507,11 @@ function Menu({ trigger, children, width = 220, align = "start", side = "bottom"
6507
6507
  });
6508
6508
  }
6509
6509
  var menuItemClass = (danger, active) => cn("flex min-h-9 w-full cursor-pointer items-center gap-2.5 border-0 px-3 py-1.5 text-left text-[13.5px] outline-none select-none", "data-[highlighted]:bg-soft data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50", active ? "bg-soft" : "bg-transparent", danger ? "text-danger-text" : "text-ink");
6510
- function MenuItem({ children, onSelect, danger, active, disabled, hint }) {
6510
+ function MenuItem({ children, onSelect, danger, active, disabled, hint, className }) {
6511
6511
  return /* @__PURE__ */ jsxs(Item2, {
6512
6512
  disabled,
6513
6513
  onSelect,
6514
- className: menuItemClass(danger, active),
6514
+ className: cn(menuItemClass(danger, active), className),
6515
6515
  children: [children, hint ? /* @__PURE__ */ jsx("span", {
6516
6516
  className: "ml-auto pl-3 text-[12px] text-muted",
6517
6517
  children: hint
@@ -6969,48 +6969,52 @@ function Toaster() {
6969
6969
  if (!shown) return null;
6970
6970
  return /* @__PURE__ */ jsx("div", {
6971
6971
  className: "pointer-events-none fixed bottom-5 left-1/2 z-[60] flex -translate-x-1/2 flex-col items-center gap-2",
6972
- children: /* @__PURE__ */ jsx(AnimatePresence, { children: list.map((item) => /* @__PURE__ */ jsxs(motion.div, {
6973
- initial: {
6974
- opacity: 0,
6975
- y: 8,
6976
- scale: .98
6977
- },
6978
- animate: {
6979
- opacity: 1,
6980
- y: 0,
6981
- scale: 1
6982
- },
6983
- exit: {
6984
- opacity: 0,
6985
- y: 4,
6986
- transition: { duration: .12 }
6987
- },
6988
- transition: {
6989
- type: "spring",
6990
- stiffness: 520,
6991
- damping: 40
6992
- },
6993
- className: "flex max-w-[420px] items-center gap-2.5 rounded-[8px] border border-rule bg-surface px-3 py-2 text-[13px] text-ink shadow-pop",
6994
- children: [
6995
- item.tone === "error" ? /* @__PURE__ */ jsx(Icon, {
6996
- name: "alert",
6997
- size: 15,
6998
- className: "text-danger-text"
6999
- }) : /* @__PURE__ */ jsx(Icon, {
7000
- name: "check",
7001
- size: 15,
7002
- className: "text-ok-text"
7003
- }),
7004
- /* @__PURE__ */ jsx("span", {
7005
- className: "font-medium",
7006
- children: item.title
7007
- }),
7008
- item.text ? /* @__PURE__ */ jsx("span", {
7009
- className: "truncate font-mono text-[12px] text-muted",
7010
- children: item.text
7011
- }) : null
7012
- ]
7013
- }, item.id)) })
6972
+ children: /* @__PURE__ */ jsx(AnimatePresence, {
6973
+ mode: "popLayout",
6974
+ children: list.map((item) => /* @__PURE__ */ jsxs(motion.div, {
6975
+ layout: true,
6976
+ initial: {
6977
+ opacity: 0,
6978
+ y: 8,
6979
+ scale: .98
6980
+ },
6981
+ animate: {
6982
+ opacity: 1,
6983
+ y: 0,
6984
+ scale: 1
6985
+ },
6986
+ exit: {
6987
+ opacity: 0,
6988
+ y: 4,
6989
+ transition: { duration: .12 }
6990
+ },
6991
+ transition: {
6992
+ type: "spring",
6993
+ stiffness: 520,
6994
+ damping: 40
6995
+ },
6996
+ className: "flex max-w-[min(420px,calc(100vw-32px))] min-w-0 items-center gap-2.5 rounded-[8px] border border-rule bg-surface px-3 py-2 text-[13px] text-ink shadow-pop",
6997
+ children: [
6998
+ item.tone === "error" ? /* @__PURE__ */ jsx(Icon, {
6999
+ name: "alert",
7000
+ size: 15,
7001
+ className: "text-danger-text"
7002
+ }) : /* @__PURE__ */ jsx(Icon, {
7003
+ name: "check",
7004
+ size: 15,
7005
+ className: "text-ok-text"
7006
+ }),
7007
+ /* @__PURE__ */ jsx("span", {
7008
+ className: "shrink-0 font-medium whitespace-nowrap",
7009
+ children: item.title
7010
+ }),
7011
+ item.text ? /* @__PURE__ */ jsx("span", {
7012
+ className: "min-w-0 truncate font-mono text-[12px] text-muted",
7013
+ children: item.text
7014
+ }) : null
7015
+ ]
7016
+ }, item.id))
7017
+ })
7014
7018
  });
7015
7019
  }
7016
7020
  //#endregion
@@ -7673,7 +7677,7 @@ var BADGE = {
7673
7677
  };
7674
7678
  function Badge({ tone = "neutral", children, className }) {
7675
7679
  return /* @__PURE__ */ jsx("span", {
7676
- className: cn("inline-flex h-[22px] shrink-0 items-center whitespace-nowrap rounded-[6px] px-2 text-[12px] font-medium", BADGE[tone], className),
7680
+ className: cn("inline-flex h-[22px] shrink-0 items-center gap-1 whitespace-nowrap rounded-[6px] px-2 text-[12px] font-medium", BADGE[tone], className),
7677
7681
  children
7678
7682
  });
7679
7683
  }
@@ -8729,6 +8733,94 @@ function Tooltip({ content, children, side = "top", align = "center", className
8729
8733
  }) })] });
8730
8734
  }
8731
8735
  //#endregion
8736
+ //#region viewer/src/pages/canvas/full-page.tsx
8737
+ /** The full-window page of one screen, relative to the viewer's base. */
8738
+ var fullHref = (canvas, item, theme) => `/c/${encodeURIComponent(canvas)}/full/${encodeURIComponent(item)}?theme=${theme}`;
8739
+ /**
8740
+ * "Open in a new tab" for a screen. A sandboxed viewer serves screens to its frames only, so the
8741
+ * tab gets the full-window page of the viewer; otherwise the screen's own URL.
8742
+ */
8743
+ function useOpenUrl(canvasId, item, theme) {
8744
+ const { frameSandbox } = useViewer();
8745
+ const { base } = useRouter();
8746
+ if (!item) return null;
8747
+ if (item.kind === "url") return item.url;
8748
+ if (!frameSandbox) return absoluteUrl(frameSrc(item.url, item.theme ?? theme));
8749
+ return absoluteUrl(`${base}${fullHref(canvasId, item.id, item.theme ?? theme)}`);
8750
+ }
8751
+ /** `/c/:canvas/full/:item`: one screen filling the window, a way out on hover and Esc. */
8752
+ function FullPage({ canvasId, itemId }) {
8753
+ const { data: canvas, error, isLoading } = useCanvas(canvasId);
8754
+ const [, navigate] = useLocation();
8755
+ const { frameSandbox } = useViewer();
8756
+ const appTheme = useTheme();
8757
+ const frame = useRef(null);
8758
+ const param = new URLSearchParams(window.location.search).get("theme");
8759
+ const items = canvas ? canvas.pages.flatMap((page) => page.sections.flatMap((section) => section.items.filter((item) => item.kind === "screen" || item.kind === "url"))) : [];
8760
+ const item = items.find((entry) => entry.id === itemId);
8761
+ const theme = (item?.kind === "screen" ? item.theme : void 0) ?? (param === "light" || param === "dark" ? param : canvas?.theme ?? appTheme);
8762
+ const url = item ? item.kind === "screen" ? frameSrc(item.url, theme) : item.url : "";
8763
+ const back = `/c/${encodeURIComponent(canvasId)}`;
8764
+ useEffect(() => {
8765
+ if (item) document.title = `${item.title} · ${canvas?.title ?? ""}`;
8766
+ }, [item, canvas?.title]);
8767
+ useEffect(() => {
8768
+ const onKey = (event) => {
8769
+ if (event.key === "Escape") navigate(back);
8770
+ };
8771
+ window.addEventListener("keydown", onKey);
8772
+ return () => window.removeEventListener("keydown", onKey);
8773
+ }, [navigate, back]);
8774
+ useEffect(() => {
8775
+ const el = frame.current;
8776
+ if (!el || item?.kind !== "screen") return;
8777
+ return listenToFrame(el, (message) => {
8778
+ if (message.type === "keydown" && message.code === "Escape") navigate(back);
8779
+ else if (message.type === "go") {
8780
+ const id = message.target.includes("/") ? message.target.split("/")[1] : message.target;
8781
+ if (items.some((entry) => entry.id === id)) navigate(fullHref(canvasId, id, theme), { replace: true });
8782
+ }
8783
+ });
8784
+ }, [url, item?.kind]);
8785
+ if (error) return /* @__PURE__ */ jsx(NotFound, {
8786
+ title: "Canvas not found",
8787
+ text: error.message
8788
+ });
8789
+ if (isLoading || !canvas) return /* @__PURE__ */ jsx("div", { className: "h-dvh bg-surface" });
8790
+ if (!item) return /* @__PURE__ */ jsx(NotFound, {
8791
+ title: "Screen not found",
8792
+ text: `No screen "${itemId}" on this canvas.`
8793
+ });
8794
+ return /* @__PURE__ */ jsxs("div", {
8795
+ className: "group fixed inset-0 bg-surface",
8796
+ children: [/* @__PURE__ */ jsx("iframe", {
8797
+ ref: frame,
8798
+ src: url,
8799
+ title: item.title,
8800
+ allow: "clipboard-read; clipboard-write; fullscreen",
8801
+ sandbox: frameSandbox,
8802
+ className: "block h-full w-full border-0",
8803
+ style: { colorScheme: theme },
8804
+ onLoad: () => item.kind === "screen" && sendTheme(frame.current, theme)
8805
+ }, `${item.id}:${item.kind === "screen" ? item.rev : item.url}`), /* @__PURE__ */ jsx("div", {
8806
+ className: "pointer-events-none absolute inset-x-0 top-0 flex justify-end p-3 opacity-0 transition-opacity duration-200 group-hover:opacity-100 focus-within:opacity-100",
8807
+ children: /* @__PURE__ */ jsxs(Link, {
8808
+ href: back,
8809
+ className: "pointer-events-auto flex h-8 items-center gap-2 rounded-[8px] border border-rule bg-surface px-3 text-[12.5px] text-ink2 no-underline shadow-pop hover:text-ink",
8810
+ children: [
8811
+ /* @__PURE__ */ jsx(Icon, {
8812
+ name: "x",
8813
+ size: 14
8814
+ }),
8815
+ "Back to ",
8816
+ canvas.title,
8817
+ /* @__PURE__ */ jsx(Kbd, { children: "esc" })
8818
+ ]
8819
+ })
8820
+ })]
8821
+ });
8822
+ }
8823
+ //#endregion
8732
8824
  //#region viewer/src/pages/canvas/sidebar.tsx
8733
8825
  function itemIcon(item) {
8734
8826
  if (item.kind === "note") return "note";
@@ -8931,11 +9023,10 @@ function SelectionBar({ canvas, store, layoutItems, theme, onPlay, inspect }) {
8931
9023
  const selected = useStore(store, (state) => state.selected);
8932
9024
  const active = useStore(store, (state) => state.active);
8933
9025
  const status = useStore(store, (state) => selected ? state.frames[selected] : void 0);
8934
- const { frameSandbox } = useViewer();
8935
9026
  const item = layoutItems.find((i) => i.id === selected);
9027
+ const openUrl = useOpenUrl(canvas.id, item?.kind === "screen" || item?.kind === "url" ? item : void 0, theme);
8936
9028
  if (!item) return null;
8937
9029
  const frame = item.kind === "screen" || item.kind === "url";
8938
- const openUrl = item.kind === "screen" && !frameSandbox ? absoluteUrl(frameSrc(item.url, item.theme ?? theme)) : item.kind === "url" ? item.url : null;
8939
9030
  const reload = () => reloadFrame(document.querySelector(`[data-item="${CSS.escape(item.id)}"] iframe`));
8940
9031
  const errors = status?.errors ?? [];
8941
9032
  return /* @__PURE__ */ jsx("div", {
@@ -12396,6 +12487,7 @@ function Player({ canvas, itemId }) {
12396
12487
  const entry = list[index];
12397
12488
  const item = entry?.item;
12398
12489
  const frameTheme = item?.kind === "screen" ? item.theme ?? theme : theme;
12490
+ const openUrl = useOpenUrl(canvas.id, item, frameTheme);
12399
12491
  const [openTheme] = useState(frameTheme);
12400
12492
  const src = item ? item.kind === "screen" ? frameSrc(item.url, openTheme) : item.url : "";
12401
12493
  const frames = useMemo(() => frameEl && item?.kind === "screen" ? [frameEl] : [], [frameEl, item?.kind]);
@@ -12438,7 +12530,6 @@ function Player({ canvas, itemId }) {
12438
12530
  const height = item.frame.height === "auto" ? Math.max(area.h - 48, 400) : item.frame.height;
12439
12531
  const scale = fit === "fit" ? Math.min(1, (area.w - 48) / width, (area.h - 48) / height) : 1;
12440
12532
  const fits = width <= area.w - 48 && height <= area.h - 48;
12441
- const openUrl = item.kind === "url" ? item.url : frameSandbox ? null : absoluteUrl(frameSrc(item.url, frameTheme));
12442
12533
  return /* @__PURE__ */ jsxs("div", {
12443
12534
  className: "flex h-dvh flex-col",
12444
12535
  children: [
@@ -12639,6 +12730,44 @@ function plural(count, one, many = `${one}s`) {
12639
12730
  return `${count} ${count === 1 ? one : many}`;
12640
12731
  }
12641
12732
  //#endregion
12733
+ //#region viewer/src/ui/skeleton.tsx
12734
+ /** A placeholder block that pulses while its content loads. */
12735
+ function Skeleton({ className, style }) {
12736
+ return /* @__PURE__ */ jsx("span", {
12737
+ "aria-hidden": "true",
12738
+ className: cn("block animate-pulse rounded-[6px] bg-soft2", className),
12739
+ style
12740
+ });
12741
+ }
12742
+ /**
12743
+ * Rows of a loading panel: a leading block, two lines of text and a trailing block, with the
12744
+ * panel's row borders, so the real rows replace them without a jump.
12745
+ */
12746
+ function SkeletonRows({ rows = 3, lead = 28, minH = 56 }) {
12747
+ return /* @__PURE__ */ jsx("div", {
12748
+ role: "status",
12749
+ "aria-label": "Loading",
12750
+ children: Array.from({ length: rows }, (_, index) => /* @__PURE__ */ jsxs("div", {
12751
+ className: "flex items-center gap-3 border-t border-rule px-4",
12752
+ style: {
12753
+ minHeight: minH,
12754
+ opacity: 1 - index * .18
12755
+ },
12756
+ children: [
12757
+ lead ? /* @__PURE__ */ jsx(Skeleton, { style: {
12758
+ width: lead,
12759
+ height: Math.min(lead, minH - 20)
12760
+ } }) : null,
12761
+ /* @__PURE__ */ jsxs("span", {
12762
+ className: "flex min-w-0 grow flex-col gap-1.5",
12763
+ children: [/* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-[38%]" }), /* @__PURE__ */ jsx(Skeleton, { className: "h-2.5 w-[62%]" })]
12764
+ }),
12765
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-14" })
12766
+ ]
12767
+ }, index))
12768
+ });
12769
+ }
12770
+ //#endregion
12642
12771
  //#region viewer/src/pages/home.tsx
12643
12772
  function Cover({ canvas }) {
12644
12773
  return /* @__PURE__ */ jsx("div", {
@@ -12666,68 +12795,84 @@ function WithActions({ actions, children }) {
12666
12795
  })]
12667
12796
  });
12668
12797
  }
12798
+ /** The embedding app's strip under the canvas list. */
12799
+ function Foot({ children }) {
12800
+ if (!children) return null;
12801
+ return /* @__PURE__ */ jsx("div", {
12802
+ className: "flex items-center gap-2.5 border-t border-rule bg-soft px-4 py-3 text-[12.5px] text-muted",
12803
+ children
12804
+ });
12805
+ }
12669
12806
  function CanvasList({ project }) {
12670
- const rowActions = useViewer().slots.canvasRowActions;
12671
- if (!project.canvases.length) return /* @__PURE__ */ jsxs(Panel, { children: [/* @__PURE__ */ jsx(PanelHead, {
12672
- title: "Canvases",
12673
- count: 0
12674
- }), /* @__PURE__ */ jsx(PanelBody, { children: /* @__PURE__ */ jsxs("div", {
12675
- className: "flex flex-col gap-2 py-6 text-center",
12676
- children: [/* @__PURE__ */ jsx("span", {
12677
- className: "text-[14px] text-ink2",
12678
- children: "No canvases yet."
12679
- }), /* @__PURE__ */ jsxs("span", {
12680
- className: "text-[13px] text-muted",
12681
- children: [
12682
- "Ask your agent to design something, or run ",
12683
- /* @__PURE__ */ jsx(Mono, { children: "design new <name>" }),
12684
- " in the project."
12685
- ]
12686
- })]
12687
- }) })] });
12688
- return /* @__PURE__ */ jsxs(Panel, { children: [/* @__PURE__ */ jsx(PanelHead, {
12689
- title: "Canvases",
12690
- count: project.canvases.length
12691
- }), project.canvases.map((canvas) => {
12692
- const actions = rowActions?.(canvas.id);
12693
- return /* @__PURE__ */ jsx(WithActions, {
12694
- actions,
12695
- children: /* @__PURE__ */ jsxs(RowLink, {
12696
- to: `/c/${encodeURIComponent(canvas.id)}`,
12697
- cols: "86px minmax(0,1fr) auto auto",
12698
- align: "llrr",
12699
- pad: "10px 16px",
12700
- minH: 74,
12701
- first: !!actions,
12807
+ const { canvasRowActions: rowActions, canvasesFoot } = useViewer().slots;
12808
+ if (!project.canvases.length) return /* @__PURE__ */ jsxs(Panel, { children: [
12809
+ /* @__PURE__ */ jsx(PanelHead, {
12810
+ title: "Canvases",
12811
+ count: 0
12812
+ }),
12813
+ /* @__PURE__ */ jsx(PanelBody, { children: /* @__PURE__ */ jsxs("div", {
12814
+ className: "flex flex-col gap-2 py-6 text-center",
12815
+ children: [/* @__PURE__ */ jsx("span", {
12816
+ className: "text-[14px] text-ink2",
12817
+ children: "No canvases yet."
12818
+ }), /* @__PURE__ */ jsxs("span", {
12819
+ className: "text-[13px] text-muted",
12702
12820
  children: [
12703
- /* @__PURE__ */ jsx(Cover, { canvas }),
12704
- /* @__PURE__ */ jsx(Two, {
12705
- top: canvas.title,
12706
- bottom: /* @__PURE__ */ jsx("span", {
12707
- className: "truncate",
12708
- children: canvas.description ?? canvas.id
12709
- })
12710
- }),
12711
- /* @__PURE__ */ jsxs("span", {
12712
- className: "flex items-center gap-2 text-[12.5px] whitespace-nowrap text-muted",
12713
- children: [
12714
- canvas.issues ? /* @__PURE__ */ jsx(Badge, {
12715
- tone: "danger",
12716
- children: plural(canvas.issues, "problem")
12717
- }) : null,
12718
- plural(canvas.screens, "screen"),
12719
- " · ",
12720
- plural(canvas.pages, "page")
12721
- ]
12722
- }),
12723
- /* @__PURE__ */ jsx("span", {
12724
- className: "w-[92px] text-[12.5px] whitespace-nowrap text-muted",
12725
- children: relativeTime(canvas.updatedAt)
12726
- })
12821
+ "Ask your agent to design something, or run ",
12822
+ /* @__PURE__ */ jsx(Mono, { children: "design new <name>" }),
12823
+ " in the project."
12727
12824
  ]
12728
- })
12729
- }, canvas.id);
12730
- })] });
12825
+ })]
12826
+ }) }),
12827
+ /* @__PURE__ */ jsx(Foot, { children: canvasesFoot })
12828
+ ] });
12829
+ return /* @__PURE__ */ jsxs(Panel, { children: [
12830
+ /* @__PURE__ */ jsx(PanelHead, {
12831
+ title: "Canvases",
12832
+ count: project.canvases.length
12833
+ }),
12834
+ project.canvases.map((canvas) => {
12835
+ const actions = rowActions?.(canvas.id);
12836
+ return /* @__PURE__ */ jsx(WithActions, {
12837
+ actions,
12838
+ children: /* @__PURE__ */ jsxs(RowLink, {
12839
+ to: `/c/${encodeURIComponent(canvas.id)}`,
12840
+ cols: "86px minmax(0,1fr) auto auto",
12841
+ align: "llrr",
12842
+ pad: "10px 16px",
12843
+ minH: 74,
12844
+ first: !!actions,
12845
+ children: [
12846
+ /* @__PURE__ */ jsx(Cover, { canvas }),
12847
+ /* @__PURE__ */ jsx(Two, {
12848
+ top: canvas.title,
12849
+ bottom: /* @__PURE__ */ jsx("span", {
12850
+ className: "truncate",
12851
+ children: canvas.description ?? canvas.id
12852
+ })
12853
+ }),
12854
+ /* @__PURE__ */ jsxs("span", {
12855
+ className: "flex items-center gap-2 text-[12.5px] whitespace-nowrap text-muted",
12856
+ children: [
12857
+ canvas.issues ? /* @__PURE__ */ jsx(Badge, {
12858
+ tone: "danger",
12859
+ children: plural(canvas.issues, "problem")
12860
+ }) : null,
12861
+ plural(canvas.screens, "screen"),
12862
+ " · ",
12863
+ plural(canvas.pages, "page")
12864
+ ]
12865
+ }),
12866
+ /* @__PURE__ */ jsx("span", {
12867
+ className: "w-[92px] text-[12.5px] whitespace-nowrap text-muted",
12868
+ children: relativeTime(canvas.updatedAt)
12869
+ })
12870
+ ]
12871
+ })
12872
+ }, canvas.id);
12873
+ }),
12874
+ /* @__PURE__ */ jsx(Foot, { children: canvasesFoot })
12875
+ ] });
12731
12876
  }
12732
12877
  function SystemCard({ project }) {
12733
12878
  const system = project.system;
@@ -12760,21 +12905,25 @@ function SystemCard({ project }) {
12760
12905
  /* @__PURE__ */ jsx(KV, {
12761
12906
  label: "Tokens",
12762
12907
  labelWidth: 120,
12908
+ end: true,
12763
12909
  children: /* @__PURE__ */ jsx(Count, { value: system.tokens })
12764
12910
  }),
12765
12911
  /* @__PURE__ */ jsx(KV, {
12766
12912
  label: "Components",
12767
12913
  labelWidth: 120,
12914
+ end: true,
12768
12915
  children: /* @__PURE__ */ jsx(Count, { value: system.components })
12769
12916
  }),
12770
12917
  /* @__PURE__ */ jsx(KV, {
12771
12918
  label: "Guidelines",
12772
12919
  labelWidth: 120,
12920
+ end: true,
12773
12921
  children: /* @__PURE__ */ jsx(Count, { value: system.guidelines })
12774
12922
  }),
12775
12923
  system.issues ? /* @__PURE__ */ jsx(KV, {
12776
12924
  label: "Problems",
12777
12925
  labelWidth: 120,
12926
+ end: true,
12778
12927
  children: /* @__PURE__ */ jsx(Badge, {
12779
12928
  tone: "danger",
12780
12929
  children: system.issues
@@ -12782,6 +12931,25 @@ function SystemCard({ project }) {
12782
12931
  }) : null
12783
12932
  ] });
12784
12933
  }
12934
+ /** The home page's shape while the project loads, so it fills in instead of popping in. */
12935
+ function HomeSkeleton() {
12936
+ return /* @__PURE__ */ jsxs(Content, { children: [/* @__PURE__ */ jsxs("div", {
12937
+ className: "flex flex-col gap-2.5 pt-1 pb-1",
12938
+ children: [/* @__PURE__ */ jsx(Skeleton, { className: "h-6 w-[220px]" }), /* @__PURE__ */ jsx(Skeleton, { className: "h-3.5 w-[300px]" })]
12939
+ }), /* @__PURE__ */ jsxs("div", {
12940
+ className: "grid items-start gap-5 max-lg:grid-cols-1!",
12941
+ style: { gridTemplateColumns: "minmax(0,1fr) 340px" },
12942
+ children: [/* @__PURE__ */ jsxs(Panel, { children: [/* @__PURE__ */ jsx(PanelHead, { title: "Canvases" }), /* @__PURE__ */ jsx(SkeletonRows, {
12943
+ rows: 3,
12944
+ lead: 86,
12945
+ minH: 74
12946
+ })] }), /* @__PURE__ */ jsxs(Panel, { children: [/* @__PURE__ */ jsx(PanelHead, { title: "Design system" }), /* @__PURE__ */ jsx(SkeletonRows, {
12947
+ rows: 3,
12948
+ lead: 0,
12949
+ minH: 40
12950
+ })] })]
12951
+ })] });
12952
+ }
12785
12953
  function HomePage() {
12786
12954
  const { data: project, error, isLoading } = useProject();
12787
12955
  const { slots } = useViewer();
@@ -12789,12 +12957,12 @@ function HomePage() {
12789
12957
  title: "Could not load the project",
12790
12958
  text: error.message
12791
12959
  });
12792
- if (isLoading || !project) return null;
12960
+ if (isLoading || !project) return /* @__PURE__ */ jsx(HomeSkeleton, {});
12793
12961
  return /* @__PURE__ */ jsxs(Content, { children: [
12794
12962
  /* @__PURE__ */ jsx(PageHead, {
12795
12963
  title: project.name,
12796
12964
  right: slots.homeActions,
12797
- sub: project.static ? `Snapshot built with foss-design ${project.version}` : /* @__PURE__ */ jsxs(Fragment, { children: [
12965
+ sub: slots.homeSub ? slots.homeSub : project.static ? `Snapshot built with foss-design ${project.version}` : /* @__PURE__ */ jsxs(Fragment, { children: [
12798
12966
  /* @__PURE__ */ jsxs(Mono, {
12799
12967
  size: 12.5,
12800
12968
  children: [project.root, "/.design"]
@@ -17233,6 +17401,13 @@ function Title() {
17233
17401
  }
17234
17402
  function Routes() {
17235
17403
  return /* @__PURE__ */ jsxs(Switch$1, { children: [
17404
+ /* @__PURE__ */ jsx(Route, {
17405
+ path: "/c/:canvas/full/:item",
17406
+ children: (params) => /* @__PURE__ */ jsx(FullPage, {
17407
+ canvasId: params.canvas,
17408
+ itemId: params.item
17409
+ })
17410
+ }),
17236
17411
  /* @__PURE__ */ jsx(Route, {
17237
17412
  path: "/c/:canvas/play/:item",
17238
17413
  children: (params) => /* @__PURE__ */ jsx(PlayPage, {
@@ -17270,6 +17445,13 @@ function ScopedRoutes({ canvas }) {
17270
17445
  replace: true
17271
17446
  });
17272
17447
  return /* @__PURE__ */ jsxs(Switch$1, { children: [
17448
+ /* @__PURE__ */ jsx(Route, {
17449
+ path: "/c/:canvas/full/:item",
17450
+ children: (params) => guard(params.canvas, /* @__PURE__ */ jsx(FullPage, {
17451
+ canvasId: canvas,
17452
+ itemId: params.item
17453
+ }))
17454
+ }),
17273
17455
  /* @__PURE__ */ jsx(Route, {
17274
17456
  path: "/c/:canvas/play/:item",
17275
17457
  children: (params) => guard(params.canvas, /* @__PURE__ */ jsx(PlayPage, {
@@ -17387,7 +17569,7 @@ function Avatar({ src, name, size = 28, shape = "circle", className }) {
17387
17569
  * A modal over a dimmed page: focus stays inside until it closes, Escape and a
17388
17570
  * click outside close it. Head and foot are a panel's; `actions` go to the foot.
17389
17571
  */
17390
- function Dialog({ open, onOpenChange, title, description, children, actions, footNote, width = 440 }) {
17572
+ function Dialog({ open, onOpenChange, title, description, children, actions, footNote, below, width = 440 }) {
17391
17573
  return /* @__PURE__ */ jsx(Dialog$1, {
17392
17574
  open,
17393
17575
  onOpenChange,
@@ -17405,6 +17587,11 @@ function Dialog({ open, onOpenChange, title, description, children, actions, foo
17405
17587
  children: /* @__PURE__ */ jsx(DialogContent, {
17406
17588
  asChild: true,
17407
17589
  forceMount: true,
17590
+ onOpenAutoFocus: (event) => {
17591
+ event.preventDefault();
17592
+ const content = event.currentTarget;
17593
+ (content.querySelector("input:not([type=\"hidden\"]), textarea, [data-autofocus]") ?? content).focus({ preventScroll: true });
17594
+ },
17408
17595
  ...description ? {} : { "aria-describedby": void 0 },
17409
17596
  children: /* @__PURE__ */ jsxs(motion.div, {
17410
17597
  initial: {
@@ -17427,7 +17614,8 @@ function Dialog({ open, onOpenChange, title, description, children, actions, foo
17427
17614
  stiffness: 560,
17428
17615
  damping: 40
17429
17616
  },
17430
- className: cn(floatingSurface, "flex max-w-full flex-col"),
17617
+ tabIndex: -1,
17618
+ className: cn(floatingSurface, "flex max-w-full flex-col outline-none"),
17431
17619
  style: { width },
17432
17620
  children: [
17433
17621
  /* @__PURE__ */ jsx(PanelHead, {
@@ -17450,6 +17638,10 @@ function Dialog({ open, onOpenChange, title, description, children, actions, foo
17450
17638
  className: "m-0 text-[13.5px] text-ink2",
17451
17639
  children: description
17452
17640
  }) : null, children] }) : null,
17641
+ below ? /* @__PURE__ */ jsx("div", {
17642
+ className: "max-h-[min(56vh,520px)] min-h-0 overflow-y-auto border-t border-rule",
17643
+ children: below
17644
+ }) : null,
17453
17645
  actions || footNote ? /* @__PURE__ */ jsx(PanelFoot, {
17454
17646
  left: footNote,
17455
17647
  right: actions
@@ -17578,4 +17770,4 @@ function TextArea({ label, hint, error, className, id, disabled, rows = 4, ...re
17578
17770
  });
17579
17771
  }
17580
17772
  //#endregion
17581
- 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, 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 };
17773
+ export { ApiError, Avatar, Badge, Bar, Button, ButtonAnchor, ButtonLink, Content, Count, DesignViewer, Dialog, GroupRow, Icon, IconButton, KV, Kbd, Mark, Menu, MenuItem, MenuLabel, MenuSeparator, Meter, Mono, Muted, NavLabel, Notice, NoticeCard, PageHead, Panel, PanelBody, PanelFoot, PanelHead, PanelTabs, Row, RowLink, Segmented, Skeleton, SkeletonRows, SlidePanel, Strip, SubNav, Switch, THead, TextArea, TextField, ThemeMenu, Toaster, Tooltip, TooltipProvider, Two, Wordmark, buttonClass, cn, copyText, floatingSurface, iconButtonClass, initials, localSource, menuItemClass, menuSurface, resolveUrl, setThemePref, staticSource, toast, useTheme, useThemePref };