create-zudo-doc 0.1.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/LICENSE +21 -0
- package/README.md +146 -0
- package/bin/create-zudo-doc.js +2 -0
- package/dist/api.d.ts +20 -0
- package/dist/api.js +13 -0
- package/dist/claude-md-gen.d.ts +2 -0
- package/dist/claude-md-gen.js +113 -0
- package/dist/cli.d.ts +39 -0
- package/dist/cli.js +157 -0
- package/dist/compose.d.ts +95 -0
- package/dist/compose.js +206 -0
- package/dist/constants.d.ts +20 -0
- package/dist/constants.js +224 -0
- package/dist/features/body-foot-util.d.ts +10 -0
- package/dist/features/body-foot-util.js +12 -0
- package/dist/features/claude-resources.d.ts +2 -0
- package/dist/features/claude-resources.js +6 -0
- package/dist/features/design-token-panel.d.ts +14 -0
- package/dist/features/design-token-panel.js +27 -0
- package/dist/features/doc-history.d.ts +9 -0
- package/dist/features/doc-history.js +11 -0
- package/dist/features/doc-tags.d.ts +19 -0
- package/dist/features/doc-tags.js +33 -0
- package/dist/features/footer-taglist.d.ts +14 -0
- package/dist/features/footer-taglist.js +17 -0
- package/dist/features/footer.d.ts +8 -0
- package/dist/features/footer.js +10 -0
- package/dist/features/i18n.d.ts +22 -0
- package/dist/features/i18n.js +41 -0
- package/dist/features/image-enlarge.d.ts +11 -0
- package/dist/features/image-enlarge.js +13 -0
- package/dist/features/index.d.ts +15 -0
- package/dist/features/index.js +53 -0
- package/dist/features/llms-txt.d.ts +11 -0
- package/dist/features/llms-txt.js +13 -0
- package/dist/features/search.d.ts +9 -0
- package/dist/features/search.js +11 -0
- package/dist/features/sidebar-resizer.d.ts +14 -0
- package/dist/features/sidebar-resizer.js +16 -0
- package/dist/features/sidebar-toggle.d.ts +13 -0
- package/dist/features/sidebar-toggle.js +15 -0
- package/dist/features/tag-governance.d.ts +14 -0
- package/dist/features/tag-governance.js +16 -0
- package/dist/features/tauri-dev.d.ts +2 -0
- package/dist/features/tauri-dev.js +25 -0
- package/dist/features/tauri.d.ts +11 -0
- package/dist/features/tauri.js +52 -0
- package/dist/features/versioning.d.ts +27 -0
- package/dist/features/versioning.js +43 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +150 -0
- package/dist/preset.d.ts +37 -0
- package/dist/preset.js +156 -0
- package/dist/prompts.d.ts +32 -0
- package/dist/prompts.js +248 -0
- package/dist/scaffold.d.ts +4 -0
- package/dist/scaffold.js +344 -0
- package/dist/settings-gen.d.ts +2 -0
- package/dist/settings-gen.js +237 -0
- package/dist/utils.d.ts +8 -0
- package/dist/utils.js +34 -0
- package/dist/zfb-config-gen.d.ts +19 -0
- package/dist/zfb-config-gen.js +222 -0
- package/package.json +65 -0
- package/templates/base/.htmlvalidate.json +5 -0
- package/templates/base/.zfb/doc-history-meta.json +1 -0
- package/templates/base/pages/404.tsx +55 -0
- package/templates/base/pages/_data.ts +179 -0
- package/templates/base/pages/_mdx-components.ts +249 -0
- package/templates/base/pages/docs/[...slug].tsx +448 -0
- package/templates/base/pages/index.tsx +158 -0
- package/templates/base/pages/lib/_body-end-islands.tsx +201 -0
- package/templates/base/pages/lib/_category-nav.tsx +148 -0
- package/templates/base/pages/lib/_category-tree-nav.tsx +104 -0
- package/templates/base/pages/lib/_compose-meta-title.ts +29 -0
- package/templates/base/pages/lib/_details.tsx +30 -0
- package/templates/base/pages/lib/_doc-history-area.tsx +178 -0
- package/templates/base/pages/lib/_doc-metainfo-area.tsx +100 -0
- package/templates/base/pages/lib/_doc-tags-area.tsx +89 -0
- package/templates/base/pages/lib/_extract-headings.ts +81 -0
- package/templates/base/pages/lib/_footer-with-defaults.tsx +234 -0
- package/templates/base/pages/lib/_frontmatter-preview-data.ts +53 -0
- package/templates/base/pages/lib/_head-with-defaults.tsx +113 -0
- package/templates/base/pages/lib/_header-with-defaults.tsx +386 -0
- package/templates/base/pages/lib/_inline-version-switcher.tsx +84 -0
- package/templates/base/pages/lib/_math-block.tsx +63 -0
- package/templates/base/pages/lib/_nav-source-docs.ts +68 -0
- package/templates/base/pages/lib/_preset-generator.tsx +81 -0
- package/templates/base/pages/lib/_search-widget-script.ts +388 -0
- package/templates/base/pages/lib/_search-widget.tsx +196 -0
- package/templates/base/pages/lib/_sidebar-with-defaults.tsx +176 -0
- package/templates/base/pages/lib/_site-tree-nav.tsx +128 -0
- package/templates/base/pages/lib/locale-merge.ts +58 -0
- package/templates/base/pages/lib/route-enumerators.ts +302 -0
- package/templates/base/pages/sitemap.xml.tsx +51 -0
- package/templates/base/plugins/connect-adapter.mjs +144 -0
- package/templates/base/plugins/copy-public-plugin.mjs +50 -0
- package/templates/base/plugins/search-index-plugin.mjs +54 -0
- package/templates/base/scripts/run-b4push.sh +102 -0
- package/templates/base/src/components/ai-chat-modal.tsx +15 -0
- package/templates/base/src/components/client-router-bootstrap.tsx +14 -0
- package/templates/base/src/components/content/component-map.ts +25 -0
- package/templates/base/src/components/content/content-blockquote.tsx +16 -0
- package/templates/base/src/components/content/content-code.tsx +117 -0
- package/templates/base/src/components/content/content-link.tsx +83 -0
- package/templates/base/src/components/content/content-ol.tsx +19 -0
- package/templates/base/src/components/content/content-paragraph.tsx +10 -0
- package/templates/base/src/components/content/content-strong.tsx +16 -0
- package/templates/base/src/components/content/content-table.tsx +18 -0
- package/templates/base/src/components/content/content-ul.tsx +18 -0
- package/templates/base/src/components/content/heading-h2.tsx +26 -0
- package/templates/base/src/components/content/heading-h3.tsx +26 -0
- package/templates/base/src/components/content/heading-h4.tsx +26 -0
- package/templates/base/src/components/design-token-panel-bootstrap.tsx +15 -0
- package/templates/base/src/components/desktop-sidebar-toggle.tsx +15 -0
- package/templates/base/src/components/doc-history.tsx +18 -0
- package/templates/base/src/components/html-preview/highlighted-code.tsx +74 -0
- package/templates/base/src/components/html-preview/html-preview.tsx +108 -0
- package/templates/base/src/components/html-preview/preflight.ts +112 -0
- package/templates/base/src/components/html-preview/preview-base.tsx +159 -0
- package/templates/base/src/components/image-enlarge.tsx +19 -0
- package/templates/base/src/components/mobile-toc.tsx +94 -0
- package/templates/base/src/components/preset-generator.tsx +14 -0
- package/templates/base/src/components/sidebar-toggle.tsx +98 -0
- package/templates/base/src/components/sidebar-tree.tsx +543 -0
- package/templates/base/src/components/site-tree-nav.tsx +233 -0
- package/templates/base/src/components/theme-toggle.tsx +93 -0
- package/templates/base/src/components/toc.tsx +63 -0
- package/templates/base/src/components/tree-nav-shared.tsx +71 -0
- package/templates/base/src/config/color-scheme-utils.ts +182 -0
- package/templates/base/src/config/color-schemes.ts +128 -0
- package/templates/base/src/config/frontmatter-preview-defaults.ts +24 -0
- package/templates/base/src/config/frontmatter-preview-renderers.tsx +46 -0
- package/templates/base/src/config/i18n.ts +225 -0
- package/templates/base/src/config/settings-types.ts +162 -0
- package/templates/base/src/config/sidebars.ts +66 -0
- package/templates/base/src/config/tag-vocabulary-types.ts +39 -0
- package/templates/base/src/config/tag-vocabulary.ts +20 -0
- package/templates/base/src/hooks/use-active-heading.ts +133 -0
- package/templates/base/src/plugins/docs-source-map.ts +103 -0
- package/templates/base/src/plugins/hast-utils.ts +10 -0
- package/templates/base/src/plugins/rehype-code-title.ts +50 -0
- package/templates/base/src/plugins/rehype-heading-links.ts +53 -0
- package/templates/base/src/plugins/rehype-image-enlarge.ts +113 -0
- package/templates/base/src/plugins/rehype-mermaid.ts +41 -0
- package/templates/base/src/plugins/rehype-strip-md-extension.ts +58 -0
- package/templates/base/src/plugins/remark-admonitions.ts +99 -0
- package/templates/base/src/plugins/remark-resolve-markdown-links.ts +127 -0
- package/templates/base/src/plugins/url-utils.ts +4 -0
- package/templates/base/src/styles/global.css +1066 -0
- package/templates/base/src/types/docs-entry.ts +39 -0
- package/templates/base/src/types/heading.ts +5 -0
- package/templates/base/src/types/locale.ts +10 -0
- package/templates/base/src/utils/base.ts +139 -0
- package/templates/base/src/utils/content-files.ts +106 -0
- package/templates/base/src/utils/dedent.ts +24 -0
- package/templates/base/src/utils/docs.ts +335 -0
- package/templates/base/src/utils/git-info.ts +70 -0
- package/templates/base/src/utils/github.ts +19 -0
- package/templates/base/src/utils/header-right-items.ts +38 -0
- package/templates/base/src/utils/nav-scope.ts +63 -0
- package/templates/base/src/utils/sidebar.ts +104 -0
- package/templates/base/src/utils/slug.ts +10 -0
- package/templates/base/src/utils/smart-break.tsx +126 -0
- package/templates/base/src/utils/tags.ts +126 -0
- package/templates/base/tsconfig.json +36 -0
- package/templates/features/bodyFootUtil/files/src/utils/github.ts +19 -0
- package/templates/features/claudeResources/files/plugins/claude-resources-plugin.mjs +137 -0
- package/templates/features/claudeResources/files/src/integrations/claude-resources/__tests__/escape-for-mdx.test.ts +34 -0
- package/templates/features/claudeResources/files/src/integrations/claude-resources/__tests__/generate.test.ts +376 -0
- package/templates/features/claudeResources/files/src/integrations/claude-resources/escape-for-mdx.ts +93 -0
- package/templates/features/claudeResources/files/src/integrations/claude-resources/generate.ts +586 -0
- package/templates/features/designTokenPanel/files/src/components/design-token-panel-bootstrap.tsx +15 -0
- package/templates/features/designTokenPanel/files/src/config/design-token-panel-config.ts +99 -0
- package/templates/features/designTokenPanel/files/src/config/design-tokens-manifest.ts +177 -0
- package/templates/features/designTokenPanel/files/src/lib/design-token-panel-bootstrap.ts +50 -0
- package/templates/features/docHistory/files/plugins/doc-history-plugin.mjs +99 -0
- package/templates/features/docHistory/files/src/components/doc-history.tsx +598 -0
- package/templates/features/docHistory/files/src/types/doc-history.ts +23 -0
- package/templates/features/docHistory/files/src/utils/doc-history.ts +180 -0
- package/templates/features/docTags/files/pages/[locale]/docs/tags/[tag].tsx +116 -0
- package/templates/features/docTags/files/pages/[locale]/docs/tags/index.tsx +99 -0
- package/templates/features/docTags/files/pages/docs/tags/[tag].tsx +101 -0
- package/templates/features/docTags/files/pages/docs/tags/index.tsx +86 -0
- package/templates/features/i18n/files/pages/[locale]/docs/[...slug].tsx +467 -0
- package/templates/features/i18n/files/pages/[locale]/index.tsx +213 -0
- package/templates/features/imageEnlarge/files/src/components/image-enlarge.tsx +248 -0
- package/templates/features/llmsTxt/files/plugins/llms-txt-plugin.mjs +74 -0
- package/templates/features/sidebarResizer/files/src/scripts/sidebar-resizer.ts +185 -0
- package/templates/features/sidebarToggle/files/src/components/desktop-sidebar-toggle.tsx +126 -0
- package/templates/features/tagGovernance/files/scripts/tags-audit.ts +576 -0
- package/templates/features/tagGovernance/files/scripts/tags-suggest.ts +428 -0
- package/templates/features/tauri/files/src/components/find-bar.tsx +122 -0
- package/templates/features/tauri/files/src/components/find-in-page-init.tsx +53 -0
- package/templates/features/tauri/files/src/utils/find-in-page.ts +175 -0
- package/templates/features/tauri/files/src-tauri/Cargo.toml +14 -0
- package/templates/features/tauri/files/src-tauri/build.rs +3 -0
- package/templates/features/tauri/files/src-tauri/capabilities/default.json +11 -0
- package/templates/features/tauri/files/src-tauri/src/main.rs +250 -0
- package/templates/features/tauri/files/src-tauri/tauri.conf.json +25 -0
- package/templates/features/tauriDev/files/src-tauri-dev/Cargo.toml +15 -0
- package/templates/features/tauriDev/files/src-tauri-dev/build.rs +3 -0
- package/templates/features/tauriDev/files/src-tauri-dev/capabilities/default.json +7 -0
- package/templates/features/tauriDev/files/src-tauri-dev/frontend/index.html +187 -0
- package/templates/features/tauriDev/files/src-tauri-dev/icons/icon.png +0 -0
- package/templates/features/tauriDev/files/src-tauri-dev/src/main.rs +995 -0
- package/templates/features/tauriDev/files/src-tauri-dev/tauri.conf.json +22 -0
- package/templates/features/tauriDev/files/src-tauri-dev/test-launch.sh +65 -0
- package/templates/features/versioning/files/pages/[locale]/docs/versions.tsx +100 -0
- package/templates/features/versioning/files/pages/docs/versions.tsx +78 -0
- package/templates/features/versioning/files/pages/v/[version]/docs/[...slug].tsx +451 -0
- package/templates/features/versioning/files/pages/v/[version]/ja/docs/[...slug].tsx +490 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { useState, useEffect, useRef } from "preact/compat";
|
|
2
|
+
import type { JSX } from "preact";
|
|
3
|
+
|
|
4
|
+
interface ImageData {
|
|
5
|
+
src: string;
|
|
6
|
+
currentSrc: string;
|
|
7
|
+
srcset?: string;
|
|
8
|
+
sizes?: string;
|
|
9
|
+
alt: string;
|
|
10
|
+
naturalWidth: number;
|
|
11
|
+
naturalHeight: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Shared shell for the enlarge `<dialog>`. The hydrated component and the
|
|
15
|
+
// SSR fallback (below) render into the same Island container, so they MUST
|
|
16
|
+
// agree on class string and inline style — otherwise the dist HTML and the
|
|
17
|
+
// post-hydration DOM disagree on size / position and the first interaction
|
|
18
|
+
// flashes. Sourcing both from the same constants closes the drift gap.
|
|
19
|
+
const DIALOG_CLASS =
|
|
20
|
+
"zd-enlarge-dialog mx-auto max-h-[90vh] max-w-[90vw] overflow-hidden border border-muted bg-surface p-0";
|
|
21
|
+
const DIALOG_STYLE = {
|
|
22
|
+
position: "fixed",
|
|
23
|
+
top: "50%",
|
|
24
|
+
left: "50%",
|
|
25
|
+
transform: "translate(-50%, -50%)",
|
|
26
|
+
} as const;
|
|
27
|
+
|
|
28
|
+
export default function ImageEnlarge() {
|
|
29
|
+
const [imgData, setImgData] = useState<ImageData | null>(null);
|
|
30
|
+
const dialogRef = useRef<HTMLDialogElement>(null);
|
|
31
|
+
|
|
32
|
+
// Eligibility detection: toggle .zd-enlarge-btn[hidden] per image
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
const resizeObservers = new Map<HTMLImageElement, ResizeObserver>();
|
|
35
|
+
let mutationObserver: MutationObserver | null = null;
|
|
36
|
+
let resizeTimer = 0;
|
|
37
|
+
|
|
38
|
+
function evaluateEligibility(img: HTMLImageElement) {
|
|
39
|
+
const container = img.closest(".zd-enlargeable");
|
|
40
|
+
if (!container) return;
|
|
41
|
+
const btn = container.querySelector(".zd-enlarge-btn") as HTMLElement | null;
|
|
42
|
+
if (!btn) return;
|
|
43
|
+
const eligible = img.naturalWidth > img.clientWidth * window.devicePixelRatio;
|
|
44
|
+
if (eligible) {
|
|
45
|
+
btn.removeAttribute("hidden");
|
|
46
|
+
} else {
|
|
47
|
+
btn.setAttribute("hidden", "");
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function observeImage(img: HTMLImageElement) {
|
|
52
|
+
if (resizeObservers.has(img)) return;
|
|
53
|
+
const ro = new ResizeObserver(() => evaluateEligibility(img));
|
|
54
|
+
ro.observe(img);
|
|
55
|
+
resizeObservers.set(img, ro);
|
|
56
|
+
if (img.complete) {
|
|
57
|
+
evaluateEligibility(img);
|
|
58
|
+
} else {
|
|
59
|
+
img.addEventListener("load", () => evaluateEligibility(img), { once: true });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function scanContent() {
|
|
64
|
+
const scope = document.querySelector("main .zd-content");
|
|
65
|
+
if (!scope) return;
|
|
66
|
+
scope.querySelectorAll<HTMLImageElement>(".zd-enlargeable img").forEach(observeImage);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function startObserving() {
|
|
70
|
+
const scope = document.querySelector("main .zd-content");
|
|
71
|
+
if (scope) {
|
|
72
|
+
mutationObserver = new MutationObserver((mutations) => {
|
|
73
|
+
for (const mutation of mutations) {
|
|
74
|
+
for (const node of mutation.addedNodes) {
|
|
75
|
+
if (!(node instanceof Element)) continue;
|
|
76
|
+
if (node.matches(".zd-enlargeable")) {
|
|
77
|
+
node.querySelectorAll<HTMLImageElement>("img").forEach(observeImage);
|
|
78
|
+
}
|
|
79
|
+
node.querySelectorAll<HTMLImageElement>(".zd-enlargeable img").forEach(observeImage);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
mutationObserver.observe(scope, { childList: true, subtree: true });
|
|
84
|
+
}
|
|
85
|
+
scanContent();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function handleWindowResize() {
|
|
89
|
+
clearTimeout(resizeTimer);
|
|
90
|
+
resizeTimer = window.setTimeout(() => {
|
|
91
|
+
resizeObservers.forEach((_, img) => evaluateEligibility(img));
|
|
92
|
+
}, 150);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function handleAfterSwap() {
|
|
96
|
+
resizeObservers.forEach((ro) => ro.disconnect());
|
|
97
|
+
resizeObservers.clear();
|
|
98
|
+
mutationObserver?.disconnect();
|
|
99
|
+
mutationObserver = null;
|
|
100
|
+
startObserving();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
startObserving();
|
|
104
|
+
window.addEventListener("resize", handleWindowResize);
|
|
105
|
+
document.addEventListener("DOMContentLoaded", handleAfterSwap);
|
|
106
|
+
|
|
107
|
+
return () => {
|
|
108
|
+
resizeObservers.forEach((ro) => ro.disconnect());
|
|
109
|
+
resizeObservers.clear();
|
|
110
|
+
mutationObserver?.disconnect();
|
|
111
|
+
window.removeEventListener("resize", handleWindowResize);
|
|
112
|
+
document.removeEventListener("DOMContentLoaded", handleAfterSwap);
|
|
113
|
+
clearTimeout(resizeTimer);
|
|
114
|
+
};
|
|
115
|
+
}, []);
|
|
116
|
+
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
function handleDocumentClick(e: MouseEvent) {
|
|
119
|
+
const target = e.target as Element;
|
|
120
|
+
const container = target.closest(".zd-enlargeable");
|
|
121
|
+
if (!container) return;
|
|
122
|
+
const btn = container.querySelector(".zd-enlarge-btn") as HTMLElement | null;
|
|
123
|
+
// Eligibility gate: only open when the expand button is visible (image is large enough).
|
|
124
|
+
if (!btn || btn.hasAttribute("hidden")) return;
|
|
125
|
+
const img = container.querySelector("img") as HTMLImageElement | null;
|
|
126
|
+
if (!img) return;
|
|
127
|
+
setImgData({
|
|
128
|
+
src: img.src,
|
|
129
|
+
currentSrc: img.currentSrc,
|
|
130
|
+
srcset: img.srcset || undefined,
|
|
131
|
+
sizes: img.sizes || undefined,
|
|
132
|
+
alt: img.alt,
|
|
133
|
+
naturalWidth: img.naturalWidth,
|
|
134
|
+
naturalHeight: img.naturalHeight,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
document.addEventListener("click", handleDocumentClick);
|
|
138
|
+
return () => document.removeEventListener("click", handleDocumentClick);
|
|
139
|
+
}, []);
|
|
140
|
+
|
|
141
|
+
// Open dialog when imgData is set
|
|
142
|
+
useEffect(() => {
|
|
143
|
+
if (!imgData) return;
|
|
144
|
+
const dialog = dialogRef.current;
|
|
145
|
+
if (!dialog) return;
|
|
146
|
+
dialog.showModal();
|
|
147
|
+
}, [imgData]);
|
|
148
|
+
|
|
149
|
+
// Handle cancel event (ESC key)
|
|
150
|
+
useEffect(() => {
|
|
151
|
+
const dialog = dialogRef.current;
|
|
152
|
+
if (!dialog) return;
|
|
153
|
+
function handleCancel() {
|
|
154
|
+
setImgData(null);
|
|
155
|
+
}
|
|
156
|
+
dialog.addEventListener("cancel", handleCancel);
|
|
157
|
+
return () => dialog.removeEventListener("cancel", handleCancel);
|
|
158
|
+
}, []);
|
|
159
|
+
|
|
160
|
+
// Reset state when dialog closes
|
|
161
|
+
useEffect(() => {
|
|
162
|
+
const dialog = dialogRef.current;
|
|
163
|
+
if (!dialog) return;
|
|
164
|
+
function handleClose() {
|
|
165
|
+
setImgData(null);
|
|
166
|
+
}
|
|
167
|
+
dialog.addEventListener("close", handleClose);
|
|
168
|
+
return () => dialog.removeEventListener("close", handleClose);
|
|
169
|
+
}, []);
|
|
170
|
+
|
|
171
|
+
// Close and reset on ClientRouter navigation
|
|
172
|
+
useEffect(() => {
|
|
173
|
+
function handleAfterSwap() {
|
|
174
|
+
const dialog = dialogRef.current;
|
|
175
|
+
if (dialog?.open) dialog.close();
|
|
176
|
+
setImgData(null);
|
|
177
|
+
}
|
|
178
|
+
document.addEventListener("DOMContentLoaded", handleAfterSwap);
|
|
179
|
+
return () => document.removeEventListener("DOMContentLoaded", handleAfterSwap);
|
|
180
|
+
}, []);
|
|
181
|
+
|
|
182
|
+
function handleBackdropClick(e: JSX.TargetedMouseEvent<HTMLDialogElement>) {
|
|
183
|
+
const dialog = dialogRef.current;
|
|
184
|
+
if (!dialog) return;
|
|
185
|
+
const rect = dialog.getBoundingClientRect();
|
|
186
|
+
if (
|
|
187
|
+
e.clientX < rect.left ||
|
|
188
|
+
e.clientX > rect.right ||
|
|
189
|
+
e.clientY < rect.top ||
|
|
190
|
+
e.clientY > rect.bottom
|
|
191
|
+
) {
|
|
192
|
+
dialog.close();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return (
|
|
197
|
+
<dialog
|
|
198
|
+
ref={dialogRef}
|
|
199
|
+
onClick={handleBackdropClick}
|
|
200
|
+
className={DIALOG_CLASS}
|
|
201
|
+
style={DIALOG_STYLE}
|
|
202
|
+
>
|
|
203
|
+
{imgData && (
|
|
204
|
+
<>
|
|
205
|
+
<div className="relative">
|
|
206
|
+
<img
|
|
207
|
+
src={imgData.currentSrc || imgData.src}
|
|
208
|
+
srcSet={imgData.srcset}
|
|
209
|
+
sizes={imgData.srcset ? "100vw" : undefined}
|
|
210
|
+
alt={imgData.alt}
|
|
211
|
+
className="block max-h-[85vh] max-w-[85vw] object-contain"
|
|
212
|
+
/>
|
|
213
|
+
</div>
|
|
214
|
+
<button
|
|
215
|
+
type="button"
|
|
216
|
+
onClick={() => dialogRef.current?.close()}
|
|
217
|
+
className="zd-enlarge-dialog-close"
|
|
218
|
+
aria-label="Close enlarged image"
|
|
219
|
+
>
|
|
220
|
+
<svg viewBox="0 0 161.03 161.03" fill="currentColor" aria-hidden="true" focusable="false">
|
|
221
|
+
<polygon points="161.03 10.27 150.76 0 80.51 70.24 10.27 0 0 10.27 70.24 80.51 0 150.76 10.27 161.03 80.51 90.78 150.76 161.03 161.03 150.76 90.78 80.51 161.03 10.27" />
|
|
222
|
+
</svg>
|
|
223
|
+
</button>
|
|
224
|
+
</>
|
|
225
|
+
)}
|
|
226
|
+
</dialog>
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Static SSR fallback for the {@link ImageEnlarge} island.
|
|
232
|
+
*
|
|
233
|
+
* Renders an empty, closed `<dialog class="zd-enlarge-dialog ...">` so the
|
|
234
|
+
* dist HTML carries the dialog shell even before hydration. A `<dialog>`
|
|
235
|
+
* without `open` is `display:none` per UA stylesheet, so screen readers
|
|
236
|
+
* and crawlers see the same shape they would post-hydration. Sources its
|
|
237
|
+
* class and inline style from the shared `DIALOG_CLASS` / `DIALOG_STYLE`
|
|
238
|
+
* constants above so the SSR shell cannot drift from the hydrated
|
|
239
|
+
* dialog (a drift would surface as a cosmetic flash on first interaction).
|
|
240
|
+
*/
|
|
241
|
+
export function ImageEnlargeSsrFallback() {
|
|
242
|
+
return (
|
|
243
|
+
<dialog
|
|
244
|
+
className={DIALOG_CLASS}
|
|
245
|
+
style={DIALOG_STYLE}
|
|
246
|
+
/>
|
|
247
|
+
);
|
|
248
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// zfb plugin module: llms-txt.
|
|
2
|
+
//
|
|
3
|
+
// Wires two lifecycle hooks for the llms-txt integration:
|
|
4
|
+
//
|
|
5
|
+
// postBuild — invokes `emitLlmsTxt` to write `dist/llms.txt`,
|
|
6
|
+
// `dist/llms-full.txt`, and the per-locale variants.
|
|
7
|
+
// `siteUrl` is normalised to `undefined` when falsy because
|
|
8
|
+
// the runner switches between absolute and root-relative URLs
|
|
9
|
+
// based on its presence (matches legacy Astro behaviour).
|
|
10
|
+
//
|
|
11
|
+
// devMiddleware — serves `/llms.txt`, `/llms-full.txt`, and the per-locale
|
|
12
|
+
// `/<code>/llms.txt` / `/<code>/llms-full.txt` variants from
|
|
13
|
+
// the on-the-fly `generateLlmsTxt` generator so dev output
|
|
14
|
+
// stays in lockstep with the production `emitLlmsTxt`
|
|
15
|
+
// byte-for-byte.
|
|
16
|
+
//
|
|
17
|
+
// `options` carries `{ siteName, siteDescription, base, siteUrl,
|
|
18
|
+
// defaultLocaleDir, locales }` from the matching entry in `zfb.config.ts`.
|
|
19
|
+
//
|
|
20
|
+
// Inline functions are not supported by zfb's plugin runtime; see the
|
|
21
|
+
// sibling `doc-history-plugin.mjs` for the rationale.
|
|
22
|
+
|
|
23
|
+
import { emitLlmsTxt, createLlmsTxtDevMiddleware } from "@takazudo/zudo-doc/integrations/llms-txt";
|
|
24
|
+
import { connectToZfbHandler } from "./connect-adapter.mjs";
|
|
25
|
+
|
|
26
|
+
export default {
|
|
27
|
+
name: "llms-txt",
|
|
28
|
+
|
|
29
|
+
postBuild(ctx) {
|
|
30
|
+
const {
|
|
31
|
+
siteName,
|
|
32
|
+
siteDescription,
|
|
33
|
+
base,
|
|
34
|
+
siteUrl,
|
|
35
|
+
defaultLocaleDir,
|
|
36
|
+
locales,
|
|
37
|
+
} = ctx.options;
|
|
38
|
+
emitLlmsTxt({
|
|
39
|
+
outDir: ctx.outDir,
|
|
40
|
+
siteName,
|
|
41
|
+
siteDescription,
|
|
42
|
+
base,
|
|
43
|
+
siteUrl: siteUrl || undefined,
|
|
44
|
+
defaultLocaleDir,
|
|
45
|
+
locales,
|
|
46
|
+
logger: ctx.logger,
|
|
47
|
+
});
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
devMiddleware(ctx) {
|
|
51
|
+
const middleware = createLlmsTxtDevMiddleware(ctx.options, ctx.logger);
|
|
52
|
+
const handler = connectToZfbHandler(middleware);
|
|
53
|
+
|
|
54
|
+
// zfb's `register(path, handler)` matches against the FULL request
|
|
55
|
+
// URL (no base-stripping). For a non-root base (e.g. "/my-docs/"),
|
|
56
|
+
// requests arrive as `/my-docs/llms.txt` (etc.), so we register
|
|
57
|
+
// every route with the base prefix. For base="/", the prefix is
|
|
58
|
+
// empty and routes are `/llms.txt` etc. as expected. The middleware
|
|
59
|
+
// accepts base-prefixed URLs via the matcher (see `matchLlmsRoute`
|
|
60
|
+
// in `dev-middleware.ts`).
|
|
61
|
+
const basePrefix = stripTrailingSlash(ctx.options.base ?? "");
|
|
62
|
+
ctx.register(`${basePrefix}/llms.txt`, handler);
|
|
63
|
+
ctx.register(`${basePrefix}/llms-full.txt`, handler);
|
|
64
|
+
for (const locale of ctx.options.locales ?? []) {
|
|
65
|
+
ctx.register(`${basePrefix}/${locale.code}/llms.txt`, handler);
|
|
66
|
+
ctx.register(`${basePrefix}/${locale.code}/llms-full.txt`, handler);
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
function stripTrailingSlash(s) {
|
|
72
|
+
if (typeof s !== "string" || s.length === 0) return "";
|
|
73
|
+
return s.endsWith("/") ? s.slice(0, -1) : s;
|
|
74
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
export function initSidebarResizer() {
|
|
2
|
+
const sidebar = document.getElementById("desktop-sidebar");
|
|
3
|
+
if (!sidebar || sidebar.querySelector("[data-sidebar-resizer]")) return;
|
|
4
|
+
|
|
5
|
+
// Resizer allows a wider range (192–448px) than the CSS default
|
|
6
|
+
// (clamp(14rem, 20vw, 22rem) = 224–352px at 16px base).
|
|
7
|
+
// CSS provides the responsive initial width; the resizer lets users
|
|
8
|
+
// go beyond that range when explicitly dragging or using keyboard arrows.
|
|
9
|
+
const MIN_W = 192;
|
|
10
|
+
const MAX_W = 448;
|
|
11
|
+
const STEP = 10;
|
|
12
|
+
const LS_KEY = "zudo-doc-sidebar-width";
|
|
13
|
+
const CSS_PROP = "--zd-sidebar-w";
|
|
14
|
+
const ACCENT_BG = "var(--zd-accent, rgba(128,128,128,0.3))";
|
|
15
|
+
const ACCENT_OUTLINE = "2px solid var(--zd-accent, rgba(128,128,128,0.5))";
|
|
16
|
+
const ACCENT_GHOST = "var(--zd-accent, rgba(128,128,128,0.5))";
|
|
17
|
+
|
|
18
|
+
function readCurrentWidth(): number {
|
|
19
|
+
const raw = getComputedStyle(document.documentElement).getPropertyValue(CSS_PROP);
|
|
20
|
+
return raw ? parseFloat(raw) || MIN_W : MIN_W;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let cachedWidth = readCurrentWidth();
|
|
24
|
+
|
|
25
|
+
const handle = document.createElement("div");
|
|
26
|
+
handle.setAttribute("data-sidebar-resizer", "");
|
|
27
|
+
handle.setAttribute("tabindex", "0");
|
|
28
|
+
handle.setAttribute("role", "separator");
|
|
29
|
+
handle.setAttribute("aria-orientation", "vertical");
|
|
30
|
+
handle.setAttribute("aria-label", "Resize sidebar");
|
|
31
|
+
handle.setAttribute("aria-valuemin", String(MIN_W));
|
|
32
|
+
handle.setAttribute("aria-valuemax", String(MAX_W));
|
|
33
|
+
handle.setAttribute("aria-valuenow", String(Math.round(cachedWidth)));
|
|
34
|
+
// 20px is wider than every common native y-scrollbar (~12-17px on
|
|
35
|
+
// Win/Linux classic; 0 on macOS overlay) so a draggable strip always remains
|
|
36
|
+
// visible to the LEFT of the scrollbar when sidebar content overflows.
|
|
37
|
+
// zudolab/zudo-doc#1660
|
|
38
|
+
Object.assign(handle.style, {
|
|
39
|
+
position: "absolute",
|
|
40
|
+
top: "0",
|
|
41
|
+
right: "0",
|
|
42
|
+
width: "20px",
|
|
43
|
+
height: "100%",
|
|
44
|
+
cursor: "col-resize",
|
|
45
|
+
zIndex: "10",
|
|
46
|
+
transition: "background 0.15s",
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
let dragging = false;
|
|
50
|
+
|
|
51
|
+
function applyWidth(w: number) {
|
|
52
|
+
cachedWidth = Math.max(MIN_W, Math.min(MAX_W, w));
|
|
53
|
+
document.documentElement.style.setProperty(CSS_PROP, cachedWidth + "px");
|
|
54
|
+
try { localStorage.setItem(LS_KEY, String(Math.round(cachedWidth))); } catch {}
|
|
55
|
+
handle.setAttribute("aria-valuenow", String(Math.round(cachedWidth)));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let focused = false;
|
|
59
|
+
|
|
60
|
+
function updateHandleVisual() {
|
|
61
|
+
if (dragging || focused) {
|
|
62
|
+
handle.style.background = ACCENT_BG;
|
|
63
|
+
} else {
|
|
64
|
+
handle.style.background = "";
|
|
65
|
+
}
|
|
66
|
+
handle.style.outline = focused && !dragging ? ACCENT_OUTLINE : "";
|
|
67
|
+
handle.style.outlineOffset = focused && !dragging ? "1px" : "";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
handle.addEventListener("focus", () => {
|
|
71
|
+
focused = true;
|
|
72
|
+
updateHandleVisual();
|
|
73
|
+
});
|
|
74
|
+
handle.addEventListener("blur", () => {
|
|
75
|
+
focused = false;
|
|
76
|
+
updateHandleVisual();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
handle.addEventListener("keydown", (e: KeyboardEvent) => {
|
|
80
|
+
let w = cachedWidth;
|
|
81
|
+
switch (e.key) {
|
|
82
|
+
case "ArrowLeft":
|
|
83
|
+
w = Math.max(MIN_W, w - STEP);
|
|
84
|
+
break;
|
|
85
|
+
case "ArrowRight":
|
|
86
|
+
w = Math.min(MAX_W, w + STEP);
|
|
87
|
+
break;
|
|
88
|
+
case "Home":
|
|
89
|
+
w = MIN_W;
|
|
90
|
+
break;
|
|
91
|
+
case "End":
|
|
92
|
+
w = MAX_W;
|
|
93
|
+
break;
|
|
94
|
+
default:
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
e.preventDefault();
|
|
98
|
+
applyWidth(w);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
handle.addEventListener("mouseenter", () => {
|
|
102
|
+
if (!dragging && !focused) handle.style.background = ACCENT_BG;
|
|
103
|
+
});
|
|
104
|
+
handle.addEventListener("mouseleave", () => {
|
|
105
|
+
if (!dragging && !focused) handle.style.background = "";
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
handle.addEventListener("pointerdown", (e: PointerEvent) => {
|
|
109
|
+
e.preventDefault();
|
|
110
|
+
handle.setPointerCapture(e.pointerId);
|
|
111
|
+
dragging = true;
|
|
112
|
+
updateHandleVisual();
|
|
113
|
+
document.documentElement.style.cursor = "col-resize";
|
|
114
|
+
document.documentElement.style.userSelect = "none";
|
|
115
|
+
|
|
116
|
+
// Ghost line — cheap to move (no reflow), shows target position
|
|
117
|
+
const ghost = document.createElement("div");
|
|
118
|
+
Object.assign(ghost.style, {
|
|
119
|
+
position: "fixed",
|
|
120
|
+
top: "0",
|
|
121
|
+
width: "2px",
|
|
122
|
+
height: "100vh",
|
|
123
|
+
background: ACCENT_GHOST,
|
|
124
|
+
pointerEvents: "none",
|
|
125
|
+
zIndex: "9999",
|
|
126
|
+
});
|
|
127
|
+
const sidebarRect = sidebar.getBoundingClientRect();
|
|
128
|
+
const sidebarLeft = sidebarRect.left;
|
|
129
|
+
ghost.style.left = (sidebarLeft + sidebarRect.width) + "px";
|
|
130
|
+
document.body.appendChild(ghost);
|
|
131
|
+
let targetWidth = 0;
|
|
132
|
+
let cleaned = false;
|
|
133
|
+
|
|
134
|
+
const onMove = (ev: PointerEvent) => {
|
|
135
|
+
targetWidth = Math.max(MIN_W, Math.min(MAX_W, ev.clientX - sidebarLeft));
|
|
136
|
+
ghost.style.left = (sidebarLeft + targetWidth) + "px";
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const cleanup = () => {
|
|
140
|
+
if (cleaned) return;
|
|
141
|
+
cleaned = true;
|
|
142
|
+
dragging = false;
|
|
143
|
+
updateHandleVisual();
|
|
144
|
+
document.documentElement.style.cursor = "";
|
|
145
|
+
document.documentElement.style.userSelect = "";
|
|
146
|
+
ghost.remove();
|
|
147
|
+
handle.removeEventListener("pointermove", onMove);
|
|
148
|
+
handle.removeEventListener("pointerup", onUp);
|
|
149
|
+
handle.removeEventListener("pointercancel", onCancel);
|
|
150
|
+
handle.removeEventListener("lostpointercapture", onLost);
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const commit = () => {
|
|
154
|
+
if (targetWidth > 0) applyWidth(targetWidth);
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// pointerup: normal end-of-drag. Commit, then teardown.
|
|
158
|
+
const onUp = () => {
|
|
159
|
+
commit();
|
|
160
|
+
cleanup();
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// lostpointercapture: per spec fires AFTER pointerup, but browsers reorder
|
|
164
|
+
// these in edge cases (cursor near y-scrollbar, fast drags, OS handoff).
|
|
165
|
+
// Commit here too so a real drag still applies if pointerup is dropped.
|
|
166
|
+
// Idempotent with onUp via the `cleaned` guard.
|
|
167
|
+
const onLost = () => {
|
|
168
|
+
commit();
|
|
169
|
+
cleanup();
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
// pointercancel: actual user/OS cancellation (touch interrupted, etc.).
|
|
173
|
+
// Do NOT commit — caller intent was to abort.
|
|
174
|
+
const onCancel = () => {
|
|
175
|
+
cleanup();
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
handle.addEventListener("pointermove", onMove);
|
|
179
|
+
handle.addEventListener("pointerup", onUp);
|
|
180
|
+
handle.addEventListener("pointercancel", onCancel);
|
|
181
|
+
handle.addEventListener("lostpointercapture", onLost);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
sidebar.appendChild(handle);
|
|
185
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect, useRef } from 'preact/hooks';
|
|
4
|
+
import { BEFORE_NAVIGATE_EVENT, AFTER_NAVIGATE_EVENT } from '@takazudo/zudo-doc/transitions';
|
|
5
|
+
|
|
6
|
+
export const SIDEBAR_STORAGE_KEY = 'zudo-doc-sidebar-visible';
|
|
7
|
+
|
|
8
|
+
function readState(): boolean {
|
|
9
|
+
if (typeof window === 'undefined') return true;
|
|
10
|
+
try {
|
|
11
|
+
return localStorage.getItem(SIDEBAR_STORAGE_KEY) !== 'false';
|
|
12
|
+
} catch {
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function setDataAttribute(isVisible: boolean) {
|
|
18
|
+
if (isVisible) {
|
|
19
|
+
document.documentElement.removeAttribute('data-sidebar-hidden');
|
|
20
|
+
} else {
|
|
21
|
+
document.documentElement.setAttribute('data-sidebar-hidden', '');
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default function DesktopSidebarToggle() {
|
|
26
|
+
// Initial state must match server render (always `true`) to avoid a
|
|
27
|
+
// hydration mismatch when the persisted preference is "hidden". The
|
|
28
|
+
// doc-layout's pre-paint inline script applies `data-sidebar-hidden`
|
|
29
|
+
// to <html> from localStorage *before* this island mounts, so the
|
|
30
|
+
// visual state stays correct; we only need to sync this island's
|
|
31
|
+
// React state to the persisted preference after hydration. Same
|
|
32
|
+
// pattern as src/components/theme-toggle.tsx (commit 9aebd8e).
|
|
33
|
+
const [visible, setVisible] = useState<boolean>(true);
|
|
34
|
+
// Tracks whether the hydration sync (below) has run. The persistence
|
|
35
|
+
// effect below skips the very first mount so we don't overwrite the
|
|
36
|
+
// user's persisted "hidden" preference with the SSR-safe default
|
|
37
|
+
// `true` before the hydration sync gets a chance to fire.
|
|
38
|
+
const hydrated = useRef(false);
|
|
39
|
+
|
|
40
|
+
// Persist state changes to localStorage and the <html> data-attribute.
|
|
41
|
+
// The `hydrated.current` guard is the real protection: it is still
|
|
42
|
+
// `false` on the very first effect run (the hydration-sync effect
|
|
43
|
+
// below sets it to `true` only after this one fires, since effects
|
|
44
|
+
// run in declaration order on mount), so the first run bails out
|
|
45
|
+
// and we don't clobber the user's persisted "hidden" preference
|
|
46
|
+
// with the SSR-safe default `true`.
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (!hydrated.current) return;
|
|
49
|
+
setDataAttribute(visible);
|
|
50
|
+
try {
|
|
51
|
+
localStorage.setItem(SIDEBAR_STORAGE_KEY, String(visible));
|
|
52
|
+
} catch {
|
|
53
|
+
// ignore storage errors
|
|
54
|
+
}
|
|
55
|
+
}, [visible]);
|
|
56
|
+
|
|
57
|
+
// After mount, read the persisted preference and reconcile state
|
|
58
|
+
// with the SSR default. Sets the ref so subsequent runs of the
|
|
59
|
+
// persistence effect above start syncing normally.
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
hydrated.current = true;
|
|
62
|
+
const actual = readState();
|
|
63
|
+
if (actual !== visible) {
|
|
64
|
+
setVisible(actual);
|
|
65
|
+
}
|
|
66
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
67
|
+
}, []);
|
|
68
|
+
|
|
69
|
+
// Re-apply data-sidebar-hidden to <html> after every SPA nav.
|
|
70
|
+
// zfb's swapRootAttributes wipes all non-preserved <html> attributes on
|
|
71
|
+
// each navigation (data-sidebar-hidden is not in NON_OVERRIDABLE_ZFB_ATTRS),
|
|
72
|
+
// and the pre-paint inline script does not re-run on SPA nav. Since this
|
|
73
|
+
// island is persisted (data-zfb-transition-persist), this listener stays
|
|
74
|
+
// registered across SPA swaps.
|
|
75
|
+
//
|
|
76
|
+
// Strategy: capture the attribute presence just before the swap
|
|
77
|
+
// (BEFORE_NAVIGATE_EVENT fires before swapRootAttributes runs), then
|
|
78
|
+
// restore it once the swap completes (AFTER_NAVIGATE_EVENT). This is
|
|
79
|
+
// authoritative regardless of how the attribute was set (toggle click,
|
|
80
|
+
// localStorage, or external mutation). (#1551, #1552 B10)
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
let wasHidden = false;
|
|
83
|
+
const capture = () => {
|
|
84
|
+
wasHidden = document.documentElement.hasAttribute('data-sidebar-hidden');
|
|
85
|
+
};
|
|
86
|
+
const restore = () => {
|
|
87
|
+
if (wasHidden) {
|
|
88
|
+
document.documentElement.setAttribute('data-sidebar-hidden', '');
|
|
89
|
+
}
|
|
90
|
+
// If not hidden, swapRootAttributes already cleared it — nothing to do.
|
|
91
|
+
};
|
|
92
|
+
document.addEventListener(BEFORE_NAVIGATE_EVENT, capture);
|
|
93
|
+
document.addEventListener(AFTER_NAVIGATE_EVENT, restore);
|
|
94
|
+
return () => {
|
|
95
|
+
document.removeEventListener(BEFORE_NAVIGATE_EVENT, capture);
|
|
96
|
+
document.removeEventListener(AFTER_NAVIGATE_EVENT, restore);
|
|
97
|
+
};
|
|
98
|
+
}, []);
|
|
99
|
+
|
|
100
|
+
return (
|
|
101
|
+
<button
|
|
102
|
+
type="button"
|
|
103
|
+
onClick={() => setVisible((v) => !v)}
|
|
104
|
+
className="zd-desktop-sidebar-toggle hidden lg:flex fixed bottom-vsp-xl z-40 items-center justify-center w-[1.5rem] h-[3rem] bg-surface border border-muted border-l-0 rounded-r-DEFAULT text-muted cursor-pointer transition-[left,color] duration-200 ease-in-out hover:text-fg"
|
|
105
|
+
aria-label={visible ? 'Hide sidebar' : 'Show sidebar'}
|
|
106
|
+
aria-pressed={visible}
|
|
107
|
+
data-zfb-transition-persist="desktop-sidebar-toggle"
|
|
108
|
+
>
|
|
109
|
+
<svg
|
|
110
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
111
|
+
className="h-icon-sm w-icon-sm"
|
|
112
|
+
aria-hidden="true"
|
|
113
|
+
fill="none"
|
|
114
|
+
viewBox="0 0 24 24"
|
|
115
|
+
stroke="currentColor"
|
|
116
|
+
strokeWidth={2}
|
|
117
|
+
>
|
|
118
|
+
<path
|
|
119
|
+
strokeLinecap="round"
|
|
120
|
+
strokeLinejoin="round"
|
|
121
|
+
d={visible ? 'M15 19l-7-7 7-7' : 'M9 5l7 7-7 7'}
|
|
122
|
+
/>
|
|
123
|
+
</svg>
|
|
124
|
+
</button>
|
|
125
|
+
);
|
|
126
|
+
}
|