@burdenoff/microfe-crewfoundry 2026.704.5 → 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.
- package/dist/crewfoundry/CrewFoundryRoutes.js +51 -43
- package/dist/crewfoundry/CrewFoundryRoutes.js.map +1 -1
- package/dist/crewfoundry/components/forms/formValues.js +5 -2
- package/dist/crewfoundry/components/forms/formValues.js.map +1 -1
- package/dist/crewfoundry/components/viz/AllocationBar.js +68 -0
- package/dist/crewfoundry/components/viz/AllocationBar.js.map +1 -0
- package/dist/crewfoundry/components/viz/AreaChart.js +102 -0
- package/dist/crewfoundry/components/viz/AreaChart.js.map +1 -0
- package/dist/crewfoundry/components/viz/AvatarStack.js +36 -0
- package/dist/crewfoundry/components/viz/AvatarStack.js.map +1 -0
- package/dist/crewfoundry/components/viz/BarChart.js +83 -0
- package/dist/crewfoundry/components/viz/BarChart.js.map +1 -0
- package/dist/crewfoundry/components/viz/CertificateCard.js +118 -0
- package/dist/crewfoundry/components/viz/CertificateCard.js.map +1 -0
- package/dist/crewfoundry/components/viz/DonutChart.js +109 -0
- package/dist/crewfoundry/components/viz/DonutChart.js.map +1 -0
- package/dist/crewfoundry/components/viz/HBarFunnel.js +54 -0
- package/dist/crewfoundry/components/viz/HBarFunnel.js.map +1 -0
- package/dist/crewfoundry/components/viz/Heatmap.js +91 -0
- package/dist/crewfoundry/components/viz/Heatmap.js.map +1 -0
- package/dist/crewfoundry/components/viz/KanbanBoard.js +50 -0
- package/dist/crewfoundry/components/viz/KanbanBoard.js.map +1 -0
- package/dist/crewfoundry/components/viz/LineChart.js +123 -0
- package/dist/crewfoundry/components/viz/LineChart.js.map +1 -0
- package/dist/crewfoundry/components/viz/OrgTree.js +83 -0
- package/dist/crewfoundry/components/viz/OrgTree.js.map +1 -0
- package/dist/crewfoundry/components/viz/PathSteps.js +68 -0
- package/dist/crewfoundry/components/viz/PathSteps.js.map +1 -0
- package/dist/crewfoundry/components/viz/ProgressRing.js +68 -0
- package/dist/crewfoundry/components/viz/ProgressRing.js.map +1 -0
- package/dist/crewfoundry/components/viz/RatingStars.js +51 -0
- package/dist/crewfoundry/components/viz/RatingStars.js.map +1 -0
- package/dist/crewfoundry/components/viz/SegmentedTabs.js +69 -0
- package/dist/crewfoundry/components/viz/SegmentedTabs.js.map +1 -0
- package/dist/crewfoundry/components/viz/SkillBar.js +3 -0
- package/dist/crewfoundry/components/viz/Sparkline.js +74 -0
- package/dist/crewfoundry/components/viz/Sparkline.js.map +1 -0
- package/dist/crewfoundry/components/viz/StatCard.js +60 -0
- package/dist/crewfoundry/components/viz/StatCard.js.map +1 -0
- package/dist/crewfoundry/components/viz/StepProgress.js +3 -0
- package/dist/crewfoundry/components/viz/Timeline.js +66 -0
- package/dist/crewfoundry/components/viz/Timeline.js.map +1 -0
- package/dist/crewfoundry/components/viz/index.js +23 -0
- package/dist/crewfoundry/components/viz/lib/Avatar.js +30 -0
- package/dist/crewfoundry/components/viz/lib/Avatar.js.map +1 -0
- package/dist/crewfoundry/components/viz/lib/CartesianFrame.js +119 -0
- package/dist/crewfoundry/components/viz/lib/CartesianFrame.js.map +1 -0
- package/dist/crewfoundry/components/viz/lib/ResponsiveSvg.js +35 -0
- package/dist/crewfoundry/components/viz/lib/ResponsiveSvg.js.map +1 -0
- package/dist/crewfoundry/components/viz/lib/format.js +21 -0
- package/dist/crewfoundry/components/viz/lib/format.js.map +1 -0
- package/dist/crewfoundry/components/viz/lib/geometry.js +69 -0
- package/dist/crewfoundry/components/viz/lib/geometry.js.map +1 -0
- package/dist/crewfoundry/components/viz/lib/hooks.js +39 -0
- package/dist/crewfoundry/components/viz/lib/hooks.js.map +1 -0
- package/dist/crewfoundry/components/viz/lib/palette.js +27 -0
- package/dist/crewfoundry/components/viz/lib/palette.js.map +1 -0
- package/dist/crewfoundry/lib/analytics.js +323 -0
- package/dist/crewfoundry/lib/analytics.js.map +1 -0
- package/dist/crewfoundry/lib/companyFinance.js +16 -0
- package/dist/crewfoundry/lib/companyFinance.js.map +1 -0
- package/dist/crewfoundry/pages/AnalyticsAnalyticsPagePage.js +226 -283
- package/dist/crewfoundry/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/AnalyticsEmployeeAnalyticsPagePage.js +107 -120
- package/dist/crewfoundry/pages/AnalyticsEmployeeAnalyticsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/AnalyticsFinancialAnalyticsPagePage.js +122 -162
- package/dist/crewfoundry/pages/AnalyticsFinancialAnalyticsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/AnalyticsHiringAnalyticsPagePage.js +121 -158
- package/dist/crewfoundry/pages/AnalyticsHiringAnalyticsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CommunicationsAnnouncementsPagePage.js +316 -211
- package/dist/crewfoundry/pages/CommunicationsAnnouncementsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CommunicationsMessagesPagePage.js +75 -27
- package/dist/crewfoundry/pages/CommunicationsMessagesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CompanyClientManagementPagePage.js +167 -165
- package/dist/crewfoundry/pages/CompanyClientManagementPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CompanyVendorManagementPagePage.js +168 -109
- package/dist/crewfoundry/pages/CompanyVendorManagementPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/DashboardDashboardPagePage.js +401 -290
- package/dist/crewfoundry/pages/DashboardDashboardPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HiringApplicationsPagePage.js +84 -75
- package/dist/crewfoundry/pages/HiringApplicationsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HiringCandidatesPagePage.js +368 -174
- package/dist/crewfoundry/pages/HiringCandidatesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HiringCreateJobPagePage.js +236 -201
- package/dist/crewfoundry/pages/HiringCreateJobPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HiringInterviewsPagePage.js +369 -196
- package/dist/crewfoundry/pages/HiringInterviewsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HiringJobDetailsPagePage.js +252 -249
- package/dist/crewfoundry/pages/HiringJobDetailsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HiringJobsPagePage.js +210 -110
- package/dist/crewfoundry/pages/HiringJobsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HrDepartmentsPagePage.js +216 -178
- package/dist/crewfoundry/pages/HrDepartmentsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HrEmployeeProfilePagePage.js +385 -0
- package/dist/crewfoundry/pages/HrEmployeeProfilePagePage.js.map +1 -0
- package/dist/crewfoundry/pages/HrEmployeesPagePage.js +189 -130
- package/dist/crewfoundry/pages/HrEmployeesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HrLeaveManagementPagePage.js +397 -241
- package/dist/crewfoundry/pages/HrLeaveManagementPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HrPayrollPagePage.js +262 -147
- package/dist/crewfoundry/pages/HrPayrollPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HrTimeTrackingPagePage.js +345 -167
- package/dist/crewfoundry/pages/HrTimeTrackingPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningCertificatesPagePage.js +98 -24
- package/dist/crewfoundry/pages/LearningCertificatesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningCourseDetailsPagePage.js +35 -31
- package/dist/crewfoundry/pages/LearningCourseDetailsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningCourseLearningPagePage.js +289 -108
- package/dist/crewfoundry/pages/LearningCourseLearningPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningCoursesPagePage.js +221 -114
- package/dist/crewfoundry/pages/LearningCoursesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningLearningPathDetailPagePage.js +136 -133
- package/dist/crewfoundry/pages/LearningLearningPathDetailPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningLearningPathsPagePage.js +148 -97
- package/dist/crewfoundry/pages/LearningLearningPathsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningMyLearningPagePage.js +264 -157
- package/dist/crewfoundry/pages/LearningMyLearningPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePageEnhancedPage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePagePage.js +85 -93
- package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceMarketplacePagePage.js +211 -122
- package/dist/crewfoundry/pages/MarketplaceMarketplacePagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceMarketplaceServicesPagePage.js +94 -54
- package/dist/crewfoundry/pages/MarketplaceMarketplaceServicesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceProjectDetailsPagePage.js +28 -25
- package/dist/crewfoundry/pages/MarketplaceProjectDetailsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceServiceDetailsPagePage.js +290 -138
- package/dist/crewfoundry/pages/MarketplaceServiceDetailsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/PaymentsInvoicesPagePage.js +169 -79
- package/dist/crewfoundry/pages/PaymentsInvoicesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/PaymentsPaymentDashboardPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/ProjectsProjectDetailsPagePage.js +65 -61
- package/dist/crewfoundry/pages/ProjectsProjectDetailsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/ProjectsProjectsPagePage.js +296 -125
- package/dist/crewfoundry/pages/ProjectsProjectsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/ProjectsResourcingPagePage.js +187 -0
- package/dist/crewfoundry/pages/ProjectsResourcingPagePage.js.map +1 -0
- package/dist/crewfoundry/pages/TeamsTeamDetailPagePage.js +142 -76
- package/dist/crewfoundry/pages/TeamsTeamDetailPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/TeamsTeamDirectoryPagePage.js +93 -43
- package/dist/crewfoundry/pages/TeamsTeamDirectoryPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/TeamsTeamSettingsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/TeamsTeamsPagePage.js +250 -98
- package/dist/crewfoundry/pages/TeamsTeamsPagePage.js.map +1 -1
- package/dist/crewfoundry/store/crewfoundryStore.js +1 -1
- package/dist/crewfoundry/store/crewfoundryStore.js.map +1 -1
- package/dist/crewfoundry/utils/cn.js +10 -0
- package/dist/crewfoundry/utils/cn.js.map +1 -0
- package/dist/crewfoundry/utils/courseProgress.js +46 -0
- package/dist/crewfoundry/utils/courseProgress.js.map +1 -0
- package/dist/node_modules/clsx/dist/clsx.js +18 -0
- package/dist/node_modules/clsx/dist/clsx.js.map +1 -0
- package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js +1716 -0
- package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AreaChart.js","names":[],"sources":["../../../../src/crewfoundry/components/viz/AreaChart.tsx"],"sourcesContent":["import { ACCENT } from './lib/palette';\nimport { CartesianFrame } from './lib/CartesianFrame';\nimport { buildAreaPath, buildLinePath } from './lib/geometry';\nimport { formatCompact } from './lib/format';\nimport { useMounted, usePrefersReducedMotion, useVizId } from './lib/hooks';\nimport type { LabeledValue } from './lib/types';\n\nexport interface AreaChartProps {\n series: LabeledValue[];\n /** Pixel height of the plot. */\n height?: number;\n /** Formats axis + tooltip values. Defaults to compact notation. */\n valueFormatter?: (value: number) => string;\n /** Accessible title; defaults to a generic description. */\n title?: string;\n className?: string;\n}\n\n/**\n * Gradient-fill area chart with x/y axes, faint grid and a pointer/keyboard\n * hover readout. Draws itself in on mount unless reduced-motion is requested.\n */\nexport function AreaChart({\n series,\n height = 200,\n valueFormatter = formatCompact,\n title,\n className,\n}: AreaChartProps) {\n const gradientId = useVizId('area-grad');\n const mounted = useMounted();\n const reduced = usePrefersReducedMotion();\n\n if (series.length === 0) {\n return null;\n }\n\n const values = series.map((s) => s.value);\n const labels = series.map((s) => s.label);\n const min = Math.min(...values);\n const max = Math.max(...values);\n const span = max - min || Math.abs(max) || 1;\n const domain: [number, number] = [min - span * 0.12, max + span * 0.12];\n const chartTitle = title ?? `Area chart of ${series.length} points`;\n\n return (\n <CartesianFrame\n labels={labels}\n domain={domain}\n height={height}\n title={chartTitle}\n formatValue={valueFormatter}\n className={className}\n tooltip={(i) => (\n <span>\n <span className=\"text-text-secondary\">{labels[i]}: </span>\n <span className=\"font-semibold tabular-nums\">{valueFormatter(values[i])}</span>\n </span>\n )}\n >\n {({ px, py, plotBottom, activeIndex }) => {\n const points = values.map((v, i) => ({ x: px(i), y: py(v) }));\n const line = buildLinePath(points);\n const area = buildAreaPath(points, plotBottom);\n const last = points[points.length - 1];\n return (\n <g style={{ color: ACCENT }}>\n <defs>\n <linearGradient id={gradientId} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"0%\" stopColor=\"currentColor\" stopOpacity={0.34} />\n <stop offset=\"100%\" stopColor=\"currentColor\" stopOpacity={0.02} />\n </linearGradient>\n </defs>\n <path\n d={area}\n fill={`url(#${gradientId})`}\n stroke=\"none\"\n style={{\n opacity: mounted || reduced ? 1 : 0,\n transition: reduced ? undefined : 'opacity 700ms ease',\n }}\n />\n <path\n d={line}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n vectorEffect=\"non-scaling-stroke\"\n pathLength={1}\n style={{\n strokeDasharray: 1,\n strokeDashoffset: mounted || reduced ? 0 : 1,\n transition: reduced ? undefined : 'stroke-dashoffset 900ms ease',\n }}\n />\n <circle cx={last.x} cy={last.y} r={3.5} fill=\"currentColor\" />\n {activeIndex !== null ? (\n <g>\n <circle\n cx={px(activeIndex)}\n cy={py(values[activeIndex])}\n r={7}\n fill=\"currentColor\"\n opacity={0.16}\n />\n <circle\n cx={px(activeIndex)}\n cy={py(values[activeIndex])}\n r={3.5}\n fill=\"currentColor\"\n stroke=\"var(--color-bg-surface)\"\n strokeWidth={1.5}\n />\n </g>\n ) : null}\n </g>\n );\n }}\n </CartesianFrame>\n );\n}\n"],"mappings":";;;;;;;AAsBA,SAAgB,EAAU,EACxB,WACA,YAAS,KACT,oBAAiB,GACjB,UACA,gBACiB;CACjB,IAAM,IAAa,EAAS,YAAY,EAClC,IAAU,GAAY,EACtB,IAAU,GAAyB;AAEzC,KAAI,EAAO,WAAW,EACpB,QAAO;CAGT,IAAM,IAAS,EAAO,KAAK,MAAM,EAAE,MAAM,EACnC,IAAS,EAAO,KAAK,MAAM,EAAE,MAAM,EACnC,IAAM,KAAK,IAAI,GAAG,EAAO,EACzB,IAAM,KAAK,IAAI,GAAG,EAAO,EACzB,IAAO,IAAM,KAAO,KAAK,IAAI,EAAI,IAAI;AAI3C,QACE,kBAAC,GAAD;EACU;EACA,QANqB,CAAC,IAAM,IAAO,KAAM,IAAM,IAAO,IAAK;EAO3D;EACR,OAPe,KAAS,iBAAiB,EAAO,OAAO;EAQvD,aAAa;EACF;EACX,UAAU,MACR,kBAAC,QAAD,EAAA,UAAA,CACE,kBAAC,QAAD;GAAM,WAAU;aAAhB,CAAuC,EAAO,IAAG,KAAS;MAC1D,kBAAC,QAAD;GAAM,WAAU;aAA8B,EAAe,EAAO,GAAG;GAAQ,CAAA,CAC1E,EAAA,CAAA;aAGP,EAAE,OAAI,OAAI,eAAY,qBAAkB;GACxC,IAAM,IAAS,EAAO,KAAK,GAAG,OAAO;IAAE,GAAG,EAAG,EAAE;IAAE,GAAG,EAAG,EAAE;IAAE,EAAE,EACvD,IAAO,EAAc,EAAO,EAC5B,IAAO,EAAc,GAAQ,EAAW,EACxC,IAAO,EAAO,EAAO,SAAS;AACpC,UACE,kBAAC,KAAD;IAAG,OAAO,EAAE,OAAO,GAAQ;cAA3B;KACE,kBAAC,QAAD,EAAA,UACE,kBAAC,kBAAD;MAAgB,IAAI;MAAY,IAAG;MAAI,IAAG;MAAI,IAAG;MAAI,IAAG;gBAAxD,CACE,kBAAC,QAAD;OAAM,QAAO;OAAK,WAAU;OAAe,aAAa;OAAQ,CAAA,EAChE,kBAAC,QAAD;OAAM,QAAO;OAAO,WAAU;OAAe,aAAa;OAAQ,CAAA,CACnD;SACZ,CAAA;KACP,kBAAC,QAAD;MACK;MACH,MAAM,QAAQ,EAAW;MACzB,QAAO;MACP,OAAO;OACL,SAAS,KAAW,IAAU,IAAI;OAClC,YAAY,IAAU,KAAA,IAAY;OACnC;MACD,CAAA;KACF,kBAAC,QAAD;MACE,GAAG;MACH,MAAK;MACL,QAAO;MACP,aAAa;MACb,eAAc;MACd,gBAAe;MACf,cAAa;MACb,YAAY;MACZ,OAAO;OACL,iBAAiB;OACjB,kBAAkB,KAAW,IAAU,IAAI;OAC3C,YAAY,IAAU,KAAA,IAAY;OACnC;MACD,CAAA;KACF,kBAAC,UAAD;MAAQ,IAAI,EAAK;MAAG,IAAI,EAAK;MAAG,GAAG;MAAK,MAAK;MAAiB,CAAA;KAC7D,MAAgB,OAkBb,OAjBF,kBAAC,KAAD,EAAA,UAAA,CACE,kBAAC,UAAD;MACE,IAAI,EAAG,EAAY;MACnB,IAAI,EAAG,EAAO,GAAa;MAC3B,GAAG;MACH,MAAK;MACL,SAAS;MACT,CAAA,EACF,kBAAC,UAAD;MACE,IAAI,EAAG,EAAY;MACnB,IAAI,EAAG,EAAO,GAAa;MAC3B,GAAG;MACH,MAAK;MACL,QAAO;MACP,aAAa;MACb,CAAA,CACA,EAAA,CAAA;KAEJ;;;EAGO,CAAA"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { cn as e } from "../../utils/cn.js";
|
|
2
|
+
import { VizAvatar as t } from "./lib/Avatar.js";
|
|
3
|
+
import { jsx as n, jsxs as r } from "react/jsx-runtime";
|
|
4
|
+
//#region src/crewfoundry/components/viz/AvatarStack.tsx
|
|
5
|
+
function i({ people: i, max: a = 5, size: o = 32, className: s }) {
|
|
6
|
+
if (i.length === 0) return null;
|
|
7
|
+
let c = i.slice(0, a), l = i.length - c.length, u = Math.round(o * .32);
|
|
8
|
+
return /* @__PURE__ */ r("div", {
|
|
9
|
+
className: e("flex items-center", s),
|
|
10
|
+
role: "img",
|
|
11
|
+
"aria-label": `${i.length} people: ${i.map((e) => e.name).join(", ")}`,
|
|
12
|
+
children: [c.map((e, r) => /* @__PURE__ */ n("span", {
|
|
13
|
+
style: { marginLeft: r === 0 ? 0 : -u },
|
|
14
|
+
children: /* @__PURE__ */ n(t, {
|
|
15
|
+
name: e.name,
|
|
16
|
+
src: e.avatarUrl,
|
|
17
|
+
size: o,
|
|
18
|
+
ring: !0
|
|
19
|
+
})
|
|
20
|
+
}, `${e.name}-${r}`)), l > 0 ? /* @__PURE__ */ r("span", {
|
|
21
|
+
className: "inline-flex items-center justify-center rounded-full border border-border-subtle bg-bg-elevated font-medium text-text-secondary ring-2 ring-[var(--color-bg-surface)]",
|
|
22
|
+
style: {
|
|
23
|
+
width: o,
|
|
24
|
+
height: o,
|
|
25
|
+
marginLeft: -u,
|
|
26
|
+
fontSize: Math.round(o * .34)
|
|
27
|
+
},
|
|
28
|
+
"aria-hidden": "true",
|
|
29
|
+
children: ["+", l]
|
|
30
|
+
}) : null]
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
34
|
+
export { i as AvatarStack };
|
|
35
|
+
|
|
36
|
+
//# sourceMappingURL=AvatarStack.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AvatarStack.js","names":[],"sources":["../../../../src/crewfoundry/components/viz/AvatarStack.tsx"],"sourcesContent":["import { cn } from '../../utils/cn';\nimport { VizAvatar } from './lib/Avatar';\nimport type { Person } from './lib/types';\n\nexport interface AvatarStackProps {\n people: Person[];\n /** Maximum avatars shown before collapsing into a \"+N\" chip. */\n max?: number;\n /** Pixel diameter of each avatar. */\n size?: number;\n className?: string;\n}\n\n/** Overlapping avatars with a \"+N\" overflow chip. */\nexport function AvatarStack({ people, max = 5, size = 32, className }: AvatarStackProps) {\n if (people.length === 0) {\n return null;\n }\n const shown = people.slice(0, max);\n const overflow = people.length - shown.length;\n const overlap = Math.round(size * 0.32);\n\n return (\n <div\n className={cn('flex items-center', className)}\n role=\"img\"\n aria-label={`${people.length} people: ${people.map((p) => p.name).join(', ')}`}\n >\n {shown.map((person, i) => (\n <span key={`${person.name}-${i}`} style={{ marginLeft: i === 0 ? 0 : -overlap }}>\n <VizAvatar name={person.name} src={person.avatarUrl} size={size} ring />\n </span>\n ))}\n {overflow > 0 ? (\n <span\n className=\"inline-flex items-center justify-center rounded-full border border-border-subtle bg-bg-elevated font-medium text-text-secondary ring-2 ring-[var(--color-bg-surface)]\"\n style={{\n width: size,\n height: size,\n marginLeft: -overlap,\n fontSize: Math.round(size * 0.34),\n }}\n aria-hidden=\"true\"\n >\n +{overflow}\n </span>\n ) : null}\n </div>\n );\n}\n"],"mappings":";;;;AAcA,SAAgB,EAAY,EAAE,WAAQ,SAAM,GAAG,UAAO,IAAI,gBAA+B;AACvF,KAAI,EAAO,WAAW,EACpB,QAAO;CAET,IAAM,IAAQ,EAAO,MAAM,GAAG,EAAI,EAC5B,IAAW,EAAO,SAAS,EAAM,QACjC,IAAU,KAAK,MAAM,IAAO,IAAK;AAEvC,QACE,kBAAC,OAAD;EACE,WAAW,EAAG,qBAAqB,EAAU;EAC7C,MAAK;EACL,cAAY,GAAG,EAAO,OAAO,WAAW,EAAO,KAAK,MAAM,EAAE,KAAK,CAAC,KAAK,KAAK;YAH9E,CAKG,EAAM,KAAK,GAAQ,MAClB,kBAAC,QAAD;GAAkC,OAAO,EAAE,YAAY,MAAM,IAAI,IAAI,CAAC,GAAS;aAC7E,kBAAC,GAAD;IAAW,MAAM,EAAO;IAAM,KAAK,EAAO;IAAiB;IAAM,MAAA;IAAO,CAAA;GACnE,EAFI,GAAG,EAAO,KAAK,GAAG,IAEtB,CACP,EACD,IAAW,IACV,kBAAC,QAAD;GACE,WAAU;GACV,OAAO;IACL,OAAO;IACP,QAAQ;IACR,YAAY,CAAC;IACb,UAAU,KAAK,MAAM,IAAO,IAAK;IAClC;GACD,eAAY;aARd,CASC,KACG,EACG;OACL,KACA"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { chartPalette as e } from "./lib/palette.js";
|
|
2
|
+
import { useMounted as t, usePrefersReducedMotion as n } from "./lib/hooks.js";
|
|
3
|
+
import { ResponsiveSvg as r } from "./lib/ResponsiveSvg.js";
|
|
4
|
+
import { scaleLinear as i } from "./lib/geometry.js";
|
|
5
|
+
import { formatCompact as a } from "./lib/format.js";
|
|
6
|
+
import { jsx as o, jsxs as s } from "react/jsx-runtime";
|
|
7
|
+
//#region src/crewfoundry/components/viz/BarChart.tsx
|
|
8
|
+
var c = {
|
|
9
|
+
top: 20,
|
|
10
|
+
bottom: 26,
|
|
11
|
+
left: 8,
|
|
12
|
+
right: 8
|
|
13
|
+
};
|
|
14
|
+
function l({ series: l, height: u = 220, valueFormatter: d = a, colorful: f = !1, hideValues: p = !1, title: m, className: h }) {
|
|
15
|
+
let g = t(), _ = n();
|
|
16
|
+
if (l.length === 0) return null;
|
|
17
|
+
let v = l.map((e) => e.value), y = Math.max(...v, 0) || 1, b = e(l.length);
|
|
18
|
+
return /* @__PURE__ */ o(r, {
|
|
19
|
+
height: u,
|
|
20
|
+
title: m ?? `Bar chart of ${l.length} categories`,
|
|
21
|
+
className: h,
|
|
22
|
+
desc: l.map((e) => `${e.label}: ${d(e.value)}`).join(", "),
|
|
23
|
+
children: ({ width: e }) => {
|
|
24
|
+
let t = c.left, n = e - c.right, r = c.top, a = u - c.bottom, m = a - r, h = (n - t) / l.length, v = Math.min(56, h * .62);
|
|
25
|
+
return /* @__PURE__ */ s("g", { children: [/* @__PURE__ */ o("line", {
|
|
26
|
+
x1: t,
|
|
27
|
+
x2: n,
|
|
28
|
+
y1: a,
|
|
29
|
+
y2: a,
|
|
30
|
+
stroke: "var(--color-chart-axis)",
|
|
31
|
+
strokeWidth: 1,
|
|
32
|
+
vectorEffect: "non-scaling-stroke",
|
|
33
|
+
opacity: .5
|
|
34
|
+
}), l.map((e, n) => {
|
|
35
|
+
let r = i(e.value, 0, y, 0, m), c = t + h * n + h / 2, l = c - v / 2, x = a - r, S = e.color ?? (f ? b[n] : "var(--color-accent-primary)");
|
|
36
|
+
return /* @__PURE__ */ s("g", { children: [
|
|
37
|
+
/* @__PURE__ */ o("title", { children: `${e.label}: ${d(e.value)}` }),
|
|
38
|
+
/* @__PURE__ */ o("rect", {
|
|
39
|
+
x: l,
|
|
40
|
+
y: x,
|
|
41
|
+
width: v,
|
|
42
|
+
height: Math.max(0, r),
|
|
43
|
+
rx: 4,
|
|
44
|
+
fill: S,
|
|
45
|
+
style: {
|
|
46
|
+
transformBox: "fill-box",
|
|
47
|
+
transformOrigin: "bottom",
|
|
48
|
+
transform: g || _ ? "scaleY(1)" : "scaleY(0)",
|
|
49
|
+
transition: _ ? void 0 : "transform 620ms cubic-bezier(0.22,1,0.36,1)",
|
|
50
|
+
transitionDelay: _ ? void 0 : `${n * 45}ms`
|
|
51
|
+
}
|
|
52
|
+
}),
|
|
53
|
+
p ? null : /* @__PURE__ */ o("text", {
|
|
54
|
+
x: c,
|
|
55
|
+
y: x - 6,
|
|
56
|
+
textAnchor: "middle",
|
|
57
|
+
fontSize: 10,
|
|
58
|
+
fontWeight: 600,
|
|
59
|
+
fill: "var(--color-text-secondary)",
|
|
60
|
+
style: {
|
|
61
|
+
opacity: g || _ ? 1 : 0,
|
|
62
|
+
transition: _ ? void 0 : "opacity 400ms ease",
|
|
63
|
+
transitionDelay: _ ? void 0 : `${n * 45 + 220}ms`
|
|
64
|
+
},
|
|
65
|
+
children: d(e.value)
|
|
66
|
+
}),
|
|
67
|
+
/* @__PURE__ */ o("text", {
|
|
68
|
+
x: c,
|
|
69
|
+
y: u - 8,
|
|
70
|
+
textAnchor: "middle",
|
|
71
|
+
fontSize: 10,
|
|
72
|
+
fill: "var(--color-chart-axis)",
|
|
73
|
+
children: e.label
|
|
74
|
+
})
|
|
75
|
+
] }, `${e.label}-${n}`);
|
|
76
|
+
})] });
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
81
|
+
export { l as BarChart };
|
|
82
|
+
|
|
83
|
+
//# sourceMappingURL=BarChart.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BarChart.js","names":[],"sources":["../../../../src/crewfoundry/components/viz/BarChart.tsx"],"sourcesContent":["import { ACCENT, chartPalette } from './lib/palette';\nimport { ResponsiveSvg } from './lib/ResponsiveSvg';\nimport { scaleLinear } from './lib/geometry';\nimport { formatCompact } from './lib/format';\nimport { useMounted, usePrefersReducedMotion } from './lib/hooks';\nimport type { Segment } from './lib/types';\n\nexport interface BarChartProps {\n /** Bars to render. A `color` on a datum overrides the palette. */\n series: Segment[];\n height?: number;\n valueFormatter?: (value: number) => string;\n /** Color each bar from the categorical palette instead of a single accent. */\n colorful?: boolean;\n /** Hide the value label above each bar. */\n hideValues?: boolean;\n title?: string;\n className?: string;\n}\n\nconst PAD = { top: 20, bottom: 26, left: 8, right: 8 };\n\n/** Vertical bar chart with value labels and an animated grow-in. */\nexport function BarChart({\n series,\n height = 220,\n valueFormatter = formatCompact,\n colorful = false,\n hideValues = false,\n title,\n className,\n}: BarChartProps) {\n const mounted = useMounted();\n const reduced = usePrefersReducedMotion();\n\n if (series.length === 0) {\n return null;\n }\n\n const values = series.map((s) => s.value);\n const max = Math.max(...values, 0) || 1;\n const palette = chartPalette(series.length);\n const chartTitle = title ?? `Bar chart of ${series.length} categories`;\n\n return (\n <ResponsiveSvg\n height={height}\n title={chartTitle}\n className={className}\n desc={series.map((s) => `${s.label}: ${valueFormatter(s.value)}`).join(', ')}\n >\n {({ width }) => {\n const plotLeft = PAD.left;\n const plotRight = width - PAD.right;\n const plotTop = PAD.top;\n const plotBottom = height - PAD.bottom;\n const plotH = plotBottom - plotTop;\n const slot = (plotRight - plotLeft) / series.length;\n const barWidth = Math.min(56, slot * 0.62);\n return (\n <g>\n <line\n x1={plotLeft}\n x2={plotRight}\n y1={plotBottom}\n y2={plotBottom}\n stroke=\"var(--color-chart-axis)\"\n strokeWidth={1}\n vectorEffect=\"non-scaling-stroke\"\n opacity={0.5}\n />\n {series.map((datum, i) => {\n const barH = scaleLinear(datum.value, 0, max, 0, plotH);\n const cx = plotLeft + slot * i + slot / 2;\n const x = cx - barWidth / 2;\n const y = plotBottom - barH;\n const color = datum.color ?? (colorful ? palette[i] : ACCENT);\n return (\n <g key={`${datum.label}-${i}`}>\n <title>{`${datum.label}: ${valueFormatter(datum.value)}`}</title>\n <rect\n x={x}\n y={y}\n width={barWidth}\n height={Math.max(0, barH)}\n rx={4}\n fill={color}\n style={{\n transformBox: 'fill-box',\n transformOrigin: 'bottom',\n transform: mounted || reduced ? 'scaleY(1)' : 'scaleY(0)',\n transition: reduced ? undefined : 'transform 620ms cubic-bezier(0.22,1,0.36,1)',\n transitionDelay: reduced ? undefined : `${i * 45}ms`,\n }}\n />\n {hideValues ? null : (\n <text\n x={cx}\n y={y - 6}\n textAnchor=\"middle\"\n fontSize={10}\n fontWeight={600}\n fill=\"var(--color-text-secondary)\"\n style={{\n opacity: mounted || reduced ? 1 : 0,\n transition: reduced ? undefined : 'opacity 400ms ease',\n transitionDelay: reduced ? undefined : `${i * 45 + 220}ms`,\n }}\n >\n {valueFormatter(datum.value)}\n </text>\n )}\n <text\n x={cx}\n y={height - 8}\n textAnchor=\"middle\"\n fontSize={10}\n fill=\"var(--color-chart-axis)\"\n >\n {datum.label}\n </text>\n </g>\n );\n })}\n </g>\n );\n }}\n </ResponsiveSvg>\n );\n}\n"],"mappings":";;;;;;;AAoBA,IAAM,IAAM;CAAE,KAAK;CAAI,QAAQ;CAAI,MAAM;CAAG,OAAO;CAAG;AAGtD,SAAgB,EAAS,EACvB,WACA,YAAS,KACT,oBAAiB,GACjB,cAAW,IACX,gBAAa,IACb,UACA,gBACgB;CAChB,IAAM,IAAU,GAAY,EACtB,IAAU,GAAyB;AAEzC,KAAI,EAAO,WAAW,EACpB,QAAO;CAGT,IAAM,IAAS,EAAO,KAAK,MAAM,EAAE,MAAM,EACnC,IAAM,KAAK,IAAI,GAAG,GAAQ,EAAE,IAAI,GAChC,IAAU,EAAa,EAAO,OAAO;AAG3C,QACE,kBAAC,GAAD;EACU;EACR,OALe,KAAS,gBAAgB,EAAO,OAAO;EAM3C;EACX,MAAM,EAAO,KAAK,MAAM,GAAG,EAAE,MAAM,IAAI,EAAe,EAAE,MAAM,GAAG,CAAC,KAAK,KAAK;aAE1E,EAAE,eAAY;GACd,IAAM,IAAW,EAAI,MACf,IAAY,IAAQ,EAAI,OACxB,IAAU,EAAI,KACd,IAAa,IAAS,EAAI,QAC1B,IAAQ,IAAa,GACrB,KAAQ,IAAY,KAAY,EAAO,QACvC,IAAW,KAAK,IAAI,IAAI,IAAO,IAAK;AAC1C,UACE,kBAAC,KAAD,EAAA,UAAA,CACE,kBAAC,QAAD;IACE,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,QAAO;IACP,aAAa;IACb,cAAa;IACb,SAAS;IACT,CAAA,EACD,EAAO,KAAK,GAAO,MAAM;IACxB,IAAM,IAAO,EAAY,EAAM,OAAO,GAAG,GAAK,GAAG,EAAM,EACjD,IAAK,IAAW,IAAO,IAAI,IAAO,GAClC,IAAI,IAAK,IAAW,GACpB,IAAI,IAAa,GACjB,IAAQ,EAAM,UAAU,IAAW,EAAQ,KAAA;AACjD,WACE,kBAAC,KAAD,EAAA,UAAA;KACE,kBAAC,SAAD,EAAA,UAAQ,GAAG,EAAM,MAAM,IAAI,EAAe,EAAM,MAAM,IAAW,CAAA;KACjE,kBAAC,QAAD;MACK;MACA;MACH,OAAO;MACP,QAAQ,KAAK,IAAI,GAAG,EAAK;MACzB,IAAI;MACJ,MAAM;MACN,OAAO;OACL,cAAc;OACd,iBAAiB;OACjB,WAAW,KAAW,IAAU,cAAc;OAC9C,YAAY,IAAU,KAAA,IAAY;OAClC,iBAAiB,IAAU,KAAA,IAAY,GAAG,IAAI,GAAG;OAClD;MACD,CAAA;KACD,IAAa,OACZ,kBAAC,QAAD;MACE,GAAG;MACH,GAAG,IAAI;MACP,YAAW;MACX,UAAU;MACV,YAAY;MACZ,MAAK;MACL,OAAO;OACL,SAAS,KAAW,IAAU,IAAI;OAClC,YAAY,IAAU,KAAA,IAAY;OAClC,iBAAiB,IAAU,KAAA,IAAY,GAAG,IAAI,KAAK,IAAI;OACxD;gBAEA,EAAe,EAAM,MAAM;MACvB,CAAA;KAET,kBAAC,QAAD;MACE,GAAG;MACH,GAAG,IAAS;MACZ,YAAW;MACX,UAAU;MACV,MAAK;gBAEJ,EAAM;MACF,CAAA;KACL,EAAA,EA3CI,GAAG,EAAM,MAAM,GAAG,IA2CtB;KAEN,CACA,EAAA,CAAA;;EAGM,CAAA"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { ACCENT as e } from "./lib/palette.js";
|
|
2
|
+
import { cn as t } from "../../utils/cn.js";
|
|
3
|
+
import { starPath as n } from "./lib/geometry.js";
|
|
4
|
+
import { Download as r } from "lucide-react";
|
|
5
|
+
import { Fragment as i, jsx as a, jsxs as o } from "react/jsx-runtime";
|
|
6
|
+
import { Button as s } from "@burdenoff/fe-libs/ui";
|
|
7
|
+
//#region src/crewfoundry/components/viz/CertificateCard.tsx
|
|
8
|
+
function c() {
|
|
9
|
+
return /* @__PURE__ */ o("svg", {
|
|
10
|
+
viewBox: "0 0 48 60",
|
|
11
|
+
width: 48,
|
|
12
|
+
height: 60,
|
|
13
|
+
"aria-hidden": "true",
|
|
14
|
+
style: {
|
|
15
|
+
color: e,
|
|
16
|
+
display: "block"
|
|
17
|
+
},
|
|
18
|
+
children: [
|
|
19
|
+
/* @__PURE__ */ a("path", {
|
|
20
|
+
d: "M18 38 L18 58 L24 52 L30 58 L30 38 Z",
|
|
21
|
+
fill: "currentColor",
|
|
22
|
+
opacity: .85
|
|
23
|
+
}),
|
|
24
|
+
/* @__PURE__ */ a("circle", {
|
|
25
|
+
cx: "24",
|
|
26
|
+
cy: "22",
|
|
27
|
+
r: "19",
|
|
28
|
+
fill: "currentColor",
|
|
29
|
+
opacity: .1
|
|
30
|
+
}),
|
|
31
|
+
/* @__PURE__ */ a("circle", {
|
|
32
|
+
cx: "24",
|
|
33
|
+
cy: "22",
|
|
34
|
+
r: "19",
|
|
35
|
+
fill: "none",
|
|
36
|
+
stroke: "currentColor",
|
|
37
|
+
strokeWidth: 2
|
|
38
|
+
}),
|
|
39
|
+
/* @__PURE__ */ a("circle", {
|
|
40
|
+
cx: "24",
|
|
41
|
+
cy: "22",
|
|
42
|
+
r: "14",
|
|
43
|
+
fill: "none",
|
|
44
|
+
stroke: "currentColor",
|
|
45
|
+
strokeWidth: 1,
|
|
46
|
+
opacity: .5
|
|
47
|
+
}),
|
|
48
|
+
/* @__PURE__ */ a("path", {
|
|
49
|
+
d: n(24, 22, 9, 3.8),
|
|
50
|
+
fill: "currentColor"
|
|
51
|
+
})
|
|
52
|
+
]
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function l({ title: e, recipient: n, date: l, issuer: u, credentialId: d, onDownload: f, downloadLabel: p = "Download", className: m }) {
|
|
56
|
+
return /* @__PURE__ */ o("div", {
|
|
57
|
+
className: t("relative flex flex-col gap-4 rounded-[var(--radius-card)] border-2 border-border-default bg-bg-surface p-5 shadow-elevation-1", "before:pointer-events-none before:absolute before:inset-1.5 before:rounded-[calc(var(--radius-card)-4px)] before:border before:border-border-subtle before:content-[\"\"]", m),
|
|
58
|
+
children: [
|
|
59
|
+
/* @__PURE__ */ o("div", {
|
|
60
|
+
className: "flex items-start justify-between gap-3",
|
|
61
|
+
children: [/* @__PURE__ */ o("div", {
|
|
62
|
+
className: "min-w-0",
|
|
63
|
+
children: [/* @__PURE__ */ a("p", {
|
|
64
|
+
className: "text-xs font-medium uppercase tracking-wide text-text-muted",
|
|
65
|
+
children: "Certificate of Completion"
|
|
66
|
+
}), /* @__PURE__ */ a("h3", {
|
|
67
|
+
className: "mt-1 truncate text-lg font-semibold text-text-primary",
|
|
68
|
+
title: e,
|
|
69
|
+
children: e
|
|
70
|
+
})]
|
|
71
|
+
}), /* @__PURE__ */ a(c, {})]
|
|
72
|
+
}),
|
|
73
|
+
/* @__PURE__ */ o("div", {
|
|
74
|
+
className: "min-w-0",
|
|
75
|
+
children: [/* @__PURE__ */ a("p", {
|
|
76
|
+
className: "text-xs text-text-muted",
|
|
77
|
+
children: "Awarded to"
|
|
78
|
+
}), /* @__PURE__ */ a("p", {
|
|
79
|
+
className: "truncate text-base font-medium text-text-primary",
|
|
80
|
+
title: n,
|
|
81
|
+
children: n
|
|
82
|
+
})]
|
|
83
|
+
}),
|
|
84
|
+
/* @__PURE__ */ o("div", {
|
|
85
|
+
className: "flex flex-wrap items-end justify-between gap-3 border-t border-border-subtle pt-3",
|
|
86
|
+
children: [/* @__PURE__ */ o("div", {
|
|
87
|
+
className: "flex flex-col gap-0.5 text-xs text-text-muted",
|
|
88
|
+
children: [/* @__PURE__ */ o("span", { children: [
|
|
89
|
+
"Issued ",
|
|
90
|
+
/* @__PURE__ */ a("span", {
|
|
91
|
+
className: "text-text-secondary",
|
|
92
|
+
children: l
|
|
93
|
+
}),
|
|
94
|
+
u ? /* @__PURE__ */ o(i, { children: [" · ", /* @__PURE__ */ a("span", {
|
|
95
|
+
className: "text-text-secondary",
|
|
96
|
+
children: u
|
|
97
|
+
})] }) : null
|
|
98
|
+
] }), d ? /* @__PURE__ */ o("span", {
|
|
99
|
+
className: "tabular-nums",
|
|
100
|
+
children: ["ID: ", d]
|
|
101
|
+
}) : null]
|
|
102
|
+
}), f ? /* @__PURE__ */ o(s, {
|
|
103
|
+
variant: "outline",
|
|
104
|
+
size: "sm",
|
|
105
|
+
onClick: f,
|
|
106
|
+
children: [/* @__PURE__ */ a(r, {
|
|
107
|
+
"aria-hidden": "true",
|
|
108
|
+
className: "h-4 w-4"
|
|
109
|
+
}), p]
|
|
110
|
+
}) : null]
|
|
111
|
+
})
|
|
112
|
+
]
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
//#endregion
|
|
116
|
+
export { l as CertificateCard };
|
|
117
|
+
|
|
118
|
+
//# sourceMappingURL=CertificateCard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CertificateCard.js","names":[],"sources":["../../../../src/crewfoundry/components/viz/CertificateCard.tsx"],"sourcesContent":["import { Download } from 'lucide-react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../../utils/cn';\nimport { ACCENT } from './lib/palette';\nimport { starPath } from './lib/geometry';\n\nexport interface CertificateCardProps {\n title: string;\n recipient: string;\n date: string;\n issuer?: string;\n credentialId?: string;\n /** When provided, renders a download button wired to this handler. */\n onDownload?: () => void;\n downloadLabel?: string;\n className?: string;\n}\n\n/** Decorative award seal (hand-drawn SVG, tokenized via currentColor). */\nfunction Seal() {\n return (\n <svg\n viewBox=\"0 0 48 60\"\n width={48}\n height={60}\n aria-hidden=\"true\"\n style={{ color: ACCENT, display: 'block' }}\n >\n <path d=\"M18 38 L18 58 L24 52 L30 58 L30 38 Z\" fill=\"currentColor\" opacity={0.85} />\n <circle cx=\"24\" cy=\"22\" r=\"19\" fill=\"currentColor\" opacity={0.1} />\n <circle cx=\"24\" cy=\"22\" r=\"19\" fill=\"none\" stroke=\"currentColor\" strokeWidth={2} />\n <circle cx=\"24\" cy=\"22\" r=\"14\" fill=\"none\" stroke=\"currentColor\" strokeWidth={1} opacity={0.5} />\n <path d={starPath(24, 22, 9, 3.8)} fill=\"currentColor\" />\n </svg>\n );\n}\n\n/** Landscape certificate tile with seal, recipient, date and download affordance. */\nexport function CertificateCard({\n title,\n recipient,\n date,\n issuer,\n credentialId,\n onDownload,\n downloadLabel = 'Download',\n className,\n}: CertificateCardProps) {\n return (\n <div\n className={cn(\n 'relative flex flex-col gap-4 rounded-[var(--radius-card)] border-2 border-border-default bg-bg-surface p-5 shadow-elevation-1',\n 'before:pointer-events-none before:absolute before:inset-1.5 before:rounded-[calc(var(--radius-card)-4px)] before:border before:border-border-subtle before:content-[\"\"]',\n className,\n )}\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-muted\">\n Certificate of Completion\n </p>\n <h3 className=\"mt-1 truncate text-lg font-semibold text-text-primary\" title={title}>\n {title}\n </h3>\n </div>\n <Seal />\n </div>\n\n <div className=\"min-w-0\">\n <p className=\"text-xs text-text-muted\">Awarded to</p>\n <p className=\"truncate text-base font-medium text-text-primary\" title={recipient}>\n {recipient}\n </p>\n </div>\n\n <div className=\"flex flex-wrap items-end justify-between gap-3 border-t border-border-subtle pt-3\">\n <div className=\"flex flex-col gap-0.5 text-xs text-text-muted\">\n <span>\n Issued <span className=\"text-text-secondary\">{date}</span>\n {issuer ? (\n <>\n {' · '}\n <span className=\"text-text-secondary\">{issuer}</span>\n </>\n ) : null}\n </span>\n {credentialId ? <span className=\"tabular-nums\">ID: {credentialId}</span> : null}\n </div>\n {onDownload ? (\n <Button variant=\"outline\" size=\"sm\" onClick={onDownload}>\n <Download aria-hidden=\"true\" className=\"h-4 w-4\" />\n {downloadLabel}\n </Button>\n ) : null}\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;AAmBA,SAAS,IAAO;AACd,QACE,kBAAC,OAAD;EACE,SAAQ;EACR,OAAO;EACP,QAAQ;EACR,eAAY;EACZ,OAAO;GAAE,OAAO;GAAQ,SAAS;GAAS;YAL5C;GAOE,kBAAC,QAAD;IAAM,GAAE;IAAuC,MAAK;IAAe,SAAS;IAAQ,CAAA;GACpF,kBAAC,UAAD;IAAQ,IAAG;IAAK,IAAG;IAAK,GAAE;IAAK,MAAK;IAAe,SAAS;IAAO,CAAA;GACnE,kBAAC,UAAD;IAAQ,IAAG;IAAK,IAAG;IAAK,GAAE;IAAK,MAAK;IAAO,QAAO;IAAe,aAAa;IAAK,CAAA;GACnF,kBAAC,UAAD;IAAQ,IAAG;IAAK,IAAG;IAAK,GAAE;IAAK,MAAK;IAAO,QAAO;IAAe,aAAa;IAAG,SAAS;IAAO,CAAA;GACjG,kBAAC,QAAD;IAAM,GAAG,EAAS,IAAI,IAAI,GAAG,IAAI;IAAE,MAAK;IAAiB,CAAA;GACrD;;;AAKV,SAAgB,EAAgB,EAC9B,UACA,cACA,SACA,WACA,iBACA,eACA,mBAAgB,YAChB,gBACuB;AACvB,QACE,kBAAC,OAAD;EACE,WAAW,EACT,iIACA,6KACA,EACD;YALH;GAOE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,KAAD;MAAG,WAAU;gBAA8D;MAEvE,CAAA,EACJ,kBAAC,MAAD;MAAI,WAAU;MAA+D;gBAC1E;MACE,CAAA,CACD;QACN,kBAAC,GAAD,EAAQ,CAAA,CACJ;;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,KAAD;KAAG,WAAU;eAA0B;KAAc,CAAA,EACrD,kBAAC,KAAD;KAAG,WAAU;KAAmD,OAAO;eACpE;KACC,CAAA,CACA;;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,QAAD,EAAA,UAAA;MAAM;MACG,kBAAC,QAAD;OAAM,WAAU;iBAAuB;OAAY,CAAA;MACzD,IACC,kBAAA,GAAA,EAAA,UAAA,CACG,OACD,kBAAC,QAAD;OAAM,WAAU;iBAAuB;OAAc,CAAA,CACpD,EAAA,CAAA,GACD;MACC,EAAA,CAAA,EACN,IAAe,kBAAC,QAAD;MAAM,WAAU;gBAAhB,CAA+B,QAAK,EAAoB;UAAG,KACvE;QACL,IACC,kBAAC,GAAD;KAAQ,SAAQ;KAAU,MAAK;KAAK,SAAS;eAA7C,CACE,kBAAC,GAAD;MAAU,eAAY;MAAO,WAAU;MAAY,CAAA,EAClD,EACM;SACP,KACA;;GACF"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { chartPalette as e } from "./lib/palette.js";
|
|
2
|
+
import { cn as t } from "../../utils/cn.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/crewfoundry/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 = e(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: t("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/crewfoundry/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(CENTER, CENTER, R_OUTER, rInner, arc.startAngle, arc.endAngle)}\n {...shared}\n >\n <title>{`${arc.segment.label}: ${valueFormatter(arc.segment.value)}`}</title>\n </path>\n );\n })}\n </g>\n </svg>\n {centerLabel !== undefined || centerCaption !== undefined ? (\n <div className=\"pointer-events-none absolute inset-0 flex flex-col items-center justify-center text-center\">\n <span className=\"text-xl font-semibold leading-none text-text-primary\">\n {centerLabel}\n </span>\n {centerCaption !== undefined ? (\n <span className=\"mt-1 text-xs text-text-muted\">{centerCaption}</span>\n ) : null}\n </div>\n ) : null}\n </div>\n {showLegend ? (\n <ul className=\"flex min-w-[8rem] flex-1 flex-col gap-1.5 text-sm\">\n {arcs.map((arc, i) => (\n <li key={`${arc.segment.label}-${i}`} className=\"flex items-center gap-2\">\n <span\n aria-hidden=\"true\"\n className=\"inline-block h-2.5 w-2.5 shrink-0 rounded-full\"\n style={{ backgroundColor: arc.color }}\n />\n <span className=\"flex-1 truncate text-text-secondary\">{arc.segment.label}</span>\n <span className=\"tabular-nums text-text-muted\">\n {toPercent(arc.segment.value, total)}%\n </span>\n </li>\n ))}\n </ul>\n ) : null}\n </div>\n );\n}\n"],"mappings":";;;;;;;AAuBA,IAAM,IAAO,KACP,IAAS,IAAO,GAChB,IAAU;AAGhB,SAAgB,EAAW,EACzB,aACA,gBACA,kBACA,UAAO,KACP,eAAY,KACZ,oBAAiB,GACjB,gBAAa,IACb,UACA,gBACkB;CAClB,IAAM,IAAU,GAAY,EACtB,IAAU,GAAyB;AAEzC,KAAI,EAAS,WAAW,EACtB,QAAO;CAGT,IAAM,IAAQ,EAAS,QAAQ,GAAK,MAAM,IAAM,KAAK,IAAI,GAAG,EAAE,MAAM,EAAE,EAAE,EAClE,IAAU,EAAa,EAAS,OAAO,EACvC,IAAS,KAAW,IAAI,IACxB,IAAa,KAAS,kBAAkB,EAAS,OAAO,YAExD,IAAY,EAAS,KAAK,MAAO,IAAQ,IAAI,KAAK,IAAI,GAAG,EAAE,MAAM,GAAG,IAAQ,EAAG,EAC/E,IAAS,EAAU,KAAK,GAAG,MAAM,EAAU,MAAM,GAAG,EAAE,CAAC,QAAQ,GAAG,MAAM,IAAI,GAAG,EAAE,CAAC,EAClF,IAAO,EAAS,KAAK,GAAS,MAAM;EACxC,IAAM,IAAW,EAAU,IACrB,IAAa,EAAO,KAAK,KACzB,IAAW,KAAK,KAAK,EAAO,KAAK,KAAY,KAAK,QAAQ;AAEhE,SAAO;GAAE;GAAS,OADJ,EAAQ,SAAS,EAAQ;GACd;GAAY;GAAU;GAAU,QAAQ,KAAY;GAAQ;GACrF;AAEF,QACE,kBAAC,OAAD;EAAK,WAAW,EAAG,qCAAqC,EAAU;YAAlE,CACE,kBAAC,OAAD;GAAK,WAAU;GAAoB,OAAO;IAAE,OAAO;IAAM,QAAQ;IAAM,UAAU;IAAQ;aAAzF,CACE,kBAAC,OAAD;IACE,SAAS,OAAO,EAAK,GAAG;IACxB,OAAO;IACP,QAAQ;IACR,MAAK;IACL,cAAY;IACZ,OAAO;KAAE,SAAS;KAAS,UAAU;KAAQ,QAAQ;KAAQ;cAN/D;KAQE,kBAAC,SAAD,EAAA,UAAQ,GAAmB,CAAA;KAC3B,kBAAC,QAAD,EAAA,UACG,EACE,KAAK,MAAM,GAAG,EAAE,MAAM,IAAI,EAAe,EAAE,MAAM,CAAC,IAAI,EAAU,EAAE,OAAO,EAAM,CAAC,IAAI,CACpF,KAAK,KAAK,EACR,CAAA;KACP,kBAAC,KAAD;MACE,OAAO;OACL,iBAAiB;OACjB,WAAW,KAAW,IAAU,aAAa;OAC7C,YAAY,IAAU,KAAA,IAAY;OACnC;gBAEA,EAAK,KAAK,GAAK,MAAM;OACpB,IAAM,IAAS;QACb,MAAM,EAAI;QACV,OAAO;SACL,SAAS,KAAW,IAAU,IAAI;SAClC,YAAY,IAAU,KAAA,IAAY;SAClC,iBAAiB,IAAU,KAAA,IAAY,GAAG,IAAI,GAAG;SAClD;QACF;AAiBD,cAhBI,EAAI,SAEJ,kBAAC,QAAD;QAEE,GAAG,KAAK,EAAO,GAAG,IAAS,EAAQ,KAAK,EAAQ,GAAG,EAAQ,SAAS,EAAO,GACzE,IAAS,EACV,KAAK,EAAQ,GAAG,EAAQ,SAAS,EAAO,GAAG,IAAS,EAAQ,KAAK,EAAO,GACvE,IAAS,EACV,KAAK,EAAO,GAAG,EAAO,SAAS,EAAO,GAAG,IAAS,EAAO,KAAK,EAAO,GAAG,EAAO,SAAS,EAAO,GAC9F,IAAS,EACV;QACD,UAAS;QACT,GAAI;QACJ,EAVK,GAAG,EAAI,QAAQ,MAAM,GAAG,IAU7B,GAIJ,kBAAC,QAAD;QAEE,GAAG,EAAiB,GAAQ,GAAQ,GAAS,GAAQ,EAAI,YAAY,EAAI,SAAS;QAClF,GAAI;kBAEJ,kBAAC,SAAD,EAAA,UAAQ,GAAG,EAAI,QAAQ,MAAM,IAAI,EAAe,EAAI,QAAQ,MAAM,IAAW,CAAA;QACxE,EALA,GAAG,EAAI,QAAQ,MAAM,GAAG,IAKxB;QAET;MACA,CAAA;KACA;OACL,MAAgB,KAAA,KAAa,MAAkB,KAAA,IAC9C,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,QAAD;KAAM,WAAU;eACb;KACI,CAAA,EACN,MAAkB,KAAA,IAEf,OADF,kBAAC,QAAD;KAAM,WAAU;eAAgC;KAAqB,CAAA,CAEnE;QACJ,KACA;MACL,IACC,kBAAC,MAAD;GAAI,WAAU;aACX,EAAK,KAAK,GAAK,MACd,kBAAC,MAAD;IAAsC,WAAU;cAAhD;KACE,kBAAC,QAAD;MACE,eAAY;MACZ,WAAU;MACV,OAAO,EAAE,iBAAiB,EAAI,OAAO;MACrC,CAAA;KACF,kBAAC,QAAD;MAAM,WAAU;gBAAuC,EAAI,QAAQ;MAAa,CAAA;KAChF,kBAAC,QAAD;MAAM,WAAU;gBAAhB,CACG,EAAU,EAAI,QAAQ,OAAO,EAAM,EAAC,IAChC;;KACJ;MAVI,GAAG,EAAI,QAAQ,MAAM,GAAG,IAU5B,CACL;GACC,CAAA,GACH,KACA"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { ACCENT as e } from "./lib/palette.js";
|
|
2
|
+
import { cn as t } from "../../utils/cn.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/crewfoundry/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: t("flex flex-col gap-2", d),
|
|
13
|
+
children: c.map((t, n) => {
|
|
14
|
+
let r = a(t.value, m, 1), i = n === 0 ? 100 : a(t.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: t.label
|
|
22
|
+
}), /* @__PURE__ */ s("span", {
|
|
23
|
+
className: "tabular-nums text-text-secondary",
|
|
24
|
+
children: [l(t.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": `${t.label}: ${l(t.value)}`,
|
|
36
|
+
children: /* @__PURE__ */ o("div", {
|
|
37
|
+
className: "h-full rounded-[var(--radius-input)]",
|
|
38
|
+
style: {
|
|
39
|
+
width: f || p ? `${r}%` : "0%",
|
|
40
|
+
backgroundColor: e,
|
|
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
|
+
}, `${t.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/crewfoundry/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,91 @@
|
|
|
1
|
+
import { ACCENT as e } from "./lib/palette.js";
|
|
2
|
+
import { cn as t } from "../../utils/cn.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/crewfoundry/components/viz/Heatmap.tsx
|
|
7
|
+
function s(e, t) {
|
|
8
|
+
return e <= 0 || t <= 0 ? 0 : i(.14 + e / t * .86, 0, 1);
|
|
9
|
+
}
|
|
10
|
+
function c({ rows: i, colLabels: c, legend: l = !0, showValues: u = !1, valueFormatter: d = (e) => String(e), title: f, className: p }) {
|
|
11
|
+
let m = n(), h = r();
|
|
12
|
+
if (i.length === 0) return null;
|
|
13
|
+
let g = Math.max(1, ...i.flatMap((e) => e.cells.map((e) => e.value))), _ = f ?? "Heatmap";
|
|
14
|
+
return /* @__PURE__ */ o("div", {
|
|
15
|
+
className: t("flex flex-col gap-3", p),
|
|
16
|
+
children: [/* @__PURE__ */ a("div", {
|
|
17
|
+
className: "overflow-x-auto",
|
|
18
|
+
children: /* @__PURE__ */ o("table", {
|
|
19
|
+
className: "border-separate border-spacing-1",
|
|
20
|
+
style: { minWidth: "min-content" },
|
|
21
|
+
children: [
|
|
22
|
+
/* @__PURE__ */ a("caption", {
|
|
23
|
+
className: "sr-only",
|
|
24
|
+
children: _
|
|
25
|
+
}),
|
|
26
|
+
c ? /* @__PURE__ */ a("thead", { children: /* @__PURE__ */ o("tr", { children: [/* @__PURE__ */ a("th", { className: "w-px" }), c.map((e, t) => /* @__PURE__ */ a("th", {
|
|
27
|
+
scope: "col",
|
|
28
|
+
className: "px-1 pb-1 text-center text-xs font-medium text-text-muted",
|
|
29
|
+
children: e
|
|
30
|
+
}, `col-${t}`))] }) }) : null,
|
|
31
|
+
/* @__PURE__ */ a("tbody", { children: i.map((t, n) => /* @__PURE__ */ o("tr", { children: [/* @__PURE__ */ a("th", {
|
|
32
|
+
scope: "row",
|
|
33
|
+
className: "whitespace-nowrap pr-2 text-right text-xs font-medium text-text-secondary",
|
|
34
|
+
children: t.label
|
|
35
|
+
}), t.cells.map((r, i) => {
|
|
36
|
+
let l = r.max ?? g, f = s(r.value, l), p = h ? 0 : (n + i) * 22;
|
|
37
|
+
return /* @__PURE__ */ a("td", {
|
|
38
|
+
className: "p-0",
|
|
39
|
+
children: /* @__PURE__ */ o("div", {
|
|
40
|
+
className: "relative flex h-9 w-9 items-center justify-center rounded-[var(--radius-input)] border border-border-subtle text-[11px] font-medium tabular-nums",
|
|
41
|
+
title: `${t.label}${c?.[i] ? ` · ${c[i]}` : ""}: ${d(r.value)}`,
|
|
42
|
+
children: [/* @__PURE__ */ a("span", {
|
|
43
|
+
"aria-hidden": "true",
|
|
44
|
+
className: "absolute inset-0 rounded-[var(--radius-input)]",
|
|
45
|
+
style: {
|
|
46
|
+
backgroundColor: e,
|
|
47
|
+
opacity: m || h ? f : 0,
|
|
48
|
+
transition: h ? void 0 : "opacity 420ms ease",
|
|
49
|
+
transitionDelay: h ? void 0 : `${p}ms`
|
|
50
|
+
}
|
|
51
|
+
}), u && r.value > 0 ? /* @__PURE__ */ a("span", {
|
|
52
|
+
className: "relative",
|
|
53
|
+
style: { color: f > .55 ? "var(--color-text-inverse)" : "var(--color-text-secondary)" },
|
|
54
|
+
children: d(r.value)
|
|
55
|
+
}) : null]
|
|
56
|
+
})
|
|
57
|
+
}, `cell-${n}-${i}`);
|
|
58
|
+
})] }, `row-${n}`)) })
|
|
59
|
+
]
|
|
60
|
+
})
|
|
61
|
+
}), l ? /* @__PURE__ */ o("div", {
|
|
62
|
+
className: "flex items-center gap-2 text-xs text-text-muted",
|
|
63
|
+
children: [
|
|
64
|
+
/* @__PURE__ */ a("span", { children: "Less" }),
|
|
65
|
+
/* @__PURE__ */ a("span", {
|
|
66
|
+
className: "flex gap-1",
|
|
67
|
+
children: [
|
|
68
|
+
.14,
|
|
69
|
+
.32,
|
|
70
|
+
.5,
|
|
71
|
+
.68,
|
|
72
|
+
.86,
|
|
73
|
+
1
|
|
74
|
+
].map((t, n) => /* @__PURE__ */ a("span", {
|
|
75
|
+
"aria-hidden": "true",
|
|
76
|
+
className: "h-3 w-3 rounded-[3px] border border-border-subtle",
|
|
77
|
+
style: {
|
|
78
|
+
backgroundColor: e,
|
|
79
|
+
opacity: t
|
|
80
|
+
}
|
|
81
|
+
}, n))
|
|
82
|
+
}),
|
|
83
|
+
/* @__PURE__ */ a("span", { children: "More" })
|
|
84
|
+
]
|
|
85
|
+
}) : null]
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
//#endregion
|
|
89
|
+
export { c as Heatmap };
|
|
90
|
+
|
|
91
|
+
//# sourceMappingURL=Heatmap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Heatmap.js","names":[],"sources":["../../../../src/crewfoundry/components/viz/Heatmap.tsx"],"sourcesContent":["import { cn } from '../../utils/cn';\nimport { ACCENT } from './lib/palette';\nimport { clamp } from './lib/geometry';\nimport { useMounted, usePrefersReducedMotion } from './lib/hooks';\n\nexport interface HeatmapCell {\n value: number;\n /** Per-cell max; otherwise the grid-wide max is used. */\n max?: number;\n}\n\nexport interface HeatmapRow {\n label: string;\n cells: HeatmapCell[];\n}\n\nexport interface HeatmapProps {\n rows: HeatmapRow[];\n colLabels?: string[];\n /** Show the intensity legend below the grid. */\n legend?: boolean;\n /** Show the numeric value inside each cell. */\n showValues?: boolean;\n valueFormatter?: (value: number) => string;\n title?: string;\n className?: string;\n}\n\n/** Opacity floor so a non-zero cell is always faintly visible. */\nfunction intensity(value: number, max: number): number {\n if (value <= 0 || max <= 0) {\n return 0;\n }\n return clamp(0.14 + 0.86 * (value / max), 0, 1);\n}\n\n/**\n * Row × column grid with accent-shaded cells. Rendered as a real table (row +\n * column headers) and horizontally scrollable so wide grids never push the page.\n */\nexport function Heatmap({\n rows,\n colLabels,\n legend = true,\n showValues = false,\n valueFormatter = (v) => String(v),\n title,\n className,\n}: HeatmapProps) {\n const mounted = useMounted();\n const reduced = usePrefersReducedMotion();\n\n if (rows.length === 0) {\n return null;\n }\n\n const gridMax = Math.max(1, ...rows.flatMap((r) => r.cells.map((c) => c.value)));\n const caption = title ?? 'Heatmap';\n const legendStops = [0.14, 0.32, 0.5, 0.68, 0.86, 1];\n\n return (\n <div className={cn('flex flex-col gap-3', className)}>\n <div className=\"overflow-x-auto\">\n <table className=\"border-separate border-spacing-1\" style={{ minWidth: 'min-content' }}>\n <caption className=\"sr-only\">{caption}</caption>\n {colLabels ? (\n <thead>\n <tr>\n <th className=\"w-px\" />\n {colLabels.map((label, i) => (\n <th\n key={`col-${i}`}\n scope=\"col\"\n className=\"px-1 pb-1 text-center text-xs font-medium text-text-muted\"\n >\n {label}\n </th>\n ))}\n </tr>\n </thead>\n ) : null}\n <tbody>\n {rows.map((row, r) => (\n <tr key={`row-${r}`}>\n <th\n scope=\"row\"\n className=\"whitespace-nowrap pr-2 text-right text-xs font-medium text-text-secondary\"\n >\n {row.label}\n </th>\n {row.cells.map((cell, c) => {\n const cellMax = cell.max ?? gridMax;\n const alpha = intensity(cell.value, cellMax);\n const delay = reduced ? 0 : (r + c) * 22;\n return (\n <td key={`cell-${r}-${c}`} className=\"p-0\">\n <div\n className=\"relative flex h-9 w-9 items-center justify-center rounded-[var(--radius-input)] border border-border-subtle text-[11px] font-medium tabular-nums\"\n title={`${row.label}${colLabels?.[c] ? ` · ${colLabels[c]}` : ''}: ${valueFormatter(\n cell.value,\n )}`}\n >\n <span\n aria-hidden=\"true\"\n className=\"absolute inset-0 rounded-[var(--radius-input)]\"\n style={{\n backgroundColor: ACCENT,\n opacity: mounted || reduced ? alpha : 0,\n transition: reduced ? undefined : 'opacity 420ms ease',\n transitionDelay: reduced ? undefined : `${delay}ms`,\n }}\n />\n {showValues && cell.value > 0 ? (\n <span\n className=\"relative\"\n style={{ color: alpha > 0.55 ? 'var(--color-text-inverse)' : 'var(--color-text-secondary)' }}\n >\n {valueFormatter(cell.value)}\n </span>\n ) : null}\n </div>\n </td>\n );\n })}\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n {legend ? (\n <div className=\"flex items-center gap-2 text-xs text-text-muted\">\n <span>Less</span>\n <span className=\"flex gap-1\">\n {legendStops.map((stop, i) => (\n <span\n key={i}\n aria-hidden=\"true\"\n className=\"h-3 w-3 rounded-[3px] border border-border-subtle\"\n style={{ backgroundColor: ACCENT, opacity: stop }}\n />\n ))}\n </span>\n <span>More</span>\n </div>\n ) : null}\n </div>\n );\n}\n"],"mappings":";;;;;;AA6BA,SAAS,EAAU,GAAe,GAAqB;AAIrD,QAHI,KAAS,KAAK,KAAO,IAChB,IAEF,EAAM,MAAe,IAAQ,IAAhB,KAAsB,GAAG,EAAE;;AAOjD,SAAgB,EAAQ,EACtB,SACA,cACA,YAAS,IACT,gBAAa,IACb,qBAAkB,MAAM,OAAO,EAAE,EACjC,UACA,gBACe;CACf,IAAM,IAAU,GAAY,EACtB,IAAU,GAAyB;AAEzC,KAAI,EAAK,WAAW,EAClB,QAAO;CAGT,IAAM,IAAU,KAAK,IAAI,GAAG,GAAG,EAAK,SAAS,MAAM,EAAE,MAAM,KAAK,MAAM,EAAE,MAAM,CAAC,CAAC,EAC1E,IAAU,KAAS;AAGzB,QACE,kBAAC,OAAD;EAAK,WAAW,EAAG,uBAAuB,EAAU;YAApD,CACE,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,SAAD;IAAO,WAAU;IAAmC,OAAO,EAAE,UAAU,eAAe;cAAtF;KACE,kBAAC,WAAD;MAAS,WAAU;gBAAW;MAAkB,CAAA;KAC/C,IACC,kBAAC,SAAD,EAAA,UACE,kBAAC,MAAD,EAAA,UAAA,CACE,kBAAC,MAAD,EAAI,WAAU,QAAS,CAAA,EACtB,EAAU,KAAK,GAAO,MACrB,kBAAC,MAAD;MAEE,OAAM;MACN,WAAU;gBAET;MACE,EALE,OAAO,IAKT,CACL,CACC,EAAA,CAAA,EACC,CAAA,GACN;KACJ,kBAAC,SAAD,EAAA,UACG,EAAK,KAAK,GAAK,MACd,kBAAC,MAAD,EAAA,UAAA,CACE,kBAAC,MAAD;MACE,OAAM;MACN,WAAU;gBAET,EAAI;MACF,CAAA,EACJ,EAAI,MAAM,KAAK,GAAM,MAAM;MAC1B,IAAM,IAAU,EAAK,OAAO,GACtB,IAAQ,EAAU,EAAK,OAAO,EAAQ,EACtC,IAAQ,IAAU,KAAK,IAAI,KAAK;AACtC,aACE,kBAAC,MAAD;OAA2B,WAAU;iBACnC,kBAAC,OAAD;QACE,WAAU;QACV,OAAO,GAAG,EAAI,QAAQ,IAAY,KAAK,MAAM,EAAU,OAAO,GAAG,IAAI,EACnE,EAAK,MACN;kBAJH,CAME,kBAAC,QAAD;SACE,eAAY;SACZ,WAAU;SACV,OAAO;UACL,iBAAiB;UACjB,SAAS,KAAW,IAAU,IAAQ;UACtC,YAAY,IAAU,KAAA,IAAY;UAClC,iBAAiB,IAAU,KAAA,IAAY,GAAG,EAAM;UACjD;SACD,CAAA,EACD,KAAc,EAAK,QAAQ,IAC1B,kBAAC,QAAD;SACE,WAAU;SACV,OAAO,EAAE,OAAO,IAAQ,MAAO,8BAA8B,+BAA+B;mBAE3F,EAAe,EAAK,MAAM;SACtB,CAAA,GACL,KACA;;OACH,EA1BI,QAAQ,EAAE,GAAG,IA0BjB;OAEP,CACC,EAAA,EAzCI,OAAO,IAyCX,CACL,EACI,CAAA;KACF;;GACJ,CAAA,EACL,IACC,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,QAAD,EAAA,UAAM,QAAW,CAAA;IACjB,kBAAC,QAAD;KAAM,WAAU;eA1EJ;MAAC;MAAM;MAAM;MAAK;MAAM;MAAM;MAAE,CA2E7B,KAAK,GAAM,MACtB,kBAAC,QAAD;MAEE,eAAY;MACZ,WAAU;MACV,OAAO;OAAE,iBAAiB;OAAQ,SAAS;OAAM;MACjD,EAJK,EAIL,CACF;KACG,CAAA;IACP,kBAAC,QAAD,EAAA,UAAM,QAAW,CAAA;IACb;OACJ,KACA"}
|