Recientemente tuve un proyecto con el requisito de respaldar la temática 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 no solo querían elegir entre temas creados previamente, sino también construir sus propios temas. ¡Supongo que la gente quiere lo que quiere!
Vamos a resumir eso en una lista completa de requisitos más detallados, ¡y luego hagámoslo!
- Definir un tema (es decir, color de fondo, color de fuente, botones, enlaces, etc.)
- Crear y guardar varios temas
- Seleccione y solicitar un tema
- Cambiar temas
- Personalizar un tema
Le entregamos exactamente eso a nuestro cliente, y lo último que supe, ¡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
Configuremos un proyecto con React y componentes con estilo. Para hacer eso, usaremos la aplicación create-react-app. 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 él en la línea de comando con cd theme-builder
. Abre el archivo src/App.js
archivo y reemplace el contenido con lo siguiente:
import React from 'react';
function App() {
return (
<h1>Theme 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 start
yarn start
Ahora puede acceder a la aplicación usando 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 instalemos esos mientras estamos en eso:
# Or, npm i ...
yarn add styled-components webfontloader lodash
Esto es lo que obtenemos:
- componentes-estilizados: Una forma flexible de diseñar componentes de React con CSS. Proporciona soporte de temas listo para usar utilizando un componente de envoltura 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: Web Font Loader ayuda a cargar fuentes de varias fuentes, como Google Fonts, Adobe Fonts, etc. Usaremos esta biblioteca para cargar fuentes cuando se aplica 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 cierta estructura 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
Un tema es un grupo estructurado de propiedades que usaremos en la aplicación.
Es posible que tenga más propiedades y / o diferentes formas de estructurarlo, pero estas son las cosas que usaremos para nuestro ejemplo.
Crea y guarda varios temas
Entonces, acabamos de ver cómo definir un tema. Ahora creemos varios temas agregando una carpeta en el proyecto en src/theme
y un archivo en él llamado, schema.json
. Esto es lo que podemos colocar en ese archivo para establecer temas de “luz” y “olas marinas”:
{
"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 de la schema.json
El 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 navegador localStorage
. Para hacer eso, crearemos otra carpeta en src/utils
con un nuevo archivo llamado, storage.js
. Solo 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 recuperar de allí. Ahora cargaremos los temas en el navegador localStorage
cuando la aplicación aparece por primera vez. Para hacer eso, abra el index.js
archivar y reemplazar 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 agregarlo al localStorage
usando la llave 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 que los temas se cargan en localStorage
.
Todos los accesorios del tema se almacenan correctamente en el navegador. localStorage
, como se ve en DevTools, en Aplicación → Almacenamiento local.
Seleccionar y aplicar un tema
Ahora podemos usar la estructura del tema y proporcionar el objeto del tema al <ThemeProvider>
envoltura.
Primero, crearemos un gancho React personalizado. Esto administrará el tema seleccionado, sabiendo si un tema está cargado correctamente o tiene algún problema. Empecemos con una nueva useTheme.js
archivo dentro del src/theme
carpeta con esto en ella:
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 de localStorage
y un booleano para indicar si el tema se carga correctamente desde el almacenamiento. También expone 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 sensibles al tema. Vamos a configurarlos en un archivo llamado, GlobalStyles.js
en el 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 <body>
, enlaces y botones, ¿verdad? Podemos usar estos en el App.js
file para ver el tema en acción reemplazando el contenido con esto:
// 1: Import
import 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 cotainer
const 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}}>
<h1>Theme 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;
Aquí están sucediendo algunas cosas:
- Importamos el
useState
yuseEffect
Reaccionar ganchos lo que nos ayudará a realizar un seguimiento de cualquiera de las variables de estado y sus cambios debido a los efectos secundarios. ImportamosThemeProvider
ystyled
de componentes con estilo. ElWebFont
también se importa para cargar fuentes. También importamos el tema personalizado,useTheme
y el componente de estilo global,GlobalStyles
. - Creamos un
Container
componente usando los estilos CSS ystyled
componente. - Declaramos las variables de estado y esté atento a los cambios.
- Cargamos todas las fuentes que son requeridos por la aplicación.
- Representamos un montón de texto y un enlace. Pero observe que estamos envolviendo todo el contenido con el
<ThemeProvider>
envoltorio que toma el tema seleccionado como accesorio. También pasamos en el<GlobalStyles/>
componente.
Actualice la aplicación y deberíamos ver el tema “ligero” predeterminado habilitado.
¡Oye, mira ese diseño limpio y austero!
Probablemente deberíamos ver si el cambio de tema funciona. Entonces, abramos el useTheme.js
archivo y cambie esta línea:
localTheme ? setTheme(localTheme) : setTheme(themes.data.light);
…a:
localTheme ? setTheme(localTheme) : setTheme(themes.data.seaWave);
Actualice la aplicación de nuevo y, con suerte, veremos el tema “olas del mar” en acción.
Ahora estamos montando las olas de este tema predominantemente azul.
Cambiar temas
¡Estupendo! 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.
Se proporciona una vista previa de cada tema en la lista de opciones.
Llamemos a cada una de estas cajas ThemeCard
, y configúrelos de manera que puedan tomar la definición de su tema como un 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} />
))
}
Pasemos ahora al marcado de un ThemeCard
. El suyo puede verse diferente, pero observe 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}`}}>
<span>Click 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 nuestro el 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 temas.
La parte divertida es que puede agregar tantos como temas en el schema.json
para cargarlos en la interfaz de usuario y cambiar. Mira esto schema.json
archivo para algunos temas más. Tenga en cuenta que también estamos guardando la información del tema aplicado en localStorage
, por lo que la selección se conservará cuando vuelva a abrir la aplicación la próxima vez.
Personaliza un tema
Quizás a tus usuarios les gusten algunos aspectos de un tema y algunos aspectos de otro. ¿Por qué hacer que elijan entre ellos cuando pueden darles la capacidad de definir ellos mismos los accesorios del tema? Podemos crear una interfaz de usuario simple que permite a los usuarios seleccionar las opciones de apariencia que desean 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 el repositorio de GitHub. El archivo fuente principal es CreateThemeContent.js
y es usado por App.js
. Creamos el nuevo objeto de tema reuniendo 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 que terminemos …
¡Gracias por leer! Espero que lo que cubrimos aquí le resulte útil para algo en lo que esté trabajando. ¡Los sistemas de tematización son divertidos! De hecho, las propiedades personalizadas de CSS hacen que eso sea cada vez más importante. Por ejemplo, consulte este enfoque para el 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 forma de Andrés Galente.
Donde todos estos son un gran ejemplo para crear 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.
¡Vamos a conectarnos! Puedes enviarme un mensaje de texto Gorjeo con comentarios, o siéntete libre de seguirlos.