Files
supabase/apps/studio/state/shortcuts/registry/table-editor.ts
Ali Waseem e7c33bf580 feat(studio): add insert, filter, sort, refresh shortcuts to the table editor (#45191)
## 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?

Feature — a second batch of table editor shortcuts, stacked on top of
#45178.

## What is the current behavior?

Inserts / filters / sort / refresh are all mouse-only. No keyboard
access, and no affordance for discovering what keybinds might exist.

## What is the new behavior?

### New shortcuts

| Keybind | Action | Surface |
|---|---|---|
| `I` then `R` | Insert row | hotkey + Cmd+K + inline keybind in Insert
dropdown |
| `I` then `C` | Insert column | hotkey + Cmd+K + inline keybind in
Insert dropdown |
| `I` then `U` | Import data from CSV | hotkey + Cmd+K + inline keybind
in Insert dropdown |
| `Shift+F` | Focus filters | hotkey + Cmd+K — focuses the new filter
bar's freeform input |
| `F` then `C` | Clear filters | hotkey + Cmd+K — gated on
`filters.length > 0` |
| `S` then `C` | Clear sort | hotkey + Cmd+K — gated on `sorts.length >
0` |
| `Shift+R` | Refresh table | hotkey + Cmd+K + hover tooltip on the
Refresh button |

All are `ignoreInputs: true` so they don't fire while typing. The insert
/ clear-filters / clear-sort shortcuts use two-step chords so they don't
clobber single-letter keys users might reach for elsewhere; Focus
filters and Refresh keep their Shift-prefixed single-step bindings.

### Infrastructure

- **New `<ShortcutBadge>`** (`components/ui/ShortcutBadge.tsx`) — inline
keybind display. Reads the sequence straight from the registry, so the
ID is the single source of truth. Renders multi-step chords with a
"then" separator between steps. Defaults to `variant="inline"` (the flat
`text-foreground/40` style used across the app in `RunButton`,
`ActionBar`, `OperationQueueSidePanel`, etc.) with `variant="pill"`
available if someone needs the boxed style.
- **Insert dropdown restyled** — each `DropdownMenuItem` in
`HeaderNew`'s Insert menu now shows its keybind inline on the right
(centered vertically, with `pr-4` + `shrink-0` so long table names in
the description never crowd the badge).
- **`RefreshButton`** swapped from `ButtonTooltip` to `<Shortcut>` so
the keybind tooltip renders automatically from the registry.
- **`FilterPopoverPrimitive` untouched** — the old filter bar is being
deleted, so Shift+F is scoped to the new filter bar only. The handler
focuses `[data-testid="filter-bar-freeform-input"]` (the existing
freeform input in the ui-patterns `FilterBar` → `FilterGroup`).

## Additional context

Stacked on #45178 (FE-3057 — initial table editor shortcuts). Rebase
after that one merges.

### Test plan

- [x] Open a table → Insert dropdown shows keybind to the right of each
item, no wrap encroachment even with long table names
- [x] `I` then `R` opens the Row editor; `I` then `C` opens the Column
editor; `I` then `U` opens the CSV import flow
- [x] `Shift+F` focuses the new filter bar's freeform input
- [x] Add a filter → `F` then `C` clears it; shortcut disabled in Cmd+K
when no filters are applied
- [x] Sort a column → `S` then `C` clears sort; shortcut disabled when
no sorts
- [x] `Shift+R` refreshes the table (spinner shows on the Refresh
button); hover the button → keybind tooltip
- [x] All seven new entries show up in Cmd+K when their gates are
satisfied

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added keyboard shortcuts for table actions: insert row, insert column,
import CSV, refresh, focus filters, clear filters, and clear sorts.
* Shortcuts are available in the command menu and show visual keyboard
hints.

* **UI**
  * Menu entries now display shortcut badges.
* Refined dropdown spacing/layout and updated the refresh control to
surface its shortcut.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-28 09:09:59 -06:00

155 lines
5.9 KiB
TypeScript

import { RegistryDefinations } from '../types'
export const TABLE_EDITOR_SHORTCUT_IDS = {
TABLE_EDITOR_JUMP_FIRST_ROW: 'table-editor.jump-first-row',
TABLE_EDITOR_JUMP_LAST_ROW: 'table-editor.jump-last-row',
TABLE_EDITOR_JUMP_FIRST_COL: 'table-editor.jump-first-col',
TABLE_EDITOR_JUMP_LAST_COL: 'table-editor.jump-last-col',
TABLE_EDITOR_TOGGLE_ROW_SELECTION: 'table-editor.toggle-row-selection',
TABLE_EDITOR_TOGGLE_ALL_ROW_SELECTION: 'table-editor.toggle-all-row-selection',
TABLE_EDITOR_SELECT_ALL_IN_TABLE: 'table-editor.select-all-in-table',
TABLE_EDITOR_DELETE_SELECTED_ROWS: 'table-editor.delete-selected-rows',
TABLE_EDITOR_START_NAVIGATION_DOWN: 'table-editor.start-navigation-down',
TABLE_EDITOR_START_NAVIGATION_UP: 'table-editor.start-navigation-up',
TABLE_EDITOR_EXIT_SELECTION: 'table-editor.exit-selection',
TABLE_EDITOR_INSERT_ROW: 'table-editor.insert-row',
TABLE_EDITOR_INSERT_COLUMN: 'table-editor.insert-column',
TABLE_EDITOR_IMPORT_CSV: 'table-editor.import-csv',
TABLE_EDITOR_FOCUS_FILTERS: 'table-editor.focus-filters',
TABLE_EDITOR_CLEAR_FILTERS: 'table-editor.clear-filters',
TABLE_EDITOR_CLEAR_SORT: 'table-editor.clear-sort',
TABLE_EDITOR_REFRESH: 'table-editor.refresh',
}
export type TableEditorShortcutId =
(typeof TABLE_EDITOR_SHORTCUT_IDS)[keyof typeof TABLE_EDITOR_SHORTCUT_IDS]
export const tableEditorRegistry: RegistryDefinations<TableEditorShortcutId> = {
[TABLE_EDITOR_SHORTCUT_IDS.TABLE_EDITOR_JUMP_FIRST_ROW]: {
id: TABLE_EDITOR_SHORTCUT_IDS.TABLE_EDITOR_JUMP_FIRST_ROW,
label: 'Jump to first row',
sequence: ['Mod+ArrowUp'],
showInSettings: false,
options: { ignoreInputs: true },
},
[TABLE_EDITOR_SHORTCUT_IDS.TABLE_EDITOR_JUMP_LAST_ROW]: {
id: TABLE_EDITOR_SHORTCUT_IDS.TABLE_EDITOR_JUMP_LAST_ROW,
label: 'Jump to last row',
sequence: ['Mod+ArrowDown'],
showInSettings: false,
options: { ignoreInputs: true },
},
[TABLE_EDITOR_SHORTCUT_IDS.TABLE_EDITOR_JUMP_FIRST_COL]: {
id: TABLE_EDITOR_SHORTCUT_IDS.TABLE_EDITOR_JUMP_FIRST_COL,
label: 'Jump to first column',
sequence: ['Mod+ArrowLeft'],
showInSettings: false,
options: { ignoreInputs: true },
},
[TABLE_EDITOR_SHORTCUT_IDS.TABLE_EDITOR_JUMP_LAST_COL]: {
id: TABLE_EDITOR_SHORTCUT_IDS.TABLE_EDITOR_JUMP_LAST_COL,
label: 'Jump to last column',
sequence: ['Mod+ArrowRight'],
showInSettings: false,
options: { ignoreInputs: true },
},
[TABLE_EDITOR_SHORTCUT_IDS.TABLE_EDITOR_TOGGLE_ROW_SELECTION]: {
id: TABLE_EDITOR_SHORTCUT_IDS.TABLE_EDITOR_TOGGLE_ROW_SELECTION,
label: 'Toggle selection on current row',
sequence: ['Shift+Space'],
showInSettings: false,
options: { ignoreInputs: true },
},
[TABLE_EDITOR_SHORTCUT_IDS.TABLE_EDITOR_TOGGLE_ALL_ROW_SELECTION]: {
id: TABLE_EDITOR_SHORTCUT_IDS.TABLE_EDITOR_TOGGLE_ALL_ROW_SELECTION,
label: 'Toggle selection on all displayed rows',
sequence: ['Mod+A'],
showInSettings: false,
options: { ignoreInputs: true },
},
[TABLE_EDITOR_SHORTCUT_IDS.TABLE_EDITOR_SELECT_ALL_IN_TABLE]: {
id: TABLE_EDITOR_SHORTCUT_IDS.TABLE_EDITOR_SELECT_ALL_IN_TABLE,
label: 'Toggle selection on all rows in table',
sequence: ['Mod+Shift+A'],
showInSettings: false,
options: { ignoreInputs: true },
},
[TABLE_EDITOR_SHORTCUT_IDS.TABLE_EDITOR_DELETE_SELECTED_ROWS]: {
id: TABLE_EDITOR_SHORTCUT_IDS.TABLE_EDITOR_DELETE_SELECTED_ROWS,
label: 'Delete selected rows',
sequence: ['Mod+Backspace'],
showInSettings: false,
options: { ignoreInputs: true },
},
[TABLE_EDITOR_SHORTCUT_IDS.TABLE_EDITOR_START_NAVIGATION_DOWN]: {
id: TABLE_EDITOR_SHORTCUT_IDS.TABLE_EDITOR_START_NAVIGATION_DOWN,
label: 'Start grid navigation (down)',
sequence: ['ArrowDown'],
showInSettings: false,
options: { ignoreInputs: true },
},
[TABLE_EDITOR_SHORTCUT_IDS.TABLE_EDITOR_START_NAVIGATION_UP]: {
id: TABLE_EDITOR_SHORTCUT_IDS.TABLE_EDITOR_START_NAVIGATION_UP,
label: 'Start grid navigation (up)',
sequence: ['ArrowUp'],
showInSettings: false,
options: { ignoreInputs: true },
},
[TABLE_EDITOR_SHORTCUT_IDS.TABLE_EDITOR_EXIT_SELECTION]: {
id: TABLE_EDITOR_SHORTCUT_IDS.TABLE_EDITOR_EXIT_SELECTION,
label: 'Exit grid selection',
sequence: ['Escape'],
showInSettings: false,
options: { ignoreInputs: true },
},
[TABLE_EDITOR_SHORTCUT_IDS.TABLE_EDITOR_INSERT_ROW]: {
id: TABLE_EDITOR_SHORTCUT_IDS.TABLE_EDITOR_INSERT_ROW,
label: 'Insert row',
sequence: ['I', 'R'],
showInSettings: false,
options: { ignoreInputs: true },
},
[TABLE_EDITOR_SHORTCUT_IDS.TABLE_EDITOR_INSERT_COLUMN]: {
id: TABLE_EDITOR_SHORTCUT_IDS.TABLE_EDITOR_INSERT_COLUMN,
label: 'Insert column',
sequence: ['I', 'C'],
showInSettings: false,
options: { ignoreInputs: true },
},
[TABLE_EDITOR_SHORTCUT_IDS.TABLE_EDITOR_IMPORT_CSV]: {
id: TABLE_EDITOR_SHORTCUT_IDS.TABLE_EDITOR_IMPORT_CSV,
label: 'Import data from CSV',
sequence: ['I', 'U'],
showInSettings: false,
options: { ignoreInputs: true },
},
[TABLE_EDITOR_SHORTCUT_IDS.TABLE_EDITOR_FOCUS_FILTERS]: {
id: TABLE_EDITOR_SHORTCUT_IDS.TABLE_EDITOR_FOCUS_FILTERS,
label: 'Focus filters',
sequence: ['Shift+F'],
showInSettings: false,
options: { ignoreInputs: true },
},
[TABLE_EDITOR_SHORTCUT_IDS.TABLE_EDITOR_CLEAR_FILTERS]: {
id: TABLE_EDITOR_SHORTCUT_IDS.TABLE_EDITOR_CLEAR_FILTERS,
label: 'Clear filters',
sequence: ['F', 'C'],
showInSettings: false,
options: { ignoreInputs: true },
},
[TABLE_EDITOR_SHORTCUT_IDS.TABLE_EDITOR_CLEAR_SORT]: {
id: TABLE_EDITOR_SHORTCUT_IDS.TABLE_EDITOR_CLEAR_SORT,
label: 'Clear sort',
sequence: ['S', 'C'],
showInSettings: false,
options: { ignoreInputs: true },
},
[TABLE_EDITOR_SHORTCUT_IDS.TABLE_EDITOR_REFRESH]: {
id: TABLE_EDITOR_SHORTCUT_IDS.TABLE_EDITOR_REFRESH,
label: 'Refresh table',
sequence: ['Shift+R'],
showInSettings: false,
options: { ignoreInputs: true },
},
}