我如何将Material-UI管理的样式应用于非Material-UI,非反应性元素?


9

我有一个使用Material UI及其主题提供程序(使用JSS)的应用程序。

我现在合并了fullcalendar-react,它实际上不是一个完整的React库-只是原始的fullcalendar代码的一个薄的React组件包装。

也就是说,我无法访问诸如渲染道具之类的东西来控制其元素样式。

但是,它确实允许您通过渲染元素时调用的回调直接访问DOM元素(例如eventRender方法)。

这是一个基本的演示沙箱。

在此处输入图片说明

现在,我要做的是使完整日历组件(例如,按钮)具有与我的应用程序其余部分相同的外观。

一种方法是,通过查看正在使用的类名称并相应地实现样式,我可以手动覆盖所有样式。

或者- 我可以实现Bootstrap主题-如其文档中所建议。

但是这些解决方案中的任何一个的问题在于:

  1. 会很多工作
  2. 如果我对MUI主题进行了更改,却忘记了更新日历主题,则它们将出现不同的同步问题。

我想做的是:

  • 神奇地将MUI主题转换为Bootstrap主题。
  • 或在MUI类名称和日历类名称之间创建映射,如下所示:
.fc-button = .MuiButtonBase-root.MuiButton-root.MuiButton-contained
.fc-button-primary= .MuiButton-containedPrimary

我不介意按摩选择器等使其工作(例如,MUI按钮有两个内部范围,而完整日历只有一个)。这主要是关于更改主题的时间-不想在两个地方都进行更改。

@extend我想到的是使用像Sass这样的语法。我可以很容易地用Sass创建全日历CSS-但是Sass如何访问MuiTheme?

也许我可以采取相反的方法-告诉MUI'嘿,这些类名的样式应类似于这些MUI类'。

关于如何解决此问题的任何具体建议?

Answers:


4

这是我的建议(显然,这不是直截了当的)。从MUI主题中获取样式,并style使用来基于该主题生成标签react-helmet。为了很好地完成此任务,我创建了一个“包装器”组件来进行地图绘制。我仅实现了主要规则,但可以将其扩展到所有其他规则。

这样,您对主题所做的任何更改也会影响映射的选择器。

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

和适配器的使用

<MuiAdapter theme={theme} />

工作演示:https//codesandbox.io/s/reverent-mccarthy-3o856

屏幕截图


我喜欢这种想法。该解决方案的问题在于,您基本上仍将自己实现所有样式(即,悬停颜色,填充,边框半径等)。例如,如果您确定按钮文字应带有下划线,则需要记住将text-decoration属性添加到头盔中。
dwjohnston

我了解您的要求。我试图采取不同的方法和操作MUI style标签,并添加他们.fc-根据地图选择,但他们已经在基础水平(如冲突displaypadding等等),所以我不明白它是如何甚至会工作,如果你将有在scss中迁移它的选项。例如:codesandbox.io/s/charming-sound-3xmj9
Mosh Feu,

哈哈-现在我喜欢。稍后我会给它更好的外观。
dwjohnston

3

您可以通过来在MUI类名称和日历类名称之间创建映射ref。这可能不是某些人所说的“最佳实践”……但这是一个解决方案:)。请注意,我已将您的组件从功能组件更新为类组件,但是您可以通过功能组件中的钩子来完成此操作。

添加裁判

ref在您要设置为参考的MUI元素中添加一个(对于您的情况是)Button

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

ref环绕div您要映射到的组件。您无法将其直接添加到组件中,因为那样一来我们就无法访问children

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

地图类

通过componentDidMount()添加需要针对正确的DOM节点的任何逻辑(对于您的情况,我为type和添加了逻辑matchingClass)。然后,在所有FullCalendar DOM节点上运行该逻辑,并替换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);
    }
  });
};

这也许有点费劲,但是它满足了您在更新更新Material UI时的未来证明要求。它还将允许您在将classList其传递到元素时对其进行更改,这具有明显的好处。

注意事项

如果“映射到”组件(FullCalendar)更新了您要定位的元素上的类(例如,将其添加.is-selected到当前按钮中)或在安装后添加了新按钮,则您必须找出一种方法来跟踪相关更改并重新运行逻辑。

我还应该提到,(显然)更改类可能会导致意想不到的后果,例如UI中断,您必须弄清楚如何修复它们。

这是工作的沙箱:https : //codesandbox.io/s/determined-frog-3loyf


1
这可行。请注意,您不必转换为类组件-您可以使用钩子来执行此操作。
dwjohnston

是的,您可以使用功能组件中的钩子来完成。我已经更新了答案以反映这一点。
Sallf

不错,很实用。但实际上并不可行,因为您始终需要参考。
Aniket Chowdhury
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.