mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
chore(studio): improve download snippet dialog (#46242)
## What kind of change does this PR introduce? Chore: component migration and copy update. ## What is the current behavior? `DownloadSnippetModal` and `RenameQueryModal` use an awkward tabs-based layout. The download action is labelled "Download" throughout, and the NPX toggle was incorrectly labelled "NPM". ## What is the new behavior? - Both modals are migrated to the `Dialog` component. - The download dialog replaces the tab layout with a select (Migration / Seed file / SQL file) and a CLI/NPX toggle. - Action language changed from "Download" to "Export" (context menu item, dialog title, select label) — more accurate since the user runs a CLI command rather than triggering a browser download. - NPM toggle label corrected to NPX; internal `npm` property key renamed to `npx` for consistency. | Before | After | | --- | --- | | <img width="1024" height="759" alt="Add Auth Hook (General) SQL Editor Pickles Pantry Supabase-C6F22F8B-19FF-486E-8C08-915895495875" src="https://github.com/user-attachments/assets/98637802-5e05-4431-87f8-b2e83216082a" /> | <img width="1024" height="759" alt="Add Auth Hook (General) SQL Editor Pickles Pantry Supabase-5B63AD0C-7CD0-4BB2-BEC8-5DBAE94963CE" src="https://github.com/user-attachments/assets/a8f48c2f-a7e2-42fd-b52c-89a133811ad8" /> | | <img width="1024" height="759" alt="Add Auth Hook (General) SQL Editor Pickles Pantry Supabase-1F28FADA-46D1-4C0A-BE96-6CFF2317FDCF" src="https://github.com/user-attachments/assets/afb904d0-689b-4756-b0b4-8177703934e4" /> | <img width="1024" height="759" alt="Add Auth Hook (General) SQL Editor Pickles Pantry Supabase-652E6E8D-1CD2-4465-BC82-46EDB974CD8C" src="https://github.com/user-attachments/assets/f1adfb6b-fba6-495f-b571-1713c3eebb4d" /> | <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Refactor** * Updated the SQL query export modal UI from a tabs-based interface to a dropdown-based selection for choosing export formats. * Revised menu and dialog labels to "Export query" for improved clarity. * Enhanced code block presentation in the export modal. <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/46242?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Gildas Garcia <1122076+djhi@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
3 files changed
+110
-75
No files matched your search
@@ -4,18 +4,22 @@ import Link from 'next/link'
|
||||
import { useState } from 'react'
|
||||
import {
|
||||
Button,
|
||||
cn,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogSection,
|
||||
DialogSectionSeparator,
|
||||
DialogTitle,
|
||||
Tabs,
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
type DialogProps,
|
||||
} from 'ui'
|
||||
import { CodeBlock } from 'ui-patterns/CodeBlock'
|
||||
|
||||
import { Markdown } from '../Markdown'
|
||||
import {
|
||||
generateFileCliCommand,
|
||||
generateMigrationCliCommand,
|
||||
@@ -25,108 +29,139 @@ import { TwoOptionToggle } from '@/components/ui/TwoOptionToggle'
|
||||
import { DOCS_URL } from '@/lib/constants'
|
||||
import { useSqlEditorV2StateSnapshot } from '@/state/sql-editor-v2'
|
||||
|
||||
const CLI_DOCS_URL = `${DOCS_URL}/guides/cli/local-development`
|
||||
|
||||
export interface DownloadSnippetModalProps extends DialogProps {
|
||||
id: string
|
||||
}
|
||||
|
||||
type DownloadFormat = 'migration' | 'seed' | 'sql'
|
||||
|
||||
export const DownloadSnippetModal = ({ id, ...props }: DownloadSnippetModalProps) => {
|
||||
const snapV2 = useSqlEditorV2StateSnapshot()
|
||||
|
||||
const snippet = snapV2.snippets[id]?.snippet
|
||||
const migrationName = snakeCase(snippet?.name)
|
||||
|
||||
const [selectedView, setSelectedView] = useState<'CLI' | 'NPM'>('CLI')
|
||||
const [downloadFormat, setDownloadFormat] = useState<DownloadFormat>('migration')
|
||||
const [selectedView, setSelectedView] = useState<'CLI' | 'NPX'>('CLI')
|
||||
|
||||
const SNIPPETS = [
|
||||
{
|
||||
id: 'migration',
|
||||
id: 'migration' as const,
|
||||
label: 'Migration',
|
||||
title: 'Download as migration',
|
||||
description: `Download the snippet in a new migration named \`${migrationName}\``,
|
||||
caption: (
|
||||
<>
|
||||
Run this command from your project directory to download the snippet in a new migration
|
||||
named <code className="text-code-inline break-normal">{migrationName}</code>.
|
||||
</>
|
||||
),
|
||||
docLink: {
|
||||
label: 'About migrations',
|
||||
href: `${DOCS_URL}/guides/deployment/database-migrations`,
|
||||
},
|
||||
cli: generateMigrationCliCommand(id, migrationName),
|
||||
npm: generateMigrationCliCommand(id, migrationName, true),
|
||||
npx: generateMigrationCliCommand(id, migrationName, true),
|
||||
},
|
||||
{
|
||||
id: 'seed',
|
||||
id: 'seed' as const,
|
||||
label: 'Seed file',
|
||||
title: 'Download as seed file',
|
||||
description:
|
||||
'If your query consists of sample data, append the snippet to the end of `supabase/seed.sql`',
|
||||
caption: (
|
||||
<>
|
||||
Run this command from your project directory to download the snippet. If your query
|
||||
consists of sample data, append it to the end of{' '}
|
||||
<code className="text-code-inline break-normal">supabase/seed.sql</code>.
|
||||
</>
|
||||
),
|
||||
docLink: {
|
||||
label: 'About seeding',
|
||||
href: `${DOCS_URL}/guides/local-development/seeding-your-database`,
|
||||
},
|
||||
cli: generateSeedCliCommand(id),
|
||||
npm: generateSeedCliCommand(id, true),
|
||||
npx: generateSeedCliCommand(id, true),
|
||||
},
|
||||
{
|
||||
id: 'sql',
|
||||
id: 'sql' as const,
|
||||
label: 'SQL file',
|
||||
title: 'Download as SQL file',
|
||||
description: `Download the snippet directly into a new SQL file named \`${migrationName}.sql\``,
|
||||
caption: (
|
||||
<>
|
||||
Run this command from your project directory to download the snippet into a new SQL file
|
||||
named <code className="text-code-inline break-normal">{migrationName}.sql</code>.
|
||||
</>
|
||||
),
|
||||
cli: generateFileCliCommand(id, migrationName),
|
||||
npm: generateFileCliCommand(id, migrationName, true),
|
||||
npx: generateFileCliCommand(id, migrationName, true),
|
||||
},
|
||||
]
|
||||
|
||||
const selectedSnippet = SNIPPETS.find((s) => s.id === downloadFormat) ?? SNIPPETS[0]
|
||||
const commandValue = selectedView === 'CLI' ? selectedSnippet.cli : selectedSnippet.npx
|
||||
|
||||
return (
|
||||
<Dialog {...props}>
|
||||
<DialogContent size="xlarge">
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Download snippet as local migration file via the Supabase CLI</DialogTitle>
|
||||
<DialogTitle>Export query</DialogTitle>
|
||||
</DialogHeader>
|
||||
<DialogSectionSeparator />
|
||||
<Tabs type="underlined" listClassNames="pl-5 pt-2">
|
||||
{SNIPPETS.map((snippet) => {
|
||||
return (
|
||||
<Tabs.Panel key={snippet.id} id={snippet.id} label={snippet.label}>
|
||||
<DialogSection>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="flex flex-col gap-y-1">
|
||||
<p className="text-base">{snippet.title}</p>
|
||||
<Markdown
|
||||
className="text-sm text-scale-1000 [&>p>code]:break-normal!"
|
||||
content={snippet.description}
|
||||
/>
|
||||
</div>
|
||||
<TwoOptionToggle
|
||||
width={50}
|
||||
options={['CLI', 'NPM']}
|
||||
activeOption={selectedView}
|
||||
borderOverride="border-muted"
|
||||
onClickOption={() =>
|
||||
selectedView === 'CLI' ? setSelectedView('NPM') : setSelectedView('CLI')
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<pre>
|
||||
<CodeBlock
|
||||
language="bash"
|
||||
className="language-bash prose dark:prose-dark max-w-none"
|
||||
>
|
||||
{selectedView === 'CLI' ? snippet.cli : snippet.npm}
|
||||
</CodeBlock>
|
||||
</pre>
|
||||
</DialogSection>
|
||||
</Tabs.Panel>
|
||||
)
|
||||
})}
|
||||
</Tabs>
|
||||
<DialogSection className="flex items-center justify-between">
|
||||
<p className="text-xs text-lighter">Run this command from your project directory</p>
|
||||
<div className="flex justify-between items-center gap-x-2">
|
||||
<Button asChild type="default" icon={<ExternalLink strokeWidth={1.5} />}>
|
||||
<Link
|
||||
href={`${DOCS_URL}/guides/deployment/database-migrations`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
About migrations
|
||||
</Link>
|
||||
</Button>
|
||||
<DialogSection className="flex flex-col gap-y-4">
|
||||
<div className="flex items-center justify-between gap-x-2">
|
||||
<p className="text-sm">Export as</p>
|
||||
<Select
|
||||
value={downloadFormat}
|
||||
onValueChange={(value) => setDownloadFormat(value as DownloadFormat)}
|
||||
>
|
||||
<SelectTrigger className="w-auto">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{SNIPPETS.map((snippet) => (
|
||||
<SelectItem key={snippet.id} value={snippet.id}>
|
||||
{snippet.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex flex-col gap-y-2">
|
||||
<div className="flex items-center justify-between gap-x-2">
|
||||
<p className="text-sm">Run with</p>
|
||||
<TwoOptionToggle
|
||||
width={50}
|
||||
options={['CLI', 'NPX']}
|
||||
activeOption={selectedView}
|
||||
borderOverride="border-muted"
|
||||
onClickOption={() =>
|
||||
selectedView === 'CLI' ? setSelectedView('NPX') : setSelectedView('CLI')
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<CodeBlock
|
||||
language="bash"
|
||||
value={commandValue}
|
||||
hideLineNumbers
|
||||
wrapperClassName="[&_pre]:px-4 [&_pre]:py-3"
|
||||
className={cn(
|
||||
'!bg-surface-75 border-border [&_code]:text-[12px] [&_code]:text-foreground'
|
||||
)}
|
||||
/>
|
||||
<p className="text-xs text-foreground-lighter leading-relaxed">
|
||||
{selectedSnippet.caption}
|
||||
</p>
|
||||
</div>
|
||||
</DialogSection>
|
||||
<DialogSection>
|
||||
<div className="flex items-center justify-start gap-x-2">
|
||||
{selectedSnippet.docLink && (
|
||||
<Button asChild type="default" icon={<ExternalLink />}>
|
||||
<Link href={selectedSnippet.docLink.href} target="_blank" rel="noreferrer">
|
||||
{selectedSnippet.docLink.label}
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Button asChild type="default" icon={<ExternalLink strokeWidth={1.5} />}>
|
||||
<Link
|
||||
href={`${DOCS_URL}/guides/cli/local-development`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<Button asChild type="default" icon={<ExternalLink />}>
|
||||
<Link href={CLI_DOCS_URL} target="_blank" rel="noreferrer">
|
||||
About CLI
|
||||
</Link>
|
||||
</Button>
|
||||
|
||||
+1
-1
@@ -411,7 +411,7 @@ export const SQLEditorTreeViewItem = ({
|
||||
onFocusCapture={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Download size={14} />
|
||||
Download as migration file
|
||||
Export query
|
||||
</ContextMenuItem>
|
||||
)}
|
||||
{onSelectDelete !== undefined && isOwner && (
|
||||
|
||||
@@ -786,11 +786,11 @@ hello world`)
|
||||
await expect(page.getByText('result found')).toBeVisible()
|
||||
await searchBar.fill('') // clear search bar
|
||||
|
||||
// download as migration file
|
||||
// export query as migration file
|
||||
await privateSnippetSection
|
||||
.getByTitle(sqlSnippetName, { exact: true })
|
||||
.click({ button: 'right' })
|
||||
await page.getByRole('menuitem', { name: 'Download as migration file' }).click()
|
||||
await page.getByRole('menuitem', { name: 'Export query' }).click()
|
||||
await expect(page.getByText('supabase migration new')).toBeVisible()
|
||||
await page.getByRole('button', { name: 'Close' }).click()
|
||||
|
||||
|
||||
Reference in new issue
Block a user