@olympusoss/canvas 2.20.2 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -35
- package/package.json +45 -177
- package/src/cn.ts +3 -0
- package/src/index.ts +12 -603
- package/src/theme.ts +62 -0
- package/src/tokens.ts +11 -0
- package/styles/base.css +17 -0
- package/styles/canvas.css +77 -52
- package/styles/components/alert.css +66 -0
- package/styles/components/app-shell.css +46 -0
- package/styles/components/avatar.css +22 -0
- package/styles/components/badge.css +83 -0
- package/styles/components/breadcrumb.css +35 -0
- package/styles/components/button-group.css +23 -0
- package/styles/components/button.css +107 -0
- package/styles/components/calendar.css +73 -0
- package/styles/components/card.css +58 -0
- package/styles/components/checkbox.css +55 -0
- package/styles/components/code-block.css +18 -0
- package/styles/components/combobox.css +75 -0
- package/styles/components/command.css +94 -0
- package/styles/components/data-table.css +142 -0
- package/styles/components/dialog.css +72 -0
- package/styles/components/dropdown.css +54 -0
- package/styles/components/empty-state.css +17 -0
- package/styles/components/field.css +27 -0
- package/styles/components/filter-panel.css +58 -0
- package/styles/components/form.css +27 -0
- package/styles/components/icon.css +8 -0
- package/styles/components/input-group.css +45 -0
- package/styles/components/input.css +56 -0
- package/styles/components/kbd.css +15 -0
- package/styles/components/page-header.css +52 -0
- package/styles/components/pagination.css +48 -0
- package/styles/components/popover.css +14 -0
- package/styles/components/radio.css +28 -0
- package/styles/components/row-menu.css +69 -0
- package/styles/components/section-card.css +49 -0
- package/styles/components/select.css +57 -0
- package/styles/components/separator.css +32 -0
- package/styles/components/sheet.css +70 -0
- package/styles/components/sidebar.css +146 -0
- package/styles/components/skeleton.css +32 -0
- package/styles/components/spinner.css +26 -0
- package/styles/components/stat-card.css +71 -0
- package/styles/components/stepper.css +63 -0
- package/styles/components/switch.css +45 -0
- package/styles/components/tabs.css +40 -0
- package/styles/components/textarea.css +31 -0
- package/styles/components/toast.css +95 -0
- package/styles/components/tooltip.css +53 -0
- package/styles/components/topbar.css +24 -0
- package/styles/components/typography.css +105 -0
- package/styles/patterns/backdrops.css +35 -0
- package/styles/patterns/density.css +66 -0
- package/styles/patterns/focus.css +22 -0
- package/styles/patterns/glass.css +85 -0
- package/styles/patterns/high-contrast.css +70 -0
- package/styles/patterns/reduced-motion.css +12 -0
- package/styles/patterns/scrollbar.css +10 -0
- package/styles/reset.css +89 -0
- package/styles/tokens/colors.css +106 -0
- package/styles/tokens/motion.css +33 -0
- package/styles/tokens/radius.css +10 -0
- package/styles/tokens/shadows.css +35 -0
- package/styles/tokens/spacing.css +19 -0
- package/styles/tokens/typography.css +6 -0
- package/styles/tokens/z-index.css +12 -0
- package/styles/utilities/display.css +66 -0
- package/styles/utilities/flexbox.css +240 -0
- package/styles/utilities/gap.css +288 -0
- package/styles/utilities/grid.css +138 -0
- package/styles/utilities/position.css +78 -0
- package/styles/utilities/sizing.css +138 -0
- package/tsconfig.json +20 -21
- package/src/components/atoms/README.md +0 -11
- package/src/components/atoms/aspect-ratio.tsx +0 -32
- package/src/components/atoms/avatar.tsx +0 -98
- package/src/components/atoms/badge.tsx +0 -44
- package/src/components/atoms/brand-mark.tsx +0 -74
- package/src/components/atoms/button.tsx +0 -105
- package/src/components/atoms/checkbox.tsx +0 -63
- package/src/components/atoms/flex-box.tsx +0 -105
- package/src/components/atoms/icon.tsx +0 -34
- package/src/components/atoms/input.tsx +0 -92
- package/src/components/atoms/label.tsx +0 -41
- package/src/components/atoms/logo.tsx +0 -89
- package/src/components/atoms/progress.tsx +0 -55
- package/src/components/atoms/radio-group.tsx +0 -122
- package/src/components/atoms/scroll-area.tsx +0 -106
- package/src/components/atoms/section.tsx +0 -48
- package/src/components/atoms/separator.tsx +0 -45
- package/src/components/atoms/skeleton.tsx +0 -17
- package/src/components/atoms/slider.tsx +0 -93
- package/src/components/atoms/spinner.tsx +0 -47
- package/src/components/atoms/switch.tsx +0 -60
- package/src/components/atoms/textarea.tsx +0 -78
- package/src/components/atoms/toggle.tsx +0 -80
- package/src/components/charts/activity-heatmap.tsx +0 -186
- package/src/components/charts/axes.tsx +0 -21
- package/src/components/charts/chart-container.tsx +0 -254
- package/src/components/charts/chart-legend.tsx +0 -67
- package/src/components/charts/chart-tooltip.tsx +0 -161
- package/src/components/charts/chart-types.tsx +0 -49
- package/src/components/charts/containers.tsx +0 -11
- package/src/components/charts/data.tsx +0 -16
- package/src/components/charts/details.tsx +0 -25
- package/src/components/charts/dot-pulse.tsx +0 -61
- package/src/components/charts/gauge.tsx +0 -106
- package/src/components/charts/grids.tsx +0 -8
- package/src/components/charts/index.ts +0 -62
- package/src/components/charts/labeled-bar-list.tsx +0 -85
- package/src/components/charts/metric-breakdown.tsx +0 -316
- package/src/components/charts/references.tsx +0 -8
- package/src/components/charts/service-health-list.tsx +0 -85
- package/src/components/charts/sparkline-area.tsx +0 -80
- package/src/components/charts/sparkline.tsx +0 -52
- package/src/components/charts/stacked-bar.tsx +0 -104
- package/src/components/charts/text.tsx +0 -10
- package/src/components/charts/world-heat-map-inner.tsx +0 -317
- package/src/components/charts/world-heat-map.tsx +0 -184
- package/src/components/molecules/README.md +0 -12
- package/src/components/molecules/action-bar.tsx +0 -73
- package/src/components/molecules/activity-item.tsx +0 -74
- package/src/components/molecules/alert.tsx +0 -86
- package/src/components/molecules/animated-background.tsx +0 -92
- package/src/components/molecules/auth-shell.tsx +0 -95
- package/src/components/molecules/brand-lockup.tsx +0 -48
- package/src/components/molecules/breadcrumb.tsx +0 -157
- package/src/components/molecules/button-group.tsx +0 -104
- package/src/components/molecules/calendar.tsx +0 -217
- package/src/components/molecules/card.tsx +0 -102
- package/src/components/molecules/client-brand.tsx +0 -95
- package/src/components/molecules/code-block.tsx +0 -86
- package/src/components/molecules/countdown-button.tsx +0 -92
- package/src/components/molecules/empty-state.tsx +0 -56
- package/src/components/molecules/error-state.tsx +0 -42
- package/src/components/molecules/field-display.tsx +0 -35
- package/src/components/molecules/input-otp.tsx +0 -74
- package/src/components/molecules/launcher-card.tsx +0 -152
- package/src/components/molecules/loading-state.tsx +0 -36
- package/src/components/molecules/notification-item.tsx +0 -67
- package/src/components/molecules/notification-list.tsx +0 -45
- package/src/components/molecules/number-badge.tsx +0 -53
- package/src/components/molecules/or-separator.tsx +0 -38
- package/src/components/molecules/page-header.tsx +0 -88
- package/src/components/molecules/page-tabs.tsx +0 -94
- package/src/components/molecules/pagination.tsx +0 -150
- package/src/components/molecules/password-input.tsx +0 -83
- package/src/components/molecules/password-strength-meter.tsx +0 -104
- package/src/components/molecules/phone-input.tsx +0 -200
- package/src/components/molecules/search-bar.tsx +0 -64
- package/src/components/molecules/secret-field.tsx +0 -158
- package/src/components/molecules/section-card.tsx +0 -91
- package/src/components/molecules/social-buttons.tsx +0 -165
- package/src/components/molecules/stat-card.tsx +0 -100
- package/src/components/molecules/status-badge.tsx +0 -42
- package/src/components/molecules/stepper.tsx +0 -96
- package/src/components/molecules/table.tsx +0 -157
- package/src/components/molecules/terminal.tsx +0 -74
- package/src/components/molecules/toggle-group.tsx +0 -145
- package/src/components/molecules/tooltip.tsx +0 -155
- package/src/components/molecules/user-avatar-chip.tsx +0 -71
- package/src/components/organisms/README.md +0 -14
- package/src/components/organisms/accordion.tsx +0 -154
- package/src/components/organisms/alert-dialog.tsx +0 -277
- package/src/components/organisms/carousel.tsx +0 -244
- package/src/components/organisms/collapsible.tsx +0 -69
- package/src/components/organisms/command.tsx +0 -144
- package/src/components/organisms/context-menu.tsx +0 -339
- package/src/components/organisms/dashboard-grid.tsx +0 -369
- package/src/components/organisms/data-table.tsx +0 -330
- package/src/components/organisms/dialog.tsx +0 -312
- package/src/components/organisms/drawer.tsx +0 -123
- package/src/components/organisms/dropdown-menu.tsx +0 -440
- package/src/components/organisms/editors/code-editor.tsx +0 -144
- package/src/components/organisms/editors/index.ts +0 -4
- package/src/components/organisms/editors/markdown-editor.tsx +0 -153
- package/src/components/organisms/editors/markdown-renderer.ts +0 -27
- package/src/components/organisms/editors/prose-canvas-classes.ts +0 -45
- package/src/components/organisms/editors/rich-text-editor.tsx +0 -126
- package/src/components/organisms/editors/toolbar/md-toolbar.tsx +0 -129
- package/src/components/organisms/editors/toolbar/rte-toolbar.tsx +0 -211
- package/src/components/organisms/editors/toolbar/toolbar-shell.tsx +0 -45
- package/src/components/organisms/editors/use-codemirror-theme.ts +0 -61
- package/src/components/organisms/error-boundary.tsx +0 -61
- package/src/components/organisms/form.tsx +0 -174
- package/src/components/organisms/hover-card.tsx +0 -115
- package/src/components/organisms/menubar.tsx +0 -498
- package/src/components/organisms/navbar.tsx +0 -104
- package/src/components/organisms/navigation-menu.tsx +0 -235
- package/src/components/organisms/popover.tsx +0 -149
- package/src/components/organisms/resizable.tsx +0 -58
- package/src/components/organisms/schema-form.tsx +0 -232
- package/src/components/organisms/select.tsx +0 -309
- package/src/components/organisms/sheet.tsx +0 -265
- package/src/components/organisms/sidebar.tsx +0 -1040
- package/src/components/organisms/sonner.tsx +0 -96
- package/src/components/organisms/tabs.tsx +0 -133
- package/src/components/organisms/theme-provider.tsx +0 -101
- package/src/hooks/use-mobile.tsx +0 -19
- package/src/lib/portal-container.tsx +0 -35
- package/src/lib/utils.ts +0 -6
- package/src/native.ts +0 -23
- package/src/tokens/colors.ts +0 -91
- package/src/tokens/index.ts +0 -3
- package/src/tokens/spacing.ts +0 -55
- package/src/tokens/typography.ts +0 -27
- package/styles/dashboard-grid.css +0 -47
- package/styles/fonts/Roboto-VariableFont_wdth_wght.ttf +0 -0
- package/styles/glass.css +0 -175
- package/styles/leaflet.css +0 -13
- package/styles/tokens.css +0 -317
- package/tailwind.config.ts +0 -70
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
|
|
3
|
-
import { cn } from "../../lib/utils";
|
|
4
|
-
|
|
5
|
-
export interface GaugeProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
6
|
-
/** Percentage to fill (0–100). Values outside the range are clamped. */
|
|
7
|
-
value: number;
|
|
8
|
-
/** Pixel size (width = height). Default `160`. */
|
|
9
|
-
size?: number;
|
|
10
|
-
/** Pixel stroke width of the ring. Default `14`. */
|
|
11
|
-
strokeWidth?: number;
|
|
12
|
-
/**
|
|
13
|
-
* CSS variable name (without leading `--`) used for the filled arc. Default
|
|
14
|
-
* `chart-1`.
|
|
15
|
-
*/
|
|
16
|
-
colorVar?: string;
|
|
17
|
-
/**
|
|
18
|
-
* Center value text. Defaults to `"{value}%"`. Pass a custom node to render
|
|
19
|
-
* something richer (e.g. a unit label).
|
|
20
|
-
*/
|
|
21
|
-
valueLabel?: React.ReactNode;
|
|
22
|
-
/** Small label rendered below the centre value. Optional. */
|
|
23
|
-
caption?: React.ReactNode;
|
|
24
|
-
/** Accessible label describing the gauge. Default `"Gauge"`. */
|
|
25
|
-
"aria-label"?: string;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Single-arc circular gauge with a centered value. Useful for adoption
|
|
30
|
-
* percentages, health scores, completion ratios, and similar 0–100 metrics.
|
|
31
|
-
* Renders a complete background ring plus a filled arc; the arc is animated
|
|
32
|
-
* via `transition-[stroke-dashoffset]` so consumers get smooth updates when
|
|
33
|
-
* `value` changes.
|
|
34
|
-
*/
|
|
35
|
-
export const Gauge = React.forwardRef<HTMLDivElement, GaugeProps>(
|
|
36
|
-
(
|
|
37
|
-
{
|
|
38
|
-
value,
|
|
39
|
-
size = 160,
|
|
40
|
-
strokeWidth = 14,
|
|
41
|
-
colorVar = "chart-1",
|
|
42
|
-
valueLabel,
|
|
43
|
-
caption,
|
|
44
|
-
className,
|
|
45
|
-
"aria-label": ariaLabel = "Gauge",
|
|
46
|
-
...props
|
|
47
|
-
},
|
|
48
|
-
ref,
|
|
49
|
-
) => {
|
|
50
|
-
const clamped = Math.max(0, Math.min(100, value));
|
|
51
|
-
const r = (size - strokeWidth) / 2;
|
|
52
|
-
const cx = size / 2;
|
|
53
|
-
const c = 2 * Math.PI * r;
|
|
54
|
-
const offset = c - (clamped / 100) * c;
|
|
55
|
-
const display = valueLabel ?? `${Math.round(clamped)}%`;
|
|
56
|
-
return (
|
|
57
|
-
<div
|
|
58
|
-
ref={ref}
|
|
59
|
-
role="meter"
|
|
60
|
-
aria-label={ariaLabel}
|
|
61
|
-
aria-valuenow={Math.round(clamped)}
|
|
62
|
-
aria-valuemin={0}
|
|
63
|
-
aria-valuemax={100}
|
|
64
|
-
className={cn("relative inline-flex flex-col items-center", className)}
|
|
65
|
-
style={{ width: size, height: caption ? "auto" : size }}
|
|
66
|
-
{...props}
|
|
67
|
-
>
|
|
68
|
-
<svg width={size} height={size} viewBox={`0 0 ${size} ${size}`} aria-hidden>
|
|
69
|
-
<circle
|
|
70
|
-
cx={cx}
|
|
71
|
-
cy={cx}
|
|
72
|
-
r={r}
|
|
73
|
-
fill="none"
|
|
74
|
-
stroke="hsl(var(--muted))"
|
|
75
|
-
strokeWidth={strokeWidth}
|
|
76
|
-
/>
|
|
77
|
-
<circle
|
|
78
|
-
cx={cx}
|
|
79
|
-
cy={cx}
|
|
80
|
-
r={r}
|
|
81
|
-
fill="none"
|
|
82
|
-
stroke={`hsl(var(--${colorVar}))`}
|
|
83
|
-
strokeWidth={strokeWidth}
|
|
84
|
-
strokeDasharray={c}
|
|
85
|
-
strokeDashoffset={offset}
|
|
86
|
-
strokeLinecap="round"
|
|
87
|
-
transform={`rotate(-90 ${cx} ${cx})`}
|
|
88
|
-
className="transition-[stroke-dashoffset] duration-500"
|
|
89
|
-
/>
|
|
90
|
-
</svg>
|
|
91
|
-
<div
|
|
92
|
-
className="pointer-events-none absolute inset-x-0 flex flex-col items-center justify-center"
|
|
93
|
-
style={{ top: 0, height: size }}
|
|
94
|
-
>
|
|
95
|
-
<span className="font-mono text-3xl font-bold tabular-nums">{display}</span>
|
|
96
|
-
{caption && (
|
|
97
|
-
<span className="mt-1 text-[11px] uppercase tracking-[0.04em] text-muted-foreground">
|
|
98
|
-
{caption}
|
|
99
|
-
</span>
|
|
100
|
-
)}
|
|
101
|
-
</div>
|
|
102
|
-
</div>
|
|
103
|
-
);
|
|
104
|
-
},
|
|
105
|
-
);
|
|
106
|
-
Gauge.displayName = "Gauge";
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Grid re-exports — pure pass-throughs. Theming via CSS in `<ChartContainer>`
|
|
5
|
-
* (`[&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50` etc.).
|
|
6
|
-
* Wrapping breaks Recharts' child-by-type detection.
|
|
7
|
-
*/
|
|
8
|
-
export { CartesianGrid, PolarGrid } from "recharts";
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
// Container + style emitter (canvas-specific)
|
|
2
|
-
|
|
3
|
-
// Chrome — canvas-token defaults
|
|
4
|
-
export {
|
|
5
|
-
CartesianAxis,
|
|
6
|
-
PolarAngleAxis,
|
|
7
|
-
PolarRadiusAxis,
|
|
8
|
-
XAxis,
|
|
9
|
-
YAxis,
|
|
10
|
-
ZAxis,
|
|
11
|
-
} from "./axes";
|
|
12
|
-
export {
|
|
13
|
-
type ChartConfig,
|
|
14
|
-
ChartContainer,
|
|
15
|
-
ChartStyle,
|
|
16
|
-
useChart,
|
|
17
|
-
} from "./chart-container";
|
|
18
|
-
export { ChartLegend, ChartLegendContent } from "./chart-legend";
|
|
19
|
-
export { ChartTooltip, ChartTooltipContent } from "./chart-tooltip";
|
|
20
|
-
// Chart-type wrappers (`margin` defaults; auto-palette runs in ChartContainer)
|
|
21
|
-
export {
|
|
22
|
-
AreaChart,
|
|
23
|
-
BarChart,
|
|
24
|
-
ComposedChart,
|
|
25
|
-
FunnelChart,
|
|
26
|
-
LineChart,
|
|
27
|
-
PieChart,
|
|
28
|
-
RadarChart,
|
|
29
|
-
RadialBarChart,
|
|
30
|
-
Sankey,
|
|
31
|
-
ScatterChart,
|
|
32
|
-
SunburstChart,
|
|
33
|
-
Treemap,
|
|
34
|
-
} from "./chart-types";
|
|
35
|
-
// Container-level — pure pass-through
|
|
36
|
-
export { Brush, Layer, ResponsiveContainer, Surface } from "./containers";
|
|
37
|
-
// Data primitives (auto-cycle palette)
|
|
38
|
-
export { Area, Bar, Funnel, Line, Pie, Radar, RadialBar, Scatter } from "./data";
|
|
39
|
-
// Detail primitives — pure pass-through
|
|
40
|
-
export {
|
|
41
|
-
ChartCell,
|
|
42
|
-
ChartCustomized,
|
|
43
|
-
Cross,
|
|
44
|
-
Curve,
|
|
45
|
-
Dot,
|
|
46
|
-
ErrorBar,
|
|
47
|
-
Polygon,
|
|
48
|
-
Rectangle,
|
|
49
|
-
Sector,
|
|
50
|
-
Trapezoid,
|
|
51
|
-
} from "./details";
|
|
52
|
-
export { CartesianGrid, PolarGrid } from "./grids";
|
|
53
|
-
export { ReferenceArea, ReferenceDot, ReferenceLine } from "./references";
|
|
54
|
-
// Text + labels (Label aliased to avoid collision with canvas form Label)
|
|
55
|
-
export { ChartLabel, LabelList, Text } from "./text";
|
|
56
|
-
|
|
57
|
-
// Geographic — Leaflet-based heat-map (peer-optional `leaflet` + `react-leaflet`)
|
|
58
|
-
export {
|
|
59
|
-
WorldHeatMap,
|
|
60
|
-
type WorldHeatMapPoint,
|
|
61
|
-
type WorldHeatMapProps,
|
|
62
|
-
} from "./world-heat-map";
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
|
|
3
|
-
import { cn } from "../../lib/utils";
|
|
4
|
-
|
|
5
|
-
export interface LabeledBarListItem {
|
|
6
|
-
/** Row label. Used as the React key. */
|
|
7
|
-
label: React.ReactNode;
|
|
8
|
-
/** Numeric value rendered in the right cap; bar fill scales against the max. */
|
|
9
|
-
value: number;
|
|
10
|
-
/** Optional leading element — e.g. a flag, avatar, or `<Icon />`. */
|
|
11
|
-
leading?: React.ReactNode;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface LabeledBarListProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
15
|
-
/** Rows rendered top-to-bottom. */
|
|
16
|
-
items: LabeledBarListItem[];
|
|
17
|
-
/**
|
|
18
|
-
* CSS variable name (without leading `--`) used for the bar fill. Default
|
|
19
|
-
* `chart-1`.
|
|
20
|
-
*/
|
|
21
|
-
colorVar?: string;
|
|
22
|
-
/** Format the value rendered to the right of each label. Default `toLocaleString`. */
|
|
23
|
-
valueFormatter?: (value: number) => string;
|
|
24
|
-
/** Pixel height of the row's bar track. Default `4`. */
|
|
25
|
-
barHeight?: number;
|
|
26
|
-
/** Caption rendered below the list. */
|
|
27
|
-
caption?: React.ReactNode;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Vertical list of labeled rows with a horizontal progress bar per row. Useful
|
|
32
|
-
* for "top regions", "schema usage", "provider connections" — anywhere a small
|
|
33
|
-
* set of named values needs proportional comparison without a full chart.
|
|
34
|
-
*/
|
|
35
|
-
export const LabeledBarList = React.forwardRef<HTMLDivElement, LabeledBarListProps>(
|
|
36
|
-
(
|
|
37
|
-
{
|
|
38
|
-
items,
|
|
39
|
-
colorVar = "chart-1",
|
|
40
|
-
valueFormatter = (v) => v.toLocaleString(),
|
|
41
|
-
barHeight = 4,
|
|
42
|
-
caption,
|
|
43
|
-
className,
|
|
44
|
-
...props
|
|
45
|
-
},
|
|
46
|
-
ref,
|
|
47
|
-
) => {
|
|
48
|
-
const max = Math.max(1, ...items.map((it) => it.value));
|
|
49
|
-
return (
|
|
50
|
-
<div ref={ref} className={cn("w-full", className)} {...props}>
|
|
51
|
-
<ul className="flex flex-col gap-2.5">
|
|
52
|
-
{items.map((item, i) => {
|
|
53
|
-
const pct = Math.max(0, Math.min(100, (item.value / max) * 100));
|
|
54
|
-
return (
|
|
55
|
-
<li key={`${i}-${item.value}`} className="flex flex-col gap-1.5">
|
|
56
|
-
<div className="flex items-center gap-2 text-[13px]">
|
|
57
|
-
{item.leading && <span className="shrink-0">{item.leading}</span>}
|
|
58
|
-
<span className="flex-1 truncate">{item.label}</span>
|
|
59
|
-
<span className="font-mono text-xs text-muted-foreground tabular-nums">
|
|
60
|
-
{valueFormatter(item.value)}
|
|
61
|
-
</span>
|
|
62
|
-
</div>
|
|
63
|
-
<div
|
|
64
|
-
className="overflow-hidden rounded-full bg-muted"
|
|
65
|
-
style={{ height: barHeight }}
|
|
66
|
-
aria-hidden
|
|
67
|
-
>
|
|
68
|
-
<div
|
|
69
|
-
className="h-full rounded-full"
|
|
70
|
-
style={{
|
|
71
|
-
width: `${pct}%`,
|
|
72
|
-
background: `hsl(var(--${colorVar}))`,
|
|
73
|
-
}}
|
|
74
|
-
/>
|
|
75
|
-
</div>
|
|
76
|
-
</li>
|
|
77
|
-
);
|
|
78
|
-
})}
|
|
79
|
-
</ul>
|
|
80
|
-
{caption && <p className="mt-3 text-xs text-muted-foreground">{caption}</p>}
|
|
81
|
-
</div>
|
|
82
|
-
);
|
|
83
|
-
},
|
|
84
|
-
);
|
|
85
|
-
LabeledBarList.displayName = "LabeledBarList";
|
|
@@ -1,316 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
|
|
3
|
-
import { cn } from "../../lib/utils";
|
|
4
|
-
|
|
5
|
-
export type MetricBreakdownTone = "success" | "warning" | "error" | "neutral";
|
|
6
|
-
|
|
7
|
-
const TONE_HSL: Record<MetricBreakdownTone, string> = {
|
|
8
|
-
success: "143 70% 40%",
|
|
9
|
-
warning: "38 92% 50%",
|
|
10
|
-
error: "0 80% 60%",
|
|
11
|
-
neutral: "var(--muted-foreground)",
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export interface MetricBreakdownRow {
|
|
15
|
-
/** Row label (often a code or category key). Used as the React key. */
|
|
16
|
-
label: React.ReactNode;
|
|
17
|
-
/** Numeric value rendered to the right of the label. */
|
|
18
|
-
value: number;
|
|
19
|
-
/**
|
|
20
|
-
* Period-over-period change as a percentage. Positive renders as `▲ N%` in
|
|
21
|
-
* the success hue, negative as `▼ N%` in the error hue. Omit to hide the
|
|
22
|
-
* delta column for that row.
|
|
23
|
-
*/
|
|
24
|
-
delta?: number;
|
|
25
|
-
/**
|
|
26
|
-
* CSS variable name (without leading `--`) used for the row's swatch and
|
|
27
|
-
* bar fill. Falls back to the wrapper's `defaultColorVar`.
|
|
28
|
-
*/
|
|
29
|
-
colorVar?: string;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface MetricBreakdownChip {
|
|
33
|
-
/** Chip label, typically a code or short tag. Used as the React key. */
|
|
34
|
-
label: React.ReactNode;
|
|
35
|
-
/** Optional count rendered after a thin separator. */
|
|
36
|
-
count?: number;
|
|
37
|
-
/** Visual tone. Default `error`. */
|
|
38
|
-
tone?: MetricBreakdownTone;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export interface MetricBreakdownProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
42
|
-
/** Primary metric value rendered as the headline number. */
|
|
43
|
-
value: React.ReactNode;
|
|
44
|
-
/** Caption rendered below the headline value (uppercase muted). */
|
|
45
|
-
label: React.ReactNode;
|
|
46
|
-
/** Secondary metric rendered top-right (e.g. an error rate). */
|
|
47
|
-
rate?: React.ReactNode;
|
|
48
|
-
/** Caption for the secondary metric (uppercase muted). */
|
|
49
|
-
rateLabel?: React.ReactNode;
|
|
50
|
-
/** Tone driving the secondary metric color. Default `neutral`. */
|
|
51
|
-
rateTone?: MetricBreakdownTone;
|
|
52
|
-
/** Sparkline data points. Rendered as an SVG line + area-fill ramp. */
|
|
53
|
-
spark?: number[];
|
|
54
|
-
/** Unit suffix rendered next to the last value of the sparkline, e.g. `req/s`. */
|
|
55
|
-
sparkUnit?: React.ReactNode;
|
|
56
|
-
/**
|
|
57
|
-
* CSS variable name (without leading `--`) for the sparkline color. Default
|
|
58
|
-
* `chart-1`. Also used as the fallback for breakdown rows that omit `colorVar`.
|
|
59
|
-
*/
|
|
60
|
-
defaultColorVar?: string;
|
|
61
|
-
/** Pixel height of the sparkline. Default `36`. */
|
|
62
|
-
sparkHeight?: number;
|
|
63
|
-
/** Category breakdown rows. */
|
|
64
|
-
breakdown?: MetricBreakdownRow[];
|
|
65
|
-
/** Format breakdown row values. Default `toLocaleString`. */
|
|
66
|
-
valueFormatter?: (value: number) => string;
|
|
67
|
-
/** Trailing chip row (e.g. recent error codes). */
|
|
68
|
-
chips?: MetricBreakdownChip[];
|
|
69
|
-
/**
|
|
70
|
-
* Label rendered before the chip row (uppercase muted). Default `"Errors"`.
|
|
71
|
-
* Pass `null` to hide.
|
|
72
|
-
*/
|
|
73
|
-
chipsLabel?: React.ReactNode;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Multi-section metric card: a headline value with an optional rate, a small
|
|
78
|
-
* inline trend sparkline, a per-category breakdown with delta arrows, and a
|
|
79
|
-
* trailing chip row for recent issues.
|
|
80
|
-
*
|
|
81
|
-
* Designed for "throughput-style" dashboards (token issuance, API request
|
|
82
|
-
* volume, job throughput, sign-ups by source): anywhere a single metric needs
|
|
83
|
-
* to be decomposed by category and contextualized by trend and notable issues
|
|
84
|
-
* in one card. Identity-agnostic; bring your own labels and color tokens.
|
|
85
|
-
*/
|
|
86
|
-
export const MetricBreakdown = React.forwardRef<HTMLDivElement, MetricBreakdownProps>(
|
|
87
|
-
(
|
|
88
|
-
{
|
|
89
|
-
value,
|
|
90
|
-
label,
|
|
91
|
-
rate,
|
|
92
|
-
rateLabel,
|
|
93
|
-
rateTone = "neutral",
|
|
94
|
-
spark,
|
|
95
|
-
sparkUnit,
|
|
96
|
-
defaultColorVar = "chart-1",
|
|
97
|
-
sparkHeight = 36,
|
|
98
|
-
breakdown,
|
|
99
|
-
valueFormatter = (v) => v.toLocaleString(),
|
|
100
|
-
chips,
|
|
101
|
-
chipsLabel = "Errors",
|
|
102
|
-
className,
|
|
103
|
-
...props
|
|
104
|
-
},
|
|
105
|
-
ref,
|
|
106
|
-
) => {
|
|
107
|
-
const rateColor =
|
|
108
|
-
rateTone === "neutral" ? "hsl(var(--muted-foreground))" : `hsl(${TONE_HSL[rateTone]})`;
|
|
109
|
-
const showChipsLabel = chipsLabel !== null && chipsLabel !== undefined && chipsLabel !== "";
|
|
110
|
-
return (
|
|
111
|
-
<div ref={ref} className={cn("w-full", className)} {...props}>
|
|
112
|
-
<div className="mb-2.5 flex items-baseline justify-between gap-3">
|
|
113
|
-
<div>
|
|
114
|
-
<div className="font-mono text-[22px] font-semibold leading-[1.1] tabular-nums">
|
|
115
|
-
{value}
|
|
116
|
-
</div>
|
|
117
|
-
<div className="text-[11px] uppercase tracking-[0.04em] text-muted-foreground">
|
|
118
|
-
{label}
|
|
119
|
-
</div>
|
|
120
|
-
</div>
|
|
121
|
-
{(rate !== undefined || rateLabel) && (
|
|
122
|
-
<div className="text-right">
|
|
123
|
-
<div
|
|
124
|
-
className="font-mono text-[13px] font-medium tabular-nums"
|
|
125
|
-
style={{ color: rateColor }}
|
|
126
|
-
>
|
|
127
|
-
{rate}
|
|
128
|
-
</div>
|
|
129
|
-
{rateLabel && (
|
|
130
|
-
<div className="text-[11px] uppercase tracking-[0.04em] text-muted-foreground">
|
|
131
|
-
{rateLabel}
|
|
132
|
-
</div>
|
|
133
|
-
)}
|
|
134
|
-
</div>
|
|
135
|
-
)}
|
|
136
|
-
</div>
|
|
137
|
-
|
|
138
|
-
{spark && spark.length > 1 && (
|
|
139
|
-
<MetricBreakdownSpark
|
|
140
|
-
data={spark}
|
|
141
|
-
height={sparkHeight}
|
|
142
|
-
colorVar={defaultColorVar}
|
|
143
|
-
unit={sparkUnit}
|
|
144
|
-
/>
|
|
145
|
-
)}
|
|
146
|
-
|
|
147
|
-
{breakdown && breakdown.length > 0 && (
|
|
148
|
-
<MetricBreakdownRows
|
|
149
|
-
rows={breakdown}
|
|
150
|
-
defaultColorVar={defaultColorVar}
|
|
151
|
-
valueFormatter={valueFormatter}
|
|
152
|
-
/>
|
|
153
|
-
)}
|
|
154
|
-
|
|
155
|
-
{chips && chips.length > 0 && (
|
|
156
|
-
<div
|
|
157
|
-
className={cn(
|
|
158
|
-
"flex flex-wrap items-center gap-1.5",
|
|
159
|
-
(breakdown && breakdown.length > 0) || spark
|
|
160
|
-
? "mt-3 border-t border-border pt-2.5"
|
|
161
|
-
: "",
|
|
162
|
-
)}
|
|
163
|
-
>
|
|
164
|
-
{showChipsLabel && (
|
|
165
|
-
<span className="mr-0.5 text-[10.5px] uppercase tracking-[0.04em] text-muted-foreground">
|
|
166
|
-
{chipsLabel}
|
|
167
|
-
</span>
|
|
168
|
-
)}
|
|
169
|
-
{chips.map((chip, i) => {
|
|
170
|
-
const tone = chip.tone ?? "error";
|
|
171
|
-
const hsl = TONE_HSL[tone];
|
|
172
|
-
return (
|
|
173
|
-
<span
|
|
174
|
-
key={`${i}-${typeof chip.label === "string" ? chip.label : i}`}
|
|
175
|
-
className="inline-flex items-center gap-1 rounded font-mono text-[10.5px]"
|
|
176
|
-
style={{
|
|
177
|
-
padding: "2px 6px",
|
|
178
|
-
background: `hsl(${hsl} / 0.1)`,
|
|
179
|
-
color: `hsl(${hsl})`,
|
|
180
|
-
}}
|
|
181
|
-
>
|
|
182
|
-
{chip.label}
|
|
183
|
-
{chip.count !== undefined && <span className="opacity-70">·{chip.count}</span>}
|
|
184
|
-
</span>
|
|
185
|
-
);
|
|
186
|
-
})}
|
|
187
|
-
</div>
|
|
188
|
-
)}
|
|
189
|
-
</div>
|
|
190
|
-
);
|
|
191
|
-
},
|
|
192
|
-
);
|
|
193
|
-
MetricBreakdown.displayName = "MetricBreakdown";
|
|
194
|
-
|
|
195
|
-
interface MetricBreakdownSparkProps {
|
|
196
|
-
data: number[];
|
|
197
|
-
height: number;
|
|
198
|
-
colorVar: string;
|
|
199
|
-
unit?: React.ReactNode;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function MetricBreakdownSpark({ data, height, colorVar, unit }: MetricBreakdownSparkProps) {
|
|
203
|
-
const max = Math.max(...data);
|
|
204
|
-
const min = Math.min(...data);
|
|
205
|
-
const range = max - min || 1;
|
|
206
|
-
const w = (data.length - 1) * 10;
|
|
207
|
-
const h = height;
|
|
208
|
-
const pts = data
|
|
209
|
-
.map((v, i) => {
|
|
210
|
-
const x = i * 10;
|
|
211
|
-
const y = h - 2 - ((v - min) / range) * (h - 6);
|
|
212
|
-
return `${x},${y}`;
|
|
213
|
-
})
|
|
214
|
-
.join(" ");
|
|
215
|
-
const area = `0,${h} ${pts} ${w},${h}`;
|
|
216
|
-
const last = data[data.length - 1];
|
|
217
|
-
const lastY = h - 2 - ((last - min) / range) * (h - 6);
|
|
218
|
-
const fillId = React.useId();
|
|
219
|
-
return (
|
|
220
|
-
<div className="relative mb-3.5" style={{ height }}>
|
|
221
|
-
<svg
|
|
222
|
-
viewBox={`0 0 ${w} ${h}`}
|
|
223
|
-
preserveAspectRatio="none"
|
|
224
|
-
className="h-full w-full overflow-visible"
|
|
225
|
-
aria-hidden
|
|
226
|
-
>
|
|
227
|
-
<defs>
|
|
228
|
-
<linearGradient id={fillId} x1="0" x2="0" y1="0" y2="1">
|
|
229
|
-
<stop offset="0%" stopColor={`hsl(var(--${colorVar}))`} stopOpacity="0.25" />
|
|
230
|
-
<stop offset="100%" stopColor={`hsl(var(--${colorVar}))`} stopOpacity="0" />
|
|
231
|
-
</linearGradient>
|
|
232
|
-
</defs>
|
|
233
|
-
<polygon points={area} fill={`url(#${fillId})`} />
|
|
234
|
-
<polyline
|
|
235
|
-
points={pts}
|
|
236
|
-
fill="none"
|
|
237
|
-
stroke={`hsl(var(--${colorVar}))`}
|
|
238
|
-
strokeWidth="1.5"
|
|
239
|
-
vectorEffect="non-scaling-stroke"
|
|
240
|
-
/>
|
|
241
|
-
<circle cx={w} cy={lastY} r="2.5" fill={`hsl(var(--${colorVar}))`} />
|
|
242
|
-
</svg>
|
|
243
|
-
{unit !== undefined && (
|
|
244
|
-
<div
|
|
245
|
-
className="absolute right-0 top-0 bg-card px-1 font-mono text-[11px]"
|
|
246
|
-
style={{ color: `hsl(var(--${colorVar}))` }}
|
|
247
|
-
>
|
|
248
|
-
{last}
|
|
249
|
-
{typeof unit === "string" ? ` ${unit}` : <> {unit}</>}
|
|
250
|
-
</div>
|
|
251
|
-
)}
|
|
252
|
-
</div>
|
|
253
|
-
);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
interface MetricBreakdownRowsProps {
|
|
257
|
-
rows: MetricBreakdownRow[];
|
|
258
|
-
defaultColorVar: string;
|
|
259
|
-
valueFormatter: (value: number) => string;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
function MetricBreakdownRows({ rows, defaultColorVar, valueFormatter }: MetricBreakdownRowsProps) {
|
|
263
|
-
const total = rows.reduce((sum, r) => sum + r.value, 0) || 1;
|
|
264
|
-
return (
|
|
265
|
-
<div className="flex flex-col gap-2">
|
|
266
|
-
{rows.map((row, i) => {
|
|
267
|
-
const colorVar = row.colorVar ?? defaultColorVar;
|
|
268
|
-
const pct = (row.value / total) * 100;
|
|
269
|
-
const hasDelta = row.delta !== undefined;
|
|
270
|
-
const up = hasDelta && (row.delta as number) >= 0;
|
|
271
|
-
const deltaHsl = up ? TONE_HSL.success : TONE_HSL.error;
|
|
272
|
-
return (
|
|
273
|
-
<div
|
|
274
|
-
key={`${i}-${typeof row.label === "string" ? row.label : i}`}
|
|
275
|
-
className="text-[12.5px]"
|
|
276
|
-
>
|
|
277
|
-
<div className="mb-1 flex items-center gap-2">
|
|
278
|
-
<span
|
|
279
|
-
className="size-2 shrink-0 rounded-sm"
|
|
280
|
-
style={{ background: `hsl(var(--${colorVar}))` }}
|
|
281
|
-
aria-hidden
|
|
282
|
-
/>
|
|
283
|
-
<span className="flex-1 truncate font-mono text-[11.5px] text-foreground">
|
|
284
|
-
{row.label}
|
|
285
|
-
</span>
|
|
286
|
-
<span className="font-mono text-[11.5px] tabular-nums">
|
|
287
|
-
{valueFormatter(row.value)}
|
|
288
|
-
</span>
|
|
289
|
-
{hasDelta && (
|
|
290
|
-
<span
|
|
291
|
-
className="min-w-[38px] text-right font-mono text-[10.5px] tabular-nums"
|
|
292
|
-
style={{ color: `hsl(${deltaHsl})` }}
|
|
293
|
-
>
|
|
294
|
-
{up ? "▲" : "▼"} {Math.abs(row.delta as number)}%
|
|
295
|
-
</span>
|
|
296
|
-
)}
|
|
297
|
-
</div>
|
|
298
|
-
<div
|
|
299
|
-
className="overflow-hidden rounded-full bg-muted"
|
|
300
|
-
style={{ height: 3 }}
|
|
301
|
-
aria-hidden
|
|
302
|
-
>
|
|
303
|
-
<div
|
|
304
|
-
className="h-full rounded-full"
|
|
305
|
-
style={{
|
|
306
|
-
width: `${pct}%`,
|
|
307
|
-
background: `hsl(var(--${colorVar}))`,
|
|
308
|
-
}}
|
|
309
|
-
/>
|
|
310
|
-
</div>
|
|
311
|
-
</div>
|
|
312
|
-
);
|
|
313
|
-
})}
|
|
314
|
-
</div>
|
|
315
|
-
);
|
|
316
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Reference primitives — pure pass-throughs. Theming via CSS in
|
|
5
|
-
* `<ChartContainer>` (`[&_.recharts-reference-line_[stroke='#ccc']]:stroke-border`).
|
|
6
|
-
* Wrapping breaks Recharts' child-by-type detection.
|
|
7
|
-
*/
|
|
8
|
-
export { ReferenceArea, ReferenceDot, ReferenceLine } from "recharts";
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
|
|
3
|
-
import { cn } from "../../lib/utils";
|
|
4
|
-
|
|
5
|
-
export type ServiceHealthStatus = "healthy" | "degraded" | "down";
|
|
6
|
-
|
|
7
|
-
export interface ServiceHealthItem {
|
|
8
|
-
/** Service name shown in the row. Used as the React key. */
|
|
9
|
-
name: string;
|
|
10
|
-
/** Status — drives the dot color. */
|
|
11
|
-
status: ServiceHealthStatus;
|
|
12
|
-
/**
|
|
13
|
-
* Right-aligned meta cells, monospace, muted-foreground. Pass an array of
|
|
14
|
-
* primitives or React nodes — each renders as its own cell.
|
|
15
|
-
*/
|
|
16
|
-
meta?: React.ReactNode[];
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface ServiceHealthListProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
20
|
-
/** Service rows rendered top-to-bottom. */
|
|
21
|
-
items: ServiceHealthItem[];
|
|
22
|
-
/** Caption rendered below the list. */
|
|
23
|
-
caption?: React.ReactNode;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const DOT_TOKENS: Record<ServiceHealthStatus, string> = {
|
|
27
|
-
healthy: "143 70% 45%",
|
|
28
|
-
degraded: "38 92% 50%",
|
|
29
|
-
down: "0 80% 60%",
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Vertical list of services with a colored status dot, name, and optional
|
|
34
|
-
* monospace meta cells (latency, uptime, region, …). The dot has a 3-px halo
|
|
35
|
-
* matching the status hue at 18% opacity to pull focus.
|
|
36
|
-
*/
|
|
37
|
-
export const ServiceHealthList = React.forwardRef<HTMLDivElement, ServiceHealthListProps>(
|
|
38
|
-
({ items, caption, className, ...props }, ref) => {
|
|
39
|
-
return (
|
|
40
|
-
<div ref={ref} className={cn("w-full", className)} {...props}>
|
|
41
|
-
<ul className="flex flex-col gap-2.5">
|
|
42
|
-
{items.map((item) => {
|
|
43
|
-
const hsl = DOT_TOKENS[item.status];
|
|
44
|
-
const isHealthy = item.status === "healthy";
|
|
45
|
-
return (
|
|
46
|
-
<li key={item.name} className="flex items-center gap-2.5 text-[13px]">
|
|
47
|
-
<span className="relative flex size-2 shrink-0">
|
|
48
|
-
{isHealthy && (
|
|
49
|
-
<span
|
|
50
|
-
aria-hidden
|
|
51
|
-
className="absolute inline-flex h-full w-full animate-ping rounded-full opacity-75"
|
|
52
|
-
style={{ background: `hsl(${hsl})` }}
|
|
53
|
-
/>
|
|
54
|
-
)}
|
|
55
|
-
<span
|
|
56
|
-
role="img"
|
|
57
|
-
aria-label={`Status: ${item.status}`}
|
|
58
|
-
className="relative inline-flex size-2 rounded-full"
|
|
59
|
-
style={{
|
|
60
|
-
background: `hsl(${hsl})`,
|
|
61
|
-
boxShadow: isHealthy
|
|
62
|
-
? `0 0 6px hsl(${hsl}), 0 0 0 3px hsl(${hsl} / 0.18)`
|
|
63
|
-
: `0 0 0 3px hsl(${hsl} / 0.18)`,
|
|
64
|
-
}}
|
|
65
|
-
/>
|
|
66
|
-
</span>
|
|
67
|
-
<span className="flex-1 font-medium">{item.name}</span>
|
|
68
|
-
{item.meta?.map((cell, i) => (
|
|
69
|
-
<span
|
|
70
|
-
key={`${item.name}-meta-${i}`}
|
|
71
|
-
className="font-mono text-[11.5px] text-muted-foreground"
|
|
72
|
-
>
|
|
73
|
-
{cell}
|
|
74
|
-
</span>
|
|
75
|
-
))}
|
|
76
|
-
</li>
|
|
77
|
-
);
|
|
78
|
-
})}
|
|
79
|
-
</ul>
|
|
80
|
-
{caption && <p className="mt-3 text-xs text-muted-foreground">{caption}</p>}
|
|
81
|
-
</div>
|
|
82
|
-
);
|
|
83
|
-
},
|
|
84
|
-
);
|
|
85
|
-
ServiceHealthList.displayName = "ServiceHealthList";
|