mirror of
https://github.com/supabase/supabase.git
synced 2026-09-08 19:08:44 +08:00
## Problem The `_Shadcn_` suffix isn't needed anymore on `Command` components ## Solution - Remove the `_Shadcn_` suffix - Simplify UI package exports - Apply prettier <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Refactor** * Simplified command component imports and exports across the UI library by removing internal naming aliases and adopting direct component references. Updated the public UI package barrel export to use wildcard re-exports for cleaner API surface. <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/46153?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
140 lines
4.2 KiB
TypeScript
140 lines
4.2 KiB
TypeScript
import { keepPreviousData } from '@tanstack/react-query'
|
|
import { useDebounce, useIntersectionObserver } from '@uidotdev/usehooks'
|
|
import { useEffect, useMemo, useRef, useState } from 'react'
|
|
import { cn, Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from 'ui'
|
|
|
|
import type { ResourcePickerRenderProps } from './SecondLevelNav.Layout'
|
|
import { usePaginatedBucketsQuery } from '@/data/storage/buckets-query'
|
|
|
|
type StorageResourceListProps = ResourcePickerRenderProps & {
|
|
projectRef?: string
|
|
}
|
|
|
|
const SEARCH_DEBOUNCE_MS = 400
|
|
|
|
const useSearchQuery = () => {
|
|
const [search, setSearch] = useState('')
|
|
const debouncedSearch = useDebounce(search, SEARCH_DEBOUNCE_MS)
|
|
const searchQuery = search.length === 0 ? undefined : debouncedSearch
|
|
|
|
return {
|
|
rawQuery: search,
|
|
query: searchQuery,
|
|
setSearch,
|
|
}
|
|
}
|
|
|
|
type UseInfiniteLoadingBucketsParams = {
|
|
projectRef?: string
|
|
searchQuery?: string
|
|
rawQuery: string
|
|
}
|
|
|
|
const useInfiniteLoadingBuckets = ({
|
|
projectRef,
|
|
searchQuery,
|
|
rawQuery,
|
|
}: UseInfiniteLoadingBucketsParams) => {
|
|
const { data, isFetching, hasNextPage, fetchNextPage, isFetchingNextPage } =
|
|
usePaginatedBucketsQuery(
|
|
{ projectRef, search: searchQuery },
|
|
{
|
|
enabled: !!projectRef,
|
|
placeholderData: rawQuery.length === 0 ? keepPreviousData : undefined,
|
|
}
|
|
)
|
|
const buckets = useMemo(() => data?.pages.flatMap((page) => page) ?? [], [data])
|
|
|
|
const scrollContainerRef = useRef<HTMLDivElement | null>(null)
|
|
const [sentinelRef, entry] = useIntersectionObserver({
|
|
threshold: 1,
|
|
root: scrollContainerRef.current,
|
|
rootMargin: '0px',
|
|
})
|
|
|
|
useEffect(() => {
|
|
if (entry?.isIntersecting && hasNextPage && !isFetching) {
|
|
fetchNextPage()
|
|
}
|
|
}, [entry?.isIntersecting, fetchNextPage, hasNextPage, isFetching])
|
|
|
|
return {
|
|
isFetching,
|
|
hasNextPage,
|
|
isFetchingNextPage,
|
|
buckets,
|
|
scrollContainerRef,
|
|
sentinelRef,
|
|
}
|
|
}
|
|
|
|
export const StorageResourceList = ({
|
|
projectRef,
|
|
selectedResource,
|
|
onSelect,
|
|
closePopover,
|
|
}: StorageResourceListProps) => {
|
|
const { rawQuery, query: searchQuery, setSearch } = useSearchQuery()
|
|
|
|
const { isFetching, hasNextPage, isFetchingNextPage, buckets, scrollContainerRef, sentinelRef } =
|
|
useInfiniteLoadingBuckets({
|
|
projectRef,
|
|
searchQuery,
|
|
rawQuery,
|
|
})
|
|
|
|
const handleSelect = (value: string) => {
|
|
onSelect(value)
|
|
closePopover()
|
|
}
|
|
|
|
const showEmptyState = !isFetching && buckets.length === 0
|
|
const emptyMessage =
|
|
rawQuery.length > 0 ? 'No buckets found for this search' : 'No buckets available'
|
|
|
|
return (
|
|
<Command shouldFilter={false}>
|
|
<CommandInput
|
|
showResetIcon
|
|
value={rawQuery}
|
|
onValueChange={setSearch}
|
|
placeholder="Search buckets..."
|
|
handleReset={() => setSearch('')}
|
|
/>
|
|
<CommandList>
|
|
<CommandEmpty hidden={!showEmptyState} className="py-3 text-sm text-foreground-light">
|
|
{emptyMessage}
|
|
</CommandEmpty>
|
|
<CommandGroup>
|
|
{isFetching && buckets.length === 0 ? (
|
|
<div className="px-4 py-3 text-sm text-foreground-light">Loading buckets...</div>
|
|
) : (
|
|
<div ref={scrollContainerRef} className="max-h-72 min-h-[150px] overflow-y-auto">
|
|
{buckets.map((bucket) => {
|
|
const isActive = bucket.name === selectedResource
|
|
return (
|
|
<CommandItem
|
|
key={bucket.id}
|
|
value={bucket.name}
|
|
className={cn(
|
|
'cursor-pointer px-4',
|
|
isActive ? 'text-foreground bg-selection' : 'text-foreground-light'
|
|
)}
|
|
onSelect={() => handleSelect(bucket.name)}
|
|
>
|
|
<p className="truncate">{bucket.name}</p>
|
|
</CommandItem>
|
|
)
|
|
})}
|
|
{hasNextPage && <div ref={sentinelRef} className="h-2 w-full" />}
|
|
</div>
|
|
)}
|
|
</CommandGroup>
|
|
{isFetchingNextPage && (
|
|
<div className="px-4 py-2 text-sm text-foreground-light">Loading more buckets...</div>
|
|
)}
|
|
</CommandList>
|
|
</Command>
|
|
)
|
|
}
|