feat(host): fixed-label show-hidden toggle; even divider clearance; 680x500 card
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
/* Directory-browser dialog (figma 813-23126 family). The shared Modal renders
|
||||
* headless here — mask, card, Escape only — and this module owns the figma
|
||||
* frame: 600×420 card (viewport-clamped), header (title + crumbs, l3 separator),
|
||||
* frame: 680×500 card (viewport-clamped; upsized from the figma 600×420),
|
||||
* header (title + crumbs, l3 separator),
|
||||
* the one-or-two-column Miller content, and the bordered footer. */
|
||||
|
||||
/* Doubled class beats Modal's own .dialog regardless of stylesheet order. */
|
||||
@@ -8,19 +9,19 @@
|
||||
* columns scroll, so shrinking the height keeps Open/Cancel reachable
|
||||
* instead of clipping them below a fixed overlay. */
|
||||
.dialog.dialog {
|
||||
width: min(600px, 100%);
|
||||
height: min(420px, calc(100dvh - 32px));
|
||||
width: min(680px, 100%);
|
||||
height: min(500px, calc(100dvh - 32px));
|
||||
padding: 0;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
/* Header block: pl24 pr14 pt22 pb12, 8px between title row and crumb row. */
|
||||
/* Header block: pl24 pr14 pt16 pb8, 8px between title row and crumb row. */
|
||||
.header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
flex: none;
|
||||
padding: 22px 14px 12px 24px;
|
||||
padding: 16px 14px 8px 24px;
|
||||
border-bottom: 1px solid var(--dsw-alias-border-l3);
|
||||
}
|
||||
|
||||
@@ -56,6 +57,7 @@
|
||||
min-height: 0;
|
||||
gap: 20px;
|
||||
overflow-x: auto;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.crumbTrail {
|
||||
@@ -126,28 +128,31 @@
|
||||
color: var(--dsw-alias-label-primary);
|
||||
}
|
||||
|
||||
/* Miller content: pt16 px24; columns are 256 wide (or full width solo) with
|
||||
* the hairline divider centered between them; each column scrolls alone. */
|
||||
/* Miller content: symmetric 16px vertical padding so the divider clears the
|
||||
* header and footer rules evenly; columns are 256 wide (or full width solo)
|
||||
* with the hairline divider centered between them; each column scrolls alone. */
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 1 0;
|
||||
min-height: 0;
|
||||
padding: 16px 24px 0;
|
||||
padding: 16px 24px;
|
||||
}
|
||||
|
||||
/* Two-pane columns split the row evenly around the divider; 256px is the
|
||||
* floor below which the row scrolls (scrollbar hidden, the effect pins the
|
||||
* child pane into view) instead of squeezing the panes. */
|
||||
.column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
width: 256px;
|
||||
flex: none;
|
||||
flex: 1 1 0;
|
||||
min-width: 256px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.columnWide {
|
||||
width: 100%;
|
||||
flex: 1 1 0;
|
||||
}
|
||||
|
||||
.divider {
|
||||
@@ -228,8 +233,8 @@
|
||||
color: var(--dsw-alias-state-error-primary);
|
||||
}
|
||||
|
||||
/* Footer: l3 separator on top, pt12 px24, New-folder pinned left; the fixed
|
||||
* card leaves the figma 28px below the 36px buttons. */
|
||||
/* Footer: l3 separator on top, symmetric padding so the row sits vertically
|
||||
* centered in the bar; New-folder and the show-hidden toggle pin left. */
|
||||
.footerBar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -238,12 +243,17 @@
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
flex: none;
|
||||
padding: 12px 24px 28px;
|
||||
padding: 16px 24px;
|
||||
border-top: 1px solid var(--dsw-alias-border-l3);
|
||||
}
|
||||
|
||||
/* Show-hidden toggle: a subtle text button in the footer, left of the gap. */
|
||||
/* Show-hidden toggle: a subtle fixed-label text button left of the gap;
|
||||
* the pressed state seats a check glyph before the label (Menu's selected
|
||||
* vocabulary) instead of flipping the wording. */
|
||||
.showHiddenToggle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
|
||||
@@ -10,13 +10,14 @@
|
||||
* selects the created folder. Open adopts the selected folder, falling back
|
||||
* to the listed level. Pure consumer of the injected browse calls — the
|
||||
* owning flow decides what "Open" means and owns the workspace-creation
|
||||
* error surface. Hidden entries are host-flagged and hidden by default;
|
||||
* a "Show hidden files" toggle in the footer reveals them (client-side only).
|
||||
* error surface. Hidden entries are host-flagged and hidden by default; the
|
||||
* footer's fixed-label "Show hidden files" toggle (aria-pressed, check when
|
||||
* on) reveals them (client-side only).
|
||||
*/
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import clsx from 'clsx'
|
||||
import {
|
||||
Button, IconChevronRightOutline14, IconFolderClose16, IconFolderOpen16, IconPlusOutline16, Modal,
|
||||
Button, IconCheckOutline16, IconChevronRightOutline14, IconFolderClose16, IconFolderOpen16, IconPlusOutline16, Modal,
|
||||
} from '@deepseek-ai/dsh-client-ui-primitives'
|
||||
import type { DirectoryEntry, DirectoryListing } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import { DirectoryBrowseError } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
@@ -451,10 +452,12 @@ export function DirectoryBrowser({ open, listDirectory, createDirectory, onOpen,
|
||||
<button
|
||||
type="button"
|
||||
className={clsx(css.showHiddenToggle, showHidden && css.showHiddenToggleActive)}
|
||||
aria-pressed={showHidden}
|
||||
disabled={parentInert}
|
||||
onClick={() => { setShowHidden(prev => !prev) }}
|
||||
>
|
||||
{t(showHidden ? 'browser.hideHidden' : 'browser.showHidden')}
|
||||
{showHidden && <IconCheckOutline16 size={14} />}
|
||||
{t('browser.showHidden')}
|
||||
</button>
|
||||
<span className={css.footerGap} />
|
||||
<Button variant="outline" className={clsx(css.footerAction)} disabled={parentInert} onClick={onClose}>{t('browser.cancel')}</Button>
|
||||
|
||||
@@ -47,7 +47,6 @@ export function apply(ctx: ClientContext): void {
|
||||
'browser.loading': '加载中…',
|
||||
'browser.truncated': '文件夹过多,仅显示开头部分。',
|
||||
'browser.showHidden': '显示隐藏文件',
|
||||
'browser.hideHidden': '隐藏隐藏文件',
|
||||
}],
|
||||
['en', {
|
||||
'browser.title': 'Select Workspace Directory',
|
||||
@@ -63,7 +62,6 @@ export function apply(ctx: ClientContext): void {
|
||||
'browser.loading': 'Loading…',
|
||||
'browser.truncated': 'Too many folders to list; only the beginning is shown.',
|
||||
'browser.showHidden': 'Show hidden files',
|
||||
'browser.hideHidden': 'Hide hidden files',
|
||||
}],
|
||||
]
|
||||
try {
|
||||
|
||||
@@ -163,7 +163,6 @@ describe('directory-picker-browse client half', () => {
|
||||
expect(injected.t('browser.title')).toBe('选择工作区目录')
|
||||
expect(injected.t('browser.newFolder')).toBe('新建文件夹')
|
||||
expect(injected.t('browser.showHidden')).toBe('显示隐藏文件')
|
||||
expect(injected.t('browser.hideHidden')).toBe('隐藏隐藏文件')
|
||||
})
|
||||
|
||||
it('drives the injected browse calls through the hole entry', async () => {
|
||||
|
||||
@@ -107,11 +107,14 @@ describe('DirectoryBrowser', () => {
|
||||
const b = mount()
|
||||
await waitFor(() => { expect(screen.getByRole('listitem')).toBeTruthy() })
|
||||
expect(screen.queryByText('.config')).toBeNull()
|
||||
// Toggle hidden files on.
|
||||
fireEvent.click(screen.getByRole('button', { name: 'browser.showHidden' }))
|
||||
// The fixed-label toggle reports its state through aria-pressed.
|
||||
const toggle = screen.getByRole('button', { name: 'browser.showHidden' })
|
||||
expect(toggle.getAttribute('aria-pressed')).toBe('false')
|
||||
fireEvent.click(toggle)
|
||||
expect(toggle.getAttribute('aria-pressed')).toBe('true')
|
||||
expect(screen.getByText('.config')).toBeTruthy()
|
||||
// Toggle hidden files off.
|
||||
fireEvent.click(screen.getByRole('button', { name: 'browser.hideHidden' }))
|
||||
fireEvent.click(toggle)
|
||||
expect(toggle.getAttribute('aria-pressed')).toBe('false')
|
||||
expect(screen.queryByText('.config')).toBeNull()
|
||||
// Close resets the toggle.
|
||||
b.view.rerender(<DirectoryBrowser {...b.props} open={false} />)
|
||||
|
||||
Reference in New Issue
Block a user