Cómo hacer un componente de lista con emoción
Estuve refactorizando un poco esta semana en Sentry y noté que no teníamos un componente de Lista genérico que pudiéramos usar en todos los proyectos y funciones. Entonces, comencé uno, pero aquí está el problema: diseñamos cosas en Sentry usando Emotion, con el cual solo tengo experiencia pasajera y se describe en los documentos como...
[…] una biblioteca diseñada para escribir estilos css con JavaScript. Proporciona una composición de estilo potente y predecible, además de una excelente experiencia para el desarrollador con funciones como mapas de origen, etiquetas y utilidades de prueba. Se admiten estilos de cadena y de objeto.
Si nunca has oído hablar de Emotion, la idea general es la siguiente: cuando trabajamos en grandes bases de código con muchos componentes, queremos asegurarnos de que podemos controlar la cascada de nuestro CSS. Entonces, digamos que tiene una .active
clase en un archivo y quiere asegurarse de que no afecte los estilos de un componente completamente separado en otro archivo que también tiene una clase .active
.
Emotion aborda este problema agregando cadenas personalizadas a los nombres de sus clases para que no entren en conflicto con otros componentes. Aquí hay un ejemplo del HTML que podría generar:
div/div
Bastante bonito, ¿eh? Sin embargo, existen muchas otras herramientas y flujos de trabajo que hacen algo muy similar, como los módulos CSS.
Para comenzar a crear el componente, primero debemos instalar Emotion en nuestro proyecto. No voy a explicar esas cosas porque serán diferentes dependiendo de su entorno y configuración. Pero una vez que esté completo, podemos seguir adelante y crear un nuevo componente como este:
import React from 'react';import styled from '@emotion/styled';export const List = styled('ul')` list-style: none; padding: 0;`;
Esto me parece bastante extraño porque no solo estamos escribiendo estilos para el ul
elemento, sino que también estamos definiendo que el componente debe representar un ul
. Combinar tanto el marcado como los estilos en un solo lugar parece extraño, pero me gusta lo simple que es. Simplemente interfiere con mi modelo mental y la separación de preocupaciones entre HTML, CSS y JavaScript.
En otro componente, podemos importar esto List
y usarlo así:
import List from 'components/list';ListThis is a list item./List
Los estilos que agregamos a nuestro componente de lista se convertirán en un nombre de clase, como .oefioaueg
y luego se agregarán al ul
elemento que definimos en el componente.
¡Pero aún no hemos terminado! Con el diseño de la lista, necesitaba poder renderizar a ul
y an ol
con el mismo componente. También necesitaba una versión que me permitiera colocar un ícono dentro de cada elemento de la lista. Así como esto:
Lo bueno (y también un poco extraño) de Emotion es que podemos usar el as
atributo para seleccionar qué elemento HTML nos gustaría representar cuando importemos nuestro componente. Podemos usar este atributo para crear nuestra ol
variante sin tener que crear una type
propiedad personalizada o algo así. Y resulta que se parece a esto:
ListThis will render a ul./ListList as="ol"This will render an ol./List
Eso no sólo es extraño para mí, ¿verdad? Sin embargo, es muy interesante porque significa que no tenemos que aplicar ninguna lógica extraña en el componente en sí solo para cambiar el marcado.
Fue en este punto que comencé a anotar cómo podría ser la API perfecta para este componente porque entonces podemos trabajar desde allí. Esto es lo que me imaginaba:
List ListItemItem 1/ListItem ListItemItem 2/ListItem ListItemItem 3/ListItem/ListList ListItem icon={IconBusiness color="orange400" size="sm" /}Item 1/ListItem ListItem icon={IconBusiness color="orange400" size="sm" /}Item 2/ListItem ListItem icon={IconBusiness color="orange400" size="sm" /}Item 3/ListItem/ListList as="ol" ListItemItem 1/ListItem ListItemItem 2/ListItem ListItemItem 3/ListItem/List
Entonces, después de hacer este boceto, supe que necesitaríamos dos componentes, junto con la capacidad de anidar subcomponentes de íconos dentro del archivo ListItem
. Podemos empezar así:
import React from 'react';import styled from '@emotion/styled';export const List = styled('ul')` list-style: none; padding: 0; margin-bottom: 20px; ol { counter-reset: numberedList; }`;
Esa ol
sintaxis peculiar es la forma en que le decimos a la emoción que estos estilos solo se aplican a un elemento cuando se representa como un archivo ol
. A menudo es una buena idea simplemente agregar un background: red;
a este elemento para asegurarse de que su componente esté procesando las cosas correctamente.
El siguiente es nuestro subcomponente, el ListItem
. Es importante tener en cuenta que en Sentry también usamos TypeScript, por lo que antes de definir nuestro ListItem
componente, primero necesitaremos configurar nuestros accesorios:
type ListItemProps = { icon?: React.ReactNode; children?: string | React.ReactNode; className?: string;};
Ahora podemos agregar nuestro IconWrapper
componente que dimensionará un Icon
componente dentro del archivo ListItem
. Si recuerdas el ejemplo anterior, quería que se viera así:
List ListItem icon={IconBusiness color="orange400" size="sm" /}Item 1/ListItem ListItem icon={IconBusiness color="orange400" size="sm" /}Item 2/ListItem ListItem icon={IconBusiness color="orange400" size="sm" /}Item 3/ListItem/List
Ese IconBusiness
componente es un componente preexistente y queremos envolverlo en un lapso para poder diseñarlo. Afortunadamente, necesitaremos solo un poquito de CSS para alinear el ícono correctamente con el texto y podremos IconWrapper
manejar todo eso por nosotros:
type ListItemProps = { icon?: React.ReactNode; children?: string | React.ReactNode; className?: string;};const IconWrapper = styled('span')` display: flex; margin-right: 15px; height: 16px; align-items: center;`;
Una vez que hayamos hecho esto finalmente podemos agregar nuestro ListItem
componente debajo de estos dos, aunque es considerablemente más complejo. Necesitaremos agregar los accesorios, luego podremos renderizar lo IconWrapper
anterior cuando el icon
accesorio exista y también renderizar el componente de icono que se le pasó. También agregué todos los estilos a continuación para que puedas ver cómo le doy estilo a cada una de estas variantes:
export const ListItem = styled(({icon, className, children}: ListItemProps) = ( li className={className} {icon ( IconWrapper {icon} /IconWrapper )} {children} /li))ListItemProps` display: flex; align-items: center; position: relative; padding-left: 34px; margin-bottom: 20px; /* Tiny circle and icon positioning */ :before, ${IconWrapper} { position: absolute; left: 0; } ul { color: #aaa; /* This pseudo is the tiny circle for ul items */ :before { content: ''; width: 6px; height: 6px; border-radius: 50%; margin-right: 15px; border: 1px solid #aaa; background-color: transparent; left: 5px; top: 10px; } /* Icon styles */ ${p = p.icon ` span { top: 4px; } /* Removes tiny circle pseudo if icon is present */ :before { content: none; } `} } /* When the list is rendered as an ol */ ol { :before { counter-increment: numberedList; content: counter(numberedList); top: 3px; display: flex; align-items: center; justify-content: center; text-align: center; width: 18px; height: 18px; font-size: 10px; font-weight: 600; border: 1px solid #aaa; border-radius: 50%; background-color: transparent; margin-right: 20px; } }`;
¡Y ahí lo tienes! Un componente relativamente simple List
construido con Emotion. Aunque, después de realizar este ejercicio, todavía no estoy seguro de que me guste la sintaxis. Creo que hace que las cosas simples sean realmente simples, pero los componentes de tamaño mediano son mucho más complicados de lo que deberían ser. Además, podría resultar bastante confuso para un recién llegado y eso me preocupa un poco.
Pero supongo que todo es una experiencia de aprendizaje. De cualquier manera, me alegro de haber tenido la oportunidad de trabajar en este pequeño componente porque me enseñó algunas cosas buenas sobre TypeScript, React y cómo tratar de hacer que nuestros estilos sean algo legibles.
Deja un comentario