imeeting/frontend/src/components/shared/StatCard/StatCard.tsx

82 lines
2.1 KiB
TypeScript
Raw Normal View History

import { ArrowUpOutlined, ArrowDownOutlined } from '@ant-design/icons'
import './StatCard.css'
import { ReactNode } from 'react'
export interface StatCardProps {
title: string
value: number | string
icon?: ReactNode
color?: string
trend?: { value: number; direction: 'up' | 'down' }
suffix?: string
layout?: 'column' | 'row'
gridColumn?: string
className?: string
onClick?: () => void
style?: React.CSSProperties
}
/**
*
*/
function StatCard({
title,
value,
icon,
color = 'blue',
trend,
suffix = '',
layout = 'column',
gridColumn,
className = '',
onClick,
style: customStyle = {},
}: StatCardProps) {
const colorMap: Record<string, string> = {
blue: '#1677ff',
green: '#52c41a',
orange: '#faad14',
red: '#ff4d4f',
purple: '#722ed1',
gray: '#8c8c8c',
}
const themeColor = colorMap[color] || color
const style = {
...(gridColumn ? { gridColumn } : {}),
...customStyle,
}
return (
<div className={`stat-card stat-card-${layout} ${className}`} style={style} onClick={onClick}>
<div className="stat-card-header">
<span className="stat-card-title">{title}</span>
{icon && (
<span className="stat-card-icon" style={{ color: themeColor }} aria-hidden="true">
{icon}
</span>
)}
</div>
<div className="stat-card-body">
<div className="stat-card-value tabular-nums" style={{ color: themeColor }}>
{value}
{suffix && <span className="stat-card-suffix">{suffix}</span>}
</div>
{trend && (
<div
className={`stat-card-trend ${trend.direction === 'up' ? 'trend-up' : 'trend-down'} tabular-nums`}
aria-label={`${trend.direction === 'up' ? 'Increase' : 'Decrease'} of ${trend.value}%`}
>
{trend.direction === 'up' ? <ArrowUpOutlined aria-hidden="true" /> : <ArrowDownOutlined aria-hidden="true" />}
<span>{Math.abs(trend.value)}%</span>
</div>
)}
</div>
</div>
)
}
export default StatCard