@geenius/docs 0.1.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.md +53 -1
- package/package.json +96 -13
- package/packages/convex/dist/index.d.ts +503 -0
- package/packages/convex/dist/index.js +482 -0
- package/packages/convex/dist/index.js.map +1 -0
- package/packages/react/dist/index.d.ts +439 -0
- package/packages/react/dist/index.js +4954 -0
- package/packages/react/dist/index.js.map +1 -0
- package/packages/react-css/{src/styles.css → dist/index.css} +183 -223
- package/packages/react-css/dist/index.css.map +1 -0
- package/packages/react-css/dist/index.d.ts +443 -0
- package/packages/react-css/dist/index.js +5058 -0
- package/packages/react-css/dist/index.js.map +1 -0
- package/packages/shared/dist/index.d.ts +684 -0
- package/packages/shared/dist/index.js +788 -0
- package/packages/shared/dist/index.js.map +1 -0
- package/packages/solidjs/dist/index.d.ts +435 -0
- package/packages/solidjs/dist/index.js +4584 -0
- package/packages/solidjs/dist/index.js.map +1 -0
- package/packages/solidjs-css/{src/styles.css → dist/index.css} +183 -223
- package/packages/solidjs-css/dist/index.css.map +1 -0
- package/packages/solidjs-css/dist/index.d.ts +432 -0
- package/packages/solidjs-css/dist/index.js +4934 -0
- package/packages/solidjs-css/dist/index.js.map +1 -0
- package/.changeset/config.json +0 -11
- package/.github/CODEOWNERS +0 -1
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -16
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -11
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -10
- package/.github/dependabot.yml +0 -11
- package/.github/workflows/ci.yml +0 -23
- package/.github/workflows/release.yml +0 -29
- package/.nvmrc +0 -1
- package/.project/ACCOUNT.yaml +0 -4
- package/.project/IDEAS.yaml +0 -7
- package/.project/PROJECT.yaml +0 -11
- package/.project/ROADMAP.yaml +0 -15
- package/CODE_OF_CONDUCT.md +0 -16
- package/CONTRIBUTING.md +0 -26
- package/SECURITY.md +0 -15
- package/SUPPORT.md +0 -8
- package/packages/convex/README.md +0 -1
- package/packages/convex/package.json +0 -12
- package/packages/convex/src/convex.config.ts +0 -3
- package/packages/convex/src/index.ts +0 -3
- package/packages/convex/src/mutations.ts +0 -270
- package/packages/convex/src/queries.ts +0 -175
- package/packages/convex/src/schema.ts +0 -55
- package/packages/react/README.md +0 -1
- package/packages/react/package.json +0 -36
- package/packages/react/src/DocsLayout.tsx +0 -116
- package/packages/react/src/DocsProvider.tsx +0 -93
- package/packages/react/src/RouterDocsContent.tsx +0 -148
- package/packages/react/src/RouterDocsLayout.tsx +0 -161
- package/packages/react/src/components/Breadcrumbs.tsx +0 -34
- package/packages/react/src/components/DocPage.tsx +0 -191
- package/packages/react/src/components/DocSearch.tsx +0 -140
- package/packages/react/src/components/DocSidebar.tsx +0 -86
- package/packages/react/src/components/DocsLayout.tsx +0 -62
- package/packages/react/src/components/EditButton.tsx +0 -26
- package/packages/react/src/components/PageNavigation.tsx +0 -45
- package/packages/react/src/components/TableOfContents.tsx +0 -46
- package/packages/react/src/components/VersionSelector.tsx +0 -60
- package/packages/react/src/components/index.ts +0 -9
- package/packages/react/src/hooks/index.ts +0 -8
- package/packages/react/src/hooks/useDocSearch.ts +0 -55
- package/packages/react/src/hooks/useDocs.ts +0 -57
- package/packages/react/src/hooks/useDocsAdmin.ts +0 -151
- package/packages/react/src/hooks/useTableOfContents.ts +0 -66
- package/packages/react/src/index.ts +0 -38
- package/packages/react/src/pages/DocSearchPage.tsx +0 -129
- package/packages/react/src/pages/DocViewPage.tsx +0 -158
- package/packages/react/src/pages/DocsAdminPage.tsx +0 -330
- package/packages/react/src/pages/DocsIndexPage.tsx +0 -172
- package/packages/react/src/pages/index.ts +0 -4
- package/packages/react/src/useDocs.ts +0 -58
- package/packages/react/tsup.config.ts +0 -12
- package/packages/react-css/README.md +0 -1
- package/packages/react-css/package.json +0 -37
- package/packages/react-css/src/DocsLayout.tsx +0 -117
- package/packages/react-css/src/DocsProvider.tsx +0 -93
- package/packages/react-css/src/RouterDocsContent.tsx +0 -60
- package/packages/react-css/src/RouterDocsLayout.tsx +0 -101
- package/packages/react-css/src/components/DocPage.tsx +0 -21
- package/packages/react-css/src/components/DocSearch.tsx +0 -55
- package/packages/react-css/src/components/DocSidebar.tsx +0 -56
- package/packages/react-css/src/components/DocsLayout.tsx +0 -28
- package/packages/react-css/src/components/common.tsx +0 -93
- package/packages/react-css/src/components/index.ts +0 -5
- package/packages/react-css/src/hooks/index.ts +0 -2
- package/packages/react-css/src/index.ts +0 -6
- package/packages/react-css/src/index.tsx +0 -3
- package/packages/react-css/src/pages/DocViewPage.tsx +0 -78
- package/packages/react-css/src/pages/DocsAdminPage.tsx +0 -101
- package/packages/react-css/src/pages/DocsIndexPage.tsx +0 -68
- package/packages/react-css/src/pages/index.ts +0 -3
- package/packages/react-css/src/useDocs.ts +0 -58
- package/packages/react-css/tsconfig.json +0 -19
- package/packages/react-css/tsup.config.ts +0 -10
- package/packages/shared/README.md +0 -1
- package/packages/shared/package.json +0 -31
- package/packages/shared/src/__tests__/docs.test.ts +0 -69
- package/packages/shared/src/config.ts +0 -80
- package/packages/shared/src/index.ts +0 -179
- package/packages/shared/src/providers/astro.ts +0 -94
- package/packages/shared/src/providers/fumadocs.ts +0 -116
- package/packages/shared/src/providers/internal.ts +0 -80
- package/packages/shared/src/types.ts +0 -73
- package/packages/shared/tsconfig.json +0 -18
- package/packages/shared/tsup.config.ts +0 -12
- package/packages/shared/vitest.config.ts +0 -4
- package/packages/solidjs/README.md +0 -1
- package/packages/solidjs/package.json +0 -33
- package/packages/solidjs/src/DocsLayout.tsx +0 -87
- package/packages/solidjs/src/DocsProvider.tsx +0 -95
- package/packages/solidjs/src/RouterDocsContent.tsx +0 -147
- package/packages/solidjs/src/RouterDocsLayout.tsx +0 -161
- package/packages/solidjs/src/components/Breadcrumbs.tsx +0 -27
- package/packages/solidjs/src/components/DocPage.tsx +0 -110
- package/packages/solidjs/src/components/DocSearch.tsx +0 -81
- package/packages/solidjs/src/components/DocSidebar.tsx +0 -92
- package/packages/solidjs/src/components/DocsLayout.tsx +0 -38
- package/packages/solidjs/src/components/EditButton.tsx +0 -15
- package/packages/solidjs/src/components/PageNavigation.tsx +0 -31
- package/packages/solidjs/src/components/TableOfContents.tsx +0 -41
- package/packages/solidjs/src/components/VersionSelector.tsx +0 -30
- package/packages/solidjs/src/components/index.ts +0 -9
- package/packages/solidjs/src/createDocs.ts +0 -62
- package/packages/solidjs/src/index.ts +0 -28
- package/packages/solidjs/src/pages/DocSearchPage.tsx +0 -72
- package/packages/solidjs/src/pages/DocViewPage.tsx +0 -80
- package/packages/solidjs/src/pages/DocsAdminPage.tsx +0 -123
- package/packages/solidjs/src/pages/DocsIndexPage.tsx +0 -85
- package/packages/solidjs/src/pages/index.ts +0 -4
- package/packages/solidjs/src/primitives/createDocSearch.ts +0 -42
- package/packages/solidjs/src/primitives/createDocs.ts +0 -35
- package/packages/solidjs/src/primitives/createDocsAdmin.ts +0 -63
- package/packages/solidjs/src/primitives/createTableOfContents.ts +0 -51
- package/packages/solidjs/src/primitives/index.ts +0 -4
- package/packages/solidjs/tsup.config.ts +0 -12
- package/packages/solidjs-css/README.md +0 -1
- package/packages/solidjs-css/package.json +0 -36
- package/packages/solidjs-css/src/DocsLayout.tsx +0 -106
- package/packages/solidjs-css/src/DocsProvider.tsx +0 -95
- package/packages/solidjs-css/src/RouterDocsContent.tsx +0 -54
- package/packages/solidjs-css/src/RouterDocsLayout.tsx +0 -104
- package/packages/solidjs-css/src/createDocs.ts +0 -62
- package/packages/solidjs-css/src/index.ts +0 -7
- package/packages/solidjs-css/src/index.tsx +0 -17
- package/packages/solidjs-css/src/pages/DocViewPage.tsx +0 -111
- package/packages/solidjs-css/src/pages/DocsAdminPage.tsx +0 -332
- package/packages/solidjs-css/src/pages/DocsIndexPage.tsx +0 -116
- package/packages/solidjs-css/src/pages/index.ts +0 -3
- package/packages/solidjs-css/src/primitives/index.ts +0 -1
- package/packages/solidjs-css/tsconfig.json +0 -20
- package/packages/solidjs-css/tsup.config.ts +0 -10
- package/pnpm-workspace.yaml +0 -2
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import type { DocPage as DocPageType } from '@geenius-docs/shared'
|
|
3
|
-
import { slugify } from '@geenius-docs/shared'
|
|
4
|
-
|
|
5
|
-
export function DocPage({ page }: { page: DocPageType }) {
|
|
6
|
-
const lines = page.content.split('\n'); const parts: string[] = []; let i = 0, inCode = false, codeLang = '', codeLines: string[] = []
|
|
7
|
-
const esc = (t: string) => t.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')
|
|
8
|
-
|
|
9
|
-
while (i < lines.length) {
|
|
10
|
-
const line = lines[i]
|
|
11
|
-
if (line.startsWith('```')) { if (!inCode) { inCode=true; codeLang=line.slice(3).trim(); codeLines=[] } else { parts.push(`<div style="margin:1rem 0"><div style="display:flex;justify-content:space-between;background:oklch(1 0 0/0.05);padding:0.5rem 1rem;border-radius:var(--docs-radius) var(--docs-radius) 0 0;font-size:0.75rem;color:oklch(1 0 0/0.5)">${codeLang||'code'}</div><pre style="overflow-x:auto;background:var(--docs-code-bg);padding:1rem;border-radius:0 0 var(--docs-radius) var(--docs-radius);font-size:0.875rem;line-height:1.65"><code>${esc(codeLines.join('\n'))}</code></pre></div>`); inCode=false }; i++; continue }
|
|
12
|
-
if (inCode) { codeLines.push(line); i++; continue }
|
|
13
|
-
const hm = line.match(/^(#{1,4})\s+(.+)$/); if (hm) { const lvl=hm[1].length,text=hm[2],id=slugify(text); parts.push(`<h${lvl} id="${id}" style="scroll-margin-top:5rem"><a href="#${id}">${esc(text)}</a></h${lvl}>`); i++; continue }
|
|
14
|
-
if (line.startsWith('>')) { const ql:string[]=[]; while(i<lines.length&&lines[i].startsWith('>')){ql.push(lines[i].replace(/^>\s?/,'')); i++}; parts.push(`<blockquote>${esc(ql.join(' '))}</blockquote>`); continue }
|
|
15
|
-
if (!line.trim()) { i++; continue }
|
|
16
|
-
const fmt=esc(line).replace(/\*\*(.+?)\*\*/g,'<strong>$1</strong>').replace(/\*(.+?)\*/g,'<em>$1</em>').replace(/`(.+?)`/g,'<code>$1</code>').replace(/\[(.+?)\]\((.+?)\)/g,'<a href="$2">$1</a>')
|
|
17
|
-
parts.push(`<p>${fmt}</p>`); i++
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return <article className="docs__content-main" dangerouslySetInnerHTML={{ __html: parts.join('') }} />
|
|
21
|
-
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect, useCallback, useRef } from 'react'
|
|
2
|
-
import type { SearchResult } from '@geenius-docs/shared'
|
|
3
|
-
import { highlightMatch } from '@geenius-docs/shared'
|
|
4
|
-
|
|
5
|
-
interface DocSearchProps {
|
|
6
|
-
results: SearchResult[]; query: string; onQuery: (q: string) => void
|
|
7
|
-
onSelect: (result: SearchResult) => void; isOpen: boolean; onClose: () => void
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function DocSearch({ results, query, onQuery, onSelect, isOpen, onClose }: DocSearchProps) {
|
|
11
|
-
const [activeIndex, setActiveIndex] = useState(0)
|
|
12
|
-
const inputRef = useRef<HTMLInputElement>(null)
|
|
13
|
-
useEffect(() => { if (isOpen) { inputRef.current?.focus(); setActiveIndex(0) } }, [isOpen])
|
|
14
|
-
useEffect(() => { setActiveIndex(0) }, [results])
|
|
15
|
-
|
|
16
|
-
const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
|
|
17
|
-
if (e.key === 'ArrowDown') { e.preventDefault(); setActiveIndex(i => Math.min(i + 1, results.length - 1)) }
|
|
18
|
-
else if (e.key === 'ArrowUp') { e.preventDefault(); setActiveIndex(i => Math.max(i - 1, 0)) }
|
|
19
|
-
else if (e.key === 'Enter' && results[activeIndex]) { e.preventDefault(); onSelect(results[activeIndex]) }
|
|
20
|
-
else if (e.key === 'Escape') { e.preventDefault(); onClose() }
|
|
21
|
-
}, [results, activeIndex, onSelect, onClose])
|
|
22
|
-
|
|
23
|
-
if (!isOpen) return null
|
|
24
|
-
|
|
25
|
-
return (
|
|
26
|
-
<div className="docs__search-modal" onClick={onClose}>
|
|
27
|
-
<div className="docs__search-backdrop" />
|
|
28
|
-
<div className="docs__search-panel" onClick={e => e.stopPropagation()}>
|
|
29
|
-
<div className="docs__search-input-wrapper">
|
|
30
|
-
<svg style={{ width: 20, height: 20, flexShrink: 0, color: 'oklch(1 0 0/0.3)' }} viewBox="0 0 20 20" fill="currentColor">
|
|
31
|
-
<path fillRule="evenodd" d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z" clipRule="evenodd" />
|
|
32
|
-
</svg>
|
|
33
|
-
<input ref={inputRef} type="text" value={query} onChange={e => onQuery(e.target.value)} onKeyDown={handleKeyDown}
|
|
34
|
-
placeholder="Search documentation…" className="docs__search-input" />
|
|
35
|
-
<kbd style={{ padding: '2px 6px', border: '1px solid oklch(1 0 0/0.1)', borderRadius: 4, background: 'oklch(1 0 0/0.03)', fontSize: 11, color: 'oklch(1 0 0/0.3)' }}>ESC</kbd>
|
|
36
|
-
</div>
|
|
37
|
-
<div className="docs__search-results">
|
|
38
|
-
{results.length === 0 && query.trim() && <div className="docs__search-empty">No results for "{query}"</div>}
|
|
39
|
-
{results.length === 0 && !query.trim() && <div className="docs__search-empty">Start typing to search…</div>}
|
|
40
|
-
{results.map((result, idx) => (
|
|
41
|
-
<button key={result.pageId} type="button" onClick={() => onSelect(result)}
|
|
42
|
-
className={`docs__search-result ${idx === activeIndex ? 'docs__search-result--active' : ''}`}>
|
|
43
|
-
<div className="docs__search-result-header">
|
|
44
|
-
<span className="docs__search-result-badge">{result.sectionTitle}</span>
|
|
45
|
-
<span className="docs__search-result-title">{result.pageTitle}</span>
|
|
46
|
-
</div>
|
|
47
|
-
<p className="docs__search-result-snippet">{highlightMatch(result.highlight, query)}</p>
|
|
48
|
-
</button>
|
|
49
|
-
))}
|
|
50
|
-
</div>
|
|
51
|
-
<div className="docs__search-footer"><span>↑↓ navigate</span><span>↵ select</span><span>esc close</span></div>
|
|
52
|
-
</div>
|
|
53
|
-
</div>
|
|
54
|
-
)
|
|
55
|
-
}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import React, { useState, useCallback } from 'react'
|
|
2
|
-
import type { DocPage, DocSection } from '@geenius-docs/shared'
|
|
3
|
-
import './styles.css'
|
|
4
|
-
|
|
5
|
-
interface DocSidebarProps {
|
|
6
|
-
sections: (DocSection & { pages?: DocPage[]; pageCount?: number })[]
|
|
7
|
-
currentPageId?: string
|
|
8
|
-
onNavigate: (page: DocPage, section: DocSection) => void
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function DocSidebar({ sections, currentPageId, onNavigate }: DocSidebarProps) {
|
|
12
|
-
const [expandedIds, setExpandedIds] = useState<Set<string>>(new Set())
|
|
13
|
-
const toggle = useCallback((id: string) => {
|
|
14
|
-
setExpandedIds(prev => { const n = new Set(prev); if (n.has(id)) n.delete(id); else n.add(id); return n })
|
|
15
|
-
}, [])
|
|
16
|
-
|
|
17
|
-
const topLevel = sections.filter(s => !s.parentId)
|
|
18
|
-
const childrenOf = (parentId: string) => sections.filter(s => s.parentId === parentId)
|
|
19
|
-
|
|
20
|
-
const renderSection = (section: DocSection & { pages?: DocPage[]; pageCount?: number }, depth: number) => {
|
|
21
|
-
const children = childrenOf(section.id)
|
|
22
|
-
const isExpanded = expandedIds.has(section.id)
|
|
23
|
-
const pages = section.pages ?? []
|
|
24
|
-
|
|
25
|
-
return (
|
|
26
|
-
<div key={section.id} className={`docs__sidebar-section ${isExpanded ? 'docs__sidebar-section--active' : ''}`} style={{ paddingLeft: depth * 12 }}>
|
|
27
|
-
<button type="button" className="docs__sidebar-section-btn" onClick={() => toggle(section.id)}>
|
|
28
|
-
{section.icon && <span className="docs__sidebar-section-icon">{section.icon}</span>}
|
|
29
|
-
<span className="docs__sidebar-section-title">{section.title}</span>
|
|
30
|
-
{section.pageCount != null && <span className="docs__sidebar-section-count">{section.pageCount}</span>}
|
|
31
|
-
<svg className={`docs__sidebar-section-chevron ${isExpanded ? 'docs__sidebar-section-chevron--open' : ''}`} viewBox="0 0 16 16">
|
|
32
|
-
<path d="M6 4l4 4-4 4" stroke="currentColor" strokeWidth="2" fill="none" />
|
|
33
|
-
</svg>
|
|
34
|
-
</button>
|
|
35
|
-
{isExpanded && (
|
|
36
|
-
<div className="docs__sidebar-pages">
|
|
37
|
-
{pages.map(page => (
|
|
38
|
-
<button key={page.id} type="button" onClick={() => onNavigate(page, section)}
|
|
39
|
-
className={`docs__sidebar-page ${currentPageId === page.id ? 'docs__sidebar-page--active' : ''}`}>
|
|
40
|
-
{page.title}
|
|
41
|
-
</button>
|
|
42
|
-
))}
|
|
43
|
-
{children.map(child => renderSection(child, depth + 1))}
|
|
44
|
-
</div>
|
|
45
|
-
)}
|
|
46
|
-
</div>
|
|
47
|
-
)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return (
|
|
51
|
-
<nav className="docs__sidebar">
|
|
52
|
-
<div className="docs__sidebar-header">Documentation</div>
|
|
53
|
-
{topLevel.map(s => renderSection(s, 0))}
|
|
54
|
-
</nav>
|
|
55
|
-
)
|
|
56
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import type { DocPage, DocSection, TocItem, BreadcrumbItem } from '@geenius-docs/shared'
|
|
3
|
-
import { DocSidebar } from './DocSidebar'
|
|
4
|
-
import { TableOfContents, Breadcrumbs } from './common'
|
|
5
|
-
|
|
6
|
-
interface DocsLayoutProps {
|
|
7
|
-
sections: (DocSection & { pages?: DocPage[] })[]
|
|
8
|
-
toc?: TocItem[]; activeHeadingId?: string; breadcrumbs?: BreadcrumbItem[]
|
|
9
|
-
currentPageId?: string; onNavigate: (page: DocPage, section: DocSection) => void
|
|
10
|
-
children: React.ReactNode
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function DocsLayout({ sections, toc, activeHeadingId, breadcrumbs, currentPageId, onNavigate, children }: DocsLayoutProps) {
|
|
14
|
-
return (
|
|
15
|
-
<div className="docs__layout">
|
|
16
|
-
<DocSidebar sections={sections} currentPageId={currentPageId} onNavigate={onNavigate} />
|
|
17
|
-
<div className="docs__content">
|
|
18
|
-
<div className="docs__content-inner">
|
|
19
|
-
<div className="docs__content-main">
|
|
20
|
-
{breadcrumbs && breadcrumbs.length > 0 && <Breadcrumbs items={breadcrumbs} />}
|
|
21
|
-
{children}
|
|
22
|
-
</div>
|
|
23
|
-
{toc && toc.length > 0 && <TableOfContents toc={toc} activeId={activeHeadingId} />}
|
|
24
|
-
</div>
|
|
25
|
-
</div>
|
|
26
|
-
</div>
|
|
27
|
-
)
|
|
28
|
-
}
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import type { TocItem, BreadcrumbItem } from '@geenius-docs/shared'
|
|
3
|
-
|
|
4
|
-
export function TableOfContents({ toc, activeId }: { toc: TocItem[]; activeId?: string }) {
|
|
5
|
-
if (toc.length === 0) return null
|
|
6
|
-
return (
|
|
7
|
-
<nav className="docs__toc">
|
|
8
|
-
<h4 className="docs__toc-heading">On this page</h4>
|
|
9
|
-
<div className="docs__toc-list">
|
|
10
|
-
{toc.map(item => (
|
|
11
|
-
<React.Fragment key={item.id}>
|
|
12
|
-
<a href={`#${item.id}`} className={`docs__toc-item ${activeId === item.id ? 'docs__toc-item--active' : ''}`}>{item.text}</a>
|
|
13
|
-
{item.children.map(child => (
|
|
14
|
-
<a key={child.id} href={`#${child.id}`} className={`docs__toc-item docs__toc-item--h3 ${activeId === child.id ? 'docs__toc-item--active' : ''}`}>{child.text}</a>
|
|
15
|
-
))}
|
|
16
|
-
</React.Fragment>
|
|
17
|
-
))}
|
|
18
|
-
</div>
|
|
19
|
-
</nav>
|
|
20
|
-
)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function Breadcrumbs({ items }: { items: BreadcrumbItem[] }) {
|
|
24
|
-
if (items.length === 0) return null
|
|
25
|
-
return (
|
|
26
|
-
<nav className="docs__breadcrumbs" aria-label="Breadcrumb">
|
|
27
|
-
{items.map((item, idx) => (
|
|
28
|
-
<React.Fragment key={idx}>
|
|
29
|
-
{idx > 0 && (
|
|
30
|
-
<svg className="docs__breadcrumb-separator" viewBox="0 0 16 16" fill="none"><path d="M6 4l4 4-4 4" stroke="currentColor" strokeWidth="1.5" /></svg>
|
|
31
|
-
)}
|
|
32
|
-
{idx === items.length - 1
|
|
33
|
-
? <span className="docs__breadcrumb-item docs__breadcrumb-item--current">{item.title}</span>
|
|
34
|
-
: <a href={item.href} className="docs__breadcrumb-item">{item.title}</a>
|
|
35
|
-
}
|
|
36
|
-
</React.Fragment>
|
|
37
|
-
))}
|
|
38
|
-
</nav>
|
|
39
|
-
)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function PageNavigation({ prev, next }: { prev?: { title: string; href: string }; next?: { title: string; href: string } }) {
|
|
43
|
-
if (!prev && !next) return null
|
|
44
|
-
return (
|
|
45
|
-
<div className="docs__page-nav">
|
|
46
|
-
{prev ? (
|
|
47
|
-
<a href={prev.href} className="docs__page-nav-prev">
|
|
48
|
-
<span className="docs__page-nav-label">← Previous</span>
|
|
49
|
-
<span className="docs__page-nav-title">{prev.title}</span>
|
|
50
|
-
</a>
|
|
51
|
-
) : <div style={{ flex: 1 }} />}
|
|
52
|
-
{next ? (
|
|
53
|
-
<a href={next.href} className="docs__page-nav-next">
|
|
54
|
-
<span className="docs__page-nav-label">Next →</span>
|
|
55
|
-
<span className="docs__page-nav-title">{next.title}</span>
|
|
56
|
-
</a>
|
|
57
|
-
) : <div style={{ flex: 1 }} />}
|
|
58
|
-
</div>
|
|
59
|
-
)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function EditButton({ pageSlug, editUrl }: { pageSlug: string; editUrl?: string }) {
|
|
63
|
-
if (!editUrl) return null
|
|
64
|
-
return (
|
|
65
|
-
<a href={`${editUrl.replace(/\/$/, '')}/${pageSlug}.mdx`} target="_blank" rel="noopener noreferrer" className="docs__edit-btn">
|
|
66
|
-
<svg className="docs__edit-btn-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor"><path d="M11.5 1.5l3 3-9 9H2.5v-3l9-9z" strokeWidth="1.5" strokeLinejoin="round" /></svg>
|
|
67
|
-
Edit this page
|
|
68
|
-
</a>
|
|
69
|
-
)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export function VersionSelector({ versions, current, onSelect }: { versions: string[]; current: string; onSelect: (v: string) => void }) {
|
|
73
|
-
const [isOpen, setIsOpen] = React.useState(false)
|
|
74
|
-
if (versions.length <= 1) return null
|
|
75
|
-
return (
|
|
76
|
-
<div className="docs__version-selector">
|
|
77
|
-
<button type="button" className="docs__version-btn" onClick={() => setIsOpen(!isOpen)}>
|
|
78
|
-
v{current}
|
|
79
|
-
<svg style={{ width: 12, height: 12, transform: isOpen ? 'rotate(180deg)' : '', transition: 'transform 0.2s' }} viewBox="0 0 16 16"><path d="M4 6l4 4 4-4" stroke="currentColor" strokeWidth="2" fill="none" /></svg>
|
|
80
|
-
</button>
|
|
81
|
-
{isOpen && (
|
|
82
|
-
<div className="docs__version-dropdown">
|
|
83
|
-
{versions.map(v => (
|
|
84
|
-
<button key={v} type="button" className={`docs__version-option ${v === current ? 'docs__version-option--active' : ''}`}
|
|
85
|
-
onClick={() => { onSelect(v); setIsOpen(false) }}>
|
|
86
|
-
v{v}{v === current && <span style={{ marginLeft: 'auto', fontSize: 10 }}>✓</span>}
|
|
87
|
-
</button>
|
|
88
|
-
))}
|
|
89
|
-
</div>
|
|
90
|
-
)}
|
|
91
|
-
</div>
|
|
92
|
-
)
|
|
93
|
-
}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
export { DocsProvider, useDocs } from './DocsProvider'
|
|
2
|
-
export type { DocsProviderProps } from './DocsProvider'
|
|
3
|
-
|
|
4
|
-
export { DocsLayout } from './DocsLayout'
|
|
5
|
-
export { RouterDocsLayout, type DocItem } from './RouterDocsLayout'
|
|
6
|
-
export { RouterDocsContent } from './RouterDocsContent'
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import React, { useEffect, useMemo } from 'react'
|
|
2
|
-
import type { DocPage as DocPageType, DocSection } from '@geenius-docs/shared'
|
|
3
|
-
import { buildBreadcrumbs } from '@geenius-docs/shared'
|
|
4
|
-
import { useDocs, useTableOfContents } from '../hooks'
|
|
5
|
-
import { DocsLayout } from '../components/DocsLayout'
|
|
6
|
-
import { DocPage } from '../components/DocPage'
|
|
7
|
-
import { EditButton, PageNavigation } from '../components/common'
|
|
8
|
-
import '../styles.css'
|
|
9
|
-
|
|
10
|
-
interface DocViewPageProps {
|
|
11
|
-
tree: (DocSection & { pages: DocPageType[]; pageCount: number })[] | undefined
|
|
12
|
-
page: DocPageType | null | undefined
|
|
13
|
-
editPageUrl?: string
|
|
14
|
-
onNavigate: (page: DocPageType, section: DocSection) => void
|
|
15
|
-
onIncrementView?: (pageId: string) => void
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function DocViewPage({ tree, page, editPageUrl, onNavigate, onIncrementView }: DocViewPageProps) {
|
|
19
|
-
const docs = useDocs(tree)
|
|
20
|
-
const { toc, activeId } = useTableOfContents(page?.content)
|
|
21
|
-
|
|
22
|
-
useEffect(() => { if (page?.id && onIncrementView) onIncrementView(page.id) }, [page?.id, onIncrementView])
|
|
23
|
-
|
|
24
|
-
const breadcrumbs = useMemo(() => page ? buildBreadcrumbs(docs.sections, page.sectionId, page.slug) : [], [docs.sections, page])
|
|
25
|
-
|
|
26
|
-
const { prev, next } = useMemo(() => {
|
|
27
|
-
if (!page) return { prev: undefined, next: undefined }
|
|
28
|
-
const all = docs.flatPages; const idx = all.findIndex(p => p.id === page.id); const sec = docs.sections.find(s => s.id === page.sectionId)
|
|
29
|
-
return {
|
|
30
|
-
prev: idx > 0 ? { title: all[idx - 1].title, href: `/docs/${sec?.slug ?? ''}/${all[idx - 1].slug}` } : undefined,
|
|
31
|
-
next: idx < all.length - 1 ? { title: all[idx + 1].title, href: `/docs/${sec?.slug ?? ''}/${all[idx + 1].slug}` } : undefined,
|
|
32
|
-
}
|
|
33
|
-
}, [page, docs.flatPages, docs.sections])
|
|
34
|
-
|
|
35
|
-
if (docs.isLoading || page === undefined) return (
|
|
36
|
-
<div className="docs__layout">
|
|
37
|
-
<div className="docs__sidebar" style={{ padding: '1rem' }}>
|
|
38
|
-
{Array.from({ length: 8 }).map((_, i) => <div key={i} className="docs__skeleton" style={{ height: 32, marginBottom: 8 }} />)}
|
|
39
|
-
</div>
|
|
40
|
-
<div className="docs__content" style={{ padding: '2rem' }}>
|
|
41
|
-
<div className="docs__skeleton" style={{ width: 384, height: 40, marginBottom: 16 }} />
|
|
42
|
-
{Array.from({ length: 10 }).map((_, i) => <div key={i} className="docs__skeleton" style={{ height: 16, marginBottom: 12, width: `${60 + Math.random() * 40}%` }} />)}
|
|
43
|
-
</div>
|
|
44
|
-
</div>
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
if (!page) return (
|
|
48
|
-
<div className="docs__empty" style={{ minHeight: '100vh', background: 'var(--docs-bg)' }}>
|
|
49
|
-
<div className="docs__empty-icon">🔍</div>
|
|
50
|
-
<h2 className="docs__empty-title">Page not found</h2>
|
|
51
|
-
</div>
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
return (
|
|
55
|
-
<DocsLayout sections={docs.sections} toc={toc} activeHeadingId={activeId} breadcrumbs={breadcrumbs} currentPageId={page.id} onNavigate={onNavigate}>
|
|
56
|
-
<div className="docs__page-header">
|
|
57
|
-
<h1 className="docs__page-title">{page.title}</h1>
|
|
58
|
-
<div className="docs__reading-meta">
|
|
59
|
-
<span className="docs__reading-meta-author">
|
|
60
|
-
{page.author.avatar
|
|
61
|
-
? <img src={page.author.avatar} alt="" className="docs__reading-meta-avatar" />
|
|
62
|
-
: <span className="docs__reading-meta-avatar-placeholder">{page.author.name[0]}</span>}
|
|
63
|
-
{page.author.name}
|
|
64
|
-
</span>
|
|
65
|
-
{page.readingTime > 0 && <span>{page.readingTime} min read</span>}
|
|
66
|
-
{page.viewCount > 0 && <span>{page.viewCount.toLocaleString()} views</span>}
|
|
67
|
-
</div>
|
|
68
|
-
{page.tags.length > 0 && <div className="docs__tags">{page.tags.map(t => <span key={t} className="docs__tag">{t}</span>)}</div>}
|
|
69
|
-
</div>
|
|
70
|
-
<DocPage page={page} />
|
|
71
|
-
<div style={{ marginTop: '2.5rem', paddingTop: '1.5rem', borderTop: '1px solid var(--docs-border)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
72
|
-
<EditButton pageSlug={page.slug} editUrl={editPageUrl} />
|
|
73
|
-
{page.version && <span style={{ fontSize: '0.6875rem', color: 'oklch(1 0 0/0.25)' }}>v{page.version}</span>}
|
|
74
|
-
</div>
|
|
75
|
-
<PageNavigation prev={prev} next={next} />
|
|
76
|
-
</DocsLayout>
|
|
77
|
-
)
|
|
78
|
-
}
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import React, { useState, useMemo } from 'react'
|
|
2
|
-
import type { DocPage, DocSection } from '@geenius-docs/shared'
|
|
3
|
-
import type { DocsAdminActions } from '@geenius-docs/react'
|
|
4
|
-
import '../styles.css'
|
|
5
|
-
|
|
6
|
-
interface DocsAdminPageProps {
|
|
7
|
-
tree: (DocSection & { pages: DocPage[]; pageCount: number })[] | undefined
|
|
8
|
-
allPages?: DocPage[]
|
|
9
|
-
admin: DocsAdminActions
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function DocsAdminPage({ tree, allPages, admin }: DocsAdminPageProps) {
|
|
13
|
-
const sections = useMemo(() => tree ?? [], [tree])
|
|
14
|
-
const [selectedId, setSelectedId] = useState<string | null>(null)
|
|
15
|
-
const [showSF, setShowSF] = useState(false)
|
|
16
|
-
const [showPF, setShowPF] = useState(false)
|
|
17
|
-
const [sf, setSf] = useState({ title: '', slug: '', description: '', icon: '', access: 'team' as const })
|
|
18
|
-
const [pf, setPf] = useState({ title: '', slug: '', content: '', access: 'team' as const, tags: '' })
|
|
19
|
-
const selected = sections.find(s => s.id === selectedId)
|
|
20
|
-
const pages = useMemo(() => { if (!selected) return []; if (allPages) return allPages.filter(p => p.sectionId === selectedId); return selected.pages ?? [] }, [selected, allPages, selectedId])
|
|
21
|
-
|
|
22
|
-
if (tree === undefined) return (
|
|
23
|
-
<div style={{ minHeight: '100vh', background: 'var(--docs-bg)', padding: '3rem 1.5rem' }}>
|
|
24
|
-
<div style={{ maxWidth: '72rem', margin: '0 auto' }}>
|
|
25
|
-
<div className="docs__skeleton" style={{ width: 192, height: 40, marginBottom: 32 }} />
|
|
26
|
-
<div className="docs__admin-grid"><div className="docs__skeleton" style={{ height: 384 }} /><div className="docs__skeleton" style={{ height: 384 }} /></div>
|
|
27
|
-
</div>
|
|
28
|
-
</div>
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
return (
|
|
32
|
-
<div style={{ minHeight: '100vh', background: 'var(--docs-bg)', color: 'var(--docs-text)' }}>
|
|
33
|
-
<div style={{ maxWidth: '72rem', margin: '0 auto', padding: '3rem 1.5rem' }}>
|
|
34
|
-
<h1 style={{ fontSize: '1.5rem', fontWeight: 700, marginBottom: '2rem' }}>Docs Admin</h1>
|
|
35
|
-
<div className="docs__admin-grid">
|
|
36
|
-
<div className="docs__admin-panel">
|
|
37
|
-
<div className="docs__admin-panel-header">
|
|
38
|
-
<h2 className="docs__admin-panel-title">Sections</h2>
|
|
39
|
-
<button type="button" className="docs__admin-add-btn" onClick={() => setShowSF(!showSF)}>+ Add</button>
|
|
40
|
-
</div>
|
|
41
|
-
{showSF && (
|
|
42
|
-
<div className="docs__admin-form">
|
|
43
|
-
<input className="docs__admin-input" placeholder="Title" value={sf.title} onChange={e => setSf({...sf, title: e.target.value})} />
|
|
44
|
-
<input className="docs__admin-input" placeholder="Slug" value={sf.slug} onChange={e => setSf({...sf, slug: e.target.value})} />
|
|
45
|
-
<input className="docs__admin-input" placeholder="Icon" value={sf.icon} onChange={e => setSf({...sf, icon: e.target.value})} />
|
|
46
|
-
<input className="docs__admin-input" placeholder="Description" value={sf.description} onChange={e => setSf({...sf, description: e.target.value})} />
|
|
47
|
-
<div className="docs__admin-form-actions">
|
|
48
|
-
<button type="button" className="docs__admin-add-btn" onClick={async () => { await admin.createSection({...sf, order: sections.length, icon: sf.icon||undefined, description: sf.description||undefined}); setSf({title:'',slug:'',description:'',icon:'',access:'team'}); setShowSF(false) }}>Create</button>
|
|
49
|
-
<button type="button" style={{padding:'0.375rem 0.75rem',border:'1px solid var(--docs-border)',borderRadius:'var(--docs-radius)',background:'transparent',color:'var(--docs-text)',fontSize:'0.6875rem',cursor:'pointer'}} onClick={() => setShowSF(false)}>Cancel</button>
|
|
50
|
-
</div>
|
|
51
|
-
</div>
|
|
52
|
-
)}
|
|
53
|
-
{sections.length === 0 && <p style={{padding:'2rem',textAlign:'center',fontSize:'0.875rem',color:'var(--docs-text-muted)'}}>No sections</p>}
|
|
54
|
-
{sections.map(s => (
|
|
55
|
-
<div key={s.id} className={`docs__admin-section-item ${selectedId === s.id ? 'docs__admin-section-item--selected' : ''}`} onClick={() => setSelectedId(s.id)}>
|
|
56
|
-
{s.icon && <span>{s.icon}</span>}
|
|
57
|
-
<span style={{flex:1,fontSize:'0.875rem',fontWeight:500,overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{s.title}</span>
|
|
58
|
-
<span style={{fontSize:'0.6875rem',opacity:0.4}}>{s.pageCount ?? 0}</span>
|
|
59
|
-
<button type="button" style={{background:'transparent',border:'none',color:'oklch(1 0 0/0.2)',cursor:'pointer',padding:4}} onClick={e => { e.stopPropagation(); if (confirm(`Delete "${s.title}"?`)) admin.deleteSection(s.id) }}>✕</button>
|
|
60
|
-
</div>
|
|
61
|
-
))}
|
|
62
|
-
</div>
|
|
63
|
-
<div className="docs__admin-panel">
|
|
64
|
-
<div className="docs__admin-panel-header">
|
|
65
|
-
<h2 className="docs__admin-panel-title">{selected ? `Pages — ${selected.title}` : 'Pages'}</h2>
|
|
66
|
-
{selected && <button type="button" className="docs__admin-add-btn" onClick={() => setShowPF(!showPF)}>+ Add</button>}
|
|
67
|
-
</div>
|
|
68
|
-
{!selected && <p style={{padding:'4rem',textAlign:'center',fontSize:'0.875rem',color:'var(--docs-text-muted)'}}>Select a section</p>}
|
|
69
|
-
{showPF && selected && (
|
|
70
|
-
<div className="docs__admin-form">
|
|
71
|
-
<input className="docs__admin-input" placeholder="Title" value={pf.title} onChange={e => setPf({...pf, title: e.target.value})} />
|
|
72
|
-
<input className="docs__admin-input" placeholder="Slug" value={pf.slug} onChange={e => setPf({...pf, slug: e.target.value})} />
|
|
73
|
-
<textarea className="docs__admin-input docs__admin-textarea" placeholder="Content (MDX)" value={pf.content} onChange={e => setPf({...pf, content: e.target.value})} />
|
|
74
|
-
<input className="docs__admin-input" placeholder="Tags (comma)" value={pf.tags} onChange={e => setPf({...pf, tags: e.target.value})} />
|
|
75
|
-
<div className="docs__admin-form-actions">
|
|
76
|
-
<button type="button" className="docs__admin-add-btn" onClick={async () => { await admin.createPage({title:pf.title,slug:pf.slug,content:pf.content,sectionId:selectedId!,access:pf.access,tags:pf.tags?pf.tags.split(',').map(t=>t.trim()):[],order:pages.length}); setPf({title:'',slug:'',content:'',access:'team',tags:''}); setShowPF(false) }}>Create</button>
|
|
77
|
-
<button type="button" style={{padding:'0.375rem 0.75rem',border:'1px solid var(--docs-border)',borderRadius:'var(--docs-radius)',background:'transparent',color:'var(--docs-text)',fontSize:'0.6875rem',cursor:'pointer'}} onClick={() => setShowPF(false)}>Cancel</button>
|
|
78
|
-
</div>
|
|
79
|
-
</div>
|
|
80
|
-
)}
|
|
81
|
-
{selected && pages.map(page => (
|
|
82
|
-
<div key={page.id} className="docs__admin-page-item">
|
|
83
|
-
<div className="docs__admin-page-info">
|
|
84
|
-
<p className="docs__admin-page-title">{page.title}</p>
|
|
85
|
-
<p className="docs__admin-page-slug">/{page.slug}</p>
|
|
86
|
-
</div>
|
|
87
|
-
<span className={`docs__status-badge docs__status-badge--${page.status}`}>{page.status}</span>
|
|
88
|
-
<div className="docs__admin-actions">
|
|
89
|
-
{page.status === 'draft' && <button type="button" className="docs__admin-action-btn docs__admin-action-btn--publish" onClick={() => admin.publishPage(page.id)}>Publish</button>}
|
|
90
|
-
{page.status === 'published' && <button type="button" className="docs__admin-action-btn docs__admin-action-btn--archive" onClick={() => admin.archivePage(page.id)}>Archive</button>}
|
|
91
|
-
<button type="button" className="docs__admin-action-btn docs__admin-action-btn--delete" onClick={() => { if (confirm(`Delete "${page.title}"?`)) admin.deletePage(page.id) }}>✕</button>
|
|
92
|
-
</div>
|
|
93
|
-
</div>
|
|
94
|
-
))}
|
|
95
|
-
{selected && pages.length === 0 && !showPF && <p style={{padding:'3rem',textAlign:'center',fontSize:'0.875rem',color:'var(--docs-text-muted)'}}>No pages</p>}
|
|
96
|
-
</div>
|
|
97
|
-
</div>
|
|
98
|
-
</div>
|
|
99
|
-
</div>
|
|
100
|
-
)
|
|
101
|
-
}
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect, useCallback } from 'react'
|
|
2
|
-
import type { DocPage, DocSection, SearchResult } from '@geenius-docs/shared'
|
|
3
|
-
import { buildDocsIndex, searchDocs } from '@geenius-docs/shared'
|
|
4
|
-
import { useDocs, useDocSearch } from '../hooks'
|
|
5
|
-
import { DocSearch } from '../components/DocSearch'
|
|
6
|
-
import '../styles.css'
|
|
7
|
-
|
|
8
|
-
interface DocsIndexPageProps {
|
|
9
|
-
tree: (DocSection & { pages: DocPage[]; pageCount: number })[] | undefined
|
|
10
|
-
onSelectPage?: (page: DocPage, section: DocSection) => void
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function DocsIndexPage({ tree, onSelectPage }: DocsIndexPageProps) {
|
|
14
|
-
const docs = useDocs(tree)
|
|
15
|
-
const [isSearchOpen, setIsSearchOpen] = useState(false)
|
|
16
|
-
const searchFn = useCallback((q: string): SearchResult[] => { const idx = buildDocsIndex(docs.flatPages, docs.sections); return searchDocs(q, idx) }, [docs.flatPages, docs.sections])
|
|
17
|
-
const search = useDocSearch(searchFn)
|
|
18
|
-
|
|
19
|
-
useEffect(() => { const h = (e: KeyboardEvent) => { if ((e.metaKey || e.ctrlKey) && e.key === 'k') { e.preventDefault(); setIsSearchOpen(true) } }; document.addEventListener('keydown', h); return () => document.removeEventListener('keydown', h) }, [])
|
|
20
|
-
|
|
21
|
-
if (docs.isLoading) return (
|
|
22
|
-
<div style={{ minHeight: '100vh', background: 'var(--docs-bg)', padding: '4rem 1.5rem' }}>
|
|
23
|
-
<div style={{ maxWidth: '64rem', margin: '0 auto' }}>
|
|
24
|
-
<div className="docs__skeleton" style={{ width: 256, height: 40, marginBottom: 40 }} />
|
|
25
|
-
<div className="docs__skeleton" style={{ maxWidth: 560, height: 48, marginBottom: 32 }} />
|
|
26
|
-
<div className="docs__section-grid">{Array.from({length: 6}).map((_,i) => <div key={i} className="docs__skeleton" style={{ height: 144 }} />)}</div>
|
|
27
|
-
</div>
|
|
28
|
-
</div>
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
if (docs.sections.length === 0) return (
|
|
32
|
-
<div className="docs__empty" style={{ minHeight: '100vh', background: 'var(--docs-bg)' }}>
|
|
33
|
-
<div className="docs__empty-icon">📚</div>
|
|
34
|
-
<h2 className="docs__empty-title">No documentation yet</h2>
|
|
35
|
-
<p className="docs__empty-desc">Create your first section and pages to get started.</p>
|
|
36
|
-
</div>
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
return (
|
|
40
|
-
<div style={{ minHeight: '100vh', background: 'var(--docs-bg)', color: 'var(--docs-text)' }}>
|
|
41
|
-
<div style={{ maxWidth: '64rem', margin: '0 auto', padding: '4rem 1.5rem' }}>
|
|
42
|
-
<h1 style={{ fontSize: '2.25rem', fontWeight: 700, letterSpacing: '-0.02em', marginBottom: '0.5rem' }}>Documentation</h1>
|
|
43
|
-
<p style={{ fontSize: '1.125rem', color: 'var(--docs-text-muted)', marginBottom: '2.5rem' }}>Browse guides, API references, and tutorials.</p>
|
|
44
|
-
|
|
45
|
-
<button type="button" className="docs__search-trigger" onClick={() => setIsSearchOpen(true)}>
|
|
46
|
-
<svg className="docs__search-trigger-icon" viewBox="0 0 20 20" fill="currentColor"><path fillRule="evenodd" d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z" clipRule="evenodd" /></svg>
|
|
47
|
-
<span className="docs__search-trigger-text">Search documentation…</span>
|
|
48
|
-
<kbd className="docs__search-trigger-kbd">⌘K</kbd>
|
|
49
|
-
</button>
|
|
50
|
-
|
|
51
|
-
<div className="docs__section-grid">
|
|
52
|
-
{docs.sections.filter(s => !s.parentId).map(section => (
|
|
53
|
-
<button key={section.id} type="button" className="docs__section-card"
|
|
54
|
-
onClick={() => { const fp = section.pages?.[0]; if (fp && onSelectPage) onSelectPage(fp, section) }}>
|
|
55
|
-
{section.icon && <div className="docs__section-card-icon">{section.icon}</div>}
|
|
56
|
-
<h3 className="docs__section-card-title">{section.title}</h3>
|
|
57
|
-
{section.description && <p className="docs__section-card-desc">{section.description}</p>}
|
|
58
|
-
<div className="docs__section-card-meta">{section.pageCount ?? 0} pages</div>
|
|
59
|
-
</button>
|
|
60
|
-
))}
|
|
61
|
-
</div>
|
|
62
|
-
</div>
|
|
63
|
-
<DocSearch results={search.results} query={search.query} onQuery={search.setQuery}
|
|
64
|
-
onSelect={r => { setIsSearchOpen(false); search.clearSearch(); const sec = docs.sections.find(s => s.slug === r.sectionSlug); const pg = docs.flatPages.find(p => p.id === r.pageId); if (pg && sec && onSelectPage) onSelectPage(pg, sec) }}
|
|
65
|
-
isOpen={isSearchOpen} onClose={() => setIsSearchOpen(false)} />
|
|
66
|
-
</div>
|
|
67
|
-
)
|
|
68
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { useState, useCallback, useMemo, useEffect } from 'react'
|
|
2
|
-
import type { DocsPage, DocsSidebar, DocsSearchResult, DocsProvider } from '@geenius-docs/shared'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* React hook for documentation rendering and navigation.
|
|
6
|
-
* Wraps a DocsProvider with React state management.
|
|
7
|
-
*/
|
|
8
|
-
export function useDocs(provider: DocsProvider) {
|
|
9
|
-
const [sidebar, setSidebar] = useState<DocsSidebar | null>(null)
|
|
10
|
-
const [currentPage, setCurrentPage] = useState<DocsPage | null>(null)
|
|
11
|
-
const [searchResults, setSearchResults] = useState<DocsSearchResult[]>([])
|
|
12
|
-
const [isLoading, setIsLoading] = useState(false)
|
|
13
|
-
const [searchQuery, setSearchQuery] = useState('')
|
|
14
|
-
|
|
15
|
-
// Load sidebar on mount
|
|
16
|
-
useEffect(() => {
|
|
17
|
-
provider.getSidebar().then(setSidebar)
|
|
18
|
-
}, [provider])
|
|
19
|
-
|
|
20
|
-
const loadPage = useCallback(async (slug: string) => {
|
|
21
|
-
setIsLoading(true)
|
|
22
|
-
try {
|
|
23
|
-
const page = await provider.getPage(slug)
|
|
24
|
-
setCurrentPage(page)
|
|
25
|
-
} finally {
|
|
26
|
-
setIsLoading(false)
|
|
27
|
-
}
|
|
28
|
-
}, [provider])
|
|
29
|
-
|
|
30
|
-
const search = useCallback(async (query: string) => {
|
|
31
|
-
setSearchQuery(query)
|
|
32
|
-
if (!query.trim()) {
|
|
33
|
-
setSearchResults([])
|
|
34
|
-
return
|
|
35
|
-
}
|
|
36
|
-
const results = await provider.search(query)
|
|
37
|
-
setSearchResults(results)
|
|
38
|
-
}, [provider])
|
|
39
|
-
|
|
40
|
-
const navigation = useMemo(() => {
|
|
41
|
-
if (!currentPage) return { prev: null, next: null }
|
|
42
|
-
return {
|
|
43
|
-
prev: currentPage.prev ?? null,
|
|
44
|
-
next: currentPage.next ?? null,
|
|
45
|
-
}
|
|
46
|
-
}, [currentPage])
|
|
47
|
-
|
|
48
|
-
return {
|
|
49
|
-
sidebar,
|
|
50
|
-
currentPage,
|
|
51
|
-
navigation,
|
|
52
|
-
isLoading,
|
|
53
|
-
searchResults,
|
|
54
|
-
searchQuery,
|
|
55
|
-
loadPage,
|
|
56
|
-
search,
|
|
57
|
-
}
|
|
58
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "../../tsconfig.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"outDir": "./dist",
|
|
5
|
-
"rootDir": "./src",
|
|
6
|
-
"jsx": "react-jsx",
|
|
7
|
-
"strict": true,
|
|
8
|
-
"skipLibCheck": true,
|
|
9
|
-
"forceConsistentCasingInFileNames": true,
|
|
10
|
-
"resolveJsonModule": true,
|
|
11
|
-
"isolatedModules": true,
|
|
12
|
-
"target": "ES2022",
|
|
13
|
-
"module": "ESNext",
|
|
14
|
-
"moduleResolution": "bundler"
|
|
15
|
-
},
|
|
16
|
-
"include": [
|
|
17
|
-
"src"
|
|
18
|
-
]
|
|
19
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
# ✦ @geenius-docs/shared\n\n> Framework-agnostic docs types, providers, and utilities\n\n---\n\n## Overview\nBuilt with Steve Jobs-level minimalism and Jony Ive-level craftsmanship, this package is designed to deliver unparalleled developer experience (DX) and rock-solid performance.\n\n## Installation\n\n```bash\npnpm add @geenius-docs/shared\n```\n\n## Usage\n\n```typescript\nimport { init } from '@geenius-docs/shared';\n\n// Initialize the module with absolute precision\ninit({\n mode: 'premium',\n});\n```\n\n## Architecture\n- **Zero-config**: It just works.\n- **Strictly Typed**: Fully written in TypeScript for flawless IntelliSense.\n- **Framework Agnostic**: seamlessly integrates into the Geenius ecosystem.\n\n---\n\n*Designed by Antigravity HQ*\n
|