forked from jordanlambrecht/tracker-tracker
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathChartCard.tsx
More file actions
114 lines (105 loc) · 3.44 KB
/
Copy pathChartCard.tsx
File metadata and controls
114 lines (105 loc) · 3.44 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
// src/components/dashboard/ChartCard.tsx
"use client"
import { ChevronUpIcon, EyeOffIcon } from "@icons"
import clsx from "clsx"
import { type ReactNode, useEffect, useRef, useState } from "react"
import { Card, ChartShimmer, Tooltip } from "@/components/ui"
interface ChartCardProps {
title: string
description?: string
collapsed: boolean
onToggleCollapse: () => void
onHide: () => void
children: ReactNode
}
function ChartCard({
title,
description,
collapsed,
onToggleCollapse,
onHide,
children,
}: ChartCardProps) {
const cardRef = useRef<HTMLDivElement>(null)
const [visible, setVisible] = useState(false)
const [mounted, setMounted] = useState(false)
// Defer initial mount until scrolled into view
useEffect(() => {
const el = cardRef.current
if (!el || visible) return
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setVisible(true)
observer.disconnect()
}
},
{ rootMargin: "200px" }
)
observer.observe(el)
return () => observer.disconnect()
}, [visible])
// Mount once visible, stay mounted. The collapse animation uses CSS
// (grid-rows-[0fr] + opacity-0) to hide content — no need to destroy
// and recreate ECharts instances on every expand/collapse cycle.
useEffect(() => {
if (visible && !collapsed && !mounted) {
setMounted(true)
}
}, [visible, collapsed, mounted])
return (
<div ref={cardRef}>
<Card
className="relative flex flex-col gap-4"
title={title}
subtitle={description && !collapsed ? description : undefined}
>
{/* Collapse / Hide controls — absolute top-right over Card header */}
<div className="absolute top-4 right-4 flex items-center gap-1 z-10">
<Tooltip content={collapsed ? "Expand" : "Collapse"}>
<button
type="button"
onClick={onToggleCollapse}
className="w-7 h-7 flex items-center justify-center text-muted hover:text-secondary nm-interactive-inset cursor-pointer rounded-nm-sm"
aria-label={collapsed ? "Expand chart" : "Collapse chart"}
>
<ChevronUpIcon
width="14"
height="14"
className="transition-transform duration-200"
style={{ transform: collapsed ? "rotate(180deg)" : "rotate(0deg)" }}
/>
</button>
</Tooltip>
<Tooltip content="Hide chart">
<button
type="button"
onClick={onHide}
className="w-7 h-7 flex items-center justify-center text-muted hover:text-secondary nm-interactive-inset cursor-pointer rounded-nm-sm"
aria-label="Hide chart"
>
<EyeOffIcon width="14" height="14" />
</button>
</Tooltip>
</div>
<div
className={clsx(
"grid transition-[grid-template-rows] duration-200 ease-out",
collapsed ? "grid-rows-[0fr]" : "grid-rows-[1fr]"
)}
>
<div
className={clsx(
"overflow-hidden p-6 -m-6 transition-opacity duration-150",
collapsed ? "opacity-0" : "opacity-100"
)}
>
{mounted ? children : visible && !collapsed && <ChartShimmer />}
</div>
</div>
</Card>
</div>
)
}
export type { ChartCardProps }
export { ChartCard }