skrypt-ai 0.3.3 → 0.3.4
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/dist/cli.js +1 -1
- package/dist/template/next.config.mjs +2 -1
- package/dist/template/package.json +1 -0
- package/dist/template/src/app/docs/[...slug]/page.tsx +4 -0
- package/dist/template/src/app/layout.tsx +15 -7
- package/dist/template/src/app/page.tsx +61 -11
- package/dist/template/src/components/breadcrumbs.tsx +10 -10
- package/dist/template/src/components/docs-layout.tsx +62 -2
- package/dist/template/src/components/header.tsx +30 -18
- package/dist/template/src/components/mdx/accordion.tsx +18 -15
- package/dist/template/src/components/mdx/callout.tsx +50 -37
- package/dist/template/src/components/mdx/card.tsx +9 -9
- package/dist/template/src/components/mdx/code-group.tsx +31 -18
- package/dist/template/src/components/mdx/highlighted-code.tsx +62 -17
- package/dist/template/src/components/mdx/steps.tsx +6 -6
- package/dist/template/src/components/mdx/tabs.tsx +3 -3
- package/dist/template/src/components/search-dialog.tsx +79 -39
- package/dist/template/src/components/sidebar.tsx +13 -12
- package/dist/template/src/components/table-of-contents.tsx +7 -7
- package/dist/template/src/styles/globals.css +240 -68
- package/package.json +1 -1
|
@@ -14,19 +14,19 @@ export function Card({ title, icon, href, children }: CardProps) {
|
|
|
14
14
|
|
|
15
15
|
const content = (
|
|
16
16
|
<div className={cn(
|
|
17
|
-
'p-
|
|
18
|
-
href && 'hover:border-[var(--color-
|
|
17
|
+
'group p-5 border border-[var(--color-border)] rounded-xl transition-all duration-200',
|
|
18
|
+
href && 'hover:border-[var(--color-border-strong)] hover:shadow-sm hover:-translate-y-0.5 cursor-pointer'
|
|
19
19
|
)}>
|
|
20
|
-
<div className="flex items-start gap-3">
|
|
20
|
+
<div className="flex items-start gap-3.5">
|
|
21
21
|
{Icon && (
|
|
22
|
-
<div className="p-2 bg-[var(--color-primary)]
|
|
23
|
-
<Icon size={
|
|
22
|
+
<div className="shrink-0 p-2 bg-[var(--color-bg-tertiary)] text-[var(--color-text-secondary)] rounded-lg group-hover:bg-[var(--color-primary-light)] group-hover:text-[var(--color-primary)] transition-colors">
|
|
23
|
+
<Icon size={18} />
|
|
24
24
|
</div>
|
|
25
25
|
)}
|
|
26
26
|
<div>
|
|
27
|
-
<h3 className="font-
|
|
27
|
+
<h3 className="font-semibold text-[0.9375rem] text-[var(--color-text)] !mt-0 !mb-0">{title}</h3>
|
|
28
28
|
{children && (
|
|
29
|
-
<div className="mt-1 text-
|
|
29
|
+
<div className="mt-1.5 text-[0.8125rem] leading-relaxed text-[var(--color-text-secondary)]">
|
|
30
30
|
{children}
|
|
31
31
|
</div>
|
|
32
32
|
)}
|
|
@@ -36,7 +36,7 @@ export function Card({ title, icon, href, children }: CardProps) {
|
|
|
36
36
|
)
|
|
37
37
|
|
|
38
38
|
if (href) {
|
|
39
|
-
return <Link href={href}>{content}</Link>
|
|
39
|
+
return <Link href={href} className="hover:no-underline">{content}</Link>
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
return content
|
|
@@ -55,7 +55,7 @@ export function CardGroup({ cols = 2, children }: CardGroupProps) {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
return (
|
|
58
|
-
<div className={cn('grid gap-
|
|
58
|
+
<div className={cn('grid gap-3 my-6', gridCols[cols])}>
|
|
59
59
|
{children}
|
|
60
60
|
</div>
|
|
61
61
|
)
|
|
@@ -72,31 +72,46 @@ export function CodeGroup({ children }: CodeGroupProps) {
|
|
|
72
72
|
const labels: Record<string, string> = {
|
|
73
73
|
typescript: 'TypeScript',
|
|
74
74
|
javascript: 'JavaScript',
|
|
75
|
+
ts: 'TypeScript',
|
|
76
|
+
js: 'JavaScript',
|
|
77
|
+
tsx: 'TypeScript',
|
|
78
|
+
jsx: 'JavaScript',
|
|
75
79
|
python: 'Python',
|
|
76
|
-
|
|
77
|
-
|
|
80
|
+
py: 'Python',
|
|
81
|
+
bash: 'Terminal',
|
|
82
|
+
shell: 'Terminal',
|
|
83
|
+
sh: 'Terminal',
|
|
78
84
|
json: 'JSON',
|
|
79
85
|
yaml: 'YAML',
|
|
86
|
+
yml: 'YAML',
|
|
80
87
|
go: 'Go',
|
|
81
88
|
rust: 'Rust',
|
|
89
|
+
ruby: 'Ruby',
|
|
90
|
+
php: 'PHP',
|
|
91
|
+
java: 'Java',
|
|
92
|
+
csharp: 'C#',
|
|
93
|
+
cs: 'C#',
|
|
94
|
+
css: 'CSS',
|
|
95
|
+
html: 'HTML',
|
|
96
|
+
sql: 'SQL',
|
|
82
97
|
}
|
|
83
|
-
return labels[lang] || lang
|
|
98
|
+
return labels[lang] || lang.charAt(0).toUpperCase() + lang.slice(1)
|
|
84
99
|
}
|
|
85
100
|
|
|
86
101
|
return (
|
|
87
|
-
<div className="my-6 border border-[var(--color-border)]
|
|
102
|
+
<div className="my-6 rounded-xl overflow-hidden border border-[var(--color-border)] bg-[var(--color-code-bg)]">
|
|
88
103
|
{/* Tab bar */}
|
|
89
|
-
<div className="flex items-center justify-between
|
|
90
|
-
<div className="flex">
|
|
104
|
+
<div className="flex items-center justify-between border-b border-white/[0.06]">
|
|
105
|
+
<div className="flex gap-0 px-1 pt-1">
|
|
91
106
|
{blocks.map((block, index) => (
|
|
92
107
|
<button
|
|
93
108
|
key={`${block.language}-${block.filename || index}`}
|
|
94
109
|
onClick={() => setActiveIndex(index)}
|
|
95
110
|
className={cn(
|
|
96
|
-
'px-
|
|
111
|
+
'px-3 py-1.5 text-[0.75rem] font-medium rounded-t-lg transition-colors',
|
|
97
112
|
index === activeIndex
|
|
98
|
-
? 'bg-[
|
|
99
|
-
: 'text-
|
|
113
|
+
? 'bg-white/[0.06] text-[var(--color-code-text)]'
|
|
114
|
+
: 'text-white/40 hover:text-white/60'
|
|
100
115
|
)}
|
|
101
116
|
>
|
|
102
117
|
{getLanguageLabel(block.language, block.filename)}
|
|
@@ -105,21 +120,19 @@ export function CodeGroup({ children }: CodeGroupProps) {
|
|
|
105
120
|
</div>
|
|
106
121
|
<button
|
|
107
122
|
onClick={copyToClipboard}
|
|
108
|
-
className="p-2 mr-2 text-
|
|
123
|
+
className="p-2 mr-2 text-white/30 hover:text-white/60 transition-colors"
|
|
109
124
|
title="Copy code"
|
|
110
125
|
>
|
|
111
|
-
{copied ? <Check size={
|
|
126
|
+
{copied ? <Check size={14} /> : <Copy size={14} />}
|
|
112
127
|
</button>
|
|
113
128
|
</div>
|
|
114
129
|
|
|
115
130
|
{/* Code content */}
|
|
116
|
-
<
|
|
117
|
-
<
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
</pre>
|
|
122
|
-
</div>
|
|
131
|
+
<pre className="!m-0 !rounded-none !border-0 !bg-transparent">
|
|
132
|
+
<code className={`language-${activeBlock.language}`}>
|
|
133
|
+
{activeBlock.code}
|
|
134
|
+
</code>
|
|
135
|
+
</pre>
|
|
123
136
|
</div>
|
|
124
137
|
)
|
|
125
138
|
}
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect, useContext, ReactNode, isValidElement, Children } from 'react'
|
|
4
|
-
import { Copy, Check } from 'lucide-react'
|
|
4
|
+
import { Copy, Check, FileCode } from 'lucide-react'
|
|
5
5
|
import { highlight, DEFAULT_THEME, type ThemeName } from '@/lib/highlight'
|
|
6
|
-
|
|
7
|
-
// Import the context directly to use useContext safely
|
|
8
6
|
import { SyntaxThemeContext } from '@/contexts/syntax-theme'
|
|
9
7
|
|
|
10
8
|
interface HighlightedCodeProps {
|
|
@@ -12,27 +10,50 @@ interface HighlightedCodeProps {
|
|
|
12
10
|
className?: string
|
|
13
11
|
}
|
|
14
12
|
|
|
13
|
+
const langLabels: Record<string, string> = {
|
|
14
|
+
typescript: 'TypeScript',
|
|
15
|
+
javascript: 'JavaScript',
|
|
16
|
+
ts: 'TypeScript',
|
|
17
|
+
js: 'JavaScript',
|
|
18
|
+
tsx: 'TSX',
|
|
19
|
+
jsx: 'JSX',
|
|
20
|
+
python: 'Python',
|
|
21
|
+
py: 'Python',
|
|
22
|
+
bash: 'Terminal',
|
|
23
|
+
shell: 'Terminal',
|
|
24
|
+
sh: 'Terminal',
|
|
25
|
+
json: 'JSON',
|
|
26
|
+
yaml: 'YAML',
|
|
27
|
+
go: 'Go',
|
|
28
|
+
rust: 'Rust',
|
|
29
|
+
css: 'CSS',
|
|
30
|
+
html: 'HTML',
|
|
31
|
+
sql: 'SQL',
|
|
32
|
+
text: '',
|
|
33
|
+
}
|
|
34
|
+
|
|
15
35
|
export function HighlightedCode({ children, className }: HighlightedCodeProps) {
|
|
16
36
|
const [copied, setCopied] = useState(false)
|
|
17
37
|
const [highlightedHtml, setHighlightedHtml] = useState<string | null>(null)
|
|
18
38
|
|
|
19
|
-
// Use context safely - fallback to default if not available
|
|
20
39
|
const syntaxContext = useContext(SyntaxThemeContext)
|
|
21
40
|
const theme: ThemeName = syntaxContext?.theme ?? DEFAULT_THEME
|
|
22
41
|
|
|
23
|
-
// Extract code text and language from children
|
|
24
42
|
let codeText = ''
|
|
25
43
|
let language = 'text'
|
|
44
|
+
let filename = ''
|
|
26
45
|
|
|
27
46
|
Children.forEach(children, (child) => {
|
|
28
47
|
if (isValidElement(child) && child.type === 'code') {
|
|
29
|
-
const props = child.props as { children?: string; className?: string }
|
|
48
|
+
const props = child.props as { children?: string; className?: string; 'data-filename'?: string }
|
|
30
49
|
codeText = props.children || ''
|
|
31
|
-
// Extract language from className like "language-typescript"
|
|
32
50
|
const classMatch = props.className?.match(/language-(\w+)/)
|
|
33
51
|
if (classMatch) {
|
|
34
52
|
language = classMatch[1]
|
|
35
53
|
}
|
|
54
|
+
if (props['data-filename']) {
|
|
55
|
+
filename = props['data-filename']
|
|
56
|
+
}
|
|
36
57
|
}
|
|
37
58
|
})
|
|
38
59
|
|
|
@@ -65,25 +86,49 @@ export function HighlightedCode({ children, className }: HighlightedCodeProps) {
|
|
|
65
86
|
setTimeout(() => setCopied(false), 2000)
|
|
66
87
|
}
|
|
67
88
|
|
|
89
|
+
const langLabel = filename || langLabels[language] || language
|
|
90
|
+
const showHeader = langLabel && language !== 'text'
|
|
91
|
+
|
|
68
92
|
return (
|
|
69
|
-
<div className="relative group my-
|
|
93
|
+
<div className="relative group my-5 rounded-xl overflow-hidden border border-[var(--color-border)] bg-[var(--color-code-bg)]">
|
|
94
|
+
{/* Filename / language header bar */}
|
|
95
|
+
{showHeader && (
|
|
96
|
+
<div className="flex items-center justify-between px-4 py-2 border-b border-white/[0.06]">
|
|
97
|
+
<div className="flex items-center gap-2 text-[0.75rem] text-white/40">
|
|
98
|
+
<FileCode size={13} />
|
|
99
|
+
<span>{langLabel}</span>
|
|
100
|
+
</div>
|
|
101
|
+
<button
|
|
102
|
+
onClick={handleCopy}
|
|
103
|
+
className="p-1 text-white/30 hover:text-white/60 transition-colors"
|
|
104
|
+
title={copied ? 'Copied!' : 'Copy code'}
|
|
105
|
+
>
|
|
106
|
+
{copied ? <Check size={13} /> : <Copy size={13} />}
|
|
107
|
+
</button>
|
|
108
|
+
</div>
|
|
109
|
+
)}
|
|
110
|
+
|
|
70
111
|
{highlightedHtml ? (
|
|
71
112
|
<div
|
|
72
|
-
className="[&>pre]
|
|
113
|
+
className="[&>pre]:!rounded-none [&>pre]:!border-0 [&>pre]:!m-0 [&>pre]:px-4 [&>pre]:py-3 [&>pre]:overflow-x-auto [&>pre]:!bg-transparent"
|
|
73
114
|
dangerouslySetInnerHTML={{ __html: highlightedHtml }}
|
|
74
115
|
/>
|
|
75
116
|
) : (
|
|
76
|
-
<pre className=
|
|
117
|
+
<pre className="!rounded-none !border-0 !m-0 px-4 py-3 overflow-x-auto !bg-transparent">
|
|
77
118
|
{children}
|
|
78
119
|
</pre>
|
|
79
120
|
)}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
121
|
+
|
|
122
|
+
{/* Copy button fallback (no header) */}
|
|
123
|
+
{!showHeader && (
|
|
124
|
+
<button
|
|
125
|
+
onClick={handleCopy}
|
|
126
|
+
className="absolute top-2.5 right-2.5 p-1.5 rounded-md text-white/30 hover:text-white/60 opacity-0 group-hover:opacity-100 transition-all"
|
|
127
|
+
title={copied ? 'Copied!' : 'Copy code'}
|
|
128
|
+
>
|
|
129
|
+
{copied ? <Check size={13} /> : <Copy size={13} />}
|
|
130
|
+
</button>
|
|
131
|
+
)}
|
|
87
132
|
</div>
|
|
88
133
|
)
|
|
89
134
|
}
|
|
@@ -8,18 +8,18 @@ export function Steps({ children }: StepsProps) {
|
|
|
8
8
|
const items = Children.toArray(children).filter(isValidElement)
|
|
9
9
|
|
|
10
10
|
return (
|
|
11
|
-
<div className="my-
|
|
11
|
+
<div className="my-8 relative">
|
|
12
12
|
{items.map((child, index) => (
|
|
13
|
-
<div key={`step-${index}`} className="flex gap-4">
|
|
13
|
+
<div key={`step-${index}`} className="flex gap-4 pb-8 last:pb-0">
|
|
14
14
|
<div className="flex flex-col items-center">
|
|
15
|
-
<div className="flex items-center justify-center w-
|
|
15
|
+
<div className="flex items-center justify-center w-7 h-7 rounded-full bg-[var(--color-text)] text-[var(--color-bg)] text-xs font-semibold shrink-0">
|
|
16
16
|
{index + 1}
|
|
17
17
|
</div>
|
|
18
18
|
{index < items.length - 1 && (
|
|
19
19
|
<div className="flex-1 w-px bg-[var(--color-border)] mt-2" />
|
|
20
20
|
)}
|
|
21
21
|
</div>
|
|
22
|
-
<div className="flex-1
|
|
22
|
+
<div className="flex-1 pt-0.5">
|
|
23
23
|
{child}
|
|
24
24
|
</div>
|
|
25
25
|
</div>
|
|
@@ -36,8 +36,8 @@ interface StepProps {
|
|
|
36
36
|
export function Step({ title, children }: StepProps) {
|
|
37
37
|
return (
|
|
38
38
|
<div>
|
|
39
|
-
<h3 className="font-
|
|
40
|
-
<div className="text-[var(--color-text-secondary)]">{children}</div>
|
|
39
|
+
<h3 className="!text-[0.9375rem] font-semibold text-[var(--color-text)] !mt-0 mb-2">{title}</h3>
|
|
40
|
+
<div className="text-[0.8125rem] text-[var(--color-text-secondary)] leading-relaxed">{children}</div>
|
|
41
41
|
</div>
|
|
42
42
|
)
|
|
43
43
|
}
|
|
@@ -31,7 +31,7 @@ interface TabListProps {
|
|
|
31
31
|
|
|
32
32
|
export function TabList({ children }: TabListProps) {
|
|
33
33
|
return (
|
|
34
|
-
<div className="flex border-b border-[var(--color-border)]">
|
|
34
|
+
<div className="flex gap-0.5 border-b border-[var(--color-border)]">
|
|
35
35
|
{children}
|
|
36
36
|
</div>
|
|
37
37
|
)
|
|
@@ -53,10 +53,10 @@ export function Tab({ value, children }: TabProps) {
|
|
|
53
53
|
<button
|
|
54
54
|
onClick={() => setActiveTab(value)}
|
|
55
55
|
className={cn(
|
|
56
|
-
'px-
|
|
56
|
+
'px-3 py-2 text-[0.8125rem] font-medium transition-colors border-b-2 -mb-px',
|
|
57
57
|
isActive
|
|
58
58
|
? 'border-[var(--color-primary)] text-[var(--color-primary)]'
|
|
59
|
-
: 'border-transparent text-[var(--color-text-
|
|
59
|
+
: 'border-transparent text-[var(--color-text-tertiary)] hover:text-[var(--color-text-secondary)]'
|
|
60
60
|
)}
|
|
61
61
|
>
|
|
62
62
|
{children}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import React, { useEffect, useState, useCallback, useRef } from 'react'
|
|
4
|
-
import { Search,
|
|
4
|
+
import { Search, FileText, ArrowRight } from 'lucide-react'
|
|
5
5
|
import Link from 'next/link'
|
|
6
6
|
import { cn } from '@/lib/utils'
|
|
7
7
|
import { search as performSearch, SearchResultWithHighlight } from '@/lib/search'
|
|
@@ -11,9 +11,6 @@ interface SearchDialogProps {
|
|
|
11
11
|
onClose: () => void
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
/**
|
|
15
|
-
* Highlight search terms in text
|
|
16
|
-
*/
|
|
17
14
|
function highlightTerms(text: string, query: string): React.ReactNode {
|
|
18
15
|
if (!query.trim()) return text
|
|
19
16
|
|
|
@@ -24,7 +21,7 @@ function highlightTerms(text: string, query: string): React.ReactNode {
|
|
|
24
21
|
|
|
25
22
|
return parts.map((part, i) =>
|
|
26
23
|
terms.some(t => part.toLowerCase() === t) ? (
|
|
27
|
-
<mark key={i} className="bg-
|
|
24
|
+
<mark key={i} className="bg-[var(--color-primary)]/20 text-[var(--color-primary)] rounded px-0.5">
|
|
28
25
|
{part}
|
|
29
26
|
</mark>
|
|
30
27
|
) : (
|
|
@@ -37,14 +34,17 @@ export function SearchDialog({ open, onClose }: SearchDialogProps) {
|
|
|
37
34
|
const [query, setQuery] = useState('')
|
|
38
35
|
const [results, setResults] = useState<SearchResultWithHighlight[]>([])
|
|
39
36
|
const [isLoading, setIsLoading] = useState(false)
|
|
37
|
+
const [selectedIndex, setSelectedIndex] = useState(0)
|
|
40
38
|
const dialogRef = useRef<HTMLDivElement>(null)
|
|
41
39
|
const inputRef = useRef<HTMLInputElement>(null)
|
|
42
40
|
|
|
43
|
-
// Focus trap and restore focus on close
|
|
44
41
|
useEffect(() => {
|
|
45
42
|
if (open) {
|
|
46
43
|
const previouslyFocused = document.activeElement as HTMLElement
|
|
47
44
|
inputRef.current?.focus()
|
|
45
|
+
setQuery('')
|
|
46
|
+
setResults([])
|
|
47
|
+
setSelectedIndex(0)
|
|
48
48
|
|
|
49
49
|
return () => {
|
|
50
50
|
previouslyFocused?.focus()
|
|
@@ -62,6 +62,7 @@ export function SearchDialog({ open, onClose }: SearchDialogProps) {
|
|
|
62
62
|
try {
|
|
63
63
|
const searchResults = await performSearch(q)
|
|
64
64
|
setResults(searchResults)
|
|
65
|
+
setSelectedIndex(0)
|
|
65
66
|
} catch (err) {
|
|
66
67
|
console.error('Search failed:', err)
|
|
67
68
|
setResults([])
|
|
@@ -71,7 +72,7 @@ export function SearchDialog({ open, onClose }: SearchDialogProps) {
|
|
|
71
72
|
}, [])
|
|
72
73
|
|
|
73
74
|
useEffect(() => {
|
|
74
|
-
const debounce = setTimeout(() => search(query),
|
|
75
|
+
const debounce = setTimeout(() => search(query), 150)
|
|
75
76
|
return () => clearTimeout(debounce)
|
|
76
77
|
}, [query, search])
|
|
77
78
|
|
|
@@ -80,7 +81,6 @@ export function SearchDialog({ open, onClose }: SearchDialogProps) {
|
|
|
80
81
|
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
|
|
81
82
|
e.preventDefault()
|
|
82
83
|
if (open) onClose()
|
|
83
|
-
else onClose() // This should trigger open, but we don't have that control here
|
|
84
84
|
}
|
|
85
85
|
if (e.key === 'Escape' && open) {
|
|
86
86
|
onClose()
|
|
@@ -91,13 +91,26 @@ export function SearchDialog({ open, onClose }: SearchDialogProps) {
|
|
|
91
91
|
return () => document.removeEventListener('keydown', handleKeyDown)
|
|
92
92
|
}, [open, onClose])
|
|
93
93
|
|
|
94
|
+
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
95
|
+
if (e.key === 'ArrowDown') {
|
|
96
|
+
e.preventDefault()
|
|
97
|
+
setSelectedIndex((prev) => Math.min(prev + 1, results.length - 1))
|
|
98
|
+
} else if (e.key === 'ArrowUp') {
|
|
99
|
+
e.preventDefault()
|
|
100
|
+
setSelectedIndex((prev) => Math.max(prev - 1, 0))
|
|
101
|
+
} else if (e.key === 'Enter' && results[selectedIndex]) {
|
|
102
|
+
onClose()
|
|
103
|
+
window.location.href = results[selectedIndex].href
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
94
107
|
if (!open) return null
|
|
95
108
|
|
|
96
109
|
return (
|
|
97
110
|
<div className="fixed inset-0 z-50" role="presentation">
|
|
98
111
|
{/* Backdrop */}
|
|
99
112
|
<div
|
|
100
|
-
className="absolute inset-0 bg-black/
|
|
113
|
+
className="absolute inset-0 bg-black/60 backdrop-blur-sm"
|
|
101
114
|
onClick={onClose}
|
|
102
115
|
aria-hidden="true"
|
|
103
116
|
/>
|
|
@@ -108,69 +121,96 @@ export function SearchDialog({ open, onClose }: SearchDialogProps) {
|
|
|
108
121
|
role="dialog"
|
|
109
122
|
aria-modal="true"
|
|
110
123
|
aria-label="Search documentation"
|
|
111
|
-
className="absolute top-[
|
|
124
|
+
className="absolute top-[15%] left-1/2 -translate-x-1/2 w-full max-w-lg px-4"
|
|
112
125
|
>
|
|
113
126
|
<div className="bg-[var(--color-bg)] border border-[var(--color-border)] rounded-xl shadow-2xl overflow-hidden">
|
|
114
127
|
{/* Search input */}
|
|
115
128
|
<div className="flex items-center gap-3 px-4 border-b border-[var(--color-border)]">
|
|
116
|
-
<Search size={
|
|
129
|
+
<Search size={16} className="text-[var(--color-text-tertiary)] shrink-0" />
|
|
117
130
|
<input
|
|
118
131
|
ref={inputRef}
|
|
119
132
|
type="text"
|
|
120
133
|
value={query}
|
|
121
134
|
onChange={(e) => setQuery(e.target.value)}
|
|
135
|
+
onKeyDown={handleKeyDown}
|
|
122
136
|
placeholder="Search documentation..."
|
|
123
|
-
className="flex-1 py-
|
|
137
|
+
className="flex-1 py-3.5 bg-transparent outline-none text-[0.875rem] text-[var(--color-text)] placeholder:text-[var(--color-text-tertiary)]"
|
|
124
138
|
aria-label="Search query"
|
|
125
139
|
/>
|
|
126
|
-
<
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
aria-label="Close search"
|
|
130
|
-
>
|
|
131
|
-
<X size={20} className="text-[var(--color-text-tertiary)]" />
|
|
132
|
-
</button>
|
|
140
|
+
<kbd className="hidden sm:inline px-1.5 py-0.5 text-[0.625rem] font-medium text-[var(--color-text-tertiary)] bg-[var(--color-bg-secondary)] border border-[var(--color-border)] rounded">
|
|
141
|
+
ESC
|
|
142
|
+
</kbd>
|
|
133
143
|
</div>
|
|
134
144
|
|
|
135
145
|
{/* Results */}
|
|
136
|
-
<div className="max-h-
|
|
146
|
+
<div className="max-h-[60vh] overflow-y-auto">
|
|
137
147
|
{results.length > 0 ? (
|
|
138
|
-
<ul className="p-
|
|
139
|
-
{results.map((result) => (
|
|
148
|
+
<ul className="p-1.5">
|
|
149
|
+
{results.map((result, index) => (
|
|
140
150
|
<li key={result.href}>
|
|
141
151
|
<Link
|
|
142
152
|
href={result.href}
|
|
143
153
|
onClick={onClose}
|
|
144
|
-
className=
|
|
154
|
+
className={cn(
|
|
155
|
+
'flex items-center gap-3 px-3 py-2.5 rounded-lg transition-colors hover:no-underline group',
|
|
156
|
+
index === selectedIndex
|
|
157
|
+
? 'bg-[var(--color-primary)] text-white'
|
|
158
|
+
: 'hover:bg-[var(--color-bg-secondary)]'
|
|
159
|
+
)}
|
|
145
160
|
>
|
|
146
|
-
<FileText size={
|
|
147
|
-
|
|
148
|
-
|
|
161
|
+
<FileText size={16} className={cn(
|
|
162
|
+
'shrink-0',
|
|
163
|
+
index === selectedIndex ? 'text-white/70' : 'text-[var(--color-text-tertiary)]'
|
|
164
|
+
)} />
|
|
165
|
+
<div className="flex-1 min-w-0">
|
|
166
|
+
<div className={cn(
|
|
167
|
+
'text-[0.8125rem] font-medium truncate',
|
|
168
|
+
index === selectedIndex ? 'text-white' : 'text-[var(--color-text)]'
|
|
169
|
+
)}>
|
|
149
170
|
{result.title}
|
|
150
171
|
</div>
|
|
151
|
-
{result.
|
|
152
|
-
<div className=
|
|
153
|
-
|
|
172
|
+
{result.snippet && (
|
|
173
|
+
<div className={cn(
|
|
174
|
+
'text-[0.75rem] truncate mt-0.5',
|
|
175
|
+
index === selectedIndex ? 'text-white/70' : 'text-[var(--color-text-tertiary)]'
|
|
176
|
+
)}>
|
|
177
|
+
{index === selectedIndex ? result.snippet : highlightTerms(result.snippet, query)}
|
|
154
178
|
</div>
|
|
155
179
|
)}
|
|
156
|
-
<div className="text-sm text-[var(--color-text-secondary)] line-clamp-2">
|
|
157
|
-
{highlightTerms(result.snippet, query)}
|
|
158
|
-
</div>
|
|
159
180
|
</div>
|
|
181
|
+
<ArrowRight size={14} className={cn(
|
|
182
|
+
'shrink-0 opacity-0 group-hover:opacity-100 transition-opacity',
|
|
183
|
+
index === selectedIndex ? 'text-white/70 opacity-100' : 'text-[var(--color-text-tertiary)]'
|
|
184
|
+
)} />
|
|
160
185
|
</Link>
|
|
161
186
|
</li>
|
|
162
187
|
))}
|
|
163
188
|
</ul>
|
|
164
|
-
) : query ? (
|
|
165
|
-
<div className="p-8 text-center text-[var(--color-text-
|
|
166
|
-
No results
|
|
189
|
+
) : query && !isLoading ? (
|
|
190
|
+
<div className="p-8 text-center text-[0.8125rem] text-[var(--color-text-tertiary)]">
|
|
191
|
+
No results for “{query}”
|
|
167
192
|
</div>
|
|
168
|
-
) : (
|
|
169
|
-
<div className="p-
|
|
170
|
-
|
|
193
|
+
) : !query ? (
|
|
194
|
+
<div className="p-6 text-center text-[0.8125rem] text-[var(--color-text-tertiary)]">
|
|
195
|
+
Type to search the docs
|
|
171
196
|
</div>
|
|
172
|
-
)}
|
|
197
|
+
) : null}
|
|
173
198
|
</div>
|
|
199
|
+
|
|
200
|
+
{/* Footer */}
|
|
201
|
+
{results.length > 0 && (
|
|
202
|
+
<div className="flex items-center gap-4 px-4 py-2 border-t border-[var(--color-border)] text-[0.6875rem] text-[var(--color-text-tertiary)]">
|
|
203
|
+
<span className="flex items-center gap-1">
|
|
204
|
+
<kbd className="px-1 py-0.5 bg-[var(--color-bg-secondary)] border border-[var(--color-border)] rounded text-[0.5625rem]">↑</kbd>
|
|
205
|
+
<kbd className="px-1 py-0.5 bg-[var(--color-bg-secondary)] border border-[var(--color-border)] rounded text-[0.5625rem]">↓</kbd>
|
|
206
|
+
navigate
|
|
207
|
+
</span>
|
|
208
|
+
<span className="flex items-center gap-1">
|
|
209
|
+
<kbd className="px-1 py-0.5 bg-[var(--color-bg-secondary)] border border-[var(--color-border)] rounded text-[0.5625rem]">↵</kbd>
|
|
210
|
+
open
|
|
211
|
+
</span>
|
|
212
|
+
</div>
|
|
213
|
+
)}
|
|
174
214
|
</div>
|
|
175
215
|
</div>
|
|
176
216
|
</div>
|
|
@@ -51,7 +51,7 @@ export function Sidebar({ open, onClose, docsConfig }: SidebarProps) {
|
|
|
51
51
|
{/* Mobile overlay */}
|
|
52
52
|
{open && (
|
|
53
53
|
<div
|
|
54
|
-
className="fixed inset-0 z-40 bg-black/50 md:hidden"
|
|
54
|
+
className="fixed inset-0 z-40 bg-black/50 backdrop-blur-sm md:hidden"
|
|
55
55
|
onClick={onClose}
|
|
56
56
|
/>
|
|
57
57
|
)}
|
|
@@ -59,12 +59,12 @@ export function Sidebar({ open, onClose, docsConfig }: SidebarProps) {
|
|
|
59
59
|
{/* Sidebar */}
|
|
60
60
|
<aside
|
|
61
61
|
className={cn(
|
|
62
|
-
'fixed left-0 top-
|
|
62
|
+
'fixed left-0 top-[var(--header-height)] bottom-0 w-[var(--sidebar-width)] border-r border-[var(--color-border)] bg-[var(--color-bg)] overflow-y-auto z-50 transition-transform duration-200',
|
|
63
63
|
'md:translate-x-0',
|
|
64
64
|
open ? 'translate-x-0' : '-translate-x-full md:translate-x-0'
|
|
65
65
|
)}
|
|
66
66
|
>
|
|
67
|
-
<nav className="
|
|
67
|
+
<nav className="px-3 py-4 space-y-6">
|
|
68
68
|
{navigation.map((section) => (
|
|
69
69
|
<NavSection key={section.title} section={section} pathname={pathname} onNavigate={onClose} />
|
|
70
70
|
))}
|
|
@@ -75,6 +75,7 @@ export function Sidebar({ open, onClose, docsConfig }: SidebarProps) {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
function NavSection({ section, pathname, onNavigate }: { section: NavItem; pathname: string; onNavigate?: () => void }) {
|
|
78
|
+
const hasActiveChild = section.items?.some((item) => pathname === item.href)
|
|
78
79
|
const [expanded, setExpanded] = useState(true)
|
|
79
80
|
|
|
80
81
|
if (!section.items) {
|
|
@@ -83,9 +84,9 @@ function NavSection({ section, pathname, onNavigate }: { section: NavItem; pathn
|
|
|
83
84
|
href={section.href || '#'}
|
|
84
85
|
onClick={onNavigate}
|
|
85
86
|
className={cn(
|
|
86
|
-
'block px-3 py-
|
|
87
|
+
'block px-3 py-1.5 text-sm rounded-lg transition-colors',
|
|
87
88
|
pathname === section.href
|
|
88
|
-
? 'bg-[var(--color-primary)]
|
|
89
|
+
? 'bg-[var(--color-primary-light)] text-[var(--color-primary)] font-medium'
|
|
89
90
|
: 'text-[var(--color-text-secondary)] hover:text-[var(--color-text)] hover:bg-[var(--color-bg-secondary)]'
|
|
90
91
|
)}
|
|
91
92
|
>
|
|
@@ -98,26 +99,26 @@ function NavSection({ section, pathname, onNavigate }: { section: NavItem; pathn
|
|
|
98
99
|
<div>
|
|
99
100
|
<button
|
|
100
101
|
onClick={() => setExpanded(!expanded)}
|
|
101
|
-
className="flex items-center justify-between w-full px-3 py-
|
|
102
|
+
className="flex items-center justify-between w-full px-3 py-1.5 text-xs font-semibold uppercase tracking-wider text-[var(--color-text-tertiary)] hover:text-[var(--color-text-secondary)] transition-colors"
|
|
102
103
|
>
|
|
103
104
|
{section.title}
|
|
104
105
|
<ChevronRight
|
|
105
|
-
size={
|
|
106
|
-
className={cn('transition-transform', expanded && 'rotate-90')}
|
|
106
|
+
size={14}
|
|
107
|
+
className={cn('transition-transform duration-200', expanded && 'rotate-90')}
|
|
107
108
|
/>
|
|
108
109
|
</button>
|
|
109
110
|
{expanded && (
|
|
110
|
-
<div className="mt-1
|
|
111
|
+
<div className="mt-1 space-y-0.5">
|
|
111
112
|
{section.items.map((item) => (
|
|
112
113
|
<Link
|
|
113
114
|
key={item.href}
|
|
114
115
|
href={item.href || '#'}
|
|
115
116
|
onClick={onNavigate}
|
|
116
117
|
className={cn(
|
|
117
|
-
'block
|
|
118
|
+
'block pl-3 ml-3 py-1.5 text-[0.8125rem] rounded-lg transition-colors border-l-2',
|
|
118
119
|
pathname === item.href
|
|
119
|
-
? '
|
|
120
|
-
: 'text-[var(--color-text-secondary)] hover:text-[var(--color-text)]'
|
|
120
|
+
? 'border-[var(--color-primary)] text-[var(--color-primary)] font-medium bg-[var(--color-primary-light)]'
|
|
121
|
+
: 'border-transparent text-[var(--color-text-secondary)] hover:text-[var(--color-text)] hover:border-[var(--color-border-strong)]'
|
|
121
122
|
)}
|
|
122
123
|
>
|
|
123
124
|
{item.title}
|
|
@@ -57,21 +57,21 @@ export function TableOfContents() {
|
|
|
57
57
|
if (headings.length === 0) return null
|
|
58
58
|
|
|
59
59
|
return (
|
|
60
|
-
<nav className="hidden xl:block fixed right-8 top-
|
|
61
|
-
<p className="text-[
|
|
60
|
+
<nav className="hidden xl:block fixed right-8 top-[calc(var(--header-height)+2rem)] w-[var(--toc-width)] max-h-[calc(100vh-var(--header-height)-4rem)] overflow-y-auto">
|
|
61
|
+
<p className="text-[0.6875rem] font-semibold uppercase tracking-widest text-[var(--color-text-tertiary)] mb-3">
|
|
62
62
|
On this page
|
|
63
63
|
</p>
|
|
64
|
-
<ul className="space-y-
|
|
64
|
+
<ul className="space-y-0.5 border-l border-[var(--color-border)]">
|
|
65
65
|
{headings.map((heading) => (
|
|
66
66
|
<li key={heading.id}>
|
|
67
67
|
<a
|
|
68
68
|
href={`#${heading.id}`}
|
|
69
69
|
className={cn(
|
|
70
|
-
'block transition-colors hover:text-[var(--color-text)]',
|
|
71
|
-
heading.level ===
|
|
70
|
+
'block py-1 text-[0.8125rem] leading-snug transition-colors border-l-2 -ml-px hover:text-[var(--color-text)] hover:no-underline',
|
|
71
|
+
heading.level === 2 ? 'pl-4' : 'pl-7',
|
|
72
72
|
activeId === heading.id
|
|
73
|
-
? 'text-[var(--color-primary)] font-medium'
|
|
74
|
-
: 'text-[var(--color-text-tertiary)]'
|
|
73
|
+
? 'border-[var(--color-primary)] text-[var(--color-primary)] font-medium'
|
|
74
|
+
: 'border-transparent text-[var(--color-text-tertiary)]'
|
|
75
75
|
)}
|
|
76
76
|
>
|
|
77
77
|
{heading.text}
|