design refine

This commit is contained in:
Saxon Fletcher committed 2026-05-22 10:58:34 +10:00
1 parent 438c80519d
commit faa33d053d
3 files changed
+56 -120

No files matched your search

@@ -104,7 +104,7 @@ describe('FileExplorerHeader', () => {
).toBeTruthy()
})
it('renders text-xs breadcrumbs in a row above the search bar in list view', () => {
it('renders a go-up button to the left of search when not at bucket root', () => {
mockUseStoragePreference.mockReturnValue(createPreference(STORAGE_VIEWS.LIST))
render(
@@ -115,19 +115,43 @@ describe('FileExplorerHeader', () => {
/>
)
const rootBreadcrumb = screen.getByRole('button', { name: 'my-bucket' })
const inactiveBreadcrumb = screen.getByRole('button', { name: 'images' })
const activeBreadcrumb = screen.getByText('2024')
expect(rootBreadcrumb).toHaveClass('text-xs')
expect(inactiveBreadcrumb).toHaveClass('text-xs', 'text-foreground-lighter')
expect(activeBreadcrumb).toHaveClass('text-xs', 'text-foreground')
expect(activeBreadcrumb).not.toHaveClass('text-foreground-lighter')
expect(screen.queryByRole('button', { name: '2024' })).not.toBeInTheDocument()
expect(screen.getByRole('button', { name: 'Go up one level' })).toBeInTheDocument()
expect(screen.queryByRole('button', { name: 'my-bucket' })).not.toBeInTheDocument()
expect(screen.queryByRole('button', { name: 'images' })).not.toBeInTheDocument()
expect(screen.getByPlaceholderText('Search in 2024...')).toBeInTheDocument()
})
it('uses breadcrumb buttons to navigate back to a previous folder level with keyboard input', async () => {
it('does not render the go-up button in column view when not at bucket root', () => {
render(
<FileExplorerHeader
itemSearchString=""
setItemSearchString={vi.fn()}
onFilesUpload={vi.fn()}
/>
)
expect(screen.queryByRole('button', { name: 'Go up one level' })).not.toBeInTheDocument()
})
it('does not render the go-up button at bucket root', () => {
const snapshot = createSnapshot()
snapshot.columns = [makeColumn('my-bucket')]
mockUseStorageExplorerStateSnapshot.mockReturnValue(snapshot)
mockUseStoragePreference.mockReturnValue(createPreference(STORAGE_VIEWS.LIST))
render(
<FileExplorerHeader
itemSearchString=""
setItemSearchString={vi.fn()}
onFilesUpload={vi.fn()}
/>
)
expect(screen.queryByRole('button', { name: 'Go up one level' })).not.toBeInTheDocument()
expect(screen.getByPlaceholderText('Search in root directory...')).toBeInTheDocument()
})
it('goes up one folder level when the go-up button is clicked', async () => {
const snapshot = createSnapshot()
mockUseStorageExplorerStateSnapshot.mockReturnValue(snapshot)
mockUseStoragePreference.mockReturnValue(createPreference(STORAGE_VIEWS.LIST))
@@ -140,12 +164,11 @@ describe('FileExplorerHeader', () => {
/>
)
const breadcrumbButton = screen.getByRole('button', { name: 'images' })
breadcrumbButton.focus()
await userEvent.keyboard('{Enter}')
await userEvent.click(screen.getByRole('button', { name: 'Go up one level' }))
expect(snapshot.popColumnAtIndex).toHaveBeenCalledWith(1)
expect(snapshot.popOpenedFoldersAtIndex).toHaveBeenCalledWith(0)
expect(snapshot.popColumn).toHaveBeenCalled()
expect(snapshot.popOpenedFolders).toHaveBeenCalled()
expect(snapshot.setSelectedFilePreview).toHaveBeenCalledWith(undefined)
})
it('opens path edit mode from Navigate and tracks the click', async () => {
@@ -3,12 +3,10 @@ import { compact, isEqual, noop } from 'lodash'
import {
ArrowLeft,
Check,
ChevronRight,
Columns,
Edit2,
FolderPlus,
List,
LoaderCircle,
RefreshCw,
Search,
Upload,
@@ -132,71 +130,6 @@ const NavigateDialog = ({
)
}
const HeaderBreadcrumbs = ({
loading,
breadcrumbs,
selectBreadcrumb,
}: {
loading: { isLoading: boolean; message: string }
breadcrumbs: string[]
selectBreadcrumb: (i: number) => void
}) => {
// Max 5 crumbs, otherwise replace middle segment with ellipsis and only
// have the first 2 and last 2 crumbs visible
const ellipsis = '...'
const breadcrumbsWithIndexes = breadcrumbs.map((name: string, index: number) => {
return { name, index }
})
const formattedBreadcrumbs =
breadcrumbsWithIndexes.length <= 5
? breadcrumbsWithIndexes
: breadcrumbsWithIndexes
.slice(0, 2)
.concat([{ name: ellipsis, index: -1 }])
.concat(
breadcrumbsWithIndexes.slice(
breadcrumbsWithIndexes.length - 2,
breadcrumbsWithIndexes.length
)
)
return loading.isLoading ? (
<div className="ml-1 flex items-center">
<LoaderCircle size={14} strokeWidth={1.5} className="animate-spin text-foreground-lighter" />
<p className="ml-3 text-xs">{loading.message}</p>
</div>
) : (
<div className="ml-1 flex min-w-0 flex-1 items-center overflow-hidden">
{formattedBreadcrumbs.map((crumb, idx: number) => {
const isEllipsis = crumb.name === ellipsis
const isActive = crumb.index === breadcrumbs.length - 1
return (
<div className="flex shrink-0 items-center" key={`${crumb.index}-${crumb.name}`}>
{idx !== 0 && (
<ChevronRight size={14} strokeWidth={1.5} className="text-foreground-muted mx-1" />
)}
{isEllipsis ? (
<span className="max-w-24 truncate text-xs text-foreground-light">{crumb.name}</span>
) : isActive ? (
<span className="max-w-24 truncate text-xs text-foreground">{crumb.name}</span>
) : (
<button
type="button"
className="max-w-24 truncate border-0 bg-transparent p-0 text-left text-xs text-foreground-lighter transition-colors hover:text-foreground focus-visible:text-foreground"
onClick={() => selectBreadcrumb(crumb.index)}
>
{crumb.name}
</button>
)}
</div>
)
})}
</div>
)
}
interface FileExplorerHeader {
itemSearchString: string
setItemSearchString: (value: string) => void
@@ -226,7 +159,6 @@ export const FileExplorerHeader = ({
popColumn,
popColumnAtIndex,
popOpenedFolders,
popOpenedFoldersAtIndex,
fetchFoldersByPath,
refetchAllOpenedFolders,
refreshAll,
@@ -245,9 +177,7 @@ export const FileExplorerHeader = ({
} = useStoragePreference(projectRef)
const breadcrumbs = columns.map((column) => column.name)
const backDisabled = columns.length <= 1
const isListView = view === STORAGE_VIEWS.LIST
const showFolderBreadcrumbs = breadcrumbs.length > 1
const isBucketRoot = breadcrumbs.length <= 1
const currentFolderName = breadcrumbs[breadcrumbs.length - 1]
const searchPlaceholder = isBucketRoot
@@ -356,13 +286,6 @@ export const FileExplorerHeader = ({
snap.setIsSearching(value.length > 0)
}
/** Methods for breadcrumbs */
const selectBreadcrumb = (columnIndex: number) => {
popColumnAtIndex(columnIndex)
popOpenedFoldersAtIndex(columnIndex - 1)
}
const refreshData = async () => {
await refreshAll()
}
@@ -374,32 +297,22 @@ export const FileExplorerHeader = ({
return (
<div className="border-b border-overlay bg-surface-100">
{isListView && showFolderBreadcrumbs && (
<div className="overflow-x-auto overflow-y-hidden border-b border-overlay">
<div className="flex min-h-8 w-max min-w-full items-center px-4 py-1.5 xl:px-4">
<Button
icon={<ArrowLeft size={14} strokeWidth={1.5} />}
size="tiny"
type="text"
className="shrink-0 px-1"
disabled={backDisabled}
onClick={() => {
setIsPathDialogOpen(false)
onSelectBack()
}}
/>
<HeaderBreadcrumbs
loading={loading}
breadcrumbs={breadcrumbs}
selectBreadcrumb={selectBreadcrumb}
/>
</div>
</div>
)}
<div className="overflow-x-auto overflow-y-hidden">
<div className="overflow-x-auto">
<div className={pageChromeRowClassName}>
<div className="flex min-w-0 flex-1 items-center overflow-hidden">
<div className="flex min-w-0 flex-1 items-center gap-2">
{isListView && !isBucketRoot && (
<Button
size="tiny"
type="outline"
aria-label="Go up one level"
className="w-7 shrink-0 px-1"
icon={<ArrowLeft size={14} strokeWidth={1.5} />}
onClick={() => {
setIsPathDialogOpen(false)
onSelectBack()
}}
/>
)}
<Input
ref={searchInputRef}
size="tiny"
@@ -50,11 +50,11 @@ const PageBreadcrumbs = ({
...props
}: PageBreadcrumbsProps) => {
return (
<div data-slot="page-breadcrumbs" className="border-b">
<div data-slot="page-breadcrumbs">
<PageContainer
size="full"
className={cn(
'flex min-h-(--header-height) items-center justify-between gap-4 py-2',
'flex min-h-(--header-height) items-center justify-between gap-4 border-b py-2',
pageChromeClassName,
containerClassName
)}