简体   繁体   English

我如何将 Material-UI 托管样式应用于非 Material-ui、非 React 元素?

[英]How would I apply Material-UI managed styles to non-material-ui, non-react elements?

I have an application where I'm using Material UI and its theme provider (using JSS).我有一个应用程序,我在其中使用 Material UI 及其主题提供程序(使用 JSS)。

I'm now incorporating fullcalendar-react , which isn't really a fully fledged React library - it's just a thin React component wrapper around the original fullcalendar code.我现在正在合并fullcalendar-react ,它并不是一个真正成熟的 React 库——它只是一个围绕原始 fullcalendar 代码的薄 React 组件包装器。

That is to say, that I don't have access to things like render props to control how it styles its elements.也就是说,我无法访问诸如渲染道具之类的东西来控制它如何设置其元素的样式。

It does however, give you access to the DOM elements directly, via a callback that is called when it renders them (eg. the eventRender method ).但是,它确实让您可以通过在呈现它们时调用的回调(例如eventRender 方法)直接访问 DOM 元素。

Here's a basic demo sandbox.这是一个基本的演示沙箱。

在此处输入图片说明

Now what I'm wanting to do is make Full Calendar components (eg, the buttons) share the same look and feel as the rest of my application.现在我想要做的是使完整日历组件(例如,按钮)与我的应用程序的其余部分共享相同的外观和感觉。

One way to do this, is that I could manually override all of the styles by looking at the class names it's using and implementing the style accordingly.一种方法是,我可以通过查看它使用的类名并相应地实现样式来手动覆盖所有样式。

Or - I could implement a Bootstrap theme - as suggested in their documentation.或者 -我可以实现一个 Bootstrap 主题 - 正如他们的文档中所建议的那样。

But the problem with either of these solutions, is that that:但是这些解决方案中的任何一个的问题在于:

  1. It would be a lot of work这将是很多工作
  2. I would have synchronisation problems, if I made changes to my MUI theme and forgot to update the calendar theme they would look different.我会遇到同步问题,如果我更改了我的 MUI 主题并忘记更新日历主题,它们看起来会有所不同。

What I would like to do is either:我想做的是:

  • Magically convert the MUI theme to a Bootstrap theme.神奇地将 MUI 主题转换为 Bootstrap 主题。
  • Or create a mapping between MUI class names and the calendar class names, something like:或者在 MUI 类名和日历类名之间创建一个映射,例如:
.fc-button = .MuiButtonBase-root.MuiButton-root.MuiButton-contained
.fc-button-primary= .MuiButton-containedPrimary

I wouldn't mind having to massage the selectors etc to make it work (ie. For example - MUI Buttons have two internal spans, whereas Full Calendar have just one).我不介意必须按摩选择器等以使其工作(例如 - MUI 按钮有两个内部跨度,而完整日历只有一个)。 It's mostly about when I change the theme - don't want to have to change it in two places.这主要是关于我何时更改主题 - 不想在两个地方更改它。

Using something like Sass with its @extend syntax would is what I have in mind.使用 Sass 之类的东西和它的@extend语法就是我的想法。 I could create the full-calendar CSS with Sass easily enough - but how would Sass get access to the MuiTheme?我可以很容易地使用 Sass 创建完整的日历 CSS - 但是 Sass 如何访问 MuiTheme?

Perhaps I could take the opposite approach - tell MUI 'Hey these class names here should be styled like these MUI classes'.也许我可以采取相反的方法 - 告诉 MUI '嘿,这里的这些类名的样式应该像这些 MUI 类一样'。

Any concrete suggestions on how I would solve this?关于我如何解决这个问题的任何具体建议?

Here is my suggestion (obviously, it's not straight forward).这是我的建议(显然,这不是直接的)。 Take the styles from the MUI theme and generate style tag based on it using react-helmet .从 MUI 主题中获取样式并使用react-helmet基于它生成style标签。 To do it event nicely, I created a "wrapper" component that do the map.为了很好地完成它,我创建了一个“包装器”组件来完成地图。 I implemented only the primary rule but it can be extended to all the others.我只实施了主要规则,但它可以扩展到所有其他规则。

This way, any change you will do in the theme will affect the mapped selectors too.这样,您在主题中所做的任何更改也会影响映射的选择器。

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

And the use of the adapter以及适配器的使用

<MuiAdapter theme={theme} />

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

截屏

You could create a mapping between MUI class names and the calendar class names by going through ref 's.您可以通过遍历ref来创建 MUI 类名和日历类名之间的映射。 It's possible that this is not what some would call "best practice"...but it's a solution :).这可能不是某些人所说的“最佳实践”……但这是一个解决方案:)。 Note that I updated your component from a functional component to a class component, but you could accomplish this with hooks in a functional component.请注意,我将您的组件从功能组件更新为类组件,但您可以使用功能组件中的钩子来完成此操作。

Add refs添加参考

Add a ref to the MUI element you want to set as a reference, in your case the Button .ref添加到要设置为引用的 MUI 元素,在您的情况下为Button

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

And a ref to a wrapping div around the component you want to map to.以及对要映射到的组件周围的环绕divref You can't add it directly to the component since that wouldn't give us access to children .您不能将它直接添加到组件中,因为这不会让我们访问children

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

Map classes地图类

From componentDidMount() add whatever logic you need to target the correct DOM node (for your case, I added logic for type and matchingClass ).componentDidMount()添加您需要的任何逻辑来定位正确的 DOM 节点(对于您的情况,我添加了typematchingClass逻辑)。 Then run that logic on all FullCalendar DOM nodes and replace the classList on any that match.然后在所有 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);
    }
  });
};

This is maybe a little heavy handed, but it meets your future proof requirement for when you updated update Material UI.这可能有点笨手笨脚,但它满足您更新 Material UI 时的未来证明要求。 It would also allow you to alter the classList as you pass it to an element, which has obvious benefits.它还允许您在将classList传递给元素时更改它,这具有明显的好处。

Caveats注意事项

If the 'mapped-to' component ( FullCalendar ) updated classes on the elements you target (like if it added .is-selected to a current button) or adds new buttons after mounting then you'd have to figure out a way to track the relevant changes and rerun the logic.如果“映射到”组件 ( FullCalendar ) 更新了您定位的元素上的类(例如,如果将.is-selected添加到当前按钮)或在安装后添加新按钮,则您必须想办法跟踪相关更改并重新运行逻辑。

I should also mention that (obviously) altering classes might have unintended consequences like a breaking UI and you'll have to figure out how to fix them.我还应该提到(显然)更改类可能会产生意想不到的后果,例如破坏 UI,您必须弄清楚如何修复它们。

Here's the working sandbox: https://codesandbox.io/s/determined-frog-3loyf这是工作沙箱: https : //codesandbox.io/s/determined-frog-3loyf

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM