feat: integrate global keyboard shortcuts across multiple components

- Added `KeyboardShortcutsProvider` to `RootLayout` for centralized keyboard shortcut management.
- Implemented `useGlobalKeyboardShortcuts` in `DailyPageClient`, `KanbanPageClient`, and `HomePageClient` to enhance navigation and task management with keyboard shortcuts.
- Updated `KeyboardShortcuts` component to render a modal for displaying available shortcuts, improving user accessibility.
- Enhanced `Header` component with buttons to open the keyboard shortcuts modal, streamlining user interaction.
This commit is contained in:
Julien Froidefond
2025-09-29 17:29:11 +02:00
parent c1a14f9196
commit 749f69680b
10 changed files with 487 additions and 8 deletions

View File

@@ -0,0 +1,210 @@
'use client';
import { createContext, useContext, useState, useEffect, ReactNode } from 'react';
import { usePathname } from 'next/navigation';
export interface KeyboardShortcut {
keys: string[];
description: string;
category?: string;
}
export interface PageShortcuts {
[pagePath: string]: KeyboardShortcut[];
}
// Configuration des raccourcis par page
const PAGE_SHORTCUTS: PageShortcuts = {
// Raccourcis globaux (toutes les pages)
'*': [
{
keys: ['Cmd', '?'],
description: 'Afficher les raccourcis clavier',
category: 'Navigation'
},
{
keys: ['Cmd', 'Ctrl', 'D'],
description: 'Basculer le thème',
category: 'Apparence'
},
{
keys: ['Cmd', 'Ctrl', 'T'],
description: 'Changer de thème sombre',
category: 'Apparence'
},
{
keys: ['Esc'],
description: 'Fermer les modales/annuler',
category: 'Navigation'
}
],
// Dashboard
'/': [
{
keys: ['Cmd', 'Ctrl', 'K'],
description: 'Vers Kanban',
category: 'Navigation'
}
],
// Kanban
'/kanban': [
{
keys: ['Cmd', 'Ctrl', 'N'],
description: 'Créer une nouvelle tâche',
category: 'Actions'
},
{
keys: ['Space'],
description: 'Basculer la vue compacte',
category: 'Vue'
},
{
keys: ['Cmd', 'Ctrl', 'F'],
description: 'Ouvrir les filtres',
category: 'Filtres'
},
{
keys: ['Cmd', 'Ctrl', 'S'],
description: 'Basculer les swimlanes',
category: 'Vue'
},
{
keys: ['Tab'],
description: 'Navigation entre colonnes',
category: 'Navigation'
},
{
keys: ['Enter'],
description: 'Valider une tâche',
category: 'Actions'
},
{
keys: ['Esc'],
description: 'Annuler la création de tâche',
category: 'Actions'
}
],
// Daily
'/daily': [
{
keys: ['←', '→'],
description: 'Navigation jour précédent/suivant',
category: 'Navigation'
},
{
keys: ['Cmd', 'Ctrl', 'T'],
description: 'Aller à aujourd\'hui',
category: 'Navigation'
},
{
keys: ['Enter'],
description: 'Valider un élément',
category: 'Actions'
}
],
// Manager
'/weekly-manager': [
{
keys: ['Cmd', 'Ctrl', 'N'],
description: 'Créer un objectif',
category: 'Actions'
},
{
keys: ['←', '→'],
description: 'Navigation semaine précédente/suivante',
category: 'Navigation'
},
{
keys: ['Cmd', 'Ctrl', 'T'],
description: 'Aller à cette semaine',
category: 'Navigation'
}
]
};
interface KeyboardShortcutsContextType {
isOpen: boolean;
shortcuts: KeyboardShortcut[];
openModal: () => void;
closeModal: () => void;
toggleModal: () => void;
}
const KeyboardShortcutsContext = createContext<KeyboardShortcutsContextType | undefined>(undefined);
interface KeyboardShortcutsProviderProps {
children: ReactNode;
}
export function KeyboardShortcutsProvider({ children }: KeyboardShortcutsProviderProps) {
const [isOpen, setIsOpen] = useState(false);
const pathname = usePathname();
// Obtenir les raccourcis pour la page actuelle
const getCurrentPageShortcuts = (): KeyboardShortcut[] => {
const shortcuts: KeyboardShortcut[] = [];
// Ajouter les raccourcis globaux
if (PAGE_SHORTCUTS['*']) {
shortcuts.push(...PAGE_SHORTCUTS['*']);
}
// Ajouter les raccourcis spécifiques à la page
const pageShortcuts = PAGE_SHORTCUTS[pathname];
if (pageShortcuts) {
shortcuts.push(...pageShortcuts);
}
return shortcuts;
};
const shortcuts = getCurrentPageShortcuts();
// Gérer le raccourci Cmd+? pour ouvrir la popup
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
// Cmd+? ou Ctrl+? pour ouvrir les raccourcis
if ((event.metaKey || event.ctrlKey) && event.key === '?') {
event.preventDefault();
setIsOpen(true);
}
// Esc pour fermer
if (event.key === 'Escape' && isOpen) {
setIsOpen(false);
}
};
document.addEventListener('keydown', handleKeyDown);
return () => {
document.removeEventListener('keydown', handleKeyDown);
};
}, [isOpen]);
const contextValue: KeyboardShortcutsContextType = {
isOpen,
shortcuts,
openModal: () => setIsOpen(true),
closeModal: () => setIsOpen(false),
toggleModal: () => setIsOpen(prev => !prev)
};
return (
<KeyboardShortcutsContext.Provider value={contextValue}>
{children}
</KeyboardShortcutsContext.Provider>
);
}
export function useKeyboardShortcutsModal() {
const context = useContext(KeyboardShortcutsContext);
if (context === undefined) {
throw new Error('useKeyboardShortcutsModal must be used within a KeyboardShortcutsProvider');
}
return context;
}