KPI Cards
Variance KPI Code
const { IconWrapper, SVGIcons } = $bluecopa.icons
const { Card, AreaChart, ProgressBar, TooltipProps } = $bluecopa.components;
const { cx, formatNumber } = $bluecopa.utils;
export const KPI = (props: {
title: string;
value: string;
icon: any
variance: string;
direction: "positive" | "negative";
directionIcon: any;
}) => {
const { title, value, icon, variance, direction, directionIcon } = props;
return (
<div
className={
"h-full cursor-pointer rounded-xl bg-white border-slate-200 items-center hover:ring-2 ring-slate-100 transition-all duration-500 border " +
props.className
}
>
<div className={"space-y-3 h-full px-6 py-4"}>
<div className="flex space-x-2 items-center text-sm text-slate-400">
{props.icon && <div className="bg-primary-100 text-primary rounded-md p-1">
<IconWrapper icon={props.icon} width={"16"} height={"16"} />
</div>}
<p className="text-sm">{title}</p>
</div>
<div
className={
"flex space-x-2 text-sm items-center " +
(direction === "positive"
? "text-lime-600"
: "text-rose-600")
}
>
<div className="text-lg font-bold">{value}</div>
{props.directionIcon && <IconWrapper icon={props.directionIcon} size="sm" />}
<div className="text-sm font-bold">{variance}</div>
</div>
</div>
</div>
);
};
export const App = (props) => {
const sampleData = {
online_order_value: {
changePercent: 5.2,
trend: "positive"
}
};
const data = props.data && Object.keys(props.data).length ? props.data : sampleData;
const online_order_value = data?.online_order_value || {};
const { title = "Untitled", value = 0 } = online_order_value;
function Example() {
return (
<div className="p-0.5 h-full">
<KPI
title="Online Orders Count"
value="379.98M"
icon={SVGIcons.SquareChecked}
variance="5%"
direction="positive"
directionIcon={SVGIcons.ArrowUp}
/>
</div>
);
}
return <Example />;
};
Variance KPI Component
Description
A compact KPI card showing a primary metric with a variance indicator. Ideal for displaying performance metrics with clear positive/negative indicators.
How to Use
<VarianceKPI title="Order Count" value="379.98M" icon={iconElement} variance="5%" direction="positive" />Import the component from the KPI component library.
Props
| Prop Name | Type | Required | Description |
|---|---|---|---|
| title | string | Yes | The title of the KPI card |
| value | string | Yes | The main value to display (can include formatting) |
| icon | SVG/Element | No | An icon to display next to the title |
| variance | string | No | The variance value (e.g., "5%") |
| direction | string | No | "positive" or "negative" to indicate trend direction |
Best Practices
- Use for key metrics where variance from a baseline is important
- Keep titles concise to prevent text wrapping
- Consider using with complementary KPIs for more context
Interactive Properties
Preview with Custom Props
Online Orders Value
Simple KPI Code
const { SparkBarChart } = $bluecopa.components;
export const ROUNDED_STYLE = 'rounded-xl ';
export const BORDER_STYLE = 'border-slate-200 ';
export function SimpleKpi({
label = 'Label',
value = '0',
changePercent = '0%',
changeDirection = 'up',
className = '',
}) {
const chartdata = [
{ month: 'Jan 23', Performance: 4000, Benchmark: 3000 },
{ month: 'Feb 23', Performance: 3000, Benchmark: 2000 },
{ month: 'Mar 23', Performance: 2000, Benchmark: 1700 },
{ month: 'Apr 23', Performance: 2780, Benchmark: 2500 },
{ month: 'May 23', Performance: 1890, Benchmark: 1890 },
{ month: 'Jun 23', Performance: 2390, Benchmark: 2000 },
{ month: 'Jul 23', Performance: 3490, Benchmark: 3000 },
];
const isUp = changeDirection === 'up';
return (
<div
className={
ROUNDED_STYLE +
BORDER_STYLE +
"flex justify-between px-4 py-2 h-full cursor-pointer bg-white items-center hover:ring-2 ring-slate-100 transition-all duration-500 border " +
className
}
>
{/* Left side */}
<div>
<h2 className="text-sm">{label}</h2>
<div className="flex justify-between items-center mt-2">
<div className="flex flex-col">
<span className="text-lg font-bold text-gray-800">{value}</span>
<div className="flex items-center mt-1">
<span className="text-gray-500 text-sm">vs last month</span>
<div
className={
'ml-2 flex items-center px-2 py-0.5 rounded-full ' +
(isUp ? 'bg-green-100' : 'bg-red-100')
}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={isUp ? 'text-green-500' : 'text-red-500'}
>
<polyline
points={isUp ? '18 15 12 9 6 15' : '6 9 12 15 18 9'}
/>
</svg>
<span
className={
'text-sm font-medium ' +
(isUp ? 'text-green-500' : 'text-red-500')
}
>
{changePercent}
</span>
</div>
</div>
</div>
</div>
</div>
{/* Right side chart */}
<div className="bg-slate-100 h-full p-2 rounded-md flex items-center">
<SparkBarChart
data={chartdata}
index="month"
categories={['Performance', 'Benchmark']}
color={['blue']}
/>
</div>
</div>
);
}
export function App(props) {
// sample fallback props
const sample = {
label: 'Online Orders Value',
value: '50,074.00',
changePercent: '12%',
changeDirection: 'up',
className: 'max-w-xs w-full',
};
// use real data if available, otherwise sample
const {
label,
value,
changePercent,
changeDirection,
className,
} = (props.data && props.data.online_order_count) || sample;
return (
<div className="p-0.5 h-full">
<SimpleKpi
label={label}
value={value}
changePercent={changePercent}
changeDirection={changeDirection}
className={className}
/>
</div>
);
}
Simple KPI Component
Description
A clean, minimal KPI card that displays a key metric with comparison to a previous period. Includes a bar chart visualization for showing performance trends over time.
How to Use
<SimpleKpi label="Online Orders Value" value="50,074.00" changePercent="12%" changeDirection="up" className="max-w-xs w-full" />
Import the SimpleKpi component and pass the appropriate props.
Props
| Prop Name | Type | Required | Description |
|---|---|---|---|
| label | string | Yes | The label or title of the metric |
| value | string | Yes | The main value to display (can include formatting) |
| changePercent | string | No | The percentage change from previous period |
| changeDirection | string | No | "up" or "down" to indicate trend direction |
| className | string | No | Additional CSS classes to apply to the component |
Best Practices
- Use when you want to display a clean comparison between current and previous periods
- The bar chart helps visualize performance over time - best for sequential data
- Keep labels short and descriptive for a cleaner appearance
- Consider using with other SimpleKpi components for a dashboard display
Interactive Properties
Interactive properties for this component will be coming soon.
Preview with Custom Props
Online Orders Value
Total Amount
Spark Area KPI Code
// Grab whatever component you need from Bluecopa
const { SparkAreaChart } = $bluecopa.components;
// Your style constants
export const ROUNDED_STYLE = 'rounded-xl ';
export const BORDER_STYLE = 'border-slate-200 ';
// The KPI + spark-area chart component
export function SparkAreaKPI({
label = 'Total Amount',
value = '$32,029',
chartData,
className = '',
}) {
// sample data to use if nothing is passed in
const sampleData = [
{ month: 'Jan 21', Performance: 4000 },
{ month: 'Feb 21', Performance: 3000 },
{ month: 'Mar 21', Performance: 2000 },
{ month: 'Apr 21', Performance: 2780 },
{ month: 'May 21', Performance: 1890 },
{ month: 'Jun 21', Performance: 2390 },
{ month: 'Jul 21', Performance: 3490 },
];
const dataToRender = Array.isArray(chartData) ? chartData : sampleData;
return (
<div
className={
ROUNDED_STYLE +
BORDER_STYLE +
'grid grid-cols-[1fr,auto] px-6 py-4 h-full cursor-pointer bg-white items-center hover:ring-2 ring-slate-100 transition-all duration-500 border ' +
className
}
>
{/* Left side: label & value */}
<div>
<h2 className="text-sm">{label}</h2>
<div className="flex justify-between items-center mt-2">
<div className="flex flex-col">
<span className="text-lg font-bold text-gray-800">{value}</span>
<div className="text-gray-500 text-sm mt-1">vs last month</div>
</div>
</div>
</div>
{/* Right side: spark area */}
<div className="bg-slate-100 h-full p-2 rounded-md flex items-center">
<SparkAreaChart
data={dataToRender}
index="month"
categories={['Performance']}
fill="solid"
colors={['blue']}
className="h-12 w-24 sm:h-10 sm:w-36"
/>
</div>
</div>
);
}
// The App wrapper: picks either real props.data or sample props
export function App(props) {
// define what the real data shape would look like
// e.g. props.data.sparkAreaKpi = { label, value, chartData, className }
const sampleProps = {
label: 'Online Orders Value',
value: '$50,074.00',
chartData: [
{ month: 'Jan 23', Performance: 4000 },
{ month: 'Feb 23', Performance: 3000 },
{ month: 'Mar 23', Performance: 2000 },
{ month: 'Apr 23', Performance: 2780 },
{ month: 'May 23', Performance: 1890 },
{ month: 'Jun 23', Performance: 2390 },
{ month: 'Jul 23', Performance: 3490 },
],
className: 'max-w-xs w-full',
};
// use real incoming props.data.sparkAreaKpi if present, otherwise sampleProps
const sparkProps =
props.data && props.data.sparkAreaKpi
? props.data.sparkAreaKpi
: sampleProps;
return (
<div className="p-0.5 h-full">
<SparkAreaKPI {...sparkProps} />
</div>
);
}
Spark Area KPI Component
Description
This KPI card combines a key metric with a sparkline area chart to show trends over time. Perfect for visualizing performance trends in a compact format.
How to Use
<SparkAreaKPI label="Total Amount" value="$32,029" changePercent="8%" changeDirection="up" />
Import the SparkAreaKPI component from the KPI library.
Props
| Prop Name | Type | Required | Description |
|---|---|---|---|
| label | string | Yes | The label describing the metric |
| value | string | Yes | The main metric value to display |
| changePercent | string | No | The percentage change compared to previous period |
| changeDirection | string | No | "up" or "down" to indicate trend direction |
| className | string | No | Additional CSS classes for the component |
Best Practices
- The left side shows the main value and comparison to previous period
- The right side displays a compact area chart showing the trend
- Color coding (green/red) indicates positive or negative change
- Use when trend visualization is as important as the current value
- The component uses Tremor's SparkAreaChart with fillType="solid" for better visibility
Interactive Properties
Interactive properties for this component will be coming soon.
Preview with Custom Props
Total Amount
Monthly Profit Overview Code
function MonthlyProfitOverview({
title,
value,
trend,
startDate,
endDate,
chartData,
lineColor,
fillColor,
className,
}) {
const width = 800;
const height = 200;
const padding = 20;
const maxValue = Math.max(...chartData.map((d) => d.value));
const createChartPath = () => {
const points = chartData.map(function (d) {
const x = (d.day / 30) * (width - padding * 2) + padding;
const y = height - (d.value / maxValue) * (height - padding * 2) - padding;
return x + "," + y;
});
return "M " + points.join(" L ");
};
const trendColorClasses =
trend >= 0 ? "bg-green-100 text-green-900" : "bg-red-100 text-red-900";
return (
<div className={className}>
<div className="flex items-center mb-2">
<div className="flex items-center text-black">
<svg
className="w-3 h-3 mr-2"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M3 8L7 4M7 4L11 8M7 4V20M14 16L18 20M18 20L22 16M18 20V4"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
<span className="text-xs text-black font-medium">{title}</span>
</div>
</div>
<div className="flex justify-between items-center mb-1">
<div className="text-sm font-medium text-black">{value}</div>
<div className={"px-1 py-0.5 rounded flex items-center " + trendColorClasses}>
<span className="text-xs font-bold">
{(trend >= 0 ? "+" : "") + trend + "%"}
</span>
</div>
</div>
<div className="relative h-24 mt-0">
<svg
width="100%"
height="100%"
viewBox={"0 0 " + width + " " + height}
preserveAspectRatio="none"
>
<defs>
<linearGradient id="areaGradient" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stopColor={fillColor} stopOpacity="0.5" />
<stop offset="100%" stopColor={fillColor} stopOpacity="0" />
</linearGradient>
</defs>
<path
d={
createChartPath() +
" L " +
(width - padding) +
"," +
(height - padding) +
" L " +
padding +
"," +
(height - padding) +
" Z"
}
fill="url(#areaGradient)"
stroke="none"
/>
<path
d={createChartPath()}
fill="none"
stroke={lineColor}
strokeWidth="3"
/>
</svg>
<div className="absolute bottom-0 left-0 text-gray-400">{startDate}</div>
<div className="absolute bottom-0 right-0 text-gray-400">{endDate}</div>
</div>
</div>
);
}
export const App = (props) => {
return (
<MonthlyProfitOverview
title="Q1 Earnings"
value="$892,432"
trend={-5.2}
startDate="1 Jan"
endDate="31 Mar"
chartData={[
{ day: 1, value: 350000 },
{ day: 10, value: 420000 },
{ day: 20, value: 380000 },
{ day: 30, value: 310000 },
]}
lineColor="#3B82F6"
fillColor="#3B82F6"
className="bg-white shadow-lg p-6 rounded-xl w-full max-w-md"
/>
);
};
Monthly Profit Overview Component
Description
A detailed profit overview card that visualizes monthly profit data with a line chart. Perfect for financial dashboards and performance reports. The component includes trend indicators and date range labels for better context.
How to Use
<MonthlyProfitOverview
title="Q1 Earnings"
value="$892,432"
trend={-5.2}
startDate="1 Jan"
endDate="31 Mar"
chartData={[
{ day: 1, value: 350000 },
{ day: 10, value: 420000 },
{ day: 20, value: 380000 },
{ day: 30, value: 310000 }
]}
lineColor="#3B82F6"
fillColor="#3B82F6"
className="bg-white shadow-lg p-6 rounded-xl w-full max-w-md"
/>All props are customizable, allowing you to adapt the component to different datasets, time periods, and visual styles.
Props Reference
| Prop Name | Type | Required | Description |
|---|---|---|---|
| title | string | Yes | Title displayed at the top of the component |
| value | string | Yes | The main KPI value to display |
| trend | number | Yes | Percentage change (positive or negative) |
| chartData | array | Yes | Array of { day: number, value: number } objects for the chart |
| startDate | string | Yes | Label for the starting date |
| endDate | string | Yes | Label for the ending date |
| lineColor | string | No | Color for the chart line (hex code) |
| fillColor | string | No | Color for the chart area fill (hex code) |
| className | string | No | CSS classes for container styling |
Features
- Line chart showing profit trends over time
- Dynamic styling based on positive/negative trends
- Customizable colors and appearance
- Date range indicators for context
- Clean, minimal design for executive dashboards
Implementation Tips
- Format your data as day/value pairs for consistency
- Use contrasting colors for better visibility (e.g., blue for line, light blue for fill)
- Match colors to your application's theme
- Consider adding interactive tooltips for detailed values
- For dense data sets, consider using a date filter or zooming capabilities
Interactive Properties
Preview with Custom Props
Financial Overview
Total Amount
KPI Dashboard Code
// grab echarts
const echarts = $bluecopa.echarts;
// style constants
const ROUNDED_STYLE = 'rounded-xl ';
const BORDER_STYLE = 'border-slate-200 ';
export function KPICard({
// props with defaults
chartData,
totalCount = 1250,
totalAmount = 85000,
className = '',
}) {
// sample multi-year data
const sampleChartData = [
{ day: 1, value: 190000, year: 2023 },
{ day: 3, value: 120000, year: 2023 },
{ day: 5, value: 250000, year: 2023 },
{ day: 7, value: 180000, year: 2022 },
{ day: 9, value: 300000, year: 2022 },
{ day: 11, value: 380000, year: 2022 },
{ day: 13, value: 420000, year: 2021 },
{ day: 15, value: 380000, year: 2021 },
// …more entries across different years…
];
// use passed data or fallback
const rawData = Array.isArray(chartData) ? chartData : sampleChartData;
// build year filter options from the data itself
const yearOptions = Array.from(
new Set(rawData.map((d) => d.year))
).sort();
const [selectedYear, setSelectedYear] = React.useState(
yearOptions[0] || new Date().getFullYear()
);
// filter for the chart
const filteredData = rawData.filter((d) => d.year === selectedYear);
// format numbers
const fmt = (n) => new Intl.NumberFormat('en-US').format(n);
return (
<div className={'p-2 max-w-md w-full ' + className}>
<div className="bg-white rounded-xl border shadow-sm p-3">
{/* Header + Year Picker */}
<div className="flex justify-between items-center mb-4">
<h2 className="text-gray-700 font-medium">KPI Dashboard</h2>
<select
className="appearance-none bg-white border border-gray-200 rounded-lg px-3 py-1 text-sm font-medium text-gray-700 shadow-sm hover:border-blue-300 focus:outline-none focus:ring-2 focus:ring-blue-400 transition-colors"
value={selectedYear}
onChange={(e) => setSelectedYear(Number(e.target.value))}
>
{yearOptions.map((yr) => (
<option key={yr} value={yr}>
{yr}
</option>
))}
</select>
</div>
{/* Totals */}
<div className="grid grid-cols-2 gap-4 mb-5">
<div>
<div className="text-gray-500 text-sm">Total Count</div>
<div className="text-lg font-bold">{totalAmount}</div>
</div>
<div>
<div className="text-gray-500 text-sm">Total Amount</div>
<div className="text-lg font-bold">{totalAmount}</div>
</div>
</div>
{/* Line Chart */}
<div className="h-64 overflow-hidden">
<LineAChart data={filteredData} />
</div>
</div>
</div>
);
}
function LineAChart({ data }) {
const chartRef = React.useRef(null);
React.useEffect(() => {
const chart = echarts.init(chartRef.current);
const option = {
tooltip: {
trigger: 'axis',
formatter: '{b}: {c}',
axisPointer: { type: 'line' },
},
grid: {
left: '3%', right: '3%', bottom: '15%', top: '10%', containLabel: true,
},
xAxis: {
type: 'category',
boundaryGap: false,
data: data.map((d) => d.day),
axisLabel: { interval: 0, rotate: 30, fontSize: 10, margin: 8 },
axisTick: { alignWithLabel: true },
},
yAxis: {
type: 'value',
axisLine: { show: false },
axisTick: { show: false },
splitLine: { show: true, lineStyle: { type: 'dashed' } },
},
series: [
{
type: 'line',
data: data.map((d) => d.value),
smooth: true,
lineStyle: { width: 3, color: '#f43f5e' },
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(244,63,94,0.8)' },
{ offset: 0.5, color: 'rgba(244,63,94,0.3)' },
{ offset: 1, color: 'rgba(244,63,94,0.05)' },
]),
},
showSymbol: true,
symbolSize: 8,
},
],
};
chart.setOption(option);
window.addEventListener('resize', chart.resize);
return () => {
window.removeEventListener('resize', chart.resize);
chart.dispose();
};
}, [data]);
return <div ref={chartRef} style={{ width: '100%', height: '100%' }} />;
}
// App wrapper — pass in real data or let KPICard use its samples
export function App(props) {
// expecting data:{ chartData: [...], totalCount: x, totalAmount: y }
const sample = {
chartData: [
{ day: 1, value: 190000, year: 2023 },
{ day: 3, value: 120000, year: 2023 },
{ day: 5, value: 250000, year: 2023 },
// …
],
totalCount: 1250,
totalAmount: 85000,
};
const { chartData, totalCount, totalAmount } = Object.values(props.data).flat() || sample;
return (
<div className="p-0.5 h-full">
<KPICard
chartData={chartData}
totalCount={totalCount}
totalAmount={totalAmount}
className="max-w-md"
/>
</div>
);
}
KPI Dashboard Component
Description
A comprehensive KPI dashboard with year filter control and a line chart visualization. Perfect for displaying financial trends with interactive filtering capabilities.
How to Use
<KPICard
title="Financial Overview"
totalAmount={92500}
selectedYear={2025}
yearOptions={[2023, 2024, 2025, 2026, 2027]}
chartData={[
{ day: 1, value: 250000 },
{ day: 5, value: 320000 },
{ day: 10, value: 290000 },
{ day: 15, value: 380000 },
{ day: 20, value: 350000 },
{ day: 25, value: 450000 },
{ day: 30, value: 420000 }
]}
className="bg-white rounded-xl border shadow-md p-4"
onYearChange={(year) => console.log('Year changed to:', year)}
/>The component includes a built-in year selector for filtering data. In real applications, you might want to modify it to fetch actual data based on the selected year.
Props Reference
| Prop Name | Type | Required | Description |
|---|---|---|---|
| title | string | No | Title displayed at the top of the dashboard |
| totalAmount | number | No | The main KPI value to display |
| selectedYear | number | No | Initial selected year in the dropdown |
| yearOptions | number[] | No | Array of years to display in the dropdown |
| chartData | array | No | Array of { day: number, value: number } objects for the chart |
| onYearChange | function | No | Callback when year selection changes |
| className | string | No | CSS classes for container styling |
Features
- Year filter dropdown for date range selection
- Responsive line chart with gradient fill
- Formatted currency values
- Tooltip showing daily values
- Clean, professional dashboard layout
Implementation Tips
- Connect the component to your actual data source
- Modify the chart options to match your specific requirements
- Consider adding more filter options (month, quarter, etc.)
- The chart uses ECharts, which offers extensive customization options
Interactive Properties
Interactive properties for this component will be coming soon.
Preview with Custom Props
Financial Overview
Total Amount
Cash Flow
+0.3%KPI Card Code
const echarts = $bluecopa.echarts;
export const App = () => {
return <KPICard />
}
const KPICard = () => {
const chartRef = React.useRef(null)
React.useEffect(() => {
if (!chartRef.current) return
const chart = echarts.init(chartRef.current, null, { renderer: 'svg' })
const dates = [
'Nov 1', 'Nov 5', 'Nov 9', 'Nov 13', 'Nov 17',
'Nov 21', 'Nov 25', 'Nov 29', 'Dec 3', 'Dec 7',
'Dec 11', 'Dec 15', 'Dec 19', 'Dec 23', 'Dec 27',
'Dec 31', 'Jan 4', 'Jan 8', 'Jan 12', 'Jan 16',
'Jan 20', 'Jan 24', 'Jan 28', 'Feb 1', 'Today'
]
const option = {
tooltip: {
show: true,
trigger: 'axis',
axisPointer: { // use shadow pointer
type: 'shadow'
},
formatter: function(params) {
return params
.map(function(p) {
return (
"<div>" +
"<span style='display:inline-block;margin-right:5px;" +
"width:10px;height:10px;background-color:" + p.color + ";'></span>" +
p.seriesName + ": $" + p.data.toFixed(2) +
"</div>"
);
})
.join('');
}
},
grid: { left: 40, right: 20, top: 20, bottom: 40 },
xAxis: {
type: 'category',
name: 'Date',
nameLocation: 'middle',
nameGap: 30,
data: dates,
axisLine: { show: true, lineStyle: { color: '#E5E7EB' } },
axisTick: { show: false },
axisLabel: { show: true, color: '#6B7280', rotate: 45 },
},
yAxis: {
type: 'value',
name: 'Amount',
nameLocation: 'middle',
nameGap: 50,
nameRotate: 90,
axisLine: { show: true, lineStyle: { color: '#E5E7EB' } },
axisTick: { show: false },
axisLabel: { show: true, color: '#6B7280' },
splitLine: { show: false },
},
series: [
{
name: 'Outflow',
type: 'bar',
stack: 'total',
data: dates.map(() => -Math.random() * 5 - 1),
barWidth: '70%',
itemStyle: { color: '#7F3DFF' },
},
{
name: 'Inflow',
type: 'bar',
stack: 'total',
data: dates.map(() => Math.random() * 8 + 2),
itemStyle: { color: '#4F9AFF' },
},
{
name: 'Extra',
type: 'bar',
stack: 'total',
data: dates.map(() => Math.random() * 4 + 1),
itemStyle: { color: '#A3D8FF' },
},
],
}
chart.setOption(option)
const resizeObserver = new ResizeObserver(() => chart.resize())
resizeObserver.observe(chartRef.current)
return () => {
resizeObserver.disconnect()
chart.dispose()
}
}, [])
return (
<div className="bg-white rounded-xl shadow p-5 max-w-md">
<div className="flex justify-between items-center mb-3">
<h3 className="text-gray-600 font-medium">Cash Flow</h3>
<span className="text-green-500 font-semibold">+0.3%</span>
</div>
<div className="flex justify-between items-center mb-4">
<div>
<div className="text-2xl font-bold">$3,178.00</div>
<div className="text-sm text-gray-400">1 Feb – Today</div>
</div>
<div className="text-right">
<div className="text-2xl font-bold ">$3,045.00</div>
<div className="text-sm text-gray-400">1 Jan – 31 Jan</div>
</div>
</div>
<div ref={chartRef} className="h-36 w-full" />
</div>
)
}
export default App;KPI Card Component
Description
A flexible KPI card component designed for displaying key performance metrics with a year selector and interactive line chart. Ideal for financial dashboards and performance tracking.
How to Use
<KPICard
title="Online Order Value"
totalAmount={1234567.89}
selectedYear={2023}
yearOptions={[2021, 2022, 2023, 2024, 2025]}
chartData={[
{ day: 1, value: 190000 },
{ day: 15, value: 380000 },
{ day: 30, value: 420000 },
]}
onYearChange={(year) => handleYearChange(year)}
className="bg-white rounded-xl border shadow-sm p-3"
/>Props Reference
| Prop Name | Type | Required | Description |
|---|---|---|---|
| title | string | No | Title of the KPI card (default: "KPI Dashboard") |
| totalAmount | number | No | The primary value to display (default: 85000) |
| selectedYear | number | No | Initially selected year (default: 2023) |
| yearOptions | array | No | Array of years to show in dropdown (default: last 10 years) |
| chartData | array | No | Array of {day, value} objects for the line chart |
| onYearChange | function | No | Callback when year selection changes |
| className | string | No | Custom CSS classes for styling the card |
Features
- Year selector with dropdown for historical data
- Currency formatting for financial data
- Interactive line chart visualization
- Fully customizable through props
- Responsive design that works in various layouts
Interactive Properties
Preview with Custom Props
Cash Flow
+0.3%Cash Report
Sanky KPI Card Code
// ← make sure React is imported
// grab your Bluecopa bits
const { IconWrapper, SVGIcons } = $bluecopa.icons;
const { Card } = $bluecopa.components;
const echarts = $bluecopa.echarts;
const SankyKpiCard = () => {
const legendItems = [
{ name: 'Approved', percentage: '24%', color: '#5368f5' },
{ name: 'Pending', percentage: '15%', color: '#d9b9f8' },
];
const formatCurrencyWithSmallDecimals = (amount) => {
const [dollars, cents] = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 2,
maximumFractionDigits: 2,
})
.format(amount)
.replace('$', '')
.split('.');
return (
<span>
{/* removed that stray apostrophe */}
<span className="text-3xl font-medium">{ '$' + dollars }</span>
<span className="text-xs font-medium align-bottom">.{cents}</span>
</span>
);
};
return (
<Card className="p-5 w-80">
<h3 className="text-gray-700 font-medium text-xs mb-4">Cash Report</h3>
<div className="flex justify-between items-center mb-6">
<div>
<div className="flex items-center">
<div className="text-gray-900 mr-3">
{formatCurrencyWithSmallDecimals(72858.8)}
</div>
<div className="bg-green-100 text-green-600 px-2 py-1.5 rounded-sm text-xs font-medium flex items-center">
<IconWrapper icon={SVGIcons.Add} width={6} height={6} />
12%
</div>
</div>
<div className="text-sm text-gray-500 mt-1">Add your description</div>
</div>
</div>
{/* use a valid Tailwind height so it renders */}
<div className="h-48 w-full flex items-center justify-center mb-4">
<SankeyChart />
</div>
<div>
<div className="grid grid-cols-2 mb-1 text-sm font-medium text-gray-400 pb-1">
<div>CATEGORY</div>
<div className="text-right">PERCENTAGE</div>
</div>
<div className="max-h-24 overflow-y-auto">
{legendItems.map((item, idx) => (
<div key={idx} className="grid grid-cols-2 text-sm py-1">
<div className="flex items-center text-gray-900">
<span
className="inline-block w-2 h-2 rounded-full mr-2"
style={{ backgroundColor: item.color }}
/>
{item.name}
</div>
<div className="text-right font-medium text-gray-500">
{item.percentage}
</div>
</div>
))}
</div>
</div>
</Card>
);
};
function SankeyChart() {
const chartRef = React.useRef(null);
React.useEffect(() => {
if (!chartRef.current) return;
const chart = echarts.init(chartRef.current);
const nodeValues = { A: 10, B: 6, C: 8, X: 7, Y: 7, Z: 7 };
chart.setOption({
tooltip: {
trigger: 'item',
formatter: function(p) {
if (p.dataType === 'node') {
return p.name + ': ' + (p.data.value || '');
} else {
return p.data.source + ' → ' + p.data.target + ': ' + p.data.value;
}
}
},
series: [{
type: 'sankey',
top: 5, bottom: 5, left: 5, right: 5,
nodeWidth: 10, nodeGap: 6, nodeAlign: 'justify',
emphasis: { focus: 'adjacency' },
data: [
{ name: 'A', value: nodeValues.A, itemStyle: { color: '#5368f5', borderRadius: 5 } },
{ name: 'B', value: nodeValues.B, itemStyle: { color: '#5368f5', borderRadius: 5 } },
{ name: 'C', value: nodeValues.C, itemStyle: { color: '#5368f5', borderRadius: 5 } },
{ name: 'X', value: nodeValues.X, itemStyle: { color: '#d9b9f8', borderRadius: 5 } },
{ name: 'Y', value: nodeValues.Y, itemStyle: { color: '#d9b9f8', borderRadius: 5 } },
{ name: 'Z', value: nodeValues.Z, itemStyle: { color: '#d9b9f8', borderRadius: 5 } },
],
links: [
{ source: 'A', target: 'X', value: 5, lineStyle: { color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [{ offset: 0, color: 'rgba(83,104,245,0.3)' }, { offset: 1, color: 'rgba(217,185,248,0.3)' }]), curveness: 0.7, width: 0.5 } },
{ source: 'A', target: 'Y', value: 3, lineStyle: { color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [{ offset: 0, color: 'rgba(83,104,245,0.3)' }, { offset: 1, color: 'rgba(217,185,248,0.3)' }]), curveness: 0.7, width: 0.5 } },
{ source: 'B', target: 'Y', value: 4, lineStyle: { color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [{ offset: 0, color: 'rgba(83,104,245,0.3)' }, { offset: 1, color: 'rgba(217,185,248,0.3)' }]), curveness: 0.7, width: 0.5 } },
{ source: 'B', target: 'Z', value: 3, lineStyle: { color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [{ offset: 0, color: 'rgba(83,104,245,0.3)' }, { offset: 1, color: 'rgba(217,185,248,0.3)' }]), curveness: 0.7, width: 0.5 } },
{ source: 'C', target: 'X', value: 2, lineStyle: { color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [{ offset: 0, color: 'rgba(83,104,245,0.3)' }, { offset: 1, color: 'rgba(217,185,248,0.3)' }]), curveness: 0.7, width: 0.5 } },
{ source: 'C', target: 'Z', value: 4, lineStyle: { color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [{ offset: 0, color: 'rgba(83,104,245,0.3)' }, { offset: 1, color: 'rgba(217,185,248,0.3)' }]), curveness: 0.7, width: 0.5 } },
],
label: { show: false }
}]
});
const resize = () => chart.resize();
window.addEventListener('resize', resize);
return () => {
window.removeEventListener('resize', resize);
chart.dispose();
};
}, []);
return <div ref={chartRef} className="w-full h-full" />;
}
export const App = () => <SankyKpiCard />;
Sankey KPI Card Component
Description
A specialized KPI card featuring a Sankey diagram visualization. Perfect for displaying flow distributions, allocations, or transformation processes between categories.
How to Use
<SankyKpiCard
title="Cash Report"
amount={72858.8}
percentageChange={12}
isPositive={true}
description="Monthly cash flow report"
legendItems={[
{ name: "Approved", percentage: "24%", color: "#5368f5" },
{ name: "Pending", percentage: "15%", color: "#d9b9f8" },
{ name: "Rejected", percentage: "8%", color: "#f87171" }
]}
className="bg-white rounded-xl shadow-sm border border-gray-200 p-5 w-80"
currency="USD"
/>Sankey diagrams are best used for visualizing flow quantities, where the width of the flows represents the quantity of the resource being transferred.
Props Reference
| Prop Name | Type | Required | Description |
|---|---|---|---|
| title | string | No | Title of the KPI card (default: "Cash Report") |
| amount | number | No | The primary value to display (default: 72858.8) |
| percentageChange | number | No | Percentage change to display (default: 12) |
| isPositive | boolean | No | Whether the percentage change is positive (default: true) |
| description | string | No | Description text below the main value (default: "Add your description") |
| legendItems | array | No | Array of { name, percentage, color } objects for the legend |
| className | string | No | Custom CSS classes for styling the card |
| currency | string | No | Currency code for formatting (default: "USD") |
Ideal Use Cases
- Budget allocations across departments
- Energy flow diagrams
- Customer journey mapping
- Supply chain visualizations
- Resource distribution analysis
Data Preparation Tips
- Sankey diagrams require data in a nodes and links format
- Each link should have source, target and value properties
- Limit the number of nodes to prevent visual clutter
- Consider using color coding to distinguish different types of flows
- For complex diagrams, enable interactivity to explore specific flows