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:
@@ -10,6 +10,7 @@ import { RecentTasks } from '@/components/dashboard/RecentTasks';
|
||||
import { ProductivityAnalytics } from '@/components/dashboard/ProductivityAnalytics';
|
||||
import { ProductivityMetrics } from '@/services/analytics/analytics';
|
||||
import { DeadlineMetrics } from '@/services/analytics/deadline-analytics';
|
||||
import { useGlobalKeyboardShortcuts } from '@/hooks/useGlobalKeyboardShortcuts';
|
||||
|
||||
interface HomePageClientProps {
|
||||
initialTasks: Task[];
|
||||
@@ -31,6 +32,20 @@ function HomePageContent({ productivityMetrics, deadlineMetrics }: {
|
||||
await createTask(data);
|
||||
};
|
||||
|
||||
// Raccourcis clavier globaux pour la page Dashboard
|
||||
useGlobalKeyboardShortcuts({
|
||||
onOpenSearch: () => {
|
||||
// Focus sur le champ de recherche s'il existe, sinon naviguer vers Kanban
|
||||
const searchInput = document.querySelector('input[placeholder*="Rechercher"]') as HTMLInputElement;
|
||||
if (searchInput) {
|
||||
searchInput.focus();
|
||||
} else {
|
||||
// Naviguer vers Kanban où il y a une recherche
|
||||
window.location.href = '/kanban';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-[var(--background)]">
|
||||
<Header
|
||||
|
||||
@@ -1,8 +1,19 @@
|
||||
'use client';
|
||||
|
||||
import { useKeyboardShortcuts } from '@/hooks/useKeyboardShortcuts';
|
||||
import { useKeyboardShortcutsModal } from '@/contexts/KeyboardShortcutsContext';
|
||||
import { KeyboardShortcutsModal } from '@/components/ui/KeyboardShortcutsModal';
|
||||
|
||||
export function KeyboardShortcuts() {
|
||||
useKeyboardShortcuts();
|
||||
return null; // Ce composant ne rend rien, il gère juste les raccourcis
|
||||
const { isOpen, shortcuts, closeModal } = useKeyboardShortcutsModal();
|
||||
|
||||
return (
|
||||
<KeyboardShortcutsModal
|
||||
isOpen={isOpen}
|
||||
onClose={closeModal}
|
||||
shortcuts={shortcuts}
|
||||
title="Raccourcis clavier"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
96
src/components/ui/KeyboardShortcutsModal.tsx
Normal file
96
src/components/ui/KeyboardShortcutsModal.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user