skrypt-ai 0.3.3 → 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/README.md +1 -1
- package/dist/auth/index.d.ts +0 -1
- package/dist/auth/index.js +3 -5
- package/dist/autofix/index.js +15 -3
- package/dist/cli.js +19 -4
- package/dist/commands/check-links.js +164 -174
- package/dist/commands/deploy.js +5 -2
- package/dist/commands/generate.js +206 -199
- package/dist/commands/i18n.js +3 -20
- package/dist/commands/init.js +47 -40
- package/dist/commands/lint.js +3 -20
- package/dist/commands/mcp.js +125 -122
- package/dist/commands/monitor.js +125 -108
- package/dist/commands/review-pr.js +1 -1
- package/dist/commands/sdk.js +1 -1
- package/dist/config/loader.js +21 -2
- package/dist/generator/organizer.d.ts +3 -0
- package/dist/generator/organizer.js +4 -9
- package/dist/generator/writer.js +2 -10
- package/dist/github/pr-comments.js +21 -8
- package/dist/plugins/index.js +1 -0
- package/dist/scanner/index.js +8 -2
- package/dist/template/docs.json +2 -1
- package/dist/template/next.config.mjs +3 -1
- package/dist/template/package.json +17 -14
- package/dist/template/public/favicon.svg +4 -0
- package/dist/template/public/search-index.json +1 -1
- package/dist/template/scripts/build-search-index.mjs +120 -25
- package/dist/template/src/app/api/chat/route.ts +11 -3
- package/dist/template/src/app/docs/README.md +28 -0
- package/dist/template/src/app/docs/[...slug]/page.tsx +141 -14
- package/dist/template/src/app/docs/auth/page.mdx +589 -0
- package/dist/template/src/app/docs/autofix/page.mdx +624 -0
- package/dist/template/src/app/docs/cli/page.mdx +217 -0
- package/dist/template/src/app/docs/config/page.mdx +428 -0
- package/dist/template/src/app/docs/configuration/page.mdx +86 -0
- package/dist/template/src/app/docs/deployment/page.mdx +112 -0
- package/dist/template/src/app/docs/error.tsx +20 -0
- package/dist/template/src/app/docs/generator/generator.md +504 -0
- package/dist/template/src/app/docs/generator/organizer.md +779 -0
- package/dist/template/src/app/docs/generator/page.mdx +613 -0
- package/dist/template/src/app/docs/github/page.mdx +502 -0
- package/dist/template/src/app/docs/llm/anthropic-client.md +549 -0
- package/dist/template/src/app/docs/llm/index.md +471 -0
- package/dist/template/src/app/docs/llm/page.mdx +428 -0
- package/dist/template/src/app/docs/llms-full.md +256 -0
- package/dist/template/src/app/docs/llms.txt +2971 -0
- package/dist/template/src/app/docs/not-found.tsx +23 -0
- package/dist/template/src/app/docs/page.mdx +0 -3
- package/dist/template/src/app/docs/plugins/page.mdx +1793 -0
- package/dist/template/src/app/docs/pro/page.mdx +121 -0
- package/dist/template/src/app/docs/quickstart/page.mdx +93 -0
- package/dist/template/src/app/docs/scanner/content-type.md +599 -0
- package/dist/template/src/app/docs/scanner/index.md +212 -0
- package/dist/template/src/app/docs/scanner/page.mdx +307 -0
- package/dist/template/src/app/docs/scanner/python.md +469 -0
- package/dist/template/src/app/docs/scanner/python_parser.md +1056 -0
- package/dist/template/src/app/docs/scanner/rust.md +325 -0
- package/dist/template/src/app/docs/scanner/typescript.md +201 -0
- package/dist/template/src/app/error.tsx +3 -3
- package/dist/template/src/app/icon.tsx +29 -0
- package/dist/template/src/app/layout.tsx +57 -7
- package/dist/template/src/app/not-found.tsx +35 -0
- package/dist/template/src/app/page.tsx +95 -11
- package/dist/template/src/components/ai-chat.tsx +26 -21
- package/dist/template/src/components/breadcrumbs.tsx +56 -12
- package/dist/template/src/components/copy-button.tsx +17 -3
- package/dist/template/src/components/docs-layout.tsx +202 -8
- package/dist/template/src/components/feedback.tsx +4 -2
- package/dist/template/src/components/footer.tsx +42 -0
- package/dist/template/src/components/header.tsx +56 -20
- package/dist/template/src/components/mdx/accordion.tsx +17 -13
- package/dist/template/src/components/mdx/callout.tsx +50 -37
- package/dist/template/src/components/mdx/card.tsx +24 -12
- package/dist/template/src/components/mdx/code-block.tsx +17 -3
- package/dist/template/src/components/mdx/code-group.tsx +78 -18
- package/dist/template/src/components/mdx/code-playground.tsx +3 -0
- package/dist/template/src/components/mdx/go-playground.tsx +3 -0
- package/dist/template/src/components/mdx/highlighted-code.tsx +178 -38
- package/dist/template/src/components/mdx/python-playground.tsx +2 -0
- package/dist/template/src/components/mdx/steps.tsx +6 -6
- package/dist/template/src/components/mdx/tabs.tsx +76 -8
- package/dist/template/src/components/page-header.tsx +19 -0
- package/dist/template/src/components/scroll-to-top.tsx +33 -0
- package/dist/template/src/components/search-dialog.tsx +251 -57
- package/dist/template/src/components/sidebar.tsx +137 -77
- package/dist/template/src/components/table-of-contents.tsx +29 -13
- package/dist/template/src/lib/highlight.ts +90 -31
- package/dist/template/src/lib/search.ts +14 -4
- package/dist/template/src/lib/theme-utils.ts +140 -0
- package/dist/template/src/styles/globals.css +397 -84
- package/dist/template/src/types/remark-gfm.d.ts +2 -0
- package/dist/utils/files.d.ts +9 -0
- package/dist/utils/files.js +33 -0
- package/dist/utils/validation.d.ts +4 -0
- package/dist/utils/validation.js +38 -0
- package/package.json +1 -4
|
@@ -1,43 +1,237 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { useState } from 'react'
|
|
3
|
+
import { useState, useEffect } from 'react'
|
|
4
|
+
import { usePathname } from 'next/navigation'
|
|
5
|
+
import Link from 'next/link'
|
|
6
|
+
import { ChevronLeft, ChevronRight, ChevronDown } from 'lucide-react'
|
|
4
7
|
import { Sidebar } from './sidebar'
|
|
5
8
|
import { Header } from './header'
|
|
6
9
|
import { TableOfContents } from './table-of-contents'
|
|
7
10
|
import { Breadcrumbs } from './breadcrumbs'
|
|
11
|
+
import { PageHeader } from './page-header'
|
|
12
|
+
import { Feedback } from './feedback'
|
|
13
|
+
import { EditLink } from './edit-link'
|
|
14
|
+
import { Footer } from './footer'
|
|
15
|
+
import { ScrollToTop } from './scroll-to-top'
|
|
8
16
|
|
|
9
17
|
interface DocsConfig {
|
|
18
|
+
name?: string
|
|
19
|
+
headerLinks?: Array<{ title: string; path: string }>
|
|
20
|
+
logo?: string
|
|
10
21
|
navigation: Array<{
|
|
11
22
|
group: string
|
|
12
|
-
|
|
23
|
+
icon?: string
|
|
24
|
+
pages: Array<{ title: string; path: string; description?: string }>
|
|
13
25
|
}>
|
|
26
|
+
footer?: {
|
|
27
|
+
links?: Array<{ title: string; url: string }>
|
|
28
|
+
}
|
|
29
|
+
editLink?: {
|
|
30
|
+
repoUrl?: string
|
|
31
|
+
branch?: string
|
|
32
|
+
docsPath?: string
|
|
33
|
+
}
|
|
14
34
|
}
|
|
15
35
|
|
|
16
|
-
|
|
36
|
+
function getAllPages(config: DocsConfig): Array<{ title: string; path: string }> {
|
|
37
|
+
return config.navigation.flatMap((group) => group.pages)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function PrevNextNav({ docsConfig }: { docsConfig: DocsConfig }) {
|
|
41
|
+
const pathname = usePathname()
|
|
42
|
+
const allPages = getAllPages(docsConfig)
|
|
43
|
+
const currentIndex = allPages.findIndex((p) => p.path === pathname)
|
|
44
|
+
|
|
45
|
+
if (currentIndex === -1) return null
|
|
46
|
+
|
|
47
|
+
const prev = currentIndex > 0 ? allPages[currentIndex - 1] : null
|
|
48
|
+
const next = currentIndex < allPages.length - 1 ? allPages[currentIndex + 1] : null
|
|
49
|
+
|
|
50
|
+
if (!prev && !next) return null
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<div className="mt-12 pt-6 border-t border-[var(--color-border)] grid grid-cols-2 gap-4">
|
|
54
|
+
{prev ? (
|
|
55
|
+
<Link
|
|
56
|
+
href={prev.path}
|
|
57
|
+
className="group flex flex-col gap-1 p-4 rounded-xl border border-[var(--color-border)] hover:border-[var(--color-border-strong)] hover:bg-[var(--color-bg-secondary)] hover:no-underline transition-all"
|
|
58
|
+
>
|
|
59
|
+
<span className="flex items-center gap-1 text-xs text-[var(--color-text-tertiary)]">
|
|
60
|
+
<ChevronLeft size={12} />
|
|
61
|
+
Previous
|
|
62
|
+
</span>
|
|
63
|
+
<span className="text-sm font-medium text-[var(--color-text)] group-hover:text-[var(--color-primary)]">
|
|
64
|
+
{prev.title}
|
|
65
|
+
</span>
|
|
66
|
+
</Link>
|
|
67
|
+
) : (
|
|
68
|
+
<div />
|
|
69
|
+
)}
|
|
70
|
+
{next && (
|
|
71
|
+
<Link
|
|
72
|
+
href={next.path}
|
|
73
|
+
className="group flex flex-col items-end gap-1 p-4 rounded-xl border border-[var(--color-border)] hover:border-[var(--color-border-strong)] hover:bg-[var(--color-bg-secondary)] hover:no-underline transition-all"
|
|
74
|
+
>
|
|
75
|
+
<span className="flex items-center gap-1 text-xs text-[var(--color-text-tertiary)]">
|
|
76
|
+
Next
|
|
77
|
+
<ChevronRight size={12} />
|
|
78
|
+
</span>
|
|
79
|
+
<span className="text-sm font-medium text-[var(--color-text)] group-hover:text-[var(--color-primary)]">
|
|
80
|
+
{next.title}
|
|
81
|
+
</span>
|
|
82
|
+
</Link>
|
|
83
|
+
)}
|
|
84
|
+
</div>
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Mobile TOC — collapsible "On this page" for screens < xl */
|
|
89
|
+
function MobileTOC() {
|
|
90
|
+
const [headings, setHeadings] = useState<Array<{ id: string; text: string; level: number }>>([])
|
|
91
|
+
const [open, setOpen] = useState(false)
|
|
92
|
+
const pathname = usePathname()
|
|
93
|
+
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
const article = document.querySelector('article')
|
|
96
|
+
if (!article) return
|
|
97
|
+
|
|
98
|
+
const elements = article.querySelectorAll('h2, h3')
|
|
99
|
+
const items: Array<{ id: string; text: string; level: number }> = []
|
|
100
|
+
|
|
101
|
+
const genericHeadings = new Set([
|
|
102
|
+
'parameters', 'returns', 'return value', 'return type',
|
|
103
|
+
'returned validator function', 'requirements',
|
|
104
|
+
'when results are returned', 'when each value is returned',
|
|
105
|
+
'validationresult shape',
|
|
106
|
+
])
|
|
107
|
+
|
|
108
|
+
elements.forEach((el) => {
|
|
109
|
+
const text = el.textContent || ''
|
|
110
|
+
const normalized = text.toLowerCase().trim()
|
|
111
|
+
if (genericHeadings.has(normalized)) return
|
|
112
|
+
|
|
113
|
+
const id = el.id || normalized.replace(/\s+/g, '-')
|
|
114
|
+
if (!el.id) el.id = id
|
|
115
|
+
items.push({ id, text, level: parseInt(el.tagName[1]) })
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
setHeadings(items)
|
|
119
|
+
setOpen(false)
|
|
120
|
+
}, [pathname])
|
|
121
|
+
|
|
122
|
+
if (headings.length === 0) return null
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<div className="lg:hidden mb-6 border border-[var(--color-border)] rounded-lg">
|
|
126
|
+
<button
|
|
127
|
+
onClick={() => setOpen(!open)}
|
|
128
|
+
className="flex items-center justify-between w-full px-4 py-3 text-[0.8125rem] font-medium text-[var(--color-text)]"
|
|
129
|
+
aria-expanded={open}
|
|
130
|
+
>
|
|
131
|
+
On this page
|
|
132
|
+
<ChevronDown size={14} className={`text-[var(--color-text-tertiary)] transition-transform duration-150 ${open ? 'rotate-180' : ''}`} />
|
|
133
|
+
</button>
|
|
134
|
+
{open && (
|
|
135
|
+
<div className="px-4 pb-3 border-t border-[var(--color-border)]">
|
|
136
|
+
<ul className="space-y-1 mt-2">
|
|
137
|
+
{headings.map((heading, i) => (
|
|
138
|
+
<li key={`${heading.id}-${i}`}>
|
|
139
|
+
<a
|
|
140
|
+
href={`#${heading.id}`}
|
|
141
|
+
onClick={() => setOpen(false)}
|
|
142
|
+
className={`block py-1 text-[0.8125rem] text-[var(--color-text-secondary)] hover:text-[var(--color-primary)] hover:no-underline ${
|
|
143
|
+
heading.level === 3 ? 'pl-4' : ''
|
|
144
|
+
}`}
|
|
145
|
+
>
|
|
146
|
+
{heading.text}
|
|
147
|
+
</a>
|
|
148
|
+
</li>
|
|
149
|
+
))}
|
|
150
|
+
</ul>
|
|
151
|
+
</div>
|
|
152
|
+
)}
|
|
153
|
+
</div>
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function getPageInfo(docsConfig: DocsConfig, pathname: string): { title?: string; description?: string } {
|
|
158
|
+
for (const group of docsConfig.navigation) {
|
|
159
|
+
for (const page of group.pages) {
|
|
160
|
+
if (page.path === pathname) {
|
|
161
|
+
return { title: page.title, description: page.description }
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return {}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function DocsLayout({
|
|
169
|
+
children,
|
|
170
|
+
docsConfig,
|
|
171
|
+
pageTitle,
|
|
172
|
+
pageDescription,
|
|
173
|
+
}: {
|
|
174
|
+
children: React.ReactNode
|
|
175
|
+
docsConfig: DocsConfig
|
|
176
|
+
pageTitle?: string
|
|
177
|
+
pageDescription?: string
|
|
178
|
+
}) {
|
|
17
179
|
const [menuOpen, setMenuOpen] = useState(false)
|
|
180
|
+
const [frontmatterDescription, setFrontmatterDescription] = useState<string | undefined>()
|
|
181
|
+
const pathname = usePathname()
|
|
182
|
+
|
|
183
|
+
// Read frontmatter description passed from the server component via data attribute
|
|
184
|
+
useEffect(() => {
|
|
185
|
+
const el = document.querySelector('[data-page-description]')
|
|
186
|
+
setFrontmatterDescription(el?.getAttribute('data-page-description') ?? undefined)
|
|
187
|
+
}, [pathname])
|
|
188
|
+
|
|
189
|
+
// Resolve title from props, falling back to docs.json navigation lookup
|
|
190
|
+
const pageInfo = getPageInfo(docsConfig, pathname)
|
|
191
|
+
const resolvedTitle = pageTitle ?? pageInfo.title
|
|
192
|
+
const resolvedDescription = pageDescription ?? frontmatterDescription ?? pageInfo.description
|
|
18
193
|
|
|
19
194
|
return (
|
|
20
|
-
<div className="min-h-screen">
|
|
195
|
+
<div className="min-h-screen flex flex-col">
|
|
21
196
|
<Header
|
|
22
197
|
onMenuToggle={() => setMenuOpen(!menuOpen)}
|
|
23
198
|
menuOpen={menuOpen}
|
|
199
|
+
siteName={docsConfig.name}
|
|
200
|
+
navLinks={docsConfig.headerLinks}
|
|
201
|
+
logo={docsConfig.logo}
|
|
24
202
|
/>
|
|
25
|
-
<div className="flex">
|
|
203
|
+
<div className="flex flex-1">
|
|
26
204
|
<Sidebar
|
|
27
205
|
open={menuOpen}
|
|
28
206
|
onClose={() => setMenuOpen(false)}
|
|
29
207
|
docsConfig={docsConfig}
|
|
30
208
|
/>
|
|
31
|
-
<main className="flex-1 min-w-0 px-
|
|
32
|
-
<div className="max-w-
|
|
33
|
-
<Breadcrumbs />
|
|
209
|
+
<main id="main-content" className="flex-1 min-w-0 px-6 md:px-10 py-8 lg:ml-[var(--sidebar-width)] lg:mr-[var(--toc-width)]">
|
|
210
|
+
<div className="max-w-[var(--content-max-width)] mx-auto">
|
|
211
|
+
<Breadcrumbs docsConfig={docsConfig} />
|
|
212
|
+
{resolvedTitle && (
|
|
213
|
+
<PageHeader title={resolvedTitle} description={resolvedDescription} />
|
|
214
|
+
)}
|
|
215
|
+
<MobileTOC />
|
|
34
216
|
<article className="prose">
|
|
35
217
|
{children}
|
|
36
218
|
</article>
|
|
219
|
+
{/* Feedback and Edit Link */}
|
|
220
|
+
<div className="mt-10 pt-6 border-t border-[var(--color-border)] flex items-center justify-between flex-wrap gap-4">
|
|
221
|
+
<Feedback />
|
|
222
|
+
<EditLink
|
|
223
|
+
repoUrl={docsConfig.editLink?.repoUrl}
|
|
224
|
+
branch={docsConfig.editLink?.branch}
|
|
225
|
+
docsPath={docsConfig.editLink?.docsPath}
|
|
226
|
+
/>
|
|
227
|
+
</div>
|
|
228
|
+
<PrevNextNav docsConfig={docsConfig} />
|
|
37
229
|
</div>
|
|
38
230
|
</main>
|
|
39
231
|
<TableOfContents />
|
|
40
232
|
</div>
|
|
233
|
+
<Footer docsConfig={docsConfig} />
|
|
234
|
+
<ScrollToTop />
|
|
41
235
|
</div>
|
|
42
236
|
)
|
|
43
237
|
}
|
|
@@ -28,9 +28,10 @@ export function Feedback() {
|
|
|
28
28
|
<div className="flex items-center gap-1">
|
|
29
29
|
<button
|
|
30
30
|
onClick={() => handleFeedback(true)}
|
|
31
|
+
aria-label="Helpful"
|
|
31
32
|
className={`p-1.5 rounded-md transition-colors ${
|
|
32
33
|
selection === 'yes'
|
|
33
|
-
? 'bg-emerald-100 text-emerald-600'
|
|
34
|
+
? 'bg-emerald-100 text-emerald-600 feedback-positive'
|
|
34
35
|
: 'text-[var(--color-text-tertiary)] hover:text-[var(--color-text)] hover:bg-[var(--color-bg-secondary)]'
|
|
35
36
|
}`}
|
|
36
37
|
>
|
|
@@ -38,9 +39,10 @@ export function Feedback() {
|
|
|
38
39
|
</button>
|
|
39
40
|
<button
|
|
40
41
|
onClick={() => handleFeedback(false)}
|
|
42
|
+
aria-label="Not helpful"
|
|
41
43
|
className={`p-1.5 rounded-md transition-colors ${
|
|
42
44
|
selection === 'no'
|
|
43
|
-
? 'bg-red-100 text-red-600'
|
|
45
|
+
? 'bg-red-100 text-red-600 feedback-negative'
|
|
44
46
|
: 'text-[var(--color-text-tertiary)] hover:text-[var(--color-text)] hover:bg-[var(--color-bg-secondary)]'
|
|
45
47
|
}`}
|
|
46
48
|
>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
interface FooterProps {
|
|
2
|
+
docsConfig: {
|
|
3
|
+
footer?: {
|
|
4
|
+
links?: Array<{ title: string; url: string }>
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function Footer({ docsConfig }: FooterProps) {
|
|
10
|
+
const links = docsConfig.footer?.links || []
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<footer className="border-t border-[var(--color-border)] lg:ml-[var(--sidebar-width)]">
|
|
14
|
+
<div className="max-w-[var(--content-max-width)] mx-auto px-6 md:px-10 py-6 flex items-center justify-between flex-wrap gap-4">
|
|
15
|
+
<div className="flex items-center gap-6">
|
|
16
|
+
{links.map((link) => (
|
|
17
|
+
<a
|
|
18
|
+
key={link.url}
|
|
19
|
+
href={link.url}
|
|
20
|
+
target="_blank"
|
|
21
|
+
rel="noopener noreferrer"
|
|
22
|
+
className="text-[0.8125rem] text-[var(--color-text-tertiary)] hover:text-[var(--color-text)] transition-colors"
|
|
23
|
+
>
|
|
24
|
+
{link.title}
|
|
25
|
+
</a>
|
|
26
|
+
))}
|
|
27
|
+
</div>
|
|
28
|
+
<span className="text-[0.75rem] text-[var(--color-text-tertiary)]">
|
|
29
|
+
Built with{' '}
|
|
30
|
+
<a
|
|
31
|
+
href="https://skrypt.sh"
|
|
32
|
+
target="_blank"
|
|
33
|
+
rel="noopener noreferrer"
|
|
34
|
+
className="text-[var(--color-text-secondary)] hover:text-[var(--color-primary)] transition-colors"
|
|
35
|
+
>
|
|
36
|
+
Skrypt
|
|
37
|
+
</a>
|
|
38
|
+
</span>
|
|
39
|
+
</div>
|
|
40
|
+
</footer>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import Link from 'next/link'
|
|
4
4
|
import { Search, Menu, X } from 'lucide-react'
|
|
5
|
-
import { useState } from 'react'
|
|
5
|
+
import { useState, useEffect } from 'react'
|
|
6
6
|
import { SearchDialog } from './search-dialog'
|
|
7
7
|
import { ThemeToggle } from './theme-toggle'
|
|
8
8
|
import { SyntaxThemeSelector } from './syntax-theme-selector'
|
|
@@ -10,48 +10,84 @@ import { SyntaxThemeSelector } from './syntax-theme-selector'
|
|
|
10
10
|
interface HeaderProps {
|
|
11
11
|
onMenuToggle?: () => void
|
|
12
12
|
menuOpen?: boolean
|
|
13
|
+
siteName?: string
|
|
14
|
+
navLinks?: Array<{ title: string; path: string }>
|
|
15
|
+
logo?: string
|
|
13
16
|
}
|
|
14
17
|
|
|
15
|
-
export function Header({ onMenuToggle, menuOpen }: HeaderProps) {
|
|
18
|
+
export function Header({ onMenuToggle, menuOpen, siteName = 'Docs', navLinks, logo }: HeaderProps) {
|
|
16
19
|
const [searchOpen, setSearchOpen] = useState(false)
|
|
20
|
+
const [isMac, setIsMac] = useState(true)
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
setIsMac(
|
|
24
|
+
navigator.platform?.toLowerCase().includes('mac') ||
|
|
25
|
+
navigator.userAgent?.toLowerCase().includes('mac')
|
|
26
|
+
)
|
|
27
|
+
}, [])
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
function handleKeyDown(e: KeyboardEvent) {
|
|
31
|
+
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
|
|
32
|
+
e.preventDefault()
|
|
33
|
+
setSearchOpen(prev => !prev)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
document.addEventListener('keydown', handleKeyDown)
|
|
37
|
+
return () => document.removeEventListener('keydown', handleKeyDown)
|
|
38
|
+
}, [])
|
|
17
39
|
|
|
18
40
|
return (
|
|
19
41
|
<>
|
|
20
|
-
<header className="sticky top-0 z-50 border-b border-[var(--color-border)] bg-[var(--color-bg)]/
|
|
21
|
-
<div className="flex items-center justify-between h-
|
|
22
|
-
<div className="flex items-center gap-
|
|
42
|
+
<header className="sticky top-0 z-50 h-[var(--header-height)] border-b border-[var(--color-border)] bg-[var(--color-bg)]/80 backdrop-blur-xl">
|
|
43
|
+
<div className="flex items-center justify-between h-full px-4 md:px-6">
|
|
44
|
+
<div className="flex items-center gap-6">
|
|
23
45
|
{/* Mobile menu button */}
|
|
24
46
|
<button
|
|
25
47
|
onClick={onMenuToggle}
|
|
26
|
-
className="md:hidden p-
|
|
48
|
+
className="md:hidden p-1.5 -ml-1.5 rounded-lg text-[var(--color-text-secondary)] hover:text-[var(--color-text)] hover:bg-[var(--color-bg-secondary)]"
|
|
27
49
|
aria-label={menuOpen ? 'Close menu' : 'Open menu'}
|
|
28
50
|
aria-expanded={menuOpen}
|
|
29
51
|
>
|
|
30
|
-
{menuOpen ? <X size={
|
|
52
|
+
{menuOpen ? <X size={18} /> : <Menu size={18} />}
|
|
31
53
|
</button>
|
|
32
54
|
|
|
33
|
-
<Link href="/" className="font-semibold text-
|
|
34
|
-
|
|
55
|
+
<Link href="/" className="flex items-center gap-2 font-semibold text-[0.9375rem] tracking-tight text-[var(--color-text)] hover:no-underline">
|
|
56
|
+
{logo ? (
|
|
57
|
+
<img src={logo} alt={siteName} className="h-6 w-auto" />
|
|
58
|
+
) : (
|
|
59
|
+
siteName
|
|
60
|
+
)}
|
|
35
61
|
</Link>
|
|
36
|
-
|
|
37
|
-
|
|
62
|
+
|
|
63
|
+
<nav className="hidden md:flex items-center gap-1">
|
|
64
|
+
<Link
|
|
65
|
+
href="/docs"
|
|
66
|
+
className="px-3 py-1.5 text-[0.8125rem] text-[var(--color-text-secondary)] hover:text-[var(--color-text)] hover:bg-[var(--color-bg-secondary)] rounded-lg hover:no-underline"
|
|
67
|
+
>
|
|
38
68
|
Documentation
|
|
39
69
|
</Link>
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
70
|
+
{navLinks?.map((link) => (
|
|
71
|
+
<Link
|
|
72
|
+
key={link.path}
|
|
73
|
+
href={link.path}
|
|
74
|
+
className="px-3 py-1.5 text-[0.8125rem] text-[var(--color-text-secondary)] hover:text-[var(--color-text)] hover:bg-[var(--color-bg-secondary)] rounded-lg hover:no-underline"
|
|
75
|
+
>
|
|
76
|
+
{link.title}
|
|
77
|
+
</Link>
|
|
78
|
+
))}
|
|
43
79
|
</nav>
|
|
44
80
|
</div>
|
|
45
81
|
|
|
46
|
-
<div className="flex items-center gap-
|
|
82
|
+
<div className="flex items-center gap-1.5">
|
|
47
83
|
<button
|
|
48
84
|
onClick={() => setSearchOpen(true)}
|
|
49
|
-
className="flex items-center gap-2 px-3 py-1.5 text-
|
|
85
|
+
className="flex items-center gap-2 px-3 py-1.5 text-[0.8125rem] text-[var(--color-text-tertiary)] bg-[var(--color-bg-secondary)] border border-[var(--color-border)] rounded-lg hover:border-[var(--color-border-strong)] hover:text-[var(--color-text-secondary)]"
|
|
50
86
|
>
|
|
51
|
-
<Search size={
|
|
52
|
-
<span className="hidden sm:inline">Search
|
|
53
|
-
<kbd className="hidden sm:inline px-1.5 py-0.5 text-
|
|
54
|
-
⌘K
|
|
87
|
+
<Search size={14} />
|
|
88
|
+
<span className="hidden sm:inline">Search...</span>
|
|
89
|
+
<kbd className="hidden sm:inline ml-2 px-1.5 py-0.5 text-[0.6875rem] font-medium bg-[var(--color-bg)] border border-[var(--color-border)] rounded">
|
|
90
|
+
{isMac ? '⌘K' : 'Ctrl+K'}
|
|
55
91
|
</kbd>
|
|
56
92
|
</button>
|
|
57
93
|
<SyntaxThemeSelector />
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import { useState, ReactNode } from 'react'
|
|
4
|
-
import {
|
|
4
|
+
import { ChevronRight } from 'lucide-react'
|
|
5
5
|
import { cn } from '@/lib/utils'
|
|
6
6
|
|
|
7
7
|
interface AccordionProps {
|
|
@@ -14,27 +14,31 @@ export function Accordion({ title, defaultOpen = false, children }: AccordionPro
|
|
|
14
14
|
const [open, setOpen] = useState(defaultOpen)
|
|
15
15
|
|
|
16
16
|
return (
|
|
17
|
-
<div className="border border-[var(--color-border)]
|
|
17
|
+
<div className="border-b border-[var(--color-border)] last:border-b-0">
|
|
18
18
|
<button
|
|
19
19
|
onClick={() => setOpen(!open)}
|
|
20
|
-
|
|
20
|
+
aria-expanded={open}
|
|
21
|
+
className="flex items-center gap-2 w-full py-3.5 text-left text-[0.875rem] font-medium text-[var(--color-text)] hover:text-[var(--color-primary)] transition-colors"
|
|
21
22
|
>
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
size={20}
|
|
23
|
+
<ChevronRight
|
|
24
|
+
size={14}
|
|
25
25
|
className={cn(
|
|
26
|
-
'text-[var(--color-text-tertiary)] transition-transform',
|
|
27
|
-
open && 'rotate-
|
|
26
|
+
'text-[var(--color-text-tertiary)] transition-transform duration-200 shrink-0',
|
|
27
|
+
open && 'rotate-90'
|
|
28
28
|
)}
|
|
29
29
|
/>
|
|
30
|
+
{title}
|
|
30
31
|
</button>
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
<div
|
|
33
|
+
className="grid transition-all duration-200"
|
|
34
|
+
style={{ gridTemplateRows: open ? '1fr' : '0fr' }}
|
|
35
|
+
>
|
|
36
|
+
<div className="overflow-hidden">
|
|
37
|
+
<div className="pl-5 pb-4 text-[0.8125rem] text-[var(--color-text-secondary)] leading-relaxed">
|
|
34
38
|
{children}
|
|
35
39
|
</div>
|
|
36
40
|
</div>
|
|
37
|
-
|
|
41
|
+
</div>
|
|
38
42
|
</div>
|
|
39
43
|
)
|
|
40
44
|
}
|
|
@@ -44,5 +48,5 @@ interface AccordionGroupProps {
|
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
export function AccordionGroup({ children }: AccordionGroupProps) {
|
|
47
|
-
return <div className="my-6
|
|
51
|
+
return <div className="my-6 border-t border-[var(--color-border)]">{children}</div>
|
|
48
52
|
}
|
|
@@ -18,74 +18,87 @@ interface CalloutProps {
|
|
|
18
18
|
|
|
19
19
|
const config: Record<CalloutType, {
|
|
20
20
|
icon: typeof InfoIcon
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
borderColor: string
|
|
22
|
+
iconColor: string
|
|
23
|
+
bgColor: string
|
|
24
|
+
titleColor: string
|
|
25
|
+
textColor: string
|
|
26
|
+
defaultTitle: string
|
|
25
27
|
}> = {
|
|
26
28
|
info: {
|
|
27
29
|
icon: InfoIcon,
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
borderColor: 'border-l-blue-500',
|
|
31
|
+
iconColor: 'text-blue-500',
|
|
32
|
+
bgColor: 'bg-blue-500/5',
|
|
33
|
+
titleColor: 'text-blue-600 dark:text-blue-400',
|
|
34
|
+
textColor: 'text-[var(--color-text-secondary)]',
|
|
35
|
+
defaultTitle: 'Info',
|
|
32
36
|
},
|
|
33
37
|
warning: {
|
|
34
38
|
icon: AlertTriangle,
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
borderColor: 'border-l-amber-500',
|
|
40
|
+
iconColor: 'text-amber-500',
|
|
41
|
+
bgColor: 'bg-amber-500/5',
|
|
42
|
+
titleColor: 'text-amber-600 dark:text-amber-400',
|
|
43
|
+
textColor: 'text-[var(--color-text-secondary)]',
|
|
44
|
+
defaultTitle: 'Warning',
|
|
39
45
|
},
|
|
40
46
|
success: {
|
|
41
47
|
icon: CheckCircle,
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
48
|
+
borderColor: 'border-l-emerald-500',
|
|
49
|
+
iconColor: 'text-emerald-500',
|
|
50
|
+
bgColor: 'bg-emerald-500/5',
|
|
51
|
+
titleColor: 'text-emerald-600 dark:text-emerald-400',
|
|
52
|
+
textColor: 'text-[var(--color-text-secondary)]',
|
|
53
|
+
defaultTitle: 'Success',
|
|
46
54
|
},
|
|
47
55
|
error: {
|
|
48
56
|
icon: XCircle,
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
57
|
+
borderColor: 'border-l-red-500',
|
|
58
|
+
iconColor: 'text-red-500',
|
|
59
|
+
bgColor: 'bg-red-500/5',
|
|
60
|
+
titleColor: 'text-red-600 dark:text-red-400',
|
|
61
|
+
textColor: 'text-[var(--color-text-secondary)]',
|
|
62
|
+
defaultTitle: 'Error',
|
|
53
63
|
},
|
|
54
64
|
tip: {
|
|
55
65
|
icon: Lightbulb,
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
66
|
+
borderColor: 'border-l-violet-500',
|
|
67
|
+
iconColor: 'text-violet-500',
|
|
68
|
+
bgColor: 'bg-violet-500/5',
|
|
69
|
+
titleColor: 'text-violet-600 dark:text-violet-400',
|
|
70
|
+
textColor: 'text-[var(--color-text-secondary)]',
|
|
71
|
+
defaultTitle: 'Tip',
|
|
60
72
|
},
|
|
61
73
|
note: {
|
|
62
74
|
icon: InfoIcon,
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
75
|
+
borderColor: 'border-l-[var(--color-border-strong)]',
|
|
76
|
+
iconColor: 'text-[var(--color-text-tertiary)]',
|
|
77
|
+
bgColor: 'bg-[var(--color-bg-secondary)]',
|
|
78
|
+
titleColor: 'text-[var(--color-text)]',
|
|
79
|
+
textColor: 'text-[var(--color-text-secondary)]',
|
|
80
|
+
defaultTitle: 'Note',
|
|
67
81
|
},
|
|
68
82
|
}
|
|
69
83
|
|
|
70
84
|
export function Callout({ type = 'info', title, children }: CalloutProps) {
|
|
71
|
-
const { icon: Icon,
|
|
85
|
+
const { icon: Icon, borderColor, iconColor, bgColor, titleColor, textColor, defaultTitle } = config[type]
|
|
72
86
|
|
|
73
87
|
return (
|
|
74
|
-
<div className={cn('my-6
|
|
88
|
+
<div className={cn('my-6 rounded-lg border-l-4 py-3 px-4', borderColor, bgColor)}>
|
|
75
89
|
<div className="flex gap-3">
|
|
76
|
-
<Icon className={cn('w-
|
|
77
|
-
<div>
|
|
78
|
-
{
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
<div className={cn('text-
|
|
90
|
+
<Icon className={cn('w-[18px] h-[18px] mt-0.5 flex-shrink-0', iconColor)} />
|
|
91
|
+
<div className="min-w-0">
|
|
92
|
+
<div className={cn('text-[0.8125rem] font-semibold mb-0.5', titleColor)}>
|
|
93
|
+
{title || defaultTitle}
|
|
94
|
+
</div>
|
|
95
|
+
<div className={cn('text-[0.8125rem] leading-relaxed [&>p]:my-1', textColor)}>{children}</div>
|
|
82
96
|
</div>
|
|
83
97
|
</div>
|
|
84
98
|
</div>
|
|
85
99
|
)
|
|
86
100
|
}
|
|
87
101
|
|
|
88
|
-
// Convenience components
|
|
89
102
|
export function Info(props: Omit<CalloutProps, 'type'>) {
|
|
90
103
|
return <Callout type="info" {...props} />
|
|
91
104
|
}
|