mirror of
https://github.com/supabase/supabase.git
synced 2026-10-16 14:49:10 +03:00
design refine
This commit is contained in:
1 parent
438c80519d
commit
faa33d053d
3 files changed
+56
-120
No files matched your search
+39
-16
@@ -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
|
||||
)}
|
||||
|
||||
Reference in new issue
Block a user