@slidev-react/client 0.2.5

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.
Files changed (131) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/LICENSE +21 -0
  3. package/README.md +16 -0
  4. package/package.json +44 -0
  5. package/src/addons/AddonProvider.tsx +25 -0
  6. package/src/addons/g2/G2Chart.tsx +370 -0
  7. package/src/addons/g2/chartPresets.ts +43 -0
  8. package/src/addons/g2/chartThemeTokens.ts +124 -0
  9. package/src/addons/g2/index.ts +36 -0
  10. package/src/addons/g2/style.css +31 -0
  11. package/src/addons/insight/Insight.tsx +10 -0
  12. package/src/addons/insight/InsightAddonProvider.tsx +20 -0
  13. package/src/addons/insight/SpotlightLayout.tsx +11 -0
  14. package/src/addons/insight/index.ts +17 -0
  15. package/src/addons/insight/style.css +34 -0
  16. package/src/addons/mermaid/MermaidDiagram.tsx +379 -0
  17. package/src/addons/mermaid/index.ts +10 -0
  18. package/src/addons/registry.test.ts +28 -0
  19. package/src/addons/registry.ts +61 -0
  20. package/src/addons/types.ts +6 -0
  21. package/src/app/App.tsx +125 -0
  22. package/src/app/README.md +18 -0
  23. package/src/app/providers/SlidesNavigationProvider.tsx +82 -0
  24. package/src/app/usePresentationBootstrap.ts +85 -0
  25. package/src/features/presentation/PresentationStatus.tsx +514 -0
  26. package/src/features/presentation/PrintSlidesView.tsx +350 -0
  27. package/src/features/presentation/browser.ts +5 -0
  28. package/src/features/presentation/draw/DrawOverlay.tsx +170 -0
  29. package/src/features/presentation/draw/DrawProvider.tsx +394 -0
  30. package/src/features/presentation/draw/persistence.test.ts +80 -0
  31. package/src/features/presentation/draw/persistence.ts +54 -0
  32. package/src/features/presentation/exportArtifacts.test.ts +48 -0
  33. package/src/features/presentation/exportArtifacts.ts +6 -0
  34. package/src/features/presentation/location.test.ts +73 -0
  35. package/src/features/presentation/location.ts +113 -0
  36. package/src/features/presentation/navigation/KeyboardController.tsx +73 -0
  37. package/src/features/presentation/navigation/PresentationNavbar.tsx +162 -0
  38. package/src/features/presentation/navigation/ShortcutsHelpOverlay.test.tsx +24 -0
  39. package/src/features/presentation/navigation/ShortcutsHelpOverlay.tsx +111 -0
  40. package/src/features/presentation/navigation/keyboardShortcuts.test.ts +74 -0
  41. package/src/features/presentation/navigation/keyboardShortcuts.ts +221 -0
  42. package/src/features/presentation/navigation/useSlidesNavigation.ts +15 -0
  43. package/src/features/presentation/overview/NotesOverview.tsx +200 -0
  44. package/src/features/presentation/overview/QuickOverview.tsx +126 -0
  45. package/src/features/presentation/path.ts +137 -0
  46. package/src/features/presentation/presenter/FlowTimelinePreview.test.tsx +54 -0
  47. package/src/features/presentation/presenter/FlowTimelinePreview.tsx +274 -0
  48. package/src/features/presentation/presenter/PresenterModeView.tsx +93 -0
  49. package/src/features/presentation/presenter/PresenterShell.tsx +286 -0
  50. package/src/features/presentation/presenter/PresenterSidePreview.tsx +68 -0
  51. package/src/features/presentation/presenter/PresenterTopProgress.tsx +28 -0
  52. package/src/features/presentation/presenter/SpeakerNotesPanel.tsx +51 -0
  53. package/src/features/presentation/presenter/StandaloneModeView.tsx +36 -0
  54. package/src/features/presentation/presenter/persistence.test.ts +26 -0
  55. package/src/features/presentation/presenter/persistence.ts +31 -0
  56. package/src/features/presentation/presenter/presentationSyncBridge.test.ts +87 -0
  57. package/src/features/presentation/presenter/presentationSyncBridge.ts +82 -0
  58. package/src/features/presentation/presenter/stage.ts +15 -0
  59. package/src/features/presentation/presenter/types.ts +30 -0
  60. package/src/features/presentation/presenter/useFullscreen.ts +58 -0
  61. package/src/features/presentation/presenter/useIdleCursor.ts +37 -0
  62. package/src/features/presentation/presenter/usePresentationFlowRuntime.ts +238 -0
  63. package/src/features/presentation/presenter/usePresenterChromeRuntime.ts +358 -0
  64. package/src/features/presentation/presenter/usePresenterSessionState.ts +226 -0
  65. package/src/features/presentation/presenter/useWakeLock.ts +110 -0
  66. package/src/features/presentation/recordingFilename.test.ts +46 -0
  67. package/src/features/presentation/recordingFilename.ts +56 -0
  68. package/src/features/presentation/reveal/Reveal.tsx +119 -0
  69. package/src/features/presentation/reveal/RevealContext.tsx +29 -0
  70. package/src/features/presentation/reveal/useRevealStep.ts +35 -0
  71. package/src/features/presentation/session.test.ts +122 -0
  72. package/src/features/presentation/session.ts +124 -0
  73. package/src/features/presentation/stage/SlidePreviewSurface.tsx +92 -0
  74. package/src/features/presentation/stage/SlideStage.tsx +159 -0
  75. package/src/features/presentation/stage/slideSurface.ts +71 -0
  76. package/src/features/presentation/stage/slideViewport.tsx +47 -0
  77. package/src/features/presentation/sync/adapters/broadcastChannelTransport.ts +40 -0
  78. package/src/features/presentation/sync/adapters/websocketTransport.ts +128 -0
  79. package/src/features/presentation/sync/model/presence.test.ts +42 -0
  80. package/src/features/presentation/sync/model/presence.ts +33 -0
  81. package/src/features/presentation/sync/model/replication.test.ts +72 -0
  82. package/src/features/presentation/sync/model/replication.ts +113 -0
  83. package/src/features/presentation/sync/model/status.test.ts +52 -0
  84. package/src/features/presentation/sync/model/status.ts +33 -0
  85. package/src/features/presentation/types.ts +1 -0
  86. package/src/features/presentation/usePresentationRecorder.ts +194 -0
  87. package/src/features/presentation/usePresentationSync.ts +423 -0
  88. package/src/index.ts +7 -0
  89. package/src/main.tsx +12 -0
  90. package/src/theme/ThemeProvider.test.ts +36 -0
  91. package/src/theme/ThemeProvider.tsx +79 -0
  92. package/src/theme/__mocks__/active-theme.ts +3 -0
  93. package/src/theme/base.css +14 -0
  94. package/src/theme/components.css +231 -0
  95. package/src/theme/index.css +11 -0
  96. package/src/theme/layouts/center.tsx +9 -0
  97. package/src/theme/layouts/cover.tsx +9 -0
  98. package/src/theme/layouts/default.tsx +5 -0
  99. package/src/theme/layouts/defaultLayouts.ts +20 -0
  100. package/src/theme/layouts/helpers.tsx +12 -0
  101. package/src/theme/layouts/image-right.tsx +21 -0
  102. package/src/theme/layouts/immersive.tsx +9 -0
  103. package/src/theme/layouts/resolveLayout.ts +9 -0
  104. package/src/theme/layouts/section.tsx +9 -0
  105. package/src/theme/layouts/statement.tsx +9 -0
  106. package/src/theme/layouts/two-cols.tsx +21 -0
  107. package/src/theme/layouts/types.ts +1 -0
  108. package/src/theme/layouts.css +133 -0
  109. package/src/theme/mark.css +379 -0
  110. package/src/theme/print.css +106 -0
  111. package/src/theme/prose.css +263 -0
  112. package/src/theme/registry.test.ts +21 -0
  113. package/src/theme/registry.ts +40 -0
  114. package/src/theme/tokens.css +148 -0
  115. package/src/theme/transitions.css +141 -0
  116. package/src/theme/types.ts +9 -0
  117. package/src/theme/useResolvedLayout.ts +24 -0
  118. package/src/types/generated-slides.d.ts +7 -0
  119. package/src/types/mdx-components.ts +7 -0
  120. package/src/types/plantuml-encoder.d.ts +7 -0
  121. package/src/ui/diagrams/PlantUmlDiagram.tsx +33 -0
  122. package/src/ui/mdx/MagicMoveDemo.tsx +114 -0
  123. package/src/ui/mdx/index.ts +21 -0
  124. package/src/ui/primitives/Annotate.test.tsx +64 -0
  125. package/src/ui/primitives/Annotate.tsx +82 -0
  126. package/src/ui/primitives/Badge.tsx +5 -0
  127. package/src/ui/primitives/Callout.tsx +24 -0
  128. package/src/ui/primitives/ChromeIconButton.tsx +58 -0
  129. package/src/ui/primitives/ChromePanel.tsx +79 -0
  130. package/src/ui/primitives/ChromeTag.tsx +70 -0
  131. package/src/ui/primitives/FormSelect.tsx +51 -0
@@ -0,0 +1,54 @@
1
+ import { renderToStaticMarkup } from "react-dom/server";
2
+ import { describe, expect, it, vi } from "vitest";
3
+ import { AddonProvider } from "../../../addons/AddonProvider";
4
+ import { DEFAULT_SLIDES_VIEWPORT } from "@slidev-react/core/slides/viewport";
5
+ import { ThemeProvider } from "../../../theme/ThemeProvider";
6
+ import { FlowTimelinePreview } from "./FlowTimelinePreview";
7
+ import type { CompiledSlide } from "./types";
8
+
9
+ const demoSlide: CompiledSlide = {
10
+ id: "timeline-demo",
11
+ component: function DemoSlide() {
12
+ return <div>Timeline Demo</div>;
13
+ },
14
+ meta: {
15
+ title: "Timeline Demo",
16
+ },
17
+ };
18
+
19
+ function renderPreview(props?: Partial<React.ComponentProps<typeof FlowTimelinePreview>>) {
20
+ return renderToStaticMarkup(
21
+ <ThemeProvider>
22
+ <AddonProvider>
23
+ <FlowTimelinePreview
24
+ slide={demoSlide}
25
+ currentClicks={1}
26
+ currentClicksTotal={3}
27
+ slidesConfig={{ slidesViewport: DEFAULT_SLIDES_VIEWPORT }}
28
+ onJumpToCue={vi.fn()}
29
+ {...props}
30
+ />
31
+ </AddonProvider>
32
+ </ThemeProvider>,
33
+ );
34
+ }
35
+
36
+ describe("FlowTimelinePreview", () => {
37
+ it("renders timeline controls and cue nodes for the current slide", () => {
38
+ const html = renderPreview();
39
+
40
+ expect(html).toContain("Timeline Preview");
41
+ expect(html).toContain("Start");
42
+ expect(html).toContain("Cue 1");
43
+ expect(html).toContain("Cue 3");
44
+ });
45
+
46
+ it("shows an empty-state message when the slide has no cue steps", () => {
47
+ const html = renderPreview({
48
+ currentClicks: 0,
49
+ currentClicksTotal: 0,
50
+ });
51
+
52
+ expect(html).toContain("No cue steps detected on this slide yet.");
53
+ });
54
+ });
@@ -0,0 +1,274 @@
1
+ import { X } from "lucide-react";
2
+ import { useEffect, useMemo, useState } from "react";
3
+ import type { SlidesConfig } from "./types";
4
+ import { resolveSlideSurface, resolveSlideSurfaceClassName } from "../stage/slideSurface";
5
+ import { RevealProvider, type RevealContextValue } from "../reveal/RevealContext";
6
+ import { useResolvedLayout } from "../../../theme/useResolvedLayout";
7
+ import { resolveOverviewStageMetrics } from "./stage";
8
+ import type { CompiledSlide } from "./types";
9
+ import { ChromeIconButton } from "../../../ui/primitives/ChromeIconButton";
10
+ import { ChromePanel } from "../../../ui/primitives/ChromePanel";
11
+ import { ChromeTag, chromeTagClassName } from "../../../ui/primitives/ChromeTag";
12
+
13
+ type FlowPreviewMode = "live" | "steps" | "final";
14
+
15
+ function noopCleanup() {}
16
+
17
+ function noopRegisterStep() {
18
+ return noopCleanup;
19
+ }
20
+
21
+ function createRevealContextValue({
22
+ slideId,
23
+ clicks,
24
+ clicksTotal,
25
+ }: {
26
+ slideId: string;
27
+ clicks: number;
28
+ clicksTotal: number;
29
+ }): RevealContextValue {
30
+ return {
31
+ slideId,
32
+ clicks,
33
+ clicksTotal,
34
+ setClicks: () => {},
35
+ registerStep: noopRegisterStep,
36
+ advance: () => {},
37
+ retreat: () => {},
38
+ canAdvance: clicks < clicksTotal,
39
+ canRetreat: clicks > 0,
40
+ };
41
+ }
42
+
43
+ function resolvePreviewStep({
44
+ mode,
45
+ currentClicks,
46
+ currentClicksTotal,
47
+ selectedClicks,
48
+ }: {
49
+ mode: FlowPreviewMode;
50
+ currentClicks: number;
51
+ currentClicksTotal: number;
52
+ selectedClicks: number;
53
+ }) {
54
+ if (mode === "final") return currentClicksTotal;
55
+ if (mode === "steps") return selectedClicks;
56
+
57
+ return currentClicks;
58
+ }
59
+
60
+ function describePreviewStep(step: number, total: number) {
61
+ if (total <= 0) return "Base state";
62
+ if (step <= 0) return "Before cue 1";
63
+ if (step >= total) return `Cue ${total}/${total} • final state`;
64
+
65
+ return `Cue ${step}/${total}`;
66
+ }
67
+
68
+ export function FlowTimelinePreview({
69
+ slide,
70
+ currentClicks,
71
+ currentClicksTotal,
72
+ slidesConfig,
73
+ onJumpToCue,
74
+ onClose,
75
+ className,
76
+ }: {
77
+ slide: CompiledSlide;
78
+ currentClicks: number;
79
+ currentClicksTotal: number;
80
+ slidesConfig: Pick<SlidesConfig, "slidesViewport" | "slidesLayout" | "slidesBackground">;
81
+ onJumpToCue?: (cueIndex: number) => void;
82
+ onClose?: () => void;
83
+ className?: string;
84
+ }) {
85
+ const { slidesViewport, slidesLayout, slidesBackground } = slidesConfig;
86
+ const [mode, setMode] = useState<FlowPreviewMode>("live");
87
+ const [selectedClicks, setSelectedClicks] = useState(currentClicks);
88
+ const Layout = useResolvedLayout(slide.meta.layout ?? slidesLayout);
89
+ const Slide = slide.component;
90
+ const overviewStage = useMemo(
91
+ () => resolveOverviewStageMetrics(slidesViewport),
92
+ [slidesViewport],
93
+ );
94
+ const cueSteps = useMemo(
95
+ () => Array.from({ length: currentClicksTotal + 1 }, (_, index) => index),
96
+ [currentClicksTotal],
97
+ );
98
+
99
+ useEffect(() => {
100
+ setSelectedClicks(currentClicks);
101
+ }, [currentClicks, currentClicksTotal, slide.id]);
102
+
103
+ const previewClicks = resolvePreviewStep({
104
+ mode,
105
+ currentClicks,
106
+ currentClicksTotal,
107
+ selectedClicks,
108
+ });
109
+ const revealContextValue = useMemo(
110
+ () =>
111
+ createRevealContextValue({
112
+ slideId: `${slide.id}:timeline-preview`,
113
+ clicks: previewClicks,
114
+ clicksTotal: currentClicksTotal,
115
+ }),
116
+ [currentClicksTotal, previewClicks, slide.id],
117
+ );
118
+ const surface = resolveSlideSurface({
119
+ meta: slide.meta,
120
+ slidesBackground,
121
+ className: resolveSlideSurfaceClassName({
122
+ layout: slide.meta.layout ?? slidesLayout,
123
+ overflowHidden: true,
124
+ }),
125
+ });
126
+ const previewLabel = describePreviewStep(previewClicks, currentClicksTotal);
127
+ const modeDescription =
128
+ mode === "live"
129
+ ? "Mirror the current stage state."
130
+ : mode === "final"
131
+ ? "Flatten to the final result."
132
+ : "Inspect one cue position without changing the stage.";
133
+
134
+ return (
135
+ <ChromePanel className={`flex flex-col ${className ?? ""}`}>
136
+ <div className="mb-3 flex items-center justify-between gap-3">
137
+ <div>
138
+ <p className="text-[11px] font-semibold uppercase tracking-[0.24em] text-slate-500">
139
+ Timeline Preview
140
+ </p>
141
+ <p className="mt-1 text-xs text-slate-500">{modeDescription}</p>
142
+ </div>
143
+ <div className="flex items-center gap-2">
144
+ <ChromeTag>{previewLabel}</ChromeTag>
145
+ {onClose && (
146
+ <ChromeIconButton onClick={onClose} aria-label="Close timeline preview" size="sm">
147
+ <X size={14} />
148
+ </ChromeIconButton>
149
+ )}
150
+ </div>
151
+ </div>
152
+ <ChromePanel
153
+ as="div"
154
+ tone="solid"
155
+ radius="inset"
156
+ padding="none"
157
+ className="mb-3 flex items-center gap-2 p-1"
158
+ >
159
+ {(["live", "steps", "final"] as const).map((value) => {
160
+ const active = mode === value;
161
+ return (
162
+ <button
163
+ key={value}
164
+ type="button"
165
+ onClick={() => setMode(value)}
166
+ className={`inline-flex flex-1 items-center justify-center rounded-md px-3 py-2 text-xs font-semibold uppercase tracking-[0.16em] transition ${
167
+ active
168
+ ? "bg-slate-900 text-white"
169
+ : "text-slate-500 hover:bg-slate-100 hover:text-slate-700"
170
+ }`}
171
+ >
172
+ {value}
173
+ </button>
174
+ );
175
+ })}
176
+ </ChromePanel>
177
+ <ChromePanel
178
+ as="div"
179
+ tone="frame"
180
+ radius="inset"
181
+ padding="none"
182
+ className="mb-3 overflow-hidden"
183
+ >
184
+ <div style={{ height: `${overviewStage.overviewStageHeight}px` }}>
185
+ <div
186
+ className="origin-top-left"
187
+ style={{
188
+ width: `${overviewStage.stageWidth}px`,
189
+ height: `${overviewStage.stageHeight}px`,
190
+ transform: `scale(${overviewStage.overviewStageScale})`,
191
+ transformOrigin: "top left",
192
+ }}
193
+ >
194
+ <RevealProvider value={revealContextValue}>
195
+ <article className={surface.className} style={surface.style}>
196
+ <Layout>
197
+ <Slide />
198
+ </Layout>
199
+ </article>
200
+ </RevealProvider>
201
+ </div>
202
+ </div>
203
+ </ChromePanel>
204
+ <div className="mb-3 flex items-center justify-between gap-3 text-xs text-slate-500">
205
+ <span>
206
+ Current stage:{" "}
207
+ {currentClicksTotal > 0 ? `${currentClicks}/${currentClicksTotal}` : "base"}
208
+ </span>
209
+ {onJumpToCue && previewClicks !== currentClicks && (
210
+ <button
211
+ onClick={() => onJumpToCue(previewClicks)}
212
+ className={chromeTagClassName({
213
+ tone: "active",
214
+ className: "transition hover:bg-emerald-100",
215
+ })}
216
+ >
217
+ Jump To Stage
218
+ </button>
219
+ )}
220
+ </div>
221
+ {currentClicksTotal > 0 ? (
222
+ <div className="grid grid-cols-2 gap-2 overflow-auto pr-1">
223
+ {cueSteps.map((step) => {
224
+ const selected = previewClicks === step;
225
+ const current = currentClicks === step;
226
+ const label = step === 0 ? "Start" : `Cue ${step}`;
227
+
228
+ return (
229
+ <button
230
+ key={step}
231
+ type="button"
232
+ onClick={() => {
233
+ setMode("steps");
234
+ setSelectedClicks(step);
235
+ }}
236
+ className={`rounded-md border px-3 py-2 text-left transition ${
237
+ selected
238
+ ? "border-slate-900 bg-slate-900 text-white"
239
+ : current
240
+ ? "border-emerald-300 bg-emerald-50 text-emerald-800"
241
+ : "border-slate-200 bg-white/90 text-slate-700 hover:border-slate-300 hover:bg-slate-50"
242
+ }`}
243
+ >
244
+ <div className="flex items-center justify-between gap-2">
245
+ <span className="text-sm font-semibold">{label}</span>
246
+ <span
247
+ className={`rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-[0.16em] ${
248
+ selected ? "bg-white/18 text-white" : "bg-slate-100 text-slate-500"
249
+ }`}
250
+ >
251
+ reveal
252
+ </span>
253
+ </div>
254
+ <div
255
+ className={`mt-1 text-xs ${
256
+ selected ? "text-white/78" : current ? "text-emerald-700" : "text-slate-500"
257
+ }`}
258
+ >
259
+ {step === 0
260
+ ? "Base slide state before cues."
261
+ : `Reveal cue ${step} becomes active.`}
262
+ </div>
263
+ </button>
264
+ );
265
+ })}
266
+ </div>
267
+ ) : (
268
+ <ChromePanel as="div" tone="dashed" radius="inset" className="px-4 py-5 text-sm">
269
+ No cue steps detected on this slide yet.
270
+ </ChromePanel>
271
+ )}
272
+ </ChromePanel>
273
+ );
274
+ }
@@ -0,0 +1,93 @@
1
+ import type { LayoutName } from "@slidev-react/core/slides/layout"
2
+ import type { TransitionName } from "@slidev-react/core/slides/transition"
3
+ import type { PresentationCursorState } from "../types"
4
+ import { RevealProvider } from "../reveal/RevealContext"
5
+ import { SlideStage } from "../stage/SlideStage"
6
+ import { PresenterSidePreview } from "./PresenterSidePreview"
7
+ import { SpeakerNotesPanel } from "./SpeakerNotesPanel"
8
+ import type { CompiledSlide, SlidesConfig } from "./types"
9
+ import type { PresentationFlowRuntime } from "./usePresentationFlowRuntime"
10
+ import type { usePresenterChromeRuntime } from "./usePresenterChromeRuntime"
11
+
12
+ export function PresenterModeView({
13
+ currentSlide,
14
+ nextSlide,
15
+ slidesConfig,
16
+ canControl,
17
+ remoteCursor,
18
+ localCursor: _localCursor,
19
+ setLocalCursor,
20
+ flow,
21
+ chrome,
22
+ navigation,
23
+ }: {
24
+ currentSlide: CompiledSlide
25
+ nextSlide: CompiledSlide | null
26
+ slidesConfig: SlidesConfig
27
+ canControl: boolean
28
+ remoteCursor: PresentationCursorState | null
29
+ localCursor: PresentationCursorState | null
30
+ setLocalCursor: (cursor: PresentationCursorState | null) => void
31
+ flow: PresentationFlowRuntime
32
+ chrome: ReturnType<typeof usePresenterChromeRuntime>
33
+ navigation: { currentIndex: number; total: number }
34
+ }) {
35
+ const CurrentSlide = currentSlide.component
36
+
37
+ return (
38
+ <div
39
+ ref={chrome.presenterLayoutRef}
40
+ style={chrome.presenterLayoutStyle}
41
+ className="grid h-full min-h-0 grid-cols-1 gap-0"
42
+ >
43
+ <section className="relative min-h-0 overflow-hidden rounded-md border border-slate-200 bg-white">
44
+ <div className="relative z-0 h-full">
45
+ <RevealProvider value={flow.revealContextValue}>
46
+ <SlideStage
47
+ Slide={CurrentSlide}
48
+ slideId={currentSlide.id}
49
+ meta={currentSlide.meta}
50
+ slidesConfig={slidesConfig}
51
+ remoteCursor={canControl ? null : remoteCursor}
52
+ onCursorChange={canControl ? setLocalCursor : undefined}
53
+ onStageAdvance={
54
+ canControl && !chrome.activeOverlay ? flow.advanceReveal : undefined
55
+ }
56
+ scaleMultiplier={chrome.stageScale}
57
+ />
58
+ </RevealProvider>
59
+ </div>
60
+ </section>
61
+ <div
62
+ role="separator"
63
+ aria-label="Resize presenter sidebar"
64
+ aria-orientation="vertical"
65
+ tabIndex={0}
66
+ onPointerDown={chrome.handleSidebarResizeStart}
67
+ onKeyDown={chrome.handleSidebarResizeKeyDown}
68
+ className="group relative hidden cursor-col-resize lg:block"
69
+ >
70
+ <div className="absolute inset-y-0 left-1/2 w-px -translate-x-1/2 bg-slate-200" />
71
+ <div className="absolute inset-y-0 left-1/2 w-1.5 -translate-x-1/2 rounded-sm bg-slate-300 opacity-0 transition-opacity group-hover:opacity-100" />
72
+ </div>
73
+ <aside className="relative z-10 flex min-h-0 min-w-0 flex-col gap-0 text-slate-900">
74
+ <div className="grid min-h-0 flex-1 gap-0 lg:grid-rows-[minmax(220px,0.92fr)_12px_minmax(0,1.08fr)]">
75
+ <PresenterSidePreview
76
+ title="Up Next"
77
+ indexLabel={nextSlide ? String(navigation.currentIndex + 2) : "--"}
78
+ slide={nextSlide}
79
+ slidesConfig={slidesConfig}
80
+ />
81
+ <div className="flex items-center justify-center px-2" aria-hidden>
82
+ <div className="h-px w-full bg-slate-200" />
83
+ </div>
84
+ <SpeakerNotesPanel
85
+ currentClicks={flow.currentClicks}
86
+ currentClicksTotal={flow.currentClicksTotal}
87
+ notes={currentSlide.meta.notes}
88
+ />
89
+ </div>
90
+ </aside>
91
+ </div>
92
+ )
93
+ }