mirror of
https://github.com/supabase/supabase.git
synced 2026-10-11 12:25:05 +03:00
## What kind of change does this PR introduce? Chore / UI consistency fix. Resolves DEPR-418. ## What is the current behavior? Shortcut hints are still hand-built in several high-traffic Studio surfaces, which leads to inconsistent rendering and stale platform-specific markup. Buttons in particular can end up with awkward spacing and baseline alignment when shortcut labels are inserted directly into the button text. ## What is the new behavior? This PR standardises those shortcut hints around `KeyboardShortcut` and updates the surrounding layout primitives to support that approach more cleanly. It includes: - Design docs - using `KeyboardShortcut` in the table side-panel `ActionBar` - replacing hardcoded operation queue button shortcuts in `OperationQueueSidePanel` - standardising the command menu trigger shortcut chip and updating the `LayoutHeader` overrides to match the new DOM shape - replacing the AI editor empty-state `Cmd/Ctrl + K` hint with `KeyboardShortcut` - refining shared shortcut/button primitives so inline shortcuts align better when used as button accessories - keeping the SQL utility shortcut work on this branch consistent with the same shared component approach | Before | After | | --- | --- | | <img width="1454" height="902" alt="CleanShot 2026-03-27 at 15 55 32@2x" src="https://github.com/user-attachments/assets/3a8de192-3f4c-480b-9d26-9b28becd0ee3" /> | <img width="1488" height="906" alt="CleanShot 2026-03-27 at 15 29 31@2x-63A17C58-D023-4D3A-9355-6C40A6485328" src="https://github.com/user-attachments/assets/46ef7f7a-2b8b-4c10-8935-84ca5ad44562" /> | | <img width="738" height="328" alt="CleanShot 2026-03-27 at 15 57 07@2x" src="https://github.com/user-attachments/assets/ad459c41-867d-42f9-a8cb-c936af8326b7" /> | <img width="726" height="290" alt="CleanShot 2026-03-27 at 15 56 29@2x-ECE4E10F-9693-4ED8-B085-DC436A839F52" src="https://github.com/user-attachments/assets/95b4bfb4-ec34-4080-8b69-211b5045ca26" /> | ## Later todo - [ ] Replace the string-based SQL editor placeholder shortcut in `SQLEditor` once that placeholder API supports rich content - [ ] Refactor `CommandOption` to use `KeyboardShortcut` instead of bespoke platform detection and command-key markup - [ ] Standardise the remaining DataTable shortcut hints (`DataTableToolbar`, `DataTableResetButton`, `DataTableFilterCommand`, `DataTableFilterControlsDrawer`) around `KeyboardShortcut` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Introduced a new KeyboardShortcut component for displaying keyboard shortcuts with two visual variants (pill and inline). * Standardized keyboard shortcut indicators across the application interface for consistent user experience. * **Bug Fixes** * Fixed capitalization inconsistencies in button labels and hotkey settings. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
121 lines
3.6 KiB
TypeScript
121 lines
3.6 KiB
TypeScript
import { noop } from 'lodash'
|
|
import { PropsWithChildren, useCallback, useState } from 'react'
|
|
import { Button, KeyboardShortcut } from 'ui'
|
|
|
|
import { useHotKey } from '@/hooks/ui/useHotKey'
|
|
|
|
interface ActionBarProps {
|
|
loading?: boolean
|
|
disableApply?: boolean
|
|
hideApply?: boolean
|
|
applyButtonLabel?: string
|
|
backButtonLabel?: string
|
|
applyFunction?: (resolve: any) => void
|
|
closePanel: () => void
|
|
formId?: string
|
|
visible?: boolean
|
|
}
|
|
|
|
export const ActionBar = ({
|
|
loading = false,
|
|
disableApply = false,
|
|
hideApply = false,
|
|
children = undefined,
|
|
applyButtonLabel = 'Apply',
|
|
backButtonLabel = 'Back',
|
|
applyFunction = undefined,
|
|
closePanel = noop,
|
|
formId,
|
|
visible = true,
|
|
}: PropsWithChildren<ActionBarProps>) => {
|
|
const [isRunning, setIsRunning] = useState(false)
|
|
|
|
const onSelectApply = useCallback(async () => {
|
|
const applyCallback = () => new Promise((resolve) => applyFunction?.(resolve))
|
|
setIsRunning(true)
|
|
await applyCallback()
|
|
setIsRunning(false)
|
|
}, [applyFunction])
|
|
|
|
const handleSave = useCallback(
|
|
(event: KeyboardEvent) => {
|
|
// Don't trigger if already running/loading, or if apply is disabled/hidden
|
|
if (isRunning || loading || disableApply || hideApply) return
|
|
|
|
// Don't trigger if the user is in a textarea (allow multi-line entry)
|
|
// unless they explicitly press CMD+Enter
|
|
const activeElement = document.activeElement
|
|
const isTextarea = activeElement?.tagName === 'TEXTAREA'
|
|
|
|
// If in a textarea and this is just an Enter key (not CMD+Enter), don't submit
|
|
if (isTextarea && !event.metaKey && !event.ctrlKey) return
|
|
|
|
event.preventDefault()
|
|
event.stopPropagation()
|
|
|
|
if (formId) {
|
|
// Form-based submission - programmatically submit the form
|
|
const form = document.getElementById(formId) as HTMLFormElement | null
|
|
if (form) {
|
|
form.requestSubmit()
|
|
}
|
|
} else if (applyFunction) {
|
|
onSelectApply()
|
|
}
|
|
},
|
|
[isRunning, loading, disableApply, hideApply, formId, applyFunction, onSelectApply]
|
|
)
|
|
|
|
useHotKey(handleSave, 'Enter', { enabled: visible })
|
|
|
|
return (
|
|
<div className="flex w-full items-center gap-3 border-t border-default px-3 py-4">
|
|
{children}
|
|
|
|
<div className="flex items-center gap-3 ml-auto">
|
|
<Button
|
|
type="default"
|
|
htmlType="button"
|
|
onClick={closePanel}
|
|
disabled={isRunning || loading}
|
|
>
|
|
{backButtonLabel}
|
|
</Button>
|
|
|
|
{applyFunction !== undefined ? (
|
|
// Old solution, necessary when loading is handled by this component itself
|
|
<Button
|
|
onClick={onSelectApply}
|
|
disabled={disableApply || isRunning || loading}
|
|
loading={isRunning || loading}
|
|
iconRight={
|
|
isRunning || loading ? undefined : (
|
|
<KeyboardShortcut keys={['Meta', 'Enter']} variant="inline" />
|
|
)
|
|
}
|
|
>
|
|
{applyButtonLabel}
|
|
</Button>
|
|
) : !hideApply ? (
|
|
// New solution, when using the Form component, loading is handled by the Form itself
|
|
// Does not require applyFunction() callback
|
|
<Button
|
|
disabled={loading || disableApply}
|
|
loading={loading}
|
|
data-testid="action-bar-save-row"
|
|
htmlType="submit"
|
|
form={formId}
|
|
iconRight={
|
|
loading ? undefined : <KeyboardShortcut keys={['Meta', 'Enter']} variant="inline" />
|
|
}
|
|
>
|
|
{applyButtonLabel}
|
|
</Button>
|
|
) : (
|
|
<div />
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|