@umodeapporg/ui 0.30.1 → 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 (40) hide show
  1. package/dist/assets/umode-ui.css +32 -0
  2. package/dist/components/UI/Flow/FlowAnnotationDraftPreview.d.ts +6 -0
  3. package/dist/components/UI/Flow/FlowAnnotationDraftPreview.js +65 -0
  4. package/dist/components/UI/Flow/FlowAnnotationDraftPreview.js.map +1 -0
  5. package/dist/components/UI/Flow/FlowAnnotationNode.d.ts +34 -0
  6. package/dist/components/UI/Flow/FlowAnnotationNode.js +231 -0
  7. package/dist/components/UI/Flow/FlowAnnotationNode.js.map +1 -0
  8. package/dist/components/UI/Flow/FlowAnnotationToolbar.d.ts +32 -0
  9. package/dist/components/UI/Flow/FlowAnnotationToolbar.js +298 -0
  10. package/dist/components/UI/Flow/FlowAnnotationToolbar.js.map +1 -0
  11. package/dist/components/UI/Flow/FlowCanvas.d.ts +2 -0
  12. package/dist/components/UI/Flow/FlowCanvas.js +278 -20
  13. package/dist/components/UI/Flow/FlowCanvas.js.map +1 -1
  14. package/dist/components/UI/Flow/FlowInspector.js +6 -5
  15. package/dist/components/UI/Flow/FlowInspector.js.map +1 -1
  16. package/dist/components/UI/Flow/FlowSelectionBar.d.ts +10 -0
  17. package/dist/components/UI/Flow/FlowSelectionBar.js +56 -0
  18. package/dist/components/UI/Flow/FlowSelectionBar.js.map +1 -0
  19. package/dist/components/UI/Flow/flowAnnotations.d.ts +38 -0
  20. package/dist/components/UI/Flow/flowAnnotations.js +123 -0
  21. package/dist/components/UI/Flow/flowAnnotations.js.map +1 -0
  22. package/dist/components/UI/Flow/flowEditorStore.d.ts +29 -5
  23. package/dist/components/UI/Flow/flowEditorStore.js +123 -23
  24. package/dist/components/UI/Flow/flowEditorStore.js.map +1 -1
  25. package/dist/components/UI/Flow/flowTypes.d.ts +30 -0
  26. package/dist/components/UI/Flow/index.d.ts +2 -1
  27. package/dist/components/UI/Flow/index.js +5 -1
  28. package/dist/components/UI/Flow/index.js.map +1 -1
  29. package/dist/components/UI/Flow/useFlowAnnotationDrawing.d.ts +25 -0
  30. package/dist/components/UI/Flow/useFlowAnnotationDrawing.js +117 -0
  31. package/dist/components/UI/Flow/useFlowAnnotationDrawing.js.map +1 -0
  32. package/dist/locales/en/ui.json.d.ts +41 -0
  33. package/dist/locales/en/ui.json.js +1 -1
  34. package/dist/locales/index.d.ts +82 -0
  35. package/dist/locales/pt/ui.json.d.ts +41 -0
  36. package/dist/locales/pt/ui.json.js +1 -1
  37. package/package.json +1 -1
  38. package/src/assets/fonts/LICENSE-Virgil.txt +45 -0
  39. package/src/assets/fonts/virgil.woff2 +0 -0
  40. package/src/assets/styles/base.css +10 -0
@@ -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