Como eu aplicaria os estilos gerenciados da UI do material a elementos não-ui e não-reagentes?

9

Eu tenho um aplicativo em que estou usando a UI do material e seu provedor de temas (usando JSS).

Agora estou incorporando o fullcalendar-react , que não é realmente uma biblioteca React completa - é apenas um invólucro fino do componente React em torno do código original do fullcalendar.

Ou seja, não tenho acesso a coisas como render adereços para controlar como ele estiliza seus elementos.

No entanto, fornece acesso diretamente aos elementos DOM, através de um retorno de chamada que é chamado quando os renderiza (por exemplo, o método eventRender ).

Aqui está uma sandbox básica de demonstração.

insira a descrição da imagem aqui

Agora, o que eu quero fazer é fazer com que os componentes do Calendário Completo (por exemplo, os botões) compartilhem a mesma aparência que o restante do meu aplicativo.

Uma maneira de fazer isso é que eu poderia substituir manualmente todos os estilos, observando os nomes das classes que está usando e implementando o estilo adequadamente.

Ou - eu poderia implementar um tema de Bootstrap - como sugerido na documentação deles.

Mas o problema com qualquer uma dessas soluções é que:

  1. Seria muito trabalho
  2. Eu teria problemas de sincronização. Se eu fizesse alterações no meu tema MUI e esquecesse de atualizar o tema do calendário, elas pareceriam diferentes.

O que eu gostaria de fazer é:

  • Converta magicamente o tema MUI para o tema Bootstrap.
  • Ou crie um mapeamento entre os nomes de classe MUI e os nomes de classe de calendário, algo como:
.fc-button = .MuiButtonBase-root.MuiButton-root.MuiButton-contained
.fc-button-primary= .MuiButton-containedPrimary

Eu não me importaria de ter que massagear os seletores, etc., para fazê-lo funcionar (por exemplo: os botões MUI têm dois intervalos internos, enquanto o Calendário Completo possui apenas um). É principalmente quando eu mudo o tema - não quero alterá-lo em dois lugares.

Usar algo como Sass com sua @extendsintaxe seria o que eu tenho em mente. Eu poderia criar o CSS de calendário completo com o Sass com bastante facilidade - mas como o Sass obteria acesso ao MuiTheme?

Talvez eu possa adotar a abordagem oposta - diga ao MUI 'Ei, esses nomes de classe aqui devem ter o estilo dessas classes MUI'.

Alguma sugestão concreta sobre como eu resolveria isso?

Dwjohnston
fonte

Respostas:

4

Aqui está minha sugestão (obviamente, não é direta). Pegue os estilos do tema MUI e gere uma styletag com base nele react-helmet. Para isso, criei um componente "wrapper" que faz o mapa. Eu implementei apenas a regra principal, mas ela pode ser estendida a todas as outras.

Dessa forma, qualquer alteração feita no tema também afetará os seletores mapeados.

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

E o uso do adaptador

<MuiAdapter theme={theme} />

Demonstração de trabalho: https://codesandbox.io/s/reverent-mccarthy-3o856

captura de tela

Mosh Feu
fonte
Eu gosto desse pensamento. O problema com esta solução é que você ainda estaria implementando todos os estilos (por exemplo, a cor do cursor, o preenchimento, o raio da borda etc.). Se, por exemplo, você decidiu que o texto do botão deveria ser sublinhado, lembre-se de adicionar a text-decorationpropriedade ao capacete.
dwjohnston
Eu entendo o que você pede. Tentei adotar uma abordagem diferente, manipular as styletags MUI e adicioná-las aos .fc-seletores de acordo com o mapa, mas elas têm conflitos nos níveis básicos (como display, paddingetc.), portanto não vejo como funcionará, mesmo se você tiver o opção para migrá-lo em scss. Por exemplo: codesandbox.io/s/charming-sound-3xmj9
Mosh Feu
Haha - agora isso eu gosto. Vou dar uma olhada melhor mais tarde.
Dwjohnston
3

Você pode criar um mapeamento entre os nomes das classes MUI e os nomes das classes de calendário, passando por ref's. É possível que isso não seja o que alguns chamam de "melhores práticas" ... mas é uma solução :). Observe que eu atualizei seu componente de um componente funcional para um componente de classe, mas você pode fazer isso com ganchos em um componente funcional.

Adicionar refs

Adicione refa ao elemento MUI que você deseja definir como referência, no seu caso, o Button.

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

E uma refquebra automática em divtorno do componente para o qual você deseja mapear. Você não pode adicioná-lo diretamente ao componente, pois isso não nos daria acesso children.

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

Classes de mapa

De componentDidMount()add qualquer lógica você precisa para atingir o nó DOM correto (para o seu caso, eu adicionei lógica para typee matchingClass). Em seguida, execute essa lógica em todos os nós do FullCalendar DOM e substitua-os classListpor qualquer que corresponda.

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

Talvez isso seja um pouco pesado, mas atende aos seus requisitos de prova futura para quando você atualiza a atualização da UI do material. Também permitiria alterar a classListmedida que você a passa para um elemento, o que tem benefícios óbvios.

Ressalvas

Se o componente 'mapeado para' ( FullCalendar) atualizou as classes nos elementos que você segmenta (como se fosse adicionado .is-selecteda um botão atual) ou adiciona novos botões após a montagem, você teria que descobrir uma maneira de rastrear as alterações relevantes e executar novamente a lógica.

Também devo mencionar que (obviamente) a alteração de classes pode ter consequências não intencionais, como uma quebra de interface do usuário, e você precisará descobrir como corrigi-las.

Aqui está a caixa de areia de trabalho: https://codesandbox.io/s/determined-frog-3loyf

sallf
fonte
11
Isso funciona. Observe que você não precisa converter para um componente de classe - você pode usar ganchos para fazer isso.
dwjohnston
Você está correto, isso pode ser feito com ganchos em um componente funcional. Atualizei a resposta para refletir isso.
sallf
Nice, bastante a abordagem pragmática. Mas não é realmente viável, pois você sempre precisaria de uma ref.
Aniket Chowdhury