mirror of
https://github.com/supabase/supabase.git
synced 2026-07-22 23:47:37 -04:00
c6fc456910
## Problem Knip reports many duplicate exports (both named and default). Besides, we're moving away from default exports and even have an eslint rule to enforce it on new code. ## Solution - Cleanup those exports - Update imports when necessary No functional changes. If it builds, it's fine
129 lines
4.1 KiB
TypeScript
129 lines
4.1 KiB
TypeScript
import { useHotkeySequence, type HotkeySequence } from '@tanstack/react-hotkeys'
|
|
import { Fragment, useCallback, useMemo } from 'react'
|
|
import { KeyboardShortcut } from 'ui'
|
|
import { useRegisterCommands, useSetCommandMenuOpen } from 'ui-patterns/CommandMenu'
|
|
|
|
import { hotkeyToKeys } from './formatShortcut'
|
|
import type { ShortcutHotkeyMeta, ShortcutOptions } from './types'
|
|
import { orderShortcutCommands } from './utils'
|
|
import { COMMAND_MENU_SECTIONS } from '@/components/interfaces/App/CommandMenu/CommandMenu.utils'
|
|
import { useLatest } from '@/hooks/misc/useLatest'
|
|
|
|
/**
|
|
* Props shared by both the hook and the `<DynamicShortcut>` component.
|
|
*
|
|
* "Dynamic" means the shortcut isn't pre-declared in `SHORTCUT_DEFINITIONS` —
|
|
* the call site provides the `id`, `sequence`, and `label` at mount time.
|
|
* Use this when the shortcut list itself is derived from data (e.g. a tab list
|
|
* whose count and labels vary per page).
|
|
*
|
|
* Behavior matches `useShortcut` for the surfaces a dynamic shortcut can
|
|
* meaningfully participate in:
|
|
* - Reference sheet — picks these up via the `meta` payload while mounted.
|
|
* - Cmd+K command menu — opt-in via `registerInCommandMenu`, same pattern
|
|
* and ordering as registered shortcuts.
|
|
*
|
|
* What it does *not* support, because both are keyed on static `ShortcutId`s:
|
|
* - User-preference toggle in Account → Preferences.
|
|
* - `showInSettings` rendering in the keyboard shortcuts settings page.
|
|
*
|
|
* `id` must be unique among currently-mounted shortcuts. TanStack's hotkey
|
|
* lib warns by default when two registrations share a `sequence`, so prefer
|
|
* scoping IDs by surface (e.g. `integration-detail.tab-${index}`).
|
|
*/
|
|
export interface DynamicShortcutProps {
|
|
id: string
|
|
sequence: HotkeySequence
|
|
label: string
|
|
callback: () => void
|
|
enabled?: boolean
|
|
referenceGroup?: string
|
|
registerInCommandMenu?: boolean
|
|
ignoreInputs?: ShortcutOptions['ignoreInputs']
|
|
timeout?: ShortcutOptions['timeout']
|
|
conflictBehavior?: ShortcutOptions['conflictBehavior']
|
|
}
|
|
|
|
export function useDynamicShortcut({
|
|
id,
|
|
sequence,
|
|
label,
|
|
callback,
|
|
enabled = true,
|
|
referenceGroup,
|
|
registerInCommandMenu = false,
|
|
ignoreInputs,
|
|
timeout,
|
|
conflictBehavior,
|
|
}: DynamicShortcutProps) {
|
|
const meta = useMemo<ShortcutHotkeyMeta>(
|
|
() => ({ id, name: label, referenceGroup }),
|
|
[id, label, referenceGroup]
|
|
)
|
|
|
|
useHotkeySequence(sequence, callback, {
|
|
enabled,
|
|
timeout,
|
|
meta,
|
|
...(ignoreInputs !== undefined && { ignoreInputs }),
|
|
...(conflictBehavior !== undefined && { conflictBehavior }),
|
|
})
|
|
|
|
const enabledInCommandMenu = enabled && registerInCommandMenu
|
|
const callbackRef = useLatest(callback)
|
|
const setCommandMenuOpen = useSetCommandMenuOpen()
|
|
const stableAction = useCallback(() => {
|
|
setCommandMenuOpen(false)
|
|
callbackRef.current()
|
|
}, [callbackRef, setCommandMenuOpen])
|
|
|
|
useRegisterCommands(
|
|
COMMAND_MENU_SECTIONS.SHORTCUTS,
|
|
[
|
|
{
|
|
id,
|
|
name: label,
|
|
action: stableAction,
|
|
badge: () => (
|
|
<div className="flex items-center gap-1">
|
|
{sequence.map((step, i) => (
|
|
<Fragment key={i}>
|
|
{i > 0 && <span className="text-foreground-lighter text-[11px]">then</span>}
|
|
<KeyboardShortcut keys={hotkeyToKeys(step)} />
|
|
</Fragment>
|
|
))}
|
|
</div>
|
|
),
|
|
},
|
|
],
|
|
{
|
|
enabled: enabledInCommandMenu,
|
|
deps: [enabled, label, sequence.join('+')],
|
|
orderCommands: orderShortcutCommands,
|
|
sectionMeta: { priority: 1 },
|
|
}
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Renderable wrapper around `useDynamicShortcut`. Lets callers compose
|
|
* shortcuts inside a `.map()` over dynamic data without violating rules-of-hooks:
|
|
*
|
|
* ```tsx
|
|
* {tabs.map((tab, i) => (
|
|
* <DynamicShortcut
|
|
* key={tab.href}
|
|
* id={`integration-detail.tab-${i}`}
|
|
* sequence={[String(i + 1)]}
|
|
* label={`Go to ${tab.label}`}
|
|
* registerInCommandMenu
|
|
* callback={() => router.push(tab.href)}
|
|
* />
|
|
* ))}
|
|
* ```
|
|
*/
|
|
export function DynamicShortcut(props: DynamicShortcutProps) {
|
|
useDynamicShortcut(props)
|
|
return null
|
|
}
|