返回 oh-my-ppt
Sidebar.tsx
根目录 / src / renderer / src / components / layout / Sidebar.tsx
1 import { useEffect, useState } from 'react'
2 import { cn } from '@renderer/lib/utils'
3 import {
4 Home,
5 FolderOpen,
6 Settings,
7 Plus,
8 ArrowLeft,
9 SwatchBook,
10 Type,
11 LayoutTemplate,
12 ChartNoAxesCombined,
13 FileCode2,
14 CircleHelp
15 } from 'lucide-react'
16 import { Link, useLocation } from 'react-router-dom'
17 import logoUrl from '@renderer/assets/images/logo.png'
18 import { useT } from '@renderer/i18n'
19 import { ipc } from '@renderer/lib/ipc'
20
21 export function Sidebar(): React.JSX.Element {
22 const location = useLocation()
23 const t = useT()
24 const isDetailPage =
25 location.pathname.startsWith('/sessions/') && location.pathname !== '/sessions'
26 const [appVersion, setAppVersion] = useState('')
27
28 useEffect(() => {
29 let disposed = false
30 void ipc
31 .getAppVersion()
32 .then((result) => {
33 if (!disposed) {
34 setAppVersion(String(result?.version || ''))
35 }
36 })
37 .catch(() => {
38 if (!disposed) setAppVersion('')
39 })
40 return () => {
41 disposed = true
42 }
43 }, [])
44
45 const navItems = [
46 { path: '/', icon: Home, label: t('nav.home') },
47 { path: '/sessions', icon: FolderOpen, label: t('nav.sessions') },
48 { path: '/templates', icon: LayoutTemplate, label: t('nav.templates') },
49 { path: '/styles', icon: SwatchBook, label: t('nav.styles') },
50 { path: '/fonts', icon: Type, label: t('nav.fonts') },
51 { path: '/token-usage', icon: ChartNoAxesCombined, label: t('nav.tokenUsage') },
52 { path: '/settings', icon: Settings, label: t('nav.settings') },
53 { path: '/edit-html', icon: FileCode2, label: t('nav.editHtml') }
54 ]
55
56 return (
57 <aside className="flex h-full w-full flex-col bg-transparent">
58 <div className="px-2 pt-1">
59 <div className="mt-1 flex items-center gap-1">
60 <img src={logoUrl} alt="Oh My PPT" className="h-14 w-14 select-none" draggable={false} />
61 <h1 className="organic-serif text-[22px] font-semibold leading-none text-[#3e4a32]">
62 Oh My PPT
63 </h1>
64 </div>
65 <p className="mt-1 text-[14px] text-[#7f876e] px-4">{t('nav.tagline')}</p>
66 </div>
67
68 <nav className="flex-1 space-y-1 px-3 pb-4 pt-5">
69 {isDetailPage && (
70 <Link
71 to="/sessions"
72 className="flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm text-[#4a5a3d] transition-colors hover:bg-[#efe5d3]/75"
73 >
74 <ArrowLeft className="w-4 h-4" />
75 {t('nav.backToSessions')}
76 </Link>
77 )}
78 {navItems.map((item) => {
79 const isActive =
80 item.path === '/' ? location.pathname === '/' : location.pathname.startsWith(item.path)
81 return (
82 <Link
83 key={item.path}
84 to={item.path}
85 className={cn(
86 'flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm transition-colors',
87 isActive
88 ? 'bg-[#dbe7ca]/80 text-[#2f3b28]'
89 : 'text-[#58664a] hover:bg-[#efe5d3]/75 hover:text-[#38452f]'
90 )}
91 >
92 <item.icon className="w-4 h-4" />
93 {item.label}
94 </Link>
95 )
96 })}
97 <a
98 href="https://www.ohmyppt.cc/#/help"
99 target="_blank"
100 rel="noopener noreferrer"
101 className="flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm text-[#58664a] transition-colors hover:bg-[#efe5d3]/75 hover:text-[#38452f]"
102 >
103 <CircleHelp className="h-4 w-4" />
104 {t('nav.helpDocs')}
105 </a>
106 </nav>
107
108 <div className="px-4 pb-4">
109 <Link
110 to="/"
111 className="flex items-center justify-between gap-2 rounded-xl bg-gradient-to-r from-[#6f8159] to-[#4f613f] px-3 py-2.5 text-[12px] font-medium text-white shadow-lg shadow-[#5d6b4d]/30 transition-all hover:translate-y-[-1px]"
112 >
113 <span className="flex min-w-0 items-center gap-2 truncate">
114 <Plus className="h-3.5 w-3.5 shrink-0" />
115 {t('nav.newPresentation')}
116 </span>
117 {appVersion ? (
118 <span className="shrink-0 text-[10px] font-normal text-white/70">v{appVersion}</span>
119 ) : null}
120 </Link>
121 </div>
122 </aside>
123 )
124 }
125
125 lines Plain Text