first commit

This commit is contained in:
Mateusz Gruszczyński
2026-03-23 15:56:18 +01:00
commit c5cc2efbac
106 changed files with 10254 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
import clsx from "clsx";
import type { PropsWithChildren } from "react";
interface BadgeProps extends PropsWithChildren {
tone?: "ok" | "warn" | "critical" | "neutral";
}
export function Badge({ tone = "neutral", children }: BadgeProps) {
const palette = {
ok: "border-emerald-400/30 bg-emerald-500/10 text-emerald-200",
warn: "border-amber-400/30 bg-amber-500/10 text-amber-200",
critical: "border-rose-400/30 bg-rose-500/10 text-rose-200",
neutral: "border-white/10 bg-white/5 text-slate-200",
};
return (
<span className={clsx("inline-flex items-center rounded-full border px-3 py-1 text-xs font-medium", palette[tone])}>
{children}
</span>
);
}

View File

@@ -0,0 +1,31 @@
import clsx from "clsx";
import type { PropsWithChildren, ReactNode } from "react";
interface CardProps extends PropsWithChildren {
title?: string;
subtitle?: string;
action?: ReactNode;
className?: string;
}
export function Card({ title, subtitle, action, className, children }: CardProps) {
return (
<section
className={clsx(
"rounded-3xl border border-white/10 bg-white/5 p-5 shadow-[0_24px_80px_rgba(15,23,42,0.35)] backdrop-blur",
className
)}
>
{(title || subtitle || action) && (
<header className="mb-4 flex items-start justify-between gap-4">
<div>
{title && <h3 className="text-base font-semibold text-white">{title}</h3>}
{subtitle && <p className="mt-1 text-sm text-slate-400">{subtitle}</p>}
</div>
{action}
</header>
)}
{children}
</section>
);
}

View File

@@ -0,0 +1,31 @@
import { useEffect, useRef } from "react";
import * as echarts from "echarts";
import type { EChartsOption } from "echarts";
interface EChartProps {
option: EChartsOption;
className?: string;
}
export function EChart({ option, className = "h-80 w-full" }: EChartProps) {
const ref = useRef<HTMLDivElement | null>(null);
useEffect(() => {
if (!ref.current) {
return;
}
const chart = echarts.init(ref.current);
chart.setOption(option);
const observer = new ResizeObserver(() => chart.resize());
observer.observe(ref.current);
return () => {
observer.disconnect();
chart.dispose();
};
}, [option]);
return <div ref={ref} className={className} />;
}

View File

@@ -0,0 +1,13 @@
interface EmptyStateProps {
title: string;
description: string;
}
export function EmptyState({ title, description }: EmptyStateProps) {
return (
<div className="rounded-3xl border border-dashed border-white/10 bg-white/3 p-10 text-center">
<h3 className="text-lg font-semibold text-white">{title}</h3>
<p className="mt-2 text-sm text-slate-400">{description}</p>
</div>
);
}

View File

@@ -0,0 +1,84 @@
import type { SVGProps } from "react";
type IconProps = SVGProps<SVGSVGElement> & { size?: number };
function BaseIcon({ size = 18, children, ...props }: IconProps) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
{...props}
>
{children}
</svg>
);
}
export function IconBolt(props: IconProps) {
return <BaseIcon {...props}><path d="M13 2L5 14h6l-1 8 8-12h-6l1-8z" /></BaseIcon>;
}
export function IconChartBar(props: IconProps) {
return <BaseIcon {...props}><path d="M4 20h16" /><path d="M7 16V8" /><path d="M12 16V4" /><path d="M17 16v-6" /></BaseIcon>;
}
export function IconChecklist(props: IconProps) {
return <BaseIcon {...props}><path d="M9 6h11" /><path d="M9 12h11" /><path d="M9 18h11" /><path d="M4 6l1.5 1.5L7.5 5" /><path d="M4 12l1.5 1.5L7.5 11" /><path d="M4 18l1.5 1.5L7.5 17" /></BaseIcon>;
}
export function IconClockHour4(props: IconProps) {
return <BaseIcon {...props}><circle cx="12" cy="12" r="9" /><path d="M12 7v5l3 2" /></BaseIcon>;
}
export function IconDatabaseImport(props: IconProps) {
return <BaseIcon {...props}><ellipse cx="12" cy="5" rx="7" ry="3" /><path d="M5 5v6c0 1.7 3.1 3 7 3s7-1.3 7-3V5" /><path d="M12 14v8" /><path d="M9 19l3 3 3-3" /></BaseIcon>;
}
export function IconDeviceDesktop(props: IconProps) {
return <BaseIcon {...props}><rect x="3" y="4" width="18" height="12" rx="2" /><path d="M8 20h8" /><path d="M12 16v4" /></BaseIcon>;
}
export function IconHistory(props: IconProps) {
return <BaseIcon {...props}><path d="M3 12a9 9 0 1 0 3-6.7" /><path d="M3 4v5h5" /><path d="M12 7v5l3 2" /></BaseIcon>;
}
export function IconLanguage(props: IconProps) {
return <BaseIcon {...props}><path d="M4 5h10" /><path d="M9 3c0 6-2 10-5 12" /><path d="M7 13c1.5 2.5 3.5 4.5 6 6" /><path d="M14 10h6" /><path d="M17 7l3 10" /><path d="M14 17h6" /></BaseIcon>;
}
export function IconLayoutDashboard(props: IconProps) {
return <BaseIcon {...props}><rect x="3" y="3" width="8" height="8" rx="1" /><rect x="13" y="3" width="8" height="5" rx="1" /><rect x="13" y="10" width="8" height="11" rx="1" /><rect x="3" y="13" width="8" height="8" rx="1" /></BaseIcon>;
}
export function IconLock(props: IconProps) {
return <BaseIcon {...props}><rect x="5" y="11" width="14" height="10" rx="2" /><path d="M8 11V8a4 4 0 0 1 8 0v3" /></BaseIcon>;
}
export function IconLogin2(props: IconProps) {
return <BaseIcon {...props}><path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4" /><path d="M10 17l5-5-5-5" /><path d="M15 12H3" /></BaseIcon>;
}
export function IconLogout(props: IconProps) {
return <BaseIcon {...props}><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" /><path d="M16 17l5-5-5-5" /><path d="M21 12H9" /></BaseIcon>;
}
export function IconMoon(props: IconProps) {
return <BaseIcon {...props}><path d="M12 3a7 7 0 1 0 9 9 9 9 0 1 1-9-9z" /></BaseIcon>;
}
export function IconPlayerPlay(props: IconProps) {
return <BaseIcon {...props}><path d="M8 5v14l11-7z" /></BaseIcon>;
}
export function IconRefresh(props: IconProps) {
return <BaseIcon {...props}><path d="M20 11a8 8 0 0 0-14-5l-2 2" /><path d="M4 3v5h5" /><path d="M4 13a8 8 0 0 0 14 5l2-2" /><path d="M20 21v-5h-5" /></BaseIcon>;
}
export function IconSettings(props: IconProps) {
return <BaseIcon {...props}><circle cx="12" cy="12" r="3" /><path d="M19.4 15a1.6 1.6 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.6 1.6 0 0 0-1.8-.3 1.6 1.6 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.2a1.6 1.6 0 0 0-1-1.5 1.6 1.6 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.6 1.6 0 0 0 .3-1.8 1.6 1.6 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.2a1.6 1.6 0 0 0 1.5-1 1.6 1.6 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.6 1.6 0 0 0 1.8.3h0A1.6 1.6 0 0 0 10 3.2V3a2 2 0 1 1 4 0v.2a1.6 1.6 0 0 0 1 1.5h0a1.6 1.6 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.6 1.6 0 0 0-.3 1.8v0a1.6 1.6 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.2a1.6 1.6 0 0 0-1.4 1z" /></BaseIcon>;
}
export function IconSun(props: IconProps) {
return <BaseIcon {...props}><circle cx="12" cy="12" r="4" /><path d="M12 2v2" /><path d="M12 20v2" /><path d="M4.9 4.9l1.4 1.4" /><path d="M17.7 17.7l1.4 1.4" /><path d="M2 12h2" /><path d="M20 12h2" /><path d="M4.9 19.1l1.4-1.4" /><path d="M17.7 6.3l1.4-1.4" /></BaseIcon>;
}
export function IconTemperature(props: IconProps) {
return <BaseIcon {...props}><path d="M14 14.76V5a2 2 0 0 0-4 0v9.76a4 4 0 1 0 4 0z" /></BaseIcon>;
}
export function IconX(props: IconProps) {
return <BaseIcon {...props}><path d="M18 6L6 18" /><path d="M6 6l12 12" /></BaseIcon>;
}
export function IconArrowsMove(props: IconProps) {
return <BaseIcon {...props}><path d="M12 2v20" /><path d="M2 12h20" /><path d="M7 7l5-5 5 5" /><path d="M7 17l5 5 5-5" /></BaseIcon>;
}

View File

@@ -0,0 +1,17 @@
import { formatValue } from "../../lib/format";
import type { MetricValue } from "../../types";
interface ValuePairProps {
metric?: MetricValue;
}
export function ValuePair({ metric }: ValuePairProps) {
return (
<div className="rounded-2xl border border-white/8 bg-slate-950/40 p-3">
<div className="text-xs uppercase tracking-[0.18em] text-slate-500">{metric?.label ?? "--"}</div>
<div className="mt-2 text-lg font-semibold text-white">
{metric ? formatValue(metric.value, metric.unit, metric.precision) : "--"}
</div>
</div>
);
}