Reto: icon component

21/23

Lectura

Reto: icon component

Por ahora los íconos de nuestra aplicación solo son etiquetas span con algún carácter representando la acción que conseguirán los usuarios al presionarlos. No está mal, funciona. Pero queremos reutilizar estos íconos fuera del componente TodoItem.

Por eso el reto de esta clase es que crees tu propio componente TodoIcon, las reglas son:

Te debe permitir elegir qué tipo de ícono quieres (marcar como completado, borrar o incluso algunos más que podamos necesitar).
Cada ícono también debe poder tener estados (cambios al color o alguna otra propiedad del ícono para darle feedback a los usuarios de que realizaron alguna acción, como dar click o pasar el mouse por encima).
Es válido que uses varios componentes en vez de solo uno para definir la lógica de tus íconos.

Haz tu mayor esfuerzo por completar el reto y publicar tu solución en los comentarios. Luego de eso puedes ver mi propuesta de solución.

Solución:

La ventaja de usar letras para simular el comportamiento de nuestros íconos es que podemos cambiarles el color con CSS extremadamente fácil. La desventaja es que no son muy “estéticas”.

La ventaja de usar imágenes es que podemos tener la versión más estética de cada ícono. La desventaja es que no podemos cambiarles el color con CSS, necesitamos a fuerzas otra imagen con el nuevo color.

Afortunadamente, tenemos una tercera alternativa con las ventajas de ambos mundos: svg.

Las imágenes en SVG son diseñadas por la persona encargada del diseño, pero al exportarlas en formato SVG tenemos la imagen “maquetada” con etiquetas HTML que podemos modificar con CSS.

Su única desventaja es la complejidad de su implementación. Pero somos hijas e hijos de Platzi, no vinimos para lo fácil, sino para nunca parar de aprender. ¡Así que a la carga!

Empecemos creando un componente TodoIcon en su propia carpeta y con sus respectivos archivos de JavaScript y CSS:

carbon (8).png

Ahora debemos pensar qué propiedades pueden necesitar nuestros íconos, yo elegí las siguientes:

type: para seleccionar qué ícono svg vamos a mostrar.
color: para seleccionar el color de nuestro ícono svg, por defecto será gray.
onClick: para invocar alguna función cuando le demos click al contenedor de nuestro ícono.

Recuerda que los usuarios no siempre dan click o presionan la pantalla de sus celulares con completa precisión, por lo que es muy buena idea crear un contenedor invisible alrededor de nuestros íconos.

💡 Material Design tiene una excelente guía sobre diseño de íconos, te recomiendo estudiarla para descubrir más detalles interesantes.

Yo decidí utilizar una etiqueta span para el contenedor de los íconos. Así que este span recibirá la propiedad onClick, mientras que el SVG (que ya en un momento vamos a crear) recibirá las otras dos propiedades.

También utilizaré la prop type para darle clases personalizadas a cada contenedor de mis íconos.

carbon (9).png

¡Contenedor listo!

Ahora debemos encargarnos de los SVG. Está perfecto si quieres hacerlos a mano con herramientas como Figma o Illustrator.

En mi caso simplemente usaré alguna herramienta de íconos como Flaticon y descargaré los íconos que vea conveniente en la misma carpeta TodoIcon. De esta forma podremos importarlos como componentes de React gracias a la configuración por defecto que nos ofrece Create React App.

Te recomiendo importar tus íconos SVG desde tus componentes en React de esta forma:

carbon (10).png

Que luego podremos llamar así:

carbon (11).png

Yo usaré dos íconos (uno de hecho y otro de delete):

carbon (12).png

Lo que nos falta es determinar cuál SVG mostrar dependiendo de la propiedad type que nos envíe el componente que llame a nuestro TodoIcon.

Hay muchas formas de lograr esto, mi forma favorita es crear un objeto con todos los íconos que nuestro componente puede mostrar:

carbon (13).png

Y luego dentro de nuestro componente TodoIconllamamos al SVG que esté dentro de la propiedad con el mismo nombre que recibamos en la prop type:

carbon (14).png

Así automáticamente mostraremos el icono CheckSVG cuando la prop type tenga el valor check o el icono DeleteSVG cuando tenga el valor delete.

Aunque hay un superpoder más que podemos usar. Si envolvemos nuestros íconos dentro de funciones vamos a poder enviarles propiedades que cambien su presentación o comportamiento.

En este caso voy a usar esta funcionalidad para enviarle la prop color a nuestros SVG (y así cambiarles su color cuando los componentes que llamen a nuestros íconos así lo requieran).

carbon (15).png

El último paso para que nuestros componentes funcionen sería agregar nuestro código CSS al nuevo archivo de estilos TodoIcon.css (y de paso borrar el código que ya no necesitamos en TodoItem.css):

carbon (16).png

¡Excelente! Nuestro componente TodoIcon ya funciona y podemos llamarlo desde el componente TodoItem para darle sus íconos mucho mejor configurados.

Aunque antes quiero hacer un poco más de composición.

Cada vez que llamamos a nuestro ícono debemos enviar varias props. Pero ¿qué tal si crearamos un componente que envíe todas esas props por nosotros?

Lo que voy a hacer es crear dos nuevos componentes, CompleteIcon y DeleteIcon. Cada uno llamará al componente TodoIcon con sus respectivas props necesarias. Y luego podremos llamar mucho más fácil a estos dos nuevos componentes donde los necesitemos.

carbon (17).png

Y estos componentes ahora sí los llamaremos desde TodoItem:

carbon (18).png

Así seguiría construyendo mi componente TodoIcon con todos los íconos que vaya requiriendo nuestra aplicación TODO Machine.

¡Te espero en la siguiente clase para desplegar nuestra aplicación a GitHub Pages!

👉 Aquí puedes encontrar el repositorio de este reto: Clase bonus 2: TodoIcon

Aportes 12

Preguntas 2

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad? Crea una cuenta o inicia sesión.

Existe una librería de React donde solo tenemos que instalarla, importar el icono que queremos y ya podremos usarlo en nuestros componentes.
.

Instalación 🚀

npm install react-icons --save

Cómo usar la herramienta

En nuestro componente, importar los iconos de la siguiente manera:

import { NOMBRE_DEL_ICONO } from 'react-icons/CATEGORIA_DEL_ICONO'

Pero… de donde obtengo los nombres y categorias de los iconos 🤔. Entren a su sitio web React Icons, escoge el que mas te guste y utilizalo en la parte de tu código en donde lo necesites.
.
Ejemplo:

import { FiArrowDownCircle } from 'react-icons/fi'

Como mencionan los compañeros, react icons facilita mucho la vida
Aquí dejo el video que vi para poder usarlo, espero les sirva y les guste
https://www.youtube.com/watch?v=aor9hlcODUE&list=LL&index=3

La manera más eficiente para resolver el reto es usando React Icons https://react-icons.github.io/react-icons. Seguir la documentación es super simple y sirve de práctica ya que durante la vida laboral, pasaremos mucho tiempo investigando si hay una solución ya hecha para un problema que tengamos.

Mi solución 😁

import React from 'react';
import './TodoIcon.css';

const contentIcon = {
    IconCheck:"ᄼ",
    IconDelete:"X",
}

const claseIcon = {
    IconCheck:"Icon Icon-check",
    IconDelete:"Icon Icon-delete",
}

const TodoIcon = ({icon, clickAction, addClase}) => {
    return(
      <span  
                className={`${claseIcon[icon]} ${addClase || ""}`}
                onClick={clickAction}
            >
            {contentIcon[icon]}
            </span>
    )
}

export default TodoIcon
<TodoIcon 
            icon="IconCheck"
            clickAction={onComplete}
            addClase={completed && 'Icon-check--active'}
/>

Antes de llegar a este reto ya habia resuelto el tema de iconos dinamicos, lo realicé con Font awesome:

solo importas la herramienta poniendo esta etiqueta en el head de tu html:

<script src="https://kit.fontawesome.com/8337609809.js" crossorigin="anonymous"></script>

y con solo ponerle clases a tus elementos puedes convertirlos en un iconos de cientos su galeria gratuita:

ejemplo:

<span className={"delete-todo fas fa-trash"}

de esta manera podemos modificar su color facilmente con css y responder a acciones de los usuarios con javascript de la siguiente manera:

<span className={`${props.completed ? "fas fa-undo todo-undo": "check fas fa-check"}`}
            onClick={props.onComplete}
            >
            </span>
            <p className={`todo-description ${props.completed && "todoitem-p_completed"}`}>{props.text}</p>

Mi solución fue ocupar react-icons.
https://react-icons.github.io/react-icons

import React from 'react';
import './TodoItem.css';
import { FiCheck, FiX } from "react-icons/fi";

function TodoItem(props) {

  return (
    <li className='TodoItem'>
      <span 
      className={`Icon Icon-check ${props.completed && 'Icon-check--active'}`}
      onClick={props.onComplete}
      >
      <FiCheck className=''/>
      </span>
      <p className={`TodoItem-p ${props.completed && 'TodoItem-p--completed'}`}>
        {props.text}
      </p>
      <span 
      className='Icon Icon-delete'
      onClick={props.onDelete}
      >
      <FiX />
      </span>
    </li>
  );
}

export { TodoItem };

se puede usar icodify también y esta para mas framework aparte de react también para vue :V

Los iconos los agregué con React Icons, adiós agregar link de fontawesome

Sin duda alguna vale la pena aprender a usar la librería React Icons, todos los iconos están en formato svg, y su uso resulta muy sencillo además de ayudar al performance

En mi caso usé FONTAWESOME solo es cuestión de agregar esta etiqueta al HEAD de HTML <script src=“https://kit.fontawesome.com/07d6162b49.js” crossorigin=“anonymous”></script> y ya en cualquier componente solo es insertar la etiqueta <i/> con los valores que tiene para cada icono en su sitio web https://fontawesome.com/

Esta fue mi solución con react-icons:

import { BiCheckCircle } from "react-icons/bi";
import { TiDelete } from "react-icons/ti";
import './TodoItem.css'

function TodoItem(props) {
    const { todo, onComplete, onDelete } = props;
    const { text, completed } = todo; 
    
    return (
        <li className="TodoItem">
            {/* <span 
            className={`Icon Icon-check ${completed && 'Icon-check--active'}`}
            onClick={onComplete}
            ></span> */}
            <BiCheckCircle 
            onClick={onComplete}
            className={`Icon Icon-check ${completed && 'Icon-check--active'}`}
            />
            <p  className={`TodoItem-p ${completed && 'TodoItem-p--complete'}`}>{text}</p>
            {/* <span 
            className="Icon Icon-delete"
            onClick={onDelete}
            >X</span> */}
            <TiDelete
            className="Icon Icon-delete"
            onClick={onDelete}
            />
        </li>
    )
}

export { TodoItem }

Cree una carpeta con los dos iconos a utilizar ‘src/icon’.

se crean los dos componentes para cada icono:

TodoCompletedIcon con su respectivo css:

import React from "react";
import iconCompleted from '../Icon/completed.png';
import './TodoCompletedIcon.css'

function TodoCompletedIcon(props) {
    return (
        <figure>
            <img
                src={iconCompleted}
                className={`Icon-check ${props.completed && 'Icon-check--active'}`}
                onClick={props.onComplete}
            />
        </figure>
    );
};

export { TodoCompletedIcon };<code> 
.Icon-check {
    cursor: pointer;
    display: inline-block;
    width: 40px;
    height: 40px;
    opacity: 20%;
}

.Icon-check--active {
    opacity: 100%;
}

.Icon-check:hover {
    opacity: 100%;
}

y TodoCancelIcon:

import React from "react";
import iconCancel from '../Icon/canceled.png';
import './TodoCancelIcon.css'

function TodoCancelIcon(props) {
    return (
        <figure>
            <img
                src={iconCancel}
                className="Icon-delete"
                onClick={props.onDelete}
                alt='cancel icon'
            />
        </figure>
    );
};

export { TodoCancelIcon };

.Icon-delete {
    position: absolute;
    height: 35px;
    width: 35px;
    top: -13px;
    right: 0;
    opacity: 70%;
}

.Icon-delete:hover {
    opacity: 100%;
}

En TodoItem agregue los componentes creados, dejando este archivo asi:

import React from "react";
import { TodoContext } from "../TodoContext";
import { TodoCompletedIcon } from "../TodoCompletedIcon";
import { TodoCancelIcon } from "../TodoCancelIcon";
import './TodoItem.css'

function TodoItem(props) {
    const {
        completeTodo,
        deleteTodo,
    } = React.useContext(TodoContext);
    return (
        <li className="TodoItem">
            <TodoCompletedIcon
                completed={props.completed}
                onComplete={() => completeTodo(props.text)}
            />

            <p className={`TodoItem-p ${props.completed && 'TodoItem-p--complete'}`}>
                {props.text}
            </p>
            <TodoCancelIcon
                onDelete={() => deleteTodo(props.text)}
            />
        </li>
    );
}

export { TodoItem }

Por ultimó se ajusta el archivo AppUI, en donde se retiran líneas de código no necesarias.

function AppUI() {
    const {
        error,
        loading,
        searchedTodos,
        openModal,
        setOpenModal,
    } = React.useContext(TodoContext);


    return (
        <React.Fragment>
            <TodoCounter />
            <TodoSearch />
            <TodoList>
                {error && <TodoError error={error} />}
                {loading && <TodoLoading />}
                {(!loading && !searchedTodos.length) && <TodoEmpty />}

                {searchedTodos.map(todo => (
                    <TodoItem
                        key={todo.text}
                        text={todo.text}
                        completed={todo.completed}
                    />
                ))}
            </TodoList>
// continua el codigo