feat(ui): add collapsible persistent sidebar
This commit is contained in:
1 parent
9ce71795b8
commit
101d2164b4
13 files changed
+153
-29
No files matched your search
@@ -17,6 +17,7 @@ import {
|
||||
ChevronRight,
|
||||
Settings,
|
||||
Laptop,
|
||||
PanelLeft,
|
||||
} from 'lucide-react'
|
||||
import { useTabStore } from '../../stores/tabStore'
|
||||
import { useSessionStore, useSnippetStore, useCommandPaletteStore, type Session } from '../../stores'
|
||||
@@ -108,6 +109,15 @@ export function CommandPalette({
|
||||
icon: <Settings size={16} />,
|
||||
action: () => onOpenSettings(),
|
||||
},
|
||||
{
|
||||
id: 'cmd:toggle-sidebar',
|
||||
type: 'command',
|
||||
label: '折叠/展开侧栏',
|
||||
description: '隐藏侧栏以专心操作终端',
|
||||
shortcut: 'Ctrl+B',
|
||||
icon: <PanelLeft size={16} />,
|
||||
action: () => window.dispatchEvent(new CustomEvent('opsxshell:toggle-sidebar')),
|
||||
},
|
||||
{
|
||||
id: 'cmd:new-tab',
|
||||
type: 'command',
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useRef, useCallback } from 'react'
|
||||
import { Plus, Zap, Server, Settings, ListChecks, Terminal, FolderSync, X, TerminalSquare } from 'lucide-react'
|
||||
import { Plus, Zap, Settings, ListChecks, Terminal, FolderSync, X, TerminalSquare, PanelLeftClose } from 'lucide-react'
|
||||
import { useSessionStore, Session } from '../../stores'
|
||||
import { useSettingsStore } from '../../stores/settingsStore'
|
||||
import { useTabStore } from '../../stores/tabStore'
|
||||
@@ -12,6 +12,7 @@ import { SessionTree } from './sidebar/SessionTree'
|
||||
import { SearchBox } from './sidebar/SearchBox'
|
||||
import { useConfirmDialog } from '../common/ConfirmDialog'
|
||||
import BatchCommandDialog from '../sessions/BatchCommandDialog'
|
||||
import appIconUrl from '../../assets/opsxshell-app-icon.png'
|
||||
|
||||
const MIN_SIDEBAR_WIDTH = 180
|
||||
const MAX_SIDEBAR_WIDTH = 400
|
||||
@@ -20,9 +21,10 @@ interface SidebarProps {
|
||||
onNewSession: () => void
|
||||
onQuickConnect: () => void
|
||||
onEditSession: (session: Session) => void
|
||||
onCollapse?: () => void
|
||||
}
|
||||
|
||||
export default function Sidebar({ onNewSession, onQuickConnect, onEditSession }: SidebarProps) {
|
||||
export default function Sidebar({ onNewSession, onQuickConnect, onEditSession, onCollapse }: SidebarProps) {
|
||||
const { sessions, setSessions, addSession, removeSession } = useSessionStore()
|
||||
const { settings, setSetting } = useSettingsStore()
|
||||
const { addTab } = useTabStore()
|
||||
@@ -259,15 +261,31 @@ export default function Sidebar({ onNewSession, onQuickConnect, onEditSession }:
|
||||
{/* Header */}
|
||||
<div className="p-3.5 border-b border-dark-700/30">
|
||||
<div className="flex items-center gap-2.5 mb-3.5 px-0.5">
|
||||
<div className="w-8 h-8 bg-gradient-to-br from-accent-blue to-accent-blue/70 rounded-lg flex items-center justify-center shadow-sm shadow-accent-blue/25">
|
||||
<Server className="w-4 h-4 text-white" />
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<img
|
||||
src={appIconUrl}
|
||||
alt="OpsXShell"
|
||||
width={32}
|
||||
height={32}
|
||||
className="h-8 w-8 flex-shrink-0 rounded-[9px] object-cover"
|
||||
draggable={false}
|
||||
/>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="font-semibold text-sm text-content-primary tracking-tight leading-none">
|
||||
OpsXShell
|
||||
</div>
|
||||
<div className="text-[10px] text-txt-faint mt-1">远程运维工作台</div>
|
||||
<div className="text-[10px] text-txt-faint mt-1">SSH / SFTP 终端</div>
|
||||
</div>
|
||||
{onCollapse && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onCollapse}
|
||||
title="折叠侧栏 (Ctrl/Cmd+B)"
|
||||
aria-label="折叠侧栏"
|
||||
className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg text-txt-muted transition-colors hover:bg-dark-700 hover:text-content-primary"
|
||||
>
|
||||
<PanelLeftClose className="h-4 w-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-1.5">
|
||||
@@ -414,9 +432,14 @@ export default function Sidebar({ onNewSession, onQuickConnect, onEditSession }:
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Resize Handle */}
|
||||
{/* Resize Handle — 双击也可折叠 */}
|
||||
<div
|
||||
onMouseDown={handleResizeStart}
|
||||
onDoubleClick={(e) => {
|
||||
e.preventDefault()
|
||||
onCollapse?.()
|
||||
}}
|
||||
title="拖拽调整宽度,双击折叠"
|
||||
className={`
|
||||
absolute top-0 right-0 bottom-0 w-1 cursor-col-resize
|
||||
hover:bg-accent-blue/50 active:bg-accent-blue/70
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Terminal, Plus, Zap, Clock, Server, ChevronRight } from 'lucide-react'
|
||||
import { Plus, Zap, Clock, Server, ChevronRight } from 'lucide-react'
|
||||
import { useSessionStore } from '../../stores'
|
||||
import { useTabStore } from '../../stores/tabStore'
|
||||
import EmptyState from '../common/EmptyState'
|
||||
import LocalShellMenu from '../layout/sidebar/LocalShellMenu'
|
||||
import appIconUrl from '../../assets/opsxshell-app-icon.png'
|
||||
|
||||
interface WelcomeViewProps {
|
||||
onNewSession?: () => void
|
||||
@@ -27,12 +28,17 @@ export default function WelcomeView({ onNewSession, onQuickConnect }: WelcomeVie
|
||||
<div className="w-full max-w-3xl px-6 py-10 sm:py-12">
|
||||
{/* Hero — 短文案 */}
|
||||
<div className="flex items-center gap-4 mb-8">
|
||||
<div className="w-14 h-14 rounded-2xl bg-gradient-to-br from-accent-blue/30 to-accent-purple/20 border border-dark-600/60 flex items-center justify-center shadow-lg shadow-black/20 flex-shrink-0">
|
||||
<Terminal className="w-7 h-7 text-content-primary" />
|
||||
</div>
|
||||
<img
|
||||
src={appIconUrl}
|
||||
alt="OpsXShell"
|
||||
width={56}
|
||||
height={56}
|
||||
className="h-14 w-14 flex-shrink-0 rounded-2xl object-cover shadow-lg shadow-black/20"
|
||||
draggable={false}
|
||||
/>
|
||||
<div className="min-w-0">
|
||||
<h1 className="text-2xl font-semibold text-content-primary tracking-tight">OpsXShell</h1>
|
||||
<p className="text-sm text-txt-muted mt-0.5">SSH / SFTP 跨平台运维工作台</p>
|
||||
<p className="text-sm text-txt-muted mt-0.5">SSH / SFTP 终端</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -216,6 +216,23 @@ export default function SettingsView() {
|
||||
<span className="w-12 text-right text-xs tabular-nums text-txt-muted">{settings.sidebarWidth}px</span>
|
||||
</div>
|
||||
</SettingRow>
|
||||
<SettingRow label="折叠侧栏" description="隐藏会话树以专心操作终端,可用 Ctrl/Cmd+B 切换">
|
||||
<button
|
||||
type="button"
|
||||
role="switch"
|
||||
aria-checked={settings.sidebarCollapsed}
|
||||
onClick={() => setSetting('sidebarCollapsed', !settings.sidebarCollapsed)}
|
||||
className={`relative h-6 w-11 rounded-full transition-colors ${
|
||||
settings.sidebarCollapsed ? 'bg-accent-blue' : 'bg-dark-600'
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
className={`absolute top-0.5 left-0.5 h-5 w-5 rounded-full bg-white transition-transform ${
|
||||
settings.sidebarCollapsed ? 'translate-x-5' : 'translate-x-0'
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
</SettingRow>
|
||||
</SettingGroup>
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user