chore(studio): table presentation improvements (#40224)

* fix table head

* fix edge functions list

* fix secret

* access keys

* policies

* misc table fixes

* publication height fix

* fix-publications

* table documentation

* fix colspan

* s3 fixes
This commit is contained in:
Danny White authored and GitHub committed 2025-11-07 04:59:48 +00:00
1 parent 277abeb1f1
commit 3b98a9844d
13 files changed
+97 -113

No files matched your search

@@ -1,44 +1,16 @@
---
title: Table
description: A responsive table component.
description: A responsive table component for basic data.
component: true
source:
shadcn: true
---
Table presents basic tabular data presentation within a [Card](/docs/components/card).
<ComponentPreview name="table-demo" peekCode wide />
## Installation
Columns will naturally take the width of their content. Width specification is therefore not required unless you require columns to span a specific width. An example use-case may be when placing multiple Table instances on the one page, where columns on each table should optically align.
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
```bash
npx shadcn-ui@latest add table
```
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Copy and paste the following code into your project.</Step>
<ComponentSource name="table" />
<Step>Update the import paths to match your project setup.</Step>
</Steps>
</TabsContent>
</Tabs>
`TableHead` includes a `whitespace-nowrap` utility class to prevent its text from wrapping. `TableCell` must have any custom width specified, as there are legitimate use cases for both wrapping (or truncated) text.
## Usage
@@ -59,7 +31,7 @@ import {
<TableCaption>A list of your recent invoices.</TableCaption>
<TableHeader>
<TableRow>
<TableHead className="w-[100px]">Invoice</TableHead>
<TableHead>Invoice</TableHead>
<TableHead>Status</TableHead>
<TableHead>Method</TableHead>
<TableHead className="text-right">Amount</TableHead>
@@ -1,4 +1,5 @@
import {
Card,
Table,
TableBody,
TableCaption,
@@ -14,74 +15,87 @@ const invoices = [
invoice: 'INV001',
paymentStatus: 'Paid',
totalAmount: '$250.00',
paymentMethod: 'Credit Card',
paymentMethod: 'Credit card',
description: 'Website design services',
},
{
invoice: 'INV002',
paymentStatus: 'Pending',
totalAmount: '$150.00',
paymentMethod: 'PayPal',
description: 'Monthly subscription fee',
},
{
invoice: 'INV003',
paymentStatus: 'Unpaid',
totalAmount: '$350.00',
paymentMethod: 'Bank Transfer',
paymentMethod: 'Bank transfer',
description: 'Consulting hours',
},
{
invoice: 'INV004',
paymentStatus: 'Paid',
totalAmount: '$450.00',
paymentMethod: 'Credit Card',
paymentMethod: 'Credit card',
description: 'Software license renewal',
},
{
invoice: 'INV005',
paymentStatus: 'Paid',
totalAmount: '$550.00',
paymentMethod: 'PayPal',
description: 'Custom development work',
},
{
invoice: 'INV006',
paymentStatus: 'Pending',
totalAmount: '$200.00',
paymentMethod: 'Bank Transfer',
paymentMethod: 'Bank transfer',
description: 'Hosting and maintenance',
},
{
invoice: 'INV007',
paymentStatus: 'Unpaid',
totalAmount: '$300.00',
paymentMethod: 'Credit Card',
paymentMethod: 'Credit card',
description: 'Training session package',
},
]
export default function TableDemo() {
return (
<Table>
<TableCaption>A list of your recent invoices.</TableCaption>
<TableHeader>
<TableRow>
<TableHead className="w-[100px]">Invoice</TableHead>
<TableHead>Status</TableHead>
<TableHead>Method</TableHead>
<TableHead className="text-right">Amount</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{invoices.map((invoice) => (
<TableRow key={invoice.invoice}>
<TableCell className="font-medium">{invoice.invoice}</TableCell>
<TableCell>{invoice.paymentStatus}</TableCell>
<TableCell>{invoice.paymentMethod}</TableCell>
<TableCell className="text-right">{invoice.totalAmount}</TableCell>
<Card className="w-full pb-4">
<Table>
<TableCaption>A list of your recent invoices</TableCaption>
<TableHeader>
<TableRow>
<TableHead>Invoice</TableHead>
<TableHead>Status</TableHead>
<TableHead>Method</TableHead>
<TableHead className="hidden md:table-cell">Description</TableHead>
<TableHead className="text-right">Amount</TableHead>
</TableRow>
))}
</TableBody>
<TableFooter>
<TableRow>
<TableCell colSpan={3}>Total</TableCell>
<TableCell className="text-right">$2,500.00</TableCell>
</TableRow>
</TableFooter>
</Table>
</TableHeader>
<TableBody>
{invoices.map((invoice) => (
<TableRow key={invoice.invoice}>
<TableCell className="text-foreground">{invoice.invoice}</TableCell>
<TableCell className="text-foreground-lighter">{invoice.paymentStatus}</TableCell>
<TableCell className="text-foreground-lighter">{invoice.paymentMethod}</TableCell>
<TableCell className="hidden md:table-cell text-foreground-muted">
{invoice.description}
</TableCell>
<TableCell className="text-right">{invoice.totalAmount}</TableCell>
</TableRow>
))}
</TableBody>
<TableFooter className="border-t">
<TableRow>
<TableCell colSpan={4}>Total</TableCell>
<TableCell className="text-right">$2,250.00</TableCell>
</TableRow>
</TableFooter>
</Table>
</Card>
)
}
@@ -147,25 +147,27 @@ export const PublicationsList = () => {
{isSuccess &&
publications.map((x) => (
<TableRow key={x.name}>
<TableCell className="flex items-center gap-x-2 items-center">
{x.name}
{/* [Joshen] Making this tooltip very specific for these 2 publications */}
{['supabase_realtime', 'supabase_realtime_messages_publication'].includes(
x.name
) && (
<Tooltip>
<TooltipTrigger>
<Info size={14} className="text-foreground-light" />
</TooltipTrigger>
<TooltipContent side="bottom">
{x.name === 'supabase_realtime'
? 'This publication is managed by Supabase and handles Postgres changes'
: x.name === 'supabase_realtime_messages_publication'
? 'This publication is managed by Supabase and handles broadcasts from the database'
: undefined}
</TooltipContent>
</Tooltip>
)}
<TableCell>
<div className="flex items-center gap-x-2">
{x.name}
{/* [Joshen] Making this tooltip very specific for these 2 publications */}
{['supabase_realtime', 'supabase_realtime_messages_publication'].includes(
x.name
) && (
<Tooltip>
<TooltipTrigger>
<Info size={14} className="text-foreground-light" />
</TooltipTrigger>
<TooltipContent side="bottom">
{x.name === 'supabase_realtime'
? 'Managed by Supabase and handles Postgres changes'
: x.name === 'supabase_realtime_messages_publication'
? 'Managed by Supabase and handles broadcasts from the database'
: undefined}
</TooltipContent>
</Tooltip>
)}
</div>
</TableCell>
<TableCell>{x.id}</TableCell>
{publicationEvents.map((event) => (
@@ -78,8 +78,8 @@ export const PublicationsTableItem = ({
return (
<TableRow key={table.id}>
<TableCell className="py-3 whitespace-nowrap">{table.name}</TableCell>
<TableCell className="py-3 whitespace-nowrap">{table.schema}</TableCell>
<TableCell className="py-3 hidden max-w-sm truncate whitespace-nowrap lg:table-cell">
<TableCell className="py-3 whitespace-nowrap text-foreground-light">{table.schema}</TableCell>
<TableCell className="py-3 whitespace-nowrap hidden lg:table-cell max-w-sm truncate text-foreground-light">
{table.comment}
</TableCell>
<TableCell className="py-3">
@@ -102,7 +102,7 @@ export const PublicationsTables = () => {
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Schema</TableHead>
<TableHead>Description</TableHead>
<TableHead className="hidden lg:table-cell">Description</TableHead>
{/*
We've disabled All tables toggle for publications.
See https://github.com/supabase/supabase/pull/7233.
@@ -20,6 +20,7 @@ import { useRouter } from 'next/router'
import { useState } from 'react'
import { useParams } from 'common'
import { LOAD_TAB_FROM_CACHE_PARAM } from 'components/grid/SupabaseGrid.utils'
import AlertError from 'components/ui/AlertError'
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import { DropdownMenuItemTooltip } from 'components/ui/DropdownMenuItemTooltip'
@@ -63,7 +64,6 @@ import {
} from 'ui'
import { ProtectedSchemaWarning } from '../ProtectedSchemaWarning'
import { formatAllEntities } from './Tables.utils'
import { LOAD_TAB_FROM_CACHE_PARAM } from 'components/grid/SupabaseGrid.utils'
interface TableListProps {
onAddTable: () => void
@@ -325,7 +325,7 @@ export const TableList = ({
<TableHead key="size" className="hidden text-right xl:table-cell">
Size (Estimated)
</TableHead>
<TableHead key="realtime" className="hidden xl:table-cell text-center">
<TableHead key="realtime" className="hidden xl:table-cell text-right">
Realtime Enabled
</TableHead>
<TableHead key="buttons"></TableHead>
@@ -457,11 +457,11 @@ export const TableList = ({
{(realtimePublication?.tables ?? []).find(
(table) => table.id === x.id
) ? (
<div className="flex justify-center">
<div className="flex justify-end">
<Check size={18} strokeWidth={2} className="text-brand" />
</div>
) : (
<div className="flex justify-center">
<div className="flex justify-end">
<X size={18} strokeWidth={2} className="text-foreground-lighter" />
</div>
)}
@@ -37,7 +37,7 @@ const EdgeFunctionSecret = ({ secret, onSelectDelete }: EdgeFunctionSecretProps)
displayAs="utc"
utcTimestamp={secret.updated_at}
labelFormat="DD MMM YYYY HH:mm:ss (ZZ)"
className="!text-sm text-foreground-light"
className="!text-sm text-foreground-light whitespace-nowrap"
/>
) : (
'-'
@@ -37,9 +37,7 @@ export const EdgeFunctionsListItem = ({ function: item }: EdgeFunctionsListItemP
className="cursor-pointer"
>
<TableCell>
<div className="flex items-center gap-2">
<p className="text-sm text-foreground">{item.name}</p>
</div>
<p className="text-sm text-foreground whitespace-nowrap">{item.name}</p>
</TableCell>
<TableCell>
<div className="text-xs text-foreground-light flex gap-2 items-center truncate">
@@ -80,7 +78,7 @@ export const EdgeFunctionsListItem = ({ function: item }: EdgeFunctionsListItemP
</TableCell>
<TableCell className="lg:table-cell">
<Tooltip>
<TooltipTrigger>
<TooltipTrigger asChild>
<div className="flex items-center space-x-2">
<p className="text-sm text-foreground-light">{dayjs(item.updated_at).fromNow()}</p>
</div>
@@ -1,8 +1,8 @@
import { PostgresPolicy } from '@supabase/postgres-meta'
import { noop } from 'lodash'
import { Bucket } from 'data/storage/buckets-query'
import { PolicyRow } from 'components/interfaces/Auth/Policies/PolicyTableRow/PolicyRow'
import { Bucket } from 'data/storage/buckets-query'
import { Bucket as BucketIcon } from 'icons'
import {
@@ -40,11 +40,13 @@ export const StoragePoliciesBucketRow = ({
}: StoragePoliciesBucketRowProps) => {
return (
<Card>
<CardHeader className="flex flex-row w-full items-center justify-between gap-0 space-y-0">
<div className="flex items-center gap-3">
<CardHeader className="flex flex-row w-full items-center justify-between gap-2 space-y-0">
<div className="flex flex-1 min-w-0 items-center gap-3">
<BucketIcon className="text-foreground-muted" size={16} strokeWidth={1.5} />
<CardTitle>{label}</CardTitle>
{bucket?.public && <Badge variant="warning">Public</Badge>}
<div className="flex flex-1 min-w-0 items-center gap-1.5">
<CardTitle className="truncate">{label}</CardTitle>
{bucket?.public && <Badge variant="warning">Public</Badge>}
</div>
</div>
<Button type="outline" onClick={() => onSelectPolicyAdd(bucket?.name, table)}>
New policy
@@ -274,15 +274,9 @@ export const S3Connection = () => {
<Table>
<TableHeader>
<TableRow>
<TableHead key="description" className="min-w-40 lg:min-w-64">
Description
</TableHead>
<TableHead key="access-key-id" className="min-w-64 w-full">
Access key ID
</TableHead>
<TableHead key="created-at" className="min-w-48">
Created at
</TableHead>
<TableHead key="description">Name</TableHead>
<TableHead key="access-key-id">Key ID</TableHead>
<TableHead key="created-at">Created at</TableHead>
<TableHead key="actions" />
</TableRow>
</TableHeader>
@@ -53,7 +53,9 @@ export const StorageCredItem = ({
<TableCell>
<Input readOnly copy value={access_key} className="font-mono" />
</TableCell>
<TableCell className="text-foreground-lighter">{daysSince(created_at)}</TableCell>
<TableCell className="text-foreground-lighter whitespace-nowrap">
{daysSince(created_at)}
</TableCell>
<TableCell className="text-right">
{canRemoveAccessKey && (
<DropdownMenu>
@@ -4,7 +4,7 @@ import * as React from 'react'
import { cn } from '../../../lib/utils/cn'
const badgeVariants = cva(
'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs bg-opacity-10',
'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs bg-opacity-10 whitespace-nowrap',
{
variants: {
variant: {
@@ -68,7 +68,7 @@ const TableHead = React.forwardRef<
<th
ref={ref}
className={cn(
'h-10 px-4 text-left align-middle heading-meta text-foreground-lighter [&:has([role=checkbox])]:pr-0',
'h-10 px-4 text-left align-middle heading-meta whitespace-nowrap text-foreground-lighter [&:has([role=checkbox])]:pr-0',
className
)}
{...props}