Files
supabase/apps/studio/components/interfaces/Database/Extensions/Extensions.tsx
Jordi Enric d640d1e67d fix(studio): tighten Version column width in Extensions table FE-3204 (#45786)
## Problem

On the Database Extensions page, the Version column takes up
disproportionate horizontal space compared to the Description column,
making the table harder to read.

## Fix

Added `w-28` to the Version `TableHead` and its corresponding
`TableCell` in `ExtensionRow`. This constrains the column to a width
appropriate for short version strings and gives the Description column
more room.

## How to test

1. Open a project in Studio and navigate to Database > Extensions.
2. Confirm the Version column is now narrow and the Description column
has proportionally more space.
3. Verify all version strings are still fully visible (e.g. `1.4.8`,
`2.5.2`).

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Style**
* Fixed the "Version" column and its cells to a consistent width for
improved table alignment.
* Preserved monospace styling and spacing in version cells for
readability.
* Removed the max-width limit on comment text cells so comments can use
more space while retaining hover tooltips for full text.

[![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/45786)
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 12:46:55 +02:00

160 lines
5.6 KiB
TypeScript

import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useParams } from 'common'
import { isNull, partition } from 'lodash'
import { AlertCircle, Search } from 'lucide-react'
import { useEffect, useRef, useState } from 'react'
import {
Card,
InputGroup,
InputGroupAddon,
InputGroupInput,
ShadowScrollArea,
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from 'ui'
import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
import { ExtensionRow } from './ExtensionRow'
import { HIDDEN_EXTENSIONS, SEARCH_TERMS } from './Extensions.constants'
import InformationBox from '@/components/ui/InformationBox'
import { NoSearchResults } from '@/components/ui/NoSearchResults'
import { useDatabaseExtensionsQuery } from '@/data/database-extensions/database-extensions-query'
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
import { onSearchInputEscape } from '@/lib/keyboard'
import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
import { useShortcut } from '@/state/shortcuts/useShortcut'
export const Extensions = () => {
const { filter } = useParams()
const { data: project } = useSelectedProjectQuery()
const [filterString, setFilterString] = useState<string>('')
const searchInputRef = useRef<HTMLInputElement>(null)
useShortcut(
SHORTCUT_IDS.LIST_PAGE_FOCUS_SEARCH,
() => {
searchInputRef.current?.focus()
searchInputRef.current?.select()
},
{ label: 'Search extensions' }
)
useShortcut(SHORTCUT_IDS.LIST_PAGE_RESET_FILTERS, () => {
setFilterString('')
})
const { data = [], isPending: isLoading } = useDatabaseExtensionsQuery({
projectRef: project?.ref,
connectionString: project?.connectionString,
})
const visibleExtensions = data.filter((ext) => !HIDDEN_EXTENSIONS.includes(ext.name))
const extensions =
filterString.length === 0
? visibleExtensions
: visibleExtensions.filter((ext) => {
const nameMatchesSearch = ext.name.toLowerCase().includes(filterString.toLowerCase())
const searchTermsMatchesSearch = (SEARCH_TERMS[ext.name] || []).some((x) =>
x.includes(filterString.toLowerCase())
)
return nameMatchesSearch || searchTermsMatchesSearch
})
const [enabledExtensions, disabledExtensions] = partition(
extensions,
(ext) => !isNull(ext.installed_version)
)
const { can: canUpdateExtensions, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions(
PermissionAction.TENANT_SQL_ADMIN_WRITE,
'extensions'
)
useEffect(() => {
if (filter !== undefined) setFilterString(filter as string)
}, [filter])
return (
<>
<div className="mb-4">
<InputGroup className="w-52">
<InputGroupInput
ref={searchInputRef}
size="tiny"
placeholder="Search for an extension"
value={filterString}
onChange={(e) => setFilterString(e.target.value)}
onKeyDown={onSearchInputEscape(filterString, setFilterString)}
/>
<InputGroupAddon>
<Search />
</InputGroupAddon>
</InputGroup>
</div>
{isPermissionsLoaded && !canUpdateExtensions && (
<InformationBox
icon={<AlertCircle className="text-foreground-light" size={18} strokeWidth={2} />}
title="You need additional permissions to update database extensions"
/>
)}
{isLoading ? (
<GenericSkeletonLoader />
) : (
<Card>
<ShadowScrollArea stickyLastColumn>
<Table>
<TableHeader>
<TableRow>
<TableHead key="name">Name</TableHead>
<TableHead key="version" className="w-28">
Version
</TableHead>
<TableHead key="schema">Schema</TableHead>
<TableHead key="description" className="min-w-80">
Description
</TableHead>
<TableHead key="used-by">Used by</TableHead>
<TableHead key="links">Links</TableHead>
{/*
[Joshen] All these classes are just to make the last column sticky
I reckon we can pull these out into the Table component where we can declare
sticky columns via props, but we can do that if we start to have more tables
in the dashboard with sticky columns
*/}
<TableHead key="enabled" className="px-0">
<div className="bg-200! px-4 w-full h-full flex items-center border-l">
Enabled
</div>
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{[...enabledExtensions, ...disabledExtensions].map((extension) => (
<ExtensionRow key={extension.name} extension={extension} />
))}
{extensions.length === 0 && (
<TableRow>
<TableCell colSpan={7}>
<NoSearchResults
className="border-none p-0! bg-transparent"
searchString={filterString}
onResetFilter={() => setFilterString('')}
/>
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</ShadowScrollArea>
</Card>
)}
</>
)
}