@textcortex/slidewise 1.6.0 → 1.7.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 +3374 -3365
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/SlidewiseEditor.tsx +1 -1
- package/src/compound/index.ts +13 -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 +10 -0
- package/src/components/editor/SlideRail.tsx +0 -285
package/package.json
CHANGED
package/src/SlidewiseEditor.tsx
CHANGED
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,
|
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
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { CSSProperties, PropsWithChildren } from "react";
|
|
2
|
+
import { useEditor } from "@/lib/StoreProvider";
|
|
3
|
+
import type { Slide } from "@/lib/types";
|
|
4
|
+
import { SlideRailItemProvider } from "./ItemContext";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Wraps a single slide in the rail. Provides the slide to descendants via
|
|
8
|
+
* context (read with `useSlideRailItem`) and wires click → `selectSlide`.
|
|
9
|
+
*
|
|
10
|
+
* The default subparts (`Thumbnail`, `Number`) read the slide off the
|
|
11
|
+
* context, so hosts can rearrange them freely. Host content (e.g. a
|
|
12
|
+
* three-dots menu, a duplicate button) drops in as additional children.
|
|
13
|
+
*
|
|
14
|
+
* ```tsx
|
|
15
|
+
* <Slidewise.SlideRail.Item slide={slide}>
|
|
16
|
+
* <Slidewise.SlideRail.Thumbnail />
|
|
17
|
+
* <Slidewise.SlideRail.Number />
|
|
18
|
+
* <MyContextMenu slide={slide} />
|
|
19
|
+
* </Slidewise.SlideRail.Item>
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export interface SlideRailItemProps {
|
|
23
|
+
slide: Slide;
|
|
24
|
+
className?: string;
|
|
25
|
+
style?: CSSProperties;
|
|
26
|
+
/**
|
|
27
|
+
* Override the click handler. The default selects the slide via the
|
|
28
|
+
* editor store; pass a function here to add host behavior (e.g. open
|
|
29
|
+
* a context drawer instead of selecting).
|
|
30
|
+
*/
|
|
31
|
+
onClick?: (slide: Slide) => void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function Item({
|
|
35
|
+
slide,
|
|
36
|
+
className,
|
|
37
|
+
style,
|
|
38
|
+
onClick,
|
|
39
|
+
children,
|
|
40
|
+
}: PropsWithChildren<SlideRailItemProps>) {
|
|
41
|
+
const slides = useEditor((s) => s.deck.slides);
|
|
42
|
+
const currentId = useEditor((s) => s.currentSlideId);
|
|
43
|
+
const selectSlide = useEditor((s) => s.selectSlide);
|
|
44
|
+
const index = slides.findIndex((s) => s.id === slide.id);
|
|
45
|
+
const isCurrent = slide.id === currentId;
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<SlideRailItemProvider value={{ slide, index, isCurrent }}>
|
|
49
|
+
<div
|
|
50
|
+
className={className}
|
|
51
|
+
onClick={() => (onClick ? onClick(slide) : selectSlide(slide.id))}
|
|
52
|
+
role="button"
|
|
53
|
+
tabIndex={0}
|
|
54
|
+
aria-current={isCurrent ? "true" : undefined}
|
|
55
|
+
aria-label={`Open slide ${index + 1}`}
|
|
56
|
+
onKeyDown={(e) => {
|
|
57
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
58
|
+
e.preventDefault();
|
|
59
|
+
onClick ? onClick(slide) : selectSlide(slide.id);
|
|
60
|
+
}
|
|
61
|
+
}}
|
|
62
|
+
style={{
|
|
63
|
+
position: "relative",
|
|
64
|
+
padding: "0 12px",
|
|
65
|
+
marginBottom: 14,
|
|
66
|
+
cursor: "pointer",
|
|
67
|
+
background:
|
|
68
|
+
"var(--slidewise-bg-rail-item, transparent)",
|
|
69
|
+
...(isCurrent
|
|
70
|
+
? {
|
|
71
|
+
background:
|
|
72
|
+
"var(--slidewise-bg-rail-item-active, var(--accent-soft))",
|
|
73
|
+
}
|
|
74
|
+
: null),
|
|
75
|
+
...style,
|
|
76
|
+
}}
|
|
77
|
+
>
|
|
78
|
+
{children}
|
|
79
|
+
</div>
|
|
80
|
+
</SlideRailItemProvider>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
import type { Slide } from "@/lib/types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Context handed to descendants of `<SlideRail.Item>` so leaf subparts
|
|
6
|
+
* (`Thumbnail`, `Number`) and host content can read which slide they're
|
|
7
|
+
* rendering against without prop drilling.
|
|
8
|
+
*/
|
|
9
|
+
export interface SlideRailItemContextValue {
|
|
10
|
+
slide: Slide;
|
|
11
|
+
/** Zero-based position in `deck.slides`. */
|
|
12
|
+
index: number;
|
|
13
|
+
/** True when this is the editor's currently-focused slide. */
|
|
14
|
+
isCurrent: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const ItemContext = createContext<SlideRailItemContextValue | null>(null);
|
|
18
|
+
|
|
19
|
+
export function SlideRailItemProvider({
|
|
20
|
+
value,
|
|
21
|
+
children,
|
|
22
|
+
}: {
|
|
23
|
+
value: SlideRailItemContextValue;
|
|
24
|
+
children: React.ReactNode;
|
|
25
|
+
}) {
|
|
26
|
+
return <ItemContext.Provider value={value}>{children}</ItemContext.Provider>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Read the current item context. Throws when used outside a
|
|
31
|
+
* `<SlideRail.Item>` — the leaf subparts are meaningless without a
|
|
32
|
+
* concrete slide to render.
|
|
33
|
+
*/
|
|
34
|
+
export function useSlideRailItem(): SlideRailItemContextValue {
|
|
35
|
+
const ctx = useContext(ItemContext);
|
|
36
|
+
if (!ctx) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
"useSlideRailItem must be used inside <Slidewise.SlideRail.Item>"
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
return ctx;
|
|
42
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { CSSProperties, ReactNode } from "react";
|
|
2
|
+
import { useEditor } from "@/lib/StoreProvider";
|
|
3
|
+
import type { Slide } from "@/lib/types";
|
|
4
|
+
import { Item } from "./Item";
|
|
5
|
+
import { Thumbnail } from "./Thumbnail";
|
|
6
|
+
import { Number as SlideNumber } from "./Number";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Iterates `deck.slides` and renders each one. Pass a render-prop child
|
|
10
|
+
* for full control over what each row contains; omit the child to get
|
|
11
|
+
* the default `<Item><Thumbnail /><Number /></Item>` layout.
|
|
12
|
+
*
|
|
13
|
+
* ```tsx
|
|
14
|
+
* // Default
|
|
15
|
+
* <Slidewise.SlideRail.List />
|
|
16
|
+
*
|
|
17
|
+
* // Custom — add a per-row context menu
|
|
18
|
+
* <Slidewise.SlideRail.List>
|
|
19
|
+
* {(slide) => (
|
|
20
|
+
* <Slidewise.SlideRail.Item slide={slide}>
|
|
21
|
+
* <Slidewise.SlideRail.Thumbnail />
|
|
22
|
+
* <Slidewise.SlideRail.Number />
|
|
23
|
+
* <MyContextMenu slide={slide} />
|
|
24
|
+
* </Slidewise.SlideRail.Item>
|
|
25
|
+
* )}
|
|
26
|
+
* </Slidewise.SlideRail.List>
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export interface SlideRailListProps {
|
|
30
|
+
className?: string;
|
|
31
|
+
style?: CSSProperties;
|
|
32
|
+
/**
|
|
33
|
+
* Optional render-prop. Receives each slide + its zero-based index;
|
|
34
|
+
* return the row content. When omitted, the default row layout
|
|
35
|
+
* is used.
|
|
36
|
+
*/
|
|
37
|
+
children?: (slide: Slide, index: number) => ReactNode;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function List({ className, style, children }: SlideRailListProps) {
|
|
41
|
+
const slides = useEditor((s) => s.deck.slides);
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<div
|
|
45
|
+
className={className}
|
|
46
|
+
style={{
|
|
47
|
+
flex: 1,
|
|
48
|
+
overflowY: "auto",
|
|
49
|
+
padding: "12px 0",
|
|
50
|
+
...style,
|
|
51
|
+
}}
|
|
52
|
+
>
|
|
53
|
+
{slides.map((slide, index) =>
|
|
54
|
+
children ? (
|
|
55
|
+
// Render-prop branch: host returns whatever they want, typically
|
|
56
|
+
// wrapping their JSX in a SlideRail.Item to wire selection.
|
|
57
|
+
<div key={slide.id}>{children(slide, index)}</div>
|
|
58
|
+
) : (
|
|
59
|
+
<Item key={slide.id} slide={slide}>
|
|
60
|
+
<Thumbnail />
|
|
61
|
+
<SlideNumber />
|
|
62
|
+
</Item>
|
|
63
|
+
)
|
|
64
|
+
)}
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { CSSProperties } from "react";
|
|
2
|
+
import { useSlideRailItem } from "./ItemContext";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Renders the slide's "01" / "02" badge in the top-left corner of the rail
|
|
6
|
+
* item. Reads the current slide's index from `<SlideRail.Item>`'s context.
|
|
7
|
+
*
|
|
8
|
+
* Render after `<SlideRail.Thumbnail>` so it stacks on top (or position
|
|
9
|
+
* it manually via `style`).
|
|
10
|
+
*/
|
|
11
|
+
export interface SlideRailNumberProps {
|
|
12
|
+
className?: string;
|
|
13
|
+
style?: CSSProperties;
|
|
14
|
+
/**
|
|
15
|
+
* Format the displayed number. Default is zero-padded to width 2
|
|
16
|
+
* (`01`, `02`, …, `10`).
|
|
17
|
+
*/
|
|
18
|
+
format?: (index: number) => string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const defaultFormat = (i: number) => String(i + 1).padStart(2, "0");
|
|
22
|
+
|
|
23
|
+
export function Number({
|
|
24
|
+
className,
|
|
25
|
+
style,
|
|
26
|
+
format = defaultFormat,
|
|
27
|
+
}: SlideRailNumberProps = {}) {
|
|
28
|
+
const { index, isCurrent } = useSlideRailItem();
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<span
|
|
32
|
+
className={className}
|
|
33
|
+
aria-hidden="true"
|
|
34
|
+
style={{
|
|
35
|
+
position: "absolute",
|
|
36
|
+
left: 20,
|
|
37
|
+
top: 6,
|
|
38
|
+
zIndex: 2,
|
|
39
|
+
width: 22,
|
|
40
|
+
height: 22,
|
|
41
|
+
borderRadius: 5,
|
|
42
|
+
background: isCurrent ? "var(--ink)" : "#6B7280",
|
|
43
|
+
color: isCurrent ? "var(--app-bg)" : "#fff",
|
|
44
|
+
display: "flex",
|
|
45
|
+
alignItems: "center",
|
|
46
|
+
justifyContent: "center",
|
|
47
|
+
fontSize: 11,
|
|
48
|
+
fontWeight: 700,
|
|
49
|
+
fontFamily: "Inter, system-ui, sans-serif",
|
|
50
|
+
pointerEvents: "none",
|
|
51
|
+
...style,
|
|
52
|
+
}}
|
|
53
|
+
>
|
|
54
|
+
{format(index)}
|
|
55
|
+
</span>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { CSSProperties, PropsWithChildren } from "react";
|
|
2
|
+
|
|
3
|
+
export const SLIDERAIL_DEFAULT_WIDTH = 168;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Container for the slide rail subparts. Owns the rail's width, surface
|
|
7
|
+
* color, and divider. Hosts replace this when they want a different rail
|
|
8
|
+
* geometry (wider, narrower, full-bleed, etc.) — otherwise just render
|
|
9
|
+
* subparts inside it.
|
|
10
|
+
*
|
|
11
|
+
* ```tsx
|
|
12
|
+
* <Slidewise.SlideRail.Root>
|
|
13
|
+
* <Slidewise.SlideRail.Header />
|
|
14
|
+
* <Slidewise.SlideRail.List />
|
|
15
|
+
* <Slidewise.SlideRail.AddButton />
|
|
16
|
+
* </Slidewise.SlideRail.Root>
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export interface SlideRailRootProps {
|
|
20
|
+
className?: string;
|
|
21
|
+
style?: CSSProperties;
|
|
22
|
+
/** Rail width in pixels. Defaults to 168. */
|
|
23
|
+
width?: number | string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function Root({
|
|
27
|
+
className,
|
|
28
|
+
style,
|
|
29
|
+
width = SLIDERAIL_DEFAULT_WIDTH,
|
|
30
|
+
children,
|
|
31
|
+
}: PropsWithChildren<SlideRailRootProps>) {
|
|
32
|
+
return (
|
|
33
|
+
<div
|
|
34
|
+
className={
|
|
35
|
+
className ? `slidewise-rail ${className}` : "slidewise-rail"
|
|
36
|
+
}
|
|
37
|
+
style={{
|
|
38
|
+
width,
|
|
39
|
+
flexShrink: 0,
|
|
40
|
+
background: "var(--slidewise-bg-rail, var(--rail-bg))",
|
|
41
|
+
borderRight: "1px solid var(--border)",
|
|
42
|
+
boxShadow: "var(--rail-shadow)",
|
|
43
|
+
display: "flex",
|
|
44
|
+
flexDirection: "column",
|
|
45
|
+
fontFamily: "Inter, system-ui, sans-serif",
|
|
46
|
+
overflow: "hidden",
|
|
47
|
+
zIndex: 5,
|
|
48
|
+
...style,
|
|
49
|
+
}}
|
|
50
|
+
>
|
|
51
|
+
{children}
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { CSSProperties } from "react";
|
|
2
|
+
import { SlideView } from "@/components/editor/SlideView";
|
|
3
|
+
import { SLIDE_W } from "@/lib/types";
|
|
4
|
+
import { useSlideRailItem } from "./ItemContext";
|
|
5
|
+
|
|
6
|
+
const DEFAULT_THUMB_W = 132;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Renders the slide preview inside the rail item. Reads the current slide
|
|
10
|
+
* from `<SlideRail.Item>`'s context. Honors the focused/non-focused state
|
|
11
|
+
* via an accent border.
|
|
12
|
+
*
|
|
13
|
+
* ```tsx
|
|
14
|
+
* <Slidewise.SlideRail.Item slide={slide}>
|
|
15
|
+
* <Slidewise.SlideRail.Thumbnail />
|
|
16
|
+
* </Slidewise.SlideRail.Item>
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export interface SlideRailThumbnailProps {
|
|
20
|
+
className?: string;
|
|
21
|
+
style?: CSSProperties;
|
|
22
|
+
/** Pixel width for the rendered thumbnail. Defaults to 132. */
|
|
23
|
+
width?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function Thumbnail({
|
|
27
|
+
className,
|
|
28
|
+
style,
|
|
29
|
+
width = DEFAULT_THUMB_W,
|
|
30
|
+
}: SlideRailThumbnailProps = {}) {
|
|
31
|
+
const { slide, isCurrent } = useSlideRailItem();
|
|
32
|
+
const scale = width / SLIDE_W;
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div style={{ position: "relative" }}>
|
|
36
|
+
<div
|
|
37
|
+
className={className}
|
|
38
|
+
style={{
|
|
39
|
+
display: "block",
|
|
40
|
+
width,
|
|
41
|
+
border: isCurrent
|
|
42
|
+
? "2px solid var(--slidewise-accent, var(--accent))"
|
|
43
|
+
: "2px solid transparent",
|
|
44
|
+
borderRadius: 8,
|
|
45
|
+
padding: 0,
|
|
46
|
+
background: "transparent",
|
|
47
|
+
overflow: "hidden",
|
|
48
|
+
transition: "border-color 120ms",
|
|
49
|
+
...style,
|
|
50
|
+
}}
|
|
51
|
+
>
|
|
52
|
+
<div style={{ pointerEvents: "none" }}>
|
|
53
|
+
<SlideView slide={slide} scale={scale} />
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { CSSProperties } from "react";
|
|
2
|
+
import { Root, type SlideRailRootProps } from "./Root";
|
|
3
|
+
import { Header, type SlideRailHeaderProps } from "./Header";
|
|
4
|
+
import { List, type SlideRailListProps } from "./List";
|
|
5
|
+
import { Item, type SlideRailItemProps } from "./Item";
|
|
6
|
+
import { Thumbnail, type SlideRailThumbnailProps } from "./Thumbnail";
|
|
7
|
+
import { Number, type SlideRailNumberProps } from "./Number";
|
|
8
|
+
import { AddButton, type SlideRailAddButtonProps } from "./AddButton";
|
|
9
|
+
|
|
10
|
+
export interface SlideRailProps {
|
|
11
|
+
className?: string;
|
|
12
|
+
style?: CSSProperties;
|
|
13
|
+
/** Rail width; forwarded to `<SlideRail.Root>`. */
|
|
14
|
+
width?: number | string;
|
|
15
|
+
/** Omit the header. */
|
|
16
|
+
hideHeader?: boolean;
|
|
17
|
+
/** Omit the "New Slide" button. */
|
|
18
|
+
hideAddButton?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Default SlideRail arrangement. Equivalent to:
|
|
23
|
+
*
|
|
24
|
+
* ```tsx
|
|
25
|
+
* <Slidewise.SlideRail.Root>
|
|
26
|
+
* <Slidewise.SlideRail.Header />
|
|
27
|
+
* <Slidewise.SlideRail.List />
|
|
28
|
+
* <Slidewise.SlideRail.AddButton />
|
|
29
|
+
* </Slidewise.SlideRail.Root>
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* For full control (per-row context menus, custom thumbnail layout,
|
|
33
|
+
* etc.), drop down to the subparts directly.
|
|
34
|
+
*/
|
|
35
|
+
function DefaultSlideRail({
|
|
36
|
+
className,
|
|
37
|
+
style,
|
|
38
|
+
width,
|
|
39
|
+
hideHeader,
|
|
40
|
+
hideAddButton,
|
|
41
|
+
}: SlideRailProps = {}) {
|
|
42
|
+
return (
|
|
43
|
+
<Root className={className} style={style} width={width}>
|
|
44
|
+
{!hideHeader && <Header />}
|
|
45
|
+
<List />
|
|
46
|
+
{!hideAddButton && <AddButton />}
|
|
47
|
+
</Root>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* `<Slidewise.SlideRail />` is both the default arrangement and a
|
|
53
|
+
* namespace of subparts. Mirrors `<Slidewise.TopBar>`.
|
|
54
|
+
*/
|
|
55
|
+
export const SlideRail = Object.assign(DefaultSlideRail, {
|
|
56
|
+
Root,
|
|
57
|
+
Header,
|
|
58
|
+
List,
|
|
59
|
+
Item,
|
|
60
|
+
Thumbnail,
|
|
61
|
+
Number,
|
|
62
|
+
AddButton,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
export { useSlideRailItem, type SlideRailItemContextValue } from "./ItemContext";
|
|
66
|
+
export type {
|
|
67
|
+
SlideRailRootProps,
|
|
68
|
+
SlideRailHeaderProps,
|
|
69
|
+
SlideRailListProps,
|
|
70
|
+
SlideRailItemProps,
|
|
71
|
+
SlideRailThumbnailProps,
|
|
72
|
+
SlideRailNumberProps,
|
|
73
|
+
SlideRailAddButtonProps,
|
|
74
|
+
};
|