PlasmoHQ / plasmo

🧩 The Browser Extension Framework
https://www.plasmo.com
MIT License
10.64k stars 370 forks source link

[BUG] Import CSS to CSUI doesn't work as expected with Mantine #776

Open caocanx opened 1 year ago

caocanx commented 1 year ago

What happened?

I am using mantine to create a CSUI. I need to import @mantine/core/styles.css. Following the Import Stylesheet guide, it doesn't work well. The code:

import { Button, Input, MantineProvider } from "@mantine/core"
import styleText from "data-text:@mantine/core/styles.css"
import type { PlasmoGetStyle } from "plasmo"

export const getStyle: PlasmoGetStyle = () => {
  const style = document.createElement("style")
  style.textContent = styleText
  return style
}

const UI = () => {
  return (
    <MantineProvider>
      <Button fullWidth>Button</Button>
      <Input variant="filled" placeholder="Input component" />
    </MantineProvider>
  )
}

export default UI

The result: (the button should have a blue background and the input should have a light gray background)

CleanShot 2023-10-02 at 00 28 38@2x

If I add a normal import line, some styles will work, some not. The code:

import { Button, Input, MantineProvider } from "@mantine/core"
import styleText from "data-text:@mantine/core/styles.css"
import type { PlasmoGetStyle } from "plasmo"

import "@mantine/core/styles.css" // add this line

export const getStyle: PlasmoGetStyle = () => {
  const style = document.createElement("style")
  style.textContent = styleText
  return style
}

const UI = () => {
  return (
    <MantineProvider>
      <Button fullWidth>Button</Button>
      <Input variant="filled" placeholder="Input component" />
    </MantineProvider>
  )
}

export default UI

The result:

CleanShot 2023-10-02 at 00 18 54@2x

Inspect the input component, it shows that some css variables are not define.

CleanShot 2023-10-02 at 00 20 15@2x

Same components works well in popup

CleanShot 2023-10-02 at 00 23 59@2x

Version

Latest

What OS are you seeing the problem on?

No response

What browsers are you seeing the problem on?

No response

Relevant log output

No response

(OPTIONAL) Contribution

Code of Conduct

louisgv commented 1 year ago

This is a tricky one. ref: #773

If you console.log the styleText, what does it contain?

caocanx commented 1 year ago

This is a tricky one. ref: #773

If you console.log the styleText, what does it contain?

Look like it contains all the content of @mantine/core/styles.css, 6898 lines css.

ailikecreative commented 1 year ago

have you ever tried this method https://mantine.dev/styles/vanilla-extract/

caocanx commented 1 year ago

have you ever tried this method https://mantine.dev/styles/vanilla-extract/

This is not highly relevant to the problems I am currently facing. What I want now is to ensure that all default styles work as expected. Then I will use Vanilla extract or CSS Modules to make some custom styles.

mylooksback commented 1 year ago

Yes, I also encountered it

0xjoo commented 1 year ago

It seems there are 2 separate issues here:

  1. Mantine adds CSS variables to :root by default (aka the <html> element, since shadow DOMs are DocumentFragments, not Documents) (ref: https://mantine.dev/theming/mantine-provider/#cssvariablesselector)
  2. CSUI injects the shadow DOM outside of <body>, which doesn't allow some Mantine global styles to be applied (ref: https://mantine.dev/styles/global-styles/#body-and-root-elements-styles).

For #⁠1, MantineProvider has a prop that allows you to change where CSS variables are added. In our case, we want it to be :host to apply to the shadow host (ref: https://mantine.dev/theming/mantine-provider/#cssvariablesselector):

<MantineProvider cssVariablesSelector=":host"></MantineProvider>

For #⁠2, manually override styles that apply to :root, html, and body:

Could you let us know if this works for you?

tianhuil commented 9 months ago

Hi @0xjoo: thank you for your solution. I've applied it and it works for buttons image but not for inputs (the below is using <TextInput variant="filled" /> : image

Information, this is working with "@mantine/core": "^7.2.2", and "plasmo": "0.84.0",.

bepitulaz commented 7 months ago

It seems there are 2 separate issues here:

  1. Mantine adds CSS variables to :root by default (aka the <html> element, since shadow DOMs are DocumentFragments, not Documents) (ref: https://mantine.dev/theming/mantine-provider/#cssvariablesselector)
  2. CSUI injects the shadow DOM outside of <body>, which doesn't allow some Mantine global styles to be applied (ref: https://mantine.dev/styles/global-styles/#body-and-root-elements-styles).

For #⁠1, MantineProvider has a prop that allows you to change where CSS variables are added. In our case, we want it to be :host to apply to the shadow host (ref: https://mantine.dev/theming/mantine-provider/#cssvariablesselector):

<MantineProvider cssVariablesSelector=":host"></MantineProvider>

For #⁠2, manually override styles that apply to :root, html, and body:

:host {
    height: 100%;
    margin: 0;
    color-scheme: var(--mantine-color-scheme);
    font-family: var(--mantine-font-family);
    font-size: var(--mantine-font-size-md);
    line-height: var(--mantine-line-height);
    background-color: var(--mantine-color-body);
    color: var(--mantine-color-text);
    -webkit-font-smoothing: var(--mantine-webkit-font-smoothing);
    -moz-osx-font-smoothing: var(--mantine-moz-font-smoothing);
}
  • Import the CSS file to your CSUI, along with the Mantine global styles:
import "@mantine/core/styles.css";
import globalCss from "data-text:@mantine/core/styles.css";
import shadowCss from "data-text:~shadow.css";
  • Export the getStyle function with both CSS applied:
export const getStyle: PlasmoGetStyle = () => {
    const style = document.createElement("style");
    style.textContent = globalCss + shadowCss;
    return style;
};

Could you let us know if this works for you?

I've just encountered this issue too. And, I can say this solution works. I used the solution above in my extension.

But, then a new problem came. The style from Mantine leaks to the main web page. It overrides some styles from the web page with Mantine's style.

Currently, I'm still trying to solve this issue. I'll post here again once I can solve this issue.

EDIT: Additional information, my extension is opening a popup on top of the main page.

bepitulaz commented 7 months ago

I've successfully solve my issue above. The problem was in this import:

import "@mantine/core/styles.css";

When we use it inside popup or the side panel page, it will work fine because it's isolated from the main webpage. However, when we create an extension with overlay/inline with the webpage, the import above will override the CSS of the main webpage because it was imported globally.

If we removed that line, some Mantine's components won't work. For my case, Mantine's modal didn't work because it was portal-ed from shadow DOM to the main webpage. So, the modal can't access the CSS inside the shadow DOM.

My solution is I implemented my own custom modal that can work inside shadow DOM.

murnifine commented 7 months ago

I've successfully solve my issue above. The problem was in this import:

import "@mantine/core/styles.css";

When we use it inside popup or the side panel page, it will work fine because it's isolated from the main webpage. However, when we create an extension with overlay/inline with the webpage, the import above will override the CSS of the main webpage because it was imported globally.

If we removed that line, some Mantine's components won't work. For my case, Mantine's modal didn't work because it was portal-ed from shadow DOM to the main webpage. So, the modal can't access the CSS inside the shadow DOM.

My solution is I implemented my own custom modal that can work inside shadow DOM.

Can you share it? I'm also having issues when using modal and darkmode.

bepitulaz commented 7 months ago

@murnifine sure. My project is open-source. Take a look at this file https://github.com/chloe-matt/sonastik/blob/main/browser-extension/contents/plasmo-overlay.tsx

murnifine commented 6 months ago

mantine v7.9.0 was recently released and seems to support the use of emotion again https://mantine.dev/styles/emotion/

0xjoo commented 6 months ago

I've successfully solve my issue above. The problem was in this import:

import "@mantine/core/styles.css";

When we use it inside popup or the side panel page, it will work fine because it's isolated from the main webpage. However, when we create an extension with overlay/inline with the webpage, the import above will override the CSS of the main webpage because it was imported globally.

If we removed that line, some Mantine's components won't work. For my case, Mantine's modal didn't work because it was portal-ed from shadow DOM to the main webpage. So, the modal can't access the CSS inside the shadow DOM.

My solution is I implemented my own custom modal that can work inside shadow DOM.

Ran into the same issue and found a workaround. Not fully tested, so please let us know if you run into any issues if you try this out.

As mentioned above, the main issue is due to the following import, which applies Mantine styles globally:

import "@mantine/core/styles.css";

However, if we remove this import, some styles are not applied, even when exporting Plasmo's getStyle function.

It appears that Plasmo's getStyle function adds the CSS style directly under #shadow-root, which does not correctly apply to Mantine components within.

It seems that when the cssVariablesSelector is set to :host, Mantine doesn't correctly find CSS variables from the <style> tag that Plasmo inserted underneath the shadow DOM.

To force Mantine to correctly use the inline styles, we can make the following changes:

  1. Import the default Mantine styles (as text):

    import mantineCss from "data-text:@mantine/core/styles.css";
  2. Import custom CSS styles to apply global styles within the shadow DOM (as text):

    import overrideCss from "data-text:~styles/override.css";

    where override.css has the following content:

    div.plasmo-csui-container {
    margin: 0;
    color-scheme: var(--mantine-color-scheme);
    font-family: var(--mantine-font-family);
    font-size: var(--mantine-font-size-md);
    line-height: var(--mantine-line-height);
    background-color: var(--mantine-color-body);
    color: var(--mantine-color-text);
    -webkit-font-smoothing: var(--mantine-webkit-font-smoothing);
    -moz-osx-font-smoothing: var(--mantine-moz-font-smoothing);
    }
  3. Define MantineProvider to use the following props:

    <MantineProvider
    theme={...}
    cssVariablesSelector="div.plasmo-csui-container"
    getRootElement={() =>
    document.querySelector("plasmo-csui").shadowRoot.querySelector("div.plasmo-csui-container")
    }
    >
    <style>{mantineCss + overrideCss}</style>
    {...}
    </MantineProvider>

The main difference you'll notice is that we changed the cssVariablesSelector prop from :host to div.plasmo-csui-container. This allows Mantine to correctly source CSS variables from within the div.plasmo-csui-container container.

You'll also notice that we updated the override.css CSS to use the same container as its selector.

We also define the getRootElement prop to correctly set the data-mantine-color-scheme attribute (Ref: https://mantine.dev/theming/mantine-provider/#getrootelement).

Lastly, we drop the use of Plasmo's getStyle function, and apply it inline via <style> tags within MantineProvider, so that the styles are inserted inline within the div.plasmo-csui-container container.

Full implementation via a dedicated theme.tsx file:

import { createTheme, MantineProvider, mergeThemeOverrides, Modal } from "@mantine/core";
import mantineCss from "data-text:@mantine/core/styles.css";
import overrideCss from "data-text:~styles/override.css";

export const defaultTheme = createTheme({});

const contentTheme = createTheme({
  components: {
    Modal: Modal.extend({ defaultProps: { withinPortal: false } })
  }
});

const mergedTheme = mergeThemeOverrides(defaultTheme, contentTheme);

export function ContentProvider({ children }) {
  return (
    <MantineProvider
      theme={mergedTheme}
      defaultColorScheme="auto"
      cssVariablesSelector="div.plasmo-csui-container"
      getRootElement={() =>
        document.querySelector("plasmo-csui").shadowRoot.querySelector("div.plasmo-csui-container")
      }
    >
      <style>{mantineCss + overrideCss}</style>
      {children}
    </MantineProvider>
  );
}

where defaultTheme can be imported to be used in your app's MantineProvider, while still applying to CSUI components. contentTheme includes theme overrides that should only be applied to CSUI components. mergedTheme merges the two and uses it within a dedicated ContentProvider, which wraps MantineProvider with the necessary overrides to apply Mantine styles to CSUI components. This should make it easier to develop CSUI components, since all the overrides are done locally in this file.

Lastly, make sure to set modals (among other components) to render within the shadow DOM, not via a portal:

const theme = createTheme({
  components: {
    Modal: Modal.extend({ defaultProps: { withinPortal: false } })
  }
});

This allows styles to be correctly applied to the modal, since it will render with the shadow DOM's styles that we set inline above.

Please let us know if this works for you!

ailikecreative commented 6 months ago

I've successfully solve my issue above. The problem was in this import:

import "@mantine/core/styles.css";

When we use it inside popup or the side panel page, it will work fine because it's isolated from the main webpage. However, when we create an extension with overlay/inline with the webpage, the import above will override the CSS of the main webpage because it was imported globally. If we removed that line, some Mantine's components won't work. For my case, Mantine's modal didn't work because it was portal-ed from shadow DOM to the main webpage. So, the modal can't access the CSS inside the shadow DOM. My solution is I implemented my own custom modal that can work inside shadow DOM.

Ran into the same issue and found a workaround. Not fully tested, so please let us know if you run into any issues if you try this out.

As mentioned above, the main issue is due to the following import, which applies Mantine styles globally:

import "@mantine/core/styles.css";

However, if we remove this import, some styles are not applied, even when exporting Plasmo's getStyle function.

It appears that Plasmo's getStyle function adds the CSS style directly under #shadow-root, which does not correctly apply to Mantine components within.

It seems that when the cssVariablesSelector is set to :host, Mantine doesn't correctly find CSS variables from the <style> tag that Plasmo inserted underneath the shadow DOM.

To force Mantine to correctly use the inline styles, we can make the following changes:

  1. Import the default Mantine styles (as text):
import mantineCss from "data-text:@mantine/core/styles.css";
  1. Import custom CSS styles to apply global styles within the shadow DOM (as text):
import overrideCss from "data-text:~styles/override.css";

where override.css has the following content:

div.plasmo-csui-container {
  margin: 0;
  color-scheme: var(--mantine-color-scheme);
  font-family: var(--mantine-font-family);
  font-size: var(--mantine-font-size-md);
  line-height: var(--mantine-line-height);
  background-color: var(--mantine-color-body);
  color: var(--mantine-color-text);
  -webkit-font-smoothing: var(--mantine-webkit-font-smoothing);
  -moz-osx-font-smoothing: var(--mantine-moz-font-smoothing);
}
  1. Define MantineProvider to use the following props:
<MantineProvider
  theme={...}
  cssVariablesSelector="div.plasmo-csui-container"
  getRootElement={() =>
    document.querySelector("plasmo-csui").shadowRoot.querySelector("div.plasmo-csui-container")
  }
>
  <style>{mantineCss + overrideCss}</style>
  {...}
</MantineProvider>

The main difference you'll notice is that we changed the cssVariablesSelector prop from :host to div.plasmo-csui-container. This allows Mantine to correctly source CSS variables from within the div.plasmo-csui-container container.

You'll also notice that we updated the override.css CSS to use the same container as its selector.

We also define the getRootElement prop to correctly set the data-mantine-color-scheme attribute (Ref: https://mantine.dev/theming/mantine-provider/#getrootelement).

Lastly, we drop the use of Plasmo's getStyle function, and apply it inline via <style> tags within MantineProvider, so that the styles are inserted inline within the div.plasmo-csui-container container.

Full implementation via a dedicated theme.tsx file:

import { createTheme, MantineProvider, mergeThemeOverrides, Modal } from "@mantine/core";
import mantineCss from "data-text:@mantine/core/styles.css";
import overrideCss from "data-text:~styles/override.css";

export const defaultTheme = createTheme({});

const contentTheme = createTheme({
  components: {
    Modal: Modal.extend({ defaultProps: { withinPortal: false } })
  }
});

const mergedTheme = mergeThemeOverrides(defaultTheme, contentTheme);

export function ContentProvider({ children }) {
  return (
    <MantineProvider
      theme={mergedTheme}
      defaultColorScheme="auto"
      cssVariablesSelector="div.plasmo-csui-container"
      getRootElement={() =>
        document.querySelector("plasmo-csui").shadowRoot.querySelector("div.plasmo-csui-container")
      }
    >
      <style>{mantineCss + overrideCss}</style>
      {children}
    </MantineProvider>
  );
}

where defaultTheme can be imported to be used in your app's MantineProvider, while still applying to CSUI components. contentTheme includes theme overrides that should only be applied to CSUI components. mergedTheme merges the two and uses it within a dedicated ContentProvider, which wraps MantineProvider with the necessary overrides to apply Mantine styles to CSUI components. This should make it easier to develop CSUI components, since all the overrides are done locally in this file.

Lastly, make sure to set modals (among other components) to render within the shadow DOM, not via a portal:

const theme = createTheme({
  components: {
    Modal: Modal.extend({ defaultProps: { withinPortal: false } })
  }
});

This allows styles to be correctly applied to the modal, since it will render with the shadow DOM's styles that we set inline above.

Please let us know if this works for you!

I have tried it, this approach seems to be better, but there are still a few problems that I encountered when trying some components.

https://github.com/PlasmoHQ/plasmo/assets/123402607/ea522800-1eda-46dc-a014-9e19b31a3502

// theme.tsx
. . .
const contentTheme = createTheme({
  components: {
    Modal: Modal.extend({ defaultProps: { withinPortal: false } }),
    Drawer: Drawer.extend({ defaultProps: { withinPortal: false } }),
    Dialog: Dialog.extend({ defaultProps: { withinPortal: false } }),
    Affix: Affix.extend({ defaultProps: { withinPortal: false } }),
    Menu: Menu.extend({ defaultProps: { withinPortal: false } }),
    HoverCard: HoverCard.extend({ defaultProps: { withinPortal: false } }),
    Popover: Popover.extend({ defaultProps: { withinPortal: false } }),
    Notifications: Notifications.extend({ defaultProps: { withinPortal: false, position: "bottom-right" } })
  }
})
. . .
Eve-Sama commented 5 months ago

I've successfully solve my issue above. The problem was in this import:

import "@mantine/core/styles.css";

When we use it inside popup or the side panel page, it will work fine because it's isolated from the main webpage. However, when we create an extension with overlay/inline with the webpage, the import above will override the CSS of the main webpage because it was imported globally. If we removed that line, some Mantine's components won't work. For my case, Mantine's modal didn't work because it was portal-ed from shadow DOM to the main webpage. So, the modal can't access the CSS inside the shadow DOM. My solution is I implemented my own custom modal that can work inside shadow DOM.

Ran into the same issue and found a workaround. Not fully tested, so please let us know if you run into any issues if you try this out.

As mentioned above, the main issue is due to the following import, which applies Mantine styles globally:

import "@mantine/core/styles.css";

However, if we remove this import, some styles are not applied, even when exporting Plasmo's getStyle function.

It appears that Plasmo's getStyle function adds the CSS style directly under #shadow-root, which does not correctly apply to Mantine components within.

It seems that when the cssVariablesSelector is set to :host, Mantine doesn't correctly find CSS variables from the <style> tag that Plasmo inserted underneath the shadow DOM.

To force Mantine to correctly use the inline styles, we can make the following changes:

1. Import the default Mantine styles (as text):
import mantineCss from "data-text:@mantine/core/styles.css";
2. Import custom CSS styles to apply global styles within the shadow DOM (as text):
import overrideCss from "data-text:~styles/override.css";

where override.css has the following content:

div.plasmo-csui-container {
  margin: 0;
  color-scheme: var(--mantine-color-scheme);
  font-family: var(--mantine-font-family);
  font-size: var(--mantine-font-size-md);
  line-height: var(--mantine-line-height);
  background-color: var(--mantine-color-body);
  color: var(--mantine-color-text);
  -webkit-font-smoothing: var(--mantine-webkit-font-smoothing);
  -moz-osx-font-smoothing: var(--mantine-moz-font-smoothing);
}
3. Define `MantineProvider` to use the following props:
<MantineProvider
  theme={...}
  cssVariablesSelector="div.plasmo-csui-container"
  getRootElement={() =>
    document.querySelector("plasmo-csui").shadowRoot.querySelector("div.plasmo-csui-container")
  }
>
  <style>{mantineCss + overrideCss}</style>
  {...}
</MantineProvider>

The main difference you'll notice is that we changed the cssVariablesSelector prop from :host to div.plasmo-csui-container. This allows Mantine to correctly source CSS variables from within the div.plasmo-csui-container container.

You'll also notice that we updated the override.css CSS to use the same container as its selector.

We also define the getRootElement prop to correctly set the data-mantine-color-scheme attribute (Ref: https://mantine.dev/theming/mantine-provider/#getrootelement).

Lastly, we drop the use of Plasmo's getStyle function, and apply it inline via <style> tags within MantineProvider, so that the styles are inserted inline within the div.plasmo-csui-container container.

Full implementation via a dedicated theme.tsx file:

import { createTheme, MantineProvider, mergeThemeOverrides, Modal } from "@mantine/core";
import mantineCss from "data-text:@mantine/core/styles.css";
import overrideCss from "data-text:~styles/override.css";

export const defaultTheme = createTheme({});

const contentTheme = createTheme({
  components: {
    Modal: Modal.extend({ defaultProps: { withinPortal: false } })
  }
});

const mergedTheme = mergeThemeOverrides(defaultTheme, contentTheme);

export function ContentProvider({ children }) {
  return (
    <MantineProvider
      theme={mergedTheme}
      defaultColorScheme="auto"
      cssVariablesSelector="div.plasmo-csui-container"
      getRootElement={() =>
        document.querySelector("plasmo-csui").shadowRoot.querySelector("div.plasmo-csui-container")
      }
    >
      <style>{mantineCss + overrideCss}</style>
      {children}
    </MantineProvider>
  );
}

where defaultTheme can be imported to be used in your app's MantineProvider, while still applying to CSUI components. contentTheme includes theme overrides that should only be applied to CSUI components. mergedTheme merges the two and uses it within a dedicated ContentProvider, which wraps MantineProvider with the necessary overrides to apply Mantine styles to CSUI components. This should make it easier to develop CSUI components, since all the overrides are done locally in this file.

Lastly, make sure to set modals (among other components) to render within the shadow DOM, not via a portal:

const theme = createTheme({
  components: {
    Modal: Modal.extend({ defaultProps: { withinPortal: false } })
  }
});

This allows styles to be correctly applied to the modal, since it will render with the shadow DOM's styles that we set inline above.

Please let us know if this works for you!

Thx to your explaination that make me got why those problems happens. I have tried ur solution but still exist a problem. So far I need Notication System and Modal. Under ur solution, the Modal works. But Notificatio lose styles. And I tryied to move Notification to plasmo-csui as below

const contentTheme = createTheme({
  components: {
    Modal: Modal.extend({ defaultProps: { withinPortal: false } }),
    Notifications: Notifications.extend({ defaultProps: { withinPortal: false, position: "bottom-right" } })
  }
})

But the position of notification is error. It's always placed in left-top. And I don't know how the location system works. So I decide make notification within portal. And try to figure out the problem that styles losted. After my effort, it works. Maybe my solution can help u.

import '@mantine/core/styles.css';
import '@mantine/notifications/styles.css';

import mantineCss from 'data-text:@mantine/core/styles.css';

export const getStyle = () => {
  const style = document.createElement('style');
  // tailwindCss refer to https://docs.plasmo.com/quickstarts/with-tailwindcss
  style.textContent = mantineCss + tailwindCss().textContent;
  return style;
};

const theme = createTheme({
  components: {
    Modal: Modal.extend({ defaultProps: { withinPortal: false } }),
  },
});

const getRootElement = () => {
  return document.querySelector('plasmo-csui').shadowRoot.querySelector('div.plasmo-csui-container') as HTMLElement;
};

function ContentApp() {
  useEffect(() => {
    const html = document.querySelector('html');
    html.setAttribute('data-mantine-color-scheme', 'light');
  }, []);

    return (
      <>
        <MantineProvider theme={theme} getRootElement={getRootElement}>
          <Notifications />
          <ModalsProvider />
        </MantineProvider>
      </>
    );
}
export default ContentApp;