Files
supabase/apps/studio/components/interfaces/Explorer/QueryCell/DisplaySettingsButton.tsx
Joshen Lim 93d9d80535 joshen/fe 4149 explorer support adding removing cells in notebook (#49037)
## Context

Related to Explorer/Notebooks - this PR adds the functionality to add /
remove cells

Separately - am thinking we can shift a lot of the "cell update" logic
into notebook-state, mainly so that each UI component doesn't need to be
aware of the notebook's `cells` but just its own cell.

I'll do it separately though to prevent bloating this PR, already left
comments where i think can be refactored

<img width="250" alt="image"
src="https://github.com/user-attachments/assets/91aae5f9-07b9-4327-8e21-8e9d71098aa6"
/>
<img width="250" alt="image"
src="https://github.com/user-attachments/assets/677de208-5c52-4155-90c6-bc0879070693"
/>
<img width="250" alt="image"
src="https://github.com/user-attachments/assets/17c3dae7-87bc-40bb-bfa0-15df101006ce"
/>
<img width="1091" height="262" alt="image"
src="https://github.com/user-attachments/assets/a09c3d23-696a-4b55-a1f8-4602e56a60dc"
/>



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

## New Features
- Add query and Markdown cells directly within notebooks.
- Move, remove, and reorder cells using drag-and-drop or cell controls.
- Add cells from empty states and notebook toolbar actions.
- Edit Markdown cells, mark edits as complete, and see placeholders for
empty content.

## Improvements
- Cell controls and hover interactions are more consistent and
responsive.
- Moving cells is disabled at the top or bottom of a notebook.
- Sample cells now use standardized content and formatting.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-14 00:17:29 +07:00

250 lines
8.3 KiB
TypeScript

import { BarChart2, Settings2, Table } from 'lucide-react'
import { useEffect, useEffectEvent, useMemo } from 'react'
import {
Checkbox,
Popover,
PopoverContent,
PopoverSeparator,
PopoverTrigger,
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
ToggleGroup,
ToggleGroupItem,
Tooltip,
TooltipContent,
TooltipTrigger,
} from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { ExplorerToolbarAction } from '../ExplorerToolbar'
import { type QueryChartConfig, type QueryDisplay, type QueryResult } from '../types'
import { checkHasNonPositiveValues } from '@/components/ui/QueryBlock/QueryBlock.utils'
interface DisplaySettingsButtonProps {
display: QueryDisplay
result?: QueryResult
columns: string[]
disabled: boolean
onChange: (display: QueryDisplay) => void
}
// [Joshen] TODO support multiple y axis charts
// [Joshen] TODO onUpdateChartConfig can likely be shifted into the notebook-state
// so this component doesn't need to know about other cells
export const DisplaySettingsButton = ({
display,
result,
columns,
disabled,
onChange,
}: DisplaySettingsButtonProps) => {
const { view, chart } = display
const {
type = 'bar',
x_column,
y_columns = [],
cumulative = false,
show_labels = false,
scale = 'linear',
} = chart ?? {}
const hasNonPositiveValues = useMemo(
() => checkHasNonPositiveValues(result?.rows ?? [], y_columns[0]),
[result, y_columns]
)
const canToggleLogScale = useMemo(() => {
if (y_columns.length === 0 || !result || (result.rows ?? []).length === 0) return false
return !hasNonPositiveValues
}, [hasNonPositiveValues, result, y_columns.length])
const onChangeView = (view: 'table' | 'chart') => {
onChange({ ...display, view })
}
const onUpdateChartConfig = (payload: Partial<QueryChartConfig>) => {
onChange({
...display,
chart: {
type: chart?.type ?? 'bar',
x_column: chart?.x_column ?? '',
y_columns: chart?.y_columns ?? [],
cumulative: chart?.cumulative ?? false,
scale: chart?.scale ?? 'linear',
show_labels: chart?.show_labels ?? false,
...payload,
},
})
}
const resetToLinearScale = useEffectEvent(() => {
onUpdateChartConfig({ scale: 'linear' })
})
useEffect(() => {
if (hasNonPositiveValues && scale === 'log') {
resetToLinearScale()
}
}, [hasNonPositiveValues, scale])
return (
<Popover>
<PopoverTrigger asChild>
<ExplorerToolbarAction disabled={disabled} icon={<Settings2 />} tooltip="Result settings" />
</PopoverTrigger>
<PopoverContent side="bottom" className="flex flex-col gap-y-3 p-0 py-3 mr-8">
<div className="flex flex-col gap-y-3 px-3">
<p className="text-xs tracking-tighter uppercase font-mono text-foreground-lighter">
Result display settings
</p>
<FormItemLayout isReactForm={false} label="View data as">
<ToggleGroup
value={view}
onValueChange={(value) => {
if (value === 'table' || value === 'chart') onChangeView(value)
}}
variant="outline"
type="single"
>
<ToggleGroupItem value="table" className="w-full gap-x-2 data-[state=on]:bg-accent">
<Table size={14} />
<p>Table</p>
</ToggleGroupItem>
<ToggleGroupItem value="chart" className="w-full gap-x-2 data-[state=on]:bg-accent">
<BarChart2 size={14} />
<p>Chart</p>
</ToggleGroupItem>
</ToggleGroup>
</FormItemLayout>
</div>
{view === 'chart' && (
<>
<PopoverSeparator />
<FormItemLayout isReactForm={false} label="Render chart as" className="px-3 mb-1">
<ToggleGroup
value={type}
onValueChange={(value) => {
if (value === 'bar' || value === 'line') onUpdateChartConfig({ type: value })
}}
variant="outline"
type="single"
>
<ToggleGroupItem value="bar" className="w-full data-[state=on]:bg-accent">
Bar
</ToggleGroupItem>
<ToggleGroupItem value="line" className="w-full data-[state=on]:bg-accent">
Line
</ToggleGroupItem>
</ToggleGroup>
</FormItemLayout>
<div className="px-3 flex flex-col gap-y-2">
<FormItemLayout isReactForm={false} layout="flex-row-reverse" label="X axis">
<Select
value={x_column}
onValueChange={(x_column) => onUpdateChartConfig({ x_column })}
>
<SelectTrigger className="w-32">
<SelectValue placeholder="Select a column" />
</SelectTrigger>
<SelectContent>
{columns.map((x) => (
<SelectItem key={x} value={x}>
{x}
</SelectItem>
))}
</SelectContent>
</Select>
</FormItemLayout>
<FormItemLayout isReactForm={false} layout="flex-row-reverse" label="Y Axis">
<Select
value={y_columns?.[0]}
onValueChange={(y_column) => onUpdateChartConfig({ y_columns: [y_column] })}
>
<SelectTrigger className="w-32">
<SelectValue placeholder="Select a column" />
</SelectTrigger>
<SelectContent>
{columns.map((x) => (
<SelectItem key={x} value={x}>
{x}
</SelectItem>
))}
</SelectContent>
</Select>
</FormItemLayout>
<FormItemLayout isReactForm={false} layout="flex-row-reverse" label="Scale">
<Select
value={scale}
onValueChange={(scale) =>
onUpdateChartConfig({ scale: scale as 'log' | 'linear' })
}
>
<SelectTrigger className="w-32">
<SelectValue placeholder="Select a column" />
</SelectTrigger>
<SelectContent>
<SelectItem value="linear">Linear</SelectItem>
<Tooltip>
<TooltipTrigger asChild>
<SelectItem
disabled={!canToggleLogScale}
value="log"
className={!canToggleLogScale ? '!pointer-events-auto' : undefined}
>
Logarithmic
</SelectItem>
</TooltipTrigger>
{!canToggleLogScale && (
<TooltipContent side="left">
{y_columns.length === 0
? 'Select a column for the Y axis first'
: 'Data contains zero or negative values'}
</TooltipContent>
)}
</Tooltip>
</SelectContent>
</Select>
</FormItemLayout>
<FormItemLayout id="cumulative" isReactForm={false} layout="flex" label="Cumulative">
<Checkbox
id="cumulative"
checked={cumulative}
onCheckedChange={(cumulative) =>
onUpdateChartConfig({ cumulative: Boolean(cumulative) })
}
/>
</FormItemLayout>
<FormItemLayout
id="show_labels"
isReactForm={false}
layout="flex"
label="Show labels"
>
<Checkbox
id="show_labels"
checked={show_labels}
onCheckedChange={(show_labels) =>
onUpdateChartConfig({ show_labels: Boolean(show_labels) })
}
/>
</FormItemLayout>
</div>
</>
)}
</PopoverContent>
</Popover>
)
}