Skip to content

Commit f63856b

Browse files
committed
feat(components): add navigation context provider and enhance footer navigation
Added NavigationContext provider to enable navigation functionality throughout the component tree. Enhanced the navigation footer with improved styling and behavior for both previous and next buttons, including hover effects and proper state management. This change improves the overall navigation experience by providing consistent access to navigation functions and better visual feedback for users interacting with the navigation controls.
1 parent 4c3d3d6 commit f63856b

15 files changed

Lines changed: 406 additions & 123 deletions

src/components/PageLayout.tsx

Lines changed: 45 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { useCallback, useEffect, useMemo, useState, type ReactNode } from 'react
22
import { flatNavItems } from '../nav';
33
import { useOutline } from './OutlineContext';
44
import { OutlineProvider } from './OutlineProvider';
5+
import { NavigationContext } from '../contexts/NavigationContext';
56

67
function BackToTop() {
78
const [visible, setVisible] = useState(false);
@@ -232,49 +233,53 @@ export function PageLayout({
232233
setActiveSectionId(id);
233234
}, []);
234235

236+
const navigationValue = useMemo(() => ({ navigate: onNavigate }), [onNavigate]);
237+
235238
return (
236-
<OutlineProvider key={activeTab}>
237-
<BackToTop />
238-
<OutlineHeader
239-
activeSectionId={activeSectionId}
240-
setActiveSectionId={setActiveSectionId}
241-
onSelectSection={onSelectSection}
242-
/>
243-
{children}
244-
245-
{/* Navigation Footer */}
246-
<div className="mt-12 pt-6 border-t border-[var(--border-subtle)]">
247-
<div className="flex items-center justify-between gap-4">
248-
<button
249-
disabled={!prev}
250-
onClick={() => prev && onNavigate(prev.id)}
251-
className={`group flex items-center gap-2 px-4 py-2.5 rounded-md text-sm font-mono border transition-all duration-200 ${
252-
prev
253-
? 'bg-[var(--bg-panel)] border-[var(--border-subtle)] text-[var(--text-secondary)] hover:bg-[var(--bg-elevated)] hover:border-[var(--cyber-blue-dim)] hover:text-[var(--cyber-blue)]'
254-
: 'bg-[var(--bg-void)] border-[var(--border-subtle)] text-[var(--text-muted)] cursor-not-allowed opacity-50'
255-
}`}
256-
>
257-
<span className={`transition-transform duration-200 ${prev ? 'group-hover:-translate-x-1' : ''}`}></span>
258-
<span className="max-w-[120px] truncate">{prev ? prev.label : 'EOF'}</span>
259-
</button>
260-
<div className="text-xs text-[var(--text-muted)] font-mono hidden sm:block">
261-
// navigate
239+
<NavigationContext.Provider value={navigationValue}>
240+
<OutlineProvider key={activeTab}>
241+
<BackToTop />
242+
<OutlineHeader
243+
activeSectionId={activeSectionId}
244+
setActiveSectionId={setActiveSectionId}
245+
onSelectSection={onSelectSection}
246+
/>
247+
{children}
248+
249+
{/* Navigation Footer */}
250+
<div className="mt-12 pt-6 border-t border-[var(--border-subtle)]">
251+
<div className="flex items-center justify-between gap-4">
252+
<button
253+
disabled={!prev}
254+
onClick={() => prev && onNavigate(prev.id)}
255+
className={`group flex items-center gap-2 px-4 py-2.5 rounded-md text-sm font-mono border transition-all duration-200 ${
256+
prev
257+
? 'bg-[var(--bg-panel)] border-[var(--border-subtle)] text-[var(--text-secondary)] hover:bg-[var(--bg-elevated)] hover:border-[var(--cyber-blue-dim)] hover:text-[var(--cyber-blue)]'
258+
: 'bg-[var(--bg-void)] border-[var(--border-subtle)] text-[var(--text-muted)] cursor-not-allowed opacity-50'
259+
}`}
260+
>
261+
<span className={`transition-transform duration-200 ${prev ? 'group-hover:-translate-x-1' : ''}`}></span>
262+
<span className="max-w-[120px] truncate">{prev ? prev.label : 'EOF'}</span>
263+
</button>
264+
<div className="text-xs text-[var(--text-muted)] font-mono hidden sm:block">
265+
// navigate
266+
</div>
267+
<button
268+
disabled={!next}
269+
onClick={() => next && onNavigate(next.id)}
270+
className={`group flex items-center gap-2 px-4 py-2.5 rounded-md text-sm font-mono border transition-all duration-200 ${
271+
next
272+
? 'bg-[var(--bg-panel)] border-[var(--border-subtle)] text-[var(--text-secondary)] hover:bg-[var(--bg-elevated)] hover:border-[var(--terminal-green-dim)] hover:text-[var(--terminal-green)]'
273+
: 'bg-[var(--bg-void)] border-[var(--border-subtle)] text-[var(--text-muted)] cursor-not-allowed opacity-50'
274+
}`}
275+
>
276+
<span className="max-w-[120px] truncate">{next ? next.label : 'EOF'}</span>
277+
<span className={`transition-transform duration-200 ${next ? 'group-hover:translate-x-1' : ''}`}></span>
278+
</button>
262279
</div>
263-
<button
264-
disabled={!next}
265-
onClick={() => next && onNavigate(next.id)}
266-
className={`group flex items-center gap-2 px-4 py-2.5 rounded-md text-sm font-mono border transition-all duration-200 ${
267-
next
268-
? 'bg-[var(--bg-panel)] border-[var(--border-subtle)] text-[var(--text-secondary)] hover:bg-[var(--bg-elevated)] hover:border-[var(--terminal-green-dim)] hover:text-[var(--terminal-green)]'
269-
: 'bg-[var(--bg-void)] border-[var(--border-subtle)] text-[var(--text-muted)] cursor-not-allowed opacity-50'
270-
}`}
271-
>
272-
<span className="max-w-[120px] truncate">{next ? next.label : 'EOF'}</span>
273-
<span className={`transition-transform duration-200 ${next ? 'group-hover:translate-x-1' : ''}`}></span>
274-
</button>
275280
</div>
276-
</div>
277-
</OutlineProvider>
281+
</OutlineProvider>
282+
</NavigationContext.Provider>
278283
);
279284
}
280285

src/components/PageLink.tsx

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { type ReactNode } from 'react';
2+
import { useNavigation } from '../contexts/NavigationContext';
3+
4+
interface PageLinkProps {
5+
/** 目标页面的 tab ID */
6+
to: string;
7+
/** 链接文本或子元素 */
8+
children: ReactNode;
9+
/** 额外的 className */
10+
className?: string;
11+
/** 自定义样式 */
12+
style?: React.CSSProperties;
13+
}
14+
15+
/**
16+
* 跨页面导航链接组件。
17+
*
18+
* 使用此组件代替 `<a href="#xxx">` 来实现真正的页面切换。
19+
*
20+
* @example
21+
* ```tsx
22+
* <PageLink to="token-accounting">Token 计费系统</PageLink>
23+
* ```
24+
*/
25+
export function PageLink({ to, children, className, style }: PageLinkProps) {
26+
const { navigate } = useNavigation();
27+
28+
return (
29+
<button
30+
onClick={() => navigate(to)}
31+
className={className}
32+
style={{
33+
cursor: 'pointer',
34+
background: 'none',
35+
border: 'none',
36+
padding: 0,
37+
font: 'inherit',
38+
textAlign: 'inherit',
39+
...style,
40+
}}
41+
>
42+
{children}
43+
</button>
44+
);
45+
}

src/components/RelatedPages.tsx

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
interface RelatedPage {
2+
id: string;
3+
label: string;
4+
description?: string;
5+
}
6+
7+
interface RelatedPagesProps {
8+
title?: string;
9+
pages: RelatedPage[];
10+
onNavigate?: (id: string) => void;
11+
}
12+
13+
export function RelatedPages({ title = '🔗 相关阅读', pages, onNavigate }: RelatedPagesProps) {
14+
return (
15+
<div className="mt-8 p-5 bg-[var(--bg-panel)] rounded-xl border border-[var(--border-subtle)]">
16+
<h3 className="text-sm font-bold font-mono text-[var(--text-muted)] mb-4 flex items-center gap-2">
17+
{title}
18+
</h3>
19+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
20+
{pages.map((page) => (
21+
<button
22+
key={page.id}
23+
onClick={() => onNavigate?.(page.id)}
24+
className="text-left p-3 bg-[var(--bg-card)] rounded-lg border border-[var(--border-subtle)] hover:border-[var(--terminal-green)] hover:bg-[var(--terminal-green)]/5 transition-all group"
25+
>
26+
<div className="font-mono text-sm text-[var(--text-primary)] group-hover:text-[var(--terminal-green)] flex items-center gap-2">
27+
<span className="text-[var(--text-muted)] group-hover:text-[var(--terminal-green)]"></span>
28+
{page.label}
29+
</div>
30+
{page.description && (
31+
<div className="text-xs text-[var(--text-muted)] mt-1">{page.description}</div>
32+
)}
33+
</button>
34+
))}
35+
</div>
36+
</div>
37+
);
38+
}

src/contexts/NavigationContext.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { createContext, useContext } from 'react';
2+
3+
export interface NavigationContextValue {
4+
navigate: (tabId: string, opts?: { replace?: boolean; preserveHash?: boolean }) => void;
5+
}
6+
7+
export const NavigationContext = createContext<NavigationContextValue | null>(null);
8+
9+
/**
10+
* Hook to access the navigation function from any page component.
11+
*
12+
* @example
13+
* ```tsx
14+
* const { navigate } = useNavigation();
15+
* // Navigate to another page
16+
* navigate('token-accounting');
17+
* ```
18+
*/
19+
export function useNavigation(): NavigationContextValue {
20+
const ctx = useContext(NavigationContext);
21+
if (!ctx) {
22+
throw new Error('useNavigation must be used within a NavigationContext.Provider');
23+
}
24+
return ctx;
25+
}

src/pages/AuthenticationFlow.tsx

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Layer } from '../components/Layer';
33
import { HighlightBox } from '../components/HighlightBox';
44
import { CodeBlock } from '../components/CodeBlock';
55
import { MermaidDiagram } from '../components/MermaidDiagram';
6+
import { useNavigation } from '../contexts/NavigationContext';
67

78
function CollapsibleSection({
89
title,
@@ -35,6 +36,8 @@ function CollapsibleSection({
3536
}
3637

3738
export function AuthenticationFlow() {
39+
const { navigate } = useNavigation();
40+
3841
return (
3942
<div>
4043
<h2 className="text-2xl text-cyan-400 mb-5">认证流程详解</h2>
@@ -635,9 +638,9 @@ private async acquireLock(lockPath: string): Promise<void> {
635638
<h4 className="text-cyan-400 font-semibold mb-2">相关页面</h4>
636639
<p className="text-sm text-gray-300">
637640
详细的 SharedTokenManager 架构和实现请参考:
638-
<a href="#shared-token-manager" className="text-cyan-400 hover:underline ml-2">
641+
<button onClick={() => navigate('shared-token-manager')} className="text-cyan-400 hover:underline ml-2 bg-transparent border-none cursor-pointer">
639642
→ Token 共享机制
640-
</a>
643+
</button>
641644
</p>
642645
</div>
643646
</CollapsibleSection>
@@ -870,18 +873,18 @@ try {
870873
<div className="mt-8 p-4 bg-gray-800/50 rounded-lg">
871874
<h3 className="text-lg font-semibold text-cyan-400 mb-3">相关页面</h3>
872875
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
873-
<a href="#shared-token-manager" className="block p-3 bg-gray-700/50 rounded hover:bg-gray-700 transition-colors">
876+
<button onClick={() => navigate('shared-token-manager')} className="block p-3 bg-gray-700/50 rounded hover:bg-gray-700 transition-colors text-left border-none cursor-pointer">
874877
<div className="text-purple-400 font-semibold">Token 共享机制</div>
875878
<div className="text-sm text-gray-400">SharedTokenManager 完整架构</div>
876-
</a>
877-
<a href="#startup-chain" className="block p-3 bg-gray-700/50 rounded hover:bg-gray-700 transition-colors">
879+
</button>
880+
<button onClick={() => navigate('startup-chain')} className="block p-3 bg-gray-700/50 rounded hover:bg-gray-700 transition-colors text-left border-none cursor-pointer">
878881
<div className="text-blue-400 font-semibold">启动链路</div>
879882
<div className="text-sm text-gray-400">认证如何触发</div>
880-
</a>
881-
<a href="#config" className="block p-3 bg-gray-700/50 rounded hover:bg-gray-700 transition-colors">
883+
</button>
884+
<button onClick={() => navigate('config')} className="block p-3 bg-gray-700/50 rounded hover:bg-gray-700 transition-colors text-left border-none cursor-pointer">
882885
<div className="text-green-400 font-semibold">配置系统</div>
883886
<div className="text-sm text-gray-400">认证相关配置项</div>
884-
</a>
887+
</button>
885888
</div>
886889
</div>
887890
</div>

src/pages/CommandExecutionContext.tsx

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
import { useState } from 'react';
77
import { CodeBlock } from '../components/CodeBlock';
88
import { MermaidDiagram } from '../components/MermaidDiagram';
9+
import { useNavigation } from '../contexts/NavigationContext';
910

1011
export function CommandExecutionContext() {
1112
const [activeTab, setActiveTab] = useState<'context' | 'loaders' | 'actions' | 'flow'>('context');
13+
const { navigate } = useNavigation();
1214

1315
return (
1416
<div className="max-w-4xl mx-auto">
@@ -933,49 +935,61 @@ export const chatCommand: SlashCommand = {
933935
<h2>🔗 相关文档</h2>
934936

935937
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', gap: '1rem' }}>
936-
<a href="#slash-cmd" className="card" style={{
938+
<button onClick={() => navigate('slash-cmd')} className="card" style={{
937939
padding: '1rem',
938940
textDecoration: 'none',
939941
background: 'rgba(59, 130, 246, 0.1)',
940942
borderRadius: '8px',
941-
transition: 'transform 0.2s'
943+
transition: 'transform 0.2s',
944+
border: 'none',
945+
cursor: 'pointer',
946+
textAlign: 'left'
942947
}}>
943948
<h4 style={{ color: 'var(--cyber-blue)', margin: '0 0 0.5rem 0' }}>💻 斜杠命令</h4>
944949
<p style={{ margin: 0, fontSize: '0.9rem' }}>命令系统概述</p>
945-
</a>
950+
</button>
946951

947-
<a href="#custom-cmd" className="card" style={{
952+
<button onClick={() => navigate('custom-cmd')} className="card" style={{
948953
padding: '1rem',
949954
textDecoration: 'none',
950955
background: 'rgba(139, 92, 246, 0.1)',
951956
borderRadius: '8px',
952-
transition: 'transform 0.2s'
957+
transition: 'transform 0.2s',
958+
border: 'none',
959+
cursor: 'pointer',
960+
textAlign: 'left'
953961
}}>
954962
<h4 style={{ color: 'var(--purple-accent)', margin: '0 0 0.5rem 0' }}>📝 自定义命令</h4>
955963
<p style={{ margin: 0, fontSize: '0.9rem' }}>TOML 文件格式详解</p>
956-
</a>
964+
</button>
957965

958-
<a href="#mcp" className="card" style={{
966+
<button onClick={() => navigate('mcp')} className="card" style={{
959967
padding: '1rem',
960968
textDecoration: 'none',
961969
background: 'rgba(16, 185, 129, 0.1)',
962970
borderRadius: '8px',
963-
transition: 'transform 0.2s'
971+
transition: 'transform 0.2s',
972+
border: 'none',
973+
cursor: 'pointer',
974+
textAlign: 'left'
964975
}}>
965976
<h4 style={{ color: 'var(--terminal-green)', margin: '0 0 0.5rem 0' }}>🌐 MCP 集成</h4>
966977
<p style={{ margin: 0, fontSize: '0.9rem' }}>MCP 提示加载机制</p>
967-
</a>
978+
</button>
968979

969-
<a href="#approval-mode" className="card" style={{
980+
<button onClick={() => navigate('approval-mode')} className="card" style={{
970981
padding: '1rem',
971982
textDecoration: 'none',
972983
background: 'rgba(245, 158, 11, 0.1)',
973984
borderRadius: '8px',
974-
transition: 'transform 0.2s'
985+
transition: 'transform 0.2s',
986+
border: 'none',
987+
cursor: 'pointer',
988+
textAlign: 'left'
975989
}}>
976990
<h4 style={{ color: 'var(--warning-color)', margin: '0 0 0.5rem 0' }}>🛡️ 审批模式</h4>
977991
<p style={{ margin: 0, fontSize: '0.9rem' }}>Shell 命令确认流程</p>
978-
</a>
992+
</button>
979993
</div>
980994
</section>
981995
</div>

0 commit comments

Comments
 (0)