Files
supabase/apps/docs/components/ProjectConfigVariables/ProjectConfigVariables.ComboBox.tsx
T
Gildas Garcia 243e079a2c chore: remove _Shadcn_ suffix from Command components (#46153)
## 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 -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](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 -->
2026-05-20 15:45:32 +02:00

162 lines
4.8 KiB
TypeScript

import { useIntersectionObserver } from '~/hooks/useIntersectionObserver'
import { noop } from 'lodash-es'
import { Check, ChevronsUpDown } from 'lucide-react'
import { useEffect, useRef, useState } from 'react'
import {
Button_Shadcn_ as Button,
cn,
Command,
CommandGroup as CommandGroup,
CommandInput,
CommandItem as CommandItem,
CommandList as CommandList,
Popover,
PopoverContent,
PopoverTrigger,
ScrollArea,
} from 'ui'
import ShimmeringLoader from 'ui-patterns/ShimmeringLoader'
export interface ComboBoxOption {
id: string
value: string
displayName: string
}
export function ComboBox<Opt extends ComboBoxOption>({
isLoading,
disabled,
name,
options,
selectedOption,
selectedDisplayName,
onSelectOption = noop,
className,
search = '',
hasNextPage = false,
isFetching = false,
isFetchingNextPage = false,
fetchNextPage,
setSearch = () => {},
useCommandSearch = true,
}: {
isLoading: boolean
disabled?: boolean
name: string
options: Opt[]
selectedOption?: string
selectedDisplayName?: string
onSelectOption?: (newValue: string) => void
className?: string
search?: string
hasNextPage?: boolean
isFetching?: boolean
isFetchingNextPage?: boolean
fetchNextPage?: () => void
setSearch?: (value: string) => void
useCommandSearch?: boolean
}) {
const [open, setOpen] = useState(false)
const scrollRootRef = useRef<HTMLDivElement | null>(null)
const [sentinelRef, entry] = useIntersectionObserver({
root: scrollRootRef.current,
threshold: 0,
rootMargin: '0px',
})
useEffect(() => {
if (!isLoading && !isFetching && !isFetchingNextPage && hasNextPage && entry?.isIntersecting) {
fetchNextPage?.()
}
}, [isLoading, isFetching, isFetchingNextPage, hasNextPage, entry?.isIntersecting, fetchNextPage])
return (
<Popover
open={open}
onOpenChange={(value) => {
setOpen(value)
if (!value) setSearch('')
}}
>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
disabled={disabled}
aria-expanded={open}
className={cn(
'overflow-hidden',
'h-auto min-h-10',
'flex justify-between',
'border-none',
'py-0 pl-0 pr-1 text-left',
className
)}
>
{selectedDisplayName ??
(isLoading && options.length > 0
? 'Loading...'
: options.length === 0
? `No ${name} found`
: `Select a ${name}...`)}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="p-0" side="bottom" align="start">
<Command shouldFilter={useCommandSearch}>
<CommandInput
placeholder={`Search ${name}...`}
className="border-none ring-0"
showResetIcon
value={search}
onValueChange={setSearch}
handleReset={() => setSearch('')}
/>
<CommandList>
<CommandGroup>
{isLoading ? (
<div className="px-2 py-1 flex flex-col gap-2">
<ShimmeringLoader className="w-full" />
<ShimmeringLoader className="w-4/5" />
</div>
) : (
<>
{search.length > 0 && options.length === 0 && (
<p className="text-xs text-center text-foreground-lighter py-3">
No {name}s found based on your search
</p>
)}
<ScrollArea className={options.length > 7 ? 'h-[210px]' : ''}>
{options.map((option) => (
<CommandItem
key={option.id}
value={option.value}
onSelect={(selectedValue: string) => {
setOpen(false)
onSelectOption(selectedValue)
}}
className="cursor-pointer"
>
<Check
className={cn(
'mr-2 h-4 w-4',
selectedOption === option.value ? 'opacity-100' : 'opacity-0'
)}
/>
{option.displayName}
</CommandItem>
))}
<div ref={sentinelRef} className="h-1 -mt-1" />
{hasNextPage && <ShimmeringLoader className="px-2 py-3" />}
</ScrollArea>
</>
)}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
)
}