@honeydeck/honeydeck 0.4.0 → 0.6.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 +4 -4
- package/DEVELOPMENT.md +6 -4
- package/Readme.md +15 -15
- package/SPEC.md +5 -4
- package/docs/browser-frame.md +38 -0
- package/docs/components.md +16 -57
- package/docs/configuration.md +13 -0
- package/docs/customization.md +2 -0
- package/docs/deeper-dive.md +32 -7
- package/docs/getting-started.md +4 -2
- package/docs/index.json +258 -0
- package/docs/keyboard.md +35 -0
- package/docs/list-style.md +53 -0
- package/docs/local-development.md +3 -1
- package/docs/mermaid.md +2 -0
- package/docs/mobile.md +2 -0
- package/docs/navigation.md +3 -1
- package/docs/notes.md +40 -0
- package/docs/pdf-export.md +6 -2
- package/docs/presenter-mode.md +8 -3
- package/docs/reveal-group.md +60 -0
- package/docs/reveal-with.md +39 -0
- package/docs/reveal.md +35 -0
- package/docs/skills.md +5 -3
- package/docs/slides.md +2 -0
- package/docs/slidev-migration.md +5 -0
- package/docs/steps-and-reveals.md +145 -8
- package/docs/timeline-steps.md +50 -0
- package/package.json +6 -2
- package/skills/SPEC.md +6 -6
- package/skills/honeydeck/SKILL.md +9 -9
- package/skills/slidev-migration/SKILL.md +7 -6
- package/src/SPEC.md +8 -3
- package/src/cli/SPEC.md +3 -2
- package/src/cli/pdf.ts +11 -4
- package/src/remark/SPEC.md +102 -2
- package/src/remark/code-utils.ts +151 -0
- package/src/remark/shiki-code-blocks.ts +329 -136
- package/src/remark/step-numbering.ts +408 -103
- package/src/runtime/Deck.tsx +133 -116
- package/src/runtime/EffectiveColorModeContext.tsx +37 -0
- package/src/runtime/SPEC.md +21 -8
- package/src/runtime/SlideCanvas.tsx +19 -16
- package/src/runtime/SlideScaleContext.tsx +23 -0
- package/src/runtime/components/CodeBlock.tsx +19 -202
- package/src/runtime/components/CodeBlockCopyButton.tsx +64 -0
- package/src/runtime/components/CodeBlockShared.ts +17 -0
- package/src/runtime/components/Fade.tsx +51 -0
- package/src/runtime/components/FadeGroup.tsx +175 -0
- package/src/runtime/components/FadeWith.tsx +54 -0
- package/src/runtime/components/MagicCodeBlock.tsx +223 -0
- package/src/runtime/components/NavBar.tsx +1 -1
- package/src/runtime/components/NormalCodeBlock.tsx +128 -0
- package/src/runtime/components/Reveal.tsx +27 -27
- package/src/runtime/components/RevealGroup.tsx +143 -41
- package/src/runtime/components/RevealWith.tsx +63 -0
- package/src/runtime/components/SPEC.md +115 -10
- package/src/runtime/components/TimelineReveal.tsx +81 -0
- package/src/runtime/components/index.ts +13 -5
- package/src/runtime/components/timelineVisibility.ts +45 -0
- package/src/runtime/index.ts +9 -1
- package/src/runtime/navigation.ts +6 -4
- package/src/runtime/presentationApi.ts +449 -0
- package/src/runtime/views/PresenterCastButton.tsx +39 -0
- package/src/runtime/views/PresenterView.tsx +21 -4
- package/src/runtime/views/SPEC.md +7 -5
- package/src/theme/base.css +67 -2
- package/src/vite-plugin/SPEC.md +20 -2
- package/src/vite-plugin/index.ts +16 -2
- package/src/vite-plugin/splitter.ts +1 -0
- package/src/vite-plugin/virtual-modules.ts +16 -6
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import type { KeyedTokensInfo } from "@shikijs/magic-move/core";
|
|
2
|
+
import { ShikiMagicMovePrecompiled } from "@shikijs/magic-move/react";
|
|
3
|
+
import { useEffect, useMemo, useState } from "react";
|
|
4
|
+
import { useEffectiveColorMode } from "../EffectiveColorModeContext.tsx";
|
|
5
|
+
import { useSlideScale } from "../SlideScaleContext.tsx";
|
|
6
|
+
import { useTimeline } from "../TimelineContext.tsx";
|
|
7
|
+
import { CodeBlock } from "./CodeBlock.tsx";
|
|
8
|
+
import { parseJsonProp, type StepGroup } from "./CodeBlockShared.ts";
|
|
9
|
+
|
|
10
|
+
type HoneydeckMagicCodeBlockProps = {
|
|
11
|
+
/** JSON-encoded unique build-time Shiki Magic Move token states for light mode. */
|
|
12
|
+
lightTokenStatesJson: string;
|
|
13
|
+
/** JSON-encoded unique build-time Shiki Magic Move token states for dark mode. */
|
|
14
|
+
darkTokenStatesJson: string;
|
|
15
|
+
/** JSON-encoded indexes from Magic Code timeline states to unique token states. */
|
|
16
|
+
tokenStateIndexesJson: string;
|
|
17
|
+
/** JSON-encoded StepGroup[] aligned to the Magic Code timeline states. */
|
|
18
|
+
stepGroupsJson: string;
|
|
19
|
+
/** JSON-encoded source strings aligned to the unique token states. */
|
|
20
|
+
sourcesJson: string;
|
|
21
|
+
/** 1-based timeline step where Magic Code state 1 activates. */
|
|
22
|
+
startAt: number;
|
|
23
|
+
/** Magic Move animation duration in milliseconds. */
|
|
24
|
+
duration: number;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type MagicToken = KeyedTokensInfo["tokens"][number];
|
|
28
|
+
|
|
29
|
+
export function getActiveCodeStateIndex(
|
|
30
|
+
stateCount: number,
|
|
31
|
+
stepIndex: number,
|
|
32
|
+
startAt: number,
|
|
33
|
+
): number {
|
|
34
|
+
if (stateCount <= 1) return 0;
|
|
35
|
+
if (startAt > 0 && stepIndex >= startAt) {
|
|
36
|
+
return Math.min(stepIndex - startAt + 1, stateCount - 1);
|
|
37
|
+
}
|
|
38
|
+
return 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const MAGIC_CODE_TOKEN_OPACITY_VAR = "--honeydeck-magic-code-token-opacity";
|
|
42
|
+
|
|
43
|
+
function withMagicCodeTokenOpacity(
|
|
44
|
+
token: MagicToken,
|
|
45
|
+
opacity: string,
|
|
46
|
+
): MagicToken {
|
|
47
|
+
return {
|
|
48
|
+
...token,
|
|
49
|
+
htmlStyle: {
|
|
50
|
+
...token.htmlStyle,
|
|
51
|
+
[MAGIC_CODE_TOKEN_OPACITY_VAR]: opacity,
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function applyMagicCodeDimming(
|
|
57
|
+
step: KeyedTokensInfo,
|
|
58
|
+
group: StepGroup | undefined,
|
|
59
|
+
): KeyedTokensInfo {
|
|
60
|
+
let lineNumber = 1;
|
|
61
|
+
return {
|
|
62
|
+
...step,
|
|
63
|
+
tokens: step.tokens.map((token) => {
|
|
64
|
+
if (token.content === "\n") {
|
|
65
|
+
lineNumber++;
|
|
66
|
+
return token;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const opacity =
|
|
70
|
+
!group || group === "all" || group.includes(lineNumber)
|
|
71
|
+
? "1"
|
|
72
|
+
: "var(--honeydeck-code-line-dim-opacity)";
|
|
73
|
+
return withMagicCodeTokenOpacity(token, opacity);
|
|
74
|
+
}),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function applyMagicCodeStepDimming(
|
|
79
|
+
steps: KeyedTokensInfo[],
|
|
80
|
+
groups: StepGroup[],
|
|
81
|
+
): KeyedTokensInfo[] {
|
|
82
|
+
return steps.map((step, index) => applyMagicCodeDimming(step, groups[index]));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function isPdfExportRender(): boolean {
|
|
86
|
+
if (typeof window === "undefined") return false;
|
|
87
|
+
|
|
88
|
+
const params = new URLSearchParams(window.location.search);
|
|
89
|
+
return params.has("honeydeckPdfRender");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function getMagicCodeTransitionOptions(
|
|
93
|
+
duration: number,
|
|
94
|
+
slideScale: number,
|
|
95
|
+
animate = true,
|
|
96
|
+
) {
|
|
97
|
+
return {
|
|
98
|
+
duration,
|
|
99
|
+
lineNumbers: false,
|
|
100
|
+
animateContainer: animate,
|
|
101
|
+
easing: "ease-in",
|
|
102
|
+
delayMove: 0,
|
|
103
|
+
delayEnter: 0,
|
|
104
|
+
delayLeave: 0,
|
|
105
|
+
delayContainer: 0,
|
|
106
|
+
// Honeydeck scales slides with CSS transforms; Shiki needs that scale so
|
|
107
|
+
// measured viewport pixels map back to slide-local CSS pixels.
|
|
108
|
+
globalScale: slideScale > 0 ? slideScale : 1,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Renders a build-time precompiled Shiki Magic Move code block. */
|
|
113
|
+
export function HoneydeckMagicCodeBlock({
|
|
114
|
+
lightTokenStatesJson,
|
|
115
|
+
darkTokenStatesJson,
|
|
116
|
+
tokenStateIndexesJson,
|
|
117
|
+
stepGroupsJson,
|
|
118
|
+
sourcesJson,
|
|
119
|
+
startAt,
|
|
120
|
+
duration,
|
|
121
|
+
}: HoneydeckMagicCodeBlockProps) {
|
|
122
|
+
const { stepIndex } = useTimeline();
|
|
123
|
+
const slideScale = useSlideScale();
|
|
124
|
+
const colorMode = useEffectiveColorMode();
|
|
125
|
+
const isPdfExport = isPdfExportRender();
|
|
126
|
+
const [isBaselineReady, setIsBaselineReady] = useState(isPdfExport);
|
|
127
|
+
|
|
128
|
+
useEffect(() => {
|
|
129
|
+
if (isPdfExport) {
|
|
130
|
+
setIsBaselineReady(true);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
let firstFrame = 0;
|
|
135
|
+
let secondFrame = 0;
|
|
136
|
+
firstFrame = window.requestAnimationFrame(() => {
|
|
137
|
+
secondFrame = window.requestAnimationFrame(() =>
|
|
138
|
+
setIsBaselineReady(true),
|
|
139
|
+
);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
return () => {
|
|
143
|
+
window.cancelAnimationFrame(firstFrame);
|
|
144
|
+
window.cancelAnimationFrame(secondFrame);
|
|
145
|
+
};
|
|
146
|
+
}, [isPdfExport]);
|
|
147
|
+
|
|
148
|
+
const tokenStateIndexes = useMemo(
|
|
149
|
+
() =>
|
|
150
|
+
parseJsonProp<number[]>(
|
|
151
|
+
tokenStateIndexesJson,
|
|
152
|
+
[],
|
|
153
|
+
"tokenStateIndexesJson",
|
|
154
|
+
),
|
|
155
|
+
[tokenStateIndexesJson],
|
|
156
|
+
);
|
|
157
|
+
const stepGroups = useMemo(
|
|
158
|
+
() => parseJsonProp<StepGroup[]>(stepGroupsJson, [], "stepGroupsJson"),
|
|
159
|
+
[stepGroupsJson],
|
|
160
|
+
);
|
|
161
|
+
const sources = useMemo(
|
|
162
|
+
() => parseJsonProp<string[]>(sourcesJson, [], "sourcesJson"),
|
|
163
|
+
[sourcesJson],
|
|
164
|
+
);
|
|
165
|
+
const tokenStates = useMemo(
|
|
166
|
+
() =>
|
|
167
|
+
parseJsonProp<KeyedTokensInfo[]>(
|
|
168
|
+
colorMode === "dark" ? darkTokenStatesJson : lightTokenStatesJson,
|
|
169
|
+
[],
|
|
170
|
+
colorMode === "dark" ? "darkTokenStatesJson" : "lightTokenStatesJson",
|
|
171
|
+
),
|
|
172
|
+
[colorMode, darkTokenStatesJson, lightTokenStatesJson],
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
const rawSteps = useMemo(
|
|
176
|
+
() =>
|
|
177
|
+
tokenStateIndexes.flatMap((index) => {
|
|
178
|
+
const state = tokenStates[index];
|
|
179
|
+
return state ? [state] : [];
|
|
180
|
+
}),
|
|
181
|
+
[tokenStateIndexes, tokenStates],
|
|
182
|
+
);
|
|
183
|
+
const dimmedSteps = useMemo(
|
|
184
|
+
() => applyMagicCodeStepDimming(rawSteps, stepGroups),
|
|
185
|
+
[rawSteps, stepGroups],
|
|
186
|
+
);
|
|
187
|
+
const activeStateIndex = getActiveCodeStateIndex(
|
|
188
|
+
dimmedSteps.length,
|
|
189
|
+
stepIndex,
|
|
190
|
+
startAt,
|
|
191
|
+
);
|
|
192
|
+
const [renderedStateIndex, setRenderedStateIndex] =
|
|
193
|
+
useState(activeStateIndex);
|
|
194
|
+
|
|
195
|
+
useEffect(() => {
|
|
196
|
+
if (!isBaselineReady) return;
|
|
197
|
+
setRenderedStateIndex(activeStateIndex);
|
|
198
|
+
}, [activeStateIndex, isBaselineReady]);
|
|
199
|
+
|
|
200
|
+
const visibleStateIndex = Math.min(
|
|
201
|
+
renderedStateIndex,
|
|
202
|
+
Math.max(0, dimmedSteps.length - 1),
|
|
203
|
+
);
|
|
204
|
+
const source =
|
|
205
|
+
sources[tokenStateIndexes[visibleStateIndex] ?? visibleStateIndex];
|
|
206
|
+
const transitionOptions = useMemo(
|
|
207
|
+
() => getMagicCodeTransitionOptions(duration, slideScale, !isPdfExport),
|
|
208
|
+
[duration, isPdfExport, slideScale],
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
if (dimmedSteps.length === 0) return null;
|
|
212
|
+
|
|
213
|
+
return (
|
|
214
|
+
<CodeBlock source={source} className="honeydeck-magic-code-block">
|
|
215
|
+
<ShikiMagicMovePrecompiled
|
|
216
|
+
steps={dimmedSteps}
|
|
217
|
+
step={visibleStateIndex}
|
|
218
|
+
animate={!isPdfExport}
|
|
219
|
+
options={transitionOptions}
|
|
220
|
+
/>
|
|
221
|
+
</CodeBlock>
|
|
222
|
+
);
|
|
223
|
+
}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* - Overview grid toggle
|
|
14
14
|
* - Layouts reference
|
|
15
15
|
* - Docs website (opens new window)
|
|
16
|
-
* - Presenter mode (opens
|
|
16
|
+
* - Presenter mode (opens current tab)
|
|
17
17
|
* - Fullscreen toggle
|
|
18
18
|
* - Mobile slide text selection toggle
|
|
19
19
|
* - Color mode cycle (system → light → dark → system)
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { useTimeline } from "../TimelineContext.tsx";
|
|
3
|
+
import { CodeBlock } from "./CodeBlock.tsx";
|
|
4
|
+
import { parseJsonProp, type StepGroup } from "./CodeBlockShared.ts";
|
|
5
|
+
|
|
6
|
+
type HoneydeckCodeBlockProps = {
|
|
7
|
+
/** Full shiki HTML output (includes the `<pre>` element). */
|
|
8
|
+
html: string;
|
|
9
|
+
/** JSON-encoded StepGroup[] — empty array = no step-through. */
|
|
10
|
+
stepsJson: string;
|
|
11
|
+
/** 1-based timeline step where group 1 activates (0 = no step-through). */
|
|
12
|
+
startAt: number;
|
|
13
|
+
/** Original fenced code text copied by the hover/focus copy control. */
|
|
14
|
+
source?: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const DATA_DIM_ATTR = /\sdata-dim=(["'])1\1/g;
|
|
18
|
+
const DATA_HIGHLIGHT_ATTR = /\sdata-highlight=(["'])1\1/g;
|
|
19
|
+
const LINE_SPAN = /<span\b([^>]*)>/g;
|
|
20
|
+
const LINE_CLASS = /\bclass=(["'])[^"']*\bline\b[^"']*\1/;
|
|
21
|
+
const DATA_LINE = /\bdata-line=(["'])(\d+)\1/;
|
|
22
|
+
const STYLE_ATTR = /\sstyle=(["'])(.*?)\1/g;
|
|
23
|
+
const DIM_STYLE_DECL = "opacity: var(--honeydeck-code-line-dim-opacity);";
|
|
24
|
+
|
|
25
|
+
function removeDimStyle(html: string): string {
|
|
26
|
+
return html.replace(STYLE_ATTR, (match, quote: string, style: string) => {
|
|
27
|
+
if (!style.includes(DIM_STYLE_DECL)) return match;
|
|
28
|
+
|
|
29
|
+
const cleanedStyle = style
|
|
30
|
+
.replace(DIM_STYLE_DECL, "")
|
|
31
|
+
.replace(/\s{2,}/g, " ")
|
|
32
|
+
.trim();
|
|
33
|
+
|
|
34
|
+
return cleanedStyle ? ` style=${quote}${cleanedStyle}${quote}` : "";
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function addHighlightAttributes(attrs: string): string {
|
|
39
|
+
return `${attrs} data-highlight="1"`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function addDimAttributes(attrs: string): string {
|
|
43
|
+
if (attrs.includes(DIM_STYLE_DECL)) return `${attrs} data-dim="1"`;
|
|
44
|
+
|
|
45
|
+
const withStyle = attrs.replace(
|
|
46
|
+
STYLE_ATTR,
|
|
47
|
+
(_match, quote: string, style: string) =>
|
|
48
|
+
` style=${quote}${style.trim()}${style.trim().endsWith(";") ? "" : ";"} ${DIM_STYLE_DECL}${quote}`,
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
if (withStyle !== attrs) return `${withStyle} data-dim="1"`;
|
|
52
|
+
|
|
53
|
+
return `${attrs} data-dim="1" style="${DIM_STYLE_DECL}"`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function applyCodeStepDimming(
|
|
57
|
+
html: string,
|
|
58
|
+
steps: StepGroup[],
|
|
59
|
+
stepIndex: number,
|
|
60
|
+
startAt: number,
|
|
61
|
+
): string {
|
|
62
|
+
const cleanHtml = removeDimStyle(
|
|
63
|
+
html.replace(DATA_DIM_ATTR, "").replace(DATA_HIGHLIGHT_ATTR, ""),
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
if (steps.length === 0) return cleanHtml;
|
|
67
|
+
|
|
68
|
+
let activeGroupIndex = 0;
|
|
69
|
+
if (startAt > 0 && stepIndex >= startAt) {
|
|
70
|
+
activeGroupIndex = Math.min(stepIndex - startAt + 1, steps.length - 1);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const activeGroup = steps[activeGroupIndex];
|
|
74
|
+
if (!activeGroup || activeGroup === "all") return cleanHtml;
|
|
75
|
+
|
|
76
|
+
return cleanHtml.replace(LINE_SPAN, (match, attrs: string) => {
|
|
77
|
+
if (!LINE_CLASS.test(attrs)) return match;
|
|
78
|
+
|
|
79
|
+
const dataLine = attrs.match(DATA_LINE);
|
|
80
|
+
if (!dataLine) return match;
|
|
81
|
+
|
|
82
|
+
const lineNumber = parseInt(dataLine[2] ?? "", 10);
|
|
83
|
+
if (activeGroup.includes(lineNumber)) {
|
|
84
|
+
return `<span${addHighlightAttributes(attrs)}>`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return `<span${addDimAttributes(attrs)}>`;
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Renders a pre-highlighted code block and applies timeline-driven line
|
|
93
|
+
* dimming for step-through walkthroughs.
|
|
94
|
+
*
|
|
95
|
+
* Export name matches the identifier injected by `remarkShikiCodeBlocks`:
|
|
96
|
+
* `import { HoneydeckCodeBlock } from '@honeydeck/honeydeck/components/code-block/normal'`
|
|
97
|
+
*/
|
|
98
|
+
export function HoneydeckCodeBlock({
|
|
99
|
+
html,
|
|
100
|
+
stepsJson,
|
|
101
|
+
startAt,
|
|
102
|
+
source,
|
|
103
|
+
}: HoneydeckCodeBlockProps) {
|
|
104
|
+
const { stepIndex } = useTimeline();
|
|
105
|
+
|
|
106
|
+
// Parse step groups once (stepsJson is static — set at compile time)
|
|
107
|
+
const steps = useMemo(
|
|
108
|
+
() => parseJsonProp<StepGroup[]>(stepsJson, []),
|
|
109
|
+
[stepsJson],
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const dimmedHtml = useMemo(
|
|
113
|
+
() => applyCodeStepDimming(html, steps, stepIndex, startAt),
|
|
114
|
+
[html, startAt, stepIndex, steps],
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<CodeBlock
|
|
119
|
+
source={source}
|
|
120
|
+
className="[&_.line]:transition-opacity [&_.line]:duration-150 [&_.line]:ease-in"
|
|
121
|
+
>
|
|
122
|
+
<div
|
|
123
|
+
// biome-ignore lint/security/noDangerouslySetInnerHtml: Shiki generates this highlighted HTML during MDX compilation.
|
|
124
|
+
dangerouslySetInnerHTML={{ __html: dimmedHtml }}
|
|
125
|
+
/>
|
|
126
|
+
</CodeBlock>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import {
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import {
|
|
3
|
+
TimelineReveal,
|
|
4
|
+
type TimelineRevealElement,
|
|
5
|
+
} from "./TimelineReveal.tsx";
|
|
3
6
|
|
|
4
7
|
// ---------------------------------------------------------------------------
|
|
5
8
|
// Types
|
|
@@ -15,9 +18,13 @@ export type RevealProps = {
|
|
|
15
18
|
* Wrapper element. Injected by the compiler from MDX context:
|
|
16
19
|
* flow/block reveals use `div`, text/inline reveals use `span`.
|
|
17
20
|
*/
|
|
18
|
-
as?:
|
|
21
|
+
as?: TimelineRevealElement;
|
|
22
|
+
/** Slide-local target name for <RevealWith target="..."> synchronization. */
|
|
23
|
+
name?: string;
|
|
19
24
|
/** Additional CSS class for custom transition overrides. */
|
|
20
25
|
className?: string;
|
|
26
|
+
/** Remove hidden content from the DOM/layout instead of reserving space. */
|
|
27
|
+
ephemeral?: boolean;
|
|
21
28
|
children?: ReactNode;
|
|
22
29
|
};
|
|
23
30
|
|
|
@@ -30,7 +37,8 @@ export type RevealProps = {
|
|
|
30
37
|
*
|
|
31
38
|
* Content appears when the slide's current step reaches `at`. Before that it
|
|
32
39
|
* is invisible while still occupying layout space, so reveals do not cause
|
|
33
|
-
* nearby content to jump around.
|
|
40
|
+
* nearby content to jump around. With `ephemeral`, hidden content is not
|
|
41
|
+
* rendered and does not reserve layout space.
|
|
34
42
|
*
|
|
35
43
|
* Reveals are cumulative: once visible, they stay visible as the presenter
|
|
36
44
|
* advances. The default transition is a simple opacity fade.
|
|
@@ -40,43 +48,35 @@ export type RevealProps = {
|
|
|
40
48
|
*
|
|
41
49
|
* Visible from the start.
|
|
42
50
|
*
|
|
43
|
-
* <Reveal>This appears at step 1.</Reveal>
|
|
51
|
+
* <Reveal name="intro">This appears at step 1.</Reveal>
|
|
44
52
|
*
|
|
45
53
|
* <Reveal>This appears at step 2.</Reveal>
|
|
46
54
|
* ```
|
|
47
55
|
*
|
|
48
56
|
* Honeydeck normally injects `at` during MDX compilation. It also injects `as`
|
|
49
57
|
* from the MDX context so block reveals render as `div` and inline reveals
|
|
50
|
-
* render as `span`.
|
|
58
|
+
* render as `span`. Optional `name` values are slide-local targets for
|
|
59
|
+
* `<RevealWith target="...">`.
|
|
51
60
|
*/
|
|
52
61
|
export function Reveal({
|
|
53
|
-
as
|
|
62
|
+
as = "div",
|
|
54
63
|
at = 1,
|
|
64
|
+
name,
|
|
55
65
|
className = "",
|
|
66
|
+
ephemeral = false,
|
|
56
67
|
children,
|
|
57
68
|
}: RevealProps) {
|
|
58
|
-
const { stepIndex, showFutureSteps, futureStepOpacity } = useTimeline();
|
|
59
|
-
const visible = stepIndex >= at;
|
|
60
|
-
const previewFuture = !visible && showFutureSteps;
|
|
61
|
-
|
|
62
|
-
const style: CSSProperties = {
|
|
63
|
-
display: Component === "span" ? "inline" : "block",
|
|
64
|
-
visibility: visible || previewFuture ? "visible" : "hidden",
|
|
65
|
-
opacity: visible ? 1 : previewFuture ? futureStepOpacity : 0,
|
|
66
|
-
transition: "opacity 300ms ease",
|
|
67
|
-
};
|
|
68
|
-
|
|
69
69
|
return (
|
|
70
|
-
<
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
70
|
+
<TimelineReveal
|
|
71
|
+
as={as}
|
|
72
|
+
at={at}
|
|
73
|
+
className={className}
|
|
74
|
+
ephemeral={ephemeral}
|
|
75
|
+
dataAttributes={{
|
|
76
|
+
"data-honeydeck-reveal-id": name,
|
|
77
|
+
}}
|
|
78
78
|
>
|
|
79
79
|
{children}
|
|
80
|
-
</
|
|
80
|
+
</TimelineReveal>
|
|
81
81
|
);
|
|
82
82
|
}
|