nextjs-slides 0.1.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 ADDED
@@ -0,0 +1,184 @@
1
+ # nextjs-slides
2
+
3
+ Composable slide deck primitives for Next.js — powered by React 19 ViewTransitions, Tailwind CSS v4, and sugar-high syntax highlighting.
4
+
5
+ Build full presentations from React components with URL-based routing, keyboard navigation, progress indicators, and smooth slide transitions — all declarative.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install nextjs-slides
11
+ ```
12
+
13
+ Peer dependencies: `next >=15`, `react >=19`, `tailwindcss >=4`.
14
+
15
+ ## Quick Start
16
+
17
+ ### 1. Import the stylesheet
18
+
19
+ In your root layout or global CSS:
20
+
21
+ ```css
22
+ @import "nextjs-slides/styles.css";
23
+ ```
24
+
25
+ ### 2. Define your slides
26
+
27
+ ```tsx
28
+ // app/slides/slides.tsx
29
+ import {
30
+ Slide,
31
+ SlideTitle,
32
+ SlideSubtitle,
33
+ SlideBadge,
34
+ SlideCode,
35
+ } from "nextjs-slides";
36
+
37
+ export const slides = [
38
+ <Slide key="intro">
39
+ <SlideBadge>Welcome</SlideBadge>
40
+ <SlideTitle>My Presentation</SlideTitle>
41
+ <SlideSubtitle>Built with nextjs-slides</SlideSubtitle>
42
+ </Slide>,
43
+
44
+ <Slide key="code" align="left">
45
+ <SlideTitle>Code Example</SlideTitle>
46
+ <SlideCode title="hello.ts">{`const greeting = "Hello, world!";
47
+ console.log(greeting);`}</SlideCode>
48
+ </Slide>,
49
+ ];
50
+ ```
51
+
52
+ ### 3. Create the layout (provider)
53
+
54
+ ```tsx
55
+ // app/slides/layout.tsx
56
+ "use client";
57
+
58
+ import { SlideDeck } from "nextjs-slides";
59
+ import { slides } from "./slides";
60
+
61
+ export default function SlidesLayout({
62
+ children,
63
+ }: {
64
+ children: React.ReactNode;
65
+ }) {
66
+ return <SlideDeck slides={slides}>{children}</SlideDeck>;
67
+ }
68
+ ```
69
+
70
+ ### 4. Add the routes
71
+
72
+ ```tsx
73
+ // app/slides/page.tsx
74
+ import { redirect } from "next/navigation";
75
+
76
+ export default function SlidesPage() {
77
+ redirect("/slides/1");
78
+ }
79
+ ```
80
+
81
+ ```tsx
82
+ // app/slides/[page]/page.tsx
83
+ import { getSlide, generateSlideParams } from "nextjs-slides";
84
+ import { slides } from "../slides";
85
+
86
+ export const generateStaticParams = () => generateSlideParams(slides);
87
+
88
+ export default async function SlidePage({
89
+ params,
90
+ }: {
91
+ params: Promise<{ page: string }>;
92
+ }) {
93
+ return getSlide(await params, slides);
94
+ }
95
+ ```
96
+
97
+ That's it. Navigate to `/slides` and you have a full slide deck.
98
+
99
+ ## `<SlideDeck>` Props
100
+
101
+ | Prop | Type | Default | Description |
102
+ | -------------- | ----------------- | ------------ | --------------------------------------- |
103
+ | `slides` | `ReactNode[]` | **required** | Your slides array |
104
+ | `basePath` | `string` | `"/slides"` | URL prefix for slide routes |
105
+ | `showProgress` | `boolean` | `true` | Show dot progress indicator |
106
+ | `showCounter` | `boolean` | `true` | Show "3 / 10" counter |
107
+ | `className` | `string` | — | Additional class for the deck container |
108
+ | `children` | `React.ReactNode` | **required** | Route content (from Next.js) |
109
+
110
+ ## Primitives
111
+
112
+ ### Layout
113
+
114
+ - **`<Slide>`** — Full-screen slide container with decorative border. Props: `align` (`"center"` | `"left"`), `className`.
115
+ - **`<SlideSplitLayout>`** — Two-column layout with vertical divider. Props: `left`, `right`, `className`.
116
+
117
+ ### Typography
118
+
119
+ - **`<SlideTitle>`** — Large bold heading (responsive h1).
120
+ - **`<SlideSubtitle>`** — Muted secondary text.
121
+ - **`<SlideBadge>`** — Inverted pill badge.
122
+ - **`<SlideHeaderBadge>`** — Italic accent label.
123
+ - **`<SlideNote>`** — Small muted footnote.
124
+
125
+ ### Content
126
+
127
+ - **`<SlideCode>`** — Syntax-highlighted code block (sugar-high). Props: `title`, `className`. Pass code as `children` string.
128
+ - **`<SlideList>`** / **`<SlideListItem>`** — Bullet list.
129
+ - **`<SlideDemo>`** — Interactive component container. Keyboard/click navigation is disabled inside. Props: `label`, `className`.
130
+
131
+ ### Structured
132
+
133
+ - **`<SlideStatementList>`** / **`<SlideStatement>`** — Title + description pairs with border separators.
134
+ - **`<SlideSpeaker>`** — Avatar + name + title row.
135
+ - **`<SlideSpeakerGrid>`** — 2-column speaker grid.
136
+ - **`<SlideSpeakerList>`** — Vertical speaker stack.
137
+
138
+ ### Navigation
139
+
140
+ - **`<SlideLink>`** — Styled link for navigation. Props: `href`, `variant` (`"primary"` | `"ghost"`), `className`.
141
+
142
+ ## Keyboard Navigation
143
+
144
+ | Key | Action |
145
+ | ------------ | -------------- |
146
+ | `→` or Space | Next slide |
147
+ | `←` | Previous slide |
148
+
149
+ Interactive areas (`<SlideDemo>`, inputs, textareas) don't trigger navigation.
150
+
151
+ ## Custom Base Path
152
+
153
+ Host slides at a different URL:
154
+
155
+ ```tsx
156
+ <SlideDeck slides={slides} basePath="/deck">
157
+ {children}
158
+ </SlideDeck>
159
+ ```
160
+
161
+ Then route at `/deck/[page]/page.tsx`.
162
+
163
+ ## Breakout Pages
164
+
165
+ Pages inside the slides folder but outside the `[page]` route render without deck navigation — useful for live demos:
166
+
167
+ ```
168
+ app/slides/
169
+ layout.tsx ← SlideDeck provider
170
+ [page]/page.tsx ← Slide routes
171
+ demo/page.tsx ← Breakout page (no deck chrome)
172
+ ```
173
+
174
+ ## Tailwind CSS
175
+
176
+ The primitives use Tailwind utility classes with standard CSS variable tokens (`--foreground`, `--background`, `--muted-foreground`, `--primary`, etc.). These are compatible with shadcn/ui themes or any Tailwind v4 setup that defines these variables.
177
+
178
+ ## Animations
179
+
180
+ 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.
181
+
182
+ ## License
183
+
184
+ MIT
package/dist/cn.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { ClassValue } from 'clsx';
2
+
3
+ declare function cn(...inputs: ClassValue[]): string;
4
+
5
+ export { cn };
package/dist/cn.js ADDED
@@ -0,0 +1,9 @@
1
+ import { clsx } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+ function cn(...inputs) {
4
+ return twMerge(clsx(inputs));
5
+ }
6
+ export {
7
+ cn
8
+ };
9
+ //# sourceMappingURL=cn.js.map
package/dist/cn.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cn.ts"],"sourcesContent":["import { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n"],"mappings":"AAAA,SAAS,YAA6B;AACtC,SAAS,eAAe;AAEjB,SAAS,MAAM,QAAsB;AAC1C,SAAO,QAAQ,KAAK,MAAM,CAAC;AAC7B;","names":[]}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Resolves the current slide from params and the slides array.
3
+ * Use in your `[page]/page.tsx` dynamic route.
4
+ *
5
+ * @example
6
+ * ```tsx
7
+ * import { getSlide } from 'nextjs-slides';
8
+ * import { slides } from '../slides';
9
+ *
10
+ * export default async function SlidePage({ params }: { params: Promise<{ page: string }> }) {
11
+ * return getSlide(await params, slides);
12
+ * }
13
+ * ```
14
+ */
15
+ declare function getSlide(params: {
16
+ page: string;
17
+ }, slides: React.ReactNode[]): React.ReactNode;
18
+ /**
19
+ * Generates static params for all slides. Use with `generateStaticParams`.
20
+ *
21
+ * @example
22
+ * ```tsx
23
+ * import { generateSlideParams } from 'nextjs-slides';
24
+ * import { slides } from '../slides';
25
+ *
26
+ * export const generateStaticParams = () => generateSlideParams(slides);
27
+ * ```
28
+ */
29
+ declare function generateSlideParams(slides: React.ReactNode[]): {
30
+ page: string;
31
+ }[];
32
+
33
+ export { generateSlideParams, getSlide };
@@ -0,0 +1,16 @@
1
+ import { notFound } from "next/navigation";
2
+ function getSlide(params, slides) {
3
+ const index = Number(params.page) - 1;
4
+ if (isNaN(index) || index < 0 || index >= slides.length) {
5
+ notFound();
6
+ }
7
+ return slides[index];
8
+ }
9
+ function generateSlideParams(slides) {
10
+ return slides.map((_, i) => ({ page: String(i + 1) }));
11
+ }
12
+ export {
13
+ generateSlideParams,
14
+ getSlide
15
+ };
16
+ //# sourceMappingURL=get-slide.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/get-slide.tsx"],"sourcesContent":["import { notFound } from 'next/navigation';\n\n/**\n * Resolves the current slide from params and the slides array.\n * Use in your `[page]/page.tsx` dynamic route.\n *\n * @example\n * ```tsx\n * import { getSlide } from 'nextjs-slides';\n * import { slides } from '../slides';\n *\n * export default async function SlidePage({ params }: { params: Promise<{ page: string }> }) {\n * return getSlide(await params, slides);\n * }\n * ```\n */\nexport function getSlide(params: { page: string }, slides: React.ReactNode[]): React.ReactNode {\n const index = Number(params.page) - 1;\n\n if (isNaN(index) || index < 0 || index >= slides.length) {\n notFound();\n }\n\n return slides[index];\n}\n\n/**\n * Generates static params for all slides. Use with `generateStaticParams`.\n *\n * @example\n * ```tsx\n * import { generateSlideParams } from 'nextjs-slides';\n * import { slides } from '../slides';\n *\n * export const generateStaticParams = () => generateSlideParams(slides);\n * ```\n */\nexport function generateSlideParams(slides: React.ReactNode[]) {\n return slides.map((_, i) => ({ page: String(i + 1) }));\n}\n"],"mappings":"AAAA,SAAS,gBAAgB;AAgBlB,SAAS,SAAS,QAA0B,QAA4C;AAC7F,QAAM,QAAQ,OAAO,OAAO,IAAI,IAAI;AAEpC,MAAI,MAAM,KAAK,KAAK,QAAQ,KAAK,SAAS,OAAO,QAAQ;AACvD,aAAS;AAAA,EACX;AAEA,SAAO,OAAO,KAAK;AACrB;AAaO,SAAS,oBAAoB,QAA2B;AAC7D,SAAO,OAAO,IAAI,CAAC,GAAG,OAAO,EAAE,MAAM,OAAO,IAAI,CAAC,EAAE,EAAE;AACvD;","names":[]}
@@ -0,0 +1,6 @@
1
+ export { SlideDeck } from './slide-deck.js';
2
+ export { generateSlideParams, getSlide } from './get-slide.js';
3
+ export { Slide, SlideBadge, SlideCode, SlideDemo, SlideHeaderBadge, SlideList, SlideListItem, SlideNote, SlideSpeaker, SlideSpeakerGrid, SlideSpeakerList, SlideSplitLayout, SlideStatement, SlideStatementList, SlideSubtitle, SlideTitle } from './primitives.js';
4
+ export { SlideLink } from './slide-link.js';
5
+ export { SlideAlign, SlideDeckConfig, SlideLinkVariant } from './types.js';
6
+ import 'react/jsx-runtime';
package/dist/index.js ADDED
@@ -0,0 +1,31 @@
1
+ import { SlideDeck } from "./slide-deck";
2
+ import { getSlide, generateSlideParams } from "./get-slide";
3
+ import { Slide, SlideSplitLayout } from "./primitives";
4
+ import { SlideTitle, SlideSubtitle, SlideBadge, SlideHeaderBadge, SlideNote } from "./primitives";
5
+ import { SlideCode, SlideList, SlideListItem, SlideDemo } from "./primitives";
6
+ import { SlideStatementList, SlideStatement } from "./primitives";
7
+ import { SlideSpeaker, SlideSpeakerGrid, SlideSpeakerList } from "./primitives";
8
+ import { SlideLink } from "./slide-link";
9
+ export {
10
+ Slide,
11
+ SlideBadge,
12
+ SlideCode,
13
+ SlideDeck,
14
+ SlideDemo,
15
+ SlideHeaderBadge,
16
+ SlideLink,
17
+ SlideList,
18
+ SlideListItem,
19
+ SlideNote,
20
+ SlideSpeaker,
21
+ SlideSpeakerGrid,
22
+ SlideSpeakerList,
23
+ SlideSplitLayout,
24
+ SlideStatement,
25
+ SlideStatementList,
26
+ SlideSubtitle,
27
+ SlideTitle,
28
+ generateSlideParams,
29
+ getSlide
30
+ };
31
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["// Provider\nexport { SlideDeck } from './slide-deck';\n\n// Routing helpers\nexport { getSlide, generateSlideParams } from './get-slide';\n\n// Primitives — Layout\nexport { Slide, SlideSplitLayout } from './primitives';\n\n// Primitives — Typography\nexport { SlideTitle, SlideSubtitle, SlideBadge, SlideHeaderBadge, SlideNote } from './primitives';\n\n// Primitives — Content\nexport { SlideCode, SlideList, SlideListItem, SlideDemo } from './primitives';\n\n// Primitives — Statements\nexport { SlideStatementList, SlideStatement } from './primitives';\n\n// Primitives — Speakers\nexport { SlideSpeaker, SlideSpeakerGrid, SlideSpeakerList } from './primitives';\n\n// Navigation\nexport { SlideLink } from './slide-link';\n\n// Types\nexport type { SlideAlign, SlideLinkVariant, SlideDeckConfig } from './types';\n"],"mappings":"AACA,SAAS,iBAAiB;AAG1B,SAAS,UAAU,2BAA2B;AAG9C,SAAS,OAAO,wBAAwB;AAGxC,SAAS,YAAY,eAAe,YAAY,kBAAkB,iBAAiB;AAGnF,SAAS,WAAW,WAAW,eAAe,iBAAiB;AAG/D,SAAS,oBAAoB,sBAAsB;AAGnD,SAAS,cAAc,kBAAkB,wBAAwB;AAGjE,SAAS,iBAAiB;","names":[]}
@@ -0,0 +1,75 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { SlideAlign } from './types.js';
3
+
4
+ declare function Slide({ children, align, className, }: {
5
+ children: React.ReactNode;
6
+ align?: SlideAlign;
7
+ className?: string;
8
+ }): react_jsx_runtime.JSX.Element;
9
+ declare function SlideSplitLayout({ left, right, className, }: {
10
+ left: React.ReactNode;
11
+ right: React.ReactNode;
12
+ className?: string;
13
+ }): react_jsx_runtime.JSX.Element;
14
+ declare function SlideTitle({ children, className }: {
15
+ children: React.ReactNode;
16
+ className?: string;
17
+ }): react_jsx_runtime.JSX.Element;
18
+ declare function SlideSubtitle({ children, className }: {
19
+ children: React.ReactNode;
20
+ className?: string;
21
+ }): react_jsx_runtime.JSX.Element;
22
+ declare function SlideBadge({ children, className }: {
23
+ children: React.ReactNode;
24
+ className?: string;
25
+ }): react_jsx_runtime.JSX.Element;
26
+ declare function SlideHeaderBadge({ children, className }: {
27
+ children: React.ReactNode;
28
+ className?: string;
29
+ }): react_jsx_runtime.JSX.Element;
30
+ declare function SlideCode({ children, className, title }: {
31
+ children: string;
32
+ className?: string;
33
+ title?: string;
34
+ }): react_jsx_runtime.JSX.Element;
35
+ declare function SlideList({ children, className }: {
36
+ children: React.ReactNode;
37
+ className?: string;
38
+ }): react_jsx_runtime.JSX.Element;
39
+ declare function SlideListItem({ children, className }: {
40
+ children: React.ReactNode;
41
+ className?: string;
42
+ }): react_jsx_runtime.JSX.Element;
43
+ declare function SlideNote({ children, className }: {
44
+ children: React.ReactNode;
45
+ className?: string;
46
+ }): react_jsx_runtime.JSX.Element;
47
+ declare function SlideDemo({ children, className, label, }: {
48
+ children: React.ReactNode;
49
+ className?: string;
50
+ label?: string;
51
+ }): react_jsx_runtime.JSX.Element;
52
+ declare function SlideStatementList({ children, className }: {
53
+ children: React.ReactNode;
54
+ className?: string;
55
+ }): react_jsx_runtime.JSX.Element;
56
+ declare function SlideStatement({ title, description, className, }: {
57
+ title: string;
58
+ description?: string;
59
+ className?: string;
60
+ }): react_jsx_runtime.JSX.Element;
61
+ declare function SlideSpeaker({ name, title, className }: {
62
+ name: string;
63
+ title: string;
64
+ className?: string;
65
+ }): react_jsx_runtime.JSX.Element;
66
+ declare function SlideSpeakerGrid({ children, className }: {
67
+ children: React.ReactNode;
68
+ className?: string;
69
+ }): react_jsx_runtime.JSX.Element;
70
+ declare function SlideSpeakerList({ children, className }: {
71
+ children: React.ReactNode;
72
+ className?: string;
73
+ }): react_jsx_runtime.JSX.Element;
74
+
75
+ export { Slide, SlideBadge, SlideCode, SlideDemo, SlideHeaderBadge, SlideList, SlideListItem, SlideNote, SlideSpeaker, SlideSpeakerGrid, SlideSpeakerList, SlideSplitLayout, SlideStatement, SlideStatementList, SlideSubtitle, SlideTitle };
@@ -0,0 +1,151 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { highlight } from "sugar-high";
3
+ import { cn } from "./cn";
4
+ import { SlideDemoContent } from "./slide-demo-content";
5
+ function Slide({
6
+ children,
7
+ align = "center",
8
+ className
9
+ }) {
10
+ return /* @__PURE__ */ jsxs(
11
+ "div",
12
+ {
13
+ className: cn(
14
+ "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",
15
+ align === "center" && "items-center text-center",
16
+ align === "left" && "items-start text-left",
17
+ className
18
+ ),
19
+ children: [
20
+ /* @__PURE__ */ jsx("div", { className: "border-foreground/10 pointer-events-none absolute inset-4 border sm:inset-6", "aria-hidden": true }),
21
+ /* @__PURE__ */ jsx(
22
+ "div",
23
+ {
24
+ className: cn(
25
+ "relative z-10 flex max-w-4xl flex-col gap-6",
26
+ align === "center" && "items-center",
27
+ align === "left" && "items-start"
28
+ ),
29
+ children
30
+ }
31
+ )
32
+ ]
33
+ }
34
+ );
35
+ }
36
+ function SlideSplitLayout({
37
+ left,
38
+ right,
39
+ className
40
+ }) {
41
+ return /* @__PURE__ */ jsxs("div", { className: cn("nxs-slide relative flex h-dvh w-dvw", className), children: [
42
+ /* @__PURE__ */ jsx("div", { className: "border-foreground/10 pointer-events-none absolute inset-4 border sm:inset-6", "aria-hidden": true }),
43
+ /* @__PURE__ */ jsx("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", children: left }),
44
+ /* @__PURE__ */ jsx("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": true }),
45
+ /* @__PURE__ */ jsx("div", { className: "relative z-10 flex w-1/2 flex-col justify-center", children: right })
46
+ ] });
47
+ }
48
+ function SlideTitle({ children, className }) {
49
+ return /* @__PURE__ */ jsx(
50
+ "h1",
51
+ {
52
+ className: cn("text-foreground text-4xl font-extrabold sm:text-5xl md:text-6xl lg:text-7xl", className),
53
+ style: { letterSpacing: "-0.04em" },
54
+ children
55
+ }
56
+ );
57
+ }
58
+ function SlideSubtitle({ children, className }) {
59
+ return /* @__PURE__ */ jsx("p", { className: cn("text-muted-foreground text-lg sm:text-xl md:text-2xl", className), children });
60
+ }
61
+ function SlideBadge({ children, className }) {
62
+ return /* @__PURE__ */ jsx(
63
+ "span",
64
+ {
65
+ className: cn(
66
+ "bg-foreground text-background inline-block rounded-full px-4 py-1.5 text-sm font-semibold tracking-wide",
67
+ className
68
+ ),
69
+ children
70
+ }
71
+ );
72
+ }
73
+ function SlideHeaderBadge({ children, className }) {
74
+ 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
+ }
76
+ function SlideCode({ children, className, title }) {
77
+ const html = highlight(children);
78
+ return /* @__PURE__ */ jsxs("div", { className: cn("w-full max-w-2xl", className), children: [
79
+ title && /* @__PURE__ */ jsx("div", { className: "text-muted-foreground mb-2 text-xs font-medium tracking-wider uppercase", children: title }),
80
+ /* @__PURE__ */ jsx("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", children: /* @__PURE__ */ jsx("code", { dangerouslySetInnerHTML: { __html: html } }) })
81
+ ] });
82
+ }
83
+ function SlideList({ children, className }) {
84
+ return /* @__PURE__ */ jsx("ul", { className: cn("flex flex-col gap-4 text-left", className), children });
85
+ }
86
+ function SlideListItem({ children, className }) {
87
+ return /* @__PURE__ */ jsxs("li", { className: cn("text-foreground/70 flex items-start gap-3 text-lg sm:text-xl", className), children: [
88
+ /* @__PURE__ */ jsx("span", { className: "bg-foreground/40 mt-2 block h-1.5 w-1.5 shrink-0 rounded-full", "aria-hidden": true }),
89
+ /* @__PURE__ */ jsx("span", { children })
90
+ ] });
91
+ }
92
+ function SlideNote({ children, className }) {
93
+ return /* @__PURE__ */ jsx("p", { className: cn("text-muted-foreground/50 mt-4 text-sm", className), children });
94
+ }
95
+ function SlideDemo({
96
+ children,
97
+ className,
98
+ label
99
+ }) {
100
+ return /* @__PURE__ */ jsxs("div", { "data-slide-interactive": true, className: cn("w-full max-w-2xl", className), children: [
101
+ label && /* @__PURE__ */ jsx("div", { className: "text-muted-foreground mb-2 text-xs font-medium tracking-wider uppercase", children: label }),
102
+ /* @__PURE__ */ jsx("div", { className: "border-foreground/10 bg-foreground/[0.03] border p-6", children: /* @__PURE__ */ jsx(SlideDemoContent, { children }) })
103
+ ] });
104
+ }
105
+ function SlideStatementList({ children, className }) {
106
+ return /* @__PURE__ */ jsx("div", { className: cn("flex w-full flex-col", className), children });
107
+ }
108
+ function SlideStatement({
109
+ title,
110
+ description,
111
+ className
112
+ }) {
113
+ return /* @__PURE__ */ jsxs("div", { className: cn("border-foreground/10 border-t px-8 py-8 last:border-b sm:px-12 md:px-16", className), children: [
114
+ /* @__PURE__ */ jsx("h3", { className: "text-foreground text-lg font-bold sm:text-xl md:text-2xl", children: title }),
115
+ description && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground mt-1 text-sm sm:text-base", children: description })
116
+ ] });
117
+ }
118
+ function SlideSpeaker({ name, title, className }) {
119
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-4", className), children: [
120
+ /* @__PURE__ */ jsx("div", { className: "bg-muted h-12 w-12 shrink-0 rounded-full", "aria-hidden": true }),
121
+ /* @__PURE__ */ jsxs("div", { children: [
122
+ /* @__PURE__ */ jsx("p", { className: "text-foreground/90 text-sm font-medium tracking-widest uppercase", children: name }),
123
+ /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm tracking-wider uppercase", children: title })
124
+ ] })
125
+ ] });
126
+ }
127
+ function SlideSpeakerGrid({ children, className }) {
128
+ return /* @__PURE__ */ jsx("div", { className: cn("grid grid-cols-1 gap-6 sm:grid-cols-2", className), children });
129
+ }
130
+ function SlideSpeakerList({ children, className }) {
131
+ return /* @__PURE__ */ jsx("div", { className: cn("flex flex-col gap-6", className), children });
132
+ }
133
+ export {
134
+ Slide,
135
+ SlideBadge,
136
+ SlideCode,
137
+ SlideDemo,
138
+ SlideHeaderBadge,
139
+ SlideList,
140
+ SlideListItem,
141
+ SlideNote,
142
+ SlideSpeaker,
143
+ SlideSpeakerGrid,
144
+ SlideSpeakerList,
145
+ SlideSplitLayout,
146
+ SlideStatement,
147
+ SlideStatementList,
148
+ SlideSubtitle,
149
+ SlideTitle
150
+ };
151
+ //# sourceMappingURL=primitives.js.map
@@ -0,0 +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 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 html = 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":[]}
@@ -0,0 +1,8 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { SlideDeckConfig } from './types.js';
3
+
4
+ declare function SlideDeck({ children, slides, basePath, showProgress, showCounter, className, }: SlideDeckConfig & {
5
+ children: React.ReactNode;
6
+ }): react_jsx_runtime.JSX.Element;
7
+
8
+ export { SlideDeck };
@@ -0,0 +1,121 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { usePathname, useRouter } from "next/navigation";
4
+ import { addTransitionType, useCallback, useEffect, useTransition, ViewTransition } from "react";
5
+ import { cn } from "./cn";
6
+ function SlideDeck({
7
+ children,
8
+ slides,
9
+ basePath = "/slides",
10
+ showProgress = true,
11
+ showCounter = true,
12
+ className
13
+ }) {
14
+ const router = useRouter();
15
+ const pathname = usePathname();
16
+ const [isPending, startTransition] = useTransition();
17
+ const total = slides.length;
18
+ const slideRoutePattern = new RegExp(`^${basePath}/(\\d+)$`);
19
+ const isSlideRoute = slideRoutePattern.test(pathname);
20
+ const current = (() => {
21
+ const match = pathname.match(slideRoutePattern);
22
+ return match ? Number(match[1]) - 1 : 0;
23
+ })();
24
+ const goTo = useCallback(
25
+ (index) => {
26
+ const clamped = Math.max(0, Math.min(index, total - 1));
27
+ if (clamped === current) return;
28
+ startTransition(() => {
29
+ addTransitionType(clamped > current ? "slide-forward" : "slide-back");
30
+ router.push(`${basePath}/${clamped + 1}`);
31
+ });
32
+ },
33
+ [basePath, current, router, startTransition, total]
34
+ );
35
+ useEffect(() => {
36
+ if (!isSlideRoute) return;
37
+ if (current > 0) router.prefetch(`${basePath}/${current}`);
38
+ if (current < total - 1) router.prefetch(`${basePath}/${current + 2}`);
39
+ }, [basePath, current, isSlideRoute, router, total]);
40
+ useEffect(() => {
41
+ if (!isSlideRoute) return;
42
+ function onKeyDown(e) {
43
+ const target = e.target;
44
+ if (target.closest("[data-slide-interactive]") || target.matches('input, textarea, select, [contenteditable="true"]')) {
45
+ return;
46
+ }
47
+ if (e.key === "ArrowRight" || e.key === " ") {
48
+ e.preventDefault();
49
+ goTo(current + 1);
50
+ } else if (e.key === "ArrowLeft") {
51
+ e.preventDefault();
52
+ goTo(current - 1);
53
+ }
54
+ }
55
+ window.addEventListener("keydown", onKeyDown);
56
+ return () => window.removeEventListener("keydown", onKeyDown);
57
+ }, [current, goTo, isSlideRoute]);
58
+ useEffect(() => {
59
+ const prev = document.body.style.overflow;
60
+ document.body.style.overflow = "hidden";
61
+ return () => {
62
+ document.body.style.overflow = prev;
63
+ };
64
+ }, []);
65
+ return /* @__PURE__ */ jsx(ViewTransition, { exit: "deck-unveil", children: /* @__PURE__ */ jsxs(
66
+ "div",
67
+ {
68
+ id: "slide-deck",
69
+ className: cn(
70
+ "bg-background text-foreground fixed inset-0 z-50 overflow-hidden font-sans select-none",
71
+ className
72
+ ),
73
+ "data-pending": isPending ? "" : void 0,
74
+ children: [
75
+ /* @__PURE__ */ jsx(
76
+ ViewTransition,
77
+ {
78
+ enter: {
79
+ default: "slide-from-right",
80
+ "slide-back": "slide-from-left",
81
+ "slide-forward": "slide-from-right"
82
+ },
83
+ exit: {
84
+ default: "slide-to-left",
85
+ "slide-back": "slide-to-right",
86
+ "slide-forward": "slide-to-left"
87
+ },
88
+ children: /* @__PURE__ */ jsx("div", { children })
89
+ },
90
+ pathname
91
+ ),
92
+ isSlideRoute && showProgress && /* @__PURE__ */ jsx(
93
+ "div",
94
+ {
95
+ className: "fixed bottom-8 left-1/2 z-50 flex -translate-x-1/2 items-center gap-1.5",
96
+ "aria-label": "Slide progress",
97
+ children: Array.from({ length: total }).map((_, i) => /* @__PURE__ */ jsx(
98
+ "div",
99
+ {
100
+ className: cn(
101
+ "h-1 transition-all duration-300",
102
+ i === current ? "bg-foreground w-6" : "bg-foreground/20 w-1"
103
+ )
104
+ },
105
+ i
106
+ ))
107
+ }
108
+ ),
109
+ isSlideRoute && showCounter && /* @__PURE__ */ jsxs("div", { className: "text-foreground/30 fixed right-8 bottom-8 z-50 font-mono text-xs tracking-wider", children: [
110
+ current + 1,
111
+ " / ",
112
+ total
113
+ ] })
114
+ ]
115
+ }
116
+ ) });
117
+ }
118
+ export {
119
+ SlideDeck
120
+ };
121
+ //# sourceMappingURL=slide-deck.js.map
@@ -0,0 +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 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 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":"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","names":[]}
@@ -0,0 +1,7 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ declare function SlideDemoContent({ children }: {
4
+ children: React.ReactNode;
5
+ }): react_jsx_runtime.JSX.Element;
6
+
7
+ export { SlideDemoContent };
@@ -0,0 +1,24 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { useEffect, useRef, useState } from "react";
4
+ function SlideDemoContent({ children }) {
5
+ const ref = useRef(null);
6
+ const [minHeight, setMinHeight] = useState(void 0);
7
+ useEffect(() => {
8
+ const el = ref.current;
9
+ if (!el) return;
10
+ const observer = new ResizeObserver((entries) => {
11
+ for (const entry of entries) {
12
+ const height = entry.borderBoxSize[0].blockSize;
13
+ setMinHeight((prev) => prev === void 0 ? height : Math.max(prev, height));
14
+ }
15
+ });
16
+ observer.observe(el);
17
+ return () => observer.disconnect();
18
+ }, []);
19
+ return /* @__PURE__ */ jsx("div", { ref, style: minHeight !== void 0 ? { minHeight } : void 0, children });
20
+ }
21
+ export {
22
+ SlideDemoContent
23
+ };
24
+ //# sourceMappingURL=slide-demo-content.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/slide-demo-content.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, useRef, useState } from 'react';\n\nexport function SlideDemoContent({ children }: { children: React.ReactNode }) {\n const ref = useRef<HTMLDivElement>(null);\n const [minHeight, setMinHeight] = useState<number | undefined>(undefined);\n\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n\n const observer = new ResizeObserver(entries => {\n for (const entry of entries) {\n const height = entry.borderBoxSize[0].blockSize;\n setMinHeight(prev => (prev === undefined ? height : Math.max(prev, height)));\n }\n });\n observer.observe(el);\n return () => observer.disconnect();\n }, []);\n\n return (\n <div ref={ref} style={minHeight !== undefined ? { minHeight } : undefined}>\n {children}\n </div>\n );\n}\n"],"mappings":";AAuBI;AArBJ,SAAS,WAAW,QAAQ,gBAAgB;AAErC,SAAS,iBAAiB,EAAE,SAAS,GAAkC;AAC5E,QAAM,MAAM,OAAuB,IAAI;AACvC,QAAM,CAAC,WAAW,YAAY,IAAI,SAA6B,MAAS;AAExE,YAAU,MAAM;AACd,UAAM,KAAK,IAAI;AACf,QAAI,CAAC,GAAI;AAET,UAAM,WAAW,IAAI,eAAe,aAAW;AAC7C,iBAAW,SAAS,SAAS;AAC3B,cAAM,SAAS,MAAM,cAAc,CAAC,EAAE;AACtC,qBAAa,UAAS,SAAS,SAAY,SAAS,KAAK,IAAI,MAAM,MAAM,CAAE;AAAA,MAC7E;AAAA,IACF,CAAC;AACD,aAAS,QAAQ,EAAE;AACnB,WAAO,MAAM,SAAS,WAAW;AAAA,EACnC,GAAG,CAAC,CAAC;AAEL,SACE,oBAAC,SAAI,KAAU,OAAO,cAAc,SAAY,EAAE,UAAU,IAAI,QAC7D,UACH;AAEJ;","names":[]}
@@ -0,0 +1,11 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { SlideLinkVariant } from './types.js';
3
+
4
+ declare function SlideLink({ href, children, className, variant, }: {
5
+ href: string;
6
+ children: React.ReactNode;
7
+ className?: string;
8
+ variant?: SlideLinkVariant;
9
+ }): react_jsx_runtime.JSX.Element;
10
+
11
+ export { SlideLink };
@@ -0,0 +1,28 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import Link from "next/link";
3
+ import { cn } from "./cn";
4
+ function SlideLink({
5
+ href,
6
+ children,
7
+ className,
8
+ variant = "primary"
9
+ }) {
10
+ return /* @__PURE__ */ jsx(
11
+ Link,
12
+ {
13
+ href,
14
+ className: cn(
15
+ "nxs-slide-link inline-flex items-center justify-center gap-2 rounded-md px-4 py-2 text-sm font-medium tracking-wide transition-all",
16
+ variant === "primary" && "bg-primary text-primary-foreground hover:bg-primary/80",
17
+ variant === "ghost" && "border-border border bg-transparent hover:bg-muted",
18
+ "mt-2",
19
+ className
20
+ ),
21
+ children
22
+ }
23
+ );
24
+ }
25
+ export {
26
+ SlideLink
27
+ };
28
+ //# sourceMappingURL=slide-link.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/slide-link.tsx"],"sourcesContent":["import Link from 'next/link';\nimport { cn } from './cn';\nimport type { SlideLinkVariant } from './types';\n\nexport function SlideLink({\n href,\n children,\n className,\n variant = 'primary',\n}: {\n href: string;\n children: React.ReactNode;\n className?: string;\n variant?: SlideLinkVariant;\n}) {\n return (\n <Link\n href={href}\n className={cn(\n 'nxs-slide-link inline-flex items-center justify-center gap-2 rounded-md px-4 py-2 text-sm font-medium tracking-wide transition-all',\n variant === 'primary' && 'bg-primary text-primary-foreground hover:bg-primary/80',\n variant === 'ghost' && 'border-border border bg-transparent hover:bg-muted',\n 'mt-2',\n className,\n )}\n >\n {children}\n </Link>\n );\n}\n"],"mappings":"AAgBI;AAhBJ,OAAO,UAAU;AACjB,SAAS,UAAU;AAGZ,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AACZ,GAKG;AACD,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,WAAW;AAAA,QACT;AAAA,QACA,YAAY,aAAa;AAAA,QACzB,YAAY,WAAW;AAAA,QACvB;AAAA,QACA;AAAA,MACF;AAAA,MAEC;AAAA;AAAA,EACH;AAEJ;","names":[]}
@@ -0,0 +1,105 @@
1
+ /* nextjs-slides — Slide transition animations and code theme tokens */
2
+
3
+ /* sugar-high code theme — light */
4
+ :root {
5
+ --sh-class: #7200c4;
6
+ --sh-identifier: #171717;
7
+ --sh-keyword: #b32c62;
8
+ --sh-string: #397c3b;
9
+ --sh-property: #005ee9;
10
+ --sh-entity: #005ee9;
11
+ --sh-jsxliterals: #171717;
12
+ --sh-sign: #666;
13
+ --sh-comment: #666666;
14
+ }
15
+
16
+ /* sugar-high code theme — dark */
17
+ .dark {
18
+ --sh-class: #b675f1;
19
+ --sh-identifier: #ededed;
20
+ --sh-keyword: #f05b8d;
21
+ --sh-string: #58c760;
22
+ --sh-property: #62a6ff;
23
+ --sh-entity: #62a6ff;
24
+ --sh-jsxliterals: #ededed;
25
+ --sh-sign: #a1a1a1;
26
+ --sh-comment: #a1a1a1;
27
+ }
28
+
29
+ /* Slide transition keyframes */
30
+ @keyframes nxs-fade {
31
+ from {
32
+ filter: blur(3px);
33
+ opacity: 0;
34
+ }
35
+ to {
36
+ filter: blur(0);
37
+ opacity: 1;
38
+ }
39
+ }
40
+
41
+ @keyframes nxs-slide {
42
+ from {
43
+ translate: var(--nxs-slide-offset);
44
+ }
45
+ to {
46
+ translate: 0;
47
+ }
48
+ }
49
+
50
+ @keyframes nxs-slide-in {
51
+ from {
52
+ opacity: 0;
53
+ transform: translateX(var(--nxs-slide-offset, 60px));
54
+ }
55
+ }
56
+
57
+ @keyframes nxs-slide-out {
58
+ to {
59
+ opacity: 0;
60
+ transform: translateX(var(--nxs-slide-offset, -60px));
61
+ }
62
+ }
63
+
64
+ @keyframes nxs-deck-unveil {
65
+ to {
66
+ opacity: 0;
67
+ transform: scale(1.03);
68
+ }
69
+ }
70
+
71
+ /* Reset default view transition animations */
72
+ ::view-transition-old(*) {
73
+ animation: none;
74
+ }
75
+ ::view-transition-new(*) {
76
+ animation: none;
77
+ }
78
+
79
+ /* Slide forward */
80
+ ::view-transition-new(.slide-from-right) {
81
+ --nxs-slide-offset: 60px;
82
+ animation: nxs-slide-in 200ms ease-out both;
83
+ }
84
+ ::view-transition-old(.slide-to-left) {
85
+ --nxs-slide-offset: -60px;
86
+ animation: nxs-slide-out 200ms ease-out both;
87
+ }
88
+
89
+ /* Slide backward */
90
+ ::view-transition-new(.slide-from-left) {
91
+ --nxs-slide-offset: -60px;
92
+ animation: nxs-slide-in 200ms ease-out both;
93
+ }
94
+ ::view-transition-old(.slide-to-right) {
95
+ --nxs-slide-offset: 60px;
96
+ animation: nxs-slide-out 200ms ease-out both;
97
+ }
98
+
99
+ /* Deck exit (leaving slide deck) */
100
+ ::view-transition-old(.deck-unveil) {
101
+ animation: nxs-deck-unveil 300ms ease-out both;
102
+ }
103
+ ::view-transition-new(.deck-unveil) {
104
+ animation: none;
105
+ }
@@ -0,0 +1,15 @@
1
+ type SlideAlign = 'center' | 'left';
2
+ type SlideLinkVariant = 'primary' | 'ghost';
3
+ interface SlideDeckConfig {
4
+ slides: React.ReactNode[];
5
+ /** Base path for slide URLs. Defaults to "/slides" */
6
+ basePath?: string;
7
+ /** Show progress dots at the bottom. Defaults to true */
8
+ showProgress?: boolean;
9
+ /** Show slide counter (e.g. "3 / 10"). Defaults to true */
10
+ showCounter?: boolean;
11
+ /** Additional className for the deck container */
12
+ className?: string;
13
+ }
14
+
15
+ export type { SlideAlign, SlideDeckConfig, SlideLinkVariant };
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "nextjs-slides",
3
+ "version": "0.1.0",
4
+ "description": "Composable slide deck primitives for Next.js — powered by React 19 ViewTransitions, Tailwind CSS, and sugar-high syntax highlighting.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js"
11
+ },
12
+ "./styles.css": "./dist/slides.css"
13
+ },
14
+ "main": "./dist/index.js",
15
+ "types": "./dist/index.d.ts",
16
+ "files": [
17
+ "dist",
18
+ "README.md"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsup",
22
+ "dev": "tsup --watch",
23
+ "typecheck": "tsc --noEmit",
24
+ "prepublishOnly": "npm run build"
25
+ },
26
+ "peerDependencies": {
27
+ "next": ">=15.0.0",
28
+ "react": ">=19.0.0",
29
+ "react-dom": ">=19.0.0",
30
+ "tailwindcss": ">=4.0.0"
31
+ },
32
+ "dependencies": {
33
+ "clsx": "^2.1.1",
34
+ "sugar-high": "^0.9.5",
35
+ "tailwind-merge": "^3.5.0"
36
+ },
37
+ "devDependencies": {
38
+ "@types/react": "^19",
39
+ "@types/react-dom": "^19",
40
+ "next": "^16.0.0",
41
+ "react": "^19.0.0",
42
+ "react-dom": "^19.0.0",
43
+ "tsup": "^8.4.0",
44
+ "typescript": "^5"
45
+ },
46
+ "keywords": [
47
+ "nextjs",
48
+ "slides",
49
+ "presentation",
50
+ "slide-deck",
51
+ "react",
52
+ "tailwind",
53
+ "view-transitions",
54
+ "sugar-high"
55
+ ],
56
+ "repository": {
57
+ "type": "git",
58
+ "url": "https://github.com/aurorascharff/nextjs-slides"
59
+ }
60
+ }