mirror of
https://github.com/supabase/supabase.git
synced 2026-09-06 09:59:03 +08:00
## Context Adds the prettify SQL CTA to Explorer Notebook and Query Tab Query tab: Prettify CTA is within the dropdown menu here <img width="1092" height="272" alt="image" src="https://github.com/user-attachments/assets/3b4f3514-69cc-4c24-a127-da1555ee905e" /> Query cell: Prettify CTA sits between the buttons in the toolbar <img width="1085" height="306" alt="image" src="https://github.com/user-attachments/assets/e503ad6c-5dda-4c2b-a824-8c1049d5e9fb" /> Also added shortcut tooltip for the run button <img width="183" height="103" alt="image" src="https://github.com/user-attachments/assets/729055b8-8e2c-4cf7-8f2e-c5726e828644" /> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added SQL prettification to Explorer query editors through the overflow menu, query toolbar, editor action, and keyboard shortcut. * Displays the configured formatting shortcut alongside the prettify action. * Prevents formatting while an AI proposal is pending. * Added a keyboard shortcut hint to the query run action. * Enhanced toolbar tooltips to support richer formatted content. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
182 lines
5.1 KiB
TypeScript
182 lines
5.1 KiB
TypeScript
import { Edit } from 'lucide-react'
|
|
import React, { useState } from 'react'
|
|
import { Button, cn } from 'ui'
|
|
import { Input } from 'ui-patterns/DataInputs/Input'
|
|
|
|
import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
|
|
|
|
export type ExplorerToolbarProps = React.ComponentProps<'div'>
|
|
|
|
/**
|
|
* Shared toolbar row for Explorer resources such as notebooks and queries.
|
|
* Defaults to 40px and follows `--header-height` from the `md` breakpoint up, so
|
|
* a consumer that defines its own header height gets toolbars matching it. The
|
|
* fallback keeps the row at its default height in apps that leave the variable
|
|
* unset, rather than dropping the rule.
|
|
*/
|
|
const ExplorerToolbar = ({ className, role = 'toolbar', ...props }: ExplorerToolbarProps) => (
|
|
<div
|
|
data-slot="explorer-toolbar"
|
|
role={role}
|
|
className={cn(
|
|
'flex h-10 w-full shrink-0 items-center gap-2 border-b bg-transparent px-3',
|
|
className
|
|
)}
|
|
{...props}
|
|
/>
|
|
)
|
|
ExplorerToolbar.displayName = 'ExplorerToolbar'
|
|
|
|
export type ExplorerToolbarIconProps = React.ComponentProps<'span'>
|
|
|
|
/** Decorative resource icon shown before the toolbar title. */
|
|
const ExplorerToolbarIcon = ({
|
|
'aria-hidden': ariaHidden = true,
|
|
className,
|
|
...props
|
|
}: ExplorerToolbarIconProps) => (
|
|
<span
|
|
data-slot="explorer-toolbar-icon"
|
|
aria-hidden={ariaHidden}
|
|
className={cn('shrink-0 text-tertiary-foreground', className)}
|
|
{...props}
|
|
/>
|
|
)
|
|
ExplorerToolbarIcon.displayName = 'ExplorerToolbarIcon'
|
|
|
|
export type ExplorerToolbarTitleProps = Omit<React.ComponentProps<'div'>, 'children'> & {
|
|
children: string
|
|
onSaveTitle?: (value: string) => void
|
|
}
|
|
|
|
/** Flexible title region for static text or an editable resource name. */
|
|
const ExplorerToolbarTitle = ({
|
|
children: title,
|
|
className,
|
|
onSaveTitle,
|
|
...props
|
|
}: ExplorerToolbarTitleProps) => {
|
|
const [isEditingTitle, setIsEditingTitle] = useState(false)
|
|
const [value, setValue] = useState(title)
|
|
|
|
const handleStartEditing = () => {
|
|
setValue(title)
|
|
setIsEditingTitle(true)
|
|
}
|
|
|
|
return (
|
|
<div
|
|
data-slot="explorer-toolbar-title"
|
|
className={cn('min-w-0 flex-1 truncate text-sm', className)}
|
|
{...props}
|
|
>
|
|
{isEditingTitle ? (
|
|
<Input
|
|
autoFocus
|
|
size="tiny"
|
|
containerClassName="max-w-64 has-[[data-slot=input-group-control]:focus-visible]:ring-0"
|
|
className="outline-none"
|
|
value={value}
|
|
onChange={(e) => setValue(e.target.value)}
|
|
onBlur={() => {
|
|
if (isEditingTitle) {
|
|
setIsEditingTitle(false)
|
|
onSaveTitle?.(value)
|
|
}
|
|
}}
|
|
onKeyDown={(e) => {
|
|
if (e.key === 'Escape') {
|
|
e.preventDefault()
|
|
e.stopPropagation()
|
|
setIsEditingTitle(false)
|
|
} else if (e.key === 'Enter') {
|
|
e.preventDefault()
|
|
e.stopPropagation()
|
|
setIsEditingTitle(false)
|
|
onSaveTitle?.(value)
|
|
}
|
|
}}
|
|
/>
|
|
) : onSaveTitle ? (
|
|
<ExplorerToolbarAction
|
|
className="group/title"
|
|
onClick={handleStartEditing}
|
|
iconRight={
|
|
<Edit
|
|
size={16}
|
|
strokeWidth={2}
|
|
className="opacity-0 transition group-hover/title:opacity-100 group-focus-visible/title:opacity-100"
|
|
/>
|
|
}
|
|
>
|
|
{title}
|
|
</ExplorerToolbarAction>
|
|
) : (
|
|
title
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
ExplorerToolbarTitle.displayName = 'ExplorerToolbarTitle'
|
|
|
|
export type ExplorerToolbarActionsProps = React.ComponentProps<'div'>
|
|
|
|
/**
|
|
* Trailing region for direct actions and composite controls such as badges,
|
|
* source menus, and display configuration.
|
|
*/
|
|
const ExplorerToolbarActions = ({ className, ...props }: ExplorerToolbarActionsProps) => (
|
|
<div
|
|
data-slot="explorer-toolbar-actions"
|
|
className={cn('ml-auto flex shrink-0 items-center gap-px', className)}
|
|
{...props}
|
|
/>
|
|
)
|
|
ExplorerToolbarActions.displayName = 'ExplorerToolbarActions'
|
|
|
|
export type ExplorerToolbarActionProps = Omit<
|
|
React.ComponentPropsWithRef<typeof Button>,
|
|
'size' | 'variant'
|
|
> & {
|
|
tooltip?: React.ReactNode
|
|
}
|
|
|
|
/**
|
|
* The standard tiny, text-style button used for a direct toolbar action.
|
|
* Icon-only actions receive the prototype's compact 28px width automatically.
|
|
* `!h-auto !w-auto` lets Lucide's `size` prop win over Button `tiny`'s 14px icon box.
|
|
*/
|
|
const ExplorerToolbarAction = ({
|
|
children,
|
|
className,
|
|
ref,
|
|
tooltip,
|
|
...props
|
|
}: ExplorerToolbarActionProps) => (
|
|
<ButtonTooltip
|
|
ref={ref}
|
|
data-slot="explorer-toolbar-action"
|
|
variant="text"
|
|
size="tiny"
|
|
className={cn(
|
|
'text-tertiary-foreground hover:text-foreground data-[state=open]:text-foreground',
|
|
'[&>[aria-hidden]]:text-current [&_svg]:!h-auto [&_svg]:!w-auto',
|
|
children == null && 'w-7 px-0',
|
|
className
|
|
)}
|
|
tooltip={{ content: { side: 'bottom', text: tooltip } }}
|
|
{...props}
|
|
>
|
|
{children}
|
|
</ButtonTooltip>
|
|
)
|
|
ExplorerToolbarAction.displayName = 'ExplorerToolbarAction'
|
|
|
|
export {
|
|
ExplorerToolbar,
|
|
ExplorerToolbarAction,
|
|
ExplorerToolbarActions,
|
|
ExplorerToolbarIcon,
|
|
ExplorerToolbarTitle,
|
|
}
|