@likec4/diagram 1.20.3 → 1.21.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 (90) hide show
  1. package/README.md +2 -2
  2. package/dist/base/BaseXYFlow.js +1 -1
  3. package/dist/base/example/_types.d.ts +3 -3
  4. package/dist/base/example/custom.d.ts +3 -2
  5. package/dist/base/primitives/compound/CompoundActionButton.js +1 -1
  6. package/dist/base/primitives/compound/CompoundDetailsButton.js +1 -1
  7. package/dist/base/primitives/compound/CompoundNodeContainer.js +1 -1
  8. package/dist/base/primitives/compound/CompoundTitle.css.d.ts +1 -0
  9. package/dist/base/primitives/compound/CompoundTitle.css.js +2 -0
  10. package/dist/base/primitives/compound/CompoundTitle.d.ts +2 -2
  11. package/dist/base/primitives/compound/CompoundTitle.js +17 -6
  12. package/dist/base/primitives/edge/EdgeLabel.js +1 -1
  13. package/dist/base/primitives/element/ElementActionButtons.js +1 -1
  14. package/dist/base/primitives/element/ElementDetailsButton.js +1 -1
  15. package/dist/base/primitives/element/ElementNodeContainer.d.ts +1 -1
  16. package/dist/base/primitives/element/ElementNodeContainer.js +15 -0
  17. package/dist/base/primitives/element/ElementShape.js +1 -1
  18. package/dist/base/primitives/element/ElementTitle.css.d.ts +0 -1
  19. package/dist/base/primitives/element/ElementTitle.css.js +8 -8
  20. package/dist/base/primitives/element/ElementTitle.d.ts +1 -1
  21. package/dist/base/primitives/element/ElementTitle.js +4 -2
  22. package/dist/base/primitives/element/styles.css.d.ts +3 -0
  23. package/dist/base/primitives/element/styles.css.js +6 -0
  24. package/dist/context/DiagramEventHandlers.js +1 -1
  25. package/dist/context/DiagramFeatures.js +1 -1
  26. package/dist/context/RootContainer.js +1 -1
  27. package/dist/hooks/useDiagram.d.ts +1 -2
  28. package/dist/hooks/useXYFlow.js +1 -1
  29. package/dist/likec4diagram/DiagramUI.d.ts +1 -2
  30. package/dist/likec4diagram/DiagramUI.js +3 -3
  31. package/dist/likec4diagram/XYFlow.js +1 -1
  32. package/dist/likec4diagram/custom/edges/NotePopover.js +1 -1
  33. package/dist/likec4diagram/custom/edges/RelationshipEdge.js +1 -1
  34. package/dist/likec4diagram/custom/edges/RelationshipsDropdownMenu.js +4 -4
  35. package/dist/likec4diagram/custom/nodes/nodeTypes.js +8 -8
  36. package/dist/likec4diagram/custom/nodes/toolbar/_shared.js +2 -2
  37. package/dist/likec4diagram/state/assign.js +1 -1
  38. package/dist/likec4diagram/state/machine.js +5 -5
  39. package/dist/likec4diagram/types.d.ts +2 -2
  40. package/dist/likec4diagram/ui/controls/Controls.js +1 -1
  41. package/dist/likec4diagram/ui/diagram-title/DiagramTitlePanel.js +1 -1
  42. package/dist/likec4diagram/ui/walkthrough/DynamicViewWalkthrough.js +2 -2
  43. package/dist/likec4diagram/useViewToNodesEdges.js +1 -1
  44. package/dist/overlays/Overlays.js +42 -21
  45. package/dist/overlays/element-details/ElementDetailsCard.d.ts +7 -3
  46. package/dist/overlays/element-details/ElementDetailsCard.js +38 -39
  47. package/dist/overlays/element-details/TabPanelDeployments.js +1 -1
  48. package/dist/overlays/element-details/TabPanelRelationships.js +14 -30
  49. package/dist/overlays/overlay/Overlay.css.js +2 -1
  50. package/dist/overlays/overlay/Overlay.js +2 -2
  51. package/dist/overlays/relationship-details/SelectEdge.js +1 -1
  52. package/dist/overlays/relationship-details/_types.d.ts +3 -3
  53. package/dist/overlays/relationship-details/actor.js +1 -1
  54. package/dist/overlays/relationship-details/custom/nodeTypes.d.ts +3 -3
  55. package/dist/overlays/relationship-details/custom/nodeTypes.js +2 -2
  56. package/dist/overlays/relationship-details/useLayoutedDetails.js +1 -4
  57. package/dist/overlays/relationships-browser/-useRelationshipsView.js +0 -3
  58. package/dist/overlays/relationships-browser/RelationshipsBrowser.js +3 -3
  59. package/dist/overlays/relationships-browser/_types.d.ts +3 -3
  60. package/dist/overlays/relationships-browser/actor.js +16 -16
  61. package/dist/overlays/relationships-browser/custom/nodeTypes.d.ts +3 -2
  62. package/dist/overlays/relationships-browser/custom/nodeTypes.js +3 -3
  63. package/dist/overlays/relationships-browser/useViewToNodesEdges.js +3 -2
  64. package/dist/style.css +1 -1
  65. package/package.json +12 -12
  66. package/src/base/example/_types.ts +3 -2
  67. package/src/base/example/useViewToNodesEdges.ts +2 -1
  68. package/src/base/primitives/compound/CompoundTitle.css.ts +27 -1
  69. package/src/base/primitives/compound/CompoundTitle.tsx +12 -2
  70. package/src/base/primitives/element/ElementNodeContainer.tsx +21 -1
  71. package/src/base/primitives/element/ElementShape.css.ts +5 -0
  72. package/src/base/primitives/element/ElementTitle.css.ts +17 -24
  73. package/src/base/primitives/element/ElementTitle.tsx +4 -2
  74. package/src/base/primitives/element/styles.css.ts +74 -0
  75. package/src/hooks/useDiagram.ts +1 -1
  76. package/src/likec4diagram/DiagramUI.tsx +2 -2
  77. package/src/likec4diagram/XYFlow.tsx +2 -2
  78. package/src/likec4diagram/custom/edges/RelationshipsDropdownMenu.tsx +3 -2
  79. package/src/likec4diagram/types.ts +2 -2
  80. package/src/likec4diagram/useViewToNodesEdges.ts +1 -1
  81. package/src/overlays/Overlays.tsx +61 -281
  82. package/src/overlays/element-details/ElementDetailsCard.tsx +213 -257
  83. package/src/overlays/element-details/TabPanelRelationships.tsx +13 -75
  84. package/src/overlays/overlay/Overlay.tsx +1 -1
  85. package/src/overlays/relationship-details/_types.ts +3 -3
  86. package/src/overlays/relationship-details/useLayoutedDetails.ts +1 -1
  87. package/src/overlays/relationships-browser/RelationshipsBrowser.tsx +44 -55
  88. package/src/overlays/relationships-browser/_types.ts +12 -2
  89. package/src/overlays/relationships-browser/actor.ts +11 -10
  90. package/src/overlays/relationships-browser/useViewToNodesEdges.ts +2 -1
@@ -3,7 +3,8 @@ import {
3
3
  type Element,
4
4
  type Fqn,
5
5
  type LikeC4View,
6
- ComputedNode,
6
+ type NodeId,
7
+ type ViewId,
7
8
  ComputedView,
8
9
  isDeploymentView,
9
10
  isScopedElementView,
@@ -12,17 +13,13 @@ import {
12
13
  type TextProps,
13
14
  ActionIcon,
14
15
  ActionIconGroup,
15
- Anchor,
16
16
  Badge,
17
17
  Box,
18
18
  Card,
19
19
  CloseButton,
20
20
  Code,
21
- CopyButton,
22
21
  Divider as MantineDivider,
23
22
  Flex,
24
- FocusTrap,
25
- FocusTrapInitialFocus,
26
23
  Group,
27
24
  RemoveScroll,
28
25
  ScrollArea,
@@ -36,18 +33,18 @@ import {
36
33
  Tooltip as MantineTooltip,
37
34
  UnstyledButton,
38
35
  } from '@mantine/core'
39
- import { useDebouncedCallback, useSessionStorage, useViewportSize } from '@mantine/hooks'
40
- import { IconCheck, IconCopy, IconExternalLink, IconFileSymlink, IconStack2, IconZoomScan } from '@tabler/icons-react'
41
- import clsx from 'clsx'
36
+ import { useSessionStorage, useViewportSize } from '@mantine/hooks'
37
+ import { useDebouncedCallback, useSyncedRef } from '@react-hookz/web'
38
+ import { IconExternalLink, IconFileSymlink, IconStack2, IconZoomScan } from '@tabler/icons-react'
39
+ import type { Rect } from '@xyflow/system'
42
40
  import { type PanInfo, m, useDragControls, useMotionValue } from 'framer-motion'
43
- import { type PropsWithChildren, memo, useCallback, useLayoutEffect, useRef } from 'react'
41
+ import { type PropsWithChildren, useCallback, useRef } from 'react'
44
42
  import { clamp, isNullish, map, only, partition, pipe } from 'remeda'
45
43
  import { Link } from '../../base'
46
- import { IconRenderer, IfEnabled } from '../../context'
44
+ import { DiagramFeatures, IconRenderer, IfEnabled } from '../../context'
47
45
  import { useUpdateEffect } from '../../hooks'
48
46
  import { useDiagram } from '../../hooks/useDiagram'
49
- import { useDiagramContext } from '../../hooks/useDiagramContext'
50
- import type { ElementIconRenderer, OnNavigateTo } from '../../LikeC4Diagram.props'
47
+ import type { OnNavigateTo } from '../../LikeC4Diagram.props'
51
48
  import { useLikeC4Model } from '../../likec4model'
52
49
  import * as css from './ElementDetailsCard.css'
53
50
  import { TabPanelDeployments } from './TabPanelDeployments'
@@ -85,6 +82,10 @@ const PropertyLabel = Text.withProps({
85
82
  })
86
83
 
87
84
  type ElementDetailsCardProps = {
85
+ viewId: ViewId
86
+ fromNode: NodeId | null
87
+ rectFromNode: Rect | null
88
+ onClose: () => void
88
89
  fqn: Fqn
89
90
  }
90
91
 
@@ -93,16 +94,13 @@ const MIN_PADDING = 24
93
94
  const TABS = ['Properties', 'Relationships', 'Views', 'Structure', 'Deployments'] as const
94
95
  type TabName = typeof TABS[number]
95
96
 
96
- export const ElementDetailsCard = memo(({ fqn }: ElementDetailsCardProps) => {
97
- const {
98
- viewId,
99
- fromNode,
100
- rectFromNode,
101
- } = useDiagramContext(s => ({
102
- viewId: s.view.id,
103
- fromNode: s.activeElementDetails?.fromNode ?? null,
104
- rectFromNode: s.activeElementDetails?.nodeRectScreen ?? null,
105
- }))
97
+ export function ElementDetailsCard({
98
+ viewId,
99
+ fromNode,
100
+ rectFromNode,
101
+ fqn,
102
+ onClose,
103
+ }: ElementDetailsCardProps) {
106
104
  const windowSize = useViewportSize()
107
105
  const windowWidth = windowSize.width || window.innerWidth || 1200,
108
106
  windowHeight = windowSize.height || window.innerHeight || 800
@@ -115,7 +113,6 @@ export const ElementDetailsCard = memo(({ fqn }: ElementDetailsCardProps) => {
115
113
  const likec4model = useLikeC4Model(true)
116
114
  const viewModel = likec4model.view(viewId)
117
115
  // const currentView = diagram.currentView()
118
-
119
116
  // const diagramNode = currentView.nodes.find(n => n.id === fqn)
120
117
  // invariant(diagramNode, `DiagramNode with fqn ${fqn} not found`)
121
118
  const nodeModel = fromNode ? viewModel.findNode(fromNode) : viewModel.findNodeWithElement(fqn)
@@ -124,15 +121,12 @@ export const ElementDetailsCard = memo(({ fqn }: ElementDetailsCardProps) => {
124
121
 
125
122
  // const incoming = elementModel.incoming().map(r => r.id).toArray()
126
123
  // const outgoing = elementModel.outgoing().map(r => r.id).toArray()
127
-
128
124
  // const incomingInView = unique(nodeModel.incoming().flatMap(e => e.$edge.relations).toArray())
129
125
  // const outgoingInView = unique(nodeModel.outgoing().flatMap(e => e.$edge.relations).toArray())
130
-
131
126
  // const notIncludedRelations = [
132
127
  // ...incoming,
133
128
  // ...outgoing
134
129
  // ].filter(r => !incomingInView.includes(r) && !outgoingInView.includes(r)).length
135
-
136
130
  const [viewsOf, otherViews] = pipe(
137
131
  [...elementModel.views()],
138
132
  map(v => v.$view),
@@ -202,15 +196,13 @@ export const ElementDetailsCard = memo(({ fqn }: ElementDetailsCardProps) => {
202
196
  }, [])
203
197
 
204
198
  const ref = useRef<HTMLDialogElement>(null)
205
- useLayoutEffect(() => {
206
- ref.current?.showModal()
207
- }, [])
208
199
 
209
- const diagramApi = useDiagram()
200
+ const onCloseRef = useSyncedRef(onClose)
210
201
  const close = useDebouncedCallback(
211
202
  () => {
212
- diagramApi.closeOverlay()
203
+ onCloseRef.current()
213
204
  },
205
+ [],
214
206
  50,
215
207
  )
216
208
 
@@ -229,7 +221,6 @@ export const ElementDetailsCard = memo(({ fqn }: ElementDetailsCardProps) => {
229
221
  <m.dialog
230
222
  ref={ref}
231
223
  className={css.dialog}
232
- layoutRoot
233
224
  initial={{
234
225
  '--backdrop-blur': '0px',
235
226
  '--backdrop-opacity': '10%',
@@ -245,6 +236,9 @@ export const ElementDetailsCard = memo(({ fqn }: ElementDetailsCardProps) => {
245
236
  duration: 0.1,
246
237
  },
247
238
  }}
239
+ onAnimationStart={() => {
240
+ ref.current?.showModal()
241
+ }}
248
242
  onClick={e => {
249
243
  if ((e.target as any)?.nodeName?.toUpperCase() === 'DIALOG') {
250
244
  e.stopPropagation()
@@ -267,7 +261,6 @@ export const ElementDetailsCard = memo(({ fqn }: ElementDetailsCardProps) => {
267
261
  shadow="md"
268
262
  component={m.div}
269
263
  className={css.card}
270
- layoutId={fqn}
271
264
  initial={{
272
265
  top,
273
266
  left,
@@ -296,248 +289,211 @@ export const ElementDetailsCard = memo(({ fqn }: ElementDetailsCardProps) => {
296
289
  height: height as any,
297
290
  }}
298
291
  data-likec4-color={nodeModel?.color ?? elementModel.color}>
299
- <FocusTrap>
300
- <FocusTrapInitialFocus />
301
- <Box
302
- className={css.cardHeader}
303
- onPointerDown={e => controls.start(e)}>
304
- <Group align="start" justify="space-between" gap={'sm'} mb={'sm'} wrap="nowrap">
305
- <Group align="start" gap={'sm'} style={{ cursor: 'default' }} wrap="nowrap">
306
- {elementIcon}
307
- <Box>
308
- <Text
309
- component={m.div}
310
- layout="position"
311
- layoutId={`${viewId}:element:title:${fqn}`}
312
- className={css.title}>
313
- {elementModel.title}
314
- </Text>
315
- {notation && (
316
- <Text component="div" c={'dimmed'} fz={'sm'} fw={500} lh={1.3} lineClamp={1}>
317
- {notation}
318
- </Text>
319
- )}
320
- </Box>
321
- </Group>
322
- <CloseButton
323
- size={'lg'}
324
- onClick={e => {
325
- e.stopPropagation()
326
- close()
327
- }}
328
- />
329
- </Group>
330
- <Group align="baseline" gap={'sm'} wrap="nowrap">
292
+ <Box
293
+ className={css.cardHeader}
294
+ onPointerDown={e => controls.start(e)}>
295
+ <Group align="start" justify="space-between" gap={'sm'} mb={'sm'} wrap="nowrap">
296
+ <Group align="start" gap={'sm'} style={{ cursor: 'default' }} wrap="nowrap">
297
+ {elementIcon}
331
298
  <Box>
332
- <SmallLabel>kind</SmallLabel>
333
- <Badge radius={'sm'} size="sm" fw={600} color="gray">{elementModel.kind}</Badge>
334
- </Box>
335
- <Box flex={1}>
336
- <SmallLabel>tags</SmallLabel>
337
- <Flex gap={4} flex={1} mt={6}>
338
- {elementModel.tags.map((tag) => (
339
- <Badge key={tag} radius={'sm'} size="sm" fw={600} variant="gradient">#{tag}</Badge>
340
- ))}
341
- {elementModel.tags.length === 0 && <Badge radius={'sm'} size="sm" fw={600} color="gray">—</Badge>}
342
- </Flex>
299
+ <Text
300
+ component={m.div}
301
+ layout="position"
302
+ layoutId={`${viewId}:element:title:${fqn}`}
303
+ className={css.title}>
304
+ {elementModel.title}
305
+ </Text>
306
+ {notation && (
307
+ <Text component="div" c={'dimmed'} fz={'sm'} fw={500} lh={1.3} lineClamp={1}>
308
+ {notation}
309
+ </Text>
310
+ )}
343
311
  </Box>
344
- <ActionIconGroup
345
- style={{
346
- alignSelf: 'flex-end',
347
- }}>
348
- {defaultLink && (
312
+ </Group>
313
+ <CloseButton
314
+ size={'lg'}
315
+ onClick={e => {
316
+ e.stopPropagation()
317
+ close()
318
+ }} />
319
+ </Group>
320
+ <Group align="baseline" gap={'sm'} wrap="nowrap">
321
+ <Box>
322
+ <SmallLabel>kind</SmallLabel>
323
+ <Badge radius={'sm'} size="sm" fw={600} color="gray">{elementModel.kind}</Badge>
324
+ </Box>
325
+ <Box flex={1}>
326
+ <SmallLabel>tags</SmallLabel>
327
+ <Flex gap={4} flex={1} mt={6}>
328
+ {elementModel.tags.map((tag) => (
329
+ <Badge key={tag} radius={'sm'} size="sm" fw={600} variant="gradient">#{tag}</Badge>
330
+ ))}
331
+ {elementModel.tags.length === 0 && <Badge radius={'sm'} size="sm" fw={600} color="gray">—</Badge>}
332
+ </Flex>
333
+ </Box>
334
+ <ActionIconGroup
335
+ style={{
336
+ alignSelf: 'flex-end',
337
+ }}>
338
+ {defaultLink && (
339
+ <ActionIcon
340
+ component="a"
341
+ href={defaultLink.url}
342
+ target="_blank"
343
+ size="lg"
344
+ variant="default"
345
+ radius="sm"
346
+ >
347
+ <IconExternalLink stroke={1.6} style={{ width: '65%' }} />
348
+ </ActionIcon>
349
+ )}
350
+ <IfEnabled feature="Vscode">
351
+ <Tooltip label="Open source">
349
352
  <ActionIcon
350
- component="a"
351
- href={defaultLink.url}
352
- target="_blank"
353
353
  size="lg"
354
354
  variant="default"
355
355
  radius="sm"
356
- >
357
- <IconExternalLink stroke={1.6} style={{ width: '65%' }} />
356
+ onClick={e => {
357
+ e.stopPropagation()
358
+ diagram.openSource({
359
+ element: elementModel.id,
360
+ })
361
+ }}>
362
+ <IconFileSymlink stroke={1.8} style={{ width: '62%' }} />
358
363
  </ActionIcon>
364
+ </Tooltip>
365
+ </IfEnabled>
366
+ {defaultView && (
367
+ <Tooltip label="Open default view">
368
+ <ActionIcon
369
+ size="lg"
370
+ variant="default"
371
+ radius="sm"
372
+ onClick={e => {
373
+ e.stopPropagation()
374
+ diagram.navigateTo(defaultView.id, fromNode ?? undefined)
375
+ }}>
376
+ <IconZoomScan style={{ width: '70%' }} />
377
+ </ActionIcon>
378
+ </Tooltip>
379
+ )}
380
+ </ActionIconGroup>
381
+ </Group>
382
+ </Box>
383
+
384
+ <Tabs
385
+ value={activeTab}
386
+ onChange={v => setActiveTab(v as any)}
387
+ variant="none"
388
+ classNames={{
389
+ root: css.tabsRoot,
390
+ list: css.tabsList,
391
+ tab: css.tabsTab,
392
+ panel: css.tabsPanel,
393
+ }}>
394
+ <TabsList>
395
+ {TABS.map(tab => (
396
+ <TabsTab key={tab} value={tab}>
397
+ {tab}
398
+ </TabsTab>
399
+ ))}
400
+ </TabsList>
401
+
402
+ <TabsPanel value="Properties">
403
+ <ScrollArea scrollbars="y" type="auto">
404
+ <Box className={css.propertiesGrid} pt={'xs'}>
405
+ <ElementProperty title="description" emptyValue="no description">
406
+ {elementModel.description}
407
+ </ElementProperty>
408
+ {elementModel.technology && (
409
+ <ElementProperty title="technology">
410
+ {elementModel.technology}
411
+ </ElementProperty>
359
412
  )}
360
- <IfEnabled feature="Vscode">
361
- <Tooltip label="Open source">
362
- <ActionIcon
363
- size="lg"
364
- variant="default"
365
- radius="sm"
366
- onClick={e => {
367
- e.stopPropagation()
368
- diagram.openSource({
369
- element: elementModel.id,
370
- })
371
- }}>
372
- <IconFileSymlink stroke={1.8} style={{ width: '62%' }} />
373
- </ActionIcon>
374
- </Tooltip>
375
- </IfEnabled>
376
- {defaultView && (
377
- <Tooltip label="Open default view">
378
- <ActionIcon
379
- size="lg"
380
- variant="default"
381
- radius="sm"
382
- onClick={e => {
383
- e.stopPropagation()
384
- diagram.navigateTo(defaultView.id, fromNode ?? undefined)
385
- }}>
386
- <IconZoomScan style={{ width: '70%' }} />
387
- </ActionIcon>
388
- </Tooltip>
413
+ {elementModel.links.length > 0 && (
414
+ <>
415
+ <PropertyLabel>links</PropertyLabel>
416
+ <Stack gap={'xs'} align="flex-start">
417
+ {elementModel.links.map((link, i) => <Link key={i} value={link} />)}
418
+ </Stack>
419
+ </>
389
420
  )}
390
- </ActionIconGroup>
391
- </Group>
392
- </Box>
393
-
394
- <Tabs
395
- value={activeTab}
396
- onChange={v => setActiveTab(v as any)}
397
- variant="none"
398
- classNames={{
399
- root: css.tabsRoot,
400
- list: css.tabsList,
401
- tab: css.tabsTab,
402
- panel: css.tabsPanel,
403
- }}>
404
- <TabsList>
405
- {TABS.map(tab => (
406
- <TabsTab key={tab} value={tab}>
407
- {tab}
408
- </TabsTab>
409
- ))}
410
- </TabsList>
411
-
412
- <TabsPanel value="Properties">
413
- <ScrollArea scrollbars="y" type="auto">
414
- <Box className={css.propertiesGrid} pt={'xs'}>
415
- <ElementProperty title="description" emptyValue="no description">
416
- {elementModel.description}
417
- </ElementProperty>
418
- {elementModel.technology && (
419
- <ElementProperty title="technology">
420
- {elementModel.technology}
421
- </ElementProperty>
422
- )}
423
- {elementModel.links.length > 0 && (
424
- <>
425
- <PropertyLabel>links</PropertyLabel>
426
- <Stack gap={'xs'} align="flex-start">
427
- {elementModel.links.map((link, i) => <Link key={i} value={link} />)}
428
- </Stack>
429
- </>
430
- )}
431
- {elementModel.$element.metadata && <ElementMetata value={elementModel.$element.metadata} />}
432
- </Box>
433
- </ScrollArea>
434
- </TabsPanel>
435
-
436
- <TabsPanel value="Relationships">
437
- {nodeModel && activeTab === 'Relationships' && (
421
+ {elementModel.$element.metadata && <ElementMetata value={elementModel.$element.metadata} />}
422
+ </Box>
423
+ </ScrollArea>
424
+ </TabsPanel>
425
+
426
+ <TabsPanel value="Relationships">
427
+ <DiagramFeatures
428
+ overrides={{
429
+ enableRelationshipBrowser: false,
430
+ enableNavigateTo: false,
431
+ }}>
432
+ {!!nodeModel && activeTab === 'Relationships' && (
438
433
  <TabPanelRelationships
439
434
  element={elementModel}
440
435
  node={nodeModel}
441
436
  currentView={diagram.currentView()} />
442
437
  )}
443
- </TabsPanel>
444
-
445
- <TabsPanel value="Views">
446
- <ScrollArea scrollbars="y" type="auto">
447
- <Stack gap={'lg'}>
448
- {viewsOf.length > 0 && (
449
- <Box>
450
- <Divider label="views of the element (scoped)" />
451
- <Stack gap={'sm'}>
452
- {viewsOf.map((view) => (
453
- <ViewButton
454
- key={view.id}
455
- view={view}
456
- onNavigateTo={to => diagram.navigateTo(to, fromNode ?? undefined)}
457
- />
458
- ))}
459
- </Stack>
460
- </Box>
461
- )}
462
- {otherViews.length > 0 && (
463
- <Box>
464
- <Divider label="views including this element" />
465
- <Stack gap={'sm'}>
466
- {otherViews.map((view) => (
467
- <ViewButton
468
- key={view.id}
469
- view={view}
470
- onNavigateTo={to => diagram.navigateTo(to, fromNode ?? undefined)}
471
- />
472
- ))}
473
- </Stack>
474
- </Box>
475
- )}
476
- </Stack>
477
- </ScrollArea>
478
- </TabsPanel>
479
-
480
- <TabsPanel value="Structure">
481
- <ScrollArea scrollbars="y" type="auto">
482
- <TabPanelStructure element={elementModel} />
483
- </ScrollArea>
484
- </TabsPanel>
485
-
486
- <TabsPanel value="Deployments">
487
- <ScrollArea scrollbars="y" type="auto">
488
- <TabPanelDeployments elementFqn={elementModel.id} />
489
- </ScrollArea>
490
- </TabsPanel>
491
- </Tabs>
492
- <m.div
493
- className={css.resizeHandle}
494
- drag
495
- dragElastic={0}
496
- dragMomentum={false}
497
- onDrag={handleDrag}
498
- dragConstraints={{ top: 0, left: 0, right: 0, bottom: 0 }}
499
- />
500
- </FocusTrap>
438
+ </DiagramFeatures>
439
+ </TabsPanel>
440
+
441
+ <TabsPanel value="Views">
442
+ <ScrollArea scrollbars="y" type="auto">
443
+ <Stack gap={'lg'}>
444
+ {viewsOf.length > 0 && (
445
+ <Box>
446
+ <Divider label="views of the element (scoped)" />
447
+ <Stack gap={'sm'}>
448
+ {viewsOf.map((view) => (
449
+ <ViewButton
450
+ key={view.id}
451
+ view={view}
452
+ onNavigateTo={to => diagram.navigateTo(to, fromNode ?? undefined)} />
453
+ ))}
454
+ </Stack>
455
+ </Box>
456
+ )}
457
+ {otherViews.length > 0 && (
458
+ <Box>
459
+ <Divider label="views including this element" />
460
+ <Stack gap={'sm'}>
461
+ {otherViews.map((view) => (
462
+ <ViewButton
463
+ key={view.id}
464
+ view={view}
465
+ onNavigateTo={to => diagram.navigateTo(to, fromNode ?? undefined)} />
466
+ ))}
467
+ </Stack>
468
+ </Box>
469
+ )}
470
+ </Stack>
471
+ </ScrollArea>
472
+ </TabsPanel>
473
+
474
+ <TabsPanel value="Structure">
475
+ <ScrollArea scrollbars="y" type="auto">
476
+ <TabPanelStructure element={elementModel} />
477
+ </ScrollArea>
478
+ </TabsPanel>
479
+
480
+ <TabsPanel value="Deployments">
481
+ <ScrollArea scrollbars="y" type="auto">
482
+ <TabPanelDeployments elementFqn={elementModel.id} />
483
+ </ScrollArea>
484
+ </TabsPanel>
485
+ </Tabs>
486
+ <m.div
487
+ className={css.resizeHandle}
488
+ drag
489
+ dragElastic={0}
490
+ dragMomentum={false}
491
+ onDrag={handleDrag}
492
+ dragConstraints={{ top: 0, left: 0, right: 0, bottom: 0 }} />
501
493
  </Card>
502
494
  </RemoveScroll>
503
495
  </m.dialog>
504
496
  )
505
- })
506
- ElementDetailsCard.displayName = 'ElementDetailsCard'
507
-
508
- const ElementIcon = (
509
- { element, viewId, renderIcon: RenderIcon }: {
510
- element: ComputedNode
511
- viewId: string
512
- renderIcon: ElementIconRenderer | null
513
- },
514
- ) => {
515
- if (!element.icon || element.icon === 'none') {
516
- return null
517
- }
518
- let icon = null as React.ReactNode
519
- if (element.icon.startsWith('http://') || element.icon.startsWith('https://')) {
520
- icon = <img src={element.icon} alt={element.title} />
521
- } else if (RenderIcon) {
522
- icon = <RenderIcon node={element} />
523
- }
524
-
525
- if (!icon) {
526
- return null
527
- }
528
- return (
529
- <m.div
530
- // layout="position"
531
- layoutId={`${viewId}:element:icon:${element.id}`}
532
- className={clsx(
533
- css.elementIcon,
534
- 'likec4-element-icon',
535
- )}
536
- data-likec4-icon={element.icon}
537
- >
538
- {icon}
539
- </m.div>
540
- )
541
497
  }
542
498
 
543
499
  const ViewButton = ({