@likec4/diagram 1.12.1 → 1.13.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.
- package/package.json +9 -9
- package/src/LikeC4CustomColors.tsx +2 -2
- package/src/LikeC4Diagram.props.ts +15 -7
- package/src/LikeC4Diagram.tsx +23 -14
- package/src/StaticLikeC4Diagram.tsx +2 -0
- package/src/components/Overlay.css.ts +78 -0
- package/src/components/Overlay.tsx +102 -0
- package/src/components/OverlayContext.tsx +11 -0
- package/src/components/index.ts +2 -0
- package/src/likec4model/index.ts +1 -0
- package/src/likec4model/useLikeC4ElementsTree.ts +44 -0
- package/src/overlays/edge-details/EdgeDetailsOverlay.css.ts +13 -0
- package/src/overlays/edge-details/EdgeDetailsOverlay.tsx +37 -0
- package/src/overlays/index.tsx +33 -0
- package/src/overlays/relationships-of/RelationshipsOverlay.tsx +33 -0
- package/src/overlays/relationships-of/RelationshipsXYFlow.tsx +359 -0
- package/src/overlays/relationships-of/SelectElement.css.ts +18 -0
- package/src/overlays/relationships-of/SelectElement.tsx +133 -0
- package/src/overlays/relationships-of/_types.ts +56 -0
- package/src/overlays/relationships-of/styles.css.ts +34 -0
- package/src/overlays/relationships-of/use-layouted-relationships.ts +607 -0
- package/src/overlays/relationships-of/xyflow/CompoundNode.tsx +76 -0
- package/src/overlays/relationships-of/xyflow/ElementNode.tsx +130 -0
- package/src/overlays/relationships-of/xyflow/EmptyNode.tsx +22 -0
- package/src/overlays/relationships-of/xyflow/RelationshipEdge.tsx +59 -0
- package/src/overlays/relationships-of/xyflow/styles.css.ts +170 -0
- package/src/state/diagram-to-xyflow.ts +1 -1
- package/src/state/diagramStore.ts +119 -27
- package/src/ui/top-left/ChangeAutoLayoutButton.tsx +35 -18
- package/src/ui/top-left/ManualLayoutToolsButton.tsx +110 -0
- package/src/ui/top-left/TopLeftPanel.tsx +15 -31
- package/src/ui/top-left/_shared.ts +2 -2
- package/src/xyflow/EdgeMarkers.tsx +19 -0
- package/src/xyflow/XYFlowEvents.tsx +21 -24
- package/src/xyflow/XYFlowInner.tsx +6 -10
- package/src/xyflow/edges/EdgeLabel.tsx +6 -5
- package/src/xyflow/edges/RelationshipEdge.tsx +4 -3
- package/src/xyflow/edges/RelationshipsDropdownMenu.tsx +16 -0
- package/src/xyflow/edges/edges.css.ts +5 -11
- package/src/xyflow/nodes/compound/CompoundNode.css.ts +17 -40
- package/src/xyflow/nodes/compound/CompoundNode.tsx +219 -28
- package/src/xyflow/nodes/element/ElementLink.tsx +11 -8
- package/src/xyflow/nodes/element/ElementNode.tsx +211 -140
- package/src/xyflow/nodes/element/element.css.ts +22 -33
- package/src/xyflow/nodes/shared/Toolbar.tsx +81 -27
- package/src/xyflow/nodes/use-animate-variants.tsx +64 -0
- package/src/xyflow/useLayoutConstraints.ts +95 -125
- package/src/xyflow/utils.ts +4 -0
- package/src/xyflow/nodes/shared/NavigateToBtn.tsx +0 -32
|
@@ -1,103 +1,140 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Text as MantineText } from '@mantine/core'
|
|
1
|
+
import { type DiagramNode, type ThemeColor } from '@likec4/core'
|
|
2
|
+
import { ActionIcon, Text as MantineText, Tooltip } from '@mantine/core'
|
|
3
3
|
import { useDebouncedValue } from '@mantine/hooks'
|
|
4
|
+
import { IconTransform, IconZoomScan } from '@tabler/icons-react'
|
|
4
5
|
import { Handle, type NodeProps, Position } from '@xyflow/react'
|
|
5
6
|
import clsx from 'clsx'
|
|
6
7
|
import { deepEqual as eq } from 'fast-equals'
|
|
7
8
|
import { m, type Variants } from 'framer-motion'
|
|
8
|
-
import { memo, useState } from 'react'
|
|
9
|
-
import { isNumber, isTruthy
|
|
10
|
-
import {
|
|
9
|
+
import { memo, useCallback, useState } from 'react'
|
|
10
|
+
import { isNumber, isTruthy } from 'remeda'
|
|
11
|
+
import { useDiagramState } from '../../../hooks/useDiagramState'
|
|
12
|
+
import type { ElementIconRenderer } from '../../../LikeC4Diagram.props'
|
|
11
13
|
import type { ElementXYFlowNode } from '../../types'
|
|
12
|
-
import { toDomPrecision } from '../../utils'
|
|
13
|
-
import { NavigateToBtn } from '../shared/NavigateToBtn'
|
|
14
|
+
import { stopPropagation, toDomPrecision } from '../../utils'
|
|
14
15
|
import { ElementToolbar } from '../shared/Toolbar'
|
|
16
|
+
import { useFramerAnimateVariants } from '../use-animate-variants'
|
|
15
17
|
import * as css from './element.css'
|
|
16
18
|
import { ElementLink } from './ElementLink'
|
|
17
19
|
import { ElementShapeSvg, SelectedIndicator } from './ElementShapeSvg'
|
|
18
20
|
|
|
19
|
-
const {
|
|
20
|
-
primary,
|
|
21
|
-
secondary,
|
|
22
|
-
muted,
|
|
23
|
-
...otherColors
|
|
24
|
-
} = defaultTheme.elements
|
|
25
|
-
|
|
26
|
-
export const themedColors = [
|
|
27
|
-
{ key: 'primary', value: primary.fill },
|
|
28
|
-
{ key: 'secondary', value: secondary.fill },
|
|
29
|
-
{ key: 'muted', value: muted.fill }
|
|
30
|
-
] satisfies Array<{ key: ThemeColor; value: string }>
|
|
31
|
-
|
|
32
|
-
export const colors = keys(otherColors).map(key => ({
|
|
33
|
-
key,
|
|
34
|
-
value: defaultTheme.elements[key].fill
|
|
35
|
-
}))
|
|
36
|
-
|
|
37
|
-
export type ThemeColorKey = typeof themedColors[0]['key']
|
|
38
|
-
export type ColorKey = typeof colors[0]['key']
|
|
39
|
-
|
|
40
21
|
const Text = MantineText.withProps({
|
|
41
22
|
component: 'div'
|
|
42
23
|
})
|
|
43
24
|
|
|
44
|
-
type ElementNodeProps = NodeProps<ElementXYFlowNode>
|
|
45
|
-
|
|
46
25
|
const selectedScale = 1.015
|
|
26
|
+
|
|
47
27
|
// Frame-motion variants
|
|
48
|
-
|
|
28
|
+
|
|
29
|
+
const VariantsRoot = {
|
|
49
30
|
idle: (_, { scale }) => ({
|
|
50
31
|
scale: 1,
|
|
51
|
-
transition:
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
32
|
+
transition: isNumber(scale) && scale > selectedScale
|
|
33
|
+
? {
|
|
34
|
+
delay: 0.1,
|
|
35
|
+
delayChildren: 0.06,
|
|
36
|
+
staggerChildren: 0.07,
|
|
37
|
+
staggerDirection: -1
|
|
38
|
+
}
|
|
39
|
+
: {}
|
|
60
40
|
}),
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
// transition: {
|
|
64
|
-
// duration: 0.8,
|
|
65
|
-
// ease: 'easeInOut',
|
|
66
|
-
// }
|
|
67
|
-
// },
|
|
68
|
-
// dragging: {
|
|
69
|
-
// scale: selectedScale
|
|
70
|
-
// },
|
|
71
|
-
// hovered: {
|
|
72
|
-
// scale: 1.08
|
|
73
|
-
// },
|
|
74
|
-
// hover: (_, {scale}) => isNumber(scale) && scale < 1.02 ? ({
|
|
75
|
-
// scale: 1.08,
|
|
76
|
-
// }) : ({
|
|
77
|
-
// scale: 1.08,
|
|
78
|
-
// transition: {
|
|
79
|
-
// delay: 1
|
|
80
|
-
// }
|
|
81
|
-
// }),
|
|
82
|
-
hovered: {
|
|
83
|
-
scale: 1.06
|
|
41
|
+
selected: {
|
|
42
|
+
scale: selectedScale
|
|
84
43
|
},
|
|
44
|
+
hovered: (_, { scale }) => ({
|
|
45
|
+
scale: 1.06,
|
|
46
|
+
transition: !isNumber(scale) || (scale >= 1 && scale < 1.06)
|
|
47
|
+
? {
|
|
48
|
+
// delay: 0.09,
|
|
49
|
+
delayChildren: 0.07,
|
|
50
|
+
staggerChildren: 0.1
|
|
51
|
+
}
|
|
52
|
+
: {}
|
|
53
|
+
}),
|
|
85
54
|
tap: {
|
|
86
55
|
scale: 0.975
|
|
87
56
|
}
|
|
88
|
-
// tap: {
|
|
89
|
-
// scale: 0.9
|
|
90
|
-
// }
|
|
91
57
|
} satisfies Variants
|
|
92
58
|
|
|
59
|
+
const VariantsNavigate = {
|
|
60
|
+
idle: {
|
|
61
|
+
'--ai-bg': 'var(--ai-bg-idle)',
|
|
62
|
+
scale: 1,
|
|
63
|
+
opacity: 0.75,
|
|
64
|
+
translateX: '-50%',
|
|
65
|
+
originY: 0.1
|
|
66
|
+
},
|
|
67
|
+
selected: {},
|
|
68
|
+
hovered: {
|
|
69
|
+
display: 'block',
|
|
70
|
+
'--ai-bg': 'var(--ai-bg-hover)',
|
|
71
|
+
scale: 1.35,
|
|
72
|
+
opacity: 1
|
|
73
|
+
},
|
|
74
|
+
'hovered:navigate': {
|
|
75
|
+
scale: 1.42
|
|
76
|
+
},
|
|
77
|
+
'tap:navigate': {
|
|
78
|
+
scale: 1.15
|
|
79
|
+
}
|
|
80
|
+
} satisfies Variants
|
|
81
|
+
VariantsNavigate['selected'] = VariantsNavigate['hovered']
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Variants for the relationships button (when Navigate button is visible)
|
|
85
|
+
*/
|
|
86
|
+
const VariantsRelationsBtn = {
|
|
87
|
+
idle: {
|
|
88
|
+
'--ai-bg': 'var(--ai-bg-idle)',
|
|
89
|
+
scale: 1,
|
|
90
|
+
opacity: 0,
|
|
91
|
+
originX: 0.25,
|
|
92
|
+
originY: 0.1,
|
|
93
|
+
translateX: -8,
|
|
94
|
+
transitionEnd: {
|
|
95
|
+
display: 'none'
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
selected: {},
|
|
99
|
+
hovered: {
|
|
100
|
+
display: 'block',
|
|
101
|
+
'--ai-bg': 'var(--ai-bg-hover)',
|
|
102
|
+
scale: 1.25,
|
|
103
|
+
opacity: 1,
|
|
104
|
+
translateX: 0
|
|
105
|
+
},
|
|
106
|
+
'hovered:navigate': {},
|
|
107
|
+
'hovered:relations': {
|
|
108
|
+
scale: 1.42
|
|
109
|
+
},
|
|
110
|
+
'tap:relations': {
|
|
111
|
+
scale: 1.15
|
|
112
|
+
}
|
|
113
|
+
} satisfies Variants
|
|
114
|
+
VariantsRelationsBtn['selected'] = VariantsRelationsBtn['hovered']
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Variants for the relationships button (when Navigate button is not visible)
|
|
118
|
+
*/
|
|
119
|
+
const VariantsRelationsBtnSingle = {
|
|
120
|
+
...VariantsNavigate,
|
|
121
|
+
idle: {
|
|
122
|
+
...VariantsNavigate.idle,
|
|
123
|
+
opacity: 0,
|
|
124
|
+
transitionEnd: {
|
|
125
|
+
display: 'none'
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
type ElementNodeProps = NodeProps<ElementXYFlowNode>
|
|
93
131
|
const isEqualProps = (prev: ElementNodeProps, next: ElementNodeProps) => (
|
|
94
132
|
prev.id === next.id
|
|
95
133
|
&& eq(prev.selected ?? false, next.selected ?? false)
|
|
96
134
|
&& eq(prev.dragging ?? false, next.dragging ?? false)
|
|
97
|
-
// && eq(prev.draggable ?? false, next.draggable ?? false)
|
|
98
135
|
&& eq(prev.width ?? 0, next.width ?? 0)
|
|
99
136
|
&& eq(prev.height ?? 0, next.height ?? 0)
|
|
100
|
-
&& eq(prev.data
|
|
137
|
+
&& eq(prev.data, next.data)
|
|
101
138
|
)
|
|
102
139
|
|
|
103
140
|
export const ElementNodeMemo = memo<ElementNodeProps>(function ElementNode({
|
|
@@ -106,32 +143,46 @@ export const ElementNodeMemo = memo<ElementNodeProps>(function ElementNode({
|
|
|
106
143
|
element
|
|
107
144
|
},
|
|
108
145
|
dragging,
|
|
109
|
-
// draggable = false,
|
|
110
146
|
selected = false,
|
|
111
147
|
width,
|
|
112
148
|
height
|
|
113
149
|
}) {
|
|
114
|
-
const
|
|
115
|
-
|
|
150
|
+
const {
|
|
151
|
+
isEditable,
|
|
152
|
+
isHovered,
|
|
153
|
+
isDimmed,
|
|
154
|
+
hasOnNavigateTo,
|
|
155
|
+
isHovercards,
|
|
156
|
+
isInteractive,
|
|
157
|
+
enableRelationshipsMode,
|
|
158
|
+
triggerOnNavigateTo,
|
|
159
|
+
openOverlay,
|
|
160
|
+
renderIcon
|
|
161
|
+
} = useDiagramState(s => ({
|
|
116
162
|
isEditable: s.readonly !== true,
|
|
117
163
|
isHovered: s.hoveredNodeId === id,
|
|
118
164
|
isDimmed: s.dimmed.has(id),
|
|
119
|
-
isInteractive: s.nodesDraggable || s.nodesSelectable ||
|
|
165
|
+
isInteractive: s.nodesDraggable || s.nodesSelectable || s.enableRelationshipsBrowser
|
|
166
|
+
|| (!!s.onNavigateTo && !!element.navigateTo),
|
|
120
167
|
isHovercards: s.showElementLinks,
|
|
121
|
-
hasOnNavigateTo: !!s.onNavigateTo
|
|
122
|
-
|
|
168
|
+
hasOnNavigateTo: !!s.onNavigateTo,
|
|
169
|
+
enableRelationshipsMode: s.enableRelationshipsBrowser,
|
|
170
|
+
triggerOnNavigateTo: s.triggerOnNavigateTo,
|
|
171
|
+
openOverlay: s.openOverlay,
|
|
172
|
+
renderIcon: s.renderIcon
|
|
123
173
|
}))
|
|
174
|
+
|
|
124
175
|
const isNavigable = hasOnNavigateTo && !!element.navigateTo
|
|
125
|
-
// const isIdle = useIdle(300)
|
|
126
|
-
// const [isToolbarVisible] = useDebouncedValue(isEditable && (isHovered || (import.meta.env.DEV && selected)), isIdle ? 50 : 350)
|
|
127
|
-
const _isToolbarVisible = isEditable && (isHovered || (import.meta.env.DEV && selected))
|
|
128
176
|
|
|
177
|
+
const _isToolbarVisible = isEditable && (isHovered || (import.meta.env.DEV && selected))
|
|
129
178
|
const [isToolbarVisible] = useDebouncedValue(_isToolbarVisible, _isToolbarVisible ? 500 : 300)
|
|
130
179
|
|
|
131
180
|
const w = toDomPrecision(width ?? element.width)
|
|
132
181
|
const h = toDomPrecision(height ?? element.height)
|
|
133
182
|
|
|
134
|
-
|
|
183
|
+
const [animateVariants, animateHandlers] = useFramerAnimateVariants()
|
|
184
|
+
|
|
185
|
+
let animate: keyof typeof VariantsRoot
|
|
135
186
|
switch (true) {
|
|
136
187
|
case dragging && selected:
|
|
137
188
|
animate = 'selected'
|
|
@@ -145,12 +196,27 @@ export const ElementNodeMemo = memo<ElementNodeProps>(function ElementNode({
|
|
|
145
196
|
case selected:
|
|
146
197
|
animate = 'selected'
|
|
147
198
|
break
|
|
199
|
+
default:
|
|
200
|
+
animate = 'idle'
|
|
148
201
|
}
|
|
149
202
|
|
|
150
|
-
const elementIcon = ElementIcon({
|
|
203
|
+
const elementIcon = ElementIcon({
|
|
204
|
+
element,
|
|
205
|
+
renderIcon
|
|
206
|
+
})
|
|
151
207
|
|
|
152
208
|
const [previewColor, setPreviewColor] = useState<ThemeColor | null>(null)
|
|
153
209
|
|
|
210
|
+
const onNavigateTo = useCallback((e: React.MouseEvent) => {
|
|
211
|
+
e.stopPropagation()
|
|
212
|
+
triggerOnNavigateTo(id, e)
|
|
213
|
+
}, [triggerOnNavigateTo, id])
|
|
214
|
+
|
|
215
|
+
const onBrowseRelations = useCallback((e: React.MouseEvent) => {
|
|
216
|
+
e.stopPropagation()
|
|
217
|
+
openOverlay({ relationshipsOf: element.id })
|
|
218
|
+
}, [openOverlay, element.id])
|
|
219
|
+
|
|
154
220
|
return (
|
|
155
221
|
<>
|
|
156
222
|
{isToolbarVisible && (
|
|
@@ -160,11 +226,6 @@ export const ElementNodeMemo = memo<ElementNodeProps>(function ElementNode({
|
|
|
160
226
|
onColorPreview={setPreviewColor}
|
|
161
227
|
/>
|
|
162
228
|
)}
|
|
163
|
-
<Handle
|
|
164
|
-
type="target"
|
|
165
|
-
position={Position.Top}
|
|
166
|
-
className={css.handleCenter}
|
|
167
|
-
/>
|
|
168
229
|
<m.div
|
|
169
230
|
id={id}
|
|
170
231
|
className={clsx([
|
|
@@ -176,50 +237,22 @@ export const ElementNodeMemo = memo<ElementNodeProps>(function ElementNode({
|
|
|
176
237
|
data-hovered={!dragging && isHovered}
|
|
177
238
|
data-likec4-color={previewColor ?? element.color}
|
|
178
239
|
data-likec4-shape={element.shape}
|
|
179
|
-
|
|
240
|
+
data-animate-target=""
|
|
241
|
+
variants={VariantsRoot}
|
|
180
242
|
initial={false}
|
|
181
|
-
animate={animate}
|
|
243
|
+
animate={(isHovered && !dragging) ? (animateVariants ?? animate) : animate}
|
|
182
244
|
tabIndex={-1}
|
|
183
245
|
{...(isInteractive && {
|
|
184
|
-
|
|
246
|
+
onTapStart: animateHandlers.onTapStart,
|
|
247
|
+
onTap: animateHandlers.onTap,
|
|
248
|
+
onTapCancel: animateHandlers.onTapCancel
|
|
185
249
|
})}
|
|
186
250
|
>
|
|
187
|
-
{
|
|
188
|
-
/* <svg
|
|
189
|
-
className={clsx(
|
|
190
|
-
css.cssShapeSvgMultiple
|
|
191
|
-
)}
|
|
192
|
-
viewBox={`0 0 ${w} ${h}`}
|
|
193
|
-
width={w}
|
|
194
|
-
height={h}
|
|
195
|
-
>
|
|
196
|
-
<ElementShapeSvg
|
|
197
|
-
shape={element.shape}
|
|
198
|
-
w={w}
|
|
199
|
-
h={h}
|
|
200
|
-
/>
|
|
201
|
-
</svg> */
|
|
202
|
-
}
|
|
203
|
-
<svg
|
|
204
|
-
className={clsx(
|
|
205
|
-
css.cssShapeSvg
|
|
206
|
-
)}
|
|
207
|
-
viewBox={`0 0 ${w} ${h}`}
|
|
208
|
-
width={w}
|
|
209
|
-
height={h}
|
|
210
|
-
>
|
|
251
|
+
<svg className={clsx(css.cssShapeSvg)} viewBox={`0 0 ${w} ${h}`} width={w} height={h}>
|
|
211
252
|
<g className={css.indicator}>
|
|
212
|
-
<SelectedIndicator
|
|
213
|
-
shape={element.shape}
|
|
214
|
-
w={w}
|
|
215
|
-
h={h}
|
|
216
|
-
/>
|
|
253
|
+
<SelectedIndicator shape={element.shape} w={w} h={h} />
|
|
217
254
|
</g>
|
|
218
|
-
<ElementShapeSvg
|
|
219
|
-
shape={element.shape}
|
|
220
|
-
w={w}
|
|
221
|
-
h={h}
|
|
222
|
-
/>
|
|
255
|
+
<ElementShapeSvg shape={element.shape} w={w} h={h} />
|
|
223
256
|
</svg>
|
|
224
257
|
<div
|
|
225
258
|
className={clsx(
|
|
@@ -230,54 +263,92 @@ export const ElementNodeMemo = memo<ElementNodeProps>(function ElementNode({
|
|
|
230
263
|
>
|
|
231
264
|
{elementIcon}
|
|
232
265
|
<div className={clsx(css.elementTextData, 'likec4-element-main-props')}>
|
|
233
|
-
<Text
|
|
234
|
-
className={clsx(css.title, 'likec4-element-title')}>
|
|
266
|
+
<Text className={clsx(css.title, 'likec4-element-title')} lineClamp={3}>
|
|
235
267
|
{element.title}
|
|
236
268
|
</Text>
|
|
237
269
|
{element.technology && (
|
|
238
|
-
<Text
|
|
239
|
-
className={clsx(css.technology, 'likec4-element-technology')}>
|
|
270
|
+
<Text className={clsx(css.technology, 'likec4-element-technology')}>
|
|
240
271
|
{element.technology}
|
|
241
272
|
</Text>
|
|
242
273
|
)}
|
|
243
274
|
{element.description && (
|
|
244
|
-
<Text
|
|
245
|
-
className={clsx(css.description, 'likec4-element-description')}>
|
|
275
|
+
<Text className={clsx(css.description, 'likec4-element-description')} lineClamp={5}>
|
|
246
276
|
{element.description}
|
|
247
277
|
</Text>
|
|
248
278
|
)}
|
|
249
279
|
</div>
|
|
250
280
|
</div>
|
|
251
281
|
{isHovercards && element.links && <ElementLink element={element} />}
|
|
252
|
-
{isNavigable &&
|
|
282
|
+
{isNavigable && (
|
|
283
|
+
<ActionIcon
|
|
284
|
+
component={m.div}
|
|
285
|
+
variants={VariantsNavigate}
|
|
286
|
+
data-animate-target="navigate"
|
|
287
|
+
className={clsx('nodrag nopan', css.navigateBtn)}
|
|
288
|
+
radius="md"
|
|
289
|
+
style={{ zIndex: 100 }}
|
|
290
|
+
role="button"
|
|
291
|
+
onClick={onNavigateTo}
|
|
292
|
+
onDoubleClick={stopPropagation}
|
|
293
|
+
{...isInteractive && animateHandlers}
|
|
294
|
+
>
|
|
295
|
+
<IconZoomScan stroke={1.8} style={{ width: '75%' }} />
|
|
296
|
+
</ActionIcon>
|
|
297
|
+
)}
|
|
298
|
+
{enableRelationshipsMode && (
|
|
299
|
+
<Tooltip fz="xs" color="dark" label="Browse relationships" withinPortal={false} openDelay={600}>
|
|
300
|
+
<ActionIcon
|
|
301
|
+
component={m.div}
|
|
302
|
+
// Is is a second button, so we need to use a different variant
|
|
303
|
+
variants={isNavigable ? VariantsRelationsBtn : VariantsRelationsBtnSingle}
|
|
304
|
+
data-animate-target={isNavigable ? 'relations' : 'navigate'}
|
|
305
|
+
className={clsx('nodrag nopan', css.navigateBtn)}
|
|
306
|
+
radius="md"
|
|
307
|
+
style={{
|
|
308
|
+
left: isNavigable ? 'calc(50% + 28px)' : '50%',
|
|
309
|
+
zIndex: 99
|
|
310
|
+
}}
|
|
311
|
+
role="button"
|
|
312
|
+
onClick={onBrowseRelations}
|
|
313
|
+
onDoubleClick={stopPropagation}
|
|
314
|
+
{...isInteractive && animateHandlers}
|
|
315
|
+
>
|
|
316
|
+
<IconTransform stroke={1.8} style={{ width: '67%' }} />
|
|
317
|
+
</ActionIcon>
|
|
318
|
+
</Tooltip>
|
|
319
|
+
)}
|
|
253
320
|
</m.div>
|
|
254
|
-
<Handle
|
|
255
|
-
|
|
256
|
-
position={Position.Top}
|
|
257
|
-
className={css.handleCenter}
|
|
258
|
-
/>
|
|
321
|
+
<Handle type="target" position={Position.Top} className={css.handleCenter} />
|
|
322
|
+
<Handle type="source" position={Position.Top} className={css.handleCenter} />
|
|
259
323
|
</>
|
|
260
324
|
)
|
|
261
325
|
}, isEqualProps)
|
|
262
326
|
|
|
263
|
-
const ElementIcon = (
|
|
264
|
-
|
|
327
|
+
const ElementIcon = (
|
|
328
|
+
{ element, renderIcon: RenderIcon }: { element: DiagramNode; renderIcon: ElementIconRenderer | null }
|
|
329
|
+
) => {
|
|
330
|
+
if (!element.icon) {
|
|
265
331
|
return null
|
|
266
332
|
}
|
|
267
|
-
if (
|
|
333
|
+
if (element.icon.startsWith('http://') || element.icon.startsWith('https://')) {
|
|
268
334
|
return (
|
|
269
335
|
<div className={clsx(css.elementIcon, 'likec4-element-icon')}>
|
|
270
|
-
<img src={
|
|
336
|
+
<img src={element.icon} alt={element.title} />
|
|
271
337
|
</div>
|
|
272
338
|
)
|
|
273
339
|
}
|
|
274
|
-
const RenderIcon =
|
|
275
|
-
const icon = RenderIcon ? <RenderIcon node={node} /> : null
|
|
340
|
+
const icon = RenderIcon ? <RenderIcon node={element} /> : null
|
|
276
341
|
if (!icon) {
|
|
277
342
|
return null
|
|
278
343
|
}
|
|
279
344
|
return (
|
|
280
|
-
<div
|
|
345
|
+
<div
|
|
346
|
+
className={clsx(
|
|
347
|
+
css.elementIcon,
|
|
348
|
+
'likec4-element-icon'
|
|
349
|
+
)}
|
|
350
|
+
data-likec4-icon={element.icon}
|
|
351
|
+
>
|
|
281
352
|
{icon}
|
|
282
353
|
</div>
|
|
283
354
|
)
|
|
@@ -23,6 +23,16 @@ export const container = style({
|
|
|
23
23
|
'&[data-hovered="true"]': {
|
|
24
24
|
willChange: 'transform'
|
|
25
25
|
}
|
|
26
|
+
},
|
|
27
|
+
':after': {
|
|
28
|
+
content: ' ',
|
|
29
|
+
position: 'absolute',
|
|
30
|
+
top: '100%',
|
|
31
|
+
left: 0,
|
|
32
|
+
right: 0,
|
|
33
|
+
height: 16,
|
|
34
|
+
background: 'transparent',
|
|
35
|
+
pointerEvents: 'all'
|
|
26
36
|
}
|
|
27
37
|
})
|
|
28
38
|
|
|
@@ -110,7 +120,7 @@ export const fillElementStroke = style({
|
|
|
110
120
|
})
|
|
111
121
|
|
|
112
122
|
export const fillMixStroke = style({
|
|
113
|
-
fill: stokeFillMix
|
|
123
|
+
fill: fallbackVar(stokeFillMix, `color-mix(in srgb, ${vars.element.stroke} 90%, ${vars.element.fill})`)
|
|
114
124
|
})
|
|
115
125
|
|
|
116
126
|
export const hasIcon = style({})
|
|
@@ -248,7 +258,12 @@ export const elementIcon = style({
|
|
|
248
258
|
alignItems: 'center',
|
|
249
259
|
justifyContent: 'center',
|
|
250
260
|
mixBlendMode: 'hard-light',
|
|
251
|
-
alignSelf: 'flex-start'
|
|
261
|
+
alignSelf: 'flex-start',
|
|
262
|
+
selectors: {
|
|
263
|
+
'&[data-likec4-icon^="azure:"]': {
|
|
264
|
+
mixBlendMode: 'normal'
|
|
265
|
+
}
|
|
266
|
+
}
|
|
252
267
|
})
|
|
253
268
|
globalStyle(`${elementIcon} svg, ${elementIcon} img`, {
|
|
254
269
|
width: '100%',
|
|
@@ -323,7 +338,7 @@ export const cssShapeSvg = style({
|
|
|
323
338
|
}
|
|
324
339
|
})
|
|
325
340
|
|
|
326
|
-
export const
|
|
341
|
+
export const navigateBtn = style({
|
|
327
342
|
zIndex: 'calc(var(--layer-overlays, 1) + 1)',
|
|
328
343
|
position: 'absolute',
|
|
329
344
|
pointerEvents: 'all',
|
|
@@ -331,45 +346,19 @@ export const cssNavigateBtn = style({
|
|
|
331
346
|
bottom: 0,
|
|
332
347
|
color: vars.element.loContrast,
|
|
333
348
|
cursor: 'pointer',
|
|
334
|
-
transformOrigin: '50% 65%',
|
|
335
|
-
opacity: 0.75,
|
|
336
|
-
transition: 'all 150ms ease-out',
|
|
337
|
-
transform: 'translate(-50%, 0)',
|
|
338
|
-
transitionDelay: '0ms',
|
|
339
349
|
backgroundColor: 'var(--ai-bg)',
|
|
340
350
|
'vars': {
|
|
341
|
-
'--ai-bg': `color-mix(in srgb , ${vars.element.fill}, transparent 99%)`,
|
|
351
|
+
'--ai-bg-idle': `color-mix(in srgb , ${vars.element.fill}, transparent 99%)`,
|
|
352
|
+
'--ai-bg': `var(--ai-bg-idle)`,
|
|
342
353
|
'--ai-bg-hover': `color-mix(in srgb , ${vars.element.fill} 65%, ${vars.element.stroke})`,
|
|
343
354
|
'--ai-hover': `color-mix(in srgb , ${vars.element.fill} 50%, ${vars.element.stroke})`
|
|
344
355
|
},
|
|
345
356
|
selectors: {
|
|
346
357
|
[`:where([data-likec4-shape='browser']) &`]: {
|
|
347
|
-
bottom:
|
|
348
|
-
},
|
|
349
|
-
[`:where(.react-flow__node:not(.dragging) ${container}:hover) &:not(:hover)`]: {
|
|
350
|
-
boxShadow: mantine.shadows.lg,
|
|
351
|
-
transform: 'translate(-50%, 0) scale(1.185)',
|
|
352
|
-
opacity: 1,
|
|
353
|
-
transitionDelay: '250ms',
|
|
354
|
-
vars: {
|
|
355
|
-
'--ai-bg': 'var(--ai-bg-hover)'
|
|
356
|
-
}
|
|
358
|
+
bottom: 4
|
|
357
359
|
}
|
|
358
360
|
},
|
|
359
361
|
':hover': {
|
|
360
|
-
boxShadow: mantine.shadows.
|
|
361
|
-
transform: 'translate(-50%, 0) scale(1.35)',
|
|
362
|
-
opacity: 1,
|
|
363
|
-
transitionDelay: '0'
|
|
364
|
-
},
|
|
365
|
-
':active': {
|
|
366
|
-
transform: 'translate(-50%, 0) scale(1.02)',
|
|
367
|
-
opacity: 1,
|
|
368
|
-
transitionDelay: '0'
|
|
362
|
+
boxShadow: mantine.shadows.md
|
|
369
363
|
}
|
|
370
364
|
})
|
|
371
|
-
globalStyle(`${cssNavigateBtn} svg.icon`, {
|
|
372
|
-
width: '65%',
|
|
373
|
-
height: '65%',
|
|
374
|
-
strokeWidth: '1.5'
|
|
375
|
-
})
|