@thanh01.pmt/presentation-kit 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,548 @@
1
+ # @learnwell/presentation-kit
2
+
3
+ > Standalone **client-side** slide rendering engine. Import Marp markdown, get fully rendered 16:9 slides with syntax highlighting, Mermaid diagrams, and custom themes — **zero server roundtrip**.
4
+
5
+ ```
6
+ npm install @learnwell/presentation-kit
7
+ ```
8
+
9
+ ---
10
+
11
+ ## Table of Contents
12
+
13
+ - [Installation](#installation)
14
+ - [Imports — Two Subpaths](#imports--two-subpaths)
15
+ - [Quick Start](#quick-start)
16
+ - [Headless (Core Only)](#headless-core-only)
17
+ - [React UI (Ready-to-Use)](#react-ui-ready-to-use)
18
+ - [API Reference — Core](#api-reference--core)
19
+ - [`renderSlide()`](#renderslidemarkdown-options)
20
+ - [`renderSlideAsync()`](#renderslideasyncmarkdown-options)
21
+ - [`createEngine()`](#createengineconfig)
22
+ - [`defineTheme()`](#definetheme-definition)
23
+ - [`renderMermaidDiagrams()`](#rendermermaidchartscontainer-theme)
24
+ - [`detectMermaidBlocks()`](#detectmermaidblockhtml)
25
+ - [`highlightCodeBlocks()`](#highlightcodeblockhtml-theme)
26
+ - [`convertTextArrows()`](#converttextarrowstext)
27
+ - [`extractMarpSource()`](#extractmarpsourceraw)
28
+ - [API Reference — React](#api-reference--react)
29
+ - [`<SlideViewer />`](#slideviewer)
30
+ - [`<PresentationView />`](#presentationview)
31
+ - [`<DeckView />`](#deckview)
32
+ - [`<ThemeSwitcher />`](#themeswitcher)
33
+ - [`<NavigationBar />`](#navigationbar)
34
+ - [`useSlideState()`](#useslidestate)
35
+ - [Theme System](#theme-system)
36
+ - [Architecture](#architecture)
37
+ - [Bundle Size](#bundle-size)
38
+ - [Package Structure](#package-structure)
39
+ - [Development](#development)
40
+ - [License](#license)
41
+
42
+ ---
43
+
44
+ ## Installation
45
+
46
+ ```bash
47
+ # Monorepo
48
+ pnpm add @learnwell/presentation-kit --workspace
49
+
50
+ # Standalone
51
+ npm install @learnwell/presentation-kit
52
+ ```
53
+
54
+ **Peer dependencies (optional — only needed for React subpath):**
55
+
56
+ ```bash
57
+ npm install react react-dom
58
+ ```
59
+
60
+ ---
61
+
62
+ ## Imports — Two Subpaths
63
+
64
+ The package provides **two import paths** so you only load what you need:
65
+
66
+ | Import path | What you get | Size | When to use |
67
+ |---|---|---|---|
68
+ | `@learnwell/presentation-kit` | Core engine (headless) | ~36KB | You build your own UI |
69
+ | `@learnwell/presentation-kit/react` | Core engine + React UI components | ~52KB | You want ready-to-use viewer |
70
+
71
+ ```ts
72
+ // ✅ Headless — no React dependency
73
+ import { renderSlide, defineTheme } from '@learnwell/presentation-kit';
74
+
75
+ // ✅ React — full UI components
76
+ import { SlideViewer, ThemeSwitcher } from '@learnwell/presentation-kit/react';
77
+
78
+ // ✅ Mix — core engine + custom UI using React hooks
79
+ import { renderSlide, useSlideState } from '@learnwell/presentation-kit/react';
80
+ ```
81
+
82
+ > **Tree-shaking:** If you only import `renderSlide` from the React subpath, bundlers will exclude unused UI components.
83
+
84
+ ---
85
+
86
+ ## Quick Start
87
+
88
+ ### Headless (Core Only)
89
+
90
+ For apps that want full control over the UI:
91
+
92
+ ```tsx
93
+ import { renderSlide } from '@learnwell/presentation-kit';
94
+
95
+ const result = renderSlide(marpMarkdown, { theme: 'dark' });
96
+
97
+ // Inject into DOM
98
+ container.innerHTML = `<style>${result.css}${result.themeCss}</style>${result.html}`;
99
+ ```
100
+
101
+ ### React UI (Ready-to-Use)
102
+
103
+ For apps that want a complete slide viewer out of the box:
104
+
105
+ ```tsx
106
+ import { SlideViewer } from '@learnwell/presentation-kit/react';
107
+
108
+ function MyPage() {
109
+ return (
110
+ <SlideViewer
111
+ markdown={marpMarkdown}
112
+ initialTheme="dark"
113
+ showThemeSwitcher
114
+ showNavigation
115
+ />
116
+ );
117
+ }
118
+ ```
119
+
120
+ That's it. You get:
121
+ - Theme switching (Dark / Navy / Light)
122
+ - Presentation mode (single slide + keyboard nav)
123
+ - Deck mode (vertical scroll)
124
+ - Mermaid diagram rendering
125
+ - Copy markdown button
126
+
127
+ ---
128
+
129
+ ## API Reference — Core
130
+
131
+ ### `renderSlide(markdown, options?)`
132
+
133
+ **Synchronous** slide rendering. Returns instantly.
134
+
135
+ ```ts
136
+ function renderSlide(markdown: string, options?: SlideRenderOptions): SlideRenderResult;
137
+ ```
138
+
139
+ | Param | Type | Default | Description |
140
+ |---|---|---|---|
141
+ | `markdown` | `string` | — | Marp markdown (with `---` separators) |
142
+ | `options.theme` | `'dark' \| 'light' \| 'navy' \| SlideTheme` | `'dark'` | Theme to apply |
143
+ | `options.enableShiki` | `boolean` | `true` | Detect code blocks (sync: detection only) |
144
+ | `options.enableMermaid` | `boolean` | `true` | Detect mermaid blocks |
145
+ | `options.containerId` | `string` | `'marp-container'` | CSS isolation ID |
146
+
147
+ **Returns:** `{ html, css, themeCss, slideCount, mermaidBlocks }`
148
+
149
+ ---
150
+
151
+ ### `renderSlideAsync(markdown, options?)`
152
+
153
+ **Async** render with Shiki syntax highlighting applied.
154
+
155
+ ```ts
156
+ function renderSlideAsync(markdown: string, options?: SlideRenderOptions): Promise<SlideRenderAsyncResult>;
157
+ ```
158
+
159
+ **Returns:** `{ ...SlideRenderResult, processedHtml }` — HTML with Shiki-highlighted code blocks.
160
+
161
+ ---
162
+
163
+ ### `createEngine(config?)`
164
+
165
+ Create a reusable engine instance with default options.
166
+
167
+ ```ts
168
+ const engine = createEngine({ theme: 'navy' });
169
+ const result = engine.render(markdown); // defaults to navy theme
170
+ ```
171
+
172
+ ---
173
+
174
+ ### `defineTheme(definition)`
175
+
176
+ Define a custom theme with full token control.
177
+
178
+ ```ts
179
+ const myTheme = defineTheme({
180
+ name: 'corporate',
181
+ tokens: { background: '#fff', text: '#1a1a2e', /* ...24 tokens */ },
182
+ });
183
+ renderSlide(markdown, { theme: myTheme });
184
+ ```
185
+
186
+ ---
187
+
188
+ ### `renderMermaidDiagrams(container, theme)`
189
+
190
+ Render mermaid diagrams in a DOM container. Browser only (returns early in SSR).
191
+
192
+ ```ts
193
+ await renderMermaidDiagrams(document.getElementById('slides'), 'dark');
194
+ ```
195
+
196
+ ---
197
+
198
+ ### `detectMermaidBlocks(html)`
199
+
200
+ Detect mermaid blocks in rendered HTML (sync, no DOM needed).
201
+
202
+ ```ts
203
+ const blocks = detectMermaidBlocks(html);
204
+ // blocks: [{ index: 0, code: 'graph TD...', sanitizedCode: '...' }]
205
+ ```
206
+
207
+ ---
208
+
209
+ ### `highlightCodeBlocks(html, theme?)`
210
+
211
+ Async Shiki post-processing for code blocks.
212
+
213
+ ```ts
214
+ const highlighted = await highlightCodeBlocks(html, 'dark');
215
+ ```
216
+
217
+ ---
218
+
219
+ ### `convertTextArrows(text)`
220
+
221
+ Convert text arrows (`->`, `-->`, `=>`) to Unicode arrows. Preserves code fences.
222
+
223
+ ---
224
+
225
+ ### `extractMarpSource(raw)`
226
+
227
+ Extract clean Marp source from wrapped markdown (strips ` ```markdown ` fences, conversational preamble).
228
+
229
+ ---
230
+
231
+ ## API Reference — React
232
+
233
+ ### `<SlideViewer />`
234
+
235
+ Complete slide viewer with all features. The main entry point for React apps.
236
+
237
+ ```tsx
238
+ import { SlideViewer } from '@learnwell/presentation-kit/react';
239
+
240
+ <SlideViewer
241
+ markdown={marpMarkdown} // Marp markdown source
242
+ initialTheme="dark" // 'dark' | 'light' | 'navy' | SlideTheme
243
+ initialMode="presentation" // 'presentation' | 'deck'
244
+ showThemeSwitcher={true} // Show theme toggle buttons
245
+ showNavigation={true} // Show prev/next/mode controls
246
+ enableKeyboard={true} // Enable keyboard navigation
247
+ themeOptions={[...]} // Custom theme options for switcher
248
+ onThemeChange={(theme) => {}} // Theme change callback
249
+ onSlideChange={(index) => {}} // Slide change callback
250
+ onModeChange={(mode) => {}} // Mode change callback
251
+ onRenderComplete={(result) => {}} // Render complete callback
252
+ className="my-viewer" // CSS class for root container
253
+ />
254
+ ```
255
+
256
+ **Features:**
257
+ - Theme switching (Dark / Navy / Light)
258
+ - Presentation mode (single slide, keyboard: ← → Space Home End)
259
+ - Deck mode (vertical scroll of all slides)
260
+ - Mermaid diagram auto-rendering
261
+ - Copy markdown button
262
+
263
+ ---
264
+
265
+ ### `<PresentationView />`
266
+
267
+ Single slide display. Use this when you want to build your own controls.
268
+
269
+ ```tsx
270
+ import { PresentationView } from '@learnwell/presentation-kit/react';
271
+
272
+ <PresentationView
273
+ html={result.html}
274
+ css={result.css}
275
+ themeCss={result.themeCss}
276
+ currentIndex={0}
277
+ slideCount={5}
278
+ />
279
+ ```
280
+
281
+ ---
282
+
283
+ ### `<DeckView />`
284
+
285
+ Vertical scroll of all slides. Use this when you want to build your own deck UI.
286
+
287
+ ```tsx
288
+ import { DeckView } from '@learnwell/presentation-kit/react';
289
+
290
+ <DeckView
291
+ html={result.html}
292
+ css={result.css}
293
+ themeCss={result.themeCss}
294
+ slideCount={5}
295
+ />
296
+ ```
297
+
298
+ ---
299
+
300
+ ### `<ThemeSwitcher />`
301
+
302
+ Theme toggle buttons. Use this when you want to add theme switching to a custom UI.
303
+
304
+ ```tsx
305
+ import { ThemeSwitcher } from '@learnwell/presentation-kit/react';
306
+
307
+ <ThemeSwitcher
308
+ currentTheme="dark"
309
+ onThemeChange={setTheme}
310
+ themes={[
311
+ { id: 'dark', label: 'Dark' },
312
+ { id: 'navy', label: 'Navy' },
313
+ { id: 'light', label: 'Light' },
314
+ ]}
315
+ />
316
+ ```
317
+
318
+ ---
319
+
320
+ ### `<NavigationBar />`
321
+
322
+ Slide navigation controls (prev/next, selector, mode toggle, copy).
323
+
324
+ ```tsx
325
+ import { NavigationBar } from '@learnwell/presentation-kit/react';
326
+
327
+ <NavigationBar
328
+ currentIndex={2}
329
+ slideCount={10}
330
+ onNavigate={setCurrentIndex}
331
+ mode="presentation"
332
+ onModeChange={setMode}
333
+ markdown={rawMarkdown}
334
+ showCopy={true}
335
+ />
336
+ ```
337
+
338
+ ---
339
+
340
+ ### `useSlideState()`
341
+
342
+ Centralized hook for slide state management. Use this when building a fully custom viewer.
343
+
344
+ ```tsx
345
+ import { useSlideState } from '@learnwell/presentation-kit/react';
346
+
347
+ function CustomViewer({ markdown }) {
348
+ const {
349
+ currentIndex, // current slide index
350
+ mode, // 'presentation' | 'deck'
351
+ theme, // current theme
352
+ result, // render result (null before first render)
353
+ slideCount, // total slides
354
+ isReady, // true after first render
355
+ goToSlide, // (index) => void
356
+ nextSlide, // () => void
357
+ prevSlide, // () => void
358
+ setMode, // (mode) => void
359
+ setTheme, // (theme) => void
360
+ } = useSlideState({
361
+ markdown,
362
+ initialTheme: 'dark',
363
+ initialMode: 'presentation',
364
+ });
365
+
366
+ // Full control over rendering and UI
367
+ return <div dangerouslySetInnerHTML={{ __html: result?.html ?? '' }} />;
368
+ }
369
+ ```
370
+
371
+ ---
372
+
373
+ ## Theme System
374
+
375
+ ### Built-in Themes
376
+
377
+ | Theme | Background | Use Case |
378
+ |---|---|---|
379
+ | `'dark'` | `#14151b` | Developer tools, modern UI |
380
+ | `'light'` | `#ffffff` | Classroom projectors, print |
381
+ | `'navy'` | `#0f172a` | STEM presentations |
382
+
383
+ ### Custom Theme
384
+
385
+ ```ts
386
+ import { defineTheme, renderSlide } from '@learnwell/presentation-kit';
387
+
388
+ const myTheme = defineTheme({
389
+ name: 'corporate',
390
+ tokens: {
391
+ background: '#ffffff',
392
+ text: '#1a1a2e',
393
+ heading: '#1a1a2e',
394
+ bold: '#1a1a2e',
395
+ link: '#2563eb',
396
+ codeBg: '#f5f5f5',
397
+ codeText: '#27272a',
398
+ inlineCodeBg: '#f5f5f5',
399
+ inlineCodeText: '#27272a',
400
+ codeBorder: 'rgba(0, 0, 0, 0.08)',
401
+ quoteBg: '#f8f9fa',
402
+ quoteBorder: 'rgba(0, 0, 0, 0.15)',
403
+ quoteText: '#27272a',
404
+ tableHeaderBg: '#f8fafc',
405
+ tableBorder: '#e2e8f0',
406
+ tableRowHover: 'rgba(0, 0, 0, 0.02)',
407
+ tableText: '#3f3f46',
408
+ cardBorder: '1px solid rgba(0, 0, 0, 0.08)',
409
+ cardShadow: '0 4px 16px -2px rgba(0, 0, 0, 0.05)',
410
+ meta: '#71717a',
411
+ mermaidDark: false,
412
+ mermaidVariables: { /* mermaid theme vars */ },
413
+ mermaidThemeCSS: '/* mermaid CSS overrides */',
414
+ },
415
+ });
416
+
417
+ renderSlide(markdown, { theme: myTheme });
418
+ ```
419
+
420
+ ---
421
+
422
+ ## Architecture
423
+
424
+ ```
425
+ @learnwell/presentation-kit ← Headless core (no React)
426
+
427
+ ├── renderSlide() ← Sync render
428
+ ├── renderSlideAsync() ← Async render (Shiki)
429
+ ├── createEngine() ← Reusable instance
430
+ ├── defineTheme() ← Custom themes
431
+ ├── renderMermaidDiagrams() ← DOM mermaid rendering
432
+ ├── detectMermaidBlocks() ← Mermaid detection
433
+ ├── highlightCodeBlocks() ← Shiki post-process
434
+ ├── convertTextArrows() ← Arrow utility
435
+ └── extractMarpSource() ← Source cleanup
436
+
437
+ @learnwell/presentation-kit/react ← React UI (optional)
438
+
439
+ ├── <SlideViewer /> ← Complete viewer (all-in-one)
440
+ ├── <PresentationView /> ← Single slide display
441
+ ├── <DeckView /> ← Vertical scroll
442
+ ├── <ThemeSwitcher /> ← Theme toggle buttons
443
+ ├── <NavigationBar /> ← Navigation controls
444
+ └── useSlideState() ← State management hook
445
+ ```
446
+
447
+ **Render pipeline:**
448
+
449
+ ```
450
+ markdown
451
+ → extractMarpSource() strip wrappers
452
+ → convertTextArrows() → → →, --> → ⟶
453
+ → marp.render() client-side Marp-core
454
+ → detectMermaidBlocks() find mermaid syntax
455
+ → [async] Shiki post-process code blocks
456
+ → generateScopedCss() theme typography CSS
457
+ → return { html, css, themeCss, slideCount, mermaidBlocks }
458
+ ```
459
+
460
+ ---
461
+
462
+ ## Bundle Size
463
+
464
+ | Import | Sync | Lazy | Total |
465
+ |---|---|---|---|
466
+ | Core only (`@learnwell/presentation-kit`) | ~36KB | — | **~36KB** |
467
+ | React UI (`@learnwell/presentation-kit/react`) | ~52KB | — | **~52KB** |
468
+ | + Shiki | +0 | ~3MB | ~3MB |
469
+ | + Mermaid | +0 | ~2MB | ~2MB |
470
+
471
+ > Core and React are bundled together in the React subpath. Shiki and Mermaid are lazy-loaded.
472
+
473
+ ---
474
+
475
+ ## Package Structure
476
+
477
+ ```
478
+ packages/presentation-kit/
479
+ ├── src/
480
+ │ ├── core/ # Headless rendering engine
481
+ │ │ ├── render.ts
482
+ │ │ ├── arrow-converter.ts
483
+ │ │ └── source-extractor.ts
484
+ │ ├── plugins/ # Lazy-loaded plugins
485
+ │ │ ├── shiki.ts
486
+ │ │ └── mermaid.ts
487
+ │ ├── themes/ # Theme system
488
+ │ │ ├── dark.ts / light.ts / navy.ts
489
+ │ │ ├── scoped-css.ts
490
+ │ │ └── index.ts
491
+ │ ├── react/ # React UI (subpath export)
492
+ │ │ ├── components/
493
+ │ │ │ ├── SlideViewer.tsx
494
+ │ │ │ ├── PresentationView.tsx
495
+ │ │ │ ├── DeckView.tsx
496
+ │ │ │ ├── ThemeSwitcher.tsx
497
+ │ │ │ └── NavigationBar.tsx
498
+ │ │ ├── hooks/
499
+ │ │ │ └── useSlideState.ts
500
+ │ │ ├── types.ts
501
+ │ │ └── index.ts
502
+ │ ├── engine.ts
503
+ │ ├── types.ts
504
+ │ └── index.ts # Core barrel export
505
+ ├── dist/ # Built output
506
+ ├── docs/ # Design docs
507
+ ├── package.json
508
+ └── README.md
509
+ ```
510
+
511
+ ---
512
+
513
+ ## Development
514
+
515
+ ```bash
516
+ pnpm install
517
+ pnpm typecheck
518
+ pnpm build
519
+ pnpm dev # watch mode
520
+ ```
521
+
522
+ ### Input Format
523
+
524
+ Marp-compatible markdown:
525
+
526
+ ```markdown
527
+ ---
528
+ marp: true
529
+ theme: default
530
+ paginate: true
531
+ ---
532
+
533
+ # Slide 1
534
+
535
+ ---
536
+
537
+ # Slide 2
538
+
539
+ ```python
540
+ print("Hello")
541
+ ```
542
+ ```
543
+
544
+ ---
545
+
546
+ ## License
547
+
548
+ MIT