@likec4/diagram 1.21.1 → 1.22.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 (109) hide show
  1. package/dist/LikeC4Diagram.js +9 -13
  2. package/dist/base/index.d.ts +0 -1
  3. package/dist/base/primitives/element/styles.css.js +1 -1
  4. package/dist/{base → components}/Link.css.js +3 -3
  5. package/dist/components/SearchControl.css.d.ts +2 -0
  6. package/dist/components/SearchControl.css.js +7 -0
  7. package/dist/components/SearchControl.d.ts +5 -0
  8. package/dist/components/SearchControl.js +17 -0
  9. package/dist/context/IconRenderer.d.ts +10 -0
  10. package/dist/context/IconRenderer.js +34 -0
  11. package/dist/hooks/useCurrentViewId.js +8 -0
  12. package/dist/hooks/useDiagram.d.ts +4 -4
  13. package/dist/likec4diagram/DiagramUI.js +0 -2
  14. package/dist/likec4diagram/XYFlow.d.ts +6 -1
  15. package/dist/likec4diagram/XYFlow.js +28 -29
  16. package/dist/likec4diagram/custom/edges/RelationshipsDropdownMenu.js +2 -1
  17. package/dist/likec4diagram/state/actorContext.d.ts +16 -16
  18. package/dist/likec4diagram/state/assign.js +1 -1
  19. package/dist/likec4diagram/state/machine.d.ts +7 -7
  20. package/dist/likec4diagram/state/machine.js +10 -7
  21. package/dist/likec4diagram/ui/controls/Controls.js +63 -48
  22. package/dist/likec4diagram/ui/controls/NavigationButtons.js +44 -69
  23. package/dist/likec4diagram/ui/controls/_shared.js +1 -1
  24. package/dist/likec4diagram/ui/diagram-title/DiagramTitlePanel.js +1 -1
  25. package/dist/likec4diagram/ui/search/ElementsColumn.css.d.ts +15 -0
  26. package/dist/likec4diagram/ui/search/ElementsColumn.css.js +30 -0
  27. package/dist/likec4diagram/ui/search/ElementsColumn.d.ts +1 -0
  28. package/dist/likec4diagram/ui/search/ElementsColumn.js +243 -0
  29. package/dist/likec4diagram/ui/search/LikeC4Search.css.d.ts +8 -0
  30. package/dist/likec4diagram/ui/search/LikeC4Search.css.js +20 -0
  31. package/dist/likec4diagram/ui/search/LikeC4Search.d.ts +1 -0
  32. package/dist/likec4diagram/ui/search/LikeC4Search.js +265 -0
  33. package/dist/likec4diagram/ui/search/SearchInput.d.ts +1 -0
  34. package/dist/likec4diagram/ui/search/SearchInput.js +196 -0
  35. package/dist/likec4diagram/ui/search/ViewsColum.d.ts +9 -0
  36. package/dist/likec4diagram/ui/search/ViewsColum.js +130 -0
  37. package/dist/likec4diagram/ui/search/ViewsColumn.css.d.ts +5 -0
  38. package/dist/likec4diagram/ui/search/ViewsColumn.css.js +12 -0
  39. package/dist/likec4diagram/ui/search/_shared.css.d.ts +16 -0
  40. package/dist/likec4diagram/ui/search/_shared.css.js +5 -0
  41. package/dist/likec4diagram/ui/search/state.d.ts +19 -0
  42. package/dist/likec4diagram/ui/search/state.js +72 -0
  43. package/dist/likec4diagram/ui/search/utils.d.ts +3 -0
  44. package/dist/likec4diagram/ui/search/utils.js +23 -0
  45. package/dist/likec4diagram/useLayoutConstraints.js +4 -5
  46. package/dist/likec4model/useLikeC4ElementsTree.d.ts +1 -0
  47. package/dist/likec4model/useLikeC4ElementsTree.js +1 -0
  48. package/dist/overlays/Overlays.js +9 -9
  49. package/dist/overlays/element-details/ElementDetailsCard.js +20 -13
  50. package/dist/overlays/overlay/Overlay.d.ts +6 -2
  51. package/dist/overlays/overlay/Overlay.js +18 -29
  52. package/dist/overlays/relationship-details/SelectEdge.js +1 -3
  53. package/dist/overlays/relationships-browser/RelationshipsBrowser.js +6 -5
  54. package/dist/overlays/relationships-browser/SelectElement.js +10 -6
  55. package/dist/overlays/relationships-browser/actor.d.ts +2 -2
  56. package/dist/overlays/relationships-browser/actor.js +8 -8
  57. package/dist/overlays/relationships-browser/custom/nodeTypes.js +2 -2
  58. package/dist/style.css +1 -1
  59. package/package.json +13 -12
  60. package/src/LikeC4Diagram.tsx +0 -1
  61. package/src/base/index.ts +0 -2
  62. package/src/base/primitives/element/styles.css.ts +2 -2
  63. package/src/{base → components}/Link.css.ts +3 -3
  64. package/src/components/SearchControl.css.ts +69 -0
  65. package/src/components/SearchControl.tsx +33 -0
  66. package/src/context/IconRenderer.tsx +54 -1
  67. package/src/globals.css.ts +0 -2
  68. package/src/likec4diagram/DiagramUI.tsx +0 -4
  69. package/src/likec4diagram/XYFlow.tsx +30 -30
  70. package/src/likec4diagram/custom/edges/RelationshipsDropdownMenu.tsx +2 -1
  71. package/src/likec4diagram/state/assign.ts +1 -1
  72. package/src/likec4diagram/state/machine.ts +12 -10
  73. package/src/likec4diagram/ui/controls/Controls.tsx +35 -25
  74. package/src/likec4diagram/ui/controls/NavigationButtons.tsx +36 -58
  75. package/src/likec4diagram/ui/controls/styles.css.ts +3 -1
  76. package/src/likec4diagram/ui/diagram-title/DiagramTitlePanel.tsx +1 -1
  77. package/src/likec4diagram/ui/search/ElementsColumn.css.ts +119 -0
  78. package/src/likec4diagram/ui/search/ElementsColumn.tsx +291 -0
  79. package/src/likec4diagram/ui/search/LikeC4Search.css.ts +117 -0
  80. package/src/likec4diagram/ui/search/LikeC4Search.tsx +312 -0
  81. package/src/likec4diagram/ui/search/SearchInput.tsx +235 -0
  82. package/src/likec4diagram/ui/search/ViewsColum.tsx +158 -0
  83. package/src/likec4diagram/ui/search/ViewsColumn.css.ts +37 -0
  84. package/src/likec4diagram/ui/search/_shared.css.ts +104 -0
  85. package/src/likec4diagram/ui/search/state.ts +76 -0
  86. package/src/likec4diagram/ui/search/utils.ts +20 -0
  87. package/src/likec4diagram/useLayoutConstraints.ts +4 -5
  88. package/src/likec4model/useLikeC4ElementsTree.ts +4 -4
  89. package/src/overlays/Overlays.tsx +31 -25
  90. package/src/overlays/element-details/ElementDetailsCard.css.ts +4 -4
  91. package/src/overlays/element-details/ElementDetailsCard.tsx +245 -240
  92. package/src/overlays/overlay/Overlay.css.ts +10 -9
  93. package/src/overlays/overlay/Overlay.tsx +38 -26
  94. package/src/overlays/relationship-details/SelectEdge.css.ts +1 -0
  95. package/src/overlays/relationship-details/SelectEdge.tsx +2 -4
  96. package/src/overlays/relationships-browser/RelationshipsBrowser.tsx +10 -5
  97. package/src/overlays/relationships-browser/SelectElement.tsx +88 -82
  98. package/src/overlays/relationships-browser/actor.ts +10 -10
  99. package/src/overlays/relationships-browser/custom/nodeTypes.tsx +2 -2
  100. package/dist/LikeC4Search.css.d.ts +0 -1
  101. package/dist/LikeC4Search.css.js +0 -5
  102. package/dist/LikeC4Search.d.ts +0 -2
  103. package/dist/LikeC4Search.js +0 -88
  104. package/src/LikeC4Search.css.ts +0 -12
  105. package/src/LikeC4Search.tsx +0 -91
  106. /package/dist/{base → components}/Link.css.d.ts +0 -0
  107. /package/dist/{base → components}/Link.d.ts +0 -0
  108. /package/dist/{base → components}/Link.js +0 -0
  109. /package/src/{base → components}/Link.tsx +0 -0
@@ -1,5 +1,16 @@
1
+ import type { ElementShape } from '@likec4/core'
2
+ import {
3
+ type Icon,
4
+ type IconProps,
5
+ IconBrowser,
6
+ IconCylinder,
7
+ IconDeviceMobile,
8
+ IconRectangularPrism,
9
+ IconReorder,
10
+ IconUser,
11
+ } from '@tabler/icons-react'
1
12
  import clsx from 'clsx'
2
- import { type ReactNode, createContext, useContext } from 'react'
13
+ import { type ForwardRefExoticComponent, type ReactNode, type RefAttributes, createContext, useContext } from 'react'
3
14
  import type { ElementIconRenderer } from '../LikeC4Diagram.props'
4
15
 
5
16
  const IconRendererContext = createContext<ElementIconRenderer | null>(null)
@@ -43,3 +54,45 @@ export function IconRenderer({
43
54
  </div>
44
55
  )
45
56
  }
57
+
58
+ const ShapeIcons = {
59
+ browser: IconBrowser,
60
+ cylinder: IconCylinder,
61
+ mobile: IconDeviceMobile,
62
+ person: IconUser,
63
+ queue: IconReorder,
64
+ rectangle: IconRectangularPrism,
65
+ storage: IconCylinder,
66
+ } satisfies Record<ElementShape, ForwardRefExoticComponent<IconProps & RefAttributes<Icon>>>
67
+
68
+ export function IconOrShapeRenderer({
69
+ element,
70
+ className,
71
+ }: {
72
+ element: {
73
+ id: string
74
+ title: string
75
+ shape: ElementShape
76
+ icon?: string | null | undefined
77
+ }
78
+ className: string
79
+ }) {
80
+ const icon = IconRenderer({
81
+ element,
82
+ className,
83
+ })
84
+ if (icon) {
85
+ return icon
86
+ }
87
+ const ShapeIcon = ShapeIcons[element.shape]
88
+ return (
89
+ <div
90
+ className={clsx(
91
+ className,
92
+ 'likec4-shape-icon',
93
+ )}
94
+ >
95
+ <ShapeIcon />
96
+ </div>
97
+ )
98
+ }
@@ -7,8 +7,6 @@ import { mantine, vars, whereDark } from './theme-vars'
7
7
  export const rootClassName = 'likec4-diagram-root'
8
8
 
9
9
  globalStyle(`.${rootClassName}`, {
10
- containerName: 'likec4-diagram-root',
11
- containerType: 'size',
12
10
  overflow: 'hidden',
13
11
  position: 'relative',
14
12
  width: '100%',
@@ -1,5 +1,4 @@
1
1
  import { IfEnabled } from '../context'
2
- import { LikeC4Search } from '../LikeC4Search'
3
2
  import { Overlays } from '../overlays/Overlays'
4
3
  import { Controls, DiagramTitlePanel, DynamicViewWalkthrough, NotationPanel } from './ui'
5
4
 
@@ -8,9 +7,6 @@ export function DiagramUI() {
8
7
  <>
9
8
  <Controls />
10
9
  <Overlays />
11
- <IfEnabled feature="Search">
12
- <LikeC4Search />
13
- </IfEnabled>
14
10
  <IfEnabled feature="ViewTitle">
15
11
  <DiagramTitlePanel />
16
12
  </IfEnabled>
@@ -2,6 +2,7 @@ import { type EdgeId, type NodeId, nonNullable } from '@likec4/core'
2
2
  import { useCallbackRef } from '@mantine/hooks'
3
3
  import clsx from 'clsx'
4
4
  import { shallowEqual } from 'fast-equals'
5
+ import { memo } from 'react'
5
6
  import type { EnforceOptional } from 'type-fest/source/enforce-optional'
6
7
  import { BaseXYFlow } from '../base/BaseXYFlow'
7
8
  import { useDiagramEventHandlers } from '../context'
@@ -9,6 +10,7 @@ import { useDiagram } from '../hooks/useDiagram'
9
10
  import { useDiagramContext } from '../hooks/useDiagramContext'
10
11
  import type { LikeC4DiagramProperties } from '../LikeC4Diagram.props'
11
12
  import { edgeTypes, nodeTypes } from './custom'
13
+ import { DiagramUI } from './DiagramUI'
12
14
  import type { DiagramContext } from './state/machine'
13
15
  import type { Types } from './types'
14
16
  import { useLayoutConstraints } from './useLayoutConstraints'
@@ -43,7 +45,7 @@ type Picked = EnforceOptional<
43
45
  >
44
46
  export type LikeC4DiagramXYFlowProps = Required<Picked>
45
47
 
46
- export const LikeC4DiagramXYFlow = ({ background, ...rest }: LikeC4DiagramXYFlowProps) => {
48
+ export const LikeC4DiagramXYFlow = memo<LikeC4DiagramXYFlowProps>(({ background, nodesDraggable, nodesSelectable }) => {
47
49
  const diagram = useDiagram()
48
50
  const {
49
51
  initialized,
@@ -80,10 +82,10 @@ export const LikeC4DiagramXYFlow = ({ background, ...rest }: LikeC4DiagramXYFlow
80
82
  nodeTypes={nodeTypes}
81
83
  edgeTypes={edgeTypes}
82
84
  onNodesChange={useCallbackRef((changes) => {
83
- diagram.send({ type: 'xyflow.applyNodeChages', changes })
85
+ diagram.send({ type: 'xyflow.applyNodeChanges', changes })
84
86
  })}
85
87
  onEdgesChange={useCallbackRef((changes) => {
86
- diagram.send({ type: 'xyflow.applyEdgeChages', changes })
88
+ diagram.send({ type: 'xyflow.applyEdgeChanges', changes })
87
89
  })}
88
90
  background={initialized ? background : 'transparent'}
89
91
  // Fitview is handled in onInit
@@ -105,6 +107,7 @@ export const LikeC4DiagramXYFlow = ({ background, ...rest }: LikeC4DiagramXYFlow
105
107
  })}
106
108
  onDoubleClick={useCallbackRef(e => {
107
109
  e.stopPropagation()
110
+ e.preventDefault()
108
111
  diagram.send({ type: 'xyflow.paneDblClick' })
109
112
  onCanvasDblClick?.(e as any)
110
113
  })}
@@ -115,34 +118,31 @@ export const LikeC4DiagramXYFlow = ({ background, ...rest }: LikeC4DiagramXYFlow
115
118
  onInit={useCallbackRef((instance) => {
116
119
  diagram.send({ type: 'xyflow.init', instance })
117
120
  })}
118
- {...onNodeContextMenu && {
119
- onNodeContextMenu: useCallbackRef((event, node) => {
120
- const diagramNode = nonNullable(
121
- diagram.findDiagramNode(node.id as NodeId),
122
- `diagramNode ${node.id} not found`,
123
- )
124
- onNodeContextMenu(diagramNode, event)
125
- }),
126
- }}
127
- {...onEdgeContextMenu && {
128
- onEdgeContextMenu: useCallbackRef((event, edge) => {
129
- const diagramEdge = nonNullable(
130
- diagram.findDiagramEdge(edge.id as EdgeId),
131
- `diagramEdge ${edge.id} not found`,
132
- )
133
- onEdgeContextMenu(diagramEdge, event)
134
- }),
135
- }}
136
- {...onCanvasContextMenu && {
137
- onPaneContextMenu: useCallbackRef((event) => {
138
- onCanvasContextMenu(event as any)
139
- }),
140
- }}
121
+ onNodeContextMenu={useCallbackRef((event, node) => {
122
+ const diagramNode = nonNullable(
123
+ diagram.findDiagramNode(node.id as NodeId),
124
+ `diagramNode ${node.id} not found`,
125
+ )
126
+ onNodeContextMenu?.(diagramNode, event)
127
+ })}
128
+ onEdgeContextMenu={useCallbackRef((event, edge) => {
129
+ const diagramEdge = nonNullable(
130
+ diagram.findDiagramEdge(edge.id as EdgeId),
131
+ `diagramEdge ${edge.id} not found`,
132
+ )
133
+ onEdgeContextMenu?.(diagramEdge, event)
134
+ })}
135
+ onPaneContextMenu={useCallbackRef((event) => {
136
+ onCanvasContextMenu?.(event as any)
137
+ })}
141
138
  {...enableFitView && {
142
139
  onViewportResize,
143
140
  }}
144
- {...(notReadOnly && rest.nodesDraggable && layoutConstraints)}
145
- {...props}
146
- {...rest} />
141
+ nodesDraggable={nodesDraggable}
142
+ nodesSelectable={nodesSelectable}
143
+ {...(notReadOnly && nodesDraggable && layoutConstraints)}
144
+ {...props}>
145
+ <DiagramUI />
146
+ </BaseXYFlow>
147
147
  )
148
- }
148
+ }, shallowEqual)
@@ -27,7 +27,7 @@ import { IconArrowRight, IconFileSymlink, IconInfoCircle, IconZoomScan } from '@
27
27
  import clsx from 'clsx'
28
28
  import { type MouseEventHandler, type PropsWithChildren, forwardRef, Fragment, useCallback } from 'react'
29
29
  import { filter, isTruthy, map, partition, pipe } from 'remeda'
30
- import { Link } from '../../../base/Link'
30
+ import { Link } from '../../../components/Link'
31
31
  import { IfEnabled, useDiagramEventHandlers, useEnabledFeature } from '../../../context'
32
32
  import { useMantinePortalProps } from '../../../hooks'
33
33
  import { useDiagram } from '../../../hooks/useDiagram'
@@ -127,6 +127,7 @@ export function RelationshipsDropdownMenu({
127
127
  closeOnClickOutside
128
128
  clickOutsideEvents={['pointerdown', 'mousedown', 'click']}
129
129
  closeOnEscape
130
+ keepMounted={false}
130
131
  closeOnItemClick={false}
131
132
  disabled={disabled}
132
133
  position="bottom-start"
@@ -321,7 +321,7 @@ export function resetEdgeControlPoints({ context }: ActionArg): Partial<DiagramC
321
321
  }
322
322
 
323
323
  // Edge is a loop
324
- if (source == target) {
324
+ if (source === target) {
325
325
  const loopSize = 80
326
326
  const centerOfTopBoundary = new VectorImpl(0, source.height || 0)
327
327
  .mul(-0.5)
@@ -4,7 +4,6 @@ import {
4
4
  type EdgeId,
5
5
  type Fqn,
6
6
  type NodeId,
7
- type RelationId,
8
7
  type StepEdgeId,
9
8
  type ViewChange,
10
9
  type ViewId,
@@ -136,8 +135,8 @@ export namespace DiagramContext {
136
135
  export type Events =
137
136
  | HotKeyEvent
138
137
  | { type: 'xyflow.init'; instance: ReactFlowInstance<Types.Node, Types.Edge> }
139
- | { type: 'xyflow.applyNodeChages'; changes: NodeChange<Types.Node>[] }
140
- | { type: 'xyflow.applyEdgeChages'; changes: EdgeChange<Types.Edge>[] }
138
+ | { type: 'xyflow.applyNodeChanges'; changes: NodeChange<Types.Node>[] }
139
+ | { type: 'xyflow.applyEdgeChanges'; changes: EdgeChange<Types.Edge>[] }
141
140
  | { type: 'xyflow.viewportMoved'; viewport: Viewport; manually: boolean }
142
141
  | { type: 'xyflow.nodeClick'; node: Types.Node }
143
142
  | { type: 'xyflow.edgeClick'; edge: Types.Edge }
@@ -634,7 +633,7 @@ export const diagramMachine = setup({
634
633
  pending: {
635
634
  entry: enqueueActions(({ enqueue }) => {
636
635
  enqueue.sendTo(c => c.context.syncLayoutActorRef, { type: 'stop' })
637
- enqueue.stopChild('layout')
636
+ enqueue.stopChild('syncLayout')
638
637
  enqueue({
639
638
  type: 'trigger:NavigateTo',
640
639
  params: ({ context }) => ({
@@ -657,15 +656,16 @@ export const diagramMachine = setup({
657
656
  },
658
657
  },
659
658
  })
659
+ enqueue.raise({ type: 'fitDiagram' }, { id: 'fitDiagram', delay: 80 })
660
660
  } else {
661
661
  enqueue({
662
662
  type: 'xyflow:setViewportCenter',
663
663
  params: getBBoxCenter(event.view.bounds),
664
664
  })
665
+ enqueue.raise({ type: 'fitDiagram', duration: 200 }, { id: 'fitDiagram', delay: 25 })
665
666
  }
666
667
  enqueue.assign(updateNavigationHistory)
667
668
  enqueue.assign(mergeXYNodesEdges)
668
- enqueue.raise({ type: 'fitDiagram' }, { id: 'fitDiagram', delay: 75 })
669
669
  }),
670
670
  target: 'done',
671
671
  },
@@ -765,12 +765,12 @@ export const diagramMachine = setup({
765
765
  },
766
766
  },
767
767
  on: {
768
- 'xyflow.applyNodeChages': {
768
+ 'xyflow.applyNodeChanges': {
769
769
  actions: assign({
770
770
  xynodes: ({ context, event }) => applyNodeChanges(event.changes, context.xynodes),
771
771
  }),
772
772
  },
773
- 'xyflow.applyEdgeChages': {
773
+ 'xyflow.applyEdgeChanges': {
774
774
  actions: assign({
775
775
  xyedges: ({ context, event }) => applyEdgeChanges(event.changes, context.xyedges),
776
776
  }),
@@ -838,17 +838,19 @@ export const diagramMachine = setup({
838
838
  },
839
839
  },
840
840
  })
841
+ enqueue.raise({ type: 'fitDiagram' }, { id: 'fitDiagram', delay: 80 })
841
842
  } else {
842
843
  enqueue({
843
844
  type: 'xyflow:setViewportCenter',
844
845
  params: getBBoxCenter(event.view.bounds),
845
846
  })
847
+ enqueue.raise({ type: 'fitDiagram', duration: 200 }, { id: 'fitDiagram', delay: 25 })
846
848
  }
847
849
  } else {
848
850
  enqueue.sendTo(c => c.context.syncLayoutActorRef, { type: 'synced' })
849
- }
850
- if (isAnotherView || !context.viewportChangedManually) {
851
- enqueue.raise({ type: 'fitDiagram' }, { id: 'fitDiagram', delay: 90 })
851
+ if (!context.viewportChangedManually) {
852
+ enqueue.raise({ type: 'fitDiagram' }, { id: 'fitDiagram', delay: 50 })
853
+ }
852
854
  }
853
855
  enqueue.assign(mergeXYNodesEdges)
854
856
  enqueue.assign({
@@ -1,14 +1,15 @@
1
- import { ActionIconGroup, Badge, Loader, Stack } from '@mantine/core'
2
- import { openSpotlight } from '@mantine/spotlight'
3
- import { IconFileSymlink, IconFocusCentered, IconSearch } from '@tabler/icons-react'
1
+ import { ActionIconGroup, Badge, Group, Loader, Stack } from '@mantine/core'
2
+ import { IconFileSymlink, IconFocusCentered, IconMenu2 } from '@tabler/icons-react'
4
3
  import { clsx } from 'clsx'
4
+ import { LayoutGroup } from 'framer-motion'
5
5
  import { memo } from 'react'
6
- import { IfNotEnabled, useDiagramEventHandlers, useEnabledFeatures } from '../../../context'
6
+ import { useDiagramEventHandlers, useEnabledFeatures } from '../../../context'
7
7
  import { useMantinePortalProps } from '../../../hooks'
8
8
  import { useDiagram } from '../../../hooks/useDiagram'
9
9
  import { useDiagramSyncLayoutState } from '../../../hooks/useDiagramActor'
10
10
  import { useDiagramContext } from '../../../hooks/useDiagramContext'
11
11
  import { stopPropagation } from '../../../utils'
12
+ import { LikeC4Search } from '../search/LikeC4Search'
12
13
  import { ActionIcon, Tooltip } from './_shared'
13
14
  import { ChangeAutoLayoutButton } from './ChangeAutoLayoutButton'
14
15
  import { LayoutDriftNotification } from './LayoutDriftNotification'
@@ -25,41 +26,47 @@ export const Controls = memo(() => {
25
26
  const diagram = useDiagram()
26
27
  const {
27
28
  onOpenSource,
29
+ onBurgerMenuClick,
28
30
  } = useDiagramEventHandlers()
29
31
  const {
30
32
  enableControls,
31
33
  enableSearch,
32
34
  enableNavigationButtons,
33
35
  enableReadOnly,
34
- enableEdgeEditing,
35
36
  enableVscode,
36
37
  } = useEnabledFeatures()
37
38
  const notReadOnly = !enableReadOnly
38
39
  const portalProps = useMantinePortalProps()
40
+
39
41
  return (
40
- <>
41
- <Stack
42
- className={clsx(
43
- 'react-flow__panel',
44
- css.panel,
45
- 'likec4-top-left-panel',
46
- )}
47
- align="flex-start"
48
- onClick={stopPropagation}
49
- gap={'xs'}>
50
- {enableNavigationButtons && <NavigationButtons />}
51
- <ActionIconGroup className={css.actionIconGroup} orientation="vertical">
52
- {enableControls && enableSearch && (
53
- <Tooltip label="Search (Ctrl+F or ⌘+F)" {...portalProps}>
42
+ <Group
43
+ align="flex-start"
44
+ className={clsx(
45
+ 'react-flow__panel',
46
+ css.panel,
47
+ 'likec4-top-left-panel',
48
+ )}
49
+ gap="xs"
50
+ onClick={stopPropagation}
51
+ >
52
+ <Stack align="flex-start" gap="xs">
53
+ <LayoutGroup>
54
+ <Group
55
+ className={clsx(css.navigationButtons, 'likec4-navigation-webview')}
56
+ gap={'xs'}>
57
+ {onBurgerMenuClick && (
54
58
  <ActionIcon
55
59
  onClick={e => {
56
60
  e.stopPropagation()
57
- openSpotlight()
61
+ onBurgerMenuClick()
58
62
  }}>
59
- <IconSearch />
63
+ <IconMenu2 />
60
64
  </ActionIcon>
61
- </Tooltip>
62
- )}
65
+ )}
66
+ {enableNavigationButtons && <NavigationButtons />}
67
+ </Group>
68
+ </LayoutGroup>
69
+ <ActionIconGroup className={css.actionIconGroup} orientation="vertical">
63
70
  {enableVscode && (
64
71
  <Tooltip label="Open source" {...portalProps}>
65
72
  <ActionIcon
@@ -90,9 +97,12 @@ export const Controls = memo(() => {
90
97
  </Tooltip>
91
98
  )}
92
99
  </ActionIconGroup>
93
- {notReadOnly && <SyncLayoutBadge />}
94
100
  </Stack>
95
- </>
101
+ <Group>
102
+ {enableSearch && <LikeC4Search />}
103
+ {notReadOnly && <SyncLayoutBadge />}
104
+ </Group>
105
+ </Group>
96
106
  )
97
107
  })
98
108
 
@@ -2,19 +2,13 @@ import { IconChevronRight } from '@tabler/icons-react'
2
2
 
3
3
  import { AnimatePresence, m } from 'framer-motion'
4
4
 
5
- import { Group } from '@mantine/core'
6
- import { IconChevronLeft, IconMenu2 } from '@tabler/icons-react'
7
- import clsx from 'clsx'
8
- import { LayoutGroup } from 'framer-motion'
9
- import { useDiagramEventHandlers } from '../../../context'
5
+ import { IconChevronLeft } from '@tabler/icons-react'
10
6
  import { useDiagram } from '../../../hooks/useDiagram'
11
7
  import { useDiagramContext } from '../../../hooks/useDiagramContext'
12
8
  import { ActionIcon } from './_shared'
13
- import * as css from './styles.css'
14
9
 
15
10
  export const NavigationButtons = () => {
16
11
  const diagram = useDiagram()
17
- const { onBurgerMenuClick } = useDiagramEventHandlers()
18
12
  const {
19
13
  hasStepBack,
20
14
  hasStepForward,
@@ -23,61 +17,45 @@ export const NavigationButtons = () => {
23
17
  hasStepForward: s.navigationHistory.currentIndex < s.navigationHistory.history.length - 1,
24
18
  }))
25
19
  return (
26
- <LayoutGroup>
27
- <Group
28
- className={clsx(css.navigationButtons, 'likec4-navigation-webview')}
29
- align="flex-start"
30
- gap={'xs'}>
31
- {onBurgerMenuClick && (
20
+ <AnimatePresence>
21
+ {hasStepBack && (
22
+ <m.div
23
+ layout
24
+ initial={{ opacity: 0.05, transform: 'translateX(-10px)' }}
25
+ animate={{ opacity: 1, transform: 'translateX(0)' }}
26
+ exit={{
27
+ opacity: 0.05,
28
+ transform: 'translateX(-10px)',
29
+ }}
30
+ key={'back'}>
32
31
  <ActionIcon
33
32
  onClick={e => {
34
33
  e.stopPropagation()
35
- onBurgerMenuClick()
34
+ diagram.navigate('back')
36
35
  }}>
37
- <IconMenu2 />
36
+ <IconChevronLeft />
38
37
  </ActionIcon>
39
- )}
40
- <AnimatePresence>
41
- {hasStepBack && (
42
- <m.div
43
- layout
44
- initial={{ opacity: 0.05, transform: 'translateX(-10px)' }}
45
- animate={{ opacity: 1, transform: 'translateX(0)' }}
46
- exit={{
47
- opacity: 0.05,
48
- transform: 'translateX(-10px)',
49
- }}
50
- key={'back'}>
51
- <ActionIcon
52
- onClick={e => {
53
- e.stopPropagation()
54
- diagram.navigate('back')
55
- }}>
56
- <IconChevronLeft />
57
- </ActionIcon>
58
- </m.div>
59
- )}
60
- {hasStepForward && (
61
- <m.div
62
- layout
63
- initial={{ opacity: 0.05, transform: 'translateX(10px)' }}
64
- animate={{ opacity: 1, transform: 'translateX(0)' }}
65
- exit={{
66
- opacity: 0,
67
- transform: 'translateX(10px)',
68
- }}
69
- key={'forward'}>
70
- <ActionIcon
71
- onClick={e => {
72
- e.stopPropagation()
73
- diagram.navigate('forward')
74
- }}>
75
- <IconChevronRight />
76
- </ActionIcon>
77
- </m.div>
78
- )}
79
- </AnimatePresence>
80
- </Group>
81
- </LayoutGroup>
38
+ </m.div>
39
+ )}
40
+ {hasStepForward && (
41
+ <m.div
42
+ layout
43
+ initial={{ opacity: 0.05, transform: 'translateX(10px)' }}
44
+ animate={{ opacity: 1, transform: 'translateX(0)' }}
45
+ exit={{
46
+ opacity: 0,
47
+ transform: 'translateX(10px)',
48
+ }}
49
+ key={'forward'}>
50
+ <ActionIcon
51
+ onClick={e => {
52
+ e.stopPropagation()
53
+ diagram.navigate('forward')
54
+ }}>
55
+ <IconChevronRight />
56
+ </ActionIcon>
57
+ </m.div>
58
+ )}
59
+ </AnimatePresence>
82
60
  )
83
61
  }
@@ -5,12 +5,14 @@ import { mantine, vars } from '../../../theme-vars'
5
5
 
6
6
  export const navigationButtons = style({
7
7
  gap: calc(mantine.spacing.xs).divide(1.5).toString(),
8
+ ':empty': {
9
+ display: 'none',
10
+ },
8
11
  })
9
12
 
10
13
  export const panel = style({
11
14
  top: fallbackVar(vars.navigationPanel.top, '1rem'),
12
15
  left: fallbackVar(vars.navigationPanel.left, '1rem'),
13
- gap: calc(mantine.spacing.xs).divide(1.5).toString(),
14
16
  margin: 0,
15
17
  })
16
18
 
@@ -3,7 +3,7 @@ import { useLocalStorage } from '@mantine/hooks'
3
3
  import { IconMenu } from '@tabler/icons-react'
4
4
  import clsx from 'clsx'
5
5
  import { AnimatePresence, m } from 'framer-motion'
6
- import { Link } from '../../../base'
6
+ import { Link } from '../../../components/Link'
7
7
  import { useDiagramContext } from '../../../hooks/useDiagramContext'
8
8
  import type { DiagramContext } from '../../state/machine'
9
9
  import * as css from './DiagramTitlePanel.css'
@@ -0,0 +1,119 @@
1
+ import { createContainer, createGlobalVar, fallbackVar, globalStyle, style } from '@vanilla-extract/css'
2
+ import { mantine } from '../../../theme-vars'
3
+ import { whereLight } from '../../../theme-vars.css'
4
+ import { button, buttonFocused, description, descriptionColor, iconColor, title } from './_shared.css'
5
+
6
+ export { focusable } from './_shared.css'
7
+
8
+ export const treeContainer = createContainer('likec4-elements-tree')
9
+
10
+ const whenContainerIsNarrow = `${treeContainer} (width < 450px)`
11
+
12
+ export const treeNode = style({
13
+ outline: 'none',
14
+ marginBottom: 8,
15
+ })
16
+ export const treeRoot = style({
17
+ overflow: 'hidden',
18
+ containerName: treeContainer,
19
+ containerType: 'inline-size',
20
+ height: '100%',
21
+ })
22
+ export const treeLabel = style({
23
+ display: 'flex',
24
+ alignItems: 'baseline',
25
+ outline: 'none !important',
26
+ gap: 4,
27
+ })
28
+ export const treeSubtree = style({
29
+ marginTop: 8,
30
+ })
31
+
32
+ export const iconSize = createGlobalVar('likec4-icon-size')
33
+ export const elementExpandIcon = style({
34
+ color: mantine.colors.dimmed,
35
+ })
36
+
37
+ export const elementButton = style([button, {
38
+ flexGrow: 1,
39
+ vars: {
40
+ [iconSize]: '24px',
41
+ },
42
+ '@container': {
43
+ [`${whenContainerIsNarrow}`]: {
44
+ vars: {
45
+ [iconSize]: '18px',
46
+ },
47
+ },
48
+ },
49
+ }])
50
+
51
+ globalStyle(`${treeNode}:focus > ${treeLabel} ${elementButton}`, buttonFocused)
52
+
53
+ export const elementTitleAndId = style({
54
+ '@container': {
55
+ [`${whenContainerIsNarrow}`]: {
56
+ flexDirection: 'column-reverse',
57
+ alignItems: 'flex-start',
58
+ gap: 2,
59
+ },
60
+ },
61
+ })
62
+
63
+ export const elementTitle = style([title])
64
+ export const elementId = style({
65
+ color: fallbackVar(descriptionColor, mantine.colors.dimmed),
66
+ fontSize: 10,
67
+ lineHeight: 1.3,
68
+ display: 'block',
69
+ fontWeight: 500,
70
+ whiteSpace: 'nowrap',
71
+ padding: '1px 5px',
72
+ borderRadius: 4,
73
+ background: `color-mix(in srgb, ${mantine.colors.dark[9]}, transparent 70%)`,
74
+ selectors: {
75
+ [`${whereLight} &`]: {
76
+ backgroundColor: `color-mix(in srgb, ${mantine.colors.gray[3]}, transparent 80%)`,
77
+ },
78
+ },
79
+ })
80
+ export const elementDescription = style([description])
81
+
82
+ export const elementIcon = style({
83
+ flex: `0 0 ${iconSize}`,
84
+ height: iconSize,
85
+ width: iconSize,
86
+ display: 'flex',
87
+ alignItems: 'center',
88
+ justifyContent: 'center',
89
+ alignSelf: 'flex-start',
90
+ marginTop: 4,
91
+ })
92
+ globalStyle(`${elementIcon} svg, ${elementIcon} img`, {
93
+ width: '100%',
94
+ height: 'auto',
95
+ maxHeight: '100%',
96
+ pointerEvents: 'none',
97
+ })
98
+ globalStyle(`${elementIcon} img`, {
99
+ objectFit: 'contain',
100
+ })
101
+ globalStyle(`${elementIcon}.likec4-shape-icon svg`, {
102
+ color: iconColor,
103
+ width: '90%',
104
+ strokeWidth: 1.5,
105
+ })
106
+
107
+ export const elementViewsCount = style({
108
+ flex: 0,
109
+ color: fallbackVar(descriptionColor, mantine.colors.dimmed),
110
+ fontSize: 10,
111
+ fontWeight: 500,
112
+ whiteSpace: 'nowrap',
113
+ lineHeight: 1.1,
114
+ '@container': {
115
+ [`${whenContainerIsNarrow}`]: {
116
+ display: 'none',
117
+ },
118
+ },
119
+ })