mirror of
https://github.com/supabase/supabase.git
synced 2026-06-17 05:08:49 +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 -->
153 lines
4.8 KiB
TypeScript
153 lines
4.8 KiB
TypeScript
import { keepPreviousData } from '@tanstack/react-query'
|
|
import { useDebounce, useIntersectionObserver } from '@uidotdev/usehooks'
|
|
import { Plus } from 'lucide-react'
|
|
import { ReactNode, useEffect, useMemo, useRef, useState } from 'react'
|
|
import {
|
|
Command,
|
|
CommandGroup,
|
|
CommandInput,
|
|
CommandItem,
|
|
CommandList,
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuTrigger,
|
|
ScrollArea,
|
|
} from 'ui'
|
|
import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
|
|
|
|
import { SIDEBAR_KEYS } from '@/components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider'
|
|
import { useContentInfiniteQuery } from '@/data/content/content-infinite-query'
|
|
import type { Content } from '@/data/content/content-query'
|
|
import { SNIPPET_PAGE_LIMIT } from '@/data/content/sql-folders-query'
|
|
import { editorPanelState } from '@/state/editor-panel-state'
|
|
import { useSidebarManagerSnapshot } from '@/state/sidebar-manager-state'
|
|
|
|
type SnippetDropdownProps = {
|
|
projectRef?: string
|
|
trigger: ReactNode
|
|
side?: 'top' | 'bottom' | 'left' | 'right'
|
|
align?: 'start' | 'center' | 'end'
|
|
className?: string
|
|
autoFocus?: boolean
|
|
onSelect: (snippet: { id: string; name: string }) => void
|
|
}
|
|
|
|
type SqlContentItem = Extract<Content, { type: 'sql' }>
|
|
|
|
export const SnippetDropdown = ({
|
|
projectRef,
|
|
trigger,
|
|
side = 'bottom',
|
|
align = 'end',
|
|
className,
|
|
autoFocus = false,
|
|
onSelect,
|
|
}: SnippetDropdownProps) => {
|
|
const { openSidebar } = useSidebarManagerSnapshot()
|
|
const scrollRootRef = useRef<HTMLDivElement | null>(null)
|
|
|
|
const [open, setOpen] = useState(false)
|
|
const [search, setSearch] = useState('')
|
|
const debouncedSearch = useDebounce(search, 500)
|
|
|
|
const {
|
|
data,
|
|
isPending: isLoading,
|
|
hasNextPage,
|
|
fetchNextPage,
|
|
isFetchingNextPage,
|
|
} = useContentInfiniteQuery(
|
|
{
|
|
projectRef,
|
|
type: 'sql',
|
|
limit: SNIPPET_PAGE_LIMIT,
|
|
name: search.length === 0 ? search : debouncedSearch,
|
|
},
|
|
{ placeholderData: keepPreviousData }
|
|
)
|
|
|
|
const snippets = useMemo(() => {
|
|
const items = data?.pages.flatMap((page) => page.content) ?? []
|
|
return items as SqlContentItem[]
|
|
}, [data?.pages])
|
|
|
|
const [sentinelRef, entry] = useIntersectionObserver({
|
|
root: scrollRootRef.current,
|
|
threshold: 0,
|
|
rootMargin: '0px',
|
|
})
|
|
|
|
useEffect(() => {
|
|
if (entry?.isIntersecting && hasNextPage && !isFetchingNextPage && !isLoading) {
|
|
fetchNextPage()
|
|
}
|
|
}, [entry?.isIntersecting, hasNextPage, isFetchingNextPage, isLoading, fetchNextPage])
|
|
|
|
return (
|
|
<DropdownMenu open={open} onOpenChange={setOpen}>
|
|
<DropdownMenuTrigger asChild>{trigger}</DropdownMenuTrigger>
|
|
<DropdownMenuContent
|
|
side={side}
|
|
align={align}
|
|
className={['w-80 p-0', className].filter(Boolean).join(' ')}
|
|
>
|
|
<Command shouldFilter={false}>
|
|
<CommandInput
|
|
showResetIcon
|
|
autoFocus={autoFocus}
|
|
placeholder="Search snippets..."
|
|
value={search}
|
|
onValueChange={setSearch}
|
|
handleReset={() => setSearch('')}
|
|
/>
|
|
<CommandList ref={scrollRootRef}>
|
|
{isLoading ? (
|
|
<p className="text-xs text-center text-foreground-lighter py-3">Loading...</p>
|
|
) : search.length > 0 && snippets.length === 0 ? (
|
|
<p className="text-xs text-center text-foreground-lighter py-3">No snippets found</p>
|
|
) : (
|
|
<CommandGroup>
|
|
<ScrollArea className={snippets.length > 7 ? 'h-[210px]' : ''}>
|
|
{snippets.map((snippet) => (
|
|
<CommandItem
|
|
key={snippet.id}
|
|
value={snippet.id}
|
|
onSelect={() => onSelect({ id: snippet.id, name: snippet.name })}
|
|
>
|
|
{snippet.name}
|
|
</CommandItem>
|
|
))}
|
|
<div ref={sentinelRef} className="h-1 -mt-1" />
|
|
{hasNextPage && (
|
|
<div className="px-2 py-1">
|
|
<ShimmeringLoader className="py-2" />
|
|
</div>
|
|
)}
|
|
</ScrollArea>
|
|
</CommandGroup>
|
|
)}
|
|
|
|
<div className="h-px bg-border-overlay -mx-1" />
|
|
|
|
<CommandGroup>
|
|
<CommandItem
|
|
className="cursor-pointer w-full"
|
|
onSelect={() => {
|
|
setOpen(false)
|
|
editorPanelState.openAsNew()
|
|
openSidebar(SIDEBAR_KEYS.EDITOR_PANEL)
|
|
}}
|
|
>
|
|
<div className="w-full flex items-center gap-2">
|
|
<Plus size={14} strokeWidth={1.5} />
|
|
<p>Create snippet</p>
|
|
</div>
|
|
</CommandItem>
|
|
</CommandGroup>
|
|
</CommandList>
|
|
</Command>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
)
|
|
}
|