@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.
Files changed (52) hide show
  1. package/package.json +22 -18
  2. package/src/LikeC4CustomColors.tsx +24 -21
  3. package/src/LikeC4Diagram.props.ts +15 -9
  4. package/src/LikeC4Diagram.tsx +33 -27
  5. package/src/LikeC4Search.tsx +146 -0
  6. package/src/StaticLikeC4Diagram.tsx +10 -5
  7. package/src/overlays/OverlayContext.tsx +1 -1
  8. package/src/overlays/Overlays.css.ts +28 -23
  9. package/src/overlays/Overlays.tsx +116 -64
  10. package/src/overlays/edge-details/xyflow/ElementNode.tsx +15 -12
  11. package/src/overlays/edge-details/xyflow/RelationshipEdge.tsx +7 -3
  12. package/src/overlays/edge-details/xyflow/styles.css.ts +22 -16
  13. package/src/overlays/element-details/ElementDetailsCard.css.ts +322 -0
  14. package/src/overlays/element-details/ElementDetailsCard.tsx +639 -0
  15. package/src/overlays/element-details/TabPanelRelationships.css.ts +106 -0
  16. package/src/overlays/element-details/TabPanelRelationships.tsx +201 -0
  17. package/src/overlays/element-details/TabPanelStructure.css.ts +36 -0
  18. package/src/overlays/element-details/TabPanelStructure.tsx +120 -0
  19. package/src/overlays/relationships-of/RelationshipsOverlay.tsx +185 -0
  20. package/src/overlays/relationships-of/RelationshipsXYFlow.tsx +179 -213
  21. package/src/overlays/relationships-of/_types.ts +15 -4
  22. package/src/overlays/relationships-of/use-layouted-relationships.ts +301 -207
  23. package/src/overlays/relationships-of/xyflow/CompoundNode.tsx +68 -14
  24. package/src/overlays/relationships-of/xyflow/ElementNode.tsx +54 -14
  25. package/src/overlays/relationships-of/xyflow/EmptyNode.tsx +1 -1
  26. package/src/overlays/relationships-of/xyflow/RelationshipEdge.tsx +51 -23
  27. package/src/overlays/relationships-of/xyflow/styles.css.ts +34 -27
  28. package/src/state/DiagramContext.tsx +15 -8
  29. package/src/state/aligners.ts +473 -0
  30. package/src/state/diagramStore.layout.ts +67 -0
  31. package/src/state/diagramStore.ts +18 -73
  32. package/src/theme-vars.ts +9 -0
  33. package/src/ui/FramerMotionConfig.tsx +18 -0
  34. package/src/ui/notation/NotationPanel.tsx +3 -3
  35. package/src/ui/top-left/ManualLayoutToolsButton.tsx +15 -0
  36. package/src/ui/top-left/TopLeftPanel.tsx +14 -1
  37. package/src/xyflow/XYFlow.tsx +18 -35
  38. package/src/xyflow/XYFlowInner.tsx +2 -2
  39. package/src/xyflow/edges/EdgeLabel.tsx +3 -3
  40. package/src/xyflow/edges/RelationshipsDropdownMenu.tsx +25 -18
  41. package/src/xyflow/nodes/compound/CompoundNode.css.ts +96 -30
  42. package/src/xyflow/nodes/compound/CompoundNode.tsx +149 -165
  43. package/src/xyflow/nodes/element/ElementDetails.css.ts +101 -0
  44. package/src/xyflow/nodes/element/ElementDetails.tsx +393 -0
  45. package/src/xyflow/nodes/element/ElementNode.tsx +165 -133
  46. package/src/xyflow/nodes/element/element.css.ts +46 -17
  47. package/src/xyflow/nodes/shared/ElementIcon.tsx +43 -0
  48. package/src/xyflow/nodes/shared/Toolbar.tsx +6 -6
  49. package/src/xyflow/{XYFlowEvents.tsx → useXYFlowEvents.ts} +26 -22
  50. package/src/xyflow/utils.ts +12 -3
  51. package/src/LikeC4CustomColors.props.ts +0 -5
  52. /package/src/{mantine → ui}/EnsureMantine.tsx +0 -0
@@ -1,96 +1,118 @@
1
- import { ActionIcon, Box, FocusTrap } from '@mantine/core'
1
+ import { ActionIcon, Box, FocusTrap, RemoveScroll } from '@mantine/core'
2
2
  import { useHotkeys } from '@mantine/hooks'
3
3
  import { IconX } from '@tabler/icons-react'
4
4
  import { ReactFlowProvider as XYFlowProvider } from '@xyflow/react'
5
5
  import { AnimatePresence, m } from 'framer-motion'
6
- import { memo, useMemo } from 'react'
6
+ import { memo, type PropsWithChildren, useEffect, useMemo, useRef } from 'react'
7
+ import { isNullish } from 'remeda'
7
8
  import { type DiagramState, useDiagramState, useDiagramStoreApi } from '../hooks/useDiagramState'
8
9
  import { EdgeDetailsXYFlow } from './edge-details/EdgeDetailsXYFlow'
9
- import { OverlayContext } from './OverlayContext'
10
+ import { ElementDetailsCard } from './element-details/ElementDetailsCard'
11
+ import { OverlayContext, useOverlayDialog } from './OverlayContext'
10
12
  import * as css from './Overlays.css'
11
- import { RelationshipsXYFlow } from './relationships-of/RelationshipsXYFlow'
13
+ import { RelationshipsOverlay } from './relationships-of/RelationshipsOverlay'
14
+
12
15
  export const Overlays = memo(() => {
13
16
  const diagramStore = useDiagramStoreApi()
14
- const activeOverlay = useDiagramState(s => s.activeOverlay)
17
+ const {
18
+ activeOverlay,
19
+ viewId
20
+ } = useDiagramState(s => ({
21
+ activeOverlay: s.activeOverlay,
22
+ viewId: s.view.id
23
+ }))
24
+
25
+ const onCloseCbRef = useRef<(() => void)>()
15
26
 
16
27
  const ctxValue = useMemo(() => ({
17
28
  openOverlay: ((overlay) => {
18
29
  diagramStore.getState().openOverlay(overlay)
19
30
  }) as DiagramState['openOverlay'],
20
- close: () => {
31
+ close: (cb?: () => void) => {
32
+ onCloseCbRef.current = cb
21
33
  diagramStore.getState().closeOverlay()
22
34
  }
23
35
  }), [diagramStore])
24
36
 
37
+ const onExitComplete = () => {
38
+ onCloseCbRef.current?.()
39
+ onCloseCbRef.current = undefined
40
+ }
41
+
25
42
  const isActive = !!activeOverlay
26
43
  useHotkeys(
27
44
  isActive
28
45
  ? [
29
46
  ['Escape', (e) => {
30
- e.stopImmediatePropagation()
31
- diagramStore.getState().closeOverlay()
47
+ e.stopPropagation()
48
+ ctxValue.close()
32
49
  }, { preventDefault: true }]
33
50
  ]
34
51
  : []
35
52
  )
53
+
36
54
  return (
37
55
  <OverlayContext.Provider value={ctxValue}>
38
- <AnimatePresence>
39
- {activeOverlay && (
40
- <Box
41
- component={m.div}
42
- className={css.container}
43
- data-likec4-color="gray"
44
- initial={{
45
- '--backdrop-blur': '0px',
46
- '--backdrop-opacity': '60%',
47
- opacity: 0,
48
- translateY: -15
49
- }}
50
- animate={{
51
- '--backdrop-blur': '10px',
52
- '--backdrop-opacity': '25%',
53
- opacity: 1,
54
- translateY: 0
55
- }}
56
- exit={{
57
- '--backdrop-blur': '1px',
58
- '--backdrop-opacity': '90%',
59
- translateY: -5,
60
- opacity: 0
61
- }}
62
- >
63
- <FocusTrap>
64
- {activeOverlay.relationshipsOf && (
65
- <XYFlowProvider
66
- defaultNodes={[]}
67
- defaultEdges={[]}>
68
- <RelationshipsXYFlow subjectId={activeOverlay.relationshipsOf} />
69
- </XYFlowProvider>
70
- )}
71
- {activeOverlay.edgeDetails && (
72
- <XYFlowProvider
73
- defaultNodes={[]}
74
- defaultEdges={[]}>
75
- <EdgeDetailsXYFlow edgeId={activeOverlay.edgeDetails} />
76
- </XYFlowProvider>
77
- )}
78
- <Box pos={'absolute'} top={'1rem'} right={'1rem'}>
79
- <ActionIcon
80
- variant="default"
81
- // color="gray"
82
- size={'lg'}
83
- data-autofocus
84
- autoFocus
85
- onClick={(e) => {
86
- e.stopPropagation()
87
- ctxValue.close()
88
- }}>
89
- <IconX />
90
- </ActionIcon>
91
- </Box>
92
- </FocusTrap>
93
- </Box>
56
+ <AnimatePresence initial={false} key={viewId} onExitComplete={onExitComplete}>
57
+ {activeOverlay?.elementDetails && (
58
+ <ElementDetailsCard key={'details card'} fqn={activeOverlay.elementDetails} />
59
+ )}
60
+ </AnimatePresence>
61
+ <AnimatePresence initial={false} onExitComplete={onExitComplete}>
62
+ {activeOverlay && isNullish(activeOverlay.elementDetails) && (
63
+ <RemoveScroll forwardProps>
64
+ <Box
65
+ component={m.div}
66
+ className={css.container}
67
+ data-likec4-color="gray"
68
+ initial={{
69
+ '--backdrop-blur': '0px',
70
+ '--backdrop-opacity': '0%',
71
+ opacity: 0,
72
+ translateY: -15
73
+ }}
74
+ animate={{
75
+ '--backdrop-blur': '10px',
76
+ '--backdrop-opacity': '70%',
77
+ opacity: 1,
78
+ translateY: 0
79
+ }}
80
+ exit={{
81
+ '--backdrop-blur': '1px',
82
+ '--backdrop-opacity': '0%',
83
+ translateY: -5,
84
+ opacity: 0,
85
+ transition: {
86
+ duration: .2
87
+ }
88
+ }}
89
+ >
90
+ <FocusTrap>
91
+ {activeOverlay.relationshipsOf && <RelationshipsOverlay subjectId={activeOverlay.relationshipsOf} />}
92
+ {activeOverlay.edgeDetails && (
93
+ <XYFlowProvider
94
+ defaultNodes={[]}
95
+ defaultEdges={[]}>
96
+ <EdgeDetailsXYFlow edgeId={activeOverlay.edgeDetails} />
97
+ </XYFlowProvider>
98
+ )}
99
+ <Box pos={'absolute'} top={'1rem'} right={'1rem'}>
100
+ <ActionIcon
101
+ variant="default"
102
+ // color="gray"
103
+ size={'lg'}
104
+ // data-autofocus
105
+ // autoFocus
106
+ onClick={(e) => {
107
+ e.stopPropagation()
108
+ ctxValue.close()
109
+ }}>
110
+ <IconX />
111
+ </ActionIcon>
112
+ </Box>
113
+ </FocusTrap>
114
+ </Box>
115
+ </RemoveScroll>
94
116
  )}
95
117
  </AnimatePresence>
96
118
  </OverlayContext.Provider>
@@ -208,3 +230,33 @@ export const Overlays = memo(() => {
208
230
  // </Box>
209
231
  // )
210
232
  // }
233
+ const OverlayDialog = ({ children }: PropsWithChildren) => {
234
+ const overlay = useOverlayDialog()
235
+ const ref = useRef<HTMLDialogElement>(null)
236
+ useEffect(() => {
237
+ ref.current?.showModal()
238
+ }, [])
239
+ return (
240
+ <m.dialog
241
+ ref={ref}
242
+ exit={{
243
+ opacity: 0,
244
+ transition: {
245
+ duration: .15
246
+ }
247
+ }}
248
+ onClick={e => {
249
+ if ((e.target as any)?.nodeName?.toUpperCase() === 'DIALOG') {
250
+ e.stopPropagation()
251
+ ref.current?.close()
252
+ }
253
+ }}
254
+ onClose={e => {
255
+ e.stopPropagation()
256
+ overlay.close()
257
+ }}
258
+ >
259
+ {children}
260
+ </m.dialog>
261
+ )
262
+ }
@@ -27,6 +27,7 @@ type ElementNodeProps = NodeProps<XYFlowTypes.ElementNode>
27
27
  function selector(s: DiagramState) {
28
28
  return {
29
29
  currentViewId: s.view.id,
30
+ enableRelationshipBrowser: s.enableRelationshipBrowser,
30
31
  onNavigateTo: s.onNavigateTo,
31
32
  onOpenSource: s.onOpenSourceElement
32
33
  }
@@ -47,7 +48,8 @@ export function ElementNode({
47
48
  const {
48
49
  currentViewId,
49
50
  onNavigateTo,
50
- onOpenSource
51
+ onOpenSource,
52
+ enableRelationshipBrowser
51
53
  } = useDiagramState(selector)
52
54
  return (
53
55
  <>
@@ -97,21 +99,22 @@ export function ElementNode({
97
99
  <Action
98
100
  onClick={(event) => {
99
101
  event.stopPropagation()
100
- setTimeout(() => onNavigateTo(navigateTo), 100)
101
- overlay.close()
102
+ overlay.close(() => onNavigateTo(navigateTo))
102
103
  }}>
103
104
  <IconZoomScan stroke={1.8} style={{ width: '75%' }} />
104
105
  </Action>
105
106
  )}
106
- <Action
107
- onClick={(event) => {
108
- event.stopPropagation()
109
- overlay.openOverlay({
110
- relationshipsOf: data.fqn
111
- })
112
- }}>
113
- <IconTransform stroke={1.8} style={{ width: '72%' }} />
114
- </Action>
107
+ {enableRelationshipBrowser && (
108
+ <Action
109
+ onClick={(event) => {
110
+ event.stopPropagation()
111
+ overlay.openOverlay({
112
+ relationshipsOf: data.fqn
113
+ })
114
+ }}>
115
+ <IconTransform stroke={1.8} style={{ width: '72%' }} />
116
+ </Action>
117
+ )}
115
118
  {onOpenSource && (
116
119
  <Action
117
120
  onClick={(event) => {
@@ -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
+ })