@burdenoff/microfe-crewfoundry 2026.704.6 → 2026.704.7

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 (148) hide show
  1. package/dist/crewfoundry/CrewFoundryRoutes.js +51 -43
  2. package/dist/crewfoundry/CrewFoundryRoutes.js.map +1 -1
  3. package/dist/crewfoundry/components/forms/formValues.js +5 -2
  4. package/dist/crewfoundry/components/forms/formValues.js.map +1 -1
  5. package/dist/crewfoundry/components/viz/AllocationBar.js +68 -0
  6. package/dist/crewfoundry/components/viz/AllocationBar.js.map +1 -0
  7. package/dist/crewfoundry/components/viz/AreaChart.js +102 -0
  8. package/dist/crewfoundry/components/viz/AreaChart.js.map +1 -0
  9. package/dist/crewfoundry/components/viz/AvatarStack.js +36 -0
  10. package/dist/crewfoundry/components/viz/AvatarStack.js.map +1 -0
  11. package/dist/crewfoundry/components/viz/BarChart.js +83 -0
  12. package/dist/crewfoundry/components/viz/BarChart.js.map +1 -0
  13. package/dist/crewfoundry/components/viz/CertificateCard.js +118 -0
  14. package/dist/crewfoundry/components/viz/CertificateCard.js.map +1 -0
  15. package/dist/crewfoundry/components/viz/DonutChart.js +109 -0
  16. package/dist/crewfoundry/components/viz/DonutChart.js.map +1 -0
  17. package/dist/crewfoundry/components/viz/HBarFunnel.js +54 -0
  18. package/dist/crewfoundry/components/viz/HBarFunnel.js.map +1 -0
  19. package/dist/crewfoundry/components/viz/Heatmap.js +91 -0
  20. package/dist/crewfoundry/components/viz/Heatmap.js.map +1 -0
  21. package/dist/crewfoundry/components/viz/KanbanBoard.js +50 -0
  22. package/dist/crewfoundry/components/viz/KanbanBoard.js.map +1 -0
  23. package/dist/crewfoundry/components/viz/LineChart.js +123 -0
  24. package/dist/crewfoundry/components/viz/LineChart.js.map +1 -0
  25. package/dist/crewfoundry/components/viz/OrgTree.js +83 -0
  26. package/dist/crewfoundry/components/viz/OrgTree.js.map +1 -0
  27. package/dist/crewfoundry/components/viz/PathSteps.js +68 -0
  28. package/dist/crewfoundry/components/viz/PathSteps.js.map +1 -0
  29. package/dist/crewfoundry/components/viz/ProgressRing.js +68 -0
  30. package/dist/crewfoundry/components/viz/ProgressRing.js.map +1 -0
  31. package/dist/crewfoundry/components/viz/RatingStars.js +51 -0
  32. package/dist/crewfoundry/components/viz/RatingStars.js.map +1 -0
  33. package/dist/crewfoundry/components/viz/SegmentedTabs.js +69 -0
  34. package/dist/crewfoundry/components/viz/SegmentedTabs.js.map +1 -0
  35. package/dist/crewfoundry/components/viz/SkillBar.js +3 -0
  36. package/dist/crewfoundry/components/viz/Sparkline.js +74 -0
  37. package/dist/crewfoundry/components/viz/Sparkline.js.map +1 -0
  38. package/dist/crewfoundry/components/viz/StatCard.js +60 -0
  39. package/dist/crewfoundry/components/viz/StatCard.js.map +1 -0
  40. package/dist/crewfoundry/components/viz/StepProgress.js +3 -0
  41. package/dist/crewfoundry/components/viz/Timeline.js +66 -0
  42. package/dist/crewfoundry/components/viz/Timeline.js.map +1 -0
  43. package/dist/crewfoundry/components/viz/index.js +23 -0
  44. package/dist/crewfoundry/components/viz/lib/Avatar.js +30 -0
  45. package/dist/crewfoundry/components/viz/lib/Avatar.js.map +1 -0
  46. package/dist/crewfoundry/components/viz/lib/CartesianFrame.js +119 -0
  47. package/dist/crewfoundry/components/viz/lib/CartesianFrame.js.map +1 -0
  48. package/dist/crewfoundry/components/viz/lib/ResponsiveSvg.js +35 -0
  49. package/dist/crewfoundry/components/viz/lib/ResponsiveSvg.js.map +1 -0
  50. package/dist/crewfoundry/components/viz/lib/format.js +21 -0
  51. package/dist/crewfoundry/components/viz/lib/format.js.map +1 -0
  52. package/dist/crewfoundry/components/viz/lib/geometry.js +69 -0
  53. package/dist/crewfoundry/components/viz/lib/geometry.js.map +1 -0
  54. package/dist/crewfoundry/components/viz/lib/hooks.js +39 -0
  55. package/dist/crewfoundry/components/viz/lib/hooks.js.map +1 -0
  56. package/dist/crewfoundry/components/viz/lib/palette.js +27 -0
  57. package/dist/crewfoundry/components/viz/lib/palette.js.map +1 -0
  58. package/dist/crewfoundry/lib/analytics.js +323 -0
  59. package/dist/crewfoundry/lib/analytics.js.map +1 -0
  60. package/dist/crewfoundry/lib/companyFinance.js +16 -0
  61. package/dist/crewfoundry/lib/companyFinance.js.map +1 -0
  62. package/dist/crewfoundry/pages/AnalyticsAnalyticsPagePage.js +226 -283
  63. package/dist/crewfoundry/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
  64. package/dist/crewfoundry/pages/AnalyticsEmployeeAnalyticsPagePage.js +107 -120
  65. package/dist/crewfoundry/pages/AnalyticsEmployeeAnalyticsPagePage.js.map +1 -1
  66. package/dist/crewfoundry/pages/AnalyticsFinancialAnalyticsPagePage.js +122 -162
  67. package/dist/crewfoundry/pages/AnalyticsFinancialAnalyticsPagePage.js.map +1 -1
  68. package/dist/crewfoundry/pages/AnalyticsHiringAnalyticsPagePage.js +121 -158
  69. package/dist/crewfoundry/pages/AnalyticsHiringAnalyticsPagePage.js.map +1 -1
  70. package/dist/crewfoundry/pages/CommunicationsAnnouncementsPagePage.js +316 -211
  71. package/dist/crewfoundry/pages/CommunicationsAnnouncementsPagePage.js.map +1 -1
  72. package/dist/crewfoundry/pages/CommunicationsMessagesPagePage.js +75 -27
  73. package/dist/crewfoundry/pages/CommunicationsMessagesPagePage.js.map +1 -1
  74. package/dist/crewfoundry/pages/CompanyClientManagementPagePage.js +167 -165
  75. package/dist/crewfoundry/pages/CompanyClientManagementPagePage.js.map +1 -1
  76. package/dist/crewfoundry/pages/CompanyVendorManagementPagePage.js +168 -109
  77. package/dist/crewfoundry/pages/CompanyVendorManagementPagePage.js.map +1 -1
  78. package/dist/crewfoundry/pages/DashboardDashboardPagePage.js +401 -290
  79. package/dist/crewfoundry/pages/DashboardDashboardPagePage.js.map +1 -1
  80. package/dist/crewfoundry/pages/HiringApplicationsPagePage.js +84 -75
  81. package/dist/crewfoundry/pages/HiringApplicationsPagePage.js.map +1 -1
  82. package/dist/crewfoundry/pages/HiringCandidatesPagePage.js +368 -174
  83. package/dist/crewfoundry/pages/HiringCandidatesPagePage.js.map +1 -1
  84. package/dist/crewfoundry/pages/HiringCreateJobPagePage.js +236 -201
  85. package/dist/crewfoundry/pages/HiringCreateJobPagePage.js.map +1 -1
  86. package/dist/crewfoundry/pages/HiringInterviewsPagePage.js +369 -196
  87. package/dist/crewfoundry/pages/HiringInterviewsPagePage.js.map +1 -1
  88. package/dist/crewfoundry/pages/HiringJobDetailsPagePage.js +269 -270
  89. package/dist/crewfoundry/pages/HiringJobDetailsPagePage.js.map +1 -1
  90. package/dist/crewfoundry/pages/HiringJobsPagePage.js +210 -110
  91. package/dist/crewfoundry/pages/HiringJobsPagePage.js.map +1 -1
  92. package/dist/crewfoundry/pages/HrDepartmentsPagePage.js +216 -178
  93. package/dist/crewfoundry/pages/HrDepartmentsPagePage.js.map +1 -1
  94. package/dist/crewfoundry/pages/HrEmployeeProfilePagePage.js +385 -0
  95. package/dist/crewfoundry/pages/HrEmployeeProfilePagePage.js.map +1 -0
  96. package/dist/crewfoundry/pages/HrEmployeesPagePage.js +189 -130
  97. package/dist/crewfoundry/pages/HrEmployeesPagePage.js.map +1 -1
  98. package/dist/crewfoundry/pages/HrLeaveManagementPagePage.js +397 -241
  99. package/dist/crewfoundry/pages/HrLeaveManagementPagePage.js.map +1 -1
  100. package/dist/crewfoundry/pages/HrPayrollPagePage.js +262 -147
  101. package/dist/crewfoundry/pages/HrPayrollPagePage.js.map +1 -1
  102. package/dist/crewfoundry/pages/HrTimeTrackingPagePage.js +345 -167
  103. package/dist/crewfoundry/pages/HrTimeTrackingPagePage.js.map +1 -1
  104. package/dist/crewfoundry/pages/LearningCertificatesPagePage.js +98 -24
  105. package/dist/crewfoundry/pages/LearningCertificatesPagePage.js.map +1 -1
  106. package/dist/crewfoundry/pages/LearningCourseDetailsPagePage.js +1 -1
  107. package/dist/crewfoundry/pages/LearningCourseDetailsPagePage.js.map +1 -1
  108. package/dist/crewfoundry/pages/LearningCourseLearningPagePage.js +289 -108
  109. package/dist/crewfoundry/pages/LearningCourseLearningPagePage.js.map +1 -1
  110. package/dist/crewfoundry/pages/LearningCoursesPagePage.js +221 -114
  111. package/dist/crewfoundry/pages/LearningCoursesPagePage.js.map +1 -1
  112. package/dist/crewfoundry/pages/LearningLearningPathDetailPagePage.js +134 -135
  113. package/dist/crewfoundry/pages/LearningLearningPathDetailPagePage.js.map +1 -1
  114. package/dist/crewfoundry/pages/LearningLearningPathsPagePage.js +148 -97
  115. package/dist/crewfoundry/pages/LearningLearningPathsPagePage.js.map +1 -1
  116. package/dist/crewfoundry/pages/LearningMyLearningPagePage.js +264 -157
  117. package/dist/crewfoundry/pages/LearningMyLearningPagePage.js.map +1 -1
  118. package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePagePage.js +85 -93
  119. package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePagePage.js.map +1 -1
  120. package/dist/crewfoundry/pages/MarketplaceMarketplacePagePage.js +211 -122
  121. package/dist/crewfoundry/pages/MarketplaceMarketplacePagePage.js.map +1 -1
  122. package/dist/crewfoundry/pages/MarketplaceMarketplaceServicesPagePage.js +94 -54
  123. package/dist/crewfoundry/pages/MarketplaceMarketplaceServicesPagePage.js.map +1 -1
  124. package/dist/crewfoundry/pages/MarketplaceServiceDetailsPagePage.js +289 -140
  125. package/dist/crewfoundry/pages/MarketplaceServiceDetailsPagePage.js.map +1 -1
  126. package/dist/crewfoundry/pages/PaymentsInvoicesPagePage.js +169 -79
  127. package/dist/crewfoundry/pages/PaymentsInvoicesPagePage.js.map +1 -1
  128. package/dist/crewfoundry/pages/ProjectsProjectsPagePage.js +296 -125
  129. package/dist/crewfoundry/pages/ProjectsProjectsPagePage.js.map +1 -1
  130. package/dist/crewfoundry/pages/ProjectsResourcingPagePage.js +187 -0
  131. package/dist/crewfoundry/pages/ProjectsResourcingPagePage.js.map +1 -0
  132. package/dist/crewfoundry/pages/TeamsTeamDetailPagePage.js +140 -78
  133. package/dist/crewfoundry/pages/TeamsTeamDetailPagePage.js.map +1 -1
  134. package/dist/crewfoundry/pages/TeamsTeamDirectoryPagePage.js +93 -43
  135. package/dist/crewfoundry/pages/TeamsTeamDirectoryPagePage.js.map +1 -1
  136. package/dist/crewfoundry/pages/TeamsTeamsPagePage.js +250 -98
  137. package/dist/crewfoundry/pages/TeamsTeamsPagePage.js.map +1 -1
  138. package/dist/crewfoundry/store/crewfoundryStore.js +1 -1
  139. package/dist/crewfoundry/store/crewfoundryStore.js.map +1 -1
  140. package/dist/crewfoundry/utils/cn.js +10 -0
  141. package/dist/crewfoundry/utils/cn.js.map +1 -0
  142. package/dist/crewfoundry/utils/courseProgress.js +46 -0
  143. package/dist/crewfoundry/utils/courseProgress.js.map +1 -0
  144. package/dist/node_modules/clsx/dist/clsx.js +18 -0
  145. package/dist/node_modules/clsx/dist/clsx.js.map +1 -0
  146. package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js +1716 -0
  147. package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -0
  148. package/package.json +1 -1
@@ -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/crewfoundry/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/crewfoundry/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"}
@@ -0,0 +1,60 @@
1
+ import { cn as e } from "../../utils/cn.js";
2
+ import { Sparkline as t } from "./Sparkline.js";
3
+ import { ArrowDownRight as n, ArrowUpRight as r } from "lucide-react";
4
+ import { jsx as i, jsxs as a } from "react/jsx-runtime";
5
+ //#region src/crewfoundry/components/viz/StatCard.tsx
6
+ function o({ label: o, value: s, delta: c, deltaSuffix: l = "%", deltaLabel: u, invertDelta: d = !1, icon: f, sparkline: p, sparklineVariant: m = "area", className: h }) {
7
+ let g = typeof c == "number", _ = d ? (c ?? 0) < 0 : (c ?? 0) > 0, v = (c ?? 0) === 0, y = (c ?? 0) > 0, b = v ? "text-text-muted" : _ ? "text-status-success-text" : "text-status-error-text";
8
+ return /* @__PURE__ */ a("div", {
9
+ className: e("flex flex-col gap-3 rounded-[var(--radius-card)] border border-border-subtle bg-bg-surface p-5 shadow-elevation-1 transition-shadow duration-200 hover:shadow-elevation-2", h),
10
+ children: [
11
+ /* @__PURE__ */ a("div", {
12
+ className: "flex items-start justify-between gap-2",
13
+ children: [/* @__PURE__ */ i("span", {
14
+ className: "text-sm font-medium text-text-secondary",
15
+ children: o
16
+ }), f ? /* @__PURE__ */ i("span", {
17
+ className: "text-text-muted",
18
+ children: f
19
+ }) : null]
20
+ }),
21
+ /* @__PURE__ */ i("div", {
22
+ className: "flex items-end justify-between gap-3",
23
+ children: /* @__PURE__ */ i("span", {
24
+ className: "text-3xl font-semibold leading-none tracking-tight tabular-nums text-text-primary",
25
+ children: s
26
+ })
27
+ }),
28
+ g ? /* @__PURE__ */ a("div", {
29
+ className: "flex items-center gap-1.5 text-sm",
30
+ children: [/* @__PURE__ */ a("span", {
31
+ className: e("inline-flex items-center gap-0.5 font-medium tabular-nums", b),
32
+ children: [
33
+ v ? null : i(y ? r : n, {
34
+ "aria-hidden": "true",
35
+ className: "h-4 w-4"
36
+ }),
37
+ y ? "+" : "",
38
+ c,
39
+ l
40
+ ]
41
+ }), u ? /* @__PURE__ */ i("span", {
42
+ className: "text-text-muted",
43
+ children: u
44
+ }) : null]
45
+ }) : null,
46
+ p && p.length > 1 ? /* @__PURE__ */ i("div", {
47
+ className: "mt-1",
48
+ children: /* @__PURE__ */ i(t, {
49
+ data: p,
50
+ variant: m,
51
+ label: `${o} trend`
52
+ })
53
+ }) : null
54
+ ]
55
+ });
56
+ }
57
+ //#endregion
58
+ export { o as StatCard };
59
+
60
+ //# sourceMappingURL=StatCard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StatCard.js","names":[],"sources":["../../../../src/crewfoundry/components/viz/StatCard.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { ArrowDownRight, ArrowUpRight } from 'lucide-react';\nimport { cn } from '../../utils/cn';\nimport { Sparkline } from './Sparkline';\n\nexport interface StatCardProps {\n label: string;\n value: ReactNode;\n /** Signed change; positive renders success (unless `invertDelta`). */\n delta?: number;\n /** Unit appended to the delta (default \"%\"). */\n deltaSuffix?: string;\n /** Context line under the delta, e.g. \"vs last month\". */\n deltaLabel?: string;\n /** When true (attrition, cost) a negative delta is the good outcome. */\n invertDelta?: boolean;\n icon?: ReactNode;\n /** Optional trend to render as a footer sparkline. */\n sparkline?: number[];\n sparklineVariant?: 'line' | 'area';\n className?: string;\n}\n\n/** KPI tile: label, big tabular value, optional signed delta and sparkline. */\nexport function StatCard({\n label,\n value,\n delta,\n deltaSuffix = '%',\n deltaLabel,\n invertDelta = false,\n icon,\n sparkline,\n sparklineVariant = 'area',\n className,\n}: StatCardProps) {\n const hasDelta = typeof delta === 'number';\n const positiveIsGood = invertDelta ? (delta ?? 0) < 0 : (delta ?? 0) > 0;\n const isNeutral = (delta ?? 0) === 0;\n const rising = (delta ?? 0) > 0;\n\n const toneClass = isNeutral\n ? 'text-text-muted'\n : positiveIsGood\n ? 'text-status-success-text'\n : 'text-status-error-text';\n\n return (\n <div\n className={cn(\n 'flex flex-col gap-3 rounded-[var(--radius-card)] border border-border-subtle bg-bg-surface p-5 shadow-elevation-1 transition-shadow duration-200 hover:shadow-elevation-2',\n className,\n )}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <span className=\"text-sm font-medium text-text-secondary\">{label}</span>\n {icon ? <span className=\"text-text-muted\">{icon}</span> : null}\n </div>\n <div className=\"flex items-end justify-between gap-3\">\n <span className=\"text-3xl font-semibold leading-none tracking-tight tabular-nums text-text-primary\">\n {value}\n </span>\n </div>\n {hasDelta ? (\n <div className=\"flex items-center gap-1.5 text-sm\">\n <span className={cn('inline-flex items-center gap-0.5 font-medium tabular-nums', toneClass)}>\n {isNeutral ? null : rising ? (\n <ArrowUpRight aria-hidden=\"true\" className=\"h-4 w-4\" />\n ) : (\n <ArrowDownRight aria-hidden=\"true\" className=\"h-4 w-4\" />\n )}\n {rising ? '+' : ''}\n {delta}\n {deltaSuffix}\n </span>\n {deltaLabel ? <span className=\"text-text-muted\">{deltaLabel}</span> : null}\n </div>\n ) : null}\n {sparkline && sparkline.length > 1 ? (\n <div className=\"mt-1\">\n <Sparkline data={sparkline} variant={sparklineVariant} label={`${label} trend`} />\n </div>\n ) : null}\n </div>\n );\n}\n"],"mappings":";;;;;AAwBA,SAAgB,EAAS,EACvB,UACA,UACA,UACA,iBAAc,KACd,eACA,iBAAc,IACd,SACA,cACA,sBAAmB,QACnB,gBACgB;CAChB,IAAM,IAAW,OAAO,KAAU,UAC5B,IAAiB,KAAe,KAAS,KAAK,KAAK,KAAS,KAAK,GACjE,KAAa,KAAS,OAAO,GAC7B,KAAU,KAAS,KAAK,GAExB,IAAY,IACd,oBACA,IACE,6BACA;AAEN,QACE,kBAAC,OAAD;EACE,WAAW,EACT,6KACA,EACD;YAJH;GAME,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,QAAD;KAAM,WAAU;eAA2C;KAAa,CAAA,EACvE,IAAO,kBAAC,QAAD;KAAM,WAAU;eAAmB;KAAY,CAAA,GAAG,KACtD;;GACN,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,QAAD;KAAM,WAAU;eACb;KACI,CAAA;IACH,CAAA;GACL,IACC,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,QAAD;KAAM,WAAW,EAAG,6DAA6D,EAAU;eAA3F;MACG,IAAY,OACX,EADkB,IACjB,IAEA,GAFD;OAAc,eAAY;OAAO,WAAU;OAAY,CAEE;MAE1D,IAAS,MAAM;MACf;MACA;MACI;QACN,IAAa,kBAAC,QAAD;KAAM,WAAU;eAAmB;KAAkB,CAAA,GAAG,KAClE;QACJ;GACH,KAAa,EAAU,SAAS,IAC/B,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD;KAAW,MAAM;KAAW,SAAS;KAAkB,OAAO,GAAG,EAAM;KAAW,CAAA;IAC9E,CAAA,GACJ;GACA"}
@@ -0,0 +1,3 @@
1
+ import "./lib/palette.js";
2
+ import "lucide-react";
3
+ import "react/jsx-runtime";
@@ -0,0 +1,66 @@
1
+ import { ACCENT as e, STATUS as t } from "./lib/palette.js";
2
+ import { cn as n } from "../../utils/cn.js";
3
+ import { jsx as r, jsxs as i } from "react/jsx-runtime";
4
+ //#region src/crewfoundry/components/viz/Timeline.tsx
5
+ function a(n) {
6
+ switch (n) {
7
+ case "success":
8
+ case "warning":
9
+ case "danger":
10
+ case "info": return t[n];
11
+ case "muted": return "var(--color-text-muted)";
12
+ default: return e;
13
+ }
14
+ }
15
+ function o({ items: e, className: t }) {
16
+ return e.length === 0 ? null : /* @__PURE__ */ r("ol", {
17
+ className: n("flex flex-col", t),
18
+ children: e.map((t, o) => {
19
+ let s = o === e.length - 1, c = a(t.tone ?? "accent");
20
+ return /* @__PURE__ */ i("li", {
21
+ className: "flex gap-3",
22
+ children: [/* @__PURE__ */ i("div", {
23
+ className: "flex flex-col items-center",
24
+ children: [/* @__PURE__ */ r("span", {
25
+ className: "mt-0.5 flex h-6 w-6 shrink-0 items-center justify-center rounded-full border-2",
26
+ style: {
27
+ borderColor: c,
28
+ color: c
29
+ },
30
+ children: t.icon ?? /* @__PURE__ */ r("span", {
31
+ "aria-hidden": "true",
32
+ className: "h-2 w-2 rounded-full",
33
+ style: { backgroundColor: c }
34
+ })
35
+ }), s ? null : /* @__PURE__ */ r("span", {
36
+ "aria-hidden": "true",
37
+ className: "w-px flex-1",
38
+ style: {
39
+ backgroundColor: "var(--color-border-subtle)",
40
+ minHeight: 12
41
+ }
42
+ })]
43
+ }), /* @__PURE__ */ i("div", {
44
+ className: n("flex-1", s ? "pb-0" : "pb-5"),
45
+ children: [/* @__PURE__ */ i("div", {
46
+ className: "flex flex-wrap items-baseline justify-between gap-x-3",
47
+ children: [/* @__PURE__ */ r("p", {
48
+ className: "text-sm font-medium text-text-primary",
49
+ children: t.title
50
+ }), t.time ? /* @__PURE__ */ r("span", {
51
+ className: "shrink-0 text-xs text-text-muted",
52
+ children: t.time
53
+ }) : null]
54
+ }), t.meta ? /* @__PURE__ */ r("div", {
55
+ className: "mt-0.5 text-sm text-text-secondary",
56
+ children: t.meta
57
+ }) : null]
58
+ })]
59
+ }, t.id ?? o);
60
+ })
61
+ });
62
+ }
63
+ //#endregion
64
+ export { o as Timeline };
65
+
66
+ //# sourceMappingURL=Timeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Timeline.js","names":[],"sources":["../../../../src/crewfoundry/components/viz/Timeline.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { cn } from '../../utils/cn';\nimport { ACCENT, STATUS, type StatusTone } from './lib/palette';\n\nexport type TimelineTone = 'accent' | StatusTone | 'muted';\n\nexport interface TimelineItem {\n id?: string;\n title: ReactNode;\n meta?: ReactNode;\n time?: ReactNode;\n /** Colors the node dot. Defaults to accent. */\n tone?: TimelineTone;\n /** Optional icon rendered inside the node. */\n icon?: ReactNode;\n}\n\nexport interface TimelineProps {\n items: TimelineItem[];\n className?: string;\n}\n\nfunction dotColor(tone: TimelineTone): string {\n switch (tone) {\n case 'success':\n case 'warning':\n case 'danger':\n case 'info':\n return STATUS[tone];\n case 'muted':\n return 'var(--color-text-muted)';\n default:\n return ACCENT;\n }\n}\n\n/** Vertical activity feed: node dot + connector + title, meta and time. */\nexport function Timeline({ items, className }: TimelineProps) {\n if (items.length === 0) {\n return null;\n }\n return (\n <ol className={cn('flex flex-col', className)}>\n {items.map((item, i) => {\n const isLast = i === items.length - 1;\n const color = dotColor(item.tone ?? 'accent');\n return (\n <li key={item.id ?? i} className=\"flex gap-3\">\n <div className=\"flex flex-col items-center\">\n <span\n className=\"mt-0.5 flex h-6 w-6 shrink-0 items-center justify-center rounded-full border-2\"\n style={{ borderColor: color, color }}\n >\n {item.icon ?? (\n <span\n aria-hidden=\"true\"\n className=\"h-2 w-2 rounded-full\"\n style={{ backgroundColor: color }}\n />\n )}\n </span>\n {isLast ? null : (\n <span\n aria-hidden=\"true\"\n className=\"w-px flex-1\"\n style={{ backgroundColor: 'var(--color-border-subtle)', minHeight: 12 }}\n />\n )}\n </div>\n <div className={cn('flex-1', isLast ? 'pb-0' : 'pb-5')}>\n <div className=\"flex flex-wrap items-baseline justify-between gap-x-3\">\n <p className=\"text-sm font-medium text-text-primary\">{item.title}</p>\n {item.time ? (\n <span className=\"shrink-0 text-xs text-text-muted\">{item.time}</span>\n ) : null}\n </div>\n {item.meta ? (\n <div className=\"mt-0.5 text-sm text-text-secondary\">{item.meta}</div>\n ) : null}\n </div>\n </li>\n );\n })}\n </ol>\n );\n}\n"],"mappings":";;;;AAsBA,SAAS,EAAS,GAA4B;AAC5C,SAAQ,GAAR;EACE,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK,OACH,QAAO,EAAO;EAChB,KAAK,QACH,QAAO;EACT,QACE,QAAO;;;AAKb,SAAgB,EAAS,EAAE,UAAO,gBAA4B;AAI5D,QAHI,EAAM,WAAW,IACZ,OAGP,kBAAC,MAAD;EAAI,WAAW,EAAG,iBAAiB,EAAU;YAC1C,EAAM,KAAK,GAAM,MAAM;GACtB,IAAM,IAAS,MAAM,EAAM,SAAS,GAC9B,IAAQ,EAAS,EAAK,QAAQ,SAAS;AAC7C,UACE,kBAAC,MAAD;IAAuB,WAAU;cAAjC,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,QAAD;MACE,WAAU;MACV,OAAO;OAAE,aAAa;OAAO;OAAO;gBAEnC,EAAK,QACJ,kBAAC,QAAD;OACE,eAAY;OACZ,WAAU;OACV,OAAO,EAAE,iBAAiB,GAAO;OACjC,CAAA;MAEC,CAAA,EACN,IAAS,OACR,kBAAC,QAAD;MACE,eAAY;MACZ,WAAU;MACV,OAAO;OAAE,iBAAiB;OAA8B,WAAW;OAAI;MACvE,CAAA,CAEA;QACN,kBAAC,OAAD;KAAK,WAAW,EAAG,UAAU,IAAS,SAAS,OAAO;eAAtD,CACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,KAAD;OAAG,WAAU;iBAAyC,EAAK;OAAU,CAAA,EACpE,EAAK,OACJ,kBAAC,QAAD;OAAM,WAAU;iBAAoC,EAAK;OAAY,CAAA,GACnE,KACA;SACL,EAAK,OACJ,kBAAC,OAAD;MAAK,WAAU;gBAAsC,EAAK;MAAW,CAAA,GACnE,KACA;OACH;MAjCI,EAAK,MAAM,EAiCf;IAEP;EACC,CAAA"}
@@ -0,0 +1,23 @@
1
+ import "./lib/palette.js";
2
+ import "./lib/geometry.js";
3
+ import "./Sparkline.js";
4
+ import "./lib/format.js";
5
+ import "./AreaChart.js";
6
+ import "./LineChart.js";
7
+ import "./BarChart.js";
8
+ import "./HBarFunnel.js";
9
+ import "./DonutChart.js";
10
+ import "./ProgressRing.js";
11
+ import "./StatCard.js";
12
+ import "./KanbanBoard.js";
13
+ import "./Heatmap.js";
14
+ import "./Timeline.js";
15
+ import "./OrgTree.js";
16
+ import "./AllocationBar.js";
17
+ import "./StepProgress.js";
18
+ import "./PathSteps.js";
19
+ import "./AvatarStack.js";
20
+ import "./RatingStars.js";
21
+ import "./SkillBar.js";
22
+ import "./CertificateCard.js";
23
+ import "./SegmentedTabs.js";
@@ -0,0 +1,30 @@
1
+ import { cn as e } from "../../../utils/cn.js";
2
+ import { getInitials as t } from "./format.js";
3
+ import { jsx as n } from "react/jsx-runtime";
4
+ //#region src/crewfoundry/components/viz/lib/Avatar.tsx
5
+ function r({ name: r, src: i, size: a = 32, className: o, ring: s = !1 }) {
6
+ return /* @__PURE__ */ n("span", {
7
+ className: e("inline-flex shrink-0 items-center justify-center overflow-hidden rounded-full border border-border-subtle bg-bg-sunken font-medium text-text-secondary", s && "ring-2 ring-[var(--color-bg-surface)]", o),
8
+ style: {
9
+ width: a,
10
+ height: a,
11
+ fontSize: Math.round(a * .38)
12
+ },
13
+ title: r,
14
+ children: i ? /* @__PURE__ */ n("img", {
15
+ src: i,
16
+ alt: r,
17
+ width: a,
18
+ height: a,
19
+ className: "h-full w-full object-cover",
20
+ loading: "lazy"
21
+ }) : /* @__PURE__ */ n("span", {
22
+ "aria-label": r,
23
+ children: t(r)
24
+ })
25
+ });
26
+ }
27
+ //#endregion
28
+ export { r as VizAvatar };
29
+
30
+ //# sourceMappingURL=Avatar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Avatar.js","names":[],"sources":["../../../../../src/crewfoundry/components/viz/lib/Avatar.tsx"],"sourcesContent":["import { cn } from '../../../utils/cn';\nimport { getInitials } from './format';\n\nexport interface VizAvatarProps {\n name: string;\n src?: string;\n /** Pixel diameter. */\n size?: number;\n className?: string;\n /** Extra ring around the avatar (used by overlapping stacks). */\n ring?: boolean;\n}\n\n/**\n * Internal avatar primitive: image when available, initials fallback otherwise.\n * Shared by AvatarStack, OrgTree and Timeline. Not exported from the barrel.\n */\nexport function VizAvatar({ name, src, size = 32, className, ring = false }: VizAvatarProps) {\n return (\n <span\n className={cn(\n 'inline-flex shrink-0 items-center justify-center overflow-hidden rounded-full border border-border-subtle bg-bg-sunken font-medium text-text-secondary',\n ring && 'ring-2 ring-[var(--color-bg-surface)]',\n className,\n )}\n style={{ width: size, height: size, fontSize: Math.round(size * 0.38) }}\n title={name}\n >\n {src ? (\n <img\n src={src}\n alt={name}\n width={size}\n height={size}\n className=\"h-full w-full object-cover\"\n loading=\"lazy\"\n />\n ) : (\n <span aria-label={name}>{getInitials(name)}</span>\n )}\n </span>\n );\n}\n"],"mappings":";;;;AAiBA,SAAgB,EAAU,EAAE,SAAM,QAAK,UAAO,IAAI,cAAW,UAAO,MAAyB;AAC3F,QACE,kBAAC,QAAD;EACE,WAAW,EACT,0JACA,KAAQ,yCACR,EACD;EACD,OAAO;GAAE,OAAO;GAAM,QAAQ;GAAM,UAAU,KAAK,MAAM,IAAO,IAAK;GAAE;EACvE,OAAO;YAEN,IACC,kBAAC,OAAD;GACO;GACL,KAAK;GACL,OAAO;GACP,QAAQ;GACR,WAAU;GACV,SAAQ;GACR,CAAA,GAEF,kBAAC,QAAD;GAAM,cAAY;aAAO,EAAY,EAAK;GAAQ,CAAA;EAE/C,CAAA"}
@@ -0,0 +1,119 @@
1
+ import { CHART_AXIS as e, CHART_GRID as t } from "./palette.js";
2
+ import { cn as n } from "../../../utils/cn.js";
3
+ import { useElementWidth as r } from "./hooks.js";
4
+ import { clamp as i, scaleLinear as a } from "./geometry.js";
5
+ import { useState as o } from "react";
6
+ import { jsx as s, jsxs as c } from "react/jsx-runtime";
7
+ //#region src/crewfoundry/components/viz/lib/CartesianFrame.tsx
8
+ var l = {
9
+ left: 40,
10
+ right: 14,
11
+ top: 14,
12
+ bottom: 26
13
+ }, u = 7;
14
+ function d({ labels: d, domain: f, height: p, title: m, desc: h, formatValue: g, yTicks: _ = 4, padding: v, tooltip: y, className: b, children: x }) {
15
+ let [S, C] = r(), [w, T] = o(null), E = C > 0 ? C : 480, D = {
16
+ ...l,
17
+ ...v
18
+ }, O = d.length, [k, A] = f, j = D.left, M = E - D.right, N = D.top, P = p - D.bottom, F = (e) => O <= 1 ? (j + M) / 2 : j + (M - j) * e / (O - 1), I = (e) => P - a(e, k, A, 0, P - N), L = Array.from({ length: _ + 1 }, (e, t) => k + (A - k) * t / _), R = Math.max(1, Math.ceil(O / u)), z = d.map((e, t) => t).filter((e) => e % R === 0 || e === O - 1), B = (e) => {
19
+ let t = S.current;
20
+ if (!t || O === 0) return;
21
+ let n = e - t.getBoundingClientRect().left, r = 0, i = Infinity;
22
+ for (let e = 0; e < O; e += 1) {
23
+ let t = Math.abs(F(e) - n);
24
+ t < i && (i = t, r = e);
25
+ }
26
+ T(r);
27
+ }, V = (e) => B(e.clientX), H = () => T(null), U = (e) => {
28
+ O !== 0 && (e.key === "ArrowRight" || e.key === "ArrowLeft" ? (e.preventDefault(), T(i((w ?? (e.key === "ArrowRight" ? -1 : O)) + (e.key === "ArrowRight" ? 1 : -1), 0, O - 1))) : e.key === "Home" ? (e.preventDefault(), T(0)) : e.key === "End" ? (e.preventDefault(), T(O - 1)) : e.key === "Escape" && T(null));
29
+ }, W = {
30
+ width: E,
31
+ height: p,
32
+ plotLeft: j,
33
+ plotRight: M,
34
+ plotTop: N,
35
+ plotBottom: P,
36
+ activeIndex: w,
37
+ px: F,
38
+ py: I
39
+ };
40
+ return /* @__PURE__ */ c("div", {
41
+ ref: S,
42
+ className: n("relative w-full rounded-[var(--radius-card)] outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]", b),
43
+ tabIndex: 0,
44
+ role: "img",
45
+ "aria-label": m,
46
+ onMouseMove: V,
47
+ onMouseLeave: H,
48
+ onBlur: H,
49
+ onKeyDown: U,
50
+ children: [/* @__PURE__ */ c("svg", {
51
+ viewBox: `0 0 ${E} ${p}`,
52
+ width: "100%",
53
+ height: p,
54
+ preserveAspectRatio: "none",
55
+ style: {
56
+ display: "block",
57
+ overflow: "visible"
58
+ },
59
+ "aria-hidden": "true",
60
+ focusable: "false",
61
+ children: [
62
+ /* @__PURE__ */ s("title", { children: m }),
63
+ h ? /* @__PURE__ */ s("desc", { children: h }) : null,
64
+ L.map((e, n) => /* @__PURE__ */ s("line", {
65
+ x1: j,
66
+ x2: M,
67
+ y1: I(e),
68
+ y2: I(e),
69
+ stroke: t,
70
+ strokeWidth: 1,
71
+ vectorEffect: "non-scaling-stroke",
72
+ opacity: .6
73
+ }, `grid-${n}`)),
74
+ L.map((t, n) => /* @__PURE__ */ s("text", {
75
+ x: j - 7,
76
+ y: I(t),
77
+ textAnchor: "end",
78
+ dominantBaseline: "central",
79
+ fontSize: 10,
80
+ fill: e,
81
+ children: g(t)
82
+ }, `ylabel-${n}`)),
83
+ z.map((t) => /* @__PURE__ */ s("text", {
84
+ x: F(t),
85
+ y: p - 8,
86
+ textAnchor: "middle",
87
+ fontSize: 10,
88
+ fill: e,
89
+ children: d[t]
90
+ }, `xlabel-${t}`)),
91
+ w === null ? null : /* @__PURE__ */ s("line", {
92
+ x1: F(w),
93
+ x2: F(w),
94
+ y1: N,
95
+ y2: P,
96
+ stroke: e,
97
+ strokeWidth: 1,
98
+ strokeDasharray: "3 3",
99
+ vectorEffect: "non-scaling-stroke",
100
+ opacity: .7
101
+ }),
102
+ x(W)
103
+ ]
104
+ }), w !== null && y ? /* @__PURE__ */ s("div", {
105
+ role: "status",
106
+ "aria-live": "polite",
107
+ className: "pointer-events-none absolute z-10 -translate-x-1/2 rounded-[var(--radius-input)] border border-border-subtle bg-bg-elevated px-2 py-1 text-xs text-text-primary shadow-elevation-2",
108
+ style: {
109
+ left: i(F(w), 48, Math.max(48, E - 48)),
110
+ top: N + 2
111
+ },
112
+ children: y(w)
113
+ }) : null]
114
+ });
115
+ }
116
+ //#endregion
117
+ export { d as CartesianFrame };
118
+
119
+ //# sourceMappingURL=CartesianFrame.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CartesianFrame.js","names":[],"sources":["../../../../../src/crewfoundry/components/viz/lib/CartesianFrame.tsx"],"sourcesContent":["import { type KeyboardEvent, type MouseEvent, type ReactNode, useState } from 'react';\nimport { cn } from '../../../utils/cn';\nimport { CHART_AXIS, CHART_GRID } from './palette';\nimport { clamp, scaleLinear } from './geometry';\nimport { useElementWidth } from './hooks';\n\nexport interface CartesianContext {\n width: number;\n height: number;\n plotLeft: number;\n plotRight: number;\n plotTop: number;\n plotBottom: number;\n activeIndex: number | null;\n /** X pixel for a data index. */\n px: (index: number) => number;\n /** Y pixel for a data value. */\n py: (value: number) => number;\n}\n\ninterface Padding {\n left: number;\n right: number;\n top: number;\n bottom: number;\n}\n\nexport interface CartesianFrameProps {\n labels: string[];\n /** Y domain [low, high]. */\n domain: [number, number];\n height: number;\n title: string;\n desc?: string;\n formatValue: (value: number) => string;\n /** Number of grid intervals (lines = intervals + 1). */\n yTicks?: number;\n padding?: Partial<Padding>;\n tooltip?: (index: number) => ReactNode;\n className?: string;\n children: (ctx: CartesianContext) => ReactNode;\n}\n\nconst DEFAULT_PADDING: Padding = { left: 40, right: 14, top: 14, bottom: 26 };\nconst MAX_X_LABELS = 7;\n\n/**\n * Shared cartesian chart chrome: faint grid, tokenized axes, and pointer +\n * keyboard driven hover with an accessible tooltip. The series marks are\n * supplied via the render-prop `children`, which receives pixel scales.\n */\nexport function CartesianFrame({\n labels,\n domain,\n height,\n title,\n desc,\n formatValue,\n yTicks = 4,\n padding,\n tooltip,\n className,\n children,\n}: CartesianFrameProps) {\n const [ref, measured] = useElementWidth<HTMLDivElement>();\n const [activeIndex, setActiveIndex] = useState<number | null>(null);\n const width = measured > 0 ? measured : 480;\n\n const pad = { ...DEFAULT_PADDING, ...padding };\n const n = labels.length;\n const [lo, hi] = domain;\n const plotLeft = pad.left;\n const plotRight = width - pad.right;\n const plotTop = pad.top;\n const plotBottom = height - pad.bottom;\n\n const px = (index: number): number =>\n n <= 1 ? (plotLeft + plotRight) / 2 : plotLeft + ((plotRight - plotLeft) * index) / (n - 1);\n const py = (value: number): number =>\n plotBottom - scaleLinear(value, lo, hi, 0, plotBottom - plotTop);\n\n const gridValues = Array.from({ length: yTicks + 1 }, (_, k) => lo + ((hi - lo) * k) / yTicks);\n const xStep = Math.max(1, Math.ceil(n / MAX_X_LABELS));\n const xLabelIndexes = labels\n .map((_, i) => i)\n .filter((i) => i % xStep === 0 || i === n - 1);\n\n const setNearest = (clientX: number): void => {\n const el = ref.current;\n if (!el || n === 0) {\n return;\n }\n const rect = el.getBoundingClientRect();\n const x = clientX - rect.left;\n let best = 0;\n let bestDistance = Infinity;\n for (let i = 0; i < n; i += 1) {\n const distance = Math.abs(px(i) - x);\n if (distance < bestDistance) {\n bestDistance = distance;\n best = i;\n }\n }\n setActiveIndex(best);\n };\n\n const handleMove = (event: MouseEvent<HTMLDivElement>): void => setNearest(event.clientX);\n const handleLeave = (): void => setActiveIndex(null);\n const handleKey = (event: KeyboardEvent<HTMLDivElement>): void => {\n if (n === 0) {\n return;\n }\n if (event.key === 'ArrowRight' || event.key === 'ArrowLeft') {\n event.preventDefault();\n const base = activeIndex ?? (event.key === 'ArrowRight' ? -1 : n);\n setActiveIndex(clamp(base + (event.key === 'ArrowRight' ? 1 : -1), 0, n - 1));\n } else if (event.key === 'Home') {\n event.preventDefault();\n setActiveIndex(0);\n } else if (event.key === 'End') {\n event.preventDefault();\n setActiveIndex(n - 1);\n } else if (event.key === 'Escape') {\n setActiveIndex(null);\n }\n };\n\n const ctx: CartesianContext = {\n width,\n height,\n plotLeft,\n plotRight,\n plotTop,\n plotBottom,\n activeIndex,\n px,\n py,\n };\n\n return (\n <div\n ref={ref}\n className={cn(\n 'relative w-full rounded-[var(--radius-card)] outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]',\n className,\n )}\n tabIndex={0}\n role=\"img\"\n aria-label={title}\n onMouseMove={handleMove}\n onMouseLeave={handleLeave}\n onBlur={handleLeave}\n onKeyDown={handleKey}\n >\n <svg\n viewBox={`0 0 ${width} ${height}`}\n width=\"100%\"\n height={height}\n preserveAspectRatio=\"none\"\n style={{ display: 'block', overflow: 'visible' }}\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n <title>{title}</title>\n {desc ? <desc>{desc}</desc> : null}\n {gridValues.map((value, k) => (\n <line\n key={`grid-${k}`}\n x1={plotLeft}\n x2={plotRight}\n y1={py(value)}\n y2={py(value)}\n stroke={CHART_GRID}\n strokeWidth={1}\n vectorEffect=\"non-scaling-stroke\"\n opacity={0.6}\n />\n ))}\n {gridValues.map((value, k) => (\n <text\n key={`ylabel-${k}`}\n x={plotLeft - 7}\n y={py(value)}\n textAnchor=\"end\"\n dominantBaseline=\"central\"\n fontSize={10}\n fill={CHART_AXIS}\n >\n {formatValue(value)}\n </text>\n ))}\n {xLabelIndexes.map((i) => (\n <text\n key={`xlabel-${i}`}\n x={px(i)}\n y={height - 8}\n textAnchor=\"middle\"\n fontSize={10}\n fill={CHART_AXIS}\n >\n {labels[i]}\n </text>\n ))}\n {activeIndex !== null ? (\n <line\n x1={px(activeIndex)}\n x2={px(activeIndex)}\n y1={plotTop}\n y2={plotBottom}\n stroke={CHART_AXIS}\n strokeWidth={1}\n strokeDasharray=\"3 3\"\n vectorEffect=\"non-scaling-stroke\"\n opacity={0.7}\n />\n ) : null}\n {children(ctx)}\n </svg>\n {activeIndex !== null && tooltip ? (\n <div\n role=\"status\"\n aria-live=\"polite\"\n className=\"pointer-events-none absolute z-10 -translate-x-1/2 rounded-[var(--radius-input)] border border-border-subtle bg-bg-elevated px-2 py-1 text-xs text-text-primary shadow-elevation-2\"\n style={{\n left: clamp(px(activeIndex), 48, Math.max(48, width - 48)),\n top: plotTop + 2,\n }}\n >\n {tooltip(activeIndex)}\n </div>\n ) : null}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ import { cn as e } from "../../../utils/cn.js";
2
+ import { useElementWidth as t } from "./hooks.js";
3
+ import { jsx as n, jsxs as r } from "react/jsx-runtime";
4
+ //#region src/crewfoundry/components/viz/lib/ResponsiveSvg.tsx
5
+ function i({ height: i, title: a, desc: o, className: s, minWidth: c = 320, overflowVisible: l = !0, children: u }) {
6
+ let [d, f] = t(), p = f > 0 ? f : c;
7
+ return /* @__PURE__ */ n("div", {
8
+ ref: d,
9
+ className: e("w-full", s),
10
+ children: /* @__PURE__ */ r("svg", {
11
+ viewBox: `0 0 ${p} ${i}`,
12
+ width: "100%",
13
+ height: i,
14
+ preserveAspectRatio: "none",
15
+ role: "img",
16
+ "aria-label": a,
17
+ style: {
18
+ display: "block",
19
+ overflow: l ? "visible" : "hidden"
20
+ },
21
+ children: [
22
+ /* @__PURE__ */ n("title", { children: a }),
23
+ o ? /* @__PURE__ */ n("desc", { children: o }) : null,
24
+ u({
25
+ width: p,
26
+ height: i
27
+ })
28
+ ]
29
+ })
30
+ });
31
+ }
32
+ //#endregion
33
+ export { i as ResponsiveSvg };
34
+
35
+ //# sourceMappingURL=ResponsiveSvg.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ResponsiveSvg.js","names":[],"sources":["../../../../../src/crewfoundry/components/viz/lib/ResponsiveSvg.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { cn } from '../../../utils/cn';\nimport { useElementWidth } from './hooks';\n\nexport interface ResponsiveSvgDims {\n width: number;\n height: number;\n}\n\nexport interface ResponsiveSvgProps {\n /** Fixed pixel height; width is measured from the container. */\n height: number;\n /** Accessible name announced for the chart. */\n title: string;\n /** Optional longer description linked via <desc>. */\n desc?: string;\n className?: string;\n /** Width assumed before the container has been measured. */\n minWidth?: number;\n /** Allow marks (endpoint halos, labels) to paint outside the viewBox. */\n overflowVisible?: boolean;\n children: (dims: ResponsiveSvgDims) => ReactNode;\n}\n\n/**\n * A width-measuring SVG frame. Because the viewBox matches the rendered pixel\n * box 1:1, circles stay circular and strokes stay crisp at any width, while the\n * chart still fills its container and reflows on resize.\n */\nexport function ResponsiveSvg({\n height,\n title,\n desc,\n className,\n minWidth = 320,\n overflowVisible = true,\n children,\n}: ResponsiveSvgProps) {\n const [ref, measured] = useElementWidth<HTMLDivElement>();\n const width = measured > 0 ? measured : minWidth;\n return (\n <div ref={ref} className={cn('w-full', className)}>\n <svg\n viewBox={`0 0 ${width} ${height}`}\n width=\"100%\"\n height={height}\n preserveAspectRatio=\"none\"\n role=\"img\"\n aria-label={title}\n style={{ display: 'block', overflow: overflowVisible ? 'visible' : 'hidden' }}\n >\n <title>{title}</title>\n {desc ? <desc>{desc}</desc> : null}\n {children({ width, height })}\n </svg>\n </div>\n );\n}\n"],"mappings":";;;;AA6BA,SAAgB,EAAc,EAC5B,WACA,UACA,SACA,cACA,cAAW,KACX,qBAAkB,IAClB,eACqB;CACrB,IAAM,CAAC,GAAK,KAAY,GAAiC,EACnD,IAAQ,IAAW,IAAI,IAAW;AACxC,QACE,kBAAC,OAAD;EAAU;EAAK,WAAW,EAAG,UAAU,EAAU;YAC/C,kBAAC,OAAD;GACE,SAAS,OAAO,EAAM,GAAG;GACzB,OAAM;GACE;GACR,qBAAoB;GACpB,MAAK;GACL,cAAY;GACZ,OAAO;IAAE,SAAS;IAAS,UAAU,IAAkB,YAAY;IAAU;aAP/E;IASE,kBAAC,SAAD,EAAA,UAAQ,GAAc,CAAA;IACrB,IAAO,kBAAC,QAAD,EAAA,UAAO,GAAY,CAAA,GAAG;IAC7B,EAAS;KAAE;KAAO;KAAQ,CAAC;IACxB;;EACF,CAAA"}
@@ -0,0 +1,21 @@
1
+ //#region src/crewfoundry/components/viz/lib/format.ts
2
+ function e(e) {
3
+ return (Math.round(e * 10) / 10).toString();
4
+ }
5
+ function t(t) {
6
+ let n = Math.abs(t);
7
+ return n >= 1e9 ? `${e(t / 1e9)}B` : n >= 1e6 ? `${e(t / 1e6)}M` : n >= 1e3 ? `${e(t / 1e3)}k` : String(t);
8
+ }
9
+ function n(e, t, n = 0) {
10
+ if (t === 0) return 0;
11
+ let r = 10 ** n;
12
+ return Math.round(e / t * 100 * r) / r;
13
+ }
14
+ function r(e) {
15
+ let t = e.trim().split(/\s+/).filter(Boolean);
16
+ return t.length === 0 ? "?" : t.length === 1 ? t[0].slice(0, 2).toUpperCase() : `${t[0][0]}${t[t.length - 1][0]}`.toUpperCase();
17
+ }
18
+ //#endregion
19
+ export { t as formatCompact, r as getInitials, n as toPercent };
20
+
21
+ //# sourceMappingURL=format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.js","names":[],"sources":["../../../../../src/crewfoundry/components/viz/lib/format.ts"],"sourcesContent":["/**\n * Pure formatting helpers for the visualization kit. No React / DOM.\n */\n\nfunction trimDecimal(n: number): string {\n return (Math.round(n * 10) / 10).toString();\n}\n\n/** Compact number formatting: 1200 -> \"1.2k\", 3_400_000 -> \"3.4M\". */\nexport function formatCompact(value: number): string {\n const abs = Math.abs(value);\n if (abs >= 1_000_000_000) {\n return `${trimDecimal(value / 1_000_000_000)}B`;\n }\n if (abs >= 1_000_000) {\n return `${trimDecimal(value / 1_000_000)}M`;\n }\n if (abs >= 1_000) {\n return `${trimDecimal(value / 1_000)}k`;\n }\n return String(value);\n}\n\n/** Value as a percentage of a total, rounded to `digits` decimals (0 when total is 0). */\nexport function toPercent(value: number, total: number, digits = 0): number {\n if (total === 0) {\n return 0;\n }\n const factor = 10 ** digits;\n return Math.round((value / total) * 100 * factor) / factor;\n}\n\n/** Up-to-two-letter initials from a display name. */\nexport function getInitials(name: string): string {\n const parts = name.trim().split(/\\s+/).filter(Boolean);\n if (parts.length === 0) {\n return '?';\n }\n if (parts.length === 1) {\n return parts[0].slice(0, 2).toUpperCase();\n }\n return `${parts[0][0]}${parts[parts.length - 1][0]}`.toUpperCase();\n}\n"],"mappings":";AAIA,SAAS,EAAY,GAAmB;AACtC,SAAQ,KAAK,MAAM,IAAI,GAAG,GAAG,IAAI,UAAU;;AAI7C,SAAgB,EAAc,GAAuB;CACnD,IAAM,IAAM,KAAK,IAAI,EAAM;AAU3B,QATI,KAAO,MACF,GAAG,EAAY,IAAQ,IAAc,CAAC,KAE3C,KAAO,MACF,GAAG,EAAY,IAAQ,IAAU,CAAC,KAEvC,KAAO,MACF,GAAG,EAAY,IAAQ,IAAM,CAAC,KAEhC,OAAO,EAAM;;AAItB,SAAgB,EAAU,GAAe,GAAe,IAAS,GAAW;AAC1E,KAAI,MAAU,EACZ,QAAO;CAET,IAAM,IAAS,MAAM;AACrB,QAAO,KAAK,MAAO,IAAQ,IAAS,MAAM,EAAO,GAAG;;AAItD,SAAgB,EAAY,GAAsB;CAChD,IAAM,IAAQ,EAAK,MAAM,CAAC,MAAM,MAAM,CAAC,OAAO,QAAQ;AAOtD,QANI,EAAM,WAAW,IACZ,MAEL,EAAM,WAAW,IACZ,EAAM,GAAG,MAAM,GAAG,EAAE,CAAC,aAAa,GAEpC,GAAG,EAAM,GAAG,KAAK,EAAM,EAAM,SAAS,GAAG,KAAK,aAAa"}
@@ -0,0 +1,69 @@
1
+ //#region src/crewfoundry/components/viz/lib/geometry.ts
2
+ function e(e, t, n) {
3
+ return Math.min(n, Math.max(t, e));
4
+ }
5
+ function t(e) {
6
+ if (e.length === 0) return [0, 0];
7
+ let t = e[0], n = e[0];
8
+ for (let r of e) r < t && (t = r), r > n && (n = r);
9
+ return [t, n];
10
+ }
11
+ function n(e) {
12
+ let [n, r] = t(e);
13
+ return [Math.min(0, n), Math.max(0, r)];
14
+ }
15
+ function r(e, t, n, r, i) {
16
+ return n === t ? (r + i) / 2 : r + (e - t) / (n - t) * (i - r);
17
+ }
18
+ function i(e) {
19
+ return Math.round(e * 100) / 100;
20
+ }
21
+ function a(e, t, a, o = 0, s) {
22
+ let c = e.length;
23
+ if (c === 0) return [];
24
+ let [l, u] = s ?? n(e), d = Math.max(0, t - o * 2), f = Math.max(0, a - o * 2);
25
+ return e.map((e, t) => {
26
+ let n = o + (c === 1 ? d / 2 : d * t / (c - 1)), a = o + f - r(e, l, u, 0, f);
27
+ return {
28
+ x: i(n),
29
+ y: i(a)
30
+ };
31
+ });
32
+ }
33
+ function o(e) {
34
+ return e.length === 0 ? "" : e.map((e, t) => `${t === 0 ? "M" : "L"}${i(e.x)},${i(e.y)}`).join(" ");
35
+ }
36
+ function s(e, t) {
37
+ if (e.length === 0) return "";
38
+ let n = o(e), r = e[0], a = e[e.length - 1];
39
+ return `${n} L${i(a.x)},${i(t)} L${i(r.x)},${i(t)} Z`;
40
+ }
41
+ function c(e, t, n, r, i = 5) {
42
+ let a = i * 2, o = [];
43
+ for (let i = 0; i < a; i += 1) {
44
+ let s = i % 2 == 0 ? n : r;
45
+ o.push(l(e, t, s, i * 360 / a));
46
+ }
47
+ return `${o.map((e, t) => `${t === 0 ? "M" : "L"}${e.x},${e.y}`).join(" ")} Z`;
48
+ }
49
+ function l(e, t, n, r) {
50
+ let a = (r - 90) * Math.PI / 180;
51
+ return {
52
+ x: i(e + n * Math.cos(a)),
53
+ y: i(t + n * Math.sin(a))
54
+ };
55
+ }
56
+ function u(e, t, n, r, i, a) {
57
+ let o = l(e, t, n, a), s = l(e, t, n, i), c = l(e, t, r, a), u = l(e, t, r, i), d = +(a - i > 180);
58
+ return [
59
+ `M${o.x},${o.y}`,
60
+ `A${n},${n} 0 ${d} 0 ${s.x},${s.y}`,
61
+ `L${u.x},${u.y}`,
62
+ `A${r},${r} 0 ${d} 1 ${c.x},${c.y}`,
63
+ "Z"
64
+ ].join(" ");
65
+ }
66
+ //#endregion
67
+ export { s as buildAreaPath, o as buildLinePath, e as clamp, u as donutSegmentPath, t as extent, n as extentWithZero, a as pointsFromValues, l as polarToCartesian, r as scaleLinear, c as starPath };
68
+
69
+ //# sourceMappingURL=geometry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"geometry.js","names":[],"sources":["../../../../../src/crewfoundry/components/viz/lib/geometry.ts"],"sourcesContent":["/**\n * Pure geometry / scaling helpers for the CrewFoundry visualization kit.\n * No React, no DOM, no side effects — safe to unit test in isolation.\n */\n\nexport interface Point {\n x: number;\n y: number;\n}\n\n/** Clamp a value into an inclusive range. */\nexport function clamp(value: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, value));\n}\n\n/** Sum of a numeric list (0 for empty). */\nexport function sum(values: readonly number[]): number {\n return values.reduce((acc, v) => acc + v, 0);\n}\n\n/** Min/max of a list. Returns [0, 0] for an empty list. */\nexport function extent(values: readonly number[]): [number, number] {\n if (values.length === 0) {\n return [0, 0];\n }\n let min = values[0];\n let max = values[0];\n for (const v of values) {\n if (v < min) {\n min = v;\n }\n if (v > max) {\n max = v;\n }\n }\n return [min, max];\n}\n\n/** Extent that always includes a zero baseline, so bars/areas anchor sensibly. */\nexport function extentWithZero(values: readonly number[]): [number, number] {\n const [min, max] = extent(values);\n return [Math.min(0, min), Math.max(0, max)];\n}\n\n/**\n * Linear map of `value` from a domain onto a range. Degenerate domains\n * (min === max) map to the range midpoint instead of dividing by zero.\n */\nexport function scaleLinear(\n value: number,\n domainMin: number,\n domainMax: number,\n rangeMin: number,\n rangeMax: number,\n): number {\n if (domainMax === domainMin) {\n return (rangeMin + rangeMax) / 2;\n }\n const t = (value - domainMin) / (domainMax - domainMin);\n return rangeMin + t * (rangeMax - rangeMin);\n}\n\n/** Round to 2 decimals — keeps generated path strings compact. */\nfunction round(n: number): number {\n return Math.round(n * 100) / 100;\n}\n\n/**\n * Evenly space a list of y-values across a box and invert Y (SVG origin is\n * top-left, so larger values render higher).\n */\nexport function pointsFromValues(\n values: readonly number[],\n width: number,\n height: number,\n padding = 0,\n domain?: [number, number],\n): Point[] {\n const n = values.length;\n if (n === 0) {\n return [];\n }\n const [dMin, dMax] = domain ?? extentWithZero(values);\n const innerW = Math.max(0, width - padding * 2);\n const innerH = Math.max(0, height - padding * 2);\n return values.map((v, i) => {\n const x = padding + (n === 1 ? innerW / 2 : (innerW * i) / (n - 1));\n const y = padding + innerH - scaleLinear(v, dMin, dMax, 0, innerH);\n return { x: round(x), y: round(y) };\n });\n}\n\n/** Build an SVG polyline `d` from points. */\nexport function buildLinePath(points: readonly Point[]): string {\n if (points.length === 0) {\n return '';\n }\n return points.map((p, i) => `${i === 0 ? 'M' : 'L'}${round(p.x)},${round(p.y)}`).join(' ');\n}\n\n/** Build a closed area `d` (line dropped to a baseline and closed). */\nexport function buildAreaPath(points: readonly Point[], baselineY: number): string {\n if (points.length === 0) {\n return '';\n }\n const line = buildLinePath(points);\n const first = points[0];\n const last = points[points.length - 1];\n return `${line} L${round(last.x)},${round(baselineY)} L${round(first.x)},${round(baselineY)} Z`;\n}\n\n/** SVG path for an n-pointed star centered at (cx, cy). */\nexport function starPath(\n cx: number,\n cy: number,\n outerRadius: number,\n innerRadius: number,\n points = 5,\n): string {\n const total = points * 2;\n const coords: Point[] = [];\n for (let i = 0; i < total; i += 1) {\n const radius = i % 2 === 0 ? outerRadius : innerRadius;\n coords.push(polarToCartesian(cx, cy, radius, (i * 360) / total));\n }\n return `${coords.map((p, i) => `${i === 0 ? 'M' : 'L'}${p.x},${p.y}`).join(' ')} Z`;\n}\n\n/** Convert a polar coordinate (degrees, clockwise from 12 o'clock) to cartesian. */\nexport function polarToCartesian(\n cx: number,\n cy: number,\n radius: number,\n angleDeg: number,\n): Point {\n const a = ((angleDeg - 90) * Math.PI) / 180;\n return { x: round(cx + radius * Math.cos(a)), y: round(cy + radius * Math.sin(a)) };\n}\n\n/**\n * Annular (donut) segment path between two angles, clockwise from 12 o'clock.\n * For a full 360° sweep the caller should draw a ring instead (this path\n * degenerates when start and end coincide).\n */\nexport function donutSegmentPath(\n cx: number,\n cy: number,\n rOuter: number,\n rInner: number,\n startAngle: number,\n endAngle: number,\n): string {\n const startOuter = polarToCartesian(cx, cy, rOuter, endAngle);\n const endOuter = polarToCartesian(cx, cy, rOuter, startAngle);\n const startInner = polarToCartesian(cx, cy, rInner, endAngle);\n const endInner = polarToCartesian(cx, cy, rInner, startAngle);\n const largeArc = endAngle - startAngle > 180 ? 1 : 0;\n return [\n `M${startOuter.x},${startOuter.y}`,\n `A${rOuter},${rOuter} 0 ${largeArc} 0 ${endOuter.x},${endOuter.y}`,\n `L${endInner.x},${endInner.y}`,\n `A${rInner},${rInner} 0 ${largeArc} 1 ${startInner.x},${startInner.y}`,\n 'Z',\n ].join(' ');\n}\n"],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import { useEffect as e, useLayoutEffect as t, useRef as n, useState as r } from "react";
2
+ //#region src/crewfoundry/components/viz/lib/hooks.ts
3
+ var i = typeof window < "u", a = i ? t : e;
4
+ function o() {
5
+ let [t, n] = r(!1);
6
+ return e(() => {
7
+ if (!i || typeof window.matchMedia != "function") return;
8
+ let e = window.matchMedia("(prefers-reduced-motion: reduce)"), t = () => n(e.matches);
9
+ return t(), e.addEventListener("change", t), () => e.removeEventListener("change", t);
10
+ }, []), t;
11
+ }
12
+ function s() {
13
+ let [t, n] = r(!1);
14
+ return e(() => {
15
+ let e = requestAnimationFrame(() => n(!0));
16
+ return () => cancelAnimationFrame(e);
17
+ }, []), t;
18
+ }
19
+ var c = 0;
20
+ function l(e = "viz") {
21
+ let [t] = r(() => (c += 1, `${e}-${c}`));
22
+ return t;
23
+ }
24
+ function u() {
25
+ let e = n(null), [t, i] = r(0);
26
+ return a(() => {
27
+ let t = e.current;
28
+ if (!t || (i(t.clientWidth), typeof ResizeObserver > "u")) return;
29
+ let n = new ResizeObserver((e) => {
30
+ let t = e[0]?.contentRect.width;
31
+ typeof t == "number" && i(t);
32
+ });
33
+ return n.observe(t), () => n.disconnect();
34
+ }, []), [e, t];
35
+ }
36
+ //#endregion
37
+ export { u as useElementWidth, s as useMounted, o as usePrefersReducedMotion, l as useVizId };
38
+
39
+ //# sourceMappingURL=hooks.js.map