@geenius/docs 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/.changeset/config.json +11 -0
- package/.github/CODEOWNERS +1 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +16 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +11 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +10 -0
- package/.github/dependabot.yml +11 -0
- package/.github/workflows/ci.yml +23 -0
- package/.github/workflows/release.yml +29 -0
- package/.nvmrc +1 -0
- package/.project/ACCOUNT.yaml +4 -0
- package/.project/IDEAS.yaml +7 -0
- package/.project/PROJECT.yaml +11 -0
- package/.project/ROADMAP.yaml +15 -0
- package/CHANGELOG.md +11 -0
- package/CODE_OF_CONDUCT.md +16 -0
- package/CONTRIBUTING.md +26 -0
- package/LICENSE +21 -0
- package/README.md +1 -0
- package/SECURITY.md +15 -0
- package/SUPPORT.md +8 -0
- package/package.json +58 -0
- package/packages/convex/README.md +1 -0
- package/packages/convex/package.json +12 -0
- package/packages/convex/src/convex.config.ts +3 -0
- package/packages/convex/src/index.ts +3 -0
- package/packages/convex/src/mutations.ts +270 -0
- package/packages/convex/src/queries.ts +175 -0
- package/packages/convex/src/schema.ts +55 -0
- package/packages/react/README.md +1 -0
- package/packages/react/package.json +36 -0
- package/packages/react/src/DocsLayout.tsx +116 -0
- package/packages/react/src/DocsProvider.tsx +93 -0
- package/packages/react/src/RouterDocsContent.tsx +148 -0
- package/packages/react/src/RouterDocsLayout.tsx +161 -0
- package/packages/react/src/components/Breadcrumbs.tsx +34 -0
- package/packages/react/src/components/DocPage.tsx +191 -0
- package/packages/react/src/components/DocSearch.tsx +140 -0
- package/packages/react/src/components/DocSidebar.tsx +86 -0
- package/packages/react/src/components/DocsLayout.tsx +62 -0
- package/packages/react/src/components/EditButton.tsx +26 -0
- package/packages/react/src/components/PageNavigation.tsx +45 -0
- package/packages/react/src/components/TableOfContents.tsx +46 -0
- package/packages/react/src/components/VersionSelector.tsx +60 -0
- package/packages/react/src/components/index.ts +9 -0
- package/packages/react/src/hooks/index.ts +8 -0
- package/packages/react/src/hooks/useDocSearch.ts +55 -0
- package/packages/react/src/hooks/useDocs.ts +57 -0
- package/packages/react/src/hooks/useDocsAdmin.ts +151 -0
- package/packages/react/src/hooks/useTableOfContents.ts +66 -0
- package/packages/react/src/index.ts +38 -0
- package/packages/react/src/pages/DocSearchPage.tsx +129 -0
- package/packages/react/src/pages/DocViewPage.tsx +158 -0
- package/packages/react/src/pages/DocsAdminPage.tsx +330 -0
- package/packages/react/src/pages/DocsIndexPage.tsx +172 -0
- package/packages/react/src/pages/index.ts +4 -0
- package/packages/react/src/useDocs.ts +58 -0
- package/packages/react/tsup.config.ts +12 -0
- package/packages/react-css/README.md +1 -0
- package/packages/react-css/package.json +37 -0
- package/packages/react-css/src/DocsLayout.tsx +117 -0
- package/packages/react-css/src/DocsProvider.tsx +93 -0
- package/packages/react-css/src/RouterDocsContent.tsx +60 -0
- package/packages/react-css/src/RouterDocsLayout.tsx +101 -0
- package/packages/react-css/src/components/DocPage.tsx +21 -0
- package/packages/react-css/src/components/DocSearch.tsx +55 -0
- package/packages/react-css/src/components/DocSidebar.tsx +56 -0
- package/packages/react-css/src/components/DocsLayout.tsx +28 -0
- package/packages/react-css/src/components/common.tsx +93 -0
- package/packages/react-css/src/components/index.ts +5 -0
- package/packages/react-css/src/hooks/index.ts +2 -0
- package/packages/react-css/src/index.ts +6 -0
- package/packages/react-css/src/index.tsx +3 -0
- package/packages/react-css/src/pages/DocViewPage.tsx +78 -0
- package/packages/react-css/src/pages/DocsAdminPage.tsx +101 -0
- package/packages/react-css/src/pages/DocsIndexPage.tsx +68 -0
- package/packages/react-css/src/pages/index.ts +3 -0
- package/packages/react-css/src/styles.css +1271 -0
- package/packages/react-css/src/useDocs.ts +58 -0
- package/packages/react-css/tsconfig.json +19 -0
- package/packages/react-css/tsup.config.ts +10 -0
- package/packages/shared/README.md +1 -0
- package/packages/shared/package.json +31 -0
- package/packages/shared/src/__tests__/docs.test.ts +69 -0
- package/packages/shared/src/config.ts +80 -0
- package/packages/shared/src/index.ts +179 -0
- package/packages/shared/src/providers/astro.ts +94 -0
- package/packages/shared/src/providers/fumadocs.ts +116 -0
- package/packages/shared/src/providers/internal.ts +80 -0
- package/packages/shared/src/types.ts +73 -0
- package/packages/shared/tsconfig.json +18 -0
- package/packages/shared/tsup.config.ts +12 -0
- package/packages/shared/vitest.config.ts +4 -0
- package/packages/solidjs/README.md +1 -0
- package/packages/solidjs/package.json +33 -0
- package/packages/solidjs/src/DocsLayout.tsx +87 -0
- package/packages/solidjs/src/DocsProvider.tsx +95 -0
- package/packages/solidjs/src/RouterDocsContent.tsx +147 -0
- package/packages/solidjs/src/RouterDocsLayout.tsx +161 -0
- package/packages/solidjs/src/components/Breadcrumbs.tsx +27 -0
- package/packages/solidjs/src/components/DocPage.tsx +110 -0
- package/packages/solidjs/src/components/DocSearch.tsx +81 -0
- package/packages/solidjs/src/components/DocSidebar.tsx +92 -0
- package/packages/solidjs/src/components/DocsLayout.tsx +38 -0
- package/packages/solidjs/src/components/EditButton.tsx +15 -0
- package/packages/solidjs/src/components/PageNavigation.tsx +31 -0
- package/packages/solidjs/src/components/TableOfContents.tsx +41 -0
- package/packages/solidjs/src/components/VersionSelector.tsx +30 -0
- package/packages/solidjs/src/components/index.ts +9 -0
- package/packages/solidjs/src/createDocs.ts +62 -0
- package/packages/solidjs/src/index.ts +28 -0
- package/packages/solidjs/src/pages/DocSearchPage.tsx +72 -0
- package/packages/solidjs/src/pages/DocViewPage.tsx +80 -0
- package/packages/solidjs/src/pages/DocsAdminPage.tsx +123 -0
- package/packages/solidjs/src/pages/DocsIndexPage.tsx +85 -0
- package/packages/solidjs/src/pages/index.ts +4 -0
- package/packages/solidjs/src/primitives/createDocSearch.ts +42 -0
- package/packages/solidjs/src/primitives/createDocs.ts +35 -0
- package/packages/solidjs/src/primitives/createDocsAdmin.ts +63 -0
- package/packages/solidjs/src/primitives/createTableOfContents.ts +51 -0
- package/packages/solidjs/src/primitives/index.ts +4 -0
- package/packages/solidjs/tsup.config.ts +12 -0
- package/packages/solidjs-css/README.md +1 -0
- package/packages/solidjs-css/package.json +36 -0
- package/packages/solidjs-css/src/DocsLayout.tsx +106 -0
- package/packages/solidjs-css/src/DocsProvider.tsx +95 -0
- package/packages/solidjs-css/src/RouterDocsContent.tsx +54 -0
- package/packages/solidjs-css/src/RouterDocsLayout.tsx +104 -0
- package/packages/solidjs-css/src/createDocs.ts +62 -0
- package/packages/solidjs-css/src/index.ts +7 -0
- package/packages/solidjs-css/src/index.tsx +17 -0
- package/packages/solidjs-css/src/pages/DocViewPage.tsx +111 -0
- package/packages/solidjs-css/src/pages/DocsAdminPage.tsx +332 -0
- package/packages/solidjs-css/src/pages/DocsIndexPage.tsx +116 -0
- package/packages/solidjs-css/src/pages/index.ts +3 -0
- package/packages/solidjs-css/src/primitives/index.ts +1 -0
- package/packages/solidjs-css/src/styles.css +1271 -0
- package/packages/solidjs-css/tsconfig.json +20 -0
- package/packages/solidjs-css/tsup.config.ts +10 -0
- package/pnpm-workspace.yaml +2 -0
|
@@ -0,0 +1,172 @@
|
|
|
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 } from '../hooks/useDocs'
|
|
5
|
+
import { useDocSearch } from '../hooks/useDocSearch'
|
|
6
|
+
import { DocSearch } from '../components/DocSearch'
|
|
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
|
+
|
|
17
|
+
const searchFn = useCallback(
|
|
18
|
+
(q: string): SearchResult[] => {
|
|
19
|
+
const index = buildDocsIndex(docs.flatPages, docs.sections)
|
|
20
|
+
return searchDocs(q, index)
|
|
21
|
+
},
|
|
22
|
+
[docs.flatPages, docs.sections]
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
const search = useDocSearch(searchFn)
|
|
26
|
+
|
|
27
|
+
// Cmd+K handler
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
const handler = (e: KeyboardEvent) => {
|
|
30
|
+
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
|
|
31
|
+
e.preventDefault()
|
|
32
|
+
setIsSearchOpen(true)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
document.addEventListener('keydown', handler)
|
|
36
|
+
return () => document.removeEventListener('keydown', handler)
|
|
37
|
+
}, [])
|
|
38
|
+
|
|
39
|
+
// Loading skeleton
|
|
40
|
+
if (docs.isLoading) {
|
|
41
|
+
return (
|
|
42
|
+
<div className="min-h-screen bg-[#090a0f] px-6 py-16">
|
|
43
|
+
<div className="mx-auto max-w-5xl">
|
|
44
|
+
<div className="mb-10 h-10 w-64 animate-pulse rounded-lg bg-white/5" />
|
|
45
|
+
<div className="mb-8 h-12 w-full max-w-xl animate-pulse rounded-xl bg-white/5" />
|
|
46
|
+
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
|
47
|
+
{Array.from({ length: 6 }).map((_, i) => (
|
|
48
|
+
<div key={i} className="h-36 animate-pulse rounded-xl bg-white/5" />
|
|
49
|
+
))}
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Empty state
|
|
57
|
+
if (docs.sections.length === 0) {
|
|
58
|
+
return (
|
|
59
|
+
<div className="flex min-h-screen flex-col items-center justify-center bg-[#090a0f] text-center">
|
|
60
|
+
<div className="mb-4 text-6xl opacity-30">📚</div>
|
|
61
|
+
<h2 className="mb-2 text-xl font-semibold text-white/80">No documentation yet</h2>
|
|
62
|
+
<p className="max-w-sm text-sm text-white/40">
|
|
63
|
+
Create your first section and pages to get started with your documentation.
|
|
64
|
+
</p>
|
|
65
|
+
</div>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<div className="min-h-screen bg-[#090a0f] text-white">
|
|
71
|
+
<div className="mx-auto max-w-5xl px-6 py-16">
|
|
72
|
+
{/* Header */}
|
|
73
|
+
<h1 className="mb-2 text-4xl font-bold tracking-tight">Documentation</h1>
|
|
74
|
+
<p className="mb-10 text-lg text-white/50">
|
|
75
|
+
Browse guides, API references, and tutorials.
|
|
76
|
+
</p>
|
|
77
|
+
|
|
78
|
+
{/* Search bar */}
|
|
79
|
+
<button
|
|
80
|
+
type="button"
|
|
81
|
+
onClick={() => setIsSearchOpen(true)}
|
|
82
|
+
className="mb-12 flex w-full max-w-xl items-center gap-3 rounded-xl border border-white/10 bg-white/5 px-5 py-3.5 text-left text-sm text-white/30 transition-colors hover:border-white/20 hover:bg-white/[0.07]"
|
|
83
|
+
>
|
|
84
|
+
<svg className="h-4.5 w-4.5 shrink-0" viewBox="0 0 20 20" fill="currentColor">
|
|
85
|
+
<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" />
|
|
86
|
+
</svg>
|
|
87
|
+
<span className="flex-1">Search documentation…</span>
|
|
88
|
+
<kbd className="rounded border border-white/10 bg-white/5 px-2 py-0.5 text-[11px]">⌘K</kbd>
|
|
89
|
+
</button>
|
|
90
|
+
|
|
91
|
+
{/* Section cards grid */}
|
|
92
|
+
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
|
93
|
+
{docs.sections
|
|
94
|
+
.filter((s) => !s.parentId)
|
|
95
|
+
.map((section) => (
|
|
96
|
+
<button
|
|
97
|
+
key={section.id}
|
|
98
|
+
type="button"
|
|
99
|
+
onClick={() => {
|
|
100
|
+
const firstPage = section.pages?.[0]
|
|
101
|
+
if (firstPage && onSelectPage) onSelectPage(firstPage, section)
|
|
102
|
+
}}
|
|
103
|
+
className="group flex flex-col rounded-xl border border-white/8 bg-white/[0.03] p-5 text-left transition-all hover:border-indigo-500/30 hover:bg-white/[0.06] hover:shadow-lg hover:shadow-indigo-500/5"
|
|
104
|
+
>
|
|
105
|
+
{section.icon && (
|
|
106
|
+
<span className="mb-3 text-2xl">{section.icon}</span>
|
|
107
|
+
)}
|
|
108
|
+
<h3 className="mb-1.5 text-base font-semibold text-white/90 group-hover:text-white transition-colors">
|
|
109
|
+
{section.title}
|
|
110
|
+
</h3>
|
|
111
|
+
{section.description && (
|
|
112
|
+
<p className="mb-3 flex-1 text-sm leading-relaxed text-white/40">
|
|
113
|
+
{section.description}
|
|
114
|
+
</p>
|
|
115
|
+
)}
|
|
116
|
+
<div className="flex items-center gap-2 text-xs text-white/25">
|
|
117
|
+
<span>{section.pageCount ?? 0} pages</span>
|
|
118
|
+
</div>
|
|
119
|
+
</button>
|
|
120
|
+
))}
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
{/* Recent pages */}
|
|
124
|
+
{docs.flatPages.length > 0 && (
|
|
125
|
+
<div className="mt-16">
|
|
126
|
+
<h2 className="mb-6 text-lg font-semibold text-white/80">Recently updated</h2>
|
|
127
|
+
<div className="space-y-2">
|
|
128
|
+
{docs.flatPages
|
|
129
|
+
.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime())
|
|
130
|
+
.slice(0, 8)
|
|
131
|
+
.map((page) => {
|
|
132
|
+
const section = docs.sections.find((s) => s.id === page.sectionId)
|
|
133
|
+
return (
|
|
134
|
+
<button
|
|
135
|
+
key={page.id}
|
|
136
|
+
type="button"
|
|
137
|
+
onClick={() => section && onSelectPage?.(page, section)}
|
|
138
|
+
className="flex w-full items-center gap-4 rounded-lg px-4 py-3 text-left transition-colors hover:bg-white/5"
|
|
139
|
+
>
|
|
140
|
+
<div className="flex-1 min-w-0">
|
|
141
|
+
<p className="text-sm font-medium text-white/80 truncate">{page.title}</p>
|
|
142
|
+
<p className="text-xs text-white/30 truncate">{section?.title}</p>
|
|
143
|
+
</div>
|
|
144
|
+
<span className="shrink-0 text-xs tabular-nums text-white/20">
|
|
145
|
+
{page.readingTime} min read
|
|
146
|
+
</span>
|
|
147
|
+
</button>
|
|
148
|
+
)
|
|
149
|
+
})}
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
)}
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
{/* Search modal */}
|
|
156
|
+
<DocSearch
|
|
157
|
+
results={search.results}
|
|
158
|
+
query={search.query}
|
|
159
|
+
onQuery={search.setQuery}
|
|
160
|
+
onSelect={(result) => {
|
|
161
|
+
setIsSearchOpen(false)
|
|
162
|
+
search.clearSearch()
|
|
163
|
+
const section = docs.sections.find((s) => s.slug === result.sectionSlug)
|
|
164
|
+
const page = docs.flatPages.find((p) => p.id === result.pageId)
|
|
165
|
+
if (page && section && onSelectPage) onSelectPage(page, section)
|
|
166
|
+
}}
|
|
167
|
+
isOpen={isSearchOpen}
|
|
168
|
+
onClose={() => setIsSearchOpen(false)}
|
|
169
|
+
/>
|
|
170
|
+
</div>
|
|
171
|
+
)
|
|
172
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# ✦ @geenius-docs/react-css\n\n> Geenius Docs — React components (vanilla CSS variant)\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/react-css\n```\n\n## Usage\n\n```typescript\nimport { init } from '@geenius-docs/react-css';\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
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@geenius-docs/react-css",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Geenius Docs \u2014 React components (vanilla CSS variant)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/index.tsx",
|
|
7
|
+
"types": "./src/index.tsx",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./src/index.tsx"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"type-check": "tsc --noEmit"
|
|
13
|
+
},
|
|
14
|
+
"peerDependencies": {
|
|
15
|
+
"react": ">=18.0.0",
|
|
16
|
+
"@tanstack/react-router": ">=1.0.0"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@geenius-docs/shared": "workspace:*",
|
|
20
|
+
"lucide-react": "^0.475.0",
|
|
21
|
+
"react-markdown": "^9.0.3",
|
|
22
|
+
"remark-gfm": "^4.0.1"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/react": "^19.0.0",
|
|
26
|
+
"react": "^19.2.4",
|
|
27
|
+
"typescript": "~6.0.2"
|
|
28
|
+
},
|
|
29
|
+
"author": "Antigravity HQ",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=20.0.0"
|
|
33
|
+
},
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { useDocs } from './DocsProvider'
|
|
3
|
+
|
|
4
|
+
interface DocsLayoutProps {
|
|
5
|
+
children?: React.ReactNode
|
|
6
|
+
currentPageId?: string
|
|
7
|
+
onSelectPage?: (id: string) => void
|
|
8
|
+
searchQuery?: string
|
|
9
|
+
onSearch?: (query: string) => void
|
|
10
|
+
searchResults?: any[]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A professional, high-quality layout for Geenius Documentation.
|
|
15
|
+
* Provides a responsive sidebar, searchable navigation, and premium typography via data-* CSS styling.
|
|
16
|
+
*/
|
|
17
|
+
export const DocsLayout: React.FC<DocsLayoutProps> = ({
|
|
18
|
+
children,
|
|
19
|
+
currentPageId,
|
|
20
|
+
onSelectPage,
|
|
21
|
+
searchQuery: searchQueryProp,
|
|
22
|
+
onSearch: onSearchProp,
|
|
23
|
+
searchResults: searchResultsProp
|
|
24
|
+
}) => {
|
|
25
|
+
const {
|
|
26
|
+
tree,
|
|
27
|
+
selectPage,
|
|
28
|
+
setSearchQuery,
|
|
29
|
+
searchQuery: contextSearchQuery,
|
|
30
|
+
searchResults: contextSearchResults
|
|
31
|
+
} = useDocs()
|
|
32
|
+
|
|
33
|
+
const q = searchQueryProp ?? contextSearchQuery
|
|
34
|
+
const results = searchResultsProp ?? contextSearchResults
|
|
35
|
+
const onSelect = onSelectPage ?? selectPage
|
|
36
|
+
const onSearch = onSearchProp ?? setSearchQuery
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div data-docs-container="">
|
|
40
|
+
{/* Searchable Sidebar */}
|
|
41
|
+
<aside data-docs-sidebar="">
|
|
42
|
+
<div data-docs-search-wrap="">
|
|
43
|
+
<div data-docs-search-input-wrap="">
|
|
44
|
+
<span data-docs-search-icon="">
|
|
45
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
|
|
46
|
+
</span>
|
|
47
|
+
<input
|
|
48
|
+
value={q}
|
|
49
|
+
onChange={(e) => onSearch(e.target.value)}
|
|
50
|
+
placeholder="Search knowledge base..."
|
|
51
|
+
data-docs-search-input=""
|
|
52
|
+
/>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<nav data-docs-nav="">
|
|
57
|
+
{q && results.length > 0 ? (
|
|
58
|
+
<div data-docs-search-results="">
|
|
59
|
+
<p data-docs-search-results-label="">
|
|
60
|
+
Search Results ({results.length})
|
|
61
|
+
</p>
|
|
62
|
+
{results.map(page => (
|
|
63
|
+
<button
|
|
64
|
+
key={page.id}
|
|
65
|
+
onClick={() => { onSelect(page.id); onSearch('') }}
|
|
66
|
+
data-docs-search-result-btn=""
|
|
67
|
+
>
|
|
68
|
+
<h5 data-docs-search-result-title="">{page.title}</h5>
|
|
69
|
+
{page.description && <p data-docs-search-result-desc="">{page.description}</p>}
|
|
70
|
+
</button>
|
|
71
|
+
))}
|
|
72
|
+
</div>
|
|
73
|
+
) : (
|
|
74
|
+
tree.map(group => (
|
|
75
|
+
<div key={group.id} data-docs-nav-group="">
|
|
76
|
+
<h4 data-docs-nav-group-title="">
|
|
77
|
+
{group.title}
|
|
78
|
+
</h4>
|
|
79
|
+
<div data-docs-nav-group-list="">
|
|
80
|
+
{group.children.map(page => {
|
|
81
|
+
const isActive = page.id === currentPageId
|
|
82
|
+
return (
|
|
83
|
+
<button
|
|
84
|
+
key={page.id}
|
|
85
|
+
onClick={() => onSelect(page.id)}
|
|
86
|
+
data-docs-nav-item=""
|
|
87
|
+
data-active={isActive}
|
|
88
|
+
>
|
|
89
|
+
<span data-docs-nav-indicator="" data-active={isActive} />
|
|
90
|
+
{page.title}
|
|
91
|
+
</button>
|
|
92
|
+
)
|
|
93
|
+
})}
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
))
|
|
97
|
+
)}
|
|
98
|
+
</nav>
|
|
99
|
+
</aside>
|
|
100
|
+
|
|
101
|
+
{/* Dynamic Content Area */}
|
|
102
|
+
<main data-docs-main="">
|
|
103
|
+
<div data-docs-content="">
|
|
104
|
+
{children}
|
|
105
|
+
|
|
106
|
+
<div data-docs-footer="">
|
|
107
|
+
<p data-docs-footer-copy="">© 2024 Geenius. Premium Documentation.</p>
|
|
108
|
+
<div data-docs-footer-links="">
|
|
109
|
+
<button data-docs-footer-link="">Edit on GitHub</button>
|
|
110
|
+
<button data-docs-footer-link="">Community Help</button>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
</main>
|
|
115
|
+
</div>
|
|
116
|
+
)
|
|
117
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import React, { createContext, useContext, useState, useMemo, useCallback } from 'react'
|
|
2
|
+
|
|
3
|
+
export interface DocsPage {
|
|
4
|
+
id: string
|
|
5
|
+
title: string
|
|
6
|
+
description?: string
|
|
7
|
+
content: any
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface DocsGroup {
|
|
11
|
+
id: string
|
|
12
|
+
title: string
|
|
13
|
+
children: DocsPage[]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type DocsTree = DocsGroup[]
|
|
17
|
+
|
|
18
|
+
interface DocsContextValue {
|
|
19
|
+
tree: DocsTree
|
|
20
|
+
currentPage: DocsPage | null
|
|
21
|
+
selectPage: (id: string) => void
|
|
22
|
+
searchQuery: string
|
|
23
|
+
setSearchQuery: (query: string) => void
|
|
24
|
+
searchResults: DocsPage[]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const DocsContext = createContext<DocsContextValue | null>(null)
|
|
28
|
+
|
|
29
|
+
interface DocsProviderProps {
|
|
30
|
+
tree: DocsTree
|
|
31
|
+
children: React.ReactNode
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* DocsProvider — Manages documentation state including current page,
|
|
36
|
+
* search, and navigation through the docs tree.
|
|
37
|
+
*/
|
|
38
|
+
export const DocsProvider: React.FC<DocsProviderProps> = ({ tree, children }) => {
|
|
39
|
+
const [currentPageId, setCurrentPageId] = useState<string | null>(tree[0]?.children[0]?.id || null)
|
|
40
|
+
const [searchQuery, setSearchQuery] = useState('')
|
|
41
|
+
|
|
42
|
+
const currentPage = useMemo(() => {
|
|
43
|
+
for (const group of tree) {
|
|
44
|
+
const page = group.children.find(p => p.id === currentPageId)
|
|
45
|
+
if (page) return page
|
|
46
|
+
}
|
|
47
|
+
return null
|
|
48
|
+
}, [tree, currentPageId])
|
|
49
|
+
|
|
50
|
+
const selectPage = useCallback((id: string) => {
|
|
51
|
+
setCurrentPageId(id)
|
|
52
|
+
}, [])
|
|
53
|
+
|
|
54
|
+
const searchResults = useMemo(() => {
|
|
55
|
+
if (!searchQuery) return []
|
|
56
|
+
const results: DocsPage[] = []
|
|
57
|
+
const query = searchQuery.toLowerCase()
|
|
58
|
+
|
|
59
|
+
for (const group of tree) {
|
|
60
|
+
for (const page of group.children) {
|
|
61
|
+
if (
|
|
62
|
+
page.title.toLowerCase().includes(query) ||
|
|
63
|
+
page.description?.toLowerCase().includes(query) ||
|
|
64
|
+
(typeof page.content === 'string' && page.content.toLowerCase().includes(query))
|
|
65
|
+
) {
|
|
66
|
+
results.push(page)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return results
|
|
71
|
+
}, [tree, searchQuery])
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<DocsContext.Provider value={{
|
|
75
|
+
tree,
|
|
76
|
+
currentPage,
|
|
77
|
+
selectPage,
|
|
78
|
+
searchQuery,
|
|
79
|
+
setSearchQuery,
|
|
80
|
+
searchResults
|
|
81
|
+
}}>
|
|
82
|
+
{children}
|
|
83
|
+
</DocsContext.Provider>
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function useDocs() {
|
|
88
|
+
const context = useContext(DocsContext)
|
|
89
|
+
if (!context) {
|
|
90
|
+
throw new Error('useDocs must be used within a DocsProvider')
|
|
91
|
+
}
|
|
92
|
+
return context
|
|
93
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import ReactMarkdown from 'react-markdown'
|
|
3
|
+
import remarkGfm from 'remark-gfm'
|
|
4
|
+
import { Link } from '@tanstack/react-router'
|
|
5
|
+
|
|
6
|
+
interface RouterDocsContentProps {
|
|
7
|
+
content: string
|
|
8
|
+
title: string
|
|
9
|
+
description: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const RouterDocsContent: React.FC<RouterDocsContentProps> = ({
|
|
13
|
+
content,
|
|
14
|
+
title,
|
|
15
|
+
description,
|
|
16
|
+
}) => {
|
|
17
|
+
return (
|
|
18
|
+
<article data-rdocs-article="">
|
|
19
|
+
{/* Header */}
|
|
20
|
+
<header data-rdocs-header="">
|
|
21
|
+
<h1 data-rdocs-title="">{title}</h1>
|
|
22
|
+
<p data-rdocs-description="">{description}</p>
|
|
23
|
+
</header>
|
|
24
|
+
|
|
25
|
+
{/* Markdown Content */}
|
|
26
|
+
<ReactMarkdown
|
|
27
|
+
remarkPlugins={[remarkGfm]}
|
|
28
|
+
components={{
|
|
29
|
+
h1: ({ children }) => <h1 data-rdocs-h1="">{children}</h1>,
|
|
30
|
+
h2: ({ children }) => <h2 data-rdocs-h2="">{children}</h2>,
|
|
31
|
+
h3: ({ children }) => <h3 data-rdocs-h3="">{children}</h3>,
|
|
32
|
+
h4: ({ children }) => <h4 data-rdocs-h4="">{children}</h4>,
|
|
33
|
+
p: ({ children }) => <p data-rdocs-p="">{children}</p>,
|
|
34
|
+
a: ({ href, children }) => {
|
|
35
|
+
if (href?.startsWith('/')) {
|
|
36
|
+
return <Link to={href} data-rdocs-a="">{children}</Link>
|
|
37
|
+
}
|
|
38
|
+
return <a href={href} target="_blank" rel="noopener noreferrer" data-rdocs-a="">{children}</a>
|
|
39
|
+
},
|
|
40
|
+
code: ({ className, children, ...props }) => {
|
|
41
|
+
if (!className) return <code data-rdocs-code-inline="">{children}</code>
|
|
42
|
+
return <code className={className} data-rdocs-code-block="" {...props}>{children}</code>
|
|
43
|
+
},
|
|
44
|
+
pre: ({ children }) => <pre data-rdocs-pre="">{children}</pre>,
|
|
45
|
+
ul: ({ children }) => <ul data-rdocs-ul="">{children}</ul>,
|
|
46
|
+
ol: ({ children }) => <ol data-rdocs-ol="">{children}</ol>,
|
|
47
|
+
li: ({ children }) => <li data-rdocs-li="">{children}</li>,
|
|
48
|
+
table: ({ children }) => <div data-rdocs-table-wrap=""><table data-rdocs-table="">{children}</table></div>,
|
|
49
|
+
thead: ({ children }) => <thead data-rdocs-thead="">{children}</thead>,
|
|
50
|
+
th: ({ children }) => <th data-rdocs-th="">{children}</th>,
|
|
51
|
+
td: ({ children }) => <td data-rdocs-td="">{children}</td>,
|
|
52
|
+
blockquote: ({ children }) => <blockquote data-rdocs-blockquote="">{children}</blockquote>,
|
|
53
|
+
hr: () => <hr data-rdocs-hr="" />,
|
|
54
|
+
}}
|
|
55
|
+
>
|
|
56
|
+
{content}
|
|
57
|
+
</ReactMarkdown>
|
|
58
|
+
</article>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
import { Link, useLocation } from '@tanstack/react-router'
|
|
3
|
+
import { Book, Menu, X, Search, ChevronRight } from 'lucide-react'
|
|
4
|
+
|
|
5
|
+
export interface DocItem {
|
|
6
|
+
slug: string
|
|
7
|
+
title: string
|
|
8
|
+
description: string
|
|
9
|
+
category: string
|
|
10
|
+
order: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface RouterDocsLayoutProps {
|
|
14
|
+
children: React.ReactNode
|
|
15
|
+
docs: DocItem[]
|
|
16
|
+
currentSlug?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const RouterDocsLayout: React.FC<RouterDocsLayoutProps> = ({ children, docs, currentSlug }) => {
|
|
20
|
+
const [isSidebarOpen, setIsSidebarOpen] = useState(false)
|
|
21
|
+
|
|
22
|
+
const groupedDocs = docs.reduce((acc, doc) => {
|
|
23
|
+
if (!acc[doc.category]) { acc[doc.category] = [] }
|
|
24
|
+
acc[doc.category].push(doc)
|
|
25
|
+
return acc
|
|
26
|
+
}, {} as Record<string, DocItem[]>)
|
|
27
|
+
|
|
28
|
+
const sortedCategories = Object.entries(groupedDocs).sort(([a], [b]) => {
|
|
29
|
+
const categoryOrder = ['Introduction', 'Getting Started', 'Guides', 'API', 'Advanced']
|
|
30
|
+
return categoryOrder.indexOf(a) - categoryOrder.indexOf(b)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div data-rdocs-layout="">
|
|
35
|
+
{/* Mobile Header */}
|
|
36
|
+
<div data-rdocs-mobile-header="">
|
|
37
|
+
<Link to="/" data-rdocs-logo="">
|
|
38
|
+
<Book />
|
|
39
|
+
<span>Documentation</span>
|
|
40
|
+
</Link>
|
|
41
|
+
<button onClick={() => setIsSidebarOpen(!isSidebarOpen)} data-rdocs-mobile-btn="">
|
|
42
|
+
{isSidebarOpen ? <X /> : <Menu />}
|
|
43
|
+
</button>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<div data-rdocs-body="">
|
|
47
|
+
{/* Sidebar */}
|
|
48
|
+
<aside data-rdocs-sidebar="" data-open={isSidebarOpen}>
|
|
49
|
+
<div data-rdocs-sidebar-content="">
|
|
50
|
+
<Link to="/" data-rdocs-logo="" data-desktop="">
|
|
51
|
+
<Book />
|
|
52
|
+
<span>Documentation</span>
|
|
53
|
+
</Link>
|
|
54
|
+
|
|
55
|
+
<div data-rdocs-search="">
|
|
56
|
+
<Search data-rdocs-search-icon="" />
|
|
57
|
+
<input type="text" placeholder="Search docs..." data-rdocs-search-input="" />
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<nav data-rdocs-nav="">
|
|
61
|
+
{sortedCategories.map(([category, categoryDocs]) => (
|
|
62
|
+
<div key={category} data-rdocs-category-group="">
|
|
63
|
+
<h3 data-rdocs-category-title="">{category}</h3>
|
|
64
|
+
<ul data-rdocs-category-list="">
|
|
65
|
+
{categoryDocs.sort((a, b) => a.order - b.order).map((doc) => {
|
|
66
|
+
const isActive = currentSlug === doc.slug
|
|
67
|
+
return (
|
|
68
|
+
<li key={doc.slug}>
|
|
69
|
+
<Link
|
|
70
|
+
to="/docs/$slug"
|
|
71
|
+
params={{ slug: doc.slug }}
|
|
72
|
+
activeOptions={{ exact: true }}
|
|
73
|
+
onClick={() => setIsSidebarOpen(false)}
|
|
74
|
+
data-rdocs-nav-link=""
|
|
75
|
+
data-active={isActive}
|
|
76
|
+
>
|
|
77
|
+
<ChevronRight data-rdocs-chevron="" data-active={isActive} />
|
|
78
|
+
{doc.title}
|
|
79
|
+
</Link>
|
|
80
|
+
</li>
|
|
81
|
+
)
|
|
82
|
+
})}
|
|
83
|
+
</ul>
|
|
84
|
+
</div>
|
|
85
|
+
))}
|
|
86
|
+
</nav>
|
|
87
|
+
</div>
|
|
88
|
+
</aside>
|
|
89
|
+
|
|
90
|
+
{isSidebarOpen && (
|
|
91
|
+
<div data-rdocs-overlay="" onClick={() => setIsSidebarOpen(false)} />
|
|
92
|
+
)}
|
|
93
|
+
|
|
94
|
+
{/* Main Content */}
|
|
95
|
+
<main data-rdocs-main="">
|
|
96
|
+
<div data-rdocs-content="">{children}</div>
|
|
97
|
+
</main>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
)
|
|
101
|
+
}
|