@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.
- package/dist/badge-BbwO7QeZ.js +1 -0
- package/dist/badge-BfiocODp.mjs +23 -0
- package/dist/charts.cjs.js +1 -1
- package/dist/charts.es.js +1 -1
- package/dist/chunk-14q5BKub.js +1 -0
- package/dist/{chunk-BH6uBOac.mjs → chunk-Cr9pTUWm.mjs} +5 -5
- package/dist/cn-DEtaFQsA.js +1 -0
- package/dist/cn-DUn6aSIQ.mjs +24 -0
- package/dist/doc.cjs.js +2 -2
- package/dist/doc.es.js +19 -19
- package/dist/editor.cjs.js +48 -0
- package/dist/editor.d.ts +1 -0
- package/dist/editor.es.js +6551 -0
- package/dist/{exceljs.min-DG9M8IZ1.mjs → exceljs.min-DL1XYDll.mjs} +1 -1
- package/dist/{exceljs.min-BuefmDRS.js → exceljs.min-qeIfSCbF.js} +1 -1
- package/dist/export.cjs.js +1 -1
- package/dist/export.es.js +1 -1
- package/dist/index.cjs.js +150 -150
- package/dist/index.es.js +26782 -27591
- package/dist/input-BfaSAGVw.js +1 -0
- package/dist/input-DVr_Qkl8.mjs +14 -0
- package/dist/rich-text.cjs.js +1 -1
- package/dist/rich-text.es.js +1 -1
- package/dist/security-CyBpuklN.mjs +122 -0
- package/dist/security-bFWwDrlg.js +1 -0
- package/dist/separator-NrkltulH.js +1 -0
- package/dist/separator-ibN2mycs.mjs +51 -0
- package/dist/src/components/editor/blocks/index.d.ts +51 -0
- package/dist/src/components/editor/blocks/waka-acceptance-criteria-block.d.ts +60 -0
- package/dist/src/components/editor/blocks/waka-ai-assist-block.d.ts +58 -0
- package/dist/src/components/editor/blocks/waka-api-endpoint-block.d.ts +63 -0
- package/dist/src/components/editor/blocks/waka-code-playground-block.d.ts +61 -0
- package/dist/src/components/editor/blocks/waka-comment-thread-block.d.ts +85 -0
- package/dist/src/components/editor/blocks/waka-diagram-block.d.ts +52 -0
- package/dist/src/components/editor/blocks/waka-embed-block.d.ts +58 -0
- package/dist/src/components/editor/blocks/waka-slash-menu-block.d.ts +67 -0
- package/dist/src/components/editor/blocks/waka-user-story-block.d.ts +79 -0
- package/dist/src/components/editor/blocks/waka-version-diff-block.d.ts +73 -0
- package/dist/src/components/editor/index.d.ts +66 -0
- package/dist/src/components/editor/waka-ai-writer.d.ts +80 -0
- package/dist/src/components/editor/waka-collaborative-editor.d.ts +93 -0
- package/dist/src/components/editor/waka-diff-viewer.d.ts +71 -0
- package/dist/src/components/editor/waka-dnd-editor.d.ts +64 -0
- package/dist/src/components/editor/waka-document-editor.d.ts +92 -0
- package/dist/src/components/editor/waka-editor-elements.d.ts +79 -0
- package/dist/src/components/editor/waka-editor-leaves.d.ts +39 -0
- package/dist/src/components/editor/waka-editor-plugins.d.ts +41 -0
- package/dist/src/components/editor/waka-editor-toolbar.d.ts +20 -0
- package/dist/src/components/editor/waka-editor.d.ts +59 -0
- package/dist/src/components/editor/waka-floating-toolbar.d.ts +47 -0
- package/dist/src/components/editor/waka-markdown-editor.d.ts +60 -0
- package/dist/src/components/editor/waka-mention-editor.d.ts +125 -0
- package/dist/src/components/editor/waka-slash-menu.d.ts +70 -0
- package/dist/src/components/editor/waka-spec-editor.d.ts +88 -0
- package/dist/src/components/index.d.ts +1 -15
- package/dist/src/editor.d.ts +26 -0
- package/dist/textarea-CdQWggYG.js +1 -0
- package/dist/textarea-DJDXJ3nd.mjs +23 -0
- package/dist/types-C2St0wOW.js +1 -0
- package/dist/{types-B6GVaSIP.mjs → types-JnqoLyuv.mjs} +214 -211
- package/dist/{useDataTableImport-BPvfo--2.mjs → useDataTableImport-BWUFesPi.mjs} +3 -3
- package/dist/{useDataTableImport-Cm_pCKnO.js → useDataTableImport-T7ddpN5k.js} +3 -3
- package/dist/waka-doc-renderer-CTxC7Trf.js +3 -0
- package/dist/{waka-doc-renderer-BkIvas3z.mjs → waka-doc-renderer-Cw-Xnyen.mjs} +264 -281
- package/dist/waka-editor-plugins-DR6tpsUC.mjs +135 -0
- package/dist/waka-editor-plugins-sGSh9hn2.js +1 -0
- package/dist/waka-rich-text-editor-BlIdtknG.js +1 -0
- package/dist/waka-rich-text-editor-D1uA3zbB.js +1 -0
- package/dist/waka-rich-text-editor-DgSWiXMW.mjs +342 -0
- package/dist/waka-rich-text-editor-DndVJuDw.mjs +2 -0
- package/package.json +87 -2
- package/src/blocks/footer/index.tsx +1 -6
- package/src/blocks/login/index.tsx +1 -7
- package/src/blocks/profile/index.tsx +3 -5
- package/src/components/editor/blocks/index.ts +182 -0
- package/src/components/editor/blocks/waka-acceptance-criteria-block.tsx +326 -0
- package/src/components/editor/blocks/waka-ai-assist-block.tsx +284 -0
- package/src/components/editor/blocks/waka-api-endpoint-block.tsx +382 -0
- package/src/components/editor/blocks/waka-code-playground-block.tsx +331 -0
- package/src/components/editor/blocks/waka-comment-thread-block.tsx +448 -0
- package/src/components/editor/blocks/waka-diagram-block.tsx +293 -0
- package/src/components/editor/blocks/waka-embed-block.tsx +416 -0
- package/src/components/editor/blocks/waka-slash-menu-block.tsx +432 -0
- package/src/components/editor/blocks/waka-user-story-block.tsx +295 -0
- package/src/components/editor/blocks/waka-version-diff-block.tsx +426 -0
- package/src/components/editor/index.ts +279 -0
- package/src/components/editor/waka-ai-writer.tsx +434 -0
- package/src/components/editor/waka-collaborative-editor.tsx +426 -0
- package/src/components/editor/waka-diff-viewer.tsx +352 -0
- package/src/components/editor/waka-dnd-editor.tsx +284 -0
- package/src/components/editor/waka-document-editor.tsx +502 -0
- package/src/components/editor/waka-editor-elements.tsx +312 -0
- package/src/components/editor/waka-editor-leaves.tsx +101 -0
- package/src/components/editor/waka-editor-plugins.ts +207 -0
- package/src/components/editor/waka-editor-toolbar.tsx +358 -0
- package/src/components/editor/waka-editor.tsx +431 -0
- package/src/components/editor/waka-floating-toolbar.tsx +268 -0
- package/src/components/editor/waka-markdown-editor.tsx +395 -0
- package/src/components/editor/waka-mention-editor.tsx +459 -0
- package/src/components/editor/waka-slash-menu.tsx +392 -0
- package/src/components/editor/waka-spec-editor.tsx +657 -0
- package/src/components/index.ts +1 -18
- package/dist/chunk-BDDJmn7V.js +0 -1
- package/dist/cn-DnPbmOCy.js +0 -1
- package/dist/cn-DpLcAzrf.mjs +0 -22
- package/dist/separator-BDReXBvI.mjs +0 -59
- package/dist/separator-BKjNl9sI.js +0 -1
- package/dist/src/components/waka-actor-badge/index.d.ts +0 -8
- package/dist/src/components/waka-actors-list/index.d.ts +0 -18
- package/dist/src/components/waka-ai-assistant-button/index.d.ts +0 -8
- package/dist/src/components/waka-document-flyover/index.d.ts +0 -10
- package/dist/src/components/waka-document-preview-popup/index.d.ts +0 -26
- package/dist/src/components/waka-hour-balance-badge/index.d.ts +0 -8
- package/dist/src/components/waka-hour-consumption-table/index.d.ts +0 -15
- package/dist/src/components/waka-hour-pack-dialog/index.d.ts +0 -8
- package/dist/src/components/waka-project-stats-header/index.d.ts +0 -15
- package/dist/src/components/waka-step-comment-bubble/index.d.ts +0 -13
- package/dist/src/components/waka-step-comment-panel/index.d.ts +0 -20
- package/dist/src/components/waka-step-permission-matrix/index.d.ts +0 -12
- package/dist/src/components/waka-time-entry-dialog/index.d.ts +0 -16
- package/dist/src/components/waka-time-tracking-flyover/index.d.ts +0 -11
- package/dist/types-BH9cQRqZ.js +0 -1
- package/dist/waka-doc-renderer-BZ2-SqyT.js +0 -3
- package/dist/waka-rich-text-editor-BJGlQgpq.js +0 -1
- package/dist/waka-rich-text-editor-BJzzxeP1.mjs +0 -361
- package/dist/waka-rich-text-editor-wnXLwvUo.js +0 -1
- package/src/components/waka-actor-badge/index.tsx +0 -34
- package/src/components/waka-actors-list/index.tsx +0 -125
- package/src/components/waka-ai-assistant-button/index.tsx +0 -31
- package/src/components/waka-document-flyover/index.tsx +0 -36
- package/src/components/waka-document-preview-popup/index.tsx +0 -103
- package/src/components/waka-hour-balance-badge/index.tsx +0 -43
- package/src/components/waka-hour-consumption-table/index.tsx +0 -72
- package/src/components/waka-hour-pack-dialog/index.tsx +0 -72
- package/src/components/waka-project-stats-header/index.tsx +0 -69
- package/src/components/waka-step-comment-bubble/index.tsx +0 -71
- package/src/components/waka-step-comment-panel/index.tsx +0 -106
- package/src/components/waka-step-permission-matrix/index.tsx +0 -65
- package/src/components/waka-time-entry-dialog/index.tsx +0 -131
- package/src/components/waka-time-tracking-flyover/index.tsx +0 -41
|
@@ -33,13 +33,11 @@ import {
|
|
|
33
33
|
X,
|
|
34
34
|
Loader2,
|
|
35
35
|
Link as LinkIcon,
|
|
36
|
+
Twitter,
|
|
37
|
+
Github,
|
|
38
|
+
Linkedin,
|
|
36
39
|
} from "lucide-react"
|
|
37
40
|
|
|
38
|
-
// Inline SVG for renamed/removed lucide icons
|
|
39
|
-
const Twitter = (props: any) => <svg {...props} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M22 4s-.7 2.1-2 3.4c1.6 10-9.4 17.3-18 11.6 2.2.1 4.4-.6 6-2C3 15.5.5 9.6 3 5c2.2 2.6 5.6 4.1 9 4-.9-4.2 4-6.6 7-3.8 1.1 0 3-1.2 3-1.2z"/></svg>
|
|
40
|
-
const Github = (props: any) => <svg {...props} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4"/><path d="M9 18c-4.51 2-5-2-7-2"/></svg>
|
|
41
|
-
const Linkedin = (props: any) => <svg {...props} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z"/><rect width="4" height="12" x="2" y="9"/><circle cx="4" cy="4" r="2"/></svg>
|
|
42
|
-
|
|
43
41
|
// ============================================
|
|
44
42
|
// TYPES
|
|
45
43
|
// ============================================
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WakaStart Editor Blocks — Custom Plate.js block elements for the WakaStart ecosystem.
|
|
3
|
+
*
|
|
4
|
+
* These blocks extend the standard Plate editor with domain-specific elements
|
|
5
|
+
* designed for PaaS project management, specification writing, and technical documentation.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { WakaUserStoryBlock, createUserStoryPlugin } from "@wakastellar/ui/editor"
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* Each block provides:
|
|
13
|
+
* - A React component for rendering in the Plate editor
|
|
14
|
+
* - A node factory function for creating Slate nodes
|
|
15
|
+
* - A plugin factory for registering with the Plate editor
|
|
16
|
+
* - Full TypeScript types for the Slate element shape
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
// ── User Story Block ────────────────────────────────────────────────────────
|
|
20
|
+
export {
|
|
21
|
+
WakaUserStoryBlock,
|
|
22
|
+
createUserStoryNodes,
|
|
23
|
+
createUserStoryPlugin,
|
|
24
|
+
USER_STORY_BLOCK_TYPE,
|
|
25
|
+
type UserStoryElement,
|
|
26
|
+
type WakaUserStoryBlockProps,
|
|
27
|
+
} from "./waka-user-story-block"
|
|
28
|
+
|
|
29
|
+
// ── Acceptance Criteria Block ───────────────────────────────────────────────
|
|
30
|
+
export {
|
|
31
|
+
WakaAcceptanceCriteriaBlock,
|
|
32
|
+
createAcceptanceCriteriaNodes,
|
|
33
|
+
createAcceptanceCriteriaPlugin,
|
|
34
|
+
ACCEPTANCE_CRITERIA_BLOCK_TYPE,
|
|
35
|
+
type AcceptanceCriterion,
|
|
36
|
+
type AcceptanceCriteriaElement,
|
|
37
|
+
type WakaAcceptanceCriteriaBlockProps,
|
|
38
|
+
} from "./waka-acceptance-criteria-block"
|
|
39
|
+
|
|
40
|
+
// ── API Endpoint Block ──────────────────────────────────────────────────────
|
|
41
|
+
export {
|
|
42
|
+
WakaApiEndpointBlock,
|
|
43
|
+
createApiEndpointNodes,
|
|
44
|
+
createApiEndpointPlugin,
|
|
45
|
+
API_ENDPOINT_BLOCK_TYPE,
|
|
46
|
+
type HttpMethod,
|
|
47
|
+
type ApiParam,
|
|
48
|
+
type ApiResponse,
|
|
49
|
+
type ApiEndpointElement,
|
|
50
|
+
type WakaApiEndpointBlockProps,
|
|
51
|
+
} from "./waka-api-endpoint-block"
|
|
52
|
+
|
|
53
|
+
// ── Diagram Block ───────────────────────────────────────────────────────────
|
|
54
|
+
export {
|
|
55
|
+
WakaDiagramBlock,
|
|
56
|
+
createDiagramNodes,
|
|
57
|
+
createDiagramPlugin,
|
|
58
|
+
DIAGRAM_BLOCK_TYPE,
|
|
59
|
+
type DiagramSyntax,
|
|
60
|
+
type DiagramElement,
|
|
61
|
+
type WakaDiagramBlockProps,
|
|
62
|
+
} from "./waka-diagram-block"
|
|
63
|
+
|
|
64
|
+
// ── AI Assist Block ─────────────────────────────────────────────────────────
|
|
65
|
+
export {
|
|
66
|
+
WakaAiAssistBlock,
|
|
67
|
+
createAiAssistNodes,
|
|
68
|
+
createAiAssistPlugin,
|
|
69
|
+
AI_ASSIST_BLOCK_TYPE,
|
|
70
|
+
type AiAssistStatus,
|
|
71
|
+
type AiAssistElement,
|
|
72
|
+
type WakaAiAssistBlockProps,
|
|
73
|
+
} from "./waka-ai-assist-block"
|
|
74
|
+
|
|
75
|
+
// ── Version Diff Block ──────────────────────────────────────────────────────
|
|
76
|
+
export {
|
|
77
|
+
WakaVersionDiffBlock,
|
|
78
|
+
createVersionDiffNodes,
|
|
79
|
+
createVersionDiffPlugin,
|
|
80
|
+
VERSION_DIFF_BLOCK_TYPE,
|
|
81
|
+
type DiffLine,
|
|
82
|
+
type DiffHunk,
|
|
83
|
+
type VersionDiffElement,
|
|
84
|
+
type WakaVersionDiffBlockProps,
|
|
85
|
+
} from "./waka-version-diff-block"
|
|
86
|
+
|
|
87
|
+
// ── Embed Block ─────────────────────────────────────────────────────────────
|
|
88
|
+
export {
|
|
89
|
+
WakaEmbedBlock,
|
|
90
|
+
createEmbedNodes,
|
|
91
|
+
createEmbedPlugin,
|
|
92
|
+
detectEmbedProvider,
|
|
93
|
+
resolveEmbedUrl,
|
|
94
|
+
EMBED_BLOCK_TYPE,
|
|
95
|
+
type EmbedProvider,
|
|
96
|
+
type EmbedElement,
|
|
97
|
+
type WakaEmbedBlockProps,
|
|
98
|
+
} from "./waka-embed-block"
|
|
99
|
+
|
|
100
|
+
// ── Code Playground Block ───────────────────────────────────────────────────
|
|
101
|
+
export {
|
|
102
|
+
WakaCodePlaygroundBlock,
|
|
103
|
+
createCodePlaygroundNodes,
|
|
104
|
+
createCodePlaygroundPlugin,
|
|
105
|
+
CODE_PLAYGROUND_BLOCK_TYPE,
|
|
106
|
+
type PlaygroundLanguage,
|
|
107
|
+
type ExecutionStatus,
|
|
108
|
+
type CodePlaygroundElement,
|
|
109
|
+
type WakaCodePlaygroundBlockProps,
|
|
110
|
+
} from "./waka-code-playground-block"
|
|
111
|
+
|
|
112
|
+
// ── Comment Thread Block ────────────────────────────────────────────────────
|
|
113
|
+
export {
|
|
114
|
+
WakaCommentThreadBlock,
|
|
115
|
+
createCommentThreadNodes,
|
|
116
|
+
createCommentThreadPlugin,
|
|
117
|
+
COMMENT_THREAD_BLOCK_TYPE,
|
|
118
|
+
type ThreadComment,
|
|
119
|
+
type ThreadStatus,
|
|
120
|
+
type CommentThreadElement,
|
|
121
|
+
type WakaCommentThreadBlockProps,
|
|
122
|
+
} from "./waka-comment-thread-block"
|
|
123
|
+
|
|
124
|
+
// ── Slash Menu with WakaStart Blocks ────────────────────────────────────────
|
|
125
|
+
export {
|
|
126
|
+
WakaSlashMenuBlock,
|
|
127
|
+
getAllWakaSlashCommands,
|
|
128
|
+
createWakaSlashPlugins,
|
|
129
|
+
WAKA_BLOCK_COMMANDS,
|
|
130
|
+
type WakaSlashBlockCommand,
|
|
131
|
+
type WakaSlashMenuBlockProps,
|
|
132
|
+
} from "./waka-slash-menu-block"
|
|
133
|
+
|
|
134
|
+
// ── Convenience: load all block plugins at once ─────────────────────────────
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Loads all WakaStart block plugins at once.
|
|
138
|
+
* Returns an array of Plate plugins to spread into your editor configuration.
|
|
139
|
+
*
|
|
140
|
+
* @example
|
|
141
|
+
* ```ts
|
|
142
|
+
* const blockPlugins = await loadAllWakaBlockPlugins()
|
|
143
|
+
* const editor = usePlateEditor({
|
|
144
|
+
* plugins: [...corePlugins, ...blockPlugins],
|
|
145
|
+
* })
|
|
146
|
+
* ```
|
|
147
|
+
*/
|
|
148
|
+
export async function loadAllWakaBlockPlugins(options?: {
|
|
149
|
+
/** Mermaid/diagram render function */
|
|
150
|
+
diagramRenderer?: (source: string, syntax: string) => Promise<string>
|
|
151
|
+
/** AI generation function */
|
|
152
|
+
aiGenerator?: (prompt: string) => Promise<string>
|
|
153
|
+
/** Code execution function */
|
|
154
|
+
codeExecutor?: (code: string, language: string) => Promise<string>
|
|
155
|
+
/** Current user ID for comments */
|
|
156
|
+
currentUserId?: string
|
|
157
|
+
/** Comment reply handler */
|
|
158
|
+
onCommentReply?: (threadId: string, content: string) => void
|
|
159
|
+
}): Promise<unknown[]> {
|
|
160
|
+
const plugins: unknown[] = []
|
|
161
|
+
|
|
162
|
+
const loaders = [
|
|
163
|
+
createUserStoryPlugin(),
|
|
164
|
+
createAcceptanceCriteriaPlugin(),
|
|
165
|
+
createApiEndpointPlugin(),
|
|
166
|
+
createDiagramPlugin(options?.diagramRenderer as never),
|
|
167
|
+
createAiAssistPlugin(options?.aiGenerator),
|
|
168
|
+
createVersionDiffPlugin(),
|
|
169
|
+
createEmbedPlugin(),
|
|
170
|
+
createCodePlaygroundPlugin(options?.codeExecutor as never),
|
|
171
|
+
createCommentThreadPlugin(options?.currentUserId, options?.onCommentReply),
|
|
172
|
+
]
|
|
173
|
+
|
|
174
|
+
const results = await Promise.allSettled(loaders)
|
|
175
|
+
for (const result of results) {
|
|
176
|
+
if (result.status === "fulfilled" && result.value) {
|
|
177
|
+
plugins.push(result.value)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return plugins
|
|
182
|
+
}
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cn } from "../../../utils/cn"
|
|
5
|
+
import type { PlateElementProps } from "../waka-editor-elements"
|
|
6
|
+
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// Types
|
|
9
|
+
// ============================================================================
|
|
10
|
+
|
|
11
|
+
export const ACCEPTANCE_CRITERIA_BLOCK_TYPE = "acceptance_criteria" as const
|
|
12
|
+
|
|
13
|
+
/** A single acceptance criterion in Gherkin format */
|
|
14
|
+
export interface AcceptanceCriterion {
|
|
15
|
+
/** Unique ID (e.g. "AC-01") */
|
|
16
|
+
id: string
|
|
17
|
+
/** Scenario name */
|
|
18
|
+
scenario: string
|
|
19
|
+
/** Given (context / precondition) */
|
|
20
|
+
given: string
|
|
21
|
+
/** When (action / trigger) */
|
|
22
|
+
when: string
|
|
23
|
+
/** Then (expected result) */
|
|
24
|
+
then: string
|
|
25
|
+
/** Whether this criterion has been validated */
|
|
26
|
+
validated: boolean
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Slate node for acceptance criteria block */
|
|
30
|
+
export interface AcceptanceCriteriaElement {
|
|
31
|
+
type: typeof ACCEPTANCE_CRITERIA_BLOCK_TYPE
|
|
32
|
+
/** Block title */
|
|
33
|
+
title: string
|
|
34
|
+
/** List of criteria */
|
|
35
|
+
criteria: AcceptanceCriterion[]
|
|
36
|
+
children: Array<{ text: string }>
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface WakaAcceptanceCriteriaBlockProps extends PlateElementProps {
|
|
40
|
+
element?: AcceptanceCriteriaElement & Record<string, unknown>
|
|
41
|
+
/** Whether the block is read-only */
|
|
42
|
+
readOnly?: boolean
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ============================================================================
|
|
46
|
+
// Gherkin keyword badge
|
|
47
|
+
// ============================================================================
|
|
48
|
+
|
|
49
|
+
function GherkinKeyword({ keyword, variant }: { keyword: string; variant: "given" | "when" | "then" }) {
|
|
50
|
+
const styles = {
|
|
51
|
+
given: "bg-violet-100 text-violet-700 dark:bg-violet-500/15 dark:text-violet-400",
|
|
52
|
+
when: "bg-amber-100 text-amber-700 dark:bg-amber-500/15 dark:text-amber-400",
|
|
53
|
+
then: "bg-emerald-100 text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-400",
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<span className={cn(
|
|
58
|
+
"inline-block text-[10px] font-bold uppercase tracking-wider px-1.5 py-0.5 rounded shrink-0 w-[52px] text-center",
|
|
59
|
+
styles[variant]
|
|
60
|
+
)}>
|
|
61
|
+
{keyword}
|
|
62
|
+
</span>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ============================================================================
|
|
67
|
+
// Criterion Row
|
|
68
|
+
// ============================================================================
|
|
69
|
+
|
|
70
|
+
interface CriterionRowProps {
|
|
71
|
+
criterion: AcceptanceCriterion
|
|
72
|
+
index: number
|
|
73
|
+
readOnly?: boolean
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function CriterionRow({ criterion, index, readOnly }: CriterionRowProps) {
|
|
77
|
+
const [isExpanded, setIsExpanded] = React.useState(true)
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<div className={cn(
|
|
81
|
+
"rounded-lg border transition-all duration-200",
|
|
82
|
+
criterion.validated
|
|
83
|
+
? "border-emerald-200 dark:border-emerald-800/40 bg-emerald-50/30 dark:bg-emerald-950/10"
|
|
84
|
+
: "border-border bg-card/50"
|
|
85
|
+
)}>
|
|
86
|
+
{/* Header */}
|
|
87
|
+
<div className="flex items-center gap-3 px-3 py-2">
|
|
88
|
+
{/* Checkbox */}
|
|
89
|
+
<button
|
|
90
|
+
type="button"
|
|
91
|
+
disabled={readOnly}
|
|
92
|
+
className={cn(
|
|
93
|
+
"flex-shrink-0 h-5 w-5 rounded-md border-2 flex items-center justify-center transition-all duration-200",
|
|
94
|
+
criterion.validated
|
|
95
|
+
? "bg-emerald-500 border-emerald-500 text-white"
|
|
96
|
+
: "border-border hover:border-emerald-400",
|
|
97
|
+
readOnly && "cursor-default"
|
|
98
|
+
)}
|
|
99
|
+
aria-label={criterion.validated ? "Mark as not validated" : "Mark as validated"}
|
|
100
|
+
aria-checked={criterion.validated}
|
|
101
|
+
role="checkbox"
|
|
102
|
+
>
|
|
103
|
+
{criterion.validated && (
|
|
104
|
+
<svg className="h-3 w-3" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={3}>
|
|
105
|
+
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
|
|
106
|
+
</svg>
|
|
107
|
+
)}
|
|
108
|
+
</button>
|
|
109
|
+
|
|
110
|
+
{/* ID badge */}
|
|
111
|
+
<span className="text-[10px] font-mono font-bold text-muted-foreground bg-muted px-1.5 py-0.5 rounded shrink-0">
|
|
112
|
+
{criterion.id}
|
|
113
|
+
</span>
|
|
114
|
+
|
|
115
|
+
{/* Scenario name */}
|
|
116
|
+
<span className={cn(
|
|
117
|
+
"text-sm font-medium flex-1 min-w-0 truncate",
|
|
118
|
+
criterion.validated && "line-through text-muted-foreground"
|
|
119
|
+
)}>
|
|
120
|
+
{criterion.scenario || "Unnamed scenario"}
|
|
121
|
+
</span>
|
|
122
|
+
|
|
123
|
+
{/* Toggle */}
|
|
124
|
+
<button
|
|
125
|
+
type="button"
|
|
126
|
+
onClick={() => setIsExpanded(!isExpanded)}
|
|
127
|
+
className="text-muted-foreground hover:text-foreground transition-colors p-0.5"
|
|
128
|
+
aria-label={isExpanded ? "Collapse" : "Expand"}
|
|
129
|
+
>
|
|
130
|
+
<svg
|
|
131
|
+
className={cn("h-3.5 w-3.5 transition-transform duration-200", isExpanded && "rotate-180")}
|
|
132
|
+
viewBox="0 0 24 24"
|
|
133
|
+
fill="none"
|
|
134
|
+
stroke="currentColor"
|
|
135
|
+
strokeWidth={2}
|
|
136
|
+
>
|
|
137
|
+
<path strokeLinecap="round" strokeLinejoin="round" d="M19 9l-7 7-7-7" />
|
|
138
|
+
</svg>
|
|
139
|
+
</button>
|
|
140
|
+
</div>
|
|
141
|
+
|
|
142
|
+
{/* Gherkin steps */}
|
|
143
|
+
{isExpanded && (
|
|
144
|
+
<div className="px-3 pb-3 pt-1 space-y-1.5 border-t border-border/50">
|
|
145
|
+
<div className="flex items-start gap-2">
|
|
146
|
+
<GherkinKeyword keyword="Given" variant="given" />
|
|
147
|
+
<span className="text-sm text-foreground/80 pt-0.5">
|
|
148
|
+
{criterion.given || <span className="italic text-muted-foreground">precondition...</span>}
|
|
149
|
+
</span>
|
|
150
|
+
</div>
|
|
151
|
+
<div className="flex items-start gap-2">
|
|
152
|
+
<GherkinKeyword keyword="When" variant="when" />
|
|
153
|
+
<span className="text-sm text-foreground/80 pt-0.5">
|
|
154
|
+
{criterion.when || <span className="italic text-muted-foreground">action...</span>}
|
|
155
|
+
</span>
|
|
156
|
+
</div>
|
|
157
|
+
<div className="flex items-start gap-2">
|
|
158
|
+
<GherkinKeyword keyword="Then" variant="then" />
|
|
159
|
+
<span className="text-sm text-foreground/80 pt-0.5">
|
|
160
|
+
{criterion.then || <span className="italic text-muted-foreground">expected result...</span>}
|
|
161
|
+
</span>
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
)}
|
|
165
|
+
</div>
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ============================================================================
|
|
170
|
+
// Element Component
|
|
171
|
+
// ============================================================================
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* WakaAcceptanceCriteriaBlock - A Plate.js block for acceptance criteria in
|
|
175
|
+
* Gherkin format (Given/When/Then). Each criterion has a checkbox for
|
|
176
|
+
* validation tracking.
|
|
177
|
+
*
|
|
178
|
+
* Register in Plate editor:
|
|
179
|
+
* ```ts
|
|
180
|
+
* components: {
|
|
181
|
+
* [ACCEPTANCE_CRITERIA_BLOCK_TYPE]: WakaAcceptanceCriteriaBlock,
|
|
182
|
+
* }
|
|
183
|
+
* ```
|
|
184
|
+
*/
|
|
185
|
+
export function WakaAcceptanceCriteriaBlock({
|
|
186
|
+
attributes,
|
|
187
|
+
children,
|
|
188
|
+
element,
|
|
189
|
+
className,
|
|
190
|
+
}: WakaAcceptanceCriteriaBlockProps) {
|
|
191
|
+
const el = element as AcceptanceCriteriaElement | undefined
|
|
192
|
+
const criteria = el?.criteria || []
|
|
193
|
+
const title = el?.title || "Acceptance Criteria"
|
|
194
|
+
|
|
195
|
+
const validatedCount = criteria.filter((c) => c.validated).length
|
|
196
|
+
const totalCount = criteria.length
|
|
197
|
+
const progress = totalCount > 0 ? (validatedCount / totalCount) * 100 : 0
|
|
198
|
+
|
|
199
|
+
return (
|
|
200
|
+
<div
|
|
201
|
+
{...attributes}
|
|
202
|
+
contentEditable={false}
|
|
203
|
+
className={cn(
|
|
204
|
+
"my-4 rounded-lg overflow-hidden",
|
|
205
|
+
"border border-border",
|
|
206
|
+
"bg-card/30",
|
|
207
|
+
"shadow-sm",
|
|
208
|
+
className
|
|
209
|
+
)}
|
|
210
|
+
>
|
|
211
|
+
{/* Header */}
|
|
212
|
+
<div className="flex items-center justify-between px-4 py-2.5 bg-muted/30 border-b border-border">
|
|
213
|
+
<div className="flex items-center gap-2">
|
|
214
|
+
<svg
|
|
215
|
+
className="h-4 w-4 text-emerald-600 dark:text-emerald-400"
|
|
216
|
+
viewBox="0 0 24 24"
|
|
217
|
+
fill="none"
|
|
218
|
+
stroke="currentColor"
|
|
219
|
+
strokeWidth={2}
|
|
220
|
+
aria-hidden="true"
|
|
221
|
+
>
|
|
222
|
+
<path strokeLinecap="round" strokeLinejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4" />
|
|
223
|
+
</svg>
|
|
224
|
+
<span className="text-xs font-bold uppercase tracking-wider text-foreground">
|
|
225
|
+
{title}
|
|
226
|
+
</span>
|
|
227
|
+
</div>
|
|
228
|
+
|
|
229
|
+
{/* Progress */}
|
|
230
|
+
<div className="flex items-center gap-2">
|
|
231
|
+
<span className="text-[10px] font-medium text-muted-foreground">
|
|
232
|
+
{validatedCount}/{totalCount}
|
|
233
|
+
</span>
|
|
234
|
+
<div className="w-16 h-1.5 bg-muted rounded-full overflow-hidden">
|
|
235
|
+
<div
|
|
236
|
+
className="h-full bg-emerald-500 rounded-full transition-all duration-500"
|
|
237
|
+
style={{ width: `${progress}%` }}
|
|
238
|
+
/>
|
|
239
|
+
</div>
|
|
240
|
+
</div>
|
|
241
|
+
</div>
|
|
242
|
+
|
|
243
|
+
{/* Criteria list */}
|
|
244
|
+
<div className="p-3 space-y-2">
|
|
245
|
+
{criteria.length > 0 ? (
|
|
246
|
+
criteria.map((criterion, index) => (
|
|
247
|
+
<CriterionRow
|
|
248
|
+
key={criterion.id}
|
|
249
|
+
criterion={criterion}
|
|
250
|
+
index={index}
|
|
251
|
+
readOnly
|
|
252
|
+
/>
|
|
253
|
+
))
|
|
254
|
+
) : (
|
|
255
|
+
<div className="py-6 text-center text-sm text-muted-foreground italic">
|
|
256
|
+
No acceptance criteria defined yet.
|
|
257
|
+
</div>
|
|
258
|
+
)}
|
|
259
|
+
</div>
|
|
260
|
+
|
|
261
|
+
{/* Hidden Slate children */}
|
|
262
|
+
<div className="hidden">{children}</div>
|
|
263
|
+
</div>
|
|
264
|
+
)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
WakaAcceptanceCriteriaBlock.displayName = "WakaAcceptanceCriteriaBlock"
|
|
268
|
+
|
|
269
|
+
// ============================================================================
|
|
270
|
+
// Node Factory
|
|
271
|
+
// ============================================================================
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Creates Slate nodes for an acceptance criteria block.
|
|
275
|
+
*/
|
|
276
|
+
export function createAcceptanceCriteriaNodes(options?: {
|
|
277
|
+
title?: string
|
|
278
|
+
criteria?: AcceptanceCriterion[]
|
|
279
|
+
}): AcceptanceCriteriaElement[] {
|
|
280
|
+
return [
|
|
281
|
+
{
|
|
282
|
+
type: ACCEPTANCE_CRITERIA_BLOCK_TYPE,
|
|
283
|
+
title: options?.title || "Acceptance Criteria",
|
|
284
|
+
criteria: options?.criteria || [
|
|
285
|
+
{
|
|
286
|
+
id: "AC-01",
|
|
287
|
+
scenario: "Nominal case",
|
|
288
|
+
given: "The user is authenticated",
|
|
289
|
+
when: "They perform the action",
|
|
290
|
+
then: "The expected result is displayed",
|
|
291
|
+
validated: false,
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
id: "AC-02",
|
|
295
|
+
scenario: "Error case",
|
|
296
|
+
given: "The service is unavailable",
|
|
297
|
+
when: "They perform the action",
|
|
298
|
+
then: "An error message is displayed",
|
|
299
|
+
validated: false,
|
|
300
|
+
},
|
|
301
|
+
],
|
|
302
|
+
children: [{ text: "" }],
|
|
303
|
+
},
|
|
304
|
+
]
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Creates a Plate plugin for acceptance criteria blocks.
|
|
309
|
+
*/
|
|
310
|
+
export async function createAcceptanceCriteriaPlugin() {
|
|
311
|
+
try {
|
|
312
|
+
const { createPlatePlugin } = await import("platejs/react")
|
|
313
|
+
return createPlatePlugin({
|
|
314
|
+
key: ACCEPTANCE_CRITERIA_BLOCK_TYPE,
|
|
315
|
+
node: {
|
|
316
|
+
isElement: true,
|
|
317
|
+
isVoid: true,
|
|
318
|
+
type: ACCEPTANCE_CRITERIA_BLOCK_TYPE,
|
|
319
|
+
component: WakaAcceptanceCriteriaBlock,
|
|
320
|
+
},
|
|
321
|
+
})
|
|
322
|
+
} catch {
|
|
323
|
+
console.warn("[WakaAcceptanceCriteriaBlock] platejs not installed")
|
|
324
|
+
return null
|
|
325
|
+
}
|
|
326
|
+
}
|