From eddfb062f184bf24629af79d5d1021bf09080e26 Mon Sep 17 00:00:00 2001 From: Ali Waseem Date: Fri, 1 May 2026 08:43:53 -0600 Subject: [PATCH] added option to disable globally --- .../Preferences/DashboardSettingsToggles.tsx | 31 ++++++++++++++++++- .../Preferences/useDashboardSettings.ts | 16 ++++++++++ .../GlobalShortcuts/ShortcutChordHud.test.tsx | 31 ++++++++++++++++++- .../ui/GlobalShortcuts/ShortcutChordHud.tsx | 4 ++- e2e/studio/utils/test.ts | 1 + packages/common/constants/local-storage.ts | 2 ++ packages/common/telemetry-constants.ts | 19 ++++++++++++ 7 files changed, 101 insertions(+), 3 deletions(-) diff --git a/apps/studio/components/interfaces/Account/Preferences/DashboardSettingsToggles.tsx b/apps/studio/components/interfaces/Account/Preferences/DashboardSettingsToggles.tsx index 17df002eb0d..235bff98556 100644 --- a/apps/studio/components/interfaces/Account/Preferences/DashboardSettingsToggles.tsx +++ b/apps/studio/components/interfaces/Account/Preferences/DashboardSettingsToggles.tsx @@ -5,18 +5,24 @@ import { Card, Form } from 'ui' import * as z from 'zod' import { DashboardToggle } from './DashboardToggle' -import { useIsInlineEditorSetting, useIsQueueOperationsSetting } from './useDashboardSettings' +import { + useIsInlineEditorSetting, + useIsQueueOperationsSetting, + useIsShortcutChordHudSetting, +} from './useDashboardSettings' import { useSendEventMutation } from '@/data/telemetry/send-event-mutation' import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' const DashboardSettingsSchema = z.object({ inlineEditorEnabled: z.boolean(), queueOperationsEnabled: z.boolean(), + shortcutChordHudEnabled: z.boolean(), }) export const DashboardSettingsToggles = () => { const { inlineEditorEnabled, setInlineEditorEnabled } = useIsInlineEditorSetting() const { isQueueOperationsEnabled, setIsQueueOperationsEnabled } = useIsQueueOperationsSetting() + const { isShortcutChordHudEnabled, setIsShortcutChordHudEnabled } = useIsShortcutChordHudSetting() const { data: org } = useSelectedOrganizationQuery() @@ -27,6 +33,7 @@ export const DashboardSettingsToggles = () => { values: { inlineEditorEnabled: inlineEditorEnabled ?? false, queueOperationsEnabled: isQueueOperationsEnabled ?? false, + shortcutChordHudEnabled: isShortcutChordHudEnabled ?? true, }, }) @@ -60,6 +67,21 @@ export const DashboardSettingsToggles = () => { ) } + const handleShortcutChordHudToggle = (value: boolean) => { + setIsShortcutChordHudEnabled(value) + form.setValue('shortcutChordHudEnabled', value) + + sendEvent({ + action: 'shortcut_chord_hud_setting_clicked', + properties: { enabled: value }, + groups: { organization: org?.slug }, + }) + + toast( + `${value ? 'Keyboard shortcut hints will now appear while typing chords' : 'Keyboard shortcut hints are now hidden'}` + ) + } + return (
@@ -76,6 +98,13 @@ export const DashboardSettingsToggles = () => { label="Queue table operations" description="Review and batch table edits in Table Editor before saving them to your database." onToggle={handleQueueOperationsToggle} + /> + diff --git a/apps/studio/components/interfaces/Account/Preferences/useDashboardSettings.ts b/apps/studio/components/interfaces/Account/Preferences/useDashboardSettings.ts index 117bef445dd..ac585d92147 100644 --- a/apps/studio/components/interfaces/Account/Preferences/useDashboardSettings.ts +++ b/apps/studio/components/interfaces/Account/Preferences/useDashboardSettings.ts @@ -25,6 +25,17 @@ export const useIsQueueOperationsSetting = () => { } } +export const useIsShortcutChordHudSetting = () => { + const [isShortcutChordHudEnabled, setIsShortcutChordHudEnabled] = useLocalStorageQuery( + LOCAL_STORAGE_KEYS.UI_SHORTCUT_CHORD_HUD, + true + ) + return { + isShortcutChordHudEnabled: isShortcutChordHudEnabled ?? true, + setIsShortcutChordHudEnabled, + } +} + export const useIsInlineEditorEnabled = () => { const { inlineEditorEnabled } = useIsInlineEditorSetting() return inlineEditorEnabled ?? false @@ -34,3 +45,8 @@ export const useIsQueueOperationsEnabled = () => { const { isQueueOperationsEnabled } = useIsQueueOperationsSetting() return isQueueOperationsEnabled ?? false } + +export const useIsShortcutChordHudEnabled = () => { + const { isShortcutChordHudEnabled } = useIsShortcutChordHudSetting() + return isShortcutChordHudEnabled ?? true +} diff --git a/apps/studio/components/ui/GlobalShortcuts/ShortcutChordHud.test.tsx b/apps/studio/components/ui/GlobalShortcuts/ShortcutChordHud.test.tsx index dbd9fc467ce..2c9eb1ea88c 100644 --- a/apps/studio/components/ui/GlobalShortcuts/ShortcutChordHud.test.tsx +++ b/apps/studio/components/ui/GlobalShortcuts/ShortcutChordHud.test.tsx @@ -4,14 +4,19 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { ShortcutChordHud } from './ShortcutChordHud' import type { ShortcutSequenceRegistration } from './ShortcutChordHud.utils' -const { mockUseHotkeyRegistrations } = vi.hoisted(() => ({ +const { mockUseHotkeyRegistrations, mockUseIsShortcutChordHudEnabled } = vi.hoisted(() => ({ mockUseHotkeyRegistrations: vi.fn(), + mockUseIsShortcutChordHudEnabled: vi.fn(() => true), })) vi.mock('@tanstack/react-hotkeys', () => ({ useHotkeyRegistrations: mockUseHotkeyRegistrations, })) +vi.mock('@/components/interfaces/Account/Preferences/useDashboardSettings', () => ({ + useIsShortcutChordHudEnabled: mockUseIsShortcutChordHudEnabled, +})) + const makeSequence = ( overrides: Partial & Pick @@ -30,6 +35,7 @@ describe('ShortcutChordHud', () => { vi.useFakeTimers() vi.setSystemTime(new Date('2026-05-01T10:00:00.000Z')) mockUseHotkeyRegistrations.mockReturnValue({ hotkeys: [], sequences: [] }) + mockUseIsShortcutChordHudEnabled.mockReturnValue(true) }) it('stays hidden when no chord is in progress', () => { @@ -38,6 +44,29 @@ describe('ShortcutChordHud', () => { expect(screen.queryByTestId('shortcut-chord-hud')).not.toBeInTheDocument() }) + it('stays hidden when the user has disabled the chord HUD preference', () => { + mockUseIsShortcutChordHudEnabled.mockReturnValue(false) + const now = Date.now() + mockUseHotkeyRegistrations.mockReturnValue({ + hotkeys: [], + sequences: [ + makeSequence({ + id: 'nav.table', + sequence: ['G', 'T'], + matchedStepCount: 1, + partialMatchLastKeyTime: now - 200, + }), + ], + }) + + render() + act(() => { + vi.advanceTimersByTime(20) + }) + + expect(screen.queryByTestId('shortcut-chord-hud')).not.toBeInTheDocument() + }) + it('renders the active chord at the bottom centre while a sequence is in progress', () => { const now = Date.now() mockUseHotkeyRegistrations.mockReturnValue({ diff --git a/apps/studio/components/ui/GlobalShortcuts/ShortcutChordHud.tsx b/apps/studio/components/ui/GlobalShortcuts/ShortcutChordHud.tsx index fdf3bfa8c95..cd41231df10 100644 --- a/apps/studio/components/ui/GlobalShortcuts/ShortcutChordHud.tsx +++ b/apps/studio/components/ui/GlobalShortcuts/ShortcutChordHud.tsx @@ -3,6 +3,7 @@ import { useEffect, useMemo, useRef, useState } from 'react' import { cn, KeyboardShortcut } from 'ui' import { getVisibleShortcutChord, isInputLikeElement } from './ShortcutChordHud.utils' +import { useIsShortcutChordHudEnabled } from '@/components/interfaces/Account/Preferences/useDashboardSettings' import { useInterval } from '@/hooks/misc/useInterval' import { hotkeyToKeys } from '@/state/shortcuts/formatShortcut' @@ -23,6 +24,7 @@ interface RenderedShortcutChord { } export function ShortcutChordHud() { + const isEnabled = useIsShortcutChordHudEnabled() const { sequences } = useHotkeyRegistrations() const [, setTick] = useState(0) const [focusVersion, setFocusVersion] = useState(0) @@ -167,7 +169,7 @@ export function ShortcutChordHud() { const displayedHud = targetHud ?? exitingHud - if (!displayedHud) { + if (!displayedHud || !isEnabled) { return null } diff --git a/e2e/studio/utils/test.ts b/e2e/studio/utils/test.ts index 05263367897..40e2c14d89c 100644 --- a/e2e/studio/utils/test.ts +++ b/e2e/studio/utils/test.ts @@ -26,6 +26,7 @@ export const test = base.extend({ `table-editor-queue-operations-banner-dismissed-${ref}`, JSON.stringify(true) ) + localStorage.setItem('supabase-ui-shortcut-chord-hud', JSON.stringify(false)) }, ref) await use(page) }, diff --git a/packages/common/constants/local-storage.ts b/packages/common/constants/local-storage.ts index 17c58ad6ddf..1ce54ab0012 100644 --- a/packages/common/constants/local-storage.ts +++ b/packages/common/constants/local-storage.ts @@ -25,6 +25,7 @@ export const LOCAL_STORAGE_KEYS = { UI_PREVIEW_PLATFORM_WEBHOOKS: 'supabase-ui-platform-webhooks', UI_PREVIEW_JIT_DB_ACCESS: 'supabase-ui-jit-db-access', UI_PREVIEW_RLS_TESTER: 'supabase-ui-rls-tester', + UI_SHORTCUT_CHORD_HUD: 'supabase-ui-shortcut-chord-hud', NEW_LAYOUT_NOTICE_ACKNOWLEDGED: 'new-layout-notice-acknowledge', TABS_INTERFACE_ACKNOWLEDGED: 'tabs-interface-acknowledge', @@ -155,6 +156,7 @@ const LOCAL_STORAGE_KEYS_ALLOWLIST = [ LOCAL_STORAGE_KEYS.UI_PREVIEW_UNIFIED_LOGS, LOCAL_STORAGE_KEYS.UI_PREVIEW_PLATFORM_WEBHOOKS, LOCAL_STORAGE_KEYS.UI_PREVIEW_JIT_DB_ACCESS, + LOCAL_STORAGE_KEYS.UI_SHORTCUT_CHORD_HUD, LOCAL_STORAGE_KEYS.LAST_SIGN_IN_METHOD, LOCAL_STORAGE_KEYS.HIDE_PROMO_TOAST, LOCAL_STORAGE_KEYS.BLOG_VIEW, diff --git a/packages/common/telemetry-constants.ts b/packages/common/telemetry-constants.ts index 2dd383b9a83..ddcc0dd2833 100644 --- a/packages/common/telemetry-constants.ts +++ b/packages/common/telemetry-constants.ts @@ -2545,6 +2545,24 @@ export interface QueueOperationsSettingClickedEvent { groups: Partial } +/** + * User toggled the keyboard shortcut hint HUD setting in account preferences. + * + * @group Events + * @source studio + * @page /dashboard/account/preferences + */ +export interface ShortcutChordHudSettingClickedEvent { + action: 'shortcut_chord_hud_setting_clicked' + properties: { + /** + * Whether the keyboard shortcut hint HUD was enabled or disabled + */ + enabled: boolean + } + groups: Partial +} + /** * User clicked the save destination button in add log drains sheet. * @@ -3349,6 +3367,7 @@ export type TelemetryEvent = | CommandMenuCommandClickedEvent | InlineEditorSettingClickedEvent | QueueOperationsSettingClickedEvent + | ShortcutChordHudSettingClickedEvent | SidebarOpenedEvent | LogDrainSaveButtonClickedEvent | LogDrainConfirmButtonSubmittedEvent