@wakastellar/ui 3.3.3 → 3.5.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 (140) hide show
  1. package/dist/badge-BbwO7QeZ.js +1 -0
  2. package/dist/badge-BfiocODp.mjs +23 -0
  3. package/dist/charts.cjs.js +1 -1
  4. package/dist/charts.es.js +1 -1
  5. package/dist/chunk-14q5BKub.js +1 -0
  6. package/dist/{chunk-BH6uBOac.mjs → chunk-Cr9pTUWm.mjs} +5 -5
  7. package/dist/cn-DEtaFQsA.js +1 -0
  8. package/dist/cn-DUn6aSIQ.mjs +24 -0
  9. package/dist/doc.cjs.js +2 -2
  10. package/dist/doc.es.js +19 -19
  11. package/dist/editor.cjs.js +48 -0
  12. package/dist/editor.d.ts +1 -0
  13. package/dist/editor.es.js +6551 -0
  14. package/dist/{exceljs.min-DG9M8IZ1.mjs → exceljs.min-DL1XYDll.mjs} +1 -1
  15. package/dist/{exceljs.min-BuefmDRS.js → exceljs.min-qeIfSCbF.js} +1 -1
  16. package/dist/export.cjs.js +1 -1
  17. package/dist/export.es.js +1 -1
  18. package/dist/index.cjs.js +150 -150
  19. package/dist/index.es.js +26782 -27591
  20. package/dist/input-BfaSAGVw.js +1 -0
  21. package/dist/input-DVr_Qkl8.mjs +14 -0
  22. package/dist/rich-text.cjs.js +1 -1
  23. package/dist/rich-text.es.js +1 -1
  24. package/dist/security-CyBpuklN.mjs +122 -0
  25. package/dist/security-bFWwDrlg.js +1 -0
  26. package/dist/separator-NrkltulH.js +1 -0
  27. package/dist/separator-ibN2mycs.mjs +51 -0
  28. package/dist/src/components/editor/blocks/index.d.ts +51 -0
  29. package/dist/src/components/editor/blocks/waka-acceptance-criteria-block.d.ts +60 -0
  30. package/dist/src/components/editor/blocks/waka-ai-assist-block.d.ts +58 -0
  31. package/dist/src/components/editor/blocks/waka-api-endpoint-block.d.ts +63 -0
  32. package/dist/src/components/editor/blocks/waka-code-playground-block.d.ts +61 -0
  33. package/dist/src/components/editor/blocks/waka-comment-thread-block.d.ts +85 -0
  34. package/dist/src/components/editor/blocks/waka-diagram-block.d.ts +52 -0
  35. package/dist/src/components/editor/blocks/waka-embed-block.d.ts +58 -0
  36. package/dist/src/components/editor/blocks/waka-slash-menu-block.d.ts +67 -0
  37. package/dist/src/components/editor/blocks/waka-user-story-block.d.ts +79 -0
  38. package/dist/src/components/editor/blocks/waka-version-diff-block.d.ts +73 -0
  39. package/dist/src/components/editor/index.d.ts +66 -0
  40. package/dist/src/components/editor/waka-ai-writer.d.ts +80 -0
  41. package/dist/src/components/editor/waka-collaborative-editor.d.ts +93 -0
  42. package/dist/src/components/editor/waka-diff-viewer.d.ts +71 -0
  43. package/dist/src/components/editor/waka-dnd-editor.d.ts +64 -0
  44. package/dist/src/components/editor/waka-document-editor.d.ts +92 -0
  45. package/dist/src/components/editor/waka-editor-elements.d.ts +79 -0
  46. package/dist/src/components/editor/waka-editor-leaves.d.ts +39 -0
  47. package/dist/src/components/editor/waka-editor-plugins.d.ts +41 -0
  48. package/dist/src/components/editor/waka-editor-toolbar.d.ts +20 -0
  49. package/dist/src/components/editor/waka-editor.d.ts +59 -0
  50. package/dist/src/components/editor/waka-floating-toolbar.d.ts +47 -0
  51. package/dist/src/components/editor/waka-markdown-editor.d.ts +60 -0
  52. package/dist/src/components/editor/waka-mention-editor.d.ts +125 -0
  53. package/dist/src/components/editor/waka-slash-menu.d.ts +70 -0
  54. package/dist/src/components/editor/waka-spec-editor.d.ts +88 -0
  55. package/dist/src/components/index.d.ts +1 -15
  56. package/dist/src/editor.d.ts +26 -0
  57. package/dist/textarea-CdQWggYG.js +1 -0
  58. package/dist/textarea-DJDXJ3nd.mjs +23 -0
  59. package/dist/types-C2St0wOW.js +1 -0
  60. package/dist/{types-B6GVaSIP.mjs → types-JnqoLyuv.mjs} +214 -211
  61. package/dist/{useDataTableImport-BPvfo--2.mjs → useDataTableImport-BWUFesPi.mjs} +3 -3
  62. package/dist/{useDataTableImport-Cm_pCKnO.js → useDataTableImport-T7ddpN5k.js} +3 -3
  63. package/dist/waka-doc-renderer-CTxC7Trf.js +3 -0
  64. package/dist/{waka-doc-renderer-BkIvas3z.mjs → waka-doc-renderer-Cw-Xnyen.mjs} +264 -281
  65. package/dist/waka-editor-plugins-DR6tpsUC.mjs +135 -0
  66. package/dist/waka-editor-plugins-sGSh9hn2.js +1 -0
  67. package/dist/waka-rich-text-editor-BlIdtknG.js +1 -0
  68. package/dist/waka-rich-text-editor-D1uA3zbB.js +1 -0
  69. package/dist/waka-rich-text-editor-DgSWiXMW.mjs +342 -0
  70. package/dist/waka-rich-text-editor-DndVJuDw.mjs +2 -0
  71. package/package.json +87 -2
  72. package/src/blocks/footer/index.tsx +1 -6
  73. package/src/blocks/login/index.tsx +1 -7
  74. package/src/blocks/profile/index.tsx +3 -5
  75. package/src/components/editor/blocks/index.ts +182 -0
  76. package/src/components/editor/blocks/waka-acceptance-criteria-block.tsx +326 -0
  77. package/src/components/editor/blocks/waka-ai-assist-block.tsx +284 -0
  78. package/src/components/editor/blocks/waka-api-endpoint-block.tsx +382 -0
  79. package/src/components/editor/blocks/waka-code-playground-block.tsx +331 -0
  80. package/src/components/editor/blocks/waka-comment-thread-block.tsx +448 -0
  81. package/src/components/editor/blocks/waka-diagram-block.tsx +293 -0
  82. package/src/components/editor/blocks/waka-embed-block.tsx +416 -0
  83. package/src/components/editor/blocks/waka-slash-menu-block.tsx +432 -0
  84. package/src/components/editor/blocks/waka-user-story-block.tsx +295 -0
  85. package/src/components/editor/blocks/waka-version-diff-block.tsx +426 -0
  86. package/src/components/editor/index.ts +279 -0
  87. package/src/components/editor/waka-ai-writer.tsx +434 -0
  88. package/src/components/editor/waka-collaborative-editor.tsx +426 -0
  89. package/src/components/editor/waka-diff-viewer.tsx +352 -0
  90. package/src/components/editor/waka-dnd-editor.tsx +284 -0
  91. package/src/components/editor/waka-document-editor.tsx +502 -0
  92. package/src/components/editor/waka-editor-elements.tsx +312 -0
  93. package/src/components/editor/waka-editor-leaves.tsx +101 -0
  94. package/src/components/editor/waka-editor-plugins.ts +207 -0
  95. package/src/components/editor/waka-editor-toolbar.tsx +358 -0
  96. package/src/components/editor/waka-editor.tsx +431 -0
  97. package/src/components/editor/waka-floating-toolbar.tsx +268 -0
  98. package/src/components/editor/waka-markdown-editor.tsx +395 -0
  99. package/src/components/editor/waka-mention-editor.tsx +459 -0
  100. package/src/components/editor/waka-slash-menu.tsx +392 -0
  101. package/src/components/editor/waka-spec-editor.tsx +657 -0
  102. package/src/components/index.ts +1 -18
  103. package/dist/chunk-BDDJmn7V.js +0 -1
  104. package/dist/cn-DnPbmOCy.js +0 -1
  105. package/dist/cn-DpLcAzrf.mjs +0 -22
  106. package/dist/separator-BDReXBvI.mjs +0 -59
  107. package/dist/separator-BKjNl9sI.js +0 -1
  108. package/dist/src/components/waka-actor-badge/index.d.ts +0 -8
  109. package/dist/src/components/waka-actors-list/index.d.ts +0 -18
  110. package/dist/src/components/waka-ai-assistant-button/index.d.ts +0 -8
  111. package/dist/src/components/waka-document-flyover/index.d.ts +0 -10
  112. package/dist/src/components/waka-document-preview-popup/index.d.ts +0 -26
  113. package/dist/src/components/waka-hour-balance-badge/index.d.ts +0 -8
  114. package/dist/src/components/waka-hour-consumption-table/index.d.ts +0 -15
  115. package/dist/src/components/waka-hour-pack-dialog/index.d.ts +0 -8
  116. package/dist/src/components/waka-project-stats-header/index.d.ts +0 -15
  117. package/dist/src/components/waka-step-comment-bubble/index.d.ts +0 -13
  118. package/dist/src/components/waka-step-comment-panel/index.d.ts +0 -20
  119. package/dist/src/components/waka-step-permission-matrix/index.d.ts +0 -12
  120. package/dist/src/components/waka-time-entry-dialog/index.d.ts +0 -16
  121. package/dist/src/components/waka-time-tracking-flyover/index.d.ts +0 -11
  122. package/dist/types-BH9cQRqZ.js +0 -1
  123. package/dist/waka-doc-renderer-BZ2-SqyT.js +0 -3
  124. package/dist/waka-rich-text-editor-BJGlQgpq.js +0 -1
  125. package/dist/waka-rich-text-editor-BJzzxeP1.mjs +0 -361
  126. package/dist/waka-rich-text-editor-wnXLwvUo.js +0 -1
  127. package/src/components/waka-actor-badge/index.tsx +0 -34
  128. package/src/components/waka-actors-list/index.tsx +0 -125
  129. package/src/components/waka-ai-assistant-button/index.tsx +0 -31
  130. package/src/components/waka-document-flyover/index.tsx +0 -36
  131. package/src/components/waka-document-preview-popup/index.tsx +0 -103
  132. package/src/components/waka-hour-balance-badge/index.tsx +0 -43
  133. package/src/components/waka-hour-consumption-table/index.tsx +0 -72
  134. package/src/components/waka-hour-pack-dialog/index.tsx +0 -72
  135. package/src/components/waka-project-stats-header/index.tsx +0 -69
  136. package/src/components/waka-step-comment-bubble/index.tsx +0 -71
  137. package/src/components/waka-step-comment-panel/index.tsx +0 -106
  138. package/src/components/waka-step-permission-matrix/index.tsx +0 -65
  139. package/src/components/waka-time-entry-dialog/index.tsx +0 -131
  140. package/src/components/waka-time-tracking-flyover/index.tsx +0 -41
@@ -0,0 +1,426 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cn } from "../../utils/cn"
5
+ import { Label } from "../label"
6
+ import { Textarea } from "../textarea"
7
+ import { Badge } from "../badge"
8
+ import { Avatar, AvatarFallback } from "../avatar"
9
+
10
+ // ─── Types ───────────────────────────────────────────────────────────────────
11
+
12
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
13
+ type SlateNode = any
14
+
15
+ /** Describes a remote user connected to the same document */
16
+ export interface CollaborationUser {
17
+ /** Unique user ID (e.g., keycloak-id) */
18
+ id: string
19
+ /** Display name */
20
+ name: string
21
+ /** Cursor color (CSS color value) */
22
+ color: string
23
+ /** Optional avatar URL */
24
+ avatar?: string
25
+ }
26
+
27
+ /** Provider configuration for Yjs */
28
+ export interface CollaborationProviderConfig {
29
+ /** Provider type */
30
+ type: "hocuspocus" | "webrtc"
31
+ /** Server URL (WebSocket) */
32
+ url: string
33
+ /** Room/document name (must be unique per document) */
34
+ roomName: string
35
+ /** Additional options */
36
+ options?: Record<string, unknown>
37
+ }
38
+
39
+ export interface WakaCollaborativeEditorProps {
40
+ /** Initial Slate content (used only if the Y.Doc is empty) */
41
+ value?: SlateNode[]
42
+ /** Change callback */
43
+ onChange?: (value: SlateNode[]) => void
44
+ /** Read-only mode */
45
+ readOnly?: boolean
46
+ /** Placeholder */
47
+ placeholder?: string
48
+ /** CSS class */
49
+ className?: string
50
+ /** Editor CSS class */
51
+ editorClassName?: string
52
+ /** Minimum height in px */
53
+ minHeight?: number
54
+ /** Label */
55
+ label?: string
56
+ /** Description */
57
+ description?: string
58
+ /** Error */
59
+ error?: string
60
+ /** Unique document ID for the collaboration room */
61
+ documentId: string
62
+ /** Current user info for cursor display */
63
+ currentUser: CollaborationUser
64
+ /** Collaboration provider configurations */
65
+ providers: CollaborationProviderConfig[]
66
+ /**
67
+ * Maximum number of concurrent participants per room.
68
+ * Enforced at the WebRTC level. Default: 10.
69
+ */
70
+ maxParticipants?: number
71
+ /** Callback when remote users change. Receives the list of connected users. */
72
+ onUsersChange?: (users: CollaborationUser[]) => void
73
+ /** Show the connected users bar at the top */
74
+ showUsersBar?: boolean
75
+ /** Extra Plate plugins */
76
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
77
+ extraPlugins?: any[]
78
+ /** Ref to editor instance */
79
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
80
+ editorRef?: React.MutableRefObject<any>
81
+ }
82
+
83
+ // ─── Component ───────────────────────────────────────────────────────────────
84
+
85
+ /**
86
+ * WakaCollaborativeEditor -- Real-time collaborative editor using Yjs.
87
+ *
88
+ * Wraps Plate.js with `@platejs/yjs` for real-time multi-user editing.
89
+ * Supports Hocuspocus (WebSocket) and WebRTC providers.
90
+ * Displays remote cursors with user names and colors.
91
+ *
92
+ * Multi-tenant aware: the `documentId` scopes the collaboration room,
93
+ * and can include tenant prefixes (e.g., `partner-123/spec-456`).
94
+ *
95
+ * Requires peer dependencies: `@platejs/yjs`, `yjs`, `y-websocket` or provider libs.
96
+ *
97
+ * @example
98
+ * ```tsx
99
+ * <WakaCollaborativeEditor
100
+ * documentId={`spec-${specId}`}
101
+ * currentUser={{ id: user.keycloakId, name: user.name, color: "#3b82f6" }}
102
+ * providers={[
103
+ * { type: "hocuspocus", url: "wss://collab.wakastart.app", roomName: `spec-${specId}` }
104
+ * ]}
105
+ * showUsersBar
106
+ * value={initialContent}
107
+ * onChange={handleChange}
108
+ * />
109
+ * ```
110
+ */
111
+ export const WakaCollaborativeEditor = React.forwardRef<HTMLDivElement, WakaCollaborativeEditorProps>(
112
+ (
113
+ {
114
+ value,
115
+ onChange,
116
+ readOnly = false,
117
+ placeholder = "Editez ensemble en temps reel...",
118
+ className,
119
+ editorClassName,
120
+ minHeight = 400,
121
+ label,
122
+ description,
123
+ error,
124
+ documentId,
125
+ currentUser,
126
+ providers,
127
+ maxParticipants = 10,
128
+ onUsersChange,
129
+ showUsersBar = true,
130
+ extraPlugins,
131
+ editorRef,
132
+ },
133
+ ref
134
+ ) => {
135
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
136
+ const [bundle, setBundle] = React.useState<any>(null)
137
+ const [loadError, setLoadError] = React.useState(false)
138
+ const [connectedUsers, setConnectedUsers] = React.useState<CollaborationUser[]>([currentUser])
139
+
140
+ const configRef = React.useRef({ documentId, currentUser, providers, maxParticipants })
141
+ configRef.current = { documentId, currentUser, providers, maxParticipants }
142
+
143
+ // ── Load Plate + Yjs ──────────────────────────────────────────────────
144
+ React.useEffect(() => {
145
+ let cancelled = false
146
+
147
+ const load = async () => {
148
+ try {
149
+ const cfg = configRef.current
150
+
151
+ const [plateReact, basicNodes, tableModule, layoutModule, calloutModule, linkModule, yjsModule] =
152
+ await Promise.all([
153
+ import("platejs/react"),
154
+ import("@platejs/basic-nodes/react"),
155
+ import("@platejs/table/react"),
156
+ import("@platejs/layout/react"),
157
+ import("@platejs/callout/react"),
158
+ import("@platejs/link/react"),
159
+ import("@platejs/yjs/react"),
160
+ ])
161
+
162
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
163
+ const plugins: any[] = [
164
+ basicNodes.BoldPlugin, basicNodes.ItalicPlugin, basicNodes.UnderlinePlugin,
165
+ basicNodes.StrikethroughPlugin, basicNodes.CodePlugin, basicNodes.HighlightPlugin,
166
+ basicNodes.H1Plugin, basicNodes.H2Plugin, basicNodes.H3Plugin,
167
+ basicNodes.H4Plugin, basicNodes.BlockquotePlugin,
168
+ tableModule.TablePlugin, tableModule.TableRowPlugin,
169
+ tableModule.TableCellPlugin, tableModule.TableCellHeaderPlugin,
170
+ layoutModule.ColumnPlugin, layoutModule.ColumnItemPlugin,
171
+ calloutModule.CalloutPlugin,
172
+ linkModule.LinkPlugin.configure({
173
+ options: { allowedSchemes: ["http", "https", "mailto", "tel"] },
174
+ }),
175
+ ]
176
+
177
+ // Build Yjs provider configs
178
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
179
+ const yjsProviders: any[] = cfg.providers.map((p) => {
180
+ if (p.type === "hocuspocus") {
181
+ return {
182
+ type: "hocuspocus",
183
+ options: { name: p.roomName, url: p.url, ...p.options },
184
+ }
185
+ }
186
+ return {
187
+ type: "webrtc",
188
+ options: {
189
+ roomName: p.roomName,
190
+ maxConns: cfg.maxParticipants - 1,
191
+ signaling: [p.url],
192
+ ...p.options,
193
+ },
194
+ }
195
+ })
196
+
197
+ plugins.push(
198
+ yjsModule.YjsPlugin.configure({
199
+ options: {
200
+ cursors: {
201
+ data: {
202
+ name: cfg.currentUser.name,
203
+ color: cfg.currentUser.color,
204
+ },
205
+ },
206
+ providers: yjsProviders,
207
+ },
208
+ }),
209
+ )
210
+
211
+ if (!cancelled) {
212
+ setBundle({
213
+ Plate: plateReact.Plate,
214
+ PlateContent: plateReact.PlateContent,
215
+ usePlateEditor: plateReact.usePlateEditor,
216
+ YjsPlugin: yjsModule.YjsPlugin,
217
+ plugins,
218
+ })
219
+ }
220
+ } catch (err) {
221
+ console.error("[WakaCollaborativeEditor] Failed to load:", err)
222
+ if (!cancelled) setLoadError(true)
223
+ }
224
+ }
225
+
226
+ load()
227
+ return () => { cancelled = true }
228
+ }, [])
229
+
230
+ // Notify parent of user changes
231
+ React.useEffect(() => {
232
+ onUsersChange?.(connectedUsers)
233
+ }, [connectedUsers, onUsersChange])
234
+
235
+ // ── Render ────────────────────────────────────────────────────────────
236
+ if (bundle) {
237
+ return (
238
+ <CollabEditorInner
239
+ ref={ref}
240
+ bundle={bundle}
241
+ value={value}
242
+ onChange={onChange}
243
+ readOnly={readOnly}
244
+ placeholder={placeholder}
245
+ className={className}
246
+ editorClassName={editorClassName}
247
+ minHeight={minHeight}
248
+ label={label}
249
+ description={description}
250
+ error={error}
251
+ documentId={documentId}
252
+ currentUser={currentUser}
253
+ showUsersBar={showUsersBar}
254
+ connectedUsers={connectedUsers}
255
+ setConnectedUsers={setConnectedUsers}
256
+ extraPlugins={extraPlugins}
257
+ editorRef={editorRef}
258
+ />
259
+ )
260
+ }
261
+
262
+ return (
263
+ <div ref={ref} className={cn("space-y-1.5", className)}>
264
+ {label && <Label>{label}</Label>}
265
+ {description && <p className="text-sm text-muted-foreground">{description}</p>}
266
+ <div className="relative">
267
+ <Textarea placeholder={placeholder} disabled style={{ minHeight }} />
268
+ <div className="absolute inset-0 flex items-center justify-center bg-background/50 rounded-lg">
269
+ <span className="text-sm text-muted-foreground animate-pulse">
270
+ {loadError
271
+ ? "Erreur de chargement (@platejs/yjs requis)"
272
+ : "Connexion a la session collaborative..."}
273
+ </span>
274
+ </div>
275
+ </div>
276
+ {error && <p className="text-sm text-destructive">{error}</p>}
277
+ </div>
278
+ )
279
+ }
280
+ )
281
+
282
+ WakaCollaborativeEditor.displayName = "WakaCollaborativeEditor"
283
+
284
+ // ─── Inner component ────────────────────────────────────────────────────────
285
+
286
+ interface CollabEditorInnerProps {
287
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
288
+ bundle: any
289
+ value?: SlateNode[]
290
+ onChange?: (value: SlateNode[]) => void
291
+ readOnly?: boolean
292
+ placeholder?: string
293
+ className?: string
294
+ editorClassName?: string
295
+ minHeight?: number
296
+ label?: string
297
+ description?: string
298
+ error?: string
299
+ documentId: string
300
+ currentUser: CollaborationUser
301
+ showUsersBar?: boolean
302
+ connectedUsers: CollaborationUser[]
303
+ setConnectedUsers: React.Dispatch<React.SetStateAction<CollaborationUser[]>>
304
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
305
+ extraPlugins?: any[]
306
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
307
+ editorRef?: React.MutableRefObject<any>
308
+ }
309
+
310
+ const CollabEditorInner = React.forwardRef<HTMLDivElement, CollabEditorInnerProps>(
311
+ (
312
+ {
313
+ bundle: { Plate, PlateContent, usePlateEditor, YjsPlugin, plugins },
314
+ value,
315
+ onChange,
316
+ readOnly,
317
+ placeholder,
318
+ className,
319
+ editorClassName,
320
+ minHeight,
321
+ label,
322
+ description,
323
+ error,
324
+ documentId,
325
+ showUsersBar,
326
+ connectedUsers,
327
+ extraPlugins,
328
+ editorRef,
329
+ },
330
+ ref
331
+ ) => {
332
+ const allPlugins = React.useMemo(
333
+ () => [...plugins, ...(extraPlugins ?? [])],
334
+ [plugins, extraPlugins]
335
+ )
336
+
337
+ const editor = usePlateEditor(
338
+ {
339
+ plugins: allPlugins,
340
+ skipInitialization: true,
341
+ },
342
+ [documentId]
343
+ )
344
+
345
+ // Initialize Yjs document
346
+ React.useEffect(() => {
347
+ try {
348
+ editor.getApi(YjsPlugin).yjs.init({
349
+ id: documentId,
350
+ autoSelect: "end",
351
+ value: value ?? [{ type: "p", children: [{ text: "" }] }],
352
+ })
353
+ } catch {
354
+ // YjsPlugin might not be available
355
+ }
356
+
357
+ return () => {
358
+ try {
359
+ editor.getApi(YjsPlugin).yjs.destroy()
360
+ } catch { /* */ }
361
+ }
362
+ }, [editor, documentId, YjsPlugin, value])
363
+
364
+ React.useEffect(() => {
365
+ if (editorRef) editorRef.current = editor
366
+ }, [editor, editorRef])
367
+
368
+ return (
369
+ <div ref={ref} className={cn("space-y-1.5", className)}>
370
+ {label && <Label>{label}</Label>}
371
+ {description && <p className="text-sm text-muted-foreground">{description}</p>}
372
+
373
+ <Plate
374
+ editor={editor}
375
+ onChange={onChange ? ({ value: v }: { value: SlateNode[] }) => onChange(v) : undefined}
376
+ readOnly={readOnly}
377
+ >
378
+ <div className={cn(
379
+ "border rounded-lg overflow-hidden",
380
+ error && "border-destructive",
381
+ )}>
382
+ {/* Connected users bar */}
383
+ {showUsersBar && (
384
+ <div className="flex items-center gap-2 px-3 py-1.5 border-b bg-muted/30">
385
+ <span className="text-xs text-muted-foreground">En ligne :</span>
386
+ <div className="flex items-center -space-x-1.5">
387
+ {connectedUsers.map((user) => (
388
+ <Avatar key={user.id} className="h-6 w-6 border-2 border-background">
389
+ <AvatarFallback
390
+ className="text-[10px] text-white font-bold"
391
+ style={{ backgroundColor: user.color }}
392
+ >
393
+ {user.name.slice(0, 2).toUpperCase()}
394
+ </AvatarFallback>
395
+ </Avatar>
396
+ ))}
397
+ </div>
398
+ <Badge variant="secondary" className="text-[10px] h-5">
399
+ {connectedUsers.length} connecte{connectedUsers.length > 1 ? "s" : ""}
400
+ </Badge>
401
+ </div>
402
+ )}
403
+
404
+ {/* Editor content */}
405
+ <div style={{ minHeight }}>
406
+ <PlateContent
407
+ placeholder={placeholder}
408
+ readOnly={readOnly}
409
+ className={cn(
410
+ "prose prose-sm max-w-none p-4",
411
+ "focus-within:outline-none",
412
+ "[&_[data-slate-editor]]:outline-none [&_[data-slate-editor]]:min-h-[inherit]",
413
+ editorClassName,
414
+ )}
415
+ />
416
+ </div>
417
+ </div>
418
+ </Plate>
419
+
420
+ {error && <p className="text-sm text-destructive">{error}</p>}
421
+ </div>
422
+ )
423
+ }
424
+ )
425
+
426
+ CollabEditorInner.displayName = "CollabEditorInner"