Si vous êtes comme moi et que vous adorez les raccourcis, vous savez à quel point il est satisfaisant d’appuyer sur quelques touches et de voir la magie opérer. Que ce soit le célèbre Ctrl+C – Ctrl+V que les développeurs utilisent pour « emprunter du code » 😉 des LLM et des pages de code, ou les raccourcis personnalisés que nous mettons en place dans nos outils préférés, les raccourcis clavier font gagner du temps et nous font sentir comme des experts en informatique.
Eh bien, n’ayez crainte ! J’ai déchiffré le code pour créer des composants qui déclenchent et répondent aux raccourcis clavier. Dans cet article, je vais vous apprendre à les créer avec React, Tailwind CSS et Framer Motion.
Table des matières
Voici tout ce que nous allons aborder :
Prérequis
-
Fondamentaux de HTML, CSS et Tailwind CSS
-
Fondamentaux de JavaScript, React et React Hooks.
Qu’est-ce qu’un composant d’écouteur de raccourci clavier (KSL) ?
Un composant d’écouteur de raccourcis clavier (KSLC) est un composant qui écoute des combinaisons de touches spécifiques et déclenche des actions dans votre application. Il est conçu pour faire en sorte que votre application réagisse aux raccourcis clavier, permettant ainsi une expérience utilisateur plus fluide et plus efficace.
Pourquoi est-ce important ?
-
Accessibilité : Le composant KSL permet aux personnes utilisant un clavier de déclencher des actions facilement, rendant votre application plus inclusive et facile à utiliser.
-
Expérience plus réactive : Les raccourcis sont rapides et efficaces, permettant aux utilisateurs d’accomplir des tâches en moins de temps. Plus besoin de chercher la souris—il suffit d’appuyer sur une touche (ou deux) et hop, l’action se déclenche !
-
Réutilisabilité : Une fois que vous avez configuré votre KSL, il peut gérer différents raccourcis dans votre application, ce qui facilite leur ajout sans avoir à réécrire la même logique.
-
Code plus propre: Au lieu de disperser les écouteurs d’événements clavier partout, le composant KSL garde les choses en ordre en centralisant la logique. Votre code reste propre, organisé et plus facile à maintenir.
Comment construire le composant KSL
J’ai préparé un dépôt GitHub avec les fichiers de démarrage pour accélérer les choses. Il suffit de cloner ce dépôt et d’installer les dépendances.
Pour ce projet, nous utilisons la page d’accueil de Tailwind comme source d’inspiration et nous créons la fonctionnalité KSL. Après avoir installé et exécuté la commande de construction, voici à quoi devrait ressembler votre page :
Comment créer le composant Révéler
Le composant révéler est le composant que nous voulons afficher lorsque nous utilisons le raccourci.
Pour commencer, créez un fichier appelé search-box.tsx
et collez-y ce code :
export default function SearchBox() {
return (
<div className="fixed top-0 left-0 w-full h-full backdrop-blur-sm bg-slate-900/50 ">
{" "}
<div className=" p-[15vh] text-[#939AA7] h-full">
<div className="max-w-xl mx-auto divide-y divide-[#939AA7] bg-[#1e293b] rounded-md">
<div className="relative flex justify-between px-4 py-2 text-sm ">
<div className="flex items-center w-full gap-2 text-white">
<BiSearch size={20} />
<input
type="text"
className="w-full h-full p-2 bg-transparent focus-within:outline-none"
placeholder="Search Documentation"
/>
</div>
<div className="absolute -translate-y-1/2 right-4 top-1/2 ">
<kbd className="p-1 text-xs rounded-[4px] bg-[#475569] font-sans font-semibold text-slate-400">
<abbr title="Escape" className="no-underline ">
Esc{" "}
</abbr>{" "}
</kbd>
</div>
</div>
<div className="flex items-center justify-center p-10 text-center ">
<h2 className="text-xl">
How many licks does it take to get to the center of a Tootsie pop?
</h2>
</div>
</div>
</div>
</div>
);
}
Alors, que se passe-t-il dans ce code ?
-
Superposition principale (
<div className="fixed top-0 left-0 ...">
)-
Il s’agit de la superposition plein écran qui assombrit l’arrière-plan.
-
La classe
backdrop-blur-sm
ajoute un léger flou à l’arrière-plan, etbg-slate-900/50
lui donne une superposition sombre semi-transparente.
-
-
Wrapper de la zone de recherche (
<div className="p-[15vh] ...">
)-
Le contenu est centré en utilisant des utilitaires de rembourrage et de flexibilité.
-
Le
max-w-xl
s’assure que la zone de recherche reste dans une largeur raisonnable pour la lisibilité.
-
Ensuite dans votre App.tsx
, créez un état qui affiche dynamiquement ce composant:
const [isOpen, setIsOpen] = useState<boolean>(false);
-
useState
: Ce crochet initialiseisOpen
àfalse
, ce qui signifie que la zone de recherche est initialement masquée. -
Lorsque
isOpen
est défini surtrue
, le composantSearchBox
sera rendu à l’écran.
Et rendre le composant de recherche:
{isOpen && <SearchBox />}
Pour afficher le composant de recherche, ajoutez une fonction de basculement au bouton d’entrée:
<button
type="button"
className="items-center hidden h-12 px-4 space-x-3 text-left rounded-lg shadow-sm sm:flex w-72 ring-slate-900/10 focus:outline-none hover:ring-2 hover:ring-sky-500 focus:ring-2 focus:ring-sky-500 bg-slate-800 ring-0 text-slate-300 highlight-white/5 hover:bg-slate-700"
onClick={() => setIsOpen(true)}>
<BiSearch size={20} />
<span className="flex-auto">Quick search...</span>
<kbd className="font-sans font-semibold text-slate-500">
<abbr title="Control" className="no-underline text-slate-500">
Ctrl{" "}
</abbr>{" "}
K
</kbd>
</button>
L’événement onClick
définit isOpen
sur true
, affichant le SearchBox
.
Mais comme vous l’avez vu, cela a été déclenché par un clic, et non par une action de raccourci clavier. Faisons cela ensuite.
Comment Déclencher le Composant via un Raccourci Clavier
Pour faire en sorte que le composant de révélation s’ouvre et se ferme en utilisant un raccourci clavier, nous utiliserons un hook useEffect
pour écouter des combinaisons de touches spécifiques et mettre à jour l’état du composant en conséquence.
Étape 1: Écouter les Événements Clavier
Ajoutez un hook useEffect
dans votre fichier App.tsx
pour écouter les pressions de touches:
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (event.ctrlKey && event.key === Key.K) {
event.preventDefault(); // Empêcher le comportement par défaut du navigateur
} };
window.addEventListener("keydown", handleKeyDown);
return () => {
window.removeEventListener("keydown", handleKeyDown);
};
}, []);
Que se passe-t-il dans ce code?
-
Mise en Place de l’Effet (
useEffect
)useEffect
garantit que l’écouteur d’événements pour les pressions de touches est ajouté lorsque le composant est monté et nettoyé lorsque le composant est démonté, évitant les fuites de mémoire.
-
Combinaison de touches (
event.ctrlKey && event.key === "k"
)-
Le
event.ctrlKey
vérifie si la touche Control est enfoncée. -
Le
event.key === "k"
assure que nous écoutons spécifiquement la touche « K ». Ensemble, cela vérifie si la combinaison Ctrl + K est pressée.
-
-
Empêcher le comportement par défaut (
event.preventDefault()
)- Certains navigateurs peuvent avoir des comportements par défaut liés aux combinaisons de touches comme Ctrl + K (par exemple, mettre le focus sur la barre d’adresse du navigateur). Appeler
preventDefault
arrête ce comportement.
- Certains navigateurs peuvent avoir des comportements par défaut liés aux combinaisons de touches comme Ctrl + K (par exemple, mettre le focus sur la barre d’adresse du navigateur). Appeler
-
Nettoyage des Événements (
return () => ...
)- La fonction de nettoyage supprime l’écouteur d’événements pour éviter que des écouteurs en double ne soient ajoutés si le composant se re-rend.
Étape 2 : Basculer la Visibilité du Composant
Ensuite, mettez à jour la fonction handleKeyDown
pour basculer la visibilité de SearchBox
lorsque le raccourci est pressé :
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
// Écouter Ctrl + K
if (event.ctrlKey && event.key === Key.K) {
event.preventDefault(); // Prévenir le comportement par défaut du navigateur
setIsOpen((prev) => !prev); // Basculer la boîte de recherche
} else if (event.key === Key.Escape) {
setIsOpen(false); // Fermer la boîte de recherche
}
};
window.addEventListener("keydown", handleKeyDown);
return () => {
window.removeEventListener("keydown", handleKeyDown);
};
}, []);
Que se passe-t-il dans ce code ?
-
Changement d’état (
setIsOpen((prev) => !prev)
)-
Lorsque Ctrl + K est pressé, le setter d’état
setIsOpen
change la visibilité de laSearchBox
. -
L’argument
prev
représente l’état précédent. L’utilisation de!prev
inverse sa valeur :-
true
(ouvert) devientfalse
(fermé). -
false
(fermé) devienttrue
(ouvert).
-
-
-
Fermeture avec la touche Échap (
event.key === "Escape"
)- Lorsque la touche Échap est pressée,
setIsOpen(false)
définit explicitement l’état surfalse
, fermant leSearchBox
.
- Lorsque la touche Échap est pressée,
Cela donne le résultat suivant :
Comment animer la visibilité du composant
Pour l’instant, notre composant fonctionne, mais il manque un peu de charme, n’est-ce pas ? Changeons cela.
Étape 1 : Créer le composant de superposition
Nous allons commencer par créer un composant de superposition, qui sert de fond sombre et flou pour la zone de recherche. Voici la version de base :
import { ReactNode } from "react";
export default function OverlayWrapper({ children }: { children: ReactNode }) {
return (
<div
className="fixed top-0 left-0 w-full h-full backdrop-blur-sm bg-slate-900/50 ">
{children}
</div>
);
}
Étape 2 : Ajouter des animations à la superposition
Maintenant, faisons en sorte que la superposition apparaisse et disparaisse en utilisant Framer Motion. Mettez à jour le composant OverlayWrapper
comme ceci :
import { motion } from "framer-motion";
import { ReactNode } from "react";
export default function OverlayWrapper({ children }: { children: ReactNode }) {
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed top-0 left-0 w-full h-full backdrop-blur-sm bg-slate-900/50 ">
{children}
</motion.div>
);
}
Propriétés d’animation clés :
-
initial
: Définit l’état de départ lorsque le composant est monté (entièrement transparent). -
animate
: Définit l’état vers lequel animer (entièrement opaque). -
exit
: Spécifie l’animation lorsque le composant est démonté (fondu en sortie).
Étape 3 : Animer la boîte de recherche
Ensuite, ajoutez un peu de mouvement à la boîte de recherche elle-même. Nous allons la faire glisser et apparaître en fondu lorsqu’elle apparaît et glisser hors de vue lorsqu’elle disparaît.
import { motion } from "framer-motion";
import { BiSearch } from "react-icons/bi";
import OverlayWrapper from "./overlay";
export default function SearchBox() {
return (
<OverlayWrapper>
<motion.div
initial={{ y: "-10%", opacity: 0 }}
animate={{ y: "0%", opacity: 1 }}
exit={{ y: "-5%", opacity: 0 }}
className=" p-[15vh] text-[#939AA7] h-full">
<div
className="max-w-xl mx-auto divide-y divide-[#939AA7] bg-[#1e293b] rounded-md"
>
<div className="relative flex justify-between px-4 py-2 text-sm ">
<div className="flex items-center w-full gap-2 text-white">
<BiSearch size={20} />
<input
type="text"
className="w-full h-full p-2 bg-transparent focus-within:outline-none"
placeholder="Search Documentation"
/>
</div>
<div className="absolute -translate-y-1/2 right-4 top-1/2 ">
<kbd className="p-1 text-xs rounded-[4px] bg-[#475569] font-sans font-semibold text-slate-400">
<abbr title="Escape" className="no-underline ">
Esc{" "}
</abbr>{" "}
</kbd>
</div>
</div>
<div className="flex items-center justify-center p-10 text-center ">
<h2 className="text-xl">
How many licks does it take to get to the center of a Tootsie pop?
</h2>
</div>
</div>
</motion.div>
</OverlayWrapper>
);
}
Étape 4 : Activer le suivi d’animation avec AnimatePresence
Enfin, enveloppez votre logique de rendu conditionnel dans le composant AnimatePresence
fourni par Framer Motion. Cela garantit que Framer Motion suit quand les éléments entrent et sortent du DOM.
<AnimatePresence>{isOpen && <SearchBox />}</AnimatePresence>
Cela permet à Framer Motion de suivre quand un élément entre et sort du DOM. Avec cela, nous obtenons le résultat suivant :
Ah, beaucoup mieux !
Comment optimiser votre composant KSL
Si vous pensiez que nous avions terminé, pas si vite… Nous avons encore un peu plus à faire.
Nous devons optimiser pour l’accessibilité. Nous devrions ajouter un moyen pour les utilisateurs de fermer le composant de recherche avec une souris, car l’accessibilité est très importante.
Pour ce faire, commencez par créer un hook appelé useClickOutside
. Ce hook utilise un élément de référence pour savoir quand un utilisateur clique en dehors de l’élément cible (boîte de recherche), ce qui est un comportement très populaire pour fermer des modaux et des KSLC.
import { useEffect } from "react";
type ClickOutsideHandler = (event: Event) => void;
export const useClickOutside = (
ref: React.RefObject<HTMLElement>,
handler: ClickOutsideHandler
) => {
useEffect(() => {
const listener = (event: Event) => {
// Ne rien faire si l'on clique sur l'élément de référence ou sur ses éléments descendants
if (!ref.current || ref.current.contains(event.target as Node)) return;
handler(event);
};
document.addEventListener("mousedown", listener);
document.addEventListener("touchstart", listener);
return () => {
document.removeEventListener("mousedown", listener);
document.removeEventListener("touchstart", listener);
};
}, [ref, handler]);
};
Pour utiliser ce hook, passez la fonction responsable de l’ouverture et de la fermeture du composant de recherche :
<AnimatePresence> {isOpen && <SearchBox close={setIsOpen} />} </AnimatePresence>
Ensuite, recevez la fonction dans la recherche avec son type de prop approprié :
export default function SearchBox({
close,
}: {
close: React.Dispatch<React.SetStateAction<boolean>>;
}) {
Après cela, créez une référence (ref) à l’élément que vous souhaitez suivre et marquez cet élément :
import { motion } from "framer-motion";
import { useRef } from "react";
import { BiSearch } from "react-icons/bi";
import { useClickOutside } from "../hooks/useClickOutside";
import OverlayWrapper from "./overlay";
export default function SearchBox({
close,
}: {
close: React.Dispatch<React.SetStateAction<boolean>>;
}) {
const searchboxRef = useRef<HTMLDivElement>(null);
return (
<OverlayWrapper>
<motion.div
initial={{ y: "-10%", opacity: 0 }}
animate={{ y: "0%", opacity: 1 }}
exit={{ y: "-5%", opacity: 0 }}
className=" p-[15vh] text-[#939AA7] h-full">
<div
className="max-w-xl mx-auto divide-y divide-[#939AA7] bg-[#1e293b] rounded-md"
ref={searchboxRef}>
<div className="relative flex justify-between px-4 py-2 text-sm ">
<div className="flex items-center w-full gap-2 text-white">
<BiSearch size={20} />
<input
type="text"
className="w-full h-full p-2 bg-transparent focus-within:outline-none"
placeholder="Search Documentation"
/>
</div>
<div className="absolute -translate-y-1/2 right-4 top-1/2 ">
<kbd className="p-1 text-xs rounded-[4px] bg-[#475569] font-sans font-semibold text-slate-400">
<abbr title="Escape" className="no-underline ">
Esc{" "}
</abbr>{" "}
</kbd>
</div>
</div>
<div className="flex items-center justify-center p-10 text-center ">
<h2 className="text-xl">
How many licks does it take to get to the center of a Tootsie pop?
</h2>
</div>
</div>
</motion.div>
</OverlayWrapper>
);
}
Ensuite, passez cette ref et la fonction à appeler lorsque un clic en dehors de cet élément est détecté.
useClickOutside(searchboxRef, () => close(false));
Tester maintenant donne le résultat suivant :
Nous pouvons également optimiser le code un peu plus. Comme nous l’avons fait avec la fonctionnalité d’accessibilité, nous pouvons rendre notre écouteur pour détecter les raccourcis beaucoup plus propre et efficace avec les étapes suivantes.
Tout d’abord, créez un fichier de hook useKeyBindings
pour gérer les combinaisons de touches.
Ensuite, définissez le hook et l’interface. Le hook acceptera un tableau de liaisons, où chaque liaison se compose de :
-
Un tableau
keys
, qui spécifie la combinaison de touches (par exemple, [« Control », « k »]) -
Une fonction de rappel, qui est appelée lorsque les touches correspondantes sont enfoncées.
import { useEffect } from "react";
// Définir la structure d'une liaison de touches
interface KeyBinding {
keys: string[]; // Tableau de touches (par exemple, ["Control", "k"])
callback: () => void; // Fonction à exécuter lorsque les touches sont enfoncées
}
export const useKeyBindings = (bindings: KeyBinding[]) => {
};
Ensuite, créez la fonction handleKeyDown
. À l’intérieur du hook, définissez une fonction qui écoutera les événements du clavier. Cette fonction vérifiera si les touches enfoncées correspondent à des combinaisons de touches définies.
Nous allons normaliser les touches en minuscules afin que la comparaison soit insensible à la casse et suivre quelles touches sont enfoncées en vérifiant ctrlKey
, shiftKey
, altKey
, metaKey
, et la touche enfoncée (par exemple, « k » pour Ctrl + K).
const handleKeyDown = (event: KeyboardEvent) => {
// Suivre les touches qui sont enfoncées
const pressedKeys = new Set<string>();
// Vérifier les touches de modification (Ctrl, Shift, Alt, Meta)
if (event.ctrlKey) pressedKeys.add("control");
if (event.shiftKey) pressedKeys.add("shift");
if (event.altKey) pressedKeys.add("alt");
if (event.metaKey) pressedKeys.add("meta");
// Ajouter la touche qui a été enfoncée (par exemple, "k" pour Ctrl + K)
if (event.key) pressedKeys.add(event.key.toLowerCase());
};
Ensuite, nous comparerons les touches enfoncées avec le tableau de touches de nos liaisons pour vérifier si elles correspondent. Si c’est le cas, nous appellerons la fonction de rappel associée. Nous veillons également à ce que le nombre de touches enfoncées corresponde au nombre de touches définies dans la liaison.
// Parcourir chaque liaison de touches
bindings.forEach(({ keys, callback }) => {
// Normaliser les touches en minuscules pour la comparaison
const normalizedKeys = keys.map((key) => key.toLowerCase());
// Vérifier si les touches enfoncées correspondent à la liaison de touches
const isMatch =
pressedKeys.size === normalizedKeys.length &&
normalizedKeys.every((key) => pressedKeys.has(key));
// Si les touches correspondent, appeler le rappel
if (isMatch) {
event.preventDefault(); // Empêcher le comportement par défaut du navigateur
callback(); // Exécuter la fonction de rappel
}
});
Enfin, configurez des écouteurs d’événements sur l’objet window pour écouter les événements de type keydown. Ces écouteurs déclencheront la fonction handleKeyDown
à chaque fois qu’une touche est pressée. Assurez-vous d’ajouter la suppression des écouteurs d’événements lorsque le composant est démonté.
useEffect(() => {
// Ajouter des écouteurs d'événements pour keydown
window.addEventListener("keydown", handleKeyDown);
// Nettoyer les écouteurs d'événements lorsque le composant est démonté
return () => {
window.removeEventListener("keydown", handleKeyDown);
};
}, [bindings]);
Le hook complet useKeyBindings
mis ensemble ressemble maintenant à ceci :
import { useEffect } from "react";
interface KeyBinding {
keys: string[]; // Une combinaison de touches pour déclencher le rappel (par exemple, ["Contrôle", "k"])
callback: () => void; // La fonction à exécuter lorsque les touches sont pressées
}
export function useKeyBindings(bindings: KeyBinding[]) {
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
bindings.forEach(({ keys, callback }) => {
const normalizedKeys = keys.map((key) => key.toLowerCase());
const pressedKeys = new Set<string>();
// Suivre explicitement les touches de modification
if (event.ctrlKey) pressedKeys.add("control");
if (event.shiftKey) pressedKeys.add("shift");
if (event.altKey) pressedKeys.add("alt");
if (event.metaKey) pressedKeys.add("meta");
// Ajouter la touche réellement pressée
if (event.key) pressedKeys.add(event.key.toLowerCase());
// Correspondance exacte : les touches pressées doivent correspondre aux touches définies
const isExactMatch =
pressedKeys.size === normalizedKeys.length &&
normalizedKeys.every((key) => pressedKeys.has(key));
if (isExactMatch) {
event.preventDefault(); // Empêcher le comportement par défaut
callback(); // Exécuter le rappel
}
});
};
window.addEventListener("keydown", handleKeyDown);
return () => {
window.removeEventListener("keydown", handleKeyDown);
};
}, [bindings]);
}
Voici comment vous pouvez utiliser ce hook dans votre App
:
import { useKeyBindings } from "./hooks/useKeyBindings";
export default function App() {
const [isOpen, setIsOpen] = useState<boolean>(false);
useKeyBindings([
{
keys: ["Control", "k"], // Écouter "Ctrl + K"
callback: () => setIsOpen((prev) => !prev), // Basculer la boîte de recherche
},
{
keys: ["Escape"], // Écouter "Échap"
callback: () => setIsOpen(false), // Fermer la boîte de recherche
},
]);
Ce qui donne le résultat suivant :
Avec cette approche, vous pouvez même ajouter plusieurs raccourcis pour déclencher la visibilité du composant de recherche.
useKeyBindings([
{
keys: ["Control", "k"], // Écoutez "Ctrl + K"
callback: () => setIsOpen((prev) => !prev), // Alternez la boîte de recherche
},
{
keys: ["Control", "d"], // Écoutez "Ctrl + D"
callback: () => setIsOpen((prev) => !prev), // Alternez la boîte de recherche
},
{
keys: ["Escape"], // Écoutez "Échap"
callback: () => setIsOpen(false), // Fermez la boîte de recherche
},
]);
Voici des liens vers toutes les ressources dont vous pourriez avoir besoin pour cet article :
Conclusion
J’espère que cet article vous a semblé être un raccourci bien chronométré, vous amenant au cœur de la création de composants de raccourcis clavier réutilisables. À chaque pression de touche et animation, vous pouvez maintenant transformer des expériences web ordinaires en expériences extraordinaires.
J’espère que vos raccourcis vous aideront à créer des applications qui plaisent à vos utilisateurs. Après tout, les meilleurs voyages commencent souvent par la bonne combinaison.
Aimez-vous mes articles ?
N’hésitez pas à m’acheter un café ici, pour garder mon esprit en marche et fournir plus d’articles comme celui-ci.
Informations de contact
Vous souhaitez vous connecter ou me contacter ? N’hésitez pas à me contacter sur les plateformes suivantes :
-
Twitter / X : @jajadavid8
-
LinkedIn : David Jaja
-
Email : [email protected]