mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
feat(studio): add navigation shortcuts with hover tooltips (#45127)
## Summary - Adds `<Shortcut>` component: binds a registered shortcut + renders a Linear-style hover tooltip (label + keybind pills) in one declaration - Adds `<ShortcutTooltip>` as the lower-level primitive for cases where binding lives elsewhere - Registers 13 G-chord navigation shortcuts (`G H` / `G T` / `G S` / `G D` / `G A` / `G B` / `G F` / `G R` / `G V` / `G O` / `G L` / `G I` / `G ,`) - Wires shortcuts into `SideBarNavLink` — binding + dynamic-delay tooltip (0ms collapsed, 1000ms expanded) replaces the old label-only collapsed tooltip Closes [FE-3048](https://linear.app/supabase/issue/FE-3048/create-navigation-shortcuts). ## Test plan - [x] Hover each main sidebar nav item with sidebar expanded — tooltip appears after delay with label + shortcut pills - [x] Collapse sidebar — tooltip appears instantly on hover - [x] Press `G` then a registered letter anywhere outside text inputs — navigates to that screen - [x] Press `G` inside a text input — no navigation fires - [x] Disabled nav items (project inactive) — no shortcut fires, no tooltip anomaly - [x] Feature-gated routes (auth/storage/realtime off) — shortcut is gone with the route <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Global keyboard shortcuts added for navigating major app sections (home, editors, database, auth, storage, functions, realtime, advisors, observability, logs, integrations, settings). * Navigation items display their keybinds in hover tooltips and can be triggered via those shortcuts. * Tooltip timing adapts to sidebar state (immediate when collapsed, delayed when expanded). * Shortcut-aware tooltip display now shows multi-step key sequences clearly. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
1 parent
fca7c86aec
commit
28a2c25e0e
7 files changed
+339
-11
No files matched your search
@@ -27,6 +27,7 @@ import {
|
||||
useSidebar,
|
||||
} from 'ui'
|
||||
|
||||
import { Shortcut } from '../ui/Shortcut'
|
||||
import { Route } from '../ui/ui.types'
|
||||
import {
|
||||
useIsPlatformWebhooksEnabled,
|
||||
@@ -47,6 +48,7 @@ import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage'
|
||||
import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
|
||||
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
|
||||
import { useAppStateSnapshot } from '@/state/app-state'
|
||||
import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
|
||||
|
||||
export const ICON_SIZE = 32
|
||||
export const ICON_STROKE_WIDTH = 1.5
|
||||
@@ -167,14 +169,23 @@ export function SideBarNavLink({
|
||||
active?: boolean
|
||||
onClick?: () => void
|
||||
} & ComponentPropsWithoutRef<typeof SidebarMenuButton>) {
|
||||
const router = useRouter()
|
||||
const { state: sidebarState } = useSidebar()
|
||||
const [sidebarBehaviour] = useLocalStorageQuery(
|
||||
LOCAL_STORAGE_KEYS.SIDEBAR_BEHAVIOR,
|
||||
DEFAULT_SIDEBAR_BEHAVIOR
|
||||
)
|
||||
|
||||
const isActiveLink = !!(route.link && !route.disabled)
|
||||
const hasShortcut = !!(route.shortcutId && isActiveLink)
|
||||
|
||||
// Collapsed: show immediately (replaces the old label-only tooltip
|
||||
// that used to surface the name of an icon-only item). Expanded:
|
||||
// slight delay so the tooltip doesn't flash while skimming the nav.
|
||||
const shortcutPopoverDelay = sidebarState === 'collapsed' ? 0 : 1000
|
||||
|
||||
const buttonProps = {
|
||||
disabled: route.disabled,
|
||||
tooltip: sidebarBehaviour === 'closed' ? route.label : '',
|
||||
isActive: active,
|
||||
className: cn('text-sm', sidebarBehaviour === 'open' ? '!px-2' : ''),
|
||||
size: 'default' as const,
|
||||
@@ -190,14 +201,27 @@ export function SideBarNavLink({
|
||||
</>
|
||||
)
|
||||
|
||||
const button = isActiveLink ? (
|
||||
<SidebarMenuButton {...buttonProps} asChild>
|
||||
<Link href={route.link!}>{content}</Link>
|
||||
</SidebarMenuButton>
|
||||
) : (
|
||||
<SidebarMenuButton {...buttonProps}>{content}</SidebarMenuButton>
|
||||
)
|
||||
|
||||
return (
|
||||
<SidebarMenuItem>
|
||||
{route.link && !route.disabled ? (
|
||||
<SidebarMenuButton {...buttonProps} asChild>
|
||||
<Link href={route.link}>{content}</Link>
|
||||
</SidebarMenuButton>
|
||||
{hasShortcut ? (
|
||||
<Shortcut
|
||||
id={route.shortcutId!}
|
||||
onTrigger={() => router.push(route.link!)}
|
||||
side="right"
|
||||
delayDuration={shortcutPopoverDelay}
|
||||
>
|
||||
{button}
|
||||
</Shortcut>
|
||||
) : (
|
||||
<SidebarMenuButton {...buttonProps}>{content}</SidebarMenuButton>
|
||||
button
|
||||
)}
|
||||
</SidebarMenuItem>
|
||||
)
|
||||
@@ -218,13 +242,9 @@ const ProjectLinks = () => {
|
||||
const router = useRouter()
|
||||
const { ref } = useParams()
|
||||
const { data: project } = useSelectedProjectQuery()
|
||||
const { data: org } = useSelectedOrganizationQuery()
|
||||
const snap = useAppStateSnapshot()
|
||||
const { securityLints, errorLints } = useLints()
|
||||
const showReports = useIsFeatureEnabled('reports:all')
|
||||
const { mutate: sendEvent } = useSendEventMutation()
|
||||
|
||||
const platformWebhooksEnabled = useIsPlatformWebhooksEnabled()
|
||||
const { isEnabled: isUnifiedLogsEnabled } = useUnifiedLogsPreview()
|
||||
|
||||
const activeRoute = router.pathname.split('/')[3]
|
||||
@@ -269,6 +289,7 @@ const ProjectLinks = () => {
|
||||
icon: <Home size={ICON_SIZE} strokeWidth={ICON_STROKE_WIDTH} />,
|
||||
link: `/project/${ref}`,
|
||||
linkElement: <ProjectIndexPageLink projectRef={ref} />,
|
||||
shortcutId: SHORTCUT_IDS.NAV_HOME,
|
||||
}}
|
||||
/>
|
||||
{toolRoutes.map((route, i) => (
|
||||
@@ -291,7 +312,7 @@ const ProjectLinks = () => {
|
||||
</SidebarGroup>
|
||||
<Separator className="w-[calc(100%-1rem)] mx-auto" />
|
||||
<SidebarGroup className="gap-0.5">
|
||||
{otherRoutes.map((route, i) => {
|
||||
{otherRoutes.map((route) => {
|
||||
if (route.key === 'advisors') {
|
||||
return (
|
||||
<div className="relative" key={route.key}>
|
||||
@@ -354,24 +375,28 @@ const OrganizationLinks = () => {
|
||||
href: `/org/${organizationSlug}`,
|
||||
key: 'projects',
|
||||
icon: <Boxes size={ICON_SIZE} strokeWidth={ICON_STROKE_WIDTH} />,
|
||||
shortcutId: SHORTCUT_IDS.NAV_ORG_PROJECTS,
|
||||
},
|
||||
{
|
||||
label: 'Team',
|
||||
href: `/org/${organizationSlug}/team`,
|
||||
key: 'team',
|
||||
icon: <Users size={ICON_SIZE} strokeWidth={ICON_STROKE_WIDTH} />,
|
||||
shortcutId: SHORTCUT_IDS.NAV_ORG_TEAM,
|
||||
},
|
||||
{
|
||||
label: 'Integrations',
|
||||
href: `/org/${organizationSlug}/integrations`,
|
||||
key: 'integrations',
|
||||
icon: <Blocks size={ICON_SIZE} strokeWidth={ICON_STROKE_WIDTH} />,
|
||||
shortcutId: SHORTCUT_IDS.NAV_ORG_INTEGRATIONS,
|
||||
},
|
||||
{
|
||||
label: 'Usage',
|
||||
href: `/org/${organizationSlug}/usage`,
|
||||
key: 'usage',
|
||||
icon: <ChartArea size={ICON_SIZE} strokeWidth={ICON_STROKE_WIDTH} />,
|
||||
shortcutId: SHORTCUT_IDS.NAV_ORG_USAGE,
|
||||
},
|
||||
...(showBilling
|
||||
? [
|
||||
@@ -380,6 +405,7 @@ const OrganizationLinks = () => {
|
||||
href: `/org/${organizationSlug}/billing`,
|
||||
key: 'billing',
|
||||
icon: <Receipt size={ICON_SIZE} strokeWidth={ICON_STROKE_WIDTH} />,
|
||||
shortcutId: SHORTCUT_IDS.NAV_ORG_BILLING,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
@@ -388,6 +414,7 @@ const OrganizationLinks = () => {
|
||||
href: `/org/${organizationSlug}/general`,
|
||||
key: 'settings',
|
||||
icon: <Settings size={ICON_SIZE} strokeWidth={ICON_STROKE_WIDTH} />,
|
||||
shortcutId: SHORTCUT_IDS.NAV_ORG_SETTINGS,
|
||||
},
|
||||
]
|
||||
|
||||
@@ -412,6 +439,7 @@ const OrganizationLinks = () => {
|
||||
key: item.label,
|
||||
icon: item.icon,
|
||||
disabled: disableAccessMfa,
|
||||
shortcutId: item.shortcutId,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { Route } from '@/components/ui/ui.types'
|
||||
import { EditorIndexPageLink } from '@/data/prefetchers/project.$ref.editor'
|
||||
import type { Project } from '@/data/projects/project-detail-query'
|
||||
import { IS_PLATFORM, PROJECT_STATUS } from '@/lib/constants'
|
||||
import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
|
||||
|
||||
interface RouteContext {
|
||||
ref?: string
|
||||
@@ -52,6 +53,7 @@ export const generateToolRoutes = (ref?: string, project?: Project): Route[] =>
|
||||
icon: <TableEditor size={ICON_SIZE} strokeWidth={ICON_STROKE_WIDTH} />,
|
||||
link: ref && (isProjectBuilding ? buildingUrl : `/project/${ref}/editor`),
|
||||
linkElement: <EditorIndexPageLink projectRef={ref} />,
|
||||
shortcutId: SHORTCUT_IDS.NAV_TABLE_EDITOR,
|
||||
},
|
||||
{
|
||||
key: 'sql',
|
||||
@@ -59,6 +61,7 @@ export const generateToolRoutes = (ref?: string, project?: Project): Route[] =>
|
||||
disabled: !isProjectActive,
|
||||
icon: <SqlEditor size={ICON_SIZE} strokeWidth={ICON_STROKE_WIDTH} />,
|
||||
link: ref && (isProjectBuilding ? buildingUrl : `/project/${ref}/sql`),
|
||||
shortcutId: SHORTCUT_IDS.NAV_SQL_EDITOR,
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -89,6 +92,7 @@ export const generateProductRoutes = (
|
||||
: isProjectActive
|
||||
? `/project/${ref}/database/schemas`
|
||||
: `/project/${ref}/database/backups/scheduled`),
|
||||
shortcutId: SHORTCUT_IDS.NAV_DATABASE,
|
||||
},
|
||||
...(authEnabled
|
||||
? [
|
||||
@@ -104,6 +108,7 @@ export const generateProductRoutes = (
|
||||
: authOverviewPageEnabled
|
||||
? `/project/${ref}/auth/overview`
|
||||
: `/project/${ref}/auth/users`),
|
||||
shortcutId: SHORTCUT_IDS.NAV_AUTH,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
@@ -115,6 +120,7 @@ export const generateProductRoutes = (
|
||||
disabled: !isProjectActive,
|
||||
icon: <Storage size={ICON_SIZE} strokeWidth={ICON_STROKE_WIDTH} />,
|
||||
link: ref && (isProjectBuilding ? buildingUrl : `/project/${ref}/storage/files`),
|
||||
shortcutId: SHORTCUT_IDS.NAV_STORAGE,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
@@ -126,6 +132,7 @@ export const generateProductRoutes = (
|
||||
disabled: false,
|
||||
icon: <EdgeFunctions size={ICON_SIZE} strokeWidth={ICON_STROKE_WIDTH} />,
|
||||
link: ref && `/project/${ref}/functions`,
|
||||
shortcutId: SHORTCUT_IDS.NAV_FUNCTIONS,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
@@ -137,6 +144,7 @@ export const generateProductRoutes = (
|
||||
disabled: !isProjectActive,
|
||||
icon: <Realtime size={ICON_SIZE} strokeWidth={ICON_STROKE_WIDTH} />,
|
||||
link: ref && (isProjectBuilding ? buildingUrl : `/project/${ref}/realtime/inspector`),
|
||||
shortcutId: SHORTCUT_IDS.NAV_REALTIME,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
@@ -160,6 +168,7 @@ export const generateOtherRoutes = (
|
||||
disabled: !isProjectActive,
|
||||
icon: <Lightbulb size={ICON_SIZE} strokeWidth={ICON_STROKE_WIDTH} />,
|
||||
link: ref && (isProjectBuilding ? buildingUrl : `/project/${ref}/advisors/security`),
|
||||
shortcutId: SHORTCUT_IDS.NAV_ADVISORS,
|
||||
},
|
||||
// Observability is only available on the platform, not for self-hosted/CLI
|
||||
...(isPlatform && reportsEnabled
|
||||
@@ -170,6 +179,7 @@ export const generateOtherRoutes = (
|
||||
disabled: !isProjectActive,
|
||||
icon: <Telescope size={ICON_SIZE} strokeWidth={ICON_STROKE_WIDTH} />,
|
||||
link: ref && (isProjectBuilding ? buildingUrl : `/project/${ref}/observability`),
|
||||
shortcutId: SHORTCUT_IDS.NAV_OBSERVABILITY,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
@@ -179,6 +189,7 @@ export const generateOtherRoutes = (
|
||||
disabled: false,
|
||||
icon: <List size={ICON_SIZE} strokeWidth={ICON_STROKE_WIDTH} />,
|
||||
link: ref && (unifiedLogsEnabled ? `/project/${ref}/logs` : `/project/${ref}/logs/explorer`),
|
||||
shortcutId: SHORTCUT_IDS.NAV_LOGS,
|
||||
},
|
||||
{
|
||||
key: 'integrations',
|
||||
@@ -186,6 +197,7 @@ export const generateOtherRoutes = (
|
||||
disabled: !isProjectActive,
|
||||
icon: <Blocks size={ICON_SIZE} strokeWidth={ICON_STROKE_WIDTH} />,
|
||||
link: ref && (isProjectBuilding ? buildingUrl : `/project/${ref}/integrations`),
|
||||
shortcutId: SHORTCUT_IDS.NAV_INTEGRATIONS,
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -202,6 +214,7 @@ export const generateSettingsRoutes = (ref?: string, features?: SettingsFeatures
|
||||
ref &&
|
||||
(isPlatform ? `/project/${ref}/settings/general` : `/project/${ref}/settings/log-drains`),
|
||||
disabled: false,
|
||||
shortcutId: SHORTCUT_IDS.NAV_SETTINGS,
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import { TooltipContentProps } from '@ui/components/shadcn/ui/tooltip'
|
||||
import type { ReactNode } from 'react'
|
||||
|
||||
import { ShortcutTooltip } from './ShortcutTooltip'
|
||||
import type { ShortcutId } from '@/state/shortcuts/registry'
|
||||
import type { ShortcutOptions } from '@/state/shortcuts/types'
|
||||
import { useShortcut } from '@/state/shortcuts/useShortcut'
|
||||
|
||||
interface ShortcutProps {
|
||||
/** Registered shortcut id — drives both the hotkey binding and the tooltip. */
|
||||
id: ShortcutId
|
||||
/** Fires on the hotkey. Usually the same handler wired to the child's `onClick`. */
|
||||
onTrigger: () => void
|
||||
/** Element to bind the shortcut to and wrap in the tooltip. */
|
||||
children: ReactNode
|
||||
/** Per-mount overrides for the shortcut — see `ShortcutOptions`. */
|
||||
options?: ShortcutOptions
|
||||
side?: TooltipContentProps['side']
|
||||
align?: TooltipContentProps['align']
|
||||
sideOffset?: number
|
||||
delayDuration?: number
|
||||
/**
|
||||
* Override the label from the registry. Use when the wrapped element's
|
||||
* action is a narrower/contextual variant of the registered shortcut.
|
||||
*/
|
||||
label?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind a registered shortcut to an element AND show its keybind on hover,
|
||||
* Linear-style. Single source of truth: one `id` drives both the hotkey
|
||||
* listener and the tooltip, so they can't drift.
|
||||
*
|
||||
* The wrapped child stays fully interactive — Radix `asChild` passes clicks,
|
||||
* focus, and refs through untouched.
|
||||
*
|
||||
* @example
|
||||
* <Shortcut id={SHORTCUT_IDS.RESULTS_COPY_MARKDOWN} onTrigger={handleCopy}>
|
||||
* <Button onClick={handleCopy}>Copy</Button>
|
||||
* </Shortcut>
|
||||
*
|
||||
* @example
|
||||
* // Gate the hotkey on local state; tooltip still renders:
|
||||
* <Shortcut
|
||||
* id={SHORTCUT_IDS.ACTION_BAR_SAVE}
|
||||
* onTrigger={handleSave}
|
||||
* options={{ enabled: hasUnsavedChanges }}
|
||||
* >
|
||||
* <Button onClick={handleSave} disabled={!hasUnsavedChanges}>Save</Button>
|
||||
* </Shortcut>
|
||||
*/
|
||||
export const Shortcut = ({
|
||||
id,
|
||||
onTrigger,
|
||||
children,
|
||||
options,
|
||||
side,
|
||||
align,
|
||||
sideOffset,
|
||||
delayDuration,
|
||||
label,
|
||||
}: ShortcutProps) => {
|
||||
useShortcut(id, onTrigger, options)
|
||||
|
||||
return (
|
||||
<ShortcutTooltip
|
||||
shortcutId={id}
|
||||
side={side}
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
delayDuration={delayDuration}
|
||||
label={label}
|
||||
>
|
||||
{children}
|
||||
</ShortcutTooltip>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import { TooltipContentProps } from '@ui/components/shadcn/ui/tooltip'
|
||||
import { Fragment, type ReactNode } from 'react'
|
||||
import { KeyboardShortcut, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
|
||||
|
||||
import { hotkeyToKeys } from '@/state/shortcuts/formatShortcut'
|
||||
import { SHORTCUT_DEFINITIONS, type ShortcutId } from '@/state/shortcuts/registry'
|
||||
|
||||
interface ShortcutTooltipProps {
|
||||
shortcutId: ShortcutId
|
||||
children: ReactNode
|
||||
side?: TooltipContentProps['side']
|
||||
align?: TooltipContentProps['align']
|
||||
sideOffset?: number
|
||||
delayDuration?: number
|
||||
/**
|
||||
* Override the label from the registry. Use when the wrapped element's
|
||||
* action is a narrower/contextual variant of the registered shortcut.
|
||||
*/
|
||||
label?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps any element to show its bound keyboard shortcut on hover/focus, in the
|
||||
* style of Linear's shortcut tooltips: `"<label> <key> [then <key>]"`.
|
||||
*
|
||||
* Uses Radix's `asChild` trigger, so the wrapped element remains fully
|
||||
* interactive — clicks, focus, and event handlers pass through untouched.
|
||||
*
|
||||
* @example
|
||||
* <ShortcutTooltip shortcutId={SHORTCUT_IDS.RESULTS_COPY_MARKDOWN}>
|
||||
* <Button onClick={handleCopy}>Copy</Button>
|
||||
* </ShortcutTooltip>
|
||||
*/
|
||||
export const ShortcutTooltip = ({
|
||||
shortcutId,
|
||||
children,
|
||||
side,
|
||||
align,
|
||||
sideOffset,
|
||||
delayDuration,
|
||||
label: labelOverride,
|
||||
}: ShortcutTooltipProps) => {
|
||||
const def = SHORTCUT_DEFINITIONS[shortcutId]
|
||||
const label = labelOverride ?? def.label
|
||||
|
||||
return (
|
||||
<Tooltip delayDuration={delayDuration}>
|
||||
<TooltipTrigger asChild>{children}</TooltipTrigger>
|
||||
<TooltipContent
|
||||
side={side}
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<span>{label}</span>
|
||||
<span className="flex items-center gap-1">
|
||||
{def.sequence.map((step, i) => (
|
||||
<Fragment key={i}>
|
||||
{i > 0 && <span className="text-foreground-lighter text-[11px]">then</span>}
|
||||
<KeyboardShortcut keys={hotkeyToKeys(step)} />
|
||||
</Fragment>
|
||||
))}
|
||||
</span>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
import type { ShortcutId } from '@/state/shortcuts/registry'
|
||||
|
||||
export interface Route {
|
||||
key: string
|
||||
label: string
|
||||
@@ -8,4 +10,10 @@ export interface Route {
|
||||
disabled?: boolean
|
||||
linkElement?: ReactNode
|
||||
items?: any | Route[]
|
||||
/**
|
||||
* Binds a registered keyboard shortcut to this route when set. The sidebar
|
||||
* entry shows the keybind on hover and jumps to `link` when the shortcut
|
||||
* fires. Ignored if `link` is not set.
|
||||
*/
|
||||
shortcutId?: ShortcutId
|
||||
}
|
||||
@@ -29,6 +29,25 @@ export const SHORTCUT_IDS = {
|
||||
OPERATION_QUEUE_TOGGLE: 'operation-queue.toggle',
|
||||
OPERATION_QUEUE_UNDO: 'operation-queue.undo',
|
||||
UNIFIED_LOGS_RESET_FOCUS: 'unified-logs.reset-focus',
|
||||
NAV_HOME: 'nav.home',
|
||||
NAV_TABLE_EDITOR: 'nav.table-editor',
|
||||
NAV_SQL_EDITOR: 'nav.sql-editor',
|
||||
NAV_DATABASE: 'nav.database',
|
||||
NAV_AUTH: 'nav.auth',
|
||||
NAV_STORAGE: 'nav.storage',
|
||||
NAV_FUNCTIONS: 'nav.functions',
|
||||
NAV_REALTIME: 'nav.realtime',
|
||||
NAV_ADVISORS: 'nav.advisors',
|
||||
NAV_OBSERVABILITY: 'nav.observability',
|
||||
NAV_LOGS: 'nav.logs',
|
||||
NAV_INTEGRATIONS: 'nav.integrations',
|
||||
NAV_SETTINGS: 'nav.settings',
|
||||
NAV_ORG_PROJECTS: 'nav.org-projects',
|
||||
NAV_ORG_TEAM: 'nav.org-team',
|
||||
NAV_ORG_INTEGRATIONS: 'nav.org-integrations',
|
||||
NAV_ORG_USAGE: 'nav.org-usage',
|
||||
NAV_ORG_BILLING: 'nav.org-billing',
|
||||
NAV_ORG_SETTINGS: 'nav.org-settings',
|
||||
} as const
|
||||
|
||||
/**
|
||||
@@ -181,4 +200,118 @@ export const SHORTCUT_DEFINITIONS: Record<ShortcutId, ShortcutDefinition> = {
|
||||
sequence: ['Mod+.'],
|
||||
showInSettings: false,
|
||||
},
|
||||
[SHORTCUT_IDS.NAV_HOME]: {
|
||||
id: SHORTCUT_IDS.NAV_HOME,
|
||||
label: 'Go to Project Overview',
|
||||
sequence: ['G', 'H'],
|
||||
showInSettings: false,
|
||||
},
|
||||
[SHORTCUT_IDS.NAV_TABLE_EDITOR]: {
|
||||
id: SHORTCUT_IDS.NAV_TABLE_EDITOR,
|
||||
label: 'Go to Table Editor',
|
||||
sequence: ['G', 'T'],
|
||||
showInSettings: false,
|
||||
},
|
||||
[SHORTCUT_IDS.NAV_SQL_EDITOR]: {
|
||||
id: SHORTCUT_IDS.NAV_SQL_EDITOR,
|
||||
label: 'Go to SQL Editor',
|
||||
sequence: ['G', 'S'],
|
||||
showInSettings: false,
|
||||
},
|
||||
[SHORTCUT_IDS.NAV_DATABASE]: {
|
||||
id: SHORTCUT_IDS.NAV_DATABASE,
|
||||
label: 'Go to Database',
|
||||
sequence: ['G', 'D'],
|
||||
showInSettings: false,
|
||||
},
|
||||
[SHORTCUT_IDS.NAV_AUTH]: {
|
||||
id: SHORTCUT_IDS.NAV_AUTH,
|
||||
label: 'Go to Authentication',
|
||||
sequence: ['G', 'A'],
|
||||
showInSettings: false,
|
||||
},
|
||||
[SHORTCUT_IDS.NAV_STORAGE]: {
|
||||
id: SHORTCUT_IDS.NAV_STORAGE,
|
||||
label: 'Go to Storage',
|
||||
sequence: ['G', 'B'],
|
||||
showInSettings: false,
|
||||
},
|
||||
[SHORTCUT_IDS.NAV_FUNCTIONS]: {
|
||||
id: SHORTCUT_IDS.NAV_FUNCTIONS,
|
||||
label: 'Go to Edge Functions',
|
||||
sequence: ['G', 'F'],
|
||||
showInSettings: false,
|
||||
},
|
||||
[SHORTCUT_IDS.NAV_REALTIME]: {
|
||||
id: SHORTCUT_IDS.NAV_REALTIME,
|
||||
label: 'Go to Realtime',
|
||||
sequence: ['G', 'R'],
|
||||
showInSettings: false,
|
||||
},
|
||||
[SHORTCUT_IDS.NAV_ADVISORS]: {
|
||||
id: SHORTCUT_IDS.NAV_ADVISORS,
|
||||
label: 'Go to Advisors',
|
||||
sequence: ['G', 'V'],
|
||||
showInSettings: false,
|
||||
},
|
||||
[SHORTCUT_IDS.NAV_OBSERVABILITY]: {
|
||||
id: SHORTCUT_IDS.NAV_OBSERVABILITY,
|
||||
label: 'Go to Observability',
|
||||
sequence: ['G', 'U'],
|
||||
showInSettings: false,
|
||||
},
|
||||
[SHORTCUT_IDS.NAV_LOGS]: {
|
||||
id: SHORTCUT_IDS.NAV_LOGS,
|
||||
label: 'Go to Logs',
|
||||
sequence: ['G', 'L'],
|
||||
showInSettings: false,
|
||||
},
|
||||
[SHORTCUT_IDS.NAV_INTEGRATIONS]: {
|
||||
id: SHORTCUT_IDS.NAV_INTEGRATIONS,
|
||||
label: 'Go to Integrations',
|
||||
sequence: ['G', 'I'],
|
||||
showInSettings: false,
|
||||
},
|
||||
[SHORTCUT_IDS.NAV_SETTINGS]: {
|
||||
id: SHORTCUT_IDS.NAV_SETTINGS,
|
||||
label: 'Go to Project Settings',
|
||||
sequence: ['G', ','],
|
||||
showInSettings: false,
|
||||
},
|
||||
[SHORTCUT_IDS.NAV_ORG_PROJECTS]: {
|
||||
id: SHORTCUT_IDS.NAV_ORG_PROJECTS,
|
||||
label: 'Go to Projects',
|
||||
sequence: ['G', 'P'],
|
||||
showInSettings: false,
|
||||
},
|
||||
[SHORTCUT_IDS.NAV_ORG_TEAM]: {
|
||||
id: SHORTCUT_IDS.NAV_ORG_TEAM,
|
||||
label: 'Go to Team',
|
||||
sequence: ['G', 'M'],
|
||||
showInSettings: false,
|
||||
},
|
||||
[SHORTCUT_IDS.NAV_ORG_INTEGRATIONS]: {
|
||||
id: SHORTCUT_IDS.NAV_ORG_INTEGRATIONS,
|
||||
label: 'Go to Organization Integrations',
|
||||
sequence: ['G', 'I'],
|
||||
showInSettings: false,
|
||||
},
|
||||
[SHORTCUT_IDS.NAV_ORG_USAGE]: {
|
||||
id: SHORTCUT_IDS.NAV_ORG_USAGE,
|
||||
label: 'Go to Usage',
|
||||
sequence: ['G', 'U'],
|
||||
showInSettings: false,
|
||||
},
|
||||
[SHORTCUT_IDS.NAV_ORG_BILLING]: {
|
||||
id: SHORTCUT_IDS.NAV_ORG_BILLING,
|
||||
label: 'Go to Billing',
|
||||
sequence: ['G', 'B'],
|
||||
showInSettings: false,
|
||||
},
|
||||
[SHORTCUT_IDS.NAV_ORG_SETTINGS]: {
|
||||
id: SHORTCUT_IDS.NAV_ORG_SETTINGS,
|
||||
label: 'Go to Organization Settings',
|
||||
sequence: ['G', 'O'],
|
||||
showInSettings: false,
|
||||
},
|
||||
}
|
||||
@@ -40,4 +40,6 @@ const TooltipContent = React.forwardRef<
|
||||
))
|
||||
TooltipContent.displayName = TooltipPrimitive.Content.displayName
|
||||
|
||||
export type TooltipContentProps = React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
||||
|
||||
export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, TooltipPortal }
|
||||
Reference in new issue
Block a user