From 3461d60abae9fe87a29ae45fee65e9f1fb2f3bc3 Mon Sep 17 00:00:00 2001 From: Charis <26616127+charislam@users.noreply.github.com> Date: Fri, 10 Jul 2026 10:27:43 -0400 Subject: [PATCH] feat(sql-editor): consolidate controls into a single top bar (#47792) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 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? Refactor / UI improvement ## What is the current behavior? SQL editor controls are split across two rows: a bar between the editor and results (tabs, download, run/save/format/db-selector/role/favorite/intellisense) and a separate footer at the very bottom (row count + limit dropdown). ## What is the new behavior? All controls are consolidated into a single top bar above the editor (⋮ overflow menu, database selector, role impersonation, limit, save, run). The bar between the editor and results now holds only result-relevant information: the Results/Explain/Chart tabs, the row-count summary, and the Export button. The limit dropdown is now always visible as a persistent query setting. --- .../interfaces/SQLEditor/SQLEditor.tsx | 95 +++---------------- .../SQLEditor/UtilityPanel/UtilityActions.tsx | 33 ++++++- .../SQLEditor/UtilityPanel/UtilityPanel.tsx | 54 +++++++---- 3 files changed, 82 insertions(+), 100 deletions(-) diff --git a/apps/studio/components/interfaces/SQLEditor/SQLEditor.tsx b/apps/studio/components/interfaces/SQLEditor/SQLEditor.tsx index 085cfdf1714..304389598bf 100644 --- a/apps/studio/components/interfaces/SQLEditor/SQLEditor.tsx +++ b/apps/studio/components/interfaces/SQLEditor/SQLEditor.tsx @@ -9,32 +9,17 @@ import { import { wrapWithRollback } from '@supabase/pg-meta/src/query' import { useQueryClient } from '@tanstack/react-query' import { IS_PLATFORM, LOCAL_STORAGE_KEYS, useFlag, useParams } from 'common' -import { ChevronUp, Loader2 } from 'lucide-react' +import { Loader2 } from 'lucide-react' import dynamic from 'next/dynamic' import { useRouter } from 'next/router' import { useCallback, useEffect, useEffectEvent, useMemo, useRef, useState } from 'react' import { toast } from 'sonner' -import { - Button, - cn, - DropdownMenu, - DropdownMenuContent, - DropdownMenuRadioGroup, - DropdownMenuRadioItem, - DropdownMenuTrigger, - ResizableHandle, - ResizablePanel, - ResizablePanelGroup, - Tooltip, - TooltipContent, - TooltipTrigger, -} from 'ui' +import { cn, ResizableHandle, ResizablePanel, ResizablePanelGroup } from 'ui' import { useSqlEditorDiff, useSqlEditorPrompt } from './hooks' import { RunQueryWarningModal } from './RunQueryWarningModal' import { generateSnippetTitle, - ROWS_PER_PAGE_OPTIONS, sqlAiDisclaimerComment, untitledSnippetTitle, } from './SQLEditor.constants' @@ -57,6 +42,7 @@ import { suffixWithLimit, } from './SQLEditor.utils' import { useAddDefinitions } from './useAddDefinitions' +import { UtilityActions } from './UtilityPanel/UtilityActions' import { UtilityPanel } from './UtilityPanel/UtilityPanel' import { isExplainQuery, @@ -65,7 +51,6 @@ import { } from '@/components/interfaces/ExplainVisualizer/ExplainVisualizer.utils' import { SIDEBAR_KEYS } from '@/components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider' import ResizableAIWidget from '@/components/ui/AIEditor/ResizableAIWidget' -import { GridFooter } from '@/components/ui/GridFooter' import { useSqlTitleGenerateMutation } from '@/data/ai/sql-title-mutation' import { useDatabaseEventTriggersQuery } from '@/data/database-event-triggers/database-event-triggers-query' import { constructHeaders, isValidConnString } from '@/data/fetchers' @@ -207,7 +192,6 @@ export const SQLEditor = () => { const id = !urlId || urlId === 'new' ? generatedId : urlId const limit = sessionSnap.limit - const results = sessionSnap.results[id]?.[0] const snippetIsLoading = !( id in snapV2.snippets && snapV2.snippets[id].snippet.content !== undefined ) @@ -916,9 +900,18 @@ export const SQLEditor = () => { }} /> -
+
+ @@ -1047,9 +1040,6 @@ export const SQLEditor = () => { isExecuting={isExecuting} isExplainExecuting={isExplainExecuting} isDisabled={isDiffOpen} - hasSelection={hasSelection} - prettifyQuery={prettifyQuery} - executeQuery={executeQueryFromButton} executeExplainQuery={executeExplainQuery} showExplainTab={!disablePrettyExplain} onDebug={onDebug} @@ -1059,63 +1049,6 @@ export const SQLEditor = () => { /> )} - -
- {results?.rows !== undefined && !isExecuting && ( - - - -

- - {results.rows.length} row{results.rows.length > 1 ? 's' : ''} - - - {results.autoLimit !== undefined && - ` (Limited to only ${results.autoLimit} rows)`} - -

-
- -

- - Results are automatically limited to preserve browser performance, in - particular if your query returns an exceptionally large number of rows. - - - - You may change or remove this limit from the dropdown on the right - -

-
-
- {results.autoLimit !== undefined && ( - - - - - - sessionSnap.setLimit(Number(val))} - > - {ROWS_PER_PAGE_OPTIONS.map((option) => ( - - {option.label} - - ))} - - - - )} -
- )} -
diff --git a/apps/studio/components/interfaces/SQLEditor/UtilityPanel/UtilityActions.tsx b/apps/studio/components/interfaces/SQLEditor/UtilityPanel/UtilityActions.tsx index 6dc0016f15c..463195e7e74 100644 --- a/apps/studio/components/interfaces/SQLEditor/UtilityPanel/UtilityActions.tsx +++ b/apps/studio/components/interfaces/SQLEditor/UtilityPanel/UtilityActions.tsx @@ -1,6 +1,6 @@ import { Hotkey } from '@tanstack/react-hotkeys' import { LOCAL_STORAGE_KEYS, useParams } from 'common' -import { AlignLeft, Check, Heart, Keyboard, MoreVertical } from 'lucide-react' +import { AlignLeft, Check, ChevronDown, Heart, Keyboard, MoreVertical } from 'lucide-react' import { toast } from 'sonner' import { Button, @@ -8,6 +8,8 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuTrigger, KeyboardShortcut, @@ -16,6 +18,7 @@ import { TooltipTrigger, } from 'ui' +import { ROWS_PER_PAGE_OPTIONS } from '../SQLEditor.constants' import { AutosaveStatus } from './AutosaveStatus' import { SqlRunButton } from './RunButton' import { SqlSaveButton } from './SaveButton' @@ -37,6 +40,7 @@ export type UtilityActionsProps = { hasSelection?: boolean prettifyQuery: () => void executeQuery: () => void + className?: string } export const UtilityActions = ({ @@ -46,6 +50,7 @@ export const UtilityActions = ({ hasSelection = false, prettifyQuery, executeQuery, + className, }: UtilityActionsProps) => { const { ref } = useParams() const snapV2 = useSqlEditorV2StateSnapshot() @@ -86,7 +91,7 @@ export const UtilityActions = ({ } return ( -
+
{/* SavingIndicator reports auto-save progress (spinner/checkmark). In manual mode AutosaveStatus + the Save button own the status, so hide it there. */} @@ -233,6 +238,30 @@ export const UtilityActions = ({ />
+ + + + + + sessionSnap.setLimit(Number(val))} + > + {ROWS_PER_PAGE_OPTIONS.map((option) => ( + + {option.label} + + ))} + + + +
{isManualSaveEnabled && } void - executeQuery: () => void executeExplainQuery: () => void showExplainTab?: boolean onDebug: () => void @@ -45,9 +49,6 @@ export const UtilityPanel = ({ isExplainExecuting, isDebugging, isDisabled, - hasSelection, - prettifyQuery, - executeQuery, executeExplainQuery, showExplainTab = true, onDebug, @@ -144,6 +145,34 @@ export const UtilityPanel = ({ Chart +
+ +
+ {result?.rows !== undefined && !isExecuting && ( + + +

+ + {result.rows.length} row{result.rows.length === 1 ? '' : 's'} + + + {result.autoLimit !== undefined && `(Limited to only ${result.autoLimit} rows)`} + +

+
+ +

+ + Results are automatically limited to preserve browser performance, in particular + if your query returns an exceptionally large number of rows. + + + You may change or remove this limit from the toolbar above. + +

+
+
+ )} {result?.rows && ( )}
- -