mirror of
https://github.com/supabase/supabase.git
synced 2026-10-15 14:19:11 +03:00
init
This commit is contained in:
1 parent
661e94b0fe
commit
de3df1a1b0
7 files changed
+465
No files matched your search
@@ -0,0 +1,178 @@
|
||||
'use client'
|
||||
|
||||
import { useState, ComponentProps } from 'react'
|
||||
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Rectangle } from 'recharts'
|
||||
import { CHART_COLORS } from './Charts.constants'
|
||||
import { CommonChartProps } from './Charts.types'
|
||||
import { useChartSize, numberFormatter } from './Charts.utils'
|
||||
import ChartHeader from './ChartHeader'
|
||||
import NoDataPlaceholder from './NoDataPlaceholder'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export interface TimelineChartDatum {
|
||||
id: string | number
|
||||
label: string
|
||||
start: number
|
||||
duration: number
|
||||
color?: string
|
||||
[key: string]: string | number | undefined
|
||||
}
|
||||
|
||||
export interface TimelineChartProps extends Omit<CommonChartProps<any>, 'data'> {
|
||||
data?: TimelineChartDatum[]
|
||||
xAxisDomain?: [number, number]
|
||||
showGrid?: boolean
|
||||
XAxisProps?: ComponentProps<typeof XAxis>
|
||||
YAxisProps?: ComponentProps<typeof YAxis>
|
||||
labelWidth?: number
|
||||
onBarClick?: (entry: TimelineChartDatum) => void
|
||||
emptyStateMessage?: string
|
||||
}
|
||||
|
||||
const TimelineChart = ({
|
||||
data = [],
|
||||
title,
|
||||
format,
|
||||
highlightedValue,
|
||||
highlightedLabel,
|
||||
valuePrecision,
|
||||
className = '',
|
||||
size = 'normal',
|
||||
emptyStateMessage,
|
||||
xAxisDomain,
|
||||
showGrid = true,
|
||||
XAxisProps,
|
||||
YAxisProps,
|
||||
labelWidth = 120,
|
||||
onBarClick,
|
||||
}: TimelineChartProps) => {
|
||||
const [focusedItemId, setFocusedItemId] = useState<string | number | null>(null)
|
||||
const { Container } = useChartSize(size)
|
||||
|
||||
if (!data || data.length === 0) {
|
||||
return <NoDataPlaceholder message={emptyStateMessage} size={size} />
|
||||
}
|
||||
|
||||
// Calculate the max end time if domain not provided
|
||||
const maxEndTime = xAxisDomain
|
||||
? xAxisDomain[1]
|
||||
: Math.max(...data.map((item) => item.start + item.duration))
|
||||
|
||||
// Create chart config for the timeline bars
|
||||
const chartConfig: ChartConfig = data.reduce((config, item) => {
|
||||
config[`item-${item.id}`] = {
|
||||
label: item.label,
|
||||
color: item.color || CHART_COLORS.GREEN_1,
|
||||
}
|
||||
return config
|
||||
}, {} as ChartConfig)
|
||||
|
||||
// Custom shape renderer for timeline bars
|
||||
const renderTimelineBar = (props: any) => {
|
||||
const { x, y, width, height, fill, index } = props
|
||||
|
||||
// Calculate position based on start time and width based on duration
|
||||
const item = data[index]
|
||||
if (!item) return null
|
||||
|
||||
const barWidth = (item.duration / maxEndTime) * width
|
||||
const barX = x + (item.start / maxEndTime) * width
|
||||
|
||||
return (
|
||||
<g>
|
||||
<Rectangle
|
||||
x={barX}
|
||||
y={y}
|
||||
width={barWidth}
|
||||
height={height}
|
||||
fill={item.color || fill}
|
||||
opacity={focusedItemId === null || focusedItemId === item.id ? 1 : 0.6}
|
||||
className={onBarClick ? 'cursor-pointer' : ''}
|
||||
rx={2}
|
||||
ry={2}
|
||||
/>
|
||||
</g>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`flex flex-col gap-y-3 ${className}`}>
|
||||
{title && (
|
||||
<ChartHeader
|
||||
title={title}
|
||||
format={format}
|
||||
highlightedValue={highlightedValue}
|
||||
highlightedLabel={highlightedLabel}
|
||||
/>
|
||||
)}
|
||||
<ChartContainer config={chartConfig} className="min-h-[300px] w-full">
|
||||
<BarChart
|
||||
layout="vertical"
|
||||
data={data}
|
||||
margin={{ top: 5, right: 30, left: labelWidth, bottom: 5 }}
|
||||
barSize={20}
|
||||
barGap={0}
|
||||
>
|
||||
{showGrid && <CartesianGrid horizontal={false} stroke="var(--border)" />}
|
||||
<XAxis
|
||||
{...XAxisProps}
|
||||
type="number"
|
||||
domain={[0, maxEndTime]}
|
||||
tickFormatter={(value) => `${value}${format ? ' ' + format : ''}`}
|
||||
tickMargin={5}
|
||||
/>
|
||||
<YAxis
|
||||
{...YAxisProps}
|
||||
type="category"
|
||||
dataKey="label"
|
||||
width={labelWidth}
|
||||
tickLine={false}
|
||||
/>
|
||||
<ChartTooltip
|
||||
content={
|
||||
<ChartTooltipContent
|
||||
formatter={(value, name, entry: any) => {
|
||||
const item = entry.payload as TimelineChartDatum
|
||||
return (
|
||||
<div className="grid gap-1">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-foreground-lighter mr-2">Start:</span>
|
||||
<span>
|
||||
{item.start} {format}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-foreground-lighter mr-2">Duration:</span>
|
||||
<span>
|
||||
{item.duration} {format}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-foreground-lighter mr-2">End:</span>
|
||||
<span>
|
||||
{item.start + item.duration} {format}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Bar
|
||||
dataKey="duration"
|
||||
shape={renderTimelineBar}
|
||||
onClick={(entry: any) => {
|
||||
onBarClick && onBarClick(entry as TimelineChartDatum)
|
||||
}}
|
||||
onMouseEnter={(entry: any) => setFocusedItemId(entry.id)}
|
||||
onMouseLeave={() => setFocusedItemId(null)}
|
||||
isAnimationActive={false}
|
||||
/>
|
||||
</BarChart>
|
||||
</ChartContainer>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TimelineChart
|
||||
@@ -0,0 +1,100 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import TimelineChart, { TimelineChartDatum } from './TimelineChart'
|
||||
import { CHART_COLORS } from './Charts.constants'
|
||||
|
||||
// Sample timeline data for page load metrics
|
||||
const sampleData: TimelineChartDatum[] = [
|
||||
{ id: 1, label: 'documentLoad', start: 0, duration: 200, color: CHART_COLORS.GREEN_1 },
|
||||
{ id: 2, label: 'documentFetch', start: 0, duration: 50, color: CHART_COLORS.GREEN_2 },
|
||||
{ id: 3, label: 'resourceFetch', start: 30, duration: 30, color: '#3870FF' },
|
||||
{ id: 4, label: 'resourceFetch', start: 40, duration: 25, color: '#3870FF' },
|
||||
{ id: 5, label: 'resourceFetch', start: 100, duration: 50, color: '#3870FF' },
|
||||
{ id: 6, label: 'resourceFetch', start: 150, duration: 30, color: '#3870FF' },
|
||||
{ id: 7, label: 'DOMContentLoaded', start: 180, duration: 10, color: CHART_COLORS.RED_1 },
|
||||
{ id: 8, label: 'onload', start: 195, duration: 5, color: CHART_COLORS.RED_2 },
|
||||
]
|
||||
|
||||
// Sample data for performance metrics - waterfall style
|
||||
const performanceData: TimelineChartDatum[] = [
|
||||
{ id: 1, label: 'DNS Lookup', start: 0, duration: 15, color: '#9F7AEA' },
|
||||
{ id: 2, label: 'TCP Connection', start: 15, duration: 20, color: '#4C51BF' },
|
||||
{ id: 3, label: 'TLS Handshake', start: 35, duration: 30, color: '#3182CE' },
|
||||
{ id: 4, label: 'Time to First Byte', start: 65, duration: 25, color: '#38B2AC' },
|
||||
{ id: 5, label: 'Content Download', start: 90, duration: 60, color: '#48BB78' },
|
||||
{ id: 6, label: 'DOM Processing', start: 150, duration: 40, color: '#ECC94B' },
|
||||
{ id: 7, label: 'Render', start: 190, duration: 35, color: '#ED8936' },
|
||||
]
|
||||
|
||||
const TimelineChartExample = () => {
|
||||
const [selectedData, setSelectedData] = useState<TimelineChartDatum | null>(null)
|
||||
const [activeDataset, setActiveDataset] = useState<'page' | 'performance'>('page')
|
||||
|
||||
// Handler for when a timeline item is clicked
|
||||
const handleBarClick = (entry: TimelineChartDatum) => {
|
||||
setSelectedData(entry)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6 p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-lg font-medium">Timeline Chart</h2>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
className={`px-3 py-1 rounded ${
|
||||
activeDataset === 'page' ? 'bg-foreground-light text-background' : 'bg-border'
|
||||
}`}
|
||||
onClick={() => setActiveDataset('page')}
|
||||
>
|
||||
Page Load
|
||||
</button>
|
||||
<button
|
||||
className={`px-3 py-1 rounded ${
|
||||
activeDataset === 'performance' ? 'bg-foreground-light text-background' : 'bg-border'
|
||||
}`}
|
||||
onClick={() => setActiveDataset('performance')}
|
||||
>
|
||||
Performance
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<TimelineChart
|
||||
data={activeDataset === 'page' ? sampleData : performanceData}
|
||||
title={activeDataset === 'page' ? 'Page Load Timeline' : 'Performance Timeline'}
|
||||
format="ms"
|
||||
size="large"
|
||||
xAxisDomain={[0, 250]}
|
||||
showGrid={true}
|
||||
onBarClick={handleBarClick}
|
||||
/>
|
||||
|
||||
{selectedData && (
|
||||
<div className="mt-4 p-3 border rounded">
|
||||
<h3 className="font-medium mb-2">Selected Timeline Item</h3>
|
||||
<div className="grid grid-cols-2 gap-2 text-sm">
|
||||
<div>Task:</div>
|
||||
<div className="font-mono">{selectedData.label}</div>
|
||||
<div>Start Time:</div>
|
||||
<div className="font-mono">{selectedData.start} ms</div>
|
||||
<div>Duration:</div>
|
||||
<div className="font-mono">{selectedData.duration} ms</div>
|
||||
<div>End Time:</div>
|
||||
<div className="font-mono">{selectedData.start + selectedData.duration} ms</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-2 text-sm text-foreground-lighter">
|
||||
<p>
|
||||
This timeline chart displays {activeDataset === 'page' ? 'page load' : 'performance'}{' '}
|
||||
metrics in milliseconds.
|
||||
</p>
|
||||
<p>Click on a bar to see detailed information.</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TimelineChartExample
|
||||
@@ -0,0 +1,114 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, ResponsiveContainer } from 'recharts'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
// Sample data for the timeline chart
|
||||
const data = [
|
||||
{ name: 'documentLoad', start: 0, duration: 200 },
|
||||
{ name: 'documentFetch', start: 0, duration: 50 },
|
||||
{ name: 'resourceFetch', start: 30, duration: 30 },
|
||||
{ name: 'resourceFetch', start: 40, duration: 25 },
|
||||
{ name: 'resourceFetch', start: 100, duration: 50 },
|
||||
{ name: 'resourceFetch', start: 150, duration: 30 },
|
||||
{ name: 'DOMContentLoaded', start: 180, duration: 10 },
|
||||
{ name: 'onload', start: 195, duration: 5 },
|
||||
]
|
||||
|
||||
// Define chart configuration with colors for each item type
|
||||
const chartConfig: ChartConfig = {
|
||||
documentLoad: {
|
||||
label: 'Document Load',
|
||||
color: '#10B981', // green
|
||||
},
|
||||
documentFetch: {
|
||||
label: 'Document Fetch',
|
||||
color: '#10B981', // green
|
||||
},
|
||||
resourceFetch: {
|
||||
label: 'Resource Fetch',
|
||||
color: '#3B82F6', // blue
|
||||
},
|
||||
DOMContentLoaded: {
|
||||
label: 'DOM Content Loaded',
|
||||
color: '#EF4444', // red
|
||||
},
|
||||
onload: {
|
||||
label: 'onLoad',
|
||||
color: '#F97316', // orange
|
||||
},
|
||||
}
|
||||
|
||||
const TimelineExample = () => {
|
||||
// Transform data for horizontal bar chart
|
||||
const transformedData = data.map((item) => ({
|
||||
...item,
|
||||
// Create a property for each type to show correct colors
|
||||
[item.name]: item.duration,
|
||||
}))
|
||||
|
||||
// Compute max time for chart domain
|
||||
const maxTime = Math.max(...data.map((item) => item.start + item.duration))
|
||||
|
||||
// Custom bar renderer to position bars at the correct start position
|
||||
const renderCustomBar = (props: any) => {
|
||||
const { x, y, width, height, fill, dataKey, index } = props
|
||||
const item = data[index]
|
||||
|
||||
// Calculate position and width based on timeline data
|
||||
const fullWidth = width / (maxTime || 1)
|
||||
const barWidth = item.duration * fullWidth
|
||||
const barX = x + item.start * fullWidth
|
||||
|
||||
return (
|
||||
<g>
|
||||
<rect x={barX} y={y} width={barWidth} height={height} fill={fill} rx={2} ry={2} />
|
||||
</g>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<h2 className="text-lg font-medium mb-4">Page Load Timeline</h2>
|
||||
|
||||
<div className="mb-4" style={{ height: '300px' }}>
|
||||
<ChartContainer config={chartConfig}>
|
||||
<BarChart
|
||||
layout="vertical"
|
||||
data={transformedData}
|
||||
margin={{ top: 20, right: 30, left: 120, bottom: 20 }}
|
||||
barGap={4}
|
||||
>
|
||||
<CartesianGrid horizontal={false} />
|
||||
<XAxis type="number" domain={[0, maxTime]} tickFormatter={(value) => `${value} ms`} />
|
||||
<YAxis type="category" dataKey="name" width={120} tickLine={false} />
|
||||
<ChartTooltip
|
||||
content={
|
||||
<ChartTooltipContent
|
||||
formatter={(value, name, entry) => (
|
||||
<div>
|
||||
<div>Start: {entry.payload.start} ms</div>
|
||||
<div>Duration: {entry.payload.duration} ms</div>
|
||||
<div>End: {entry.payload.start + entry.payload.duration} ms</div>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{Object.keys(chartConfig).map((key) => (
|
||||
<Bar key={key} dataKey={key} shape={renderCustomBar} isAnimationActive={false} />
|
||||
))}
|
||||
</BarChart>
|
||||
</ChartContainer>
|
||||
</div>
|
||||
|
||||
<div className="text-sm text-foreground-lighter">
|
||||
<p>This timeline chart displays page load metrics in milliseconds.</p>
|
||||
<p>Click on a bar to see detailed information.</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TimelineExample
|
||||
@@ -0,0 +1,28 @@
|
||||
export { default as BarChart } from './BarChart'
|
||||
// LineChart missing, commenting out until it's implemented
|
||||
// export { default as LineChart } from './LineChart'
|
||||
export { default as AreaChart } from './AreaChart'
|
||||
export { default as StackedBarChart } from './StackedBarChart'
|
||||
export { default as ComposedChart } from './ComposedChart'
|
||||
export { default as CandlestickChart } from './CandlestickChart'
|
||||
export { default as TimelineChart } from './TimelineChart'
|
||||
export { default as ChartHeader } from './ChartHeader'
|
||||
|
||||
// Fix wildcard exports by exporting specific items
|
||||
// From Charts.types
|
||||
export type { Datum, CommonChartProps } from './Charts.types'
|
||||
|
||||
// From Charts.utils
|
||||
export {
|
||||
numberFormatter,
|
||||
useChartSize,
|
||||
timestampFormatter,
|
||||
precisionFormatter,
|
||||
} from './Charts.utils'
|
||||
|
||||
// From Charts.constants
|
||||
export { CHART_COLORS, DateTimeFormats, DEFAULT_STACK_COLORS } from './Charts.constants'
|
||||
|
||||
// From useChartHighlight
|
||||
export { useChartHighlight } from './useChartHighlight'
|
||||
export type { ChartHighlight } from './useChartHighlight'
|
||||
@@ -0,0 +1,15 @@
|
||||
import CandlestickChartExample from 'components/ui/Charts/CandlestickChartExample'
|
||||
import { NextPage } from 'next'
|
||||
|
||||
const CandlestickDemoPage: NextPage = () => {
|
||||
return (
|
||||
<div className="p-4">
|
||||
<h1 className="text-2xl font-bold mb-8">Candlestick Chart Demo</h1>
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<CandlestickChartExample />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CandlestickDemoPage
|
||||
@@ -0,0 +1,15 @@
|
||||
import TimelineChartExample from 'components/ui/Charts/TimelineChartExample'
|
||||
import { NextPage } from 'next'
|
||||
|
||||
const TimelineDemoPage: NextPage = () => {
|
||||
return (
|
||||
<div className="p-4">
|
||||
<h1 className="text-2xl font-bold mb-8">Timeline Chart Demo</h1>
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<TimelineChartExample />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TimelineDemoPage
|
||||
@@ -0,0 +1,15 @@
|
||||
import TimelineExample from 'components/ui/Charts/TimelineExample'
|
||||
import { NextPage } from 'next'
|
||||
|
||||
const TimelineExamplePage: NextPage = () => {
|
||||
return (
|
||||
<div className="p-4">
|
||||
<h1 className="text-2xl font-bold mb-8">Timeline Example</h1>
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<TimelineExample />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TimelineExamplePage
|
||||
Reference in new issue
Block a user