@likec4/diagram 1.16.0 → 1.17.1
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 +22 -18
- package/src/LikeC4CustomColors.tsx +24 -21
- package/src/LikeC4Diagram.props.ts +15 -9
- package/src/LikeC4Diagram.tsx +33 -27
- package/src/LikeC4Search.tsx +146 -0
- package/src/StaticLikeC4Diagram.tsx +10 -5
- package/src/overlays/OverlayContext.tsx +1 -1
- package/src/overlays/Overlays.css.ts +28 -23
- package/src/overlays/Overlays.tsx +116 -64
- package/src/overlays/edge-details/xyflow/ElementNode.tsx +15 -12
- package/src/overlays/edge-details/xyflow/RelationshipEdge.tsx +7 -3
- package/src/overlays/edge-details/xyflow/styles.css.ts +22 -16
- package/src/overlays/element-details/ElementDetailsCard.css.ts +322 -0
- package/src/overlays/element-details/ElementDetailsCard.tsx +639 -0
- package/src/overlays/element-details/TabPanelRelationships.css.ts +106 -0
- package/src/overlays/element-details/TabPanelRelationships.tsx +201 -0
- package/src/overlays/element-details/TabPanelStructure.css.ts +36 -0
- package/src/overlays/element-details/TabPanelStructure.tsx +120 -0
- package/src/overlays/relationships-of/RelationshipsOverlay.tsx +185 -0
- package/src/overlays/relationships-of/RelationshipsXYFlow.tsx +179 -213
- package/src/overlays/relationships-of/_types.ts +15 -4
- package/src/overlays/relationships-of/use-layouted-relationships.ts +301 -207
- package/src/overlays/relationships-of/xyflow/CompoundNode.tsx +68 -14
- package/src/overlays/relationships-of/xyflow/ElementNode.tsx +54 -14
- package/src/overlays/relationships-of/xyflow/EmptyNode.tsx +1 -1
- package/src/overlays/relationships-of/xyflow/RelationshipEdge.tsx +51 -23
- package/src/overlays/relationships-of/xyflow/styles.css.ts +34 -27
- package/src/state/DiagramContext.tsx +15 -8
- package/src/state/aligners.ts +473 -0
- package/src/state/diagramStore.layout.ts +67 -0
- package/src/state/diagramStore.ts +18 -73
- package/src/theme-vars.ts +9 -0
- package/src/ui/FramerMotionConfig.tsx +18 -0
- package/src/ui/notation/NotationPanel.tsx +3 -3
- package/src/ui/top-left/ManualLayoutToolsButton.tsx +15 -0
- package/src/ui/top-left/TopLeftPanel.tsx +14 -1
- package/src/xyflow/XYFlow.tsx +18 -35
- package/src/xyflow/XYFlowInner.tsx +2 -2
- package/src/xyflow/edges/EdgeLabel.tsx +3 -3
- package/src/xyflow/edges/RelationshipsDropdownMenu.tsx +25 -18
- package/src/xyflow/nodes/compound/CompoundNode.css.ts +96 -30
- package/src/xyflow/nodes/compound/CompoundNode.tsx +149 -165
- package/src/xyflow/nodes/element/ElementDetails.css.ts +101 -0
- package/src/xyflow/nodes/element/ElementDetails.tsx +393 -0
- package/src/xyflow/nodes/element/ElementNode.tsx +165 -133
- package/src/xyflow/nodes/element/element.css.ts +46 -17
- package/src/xyflow/nodes/shared/ElementIcon.tsx +43 -0
- package/src/xyflow/nodes/shared/Toolbar.tsx +6 -6
- package/src/xyflow/{XYFlowEvents.tsx → useXYFlowEvents.ts} +26 -22
- package/src/xyflow/utils.ts +12 -3
- package/src/LikeC4CustomColors.props.ts +0 -5
- /package/src/{mantine → ui}/EnsureMantine.tsx +0 -0
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { Text as MantineText } from '@mantine/core'
|
|
2
|
-
import { Handle, type NodeProps, Position } from '@xyflow/react'
|
|
2
|
+
import { Handle, type NodeProps, Position, useStore } from '@xyflow/react'
|
|
3
3
|
import clsx from 'clsx'
|
|
4
|
+
import { deepEqual, shallowEqual } from 'fast-equals'
|
|
4
5
|
import { m } from 'framer-motion'
|
|
6
|
+
import { memo, useCallback } from 'react'
|
|
5
7
|
import type { XYFlowTypes } from '../_types'
|
|
6
8
|
import * as css from './styles.css'
|
|
7
9
|
|
|
@@ -11,15 +13,44 @@ const Text = MantineText.withProps({
|
|
|
11
13
|
|
|
12
14
|
type CompoundNodeProps = NodeProps<XYFlowTypes.CompoundNode>
|
|
13
15
|
|
|
14
|
-
export
|
|
16
|
+
export const CompoundNode = memo<CompoundNodeProps>(({
|
|
17
|
+
id,
|
|
15
18
|
data: {
|
|
16
19
|
element,
|
|
17
20
|
ports,
|
|
21
|
+
layoutId = id,
|
|
22
|
+
leaving = false,
|
|
23
|
+
initialAnimation = true,
|
|
18
24
|
...data
|
|
19
25
|
},
|
|
20
26
|
width = 200,
|
|
21
|
-
|
|
22
|
-
|
|
27
|
+
height = 100,
|
|
28
|
+
...props
|
|
29
|
+
}) => {
|
|
30
|
+
const scale = (diff: number) => ({
|
|
31
|
+
scaleX: (width + diff) / width,
|
|
32
|
+
scaleY: (height + diff) / height
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
let opacity = 1
|
|
36
|
+
if (data.dimmed) {
|
|
37
|
+
opacity = data.dimmed === 'immediate' ? 0.05 : 0.15
|
|
38
|
+
}
|
|
39
|
+
if (leaving) {
|
|
40
|
+
opacity = 0
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const {
|
|
44
|
+
elementsSelectable
|
|
45
|
+
} = useStore(
|
|
46
|
+
useCallback((s) => ({
|
|
47
|
+
elementsSelectable: s.elementsSelectable
|
|
48
|
+
}), []),
|
|
49
|
+
shallowEqual
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
const selectable = props.selectable ?? elementsSelectable
|
|
53
|
+
|
|
23
54
|
return (
|
|
24
55
|
<>
|
|
25
56
|
<m.div
|
|
@@ -27,27 +58,48 @@ export function CompoundNode({
|
|
|
27
58
|
css.compoundNodeBody,
|
|
28
59
|
'likec4-compound-node'
|
|
29
60
|
])}
|
|
30
|
-
|
|
61
|
+
layoutId={layoutId}
|
|
62
|
+
data-compound-depth={data.depth ?? 1}
|
|
31
63
|
data-likec4-color={element.color}
|
|
64
|
+
initial={(layoutId === id && initialAnimation)
|
|
65
|
+
? {
|
|
66
|
+
...scale(-20),
|
|
67
|
+
opacity: 0,
|
|
68
|
+
width,
|
|
69
|
+
height
|
|
70
|
+
}
|
|
71
|
+
: false}
|
|
32
72
|
animate={{
|
|
33
|
-
|
|
73
|
+
...scale(0),
|
|
74
|
+
opacity,
|
|
75
|
+
width,
|
|
76
|
+
height,
|
|
34
77
|
transition: {
|
|
35
|
-
|
|
78
|
+
opacity: {
|
|
79
|
+
delay: !leaving && data.dimmed === true ? .4 : 0,
|
|
80
|
+
...((leaving || data.dimmed === 'immediate') && {
|
|
81
|
+
duration: 0.09
|
|
82
|
+
})
|
|
83
|
+
}
|
|
36
84
|
}
|
|
37
85
|
}}
|
|
38
86
|
{...(selectable && {
|
|
39
87
|
whileHover: {
|
|
40
|
-
scale
|
|
88
|
+
...scale(12),
|
|
89
|
+
scaleX: (width + 12) / width,
|
|
90
|
+
scaleY: (height + 12) / height,
|
|
41
91
|
transition: {
|
|
42
|
-
delay: 0.
|
|
92
|
+
delay: 0.1
|
|
43
93
|
}
|
|
44
94
|
},
|
|
45
95
|
whileTap: {
|
|
46
|
-
scale
|
|
96
|
+
...scale(-12)
|
|
47
97
|
}
|
|
48
98
|
})}
|
|
49
99
|
>
|
|
50
|
-
<Text className={css.compoundNodeTitle} maw={width - 20}>
|
|
100
|
+
<Text className={css.compoundNodeTitle} maw={width - 20}>
|
|
101
|
+
{element.title}
|
|
102
|
+
</Text>
|
|
51
103
|
</m.div>
|
|
52
104
|
{ports.left.map(({ id, type }, i) => (
|
|
53
105
|
<Handle
|
|
@@ -57,7 +109,7 @@ export function CompoundNode({
|
|
|
57
109
|
position={Position.Left}
|
|
58
110
|
style={{
|
|
59
111
|
visibility: 'hidden',
|
|
60
|
-
top: `${
|
|
112
|
+
top: `${20 * (i + 1)}px`
|
|
61
113
|
}} />
|
|
62
114
|
))}
|
|
63
115
|
{ports.right.map(({ id, type }, i) => (
|
|
@@ -68,9 +120,11 @@ export function CompoundNode({
|
|
|
68
120
|
position={Position.Right}
|
|
69
121
|
style={{
|
|
70
122
|
visibility: 'hidden',
|
|
71
|
-
top: `${
|
|
123
|
+
top: `${20 * (i + 1)}px`
|
|
72
124
|
}} />
|
|
73
125
|
))}
|
|
74
126
|
</>
|
|
75
127
|
)
|
|
76
|
-
}
|
|
128
|
+
}, (prev, next) => {
|
|
129
|
+
return deepEqual(prev.data, next.data)
|
|
130
|
+
})
|
|
@@ -2,7 +2,9 @@ import { ActionIcon, Box, Group, Text as MantineText } from '@mantine/core'
|
|
|
2
2
|
import { IconFileSymlink, IconTransform, IconZoomScan } from '@tabler/icons-react'
|
|
3
3
|
import { Handle, type NodeProps, Position } from '@xyflow/react'
|
|
4
4
|
import clsx from 'clsx'
|
|
5
|
+
import { deepEqual } from 'fast-equals'
|
|
5
6
|
import { m } from 'framer-motion'
|
|
7
|
+
import { memo } from 'react'
|
|
6
8
|
import { type DiagramState, useDiagramState } from '../../../hooks'
|
|
7
9
|
import { ElementShapeSvg } from '../../../xyflow/nodes/element/ElementShapeSvg'
|
|
8
10
|
import { stopPropagation } from '../../../xyflow/utils'
|
|
@@ -32,23 +34,41 @@ function selector(s: DiagramState) {
|
|
|
32
34
|
}
|
|
33
35
|
}
|
|
34
36
|
|
|
35
|
-
export
|
|
37
|
+
export const ElementNode = memo<ElementNodeProps>(({
|
|
38
|
+
id,
|
|
36
39
|
data: {
|
|
37
40
|
element,
|
|
38
41
|
ports,
|
|
39
42
|
navigateTo,
|
|
43
|
+
layoutId = id,
|
|
44
|
+
leaving = false,
|
|
45
|
+
initialAnimation = true,
|
|
40
46
|
...data
|
|
41
47
|
},
|
|
42
48
|
selectable = true,
|
|
43
49
|
width: w = 100,
|
|
44
50
|
height: h = 100
|
|
45
|
-
}
|
|
51
|
+
}) => {
|
|
46
52
|
const overlay = useOverlayDialog()
|
|
47
53
|
const {
|
|
48
54
|
currentViewId,
|
|
49
55
|
onNavigateTo,
|
|
50
56
|
onOpenSource
|
|
51
57
|
} = useDiagramState(selector)
|
|
58
|
+
|
|
59
|
+
const scale = (diff: number) => ({
|
|
60
|
+
scaleX: (w + diff) / w,
|
|
61
|
+
scaleY: (h + diff) / h
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
let opacity = 1
|
|
65
|
+
if (data.dimmed) {
|
|
66
|
+
opacity = data.dimmed === 'immediate' ? 0.05 : 0.15
|
|
67
|
+
}
|
|
68
|
+
if (leaving) {
|
|
69
|
+
opacity = 0
|
|
70
|
+
}
|
|
71
|
+
|
|
52
72
|
return (
|
|
53
73
|
<>
|
|
54
74
|
<m.div
|
|
@@ -56,22 +76,39 @@ export function ElementNode({
|
|
|
56
76
|
css.elementNode,
|
|
57
77
|
'likec4-element-node'
|
|
58
78
|
])}
|
|
79
|
+
layoutId={layoutId}
|
|
59
80
|
data-likec4-color={element.color}
|
|
81
|
+
initial={(layoutId === id && initialAnimation)
|
|
82
|
+
? {
|
|
83
|
+
...scale(-20),
|
|
84
|
+
opacity: 0,
|
|
85
|
+
width: w,
|
|
86
|
+
height: h
|
|
87
|
+
}
|
|
88
|
+
: false}
|
|
60
89
|
animate={{
|
|
61
|
-
|
|
90
|
+
...scale(0),
|
|
91
|
+
opacity,
|
|
92
|
+
width: w,
|
|
93
|
+
height: h,
|
|
62
94
|
transition: {
|
|
63
|
-
|
|
95
|
+
opacity: {
|
|
96
|
+
delay: !leaving && data.dimmed === true ? .4 : 0,
|
|
97
|
+
...((leaving || data.dimmed === 'immediate') && {
|
|
98
|
+
duration: 0.09
|
|
99
|
+
})
|
|
100
|
+
}
|
|
64
101
|
}
|
|
65
102
|
}}
|
|
66
103
|
{...(selectable && {
|
|
67
104
|
whileHover: {
|
|
68
|
-
scale
|
|
69
|
-
transition: {
|
|
70
|
-
|
|
71
|
-
}
|
|
105
|
+
...scale(16)
|
|
106
|
+
// transition: {
|
|
107
|
+
// delay: 0.1
|
|
108
|
+
// }
|
|
72
109
|
},
|
|
73
110
|
whileTap: {
|
|
74
|
-
scale
|
|
111
|
+
...scale(-8)
|
|
75
112
|
}
|
|
76
113
|
})}
|
|
77
114
|
>
|
|
@@ -86,7 +123,9 @@ export function ElementNode({
|
|
|
86
123
|
<ElementShapeSvg shape={element.shape} w={w} h={h} />
|
|
87
124
|
</svg>
|
|
88
125
|
<Box className={css.elementNodeContent}>
|
|
89
|
-
<Text className={css.elementNodeTitle} lineClamp={2}>
|
|
126
|
+
<Text className={css.elementNodeTitle} lineClamp={2}>
|
|
127
|
+
{element.title}
|
|
128
|
+
</Text>
|
|
90
129
|
{element.description && (
|
|
91
130
|
<Text className={css.elementNodeDescription} lineClamp={4}>{element.description}</Text>
|
|
92
131
|
)}
|
|
@@ -96,13 +135,12 @@ export function ElementNode({
|
|
|
96
135
|
<Action
|
|
97
136
|
onClick={(event) => {
|
|
98
137
|
event.stopPropagation()
|
|
99
|
-
|
|
100
|
-
overlay.close()
|
|
138
|
+
overlay.close(() => onNavigateTo(navigateTo))
|
|
101
139
|
}}>
|
|
102
140
|
<IconZoomScan stroke={1.8} style={{ width: '75%' }} />
|
|
103
141
|
</Action>
|
|
104
142
|
)}
|
|
105
|
-
{data.column !== '
|
|
143
|
+
{data.column !== 'subjects' && (
|
|
106
144
|
<Action
|
|
107
145
|
onClick={(event) => {
|
|
108
146
|
event.stopPropagation()
|
|
@@ -148,4 +186,6 @@ export function ElementNode({
|
|
|
148
186
|
))}
|
|
149
187
|
</>
|
|
150
188
|
)
|
|
151
|
-
}
|
|
189
|
+
}, (prev, next) => {
|
|
190
|
+
return deepEqual(prev.data, next.data)
|
|
191
|
+
})
|
|
@@ -16,7 +16,7 @@ export function EmptyNode({
|
|
|
16
16
|
}: EmptyNodeProps) {
|
|
17
17
|
return (
|
|
18
18
|
<Box className={css.emptyNode}>
|
|
19
|
-
<Text c={'dimmed'} fw={500}>No {column === 'incomers' ? 'incoming' : 'outgoing'}</Text>
|
|
19
|
+
<Text c={'dimmed'} fz={'lg'} fw={500}>No {column === 'incomers' ? 'incoming' : 'outgoing'}</Text>
|
|
20
20
|
</Box>
|
|
21
21
|
)
|
|
22
22
|
}
|
|
@@ -1,30 +1,52 @@
|
|
|
1
|
-
import { ActionIcon, Box, Group, Stack, Text, ThemeIcon } from '@mantine/core'
|
|
1
|
+
import { ActionIcon, Box, Group, Stack, Text, ThemeIcon, Tooltip as MantineTooltip } from '@mantine/core'
|
|
2
2
|
import { IconBoxMultipleFilled, IconZoomScan } from '@tabler/icons-react'
|
|
3
3
|
import { BaseEdge, EdgeLabelRenderer, type EdgeProps, getBezierPath } from '@xyflow/react'
|
|
4
4
|
import clsx from 'clsx'
|
|
5
|
+
import { useEffect, useRef } from 'react'
|
|
5
6
|
import { only } from 'remeda'
|
|
6
7
|
import { useDiagramState } from '../../../hooks/useDiagramState'
|
|
7
8
|
import { stopPropagation } from '../../../xyflow/utils'
|
|
9
|
+
import { useOverlayDialog } from '../../OverlayContext'
|
|
8
10
|
import type { XYFlowTypes } from '../_types'
|
|
9
11
|
import { ZIndexes } from '../use-layouted-relationships'
|
|
10
12
|
import * as css from './styles.css'
|
|
11
13
|
|
|
14
|
+
const Tooltip = MantineTooltip.withProps({
|
|
15
|
+
color: 'dark',
|
|
16
|
+
fz: 'sm',
|
|
17
|
+
openDelay: 400,
|
|
18
|
+
closeDelay: 150,
|
|
19
|
+
withinPortal: false,
|
|
20
|
+
label: '',
|
|
21
|
+
children: null,
|
|
22
|
+
offset: 4
|
|
23
|
+
})
|
|
24
|
+
|
|
12
25
|
export function RelationshipEdge({
|
|
13
26
|
data,
|
|
14
27
|
label,
|
|
15
28
|
...props
|
|
16
29
|
}: EdgeProps<XYFlowTypes.Edge>) {
|
|
17
|
-
const
|
|
30
|
+
const {
|
|
31
|
+
viewId,
|
|
32
|
+
onNavigateTo
|
|
33
|
+
} = useDiagramState(s => ({
|
|
34
|
+
viewId: s.view.id,
|
|
35
|
+
onNavigateTo: s.onNavigateTo
|
|
36
|
+
}))
|
|
37
|
+
const overlay = useOverlayDialog()
|
|
18
38
|
const [edgePath, labelX, labelY] = getBezierPath(props)
|
|
19
39
|
const navigateTo = onNavigateTo ? only(data.relations)?.navigateTo : undefined
|
|
20
40
|
const isMultiRelation = data.relations.length > 1
|
|
21
41
|
const technology = only(data.relations)?.technology
|
|
42
|
+
|
|
22
43
|
return (
|
|
23
44
|
<>
|
|
24
45
|
<g
|
|
25
|
-
className={css.
|
|
46
|
+
className={css.edgeContainer}
|
|
26
47
|
data-edge-dimmed={data.dimmed}
|
|
27
48
|
data-edge-hovered={data.hovered}
|
|
49
|
+
data-likec4-color={data.includedInCurrentView ? 'gray' : 'amber'}
|
|
28
50
|
>
|
|
29
51
|
<BaseEdge
|
|
30
52
|
path={edgePath}
|
|
@@ -37,7 +59,7 @@ export function RelationshipEdge({
|
|
|
37
59
|
style={{
|
|
38
60
|
position: 'absolute',
|
|
39
61
|
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
|
|
40
|
-
maxWidth: Math.abs(props.targetX - props.sourceX -
|
|
62
|
+
maxWidth: Math.abs(props.targetX - props.sourceX - 70),
|
|
41
63
|
zIndex: ZIndexes.max
|
|
42
64
|
}}
|
|
43
65
|
className={clsx([
|
|
@@ -46,25 +68,29 @@ export function RelationshipEdge({
|
|
|
46
68
|
])}
|
|
47
69
|
data-edge-dimmed={data.dimmed}
|
|
48
70
|
data-edge-hovered={data.hovered}
|
|
71
|
+
data-likec4-color={data.includedInCurrentView ? 'gray' : 'amber'}
|
|
72
|
+
// {...data.includedInCurrentView === false && { 'data-likec4-color': 'amber' }}
|
|
49
73
|
>
|
|
50
74
|
{label && (
|
|
51
|
-
<
|
|
52
|
-
{
|
|
53
|
-
|
|
54
|
-
<
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
75
|
+
<Tooltip label="Not included in current view" disabled={data.includedInCurrentView} color={'orange'}>
|
|
76
|
+
<Group gap={6} wrap="nowrap">
|
|
77
|
+
{isMultiRelation && (
|
|
78
|
+
<ThemeIcon size={'sm'} variant="transparent" color="orange">
|
|
79
|
+
<IconBoxMultipleFilled style={{ width: '80%' }} />
|
|
80
|
+
</ThemeIcon>
|
|
81
|
+
)}
|
|
82
|
+
<Text
|
|
83
|
+
fw={isMultiRelation ? '500' : '400'}
|
|
84
|
+
style={{
|
|
85
|
+
whiteSpace: isMultiRelation ? 'nowrap' : undefined
|
|
86
|
+
}}
|
|
87
|
+
component={'div'}
|
|
88
|
+
className={css.edgeLabelText}
|
|
89
|
+
lineClamp={3}>
|
|
90
|
+
{label}
|
|
91
|
+
</Text>
|
|
92
|
+
</Group>
|
|
93
|
+
</Tooltip>
|
|
68
94
|
)}
|
|
69
95
|
{technology && (
|
|
70
96
|
<Text component={'div'} className={css.edgeLabelTechnology}>
|
|
@@ -73,7 +99,7 @@ export function RelationshipEdge({
|
|
|
73
99
|
{' ]'}
|
|
74
100
|
</Text>
|
|
75
101
|
)}
|
|
76
|
-
{navigateTo && (
|
|
102
|
+
{navigateTo && viewId !== navigateTo && (
|
|
77
103
|
<Box ta={'center'} mt={4}>
|
|
78
104
|
<ActionIcon
|
|
79
105
|
variant="default"
|
|
@@ -82,7 +108,9 @@ export function RelationshipEdge({
|
|
|
82
108
|
onPointerDownCapture={stopPropagation}
|
|
83
109
|
onClick={event => {
|
|
84
110
|
event.stopPropagation()
|
|
85
|
-
|
|
111
|
+
overlay.close(() => {
|
|
112
|
+
onNavigateTo?.(navigateTo, event)
|
|
113
|
+
})
|
|
86
114
|
}}
|
|
87
115
|
role="button"
|
|
88
116
|
onDoubleClick={stopPropagation}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { rem } from '@mantine/core'
|
|
2
2
|
import { globalStyle, style } from '@vanilla-extract/css'
|
|
3
|
-
import { mantine, vars,
|
|
3
|
+
import { mantine, transitions, vars, whereDark, xyvars } from '../../../theme-vars'
|
|
4
|
+
import { mixColor } from '../../Overlays.css'
|
|
4
5
|
|
|
5
6
|
export const elementNode = style({
|
|
7
|
+
position: 'relative',
|
|
6
8
|
width: '100%',
|
|
7
9
|
height: '100%'
|
|
8
10
|
})
|
|
@@ -27,7 +29,7 @@ export const elementNodeTitle = style({
|
|
|
27
29
|
fontOpticalSizing: 'auto',
|
|
28
30
|
fontStyle: 'normal',
|
|
29
31
|
fontWeight: 500,
|
|
30
|
-
fontSize:
|
|
32
|
+
fontSize: 18,
|
|
31
33
|
lineHeight: 1.25,
|
|
32
34
|
textWrap: 'balance',
|
|
33
35
|
color: vars.element.hiContrast
|
|
@@ -40,30 +42,30 @@ export const elementNodeDescription = style({
|
|
|
40
42
|
fontFamily: vars.element.font,
|
|
41
43
|
fontOpticalSizing: 'auto',
|
|
42
44
|
fontStyle: 'normal',
|
|
43
|
-
fontSize: mantine.fontSizes.
|
|
45
|
+
fontSize: mantine.fontSizes.sm,
|
|
44
46
|
lineHeight: 1.25,
|
|
45
47
|
textWrap: 'balance',
|
|
46
48
|
color: vars.element.loContrast
|
|
47
49
|
})
|
|
48
50
|
|
|
49
51
|
export const compoundNodeBody = style({
|
|
52
|
+
position: 'relative',
|
|
50
53
|
width: '100%',
|
|
51
54
|
height: '100%',
|
|
52
|
-
boxShadow: '0 4px 10px 0.5px rgba(0,0,0,0.1) ,
|
|
53
|
-
background: `color-mix(in srgb , ${vars.element.fill}, transparent 10%)`,
|
|
54
|
-
|
|
55
|
+
boxShadow: '0 4px 10px 0.5px rgba(0,0,0,0.1) , 1px 1px 4px -1px rgba(0,0,0,0.3)',
|
|
56
|
+
// background: `color-mix(in srgb , ${vars.element.fill}, transparent 10%)`,
|
|
57
|
+
background: vars.element.fill,
|
|
58
|
+
borderRadius: 6
|
|
55
59
|
})
|
|
56
60
|
|
|
57
61
|
export const compoundNodeTitle = style({
|
|
58
62
|
fontFamily: vars.compound.font,
|
|
59
|
-
fontOpticalSizing: 'auto',
|
|
60
|
-
fontStyle: 'normal',
|
|
61
63
|
fontWeight: 600,
|
|
62
|
-
fontSize:
|
|
64
|
+
fontSize: 15,
|
|
63
65
|
lineHeight: 1,
|
|
64
66
|
textTransform: 'uppercase',
|
|
65
|
-
paddingTop:
|
|
66
|
-
paddingLeft:
|
|
67
|
+
paddingTop: 14,
|
|
68
|
+
paddingLeft: 12,
|
|
67
69
|
mixBlendMode: 'screen',
|
|
68
70
|
color: vars.compound.titleColor
|
|
69
71
|
})
|
|
@@ -86,19 +88,32 @@ export const cssShapeSvg = style({
|
|
|
86
88
|
|
|
87
89
|
const DimmedTransitionDelay = '400ms'
|
|
88
90
|
|
|
89
|
-
export const
|
|
91
|
+
export const edgeContainer = style({
|
|
90
92
|
opacity: 1,
|
|
91
|
-
transition:
|
|
92
|
-
|
|
93
|
+
transition: transitions.fast,
|
|
94
|
+
vars: {
|
|
95
|
+
[xyvars.edge.stroke]: vars.relation.lineColor,
|
|
96
|
+
[xyvars.edge.strokeSelected]: `color-mix(in srgb, ${vars.relation.lineColor}, ${mixColor} 35%)`,
|
|
97
|
+
[xyvars.edge.labelColor]: `color-mix(in srgb, ${vars.relation.labelColor}, rgba(255 255 255 / 0.85) 50%)`,
|
|
98
|
+
[xyvars.edge.labelBgColor]: `color-mix(in srgb, ${vars.relation.labelBgColor}, transparent 20%)`
|
|
99
|
+
},
|
|
93
100
|
selectors: {
|
|
94
101
|
[`&[data-edge-dimmed='true']`]: {
|
|
95
|
-
opacity: 0.
|
|
102
|
+
opacity: 0.2,
|
|
96
103
|
transitionDelay: DimmedTransitionDelay
|
|
104
|
+
},
|
|
105
|
+
[`&[data-edge-dimmed='immediate']`]: {
|
|
106
|
+
opacity: 0.2
|
|
107
|
+
},
|
|
108
|
+
[`${whereDark} &`]: {
|
|
109
|
+
vars: {
|
|
110
|
+
[xyvars.edge.labelBgColor]: `color-mix(in srgb, ${vars.relation.labelBgColor}, transparent 50%)`
|
|
111
|
+
}
|
|
97
112
|
}
|
|
98
113
|
}
|
|
99
114
|
})
|
|
100
115
|
|
|
101
|
-
export const edgeLabel = style({
|
|
116
|
+
export const edgeLabel = style([edgeContainer, {
|
|
102
117
|
padding: '2px 5px',
|
|
103
118
|
fontFamily: vars.likec4.font,
|
|
104
119
|
position: 'absolute',
|
|
@@ -111,21 +126,13 @@ export const edgeLabel = style({
|
|
|
111
126
|
// if you have an interactive element, set pointer-events: all
|
|
112
127
|
pointerEvents: 'all',
|
|
113
128
|
textWrap: 'pretty',
|
|
114
|
-
whiteSpace: 'preserve-breaks'
|
|
115
|
-
|
|
116
|
-
transition: 'all 0.2s ease-in-out',
|
|
117
|
-
selectors: {
|
|
118
|
-
[`&[data-edge-dimmed='true']`]: {
|
|
119
|
-
opacity: 0.1,
|
|
120
|
-
transitionDelay: DimmedTransitionDelay
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
})
|
|
129
|
+
whiteSpace: 'preserve-breaks'
|
|
130
|
+
}])
|
|
124
131
|
|
|
125
132
|
export const edgeLabelText = style({
|
|
126
133
|
textAlign: 'center',
|
|
127
134
|
whiteSpaceCollapse: 'preserve-breaks',
|
|
128
|
-
fontSize:
|
|
135
|
+
fontSize: mantine.fontSizes.md,
|
|
129
136
|
lineHeight: mantine.lineHeights.xs
|
|
130
137
|
})
|
|
131
138
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { invariant } from '@likec4/core'
|
|
2
|
-
import {
|
|
2
|
+
import { Box } from '@mantine/core'
|
|
3
|
+
import { useMergedRef, useReducedMotion } from '@mantine/hooks'
|
|
3
4
|
import { deepEqual, shallowEqual } from 'fast-equals'
|
|
4
5
|
import { useAnimate } from 'framer-motion'
|
|
5
6
|
import { createContext, type PropsWithChildren, useCallback, useEffect, useRef } from 'react'
|
|
@@ -25,6 +26,7 @@ export function DiagramContextProvider({
|
|
|
25
26
|
whereFilter,
|
|
26
27
|
...props
|
|
27
28
|
}: DiagramContextProviderProps) {
|
|
29
|
+
const isMotionReduced = useReducedMotion() ?? false
|
|
28
30
|
const [scope, animate] = useAnimate()
|
|
29
31
|
const containerRef = useRef<HTMLDivElement>(null)
|
|
30
32
|
const ref = useMergedRef(scope, containerRef)
|
|
@@ -70,19 +72,24 @@ export function DiagramContextProvider({
|
|
|
70
72
|
|
|
71
73
|
const api = store.current
|
|
72
74
|
useEffect(() => {
|
|
75
|
+
if (isMotionReduced) {
|
|
76
|
+
return
|
|
77
|
+
}
|
|
73
78
|
return api.subscribe(s => !!s.activeOverlay, (isActiveOverlay) => {
|
|
74
79
|
animate('.likec4-diagram .react-flow__renderer', {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
transform: isActiveOverlay ? `translateY(
|
|
80
|
+
opacity: isActiveOverlay ? 0.7 : 1,
|
|
81
|
+
filter: isActiveOverlay ? 'grayscale(1)' : 'grayscale(0)',
|
|
82
|
+
transform: isActiveOverlay ? `perspective(300px) translateZ(-10px) translateY(2px)` : `translateY(0)`
|
|
78
83
|
}, {
|
|
79
|
-
duration: isActiveOverlay ? 0.
|
|
84
|
+
duration: isActiveOverlay ? 0.4 : 0.2
|
|
80
85
|
})
|
|
86
|
+
}, {
|
|
87
|
+
fireImmediately: true
|
|
81
88
|
})
|
|
82
|
-
}, [api])
|
|
89
|
+
}, [api, isMotionReduced])
|
|
83
90
|
|
|
84
91
|
return (
|
|
85
|
-
<
|
|
92
|
+
<Box
|
|
86
93
|
ref={ref}
|
|
87
94
|
className={className}
|
|
88
95
|
{...(keepAspectRatio && {
|
|
@@ -94,7 +101,7 @@ export function DiagramContextProvider({
|
|
|
94
101
|
<DiagramContext.Provider value={api}>
|
|
95
102
|
{children}
|
|
96
103
|
</DiagramContext.Provider>
|
|
97
|
-
</
|
|
104
|
+
</Box>
|
|
98
105
|
)
|
|
99
106
|
}
|
|
100
107
|
DiagramContextProvider.displayName = 'DiagramContextProvider'
|