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 鈥渆st茅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 鈥渕aquetada鈥 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=鈥渁nonymous鈥></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 鈥榮rc/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