¿Cómo aplicaría los estilos administrados de Material-UI a elementos que no son material-ui, no reaccionan?

9

Tengo una aplicación en la que estoy usando Material UI y su proveedor de temas (usando JSS).

Ahora estoy incorporando fullcalendar-react , que en realidad no es una biblioteca React completa, es solo un delgado contenedor de componentes React alrededor del código original de fullcalendar.

Es decir, que no tengo acceso a cosas como los accesorios de renderizado para controlar cómo da estilo a sus elementos.

Sin embargo, le da acceso a los elementos DOM directamente, a través de una devolución de llamada que se llama cuando los procesa (por ejemplo, el método eventRender ).

Aquí hay un sandbox de demostración básico.

ingrese la descripción de la imagen aquí

Ahora, lo que quiero hacer es hacer que los componentes del calendario completo (por ejemplo, los botones) compartan el mismo aspecto que el resto de mi aplicación.

Una forma de hacer esto es que podría anular manualmente todos los estilos mirando los nombres de clase que está usando e implementando el estilo en consecuencia.

O bien, podría implementar un tema Bootstrap, como se sugiere en su documentación.

Pero el problema con cualquiera de estas soluciones es que:

  1. Seria mucho trabajo
  2. Tendría problemas de sincronización, si realizara cambios en mi tema MUI y olvidara actualizar el tema del calendario, se verían diferentes.

Lo que me gustaría hacer es:

  • Convierta mágicamente el tema MUI en un tema Bootstrap.
  • O cree una asignación entre los nombres de clase MUI y los nombres de clase de calendario, algo como:
.fc-button = .MuiButtonBase-root.MuiButton-root.MuiButton-contained
.fc-button-primary= .MuiButton-containedPrimary

No me importaría tener que masajear los selectores, etc. para que funcione (es decir, por ejemplo, los botones MUI tienen dos tramos internos, mientras que el calendario completo tiene solo uno). Se trata principalmente de cuando cambio el tema, no quiero tener que cambiarlo en dos lugares.

Usar algo como Sass con su @extendsintaxis sería lo que tengo en mente. Podría crear el CSS de calendario completo con Sass con bastante facilidad, pero ¿cómo podría Sass acceder a MuiTheme?

Tal vez podría tomar el enfoque opuesto: decirle a MUI 'Hola, estos nombres de clase aquí deberían tener el mismo estilo que estas clases de MUI'.

¿Alguna sugerencia concreta sobre cómo resolvería esto?

dwjohnston
fuente

Respuestas:

4

Aquí está mi sugerencia (obviamente, no es sencillo). Tome los estilos del tema MUI y genere styleetiquetas basadas en él usando react-helmet. Para hacerlo bien, creé un componente "envoltorio" que hace el mapa. Implementé solo la regla principal, pero se puede extender a todos los demás.

De esta manera, cualquier cambio que haga en el tema también afectará a los selectores asignados.

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

Y el uso del adaptador

<MuiAdapter theme={theme} />

Demostración de trabajo: https://codesandbox.io/s/reverent-mccarthy-3o856

captura de pantalla

Mosh Feu
fuente
Me gusta este pensamiento. El problema con esta solución es que básicamente todavía estaría implementando todos los estilos usted mismo (es decir, el color de desplazamiento, el relleno, el radio del borde, etc.). Si, por ejemplo, decidió que el texto del botón debe estar subrayado, deberá recordar agregar la text-decorationpropiedad al casco.
dwjohnston
Entiendo lo que pides. Intenté adoptar un enfoque diferente y manipular las styleetiquetas MUI y agregarles .fc-selectores de acuerdo con el mapa, pero tienen conflictos en los niveles base (como display, paddingetc.), por lo que no veo cómo funcionará incluso si tuviera el opción de migrarlo en scss. Por ejemplo: codesandbox.io/s/charming-sound-3xmj9
Mosh Feu
Jaja - ahora esto me gusta. Lo miraré mejor más adelante.
dwjohnston
3

Puede crear una asignación entre los nombres de clase MUI y los nombres de clase de calendario yendo a través de ref's. Es posible que esto no sea lo que algunos llamarían "mejores prácticas" ... pero es una solución :). Tenga en cuenta que actualicé su componente de un componente funcional a un componente de clase, pero podría lograr esto con ganchos en un componente funcional.

Añadir referencias

Agregue un refelemento de MUI que desee establecer como referencia, en su caso, el Button.

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

Y refa un ajuste divalrededor del componente al que desea asignar. No puede agregarlo directamente al componente ya que eso no nos daría acceso children.

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

Clases de mapas

Desde componentDidMount()agregar cualquier lógica que necesite para apuntar al nodo DOM correcto (para su caso, agregué lógica para typey matchingClass). Luego ejecute esa lógica en todos los nodos DOM de FullCalendar y reemplace los classListque coincidan.

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

Esto puede ser un poco pesado, pero cumple con su requisito de prueba futura para cuando actualice la interfaz de usuario de material de actualización. También le permitiría modificarlo a classListmedida que lo pasa a un elemento, lo que tiene beneficios obvios.

Advertencias

Si el componente 'mapeado' ( FullCalendar) actualizó las clases en los elementos a los que apunta (como si se agregara .is-selecteda un botón actual) o agrega nuevos botones después del montaje, entonces tendría que encontrar una manera de rastrear los cambios relevantes y volver a ejecutar la lógica.

También debo mencionar que (obviamente) alterar las clases podría tener consecuencias no deseadas, como una interfaz de usuario que no funciona, y tendrás que descubrir cómo solucionarlas.

Aquí está el sandbox de trabajo: https://codesandbox.io/s/determined-frog-3loyf

sallf
fuente
1
Esto funciona. Tenga en cuenta que no tiene que convertir a un componente de clase, puede usar ganchos para hacer esto.
dwjohnston
Ya tienes razón, esto se puede hacer con ganchos en un componente funcional. He actualizado la respuesta para reflejar eso.
sallf
Agradable, bastante el enfoque pragmático. Pero no es realmente factible, ya que siempre necesitaría una referencia.
Aniket Chowdhury