@textcortex/slidewise 1.4.0 → 1.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/dist/index.mjs +5136 -5000
- package/dist/index.mjs.map +1 -1
- package/dist/slidewise.css +1 -1
- package/package.json +1 -1
- package/src/SlidewiseEditor.css +91 -0
- package/src/SlidewiseEditor.tsx +32 -0
- package/src/SlidewiseFileEditor.tsx +44 -6
- package/src/compound/LabelsContext.tsx +121 -0
- package/src/compound/SlidewiseRoot.tsx +101 -15
- package/src/compound/SurfacesContext.tsx +128 -0
- package/src/compound/index.ts +13 -0
- package/src/compound/parts.tsx +1 -1
- package/src/compound/topbar/Export.tsx +7 -4
- package/src/compound/topbar/Play.tsx +7 -4
- package/src/compound/topbar/Redo.tsx +6 -3
- package/src/compound/topbar/Root.tsx +2 -1
- package/src/compound/topbar/Save.tsx +7 -5
- package/src/compound/topbar/ThemeToggle.tsx +4 -2
- package/src/compound/topbar/Title.tsx +4 -2
- package/src/compound/topbar/Undo.tsx +6 -3
- package/src/index.ts +5 -0
package/package.json
CHANGED
package/src/SlidewiseEditor.css
CHANGED
|
@@ -34,6 +34,54 @@
|
|
|
34
34
|
--slidewise-bar-bg: var(--app-bg);
|
|
35
35
|
--slidewise-accent: var(--accent);
|
|
36
36
|
|
|
37
|
+
/* Per-surface background tokens. Map onto internal vars by default so
|
|
38
|
+
existing theme styling is preserved. Hosts override any subset (or
|
|
39
|
+
pass the `surfaces` prop on <Slidewise.Root>) to retint individual
|
|
40
|
+
regions independently. */
|
|
41
|
+
--slidewise-bg-app: var(--app-bg);
|
|
42
|
+
--slidewise-bg-topbar: var(--app-bg);
|
|
43
|
+
--slidewise-bg-rail: var(--rail-bg);
|
|
44
|
+
--slidewise-bg-rail-item: transparent;
|
|
45
|
+
--slidewise-bg-rail-item-active: var(--accent-soft);
|
|
46
|
+
--slidewise-bg-canvas-frame: transparent;
|
|
47
|
+
--slidewise-bg-canvas-from: var(--canvas-bg-from);
|
|
48
|
+
--slidewise-bg-canvas-to: var(--canvas-bg-to);
|
|
49
|
+
--slidewise-bg-bottom-toolbar: var(--toolbar-bg);
|
|
50
|
+
--slidewise-bg-right-panel: var(--rail-bg);
|
|
51
|
+
--slidewise-bg-menu: var(--menu-bg);
|
|
52
|
+
--slidewise-bg-tooltip: var(--menu-bg);
|
|
53
|
+
--slidewise-bg-popover: var(--menu-bg);
|
|
54
|
+
--slidewise-bg-dialog: var(--menu-bg);
|
|
55
|
+
--slidewise-bg-slide: #ffffff;
|
|
56
|
+
--slidewise-bg-selection: var(--accent-soft);
|
|
57
|
+
--slidewise-bg-hover: var(--hover);
|
|
58
|
+
--slidewise-bg-active: var(--active);
|
|
59
|
+
--slidewise-bg-input: var(--input-bg);
|
|
60
|
+
--slidewise-bg-button: transparent;
|
|
61
|
+
--slidewise-bg-button-hover: var(--hover);
|
|
62
|
+
--slidewise-bg-pill: var(--smart-grad);
|
|
63
|
+
--slidewise-bg-unsaved-badge: rgba(232, 80, 76, 0.12);
|
|
64
|
+
|
|
65
|
+
/* Motion tokens. Hosts retune the editor's whole animation feel by
|
|
66
|
+
overriding these. Defaults match Material 3 "standard" easings. */
|
|
67
|
+
--slidewise-duration-instant: 0ms;
|
|
68
|
+
--slidewise-duration-fast: 120ms;
|
|
69
|
+
--slidewise-duration-base: 200ms;
|
|
70
|
+
--slidewise-duration-slow: 320ms;
|
|
71
|
+
--slidewise-easing-standard: cubic-bezier(0.2, 0, 0, 1);
|
|
72
|
+
--slidewise-easing-emphasized: cubic-bezier(0.05, 0.7, 0.1, 1);
|
|
73
|
+
--slidewise-easing-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
74
|
+
|
|
75
|
+
/* Per-region animation flags. Set to 0 on a wrapping scope to disable a
|
|
76
|
+
specific region's animations without killing motion globally. They are
|
|
77
|
+
read internally by region-scoped transitions; CSS rules use them via
|
|
78
|
+
`transition-duration: calc(var(--slidewise-duration-base) * var(--slidewise-anim-topbar))`. */
|
|
79
|
+
--slidewise-anim-topbar: 1;
|
|
80
|
+
--slidewise-anim-rail: 1;
|
|
81
|
+
--slidewise-anim-canvas: 1;
|
|
82
|
+
--slidewise-anim-floating-toolbar: 1;
|
|
83
|
+
--slidewise-anim-play-mode: 1;
|
|
84
|
+
|
|
37
85
|
/* Layout backgrounds */
|
|
38
86
|
--app-bg: #ffffff;
|
|
39
87
|
--rail-bg: #fafafb;
|
|
@@ -126,6 +174,12 @@
|
|
|
126
174
|
--canvas-bg-from: #181c38;
|
|
127
175
|
--canvas-bg-to: #0c1027;
|
|
128
176
|
|
|
177
|
+
/* Dark-mode overrides for the public --slidewise-bg-* surface tokens.
|
|
178
|
+
Hosts can still override any one of these from a higher specificity
|
|
179
|
+
selector or via the `surfaces` prop. */
|
|
180
|
+
--slidewise-bg-slide: #ffffff;
|
|
181
|
+
--slidewise-bg-unsaved-badge: rgba(232, 80, 76, 0.18);
|
|
182
|
+
|
|
129
183
|
/* Charcoal-purple surface kit from textcortex/platform#7428 — neutral
|
|
130
184
|
dark base, faint inset highlight, layered shadow that lifts to a
|
|
131
185
|
subtle plum tint on hover. */
|
|
@@ -261,3 +315,40 @@
|
|
|
261
315
|
transition: none;
|
|
262
316
|
}
|
|
263
317
|
}
|
|
318
|
+
|
|
319
|
+
/*
|
|
320
|
+
* Reduced motion plumbing.
|
|
321
|
+
*
|
|
322
|
+
* <Slidewise.Root reduceMotion="system"> → no class; respects the
|
|
323
|
+
* OS preference via the media
|
|
324
|
+
* query below.
|
|
325
|
+
* <Slidewise.Root reduceMotion={true}> → adds `.reduce-motion` class
|
|
326
|
+
* for hard-off.
|
|
327
|
+
* <Slidewise.Root reduceMotion={false}> → adds `.reduce-motion-off`
|
|
328
|
+
* class to override the OS
|
|
329
|
+
* preference (testing /
|
|
330
|
+
* previewing animations).
|
|
331
|
+
*/
|
|
332
|
+
.slidewise-editor.reduce-motion,
|
|
333
|
+
.slidewise-editor.reduce-motion *,
|
|
334
|
+
.slidewise-editor.reduce-motion *::before,
|
|
335
|
+
.slidewise-editor.reduce-motion *::after {
|
|
336
|
+
animation-duration: 0ms !important;
|
|
337
|
+
animation-delay: 0ms !important;
|
|
338
|
+
transition-duration: 0ms !important;
|
|
339
|
+
transition-delay: 0ms !important;
|
|
340
|
+
scroll-behavior: auto !important;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
@media (prefers-reduced-motion: reduce) {
|
|
344
|
+
.slidewise-editor:not(.reduce-motion-off),
|
|
345
|
+
.slidewise-editor:not(.reduce-motion-off) *,
|
|
346
|
+
.slidewise-editor:not(.reduce-motion-off) *::before,
|
|
347
|
+
.slidewise-editor:not(.reduce-motion-off) *::after {
|
|
348
|
+
animation-duration: 0ms !important;
|
|
349
|
+
animation-delay: 0ms !important;
|
|
350
|
+
transition-duration: 0ms !important;
|
|
351
|
+
transition-delay: 0ms !important;
|
|
352
|
+
scroll-behavior: auto !important;
|
|
353
|
+
}
|
|
354
|
+
}
|
package/src/SlidewiseEditor.tsx
CHANGED
|
@@ -15,6 +15,9 @@ import {
|
|
|
15
15
|
CanvasFrame,
|
|
16
16
|
} from "./compound/parts";
|
|
17
17
|
import type { SlidewiseIcons } from "./compound/IconContext";
|
|
18
|
+
import type { SlidewiseLabels } from "./compound/LabelsContext";
|
|
19
|
+
import type { SlidewiseSurfaces } from "./compound/SurfacesContext";
|
|
20
|
+
import type { Transition } from "framer-motion";
|
|
18
21
|
import type { Deck } from "@/lib/types";
|
|
19
22
|
import "./SlidewiseEditor.css";
|
|
20
23
|
|
|
@@ -65,6 +68,16 @@ export interface SlidewiseEditorProps {
|
|
|
65
68
|
showBottomToolbar?: boolean;
|
|
66
69
|
/** Override the bundled Geist font; sets `--font-geist-sans` on the root. */
|
|
67
70
|
fontFamily?: string;
|
|
71
|
+
/**
|
|
72
|
+
* Reduced-motion behavior. `"system"` (default) respects the OS
|
|
73
|
+
* preference; `true` forces motion off; `false` forces it on.
|
|
74
|
+
*/
|
|
75
|
+
reduceMotion?: boolean | "system";
|
|
76
|
+
/**
|
|
77
|
+
* Default framer-motion transition applied via `<MotionConfig>`. Use
|
|
78
|
+
* this to retune the editor's animation feel globally.
|
|
79
|
+
*/
|
|
80
|
+
transition?: Transition;
|
|
68
81
|
/**
|
|
69
82
|
* Per-action icon overrides. Pass a ReactNode for any of `undo`, `redo`,
|
|
70
83
|
* `save`, `play`, `themeLight`, `themeDark`, `export`, `smart` to skin the
|
|
@@ -72,6 +85,17 @@ export interface SlidewiseEditorProps {
|
|
|
72
85
|
* bundled lucide-react icons.
|
|
73
86
|
*/
|
|
74
87
|
icons?: SlidewiseIcons;
|
|
88
|
+
/**
|
|
89
|
+
* User-visible string overrides for i18n. Pass any subset; missing
|
|
90
|
+
* entries fall back to English defaults.
|
|
91
|
+
*/
|
|
92
|
+
labels?: SlidewiseLabels;
|
|
93
|
+
/**
|
|
94
|
+
* Per-surface background overrides, equivalent to setting the
|
|
95
|
+
* `--slidewise-bg-*` CSS variables. See `SlidewiseSurfaces` for the full
|
|
96
|
+
* list of keys.
|
|
97
|
+
*/
|
|
98
|
+
surfaces?: SlidewiseSurfaces;
|
|
75
99
|
/** Extra class names appended to the editor root. */
|
|
76
100
|
className?: string;
|
|
77
101
|
/** Inline style applied to the editor root. */
|
|
@@ -122,7 +146,11 @@ export const SlidewiseEditor = forwardRef<
|
|
|
122
146
|
showTopBar = true,
|
|
123
147
|
showBottomToolbar = true,
|
|
124
148
|
fontFamily,
|
|
149
|
+
reduceMotion,
|
|
150
|
+
transition,
|
|
125
151
|
icons,
|
|
152
|
+
labels,
|
|
153
|
+
surfaces,
|
|
126
154
|
className,
|
|
127
155
|
style,
|
|
128
156
|
},
|
|
@@ -145,7 +173,11 @@ export const SlidewiseEditor = forwardRef<
|
|
|
145
173
|
theme,
|
|
146
174
|
initialSlideId,
|
|
147
175
|
fontFamily,
|
|
176
|
+
reduceMotion,
|
|
177
|
+
transition,
|
|
148
178
|
icons,
|
|
179
|
+
labels,
|
|
180
|
+
surfaces,
|
|
149
181
|
className,
|
|
150
182
|
style,
|
|
151
183
|
};
|
|
@@ -10,6 +10,10 @@ import { SlidewiseEditor, type SlidewiseEditorHandle } from "./SlidewiseEditor";
|
|
|
10
10
|
import { parsePptx, serializeDeck } from "@/lib/pptx";
|
|
11
11
|
import type { Deck } from "@/lib/types";
|
|
12
12
|
import type { SlidewiseIcons } from "./compound/IconContext";
|
|
13
|
+
import type { SlidewiseLabels } from "./compound/LabelsContext";
|
|
14
|
+
import type { SlidewiseSurfaces } from "./compound/SurfacesContext";
|
|
15
|
+
import { DEFAULT_LABELS } from "./compound/LabelsContext";
|
|
16
|
+
import type { Transition } from "framer-motion";
|
|
13
17
|
import type { HistoryState, SelectionSnapshot } from "./compound/SlidewiseRoot";
|
|
14
18
|
|
|
15
19
|
export interface SlidewiseFileEditorProps {
|
|
@@ -91,6 +95,24 @@ export interface SlidewiseFileEditorProps {
|
|
|
91
95
|
* editor's chrome with your own icon set.
|
|
92
96
|
*/
|
|
93
97
|
icons?: SlidewiseIcons;
|
|
98
|
+
/**
|
|
99
|
+
* User-visible string overrides for i18n. Pass any subset; missing
|
|
100
|
+
* entries fall back to English defaults. The "Unsaved changes" badge
|
|
101
|
+
* and the loading / load-error messages also key off this table.
|
|
102
|
+
*/
|
|
103
|
+
labels?: SlidewiseLabels;
|
|
104
|
+
/**
|
|
105
|
+
* Per-surface background overrides; equivalent to setting the
|
|
106
|
+
* `--slidewise-bg-*` CSS variables.
|
|
107
|
+
*/
|
|
108
|
+
surfaces?: SlidewiseSurfaces;
|
|
109
|
+
/**
|
|
110
|
+
* Reduced-motion behavior. `"system"` (default) respects the OS
|
|
111
|
+
* preference; `true` forces motion off; `false` forces it on.
|
|
112
|
+
*/
|
|
113
|
+
reduceMotion?: boolean | "system";
|
|
114
|
+
/** Default framer-motion transition applied via `<MotionConfig>`. */
|
|
115
|
+
transition?: Transition;
|
|
94
116
|
className?: string;
|
|
95
117
|
style?: CSSProperties;
|
|
96
118
|
/**
|
|
@@ -193,6 +215,10 @@ export const SlidewiseFileEditor = forwardRef<
|
|
|
193
215
|
showBottomToolbar,
|
|
194
216
|
fontFamily,
|
|
195
217
|
icons,
|
|
218
|
+
labels,
|
|
219
|
+
surfaces,
|
|
220
|
+
reduceMotion,
|
|
221
|
+
transition,
|
|
196
222
|
className,
|
|
197
223
|
style,
|
|
198
224
|
parse = parsePptx,
|
|
@@ -337,15 +363,18 @@ export const SlidewiseFileEditor = forwardRef<
|
|
|
337
363
|
if (state.status === "loading") {
|
|
338
364
|
return (
|
|
339
365
|
<div style={{ ...frameStyle, ...style }} className={className}>
|
|
340
|
-
<div style={messageStyle}>
|
|
366
|
+
<div style={messageStyle}>
|
|
367
|
+
{labels?.fileLoading ?? DEFAULT_LABELS.fileLoading}
|
|
368
|
+
</div>
|
|
341
369
|
</div>
|
|
342
370
|
);
|
|
343
371
|
}
|
|
344
372
|
if (state.status === "error") {
|
|
373
|
+
const fmt = labels?.fileLoadError ?? DEFAULT_LABELS.fileLoadError;
|
|
345
374
|
return (
|
|
346
375
|
<div style={{ ...frameStyle, ...style }} className={className}>
|
|
347
376
|
<div style={{ ...messageStyle, color: "#E8504C" }}>
|
|
348
|
-
|
|
377
|
+
{fmt(state.error.message)}
|
|
349
378
|
</div>
|
|
350
379
|
</div>
|
|
351
380
|
);
|
|
@@ -363,6 +392,10 @@ export const SlidewiseFileEditor = forwardRef<
|
|
|
363
392
|
showBottomToolbar={showBottomToolbar}
|
|
364
393
|
fontFamily={fontFamily}
|
|
365
394
|
icons={icons}
|
|
395
|
+
labels={labels}
|
|
396
|
+
surfaces={surfaces}
|
|
397
|
+
reduceMotion={reduceMotion}
|
|
398
|
+
transition={transition}
|
|
366
399
|
onChange={(next) => {
|
|
367
400
|
onChangeRef.current?.(next);
|
|
368
401
|
}}
|
|
@@ -382,7 +415,11 @@ export const SlidewiseFileEditor = forwardRef<
|
|
|
382
415
|
await saveBlob(blob);
|
|
383
416
|
}}
|
|
384
417
|
/>
|
|
385
|
-
{dirty &&
|
|
418
|
+
{dirty && (
|
|
419
|
+
<UnsavedBadge
|
|
420
|
+
label={labels?.unsavedBadge ?? DEFAULT_LABELS.unsavedBadge}
|
|
421
|
+
/>
|
|
422
|
+
)}
|
|
386
423
|
</div>
|
|
387
424
|
);
|
|
388
425
|
});
|
|
@@ -403,7 +440,7 @@ const messageStyle: CSSProperties = {
|
|
|
403
440
|
color: "#5b6178",
|
|
404
441
|
};
|
|
405
442
|
|
|
406
|
-
function UnsavedBadge() {
|
|
443
|
+
function UnsavedBadge({ label }: { label: string }) {
|
|
407
444
|
return (
|
|
408
445
|
<div
|
|
409
446
|
style={{
|
|
@@ -411,7 +448,8 @@ function UnsavedBadge() {
|
|
|
411
448
|
top: 12,
|
|
412
449
|
right: 12,
|
|
413
450
|
padding: "4px 10px",
|
|
414
|
-
background:
|
|
451
|
+
background:
|
|
452
|
+
"var(--slidewise-bg-unsaved-badge, rgba(232, 80, 76, 0.12))",
|
|
415
453
|
color: "#E8504C",
|
|
416
454
|
borderRadius: 999,
|
|
417
455
|
fontFamily: "Inter, system-ui, sans-serif",
|
|
@@ -422,7 +460,7 @@ function UnsavedBadge() {
|
|
|
422
460
|
pointerEvents: "none",
|
|
423
461
|
}}
|
|
424
462
|
>
|
|
425
|
-
|
|
463
|
+
{label}
|
|
426
464
|
</div>
|
|
427
465
|
);
|
|
428
466
|
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { createContext, useContext, useMemo, type ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Every user-visible string in the editor's chrome. Pass any subset on
|
|
5
|
+
* `<Slidewise.Root labels={...}>` to localise; missing entries fall back
|
|
6
|
+
* to the English defaults. Use this prop together with `icons` to fully
|
|
7
|
+
* skin Slidewise for non-English deployments.
|
|
8
|
+
*
|
|
9
|
+
* Single-string entries are used both as the button's visible label AND
|
|
10
|
+
* as its `aria-label`. Where the two need to diverge (the Save button
|
|
11
|
+
* cycles through three states; the theme toggle shows different labels
|
|
12
|
+
* per theme), nested keys are provided.
|
|
13
|
+
*/
|
|
14
|
+
export interface SlidewiseLabels {
|
|
15
|
+
// TopBar buttons (visible text)
|
|
16
|
+
save?: { idle?: string; saving?: string; saved?: string };
|
|
17
|
+
play?: string;
|
|
18
|
+
stop?: string;
|
|
19
|
+
export?: string;
|
|
20
|
+
undo?: string;
|
|
21
|
+
redo?: string;
|
|
22
|
+
themeToggle?: { toDark?: string; toLight?: string };
|
|
23
|
+
|
|
24
|
+
// Pills + status indicators
|
|
25
|
+
smart?: string;
|
|
26
|
+
unsavedBadge?: string;
|
|
27
|
+
|
|
28
|
+
// Inputs
|
|
29
|
+
titleAriaLabel?: string;
|
|
30
|
+
|
|
31
|
+
// SlideRail (reserved for the future SlideRail compound subparts)
|
|
32
|
+
addSlide?: string;
|
|
33
|
+
duplicateSlide?: string;
|
|
34
|
+
deleteSlide?: string;
|
|
35
|
+
|
|
36
|
+
// Errors
|
|
37
|
+
fileLoadError?: (msg: string) => string;
|
|
38
|
+
fileLoading?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The fully-resolved label table — every key present, no optionals. Internal
|
|
43
|
+
* components read this directly so they never have to check for undefined.
|
|
44
|
+
*/
|
|
45
|
+
export interface ResolvedLabels {
|
|
46
|
+
save: { idle: string; saving: string; saved: string };
|
|
47
|
+
play: string;
|
|
48
|
+
stop: string;
|
|
49
|
+
export: string;
|
|
50
|
+
undo: string;
|
|
51
|
+
redo: string;
|
|
52
|
+
themeToggle: { toDark: string; toLight: string };
|
|
53
|
+
smart: string;
|
|
54
|
+
unsavedBadge: string;
|
|
55
|
+
titleAriaLabel: string;
|
|
56
|
+
addSlide: string;
|
|
57
|
+
duplicateSlide: string;
|
|
58
|
+
deleteSlide: string;
|
|
59
|
+
fileLoadError: (msg: string) => string;
|
|
60
|
+
fileLoading: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const DEFAULT_LABELS: ResolvedLabels = {
|
|
64
|
+
save: { idle: "Save", saving: "Saving…", saved: "Saved" },
|
|
65
|
+
play: "Play",
|
|
66
|
+
stop: "Stop",
|
|
67
|
+
export: "Export",
|
|
68
|
+
undo: "Undo",
|
|
69
|
+
redo: "Redo",
|
|
70
|
+
themeToggle: { toDark: "Dark mode", toLight: "Light mode" },
|
|
71
|
+
smart: "Smart",
|
|
72
|
+
unsavedBadge: "Unsaved changes",
|
|
73
|
+
titleAriaLabel: "Deck title",
|
|
74
|
+
addSlide: "Add slide",
|
|
75
|
+
duplicateSlide: "Duplicate slide",
|
|
76
|
+
deleteSlide: "Delete slide",
|
|
77
|
+
fileLoadError: (msg) => `Could not open file: ${msg}`,
|
|
78
|
+
fileLoading: "Loading…",
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
function mergeLabels(
|
|
82
|
+
overrides: SlidewiseLabels | undefined
|
|
83
|
+
): ResolvedLabels {
|
|
84
|
+
if (!overrides) return DEFAULT_LABELS;
|
|
85
|
+
return {
|
|
86
|
+
save: { ...DEFAULT_LABELS.save, ...overrides.save },
|
|
87
|
+
play: overrides.play ?? DEFAULT_LABELS.play,
|
|
88
|
+
stop: overrides.stop ?? DEFAULT_LABELS.stop,
|
|
89
|
+
export: overrides.export ?? DEFAULT_LABELS.export,
|
|
90
|
+
undo: overrides.undo ?? DEFAULT_LABELS.undo,
|
|
91
|
+
redo: overrides.redo ?? DEFAULT_LABELS.redo,
|
|
92
|
+
themeToggle: { ...DEFAULT_LABELS.themeToggle, ...overrides.themeToggle },
|
|
93
|
+
smart: overrides.smart ?? DEFAULT_LABELS.smart,
|
|
94
|
+
unsavedBadge: overrides.unsavedBadge ?? DEFAULT_LABELS.unsavedBadge,
|
|
95
|
+
titleAriaLabel: overrides.titleAriaLabel ?? DEFAULT_LABELS.titleAriaLabel,
|
|
96
|
+
addSlide: overrides.addSlide ?? DEFAULT_LABELS.addSlide,
|
|
97
|
+
duplicateSlide: overrides.duplicateSlide ?? DEFAULT_LABELS.duplicateSlide,
|
|
98
|
+
deleteSlide: overrides.deleteSlide ?? DEFAULT_LABELS.deleteSlide,
|
|
99
|
+
fileLoadError: overrides.fileLoadError ?? DEFAULT_LABELS.fileLoadError,
|
|
100
|
+
fileLoading: overrides.fileLoading ?? DEFAULT_LABELS.fileLoading,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const LabelsContext = createContext<ResolvedLabels>(DEFAULT_LABELS);
|
|
105
|
+
|
|
106
|
+
export function LabelsProvider({
|
|
107
|
+
labels,
|
|
108
|
+
children,
|
|
109
|
+
}: {
|
|
110
|
+
labels: SlidewiseLabels | undefined;
|
|
111
|
+
children: ReactNode;
|
|
112
|
+
}) {
|
|
113
|
+
const resolved = useMemo(() => mergeLabels(labels), [labels]);
|
|
114
|
+
return (
|
|
115
|
+
<LabelsContext.Provider value={resolved}>{children}</LabelsContext.Provider>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function useLabels(): ResolvedLabels {
|
|
120
|
+
return useContext(LabelsContext);
|
|
121
|
+
}
|
|
@@ -18,10 +18,17 @@ import { collectFontFamilies, ensureGoogleFontsLoaded } from "@/lib/fonts";
|
|
|
18
18
|
import type { Deck } from "@/lib/types";
|
|
19
19
|
import { GridView } from "@/components/editor/GridView";
|
|
20
20
|
import { PlayMode } from "@/components/editor/PlayMode";
|
|
21
|
+
import { MotionConfig, type Transition } from "framer-motion";
|
|
21
22
|
import { HostProvider } from "./HostContext";
|
|
22
23
|
import { IconProvider, type SlidewiseIcons } from "./IconContext";
|
|
23
24
|
import { ReadOnlyProvider } from "./ReadOnlyContext";
|
|
24
25
|
import { DirtyProvider } from "./DirtyContext";
|
|
26
|
+
import { LabelsProvider, type SlidewiseLabels } from "./LabelsContext";
|
|
27
|
+
import {
|
|
28
|
+
SurfacesProvider,
|
|
29
|
+
surfacesToCssVars,
|
|
30
|
+
type SlidewiseSurfaces,
|
|
31
|
+
} from "./SurfacesContext";
|
|
25
32
|
|
|
26
33
|
export interface SlidewiseRootProps {
|
|
27
34
|
/**
|
|
@@ -66,12 +73,51 @@ export interface SlidewiseRootProps {
|
|
|
66
73
|
initialSlideId?: string;
|
|
67
74
|
/** Override the default Geist font; sets `--slidewise-font-sans`. */
|
|
68
75
|
fontFamily?: string;
|
|
76
|
+
/**
|
|
77
|
+
* Controls reduced-motion behavior.
|
|
78
|
+
*
|
|
79
|
+
* - `"system"` (default) — respect the user's OS preference via the
|
|
80
|
+
* `prefers-reduced-motion` media query.
|
|
81
|
+
* - `true` — force all CSS animations + transitions off and tell
|
|
82
|
+
* framer-motion to skip motion. Use for hosts whose own product
|
|
83
|
+
* already has a global motion-off toggle.
|
|
84
|
+
* - `false` — force motion on even when the OS reports reduced-motion.
|
|
85
|
+
* Useful for previewing animations during development; not generally
|
|
86
|
+
* recommended in production since it overrides an accessibility hint.
|
|
87
|
+
*/
|
|
88
|
+
reduceMotion?: boolean | "system";
|
|
89
|
+
/**
|
|
90
|
+
* Default framer-motion transition. Passed through to a wrapping
|
|
91
|
+
* `<MotionConfig>` so every motion component inside the editor inherits
|
|
92
|
+
* it. Useful for retuning the editor's overall feel (faster, springier,
|
|
93
|
+
* etc.) without touching individual components.
|
|
94
|
+
*
|
|
95
|
+
* For CSS transitions, override the duration/easing tokens instead —
|
|
96
|
+
* `--slidewise-duration-base`, `--slidewise-easing-standard`, etc.
|
|
97
|
+
*/
|
|
98
|
+
transition?: Transition;
|
|
69
99
|
/**
|
|
70
100
|
* Per-action icon overrides for the chrome. Hosts pass any subset to
|
|
71
101
|
* skin Slidewise with their own icon set; missing slots fall back to
|
|
72
102
|
* the bundled lucide icons.
|
|
73
103
|
*/
|
|
74
104
|
icons?: SlidewiseIcons;
|
|
105
|
+
/**
|
|
106
|
+
* User-visible string overrides for i18n. Pass any subset; missing
|
|
107
|
+
* entries fall back to English defaults. Pairs with `icons` for full
|
|
108
|
+
* locale customization.
|
|
109
|
+
*/
|
|
110
|
+
labels?: SlidewiseLabels;
|
|
111
|
+
/**
|
|
112
|
+
* Per-surface background overrides. Equivalent to setting the
|
|
113
|
+
* `--slidewise-bg-*` CSS variables on the root, but as a typed prop so
|
|
114
|
+
* hosts can drive theming from JS without writing CSS:
|
|
115
|
+
*
|
|
116
|
+
* ```tsx
|
|
117
|
+
* <Slidewise.Root surfaces={{ app: "#0b0d10", rail: "#1c1c22" }}>
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
surfaces?: SlidewiseSurfaces;
|
|
75
121
|
/** Extra class names appended to the root. */
|
|
76
122
|
className?: string;
|
|
77
123
|
/** Inline style applied to the root. */
|
|
@@ -190,7 +236,11 @@ function RootInner({
|
|
|
190
236
|
theme,
|
|
191
237
|
initialSlideId,
|
|
192
238
|
fontFamily,
|
|
239
|
+
reduceMotion = "system",
|
|
240
|
+
transition,
|
|
193
241
|
icons,
|
|
242
|
+
labels,
|
|
243
|
+
surfaces,
|
|
194
244
|
className,
|
|
195
245
|
style,
|
|
196
246
|
children,
|
|
@@ -429,22 +479,58 @@ function RootInner({
|
|
|
429
479
|
}
|
|
430
480
|
: undefined;
|
|
431
481
|
|
|
482
|
+
// Merge host's `style` prop with the surface overrides so a host can
|
|
483
|
+
// pass both without one clobbering the other. Surfaces win on conflict
|
|
484
|
+
// because they're the more specific theming intent.
|
|
485
|
+
const surfaceVars = surfacesToCssVars(surfaces);
|
|
486
|
+
const mergedStyle: CSSProperties | undefined = surfaceVars
|
|
487
|
+
? { ...style, ...(surfaceVars as CSSProperties) }
|
|
488
|
+
: style;
|
|
489
|
+
|
|
490
|
+
// Map our reduceMotion enum to:
|
|
491
|
+
// - a CSS class on the root (drives the @media + class rules in
|
|
492
|
+
// SlidewiseEditor.css)
|
|
493
|
+
// - framer-motion's reducedMotion prop on MotionConfig
|
|
494
|
+
const motionClass =
|
|
495
|
+
reduceMotion === true
|
|
496
|
+
? "reduce-motion"
|
|
497
|
+
: reduceMotion === false
|
|
498
|
+
? "reduce-motion-off"
|
|
499
|
+
: "";
|
|
500
|
+
const fmReducedMotion =
|
|
501
|
+
reduceMotion === true
|
|
502
|
+
? "always"
|
|
503
|
+
: reduceMotion === false
|
|
504
|
+
? "never"
|
|
505
|
+
: "user";
|
|
506
|
+
const combinedClassName = motionClass
|
|
507
|
+
? className
|
|
508
|
+
? `${className} ${motionClass}`
|
|
509
|
+
: motionClass
|
|
510
|
+
: className;
|
|
511
|
+
|
|
432
512
|
return (
|
|
433
|
-
<
|
|
434
|
-
<
|
|
435
|
-
<
|
|
436
|
-
<
|
|
437
|
-
<
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
513
|
+
<MotionConfig reducedMotion={fmReducedMotion} transition={transition}>
|
|
514
|
+
<ReadOnlyProvider readOnly={readOnly}>
|
|
515
|
+
<IconProvider icons={icons ?? {}}>
|
|
516
|
+
<LabelsProvider labels={labels}>
|
|
517
|
+
<SurfacesProvider surfaces={surfaces}>
|
|
518
|
+
<DirtyProvider dirty={dirty}>
|
|
519
|
+
<HostProvider callbacks={{ onSave: wrappedSave, onExport }}>
|
|
520
|
+
<RootShell
|
|
521
|
+
fontFamily={fontFamily}
|
|
522
|
+
className={combinedClassName}
|
|
523
|
+
style={mergedStyle}
|
|
524
|
+
>
|
|
525
|
+
{children}
|
|
526
|
+
</RootShell>
|
|
527
|
+
</HostProvider>
|
|
528
|
+
</DirtyProvider>
|
|
529
|
+
</SurfacesProvider>
|
|
530
|
+
</LabelsProvider>
|
|
531
|
+
</IconProvider>
|
|
532
|
+
</ReadOnlyProvider>
|
|
533
|
+
</MotionConfig>
|
|
448
534
|
);
|
|
449
535
|
}
|
|
450
536
|
|