@textcortex/slidewise 1.6.0 → 1.8.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 +5378 -5314
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/SlidewiseEditor.tsx +11 -1
- package/src/SlidewiseFileEditor.tsx +8 -0
- package/src/components/editor/Canvas.tsx +15 -8
- package/src/components/editor/SlideView.tsx +6 -1
- package/src/compound/CanvasContext.tsx +176 -0
- package/src/compound/SlidewiseRoot.tsx +34 -11
- package/src/compound/index.ts +21 -1
- package/src/compound/parts.tsx +4 -11
- package/src/compound/sliderail/AddButton.tsx +76 -0
- package/src/compound/sliderail/Header.tsx +84 -0
- package/src/compound/sliderail/Item.tsx +82 -0
- package/src/compound/sliderail/ItemContext.tsx +42 -0
- package/src/compound/sliderail/List.tsx +67 -0
- package/src/compound/sliderail/Number.tsx +57 -0
- package/src/compound/sliderail/Root.tsx +54 -0
- package/src/compound/sliderail/Thumbnail.tsx +58 -0
- package/src/compound/sliderail/index.tsx +74 -0
- package/src/index.ts +15 -0
- package/src/components/editor/SlideRail.tsx +0 -285
package/package.json
CHANGED
package/src/SlidewiseEditor.tsx
CHANGED
|
@@ -7,8 +7,8 @@ import {
|
|
|
7
7
|
type SelectionSnapshot,
|
|
8
8
|
} from "./compound/SlidewiseRoot";
|
|
9
9
|
import { TopBar } from "./compound/topbar";
|
|
10
|
+
import { SlideRail } from "./compound/sliderail";
|
|
10
11
|
import {
|
|
11
|
-
SlideRail,
|
|
12
12
|
Canvas,
|
|
13
13
|
BottomToolbar,
|
|
14
14
|
Body,
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
import type { SlidewiseIcons } from "./compound/IconContext";
|
|
18
18
|
import type { SlidewiseLabels } from "./compound/LabelsContext";
|
|
19
19
|
import type { SlidewiseSurfaces } from "./compound/SurfacesContext";
|
|
20
|
+
import type { SlidewiseCanvasConfig } from "./compound/CanvasContext";
|
|
20
21
|
import type { Transition } from "framer-motion";
|
|
21
22
|
import type { Deck } from "@/lib/types";
|
|
22
23
|
import "./SlidewiseEditor.css";
|
|
@@ -96,6 +97,13 @@ export interface SlidewiseEditorProps {
|
|
|
96
97
|
* list of keys.
|
|
97
98
|
*/
|
|
98
99
|
surfaces?: SlidewiseSurfaces;
|
|
100
|
+
/**
|
|
101
|
+
* Canvas/viewport configuration: padding, initial zoom, slide shadow +
|
|
102
|
+
* radius, and host-driven slide-background overrides. Use this when the
|
|
103
|
+
* default "slide fills the workspace" presentation isn't right for your
|
|
104
|
+
* host chrome.
|
|
105
|
+
*/
|
|
106
|
+
canvas?: SlidewiseCanvasConfig;
|
|
99
107
|
/** Extra class names appended to the editor root. */
|
|
100
108
|
className?: string;
|
|
101
109
|
/** Inline style applied to the editor root. */
|
|
@@ -151,6 +159,7 @@ export const SlidewiseEditor = forwardRef<
|
|
|
151
159
|
icons,
|
|
152
160
|
labels,
|
|
153
161
|
surfaces,
|
|
162
|
+
canvas,
|
|
154
163
|
className,
|
|
155
164
|
style,
|
|
156
165
|
},
|
|
@@ -178,6 +187,7 @@ export const SlidewiseEditor = forwardRef<
|
|
|
178
187
|
icons,
|
|
179
188
|
labels,
|
|
180
189
|
surfaces,
|
|
190
|
+
canvas,
|
|
181
191
|
className,
|
|
182
192
|
style,
|
|
183
193
|
};
|
|
@@ -12,6 +12,7 @@ import type { Deck } from "@/lib/types";
|
|
|
12
12
|
import type { SlidewiseIcons } from "./compound/IconContext";
|
|
13
13
|
import type { SlidewiseLabels } from "./compound/LabelsContext";
|
|
14
14
|
import type { SlidewiseSurfaces } from "./compound/SurfacesContext";
|
|
15
|
+
import type { SlidewiseCanvasConfig } from "./compound/CanvasContext";
|
|
15
16
|
import { DEFAULT_LABELS } from "./compound/LabelsContext";
|
|
16
17
|
import type { Transition } from "framer-motion";
|
|
17
18
|
import type { HistoryState, SelectionSnapshot } from "./compound/SlidewiseRoot";
|
|
@@ -106,6 +107,11 @@ export interface SlidewiseFileEditorProps {
|
|
|
106
107
|
* `--slidewise-bg-*` CSS variables.
|
|
107
108
|
*/
|
|
108
109
|
surfaces?: SlidewiseSurfaces;
|
|
110
|
+
/**
|
|
111
|
+
* Canvas/viewport configuration: padding, initial zoom, slide shadow +
|
|
112
|
+
* radius, and host-driven slide-background overrides.
|
|
113
|
+
*/
|
|
114
|
+
canvas?: SlidewiseCanvasConfig;
|
|
109
115
|
/**
|
|
110
116
|
* Reduced-motion behavior. `"system"` (default) respects the OS
|
|
111
117
|
* preference; `true` forces motion off; `false` forces it on.
|
|
@@ -217,6 +223,7 @@ export const SlidewiseFileEditor = forwardRef<
|
|
|
217
223
|
icons,
|
|
218
224
|
labels,
|
|
219
225
|
surfaces,
|
|
226
|
+
canvas,
|
|
220
227
|
reduceMotion,
|
|
221
228
|
transition,
|
|
222
229
|
className,
|
|
@@ -394,6 +401,7 @@ export const SlidewiseFileEditor = forwardRef<
|
|
|
394
401
|
icons={icons}
|
|
395
402
|
labels={labels}
|
|
396
403
|
surfaces={surfaces}
|
|
404
|
+
canvas={canvas}
|
|
397
405
|
reduceMotion={reduceMotion}
|
|
398
406
|
transition={transition}
|
|
399
407
|
onChange={(next) => {
|
|
@@ -5,6 +5,10 @@ import { SLIDE_W, SLIDE_H, type SlideElement, type ElementDraft } from "@/lib/ty
|
|
|
5
5
|
import { ElementView } from "./ElementView";
|
|
6
6
|
import { SelectionFrame } from "./SelectionFrame";
|
|
7
7
|
import { FloatingToolbar } from "./FloatingToolbar";
|
|
8
|
+
import {
|
|
9
|
+
useCanvasConfig,
|
|
10
|
+
resolveSlideBackground,
|
|
11
|
+
} from "@/compound/CanvasContext";
|
|
8
12
|
|
|
9
13
|
export function Canvas() {
|
|
10
14
|
const store = useEditorStore();
|
|
@@ -22,6 +26,7 @@ export function Canvas() {
|
|
|
22
26
|
const deleteElement = useEditor((s) => s.deleteElement);
|
|
23
27
|
const pushHistory = useEditor((s) => s.pushHistory);
|
|
24
28
|
|
|
29
|
+
const canvasConfig = useCanvasConfig();
|
|
25
30
|
const [editingId, setEditingId] = useState<string | null>(null);
|
|
26
31
|
const wrapRef = useRef<HTMLDivElement>(null);
|
|
27
32
|
const [autoScale, setAutoScale] = useState(0.6);
|
|
@@ -30,10 +35,12 @@ export function Canvas() {
|
|
|
30
35
|
if (fitMode !== "fit" || !wrapRef.current) return;
|
|
31
36
|
const recompute = () => {
|
|
32
37
|
const r = wrapRef.current!.getBoundingClientRect();
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
|
|
38
|
+
// Host-controllable padding via `canvas.padding` on <Slidewise.Root>.
|
|
39
|
+
// Default mirrors what the editor needed before the prop existed —
|
|
40
|
+
// ~32px horizontal breathing room and enough vertical headroom for
|
|
41
|
+
// the floating toolbar (~56) and the bottom toolbar (~76).
|
|
42
|
+
const padX = canvasConfig.padding.x * 2;
|
|
43
|
+
const padY = canvasConfig.padding.y * 2;
|
|
37
44
|
const fit = Math.min(
|
|
38
45
|
(r.width - padX) / SLIDE_W,
|
|
39
46
|
(r.height - padY) / SLIDE_H
|
|
@@ -44,7 +51,7 @@ export function Canvas() {
|
|
|
44
51
|
const ro = new ResizeObserver(recompute);
|
|
45
52
|
ro.observe(wrapRef.current);
|
|
46
53
|
return () => ro.disconnect();
|
|
47
|
-
}, [fitMode]);
|
|
54
|
+
}, [fitMode, canvasConfig.padding.x, canvasConfig.padding.y]);
|
|
48
55
|
|
|
49
56
|
const scale = fitMode === "fit" ? autoScale : zoom;
|
|
50
57
|
|
|
@@ -245,9 +252,9 @@ export function Canvas() {
|
|
|
245
252
|
width: SLIDE_W * scale,
|
|
246
253
|
height: SLIDE_H * scale,
|
|
247
254
|
transform: "translate(-50%, -50%)",
|
|
248
|
-
background: slide
|
|
249
|
-
borderRadius:
|
|
250
|
-
boxShadow:
|
|
255
|
+
background: resolveSlideBackground(canvasConfig, slide),
|
|
256
|
+
borderRadius: canvasConfig.slideRadius,
|
|
257
|
+
boxShadow: canvasConfig.slideShadow,
|
|
251
258
|
}}
|
|
252
259
|
>
|
|
253
260
|
<div
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { Slide } from "@/lib/types";
|
|
2
2
|
import { SLIDE_W, SLIDE_H } from "@/lib/types";
|
|
3
3
|
import { ElementView } from "./ElementView";
|
|
4
|
+
import {
|
|
5
|
+
useCanvasConfig,
|
|
6
|
+
resolveSlideBackground,
|
|
7
|
+
} from "@/compound/CanvasContext";
|
|
4
8
|
|
|
5
9
|
export function SlideView({
|
|
6
10
|
slide,
|
|
@@ -9,12 +13,13 @@ export function SlideView({
|
|
|
9
13
|
slide: Slide;
|
|
10
14
|
scale?: number;
|
|
11
15
|
}) {
|
|
16
|
+
const canvasConfig = useCanvasConfig();
|
|
12
17
|
return (
|
|
13
18
|
<div
|
|
14
19
|
style={{
|
|
15
20
|
width: SLIDE_W * scale,
|
|
16
21
|
height: SLIDE_H * scale,
|
|
17
|
-
background: slide
|
|
22
|
+
background: resolveSlideBackground(canvasConfig, slide),
|
|
18
23
|
position: "relative",
|
|
19
24
|
overflow: "hidden",
|
|
20
25
|
borderRadius: 12 * scale,
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { createContext, useContext, useMemo, type ReactNode } from "react";
|
|
2
|
+
import type { Slide } from "@/lib/types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Canvas/viewport configuration. Lets hosts tame the slide presentation so a
|
|
6
|
+
* slide with a bold background fill doesn't paint the entire workspace —
|
|
7
|
+
* which is what `<Slidewise.Root>` did by default before this prop existed.
|
|
8
|
+
*
|
|
9
|
+
* Pass any subset:
|
|
10
|
+
*
|
|
11
|
+
* ```tsx
|
|
12
|
+
* <Slidewise.Root
|
|
13
|
+
* canvas={{
|
|
14
|
+
* padding: { x: 48, y: 32 },
|
|
15
|
+
* defaultZoom: 0.7,
|
|
16
|
+
* slideRadius: 12,
|
|
17
|
+
* slideShadow:
|
|
18
|
+
* "0 1px 2px rgba(0,0,0,0.25), 0 24px 60px rgba(0,0,0,0.45)",
|
|
19
|
+
* resolveSlideBackground: (slide) =>
|
|
20
|
+
* hostThemeOverridesSlideBg ? "#ffffff" : undefined,
|
|
21
|
+
* }}
|
|
22
|
+
* >
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* Outside the slide rectangle hosts still own the canvas-frame backdrop via
|
|
26
|
+
* the `--slidewise-bg-canvas-from` / `--slidewise-bg-canvas-to` CSS tokens
|
|
27
|
+
* or the `surfaces.canvasFrom` / `surfaces.canvasTo` prop entries.
|
|
28
|
+
*/
|
|
29
|
+
export interface SlidewiseCanvasConfig {
|
|
30
|
+
/**
|
|
31
|
+
* Padding between the slide and the canvas frame, in pixels. Used both
|
|
32
|
+
* for the auto-fit calculation and as actual whitespace around the
|
|
33
|
+
* slide. Pass a number for uniform padding, or an object for per-axis
|
|
34
|
+
* control. Default: roughly the room the floating toolbars need —
|
|
35
|
+
* 32px horizontal, ~148px vertical (top bar + bottom toolbar).
|
|
36
|
+
*/
|
|
37
|
+
padding?: number | { x?: number; y?: number };
|
|
38
|
+
/**
|
|
39
|
+
* How the slide scales inside the canvas:
|
|
40
|
+
* - `"fit"` (default) auto-shrinks the slide to fit while preserving aspect
|
|
41
|
+
* - `"fill"` reserved for a future fill behavior; currently equivalent to fit
|
|
42
|
+
* - `"manual"` uses `defaultZoom` (or whatever the user has set via
|
|
43
|
+
* `api.setZoom` / pinch-zoom) verbatim
|
|
44
|
+
*
|
|
45
|
+
* Applied via `store.setFitMode()` once on mount. Subsequent user
|
|
46
|
+
* interactions (zoom in, fit toggle) override this.
|
|
47
|
+
*/
|
|
48
|
+
fitMode?: "fit" | "fill" | "manual";
|
|
49
|
+
/**
|
|
50
|
+
* Initial zoom level when `fitMode === "manual"` (or as a starting point
|
|
51
|
+
* when the user switches off auto-fit). 1 = 100%. Clamped to [0.1, 4].
|
|
52
|
+
*/
|
|
53
|
+
defaultZoom?: number;
|
|
54
|
+
/**
|
|
55
|
+
* Box-shadow applied to the slide paper. Any valid CSS `box-shadow`
|
|
56
|
+
* value. Defaults to `var(--slide-shadow)`.
|
|
57
|
+
*/
|
|
58
|
+
slideShadow?: string;
|
|
59
|
+
/**
|
|
60
|
+
* Border-radius applied to the slide paper. Number = pixels; string =
|
|
61
|
+
* any valid CSS length. Defaults to `8`.
|
|
62
|
+
*/
|
|
63
|
+
slideRadius?: number | string;
|
|
64
|
+
/**
|
|
65
|
+
* Hard-override the slide's background paint regardless of what the
|
|
66
|
+
* deck's `slide.background` says. Useful when the host wants every
|
|
67
|
+
* slide to render as a neutral surface (`#ffffff`, host-tinted, etc.)
|
|
68
|
+
* for a viewer experience where the deck's baked fills would clash
|
|
69
|
+
* with the chrome.
|
|
70
|
+
*
|
|
71
|
+
* If both `forceSlideBackground` and `resolveSlideBackground` are
|
|
72
|
+
* passed, the force value wins.
|
|
73
|
+
*/
|
|
74
|
+
forceSlideBackground?: string;
|
|
75
|
+
/**
|
|
76
|
+
* Per-slide background resolver. Receives the current slide; return a
|
|
77
|
+
* CSS value to override, or `undefined` to fall through to the slide's
|
|
78
|
+
* own `background` property. Useful for "respect host theme but only
|
|
79
|
+
* for slides that don't explicitly set a fill" patterns.
|
|
80
|
+
*/
|
|
81
|
+
resolveSlideBackground?: (slide: Slide) => string | undefined;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Fully-resolved canvas config, with internal defaults filled in. Consumers
|
|
86
|
+
* (the Canvas component, host-rendered surfaces) read this shape via
|
|
87
|
+
* `useCanvasConfig()` and never have to check for undefined.
|
|
88
|
+
*/
|
|
89
|
+
export interface ResolvedCanvasConfig {
|
|
90
|
+
padding: { x: number; y: number };
|
|
91
|
+
fitMode: "fit" | "fill" | "manual" | null;
|
|
92
|
+
defaultZoom: number | null;
|
|
93
|
+
slideShadow: string;
|
|
94
|
+
slideRadius: number | string;
|
|
95
|
+
forceSlideBackground: string | null;
|
|
96
|
+
resolveSlideBackground:
|
|
97
|
+
| ((slide: Slide) => string | undefined)
|
|
98
|
+
| null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export const DEFAULT_CANVAS_CONFIG: ResolvedCanvasConfig = {
|
|
102
|
+
// Matches the previous hardcoded padX=32 / padY=56+76+16 in Canvas.tsx —
|
|
103
|
+
// enough vertical room for the top bar + the floating bottom toolbar.
|
|
104
|
+
padding: { x: 32, y: 148 },
|
|
105
|
+
fitMode: null,
|
|
106
|
+
defaultZoom: null,
|
|
107
|
+
slideShadow: "var(--slide-shadow)",
|
|
108
|
+
slideRadius: 8,
|
|
109
|
+
forceSlideBackground: null,
|
|
110
|
+
resolveSlideBackground: null,
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
function mergeCanvasConfig(
|
|
114
|
+
config: SlidewiseCanvasConfig | undefined
|
|
115
|
+
): ResolvedCanvasConfig {
|
|
116
|
+
if (!config) return DEFAULT_CANVAS_CONFIG;
|
|
117
|
+
let padding = DEFAULT_CANVAS_CONFIG.padding;
|
|
118
|
+
if (typeof config.padding === "number") {
|
|
119
|
+
padding = { x: config.padding, y: config.padding };
|
|
120
|
+
} else if (config.padding && typeof config.padding === "object") {
|
|
121
|
+
padding = {
|
|
122
|
+
x: config.padding.x ?? DEFAULT_CANVAS_CONFIG.padding.x,
|
|
123
|
+
y: config.padding.y ?? DEFAULT_CANVAS_CONFIG.padding.y,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
padding,
|
|
128
|
+
fitMode: config.fitMode ?? null,
|
|
129
|
+
defaultZoom: config.defaultZoom ?? null,
|
|
130
|
+
slideShadow: config.slideShadow ?? DEFAULT_CANVAS_CONFIG.slideShadow,
|
|
131
|
+
slideRadius: config.slideRadius ?? DEFAULT_CANVAS_CONFIG.slideRadius,
|
|
132
|
+
forceSlideBackground: config.forceSlideBackground ?? null,
|
|
133
|
+
resolveSlideBackground: config.resolveSlideBackground ?? null,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const CanvasContext = createContext<ResolvedCanvasConfig>(DEFAULT_CANVAS_CONFIG);
|
|
138
|
+
|
|
139
|
+
export function CanvasConfigProvider({
|
|
140
|
+
config,
|
|
141
|
+
children,
|
|
142
|
+
}: {
|
|
143
|
+
config: SlidewiseCanvasConfig | undefined;
|
|
144
|
+
children: ReactNode;
|
|
145
|
+
}) {
|
|
146
|
+
const value = useMemo(() => mergeCanvasConfig(config), [config]);
|
|
147
|
+
return (
|
|
148
|
+
<CanvasContext.Provider value={value}>{children}</CanvasContext.Provider>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Read the resolved canvas configuration. Always returns a complete object
|
|
154
|
+
* with defaults filled in.
|
|
155
|
+
*/
|
|
156
|
+
export function useCanvasConfig(): ResolvedCanvasConfig {
|
|
157
|
+
return useContext(CanvasContext);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Convenience helper used by the internal Canvas component (and exported
|
|
162
|
+
* so host-rendered slide previews can reuse the same resolution rules).
|
|
163
|
+
* Returns whatever the canvas config says, falling back to the slide's
|
|
164
|
+
* own `background` property.
|
|
165
|
+
*/
|
|
166
|
+
export function resolveSlideBackground(
|
|
167
|
+
config: ResolvedCanvasConfig,
|
|
168
|
+
slide: Slide
|
|
169
|
+
): string {
|
|
170
|
+
if (config.forceSlideBackground) return config.forceSlideBackground;
|
|
171
|
+
if (config.resolveSlideBackground) {
|
|
172
|
+
const resolved = config.resolveSlideBackground(slide);
|
|
173
|
+
if (resolved !== undefined) return resolved;
|
|
174
|
+
}
|
|
175
|
+
return slide.background;
|
|
176
|
+
}
|
|
@@ -29,6 +29,10 @@ import {
|
|
|
29
29
|
surfacesToCssVars,
|
|
30
30
|
type SlidewiseSurfaces,
|
|
31
31
|
} from "./SurfacesContext";
|
|
32
|
+
import {
|
|
33
|
+
CanvasConfigProvider,
|
|
34
|
+
type SlidewiseCanvasConfig,
|
|
35
|
+
} from "./CanvasContext";
|
|
32
36
|
|
|
33
37
|
export interface SlidewiseRootProps {
|
|
34
38
|
/**
|
|
@@ -118,6 +122,14 @@ export interface SlidewiseRootProps {
|
|
|
118
122
|
* ```
|
|
119
123
|
*/
|
|
120
124
|
surfaces?: SlidewiseSurfaces;
|
|
125
|
+
/**
|
|
126
|
+
* Canvas/viewport configuration. Set `padding`, `slideRadius`,
|
|
127
|
+
* `slideShadow`, and an initial `defaultZoom` to keep the slide
|
|
128
|
+
* presented as a centered card rather than letting a bold deck fill
|
|
129
|
+
* paint the entire workspace. `forceSlideBackground` /
|
|
130
|
+
* `resolveSlideBackground` let hosts override per-slide fills.
|
|
131
|
+
*/
|
|
132
|
+
canvas?: SlidewiseCanvasConfig;
|
|
121
133
|
/** Extra class names appended to the root. */
|
|
122
134
|
className?: string;
|
|
123
135
|
/** Inline style applied to the root. */
|
|
@@ -241,6 +253,7 @@ function RootInner({
|
|
|
241
253
|
icons,
|
|
242
254
|
labels,
|
|
243
255
|
surfaces,
|
|
256
|
+
canvas,
|
|
244
257
|
className,
|
|
245
258
|
style,
|
|
246
259
|
children,
|
|
@@ -305,6 +318,14 @@ function RootInner({
|
|
|
305
318
|
store.getState().selectSlide(initialSlideId);
|
|
306
319
|
}
|
|
307
320
|
}
|
|
321
|
+
// Apply canvas defaults once on mount. setZoom auto-flips fitMode to
|
|
322
|
+
// "manual" so we set fitMode last to honor an explicit canvas.fitMode.
|
|
323
|
+
if (canvas?.defaultZoom !== undefined) {
|
|
324
|
+
store.getState().setZoom(canvas.defaultZoom);
|
|
325
|
+
}
|
|
326
|
+
if (canvas?.fitMode) {
|
|
327
|
+
store.getState().setFitMode(canvas.fitMode);
|
|
328
|
+
}
|
|
308
329
|
// run once on mount
|
|
309
330
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
310
331
|
}, []);
|
|
@@ -515,17 +536,19 @@ function RootInner({
|
|
|
515
536
|
<IconProvider icons={icons ?? {}}>
|
|
516
537
|
<LabelsProvider labels={labels}>
|
|
517
538
|
<SurfacesProvider surfaces={surfaces}>
|
|
518
|
-
<
|
|
519
|
-
<
|
|
520
|
-
<
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
539
|
+
<CanvasConfigProvider config={canvas}>
|
|
540
|
+
<DirtyProvider dirty={dirty}>
|
|
541
|
+
<HostProvider callbacks={{ onSave: wrappedSave, onExport }}>
|
|
542
|
+
<RootShell
|
|
543
|
+
fontFamily={fontFamily}
|
|
544
|
+
className={combinedClassName}
|
|
545
|
+
style={mergedStyle}
|
|
546
|
+
>
|
|
547
|
+
{children}
|
|
548
|
+
</RootShell>
|
|
549
|
+
</HostProvider>
|
|
550
|
+
</DirtyProvider>
|
|
551
|
+
</CanvasConfigProvider>
|
|
529
552
|
</SurfacesProvider>
|
|
530
553
|
</LabelsProvider>
|
|
531
554
|
</IconProvider>
|
package/src/compound/index.ts
CHANGED
|
@@ -40,7 +40,6 @@ export {
|
|
|
40
40
|
type SelectionSnapshot,
|
|
41
41
|
} from "./SlidewiseRoot";
|
|
42
42
|
export {
|
|
43
|
-
SlideRail,
|
|
44
43
|
Canvas,
|
|
45
44
|
BottomToolbar,
|
|
46
45
|
RightPanel,
|
|
@@ -50,6 +49,19 @@ export {
|
|
|
50
49
|
} from "./parts";
|
|
51
50
|
|
|
52
51
|
export { TopBar, type TopBarProps, type TopBarSlotId } from "./topbar";
|
|
52
|
+
export {
|
|
53
|
+
SlideRail,
|
|
54
|
+
useSlideRailItem,
|
|
55
|
+
type SlideRailProps,
|
|
56
|
+
type SlideRailRootProps,
|
|
57
|
+
type SlideRailHeaderProps,
|
|
58
|
+
type SlideRailListProps,
|
|
59
|
+
type SlideRailItemProps,
|
|
60
|
+
type SlideRailThumbnailProps,
|
|
61
|
+
type SlideRailNumberProps,
|
|
62
|
+
type SlideRailAddButtonProps,
|
|
63
|
+
type SlideRailItemContextValue,
|
|
64
|
+
} from "./sliderail";
|
|
53
65
|
export type {
|
|
54
66
|
TopBarRootProps,
|
|
55
67
|
TopBarTitleProps,
|
|
@@ -87,6 +99,14 @@ export {
|
|
|
87
99
|
surfacesToCssVars,
|
|
88
100
|
type SlidewiseSurfaces,
|
|
89
101
|
} from "./SurfacesContext";
|
|
102
|
+
export {
|
|
103
|
+
CanvasConfigProvider,
|
|
104
|
+
useCanvasConfig,
|
|
105
|
+
resolveSlideBackground,
|
|
106
|
+
DEFAULT_CANVAS_CONFIG,
|
|
107
|
+
type SlidewiseCanvasConfig,
|
|
108
|
+
type ResolvedCanvasConfig,
|
|
109
|
+
} from "./CanvasContext";
|
|
90
110
|
|
|
91
111
|
/**
|
|
92
112
|
* Store hooks. Use these from host components anywhere under
|
package/src/compound/parts.tsx
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { CSSProperties, ReactNode } from "react";
|
|
2
|
-
import { SlideRail as SlideRailInternal } from "@/components/editor/SlideRail";
|
|
3
2
|
import { Canvas as CanvasInternal } from "@/components/editor/Canvas";
|
|
4
3
|
import { BottomToolbar as BottomToolbarInternal } from "@/components/editor/BottomToolbar";
|
|
5
4
|
|
|
@@ -8,9 +7,10 @@ import { BottomToolbar as BottomToolbarInternal } from "@/components/editor/Bott
|
|
|
8
7
|
* so any part can be omitted, wrapped, or replaced. None of these accept
|
|
9
8
|
* deck/onChange/onSave props — those live on `<Slidewise.Root>`.
|
|
10
9
|
*
|
|
11
|
-
* Note: `<Slidewise.TopBar>`
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* Note: `<Slidewise.TopBar>` and `<Slidewise.SlideRail>` are defined
|
|
11
|
+
* separately under `./topbar/` and `./sliderail/` because they decompose
|
|
12
|
+
* into their own subparts. Both ship a callable component for the default
|
|
13
|
+
* arrangement plus a namespace of named subparts.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
export interface RegionProps {
|
|
@@ -18,13 +18,6 @@ export interface RegionProps {
|
|
|
18
18
|
style?: CSSProperties;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
/**
|
|
22
|
-
* Left-side slide thumbnail rail with add/duplicate/delete.
|
|
23
|
-
*/
|
|
24
|
-
export function SlideRail(_props: RegionProps = {}) {
|
|
25
|
-
return <SlideRailInternal />;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
21
|
/**
|
|
29
22
|
* The main editing canvas. This is the only part that's effectively required
|
|
30
23
|
* — without it the editor renders nothing visible. Layout-wise it expects
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { CSSProperties, ReactNode } from "react";
|
|
2
|
+
import { Plus } from "lucide-react";
|
|
3
|
+
import { useEditor } from "@/lib/StoreProvider";
|
|
4
|
+
import { useReadOnly } from "../ReadOnlyContext";
|
|
5
|
+
import { useLabels } from "../LabelsContext";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Dashed "New Slide" button at the bottom of the rail. Calls
|
|
9
|
+
* `store.addSlide()`. Hidden in read-only mode.
|
|
10
|
+
*
|
|
11
|
+
* Replace with your own button when you need a different shape; the
|
|
12
|
+
* store action is exported (`useEditor((s) => s.addSlide)`).
|
|
13
|
+
*/
|
|
14
|
+
export interface SlideRailAddButtonProps {
|
|
15
|
+
className?: string;
|
|
16
|
+
style?: CSSProperties;
|
|
17
|
+
ariaLabel?: string;
|
|
18
|
+
/** Override the visible label. Defaults to `labels.addSlide`. */
|
|
19
|
+
label?: string;
|
|
20
|
+
children?: ReactNode;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function AddButton({
|
|
24
|
+
className,
|
|
25
|
+
style,
|
|
26
|
+
ariaLabel,
|
|
27
|
+
label,
|
|
28
|
+
children,
|
|
29
|
+
}: SlideRailAddButtonProps = {}) {
|
|
30
|
+
const addSlide = useEditor((s) => s.addSlide);
|
|
31
|
+
const readOnly = useReadOnly();
|
|
32
|
+
const labels = useLabels();
|
|
33
|
+
if (readOnly) return null;
|
|
34
|
+
|
|
35
|
+
const resolved = label ?? labels.addSlide;
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<button
|
|
39
|
+
type="button"
|
|
40
|
+
className={className}
|
|
41
|
+
aria-label={ariaLabel ?? resolved}
|
|
42
|
+
onClick={() => addSlide()}
|
|
43
|
+
style={{
|
|
44
|
+
height: 44,
|
|
45
|
+
margin: 12,
|
|
46
|
+
display: "flex",
|
|
47
|
+
alignItems: "center",
|
|
48
|
+
justifyContent: "center",
|
|
49
|
+
gap: 6,
|
|
50
|
+
background: "var(--slidewise-bg-app, var(--app-bg))",
|
|
51
|
+
border: "1px dashed var(--border-dashed)",
|
|
52
|
+
borderRadius: "var(--slidewise-radius, 10px)",
|
|
53
|
+
color: "var(--ink)",
|
|
54
|
+
fontSize: 13,
|
|
55
|
+
fontWeight: 500,
|
|
56
|
+
cursor: "pointer",
|
|
57
|
+
transition: "background 120ms, border-color 120ms, color 120ms",
|
|
58
|
+
fontFamily: "inherit",
|
|
59
|
+
...style,
|
|
60
|
+
}}
|
|
61
|
+
onMouseEnter={(e) => {
|
|
62
|
+
e.currentTarget.style.borderColor =
|
|
63
|
+
"var(--slidewise-accent, var(--accent))";
|
|
64
|
+
e.currentTarget.style.color =
|
|
65
|
+
"var(--slidewise-accent, var(--accent))";
|
|
66
|
+
}}
|
|
67
|
+
onMouseLeave={(e) => {
|
|
68
|
+
e.currentTarget.style.borderColor = "var(--border-dashed)";
|
|
69
|
+
e.currentTarget.style.color = "var(--ink)";
|
|
70
|
+
}}
|
|
71
|
+
>
|
|
72
|
+
{children ?? <Plus size={14} />}
|
|
73
|
+
{resolved}
|
|
74
|
+
</button>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { CSSProperties, PropsWithChildren } from "react";
|
|
2
|
+
import { LayoutGrid } from "lucide-react";
|
|
3
|
+
import { useEditor } from "@/lib/StoreProvider";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Default rail header. Renders the grid-view button and a "current / total"
|
|
7
|
+
* slide counter. Hosts can pass `children` to replace the whole content
|
|
8
|
+
* while keeping the height + border, or render their own `<header>` instead.
|
|
9
|
+
*
|
|
10
|
+
* ```tsx
|
|
11
|
+
* // Default counter
|
|
12
|
+
* <Slidewise.SlideRail.Header />
|
|
13
|
+
*
|
|
14
|
+
* // Custom content
|
|
15
|
+
* <Slidewise.SlideRail.Header>
|
|
16
|
+
* <strong>{deckTitle}</strong>
|
|
17
|
+
* </Slidewise.SlideRail.Header>
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export interface SlideRailHeaderProps {
|
|
21
|
+
className?: string;
|
|
22
|
+
style?: CSSProperties;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function Header({
|
|
26
|
+
className,
|
|
27
|
+
style,
|
|
28
|
+
children,
|
|
29
|
+
}: PropsWithChildren<SlideRailHeaderProps>) {
|
|
30
|
+
const slides = useEditor((s) => s.deck.slides);
|
|
31
|
+
const currentId = useEditor((s) => s.currentSlideId);
|
|
32
|
+
const setView = useEditor((s) => s.setView);
|
|
33
|
+
const idx = slides.findIndex((s) => s.id === currentId);
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div
|
|
37
|
+
className={className}
|
|
38
|
+
style={{
|
|
39
|
+
height: 36,
|
|
40
|
+
display: "flex",
|
|
41
|
+
alignItems: "center",
|
|
42
|
+
justifyContent: "space-between",
|
|
43
|
+
padding: "0 12px",
|
|
44
|
+
fontSize: 12,
|
|
45
|
+
color: "var(--ink-muted)",
|
|
46
|
+
borderBottom: "1px solid var(--border)",
|
|
47
|
+
...style,
|
|
48
|
+
}}
|
|
49
|
+
>
|
|
50
|
+
{children ?? (
|
|
51
|
+
<>
|
|
52
|
+
<button
|
|
53
|
+
title="Slide overview"
|
|
54
|
+
aria-label="Open slide overview"
|
|
55
|
+
onClick={() => setView("grid")}
|
|
56
|
+
style={{
|
|
57
|
+
width: 28,
|
|
58
|
+
height: 28,
|
|
59
|
+
border: "none",
|
|
60
|
+
borderRadius: 6,
|
|
61
|
+
background: "transparent",
|
|
62
|
+
display: "flex",
|
|
63
|
+
alignItems: "center",
|
|
64
|
+
justifyContent: "center",
|
|
65
|
+
color: "var(--ink)",
|
|
66
|
+
cursor: "pointer",
|
|
67
|
+
}}
|
|
68
|
+
onMouseEnter={(e) =>
|
|
69
|
+
(e.currentTarget.style.background = "var(--hover-strong)")
|
|
70
|
+
}
|
|
71
|
+
onMouseLeave={(e) =>
|
|
72
|
+
(e.currentTarget.style.background = "transparent")
|
|
73
|
+
}
|
|
74
|
+
>
|
|
75
|
+
<LayoutGrid size={14} />
|
|
76
|
+
</button>
|
|
77
|
+
<span>
|
|
78
|
+
{idx + 1} / {slides.length}
|
|
79
|
+
</span>
|
|
80
|
+
</>
|
|
81
|
+
)}
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
84
|
+
}
|