nextjs-slides 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -28,7 +28,6 @@ Open http://localhost:3000 — choose "Geist deck" or "Alternate deck" (Playfair
28
28
 
29
29
  Peer dependencies: `next >=15`, `react >=19`, `tailwindcss >=4`.
30
30
 
31
-
32
31
  ## Quick Start
33
32
 
34
33
  ### 1. Import the stylesheet
@@ -36,8 +35,8 @@ Peer dependencies: `next >=15`, `react >=19`, `tailwindcss >=4`.
36
35
  In your root layout or global CSS:
37
36
 
38
37
  ```css
39
- @import "tailwindcss";
40
- @import "nextjs-slides/styles.css";
38
+ @import 'tailwindcss';
39
+ @import 'nextjs-slides/styles.css';
41
40
 
42
41
  @source "../node_modules/nextjs-slides/dist";
43
42
  ```
@@ -54,7 +53,7 @@ import {
54
53
  SlideSubtitle,
55
54
  SlideBadge,
56
55
  SlideCode,
57
- } from "nextjs-slides";
56
+ } from 'nextjs-slides';
58
57
 
59
58
  export const slides = [
60
59
  <Slide key="intro">
@@ -75,8 +74,8 @@ console.log(greeting);`}</SlideCode>
75
74
 
76
75
  ```tsx
77
76
  // app/slides/layout.tsx
78
- import { SlideDeck } from "nextjs-slides";
79
- import { slides } from "./slides";
77
+ import { SlideDeck } from 'nextjs-slides';
78
+ import { slides } from './slides';
80
79
 
81
80
  export default function SlidesLayout({
82
81
  children,
@@ -93,17 +92,17 @@ export default function SlidesLayout({
93
92
 
94
93
  ```tsx
95
94
  // app/slides/page.tsx
96
- import { redirect } from "next/navigation";
95
+ import { redirect } from 'next/navigation';
97
96
 
98
97
  export default function SlidesPage() {
99
- redirect("/slides/1");
98
+ redirect('/slides/1');
100
99
  }
101
100
  ```
102
101
 
103
102
  ```tsx
104
103
  // app/slides/[page]/page.tsx
105
- import { getSlide, generateSlideParams } from "nextjs-slides";
106
- import { slides } from "../slides";
104
+ import { getSlide, generateSlideParams } from 'nextjs-slides';
105
+ import { slides } from '../slides';
107
106
 
108
107
  export const generateStaticParams = () => generateSlideParams(slides);
109
108
 
@@ -120,17 +119,17 @@ That's it. Navigate to `/slides` and you have a full slide deck.
120
119
 
121
120
  ## `<SlideDeck>` Props
122
121
 
123
- | Prop | Type | Default | Description |
124
- | -------------- | --------------------------------- | ------------ | ------------------------------------------------------- |
125
- | `slides` | `ReactNode[]` | **required** | Your slides array |
126
- | `speakerNotes` | `(string \| ReactNode \| null)[]` | — | Notes per slide (same index). See Speaker Notes below. |
127
- | `syncEndpoint` | `string` | — | API route for presenter ↔ phone sync. |
128
- | `basePath` | `string` | `"/slides"` | URL prefix for slide routes |
129
- | `exitUrl` | `string` | — | URL for exit button (×). Shows in top-right when set. |
130
- | `showProgress` | `boolean` | `true` | Show dot progress indicator |
131
- | `showCounter` | `boolean` | `true` | Show "3 / 10" counter |
132
- | `className` | `string` | — | Additional class for the deck container |
133
- | `children` | `React.ReactNode` | **required** | Route content (from Next.js) |
122
+ | Prop | Type | Default | Description |
123
+ | -------------- | --------------------------------- | ------------ | ------------------------------------------------------ |
124
+ | `slides` | `ReactNode[]` | **required** | Your slides array |
125
+ | `speakerNotes` | `(string \| ReactNode \| null)[]` | — | Notes per slide (same index). See Speaker Notes below. |
126
+ | `syncEndpoint` | `string` | — | API route for presenter ↔ phone sync. |
127
+ | `basePath` | `string` | `"/slides"` | URL prefix for slide routes |
128
+ | `exitUrl` | `string` | — | URL for exit button (×). Shows in top-right when set. |
129
+ | `showProgress` | `boolean` | `true` | Show dot progress indicator |
130
+ | `showCounter` | `boolean` | `true` | Show "3 / 10" counter |
131
+ | `className` | `string` | — | Additional class for the deck container |
132
+ | `children` | `React.ReactNode` | **required** | Route content (from Next.js) |
134
133
 
135
134
  ## Primitives
136
135
 
@@ -196,20 +195,20 @@ Slide 4 notes. Slide 3 had none.
196
195
  **Leading document title:** If the file starts with `# My Title` (a single heading line), use `stripLeadingTitle: true` so that block isn't treated as slide 1:
197
196
 
198
197
  ```ts
199
- parseSpeakerNotes(markdown, { stripLeadingTitle: true })
198
+ parseSpeakerNotes(markdown, { stripLeadingTitle: true });
200
199
  ```
201
200
 
202
201
  Parse the file and pass it to `SlideDeck`. Include `syncEndpoint` so the phone can follow along:
203
202
 
204
203
  ```tsx
205
204
  // app/slides/layout.tsx
206
- import fs from "fs";
207
- import path from "path";
208
- import { SlideDeck, parseSpeakerNotes } from "nextjs-slides";
209
- import { slides } from "./slides";
205
+ import fs from 'fs';
206
+ import path from 'path';
207
+ import { SlideDeck, parseSpeakerNotes } from 'nextjs-slides';
208
+ import { slides } from './slides';
210
209
 
211
210
  const notes = parseSpeakerNotes(
212
- fs.readFileSync(path.join(process.cwd(), "app/slides/notes.md"), "utf-8")
211
+ fs.readFileSync(path.join(process.cwd(), 'app/slides/notes.md'), 'utf-8')
213
212
  );
214
213
 
215
214
  export default function SlidesLayout({
@@ -218,7 +217,11 @@ export default function SlidesLayout({
218
217
  children: React.ReactNode;
219
218
  }) {
220
219
  return (
221
- <SlideDeck slides={slides} speakerNotes={notes} syncEndpoint="/api/nxs-sync">
220
+ <SlideDeck
221
+ slides={slides}
222
+ speakerNotes={notes}
223
+ syncEndpoint="/api/nxs-sync"
224
+ >
222
225
  {children}
223
226
  </SlideDeck>
224
227
  );
@@ -237,19 +240,19 @@ Open `/notes` on your phone while presenting on your laptop. The phone shows the
237
240
 
238
241
  ```ts
239
242
  // app/api/nxs-sync/route.ts
240
- export { GET, POST } from "nextjs-slides/sync";
243
+ export { GET, POST } from 'nextjs-slides/sync';
241
244
  ```
242
245
 
243
246
  **2. Create the notes page** (same `notes.md`, same `parseSpeakerNotes` — indices must match slides):
244
247
 
245
248
  ```tsx
246
249
  // app/notes/page.tsx
247
- import fs from "fs";
248
- import path from "path";
249
- import { parseSpeakerNotes, SlideNotesView } from "nextjs-slides";
250
+ import fs from 'fs';
251
+ import path from 'path';
252
+ import { parseSpeakerNotes, SlideNotesView } from 'nextjs-slides';
250
253
 
251
254
  const notes = parseSpeakerNotes(
252
- fs.readFileSync(path.join(process.cwd(), "app/slides/notes.md"), "utf-8")
255
+ fs.readFileSync(path.join(process.cwd(), 'app/slides/notes.md'), 'utf-8')
253
256
  );
254
257
 
255
258
  export default function NotesPage() {
@@ -284,7 +287,12 @@ The notes view auto-follows the deck during slides. Once you tap "Next" past the
284
287
  Use `basePath` for a different URL, `exitUrl` for an exit button (×), and `className` for scoped font/syntax overrides:
285
288
 
286
289
  ```tsx
287
- <SlideDeck slides={slides} basePath="/slides-alt" exitUrl="/" className="slides-alt-deck">
290
+ <SlideDeck
291
+ slides={slides}
292
+ basePath="/slides-alt"
293
+ exitUrl="/"
294
+ className="slides-alt-deck"
295
+ >
288
296
  {children}
289
297
  </SlideDeck>
290
298
  ```
@@ -316,13 +324,16 @@ Install `geist`, wire the fonts in your layout, and add the theme variables:
316
324
 
317
325
  ```tsx
318
326
  // app/layout.tsx
319
- import { GeistSans } from "geist/font/sans";
320
- import { GeistMono } from "geist/font/mono";
321
- import { GeistPixelSquare } from "geist/font/pixel";
327
+ import { GeistSans } from 'geist/font/sans';
328
+ import { GeistMono } from 'geist/font/mono';
329
+ import { GeistPixelSquare } from 'geist/font/pixel';
322
330
 
323
331
  export default function RootLayout({ children }) {
324
332
  return (
325
- <html lang="en" className={`${GeistSans.variable} ${GeistMono.variable} ${GeistPixelSquare.variable}`}>
333
+ <html
334
+ lang="en"
335
+ className={`${GeistSans.variable} ${GeistMono.variable} ${GeistPixelSquare.variable}`}
336
+ >
326
337
  <body className={GeistSans.className}>{children}</body>
327
338
  </html>
328
339
  );
@@ -333,7 +344,9 @@ export default function RootLayout({ children }) {
333
344
  /* globals.css @theme inline */
334
345
  --font-sans: var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif;
335
346
  --font-mono: var(--font-geist-mono), ui-monospace, monospace;
336
- --font-pixel: var(--font-geist-pixel-square), var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif;
347
+ --font-pixel:
348
+ var(--font-geist-pixel-square), var(--font-geist-sans), ui-sans-serif,
349
+ system-ui, sans-serif;
337
350
  ```
338
351
 
339
352
  Use `className="font-pixel"` on primitives where you want the pixel display font.
package/dist/cn.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { ClassValue } from 'clsx';
2
2
 
3
+ /** Merge Tailwind classes with `clsx` + `tailwind-merge` (internal utility). */
3
4
  declare function cn(...inputs: ClassValue[]): string;
4
5
 
5
6
  export { cn };
package/dist/cn.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cn.ts"],"sourcesContent":["import { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n"],"mappings":"AAAA,SAAS,YAA6B;AACtC,SAAS,eAAe;AAEjB,SAAS,MAAM,QAAsB;AAC1C,SAAO,QAAQ,KAAK,MAAM,CAAC;AAC7B;","names":[]}
1
+ {"version":3,"sources":["../src/cn.ts"],"sourcesContent":["import { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\n/** Merge Tailwind classes with `clsx` + `tailwind-merge` (internal utility). */\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n"],"mappings":"AAAA,SAAS,YAA6B;AACtC,SAAS,eAAe;AAGjB,SAAS,MAAM,QAAsB;AAC1C,SAAO,QAAQ,KAAK,MAAM,CAAC;AAC7B;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/get-slide.tsx"],"sourcesContent":["import { notFound } from 'next/navigation';\n\n/**\n * Resolves the current slide from params and the slides array.\n * Use in your `[page]/page.tsx` dynamic route.\n *\n * @example\n * ```tsx\n * import { getSlide } from 'nextjs-slides';\n * import { slides } from '../slides';\n *\n * export default async function SlidePage({ params }: { params: Promise<{ page: string }> }) {\n * return getSlide(await params, slides);\n * }\n * ```\n */\nexport function getSlide(params: { page: string }, slides: React.ReactNode[]): React.ReactNode {\n const index = Number(params.page) - 1;\n\n if (isNaN(index) || index < 0 || index >= slides.length) {\n notFound();\n }\n\n return slides[index];\n}\n\n/**\n * Generates static params for all slides. Use with `generateStaticParams`.\n *\n * @example\n * ```tsx\n * import { generateSlideParams } from 'nextjs-slides';\n * import { slides } from '../slides';\n *\n * export const generateStaticParams = () => generateSlideParams(slides);\n * ```\n */\nexport function generateSlideParams(slides: React.ReactNode[]) {\n return slides.map((_, i) => ({ page: String(i + 1) }));\n}\n"],"mappings":"AAAA,SAAS,gBAAgB;AAgBlB,SAAS,SAAS,QAA0B,QAA4C;AAC7F,QAAM,QAAQ,OAAO,OAAO,IAAI,IAAI;AAEpC,MAAI,MAAM,KAAK,KAAK,QAAQ,KAAK,SAAS,OAAO,QAAQ;AACvD,aAAS;AAAA,EACX;AAEA,SAAO,OAAO,KAAK;AACrB;AAaO,SAAS,oBAAoB,QAA2B;AAC7D,SAAO,OAAO,IAAI,CAAC,GAAG,OAAO,EAAE,MAAM,OAAO,IAAI,CAAC,EAAE,EAAE;AACvD;","names":[]}
1
+ {"version":3,"sources":["../src/get-slide.tsx"],"sourcesContent":["import { notFound } from 'next/navigation';\n\n/**\n * Resolves the current slide from params and the slides array.\n * Use in your `[page]/page.tsx` dynamic route.\n *\n * @example\n * ```tsx\n * import { getSlide } from 'nextjs-slides';\n * import { slides } from '../slides';\n *\n * export default async function SlidePage({ params }: { params: Promise<{ page: string }> }) {\n * return getSlide(await params, slides);\n * }\n * ```\n */\nexport function getSlide(\n params: { page: string },\n slides: React.ReactNode[]\n): React.ReactNode {\n const index = Number(params.page) - 1;\n\n if (isNaN(index) || index < 0 || index >= slides.length) {\n notFound();\n }\n\n return slides[index];\n}\n\n/**\n * Generates static params for all slides. Use with `generateStaticParams`.\n *\n * @example\n * ```tsx\n * import { generateSlideParams } from 'nextjs-slides';\n * import { slides } from '../slides';\n *\n * export const generateStaticParams = () => generateSlideParams(slides);\n * ```\n */\nexport function generateSlideParams(slides: React.ReactNode[]) {\n return slides.map((_, i) => ({ page: String(i + 1) }));\n}\n"],"mappings":"AAAA,SAAS,gBAAgB;AAgBlB,SAAS,SACd,QACA,QACiB;AACjB,QAAM,QAAQ,OAAO,OAAO,IAAI,IAAI;AAEpC,MAAI,MAAM,KAAK,KAAK,QAAQ,KAAK,SAAS,OAAO,QAAQ;AACvD,aAAS;AAAA,EACX;AAEA,SAAO,OAAO,KAAK;AACrB;AAaO,SAAS,oBAAoB,QAA2B;AAC7D,SAAO,OAAO,IAAI,CAAC,GAAG,OAAO,EAAE,MAAM,OAAO,IAAI,CAAC,EAAE,EAAE;AACvD;","names":[]}
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { SlideDeck } from './slide-deck.js';
2
2
  export { generateSlideParams, getSlide } from './get-slide.js';
3
- export { Slide, SlideBadge, SlideCode, SlideDemo, SlideHeaderBadge, SlideList, SlideListItem, SlideNote, SlideSpeaker, SlideSpeakerGrid, SlideSpeakerList, SlideSplitLayout, SlideStatement, SlideStatementList, SlideSubtitle, SlideTitle } from './primitives.js';
3
+ export { Slide, SlideBadge, SlideCode, SlideColumns, SlideDemo, SlideHeaderBadge, SlideList, SlideListItem, SlideNote, SlideSpeaker, SlideSpeakerGrid, SlideSpeakerList, SlideSplitLayout, SlideStatement, SlideStatementList, SlideSubtitle, SlideTitle } from './primitives.js';
4
4
  export { SlideLink } from './slide-link.js';
5
5
  export { parseSpeakerNotes } from './parse-speaker-notes.js';
6
6
  export { SlideNotesView } from './slide-notes-view.js';
package/dist/index.js CHANGED
@@ -1,7 +1,13 @@
1
1
  import { SlideDeck } from "./slide-deck";
2
2
  import { getSlide, generateSlideParams } from "./get-slide";
3
- import { Slide, SlideSplitLayout } from "./primitives";
4
- import { SlideTitle, SlideSubtitle, SlideBadge, SlideHeaderBadge, SlideNote } from "./primitives";
3
+ import { Slide, SlideColumns, SlideSplitLayout } from "./primitives";
4
+ import {
5
+ SlideTitle,
6
+ SlideSubtitle,
7
+ SlideBadge,
8
+ SlideHeaderBadge,
9
+ SlideNote
10
+ } from "./primitives";
5
11
  import { SlideCode, SlideList, SlideListItem, SlideDemo } from "./primitives";
6
12
  import { SlideStatementList, SlideStatement } from "./primitives";
7
13
  import { SlideSpeaker, SlideSpeakerGrid, SlideSpeakerList } from "./primitives";
@@ -12,6 +18,7 @@ export {
12
18
  Slide,
13
19
  SlideBadge,
14
20
  SlideCode,
21
+ SlideColumns,
15
22
  SlideDeck,
16
23
  SlideDemo,
17
24
  SlideHeaderBadge,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["// Provider\nexport { SlideDeck } from './slide-deck';\n\n// Routing helpers\nexport { getSlide, generateSlideParams } from './get-slide';\n\n// Primitives — Layout\nexport { Slide, SlideSplitLayout } from './primitives';\n\n// Primitives — Typography\nexport { SlideTitle, SlideSubtitle, SlideBadge, SlideHeaderBadge, SlideNote } from './primitives';\n\n// Primitives — Content\nexport { SlideCode, SlideList, SlideListItem, SlideDemo } from './primitives';\n\n// Primitives — Statements\nexport { SlideStatementList, SlideStatement } from './primitives';\n\n// Primitives — Speakers\nexport { SlideSpeaker, SlideSpeakerGrid, SlideSpeakerList } from './primitives';\n\n// Navigation\nexport { SlideLink } from './slide-link';\n\n// Utilities\nexport { parseSpeakerNotes } from './parse-speaker-notes';\n\n// Speaker notes\nexport { SlideNotesView } from './slide-notes-view';\n\n// Types\nexport type { SlideAlign, SlideLinkVariant, SlideDeckConfig } from './types';\n"],"mappings":"AACA,SAAS,iBAAiB;AAG1B,SAAS,UAAU,2BAA2B;AAG9C,SAAS,OAAO,wBAAwB;AAGxC,SAAS,YAAY,eAAe,YAAY,kBAAkB,iBAAiB;AAGnF,SAAS,WAAW,WAAW,eAAe,iBAAiB;AAG/D,SAAS,oBAAoB,sBAAsB;AAGnD,SAAS,cAAc,kBAAkB,wBAAwB;AAGjE,SAAS,iBAAiB;AAG1B,SAAS,yBAAyB;AAGlC,SAAS,sBAAsB;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["// Provider\nexport { SlideDeck } from './slide-deck';\n\n// Routing helpers\nexport { getSlide, generateSlideParams } from './get-slide';\n\n// Primitives — Layout\nexport { Slide, SlideColumns, SlideSplitLayout } from './primitives';\n\n// Primitives — Typography\nexport {\n SlideTitle,\n SlideSubtitle,\n SlideBadge,\n SlideHeaderBadge,\n SlideNote,\n} from './primitives';\n\n// Primitives — Content\nexport { SlideCode, SlideList, SlideListItem, SlideDemo } from './primitives';\n\n// Primitives — Statements\nexport { SlideStatementList, SlideStatement } from './primitives';\n\n// Primitives — Speakers\nexport { SlideSpeaker, SlideSpeakerGrid, SlideSpeakerList } from './primitives';\n\n// Navigation\nexport { SlideLink } from './slide-link';\n\n// Utilities\nexport { parseSpeakerNotes } from './parse-speaker-notes';\n\n// Speaker notes\nexport { SlideNotesView } from './slide-notes-view';\n\n// Types\nexport type { SlideAlign, SlideLinkVariant, SlideDeckConfig } from './types';\n"],"mappings":"AACA,SAAS,iBAAiB;AAG1B,SAAS,UAAU,2BAA2B;AAG9C,SAAS,OAAO,cAAc,wBAAwB;AAGtD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP,SAAS,WAAW,WAAW,eAAe,iBAAiB;AAG/D,SAAS,oBAAoB,sBAAsB;AAGnD,SAAS,cAAc,kBAAkB,wBAAwB;AAGjE,SAAS,iBAAiB;AAG1B,SAAS,yBAAyB;AAGlC,SAAS,sBAAsB;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/parse-speaker-notes.ts"],"sourcesContent":["/**\n * Parse speaker notes from a markdown string.\n *\n * Format: one section per slide, separated by `---` on its own line.\n * Sections map to slides by position: notes[0] = slide 1, notes[1] = slide 2, etc.\n * Empty sections produce `null` (no notes for that slide).\n *\n * @param stripLeadingTitle - If true, removes a leading section that is a single\n * markdown heading (# Title). Use when the file starts with a document title.\n *\n * @example\n * ```md\n * Welcome everyone. This is the opening slide.\n *\n * ---\n *\n * Talk about the base container here.\n *\n * ---\n *\n * ---\n *\n * Slide 4 notes. Slide 3 had none.\n * ```\n *\n * @example\n * ```tsx\n * const notes = parseSpeakerNotes(markdown, { stripLeadingTitle: true });\n * ```\n */\nexport function parseSpeakerNotes(\n markdown: string,\n options?: { stripLeadingTitle?: boolean },\n): (string | null)[] {\n let sections = markdown.split(/^---$/m).map((section) => {\n const trimmed = section.trim();\n return trimmed.length > 0 ? trimmed : null;\n });\n\n if (options?.stripLeadingTitle && sections[0] && /^#+\\s+.+$/.test(sections[0].replace(/\\n.*/s, ''))) {\n sections = sections.slice(1);\n }\n\n return sections;\n}\n"],"mappings":"AA8BO,SAAS,kBACd,UACA,SACmB;AACnB,MAAI,WAAW,SAAS,MAAM,QAAQ,EAAE,IAAI,CAAC,YAAY;AACvD,UAAM,UAAU,QAAQ,KAAK;AAC7B,WAAO,QAAQ,SAAS,IAAI,UAAU;AAAA,EACxC,CAAC;AAED,MAAI,SAAS,qBAAqB,SAAS,CAAC,KAAK,YAAY,KAAK,SAAS,CAAC,EAAE,QAAQ,SAAS,EAAE,CAAC,GAAG;AACnG,eAAW,SAAS,MAAM,CAAC;AAAA,EAC7B;AAEA,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../src/parse-speaker-notes.ts"],"sourcesContent":["/**\n * Parse speaker notes from a markdown string.\n *\n * Format: one section per slide, separated by `---` on its own line.\n * Sections map to slides by position: notes[0] = slide 1, notes[1] = slide 2, etc.\n * Empty sections produce `null` (no notes for that slide).\n *\n * @param stripLeadingTitle - If true, removes a leading section that is a single\n * markdown heading (# Title). Use when the file starts with a document title.\n *\n * @example\n * ```md\n * Welcome everyone. This is the opening slide.\n *\n * ---\n *\n * Talk about the base container here.\n *\n * ---\n *\n * ---\n *\n * Slide 4 notes. Slide 3 had none.\n * ```\n *\n * @example\n * ```tsx\n * const notes = parseSpeakerNotes(markdown, { stripLeadingTitle: true });\n * ```\n */\nexport function parseSpeakerNotes(\n markdown: string,\n options?: { stripLeadingTitle?: boolean }\n): (string | null)[] {\n let sections = markdown.split(/^---$/m).map((section) => {\n const trimmed = section.trim();\n return trimmed.length > 0 ? trimmed : null;\n });\n\n if (\n options?.stripLeadingTitle &&\n sections[0] &&\n /^#+\\s+.+$/.test(sections[0].replace(/\\n.*/s, ''))\n ) {\n sections = sections.slice(1);\n }\n\n return sections;\n}\n"],"mappings":"AA8BO,SAAS,kBACd,UACA,SACmB;AACnB,MAAI,WAAW,SAAS,MAAM,QAAQ,EAAE,IAAI,CAAC,YAAY;AACvD,UAAM,UAAU,QAAQ,KAAK;AAC7B,WAAO,QAAQ,SAAS,IAAI,UAAU;AAAA,EACxC,CAAC;AAED,MACE,SAAS,qBACT,SAAS,CAAC,KACV,YAAY,KAAK,SAAS,CAAC,EAAE,QAAQ,SAAS,EAAE,CAAC,GACjD;AACA,eAAW,SAAS,MAAM,CAAC;AAAA,EAC7B;AAEA,SAAO;AACT;","names":[]}
@@ -1,63 +1,178 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { SlideAlign } from './types.js';
3
3
 
4
+ /**
5
+ * Full-viewport slide container with centered content and a decorative border.
6
+ *
7
+ * This is the primary slide primitive — use it as a top-level element in
8
+ * the slides array. For a two-column layout that fills the whole viewport,
9
+ * use {@link SlideSplitLayout} instead.
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * <Slide align="left">
14
+ * <SlideTitle>My Slide</SlideTitle>
15
+ * <SlideSubtitle>Supporting text</SlideSubtitle>
16
+ * </Slide>
17
+ * ```
18
+ */
4
19
  declare function Slide({ children, align, className, }: {
5
20
  children: React.ReactNode;
21
+ /** Content alignment. `"center"` centers both horizontally and text; `"left"` aligns to the start. */
6
22
  align?: SlideAlign;
7
23
  className?: string;
8
24
  }): react_jsx_runtime.JSX.Element;
25
+ /**
26
+ * Inline two-column grid for use **inside** a `Slide`.
27
+ *
28
+ * Use this when you need a title or other content above two columns.
29
+ * For a full-viewport two-column slide, use `SlideSplitLayout` instead.
30
+ */
31
+ declare function SlideColumns({ left, right, className, }: {
32
+ left: React.ReactNode;
33
+ right: React.ReactNode;
34
+ className?: string;
35
+ }): react_jsx_runtime.JSX.Element;
36
+ /**
37
+ * Full-viewport two-column slide — a **top-level** alternative to `Slide`.
38
+ *
39
+ * Do **not** nest this inside `Slide`; it renders its own `h-dvh w-dvw`
40
+ * container, border, and padding. To combine a title with two columns,
41
+ * use `SlideColumns` inside a `Slide` instead.
42
+ */
9
43
  declare function SlideSplitLayout({ left, right, className, }: {
10
44
  left: React.ReactNode;
11
45
  right: React.ReactNode;
12
46
  className?: string;
13
47
  }): react_jsx_runtime.JSX.Element;
14
- declare function SlideTitle({ children, className }: {
48
+ /**
49
+ * Primary heading for a slide. Renders an `<h1>` with responsive sizing
50
+ * that scales from `text-4xl` to `text-7xl` across breakpoints.
51
+ *
52
+ * Override the default size with `className` (e.g. `className="text-3xl sm:text-4xl"`).
53
+ */
54
+ declare function SlideTitle({ children, className, }: {
15
55
  children: React.ReactNode;
16
56
  className?: string;
17
57
  }): react_jsx_runtime.JSX.Element;
18
- declare function SlideSubtitle({ children, className }: {
58
+ /**
59
+ * Secondary text below a title. Renders a `<p>` in a muted foreground color
60
+ * with responsive sizing (`text-lg` to `text-2xl`).
61
+ */
62
+ declare function SlideSubtitle({ children, className, }: {
19
63
  children: React.ReactNode;
20
64
  className?: string;
21
65
  }): react_jsx_runtime.JSX.Element;
22
- declare function SlideBadge({ children, className }: {
66
+ /**
67
+ * Small pill-shaped label, typically placed above a title to categorise
68
+ * the slide (e.g. component name, topic tag).
69
+ */
70
+ declare function SlideBadge({ children, className, }: {
23
71
  children: React.ReactNode;
24
72
  className?: string;
25
73
  }): react_jsx_runtime.JSX.Element;
26
- declare function SlideHeaderBadge({ children, className }: {
74
+ /**
75
+ * Italic accent text for slide headers — use for event names, series labels,
76
+ * or other branding above the title.
77
+ */
78
+ declare function SlideHeaderBadge({ children, className, }: {
27
79
  children: React.ReactNode;
28
80
  className?: string;
29
81
  }): react_jsx_runtime.JSX.Element;
30
- declare function SlideCode({ children, className, title }: {
82
+ /**
83
+ * Syntax-highlighted code block powered by highlight.js.
84
+ *
85
+ * Pass code as a **string** child. The language is auto-detected from the
86
+ * `title` file extension (e.g. `"example.tsx"` → TypeScript); falls back
87
+ * to TypeScript when unspecified. Supports JS, TS, JSX, and TSX.
88
+ *
89
+ * Theme colours are controlled by CSS custom properties (`--sh-*` / `--nxs-code-*`)
90
+ * defined in `nextjs-slides/styles.css`. Override them in `:root` or `.dark`.
91
+ *
92
+ * @example
93
+ * ```tsx
94
+ * <SlideCode title="api.ts">{`export async function fetchData() {
95
+ * return fetch('/api/data');
96
+ * }`}</SlideCode>
97
+ * ```
98
+ */
99
+ declare function SlideCode({ children, className, title, }: {
100
+ /** Code string to highlight. Leading/trailing whitespace is trimmed automatically. */
31
101
  children: string;
32
102
  className?: string;
103
+ /** File name shown above the code block. Its extension determines the highlight language. */
33
104
  title?: string;
34
105
  }): react_jsx_runtime.JSX.Element;
35
- declare function SlideList({ children, className }: {
106
+ /**
107
+ * Bullet-point list container. Wrap {@link SlideListItem} children inside this.
108
+ */
109
+ declare function SlideList({ children, className, }: {
36
110
  children: React.ReactNode;
37
111
  className?: string;
38
112
  }): react_jsx_runtime.JSX.Element;
39
- declare function SlideListItem({ children, className }: {
113
+ /**
114
+ * Single bullet item inside a {@link SlideList}. Renders a small dot
115
+ * followed by the content.
116
+ */
117
+ declare function SlideListItem({ children, className, }: {
40
118
  children: React.ReactNode;
41
119
  className?: string;
42
120
  }): react_jsx_runtime.JSX.Element;
43
- declare function SlideNote({ children, className }: {
121
+ /**
122
+ * Small footnote text in a faded colour, typically placed at the bottom
123
+ * of a slide for annotations or caveats.
124
+ */
125
+ declare function SlideNote({ children, className, }: {
44
126
  children: React.ReactNode;
45
127
  className?: string;
46
128
  }): react_jsx_runtime.JSX.Element;
129
+ /**
130
+ * Live interactive component embed. Keyboard navigation (arrow keys, space)
131
+ * is disabled while focus is inside the demo area so the embedded component
132
+ * can handle its own input.
133
+ *
134
+ * The container tracks its maximum height to prevent layout jumps when the
135
+ * child re-renders with different content sizes.
136
+ *
137
+ * @example
138
+ * ```tsx
139
+ * <SlideDemo label="Live counter">
140
+ * <Counter />
141
+ * </SlideDemo>
142
+ * ```
143
+ */
47
144
  declare function SlideDemo({ children, className, label, }: {
48
145
  children: React.ReactNode;
49
146
  className?: string;
147
+ /** Optional uppercase label shown above the demo area. */
50
148
  label?: string;
51
149
  }): react_jsx_runtime.JSX.Element;
52
- declare function SlideStatementList({ children, className }: {
150
+ /**
151
+ * Container for {@link SlideStatement} items. Adds border separators between
152
+ * statements automatically.
153
+ */
154
+ declare function SlideStatementList({ children, className, }: {
53
155
  children: React.ReactNode;
54
156
  className?: string;
55
157
  }): react_jsx_runtime.JSX.Element;
158
+ /**
159
+ * Title + description pair for structured content blocks.
160
+ * Use inside a {@link SlideStatementList} for automatic border separators.
161
+ */
56
162
  declare function SlideStatement({ title, description, className, }: {
163
+ /** Bold heading text. */
57
164
  title: string;
165
+ /** Optional muted description below the title. */
58
166
  description?: string;
59
167
  className?: string;
60
168
  }): react_jsx_runtime.JSX.Element;
169
+ /**
170
+ * Speaker card with an avatar circle, name, and role/title.
171
+ * When `avatar` is omitted a placeholder circle is shown.
172
+ *
173
+ * Use inside {@link SlideSpeakerGrid} or {@link SlideSpeakerList} to
174
+ * lay out multiple speakers.
175
+ */
61
176
  declare function SlideSpeaker({ name, title, avatar, className, }: {
62
177
  name: string;
63
178
  title: string;
@@ -65,13 +180,20 @@ declare function SlideSpeaker({ name, title, avatar, className, }: {
65
180
  avatar?: string;
66
181
  className?: string;
67
182
  }): react_jsx_runtime.JSX.Element;
68
- declare function SlideSpeakerGrid({ children, className }: {
183
+ /**
184
+ * Two-column responsive grid for laying out {@link SlideSpeaker} cards
185
+ * side by side (stacks to one column on small screens).
186
+ */
187
+ declare function SlideSpeakerGrid({ children, className, }: {
69
188
  children: React.ReactNode;
70
189
  className?: string;
71
190
  }): react_jsx_runtime.JSX.Element;
72
- declare function SlideSpeakerList({ children, className }: {
191
+ /**
192
+ * Vertical stack layout for {@link SlideSpeaker} cards.
193
+ */
194
+ declare function SlideSpeakerList({ children, className, }: {
73
195
  children: React.ReactNode;
74
196
  className?: string;
75
197
  }): react_jsx_runtime.JSX.Element;
76
198
 
77
- export { Slide, SlideBadge, SlideCode, SlideDemo, SlideHeaderBadge, SlideList, SlideListItem, SlideNote, SlideSpeaker, SlideSpeakerGrid, SlideSpeakerList, SlideSplitLayout, SlideStatement, SlideStatementList, SlideSubtitle, SlideTitle };
199
+ export { Slide, SlideBadge, SlideCode, SlideColumns, SlideDemo, SlideHeaderBadge, SlideList, SlideListItem, SlideNote, SlideSpeaker, SlideSpeakerGrid, SlideSpeakerList, SlideSplitLayout, SlideStatement, SlideStatementList, SlideSubtitle, SlideTitle };
@@ -11,7 +11,8 @@ function highlightCode(code, lang) {
11
11
  if (!lang) return hljs.highlight(code, { language: "typescript" }).value;
12
12
  const language = lang === "ts" || lang === "tsx" ? "typescript" : lang;
13
13
  const registered = hljs.getLanguage(language);
14
- if (!registered) return hljs.highlight(code, { language: "typescript" }).value;
14
+ if (!registered)
15
+ return hljs.highlight(code, { language: "typescript" }).value;
15
16
  return hljs.highlight(code, { language }).value;
16
17
  }
17
18
  import { SlideDemoContent } from "./slide-demo-content";
@@ -30,7 +31,13 @@ function Slide({
30
31
  className
31
32
  ),
32
33
  children: [
33
- /* @__PURE__ */ jsx("div", { className: "border-foreground/10 pointer-events-none absolute inset-4 border sm:inset-6", "aria-hidden": true }),
34
+ /* @__PURE__ */ jsx(
35
+ "div",
36
+ {
37
+ className: "border-foreground/10 pointer-events-none absolute inset-4 border sm:inset-6",
38
+ "aria-hidden": true
39
+ }
40
+ ),
34
41
  /* @__PURE__ */ jsx(
35
42
  "div",
36
43
  {
@@ -46,32 +53,84 @@ function Slide({
46
53
  }
47
54
  );
48
55
  }
49
- function SlideSplitLayout({
56
+ function SlideColumns({
50
57
  left,
51
58
  right,
52
59
  className
53
60
  }) {
54
- return /* @__PURE__ */ jsxs("div", { className: cn("nxs-slide nxs-slide-split relative flex h-dvh w-dvw", className), children: [
55
- /* @__PURE__ */ jsx("div", { className: "border-foreground/10 pointer-events-none absolute inset-4 border sm:inset-6", "aria-hidden": true }),
56
- /* @__PURE__ */ jsx("div", { className: "nxs-slide-split-col relative z-10 flex w-1/2 flex-col justify-center px-12 py-20 sm:px-16 md:px-20 lg:px-24", children: left }),
57
- /* @__PURE__ */ jsx("div", { className: "nxs-slide-split-divider bg-foreground/10 absolute top-4 bottom-4 left-1/2 z-10 w-px sm:top-6 sm:bottom-6", "aria-hidden": true }),
58
- /* @__PURE__ */ jsx("div", { className: "nxs-slide-split-col relative z-10 flex w-1/2 flex-col justify-center px-12 py-20 sm:px-16 md:px-20 lg:px-24", children: right })
61
+ return /* @__PURE__ */ jsxs("div", { className: cn("grid w-full grid-cols-2 gap-8", className), children: [
62
+ /* @__PURE__ */ jsx("div", { className: "flex min-w-0 flex-col gap-4", children: left }),
63
+ /* @__PURE__ */ jsx("div", { className: "flex min-w-0 flex-col gap-4", children: right })
59
64
  ] });
60
65
  }
61
- function SlideTitle({ children, className }) {
66
+ function SlideSplitLayout({
67
+ left,
68
+ right,
69
+ className
70
+ }) {
71
+ return /* @__PURE__ */ jsxs(
72
+ "div",
73
+ {
74
+ className: cn(
75
+ "nxs-slide nxs-slide-split relative flex h-dvh w-dvw",
76
+ className
77
+ ),
78
+ children: [
79
+ /* @__PURE__ */ jsx(
80
+ "div",
81
+ {
82
+ className: "border-foreground/10 pointer-events-none absolute inset-4 border sm:inset-6",
83
+ "aria-hidden": true
84
+ }
85
+ ),
86
+ /* @__PURE__ */ jsx("div", { className: "nxs-slide-split-col relative z-10 flex w-1/2 flex-col justify-center px-12 py-20 sm:px-16 md:px-20 lg:px-24", children: left }),
87
+ /* @__PURE__ */ jsx(
88
+ "div",
89
+ {
90
+ className: "nxs-slide-split-divider bg-foreground/10 absolute top-4 bottom-4 left-1/2 z-10 w-px sm:top-6 sm:bottom-6",
91
+ "aria-hidden": true
92
+ }
93
+ ),
94
+ /* @__PURE__ */ jsx("div", { className: "nxs-slide-split-col relative z-10 flex w-1/2 flex-col justify-center px-12 py-20 sm:px-16 md:px-20 lg:px-24", children: right })
95
+ ]
96
+ }
97
+ );
98
+ }
99
+ function SlideTitle({
100
+ children,
101
+ className
102
+ }) {
62
103
  return /* @__PURE__ */ jsx(
63
104
  "h1",
64
105
  {
65
- className: cn("text-foreground text-4xl font-extrabold sm:text-5xl md:text-6xl lg:text-7xl", className),
106
+ className: cn(
107
+ "text-foreground text-4xl font-extrabold sm:text-5xl md:text-6xl lg:text-7xl",
108
+ className
109
+ ),
66
110
  style: { letterSpacing: "-0.04em" },
67
111
  children
68
112
  }
69
113
  );
70
114
  }
71
- function SlideSubtitle({ children, className }) {
72
- return /* @__PURE__ */ jsx("p", { className: cn("text-muted-foreground text-lg sm:text-xl md:text-2xl", className), children });
115
+ function SlideSubtitle({
116
+ children,
117
+ className
118
+ }) {
119
+ return /* @__PURE__ */ jsx(
120
+ "p",
121
+ {
122
+ className: cn(
123
+ "text-muted-foreground text-lg sm:text-xl md:text-2xl",
124
+ className
125
+ ),
126
+ children
127
+ }
128
+ );
73
129
  }
74
- function SlideBadge({ children, className }) {
130
+ function SlideBadge({
131
+ children,
132
+ className
133
+ }) {
75
134
  return /* @__PURE__ */ jsx(
76
135
  "span",
77
136
  {
@@ -83,27 +142,58 @@ function SlideBadge({ children, className }) {
83
142
  }
84
143
  );
85
144
  }
86
- function SlideHeaderBadge({ children, className }) {
145
+ function SlideHeaderBadge({
146
+ children,
147
+ className
148
+ }) {
87
149
  return /* @__PURE__ */ jsx("div", { className: cn("flex items-center gap-3", className), children: /* @__PURE__ */ jsx("span", { className: "text-foreground text-xl font-semibold tracking-tight italic sm:text-2xl", children }) });
88
150
  }
89
- function SlideCode({ children, className, title }) {
151
+ function SlideCode({
152
+ children,
153
+ className,
154
+ title
155
+ }) {
90
156
  const lang = title?.split(".").pop();
91
- const html = highlightCode(children, lang);
157
+ const html = highlightCode(children.trim(), lang);
92
158
  return /* @__PURE__ */ jsxs("div", { className: cn("nxs-code-wrapper", className), children: [
93
159
  title && /* @__PURE__ */ jsx("div", { className: "text-muted-foreground mb-2 text-xs font-medium tracking-wider uppercase", children: title }),
94
160
  /* @__PURE__ */ jsx("pre", { className: "nxs-code-block", children: /* @__PURE__ */ jsx("code", { dangerouslySetInnerHTML: { __html: html } }) })
95
161
  ] });
96
162
  }
97
- function SlideList({ children, className }) {
163
+ function SlideList({
164
+ children,
165
+ className
166
+ }) {
98
167
  return /* @__PURE__ */ jsx("ul", { className: cn("flex flex-col gap-4 text-left", className), children });
99
168
  }
100
- function SlideListItem({ children, className }) {
101
- return /* @__PURE__ */ jsxs("li", { className: cn("text-foreground/70 flex items-start gap-3 text-lg sm:text-xl", className), children: [
102
- /* @__PURE__ */ jsx("span", { className: "bg-foreground/40 mt-2 block h-1.5 w-1.5 shrink-0 rounded-full", "aria-hidden": true }),
103
- /* @__PURE__ */ jsx("span", { children })
104
- ] });
169
+ function SlideListItem({
170
+ children,
171
+ className
172
+ }) {
173
+ return /* @__PURE__ */ jsxs(
174
+ "li",
175
+ {
176
+ className: cn(
177
+ "text-foreground/70 flex items-start gap-3 text-lg sm:text-xl",
178
+ className
179
+ ),
180
+ children: [
181
+ /* @__PURE__ */ jsx(
182
+ "span",
183
+ {
184
+ className: "bg-foreground/40 mt-2 block h-1.5 w-1.5 shrink-0 rounded-full",
185
+ "aria-hidden": true
186
+ }
187
+ ),
188
+ /* @__PURE__ */ jsx("span", { children })
189
+ ]
190
+ }
191
+ );
105
192
  }
106
- function SlideNote({ children, className }) {
193
+ function SlideNote({
194
+ children,
195
+ className
196
+ }) {
107
197
  return /* @__PURE__ */ jsx("p", { className: cn("text-muted-foreground/50 mt-4 text-sm", className), children });
108
198
  }
109
199
  function SlideDemo({
@@ -111,12 +201,22 @@ function SlideDemo({
111
201
  className,
112
202
  label
113
203
  }) {
114
- return /* @__PURE__ */ jsxs("div", { "data-slide-interactive": true, className: cn("min-w-0 w-full max-w-2xl", className), children: [
115
- label && /* @__PURE__ */ jsx("div", { className: "text-muted-foreground mb-2 text-xs font-medium tracking-wider uppercase", children: label }),
116
- /* @__PURE__ */ jsx("div", { className: "border-foreground/10 bg-foreground/[0.03] min-w-0 w-full max-w-full border p-4 sm:p-6", children: /* @__PURE__ */ jsx(SlideDemoContent, { children }) })
117
- ] });
204
+ return /* @__PURE__ */ jsxs(
205
+ "div",
206
+ {
207
+ "data-slide-interactive": true,
208
+ className: cn("min-w-0 w-full max-w-2xl", className),
209
+ children: [
210
+ label && /* @__PURE__ */ jsx("div", { className: "text-muted-foreground mb-2 text-xs font-medium tracking-wider uppercase", children: label }),
211
+ /* @__PURE__ */ jsx("div", { className: "border-foreground/10 bg-foreground/[0.03] min-w-0 w-full max-w-full border p-4 sm:p-6", children: /* @__PURE__ */ jsx(SlideDemoContent, { children }) })
212
+ ]
213
+ }
214
+ );
118
215
  }
119
- function SlideStatementList({ children, className }) {
216
+ function SlideStatementList({
217
+ children,
218
+ className
219
+ }) {
120
220
  return /* @__PURE__ */ jsx("div", { className: cn("flex min-w-0 w-full flex-col", className), children });
121
221
  }
122
222
  function SlideStatement({
@@ -124,10 +224,19 @@ function SlideStatement({
124
224
  description,
125
225
  className
126
226
  }) {
127
- return /* @__PURE__ */ jsxs("div", { className: cn("border-foreground/10 border-t px-8 py-8 last:border-b sm:px-12 md:px-16", className), children: [
128
- /* @__PURE__ */ jsx("h3", { className: "text-foreground text-lg font-bold sm:text-xl md:text-2xl", children: title }),
129
- description && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground mt-1 text-sm sm:text-base", children: description })
130
- ] });
227
+ return /* @__PURE__ */ jsxs(
228
+ "div",
229
+ {
230
+ className: cn(
231
+ "border-foreground/10 border-t px-8 py-8 last:border-b sm:px-12 md:px-16",
232
+ className
233
+ ),
234
+ children: [
235
+ /* @__PURE__ */ jsx("h3", { className: "text-foreground text-lg font-bold sm:text-xl md:text-2xl", children: title }),
236
+ description && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground mt-1 text-sm sm:text-base", children: description })
237
+ ]
238
+ }
239
+ );
131
240
  }
132
241
  function SlideSpeaker({
133
242
  name,
@@ -153,16 +262,23 @@ function SlideSpeaker({
153
262
  ] })
154
263
  ] });
155
264
  }
156
- function SlideSpeakerGrid({ children, className }) {
265
+ function SlideSpeakerGrid({
266
+ children,
267
+ className
268
+ }) {
157
269
  return /* @__PURE__ */ jsx("div", { className: cn("grid grid-cols-1 gap-6 sm:grid-cols-2", className), children });
158
270
  }
159
- function SlideSpeakerList({ children, className }) {
271
+ function SlideSpeakerList({
272
+ children,
273
+ className
274
+ }) {
160
275
  return /* @__PURE__ */ jsx("div", { className: cn("flex flex-col gap-6", className), children });
161
276
  }
162
277
  export {
163
278
  Slide,
164
279
  SlideBadge,
165
280
  SlideCode,
281
+ SlideColumns,
166
282
  SlideDemo,
167
283
  SlideHeaderBadge,
168
284
  SlideList,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/primitives.tsx"],"sourcesContent":["import hljs from 'highlight.js/lib/core';\nimport javascript from 'highlight.js/lib/languages/javascript';\nimport typescript from 'highlight.js/lib/languages/typescript';\nimport xml from 'highlight.js/lib/languages/xml';\nimport { cn } from './cn';\n\nhljs.registerLanguage('javascript', javascript);\nhljs.registerLanguage('typescript', typescript);\nhljs.registerLanguage('xml', xml);\n\nfunction highlightCode(code: string, lang?: string): string {\n if (!lang) return hljs.highlight(code, { language: 'typescript' }).value;\n const language = lang === 'ts' || lang === 'tsx' ? 'typescript' : lang;\n const registered = hljs.getLanguage(language);\n if (!registered) return hljs.highlight(code, { language: 'typescript' }).value;\n return hljs.highlight(code, { language }).value;\n}\nimport { SlideDemoContent } from './slide-demo-content';\nimport type { SlideAlign } from './types';\n\nexport function Slide({\n children,\n align = 'center',\n className,\n}: {\n children: React.ReactNode;\n align?: SlideAlign;\n className?: string;\n}) {\n return (\n <div\n className={cn(\n 'nxs-slide relative flex h-dvh w-dvw flex-col justify-center gap-8 px-12 py-20 sm:px-24 md:px-32 lg:px-40',\n align === 'center' && 'items-center text-center',\n align === 'left' && 'items-start text-left',\n className,\n )}\n >\n <div className=\"border-foreground/10 pointer-events-none absolute inset-4 border sm:inset-6\" aria-hidden />\n <div\n className={cn(\n 'relative z-10 flex max-w-4xl flex-col gap-10',\n align === 'center' && 'items-center',\n align === 'left' && 'items-start',\n )}\n >\n {children}\n </div>\n </div>\n );\n}\n\nexport function SlideSplitLayout({\n left,\n right,\n className,\n}: {\n left: React.ReactNode;\n right: React.ReactNode;\n className?: string;\n}) {\n return (\n <div className={cn('nxs-slide nxs-slide-split relative flex h-dvh w-dvw', className)}>\n <div className=\"border-foreground/10 pointer-events-none absolute inset-4 border sm:inset-6\" aria-hidden />\n <div className=\"nxs-slide-split-col relative z-10 flex w-1/2 flex-col justify-center px-12 py-20 sm:px-16 md:px-20 lg:px-24\">\n {left}\n </div>\n <div className=\"nxs-slide-split-divider bg-foreground/10 absolute top-4 bottom-4 left-1/2 z-10 w-px sm:top-6 sm:bottom-6\" aria-hidden />\n <div className=\"nxs-slide-split-col relative z-10 flex w-1/2 flex-col justify-center px-12 py-20 sm:px-16 md:px-20 lg:px-24\">{right}</div>\n </div>\n );\n}\n\nexport function SlideTitle({ children, className }: { children: React.ReactNode; className?: string }) {\n return (\n <h1\n className={cn('text-foreground text-4xl font-extrabold sm:text-5xl md:text-6xl lg:text-7xl', className)}\n style={{ letterSpacing: '-0.04em' }}\n >\n {children}\n </h1>\n );\n}\n\nexport function SlideSubtitle({ children, className }: { children: React.ReactNode; className?: string }) {\n return <p className={cn('text-muted-foreground text-lg sm:text-xl md:text-2xl', className)}>{children}</p>;\n}\n\nexport function SlideBadge({ children, className }: { children: React.ReactNode; className?: string }) {\n return (\n <span\n className={cn(\n 'bg-foreground text-background inline-block w-fit shrink-0 rounded-full px-4 py-1.5 text-sm font-semibold tracking-wide',\n className,\n )}\n >\n {children}\n </span>\n );\n}\n\nexport function SlideHeaderBadge({ children, className }: { children: React.ReactNode; className?: string }) {\n return (\n <div className={cn('flex items-center gap-3', className)}>\n <span className=\"text-foreground text-xl font-semibold tracking-tight italic sm:text-2xl\">{children}</span>\n </div>\n );\n}\n\nexport function SlideCode({ children, className, title }: { children: string; className?: string; title?: string }) {\n const lang = title?.split('.').pop();\n const html = highlightCode(children, lang);\n\n return (\n <div className={cn('nxs-code-wrapper', className)}>\n {title && <div className=\"text-muted-foreground mb-2 text-xs font-medium tracking-wider uppercase\">{title}</div>}\n <pre className=\"nxs-code-block\">\n <code dangerouslySetInnerHTML={{ __html: html }} />\n </pre>\n </div>\n );\n}\n\nexport function SlideList({ children, className }: { children: React.ReactNode; className?: string }) {\n return <ul className={cn('flex flex-col gap-4 text-left', className)}>{children}</ul>;\n}\n\nexport function SlideListItem({ children, className }: { children: React.ReactNode; className?: string }) {\n return (\n <li className={cn('text-foreground/70 flex items-start gap-3 text-lg sm:text-xl', className)}>\n <span className=\"bg-foreground/40 mt-2 block h-1.5 w-1.5 shrink-0 rounded-full\" aria-hidden />\n <span>{children}</span>\n </li>\n );\n}\n\nexport function SlideNote({ children, className }: { children: React.ReactNode; className?: string }) {\n return <p className={cn('text-muted-foreground/50 mt-4 text-sm', className)}>{children}</p>;\n}\n\nexport function SlideDemo({\n children,\n className,\n label,\n}: {\n children: React.ReactNode;\n className?: string;\n label?: string;\n}) {\n return (\n <div data-slide-interactive className={cn('min-w-0 w-full max-w-2xl', className)}>\n {label && <div className=\"text-muted-foreground mb-2 text-xs font-medium tracking-wider uppercase\">{label}</div>}\n <div className=\"border-foreground/10 bg-foreground/[0.03] min-w-0 w-full max-w-full border p-4 sm:p-6\">\n <SlideDemoContent>{children}</SlideDemoContent>\n </div>\n </div>\n );\n}\n\nexport function SlideStatementList({ children, className }: { children: React.ReactNode; className?: string }) {\n return <div className={cn('flex min-w-0 w-full flex-col', className)}>{children}</div>;\n}\n\nexport function SlideStatement({\n title,\n description,\n className,\n}: {\n title: string;\n description?: string;\n className?: string;\n}) {\n return (\n <div className={cn('border-foreground/10 border-t px-8 py-8 last:border-b sm:px-12 md:px-16', className)}>\n <h3 className=\"text-foreground text-lg font-bold sm:text-xl md:text-2xl\">{title}</h3>\n {description && <p className=\"text-muted-foreground mt-1 text-sm sm:text-base\">{description}</p>}\n </div>\n );\n}\n\nexport function SlideSpeaker({\n name,\n title,\n avatar,\n className,\n}: {\n name: string;\n title: string;\n /** Image URL or path for the speaker avatar. Falls back to placeholder when omitted. */\n avatar?: string;\n className?: string;\n}) {\n return (\n <div className={cn('flex items-center gap-4', className)}>\n <div\n className={cn(\n 'h-12 w-12 shrink-0 overflow-hidden rounded-full',\n avatar ? 'relative' : 'bg-foreground/15 border-foreground/20 border',\n )}\n aria-hidden\n >\n {avatar ? (\n <img src={avatar} alt=\"\" className=\"h-full w-full object-cover\" />\n ) : null}\n </div>\n <div>\n <p className=\"text-foreground/90 text-sm font-medium tracking-widest uppercase\">{name}</p>\n <p className=\"text-muted-foreground text-sm tracking-wider uppercase\">{title}</p>\n </div>\n </div>\n );\n}\n\nexport function SlideSpeakerGrid({ children, className }: { children: React.ReactNode; className?: string }) {\n return <div className={cn('grid grid-cols-1 gap-6 sm:grid-cols-2', className)}>{children}</div>;\n}\n\nexport function SlideSpeakerList({ children, className }: { children: React.ReactNode; className?: string }) {\n return <div className={cn('flex flex-col gap-6', className)}>{children}</div>;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/primitives.tsx"],"sourcesContent":["import hljs from 'highlight.js/lib/core';\nimport javascript from 'highlight.js/lib/languages/javascript';\nimport typescript from 'highlight.js/lib/languages/typescript';\nimport xml from 'highlight.js/lib/languages/xml';\nimport { cn } from './cn';\n\nhljs.registerLanguage('javascript', javascript);\nhljs.registerLanguage('typescript', typescript);\nhljs.registerLanguage('xml', xml);\n\nfunction highlightCode(code: string, lang?: string): string {\n if (!lang) return hljs.highlight(code, { language: 'typescript' }).value;\n const language = lang === 'ts' || lang === 'tsx' ? 'typescript' : lang;\n const registered = hljs.getLanguage(language);\n if (!registered)\n return hljs.highlight(code, { language: 'typescript' }).value;\n return hljs.highlight(code, { language }).value;\n}\nimport { SlideDemoContent } from './slide-demo-content';\nimport type { SlideAlign } from './types';\n\n/**\n * Full-viewport slide container with centered content and a decorative border.\n *\n * This is the primary slide primitive — use it as a top-level element in\n * the slides array. For a two-column layout that fills the whole viewport,\n * use {@link SlideSplitLayout} instead.\n *\n * @example\n * ```tsx\n * <Slide align=\"left\">\n * <SlideTitle>My Slide</SlideTitle>\n * <SlideSubtitle>Supporting text</SlideSubtitle>\n * </Slide>\n * ```\n */\nexport function Slide({\n children,\n align = 'center',\n className,\n}: {\n children: React.ReactNode;\n /** Content alignment. `\"center\"` centers both horizontally and text; `\"left\"` aligns to the start. */\n align?: SlideAlign;\n className?: string;\n}) {\n return (\n <div\n className={cn(\n 'nxs-slide relative flex h-dvh w-dvw flex-col justify-center gap-8 px-12 py-20 sm:px-24 md:px-32 lg:px-40',\n align === 'center' && 'items-center text-center',\n align === 'left' && 'items-start text-left',\n className\n )}\n >\n <div\n className=\"border-foreground/10 pointer-events-none absolute inset-4 border sm:inset-6\"\n aria-hidden\n />\n <div\n className={cn(\n 'relative z-10 flex max-w-4xl flex-col gap-10',\n align === 'center' && 'items-center',\n align === 'left' && 'items-start'\n )}\n >\n {children}\n </div>\n </div>\n );\n}\n\n/**\n * Inline two-column grid for use **inside** a `Slide`.\n *\n * Use this when you need a title or other content above two columns.\n * For a full-viewport two-column slide, use `SlideSplitLayout` instead.\n */\nexport function SlideColumns({\n left,\n right,\n className,\n}: {\n left: React.ReactNode;\n right: React.ReactNode;\n className?: string;\n}) {\n return (\n <div className={cn('grid w-full grid-cols-2 gap-8', className)}>\n <div className=\"flex min-w-0 flex-col gap-4\">{left}</div>\n <div className=\"flex min-w-0 flex-col gap-4\">{right}</div>\n </div>\n );\n}\n\n/**\n * Full-viewport two-column slide — a **top-level** alternative to `Slide`.\n *\n * Do **not** nest this inside `Slide`; it renders its own `h-dvh w-dvw`\n * container, border, and padding. To combine a title with two columns,\n * use `SlideColumns` inside a `Slide` instead.\n */\nexport function SlideSplitLayout({\n left,\n right,\n className,\n}: {\n left: React.ReactNode;\n right: React.ReactNode;\n className?: string;\n}) {\n return (\n <div\n className={cn(\n 'nxs-slide nxs-slide-split relative flex h-dvh w-dvw',\n className\n )}\n >\n <div\n className=\"border-foreground/10 pointer-events-none absolute inset-4 border sm:inset-6\"\n aria-hidden\n />\n <div className=\"nxs-slide-split-col relative z-10 flex w-1/2 flex-col justify-center px-12 py-20 sm:px-16 md:px-20 lg:px-24\">\n {left}\n </div>\n <div\n className=\"nxs-slide-split-divider bg-foreground/10 absolute top-4 bottom-4 left-1/2 z-10 w-px sm:top-6 sm:bottom-6\"\n aria-hidden\n />\n <div className=\"nxs-slide-split-col relative z-10 flex w-1/2 flex-col justify-center px-12 py-20 sm:px-16 md:px-20 lg:px-24\">\n {right}\n </div>\n </div>\n );\n}\n\n/**\n * Primary heading for a slide. Renders an `<h1>` with responsive sizing\n * that scales from `text-4xl` to `text-7xl` across breakpoints.\n *\n * Override the default size with `className` (e.g. `className=\"text-3xl sm:text-4xl\"`).\n */\nexport function SlideTitle({\n children,\n className,\n}: {\n children: React.ReactNode;\n className?: string;\n}) {\n return (\n <h1\n className={cn(\n 'text-foreground text-4xl font-extrabold sm:text-5xl md:text-6xl lg:text-7xl',\n className\n )}\n style={{ letterSpacing: '-0.04em' }}\n >\n {children}\n </h1>\n );\n}\n\n/**\n * Secondary text below a title. Renders a `<p>` in a muted foreground color\n * with responsive sizing (`text-lg` to `text-2xl`).\n */\nexport function SlideSubtitle({\n children,\n className,\n}: {\n children: React.ReactNode;\n className?: string;\n}) {\n return (\n <p\n className={cn(\n 'text-muted-foreground text-lg sm:text-xl md:text-2xl',\n className\n )}\n >\n {children}\n </p>\n );\n}\n\n/**\n * Small pill-shaped label, typically placed above a title to categorise\n * the slide (e.g. component name, topic tag).\n */\nexport function SlideBadge({\n children,\n className,\n}: {\n children: React.ReactNode;\n className?: string;\n}) {\n return (\n <span\n className={cn(\n 'bg-foreground text-background inline-block w-fit shrink-0 rounded-full px-4 py-1.5 text-sm font-semibold tracking-wide',\n className\n )}\n >\n {children}\n </span>\n );\n}\n\n/**\n * Italic accent text for slide headers — use for event names, series labels,\n * or other branding above the title.\n */\nexport function SlideHeaderBadge({\n children,\n className,\n}: {\n children: React.ReactNode;\n className?: string;\n}) {\n return (\n <div className={cn('flex items-center gap-3', className)}>\n <span className=\"text-foreground text-xl font-semibold tracking-tight italic sm:text-2xl\">\n {children}\n </span>\n </div>\n );\n}\n\n/**\n * Syntax-highlighted code block powered by highlight.js.\n *\n * Pass code as a **string** child. The language is auto-detected from the\n * `title` file extension (e.g. `\"example.tsx\"` → TypeScript); falls back\n * to TypeScript when unspecified. Supports JS, TS, JSX, and TSX.\n *\n * Theme colours are controlled by CSS custom properties (`--sh-*` / `--nxs-code-*`)\n * defined in `nextjs-slides/styles.css`. Override them in `:root` or `.dark`.\n *\n * @example\n * ```tsx\n * <SlideCode title=\"api.ts\">{`export async function fetchData() {\n * return fetch('/api/data');\n * }`}</SlideCode>\n * ```\n */\nexport function SlideCode({\n children,\n className,\n title,\n}: {\n /** Code string to highlight. Leading/trailing whitespace is trimmed automatically. */\n children: string;\n className?: string;\n /** File name shown above the code block. Its extension determines the highlight language. */\n title?: string;\n}) {\n const lang = title?.split('.').pop();\n const html = highlightCode(children.trim(), lang);\n\n return (\n <div className={cn('nxs-code-wrapper', className)}>\n {title && (\n <div className=\"text-muted-foreground mb-2 text-xs font-medium tracking-wider uppercase\">\n {title}\n </div>\n )}\n <pre className=\"nxs-code-block\">\n <code dangerouslySetInnerHTML={{ __html: html }} />\n </pre>\n </div>\n );\n}\n\n/**\n * Bullet-point list container. Wrap {@link SlideListItem} children inside this.\n */\nexport function SlideList({\n children,\n className,\n}: {\n children: React.ReactNode;\n className?: string;\n}) {\n return (\n <ul className={cn('flex flex-col gap-4 text-left', className)}>\n {children}\n </ul>\n );\n}\n\n/**\n * Single bullet item inside a {@link SlideList}. Renders a small dot\n * followed by the content.\n */\nexport function SlideListItem({\n children,\n className,\n}: {\n children: React.ReactNode;\n className?: string;\n}) {\n return (\n <li\n className={cn(\n 'text-foreground/70 flex items-start gap-3 text-lg sm:text-xl',\n className\n )}\n >\n <span\n className=\"bg-foreground/40 mt-2 block h-1.5 w-1.5 shrink-0 rounded-full\"\n aria-hidden\n />\n <span>{children}</span>\n </li>\n );\n}\n\n/**\n * Small footnote text in a faded colour, typically placed at the bottom\n * of a slide for annotations or caveats.\n */\nexport function SlideNote({\n children,\n className,\n}: {\n children: React.ReactNode;\n className?: string;\n}) {\n return (\n <p className={cn('text-muted-foreground/50 mt-4 text-sm', className)}>\n {children}\n </p>\n );\n}\n\n/**\n * Live interactive component embed. Keyboard navigation (arrow keys, space)\n * is disabled while focus is inside the demo area so the embedded component\n * can handle its own input.\n *\n * The container tracks its maximum height to prevent layout jumps when the\n * child re-renders with different content sizes.\n *\n * @example\n * ```tsx\n * <SlideDemo label=\"Live counter\">\n * <Counter />\n * </SlideDemo>\n * ```\n */\nexport function SlideDemo({\n children,\n className,\n label,\n}: {\n children: React.ReactNode;\n className?: string;\n /** Optional uppercase label shown above the demo area. */\n label?: string;\n}) {\n return (\n <div\n data-slide-interactive\n className={cn('min-w-0 w-full max-w-2xl', className)}\n >\n {label && (\n <div className=\"text-muted-foreground mb-2 text-xs font-medium tracking-wider uppercase\">\n {label}\n </div>\n )}\n <div className=\"border-foreground/10 bg-foreground/[0.03] min-w-0 w-full max-w-full border p-4 sm:p-6\">\n <SlideDemoContent>{children}</SlideDemoContent>\n </div>\n </div>\n );\n}\n\n/**\n * Container for {@link SlideStatement} items. Adds border separators between\n * statements automatically.\n */\nexport function SlideStatementList({\n children,\n className,\n}: {\n children: React.ReactNode;\n className?: string;\n}) {\n return (\n <div className={cn('flex min-w-0 w-full flex-col', className)}>\n {children}\n </div>\n );\n}\n\n/**\n * Title + description pair for structured content blocks.\n * Use inside a {@link SlideStatementList} for automatic border separators.\n */\nexport function SlideStatement({\n title,\n description,\n className,\n}: {\n /** Bold heading text. */\n title: string;\n /** Optional muted description below the title. */\n description?: string;\n className?: string;\n}) {\n return (\n <div\n className={cn(\n 'border-foreground/10 border-t px-8 py-8 last:border-b sm:px-12 md:px-16',\n className\n )}\n >\n <h3 className=\"text-foreground text-lg font-bold sm:text-xl md:text-2xl\">\n {title}\n </h3>\n {description && (\n <p className=\"text-muted-foreground mt-1 text-sm sm:text-base\">\n {description}\n </p>\n )}\n </div>\n );\n}\n\n/**\n * Speaker card with an avatar circle, name, and role/title.\n * When `avatar` is omitted a placeholder circle is shown.\n *\n * Use inside {@link SlideSpeakerGrid} or {@link SlideSpeakerList} to\n * lay out multiple speakers.\n */\nexport function SlideSpeaker({\n name,\n title,\n avatar,\n className,\n}: {\n name: string;\n title: string;\n /** Image URL or path for the speaker avatar. Falls back to placeholder when omitted. */\n avatar?: string;\n className?: string;\n}) {\n return (\n <div className={cn('flex items-center gap-4', className)}>\n <div\n className={cn(\n 'h-12 w-12 shrink-0 overflow-hidden rounded-full',\n avatar ? 'relative' : 'bg-foreground/15 border-foreground/20 border'\n )}\n aria-hidden\n >\n {avatar ? (\n <img src={avatar} alt=\"\" className=\"h-full w-full object-cover\" />\n ) : null}\n </div>\n <div>\n <p className=\"text-foreground/90 text-sm font-medium tracking-widest uppercase\">\n {name}\n </p>\n <p className=\"text-muted-foreground text-sm tracking-wider uppercase\">\n {title}\n </p>\n </div>\n </div>\n );\n}\n\n/**\n * Two-column responsive grid for laying out {@link SlideSpeaker} cards\n * side by side (stacks to one column on small screens).\n */\nexport function SlideSpeakerGrid({\n children,\n className,\n}: {\n children: React.ReactNode;\n className?: string;\n}) {\n return (\n <div className={cn('grid grid-cols-1 gap-6 sm:grid-cols-2', className)}>\n {children}\n </div>\n );\n}\n\n/**\n * Vertical stack layout for {@link SlideSpeaker} cards.\n */\nexport function SlideSpeakerList({\n children,\n className,\n}: {\n children: React.ReactNode;\n className?: string;\n}) {\n return <div className={cn('flex flex-col gap-6', className)}>{children}</div>;\n}\n"],"mappings":"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","names":[]}
@@ -1,6 +1,35 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { SlideDeckConfig } from './types.js';
3
3
 
4
+ /**
5
+ * Top-level slide deck provider that wraps the current slide's content.
6
+ *
7
+ * Place this in your slides layout (e.g. `app/slides/layout.tsx`). It provides:
8
+ * - **Keyboard navigation** — Arrow keys and Space to navigate slides.
9
+ * - **ViewTransition animations** — Slide-in/out with directional awareness.
10
+ * - **Progress UI** — Dots and a counter at the bottom of the viewport.
11
+ * - **Exit button** — When `exitUrl` is set, shows an × in the top-right corner.
12
+ * - **Presenter sync** — When `syncEndpoint` is set, POSTs the current slide
13
+ * on navigation for `SlideNotesView` to poll.
14
+ *
15
+ * `SlideDeck` must be the **direct child** of the layout (no wrapper div)
16
+ * for the deck-unveil exit animation to work correctly.
17
+ *
18
+ * @example
19
+ * ```tsx
20
+ * // app/slides/layout.tsx
21
+ * import { SlideDeck } from 'nextjs-slides';
22
+ * import { slides } from './slides';
23
+ *
24
+ * export default function SlidesLayout({ children }: { children: React.ReactNode }) {
25
+ * return (
26
+ * <SlideDeck slides={slides} exitUrl="/" syncEndpoint="/api/nxs-sync">
27
+ * {children}
28
+ * </SlideDeck>
29
+ * );
30
+ * }
31
+ * ```
32
+ */
4
33
  declare function SlideDeck({ children, slides, basePath, exitUrl, showProgress, showCounter, syncEndpoint, className, ...rest }: SlideDeckConfig & {
5
34
  children: React.ReactNode;
6
35
  }): react_jsx_runtime.JSX.Element;
@@ -2,7 +2,13 @@
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import Link from "next/link";
4
4
  import { usePathname, useRouter } from "next/navigation";
5
- import { addTransitionType, useCallback, useEffect, useTransition, ViewTransition } from "react";
5
+ import {
6
+ addTransitionType,
7
+ useCallback,
8
+ useEffect,
9
+ useTransition,
10
+ ViewTransition
11
+ } from "react";
6
12
  import { cn } from "./cn";
7
13
  function SlideDeck({
8
14
  children,
@@ -141,10 +147,24 @@ function SlideDeck({
141
147
  href: exitUrl,
142
148
  className: "text-foreground/50 hover:text-foreground fixed top-6 right-8 z-50 flex h-10 w-10 items-center justify-center rounded-md transition-colors hover:bg-foreground/10",
143
149
  "aria-label": "Exit presentation",
144
- children: /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
145
- /* @__PURE__ */ jsx("path", { d: "M18 6 6 18" }),
146
- /* @__PURE__ */ jsx("path", { d: "m6 6 12 12" })
147
- ] })
150
+ children: /* @__PURE__ */ jsxs(
151
+ "svg",
152
+ {
153
+ xmlns: "http://www.w3.org/2000/svg",
154
+ width: "20",
155
+ height: "20",
156
+ viewBox: "0 0 24 24",
157
+ fill: "none",
158
+ stroke: "currentColor",
159
+ strokeWidth: "2",
160
+ strokeLinecap: "round",
161
+ strokeLinejoin: "round",
162
+ children: [
163
+ /* @__PURE__ */ jsx("path", { d: "M18 6 6 18" }),
164
+ /* @__PURE__ */ jsx("path", { d: "m6 6 12 12" })
165
+ ]
166
+ }
167
+ )
148
168
  }
149
169
  )
150
170
  ]
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/slide-deck.tsx"],"sourcesContent":["'use client';\n\nimport Link from 'next/link';\nimport { usePathname, useRouter } from 'next/navigation';\nimport { addTransitionType, useCallback, useEffect, useTransition, ViewTransition } from 'react';\nimport { cn } from './cn';\nimport type { SlideDeckConfig } from './types';\n\nexport function SlideDeck({\n children,\n slides,\n basePath = '/slides',\n exitUrl,\n showProgress = true,\n showCounter = true,\n syncEndpoint,\n className,\n ...rest\n}: SlideDeckConfig & { children: React.ReactNode }) {\n void rest; // accepts speakerNotes and other optional config\n const router = useRouter();\n const pathname = usePathname();\n const [isPending, startTransition] = useTransition();\n\n const total = slides.length;\n const slideRoutePattern = new RegExp(`^${basePath}/(\\\\d+)$`);\n const isSlideRoute = slideRoutePattern.test(pathname);\n const current = (() => {\n const match = pathname.match(slideRoutePattern);\n return match ? Number(match[1]) - 1 : 0;\n })();\n\n const goTo = useCallback(\n (index: number) => {\n const clamped = Math.max(0, Math.min(index, total - 1));\n if (clamped === current) return;\n const targetSlide = clamped + 1; // 1-based for sync API\n if (syncEndpoint) {\n fetch(syncEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ slide: targetSlide, total }),\n }).catch(() => {});\n }\n startTransition(() => {\n addTransitionType(clamped > current ? 'slide-forward' : 'slide-back');\n router.push(`${basePath}/${targetSlide}`);\n });\n },\n [basePath, current, router, startTransition, syncEndpoint, total],\n );\n\n useEffect(() => {\n if (!isSlideRoute) return;\n if (current > 0) router.prefetch(`${basePath}/${current}`);\n if (current < total - 1) router.prefetch(`${basePath}/${current + 2}`);\n }, [basePath, current, isSlideRoute, router, total]);\n\n useEffect(() => {\n if (!isSlideRoute) return;\n function onKeyDown(e: KeyboardEvent) {\n const target = e.target as HTMLElement;\n if (\n target.closest('[data-slide-interactive]') ||\n target.matches('input, textarea, select, [contenteditable=\"true\"]')\n ) {\n return;\n }\n if (e.key === 'ArrowRight' || e.key === ' ') {\n e.preventDefault();\n goTo(current + 1);\n } else if (e.key === 'ArrowLeft') {\n e.preventDefault();\n goTo(current - 1);\n }\n }\n window.addEventListener('keydown', onKeyDown);\n return () => window.removeEventListener('keydown', onKeyDown);\n }, [current, goTo, isSlideRoute]);\n\n useEffect(() => {\n const prev = document.body.style.overflow;\n document.body.style.overflow = 'hidden';\n return () => {\n document.body.style.overflow = prev;\n };\n }, []);\n\n useEffect(() => {\n if (!syncEndpoint || !isSlideRoute || isPending) return;\n fetch(syncEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ slide: current + 1, total }),\n }).catch(() => {});\n }, [syncEndpoint, current, total, isSlideRoute, isPending]);\n\n return (\n <ViewTransition default=\"none\" exit=\"deck-unveil\">\n <div\n id=\"slide-deck\"\n className={cn(\n 'bg-background text-foreground fixed inset-0 z-50 flex flex-col overflow-hidden font-sans select-none',\n className,\n )}\n data-pending={isPending ? '' : undefined}\n >\n <div className=\"flex-1 overflow-hidden\">\n <ViewTransition\n key={pathname}\n default=\"none\"\n enter={{\n default: 'slide-from-right',\n 'slide-back': 'slide-from-left',\n 'slide-forward': 'slide-from-right',\n }}\n exit={{\n default: 'slide-to-left',\n 'slide-back': 'slide-to-right',\n 'slide-forward': 'slide-to-left',\n }}\n >\n <div>{children}</div>\n </ViewTransition>\n </div>\n\n {isSlideRoute && showProgress && (\n <div\n className=\"fixed bottom-8 left-1/2 z-50 flex -translate-x-1/2 items-center gap-1.5\"\n aria-label=\"Slide progress\"\n >\n {Array.from({ length: total }).map((_, i) => (\n <div\n key={i}\n className={cn(\n 'h-1 transition-all duration-300',\n i === current ? 'bg-foreground w-6' : 'bg-foreground/20 w-1',\n )}\n />\n ))}\n </div>\n )}\n\n {isSlideRoute && showCounter && (\n <div className=\"text-foreground/30 fixed right-8 bottom-8 z-50 font-mono text-xs tracking-wider\">\n {current + 1} / {total}\n </div>\n )}\n\n {isSlideRoute && exitUrl && (\n <Link\n href={exitUrl}\n className=\"text-foreground/50 hover:text-foreground fixed top-6 right-8 z-50 flex h-10 w-10 items-center justify-center rounded-md transition-colors hover:bg-foreground/10\"\n aria-label=\"Exit presentation\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </Link>\n )}\n </div>\n </ViewTransition>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/slide-deck.tsx"],"sourcesContent":["'use client';\n\nimport Link from 'next/link';\nimport { usePathname, useRouter } from 'next/navigation';\nimport {\n addTransitionType,\n useCallback,\n useEffect,\n useTransition,\n ViewTransition,\n} from 'react';\nimport { cn } from './cn';\nimport type { SlideDeckConfig } from './types';\n\n/**\n * Top-level slide deck provider that wraps the current slide's content.\n *\n * Place this in your slides layout (e.g. `app/slides/layout.tsx`). It provides:\n * - **Keyboard navigation** — Arrow keys and Space to navigate slides.\n * - **ViewTransition animations** — Slide-in/out with directional awareness.\n * - **Progress UI** — Dots and a counter at the bottom of the viewport.\n * - **Exit button** — When `exitUrl` is set, shows an × in the top-right corner.\n * - **Presenter sync** — When `syncEndpoint` is set, POSTs the current slide\n * on navigation for `SlideNotesView` to poll.\n *\n * `SlideDeck` must be the **direct child** of the layout (no wrapper div)\n * for the deck-unveil exit animation to work correctly.\n *\n * @example\n * ```tsx\n * // app/slides/layout.tsx\n * import { SlideDeck } from 'nextjs-slides';\n * import { slides } from './slides';\n *\n * export default function SlidesLayout({ children }: { children: React.ReactNode }) {\n * return (\n * <SlideDeck slides={slides} exitUrl=\"/\" syncEndpoint=\"/api/nxs-sync\">\n * {children}\n * </SlideDeck>\n * );\n * }\n * ```\n */\nexport function SlideDeck({\n children,\n slides,\n basePath = '/slides',\n exitUrl,\n showProgress = true,\n showCounter = true,\n syncEndpoint,\n className,\n ...rest\n}: SlideDeckConfig & { children: React.ReactNode }) {\n void rest; // accepts speakerNotes and other optional config\n const router = useRouter();\n const pathname = usePathname();\n const [isPending, startTransition] = useTransition();\n\n const total = slides.length;\n const slideRoutePattern = new RegExp(`^${basePath}/(\\\\d+)$`);\n const isSlideRoute = slideRoutePattern.test(pathname);\n const current = (() => {\n const match = pathname.match(slideRoutePattern);\n return match ? Number(match[1]) - 1 : 0;\n })();\n\n const goTo = useCallback(\n (index: number) => {\n const clamped = Math.max(0, Math.min(index, total - 1));\n if (clamped === current) return;\n const targetSlide = clamped + 1; // 1-based for sync API\n if (syncEndpoint) {\n fetch(syncEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ slide: targetSlide, total }),\n }).catch(() => {});\n }\n startTransition(() => {\n addTransitionType(clamped > current ? 'slide-forward' : 'slide-back');\n router.push(`${basePath}/${targetSlide}`);\n });\n },\n [basePath, current, router, startTransition, syncEndpoint, total]\n );\n\n useEffect(() => {\n if (!isSlideRoute) return;\n if (current > 0) router.prefetch(`${basePath}/${current}`);\n if (current < total - 1) router.prefetch(`${basePath}/${current + 2}`);\n }, [basePath, current, isSlideRoute, router, total]);\n\n useEffect(() => {\n if (!isSlideRoute) return;\n function onKeyDown(e: KeyboardEvent) {\n const target = e.target as HTMLElement;\n if (\n target.closest('[data-slide-interactive]') ||\n target.matches('input, textarea, select, [contenteditable=\"true\"]')\n ) {\n return;\n }\n if (e.key === 'ArrowRight' || e.key === ' ') {\n e.preventDefault();\n goTo(current + 1);\n } else if (e.key === 'ArrowLeft') {\n e.preventDefault();\n goTo(current - 1);\n }\n }\n window.addEventListener('keydown', onKeyDown);\n return () => window.removeEventListener('keydown', onKeyDown);\n }, [current, goTo, isSlideRoute]);\n\n useEffect(() => {\n const prev = document.body.style.overflow;\n document.body.style.overflow = 'hidden';\n return () => {\n document.body.style.overflow = prev;\n };\n }, []);\n\n useEffect(() => {\n if (!syncEndpoint || !isSlideRoute || isPending) return;\n fetch(syncEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ slide: current + 1, total }),\n }).catch(() => {});\n }, [syncEndpoint, current, total, isSlideRoute, isPending]);\n\n return (\n <ViewTransition default=\"none\" exit=\"deck-unveil\">\n <div\n id=\"slide-deck\"\n className={cn(\n 'bg-background text-foreground fixed inset-0 z-50 flex flex-col overflow-hidden font-sans select-none',\n className\n )}\n data-pending={isPending ? '' : undefined}\n >\n <div className=\"flex-1 overflow-hidden\">\n <ViewTransition\n key={pathname}\n default=\"none\"\n enter={{\n default: 'slide-from-right',\n 'slide-back': 'slide-from-left',\n 'slide-forward': 'slide-from-right',\n }}\n exit={{\n default: 'slide-to-left',\n 'slide-back': 'slide-to-right',\n 'slide-forward': 'slide-to-left',\n }}\n >\n <div>{children}</div>\n </ViewTransition>\n </div>\n\n {isSlideRoute && showProgress && (\n <div\n className=\"fixed bottom-8 left-1/2 z-50 flex -translate-x-1/2 items-center gap-1.5\"\n aria-label=\"Slide progress\"\n >\n {Array.from({ length: total }).map((_, i) => (\n <div\n key={i}\n className={cn(\n 'h-1 transition-all duration-300',\n i === current ? 'bg-foreground w-6' : 'bg-foreground/20 w-1'\n )}\n />\n ))}\n </div>\n )}\n\n {isSlideRoute && showCounter && (\n <div className=\"text-foreground/30 fixed right-8 bottom-8 z-50 font-mono text-xs tracking-wider\">\n {current + 1} / {total}\n </div>\n )}\n\n {isSlideRoute && exitUrl && (\n <Link\n href={exitUrl}\n className=\"text-foreground/50 hover:text-foreground fixed top-6 right-8 z-50 flex h-10 w-10 items-center justify-center rounded-md transition-colors hover:bg-foreground/10\"\n aria-label=\"Exit presentation\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </Link>\n )}\n </div>\n </ViewTransition>\n );\n}\n"],"mappings":"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","names":[]}
@@ -1,5 +1,12 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
 
3
+ /**
4
+ * Client wrapper for {@link SlideDemo} content that tracks the maximum
5
+ * rendered height via `ResizeObserver`, preventing layout jumps when
6
+ * the child re-renders with different content sizes.
7
+ *
8
+ * @internal Used by `SlideDemo` — not exported from the public API.
9
+ */
3
10
  declare function SlideDemoContent({ children }: {
4
11
  children: React.ReactNode;
5
12
  }): react_jsx_runtime.JSX.Element;
@@ -10,7 +10,9 @@ function SlideDemoContent({ children }) {
10
10
  const observer = new ResizeObserver((entries) => {
11
11
  for (const entry of entries) {
12
12
  const height = entry.borderBoxSize[0].blockSize;
13
- setMinHeight((prev) => prev === void 0 ? height : Math.max(prev, height));
13
+ setMinHeight(
14
+ (prev) => prev === void 0 ? height : Math.max(prev, height)
15
+ );
14
16
  }
15
17
  });
16
18
  observer.observe(el);
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/slide-demo-content.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, useRef, useState } from 'react';\n\nexport function SlideDemoContent({ children }: { children: React.ReactNode }) {\n const ref = useRef<HTMLDivElement>(null);\n const [minHeight, setMinHeight] = useState<number | undefined>(undefined);\n\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n\n const observer = new ResizeObserver(entries => {\n for (const entry of entries) {\n const height = entry.borderBoxSize[0].blockSize;\n setMinHeight(prev => (prev === undefined ? height : Math.max(prev, height)));\n }\n });\n observer.observe(el);\n return () => observer.disconnect();\n }, []);\n\n return (\n <div ref={ref} style={minHeight !== undefined ? { minHeight } : undefined}>\n {children}\n </div>\n );\n}\n"],"mappings":";AAuBI;AArBJ,SAAS,WAAW,QAAQ,gBAAgB;AAErC,SAAS,iBAAiB,EAAE,SAAS,GAAkC;AAC5E,QAAM,MAAM,OAAuB,IAAI;AACvC,QAAM,CAAC,WAAW,YAAY,IAAI,SAA6B,MAAS;AAExE,YAAU,MAAM;AACd,UAAM,KAAK,IAAI;AACf,QAAI,CAAC,GAAI;AAET,UAAM,WAAW,IAAI,eAAe,aAAW;AAC7C,iBAAW,SAAS,SAAS;AAC3B,cAAM,SAAS,MAAM,cAAc,CAAC,EAAE;AACtC,qBAAa,UAAS,SAAS,SAAY,SAAS,KAAK,IAAI,MAAM,MAAM,CAAE;AAAA,MAC7E;AAAA,IACF,CAAC;AACD,aAAS,QAAQ,EAAE;AACnB,WAAO,MAAM,SAAS,WAAW;AAAA,EACnC,GAAG,CAAC,CAAC;AAEL,SACE,oBAAC,SAAI,KAAU,OAAO,cAAc,SAAY,EAAE,UAAU,IAAI,QAC7D,UACH;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../src/slide-demo-content.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, useRef, useState } from 'react';\n\n/**\n * Client wrapper for {@link SlideDemo} content that tracks the maximum\n * rendered height via `ResizeObserver`, preventing layout jumps when\n * the child re-renders with different content sizes.\n *\n * @internal Used by `SlideDemo` — not exported from the public API.\n */\nexport function SlideDemoContent({ children }: { children: React.ReactNode }) {\n const ref = useRef<HTMLDivElement>(null);\n const [minHeight, setMinHeight] = useState<number | undefined>(undefined);\n\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n\n const observer = new ResizeObserver((entries) => {\n for (const entry of entries) {\n const height = entry.borderBoxSize[0].blockSize;\n setMinHeight((prev) =>\n prev === undefined ? height : Math.max(prev, height)\n );\n }\n });\n observer.observe(el);\n return () => observer.disconnect();\n }, []);\n\n return (\n <div ref={ref} style={minHeight !== undefined ? { minHeight } : undefined}>\n {children}\n </div>\n );\n}\n"],"mappings":";AAgCI;AA9BJ,SAAS,WAAW,QAAQ,gBAAgB;AASrC,SAAS,iBAAiB,EAAE,SAAS,GAAkC;AAC5E,QAAM,MAAM,OAAuB,IAAI;AACvC,QAAM,CAAC,WAAW,YAAY,IAAI,SAA6B,MAAS;AAExE,YAAU,MAAM;AACd,UAAM,KAAK,IAAI;AACf,QAAI,CAAC,GAAI;AAET,UAAM,WAAW,IAAI,eAAe,CAAC,YAAY;AAC/C,iBAAW,SAAS,SAAS;AAC3B,cAAM,SAAS,MAAM,cAAc,CAAC,EAAE;AACtC;AAAA,UAAa,CAAC,SACZ,SAAS,SAAY,SAAS,KAAK,IAAI,MAAM,MAAM;AAAA,QACrD;AAAA,MACF;AAAA,IACF,CAAC;AACD,aAAS,QAAQ,EAAE;AACnB,WAAO,MAAM,SAAS,WAAW;AAAA,EACnC,GAAG,CAAC,CAAC;AAEL,SACE,oBAAC,SAAI,KAAU,OAAO,cAAc,SAAY,EAAE,UAAU,IAAI,QAC7D,UACH;AAEJ;","names":[]}
@@ -1,10 +1,21 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { SlideLinkVariant } from './types.js';
3
3
 
4
+ /**
5
+ * Styled Next.js `<Link>` for navigating between slides, to breakout pages,
6
+ * or to external URLs.
7
+ *
8
+ * @example
9
+ * ```tsx
10
+ * <SlideLink href="/slides/demo1">Breakout page →</SlideLink>
11
+ * <SlideLink href="/" variant="ghost">Exit deck</SlideLink>
12
+ * ```
13
+ */
4
14
  declare function SlideLink({ href, children, className, variant, }: {
5
15
  href: string;
6
16
  children: React.ReactNode;
7
17
  className?: string;
18
+ /** Visual style. `"primary"` is a solid button; `"ghost"` is a bordered transparent button. */
8
19
  variant?: SlideLinkVariant;
9
20
  }): react_jsx_runtime.JSX.Element;
10
21
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/slide-link.tsx"],"sourcesContent":["import Link from 'next/link';\nimport { cn } from './cn';\nimport type { SlideLinkVariant } from './types';\n\nexport function SlideLink({\n href,\n children,\n className,\n variant = 'primary',\n}: {\n href: string;\n children: React.ReactNode;\n className?: string;\n variant?: SlideLinkVariant;\n}) {\n return (\n <Link\n href={href}\n className={cn(\n 'nxs-slide-link inline-flex items-center justify-center gap-2 rounded-md px-4 py-2 text-sm font-medium tracking-wide transition-all',\n variant === 'primary' && 'bg-primary text-primary-foreground hover:bg-primary/80',\n variant === 'ghost' && 'border-border border bg-transparent hover:bg-muted',\n 'mt-2',\n className,\n )}\n >\n {children}\n </Link>\n );\n}\n"],"mappings":"AAgBI;AAhBJ,OAAO,UAAU;AACjB,SAAS,UAAU;AAGZ,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AACZ,GAKG;AACD,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,WAAW;AAAA,QACT;AAAA,QACA,YAAY,aAAa;AAAA,QACzB,YAAY,WAAW;AAAA,QACvB;AAAA,QACA;AAAA,MACF;AAAA,MAEC;AAAA;AAAA,EACH;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../src/slide-link.tsx"],"sourcesContent":["import Link from 'next/link';\nimport { cn } from './cn';\nimport type { SlideLinkVariant } from './types';\n\n/**\n * Styled Next.js `<Link>` for navigating between slides, to breakout pages,\n * or to external URLs.\n *\n * @example\n * ```tsx\n * <SlideLink href=\"/slides/demo1\">Breakout page →</SlideLink>\n * <SlideLink href=\"/\" variant=\"ghost\">Exit deck</SlideLink>\n * ```\n */\nexport function SlideLink({\n href,\n children,\n className,\n variant = 'primary',\n}: {\n href: string;\n children: React.ReactNode;\n className?: string;\n /** Visual style. `\"primary\"` is a solid button; `\"ghost\"` is a bordered transparent button. */\n variant?: SlideLinkVariant;\n}) {\n return (\n <Link\n href={href}\n className={cn(\n 'nxs-slide-link inline-flex items-center justify-center gap-2 rounded-md px-4 py-2 text-sm font-medium tracking-wide transition-all',\n variant === 'primary' &&\n 'bg-primary text-primary-foreground hover:bg-primary/80',\n variant === 'ghost' &&\n 'border-border border bg-transparent hover:bg-muted',\n 'mt-2',\n className\n )}\n >\n {children}\n </Link>\n );\n}\n"],"mappings":"AA2BI;AA3BJ,OAAO,UAAU;AACjB,SAAS,UAAU;AAaZ,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AACZ,GAMG;AACD,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,WAAW;AAAA,QACT;AAAA,QACA,YAAY,aACV;AAAA,QACF,YAAY,WACV;AAAA,QACF;AAAA,QACA;AAAA,MACF;AAAA,MAEC;AAAA;AAAA,EACH;AAEJ;","names":[]}
@@ -111,7 +111,13 @@ function SlideNotesView({
111
111
  },
112
112
  children: currentNote
113
113
  }
114
- ) : /* @__PURE__ */ jsx("p", { style: { fontSize: "18px", color: "#525252", fontStyle: "italic" }, children: "No notes for this slide." })
114
+ ) : /* @__PURE__ */ jsx(
115
+ "p",
116
+ {
117
+ style: { fontSize: "18px", color: "#525252", fontStyle: "italic" },
118
+ children: "No notes for this slide."
119
+ }
120
+ )
115
121
  }
116
122
  ),
117
123
  /* @__PURE__ */ jsxs(
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/slide-notes-view.tsx"],"sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\n\n/**\n * Phone-friendly speaker notes view that stays in sync with the presenter.\n *\n * Polls the sync endpoint to track the current slide and displays the\n * corresponding note. Notes beyond the slide count are treated as \"demo notes\"\n * — advance through them manually using the on-screen buttons.\n *\n * @example\n * ```tsx\n * // app/notes/page.tsx\n * import fs from 'fs';\n * import path from 'path';\n * import { parseSpeakerNotes, SlideNotesView } from 'nextjs-slides';\n *\n * const notes = parseSpeakerNotes(\n * fs.readFileSync(path.join(process.cwd(), 'app/slides/notes.md'), 'utf-8'),\n * );\n *\n * export default function NotesPage() {\n * return <SlideNotesView notes={notes} syncEndpoint=\"/api/nxs-sync\" />;\n * }\n * ```\n */\nexport function SlideNotesView({\n notes,\n syncEndpoint,\n pollInterval = 500,\n}: {\n /** Speaker notes array. Indices 0…slides-1 match slides; extras are demo notes. */\n notes: (string | null)[];\n /** API endpoint created with the sync route handlers. */\n syncEndpoint: string;\n /** Polling interval in ms. Defaults to 500. */\n pollInterval?: number;\n}) {\n const [noteIndex, setNoteIndex] = useState(0);\n const [totalSlides, setTotalSlides] = useState(1);\n const [connected, setConnected] = useState(false);\n const manualOverride = useRef(false);\n\n const poll = useCallback(async () => {\n try {\n const res = await fetch(syncEndpoint, { cache: 'no-store' });\n if (!res.ok) return;\n const data = await res.json();\n const syncIndex = (data.slide as number) - 1;\n setTotalSlides(data.total);\n setConnected(true);\n\n setNoteIndex(prev => {\n if (manualOverride.current && prev >= data.total) return prev;\n manualOverride.current = false;\n return syncIndex;\n });\n } catch {\n setConnected(false);\n }\n }, [syncEndpoint]);\n\n useEffect(() => {\n poll();\n const id = setInterval(poll, pollInterval);\n return () => clearInterval(id);\n }, [poll, pollInterval]);\n\n const goNext = useCallback(() => {\n setNoteIndex(prev => {\n if (prev >= notes.length - 1) return prev;\n manualOverride.current = true;\n return prev + 1;\n });\n }, [notes.length]);\n\n const goPrev = useCallback(() => {\n setNoteIndex(prev => {\n if (prev <= 0) return prev;\n manualOverride.current = true;\n return prev - 1;\n });\n }, []);\n\n const currentNote = noteIndex >= 0 && noteIndex < notes.length ? notes[noteIndex] : null;\n const inDemoNotes = noteIndex >= totalSlides;\n const displayNumber = noteIndex + 1;\n\n const label = inDemoNotes\n ? `Demo ${displayNumber - totalSlides} / ${notes.length - totalSlides}`\n : `Slide ${displayNumber} / ${totalSlides}`;\n\n return (\n <div\n style={{\n minHeight: '100dvh',\n display: 'flex',\n flexDirection: 'column',\n backgroundColor: '#0a0a0a',\n color: '#e5e5e5',\n fontFamily: 'system-ui, -apple-system, sans-serif',\n }}\n >\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n padding: '12px 20px',\n borderBottom: '1px solid #262626',\n fontSize: '13px',\n color: '#737373',\n }}\n >\n <span>{label}</span>\n <span\n style={{\n width: 8,\n height: 8,\n borderRadius: '50%',\n backgroundColor: connected ? '#22c55e' : '#ef4444',\n }}\n />\n </div>\n\n <div\n style={{\n flex: 1,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n padding: '24px 20px',\n }}\n >\n {currentNote ? (\n <p\n style={{\n fontSize: 'clamp(18px, 4vw, 28px)',\n lineHeight: 1.6,\n maxWidth: '640px',\n whiteSpace: 'pre-wrap',\n }}\n >\n {currentNote}\n </p>\n ) : (\n <p style={{ fontSize: '18px', color: '#525252', fontStyle: 'italic' }}>\n No notes for this slide.\n </p>\n )}\n </div>\n\n <div\n style={{\n display: 'flex',\n gap: '12px',\n padding: '16px 20px',\n borderTop: '1px solid #262626',\n }}\n >\n <button\n onClick={goPrev}\n disabled={noteIndex <= 0}\n style={{\n flex: 1,\n padding: '14px',\n fontSize: '16px',\n fontWeight: 500,\n border: '1px solid #333',\n borderRadius: '10px',\n backgroundColor: noteIndex <= 0 ? '#111' : '#1a1a1a',\n color: noteIndex <= 0 ? '#444' : '#e5e5e5',\n cursor: noteIndex <= 0 ? 'default' : 'pointer',\n WebkitTapHighlightColor: 'transparent',\n }}\n >\n ← Prev\n </button>\n <button\n onClick={goNext}\n disabled={noteIndex >= notes.length - 1}\n style={{\n flex: 1,\n padding: '14px',\n fontSize: '16px',\n fontWeight: 500,\n border: '1px solid #333',\n borderRadius: '10px',\n backgroundColor: noteIndex >= notes.length - 1 ? '#111' : '#1a1a1a',\n color: noteIndex >= notes.length - 1 ? '#444' : '#e5e5e5',\n cursor: noteIndex >= notes.length - 1 ? 'default' : 'pointer',\n WebkitTapHighlightColor: 'transparent',\n }}\n >\n Next →\n </button>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/slide-notes-view.tsx"],"sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\n\n/**\n * Phone-friendly speaker notes view that stays in sync with the presenter.\n *\n * Polls the sync endpoint to track the current slide and displays the\n * corresponding note. Notes beyond the slide count are treated as \"demo notes\"\n * — advance through them manually using the on-screen buttons.\n *\n * @example\n * ```tsx\n * // app/notes/page.tsx\n * import fs from 'fs';\n * import path from 'path';\n * import { parseSpeakerNotes, SlideNotesView } from 'nextjs-slides';\n *\n * const notes = parseSpeakerNotes(\n * fs.readFileSync(path.join(process.cwd(), 'app/slides/notes.md'), 'utf-8'),\n * );\n *\n * export default function NotesPage() {\n * return <SlideNotesView notes={notes} syncEndpoint=\"/api/nxs-sync\" />;\n * }\n * ```\n */\nexport function SlideNotesView({\n notes,\n syncEndpoint,\n pollInterval = 500,\n}: {\n /** Speaker notes array. Indices 0…slides-1 match slides; extras are demo notes. */\n notes: (string | null)[];\n /** API endpoint created with the sync route handlers. */\n syncEndpoint: string;\n /** Polling interval in ms. Defaults to 500. */\n pollInterval?: number;\n}) {\n const [noteIndex, setNoteIndex] = useState(0);\n const [totalSlides, setTotalSlides] = useState(1);\n const [connected, setConnected] = useState(false);\n const manualOverride = useRef(false);\n\n const poll = useCallback(async () => {\n try {\n const res = await fetch(syncEndpoint, { cache: 'no-store' });\n if (!res.ok) return;\n const data = await res.json();\n const syncIndex = (data.slide as number) - 1;\n setTotalSlides(data.total);\n setConnected(true);\n\n setNoteIndex((prev) => {\n if (manualOverride.current && prev >= data.total) return prev;\n manualOverride.current = false;\n return syncIndex;\n });\n } catch {\n setConnected(false);\n }\n }, [syncEndpoint]);\n\n useEffect(() => {\n poll();\n const id = setInterval(poll, pollInterval);\n return () => clearInterval(id);\n }, [poll, pollInterval]);\n\n const goNext = useCallback(() => {\n setNoteIndex((prev) => {\n if (prev >= notes.length - 1) return prev;\n manualOverride.current = true;\n return prev + 1;\n });\n }, [notes.length]);\n\n const goPrev = useCallback(() => {\n setNoteIndex((prev) => {\n if (prev <= 0) return prev;\n manualOverride.current = true;\n return prev - 1;\n });\n }, []);\n\n const currentNote =\n noteIndex >= 0 && noteIndex < notes.length ? notes[noteIndex] : null;\n const inDemoNotes = noteIndex >= totalSlides;\n const displayNumber = noteIndex + 1;\n\n const label = inDemoNotes\n ? `Demo ${displayNumber - totalSlides} / ${notes.length - totalSlides}`\n : `Slide ${displayNumber} / ${totalSlides}`;\n\n return (\n <div\n style={{\n minHeight: '100dvh',\n display: 'flex',\n flexDirection: 'column',\n backgroundColor: '#0a0a0a',\n color: '#e5e5e5',\n fontFamily: 'system-ui, -apple-system, sans-serif',\n }}\n >\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n padding: '12px 20px',\n borderBottom: '1px solid #262626',\n fontSize: '13px',\n color: '#737373',\n }}\n >\n <span>{label}</span>\n <span\n style={{\n width: 8,\n height: 8,\n borderRadius: '50%',\n backgroundColor: connected ? '#22c55e' : '#ef4444',\n }}\n />\n </div>\n\n <div\n style={{\n flex: 1,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n padding: '24px 20px',\n }}\n >\n {currentNote ? (\n <p\n style={{\n fontSize: 'clamp(18px, 4vw, 28px)',\n lineHeight: 1.6,\n maxWidth: '640px',\n whiteSpace: 'pre-wrap',\n }}\n >\n {currentNote}\n </p>\n ) : (\n <p\n style={{ fontSize: '18px', color: '#525252', fontStyle: 'italic' }}\n >\n No notes for this slide.\n </p>\n )}\n </div>\n\n <div\n style={{\n display: 'flex',\n gap: '12px',\n padding: '16px 20px',\n borderTop: '1px solid #262626',\n }}\n >\n <button\n onClick={goPrev}\n disabled={noteIndex <= 0}\n style={{\n flex: 1,\n padding: '14px',\n fontSize: '16px',\n fontWeight: 500,\n border: '1px solid #333',\n borderRadius: '10px',\n backgroundColor: noteIndex <= 0 ? '#111' : '#1a1a1a',\n color: noteIndex <= 0 ? '#444' : '#e5e5e5',\n cursor: noteIndex <= 0 ? 'default' : 'pointer',\n WebkitTapHighlightColor: 'transparent',\n }}\n >\n ← Prev\n </button>\n <button\n onClick={goNext}\n disabled={noteIndex >= notes.length - 1}\n style={{\n flex: 1,\n padding: '14px',\n fontSize: '16px',\n fontWeight: 500,\n border: '1px solid #333',\n borderRadius: '10px',\n backgroundColor: noteIndex >= notes.length - 1 ? '#111' : '#1a1a1a',\n color: noteIndex >= notes.length - 1 ? '#444' : '#e5e5e5',\n cursor: noteIndex >= notes.length - 1 ? 'default' : 'pointer',\n WebkitTapHighlightColor: 'transparent',\n }}\n >\n Next →\n </button>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
package/dist/slides.css CHANGED
@@ -1,5 +1,8 @@
1
1
  /* nextjs-slides — Slide transition animations and code theme tokens */
2
2
 
3
+ /* Ensure Tailwind v4 scans this package's component files for utility classes */
4
+ @source "./*.js";
5
+
3
6
  /* Default code theme — override --sh-* and --nxs-code-* in :root or .dark */
4
7
  :root {
5
8
  --nxs-code-bg: #ffffff;
@@ -40,30 +43,9 @@
40
43
 
41
44
  .nxs-slide-split-col {
42
45
  min-width: 0;
43
- width: 50%;
44
46
  overflow-x: auto;
45
47
  }
46
48
 
47
- /* Split layout: stack columns below 1024px — use grid to bypass flex overrides */
48
- @media (max-width: 1023px) {
49
- #slide-deck .nxs-slide-split {
50
- display: grid !important;
51
- grid-template-columns: 1fr !important;
52
- overflow-y: auto;
53
- overflow-x: hidden;
54
- }
55
-
56
- #slide-deck .nxs-slide-split .nxs-slide-split-col {
57
- width: 100% !important;
58
- max-width: 100% !important;
59
- min-width: 0 !important;
60
- }
61
-
62
- #slide-deck .nxs-slide-split .nxs-slide-split-divider {
63
- display: none !important;
64
- }
65
- }
66
-
67
49
  /* Code block wrapper — constrains width so code doesn't blow out the slide */
68
50
  .nxs-code-wrapper {
69
51
  min-width: 0;
@@ -76,7 +58,9 @@
76
58
  background: var(--nxs-code-bg);
77
59
  border: 1px solid var(--nxs-code-border);
78
60
  color: var(--nxs-code-text);
79
- font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
61
+ font-family:
62
+ ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono',
63
+ 'Courier New', monospace;
80
64
  font-size: clamp(0.75rem, 1.5vw + 0.5rem, 0.875rem);
81
65
  line-height: 1.7;
82
66
  text-align: left;
@@ -93,23 +77,44 @@
93
77
  }
94
78
  }
95
79
  .nxs-code-block .hljs-keyword,
96
- .nxs-code-block .hljs-literal { color: var(--sh-keyword); }
97
- .nxs-code-block .hljs-string { color: var(--sh-string); }
98
- .nxs-code-block .hljs-number { color: var(--sh-literal); }
99
- .nxs-code-block .hljs-comment { color: var(--sh-comment); }
100
- .nxs-code-block .hljs-title.class_ { color: var(--sh-class); }
101
- .nxs-code-block .hljs-title.function_ { color: var(--sh-entity); }
80
+ .nxs-code-block .hljs-literal {
81
+ color: var(--sh-keyword);
82
+ }
83
+ .nxs-code-block .hljs-string {
84
+ color: var(--sh-string);
85
+ }
86
+ .nxs-code-block .hljs-number {
87
+ color: var(--sh-literal);
88
+ }
89
+ .nxs-code-block .hljs-comment {
90
+ color: var(--sh-comment);
91
+ }
92
+ .nxs-code-block .hljs-title.class_ {
93
+ color: var(--sh-class);
94
+ }
95
+ .nxs-code-block .hljs-title.function_ {
96
+ color: var(--sh-entity);
97
+ }
102
98
  .nxs-code-block .hljs-attr,
103
99
  .nxs-code-block .hljs-attribute,
104
- .nxs-code-block .hljs-params { color: var(--sh-property); }
100
+ .nxs-code-block .hljs-params {
101
+ color: var(--sh-property);
102
+ }
105
103
  .nxs-code-block .hljs-built_in,
106
- .nxs-code-block .hljs-variable { color: var(--sh-identifier); }
104
+ .nxs-code-block .hljs-variable {
105
+ color: var(--sh-identifier);
106
+ }
107
107
  .nxs-code-block .hljs-name,
108
- .nxs-code-block .hljs-tag .hljs-name { color: var(--sh-tag); }
109
- .nxs-code-block .hljs-tag { color: var(--sh-sign); }
108
+ .nxs-code-block .hljs-tag .hljs-name {
109
+ color: var(--sh-tag);
110
+ }
111
+ .nxs-code-block .hljs-tag {
112
+ color: var(--sh-sign);
113
+ }
110
114
  .nxs-code-block .hljs-punctuation,
111
- .nxs-code-block .hljs-operator { color: var(--sh-sign); }
112
-
115
+ .nxs-code-block .hljs-operator {
116
+ color: var(--sh-sign);
117
+ }
113
118
 
114
119
  /* Slide transition keyframes */
115
120
  @keyframes nxs-fade {
package/dist/types.d.ts CHANGED
@@ -1,5 +1,8 @@
1
+ /** Content alignment for {@link Slide}. */
1
2
  type SlideAlign = 'center' | 'left';
3
+ /** Visual style for {@link SlideLink}. */
2
4
  type SlideLinkVariant = 'primary' | 'ghost';
5
+ /** Configuration for the {@link SlideDeck} provider. */
3
6
  interface SlideDeckConfig {
4
7
  slides: React.ReactNode[];
5
8
  /** Speaker notes per slide (same index as slides). Use `parseSpeakerNotes()` to load from a markdown file. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nextjs-slides",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
4
4
  "description": "Composable slide deck primitives for Next.js — powered by React 19 ViewTransitions, Tailwind CSS, and highlight.js syntax highlighting.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -24,6 +24,8 @@
24
24
  "scripts": {
25
25
  "build": "tsup",
26
26
  "dev": "tsup --watch",
27
+ "format": "prettier --write .",
28
+ "format:check": "prettier --check .",
27
29
  "lint": "eslint .",
28
30
  "lint:fix": "eslint . --fix",
29
31
  "test": "vitest run",
@@ -55,6 +57,7 @@
55
57
  "eslint": "^10.0.2",
56
58
  "jsdom": "^25.0.0",
57
59
  "next": "^16.0.0",
60
+ "prettier": "^3.8.1",
58
61
  "react": "^19.0.0",
59
62
  "react-dom": "^19.0.0",
60
63
  "semantic-release": "^24.2.0",