mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 01:45:10 +03:00
Five layout fixes across Explorer chat, notebooks, and the sidebar. ### Chat - **Conversation fade overlapped the scrollbar.** The top and bottom gradients are positioned against the conversation's padding box, which includes the scroll container's scrollbar gutter, so `inset-x-0` painted them over the scrollbar. They now stop at the conversation's content gutter, which `Conversation` owns for both the content and the fades. - **Composer background bled past the input's radius.** The form paints the surface behind the textarea but had no radius of its own, so its square corners showed outside the `rounded-lg` input. It now shares the radius. - **Message parts used two different widths.** Wide parts come down to `max-w-3xl` so every part shares a column, matching `AssistantQueryCell` and `AssistantNotebookPreview`. `isWide` / `isWideMessagePart` stay in place with both widths equal, so a part can diverge again later without rebuilding the mechanism. ### Notebooks - **Cell controls sat at the container edge.** Each cell centred itself at its own max width while the grip and add-cell button stayed at the far left of the full-width row, leaving a large gap. `SortableSection` takes a `sectionWidth` and carries its control gutter twice — once as the controls, once as padding on the other side — so the section stays centred with its controls immediately beside it. Cell widths are unchanged (prose `48rem`, query `72rem`); set them equal and the two cell types' controls line up on their own. The controls stay in flow rather than floating in an outside gutter, so on a viewport narrower than the cap the row just fills the space instead of clipping the controls into the padding. ### Sidebar - **Search icon didn't line up with the menu row icons.** The row box already sits flush with the search input's box, so rows moved from `pl-3` to `pl-2` to put their icons on the same 8px offset the search icon uses. Spacing between the input and the list now matches the 12px side padding. ### Testing `pnpm --filter studio run typecheck`, Prettier, and 378 tests across `Explorer`, `ProjectHome`, `AIAssistantPanel`, and `ExplorerLayout` pass. ESLint warning counts are unchanged from master. These were reasoned from layout rather than checked in a browser, so they're worth a look on a preview before merge. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **UI Improvements** * Updated Explorer layouts with flexible, configurable widths for notebook and query sections. * Refined navigation spacing and padding across Explorer views. * Centered and standardized AI Assistant preview, query, and message content widths. * Improved chat form styling with rounded corners. * Adjusted conversation spacing and fade overlays to avoid overlapping the scrollbar. * Preserved full-width behavior where appropriate while keeping controls aligned. * **Tests** * Updated layout tests to reflect revised width and alignment behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
144 lines
5.1 KiB
TypeScript
144 lines
5.1 KiB
TypeScript
import { useParams } from 'common'
|
|
import { NotebookText } from 'lucide-react'
|
|
import { useMemo, useState } from 'react'
|
|
import { Button, cn } from 'ui'
|
|
|
|
import {
|
|
formatNotebookDiffSummary,
|
|
getEntryKey,
|
|
notebookEntriesNeedDatabaseLookup,
|
|
summarizeNotebookDiff,
|
|
type NotebookDatabaseContext,
|
|
} from './AssistantNotebookPreview.utils'
|
|
import { AssistantNotebookPreviewCell } from './AssistantNotebookPreviewCell'
|
|
import {
|
|
ExplorerToolbar,
|
|
ExplorerToolbarActions,
|
|
ExplorerToolbarIcon,
|
|
ExplorerToolbarTitle,
|
|
} from '@/components/interfaces/Explorer/ExplorerToolbar'
|
|
import type { QueryResult } from '@/components/interfaces/Explorer/types'
|
|
import type { NotebookCellDiffEntry } from '@/data/content/notebooks/notebook-operations'
|
|
import { useReadReplicasQuery } from '@/data/read-replicas/replicas-query'
|
|
|
|
export interface AssistantNotebookPreviewProps {
|
|
entries: NotebookCellDiffEntry[]
|
|
mode: 'create' | 'update' | 'run'
|
|
/** The notebook's name, shown in the toolbar. Falls back to a generic label. */
|
|
title?: string
|
|
/** Query results keyed by persisted cell id. */
|
|
results?: Record<string, QueryResult>
|
|
className?: string
|
|
}
|
|
|
|
const VISIBLE_ENTRY_LIMIT = 5
|
|
|
|
const FALLBACK_TITLE = {
|
|
create: 'New notebook',
|
|
update: 'Notebook changes',
|
|
run: 'Notebook run',
|
|
} as const
|
|
|
|
/**
|
|
* Read-only minified notebook for assistant create/update proposals and notebook runs.
|
|
* Composes the same Explorer toolbar and query-result surfaces as notebook tabs; the
|
|
* surrounding `Confirm` card owns the frame. Pure presentational: no data fetching,
|
|
* approval, or notebook-editor state — callers adapt those concerns into entries/results.
|
|
*/
|
|
export const AssistantNotebookPreview = ({
|
|
entries,
|
|
mode,
|
|
title,
|
|
results,
|
|
className,
|
|
}: AssistantNotebookPreviewProps) => {
|
|
const { ref: projectRef } = useParams()
|
|
const [isShowingAllEntries, setIsShowingAllEntries] = useState(false)
|
|
const [expandedOverrides, setExpandedOverrides] = useState<Record<string, boolean>>({})
|
|
|
|
const needsDatabaseLookup = notebookEntriesNeedDatabaseLookup(entries, projectRef)
|
|
const {
|
|
data: databases,
|
|
isPending: isLoadingDatabases,
|
|
isError: isDatabaseError,
|
|
} = useReadReplicasQuery({ projectRef }, { enabled: needsDatabaseLookup })
|
|
const databasesByIdentifier = useMemo(
|
|
() => new Map((databases ?? []).map((database) => [database.identifier, database])),
|
|
[databases]
|
|
)
|
|
const databaseContext: NotebookDatabaseContext = !needsDatabaseLookup
|
|
? { status: 'success', projectRef, databasesByIdentifier }
|
|
: isLoadingDatabases
|
|
? { status: 'loading', projectRef }
|
|
: isDatabaseError
|
|
? { status: 'error', projectRef }
|
|
: { status: 'success', projectRef, databasesByIdentifier }
|
|
|
|
const summary = summarizeNotebookDiff(entries, mode)
|
|
const visibleEntries = isShowingAllEntries ? entries : entries.slice(0, VISIBLE_ENTRY_LIMIT)
|
|
const hiddenCount = entries.length - visibleEntries.length
|
|
const hasResults = results !== undefined
|
|
|
|
const isExpanded = (entry: NotebookCellDiffEntry) =>
|
|
expandedOverrides[getEntryKey(entry)] === true
|
|
|
|
return (
|
|
<div className={cn('flex w-full min-w-0 max-w-3xl mx-auto flex-col', className)}>
|
|
<ExplorerToolbar aria-label="Notebook toolbar">
|
|
<ExplorerToolbarIcon>
|
|
<NotebookText size={16} strokeWidth={2} />
|
|
</ExplorerToolbarIcon>
|
|
<ExplorerToolbarTitle>{title ?? FALLBACK_TITLE[mode]}</ExplorerToolbarTitle>
|
|
<ExplorerToolbarActions>
|
|
<span className="shrink-0 text-sm text-muted-foreground">
|
|
{formatNotebookDiffSummary(summary)}
|
|
</span>
|
|
</ExplorerToolbarActions>
|
|
</ExplorerToolbar>
|
|
<div className="p-2">
|
|
<div
|
|
className={cn(
|
|
hasResults ? 'flex flex-col gap-2' : 'overflow-hidden rounded-md border bg-surface-100'
|
|
)}
|
|
>
|
|
<div className={cn(hasResults ? 'contents' : 'divide-y divide-border')}>
|
|
{visibleEntries.map((entry) => {
|
|
const key = getEntryKey(entry)
|
|
return (
|
|
<div
|
|
key={key}
|
|
className={cn(hasResults && 'overflow-hidden rounded-md border bg-surface-100')}
|
|
>
|
|
<AssistantNotebookPreviewCell
|
|
entry={entry}
|
|
mode={mode}
|
|
result={results?.[key]}
|
|
isExpanded={isExpanded(entry)}
|
|
databaseContext={databaseContext}
|
|
onExpandedChange={(open) =>
|
|
setExpandedOverrides((prev) => ({ ...prev, [key]: open }))
|
|
}
|
|
/>
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
{hiddenCount > 0 && (
|
|
<Button
|
|
variant="text"
|
|
size="tiny"
|
|
className={cn(
|
|
'w-full text-foreground-light',
|
|
!hasResults && 'rounded-none border-0 border-t border-default'
|
|
)}
|
|
onClick={() => setIsShowingAllEntries(true)}
|
|
>
|
|
Show {hiddenCount} more cell{hiddenCount === 1 ? '' : 's'}
|
|
</Button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|