UNPKG

@fjell/docs-template

Version:

Shared documentation template for Fjell projects

470 lines (458 loc) 16.7 kB
// src/components/DocsApp.tsx import { useEffect, useState } from "react"; // src/components/Navigation.tsx import { Fragment, jsx, jsxs } from "react/jsx-runtime"; var Navigation = ({ sections, currentSection, currentSubsection, onSectionChange, sidebarOpen, setSidebarOpen }) => { return /* @__PURE__ */ jsxs(Fragment, { children: [ /* @__PURE__ */ jsx( "button", { className: "mobile-menu-button", onClick: () => setSidebarOpen(!sidebarOpen), "aria-label": "Toggle navigation", children: /* @__PURE__ */ jsxs("div", { className: "hamburger", children: [ /* @__PURE__ */ jsx("span", {}), /* @__PURE__ */ jsx("span", {}), /* @__PURE__ */ jsx("span", {}) ] }) } ), /* @__PURE__ */ jsxs("div", { className: `sidebar ${sidebarOpen ? "sidebar-open" : ""}`, children: [ /* @__PURE__ */ jsx("nav", { className: "sidebar-nav", children: /* @__PURE__ */ jsx("ul", { className: "nav-sections", children: sections.map((section) => /* @__PURE__ */ jsxs("li", { children: [ /* @__PURE__ */ jsx( "button", { className: `nav-item ${currentSection === section.id && !currentSubsection ? "active" : ""}`, onClick: () => { onSectionChange(section.id); setSidebarOpen(false); }, children: /* @__PURE__ */ jsxs("div", { className: "nav-item-content", children: [ /* @__PURE__ */ jsx("span", { className: "nav-title", children: section.title }), /* @__PURE__ */ jsx("span", { className: "nav-subtitle", children: section.subtitle }) ] }) } ), section.subsections && currentSection === section.id && /* @__PURE__ */ jsx("ul", { className: "nav-subsections", children: section.subsections.map((subsection) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx( "button", { className: `nav-subitem ${currentSubsection === subsection.id ? "active" : ""}`, onClick: () => { onSectionChange(section.id, subsection.id); setSidebarOpen(false); }, children: /* @__PURE__ */ jsxs("div", { className: "nav-subitem-content", children: [ /* @__PURE__ */ jsx("span", { className: "nav-subtitle", children: subsection.title }), /* @__PURE__ */ jsx("span", { className: "nav-description", children: subsection.subtitle }) ] }) } ) }, subsection.id)) }) ] }, section.id)) }) }), /* @__PURE__ */ jsx("div", { className: "sidebar-footer", children: /* @__PURE__ */ jsx("div", { className: "footer-text", children: "Built with Fjell Documentation Template" }) }) ] }) ] }); }; // src/components/ContentRenderer.tsx import ReactMarkdown from "react-markdown"; import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; import { oneLight } from "react-syntax-highlighter/dist/esm/styles/prism"; import remarkGfm from "remark-gfm"; import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime"; var ContentRenderer = ({ content, sectionData, loading, config, onImageClick }) => { if (loading) { return /* @__PURE__ */ jsx2("main", { className: "main-content", children: /* @__PURE__ */ jsx2("div", { className: "content", children: /* @__PURE__ */ jsxs2("div", { className: "loading-spinner", children: [ /* @__PURE__ */ jsx2("div", { className: "spinner" }), /* @__PURE__ */ jsx2("p", { children: "Loading documentation..." }) ] }) }) }); } return /* @__PURE__ */ jsx2("main", { className: "main-content", children: /* @__PURE__ */ jsxs2("div", { className: "content", children: [ /* @__PURE__ */ jsx2("div", { className: "content-header", children: sectionData && /* @__PURE__ */ jsxs2(Fragment2, { children: [ /* @__PURE__ */ jsxs2("div", { className: "breadcrumb", children: [ /* @__PURE__ */ jsx2("span", { className: "breadcrumb-project", children: config.projectName }), /* @__PURE__ */ jsx2("span", { className: "breadcrumb-separator", children: "/" }), /* @__PURE__ */ jsx2("span", { className: "breadcrumb-section", children: sectionData.title }) ] }), /* @__PURE__ */ jsx2("h1", { className: "page-title", children: sectionData.title }), /* @__PURE__ */ jsx2("p", { className: "page-subtitle", children: sectionData.subtitle }) ] }) }), /* @__PURE__ */ jsx2("div", { className: "markdown-content", children: /* @__PURE__ */ jsx2( ReactMarkdown, { remarkPlugins: [remarkGfm], components: { code({ inline, className, children, ...props }) { const match = /language-(\w+)/.exec(className || ""); return !inline && match ? /* @__PURE__ */ jsx2( SyntaxHighlighter, { style: oneLight, language: match[1], PreTag: "div", ...props, children: String(children).replace(/\n$/, "") } ) : /* @__PURE__ */ jsx2("code", { className, ...props, children }); }, img({ src, alt, ...props }) { const imageSrc = src?.startsWith("/") ? src : `${config.basePath}${src || ""}`; return /* @__PURE__ */ jsx2( "img", { src: imageSrc, alt: alt || "", ...props, onClick: () => onImageClick(imageSrc), style: { cursor: "pointer" }, title: "Click to view fullscreen" } ); }, a({ href, children, ...props }) { const isExternal = href?.startsWith("http") || href?.startsWith("//"); const linkProps = { href, ...props, ...isExternal && { target: "_blank", rel: "noopener noreferrer" } }; return /* @__PURE__ */ jsx2("a", { ...linkProps, children }); } }, children: content } ) }) ] }) }); }; // src/components/Header.tsx import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime"; var Header = ({ config, version, sidebarOpen, setSidebarOpen }) => { const { projectName, branding } = config; const nameParts = projectName.split(" "); const brandMain = nameParts[0] || projectName; const brandSub = nameParts.slice(1).join(" "); return /* @__PURE__ */ jsx3("header", { className: "header", children: /* @__PURE__ */ jsxs3("div", { className: "header-container", children: [ /* @__PURE__ */ jsxs3("div", { className: "brand", children: [ /* @__PURE__ */ jsxs3("h1", { className: "brand-title", children: [ /* @__PURE__ */ jsx3("span", { className: "brand-fjell", children: brandMain }), brandSub && /* @__PURE__ */ jsx3("span", { className: "brand-default", children: brandSub }) ] }), /* @__PURE__ */ jsx3("p", { className: "brand-tagline", children: branding.tagline }) ] }), /* @__PURE__ */ jsxs3("div", { className: "header-actions", children: [ version && (branding.github ? /* @__PURE__ */ jsxs3( "a", { href: `${branding.github}/releases`, className: "version-badge", target: "_blank", rel: "noopener noreferrer", title: "View releases", children: [ "v", version ] } ) : /* @__PURE__ */ jsxs3("span", { className: "version-badge", title: "Version information", children: [ "v", version ] })), branding.github && /* @__PURE__ */ jsx3( "a", { href: branding.github, className: "header-link", target: "_blank", rel: "noopener noreferrer", children: "View Source" } ), branding.npm && /* @__PURE__ */ jsx3( "a", { href: branding.npm, className: "header-link", target: "_blank", rel: "noopener noreferrer", children: "Install Package" } ) ] }), /* @__PURE__ */ jsxs3( "button", { className: "menu-toggle", onClick: () => setSidebarOpen(!sidebarOpen), "aria-label": "Toggle navigation", children: [ /* @__PURE__ */ jsx3("span", { className: "menu-line" }), /* @__PURE__ */ jsx3("span", { className: "menu-line" }), /* @__PURE__ */ jsx3("span", { className: "menu-line" }) ] } ) ] }) }); }; // src/components/Layout.tsx import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime"; var Layout = ({ config, children, sidebarOpen, setSidebarOpen, fullscreenImage, setFullscreenImage, version }) => { return /* @__PURE__ */ jsxs4("div", { className: `docs-app ${config.branding.theme ? `brand-${config.branding.theme}` : ""}`, children: [ /* @__PURE__ */ jsx4("div", { className: "header-background" }), /* @__PURE__ */ jsx4( Header, { config, version, sidebarOpen, setSidebarOpen } ), /* @__PURE__ */ jsx4("div", { className: "layout", children }), fullscreenImage && /* @__PURE__ */ jsx4( "div", { className: "fullscreen-overlay", onClick: () => setFullscreenImage(null), children: /* @__PURE__ */ jsxs4("div", { className: "fullscreen-content", children: [ /* @__PURE__ */ jsx4( "img", { src: fullscreenImage, alt: "Fullscreen view", className: "fullscreen-image" } ), /* @__PURE__ */ jsx4( "button", { className: "close-fullscreen", onClick: () => setFullscreenImage(null), "aria-label": "Close fullscreen view", children: "\xD7" } ) ] }) } ), sidebarOpen && /* @__PURE__ */ jsx4( "div", { className: "sidebar-overlay", onClick: () => setSidebarOpen(false) } ) ] }); }; // src/utils/contentLoader.ts var loadDocument = async (url) => { const response = await fetch(url); if (!response.ok) { throw new Error(`Failed to fetch ${url}: ${response.status}`); } return response.text(); }; var processContent = (content) => { return content; }; var extractSection = (content, sectionTitle) => { const sections = content.split("##"); const targetSection = sections.find( (s) => s.trim().toLowerCase().startsWith(sectionTitle.toLowerCase()) ); if (targetSection) { return "##" + targetSection; } return content; }; var extractMultipleSections = (content, sectionTitles) => { const sections = content.split("##"); const matchingSections = sections.filter( (s) => sectionTitles.some( (title) => s.trim().toLowerCase().startsWith(title.toLowerCase()) ) ); if (matchingSections.length > 0) { return matchingSections.map((s) => "##" + s).join("\n\n"); } return content; }; var createGettingStartedContent = (content) => { const installSection = extractSection(content, "Installation"); const basicSection = extractSection(content, "Basic Usage"); const configSection = extractSection(content, "Configuration"); let result = "# Getting Started\n\n"; if (installSection !== content && installSection.trim()) result += installSection + "\n\n"; if (basicSection !== content && basicSection.trim()) result += basicSection + "\n\n"; if (configSection !== content && configSection.trim()) result += configSection + "\n\n"; return result; }; // src/components/DocsApp.tsx import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime"; var DocsApp = ({ config }) => { const [currentSection, setCurrentSection] = useState(config.sections[0]?.id || "overview"); const [currentSubsection, setCurrentSubsection] = useState(null); const [documents, setDocuments] = useState({}); const [loading, setLoading] = useState(true); const [sidebarOpen, setSidebarOpen] = useState(false); const [fullscreenImage, setFullscreenImage] = useState(null); const [version, setVersion] = useState(""); useEffect(() => { const handleRouting = () => { const hash = window.location.hash.slice(1); const [sectionId, subsectionId] = hash.split("/"); if (sectionId && config.sections.find((s) => s.id === sectionId)) { setCurrentSection(sectionId); setCurrentSubsection(subsectionId || null); } }; handleRouting(); window.addEventListener("hashchange", handleRouting); return () => window.removeEventListener("hashchange", handleRouting); }, [config]); useEffect(() => { const loadDocuments = async () => { const loadedDocs = {}; if (config.branding.backgroundImage) { document.documentElement.style.setProperty( "--brand-background-image", `url('${config.branding.backgroundImage}')` ); } if (config.version.source === "manual" && config.version.value) { setVersion(config.version.value); } else if (config.version.source === "env" && config.version.envVar) { setVersion(process.env[config.version.envVar] || "dev"); } else if (config.version.source === "package.json") { setVersion(window.__APP_VERSION__ || "1.0.0"); } else { setVersion(window.__APP_VERSION__ || "1.0.0"); } for (const section of config.sections) { if (section.file) { try { const content = await loadDocument(section.file); const processedContent = config.customContent?.[section.id] ? config.customContent[section.id](content) : content; loadedDocs[section.id] = processedContent; } catch (error) { console.error(`Error loading ${section.file}:`, error); loadedDocs[section.id] = getDefaultContent(section); } } else { loadedDocs[section.id] = getDefaultContent(section); } if (section.subsections) { for (const subsection of section.subsections) { const subsectionKey = `${section.id}/${subsection.id}`; try { const content = await loadDocument(subsection.file); loadedDocs[subsectionKey] = content; } catch (error) { console.error(`Error loading ${subsection.file}:`, error); loadedDocs[subsectionKey] = `# ${subsection.title} ${subsection.subtitle} Content coming soon...`; } } } } setDocuments(loadedDocs); setLoading(false); }; loadDocuments(); }, [config]); const getDefaultContent = (section) => { return `# ${section.title} ${section.subtitle} Content coming soon...`; }; const currentSectionData = config.sections.find((s) => s.id === currentSection); const currentContentKey = currentSubsection ? `${currentSection}/${currentSubsection}` : currentSection; const currentContent = documents[currentContentKey] || ""; const handleSectionChange = (sectionId, subsectionId) => { setCurrentSection(sectionId); setCurrentSubsection(subsectionId || null); const hash = subsectionId ? `${sectionId}/${subsectionId}` : sectionId; window.location.hash = hash; }; return /* @__PURE__ */ jsxs5( Layout, { config, sidebarOpen, setSidebarOpen, fullscreenImage, setFullscreenImage, version, children: [ /* @__PURE__ */ jsx5( Navigation, { sections: config.sections, currentSection, currentSubsection, onSectionChange: handleSectionChange, sidebarOpen, setSidebarOpen } ), /* @__PURE__ */ jsx5( ContentRenderer, { content: currentContent, sectionId: currentSection, subsectionId: currentSubsection, sectionData: currentSectionData, loading, config, onImageClick: setFullscreenImage } ) ] } ); }; // src/index.ts var renderDocsApp = (config) => { return DocsApp({ config }); }; export { ContentRenderer, DocsApp, Header, Layout, Navigation, createGettingStartedContent, extractMultipleSections, extractSection, loadDocument, processContent, renderDocsApp }; //# sourceMappingURL=index.js.map