Add untracked chart and schema files
This commit is contained in:
45
components/charts/primitives/chart-container.tsx
Normal file
45
components/charts/primitives/chart-container.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
type ChartContainerProps = {
|
||||
children: React.ReactNode;
|
||||
height?: number;
|
||||
loading?: boolean;
|
||||
error?: string | null;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export function ChartContainer({
|
||||
children,
|
||||
height = 400,
|
||||
loading = false,
|
||||
error = null,
|
||||
className
|
||||
}: ChartContainerProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'relative w-full rounded-xl border border-[color:var(--line-weak)] bg-[color:var(--panel-soft)]',
|
||||
className
|
||||
)}
|
||||
style={{ height: `${height}px` }}
|
||||
>
|
||||
{loading && (
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<div className="text-sm text-[color:var(--terminal-muted)]">Loading chart...</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<div className="text-sm text-[color:var(--danger)]">{error}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!loading && !error && (
|
||||
<div className="h-full w-full p-4">
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
76
components/charts/primitives/chart-toolbar.tsx
Normal file
76
components/charts/primitives/chart-toolbar.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
import { Download } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { ChartType, TimeRange } from '@/lib/types';
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
||||
type ChartToolbarProps = {
|
||||
chartType: ChartType;
|
||||
timeRange: TimeRange;
|
||||
onChartTypeChange: (type: ChartType) => void;
|
||||
onTimeRangeChange: (range: TimeRange) => void;
|
||||
onExport: () => void;
|
||||
};
|
||||
|
||||
const TIME_RANGES: TimeRange[] = ['1W', '1M', '3M', '1Y', '3Y', '5Y', '10Y', '20Y'];
|
||||
const CHART_TYPES: { value: ChartType; label: string }[] = [
|
||||
{ value: 'line', label: 'Line' },
|
||||
{ value: 'combination', label: 'Compare' }
|
||||
];
|
||||
|
||||
export function ChartToolbar({
|
||||
chartType,
|
||||
timeRange,
|
||||
onChartTypeChange,
|
||||
onTimeRangeChange,
|
||||
onExport
|
||||
}: ChartToolbarProps) {
|
||||
return (
|
||||
<div className="mb-4 flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{TIME_RANGES.map((range) => (
|
||||
<button
|
||||
key={range}
|
||||
type="button"
|
||||
onClick={() => onTimeRangeChange(range)}
|
||||
className={cn(
|
||||
'min-h-9 rounded-lg px-3 py-1.5 text-xs font-medium transition-colors',
|
||||
timeRange === range
|
||||
? 'bg-[color:var(--accent)] text-[#16181c]'
|
||||
: 'bg-[color:var(--panel-soft)] text-[color:var(--terminal-muted)] hover:bg-[color:var(--panel-bright)] hover:text-[color:var(--terminal-bright)]'
|
||||
)}
|
||||
>
|
||||
{range}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex gap-1">
|
||||
{CHART_TYPES.map((type) => (
|
||||
<button
|
||||
key={type.value}
|
||||
type="button"
|
||||
onClick={() => onChartTypeChange(type.value)}
|
||||
className={cn(
|
||||
'rounded-lg px-3 py-1.5 text-xs font-medium transition-colors',
|
||||
chartType === type.value
|
||||
? 'bg-[color:var(--accent)] text-[#16181c]'
|
||||
: 'bg-[color:var(--panel-soft)] text-[color:var(--terminal-muted)] hover:bg-[color:var(--panel-bright)] hover:text-[color:var(--terminal-bright)]'
|
||||
)}
|
||||
>
|
||||
{type.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={onExport}
|
||||
className="min-h-9 gap-1.5 px-2.5 py-1.5 text-xs"
|
||||
>
|
||||
<Download className="h-3.5 w-3.5" />
|
||||
Export
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
79
components/charts/primitives/chart-tooltip.tsx
Normal file
79
components/charts/primitives/chart-tooltip.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import type { TooltipContentProps, TooltipPayloadEntry } from 'recharts';
|
||||
import { formatCurrency, formatCompactCurrency } from '@/lib/format';
|
||||
import { getChartColors } from '../utils/chart-colors';
|
||||
import { isOHLCVData } from '../utils/chart-data-transformers';
|
||||
|
||||
type ChartTooltipProps = TooltipContentProps & {
|
||||
formatters?: {
|
||||
price?: (value: number) => string;
|
||||
date?: (value: string) => string;
|
||||
volume?: (value: number) => string;
|
||||
};
|
||||
};
|
||||
|
||||
export function ChartTooltip(props: ChartTooltipProps) {
|
||||
const { active, payload, label, formatters } = props;
|
||||
|
||||
if (!active || !payload || payload.length === 0) return null;
|
||||
|
||||
const colors = getChartColors();
|
||||
const formatDate = formatters?.date || ((date: string) => new Date(date).toLocaleDateString());
|
||||
const formatPrice = formatters?.price || formatCurrency;
|
||||
const formatVolume = formatters?.volume || formatCompactCurrency;
|
||||
|
||||
const data = payload[0].payload;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="min-w-[180px] rounded-xl border p-3 backdrop-blur-sm"
|
||||
style={{
|
||||
backgroundColor: colors.tooltipBg,
|
||||
borderColor: colors.tooltipBorder
|
||||
}}
|
||||
>
|
||||
<div className="mb-2 text-xs uppercase tracking-wider" style={{ color: colors.muted }}>
|
||||
{formatDate(label as string)}
|
||||
</div>
|
||||
|
||||
{isOHLCVData(data) ? (
|
||||
<div className="space-y-1.5">
|
||||
<TooltipRow label="Open" value={formatPrice(data.open)} color={colors.text} />
|
||||
<TooltipRow label="High" value={formatPrice(data.high)} color={colors.positive} />
|
||||
<TooltipRow label="Low" value={formatPrice(data.low)} color={colors.negative} />
|
||||
<TooltipRow label="Close" value={formatPrice(data.close)} color={colors.text} />
|
||||
<div className="mt-2 border-t pt-2" style={{ borderColor: colors.tooltipBorder }}>
|
||||
<TooltipRow label="Volume" value={formatVolume(data.volume)} color={colors.muted} />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-1.5">
|
||||
{payload.map((entry: TooltipPayloadEntry, index: number) => (
|
||||
<TooltipRow
|
||||
key={index}
|
||||
label={String(entry.name || 'Value')}
|
||||
value={formatPrice(entry.value as number)}
|
||||
color={entry.color || colors.text}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type TooltipRowProps = {
|
||||
label: string;
|
||||
value: string;
|
||||
color: string;
|
||||
};
|
||||
|
||||
function TooltipRow({ label, value, color }: TooltipRowProps) {
|
||||
return (
|
||||
<div className="flex items-center justify-between gap-4 text-xs">
|
||||
<span style={{ color: color }}>{label}:</span>
|
||||
<span className="font-mono font-medium" style={{ color: color }}>
|
||||
{value}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
58
components/charts/primitives/volume-indicator.tsx
Normal file
58
components/charts/primitives/volume-indicator.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import { BarChart, Bar, YAxis, Tooltip, ResponsiveContainer } from 'recharts';
|
||||
import type { OHLCVDataPoint } from '@/lib/types';
|
||||
import { getChartColors } from '../utils/chart-colors';
|
||||
import { formatCompactCurrency } from '@/lib/format';
|
||||
|
||||
type VolumeIndicatorProps = {
|
||||
data: OHLCVDataPoint[];
|
||||
height?: number;
|
||||
formatters?: {
|
||||
volume?: (value: number) => string;
|
||||
};
|
||||
};
|
||||
|
||||
export function VolumeIndicator({
|
||||
data,
|
||||
height = 80,
|
||||
formatters
|
||||
}: VolumeIndicatorProps) {
|
||||
const colors = getChartColors();
|
||||
if (data.length === 0) return null;
|
||||
|
||||
const formatVolume = formatters?.volume || formatCompactCurrency;
|
||||
|
||||
return (
|
||||
<div style={{ height: `${height}px`, width: '100%' }} className="mt-2">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<BarChart data={data}>
|
||||
<YAxis
|
||||
orientation="right"
|
||||
tickFormatter={formatVolume}
|
||||
stroke={colors.muted}
|
||||
fontSize={10}
|
||||
width={60}
|
||||
/>
|
||||
<Tooltip
|
||||
formatter={(value) => [
|
||||
formatVolume(typeof value === 'number' ? value : Number(value ?? 0)),
|
||||
'Volume'
|
||||
]}
|
||||
labelFormatter={(label) => `Date: ${label}`}
|
||||
contentStyle={{
|
||||
backgroundColor: colors.tooltipBg,
|
||||
border: `1px solid ${colors.tooltipBorder}`,
|
||||
borderRadius: '0.75rem',
|
||||
fontSize: '0.75rem'
|
||||
}}
|
||||
/>
|
||||
<Bar
|
||||
dataKey="volume"
|
||||
fill={colors.volume}
|
||||
opacity={0.3}
|
||||
isAnimationActive={data.length <= 500}
|
||||
/>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user