@umodeapporg/ui 0.30.0 → 0.31.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 (45) hide show
  1. package/dist/assets/umode-ui.css +32 -0
  2. package/dist/components/Form/ControlledTabs.js +37 -23
  3. package/dist/components/Form/ControlledTabs.js.map +1 -1
  4. package/dist/components/Form/ControlledTabsPrimitives.js.map +1 -1
  5. package/dist/components/UI/Flow/FlowAnnotationDraftPreview.d.ts +6 -0
  6. package/dist/components/UI/Flow/FlowAnnotationDraftPreview.js +65 -0
  7. package/dist/components/UI/Flow/FlowAnnotationDraftPreview.js.map +1 -0
  8. package/dist/components/UI/Flow/FlowAnnotationNode.d.ts +34 -0
  9. package/dist/components/UI/Flow/FlowAnnotationNode.js +231 -0
  10. package/dist/components/UI/Flow/FlowAnnotationNode.js.map +1 -0
  11. package/dist/components/UI/Flow/FlowAnnotationToolbar.d.ts +32 -0
  12. package/dist/components/UI/Flow/FlowAnnotationToolbar.js +298 -0
  13. package/dist/components/UI/Flow/FlowAnnotationToolbar.js.map +1 -0
  14. package/dist/components/UI/Flow/FlowCanvas.d.ts +2 -0
  15. package/dist/components/UI/Flow/FlowCanvas.js +278 -20
  16. package/dist/components/UI/Flow/FlowCanvas.js.map +1 -1
  17. package/dist/components/UI/Flow/FlowInspector.js +6 -5
  18. package/dist/components/UI/Flow/FlowInspector.js.map +1 -1
  19. package/dist/components/UI/Flow/FlowSelectionBar.d.ts +10 -0
  20. package/dist/components/UI/Flow/FlowSelectionBar.js +56 -0
  21. package/dist/components/UI/Flow/FlowSelectionBar.js.map +1 -0
  22. package/dist/components/UI/Flow/flowAnnotations.d.ts +38 -0
  23. package/dist/components/UI/Flow/flowAnnotations.js +123 -0
  24. package/dist/components/UI/Flow/flowAnnotations.js.map +1 -0
  25. package/dist/components/UI/Flow/flowEditorStore.d.ts +29 -5
  26. package/dist/components/UI/Flow/flowEditorStore.js +123 -23
  27. package/dist/components/UI/Flow/flowEditorStore.js.map +1 -1
  28. package/dist/components/UI/Flow/flowTypes.d.ts +30 -0
  29. package/dist/components/UI/Flow/index.d.ts +2 -1
  30. package/dist/components/UI/Flow/index.js +5 -1
  31. package/dist/components/UI/Flow/index.js.map +1 -1
  32. package/dist/components/UI/Flow/useFlowAnnotationDrawing.d.ts +25 -0
  33. package/dist/components/UI/Flow/useFlowAnnotationDrawing.js +117 -0
  34. package/dist/components/UI/Flow/useFlowAnnotationDrawing.js.map +1 -0
  35. package/dist/locales/en/ui.json.d.ts +41 -0
  36. package/dist/locales/en/ui.json.js +1 -1
  37. package/dist/locales/index.d.ts +82 -0
  38. package/dist/locales/pt/ui.json.d.ts +41 -0
  39. package/dist/locales/pt/ui.json.js +1 -1
  40. package/package.json +1 -1
  41. package/src/assets/fonts/LICENSE-Virgil.txt +45 -0
  42. package/src/assets/fonts/virgil.woff2 +0 -0
  43. package/src/assets/styles/base.css +10 -0
  44. package/src/assets/styles/theme.css +15 -6
  45. package/tailwind.preset.js +1 -1
@@ -0,0 +1,231 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useRef, useEffect } from "react";
3
+ import { mergeClasses } from "../../../helpers/mergeClasses.js";
4
+ import { NodeResizer } from "@xyflow/react";
5
+ import { FLOW_ANNOTATION_MIN_SIZE, strokeWidthOf, smoothStrokePath } from "./flowAnnotations.js";
6
+ const FLOW_ANNOTATION_TONE_CLASS = {
7
+ neutral: "text-foreground-muted",
8
+ brand: "text-brand-emphasis",
9
+ info: "text-info-emphasis",
10
+ success: "text-success-emphasis",
11
+ warning: "text-warning-emphasis",
12
+ danger: "text-danger-emphasis"
13
+ };
14
+ const FLOW_ANNOTATION_STROKE_CLASS = {
15
+ neutral: "stroke-foreground-muted",
16
+ brand: "stroke-brand",
17
+ info: "stroke-info",
18
+ success: "stroke-success",
19
+ warning: "stroke-warning",
20
+ danger: "stroke-danger"
21
+ };
22
+ const ARROWHEAD_CLASS = {
23
+ neutral: "fill-foreground-muted",
24
+ brand: "fill-brand",
25
+ info: "fill-info",
26
+ success: "fill-success",
27
+ warning: "fill-warning",
28
+ danger: "fill-danger"
29
+ };
30
+ const FLOW_ANNOTATION_SWATCH_CLASS = {
31
+ neutral: "bg-foreground-muted",
32
+ brand: "bg-brand",
33
+ info: "bg-info",
34
+ success: "bg-success",
35
+ warning: "bg-warning",
36
+ danger: "bg-danger"
37
+ };
38
+ const STEP_TONE_CLASS = {
39
+ neutral: "bg-ink text-ink-fg",
40
+ brand: "bg-brand text-brand-fg",
41
+ info: "bg-info text-info-fg",
42
+ success: "bg-success text-success-fg",
43
+ warning: "bg-warning text-warning-fg",
44
+ danger: "bg-danger text-danger-fg"
45
+ };
46
+ const FLOW_ANNOTATION_TEXT_SIZE_CLASS = {
47
+ thin: "text-base",
48
+ medium: "text-xl",
49
+ thick: "text-3xl"
50
+ };
51
+ const HIT_BAND_PX = 12;
52
+ const HIT_POINTER_EVENTS = "stroke";
53
+ const MARKER_ORIENT = "auto-start-reverse";
54
+ const RESIZE_HANDLE_CLASS = "umode-flow-annotation-handle";
55
+ const RESIZE_LINE_CLASS = "umode-flow-annotation-line";
56
+ const TEXTAREA_WRAP = "off";
57
+ const FOCUS_ATTEMPTS = 10;
58
+ const EMPTY_LINE_FILLER = "​";
59
+ const flowAnnotationMarkerUrl = (markerId) => `url(#${markerId})`;
60
+ const FlowAnnotationArrowMarker = ({
61
+ markerId,
62
+ tone
63
+ }) => /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx("marker", { id: markerId, viewBox: "0 0 10 10", refX: 7, refY: 5, markerWidth: 4, markerHeight: 4, orient: MARKER_ORIENT, children: /* @__PURE__ */ jsx("path", { d: "M 0 0 L 10 5 L 0 10 z", className: ARROWHEAD_CLASS[tone] }) }) });
64
+ const AnnotationShape = ({ annotation, markerId }) => {
65
+ const { width, height } = annotation.size;
66
+ const strokeWidth = strokeWidthOf(annotation);
67
+ const inset = strokeWidth / 2;
68
+ const [start, end] = annotation.points ?? [];
69
+ const hasArrowhead = annotation.kind === "arrow";
70
+ const shape = (() => {
71
+ if (annotation.kind === "rectangle")
72
+ return /* @__PURE__ */ jsx(
73
+ "rect",
74
+ {
75
+ x: inset,
76
+ y: inset,
77
+ width: Math.max(width - strokeWidth, 0),
78
+ height: Math.max(height - strokeWidth, 0),
79
+ rx: 4
80
+ }
81
+ );
82
+ if (annotation.kind === "ellipse")
83
+ return /* @__PURE__ */ jsx(
84
+ "ellipse",
85
+ {
86
+ cx: width / 2,
87
+ cy: height / 2,
88
+ rx: Math.max(width / 2 - inset, 0),
89
+ ry: Math.max(height / 2 - inset, 0)
90
+ }
91
+ );
92
+ if ((annotation.kind === "arrow" || annotation.kind === "line") && start && end)
93
+ return /* @__PURE__ */ jsx("line", { x1: start.x, y1: start.y, x2: end.x, y2: end.y });
94
+ return /* @__PURE__ */ jsx("path", { d: smoothStrokePath(annotation.points ?? []) });
95
+ })();
96
+ return /* @__PURE__ */ jsxs("svg", { "aria-hidden": true, className: "pointer-events-none absolute inset-0 h-full w-full overflow-visible", fill: "none", children: [
97
+ hasArrowhead && /* @__PURE__ */ jsx(FlowAnnotationArrowMarker, { markerId, tone: annotation.tone }),
98
+ /* @__PURE__ */ jsx(
99
+ "g",
100
+ {
101
+ className: FLOW_ANNOTATION_STROKE_CLASS[annotation.tone],
102
+ strokeWidth,
103
+ strokeLinecap: "round",
104
+ strokeLinejoin: "round",
105
+ markerEnd: hasArrowhead ? flowAnnotationMarkerUrl(markerId) : void 0,
106
+ children: shape
107
+ }
108
+ ),
109
+ /* @__PURE__ */ jsx("g", { stroke: "transparent", strokeWidth: strokeWidth + HIT_BAND_PX, pointerEvents: HIT_POINTER_EVENTS, children: shape })
110
+ ] });
111
+ };
112
+ const FlowAnnotationNode = ({ id, data, selected }) => {
113
+ const { annotation, readOnly, editing, placeholder } = data;
114
+ const textareaRef = useRef(null);
115
+ useEffect(() => {
116
+ if (!editing) return void 0;
117
+ let frame = 0;
118
+ let attemptsLeft = FOCUS_ATTEMPTS;
119
+ const focusTextarea = () => {
120
+ const textarea = textareaRef.current;
121
+ if (!textarea) return;
122
+ textarea.focus();
123
+ if (document.activeElement === textarea) {
124
+ textarea.setSelectionRange(textarea.value.length, textarea.value.length);
125
+ return;
126
+ }
127
+ attemptsLeft -= 1;
128
+ if (attemptsLeft > 0) frame = window.requestAnimationFrame(focusTextarea);
129
+ };
130
+ focusTextarea();
131
+ return () => window.cancelAnimationFrame(frame);
132
+ }, [editing]);
133
+ const markerId = `umode-flow-annotation-arrow-${id}`;
134
+ const isText = annotation.kind === "text";
135
+ const toneClass = FLOW_ANNOTATION_TONE_CLASS[annotation.tone] ?? FLOW_ANNOTATION_TONE_CLASS.neutral;
136
+ const textSizeClass = FLOW_ANNOTATION_TEXT_SIZE_CLASS[annotation.stroke ?? "medium"];
137
+ const textMetricsClass = mergeClasses("umode-flow-annotation-text whitespace-pre", textSizeClass, "leading-snug");
138
+ const canResize = !readOnly && selected && !editing && !isText && annotation.kind !== "step";
139
+ const content = (() => {
140
+ if (annotation.kind === "step")
141
+ return /* @__PURE__ */ jsx(
142
+ "div",
143
+ {
144
+ className: mergeClasses(
145
+ "umode-flow-annotation-text pointer-events-auto flex h-full w-full items-center justify-center rounded-full text-lg leading-none shadow-sm",
146
+ STEP_TONE_CLASS[annotation.tone] ?? STEP_TONE_CLASS.danger
147
+ ),
148
+ children: annotation.step
149
+ }
150
+ );
151
+ if (!isText) return /* @__PURE__ */ jsx(AnnotationShape, { annotation, markerId });
152
+ const text = annotation.text ?? "";
153
+ return /* @__PURE__ */ jsxs(
154
+ "div",
155
+ {
156
+ role: "presentation",
157
+ onDoubleClick: readOnly || editing ? void 0 : data.onEditStart,
158
+ className: mergeClasses(
159
+ "umode-flow-annotation-text pointer-events-auto relative inline-block whitespace-pre leading-snug",
160
+ textSizeClass
161
+ ),
162
+ children: [
163
+ /* @__PURE__ */ jsx(
164
+ "span",
165
+ {
166
+ "aria-hidden": editing,
167
+ className: mergeClasses(textMetricsClass, "block min-w-[0.5rem]", editing && "invisible"),
168
+ children: `${text}${EMPTY_LINE_FILLER}`
169
+ }
170
+ ),
171
+ editing && /* @__PURE__ */ jsx(
172
+ "textarea",
173
+ {
174
+ ref: textareaRef,
175
+ "aria-label": placeholder,
176
+ value: text,
177
+ wrap: TEXTAREA_WRAP,
178
+ onChange: (event) => data.onTextChange(event.target.value),
179
+ onBlur: data.onEditEnd,
180
+ onKeyDown: (event) => {
181
+ if (event.key === "Escape") event.currentTarget.blur();
182
+ },
183
+ className: mergeClasses(
184
+ textMetricsClass,
185
+ "nodrag nopan nowheel absolute inset-0 h-full w-full select-text resize-none overflow-hidden border-0 bg-transparent p-0 text-current focus:outline-none focus:ring-0"
186
+ )
187
+ }
188
+ )
189
+ ]
190
+ }
191
+ );
192
+ })();
193
+ return /* @__PURE__ */ jsxs(
194
+ "div",
195
+ {
196
+ className: mergeClasses(
197
+ isText ? "relative inline-block" : "relative h-full w-full",
198
+ toneClass,
199
+ selected && !editing && "rounded-md outline-dashed outline-2 outline-offset-4 outline-border-emphasis"
200
+ ),
201
+ children: [
202
+ /* @__PURE__ */ jsx(
203
+ NodeResizer,
204
+ {
205
+ isVisible: canResize,
206
+ minWidth: FLOW_ANNOTATION_MIN_SIZE,
207
+ minHeight: FLOW_ANNOTATION_MIN_SIZE,
208
+ handleClassName: RESIZE_HANDLE_CLASS,
209
+ lineClassName: RESIZE_LINE_CLASS,
210
+ onResizeStart: data.onResizeStart,
211
+ onResize: (_event, params) => data.onResize({
212
+ position: { x: params.x, y: params.y },
213
+ size: { width: params.width, height: params.height }
214
+ })
215
+ }
216
+ ),
217
+ content
218
+ ]
219
+ }
220
+ );
221
+ };
222
+ export {
223
+ FLOW_ANNOTATION_STROKE_CLASS,
224
+ FLOW_ANNOTATION_SWATCH_CLASS,
225
+ FLOW_ANNOTATION_TEXT_SIZE_CLASS,
226
+ FLOW_ANNOTATION_TONE_CLASS,
227
+ FlowAnnotationArrowMarker,
228
+ FlowAnnotationNode as default,
229
+ flowAnnotationMarkerUrl
230
+ };
231
+ //# sourceMappingURL=FlowAnnotationNode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FlowAnnotationNode.js","sources":["../../../../src/components/UI/Flow/FlowAnnotationNode.tsx"],"sourcesContent":["import { useEffect, useRef } from 'react';\n\nimport { mergeClasses } from '@/helpers/mergeClasses';\nimport { type Node, type NodeProps, NodeResizer } from '@xyflow/react';\n\nimport { FLOW_ANNOTATION_MIN_SIZE, smoothStrokePath, strokeWidthOf } from './flowAnnotations';\nimport type { IFlowAnnotationGeometry } from './flowEditorStore';\nimport type { IFlowAnnotation, TFlowAnnotationStroke, TFlowAnnotationTone } from './flowTypes';\n\n/** Text ink: the tone's `-emphasis`, the variant validated for reading. */\nexport const FLOW_ANNOTATION_TONE_CLASS: Record<TFlowAnnotationTone, string> = {\n neutral: 'text-foreground-muted',\n brand: 'text-brand-emphasis',\n info: 'text-info-emphasis',\n success: 'text-success-emphasis',\n warning: 'text-warning-emphasis',\n danger: 'text-danger-emphasis',\n};\n\n/** Strokes are graphics, not text: they take the tone's fill colour, validated at 3:1 for exactly that. */\nexport const FLOW_ANNOTATION_STROKE_CLASS: Record<TFlowAnnotationTone, string> = {\n neutral: 'stroke-foreground-muted',\n brand: 'stroke-brand',\n info: 'stroke-info',\n success: 'stroke-success',\n warning: 'stroke-warning',\n danger: 'stroke-danger',\n};\n\nconst ARROWHEAD_CLASS: Record<TFlowAnnotationTone, string> = {\n neutral: 'fill-foreground-muted',\n brand: 'fill-brand',\n info: 'fill-info',\n success: 'fill-success',\n warning: 'fill-warning',\n danger: 'fill-danger',\n};\n\nexport const FLOW_ANNOTATION_SWATCH_CLASS: Record<TFlowAnnotationTone, string> = {\n neutral: 'bg-foreground-muted',\n brand: 'bg-brand',\n info: 'bg-info',\n success: 'bg-success',\n warning: 'bg-warning',\n danger: 'bg-danger',\n};\n\n/** A step is a solid badge: the fill is the tone and the number takes the tone's own ink. */\nconst STEP_TONE_CLASS: Record<TFlowAnnotationTone, string> = {\n neutral: 'bg-ink text-ink-fg',\n brand: 'bg-brand text-brand-fg',\n info: 'bg-info text-info-fg',\n success: 'bg-success text-success-fg',\n warning: 'bg-warning text-warning-fg',\n danger: 'bg-danger text-danger-fg',\n};\n\nexport const FLOW_ANNOTATION_TEXT_SIZE_CLASS: Record<TFlowAnnotationStroke, string> = {\n thin: 'text-base',\n medium: 'text-xl',\n thick: 'text-3xl',\n};\n\n/** The invisible band around a stroke that still catches the pointer: a thin line is hard to grab. */\nconst HIT_BAND_PX = 12;\nconst HIT_POINTER_EVENTS = 'stroke';\nconst MARKER_ORIENT = 'auto-start-reverse';\nconst RESIZE_HANDLE_CLASS = 'umode-flow-annotation-handle';\nconst RESIZE_LINE_CLASS = 'umode-flow-annotation-line';\nconst TEXTAREA_WRAP = 'off';\nconst FOCUS_ATTEMPTS = 10;\n/** Keeps a trailing empty line (or an empty note) one line tall in the hidden mirror. */\nconst EMPTY_LINE_FILLER = '​';\n\nexport const flowAnnotationMarkerUrl = (markerId: string): string => `url(#${markerId})`;\n\n/** The arrowhead, painted with the stroke colour of the arrow that uses it. */\nexport const FlowAnnotationArrowMarker: React.FC<{ markerId: string; tone: TFlowAnnotationTone }> = ({\n markerId,\n tone,\n}) => (\n <defs>\n <marker id={markerId} viewBox=\"0 0 10 10\" refX={7} refY={5} markerWidth={4} markerHeight={4} orient={MARKER_ORIENT}>\n <path d=\"M 0 0 L 10 5 L 0 10 z\" className={ARROWHEAD_CLASS[tone]} />\n </marker>\n </defs>\n);\n\nexport interface IFlowAnnotationNodeData extends Record<string, unknown> {\n annotation: IFlowAnnotation;\n readOnly: boolean;\n editing: boolean;\n placeholder: string;\n onTextChange: (text: string) => void;\n onEditStart: () => void;\n onEditEnd: () => void;\n onResizeStart: () => void;\n onResize: (geometry: IFlowAnnotationGeometry) => void;\n}\n\nexport type TFlowAnnotationNode = Node<IFlowAnnotationNodeData, 'flowAnnotation'>;\n\ninterface IAnnotationShapeProps {\n annotation: IFlowAnnotation;\n markerId: string;\n}\n\n/** Draws the stroke twice: once visible, once wide and transparent to catch the pointer. */\nconst AnnotationShape: React.FC<IAnnotationShapeProps> = ({ annotation, markerId }) => {\n const { width, height } = annotation.size;\n const strokeWidth = strokeWidthOf(annotation);\n const inset = strokeWidth / 2;\n const [start, end] = annotation.points ?? [];\n const hasArrowhead = annotation.kind === 'arrow';\n\n const shape = (() => {\n if (annotation.kind === 'rectangle')\n return (\n <rect\n x={inset}\n y={inset}\n width={Math.max(width - strokeWidth, 0)}\n height={Math.max(height - strokeWidth, 0)}\n rx={4}\n />\n );\n if (annotation.kind === 'ellipse')\n return (\n <ellipse\n cx={width / 2}\n cy={height / 2}\n rx={Math.max(width / 2 - inset, 0)}\n ry={Math.max(height / 2 - inset, 0)}\n />\n );\n if ((annotation.kind === 'arrow' || annotation.kind === 'line') && start && end)\n return <line x1={start.x} y1={start.y} x2={end.x} y2={end.y} />;\n return <path d={smoothStrokePath(annotation.points ?? [])} />;\n })();\n\n return (\n <svg aria-hidden className=\"pointer-events-none absolute inset-0 h-full w-full overflow-visible\" fill=\"none\">\n {hasArrowhead && <FlowAnnotationArrowMarker markerId={markerId} tone={annotation.tone} />}\n <g\n className={FLOW_ANNOTATION_STROKE_CLASS[annotation.tone]}\n strokeWidth={strokeWidth}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n markerEnd={hasArrowhead ? flowAnnotationMarkerUrl(markerId) : undefined}\n >\n {shape}\n </g>\n <g stroke=\"transparent\" strokeWidth={strokeWidth + HIT_BAND_PX} pointerEvents={HIT_POINTER_EVENTS}>\n {shape}\n </g>\n </svg>\n );\n};\n\n/**\n * One annotation on the canvas. Shapes only catch the pointer on their stroke, so a frame drawn\n * around blocks never steals the click meant for what is inside it; once selected, the whole box\n * can be dragged. Text is written in Virgil, the hand-drawn face of Excalidraw.\n */\nconst FlowAnnotationNode: React.FC<NodeProps<TFlowAnnotationNode>> = ({ id, data, selected }) => {\n const { annotation, readOnly, editing, placeholder } = data;\n const textareaRef = useRef<HTMLTextAreaElement>(null);\n\n // xyflow keeps a new node `visibility: hidden` until it is measured, and a hidden field refuses\n // focus: keep trying for a few frames, or the keys typed next land on the canvas as tool shortcuts.\n useEffect(() => {\n if (!editing) return undefined;\n let frame = 0;\n let attemptsLeft = FOCUS_ATTEMPTS;\n const focusTextarea = () => {\n const textarea = textareaRef.current;\n if (!textarea) return;\n textarea.focus();\n if (document.activeElement === textarea) {\n textarea.setSelectionRange(textarea.value.length, textarea.value.length);\n return;\n }\n attemptsLeft -= 1;\n if (attemptsLeft > 0) frame = window.requestAnimationFrame(focusTextarea);\n };\n focusTextarea();\n return () => window.cancelAnimationFrame(frame);\n }, [editing]);\n\n const markerId = `umode-flow-annotation-arrow-${id}`;\n const isText = annotation.kind === 'text';\n const toneClass = FLOW_ANNOTATION_TONE_CLASS[annotation.tone] ?? FLOW_ANNOTATION_TONE_CLASS.neutral;\n const textSizeClass = FLOW_ANNOTATION_TEXT_SIZE_CLASS[annotation.stroke ?? 'medium'];\n // The mirror and the field must measure the same: one face, one size, one line height, no wrap.\n const textMetricsClass = mergeClasses('umode-flow-annotation-text whitespace-pre', textSizeClass, 'leading-snug');\n const canResize = !readOnly && selected && !editing && !isText && annotation.kind !== 'step';\n\n const content = (() => {\n if (annotation.kind === 'step')\n return (\n <div\n className={mergeClasses(\n 'umode-flow-annotation-text pointer-events-auto flex h-full w-full items-center justify-center rounded-full text-lg leading-none shadow-sm',\n STEP_TONE_CLASS[annotation.tone] ?? STEP_TONE_CLASS.danger,\n )}\n >\n {annotation.step}\n </div>\n );\n if (!isText) return <AnnotationShape annotation={annotation} markerId={markerId} />;\n\n // Like Excalidraw: no box, no placeholder, only the caret. The hidden mirror gives the note the\n // size of what is typed, so it grows as you write and only breaks a line on Enter.\n const text = annotation.text ?? '';\n return (\n <div\n role=\"presentation\"\n onDoubleClick={readOnly || editing ? undefined : data.onEditStart}\n className={mergeClasses(\n 'umode-flow-annotation-text pointer-events-auto relative inline-block whitespace-pre leading-snug',\n textSizeClass,\n )}\n >\n <span\n aria-hidden={editing}\n className={mergeClasses(textMetricsClass, 'block min-w-[0.5rem]', editing && 'invisible')}\n >\n {`${text}${EMPTY_LINE_FILLER}`}\n </span>\n {editing && (\n <textarea\n ref={textareaRef}\n aria-label={placeholder}\n value={text}\n wrap={TEXTAREA_WRAP}\n onChange={(event) => data.onTextChange(event.target.value)}\n onBlur={data.onEditEnd}\n onKeyDown={(event) => {\n if (event.key === 'Escape') event.currentTarget.blur();\n }}\n className={mergeClasses(\n textMetricsClass,\n 'nodrag nopan nowheel absolute inset-0 h-full w-full select-text resize-none overflow-hidden border-0 bg-transparent p-0 text-current focus:outline-none focus:ring-0',\n )}\n />\n )}\n </div>\n );\n })();\n\n return (\n <div\n className={mergeClasses(\n isText ? 'relative inline-block' : 'relative h-full w-full',\n toneClass,\n selected && !editing && 'rounded-md outline-dashed outline-2 outline-offset-4 outline-border-emphasis',\n )}\n >\n <NodeResizer\n isVisible={canResize}\n minWidth={FLOW_ANNOTATION_MIN_SIZE}\n minHeight={FLOW_ANNOTATION_MIN_SIZE}\n handleClassName={RESIZE_HANDLE_CLASS}\n lineClassName={RESIZE_LINE_CLASS}\n onResizeStart={data.onResizeStart}\n onResize={(_event, params) =>\n data.onResize({\n position: { x: params.x, y: params.y },\n size: { width: params.width, height: params.height },\n })\n }\n />\n {content}\n </div>\n );\n};\n\nexport default FlowAnnotationNode;\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ import { SymbolCodepoints } from '../MaterialSymbol';
2
+ import { TFlowAnnotationStroke, TFlowAnnotationTone, TFlowCanvasTool, TFlowShapeTool } from './flowTypes';
3
+ interface IFlowAnnotationToolbarProps {
4
+ tool: TFlowCanvasTool;
5
+ /** The shape the shape button draws: the last one picked in its menu. */
6
+ shapeTool: TFlowShapeTool;
7
+ onToolChange: (tool: TFlowCanvasTool) => void;
8
+ tone: TFlowAnnotationTone;
9
+ onToneChange: (tone: TFlowAnnotationTone) => void;
10
+ stroke: TFlowAnnotationStroke;
11
+ onStrokeChange: (stroke: TFlowAnnotationStroke) => void;
12
+ canUndo: boolean;
13
+ canRedo: boolean;
14
+ onUndo: () => void;
15
+ onRedo: () => void;
16
+ canDelete: boolean;
17
+ onDelete: () => void;
18
+ onClose: () => void;
19
+ }
20
+ export declare const FLOW_TOOL_ICON: Record<TFlowCanvasTool, SymbolCodepoints>;
21
+ /** Single keys, as in Figma and Excalidraw. */
22
+ export declare const FLOW_TOOL_SHORTCUT: Partial<Record<TFlowCanvasTool, string>>;
23
+ /**
24
+ * The annotation toolbar, laid out like the annotation bar of a screenshot tool: what to draw, its
25
+ * colour and weight, history, delete, and close.
26
+ */
27
+ declare const FlowAnnotationToolbar: React.FC<IFlowAnnotationToolbarProps>;
28
+ /** What the closed toolbar leaves on the canvas: one button that opens it again. */
29
+ export declare const FlowAnnotationLauncher: React.FC<{
30
+ onOpen: () => void;
31
+ }>;
32
+ export default FlowAnnotationToolbar;
@@ -0,0 +1,298 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { useTranslation } from "react-i18next";
4
+ import ControlledButton from "../../Form/ControlledButton.js";
5
+ import ControlledMenu from "../ControlledMenu.js";
6
+ import ControlledPopover from "../ControlledPopover.js";
7
+ import ControlledTooltip from "../ControlledTooltip.js";
8
+ import { MaterialSymbol } from "../MaterialSymbol/index.js";
9
+ import { mergeClasses } from "../../../helpers/mergeClasses.js";
10
+ import { FLOW_ANNOTATION_SWATCH_CLASS } from "./FlowAnnotationNode.js";
11
+ import { FLOW_ANNOTATION_TONES, FLOW_ANNOTATION_STROKES, FLOW_ANNOTATION_STROKE_WIDTH } from "./flowAnnotations.js";
12
+ const TEXT_TOOL = "text";
13
+ const FLOW_TOOL_ICON = {
14
+ select: "arrow_selector_tool",
15
+ drawing: "edit",
16
+ line: "pen_size_2",
17
+ arrow: "north_east",
18
+ rectangle: "check_box_outline_blank",
19
+ ellipse: "radio_button_unchecked",
20
+ step: "counter_1",
21
+ [TEXT_TOOL]: "title"
22
+ };
23
+ const FLOW_TOOL_SHORTCUT = {
24
+ select: "V",
25
+ drawing: "P",
26
+ line: "L",
27
+ arrow: "A",
28
+ rectangle: "R",
29
+ ellipse: "O",
30
+ [TEXT_TOOL]: "T"
31
+ };
32
+ const SHAPE_TOOLS = ["line", "arrow", "rectangle", "ellipse", "step"];
33
+ const ICON_BUTTON_CLASS = "interactive flex h-8 items-center justify-center gap-x-0.5 rounded-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-40";
34
+ const toolButtonClass = (active) => mergeClasses(
35
+ ICON_BUTTON_CLASS,
36
+ "min-w-8 px-1.5",
37
+ active ? "bg-brand-soft text-brand-emphasis" : "text-foreground-muted hover:bg-muted hover:text-foreground"
38
+ );
39
+ const Divider = () => /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "mx-0.5 h-5 w-px shrink-0 bg-border" });
40
+ const CHOICE_CLASS = "interactive flex w-full items-center gap-x-2.5 rounded-lg px-2.5 py-1.5 text-sm text-foreground-muted hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring";
41
+ const FlowAnnotationToolbar = ({
42
+ tool,
43
+ shapeTool,
44
+ onToolChange,
45
+ tone,
46
+ onToneChange,
47
+ stroke,
48
+ onStrokeChange,
49
+ canUndo,
50
+ canRedo,
51
+ onUndo,
52
+ onRedo,
53
+ canDelete,
54
+ onDelete,
55
+ onClose
56
+ }) => {
57
+ const { t } = useTranslation("ui");
58
+ const [colorOpen, setColorOpen] = useState(false);
59
+ const [strokeOpen, setStrokeOpen] = useState(false);
60
+ const toolLabels = {
61
+ select: t("flow.annotations.tools.select"),
62
+ drawing: t("flow.annotations.tools.drawing"),
63
+ line: t("flow.annotations.tools.line"),
64
+ arrow: t("flow.annotations.tools.arrow"),
65
+ rectangle: t("flow.annotations.tools.rectangle"),
66
+ ellipse: t("flow.annotations.tools.ellipse"),
67
+ step: t("flow.annotations.tools.step"),
68
+ [TEXT_TOOL]: t("flow.annotations.tools.text")
69
+ };
70
+ const toneLabels = {
71
+ danger: t("flow.annotations.tones.danger"),
72
+ warning: t("flow.annotations.tones.warning"),
73
+ success: t("flow.annotations.tones.success"),
74
+ info: t("flow.annotations.tones.info"),
75
+ brand: t("flow.annotations.tones.brand"),
76
+ neutral: t("flow.annotations.tones.neutral")
77
+ };
78
+ const strokeLabels = {
79
+ thin: t("flow.annotations.strokes.thin"),
80
+ medium: t("flow.annotations.strokes.medium"),
81
+ thick: t("flow.annotations.strokes.thick")
82
+ };
83
+ const tooltipOf = (target) => {
84
+ const shortcut = FLOW_TOOL_SHORTCUT[target];
85
+ return shortcut ? t("flow.annotations.tools.withShortcut", { label: toolLabels[target], shortcut }) : toolLabels[target];
86
+ };
87
+ const toolButton = (target) => /* @__PURE__ */ jsx(ControlledTooltip, { content: tooltipOf(target), placement: "top", children: /* @__PURE__ */ jsx(
88
+ ControlledButton,
89
+ {
90
+ "aria-label": toolLabels[target],
91
+ "aria-pressed": tool === target,
92
+ defaultHtmlButton: true,
93
+ onClick: () => onToolChange(target),
94
+ className: toolButtonClass(tool === target),
95
+ children: /* @__PURE__ */ jsx(MaterialSymbol, { icon: FLOW_TOOL_ICON[target], size: 18, "aria-hidden": true })
96
+ }
97
+ ) }, target);
98
+ const isShapeActive = SHAPE_TOOLS.includes(tool);
99
+ return /* @__PURE__ */ jsxs(
100
+ "div",
101
+ {
102
+ role: "toolbar",
103
+ "aria-label": t("flow.annotations.toolbar"),
104
+ className: "flex items-center gap-x-0.5 rounded-xl border border-border bg-surface p-1 shadow-md",
105
+ children: [
106
+ toolButton("select"),
107
+ toolButton("drawing"),
108
+ /* @__PURE__ */ jsx(
109
+ ControlledMenu,
110
+ {
111
+ placement: "top-start",
112
+ triggerTooltip: t("flow.annotations.tools.shapes"),
113
+ items: SHAPE_TOOLS.map((shape) => ({
114
+ key: shape,
115
+ label: toolLabels[shape],
116
+ icon: FLOW_TOOL_ICON[shape],
117
+ onClick: () => onToolChange(shape)
118
+ })),
119
+ trigger: /* @__PURE__ */ jsxs(
120
+ ControlledButton,
121
+ {
122
+ "aria-label": t("flow.annotations.tools.shapes"),
123
+ "aria-pressed": isShapeActive,
124
+ defaultHtmlButton: true,
125
+ className: toolButtonClass(isShapeActive),
126
+ children: [
127
+ /* @__PURE__ */ jsx(MaterialSymbol, { icon: FLOW_TOOL_ICON[shapeTool], size: 18, "aria-hidden": true }),
128
+ /* @__PURE__ */ jsx(MaterialSymbol, { icon: "expand_more", size: 14, "aria-hidden": true })
129
+ ]
130
+ }
131
+ )
132
+ }
133
+ ),
134
+ toolButton(TEXT_TOOL),
135
+ /* @__PURE__ */ jsx(Divider, {}),
136
+ /* @__PURE__ */ jsx(
137
+ ControlledPopover,
138
+ {
139
+ placement: "top",
140
+ open: colorOpen,
141
+ onOpenChange: setColorOpen,
142
+ triggerTooltip: t("flow.annotations.color"),
143
+ contentClassName: "flex items-center gap-x-1 p-1.5",
144
+ trigger: /* @__PURE__ */ jsxs(
145
+ ControlledButton,
146
+ {
147
+ "aria-label": t("flow.annotations.color"),
148
+ defaultHtmlButton: true,
149
+ className: mergeClasses(toolButtonClass(false), "px-1.5"),
150
+ children: [
151
+ /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: mergeClasses("size-4 rounded-full", FLOW_ANNOTATION_SWATCH_CLASS[tone]) }),
152
+ /* @__PURE__ */ jsx(MaterialSymbol, { icon: "expand_more", size: 14, "aria-hidden": true })
153
+ ]
154
+ }
155
+ ),
156
+ children: FLOW_ANNOTATION_TONES.map((option) => /* @__PURE__ */ jsx(ControlledTooltip, { content: toneLabels[option], placement: "top", children: /* @__PURE__ */ jsx(
157
+ ControlledButton,
158
+ {
159
+ "aria-label": toneLabels[option],
160
+ "aria-pressed": tone === option,
161
+ defaultHtmlButton: true,
162
+ onClick: () => {
163
+ onToneChange(option);
164
+ setColorOpen(false);
165
+ },
166
+ className: mergeClasses(
167
+ "interactive flex size-8 items-center justify-center rounded-full focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
168
+ tone === option ? "bg-muted-active" : "hover:bg-muted"
169
+ ),
170
+ children: /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: mergeClasses("size-5 rounded-full", FLOW_ANNOTATION_SWATCH_CLASS[option]) })
171
+ }
172
+ ) }, option))
173
+ }
174
+ ),
175
+ /* @__PURE__ */ jsx(
176
+ ControlledPopover,
177
+ {
178
+ placement: "top",
179
+ open: strokeOpen,
180
+ onOpenChange: setStrokeOpen,
181
+ triggerTooltip: t("flow.annotations.stroke"),
182
+ contentClassName: "flex w-40 flex-col p-1",
183
+ trigger: /* @__PURE__ */ jsxs(
184
+ ControlledButton,
185
+ {
186
+ "aria-label": t("flow.annotations.stroke"),
187
+ defaultHtmlButton: true,
188
+ className: mergeClasses(toolButtonClass(false), "px-1.5"),
189
+ children: [
190
+ /* @__PURE__ */ jsx(
191
+ "span",
192
+ {
193
+ "aria-hidden": true,
194
+ className: "w-5 rounded-full bg-current",
195
+ style: { height: FLOW_ANNOTATION_STROKE_WIDTH[stroke] }
196
+ }
197
+ ),
198
+ /* @__PURE__ */ jsx(MaterialSymbol, { icon: "expand_more", size: 14, "aria-hidden": true })
199
+ ]
200
+ }
201
+ ),
202
+ children: FLOW_ANNOTATION_STROKES.map((option) => /* @__PURE__ */ jsxs(
203
+ ControlledButton,
204
+ {
205
+ "aria-pressed": stroke === option,
206
+ defaultHtmlButton: true,
207
+ onClick: () => {
208
+ onStrokeChange(option);
209
+ setStrokeOpen(false);
210
+ },
211
+ className: mergeClasses(CHOICE_CLASS, stroke === option && "bg-muted text-foreground"),
212
+ children: [
213
+ /* @__PURE__ */ jsx(
214
+ "span",
215
+ {
216
+ "aria-hidden": true,
217
+ className: "w-6 shrink-0 rounded-full bg-current",
218
+ style: { height: FLOW_ANNOTATION_STROKE_WIDTH[option] }
219
+ }
220
+ ),
221
+ /* @__PURE__ */ jsx("span", { children: strokeLabels[option] })
222
+ ]
223
+ },
224
+ option
225
+ ))
226
+ }
227
+ ),
228
+ /* @__PURE__ */ jsx(Divider, {}),
229
+ /* @__PURE__ */ jsx(ControlledTooltip, { content: t("flow.controls.undo"), placement: "top", children: /* @__PURE__ */ jsx(
230
+ ControlledButton,
231
+ {
232
+ "aria-label": t("flow.controls.undo"),
233
+ defaultHtmlButton: true,
234
+ disabled: !canUndo,
235
+ onClick: onUndo,
236
+ className: toolButtonClass(false),
237
+ children: /* @__PURE__ */ jsx(MaterialSymbol, { icon: "undo", size: 18, "aria-hidden": true })
238
+ }
239
+ ) }),
240
+ /* @__PURE__ */ jsx(ControlledTooltip, { content: t("flow.controls.redo"), placement: "top", children: /* @__PURE__ */ jsx(
241
+ ControlledButton,
242
+ {
243
+ "aria-label": t("flow.controls.redo"),
244
+ defaultHtmlButton: true,
245
+ disabled: !canRedo,
246
+ onClick: onRedo,
247
+ className: toolButtonClass(false),
248
+ children: /* @__PURE__ */ jsx(MaterialSymbol, { icon: "redo", size: 18, "aria-hidden": true })
249
+ }
250
+ ) }),
251
+ /* @__PURE__ */ jsx(ControlledTooltip, { content: t("flow.annotations.deleteSelection"), placement: "top", children: /* @__PURE__ */ jsx(
252
+ ControlledButton,
253
+ {
254
+ "aria-label": t("flow.annotations.deleteSelection"),
255
+ defaultHtmlButton: true,
256
+ disabled: !canDelete,
257
+ onClick: onDelete,
258
+ className: toolButtonClass(false),
259
+ children: /* @__PURE__ */ jsx(MaterialSymbol, { icon: "delete", size: 18, "aria-hidden": true })
260
+ }
261
+ ) }),
262
+ /* @__PURE__ */ jsx(Divider, {}),
263
+ /* @__PURE__ */ jsx(ControlledTooltip, { content: t("flow.annotations.close"), placement: "top", children: /* @__PURE__ */ jsx(
264
+ ControlledButton,
265
+ {
266
+ "aria-label": t("flow.annotations.close"),
267
+ defaultHtmlButton: true,
268
+ onClick: onClose,
269
+ className: toolButtonClass(false),
270
+ children: /* @__PURE__ */ jsx(MaterialSymbol, { icon: "close", size: 18, "aria-hidden": true })
271
+ }
272
+ ) })
273
+ ]
274
+ }
275
+ );
276
+ };
277
+ const FlowAnnotationLauncher = ({ onOpen }) => {
278
+ const { t } = useTranslation("ui");
279
+ return /* @__PURE__ */ jsxs(
280
+ ControlledButton,
281
+ {
282
+ defaultHtmlButton: true,
283
+ onClick: onOpen,
284
+ className: "interactive flex h-9 items-center gap-x-1.5 rounded-xl border border-border bg-surface px-3 text-sm font-medium text-foreground-muted shadow-md hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
285
+ children: [
286
+ /* @__PURE__ */ jsx(MaterialSymbol, { icon: "edit", size: 18, "aria-hidden": true }),
287
+ /* @__PURE__ */ jsx("span", { children: t("flow.annotations.open") })
288
+ ]
289
+ }
290
+ );
291
+ };
292
+ export {
293
+ FLOW_TOOL_ICON,
294
+ FLOW_TOOL_SHORTCUT,
295
+ FlowAnnotationLauncher,
296
+ FlowAnnotationToolbar as default
297
+ };
298
+ //# sourceMappingURL=FlowAnnotationToolbar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FlowAnnotationToolbar.js","sources":["../../../../src/components/UI/Flow/FlowAnnotationToolbar.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useTranslation } from 'react-i18next';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport ControlledMenu from '@/components/UI/ControlledMenu';\nimport ControlledPopover from '@/components/UI/ControlledPopover';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { MaterialSymbol, type SymbolCodepoints } from '@/components/UI/MaterialSymbol';\nimport { mergeClasses } from '@/helpers/mergeClasses';\n\nimport { FLOW_ANNOTATION_SWATCH_CLASS } from './FlowAnnotationNode';\nimport { FLOW_ANNOTATION_STROKE_WIDTH, FLOW_ANNOTATION_STROKES, FLOW_ANNOTATION_TONES } from './flowAnnotations';\nimport type { TFlowAnnotationStroke, TFlowAnnotationTone, TFlowCanvasTool, TFlowShapeTool } from './flowTypes';\n\ninterface IFlowAnnotationToolbarProps {\n tool: TFlowCanvasTool;\n /** The shape the shape button draws: the last one picked in its menu. */\n shapeTool: TFlowShapeTool;\n onToolChange: (tool: TFlowCanvasTool) => void;\n tone: TFlowAnnotationTone;\n onToneChange: (tone: TFlowAnnotationTone) => void;\n stroke: TFlowAnnotationStroke;\n onStrokeChange: (stroke: TFlowAnnotationStroke) => void;\n canUndo: boolean;\n canRedo: boolean;\n onUndo: () => void;\n onRedo: () => void;\n canDelete: boolean;\n onDelete: () => void;\n onClose: () => void;\n}\n\n// The text tool is keyed by a constant: a literal `text:` key reads as copy to the i18n lint.\nconst TEXT_TOOL: TFlowCanvasTool = 'text';\n\nexport const FLOW_TOOL_ICON: Record<TFlowCanvasTool, SymbolCodepoints> = {\n select: 'arrow_selector_tool',\n drawing: 'edit',\n line: 'pen_size_2',\n arrow: 'north_east',\n rectangle: 'check_box_outline_blank',\n ellipse: 'radio_button_unchecked',\n step: 'counter_1',\n [TEXT_TOOL]: 'title',\n};\n\n/** Single keys, as in Figma and Excalidraw. */\nexport const FLOW_TOOL_SHORTCUT: Partial<Record<TFlowCanvasTool, string>> = {\n select: 'V',\n drawing: 'P',\n line: 'L',\n arrow: 'A',\n rectangle: 'R',\n ellipse: 'O',\n [TEXT_TOOL]: 'T',\n};\n\nconst SHAPE_TOOLS: Array<TFlowShapeTool> = ['line', 'arrow', 'rectangle', 'ellipse', 'step'];\n\nconst ICON_BUTTON_CLASS =\n 'interactive flex h-8 items-center justify-center gap-x-0.5 rounded-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-40';\n\nconst toolButtonClass = (active: boolean): string =>\n mergeClasses(\n ICON_BUTTON_CLASS,\n 'min-w-8 px-1.5',\n active ? 'bg-brand-soft text-brand-emphasis' : 'text-foreground-muted hover:bg-muted hover:text-foreground',\n );\n\nconst Divider: React.FC = () => <span aria-hidden className=\"mx-0.5 h-5 w-px shrink-0 bg-border\" />;\n\nconst CHOICE_CLASS =\n 'interactive flex w-full items-center gap-x-2.5 rounded-lg px-2.5 py-1.5 text-sm text-foreground-muted hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring';\n\n/**\n * The annotation toolbar, laid out like the annotation bar of a screenshot tool: what to draw, its\n * colour and weight, history, delete, and close.\n */\nconst FlowAnnotationToolbar: React.FC<IFlowAnnotationToolbarProps> = ({\n tool,\n shapeTool,\n onToolChange,\n tone,\n onToneChange,\n stroke,\n onStrokeChange,\n canUndo,\n canRedo,\n onUndo,\n onRedo,\n canDelete,\n onDelete,\n onClose,\n}) => {\n const { t } = useTranslation('ui');\n const [colorOpen, setColorOpen] = useState(false);\n const [strokeOpen, setStrokeOpen] = useState(false);\n\n const toolLabels: Record<TFlowCanvasTool, string> = {\n select: t('flow.annotations.tools.select'),\n drawing: t('flow.annotations.tools.drawing'),\n line: t('flow.annotations.tools.line'),\n arrow: t('flow.annotations.tools.arrow'),\n rectangle: t('flow.annotations.tools.rectangle'),\n ellipse: t('flow.annotations.tools.ellipse'),\n step: t('flow.annotations.tools.step'),\n [TEXT_TOOL]: t('flow.annotations.tools.text'),\n };\n const toneLabels: Record<TFlowAnnotationTone, string> = {\n danger: t('flow.annotations.tones.danger'),\n warning: t('flow.annotations.tones.warning'),\n success: t('flow.annotations.tones.success'),\n info: t('flow.annotations.tones.info'),\n brand: t('flow.annotations.tones.brand'),\n neutral: t('flow.annotations.tones.neutral'),\n };\n const strokeLabels: Record<TFlowAnnotationStroke, string> = {\n thin: t('flow.annotations.strokes.thin'),\n medium: t('flow.annotations.strokes.medium'),\n thick: t('flow.annotations.strokes.thick'),\n };\n const tooltipOf = (target: TFlowCanvasTool): string => {\n const shortcut = FLOW_TOOL_SHORTCUT[target];\n return shortcut\n ? t('flow.annotations.tools.withShortcut', { label: toolLabels[target], shortcut })\n : toolLabels[target];\n };\n\n const toolButton = (target: TFlowCanvasTool) => (\n <ControlledTooltip key={target} content={tooltipOf(target)} placement=\"top\">\n <ControlledButton\n aria-label={toolLabels[target]}\n aria-pressed={tool === target}\n defaultHtmlButton\n onClick={() => onToolChange(target)}\n className={toolButtonClass(tool === target)}\n >\n <MaterialSymbol icon={FLOW_TOOL_ICON[target]} size={18} aria-hidden />\n </ControlledButton>\n </ControlledTooltip>\n );\n\n const isShapeActive = SHAPE_TOOLS.includes(tool as TFlowShapeTool);\n\n return (\n <div\n role=\"toolbar\"\n aria-label={t('flow.annotations.toolbar')}\n className=\"flex items-center gap-x-0.5 rounded-xl border border-border bg-surface p-1 shadow-md\"\n >\n {toolButton('select')}\n {toolButton('drawing')}\n <ControlledMenu\n placement=\"top-start\"\n triggerTooltip={t('flow.annotations.tools.shapes')}\n items={SHAPE_TOOLS.map((shape) => ({\n key: shape,\n label: toolLabels[shape],\n icon: FLOW_TOOL_ICON[shape],\n onClick: () => onToolChange(shape),\n }))}\n trigger={\n <ControlledButton\n aria-label={t('flow.annotations.tools.shapes')}\n aria-pressed={isShapeActive}\n defaultHtmlButton\n className={toolButtonClass(isShapeActive)}\n >\n <MaterialSymbol icon={FLOW_TOOL_ICON[shapeTool]} size={18} aria-hidden />\n <MaterialSymbol icon=\"expand_more\" size={14} aria-hidden />\n </ControlledButton>\n }\n />\n {toolButton(TEXT_TOOL)}\n\n <Divider />\n\n <ControlledPopover\n placement=\"top\"\n open={colorOpen}\n onOpenChange={setColorOpen}\n triggerTooltip={t('flow.annotations.color')}\n contentClassName=\"flex items-center gap-x-1 p-1.5\"\n trigger={\n <ControlledButton\n aria-label={t('flow.annotations.color')}\n defaultHtmlButton\n className={mergeClasses(toolButtonClass(false), 'px-1.5')}\n >\n <span aria-hidden className={mergeClasses('size-4 rounded-full', FLOW_ANNOTATION_SWATCH_CLASS[tone])} />\n <MaterialSymbol icon=\"expand_more\" size={14} aria-hidden />\n </ControlledButton>\n }\n >\n {FLOW_ANNOTATION_TONES.map((option) => (\n <ControlledTooltip key={option} content={toneLabels[option]} placement=\"top\">\n <ControlledButton\n aria-label={toneLabels[option]}\n aria-pressed={tone === option}\n defaultHtmlButton\n onClick={() => {\n onToneChange(option);\n setColorOpen(false);\n }}\n className={mergeClasses(\n 'interactive flex size-8 items-center justify-center rounded-full focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',\n tone === option ? 'bg-muted-active' : 'hover:bg-muted',\n )}\n >\n <span aria-hidden className={mergeClasses('size-5 rounded-full', FLOW_ANNOTATION_SWATCH_CLASS[option])} />\n </ControlledButton>\n </ControlledTooltip>\n ))}\n </ControlledPopover>\n\n <ControlledPopover\n placement=\"top\"\n open={strokeOpen}\n onOpenChange={setStrokeOpen}\n triggerTooltip={t('flow.annotations.stroke')}\n contentClassName=\"flex w-40 flex-col p-1\"\n trigger={\n <ControlledButton\n aria-label={t('flow.annotations.stroke')}\n defaultHtmlButton\n className={mergeClasses(toolButtonClass(false), 'px-1.5')}\n >\n <span\n aria-hidden\n className=\"w-5 rounded-full bg-current\"\n style={{ height: FLOW_ANNOTATION_STROKE_WIDTH[stroke] }}\n />\n <MaterialSymbol icon=\"expand_more\" size={14} aria-hidden />\n </ControlledButton>\n }\n >\n {FLOW_ANNOTATION_STROKES.map((option) => (\n <ControlledButton\n key={option}\n aria-pressed={stroke === option}\n defaultHtmlButton\n onClick={() => {\n onStrokeChange(option);\n setStrokeOpen(false);\n }}\n className={mergeClasses(CHOICE_CLASS, stroke === option && 'bg-muted text-foreground')}\n >\n <span\n aria-hidden\n className=\"w-6 shrink-0 rounded-full bg-current\"\n style={{ height: FLOW_ANNOTATION_STROKE_WIDTH[option] }}\n />\n <span>{strokeLabels[option]}</span>\n </ControlledButton>\n ))}\n </ControlledPopover>\n\n <Divider />\n\n <ControlledTooltip content={t('flow.controls.undo')} placement=\"top\">\n <ControlledButton\n aria-label={t('flow.controls.undo')}\n defaultHtmlButton\n disabled={!canUndo}\n onClick={onUndo}\n className={toolButtonClass(false)}\n >\n <MaterialSymbol icon=\"undo\" size={18} aria-hidden />\n </ControlledButton>\n </ControlledTooltip>\n <ControlledTooltip content={t('flow.controls.redo')} placement=\"top\">\n <ControlledButton\n aria-label={t('flow.controls.redo')}\n defaultHtmlButton\n disabled={!canRedo}\n onClick={onRedo}\n className={toolButtonClass(false)}\n >\n <MaterialSymbol icon=\"redo\" size={18} aria-hidden />\n </ControlledButton>\n </ControlledTooltip>\n <ControlledTooltip content={t('flow.annotations.deleteSelection')} placement=\"top\">\n <ControlledButton\n aria-label={t('flow.annotations.deleteSelection')}\n defaultHtmlButton\n disabled={!canDelete}\n onClick={onDelete}\n className={toolButtonClass(false)}\n >\n <MaterialSymbol icon=\"delete\" size={18} aria-hidden />\n </ControlledButton>\n </ControlledTooltip>\n\n <Divider />\n\n <ControlledTooltip content={t('flow.annotations.close')} placement=\"top\">\n <ControlledButton\n aria-label={t('flow.annotations.close')}\n defaultHtmlButton\n onClick={onClose}\n className={toolButtonClass(false)}\n >\n <MaterialSymbol icon=\"close\" size={18} aria-hidden />\n </ControlledButton>\n </ControlledTooltip>\n </div>\n );\n};\n\n/** What the closed toolbar leaves on the canvas: one button that opens it again. */\nexport const FlowAnnotationLauncher: React.FC<{ onOpen: () => void }> = ({ onOpen }) => {\n const { t } = useTranslation('ui');\n return (\n <ControlledButton\n defaultHtmlButton\n onClick={onOpen}\n className=\"interactive flex h-9 items-center gap-x-1.5 rounded-xl border border-border bg-surface px-3 text-sm font-medium text-foreground-muted shadow-md hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <MaterialSymbol icon=\"edit\" size={18} aria-hidden />\n <span>{t('flow.annotations.open')}</span>\n </ControlledButton>\n );\n};\n\nexport default FlowAnnotationToolbar;\n"],"names":[],"mappings":"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"}
@@ -11,6 +11,8 @@ export interface IFlowCanvasProps {
11
11
  controlsPosition?: PanelPosition;
12
12
  /** The dot grid behind the blocks. Blocks still snap to the grid when it is hidden. */
13
13
  background?: 'dots' | 'none';
14
+ /** Shows the drawing toolbar (pen, shapes, text). Annotations already in the graph render either way. */
15
+ annotationTools?: boolean;
14
16
  }
15
17
  /**
16
18
  * The editing surface. Renders the graph held by `FlowEditorProvider` and turns every