FormidableLabs / spectacle

A React-based library for creating sleek presentations using JSX syntax that gives you the ability to live demo your code.
https://commerce.nearform.com/open-source/spectacle/
MIT License
9.77k stars 693 forks source link

Appear component does not function when placed in an MDX file #870

Open antonwintergerst opened 4 years ago

antonwintergerst commented 4 years ago

Describe Your Environment

What version of Spectacle are you using? spectacle@6.0.1

What version of React are you using? react@16.13.1

What browser are you using? Chrome 80.0.3987.149

What machine are you on? MacBook Pro (15-inch 2017)

Describe the Problem

Appear component does not function when placed in an MDX file.

Expected behavior: Slide navigation should step through Appear steps before moving to next slide.

Actual behavior: Slide navigation skips Appear steps and moves to next slide.

Additional Information

These were the steps I took to setup a project:

  1. Install global dependencies npm i typescript spectacle-cli -g

  2. Create a new presentation spectacle-boilerplate --mode mdx --dir "some-presentation"

  3. Add TypeScript support

    yarn add typescript @babel/plugin-proposal-class-properties @babel/preset-env @babel/preset-typescript -D
    tsc --init --declaration --allowSyntheticDefaultImports --target esnext --outDir lib
  4. Add a .babelrc file

    {
    "presets": ["@babel/env", "@babel/typescript", "@babel/preset-react"],
    "plugins": ["@babel/proposal-class-properties"]
    }
  5. Update webpack.config.js

    // Add this to module.exports
    resolve: {
    extensions: ['.ts', '.tsx', '.js', '.json']
    }
    // Change test: /\.(jsx)?$/, to
    test: /\.(ts|js)x?$/,

How was I able to even render an Appear component in MDX?

By updating the mdxComponentMap to include the Appear tag and then mapping this to the Appear component:

const Presentation = () => (
  <MDXProvider
    components={{
      ...mdxComponentMap,
      Appear: props => <Appear {...props} />
    }}
  >
    <Deck loop theme={theme} template={template}>
      {slides
        .map((MDXSlide, i) => [MDXSlide, notes[i]])
        .map(([MDXSlide, MDXNote], i) => (
          <Slide key={`slide-${i}`} slideNum={i}>
            <MDXSlide />
            <Notes>
              <MDXNote />
            </Notes>
          </Slide>
        ))}
    </Deck>
  </MDXProvider>
);

This allows markdown to be written like so:

<Appear elementNum={1}>

- Lorem ipsum
- Lorem ipsum
- Lorem ipsum

</Appear>

And it is correctly rendering the expected HTML

<div style="opacity: 0;">
  <ul color="primary" font-family="text" font-size="text" class="sc-bZQynM fHBYr">
    <li class="sc-gzVnrw eGyYYk">Lorem ipsum</li>
    <li class="sc-gzVnrw eGyYYk">Lorem ipsum</li>
    <li class="sc-gzVnrw eGyYYk">Lorem ipsum</li>
  </ul>
</div>

However, the component is not detected by the searchChildrenForAppear function and this results in the navigator not being able to step through these Appear steps. Here's where that function gets called: https://github.com/FormidableLabs/spectacle/blob/ecd313b6ad02aa9a769a8dc46bb300613e26aea9/src/components/deck/index.js#L143

Any assistance would be greatly appreciated 🙌

antonwintergerst commented 4 years ago

I've found a work around which is a bit hacky but at least it functions:

const Presentation = () => (
  <MDXProvider
    components={{
      ...mdxComponentMap,
      Appear: props => <Appear {...props} />
    }}
  >
    <Deck loop theme={theme} template={template}>
      {slides
        .map((MDXSlide, i) => [MDXSlide, notes[i]])
        .map(([MDXSlide, MDXNote], i) => {
          // find the number of appear elements on this slide
          const appearElementCount = (
            MDXSlide.toString().match(/\[\"mdx\"\]\)\(Appear/g) || []
          ).length;

          // render new empty Appear elements that will get picked up by the `searchChildrenForAppear` function
          // mdx Appear markdowns should have the same elementNum prop values and this will trick the navigator into making both appear
          return (
            <Slide key={`slide-${i}`} slideNum={i + 1}>
              <MDXSlide />
              <div>
                {Array(appearElementCount)
                  .fill(0)
                  .map((_, a) => (
                    <Appear elementNum={a} key={`slide-${i}-appear-${a}`} />
                  ))}
              </div>
              <Notes>
                <MDXNote />
              </Notes>
            </Slide>
          );
        })}
    </Deck>
  </MDXProvider>
);
ryan-roemer commented 4 years ago

Great find!

The MDX loader's main job is translating MD in MDX file to JSX and allowing existing JSX to work. The Appear element isn't really something first class in MDX, it's more of something that should be supported via a normal import, so I really think we're looking at supporting something like:

---

import { Appear } from 'spectacle';

## Use Spectacle Appear Tag!

<Appear elementNum={0}>
Should appear later!
</Appear>

The above does not work, so I think our best starting point is adding the above slide to https://github.com/FormidableLabs/spectacle-mdx-loader/blob/master/examples/mdx/slides.mdx and getting that working with some combination of work in spectacle and spectacle-mdx-loader repositories.

ryan-roemer commented 4 years ago

It's looking more like something that may be primarily in spectacle-mdx-loader. As a debugging helper, here's what is currently generated for the above slide:

function MDXContentWrapperSlide1(props) {
/* @jsx mdx */
const makeShortcode = name => function MDXDefaultShortcode(props) {
  console.warn("Component " + name + " was not imported, exported, or provided by MDXProvider as global scope")
  return <div {...props}/>
};

const layoutProps = {
  testProp
};

const MDXLayout = "wrapper";

function MDXContent({
  components,
  ...props
}) {
  return <MDXLayout {...layoutProps} {...props} components={components} mdxType="MDXLayout">
    <h2>{`Use Spectacle Appear Tag!`}</h2>
    <Appear elementNum={0} mdxType="Appear">
Should appear later!
    </Appear>
  </MDXLayout>;
};