@sqlrooms/discuss 0.27.0 → 0.28.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.
Files changed (2) hide show
  1. package/README.md +71 -304
  2. package/package.json +5 -5
package/README.md CHANGED
@@ -1,74 +1,17 @@
1
- A simple discussion system for SQLRooms applications. Can be used for commenting and annotation with support for threaded conversations, real-time updates, and anchor-based discussions.
1
+ Threaded discussion slice and UI components for SQLRooms.
2
2
 
3
- ## Overview
3
+ Use this package to add comments/replies linked to data points or UI anchors.
4
4
 
5
- The `@sqlrooms/discuss` module provides a complete discussion system with the following key features:
5
+ ## Installation
6
6
 
7
- - **Threaded conversations**: Support for replies to discussions and comments
8
- - **Anchor-based discussions**: Link discussions to specific data points or UI elements
9
- - **Real-time state management**: Built on Zustand for reactive updates
10
- - **Customizable rendering**: Flexible component system for custom UI implementations
11
- - **Delete confirmation**: Built-in confirmation dialogs for safe content removal
12
- - **Highlighting**: Visual highlighting of specific discussions
13
-
14
- ## Main Components
15
-
16
- ### Core Components
17
-
18
- #### `DiscussionList`
19
-
20
- The main container component that renders all discussions with built-in forms for adding, editing, and replying to content.
21
-
22
- ```tsx
23
- import {DiscussionList} from '@sqlrooms/discuss';
24
-
25
- <DiscussionList
26
- className="flex flex-col gap-4"
27
- renderComment={(props) => <CustomCommentRenderer {...props} />}
28
- renderDiscussion={(props) => <CustomDiscussionRenderer {...props} />}
29
- />;
30
- ```
31
-
32
- #### `CommentItem`
33
-
34
- Individual comment renderer with built-in edit/delete actions.
35
-
36
- ```tsx
37
- import {CommentItem} from '@sqlrooms/discuss';
38
-
39
- <CommentItem comment={comment} discussion={discussion}>
40
- <div className="flex flex-col gap-1">
41
- <div className="text-muted-foreground text-xs">
42
- {comment.userId} - {formatTimeRelative(comment.timestamp)}
43
- </div>
44
- <div className="whitespace-pre-wrap text-sm">{comment.text}</div>
45
- </div>
46
- </CommentItem>;
7
+ ```bash
8
+ npm install @sqlrooms/discuss @sqlrooms/room-shell @sqlrooms/ui @sqlrooms/utils
47
9
  ```
48
10
 
49
- #### `DiscussionItem`
50
-
51
- Container for a complete discussion thread including root comment and replies.
52
-
53
- ```tsx
54
- import {DiscussionItem} from '@sqlrooms/discuss';
55
-
56
- <DiscussionItem
57
- discussion={discussion}
58
- renderComment={customCommentRenderer}
59
- className="rounded border p-4"
60
- />;
61
- ```
62
-
63
- ### Store Integration
64
-
65
- #### Store Setup with `createDiscussSlice`
66
-
67
- To use the discussion system, you need to integrate it with your room store using `createDiscussSlice`:
11
+ ## Store setup
68
12
 
69
13
  ```tsx
70
14
  import {
71
- createDefaultDiscussConfig,
72
15
  createDiscussSlice,
73
16
  DiscussSliceConfig,
74
17
  DiscussSliceState,
@@ -77,284 +20,108 @@ import {
77
20
  BaseRoomConfig,
78
21
  createRoomShellSlice,
79
22
  createRoomStore,
23
+ LayoutConfig,
80
24
  RoomShellSliceState,
25
+ persistSliceConfigs,
81
26
  } from '@sqlrooms/room-shell';
82
- import {z} from 'zod';
83
-
84
- // 1. Extend your app config with DiscussSliceConfig
85
- export const RoomConfig = BaseRoomConfig.merge(DiscussSliceConfig);
86
- export type RoomConfig = z.infer<typeof RoomConfig>;
87
27
 
88
- // 2. Extend your app state with DiscussSliceState
89
- export type RoomState = RoomShellSliceState<RoomConfig> & DiscussSliceState;
28
+ type RoomState = RoomShellSliceState & DiscussSliceState;
90
29
 
91
- // 3. Create the store with discuss slice
92
- export const {roomStore, useRoomStore} = createRoomStore<RoomConfig, RoomState>(
93
- (set, get, store) => ({
94
- // Add the discuss slice with a user ID
95
- ...createDiscussSlice({userId: 'current-user-id'})(set, get, store),
96
-
97
- // Add your room shell slice
98
- ...createRoomShellSlice<RoomConfig>({
99
- connector: yourDatabaseConnector,
100
- config: {
101
- // Include default discuss config
102
- ...createDefaultDiscussConfig(),
103
- // Your other config...
104
- layout: {
105
- /* your layout */
106
- },
107
- dataSources: [
108
- /* your data sources */
109
- ],
110
- },
111
- room: {
112
- // Your room configuration
30
+ export const {roomStore, useRoomStore} = createRoomStore<RoomState>(
31
+ persistSliceConfigs(
32
+ {
33
+ name: 'discuss-demo-storage',
34
+ sliceConfigSchemas: {
35
+ room: BaseRoomConfig,
36
+ layout: LayoutConfig,
37
+ discuss: DiscussSliceConfig,
113
38
  },
114
- })(set, get, store),
115
- }),
39
+ },
40
+ (set, get, store) => ({
41
+ ...createRoomShellSlice({
42
+ config: {title: 'Discuss Demo', dataSources: []},
43
+ })(set, get, store),
44
+ ...createDiscussSlice({userId: 'user-1'})(set, get, store),
45
+ }),
46
+ ),
116
47
  );
117
48
  ```
118
49
 
119
- #### Using the Store Hook
50
+ ## Initialization
120
51
 
121
- Access discussion state and actions using the provided hook:
52
+ Call `room.initialize()` after store creation so slice lifecycle hooks run:
122
53
 
123
54
  ```tsx
124
- import {useStoreWithDiscussion} from '@sqlrooms/discuss';
55
+ import {useEffect} from 'react';
56
+ import {roomStore} from './store';
125
57
 
126
- function MyComponent() {
127
- // Get discussions
128
- const discussions = useStoreWithDiscussion(
129
- (state) => state.discuss.config.discussions,
130
- );
58
+ function App() {
59
+ useEffect(() => {
60
+ void roomStore.getState().room.initialize();
61
+ }, []);
131
62
 
132
- // Get actions
133
- const addDiscussion = useStoreWithDiscussion(
134
- (state) => state.discuss.addDiscussion,
135
- );
136
- const setReplyToItem = useStoreWithDiscussion(
137
- (state) => state.discuss.setReplyToItem,
138
- );
139
-
140
- // Add a new discussion
141
- const handleAddDiscussion = (text: string, anchorId?: string) => {
142
- addDiscussion(text, anchorId);
143
- };
144
-
145
- // Start replying to a discussion
146
- const handleReply = (discussionId: string) => {
147
- setReplyToItem({discussionId});
148
- };
63
+ return null;
149
64
  }
150
65
  ```
151
66
 
152
- ## Usage Examples
153
-
154
- ### Basic Discussion Panel
67
+ ## Render discussions
155
68
 
156
69
  ```tsx
157
- import {DiscussionList, CommentItem} from '@sqlrooms/discuss';
158
- import {useStoreWithDiscussion} from '@sqlrooms/discuss';
70
+ import {CommentItem, DiscussionList} from '@sqlrooms/discuss';
159
71
  import {formatTimeRelative} from '@sqlrooms/utils';
72
+ import {useRoomStore} from './store';
160
73
 
161
- const DiscussionPanel = () => {
162
- const discussions = useStoreWithDiscussion(
163
- (state) => state.discuss.config.discussions,
164
- );
74
+ export function DiscussionPanel() {
75
+ const discussions = useRoomStore((state) => state.discuss.config.discussions);
165
76
 
166
- return (
167
- <div className="h-full">
168
- {discussions.length === 0 ? (
169
- <div className="py-10 text-center text-gray-400">
170
- <p>No comments yet. Start a discussion!</p>
171
- </div>
172
- ) : (
173
- <DiscussionList
174
- className="flex flex-col gap-4"
175
- renderComment={(props) => {
176
- const {comment, discussion} = props;
177
- const {anchorId} = discussion;
178
- const isRootComment = comment.id === discussion.rootComment.id;
77
+ if (discussions.length === 0) {
78
+ return <div className="p-4 text-sm text-muted-foreground">No discussions yet.</div>;
79
+ }
179
80
 
180
- return (
181
- <CommentItem {...props}>
182
- <div className="flex flex-col gap-1">
183
- {anchorId && isRootComment && (
184
- <div className="text-md flex items-center gap-2 font-medium">
185
- 📍 Linked to: {anchorId}
186
- </div>
187
- )}
188
- <div className="text-muted-foreground text-xs">
189
- {comment.userId} - {formatTimeRelative(comment.timestamp)}
190
- </div>
191
- <div className="whitespace-pre-wrap text-sm">
192
- {comment.text}
193
- </div>
194
- </div>
195
- </CommentItem>
196
- );
197
- }}
198
- />
199
- )}
200
- </div>
201
- );
202
- };
203
- ```
204
-
205
- ### Anchor-Based Discussions
206
-
207
- Link discussions to specific data points or UI elements:
208
-
209
- ```tsx
210
- import {useStoreWithDiscussion} from '@sqlrooms/discuss';
211
-
212
- function DataVisualization() {
213
- const addDiscussion = useStoreWithDiscussion(
214
- (state) => state.discuss.addDiscussion,
215
- );
216
- const setHighlightedDiscussionId = useStoreWithDiscussion(
217
- (state) => state.discuss.setHighlightedDiscussionId,
218
- );
219
-
220
- // Handle clicking on a data point
221
- const handleDataPointClick = (dataId: string) => {
222
- // Add a discussion linked to this data point
223
- addDiscussion('What do you think about this data point?', dataId);
224
- };
225
-
226
- // Highlight related discussion when hovering over data
227
- const handleDataPointHover = (dataId: string) => {
228
- const discussions =
229
- useStoreWithDiscussion.getState().discuss.config.discussions;
230
- const relatedDiscussion = discussions.find((d) => d.anchorId === dataId);
231
- if (relatedDiscussion) {
232
- setHighlightedDiscussionId(relatedDiscussion.id);
233
- }
234
- };
235
-
236
- return (
237
- <div>
238
- {/* Your data visualization with clickable elements */}
239
- <div
240
- onClick={() => handleDataPointClick('airport-LAX')}
241
- onMouseEnter={() => handleDataPointHover('airport-LAX')}
242
- className="cursor-pointer hover:bg-blue-100"
243
- >
244
- LAX Airport
245
- </div>
246
- </div>
247
- );
248
- }
249
- ```
250
-
251
- ### Custom Comment Rendering
252
-
253
- ```tsx
254
- import {DiscussionList, CommentItem} from '@sqlrooms/discuss';
255
- import {Avatar, Badge} from '@sqlrooms/ui';
256
-
257
- const CustomDiscussionPanel = () => {
258
81
  return (
259
82
  <DiscussionList
260
- renderComment={(props) => {
261
- const {comment, discussion} = props;
262
-
263
- return (
264
- <CommentItem {...props}>
265
- <div className="flex gap-3">
266
- <Avatar className="h-8 w-8">
267
- {comment.userId.charAt(0).toUpperCase()}
268
- </Avatar>
269
- <div className="flex-1">
270
- <div className="mb-1 flex items-center gap-2">
271
- <span className="font-medium">{comment.userId}</span>
272
- <Badge variant="secondary" className="text-xs">
273
- {formatTimeRelative(comment.timestamp)}
274
- </Badge>
275
- </div>
276
- <div className="text-sm leading-relaxed">{comment.text}</div>
277
- </div>
278
- </div>
279
- </CommentItem>
280
- );
281
- }}
83
+ className="flex flex-col gap-4"
84
+ renderComment={(props) => (
85
+ <CommentItem {...props}>
86
+ <div className="text-xs text-muted-foreground">
87
+ {props.comment.userId} {formatTimeRelative(props.comment.timestamp)}
88
+ </div>
89
+ <div className="whitespace-pre-wrap text-sm">{props.comment.text}</div>
90
+ </CommentItem>
91
+ )}
282
92
  />
283
93
  );
284
- };
94
+ }
285
95
  ```
286
96
 
287
- ### Programmatic Discussion Management
97
+ ## Actions
288
98
 
289
99
  ```tsx
290
- import {useStoreWithDiscussion} from '@sqlrooms/discuss';
291
-
292
- function DiscussionManager() {
293
- const {
294
- addDiscussion,
295
- removeDiscussion,
296
- addComment,
297
- setReplyToItem,
298
- setEditingItem,
299
- submitEdit,
300
- } = useStoreWithDiscussion((state) => state.discuss);
301
-
302
- // Add a new discussion
303
- const createDiscussion = () => {
304
- addDiscussion('New discussion topic', 'optional-anchor-id');
305
- };
100
+ import {useRoomStore} from './store';
101
+ import {Button} from '@sqlrooms/ui';
306
102
 
307
- // Reply to a discussion
308
- const replyToDiscussion = (discussionId: string) => {
309
- setReplyToItem({discussionId});
310
- // User can now type in the form and call submitEdit
311
- };
312
-
313
- // Edit a comment
314
- const editComment = (discussionId: string, commentId: string) => {
315
- setEditingItem({discussionId, commentId});
316
- // User can now edit in the form and call submitEdit
317
- };
318
-
319
- // Direct comment addition (bypassing UI state)
320
- const addDirectComment = (discussionId: string, text: string) => {
321
- addComment(discussionId, text);
322
- };
103
+ function DiscussionActions() {
104
+ const addDiscussion = useRoomStore((state) => state.discuss.addDiscussion);
105
+ const setReplyToItem = useRoomStore((state) => state.discuss.setReplyToItem);
106
+ const submitEdit = useRoomStore((state) => state.discuss.submitEdit);
323
107
 
324
108
  return (
325
109
  <div className="flex gap-2">
326
- <button onClick={createDiscussion}>New Discussion</button>
327
- <button onClick={() => replyToDiscussion('discussion-id')}>Reply</button>
328
- <button onClick={() => editComment('discussion-id', 'comment-id')}>
329
- Edit
330
- </button>
110
+ <Button onClick={() => addDiscussion('Initial note', 'data-point-id')}>
111
+ Add discussion
112
+ </Button>
113
+ <Button onClick={() => setReplyToItem({discussionId: 'some-id'})}>
114
+ Reply mode
115
+ </Button>
116
+ <Button onClick={() => submitEdit('Reply text')}>Submit</Button>
331
117
  </div>
332
118
  );
333
119
  }
334
120
  ```
335
121
 
336
- ## API Reference
337
-
338
- ### Types
339
-
340
- - `Comment`: Individual comment with id, userId, text, timestamp, and optional parentId
341
- - `Discussion`: Container with id, optional anchorId, rootComment, and array of reply comments
342
- - `DiscussSliceConfig`: Configuration type for the discuss slice
343
- - `DiscussSliceState`: State type including all discussion actions and UI state
344
-
345
- ### Key Functions
346
-
347
- - `createDiscussSlice({ userId })`: Creates the discussion slice for your store
348
- - `createDefaultDiscussConfig()`: Returns default configuration for discussions
349
- - `useStoreWithDiscussion(selector)`: Hook to access discussion state and actions
350
-
351
- ### Main Actions
352
-
353
- - `submitEdit(text)`: Submit based on current UI state (add/reply/edit)
354
- - `addDiscussion(text, anchorId?)`: Add new discussion
355
- - `addComment(discussionId, text, parentId?)`: Add reply to discussion
356
- - `setReplyToItem(item)`: Set reply context for UI
357
- - `setEditingItem(item)`: Set editing context for UI
358
- - `setHighlightedDiscussionId(id)`: Highlight specific discussion
122
+ ## Main exports
359
123
 
360
- This module integrates seamlessly with the SQLRooms ecosystem and provides a complete foundation for building collaborative discussion features in your applications.
124
+ - components: `DiscussionList`, `DiscussionItem`, `CommentItem`, `DeleteConfirmDialog`
125
+ - slice: `createDiscussSlice`, `createDefaultDiscussConfig`
126
+ - config/types: `DiscussSliceConfig`, `DiscussSliceState`, `Discussion`, `Comment`
127
+ - hook: `useStoreWithDiscussion`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sqlrooms/discuss",
3
- "version": "0.27.0",
3
+ "version": "0.28.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "main": "dist/index.js",
@@ -16,9 +16,9 @@
16
16
  },
17
17
  "dependencies": {
18
18
  "@paralleldrive/cuid2": "^3.0.0",
19
- "@sqlrooms/room-shell": "0.27.0",
20
- "@sqlrooms/ui": "0.27.0",
21
- "@sqlrooms/utils": "0.27.0",
19
+ "@sqlrooms/room-shell": "0.28.0",
20
+ "@sqlrooms/ui": "0.28.0",
21
+ "@sqlrooms/utils": "0.28.0",
22
22
  "immer": "^11.0.1",
23
23
  "lucide-react": "^0.556.0",
24
24
  "zod": "^4.1.8"
@@ -33,5 +33,5 @@
33
33
  "lint": "eslint .",
34
34
  "typedoc": "typedoc"
35
35
  },
36
- "gitHead": "f215995ab4adeac4c58171739261a15cbba9e82b"
36
+ "gitHead": "dcac54f8adf77240e293c93d224a0ce9fd8142a9"
37
37
  }