Files
supabase/apps/studio/components/interfaces/Database/Schemas/FindTableSelector.tsx
T
Joshen Lim 09a1f431bc Mass update for all lucide icon imports to use the component with Icon suffix (#51659)
## Context

This is largely just a mechanical change here:
Opting to use lucide icons with the `Icon` suffix across all icon
imports within `studio`
- so that it’s clearer which components are icons + reduces the
possibility of name collision with our own components
- e.g <User /> vs <UserIcon />

Also added a eslint rule for this:
<img width="1069" height="167" alt="image"
src="https://github.com/user-attachments/assets/7f36f443-5ca2-4bf6-8d22-86e774513bfc"
/>

And updated `AGENTS.md` as such:
```
Icons come from `lucide-react`, always imported with the `Icon` suffix (`import { CheckIcon, Trash2Icon } from 'lucide-react'`) — never the bare names (`Check`) or `Check as CheckIcon` aliases (lint-enforced).
```
2026-10-14 12:56:55 +02:00

143 lines
4.5 KiB
TypeScript

import { Loader2Icon, SearchIcon } from 'lucide-react'
import { ComponentPropsWithoutRef, forwardRef, useMemo, useState } from 'react'
import {
Button,
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
Popover,
PopoverContent,
PopoverTrigger,
ScrollArea,
} from 'ui'
import { useInfiniteTablesQuery } from '@/data/tables/tables-query'
import { useDebouncedValue } from '@/hooks/misc/useDebouncedValue'
import type { SafePostgresTable } from '@/lib/postgres-types'
type FindTableSelectorProps = Omit<ComponentPropsWithoutRef<'div'>, 'onSelect'> & {
projectRef?: string
connectionString?: string | null
schema?: string
disabled?: boolean
size?: 'tiny' | 'small'
open: boolean
onOpenChange: (open: boolean) => void
onSelect: (table: SafePostgresTable) => void
}
export const FindTableSelector = forwardRef<HTMLDivElement, FindTableSelectorProps>(
(
{
className,
projectRef,
connectionString,
schema,
disabled = false,
size = 'tiny',
open,
onOpenChange,
onSelect,
...rest
},
ref
) => {
const [search, setSearch] = useState('')
const debouncedSearch = useDebouncedValue(search, 300)
const nameFilter = debouncedSearch.trim() || undefined
const { data, isFetching, hasNextPage, isFetchingNextPage, fetchNextPage } =
useInfiniteTablesQuery(
{
projectRef,
connectionString,
schema,
includeColumns: false,
pageSize: 50,
nameFilter,
},
{ enabled: open }
)
const tables = useMemo(() => data?.pages.flat() ?? [], [data])
const handleOpenChange = (next: boolean) => {
if (!next) setSearch('')
onOpenChange(next)
}
return (
<div ref={ref} className={className} {...rest}>
<Popover open={open} onOpenChange={handleOpenChange} modal={false}>
<PopoverTrigger asChild>
<Button
size={size}
disabled={disabled}
data-testid="find-table-selector"
icon={<SearchIcon size={14} strokeWidth={1.5} className="text-foreground-muted" />}
>
<span className="text-foreground-lighter">Find table…</span>
</Button>
</PopoverTrigger>
<PopoverContent className="p-0 w-[260px] pointer-events-auto" side="bottom" align="start">
<Command shouldFilter={false}>
<CommandInput
className="text-xs"
placeholder="Find table…"
value={search}
onValueChange={setSearch}
/>
<CommandList>
{isFetching && tables.length === 0 ? (
<div className="flex items-center justify-center gap-x-2 py-6 text-xs text-foreground-light">
<Loader2Icon className="animate-spin" size={14} />
<span>Loading tables</span>
</div>
) : (
<>
<CommandEmpty>No tables found</CommandEmpty>
<CommandGroup>
<ScrollArea className={tables.length > 7 ? 'h-[210px]' : ''}>
{tables.map((table) => (
<CommandItem
key={table.id}
value={String(table.id)}
className="cursor-pointer flex items-center justify-between space-x-2 w-full"
onSelect={() => {
onSelect(table)
handleOpenChange(false)
}}
>
<span>{table.name}</span>
</CommandItem>
))}
{hasNextPage && (
<div className="px-2 py-1.5">
<Button
block
size="tiny"
loading={isFetchingNextPage}
onClick={() => fetchNextPage()}
>
Load more
</Button>
</div>
)}
</ScrollArea>
</CommandGroup>
</>
)}
</CommandList>
</Command>
</PopoverContent>
</Popover>
</div>
)
}
)
FindTableSelector.displayName = 'FindTableSelector'