@tldiagram/core-ui 1.95.0 → 1.95.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/dist/components/MiniZoomOnboarding.d.ts +2 -1
- package/dist/components/ZUI/ZUICanvas.d.ts +1 -0
- package/dist/index.js +6165 -6150
- package/package.json +1 -1
- package/src/components/MiniZoomOnboarding.tsx +29 -22
- package/src/components/ZUI/ZUICanvas.tsx +11 -6
- package/src/pages/InfiniteZoom.tsx +35 -8
package/package.json
CHANGED
|
@@ -1,18 +1,13 @@
|
|
|
1
|
-
import { Box, HStack, Text } from '@chakra-ui/react'
|
|
2
|
-
import { keyframes } from '@emotion/react'
|
|
1
|
+
import { Box, CloseButton, HStack, Text } from '@chakra-ui/react'
|
|
3
2
|
import { ZoomInIcon } from './Icons'
|
|
4
3
|
|
|
5
4
|
interface Props {
|
|
6
5
|
isVisible: boolean
|
|
6
|
+
onClose?: () => void
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
const pulseGlow = keyframes`
|
|
10
|
-
0% { box-shadow: 0 0 0 0 rgba(var(--accent-rgb), 0.4); }
|
|
11
|
-
70% { box-shadow: 0 0 0 20px rgba(var(--accent-rgb), 0); }
|
|
12
|
-
100% { box-shadow: 0 0 0 0 rgba(var(--accent-rgb), 0); }
|
|
13
|
-
`
|
|
14
9
|
|
|
15
|
-
export default function MiniZoomOnboarding({ isVisible }: Props) {
|
|
10
|
+
export default function MiniZoomOnboarding({ isVisible, onClose }: Props) {
|
|
16
11
|
return (
|
|
17
12
|
<Box
|
|
18
13
|
position="absolute"
|
|
@@ -22,14 +17,14 @@ export default function MiniZoomOnboarding({ isVisible }: Props) {
|
|
|
22
17
|
zIndex={100}
|
|
23
18
|
opacity={isVisible ? 1 : 0}
|
|
24
19
|
transition="all 0.8s cubic-bezier(0.16, 1, 0.3, 1)"
|
|
25
|
-
pointerEvents=
|
|
20
|
+
pointerEvents={isVisible ? 'auto' : 'none'}
|
|
26
21
|
>
|
|
27
22
|
<Box
|
|
28
23
|
className="glass"
|
|
29
24
|
px={6}
|
|
25
|
+
pr={onClose ? 11 : 6}
|
|
30
26
|
py={4}
|
|
31
27
|
borderRadius="12px"
|
|
32
|
-
animation={isVisible ? `${pulseGlow} 3s infinite` : 'none'}
|
|
33
28
|
position="relative"
|
|
34
29
|
overflow="hidden"
|
|
35
30
|
border="1.5px solid rgba(var(--accent-rgb), 0.3)"
|
|
@@ -44,27 +39,39 @@ export default function MiniZoomOnboarding({ isVisible }: Props) {
|
|
|
44
39
|
bg="var(--accent)"
|
|
45
40
|
opacity={0.8}
|
|
46
41
|
/>
|
|
47
|
-
|
|
42
|
+
{onClose && (
|
|
43
|
+
<CloseButton
|
|
44
|
+
aria-label="Dismiss zoom hint"
|
|
45
|
+
position="absolute"
|
|
46
|
+
top={2}
|
|
47
|
+
right={2}
|
|
48
|
+
size="sm"
|
|
49
|
+
color="whiteAlpha.700"
|
|
50
|
+
_hover={{ color: 'white', bg: 'whiteAlpha.200' }}
|
|
51
|
+
onClick={onClose}
|
|
52
|
+
/>
|
|
53
|
+
)}
|
|
54
|
+
|
|
48
55
|
<HStack spacing={5} pl={3}>
|
|
49
56
|
<Box color="var(--accent)">
|
|
50
57
|
<ZoomInIcon size={24} />
|
|
51
58
|
</Box>
|
|
52
59
|
<Box>
|
|
53
|
-
<Text
|
|
54
|
-
fontSize="10px"
|
|
55
|
-
color="var(--accent)"
|
|
56
|
-
fontWeight="900"
|
|
57
|
-
letterSpacing="0.15em"
|
|
58
|
-
textTransform="uppercase"
|
|
60
|
+
<Text
|
|
61
|
+
fontSize="10px"
|
|
62
|
+
color="var(--accent)"
|
|
63
|
+
fontWeight="900"
|
|
64
|
+
letterSpacing="0.15em"
|
|
65
|
+
textTransform="uppercase"
|
|
59
66
|
mb={0.5}
|
|
60
67
|
opacity={0.9}
|
|
61
68
|
>
|
|
62
|
-
|
|
69
|
+
Hint:
|
|
63
70
|
</Text>
|
|
64
|
-
<Text
|
|
65
|
-
fontSize="15px"
|
|
66
|
-
color="white"
|
|
67
|
-
fontWeight="600"
|
|
71
|
+
<Text
|
|
72
|
+
fontSize="15px"
|
|
73
|
+
color="white"
|
|
74
|
+
fontWeight="600"
|
|
68
75
|
whiteSpace="nowrap"
|
|
69
76
|
letterSpacing="-0.01em"
|
|
70
77
|
>
|
|
@@ -51,6 +51,7 @@ interface Props {
|
|
|
51
51
|
onReady?: () => void
|
|
52
52
|
onZoom?: () => void
|
|
53
53
|
onPan?: () => void
|
|
54
|
+
initialCameraFrame?: ZUICameraFrame
|
|
54
55
|
highlightedTags?: string[]
|
|
55
56
|
highlightColor?: string
|
|
56
57
|
hiddenTags?: string[]
|
|
@@ -319,7 +320,7 @@ function findFirstExpandableNodeInTree(
|
|
|
319
320
|
return null
|
|
320
321
|
}
|
|
321
322
|
|
|
322
|
-
export const ZUICanvas = forwardRef<ZUICanvasHandle, Props>(function ZUICanvas({ data, onReady, onZoom, onPan, highlightedTags, highlightColor, hiddenTags, crossBranchSettings, hoverLocked = false }, ref) {
|
|
323
|
+
export const ZUICanvas = forwardRef<ZUICanvasHandle, Props>(function ZUICanvas({ data, onReady, onZoom, onPan, initialCameraFrame, highlightedTags, highlightColor, hiddenTags, crossBranchSettings, hoverLocked = false }, ref) {
|
|
323
324
|
const canvasRef = useRef<HTMLCanvasElement>(null)
|
|
324
325
|
const containerRef = useRef<HTMLDivElement>(null)
|
|
325
326
|
const cameraTransitionRef = useRef<number | null>(null)
|
|
@@ -583,6 +584,11 @@ export const ZUICanvas = forwardRef<ZUICanvasHandle, Props>(function ZUICanvas({
|
|
|
583
584
|
return true
|
|
584
585
|
}, [layout.groups, maxZoom, setHoveredItem, setViewState])
|
|
585
586
|
|
|
587
|
+
const fitInitialView = useCallback((w: number, h: number) => {
|
|
588
|
+
if (initialCameraFrame && setCameraFrame(initialCameraFrame)) return
|
|
589
|
+
fitView(w, h, layout.bbox)
|
|
590
|
+
}, [fitView, initialCameraFrame, layout.bbox, setCameraFrame])
|
|
591
|
+
|
|
586
592
|
useEffect(() => {
|
|
587
593
|
return () => {
|
|
588
594
|
if (cameraTransitionRef.current !== null) {
|
|
@@ -600,14 +606,13 @@ export const ZUICanvas = forwardRef<ZUICanvasHandle, Props>(function ZUICanvas({
|
|
|
600
606
|
// Only set as initialized if we have valid dimensions
|
|
601
607
|
if (w > 0 && h > 0) {
|
|
602
608
|
setContainerSize({ w, h })
|
|
603
|
-
|
|
609
|
+
fitInitialView(w, h)
|
|
604
610
|
if (!initialized) {
|
|
605
611
|
setInitialized(true)
|
|
606
612
|
onReady?.()
|
|
607
613
|
}
|
|
608
614
|
}
|
|
609
|
-
|
|
610
|
-
}, [layout, initialized, onReady])
|
|
615
|
+
}, [initialized, onReady, fitInitialView])
|
|
611
616
|
|
|
612
617
|
// ── Expose fitView to parent ─────────────────────────────────────
|
|
613
618
|
useImperativeHandle(
|
|
@@ -670,7 +675,7 @@ export const ZUICanvas = forwardRef<ZUICanvasHandle, Props>(function ZUICanvas({
|
|
|
670
675
|
|
|
671
676
|
// Trigger initialization if it hasn't happened yet
|
|
672
677
|
if (!initialized && w > 0 && h > 0) {
|
|
673
|
-
|
|
678
|
+
fitInitialView(w, h)
|
|
674
679
|
setInitialized(true)
|
|
675
680
|
onReady?.()
|
|
676
681
|
}
|
|
@@ -680,7 +685,7 @@ export const ZUICanvas = forwardRef<ZUICanvasHandle, Props>(function ZUICanvas({
|
|
|
680
685
|
ro.observe(container)
|
|
681
686
|
resize()
|
|
682
687
|
return () => ro.disconnect()
|
|
683
|
-
}, [initialized,
|
|
688
|
+
}, [initialized, fitInitialView, onReady])
|
|
684
689
|
|
|
685
690
|
useEffect(() => {
|
|
686
691
|
if (!initialized) return // Don't start loop until initialized
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/pages/InfiniteZoom.tsx Explore page holds the ZUI feature
|
|
2
2
|
import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'
|
|
3
|
-
import { useNavigate, useParams } from 'react-router-dom'
|
|
3
|
+
import { useLocation, useNavigate, useParams } from 'react-router-dom'
|
|
4
4
|
import {
|
|
5
5
|
Box,
|
|
6
6
|
Button,
|
|
@@ -44,11 +44,13 @@ const MINI_ONBOARDING_KEY = 'shared_zoom_onboarding_dismissed'
|
|
|
44
44
|
// ── Inner component ────────────────────────────────────────────────
|
|
45
45
|
function InfiniteZoomInner({ sharedToken, shareSlot }: Props, ref?: React.Ref<InfiniteZoomHandle>) {
|
|
46
46
|
const navigate = useNavigate()
|
|
47
|
+
const location = useLocation()
|
|
47
48
|
|
|
48
49
|
const [data, setData] = useState<ExploreData | null>(null)
|
|
49
50
|
const [loading, setLoading] = useState(true)
|
|
50
51
|
const [canvasReady, setCanvasReady] = useState(false)
|
|
51
52
|
const [showMiniOnboarding, setShowMiniOnboarding] = useState(false)
|
|
53
|
+
const [miniOnboardingInteractionSeen, setMiniOnboardingInteractionSeen] = useState(false)
|
|
52
54
|
const [tagColors] = useState<Record<string, import('../types').Tag>>({})
|
|
53
55
|
const [layers, setLayers] = useState<ViewLayer[]>([])
|
|
54
56
|
const [highlightedTags, setHighlightedTags] = useState<string[]>([])
|
|
@@ -59,6 +61,15 @@ function InfiniteZoomInner({ sharedToken, shareSlot }: Props, ref?: React.Ref<In
|
|
|
59
61
|
const crossBranchSurface = sharedToken ? 'zui-shared' : 'zui'
|
|
60
62
|
const { settings: crossBranchSettings, setEnabled: setCrossBranchEnabled } = useCrossBranchContextSettings(crossBranchSurface)
|
|
61
63
|
|
|
64
|
+
const cameraProfile = useMemo(() => new URLSearchParams(location.search).get('profile'), [location.search])
|
|
65
|
+
const isDetailToOverviewProfile = sharedToken && cameraProfile === 'detail-to-overview'
|
|
66
|
+
|
|
67
|
+
const initialCameraFrame = useMemo<ZUICameraFrame | undefined>(() => {
|
|
68
|
+
return isDetailToOverviewProfile
|
|
69
|
+
? { profile: 'detail-to-overview', progress: 0 }
|
|
70
|
+
: undefined
|
|
71
|
+
}, [isDetailToOverviewProfile])
|
|
72
|
+
|
|
62
73
|
useImperativeHandle(ref, () => ({
|
|
63
74
|
focusDiagram(viewId: number) {
|
|
64
75
|
return zuiRef.current?.focusDiagram(viewId) ?? false
|
|
@@ -124,17 +135,32 @@ function InfiniteZoomInner({ sharedToken, shareSlot }: Props, ref?: React.Ref<In
|
|
|
124
135
|
}, [])
|
|
125
136
|
|
|
126
137
|
useEffect(() => {
|
|
138
|
+
if (isDetailToOverviewProfile) return
|
|
127
139
|
if (sharedToken && canvasReady && !localStorage.getItem(MINI_ONBOARDING_KEY)) {
|
|
128
140
|
setShowMiniOnboarding(true)
|
|
129
141
|
}
|
|
130
|
-
}, [sharedToken, canvasReady])
|
|
142
|
+
}, [sharedToken, canvasReady, isDetailToOverviewProfile])
|
|
131
143
|
|
|
132
|
-
const
|
|
144
|
+
const dismissMiniOnboarding = useCallback(() => {
|
|
133
145
|
if (showMiniOnboarding) {
|
|
134
146
|
setShowMiniOnboarding(false)
|
|
135
|
-
|
|
147
|
+
if (!isDetailToOverviewProfile) {
|
|
148
|
+
localStorage.setItem(MINI_ONBOARDING_KEY, 'true')
|
|
149
|
+
}
|
|
136
150
|
}
|
|
137
|
-
}, [showMiniOnboarding])
|
|
151
|
+
}, [isDetailToOverviewProfile, showMiniOnboarding])
|
|
152
|
+
|
|
153
|
+
const showMiniOnboardingAfterCanvasInteraction = useCallback(() => {
|
|
154
|
+
if (!isDetailToOverviewProfile || miniOnboardingInteractionSeen) return
|
|
155
|
+
setMiniOnboardingInteractionSeen(true)
|
|
156
|
+
setShowMiniOnboarding(true)
|
|
157
|
+
}, [isDetailToOverviewProfile, miniOnboardingInteractionSeen])
|
|
158
|
+
|
|
159
|
+
const handleCanvasZoom = useCallback(() => {
|
|
160
|
+
setMiniOnboardingInteractionSeen(true)
|
|
161
|
+
dismissMiniOnboarding()
|
|
162
|
+
}, [dismissMiniOnboarding])
|
|
163
|
+
|
|
138
164
|
useEffect(() => {
|
|
139
165
|
const loader = sharedToken ? api.explore.loadShared(sharedToken) : api.explore.load()
|
|
140
166
|
loader.then((d) => {
|
|
@@ -243,8 +269,9 @@ function InfiniteZoomInner({ sharedToken, shareSlot }: Props, ref?: React.Ref<In
|
|
|
243
269
|
ref={zuiRef}
|
|
244
270
|
data={data}
|
|
245
271
|
onReady={handleCanvasReady}
|
|
246
|
-
onZoom={
|
|
247
|
-
onPan={
|
|
272
|
+
onZoom={handleCanvasZoom}
|
|
273
|
+
onPan={showMiniOnboardingAfterCanvasInteraction}
|
|
274
|
+
initialCameraFrame={initialCameraFrame}
|
|
248
275
|
highlightedTags={highlightedTags}
|
|
249
276
|
highlightColor={highlightColor}
|
|
250
277
|
hiddenTags={hiddenTags}
|
|
@@ -254,7 +281,7 @@ function InfiniteZoomInner({ sharedToken, shareSlot }: Props, ref?: React.Ref<In
|
|
|
254
281
|
|
|
255
282
|
{/* Onboarding overlay */}
|
|
256
283
|
{data && !sharedToken && <ExploreOnboarding hasLinkedNodes={!!(data.navigations?.length > 0)} />}
|
|
257
|
-
<MiniZoomOnboarding isVisible={showMiniOnboarding} />
|
|
284
|
+
<MiniZoomOnboarding isVisible={showMiniOnboarding} onClose={dismissMiniOnboarding} />
|
|
258
285
|
|
|
259
286
|
{/* Bottom toolbar */}
|
|
260
287
|
<Box
|