@textcortex/slidewise 1.4.0 → 1.5.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 +4576 -4462
- package/dist/index.mjs.map +1 -1
- package/dist/slidewise.css +1 -1
- package/package.json +1 -1
- package/src/SlidewiseEditor.css +34 -0
- package/src/SlidewiseEditor.tsx +17 -0
- package/src/SlidewiseFileEditor.tsx +32 -6
- package/src/compound/LabelsContext.tsx +121 -0
- package/src/compound/SlidewiseRoot.tsx +47 -11
- 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,34 @@
|
|
|
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
|
+
|
|
37
65
|
/* Layout backgrounds */
|
|
38
66
|
--app-bg: #ffffff;
|
|
39
67
|
--rail-bg: #fafafb;
|
|
@@ -126,6 +154,12 @@
|
|
|
126
154
|
--canvas-bg-from: #181c38;
|
|
127
155
|
--canvas-bg-to: #0c1027;
|
|
128
156
|
|
|
157
|
+
/* Dark-mode overrides for the public --slidewise-bg-* surface tokens.
|
|
158
|
+
Hosts can still override any one of these from a higher specificity
|
|
159
|
+
selector or via the `surfaces` prop. */
|
|
160
|
+
--slidewise-bg-slide: #ffffff;
|
|
161
|
+
--slidewise-bg-unsaved-badge: rgba(232, 80, 76, 0.18);
|
|
162
|
+
|
|
129
163
|
/* Charcoal-purple surface kit from textcortex/platform#7428 — neutral
|
|
130
164
|
dark base, faint inset highlight, layered shadow that lifts to a
|
|
131
165
|
subtle plum tint on hover. */
|
package/src/SlidewiseEditor.tsx
CHANGED
|
@@ -15,6 +15,8 @@ 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";
|
|
18
20
|
import type { Deck } from "@/lib/types";
|
|
19
21
|
import "./SlidewiseEditor.css";
|
|
20
22
|
|
|
@@ -72,6 +74,17 @@ export interface SlidewiseEditorProps {
|
|
|
72
74
|
* bundled lucide-react icons.
|
|
73
75
|
*/
|
|
74
76
|
icons?: SlidewiseIcons;
|
|
77
|
+
/**
|
|
78
|
+
* User-visible string overrides for i18n. Pass any subset; missing
|
|
79
|
+
* entries fall back to English defaults.
|
|
80
|
+
*/
|
|
81
|
+
labels?: SlidewiseLabels;
|
|
82
|
+
/**
|
|
83
|
+
* Per-surface background overrides, equivalent to setting the
|
|
84
|
+
* `--slidewise-bg-*` CSS variables. See `SlidewiseSurfaces` for the full
|
|
85
|
+
* list of keys.
|
|
86
|
+
*/
|
|
87
|
+
surfaces?: SlidewiseSurfaces;
|
|
75
88
|
/** Extra class names appended to the editor root. */
|
|
76
89
|
className?: string;
|
|
77
90
|
/** Inline style applied to the editor root. */
|
|
@@ -123,6 +136,8 @@ export const SlidewiseEditor = forwardRef<
|
|
|
123
136
|
showBottomToolbar = true,
|
|
124
137
|
fontFamily,
|
|
125
138
|
icons,
|
|
139
|
+
labels,
|
|
140
|
+
surfaces,
|
|
126
141
|
className,
|
|
127
142
|
style,
|
|
128
143
|
},
|
|
@@ -146,6 +161,8 @@ export const SlidewiseEditor = forwardRef<
|
|
|
146
161
|
initialSlideId,
|
|
147
162
|
fontFamily,
|
|
148
163
|
icons,
|
|
164
|
+
labels,
|
|
165
|
+
surfaces,
|
|
149
166
|
className,
|
|
150
167
|
style,
|
|
151
168
|
};
|
|
@@ -10,6 +10,9 @@ 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";
|
|
13
16
|
import type { HistoryState, SelectionSnapshot } from "./compound/SlidewiseRoot";
|
|
14
17
|
|
|
15
18
|
export interface SlidewiseFileEditorProps {
|
|
@@ -91,6 +94,17 @@ export interface SlidewiseFileEditorProps {
|
|
|
91
94
|
* editor's chrome with your own icon set.
|
|
92
95
|
*/
|
|
93
96
|
icons?: SlidewiseIcons;
|
|
97
|
+
/**
|
|
98
|
+
* User-visible string overrides for i18n. Pass any subset; missing
|
|
99
|
+
* entries fall back to English defaults. The "Unsaved changes" badge
|
|
100
|
+
* and the loading / load-error messages also key off this table.
|
|
101
|
+
*/
|
|
102
|
+
labels?: SlidewiseLabels;
|
|
103
|
+
/**
|
|
104
|
+
* Per-surface background overrides; equivalent to setting the
|
|
105
|
+
* `--slidewise-bg-*` CSS variables.
|
|
106
|
+
*/
|
|
107
|
+
surfaces?: SlidewiseSurfaces;
|
|
94
108
|
className?: string;
|
|
95
109
|
style?: CSSProperties;
|
|
96
110
|
/**
|
|
@@ -193,6 +207,8 @@ export const SlidewiseFileEditor = forwardRef<
|
|
|
193
207
|
showBottomToolbar,
|
|
194
208
|
fontFamily,
|
|
195
209
|
icons,
|
|
210
|
+
labels,
|
|
211
|
+
surfaces,
|
|
196
212
|
className,
|
|
197
213
|
style,
|
|
198
214
|
parse = parsePptx,
|
|
@@ -337,15 +353,18 @@ export const SlidewiseFileEditor = forwardRef<
|
|
|
337
353
|
if (state.status === "loading") {
|
|
338
354
|
return (
|
|
339
355
|
<div style={{ ...frameStyle, ...style }} className={className}>
|
|
340
|
-
<div style={messageStyle}>
|
|
356
|
+
<div style={messageStyle}>
|
|
357
|
+
{labels?.fileLoading ?? DEFAULT_LABELS.fileLoading}
|
|
358
|
+
</div>
|
|
341
359
|
</div>
|
|
342
360
|
);
|
|
343
361
|
}
|
|
344
362
|
if (state.status === "error") {
|
|
363
|
+
const fmt = labels?.fileLoadError ?? DEFAULT_LABELS.fileLoadError;
|
|
345
364
|
return (
|
|
346
365
|
<div style={{ ...frameStyle, ...style }} className={className}>
|
|
347
366
|
<div style={{ ...messageStyle, color: "#E8504C" }}>
|
|
348
|
-
|
|
367
|
+
{fmt(state.error.message)}
|
|
349
368
|
</div>
|
|
350
369
|
</div>
|
|
351
370
|
);
|
|
@@ -363,6 +382,8 @@ export const SlidewiseFileEditor = forwardRef<
|
|
|
363
382
|
showBottomToolbar={showBottomToolbar}
|
|
364
383
|
fontFamily={fontFamily}
|
|
365
384
|
icons={icons}
|
|
385
|
+
labels={labels}
|
|
386
|
+
surfaces={surfaces}
|
|
366
387
|
onChange={(next) => {
|
|
367
388
|
onChangeRef.current?.(next);
|
|
368
389
|
}}
|
|
@@ -382,7 +403,11 @@ export const SlidewiseFileEditor = forwardRef<
|
|
|
382
403
|
await saveBlob(blob);
|
|
383
404
|
}}
|
|
384
405
|
/>
|
|
385
|
-
{dirty &&
|
|
406
|
+
{dirty && (
|
|
407
|
+
<UnsavedBadge
|
|
408
|
+
label={labels?.unsavedBadge ?? DEFAULT_LABELS.unsavedBadge}
|
|
409
|
+
/>
|
|
410
|
+
)}
|
|
386
411
|
</div>
|
|
387
412
|
);
|
|
388
413
|
});
|
|
@@ -403,7 +428,7 @@ const messageStyle: CSSProperties = {
|
|
|
403
428
|
color: "#5b6178",
|
|
404
429
|
};
|
|
405
430
|
|
|
406
|
-
function UnsavedBadge() {
|
|
431
|
+
function UnsavedBadge({ label }: { label: string }) {
|
|
407
432
|
return (
|
|
408
433
|
<div
|
|
409
434
|
style={{
|
|
@@ -411,7 +436,8 @@ function UnsavedBadge() {
|
|
|
411
436
|
top: 12,
|
|
412
437
|
right: 12,
|
|
413
438
|
padding: "4px 10px",
|
|
414
|
-
background:
|
|
439
|
+
background:
|
|
440
|
+
"var(--slidewise-bg-unsaved-badge, rgba(232, 80, 76, 0.12))",
|
|
415
441
|
color: "#E8504C",
|
|
416
442
|
borderRadius: 999,
|
|
417
443
|
fontFamily: "Inter, system-ui, sans-serif",
|
|
@@ -422,7 +448,7 @@ function UnsavedBadge() {
|
|
|
422
448
|
pointerEvents: "none",
|
|
423
449
|
}}
|
|
424
450
|
>
|
|
425
|
-
|
|
451
|
+
{label}
|
|
426
452
|
</div>
|
|
427
453
|
);
|
|
428
454
|
}
|
|
@@ -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
|
+
}
|
|
@@ -22,6 +22,12 @@ import { HostProvider } from "./HostContext";
|
|
|
22
22
|
import { IconProvider, type SlidewiseIcons } from "./IconContext";
|
|
23
23
|
import { ReadOnlyProvider } from "./ReadOnlyContext";
|
|
24
24
|
import { DirtyProvider } from "./DirtyContext";
|
|
25
|
+
import { LabelsProvider, type SlidewiseLabels } from "./LabelsContext";
|
|
26
|
+
import {
|
|
27
|
+
SurfacesProvider,
|
|
28
|
+
surfacesToCssVars,
|
|
29
|
+
type SlidewiseSurfaces,
|
|
30
|
+
} from "./SurfacesContext";
|
|
25
31
|
|
|
26
32
|
export interface SlidewiseRootProps {
|
|
27
33
|
/**
|
|
@@ -72,6 +78,22 @@ export interface SlidewiseRootProps {
|
|
|
72
78
|
* the bundled lucide icons.
|
|
73
79
|
*/
|
|
74
80
|
icons?: SlidewiseIcons;
|
|
81
|
+
/**
|
|
82
|
+
* User-visible string overrides for i18n. Pass any subset; missing
|
|
83
|
+
* entries fall back to English defaults. Pairs with `icons` for full
|
|
84
|
+
* locale customization.
|
|
85
|
+
*/
|
|
86
|
+
labels?: SlidewiseLabels;
|
|
87
|
+
/**
|
|
88
|
+
* Per-surface background overrides. Equivalent to setting the
|
|
89
|
+
* `--slidewise-bg-*` CSS variables on the root, but as a typed prop so
|
|
90
|
+
* hosts can drive theming from JS without writing CSS:
|
|
91
|
+
*
|
|
92
|
+
* ```tsx
|
|
93
|
+
* <Slidewise.Root surfaces={{ app: "#0b0d10", rail: "#1c1c22" }}>
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
surfaces?: SlidewiseSurfaces;
|
|
75
97
|
/** Extra class names appended to the root. */
|
|
76
98
|
className?: string;
|
|
77
99
|
/** Inline style applied to the root. */
|
|
@@ -191,6 +213,8 @@ function RootInner({
|
|
|
191
213
|
initialSlideId,
|
|
192
214
|
fontFamily,
|
|
193
215
|
icons,
|
|
216
|
+
labels,
|
|
217
|
+
surfaces,
|
|
194
218
|
className,
|
|
195
219
|
style,
|
|
196
220
|
children,
|
|
@@ -429,20 +453,32 @@ function RootInner({
|
|
|
429
453
|
}
|
|
430
454
|
: undefined;
|
|
431
455
|
|
|
456
|
+
// Merge host's `style` prop with the surface overrides so a host can
|
|
457
|
+
// pass both without one clobbering the other. Surfaces win on conflict
|
|
458
|
+
// because they're the more specific theming intent.
|
|
459
|
+
const surfaceVars = surfacesToCssVars(surfaces);
|
|
460
|
+
const mergedStyle: CSSProperties | undefined = surfaceVars
|
|
461
|
+
? { ...style, ...(surfaceVars as CSSProperties) }
|
|
462
|
+
: style;
|
|
463
|
+
|
|
432
464
|
return (
|
|
433
465
|
<ReadOnlyProvider readOnly={readOnly}>
|
|
434
466
|
<IconProvider icons={icons ?? {}}>
|
|
435
|
-
<
|
|
436
|
-
<
|
|
437
|
-
<
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
467
|
+
<LabelsProvider labels={labels}>
|
|
468
|
+
<SurfacesProvider surfaces={surfaces}>
|
|
469
|
+
<DirtyProvider dirty={dirty}>
|
|
470
|
+
<HostProvider callbacks={{ onSave: wrappedSave, onExport }}>
|
|
471
|
+
<RootShell
|
|
472
|
+
fontFamily={fontFamily}
|
|
473
|
+
className={className}
|
|
474
|
+
style={mergedStyle}
|
|
475
|
+
>
|
|
476
|
+
{children}
|
|
477
|
+
</RootShell>
|
|
478
|
+
</HostProvider>
|
|
479
|
+
</DirtyProvider>
|
|
480
|
+
</SurfacesProvider>
|
|
481
|
+
</LabelsProvider>
|
|
446
482
|
</IconProvider>
|
|
447
483
|
</ReadOnlyProvider>
|
|
448
484
|
);
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { createContext, useContext, useMemo, type ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Per-surface background overrides. Each key maps to a CSS variable that the
|
|
5
|
+
* library's stylesheet reads. Values can be any valid CSS background
|
|
6
|
+
* (color, gradient, var() reference, etc.). Equivalent to setting the
|
|
7
|
+
* `--slidewise-bg-*` variables manually — provided as a typed prop so
|
|
8
|
+
* hosts can drive theming from JS.
|
|
9
|
+
*
|
|
10
|
+
* Missing keys fall back to the library's defaults; supply only what you
|
|
11
|
+
* want to override.
|
|
12
|
+
*/
|
|
13
|
+
export interface SlidewiseSurfaces {
|
|
14
|
+
/** Outermost app shell. Maps to `--slidewise-bg-app`. */
|
|
15
|
+
app?: string;
|
|
16
|
+
/** Top bar surface. `--slidewise-bg-topbar`. */
|
|
17
|
+
topbar?: string;
|
|
18
|
+
/** Slide rail container. `--slidewise-bg-rail`. */
|
|
19
|
+
rail?: string;
|
|
20
|
+
/** Inactive slide rail item. `--slidewise-bg-rail-item`. */
|
|
21
|
+
railItem?: string;
|
|
22
|
+
/** Active/selected slide rail item. `--slidewise-bg-rail-item-active`. */
|
|
23
|
+
railItemActive?: string;
|
|
24
|
+
/** Canvas frame (around the slide). `--slidewise-bg-canvas-frame`. */
|
|
25
|
+
canvasFrame?: string;
|
|
26
|
+
/** Canvas backdrop gradient start. `--slidewise-bg-canvas-from`. */
|
|
27
|
+
canvasFrom?: string;
|
|
28
|
+
/** Canvas backdrop gradient end. `--slidewise-bg-canvas-to`. */
|
|
29
|
+
canvasTo?: string;
|
|
30
|
+
/** Floating bottom toolbar. `--slidewise-bg-bottom-toolbar`. */
|
|
31
|
+
bottomToolbar?: string;
|
|
32
|
+
/** Right panel surface. `--slidewise-bg-right-panel`. */
|
|
33
|
+
rightPanel?: string;
|
|
34
|
+
/** Popover/menu surface. `--slidewise-bg-menu`. */
|
|
35
|
+
menu?: string;
|
|
36
|
+
/** Tooltip surface. `--slidewise-bg-tooltip`. */
|
|
37
|
+
tooltip?: string;
|
|
38
|
+
/** Popover surface. `--slidewise-bg-popover`. */
|
|
39
|
+
popover?: string;
|
|
40
|
+
/** Dialog surface. `--slidewise-bg-dialog`. */
|
|
41
|
+
dialog?: string;
|
|
42
|
+
/** Slide paper. `--slidewise-bg-slide`. */
|
|
43
|
+
slide?: string;
|
|
44
|
+
/** Selection overlay tint. `--slidewise-bg-selection`. */
|
|
45
|
+
selection?: string;
|
|
46
|
+
/** Hover state. `--slidewise-bg-hover`. */
|
|
47
|
+
hover?: string;
|
|
48
|
+
/** Active/pressed state. `--slidewise-bg-active`. */
|
|
49
|
+
active?: string;
|
|
50
|
+
/** Form input. `--slidewise-bg-input`. */
|
|
51
|
+
input?: string;
|
|
52
|
+
/** Chrome button. `--slidewise-bg-button`. */
|
|
53
|
+
button?: string;
|
|
54
|
+
/** Chrome button on hover. `--slidewise-bg-button-hover`. */
|
|
55
|
+
buttonHover?: string;
|
|
56
|
+
/** Smart pill. `--slidewise-bg-pill`. */
|
|
57
|
+
pill?: string;
|
|
58
|
+
/** Unsaved-changes badge. `--slidewise-bg-unsaved-badge`. */
|
|
59
|
+
unsavedBadge?: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const KEY_TO_VAR: Record<keyof SlidewiseSurfaces, string> = {
|
|
63
|
+
app: "--slidewise-bg-app",
|
|
64
|
+
topbar: "--slidewise-bg-topbar",
|
|
65
|
+
rail: "--slidewise-bg-rail",
|
|
66
|
+
railItem: "--slidewise-bg-rail-item",
|
|
67
|
+
railItemActive: "--slidewise-bg-rail-item-active",
|
|
68
|
+
canvasFrame: "--slidewise-bg-canvas-frame",
|
|
69
|
+
canvasFrom: "--slidewise-bg-canvas-from",
|
|
70
|
+
canvasTo: "--slidewise-bg-canvas-to",
|
|
71
|
+
bottomToolbar: "--slidewise-bg-bottom-toolbar",
|
|
72
|
+
rightPanel: "--slidewise-bg-right-panel",
|
|
73
|
+
menu: "--slidewise-bg-menu",
|
|
74
|
+
tooltip: "--slidewise-bg-tooltip",
|
|
75
|
+
popover: "--slidewise-bg-popover",
|
|
76
|
+
dialog: "--slidewise-bg-dialog",
|
|
77
|
+
slide: "--slidewise-bg-slide",
|
|
78
|
+
selection: "--slidewise-bg-selection",
|
|
79
|
+
hover: "--slidewise-bg-hover",
|
|
80
|
+
active: "--slidewise-bg-active",
|
|
81
|
+
input: "--slidewise-bg-input",
|
|
82
|
+
button: "--slidewise-bg-button",
|
|
83
|
+
buttonHover: "--slidewise-bg-button-hover",
|
|
84
|
+
pill: "--slidewise-bg-pill",
|
|
85
|
+
unsavedBadge: "--slidewise-bg-unsaved-badge",
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Convert a `SlidewiseSurfaces` map into an object suitable for spreading
|
|
90
|
+
* into a React `style` prop. Returns `null` when no overrides are present
|
|
91
|
+
* so the consumer doesn't allocate a fresh style object every render.
|
|
92
|
+
*/
|
|
93
|
+
export function surfacesToCssVars(
|
|
94
|
+
surfaces: SlidewiseSurfaces | undefined
|
|
95
|
+
): Record<string, string> | null {
|
|
96
|
+
if (!surfaces) return null;
|
|
97
|
+
const entries: [string, string][] = [];
|
|
98
|
+
for (const key of Object.keys(KEY_TO_VAR) as (keyof SlidewiseSurfaces)[]) {
|
|
99
|
+
const value = surfaces[key];
|
|
100
|
+
if (value !== undefined) entries.push([KEY_TO_VAR[key], value]);
|
|
101
|
+
}
|
|
102
|
+
if (entries.length === 0) return null;
|
|
103
|
+
return Object.fromEntries(entries);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const SurfacesContext = createContext<SlidewiseSurfaces | null>(null);
|
|
107
|
+
|
|
108
|
+
export function SurfacesProvider({
|
|
109
|
+
surfaces,
|
|
110
|
+
children,
|
|
111
|
+
}: {
|
|
112
|
+
surfaces: SlidewiseSurfaces | undefined;
|
|
113
|
+
children: ReactNode;
|
|
114
|
+
}) {
|
|
115
|
+
const value = useMemo(() => surfaces ?? null, [surfaces]);
|
|
116
|
+
return (
|
|
117
|
+
<SurfacesContext.Provider value={value}>{children}</SurfacesContext.Provider>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Read the surface override map. Mostly used by `<Slidewise.Root>` itself,
|
|
123
|
+
* but exposed so host-rendered surfaces (e.g. a custom panel) can apply
|
|
124
|
+
* the same tokens for visual consistency.
|
|
125
|
+
*/
|
|
126
|
+
export function useSurfaces(): SlidewiseSurfaces | null {
|
|
127
|
+
return useContext(SurfacesContext);
|
|
128
|
+
}
|
package/src/compound/index.ts
CHANGED
|
@@ -74,6 +74,19 @@ export {
|
|
|
74
74
|
} from "./IconContext";
|
|
75
75
|
export { ReadOnlyProvider, useReadOnly } from "./ReadOnlyContext";
|
|
76
76
|
export { DirtyProvider, useDirty } from "./DirtyContext";
|
|
77
|
+
export {
|
|
78
|
+
LabelsProvider,
|
|
79
|
+
useLabels,
|
|
80
|
+
DEFAULT_LABELS,
|
|
81
|
+
type SlidewiseLabels,
|
|
82
|
+
type ResolvedLabels,
|
|
83
|
+
} from "./LabelsContext";
|
|
84
|
+
export {
|
|
85
|
+
SurfacesProvider,
|
|
86
|
+
useSurfaces,
|
|
87
|
+
surfacesToCssVars,
|
|
88
|
+
type SlidewiseSurfaces,
|
|
89
|
+
} from "./SurfacesContext";
|
|
77
90
|
|
|
78
91
|
/**
|
|
79
92
|
* Store hooks. Use these from host components anywhere under
|
package/src/compound/parts.tsx
CHANGED
|
@@ -68,7 +68,7 @@ export function RightPanel({
|
|
|
68
68
|
width,
|
|
69
69
|
flexShrink: 0,
|
|
70
70
|
height: "100%",
|
|
71
|
-
background: "var(--rail-bg)",
|
|
71
|
+
background: "var(--slidewise-bg-right-panel, var(--rail-bg))",
|
|
72
72
|
borderLeft: "1px solid var(--border)",
|
|
73
73
|
boxShadow: "var(--rail-shadow)",
|
|
74
74
|
overflow: "auto",
|
|
@@ -3,6 +3,7 @@ import { Download } from "lucide-react";
|
|
|
3
3
|
import { useEditorStore } from "@/lib/StoreProvider";
|
|
4
4
|
import { useHostCallbacks } from "../HostContext";
|
|
5
5
|
import { useIcons } from "../IconContext";
|
|
6
|
+
import { useLabels } from "../LabelsContext";
|
|
6
7
|
import { primaryBtnStyle, primaryHoverHandlers } from "./styles";
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -24,13 +25,15 @@ export interface TopBarExportProps {
|
|
|
24
25
|
export function Export({
|
|
25
26
|
className,
|
|
26
27
|
style,
|
|
27
|
-
ariaLabel
|
|
28
|
-
label
|
|
28
|
+
ariaLabel,
|
|
29
|
+
label,
|
|
29
30
|
children,
|
|
30
31
|
}: TopBarExportProps = {}) {
|
|
31
32
|
const store = useEditorStore();
|
|
32
33
|
const { onExport: onExportHost } = useHostCallbacks();
|
|
33
34
|
const icons = useIcons();
|
|
35
|
+
const labels = useLabels();
|
|
36
|
+
const resolved = label ?? labels.export;
|
|
34
37
|
|
|
35
38
|
const onClick = () => {
|
|
36
39
|
const deck = store.getState().deck;
|
|
@@ -53,13 +56,13 @@ export function Export({
|
|
|
53
56
|
<button
|
|
54
57
|
type="button"
|
|
55
58
|
className={className}
|
|
56
|
-
aria-label={ariaLabel}
|
|
59
|
+
aria-label={ariaLabel ?? resolved}
|
|
57
60
|
onClick={onClick}
|
|
58
61
|
style={{ ...primaryBtnStyle(), ...style }}
|
|
59
62
|
{...primaryHoverHandlers()}
|
|
60
63
|
>
|
|
61
64
|
{children ?? icons.export ?? <Download size={14} />}
|
|
62
|
-
{
|
|
65
|
+
{resolved}
|
|
63
66
|
</button>
|
|
64
67
|
);
|
|
65
68
|
}
|
|
@@ -2,6 +2,7 @@ import type { CSSProperties, ReactNode } from "react";
|
|
|
2
2
|
import { Play as PlayIcon } from "lucide-react";
|
|
3
3
|
import { useEditor } from "@/lib/StoreProvider";
|
|
4
4
|
import { useIcons } from "../IconContext";
|
|
5
|
+
import { useLabels } from "../LabelsContext";
|
|
5
6
|
import { chromeBtnStyle, hoverHandlers } from "./styles";
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -19,24 +20,26 @@ export interface TopBarPlayProps {
|
|
|
19
20
|
export function Play({
|
|
20
21
|
className,
|
|
21
22
|
style,
|
|
22
|
-
ariaLabel
|
|
23
|
-
label
|
|
23
|
+
ariaLabel,
|
|
24
|
+
label,
|
|
24
25
|
children,
|
|
25
26
|
}: TopBarPlayProps = {}) {
|
|
26
27
|
const play = useEditor((s) => s.play);
|
|
27
28
|
const icons = useIcons();
|
|
29
|
+
const labels = useLabels();
|
|
30
|
+
const resolved = label ?? labels.play;
|
|
28
31
|
|
|
29
32
|
return (
|
|
30
33
|
<button
|
|
31
34
|
type="button"
|
|
32
35
|
className={className}
|
|
33
|
-
aria-label={ariaLabel}
|
|
36
|
+
aria-label={ariaLabel ?? resolved}
|
|
34
37
|
onClick={play}
|
|
35
38
|
style={{ ...chromeBtnStyle(), ...style }}
|
|
36
39
|
{...hoverHandlers()}
|
|
37
40
|
>
|
|
38
41
|
{children ?? icons.play ?? <PlayIcon size={14} />}
|
|
39
|
-
{
|
|
42
|
+
{resolved}
|
|
40
43
|
</button>
|
|
41
44
|
);
|
|
42
45
|
}
|
|
@@ -3,6 +3,7 @@ import { Redo2 } from "lucide-react";
|
|
|
3
3
|
import { useEditor } from "@/lib/StoreProvider";
|
|
4
4
|
import { useIcons } from "../IconContext";
|
|
5
5
|
import { useReadOnly } from "../ReadOnlyContext";
|
|
6
|
+
import { useLabels } from "../LabelsContext";
|
|
6
7
|
import { iconBtnStyle, hoverHandlers } from "./styles";
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -19,21 +20,23 @@ export interface TopBarRedoProps {
|
|
|
19
20
|
export function Redo({
|
|
20
21
|
className,
|
|
21
22
|
style,
|
|
22
|
-
ariaLabel
|
|
23
|
+
ariaLabel,
|
|
23
24
|
children,
|
|
24
25
|
}: TopBarRedoProps = {}) {
|
|
25
26
|
const redo = useEditor((s) => s.redo);
|
|
26
27
|
const canRedo = useEditor((s) => s.future.length > 0);
|
|
27
28
|
const icons = useIcons();
|
|
28
29
|
const readOnly = useReadOnly();
|
|
30
|
+
const labels = useLabels();
|
|
31
|
+
const resolvedAria = ariaLabel ?? labels.redo;
|
|
29
32
|
if (readOnly) return null;
|
|
30
33
|
|
|
31
34
|
return (
|
|
32
35
|
<button
|
|
33
36
|
type="button"
|
|
34
37
|
className={className}
|
|
35
|
-
title={
|
|
36
|
-
aria-label={
|
|
38
|
+
title={resolvedAria}
|
|
39
|
+
aria-label={resolvedAria}
|
|
37
40
|
disabled={!canRedo}
|
|
38
41
|
onClick={redo}
|
|
39
42
|
style={{
|