nkzw-tech / remdx

Beautiful Minimalist React & MDX Presentations

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Style slide container

karlhorky opened this issue · comments

I came across a need to style the slide container for all slides in all slide decks (making the default justifyContent: 'center' instead of justifyContent: 'flex-start'.

Looking at the readme, the Usage section mentions I can "customize my style via" without mentioning what I can do:

Screenshot 2024-02-13 at 19 44 43

Workaround

Currently I'm using patch-package to alter node_modules/@nkzw/remdx/index.js the justifyContent: "flex-start" to justifyContent: "center", as so:

          ...swipeHandler,
          children: /* @__PURE__ */ jsx3(
            "div",
            {
              style: {
                display: "flex",
                flex: 1,
                flexDirection: "column",
                justifyContent: "center",
                padding
              },
              children: /* @__PURE__ */ jsx3(Suspense, { children })
            }
          )

But this seems to be a bit of a large hammer for the job - is there an easier way?

It seemed like a good default. The way I center content in slides is by using a <Center> component like here: https://github.com/nkzw-tech/turn-based-ai-talk/blob/main/slides.re.mdx#L94-L110. As said previously, it would be great to have a standard library in a separate package for this sort of stuff.

However, your use-case is valid. Here are some possibilities beyond using the <Center> component option:

  • render and slidesToComponent are just basic wrappers to simplify rendering a slide deck. You can build your own version of slidesToComponent.tsx to compose your deck, and you could simply wrap all slide components with a <Center /> component in your version of slidesToComponent.tsx.
  • We could remove the nested divs in Slide.tsx and collapse both into one, after which CSS styles via exporting a Theme will allow you to customize justifyContent directly.
  • We could add a prop to Slide.tsx to customize the inner div's styles and then either you can use a custom slidesToComponent.tsx or we can add a way to export the default style via a config option in each slide deck.

There are probably other solutions, but those are some that I can think of.

Nice, it sounds like there are a few ways forward here then.

As a user of ReMDX, I would expect I could customize:

  1. Global styles / theme for all slide decks created by an instance of ReMDX (useful in the case of multiple slide decks)

    Maybe this could be an argument to the render() function:

    <!doctype html>
    <html>
    <head>
    <meta charset="utf-8" />
    <title>ReMDX</title>
    <meta
    name="viewport"
    content="width=device-width, user-scalable=no, initial-scale=1, viewport-fit=cover"
    />
    <meta name="apple-mobile-web-app-capable" content="yes" />
    <meta name="apple-mobile-web-app-status-bar-style" content="default" />
    </head>
    <body>
    <div id="app"></div>
    <script type="module">
    import '@nkzw/remdx/style.css';
    import { render } from '@nkzw/remdx';
    render(document.getElementById('app'), import('./slides.re.mdx'));
    </script>
    </body>
    </html>

  2. Deck styles / theme for all slides in a single slide deck - to alter the global styling applied to a single slide deck

    Potential option: Exporting Theme via front matter + collapsing nested divs as you mentioned above in your 2nd bullet point

  3. Slide styles / theme for a single slide - to alter the styles

    Here components like <Center /> are already maybe enough... (maybe doesn't need separate front matter option or similar for each slide?)

For my immediate use case (making all slide decks uniform), I am primarily interested in number 1. above. However, I can see the use cases of 2. and 3. as well.

What are your thoughts in going these directions?

In fa5802d I added the ability to wrap slides with a custom container via:

export { Container } from './Container';

Container.tsx can look like this:

import { ReactNode } from 'react';

export const Container = ({ children }: { children: ReactNode }) => (
  <div style={{ background: 'red' }}>{children}</div>
);

Every slide in the current deck will use this container. The <Slide> component now takes a component as a container prop with children and style props in case you have a custom renderer.

Released in 0.11.0.

Great, thanks!

I'm just trying to figure out how to use this version, to try to achieve "1. Global styles / theme for all slide decks created by an instance of ReMDX":

The <Slide> component now takes a component as a container prop with children and style props in case you have a custom renderer.

I copied out the code from remdx/src/render.tsx and remdx/src/slidesToComponent.tsx to try to create my own render.tsx file, kind of like this below:

import { MDXProvider } from '@mdx-js/react';
import Slide from '@nkzw/remdx/index';
// ... more copied imports, some of them which fail ...

export default async function slidesToComponent(module: Promise<ReMDXModule>) {
  const {
    Components,
    Container,
    Themes,
    Transitions,
    default: slides,
  } = await module;
  return (
    <MDXProvider components={{ ...DefaultComponents, ...Components }}>
      <Deck
        slides={slides.map(({ Component, data }, index) => (
          <Slide
            container={Container}
            id={index}
            image={data?.image}
            key={index}
            style={Themes?.[data?.theme] || Themes?.default}
            transition={
              Transitions?.[data?.transition] ||
              DefaultTransitions[data?.transition] ||
              undefined
            }
          >
            <Component />
          </Slide>
        ))}
      />
    </MDXProvider>
  );
}

const roots = new WeakMap<HTMLElement, Root>();

export default async function render(
  element: HTMLElement | null,
  module: Promise<ReMDXModule>,
) {
  if (!element) {
    throw new Error(`remdx: The provided DOM node could not be found.`);
  }

  if (!roots.has(element)) {
    roots.set(element, createRoot(element));
  }

  roots.get(element)?.render(await slidesToComponent(module));
}

Is this what you mean?

Ideally I would just want to be able to use the provided render() from @nkzw/remdx and pass in a Container parameter / option.

But if this is the recommended approach, to copy+paste the render() and slidesToComponent() functions from ReMDX, I can also do this. I would want to avoid copy+pasting too much, eg. I still want to rely on the other functions and components from ReMDX.

If this is the recommended approach, I guess it would be good to:

I tried to fix the errors and missing exports

index.html

<!doctype html>
<html>
  <!-- ... more <head> things here ... -->
  <body>
    <div id="app"></div>
    <script type="module">
      import '@nkzw/remdx/style.css';
      import { render } from './render.tsx';

      render(
        document.getElementById('app'),
        import('./slides.re.mdx'),
      );
    </script>
  </body>
</html>

render.tsx

import { MDXProvider } from '@mdx-js/react';
// @ts-expect-error
import { Deck, Slide } from '@nkzw/remdx/index.js';
import { ReMDXModule, SlideTransition } from '@nkzw/remdx/types.jsx';
import { createRoot, Root } from 'react-dom/client';

const defaultTransition = {
  enter: {
    opacity: 1,
    transform: 'translateX(0%)',
  },
  from: {
    opacity: 0,
    transform: 'translateX(100%)',
  },
  leave: {
    opacity: 1,
    transform: 'translateX(-100%)',
  },
};

const defaultTransitions: Record<string, SlideTransition> = {
  default: defaultTransition,
  leaveOnly: {
    enter: {
      transform: 'translateX(0%)',
    },
    from: {},
    leave: {
      transform: 'translateX(-100%)',
    },
  },
  none: {
    enter: {},
    from: {},
    leave: {},
  },
  opacity: {
    enter: {
      opacity: 1,
    },
    from: {
      opacity: 0,
    },
    leave: {
      opacity: 1,
    },
  },
  transformRight: {
    enter: {
      transform: 'translateX(0%)',
    },
    from: {
      transform: 'translateX(100%)',
    },
    leave: {
      transform: 'translateX(-100%)',
    },
  },
};

type ContainerProps = {
  children: React.ReactNode;
};

function Container(props: ContainerProps) {
  return (
    <div className="p-[5vh 8vw] flex flex-1 flex-col justify-center">
      {props.children}
    </div>
  );
}

function Image({
  src: source,
  ...props
}: React.DetailedHTMLProps<
  React.ImgHTMLAttributes<HTMLImageElement>,
  HTMLImageElement
>) {
  if (!source) {
    return null;
  }
  const [src, query] = source.split('?');
  return (
    <img
      loading="lazy"
      src={src}
      style={Object.fromEntries(new URLSearchParams(query))}
      {...props}
    />
  );
}

async function slidesToComponent(module: Promise<ReMDXModule>) {
  const {
    /* eslint-disable @typescript-eslint/naming-convention --
    React Components can be PascalCase */
    Components,
    Themes,
    Transitions,
    /* eslint-enable @typescript-eslint/naming-convention --
    React Components can be PascalCase */
    default: slides,
  } = await module;
  return (
    <MDXProvider components={{ img: Image, ...Components }}>
      <Deck
        slides={slides.map(({ Component, data }, index) => (
          <Slide
            // eslint-disable-next-line react/no-array-index-key -- Slide order will not change
            key={`slide-${index}`}
            container={Container}
            id={index}
            image={data.image}
            style={(data.theme && Themes?.[data.theme]) || Themes?.default}
            transition={
              (data.transition &&
                (Transitions?.[data.transition] ||
                  defaultTransitions[data.transition])) ||
              undefined
            }
          >
            <Component />
          </Slide>
        ))}
      />
    </MDXProvider>
  );
}

const roots = new WeakMap<HTMLElement, Root>();

export async function render(
  element: HTMLElement | null,
  module: Promise<ReMDXModule>,
) {
  if (!element) {
    throw new Error('remdx: The provided DOM node could not be found.');
  }

  if (!roots.has(element)) {
    roots.set(element, createRoot(element));
  }

  roots.get(element)?.render(await slidesToComponent(module));
}

This no longer causes a build error, but I have runtime errors in the browser console when trying this approach:

Screenshot 2024-02-19 at 18 22 17

slides.re.mdx:246 Uncaught Error: Expected component `Center` to be defined: you likely forgot to import, pass, or provide it.
It’s referenced in your code at `12:1-14:10`
    at _missingMdxReference (slides.re.mdx:246:9)
    at _createMdxContent (slides.re.mdx:188:16)
    at MDXContent (slides.re.mdx:243:14)
    at renderWithHooks (chunk-UVTSGMSA.js?v=d96c63a4:12171:26)
    at mountIndeterminateComponent (chunk-UVTSGMSA.js?v=d96c63a4:14921:21)
    at beginWork (chunk-UVTSGMSA.js?v=d96c63a4:15902:22)
    at HTMLUnknownElement.callCallback2 (chunk-UVTSGMSA.js?v=d96c63a4:3674:22)
    at Object.invokeGuardedCallbackDev (chunk-UVTSGMSA.js?v=d96c63a4:3699:24)
    at invokeGuardedCallback (chunk-UVTSGMSA.js?v=d96c63a4:3733:39)
    at beginWork$1 (chunk-UVTSGMSA.js?v=d96c63a4:19761:15)
_missingMdxReference @ slides.re.mdx:246
_createMdxContent @ slides.re.mdx:188
MDXContent @ slides.re.mdx:243
renderWithHooks @ chunk-UVTSGMSA.js?v=d96c63a4:12171
mountIndeterminateComponent @ chunk-UVTSGMSA.js?v=d96c63a4:14921
beginWork @ chunk-UVTSGMSA.js?v=d96c63a4:15902
callCallback2 @ chunk-UVTSGMSA.js?v=d96c63a4:3674
invokeGuardedCallbackDev @ chunk-UVTSGMSA.js?v=d96c63a4:3699
invokeGuardedCallback @ chunk-UVTSGMSA.js?v=d96c63a4:3733
beginWork$1 @ chunk-UVTSGMSA.js?v=d96c63a4:19761
performUnitOfWork @ chunk-UVTSGMSA.js?v=d96c63a4:19194
workLoopSync @ chunk-UVTSGMSA.js?v=d96c63a4:19133
renderRootSync @ chunk-UVTSGMSA.js?v=d96c63a4:19112
performConcurrentWorkOnRoot @ chunk-UVTSGMSA.js?v=d96c63a4:18674
workLoop @ chunk-UVTSGMSA.js?v=d96c63a4:197
flushWork @ chunk-UVTSGMSA.js?v=d96c63a4:176
performWorkUntilDeadline @ chunk-UVTSGMSA.js?v=d96c63a4:384
Show 14 more frames
Show less
slides.re.mdx:336 Uncaught Error: Expected component `NotRecommended` to be defined: you likely forgot to import, pass, or provide it.
It’s referenced in your code at `16:1-16:19`
    at _missingMdxReference (slides.re.mdx:336:9)
    at _createMdxContent (slides.re.mdx:264:24)
    at MDXContent (slides.re.mdx:333:14)
    at renderWithHooks (chunk-UVTSGMSA.js?v=d96c63a4:12171:26)
    at mountIndeterminateComponent (chunk-UVTSGMSA.js?v=d96c63a4:14921:21)
    at beginWork (chunk-UVTSGMSA.js?v=d96c63a4:15902:22)
    at HTMLUnknownElement.callCallback2 (chunk-UVTSGMSA.js?v=d96c63a4:3674:22)
    at Object.invokeGuardedCallbackDev (chunk-UVTSGMSA.js?v=d96c63a4:3699:24)
    at invokeGuardedCallback (chunk-UVTSGMSA.js?v=d96c63a4:3733:39)
    at beginWork$1 (chunk-UVTSGMSA.js?v=d96c63a4:19761:15)
_missingMdxReference @ slides.re.mdx:336
_createMdxContent @ slides.re.mdx:264
MDXContent @ slides.re.mdx:333
renderWithHooks @ chunk-UVTSGMSA.js?v=d96c63a4:12171
mountIndeterminateComponent @ chunk-UVTSGMSA.js?v=d96c63a4:14921
beginWork @ chunk-UVTSGMSA.js?v=d96c63a4:15902
callCallback2 @ chunk-UVTSGMSA.js?v=d96c63a4:3674
invokeGuardedCallbackDev @ chunk-UVTSGMSA.js?v=d96c63a4:3699
invokeGuardedCallback @ chunk-UVTSGMSA.js?v=d96c63a4:3733
beginWork$1 @ chunk-UVTSGMSA.js?v=d96c63a4:19761
performUnitOfWork @ chunk-UVTSGMSA.js?v=d96c63a4:19194
workLoopSync @ chunk-UVTSGMSA.js?v=d96c63a4:19133
renderRootSync @ chunk-UVTSGMSA.js?v=d96c63a4:19112
performConcurrentWorkOnRoot @ chunk-UVTSGMSA.js?v=d96c63a4:18674
workLoop @ chunk-UVTSGMSA.js?v=d96c63a4:197
flushWork @ chunk-UVTSGMSA.js?v=d96c63a4:176
performWorkUntilDeadline @ chunk-UVTSGMSA.js?v=d96c63a4:384
Show 14 more frames
Show less
slides.re.mdx:389 Uncaught Error: Expected component `Content` to be defined: you likely forgot to import, pass, or provide it.
It’s referenced in your code at `10:1-10:51`
    at _missingMdxReference (slides.re.mdx:389:9)
    at _createMdxContent (slides.re.mdx:351:17)
    at MDXContent (slides.re.mdx:386:14)
    at renderWithHooks (chunk-UVTSGMSA.js?v=d96c63a4:12171:26)
    at mountIndeterminateComponent (chunk-UVTSGMSA.js?v=d96c63a4:14921:21)
    at beginWork (chunk-UVTSGMSA.js?v=d96c63a4:15902:22)
    at HTMLUnknownElement.callCallback2 (chunk-UVTSGMSA.js?v=d96c63a4:3674:22)
    at Object.invokeGuardedCallbackDev (chunk-UVTSGMSA.js?v=d96c63a4:3699:24)
    at invokeGuardedCallback (chunk-UVTSGMSA.js?v=d96c63a4:3733:39)
    at beginWork$1 (chunk-UVTSGMSA.js?v=d96c63a4:19761:15)

It seems like it's having troubles resolving the components in the await(module).Components - although if I console.log() them, I can see the object... 🤔

Components {Center: ƒ, Columns: ƒ, Content: ƒ, HeadsUp: ƒ, NewTerm: ƒ, …}

Anyway, maybe I'm going in the wrong direction here anyway, in case this is not really the recommended approach for "1. Global styles / theme for all slide decks created by an instance of ReMDX".

imho the best solution is to have one module that you export from at the top of each slide deck despite leading to one duplicated line across all slide decks.

export { Components, Container, Them } from 'my-remdx-setup';

If you don't love that, then your solution should work too. It's hard to make out what the issue is from the pasted code, if you put a full repo together that shows the issue, I'm happy to help debug it.

your solution should work too. It's hard to make out what the issue is from the pasted code, if you put a full repo together that shows the issue, I'm happy to help debug it.

Alright, I put together a reproduction repo for the custom render() problems I described above at https://github.com/karlhorky/repro-remdx-custom-render .

I used the latest versions of everything (also re-copy+pasted from new ReMDX packages), and made minimal changes to the create-remdx starter files:

  1. Added render.tsx with the changes described in my comment above
  2. Changed the import path in index.html
  3. Added <Center># Welcome to ReMDX</Center> in slides.re.mdx

Error message:

slides.re.mdx:42 Uncaught Error: Expected component `Center` to be defined: you likely forgot to import, pass, or provide it.
It’s referenced in your code at `6:1-6:36`
    at _missingMdxReference (slides.re.mdx:42:9)
    at _createMdxContent (slides.re.mdx:16:16)
    at MDXContent (slides.re.mdx:39:14)
    at renderWithHooks (chunk-EOWBSJ4E.js?v=59567093:11566:26)
    at mountIndeterminateComponent (chunk-EOWBSJ4E.js?v=59567093:14944:21)
    at beginWork (chunk-EOWBSJ4E.js?v=59567093:15932:22)
    at HTMLUnknownElement.callCallback2 (chunk-EOWBSJ4E.js?v=59567093:3672:22)
    at Object.invokeGuardedCallbackDev (chunk-EOWBSJ4E.js?v=59567093:3697:24)
    at invokeGuardedCallback (chunk-EOWBSJ4E.js?v=59567093:3731:39)
    at beginWork$1 (chunk-EOWBSJ4E.js?v=59567093:19791:15)
_missingMdxReference @ slides.re.mdx:42
_createMdxContent @ slides.re.mdx:16
MDXContent @ slides.re.mdx:39
renderWithHooks @ chunk-EOWBSJ4E.js?v=59567093:11566
mountIndeterminateComponent @ chunk-EOWBSJ4E.js?v=59567093:14944
beginWork @ chunk-EOWBSJ4E.js?v=59567093:15932
callCallback2 @ chunk-EOWBSJ4E.js?v=59567093:3672
invokeGuardedCallbackDev @ chunk-EOWBSJ4E.js?v=59567093:3697
invokeGuardedCallback @ chunk-EOWBSJ4E.js?v=59567093:3731
beginWork$1 @ chunk-EOWBSJ4E.js?v=59567093:19791
performUnitOfWork @ chunk-EOWBSJ4E.js?v=59567093:19224
workLoopSync @ chunk-EOWBSJ4E.js?v=59567093:19163
renderRootSync @ chunk-EOWBSJ4E.js?v=59567093:19142
performConcurrentWorkOnRoot @ chunk-EOWBSJ4E.js?v=59567093:18704
workLoop @ chunk-EOWBSJ4E.js?v=59567093:195
flushWork @ chunk-EOWBSJ4E.js?v=59567093:174
performWorkUntilDeadline @ chunk-EOWBSJ4E.js?v=59567093:382
Show 14 more frames
Show less
slides.re.mdx:42 Uncaught Error: Expected component `Center` to be defined: you likely forgot to import, pass, or provide it.
It’s referenced in your code at `6:1-6:36`
    at _missingMdxReference (slides.re.mdx:42:9)
    at _createMdxContent (slides.re.mdx:16:16)
    at MDXContent (slides.re.mdx:39:14)
    at renderWithHooks (chunk-EOWBSJ4E.js?v=59567093:11566:26)
    at mountIndeterminateComponent (chunk-EOWBSJ4E.js?v=59567093:14944:21)
    at beginWork (chunk-EOWBSJ4E.js?v=59567093:15932:22)
    at HTMLUnknownElement.callCallback2 (chunk-EOWBSJ4E.js?v=59567093:3672:22)
    at Object.invokeGuardedCallbackDev (chunk-EOWBSJ4E.js?v=59567093:3697:24)
    at invokeGuardedCallback (chunk-EOWBSJ4E.js?v=59567093:3731:39)
    at beginWork$1 (chunk-EOWBSJ4E.js?v=59567093:19791:15)
_missingMdxReference @ slides.re.mdx:42
_createMdxContent @ slides.re.mdx:16
MDXContent @ slides.re.mdx:39
renderWithHooks @ chunk-EOWBSJ4E.js?v=59567093:11566
mountIndeterminateComponent @ chunk-EOWBSJ4E.js?v=59567093:14944
beginWork @ chunk-EOWBSJ4E.js?v=59567093:15932
callCallback2 @ chunk-EOWBSJ4E.js?v=59567093:3672
invokeGuardedCallbackDev @ chunk-EOWBSJ4E.js?v=59567093:3697
invokeGuardedCallback @ chunk-EOWBSJ4E.js?v=59567093:3731
beginWork$1 @ chunk-EOWBSJ4E.js?v=59567093:19791
performUnitOfWork @ chunk-EOWBSJ4E.js?v=59567093:19224
workLoopSync @ chunk-EOWBSJ4E.js?v=59567093:19163
renderRootSync @ chunk-EOWBSJ4E.js?v=59567093:19142
recoverFromConcurrentError @ chunk-EOWBSJ4E.js?v=59567093:18762
performConcurrentWorkOnRoot @ chunk-EOWBSJ4E.js?v=59567093:18710
workLoop @ chunk-EOWBSJ4E.js?v=59567093:195
flushWork @ chunk-EOWBSJ4E.js?v=59567093:174
performWorkUntilDeadline @ chunk-EOWBSJ4E.js?v=59567093:382
Show 15 more frames
Show less
chunk-EOWBSJ4E.js?v=59567093:14050 The above error occurred in the <MDXContent> component:

    at MDXContent (http://localhost:5173/slides.re.mdx?t=1717160128331:27:8)
    at MDXContentWrapper (http://localhost:5173/slides.re.mdx?t=1717160128331:44:14)
    at Suspense
    at div
    at Container (http://localhost:5173/node_modules/.vite/deps/chunk-KTCQYNXE.js?v=59567093:4977:39)
    at div
    at div
    at http://localhost:5173/node_modules/.vite/deps/chunk-KTCQYNXE.js?v=59567093:2550:51
    at Slide (http://localhost:5173/node_modules/.vite/deps/chunk-KTCQYNXE.js?v=59567093:4975:3)
    at div
    at div
    at Deck (http://localhost:5173/node_modules/.vite/deps/chunk-KTCQYNXE.js?v=59567093:1238:3)
    at MDXProvider (http://localhost:5173/node_modules/.vite/deps/@mdx-js_react.js?v=59567093:24:18)

Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.
logCapturedError @ chunk-EOWBSJ4E.js?v=59567093:14050
update.callback @ chunk-EOWBSJ4E.js?v=59567093:14070
callCallback @ chunk-EOWBSJ4E.js?v=59567093:11266
commitUpdateQueue @ chunk-EOWBSJ4E.js?v=59567093:11283
commitLayoutEffectOnFiber @ chunk-EOWBSJ4E.js?v=59567093:17113
commitLayoutMountEffects_complete @ chunk-EOWBSJ4E.js?v=59567093:18006
commitLayoutEffects_begin @ chunk-EOWBSJ4E.js?v=59567093:17995
commitLayoutEffects @ chunk-EOWBSJ4E.js?v=59567093:17946
commitRootImpl @ chunk-EOWBSJ4E.js?v=59567093:19379
commitRoot @ chunk-EOWBSJ4E.js?v=59567093:19303
finishConcurrentRender @ chunk-EOWBSJ4E.js?v=59567093:18786
performConcurrentWorkOnRoot @ chunk-EOWBSJ4E.js?v=59567093:18744
workLoop @ chunk-EOWBSJ4E.js?v=59567093:195
flushWork @ chunk-EOWBSJ4E.js?v=59567093:174
performWorkUntilDeadline @ chunk-EOWBSJ4E.js?v=59567093:382
Show 15 more frames
Show less
chunk-EOWBSJ4E.js?v=59567093:19439 Uncaught Error: Expected component `Center` to be defined: you likely forgot to import, pass, or provide it.
It’s referenced in your code at `6:1-6:36`
    at _missingMdxReference (slides.re.mdx:42:9)
    at _createMdxContent (slides.re.mdx:16:16)
    at MDXContent (slides.re.mdx:39:14)
    at renderWithHooks (chunk-EOWBSJ4E.js?v=59567093:11566:26)
    at mountIndeterminateComponent (chunk-EOWBSJ4E.js?v=59567093:14944:21)
    at beginWork (chunk-EOWBSJ4E.js?v=59567093:15932:22)
    at beginWork$1 (chunk-EOWBSJ4E.js?v=59567093:19779:22)
    at performUnitOfWork (chunk-EOWBSJ4E.js?v=59567093:19224:20)
    at workLoopSync (chunk-EOWBSJ4E.js?v=59567093:19163:13)
    at renderRootSync (chunk-EOWBSJ4E.js?v=59567093:19142:15)

Screenshot 2024-05-31 at 15 17 39

your solution should work too

Since you alluded above to this being the recommended approach, maybe I should also do another PR to make this pattern easier to implement with less copy/pasting?

I wrote some first recommendations at the bottom of this comment above:

If this is the recommended approach, I guess it would be good to:

The issue is that remdx bundles MDXProvider, so you are bringing in a different version of MDX's context. This means that slides cannot find the right context to receive the list of components from. This can be fixed by changing ReMDX to export MDXProvider as well, and using that instead of the @mdx-js/react version. See cpojer/repro-remdx-custom-render@60ee3cb for the fixed version of your repo.

The default remdx setup only exports the image component. I don't think we need to export that right now tbh, people can copy-paste that one since it's only a few lines of code. I'm ok with exporting other things that aren't there yet.

Ok nice, thanks!

Then I'll do a PR to export some things to make it simpler to make a custom render()

@cpojer opened a PR here - I guess it's only MDXProvider, defaultTransition and Transitions to export... 🤔

Still is quite a bit of code for a custom render() for a custom Container for all slide decks, but a bit better at least.

Custom slides <Container> for multiple decks (with custom render())

Now that #21 has been merged and published as @nkzw/remdx@0.14.0, it's possible to create a custom <Container> for multiple slide decks like this:

Repository:

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>ReMDX</title>
    <meta name="viewport" content="width=device-width,
    user-scalable=no, initial-scale=1, viewport-fit=cover" />
    <meta name="apple-mobile-web-app-capable" content="yes" />
    <meta name="apple-mobile-web-app-status-bar-style"
    content="default" />
  </head>
  <body>
    <div id="app"></div>
    <script type="module">
      import '@nkzw/remdx/style.css';
      import { render } from './render';

      render(document.getElementById('app'), import('./slides.re.mdx'));
    </script>
  </body>
</html>

render.tsx

import { Deck, MDXProvider, Slide, Transitions } from '@nkzw/remdx/index.js';
import { ReMDXModule } from '@nkzw/remdx/types.jsx';
import { createRoot, Root } from 'react-dom/client';

// Copy the Image component from ReMDX, because it is not exported
function Image({
  src: source,
  ...props
}: React.DetailedHTMLProps<
  React.ImgHTMLAttributes<HTMLImageElement>,
  HTMLImageElement
>) {
  if (!source) {
    return null;
  }
  const [src, query] = source.split('?');
  return (
    <img
      loading="lazy"
      src={src}
      style={Object.fromEntries(new URLSearchParams(query))}
      {...props}
    />
  );
}

async function slidesToComponent(module: Promise<ReMDXModule>) {
  const {
    Components,
    Container,
    Themes,
    Transitions: slidesTransitions,
    default: slides,
  } = await module;
  return (
    <MDXProvider
      components={{
        // Copy the default components (...DefaultComponents)
        // from ReMDX, because they are not exported
        img: Image,

        ...Components,
      }}
    >
      <Deck
        slides={slides.map(({ Component, data }, index) => (
          <Slide
            container={Container}
            id={index}
            image={data?.image}
            key={index}
            style={Themes?.[data?.theme] || Themes?.default}
            transition={
              slidesTransitions?.[data?.transition] ||
              Transitions[data?.transition] ||
              undefined
            }
          >
            <Component />
          </Slide>
        ))}
      />
    </MDXProvider>
  );
}

const roots = new WeakMap<HTMLElement, Root>();

export async function render(
  element: HTMLElement | null,
  module: Promise<ReMDXModule>,
) {
  if (!element) {
    throw new Error(`remdx: The provided DOM node could not be found.`);
  }

  if (!roots.has(element)) {
    roots.set(element, createRoot(element));
  }

  roots.get(element)?.render(await slidesToComponent(module));
}

Oh, thinking about this more, I also came up with an alternative - import a custom Container in the index.html file and pass this along with the slide decks module:

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>ReMDX</title>
    <meta name="viewport" content="width=device-width,
    user-scalable=no, initial-scale=1, viewport-fit=cover" />
    <meta name="apple-mobile-web-app-capable" content="yes" />
    <meta name="apple-mobile-web-app-status-bar-style"
    content="default" />
  </head>
  <body>
    <div id="app"></div>
    <script type="module">
      import '@nkzw/remdx/style.css';
-     import { render } from './render';
+     import { render } from '@nkzw/remdx';
+     import Container from './Container';

-     render(document.getElementById('app'), import('./slides.re.mdx'));
+     render(
+       document.getElementById('app'),
+       (async () => {
+         const slides = await import('./slides.re.mdx');
+         return {
+           Container,
+           ...slides,
+         };
+       })(),
+     );
    </script>
  </body>
</html>

Commit: karlhorky/repro-remdx-custom-render@b09b074

Do you see any downsides with this approach? Seems a lot more minimal than the custom render(), if you don't need full control, but only want to override the Container... 🤔

I didn't think of that, seems good!