Skip to content

Commit 4833e04

Browse files
authored
Example: Update comments canvas to use high-level components (liveblocks#3143)
1 parent f1d63cb commit 4833e04

8 files changed

Lines changed: 953 additions & 553 deletions

File tree

examples/nextjs-comments-canvas/package-lock.json

Lines changed: 796 additions & 218 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/nextjs-comments-canvas/package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@
1010
},
1111
"dependencies": {
1212
"@dnd-kit/core": "^6.1.0",
13-
"@liveblocks/client": "^3.14.0",
14-
"@liveblocks/node": "^3.14.0",
15-
"@liveblocks/react": "^3.14.0",
16-
"@liveblocks/react-ui": "^3.14.0",
13+
"@liveblocks/client": "^3.15.0-components1",
14+
"@liveblocks/node": "^3.15.0-components1",
15+
"@liveblocks/react": "^3.15.0-components1",
16+
"@liveblocks/react-ui": "^3.15.0-components1",
1717
"next": "^16.1.6",
1818
"react": "^18.3.1",
1919
"react-dom": "^18.3.1",

examples/nextjs-comments-canvas/src/components/CommentsCanvas.module.css

Lines changed: 0 additions & 53 deletions
This file was deleted.
Lines changed: 31 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,17 @@
1-
import { Thread } from "@liveblocks/react-ui";
2-
import { ThreadData } from "@liveblocks/core";
1+
import { ThreadData } from "@liveblocks/client";
32
import { useThreads, useEditThreadMetadata } from "@liveblocks/react/suspense";
4-
import { useUser } from "@liveblocks/react";
53
import {
64
DataRef,
75
DndContext,
86
DragEndEvent,
97
PointerSensor,
108
TouchSensor,
11-
useDraggable,
129
useSensor,
1310
useSensors,
1411
} from "@dnd-kit/core";
15-
import { useCallback, useMemo, useRef, useState } from "react";
16-
import styles from "./CommentsCanvas.module.css";
17-
import { Toolbar } from "./Toolbar";
18-
import { useMaxZIndex, useNearEdge } from "../hooks";
12+
import { useCallback } from "react";
13+
import { PlaceThreadButton } from "./PlaceThreadButton";
14+
import { DraggableThread } from "./DraggableThread";
1915

2016
export function CommentsCanvas() {
2117
const { threads } = useThreads();
@@ -36,103 +32,41 @@ export function CommentsCanvas() {
3632
);
3733

3834
// On drag end, update thread metadata with new coords
39-
const handleDragEnd = useCallback(({ active, delta }: DragEndEvent) => {
40-
const thread = (active.data as DataRef<{ thread: ThreadData }>).current
41-
?.thread;
42-
43-
if (!thread) {
44-
return;
45-
}
46-
47-
editThreadMetadata({
48-
threadId: thread.id,
49-
metadata: {
50-
x: thread.metadata.x + delta.x,
51-
y: thread.metadata.y + delta.y,
52-
},
53-
});
54-
}, []);
55-
56-
return (
57-
<div className={`${styles.wrapper} lb-root`}>
58-
<div className={styles.threads}>
59-
<DndContext onDragEnd={handleDragEnd} sensors={sensors}>
60-
{threads.map((thread) => (
61-
<DraggableThread key={thread.id} thread={thread} />
62-
))}
63-
</DndContext>
64-
</div>
65-
<Toolbar />
66-
</div>
35+
const handleDragEnd = useCallback(
36+
({ active, delta }: DragEndEvent) => {
37+
const thread = (active.data as DataRef<{ thread: ThreadData }>).current
38+
?.thread;
39+
if (!thread) {
40+
return;
41+
}
42+
editThreadMetadata({
43+
threadId: thread.id,
44+
metadata: {
45+
x: thread.metadata.x + delta.x,
46+
y: thread.metadata.y + delta.y,
47+
},
48+
});
49+
},
50+
[editThreadMetadata]
6751
);
68-
}
69-
70-
// A draggable thread
71-
function DraggableThread({ thread }: { thread: ThreadData }) {
72-
// Open threads that have just been created
73-
const startOpen = useMemo(() => {
74-
return Number(new Date()) - Number(new Date(thread.createdAt)) <= 100;
75-
}, [thread]);
76-
const [open, setOpen] = useState(startOpen);
77-
78-
// Enable drag
79-
const { attributes, listeners, setNodeRef, transform, node } = useDraggable({
80-
id: thread.id,
81-
data: { thread }, // Pass thread to DndContext drag end event
82-
});
83-
84-
// If currently dragging, add drag values to current metadata
85-
const x = transform ? transform.x + thread.metadata.x : thread.metadata.x;
86-
const y = transform ? transform.y + thread.metadata.y : thread.metadata.y;
87-
88-
// Get the creator of the thread
89-
const { user: creator } = useUser(thread.comments[0].userId);
90-
91-
// Used to set z-index higher than other threads when pointer down
92-
const editThreadMetadata = useEditThreadMetadata();
93-
const maxZIndex = useMaxZIndex();
94-
95-
// Used to flip thread near edge of screen
96-
const { nearRightEdge, nearBottomEdge } = useNearEdge(node);
9752

9853
return (
9954
<div
100-
ref={setNodeRef}
101-
className={styles.draggableThread}
102-
onPointerDown={() =>
103-
editThreadMetadata({
104-
threadId: thread.id,
105-
metadata: { zIndex: maxZIndex + 1 },
106-
})
107-
}
10855
style={{
109-
transform: `translate3d(${x}px, ${y}px, 0)`,
110-
zIndex: thread.metadata?.zIndex || 0,
56+
position: "relative",
57+
width: "100vw",
58+
height: "100vh",
59+
overflow: "hidden",
11160
}}
11261
>
113-
<div {...listeners} {...attributes}>
114-
<div className={styles.avatar} onClick={() => setOpen(!open)}>
115-
{creator ? (
116-
<img
117-
src={creator.avatar}
118-
alt={creator.name}
119-
width="28px"
120-
height="28px"
121-
draggable={false}
122-
/>
123-
) : (
124-
<div />
125-
)}
126-
</div>
62+
<div style={{ isolation: "isolate" }}>
63+
<DndContext onDragEnd={handleDragEnd} sensors={sensors}>
64+
{threads.map((thread) => (
65+
<DraggableThread key={thread.id} thread={thread} />
66+
))}
67+
</DndContext>
12768
</div>
128-
{open ? (
129-
<Thread
130-
thread={thread}
131-
className="thread"
132-
data-flip-vertical={nearBottomEdge || undefined}
133-
data-flip-horizontal={nearRightEdge || undefined}
134-
/>
135-
) : null}
69+
<PlaceThreadButton />
13670
</div>
13771
);
13872
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { useMemo } from "react";
2+
import { useEditThreadMetadata } from "@liveblocks/react/suspense";
3+
import { FloatingThread, CommentPin } from "@liveblocks/react-ui";
4+
import { ThreadData } from "@liveblocks/client";
5+
import { useDraggable } from "@dnd-kit/core";
6+
import { useMaxZIndex } from "../hooks";
7+
8+
// A draggable thread
9+
export function DraggableThread({ thread }: { thread: ThreadData }) {
10+
// Open threads that have just been created
11+
const defaultOpen = useMemo(() => {
12+
return Number(new Date()) - Number(new Date(thread.createdAt)) <= 100;
13+
}, [thread]);
14+
15+
// Enable drag
16+
const { isDragging, attributes, listeners, setNodeRef, transform } =
17+
useDraggable({
18+
id: thread.id,
19+
data: { thread }, // Pass thread to DndContext drag end event
20+
});
21+
22+
// If currently dragging, add drag values to current metadata
23+
const x = transform ? transform.x + thread.metadata.x : thread.metadata.x;
24+
const y = transform ? transform.y + thread.metadata.y : thread.metadata.y;
25+
26+
// Used to set z-index higher than other threads when dragging
27+
const editThreadMetadata = useEditThreadMetadata();
28+
const maxZIndex = useMaxZIndex();
29+
30+
return (
31+
<FloatingThread
32+
thread={thread}
33+
defaultOpen={defaultOpen}
34+
side="right"
35+
style={{ pointerEvents: isDragging ? "none" : "auto" }}
36+
>
37+
<div
38+
ref={setNodeRef}
39+
onPointerDown={() =>
40+
editThreadMetadata({
41+
threadId: thread.id,
42+
metadata: { zIndex: maxZIndex + 1 },
43+
})
44+
}
45+
style={{
46+
position: "absolute",
47+
top: 0,
48+
left: 0,
49+
transform: `translate3d(${x}px, ${y}px, 0)`,
50+
zIndex: thread.metadata?.zIndex || 0,
51+
}}
52+
>
53+
<CommentPin
54+
userId={thread.comments[0]?.userId}
55+
corner="top-left"
56+
{...listeners}
57+
{...attributes}
58+
/>
59+
</div>
60+
</FloatingThread>
61+
);
62+
}

0 commit comments

Comments
 (0)