77 */
88import cn from 'classnames'
99import { format } from 'date-fns'
10- import { useMemo , type ReactNode } from 'react'
10+ import React , { useMemo , type ReactNode } from 'react'
1111import {
1212 Area ,
1313 AreaChart ,
@@ -22,6 +22,8 @@ import type { TooltipProps } from 'recharts/types/component/Tooltip'
2222import type { ChartDatum } from '@oxide/api'
2323import { Error12Icon } from '@oxide/design-system/icons/react'
2424
25+ import { classed } from '~/util/classed'
26+
2527// Recharts's built-in ticks behavior is useless and probably broken
2628/**
2729 * Split the data into n evenly spaced ticks, with one at the left end and one a
@@ -115,7 +117,6 @@ type TimeSeriesChartProps = {
115117 endTime : Date
116118 unit ?: string
117119 yAxisTickFormatter ?: ( val : number ) => string
118- hasBorder ?: boolean
119120 hasError ?: boolean
120121}
121122
@@ -164,15 +165,13 @@ const SkeletonMetric = ({
164165)
165166
166167export function TimeSeriesChart ( {
167- className,
168168 data : rawData ,
169169 title,
170170 interpolation = 'linear' ,
171171 startTime,
172172 endTime,
173173 unit,
174174 yAxisTickFormatter = ( val ) => val . toLocaleString ( ) ,
175- hasBorder = true ,
176175 hasError = false ,
177176} : TimeSeriesChartProps ) {
178177 // We use the largest data point +20% for the graph scale. !rawData doesn't
@@ -206,32 +205,28 @@ export function TimeSeriesChart({
206205 // re-render on every render of the parent when the data is undefined
207206 const data = useMemo ( ( ) => rawData || [ ] , [ rawData ] )
208207
209- const wrapperClass = cn ( className , hasBorder && 'rounded-lg border border-default' )
210-
211208 if ( hasError ) {
212209 return (
213- < SkeletonMetric className = { wrapperClass } >
210+ < SkeletonMetric >
214211 < MetricsError />
215212 </ SkeletonMetric >
216213 )
217214 }
218215
219216 if ( ! data || data . length === 0 ) {
220217 return (
221- < SkeletonMetric shimmer className = { wrapperClass } >
218+ < SkeletonMetric shimmer >
222219 < MetricsLoadingIndicator />
223220 </ SkeletonMetric >
224221 )
225222 }
226223
227- const margin = { top : 0 , right : hasBorder ? 16 : 0 , bottom : 0 , left : 0 }
228-
229224 // ResponsiveContainer has default height and width of 100%
230225 // https://recharts.org/en-US/api/ResponsiveContainer
231226 return (
232- < div className = "h-[300px] w-full " >
233- < ResponsiveContainer className = { wrapperClass } >
234- < AreaChart data = { data } margin = { margin } >
227+ < div className = "px-5 pb-5 pt-8 " >
228+ < ResponsiveContainer height = { 300 } >
229+ < AreaChart data = { data } margin = { { top : 0 , right : 0 , bottom : 0 , left : 0 } } >
235230 < CartesianGrid stroke = { GRID_GRAY } vertical = { false } />
236231 < XAxis
237232 axisLine = { { stroke : GRID_GRAY } }
@@ -314,3 +309,27 @@ const MetricsError = () => (
314309 />
315310 </ >
316311)
312+
313+ export const ChartContainer = classed . div `flex w-full grow flex-col rounded-lg border border-default`
314+
315+ type ChartHeaderProps = {
316+ title : string
317+ label : string
318+ description ?: string
319+ children ?: React . ReactNode
320+ }
321+
322+ export function ChartHeader ( { title, label, description, children } : ChartHeaderProps ) {
323+ return (
324+ < div className = "flex items-center justify-between border-b px-5 pb-4 pt-5 border-secondary" >
325+ < div >
326+ < h2 className = "flex items-baseline gap-1.5" >
327+ < div className = "text-sans-semi-lg" > { title } </ div >
328+ < div className = "text-sans-md text-secondary" > { label } </ div >
329+ </ h2 >
330+ < div className = "mt-0.5 text-sans-md text-secondary" > { description } </ div >
331+ </ div >
332+ { children }
333+ </ div >
334+ )
335+ }
0 commit comments