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.
@@ -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-4 border border-[var(--color-border)] rounded-lg transition-colors',
18
- href && 'hover:border-[var(--color-primary)] hover:bg-[var(--color-bg-secondary)] cursor-pointer'
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)]/10 text-[var(--color-primary)] rounded-lg">
23
- <Icon size={20} />
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-medium text-[var(--color-text)]">{title}</h3>
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-sm text-[var(--color-text-secondary)]">
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-4 my-6', gridCols[cols])}>
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
- bash: 'Bash',
77
- shell: 'Shell',
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)] rounded-lg overflow-hidden">
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 bg-[var(--color-bg-secondary)] border-b border-[var(--color-border)]">
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-4 py-2 text-sm font-medium transition-colors',
111
+ 'px-3 py-1.5 text-[0.75rem] font-medium rounded-t-lg transition-colors',
97
112
  index === activeIndex
98
- ? 'bg-[var(--color-code-bg)] text-[var(--color-code-text)]'
99
- : 'text-[var(--color-text-secondary)] hover:text-[var(--color-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-[var(--color-text-tertiary)] hover:text-[var(--color-text)] transition-colors"
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={16} /> : <Copy size={16} />}
126
+ {copied ? <Check size={14} /> : <Copy size={14} />}
112
127
  </button>
113
128
  </div>
114
129
 
115
130
  {/* Code content */}
116
- <div className="bg-[var(--color-code-bg)]">
117
- <pre className="!m-0 !rounded-none">
118
- <code className={`language-${activeBlock.language}`}>
119
- {activeBlock.code}
120
- </code>
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-4">
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]:rounded-lg [&>pre]:p-4 [&>pre]:overflow-x-auto"
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={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
- <button
81
- onClick={handleCopy}
82
- className="absolute top-2 right-2 p-1.5 rounded bg-[var(--color-bg-tertiary)]/80 text-[var(--color-text-tertiary)] hover:text-[var(--color-text)] opacity-0 group-hover:opacity-100 transition-opacity"
83
- title={copied ? 'Copied!' : 'Copy code'}
84
- >
85
- {copied ? <Check size={14} /> : <Copy size={14} />}
86
- </button>
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-6 space-y-6">
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-8 h-8 rounded-full bg-[var(--color-primary)] text-white text-sm font-medium">
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 pb-6">
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-medium text-[var(--color-text)] mb-2">{title}</h3>
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-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-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-secondary)] hover: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, X, FileText } from 'lucide-react'
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-yellow-200 dark:bg-yellow-800 text-inherit rounded px-0.5">
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), 200)
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/50 backdrop-blur-sm"
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-[20%] left-1/2 -translate-x-1/2 w-full max-w-xl"
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={20} className="text-[var(--color-text-tertiary)]" />
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-4 bg-transparent outline-none text-[var(--color-text)] placeholder:text-[var(--color-text-tertiary)]"
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
- <button
127
- onClick={onClose}
128
- className="p-1 hover:bg-[var(--color-bg-secondary)] rounded"
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-80 overflow-y-auto">
146
+ <div className="max-h-[60vh] overflow-y-auto">
137
147
  {results.length > 0 ? (
138
- <ul className="p-2">
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="flex items-start gap-3 px-3 py-2 rounded-lg hover:bg-[var(--color-bg-secondary)] transition-colors"
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={20} className="text-[var(--color-text-tertiary)] mt-0.5" />
147
- <div>
148
- <div className="font-medium text-[var(--color-text)]">
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.section && (
152
- <div className="text-xs text-[var(--color-text-tertiary)]">
153
- {result.section}
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-secondary)]">
166
- No results found for "{query}"
189
+ ) : query && !isLoading ? (
190
+ <div className="p-8 text-center text-[0.8125rem] text-[var(--color-text-tertiary)]">
191
+ No results for &ldquo;{query}&rdquo;
167
192
  </div>
168
- ) : (
169
- <div className="p-8 text-center text-[var(--color-text-tertiary)]">
170
- Start typing to search...
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]">&uarr;</kbd>
205
+ <kbd className="px-1 py-0.5 bg-[var(--color-bg-secondary)] border border-[var(--color-border)] rounded text-[0.5625rem]">&darr;</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]">&crarr;</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-16 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',
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="p-4 space-y-6">
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-2 text-sm rounded-md transition-colors',
87
+ 'block px-3 py-1.5 text-sm rounded-lg transition-colors',
87
88
  pathname === section.href
88
- ? 'bg-[var(--color-primary)]/10 text-[var(--color-primary)] font-medium'
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-2 text-sm font-medium text-[var(--color-text)] hover:bg-[var(--color-bg-secondary)] rounded-md transition-colors"
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={16}
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 ml-3 space-y-1 border-l border-[var(--color-border)] pl-3">
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 px-3 py-1.5 text-sm rounded-md transition-colors',
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
- ? 'bg-[var(--color-primary)]/10 text-[var(--color-primary)] font-medium'
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-24 w-[var(--toc-width)] max-h-[calc(100vh-8rem)] overflow-y-auto">
61
- <p className="text-[11px] font-semibold uppercase tracking-wider text-[var(--color-text-tertiary)] mb-3">
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-2 text-[13px]">
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 === 3 && 'pl-3',
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}