Tematización y cambio de tema con React y componentes con estilo

Recientemente tuve un proyecto que requería admitir temas en el sitio web. Era un requisito un poco extraño, ya que la aplicación es utilizada principalmente por un puñado de administradores. Una sorpresa aún mayor fue que querían no sólo elegir entre temas creados previamente, sino también crear sus propios temas. ¡Supongo que la gente quiere lo que quiere!

Resumamos eso en una lista completa de requisitos más detallados y ¡luego hagámoslo!

  • Defina un tema (es decir, color de fondo, color de fuente, botones, enlaces, etc.)
  • Crea y guarda múltiples temas.
  • Seleccionar y aplicar un tema
  • Cambiar temas
  • Personaliza un tema

Entregamos exactamente eso a nuestro cliente, y lo último que supe fue que lo estaban usando felizmente.

Empecemos a construir exactamente eso. Usaremos React y componentes con estilo. Todo el código fuente utilizado en el artículo se puede encontrar en el Repositorio de GitHub.

Ver demostración

La puesta en marcha

Configuramos un proyecto con React y componentes con estilo. Para hacer eso, usaremos la aplicación create-react. Nos brinda el entorno que necesitamos para desarrollar y probar aplicaciones React rápidamente.

Abra un símbolo del sistema y use este comando para crear el proyecto:

npx create-react-app theme-builder

El último argumento, theme-builderes solo el nombre del proyecto (y por lo tanto, el nombre de la carpeta). Puedes usar lo que quieras.

Puede que tarde un poco. Cuando termine, navegue hasta allí en la línea de comando con cd theme-builder. Abra el archivo src/App.jsy reemplace el contenido con lo siguiente:

import React from 'react';function App() {  return (    h1Theme Builder/h1  );}export default App;

Este es un componente básico de React que modificaremos pronto. Ejecute el siguiente comando desde la carpeta raíz del proyecto para iniciar la aplicación:

# Or, npm run startyarn start

Ahora puedes acceder a la aplicación mediante la URL http://localhost:3000.

create-react-app viene con el archivo de prueba para el componente de la aplicación. Como no escribiremos ninguna prueba para los componentes como parte de este artículo, puede optar por eliminar ese archivo.

Tenemos que instalar algunas dependencias para nuestra aplicación. Así que instalamoslos mientras estamos en ello:

# Or, npm i ...yarn add styled-components webfontloader lodash

Esto es lo que obtenemos:

  • Componentes con estilo: una forma flexible de diseñar componentes de React con CSS. Proporciona compatibilidad con temas listos para usar mediante un componente contenedor llamado ThemeProvider. Este componente es responsable de proporcionar el tema a todos los demás componentes de React que están incluidos en él. Veremos esto en acción en un minuto.
  • Cargador de fuentes web: El cargador de fuentes web ayuda a cargar fuentes de varias fuentes, como Google Fonts, Adobe Fonts, etc. Usaremos esta biblioteca para cargar fuentes cuando se aplique un tema.
  • lodash: Esta es una biblioteca de utilidades de JavaScript para algunos pequeños extras útiles.

Definir un tema

Este es el primero de nuestros requisitos. Un tema debe tener una estructura determinada para definir la apariencia, incluidos colores, fuentes, etc. Para nuestra aplicación, definiremos cada tema con estas propiedades:

  • identificador único
  • nombre del tema
  • definiciones de color
  • fuentes

Es posible que tenga más propiedades y/o diferentes formas de estructurarlo, pero estas son las cosas que usaremos en nuestro ejemplo.

Crea y guarda múltiples temas.

Entonces, acabamos de ver cómo definir un tema. Ahora creamos varios temas agregando una carpeta en el proyecto src/themey un archivo llamado schema.json. Esto es lo que podemos colocar en ese archivo para establecer temas de “luz” y “olas del mar”:

{  "data" : {    "light" : {      "id": "T_001",      "name": "Light",      "colors": {        "body": "#FFFFFF",        "text": "#000000",        "button": {          "text": "#FFFFFF",          "background": "#000000"        },        "link": {          "text": "teal",          "opacity": 1        }      },      "font": "Tinos"    },    "seaWave" : {      "id": "T_007",      "name": "Sea Wave",      "colors": {        "body": "#9be7ff",        "text": "#0d47a1",        "button": {          "text": "#ffffff",          "background": "#0d47a1"        },        "link": {          "text": "#0d47a1",          "opacity": 0.8        }      },      "font": "Ubuntu"    }  }}

El contenido del schema.jsonarchivo se puede guardar en una base de datos para que podamos conservar todos los temas junto con la selección del tema. Por ahora, simplemente lo almacenaremos en el archivo localStorage. Para hacer eso, crearemos otra carpeta src/utilscon un nuevo archivo llamado storage.js. Sólo necesitamos unas pocas líneas de código para configurar localStorage:

export const setToLS = (key, value) = {  window.localStorage.setItem(key, JSON.stringify(value));}export const getFromLS = key = {  const value = window.localStorage.getItem(key);  if (value) {    return JSON.parse(value);  }}

Estas son funciones de utilidad simples para almacenar datos en el navegador localStoragey recuperarlos desde allí. Ahora cargaremos los temas en el navegador localStoragecuando la aplicación aparezca por primera vez. Para hacer eso, abra el index.jsarchivo y reemplace el contenido con lo siguiente,

import React from 'react';import ReactDOM from 'react-dom';import App from './App';import * as themes from './theme/schema.json';import { setToLS } from './utils/storage';const Index = () = {  setToLS('all-themes', themes.default);  return(    App /  )}ReactDOM.render(  Index /  document.getElementById('root'),);

Aquí, obtenemos la información del tema del schema.jsonarchivo y la agregamos localStorageusando la clave all-themes. Si detuvo la ejecución de la aplicación, iníciela nuevamente y acceda a la interfaz de usuario. Puede utilizar DevTools en el navegador para ver los temas cargados localStorage.

Seleccionar y aplicar un tema

Ahora podemos usar la estructura del tema y proporcionar el objeto del tema al ThemeProvidercontenedor.

Primero, crearemos un gancho de React personalizado. Esto administrará el tema seleccionado, sabiendo si un tema está cargado correctamente o tiene algún problema. Comenzamos con un nuevo useTheme.jsarchivo dentro de la src/themecarpeta que contiene esto:

import { useEffect, useState } from 'react';import { setToLS, getFromLS } from '../utils/storage';import _ from 'lodash';export const useTheme = () = {  const themes = getFromLS('all-themes');  const [theme, setTheme] = useState(themes.data.light);  const [themeLoaded, setThemeLoaded] = useState(false);  const setMode = mode = {    setToLS('theme', mode)    setTheme(mode);  };  const getFonts = () = {    const allFonts = _.values(_.mapValues(themes.data, 'font'));    return allFonts;  }  useEffect(() ={    const localTheme = getFromLS('theme');    localTheme ? setTheme(localTheme) : setTheme(themes.data.light);    setThemeLoaded(true);  }, []);  return { theme, themeLoaded, setMode, getFonts };};

Este gancho de React personalizado devuelve el tema seleccionado localStoragey un valor booleano para indicar si el tema se cargó correctamente desde el almacenamiento. También exponen una función, setModepara aplicar un tema mediante programación. Volveremos a eso en un momento. Con esto también obtenemos una lista de fuentes que podemos cargar más tarde usando un cargador de fuentes web.

Sería una buena idea usar estilos globales para controlar cosas, como el color de fondo del sitio, la fuente, el botón, etc. styled-components proporciona un componente llamado createGlobalStyleque establece componentes globales que tienen en cuenta el tema. Configurémoslos en un archivo llamado, GlobalStyles.jsen la src/themecarpeta con el siguiente código:

import { createGlobalStyle} from "styled-components";export const GlobalStyles = createGlobalStyle`  body {    background: ${({ theme }) = theme.colors.body};    color: ${({ theme }) = theme.colors.text};    font-family: ${({ theme }) = theme.font};    transition: all 0.50s linear;  }  a {    color: ${({ theme }) = theme.colors.link.text};    cursor: pointer;  }  button {    border: 0;    display: inline-block;    padding: 12px 24px;    font-size: 14px;    border-radius: 4px;    margin-top: 5px;    cursor: pointer;    background-color: #1064EA;    color: #FFFFFF;    font-family: ${({ theme }) = theme.font};  }  button.btn {    background-color: ${({ theme }) = theme.colors.button.background};    color: ${({ theme }) = theme.colors.button.text};  }`;

Solo algo de CSS para los bodyenlaces y botones, ¿verdad? Podemos usarlos en el App.jsarchivo para ver el tema en acción reemplazando el contenido con esto:

// 1: Importimport React, { useState, useEffect } from 'react';import styled, { ThemeProvider } from "styled-components";import WebFont from 'webfontloader';import { GlobalStyles } from './theme/GlobalStyles';import {useTheme} from './theme/useTheme';// 2: Create a cotainerconst Container = styled.div`  margin: 5px auto 5px auto;`;function App() {  // 3: Get the selected theme, font list, etc.  const {theme, themeLoaded, getFonts} = useTheme();  const [selectedTheme, setSelectedTheme] = useState(theme);  useEffect(() = {    setSelectedTheme(theme);   }, [themeLoaded]);  // 4: Load all the fonts  useEffect(() = {    WebFont.load({      google: {        families: getFonts()      }    });  });  // 5: Render if the theme is loaded.  return (        {      themeLoaded  ThemeProvider theme={ selectedTheme }        GlobalStyles/        Container style={{fontFamily: selectedTheme.font}}          h1Theme Builder/h1          p            This is a theming system with a Theme Switcher and Theme Builder.            Do you want to see the source code? a href="https://github.com/atapas/theme-builder" target="_blank"Click here./a          /p        /Container      /ThemeProvider    }    /  );}export default App;

Algunas cosas están sucediendo aquí:

  1. Importamos los ganchos useStatey useEffectReact que nos ayudarán a realizar un seguimiento de cualquiera de las variables de estado y sus cambios debido a cualquier efecto secundario. Importamos ThemeProvidery styleddesde componentes con estilo. También se WebFontimporta para cargar fuentes. También importamos el tema personalizado useThemey el componente de estilo global GlobalStyles.
  2. Creamos un Containercomponente usando los estilos y styledel componente CSS.
  3. Declaramos las variables de estado y buscamos los cambios.
  4. Cargamos todas las fuentes que requiere la aplicación.
  5. Representamos un montón de texto y un enlace. Pero observe que estamos envolviendo todo el contenido con el ThemeProvidercontenedor que toma el tema seleccionado como accesorio. También pasamos el GlobalStyles/componente.

Actualice la aplicación y deberíamos ver habilitado el tema "ligero" predeterminado.

Probablemente deberíamos ver si cambiar de tema funciona. Entonces, abramos el useTheme.jsarchivo y cambiemos esta línea:

localTheme ? setTheme(localTheme) : setTheme(themes.data.light);

…a:

localTheme ? setTheme(localTheme) : setTheme(themes.data.seaWave);

Actualiza la aplicación nuevamente y, con suerte, veremos el tema "ola del mar" en acción.

Cambiar temas

¡Excelente! Somos capaces de aplicar correctamente los temas. ¿Qué tal crear una forma de cambiar de tema con solo hacer clic en un botón? ¡Por supuesto que podemos hacer eso! También podemos proporcionar algún tipo de vista previa del tema.

Llamemos a cada uno de estos cuadros a ThemeCardy configurémoslos de manera que puedan tomar la definición del tema como accesorio. Repasaremos todos los temas, los recorreremos y completaremos cada uno como un ThemeCardcomponente.

{  themes.length  0    themes.map(theme =(    ThemeCard theme={data[theme]} key={data[theme].id} /  ))}

Ahora pasemos al marcado de ThemeCard. El tuyo puede verse diferente, pero observa cómo extraemos sus propias propiedades de color y fuente y luego las aplicamos:

const ThemeCard = props = {  return(    Wrapper       style={{backgroundColor: `${data[_.camelCase(props.theme.name)].colors.body}`, color: `${data[_.camelCase(props.theme.name)].colors.text}`, fontFamily: `${data[_.camelCase(props.theme.name)].font}`}}      spanClick on the button to set this theme/span      ThemedButton        onClick={ (theme) = themeSwitcher(props.theme) }        style={{backgroundColor: `${data[_.camelCase(props.theme.name)].colors.button.background}`, color: `${data[_.camelCase(props.theme.name)].colors.button.text}`, fontFamily: `${data[_.camelCase(props.theme.name)].font}`}}        {props.theme.name}      /ThemedButton    /Wrapper  )}

A continuación, creemos un archivo llamado ThemeSelector.jsen nuestra srccarpeta. Copie el contenido desde aquí y suéltelo en el archivo para establecer nuestro selector de temas, que debemos importar en App.js:

import ThemeSelector from './ThemeSelector';

Ahora podemos usarlo dentro del Containercomponente:

Container style={{fontFamily: selectedTheme.font}}  // same as before  ThemeSelector setter={ setSelectedTheme } //Container 

Actualicemos el navegador ahora y veamos cómo funciona el cambio de tema.

Lo divertido es que puedes agregar tantos temas en el schema.jsonarchivo para cargarlos en la interfaz de usuario y cambiar. Consulte este schema.jsonarchivo para ver más temas. Tenga en cuenta que también guardamos la información del tema aplicado en localStorage, por lo que la selección se conservará la próxima vez que vuelva a abrir la aplicación.

Personaliza un tema

Quizás a sus usuarios les gusten algunos aspectos de un tema y algunos aspectos de otro. ¿Por qué hacerles elegir entre ellos cuando pueden darles la posibilidad de definir ellos mismos los accesorios del tema? Podemos crear una interfaz de usuario sencilla que permita a los usuarios seleccionar las opciones de apariencia que deseen e incluso guardar sus preferencias.

No cubriremos la explicación del código de creación del tema en detalle, pero debería ser fácil siguiendo el código en GitHub Repo. El archivo fuente principal es CreateThemeContent.jsy es utilizado por App.js. Creamos el nuevo objeto de tema recopilando el valor de cada evento de cambio de elemento de entrada y agregamos el objeto a la colección de objetos de tema. Eso es todo.

Antes de terminar…

¡Gracias por leer! Espero que lo que cubrimos aquí te resulte útil para algo en lo que estás trabajando. ¡Los sistemas de temática son divertidos! De hecho, las propiedades personalizadas de CSS lo están haciendo cada vez más popular. Por ejemplo, consulte este enfoque de color de Dieter Raber y este resumen de Chris. También existe esta configuración de Michelle Barker que se basa en propiedades personalizadas utilizadas con Tailwind CSS. Aquí hay otra manera más de Andrés Galente.

Si bien todos estos son excelentes ejemplos para la creación de temas, espero que este artículo ayude a llevar ese concepto al siguiente nivel al almacenar propiedades, cambiar fácilmente entre temas, brindar a los usuarios una forma de personalizar un tema y guardar esas preferencias.

¡Conectémonos! Puedes enviarme un mensaje de texto en Twitter con comentarios o no dudes en seguirme.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Subir