@likec4/diagram 1.15.1 → 1.17.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 (47) hide show
  1. package/package.json +7 -7
  2. package/src/LikeC4CustomColors.tsx +24 -21
  3. package/src/LikeC4Diagram.props.ts +9 -9
  4. package/src/LikeC4Diagram.tsx +25 -26
  5. package/src/StaticLikeC4Diagram.tsx +9 -3
  6. package/src/overlays/OverlayContext.tsx +1 -1
  7. package/src/overlays/Overlays.css.ts +28 -23
  8. package/src/overlays/Overlays.tsx +116 -64
  9. package/src/overlays/edge-details/xyflow/ElementNode.tsx +15 -12
  10. package/src/overlays/edge-details/xyflow/RelationshipEdge.tsx +7 -3
  11. package/src/overlays/edge-details/xyflow/styles.css.ts +22 -16
  12. package/src/overlays/element-details/ElementDetailsCard.css.ts +322 -0
  13. package/src/overlays/element-details/ElementDetailsCard.tsx +639 -0
  14. package/src/overlays/element-details/TabPanelRelationships.css.ts +106 -0
  15. package/src/overlays/element-details/TabPanelRelationships.tsx +201 -0
  16. package/src/overlays/element-details/TabPanelStructure.css.ts +36 -0
  17. package/src/overlays/element-details/TabPanelStructure.tsx +120 -0
  18. package/src/overlays/relationships-of/RelationshipsOverlay.tsx +185 -0
  19. package/src/overlays/relationships-of/RelationshipsXYFlow.tsx +179 -213
  20. package/src/overlays/relationships-of/_types.ts +15 -4
  21. package/src/overlays/relationships-of/use-layouted-relationships.ts +301 -207
  22. package/src/overlays/relationships-of/xyflow/CompoundNode.tsx +68 -14
  23. package/src/overlays/relationships-of/xyflow/ElementNode.tsx +54 -14
  24. package/src/overlays/relationships-of/xyflow/EmptyNode.tsx +1 -1
  25. package/src/overlays/relationships-of/xyflow/RelationshipEdge.tsx +51 -23
  26. package/src/overlays/relationships-of/xyflow/styles.css.ts +34 -27
  27. package/src/state/DiagramContext.tsx +15 -8
  28. package/src/state/diagramStore.ts +14 -6
  29. package/src/theme-vars.ts +9 -0
  30. package/src/ui/FramerMotionConfig.tsx +18 -0
  31. package/src/ui/notation/NotationPanel.tsx +3 -3
  32. package/src/xyflow/XYFlow.tsx +18 -35
  33. package/src/xyflow/XYFlowInner.tsx +2 -2
  34. package/src/xyflow/edges/EdgeLabel.tsx +3 -3
  35. package/src/xyflow/edges/RelationshipsDropdownMenu.tsx +25 -18
  36. package/src/xyflow/nodes/compound/CompoundNode.css.ts +96 -30
  37. package/src/xyflow/nodes/compound/CompoundNode.tsx +149 -165
  38. package/src/xyflow/nodes/element/ElementDetails.css.ts +101 -0
  39. package/src/xyflow/nodes/element/ElementDetails.tsx +393 -0
  40. package/src/xyflow/nodes/element/ElementNode.tsx +110 -116
  41. package/src/xyflow/nodes/element/element.css.ts +38 -18
  42. package/src/xyflow/nodes/shared/ElementIcon.tsx +43 -0
  43. package/src/xyflow/nodes/shared/Toolbar.tsx +6 -6
  44. package/src/xyflow/{XYFlowEvents.tsx → useXYFlowEvents.ts} +26 -22
  45. package/src/xyflow/utils.ts +12 -3
  46. package/src/LikeC4CustomColors.props.ts +0 -5
  47. /package/src/{mantine → ui}/EnsureMantine.tsx +0 -0
@@ -2,8 +2,9 @@ import { ActionIcon, Box, Stack, Text } from '@mantine/core'
2
2
  import { IconZoomScan } from '@tabler/icons-react'
3
3
  import { BaseEdge, EdgeLabelRenderer, type EdgeProps, getBezierPath } from '@xyflow/react'
4
4
  import clsx from 'clsx'
5
- import { useDiagramState } from '../../../hooks/useDiagramState'
5
+ import { useDiagramState, useDiagramStoreApi } from '../../../hooks/useDiagramState'
6
6
  import { stopPropagation } from '../../../xyflow/utils'
7
+ import { useOverlayDialog } from '../../OverlayContext'
7
8
  import type { XYFlowTypes } from '../_types'
8
9
  import { ZIndexes } from '../use-layouted-edge-details'
9
10
  import * as css from './styles.css'
@@ -13,13 +14,14 @@ export function RelationshipEdge({
13
14
  label,
14
15
  ...props
15
16
  }: EdgeProps<XYFlowTypes.Edge>) {
17
+ const overlay = useOverlayDialog()
16
18
  const onNavigateTo = useDiagramState(s => s.onNavigateTo)
17
19
  const [edgePath, labelX, labelY] = getBezierPath(props)
18
20
  const navigateTo = data.relation.navigateTo
19
21
  return (
20
22
  <>
21
23
  <g
22
- className={css.edgePath}
24
+ className={css.edgeContainer}
23
25
  data-edge-dimmed={data.dimmed}
24
26
  data-edge-hovered={data.hovered}
25
27
  >
@@ -65,7 +67,9 @@ export function RelationshipEdge({
65
67
  onPointerDownCapture={stopPropagation}
66
68
  onClick={event => {
67
69
  event.stopPropagation()
68
- onNavigateTo(navigateTo, event)
70
+ overlay.close(() => {
71
+ onNavigateTo(navigateTo)
72
+ })
69
73
  }}
70
74
  role="button"
71
75
  onDoubleClick={stopPropagation}
@@ -1,5 +1,6 @@
1
1
  import { globalStyle, style } from '@vanilla-extract/css'
2
- import { mantine, vars, whereLight, xyvars } from '../../../theme-vars'
2
+ import { mantine, transitions, vars, whereDark, xyvars } from '../../../theme-vars'
3
+ import { mixColor } from '../../Overlays.css'
3
4
 
4
5
  export const elementNode = style({
5
6
  width: '100%',
@@ -85,19 +86,32 @@ export const cssShapeSvg = style({
85
86
 
86
87
  const DimmedTransitionDelay = '400ms'
87
88
 
88
- export const edgePath = style({
89
+ export const edgeContainer = style({
89
90
  opacity: 1,
90
- transition: 'all 0.2s ease-in-out',
91
-
91
+ transition: transitions.fast,
92
+ vars: {
93
+ [xyvars.edge.stroke]: vars.relation.lineColor,
94
+ [xyvars.edge.strokeSelected]: `color-mix(in srgb, ${vars.relation.lineColor}, ${mixColor} 35%)`,
95
+ [xyvars.edge.labelColor]: `color-mix(in srgb, ${vars.relation.labelColor}, rgba(255 255 255 / 0.85) 50%)`,
96
+ [xyvars.edge.labelBgColor]: `color-mix(in srgb, ${vars.relation.labelBgColor}, transparent 20%)`
97
+ },
92
98
  selectors: {
93
99
  [`&[data-edge-dimmed='true']`]: {
94
- opacity: 0.1,
100
+ opacity: 0.2,
95
101
  transitionDelay: DimmedTransitionDelay
102
+ },
103
+ [`&[data-edge-dimmed='immediate']`]: {
104
+ opacity: 0.2
105
+ },
106
+ [`${whereDark} &`]: {
107
+ vars: {
108
+ [xyvars.edge.labelBgColor]: `color-mix(in srgb, ${vars.relation.labelBgColor}, transparent 50%)`
109
+ }
96
110
  }
97
111
  }
98
112
  })
99
113
 
100
- export const edgeLabel = style({
114
+ export const edgeLabel = style([edgeContainer, {
101
115
  padding: '2px 5px',
102
116
  fontFamily: vars.likec4.font,
103
117
  position: 'absolute',
@@ -110,16 +124,8 @@ export const edgeLabel = style({
110
124
  // if you have an interactive element, set pointer-events: all
111
125
  pointerEvents: 'all',
112
126
  textWrap: 'pretty',
113
- whiteSpace: 'preserve-breaks',
114
- opacity: 1,
115
- transition: 'all 0.2s ease-in-out',
116
- selectors: {
117
- [`&[data-edge-dimmed='true']`]: {
118
- opacity: 0.1,
119
- transitionDelay: DimmedTransitionDelay
120
- }
121
- }
122
- })
127
+ whiteSpace: 'preserve-breaks'
128
+ }])
123
129
 
124
130
  export const edgeLabelText = style({
125
131
  textAlign: 'center',
@@ -0,0 +1,322 @@
1
+ import { rem } from '@mantine/core'
2
+ import { createVar, fallbackVar, globalStyle, style } from '@vanilla-extract/css'
3
+ import { easings, mantine, transitions, vars, whereDark, whereLight } from '../../theme-vars'
4
+
5
+ export const cardBg = createVar('card-bg')
6
+ const cardBgImage = createVar('card-bg-top')
7
+
8
+ export const dialog = style({
9
+ boxSizing: 'border-box',
10
+ margin: 0,
11
+ padding: 0,
12
+ position: 'fixed',
13
+ inset: 0,
14
+ width: '100%',
15
+ height: '100%',
16
+ maxWidth: '100%',
17
+ maxHeight: '100%',
18
+ background: 'transparent',
19
+ border: '0 solid transparent',
20
+ vars: {
21
+ '--backdrop-opacity': '0%',
22
+ '--backdrop-blur': '0px'
23
+ },
24
+ selectors: {
25
+ [`&::backdrop`]: {
26
+ WebkitBackdropFilter: 'blur(var(--backdrop-blur))',
27
+ backdropFilter: 'blur(var(--backdrop-blur))',
28
+ backgroundColor: `rgb(36 36 36 / var(--backdrop-opacity))`
29
+ }
30
+ }
31
+ })
32
+ export const card = style({
33
+ position: 'absolute',
34
+ pointerEvents: 'all',
35
+ gap: mantine.spacing.lg,
36
+ justifyContent: 'stretch',
37
+ vars: {
38
+ [cardBg]: mantine.colors.body,
39
+ [cardBgImage]: `linear-gradient(180deg, ${vars.element.fill}, ${vars.element.fill} 4px, transparent 4px)`
40
+ },
41
+ backgroundColor: cardBg,
42
+ backgroundImage: cardBgImage,
43
+ borderTopColor: vars.element.fill,
44
+ selectors: {
45
+ [`${whereDark} &`]: {
46
+ vars: {
47
+ [cardBg]: mantine.colors.dark[6],
48
+ [cardBgImage]: `
49
+ linear-gradient(180deg, color-mix(in srgb, ${vars.element.fill} 15%, transparent), transparent 80px),
50
+ linear-gradient(180deg, ${vars.element.fill}, ${vars.element.fill} 4px, transparent 4px)
51
+ `
52
+ }
53
+ }
54
+ }
55
+ })
56
+
57
+ export const cardHeader = style({
58
+ flex: 0,
59
+ cursor: 'move'
60
+ })
61
+
62
+ globalStyle(`:where(${card}) .react-flow__attribution`, {
63
+ display: 'none'
64
+ })
65
+
66
+ export const title = style({
67
+ display: 'block',
68
+ fontFamily: vars.element.font,
69
+ fontOpticalSizing: 'auto',
70
+ fontStyle: 'normal',
71
+ fontWeight: 600,
72
+ fontSize: 24,
73
+ // lineHeight: 1.15,
74
+ lineHeight: mantine.lineHeights.xs
75
+ // color: vars.element.hiContrast
76
+ })
77
+
78
+ const iconSize = '40px'
79
+ export const elementIcon = style({
80
+ flex: `0 0 ${iconSize}`,
81
+ height: iconSize,
82
+ width: iconSize,
83
+ display: 'flex',
84
+ alignItems: 'center',
85
+ justifyContent: 'center',
86
+ alignSelf: 'flex-start',
87
+ cursor: 'move',
88
+ selectors: {
89
+ [`${whereDark} &`]: {
90
+ mixBlendMode: 'hard-light'
91
+ }
92
+ }
93
+ })
94
+ globalStyle(`${elementIcon} svg, ${elementIcon} img`, {
95
+ width: '100%',
96
+ height: 'auto',
97
+ maxHeight: '100%',
98
+ pointerEvents: 'none',
99
+ filter: `
100
+ drop-shadow(0 0 3px rgb(0 0 0 / 10%))
101
+ drop-shadow(0 1px 8px rgb(0 0 0 / 5%))
102
+ drop-shadow(1px 1px 16px rgb(0 0 0 / 2%))
103
+ `
104
+ })
105
+ globalStyle(`${elementIcon} img`, {
106
+ objectFit: 'contain'
107
+ })
108
+
109
+ const viewTitleColor = createVar('view-title-color')
110
+ const iconColor = createVar('icon-color')
111
+ export const viewButton = style({
112
+ // display: 'flex',
113
+ width: '100%',
114
+ background: mantine.colors.body,
115
+ borderRadius: mantine.radius.sm,
116
+ padding: `10px 8px`,
117
+ // gap: 6,
118
+ // alignItems: 'flex-start',
119
+ transition: transitions.fast,
120
+ border: `1px solid ${mantine.colors.defaultBorder}`,
121
+ vars: {
122
+ [viewTitleColor]: mantine.colors.dark[1]
123
+ },
124
+ ':hover': {
125
+ background: mantine.colors.defaultHover,
126
+ vars: {
127
+ [iconColor]: mantine.colors.dark[1],
128
+ [viewTitleColor]: mantine.colors.defaultColor
129
+ }
130
+ },
131
+ selectors: {
132
+ [`${whereDark} &`]: {
133
+ background: mantine.colors.dark[6]
134
+ },
135
+ [`${whereLight} &`]: {
136
+ vars: {
137
+ [iconColor]: mantine.colors.gray[6],
138
+ [viewTitleColor]: mantine.colors.gray[7]
139
+ }
140
+ },
141
+ [`${whereLight} &:hover`]: {
142
+ vars: {
143
+ [iconColor]: mantine.colors.gray[7]
144
+ }
145
+ }
146
+ }
147
+ })
148
+
149
+ globalStyle(`${viewButton} .mantine-ThemeIcon-root`, {
150
+ transition: transitions.fast,
151
+ color: fallbackVar(iconColor, mantine.colors.dark[2]),
152
+ vars: {
153
+ '--ti-size': '22px'
154
+ }
155
+ })
156
+
157
+ globalStyle(`${viewButton} .mantine-ThemeIcon-root:hover`, {
158
+ color: mantine.colors.defaultColor
159
+ })
160
+
161
+ globalStyle(`${viewButton} > *`, {
162
+ transition: `all 130ms ${easings.inOut}`
163
+ })
164
+ globalStyle(`${viewButton}:hover > *`, {
165
+ transitionTimingFunction: easings.out,
166
+ transform: 'translateX(1.6px)'
167
+ })
168
+
169
+ export const viewButtonTitle = style({
170
+ transition: transitions.fast,
171
+ color: fallbackVar(viewTitleColor, mantine.colors.gray[7]),
172
+ fontSize: 15,
173
+ fontWeight: 500,
174
+ lineHeight: 1.4
175
+ })
176
+ // globalStyle(`${viewButton} .tabler-icon`, {
177
+ // width: '85%',
178
+ // })
179
+ // globalStyle(`${viewButton} .tabler-icon`, {
180
+ // width: '85%',
181
+ // opacity: 0.75
182
+ // })
183
+
184
+ // globalStyle(`${whereLight} ${viewButton} .mantine-Button-label`, {
185
+ // color: mantine.colors.dark[3]
186
+ // })
187
+
188
+ export const tabsRoot = style({
189
+ flex: 1,
190
+ display: 'flex',
191
+ flexDirection: 'column',
192
+ justifyContent: 'stretch',
193
+ overflow: 'hidden',
194
+ gap: mantine.spacing.sm
195
+ })
196
+
197
+ export const tabsList = style({
198
+ // flex: '0',
199
+ background: mantine.colors.gray[1],
200
+ borderRadius: mantine.radius.sm,
201
+ flexWrap: 'nowrap',
202
+ gap: 6,
203
+ padding: 4,
204
+ selectors: {
205
+ [`${whereDark} &`]: {
206
+ background: mantine.colors.dark[7]
207
+ }
208
+ }
209
+ })
210
+
211
+ export const tabsTab = style({
212
+ fontSize: mantine.fontSizes.xs,
213
+ fontWeight: 500,
214
+ flexGrow: 1,
215
+ padding: '6px 8px',
216
+ transition: transitions.fast,
217
+ borderRadius: mantine.radius.sm,
218
+ color: mantine.colors.gray[7],
219
+ ':hover': {
220
+ transitionTimingFunction: easings.out,
221
+ color: mantine.colors.defaultColor,
222
+ background: mantine.colors.gray[3]
223
+ },
224
+ selectors: {
225
+ ['&[data-active]']: {
226
+ transition: 'none',
227
+ background: mantine.colors.white,
228
+ boxShadow: mantine.shadows.xs,
229
+ color: mantine.colors.defaultColor
230
+ },
231
+ [`${whereDark} &`]: {
232
+ color: mantine.colors.dark[1]
233
+ },
234
+ [`${whereDark} &:hover`]: {
235
+ color: mantine.colors.white,
236
+ background: mantine.colors.dark[6]
237
+ },
238
+ [`${whereDark} &:is([data-active])`]: {
239
+ color: mantine.colors.white,
240
+ background: mantine.colors.dark[5]
241
+ }
242
+ }
243
+ })
244
+
245
+ export const tabsPanel = style({
246
+ flex: 1,
247
+ overflow: 'hidden',
248
+ position: 'relative'
249
+ })
250
+
251
+ globalStyle(`${tabsPanel}:not(:has(.mantine-ScrollArea-root))`, {
252
+ paddingLeft: 4,
253
+ paddingRight: 4
254
+ })
255
+ globalStyle(`${tabsPanel} .mantine-ScrollArea-root`, {
256
+ width: '100%',
257
+ height: '100%'
258
+ })
259
+ globalStyle(`${tabsPanel} .mantine-ScrollArea-viewport > div`, {
260
+ paddingLeft: 4,
261
+ paddingRight: 4
262
+ })
263
+
264
+ export const propertiesGrid = style({
265
+ flex: 1,
266
+ display: 'grid',
267
+ gridTemplateColumns: 'min-content 1fr',
268
+ gridAutoRows: 'min-content max-content',
269
+ gap: `${rem(20)} ${rem(16)}`,
270
+ alignItems: 'baseline',
271
+ justifyItems: 'stretch'
272
+ })
273
+
274
+ export const propertyLabel = style({
275
+ justifySelf: 'end'
276
+ })
277
+
278
+ export const elementLink = style({
279
+ transition: transitions.fast,
280
+ border: `1px dashed ${mantine.colors.defaultBorder}`,
281
+ borderRadius: mantine.radius.sm,
282
+ minHeight: 30,
283
+ padding: '3px 16px 3px 3px',
284
+ cursor: 'pointer',
285
+ color: mantine.colors.gray[7],
286
+ ':hover': {
287
+ transitionTimingFunction: easings.out,
288
+ borderStyle: 'solid',
289
+ color: mantine.colors.defaultColor,
290
+ background: mantine.colors.defaultHover
291
+ },
292
+ selectors: {
293
+ [`${whereDark} &`]: {
294
+ color: mantine.colors.dark[1]
295
+ }
296
+ }
297
+ })
298
+ globalStyle(`${elementLink} > *`, {
299
+ transition: transitions.fast
300
+ })
301
+ globalStyle(`${elementLink}:hover > *`, {
302
+ transitionTimingFunction: easings.out,
303
+ transform: 'translateX(1px)'
304
+ })
305
+
306
+ export const resizeHandle = style({
307
+ position: 'absolute',
308
+ width: 14,
309
+ height: 14,
310
+ border: `3.5px solid ${mantine.colors.dark[3]}`,
311
+ borderTop: 'none',
312
+ borderLeft: 'none',
313
+ borderRadius: 2,
314
+ bottom: 2,
315
+ right: 2,
316
+ transition: transitions.fast,
317
+ cursor: 'se-resize',
318
+ ':hover': {
319
+ borderWidth: 4,
320
+ borderColor: mantine.colors.dark[1]
321
+ }
322
+ })