sidebar: VSCode-style drag-to-edge splitting, collapse instead of close, theme-compliant tokens

- Split panes by dragging a tab to a pane edge (left/right/up/down zones
  with a drop overlay) or into the center to merge; the toolbar split
  buttons are gone (split-pane.tsx, state.ts moveTabToEdge/insertLeafAt).
- The header close button is now a collapse button; the expand button on
  the right edge stays visible whenever the panel is collapsed, so the
  sidebar can always be reopened (Sidebar.tsx, locales collapse/expand).
- Styles now follow the DSH web-styling rules exactly: --dsw-alias-*
  semantic colors (state-error/warn tokens), --dsw-font-* typography roles
  (size + line-height paired), --dsw-shadow-lv* elevation, no color
  literals or static palette values (sidebar.module.css).
- Tests: +3 for moveTabToEdge (edge split, center merge, self reorder).
This commit is contained in:
Menghuan1918
2026-08-08 10:35:47 +08:00
parent 63e2988ef1
commit 58b4bd71ba
8 changed files with 282 additions and 110 deletions
+3 -24
View File
@@ -8,11 +8,10 @@
import { useState, type ReactNode } from 'react'
import clsx from 'clsx'
import {
IconChevronDownOutline14, IconChevronLeftOutline14, IconChevronRightOutline14,
IconChevronUpOutline14, IconCloseFill14, IconPlusOutline16, Menu,
IconCloseFill14, IconPlusOutline16, Menu,
} from '@deepseek-ai/dsh-client-ui-primitives'
import type { SidebarTab } from './state.ts'
import { t, type CopyKey } from './locales.ts'
import { t } from './locales.ts'
import css from './sidebar.module.css'
/** One + menu option. */
@@ -44,27 +43,19 @@ export function parseDrag(raw: string): TabDragPayload | null {
}
}
const SPLIT_DIRS: Array<{ dir: 'left' | 'right' | 'up' | 'down'; label: CopyKey; icon: (p: { size?: number }) => ReactNode }> = [
{ dir: 'left', label: 'splitLeft', icon: IconChevronLeftOutline14 },
{ dir: 'right', label: 'splitRight', icon: IconChevronRightOutline14 },
{ dir: 'up', label: 'splitUp', icon: IconChevronUpOutline14 },
{ dir: 'down', label: 'splitDown', icon: IconChevronDownOutline14 },
]
export function TabBar(props: {
paneId: string
tabs: SidebarTab[]
active: string | null
onActivate: (tabId: string) => void
onClose: (tabId: string) => void
onSplit: (dir: 'row' | 'col') => void
onNewTab: (optionId: string) => void
newTabOptions: NewTabOption[]
/** Drop of a tab from any pane: (payload, insertBeforeTabId | null). */
onDropTab: (payload: TabDragPayload, before: string | null) => void
}) {
const {
paneId, tabs, active, onActivate, onClose, onSplit, onNewTab, newTabOptions, onDropTab,
paneId, tabs, active, onActivate, onClose, onNewTab, newTabOptions, onDropTab,
} = props
const [menuOpen, setMenuOpen] = useState(false)
const [dragOver, setDragOver] = useState(false)
@@ -122,18 +113,6 @@ export function TabBar(props: {
))}
</div>
<div className={css.tabBarActions}>
{SPLIT_DIRS.map(({ dir, label, icon: Icon }) => (
<button
key={dir}
type="button"
className={css.tabBarButton}
aria-label={t(label)}
title={t(label)}
onClick={() => { onSplit(dir === 'left' || dir === 'right' ? 'row' : 'col') }}
>
<Icon />
</button>
))}
<Menu
open={menuOpen}
onClose={() => { setMenuOpen(false) }}