nextjs-slides 0.9.0 → 0.10.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 +39 -0
- package/dist/slide-deck.d.ts +1 -1
- package/dist/slide-deck.js +5 -4
- package/dist/slide-deck.js.map +1 -1
- package/dist/types.d.ts +4 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -126,6 +126,7 @@ That's it. Navigate to `/slides` and you have a full slide deck.
|
|
|
126
126
|
| `exitUrl` | `string` | — | URL for exit button (×). Shows in top-right when set. |
|
|
127
127
|
| `showProgress` | `boolean` | `true` | Show dot progress indicator |
|
|
128
128
|
| `showCounter` | `boolean` | `true` | Show "3 / 10" counter |
|
|
129
|
+
| `transition` | `boolean` | `true` | Enable default slide animations. Set `false` for custom morphs. |
|
|
129
130
|
| `className` | `string` | — | Additional class for the deck container |
|
|
130
131
|
| `children` | `React.ReactNode` | **required** | Route content (from Next.js) |
|
|
131
132
|
|
|
@@ -377,6 +378,44 @@ Use `className="font-pixel"` on primitives where you want the pixel display font
|
|
|
377
378
|
|
|
378
379
|
Slide transitions use the React 19 `<ViewTransition>` component with `addTransitionType()`. The CSS in `nextjs-slides/styles.css` defines the `::view-transition-*` animations. Override them in your own CSS to customize.
|
|
379
380
|
|
|
381
|
+
### Magic Move (custom morphs)
|
|
382
|
+
|
|
383
|
+
Set `transition={false}` on `<SlideDeck>` to disable the default directional slide animation. Then add inline `viewTransitionName` styles to elements that should morph between slides:
|
|
384
|
+
|
|
385
|
+
```tsx
|
|
386
|
+
function CycleBox({ children, name }: { children: React.ReactNode; name: string }) {
|
|
387
|
+
return (
|
|
388
|
+
<div
|
|
389
|
+
className="rounded-xl border px-7 py-3.5 text-xl font-semibold"
|
|
390
|
+
style={{ viewTransitionName: name }}
|
|
391
|
+
>
|
|
392
|
+
{children}
|
|
393
|
+
</div>
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export const slides = [
|
|
398
|
+
<Slide key="basic">
|
|
399
|
+
<SlideTitle>Render Cycle</SlideTitle>
|
|
400
|
+
<div className="flex gap-4">
|
|
401
|
+
<CycleBox name="box-a">Event</CycleBox>
|
|
402
|
+
<CycleBox name="box-b">Commit</CycleBox>
|
|
403
|
+
</div>
|
|
404
|
+
</Slide>,
|
|
405
|
+
|
|
406
|
+
<Slide key="expanded">
|
|
407
|
+
<SlideTitle>Render Cycle</SlideTitle>
|
|
408
|
+
<div className="flex gap-4">
|
|
409
|
+
<CycleBox name="box-a">Event</CycleBox>
|
|
410
|
+
<span>loading</span>
|
|
411
|
+
<CycleBox name="box-b">Commit</CycleBox>
|
|
412
|
+
</div>
|
|
413
|
+
</Slide>,
|
|
414
|
+
];
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
Elements with matching `viewTransitionName` values across consecutive slides morph their position, size, and opacity automatically. New elements fade in, removed elements fade out. Requires `viewTransition: true` in your `next.config`.
|
|
418
|
+
|
|
380
419
|
## Troubleshooting
|
|
381
420
|
|
|
382
421
|
**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.
|
package/dist/slide-deck.d.ts
CHANGED
|
@@ -30,7 +30,7 @@ import { SlideDeckConfig } from './types.js';
|
|
|
30
30
|
* }
|
|
31
31
|
* ```
|
|
32
32
|
*/
|
|
33
|
-
declare function SlideDeck({ children, slides, basePath, exitUrl, showProgress, showCounter, syncEndpoint, className, speakerNotes: _speakerNotes, }: SlideDeckConfig & {
|
|
33
|
+
declare function SlideDeck({ children, slides, basePath, exitUrl, showProgress, showCounter, syncEndpoint, className, transition, speakerNotes: _speakerNotes, }: SlideDeckConfig & {
|
|
34
34
|
children: React.ReactNode;
|
|
35
35
|
}): react_jsx_runtime.JSX.Element;
|
|
36
36
|
|
package/dist/slide-deck.js
CHANGED
|
@@ -27,6 +27,7 @@ function SlideDeck({
|
|
|
27
27
|
showCounter = true,
|
|
28
28
|
syncEndpoint,
|
|
29
29
|
className,
|
|
30
|
+
transition = true,
|
|
30
31
|
speakerNotes: _speakerNotes
|
|
31
32
|
}) {
|
|
32
33
|
const router = useRouter();
|
|
@@ -118,16 +119,16 @@ function SlideDeck({
|
|
|
118
119
|
ViewTransition,
|
|
119
120
|
{
|
|
120
121
|
default: "none",
|
|
121
|
-
enter: {
|
|
122
|
+
enter: transition ? {
|
|
122
123
|
default: "slide-from-right",
|
|
123
124
|
[TRANSITION_BACK]: "slide-from-left",
|
|
124
125
|
[TRANSITION_FORWARD]: "slide-from-right"
|
|
125
|
-
},
|
|
126
|
-
exit: {
|
|
126
|
+
} : void 0,
|
|
127
|
+
exit: transition ? {
|
|
127
128
|
default: "slide-to-left",
|
|
128
129
|
[TRANSITION_BACK]: "slide-to-right",
|
|
129
130
|
[TRANSITION_FORWARD]: "slide-to-left"
|
|
130
|
-
},
|
|
131
|
+
} : void 0,
|
|
131
132
|
children: /* @__PURE__ */ jsx("div", { children })
|
|
132
133
|
},
|
|
133
134
|
pathname
|
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 {\n addTransitionType,\n useCallback,\n useEffect,\n useMemo,\n useTransition,\n ViewTransition,\n} from 'react';\nimport { cn } from './cn';\nimport { ExitIcon } from './icons';\nimport type { SlideDeckConfig } from './types';\n\nconst TRANSITION_FORWARD = 'slide-forward';\nconst TRANSITION_BACK = 'slide-back';\n\nfunction getSlideIndex(pathname: string, pattern: RegExp): number {\n const match = pathname.match(pattern);\n return match ? Number(match[1]) - 1 : 0;\n}\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 speakerNotes: _speakerNotes,\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 = useMemo(\n () =>\n new RegExp(`^${basePath.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}/(\\\\d+)$`),\n [basePath]\n );\n const isSlideRoute = slideRoutePattern.test(pathname);\n const current = useMemo(\n () => getSlideIndex(pathname, slideRoutePattern),\n [pathname, slideRoutePattern]\n );\n\n const syncSlide = useCallback(\n (slide: number) => {\n if (!syncEndpoint || !isSlideRoute) return;\n fetch(syncEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ slide, total }),\n }).catch(() => {});\n },\n [isSlideRoute, syncEndpoint, total]\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 syncSlide(targetSlide); // Immediate feedback for phone sync\n startTransition(() => {\n addTransitionType(\n clamped > current ? TRANSITION_FORWARD : TRANSITION_BACK\n );\n router.push(`${basePath}/${targetSlide}`);\n });\n },\n [basePath, current, router, startTransition, syncSlide, 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 (!isPending && isSlideRoute) {\n syncSlide(current + 1);\n }\n }, [current, isPending, isSlideRoute, syncSlide]);\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 [TRANSITION_BACK]: 'slide-from-left',\n [TRANSITION_FORWARD]: 'slide-from-right',\n }}\n exit={{\n default: 'slide-to-left',\n [TRANSITION_BACK]: 'slide-to-right',\n [TRANSITION_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 <ExitIcon />\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 useMemo,\n useTransition,\n ViewTransition,\n} from 'react';\nimport { cn } from './cn';\nimport { ExitIcon } from './icons';\nimport type { SlideDeckConfig } from './types';\n\nconst TRANSITION_FORWARD = 'slide-forward';\nconst TRANSITION_BACK = 'slide-back';\n\nfunction getSlideIndex(pathname: string, pattern: RegExp): number {\n const match = pathname.match(pattern);\n return match ? Number(match[1]) - 1 : 0;\n}\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 transition = true,\n speakerNotes: _speakerNotes,\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 = useMemo(\n () =>\n new RegExp(`^${basePath.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}/(\\\\d+)$`),\n [basePath]\n );\n const isSlideRoute = slideRoutePattern.test(pathname);\n const current = useMemo(\n () => getSlideIndex(pathname, slideRoutePattern),\n [pathname, slideRoutePattern]\n );\n\n const syncSlide = useCallback(\n (slide: number) => {\n if (!syncEndpoint || !isSlideRoute) return;\n fetch(syncEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ slide, total }),\n }).catch(() => {});\n },\n [isSlideRoute, syncEndpoint, total]\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 syncSlide(targetSlide); // Immediate feedback for phone sync\n startTransition(() => {\n addTransitionType(\n clamped > current ? TRANSITION_FORWARD : TRANSITION_BACK\n );\n router.push(`${basePath}/${targetSlide}`);\n });\n },\n [basePath, current, router, startTransition, syncSlide, 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 (!isPending && isSlideRoute) {\n syncSlide(current + 1);\n }\n }, [current, isPending, isSlideRoute, syncSlide]);\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={transition ? {\n default: 'slide-from-right',\n [TRANSITION_BACK]: 'slide-from-left',\n [TRANSITION_FORWARD]: 'slide-from-right',\n } : undefined}\n exit={transition ? {\n default: 'slide-to-left',\n [TRANSITION_BACK]: 'slide-to-right',\n [TRANSITION_FORWARD]: 'slide-to-left',\n } : undefined}\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 <ExitIcon />\n </Link>\n )}\n </div>\n </ViewTransition>\n );\n}\n"],"mappings":"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","names":[]}
|
package/dist/types.d.ts
CHANGED
|
@@ -17,6 +17,10 @@ interface SlideDeckConfig {
|
|
|
17
17
|
showCounter?: boolean;
|
|
18
18
|
/** API endpoint for presenter ↔ phone sync. See `SlideNotesView` and the sync route handlers. */
|
|
19
19
|
syncEndpoint?: string;
|
|
20
|
+
/** Control the slide transition animation.
|
|
21
|
+
* - `true` (default) — directional slide-in/out
|
|
22
|
+
* - `false` — no wrapper animation (use with inline `viewTransitionName` for magic-move morphs) */
|
|
23
|
+
transition?: boolean;
|
|
20
24
|
/** Additional className for the deck container */
|
|
21
25
|
className?: string;
|
|
22
26
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nextjs-slides",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.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",
|