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

@@ -7,6 +7,7 @@ import Link from 'next/link';
import { useState } from 'react';
import { Theme } from '@/lib/theme-config';
import { THEME_CONFIG, getThemeMetadata } from '@/lib/theme-config';
import { useKeyboardShortcutsModal } from '@/contexts/KeyboardShortcutsContext';
interface HeaderProps {
title?: string;
@@ -20,6 +21,7 @@ export function Header({ title = "TowerControl", subtitle = "Task Management", s
const pathname = usePathname();
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const [themeDropdownOpen, setThemeDropdownOpen] = useState(false);
const { openModal: openShortcutsModal } = useKeyboardShortcutsModal();
// Liste des thèmes disponibles avec leurs labels et icônes
const themes: { value: Theme; label: string; icon: string }[] = THEME_CONFIG.allThemes.map(themeValue => {
@@ -97,6 +99,15 @@ export function Header({ title = "TowerControl", subtitle = "Task Management", s
{/* Controls mobile/tablette */}
<div className="flex items-center gap-2 flex-shrink-0">
{/* Keyboard Shortcuts */}
<button
onClick={openShortcutsModal}
className="text-[var(--muted-foreground)] hover:text-[var(--primary)] transition-colors p-2 rounded-md hover:bg-[var(--card-hover)]"
title="Raccourcis clavier (Cmd+?)"
>
</button>
{/* Theme Dropdown */}
<div className="relative">
<button
@@ -197,6 +208,15 @@ export function Header({ title = "TowerControl", subtitle = "Task Management", s
</Link>
))}
{/* Keyboard Shortcuts desktop */}
<button
onClick={openShortcutsModal}
className="text-[var(--muted-foreground)] hover:text-[var(--primary)] transition-colors p-1 rounded-md hover:bg-[var(--card-hover)]"
title="Raccourcis clavier (Cmd+?)"
>
</button>
{/* Theme Dropdown desktop */}
<div className="relative">
<button

View File

@@ -0,0 +1,96 @@
'use client';
import { Modal } from './Modal';
import { cn } from '@/lib/utils';
interface KeyboardShortcut {
keys: string[];
description: string;
category?: string;
}
interface KeyboardShortcutsModalProps {
isOpen: boolean;
onClose: () => void;
shortcuts: KeyboardShortcut[];
title?: string;
}
function KeyBadge({ keyText }: { keyText: string }) {
return (
<kbd className="inline-flex items-center px-2 py-1 text-xs font-mono font-medium text-[var(--foreground)] bg-[var(--card)] border border-[var(--border)] rounded-md shadow-sm">
{keyText}
</kbd>
);
}
function ShortcutRow({ shortcut }: { shortcut: KeyboardShortcut }) {
return (
<div className="flex items-center justify-between py-2 px-3 rounded-md hover:bg-[var(--card-hover)] transition-colors">
<div className="flex items-center gap-2">
{shortcut.keys.map((key, index) => (
<div key={index} className="flex items-center gap-1">
<KeyBadge keyText={key} />
{index < shortcut.keys.length - 1 && (
<span className="text-[var(--muted-foreground)] text-xs">+</span>
)}
</div>
))}
</div>
<span className="text-sm text-[var(--foreground)] font-mono">
{shortcut.description}
</span>
</div>
);
}
export function KeyboardShortcutsModal({
isOpen,
onClose,
shortcuts,
title = "Raccourcis clavier"
}: KeyboardShortcutsModalProps) {
// Grouper les raccourcis par catégorie
const groupedShortcuts = shortcuts.reduce((acc, shortcut) => {
const category = shortcut.category || 'Général';
if (!acc[category]) {
acc[category] = [];
}
acc[category].push(shortcut);
return acc;
}, {} as Record<string, KeyboardShortcut[]>);
return (
<Modal isOpen={isOpen} onClose={onClose} title={title} size="lg">
<div className="space-y-6">
{Object.entries(groupedShortcuts).map(([category, categoryShortcuts]) => (
<div key={category}>
<h3 className="text-sm font-mono font-semibold text-[var(--primary)] uppercase tracking-wider mb-3">
{category}
</h3>
<div className="space-y-1">
{categoryShortcuts.map((shortcut, index) => (
<ShortcutRow key={index} shortcut={shortcut} />
))}
</div>
</div>
))}
{shortcuts.length === 0 && (
<div className="text-center py-8">
<p className="text-[var(--muted-foreground)] font-mono">
Aucun raccourci disponible sur cette page
</p>
</div>
)}
</div>
{/* Footer avec info */}
<div className="mt-6 pt-4 border-t border-[var(--border)]/50">
<p className="text-xs text-[var(--muted-foreground)] font-mono text-center">
Appuyez sur <KeyBadge keyText="Esc" /> pour fermer cette fenêtre
</p>
</div>
</Modal>
);
}