nextjs-slides 0.1.6 → 0.2.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 +21 -18
- package/dist/primitives.js +14 -9
- package/dist/primitives.js.map +1 -1
- package/dist/slide-deck.d.ts +1 -1
- package/dist/slide-deck.js +15 -1
- package/dist/slide-deck.js.map +1 -1
- package/dist/slides.css +51 -25
- package/dist/types.d.ts +2 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/nextjs-slides)
|
|
4
4
|
|
|
5
|
-
Composable slide deck primitives for Next.js — powered by React 19 ViewTransitions, Tailwind CSS v4, and
|
|
5
|
+
Composable slide deck primitives for Next.js — powered by React 19 ViewTransitions, Tailwind CSS v4, and highlight.js syntax highlighting.
|
|
6
6
|
|
|
7
7
|
Build full presentations from React components with URL-based routing, keyboard navigation, progress indicators, and smooth slide transitions — all declarative.
|
|
8
8
|
|
|
@@ -22,7 +22,7 @@ A minimal demo app lives in `examples/demo`. From the repo root:
|
|
|
22
22
|
npm run build && cd examples/demo && npm install && npm run dev
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
Open http://localhost:3000
|
|
25
|
+
Open http://localhost:3000 — choose "Geist deck" or "Alternate deck" (Playfair + Dracula theme).
|
|
26
26
|
|
|
27
27
|
Peer dependencies: `next >=15`, `react >=19`, `tailwindcss >=4`.
|
|
28
28
|
|
|
@@ -129,14 +129,15 @@ That's it. Navigate to `/slides` and you have a full slide deck.
|
|
|
129
129
|
|
|
130
130
|
## `<SlideDeck>` Props
|
|
131
131
|
|
|
132
|
-
| Prop | Type | Default | Description
|
|
133
|
-
| -------------- | ----------------- | ------------ |
|
|
134
|
-
| `slides` | `ReactNode[]` | **required** | Your slides array
|
|
135
|
-
| `basePath` | `string` | `"/slides"` | URL prefix for slide routes
|
|
136
|
-
| `
|
|
137
|
-
| `
|
|
138
|
-
| `
|
|
139
|
-
| `
|
|
132
|
+
| Prop | Type | Default | Description |
|
|
133
|
+
| -------------- | ----------------- | ------------ | ------------------------------------------------------- |
|
|
134
|
+
| `slides` | `ReactNode[]` | **required** | Your slides array |
|
|
135
|
+
| `basePath` | `string` | `"/slides"` | URL prefix for slide routes |
|
|
136
|
+
| `exitUrl` | `string` | — | URL for exit button (×). Shows in top-right when set. |
|
|
137
|
+
| `showProgress` | `boolean` | `true` | Show dot progress indicator |
|
|
138
|
+
| `showCounter` | `boolean` | `true` | Show "3 / 10" counter |
|
|
139
|
+
| `className` | `string` | — | Additional class for the deck container |
|
|
140
|
+
| `children` | `React.ReactNode` | **required** | Route content (from Next.js) |
|
|
140
141
|
|
|
141
142
|
## Primitives
|
|
142
143
|
|
|
@@ -155,7 +156,7 @@ That's it. Navigate to `/slides` and you have a full slide deck.
|
|
|
155
156
|
|
|
156
157
|
### Content
|
|
157
158
|
|
|
158
|
-
- **`<SlideCode>`** — Syntax-highlighted code block (
|
|
159
|
+
- **`<SlideCode>`** — Syntax-highlighted code block (highlight.js). Props: `title`, `className`. Pass code as `children` string. Supports JS, TS, JSX, TSX (language inferred from `title`, e.g. `example.tsx`).
|
|
159
160
|
- **`<SlideList>`** / **`<SlideListItem>`** — Bullet list.
|
|
160
161
|
- **`<SlideDemo>`** — Interactive component container. Keyboard navigation is disabled inside so you can use inputs and buttons. Props: `label`, `className`.
|
|
161
162
|
|
|
@@ -179,17 +180,17 @@ That's it. Navigate to `/slides` and you have a full slide deck.
|
|
|
179
180
|
|
|
180
181
|
Keyboard events are ignored inside `<SlideDemo>`, inputs, and textareas so you can interact without advancing slides.
|
|
181
182
|
|
|
182
|
-
## Custom Base Path
|
|
183
|
+
## Custom Base Path & Multiple Decks
|
|
183
184
|
|
|
184
|
-
|
|
185
|
+
Use `basePath` for a different URL, `exitUrl` for an exit button (×), and `className` for scoped font/syntax overrides:
|
|
185
186
|
|
|
186
187
|
```tsx
|
|
187
|
-
<SlideDeck slides={slides} basePath="/deck">
|
|
188
|
+
<SlideDeck slides={slides} basePath="/slides-alt" exitUrl="/" className="slides-alt-deck">
|
|
188
189
|
{children}
|
|
189
190
|
</SlideDeck>
|
|
190
191
|
```
|
|
191
192
|
|
|
192
|
-
|
|
193
|
+
Route at `/slides-alt/[page]/page.tsx`. Keep `SlideDeck` as the direct layout child (no wrapper div) so the exit animation works.
|
|
193
194
|
|
|
194
195
|
## Breakout Pages
|
|
195
196
|
|
|
@@ -206,9 +207,9 @@ app/slides/
|
|
|
206
207
|
|
|
207
208
|
The library **inherits** your app's theme. Primitives use Tailwind utilities that resolve to CSS variables: `--foreground`, `--background`, `--muted-foreground`, `--primary`, `--primary-foreground`, `--border`, `--muted`. Compatible with shadcn/ui and any Tailwind v4 setup that defines these.
|
|
208
209
|
|
|
209
|
-
`nextjs-slides/styles.css` adds
|
|
210
|
+
`nextjs-slides/styles.css` adds the Vercel-inspired code theme (`--nxs-code-*`, `--sh-*`) and slide transition animations. No scoping — slides inherit your global styles.
|
|
210
211
|
|
|
211
|
-
**Customize code
|
|
212
|
+
**Customize code block:** Override `--nxs-code-bg`, `--nxs-code-border`, `--nxs-code-text` for block styling. Override `--sh-*` for syntax highlighting: `--sh-keyword`, `--sh-string`, `--sh-property`, `--sh-entity`, `--sh-class`, `--sh-tag` (JSX/HTML tags), `--sh-identifier`, `--sh-literal`, `--sh-comment`, `--sh-sign`.
|
|
212
213
|
|
|
213
214
|
### Geist fonts (optional)
|
|
214
215
|
|
|
@@ -246,10 +247,12 @@ Slide transitions use the React 19 `<ViewTransition>` component with `addTransit
|
|
|
246
247
|
|
|
247
248
|
## Troubleshooting
|
|
248
249
|
|
|
249
|
-
**SlideCode syntax highlighting looks broken or colorless** — Ensure you import `nextjs-slides/styles.css` in your root layout or global CSS (see Quick Start). The `--sh-*` variables must be in scope for
|
|
250
|
+
**SlideCode syntax highlighting looks broken or colorless** — Ensure you import `nextjs-slides/styles.css` in your root layout or global CSS (see Quick Start). The `--sh-*` variables must be in scope for highlight.js tokens to display correctly.
|
|
250
251
|
|
|
251
252
|
**`@source` path not found** — The `@source "../node_modules/nextjs-slides/dist"` path is relative to your CSS file. If your `globals.css` lives in `app/`, use `../node_modules/...`. If it lives in the project root, use `./node_modules/nextjs-slides/dist`.
|
|
252
253
|
|
|
254
|
+
**Exit animation (deck-unveil) not running** — Ensure `SlideDeck` is the direct child of the layout. Wrapping it in a `<div>` can prevent the ViewTransition exit from firing. Use the `className` prop for scoped styling instead.
|
|
255
|
+
|
|
253
256
|
## For maintainers
|
|
254
257
|
|
|
255
258
|
See [DEPLOYMENT.md](DEPLOYMENT.md) for Vercel deployment and release workflow.
|
package/dist/primitives.js
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import
|
|
2
|
+
import hljs from "highlight.js/lib/core";
|
|
3
|
+
import javascript from "highlight.js/lib/languages/javascript";
|
|
4
|
+
import typescript from "highlight.js/lib/languages/typescript";
|
|
5
|
+
import xml from "highlight.js/lib/languages/xml";
|
|
3
6
|
import { cn } from "./cn";
|
|
7
|
+
hljs.registerLanguage("javascript", javascript);
|
|
8
|
+
hljs.registerLanguage("typescript", typescript);
|
|
9
|
+
hljs.registerLanguage("xml", xml);
|
|
10
|
+
function highlightCode(code, lang) {
|
|
11
|
+
const language = lang === "ts" || lang === "tsx" ? "typescript" : lang ?? "typescript";
|
|
12
|
+
return hljs.highlight(code, { language }).value;
|
|
13
|
+
}
|
|
4
14
|
import { SlideDemoContent } from "./slide-demo-content";
|
|
5
15
|
function Slide({
|
|
6
16
|
children,
|
|
@@ -73,17 +83,12 @@ function SlideBadge({ children, className }) {
|
|
|
73
83
|
function SlideHeaderBadge({ children, className }) {
|
|
74
84
|
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 }) });
|
|
75
85
|
}
|
|
76
|
-
function reclassLiterals(html) {
|
|
77
|
-
return html.replace(
|
|
78
|
-
/(<span[^>]*class=")sh__token--(?:identifier|class)("[^>]*>)(null|false|true|undefined)(<\/span>)/g,
|
|
79
|
-
(_, before, after, literal, close) => `${before}sh__token--keyword${after}${literal}${close}`
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
86
|
function SlideCode({ children, className, title }) {
|
|
83
|
-
const
|
|
87
|
+
const lang = title?.split(".").pop();
|
|
88
|
+
const html = highlightCode(children, lang);
|
|
84
89
|
return /* @__PURE__ */ jsxs("div", { className: cn("w-full max-w-2xl", className), children: [
|
|
85
90
|
title && /* @__PURE__ */ jsx("div", { className: "text-muted-foreground mb-2 text-xs font-medium tracking-wider uppercase", children: title }),
|
|
86
|
-
/* @__PURE__ */ jsx("pre", { className: "nxs-code-block
|
|
91
|
+
/* @__PURE__ */ jsx("pre", { className: "nxs-code-block overflow-x-auto border p-6 text-left font-mono text-[13px] leading-[1.7] sm:text-sm", children: /* @__PURE__ */ jsx("code", { dangerouslySetInnerHTML: { __html: html } }) })
|
|
87
92
|
] });
|
|
88
93
|
}
|
|
89
94
|
function SlideList({ children, className }) {
|
package/dist/primitives.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/primitives.tsx"],"sourcesContent":["import { highlight } from 'sugar-high';\nimport { cn } from './cn';\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-6',\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 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=\"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=\"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=\"relative z-10 flex w-1/2 flex-col justify-center\">{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\n/** sugar-high treats null as \"class\" and may miss some literals; reclass so they use keyword styling */\nfunction reclassLiterals(html: string): string {\n return html.replace(\n /(<span[^>]*class=\")sh__token--(?:identifier|class)(\"[^>]*>)(null|false|true|undefined)(<\\/span>)/g,\n (_, before, after, literal, close) => `${before}sh__token--keyword${after}${literal}${close}`,\n );\n}\n\nexport function SlideCode({ children, className, title }: { children: string; className?: string; title?: string }) {\n const html = reclassLiterals(highlight(children));\n\n return (\n <div className={cn('w-full max-w-2xl', 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 border-foreground/10 bg-foreground/[0.03] overflow-x-auto border p-6 text-left font-mono text-[13px] leading-[1.7] sm:text-sm\">\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('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] border 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 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({ name, title, className }: { name: string; title: string; className?: string }) {\n return (\n <div className={cn('flex items-center gap-4', className)}>\n <div className=\"bg-muted h-12 w-12 shrink-0 rounded-full\" aria-hidden />\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 const language = lang === 'ts' || lang === 'tsx' ? 'typescript' : lang ?? 'typescript';\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-6',\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 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=\"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=\"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=\"relative z-10 flex w-1/2 flex-col justify-center\">{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('w-full max-w-2xl', 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 overflow-x-auto border p-6 text-left font-mono text-[13px] leading-[1.7] sm:text-sm\">\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('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] border 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 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({ name, title, className }: { name: string; title: string; className?: string }) {\n return (\n <div className={cn('flex items-center gap-4', className)}>\n <div className=\"bg-muted h-12 w-12 shrink-0 rounded-full\" aria-hidden />\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":[]}
|
package/dist/slide-deck.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { SlideDeckConfig } from './types.js';
|
|
3
3
|
|
|
4
|
-
declare function SlideDeck({ children, slides, basePath, showProgress, showCounter, className, }: SlideDeckConfig & {
|
|
4
|
+
declare function SlideDeck({ children, slides, basePath, exitUrl, showProgress, showCounter, className, }: SlideDeckConfig & {
|
|
5
5
|
children: React.ReactNode;
|
|
6
6
|
}): react_jsx_runtime.JSX.Element;
|
|
7
7
|
|
package/dist/slide-deck.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import Link from "next/link";
|
|
3
4
|
import { usePathname, useRouter } from "next/navigation";
|
|
4
5
|
import { addTransitionType, useCallback, useEffect, useTransition, ViewTransition } from "react";
|
|
5
6
|
import { cn } from "./cn";
|
|
@@ -7,6 +8,7 @@ function SlideDeck({
|
|
|
7
8
|
children,
|
|
8
9
|
slides,
|
|
9
10
|
basePath = "/slides",
|
|
11
|
+
exitUrl,
|
|
10
12
|
showProgress = true,
|
|
11
13
|
showCounter = true,
|
|
12
14
|
className
|
|
@@ -111,7 +113,19 @@ function SlideDeck({
|
|
|
111
113
|
current + 1,
|
|
112
114
|
" / ",
|
|
113
115
|
total
|
|
114
|
-
] })
|
|
116
|
+
] }),
|
|
117
|
+
isSlideRoute && exitUrl && /* @__PURE__ */ jsx(
|
|
118
|
+
Link,
|
|
119
|
+
{
|
|
120
|
+
href: exitUrl,
|
|
121
|
+
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",
|
|
122
|
+
"aria-label": "Exit presentation",
|
|
123
|
+
children: /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
124
|
+
/* @__PURE__ */ jsx("path", { d: "M18 6 6 18" }),
|
|
125
|
+
/* @__PURE__ */ jsx("path", { d: "m6 6 12 12" })
|
|
126
|
+
] })
|
|
127
|
+
}
|
|
128
|
+
)
|
|
115
129
|
]
|
|
116
130
|
}
|
|
117
131
|
) });
|
package/dist/slide-deck.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/slide-deck.tsx"],"sourcesContent":["'use client';\n\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 showProgress = true,\n showCounter = true,\n className,\n}: SlideDeckConfig & { children: React.ReactNode }) {\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 startTransition(() => {\n addTransitionType(clamped > current ? 'slide-forward' : 'slide-back');\n router.push(`${basePath}/${clamped + 1}`);\n });\n },\n [basePath, current, router, startTransition, 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 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 overflow-hidden font-sans select-none',\n className,\n )}\n data-pending={isPending ? '' : undefined}\n >\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\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 </div>\n </ViewTransition>\n );\n}\n"],"mappings":";
|
|
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 className,\n}: SlideDeckConfig & { children: React.ReactNode }) {\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 startTransition(() => {\n addTransitionType(clamped > current ? 'slide-forward' : 'slide-back');\n router.push(`${basePath}/${clamped + 1}`);\n });\n },\n [basePath, current, router, startTransition, 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 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 overflow-hidden font-sans select-none',\n className,\n )}\n data-pending={isPending ? '' : undefined}\n >\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\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":[]}
|
package/dist/slides.css
CHANGED
|
@@ -1,36 +1,62 @@
|
|
|
1
1
|
/* nextjs-slides — Slide transition animations and code theme tokens */
|
|
2
2
|
|
|
3
|
-
/*
|
|
4
|
-
Override these --sh-* variables in your :root / .dark to customize. */
|
|
3
|
+
/* GitHub-style theme (Vercel/docs) — override --sh-* and --nxs-code-* in :root / .dark */
|
|
5
4
|
:root {
|
|
6
|
-
--
|
|
7
|
-
--
|
|
8
|
-
--
|
|
9
|
-
--sh-
|
|
10
|
-
--sh-
|
|
11
|
-
--sh-
|
|
12
|
-
--sh-
|
|
13
|
-
--sh-
|
|
14
|
-
--sh-
|
|
15
|
-
--sh-
|
|
16
|
-
--sh-
|
|
5
|
+
--nxs-code-bg: #ffffff;
|
|
6
|
+
--nxs-code-border: #eaeaea;
|
|
7
|
+
--nxs-code-text: #24292e;
|
|
8
|
+
--sh-keyword: #d73a49;
|
|
9
|
+
--sh-string: #032f62;
|
|
10
|
+
--sh-property: #005cc5;
|
|
11
|
+
--sh-class: #6f42c1;
|
|
12
|
+
--sh-entity: #6f42c1;
|
|
13
|
+
--sh-tag: #22863a;
|
|
14
|
+
--sh-identifier: #24292e;
|
|
15
|
+
--sh-literal: #005cc5;
|
|
16
|
+
--sh-comment: #6a737d;
|
|
17
|
+
--sh-sign: #24292e;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
/* sugar-high code theme — dark (Vercel) */
|
|
20
20
|
.dark {
|
|
21
|
-
--
|
|
22
|
-
--
|
|
23
|
-
--
|
|
24
|
-
--sh-
|
|
25
|
-
--sh-
|
|
26
|
-
--sh-
|
|
27
|
-
--sh-
|
|
28
|
-
--sh-
|
|
29
|
-
--sh-
|
|
30
|
-
--sh-
|
|
31
|
-
--sh-
|
|
21
|
+
--nxs-code-bg: #0d1117;
|
|
22
|
+
--nxs-code-border: #30363d;
|
|
23
|
+
--nxs-code-text: #c9d1d9;
|
|
24
|
+
--sh-keyword: #ff7b72;
|
|
25
|
+
--sh-string: #a5d6ff;
|
|
26
|
+
--sh-property: #79c0ff;
|
|
27
|
+
--sh-class: #d2a8ff;
|
|
28
|
+
--sh-entity: #d2a8ff;
|
|
29
|
+
--sh-tag: #7ee787;
|
|
30
|
+
--sh-identifier: #c9d1d9;
|
|
31
|
+
--sh-literal: #79c0ff;
|
|
32
|
+
--sh-comment: #8b949e;
|
|
33
|
+
--sh-sign: #c9d1d9;
|
|
32
34
|
}
|
|
33
35
|
|
|
36
|
+
/* Map highlight.js to theme variables (all overwritable via --sh-*) */
|
|
37
|
+
.nxs-code-block {
|
|
38
|
+
background: var(--nxs-code-bg);
|
|
39
|
+
border-color: var(--nxs-code-border);
|
|
40
|
+
color: var(--nxs-code-text);
|
|
41
|
+
}
|
|
42
|
+
.nxs-code-block .hljs-keyword,
|
|
43
|
+
.nxs-code-block .hljs-literal { color: var(--sh-keyword); }
|
|
44
|
+
.nxs-code-block .hljs-string { color: var(--sh-string); }
|
|
45
|
+
.nxs-code-block .hljs-number { color: var(--sh-literal); }
|
|
46
|
+
.nxs-code-block .hljs-comment { color: var(--sh-comment); }
|
|
47
|
+
.nxs-code-block .hljs-title.class_ { color: var(--sh-class); }
|
|
48
|
+
.nxs-code-block .hljs-title.function_ { color: var(--sh-entity); }
|
|
49
|
+
.nxs-code-block .hljs-attr,
|
|
50
|
+
.nxs-code-block .hljs-attribute,
|
|
51
|
+
.nxs-code-block .hljs-params { color: var(--sh-property); }
|
|
52
|
+
.nxs-code-block .hljs-built_in,
|
|
53
|
+
.nxs-code-block .hljs-variable { color: var(--sh-identifier); }
|
|
54
|
+
.nxs-code-block .hljs-name,
|
|
55
|
+
.nxs-code-block .hljs-tag .hljs-name { color: var(--sh-tag); }
|
|
56
|
+
.nxs-code-block .hljs-tag { color: var(--sh-sign); }
|
|
57
|
+
.nxs-code-block .hljs-punctuation,
|
|
58
|
+
.nxs-code-block .hljs-operator { color: var(--sh-sign); }
|
|
59
|
+
|
|
34
60
|
|
|
35
61
|
/* Slide transition keyframes */
|
|
36
62
|
@keyframes nxs-fade {
|
package/dist/types.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ interface SlideDeckConfig {
|
|
|
4
4
|
slides: React.ReactNode[];
|
|
5
5
|
/** Base path for slide URLs. Defaults to "/slides" */
|
|
6
6
|
basePath?: string;
|
|
7
|
+
/** URL to navigate to when exiting the deck. When set, shows an exit button. */
|
|
8
|
+
exitUrl?: string;
|
|
7
9
|
/** Show progress dots at the bottom. Defaults to true */
|
|
8
10
|
showProgress?: boolean;
|
|
9
11
|
/** Show slide counter (e.g. "3 / 10"). Defaults to true */
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nextjs-slides",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Composable slide deck primitives for Next.js — powered by React 19 ViewTransitions, Tailwind CSS, and
|
|
3
|
+
"version": "0.2.0",
|
|
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",
|
|
7
7
|
"exports": {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"clsx": "^2.1.1",
|
|
38
|
-
"
|
|
38
|
+
"highlight.js": "^11.11.1",
|
|
39
39
|
"tailwind-merge": "^3.5.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"react",
|
|
68
68
|
"tailwind",
|
|
69
69
|
"view-transitions",
|
|
70
|
-
"
|
|
70
|
+
"highlight.js"
|
|
71
71
|
],
|
|
72
72
|
"repository": {
|
|
73
73
|
"type": "git",
|