@burdenoff/microfe-kadaikodi 2026.704.4 → 2026.704.5

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.
Files changed (126) hide show
  1. package/dist/kadaikodi/KadaikodiRoutes.js +30 -26
  2. package/dist/kadaikodi/KadaikodiRoutes.js.map +1 -1
  3. package/dist/kadaikodi/components/viz/AllocationBar.js +68 -0
  4. package/dist/kadaikodi/components/viz/AllocationBar.js.map +1 -0
  5. package/dist/kadaikodi/components/viz/AreaChart.js +102 -0
  6. package/dist/kadaikodi/components/viz/AreaChart.js.map +1 -0
  7. package/dist/kadaikodi/components/viz/AvatarStack.js +36 -0
  8. package/dist/kadaikodi/components/viz/AvatarStack.js.map +1 -0
  9. package/dist/kadaikodi/components/viz/BarChart.js +83 -0
  10. package/dist/kadaikodi/components/viz/BarChart.js.map +1 -0
  11. package/dist/kadaikodi/components/viz/CertificateCard.js +4 -0
  12. package/dist/kadaikodi/components/viz/DonutChart.js +109 -0
  13. package/dist/kadaikodi/components/viz/DonutChart.js.map +1 -0
  14. package/dist/kadaikodi/components/viz/HBarFunnel.js +54 -0
  15. package/dist/kadaikodi/components/viz/HBarFunnel.js.map +1 -0
  16. package/dist/kadaikodi/components/viz/Heatmap.js +3 -0
  17. package/dist/kadaikodi/components/viz/KanbanBoard.js +50 -0
  18. package/dist/kadaikodi/components/viz/KanbanBoard.js.map +1 -0
  19. package/dist/kadaikodi/components/viz/LineChart.js +4 -0
  20. package/dist/kadaikodi/components/viz/OrgTree.js +2 -0
  21. package/dist/kadaikodi/components/viz/PathSteps.js +3 -0
  22. package/dist/kadaikodi/components/viz/ProgressRing.js +68 -0
  23. package/dist/kadaikodi/components/viz/ProgressRing.js.map +1 -0
  24. package/dist/kadaikodi/components/viz/RatingStars.js +51 -0
  25. package/dist/kadaikodi/components/viz/RatingStars.js.map +1 -0
  26. package/dist/kadaikodi/components/viz/SegmentedTabs.js +69 -0
  27. package/dist/kadaikodi/components/viz/SegmentedTabs.js.map +1 -0
  28. package/dist/kadaikodi/components/viz/SkillBar.js +3 -0
  29. package/dist/kadaikodi/components/viz/Sparkline.js +74 -0
  30. package/dist/kadaikodi/components/viz/Sparkline.js.map +1 -0
  31. package/dist/kadaikodi/components/viz/StatCard.js +60 -0
  32. package/dist/kadaikodi/components/viz/StatCard.js.map +1 -0
  33. package/dist/kadaikodi/components/viz/StepProgress.js +64 -0
  34. package/dist/kadaikodi/components/viz/StepProgress.js.map +1 -0
  35. package/dist/kadaikodi/components/viz/Timeline.js +66 -0
  36. package/dist/kadaikodi/components/viz/Timeline.js.map +1 -0
  37. package/dist/kadaikodi/components/viz/index.js +23 -0
  38. package/dist/kadaikodi/components/viz/lib/Avatar.js +30 -0
  39. package/dist/kadaikodi/components/viz/lib/Avatar.js.map +1 -0
  40. package/dist/kadaikodi/components/viz/lib/CartesianFrame.js +119 -0
  41. package/dist/kadaikodi/components/viz/lib/CartesianFrame.js.map +1 -0
  42. package/dist/kadaikodi/components/viz/lib/ResponsiveSvg.js +35 -0
  43. package/dist/kadaikodi/components/viz/lib/ResponsiveSvg.js.map +1 -0
  44. package/dist/kadaikodi/components/viz/lib/format.js +21 -0
  45. package/dist/kadaikodi/components/viz/lib/format.js.map +1 -0
  46. package/dist/kadaikodi/components/viz/lib/geometry.js +61 -0
  47. package/dist/kadaikodi/components/viz/lib/geometry.js.map +1 -0
  48. package/dist/kadaikodi/components/viz/lib/hooks.js +39 -0
  49. package/dist/kadaikodi/components/viz/lib/hooks.js.map +1 -0
  50. package/dist/kadaikodi/components/viz/lib/palette.js +27 -0
  51. package/dist/kadaikodi/components/viz/lib/palette.js.map +1 -0
  52. package/dist/kadaikodi/hooks/useKadaikodiApi.js +18 -10
  53. package/dist/kadaikodi/hooks/useKadaikodiApi.js.map +1 -1
  54. package/dist/kadaikodi/pages/HomePage.js +373 -631
  55. package/dist/kadaikodi/pages/HomePage.js.map +1 -1
  56. package/dist/kadaikodi/pages/admin/AdminAnalyticsPage.js +304 -169
  57. package/dist/kadaikodi/pages/admin/AdminAnalyticsPage.js.map +1 -1
  58. package/dist/kadaikodi/pages/admin/AdminDashboard.js +255 -271
  59. package/dist/kadaikodi/pages/admin/AdminDashboard.js.map +1 -1
  60. package/dist/kadaikodi/pages/admin/AdminFinancialPage.js +185 -125
  61. package/dist/kadaikodi/pages/admin/AdminFinancialPage.js.map +1 -1
  62. package/dist/kadaikodi/pages/admin/AdminOrdersPage.js +141 -102
  63. package/dist/kadaikodi/pages/admin/AdminOrdersPage.js.map +1 -1
  64. package/dist/kadaikodi/pages/admin/CartManagementPage.js +558 -718
  65. package/dist/kadaikodi/pages/admin/CartManagementPage.js.map +1 -1
  66. package/dist/kadaikodi/pages/admin/UserManagementPage.js.map +1 -1
  67. package/dist/kadaikodi/pages/cart-owner/AnalyticsDashboard.js +142 -55
  68. package/dist/kadaikodi/pages/cart-owner/AnalyticsDashboard.js.map +1 -1
  69. package/dist/kadaikodi/pages/cart-owner/CartOwnerDashboard.js +129 -164
  70. package/dist/kadaikodi/pages/cart-owner/CartOwnerDashboard.js.map +1 -1
  71. package/dist/kadaikodi/pages/cart-owner/InventoryManagementPage.js +379 -361
  72. package/dist/kadaikodi/pages/cart-owner/InventoryManagementPage.js.map +1 -1
  73. package/dist/kadaikodi/pages/cart-owner/InvestmentListingPage.js +266 -166
  74. package/dist/kadaikodi/pages/cart-owner/InvestmentListingPage.js.map +1 -1
  75. package/dist/kadaikodi/pages/cart-owner/MerchantOnboardingPage.js +373 -0
  76. package/dist/kadaikodi/pages/cart-owner/MerchantOnboardingPage.js.map +1 -0
  77. package/dist/kadaikodi/pages/cart-owner/OrderManagementPage.js +288 -161
  78. package/dist/kadaikodi/pages/cart-owner/OrderManagementPage.js.map +1 -1
  79. package/dist/kadaikodi/pages/cart-owner/StaffManagementPage.js +180 -181
  80. package/dist/kadaikodi/pages/cart-owner/StaffManagementPage.js.map +1 -1
  81. package/dist/kadaikodi/pages/common/HelpSupportPage.js.map +1 -1
  82. package/dist/kadaikodi/pages/common/SettingsPage.js.map +1 -1
  83. package/dist/kadaikodi/pages/customer/BookingDetailsPage.js +17 -34
  84. package/dist/kadaikodi/pages/customer/BookingDetailsPage.js.map +1 -1
  85. package/dist/kadaikodi/pages/customer/CartDetailPage.js +405 -248
  86. package/dist/kadaikodi/pages/customer/CartDetailPage.js.map +1 -1
  87. package/dist/kadaikodi/pages/customer/CartDiscoveryPage.js +258 -232
  88. package/dist/kadaikodi/pages/customer/CartDiscoveryPage.js.map +1 -1
  89. package/dist/kadaikodi/pages/customer/CheckoutPage.js +254 -205
  90. package/dist/kadaikodi/pages/customer/CheckoutPage.js.map +1 -1
  91. package/dist/kadaikodi/pages/customer/DirectServiceBookingPage.js +270 -230
  92. package/dist/kadaikodi/pages/customer/DirectServiceBookingPage.js.map +1 -1
  93. package/dist/kadaikodi/pages/customer/FavoritesPage.js +149 -412
  94. package/dist/kadaikodi/pages/customer/FavoritesPage.js.map +1 -1
  95. package/dist/kadaikodi/pages/customer/OrderHistoryPage.js +173 -134
  96. package/dist/kadaikodi/pages/customer/OrderHistoryPage.js.map +1 -1
  97. package/dist/kadaikodi/pages/customer/OrderTrackingPage.js +176 -175
  98. package/dist/kadaikodi/pages/customer/OrderTrackingPage.js.map +1 -1
  99. package/dist/kadaikodi/pages/customer/ReviewsPage.js +295 -438
  100. package/dist/kadaikodi/pages/customer/ReviewsPage.js.map +1 -1
  101. package/dist/kadaikodi/pages/customer/shared.js +181 -0
  102. package/dist/kadaikodi/pages/customer/shared.js.map +1 -0
  103. package/dist/kadaikodi/pages/investor/InvestmentDetailsPage.js +438 -311
  104. package/dist/kadaikodi/pages/investor/InvestmentDetailsPage.js.map +1 -1
  105. package/dist/kadaikodi/pages/investor/InvestmentOpportunitiesPage.js +320 -293
  106. package/dist/kadaikodi/pages/investor/InvestmentOpportunitiesPage.js.map +1 -1
  107. package/dist/kadaikodi/pages/investor/InvestorAnalyticsPage.js +159 -91
  108. package/dist/kadaikodi/pages/investor/InvestorAnalyticsPage.js.map +1 -1
  109. package/dist/kadaikodi/pages/investor/InvestorDashboard.js +137 -107
  110. package/dist/kadaikodi/pages/investor/InvestorDashboard.js.map +1 -1
  111. package/dist/kadaikodi/pages/investor/InvestorInvestmentsPage.js +157 -135
  112. package/dist/kadaikodi/pages/investor/InvestorInvestmentsPage.js.map +1 -1
  113. package/dist/kadaikodi/pages/investor/InvestorPortfolioPage.js +280 -206
  114. package/dist/kadaikodi/pages/investor/InvestorPortfolioPage.js.map +1 -1
  115. package/dist/kadaikodi/pages/investor/InvestorProfilePage.js.map +1 -1
  116. package/dist/kadaikodi/pages/worker/TaskDetailsPage.js +424 -448
  117. package/dist/kadaikodi/pages/worker/TaskDetailsPage.js.map +1 -1
  118. package/dist/kadaikodi/pages/worker/WorkerDashboard.js +328 -445
  119. package/dist/kadaikodi/pages/worker/WorkerDashboard.js.map +1 -1
  120. package/dist/kadaikodi/pages/worker/WorkerOrdersPage.js +141 -164
  121. package/dist/kadaikodi/pages/worker/WorkerOrdersPage.js.map +1 -1
  122. package/dist/kadaikodi/pages/worker/WorkerProfilePage.js +300 -296
  123. package/dist/kadaikodi/pages/worker/WorkerProfilePage.js.map +1 -1
  124. package/dist/kadaikodi/pages/worker/WorkerSchedulePage.js +284 -309
  125. package/dist/kadaikodi/pages/worker/WorkerSchedulePage.js.map +1 -1
  126. package/package.json +1 -1
@@ -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/kadaikodi/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/kadaikodi/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":"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"}
@@ -0,0 +1,54 @@
1
+ import { cn as e } from "../../utils/cn.js";
2
+ import { ACCENT as t } from "./lib/palette.js";
3
+ import { useMounted as n, usePrefersReducedMotion as r } from "./lib/hooks.js";
4
+ import { formatCompact as i, toPercent as a } from "./lib/format.js";
5
+ import { jsx as o, jsxs as s } from "react/jsx-runtime";
6
+ //#region src/kadaikodi/components/viz/HBarFunnel.tsx
7
+ function c({ stages: c, valueFormatter: l = i, showConversion: u = !0, className: d }) {
8
+ let f = n(), p = r();
9
+ if (c.length === 0) return null;
10
+ let m = Math.max(c[0]?.value ?? 0, 1);
11
+ return /* @__PURE__ */ o("ol", {
12
+ className: e("flex flex-col gap-2", d),
13
+ children: c.map((e, n) => {
14
+ let r = a(e.value, m, 1), i = n === 0 ? 100 : a(e.value, c[n - 1].value, 1), d = 1 - n / Math.max(c.length, 2) * .62;
15
+ return /* @__PURE__ */ s("li", {
16
+ className: "flex flex-col gap-1",
17
+ children: [/* @__PURE__ */ s("div", {
18
+ className: "flex items-baseline justify-between text-sm",
19
+ children: [/* @__PURE__ */ o("span", {
20
+ className: "font-medium text-text-primary",
21
+ children: e.label
22
+ }), /* @__PURE__ */ s("span", {
23
+ className: "tabular-nums text-text-secondary",
24
+ children: [l(e.value), u && n > 0 ? /* @__PURE__ */ s("span", {
25
+ className: "ml-2 text-xs text-text-muted",
26
+ children: [i, "%"]
27
+ }) : null]
28
+ })]
29
+ }), /* @__PURE__ */ o("div", {
30
+ className: "h-7 w-full overflow-hidden rounded-[var(--radius-input)] bg-bg-sunken",
31
+ role: "progressbar",
32
+ "aria-valuenow": Math.round(r),
33
+ "aria-valuemin": 0,
34
+ "aria-valuemax": 100,
35
+ "aria-label": `${e.label}: ${l(e.value)}`,
36
+ children: /* @__PURE__ */ o("div", {
37
+ className: "h-full rounded-[var(--radius-input)]",
38
+ style: {
39
+ width: f || p ? `${r}%` : "0%",
40
+ backgroundColor: t,
41
+ opacity: d,
42
+ transition: p ? void 0 : "width 720ms cubic-bezier(0.22,1,0.36,1)",
43
+ transitionDelay: p ? void 0 : `${n * 70}ms`
44
+ }
45
+ })
46
+ })]
47
+ }, `${e.label}-${n}`);
48
+ })
49
+ });
50
+ }
51
+ //#endregion
52
+ export { c as HBarFunnel };
53
+
54
+ //# sourceMappingURL=HBarFunnel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HBarFunnel.js","names":[],"sources":["../../../../src/kadaikodi/components/viz/HBarFunnel.tsx"],"sourcesContent":["import { cn } from '../../utils/cn';\nimport { ACCENT } from './lib/palette';\nimport { formatCompact, toPercent } from './lib/format';\nimport { useMounted, usePrefersReducedMotion } from './lib/hooks';\nimport type { LabeledValue } from './lib/types';\n\nexport interface HBarFunnelProps {\n /** Ordered funnel stages, largest first. */\n stages: LabeledValue[];\n valueFormatter?: (value: number) => string;\n /** Show conversion % relative to the first stage. */\n showConversion?: boolean;\n className?: string;\n}\n\n/**\n * Horizontal descending funnel. Each stage renders as a bar whose width is\n * proportional to the top-of-funnel value; the accent fades down the funnel so\n * the drop-off reads at a glance. Built with CSS bars for crisp, real text.\n */\nexport function HBarFunnel({\n stages,\n valueFormatter = formatCompact,\n showConversion = true,\n className,\n}: HBarFunnelProps) {\n const mounted = useMounted();\n const reduced = usePrefersReducedMotion();\n\n if (stages.length === 0) {\n return null;\n }\n\n const top = Math.max(stages[0]?.value ?? 0, 1);\n\n return (\n <ol className={cn('flex flex-col gap-2', className)}>\n {stages.map((stage, i) => {\n const pct = toPercent(stage.value, top, 1);\n const conversion = i === 0 ? 100 : toPercent(stage.value, stages[i - 1].value, 1);\n const fillOpacity = 1 - (i / Math.max(stages.length, 2)) * 0.62;\n return (\n <li key={`${stage.label}-${i}`} className=\"flex flex-col gap-1\">\n <div className=\"flex items-baseline justify-between text-sm\">\n <span className=\"font-medium text-text-primary\">{stage.label}</span>\n <span className=\"tabular-nums text-text-secondary\">\n {valueFormatter(stage.value)}\n {showConversion && i > 0 ? (\n <span className=\"ml-2 text-xs text-text-muted\">{conversion}%</span>\n ) : null}\n </span>\n </div>\n <div\n className=\"h-7 w-full overflow-hidden rounded-[var(--radius-input)] bg-bg-sunken\"\n role=\"progressbar\"\n aria-valuenow={Math.round(pct)}\n aria-valuemin={0}\n aria-valuemax={100}\n aria-label={`${stage.label}: ${valueFormatter(stage.value)}`}\n >\n <div\n className=\"h-full rounded-[var(--radius-input)]\"\n style={{\n width: mounted || reduced ? `${pct}%` : '0%',\n backgroundColor: ACCENT,\n opacity: fillOpacity,\n transition: reduced ? undefined : 'width 720ms cubic-bezier(0.22,1,0.36,1)',\n transitionDelay: reduced ? undefined : `${i * 70}ms`,\n }}\n />\n </div>\n </li>\n );\n })}\n </ol>\n );\n}\n"],"mappings":";;;;;;AAoBA,SAAgB,EAAW,EACzB,WACA,oBAAiB,GACjB,oBAAiB,IACjB,gBACkB;CAClB,IAAM,IAAU,GAAY,EACtB,IAAU,GAAyB;AAEzC,KAAI,EAAO,WAAW,EACpB,QAAO;CAGT,IAAM,IAAM,KAAK,IAAI,EAAO,IAAI,SAAS,GAAG,EAAE;AAE9C,QACE,kBAAC,MAAD;EAAI,WAAW,EAAG,uBAAuB,EAAU;YAChD,EAAO,KAAK,GAAO,MAAM;GACxB,IAAM,IAAM,EAAU,EAAM,OAAO,GAAK,EAAE,EACpC,IAAa,MAAM,IAAI,MAAM,EAAU,EAAM,OAAO,EAAO,IAAI,GAAG,OAAO,EAAE,EAC3E,IAAc,IAAK,IAAI,KAAK,IAAI,EAAO,QAAQ,EAAE,GAAI;AAC3D,UACE,kBAAC,MAAD;IAAgC,WAAU;cAA1C,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,QAAD;MAAM,WAAU;gBAAiC,EAAM;MAAa,CAAA,EACpE,kBAAC,QAAD;MAAM,WAAU;gBAAhB,CACG,EAAe,EAAM,MAAM,EAC3B,KAAkB,IAAI,IACrB,kBAAC,QAAD;OAAM,WAAU;iBAAhB,CAAgD,GAAW,IAAQ;WACjE,KACC;QACH;QACN,kBAAC,OAAD;KACE,WAAU;KACV,MAAK;KACL,iBAAe,KAAK,MAAM,EAAI;KAC9B,iBAAe;KACf,iBAAe;KACf,cAAY,GAAG,EAAM,MAAM,IAAI,EAAe,EAAM,MAAM;eAE1D,kBAAC,OAAD;MACE,WAAU;MACV,OAAO;OACL,OAAO,KAAW,IAAU,GAAG,EAAI,KAAK;OACxC,iBAAiB;OACjB,SAAS;OACT,YAAY,IAAU,KAAA,IAAY;OAClC,iBAAiB,IAAU,KAAA,IAAY,GAAG,IAAI,GAAG;OAClD;MACD,CAAA;KACE,CAAA,CACH;MA7BI,GAAG,EAAM,MAAM,GAAG,IA6BtB;IAEP;EACC,CAAA"}
@@ -0,0 +1,3 @@
1
+ import "./lib/palette.js";
2
+ import "./lib/hooks.js";
3
+ import "react/jsx-runtime";
@@ -0,0 +1,50 @@
1
+ import { cn as e } from "../../utils/cn.js";
2
+ import "./lib/palette.js";
3
+ import { jsx as t, jsxs as n } from "react/jsx-runtime";
4
+ //#region src/kadaikodi/components/viz/KanbanBoard.tsx
5
+ function r({ columns: r, columnWidth: i = 288, emptyColumnHint: a = "Nothing here yet", className: o }) {
6
+ return /* @__PURE__ */ t("div", {
7
+ className: e("flex gap-4 overflow-x-auto pb-2", o),
8
+ role: "list",
9
+ "aria-label": "Board columns",
10
+ children: r.map((e) => /* @__PURE__ */ n("section", {
11
+ role: "listitem",
12
+ "aria-label": `${e.title}, ${e.items.length} items`,
13
+ tabIndex: 0,
14
+ className: "flex shrink-0 flex-col rounded-[var(--radius-card)] border border-border-subtle bg-bg-sunken outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]",
15
+ style: { width: i },
16
+ children: [/* @__PURE__ */ n("header", {
17
+ className: "flex items-center gap-2 border-b border-border-subtle px-3 py-2.5",
18
+ children: [
19
+ /* @__PURE__ */ t("span", {
20
+ "aria-hidden": "true",
21
+ className: "h-3.5 w-1 shrink-0 rounded-full",
22
+ style: { backgroundColor: e.accent ?? "var(--color-accent-primary)" }
23
+ }),
24
+ /* @__PURE__ */ t("h3", {
25
+ className: "flex-1 truncate text-sm font-semibold text-text-primary",
26
+ children: e.title
27
+ }),
28
+ /* @__PURE__ */ t("span", {
29
+ className: "inline-flex min-w-5 items-center justify-center rounded-full bg-bg-elevated px-1.5 text-xs font-medium tabular-nums text-text-secondary",
30
+ children: e.items.length
31
+ })
32
+ ]
33
+ }), /* @__PURE__ */ t("ul", {
34
+ className: "flex flex-1 flex-col gap-2 p-2",
35
+ children: e.items.length === 0 ? /* @__PURE__ */ t("li", {
36
+ className: "rounded-[var(--radius-input)] border border-dashed border-border-subtle px-3 py-6 text-center text-xs text-text-muted",
37
+ children: a
38
+ }) : e.items.map((e, n) => /* @__PURE__ */ t("li", {
39
+ tabIndex: 0,
40
+ className: "rounded-[var(--radius-input)] outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]",
41
+ children: e
42
+ }, n))
43
+ })]
44
+ }, e.id))
45
+ });
46
+ }
47
+ //#endregion
48
+ export { r as KanbanBoard };
49
+
50
+ //# sourceMappingURL=KanbanBoard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"KanbanBoard.js","names":[],"sources":["../../../../src/kadaikodi/components/viz/KanbanBoard.tsx"],"sourcesContent":["import { cn } from '../../utils/cn';\nimport { ACCENT } from './lib/palette';\nimport type { KanbanColumnData } from './lib/types';\n\nexport interface KanbanBoardProps {\n columns: KanbanColumnData[];\n /** Fixed column width in pixels. */\n columnWidth?: number;\n /** Optional empty-state node rendered inside a column with no items. */\n emptyColumnHint?: string;\n className?: string;\n}\n\n/**\n * Generic columns board. The caller supplies pre-rendered card nodes; the board\n * owns layout, horizontal scroll and keyboard-focusable columns/cards. It never\n * scrolls the page sideways — overflow is contained here.\n */\nexport function KanbanBoard({\n columns,\n columnWidth = 288,\n emptyColumnHint = 'Nothing here yet',\n className,\n}: KanbanBoardProps) {\n return (\n <div\n className={cn('flex gap-4 overflow-x-auto pb-2', className)}\n role=\"list\"\n aria-label=\"Board columns\"\n >\n {columns.map((column) => (\n <section\n key={column.id}\n role=\"listitem\"\n aria-label={`${column.title}, ${column.items.length} items`}\n tabIndex={0}\n className=\"flex shrink-0 flex-col rounded-[var(--radius-card)] border border-border-subtle bg-bg-sunken outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n style={{ width: columnWidth }}\n >\n <header className=\"flex items-center gap-2 border-b border-border-subtle px-3 py-2.5\">\n <span\n aria-hidden=\"true\"\n className=\"h-3.5 w-1 shrink-0 rounded-full\"\n style={{ backgroundColor: column.accent ?? ACCENT }}\n />\n <h3 className=\"flex-1 truncate text-sm font-semibold text-text-primary\">\n {column.title}\n </h3>\n <span className=\"inline-flex min-w-5 items-center justify-center rounded-full bg-bg-elevated px-1.5 text-xs font-medium tabular-nums text-text-secondary\">\n {column.items.length}\n </span>\n </header>\n <ul className=\"flex flex-1 flex-col gap-2 p-2\">\n {column.items.length === 0 ? (\n <li className=\"rounded-[var(--radius-input)] border border-dashed border-border-subtle px-3 py-6 text-center text-xs text-text-muted\">\n {emptyColumnHint}\n </li>\n ) : (\n column.items.map((item, i) => (\n <li\n key={i}\n tabIndex={0}\n className=\"rounded-[var(--radius-input)] outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n {item}\n </li>\n ))\n )}\n </ul>\n </section>\n ))}\n </div>\n );\n}\n"],"mappings":";;;;AAkBA,SAAgB,EAAY,EAC1B,YACA,iBAAc,KACd,qBAAkB,oBAClB,gBACmB;AACnB,QACE,kBAAC,OAAD;EACE,WAAW,EAAG,mCAAmC,EAAU;EAC3D,MAAK;EACL,cAAW;YAEV,EAAQ,KAAK,MACZ,kBAAC,WAAD;GAEE,MAAK;GACL,cAAY,GAAG,EAAO,MAAM,IAAI,EAAO,MAAM,OAAO;GACpD,UAAU;GACV,WAAU;GACV,OAAO,EAAE,OAAO,GAAa;aAN/B,CAQE,kBAAC,UAAD;IAAQ,WAAU;cAAlB;KACE,kBAAC,QAAD;MACE,eAAY;MACZ,WAAU;MACV,OAAO,EAAE,iBAAiB,EAAO,UAAA,+BAAkB;MACnD,CAAA;KACF,kBAAC,MAAD;MAAI,WAAU;gBACX,EAAO;MACL,CAAA;KACL,kBAAC,QAAD;MAAM,WAAU;gBACb,EAAO,MAAM;MACT,CAAA;KACA;OACT,kBAAC,MAAD;IAAI,WAAU;cACX,EAAO,MAAM,WAAW,IACvB,kBAAC,MAAD;KAAI,WAAU;eACX;KACE,CAAA,GAEL,EAAO,MAAM,KAAK,GAAM,MACtB,kBAAC,MAAD;KAEE,UAAU;KACV,WAAU;eAET;KACE,EALE,EAKF,CACL;IAED,CAAA,CACG;KArCH,EAAO,GAqCJ,CACV;EACE,CAAA"}
@@ -0,0 +1,4 @@
1
+ import "./lib/palette.js";
2
+ import "./lib/hooks.js";
3
+ import "./lib/CartesianFrame.js";
4
+ import "react/jsx-runtime";
@@ -0,0 +1,2 @@
1
+ import "./lib/Avatar.js";
2
+ import "react/jsx-runtime";
@@ -0,0 +1,3 @@
1
+ import "./lib/palette.js";
2
+ import "lucide-react";
3
+ import "react/jsx-runtime";
@@ -0,0 +1,68 @@
1
+ import { cn as e } from "../../utils/cn.js";
2
+ import { ACCENT as t } from "./lib/palette.js";
3
+ import { useMounted as n, usePrefersReducedMotion as r } from "./lib/hooks.js";
4
+ import { clamp as i } from "./lib/geometry.js";
5
+ import { jsx as a, jsxs as o } from "react/jsx-runtime";
6
+ //#region src/kadaikodi/components/viz/ProgressRing.tsx
7
+ var s = 100, c = s / 2;
8
+ function l({ value: l, max: u = 100, label: d, caption: f, size: p = 132, thickness: m = 10, color: h = t, title: g, className: _ }) {
9
+ let v = n(), y = r(), b = c - m / 2, x = 2 * Math.PI * b, S = i(u === 0 ? 0 : l / u, 0, 1), C = x * (1 - S), w = v || y ? C : x, T = g ?? `Progress: ${Math.round(S * 100)} percent`;
10
+ return /* @__PURE__ */ o("div", {
11
+ className: e("relative inline-flex shrink-0", _),
12
+ style: {
13
+ width: p,
14
+ height: p,
15
+ maxWidth: "100%"
16
+ },
17
+ children: [/* @__PURE__ */ o("svg", {
18
+ viewBox: `0 0 ${s} ${s}`,
19
+ width: p,
20
+ height: p,
21
+ role: "img",
22
+ "aria-label": T,
23
+ style: {
24
+ display: "block",
25
+ maxWidth: "100%",
26
+ height: "auto"
27
+ },
28
+ children: [
29
+ /* @__PURE__ */ a("title", { children: T }),
30
+ /* @__PURE__ */ a("circle", {
31
+ cx: c,
32
+ cy: c,
33
+ r: b,
34
+ fill: "none",
35
+ stroke: "var(--color-chart-grid)",
36
+ strokeWidth: m,
37
+ opacity: .7
38
+ }),
39
+ /* @__PURE__ */ a("circle", {
40
+ cx: c,
41
+ cy: c,
42
+ r: b,
43
+ fill: "none",
44
+ stroke: h,
45
+ strokeWidth: m,
46
+ strokeLinecap: "round",
47
+ strokeDasharray: x,
48
+ strokeDashoffset: w,
49
+ transform: `rotate(-90 ${c} ${c})`,
50
+ style: { transition: y ? void 0 : "stroke-dashoffset 900ms cubic-bezier(0.22,1,0.36,1)" }
51
+ })
52
+ ]
53
+ }), /* @__PURE__ */ o("div", {
54
+ className: "pointer-events-none absolute inset-0 flex flex-col items-center justify-center text-center",
55
+ children: [/* @__PURE__ */ a("span", {
56
+ className: "text-2xl font-semibold leading-none text-text-primary",
57
+ children: d ?? Math.round(S * 100)
58
+ }), f === void 0 ? null : /* @__PURE__ */ a("span", {
59
+ className: "mt-1 text-xs text-text-muted",
60
+ children: f
61
+ })]
62
+ })]
63
+ });
64
+ }
65
+ //#endregion
66
+ export { l as ProgressRing };
67
+
68
+ //# sourceMappingURL=ProgressRing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProgressRing.js","names":[],"sources":["../../../../src/kadaikodi/components/viz/ProgressRing.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { cn } from '../../utils/cn';\nimport { ACCENT } from './lib/palette';\nimport { clamp } from './lib/geometry';\nimport { useMounted, usePrefersReducedMotion } from './lib/hooks';\n\nexport interface ProgressRingProps {\n value: number;\n max?: number;\n /** Center content. Defaults to the rounded value (e.g. \"4.6\" or \"72%\"). */\n label?: ReactNode;\n /** Small caption under the center label. */\n caption?: ReactNode;\n size?: number;\n /** Stroke thickness in viewBox units. */\n thickness?: number;\n /** Tokenized arc color; defaults to the product accent. */\n color?: string;\n title?: string;\n className?: string;\n}\n\nconst VIEW = 100;\nconst CENTER = VIEW / 2;\n\n/** Circular gauge for a score or percentage, with an animated sweep. */\nexport function ProgressRing({\n value,\n max = 100,\n label,\n caption,\n size = 132,\n thickness = 10,\n color = ACCENT,\n title,\n className,\n}: ProgressRingProps) {\n const mounted = useMounted();\n const reduced = usePrefersReducedMotion();\n\n const radius = CENTER - thickness / 2;\n const circumference = 2 * Math.PI * radius;\n const fraction = clamp(max === 0 ? 0 : value / max, 0, 1);\n const target = circumference * (1 - fraction);\n const offset = mounted || reduced ? target : circumference;\n const ringTitle = title ?? `Progress: ${Math.round(fraction * 100)} percent`;\n\n return (\n <div\n className={cn('relative inline-flex shrink-0', className)}\n style={{ width: size, height: size, maxWidth: '100%' }}\n >\n <svg\n viewBox={`0 0 ${VIEW} ${VIEW}`}\n width={size}\n height={size}\n role=\"img\"\n aria-label={ringTitle}\n style={{ display: 'block', maxWidth: '100%', height: 'auto' }}\n >\n <title>{ringTitle}</title>\n <circle\n cx={CENTER}\n cy={CENTER}\n r={radius}\n fill=\"none\"\n stroke=\"var(--color-chart-grid)\"\n strokeWidth={thickness}\n opacity={0.7}\n />\n <circle\n cx={CENTER}\n cy={CENTER}\n r={radius}\n fill=\"none\"\n stroke={color}\n strokeWidth={thickness}\n strokeLinecap=\"round\"\n strokeDasharray={circumference}\n strokeDashoffset={offset}\n transform={`rotate(-90 ${CENTER} ${CENTER})`}\n style={{\n transition: reduced ? undefined : 'stroke-dashoffset 900ms cubic-bezier(0.22,1,0.36,1)',\n }}\n />\n </svg>\n <div className=\"pointer-events-none absolute inset-0 flex flex-col items-center justify-center text-center\">\n <span className=\"text-2xl font-semibold leading-none text-text-primary\">\n {label ?? Math.round(fraction * 100)}\n </span>\n {caption !== undefined ? (\n <span className=\"mt-1 text-xs text-text-muted\">{caption}</span>\n ) : null}\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;AAsBA,IAAM,IAAO,KACP,IAAS,IAAO;AAGtB,SAAgB,EAAa,EAC3B,UACA,SAAM,KACN,UACA,YACA,UAAO,KACP,eAAY,IACZ,WAAQ,GACR,UACA,gBACoB;CACpB,IAAM,IAAU,GAAY,EACtB,IAAU,GAAyB,EAEnC,IAAS,IAAS,IAAY,GAC9B,IAAgB,IAAI,KAAK,KAAK,GAC9B,IAAW,EAAM,MAAQ,IAAI,IAAI,IAAQ,GAAK,GAAG,EAAE,EACnD,IAAS,KAAiB,IAAI,IAC9B,IAAS,KAAW,IAAU,IAAS,GACvC,IAAY,KAAS,aAAa,KAAK,MAAM,IAAW,IAAI,CAAC;AAEnE,QACE,kBAAC,OAAD;EACE,WAAW,EAAG,iCAAiC,EAAU;EACzD,OAAO;GAAE,OAAO;GAAM,QAAQ;GAAM,UAAU;GAAQ;YAFxD,CAIE,kBAAC,OAAD;GACE,SAAS,OAAO,EAAK,GAAG;GACxB,OAAO;GACP,QAAQ;GACR,MAAK;GACL,cAAY;GACZ,OAAO;IAAE,SAAS;IAAS,UAAU;IAAQ,QAAQ;IAAQ;aAN/D;IAQE,kBAAC,SAAD,EAAA,UAAQ,GAAkB,CAAA;IAC1B,kBAAC,UAAD;KACE,IAAI;KACJ,IAAI;KACJ,GAAG;KACH,MAAK;KACL,QAAO;KACP,aAAa;KACb,SAAS;KACT,CAAA;IACF,kBAAC,UAAD;KACE,IAAI;KACJ,IAAI;KACJ,GAAG;KACH,MAAK;KACL,QAAQ;KACR,aAAa;KACb,eAAc;KACd,iBAAiB;KACjB,kBAAkB;KAClB,WAAW,cAAc,EAAO,GAAG,EAAO;KAC1C,OAAO,EACL,YAAY,IAAU,KAAA,IAAY,uDACnC;KACD,CAAA;IACE;MACN,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,QAAD;IAAM,WAAU;cACb,KAAS,KAAK,MAAM,IAAW,IAAI;IAC/B,CAAA,EACN,MAAY,KAAA,IAET,OADF,kBAAC,QAAD;IAAM,WAAU;cAAgC;IAAe,CAAA,CAE7D;KACF"}
@@ -0,0 +1,51 @@
1
+ import { cn as e } from "../../utils/cn.js";
2
+ import { STATUS as t } from "./lib/palette.js";
3
+ import { clamp as n } from "./lib/geometry.js";
4
+ import { Star as r } from "lucide-react";
5
+ import { jsx as i, jsxs as a } from "react/jsx-runtime";
6
+ //#region src/kadaikodi/components/viz/RatingStars.tsx
7
+ function o({ value: o, max: s = 5, showValue: c = !1, count: l, size: u = 16, className: d }) {
8
+ let f = n(o, 0, s), p = f / s * 100, m = Math.round(u * .15), h = (e) => /* @__PURE__ */ i("span", {
9
+ className: "flex",
10
+ style: { gap: m },
11
+ "aria-hidden": "true",
12
+ children: Array.from({ length: s }, (n, a) => /* @__PURE__ */ i(r, {
13
+ width: u,
14
+ height: u,
15
+ strokeWidth: 1.5,
16
+ style: { color: e ? t.warning : "var(--color-border-strong)" },
17
+ fill: e ? "currentColor" : "none"
18
+ }, a))
19
+ });
20
+ return /* @__PURE__ */ a("span", {
21
+ className: e("inline-flex items-center gap-1.5", d),
22
+ role: "img",
23
+ "aria-label": `Rated ${f} out of ${s}${l === void 0 ? "" : ` from ${l} reviews`}`,
24
+ children: [
25
+ /* @__PURE__ */ a("span", {
26
+ className: "relative inline-flex",
27
+ children: [h(!1), /* @__PURE__ */ i("span", {
28
+ className: "absolute left-0 top-0 overflow-hidden",
29
+ style: { width: `${p}%` },
30
+ children: h(!0)
31
+ })]
32
+ }),
33
+ c ? /* @__PURE__ */ i("span", {
34
+ className: "text-sm font-medium tabular-nums text-text-secondary",
35
+ children: f.toFixed(1)
36
+ }) : null,
37
+ l === void 0 ? null : /* @__PURE__ */ a("span", {
38
+ className: "text-xs text-text-muted",
39
+ children: [
40
+ "(",
41
+ l,
42
+ ")"
43
+ ]
44
+ })
45
+ ]
46
+ });
47
+ }
48
+ //#endregion
49
+ export { o as RatingStars };
50
+
51
+ //# sourceMappingURL=RatingStars.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RatingStars.js","names":[],"sources":["../../../../src/kadaikodi/components/viz/RatingStars.tsx"],"sourcesContent":["import { Star } from 'lucide-react';\nimport { cn } from '../../utils/cn';\nimport { STATUS } from './lib/palette';\nimport { clamp } from './lib/geometry';\n\nexport interface RatingStarsProps {\n value: number;\n max?: number;\n /** Show the numeric value next to the stars. */\n showValue?: boolean;\n /** Optional review count, e.g. \"(128)\". */\n count?: number;\n /** Star size in pixels. */\n size?: number;\n className?: string;\n}\n\n/** Filled / empty stars for a 0–max rating, with optional value + count. */\nexport function RatingStars({\n value,\n max = 5,\n showValue = false,\n count,\n size = 16,\n className,\n}: RatingStarsProps) {\n const clamped = clamp(value, 0, max);\n const fillPct = (clamped / max) * 100;\n const gap = Math.round(size * 0.15);\n\n const row = (filled: boolean) => (\n <span className=\"flex\" style={{ gap }} aria-hidden=\"true\">\n {Array.from({ length: max }, (_, i) => (\n <Star\n key={i}\n width={size}\n height={size}\n strokeWidth={1.5}\n style={{ color: filled ? STATUS.warning : 'var(--color-border-strong)' }}\n fill={filled ? 'currentColor' : 'none'}\n />\n ))}\n </span>\n );\n\n return (\n <span\n className={cn('inline-flex items-center gap-1.5', className)}\n role=\"img\"\n aria-label={`Rated ${clamped} out of ${max}${count !== undefined ? ` from ${count} reviews` : ''}`}\n >\n <span className=\"relative inline-flex\">\n {row(false)}\n <span className=\"absolute left-0 top-0 overflow-hidden\" style={{ width: `${fillPct}%` }}>\n {row(true)}\n </span>\n </span>\n {showValue ? (\n <span className=\"text-sm font-medium tabular-nums text-text-secondary\">\n {clamped.toFixed(1)}\n </span>\n ) : null}\n {count !== undefined ? <span className=\"text-xs text-text-muted\">({count})</span> : null}\n </span>\n );\n}\n"],"mappings":";;;;;;AAkBA,SAAgB,EAAY,EAC1B,UACA,SAAM,GACN,eAAY,IACZ,UACA,UAAO,IACP,gBACmB;CACnB,IAAM,IAAU,EAAM,GAAO,GAAG,EAAI,EAC9B,IAAW,IAAU,IAAO,KAC5B,IAAM,KAAK,MAAM,IAAO,IAAK,EAE7B,KAAO,MACX,kBAAC,QAAD;EAAM,WAAU;EAAO,OAAO,EAAE,QAAK;EAAE,eAAY;YAChD,MAAM,KAAK,EAAE,QAAQ,GAAK,GAAG,GAAG,MAC/B,kBAAC,GAAD;GAEE,OAAO;GACP,QAAQ;GACR,aAAa;GACb,OAAO,EAAE,OAAO,IAAS,EAAO,UAAU,8BAA8B;GACxE,MAAM,IAAS,iBAAiB;GAChC,EANK,EAML,CACF;EACG,CAAA;AAGT,QACE,kBAAC,QAAD;EACE,WAAW,EAAG,oCAAoC,EAAU;EAC5D,MAAK;EACL,cAAY,SAAS,EAAQ,UAAU,IAAM,MAAU,KAAA,IAAuC,KAA3B,SAAS,EAAM;YAHpF;GAKE,kBAAC,QAAD;IAAM,WAAU;cAAhB,CACG,EAAI,GAAM,EACX,kBAAC,QAAD;KAAM,WAAU;KAAwC,OAAO,EAAE,OAAO,GAAG,EAAQ,IAAI;eACpF,EAAI,GAAK;KACL,CAAA,CACF;;GACN,IACC,kBAAC,QAAD;IAAM,WAAU;cACb,EAAQ,QAAQ,EAAE;IACd,CAAA,GACL;GACH,MAAU,KAAA,IAAyE,OAA7D,kBAAC,QAAD;IAAM,WAAU;cAAhB;KAA0C;KAAE;KAAM;KAAQ;;GAC5E"}
@@ -0,0 +1,69 @@
1
+ import { cn as e } from "../../utils/cn.js";
2
+ import { ACCENT as t } from "./lib/palette.js";
3
+ import { useRef as n } from "react";
4
+ import { jsx as r, jsxs as i } from "react/jsx-runtime";
5
+ //#region src/kadaikodi/components/viz/SegmentedTabs.tsx
6
+ function a({ tabs: a, value: o, onChange: s, variant: c = "pill", fill: l = !1, className: u, "aria-label": d }) {
7
+ let f = n([]), p = (e) => {
8
+ let t = (e + a.length) % a.length, n = a[t];
9
+ n && (s(n.id), f.current[t]?.focus());
10
+ }, m = (e, t) => {
11
+ switch (e.key) {
12
+ case "ArrowRight":
13
+ case "ArrowDown":
14
+ e.preventDefault(), p(t + 1);
15
+ break;
16
+ case "ArrowLeft":
17
+ case "ArrowUp":
18
+ e.preventDefault(), p(t - 1);
19
+ break;
20
+ case "Home":
21
+ e.preventDefault(), p(0);
22
+ break;
23
+ case "End":
24
+ e.preventDefault(), p(a.length - 1);
25
+ break;
26
+ default: break;
27
+ }
28
+ }, h = c === "pill";
29
+ return /* @__PURE__ */ r("div", {
30
+ role: "tablist",
31
+ "aria-label": d,
32
+ className: e("flex items-center", h ? "gap-1 rounded-full border border-border-subtle bg-bg-sunken p-1" : "gap-1 border-b border-border-subtle", l && "w-full", u),
33
+ children: a.map((n, a) => {
34
+ let c = n.id === o;
35
+ return /* @__PURE__ */ i("button", {
36
+ ref: (e) => {
37
+ f.current[a] = e;
38
+ },
39
+ type: "button",
40
+ role: "tab",
41
+ "aria-selected": c,
42
+ "aria-controls": n.controls,
43
+ tabIndex: c ? 0 : -1,
44
+ onClick: () => s(n.id),
45
+ onKeyDown: (e) => m(e, a),
46
+ className: e("inline-flex items-center justify-center gap-1.5 whitespace-nowrap text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]", l && "flex-1", h ? e("rounded-full px-3 py-1.5", c ? "bg-bg-surface text-text-primary shadow-elevation-1" : "text-text-secondary hover:text-text-primary") : e("-mb-px border-b-2 px-3 py-2", c ? "text-text-primary" : "border-transparent text-text-secondary hover:text-text-primary")),
47
+ style: !h && c ? {
48
+ borderColor: t,
49
+ color: "var(--color-text-primary)"
50
+ } : void 0,
51
+ children: [
52
+ n.icon ? /* @__PURE__ */ r("span", {
53
+ "aria-hidden": "true",
54
+ children: n.icon
55
+ }) : null,
56
+ n.label,
57
+ n.count === void 0 ? null : /* @__PURE__ */ r("span", {
58
+ className: e("inline-flex min-w-5 items-center justify-center rounded-full px-1.5 text-xs tabular-nums", c ? "bg-bg-sunken text-text-secondary" : "bg-bg-elevated text-text-muted"),
59
+ children: n.count
60
+ })
61
+ ]
62
+ }, n.id);
63
+ })
64
+ });
65
+ }
66
+ //#endregion
67
+ export { a as SegmentedTabs };
68
+
69
+ //# sourceMappingURL=SegmentedTabs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SegmentedTabs.js","names":[],"sources":["../../../../src/kadaikodi/components/viz/SegmentedTabs.tsx"],"sourcesContent":["import { type KeyboardEvent, type ReactNode, useRef } from 'react';\nimport { cn } from '../../utils/cn';\nimport { ACCENT } from './lib/palette';\n\nexport interface SegmentedTab {\n id: string;\n label: ReactNode;\n icon?: ReactNode;\n count?: number;\n /** id of the panel this tab controls (for aria-controls). */\n controls?: string;\n}\n\nexport interface SegmentedTabsProps {\n tabs: SegmentedTab[];\n value: string;\n onChange: (id: string) => void;\n variant?: 'pill' | 'underline';\n /** Stretch tabs to fill the available width. */\n fill?: boolean;\n className?: string;\n 'aria-label'?: string;\n}\n\n/**\n * A controlled pill / underline tab switcher with a proper tablist:\n * roving tab focus, arrow / Home / End navigation, and tokenized styling.\n */\nexport function SegmentedTabs({\n tabs,\n value,\n onChange,\n variant = 'pill',\n fill = false,\n className,\n 'aria-label': ariaLabel,\n}: SegmentedTabsProps) {\n const refs = useRef<(HTMLButtonElement | null)[]>([]);\n\n const focusTab = (index: number): void => {\n const clamped = (index + tabs.length) % tabs.length;\n const tab = tabs[clamped];\n if (tab) {\n onChange(tab.id);\n refs.current[clamped]?.focus();\n }\n };\n\n const handleKey = (event: KeyboardEvent<HTMLButtonElement>, index: number): void => {\n switch (event.key) {\n case 'ArrowRight':\n case 'ArrowDown':\n event.preventDefault();\n focusTab(index + 1);\n break;\n case 'ArrowLeft':\n case 'ArrowUp':\n event.preventDefault();\n focusTab(index - 1);\n break;\n case 'Home':\n event.preventDefault();\n focusTab(0);\n break;\n case 'End':\n event.preventDefault();\n focusTab(tabs.length - 1);\n break;\n default:\n break;\n }\n };\n\n const isPill = variant === 'pill';\n\n return (\n <div\n role=\"tablist\"\n aria-label={ariaLabel}\n className={cn(\n 'flex items-center',\n isPill\n ? 'gap-1 rounded-full border border-border-subtle bg-bg-sunken p-1'\n : 'gap-1 border-b border-border-subtle',\n fill && 'w-full',\n className\n )}\n >\n {tabs.map((tab, i) => {\n const selected = tab.id === value;\n return (\n <button\n key={tab.id}\n ref={(node) => {\n refs.current[i] = node;\n }}\n type=\"button\"\n role=\"tab\"\n aria-selected={selected}\n aria-controls={tab.controls}\n tabIndex={selected ? 0 : -1}\n onClick={() => onChange(tab.id)}\n onKeyDown={(event) => handleKey(event, i)}\n className={cn(\n 'inline-flex items-center justify-center gap-1.5 whitespace-nowrap text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]',\n fill && 'flex-1',\n isPill\n ? cn(\n 'rounded-full px-3 py-1.5',\n selected\n ? 'bg-bg-surface text-text-primary shadow-elevation-1'\n : 'text-text-secondary hover:text-text-primary'\n )\n : cn(\n '-mb-px border-b-2 px-3 py-2',\n selected\n ? 'text-text-primary'\n : 'border-transparent text-text-secondary hover:text-text-primary'\n )\n )}\n style={\n !isPill && selected\n ? { borderColor: ACCENT, color: 'var(--color-text-primary)' }\n : undefined\n }\n >\n {tab.icon ? <span aria-hidden=\"true\">{tab.icon}</span> : null}\n {tab.label}\n {tab.count !== undefined ? (\n <span\n className={cn(\n 'inline-flex min-w-5 items-center justify-center rounded-full px-1.5 text-xs tabular-nums',\n selected ? 'bg-bg-sunken text-text-secondary' : 'bg-bg-elevated text-text-muted'\n )}\n >\n {tab.count}\n </span>\n ) : null}\n </button>\n );\n })}\n </div>\n );\n}\n"],"mappings":";;;;;AA4BA,SAAgB,EAAc,EAC5B,SACA,UACA,aACA,aAAU,QACV,UAAO,IACP,cACA,cAAc,KACO;CACrB,IAAM,IAAO,EAAqC,EAAE,CAAC,EAE/C,KAAY,MAAwB;EACxC,IAAM,KAAW,IAAQ,EAAK,UAAU,EAAK,QACvC,IAAM,EAAK;AACjB,EAAI,MACF,EAAS,EAAI,GAAG,EAChB,EAAK,QAAQ,IAAU,OAAO;IAI5B,KAAa,GAAyC,MAAwB;AAClF,UAAQ,EAAM,KAAd;GACE,KAAK;GACL,KAAK;AAEH,IADA,EAAM,gBAAgB,EACtB,EAAS,IAAQ,EAAE;AACnB;GACF,KAAK;GACL,KAAK;AAEH,IADA,EAAM,gBAAgB,EACtB,EAAS,IAAQ,EAAE;AACnB;GACF,KAAK;AAEH,IADA,EAAM,gBAAgB,EACtB,EAAS,EAAE;AACX;GACF,KAAK;AAEH,IADA,EAAM,gBAAgB,EACtB,EAAS,EAAK,SAAS,EAAE;AACzB;GACF,QACE;;IAIA,IAAS,MAAY;AAE3B,QACE,kBAAC,OAAD;EACE,MAAK;EACL,cAAY;EACZ,WAAW,EACT,qBACA,IACI,oEACA,uCACJ,KAAQ,UACR,EACD;YAEA,EAAK,KAAK,GAAK,MAAM;GACpB,IAAM,IAAW,EAAI,OAAO;AAC5B,UACE,kBAAC,UAAD;IAEE,MAAM,MAAS;AACb,OAAK,QAAQ,KAAK;;IAEpB,MAAK;IACL,MAAK;IACL,iBAAe;IACf,iBAAe,EAAI;IACnB,UAAU,IAAW,IAAI;IACzB,eAAe,EAAS,EAAI,GAAG;IAC/B,YAAY,MAAU,EAAU,GAAO,EAAE;IACzC,WAAW,EACT,0LACA,KAAQ,UACR,IACI,EACE,4BACA,IACI,uDACA,8CACL,GACD,EACE,+BACA,IACI,sBACA,iEACL,CACN;IACD,OACE,CAAC,KAAU,IACP;KAAE,aAAa;KAAQ,OAAO;KAA6B,GAC3D,KAAA;cAhCR;KAmCG,EAAI,OAAO,kBAAC,QAAD;MAAM,eAAY;gBAAQ,EAAI;MAAY,CAAA,GAAG;KACxD,EAAI;KACJ,EAAI,UAAU,KAAA,IASX,OARF,kBAAC,QAAD;MACE,WAAW,EACT,4FACA,IAAW,qCAAqC,iCACjD;gBAEA,EAAI;MACA,CAAA;KAEF;MA9CF,EAAI,GA8CF;IAEX;EACE,CAAA"}
@@ -0,0 +1,3 @@
1
+ import "./lib/palette.js";
2
+ import "./lib/hooks.js";
3
+ import "react/jsx-runtime";
@@ -0,0 +1,74 @@
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 { ResponsiveSvg as i } from "./lib/ResponsiveSvg.js";
4
+ import { buildAreaPath as a, buildLinePath as o, pointsFromValues as s } from "./lib/geometry.js";
5
+ import { jsx as c, jsxs as l } from "react/jsx-runtime";
6
+ //#region src/kadaikodi/components/viz/Sparkline.tsx
7
+ var u = 3;
8
+ function d({ data: d, variant: f = "line", height: p = 36, label: m, className: h }) {
9
+ let g = r("spark-grad"), _ = t(), v = n();
10
+ return d.length === 0 ? null : /* @__PURE__ */ c(i, {
11
+ height: p,
12
+ title: m ?? `Trend of ${d.length} points`,
13
+ minWidth: 96,
14
+ className: h,
15
+ children: ({ width: t }) => {
16
+ let n = s(d, t, p, u), r = o(n), i = a(n, p - u), m = n[n.length - 1];
17
+ return /* @__PURE__ */ l("g", {
18
+ style: { color: e },
19
+ children: [
20
+ /* @__PURE__ */ c("defs", { children: /* @__PURE__ */ l("linearGradient", {
21
+ id: g,
22
+ x1: "0",
23
+ y1: "0",
24
+ x2: "0",
25
+ y2: "1",
26
+ children: [/* @__PURE__ */ c("stop", {
27
+ offset: "0%",
28
+ stopColor: "currentColor",
29
+ stopOpacity: .28
30
+ }), /* @__PURE__ */ c("stop", {
31
+ offset: "100%",
32
+ stopColor: "currentColor",
33
+ stopOpacity: 0
34
+ })]
35
+ }) }),
36
+ f === "area" ? /* @__PURE__ */ c("path", {
37
+ d: i,
38
+ fill: `url(#${g})`,
39
+ stroke: "none",
40
+ style: {
41
+ opacity: _ || v ? 1 : 0,
42
+ transition: v ? void 0 : "opacity 700ms ease"
43
+ }
44
+ }) : null,
45
+ /* @__PURE__ */ c("path", {
46
+ d: r,
47
+ fill: "none",
48
+ stroke: "currentColor",
49
+ strokeWidth: 2,
50
+ strokeLinecap: "round",
51
+ strokeLinejoin: "round",
52
+ vectorEffect: "non-scaling-stroke",
53
+ pathLength: 1,
54
+ style: {
55
+ strokeDasharray: 1,
56
+ strokeDashoffset: _ || v ? 0 : 1,
57
+ transition: v ? void 0 : "stroke-dashoffset 900ms ease"
58
+ }
59
+ }),
60
+ /* @__PURE__ */ c("circle", {
61
+ cx: m.x,
62
+ cy: m.y,
63
+ r: 2.6,
64
+ fill: "currentColor"
65
+ })
66
+ ]
67
+ });
68
+ }
69
+ });
70
+ }
71
+ //#endregion
72
+ export { d as Sparkline };
73
+
74
+ //# sourceMappingURL=Sparkline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Sparkline.js","names":[],"sources":["../../../../src/kadaikodi/components/viz/Sparkline.tsx"],"sourcesContent":["import { ACCENT } from './lib/palette';\nimport { ResponsiveSvg } from './lib/ResponsiveSvg';\nimport { buildAreaPath, buildLinePath, pointsFromValues } from './lib/geometry';\nimport { useMounted, usePrefersReducedMotion, useVizId } from './lib/hooks';\n\nexport interface SparklineProps {\n /** Ordered trend values (oldest first). */\n data: number[];\n variant?: 'line' | 'area';\n /** Pixel height of the sparkline. */\n height?: number;\n /** Accessible label; defaults to a generic trend description. */\n label?: string;\n className?: string;\n}\n\nconst PAD = 3;\n\n/**\n * Tiny inline trend line/area for KPI cards. Draws itself in on mount\n * (unless reduced-motion is requested) and emphasizes the final point.\n */\nexport function Sparkline({\n data,\n variant = 'line',\n height = 36,\n label,\n className,\n}: SparklineProps) {\n const gradientId = useVizId('spark-grad');\n const mounted = useMounted();\n const reduced = usePrefersReducedMotion();\n\n if (data.length === 0) {\n return null;\n }\n\n const title = label ?? `Trend of ${data.length} points`;\n\n return (\n <ResponsiveSvg height={height} title={title} minWidth={96} className={className}>\n {({ width }) => {\n const points = pointsFromValues(data, width, height, PAD);\n const linePath = buildLinePath(points);\n const areaPath = buildAreaPath(points, height - PAD);\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.28} />\n <stop offset=\"100%\" stopColor=\"currentColor\" stopOpacity={0} />\n </linearGradient>\n </defs>\n {variant === 'area' ? (\n <path\n d={areaPath}\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 ) : null}\n <path\n d={linePath}\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={2.6} fill=\"currentColor\" />\n </g>\n );\n }}\n </ResponsiveSvg>\n );\n}\n"],"mappings":";;;;;;AAgBA,IAAM,IAAM;AAMZ,SAAgB,EAAU,EACxB,SACA,aAAU,QACV,YAAS,IACT,UACA,gBACiB;CACjB,IAAM,IAAa,EAAS,aAAa,EACnC,IAAU,GAAY,EACtB,IAAU,GAAyB;AAQzC,QANI,EAAK,WAAW,IACX,OAMP,kBAAC,GAAD;EAAuB;EAAe,OAH1B,KAAS,YAAY,EAAK,OAAO;EAGA,UAAU;EAAe;aAClE,EAAE,eAAY;GACd,IAAM,IAAS,EAAiB,GAAM,GAAO,GAAQ,EAAI,EACnD,IAAW,EAAc,EAAO,EAChC,IAAW,EAAc,GAAQ,IAAS,EAAI,EAC9C,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;OAAK,CAAA,CAChD;SACZ,CAAA;KACN,MAAY,SACX,kBAAC,QAAD;MACE,GAAG;MACH,MAAM,QAAQ,EAAW;MACzB,QAAO;MACP,OAAO;OACL,SAAS,KAAW,IAAU,IAAI;OAClC,YAAY,IAAU,KAAA,IAAY;OACnC;MACD,CAAA,GACA;KACJ,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;KAC5D;;;EAGM,CAAA"}