@likec4/diagram 1.3.0 → 1.4.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/dist/LikeC4Diagram.d.ts +1 -1
- package/dist/LikeC4Diagram.props.d.ts +6 -1
- package/dist/index.mjs +270 -168
- package/dist/state/DiagramContext.d.ts +155 -154
- package/dist/state/diagramStore.d.ts +154 -154
- package/dist/state/index.d.ts +0 -1
- package/dist/state/useDiagramStore.d.ts +3 -4622
- package/dist/state/useNavigationHistory.d.ts +5 -0
- package/dist/style.css +86 -54
- package/dist/theme.css.d.ts +5 -0
- package/dist/ui/BackwardForwardPanel.css.d.ts +1 -0
- package/dist/ui/BackwardForwardPanel.d.ts +1 -0
- package/dist/ui/DiagramTitlePanel.css.d.ts +1 -1
- package/dist/ui/OptionsPanel.d.ts +1 -3
- package/dist/xyflow/XYFlowEvents.d.ts +4 -4
- package/dist/xyflow/XYFlowInner.d.ts +1 -0
- package/dist/xyflow/edges/RelationshipEdge.d.ts +1 -1
- package/dist/xyflow/hooks/useXYFlow.d.ts +9 -9
- package/dist/xyflow/nodes/element/element.css.d.ts +1 -0
- package/dist/xyflow/types.d.ts +1 -1
- package/package.json +10 -10
- package/src/LikeC4Diagram.css.ts +11 -1
- package/src/LikeC4Diagram.props.ts +10 -3
- package/src/LikeC4Diagram.tsx +30 -7
- package/src/globals.css.ts +1 -1
- package/src/state/DiagramContext.tsx +3 -3
- package/src/state/diagramStore.ts +27 -30
- package/src/state/index.ts +0 -1
- package/src/state/useDiagramStore.ts +6 -13
- package/src/state/useNavigationHistory.ts +39 -0
- package/src/theme.css.ts +5 -0
- package/src/ui/BackwardForwardPanel.css.ts +22 -0
- package/src/ui/BackwardForwardPanel.tsx +59 -0
- package/src/ui/DiagramTitlePanel.css.ts +11 -5
- package/src/ui/DiagramTitlePanel.tsx +105 -93
- package/src/ui/OptionsPanel.tsx +28 -19
- package/src/xyflow/XYFlowInner.tsx +5 -2
- package/src/xyflow/diagram-to-xyflow.ts +2 -13
- package/src/xyflow/edges/RelationshipEdge.tsx +11 -8
- package/src/xyflow/edges/edges.css.ts +14 -10
- package/src/xyflow/hooks/useLayoutConstraints.ts +2 -1
- package/src/xyflow/nodes/compound/CompoundNode.css.ts +8 -8
- package/src/xyflow/nodes/element/ElementNode.tsx +7 -8
- package/src/xyflow/nodes/element/element.css.ts +18 -5
package/src/LikeC4Diagram.tsx
CHANGED
|
@@ -4,7 +4,7 @@ import clsx from 'clsx'
|
|
|
4
4
|
import { DEV } from 'esm-env'
|
|
5
5
|
import { shallowEqual } from 'fast-equals'
|
|
6
6
|
import { domAnimation, LazyMotion } from 'framer-motion'
|
|
7
|
-
import { memo, useRef } from 'react'
|
|
7
|
+
import { memo, useEffect, useRef, useState } from 'react'
|
|
8
8
|
import useTilg from 'tilg'
|
|
9
9
|
import { rootClassName } from './globals.css'
|
|
10
10
|
import { KeepAspectRatioContainer } from './KeepAspectRatioContainer'
|
|
@@ -12,7 +12,7 @@ import { cssDisablePan, cssNoControls, cssReactFlow, cssTransparentBg } from './
|
|
|
12
12
|
import { type LikeC4DiagramEventHandlers, type LikeC4DiagramProperties } from './LikeC4Diagram.props'
|
|
13
13
|
import { EnsureMantine } from './mantine/EnsureMantine'
|
|
14
14
|
import { DiagramContextProvider } from './state/DiagramContext'
|
|
15
|
-
import {
|
|
15
|
+
import { useDiagramStoreApi } from './state/useDiagramStore'
|
|
16
16
|
import { diagramViewToXYFlowData } from './xyflow/diagram-to-xyflow'
|
|
17
17
|
import { FitViewOnDiagramChange } from './xyflow/FitviewOnDiagramChange'
|
|
18
18
|
import { SelectEdgesOnNodeFocus } from './xyflow/SelectEdgesOnNodeFocus'
|
|
@@ -36,6 +36,7 @@ export function LikeC4Diagram({
|
|
|
36
36
|
controls = false,
|
|
37
37
|
showElementLinks = true,
|
|
38
38
|
showDiagramTitle = true,
|
|
39
|
+
showNavigationButtons = false,
|
|
39
40
|
enableDynamicViewWalkthrough = true,
|
|
40
41
|
initialWidth,
|
|
41
42
|
initialHeight,
|
|
@@ -113,6 +114,7 @@ export function LikeC4Diagram({
|
|
|
113
114
|
controls={controls}
|
|
114
115
|
pannable={pannable}
|
|
115
116
|
showDiagramTitle={showDiagramTitle}
|
|
117
|
+
showNavigationButtons={showNavigationButtons}
|
|
116
118
|
enableDynamicViewWalkthrough={enableDynamicViewWalkthrough}
|
|
117
119
|
/>
|
|
118
120
|
</KeepAspectRatioContainer>
|
|
@@ -132,6 +134,7 @@ type LikeC4DiagramInnerProps = {
|
|
|
132
134
|
defaultNodes: XYFlowNode[]
|
|
133
135
|
defaultEdges: XYFlowEdge[]
|
|
134
136
|
showDiagramTitle: boolean
|
|
137
|
+
showNavigationButtons: boolean
|
|
135
138
|
enableDynamicViewWalkthrough: boolean
|
|
136
139
|
}
|
|
137
140
|
const LikeC4DiagramInnerMemo = memo<LikeC4DiagramInnerProps>(function LikeC4DiagramInner({
|
|
@@ -143,9 +146,26 @@ const LikeC4DiagramInnerMemo = memo<LikeC4DiagramInnerProps>(function LikeC4Diag
|
|
|
143
146
|
defaultNodes,
|
|
144
147
|
defaultEdges,
|
|
145
148
|
showDiagramTitle,
|
|
149
|
+
showNavigationButtons,
|
|
146
150
|
enableDynamicViewWalkthrough
|
|
147
151
|
}) {
|
|
148
152
|
DEV && useTilg()
|
|
153
|
+
const diagramApi = useDiagramStoreApi()
|
|
154
|
+
const [isInitialized, setIsInitialized] = useState(diagramApi.getState().initialized)
|
|
155
|
+
|
|
156
|
+
useEffect(() => {
|
|
157
|
+
if (isInitialized) {
|
|
158
|
+
return
|
|
159
|
+
}
|
|
160
|
+
return diagramApi.subscribe(
|
|
161
|
+
s => s.initialized,
|
|
162
|
+
setIsInitialized,
|
|
163
|
+
{
|
|
164
|
+
fireImmediately: true
|
|
165
|
+
}
|
|
166
|
+
)
|
|
167
|
+
}, [isInitialized])
|
|
168
|
+
|
|
149
169
|
return (
|
|
150
170
|
<>
|
|
151
171
|
<XYFlow
|
|
@@ -160,17 +180,20 @@ const LikeC4DiagramInnerMemo = memo<LikeC4DiagramInnerProps>(function LikeC4Diag
|
|
|
160
180
|
)}
|
|
161
181
|
>
|
|
162
182
|
<XYFlowInner
|
|
183
|
+
showNavigationButtons={showNavigationButtons}
|
|
163
184
|
showDiagramTitle={showDiagramTitle}
|
|
164
185
|
enableDynamicViewWalkthrough={enableDynamicViewWalkthrough}
|
|
165
186
|
background={background}
|
|
166
187
|
controls={controls}
|
|
167
188
|
/>
|
|
168
189
|
</XYFlow>
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
190
|
+
{isInitialized && (
|
|
191
|
+
<>
|
|
192
|
+
<SyncWithDiagram />
|
|
193
|
+
{fitView && <FitViewOnDiagramChange />}
|
|
194
|
+
{fitView && zoomable && <SelectEdgesOnNodeFocus />}
|
|
195
|
+
</>
|
|
196
|
+
)}
|
|
174
197
|
</>
|
|
175
198
|
)
|
|
176
199
|
}, shallowEqual)
|
package/src/globals.css.ts
CHANGED
|
@@ -4,12 +4,12 @@ import { useUpdateEffect } from '../hooks'
|
|
|
4
4
|
import { useXYFlow } from '../xyflow/hooks'
|
|
5
5
|
import { createDiagramStore, type DiagramInitialState } from './diagramStore'
|
|
6
6
|
|
|
7
|
-
type
|
|
8
|
-
export const DiagramContext = createContext<
|
|
7
|
+
export type DiagramZustandStore = ReturnType<typeof createDiagramStore>
|
|
8
|
+
export const DiagramContext = createContext<DiagramZustandStore | null>(null)
|
|
9
9
|
|
|
10
10
|
export function DiagramContextProvider({ children, view, ...props }: PropsWithChildren<DiagramInitialState>) {
|
|
11
11
|
const xyflow = useXYFlow()
|
|
12
|
-
const store = useRef<
|
|
12
|
+
const store = useRef<DiagramZustandStore>()
|
|
13
13
|
|
|
14
14
|
if (!store.current) {
|
|
15
15
|
store.current = createDiagramStore({
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { DiagramNode, DiagramView, Fqn, ViewID } from '@likec4/core'
|
|
2
|
-
import { invariant, nonexhaustive,
|
|
2
|
+
import { invariant, nonexhaustive, StepEdgeId } from '@likec4/core'
|
|
3
3
|
import { getViewportForBounds, type XYPosition } from '@xyflow/react'
|
|
4
|
-
import {
|
|
4
|
+
import { getNodeDimensions } from '@xyflow/system'
|
|
5
5
|
import { DEV } from 'esm-env'
|
|
6
6
|
import { deepEqual, shallowEqual } from 'fast-equals'
|
|
7
7
|
import type { MouseEvent as ReactMouseEvent } from 'react'
|
|
8
|
-
import {
|
|
8
|
+
import { entries, isEmpty, reduce } from 'remeda'
|
|
9
9
|
import type { Exact, Except, RequiredKeysOf, Simplify } from 'type-fest'
|
|
10
10
|
import { devtools, subscribeWithSelector } from 'zustand/middleware'
|
|
11
11
|
import { shallow } from 'zustand/shallow'
|
|
@@ -30,7 +30,6 @@ export type DiagramStore = {
|
|
|
30
30
|
experimentalEdgeEditing: boolean
|
|
31
31
|
|
|
32
32
|
// Internal state
|
|
33
|
-
viewSyncState: 'synced' | 'changed'
|
|
34
33
|
viewSyncDebounceTimeout: number | null
|
|
35
34
|
|
|
36
35
|
xyflow: XYFlowInstance
|
|
@@ -109,6 +108,8 @@ interface DiagramStoreActions {
|
|
|
109
108
|
|
|
110
109
|
getElement(id: Fqn): DiagramNode | null
|
|
111
110
|
triggerChangeElementStyle: (change: Changes.ChangeElementStyle) => void
|
|
111
|
+
|
|
112
|
+
cancelSaveManualLayout: () => void
|
|
112
113
|
triggerSaveManualLayout: (xystore: XYStoreApi) => void
|
|
113
114
|
triggerOnNavigateTo: (xynodeId: string, event: ReactMouseEvent) => void
|
|
114
115
|
fitDiagram: (xyStore: XYStoreApi) => void
|
|
@@ -120,7 +121,6 @@ interface DiagramStoreActions {
|
|
|
120
121
|
export type DiagramState = Simplify<DiagramStore & DiagramStoreActions>
|
|
121
122
|
|
|
122
123
|
const DEFAULT_PROPS: Except<DiagramStore, RequiredKeysOf<DiagramInitialState> | 'xyflow'> = {
|
|
123
|
-
viewSyncState: 'synced',
|
|
124
124
|
viewSyncDebounceTimeout: null,
|
|
125
125
|
initialized: false,
|
|
126
126
|
xyflowSynced: false,
|
|
@@ -174,7 +174,6 @@ export function createDiagramStore<T extends Exact<CreateDiagramStore, T>>(props
|
|
|
174
174
|
updateView: (nextView) => {
|
|
175
175
|
let {
|
|
176
176
|
viewSyncDebounceTimeout,
|
|
177
|
-
viewSyncState,
|
|
178
177
|
xyflow,
|
|
179
178
|
dimmed,
|
|
180
179
|
xyflowSynced,
|
|
@@ -189,22 +188,12 @@ export function createDiagramStore<T extends Exact<CreateDiagramStore, T>>(props
|
|
|
189
188
|
hoveredNodeId
|
|
190
189
|
} = get()
|
|
191
190
|
|
|
192
|
-
if (viewSyncDebounceTimeout) {
|
|
191
|
+
if (viewSyncDebounceTimeout !== null) {
|
|
193
192
|
clearTimeout(viewSyncDebounceTimeout)
|
|
194
193
|
viewSyncDebounceTimeout = null
|
|
195
194
|
}
|
|
196
195
|
|
|
197
196
|
if (shallowEqual(current, nextView)) {
|
|
198
|
-
if (!xyflowSynced || viewSyncState === 'changed') {
|
|
199
|
-
set(
|
|
200
|
-
{
|
|
201
|
-
viewSyncState: 'synced',
|
|
202
|
-
xyflowSynced: true
|
|
203
|
-
},
|
|
204
|
-
noReplace,
|
|
205
|
-
'updateView: xyflow synced'
|
|
206
|
-
)
|
|
207
|
-
}
|
|
208
197
|
DEV && console.debug('store: skip updateView')
|
|
209
198
|
return
|
|
210
199
|
}
|
|
@@ -272,7 +261,6 @@ export function createDiagramStore<T extends Exact<CreateDiagramStore, T>>(props
|
|
|
272
261
|
|
|
273
262
|
set(
|
|
274
263
|
{
|
|
275
|
-
viewSyncState: 'synced',
|
|
276
264
|
viewSyncDebounceTimeout,
|
|
277
265
|
view: nextView,
|
|
278
266
|
activeDynamicViewStep,
|
|
@@ -324,25 +312,25 @@ export function createDiagramStore<T extends Exact<CreateDiagramStore, T>>(props
|
|
|
324
312
|
|
|
325
313
|
setHoveredNode: (nodeId) => {
|
|
326
314
|
if (nodeId !== get().hoveredNodeId) {
|
|
327
|
-
set({ hoveredNodeId: nodeId }
|
|
315
|
+
set({ hoveredNodeId: nodeId })
|
|
328
316
|
}
|
|
329
317
|
},
|
|
330
318
|
|
|
331
319
|
setHoveredEdge: (edgeId) => {
|
|
332
320
|
if (edgeId !== get().hoveredEdgeId) {
|
|
333
|
-
set({ hoveredEdgeId: edgeId }
|
|
321
|
+
set({ hoveredEdgeId: edgeId })
|
|
334
322
|
}
|
|
335
323
|
},
|
|
336
324
|
|
|
337
325
|
setLastClickedNode: (nodeId) => {
|
|
338
326
|
if (nodeId !== get().lastClickedNodeId) {
|
|
339
|
-
set({ lastClickedNodeId: nodeId }
|
|
327
|
+
set({ lastClickedNodeId: nodeId })
|
|
340
328
|
}
|
|
341
329
|
},
|
|
342
330
|
|
|
343
331
|
setLastClickedEdge: (edgeId) => {
|
|
344
332
|
if (edgeId !== get().lastClickedEdgeId) {
|
|
345
|
-
set({ lastClickedEdgeId: edgeId }
|
|
333
|
+
set({ lastClickedEdgeId: edgeId })
|
|
346
334
|
}
|
|
347
335
|
},
|
|
348
336
|
|
|
@@ -441,15 +429,20 @@ export function createDiagramStore<T extends Exact<CreateDiagramStore, T>>(props
|
|
|
441
429
|
...view,
|
|
442
430
|
nodes
|
|
443
431
|
})
|
|
444
|
-
if (onChange) {
|
|
445
|
-
set({ viewSyncState: 'changed' }, noReplace, 'change sync state')
|
|
446
|
-
}
|
|
447
432
|
}
|
|
448
433
|
// Trigger change event, even if there are no changes with local state
|
|
449
434
|
// but we maybe out of sync with the server
|
|
450
435
|
onChange?.({ change })
|
|
451
436
|
},
|
|
452
437
|
|
|
438
|
+
cancelSaveManualLayout: () => {
|
|
439
|
+
let { viewSyncDebounceTimeout } = get()
|
|
440
|
+
if (viewSyncDebounceTimeout !== null) {
|
|
441
|
+
clearTimeout(viewSyncDebounceTimeout)
|
|
442
|
+
set({ viewSyncDebounceTimeout: null })
|
|
443
|
+
}
|
|
444
|
+
},
|
|
445
|
+
|
|
453
446
|
triggerSaveManualLayout: (xystore) => {
|
|
454
447
|
let { viewSyncDebounceTimeout: debounced, onChange } = get()
|
|
455
448
|
if (!onChange) {
|
|
@@ -462,6 +455,7 @@ export function createDiagramStore<T extends Exact<CreateDiagramStore, T>>(props
|
|
|
462
455
|
debounced = setTimeout(() => {
|
|
463
456
|
const { nodeLookup } = xystore.getState()
|
|
464
457
|
const { xyflow, onChange } = get()
|
|
458
|
+
set({ viewSyncDebounceTimeout: null })
|
|
465
459
|
|
|
466
460
|
const movedNodes = new StringSet()
|
|
467
461
|
const nodes = reduce([...nodeLookup.values()], (acc, node) => {
|
|
@@ -490,6 +484,10 @@ export function createDiagramStore<T extends Exact<CreateDiagramStore, T>>(props
|
|
|
490
484
|
}
|
|
491
485
|
return acc
|
|
492
486
|
}, {} as Changes.SaveManualLayout['edges'])
|
|
487
|
+
if (movedNodes.size === 0 && isEmpty(edges)) {
|
|
488
|
+
DEV && console.debug('ignore triggerSaveManualLayout, as no changes detected')
|
|
489
|
+
return
|
|
490
|
+
}
|
|
493
491
|
|
|
494
492
|
const change: Changes.SaveManualLayout = {
|
|
495
493
|
op: 'save-manual-layout',
|
|
@@ -500,16 +498,15 @@ export function createDiagramStore<T extends Exact<CreateDiagramStore, T>>(props
|
|
|
500
498
|
if (DEV) {
|
|
501
499
|
console.debug('triggerSaveManualLayout', change)
|
|
502
500
|
}
|
|
503
|
-
|
|
501
|
+
|
|
504
502
|
onChange?.({ change })
|
|
505
|
-
},
|
|
503
|
+
}, 2000)
|
|
506
504
|
set(
|
|
507
505
|
{
|
|
508
|
-
viewSyncDebounceTimeout: debounced
|
|
509
|
-
viewSyncState: 'changed'
|
|
506
|
+
viewSyncDebounceTimeout: debounced
|
|
510
507
|
},
|
|
511
508
|
noReplace,
|
|
512
|
-
'
|
|
509
|
+
'debounce sync state'
|
|
513
510
|
)
|
|
514
511
|
},
|
|
515
512
|
|
package/src/state/index.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { shallowEqual } from 'fast-equals'
|
|
2
|
-
import { useContext
|
|
2
|
+
import { useContext } from 'react'
|
|
3
3
|
import { useStoreWithEqualityFn as useZustandStore } from 'zustand/traditional'
|
|
4
4
|
|
|
5
|
-
import { DiagramContext } from './DiagramContext'
|
|
5
|
+
import { DiagramContext, type DiagramZustandStore } from './DiagramContext'
|
|
6
6
|
import type { DiagramState } from './diagramStore'
|
|
7
7
|
|
|
8
8
|
function useDiagramState<StateSlice = unknown>(
|
|
9
9
|
selector: (state: DiagramState) => StateSlice,
|
|
10
10
|
equalityFn?: (a: StateSlice, b: StateSlice) => boolean
|
|
11
|
-
) {
|
|
11
|
+
): StateSlice {
|
|
12
12
|
const store = useContext(DiagramContext)
|
|
13
13
|
|
|
14
14
|
if (store === null) {
|
|
@@ -18,22 +18,15 @@ function useDiagramState<StateSlice = unknown>(
|
|
|
18
18
|
return useZustandStore(store, selector, equalityFn ?? shallowEqual)
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
export type DiagramStoreApi = Readonly<Pick<DiagramZustandStore, 'getState' | 'setState' | 'subscribe'>>
|
|
22
|
+
function useDiagramStoreApi(): DiagramStoreApi {
|
|
22
23
|
const store = useContext(DiagramContext)
|
|
23
24
|
|
|
24
25
|
if (store === null) {
|
|
25
26
|
throw new Error('useDiagramStoreApi could be used only inside DiagramContext')
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
return
|
|
29
|
-
() => ({
|
|
30
|
-
getState: store.getState,
|
|
31
|
-
setState: store.setState,
|
|
32
|
-
subscribe: store.subscribe
|
|
33
|
-
}),
|
|
34
|
-
[store]
|
|
35
|
-
)
|
|
29
|
+
return store
|
|
36
30
|
}
|
|
37
|
-
export type DiagramStoreApi = ReturnType<typeof useDiagramStoreApi>
|
|
38
31
|
|
|
39
32
|
export { useDiagramState, useDiagramStoreApi }
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { useStateHistory } from '@mantine/hooks'
|
|
2
|
+
import { useMemo } from 'react'
|
|
3
|
+
import { useUpdateEffect } from '../hooks/useUpdateEffect'
|
|
4
|
+
import { useDiagramState, useDiagramStoreApi } from './useDiagramStore'
|
|
5
|
+
|
|
6
|
+
export function useNavigationHistory() {
|
|
7
|
+
const store = useDiagramStoreApi()
|
|
8
|
+
const viewId = useDiagramState(s => s.view.id)
|
|
9
|
+
|
|
10
|
+
const [historyViewId, historyOps, {
|
|
11
|
+
history,
|
|
12
|
+
current: historyIndex
|
|
13
|
+
}] = useStateHistory(viewId)
|
|
14
|
+
|
|
15
|
+
const hasBack = historyIndex > 0
|
|
16
|
+
const hasForward = historyIndex < history.length - 1
|
|
17
|
+
|
|
18
|
+
useUpdateEffect(() => {
|
|
19
|
+
if (viewId !== historyViewId) {
|
|
20
|
+
historyOps.set(viewId)
|
|
21
|
+
}
|
|
22
|
+
}, [viewId])
|
|
23
|
+
|
|
24
|
+
useUpdateEffect(() => {
|
|
25
|
+
if (viewId !== historyViewId) {
|
|
26
|
+
store.getState().onNavigateTo?.(historyViewId)
|
|
27
|
+
}
|
|
28
|
+
}, [historyViewId])
|
|
29
|
+
|
|
30
|
+
return useMemo(() => ({
|
|
31
|
+
hasBack,
|
|
32
|
+
hasForward,
|
|
33
|
+
ops: historyOps
|
|
34
|
+
}), [
|
|
35
|
+
hasBack,
|
|
36
|
+
hasForward,
|
|
37
|
+
historyOps
|
|
38
|
+
])
|
|
39
|
+
}
|
package/src/theme.css.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createGlobalThemeContract } from '@vanilla-extract/css'
|
|
2
2
|
|
|
3
3
|
export const vars = createGlobalThemeContract({
|
|
4
|
+
safariAnimationHook: 'safari-animation-hook',
|
|
4
5
|
default: {
|
|
5
6
|
font: 'default-font-family'
|
|
6
7
|
},
|
|
@@ -32,6 +33,10 @@ export const vars = createGlobalThemeContract({
|
|
|
32
33
|
optionsPanel: {
|
|
33
34
|
top: 'options-panel-top',
|
|
34
35
|
right: 'options-panel-right'
|
|
36
|
+
},
|
|
37
|
+
navigationPanel: {
|
|
38
|
+
top: 'navigation-panel-top',
|
|
39
|
+
left: 'navigation-panel-left'
|
|
35
40
|
}
|
|
36
41
|
}, (value) => `likec4-${value}`)
|
|
37
42
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { fallbackVar, globalStyle, style } from '@vanilla-extract/css'
|
|
2
|
+
import { mantine } from '../mantine.css'
|
|
3
|
+
import { vars } from '../theme.css'
|
|
4
|
+
|
|
5
|
+
export const panel = style({
|
|
6
|
+
top: fallbackVar(vars.navigationPanel.top, '1rem'),
|
|
7
|
+
left: fallbackVar(vars.navigationPanel.left, '1rem'),
|
|
8
|
+
margin: 0
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
globalStyle(`${panel} .mantine-ActionIcon-root`, {
|
|
12
|
+
vars: {
|
|
13
|
+
['--ai-size']: 'var(--ai-size-md)'
|
|
14
|
+
},
|
|
15
|
+
'@media': {
|
|
16
|
+
[mantine.largerThan('md')]: {
|
|
17
|
+
vars: {
|
|
18
|
+
['--ai-size']: 'var(--ai-size-lg)'
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
})
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { ActionIcon, Group } from '@mantine/core'
|
|
2
|
+
import { IconChevronLeft, IconChevronRight } from '@tabler/icons-react'
|
|
3
|
+
import clsx from 'clsx'
|
|
4
|
+
import { AnimatePresence, m } from 'framer-motion'
|
|
5
|
+
import { useNavigationHistory } from '../state/useNavigationHistory'
|
|
6
|
+
import * as css from './BackwardForwardPanel.css'
|
|
7
|
+
|
|
8
|
+
export function BackwardForwardPanel() {
|
|
9
|
+
const { hasBack, hasForward, ops } = useNavigationHistory()
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<Group
|
|
13
|
+
className={clsx(
|
|
14
|
+
'react-flow__panel',
|
|
15
|
+
css.panel,
|
|
16
|
+
'likec4-navigation-panel'
|
|
17
|
+
)}
|
|
18
|
+
gap={'xs'}>
|
|
19
|
+
<AnimatePresence>
|
|
20
|
+
{hasBack && (
|
|
21
|
+
<m.div
|
|
22
|
+
layout
|
|
23
|
+
initial={{ opacity: 0.05, transform: 'translateX(-10px)' }}
|
|
24
|
+
animate={{ opacity: 1, transform: 'translateX(0)' }}
|
|
25
|
+
exit={{
|
|
26
|
+
opacity: 0.05,
|
|
27
|
+
transform: 'translateX(-10px)'
|
|
28
|
+
}}
|
|
29
|
+
key={'back'}>
|
|
30
|
+
<ActionIcon
|
|
31
|
+
variant="light"
|
|
32
|
+
color="gray"
|
|
33
|
+
onClick={() => ops.back()}>
|
|
34
|
+
<IconChevronLeft />
|
|
35
|
+
</ActionIcon>
|
|
36
|
+
</m.div>
|
|
37
|
+
)}
|
|
38
|
+
{hasForward && (
|
|
39
|
+
<m.div
|
|
40
|
+
layout
|
|
41
|
+
initial={{ opacity: 0.05, transform: 'translateX(10px)' }}
|
|
42
|
+
animate={{ opacity: 1, transform: 'translateX(0)' }}
|
|
43
|
+
exit={{
|
|
44
|
+
opacity: 0,
|
|
45
|
+
transform: 'translateX(10px)'
|
|
46
|
+
}}
|
|
47
|
+
key={'forward'}>
|
|
48
|
+
<ActionIcon
|
|
49
|
+
variant="light"
|
|
50
|
+
color="gray"
|
|
51
|
+
onClick={() => ops.forward()}>
|
|
52
|
+
<IconChevronRight />
|
|
53
|
+
</ActionIcon>
|
|
54
|
+
</m.div>
|
|
55
|
+
)}
|
|
56
|
+
</AnimatePresence>
|
|
57
|
+
</Group>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { globalStyle, style } from '@vanilla-extract/css'
|
|
1
|
+
import { fallbackVar, globalStyle, style } from '@vanilla-extract/css'
|
|
2
2
|
import { mantine } from '../mantine.css'
|
|
3
|
+
import { vars } from '../theme.css'
|
|
3
4
|
|
|
4
5
|
export const container = style({
|
|
5
6
|
bottom: 0,
|
|
@@ -8,17 +9,22 @@ export const container = style({
|
|
|
8
9
|
margin: 0
|
|
9
10
|
})
|
|
10
11
|
|
|
11
|
-
export const
|
|
12
|
+
export const card = style({
|
|
12
13
|
cursor: 'default',
|
|
13
|
-
minWidth:
|
|
14
|
+
minWidth: 150,
|
|
14
15
|
maxWidth: 'calc(100vw - 16px)',
|
|
15
16
|
backgroundColor: `color-mix(in srgb, ${mantine.colors.body}, transparent 20%)`,
|
|
16
|
-
WebkitBackdropFilter: 'blur(8px)',
|
|
17
|
-
backdropFilter: 'blur(8px)',
|
|
17
|
+
WebkitBackdropFilter: fallbackVar(vars.safariAnimationHook, 'blur(8px)'),
|
|
18
|
+
backdropFilter: fallbackVar(vars.safariAnimationHook, 'blur(8px)'),
|
|
18
19
|
'@media': {
|
|
19
20
|
[mantine.largerThan('md')]: {
|
|
20
21
|
maxWidth: '45vw'
|
|
21
22
|
}
|
|
23
|
+
},
|
|
24
|
+
selectors: {
|
|
25
|
+
[`:where([data-mantine-color-scheme='dark']) &`]: {
|
|
26
|
+
backgroundColor: `color-mix(in srgb, ${mantine.colors.dark[6]}, transparent 20%)`
|
|
27
|
+
}
|
|
22
28
|
}
|
|
23
29
|
})
|
|
24
30
|
|