@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,434 @@
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
+
8
+ // ─── Types ───────────────────────────────────────────────────────────────────
9
+
10
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
+ type SlateNode = any
12
+
13
+ /** Predefined AI action available in the AI menu */
14
+ export interface AIAction {
15
+ /** Unique key */
16
+ key: string
17
+ /** Display label */
18
+ label: string
19
+ /** Icon name from lucide-react (optional) */
20
+ icon?: string
21
+ /** Prompt template. Use {{selection}} for the selected text placeholder. */
22
+ prompt: string
23
+ /** Group name for the menu */
24
+ group?: string
25
+ }
26
+
27
+ /** Default AI actions available in the WakaAIWriter menu */
28
+ export const DEFAULT_AI_ACTIONS: AIAction[] = [
29
+ // Generation
30
+ { key: "continue", label: "Continuer la redaction", prompt: "Continue writing the following text naturally:\n\n{{selection}}", group: "Generer" },
31
+ { key: "summarize", label: "Resumer", prompt: "Summarize the following text concisely:\n\n{{selection}}", group: "Generer" },
32
+ { key: "explain", label: "Expliquer", prompt: "Explain the following text in simple terms:\n\n{{selection}}", group: "Generer" },
33
+ { key: "outline", label: "Creer un plan", prompt: "Create a structured outline from the following text:\n\n{{selection}}", group: "Generer" },
34
+ // Editing
35
+ { key: "improve", label: "Ameliorer le style", prompt: "Improve the writing quality of the following text while keeping the same meaning:\n\n{{selection}}", group: "Editer" },
36
+ { key: "shorter", label: "Raccourcir", prompt: "Make the following text shorter while keeping the key information:\n\n{{selection}}", group: "Editer" },
37
+ { key: "longer", label: "Developper", prompt: "Expand and elaborate on the following text:\n\n{{selection}}", group: "Editer" },
38
+ { key: "fix-grammar", label: "Corriger l'orthographe", prompt: "Fix all spelling and grammar errors in the following text:\n\n{{selection}}", group: "Editer" },
39
+ { key: "simplify", label: "Simplifier", prompt: "Simplify the language of the following text to make it easier to understand:\n\n{{selection}}", group: "Editer" },
40
+ // Translation
41
+ { key: "translate-en", label: "Traduire en anglais", prompt: "Translate the following text to English:\n\n{{selection}}", group: "Traduire" },
42
+ { key: "translate-fr", label: "Traduire en francais", prompt: "Translate the following text to French:\n\n{{selection}}", group: "Traduire" },
43
+ { key: "translate-es", label: "Traduire en espagnol", prompt: "Translate the following text to Spanish:\n\n{{selection}}", group: "Traduire" },
44
+ { key: "translate-de", label: "Traduire en allemand", prompt: "Translate the following text to German:\n\n{{selection}}", group: "Traduire" },
45
+ ]
46
+
47
+ export interface WakaAIWriterProps {
48
+ /** Initial Slate content */
49
+ value?: SlateNode[]
50
+ /** Change callback */
51
+ onChange?: (value: SlateNode[]) => void
52
+ /** API endpoint for AI completions (POST) */
53
+ aiEndpoint: string
54
+ /**
55
+ * System prompt sent to the AI API alongside the user prompt.
56
+ * Allows per-tenant/per-app customization of the AI behavior.
57
+ */
58
+ systemPrompt?: string
59
+ /** AI actions available in the slash menu and AI menu */
60
+ aiActions?: AIAction[]
61
+ /** Enable ghost text copilot (inline AI suggestions) */
62
+ enableCopilot?: boolean
63
+ /** Debounce delay for copilot suggestions in ms (default: 500) */
64
+ copilotDebounceMs?: number
65
+ /** Headers to send with AI requests (e.g. Authorization) */
66
+ aiHeaders?: Record<string, string>
67
+ /** Read-only mode */
68
+ readOnly?: boolean
69
+ /** Placeholder text */
70
+ placeholder?: string
71
+ /** CSS class */
72
+ className?: string
73
+ /** Editor area CSS class */
74
+ editorClassName?: string
75
+ /** Minimum height in px */
76
+ minHeight?: number
77
+ /** Label */
78
+ label?: string
79
+ /** Description */
80
+ description?: string
81
+ /** Error */
82
+ error?: string
83
+ /** Extra Plate plugins */
84
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
85
+ extraPlugins?: any[]
86
+ /** Ref to the Plate editor instance */
87
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
88
+ editorRef?: React.MutableRefObject<any>
89
+ }
90
+
91
+ // ─── Plate types (loaded dynamically) ────────────────────────────────────────
92
+
93
+ interface PlateBundle {
94
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
95
+ Plate: React.ComponentType<any>
96
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
97
+ PlateContent: React.ComponentType<any>
98
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
99
+ usePlateEditor: (config: any) => any
100
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
101
+ plugins: any[]
102
+ }
103
+
104
+ // ─── Component ───────────────────────────────────────────────────────────────
105
+
106
+ /**
107
+ * WakaAIWriter — AI-powered rich text editor.
108
+ *
109
+ * Wraps Plate.js with `@platejs/ai` for:
110
+ * - Copilot ghost text (inline suggestions while typing)
111
+ * - AI menu (Cmd+J or `/ai`) with customizable actions
112
+ * - Configurable system prompt per tenant / app
113
+ *
114
+ * Requires peer dependencies: `platejs`, `@platejs/ai`, `@platejs/markdown`,
115
+ * `@platejs/basic-nodes`, `@platejs/selection`.
116
+ *
117
+ * @example
118
+ * ```tsx
119
+ * <WakaAIWriter
120
+ * aiEndpoint="/api/ai/completion"
121
+ * systemPrompt="You are a helpful writing assistant for healthcare documentation."
122
+ * enableCopilot
123
+ * />
124
+ * ```
125
+ */
126
+ export const WakaAIWriter = React.forwardRef<HTMLDivElement, WakaAIWriterProps>(
127
+ (
128
+ {
129
+ value,
130
+ onChange,
131
+ aiEndpoint,
132
+ systemPrompt = "You are an advanced AI writing assistant. Help the user write, edit, and improve their content.",
133
+ aiActions = DEFAULT_AI_ACTIONS,
134
+ enableCopilot = true,
135
+ copilotDebounceMs = 500,
136
+ aiHeaders,
137
+ readOnly = false,
138
+ placeholder = "Tapez / pour les commandes, ou commencez a ecrire...",
139
+ className,
140
+ editorClassName,
141
+ minHeight = 300,
142
+ label,
143
+ description,
144
+ error,
145
+ extraPlugins,
146
+ editorRef,
147
+ },
148
+ ref
149
+ ) => {
150
+ const [bundle, setBundle] = React.useState<PlateBundle | null>(null)
151
+ const [loadError, setLoadError] = React.useState(false)
152
+
153
+ // Stable refs for config values used in plugin setup
154
+ const configRef = React.useRef({ aiEndpoint, systemPrompt, aiActions, enableCopilot, copilotDebounceMs, aiHeaders })
155
+ configRef.current = { aiEndpoint, systemPrompt, aiActions, enableCopilot, copilotDebounceMs, aiHeaders }
156
+
157
+ React.useEffect(() => {
158
+ let cancelled = false
159
+
160
+ const load = async () => {
161
+ try {
162
+ const [
163
+ plateReact,
164
+ basicNodes,
165
+ table,
166
+ layout,
167
+ callout,
168
+ aiModule,
169
+ markdownModule,
170
+ selectionModule,
171
+ linkModule,
172
+ indentModule,
173
+ ] = await Promise.all([
174
+ import("platejs/react"),
175
+ import("@platejs/basic-nodes/react"),
176
+ import("@platejs/table/react"),
177
+ import("@platejs/layout/react"),
178
+ import("@platejs/callout/react"),
179
+ import("@platejs/ai/react"),
180
+ import("@platejs/markdown"),
181
+ import("@platejs/selection/react"),
182
+ import("@platejs/link/react"),
183
+ import("@platejs/indent/react"),
184
+ ])
185
+
186
+ const cfg = configRef.current
187
+
188
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
189
+ const plugins: any[] = [
190
+ // Core marks
191
+ basicNodes.BoldPlugin,
192
+ basicNodes.ItalicPlugin,
193
+ basicNodes.UnderlinePlugin,
194
+ basicNodes.StrikethroughPlugin,
195
+ basicNodes.CodePlugin,
196
+ basicNodes.HighlightPlugin,
197
+ // Headings
198
+ basicNodes.H1Plugin,
199
+ basicNodes.H2Plugin,
200
+ basicNodes.H3Plugin,
201
+ basicNodes.H4Plugin,
202
+ basicNodes.BlockquotePlugin,
203
+ // Table
204
+ table.TablePlugin,
205
+ table.TableRowPlugin,
206
+ table.TableCellPlugin,
207
+ table.TableCellHeaderPlugin,
208
+ // Layout
209
+ layout.ColumnPlugin,
210
+ layout.ColumnItemPlugin,
211
+ // Callout
212
+ callout.CalloutPlugin,
213
+ // Link
214
+ linkModule.LinkPlugin.configure({
215
+ options: { allowedSchemes: ["http", "https", "mailto", "tel"] },
216
+ }),
217
+ // Indent
218
+ indentModule.IndentPlugin,
219
+ // Selection (required by AI)
220
+ selectionModule.BlockSelectionPlugin,
221
+ // Markdown (required by AI)
222
+ markdownModule.MarkdownPlugin,
223
+ // AI plugins
224
+ aiModule.AIPlugin,
225
+ aiModule.AIChatPlugin.configure({
226
+ options: {
227
+ chat: {
228
+ api: cfg.aiEndpoint,
229
+ body: {
230
+ system: cfg.systemPrompt,
231
+ },
232
+ headers: cfg.aiHeaders,
233
+ },
234
+ },
235
+ }),
236
+ ]
237
+
238
+ // Copilot plugin
239
+ if (cfg.enableCopilot && aiModule.CopilotPlugin) {
240
+ plugins.push(
241
+ aiModule.CopilotPlugin.configure(({ api }: { api: { copilot: { setBlockSuggestion: (o: { text: string }) => void } } }) => ({
242
+ options: {
243
+ completeOptions: {
244
+ api: cfg.aiEndpoint,
245
+ body: {
246
+ system: cfg.systemPrompt,
247
+ },
248
+ headers: cfg.aiHeaders,
249
+ onFinish: (_: unknown, completion: string) => {
250
+ if (completion === "0") return
251
+ api.copilot.setBlockSuggestion({ text: completion })
252
+ },
253
+ },
254
+ debounceDelay: cfg.copilotDebounceMs,
255
+ },
256
+ shortcuts: {
257
+ accept: { keys: "tab" },
258
+ acceptNextWord: { keys: "mod+right" },
259
+ reject: { keys: "escape" },
260
+ triggerSuggestion: { keys: "ctrl+space" },
261
+ },
262
+ }))
263
+ )
264
+ }
265
+
266
+ if (!cancelled) {
267
+ setBundle({
268
+ Plate: plateReact.Plate,
269
+ PlateContent: plateReact.PlateContent,
270
+ usePlateEditor: plateReact.usePlateEditor,
271
+ plugins,
272
+ })
273
+ }
274
+ } catch (err) {
275
+ console.error("[WakaAIWriter] Failed to load plugins:", err)
276
+ if (!cancelled) setLoadError(true)
277
+ }
278
+ }
279
+
280
+ load()
281
+ return () => { cancelled = true }
282
+ }, []) // Load once; config changes read from ref
283
+
284
+ if (bundle) {
285
+ return (
286
+ <AIWriterInner
287
+ ref={ref}
288
+ bundle={bundle}
289
+ value={value}
290
+ onChange={onChange}
291
+ readOnly={readOnly}
292
+ placeholder={placeholder}
293
+ className={className}
294
+ editorClassName={editorClassName}
295
+ minHeight={minHeight}
296
+ label={label}
297
+ description={description}
298
+ error={error}
299
+ extraPlugins={extraPlugins}
300
+ editorRef={editorRef}
301
+ />
302
+ )
303
+ }
304
+
305
+ // Fallback
306
+ return (
307
+ <div ref={ref} className={cn("space-y-1.5", className)}>
308
+ {label && <Label>{label}</Label>}
309
+ {description && <p className="text-sm text-muted-foreground">{description}</p>}
310
+ <div className="relative">
311
+ <Textarea
312
+ placeholder={placeholder}
313
+ disabled
314
+ style={{ minHeight }}
315
+ className={cn(error && "border-destructive")}
316
+ />
317
+ <div className="absolute inset-0 flex items-center justify-center bg-background/50 rounded-lg">
318
+ <span className="text-sm text-muted-foreground animate-pulse">
319
+ {loadError ? "Erreur de chargement de l'editeur IA" : "Chargement de l'editeur IA..."}
320
+ </span>
321
+ </div>
322
+ </div>
323
+ {error && <p className="text-sm text-destructive">{error}</p>}
324
+ </div>
325
+ )
326
+ }
327
+ )
328
+
329
+ WakaAIWriter.displayName = "WakaAIWriter"
330
+
331
+ // ─── Inner Plate component ──────────────────────────────────────────────────
332
+
333
+ interface AIWriterInnerProps {
334
+ bundle: PlateBundle
335
+ value?: SlateNode[]
336
+ onChange?: (value: SlateNode[]) => void
337
+ readOnly?: boolean
338
+ placeholder?: string
339
+ className?: string
340
+ editorClassName?: string
341
+ minHeight?: number
342
+ label?: string
343
+ description?: string
344
+ error?: string
345
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
346
+ extraPlugins?: any[]
347
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
348
+ editorRef?: React.MutableRefObject<any>
349
+ }
350
+
351
+ const AIWriterInner = React.forwardRef<HTMLDivElement, AIWriterInnerProps>(
352
+ (
353
+ {
354
+ bundle: { Plate, PlateContent, usePlateEditor, plugins },
355
+ value,
356
+ onChange,
357
+ readOnly,
358
+ placeholder,
359
+ className,
360
+ editorClassName,
361
+ minHeight,
362
+ label,
363
+ description,
364
+ error,
365
+ extraPlugins,
366
+ editorRef,
367
+ },
368
+ ref
369
+ ) => {
370
+ const allPlugins = React.useMemo(
371
+ () => [...plugins, ...(extraPlugins ?? [])],
372
+ [plugins, extraPlugins]
373
+ )
374
+
375
+ const editor = usePlateEditor({
376
+ plugins: allPlugins,
377
+ value: value ?? [{ type: "p", children: [{ text: "" }] }],
378
+ })
379
+
380
+ React.useEffect(() => {
381
+ if (editorRef) editorRef.current = editor
382
+ }, [editor, editorRef])
383
+
384
+ return (
385
+ <div ref={ref} className={cn("space-y-1.5", className)}>
386
+ {label && <Label>{label}</Label>}
387
+ {description && <p className="text-sm text-muted-foreground">{description}</p>}
388
+
389
+ <Plate
390
+ editor={editor}
391
+ onChange={onChange ? ({ value: v }: { value: SlateNode[] }) => onChange(v) : undefined}
392
+ readOnly={readOnly}
393
+ >
394
+ <div className={cn(
395
+ "border rounded-lg overflow-hidden",
396
+ error && "border-destructive",
397
+ readOnly && "opacity-80",
398
+ )}>
399
+ {/* AI toolbar hint */}
400
+ {!readOnly && (
401
+ <div className="flex items-center gap-2 px-3 py-1.5 border-b bg-muted/30 text-xs text-muted-foreground">
402
+ <kbd className="rounded border bg-muted px-1.5 py-0.5 font-mono text-[10px]">Ctrl+J</kbd>
403
+ <span>Ouvrir le menu IA</span>
404
+ <span className="mx-1">|</span>
405
+ <kbd className="rounded border bg-muted px-1.5 py-0.5 font-mono text-[10px]">/</kbd>
406
+ <span>Commandes</span>
407
+ <span className="mx-1">|</span>
408
+ <kbd className="rounded border bg-muted px-1.5 py-0.5 font-mono text-[10px]">Ctrl+Espace</kbd>
409
+ <span>Copilot</span>
410
+ </div>
411
+ )}
412
+
413
+ <div style={{ minHeight }}>
414
+ <PlateContent
415
+ placeholder={placeholder}
416
+ readOnly={readOnly}
417
+ className={cn(
418
+ "prose prose-sm max-w-none p-4",
419
+ "focus-within:outline-none",
420
+ "[&_[data-slate-editor]]:outline-none [&_[data-slate-editor]]:min-h-[inherit]",
421
+ editorClassName
422
+ )}
423
+ />
424
+ </div>
425
+ </div>
426
+ </Plate>
427
+
428
+ {error && <p className="text-sm text-destructive">{error}</p>}
429
+ </div>
430
+ )
431
+ }
432
+ )
433
+
434
+ AIWriterInner.displayName = "AIWriterInner"