tajo / ladle

🥄 Develop, test and document your React story components faster.
https://www.ladle.dev
MIT License
2.61k stars 90 forks source link

@mui/material styles only applied when story width is unset in the production build #346

Closed dtgreene closed 1 year ago

dtgreene commented 1 year ago

Describe the bug Production Ladle builds that use the @mui/material package do not apply the appropriate styles when the story width is changed. Works as expected when using ladle serve.

With the story width set to unset:

Screen Shot 2023-02-09 at 1 13 18 PM

Setting story width to any other value:

Screen Shot 2023-02-09 at 1 13 27 PM

My guess is that it has something to do with the css-in-js. I'm going to try to reproduce with a similar library like styled-components .

Reproduction https://stackblitz.com/edit/ladle-ehhg6r Run pnpm run prod

tajo commented 1 year ago

The issue is that the width addon puts stories into an iframe. We copy over styles from the main window but CSS in JS libraries are a bit tricky to handle like that and it's also different for each one of them. This is how we do it for Styletron: https://github.com/uber/baseweb/blob/master/.ladle/components.tsx#L27:L40

I want to add some documentation & setup examples for different (most popular) CSS in libraries.

dtgreene commented 1 year ago

Thanks that pointed me in the right direction. I got it working with this components.tsx:

import React, { useMemo } from 'react'
import { GlobalProvider } from '@ladle/react'
import { CssBaseline, ThemeProvider } from '@mui/material'
import { theme } from '../src/theme'
import { CacheProvider } from '@emotion/react'
import createCache from '@emotion/cache'

import '../src/index.css'

let prevWidth = 0
const defaultCache = createCache({
  key: 'emotion',
  container: document.head,
})

// all stories are wrapped in this
export const Provider: GlobalProvider = ({ children, globalState }) => {
  const cache = useMemo(() => {
    const widthChange =
      (prevWidth > 0 && globalState.width === 0) ||
      (prevWidth === 0 && globalState.width > 0)

    if (widthChange) {
      const iframe = document.querySelector(
        `[title='Story ${globalState.story}']`,
      ) as HTMLIFrameElement | null

      const targetHead = iframe?.contentDocument?.head

      if (targetHead) {
        return createCache({
          key: 'emotion',
          container: targetHead,
        })
      }
    }

    return defaultCache
  }, [globalState.width])

  return (
    <CacheProvider value={cache}>
      <ThemeProvider theme={theme}>
        <CssBaseline />
        {children}
      </ThemeProvider>
    </CacheProvider>
  )
}