Files
supabase/apps/studio/components/ui/FunctionSelector.tsx
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

197 lines
6.3 KiB
TypeScript

import { useParams } from 'common'
import { uniqBy } from 'lodash'
import { Check, ChevronsUpDown, Plus } from 'lucide-react'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useState } from 'react'
import {
Alert,
AlertDescription,
AlertTitle,
Button,
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
Popover,
PopoverContent,
PopoverTrigger,
ScrollArea,
} from 'ui'
import {
DatabaseFunctionsData,
useDatabaseFunctionsQuery,
} from '@/data/database-functions/database-functions-query'
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
type DatabaseFunction = DatabaseFunctionsData[number]
interface FunctionSelectorProps {
className?: string
size?: 'tiny' | 'small'
showError?: boolean
schema?: string
value: string
onChange: (value: string) => void
disabled?: boolean
stopScrollPropagation?: boolean
// used to filter the functions by a criteria
filterFunction?: (func: DatabaseFunction) => boolean
noResultsLabel?: React.ReactNode
}
const FunctionSelector = ({
className,
size = 'tiny',
showError = true,
disabled = false,
schema,
value,
onChange,
stopScrollPropagation = false,
filterFunction = () => true,
noResultsLabel = <span>No functions found in this schema.</span>,
}: FunctionSelectorProps) => {
const router = useRouter()
const { ref } = useParams()
const { data: project } = useSelectedProjectQuery()
const [open, setOpen] = useState(false)
const {
data,
error,
isPending: isLoading,
isError,
isSuccess,
refetch,
} = useDatabaseFunctionsQuery({
projectRef: project?.ref,
connectionString: project?.connectionString,
})
const filteredFunctions = (data ?? [])
.filter((func) => schema && func.schema === schema)
.filter(filterFunction)
const functions = uniqBy(filteredFunctions, (func) => func.name)
return (
<div className={className}>
{isLoading && (
<Button type="default" className="justify-start" block size={size} loading>
Loading functions...
</Button>
)}
{showError && isError && (
<Alert variant="warning" className="px-3! py-3!">
<AlertTitle className="text-xs text-amber-900">Failed to load functions</AlertTitle>
<AlertDescription className="text-xs mb-2">Error: {error.message}</AlertDescription>
<Button type="default" size="tiny" onClick={() => refetch()}>
Reload functions
</Button>
</Alert>
)}
{isSuccess && (
<Popover open={open} onOpenChange={setOpen} modal={false}>
<PopoverTrigger asChild>
<Button
size={size}
disabled={!!disabled}
type="default"
className={`w-full [&>span]:w-full ${size === 'small' ? 'py-1.5' : ''}`}
iconRight={
<ChevronsUpDown className="text-foreground-muted" strokeWidth={2} size={14} />
}
>
{value ? (
<div className="w-full flex gap-1">
<p className="text-foreground-lighter">function:</p>
<p className="text-foreground">{value}</p>
</div>
) : (
<div className="w-full flex gap-1">
<p className="text-foreground-lighter">Select a function</p>
</div>
)}
</Button>
</PopoverTrigger>
<PopoverContent className="p-0" side="bottom" align="start" sameWidthAsTrigger>
<Command>
<CommandInput placeholder="Search functions..." />
<CommandList
onWheel={stopScrollPropagation ? (event) => event.stopPropagation() : undefined}
>
<CommandEmpty>No functions found</CommandEmpty>
<CommandGroup>
<ScrollArea className={(functions || []).length > 7 ? 'h-[210px]' : ''}>
{!functions.length && (
<CommandItem
key="no-function-found"
disabled={true}
className="flex items-center justify-between space-x-2 w-full"
>
{noResultsLabel}
</CommandItem>
)}
{functions.map((func) => (
<CommandItem
key={func.id}
value={func.name.replaceAll('"', '')}
className="cursor-pointer flex items-center justify-between space-x-2 w-full"
onSelect={() => {
onChange(func.name)
setOpen(false)
}}
onClick={() => {
onChange(func.name)
setOpen(false)
}}
>
<span>{func.name}</span>
{value === func.name && (
<Check className="text-brand" size={14} strokeWidth={2} />
)}
</CommandItem>
))}
</ScrollArea>
</CommandGroup>
<CommandSeparator />
<CommandGroup>
<CommandItem
className="cursor-pointer w-full"
onSelect={() => {
setOpen(false)
router.push(`/project/${ref}/database/functions`)
}}
onClick={() => setOpen(false)}
>
<Link
href={`/project/${ref}/database/functions`}
onClick={() => {
setOpen(false)
}}
className="w-full flex items-center gap-2"
>
<Plus size={14} strokeWidth={1.5} />
<p>New function</p>
</Link>
</CommandItem>
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
)}
</div>
)
}
export default FunctionSelector