@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,451 @@
1
+ import { useEffect, useRef, useState } from 'react'
2
+ import { useAccentColor } from '../context/ThemeContext'
3
+ import { hexToRgba } from '../constants/colors'
4
+ import { Handle, Position } from 'reactflow'
5
+ import { keyframes } from '@emotion/react'
6
+ import {
7
+ Box,
8
+ Flex,
9
+ IconButton,
10
+ Input,
11
+ Menu,
12
+ MenuButton,
13
+ MenuItem,
14
+ MenuList,
15
+ Portal,
16
+ Spinner,
17
+ Text,
18
+ Tooltip,
19
+ useDisclosure,
20
+ } from '@chakra-ui/react'
21
+ import { api } from '../api/client'
22
+
23
+ const shimmer = keyframes`
24
+ 0% { background-position: -260px 0 }
25
+ 100% { background-position: 260px 0 }
26
+ `
27
+
28
+ const badgePop = keyframes`
29
+ from { opacity: 0; transform: scale(0.7); }
30
+ to { opacity: 1; transform: scale(1); }
31
+ `
32
+
33
+ // Badges float OUTSIDE the card in the gap between nodes - avoids competing with card content
34
+ const WASD_BADGE_POS = {
35
+ w: { bottom: '-18px', left: 'calc(50% - 15px)' },
36
+ s: { top: '-18px', left: 'calc(50% - 15px)' },
37
+ a: { right: '-18px', top: 'calc(50% - 12px)' },
38
+ d: { left: '-18px', top: 'calc(50% - 12px)' },
39
+ } as const
40
+
41
+ export interface ViewGridNodeData {
42
+ id: number
43
+ name: string
44
+ level_label: string | null
45
+ counts?: { nodes: number; edges: number }
46
+ focused: boolean
47
+ canEdit: boolean
48
+ isEditing: boolean
49
+ editName: string
50
+ onFocus: () => void
51
+ onOpen: () => void
52
+ onStartRename: () => void
53
+ onDetails: () => void
54
+ onDelete: () => void
55
+ onShare: () => void
56
+ onEditNameChange: (v: string) => void
57
+ onEditCommit: () => void
58
+ onEditCancel: () => void
59
+ /** On touch/mobile: tap navigates directly instead of just focusing */
60
+ isMobile?: boolean
61
+ /** Key that navigates to this card from the currently selected card */
62
+ wasdKey?: 'w' | 'a' | 's' | 'd'
63
+ }
64
+
65
+ export default function ViewGridNode({ data }: { data: ViewGridNodeData }) {
66
+ const inputRef = useRef<HTMLInputElement>(null)
67
+ const boxRef = useRef<HTMLDivElement>(null)
68
+ const { accent } = useAccentColor()
69
+ const [thumbnailUrl, setThumbnailUrl] = useState<string | null>(null)
70
+ const [isLoading, setIsLoading] = useState(false)
71
+ const [hasRequested, setHasRequested] = useState(false)
72
+
73
+ const { isOpen: isMenuOpen, onOpen: onMenuOpen, onClose: onMenuClose } = useDisclosure()
74
+ const [isTooltipOpen, setIsTooltipOpen] = useState(false)
75
+
76
+ useEffect(() => {
77
+ if (!isMenuOpen && !isTooltipOpen) return
78
+
79
+ const closeFloatingUi = () => {
80
+ onMenuClose()
81
+ setIsTooltipOpen(false)
82
+ }
83
+
84
+ window.addEventListener('wheel', closeFloatingUi, { passive: true, capture: true })
85
+ window.addEventListener('touchmove', closeFloatingUi, { passive: true, capture: true })
86
+
87
+ return () => {
88
+ window.removeEventListener('wheel', closeFloatingUi, { capture: true })
89
+ window.removeEventListener('touchmove', closeFloatingUi, { capture: true })
90
+ }
91
+ }, [isMenuOpen, isTooltipOpen, onMenuClose])
92
+
93
+ useEffect(() => {
94
+ if (data.isEditing && inputRef.current) {
95
+ inputRef.current.focus()
96
+ inputRef.current.select()
97
+ }
98
+ }, [data.isEditing])
99
+
100
+ // Trigger thumbnail request when card becomes visible
101
+ useEffect(() => {
102
+ if (!boxRef.current) return
103
+ const observer = new IntersectionObserver(
104
+ (entries) => {
105
+ if (entries[0]?.isIntersecting) {
106
+ setHasRequested(true)
107
+ observer.disconnect()
108
+ }
109
+ },
110
+ { threshold: 0.1 }
111
+ )
112
+ observer.observe(boxRef.current)
113
+ return () => observer.disconnect()
114
+ }, [])
115
+
116
+ useEffect(() => {
117
+ if (!hasRequested) return
118
+
119
+ let active = true
120
+ let url: string | null = null
121
+
122
+ const loadThumbnail = async () => {
123
+ setIsLoading(true)
124
+ try {
125
+ url = await api.workspace.views.thumbnail(data.id)
126
+ if (active && url) {
127
+ setThumbnailUrl(url)
128
+ }
129
+ } catch (err) {
130
+ console.error('Failed to load thumbnail:', err)
131
+ } finally {
132
+ if (active) setIsLoading(false)
133
+ }
134
+ }
135
+
136
+ loadThumbnail()
137
+
138
+ return () => {
139
+ active = false
140
+ if (url) {
141
+ URL.revokeObjectURL(url)
142
+ }
143
+ }
144
+ }, [hasRequested, data.id])
145
+
146
+ const borderColor = data.focused ? accent : 'rgba(255,255,255,0.14)'
147
+
148
+ const boxShadow = data.focused
149
+ ? `0 0 24px ${hexToRgba(accent, 0.4)}`
150
+ : '0 8px 24px rgba(0,0,0,0.4), inset 0 1px 0 rgba(255,255,255,0.05)'
151
+
152
+ return (
153
+ // Outer container: sizing + group context, overflow visible for the "New Child" hover button
154
+ // `nopan` class tells ReactFlow's d3-zoom to not start panning on mousedown within this node
155
+ <Box
156
+ ref={boxRef}
157
+ role="group"
158
+ className="nopan"
159
+ w="260px"
160
+ h="150px"
161
+ position="relative"
162
+ userSelect="none"
163
+ transition="opacity 0.3s cubic-bezier(0.16, 1, 0.3, 1)"
164
+ >
165
+ <Handle
166
+ type="target"
167
+ position={Position.Top}
168
+ style={{ opacity: 0, pointerEvents: 'none' }}
169
+ isConnectable={false}
170
+ />
171
+ <Handle
172
+ type="source"
173
+ position={Position.Bottom}
174
+ style={{ opacity: 0, pointerEvents: 'none' }}
175
+ isConnectable={false}
176
+ />
177
+
178
+ {/* Card surface - clipped to border radius */}
179
+ <Box
180
+ position="absolute"
181
+ inset={0}
182
+ bg="var(--bg-card-solid)"
183
+ borderRadius="12px"
184
+ border="1.5px solid"
185
+ borderColor={borderColor}
186
+ boxShadow={boxShadow}
187
+ transform={data.focused ? 'scale(1.025) translateY(-2px)' : 'scale(1)'}
188
+ cursor={data.canEdit ? 'pointer' : 'pointer'}
189
+ transition="all 0.2s cubic-bezier(0.16, 1, 0.3, 1)"
190
+ overflow="hidden"
191
+ onClick={data.isMobile ? data.onOpen : data.onFocus}
192
+ onDoubleClick={data.onOpen}
193
+ onMouseDown={(e) => e.stopPropagation()}
194
+ _groupHover={{
195
+ borderColor: data.focused
196
+ ? borderColor
197
+ : data.canEdit ? 'rgba(255,255,255,0.3)' : 'rgba(255,255,255,0.22)',
198
+ boxShadow: data.focused
199
+ ? boxShadow
200
+ : data.canEdit
201
+ ? '0 14px 40px rgba(0,0,0,0.6), inset 0 1px 0 rgba(255,255,255,0.12)'
202
+ : '0 12px 32px rgba(0,0,0,0.5), inset 0 1px 0 rgba(255,255,255,0.08)',
203
+ transform: data.focused
204
+ ? undefined
205
+ : data.canEdit ? 'scale(1.015) translateY(-1px)' : undefined,
206
+ }}
207
+ >
208
+ {/* Thumbnail area - Full height behind info area */}
209
+ <Box
210
+ position="absolute"
211
+ inset={0}
212
+ overflow="hidden"
213
+ borderRadius="8px 8px 0 0"
214
+ flexShrink={0}
215
+ bg="var(--bg-card-solid)"
216
+ >
217
+ {thumbnailUrl ? (
218
+ <Box
219
+ as="img"
220
+ src={thumbnailUrl}
221
+ w="100%"
222
+ h="100%"
223
+ objectFit="contain"
224
+ display="block"
225
+ p={2}
226
+ bg="var(--bg-card-solid)"
227
+ style={{
228
+ filter: 'brightness(0) saturate(100%) invert(35%) sepia(26%) forum-blue(82%) hue-rotate(180deg) brightness(95%) contrast(90%)',
229
+ }}
230
+ />
231
+ ) : (
232
+ <Flex
233
+ w="100%"
234
+ h="100%"
235
+ align="center"
236
+ justify="center"
237
+ background="linear-gradient(90deg, var(--bg-element) 25%, color-mix(in srgb, var(--bg-element), white 5%) 50%, var(--bg-element) 75%)"
238
+ backgroundSize="520px 100%"
239
+ sx={{ animation: !isLoading ? `${shimmer} 1.4s ease infinite` : 'none' }}
240
+ >
241
+ {isLoading && <Spinner size="sm" color="whiteAlpha.400" />}
242
+ </Flex>
243
+ )}
244
+ </Box>
245
+
246
+ {/* Info area - name, tech etc */}
247
+ <Flex
248
+ direction="column"
249
+ position="absolute"
250
+ bottom={0}
251
+ left={0}
252
+ right={0}
253
+ h="64px"
254
+ px={3}
255
+ pt={2}
256
+ pb={2}
257
+ bg="color-mix(in srgb, rgba(var(--bg-element-rgb), 0.7), black 20%)"
258
+ backdropFilter="blur(1px) saturate(180%)"
259
+ borderTop="1px solid rgba(255, 255, 255, 0.08)"
260
+ boxShadow="0 -4px 12px rgba(0,0,0,0.15)"
261
+ >
262
+ {/* Name row */}
263
+ <Flex align="flex-start" gap={1.5} flex={1} minH={0}>
264
+ {data.isEditing ? (
265
+ <Input
266
+ ref={inputRef}
267
+ value={data.editName}
268
+ size="sm"
269
+ variant="flushed"
270
+ borderColor="var(--accent)"
271
+ color="gray.100"
272
+ fontWeight="semibold"
273
+ flex={1}
274
+ minW={0}
275
+ fontSize="sm"
276
+ onChange={(e) => data.onEditNameChange(e.target.value)}
277
+ onBlur={data.onEditCommit}
278
+ onKeyDown={(e) => {
279
+ if (e.key === 'Enter') data.onEditCommit()
280
+ if (e.key === 'Escape') data.onEditCancel()
281
+ e.stopPropagation()
282
+ }}
283
+ onClick={(e) => e.stopPropagation()}
284
+ />
285
+ ) : (
286
+ <Text
287
+ flex={1}
288
+ minW={0}
289
+ fontSize="sm"
290
+ fontWeight="semibold"
291
+ color="gray.100"
292
+ noOfLines={1}
293
+ lineHeight={1.35}
294
+ textShadow="0 1px 2px rgba(0,0,0,0.5)"
295
+ >
296
+ {data.name}
297
+ </Text>
298
+ )}
299
+
300
+ {!data.isEditing && (
301
+ <Flex align="center" gap={1} onClick={(e) => e.stopPropagation()} mt="-2px">
302
+ <Menu
303
+ isLazy
304
+ placement="bottom"
305
+ closeOnBlur={true}
306
+ closeOnSelect={true}
307
+ isOpen={isMenuOpen}
308
+ onOpen={onMenuOpen}
309
+ onClose={onMenuClose}
310
+ >
311
+ <Tooltip
312
+ label="More actions"
313
+ placement="top"
314
+ openDelay={400}
315
+ isOpen={isTooltipOpen && !isMenuOpen}
316
+ hasArrow
317
+ >
318
+ <MenuButton
319
+ as={IconButton}
320
+ aria-label="More actions"
321
+ icon={
322
+ <Text fontSize="13px" lineHeight={1} letterSpacing="0.12em">
323
+ ···
324
+ </Text>
325
+ }
326
+ size="xs"
327
+ variant="ghost"
328
+ color="gray.400"
329
+ _hover={{ color: 'gray.100', bg: 'whiteAlpha.200' }}
330
+ h="22px"
331
+ minW="22px"
332
+ onMouseEnter={() => setIsTooltipOpen(true)}
333
+ onMouseLeave={() => setIsTooltipOpen(false)}
334
+ />
335
+ </Tooltip>
336
+ <Portal>
337
+ <MenuList
338
+ bg="var(--bg-panel)"
339
+ borderColor="var(--border-main)"
340
+ minW="152px"
341
+ py={1}
342
+ zIndex={8}
343
+ fontSize="sm"
344
+ boxShadow="xl"
345
+ onMouseLeave={onMenuClose}
346
+ >
347
+ {data.canEdit && (
348
+ <MenuItem
349
+ onClick={(e) => { e.stopPropagation(); data.onStartRename(); onMenuClose() }}
350
+ bg="transparent"
351
+ color="gray.300"
352
+ _hover={{ bg: 'whiteAlpha.100', color: 'white' }}
353
+ _focus={{ bg: 'whiteAlpha.100' }}
354
+ >
355
+ Rename
356
+ </MenuItem>
357
+ )}
358
+ <MenuItem
359
+ onClick={(e) => { e.stopPropagation(); data.onDetails(); onMenuClose() }}
360
+ bg="transparent"
361
+ color="gray.300"
362
+ _hover={{ bg: 'whiteAlpha.100', color: 'white' }}
363
+ _focus={{ bg: 'whiteAlpha.100' }}
364
+ >
365
+ Details
366
+ </MenuItem>
367
+ {data.canEdit && (
368
+ <MenuItem
369
+ onClick={(e) => { e.stopPropagation(); data.onDelete(); onMenuClose() }}
370
+ bg="transparent"
371
+ color="red.400"
372
+ _hover={{ bg: 'whiteAlpha.100', color: 'red.300' }}
373
+ _focus={{ bg: 'whiteAlpha.100' }}
374
+ >
375
+ Delete
376
+ </MenuItem>
377
+ )}
378
+ </MenuList>
379
+ </Portal>
380
+ </Menu>
381
+ </Flex>
382
+ )}
383
+ </Flex>
384
+
385
+ {/* Bottom row: level label + stats */}
386
+ <Flex align="center" justify="space-between" flexShrink={0} gap={1}>
387
+ {data.level_label ? (
388
+ <Text
389
+ fontSize="9px"
390
+ color="var(--accent)"
391
+ textTransform="uppercase"
392
+ letterSpacing="0.1em"
393
+ fontWeight="bold"
394
+ flexShrink={0}
395
+ textShadow="0 1px 2px rgba(0,0,0,0.5)"
396
+ >
397
+ {data.level_label}
398
+ </Text>
399
+ ) : (
400
+ <Box flexShrink={0} />
401
+ )}
402
+
403
+ <Text
404
+ fontSize="10px"
405
+ color="gray.400"
406
+ letterSpacing="0.01em"
407
+ flexShrink={0}
408
+ textShadow="0 1px 2px rgba(0,0,0,0.5)"
409
+ >
410
+ {data.counts
411
+ ? `${data.counts.nodes}n · ${data.counts.edges}e`
412
+ : '-'}
413
+ </Text>
414
+ </Flex>
415
+ </Flex>
416
+ </Box>
417
+
418
+ {/* WASD navigation hint badge - floats outside the card in the gap between nodes */}
419
+ {data.wasdKey && !data.isEditing && (
420
+ <Box
421
+ position="absolute"
422
+ {...WASD_BADGE_POS[data.wasdKey]}
423
+ zIndex={8}
424
+ pointerEvents="none"
425
+ sx={{ animation: `${badgePop} 0.22s cubic-bezier(0.16, 1, 0.3, 1) both` }}
426
+ >
427
+ <Flex
428
+ align="center"
429
+ justify="center"
430
+ w="30px"
431
+ h="24px"
432
+ bg="var(--bg-menu)"
433
+ border={`1.5px solid ${hexToRgba(accent, 0.65)}`}
434
+ borderRadius="5px"
435
+ boxShadow="0 3px 10px rgba(0,0,0,0.6), inset 0 1px 0 rgba(255,255,255,0.07)"
436
+ >
437
+ <Text
438
+ fontSize="11px"
439
+ fontWeight="800"
440
+ color="#c4dcf5"
441
+ lineHeight={1}
442
+ letterSpacing="0.04em"
443
+ >
444
+ {data.wasdKey.toUpperCase()}
445
+ </Text>
446
+ </Flex>
447
+ </Box>
448
+ )}
449
+ </Box>
450
+ )
451
+ }
@@ -0,0 +1,60 @@
1
+ import React from 'react'
2
+ import { HStack, Text } from '@chakra-ui/react'
3
+ import { EditIcon } from '@chakra-ui/icons' // Or from './Icons' ? Let's use the one from chakra as in original code. Wait, original code uses from chakra. No, original code says: import { ChevronLeftIcon, ChevronRightIcon, DownloadIcon, EditIcon } from '@chakra-ui/icons'
4
+
5
+ export interface ViewHeaderButtonProps {
6
+ name?: string
7
+ onOpen: () => void
8
+ }
9
+
10
+ /**
11
+ * Name: Diagram Button
12
+ * Role: Appears on the headerbar and it opens the diagram details panel.
13
+ * Location: Headerbar at the top.
14
+ * Aliases: Diagram Name Button, Header Title.
15
+ */
16
+ export default function ViewHeaderButton({ name, onOpen }: ViewHeaderButtonProps) {
17
+ return (
18
+ <HStack
19
+ as="button"
20
+ onClick={onOpen}
21
+ spacing={2.5}
22
+ bg="linear-gradient(180deg, rgba(var(--bg-main-rgb), 0.98) 0%, rgba(var(--bg-main-rgb), 0.9) 100%)"
23
+ border="1px solid"
24
+ borderTop="none"
25
+ borderColor="whiteAlpha.100"
26
+ px={5}
27
+ py={0}
28
+ borderBottomRadius="12px"
29
+ borderTopRadius="0"
30
+ maxW="460px"
31
+ minH="34px"
32
+ top="39px"
33
+ cursor="pointer"
34
+ position="relative"
35
+ alignSelf="flex-start"
36
+ boxShadow="0 8px 16px rgba(0,0,0,0.22), 0 2px 4px rgba(0,0,0,0.18), inset 0 -1px 0 rgba(255,255,255,0.04)"
37
+ transition="all 0.2s"
38
+ role="group"
39
+ >
40
+ <EditIcon
41
+ boxSize="11px"
42
+ color="var(--accent)"
43
+ opacity={0.55}
44
+ transition="all 0.2s"
45
+ _groupHover={{ opacity: 1 }}
46
+ flexShrink={0}
47
+ />
48
+ <Text
49
+ fontSize="sm"
50
+ color="whiteAlpha.900"
51
+ fontWeight="700"
52
+ letterSpacing="0.01em"
53
+ noOfLines={1}
54
+ textShadow="0 1px 0 rgba(0,0,0,0.22)"
55
+ >
56
+ {name || 'Untitled View'}
57
+ </Text>
58
+ </HStack>
59
+ )
60
+ }
@@ -0,0 +1,162 @@
1
+ import { memo, useEffect, useState } from 'react'
2
+ import {
3
+ Box,
4
+ Button,
5
+ Divider,
6
+ FormControl,
7
+ FormLabel,
8
+ HStack,
9
+ Input,
10
+ Text,
11
+ Textarea,
12
+ useBreakpointValue,
13
+ VStack,
14
+ } from '@chakra-ui/react'
15
+ import { api } from '../api/client'
16
+ import type { ViewTreeNode } from '../types'
17
+ import SlidingPanel from './SlidingPanel'
18
+ import PanelHeader from './PanelHeader'
19
+ import LayoutSection from './LayoutSection'
20
+ import ScrollIndicatorWrapper from './ScrollIndicatorWrapper'
21
+
22
+ import { useContext } from 'react'
23
+ import { ViewEditorContext } from '../pages/ViewEditor/context'
24
+
25
+ interface Props {
26
+ isOpen: boolean
27
+ onClose: () => void
28
+ view: ViewTreeNode | null
29
+ canEdit?: boolean
30
+ onSave: (updated: ViewTreeNode) => void
31
+ hasBackdrop?: boolean
32
+ }
33
+
34
+ /**
35
+ * Name: View Details Panel
36
+ * Role: Opens on the right and allows view field updates.
37
+ * Location: Right side of the screen on desktop. Overlays screen on mobile.
38
+ * Aliases: View Properties, View Settings.
39
+ */
40
+ function ViewPanel({ isOpen, onClose, view, canEdit: canEditProp, onSave, hasBackdrop = true }: Props) {
41
+ const ctx = useContext(ViewEditorContext)
42
+ const canEdit = canEditProp ?? ctx?.canEdit ?? true
43
+ const isReadOnly = !canEdit
44
+ const isMobile = useBreakpointValue({ base: true, md: false }) ?? false
45
+ const [name, setName] = useState('')
46
+ const [description, setDescription] = useState('')
47
+ const [levelLabel, setLevelLabel] = useState('')
48
+ const [saving, setSaving] = useState(false)
49
+
50
+ useEffect(() => {
51
+ if (view) {
52
+ setName(view.name)
53
+ setDescription(view.description || '')
54
+ setLevelLabel(view.level_label || '')
55
+ }
56
+ }, [view])
57
+
58
+ useEffect(() => {
59
+ if (!isOpen) return
60
+ const handler = (e: KeyboardEvent) => {
61
+ if (e.key === 'Escape') onClose()
62
+ }
63
+ window.addEventListener('keydown', handler)
64
+ return () => window.removeEventListener('keydown', handler)
65
+ }, [isOpen, onClose])
66
+
67
+ const handleSave = async () => {
68
+ if (isReadOnly || !view) return
69
+ setSaving(true)
70
+ try {
71
+ const updated = await api.workspace.views.update(view.id, {
72
+ name: name.trim(),
73
+ label: levelLabel,
74
+ })
75
+ onSave({ ...view, name: updated.name, level_label: updated.label })
76
+ onClose()
77
+ } catch {
78
+ // intentionally empty
79
+ } finally {
80
+ setSaving(false)
81
+ }
82
+ }
83
+
84
+ return (
85
+ <SlidingPanel isOpen={isOpen} onClose={onClose} panelKey="view" side={isMobile ? 'left' : 'right'} width="320px" hasBackdrop={hasBackdrop}>
86
+ <PanelHeader title="View Details" onClose={onClose} />
87
+
88
+ {/* Body */}
89
+ <ScrollIndicatorWrapper px={4} py={4}>
90
+ <VStack spacing={4} align="stretch">
91
+ <FormControl isRequired>
92
+ <FormLabel>Name</FormLabel>
93
+ <Input
94
+ size="sm"
95
+ value={name}
96
+ isDisabled={isReadOnly}
97
+ onChange={(e) => setName(e.target.value)}
98
+ />
99
+ </FormControl>
100
+ <FormControl>
101
+ <FormLabel>Level Label</FormLabel>
102
+ <Input
103
+ size="sm"
104
+ value={levelLabel}
105
+ isDisabled={isReadOnly}
106
+ onChange={(e) => setLevelLabel(e.target.value)}
107
+ placeholder="e.g. System Context, Containers…"
108
+ />
109
+ </FormControl>
110
+ <FormControl>
111
+ <FormLabel>Description</FormLabel>
112
+ <Textarea
113
+ size="sm"
114
+ value={description}
115
+ isDisabled={isReadOnly}
116
+ onChange={(e) => setDescription(e.target.value)}
117
+ placeholder="Optional description"
118
+ rows={4}
119
+ />
120
+ </FormControl>
121
+ <LayoutSection view={view} canEdit={canEdit} />
122
+
123
+ {view && (
124
+ <Box pt={2} borderTop="1px solid" borderColor="whiteAlpha.50">
125
+ <Text fontSize="xs" color="gray.600">
126
+ Created {new Date(view.created_at).toLocaleString()}
127
+ </Text>
128
+ <Text fontSize="xs" color="gray.600">
129
+ Updated {new Date(view.updated_at).toLocaleString()}
130
+ </Text>
131
+ </Box>
132
+ )}
133
+ </VStack>
134
+ </ScrollIndicatorWrapper>
135
+
136
+ <Divider borderColor="whiteAlpha.100" />
137
+
138
+ {/* Footer */}
139
+ <HStack px={4} py={3} justify="flex-end" flexShrink={0}>
140
+ <HStack>
141
+ <Button variant="ghost" size="sm" onClick={onClose}>
142
+ Cancel
143
+ </Button>
144
+ {canEdit && (
145
+ <Button
146
+ size="sm"
147
+ px={5}
148
+ colorScheme="blue"
149
+ onClick={handleSave}
150
+ isLoading={saving}
151
+ isDisabled={isReadOnly || !name.trim()}
152
+ >
153
+ Save
154
+ </Button>
155
+ )}
156
+ </HStack>
157
+ </HStack>
158
+ </SlidingPanel>
159
+ )
160
+ }
161
+
162
+ export default memo(ViewPanel)