Files
supabase/apps/studio/components/ui/DataTable/DataTableSheetRowAction.tsx
kemal.earth b2e5476146 feat(studio): tidy up bottom tray in logs interface (#45371)
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## What kind of change does this PR introduce?

Tidying up the bottom panel in unified logs. Taking care of some visual
quirks etc. Also preparing this area to house some other future concepts
via tabs.

| Before | After |
|--------|--------|
| <img width="828" height="384" alt="Screenshot 2026-04-30 at 11 24 09"
src="https://github.com/user-attachments/assets/804bdf1c-7cdb-4dd8-bf1e-31c434ef1436"
/> | <img width="830" height="407" alt="Screenshot 2026-04-30 at 11 22
53"
src="https://github.com/user-attachments/assets/28555efe-f893-4bae-bcb0-284e6db733e6"
/> |




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

* **New Features**
  * Redesigned service flow panel with Overview and Raw JSON tabs
* Added Previous/Next navigation controls with Arrow Up/Down keyboard
support
* New detail components and section headers with icons for clearer
organization
  * Improved Postgres detail view and message/session display

* **Bug Fixes / Changes**
  * Removed legacy header UI and related controls

* **UI / Style**
  * Enhanced copy-to-clipboard feedback animation
  * Updated "Load more" button styling
  * Adjusted panel sizing for improved resizing behavior
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Ali Waseem <waseema393@gmail.com>
2026-05-06 16:32:31 +01:00

191 lines
5.8 KiB
TypeScript

import { Table } from '@tanstack/react-table'
import { endOfDay, endOfHour, startOfDay, startOfHour } from 'date-fns'
import {
CalendarClock,
CalendarDays,
CalendarSearch,
ChevronLeft,
ChevronRight,
Copy,
Equal,
Search,
} from 'lucide-react'
import { ComponentPropsWithRef } from 'react'
import {
cn,
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from 'ui'
import { DataTableFilterField } from '@/components/ui/DataTable/DataTable.types'
import { useCopyToClipboard } from '@/hooks/ui/useCopyToClipboard'
interface DataTableSheetRowActionProps<
TData,
TFields extends DataTableFilterField<TData>,
> extends ComponentPropsWithRef<typeof DropdownMenuTrigger> {
fieldValue: TFields['value']
filterFields: TFields[]
value: string | number
table: Table<TData>
}
export function DataTableSheetRowAction<TData, TFields extends DataTableFilterField<TData>>({
fieldValue,
filterFields,
value,
children,
className,
table,
onKeyDown,
...props
}: DataTableSheetRowActionProps<TData, TFields>) {
const { copy, isCopied } = useCopyToClipboard()
const field = filterFields.find((field) => field.value === fieldValue)
const column = table.getColumn(fieldValue.toString())
if (!field || !column) return null
function renderOptions() {
if (!field) return null
switch (field.type) {
case 'checkbox':
return (
<DropdownMenuItem
onClick={() => {
const filterValue = column?.getFilterValue() as undefined | Array<unknown>
const newValue = filterValue?.includes(value)
? filterValue
: [...(filterValue || []), value]
column?.setFilterValue(newValue)
}}
className="flex items-center gap-2"
>
<Search size={16} />
Include
</DropdownMenuItem>
)
case 'input':
return (
<DropdownMenuItem
onClick={() => column?.setFilterValue(value)}
className="flex items-center gap-2"
>
<Search size={16} />
Include
</DropdownMenuItem>
)
case 'slider':
return (
<DropdownMenuGroup>
<DropdownMenuItem
onClick={() => column?.setFilterValue([0, value])}
className="flex items-center gap-2"
>
{/* FIXME: change icon as it is not clear */}
<ChevronLeft size={16} />
Less or equal than
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => column?.setFilterValue([value, 5000])}
className="flex items-center gap-2"
>
{/* FIXME: change icon as it is not clear */}
<ChevronRight size={16} />
Greater or equal than
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => column?.setFilterValue([value])}
className="flex items-center gap-2"
>
<Equal size={16} />
Equal to
</DropdownMenuItem>
</DropdownMenuGroup>
)
case 'timerange':
const date = new Date(value)
return (
<DropdownMenuGroup>
<DropdownMenuItem
onClick={() => column?.setFilterValue([date])}
className="flex items-center gap-2"
>
<CalendarSearch size={16} />
Exact timestamp
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
const start = startOfHour(date)
const end = endOfHour(date)
column?.setFilterValue([start, end])
}}
className="flex items-center gap-2"
>
<CalendarClock size={16} />
Same hour
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
const start = startOfDay(date)
const end = endOfDay(date)
column?.setFilterValue([start, end])
}}
className="flex items-center gap-2"
>
<CalendarDays size={16} />
Same day
</DropdownMenuItem>
</DropdownMenuGroup>
)
default:
return null
}
}
return (
<DropdownMenu>
<DropdownMenuTrigger
className={cn(
'rounded-md ring-offset-background focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
'relative',
className
)}
onKeyDown={(e) => {
if (e.key === 'ArrowDown') {
// REMINDER: default behavior is to open the dropdown menu
// But because we use it to navigate between rows, we need to prevent it
// and only use "Enter" to select the option
e.preventDefault()
}
onKeyDown?.(e)
}}
{...props}
>
{children}
{isCopied ? (
<div className="absolute inset-0 flex items-center justify-center rounded-md bg-surface-100/80 backdrop-blur-sm animate-in fade-in duration-150">
<span className="font-mono text-xs text-foreground-light">Copied</span>
</div>
) : null}
</DropdownMenuTrigger>
<DropdownMenuContent align="end" side="bottom" className="w-40">
{renderOptions()}
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={() => copy(String(value), { timeout: 1000 })}
className="flex items-center gap-2"
>
<Copy size={16} />
Copy value
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}