feat: enhance KanbanPageClient and KeyboardShortcuts with new functionality

- Added `toggleFontSize` and `handleToggleDueDateFilter` to `KanbanPageClient` for improved user control over font size and due date visibility.
- Replaced `useKeyboardShortcuts` with `useGlobalKeyboardShortcuts` for better shortcut management across components.
- Updated keyboard shortcuts in `KeyboardShortcutsContext` to include new actions for toggling objectives, due date filters, and font size.
- Refined `KeyboardShortcutsModal` layout for better usability and consistency.
- Removed deprecated `useKeyboardShortcuts` hook to streamline codebase.
This commit is contained in:
Julien Froidefond
2025-09-29 20:57:00 +02:00
parent 749f69680b
commit 32f9d1d5de
6 changed files with 127 additions and 97 deletions

View File

@@ -21,7 +21,7 @@ interface KanbanPageClientProps {
function KanbanPageContent() { function KanbanPageContent() {
const { syncing, createTask, activeFiltersCount, kanbanFilters, setKanbanFilters } = useTasksContext(); const { syncing, createTask, activeFiltersCount, kanbanFilters, setKanbanFilters } = useTasksContext();
const { preferences, updateViewPreferences } = useUserPreferences(); const { preferences, updateViewPreferences, toggleFontSize } = useUserPreferences();
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
const isMobile = useIsMobile(768); // Tailwind md breakpoint const isMobile = useIsMobile(768); // Tailwind md breakpoint
const searchParams = useSearchParams(); const searchParams = useSearchParams();
@@ -50,6 +50,13 @@ function KanbanPageContent() {
updateViewPreferences({ swimlanesByTags: !swimlanesByTags }); updateViewPreferences({ swimlanesByTags: !swimlanesByTags });
}; };
const handleToggleDueDateFilter = () => {
setKanbanFilters({
...kanbanFilters,
showWithDueDate: !kanbanFilters.showWithDueDate
});
};
// Handler pour la création de tâche depuis la barre de contrôles // Handler pour la création de tâche depuis la barre de contrôles
const handleCreateTask = async (data: CreateTaskData) => { const handleCreateTask = async (data: CreateTaskData) => {
await createTask(data); await createTask(data);
@@ -62,6 +69,9 @@ function KanbanPageContent() {
onToggleFilters: handleToggleFilters, onToggleFilters: handleToggleFilters,
onToggleCompactView: handleToggleCompactView, onToggleCompactView: handleToggleCompactView,
onToggleSwimlanes: handleToggleSwimlanes, onToggleSwimlanes: handleToggleSwimlanes,
onToggleObjectives: handleToggleObjectives,
onToggleDueDateFilter: handleToggleDueDateFilter,
onToggleFontSize: toggleFontSize,
onOpenSearch: () => { onOpenSearch: () => {
// Focus sur le champ de recherche dans les contrôles desktop // Focus sur le champ de recherche dans les contrôles desktop
const searchInput = document.querySelector('input[placeholder*="Rechercher"]') as HTMLInputElement; const searchInput = document.querySelector('input[placeholder*="Rechercher"]') as HTMLInputElement;

View File

@@ -1,11 +1,11 @@
'use client'; 'use client';
import { useKeyboardShortcuts } from '@/hooks/useKeyboardShortcuts'; import { useGlobalKeyboardShortcuts } from '@/hooks/useGlobalKeyboardShortcuts';
import { useKeyboardShortcutsModal } from '@/contexts/KeyboardShortcutsContext'; import { useKeyboardShortcutsModal } from '@/contexts/KeyboardShortcutsContext';
import { KeyboardShortcutsModal } from '@/components/ui/KeyboardShortcutsModal'; import { KeyboardShortcutsModal } from '@/components/ui/KeyboardShortcutsModal';
export function KeyboardShortcuts() { export function KeyboardShortcuts() {
useKeyboardShortcuts(); useGlobalKeyboardShortcuts();
const { isOpen, shortcuts, closeModal } = useKeyboardShortcutsModal(); const { isOpen, shortcuts, closeModal } = useKeyboardShortcutsModal();
return ( return (

View File

@@ -18,7 +18,7 @@ interface KeyboardShortcutsModalProps {
function KeyBadge({ keyText }: { keyText: string }) { function KeyBadge({ keyText }: { keyText: string }) {
return ( 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"> <kbd className="inline-flex items-center px-1.5 py-0.5 text-xs font-mono font-medium text-[var(--foreground)] bg-[var(--card)] border border-[var(--border)] rounded shadow-sm">
{keyText} {keyText}
</kbd> </kbd>
); );
@@ -26,10 +26,10 @@ function KeyBadge({ keyText }: { keyText: string }) {
function ShortcutRow({ shortcut }: { shortcut: KeyboardShortcut }) { function ShortcutRow({ shortcut }: { shortcut: KeyboardShortcut }) {
return ( 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 justify-between py-1 px-2 rounded hover:bg-[var(--card-hover)] transition-colors">
<div className="flex items-center gap-2"> <div className="flex items-center gap-1">
{shortcut.keys.map((key, index) => ( {shortcut.keys.map((key, index) => (
<div key={index} className="flex items-center gap-1"> <div key={index} className="flex items-center gap-0.5">
<KeyBadge keyText={key} /> <KeyBadge keyText={key} />
{index < shortcut.keys.length - 1 && ( {index < shortcut.keys.length - 1 && (
<span className="text-[var(--muted-foreground)] text-xs">+</span> <span className="text-[var(--muted-foreground)] text-xs">+</span>
@@ -37,7 +37,7 @@ function ShortcutRow({ shortcut }: { shortcut: KeyboardShortcut }) {
</div> </div>
))} ))}
</div> </div>
<span className="text-sm text-[var(--foreground)] font-mono"> <span className="text-xs text-[var(--foreground)] font-mono">
{shortcut.description} {shortcut.description}
</span> </span>
</div> </div>
@@ -61,14 +61,14 @@ export function KeyboardShortcutsModal({
}, {} as Record<string, KeyboardShortcut[]>); }, {} as Record<string, KeyboardShortcut[]>);
return ( return (
<Modal isOpen={isOpen} onClose={onClose} title={title} size="lg"> <Modal isOpen={isOpen} onClose={onClose} title={title} size="md">
<div className="space-y-6"> <div className="space-y-4">
{Object.entries(groupedShortcuts).map(([category, categoryShortcuts]) => ( {Object.entries(groupedShortcuts).map(([category, categoryShortcuts]) => (
<div key={category}> <div key={category}>
<h3 className="text-sm font-mono font-semibold text-[var(--primary)] uppercase tracking-wider mb-3"> <h3 className="text-xs font-mono font-semibold text-[var(--primary)] uppercase tracking-wider mb-2">
{category} {category}
</h3> </h3>
<div className="space-y-1"> <div className="space-y-0.5">
{categoryShortcuts.map((shortcut, index) => ( {categoryShortcuts.map((shortcut, index) => (
<ShortcutRow key={index} shortcut={shortcut} /> <ShortcutRow key={index} shortcut={shortcut} />
))} ))}
@@ -77,8 +77,8 @@ export function KeyboardShortcutsModal({
))} ))}
{shortcuts.length === 0 && ( {shortcuts.length === 0 && (
<div className="text-center py-8"> <div className="text-center py-6">
<p className="text-[var(--muted-foreground)] font-mono"> <p className="text-xs text-[var(--muted-foreground)] font-mono">
Aucun raccourci disponible sur cette page Aucun raccourci disponible sur cette page
</p> </p>
</div> </div>
@@ -86,9 +86,9 @@ export function KeyboardShortcutsModal({
</div> </div>
{/* Footer avec info */} {/* Footer avec info */}
<div className="mt-6 pt-4 border-t border-[var(--border)]/50"> <div className="mt-4 pt-3 border-t border-[var(--border)]/50">
<p className="text-xs text-[var(--muted-foreground)] font-mono text-center"> <p className="text-xs text-[var(--muted-foreground)] font-mono text-center">
Appuyez sur <KeyBadge keyText="Esc" /> pour fermer cette fenêtre <KeyBadge keyText="Esc" /> pour fermer
</p> </p>
</div> </div>
</Modal> </Modal>

View File

@@ -23,13 +23,13 @@ const PAGE_SHORTCUTS: PageShortcuts = {
category: 'Navigation' category: 'Navigation'
}, },
{ {
keys: ['Cmd', 'Ctrl', 'D'], keys: ['Shift', 'Q'],
description: 'Basculer le thème', description: 'Basculer le thème',
category: 'Apparence' category: 'Apparence'
}, },
{ {
keys: ['Cmd', 'Ctrl', 'T'], keys: ['Shift', 'W'],
description: 'Changer de thème sombre', description: 'Faire tourner les thèmes dark',
category: 'Apparence' category: 'Apparence'
}, },
{ {
@@ -42,7 +42,7 @@ const PAGE_SHORTCUTS: PageShortcuts = {
// Dashboard // Dashboard
'/': [ '/': [
{ {
keys: ['Cmd', 'Ctrl', 'K'], keys: ['Shift', 'K'],
description: 'Vers Kanban', description: 'Vers Kanban',
category: 'Navigation' category: 'Navigation'
} }
@@ -51,7 +51,7 @@ const PAGE_SHORTCUTS: PageShortcuts = {
// Kanban // Kanban
'/kanban': [ '/kanban': [
{ {
keys: ['Cmd', 'Ctrl', 'N'], keys: ['Shift', 'N'],
description: 'Créer une nouvelle tâche', description: 'Créer une nouvelle tâche',
category: 'Actions' category: 'Actions'
}, },
@@ -61,15 +61,30 @@ const PAGE_SHORTCUTS: PageShortcuts = {
category: 'Vue' category: 'Vue'
}, },
{ {
keys: ['Cmd', 'Ctrl', 'F'], keys: ['Shift', 'F'],
description: 'Ouvrir les filtres', description: 'Ouvrir les filtres',
category: 'Filtres' category: 'Filtres'
}, },
{ {
keys: ['Cmd', 'Ctrl', 'S'], keys: ['Shift', 'S'],
description: 'Basculer les swimlanes', description: 'Basculer les swimlanes',
category: 'Vue' category: 'Vue'
}, },
{
keys: ['Shift', 'O'],
description: 'Basculer les objectifs',
category: 'Vue'
},
{
keys: ['Shift', 'D'],
description: 'Filtrer par date de fin',
category: 'Filtres'
},
{
keys: ['Shift', 'Z'],
description: 'Basculer la taille de police',
category: 'Vue'
},
{ {
keys: ['Tab'], keys: ['Tab'],
description: 'Navigation entre colonnes', description: 'Navigation entre colonnes',
@@ -95,7 +110,7 @@ const PAGE_SHORTCUTS: PageShortcuts = {
category: 'Navigation' category: 'Navigation'
}, },
{ {
keys: ['Cmd', 'Ctrl', 'T'], keys: ['Shift', 'H'],
description: 'Aller à aujourd\'hui', description: 'Aller à aujourd\'hui',
category: 'Navigation' category: 'Navigation'
}, },

View File

@@ -2,12 +2,16 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { usePathname } from 'next/navigation'; import { usePathname } from 'next/navigation';
import { useTheme } from '@/contexts/ThemeContext';
interface KeyboardShortcutsActions { interface KeyboardShortcutsActions {
onCreateTask?: () => void; onCreateTask?: () => void;
onToggleFilters?: () => void; onToggleFilters?: () => void;
onToggleCompactView?: () => void; onToggleCompactView?: () => void;
onToggleSwimlanes?: () => void; onToggleSwimlanes?: () => void;
onToggleObjectives?: () => void;
onToggleDueDateFilter?: () => void;
onToggleFontSize?: () => void;
onNavigatePrevious?: () => void; onNavigatePrevious?: () => void;
onNavigateNext?: () => void; onNavigateNext?: () => void;
onGoToToday?: () => void; onGoToToday?: () => void;
@@ -21,6 +25,7 @@ interface KeyboardShortcutsActions {
export function useGlobalKeyboardShortcuts(actions: KeyboardShortcutsActions = {}) { export function useGlobalKeyboardShortcuts(actions: KeyboardShortcutsActions = {}) {
const pathname = usePathname(); const pathname = usePathname();
const { toggleTheme, cycleDarkThemes } = useTheme();
useEffect(() => { useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => { const handleKeyDown = (event: KeyboardEvent) => {
@@ -36,61 +41,91 @@ export function useGlobalKeyboardShortcuts(actions: KeyboardShortcutsActions = {
return; return;
} }
// Cmd+K - Ouvrir la recherche rapide // Raccourcis globaux (toutes les pages)
if ((event.metaKey || event.ctrlKey) && event.key === 'k') { if (event.shiftKey) {
switch (event.key) {
case 'Q':
event.preventDefault();
toggleTheme();
return;
case 'W':
event.preventDefault();
cycleDarkThemes();
return;
case 'K':
event.preventDefault(); event.preventDefault();
actions.onOpenSearch?.(); actions.onOpenSearch?.();
return; return;
} }
}
// Cmd+N - Créer une nouvelle tâche/élément // Raccourcis spécifiques par page
if ((event.metaKey || event.ctrlKey) && event.key === 'n') { switch (pathname) {
case '/kanban':
handleKanbanShortcuts(event, actions);
break;
case '/daily':
handleDailyShortcuts(event, actions);
break;
default:
handleDefaultShortcuts(event, actions);
break;
}
};
// Fonctions de gestion des raccourcis par page
const handleKanbanShortcuts = (event: KeyboardEvent, actions: KeyboardShortcutsActions) => {
if (event.shiftKey) {
switch (event.key) {
case 'N':
event.preventDefault(); event.preventDefault();
actions.onCreateTask?.(); actions.onCreateTask?.();
return; break;
} case 'F':
// Cmd+F - Ouvrir les filtres (Kanban)
if ((event.metaKey || event.ctrlKey) && event.key === 'f') {
event.preventDefault(); event.preventDefault();
actions.onToggleFilters?.(); actions.onToggleFilters?.();
return; break;
} case 'S':
// Cmd+S - Basculer les swimlanes (Kanban)
if ((event.metaKey || event.ctrlKey) && event.key === 's') {
event.preventDefault(); event.preventDefault();
actions.onToggleSwimlanes?.(); actions.onToggleSwimlanes?.();
return; break;
case 'O':
event.preventDefault();
actions.onToggleObjectives?.();
break;
case 'D':
event.preventDefault();
actions.onToggleDueDateFilter?.();
break;
case 'Z':
event.preventDefault();
actions.onToggleFontSize?.();
break;
} }
} else if (event.key === ' ') {
// Space - Basculer la vue compacte (Kanban)
if (event.key === ' ' && pathname === '/kanban') {
event.preventDefault(); event.preventDefault();
actions.onToggleCompactView?.(); actions.onToggleCompactView?.();
return;
} }
};
// Cmd+T - Aller à aujourd'hui/cette semaine const handleDailyShortcuts = (event: KeyboardEvent, actions: KeyboardShortcutsActions) => {
if ((event.metaKey || event.ctrlKey) && event.key === 't') { if (event.shiftKey && event.key === 'H') {
event.preventDefault(); event.preventDefault();
actions.onGoToToday?.(); actions.onGoToToday?.();
return;
} }
};
// Flèches gauche/droite - Navigation const handleDefaultShortcuts = (event: KeyboardEvent, actions: KeyboardShortcutsActions) => {
if (event.key === 'ArrowLeft') { switch (event.key) {
case 'ArrowLeft':
event.preventDefault(); event.preventDefault();
actions.onNavigatePrevious?.(); actions.onNavigatePrevious?.();
return; break;
} case 'ArrowRight':
if (event.key === 'ArrowRight') {
event.preventDefault(); event.preventDefault();
actions.onNavigateNext?.(); actions.onNavigateNext?.();
return; break;
} }
}; };
document.addEventListener('keydown', handleKeyDown); document.addEventListener('keydown', handleKeyDown);
@@ -98,5 +133,5 @@ export function useGlobalKeyboardShortcuts(actions: KeyboardShortcutsActions = {
return () => { return () => {
document.removeEventListener('keydown', handleKeyDown); document.removeEventListener('keydown', handleKeyDown);
}; };
}, [pathname, actions]); }, [pathname, actions, toggleTheme, cycleDarkThemes]);
} }

View File

@@ -1,30 +0,0 @@
'use client';
import { useEffect } from 'react';
import { useTheme } from '@/contexts/ThemeContext';
export function useKeyboardShortcuts() {
const { toggleTheme, cycleDarkThemes } = useTheme();
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
// Cmd + Shift + D pour basculer entre light et le thème dark préféré
if ((event.metaKey || event.ctrlKey) && event.shiftKey && event.key === 'D') {
event.preventDefault();
toggleTheme();
}
// Cmd + Shift + T pour faire tourner les thèmes dark
if ((event.metaKey || event.ctrlKey) && event.shiftKey && event.key === 'T') {
event.preventDefault();
cycleDarkThemes();
}
};
document.addEventListener('keydown', handleKeyDown);
return () => {
document.removeEventListener('keydown', handleKeyDown);
};
}, [toggleTheme, cycleDarkThemes]);
}