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-builder
es 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.js
y 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/theme
y 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.json
archivo 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/utils
con 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 localStorage
y recuperarlos desde allí. Ahora cargaremos los temas en el navegador localStorage
cuando la aplicación aparezca por primera vez. Para hacer eso, abra el index.js
archivo 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.json
archivo y la agregamos localStorage
usando 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 ThemeProvider
contenedor.
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.js
archivo dentro de la src/theme
carpeta 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 localStorage
y un valor booleano para indicar si el tema se cargó correctamente desde el almacenamiento. También exponen una función, setMode
para 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 createGlobalStyle
que establece componentes globales que tienen en cuenta el tema. Configurémoslos en un archivo llamado, GlobalStyles.js
en la src/theme
carpeta 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 body
enlaces y botones, ¿verdad? Podemos usarlos en el App.js
archivo 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í:
- Importamos los ganchos
useState
yuseEffect
React que nos ayudarán a realizar un seguimiento de cualquiera de las variables de estado y sus cambios debido a cualquier efecto secundario. ImportamosThemeProvider
ystyled
desde componentes con estilo. También seWebFont
importa para cargar fuentes. También importamos el tema personalizadouseTheme
y el componente de estilo globalGlobalStyles
. - Creamos un
Container
componente usando los estilos ystyled
el componente CSS. - Declaramos las variables de estado y buscamos los cambios.
- Cargamos todas las fuentes que requiere la aplicación.
- Representamos un montón de texto y un enlace. Pero observe que estamos envolviendo todo el contenido con el
ThemeProvider
contenedor que toma el tema seleccionado como accesorio. También pasamos elGlobalStyles/
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.js
archivo 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 ThemeCard
y 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 ThemeCard
componente.
{ 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.js
en nuestra src
carpeta. 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 Container
componente:
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.json
archivo para cargarlos en la interfaz de usuario y cambiar. Consulte este schema.json
archivo 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.js
y 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