nextjs-slides 0.8.0 → 0.8.2

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.
@@ -3,6 +3,7 @@ import hljs from "highlight.js/lib/core";
3
3
  import javascript from "highlight.js/lib/languages/javascript";
4
4
  import typescript from "highlight.js/lib/languages/typescript";
5
5
  import xml from "highlight.js/lib/languages/xml";
6
+ import { SlideDemoContent } from "./slide-demo-content";
6
7
  import { cn } from "./cn";
7
8
  hljs.registerLanguage("javascript", javascript);
8
9
  hljs.registerLanguage("typescript", typescript);
@@ -15,7 +16,6 @@ function highlightCode(code, lang) {
15
16
  return hljs.highlight(code, { language: "typescript" }).value;
16
17
  return hljs.highlight(code, { language }).value;
17
18
  }
18
- import { SlideDemoContent } from "./slide-demo-content";
19
19
  function Slide({
20
20
  children,
21
21
  align = "center",
@@ -156,7 +156,7 @@ function SlideCode({
156
156
  const lang = title?.split(".").pop();
157
157
  const html = highlightCode(children.trim(), lang);
158
158
  return /* @__PURE__ */ jsxs("div", { className: cn("nxs-code-wrapper", className), children: [
159
- title && /* @__PURE__ */ jsx("div", { className: "text-muted-foreground mb-2 text-xs font-medium tracking-wider uppercase", children: title }),
159
+ title && /* @__PURE__ */ jsx("div", { className: "text-muted-foreground mb-2 text-sm font-medium tracking-wider uppercase", children: title }),
160
160
  /* @__PURE__ */ jsx("pre", { className: "nxs-code-block", children: /* @__PURE__ */ jsx("code", { dangerouslySetInnerHTML: { __html: html } }) })
161
161
  ] });
162
162
  }
@@ -194,7 +194,7 @@ function SlideNote({
194
194
  children,
195
195
  className
196
196
  }) {
197
- return /* @__PURE__ */ jsx("p", { className: cn("text-muted-foreground/50 text-sm", className), children });
197
+ return /* @__PURE__ */ jsx("p", { className: cn("text-muted-foreground text-base sm:text-lg", className), children });
198
198
  }
199
199
  function SlideDemo({
200
200
  children,
@@ -207,7 +207,7 @@ function SlideDemo({
207
207
  "data-slide-interactive": true,
208
208
  className: cn("min-w-0 w-full max-w-2xl", className),
209
209
  children: [
210
- label && /* @__PURE__ */ jsx("div", { className: "text-muted-foreground mb-2 text-xs font-medium tracking-wider uppercase", children: label }),
210
+ label && /* @__PURE__ */ jsx("div", { className: "text-muted-foreground mb-2 text-sm font-medium tracking-wider uppercase", children: label }),
211
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
212
  ]
213
213
  }
@@ -233,7 +233,7 @@ function SlideStatement({
233
233
  ),
234
234
  children: [
235
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 })
236
+ description && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground mt-1 text-base sm:text-lg", children: description })
237
237
  ]
238
238
  }
239
239
  );
@@ -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)\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-6',\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 gap-6 px-12 py-16 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 gap-6 px-12 py-16 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-black 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 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-6 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":"AA+CI,SAQE,KARF;AA/CJ,OAAO,UAAU;AACjB,OAAO,gBAAgB;AACvB,OAAO,gBAAgB;AACvB,OAAO,SAAS;AAChB,SAAS,UAAU;AAEnB,KAAK,iBAAiB,cAAc,UAAU;AAC9C,KAAK,iBAAiB,cAAc,UAAU;AAC9C,KAAK,iBAAiB,OAAO,GAAG;AAEhC,SAAS,cAAc,MAAc,MAAuB;AAC1D,MAAI,CAAC,KAAM,QAAO,KAAK,UAAU,MAAM,EAAE,UAAU,aAAa,CAAC,EAAE;AACnE,QAAM,WAAW,SAAS,QAAQ,SAAS,QAAQ,eAAe;AAClE,QAAM,aAAa,KAAK,YAAY,QAAQ;AAC5C,MAAI,CAAC;AACH,WAAO,KAAK,UAAU,MAAM,EAAE,UAAU,aAAa,CAAC,EAAE;AAC1D,SAAO,KAAK,UAAU,MAAM,EAAE,SAAS,CAAC,EAAE;AAC5C;AACA,SAAS,wBAAwB;AAkB1B,SAAS,MAAM;AAAA,EACpB;AAAA,EACA,QAAQ;AAAA,EACR;AACF,GAKG;AACD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA,UAAU,YAAY;AAAA,QACtB,UAAU,UAAU;AAAA,QACpB;AAAA,MACF;AAAA,MAEA;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YACV,eAAW;AAAA;AAAA,QACb;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,WAAW;AAAA,cACT;AAAA,cACA,UAAU,YAAY;AAAA,cACtB,UAAU,UAAU;AAAA,YACtB;AAAA,YAEC;AAAA;AAAA,QACH;AAAA;AAAA;AAAA,EACF;AAEJ;AAQO,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,SACE,qBAAC,SAAI,WAAW,GAAG,iCAAiC,SAAS,GAC3D;AAAA,wBAAC,SAAI,WAAU,+BAA+B,gBAAK;AAAA,IACnD,oBAAC,SAAI,WAAU,+BAA+B,iBAAM;AAAA,KACtD;AAEJ;AASO,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MAEA;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YACV,eAAW;AAAA;AAAA,QACb;AAAA,QACA,oBAAC,SAAI,WAAU,qHACZ,gBACH;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YACV,eAAW;AAAA;AAAA,QACb;AAAA,QACA,oBAAC,SAAI,WAAU,qHACZ,iBACH;AAAA;AAAA;AAAA,EACF;AAEJ;AAQO,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AACF,GAGG;AACD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACA,OAAO,EAAE,eAAe,UAAU;AAAA,MAEjC;AAAA;AAAA,EACH;AAEJ;AAMO,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AACF,GAGG;AACD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MAEC;AAAA;AAAA,EACH;AAEJ;AAMO,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AACF,GAGG;AACD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MAEC;AAAA;AAAA,EACH;AAEJ;AAMO,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA;AACF,GAGG;AACD,SACE,oBAAC,SAAI,WAAW,GAAG,2BAA2B,SAAS,GACrD,8BAAC,UAAK,WAAU,2EACb,UACH,GACF;AAEJ;AAmBO,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AACF,GAMG;AACD,QAAM,OAAO,OAAO,MAAM,GAAG,EAAE,IAAI;AACnC,QAAM,OAAO,cAAc,SAAS,KAAK,GAAG,IAAI;AAEhD,SACE,qBAAC,SAAI,WAAW,GAAG,oBAAoB,SAAS,GAC7C;AAAA,aACC,oBAAC,SAAI,WAAU,2EACZ,iBACH;AAAA,IAEF,oBAAC,SAAI,WAAU,kBACb,8BAAC,UAAK,yBAAyB,EAAE,QAAQ,KAAK,GAAG,GACnD;AAAA,KACF;AAEJ;AAKO,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AACF,GAGG;AACD,SACE,oBAAC,QAAG,WAAW,GAAG,iCAAiC,SAAS,GACzD,UACH;AAEJ;AAMO,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AACF,GAGG;AACD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MAEA;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YACV,eAAW;AAAA;AAAA,QACb;AAAA,QACA,oBAAC,UAAM,UAAS;AAAA;AAAA;AAAA,EAClB;AAEJ;AAMO,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AACF,GAGG;AACD,SACE,oBAAC,OAAE,WAAW,GAAG,oCAAoC,SAAS,GAC3D,UACH;AAEJ;AAiBO,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AACF,GAKG;AACD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,0BAAsB;AAAA,MACtB,WAAW,GAAG,4BAA4B,SAAS;AAAA,MAElD;AAAA,iBACC,oBAAC,SAAI,WAAU,2EACZ,iBACH;AAAA,QAEF,oBAAC,SAAI,WAAU,yFACb,8BAAC,oBAAkB,UAAS,GAC9B;AAAA;AAAA;AAAA,EACF;AAEJ;AAMO,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA;AACF,GAGG;AACD,SACE,oBAAC,SAAI,WAAW,GAAG,gCAAgC,SAAS,GACzD,UACH;AAEJ;AAMO,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AACF,GAMG;AACD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MAEA;AAAA,4BAAC,QAAG,WAAU,4DACX,iBACH;AAAA,QACC,eACC,oBAAC,OAAE,WAAU,mDACV,uBACH;AAAA;AAAA;AAAA,EAEJ;AAEJ;AASO,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAMG;AACD,SACE,qBAAC,SAAI,WAAW,GAAG,2BAA2B,SAAS,GACrD;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,WAAW;AAAA,UACT;AAAA,UACA,SAAS,aAAa;AAAA,QACxB;AAAA,QACA,eAAW;AAAA,QAEV,mBACC,oBAAC,SAAI,KAAK,QAAQ,KAAI,IAAG,WAAU,8BAA6B,IAC9D;AAAA;AAAA,IACN;AAAA,IACA,qBAAC,SACC;AAAA,0BAAC,OAAE,WAAU,oEACV,gBACH;AAAA,MACA,oBAAC,OAAE,WAAU,0DACV,iBACH;AAAA,OACF;AAAA,KACF;AAEJ;AAMO,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA;AACF,GAGG;AACD,SACE,oBAAC,SAAI,WAAW,GAAG,yCAAyC,SAAS,GAClE,UACH;AAEJ;AAKO,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA;AACF,GAGG;AACD,SAAO,oBAAC,SAAI,WAAW,GAAG,uBAAuB,SAAS,GAAI,UAAS;AACzE;","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 { SlideDemoContent } from './slide-demo-content';\nimport { cn } from './cn';\nimport type { SlideAlign } from './types';\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}\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-6',\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 gap-6 px-12 py-16 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 gap-6 px-12 py-16 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-black 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-sm 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 text-base sm:text-lg', 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-sm 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-6 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-base sm:text-lg\">\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":[]}
@@ -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, ...rest }: SlideDeckConfig & {
33
+ declare function SlideDeck({ children, slides, basePath, exitUrl, showProgress, showCounter, syncEndpoint, className, speakerNotes: _speakerNotes, }: SlideDeckConfig & {
34
34
  children: React.ReactNode;
35
35
  }): react_jsx_runtime.JSX.Element;
36
36
 
@@ -6,10 +6,18 @@ import {
6
6
  addTransitionType,
7
7
  useCallback,
8
8
  useEffect,
9
+ useMemo,
9
10
  useTransition,
10
11
  ViewTransition
11
12
  } from "react";
12
13
  import { cn } from "./cn";
14
+ import { ExitIcon } from "./icons";
15
+ const TRANSITION_FORWARD = "slide-forward";
16
+ const TRANSITION_BACK = "slide-back";
17
+ function getSlideIndex(pathname, pattern) {
18
+ const match = pathname.match(pattern);
19
+ return match ? Number(match[1]) - 1 : 0;
20
+ }
13
21
  function SlideDeck({
14
22
  children,
15
23
  slides,
@@ -19,38 +27,47 @@ function SlideDeck({
19
27
  showCounter = true,
20
28
  syncEndpoint,
21
29
  className,
22
- ...rest
30
+ speakerNotes: _speakerNotes
23
31
  }) {
24
- void rest;
25
32
  const router = useRouter();
26
33
  const pathname = usePathname();
27
34
  const [isPending, startTransition] = useTransition();
28
35
  const total = slides.length;
29
- const slideRoutePattern = new RegExp(`^${basePath}/(\\d+)$`);
36
+ const slideRoutePattern = useMemo(
37
+ () => new RegExp(`^${basePath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/(\\d+)$`),
38
+ [basePath]
39
+ );
30
40
  const isSlideRoute = slideRoutePattern.test(pathname);
31
- const current = (() => {
32
- const match = pathname.match(slideRoutePattern);
33
- return match ? Number(match[1]) - 1 : 0;
34
- })();
41
+ const current = useMemo(
42
+ () => getSlideIndex(pathname, slideRoutePattern),
43
+ [pathname, slideRoutePattern]
44
+ );
45
+ const syncSlide = useCallback(
46
+ (slide) => {
47
+ if (!syncEndpoint || !isSlideRoute) return;
48
+ fetch(syncEndpoint, {
49
+ method: "POST",
50
+ headers: { "Content-Type": "application/json" },
51
+ body: JSON.stringify({ slide, total })
52
+ }).catch(() => {
53
+ });
54
+ },
55
+ [isSlideRoute, syncEndpoint, total]
56
+ );
35
57
  const goTo = useCallback(
36
58
  (index) => {
37
59
  const clamped = Math.max(0, Math.min(index, total - 1));
38
60
  if (clamped === current) return;
39
61
  const targetSlide = clamped + 1;
40
- if (syncEndpoint) {
41
- fetch(syncEndpoint, {
42
- method: "POST",
43
- headers: { "Content-Type": "application/json" },
44
- body: JSON.stringify({ slide: targetSlide, total })
45
- }).catch(() => {
46
- });
47
- }
62
+ syncSlide(targetSlide);
48
63
  startTransition(() => {
49
- addTransitionType(clamped > current ? "slide-forward" : "slide-back");
64
+ addTransitionType(
65
+ clamped > current ? TRANSITION_FORWARD : TRANSITION_BACK
66
+ );
50
67
  router.push(`${basePath}/${targetSlide}`);
51
68
  });
52
69
  },
53
- [basePath, current, router, startTransition, syncEndpoint, total]
70
+ [basePath, current, router, startTransition, syncSlide, total]
54
71
  );
55
72
  useEffect(() => {
56
73
  if (!isSlideRoute) return;
@@ -83,14 +100,10 @@ function SlideDeck({
83
100
  };
84
101
  }, []);
85
102
  useEffect(() => {
86
- if (!syncEndpoint || !isSlideRoute || isPending) return;
87
- fetch(syncEndpoint, {
88
- method: "POST",
89
- headers: { "Content-Type": "application/json" },
90
- body: JSON.stringify({ slide: current + 1, total })
91
- }).catch(() => {
92
- });
93
- }, [syncEndpoint, current, total, isSlideRoute, isPending]);
103
+ if (!isPending && isSlideRoute) {
104
+ syncSlide(current + 1);
105
+ }
106
+ }, [current, isPending, isSlideRoute, syncSlide]);
94
107
  return /* @__PURE__ */ jsx(ViewTransition, { default: "none", exit: "deck-unveil", children: /* @__PURE__ */ jsxs(
95
108
  "div",
96
109
  {
@@ -107,13 +120,13 @@ function SlideDeck({
107
120
  default: "none",
108
121
  enter: {
109
122
  default: "slide-from-right",
110
- "slide-back": "slide-from-left",
111
- "slide-forward": "slide-from-right"
123
+ [TRANSITION_BACK]: "slide-from-left",
124
+ [TRANSITION_FORWARD]: "slide-from-right"
112
125
  },
113
126
  exit: {
114
127
  default: "slide-to-left",
115
- "slide-back": "slide-to-right",
116
- "slide-forward": "slide-to-left"
128
+ [TRANSITION_BACK]: "slide-to-right",
129
+ [TRANSITION_FORWARD]: "slide-to-left"
117
130
  },
118
131
  children: /* @__PURE__ */ jsx("div", { children })
119
132
  },
@@ -147,24 +160,7 @@ function SlideDeck({
147
160
  href: exitUrl,
148
161
  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",
149
162
  "aria-label": "Exit presentation",
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
- )
163
+ children: /* @__PURE__ */ jsx(ExitIcon, {})
168
164
  }
169
165
  )
170
166
  ]
@@ -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 useTransition,\n ViewTransition,\n} from 'react';\nimport { cn } from './cn';\nimport type { SlideDeckConfig } from './types';\n\n/**\n * Top-level slide deck provider that wraps the current slide's content.\n *\n * Place this in your slides layout (e.g. `app/slides/layout.tsx`). It provides:\n * - **Keyboard navigation** — Arrow keys and Space to navigate slides.\n * - **ViewTransition animations** — Slide-in/out with directional awareness.\n * - **Progress UI** — Dots and a counter at the bottom of the viewport.\n * - **Exit button** — When `exitUrl` is set, shows an × in the top-right corner.\n * - **Presenter sync** — When `syncEndpoint` is set, POSTs the current slide\n * on navigation for `SlideNotesView` to poll.\n *\n * `SlideDeck` must be the **direct child** of the layout (no wrapper div)\n * for the deck-unveil exit animation to work correctly.\n *\n * @example\n * ```tsx\n * // app/slides/layout.tsx\n * import { SlideDeck } from 'nextjs-slides';\n * import { slides } from './slides';\n *\n * export default function SlidesLayout({ children }: { children: React.ReactNode }) {\n * return (\n * <SlideDeck slides={slides} exitUrl=\"/\" syncEndpoint=\"/api/nxs-sync\">\n * {children}\n * </SlideDeck>\n * );\n * }\n * ```\n */\nexport function SlideDeck({\n children,\n slides,\n basePath = '/slides',\n exitUrl,\n showProgress = true,\n showCounter = true,\n syncEndpoint,\n className,\n ...rest\n}: SlideDeckConfig & { children: React.ReactNode }) {\n void rest; // accepts speakerNotes and other optional config\n const router = useRouter();\n const pathname = usePathname();\n const [isPending, startTransition] = useTransition();\n\n const total = slides.length;\n const slideRoutePattern = new RegExp(`^${basePath}/(\\\\d+)$`);\n const isSlideRoute = slideRoutePattern.test(pathname);\n const current = (() => {\n const match = pathname.match(slideRoutePattern);\n return match ? Number(match[1]) - 1 : 0;\n })();\n\n const goTo = useCallback(\n (index: number) => {\n const clamped = Math.max(0, Math.min(index, total - 1));\n if (clamped === current) return;\n const targetSlide = clamped + 1; // 1-based for sync API\n if (syncEndpoint) {\n fetch(syncEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ slide: targetSlide, total }),\n }).catch(() => {});\n }\n startTransition(() => {\n addTransitionType(clamped > current ? 'slide-forward' : 'slide-back');\n router.push(`${basePath}/${targetSlide}`);\n });\n },\n [basePath, current, router, startTransition, syncEndpoint, total]\n );\n\n useEffect(() => {\n if (!isSlideRoute) return;\n if (current > 0) router.prefetch(`${basePath}/${current}`);\n if (current < total - 1) router.prefetch(`${basePath}/${current + 2}`);\n }, [basePath, current, isSlideRoute, router, total]);\n\n useEffect(() => {\n if (!isSlideRoute) return;\n function onKeyDown(e: KeyboardEvent) {\n const target = e.target as HTMLElement;\n if (\n target.closest('[data-slide-interactive]') ||\n target.matches('input, textarea, select, [contenteditable=\"true\"]')\n ) {\n return;\n }\n if (e.key === 'ArrowRight' || e.key === ' ') {\n e.preventDefault();\n goTo(current + 1);\n } else if (e.key === 'ArrowLeft') {\n e.preventDefault();\n goTo(current - 1);\n }\n }\n window.addEventListener('keydown', onKeyDown);\n return () => window.removeEventListener('keydown', onKeyDown);\n }, [current, goTo, isSlideRoute]);\n\n useEffect(() => {\n const prev = document.body.style.overflow;\n document.body.style.overflow = 'hidden';\n return () => {\n document.body.style.overflow = prev;\n };\n }, []);\n\n useEffect(() => {\n if (!syncEndpoint || !isSlideRoute || isPending) return;\n fetch(syncEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ slide: current + 1, total }),\n }).catch(() => {});\n }, [syncEndpoint, current, total, isSlideRoute, isPending]);\n\n return (\n <ViewTransition default=\"none\" exit=\"deck-unveil\">\n <div\n id=\"slide-deck\"\n className={cn(\n 'bg-background text-foreground fixed inset-0 z-50 flex flex-col overflow-hidden font-sans select-none',\n className\n )}\n data-pending={isPending ? '' : undefined}\n >\n <div className=\"flex-1 overflow-hidden\">\n <ViewTransition\n key={pathname}\n default=\"none\"\n enter={{\n default: 'slide-from-right',\n 'slide-back': 'slide-from-left',\n 'slide-forward': 'slide-from-right',\n }}\n exit={{\n default: 'slide-to-left',\n 'slide-back': 'slide-to-right',\n 'slide-forward': 'slide-to-left',\n }}\n >\n <div>{children}</div>\n </ViewTransition>\n </div>\n\n {isSlideRoute && showProgress && (\n <div\n className=\"fixed bottom-8 left-1/2 z-50 flex -translate-x-1/2 items-center gap-1.5\"\n aria-label=\"Slide progress\"\n >\n {Array.from({ length: total }).map((_, i) => (\n <div\n key={i}\n className={cn(\n 'h-1 transition-all duration-300',\n i === current ? 'bg-foreground w-6' : 'bg-foreground/20 w-1'\n )}\n />\n ))}\n </div>\n )}\n\n {isSlideRoute && showCounter && (\n <div className=\"text-foreground/30 fixed right-8 bottom-8 z-50 font-mono text-xs tracking-wider\">\n {current + 1} / {total}\n </div>\n )}\n\n {isSlideRoute && exitUrl && (\n <Link\n href={exitUrl}\n className=\"text-foreground/50 hover:text-foreground fixed top-6 right-8 z-50 flex h-10 w-10 items-center justify-center rounded-md transition-colors hover:bg-foreground/10\"\n aria-label=\"Exit presentation\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </Link>\n )}\n </div>\n </ViewTransition>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"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":[]}
@@ -9,7 +9,8 @@ function SlideDemoContent({ children }) {
9
9
  if (!el) return;
10
10
  const observer = new ResizeObserver((entries) => {
11
11
  for (const entry of entries) {
12
- const height = entry.borderBoxSize[0].blockSize;
12
+ const blockSize = entry.borderBoxSize?.[0]?.blockSize;
13
+ const height = blockSize ?? entry.contentRect?.height ?? 0;
13
14
  setMinHeight(
14
15
  (prev) => prev === void 0 ? height : Math.max(prev, height)
15
16
  );
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/slide-demo-content.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, useRef, useState } from 'react';\n\n/**\n * Client wrapper for {@link SlideDemo} content that tracks the maximum\n * rendered height via `ResizeObserver`, preventing layout jumps when\n * the child re-renders with different content sizes.\n *\n * @internal Used by `SlideDemo` — not exported from the public API.\n */\nexport function SlideDemoContent({ children }: { children: React.ReactNode }) {\n const ref = useRef<HTMLDivElement>(null);\n const [minHeight, setMinHeight] = useState<number | undefined>(undefined);\n\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n\n const observer = new ResizeObserver((entries) => {\n for (const entry of entries) {\n const height = entry.borderBoxSize[0].blockSize;\n setMinHeight((prev) =>\n prev === undefined ? height : Math.max(prev, height)\n );\n }\n });\n observer.observe(el);\n return () => observer.disconnect();\n }, []);\n\n return (\n <div ref={ref} style={minHeight !== undefined ? { minHeight } : undefined}>\n {children}\n </div>\n );\n}\n"],"mappings":";AAgCI;AA9BJ,SAAS,WAAW,QAAQ,gBAAgB;AASrC,SAAS,iBAAiB,EAAE,SAAS,GAAkC;AAC5E,QAAM,MAAM,OAAuB,IAAI;AACvC,QAAM,CAAC,WAAW,YAAY,IAAI,SAA6B,MAAS;AAExE,YAAU,MAAM;AACd,UAAM,KAAK,IAAI;AACf,QAAI,CAAC,GAAI;AAET,UAAM,WAAW,IAAI,eAAe,CAAC,YAAY;AAC/C,iBAAW,SAAS,SAAS;AAC3B,cAAM,SAAS,MAAM,cAAc,CAAC,EAAE;AACtC;AAAA,UAAa,CAAC,SACZ,SAAS,SAAY,SAAS,KAAK,IAAI,MAAM,MAAM;AAAA,QACrD;AAAA,MACF;AAAA,IACF,CAAC;AACD,aAAS,QAAQ,EAAE;AACnB,WAAO,MAAM,SAAS,WAAW;AAAA,EACnC,GAAG,CAAC,CAAC;AAEL,SACE,oBAAC,SAAI,KAAU,OAAO,cAAc,SAAY,EAAE,UAAU,IAAI,QAC7D,UACH;AAEJ;","names":[]}
1
+ {"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 blockSize = entry.borderBoxSize?.[0]?.blockSize;\n const height = blockSize ?? entry.contentRect?.height ?? 0;\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":";AAiCI;AA/BJ,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,YAAY,MAAM,gBAAgB,CAAC,GAAG;AAC5C,cAAM,SAAS,aAAa,MAAM,aAAa,UAAU;AACzD;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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nextjs-slides",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
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",