@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,459 @@
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 { Avatar, AvatarFallback, AvatarImage } from "../avatar"
8
+
9
+ // ─── Types ───────────────────────────────────────────────────────────────────
10
+
11
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
12
+ type SlateNode = any
13
+
14
+ /** A mentionable user entity */
15
+ export interface MentionableUser {
16
+ /** Unique ID (e.g., keycloak-id or user wid) */
17
+ id: string
18
+ /** Display name */
19
+ name: string
20
+ /** Email address */
21
+ email?: string
22
+ /** Avatar URL */
23
+ avatar?: string
24
+ /** Role or title (shown in the suggestion list) */
25
+ role?: string
26
+ /** Admin level (for display badge) */
27
+ adminLevel?: string
28
+ }
29
+
30
+ /** Describes a mention that was inserted into the document */
31
+ export interface InsertedMention {
32
+ /** The user that was mentioned */
33
+ user: MentionableUser
34
+ /** The Slate path where the mention was inserted */
35
+ path?: number[]
36
+ }
37
+
38
+ export interface WakaMentionEditorProps {
39
+ /** Initial Slate content */
40
+ value?: SlateNode[]
41
+ /** Change callback */
42
+ onChange?: (value: SlateNode[]) => void
43
+ /** Read-only mode */
44
+ readOnly?: boolean
45
+ /** Placeholder */
46
+ placeholder?: string
47
+ /** CSS class */
48
+ className?: string
49
+ /** Editor CSS class */
50
+ editorClassName?: string
51
+ /** Minimum height in px */
52
+ minHeight?: number
53
+ /** Label */
54
+ label?: string
55
+ /** Description */
56
+ description?: string
57
+ /** Error */
58
+ error?: string
59
+ /**
60
+ * Async search function that resolves mentionable users.
61
+ * Called when the user types after "@".
62
+ * Should return matching users from the WakaStart API.
63
+ */
64
+ onSearch: (query: string) => Promise<MentionableUser[]>
65
+ /**
66
+ * Callback when a mention is inserted.
67
+ * Can be used to trigger notifications.
68
+ */
69
+ onMentionInserted?: (mention: InsertedMention) => void
70
+ /**
71
+ * Trigger character for mentions. Default: "@"
72
+ */
73
+ trigger?: string
74
+ /**
75
+ * Debounce delay for the search function in ms.
76
+ * Default: 300
77
+ */
78
+ searchDebounceMs?: number
79
+ /**
80
+ * Maximum number of search results to show.
81
+ * Default: 8
82
+ */
83
+ maxResults?: number
84
+ /** Extra Plate plugins */
85
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
86
+ extraPlugins?: any[]
87
+ /** Ref to editor instance */
88
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
89
+ editorRef?: React.MutableRefObject<any>
90
+ }
91
+
92
+ // ─── Component ───────────────────────────────────────────────────────────────
93
+
94
+ /**
95
+ * WakaMentionEditor -- Rich text editor with @mention support.
96
+ *
97
+ * Integrates `@platejs/mention` to provide user mention functionality.
98
+ * When the user types "@", a dropdown with matching users appears.
99
+ * The search function is async, allowing API-backed user resolution
100
+ * scoped to the current tenant (Partner > Network > Customer).
101
+ *
102
+ * When a mention is inserted, the `onMentionInserted` callback fires,
103
+ * allowing the app to trigger notifications via ws-back-wakastart.
104
+ *
105
+ * The mention element renders as an inline pill with the user's
106
+ * avatar and name, styled with design system tokens.
107
+ *
108
+ * Useful for:
109
+ * - Spec review comments with @author mentions
110
+ * - Task assignment in WakaStart projects
111
+ * - Chat/collaboration features
112
+ * - Audit notes with @reviewer references
113
+ *
114
+ * @example
115
+ * ```tsx
116
+ * <WakaMentionEditor
117
+ * value={content}
118
+ * onChange={setContent}
119
+ * onSearch={async (q) => {
120
+ * const users = await usersApi.search(q);
121
+ * return users.map(u => ({ id: u.wid, name: u.fullName, email: u.email }));
122
+ * }}
123
+ * onMentionInserted={(m) => {
124
+ * notificationsApi.send(m.user.id, "You were mentioned in a document");
125
+ * }}
126
+ * />
127
+ * ```
128
+ */
129
+ export const WakaMentionEditor = React.forwardRef<HTMLDivElement, WakaMentionEditorProps>(
130
+ (
131
+ {
132
+ value,
133
+ onChange,
134
+ readOnly = false,
135
+ placeholder = "Tapez @ pour mentionner un utilisateur...",
136
+ className,
137
+ editorClassName,
138
+ minHeight = 200,
139
+ label,
140
+ description,
141
+ error,
142
+ onSearch,
143
+ onMentionInserted,
144
+ trigger = "@",
145
+ searchDebounceMs = 300,
146
+ maxResults = 8,
147
+ extraPlugins,
148
+ editorRef,
149
+ },
150
+ ref
151
+ ) => {
152
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
153
+ const [bundle, setBundle] = React.useState<any>(null)
154
+ const [loadError, setLoadError] = React.useState(false)
155
+ const [suggestions, setSuggestions] = React.useState<MentionableUser[]>([])
156
+ const [searching, setSearching] = React.useState(false)
157
+ const searchTimerRef = React.useRef<ReturnType<typeof setTimeout>>()
158
+
159
+ const configRef = React.useRef({ onSearch, trigger, searchDebounceMs, maxResults, onMentionInserted })
160
+ configRef.current = { onSearch, trigger, searchDebounceMs, maxResults, onMentionInserted }
161
+
162
+ // ── Load Plate + Mention ──────────────────────────────────────────────
163
+ React.useEffect(() => {
164
+ let cancelled = false
165
+
166
+ const load = async () => {
167
+ try {
168
+ const cfg = configRef.current
169
+
170
+ const [plateReact, basicNodes, tableModule, layoutModule, calloutModule, linkModule, mentionModule] =
171
+ await Promise.all([
172
+ import("platejs/react"),
173
+ import("@platejs/basic-nodes/react"),
174
+ import("@platejs/table/react"),
175
+ import("@platejs/layout/react"),
176
+ import("@platejs/callout/react"),
177
+ import("@platejs/link/react"),
178
+ import("@platejs/mention/react"),
179
+ ])
180
+
181
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
182
+ const plugins: any[] = [
183
+ basicNodes.BoldPlugin, basicNodes.ItalicPlugin, basicNodes.UnderlinePlugin,
184
+ basicNodes.StrikethroughPlugin, basicNodes.CodePlugin, basicNodes.HighlightPlugin,
185
+ basicNodes.H1Plugin, basicNodes.H2Plugin, basicNodes.H3Plugin,
186
+ basicNodes.H4Plugin, basicNodes.BlockquotePlugin,
187
+ tableModule.TablePlugin, tableModule.TableRowPlugin,
188
+ tableModule.TableCellPlugin, tableModule.TableCellHeaderPlugin,
189
+ layoutModule.ColumnPlugin, layoutModule.ColumnItemPlugin,
190
+ calloutModule.CalloutPlugin,
191
+ linkModule.LinkPlugin.configure({
192
+ options: { allowedSchemes: ["http", "https", "mailto", "tel"] },
193
+ }),
194
+ // Mention plugin
195
+ mentionModule.MentionPlugin.configure({
196
+ options: {
197
+ trigger: cfg.trigger,
198
+ triggerPreviousCharPattern: /^$|^[\s"']$/,
199
+ insertSpaceAfterMention: true,
200
+ },
201
+ }),
202
+ mentionModule.MentionInputPlugin,
203
+ ]
204
+
205
+ if (!cancelled) {
206
+ setBundle({
207
+ Plate: plateReact.Plate,
208
+ PlateContent: plateReact.PlateContent,
209
+ usePlateEditor: plateReact.usePlateEditor,
210
+ plugins,
211
+ })
212
+ }
213
+ } catch (err) {
214
+ console.error("[WakaMentionEditor] Failed to load:", err)
215
+ if (!cancelled) setLoadError(true)
216
+ }
217
+ }
218
+
219
+ load()
220
+ return () => { cancelled = true }
221
+ }, [])
222
+
223
+ // ── Search handler ────────────────────────────────────────────────────
224
+ const handleSearch = React.useCallback(
225
+ (query: string) => {
226
+ if (searchTimerRef.current) clearTimeout(searchTimerRef.current)
227
+
228
+ if (!query.trim()) {
229
+ setSuggestions([])
230
+ setSearching(false)
231
+ return
232
+ }
233
+
234
+ setSearching(true)
235
+ searchTimerRef.current = setTimeout(async () => {
236
+ try {
237
+ const cfg = configRef.current
238
+ const results = await cfg.onSearch(query)
239
+ setSuggestions(results.slice(0, cfg.maxResults))
240
+ } catch {
241
+ setSuggestions([])
242
+ } finally {
243
+ setSearching(false)
244
+ }
245
+ }, configRef.current.searchDebounceMs)
246
+ },
247
+ []
248
+ )
249
+
250
+ // ── Render ────────────────────────────────────────────────────────────
251
+ if (bundle) {
252
+ return (
253
+ <MentionEditorInner
254
+ ref={ref}
255
+ bundle={bundle}
256
+ value={value}
257
+ onChange={onChange}
258
+ readOnly={readOnly}
259
+ placeholder={placeholder}
260
+ className={className}
261
+ editorClassName={editorClassName}
262
+ minHeight={minHeight}
263
+ label={label}
264
+ description={description}
265
+ error={error}
266
+ suggestions={suggestions}
267
+ searching={searching}
268
+ onSearch={handleSearch}
269
+ onMentionInserted={onMentionInserted}
270
+ extraPlugins={extraPlugins}
271
+ editorRef={editorRef}
272
+ />
273
+ )
274
+ }
275
+
276
+ return (
277
+ <div ref={ref} className={cn("space-y-1.5", className)}>
278
+ {label && <Label>{label}</Label>}
279
+ {description && <p className="text-sm text-muted-foreground">{description}</p>}
280
+ <div className="relative">
281
+ <Textarea placeholder={placeholder} disabled style={{ minHeight }} />
282
+ <div className="absolute inset-0 flex items-center justify-center bg-background/50 rounded-lg">
283
+ <span className="text-sm text-muted-foreground animate-pulse">
284
+ {loadError ? "Erreur (@platejs/mention requis)" : "Chargement..."}
285
+ </span>
286
+ </div>
287
+ </div>
288
+ {error && <p className="text-sm text-destructive">{error}</p>}
289
+ </div>
290
+ )
291
+ }
292
+ )
293
+
294
+ WakaMentionEditor.displayName = "WakaMentionEditor"
295
+
296
+ // ─── Inner component ────────────────────────────────────────────────────────
297
+
298
+ interface MentionEditorInnerProps {
299
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
300
+ bundle: any
301
+ value?: SlateNode[]
302
+ onChange?: (value: SlateNode[]) => void
303
+ readOnly?: boolean
304
+ placeholder?: string
305
+ className?: string
306
+ editorClassName?: string
307
+ minHeight?: number
308
+ label?: string
309
+ description?: string
310
+ error?: string
311
+ suggestions: MentionableUser[]
312
+ searching: boolean
313
+ onSearch: (query: string) => void
314
+ onMentionInserted?: (mention: InsertedMention) => void
315
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
316
+ extraPlugins?: any[]
317
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
318
+ editorRef?: React.MutableRefObject<any>
319
+ }
320
+
321
+ const MentionEditorInner = React.forwardRef<HTMLDivElement, MentionEditorInnerProps>(
322
+ (
323
+ {
324
+ bundle: { Plate, PlateContent, usePlateEditor, plugins },
325
+ value,
326
+ onChange,
327
+ readOnly,
328
+ placeholder,
329
+ className,
330
+ editorClassName,
331
+ minHeight,
332
+ label,
333
+ description,
334
+ error,
335
+ suggestions,
336
+ searching,
337
+ extraPlugins,
338
+ editorRef,
339
+ },
340
+ ref
341
+ ) => {
342
+ const allPlugins = React.useMemo(
343
+ () => [...plugins, ...(extraPlugins ?? [])],
344
+ [plugins, extraPlugins]
345
+ )
346
+
347
+ const editor = usePlateEditor({
348
+ plugins: allPlugins,
349
+ value: value ?? [{ type: "p", children: [{ text: "" }] }],
350
+ })
351
+
352
+ React.useEffect(() => {
353
+ if (editorRef) editorRef.current = editor
354
+ }, [editor, editorRef])
355
+
356
+ return (
357
+ <div ref={ref} className={cn("space-y-1.5", className)}>
358
+ {label && <Label>{label}</Label>}
359
+ {description && <p className="text-sm text-muted-foreground">{description}</p>}
360
+
361
+ <Plate
362
+ editor={editor}
363
+ onChange={onChange ? ({ value: v }: { value: SlateNode[] }) => onChange(v) : undefined}
364
+ readOnly={readOnly}
365
+ >
366
+ <div className={cn(
367
+ "border rounded-lg overflow-hidden relative",
368
+ error && "border-destructive",
369
+ )}>
370
+ <div style={{ minHeight }}>
371
+ <PlateContent
372
+ placeholder={placeholder}
373
+ readOnly={readOnly}
374
+ className={cn(
375
+ "prose prose-sm max-w-none p-4",
376
+ "focus-within:outline-none",
377
+ "[&_[data-slate-editor]]:outline-none [&_[data-slate-editor]]:min-h-[inherit]",
378
+ editorClassName,
379
+ )}
380
+ />
381
+ </div>
382
+
383
+ {/* Mention suggestions dropdown (rendered as overlay) */}
384
+ {suggestions.length > 0 && (
385
+ <MentionSuggestions suggestions={suggestions} searching={searching} />
386
+ )}
387
+ </div>
388
+ </Plate>
389
+
390
+ {error && <p className="text-sm text-destructive">{error}</p>}
391
+ </div>
392
+ )
393
+ }
394
+ )
395
+
396
+ MentionEditorInner.displayName = "MentionEditorInner"
397
+
398
+ // ─── Mention suggestions component ──────────────────────────────────────────
399
+
400
+ /**
401
+ * Standalone mention suggestion list component.
402
+ * Can be used outside of WakaMentionEditor for custom integrations.
403
+ */
404
+ export function MentionSuggestions({
405
+ suggestions,
406
+ searching,
407
+ className,
408
+ onSelect,
409
+ }: {
410
+ suggestions: MentionableUser[]
411
+ searching?: boolean
412
+ className?: string
413
+ onSelect?: (user: MentionableUser) => void
414
+ }) {
415
+ return (
416
+ <div className={cn(
417
+ "absolute z-50 w-64 max-h-60 overflow-y-auto",
418
+ "rounded-lg border bg-popover shadow-lg",
419
+ "p-1 mt-1",
420
+ className,
421
+ )}>
422
+ {searching && (
423
+ <div className="px-2 py-1.5 text-xs text-muted-foreground animate-pulse">
424
+ Recherche...
425
+ </div>
426
+ )}
427
+ {suggestions.map((user) => (
428
+ <button
429
+ key={user.id}
430
+ type="button"
431
+ className={cn(
432
+ "flex w-full items-center gap-2.5 rounded-md px-2 py-1.5 text-left",
433
+ "text-sm transition-colors cursor-pointer",
434
+ "hover:bg-accent/50",
435
+ )}
436
+ onClick={() => onSelect?.(user)}
437
+ >
438
+ <Avatar className="h-6 w-6">
439
+ {user.avatar && <AvatarImage src={user.avatar} alt={user.name} />}
440
+ <AvatarFallback className="text-[10px] bg-primary/10 text-primary">
441
+ {user.name.slice(0, 2).toUpperCase()}
442
+ </AvatarFallback>
443
+ </Avatar>
444
+ <div className="min-w-0 flex-1">
445
+ <div className="font-medium truncate">{user.name}</div>
446
+ {user.email && (
447
+ <div className="text-xs text-muted-foreground truncate">{user.email}</div>
448
+ )}
449
+ </div>
450
+ {user.role && (
451
+ <span className="text-[10px] text-muted-foreground shrink-0">{user.role}</span>
452
+ )}
453
+ </button>
454
+ ))}
455
+ </div>
456
+ )
457
+ }
458
+
459
+ MentionSuggestions.displayName = "MentionSuggestions"