En otro tutorial ya creamos menu desplegable con html y css en esta ocasión aprenderás a crear pequeño menú desplegable estilo dropdown utilizando reactjs y javascript siguiendo las mejores prácticas de accesibilidad, uno de los puntos en los que fallan la mayoría de menús desplegables que se encuentran en la web.
Los dropdowns suelen estar escritos solo con css y para hacer aparecer la lista de enlaces se usa un efecto hover para este ejemplo usaremos javascript para que los elementos de la lista desplegable aparezcan cuando se haga click sobre el.
Código jsx de menú desplegable
import React, { useState, useEffect, useRef } from "react";import {dropdown_wrapper,dropdown_activator,dropdown_item_list,active,item_list,} from "./dropdown.module.css";function Dropdown({ items = [], dropdownTitle }) {const activatorRef = useRef(null);const dropdownListRef = useRef(null);const [isOpen, setIsOpen] = useState(false);const clickHandler = () => {setIsOpen(!isOpen);};const keyHandler = event => {// console.log(event);if (event.key === "Escape" && isOpen) {setIsOpen(false);}};const clickOutsideHandler = event => {if (dropdownListRef.current) {if (dropdownListRef.current.contains(event.target) ||activatorRef.current.contains(event.target)) {return;}setIsOpen(false);}};useEffect(() => {if (isOpen) {dropdownListRef.current.querySelector("a").focus();document.addEventListener("mousedown", clickOutsideHandler);} else {document.addEventListener("mousedown", clickOutsideHandler);}}, [isOpen]);return (<div className={dropdown_wrapper} onKeyUp={keyHandler}><buttonclassName={dropdown_activator}aria-haspopup="true"aria-controls={dropdownTitle}onClick={clickHandler}ref={activatorRef}>{dropdownTitle}{" "}{isOpen ? (<svgheight="24"fill="rgb(70,70,70)"viewBox="0 0 24 24"width="24"xmlns="http://www.w3.org/2000/svg"><path d="m0 0h24v24h-24z" fill="none" /><path d="m7.41 15.41 4.59-4.58 4.59 4.58 1.41-1.41-6-6-6 6z" /></svg>) : (<svgheight="24"fill="rgb(70,70,70)"viewBox="0 0 24 24"width="24"xmlns="http://www.w3.org/2000/svg"><path d="m0 0h24v24h-24z" fill="none" /><path d="m7.41 8.59 4.59 4.58 4.59-4.58 1.41 1.41-6 6-6-6z" /></svg>)}</button><ulref={dropdownListRef}className={`${dropdown_item_list} ${isOpen ? active : ""} `}>{items.map((item, index) => {return (<li className={item_list} key={index}><a href={item.slug}>{item.anchor}</a></li>);})}</ul></div>);}export default Dropdown;
Css necesario para el funcionamiento del dropdown
.dropdown_wrapper {position: relative;display: inline-block;}.dropdown_activator {align-items: center;background-color: inherit;border: none;height: 100%;color: gray;font-weight: 500;letter-spacing: 1.1px;display: flex;align-items: center;font-size: inherit;padding: 1rem 0.6rem;cursor: pointer;width: 100%;}.dropdown_activator:hover {border-bottom: 1px solid silver;border-image: linear-gradient(to right,transparent 20%,#1a1b1b,transparent 80%) 30;}.dropdown_item_list {background: white;display: none;margin: 0;z-index: 1000;position: absolute;box-shadow: 0 0 2px 0 gray;border-radius: 5px;padding: 0;}.dropdown_item_list.active {display: grid;grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));}.item_list {list-style: none;}.item_list:last-child a {border-bottom: none;}.item_list a,.item_list a:link {display: flex;/* gap: 0.8rem; */color: #666666;font-weight: 600;text-decoration: none;padding: 0.8rem;transition: all 0.1s linear;}.item_list a:hover {background-color: gray;color: white;}
Explicación paso a paso del código del dropdown
Para crear este componente necesitaremos usar los ganchos useState, useEffect y useRef así que asegurate de importarlos en tu componente.
Props necesarias para nuestro componente
function Dropdown({ items = [], dropdownTitle }) {/*Resto del código*/}
El Componente resivirá dos props:
- items: los elementos que que conforman al menú, para este caso necesitamos un array de obejetos en el cual cada obejeto debe tener los valores slug y anchor, slug hacia donde apunte el enlace y anchor será el texto del enlace .
- dropdownTitle: Esta propiedad será el texto visible en el botón que activará el desplegable
Hooks necesarios para el componente
/* ...Resto del código */const activatorRef = useRef(null);const dropdownListRef = useRef(null);const [isOpen, setIsOpen] = useState(false);/* ...Resto del código */
- activatorRef y dropdownListRef: necesitaremos acceder a los nodos del dom tanto del botón que despliega el dropdown, como de la lista de enlaces.
- También un típico estado toggle para controlar el comportamiento del desplegable, debe estar inicializado en falso ya que en un primer momento el dropdown estará cerrado.
Funciones para modificar el estado del dropdown
const clickHandler = () => {setIsOpen(!isOpen);};const keyHandler = event => {// console.log(event);if (event.key === "Escape" && isOpen) {setIsOpen(false);}};const clickOutsideHandler = event => {if (dropdownListRef.current) {if (dropdownListRef.current.contains(event.target) ||activatorRef.current.contains(event.target)) {return;}setIsOpen(false);}};
- clickHandler: esta función lo unico que hará es cambiar el estado de false a true viceversa siempre que se haga click sobre el activatorRef.
- keyHandler: Este dropdown debe funcionar sin usar el ratón para hacerlo usable y accesible para todos los usuarios, así que con esta función escuchamos el teclado y en caso de que la tecla pulsada sea Escape cambiamos el estado para cerrar el dropdown.
- clickOutsideHandler: Con esta función detectaremos cada vez que ocurra un click fuera del dropdown y en caso de haberlo echo cambiaremos el estado a falso. Por otro lado en caso que el click sea dentro de alguna de las dos referencias no se debe hacer nada por eso la primera condicional.
// Condicional para verificar que la referencia del dropdwon existeif (dropdownListRef.current) {//Condicional para ver si el click es dentro del dropdown, si es el caso no hacer nada y si no cambiar el estado a falseif (dropdownListRef.current.contains(event.target) ||activatorRef.current.contains(event.target)) {return;}setIsOpen(false);}}
Hook useEffect para detectar los clicks fuera el desplegable
useEffect(() => {if (isOpen) {dropdownListRef.current.querySelector("a").focus();document.addEventListener("mousedown", clickOutsideHandler);} else {document.addEventListener("mousedown", clickOutsideHandler);}}, [isOpen]);
- En este gancho ejecutaremos la función clickOutsideHandler sobre todo el documento usando el evento mousedown.
- En el caso que el desplegable este abierto enfocaremos el enlace dentro del dropdown para continuar con el flujo correcto de elementos clicables para los usuarios que recorren el documento con el tabulador del teclado.
- Este efecto se debe ejecutar cada vez que cambie el estado isOpen así que se lo pasamos como dependencia al useEffect.
Jsx y renderizado del dropdown
<div className={dropdown_wrapper} onKeyUp={keyHandler}></div>
Elemento contenedor le pasamos la clase dropdown_wrapperque definimos en nuestro css y en el evento onKeyUp la función keyHandler
<buttonclassName={dropdown_activator}aria-haspopup="true"aria-controls={dropdownTitle}onClick={clickHandler}ref={activatorRef}>{dropdownTitle}{" "}{isOpen ? (<svgheight="24"fill="rgb(70,70,70)"viewBox="0 0 24 24"width="24"xmlns="http://www.w3.org/2000/svg"><path d="m0 0h24v24h-24z" fill="none" /><path d="m7.41 15.41 4.59-4.58 4.59 4.58 1.41-1.41-6-6-6 6z" /></svg>) : (<svgheight="24"fill="rgb(70,70,70)"viewBox="0 0 24 24"width="24"xmlns="http://www.w3.org/2000/svg"><path d="m0 0h24v24h-24z" fill="none" /><path d="m7.41 8.59 4.59 4.58 4.59-4.58 1.41 1.41-6 6-6-6z" /></svg>)}</button>
Al botón que actuará comoactivador le pasamos la clase dropdown_activator, ejecutamos la función clickHandler en el evento Onclick y le pasamos lareferencia activatorRef previamente definida con el gancho useRef.
Aria es un conjunto de atributos que definen formas de hacer que el contenido web sea más accesible para usuarios con discapacidades aprende más en este enlace
Dentro del botón renderizamos el dropdownTitle y dependiendo del estado isOpen mostramos un svg con una flecha para arriba o para abajo
<ulref={dropdownListRef}className={`${dropdown_item_list} ${isOpen ? active : ""} `}>{items.map((item, index) => {return (<li className={item_list} key={index}><a href={item.slug}>{item.anchor}</a></li>);})}</ul>
Este elemento renderizará nuestra lista de enlaces así que le pasamos la referencia dropdownListRef previamente definida.
Agregamos la clase dropdown_item_list para darle algo de estilos y también usamos un operador ternario para agregar la clase active dependiendo si el estado isOpen es verdadero o falso. La clase active es la encargada de hacer que la lista se muestre o se oculte.
Por último recorremos el arreglo items y renderizamos un li con un enlace en el que el atributo href es item.slug y el texto del enlace item.anchor
// Estrutura del arreglo itemsconst items = [{slug: "/link1/",anchor: "Link 1",},{slug: "/link2/",anchor: "Link 2",},{slug: "/link3/",anchor: "Link 3",},];
Demostración final menu desplegable con reactjs
Soy Juneiker Castillo, un desarrollador web frontend apasionado por la programación y la creación de sitios web modernos rápidos y escalables, en fin un friki 🤓 de javascript enamorado de react js ⚛️.
Sobre mi