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 +52 -39
- package/dist/cn.d.ts +1 -0
- package/dist/cn.js.map +1 -1
- package/dist/get-slide.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +9 -2
- package/dist/index.js.map +1 -1
- package/dist/parse-speaker-notes.js.map +1 -1
- package/dist/primitives.d.ts +134 -12
- package/dist/primitives.js +150 -34
- package/dist/primitives.js.map +1 -1
- package/dist/slide-deck.d.ts +29 -0
- package/dist/slide-deck.js +25 -5
- package/dist/slide-deck.js.map +1 -1
- package/dist/slide-demo-content.d.ts +7 -0
- package/dist/slide-demo-content.js +3 -1
- package/dist/slide-demo-content.js.map +1 -1
- package/dist/slide-link.d.ts +11 -0
- package/dist/slide-link.js.map +1 -1
- package/dist/slide-notes-view.js +7 -1
- package/dist/slide-notes-view.js.map +1 -1
- package/dist/slides.css +39 -34
- package/dist/types.d.ts +3 -0
- package/package.json +4 -1
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
|
|
40
|
-
@import
|
|
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
|
|
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
|
|
79
|
-
import { slides } from
|
|
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
|
|
95
|
+
import { redirect } from 'next/navigation';
|
|
97
96
|
|
|
98
97
|
export default function SlidesPage() {
|
|
99
|
-
redirect(
|
|
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
|
|
106
|
-
import { slides } from
|
|
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
|
|
207
|
-
import path from
|
|
208
|
-
import { SlideDeck, parseSpeakerNotes } from
|
|
209
|
-
import { slides } from
|
|
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(),
|
|
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
|
|
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
|
|
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
|
|
248
|
-
import path from
|
|
249
|
-
import { parseSpeakerNotes, SlideNotesView } from
|
|
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(),
|
|
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
|
|
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
|
|
320
|
-
import { GeistMono } from
|
|
321
|
-
import { GeistPixelSquare } from
|
|
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
|
|
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:
|
|
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
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;
|
|
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":[]}
|
package/dist/get-slide.js.map
CHANGED
|
@@ -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 }
|
|
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 {
|
|
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 {
|
|
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 }
|
|
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":[]}
|
package/dist/primitives.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 };
|
package/dist/primitives.js
CHANGED
|
@@ -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)
|
|
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(
|
|
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
|
|
56
|
+
function SlideColumns({
|
|
50
57
|
left,
|
|
51
58
|
right,
|
|
52
59
|
className
|
|
53
60
|
}) {
|
|
54
|
-
return /* @__PURE__ */ jsxs("div", { className: cn("
|
|
55
|
-
/* @__PURE__ */ jsx("div", { className: "
|
|
56
|
-
/* @__PURE__ */ jsx("div", { className: "
|
|
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
|
|
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(
|
|
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({
|
|
72
|
-
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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({
|
|
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(
|
|
115
|
-
|
|
116
|
-
|
|
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({
|
|
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(
|
|
128
|
-
|
|
129
|
-
|
|
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({
|
|
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({
|
|
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,
|
package/dist/primitives.js.map
CHANGED
|
@@ -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":[]}
|
package/dist/slide-deck.d.ts
CHANGED
|
@@ -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;
|
package/dist/slide-deck.js
CHANGED
|
@@ -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 {
|
|
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(
|
|
145
|
-
|
|
146
|
-
|
|
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
|
]
|
package/dist/slide-deck.js.map
CHANGED
|
@@ -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":";AA6JY,cAsBF,YAtBE;AA3JZ,OAAO,UAAU;AACjB,SAAS,aAAa,iBAAiB;AACvC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,UAAU;AAgCZ,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX;AAAA,EACA,eAAe;AAAA,EACf,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAoD;AAClD,OAAK;AACL,QAAM,SAAS,UAAU;AACzB,QAAM,WAAW,YAAY;AAC7B,QAAM,CAAC,WAAW,eAAe,IAAI,cAAc;AAEnD,QAAM,QAAQ,OAAO;AACrB,QAAM,oBAAoB,IAAI,OAAO,IAAI,QAAQ,UAAU;AAC3D,QAAM,eAAe,kBAAkB,KAAK,QAAQ;AACpD,QAAM,WAAW,MAAM;AACrB,UAAM,QAAQ,SAAS,MAAM,iBAAiB;AAC9C,WAAO,QAAQ,OAAO,MAAM,CAAC,CAAC,IAAI,IAAI;AAAA,EACxC,GAAG;AAEH,QAAM,OAAO;AAAA,IACX,CAAC,UAAkB;AACjB,YAAM,UAAU,KAAK,IAAI,GAAG,KAAK,IAAI,OAAO,QAAQ,CAAC,CAAC;AACtD,UAAI,YAAY,QAAS;AACzB,YAAM,cAAc,UAAU;AAC9B,UAAI,cAAc;AAChB,cAAM,cAAc;AAAA,UAClB,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC9C,MAAM,KAAK,UAAU,EAAE,OAAO,aAAa,MAAM,CAAC;AAAA,QACpD,CAAC,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AAAA,MACnB;AACA,sBAAgB,MAAM;AACpB,0BAAkB,UAAU,UAAU,kBAAkB,YAAY;AACpE,eAAO,KAAK,GAAG,QAAQ,IAAI,WAAW,EAAE;AAAA,MAC1C,CAAC;AAAA,IACH;AAAA,IACA,CAAC,UAAU,SAAS,QAAQ,iBAAiB,cAAc,KAAK;AAAA,EAClE;AAEA,YAAU,MAAM;AACd,QAAI,CAAC,aAAc;AACnB,QAAI,UAAU,EAAG,QAAO,SAAS,GAAG,QAAQ,IAAI,OAAO,EAAE;AACzD,QAAI,UAAU,QAAQ,EAAG,QAAO,SAAS,GAAG,QAAQ,IAAI,UAAU,CAAC,EAAE;AAAA,EACvE,GAAG,CAAC,UAAU,SAAS,cAAc,QAAQ,KAAK,CAAC;AAEnD,YAAU,MAAM;AACd,QAAI,CAAC,aAAc;AACnB,aAAS,UAAU,GAAkB;AACnC,YAAM,SAAS,EAAE;AACjB,UACE,OAAO,QAAQ,0BAA0B,KACzC,OAAO,QAAQ,mDAAmD,GAClE;AACA;AAAA,MACF;AACA,UAAI,EAAE,QAAQ,gBAAgB,EAAE,QAAQ,KAAK;AAC3C,UAAE,eAAe;AACjB,aAAK,UAAU,CAAC;AAAA,MAClB,WAAW,EAAE,QAAQ,aAAa;AAChC,UAAE,eAAe;AACjB,aAAK,UAAU,CAAC;AAAA,MAClB;AAAA,IACF;AACA,WAAO,iBAAiB,WAAW,SAAS;AAC5C,WAAO,MAAM,OAAO,oBAAoB,WAAW,SAAS;AAAA,EAC9D,GAAG,CAAC,SAAS,MAAM,YAAY,CAAC;AAEhC,YAAU,MAAM;AACd,UAAM,OAAO,SAAS,KAAK,MAAM;AACjC,aAAS,KAAK,MAAM,WAAW;AAC/B,WAAO,MAAM;AACX,eAAS,KAAK,MAAM,WAAW;AAAA,IACjC;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,YAAU,MAAM;AACd,QAAI,CAAC,gBAAgB,CAAC,gBAAgB,UAAW;AACjD,UAAM,cAAc;AAAA,MAClB,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,EAAE,OAAO,UAAU,GAAG,MAAM,CAAC;AAAA,IACpD,CAAC,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EACnB,GAAG,CAAC,cAAc,SAAS,OAAO,cAAc,SAAS,CAAC;AAE1D,SACE,oBAAC,kBAAe,SAAQ,QAAO,MAAK,eAClC;AAAA,IAAC;AAAA;AAAA,MACC,IAAG;AAAA,MACH,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACA,gBAAc,YAAY,KAAK;AAAA,MAE/B;AAAA,4BAAC,SAAI,WAAU,0BACb;AAAA,UAAC;AAAA;AAAA,YAEC,SAAQ;AAAA,YACR,OAAO;AAAA,cACL,SAAS;AAAA,cACT,cAAc;AAAA,cACd,iBAAiB;AAAA,YACnB;AAAA,YACA,MAAM;AAAA,cACJ,SAAS;AAAA,cACT,cAAc;AAAA,cACd,iBAAiB;AAAA,YACnB;AAAA,YAEA,8BAAC,SAAK,UAAS;AAAA;AAAA,UAbV;AAAA,QAcP,GACF;AAAA,QAEC,gBAAgB,gBACf;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YACV,cAAW;AAAA,YAEV,gBAAM,KAAK,EAAE,QAAQ,MAAM,CAAC,EAAE,IAAI,CAAC,GAAG,MACrC;AAAA,cAAC;AAAA;AAAA,gBAEC,WAAW;AAAA,kBACT;AAAA,kBACA,MAAM,UAAU,sBAAsB;AAAA,gBACxC;AAAA;AAAA,cAJK;AAAA,YAKP,CACD;AAAA;AAAA,QACH;AAAA,QAGD,gBAAgB,eACf,qBAAC,SAAI,WAAU,mFACZ;AAAA,oBAAU;AAAA,UAAE;AAAA,UAAI;AAAA,WACnB;AAAA,QAGD,gBAAgB,WACf;AAAA,UAAC;AAAA;AAAA,YACC,MAAM;AAAA,YACN,WAAU;AAAA,YACV,cAAW;AAAA,YAEX;AAAA,cAAC;AAAA;AAAA,gBACC,OAAM;AAAA,gBACN,OAAM;AAAA,gBACN,QAAO;AAAA,gBACP,SAAQ;AAAA,gBACR,MAAK;AAAA,gBACL,QAAO;AAAA,gBACP,aAAY;AAAA,gBACZ,eAAc;AAAA,gBACd,gBAAe;AAAA,gBAEf;AAAA,sCAAC,UAAK,GAAE,cAAa;AAAA,kBACrB,oBAAC,UAAK,GAAE,cAAa;AAAA;AAAA;AAAA,YACvB;AAAA;AAAA,QACF;AAAA;AAAA;AAAA,EAEJ,GACF;AAEJ;","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(
|
|
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
|
|
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":[]}
|
package/dist/slide-link.d.ts
CHANGED
|
@@ -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
|
|
package/dist/slide-link.js.map
CHANGED
|
@@ -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'
|
|
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":[]}
|
package/dist/slide-notes-view.js
CHANGED
|
@@ -111,7 +111,13 @@ function SlideNotesView({
|
|
|
111
111
|
},
|
|
112
112
|
children: currentNote
|
|
113
113
|
}
|
|
114
|
-
) : /* @__PURE__ */ jsx(
|
|
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
|
|
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:
|
|
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 {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
.nxs-code-block .hljs-
|
|
100
|
-
|
|
101
|
-
|
|
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 {
|
|
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 {
|
|
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 {
|
|
109
|
-
|
|
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 {
|
|
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.
|
|
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",
|