Maison > interface Web > tutoriel CSS > Une approche différente de l'écriture de variantes de composants avec Tailwind CSS

Une approche différente de l'écriture de variantes de composants avec Tailwind CSS

Patricia Arquette
Libérer: 2024-11-21 05:35:11
original
671 Les gens l'ont consulté

A different approach to writing component variants with Tailwind CSS

Le problème

Traditionnellement, lors de l'écriture de variantes de composants avec Tailwind CSS, j'ai opté pour des cartes de classes simples qui mappent une valeur d'accessoire aux emplacements de composants :

type TTheme = "DEFAULT" | "SECONDARY";
interface IComponentSlot {
  root: string;
  accent: string;
}

const THEME_MAP: Record<TTheme, IComponentSlot> = {
  DEFAULT: {
    root: "bg-red hover:background-pink",
    accent: "text-blue hover:text-green",
  },
  SECONDARY: {
    root: "bg-green hover:background-black",
    accent: "text-pink hover:text-white",
  }
}

<div :class="THEME_MAP['DEFAULT'].root">
  <div :class="THEME_MAP['DEFAULT'].accent">/**/</div>
</div>
Copier après la connexion

Le problème de cette approche est de maintenir les classes requises alignées les unes sur les autres, en garantissant que chaque variante possède toutes les classes requises, en particulier dans les composants plus complexes. Dans les composants où nous souhaitons partager un style, par exemple la couleur du texte, sur différents emplacements de composants, cela nous oblige à mettre à jour chaque emplacement individuellement.

Les limites de Tailwind

Tailwind génère des classes utilitaires en analysant votre base de code et les chaînes correspondantes, cela signifie que même si Tailwind peut créer des classes à partir de valeurs arbitraires, nous ne pouvons pas les lancer dynamiquement sans créer une liste sécurisée. Donc ça ne marchera pas :

// .ts
type TTheme = "DEFAULT" | "SECONDARY";

const colors: Record<TTheme, string> = {
  DEFAULT: "red",
  SECONDARY: "blue",
}

// .html
<div :class="`text-[${colors[DEFAULT]}]`">
Copier après la connexion

Cependant, nous pouvons imiter le comportement souhaité en tirant parti des variables CSS, ce que Tailwind utilise sous le capot pour beaucoup de ses classes. Nous pouvons définir une variable via une classe dans Tailwind en utilisant la syntaxe suivante : [--my-variable-key:--my-variable-value]
Alors, comment pourrions-nous mettre à jour l'exemple de code ci-dessus pour utiliser des valeurs dynamiques ?

// .ts
type TTheme = "DEFAULT" | "SECONDARY";

const colors: Record<TTheme, string> = {
  DEFAULT: "[--text-color:red]",
  SECONDARY: "[--text-color:blue]",
}

// .html
<div
  :class="[
    colors[DEFAULT],
    'text-[--text-color]'
  ]">
Copier après la connexion

Résoudre le problème initial

Maintenant que nous comprenons les limites de Tailwind, nous devons chercher des moyens de résoudre notre problème initial causé par notre approche des cartes de classes. Nous pouvons commencer par simplifier nos cartes de classe :

type TTheme = "DEFAULT" | "SECONDARY";
interface IComponentSlot {
  root: string;
  accent: string;
}

const THEME_MAP: Record<TTheme, string> = {
  DEFAULT: "[--backgound:red] [--hover__background:pink] [--text:blue] [--hover__text:green]",
  SECONDARY: "[--backgound:green] [--hover__background:black] [--text:pink] [--hover__text:white]",
}

<div>



<p>Unfortunately, this alone doesn't solve our problem, we still can't ensure we've set all of the classes we need to display each variant correctly. So how can we take this a step further? Well, we could begin writing an interface to force us to set specified values:<br>
</p>

<pre class="brush:php;toolbar:false">interface IComponentThemeVariables {
  backgound: string;
  hover__backgound: string;
  text: string;
  hover__text: string;
}

const THEME_MAP: Record<TTheme, IComponentThemeVariables> = {
  DEFAULT: {
    backgound: "[--backgound:red]",
    text: "[--hover__background:pink]",
    hover__background: "[--text:blue]",
    hover__text:"[--hover__text:green]",
  },
  SECONDARY: {
    backgound: "[--backgound:green]",
    text: "[--hover__background:black]",
    hover__background: "[--text:pink]",
    hover__text:"[--hover__text:white]",
  },
}
Copier après la connexion

Donc cela fonctionnerait, mais il y a toujours un problème, rien ne nous empêche de mélanger nos valeurs de chaîne. Par exemple, nous pourrions accidentellement définir l'arrière-plan des touches sur [--text:blue].

Alors peut-être devrions-nous également saisir nos valeurs. Nous ne pouvons pas taper des classes entières, ce serait un cauchemar de maintenance, et si nous tapions nos couleurs et écrivions une méthode d'assistance pour générer nos variables CSS :

type TColor = "red" | "pink" | "blue" | "green" | "black" | "white";

interface IComponentThemeVariables {
  backgound: TColor;
  hover__backgound: TColor;
  text: TColor;
  hover__text: TColor;
}

// Example variableMap method at the end of the article

const THEME_MAP: Record<TTheme, string> = {
  DEFAULT: variableMap({
    backgound: "red",
    text: "pink",
    hover__background: "blue",
    hover__text:"green",
  }),
  SECONDARY: variableMap({
    backgound: "green",
    text: "black",
    hover__background: "pink",
    hover__text:"white",
  }),
}
Copier après la connexion

D'accord, c'est génial, nous pouvons nous assurer que nous définissons toujours les variables correctes pour chaque variante de notre composant. Mais attendez, nous venons de rencontrer le problème initial que nous avons trouvé avec Tailwind, nous ne pouvons pas simplement générer des classes, Tailwind ne les récupérera pas. Alors, comment allons-nous aborder cela ?

Qu'en est-il du CSS dans JS ?

CSS dans JS semblait être la réponse évidente ici, il suffit de générer une classe qui crée une classe personnalisée avec des variables correctes. Mais il y a un problème, le Javascript s'exécute sur le client, et cela provoque un "Flash", où le composant se charge initialement sans que les variables soient définies avant la mise à jour pour s'afficher correctement.

Comment les CSS dans les bibliothèques JS gèrent-ils cela ?

Les bibliothèques comme Emotion gèrent ce problème en insérant des balises de style en ligne sur les composants :

<body>
  <div>
    <style data-emotion-css="21cs4">.css-21cs4 { font-size: 12 }</style>
    <div>



<p>This didn't feel like the right approach to me.</p>

<h3>
  
  
  So how do we solve this?
</h3>

<p>I was working with Vue, this led me down the path of v-bind in CSS, a feature in Vue to bind Javascript as CSS values. I'd only used this feature sparingly in the past and never taken a deep dive into what it's doing. v-bind in CSS simply sets an inline style on the relevant element.</p>

<p>This jogged my memory about a Tweet I saw from the creator of Tailwind CSS, Adam Wathan a couple of months previously:</p>

<p>So how does this help us? Well, while we can't dynamically generate Tailwind classes, we can dynamically generate inline styles and consume those inline styles from our Tailwind classes. So what would that look like?<br>
</p>

<pre class="brush:php;toolbar:false">type TColor = "red" | "pink" | "blue" | "green" | "black" | "white";

interface IComponentThemeVariables {
  backgound: TColor;
  hover__backgound: TColor;
  text: TColor;
  hover__text: TColor;
}

// Example variableMap method at the end of the article

const THEME_MAP: Record<TTheme, string> = {
  DEFAULT: variableMap({
    backgound: "red",
    text: "pink",
    hover__background: "blue",
    hover__text: "green",
  }),
  SECONDARY: variableMap({
    backgound: "green",
    text: "black",
    hover__background: "pink",
    hover__text: "white",
  }),
}

<div
 >



<h2>
  
  
  Conclusion
</h2>

<p>By combining the powers of Typescript, CSS variables, and inline styles we were able to ensure that while using Tailwind CSS, each variant of our component would have every option set and with the correct type.</p>

<p>This is an experimental approach on which I'm sure there will be some strong opinions. Am I convinced this is the best approach? At this stage, I'm not sure, but I think it has legs.</p>

<p>If you've found this article interesting or useful, please follow me on Bluesky (I'm most active here), Medium, Dev and/ or Twitter.</p>

<h3>
  
  
  Example: variableMap
</h3>



<pre class="brush:php;toolbar:false">// variableMap example
export const variableMap = <T extends Record<string, string>>(
  map: T
): string => {
  const styles: string[] = [];
  Object.entries(map).forEach(([key, value]) => {
    const wrappedValue = value.startsWith("--") ? `var(${value})` : value;
    const variableClass = `--${key}: ${wrappedValue};`;
    styles.push(variableClass);
  });
  return styles.join(" ");
};
Copier après la connexion

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

source:dev.to
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Derniers articles par auteur
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal