@honeydeck/honeydeck 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/AGENTS.md +25 -0
- package/DEVELOPMENT.md +522 -0
- package/LICENSE +21 -0
- package/Readme.md +49 -0
- package/SPEC.md +88 -0
- package/docs/components.md +63 -0
- package/docs/configuration.md +91 -0
- package/docs/getting-started.md +116 -0
- package/docs/kit-authoring.md +207 -0
- package/docs/kits.md +387 -0
- package/docs/local-development.md +95 -0
- package/docs/mermaid.md +198 -0
- package/docs/mobile.md +108 -0
- package/docs/navigation.md +93 -0
- package/docs/next-steps.md +377 -0
- package/docs/pdf-export.md +91 -0
- package/docs/presenter-mode.md +104 -0
- package/docs/slides.md +130 -0
- package/docs/slidev-migration.md +42 -0
- package/docs/steps-and-reveals.md +171 -0
- package/package.json +134 -0
- package/skills/SPEC.md +21 -0
- package/skills/honeydeck/SKILL.md +65 -0
- package/skills/presentation-writing/SKILL.md +75 -0
- package/skills/slidev-migration/SKILL.md +153 -0
- package/src/SPEC.md +89 -0
- package/src/assets.d.ts +30 -0
- package/src/cli/SPEC.md +230 -0
- package/src/cli/args.ts +3 -0
- package/src/cli/banner.ts +9 -0
- package/src/cli/bin.js +5 -0
- package/src/cli/build.ts +229 -0
- package/src/cli/deck-path.ts +32 -0
- package/src/cli/dev.ts +263 -0
- package/src/cli/index.ts +126 -0
- package/src/cli/init.ts +369 -0
- package/src/cli/pdf.ts +923 -0
- package/src/cli/skill.ts +75 -0
- package/src/cli/templates/SPEC.md +70 -0
- package/src/cli/templates/deck-mdx.ts +15 -0
- package/src/cli/templates/package-json.ts +36 -0
- package/src/cli/templates/sparkle-button.ts +15 -0
- package/src/cli/templates/starter/components/SparkleButton.tsx +84 -0
- package/src/cli/templates/starter/deck.mdx +153 -0
- package/src/cli/templates/starter/styles.css +14 -0
- package/src/cli/templates/styles-css.ts +14 -0
- package/src/defaults.ts +1 -0
- package/src/layouts/ColorModeImage.tsx +55 -0
- package/src/layouts/SPEC.md +393 -0
- package/src/layouts/SlideFrame.tsx +48 -0
- package/src/layouts/bee/Blank.tsx +12 -0
- package/src/layouts/bee/Cover.tsx +70 -0
- package/src/layouts/bee/Default.tsx +42 -0
- package/src/layouts/bee/Image/Image.tsx +151 -0
- package/src/layouts/bee/Image/placeholder-dark.webp +0 -0
- package/src/layouts/bee/Image/placeholder-vertical-dark.webp +0 -0
- package/src/layouts/bee/Image/placeholder-vertical.webp +0 -0
- package/src/layouts/bee/Image/placeholder.webp +0 -0
- package/src/layouts/bee/ImageLeft.tsx +27 -0
- package/src/layouts/bee/ImageRight.tsx +27 -0
- package/src/layouts/bee/ImageSide.tsx +107 -0
- package/src/layouts/bee/Section.tsx +40 -0
- package/src/layouts/bee/TwoCol.tsx +108 -0
- package/src/layouts/bee/index.ts +40 -0
- package/src/layouts/clean/Blank.tsx +12 -0
- package/src/layouts/clean/Cover.tsx +58 -0
- package/src/layouts/clean/Default.tsx +33 -0
- package/src/layouts/clean/Image/Image.tsx +103 -0
- package/src/layouts/clean/ImageLeft.tsx +27 -0
- package/src/layouts/clean/ImageRight.tsx +27 -0
- package/src/layouts/clean/ImageSide.tsx +113 -0
- package/src/layouts/clean/Section.tsx +35 -0
- package/src/layouts/clean/TwoCol.tsx +63 -0
- package/src/layouts/clean/index.ts +40 -0
- package/src/layouts/index.ts +60 -0
- package/src/layouts/placeholders.ts +9 -0
- package/src/layouts/utils.ts +13 -0
- package/src/remark/SPEC.md +49 -0
- package/src/remark/h1-extract.ts +124 -0
- package/src/remark/index.ts +4 -0
- package/src/remark/shiki-code-blocks.ts +325 -0
- package/src/remark/step-numbering.ts +412 -0
- package/src/runtime/Deck.tsx +533 -0
- package/src/runtime/SPEC.md +256 -0
- package/src/runtime/SlideCanvas.tsx +95 -0
- package/src/runtime/TimelineContext.tsx +122 -0
- package/src/runtime/app-shell/index.html +31 -0
- package/src/runtime/app-shell/main.tsx +42 -0
- package/src/runtime/aspectRatio.ts +34 -0
- package/src/runtime/colorMode.ts +23 -0
- package/src/runtime/components/BrowserFrame.tsx +233 -0
- package/src/runtime/components/Button.tsx +57 -0
- package/src/runtime/components/CodeBlock.tsx +210 -0
- package/src/runtime/components/ColorModeCycleButton.tsx +59 -0
- package/src/runtime/components/ErrorBoundary.tsx +125 -0
- package/src/runtime/components/Keyboard.tsx +87 -0
- package/src/runtime/components/ListStyle.tsx +203 -0
- package/src/runtime/components/NavBar.tsx +223 -0
- package/src/runtime/components/NavBarButton.tsx +47 -0
- package/src/runtime/components/NavBarDivider.tsx +3 -0
- package/src/runtime/components/Notes.tsx +171 -0
- package/src/runtime/components/Reveal.tsx +82 -0
- package/src/runtime/components/RevealGroup.tsx +193 -0
- package/src/runtime/components/SPEC.md +263 -0
- package/src/runtime/components/SlideNumberBadge.tsx +11 -0
- package/src/runtime/components/TimelineSteps.tsx +115 -0
- package/src/runtime/components/index.ts +55 -0
- package/src/runtime/index.ts +42 -0
- package/src/runtime/inputOwnership.ts +68 -0
- package/src/runtime/keyboardTarget.ts +7 -0
- package/src/runtime/lastSlideRoute.ts +56 -0
- package/src/runtime/navigation.ts +211 -0
- package/src/runtime/router.ts +157 -0
- package/src/runtime/slideData.ts +137 -0
- package/src/runtime/sync.ts +267 -0
- package/src/runtime/types.ts +182 -0
- package/src/runtime/useKeyboardNav.ts +138 -0
- package/src/runtime/useSwipeNav.ts +257 -0
- package/src/runtime/views/DocsView.tsx +74 -0
- package/src/runtime/views/OverviewView.tsx +386 -0
- package/src/runtime/views/PresenterNotesPanel.tsx +76 -0
- package/src/runtime/views/PresenterView.tsx +340 -0
- package/src/runtime/views/SPEC.md +152 -0
- package/src/runtime/views/docs/ComponentsTab.tsx +178 -0
- package/src/runtime/views/docs/DocsHeader.tsx +101 -0
- package/src/runtime/views/docs/Intro.tsx +20 -0
- package/src/runtime/views/docs/LayoutsTab.tsx +324 -0
- package/src/runtime/views/docs/ThemeTab.tsx +110 -0
- package/src/runtime/views/index.ts +7 -0
- package/src/runtime/views/overviewGrid.ts +106 -0
- package/src/runtime/views/presenterPreview.ts +27 -0
- package/src/runtime/virtual-modules.d.ts +98 -0
- package/src/theme/SPEC.md +179 -0
- package/src/theme/base.css +623 -0
- package/src/theme/bee.css +35 -0
- package/src/theme/clean.css +38 -0
- package/src/vite-plugin/SPEC.md +114 -0
- package/src/vite-plugin/component-doc-crawler.ts +350 -0
- package/src/vite-plugin/deck-loader.ts +148 -0
- package/src/vite-plugin/index.ts +373 -0
- package/src/vite-plugin/layout-demo-crawler.ts +802 -0
- package/src/vite-plugin/splitter.ts +353 -0
- package/src/vite-plugin/token-manifest.ts +163 -0
- package/src/vite-plugin/virtual-modules.ts +587 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
# Honeydeck Core Components Specification
|
|
2
|
+
|
|
3
|
+
> Observable behavior for public built-in React components.
|
|
4
|
+
|
|
5
|
+
## Core Components
|
|
6
|
+
|
|
7
|
+
All core components are explicit imports from the `'@honeydeck/honeydeck'` package. They are also exported from `@honeydeck/honeydeck/components`:
|
|
8
|
+
|
|
9
|
+
```mdx
|
|
10
|
+
import { Reveal, RevealGroup, TimelineSteps, ListStyle, Keyboard, BrowserFrame, Notes } from '@honeydeck/honeydeck'
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Injected fenced code blocks render through `HoneydeckCodeBlock` from the direct `@honeydeck/honeydeck/components/code-block` subpath. The component is not part of the public barrel, but transformed slide code blocks must show syntax-highlighted code and reveal an icon-only copy button on hover or keyboard focus. Copying writes the original fenced source text.
|
|
14
|
+
|
|
15
|
+
### Color Mode Controls
|
|
16
|
+
|
|
17
|
+
`@honeydeck/honeydeck/components` exports the configured color mode type and color mode cycle button for public imports used by Honeydeck chrome surfaces and the marketing site.
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import { ColorModeCycleButton, type ColorMode } from '@honeydeck/honeydeck/components'
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Behavior:
|
|
24
|
+
|
|
25
|
+
- `ColorMode` is `"system" | "light" | "dark"`.
|
|
26
|
+
- `getNextColorMode(mode)` cycles `system` → `light` → `dark` → `system`.
|
|
27
|
+
- `<ColorModeCycleButton>` renders the matching Lucide icon for the configured mode: monitor for `system`, sun for `light`, moon for `dark`.
|
|
28
|
+
- Clicking the button calls `onSetColorMode` with the next configured mode.
|
|
29
|
+
- The button accepts `className`, `iconSize`, `title`, and `ariaLabel` so slide chrome, reference pages, and other Honeydeck surfaces can share behavior while styling the control locally.
|
|
30
|
+
|
|
31
|
+
### Button Controls
|
|
32
|
+
|
|
33
|
+
`@honeydeck/honeydeck/components` exports generic Honeydeck-token-based button primitives and class recipes for runtime chrome, runtime reference pages, and marketing controls.
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
import { Button, buttonPrimaryClass, iconButtonClass } from '@honeydeck/honeydeck/components'
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Behavior:
|
|
40
|
+
|
|
41
|
+
- `<Button>` renders a native `button` with `type="button"` by default.
|
|
42
|
+
- `variant` selects one of `primary`, `secondary`, `icon`, `small`, or `quiet`.
|
|
43
|
+
- `buttonClass(variant, className)` returns the matching token-based Tailwind class string and appends `className` when provided.
|
|
44
|
+
- Exported class recipes use only shipped Honeydeck base-theme tokens for foreground, surface, border, primary, and transition behavior (`--honeydeck-primary`, `--honeydeck-primary-foreground`, `--honeydeck-surface`, `--honeydeck-surface-foreground`, `--honeydeck-border`, `--honeydeck-foreground`, `--honeydeck-background`) so public imports do not depend on marketing-only aliases at publish time.
|
|
45
|
+
|
|
46
|
+
### Runtime chrome buttons
|
|
47
|
+
|
|
48
|
+
Icon-only runtime chrome buttons, including the floating navigation bar actions, expose an explicit accessible name with `aria-label` and keep matching hover titles for the same action text.
|
|
49
|
+
|
|
50
|
+
### `<Reveal>`
|
|
51
|
+
|
|
52
|
+
Reveals content at the next timeline step.
|
|
53
|
+
|
|
54
|
+
```mdx
|
|
55
|
+
<Reveal>This appears at step 1</Reveal>
|
|
56
|
+
<Reveal>This appears at step 2</Reveal>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Behavior:
|
|
60
|
+
|
|
61
|
+
- Hidden content **reserves layout space** (`visibility: hidden` + `opacity: 0`, not `display: none`)
|
|
62
|
+
- Runtime wrapper matches MDX context: flow/block reveals render a block-level `div`, text/inline reveals render an inline `span`
|
|
63
|
+
- Nested reveals are supported; inline nested reveals inside paragraphs must not create invalid `div`-inside-`p` HTML
|
|
64
|
+
- Default effect: fade in
|
|
65
|
+
- Reveals are **cumulative** (once visible, stays visible)
|
|
66
|
+
- Supports `className` for custom transitions
|
|
67
|
+
- Supports `at?: number`; Honeydeck injects this during compilation and manual use works as an escape hatch
|
|
68
|
+
- Supports `as?: "div" | "span"`; Honeydeck injects this during compilation and manual use works as an escape hatch
|
|
69
|
+
- No `effect` prop
|
|
70
|
+
|
|
71
|
+
### `<RevealGroup>`
|
|
72
|
+
|
|
73
|
+
Convenience: reveals each meaningful direct child one by one. Whitespace-only text children are ignored. As a special case, when a direct child is a Markdown/HTML/JSX list, each item in that list is revealed one after another while preserving the list container. Empty groups currently consume one timeline step.
|
|
74
|
+
|
|
75
|
+
```mdx
|
|
76
|
+
<RevealGroup>
|
|
77
|
+
- First point
|
|
78
|
+
- Second point
|
|
79
|
+
- Third point
|
|
80
|
+
</RevealGroup>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Each list item becomes its own timeline step.
|
|
84
|
+
|
|
85
|
+
Nested timeline entries inside a group target are flattened after that target
|
|
86
|
+
and before the following group target:
|
|
87
|
+
|
|
88
|
+
```mdx
|
|
89
|
+
<RevealGroup>
|
|
90
|
+
<div>
|
|
91
|
+
Parent item
|
|
92
|
+
<Reveal>Nested detail</Reveal>
|
|
93
|
+
</div>
|
|
94
|
+
<div>Sibling item</div>
|
|
95
|
+
</RevealGroup>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Timeline:
|
|
99
|
+
|
|
100
|
+
1. Parent item appears
|
|
101
|
+
2. Nested detail appears
|
|
102
|
+
3. Sibling item appears
|
|
103
|
+
|
|
104
|
+
### `<ListStyle>`
|
|
105
|
+
|
|
106
|
+
Styles Markdown/HTML/JSX lists inside a wrapper. By default, it removes bullets from every list inside it. With the `bullets` prop, it renders custom bullet markers and supports one marker per nesting level. Deeper levels reuse the last configured marker.
|
|
107
|
+
|
|
108
|
+
```mdx
|
|
109
|
+
import { ListStyle } from '@honeydeck/honeydeck'
|
|
110
|
+
import { CheckIcon, CircleIcon } from 'lucide-react'
|
|
111
|
+
|
|
112
|
+
<ListStyle>
|
|
113
|
+
- No marker
|
|
114
|
+
- Still aligned
|
|
115
|
+
</ListStyle>
|
|
116
|
+
|
|
117
|
+
<ListStyle bullets={[<CheckIcon />, <CircleIcon />]}>
|
|
118
|
+
- Level one uses a check icon
|
|
119
|
+
- Level two uses a circle icon
|
|
120
|
+
</ListStyle>
|
|
121
|
+
|
|
122
|
+
<ListStyle bullets={["→", "–", "·"]}>
|
|
123
|
+
- Level one
|
|
124
|
+
- Level two
|
|
125
|
+
- Level three
|
|
126
|
+
</ListStyle>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Behavior:
|
|
130
|
+
|
|
131
|
+
- Native list markers are removed for all nested lists in the wrapper.
|
|
132
|
+
- `bullets` accepts a single React node/string marker or an array of markers by nesting level.
|
|
133
|
+
- `bullets={false}`, `bullets="none"`, `bullets={null}`, or omitting `bullets` renders markerless lists.
|
|
134
|
+
- Custom marker injection applies to authored list elements passed as children; native markers remain hidden for any deeper rendered lists because styling is scoped to the wrapper.
|
|
135
|
+
|
|
136
|
+
### `<Keyboard>`
|
|
137
|
+
|
|
138
|
+
Displays one keyboard key or a keyboard shortcut using semantic `<kbd>` markup.
|
|
139
|
+
|
|
140
|
+
```mdx
|
|
141
|
+
import { Keyboard } from '@honeydeck/honeydeck'
|
|
142
|
+
|
|
143
|
+
Press <Keyboard>Esc</Keyboard> to close overview.
|
|
144
|
+
|
|
145
|
+
Open command palette with <Keyboard keys={["Ctrl", "Shift", "P"]} />.
|
|
146
|
+
|
|
147
|
+
Advance with <Keyboard keys="Space" />.
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Props:
|
|
151
|
+
|
|
152
|
+
- `keys?: ReactNode | ReactNode[]` — key label or ordered shortcut key labels.
|
|
153
|
+
- `children?: ReactNode` — single key label when `keys` is omitted.
|
|
154
|
+
- `separator?: ReactNode` — separator rendered between array entries; defaults to `+`.
|
|
155
|
+
- `className?: string` — applied to the outer wrapper.
|
|
156
|
+
|
|
157
|
+
Behavior:
|
|
158
|
+
|
|
159
|
+
- A single `children` value or single `keys` value renders one `<kbd>`.
|
|
160
|
+
- An array `keys` value renders one `<kbd>` per item, in order, separated by `separator`.
|
|
161
|
+
- The component is inline by default so it works inside prose.
|
|
162
|
+
- It uses Honeydeck default styling and can be customized with `className`.
|
|
163
|
+
- It does not participate in the Honeydeck timeline.
|
|
164
|
+
|
|
165
|
+
### `<BrowserFrame>`
|
|
166
|
+
|
|
167
|
+
Displays an iframe inside a macOS-style browser window frame.
|
|
168
|
+
|
|
169
|
+
```mdx
|
|
170
|
+
import { BrowserFrame } from '@honeydeck/honeydeck'
|
|
171
|
+
|
|
172
|
+
<BrowserFrame
|
|
173
|
+
src="https://example.com"
|
|
174
|
+
addressBar="example.com"
|
|
175
|
+
fallbackImage="/example-fallback-light.png"
|
|
176
|
+
fallbackDarkImage="/example-fallback-dark.png"
|
|
177
|
+
/>
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Props:
|
|
181
|
+
|
|
182
|
+
- `src: string` — iframe URL.
|
|
183
|
+
- `addressBar?: ReactNode` — optional content shown in the address-bar field. When omitted, no address-bar field is rendered.
|
|
184
|
+
- `fallbackImage?: string` — light/default screenshot shown when the iframe cannot be loaded or fallback mode is toggled on.
|
|
185
|
+
- `fallbackDarkImage?: string` — dark-mode screenshot shown in dark color mode; falls back to `fallbackImage` when omitted.
|
|
186
|
+
- `fallbackAlt?: string` — accessible alt text for fallback images; defaults to `Fallback preview`.
|
|
187
|
+
- `defaultFallback?: boolean` — initially render the fallback image instead of the iframe, useful for demos and final-state screenshots.
|
|
188
|
+
- `aspectRatio?: CSSProperties["aspectRatio"]` — aspect ratio for the full browser window; defaults to `16 / 9`.
|
|
189
|
+
- `className?: string` — applied to the outer wrapper.
|
|
190
|
+
- `iframeClassName?: string` — applied to the iframe.
|
|
191
|
+
- Standard iframe attributes such as `allow`, `sandbox`, `loading`, and `referrerPolicy` are forwarded.
|
|
192
|
+
|
|
193
|
+
Behavior:
|
|
194
|
+
|
|
195
|
+
- Renders a single `<iframe>` with a surrounding browser chrome.
|
|
196
|
+
- The frame stretches to the largest size that fits its available parent space while preserving the configured aspect ratio, using CSS sizing (Tailwind utilities and container query units) rather than JavaScript measurement.
|
|
197
|
+
- The chrome uses macOS traffic-light controls and an optional address-bar field.
|
|
198
|
+
- When a fallback image is configured, iframe load errors switch the frame to fallback mode.
|
|
199
|
+
- The fallback uses `fallbackDarkImage` in dark mode and `fallbackImage` otherwise.
|
|
200
|
+
- While fallback mode is active, the top browser chrome shows a badge aligned with the address-bar field so presenters can see that the iframe is not live.
|
|
201
|
+
- When a fallback image is configured, a fourth round control sits next to the macOS traffic-light controls. It is visible only when the control itself is hovered or keyboard-focused, and it toggles fallback mode on and off.
|
|
202
|
+
- Styling uses Honeydeck theme tokens for surface, foreground, border, radius, font, and shadow colors.
|
|
203
|
+
- The component does not participate in the Honeydeck timeline.
|
|
204
|
+
|
|
205
|
+
### `<TimelineSteps>`
|
|
206
|
+
|
|
207
|
+
Reserves a static block of timeline steps for an imported custom component.
|
|
208
|
+
The custom component reads its local state with `useTimelineSteps()`.
|
|
209
|
+
|
|
210
|
+
```mdx
|
|
211
|
+
import { Reveal, TimelineSteps } from '@honeydeck/honeydeck'
|
|
212
|
+
import { AccordionDemo } from './AccordionDemo'
|
|
213
|
+
|
|
214
|
+
<Reveal>Intro appears first</Reveal>
|
|
215
|
+
|
|
216
|
+
<TimelineSteps steps={3}>
|
|
217
|
+
<AccordionDemo />
|
|
218
|
+
</TimelineSteps>
|
|
219
|
+
|
|
220
|
+
<Reveal>Outro appears after the accordion</Reveal>
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Inside `AccordionDemo`:
|
|
224
|
+
|
|
225
|
+
```tsx
|
|
226
|
+
import { useTimelineSteps } from '@honeydeck/honeydeck'
|
|
227
|
+
|
|
228
|
+
export function AccordionDemo() {
|
|
229
|
+
const { phase, stepIndex, stepCount, isPdfFinalRender } = useTimelineSteps()
|
|
230
|
+
// phase: "before" | "active" | "after"
|
|
231
|
+
// stepIndex: 0 before start, 1..stepCount while active, stepCount after end
|
|
232
|
+
// isPdfFinalRender: true only for one-page final-state PDF export
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Behavior:
|
|
237
|
+
|
|
238
|
+
- `steps` must be a literal positive integer in slide MDX, for example
|
|
239
|
+
`steps={3}`. Dynamic values are not supported because the timeline is counted
|
|
240
|
+
at build time.
|
|
241
|
+
- `<TimelineSteps>` must appear at the usage site in slide MDX. Imported TSX
|
|
242
|
+
components cannot register steps by rendering `<TimelineSteps>` internally,
|
|
243
|
+
because their internals are not visible to the MDX compiler.
|
|
244
|
+
- Nested Honeydeck timeline producers inside `<TimelineSteps>` are not supported.
|
|
245
|
+
Use the wrapper to reserve the block, then use `useTimelineSteps()` inside
|
|
246
|
+
the custom component.
|
|
247
|
+
- Hook state includes `{ phase, stepIndex, stepCount, startAt, endAt, isPdfFinalRender }`.
|
|
248
|
+
- In `isPdfFinalRender`, custom step components may render a PDF-specific final
|
|
249
|
+
composition, such as opening all accordion sections. Step-by-step PDF export
|
|
250
|
+
(`pdfSteps: all`) uses the normal timeline states and does not set this flag.
|
|
251
|
+
|
|
252
|
+
### `<Notes>`
|
|
253
|
+
|
|
254
|
+
Presenter notes. Hidden from audience view and normal PDF. Notes are collected in presenter mode through runtime context and are not emitted into the audience DOM. Markdown authored inside `<Notes>` renders as formatted speaker notes in presenter mode, including headings, paragraphs, lists, links, inline code, code blocks, and block quotes.
|
|
255
|
+
|
|
256
|
+
```mdx
|
|
257
|
+
<Notes>
|
|
258
|
+
# Demo cue
|
|
259
|
+
|
|
260
|
+
- Remember to demo the sparkle button here.
|
|
261
|
+
- Mention PDF export.
|
|
262
|
+
</Notes>
|
|
263
|
+
```
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function SlideNumberBadge({ slide }: { slide: number }) {
|
|
2
|
+
return (
|
|
3
|
+
<div
|
|
4
|
+
role="status"
|
|
5
|
+
aria-label={`Slide ${slide}`}
|
|
6
|
+
className="absolute right-4 bottom-4 z-50 pointer-events-none text-3xl md:text-4xl tabular-nums text-foreground/75"
|
|
7
|
+
>
|
|
8
|
+
{slide}
|
|
9
|
+
</div>
|
|
10
|
+
);
|
|
11
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { createContext, type ReactNode, useContext } from "react";
|
|
2
|
+
import { useTimeline } from "../TimelineContext.tsx";
|
|
3
|
+
|
|
4
|
+
export type TimelineStepsPhase = "before" | "active" | "after";
|
|
5
|
+
|
|
6
|
+
export type TimelineStepsState = {
|
|
7
|
+
/** Local step index within this block. 0 before start, 1..stepCount while active. */
|
|
8
|
+
stepIndex: number;
|
|
9
|
+
/** Number of steps reserved by this block. */
|
|
10
|
+
stepCount: number;
|
|
11
|
+
/** Where the slide timeline is relative to this block. */
|
|
12
|
+
phase: TimelineStepsPhase;
|
|
13
|
+
/** First absolute slide step reserved by this block. */
|
|
14
|
+
startAt: number;
|
|
15
|
+
/** Last absolute slide step reserved by this block. */
|
|
16
|
+
endAt: number;
|
|
17
|
+
/**
|
|
18
|
+
* True while `honeydeck pdf` is capturing one final-state page per slide.
|
|
19
|
+
* Use this for components that should render an all-open/all-visible PDF state.
|
|
20
|
+
*/
|
|
21
|
+
isPdfFinalRender: boolean;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type TimelineStepsProps = {
|
|
25
|
+
/**
|
|
26
|
+
* Number of slide timeline steps reserved for the children.
|
|
27
|
+
* Must be a literal positive integer in MDX.
|
|
28
|
+
*/
|
|
29
|
+
steps: number;
|
|
30
|
+
/**
|
|
31
|
+
* First absolute slide step. Injected by the Honeydeck compiler.
|
|
32
|
+
* Defaults to 1 only for direct runtime/test usage.
|
|
33
|
+
*/
|
|
34
|
+
at?: number;
|
|
35
|
+
children?: ReactNode;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const TimelineStepsContext = createContext<TimelineStepsState | null>(null);
|
|
39
|
+
|
|
40
|
+
function assertPositiveInteger(value: number, propName: string): void {
|
|
41
|
+
if (!Number.isInteger(value) || value < 1) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
`Honeydeck <TimelineSteps> requires ${propName} to be a positive integer.`,
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Reserves a static block of slide timeline steps for a custom component.
|
|
50
|
+
*
|
|
51
|
+
* Wrap custom interactive content in `TimelineSteps`, then call
|
|
52
|
+
* `useTimelineSteps()` inside that content to read the local step index,
|
|
53
|
+
* total step count, phase, and PDF-final-render state.
|
|
54
|
+
*
|
|
55
|
+
* ```mdx
|
|
56
|
+
* import { TimelineSteps, useTimelineSteps } from '@honeydeck/honeydeck'
|
|
57
|
+
*
|
|
58
|
+
* function AccordionDemo() {
|
|
59
|
+
* const { phase, stepIndex, stepCount, isPdfFinalRender } = useTimelineSteps()
|
|
60
|
+
* return <div>{phase}: {stepIndex} / {stepCount}</div>
|
|
61
|
+
* }
|
|
62
|
+
*
|
|
63
|
+
* <TimelineSteps steps={3}>
|
|
64
|
+
* <AccordionDemo />
|
|
65
|
+
* </TimelineSteps>
|
|
66
|
+
* ```
|
|
67
|
+
*
|
|
68
|
+
* Keep `steps` as a literal positive integer in slide MDX so Honeydeck can
|
|
69
|
+
* calculate the slide step count at build time. The compiler injects `at`;
|
|
70
|
+
* set it manually only in runtime tests or highly controlled custom usage.
|
|
71
|
+
*/
|
|
72
|
+
export function TimelineSteps({ steps, at = 1, children }: TimelineStepsProps) {
|
|
73
|
+
assertPositiveInteger(steps, "steps");
|
|
74
|
+
assertPositiveInteger(at, "at");
|
|
75
|
+
|
|
76
|
+
const { stepIndex: slideStepIndex, isPdfFinalRender } = useTimeline();
|
|
77
|
+
const startAt = at;
|
|
78
|
+
const endAt = at + steps - 1;
|
|
79
|
+
|
|
80
|
+
let phase: TimelineStepsPhase = "active";
|
|
81
|
+
let localStepIndex = slideStepIndex - startAt + 1;
|
|
82
|
+
|
|
83
|
+
if (slideStepIndex < startAt) {
|
|
84
|
+
phase = "before";
|
|
85
|
+
localStepIndex = 0;
|
|
86
|
+
} else if (slideStepIndex > endAt) {
|
|
87
|
+
phase = "after";
|
|
88
|
+
localStepIndex = steps;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const value: TimelineStepsState = {
|
|
92
|
+
stepIndex: localStepIndex,
|
|
93
|
+
stepCount: steps,
|
|
94
|
+
phase,
|
|
95
|
+
startAt,
|
|
96
|
+
endAt,
|
|
97
|
+
isPdfFinalRender,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
return (
|
|
101
|
+
<TimelineStepsContext.Provider value={value}>
|
|
102
|
+
{children}
|
|
103
|
+
</TimelineStepsContext.Provider>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function useTimelineSteps(): TimelineStepsState {
|
|
108
|
+
const value = useContext(TimelineStepsContext);
|
|
109
|
+
if (!value) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
"Honeydeck useTimelineSteps() must be used inside <TimelineSteps>.",
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
return value;
|
|
115
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public runtime component exports for Honeydeck.
|
|
3
|
+
*
|
|
4
|
+
* These are the components that end users import in their MDX slides:
|
|
5
|
+
*
|
|
6
|
+
* ```mdx
|
|
7
|
+
* import { Reveal, RevealGroup, Notes } from '@honeydeck/honeydeck'
|
|
8
|
+
* ```
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export type { BrowserFrameProps } from "./BrowserFrame.tsx";
|
|
12
|
+
export { BrowserFrame } from "./BrowserFrame.tsx";
|
|
13
|
+
export type { ButtonProps, ButtonVariant } from "./Button.tsx";
|
|
14
|
+
export {
|
|
15
|
+
Button,
|
|
16
|
+
buttonClass,
|
|
17
|
+
buttonPrimaryClass,
|
|
18
|
+
buttonSecondaryClass,
|
|
19
|
+
hoverBorderClass,
|
|
20
|
+
iconButtonClass,
|
|
21
|
+
quietLinkClass,
|
|
22
|
+
smallButtonClass,
|
|
23
|
+
surfaceControlClass,
|
|
24
|
+
transitionClass,
|
|
25
|
+
} from "./Button.tsx";
|
|
26
|
+
export type {
|
|
27
|
+
ColorMode,
|
|
28
|
+
ColorModeCycleButtonProps,
|
|
29
|
+
} from "./ColorModeCycleButton.tsx";
|
|
30
|
+
export {
|
|
31
|
+
COLOR_MODES,
|
|
32
|
+
ColorModeCycleButton,
|
|
33
|
+
getNextColorMode,
|
|
34
|
+
} from "./ColorModeCycleButton.tsx";
|
|
35
|
+
export type { KeyboardKey, KeyboardProps } from "./Keyboard.tsx";
|
|
36
|
+
export { Keyboard } from "./Keyboard.tsx";
|
|
37
|
+
export type { ListBullet, ListBullets, ListStyleProps } from "./ListStyle.tsx";
|
|
38
|
+
export { ListStyle } from "./ListStyle.tsx";
|
|
39
|
+
export type { NotesProps } from "./Notes.tsx";
|
|
40
|
+
export { Notes } from "./Notes.tsx";
|
|
41
|
+
export type { RevealProps } from "./Reveal.tsx";
|
|
42
|
+
export { Reveal } from "./Reveal.tsx";
|
|
43
|
+
export type { RevealGroupProps } from "./RevealGroup.tsx";
|
|
44
|
+
export { RevealGroup } from "./RevealGroup.tsx";
|
|
45
|
+
export type {
|
|
46
|
+
TimelineStepsPhase,
|
|
47
|
+
TimelineStepsProps,
|
|
48
|
+
TimelineStepsState,
|
|
49
|
+
} from "./TimelineSteps.tsx";
|
|
50
|
+
export { TimelineSteps, useTimelineSteps } from "./TimelineSteps.tsx";
|
|
51
|
+
|
|
52
|
+
// HoneydeckCodeBlock is intentionally NOT exported from the public barrel.
|
|
53
|
+
// It is an internal component injected by remarkShikiCodeBlocks via a
|
|
54
|
+
// direct subpath import: '@honeydeck/honeydeck/components/code-block'.
|
|
55
|
+
// Do not import it from '@honeydeck/honeydeck' or '@honeydeck/honeydeck/components' — use the subpath.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main Honeydeck runtime entry point.
|
|
3
|
+
*
|
|
4
|
+
* This is what end-users import in their MDX slides:
|
|
5
|
+
* ```mdx
|
|
6
|
+
* import { Reveal, RevealGroup, Notes } from '@honeydeck/honeydeck'
|
|
7
|
+
* ```
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export type { ColorModeImageProps } from "../layouts/ColorModeImage.tsx";
|
|
11
|
+
export { ColorModeImage } from "../layouts/ColorModeImage.tsx";
|
|
12
|
+
export type { BrowserFrameProps } from "./components/BrowserFrame.tsx";
|
|
13
|
+
export { BrowserFrame } from "./components/BrowserFrame.tsx";
|
|
14
|
+
export type { KeyboardKey, KeyboardProps } from "./components/Keyboard.tsx";
|
|
15
|
+
export { Keyboard } from "./components/Keyboard.tsx";
|
|
16
|
+
export type {
|
|
17
|
+
ListBullet,
|
|
18
|
+
ListBullets,
|
|
19
|
+
ListStyleProps,
|
|
20
|
+
} from "./components/ListStyle.tsx";
|
|
21
|
+
export { ListStyle } from "./components/ListStyle.tsx";
|
|
22
|
+
export type { NotesProps } from "./components/Notes.tsx";
|
|
23
|
+
export { Notes } from "./components/Notes.tsx";
|
|
24
|
+
export type { RevealProps } from "./components/Reveal.tsx";
|
|
25
|
+
export { Reveal } from "./components/Reveal.tsx";
|
|
26
|
+
export type { RevealGroupProps } from "./components/RevealGroup.tsx";
|
|
27
|
+
export { RevealGroup } from "./components/RevealGroup.tsx";
|
|
28
|
+
export type {
|
|
29
|
+
TimelineStepsPhase,
|
|
30
|
+
TimelineStepsProps,
|
|
31
|
+
TimelineStepsState,
|
|
32
|
+
} from "./components/TimelineSteps.tsx";
|
|
33
|
+
export {
|
|
34
|
+
TimelineSteps,
|
|
35
|
+
useTimelineSteps,
|
|
36
|
+
} from "./components/TimelineSteps.tsx";
|
|
37
|
+
export type {
|
|
38
|
+
TimelineContextValue,
|
|
39
|
+
TimelineProviderProps,
|
|
40
|
+
} from "./TimelineContext.tsx";
|
|
41
|
+
// TimelineProvider and useTimeline are exported for kit/layout authors.
|
|
42
|
+
export { TimelineProvider, useTimeline } from "./TimelineContext.tsx";
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const INTERACTIVE_SELECTOR = [
|
|
2
|
+
"button",
|
|
3
|
+
"a[href]",
|
|
4
|
+
"input",
|
|
5
|
+
"textarea",
|
|
6
|
+
"select",
|
|
7
|
+
"summary",
|
|
8
|
+
"[contenteditable='true']",
|
|
9
|
+
"[role='button']",
|
|
10
|
+
"[role='link']",
|
|
11
|
+
].join(",");
|
|
12
|
+
|
|
13
|
+
const SCROLL_OVERFLOW_VALUES = new Set(["auto", "scroll"]);
|
|
14
|
+
|
|
15
|
+
function isElement(value: EventTarget | null): value is Element {
|
|
16
|
+
return typeof Element !== "undefined" && value instanceof Element;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function isHTMLElement(value: Element): value is HTMLElement {
|
|
20
|
+
return typeof HTMLElement !== "undefined" && value instanceof HTMLElement;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function allowsScroll(value: string): boolean {
|
|
24
|
+
return SCROLL_OVERFLOW_VALUES.has(value);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function isInteractiveTarget(target: EventTarget | null): boolean {
|
|
28
|
+
if (!isElement(target)) return false;
|
|
29
|
+
return target.closest(INTERACTIVE_SELECTOR) !== null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function findScrollableAncestor(
|
|
33
|
+
target: EventTarget | null,
|
|
34
|
+
boundary?: Element | null,
|
|
35
|
+
): HTMLElement | null {
|
|
36
|
+
if (!isElement(target)) return null;
|
|
37
|
+
|
|
38
|
+
let current: Element | null = target;
|
|
39
|
+
while (current && current !== boundary) {
|
|
40
|
+
if (isHTMLElement(current)) {
|
|
41
|
+
if (current.hasAttribute("data-honeydeck-scrollable")) return current;
|
|
42
|
+
|
|
43
|
+
const style = window.getComputedStyle(current);
|
|
44
|
+
const canScrollY =
|
|
45
|
+
allowsScroll(style.overflowY) &&
|
|
46
|
+
current.scrollHeight > current.clientHeight;
|
|
47
|
+
const canScrollX =
|
|
48
|
+
allowsScroll(style.overflowX) &&
|
|
49
|
+
current.scrollWidth > current.clientWidth;
|
|
50
|
+
|
|
51
|
+
if (canScrollY || canScrollX) return current;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
current = current.parentElement;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function shouldDeckOwnTouchGesture(
|
|
61
|
+
target: EventTarget | null,
|
|
62
|
+
boundary?: Element | null,
|
|
63
|
+
): boolean {
|
|
64
|
+
if (!isElement(target)) return false;
|
|
65
|
+
if (target.closest("[data-honeydeck-no-swipe]")) return false;
|
|
66
|
+
if (isInteractiveTarget(target)) return false;
|
|
67
|
+
return findScrollableAncestor(target, boundary) === null;
|
|
68
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export function isEditableKeyboardTarget(target: EventTarget | null): boolean {
|
|
2
|
+
if (typeof HTMLElement === "undefined") return false;
|
|
3
|
+
if (!(target instanceof HTMLElement)) return false;
|
|
4
|
+
|
|
5
|
+
const tag = target.tagName;
|
|
6
|
+
return tag === "INPUT" || tag === "TEXTAREA" || target.isContentEditable;
|
|
7
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Route } from "./router.ts";
|
|
2
|
+
|
|
3
|
+
const STORAGE_KEY = "honeydeck:last-slide-route";
|
|
4
|
+
const FALLBACK_SLIDE_ROUTE: Route = { view: "slide", slide: 1, step: 0 };
|
|
5
|
+
|
|
6
|
+
function isStorageAvailable(): boolean {
|
|
7
|
+
return typeof sessionStorage !== "undefined";
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function toStoredSlideRoute(route: Route): Route | null {
|
|
11
|
+
if (route.view !== "slide") return null;
|
|
12
|
+
|
|
13
|
+
const slide =
|
|
14
|
+
Number.isFinite(route.slide) && route.slide >= 1 ? route.slide : 1;
|
|
15
|
+
const step = Number.isFinite(route.step) && route.step >= 0 ? route.step : 0;
|
|
16
|
+
|
|
17
|
+
return { view: "slide", slide, step };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function rememberSlideRoute(route: Route): void {
|
|
21
|
+
const slideRoute = toStoredSlideRoute(route);
|
|
22
|
+
if (!slideRoute || !isStorageAvailable()) return;
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(slideRoute));
|
|
26
|
+
} catch {
|
|
27
|
+
// Session storage can be unavailable in restrictive browser contexts.
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function getRememberedSlideRoute(): Route {
|
|
32
|
+
if (!isStorageAvailable()) return FALLBACK_SLIDE_ROUTE;
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const value = sessionStorage.getItem(STORAGE_KEY);
|
|
36
|
+
if (!value) return FALLBACK_SLIDE_ROUTE;
|
|
37
|
+
|
|
38
|
+
const parsed = JSON.parse(value) as Partial<Route>;
|
|
39
|
+
const slide =
|
|
40
|
+
typeof parsed.slide === "number" &&
|
|
41
|
+
Number.isFinite(parsed.slide) &&
|
|
42
|
+
parsed.slide >= 1
|
|
43
|
+
? parsed.slide
|
|
44
|
+
: 1;
|
|
45
|
+
const step =
|
|
46
|
+
typeof parsed.step === "number" &&
|
|
47
|
+
Number.isFinite(parsed.step) &&
|
|
48
|
+
parsed.step >= 0
|
|
49
|
+
? parsed.step
|
|
50
|
+
: 0;
|
|
51
|
+
|
|
52
|
+
return { view: "slide", slide, step };
|
|
53
|
+
} catch {
|
|
54
|
+
return FALLBACK_SLIDE_ROUTE;
|
|
55
|
+
}
|
|
56
|
+
}
|