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