Slidewright

React app

A deck in a React app, with a live editor, a custom layout and a directive component.

This app shows a text area with Markdown, and a <Deck> of that Markdown. Type in the text area: the deck changes, and shows the slide that has the cursor. The second slide uses a custom layout. The third slide uses a component for the :::callout directive.

A deck in a React app

Edit the Markdown: the deck changes as you type, and shows the slide under the cursor.

  • <Deck markdown={…} /> renders the source
  • The deck stays on the same slide and step
1 / 4
Slide 1 of 4: A deck in a React app

Source

The app keeps the Markdown and the position of the deck in its state. getSlideAtLine from @slidewright/core finds the slide that has the cursor:

examples/react/src/app.tsx
import { getSlideAtLine, parseDeck } from "@slidewright/core";
import { Deck, type DeckPosition } from "@slidewright/react";
import { useState } from "react";
import { components, layouts } from "./parts";
import slides from "./slides.md?raw";

export function App() {
  const [markdown, setMarkdown] = useState(slides);
  const [position, setPosition] = useState<DeckPosition>({ slide: 0, step: 0 });

  // Show the slide under the cursor.
  function follow(editor: HTMLTextAreaElement) {
    const line = editor.value
      .slice(0, editor.selectionStart)
      .split("\n").length;
    const slide = getSlideAtLine(parseDeck(editor.value), line);
    if (slide !== position.slide) setPosition({ slide, step: 0 });
  }

  return (
    <main className="app">
      <textarea
        aria-label="Deck source"
        spellCheck={false}
        value={markdown}
        onChange={(event) => setMarkdown(event.target.value)}
        onSelect={(event) => follow(event.currentTarget)}
      />
      <Deck
        markdown={markdown}
        position={position}
        onPositionChange={setPosition}
        layouts={layouts}
        components={components}
      />
    </main>
  );
}

The layout and the component:

examples/react/src/parts.tsx
import type { DirectiveComponents, Layout } from "@slidewright/react";
import type { ReactNode } from "react";
import "./parts.css";

/** `layout: sidebar`: the content, with `:::aside` in a column beside it. */
const Sidebar: Layout = ({ children, slots }) => (
  <>
    <div data-part="main">{children}</div>
    <aside data-part="aside">{slots.aside}</aside>
  </>
);
Sidebar.slots = ["aside"];

/** `:::callout{tone="warning"}`: a note that stands out. */
function Callout({
  tone = "info",
  children,
}: {
  tone?: string;
  children?: ReactNode;
}) {
  return (
    <div className="callout" data-tone={tone}>
      {children}
    </div>
  );
}

export const layouts: Record<string, Layout> = { sidebar: Sidebar };

export const components: DirectiveComponents = { callout: Callout };

The CSS for the layout and the component:

examples/react/src/parts.css
/* `layout: sidebar`, arranged by its name. */
[data-layout="sidebar"] {
  display: grid;
  grid-template-columns: 2fr 1fr;
  gap: 2em;
}

[data-layout="sidebar"] [data-part="aside"] {
  padding-left: 1.5em;
  border-left: 2px solid var(--deck-border);
  color: var(--deck-muted);
  font-size: 0.85em;
}

/* `:::callout` */
.callout {
  margin-bottom: 0.75em;
  padding: 0.7em 1em;
  border-radius: var(--deck-radius);
  background: color-mix(in srgb, var(--deck-accent) 12%, transparent);
}

.callout[data-tone="warning"] {
  background: color-mix(in srgb, #f59e0b 20%, transparent);
}

.callout > :last-child {
  margin-bottom: 0;
}

The deck:

examples/react/src/slides.md
---
title: A deck in a React app
---

# A deck in a React app

Edit the Markdown: the deck changes as you type, and shows the
slide under the cursor.

- `<Deck markdown={…} />` renders the source
- The deck stays on the same slide and step

---
layout: sidebar
---

# A custom layout

`layout: sidebar` is a component from the `layouts` prop.

- The content goes in the main column
- `:::aside` goes beside it

:::aside
**Sidebar**

The layout lists `aside` in its `slots`, so this block goes here.
:::

---

# A custom component

:::callout
`:::callout` renders the `Callout` component from the `components` prop.
:::

:::callout{tone="warning"}
Attributes such as `tone="warning"` arrive as props.
:::

---

# Steps work as usual

- Press → in the deck to show the next step

<!-- step -->

- Edit this slide: the deck stays on this step

To start the app from a clone of the repository, run this command in the examples folder:

bun run react

See the @slidewright/react reference for all the props of <Deck>.

On this page