@dreamlake/dockit 0.1.1 → 0.2.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.
@@ -60,22 +60,36 @@ function CodeBlock({ children, filename, lang }) {
60
60
  },
61
61
  children: [
62
62
  /* @__PURE__ */ jsx("span", { style: { fontWeight: 600, textTransform: "none", letterSpacing: 0 }, children: filename }),
63
- lang && /* @__PURE__ */ jsx("span", { className: "uppercase", style: { fontWeight: 600, letterSpacing: "0.06em" }, children: lang }),
63
+ lang && /* @__PURE__ */ jsx(
64
+ "span",
65
+ {
66
+ className: "uppercase opacity-0 group-hover:opacity-100 focus-within:opacity-100 transition-opacity duration-150",
67
+ style: { fontWeight: 600, letterSpacing: "0.06em" },
68
+ children: lang
69
+ }
70
+ ),
64
71
  /* @__PURE__ */ jsx("span", { style: { flex: 1 } }),
65
- /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center", style: { gap: 6 }, children: [
66
- /* @__PURE__ */ jsx(
67
- "button",
68
- {
69
- type: "button",
70
- "aria-pressed": showLines,
71
- title: showLines ? ":set nonu" : ":set nu",
72
- onClick: () => setShowLines(!showLines),
73
- style: headerBtnStyle(showLines),
74
- children: /* @__PURE__ */ jsx(HashIcon, {})
75
- }
76
- ),
77
- /* @__PURE__ */ jsx("button", { type: "button", onClick: copy, style: headerBtnStyle(copied), children: copied ? "Copied" : "Copy" })
78
- ] })
72
+ /* @__PURE__ */ jsxs(
73
+ "span",
74
+ {
75
+ className: "inline-flex items-center opacity-0 group-hover:opacity-100 focus-within:opacity-100 transition-opacity duration-150",
76
+ style: { gap: 6 },
77
+ children: [
78
+ /* @__PURE__ */ jsx(
79
+ "button",
80
+ {
81
+ type: "button",
82
+ "aria-pressed": showLines,
83
+ title: showLines ? ":set nonu" : ":set nu",
84
+ onClick: () => setShowLines(!showLines),
85
+ style: headerBtnStyle(showLines),
86
+ children: /* @__PURE__ */ jsx(HashIcon, {})
87
+ }
88
+ ),
89
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: copy, style: headerBtnStyle(copied), children: copied ? "Copied" : "Copy" })
90
+ ]
91
+ }
92
+ )
79
93
  ]
80
94
  }
81
95
  ),
@@ -112,6 +112,48 @@ function HashIcon() {
112
112
  }
113
113
  );
114
114
  }
115
+ function MaximizeIcon() {
116
+ return /* @__PURE__ */ jsxs(
117
+ "svg",
118
+ {
119
+ viewBox: "0 0 24 24",
120
+ fill: "none",
121
+ stroke: "currentColor",
122
+ strokeWidth: "2",
123
+ strokeLinecap: "round",
124
+ strokeLinejoin: "round",
125
+ "aria-hidden": true,
126
+ style: { width: 12, height: 12, display: "block" },
127
+ children: [
128
+ /* @__PURE__ */ jsx("polyline", { points: "15 3 21 3 21 9" }),
129
+ /* @__PURE__ */ jsx("polyline", { points: "9 21 3 21 3 15" }),
130
+ /* @__PURE__ */ jsx("line", { x1: "21", y1: "3", x2: "14", y2: "10" }),
131
+ /* @__PURE__ */ jsx("line", { x1: "3", y1: "21", x2: "10", y2: "14" })
132
+ ]
133
+ }
134
+ );
135
+ }
136
+ function MinimizeIcon() {
137
+ return /* @__PURE__ */ jsxs(
138
+ "svg",
139
+ {
140
+ viewBox: "0 0 24 24",
141
+ fill: "none",
142
+ stroke: "currentColor",
143
+ strokeWidth: "2",
144
+ strokeLinecap: "round",
145
+ strokeLinejoin: "round",
146
+ "aria-hidden": true,
147
+ style: { width: 12, height: 12, display: "block" },
148
+ children: [
149
+ /* @__PURE__ */ jsx("polyline", { points: "4 14 10 14 10 20" }),
150
+ /* @__PURE__ */ jsx("polyline", { points: "20 10 14 10 14 4" }),
151
+ /* @__PURE__ */ jsx("line", { x1: "14", y1: "10", x2: "21", y2: "3" }),
152
+ /* @__PURE__ */ jsx("line", { x1: "3", y1: "21", x2: "10", y2: "14" })
153
+ ]
154
+ }
155
+ );
156
+ }
115
157
  function Preview({
116
158
  children,
117
159
  source,
@@ -122,8 +164,17 @@ function Preview({
122
164
  }) {
123
165
  const [tab, setTab] = useState(defaultTab);
124
166
  const [copied, setCopied] = useState(false);
167
+ const [fullscreen, setFullscreen] = useState(false);
125
168
  const [showLines, setShowLines] = useLineNumbers();
126
169
  const visibleSource = tab === "data" ? dataSource ?? "" : source;
170
+ useEffect(() => {
171
+ if (!fullscreen) return;
172
+ function onKey(e) {
173
+ if (e.key === "Escape") setFullscreen(false);
174
+ }
175
+ window.addEventListener("keydown", onKey);
176
+ return () => window.removeEventListener("keydown", onKey);
177
+ }, [fullscreen]);
127
178
  function copy() {
128
179
  navigator.clipboard.writeText(visibleSource).then(
129
180
  () => {
@@ -139,7 +190,20 @@ function Preview({
139
190
  {
140
191
  "data-line-numbers": showLines ? "on" : "off",
141
192
  className: "border border-doc-template-faint bg-doc-template-panel",
142
- style: { margin: "18px 0 22px", borderRadius: "var(--radius-doc-template)", overflow: "hidden" },
193
+ style: fullscreen ? {
194
+ // Full-viewport overlay. `--color-doc-template-bg` is the page
195
+ // surface token so the backdrop matches the site in both light
196
+ // and dark themes.
197
+ position: "fixed",
198
+ inset: 0,
199
+ zIndex: 1e3,
200
+ margin: 0,
201
+ borderRadius: 0,
202
+ background: "var(--color-doc-template-bg)",
203
+ display: "flex",
204
+ flexDirection: "column",
205
+ overflow: "hidden"
206
+ } : { margin: "18px 0 22px", borderRadius: "var(--radius-doc-template)", overflow: "hidden" },
143
207
  children: [
144
208
  /* @__PURE__ */ jsxs(
145
209
  "div",
@@ -206,11 +270,54 @@ function Preview({
206
270
  children: copied ? "Copied" : "Copy"
207
271
  }
208
272
  )
209
- ] })
273
+ ] }),
274
+ tab === "preview" && /* @__PURE__ */ jsx("span", { style: { marginLeft: "auto" } }),
275
+ /* @__PURE__ */ jsx(
276
+ "button",
277
+ {
278
+ type: "button",
279
+ "aria-pressed": fullscreen,
280
+ "aria-label": fullscreen ? "Exit fullscreen" : "Enter fullscreen",
281
+ title: fullscreen ? "Exit fullscreen (Esc)" : "Enter fullscreen",
282
+ onClick: () => setFullscreen((f) => !f),
283
+ style: headerBtnStyle(fullscreen),
284
+ children: fullscreen ? /* @__PURE__ */ jsx(MinimizeIcon, {}) : /* @__PURE__ */ jsx(MaximizeIcon, {})
285
+ }
286
+ )
210
287
  ]
211
288
  }
212
289
  ),
213
- tab === "preview" ? /* @__PURE__ */ jsx("div", { style: { padding: 24, minHeight: 80, ...height ? { height } : null }, children }) : /* @__PURE__ */ jsx("div", { style: { background: "var(--color-doc-template-code)" }, children: /* @__PURE__ */ jsx(HighlightedSource, { code: visibleSource, filename }) })
290
+ tab === "preview" ? /* @__PURE__ */ jsxs(
291
+ "div",
292
+ {
293
+ ...fullscreen ? { "data-preview-fill": "" } : null,
294
+ style: fullscreen ? {
295
+ // Full-bleed: drop the padding so a wide demo reaches every
296
+ // edge of the overlay. The `[data-preview-fill]` rule below
297
+ // then stretches the demo's own wrapper to this pane's
298
+ // height, so a specimen with a fixed height grows to fill
299
+ // the viewport instead of leaving dead space below it.
300
+ padding: 0,
301
+ flex: 1,
302
+ minHeight: 0,
303
+ width: "100%",
304
+ overflow: "auto"
305
+ } : { padding: 24, minHeight: 80, ...height ? { height } : null },
306
+ children: [
307
+ fullscreen && /* @__PURE__ */ jsx("style", { children: "[data-preview-fill]>:not(style){height:100%!important}" }),
308
+ children
309
+ ]
310
+ }
311
+ ) : /* @__PURE__ */ jsx(
312
+ "div",
313
+ {
314
+ style: {
315
+ background: "var(--color-doc-template-code)",
316
+ ...fullscreen ? { flex: 1, minHeight: 0, overflow: "auto" } : null
317
+ },
318
+ children: /* @__PURE__ */ jsx(HighlightedSource, { code: visibleSource, filename })
319
+ }
320
+ )
214
321
  ]
215
322
  }
216
323
  );
@@ -1,7 +1,7 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { useMemo, useState, useEffect } from "react";
3
3
  import { usePageContext } from "vike-react/usePageContext";
4
- import { groupedPages, groupedVisiblePages } from "../lib/navigation.js";
4
+ import { normalizePath, groupedPages, groupedVisiblePages } from "../lib/navigation.js";
5
5
  import { useHiddenToggle } from "../lib/use-hidden-toggle.js";
6
6
  import { tabForUrl, urlInTab } from "../lib/tabs.js";
7
7
  const SIDEBAR_KEY = "sidebar-collapsed";
@@ -15,6 +15,7 @@ function loadCollapsed() {
15
15
  }
16
16
  function Sidebar() {
17
17
  const { urlPathname } = usePageContext();
18
+ const currentPath = normalizePath(urlPathname);
18
19
  const [showHidden] = useHiddenToggle();
19
20
  const allGroups = showHidden ? groupedPages : groupedVisiblePages;
20
21
  const activeTab = tabForUrl(urlPathname);
@@ -36,7 +37,7 @@ function Sidebar() {
36
37
  });
37
38
  }
38
39
  useEffect(() => {
39
- const g = groups.find((g2) => g2.items.some((i) => i.path === urlPathname));
40
+ const g = groups.find((g2) => g2.items.some((i) => i.path === currentPath));
40
41
  if (g?.label && collapsed.has(g.label)) {
41
42
  setCollapsed((prev) => {
42
43
  const next = new Set(prev);
@@ -84,7 +85,7 @@ function Sidebar() {
84
85
  "a",
85
86
  {
86
87
  href: item.path,
87
- "aria-current": item.path === urlPathname ? "page" : void 0,
88
+ "aria-current": item.path === currentPath ? "page" : void 0,
88
89
  className: "sidebar-link",
89
90
  children: [
90
91
  /* @__PURE__ */ jsx("span", { style: { flex: 1, minWidth: 0 }, children: item.title }),
@@ -1,7 +1,7 @@
1
1
  import { jsxs, jsx } from "react/jsx-runtime";
2
2
  import { useState, useRef, useEffect, useMemo } from "react";
3
3
  import { usePageContext } from "vike-react/usePageContext";
4
- import { pages } from "../lib/navigation.js";
4
+ import { normalizePath, findPage } from "../lib/navigation.js";
5
5
  import { siteConfig } from "../site-config.js";
6
6
  const TOC_KEY_PREFIX = "toc-collapsed:";
7
7
  const X0 = 4;
@@ -51,15 +51,16 @@ function scrollToHeading(e, id) {
51
51
  }
52
52
  function TOC() {
53
53
  const { urlPathname } = usePageContext();
54
+ const currentPath = normalizePath(urlPathname);
54
55
  const mdHref = (() => {
55
- const meta = pages.find((p) => p.path === urlPathname);
56
+ const meta = findPage(urlPathname);
56
57
  if (!meta || meta.noindex) return null;
57
- return urlPathname === "/" ? "/index.md" : `${urlPathname}.md`;
58
+ return currentPath === "/" ? "/index.md" : `${currentPath}.md`;
58
59
  })();
59
60
  const editHref = (() => {
60
- const meta = pages.find((p) => p.path === urlPathname);
61
+ const meta = findPage(urlPathname);
61
62
  if (!meta) return null;
62
- const slug = urlPathname === "/" ? "index" : urlPathname.replace(/^\//, "");
63
+ const slug = currentPath === "/" ? "index" : currentPath.replace(/^\//, "");
63
64
  const pagesPath = siteConfig.docsPagesPath ?? "docs/pages";
64
65
  return `${siteConfig.docsRepoUrl}/edit/${siteConfig.docsBranch}/${pagesPath}/${slug}/+Page.mdx`;
65
66
  })();
@@ -138,7 +139,7 @@ function TOC() {
138
139
  setHeadings([]);
139
140
  return;
140
141
  }
141
- const current = pages.find((p) => p.path === urlPathname);
142
+ const current = findPage(urlPathname);
142
143
  const tocLevel = current?.tocLevel ?? 3;
143
144
  const selector = tocLevel >= 3 ? "[data-toc-anchor], h3[id]" : "[data-toc-anchor]";
144
145
  function collect() {
@@ -1,2 +1,5 @@
1
1
  import { JSX } from 'react';
2
+ /** Topbar theme control. The variant comes from `siteConfig.themeToggle`:
3
+ * `'cycle'` (default) renders the single cycling button, `'segmented'`
4
+ * the three-button slider. Both share ThemeProvider state. */
2
5
  export declare function ThemeToggle(): JSX.Element;
@@ -1,6 +1,7 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { useTheme } from "./ThemeProvider.js";
3
3
  import { ClientOnly } from "./ClientOnly.js";
4
+ import { siteConfig } from "../site-config.js";
4
5
  const SIZE = 24;
5
6
  const PAD = 2;
6
7
  const SPRING = "cubic-bezier(0.34, 1.45, 0.55, 1)";
@@ -61,11 +62,11 @@ const DarkIcon = () => /* @__PURE__ */ jsx(
61
62
  }
62
63
  );
63
64
  const SEGMENTS = [
64
- { value: "light", label: "Light", Icon: LightIcon },
65
+ { value: "light", label: "Light", Icon: LightIcon, inactiveRotate: -18 },
65
66
  { value: "system", label: "System", Icon: SystemIcon },
66
- { value: "dark", label: "Dark", Icon: DarkIcon }
67
+ { value: "dark", label: "Dark", Icon: DarkIcon, inactiveRotate: 18 }
67
68
  ];
68
- function ToggleInner() {
69
+ function CycleToggle() {
69
70
  const { theme, setTheme } = useTheme();
70
71
  const idx = Math.max(0, SEGMENTS.findIndex((s) => s.value === theme));
71
72
  const current = SEGMENTS[idx];
@@ -101,8 +102,80 @@ function ToggleInner() {
101
102
  }
102
103
  );
103
104
  }
105
+ function SegmentedToggle() {
106
+ const { theme, setTheme } = useTheme();
107
+ const idx = Math.max(0, SEGMENTS.findIndex((s) => s.value === theme));
108
+ return /* @__PURE__ */ jsxs(
109
+ "div",
110
+ {
111
+ role: "group",
112
+ "aria-label": "Theme",
113
+ className: "relative inline-flex items-center bg-doc-template-panel",
114
+ style: { padding: PAD, borderRadius: 999, isolation: "isolate" },
115
+ children: [
116
+ /* @__PURE__ */ jsx(
117
+ "span",
118
+ {
119
+ "aria-hidden": true,
120
+ className: "bg-doc-template-chip",
121
+ style: {
122
+ position: "absolute",
123
+ top: PAD,
124
+ left: PAD,
125
+ width: SIZE,
126
+ height: SIZE,
127
+ borderRadius: 999,
128
+ transform: `translateX(${idx * SIZE}px)`,
129
+ transition: `transform 0.42s ${SPRING}`,
130
+ zIndex: 0,
131
+ willChange: "transform"
132
+ }
133
+ }
134
+ ),
135
+ SEGMENTS.map((seg) => {
136
+ const pressed = seg.value === theme;
137
+ return /* @__PURE__ */ jsx(
138
+ "button",
139
+ {
140
+ type: "button",
141
+ "aria-label": seg.label,
142
+ title: seg.label,
143
+ "aria-pressed": pressed,
144
+ onClick: () => setTheme(seg.value),
145
+ className: pressed ? "inline-flex items-center justify-center bg-transparent border-0 cursor-pointer text-doc-template-ink" : "inline-flex items-center justify-center bg-transparent border-0 cursor-pointer text-doc-template-muted hover:text-doc-template-ink",
146
+ style: {
147
+ position: "relative",
148
+ zIndex: 1,
149
+ width: SIZE,
150
+ height: SIZE,
151
+ borderRadius: 999,
152
+ padding: 0,
153
+ margin: 0,
154
+ transition: "color 0.25s ease"
155
+ },
156
+ children: /* @__PURE__ */ jsx(
157
+ "span",
158
+ {
159
+ style: {
160
+ display: "inline-flex",
161
+ transform: pressed ? "scale(1) rotate(0deg)" : `scale(0.85)${seg.inactiveRotate ? ` rotate(${seg.inactiveRotate}deg)` : ""}`,
162
+ opacity: pressed ? 1 : 0.65,
163
+ transition: `transform 0.42s ${SPRING}, opacity 0.25s ease`
164
+ },
165
+ children: /* @__PURE__ */ jsx(seg.Icon, {})
166
+ }
167
+ )
168
+ },
169
+ seg.value
170
+ );
171
+ })
172
+ ]
173
+ }
174
+ );
175
+ }
104
176
  function ThemeToggle() {
105
- return /* @__PURE__ */ jsx(ClientOnly, { children: /* @__PURE__ */ jsx(ToggleInner, {}) });
177
+ const segmented = siteConfig.themeToggle === "segmented";
178
+ return /* @__PURE__ */ jsx(ClientOnly, { children: segmented ? /* @__PURE__ */ jsx(SegmentedToggle, {}) : /* @__PURE__ */ jsx(CycleToggle, {}) });
106
179
  }
107
180
  export {
108
181
  ThemeToggle