Files
Danny White 476d4a5851 refactor(ui): drop redundant Button variant="default" props (#50161)
## What kind of change does this PR introduce?

Mechanical cleanup on top of the Button default-variant change (#50160).

## What is the current behavior?

Many callsites still pass `variant="default"` even though that is now
the component default.

## What is the new behavior?

Removes redundant static `variant="default"` from legacy `Button` and
`ButtonTooltip` callsites. Keeps explicit defaults where they document
the API:

- `button-default.tsx` and `button-sizes.tsx` demos
- `DocsButton`, which pins neutral styling at the wrapper boundary

## To test

Studio:

- [Auth → Rate
Limits](https://studio-staging-2s957kwc4-supabase.vercel.app/dashboard/project/_/auth/rate-limits):
dirty the form so Cancel appears; Cancel stays neutral, Save stays green
- [Project Settings → API
Keys](https://studio-staging-2s957kwc4-supabase.vercel.app/dashboard/project/_/settings/api-keys):
`DocsButton` in the header actions stays neutral

Design system:

- [Design system →
Button](https://design-system-git-dnywh-dc924ac1-supabase.vercel.app/design-system/docs/components/button):
`button-default` / `button-sizes` still show explicit default styling;
Primary (green) is restricted to the Primary section (and `asChild`)

WWW:

- [www → Brand
assets](https://zone-www-dot-com-git-dnywh-dc924ac1-supabase.vercel.app/brand-assets):
Download logo kit / Download button kit stay neutral
2026-09-11 17:05:26 +10:00

250 lines
8.3 KiB
TypeScript

import { ChevronLeft, ChevronRight, RefreshCw, Terminal } from 'lucide-react'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useState } from 'react'
import {
Badge,
Button,
Card,
CardFooter,
Input,
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from 'ui'
import { COMPUTE_REGION_SHORT } from './Compute.constants'
import type {
ComputeInstance,
ComputeInstanceAccess,
ComputeInstanceBuildState,
} from './Compute.types'
import { filterComputeInstances, formatResources, getPage } from './Compute.utils'
import { ComputeInstanceStatePill } from './ComputeInstanceStatePill'
import { RuntimeBadge } from './RuntimeBadge'
import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
interface ComputeListProps {
projectRef: string
instances: ComputeInstance[]
onDeploy: () => void
onRefresh: () => void
isRefreshing: boolean
}
const STATE_FILTERS: { value: ComputeInstanceBuildState | 'all'; label: string }[] = [
{ value: 'all', label: 'All states' },
{ value: 'active', label: 'Active' },
{ value: 'building', label: 'Building' },
{ value: 'failed', label: 'Failed' },
]
const ACCESS_FILTERS: { value: ComputeInstanceAccess | 'all'; label: string }[] = [
{ value: 'all', label: 'All access' },
{ value: 'public', label: 'Public' },
{ value: 'private', label: 'Private' },
]
const PAGE_SIZE = 10
const parseStateFilter = (value: string): ComputeInstanceBuildState | 'all' =>
STATE_FILTERS.find((option) => option.value === value)?.value ?? 'all'
const parseAccessFilter = (value: string): ComputeInstanceAccess | 'all' =>
ACCESS_FILTERS.find((option) => option.value === value)?.value ?? 'all'
export const ComputeList = ({
projectRef,
instances,
onDeploy,
onRefresh,
isRefreshing,
}: ComputeListProps) => {
const router = useRouter()
const [search, setSearch] = useState('')
const [stateFilter, setStateFilter] = useState<ComputeInstanceBuildState | 'all'>('all')
const [accessFilter, setAccessFilter] = useState<ComputeInstanceAccess | 'all'>('all')
const [page, setPage] = useState(1)
const filtered = filterComputeInstances(instances, {
search,
state: stateFilter,
access: accessFilter,
})
const { items: paged, currentPage, totalPages, startIndex } = getPage(filtered, page, PAGE_SIZE)
const resetToFirstPage = () => setPage(1)
const computeInstancePagePath = (name: string) => `/project/${projectRef}/compute/${name}`
return (
<div className="space-y-4">
<div className="flex flex-col gap-3 md:flex-row md:items-center">
<Input
size="small"
className="w-full md:w-64"
placeholder="Search by name"
value={search}
onChange={(e) => {
setSearch(e.target.value)
resetToFirstPage()
}}
/>
<Select
value={stateFilter}
onValueChange={(value) => {
setStateFilter(parseStateFilter(value))
resetToFirstPage()
}}
>
<SelectTrigger size="small" className="w-full md:w-40">
<SelectValue />
</SelectTrigger>
<SelectContent>
{STATE_FILTERS.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
<Select
value={accessFilter}
onValueChange={(value) => {
setAccessFilter(parseAccessFilter(value))
resetToFirstPage()
}}
>
<SelectTrigger size="small" className="w-full md:w-40">
<SelectValue />
</SelectTrigger>
<SelectContent>
{ACCESS_FILTERS.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
<div className="flex items-center gap-3 md:ml-auto">
<Button icon={<RefreshCw />} loading={isRefreshing} onClick={onRefresh}>
Refresh
</Button>
<Button variant="primary" icon={<Terminal />} onClick={onDeploy}>
Deploy
</Button>
</div>
</div>
<Card>
<Table className="table-fixed">
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead className="w-36">State</TableHead>
<TableHead className="w-40">Runtime</TableHead>
<TableHead className="w-28">Access</TableHead>
<TableHead className="hidden w-20 xl:table-cell">Region</TableHead>
<TableHead className="hidden w-48 lg:table-cell">Resources</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{filtered.length === 0 && (
<TableRow>
<TableCell colSpan={6} className="text-center text-foreground-lighter">
No instances match your filters
</TableCell>
</TableRow>
)}
{paged.map((instance) => (
<TableRow
key={instance.name}
className="cursor-pointer"
onClick={() => router.push(computeInstancePagePath(instance.name))}
>
<TableCell className="max-w-0 font-medium text-foreground">
<Link
href={computeInstancePagePath(instance.name)}
className="block truncate"
onClick={(e) => e.stopPropagation()}
>
{instance.name}
</Link>
</TableCell>
<TableCell className="w-36">
<ComputeInstanceStatePill instance={instance} />
</TableCell>
<TableCell className="w-40 truncate">
<RuntimeBadge runtime={instance.runtime} />
</TableCell>
<TableCell className="w-28">
{instance.access === 'public' ? (
<Badge variant="success">Public</Badge>
) : (
<Badge>Private</Badge>
)}
</TableCell>
<TableCell className="hidden w-20 truncate text-foreground-light xl:table-cell">
{COMPUTE_REGION_SHORT}
</TableCell>
<TableCell className="hidden w-48 truncate text-foreground-light lg:table-cell">
{formatResources(instance)}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
{filtered.length > 0 && (
<CardFooter className="flex items-center justify-between border-t p-4">
<p className="text-sm text-foreground-muted">
Showing {startIndex + 1} to {startIndex + paged.length} of {filtered.length} instance
{filtered.length === 1 ? '' : 's'}
</p>
<div className="flex items-center gap-x-2" aria-label="Pagination">
<ButtonTooltip
icon={<ChevronLeft />}
aria-label="Previous page"
size="tiny"
disabled={currentPage === 1}
onClick={() => setPage(currentPage - 1)}
tooltip={{
content: {
side: 'top',
text: currentPage === 1 ? 'Already on the first page' : undefined,
},
}}
/>
<span className="text-sm text-foreground-light tabular-nums">
Page {currentPage} of {totalPages}
</span>
<ButtonTooltip
icon={<ChevronRight />}
aria-label="Next page"
size="tiny"
disabled={currentPage >= totalPages}
onClick={() => setPage(currentPage + 1)}
tooltip={{
content: {
side: 'top',
text: currentPage >= totalPages ? 'Already on the last page' : undefined,
},
}}
/>
</div>
</CardFooter>
)}
</Card>
</div>
)
}