Comment appliquer des styles gérés Material-UI à des éléments non-Material-Ui, non-react?

9

J'ai une application où j'utilise Material UI et son fournisseur de thème (en utilisant JSS).

J'incorpore maintenant fullcalendar-react , qui n'est pas vraiment une bibliothèque React à part entière - c'est juste une fine enveloppe de composant React autour du code original fullcalendar.

C'est-à-dire que je n'ai pas accès à des choses comme les accessoires de rendu pour contrôler la façon dont il stylise ses éléments.

Cependant, il vous donne directement accès aux éléments DOM, via un rappel qui est appelé lors de leur rendu (par exemple, la méthode eventRender ).

Voici un sandbox de démonstration de base.

entrez la description de l'image ici

Maintenant, ce que je veux faire, c'est que les composants du calendrier complet (par exemple, les boutons) partagent le même aspect et la même sensation que le reste de mon application.

Une façon de le faire est que je pouvais remplacer manuellement tous les styles en regardant les noms de classe qu'il utilise et en implémentant le style en conséquence.

Ou - je pourrais implémenter un thème Bootstrap - comme suggéré dans leur documentation.

Mais le problème avec l'une ou l'autre de ces solutions est que:

  1. Ce serait beaucoup de travail
  2. J'aurais des problèmes de synchronisation, si j'apportais des modifications à mon thème MUI et que j'oubliais de mettre à jour le thème du calendrier, ils auraient l'air différents.

Ce que je voudrais faire, c'est:

  • Convertissez comme par magie le thème MUI en thème Bootstrap.
  • Ou créez un mappage entre les noms de classe MUI et les noms de classe de calendrier, quelque chose comme:
.fc-button = .MuiButtonBase-root.MuiButton-root.MuiButton-contained
.fc-button-primary= .MuiButton-containedPrimary

Cela ne me dérangerait pas d'avoir à masser les sélecteurs, etc. pour le faire fonctionner (par exemple. Par exemple - les boutons MUI ont deux portées internes, tandis que le calendrier complet n'en a qu'une). C'est surtout quand je change de thème - je ne veux pas avoir à le changer à deux endroits.

Utiliser quelque chose comme Sass avec sa @extendsyntaxe serait ce que j'ai en tête. Je pourrais créer le CSS complet du calendrier avec Sass assez facilement - mais comment Sass aurait-il accès au MuiTheme?

Je pourrais peut-être adopter l'approche inverse - dire à MUI 'Hé, ces noms de classe ici devraient être stylisés comme ces classes MUI'.

Avez-vous des suggestions concrètes sur la façon de résoudre ce problème?

dwjohnston
la source

Réponses:

4

Voici ma suggestion (évidemment, ce n'est pas simple). Prenez les styles du thème MUI et générez une stylebalise en fonction de celui-ci en utilisant react-helmet. Pour le faire bien, j'ai créé un composant "wrapper" qui fait la carte. J'ai implémenté uniquement la règle principale mais elle peut être étendue à toutes les autres.

De cette façon, toute modification que vous apporterez au thème affectera également les sélecteurs mappés.

import React from "react";
import { Helmet } from "react-helmet";

export function MuiAdapter({ theme }) {
  if (!theme.palette) {
    return <></>;
  }
  return (
    <Helmet>
      <style type="text/css">{`
          .fc-button-primary {
            background: ${theme.palette.primary.main}
          }
          /* more styles go here */
      `}</style>
    </Helmet>
  );
}

Et l'utilisation de l'adaptateur

<MuiAdapter theme={theme} />

Démo de travail: https://codesandbox.io/s/reverent-mccarthy-3o856

capture d'écran

Mosh Feu
la source
J'aime cette réflexion. Le problème avec cette solution est que vous implémenteriez toujours tous les styles vous-même (c'est-à-dire la couleur du survol, le rembourrage, le rayon de la bordure, etc.). Si, par exemple, vous avez décidé que le texte du bouton devait être souligné, vous devez vous rappeler d'ajouter la text-decorationpropriété au casque.
dwjohnston
Je comprends ce que tu demandes. J'ai essayé d'adopter une approche différente et de manipuler les stylebalises MUI et de leur ajouter des .fc-sélecteurs en fonction de la carte, mais ils ont des conflits dans les niveaux de base (tels que display, paddingetc.), donc je ne vois pas comment cela fonctionnera même si vous auriez le option pour le migrer dans scss. Par exemple: codesandbox.io/s/charming-sound-3xmj9
Mosh Feu
Haha - maintenant j'aime ça. Je vais lui donner un meilleur look plus tard.
dwjohnston
3

Vous pouvez créer un mappage entre les noms de classe MUI et les noms de classe de calendrier en parcourant refles. Il est possible que ce ne soit pas ce que certains appellent les "meilleures pratiques" ... mais c'est une solution :). Notez que j'ai mis à jour votre composant d'un composant fonctionnel vers un composant de classe, mais vous pouvez le faire avec des crochets dans un composant fonctionnel.

Ajouter des références

Ajoutez un refà l'élément MUI que vous souhaitez définir comme référence, dans votre cas, le Button.

<Button
  color="primary"
  variant="contained"
  ref={x => {
    this.primaryBtn = x;
  }}
>

Et un refenvelopper divautour du composant que vous souhaitez mapper. Vous ne pouvez pas l'ajouter directement au composant car cela ne nous donnerait pas accès àchildren .

<div
  ref={x => {
    this.fullCal = x;
  }}
>
  <FullCalendar
    ...
  />
</div>

Classes de carte

De l' componentDidMount()ajout de la logique dont vous avez besoin pour cibler le nœud DOM correct (pour votre cas, j'ai ajouté la logique pour typeet matchingClass). Exécutez ensuite cette logique sur tous les nœuds DOM FullCalendar et remplacez-le classListsur tous ceux qui correspondent.

componentDidMount() {
  this.updatePrimaryBtns();
}

updatePrimaryBtns = () => {
  const children = Array.from(this.fullCal.children);
  // Options
  const type = "BUTTON";
  const matchingClass = "fc-button-primary";

  this.mapClassToElem(children, type, matchingClass);
};

mapClassToElem = (arr, type, matchingClass) => {
  arr.forEach(elem => {
    const { tagName, classList } = elem;
    // Check for match
    if (tagName === type && Array.from(classList).includes(matchingClass)) {
      elem.classList = this.primaryBtn.classList.value;
    }

    // Run on any children
    const next = elem.children;
    if (next.length > 0) {
      this.mapClassToElem(Array.from(next), type, matchingClass);
    }
  });
};

C'est peut-être un peu lourd, mais cela répond à vos exigences de preuve futures lorsque vous mettrez à jour l'interface utilisateur du matériel. Cela vous permettrait également de modifierclassList lorsque vous le transmettez à un élément, ce qui présente des avantages évidents.

Avertissements

Si le composant (mappé FullCalendar) a mis à jour les classes sur les éléments que vous ciblez (comme s'il avait ajouté.is-selected à un bouton actuel) ou de nouveaux boutons après le montage, vous deviez trouver un moyen de suivre les modifications pertinentes et de réexécuter la logique.

Je dois également mentionner que (évidemment) la modification des classes peut avoir des conséquences inattendues comme une rupture de l'interface utilisateur et vous devrez trouver un moyen de les corriger.

Voici le sandbox qui fonctionne: https://codesandbox.io/s/determ-frog-3loyf

sallf
la source
1
Cela marche. Notez que vous n'avez pas besoin de convertir en composant de classe - vous pouvez utiliser des hooks pour ce faire.
dwjohnston
Vous avez raison, cela peut être fait avec des crochets dans un composant fonctionnel. J'ai mis à jour la réponse pour refléter cela.
sallf
Sympa, l'approche assez pragmatique. Mais pas vraiment faisable car vous auriez toujours besoin d'une référence.
Aniket Chowdhury