@burdenoff/microfe-theglobalfuel 2026.705.3 → 2026.705.4
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/dist/theglobalfuel/TheGlobalFuelRoot.js +54 -46
- package/dist/theglobalfuel/TheGlobalFuelRoot.js.map +1 -1
- package/dist/theglobalfuel/TheGlobalFuelRoutes.js +10 -6
- package/dist/theglobalfuel/TheGlobalFuelRoutes.js.map +1 -1
- package/dist/theglobalfuel/components/DataTable.js +126 -58
- package/dist/theglobalfuel/components/DataTable.js.map +1 -1
- package/dist/theglobalfuel/components/EmptyState.js +4 -4
- package/dist/theglobalfuel/components/EmptyState.js.map +1 -1
- package/dist/theglobalfuel/components/MetricsCard.js +2 -2
- package/dist/theglobalfuel/components/MetricsCard.js.map +1 -1
- package/dist/theglobalfuel/components/WorldMap.js +115 -0
- package/dist/theglobalfuel/components/WorldMap.js.map +1 -0
- package/dist/theglobalfuel/components/index.js +9 -0
- package/dist/theglobalfuel/components/viz/AllocationBar.js +68 -0
- package/dist/theglobalfuel/components/viz/AllocationBar.js.map +1 -0
- package/dist/theglobalfuel/components/viz/AreaChart.js +102 -0
- package/dist/theglobalfuel/components/viz/AreaChart.js.map +1 -0
- package/dist/theglobalfuel/components/viz/AvatarStack.js +2 -0
- package/dist/theglobalfuel/components/viz/BarChart.js +4 -0
- package/dist/theglobalfuel/components/viz/CertificateCard.js +4 -0
- package/dist/theglobalfuel/components/viz/DonutChart.js +109 -0
- package/dist/theglobalfuel/components/viz/DonutChart.js.map +1 -0
- package/dist/theglobalfuel/components/viz/HBarFunnel.js +3 -0
- package/dist/theglobalfuel/components/viz/Heatmap.js +3 -0
- package/dist/theglobalfuel/components/viz/KanbanBoard.js +2 -0
- package/dist/theglobalfuel/components/viz/LineChart.js +4 -0
- package/dist/theglobalfuel/components/viz/OrgTree.js +2 -0
- package/dist/theglobalfuel/components/viz/PathSteps.js +68 -0
- package/dist/theglobalfuel/components/viz/PathSteps.js.map +1 -0
- package/dist/theglobalfuel/components/viz/ProgressRing.js +6 -0
- package/dist/theglobalfuel/components/viz/ProgressRing.js.map +1 -0
- package/dist/theglobalfuel/components/viz/RatingStars.js +3 -0
- package/dist/theglobalfuel/components/viz/SegmentedTabs.js +3 -0
- package/dist/theglobalfuel/components/viz/SkillBar.js +3 -0
- package/dist/theglobalfuel/components/viz/Sparkline.js +74 -0
- package/dist/theglobalfuel/components/viz/Sparkline.js.map +1 -0
- package/dist/theglobalfuel/components/viz/StatCard.js +3 -0
- package/dist/theglobalfuel/components/viz/StepProgress.js +64 -0
- package/dist/theglobalfuel/components/viz/StepProgress.js.map +1 -0
- package/dist/theglobalfuel/components/viz/Timeline.js +66 -0
- package/dist/theglobalfuel/components/viz/Timeline.js.map +1 -0
- package/dist/theglobalfuel/components/viz/index.js +23 -0
- package/dist/theglobalfuel/components/viz/lib/Avatar.js +1 -0
- package/dist/theglobalfuel/components/viz/lib/CartesianFrame.js +119 -0
- package/dist/theglobalfuel/components/viz/lib/CartesianFrame.js.map +1 -0
- package/dist/theglobalfuel/components/viz/lib/ResponsiveSvg.js +35 -0
- package/dist/theglobalfuel/components/viz/lib/ResponsiveSvg.js.map +1 -0
- package/dist/theglobalfuel/components/viz/lib/format.js +17 -0
- package/dist/theglobalfuel/components/viz/lib/format.js.map +1 -0
- package/dist/theglobalfuel/components/viz/lib/geometry.js +61 -0
- package/dist/theglobalfuel/components/viz/lib/geometry.js.map +1 -0
- package/dist/theglobalfuel/components/viz/lib/hooks.js +39 -0
- package/dist/theglobalfuel/components/viz/lib/hooks.js.map +1 -0
- package/dist/theglobalfuel/components/viz/lib/palette.js +27 -0
- package/dist/theglobalfuel/components/viz/lib/palette.js.map +1 -0
- package/dist/theglobalfuel/pages/assets/AssetDetailPage.js +431 -316
- package/dist/theglobalfuel/pages/assets/AssetDetailPage.js.map +1 -1
- package/dist/theglobalfuel/pages/dashboard/DashboardPage.js +525 -227
- package/dist/theglobalfuel/pages/dashboard/DashboardPage.js.map +1 -1
- package/dist/theglobalfuel/pages/emissions/EmissionsPage.js +255 -206
- package/dist/theglobalfuel/pages/emissions/EmissionsPage.js.map +1 -1
- package/dist/theglobalfuel/pages/provenance/ProvenancePage.js +465 -0
- package/dist/theglobalfuel/pages/provenance/ProvenancePage.js.map +1 -0
- package/dist/theglobalfuel/pages/shipments/ShipmentsPage.js +356 -227
- package/dist/theglobalfuel/pages/shipments/ShipmentsPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -29,11 +29,11 @@ var s = {
|
|
|
29
29
|
return /* @__PURE__ */ a(o, {
|
|
30
30
|
treatment: p ? "glass" : "metric",
|
|
31
31
|
glow: p,
|
|
32
|
-
className: e("p-6", m && l[m], f),
|
|
32
|
+
className: e("p-5 sm:p-6 transition-transform duration-200 will-change-transform hover:-translate-y-0.5", m && l[m], f),
|
|
33
33
|
children: [/* @__PURE__ */ a("div", {
|
|
34
34
|
className: "flex items-center justify-between",
|
|
35
35
|
children: [/* @__PURE__ */ i("div", {
|
|
36
|
-
className: e("text-text-secondary", m && c[m]),
|
|
36
|
+
className: e("flex h-10 w-10 items-center justify-center rounded-lg bg-bg-sunken text-text-secondary", m && c[m]),
|
|
37
37
|
children: t
|
|
38
38
|
}), h ?? (u && _ && v ? /* @__PURE__ */ a("div", {
|
|
39
39
|
className: e("flex items-center gap-1 text-xs font-medium", _.color),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MetricsCard.js","names":[],"sources":["../../../src/theglobalfuel/components/MetricsCard.tsx"],"sourcesContent":["/**\n * Metrics Card Component\n * @module theglobalfuel/components\n */\n\nimport type { FC, ReactNode } from 'react';\nimport { TrendingUp, TrendingDown, Minus } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\nimport type { TrendDirection } from '../types';\n\ntype Accent = 'success' | 'warning' | 'error';\n\ninterface MetricsCardProps {\n icon: ReactNode;\n value: string | number;\n label: string;\n trend?: TrendDirection;\n trendValue?: string;\n className?: string;\n /** Render as the page's hero metric (glass treatment + accent glow). */\n featured?: boolean;\n /** Severity accent — tints the icon and adds a soft top-edge glow. */\n accent?: Accent;\n /** Optional pill shown in the top-right (e.g. \"Critical\"). */\n pill?: ReactNode;\n /** Optional slot under the value (e.g. a MiniBar or sub-label). */\n footer?: ReactNode;\n}\n\nconst trendConfig: Record<TrendDirection, { icon: typeof TrendingUp; color: string }> = {\n up: { icon: TrendingUp, color: 'text-status-success-text' },\n down: { icon: TrendingDown, color: 'text-status-error-text' },\n neutral: { icon: Minus, color: 'text-text-secondary' },\n};\n\nconst accentIcon: Record<Accent, string> = {\n success: 'text-status-success-text',\n warning: 'text-status-warning-text',\n error: 'text-status-error-text',\n};\n\nconst accentGlow: Record<Accent, string> = {\n success: 'shadow-[inset_0_1px_0_0_var(--color-status-success-text)]',\n warning: 'shadow-[inset_0_1px_0_0_var(--color-status-warning-text)]',\n error: 'shadow-[inset_0_1px_0_0_var(--color-status-error-text)]',\n};\n\nexport const MetricsCard: FC<MetricsCardProps> = ({\n icon,\n value,\n label,\n trend,\n trendValue,\n className,\n featured = false,\n accent,\n pill,\n footer,\n}) => {\n const trendInfo = trend ? trendConfig[trend] : null;\n const TrendIcon = trendInfo?.icon;\n\n return (\n <GlassCard\n treatment={featured ? 'glass' : 'metric'}\n glow={featured}\n className={cn('p-6'
|
|
1
|
+
{"version":3,"file":"MetricsCard.js","names":[],"sources":["../../../src/theglobalfuel/components/MetricsCard.tsx"],"sourcesContent":["/**\n * Metrics Card Component\n * @module theglobalfuel/components\n */\n\nimport type { FC, ReactNode } from 'react';\nimport { TrendingUp, TrendingDown, Minus } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\nimport type { TrendDirection } from '../types';\n\ntype Accent = 'success' | 'warning' | 'error';\n\ninterface MetricsCardProps {\n icon: ReactNode;\n value: string | number;\n label: string;\n trend?: TrendDirection;\n trendValue?: string;\n className?: string;\n /** Render as the page's hero metric (glass treatment + accent glow). */\n featured?: boolean;\n /** Severity accent — tints the icon and adds a soft top-edge glow. */\n accent?: Accent;\n /** Optional pill shown in the top-right (e.g. \"Critical\"). */\n pill?: ReactNode;\n /** Optional slot under the value (e.g. a MiniBar or sub-label). */\n footer?: ReactNode;\n}\n\nconst trendConfig: Record<TrendDirection, { icon: typeof TrendingUp; color: string }> = {\n up: { icon: TrendingUp, color: 'text-status-success-text' },\n down: { icon: TrendingDown, color: 'text-status-error-text' },\n neutral: { icon: Minus, color: 'text-text-secondary' },\n};\n\nconst accentIcon: Record<Accent, string> = {\n success: 'text-status-success-text',\n warning: 'text-status-warning-text',\n error: 'text-status-error-text',\n};\n\nconst accentGlow: Record<Accent, string> = {\n success: 'shadow-[inset_0_1px_0_0_var(--color-status-success-text)]',\n warning: 'shadow-[inset_0_1px_0_0_var(--color-status-warning-text)]',\n error: 'shadow-[inset_0_1px_0_0_var(--color-status-error-text)]',\n};\n\nexport const MetricsCard: FC<MetricsCardProps> = ({\n icon,\n value,\n label,\n trend,\n trendValue,\n className,\n featured = false,\n accent,\n pill,\n footer,\n}) => {\n const trendInfo = trend ? trendConfig[trend] : null;\n const TrendIcon = trendInfo?.icon;\n\n return (\n <GlassCard\n treatment={featured ? 'glass' : 'metric'}\n glow={featured}\n className={cn(\n 'p-5 sm:p-6 transition-transform duration-200 will-change-transform hover:-translate-y-0.5',\n accent && accentGlow[accent],\n className\n )}\n >\n <div className=\"flex items-center justify-between\">\n <div\n className={cn(\n 'flex h-10 w-10 items-center justify-center rounded-lg bg-bg-sunken text-text-secondary',\n accent && accentIcon[accent]\n )}\n >\n {icon}\n </div>\n {pill ??\n (trend && trendInfo && TrendIcon ? (\n <div className={cn('flex items-center gap-1 text-xs font-medium', trendInfo.color)}>\n <TrendIcon className=\"h-3.5 w-3.5\" />\n {trendValue && <span>{trendValue}</span>}\n </div>\n ) : null)}\n </div>\n <div className=\"mt-3\">\n <p className=\"text-2xl font-bold text-text-primary tabular-nums\">{value}</p>\n <p className=\"mt-1 text-sm text-text-secondary\">{label}</p>\n {footer && <div className=\"mt-3\">{footer}</div>}\n </div>\n </GlassCard>\n );\n};\n"],"mappings":";;;;;AA8BA,IAAM,IAAkF;CACtF,IAAI;EAAE,MAAM;EAAY,OAAO;EAA4B;CAC3D,MAAM;EAAE,MAAM;EAAc,OAAO;EAA0B;CAC7D,SAAS;EAAE,MAAM;EAAO,OAAO;EAAuB;CACvD,EAEK,IAAqC;CACzC,SAAS;CACT,SAAS;CACT,OAAO;CACR,EAEK,IAAqC;CACzC,SAAS;CACT,SAAS;CACT,OAAO;CACR,EAEY,KAAqC,EAChD,SACA,UACA,UACA,UACA,eACA,cACA,cAAW,IACX,WACA,SACA,gBACI;CACJ,IAAM,IAAY,IAAQ,EAAY,KAAS,MACzC,IAAY,GAAW;AAE7B,QACE,kBAAC,GAAD;EACE,WAAW,IAAW,UAAU;EAChC,MAAM;EACN,WAAW,EACT,6FACA,KAAU,EAAW,IACrB,EACD;YAPH,CASE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IACE,WAAW,EACT,0FACA,KAAU,EAAW,GACtB;cAEA;IACG,CAAA,EACL,MACE,KAAS,KAAa,IACrB,kBAAC,OAAD;IAAK,WAAW,EAAG,+CAA+C,EAAU,MAAM;cAAlF,CACE,kBAAC,GAAD,EAAW,WAAU,eAAgB,CAAA,EACpC,KAAc,kBAAC,QAAD,EAAA,UAAO,GAAkB,CAAA,CACpC;QACJ,MACF;MACN,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,KAAD;KAAG,WAAU;eAAqD;KAAU,CAAA;IAC5E,kBAAC,KAAD;KAAG,WAAU;eAAoC;KAAU,CAAA;IAC1D,KAAU,kBAAC,OAAD;KAAK,WAAU;eAAQ;KAAa,CAAA;IAC3C;KACI"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { cn as e } from "../utils/cn.js";
|
|
2
|
+
import { useMemo as t } from "react";
|
|
3
|
+
import { jsx as n, jsxs as r } from "react/jsx-runtime";
|
|
4
|
+
//#region src/theglobalfuel/components/WorldMap.tsx
|
|
5
|
+
var i = [
|
|
6
|
+
"M55,34 Q70,24 96,30 Q118,34 112,52 Q104,70 88,72 Q70,74 62,60 Q50,50 55,34 Z",
|
|
7
|
+
"M96,74 Q108,72 112,82 Q118,96 110,116 Q104,140 96,150 Q88,138 90,116 Q88,96 96,74 Z",
|
|
8
|
+
"M168,34 Q184,28 196,36 Q200,46 190,52 Q176,56 168,48 Q164,40 168,34 Z",
|
|
9
|
+
"M176,58 Q198,54 210,66 Q214,84 204,104 Q196,118 186,112 Q176,98 176,80 Q172,66 176,58 Z",
|
|
10
|
+
"M198,30 Q236,22 276,30 Q292,40 282,58 Q262,74 232,70 Q210,68 202,54 Q194,42 198,30 Z",
|
|
11
|
+
"M270,104 Q290,100 302,110 Q304,122 292,126 Q276,128 270,118 Q266,110 270,104 Z"
|
|
12
|
+
], a = (e) => ({
|
|
13
|
+
x: e.lng + 180,
|
|
14
|
+
y: 90 - e.lat
|
|
15
|
+
}), o = ({ points: o, className: s, showReadout: c = !0 }) => {
|
|
16
|
+
let l = t(() => o.map((e) => ({
|
|
17
|
+
...e,
|
|
18
|
+
...a(e)
|
|
19
|
+
})), [o]), u = l.find((e) => e.primary) ?? l[0];
|
|
20
|
+
return /* @__PURE__ */ r("div", {
|
|
21
|
+
className: e("flex flex-col gap-2", s),
|
|
22
|
+
children: [/* @__PURE__ */ n("div", {
|
|
23
|
+
className: "overflow-hidden rounded-xl border border-border-seam bg-bg-sunken/60",
|
|
24
|
+
children: /* @__PURE__ */ r("svg", {
|
|
25
|
+
viewBox: "0 0 360 180",
|
|
26
|
+
className: "h-full w-full",
|
|
27
|
+
role: "img",
|
|
28
|
+
"aria-label": "Asset location map",
|
|
29
|
+
children: [
|
|
30
|
+
/* @__PURE__ */ r("g", {
|
|
31
|
+
stroke: "var(--color-border-seam)",
|
|
32
|
+
strokeWidth: "0.4",
|
|
33
|
+
opacity: "0.5",
|
|
34
|
+
children: [[
|
|
35
|
+
30,
|
|
36
|
+
60,
|
|
37
|
+
90,
|
|
38
|
+
120,
|
|
39
|
+
150,
|
|
40
|
+
180,
|
|
41
|
+
210,
|
|
42
|
+
240,
|
|
43
|
+
270,
|
|
44
|
+
300,
|
|
45
|
+
330
|
|
46
|
+
].map((e) => /* @__PURE__ */ n("line", {
|
|
47
|
+
x1: e,
|
|
48
|
+
y1: "0",
|
|
49
|
+
x2: e,
|
|
50
|
+
y2: "180"
|
|
51
|
+
}, `m${e}`)), [
|
|
52
|
+
30,
|
|
53
|
+
60,
|
|
54
|
+
90,
|
|
55
|
+
120,
|
|
56
|
+
150
|
|
57
|
+
].map((e) => /* @__PURE__ */ n("line", {
|
|
58
|
+
x1: "0",
|
|
59
|
+
y1: e,
|
|
60
|
+
x2: "360",
|
|
61
|
+
y2: e
|
|
62
|
+
}, `p${e}`))]
|
|
63
|
+
}),
|
|
64
|
+
/* @__PURE__ */ n("g", {
|
|
65
|
+
fill: "var(--color-bg-surface)",
|
|
66
|
+
stroke: "var(--color-border-strong)",
|
|
67
|
+
strokeWidth: "0.5",
|
|
68
|
+
children: i.map((e, t) => /* @__PURE__ */ n("path", { d: e }, t))
|
|
69
|
+
}),
|
|
70
|
+
l.map((e, t) => /* @__PURE__ */ r("g", { children: [
|
|
71
|
+
e.primary && /* @__PURE__ */ n("circle", {
|
|
72
|
+
cx: e.x,
|
|
73
|
+
cy: e.y,
|
|
74
|
+
r: "6",
|
|
75
|
+
fill: "var(--color-status-success-text)",
|
|
76
|
+
opacity: "0.25",
|
|
77
|
+
className: "motion-safe:animate-vc-glow-pulse"
|
|
78
|
+
}),
|
|
79
|
+
/* @__PURE__ */ n("circle", {
|
|
80
|
+
cx: e.x,
|
|
81
|
+
cy: e.y,
|
|
82
|
+
r: "2.4",
|
|
83
|
+
fill: "var(--color-status-success-text)"
|
|
84
|
+
}),
|
|
85
|
+
/* @__PURE__ */ n("circle", {
|
|
86
|
+
cx: e.x,
|
|
87
|
+
cy: e.y,
|
|
88
|
+
r: "2.4",
|
|
89
|
+
fill: "none",
|
|
90
|
+
stroke: "var(--color-bg-canvas)",
|
|
91
|
+
strokeWidth: "0.6"
|
|
92
|
+
})
|
|
93
|
+
] }, t))
|
|
94
|
+
]
|
|
95
|
+
})
|
|
96
|
+
}), c && u && /* @__PURE__ */ r("div", {
|
|
97
|
+
className: "flex items-center justify-between px-1 text-xs text-text-tertiary",
|
|
98
|
+
children: [/* @__PURE__ */ n("span", {
|
|
99
|
+
className: "truncate",
|
|
100
|
+
children: u.label ?? "Location"
|
|
101
|
+
}), /* @__PURE__ */ r("span", {
|
|
102
|
+
className: "tabular-nums",
|
|
103
|
+
children: [
|
|
104
|
+
u.lat.toFixed(3),
|
|
105
|
+
", ",
|
|
106
|
+
u.lng.toFixed(3)
|
|
107
|
+
]
|
|
108
|
+
})]
|
|
109
|
+
})]
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
//#endregion
|
|
113
|
+
export { o as WorldMap };
|
|
114
|
+
|
|
115
|
+
//# sourceMappingURL=WorldMap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WorldMap.js","names":[],"sources":["../../../src/theglobalfuel/components/WorldMap.tsx"],"sourcesContent":["/**\n * WorldMap — a self-contained, tokenized SVG equirectangular locator.\n * @module theglobalfuel/components\n *\n * No external map tiles or libraries: a subtle graticule + simplified continent\n * silhouettes over which real asset coordinates are projected as glowing\n * markers. Every colour is a fe-libs semantic token so it themes automatically;\n * the SVG scales responsively via its viewBox. Latitude/longitude are projected\n * equirectangularly: x = lng + 180, y = 90 − lat (viewBox 0 0 360 180).\n */\n\nimport { useMemo, type FC } from 'react';\nimport { cn } from '../utils/cn';\n\nexport interface GeoPoint {\n lat: number;\n lng: number;\n label?: string;\n /** Highlighted (pulsing) marker — use for the focused asset. */\n primary?: boolean;\n}\n\ninterface WorldMapProps {\n points: GeoPoint[];\n className?: string;\n /** Show the coordinate readout of the primary point beneath the map. */\n showReadout?: boolean;\n}\n\n// Simplified continent silhouettes in the 360×180 equirectangular space\n// (x = lng+180, y = 90−lat). Deliberately stylised — a recognisable backdrop,\n// not a survey map — so it stays compact and dependency-free.\nconst CONTINENTS: string[] = [\n // North America\n 'M55,34 Q70,24 96,30 Q118,34 112,52 Q104,70 88,72 Q70,74 62,60 Q50,50 55,34 Z',\n // Central + South America\n 'M96,74 Q108,72 112,82 Q118,96 110,116 Q104,140 96,150 Q88,138 90,116 Q88,96 96,74 Z',\n // Europe\n 'M168,34 Q184,28 196,36 Q200,46 190,52 Q176,56 168,48 Q164,40 168,34 Z',\n // Africa\n 'M176,58 Q198,54 210,66 Q214,84 204,104 Q196,118 186,112 Q176,98 176,80 Q172,66 176,58 Z',\n // Asia\n 'M198,30 Q236,22 276,30 Q292,40 282,58 Q262,74 232,70 Q210,68 202,54 Q194,42 198,30 Z',\n // Australia\n 'M270,104 Q290,100 302,110 Q304,122 292,126 Q276,128 270,118 Q266,110 270,104 Z',\n];\n\nconst project = (p: GeoPoint) => ({ x: p.lng + 180, y: 90 - p.lat });\n\nexport const WorldMap: FC<WorldMapProps> = ({ points, className, showReadout = true }) => {\n const projected = useMemo(() => points.map((p) => ({ ...p, ...project(p) })), [points]);\n const primary = projected.find((p) => p.primary) ?? projected[0];\n\n return (\n <div className={cn('flex flex-col gap-2', className)}>\n <div className=\"overflow-hidden rounded-xl border border-border-seam bg-bg-sunken/60\">\n <svg viewBox=\"0 0 360 180\" className=\"h-full w-full\" role=\"img\" aria-label=\"Asset location map\">\n {/* graticule */}\n <g stroke=\"var(--color-border-seam)\" strokeWidth=\"0.4\" opacity=\"0.5\">\n {[30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330].map((x) => (\n <line key={`m${x}`} x1={x} y1=\"0\" x2={x} y2=\"180\" />\n ))}\n {[30, 60, 90, 120, 150].map((y) => (\n <line key={`p${y}`} x1=\"0\" y1={y} x2=\"360\" y2={y} />\n ))}\n </g>\n {/* continents */}\n <g fill=\"var(--color-bg-surface)\" stroke=\"var(--color-border-strong)\" strokeWidth=\"0.5\">\n {CONTINENTS.map((d, i) => (\n <path key={i} d={d} />\n ))}\n </g>\n {/* markers */}\n {projected.map((p, i) => (\n <g key={i}>\n {p.primary && (\n <circle\n cx={p.x}\n cy={p.y}\n r=\"6\"\n fill=\"var(--color-status-success-text)\"\n opacity=\"0.25\"\n className=\"motion-safe:animate-vc-glow-pulse\"\n />\n )}\n <circle cx={p.x} cy={p.y} r=\"2.4\" fill=\"var(--color-status-success-text)\" />\n <circle cx={p.x} cy={p.y} r=\"2.4\" fill=\"none\" stroke=\"var(--color-bg-canvas)\" strokeWidth=\"0.6\" />\n </g>\n ))}\n </svg>\n </div>\n {showReadout && primary && (\n <div className=\"flex items-center justify-between px-1 text-xs text-text-tertiary\">\n <span className=\"truncate\">{primary.label ?? 'Location'}</span>\n <span className=\"tabular-nums\">\n {primary.lat.toFixed(3)}, {primary.lng.toFixed(3)}\n </span>\n </div>\n )}\n </div>\n );\n};\n"],"mappings":";;;;AAgCA,IAAM,IAAuB;CAE3B;CAEA;CAEA;CAEA;CAEA;CAEA;CACD,EAEK,KAAW,OAAiB;CAAE,GAAG,EAAE,MAAM;CAAK,GAAG,KAAK,EAAE;CAAK,GAEtD,KAA+B,EAAE,WAAQ,cAAW,iBAAc,SAAW;CACxF,IAAM,IAAY,QAAc,EAAO,KAAK,OAAO;EAAE,GAAG;EAAG,GAAG,EAAQ,EAAE;EAAE,EAAE,EAAE,CAAC,EAAO,CAAC,EACjF,IAAU,EAAU,MAAM,MAAM,EAAE,QAAQ,IAAI,EAAU;AAE9D,QACE,kBAAC,OAAD;EAAK,WAAW,EAAG,uBAAuB,EAAU;YAApD,CACE,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,OAAD;IAAK,SAAQ;IAAc,WAAU;IAAgB,MAAK;IAAM,cAAW;cAA3E;KAEE,kBAAC,KAAD;MAAG,QAAO;MAA2B,aAAY;MAAM,SAAQ;gBAA/D,CACG;OAAC;OAAI;OAAI;OAAI;OAAK;OAAK;OAAK;OAAK;OAAK;OAAK;OAAK;OAAI,CAAC,KAAK,MACzD,kBAAC,QAAD;OAAoB,IAAI;OAAG,IAAG;OAAI,IAAI;OAAG,IAAG;OAAQ,EAAzC,IAAI,IAAqC,CACpD,EACD;OAAC;OAAI;OAAI;OAAI;OAAK;OAAI,CAAC,KAAK,MAC3B,kBAAC,QAAD;OAAoB,IAAG;OAAI,IAAI;OAAG,IAAG;OAAM,IAAI;OAAK,EAAzC,IAAI,IAAqC,CACpD,CACA;;KAEJ,kBAAC,KAAD;MAAG,MAAK;MAA0B,QAAO;MAA6B,aAAY;gBAC/E,EAAW,KAAK,GAAG,MAClB,kBAAC,QAAD,EAAiB,MAAK,EAAX,EAAW,CACtB;MACA,CAAA;KAEH,EAAU,KAAK,GAAG,MACjB,kBAAC,KAAD,EAAA,UAAA;MACG,EAAE,WACD,kBAAC,UAAD;OACE,IAAI,EAAE;OACN,IAAI,EAAE;OACN,GAAE;OACF,MAAK;OACL,SAAQ;OACR,WAAU;OACV,CAAA;MAEJ,kBAAC,UAAD;OAAQ,IAAI,EAAE;OAAG,IAAI,EAAE;OAAG,GAAE;OAAM,MAAK;OAAqC,CAAA;MAC5E,kBAAC,UAAD;OAAQ,IAAI,EAAE;OAAG,IAAI,EAAE;OAAG,GAAE;OAAM,MAAK;OAAO,QAAO;OAAyB,aAAY;OAAQ,CAAA;MAChG,EAAA,EAbI,EAaJ,CACJ;KACE;;GACF,CAAA,EACL,KAAe,KACd,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,QAAD;IAAM,WAAU;cAAY,EAAQ,SAAS;IAAkB,CAAA,EAC/D,kBAAC,QAAD;IAAM,WAAU;cAAhB;KACG,EAAQ,IAAI,QAAQ,EAAE;KAAC;KAAG,EAAQ,IAAI,QAAQ,EAAE;KAC5C;MACH;KAEJ"}
|
|
@@ -5,6 +5,14 @@ import "./MiniBar.js";
|
|
|
5
5
|
import "./DataTable.js";
|
|
6
6
|
import "./StateViews.js";
|
|
7
7
|
import "./EmptyState.js";
|
|
8
|
+
import "./viz/Sparkline.js";
|
|
9
|
+
import "./viz/AreaChart.js";
|
|
10
|
+
import "./viz/DonutChart.js";
|
|
11
|
+
import "./viz/Timeline.js";
|
|
12
|
+
import "./viz/AllocationBar.js";
|
|
13
|
+
import "./viz/StepProgress.js";
|
|
14
|
+
import "./viz/PathSteps.js";
|
|
15
|
+
import "./viz/index.js";
|
|
8
16
|
import "./FilterTabs.js";
|
|
9
17
|
import "./Tabs.js";
|
|
10
18
|
import "./Pagination.js";
|
|
@@ -12,3 +20,4 @@ import "./EvidenceUpload.js";
|
|
|
12
20
|
import "./FileLink.js";
|
|
13
21
|
import "./TheDiscussion.js";
|
|
14
22
|
import "./TheTags.js";
|
|
23
|
+
import "./WorldMap.js";
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { cn as e } from "../../utils/cn.js";
|
|
2
|
+
import { chartPalette as t } from "./lib/palette.js";
|
|
3
|
+
import { useMounted as n, usePrefersReducedMotion as r } from "./lib/hooks.js";
|
|
4
|
+
import { toPercent as i } from "./lib/format.js";
|
|
5
|
+
import { jsx as a, jsxs as o } from "react/jsx-runtime";
|
|
6
|
+
//#region src/theglobalfuel/components/viz/AllocationBar.tsx
|
|
7
|
+
function s({ segments: s, label: c, rightLabel: l, showLegend: u = !0, height: d = 12, className: f }) {
|
|
8
|
+
let p = n(), m = r();
|
|
9
|
+
if (s.length === 0) return null;
|
|
10
|
+
let h = s.reduce((e, t) => e + Math.max(0, t.value), 0) || 1, g = t(s.length), _ = s.map((e, t) => ({
|
|
11
|
+
segment: e,
|
|
12
|
+
color: e.color ?? g[t],
|
|
13
|
+
pct: i(e.value, h, 1)
|
|
14
|
+
}));
|
|
15
|
+
return /* @__PURE__ */ o("div", {
|
|
16
|
+
className: e("flex flex-col gap-2", f),
|
|
17
|
+
children: [
|
|
18
|
+
c !== void 0 || l !== void 0 ? /* @__PURE__ */ o("div", {
|
|
19
|
+
className: "flex items-baseline justify-between gap-3 text-sm",
|
|
20
|
+
children: [/* @__PURE__ */ a("span", {
|
|
21
|
+
className: "font-medium text-text-secondary",
|
|
22
|
+
children: c
|
|
23
|
+
}), /* @__PURE__ */ a("span", {
|
|
24
|
+
className: "font-semibold tabular-nums text-text-primary",
|
|
25
|
+
children: l
|
|
26
|
+
})]
|
|
27
|
+
}) : null,
|
|
28
|
+
/* @__PURE__ */ a("div", {
|
|
29
|
+
className: "flex w-full overflow-hidden rounded-full bg-bg-sunken",
|
|
30
|
+
style: { height: d },
|
|
31
|
+
role: "img",
|
|
32
|
+
"aria-label": _.map((e) => `${e.segment.label} ${e.pct}%`).join(", "),
|
|
33
|
+
children: _.map((e, t) => /* @__PURE__ */ a("div", {
|
|
34
|
+
title: `${e.segment.label}: ${e.pct}%`,
|
|
35
|
+
className: "h-full first:rounded-l-full last:rounded-r-full",
|
|
36
|
+
style: {
|
|
37
|
+
width: p || m ? `${e.pct}%` : "0%",
|
|
38
|
+
backgroundColor: e.color,
|
|
39
|
+
transition: m ? void 0 : "width 640ms cubic-bezier(0.22,1,0.36,1)",
|
|
40
|
+
transitionDelay: m ? void 0 : `${t * 60}ms`
|
|
41
|
+
}
|
|
42
|
+
}, `${e.segment.label}-${t}`))
|
|
43
|
+
}),
|
|
44
|
+
u ? /* @__PURE__ */ a("ul", {
|
|
45
|
+
className: "flex flex-wrap gap-x-4 gap-y-1 text-xs text-text-secondary",
|
|
46
|
+
children: _.map((e, t) => /* @__PURE__ */ o("li", {
|
|
47
|
+
className: "inline-flex items-center gap-1.5",
|
|
48
|
+
children: [
|
|
49
|
+
/* @__PURE__ */ a("span", {
|
|
50
|
+
"aria-hidden": "true",
|
|
51
|
+
className: "inline-block h-2.5 w-2.5 rounded-full",
|
|
52
|
+
style: { backgroundColor: e.color }
|
|
53
|
+
}),
|
|
54
|
+
/* @__PURE__ */ a("span", { children: e.segment.label }),
|
|
55
|
+
/* @__PURE__ */ o("span", {
|
|
56
|
+
className: "tabular-nums text-text-muted",
|
|
57
|
+
children: [e.pct, "%"]
|
|
58
|
+
})
|
|
59
|
+
]
|
|
60
|
+
}, `${e.segment.label}-${t}`))
|
|
61
|
+
}) : null
|
|
62
|
+
]
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
//#endregion
|
|
66
|
+
export { s as AllocationBar };
|
|
67
|
+
|
|
68
|
+
//# sourceMappingURL=AllocationBar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AllocationBar.js","names":[],"sources":["../../../../src/theglobalfuel/components/viz/AllocationBar.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { cn } from '../../utils/cn';\nimport { chartPalette } from './lib/palette';\nimport { toPercent } from './lib/format';\nimport { useMounted, usePrefersReducedMotion } from './lib/hooks';\nimport type { Segment } from './lib/types';\n\nexport interface AllocationBarProps {\n /** Segments that make up the bar; widths are proportional to their values. */\n segments: Segment[];\n /** Optional label shown above the bar (left). */\n label?: ReactNode;\n /** Headline value shown above the bar (right), e.g. \"82% utilized\". */\n rightLabel?: ReactNode;\n showLegend?: boolean;\n /** Bar height in pixels. */\n height?: number;\n className?: string;\n}\n\n/**\n * A single horizontal bar split into labeled, tokenized segments that sum to\n * 100% — ideal for utilization / capacity breakdowns.\n */\nexport function AllocationBar({\n segments,\n label,\n rightLabel,\n showLegend = true,\n height = 12,\n className,\n}: AllocationBarProps) {\n const mounted = useMounted();\n const reduced = usePrefersReducedMotion();\n\n if (segments.length === 0) {\n return null;\n }\n\n const total = segments.reduce((acc, s) => acc + Math.max(0, s.value), 0) || 1;\n const palette = chartPalette(segments.length);\n const parts = segments.map((segment, i) => ({\n segment,\n color: segment.color ?? palette[i],\n pct: toPercent(segment.value, total, 1),\n }));\n\n return (\n <div className={cn('flex flex-col gap-2', className)}>\n {label !== undefined || rightLabel !== undefined ? (\n <div className=\"flex items-baseline justify-between gap-3 text-sm\">\n <span className=\"font-medium text-text-secondary\">{label}</span>\n <span className=\"font-semibold tabular-nums text-text-primary\">{rightLabel}</span>\n </div>\n ) : null}\n <div\n className=\"flex w-full overflow-hidden rounded-full bg-bg-sunken\"\n style={{ height }}\n role=\"img\"\n aria-label={parts.map((p) => `${p.segment.label} ${p.pct}%`).join(', ')}\n >\n {parts.map((part, i) => (\n <div\n key={`${part.segment.label}-${i}`}\n title={`${part.segment.label}: ${part.pct}%`}\n className=\"h-full first:rounded-l-full last:rounded-r-full\"\n style={{\n width: mounted || reduced ? `${part.pct}%` : '0%',\n backgroundColor: part.color,\n transition: reduced ? undefined : 'width 640ms cubic-bezier(0.22,1,0.36,1)',\n transitionDelay: reduced ? undefined : `${i * 60}ms`,\n }}\n />\n ))}\n </div>\n {showLegend ? (\n <ul className=\"flex flex-wrap gap-x-4 gap-y-1 text-xs text-text-secondary\">\n {parts.map((part, i) => (\n <li key={`${part.segment.label}-${i}`} className=\"inline-flex items-center gap-1.5\">\n <span\n aria-hidden=\"true\"\n className=\"inline-block h-2.5 w-2.5 rounded-full\"\n style={{ backgroundColor: part.color }}\n />\n <span>{part.segment.label}</span>\n <span className=\"tabular-nums text-text-muted\">{part.pct}%</span>\n </li>\n ))}\n </ul>\n ) : null}\n </div>\n );\n}\n"],"mappings":";;;;;;AAwBA,SAAgB,EAAc,EAC5B,aACA,UACA,eACA,gBAAa,IACb,YAAS,IACT,gBACqB;CACrB,IAAM,IAAU,GAAY,EACtB,IAAU,GAAyB;AAEzC,KAAI,EAAS,WAAW,EACtB,QAAO;CAGT,IAAM,IAAQ,EAAS,QAAQ,GAAK,MAAM,IAAM,KAAK,IAAI,GAAG,EAAE,MAAM,EAAE,EAAE,IAAI,GACtE,IAAU,EAAa,EAAS,OAAO,EACvC,IAAQ,EAAS,KAAK,GAAS,OAAO;EAC1C;EACA,OAAO,EAAQ,SAAS,EAAQ;EAChC,KAAK,EAAU,EAAQ,OAAO,GAAO,EAAE;EACxC,EAAE;AAEH,QACE,kBAAC,OAAD;EAAK,WAAW,EAAG,uBAAuB,EAAU;YAApD;GACG,MAAU,KAAA,KAAa,MAAe,KAAA,IACrC,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,QAAD;KAAM,WAAU;eAAmC;KAAa,CAAA,EAChE,kBAAC,QAAD;KAAM,WAAU;eAAgD;KAAkB,CAAA,CAC9E;QACJ;GACJ,kBAAC,OAAD;IACE,WAAU;IACV,OAAO,EAAE,WAAQ;IACjB,MAAK;IACL,cAAY,EAAM,KAAK,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG,EAAE,IAAI,GAAG,CAAC,KAAK,KAAK;cAEtE,EAAM,KAAK,GAAM,MAChB,kBAAC,OAAD;KAEE,OAAO,GAAG,EAAK,QAAQ,MAAM,IAAI,EAAK,IAAI;KAC1C,WAAU;KACV,OAAO;MACL,OAAO,KAAW,IAAU,GAAG,EAAK,IAAI,KAAK;MAC7C,iBAAiB,EAAK;MACtB,YAAY,IAAU,KAAA,IAAY;MAClC,iBAAiB,IAAU,KAAA,IAAY,GAAG,IAAI,GAAG;MAClD;KACD,EATK,GAAG,EAAK,QAAQ,MAAM,GAAG,IAS9B,CACF;IACE,CAAA;GACL,IACC,kBAAC,MAAD;IAAI,WAAU;cACX,EAAM,KAAK,GAAM,MAChB,kBAAC,MAAD;KAAuC,WAAU;eAAjD;MACE,kBAAC,QAAD;OACE,eAAY;OACZ,WAAU;OACV,OAAO,EAAE,iBAAiB,EAAK,OAAO;OACtC,CAAA;MACF,kBAAC,QAAD,EAAA,UAAO,EAAK,QAAQ,OAAa,CAAA;MACjC,kBAAC,QAAD;OAAM,WAAU;iBAAhB,CAAgD,EAAK,KAAI,IAAQ;;MAC9D;OARI,GAAG,EAAK,QAAQ,MAAM,GAAG,IAQ7B,CACL;IACC,CAAA,GACH;GACA"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { ACCENT as e } from "./lib/palette.js";
|
|
2
|
+
import { useMounted as t, usePrefersReducedMotion as n, useVizId as r } from "./lib/hooks.js";
|
|
3
|
+
import { buildAreaPath as i, buildLinePath as a } from "./lib/geometry.js";
|
|
4
|
+
import { CartesianFrame as o } from "./lib/CartesianFrame.js";
|
|
5
|
+
import { formatCompact as s } from "./lib/format.js";
|
|
6
|
+
import { jsx as c, jsxs as l } from "react/jsx-runtime";
|
|
7
|
+
//#region src/theglobalfuel/components/viz/AreaChart.tsx
|
|
8
|
+
function u({ series: u, height: d = 200, valueFormatter: f = s, title: p, className: m }) {
|
|
9
|
+
let h = r("area-grad"), g = t(), _ = n();
|
|
10
|
+
if (u.length === 0) return null;
|
|
11
|
+
let v = u.map((e) => e.value), y = u.map((e) => e.label), b = Math.min(...v), x = Math.max(...v), S = x - b || Math.abs(x) || 1;
|
|
12
|
+
return /* @__PURE__ */ c(o, {
|
|
13
|
+
labels: y,
|
|
14
|
+
domain: [b - S * .12, x + S * .12],
|
|
15
|
+
height: d,
|
|
16
|
+
title: p ?? `Area chart of ${u.length} points`,
|
|
17
|
+
formatValue: f,
|
|
18
|
+
className: m,
|
|
19
|
+
tooltip: (e) => /* @__PURE__ */ l("span", { children: [/* @__PURE__ */ l("span", {
|
|
20
|
+
className: "text-text-secondary",
|
|
21
|
+
children: [y[e], ": "]
|
|
22
|
+
}), /* @__PURE__ */ c("span", {
|
|
23
|
+
className: "font-semibold tabular-nums",
|
|
24
|
+
children: f(v[e])
|
|
25
|
+
})] }),
|
|
26
|
+
children: ({ px: t, py: n, plotBottom: r, activeIndex: o }) => {
|
|
27
|
+
let s = v.map((e, r) => ({
|
|
28
|
+
x: t(r),
|
|
29
|
+
y: n(e)
|
|
30
|
+
})), u = a(s), d = i(s, r), f = s[s.length - 1];
|
|
31
|
+
return /* @__PURE__ */ l("g", {
|
|
32
|
+
style: { color: e },
|
|
33
|
+
children: [
|
|
34
|
+
/* @__PURE__ */ c("defs", { children: /* @__PURE__ */ l("linearGradient", {
|
|
35
|
+
id: h,
|
|
36
|
+
x1: "0",
|
|
37
|
+
y1: "0",
|
|
38
|
+
x2: "0",
|
|
39
|
+
y2: "1",
|
|
40
|
+
children: [/* @__PURE__ */ c("stop", {
|
|
41
|
+
offset: "0%",
|
|
42
|
+
stopColor: "currentColor",
|
|
43
|
+
stopOpacity: .34
|
|
44
|
+
}), /* @__PURE__ */ c("stop", {
|
|
45
|
+
offset: "100%",
|
|
46
|
+
stopColor: "currentColor",
|
|
47
|
+
stopOpacity: .02
|
|
48
|
+
})]
|
|
49
|
+
}) }),
|
|
50
|
+
/* @__PURE__ */ c("path", {
|
|
51
|
+
d,
|
|
52
|
+
fill: `url(#${h})`,
|
|
53
|
+
stroke: "none",
|
|
54
|
+
style: {
|
|
55
|
+
opacity: g || _ ? 1 : 0,
|
|
56
|
+
transition: _ ? void 0 : "opacity 700ms ease"
|
|
57
|
+
}
|
|
58
|
+
}),
|
|
59
|
+
/* @__PURE__ */ c("path", {
|
|
60
|
+
d: u,
|
|
61
|
+
fill: "none",
|
|
62
|
+
stroke: "currentColor",
|
|
63
|
+
strokeWidth: 2,
|
|
64
|
+
strokeLinecap: "round",
|
|
65
|
+
strokeLinejoin: "round",
|
|
66
|
+
vectorEffect: "non-scaling-stroke",
|
|
67
|
+
pathLength: 1,
|
|
68
|
+
style: {
|
|
69
|
+
strokeDasharray: 1,
|
|
70
|
+
strokeDashoffset: g || _ ? 0 : 1,
|
|
71
|
+
transition: _ ? void 0 : "stroke-dashoffset 900ms ease"
|
|
72
|
+
}
|
|
73
|
+
}),
|
|
74
|
+
/* @__PURE__ */ c("circle", {
|
|
75
|
+
cx: f.x,
|
|
76
|
+
cy: f.y,
|
|
77
|
+
r: 3.5,
|
|
78
|
+
fill: "currentColor"
|
|
79
|
+
}),
|
|
80
|
+
o === null ? null : /* @__PURE__ */ l("g", { children: [/* @__PURE__ */ c("circle", {
|
|
81
|
+
cx: t(o),
|
|
82
|
+
cy: n(v[o]),
|
|
83
|
+
r: 7,
|
|
84
|
+
fill: "currentColor",
|
|
85
|
+
opacity: .16
|
|
86
|
+
}), /* @__PURE__ */ c("circle", {
|
|
87
|
+
cx: t(o),
|
|
88
|
+
cy: n(v[o]),
|
|
89
|
+
r: 3.5,
|
|
90
|
+
fill: "currentColor",
|
|
91
|
+
stroke: "var(--color-bg-surface)",
|
|
92
|
+
strokeWidth: 1.5
|
|
93
|
+
})] })
|
|
94
|
+
]
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
//#endregion
|
|
100
|
+
export { u as AreaChart };
|
|
101
|
+
|
|
102
|
+
//# sourceMappingURL=AreaChart.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AreaChart.js","names":[],"sources":["../../../../src/theglobalfuel/components/viz/AreaChart.tsx"],"sourcesContent":["import { ACCENT } from './lib/palette';\nimport { CartesianFrame } from './lib/CartesianFrame';\nimport { buildAreaPath, buildLinePath } from './lib/geometry';\nimport { formatCompact } from './lib/format';\nimport { useMounted, usePrefersReducedMotion, useVizId } from './lib/hooks';\nimport type { LabeledValue } from './lib/types';\n\nexport interface AreaChartProps {\n series: LabeledValue[];\n /** Pixel height of the plot. */\n height?: number;\n /** Formats axis + tooltip values. Defaults to compact notation. */\n valueFormatter?: (value: number) => string;\n /** Accessible title; defaults to a generic description. */\n title?: string;\n className?: string;\n}\n\n/**\n * Gradient-fill area chart with x/y axes, faint grid and a pointer/keyboard\n * hover readout. Draws itself in on mount unless reduced-motion is requested.\n */\nexport function AreaChart({\n series,\n height = 200,\n valueFormatter = formatCompact,\n title,\n className,\n}: AreaChartProps) {\n const gradientId = useVizId('area-grad');\n const mounted = useMounted();\n const reduced = usePrefersReducedMotion();\n\n if (series.length === 0) {\n return null;\n }\n\n const values = series.map((s) => s.value);\n const labels = series.map((s) => s.label);\n const min = Math.min(...values);\n const max = Math.max(...values);\n const span = max - min || Math.abs(max) || 1;\n const domain: [number, number] = [min - span * 0.12, max + span * 0.12];\n const chartTitle = title ?? `Area chart of ${series.length} points`;\n\n return (\n <CartesianFrame\n labels={labels}\n domain={domain}\n height={height}\n title={chartTitle}\n formatValue={valueFormatter}\n className={className}\n tooltip={(i) => (\n <span>\n <span className=\"text-text-secondary\">{labels[i]}: </span>\n <span className=\"font-semibold tabular-nums\">{valueFormatter(values[i])}</span>\n </span>\n )}\n >\n {({ px, py, plotBottom, activeIndex }) => {\n const points = values.map((v, i) => ({ x: px(i), y: py(v) }));\n const line = buildLinePath(points);\n const area = buildAreaPath(points, plotBottom);\n const last = points[points.length - 1];\n return (\n <g style={{ color: ACCENT }}>\n <defs>\n <linearGradient id={gradientId} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"0%\" stopColor=\"currentColor\" stopOpacity={0.34} />\n <stop offset=\"100%\" stopColor=\"currentColor\" stopOpacity={0.02} />\n </linearGradient>\n </defs>\n <path\n d={area}\n fill={`url(#${gradientId})`}\n stroke=\"none\"\n style={{\n opacity: mounted || reduced ? 1 : 0,\n transition: reduced ? undefined : 'opacity 700ms ease',\n }}\n />\n <path\n d={line}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n vectorEffect=\"non-scaling-stroke\"\n pathLength={1}\n style={{\n strokeDasharray: 1,\n strokeDashoffset: mounted || reduced ? 0 : 1,\n transition: reduced ? undefined : 'stroke-dashoffset 900ms ease',\n }}\n />\n <circle cx={last.x} cy={last.y} r={3.5} fill=\"currentColor\" />\n {activeIndex !== null ? (\n <g>\n <circle\n cx={px(activeIndex)}\n cy={py(values[activeIndex])}\n r={7}\n fill=\"currentColor\"\n opacity={0.16}\n />\n <circle\n cx={px(activeIndex)}\n cy={py(values[activeIndex])}\n r={3.5}\n fill=\"currentColor\"\n stroke=\"var(--color-bg-surface)\"\n strokeWidth={1.5}\n />\n </g>\n ) : null}\n </g>\n );\n }}\n </CartesianFrame>\n );\n}\n"],"mappings":";;;;;;;AAsBA,SAAgB,EAAU,EACxB,WACA,YAAS,KACT,oBAAiB,GACjB,UACA,gBACiB;CACjB,IAAM,IAAa,EAAS,YAAY,EAClC,IAAU,GAAY,EACtB,IAAU,GAAyB;AAEzC,KAAI,EAAO,WAAW,EACpB,QAAO;CAGT,IAAM,IAAS,EAAO,KAAK,MAAM,EAAE,MAAM,EACnC,IAAS,EAAO,KAAK,MAAM,EAAE,MAAM,EACnC,IAAM,KAAK,IAAI,GAAG,EAAO,EACzB,IAAM,KAAK,IAAI,GAAG,EAAO,EACzB,IAAO,IAAM,KAAO,KAAK,IAAI,EAAI,IAAI;AAI3C,QACE,kBAAC,GAAD;EACU;EACA,QANqB,CAAC,IAAM,IAAO,KAAM,IAAM,IAAO,IAAK;EAO3D;EACR,OAPe,KAAS,iBAAiB,EAAO,OAAO;EAQvD,aAAa;EACF;EACX,UAAU,MACR,kBAAC,QAAD,EAAA,UAAA,CACE,kBAAC,QAAD;GAAM,WAAU;aAAhB,CAAuC,EAAO,IAAG,KAAS;MAC1D,kBAAC,QAAD;GAAM,WAAU;aAA8B,EAAe,EAAO,GAAG;GAAQ,CAAA,CAC1E,EAAA,CAAA;aAGP,EAAE,OAAI,OAAI,eAAY,qBAAkB;GACxC,IAAM,IAAS,EAAO,KAAK,GAAG,OAAO;IAAE,GAAG,EAAG,EAAE;IAAE,GAAG,EAAG,EAAE;IAAE,EAAE,EACvD,IAAO,EAAc,EAAO,EAC5B,IAAO,EAAc,GAAQ,EAAW,EACxC,IAAO,EAAO,EAAO,SAAS;AACpC,UACE,kBAAC,KAAD;IAAG,OAAO,EAAE,OAAO,GAAQ;cAA3B;KACE,kBAAC,QAAD,EAAA,UACE,kBAAC,kBAAD;MAAgB,IAAI;MAAY,IAAG;MAAI,IAAG;MAAI,IAAG;MAAI,IAAG;gBAAxD,CACE,kBAAC,QAAD;OAAM,QAAO;OAAK,WAAU;OAAe,aAAa;OAAQ,CAAA,EAChE,kBAAC,QAAD;OAAM,QAAO;OAAO,WAAU;OAAe,aAAa;OAAQ,CAAA,CACnD;SACZ,CAAA;KACP,kBAAC,QAAD;MACK;MACH,MAAM,QAAQ,EAAW;MACzB,QAAO;MACP,OAAO;OACL,SAAS,KAAW,IAAU,IAAI;OAClC,YAAY,IAAU,KAAA,IAAY;OACnC;MACD,CAAA;KACF,kBAAC,QAAD;MACE,GAAG;MACH,MAAK;MACL,QAAO;MACP,aAAa;MACb,eAAc;MACd,gBAAe;MACf,cAAa;MACb,YAAY;MACZ,OAAO;OACL,iBAAiB;OACjB,kBAAkB,KAAW,IAAU,IAAI;OAC3C,YAAY,IAAU,KAAA,IAAY;OACnC;MACD,CAAA;KACF,kBAAC,UAAD;MAAQ,IAAI,EAAK;MAAG,IAAI,EAAK;MAAG,GAAG;MAAK,MAAK;MAAiB,CAAA;KAC7D,MAAgB,OAkBb,OAjBF,kBAAC,KAAD,EAAA,UAAA,CACE,kBAAC,UAAD;MACE,IAAI,EAAG,EAAY;MACnB,IAAI,EAAG,EAAO,GAAa;MAC3B,GAAG;MACH,MAAK;MACL,SAAS;MACT,CAAA,EACF,kBAAC,UAAD;MACE,IAAI,EAAG,EAAY;MACnB,IAAI,EAAG,EAAO,GAAa;MAC3B,GAAG;MACH,MAAK;MACL,QAAO;MACP,aAAa;MACb,CAAA,CACA,EAAA,CAAA;KAEJ;;;EAGO,CAAA"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { cn as e } from "../../utils/cn.js";
|
|
2
|
+
import { chartPalette as t } from "./lib/palette.js";
|
|
3
|
+
import { useMounted as n, usePrefersReducedMotion as r } from "./lib/hooks.js";
|
|
4
|
+
import { donutSegmentPath as i } from "./lib/geometry.js";
|
|
5
|
+
import { formatCompact as a, toPercent as o } from "./lib/format.js";
|
|
6
|
+
import { jsx as s, jsxs as c } from "react/jsx-runtime";
|
|
7
|
+
//#region src/theglobalfuel/components/viz/DonutChart.tsx
|
|
8
|
+
var l = 100, u = l / 2, d = 46;
|
|
9
|
+
function f({ segments: f, centerLabel: p, centerCaption: m, size: h = 168, thickness: g = .28, valueFormatter: _ = a, showLegend: v = !0, title: y, className: b }) {
|
|
10
|
+
let x = n(), S = r();
|
|
11
|
+
if (f.length === 0) return null;
|
|
12
|
+
let C = f.reduce((e, t) => e + Math.max(0, t.value), 0), w = t(f.length), T = d * (1 - g), E = y ?? `Donut chart of ${f.length} segments`, D = f.map((e) => C > 0 ? Math.max(0, e.value) / C : 0), O = D.map((e, t) => D.slice(0, t).reduce((e, t) => e + t, 0)), k = f.map((e, t) => {
|
|
13
|
+
let n = D[t], r = O[t] * 360, i = Math.min((O[t] + n) * 360, 359.999);
|
|
14
|
+
return {
|
|
15
|
+
segment: e,
|
|
16
|
+
color: e.color ?? w[t],
|
|
17
|
+
startAngle: r,
|
|
18
|
+
endAngle: i,
|
|
19
|
+
fraction: n,
|
|
20
|
+
isFull: n >= .9999
|
|
21
|
+
};
|
|
22
|
+
});
|
|
23
|
+
return /* @__PURE__ */ c("div", {
|
|
24
|
+
className: e("flex flex-wrap items-center gap-5", b),
|
|
25
|
+
children: [/* @__PURE__ */ c("div", {
|
|
26
|
+
className: "relative shrink-0",
|
|
27
|
+
style: {
|
|
28
|
+
width: h,
|
|
29
|
+
height: h,
|
|
30
|
+
maxWidth: "100%"
|
|
31
|
+
},
|
|
32
|
+
children: [/* @__PURE__ */ c("svg", {
|
|
33
|
+
viewBox: `0 0 ${l} ${l}`,
|
|
34
|
+
width: h,
|
|
35
|
+
height: h,
|
|
36
|
+
role: "img",
|
|
37
|
+
"aria-label": E,
|
|
38
|
+
style: {
|
|
39
|
+
display: "block",
|
|
40
|
+
maxWidth: "100%",
|
|
41
|
+
height: "auto"
|
|
42
|
+
},
|
|
43
|
+
children: [
|
|
44
|
+
/* @__PURE__ */ s("title", { children: E }),
|
|
45
|
+
/* @__PURE__ */ s("desc", { children: f.map((e) => `${e.label}: ${_(e.value)} (${o(e.value, C)}%)`).join(", ") }),
|
|
46
|
+
/* @__PURE__ */ s("g", {
|
|
47
|
+
style: {
|
|
48
|
+
transformOrigin: "center",
|
|
49
|
+
transform: x || S ? "scale(1)" : "scale(0.86)",
|
|
50
|
+
transition: S ? void 0 : "transform 520ms cubic-bezier(0.22,1,0.36,1)"
|
|
51
|
+
},
|
|
52
|
+
children: k.map((e, t) => {
|
|
53
|
+
let n = {
|
|
54
|
+
fill: e.color,
|
|
55
|
+
style: {
|
|
56
|
+
opacity: x || S ? 1 : 0,
|
|
57
|
+
transition: S ? void 0 : "opacity 460ms ease",
|
|
58
|
+
transitionDelay: S ? void 0 : `${t * 70}ms`
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
return e.isFull ? /* @__PURE__ */ s("path", {
|
|
62
|
+
d: `M ${u} ${u - d} A ${d} ${d} 0 1 0 ${u} ${u + d} A ${d} ${d} 0 1 0 ${u} ${u - d} M ${u} ${u - T} A ${T} ${T} 0 1 1 ${u} ${u + T} A ${T} ${T} 0 1 1 ${u} ${u - T} Z`,
|
|
63
|
+
fillRule: "evenodd",
|
|
64
|
+
...n
|
|
65
|
+
}, `${e.segment.label}-${t}`) : /* @__PURE__ */ s("path", {
|
|
66
|
+
d: i(u, u, d, T, e.startAngle, e.endAngle),
|
|
67
|
+
...n,
|
|
68
|
+
children: /* @__PURE__ */ s("title", { children: `${e.segment.label}: ${_(e.segment.value)}` })
|
|
69
|
+
}, `${e.segment.label}-${t}`);
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
]
|
|
73
|
+
}), p !== void 0 || m !== void 0 ? /* @__PURE__ */ c("div", {
|
|
74
|
+
className: "pointer-events-none absolute inset-0 flex flex-col items-center justify-center text-center",
|
|
75
|
+
children: [/* @__PURE__ */ s("span", {
|
|
76
|
+
className: "text-xl font-semibold leading-none text-text-primary",
|
|
77
|
+
children: p
|
|
78
|
+
}), m === void 0 ? null : /* @__PURE__ */ s("span", {
|
|
79
|
+
className: "mt-1 text-xs text-text-muted",
|
|
80
|
+
children: m
|
|
81
|
+
})]
|
|
82
|
+
}) : null]
|
|
83
|
+
}), v ? /* @__PURE__ */ s("ul", {
|
|
84
|
+
className: "flex min-w-[8rem] flex-1 flex-col gap-1.5 text-sm",
|
|
85
|
+
children: k.map((e, t) => /* @__PURE__ */ c("li", {
|
|
86
|
+
className: "flex items-center gap-2",
|
|
87
|
+
children: [
|
|
88
|
+
/* @__PURE__ */ s("span", {
|
|
89
|
+
"aria-hidden": "true",
|
|
90
|
+
className: "inline-block h-2.5 w-2.5 shrink-0 rounded-full",
|
|
91
|
+
style: { backgroundColor: e.color }
|
|
92
|
+
}),
|
|
93
|
+
/* @__PURE__ */ s("span", {
|
|
94
|
+
className: "flex-1 truncate text-text-secondary",
|
|
95
|
+
children: e.segment.label
|
|
96
|
+
}),
|
|
97
|
+
/* @__PURE__ */ c("span", {
|
|
98
|
+
className: "tabular-nums text-text-muted",
|
|
99
|
+
children: [o(e.segment.value, C), "%"]
|
|
100
|
+
})
|
|
101
|
+
]
|
|
102
|
+
}, `${e.segment.label}-${t}`))
|
|
103
|
+
}) : null]
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
//#endregion
|
|
107
|
+
export { f as DonutChart };
|
|
108
|
+
|
|
109
|
+
//# sourceMappingURL=DonutChart.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DonutChart.js","names":[],"sources":["../../../../src/theglobalfuel/components/viz/DonutChart.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { cn } from '../../utils/cn';\nimport { chartPalette } from './lib/palette';\nimport { donutSegmentPath } from './lib/geometry';\nimport { formatCompact, toPercent } from './lib/format';\nimport { useMounted, usePrefersReducedMotion } from './lib/hooks';\nimport type { Segment } from './lib/types';\n\nexport interface DonutChartProps {\n segments: Segment[];\n centerLabel?: ReactNode;\n /** Small caption under the center label. */\n centerCaption?: ReactNode;\n /** Pixel diameter (scales down responsively). */\n size?: number;\n /** Ring thickness as a fraction of the radius (0–1). */\n thickness?: number;\n valueFormatter?: (value: number) => string;\n showLegend?: boolean;\n title?: string;\n className?: string;\n}\n\nconst VIEW = 100;\nconst CENTER = VIEW / 2;\nconst R_OUTER = 46;\n\n/** Donut chart with a center label and an optional legend. */\nexport function DonutChart({\n segments,\n centerLabel,\n centerCaption,\n size = 168,\n thickness = 0.28,\n valueFormatter = formatCompact,\n showLegend = true,\n title,\n className,\n}: DonutChartProps) {\n const mounted = useMounted();\n const reduced = usePrefersReducedMotion();\n\n if (segments.length === 0) {\n return null;\n }\n\n const total = segments.reduce((acc, s) => acc + Math.max(0, s.value), 0);\n const palette = chartPalette(segments.length);\n const rInner = R_OUTER * (1 - thickness);\n const chartTitle = title ?? `Donut chart of ${segments.length} segments`;\n\n const fractions = segments.map((s) => (total > 0 ? Math.max(0, s.value) / total : 0));\n const starts = fractions.map((_, i) => fractions.slice(0, i).reduce((a, b) => a + b, 0));\n const arcs = segments.map((segment, i) => {\n const fraction = fractions[i];\n const startAngle = starts[i] * 360;\n const endAngle = Math.min((starts[i] + fraction) * 360, 359.999);\n const color = segment.color ?? palette[i];\n return { segment, color, startAngle, endAngle, fraction, isFull: fraction >= 0.9999 };\n });\n\n return (\n <div className={cn('flex flex-wrap items-center gap-5', className)}>\n <div className=\"relative shrink-0\" style={{ width: size, height: size, maxWidth: '100%' }}>\n <svg\n viewBox={`0 0 ${VIEW} ${VIEW}`}\n width={size}\n height={size}\n role=\"img\"\n aria-label={chartTitle}\n style={{ display: 'block', maxWidth: '100%', height: 'auto' }}\n >\n <title>{chartTitle}</title>\n <desc>\n {segments\n .map((s) => `${s.label}: ${valueFormatter(s.value)} (${toPercent(s.value, total)}%)`)\n .join(', ')}\n </desc>\n <g\n style={{\n transformOrigin: 'center',\n transform: mounted || reduced ? 'scale(1)' : 'scale(0.86)',\n transition: reduced ? undefined : 'transform 520ms cubic-bezier(0.22,1,0.36,1)',\n }}\n >\n {arcs.map((arc, i) => {\n const shared = {\n fill: arc.color,\n style: {\n opacity: mounted || reduced ? 1 : 0,\n transition: reduced ? undefined : 'opacity 460ms ease',\n transitionDelay: reduced ? undefined : `${i * 70}ms`,\n },\n } as const;\n if (arc.isFull) {\n return (\n <path\n key={`${arc.segment.label}-${i}`}\n d={`M ${CENTER} ${CENTER - R_OUTER} A ${R_OUTER} ${R_OUTER} 0 1 0 ${CENTER} ${\n CENTER + R_OUTER\n } A ${R_OUTER} ${R_OUTER} 0 1 0 ${CENTER} ${CENTER - R_OUTER} M ${CENTER} ${\n CENTER - rInner\n } A ${rInner} ${rInner} 0 1 1 ${CENTER} ${CENTER + rInner} A ${rInner} ${rInner} 0 1 1 ${CENTER} ${\n CENTER - rInner\n } Z`}\n fillRule=\"evenodd\"\n {...shared}\n />\n );\n }\n return (\n <path\n key={`${arc.segment.label}-${i}`}\n d={donutSegmentPath(\n CENTER,\n CENTER,\n R_OUTER,\n rInner,\n arc.startAngle,\n arc.endAngle\n )}\n {...shared}\n >\n <title>{`${arc.segment.label}: ${valueFormatter(arc.segment.value)}`}</title>\n </path>\n );\n })}\n </g>\n </svg>\n {centerLabel !== undefined || centerCaption !== undefined ? (\n <div className=\"pointer-events-none absolute inset-0 flex flex-col items-center justify-center text-center\">\n <span className=\"text-xl font-semibold leading-none text-text-primary\">\n {centerLabel}\n </span>\n {centerCaption !== undefined ? (\n <span className=\"mt-1 text-xs text-text-muted\">{centerCaption}</span>\n ) : null}\n </div>\n ) : null}\n </div>\n {showLegend ? (\n <ul className=\"flex min-w-[8rem] flex-1 flex-col gap-1.5 text-sm\">\n {arcs.map((arc, i) => (\n <li key={`${arc.segment.label}-${i}`} className=\"flex items-center gap-2\">\n <span\n aria-hidden=\"true\"\n className=\"inline-block h-2.5 w-2.5 shrink-0 rounded-full\"\n style={{ backgroundColor: arc.color }}\n />\n <span className=\"flex-1 truncate text-text-secondary\">{arc.segment.label}</span>\n <span className=\"tabular-nums text-text-muted\">\n {toPercent(arc.segment.value, total)}%\n </span>\n </li>\n ))}\n </ul>\n ) : null}\n </div>\n );\n}\n"],"mappings":";;;;;;;AAuBA,IAAM,IAAO,KACP,IAAS,IAAO,GAChB,IAAU;AAGhB,SAAgB,EAAW,EACzB,aACA,gBACA,kBACA,UAAO,KACP,eAAY,KACZ,oBAAiB,GACjB,gBAAa,IACb,UACA,gBACkB;CAClB,IAAM,IAAU,GAAY,EACtB,IAAU,GAAyB;AAEzC,KAAI,EAAS,WAAW,EACtB,QAAO;CAGT,IAAM,IAAQ,EAAS,QAAQ,GAAK,MAAM,IAAM,KAAK,IAAI,GAAG,EAAE,MAAM,EAAE,EAAE,EAClE,IAAU,EAAa,EAAS,OAAO,EACvC,IAAS,KAAW,IAAI,IACxB,IAAa,KAAS,kBAAkB,EAAS,OAAO,YAExD,IAAY,EAAS,KAAK,MAAO,IAAQ,IAAI,KAAK,IAAI,GAAG,EAAE,MAAM,GAAG,IAAQ,EAAG,EAC/E,IAAS,EAAU,KAAK,GAAG,MAAM,EAAU,MAAM,GAAG,EAAE,CAAC,QAAQ,GAAG,MAAM,IAAI,GAAG,EAAE,CAAC,EAClF,IAAO,EAAS,KAAK,GAAS,MAAM;EACxC,IAAM,IAAW,EAAU,IACrB,IAAa,EAAO,KAAK,KACzB,IAAW,KAAK,KAAK,EAAO,KAAK,KAAY,KAAK,QAAQ;AAEhE,SAAO;GAAE;GAAS,OADJ,EAAQ,SAAS,EAAQ;GACd;GAAY;GAAU;GAAU,QAAQ,KAAY;GAAQ;GACrF;AAEF,QACE,kBAAC,OAAD;EAAK,WAAW,EAAG,qCAAqC,EAAU;YAAlE,CACE,kBAAC,OAAD;GAAK,WAAU;GAAoB,OAAO;IAAE,OAAO;IAAM,QAAQ;IAAM,UAAU;IAAQ;aAAzF,CACE,kBAAC,OAAD;IACE,SAAS,OAAO,EAAK,GAAG;IACxB,OAAO;IACP,QAAQ;IACR,MAAK;IACL,cAAY;IACZ,OAAO;KAAE,SAAS;KAAS,UAAU;KAAQ,QAAQ;KAAQ;cAN/D;KAQE,kBAAC,SAAD,EAAA,UAAQ,GAAmB,CAAA;KAC3B,kBAAC,QAAD,EAAA,UACG,EACE,KAAK,MAAM,GAAG,EAAE,MAAM,IAAI,EAAe,EAAE,MAAM,CAAC,IAAI,EAAU,EAAE,OAAO,EAAM,CAAC,IAAI,CACpF,KAAK,KAAK,EACR,CAAA;KACP,kBAAC,KAAD;MACE,OAAO;OACL,iBAAiB;OACjB,WAAW,KAAW,IAAU,aAAa;OAC7C,YAAY,IAAU,KAAA,IAAY;OACnC;gBAEA,EAAK,KAAK,GAAK,MAAM;OACpB,IAAM,IAAS;QACb,MAAM,EAAI;QACV,OAAO;SACL,SAAS,KAAW,IAAU,IAAI;SAClC,YAAY,IAAU,KAAA,IAAY;SAClC,iBAAiB,IAAU,KAAA,IAAY,GAAG,IAAI,GAAG;SAClD;QACF;AAiBD,cAhBI,EAAI,SAEJ,kBAAC,QAAD;QAEE,GAAG,KAAK,EAAO,GAAG,IAAS,EAAQ,KAAK,EAAQ,GAAG,EAAQ,SAAS,EAAO,GACzE,IAAS,EACV,KAAK,EAAQ,GAAG,EAAQ,SAAS,EAAO,GAAG,IAAS,EAAQ,KAAK,EAAO,GACvE,IAAS,EACV,KAAK,EAAO,GAAG,EAAO,SAAS,EAAO,GAAG,IAAS,EAAO,KAAK,EAAO,GAAG,EAAO,SAAS,EAAO,GAC9F,IAAS,EACV;QACD,UAAS;QACT,GAAI;QACJ,EAVK,GAAG,EAAI,QAAQ,MAAM,GAAG,IAU7B,GAIJ,kBAAC,QAAD;QAEE,GAAG,EACD,GACA,GACA,GACA,GACA,EAAI,YACJ,EAAI,SACL;QACD,GAAI;kBAEJ,kBAAC,SAAD,EAAA,UAAQ,GAAG,EAAI,QAAQ,MAAM,IAAI,EAAe,EAAI,QAAQ,MAAM,IAAW,CAAA;QACxE,EAZA,GAAG,EAAI,QAAQ,MAAM,GAAG,IAYxB;QAET;MACA,CAAA;KACA;OACL,MAAgB,KAAA,KAAa,MAAkB,KAAA,IAC9C,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,QAAD;KAAM,WAAU;eACb;KACI,CAAA,EACN,MAAkB,KAAA,IAEf,OADF,kBAAC,QAAD;KAAM,WAAU;eAAgC;KAAqB,CAAA,CAEnE;QACJ,KACA;MACL,IACC,kBAAC,MAAD;GAAI,WAAU;aACX,EAAK,KAAK,GAAK,MACd,kBAAC,MAAD;IAAsC,WAAU;cAAhD;KACE,kBAAC,QAAD;MACE,eAAY;MACZ,WAAU;MACV,OAAO,EAAE,iBAAiB,EAAI,OAAO;MACrC,CAAA;KACF,kBAAC,QAAD;MAAM,WAAU;gBAAuC,EAAI,QAAQ;MAAa,CAAA;KAChF,kBAAC,QAAD;MAAM,WAAU;gBAAhB,CACG,EAAU,EAAI,QAAQ,OAAO,EAAM,EAAC,IAChC;;KACJ;MAVI,GAAG,EAAI,QAAQ,MAAM,GAAG,IAU5B,CACL;GACC,CAAA,GACH,KACA"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { cn as e } from "../../utils/cn.js";
|
|
2
|
+
import { ACCENT as t } from "./lib/palette.js";
|
|
3
|
+
import { Check as n, Lock as r } from "lucide-react";
|
|
4
|
+
import { jsx as i, jsxs as a } from "react/jsx-runtime";
|
|
5
|
+
//#region src/theglobalfuel/components/viz/PathSteps.tsx
|
|
6
|
+
function o({ steps: o, className: s }) {
|
|
7
|
+
return o.length === 0 ? null : /* @__PURE__ */ i("ol", {
|
|
8
|
+
className: e("flex items-start overflow-x-auto pb-1", s),
|
|
9
|
+
"aria-label": "Learning path",
|
|
10
|
+
children: o.map((s, c) => {
|
|
11
|
+
let l = s.state ?? "todo";
|
|
12
|
+
return /* @__PURE__ */ a("li", {
|
|
13
|
+
className: "flex min-w-[5.5rem] flex-1 flex-col items-center",
|
|
14
|
+
"aria-current": l === "active" ? "step" : void 0,
|
|
15
|
+
children: [
|
|
16
|
+
/* @__PURE__ */ a("div", {
|
|
17
|
+
className: "flex w-full items-center",
|
|
18
|
+
children: [
|
|
19
|
+
c > 0 ? /* @__PURE__ */ i("span", {
|
|
20
|
+
"aria-hidden": "true",
|
|
21
|
+
className: "h-0.5 flex-1 rounded-full",
|
|
22
|
+
style: { backgroundColor: l === "done" ? t : "var(--color-border-subtle)" }
|
|
23
|
+
}) : /* @__PURE__ */ i("span", {
|
|
24
|
+
className: "flex-1",
|
|
25
|
+
"aria-hidden": "true"
|
|
26
|
+
}),
|
|
27
|
+
/* @__PURE__ */ i("span", {
|
|
28
|
+
className: e("flex h-9 w-9 shrink-0 items-center justify-center rounded-full border-2 text-xs font-semibold transition-colors", l === "done" && "border-transparent text-text-inverse", l === "active" && "bg-bg-surface shadow-elevation-1", l === "todo" && "border-border-default bg-bg-surface text-text-secondary", l === "locked" && "border-border-subtle bg-bg-sunken text-text-muted"),
|
|
29
|
+
style: l === "done" ? { backgroundColor: t } : l === "active" ? {
|
|
30
|
+
borderColor: t,
|
|
31
|
+
color: t
|
|
32
|
+
} : void 0,
|
|
33
|
+
children: l === "done" ? /* @__PURE__ */ i(n, {
|
|
34
|
+
"aria-hidden": "true",
|
|
35
|
+
className: "h-4 w-4"
|
|
36
|
+
}) : l === "locked" ? /* @__PURE__ */ i(r, {
|
|
37
|
+
"aria-hidden": "true",
|
|
38
|
+
className: "h-3.5 w-3.5"
|
|
39
|
+
}) : c + 1
|
|
40
|
+
}),
|
|
41
|
+
c < o.length - 1 ? /* @__PURE__ */ i("span", {
|
|
42
|
+
"aria-hidden": "true",
|
|
43
|
+
className: "h-0.5 flex-1 rounded-full",
|
|
44
|
+
style: { backgroundColor: l === "done" ? t : "var(--color-border-subtle)" }
|
|
45
|
+
}) : /* @__PURE__ */ i("span", {
|
|
46
|
+
className: "flex-1",
|
|
47
|
+
"aria-hidden": "true"
|
|
48
|
+
})
|
|
49
|
+
]
|
|
50
|
+
}),
|
|
51
|
+
/* @__PURE__ */ i("span", {
|
|
52
|
+
className: e("mt-2 max-w-[7rem] truncate px-1 text-center text-xs", l === "locked" ? "text-text-muted" : "font-medium text-text-primary"),
|
|
53
|
+
title: s.label,
|
|
54
|
+
children: s.label
|
|
55
|
+
}),
|
|
56
|
+
s.meta ? /* @__PURE__ */ i("span", {
|
|
57
|
+
className: "mt-0.5 px-1 text-center text-[11px] text-text-muted",
|
|
58
|
+
children: s.meta
|
|
59
|
+
}) : null
|
|
60
|
+
]
|
|
61
|
+
}, `${s.label}-${c}`);
|
|
62
|
+
})
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
//#endregion
|
|
66
|
+
export { o as PathSteps };
|
|
67
|
+
|
|
68
|
+
//# sourceMappingURL=PathSteps.js.map
|