136 lines
3.8 KiB
TypeScript
136 lines
3.8 KiB
TypeScript
import { useEffect, useCallback } from 'react';
|
|
import { useNavigate } from 'react-router-dom';
|
|
import { useUIStore } from '@/stores/ui';
|
|
|
|
/**
|
|
* FE-COMP-022: Global keyboard shortcuts hook for common actions
|
|
*/
|
|
|
|
export interface GlobalKeyboardShortcutsOptions {
|
|
enabled?: boolean;
|
|
preventDefault?: boolean;
|
|
onHelpOpen?: () => void;
|
|
}
|
|
|
|
export function useGlobalKeyboardShortcuts(
|
|
options: GlobalKeyboardShortcutsOptions = {},
|
|
) {
|
|
const { enabled = true, preventDefault = true, onHelpOpen } = options;
|
|
const navigate = useNavigate();
|
|
const { setSidebarOpen, sidebarOpen } = useUIStore();
|
|
|
|
const handleKeyDown = useCallback(
|
|
(e: KeyboardEvent) => {
|
|
if (!enabled) return;
|
|
|
|
// Ignorer si l'utilisateur est en train de taper dans un input/textarea/contenteditable
|
|
const target = e.target as HTMLElement;
|
|
if (
|
|
target &&
|
|
(target.tagName === 'INPUT' ||
|
|
target.tagName === 'TEXTAREA' ||
|
|
target.isContentEditable === true ||
|
|
target.getAttribute('role') === 'textbox')
|
|
) {
|
|
// Permettre Ctrl+K même dans les inputs pour la recherche
|
|
if (e.key === 'k' && (e.ctrlKey || e.metaKey)) {
|
|
// Ctrl+K ou Cmd+K : Focus sur la recherche
|
|
if (preventDefault) {
|
|
e.preventDefault();
|
|
}
|
|
const searchInput = document.querySelector(
|
|
'input[type="search"], input[placeholder*="search" i], input[placeholder*="rechercher" i]',
|
|
) as HTMLInputElement;
|
|
if (searchInput) {
|
|
searchInput.focus();
|
|
searchInput.select();
|
|
} else {
|
|
// Sinon, naviguer vers la page de recherche
|
|
navigate('/search');
|
|
}
|
|
return;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Ctrl+K ou Cmd+K : Focus sur la recherche ou naviguer vers /search
|
|
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
|
|
if (preventDefault) {
|
|
e.preventDefault();
|
|
}
|
|
const searchInput = document.querySelector(
|
|
'input[type="search"], input[placeholder*="search" i], input[placeholder*="rechercher" i]',
|
|
) as HTMLInputElement;
|
|
if (searchInput) {
|
|
searchInput.focus();
|
|
searchInput.select();
|
|
} else {
|
|
navigate('/search');
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Ctrl+N ou Cmd+N : Nouveau message/chat
|
|
if ((e.ctrlKey || e.metaKey) && e.key === 'n') {
|
|
if (preventDefault) {
|
|
e.preventDefault();
|
|
}
|
|
navigate('/chat');
|
|
return;
|
|
}
|
|
|
|
// Ctrl+B ou Cmd+B : Toggle sidebar
|
|
if ((e.ctrlKey || e.metaKey) && e.key === 'b') {
|
|
if (preventDefault) {
|
|
e.preventDefault();
|
|
}
|
|
setSidebarOpen(!sidebarOpen);
|
|
return;
|
|
}
|
|
|
|
// Escape : Fermer les modals/dropdowns ou retourner en arrière
|
|
if (e.key === 'Escape') {
|
|
// Fermer les dropdowns ouverts
|
|
const openDropdowns = document.querySelectorAll(
|
|
'[role="menu"][aria-expanded="true"]',
|
|
);
|
|
openDropdowns.forEach((dropdown) => {
|
|
const button = dropdown.previousElementSibling as HTMLElement;
|
|
if (button) {
|
|
button.click();
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
|
|
// ? : Afficher l'aide des raccourcis
|
|
if (e.key === '?' && !e.ctrlKey && !e.metaKey && !e.shiftKey) {
|
|
if (preventDefault) {
|
|
e.preventDefault();
|
|
}
|
|
if (onHelpOpen) {
|
|
onHelpOpen();
|
|
}
|
|
return;
|
|
}
|
|
},
|
|
[
|
|
enabled,
|
|
preventDefault,
|
|
navigate,
|
|
setSidebarOpen,
|
|
sidebarOpen,
|
|
onHelpOpen,
|
|
],
|
|
);
|
|
|
|
useEffect(() => {
|
|
if (!enabled) return;
|
|
|
|
window.addEventListener('keydown', handleKeyDown);
|
|
|
|
return () => {
|
|
window.removeEventListener('keydown', handleKeyDown);
|
|
};
|
|
}, [enabled, handleKeyDown]);
|
|
}
|