@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
package/CHANGELOG.md ADDED
@@ -0,0 +1,42 @@
1
+ # @slidev-react/client
2
+
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - ## v0.2.0
8
+
9
+ ### Features
10
+
11
+ - Add React Compiler support for automatic render optimization
12
+ - Add code splitting for better bundle optimization
13
+
14
+ ### Bug Fixes
15
+
16
+ - Fix Mermaid diagram type errors
17
+
18
+ ### Patch Changes
19
+
20
+ - Updated dependencies
21
+ - @slidev-react/core@0.2.0
22
+
23
+ ## 0.1.0
24
+
25
+ ### Minor Changes
26
+
27
+ - ## v0.1.0
28
+
29
+ ### Features
30
+
31
+ - Add React Compiler support for automatic render optimization
32
+ - Add code splitting for better bundle optimization
33
+
34
+ ### Bug Fixes
35
+
36
+ - Fix Mermaid diagram type errors
37
+
38
+ ### Patch Changes
39
+
40
+ - Updated dependencies [ca342f8]
41
+ - Updated dependencies
42
+ - @slidev-react/core@0.1.0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 slidev-react contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,16 @@
1
+ # `@slidev-react/client`
2
+
3
+ `@slidev-react/client` 承接浏览器端能力:
4
+
5
+ - React app 入口
6
+ - providers
7
+ - presentation UI / stage
8
+ - theme 和 MDX runtime 组合
9
+
10
+ 当前这部分源码已经主要收进:
11
+
12
+ - `packages/client/src/app`
13
+ - `packages/client/src/features/presentation`
14
+ - `packages/client/src/theme`
15
+ - `packages/client/src/ui`
16
+ - `packages/client/src/addons`
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@slidev-react/client",
3
+ "version": "0.2.5",
4
+ "description": "Browser-side React app and presentation UI for slidev-react",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "exports": {
8
+ ".": "./src/index.ts"
9
+ },
10
+ "dependencies": {
11
+ "katex": "0.16.33",
12
+ "lucide-react": "0.568.0",
13
+ "plantuml-encoder": "1.4.0",
14
+ "shiki": "4.0.1",
15
+ "shiki-magic-move": "1.2.1",
16
+ "zod": "4.3.6",
17
+ "@slidev-react/core": "0.2.5"
18
+ },
19
+ "peerDependencies": {
20
+ "@antv/g-svg": ">=2.0.0",
21
+ "@antv/g2": ">=5.0.0",
22
+ "@mdx-js/react": ">=3.0.0",
23
+ "mermaid": ">=11.0.0",
24
+ "react": ">=19.0.0",
25
+ "react-dom": ">=19.0.0"
26
+ },
27
+ "peerDependenciesMeta": {
28
+ "@antv/g-svg": {
29
+ "optional": true
30
+ },
31
+ "@antv/g2": {
32
+ "optional": true
33
+ },
34
+ "mermaid": {
35
+ "optional": true
36
+ }
37
+ },
38
+ "engines": {
39
+ "node": ">=22"
40
+ },
41
+ "publishConfig": {
42
+ "access": "public"
43
+ }
44
+ }
@@ -0,0 +1,25 @@
1
+ import { createContext, useContext, useMemo, type ReactNode } from "react";
2
+ import { resolveSlideAddons } from "./registry";
3
+ import type { ResolvedSlideAddons } from "./types";
4
+
5
+ const AddonContext = createContext<ResolvedSlideAddons | null>(null);
6
+
7
+ export function AddonProvider({
8
+ addonIds,
9
+ children,
10
+ }: {
11
+ addonIds?: string[];
12
+ children: ReactNode;
13
+ }) {
14
+ const addonKey = useMemo(() => (addonIds ?? []).join("\0"), [addonIds]);
15
+ const addons = useMemo(() => resolveSlideAddons(addonIds), [addonIds, addonKey]);
16
+
17
+ return <AddonContext.Provider value={addons}>{children}</AddonContext.Provider>;
18
+ }
19
+
20
+ export function useSlideAddons() {
21
+ const context = useContext(AddonContext);
22
+ if (!context) throw new Error("useSlideAddons must be used inside AddonProvider");
23
+
24
+ return context;
25
+ }
@@ -0,0 +1,370 @@
1
+ import { Chart as G2Chart, type G2Spec, register } from '@antv/g2'
2
+ import { Renderer as SvgRenderer } from '@antv/g-svg'
3
+ import { useEffect, useRef, useState } from 'react'
4
+
5
+ // Register composite marks from plotlib (gauge, liquid, wordCloud, boxplot)
6
+ import { plotlib } from '@antv/g2/esm/lib/plot'
7
+ for (const [key, value] of Object.entries(plotlib())) {
8
+ register(key as Parameters<typeof register>[0], value as Parameters<typeof register>[1])
9
+ }
10
+
11
+ import { buildSlidevTheme, sizePresets, type ChartSize } from './chartThemeTokens'
12
+ import { chartPresets, type PresetName } from './chartPresets'
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // Base Chart — L1 (G2Spec passthrough with theme/preset support)
16
+ // ---------------------------------------------------------------------------
17
+
18
+ type ChartProps = G2Spec & {
19
+ width?: number
20
+ height?: number
21
+ /** Named size preset */
22
+ size?: ChartSize
23
+ /** Named chart preset */
24
+ preset?: PresetName
25
+ }
26
+
27
+ export function Chart({
28
+ width,
29
+ height,
30
+ size,
31
+ preset,
32
+ ...spec
33
+ }: ChartProps) {
34
+ const resolved = resolveSize(width, height, size)
35
+ const containerRef = useRef<HTMLDivElement>(null)
36
+ const chartRef = useRef<G2Chart | null>(null)
37
+ const [error, setError] = useState<string | null>(null)
38
+
39
+ useEffect(() => {
40
+ const el = containerRef.current
41
+ if (!el) return
42
+
43
+ let cancelled = false
44
+
45
+ const render = async () => {
46
+ try {
47
+ setError(null)
48
+
49
+ if (chartRef.current) {
50
+ try { chartRef.current.destroy() } catch { /* noop */ }
51
+ chartRef.current = null
52
+ }
53
+
54
+ const chart = new G2Chart({
55
+ container: el,
56
+ renderer: new SvgRenderer(),
57
+ width: resolved.width,
58
+ height: resolved.height,
59
+ })
60
+
61
+ // Merge: preset defaults → user spec → theme
62
+ const presetSpec = preset ? (chartPresets[preset] ?? {}) : {}
63
+ const userTheme = typeof spec.theme === 'object' ? spec.theme : {}
64
+ const slidevTheme = buildSlidevTheme()
65
+
66
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- G2Spec is a huge union; casting is the pragmatic escape
67
+ chart.options({
68
+ ...presetSpec,
69
+ ...spec,
70
+ width: resolved.width,
71
+ height: resolved.height,
72
+ theme: { type: 'classic', ...slidevTheme, ...userTheme },
73
+ } as any)
74
+
75
+ if (cancelled) { chart.destroy(); return }
76
+
77
+ chartRef.current = chart
78
+ await chart.render()
79
+
80
+ if (cancelled) {
81
+ chart.destroy()
82
+ chartRef.current = null
83
+ }
84
+ } catch (err) {
85
+ if (!cancelled) {
86
+ setError(err instanceof Error ? err.message : String(err))
87
+ }
88
+ }
89
+ }
90
+
91
+ void render()
92
+
93
+ return () => {
94
+ cancelled = true
95
+ if (chartRef.current) {
96
+ try { chartRef.current.destroy() } catch { /* noop */ }
97
+ chartRef.current = null
98
+ }
99
+ }
100
+ // eslint-disable-next-line react-hooks/exhaustive-deps
101
+ }, [resolved.width, resolved.height, preset, JSON.stringify(spec)])
102
+
103
+ if (error) {
104
+ return (
105
+ <div className="my-3 rounded-xl border border-rose-300 bg-rose-50 p-3 text-sm text-rose-900">
106
+ Chart render error: {error}
107
+ </div>
108
+ )
109
+ }
110
+
111
+ return (
112
+ <div ref={containerRef} style={{ width: resolved.width, height: resolved.height }} />
113
+ )
114
+ }
115
+
116
+ // ---------------------------------------------------------------------------
117
+ // Helpers
118
+ // ---------------------------------------------------------------------------
119
+
120
+ function resolveSize(
121
+ width: number | undefined,
122
+ height: number | undefined,
123
+ size: ChartSize | undefined,
124
+ ) {
125
+ if (width !== undefined || height !== undefined) {
126
+ return {
127
+ width: width ?? sizePresets.full.width,
128
+ height: height ?? sizePresets.full.height,
129
+ }
130
+ }
131
+ const preset = size ? sizePresets[size] : sizePresets.full
132
+ return { width: preset.width, height: preset.height }
133
+ }
134
+
135
+ // ---------------------------------------------------------------------------
136
+ // Semantic chart components — L3
137
+ // ---------------------------------------------------------------------------
138
+
139
+ type SemanticBase = {
140
+ data: Record<string, unknown>[]
141
+ width?: number
142
+ height?: number
143
+ size?: ChartSize
144
+ }
145
+
146
+ /* ─── BarChart ─── */
147
+
148
+ type BarChartProps = SemanticBase & {
149
+ x: string
150
+ y: string
151
+ color?: string
152
+ group?: string
153
+ stack?: boolean
154
+ }
155
+
156
+ export function BarChart({ data, x, y, color, group, stack, ...rest }: BarChartProps) {
157
+ const encode: Record<string, unknown> = { x, y }
158
+ if (color) encode.color = color
159
+ if (group) encode.color = group
160
+ const transform = stack ? [{ type: 'stackY' as const }] : undefined
161
+ return <Chart type="interval" data={data} encode={encode} transform={transform} {...rest} />
162
+ }
163
+
164
+ /* ─── LineChart ─── */
165
+
166
+ type LineChartProps = SemanticBase & {
167
+ x: string
168
+ y: string
169
+ color?: string
170
+ curve?: boolean
171
+ }
172
+
173
+ export function LineChart({ data, x, y, color, ...rest }: LineChartProps) {
174
+ const encode: Record<string, unknown> = { x, y }
175
+ if (color) encode.color = color
176
+ return <Chart type="line" data={data} encode={encode} {...rest} />
177
+ }
178
+
179
+ /* ─── AreaChart ─── */
180
+
181
+ type AreaChartProps = SemanticBase & {
182
+ x: string
183
+ y: string
184
+ color?: string
185
+ stack?: boolean
186
+ }
187
+
188
+ export function AreaChart({ data, x, y, color, stack, ...rest }: AreaChartProps) {
189
+ const encode: Record<string, unknown> = { x, y }
190
+ if (color) encode.color = color
191
+ const transform = stack ? [{ type: 'stackY' as const }] : undefined
192
+ return <Chart type="area" data={data} encode={encode} transform={transform} {...rest} />
193
+ }
194
+
195
+ /* ─── ScatterChart ─── */
196
+
197
+ type ScatterChartProps = SemanticBase & {
198
+ x: string
199
+ y: string
200
+ color?: string
201
+ sizeField?: string
202
+ shape?: string
203
+ }
204
+
205
+ export function ScatterChart({ data, x, y, color, sizeField, shape, ...rest }: ScatterChartProps) {
206
+ const encode: Record<string, unknown> = { x, y }
207
+ if (color) encode.color = color
208
+ if (sizeField) encode.size = sizeField
209
+ if (shape) encode.shape = shape
210
+ return <Chart type="point" data={data} encode={encode} {...rest} />
211
+ }
212
+
213
+ /* ─── PieChart ─── */
214
+
215
+ type PieChartProps = SemanticBase & {
216
+ value: string
217
+ label: string
218
+ color?: string
219
+ donut?: boolean
220
+ }
221
+
222
+ export function PieChart({ data, value, label, color, donut, ...rest }: PieChartProps) {
223
+ const encode: Record<string, unknown> = { y: value, color: color ?? label }
224
+ const innerRadius = donut ? 0.6 : undefined
225
+ return (
226
+ <Chart
227
+ type="interval"
228
+ data={data}
229
+ encode={encode}
230
+ coordinate={{ type: 'theta' }}
231
+ style={{ stroke: '#fff', lineWidth: 1, ...(innerRadius !== undefined ? { innerRadius } : {}) }}
232
+ {...rest}
233
+ />
234
+ )
235
+ }
236
+
237
+ /* ─── RadarChart ─── */
238
+
239
+ type RadarChartProps = SemanticBase & {
240
+ x: string
241
+ y: string
242
+ color?: string
243
+ area?: boolean
244
+ }
245
+
246
+ export function RadarChart({ data, x, y, color, area, ...rest }: RadarChartProps) {
247
+ const encode: Record<string, unknown> = { x, y }
248
+ if (color) encode.color = color
249
+ if (area) {
250
+ return (
251
+ <Chart
252
+ type="area"
253
+ data={data}
254
+ encode={encode}
255
+ coordinate={{ type: 'polar' }}
256
+ style={{ fillOpacity: 0.5 }}
257
+ axis={{ x: { grid: true }, y: { zIndex: 1, title: false } }}
258
+ {...rest}
259
+ />
260
+ )
261
+ }
262
+ return (
263
+ <Chart
264
+ type="line"
265
+ data={data}
266
+ encode={encode}
267
+ coordinate={{ type: 'polar' }}
268
+ axis={{ x: { grid: true }, y: { zIndex: 1, title: false } }}
269
+ {...rest}
270
+ />
271
+ )
272
+ }
273
+
274
+ /* ─── HeatmapChart ─── */
275
+
276
+ type HeatmapChartProps = SemanticBase & {
277
+ x: string
278
+ y: string
279
+ color: string
280
+ }
281
+
282
+ export function HeatmapChart({ data, x, y, color, ...rest }: HeatmapChartProps) {
283
+ return (
284
+ <Chart
285
+ type="cell"
286
+ data={data}
287
+ encode={{ x, y, color }}
288
+ style={{ inset: 0.5 }}
289
+ {...rest}
290
+ />
291
+ )
292
+ }
293
+
294
+ /* ─── FunnelChart ─── */
295
+
296
+ type FunnelChartProps = SemanticBase & {
297
+ x: string
298
+ y: string
299
+ color?: string
300
+ }
301
+
302
+ export function FunnelChart({ data, x, y, color, ...rest }: FunnelChartProps) {
303
+ const encode: Record<string, unknown> = { x, y, color: color ?? x, shape: 'funnel' }
304
+ return <Chart type="interval" data={data} encode={encode} coordinate={{ transform: [{ type: 'transpose' }] }} {...rest} />
305
+ }
306
+
307
+ /* ─── WordCloud ─── */
308
+
309
+ type WordCloudProps = SemanticBase & {
310
+ text: string
311
+ value: string
312
+ color?: string
313
+ }
314
+
315
+ export function WordCloudChart({ data, text, value, color, ...rest }: WordCloudProps) {
316
+ const encode: Record<string, unknown> = { text, value }
317
+ if (color) encode.color = color
318
+ return <Chart type="wordCloud" data={data} encode={encode} legend={false} {...rest} />
319
+ }
320
+
321
+ /* ─── GaugeChart ─── */
322
+
323
+ type GaugeChartProps = {
324
+ value: number
325
+ min?: number
326
+ max?: number
327
+ width?: number
328
+ height?: number
329
+ size?: ChartSize
330
+ }
331
+
332
+ export function GaugeChart({ value, min = 0, max = 100, ...rest }: GaugeChartProps) {
333
+ const data = { value: { target: value, total: max - min, name: '' } }
334
+ return <Chart type="gauge" data={data} {...rest} />
335
+ }
336
+
337
+ /* ─── TreemapChart ─── */
338
+
339
+ type TreemapChartProps = {
340
+ data: Record<string, unknown>
341
+ value: string
342
+ width?: number
343
+ height?: number
344
+ size?: ChartSize
345
+ }
346
+
347
+ export function TreemapChart({ data, value, ...rest }: TreemapChartProps) {
348
+ return (
349
+ <Chart
350
+ type="treemap"
351
+ data={{ value: data }}
352
+ encode={{ value }}
353
+ {...rest}
354
+ />
355
+ )
356
+ }
357
+
358
+ /* ─── WaterfallChart ─── */
359
+
360
+ type WaterfallChartProps = SemanticBase & {
361
+ x: string
362
+ y: string
363
+ color?: string
364
+ }
365
+
366
+ export function WaterfallChart({ data, x, y, color, ...rest }: WaterfallChartProps) {
367
+ const encode: Record<string, unknown> = { x, y }
368
+ if (color) encode.color = color
369
+ return <Chart type="interval" data={data} encode={encode} transform={[{ type: 'diffY' as const }]} {...rest} />
370
+ }
@@ -0,0 +1,43 @@
1
+ import type { G2Spec } from '@antv/g2'
2
+
3
+ /**
4
+ * Named presets that expand into partial G2Spec configurations.
5
+ * Use via `<Chart preset="pie" />` or in semantic components.
6
+ */
7
+ export type PresetName = keyof typeof chartPresets
8
+
9
+ /* eslint-disable @typescript-eslint/consistent-type-assertions */
10
+ export const chartPresets = {
11
+ // --- Basic marks ---
12
+ bar: { type: 'interval' } as Partial<G2Spec>,
13
+ column: { type: 'interval', coordinate: { transform: [{ type: 'transpose' }] } } as Partial<G2Spec>,
14
+ line: { type: 'line' } as Partial<G2Spec>,
15
+ area: { type: 'area' } as Partial<G2Spec>,
16
+ scatter: { type: 'point' } as Partial<G2Spec>,
17
+
18
+ // --- Polar / radial ---
19
+ pie: { type: 'interval', coordinate: { type: 'theta' }, style: { stroke: '#fff', lineWidth: 1 } } as Partial<G2Spec>,
20
+ donut: { type: 'interval', coordinate: { type: 'theta' }, style: { stroke: '#fff', lineWidth: 1, innerRadius: 0.6 } } as Partial<G2Spec>,
21
+ rose: { type: 'interval', coordinate: { type: 'polar' } } as Partial<G2Spec>,
22
+ radar: { type: 'line', coordinate: { type: 'polar' } } as Partial<G2Spec>,
23
+ radial: { type: 'interval', coordinate: { type: 'radial' } } as Partial<G2Spec>,
24
+
25
+ // --- Statistical ---
26
+ histogram: { type: 'rect', transform: [{ type: 'binX', y: 'count' }] } as Partial<G2Spec>,
27
+ heatmap: { type: 'cell' } as Partial<G2Spec>,
28
+
29
+ // --- Hierarchy / relational ---
30
+ treemap: { type: 'treemap' } as Partial<G2Spec>,
31
+ sunburst: { type: 'sunburst' } as Partial<G2Spec>,
32
+ sankey: { type: 'sankey' } as Partial<G2Spec>,
33
+ wordcloud: { type: 'wordCloud' } as Partial<G2Spec>,
34
+
35
+ // --- Indicator (registered via plotlib) ---
36
+ gauge: { type: 'gauge' } as Partial<G2Spec>,
37
+ liquid: { type: 'liquid' } as Partial<G2Spec>,
38
+
39
+ // --- Composite ---
40
+ funnel: { type: 'interval', coordinate: { transform: [{ type: 'transpose' }] } } as Partial<G2Spec>,
41
+ waterfall: { type: 'interval', transform: [{ type: 'diffY' }] } as Partial<G2Spec>,
42
+ } as const
43
+ /* eslint-enable @typescript-eslint/consistent-type-assertions */
@@ -0,0 +1,124 @@
1
+ import type { G2Theme } from '@antv/g2/esm/runtime/types/theme'
2
+
3
+ // ---------------------------------------------------------------------------
4
+ // Color palettes
5
+ // ---------------------------------------------------------------------------
6
+
7
+ /** 10-color categorical palette for series / groups */
8
+ export const categoryPalette = [
9
+ '#60a5fa', // blue
10
+ '#34d399', // emerald
11
+ '#a78bfa', // violet
12
+ '#f472b6', // pink
13
+ '#fbbf24', // amber
14
+ '#f87171', // red
15
+ '#22d3ee', // cyan
16
+ '#fb923c', // orange
17
+ '#818cf8', // indigo
18
+ '#2dd4bf', // teal
19
+ ]
20
+
21
+ /** Sequential palette for heatmaps / density (light → dark green) */
22
+ export const sequentialPalette = [
23
+ '#dcfce7', '#86efac', '#4ade80', '#22c55e', '#15803d', '#052e16',
24
+ ]
25
+
26
+ /** Diverging palette for positive/negative deviation */
27
+ export const divergingPalette = [
28
+ '#ef4444', '#fca5a5', '#fefce8', '#86efac', '#22c55e',
29
+ ]
30
+
31
+ /** Semantic colors for business meaning */
32
+ export const semanticColors = {
33
+ positive: '#22c55e',
34
+ negative: '#ef4444',
35
+ warning: '#f59e0b',
36
+ neutral: '#94a3b8',
37
+ }
38
+
39
+ // ---------------------------------------------------------------------------
40
+ // Resolve CSS variables at runtime
41
+ // ---------------------------------------------------------------------------
42
+
43
+ function resolveVar(name: string, fallback: string): string {
44
+ if (typeof document === 'undefined') return fallback
45
+ const value = getComputedStyle(document.documentElement).getPropertyValue(name).trim()
46
+ return value || fallback
47
+ }
48
+
49
+ export function resolveChartFont(): string {
50
+ return resolveVar(
51
+ '--font-sans',
52
+ 'Inter, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif',
53
+ )
54
+ }
55
+
56
+ // ---------------------------------------------------------------------------
57
+ // Build G2 theme
58
+ // ---------------------------------------------------------------------------
59
+
60
+ export function buildSlidevTheme(): G2Theme {
61
+ const font = resolveChartFont()
62
+ const textColor = resolveVar('--slide-color-body', '#0f172a')
63
+ const mutedColor = resolveVar('--slide-color-muted', '#475569')
64
+ const accent = '#22c55e'
65
+
66
+ return {
67
+ color: accent,
68
+ category10: categoryPalette,
69
+ category20: categoryPalette,
70
+ axis: {
71
+ labelFontSize: 17,
72
+ labelFill: mutedColor,
73
+ labelFontFamily: font,
74
+ titleFontSize: 19,
75
+ titleFill: textColor,
76
+ titleFontFamily: font,
77
+ titleFontWeight: 'bold',
78
+ gridStroke: '#e2e8f0',
79
+ gridStrokeOpacity: 1,
80
+ lineStroke: '#94a3b8',
81
+ lineLineWidth: 1,
82
+ tickStroke: '#94a3b8',
83
+ },
84
+ legendCategory: {
85
+ itemLabelFontSize: 18,
86
+ itemLabelFill: mutedColor,
87
+ itemLabelFontFamily: font,
88
+ titleFontSize: 19,
89
+ titleFill: textColor,
90
+ titleFontFamily: font,
91
+ titleFontWeight: 'bold',
92
+ },
93
+ title: {
94
+ titleFontSize: 22,
95
+ titleFill: textColor,
96
+ titleFontFamily: font,
97
+ titleFontWeight: 'bold',
98
+ subtitleFontSize: 17,
99
+ subtitleFill: mutedColor,
100
+ subtitleFontFamily: font,
101
+ },
102
+ label: {
103
+ fontSize: 18,
104
+ fontFamily: font,
105
+ fill: mutedColor,
106
+ },
107
+ }
108
+ }
109
+
110
+
111
+ // ---------------------------------------------------------------------------
112
+ // Default sizes
113
+ // ---------------------------------------------------------------------------
114
+
115
+ export type ChartSize = 'full' | 'wide' | 'half' | 'compact' | 'mini'
116
+
117
+ export const sizePresets: Record<ChartSize, { width: number; height: number }> = {
118
+ full: { width: 1280, height: 600 },
119
+ wide: { width: 1280, height: 500 },
120
+ half: { width: 600, height: 400 },
121
+ compact: { width: 400, height: 300 },
122
+ mini: { width: 200, height: 80 },
123
+ }
124
+