Tematización y cambio de tema con React y componentes con estilo | Programar Plus

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.

Un simple título 1 que dice Theme Builder en negro sobre un fondo blanco.

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

Captura de pantalla de un editor de código que muestra la estructura organizada de propiedades para un tema de olas marinas.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.

El tema con DevTools abierto y mostrando las propiedades del tema en la consola.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:

  1. Importamos el useState y useEffect 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. Importamos ThemeProvider y styled de componentes con estilo. El WebFont también se importa para cargar fuentes. También importamos el tema personalizado, useThemey el componente de estilo global, GlobalStyles.
  2. Creamos un Container componente usando los estilos CSS y styled componente.
  3. Declaramos las variables de estado y esté atento a los cambios.
  4. Cargamos todas las fuentes que son requeridos por la aplicación.
  5. 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.

El tema con fondo blanco y texto negro.¡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.

El mismo tema con un esquema de color azul con un fondo azul claro y texto azul oscuro y un botón azul.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.

Un encabezado indica al usuario que seleccione un tema y dos componentes de la tarjeta están debajo del encabezado, uno al lado del otro, mostrando vistas previas del tema de la luz y el tema de las olas del mar.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.

Una captura de pantalla animada que muestra el cambio de tema cuando se selecciona de la lista de opciones de tarjeta de tema.

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.

Tema seleccionado almacenado en el almacenamiento local.

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.

Captura de pantalla animada que muestra una apertura modal con una lista de opciones de tema para personalizar la apariencia, incluido el nombre, el color de fondo, el color del texto, el color del texto del botón, el color del enlace y la fuente.

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.