Wie würde ich von Material-UI verwaltete Stile auf nicht-material-ui, nicht reagierende Elemente anwenden?

9

Ich habe eine Anwendung, in der ich die Material-Benutzeroberfläche und ihren Themenanbieter (mit JSS) verwende.

Ich verwende jetzt Fullcalendar- React, was eigentlich keine vollwertige React-Bibliothek ist - es ist nur ein dünner React-Komponenten-Wrapper um den ursprünglichen Fullcalendar-Code.

Das heißt, ich habe keinen Zugriff auf Dinge wie Render-Requisiten, um zu steuern, wie sie ihre Elemente formatieren.

Sie erhalten jedoch direkten Zugriff auf die DOM-Elemente über einen Rückruf, der beim Rendern aufgerufen wird (z. B. die eventRender-Methode ).

Hier ist eine grundlegende Demo-Sandbox.

Geben Sie hier die Bildbeschreibung ein

Jetzt möchte ich dafür sorgen, dass die Komponenten des vollständigen Kalenders (z. B. die Schaltflächen) das gleiche Erscheinungsbild wie der Rest meiner Anwendung haben.

Eine Möglichkeit, dies zu tun, besteht darin, dass ich alle Stile manuell überschreiben kann, indem ich mir die verwendeten Klassennamen ansehe und den Stil entsprechend implementiere.

Oder - ich könnte ein Bootstrap-Thema implementieren - wie in der Dokumentation vorgeschlagen.

Das Problem bei jeder dieser Lösungen ist jedoch, dass:

  1. Es wäre viel Arbeit
  2. Ich hätte Synchronisationsprobleme, wenn ich Änderungen an meinem MUI-Thema vorgenommen und vergessen hätte, das Kalenderthema zu aktualisieren, würden sie anders aussehen.

Was ich tun möchte, ist entweder:

  • Konvertieren Sie das MUI-Design auf magische Weise in ein Bootstrap-Design.
  • Oder erstellen Sie eine Zuordnung zwischen MUI-Klassennamen und den Kalenderklassennamen.
.fc-button = .MuiButtonBase-root.MuiButton-root.MuiButton-contained
.fc-button-primary= .MuiButton-containedPrimary

Es würde mir nichts ausmachen, die Selektoren usw. massieren zu müssen, damit es funktioniert (z. B. - MUI-Schaltflächen haben zwei interne Bereiche, während der vollständige Kalender nur einen hat). Es geht hauptsächlich darum, wann ich das Thema ändere - ich möchte es nicht an zwei Stellen ändern müssen.

@extendIch habe vor, so etwas wie Sass mit seiner Syntax zu verwenden. Ich könnte das vollständige Kalender-CSS mit Sass leicht genug erstellen - aber wie würde Sass Zugriff auf das MuiTheme erhalten?

Vielleicht könnte ich den umgekehrten Ansatz wählen - sagen Sie MUI: "Hey, diese Klassennamen hier sollten wie diese MUI-Klassen gestaltet sein."

Irgendwelche konkreten Vorschläge, wie ich das lösen würde?

Dwjohnston
quelle

Antworten:

4

Hier ist mein Vorschlag (offensichtlich ist es nicht einfach). Nehmen Sie die Stile aus dem MUI-Thema und generieren Sie darauf stylebasierend ein Tag react-helmet. Um dies gut zu machen, habe ich eine "Wrapper" -Komponente erstellt, die die Karte erstellt. Ich habe nur die primäre Regel implementiert, aber sie kann auf alle anderen erweitert werden.

Auf diese Weise wirkt sich jede Änderung, die Sie am Thema vornehmen, auch auf die zugeordneten Selektoren aus.

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>
  );
}

Und die Verwendung des Adapters

<MuiAdapter theme={theme} />

Arbeitsdemo: https://codesandbox.io/s/reverent-mccarthy-3o856

Bildschirmfoto

Mosh Feu
quelle
Ich mag dieses Denken. Das Problem bei dieser Lösung ist, dass Sie im Grunde immer noch alle Stile selbst implementieren würden (dh die Schwebefarbe, die Polsterung, den Randradius usw.). Wenn Sie beispielsweise entschieden haben, dass der Schaltflächentext unterstrichen werden soll, müssen Sie daran denken, die text-decorationEigenschaft dem Helm hinzuzufügen .
Dwjohnston
Ich verstehe, wonach du fragst. Ich habe versucht , einen anderen Ansatz zu nehmen und die MUI zu manipulieren styleTags und sie hinzufügen .fc-Selektoren nach der Karte , aber sie haben Konflikte in den Basisebenen (wie display, paddingetc.) , so sehe ich nicht , wie es auch funktioniert , wenn Sie das hätte Option zum Migrieren in scss. Zum Beispiel: Codesandbox.io/s/charming-sound-3xmj9
Mosh Feu
Haha - jetzt mag ich das. Ich werde es später genauer betrachten.
Dwjohnston
3

Sie können eine Zuordnung zwischen MUI-Klassennamen und Kalenderklassennamen erstellen, indem Sie die durchgehen ref. Es ist möglich, dass dies nicht das ist, was manche als "Best Practice" bezeichnen würden ... aber es ist eine Lösung :). Beachten Sie, dass ich Ihre Komponente von einer Funktionskomponente zu einer Klassenkomponente aktualisiert habe. Sie können dies jedoch mit Hooks in einer Funktionskomponente erreichen.

Refs hinzufügen

Fügen Sie refdem MUI-Element, das Sie als Referenz festlegen möchten, ein hinzu, in Ihrem Fall das Button.

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

Und ein refUmbruch divum die Komponente, der Sie zuordnen möchten. Sie können es nicht direkt zur Komponente hinzufügen, da wir dadurch keinen Zugriff darauf haben children.

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

Kartenklassen

Von componentDidMount()addieren , was Logik Sie den richtigen DOM - Knoten zum Ziel müssen (für Ihren Fall, habe ich Logik für typeund matchingClass). Führen Sie diese Logik dann auf allen FullCalendar-DOM-Knoten aus und ersetzen Sie sie auf allen übereinstimmenden Knoten classList.

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);
    }
  });
};

Dies ist vielleicht etwas umständlich, erfüllt jedoch Ihre Anforderungen an die Zukunftssicherheit, wenn Sie die Aktualisierung der Material-Benutzeroberfläche aktualisiert haben. Es würde Ihnen auch ermöglichen, das zu ändern, classListwenn Sie es an ein Element übergeben, was offensichtliche Vorteile hat.

Vorsichtsmaßnahmen

Wenn die Komponente ' FullCalendar) ' zugeordnet ( ) Klassen für die Elemente aktualisiert hat, auf die Sie abzielen (z. B. wenn sie .is-selectedeiner aktuellen Schaltfläche hinzugefügt wurden ) oder nach dem Mounten neue Schaltflächen hinzufügt, müssen Sie einen Weg finden, um die relevanten Änderungen zu verfolgen und erneut auszuführen die Logik.

Ich sollte auch erwähnen, dass (offensichtlich) das Ändern von Klassen unbeabsichtigte Konsequenzen haben kann, wie z. B. eine fehlerhafte Benutzeroberfläche, und Sie müssen herausfinden, wie Sie diese beheben können.

Hier ist die funktionierende Sandbox: https://codesandbox.io/s/determined-frog-3loyf

sallf
quelle
1
Das funktioniert. Beachten Sie, dass Sie nicht in eine Klassenkomponente konvertieren müssen - Sie können dazu Hooks verwenden.
Dwjohnston
Ja, Sie haben Recht, dies kann mit Haken in einer Funktionskomponente erfolgen. Ich habe die Antwort aktualisiert, um dies widerzuspiegeln.
Sallf
Schön, ziemlich pragmatisch. Aber nicht wirklich machbar, da Sie immer einen Schiedsrichter brauchen würden.
Aniket Chowdhury