Dropdown manages hotkeys now

This commit is contained in:
Gregory Schier
2024-01-11 10:18:05 -08:00
parent dbaf1da3ce
commit bd5ae12f2e
12 changed files with 177 additions and 150 deletions

View File

@@ -4,7 +4,6 @@ import { useActiveEnvironment } from '../hooks/useActiveEnvironment';
import { useAppRoutes } from '../hooks/useAppRoutes'; import { useAppRoutes } from '../hooks/useAppRoutes';
import { useCreateEnvironment } from '../hooks/useCreateEnvironment'; import { useCreateEnvironment } from '../hooks/useCreateEnvironment';
import { useEnvironments } from '../hooks/useEnvironments'; import { useEnvironments } from '../hooks/useEnvironments';
import { useHotkey } from '../hooks/useHotkey';
import type { ButtonProps } from './core/Button'; import type { ButtonProps } from './core/Button';
import { Button } from './core/Button'; import { Button } from './core/Button';
import type { DropdownItem } from './core/Dropdown'; import type { DropdownItem } from './core/Dropdown';
@@ -35,8 +34,6 @@ export const EnvironmentActionsDropdown = memo(function EnvironmentActionsDropdo
}); });
}, [dialog, activeEnvironment]); }, [dialog, activeEnvironment]);
useHotkey('environmentEditor.toggle', showEnvironmentDialog, { enable: environments.length > 0 });
const items: DropdownItem[] = useMemo( const items: DropdownItem[] = useMemo(
() => [ () => [
...environments.map( ...environments.map(

View File

@@ -1,4 +1,4 @@
import { hotkeyActions } from '../hooks/useHotkey'; import { hotkeyActions } from '../hooks/useHotKey';
import { HotKeyList } from './core/HotKeyList'; import { HotKeyList } from './core/HotKeyList';
export const KeyboardShortcutsDialog = () => { export const KeyboardShortcutsDialog = () => {

View File

@@ -2,7 +2,6 @@ import { invoke, shell } from '@tauri-apps/api';
import { useRef } from 'react'; import { useRef } from 'react';
import { useAppVersion } from '../hooks/useAppVersion'; import { useAppVersion } from '../hooks/useAppVersion';
import { useExportData } from '../hooks/useExportData'; import { useExportData } from '../hooks/useExportData';
import { useHotkey } from '../hooks/useHotkey';
import { useImportData } from '../hooks/useImportData'; import { useImportData } from '../hooks/useImportData';
import { useTheme } from '../hooks/useTheme'; import { useTheme } from '../hooks/useTheme';
import { useUpdateMode } from '../hooks/useUpdateMode'; import { useUpdateMode } from '../hooks/useUpdateMode';
@@ -24,17 +23,6 @@ export function SettingsDropdown() {
const dropdownRef = useRef<DropdownRef>(null); const dropdownRef = useRef<DropdownRef>(null);
const dialog = useDialog(); const dialog = useDialog();
const showHotkeyHelp = () => {
dialog.show({
id: 'hotkey-help',
title: 'Keyboard Shortcuts',
size: 'sm',
render: () => <KeyboardShortcutsDialog />,
});
};
useHotkey('hotkeys.showHelp', showHotkeyHelp);
return ( return (
<Dropdown <Dropdown
ref={dropdownRef} ref={dropdownRef}
@@ -83,7 +71,14 @@ export function SettingsDropdown() {
key: 'hotkeys', key: 'hotkeys',
label: 'Keyboard shortcuts', label: 'Keyboard shortcuts',
hotkeyAction: 'hotkeys.showHelp', hotkeyAction: 'hotkeys.showHelp',
onSelect: showHotkeyHelp, onSelect: () => {
dialog.show({
id: 'hotkey-help',
title: 'Keyboard Shortcuts',
size: 'sm',
render: () => <KeyboardShortcutsDialog />,
});
},
leftSlot: <Icon icon="keyboard" />, leftSlot: <Icon icon="keyboard" />,
}, },
{ type: 'separator', label: `Yaak v${appVersion.data}` }, { type: 'separator', label: `Yaak v${appVersion.data}` },

View File

@@ -16,7 +16,7 @@ import { useDeleteFolder } from '../hooks/useDeleteFolder';
import { useDeleteRequest } from '../hooks/useDeleteRequest'; import { useDeleteRequest } from '../hooks/useDeleteRequest';
import { useDuplicateRequest } from '../hooks/useDuplicateRequest'; import { useDuplicateRequest } from '../hooks/useDuplicateRequest';
import { useFolders } from '../hooks/useFolders'; import { useFolders } from '../hooks/useFolders';
import { useHotkey } from '../hooks/useHotkey'; import { useHotKey } from '../hooks/useHotKey';
import { useKeyValue } from '../hooks/useKeyValue'; import { useKeyValue } from '../hooks/useKeyValue';
import { useLatestResponse } from '../hooks/useLatestResponse'; import { useLatestResponse } from '../hooks/useLatestResponse';
import { usePrompt } from '../hooks/usePrompt'; import { usePrompt } from '../hooks/usePrompt';
@@ -55,7 +55,6 @@ export function Sidebar({ className }: Props) {
const { hidden } = useSidebarHidden(); const { hidden } = useSidebarHidden();
const sidebarRef = useRef<HTMLLIElement>(null); const sidebarRef = useRef<HTMLLIElement>(null);
const activeRequestId = useActiveRequestId(); const activeRequestId = useActiveRequestId();
const duplicateRequest = useDuplicateRequest({ id: activeRequestId ?? '', navigateAfter: true });
const activeEnvironmentId = useActiveEnvironmentId(); const activeEnvironmentId = useActiveEnvironmentId();
const requests = useRequests(); const requests = useRequests();
const folders = useFolders(); const folders = useFolders();
@@ -76,8 +75,6 @@ export function Sidebar({ className }: Props) {
namespace: NAMESPACE_NO_SYNC, namespace: NAMESPACE_NO_SYNC,
}); });
useHotkey('request.duplicate', () => duplicateRequest.mutate());
const isCollapsed = useCallback( const isCollapsed = useCallback(
(id: string) => collapsed.value?.[id] ?? false, (id: string) => collapsed.value?.[id] ?? false,
[collapsed.value], [collapsed.value],
@@ -209,7 +206,7 @@ export function Sidebar({ className }: Props) {
useKeyPressEvent('Backspace', handleDeleteKey); useKeyPressEvent('Backspace', handleDeleteKey);
useKeyPressEvent('Delete', handleDeleteKey); useKeyPressEvent('Delete', handleDeleteKey);
useHotkey('sidebar.focus', () => { useHotKey('sidebar.focus', () => {
if (hidden || hasFocus) return; if (hidden || hasFocus) return;
// Select 0 index on focus if none selected // Select 0 index on focus if none selected
focusActiveRequest( focusActiveRequest(
@@ -649,10 +646,7 @@ const SidebarItem = forwardRef(function SidebarItem(
label: 'Duplicate', label: 'Duplicate',
hotkeyAction: 'request.duplicate', hotkeyAction: 'request.duplicate',
leftSlot: <Icon icon="copy" />, leftSlot: <Icon icon="copy" />,
onSelect: () => { onSelect: () => duplicateRequest.mutate(),
console.log('DUPLICATE');
duplicateRequest.mutate();
},
}, },
{ {
key: 'deleteRequest', key: 'deleteRequest',

View File

@@ -1,7 +1,6 @@
import { memo } from 'react'; import { memo } from 'react';
import { useCreateFolder } from '../hooks/useCreateFolder'; import { useCreateFolder } from '../hooks/useCreateFolder';
import { useCreateRequest } from '../hooks/useCreateRequest'; import { useCreateRequest } from '../hooks/useCreateRequest';
import { useHotkey } from '../hooks/useHotkey';
import { useSidebarHidden } from '../hooks/useSidebarHidden'; import { useSidebarHidden } from '../hooks/useSidebarHidden';
import { Dropdown } from './core/Dropdown'; import { Dropdown } from './core/Dropdown';
import { IconButton } from './core/IconButton'; import { IconButton } from './core/IconButton';
@@ -12,8 +11,6 @@ export const SidebarActions = memo(function SidebarActions() {
const createFolder = useCreateFolder(); const createFolder = useCreateFolder();
const { hidden, toggle } = useSidebarHidden(); const { hidden, toggle } = useSidebarHidden();
useHotkey('request.create', () => createRequest.mutate({}));
return ( return (
<HStack> <HStack>
<IconButton <IconButton

View File

@@ -2,7 +2,7 @@ import classNames from 'classnames';
import type { EditorView } from 'codemirror'; import type { EditorView } from 'codemirror';
import type { FormEvent } from 'react'; import type { FormEvent } from 'react';
import { memo, useCallback, useRef, useState } from 'react'; import { memo, useCallback, useRef, useState } from 'react';
import { useHotkey } from '../hooks/useHotkey'; import { useHotKey } from '../hooks/useHotKey';
import { useIsResponseLoading } from '../hooks/useIsResponseLoading'; import { useIsResponseLoading } from '../hooks/useIsResponseLoading';
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey'; import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
import { useSendRequest } from '../hooks/useSendRequest'; import { useSendRequest } from '../hooks/useSendRequest';
@@ -40,7 +40,7 @@ export const UrlBar = memo(function UrlBar({ id: requestId, url, method, classNa
[sendRequest], [sendRequest],
); );
useHotkey('urlBar.focus', () => { useHotKey('urlBar.focus', () => {
const head = inputRef.current?.state.doc.length ?? 0; const head = inputRef.current?.state.doc.length ?? 0;
inputRef.current?.dispatch({ inputRef.current?.dispatch({
selection: { anchor: 0, head }, selection: { anchor: 0, head },

View File

@@ -1,8 +1,8 @@
import classNames from 'classnames'; import classNames from 'classnames';
import type { HTMLAttributes, ReactNode } from 'react'; import type { HTMLAttributes, ReactNode } from 'react';
import { forwardRef, useImperativeHandle, useMemo, useRef } from 'react'; import { forwardRef, useImperativeHandle, useMemo, useRef } from 'react';
import type { HotkeyAction } from '../../hooks/useHotkey'; import type { HotkeyAction } from '../../hooks/useHotKey';
import { useFormattedHotkey, useHotkey } from '../../hooks/useHotkey'; import { useFormattedHotkey, useHotKey } from '../../hooks/useHotKey';
import { Icon } from './Icon'; import { Icon } from './Icon';
const colorStyles = { const colorStyles = {
@@ -80,7 +80,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button
() => buttonRef.current, () => buttonRef.current,
); );
useHotkey(hotkeyAction ?? null, () => { useHotKey(hotkeyAction ?? null, () => {
buttonRef.current?.click(); buttonRef.current?.click();
}); });

View File

@@ -20,7 +20,8 @@ import React, {
useState, useState,
} from 'react'; } from 'react';
import { useKey, useKeyPressEvent, useWindowSize } from 'react-use'; import { useKey, useKeyPressEvent, useWindowSize } from 'react-use';
import type { HotkeyAction } from '../../hooks/useHotkey'; import type { HotkeyAction } from '../../hooks/useHotKey';
import { useHotKey } from '../../hooks/useHotKey';
import { Overlay } from '../Overlay'; import { Overlay } from '../Overlay';
import { Button } from './Button'; import { Button } from './Button';
import { HotKey } from './HotKey'; import { HotKey } from './HotKey';
@@ -50,6 +51,7 @@ export type DropdownItem = DropdownItemDefault | DropdownItemSeparator;
export interface DropdownProps { export interface DropdownProps {
children: ReactElement<HTMLAttributes<HTMLButtonElement>>; children: ReactElement<HTMLAttributes<HTMLButtonElement>>;
items: DropdownItem[]; items: DropdownItem[];
openOnHotKeyAction?: HotkeyAction;
} }
export interface DropdownRef { export interface DropdownRef {
@@ -63,20 +65,24 @@ export interface DropdownRef {
} }
export const Dropdown = forwardRef<DropdownRef, DropdownProps>(function Dropdown( export const Dropdown = forwardRef<DropdownRef, DropdownProps>(function Dropdown(
{ children, items }: DropdownProps, { children, items, openOnHotKeyAction }: DropdownProps,
ref, ref,
) { ) {
const [open, setOpen] = useState<boolean>(false); const [isOpen, setIsOpen] = useState<boolean>(false);
const [defaultSelectedIndex, setDefaultSelectedIndex] = useState<number>(); const [defaultSelectedIndex, setDefaultSelectedIndex] = useState<number>();
const buttonRef = useRef<HTMLButtonElement>(null); const buttonRef = useRef<HTMLButtonElement>(null);
const menuRef = useRef<Omit<DropdownRef, 'open'>>(null); const menuRef = useRef<Omit<DropdownRef, 'open'>>(null);
useHotKey(openOnHotKeyAction ?? null, () => {
setIsOpen(true);
});
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
...menuRef.current, ...menuRef.current,
isOpen: open, isOpen: isOpen,
toggle(activeIndex?: number) { toggle(activeIndex?: number) {
if (!open) this.open(activeIndex); if (!isOpen) this.open(activeIndex);
else setOpen(false); else setIsOpen(false);
}, },
open(activeIndex?: number) { open(activeIndex?: number) {
if (activeIndex === undefined) { if (activeIndex === undefined) {
@@ -84,7 +90,7 @@ export const Dropdown = forwardRef<DropdownRef, DropdownProps>(function Dropdown
} else { } else {
setDefaultSelectedIndex(activeIndex >= 0 ? activeIndex : items.length + activeIndex); setDefaultSelectedIndex(activeIndex >= 0 ? activeIndex : items.length + activeIndex);
} }
setOpen(true); setIsOpen(true);
}, },
})); }));
@@ -101,41 +107,40 @@ export const Dropdown = forwardRef<DropdownRef, DropdownProps>(function Dropdown
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
setDefaultSelectedIndex(undefined); setDefaultSelectedIndex(undefined);
setOpen((o) => !o); setIsOpen((o) => !o);
}), }),
}; };
return cloneElement(existingChild, props); return cloneElement(existingChild, props);
}, [children]); }, [children]);
const handleClose = useCallback(() => { const handleClose = useCallback(() => {
setOpen(false); setIsOpen(false);
buttonRef.current?.focus(); buttonRef.current?.focus();
}, []); }, []);
useEffect(() => { useEffect(() => {
buttonRef.current?.setAttribute('aria-expanded', open.toString()); buttonRef.current?.setAttribute('aria-expanded', isOpen.toString());
}, [open]); }, [isOpen]);
const windowSize = useWindowSize(); const windowSize = useWindowSize();
const triggerRect = useMemo(() => { const triggerRect = useMemo(() => {
if (!windowSize) return null; // No-op to TS happy with this dep if (!windowSize) return null; // No-op to TS happy with this dep
if (!open) return null; if (!isOpen) return null;
return buttonRef.current?.getBoundingClientRect(); return buttonRef.current?.getBoundingClientRect();
}, [open, windowSize]); }, [isOpen, windowSize]);
return ( return (
<> <>
{child} {child}
{open && triggerRect && ( <Menu
<Menu ref={menuRef}
ref={menuRef} showTriangle
showTriangle defaultSelectedIndex={defaultSelectedIndex}
defaultSelectedIndex={defaultSelectedIndex} items={items}
items={items} triggerShape={triggerRect ?? null}
triggerShape={triggerRect} onClose={handleClose}
onClose={handleClose} isOpen={isOpen}
/> />
)}
</> </>
); );
}); });
@@ -161,15 +166,12 @@ export const ContextMenu = forwardRef<DropdownRef, ContextMenuProps>(function Co
[show], [show],
); );
if (show === null) {
return null;
}
return ( return (
<Menu <Menu
className={className} className={className}
ref={ref} ref={ref}
items={items} items={items}
isOpen={show != null}
onClose={onClose} onClose={onClose}
triggerShape={triggerShape} triggerShape={triggerShape}
/> />
@@ -180,13 +182,22 @@ interface MenuProps {
className?: string; className?: string;
defaultSelectedIndex?: number; defaultSelectedIndex?: number;
items: DropdownProps['items']; items: DropdownProps['items'];
triggerShape: Pick<DOMRect, 'top' | 'bottom' | 'left' | 'right'>; triggerShape: Pick<DOMRect, 'top' | 'bottom' | 'left' | 'right'> | null;
onClose: () => void; onClose: () => void;
showTriangle?: boolean; showTriangle?: boolean;
isOpen: boolean;
} }
const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuProps>(function Menu( const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuProps>(function Menu(
{ className, items, onClose, triggerShape, defaultSelectedIndex, showTriangle }: MenuProps, {
className,
isOpen,
items,
onClose,
triggerShape,
defaultSelectedIndex,
showTriangle,
}: MenuProps,
ref, ref,
) { ) {
const containerRef = useRef<HTMLDivElement | null>(null); const containerRef = useRef<HTMLDivElement | null>(null);
@@ -291,6 +302,8 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
containerStyles: CSSProperties; containerStyles: CSSProperties;
triangleStyles: CSSProperties | null; triangleStyles: CSSProperties | null;
}>(() => { }>(() => {
if (triggerShape == null) return { containerStyles: {}, triangleStyles: null };
const docRect = document.documentElement.getBoundingClientRect(); const docRect = document.documentElement.getBoundingClientRect();
const width = triggerShape.right - triggerShape.left; const width = triggerShape.right - triggerShape.left;
const hSpaceRemaining = docRect.width - triggerShape.left; const hSpaceRemaining = docRect.width - triggerShape.left;
@@ -322,61 +335,76 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
if (items.length === 0) return null; if (items.length === 0) return null;
return ( return (
<Overlay open variant="transparent" portalName="dropdown" zIndex={50}> <>
<div> {items.map(
<div tabIndex={-1} aria-hidden className="fixed inset-0 z-30" onClick={onClose} /> (item) =>
<motion.div item.type !== 'separator' && (
tabIndex={0} <MenuItemHotKey
onKeyDown={handleMenuKeyDown} key={item.key}
initial={{ opacity: 0, y: -5, scale: 0.98 }} onSelect={handleSelect}
animate={{ opacity: 1, y: 0, scale: 1 }} item={item}
role="menu" action={item.hotkeyAction}
aria-orientation="vertical"
dir="ltr"
ref={containerRef}
style={containerStyles}
className={classNames(className, 'outline-none my-1 pointer-events-auto fixed z-50')}
>
{triangleStyles && showTriangle && (
<span
aria-hidden
style={triangleStyles}
className="bg-gray-50 absolute rotate-45 border-gray-200 border-t border-l"
/> />
)} ),
{containerStyles && ( )}
<VStack {isOpen && (
space={0.5} <Overlay open variant="transparent" portalName="dropdown" zIndex={50}>
ref={initMenu} <div>
style={menuStyles} <div tabIndex={-1} aria-hidden className="fixed inset-0 z-30" onClick={onClose} />
className={classNames( <motion.div
className, tabIndex={0}
'h-auto bg-gray-50 rounded-md shadow-lg dark:shadow-gray-0 py-1.5 border', onKeyDown={handleMenuKeyDown}
'border-gray-200 overflow-auto mb-1 mx-0.5', initial={{ opacity: 0, y: -5, scale: 0.98 }}
)} animate={{ opacity: 1, y: 0, scale: 1 }}
role="menu"
aria-orientation="vertical"
dir="ltr"
ref={containerRef}
style={containerStyles}
className={classNames(className, 'outline-none my-1 pointer-events-auto fixed z-50')}
> >
{items.map((item, i) => { {triangleStyles && showTriangle && (
if (item.type === 'separator') { <span
return <Separator key={i} className="my-1.5" label={item.label} />; aria-hidden
} style={triangleStyles}
if (item.hidden) { className="bg-gray-50 absolute rotate-45 border-gray-200 border-t border-l"
return null; />
} )}
return ( {containerStyles && (
<MenuItem <VStack
focused={i === selectedIndex} space={0.5}
onFocus={handleFocus} ref={initMenu}
onSelect={handleSelect} style={menuStyles}
key={item.key} className={classNames(
item={item} className,
/> 'h-auto bg-gray-50 rounded-md shadow-lg dark:shadow-gray-0 py-1.5 border',
); 'border-gray-200 overflow-auto mb-1 mx-0.5',
})} )}
</VStack> >
)} {items.map((item, i) => {
</motion.div> if (item.type === 'separator') {
</div> return <Separator key={i} className="my-1.5" label={item.label} />;
</Overlay> }
if (item.hidden) {
return null;
}
return (
<MenuItem
focused={i === selectedIndex}
onFocus={handleFocus}
onSelect={handleSelect}
key={item.key}
item={item}
/>
);
})}
</VStack>
)}
</motion.div>
</div>
</Overlay>
)}
</>
); );
}); });
@@ -443,3 +471,19 @@ function MenuItem({ className, focused, onFocus, item, onSelect, ...props }: Men
</Button> </Button>
); );
} }
interface MenuItemHotKeyProps {
action: HotkeyAction | undefined;
onSelect: MenuItemProps['onSelect'];
item: MenuItemProps['item'];
}
function MenuItemHotKey({ action, onSelect, item }: MenuItemHotKeyProps) {
if (action) {
console.log('MENU ITEM HOTKEY', action, item);
}
useHotKey(action ?? null, () => {
onSelect(item);
});
return null;
}

View File

@@ -1,7 +1,8 @@
import classNames from 'classnames'; import classNames from 'classnames';
import type { HotkeyAction } from '../../hooks/useHotkey'; import type { HotkeyAction } from '../../hooks/useHotKey';
import { useFormattedHotkey } from '../../hooks/useHotkey'; import { useFormattedHotkey } from '../../hooks/useHotKey';
import { useOsInfo } from '../../hooks/useOsInfo'; import { useOsInfo } from '../../hooks/useOsInfo';
import { HStack } from './Stacks';
interface Props { interface Props {
action: HotkeyAction | null; action: HotkeyAction | null;
@@ -17,14 +18,18 @@ export function HotKey({ action, className, variant }: Props) {
} }
return ( return (
<span <HStack
className={classNames( className={classNames(
className, className,
variant === 'with-bg' && 'rounded border', variant === 'with-bg' && 'rounded border',
'text-sm text-gray-1000 text-opacity-disabled', 'text-gray-1000 text-opacity-disabled',
)} )}
> >
{label} {label.split('').map((char, index) => (
</span> <div key={index} className="w-[1.1em] text-center">
{char}
</div>
))}
</HStack>
); );
} }

View File

@@ -1,8 +1,8 @@
import type { HotkeyAction } from '../../hooks/useHotkey'; import type { HotkeyAction } from '../../hooks/useHotKey';
import { useHotKeyLabel } from '../../hooks/useHotkey'; import { useHotKeyLabel } from '../../hooks/useHotKey';
interface Props { interface Props {
action: HotkeyAction | null; action: HotkeyAction;
} }
export function HotKeyLabel({ action }: Props) { export function HotKeyLabel({ action }: Props) {

View File

@@ -1,7 +1,8 @@
import React from 'react'; import React from 'react';
import type { HotkeyAction } from '../../hooks/useHotkey'; import type { HotkeyAction } from '../../hooks/useHotKey';
import { HotKey } from './HotKey'; import { HotKey } from './HotKey';
import { HotKeyLabel } from './HotKeyLabel'; import { HotKeyLabel } from './HotKeyLabel';
import { HStack, VStack } from './Stacks';
interface Props { interface Props {
hotkeys: HotkeyAction[]; hotkeys: HotkeyAction[];
@@ -10,14 +11,14 @@ interface Props {
export const HotKeyList = ({ hotkeys }: Props) => { export const HotKeyList = ({ hotkeys }: Props) => {
return ( return (
<div className="mx-auto h-full flex items-center text-gray-700 text-sm"> <div className="mx-auto h-full flex items-center text-gray-700 text-sm">
<div className="flex flex-col gap-1"> <VStack space={2}>
{hotkeys.map((hotkey) => ( {hotkeys.map((hotkey) => (
<div key={hotkey} className="grid grid-cols-2"> <HStack key={hotkey} className="grid grid-cols-2">
<HotKeyLabel action={hotkey} /> <HotKeyLabel action={hotkey} />
<HotKey className="ml-auto" action={hotkey} /> <HotKey className="ml-auto" action={hotkey} />
</div> </HStack>
))} ))}
</div> </VStack>
</div> </div>
); );
}; };

View File

@@ -24,13 +24,24 @@ const hotkeys: Record<HotkeyAction, string[]> = {
'hotkeys.showHelp': ['CmdCtrl+/'], 'hotkeys.showHelp': ['CmdCtrl+/'],
}; };
const hotkeyLabels: Record<HotkeyAction, string> = {
'request.send': 'Send Request',
'request.create': 'New Request',
'request.duplicate': 'Duplicate Request',
'sidebar.toggle': 'Toggle Sidebar',
'sidebar.focus': 'Focus Sidebar',
'urlBar.focus': 'Focus URL',
'environmentEditor.toggle': 'Edit Environments',
'hotkeys.showHelp': 'Show Hotkeys',
};
export const hotkeyActions: HotkeyAction[] = Object.keys(hotkeys) as (keyof typeof hotkeys)[]; export const hotkeyActions: HotkeyAction[] = Object.keys(hotkeys) as (keyof typeof hotkeys)[];
interface Options { interface Options {
enable?: boolean; enable?: boolean;
} }
export function useHotkey( export function useHotKey(
action: HotkeyAction | null, action: HotkeyAction | null,
callback: (e: KeyboardEvent) => void, callback: (e: KeyboardEvent) => void,
options: Options = {}, options: Options = {},
@@ -97,25 +108,8 @@ export function useAnyHotkey(
}, [options.enable, os]); }, [options.enable, os]);
} }
export function useHotKeyLabel(action: HotkeyAction | null): string { export function useHotKeyLabel(action: HotkeyAction): string {
switch (action) { return hotkeyLabels[action];
case 'request.send':
return 'Send Request';
case 'request.create':
return 'New Request';
case 'request.duplicate':
return 'Duplicate Request';
case 'sidebar.toggle':
return 'Toggle Sidebar';
case 'sidebar.focus':
return 'Focus Sidebar';
case 'urlBar.focus':
return 'Focus URL';
case 'environmentEditor.toggle':
return 'Edit Environments';
default:
return 'Unknown';
}
} }
export function useFormattedHotkey(action: HotkeyAction | null): string | null { export function useFormattedHotkey(action: HotkeyAction | null): string | null {