helixevo 0.2.39 → 0.2.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/dashboard/app/changelog/page.tsx +179 -54
- package/dashboard/app/commands/page.tsx +243 -159
- package/dashboard/app/evolution/page.tsx +105 -102
- package/dashboard/app/frontier/page.tsx +103 -100
- package/dashboard/app/globals.css +1105 -403
- package/dashboard/app/guide/page.tsx +48 -4
- package/dashboard/app/layout.tsx +28 -57
- package/dashboard/app/network/client.tsx +453 -269
- package/dashboard/app/network/page.tsx +12 -2
- package/dashboard/app/page.tsx +166 -185
- package/dashboard/app/projects/client.tsx +891 -509
- package/dashboard/app/research/client.tsx +180 -248
- package/dashboard/components/SkillFlowNode.tsx +86 -128
- package/dashboard/components/console-panel.tsx +25 -0
- package/dashboard/components/metric-card.tsx +45 -0
- package/dashboard/components/overview-actions.tsx +29 -40
- package/dashboard/components/page-hero.tsx +44 -0
- package/dashboard/components/quick-actions.tsx +93 -167
- package/dashboard/components/section-frame.tsx +35 -0
- package/dashboard/components/sidebar-nav.tsx +75 -0
- package/dashboard/components/update-banner.tsx +101 -145
- package/dashboard/lib/data.ts +2 -2
- package/dist/cli.js +225 -156
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { useRef, useState, type ComponentType } from 'react'
|
|
4
|
+
import { ConsolePanel } from './console-panel'
|
|
4
5
|
|
|
5
6
|
interface Action {
|
|
6
7
|
id: string
|
|
@@ -22,10 +23,17 @@ interface QuickActionsProps {
|
|
|
22
23
|
|
|
23
24
|
type RunState = 'idle' | 'running' | 'success' | 'error' | 'stopped'
|
|
24
25
|
|
|
26
|
+
function toneForState(state: RunState): 'neutral' | 'green' | 'red' | 'yellow' {
|
|
27
|
+
if (state === 'success') return 'green'
|
|
28
|
+
if (state === 'error') return 'red'
|
|
29
|
+
if (state === 'stopped') return 'yellow'
|
|
30
|
+
return 'neutral'
|
|
31
|
+
}
|
|
32
|
+
|
|
25
33
|
export function QuickActions({ actions, flowDiagram: FlowDiagram }: QuickActionsProps) {
|
|
26
34
|
const [running, setRunning] = useState<string | null>(null)
|
|
27
35
|
const [state, setState] = useState<RunState>('idle')
|
|
28
|
-
const [output, setOutput] = useState
|
|
36
|
+
const [output, setOutput] = useState('')
|
|
29
37
|
const abortRef = useRef<AbortController | null>(null)
|
|
30
38
|
const outputRef = useRef<HTMLPreElement | null>(null)
|
|
31
39
|
|
|
@@ -61,10 +69,8 @@ export function QuickActions({ actions, flowDiagram: FlowDiagram }: QuickActions
|
|
|
61
69
|
if (done) break
|
|
62
70
|
|
|
63
71
|
buffer += decoder.decode(value, { stream: true })
|
|
64
|
-
|
|
65
|
-
// Parse SSE events from buffer
|
|
66
72
|
const events = buffer.split('\n\n')
|
|
67
|
-
buffer = events.pop() ?? ''
|
|
73
|
+
buffer = events.pop() ?? ''
|
|
68
74
|
|
|
69
75
|
for (const event of events) {
|
|
70
76
|
const lines = event.split('\n')
|
|
@@ -80,11 +86,8 @@ export function QuickActions({ actions, flowDiagram: FlowDiagram }: QuickActions
|
|
|
80
86
|
try {
|
|
81
87
|
const text = JSON.parse(eventData) as string
|
|
82
88
|
setOutput(prev => prev + text)
|
|
83
|
-
// Auto-scroll to bottom
|
|
84
89
|
setTimeout(() => {
|
|
85
|
-
if (outputRef.current)
|
|
86
|
-
outputRef.current.scrollTop = outputRef.current.scrollHeight
|
|
87
|
-
}
|
|
90
|
+
if (outputRef.current) outputRef.current.scrollTop = outputRef.current.scrollHeight
|
|
88
91
|
}, 10)
|
|
89
92
|
} catch {}
|
|
90
93
|
}
|
|
@@ -106,8 +109,7 @@ export function QuickActions({ actions, flowDiagram: FlowDiagram }: QuickActions
|
|
|
106
109
|
}
|
|
107
110
|
}
|
|
108
111
|
|
|
109
|
-
|
|
110
|
-
if (state === 'running') setState('success')
|
|
112
|
+
setState(prev => prev === 'running' ? 'success' : prev)
|
|
111
113
|
} catch (err: unknown) {
|
|
112
114
|
if (err instanceof Error && err.name === 'AbortError') {
|
|
113
115
|
try { await fetch('/api/run', { method: 'DELETE' }) } catch {}
|
|
@@ -130,176 +132,100 @@ export function QuickActions({ actions, flowDiagram: FlowDiagram }: QuickActions
|
|
|
130
132
|
const handleClose = () => {
|
|
131
133
|
setRunning(null)
|
|
132
134
|
setState('idle')
|
|
133
|
-
setOutput(
|
|
135
|
+
setOutput('')
|
|
134
136
|
}
|
|
135
137
|
|
|
136
|
-
const
|
|
137
|
-
: state === 'error' ? 'var(--red)'
|
|
138
|
-
: state === 'stopped' ? 'var(--yellow)'
|
|
139
|
-
: 'var(--text-secondary)'
|
|
140
|
-
|
|
141
|
-
const stateBorderColor = state === 'success' ? 'var(--green-border)'
|
|
142
|
-
: state === 'error' ? 'var(--red-border)'
|
|
143
|
-
: state === 'stopped' ? 'var(--yellow-border)'
|
|
144
|
-
: 'var(--border)'
|
|
145
|
-
|
|
146
|
-
const stateBgColor = state === 'success' ? 'var(--green-light)'
|
|
147
|
-
: state === 'error' ? 'var(--red-light)'
|
|
148
|
-
: state === 'stopped' ? 'var(--yellow-light)'
|
|
149
|
-
: 'var(--bg-section)'
|
|
138
|
+
const currentAction = actions.find(action => action.id === running)
|
|
150
139
|
|
|
151
140
|
return (
|
|
152
141
|
<>
|
|
153
|
-
<div
|
|
154
|
-
{actions.map(action =>
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
width: 16, height: 16, border: '2px solid var(--border)',
|
|
180
|
-
borderTopColor: action.color, borderRadius: '50%',
|
|
181
|
-
animation: 'actionSpin 0.8s linear infinite',
|
|
182
|
-
flexShrink: 0,
|
|
183
|
-
}} />
|
|
184
|
-
) : (
|
|
185
|
-
<div style={{
|
|
186
|
-
width: 26, height: 26, borderRadius: 7, flexShrink: 0,
|
|
187
|
-
background: action.disabled ? 'var(--bg-hover)' : `${action.color}12`,
|
|
188
|
-
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
189
|
-
}}>
|
|
190
|
-
<svg width="13" height="13" viewBox="0 0 24 24" fill="none"
|
|
191
|
-
stroke={action.disabled ? 'var(--text-muted)' : action.color}
|
|
192
|
-
strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
193
|
-
<path d={action.icon} />
|
|
194
|
-
</svg>
|
|
142
|
+
<div className="quick-actions-grid">
|
|
143
|
+
{actions.map((action) => {
|
|
144
|
+
const cardColor = action.disabled ? 'var(--text-muted)' : action.color
|
|
145
|
+
return (
|
|
146
|
+
<button
|
|
147
|
+
key={action.id}
|
|
148
|
+
onClick={() => handleRun(action)}
|
|
149
|
+
disabled={action.disabled || (running !== null && running !== action.id)}
|
|
150
|
+
title={action.disabled ? action.disabledReason : action.description}
|
|
151
|
+
className="action-card"
|
|
152
|
+
style={{
|
|
153
|
+
color: cardColor,
|
|
154
|
+
opacity: running !== null && running !== action.id ? 0.42 : 1,
|
|
155
|
+
background: action.disabled ? 'linear-gradient(180deg, rgba(239,235,228,0.88), rgba(239,235,228,0.8))' : undefined,
|
|
156
|
+
borderColor: running === action.id && state === 'running' ? action.color : undefined,
|
|
157
|
+
}}
|
|
158
|
+
>
|
|
159
|
+
<div className="action-card-header">
|
|
160
|
+
<div className="action-card-icon" style={{ color: cardColor, background: action.disabled ? 'rgba(255,255,255,0.55)' : `${action.color}18` }}>
|
|
161
|
+
{running === action.id && state === 'running' ? (
|
|
162
|
+
<span style={{ width: 16, height: 16, border: '2px solid rgba(97,93,86,0.28)', borderTopColor: action.color, borderRadius: '50%', animation: 'actionSpin 0.8s linear infinite' }} />
|
|
163
|
+
) : (
|
|
164
|
+
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
|
|
165
|
+
<path d={action.icon} />
|
|
166
|
+
</svg>
|
|
167
|
+
)}
|
|
195
168
|
</div>
|
|
196
|
-
|
|
197
|
-
<span style={{ fontSize: 13 }}>{action.label}</span>
|
|
198
|
-
</div>
|
|
199
|
-
|
|
200
|
-
{/* Subtitle */}
|
|
201
|
-
{action.subtitle && (
|
|
202
|
-
<div style={{
|
|
203
|
-
fontSize: 11, fontWeight: 400, lineHeight: 1.45,
|
|
204
|
-
color: action.disabled ? 'var(--text-muted)' : 'var(--text-dim)',
|
|
205
|
-
marginTop: 2,
|
|
206
|
-
}}>
|
|
207
|
-
{action.disabled ? action.disabledReason : action.subtitle}
|
|
169
|
+
<div className="action-card-title">{action.label}</div>
|
|
208
170
|
</div>
|
|
209
|
-
)}
|
|
210
171
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
172
|
+
{action.subtitle ? (
|
|
173
|
+
<div className="action-card-subtitle">
|
|
174
|
+
{action.disabled ? action.disabledReason : action.subtitle}
|
|
175
|
+
</div>
|
|
176
|
+
) : null}
|
|
177
|
+
|
|
178
|
+
{FlowDiagram && action.flowSteps && !action.disabled ? (
|
|
179
|
+
<FlowDiagram steps={action.flowSteps} color={action.color} />
|
|
180
|
+
) : null}
|
|
181
|
+
</button>
|
|
182
|
+
)
|
|
183
|
+
})}
|
|
217
184
|
</div>
|
|
218
185
|
|
|
219
|
-
{
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
display: 'flex', justifyContent: 'space-between', alignItems: 'center',
|
|
230
|
-
padding: '10px 14px',
|
|
231
|
-
background: stateBgColor,
|
|
232
|
-
borderBottom: '1px solid var(--border)',
|
|
233
|
-
}}>
|
|
234
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 12, fontWeight: 600 }}>
|
|
235
|
-
{state === 'running' && (
|
|
236
|
-
<span style={{
|
|
237
|
-
width: 12, height: 12, border: '2px solid var(--border)',
|
|
238
|
-
borderTopColor: 'var(--purple)', borderRadius: '50%',
|
|
239
|
-
animation: 'actionSpin 0.8s linear infinite',
|
|
240
|
-
}} />
|
|
241
|
-
)}
|
|
242
|
-
{state === 'success' && <span style={{ color: 'var(--green)' }}>✓</span>}
|
|
243
|
-
{state === 'error' && <span style={{ color: 'var(--red)' }}>✗</span>}
|
|
244
|
-
{state === 'stopped' && <span style={{ color: 'var(--yellow)' }}>■</span>}
|
|
245
|
-
<span style={{ color: stateColor }}>
|
|
246
|
-
{state === 'running' ? `Running ${actions.find(a => a.id === running)?.label}...`
|
|
247
|
-
: state === 'success' ? 'Completed'
|
|
248
|
-
: state === 'stopped' ? 'Stopped'
|
|
249
|
-
: 'Failed'}
|
|
186
|
+
{running && state !== 'idle' ? (
|
|
187
|
+
<div style={{ marginTop: 16 }}>
|
|
188
|
+
<ConsolePanel
|
|
189
|
+
tone={toneForState(state)}
|
|
190
|
+
title={
|
|
191
|
+
<span>
|
|
192
|
+
{state === 'running' ? `Running ${currentAction?.label ?? 'Action'}…` :
|
|
193
|
+
state === 'success' ? `${currentAction?.label ?? 'Action'} completed` :
|
|
194
|
+
state === 'stopped' ? `${currentAction?.label ?? 'Action'} stopped` :
|
|
195
|
+
`${currentAction?.label ?? 'Action'} failed`}
|
|
250
196
|
</span>
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
<button onClick={handleStop} style={{
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
{
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
}}>
|
|
279
|
-
Close
|
|
280
|
-
</button>
|
|
281
|
-
</>
|
|
282
|
-
)}
|
|
283
|
-
</div>
|
|
284
|
-
</div>
|
|
285
|
-
|
|
286
|
-
<pre ref={outputRef} style={{
|
|
287
|
-
padding: '12px 14px', margin: 0,
|
|
288
|
-
fontSize: 11, lineHeight: 1.5,
|
|
289
|
-
fontFamily: 'var(--font-mono)',
|
|
290
|
-
color: 'var(--text-secondary)',
|
|
291
|
-
maxHeight: 400, overflow: 'auto',
|
|
292
|
-
whiteSpace: 'pre-wrap', wordBreak: 'break-word',
|
|
293
|
-
}}>
|
|
294
|
-
{output || (state === 'running' ? 'Starting...' : 'No output')}
|
|
295
|
-
</pre>
|
|
197
|
+
}
|
|
198
|
+
actions={
|
|
199
|
+
state === 'running' ? (
|
|
200
|
+
<button onClick={handleStop} className="badge badge-red" style={{ border: 'none', cursor: 'pointer' }}>Stop</button>
|
|
201
|
+
) : (
|
|
202
|
+
<div style={{ display: 'flex', gap: 8 }}>
|
|
203
|
+
<button onClick={() => window.location.reload()} className="badge badge-gray" style={{ border: 'none', cursor: 'pointer' }}>Refresh</button>
|
|
204
|
+
<button onClick={handleClose} className="badge badge-gray" style={{ border: 'none', cursor: 'pointer' }}>Close</button>
|
|
205
|
+
</div>
|
|
206
|
+
)
|
|
207
|
+
}
|
|
208
|
+
>
|
|
209
|
+
<pre
|
|
210
|
+
ref={outputRef}
|
|
211
|
+
style={{
|
|
212
|
+
margin: 0,
|
|
213
|
+
padding: '16px 18px',
|
|
214
|
+
fontSize: 11.5,
|
|
215
|
+
lineHeight: 1.62,
|
|
216
|
+
color: 'var(--text-secondary)',
|
|
217
|
+
whiteSpace: 'pre-wrap',
|
|
218
|
+
wordBreak: 'break-word',
|
|
219
|
+
}}
|
|
220
|
+
>
|
|
221
|
+
{output || (state === 'running' ? 'Starting…' : 'No output')}
|
|
222
|
+
</pre>
|
|
223
|
+
</ConsolePanel>
|
|
296
224
|
</div>
|
|
297
|
-
)}
|
|
225
|
+
) : null}
|
|
298
226
|
|
|
299
227
|
<style>{`
|
|
300
|
-
@keyframes actionSpin {
|
|
301
|
-
to { transform: rotate(360deg); }
|
|
302
|
-
}
|
|
228
|
+
@keyframes actionSpin { to { transform: rotate(360deg); } }
|
|
303
229
|
`}</style>
|
|
304
230
|
</>
|
|
305
231
|
)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import type { ReactNode } from 'react'
|
|
4
|
+
|
|
5
|
+
export function SectionFrame({
|
|
6
|
+
eyebrow,
|
|
7
|
+
title,
|
|
8
|
+
description,
|
|
9
|
+
actions,
|
|
10
|
+
children,
|
|
11
|
+
tone = 'neutral',
|
|
12
|
+
className = '',
|
|
13
|
+
}: {
|
|
14
|
+
eyebrow?: string
|
|
15
|
+
title: string
|
|
16
|
+
description?: ReactNode
|
|
17
|
+
actions?: ReactNode
|
|
18
|
+
children: ReactNode
|
|
19
|
+
tone?: 'neutral' | 'purple' | 'green' | 'blue' | 'yellow' | 'red'
|
|
20
|
+
className?: string
|
|
21
|
+
}) {
|
|
22
|
+
return (
|
|
23
|
+
<section className={`section-frame section-frame-${tone} ${className}`.trim()}>
|
|
24
|
+
<div className="section-frame-header">
|
|
25
|
+
<div>
|
|
26
|
+
{eyebrow ? <div className="section-frame-eyebrow">{eyebrow}</div> : null}
|
|
27
|
+
<div className="section-frame-title">{title}</div>
|
|
28
|
+
{description ? <div className="section-frame-description">{description}</div> : null}
|
|
29
|
+
</div>
|
|
30
|
+
{actions ? <div className="section-frame-actions">{actions}</div> : null}
|
|
31
|
+
</div>
|
|
32
|
+
<div className="section-frame-body">{children}</div>
|
|
33
|
+
</section>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import Link from 'next/link'
|
|
4
|
+
import { usePathname } from 'next/navigation'
|
|
5
|
+
|
|
6
|
+
interface NavItem {
|
|
7
|
+
href: string
|
|
8
|
+
label: string
|
|
9
|
+
icon: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const WORKSPACE_NAV: NavItem[] = [
|
|
13
|
+
{ href: '/', label: 'Overview', icon: 'M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-4 0a1 1 0 01-1-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 01-1 1' },
|
|
14
|
+
{ href: '/network', label: 'Skill Network', icon: 'M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1' },
|
|
15
|
+
{ href: '/evolution', label: 'Evolution', icon: 'M13 7h8m0 0v8m0-8l-8 8-4-4-6 6' },
|
|
16
|
+
{ href: '/research', label: 'Research', icon: 'M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z' },
|
|
17
|
+
{ href: '/frontier', label: 'Frontier', icon: 'M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z' },
|
|
18
|
+
{ href: '/projects', label: 'Projects', icon: 'M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z' },
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
const REFERENCE_NAV: NavItem[] = [
|
|
22
|
+
{ href: '/commands', label: 'Commands', icon: 'M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z' },
|
|
23
|
+
{ href: '/guide', label: 'Guide', icon: 'M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253' },
|
|
24
|
+
{ href: '/changelog', label: 'Changelog', icon: 'M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z M14 2v6h6 M16 13H8 M16 17H8 M10 9H8' },
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
function NavIcon({ d }: { d: string }) {
|
|
28
|
+
return (
|
|
29
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
|
|
30
|
+
{d.split(' M').map((segment, idx) => (
|
|
31
|
+
<path key={idx} d={idx === 0 ? segment : `M${segment}`} />
|
|
32
|
+
))}
|
|
33
|
+
</svg>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function isActive(pathname: string, href: string) {
|
|
38
|
+
if (href === '/') return pathname === '/'
|
|
39
|
+
return pathname === href || pathname.startsWith(`${href}/`)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function NavGroup({ title, items, pathname }: { title: string; items: NavItem[]; pathname: string }) {
|
|
43
|
+
return (
|
|
44
|
+
<div className="sidebar-group">
|
|
45
|
+
<div className="sidebar-group-label">{title}</div>
|
|
46
|
+
<div className="sidebar-group-items">
|
|
47
|
+
{items.map((item) => {
|
|
48
|
+
const active = isActive(pathname, item.href)
|
|
49
|
+
return (
|
|
50
|
+
<Link
|
|
51
|
+
key={item.href}
|
|
52
|
+
href={item.href}
|
|
53
|
+
className={`nav-item ${active ? 'active' : ''}`}
|
|
54
|
+
aria-current={active ? 'page' : undefined}
|
|
55
|
+
>
|
|
56
|
+
<span className="nav-icon"><NavIcon d={item.icon} /></span>
|
|
57
|
+
<span>{item.label}</span>
|
|
58
|
+
</Link>
|
|
59
|
+
)
|
|
60
|
+
})}
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function SidebarNav() {
|
|
67
|
+
const pathname = usePathname() || '/'
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<>
|
|
71
|
+
<NavGroup title="Workspace" items={WORKSPACE_NAV} pathname={pathname} />
|
|
72
|
+
<NavGroup title="Reference" items={REFERENCE_NAV} pathname={pathname} />
|
|
73
|
+
</>
|
|
74
|
+
)
|
|
75
|
+
}
|