ui: preview/edit toggle for markdown, empty-pane open cards, + flush to the rightmost tab
- markdown tabs open in rendered preview with a 预览/编辑 segmented toggle; edit mode uses the CodeMirror source editor (markdown highlighting), the preview renders the live unsaved draft, and the editor stays mounted while previewing so edits survive the toggle. - an empty pane shows openable-type cards (explorer / git / terminal, the same options as the + menu, terminal dimmed at the cap) instead of the bare 'this pane is empty' message; clicking a card opens the tab. - the + button sits immediately right of the last tab (sticky at the scrollport's right edge when the tab row overflows, so it stays reachable).
This commit is contained in:
@@ -12,9 +12,11 @@
|
||||
import { Fragment, useRef, useState } from 'react'
|
||||
import type { ReactNode } from 'react'
|
||||
import clsx from 'clsx'
|
||||
import {
|
||||
IconBranchOutline16, IconCodeOutline16, IconFolderOpen16,
|
||||
} from '@deepseek-ai/dsh-client-ui-primitives'
|
||||
import type { SidebarState, SidebarTab, SplitNode } from './state.ts'
|
||||
import type { DropZone } from './state.ts'
|
||||
import { t } from './locales.ts'
|
||||
import { TabBar, type NewTabOption, parseDrag, type TabDragPayload } from './TabBar.tsx'
|
||||
import css from './sidebar.module.css'
|
||||
|
||||
@@ -79,6 +81,44 @@ function zoneAt(event: React.DragEvent, pane: HTMLElement): DropZone {
|
||||
return 'center'
|
||||
}
|
||||
|
||||
/** The icon of one openable type card (mirror of the + menu options). */
|
||||
function optionIcon(optionId: string): ReactNode {
|
||||
switch (optionId) {
|
||||
case 'explorer': return <IconFolderOpen16 size={16} />
|
||||
case 'git': return <IconBranchOutline16 size={16} />
|
||||
case 'terminal': return <IconCodeOutline16 size={16} />
|
||||
default: return <IconCodeOutline16 size={16} />
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An empty pane's welcome cards: the openable types as cards, clicked to
|
||||
* open (instead of a bare "this pane is empty" message).
|
||||
*/
|
||||
function PaneEmptyCards(props: {
|
||||
newTabOptions: NewTabOption[]
|
||||
onNewTab: (optionId: string) => void
|
||||
}) {
|
||||
const { newTabOptions, onNewTab } = props
|
||||
return (
|
||||
<div className={css.paneEmptyCards}>
|
||||
{newTabOptions.map(option => (
|
||||
<button
|
||||
key={option.id}
|
||||
type="button"
|
||||
className={css.paneCard}
|
||||
disabled={option.disabled === true}
|
||||
title={option.label}
|
||||
onClick={() => { onNewTab(option.id) }}
|
||||
>
|
||||
{optionIcon(option.id)}
|
||||
<span>{option.label}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/** A leaf: tab strip + active content + VSCode-style drop target for tabs. */
|
||||
function LeafView(props: {
|
||||
leaf: { id: string; tabs: SidebarTab[]; active: string | null }
|
||||
@@ -135,7 +175,7 @@ function LeafView(props: {
|
||||
{leaf.tabs.length > 0 ? (
|
||||
<div className={css.paneContent}>{activeTab !== undefined ? renderTab(activeTab) : null}</div>
|
||||
) : (
|
||||
<div className={css.paneEmpty}>{t('emptyPane')}</div>
|
||||
<PaneEmptyCards newTabOptions={newTabOptions} onNewTab={onNewTab} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user