-
-
Notifications
You must be signed in to change notification settings - Fork 68
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a tutorial on how to create slide show presentations with React F…
…low. (#438) * 💩 * ♻️ Refactor complete App example to match repo. * 📝 Add comment encouraging folks to play around with the slides object to see changes. * ♻️ Consistently use ReactFlow default export. * ✨ Create a focus-on-click example app. * 📝 Write more words. * 🚧 Keep hammering away. * ♻️ Refactor app to match standalone repo. * minor fixes to copy and code snippets * 🐛 Fixed a bug where slide controls were partially hidden by sandbox controls. * 📝 Callout the need for parent element to have width/height. * 📝 Add 'minZoom' prop to code examples, explain why. * 📝 Consistently name main component 'App' in code snippets. * 📝 Add missing 'initialSlide' definition in code snippet. * 📝 Tweak higlighted lines in codesnippet. * 📝 Make clearer that the 'FitViewOptions' snippet is from the api reference. * 🚚 Move url to 'slide-shows-with-react-flow'. * 🚚 Move url to 'slide-shows-with-react-flow'. * 🔧 Fix typo in config. * add tutorial image for socials, minor copy tweaks * minor css tweaks for demo at top of tutorial * 🎨 Align styles in all slide show examples. * ♻️ Upgrade examples to reactflow v12. * ♻️ Update tutorial to reactflow v12. * ♻️ Update examples to reactflow v12. * 📝 Update tutorial examples to reactflow v12. * ✨ Add whats-new entry. --------- Co-authored-by: johnrobbjr <[email protected]>
- Loading branch information
1 parent
f25fa21
commit edb1e46
Showing
32 changed files
with
12,209 additions
and
11,915 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file added
BIN
+199 KB
sites/reactflow.dev/public/img/tutorials/presentation/ideal-layout.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+510 KB
sites/reactflow.dev/public/img/tutorials/presentation/slideshow-thumb.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
52 changes: 52 additions & 0 deletions
52
sites/reactflow.dev/src/components/example-viewer/blog-flows/presentation/App/Slide.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import { type Node, type NodeProps, useReactFlow } from '@xyflow/react'; | ||
import { Remark } from 'react-remark'; | ||
import { useCallback } from 'react'; | ||
|
||
export type SlideNode = Node<SlideData, 'slide'>; | ||
|
||
export type SlideData = { | ||
source: string; | ||
left?: string; | ||
up?: string; | ||
down?: string; | ||
right?: string; | ||
}; | ||
|
||
export const SLIDE_WIDTH = 1920; | ||
export const SLIDE_HEIGHT = 1080; | ||
|
||
// The padding constant is used when computing the presentation layout. It adds | ||
// a bit of space between each slide | ||
export const SLIDE_PADDING = 100; | ||
|
||
const style = { | ||
width: `${SLIDE_WIDTH}px`, | ||
height: `${SLIDE_HEIGHT}px`, | ||
} satisfies React.CSSProperties; | ||
|
||
export function Slide({ data }: NodeProps<SlideNode>) { | ||
const { source, left, up, down, right } = data; | ||
const { fitView } = useReactFlow(); | ||
|
||
const moveToNextSlide = useCallback( | ||
(event: React.MouseEvent, id: string) => { | ||
// Prevent the click event from propagating so `onNodeClick` is not | ||
// triggered when clicking on the control buttons. | ||
event.stopPropagation(); | ||
fitView({ nodes: [{ id }], duration: 100 }); | ||
}, | ||
[fitView], | ||
); | ||
|
||
return ( | ||
<article className="slide" style={style}> | ||
<Remark>{source}</Remark> | ||
<footer className="slide__controls nopan"> | ||
{left && <button onClick={(e) => moveToNextSlide(e, left)}>←</button>} | ||
{up && <button onClick={(e) => moveToNextSlide(e, up)}>↑</button>} | ||
{down && <button onClick={(e) => moveToNextSlide(e, down)}>↓</button>} | ||
{right && <button onClick={(e) => moveToNextSlide(e, right)}>→</button>} | ||
</footer> | ||
</article> | ||
); | ||
} |
63 changes: 63 additions & 0 deletions
63
sites/reactflow.dev/src/components/example-viewer/blog-flows/presentation/App/index.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
body { | ||
margin: 0; | ||
background-color: #f8f8f8; | ||
font-family: sans-serif; | ||
color: #111; | ||
} | ||
|
||
html, | ||
body, | ||
#root { | ||
height: 100%; | ||
} | ||
|
||
.slide { | ||
box-sizing: border-box; | ||
box-shadow: 0rem 1rem 4rem 0.25rem rgba(0, 0, 0, 0.06); | ||
width: 1920px; | ||
height: 1080px; | ||
overflow: hidden; | ||
position: relative; | ||
background: #fff; | ||
border: 1px solid #e0e0e0; | ||
border-radius: 8px; | ||
padding: 6rem; | ||
padding-top: 10rem; | ||
padding-left: 8rem; | ||
font-size: 4rem; | ||
} | ||
|
||
.slide h1 { | ||
font-size: 8rem; | ||
margin-bottom: 1rem; | ||
} | ||
|
||
.slide ul li { | ||
margin-bottom: 1rem; | ||
} | ||
|
||
.slide__controls { | ||
position: absolute; | ||
bottom: 4rem; | ||
right: 4rem; | ||
display: flex; | ||
justify-content: end; | ||
gap: 1rem; | ||
} | ||
|
||
.slide__controls button { | ||
font-size: 4rem; | ||
padding: 1rem 2rem; | ||
border-radius: 1rem; | ||
border: 2px solid #000; | ||
background-color: #fff; | ||
color: #111; | ||
|
||
&:hover { | ||
background-color: #f8f8f8; | ||
} | ||
|
||
&:active { | ||
background-color: #e0e0e0; | ||
} | ||
} |
85 changes: 85 additions & 0 deletions
85
sites/reactflow.dev/src/components/example-viewer/blog-flows/presentation/App/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import { KeyboardEventHandler, useCallback, useState } from 'react'; | ||
import { | ||
ReactFlow, | ||
ReactFlowProvider, | ||
useReactFlow, | ||
NodeMouseHandler, | ||
Background, | ||
BackgroundVariant, | ||
} from '@xyflow/react'; | ||
|
||
import '@xyflow/react/dist/style.css'; | ||
import './index.css'; | ||
|
||
import { Slide, SlideData } from './Slide'; | ||
import { slides, slidesToElements } from './slides'; | ||
|
||
const nodeTypes = { | ||
slide: Slide, | ||
}; | ||
|
||
const initialSlide = '01'; | ||
const { nodes, edges } = slidesToElements(initialSlide, slides); | ||
|
||
function App() { | ||
const [currentSlide, setCurrentSlide] = useState(initialSlide); | ||
const { fitView } = useReactFlow(); | ||
|
||
const handleKeyPress = useCallback<KeyboardEventHandler>( | ||
(event) => { | ||
const slide = slides[currentSlide]; | ||
|
||
switch (event.key) { | ||
case 'ArrowLeft': | ||
case 'ArrowUp': | ||
case 'ArrowDown': | ||
case 'ArrowRight': { | ||
const direction = event.key.slice(5).toLowerCase() as keyof SlideData; | ||
const target = slide[direction]; | ||
|
||
// Prevent the arrow keys from scrolling the page when React Flow is | ||
// only part of a larger application. | ||
event.preventDefault(); | ||
|
||
if (target) { | ||
setCurrentSlide(target); | ||
fitView({ nodes: [{ id: target }], duration: 100 }); | ||
} | ||
} | ||
} | ||
}, | ||
[fitView, currentSlide], | ||
); | ||
|
||
const handleNodeClick = useCallback<NodeMouseHandler>( | ||
(_, node) => { | ||
if (node.id !== currentSlide) { | ||
setCurrentSlide(node.id); | ||
fitView({ nodes: [{ id: node.id }], duration: 100 }); | ||
} | ||
}, | ||
[fitView, currentSlide], | ||
); | ||
|
||
return ( | ||
<ReactFlow | ||
nodes={nodes} | ||
nodeTypes={nodeTypes} | ||
nodesDraggable={false} | ||
edges={edges} | ||
fitView | ||
fitViewOptions={{ nodes: [{ id: initialSlide }], duration: 100 }} | ||
minZoom={0.1} | ||
onKeyDown={handleKeyPress} | ||
onNodeClick={handleNodeClick} | ||
> | ||
<Background color="#f2f2f2" variant={BackgroundVariant.Lines} /> | ||
</ReactFlow> | ||
); | ||
} | ||
|
||
export default () => ( | ||
<ReactFlowProvider> | ||
<App /> | ||
</ReactFlowProvider> | ||
); |
135 changes: 135 additions & 0 deletions
135
sites/reactflow.dev/src/components/example-viewer/blog-flows/presentation/App/slides.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
import { Edge, Node } from '@xyflow/react'; | ||
import { SLIDE_WIDTH, SLIDE_HEIGHT, SLIDE_PADDING, SlideData } from './Slide'; | ||
|
||
const slide01 = { | ||
id: '01', | ||
data: { | ||
right: '02', | ||
source: ` | ||
# Slide 1 | ||
- This is the first slide | ||
- It has a right arrow to go to the next slide | ||
`, | ||
}, | ||
}; | ||
|
||
const slide02 = { | ||
id: '02', | ||
data: { | ||
left: '01', | ||
up: '03', | ||
right: '04', | ||
source: ` | ||
# Slide 2 | ||
- This is the second slide | ||
- It has a left arrow to go back to the first slide | ||
- It has an up arrow to go to the third slide | ||
- It has a right arrow to go to the fourth slide | ||
`, | ||
}, | ||
}; | ||
|
||
const slide03 = { | ||
id: '03', | ||
data: { | ||
down: '02', | ||
source: ` | ||
# Slide 3 | ||
- This is the third slide | ||
- It has a down arrow to go back to the second slide | ||
`, | ||
}, | ||
}; | ||
|
||
const slide04 = { | ||
id: '04', | ||
data: { | ||
left: '02', | ||
source: ` | ||
# Slide 4 | ||
- This is the fourth slide | ||
- It has a left arrow to go back to the second slide | ||
`, | ||
}, | ||
}; | ||
|
||
export const slides = Object.fromEntries( | ||
[slide01, slide02, slide03, slide04].map(({ id, data }) => [id, data]), | ||
) as Record<string, SlideData>; | ||
|
||
export const slidesToElements = ( | ||
initial: string, | ||
slides: Record<string, SlideData>, | ||
) => { | ||
const stack = [{ id: initial, position: { x: 0, y: 0 } }]; | ||
const visited = new Set(); | ||
const nodes: Node<SlideData>[] = []; | ||
const edges: Edge[] = []; | ||
|
||
while (stack.length) { | ||
const { id, position } = stack.pop()!; | ||
const data = slides[id]; | ||
const node = { id, type: 'slide', position, data }; | ||
|
||
if (data.left && !visited.has(data.left)) { | ||
const nextPosition = { | ||
x: position.x - (SLIDE_WIDTH + SLIDE_PADDING), | ||
y: position.y, | ||
}; | ||
|
||
stack.push({ id: data.left, position: nextPosition }); | ||
edges.push({ | ||
id: `${id}->${data.left}`, | ||
source: id, | ||
target: data.left, | ||
}); | ||
} | ||
|
||
if (data.up && !visited.has(data.up)) { | ||
const nextPosition = { | ||
x: position.x, | ||
y: position.y - (SLIDE_HEIGHT + SLIDE_PADDING), | ||
}; | ||
|
||
stack.push({ id: data.up, position: nextPosition }); | ||
edges.push({ id: `${id}->${data.up}`, source: id, target: data.up }); | ||
} | ||
|
||
if (data.down && !visited.has(data.down)) { | ||
const nextPosition = { | ||
x: position.x, | ||
y: position.y + (SLIDE_HEIGHT + SLIDE_PADDING), | ||
}; | ||
|
||
stack.push({ id: data.down, position: nextPosition }); | ||
edges.push({ | ||
id: `${id}->${data.down}`, | ||
source: id, | ||
target: data.down, | ||
}); | ||
} | ||
|
||
if (data.right && !visited.has(data.right)) { | ||
const nextPosition = { | ||
x: position.x + (SLIDE_WIDTH + SLIDE_PADDING), | ||
y: position.y, | ||
}; | ||
|
||
stack.push({ id: data.right, position: nextPosition }); | ||
edges.push({ | ||
id: `${id}->${data.down}`, | ||
source: id, | ||
target: data.right, | ||
}); | ||
} | ||
|
||
nodes.push(node); | ||
visited.add(id); | ||
} | ||
|
||
return { nodes, edges }; | ||
}; |
28 changes: 28 additions & 0 deletions
28
...eactflow.dev/src/components/example-viewer/blog-flows/presentation/FocusOnClick/Slide.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { type Node, type NodeProps } from '@xyflow/react'; | ||
import { Remark } from 'react-remark'; | ||
|
||
export const SLIDE_WIDTH = 1920; | ||
export const SLIDE_HEIGHT = 1080; | ||
|
||
export type SlideNode = Node<SlideData, 'slide'>; | ||
|
||
export type SlideData = { | ||
source: string; | ||
left?: string; | ||
up?: string; | ||
down?: string; | ||
right?: string; | ||
}; | ||
|
||
const style = { | ||
width: `${SLIDE_WIDTH}px`, | ||
height: `${SLIDE_HEIGHT}px`, | ||
} satisfies React.CSSProperties; | ||
|
||
export function Slide({ data }: NodeProps<SlideNode>) { | ||
return ( | ||
<article className="slide" style={style}> | ||
<Remark>{data.source}</Remark> | ||
</article> | ||
); | ||
} |
Oops, something went wrong.