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

时间:2019-12-06 05:19:26

标签: css reactjs fullcalendar jss

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

我现在要合并fullcalendar-react,它实际上并不是一个完善的React库-只是原始的全日历代码周围的薄薄的React组件包装。

这就是说,我无权使用诸如渲染道具之类的东西来控制其元素样式。

但是,确实可以通过渲染元素时调用的回调(例如eventRender method)直接访问DOM元素。

Here's a basic demo sandbox.

enter image description here

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

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

或者-I could implement a Bootstrap theme - as suggested in their documentation.

但这两种解决方案的问题在于:

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

我想做的是:

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

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

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

也许我可以采取相反的方法-告诉MUI'嘿,这里的这些类名称应像这些MUI类一样设置样式”。

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

2 个答案:

答案 0 :(得分:4)

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

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

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

screen shot

答案 1 :(得分:3)

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

添加参考

向要设置为引用的MUI元素添加ref,在您的情况下为Button

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

refdiv包裹在要映射的组件周围。您无法将其直接添加到组件中,因为那样一来我们就无法访问children

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

地图类

componentDidMount()中添加针对正确的DOM节点所需的任何逻辑(对于您的情况,我为typematchingClass添加了逻辑)。然后在所有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