feat(host): show-hidden toggle in the directory browser footer
This commit is contained in:
@@ -242,6 +242,32 @@
|
||||
border-top: 1px solid var(--dsw-alias-border-l3);
|
||||
}
|
||||
|
||||
/* Show-hidden toggle: a subtle text button in the footer, left of the gap. */
|
||||
.showHiddenToggle {
|
||||
border: none;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
font-weight: 500;
|
||||
color: var(--dsw-alias-label-secondary);
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.showHiddenToggle:hover {
|
||||
color: var(--dsw-alias-label-primary);
|
||||
}
|
||||
|
||||
.showHiddenToggle:disabled {
|
||||
color: var(--dsw-alias-label-caption);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.showHiddenToggleActive {
|
||||
color: var(--dsw-alias-label-primary);
|
||||
}
|
||||
|
||||
.footerGap {
|
||||
flex: 1 1 0;
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
* 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 filtered here (a
|
||||
* show-hidden toggle is deferred work, client-side only).
|
||||
* error surface. Hidden entries are host-flagged and hidden by default;
|
||||
* a "Show hidden files" toggle in the footer reveals them (client-side only).
|
||||
*/
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import clsx from 'clsx'
|
||||
@@ -60,16 +60,17 @@ function displayCrumbs(listing: DirectoryListing, homeLabel: string): DirectoryE
|
||||
}
|
||||
|
||||
/** One column of folder rows (the Miller view renders one or two of these). */
|
||||
function LevelColumn({ entries, selectedPath, busy, onPick, wide }: {
|
||||
function LevelColumn({ entries, selectedPath, busy, onPick, wide, showHidden }: {
|
||||
entries: readonly DirectoryEntry[]
|
||||
selectedPath: string | null
|
||||
busy: boolean
|
||||
onPick: (entry: DirectoryEntry) => void
|
||||
wide: boolean
|
||||
showHidden: boolean
|
||||
}) {
|
||||
return (
|
||||
<div className={clsx(css.column, wide && css.columnWide)} role="list">
|
||||
{entries.filter(entry => !entry.hidden).map((entry) => {
|
||||
{entries.filter(entry => showHidden || !entry.hidden).map((entry) => {
|
||||
const selected = entry.path === selectedPath
|
||||
return (
|
||||
// The wrapper carries the list semantics; the row keeps its NATIVE
|
||||
@@ -110,6 +111,8 @@ export function DirectoryBrowser({ open, listDirectory, createDirectory, onOpen,
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
// Path-edit state: null = breadcrumb mode; a string = the draft being typed.
|
||||
const [pathDraft, setPathDraft] = useState<string | null>(null)
|
||||
// Show-hidden toggle state (pure client-side filter, reset on close).
|
||||
const [showHidden, setShowHidden] = useState(false)
|
||||
// Create-folder state: null = closed; a string = the nested dialog's draft.
|
||||
const [folderDraft, setFolderDraft] = useState<string | null>(null)
|
||||
const [creatingFolder, setCreatingFolder] = useState(false)
|
||||
@@ -213,6 +216,7 @@ export function DirectoryBrowser({ open, listDirectory, createDirectory, onOpen,
|
||||
setSelected(null)
|
||||
setChild(null)
|
||||
setCreatingFolder(false)
|
||||
setShowHidden(false)
|
||||
navigate()
|
||||
return
|
||||
}
|
||||
@@ -409,6 +413,7 @@ export function DirectoryBrowser({ open, listDirectory, createDirectory, onOpen,
|
||||
busy={parentInert}
|
||||
onPick={select}
|
||||
wide={!twoPane}
|
||||
showHidden={showHidden}
|
||||
/>
|
||||
)}
|
||||
{twoPane && <span className={css.divider} />}
|
||||
@@ -419,6 +424,7 @@ export function DirectoryBrowser({ open, listDirectory, createDirectory, onOpen,
|
||||
busy={parentInert}
|
||||
onPick={advance}
|
||||
wide={false}
|
||||
showHidden={showHidden}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -442,6 +448,14 @@ export function DirectoryBrowser({ open, listDirectory, createDirectory, onOpen,
|
||||
>
|
||||
{t('browser.newFolder')}
|
||||
</Button>
|
||||
<button
|
||||
type="button"
|
||||
className={clsx(css.showHiddenToggle, showHidden && css.showHiddenToggleActive)}
|
||||
disabled={parentInert}
|
||||
onClick={() => { setShowHidden(prev => !prev) }}
|
||||
>
|
||||
{t(showHidden ? 'browser.hideHidden' : 'browser.showHidden')}
|
||||
</button>
|
||||
<span className={css.footerGap} />
|
||||
<Button variant="outline" className={clsx(css.footerAction)} disabled={parentInert} onClick={onClose}>{t('browser.cancel')}</Button>
|
||||
<Button
|
||||
|
||||
@@ -46,6 +46,8 @@ export function apply(ctx: ClientContext): void {
|
||||
'browser.editPath': '编辑路径',
|
||||
'browser.loading': '加载中…',
|
||||
'browser.truncated': '文件夹过多,仅显示开头部分。',
|
||||
'browser.showHidden': '显示隐藏文件',
|
||||
'browser.hideHidden': '隐藏隐藏文件',
|
||||
}],
|
||||
['en', {
|
||||
'browser.title': 'Select Workspace Directory',
|
||||
@@ -60,6 +62,8 @@ export function apply(ctx: ClientContext): void {
|
||||
'browser.editPath': 'Edit path',
|
||||
'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 {
|
||||
|
||||
@@ -162,6 +162,8 @@ describe('directory-picker-browse client half', () => {
|
||||
// zh is the shipped default locale.
|
||||
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 () => {
|
||||
|
||||
@@ -103,6 +103,23 @@ describe('DirectoryBrowser', () => {
|
||||
expect(screen.queryByRole('button', { name: '/' })).toBeNull()
|
||||
})
|
||||
|
||||
it('shows hidden entries when the toggle is on and hides them again on close', async () => {
|
||||
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' }))
|
||||
expect(screen.getByText('.config')).toBeTruthy()
|
||||
// Toggle hidden files off.
|
||||
fireEvent.click(screen.getByRole('button', { name: 'browser.hideHidden' }))
|
||||
expect(screen.queryByText('.config')).toBeNull()
|
||||
// Close resets the toggle.
|
||||
b.view.rerender(<DirectoryBrowser {...b.props} open={false} />)
|
||||
b.view.rerender(<DirectoryBrowser {...b.props} open />)
|
||||
await waitFor(() => { expect(screen.getByRole('listitem')).toBeTruthy() })
|
||||
expect(screen.queryByText('.config')).toBeNull()
|
||||
})
|
||||
|
||||
it('selects a row into the two-pane view: children preview right, crumbs follow the selection', async () => {
|
||||
const b = mount()
|
||||
await waitFor(() => { expect(screen.getByRole('listitem')).toBeTruthy() })
|
||||
|
||||
Reference in New Issue
Block a user