Skip to content

Commit 4aaf1fd

Browse files
committed
feat: implement responsive layout with Drawer for narrow screens and enhance ActivityBar with tooltip management
1 parent 0677358 commit 4aaf1fd

7 files changed

Lines changed: 149 additions & 73 deletions

File tree

src/renderer/src/components/layout/Layout.tsx

Lines changed: 77 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { useState, useEffect } from 'react'
2-
import { Layout as AntLayout, Button, Tooltip } from 'antd'
2+
import { Layout as AntLayout, Button, Tooltip, Drawer } from 'antd'
33
import { useUIStore } from '../../stores/uiStore'
44
import { useSearchStore } from '../../stores/searchStore'
55
import Sidebar from './sidebar/Sidebar'
@@ -11,11 +11,30 @@ import TitleBar from './titlebar/TitleBar'
1111

1212
const { Sider, Content } = AntLayout
1313

14+
// 定义窄屏幕的阈值
15+
const NARROW_SCREEN_THRESHOLD = 768
16+
1417
export default function Layout() {
1518
const { sidebarCollapsed, sidebarWidth, toggleSidebar } = useUIStore()
1619
const { clearSearch, setFilterFolderId } = useSearchStore()
1720
const [activeTab, setActiveTab] = useState<ActivityBarTab>('explore')
1821
const [searchOpen, setSearchOpen] = useState(false)
22+
const [isNarrowScreen, setIsNarrowScreen] = useState(false)
23+
24+
// 监听窗口大小变化
25+
useEffect(() => {
26+
const handleResize = () => {
27+
setIsNarrowScreen(window.innerWidth < NARROW_SCREEN_THRESHOLD)
28+
}
29+
30+
// 初始化
31+
handleResize()
32+
33+
window.addEventListener('resize', handleResize)
34+
return () => {
35+
window.removeEventListener('resize', handleResize)
36+
}
37+
}, [])
1938

2039
// 处理键盘快捷键
2140
useEffect(() => {
@@ -46,10 +65,23 @@ export default function Layout() {
4665
}
4766

4867
const handleActivityTabChange = (tab: ActivityBarTab) => {
49-
setActiveTab(tab)
50-
// 如果侧边栏折叠,自动展开
51-
if (sidebarCollapsed) {
52-
toggleSidebar()
68+
// 如果点击的是当前已激活的标签
69+
if (tab === activeTab) {
70+
// 如果侧边栏是展开状态,则切换为收起
71+
if (!sidebarCollapsed) {
72+
toggleSidebar()
73+
}
74+
// 如果已经是收起状态,则展开(复用下面的逻辑)
75+
else {
76+
toggleSidebar()
77+
}
78+
} else {
79+
// 点击的是不同的标签
80+
setActiveTab(tab)
81+
// 如果侧边栏折叠,自动展开
82+
if (sidebarCollapsed) {
83+
toggleSidebar()
84+
}
5385
}
5486
}
5587

@@ -70,36 +102,54 @@ export default function Layout() {
70102
return (
71103
<div className="app-layout">
72104
{/* 自定义标题栏 */}
73-
<TitleBar
74-
title="Pointer - AI聊天助手"
75-
sidebarCollapsed={sidebarCollapsed}
76-
onToggleSidebar={toggleSidebar}
77-
/>
105+
<TitleBar title="Pointer - AI聊天助手" />
78106

79107
<AntLayout className="app-main-layout">
80108
{/* ActivityBar */}
81109
<Sider width={64} collapsedWidth={64} theme="light" className="app-activity-bar">
82110
<ActivityBar activeTab={activeTab} onTabChange={handleActivityTabChange} />
83111
</Sider>
84112

85-
{/* Sidebar */}
86-
<Sider
87-
width={sidebarWidth}
88-
collapsedWidth={0}
89-
collapsed={sidebarCollapsed}
90-
theme="light"
91-
className="app-sider"
92-
style={{ position: 'relative' }}
93-
>
94-
<Sidebar
113+
{/* 窄屏模式:使用 Drawer */}
114+
{isNarrowScreen ? (
115+
<Drawer
116+
placement="left"
117+
open={!sidebarCollapsed}
118+
onClose={toggleSidebar}
119+
width={sidebarWidth}
120+
styles={{ body: { padding: 0 } }}
121+
mask={true}
122+
maskClosable={true}
123+
closable={false}
124+
>
125+
<Sidebar
126+
collapsed={false}
127+
activeTab={activeTab}
128+
onSearchOpen={handleSearchOpen}
129+
onSettingsOpen={handleSearchOpen}
130+
onFindInFolder={handleFindInFolder}
131+
/>
132+
</Drawer>
133+
) : (
134+
/* 宽屏模式:使用 Sider */
135+
<Sider
136+
width={sidebarWidth}
137+
collapsedWidth={0}
95138
collapsed={sidebarCollapsed}
96-
activeTab={activeTab}
97-
onSearchOpen={handleSearchOpen}
98-
onSettingsOpen={handleSearchOpen}
99-
onFindInFolder={handleFindInFolder}
100-
/>
101-
<ResizeHandle />
102-
</Sider>
139+
theme="light"
140+
className="app-sider"
141+
style={{ position: 'relative' }}
142+
>
143+
<Sidebar
144+
collapsed={sidebarCollapsed}
145+
activeTab={activeTab}
146+
onSearchOpen={handleSearchOpen}
147+
onSettingsOpen={handleSearchOpen}
148+
onFindInFolder={handleFindInFolder}
149+
/>
150+
<ResizeHandle />
151+
</Sider>
152+
)}
103153

104154
<Content className="app-content">
105155
<TabsArea />

src/renderer/src/components/layout/activitybar/ActivityBar.tsx

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react'
1+
import React, { useState } from 'react'
22
import { Button, Badge, Tooltip } from 'antd'
33
import {
44
FolderOutlined,
@@ -25,6 +25,8 @@ export default function ActivityBar({ activeTab, onTabChange }: ActivityBarProps
2525
const { createAndOpenSettingsPage } = usePagesStore()
2626
const { openTab } = useTabsStore()
2727
const { getStats } = useFavoritesStore()
28+
const [tooltipVisible, setTooltipVisible] = useState<Record<string, boolean>>({})
29+
const [clickLocked, setClickLocked] = useState(false)
2830

2931
// 计算活跃任务数量
3032
const activeTaskCount = getRunningTasksCount()
@@ -34,8 +36,12 @@ export default function ActivityBar({ activeTab, onTabChange }: ActivityBarProps
3436

3537
// 处理设置按钮点击
3638
const handleSettingsClick = () => {
39+
setClickLocked(true)
40+
setTooltipVisible({ ...tooltipVisible, settings: false })
3741
const settingsPageId = createAndOpenSettingsPage('llm') // 默认打开LLM配置,因为这是用户最常需要的设置
3842
openTab(settingsPageId)
43+
// 300ms 后解锁
44+
setTimeout(() => setClickLocked(false), 300)
3945
}
4046

4147
const items = [
@@ -66,24 +72,62 @@ export default function ActivityBar({ activeTab, onTabChange }: ActivityBarProps
6672
}
6773
]
6874

75+
// 处理标签页点击
76+
const handleTabClick = (tab: ActivityBarTab) => {
77+
setClickLocked(true)
78+
// 关闭所有 tooltip
79+
const allClosed: Record<string, boolean> = {}
80+
items.forEach(item => {
81+
allClosed[item.key] = false
82+
})
83+
setTooltipVisible(allClosed)
84+
onTabChange(tab)
85+
// 300ms 后解锁
86+
setTimeout(() => setClickLocked(false), 300)
87+
}
88+
89+
// 处理 tooltip 显示变化
90+
const handleTooltipOpenChange = (key: string, visible: boolean) => {
91+
// 如果在点击锁定期间,不允许显示 tooltip
92+
if (clickLocked && visible) {
93+
return
94+
}
95+
setTooltipVisible({ ...tooltipVisible, [key]: visible })
96+
}
97+
6998
return (
7099
<div className="activity-bar">
71100
{items.map((item) => (
72-
<Tooltip key={item.key} title={item.tooltip} placement="right">
101+
<Tooltip
102+
key={item.key}
103+
title={item.tooltip}
104+
placement="right"
105+
mouseLeaveDelay={0}
106+
destroyTooltipOnHide
107+
open={tooltipVisible[item.key]}
108+
onOpenChange={(visible) => handleTooltipOpenChange(item.key, visible)}
109+
>
73110
<Badge count={item.badge} size="small" offset={[4, -4]}>
74111
<Button
75112
type={activeTab === item.key ? 'primary' : 'text'}
76113
size="large"
77114
icon={item.icon}
78115
className="activity-bar-button"
79-
onClick={() => onTabChange(item.key)}
116+
onClick={() => handleTabClick(item.key)}
80117
/>
81118
</Badge>
82119
</Tooltip>
83120
))}
84121

85122
{/* 设置按钮独立处理 */}
86-
<Tooltip title="设置 - 应用程序设置" placement="right">
123+
<Tooltip
124+
title="设置 - 应用程序设置"
125+
placement="right"
126+
mouseLeaveDelay={0}
127+
destroyTooltipOnHide
128+
open={tooltipVisible.settings}
129+
onOpenChange={(visible) => handleTooltipOpenChange('settings', visible)}
130+
>
87131
<Button
88132
type="text"
89133
size="large"

src/renderer/src/components/layout/activitybar/activitybar.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@
66
background: #fafafa;
77
border-right: 1px solid #f0f0f0;
88
gap: 4px;
9+
height: 100%;
910
}

src/renderer/src/components/layout/titlebar/TitleBar.tsx

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,19 @@
11
import React, { useState, useEffect } from 'react'
22
import { Button, Tooltip } from 'antd'
3-
import {
4-
MenuUnfoldOutlined,
5-
MenuFoldOutlined,
6-
MinusOutlined,
7-
BorderOutlined,
3+
import {
4+
MinusOutlined,
5+
BorderOutlined,
86
CloseOutlined,
9-
CopyOutlined
7+
CopyOutlined,
8+
SendOutlined
109
} from '@ant-design/icons'
1110
import './titlebar.css'
1211

1312
interface TitleBarProps {
1413
title: string
15-
sidebarCollapsed: boolean
16-
onToggleSidebar: () => void
1714
}
1815

19-
export default function TitleBar({ title, sidebarCollapsed, onToggleSidebar }: TitleBarProps) {
16+
export default function TitleBar({ title }: TitleBarProps) {
2017
const [isMaximized, setIsMaximized] = useState(false)
2118
const [isMac, setIsMac] = useState(false)
2219

@@ -65,14 +62,7 @@ export default function TitleBar({ title, sidebarCollapsed, onToggleSidebar }: T
6562
<div className={`custom-title-bar ${isMac ? 'mac' : ''}`}>
6663
<div className="title-bar-drag-region">
6764
<div className="title-bar-left">
68-
<Tooltip title={sidebarCollapsed ? '展开侧边栏' : '收起侧边栏'}>
69-
<Button
70-
type="text"
71-
icon={sidebarCollapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
72-
onClick={onToggleSidebar}
73-
className="title-bar-button"
74-
/>
75-
</Tooltip>
65+
<SendOutlined className="title-bar-logo" />
7666
<h2 className="title-bar-title">{title}</h2>
7767
</div>
7868
{!isMac && (

src/renderer/src/components/layout/titlebar/titlebar.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@
7575
color: #fff;
7676
}
7777

78+
.title-bar-logo {
79+
font-size: 16px;
80+
padding: 0 16px;
81+
}
82+
7883
.title-bar-title {
7984
margin: 0;
8085
font-size: 12px;

src/renderer/src/components/pages/chat/ChatHeader.tsx

Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -152,11 +152,6 @@ export default function ChatHeader({
152152
]
153153

154154
const collapseOptions: MenuProps['items'] = [
155-
{
156-
key: 'collapse-all',
157-
label: '折叠全部消息',
158-
onClick: onCollapseAll
159-
},
160155
{
161156
key: 'expand-all',
162157
label: '展开全部消息',
@@ -392,25 +387,17 @@ export default function ChatHeader({
392387
</div>
393388
<div className="chat-header-right">
394389
<Space>
395-
{/* 消息树切换按钮 */}
396-
{onToggleMessageTree && (
397-
<Button
398-
type="text"
399-
size="small"
400-
icon={<BranchesOutlined />}
401-
onClick={onToggleMessageTree}
402-
title={messageTreeCollapsed ? '展开消息树' : '收起消息树'}
403-
>
404-
消息树
405-
</Button>
406-
)}
407390
{/* 消息折叠/展开下拉按钮 */}
408391
{messages.length > 0 && (
409-
<Dropdown menu={{ items: collapseOptions }} trigger={['click']}>
410-
<Button type="text" size="small" icon={<DownOutlined />}>
411-
折叠展开
412-
</Button>
413-
</Dropdown>
392+
<Dropdown.Button
393+
type="text"
394+
menu={{ items: collapseOptions }}
395+
trigger={['click']}
396+
onClick={onCollapseAll}
397+
icon={<DownOutlined />}
398+
>
399+
折叠全部
400+
</Dropdown.Button>
414401
)}
415402
<Dropdown menu={{ items: exportOptions }} trigger={['click']}>
416403
<Button icon={<ExportOutlined />} type="text">
@@ -419,8 +406,7 @@ export default function ChatHeader({
419406
</Dropdown>
420407
{/* 更多选项按钮 */}
421408
<Dropdown menu={{ items: moreOptions }} trigger={['click']}>
422-
<Button type="text" size="small" icon={<MoreOutlined />}>
423-
更多
409+
<Button type="text" icon={<MoreOutlined />}>
424410
</Button>
425411
</Dropdown>
426412
</Space>

src/renderer/src/components/pages/chat/message-tree-sidebar.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@
7272

7373
/* 侧边栏头部 */
7474
.tree-sidebar-header {
75-
padding: 12px 16px;
75+
padding: 12px 8px;
7676
border-bottom: 1px solid #e8e8e8;
7777
background: #fff;
7878
display: flex;

0 commit comments

Comments
 (0)