@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,432 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cn } from "../../../utils/cn"
5
+ import type { SlashCommand } from "../waka-slash-menu"
6
+ import { DEFAULT_SLASH_COMMANDS } from "../waka-slash-menu"
7
+
8
+ // ============================================================================
9
+ // Types
10
+ // ============================================================================
11
+
12
+ /** Extended slash command with WakaStart-specific blocks */
13
+ export interface WakaSlashBlockCommand extends SlashCommand {
14
+ /** Preview/thumbnail for the block */
15
+ preview?: React.ReactNode
16
+ /** Whether this is a WakaStart custom block (vs standard Plate block) */
17
+ isCustom?: boolean
18
+ }
19
+
20
+ export interface WakaSlashMenuBlockProps {
21
+ /** All available commands (merges with defaults) */
22
+ commands?: WakaSlashBlockCommand[]
23
+ /** Filter query (typed after "/") */
24
+ query?: string
25
+ /** Called when a command is selected */
26
+ onSelect?: (command: WakaSlashBlockCommand) => void
27
+ /** Active (highlighted) index */
28
+ activeIndex?: number
29
+ /** Callback when active index changes */
30
+ onActiveIndexChange?: (index: number) => void
31
+ /** CSS class */
32
+ className?: string
33
+ }
34
+
35
+ // ============================================================================
36
+ // Default WakaStart Block Commands
37
+ // ============================================================================
38
+
39
+ /**
40
+ * WakaStart-specific slash commands for custom blocks.
41
+ * These extend the standard Plate slash commands with blocks specific
42
+ * to the WakaStart specification and project management workflow.
43
+ */
44
+ export const WAKA_BLOCK_COMMANDS: WakaSlashBlockCommand[] = [
45
+ // User Story
46
+ {
47
+ key: "user-story",
48
+ label: "User Story",
49
+ description: "Structured user story (As a / I want / So that)",
50
+ icon: (() => {
51
+ // Lazy icon - we use a simple component approach
52
+ const { Users } = require("lucide-react") as { Users: React.ComponentType<{ className?: string }> }
53
+ return Users
54
+ })(),
55
+ group: "WakaStart Blocks",
56
+ keywords: ["user", "story", "agile", "requirement", "persona"],
57
+ isCustom: true,
58
+ action: (editor) => {
59
+ try {
60
+ const { createUserStoryNodes } = require("./waka-user-story-block")
61
+ editor.insertNodes(createUserStoryNodes())
62
+ } catch { /* */ }
63
+ },
64
+ },
65
+ // Acceptance Criteria
66
+ {
67
+ key: "acceptance-criteria",
68
+ label: "Acceptance Criteria",
69
+ description: "Gherkin-format criteria (Given / When / Then)",
70
+ icon: (() => {
71
+ const { CheckCircle2 } = require("lucide-react") as { CheckCircle2: React.ComponentType<{ className?: string }> }
72
+ return CheckCircle2
73
+ })(),
74
+ group: "WakaStart Blocks",
75
+ keywords: ["acceptance", "criteria", "gherkin", "given", "when", "then", "test"],
76
+ isCustom: true,
77
+ action: (editor) => {
78
+ try {
79
+ const { createAcceptanceCriteriaNodes } = require("./waka-acceptance-criteria-block")
80
+ editor.insertNodes(createAcceptanceCriteriaNodes())
81
+ } catch { /* */ }
82
+ },
83
+ },
84
+ // API Endpoint
85
+ {
86
+ key: "api-endpoint",
87
+ label: "API Endpoint",
88
+ description: "REST API documentation (method, route, body, response)",
89
+ icon: (() => {
90
+ const { Zap } = require("lucide-react") as { Zap: React.ComponentType<{ className?: string }> }
91
+ return Zap
92
+ })(),
93
+ group: "WakaStart Blocks",
94
+ keywords: ["api", "endpoint", "rest", "route", "get", "post", "put", "delete"],
95
+ isCustom: true,
96
+ action: (editor) => {
97
+ try {
98
+ const { createApiEndpointNodes } = require("./waka-api-endpoint-block")
99
+ editor.insertNodes(createApiEndpointNodes())
100
+ } catch { /* */ }
101
+ },
102
+ },
103
+ // Diagram
104
+ {
105
+ key: "diagram",
106
+ label: "Diagram",
107
+ description: "Mermaid / PlantUML diagram with live preview",
108
+ icon: (() => {
109
+ const { GitBranch } = require("lucide-react") as { GitBranch: React.ComponentType<{ className?: string }> }
110
+ return GitBranch
111
+ })(),
112
+ group: "WakaStart Blocks",
113
+ keywords: ["diagram", "mermaid", "plantuml", "flowchart", "sequence", "graph"],
114
+ isCustom: true,
115
+ action: (editor) => {
116
+ try {
117
+ const { createDiagramNodes } = require("./waka-diagram-block")
118
+ editor.insertNodes(createDiagramNodes())
119
+ } catch { /* */ }
120
+ },
121
+ },
122
+ // AI Assist
123
+ {
124
+ key: "ai-assist",
125
+ label: "AI Assistant",
126
+ description: "Ask the AI to generate content for your document",
127
+ icon: (() => {
128
+ const { Sparkles } = require("lucide-react") as { Sparkles: React.ComponentType<{ className?: string }> }
129
+ return Sparkles
130
+ })(),
131
+ group: "WakaStart Blocks",
132
+ keywords: ["ai", "ia", "assistant", "generate", "write", "help", "claude"],
133
+ isCustom: true,
134
+ action: (editor) => {
135
+ try {
136
+ const { createAiAssistNodes } = require("./waka-ai-assist-block")
137
+ editor.insertNodes(createAiAssistNodes())
138
+ } catch { /* */ }
139
+ },
140
+ },
141
+ // Version Diff
142
+ {
143
+ key: "version-diff",
144
+ label: "Version Diff",
145
+ description: "Compare two versions of text with visual diff",
146
+ icon: (() => {
147
+ const { GitCompare } = require("lucide-react") as { GitCompare: React.ComponentType<{ className?: string }> }
148
+ return GitCompare
149
+ })(),
150
+ group: "WakaStart Blocks",
151
+ keywords: ["diff", "version", "compare", "change", "history", "git"],
152
+ isCustom: true,
153
+ action: (editor) => {
154
+ try {
155
+ const { createVersionDiffNodes } = require("./waka-version-diff-block")
156
+ editor.insertNodes(createVersionDiffNodes())
157
+ } catch { /* */ }
158
+ },
159
+ },
160
+ // Embed
161
+ {
162
+ key: "embed",
163
+ label: "Embed",
164
+ description: "Embed external content (Figma, YouTube, CodeSandbox...)",
165
+ icon: (() => {
166
+ const { ExternalLink } = require("lucide-react") as { ExternalLink: React.ComponentType<{ className?: string }> }
167
+ return ExternalLink
168
+ })(),
169
+ group: "WakaStart Blocks",
170
+ keywords: ["embed", "figma", "youtube", "codesandbox", "loom", "iframe", "video"],
171
+ isCustom: true,
172
+ action: (editor) => {
173
+ try {
174
+ const { createEmbedNodes } = require("./waka-embed-block")
175
+ editor.insertNodes(createEmbedNodes(""))
176
+ } catch { /* */ }
177
+ },
178
+ },
179
+ // Code Playground
180
+ {
181
+ key: "code-playground",
182
+ label: "Code Playground",
183
+ description: "Executable code block with console output",
184
+ icon: (() => {
185
+ const { Terminal } = require("lucide-react") as { Terminal: React.ComponentType<{ className?: string }> }
186
+ return Terminal
187
+ })(),
188
+ group: "WakaStart Blocks",
189
+ keywords: ["code", "playground", "run", "execute", "terminal", "console", "sandbox"],
190
+ isCustom: true,
191
+ action: (editor) => {
192
+ try {
193
+ const { createCodePlaygroundNodes } = require("./waka-code-playground-block")
194
+ editor.insertNodes(createCodePlaygroundNodes())
195
+ } catch { /* */ }
196
+ },
197
+ },
198
+ // Comment Thread
199
+ {
200
+ key: "comment-thread",
201
+ label: "Comment Thread",
202
+ description: "Inline discussion thread (like Google Docs)",
203
+ icon: (() => {
204
+ const { MessageSquare } = require("lucide-react") as { MessageSquare: React.ComponentType<{ className?: string }> }
205
+ return MessageSquare
206
+ })(),
207
+ group: "WakaStart Blocks",
208
+ keywords: ["comment", "thread", "discussion", "review", "feedback", "note"],
209
+ isCustom: true,
210
+ action: (editor) => {
211
+ try {
212
+ const { createCommentThreadNodes } = require("./waka-comment-thread-block")
213
+ editor.insertNodes(createCommentThreadNodes())
214
+ } catch { /* */ }
215
+ },
216
+ },
217
+ ]
218
+
219
+ /**
220
+ * Get all slash commands: standard Plate commands + WakaStart custom blocks.
221
+ */
222
+ export function getAllWakaSlashCommands(extraCommands?: WakaSlashBlockCommand[]): WakaSlashBlockCommand[] {
223
+ return [
224
+ ...DEFAULT_SLASH_COMMANDS.map((cmd) => ({ ...cmd, isCustom: false })),
225
+ ...WAKA_BLOCK_COMMANDS,
226
+ ...(extraCommands || []),
227
+ ]
228
+ }
229
+
230
+ // ============================================================================
231
+ // Component
232
+ // ============================================================================
233
+
234
+ /**
235
+ * WakaSlashMenuBlock - An enhanced slash menu that includes all standard Plate
236
+ * blocks PLUS WakaStart-specific custom blocks (User Story, API Endpoint,
237
+ * Diagram, AI Assist, Code Playground, etc.).
238
+ *
239
+ * This replaces the standard WakaSlashMenu when you want the full WakaStart
240
+ * block vocabulary available in your editor.
241
+ *
242
+ * @example
243
+ * ```tsx
244
+ * // In your Plate slash plugin configuration:
245
+ * const slashPlugins = await createWakaSlashPlugins()
246
+ *
247
+ * // Or use the menu component standalone:
248
+ * <WakaSlashMenuBlock
249
+ * query={searchQuery}
250
+ * onSelect={(cmd) => cmd.action(editor)}
251
+ * />
252
+ * ```
253
+ */
254
+ export function WakaSlashMenuBlock({
255
+ commands,
256
+ query = "",
257
+ onSelect,
258
+ activeIndex: controlledIndex,
259
+ onActiveIndexChange,
260
+ className,
261
+ }: WakaSlashMenuBlockProps) {
262
+ const allCommands = React.useMemo(
263
+ () => commands || getAllWakaSlashCommands(),
264
+ [commands]
265
+ )
266
+
267
+ const [internalIndex, setInternalIndex] = React.useState(0)
268
+ const activeIndex = controlledIndex ?? internalIndex
269
+ const setActiveIndex = onActiveIndexChange || setInternalIndex
270
+
271
+ // Filter commands
272
+ const filtered = React.useMemo(() => {
273
+ if (!query) return allCommands
274
+ const q = query.toLowerCase()
275
+ return allCommands.filter(
276
+ (cmd) =>
277
+ cmd.label.toLowerCase().includes(q) ||
278
+ cmd.key.toLowerCase().includes(q) ||
279
+ cmd.description?.toLowerCase().includes(q) ||
280
+ cmd.keywords?.some((kw) => kw.toLowerCase().includes(q))
281
+ )
282
+ }, [allCommands, query])
283
+
284
+ // Group commands
285
+ const groups = React.useMemo(() => {
286
+ const map = new Map<string, WakaSlashBlockCommand[]>()
287
+ for (const cmd of filtered) {
288
+ const group = cmd.group || "Other"
289
+ if (!map.has(group)) map.set(group, [])
290
+ map.get(group)!.push(cmd)
291
+ }
292
+ return map
293
+ }, [filtered])
294
+
295
+ // Reset active index on filter change
296
+ React.useEffect(() => setActiveIndex(0), [query])
297
+
298
+ // Keyboard navigation
299
+ const handleKeyDown = React.useCallback(
300
+ (e: React.KeyboardEvent) => {
301
+ if (e.key === "ArrowDown") {
302
+ e.preventDefault()
303
+ setActiveIndex(Math.min(activeIndex + 1, filtered.length - 1))
304
+ } else if (e.key === "ArrowUp") {
305
+ e.preventDefault()
306
+ setActiveIndex(Math.max(activeIndex - 1, 0))
307
+ } else if (e.key === "Enter" && filtered[activeIndex]) {
308
+ e.preventDefault()
309
+ onSelect?.(filtered[activeIndex])
310
+ }
311
+ },
312
+ [filtered, activeIndex, onSelect, setActiveIndex]
313
+ )
314
+
315
+ return (
316
+ <div
317
+ className={cn(
318
+ "w-80 max-h-96 overflow-y-auto",
319
+ "rounded-lg border bg-popover shadow-xl",
320
+ "p-1",
321
+ className
322
+ )}
323
+ role="listbox"
324
+ onKeyDown={handleKeyDown}
325
+ aria-label="Slash commands"
326
+ >
327
+ {filtered.length === 0 && (
328
+ <div className="px-3 py-6 text-center text-sm text-muted-foreground">
329
+ No blocks found for "{query}"
330
+ </div>
331
+ )}
332
+
333
+ {Array.from(groups.entries()).map(([groupName, cmds]) => (
334
+ <div key={groupName} className="mb-1">
335
+ <div className="px-2 py-1.5 text-[10px] font-bold uppercase tracking-wider text-muted-foreground/70 flex items-center gap-2">
336
+ {groupName}
337
+ {groupName === "WakaStart Blocks" && (
338
+ <span className="px-1 py-0.5 rounded bg-primary/10 text-primary text-[8px] font-bold normal-case tracking-normal">
339
+ Custom
340
+ </span>
341
+ )}
342
+ </div>
343
+ {cmds.map((cmd) => {
344
+ const globalIndex = filtered.indexOf(cmd)
345
+ const Icon = cmd.icon as React.ComponentType<{ className?: string }>
346
+ return (
347
+ <button
348
+ key={cmd.key}
349
+ role="option"
350
+ aria-selected={globalIndex === activeIndex}
351
+ className={cn(
352
+ "flex w-full items-center gap-3 rounded-md px-2 py-2 text-left",
353
+ "text-sm transition-colors cursor-pointer",
354
+ globalIndex === activeIndex
355
+ ? "bg-accent text-accent-foreground"
356
+ : "hover:bg-accent/50"
357
+ )}
358
+ onMouseEnter={() => setActiveIndex(globalIndex)}
359
+ onClick={() => onSelect?.(cmd)}
360
+ >
361
+ <div className={cn(
362
+ "flex h-8 w-8 shrink-0 items-center justify-center rounded-md border",
363
+ cmd.isCustom
364
+ ? "border-primary/20 bg-primary/5"
365
+ : "border-border bg-background"
366
+ )}>
367
+ {typeof Icon === "function" ? <Icon className="h-4 w-4" /> : Icon}
368
+ </div>
369
+ <div className="min-w-0 flex-1">
370
+ <div className="flex items-center gap-1.5">
371
+ <span className="font-medium text-sm">{cmd.label}</span>
372
+ {cmd.isCustom && (
373
+ <span className="h-1.5 w-1.5 rounded-full bg-primary flex-shrink-0" />
374
+ )}
375
+ </div>
376
+ {cmd.description && (
377
+ <div className="text-[11px] text-muted-foreground truncate mt-0.5">
378
+ {cmd.description}
379
+ </div>
380
+ )}
381
+ </div>
382
+ </button>
383
+ )
384
+ })}
385
+ </div>
386
+ ))}
387
+ </div>
388
+ )
389
+ }
390
+
391
+ WakaSlashMenuBlock.displayName = "WakaSlashMenuBlock"
392
+
393
+ // ============================================================================
394
+ // Plugin Helper
395
+ // ============================================================================
396
+
397
+ /**
398
+ * Creates slash command plugin configuration with all WakaStart blocks registered.
399
+ *
400
+ * @example
401
+ * ```ts
402
+ * const plugins = await createWakaSlashPlugins()
403
+ * // Add to editor plugins array
404
+ * ```
405
+ */
406
+ export async function createWakaSlashPlugins(
407
+ extraCommands?: WakaSlashBlockCommand[]
408
+ ) {
409
+ try {
410
+ const { SlashPlugin, SlashInputPlugin } = await import("@platejs/combobox/react")
411
+ const allCommands = getAllWakaSlashCommands(extraCommands)
412
+
413
+ return [
414
+ SlashPlugin.configure({
415
+ options: {
416
+ trigger: "/",
417
+ triggerPreviousCharPattern: /^$|^[\s"']$/,
418
+ rules: allCommands.map((cmd) => ({
419
+ key: cmd.key,
420
+ text: cmd.label,
421
+ keywords: cmd.keywords,
422
+ onSelect: cmd.action,
423
+ })),
424
+ },
425
+ }),
426
+ SlashInputPlugin,
427
+ ]
428
+ } catch {
429
+ console.warn("[WakaSlashMenuBlock] @platejs/combobox not installed, slash commands disabled")
430
+ return []
431
+ }
432
+ }