Jak zastosować style zarządzane przez Material-UI do elementów niematerialnych i niereagujących?

9

Mam aplikację, w której korzystam z Material UI i jej dostawcy motywów (używając JSS).

Teraz włączam funkcję fullcalendar-reag , która nie jest tak naprawdę pełnoprawną biblioteką React - to tylko cienkie opakowanie komponentu React wokół oryginalnego kodu fullcalendar.

To znaczy, że nie mam dostępu do takich rzeczy, jak renderowanie rekwizytów, aby kontrolować, jak stylizuje swoje elementy.

Daje jednak dostęp do elementów DOM bezpośrednio, poprzez wywołanie zwrotne, które jest wywoływane podczas ich renderowania (np. Metoda eventRender ).

Oto podstawowa piaskownica demo.

wprowadź opis zdjęcia tutaj

Teraz chcę, aby komponenty Full Calendar (np. Przyciski) miały ten sam wygląd i działanie, co reszta mojej aplikacji.

Jednym ze sposobów, aby to zrobić, jest to, że mogę ręcznie zastąpić wszystkie style, sprawdzając nazwy klas, których używa, i odpowiednio wdrażając styl.

Lub - mógłbym wdrożyć motyw Bootstrap - zgodnie z sugestią w ich dokumentacji.

Problem z jednym z tych rozwiązań polega na tym, że:

  1. To byłoby dużo pracy
  2. Miałem problemy z synchronizacją, jeśli dokonałbym zmian w motywie MUI i zapomniałem zaktualizować motyw kalendarza, wyglądałyby inaczej.

Chciałbym albo:

  • Magicznie przekonwertuj motyw MUI na motyw Bootstrap.
  • Lub utwórz mapowanie między nazwami klas MUI a nazwami klas kalendarza, na przykład:
.fc-button = .MuiButtonBase-root.MuiButton-root.MuiButton-contained
.fc-button-primary= .MuiButton-containedPrimary

Nie miałbym nic przeciwko masowaniu selektorów itp., Aby działało (tj. Na przykład - przyciski MUI mają dwa wewnętrzne zakresy, podczas gdy pełny kalendarz ma tylko jeden). Chodzi przede wszystkim o zmianę motywu - nie chcę go zmieniać w dwóch miejscach.

@extendUżywam czegoś takiego jak Sass ze składnią, co mam na myśli. Mógłbym łatwo stworzyć pełny kalendarz CSS z Sass - ale w jaki sposób Sass miałby uzyskać dostęp do MuiTheme?

Być może mógłbym przyjąć odwrotne podejście - powiedz MUI: „Hej, te nazwy klas powinny być stylizowane jak te klasy MUI”.

Jakieś konkretne sugestie, jak to rozwiązać?

dwjohnston
źródło

Odpowiedzi:

4

Oto moja sugestia (oczywiście nie jest prosta). Pobierz style z motywu MUI i wygeneruj styleznacznik na jego podstawie za pomocą react-helmet. Aby zrobić to dobrze, stworzyłem komponent „wrapper”, który wykonuje mapę. Wdrożyłem tylko podstawową zasadę, ale można ją rozszerzyć na wszystkie pozostałe.

W ten sposób każda zmiana dokonana w motywie wpłynie również na zmapowane selektory.

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

I użycie adaptera

<MuiAdapter theme={theme} />

Działające demo: https://codesandbox.io/s/reverent-mccarthy-3o856

zrzut ekranu

Mosh Feu
źródło
Lubię to myślenie. Problem z tym rozwiązaniem polega na tym, że nadal sam zasadniczo wdrażasz wszystkie style (tj. Kolor najechania kursorem, dopełnienie, promień obramowania itp.). Jeśli na przykład zdecydowałeś, że tekst przycisku powinien zostać podkreślony, musisz pamiętać, aby dodać text-decorationwłaściwość do kasku.
dwjohnston,
Rozumiem o co prosisz. Próbowałem podjąć inne podejście i manipulować MUI styletagi i dodać je .fc-selektorów zgodnie mapie ale mają konfliktów poziomów bazowych (takich jak display, paddingitd.), Więc nie wiem, jak to będzie działać nawet gdyby masz opcja migracji do scss. Na przykład : odesandbox.io/s/charming-sound-3xmj9
Mosh Feu
Haha - teraz to lubię. Później lepiej to sprawdzę.
dwjohnston,
3

Możesz utworzyć odwzorowanie między nazwami klas MUI a nazwami klas kalendarza, przechodząc do nich ref. Możliwe, że niektórzy nie nazywają tego „najlepszą praktyką” ... ale to rozwiązanie :). Zauważ, że zaktualizowałem twój komponent z komponentu funkcjonalnego do komponentu klasy, ale możesz to zrobić za pomocą zaczepów w komponencie funkcjonalnym.

Dodaj referencje

Dodaj element refdo elementu MUI, który chcesz ustawić jako odniesienie, w twoim przypadku Button.

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

I refna zawijanie divwokół komponentu, na który chcesz zmapować. Nie możesz dodać go bezpośrednio do komponentu, ponieważ nie dałoby nam to dostępu children.

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

Klasy map

Od componentDidMount()dodania dowolnej logiki, której potrzebujesz, aby kierować na właściwy węzeł DOM (dla twojego przypadku dodałem logikę dla typei matchingClass). Następnie uruchom tę logikę na wszystkich węzłach DOM FullCalendar i zamień classListna wszystkie pasujące.

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

Jest to może trochę trudne, ale spełnia twoje przyszłe wymagania dotyczące aktualizacji po aktualizacji interfejsu Material Material. Pozwoliłoby to również na zmianę classListelementu, który przekazujesz do elementu, co ma oczywiste zalety.

Ostrzeżenia

Jeśli komponent „zamapowany na” ( FullCalendar) zaktualizował klasy na elementach, na które celujesz (na przykład, jeśli dodano go .is-selecteddo bieżącego przycisku) lub doda nowe przyciski po zamontowaniu, musisz znaleźć sposób na śledzenie odpowiednich zmian i ponowne uruchomienie logika.

Powinienem również wspomnieć, że (oczywiście) zmienianie klas może mieć niezamierzone konsekwencje, takie jak zepsuty interfejs użytkownika, a ty musisz wymyślić, jak je naprawić.

Oto działająca piaskownica: https://codesandbox.io/s/determined-frog-3loyf

sallf
źródło
1
To działa. Pamiętaj, że nie musisz konwertować na komponent klasy - możesz to zrobić za pomocą haków.
dwjohnston
Tak, masz rację, można to zrobić za pomocą haków w funkcjonalnym elemencie. Zaktualizowałem odpowiedź, aby to odzwierciedlić.
sallf 13.12.19
Ładne, dość pragmatyczne podejście. Ale nie jest to wykonalne, ponieważ zawsze potrzebujesz referencji.
Aniket Chowdhury,