@tldiagram/core-ui 1.87.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 (272) hide show
  1. package/dist/App.d.ts +1 -0
  2. package/dist/api/client.d.ts +143 -0
  3. package/dist/api/transport-vscode.d.ts +8 -0
  4. package/dist/api/transport.d.ts +1 -0
  5. package/dist/components/CodePreviewPanel-vscode.d.ts +7 -0
  6. package/dist/components/CodePreviewPanel.d.ts +9 -0
  7. package/dist/components/ConfirmDialog.d.ts +12 -0
  8. package/dist/components/ConnectorPanel.d.ts +21 -0
  9. package/dist/components/ContextBoundaryElement.d.ts +11 -0
  10. package/dist/components/ContextNeighborElement.d.ts +29 -0
  11. package/dist/components/ContextStraightConnector.d.ts +4 -0
  12. package/dist/components/CrossBranchControls.d.ts +9 -0
  13. package/dist/components/DependenciesOnboarding.d.ts +5 -0
  14. package/dist/components/DrawingCanvas.d.ts +39 -0
  15. package/dist/components/ElementLibrary-vscode.d.ts +7 -0
  16. package/dist/components/ElementLibrary.d.ts +22 -0
  17. package/dist/components/ElementNode.d.ts +36 -0
  18. package/dist/components/ElementPanel.d.ts +25 -0
  19. package/dist/components/ExploreOnboarding.d.ts +5 -0
  20. package/dist/components/ExplorePageOnboarding.d.ts +5 -0
  21. package/dist/components/ExportModal.d.ts +16 -0
  22. package/dist/components/FloatingEdge.d.ts +9 -0
  23. package/dist/components/GitSourceLinker.d.ts +8 -0
  24. package/dist/components/HeaderContext.d.ts +16 -0
  25. package/dist/components/Icons.d.ts +95 -0
  26. package/dist/components/ImportModal.d.ts +10 -0
  27. package/dist/components/InlineElementAdder.d.ts +17 -0
  28. package/dist/components/LayoutSection.d.ts +7 -0
  29. package/dist/components/LocalSourceLinker.d.ts +8 -0
  30. package/dist/components/MiniZoomOnboarding.d.ts +5 -0
  31. package/dist/components/NavBreadcrumb.d.ts +6 -0
  32. package/dist/components/NodeBody.d.ts +12 -0
  33. package/dist/components/NodeContainer.d.ts +8 -0
  34. package/dist/components/NodeHoverCard.d.ts +10 -0
  35. package/dist/components/PanelHeader.d.ts +8 -0
  36. package/dist/components/PanelUI.d.ts +3 -0
  37. package/dist/components/ProxyConnectorEdge.d.ts +4 -0
  38. package/dist/components/ProxyConnectorPanel.d.ts +9 -0
  39. package/dist/components/SafeBackground.d.ts +13 -0
  40. package/dist/components/ScrollIndicatorWrapper.d.ts +8 -0
  41. package/dist/components/SetChildModal.d.ts +10 -0
  42. package/dist/components/SetParentModal.d.ts +10 -0
  43. package/dist/components/SlidingPanel.d.ts +16 -0
  44. package/dist/components/TagUpsert.d.ts +8 -0
  45. package/dist/components/TopMenuBar.d.ts +8 -0
  46. package/dist/components/ViewBezierConnector.d.ts +4 -0
  47. package/dist/components/ViewDrawMenu.d.ts +22 -0
  48. package/dist/components/ViewEditorEdgeLabelLayout.d.ts +16 -0
  49. package/dist/components/ViewEditorOnboarding.d.ts +5 -0
  50. package/dist/components/ViewExplorer/TagManager/ColorPicker.d.ts +7 -0
  51. package/dist/components/ViewExplorer/TagManager/GroupNamingPopover.d.ts +10 -0
  52. package/dist/components/ViewExplorer/TagManager/LayerItem.d.ts +27 -0
  53. package/dist/components/ViewExplorer/TagManager/TagItem.d.ts +25 -0
  54. package/dist/components/ViewExplorer/TagManager/index.d.ts +21 -0
  55. package/dist/components/ViewExplorer/ViewNavigator.d.ts +11 -0
  56. package/dist/components/ViewExplorer/ViewSearch.d.ts +8 -0
  57. package/dist/components/ViewExplorer/ViewTree.d.ts +18 -0
  58. package/dist/components/ViewExplorer/index.d.ts +31 -0
  59. package/dist/components/ViewExplorer/types.d.ts +11 -0
  60. package/dist/components/ViewExplorer/utils.d.ts +6 -0
  61. package/dist/components/ViewExplorer-vscode.d.ts +6 -0
  62. package/dist/components/ViewFloatingMenu-vscode.d.ts +27 -0
  63. package/dist/components/ViewFloatingMenu.d.ts +39 -0
  64. package/dist/components/ViewGridNode.d.ts +29 -0
  65. package/dist/components/ViewHeaderButton.d.ts +11 -0
  66. package/dist/components/ViewPanel.d.ts +18 -0
  67. package/dist/components/ViewsGridOnboarding.d.ts +5 -0
  68. package/dist/components/ZUI/ZUICanvas.d.ts +18 -0
  69. package/dist/components/ZUI/index.d.ts +2 -0
  70. package/dist/components/ZUI/layout.d.ts +18 -0
  71. package/dist/components/ZUI/proxy.d.ts +25 -0
  72. package/dist/components/ZUI/renderer.d.ts +30 -0
  73. package/dist/components/ZUI/types.d.ts +140 -0
  74. package/dist/components/ZUI/useZUIInteraction.d.ts +21 -0
  75. package/dist/config/runtime-vscode.d.ts +22 -0
  76. package/dist/config/runtime.d.ts +5 -0
  77. package/dist/constants/colors.d.ts +27 -0
  78. package/dist/constants/diagramColors.d.ts +1 -0
  79. package/dist/context/ThemeContext.d.ts +27 -0
  80. package/dist/crossBranch/graph.d.ts +13 -0
  81. package/dist/crossBranch/resolve.d.ts +22 -0
  82. package/dist/crossBranch/settings.d.ts +6 -0
  83. package/dist/crossBranch/store.d.ts +11 -0
  84. package/dist/crossBranch/types.d.ts +96 -0
  85. package/dist/demo/DemoPage.d.ts +9 -0
  86. package/dist/demo/seed.d.ts +9 -0
  87. package/dist/demo/store.d.ts +137 -0
  88. package/dist/demo/viewEditor.d.ts +26 -0
  89. package/dist/favicon.svg +35 -0
  90. package/dist/hooks/useSafeFitView.d.ts +16 -0
  91. package/dist/index.css +1 -0
  92. package/dist/index.d.ts +115 -0
  93. package/dist/index.js +19966 -0
  94. package/dist/lib/vscodeBridge-vscode.d.ts +13 -0
  95. package/dist/lib/vscodeBridge.d.ts +5 -0
  96. package/dist/logo-120.png +0 -0
  97. package/dist/logo-bw.png +0 -0
  98. package/dist/logo-bw.svg +15 -0
  99. package/dist/logo-text.svg +51 -0
  100. package/dist/logo.svg +35 -0
  101. package/dist/pages/AppearanceSettings.d.ts +3 -0
  102. package/dist/pages/Dependencies.d.ts +1 -0
  103. package/dist/pages/InfiniteZoom.d.ts +7 -0
  104. package/dist/pages/Settings.d.ts +7 -0
  105. package/dist/pages/ViewEditor/components/EditorMenus.d.ts +24 -0
  106. package/dist/pages/ViewEditor/components/EditorOverlays.d.ts +30 -0
  107. package/dist/pages/ViewEditor/components/EmptyCanvasState.d.ts +7 -0
  108. package/dist/pages/ViewEditor/context.d.ts +13 -0
  109. package/dist/pages/ViewEditor/hooks/useCanvasInteractions.d.ts +201 -0
  110. package/dist/pages/ViewEditor/hooks/useDrawingEngine.d.ts +40 -0
  111. package/dist/pages/ViewEditor/hooks/useViewContextNeighbours.d.ts +20 -0
  112. package/dist/pages/ViewEditor/hooks/useViewData.d.ts +74 -0
  113. package/dist/pages/ViewEditor/index.d.ts +8 -0
  114. package/dist/pages/ViewEditor/utils.d.ts +14 -0
  115. package/dist/pages/Views.d.ts +6 -0
  116. package/dist/pages/ViewsGrid.d.ts +6 -0
  117. package/dist/pkg/importer/mermaid.d.ts +7 -0
  118. package/dist/pkg/importer/mermaid.test.d.ts +1 -0
  119. package/dist/platform/PlatformContext.d.ts +6 -0
  120. package/dist/platform/context.d.ts +3 -0
  121. package/dist/platform/local.d.ts +2 -0
  122. package/dist/platform/types.d.ts +17 -0
  123. package/dist/slots.d.ts +67 -0
  124. package/dist/theme.d.ts +2 -0
  125. package/dist/types/index.d.ts +193 -0
  126. package/dist/types/vscode-messages.d.ts +60 -0
  127. package/dist/utils/edgeDistribution.d.ts +34 -0
  128. package/dist/utils/githubApi.d.ts +4 -0
  129. package/dist/utils/githubCache.d.ts +17 -0
  130. package/dist/utils/ids.d.ts +2 -0
  131. package/dist/utils/technologyCatalog.d.ts +15 -0
  132. package/dist/utils/toast.d.ts +15 -0
  133. package/dist/utils/treesitter.d.ts +13 -0
  134. package/dist/utils/url.d.ts +12 -0
  135. package/package.json +159 -0
  136. package/src/App.tsx +141 -0
  137. package/src/api/client.ts +618 -0
  138. package/src/api/transport-vscode.ts +28 -0
  139. package/src/api/transport.ts +7 -0
  140. package/src/assets/logo-mark.svg +31 -0
  141. package/src/assets/logo-wordmark.svg +22 -0
  142. package/src/assets/logo.svg +35 -0
  143. package/src/components/CodePreviewPanel-vscode.tsx +85 -0
  144. package/src/components/CodePreviewPanel.tsx +384 -0
  145. package/src/components/ConfirmDialog.tsx +66 -0
  146. package/src/components/ConnectorPanel.tsx +403 -0
  147. package/src/components/ContextBoundaryElement.tsx +35 -0
  148. package/src/components/ContextNeighborElement.tsx +282 -0
  149. package/src/components/ContextStraightConnector.tsx +144 -0
  150. package/src/components/CrossBranchControls.tsx +105 -0
  151. package/src/components/DependenciesOnboarding.tsx +427 -0
  152. package/src/components/DrawingCanvas.tsx +391 -0
  153. package/src/components/ElementLibrary-vscode.tsx +9 -0
  154. package/src/components/ElementLibrary.tsx +512 -0
  155. package/src/components/ElementNode.tsx +1033 -0
  156. package/src/components/ElementPanel.tsx +928 -0
  157. package/src/components/ExploreOnboarding.tsx +347 -0
  158. package/src/components/ExplorePageOnboarding.tsx +383 -0
  159. package/src/components/ExportModal.tsx +132 -0
  160. package/src/components/FloatingEdge.tsx +115 -0
  161. package/src/components/GitSourceLinker.tsx +1053 -0
  162. package/src/components/HeaderContext.tsx +30 -0
  163. package/src/components/Icons.tsx +245 -0
  164. package/src/components/ImportModal.tsx +219 -0
  165. package/src/components/InlineElementAdder.tsx +216 -0
  166. package/src/components/LayoutSection.tsx +624 -0
  167. package/src/components/LocalSourceLinker.tsx +330 -0
  168. package/src/components/MiniZoomOnboarding.tsx +78 -0
  169. package/src/components/NavBreadcrumb.tsx +24 -0
  170. package/src/components/NodeBody.tsx +89 -0
  171. package/src/components/NodeContainer.tsx +58 -0
  172. package/src/components/NodeHoverCard.tsx +135 -0
  173. package/src/components/PanelHeader.tsx +36 -0
  174. package/src/components/PanelUI.tsx +24 -0
  175. package/src/components/ProxyConnectorEdge.tsx +169 -0
  176. package/src/components/ProxyConnectorPanel.tsx +130 -0
  177. package/src/components/SafeBackground.tsx +19 -0
  178. package/src/components/ScrollIndicatorWrapper.tsx +117 -0
  179. package/src/components/SetChildModal.tsx +191 -0
  180. package/src/components/SetParentModal.tsx +187 -0
  181. package/src/components/SlidingPanel.tsx +114 -0
  182. package/src/components/TagUpsert.tsx +142 -0
  183. package/src/components/TopMenuBar.tsx +380 -0
  184. package/src/components/ViewBezierConnector.tsx +143 -0
  185. package/src/components/ViewDrawMenu.tsx +270 -0
  186. package/src/components/ViewEditorEdgeLabelLayout.ts +189 -0
  187. package/src/components/ViewEditorOnboarding.tsx +445 -0
  188. package/src/components/ViewExplorer/TagManager/ColorPicker.tsx +49 -0
  189. package/src/components/ViewExplorer/TagManager/GroupNamingPopover.tsx +96 -0
  190. package/src/components/ViewExplorer/TagManager/LayerItem.tsx +228 -0
  191. package/src/components/ViewExplorer/TagManager/TagItem.tsx +242 -0
  192. package/src/components/ViewExplorer/TagManager/index.tsx +418 -0
  193. package/src/components/ViewExplorer/ViewNavigator.tsx +121 -0
  194. package/src/components/ViewExplorer/ViewSearch.tsx +33 -0
  195. package/src/components/ViewExplorer/ViewTree.tsx +98 -0
  196. package/src/components/ViewExplorer/index.tsx +384 -0
  197. package/src/components/ViewExplorer/types.ts +13 -0
  198. package/src/components/ViewExplorer/utils.ts +56 -0
  199. package/src/components/ViewExplorer-vscode.tsx +8 -0
  200. package/src/components/ViewFloatingMenu-vscode.tsx +248 -0
  201. package/src/components/ViewFloatingMenu.tsx +379 -0
  202. package/src/components/ViewGridNode.tsx +451 -0
  203. package/src/components/ViewHeaderButton.tsx +60 -0
  204. package/src/components/ViewPanel.tsx +162 -0
  205. package/src/components/ViewsGridOnboarding.tsx +400 -0
  206. package/src/components/ZUI/ZUICanvas.tsx +853 -0
  207. package/src/components/ZUI/index.ts +3 -0
  208. package/src/components/ZUI/layout.ts +323 -0
  209. package/src/components/ZUI/proxy.ts +278 -0
  210. package/src/components/ZUI/renderer.ts +1189 -0
  211. package/src/components/ZUI/types.ts +150 -0
  212. package/src/components/ZUI/useZUIInteraction.ts +720 -0
  213. package/src/config/runtime-vscode.ts +46 -0
  214. package/src/config/runtime.ts +30 -0
  215. package/src/constants/colors.ts +80 -0
  216. package/src/constants/diagramColors.ts +9 -0
  217. package/src/context/ThemeContext.tsx +158 -0
  218. package/src/crossBranch/graph.ts +207 -0
  219. package/src/crossBranch/resolve.ts +643 -0
  220. package/src/crossBranch/settings.ts +59 -0
  221. package/src/crossBranch/store.ts +71 -0
  222. package/src/crossBranch/types.ts +102 -0
  223. package/src/demo/DemoPage.tsx +184 -0
  224. package/src/demo/seed.ts +67 -0
  225. package/src/demo/store.ts +536 -0
  226. package/src/demo/viewEditor.ts +110 -0
  227. package/src/hooks/useSafeFitView.ts +60 -0
  228. package/src/index.css +309 -0
  229. package/src/index.ts +184 -0
  230. package/src/kafka-ss.png +0 -0
  231. package/src/lib/vscodeBridge-vscode.ts +27 -0
  232. package/src/lib/vscodeBridge.ts +7 -0
  233. package/src/main.tsx +46 -0
  234. package/src/pages/AppearanceSettings.tsx +135 -0
  235. package/src/pages/Dependencies.tsx +926 -0
  236. package/src/pages/InfiniteZoom.tsx +404 -0
  237. package/src/pages/Settings.tsx +91 -0
  238. package/src/pages/ViewEditor/EDGE_DISTRIBUTION.md +64 -0
  239. package/src/pages/ViewEditor/components/EditorMenus.tsx +112 -0
  240. package/src/pages/ViewEditor/components/EditorOverlays.tsx +172 -0
  241. package/src/pages/ViewEditor/components/EmptyCanvasState.tsx +42 -0
  242. package/src/pages/ViewEditor/context.tsx +21 -0
  243. package/src/pages/ViewEditor/hooks/useCanvasInteractions.ts +1349 -0
  244. package/src/pages/ViewEditor/hooks/useDrawingEngine.ts +127 -0
  245. package/src/pages/ViewEditor/hooks/useViewContextNeighbours.ts +501 -0
  246. package/src/pages/ViewEditor/hooks/useViewData.ts +491 -0
  247. package/src/pages/ViewEditor/index.tsx +1366 -0
  248. package/src/pages/ViewEditor/utils.ts +88 -0
  249. package/src/pages/Views.tsx +171 -0
  250. package/src/pages/ViewsGrid.tsx +1310 -0
  251. package/src/pkg/importer/mermaid.test.ts +141 -0
  252. package/src/pkg/importer/mermaid.ts +76 -0
  253. package/src/platform/PlatformContext.tsx +17 -0
  254. package/src/platform/context.ts +9 -0
  255. package/src/platform/local.tsx +15 -0
  256. package/src/platform/types.ts +19 -0
  257. package/src/slots.ts +92 -0
  258. package/src/styles/editor-panels.css +66 -0
  259. package/src/styles/theme.css +56 -0
  260. package/src/theme.ts +336 -0
  261. package/src/types/index.ts +234 -0
  262. package/src/types/offline-ambient.d.ts +14 -0
  263. package/src/types/vscode-messages.ts +32 -0
  264. package/src/utils/edgeDistribution.ts +103 -0
  265. package/src/utils/githubApi.ts +121 -0
  266. package/src/utils/githubCache.ts +108 -0
  267. package/src/utils/ids.ts +9 -0
  268. package/src/utils/technologyCatalog.ts +143 -0
  269. package/src/utils/toast.ts +100 -0
  270. package/src/utils/treesitter.ts +147 -0
  271. package/src/utils/url.ts +72 -0
  272. package/src/vite-env.d.ts +1 -0
@@ -0,0 +1,248 @@
1
+ import React from 'react'
2
+ import {
3
+ HStack, Tooltip, Button, Box, Text,
4
+ Menu, MenuButton, MenuList, MenuOptionGroup, MenuItemOption, MenuDivider, MenuItem
5
+ } from '@chakra-ui/react'
6
+ import { AddElementIcon as AddElementSvg } from './Icons'
7
+ import { KbdHint } from './PanelUI'
8
+ // Inline the props interface to avoid circular dependency with the web variant
9
+ // (vite.vscode.config.ts overrides ViewFloatingMenu.tsx → this file)
10
+ interface ViewFloatingMenuProps {
11
+ canEdit: boolean
12
+ handleAddElementAtCenter: () => void
13
+ drawingMode: boolean
14
+ setDrawingMode: React.Dispatch<React.SetStateAction<boolean>>
15
+ hasDrawingPaths: boolean
16
+ drawingVisible: boolean
17
+ setDrawingVisible: React.Dispatch<React.SetStateAction<boolean>>
18
+ extrasOpen: boolean
19
+ setExtrasOpen: React.Dispatch<React.SetStateAction<boolean>>
20
+ onImport: () => void
21
+ onExport: () => void
22
+ onShare: () => void
23
+ isFreePlan: boolean
24
+ canUpgrade?: boolean
25
+ activeTags?: string[]
26
+ setActiveTags?: (tags: string[]) => void
27
+ availableTags?: string[]
28
+ }
29
+
30
+ function LayerIcon() {
31
+ return (
32
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
33
+ <polygon points="12 2 2 7 12 12 22 7 12 2" />
34
+ <polyline points="2 17 12 22 22 17" />
35
+ <polyline points="2 12 12 17 22 12" />
36
+ </svg>
37
+ )
38
+ }
39
+
40
+ function PencilSvg() {
41
+ return (
42
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
43
+ <path d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z" />
44
+ </svg>
45
+ )
46
+ }
47
+ function EyeSvg() {
48
+ return (
49
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
50
+ <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
51
+ <circle cx="12" cy="12" r="3" />
52
+ </svg>
53
+ )
54
+ }
55
+ function EyeOffSvg() {
56
+ return (
57
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
58
+ <path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24" />
59
+ <line x1="1" y1="1" x2="23" y2="23" />
60
+ </svg>
61
+ )
62
+ }
63
+
64
+ /**
65
+ * VS Code variant of ViewFloatingMenu.
66
+ * Import, Export, and Share are removed they are registered as VS Code commands instead.
67
+ * The extras toggle chevron is also removed since there are no extras to expand.
68
+ */
69
+ export default function ViewFloatingMenu({
70
+ canEdit,
71
+ handleAddElementAtCenter,
72
+ drawingMode,
73
+ setDrawingMode,
74
+ hasDrawingPaths,
75
+ drawingVisible,
76
+ setDrawingVisible,
77
+ activeTags = [],
78
+ setActiveTags,
79
+ availableTags = [],
80
+ }: ViewFloatingMenuProps) {
81
+ return (
82
+ <HStack
83
+ position="absolute"
84
+ left="50%"
85
+ transform="translateX(-50%)"
86
+ zIndex={20}
87
+ spacing={0}
88
+ bg="var(--bg-panel)"
89
+ border="1px solid"
90
+ borderColor="whiteAlpha.100"
91
+ rounded="xl"
92
+ boxShadow="0 8px 32px rgba(0,0,0,0.5)"
93
+ backdropFilter="blur(20px)"
94
+ px={1.5}
95
+ py={1.5}
96
+ pointerEvents="auto"
97
+ style={{ bottom: 'calc(1.25rem + env(safe-area-inset-bottom, 0px))' } as React.CSSProperties}
98
+ transition="all 0.2s cubic-bezier(0.4, 0, 0.2, 1)"
99
+ >
100
+ <Tooltip label="Create new element (C)" placement="top" openDelay={200}>
101
+ <Button
102
+ variant="ghost"
103
+ h="28px"
104
+ px={2.5}
105
+ color="var(--accent)"
106
+ isDisabled={!canEdit}
107
+ _disabled={{ opacity: 0.35, cursor: 'not-allowed' }}
108
+ _hover={{ bg: 'rgba(var(--accent-rgb), 0.12)', color: 'var(--accent)' }}
109
+ onClick={() => handleAddElementAtCenter()}
110
+ >
111
+ <HStack spacing={1.5}>
112
+ <AddElementSvg />
113
+ <Text fontSize="11px" fontWeight="semibold">
114
+ Add Element
115
+ </Text>
116
+ <KbdHint>C</KbdHint>
117
+ </HStack>
118
+ </Button>
119
+ </Tooltip>
120
+
121
+ <Box w="1px" h="16px" bg="whiteAlpha.100" flexShrink={0} mx={0.5} />
122
+
123
+ {/* Draw mode toggle */}
124
+ <Tooltip
125
+ label={drawingMode ? 'Exit drawing mode' : 'Draw on diagram'}
126
+ placement="top"
127
+ openDelay={200}
128
+ >
129
+ <Button
130
+ variant="ghost"
131
+ h="28px"
132
+ px={2.5}
133
+ color={drawingMode ? 'var(--accent)' : 'gray.300'}
134
+ bg={drawingMode ? 'rgba(var(--accent-rgb), 0.12)' : 'transparent'}
135
+ _hover={{ bg: drawingMode ? 'rgba(var(--accent-rgb), 0.18)' : 'rgba(var(--accent-rgb), 0.12)', color: 'var(--accent)' }}
136
+ onClick={() => setDrawingMode((v) => !v)}
137
+ aria-label={drawingMode ? 'Exit drawing mode' : 'Enter drawing mode'}
138
+ >
139
+ <HStack spacing={1.5}>
140
+ <PencilSvg />
141
+ <Text fontSize="11px" fontWeight="normal">Draw</Text>
142
+ </HStack>
143
+ </Button>
144
+ </Tooltip>
145
+
146
+ {/* Drawing layer visibility toggle - only shown when there are strokes */}
147
+ {hasDrawingPaths && (
148
+ <>
149
+ <Box w="1px" h="16px" bg="whiteAlpha.100" flexShrink={0} mx={0.5} />
150
+ <Tooltip
151
+ label={drawingVisible ? 'Hide Drawings' : 'Show Drawings'}
152
+ placement="top"
153
+ openDelay={200}
154
+ >
155
+ <Button
156
+ variant="ghost"
157
+ h="28px"
158
+ minW="28px"
159
+ px={2}
160
+ color={drawingVisible ? 'gray.300' : 'gray.600'}
161
+ _hover={{ bg: 'rgba(var(--accent-rgb), 0.12)', color: 'var(--accent)' }}
162
+ onClick={() => setDrawingVisible((v) => !v)}
163
+ aria-label={drawingVisible ? 'Hide Drawings' : 'Show Drawings'}
164
+ >
165
+ {drawingVisible ? (
166
+ <HStack spacing={1.5}>
167
+ <EyeSvg />
168
+ <Text fontSize="11px" fontWeight="normal">Hide Drawings</Text>
169
+ </HStack>
170
+ ) : (
171
+ <HStack spacing={1.5}>
172
+ <EyeOffSvg />
173
+ <Text fontSize="11px" fontWeight="normal">Show Drawings</Text>
174
+ </HStack>
175
+ )}
176
+ </Button>
177
+ </Tooltip>
178
+ </>
179
+ )}
180
+
181
+ <Box w="1px" h="16px" bg="whiteAlpha.100" flexShrink={0} mx={0.5} />
182
+
183
+ {/* Layers/Tags menu */}
184
+ <Menu closeOnSelect={false} placement="top">
185
+ <Tooltip label="Filter elements by tag" placement="top" openDelay={200}>
186
+ <MenuButton
187
+ as={Button}
188
+ variant="ghost"
189
+ h="28px"
190
+ px={2.5}
191
+ color={activeTags.length > 0 ? 'var(--accent)' : 'gray.300'}
192
+ bg={activeTags.length > 0 ? 'rgba(var(--accent-rgb), 0.12)' : 'transparent'}
193
+ _hover={{ bg: activeTags.length > 0 ? 'rgba(var(--accent-rgb), 0.18)' : 'rgba(var(--accent-rgb), 0.12)', color: 'var(--accent)' }}
194
+ >
195
+ <HStack spacing={1.5}>
196
+ <LayerIcon />
197
+ <Text fontSize="11px" fontWeight="normal">Layers</Text>
198
+ {activeTags.length > 0 && (
199
+ <Box bg="var(--accent)" color="white" rounded="full" px={1} minW="14px" fontSize="9px" fontWeight="bold">
200
+ {activeTags.length}
201
+ </Box>
202
+ )}
203
+ </HStack>
204
+ </MenuButton>
205
+ </Tooltip>
206
+ <MenuList bg="var(--bg-panel)" borderColor="whiteAlpha.100" shadow="0 8px 32px rgba(0,0,0,0.5)" rounded="xl" py={2} maxH="300px" overflowY="auto">
207
+ {availableTags.length === 0 ? (
208
+ <Box px={4} py={2}>
209
+ <Text fontSize="xs" color="gray.500">No tags in workspace</Text>
210
+ </Box>
211
+ ) : (
212
+ <MenuOptionGroup
213
+ title="Filter by Tags"
214
+ type="checkbox"
215
+ value={activeTags}
216
+ onChange={(val) => setActiveTags?.(val as string[])}
217
+ >
218
+ {availableTags.map((tag) => (
219
+ <MenuItemOption
220
+ key={tag}
221
+ value={tag}
222
+ fontSize="xs"
223
+ _hover={{ bg: 'whiteAlpha.50' }}
224
+ _checked={{ color: 'var(--accent)' }}
225
+ >
226
+ {tag}
227
+ </MenuItemOption>
228
+ ))}
229
+ </MenuOptionGroup>
230
+ )}
231
+ {activeTags.length > 0 && (
232
+ <>
233
+ <MenuDivider borderColor="whiteAlpha.100" />
234
+ <MenuItem
235
+ fontSize="xs"
236
+ color="red.400"
237
+ _hover={{ bg: 'whiteAlpha.50' }}
238
+ onClick={() => setActiveTags?.([])}
239
+ >
240
+ Clear all filters
241
+ </MenuItem>
242
+ </>
243
+ )}
244
+ </MenuList>
245
+ </Menu>
246
+ </HStack>
247
+ )
248
+ }
@@ -0,0 +1,379 @@
1
+ import React, { memo } from 'react'
2
+ import type { ViewFloatingMenuSlots } from '../slots'
3
+
4
+ import {
5
+ HStack, Tooltip, Button, Box, Text, Popover, PopoverTrigger, Portal, PopoverContent, PopoverBody, IconButton, useDisclosure
6
+ } from '@chakra-ui/react'
7
+ import { DownloadIcon } from '@chakra-ui/icons'
8
+ import {
9
+ AddElementIcon as AddElementSvg,
10
+ EditIcon as PencilSvg,
11
+ EyeIcon as EyeSvg,
12
+ EyeOffIcon as EyeOffSvg,
13
+ ImportIcon,
14
+ ExpandExtrasIcon as ExpandExtrasSvg,
15
+ CollapseExtrasIcon as CollapseExtrasSvg,
16
+ FocusIcon as FocusSvg,
17
+ TagsIcon,
18
+ } from './Icons'
19
+ import { KbdHint } from './PanelUI'
20
+ import { useViewEditorContext } from '../pages/ViewEditor/context'
21
+ import type { Tag, ViewLayer } from '../types'
22
+
23
+ export interface ViewFloatingMenuProps extends ViewFloatingMenuSlots {
24
+ handleAddElementAtCenter: () => void
25
+ drawingMode: boolean
26
+ setDrawingMode: React.Dispatch<React.SetStateAction<boolean>>
27
+ hasDrawingPaths: boolean
28
+ drawingVisible: boolean
29
+ setDrawingVisible: React.Dispatch<React.SetStateAction<boolean>>
30
+ extrasOpen: boolean
31
+ setExtrasOpen: React.Dispatch<React.SetStateAction<boolean>>
32
+ disableImportExport?: boolean
33
+ onImport: () => void
34
+ onExport: () => void
35
+ onShare?: () => void
36
+ focusMode: boolean
37
+ onFocusModeChange: (enabled: boolean) => void
38
+
39
+ // Tag-related props
40
+ allTags: string[]
41
+ layers: ViewLayer[]
42
+ tagColors: Record<string, Tag>
43
+ hiddenTags: string[]
44
+ toggleTagVisibility: (tag: string) => void
45
+ toggleLayerVisibility: (layer: ViewLayer) => void
46
+ tagCounts: Record<string, number>
47
+ layerElementCounts: Record<number, number>
48
+ setHighlightedTags: (tags: string[] | null) => void
49
+ setHighlightColor: (color: string | null) => void
50
+
51
+ toolbarSlot?: React.ReactNode
52
+ }
53
+
54
+ /**
55
+ * Name: Floating Menu
56
+ * Role: Shows add element, draw, export, import, and share buttons with a collapsible section.
57
+ * Location: Floating at the bottom center of the editor.
58
+ * Aliases: Bottom bar, Action bar.
59
+ */
60
+ function ViewFloatingMenu({
61
+ handleAddElementAtCenter,
62
+ drawingMode,
63
+ setDrawingMode,
64
+ hasDrawingPaths,
65
+ drawingVisible,
66
+ setDrawingVisible,
67
+ extrasOpen,
68
+ setExtrasOpen,
69
+ disableImportExport = false,
70
+ onImport,
71
+ onExport,
72
+ focusMode,
73
+ onFocusModeChange,
74
+ allTags,
75
+ layers,
76
+ tagColors,
77
+ hiddenTags,
78
+ toggleTagVisibility,
79
+ toggleLayerVisibility,
80
+ tagCounts,
81
+ layerElementCounts,
82
+ setHighlightedTags,
83
+ setHighlightColor,
84
+ shareSlot,
85
+ toolbarSlot,
86
+ }: ViewFloatingMenuProps) {
87
+ const { canEdit } = useViewEditorContext()
88
+ const { isOpen: isTagsOpen, onClose: onTagsClose, onToggle: onTagsToggle } = useDisclosure()
89
+
90
+ return (
91
+ <HStack
92
+ position="absolute"
93
+ left="50%"
94
+ transform="translateX(-50%)"
95
+ zIndex={20}
96
+ spacing={0}
97
+ bg="var(--bg-panel)"
98
+ border="1px solid"
99
+ borderColor="whiteAlpha.100"
100
+ rounded="xl"
101
+ boxShadow="0 8px 32px rgba(0,0,0,0.5)"
102
+ backdropFilter="blur(20px)"
103
+ px={1.5}
104
+ py={1.5}
105
+ pointerEvents="auto"
106
+ style={{ bottom: 'calc(1.25rem + env(safe-area-inset-bottom, 0px))' } as React.CSSProperties}
107
+ transition="all 0.2s cubic-bezier(0.4, 0, 0.2, 1)"
108
+ >
109
+ <Tooltip label="Create new element (C)" placement="top" openDelay={200}>
110
+ <Button
111
+ variant="ghost"
112
+ h="28px"
113
+ px={2.5}
114
+ color="var(--accent)"
115
+ isDisabled={!canEdit}
116
+ _disabled={{ opacity: 0.35, cursor: 'not-allowed' }}
117
+ _hover={{ bg: 'rgba(var(--accent-rgb), 0.12)', color: 'var(--accent)' }}
118
+ onClick={() => handleAddElementAtCenter()}
119
+ >
120
+ <HStack spacing={1.5}>
121
+ <AddElementSvg />
122
+ <Text fontSize="11px" fontWeight="semibold">
123
+ Add Element
124
+ </Text>
125
+ <KbdHint>C</KbdHint>
126
+ </HStack>
127
+ </Button>
128
+ </Tooltip>
129
+
130
+ <Box w="1px" h="16px" bg="whiteAlpha.100" flexShrink={0} mx={0.5} />
131
+ <Tooltip label={focusMode ? 'Show context' : 'Focus on this view'} placement="top" openDelay={200}>
132
+ <Button
133
+ variant="ghost"
134
+ h="28px"
135
+ px={2.5}
136
+ color={focusMode ? 'var(--accent)' : 'gray.300'}
137
+ bg={focusMode ? 'rgba(var(--accent-rgb), 0.12)' : 'transparent'}
138
+ _hover={{ bg: 'rgba(var(--accent-rgb), 0.12)', color: 'var(--accent)' }}
139
+ onClick={() => onFocusModeChange(!focusMode)}
140
+ >
141
+ <HStack spacing={1.5}>
142
+ <FocusSvg />
143
+ <Text fontSize="11px" fontWeight="normal">Focus View</Text>
144
+ <Box w="6px" h="6px" rounded="full" bg={focusMode ? 'var(--accent)' : 'gray.500'} />
145
+ </HStack>
146
+ </Button>
147
+ </Tooltip>
148
+ <Box w="1px" h="16px" bg="whiteAlpha.100" flexShrink={0} mx={0.5} />
149
+
150
+ {(allTags.length > 0 || layers.length > 0) && (
151
+ <>
152
+ <Popover
153
+ isOpen={isTagsOpen}
154
+ onClose={() => { onTagsClose(); setHighlightedTags(null); setHighlightColor(null) }}
155
+ placement="top"
156
+ isLazy
157
+ closeOnBlur
158
+ >
159
+ <PopoverTrigger>
160
+ <Button
161
+ variant="ghost" h="28px" px={2.5}
162
+ color={isTagsOpen ? 'var(--accent)' : 'gray.300'}
163
+ _hover={{ bg: 'rgba(var(--accent-rgb), 0.12)', color: 'var(--accent)' }}
164
+ onClick={onTagsToggle}
165
+ >
166
+ <HStack spacing={1.5}>
167
+ <TagsIcon />
168
+ <Text fontSize="11px" fontWeight="normal">Tags</Text>
169
+ </HStack>
170
+ </Button>
171
+ </PopoverTrigger>
172
+ <Portal>
173
+ <PopoverContent
174
+ bg="var(--bg-panel)"
175
+ backdropFilter="blur(20px)"
176
+ borderColor="whiteAlpha.100"
177
+ boxShadow="0 8px 32px rgba(0,0,0,0.5)"
178
+ borderRadius="lg"
179
+ width="220px"
180
+ _focus={{ boxShadow: 'none' }}
181
+ onMouseLeave={() => { setHighlightedTags(null); setHighlightColor(null) }}
182
+ >
183
+ <PopoverBody p={2} maxH="360px" overflowY="auto">
184
+ {layers.map(layer => {
185
+ const isHidden = layer.tags.length > 0 && layer.tags.every(t => hiddenTags.includes(t))
186
+ return (
187
+ <HStack
188
+ key={`layer-${layer.id}`}
189
+ px={2}
190
+ py={1}
191
+ spacing={2}
192
+ borderRadius="md"
193
+ _hover={{ bg: 'whiteAlpha.100' }}
194
+ onMouseEnter={() => { setHighlightedTags(layer.tags); setHighlightColor(layer.color || '') }}
195
+ opacity={isHidden ? 0.4 : 1}
196
+ transition="opacity 0.15s"
197
+ >
198
+ <Box w="10px" h="10px" rounded="full" bg={layer.color || 'gray.500'} flexShrink={0} />
199
+ <Text fontSize="xs" fontWeight="600" color="white" flex={1} isTruncated>
200
+ {layer.name}
201
+ </Text>
202
+ <Text fontSize="10px" color="gray.600" flexShrink={0}>
203
+ {layerElementCounts[layer.id] ?? 0}
204
+ </Text>
205
+ <IconButton
206
+ aria-label={isHidden ? 'Show layer' : 'Hide layer'}
207
+ icon={isHidden ? <EyeOffSvg size={12} /> : <EyeSvg size={12} />}
208
+ size="xs"
209
+ variant="ghost"
210
+ color={isHidden ? 'whiteAlpha.300' : 'whiteAlpha.600'}
211
+ _hover={{ color: 'white', bg: 'whiteAlpha.200' }}
212
+ onClick={(e) => { e.stopPropagation(); toggleLayerVisibility(layer) }}
213
+ flexShrink={0}
214
+ />
215
+ </HStack>
216
+ )
217
+ })}
218
+
219
+ {allTags.map(tag => {
220
+ const isHidden = hiddenTags.includes(tag)
221
+ return (
222
+ <HStack
223
+ key={`tag-${tag}`}
224
+ px={2}
225
+ py={1}
226
+ spacing={2}
227
+ borderRadius="md"
228
+ onMouseEnter={() => { setHighlightedTags([tag]); setHighlightColor(tagColors[tag]?.color || '') }}
229
+ opacity={isHidden ? 0.4 : 1}
230
+ transition="opacity 0.15s"
231
+ >
232
+ <Box w="8px" h="8px" rounded="full" bg={tagColors[tag]?.color || '#A0AEC0'} flexShrink={0} />
233
+ <Text fontSize="xs" fontWeight="600" color="gray.300" flex={1} isTruncated>
234
+ {tag}
235
+ </Text>
236
+ <Text fontSize="10px" color="gray.600" flexShrink={0}>
237
+ {tagCounts[tag] ?? 0}
238
+ </Text>
239
+ <IconButton
240
+ aria-label={isHidden ? 'Show tag' : 'Hide tag'}
241
+ icon={isHidden ? <EyeOffSvg size={12} /> : <EyeSvg size={12} />}
242
+ size="xs"
243
+ variant="ghost"
244
+ color={isHidden ? 'whiteAlpha.300' : 'whiteAlpha.600'}
245
+ _hover={{ color: 'white', bg: 'whiteAlpha.200' }}
246
+ onClick={(e) => { e.stopPropagation(); toggleTagVisibility(tag) }}
247
+ flexShrink={0}
248
+ />
249
+ </HStack>
250
+ )
251
+ })}
252
+ </PopoverBody>
253
+ </PopoverContent>
254
+ </Portal>
255
+ </Popover>
256
+ <Box w="1px" h="16px" bg="whiteAlpha.100" flexShrink={0} mx={0.5} />
257
+ </>
258
+ )}
259
+
260
+ {/* Draw mode toggle */}
261
+ <Tooltip
262
+ label={drawingMode ? 'Exit drawing mode' : 'Draw on diagram'}
263
+ placement="top"
264
+ openDelay={200}
265
+ >
266
+ <Button
267
+ variant="ghost"
268
+ h="28px"
269
+ px={2.5}
270
+ color={drawingMode ? 'var(--accent)' : 'gray.300'}
271
+ bg={drawingMode ? 'rgba(var(--accent-rgb), 0.12)' : 'transparent'}
272
+ _hover={{ bg: drawingMode ? 'rgba(var(--accent-rgb), 0.18)' : 'rgba(var(--accent-rgb), 0.12)', color: 'var(--accent)' }}
273
+ onClick={() => setDrawingMode((v) => !v)}
274
+ aria-label={drawingMode ? 'Exit drawing mode' : 'Enter drawing mode'}
275
+ >
276
+ <HStack spacing={1.5}>
277
+ <PencilSvg />
278
+ <Text fontSize="11px" fontWeight="normal">Draw</Text>
279
+ </HStack>
280
+ </Button>
281
+ </Tooltip>
282
+
283
+ {/* Drawing layer visibility toggle - only shown when there are strokes */}
284
+ {hasDrawingPaths && (
285
+ <>
286
+ <Box w="1px" h="16px" bg="whiteAlpha.100" flexShrink={0} mx={0.5} />
287
+ <Tooltip
288
+ label={drawingVisible ? 'Hide Drawings' : 'Show Drawings'}
289
+ placement="top"
290
+ openDelay={200}
291
+ >
292
+ <Button
293
+ variant="ghost"
294
+ h="28px"
295
+ minW="28px"
296
+ px={2}
297
+ color={drawingVisible ? 'gray.300' : 'gray.600'}
298
+ _hover={{ bg: 'rgba(var(--accent-rgb), 0.12)', color: 'var(--accent)' }}
299
+ onClick={() => setDrawingVisible((v) => !v)}
300
+ aria-label={drawingVisible ? 'Hide Drawings' : 'Show Drawings'}
301
+ >
302
+ {drawingVisible ? (
303
+ <HStack spacing={1.5}>
304
+ <EyeSvg />
305
+ <Text fontSize="11px" fontWeight="normal">Hide Drawings</Text>
306
+ </HStack>
307
+ ) : (
308
+ <HStack spacing={1.5}>
309
+ <EyeOffSvg />
310
+ <Text fontSize="11px" fontWeight="normal">Show Drawings</Text>
311
+ </HStack>
312
+ )}
313
+ </Button>
314
+ </Tooltip>
315
+ </>
316
+ )}
317
+
318
+ {extrasOpen && (
319
+ <>
320
+ <Box w="1px" h="16px" bg="whiteAlpha.100" flexShrink={0} mx={0.5} />
321
+ <HStack spacing={1} pl={1} pr={0.5}>
322
+
323
+ <Button
324
+ variant="ghost"
325
+ h="28px"
326
+ px={2.5}
327
+ color="gray.300"
328
+ leftIcon={<ImportIcon />}
329
+ isDisabled={disableImportExport}
330
+ _disabled={{ opacity: 0.35, cursor: 'not-allowed' }}
331
+ _hover={{ bg: 'rgba(var(--accent-rgb), 0.12)', color: 'var(--accent)' }}
332
+ onClick={onImport}
333
+ >
334
+ <Text fontSize="11px" fontWeight="normal">Import</Text>
335
+ </Button>
336
+
337
+ <Box w="1px" h="16px" bg="whiteAlpha.100" flexShrink={0} mx={0.5} />
338
+
339
+ <Button
340
+ variant="ghost"
341
+ h="28px"
342
+ px={2.5}
343
+ color="gray.300"
344
+ leftIcon={<DownloadIcon />}
345
+ isDisabled={disableImportExport}
346
+ _disabled={{ opacity: 0.35, cursor: 'not-allowed' }}
347
+ _hover={{ bg: 'rgba(var(--accent-rgb), 0.12)', color: 'var(--accent)' }}
348
+ onClick={onExport}
349
+ >
350
+ <Text fontSize="11px" fontWeight="normal">Export</Text>
351
+ </Button>
352
+
353
+ {shareSlot}
354
+ {toolbarSlot}
355
+ </HStack>
356
+ </>
357
+ )}
358
+
359
+ <Box w="1px" h="16px" bg="whiteAlpha.100" flexShrink={0} mx={0.5} />
360
+ <Button
361
+ variant="ghost"
362
+ h="28px"
363
+ minW="36px"
364
+ px={2}
365
+ display="inline-flex"
366
+ alignItems="center"
367
+ justifyContent="center"
368
+ color="gray.300"
369
+ _hover={{ bg: 'rgba(var(--accent-rgb), 0.12)', color: 'var(--accent)' }}
370
+ onClick={() => setExtrasOpen((prev) => !prev)}
371
+ aria-label={extrasOpen ? 'Collapse extras' : 'Expand extras'}
372
+ >
373
+ {extrasOpen ? <CollapseExtrasSvg /> : <ExpandExtrasSvg />}
374
+ </Button>
375
+ </HStack>
376
+ )
377
+ }
378
+
379
+ export default memo(ViewFloatingMenu)