@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,50 @@
|
|
|
1
|
+
import "./lib/palette.js";
|
|
2
|
+
import { cn as e } from "../../utils/cn.js";
|
|
3
|
+
import { jsx as t, jsxs as n } from "react/jsx-runtime";
|
|
4
|
+
//#region src/crewfoundry/components/viz/KanbanBoard.tsx
|
|
5
|
+
function r({ columns: r, columnWidth: i = 288, emptyColumnHint: a = "Nothing here yet", className: o }) {
|
|
6
|
+
return /* @__PURE__ */ t("div", {
|
|
7
|
+
className: e("flex gap-4 overflow-x-auto pb-2", o),
|
|
8
|
+
role: "list",
|
|
9
|
+
"aria-label": "Board columns",
|
|
10
|
+
children: r.map((e) => /* @__PURE__ */ n("section", {
|
|
11
|
+
role: "listitem",
|
|
12
|
+
"aria-label": `${e.title}, ${e.items.length} items`,
|
|
13
|
+
tabIndex: 0,
|
|
14
|
+
className: "flex shrink-0 flex-col rounded-[var(--radius-card)] border border-border-subtle bg-bg-sunken outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]",
|
|
15
|
+
style: { width: i },
|
|
16
|
+
children: [/* @__PURE__ */ n("header", {
|
|
17
|
+
className: "flex items-center gap-2 border-b border-border-subtle px-3 py-2.5",
|
|
18
|
+
children: [
|
|
19
|
+
/* @__PURE__ */ t("span", {
|
|
20
|
+
"aria-hidden": "true",
|
|
21
|
+
className: "h-3.5 w-1 shrink-0 rounded-full",
|
|
22
|
+
style: { backgroundColor: e.accent ?? "var(--color-accent-primary)" }
|
|
23
|
+
}),
|
|
24
|
+
/* @__PURE__ */ t("h3", {
|
|
25
|
+
className: "flex-1 truncate text-sm font-semibold text-text-primary",
|
|
26
|
+
children: e.title
|
|
27
|
+
}),
|
|
28
|
+
/* @__PURE__ */ t("span", {
|
|
29
|
+
className: "inline-flex min-w-5 items-center justify-center rounded-full bg-bg-elevated px-1.5 text-xs font-medium tabular-nums text-text-secondary",
|
|
30
|
+
children: e.items.length
|
|
31
|
+
})
|
|
32
|
+
]
|
|
33
|
+
}), /* @__PURE__ */ t("ul", {
|
|
34
|
+
className: "flex flex-1 flex-col gap-2 p-2",
|
|
35
|
+
children: e.items.length === 0 ? /* @__PURE__ */ t("li", {
|
|
36
|
+
className: "rounded-[var(--radius-input)] border border-dashed border-border-subtle px-3 py-6 text-center text-xs text-text-muted",
|
|
37
|
+
children: a
|
|
38
|
+
}) : e.items.map((e, n) => /* @__PURE__ */ t("li", {
|
|
39
|
+
tabIndex: 0,
|
|
40
|
+
className: "rounded-[var(--radius-input)] outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]",
|
|
41
|
+
children: e
|
|
42
|
+
}, n))
|
|
43
|
+
})]
|
|
44
|
+
}, e.id))
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
export { r as KanbanBoard };
|
|
49
|
+
|
|
50
|
+
//# sourceMappingURL=KanbanBoard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"KanbanBoard.js","names":[],"sources":["../../../../src/crewfoundry/components/viz/KanbanBoard.tsx"],"sourcesContent":["import { cn } from '../../utils/cn';\nimport { ACCENT } from './lib/palette';\nimport type { KanbanColumnData } from './lib/types';\n\nexport interface KanbanBoardProps {\n columns: KanbanColumnData[];\n /** Fixed column width in pixels. */\n columnWidth?: number;\n /** Optional empty-state node rendered inside a column with no items. */\n emptyColumnHint?: string;\n className?: string;\n}\n\n/**\n * Generic columns board. The caller supplies pre-rendered card nodes; the board\n * owns layout, horizontal scroll and keyboard-focusable columns/cards. It never\n * scrolls the page sideways — overflow is contained here.\n */\nexport function KanbanBoard({\n columns,\n columnWidth = 288,\n emptyColumnHint = 'Nothing here yet',\n className,\n}: KanbanBoardProps) {\n return (\n <div\n className={cn('flex gap-4 overflow-x-auto pb-2', className)}\n role=\"list\"\n aria-label=\"Board columns\"\n >\n {columns.map((column) => (\n <section\n key={column.id}\n role=\"listitem\"\n aria-label={`${column.title}, ${column.items.length} items`}\n tabIndex={0}\n className=\"flex shrink-0 flex-col rounded-[var(--radius-card)] border border-border-subtle bg-bg-sunken outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n style={{ width: columnWidth }}\n >\n <header className=\"flex items-center gap-2 border-b border-border-subtle px-3 py-2.5\">\n <span\n aria-hidden=\"true\"\n className=\"h-3.5 w-1 shrink-0 rounded-full\"\n style={{ backgroundColor: column.accent ?? ACCENT }}\n />\n <h3 className=\"flex-1 truncate text-sm font-semibold text-text-primary\">\n {column.title}\n </h3>\n <span className=\"inline-flex min-w-5 items-center justify-center rounded-full bg-bg-elevated px-1.5 text-xs font-medium tabular-nums text-text-secondary\">\n {column.items.length}\n </span>\n </header>\n <ul className=\"flex flex-1 flex-col gap-2 p-2\">\n {column.items.length === 0 ? (\n <li className=\"rounded-[var(--radius-input)] border border-dashed border-border-subtle px-3 py-6 text-center text-xs text-text-muted\">\n {emptyColumnHint}\n </li>\n ) : (\n column.items.map((item, i) => (\n <li\n key={i}\n tabIndex={0}\n className=\"rounded-[var(--radius-input)] outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n {item}\n </li>\n ))\n )}\n </ul>\n </section>\n ))}\n </div>\n );\n}\n"],"mappings":";;;;AAkBA,SAAgB,EAAY,EAC1B,YACA,iBAAc,KACd,qBAAkB,oBAClB,gBACmB;AACnB,QACE,kBAAC,OAAD;EACE,WAAW,EAAG,mCAAmC,EAAU;EAC3D,MAAK;EACL,cAAW;YAEV,EAAQ,KAAK,MACZ,kBAAC,WAAD;GAEE,MAAK;GACL,cAAY,GAAG,EAAO,MAAM,IAAI,EAAO,MAAM,OAAO;GACpD,UAAU;GACV,WAAU;GACV,OAAO,EAAE,OAAO,GAAa;aAN/B,CAQE,kBAAC,UAAD;IAAQ,WAAU;cAAlB;KACE,kBAAC,QAAD;MACE,eAAY;MACZ,WAAU;MACV,OAAO,EAAE,iBAAiB,EAAO,UAAA,+BAAkB;MACnD,CAAA;KACF,kBAAC,MAAD;MAAI,WAAU;gBACX,EAAO;MACL,CAAA;KACL,kBAAC,QAAD;MAAM,WAAU;gBACb,EAAO,MAAM;MACT,CAAA;KACA;OACT,kBAAC,MAAD;IAAI,WAAU;cACX,EAAO,MAAM,WAAW,IACvB,kBAAC,MAAD;KAAI,WAAU;eACX;KACE,CAAA,GAEL,EAAO,MAAM,KAAK,GAAM,MACtB,kBAAC,MAAD;KAEE,UAAU;KACV,WAAU;eAET;KACE,EALE,EAKF,CACL;IAED,CAAA,CACG;KArCH,EAAO,GAqCJ,CACV;EACE,CAAA"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { chartPalette as e } from "./lib/palette.js";
|
|
2
|
+
import { useMounted as t, usePrefersReducedMotion as n } from "./lib/hooks.js";
|
|
3
|
+
import { buildLinePath as r } from "./lib/geometry.js";
|
|
4
|
+
import { CartesianFrame as i } from "./lib/CartesianFrame.js";
|
|
5
|
+
import { formatCompact as a } from "./lib/format.js";
|
|
6
|
+
import { Fragment as o, jsx as s, jsxs as c } from "react/jsx-runtime";
|
|
7
|
+
//#region src/crewfoundry/components/viz/LineChart.tsx
|
|
8
|
+
function l({ series: l, compare: u, seriesLabel: d = "Series", compareLabel: f = "Compare", height: p = 200, valueFormatter: m = a, title: h, className: g }) {
|
|
9
|
+
let _ = t(), v = n();
|
|
10
|
+
if (l.length === 0) return null;
|
|
11
|
+
let y = l.map((e) => e.label), b = l.map((e) => e.value), x = Array.isArray(u) && u.length > 0, S = x ? [...b, ...u] : b, C = Math.min(...S), w = Math.max(...S), T = w - C || Math.abs(w) || 1, E = [C - T * .12, w + T * .12], [D, O] = e(2), k = h ?? `Line chart of ${l.length} points`, A = {
|
|
12
|
+
strokeDasharray: 1,
|
|
13
|
+
strokeDashoffset: _ || v ? 0 : 1,
|
|
14
|
+
transition: v ? void 0 : "stroke-dashoffset 900ms ease"
|
|
15
|
+
};
|
|
16
|
+
return /* @__PURE__ */ c("div", {
|
|
17
|
+
className: g,
|
|
18
|
+
children: [x ? /* @__PURE__ */ c("div", {
|
|
19
|
+
className: "mb-2 flex flex-wrap items-center gap-4 text-xs text-text-secondary",
|
|
20
|
+
children: [/* @__PURE__ */ c("span", {
|
|
21
|
+
className: "inline-flex items-center gap-1.5",
|
|
22
|
+
children: [/* @__PURE__ */ s("span", {
|
|
23
|
+
"aria-hidden": "true",
|
|
24
|
+
className: "inline-block h-2 w-3 rounded-full",
|
|
25
|
+
style: { backgroundColor: D }
|
|
26
|
+
}), d]
|
|
27
|
+
}), /* @__PURE__ */ c("span", {
|
|
28
|
+
className: "inline-flex items-center gap-1.5",
|
|
29
|
+
children: [/* @__PURE__ */ s("span", {
|
|
30
|
+
"aria-hidden": "true",
|
|
31
|
+
className: "inline-block h-2 w-3 rounded-full",
|
|
32
|
+
style: { backgroundColor: O }
|
|
33
|
+
}), f]
|
|
34
|
+
})]
|
|
35
|
+
}) : null, /* @__PURE__ */ s(i, {
|
|
36
|
+
labels: y,
|
|
37
|
+
domain: E,
|
|
38
|
+
height: p,
|
|
39
|
+
title: k,
|
|
40
|
+
formatValue: m,
|
|
41
|
+
tooltip: (e) => /* @__PURE__ */ c("span", {
|
|
42
|
+
className: "flex flex-col gap-0.5",
|
|
43
|
+
children: [
|
|
44
|
+
/* @__PURE__ */ s("span", {
|
|
45
|
+
className: "text-text-secondary",
|
|
46
|
+
children: y[e]
|
|
47
|
+
}),
|
|
48
|
+
/* @__PURE__ */ c("span", {
|
|
49
|
+
className: "font-semibold tabular-nums",
|
|
50
|
+
style: { color: D },
|
|
51
|
+
children: [
|
|
52
|
+
d,
|
|
53
|
+
": ",
|
|
54
|
+
m(b[e])
|
|
55
|
+
]
|
|
56
|
+
}),
|
|
57
|
+
x && u ? /* @__PURE__ */ c("span", {
|
|
58
|
+
className: "font-semibold tabular-nums",
|
|
59
|
+
style: { color: O },
|
|
60
|
+
children: [
|
|
61
|
+
f,
|
|
62
|
+
": ",
|
|
63
|
+
m(u[e])
|
|
64
|
+
]
|
|
65
|
+
}) : null
|
|
66
|
+
]
|
|
67
|
+
}),
|
|
68
|
+
children: ({ px: e, py: t, activeIndex: n }) => {
|
|
69
|
+
let i = b.map((n, r) => ({
|
|
70
|
+
x: e(r),
|
|
71
|
+
y: t(n)
|
|
72
|
+
})), a = x && u ? u.map((n, r) => ({
|
|
73
|
+
x: e(r),
|
|
74
|
+
y: t(n)
|
|
75
|
+
})) : [];
|
|
76
|
+
return /* @__PURE__ */ c("g", { children: [
|
|
77
|
+
a.length > 0 ? /* @__PURE__ */ s("path", {
|
|
78
|
+
d: r(a),
|
|
79
|
+
fill: "none",
|
|
80
|
+
stroke: O,
|
|
81
|
+
strokeWidth: 2,
|
|
82
|
+
strokeLinecap: "round",
|
|
83
|
+
strokeLinejoin: "round",
|
|
84
|
+
strokeOpacity: .85,
|
|
85
|
+
vectorEffect: "non-scaling-stroke",
|
|
86
|
+
pathLength: 1,
|
|
87
|
+
style: A
|
|
88
|
+
}) : null,
|
|
89
|
+
/* @__PURE__ */ s("path", {
|
|
90
|
+
d: r(i),
|
|
91
|
+
fill: "none",
|
|
92
|
+
stroke: D,
|
|
93
|
+
strokeWidth: 2.25,
|
|
94
|
+
strokeLinecap: "round",
|
|
95
|
+
strokeLinejoin: "round",
|
|
96
|
+
vectorEffect: "non-scaling-stroke",
|
|
97
|
+
pathLength: 1,
|
|
98
|
+
style: A
|
|
99
|
+
}),
|
|
100
|
+
n === null ? null : /* @__PURE__ */ c(o, { children: [/* @__PURE__ */ s("circle", {
|
|
101
|
+
cx: e(n),
|
|
102
|
+
cy: t(b[n]),
|
|
103
|
+
r: 3.5,
|
|
104
|
+
fill: D,
|
|
105
|
+
stroke: "var(--color-bg-surface)",
|
|
106
|
+
strokeWidth: 1.5
|
|
107
|
+
}), x && u ? /* @__PURE__ */ s("circle", {
|
|
108
|
+
cx: e(n),
|
|
109
|
+
cy: t(u[n]),
|
|
110
|
+
r: 3.5,
|
|
111
|
+
fill: O,
|
|
112
|
+
stroke: "var(--color-bg-surface)",
|
|
113
|
+
strokeWidth: 1.5
|
|
114
|
+
}) : null] })
|
|
115
|
+
] });
|
|
116
|
+
}
|
|
117
|
+
})]
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
//#endregion
|
|
121
|
+
export { l as LineChart };
|
|
122
|
+
|
|
123
|
+
//# sourceMappingURL=LineChart.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LineChart.js","names":[],"sources":["../../../../src/crewfoundry/components/viz/LineChart.tsx"],"sourcesContent":["import { chartPalette } from './lib/palette';\nimport { CartesianFrame } from './lib/CartesianFrame';\nimport { buildLinePath } from './lib/geometry';\nimport { formatCompact } from './lib/format';\nimport { useMounted, usePrefersReducedMotion } from './lib/hooks';\nimport type { LabeledValue } from './lib/types';\n\nexport interface LineChartProps {\n /** Primary series (its labels drive the x-axis). */\n series: LabeledValue[];\n /** Optional second series, aligned to the primary series by index. */\n compare?: number[];\n seriesLabel?: string;\n compareLabel?: string;\n height?: number;\n valueFormatter?: (value: number) => string;\n title?: string;\n className?: string;\n}\n\n/**\n * Multi-point line chart (trend / attrition) with an optional second series,\n * shared axes + hover, and an animated draw-in.\n */\nexport function LineChart({\n series,\n compare,\n seriesLabel = 'Series',\n compareLabel = 'Compare',\n height = 200,\n valueFormatter = formatCompact,\n title,\n className,\n}: LineChartProps) {\n const mounted = useMounted();\n const reduced = usePrefersReducedMotion();\n\n if (series.length === 0) {\n return null;\n }\n\n const labels = series.map((s) => s.label);\n const primary = series.map((s) => s.value);\n const hasCompare = Array.isArray(compare) && compare.length > 0;\n const all = hasCompare ? [...primary, ...compare] : primary;\n const min = Math.min(...all);\n const max = Math.max(...all);\n const span = max - min || Math.abs(max) || 1;\n const domain: [number, number] = [min - span * 0.12, max + span * 0.12];\n const [primaryColor, compareColor] = chartPalette(2);\n const chartTitle = title ?? `Line chart of ${series.length} points`;\n\n const drawStyle = {\n strokeDasharray: 1,\n strokeDashoffset: mounted || reduced ? 0 : 1,\n transition: reduced ? undefined : 'stroke-dashoffset 900ms ease',\n } as const;\n\n return (\n <div className={className}>\n {hasCompare ? (\n <div className=\"mb-2 flex flex-wrap items-center gap-4 text-xs text-text-secondary\">\n <span className=\"inline-flex items-center gap-1.5\">\n <span\n aria-hidden=\"true\"\n className=\"inline-block h-2 w-3 rounded-full\"\n style={{ backgroundColor: primaryColor }}\n />\n {seriesLabel}\n </span>\n <span className=\"inline-flex items-center gap-1.5\">\n <span\n aria-hidden=\"true\"\n className=\"inline-block h-2 w-3 rounded-full\"\n style={{ backgroundColor: compareColor }}\n />\n {compareLabel}\n </span>\n </div>\n ) : null}\n <CartesianFrame\n labels={labels}\n domain={domain}\n height={height}\n title={chartTitle}\n formatValue={valueFormatter}\n tooltip={(i) => (\n <span className=\"flex flex-col gap-0.5\">\n <span className=\"text-text-secondary\">{labels[i]}</span>\n <span className=\"font-semibold tabular-nums\" style={{ color: primaryColor }}>\n {seriesLabel}: {valueFormatter(primary[i])}\n </span>\n {hasCompare && compare ? (\n <span className=\"font-semibold tabular-nums\" style={{ color: compareColor }}>\n {compareLabel}: {valueFormatter(compare[i])}\n </span>\n ) : null}\n </span>\n )}\n >\n {({ px, py, activeIndex }) => {\n const primaryPoints = primary.map((v, i) => ({ x: px(i), y: py(v) }));\n const comparePoints = hasCompare && compare ? compare.map((v, i) => ({ x: px(i), y: py(v) })) : [];\n return (\n <g>\n {comparePoints.length > 0 ? (\n <path\n d={buildLinePath(comparePoints)}\n fill=\"none\"\n stroke={compareColor}\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeOpacity={0.85}\n vectorEffect=\"non-scaling-stroke\"\n pathLength={1}\n style={drawStyle}\n />\n ) : null}\n <path\n d={buildLinePath(primaryPoints)}\n fill=\"none\"\n stroke={primaryColor}\n strokeWidth={2.25}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n vectorEffect=\"non-scaling-stroke\"\n pathLength={1}\n style={drawStyle}\n />\n {activeIndex !== null ? (\n <>\n <circle\n cx={px(activeIndex)}\n cy={py(primary[activeIndex])}\n r={3.5}\n fill={primaryColor}\n stroke=\"var(--color-bg-surface)\"\n strokeWidth={1.5}\n />\n {hasCompare && compare ? (\n <circle\n cx={px(activeIndex)}\n cy={py(compare[activeIndex])}\n r={3.5}\n fill={compareColor}\n stroke=\"var(--color-bg-surface)\"\n strokeWidth={1.5}\n />\n ) : null}\n </>\n ) : null}\n </g>\n );\n }}\n </CartesianFrame>\n </div>\n );\n}\n"],"mappings":";;;;;;;AAwBA,SAAgB,EAAU,EACxB,WACA,YACA,iBAAc,UACd,kBAAe,WACf,YAAS,KACT,oBAAiB,GACjB,UACA,gBACiB;CACjB,IAAM,IAAU,GAAY,EACtB,IAAU,GAAyB;AAEzC,KAAI,EAAO,WAAW,EACpB,QAAO;CAGT,IAAM,IAAS,EAAO,KAAK,MAAM,EAAE,MAAM,EACnC,IAAU,EAAO,KAAK,MAAM,EAAE,MAAM,EACpC,IAAa,MAAM,QAAQ,EAAQ,IAAI,EAAQ,SAAS,GACxD,IAAM,IAAa,CAAC,GAAG,GAAS,GAAG,EAAQ,GAAG,GAC9C,IAAM,KAAK,IAAI,GAAG,EAAI,EACtB,IAAM,KAAK,IAAI,GAAG,EAAI,EACtB,IAAO,IAAM,KAAO,KAAK,IAAI,EAAI,IAAI,GACrC,IAA2B,CAAC,IAAM,IAAO,KAAM,IAAM,IAAO,IAAK,EACjE,CAAC,GAAc,KAAgB,EAAa,EAAE,EAC9C,IAAa,KAAS,iBAAiB,EAAO,OAAO,UAErD,IAAY;EAChB,iBAAiB;EACjB,kBAAkB,KAAW,IAAU,IAAI;EAC3C,YAAY,IAAU,KAAA,IAAY;EACnC;AAED,QACE,kBAAC,OAAD;EAAgB;YAAhB,CACG,IACC,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,QAAD;IAAM,WAAU;cAAhB,CACE,kBAAC,QAAD;KACE,eAAY;KACZ,WAAU;KACV,OAAO,EAAE,iBAAiB,GAAc;KACxC,CAAA,EACD,EACI;OACP,kBAAC,QAAD;IAAM,WAAU;cAAhB,CACE,kBAAC,QAAD;KACE,eAAY;KACZ,WAAU;KACV,OAAO,EAAE,iBAAiB,GAAc;KACxC,CAAA,EACD,EACI;MACH;OACJ,MACJ,kBAAC,GAAD;GACU;GACA;GACA;GACR,OAAO;GACP,aAAa;GACb,UAAU,MACR,kBAAC,QAAD;IAAM,WAAU;cAAhB;KACE,kBAAC,QAAD;MAAM,WAAU;gBAAuB,EAAO;MAAU,CAAA;KACxD,kBAAC,QAAD;MAAM,WAAU;MAA6B,OAAO,EAAE,OAAO,GAAc;gBAA3E;OACG;OAAY;OAAG,EAAe,EAAQ,GAAG;OACrC;;KACN,KAAc,IACb,kBAAC,QAAD;MAAM,WAAU;MAA6B,OAAO,EAAE,OAAO,GAAc;gBAA3E;OACG;OAAa;OAAG,EAAe,EAAQ,GAAG;OACtC;UACL;KACC;;cAGP,EAAE,OAAI,OAAI,qBAAkB;IAC5B,IAAM,IAAgB,EAAQ,KAAK,GAAG,OAAO;KAAE,GAAG,EAAG,EAAE;KAAE,GAAG,EAAG,EAAE;KAAE,EAAE,EAC/D,IAAgB,KAAc,IAAU,EAAQ,KAAK,GAAG,OAAO;KAAE,GAAG,EAAG,EAAE;KAAE,GAAG,EAAG,EAAE;KAAE,EAAE,GAAG,EAAE;AAClG,WACE,kBAAC,KAAD,EAAA,UAAA;KACG,EAAc,SAAS,IACtB,kBAAC,QAAD;MACE,GAAG,EAAc,EAAc;MAC/B,MAAK;MACL,QAAQ;MACR,aAAa;MACb,eAAc;MACd,gBAAe;MACf,eAAe;MACf,cAAa;MACb,YAAY;MACZ,OAAO;MACP,CAAA,GACA;KACJ,kBAAC,QAAD;MACE,GAAG,EAAc,EAAc;MAC/B,MAAK;MACL,QAAQ;MACR,aAAa;MACb,eAAc;MACd,gBAAe;MACf,cAAa;MACb,YAAY;MACZ,OAAO;MACP,CAAA;KACD,MAAgB,OAqBb,OApBF,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,UAAD;MACE,IAAI,EAAG,EAAY;MACnB,IAAI,EAAG,EAAQ,GAAa;MAC5B,GAAG;MACH,MAAM;MACN,QAAO;MACP,aAAa;MACb,CAAA,EACD,KAAc,IACb,kBAAC,UAAD;MACE,IAAI,EAAG,EAAY;MACnB,IAAI,EAAG,EAAQ,GAAa;MAC5B,GAAG;MACH,MAAM;MACN,QAAO;MACP,aAAa;MACb,CAAA,GACA,KACH,EAAA,CAAA;KAEH,EAAA,CAAA;;GAGO,CAAA,CACb"}
|
|
@@ -0,0 +1,83 @@
|
|
|
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/OrgTree.tsx
|
|
5
|
+
var i = "var(--color-border-strong)", a = 16;
|
|
6
|
+
function o({ node: e }) {
|
|
7
|
+
return /* @__PURE__ */ r("div", {
|
|
8
|
+
className: "flex w-40 flex-col items-center gap-1 rounded-[var(--radius-card)] border border-border-subtle bg-bg-surface px-3 py-2.5 text-center shadow-elevation-1",
|
|
9
|
+
children: [
|
|
10
|
+
/* @__PURE__ */ n(t, {
|
|
11
|
+
name: e.name,
|
|
12
|
+
src: e.avatarUrl,
|
|
13
|
+
size: 36
|
|
14
|
+
}),
|
|
15
|
+
/* @__PURE__ */ n("span", {
|
|
16
|
+
className: "mt-0.5 truncate text-sm font-medium text-text-primary",
|
|
17
|
+
title: e.name,
|
|
18
|
+
children: e.name
|
|
19
|
+
}),
|
|
20
|
+
e.role ? /* @__PURE__ */ n("span", {
|
|
21
|
+
className: "truncate text-xs text-text-muted",
|
|
22
|
+
title: e.role,
|
|
23
|
+
children: e.role
|
|
24
|
+
}) : null
|
|
25
|
+
]
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
function s({ node: e }) {
|
|
29
|
+
let t = e.children ?? [];
|
|
30
|
+
return /* @__PURE__ */ r("li", {
|
|
31
|
+
className: "inline-flex list-none flex-col items-center",
|
|
32
|
+
children: [/* @__PURE__ */ n(o, { node: e }), t.length > 0 ? /* @__PURE__ */ r("div", {
|
|
33
|
+
className: "flex flex-col items-center",
|
|
34
|
+
children: [/* @__PURE__ */ n("span", {
|
|
35
|
+
"aria-hidden": "true",
|
|
36
|
+
style: {
|
|
37
|
+
width: 1,
|
|
38
|
+
height: a,
|
|
39
|
+
backgroundColor: i
|
|
40
|
+
}
|
|
41
|
+
}), /* @__PURE__ */ n("ul", {
|
|
42
|
+
className: "flex items-start",
|
|
43
|
+
children: t.map((e, o) => /* @__PURE__ */ r("li", {
|
|
44
|
+
className: "relative flex flex-col items-center px-3",
|
|
45
|
+
children: [
|
|
46
|
+
/* @__PURE__ */ n("span", {
|
|
47
|
+
"aria-hidden": "true",
|
|
48
|
+
className: "absolute top-0 h-px",
|
|
49
|
+
style: {
|
|
50
|
+
left: o === 0 ? "50%" : 0,
|
|
51
|
+
right: o === t.length - 1 ? "50%" : 0,
|
|
52
|
+
backgroundColor: i
|
|
53
|
+
}
|
|
54
|
+
}),
|
|
55
|
+
/* @__PURE__ */ n("span", {
|
|
56
|
+
"aria-hidden": "true",
|
|
57
|
+
style: {
|
|
58
|
+
width: 1,
|
|
59
|
+
height: a,
|
|
60
|
+
backgroundColor: i
|
|
61
|
+
}
|
|
62
|
+
}),
|
|
63
|
+
/* @__PURE__ */ n(s, { node: e })
|
|
64
|
+
]
|
|
65
|
+
}, e.id ?? o))
|
|
66
|
+
})]
|
|
67
|
+
}) : null]
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
function c({ root: t, className: r }) {
|
|
71
|
+
return /* @__PURE__ */ n("div", {
|
|
72
|
+
className: e("overflow-x-auto pb-2", r),
|
|
73
|
+
"aria-label": "Organization chart",
|
|
74
|
+
children: /* @__PURE__ */ n("ul", {
|
|
75
|
+
className: "inline-flex min-w-min justify-center p-2",
|
|
76
|
+
children: /* @__PURE__ */ n(s, { node: t })
|
|
77
|
+
})
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
81
|
+
export { c as OrgTree };
|
|
82
|
+
|
|
83
|
+
//# sourceMappingURL=OrgTree.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OrgTree.js","names":[],"sources":["../../../../src/crewfoundry/components/viz/OrgTree.tsx"],"sourcesContent":["import { cn } from '../../utils/cn';\nimport { VizAvatar } from './lib/Avatar';\n\nexport interface OrgNode {\n id?: string;\n name: string;\n role?: string;\n avatarUrl?: string;\n children?: OrgNode[];\n}\n\nexport interface OrgTreeProps {\n root: OrgNode;\n className?: string;\n}\n\nconst CONNECTOR = 'var(--color-border-strong)';\nconst TRUNK_HEIGHT = 16;\n\nfunction NodeCard({ node }: { node: OrgNode }) {\n return (\n <div className=\"flex w-40 flex-col items-center gap-1 rounded-[var(--radius-card)] border border-border-subtle bg-bg-surface px-3 py-2.5 text-center shadow-elevation-1\">\n <VizAvatar name={node.name} src={node.avatarUrl} size={36} />\n <span className=\"mt-0.5 truncate text-sm font-medium text-text-primary\" title={node.name}>\n {node.name}\n </span>\n {node.role ? (\n <span className=\"truncate text-xs text-text-muted\" title={node.role}>\n {node.role}\n </span>\n ) : null}\n </div>\n );\n}\n\nfunction Subtree({ node }: { node: OrgNode }) {\n const children = node.children ?? [];\n return (\n <li className=\"inline-flex list-none flex-col items-center\">\n <NodeCard node={node} />\n {children.length > 0 ? (\n <div className=\"flex flex-col items-center\">\n {/* trunk from this node down to the connector bus */}\n <span\n aria-hidden=\"true\"\n style={{ width: 1, height: TRUNK_HEIGHT, backgroundColor: CONNECTOR }}\n />\n <ul className=\"flex items-start\">\n {children.map((child, i) => (\n <li key={child.id ?? i} className=\"relative flex flex-col items-center px-3\">\n {/* horizontal bus segment (trimmed at the outer children) */}\n <span\n aria-hidden=\"true\"\n className=\"absolute top-0 h-px\"\n style={{\n left: i === 0 ? '50%' : 0,\n right: i === children.length - 1 ? '50%' : 0,\n backgroundColor: CONNECTOR,\n }}\n />\n {/* branch from the bus down to the child */}\n <span\n aria-hidden=\"true\"\n style={{ width: 1, height: TRUNK_HEIGHT, backgroundColor: CONNECTOR }}\n />\n <Subtree node={child} />\n </li>\n ))}\n </ul>\n </div>\n ) : null}\n </li>\n );\n}\n\n/** Simple top-down org tree with connected nodes; scrolls horizontally when wide. */\nexport function OrgTree({ root, className }: OrgTreeProps) {\n return (\n <div className={cn('overflow-x-auto pb-2', className)} aria-label=\"Organization chart\">\n <ul className=\"inline-flex min-w-min justify-center p-2\">\n <Subtree node={root} />\n </ul>\n </div>\n );\n}\n"],"mappings":";;;;AAgBA,IAAM,IAAY,8BACZ,IAAe;AAErB,SAAS,EAAS,EAAE,WAA2B;AAC7C,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD;IAAW,MAAM,EAAK;IAAM,KAAK,EAAK;IAAW,MAAM;IAAM,CAAA;GAC7D,kBAAC,QAAD;IAAM,WAAU;IAAwD,OAAO,EAAK;cACjF,EAAK;IACD,CAAA;GACN,EAAK,OACJ,kBAAC,QAAD;IAAM,WAAU;IAAmC,OAAO,EAAK;cAC5D,EAAK;IACD,CAAA,GACL;GACA;;;AAIV,SAAS,EAAQ,EAAE,WAA2B;CAC5C,IAAM,IAAW,EAAK,YAAY,EAAE;AACpC,QACE,kBAAC,MAAD;EAAI,WAAU;YAAd,CACE,kBAAC,GAAD,EAAgB,SAAQ,CAAA,EACvB,EAAS,SAAS,IACjB,kBAAC,OAAD;GAAK,WAAU;aAAf,CAEE,kBAAC,QAAD;IACE,eAAY;IACZ,OAAO;KAAE,OAAO;KAAG,QAAQ;KAAc,iBAAiB;KAAW;IACrE,CAAA,EACF,kBAAC,MAAD;IAAI,WAAU;cACX,EAAS,KAAK,GAAO,MACpB,kBAAC,MAAD;KAAwB,WAAU;eAAlC;MAEE,kBAAC,QAAD;OACE,eAAY;OACZ,WAAU;OACV,OAAO;QACL,MAAM,MAAM,IAAI,QAAQ;QACxB,OAAO,MAAM,EAAS,SAAS,IAAI,QAAQ;QAC3C,iBAAiB;QAClB;OACD,CAAA;MAEF,kBAAC,QAAD;OACE,eAAY;OACZ,OAAO;QAAE,OAAO;QAAG,QAAQ;QAAc,iBAAiB;QAAW;OACrE,CAAA;MACF,kBAAC,GAAD,EAAS,MAAM,GAAS,CAAA;MACrB;OAjBI,EAAM,MAAM,EAiBhB,CACL;IACC,CAAA,CACD;OACJ,KACD;;;AAKT,SAAgB,EAAQ,EAAE,SAAM,gBAA2B;AACzD,QACE,kBAAC,OAAD;EAAK,WAAW,EAAG,wBAAwB,EAAU;EAAE,cAAW;YAChE,kBAAC,MAAD;GAAI,WAAU;aACZ,kBAAC,GAAD,EAAS,MAAM,GAAQ,CAAA;GACpB,CAAA;EACD,CAAA"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { ACCENT as e } from "./lib/palette.js";
|
|
2
|
+
import { cn as t } from "../../utils/cn.js";
|
|
3
|
+
import { Check as n, Lock as r } from "lucide-react";
|
|
4
|
+
import { jsx as i, jsxs as a } from "react/jsx-runtime";
|
|
5
|
+
//#region src/crewfoundry/components/viz/PathSteps.tsx
|
|
6
|
+
function o({ steps: o, className: s }) {
|
|
7
|
+
return o.length === 0 ? null : /* @__PURE__ */ i("ol", {
|
|
8
|
+
className: t("flex items-start overflow-x-auto pb-1", s),
|
|
9
|
+
"aria-label": "Learning path",
|
|
10
|
+
children: o.map((s, c) => {
|
|
11
|
+
let l = s.state ?? "todo";
|
|
12
|
+
return /* @__PURE__ */ a("li", {
|
|
13
|
+
className: "flex min-w-[5.5rem] flex-1 flex-col items-center",
|
|
14
|
+
"aria-current": l === "active" ? "step" : void 0,
|
|
15
|
+
children: [
|
|
16
|
+
/* @__PURE__ */ a("div", {
|
|
17
|
+
className: "flex w-full items-center",
|
|
18
|
+
children: [
|
|
19
|
+
c > 0 ? /* @__PURE__ */ i("span", {
|
|
20
|
+
"aria-hidden": "true",
|
|
21
|
+
className: "h-0.5 flex-1 rounded-full",
|
|
22
|
+
style: { backgroundColor: l === "done" ? e : "var(--color-border-subtle)" }
|
|
23
|
+
}) : /* @__PURE__ */ i("span", {
|
|
24
|
+
className: "flex-1",
|
|
25
|
+
"aria-hidden": "true"
|
|
26
|
+
}),
|
|
27
|
+
/* @__PURE__ */ i("span", {
|
|
28
|
+
className: t("flex h-9 w-9 shrink-0 items-center justify-center rounded-full border-2 text-xs font-semibold transition-colors", l === "done" && "border-transparent text-text-inverse", l === "active" && "bg-bg-surface shadow-elevation-1", l === "todo" && "border-border-default bg-bg-surface text-text-secondary", l === "locked" && "border-border-subtle bg-bg-sunken text-text-muted"),
|
|
29
|
+
style: l === "done" ? { backgroundColor: e } : l === "active" ? {
|
|
30
|
+
borderColor: e,
|
|
31
|
+
color: e
|
|
32
|
+
} : void 0,
|
|
33
|
+
children: l === "done" ? /* @__PURE__ */ i(n, {
|
|
34
|
+
"aria-hidden": "true",
|
|
35
|
+
className: "h-4 w-4"
|
|
36
|
+
}) : l === "locked" ? /* @__PURE__ */ i(r, {
|
|
37
|
+
"aria-hidden": "true",
|
|
38
|
+
className: "h-3.5 w-3.5"
|
|
39
|
+
}) : c + 1
|
|
40
|
+
}),
|
|
41
|
+
c < o.length - 1 ? /* @__PURE__ */ i("span", {
|
|
42
|
+
"aria-hidden": "true",
|
|
43
|
+
className: "h-0.5 flex-1 rounded-full",
|
|
44
|
+
style: { backgroundColor: l === "done" ? e : "var(--color-border-subtle)" }
|
|
45
|
+
}) : /* @__PURE__ */ i("span", {
|
|
46
|
+
className: "flex-1",
|
|
47
|
+
"aria-hidden": "true"
|
|
48
|
+
})
|
|
49
|
+
]
|
|
50
|
+
}),
|
|
51
|
+
/* @__PURE__ */ i("span", {
|
|
52
|
+
className: t("mt-2 max-w-[7rem] truncate px-1 text-center text-xs", l === "locked" ? "text-text-muted" : "font-medium text-text-primary"),
|
|
53
|
+
title: s.label,
|
|
54
|
+
children: s.label
|
|
55
|
+
}),
|
|
56
|
+
s.meta ? /* @__PURE__ */ i("span", {
|
|
57
|
+
className: "mt-0.5 px-1 text-center text-[11px] text-text-muted",
|
|
58
|
+
children: s.meta
|
|
59
|
+
}) : null
|
|
60
|
+
]
|
|
61
|
+
}, `${s.label}-${c}`);
|
|
62
|
+
})
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
//#endregion
|
|
66
|
+
export { o as PathSteps };
|
|
67
|
+
|
|
68
|
+
//# sourceMappingURL=PathSteps.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PathSteps.js","names":[],"sources":["../../../../src/crewfoundry/components/viz/PathSteps.tsx"],"sourcesContent":["import { Check, Lock } from 'lucide-react';\nimport { cn } from '../../utils/cn';\nimport { ACCENT } from './lib/palette';\n\nexport type PathStepState = 'done' | 'active' | 'todo' | 'locked';\n\nexport interface PathStep {\n label: string;\n meta?: string;\n state?: PathStepState;\n}\n\nexport interface PathStepsProps {\n steps: PathStep[];\n className?: string;\n}\n\n/**\n * A sequence of course nodes (circles + labels + connectors) for a learning\n * path. Each node carries its own state; the connector into a node is filled\n * once that node is complete. Scrolls horizontally on narrow screens.\n */\nexport function PathSteps({ steps, className }: PathStepsProps) {\n if (steps.length === 0) {\n return null;\n }\n return (\n <ol className={cn('flex items-start overflow-x-auto pb-1', className)} aria-label=\"Learning path\">\n {steps.map((step, i) => {\n const state = step.state ?? 'todo';\n const connectorFilled = state === 'done';\n return (\n <li\n key={`${step.label}-${i}`}\n className=\"flex min-w-[5.5rem] flex-1 flex-col items-center\"\n aria-current={state === 'active' ? 'step' : undefined}\n >\n <div className=\"flex w-full items-center\">\n {i > 0 ? (\n <span\n aria-hidden=\"true\"\n className=\"h-0.5 flex-1 rounded-full\"\n style={{ backgroundColor: connectorFilled ? ACCENT : 'var(--color-border-subtle)' }}\n />\n ) : (\n <span className=\"flex-1\" aria-hidden=\"true\" />\n )}\n <span\n className={cn(\n 'flex h-9 w-9 shrink-0 items-center justify-center rounded-full border-2 text-xs font-semibold transition-colors',\n state === 'done' && 'border-transparent text-text-inverse',\n state === 'active' && 'bg-bg-surface shadow-elevation-1',\n state === 'todo' && 'border-border-default bg-bg-surface text-text-secondary',\n state === 'locked' && 'border-border-subtle bg-bg-sunken text-text-muted',\n )}\n style={\n state === 'done'\n ? { backgroundColor: ACCENT }\n : state === 'active'\n ? { borderColor: ACCENT, color: ACCENT }\n : undefined\n }\n >\n {state === 'done' ? (\n <Check aria-hidden=\"true\" className=\"h-4 w-4\" />\n ) : state === 'locked' ? (\n <Lock aria-hidden=\"true\" className=\"h-3.5 w-3.5\" />\n ) : (\n i + 1\n )}\n </span>\n {i < steps.length - 1 ? (\n <span\n aria-hidden=\"true\"\n className=\"h-0.5 flex-1 rounded-full\"\n style={{\n backgroundColor:\n state === 'done' ? ACCENT : 'var(--color-border-subtle)',\n }}\n />\n ) : (\n <span className=\"flex-1\" aria-hidden=\"true\" />\n )}\n </div>\n <span\n className={cn(\n 'mt-2 max-w-[7rem] truncate px-1 text-center text-xs',\n state === 'locked' ? 'text-text-muted' : 'font-medium text-text-primary',\n )}\n title={step.label}\n >\n {step.label}\n </span>\n {step.meta ? (\n <span className=\"mt-0.5 px-1 text-center text-[11px] text-text-muted\">{step.meta}</span>\n ) : null}\n </li>\n );\n })}\n </ol>\n );\n}\n"],"mappings":";;;;;AAsBA,SAAgB,EAAU,EAAE,UAAO,gBAA6B;AAI9D,QAHI,EAAM,WAAW,IACZ,OAGP,kBAAC,MAAD;EAAI,WAAW,EAAG,yCAAyC,EAAU;EAAE,cAAW;YAC/E,EAAM,KAAK,GAAM,MAAM;GACtB,IAAM,IAAQ,EAAK,SAAS;AAE5B,UACE,kBAAC,MAAD;IAEE,WAAU;IACV,gBAAc,MAAU,WAAW,SAAS,KAAA;cAH9C;KAKE,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACG,IAAI,IACH,kBAAC,QAAD;QACE,eAAY;QACZ,WAAU;QACV,OAAO,EAAE,iBAZK,MAAU,SAYoB,IAAS,8BAA8B;QACnF,CAAA,GAEF,kBAAC,QAAD;QAAM,WAAU;QAAS,eAAY;QAAS,CAAA;OAEhD,kBAAC,QAAD;QACE,WAAW,EACT,mHACA,MAAU,UAAU,wCACpB,MAAU,YAAY,oCACtB,MAAU,UAAU,2DACpB,MAAU,YAAY,oDACvB;QACD,OACE,MAAU,SACN,EAAE,iBAAiB,GAAQ,GAC3B,MAAU,WACR;SAAE,aAAa;SAAQ,OAAO;SAAQ,GACtC,KAAA;kBAGP,MAAU,SACT,kBAAC,GAAD;SAAO,eAAY;SAAO,WAAU;SAAY,CAAA,GAC9C,MAAU,WACZ,kBAAC,GAAD;SAAM,eAAY;SAAO,WAAU;SAAgB,CAAA,GAEnD,IAAI;QAED,CAAA;OACN,IAAI,EAAM,SAAS,IAClB,kBAAC,QAAD;QACE,eAAY;QACZ,WAAU;QACV,OAAO,EACL,iBACE,MAAU,SAAS,IAAS,8BAC/B;QACD,CAAA,GAEF,kBAAC,QAAD;QAAM,WAAU;QAAS,eAAY;QAAS,CAAA;OAE5C;;KACN,kBAAC,QAAD;MACE,WAAW,EACT,uDACA,MAAU,WAAW,oBAAoB,gCAC1C;MACD,OAAO,EAAK;gBAEX,EAAK;MACD,CAAA;KACN,EAAK,OACJ,kBAAC,QAAD;MAAM,WAAU;gBAAuD,EAAK;MAAY,CAAA,GACtF;KACD;MA/DE,GAAG,EAAK,MAAM,GAAG,IA+DnB;IAEP;EACC,CAAA"}
|
|
@@ -0,0 +1,68 @@
|
|
|
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/ProgressRing.tsx
|
|
7
|
+
var s = 100, c = s / 2;
|
|
8
|
+
function l({ value: l, max: u = 100, label: d, caption: f, size: p = 132, thickness: m = 10, color: h = e, title: g, className: _ }) {
|
|
9
|
+
let v = n(), y = r(), b = c - m / 2, x = 2 * Math.PI * b, S = i(u === 0 ? 0 : l / u, 0, 1), C = x * (1 - S), w = v || y ? C : x, T = g ?? `Progress: ${Math.round(S * 100)} percent`;
|
|
10
|
+
return /* @__PURE__ */ o("div", {
|
|
11
|
+
className: t("relative inline-flex shrink-0", _),
|
|
12
|
+
style: {
|
|
13
|
+
width: p,
|
|
14
|
+
height: p,
|
|
15
|
+
maxWidth: "100%"
|
|
16
|
+
},
|
|
17
|
+
children: [/* @__PURE__ */ o("svg", {
|
|
18
|
+
viewBox: `0 0 ${s} ${s}`,
|
|
19
|
+
width: p,
|
|
20
|
+
height: p,
|
|
21
|
+
role: "img",
|
|
22
|
+
"aria-label": T,
|
|
23
|
+
style: {
|
|
24
|
+
display: "block",
|
|
25
|
+
maxWidth: "100%",
|
|
26
|
+
height: "auto"
|
|
27
|
+
},
|
|
28
|
+
children: [
|
|
29
|
+
/* @__PURE__ */ a("title", { children: T }),
|
|
30
|
+
/* @__PURE__ */ a("circle", {
|
|
31
|
+
cx: c,
|
|
32
|
+
cy: c,
|
|
33
|
+
r: b,
|
|
34
|
+
fill: "none",
|
|
35
|
+
stroke: "var(--color-chart-grid)",
|
|
36
|
+
strokeWidth: m,
|
|
37
|
+
opacity: .7
|
|
38
|
+
}),
|
|
39
|
+
/* @__PURE__ */ a("circle", {
|
|
40
|
+
cx: c,
|
|
41
|
+
cy: c,
|
|
42
|
+
r: b,
|
|
43
|
+
fill: "none",
|
|
44
|
+
stroke: h,
|
|
45
|
+
strokeWidth: m,
|
|
46
|
+
strokeLinecap: "round",
|
|
47
|
+
strokeDasharray: x,
|
|
48
|
+
strokeDashoffset: w,
|
|
49
|
+
transform: `rotate(-90 ${c} ${c})`,
|
|
50
|
+
style: { transition: y ? void 0 : "stroke-dashoffset 900ms cubic-bezier(0.22,1,0.36,1)" }
|
|
51
|
+
})
|
|
52
|
+
]
|
|
53
|
+
}), /* @__PURE__ */ o("div", {
|
|
54
|
+
className: "pointer-events-none absolute inset-0 flex flex-col items-center justify-center text-center",
|
|
55
|
+
children: [/* @__PURE__ */ a("span", {
|
|
56
|
+
className: "text-2xl font-semibold leading-none text-text-primary",
|
|
57
|
+
children: d ?? Math.round(S * 100)
|
|
58
|
+
}), f === void 0 ? null : /* @__PURE__ */ a("span", {
|
|
59
|
+
className: "mt-1 text-xs text-text-muted",
|
|
60
|
+
children: f
|
|
61
|
+
})]
|
|
62
|
+
})]
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
//#endregion
|
|
66
|
+
export { l as ProgressRing };
|
|
67
|
+
|
|
68
|
+
//# sourceMappingURL=ProgressRing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProgressRing.js","names":[],"sources":["../../../../src/crewfoundry/components/viz/ProgressRing.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { cn } from '../../utils/cn';\nimport { ACCENT } from './lib/palette';\nimport { clamp } from './lib/geometry';\nimport { useMounted, usePrefersReducedMotion } from './lib/hooks';\n\nexport interface ProgressRingProps {\n value: number;\n max?: number;\n /** Center content. Defaults to the rounded value (e.g. \"4.6\" or \"72%\"). */\n label?: ReactNode;\n /** Small caption under the center label. */\n caption?: ReactNode;\n size?: number;\n /** Stroke thickness in viewBox units. */\n thickness?: number;\n /** Tokenized arc color; defaults to the product accent. */\n color?: string;\n title?: string;\n className?: string;\n}\n\nconst VIEW = 100;\nconst CENTER = VIEW / 2;\n\n/** Circular gauge for a score or percentage, with an animated sweep. */\nexport function ProgressRing({\n value,\n max = 100,\n label,\n caption,\n size = 132,\n thickness = 10,\n color = ACCENT,\n title,\n className,\n}: ProgressRingProps) {\n const mounted = useMounted();\n const reduced = usePrefersReducedMotion();\n\n const radius = CENTER - thickness / 2;\n const circumference = 2 * Math.PI * radius;\n const fraction = clamp(max === 0 ? 0 : value / max, 0, 1);\n const target = circumference * (1 - fraction);\n const offset = mounted || reduced ? target : circumference;\n const ringTitle = title ?? `Progress: ${Math.round(fraction * 100)} percent`;\n\n return (\n <div\n className={cn('relative inline-flex shrink-0', className)}\n style={{ width: size, height: size, maxWidth: '100%' }}\n >\n <svg\n viewBox={`0 0 ${VIEW} ${VIEW}`}\n width={size}\n height={size}\n role=\"img\"\n aria-label={ringTitle}\n style={{ display: 'block', maxWidth: '100%', height: 'auto' }}\n >\n <title>{ringTitle}</title>\n <circle\n cx={CENTER}\n cy={CENTER}\n r={radius}\n fill=\"none\"\n stroke=\"var(--color-chart-grid)\"\n strokeWidth={thickness}\n opacity={0.7}\n />\n <circle\n cx={CENTER}\n cy={CENTER}\n r={radius}\n fill=\"none\"\n stroke={color}\n strokeWidth={thickness}\n strokeLinecap=\"round\"\n strokeDasharray={circumference}\n strokeDashoffset={offset}\n transform={`rotate(-90 ${CENTER} ${CENTER})`}\n style={{\n transition: reduced ? undefined : 'stroke-dashoffset 900ms cubic-bezier(0.22,1,0.36,1)',\n }}\n />\n </svg>\n <div className=\"pointer-events-none absolute inset-0 flex flex-col items-center justify-center text-center\">\n <span className=\"text-2xl font-semibold leading-none text-text-primary\">\n {label ?? Math.round(fraction * 100)}\n </span>\n {caption !== undefined ? (\n <span className=\"mt-1 text-xs text-text-muted\">{caption}</span>\n ) : null}\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;AAsBA,IAAM,IAAO,KACP,IAAS,IAAO;AAGtB,SAAgB,EAAa,EAC3B,UACA,SAAM,KACN,UACA,YACA,UAAO,KACP,eAAY,IACZ,WAAQ,GACR,UACA,gBACoB;CACpB,IAAM,IAAU,GAAY,EACtB,IAAU,GAAyB,EAEnC,IAAS,IAAS,IAAY,GAC9B,IAAgB,IAAI,KAAK,KAAK,GAC9B,IAAW,EAAM,MAAQ,IAAI,IAAI,IAAQ,GAAK,GAAG,EAAE,EACnD,IAAS,KAAiB,IAAI,IAC9B,IAAS,KAAW,IAAU,IAAS,GACvC,IAAY,KAAS,aAAa,KAAK,MAAM,IAAW,IAAI,CAAC;AAEnE,QACE,kBAAC,OAAD;EACE,WAAW,EAAG,iCAAiC,EAAU;EACzD,OAAO;GAAE,OAAO;GAAM,QAAQ;GAAM,UAAU;GAAQ;YAFxD,CAIE,kBAAC,OAAD;GACE,SAAS,OAAO,EAAK,GAAG;GACxB,OAAO;GACP,QAAQ;GACR,MAAK;GACL,cAAY;GACZ,OAAO;IAAE,SAAS;IAAS,UAAU;IAAQ,QAAQ;IAAQ;aAN/D;IAQE,kBAAC,SAAD,EAAA,UAAQ,GAAkB,CAAA;IAC1B,kBAAC,UAAD;KACE,IAAI;KACJ,IAAI;KACJ,GAAG;KACH,MAAK;KACL,QAAO;KACP,aAAa;KACb,SAAS;KACT,CAAA;IACF,kBAAC,UAAD;KACE,IAAI;KACJ,IAAI;KACJ,GAAG;KACH,MAAK;KACL,QAAQ;KACR,aAAa;KACb,eAAc;KACd,iBAAiB;KACjB,kBAAkB;KAClB,WAAW,cAAc,EAAO,GAAG,EAAO;KAC1C,OAAO,EACL,YAAY,IAAU,KAAA,IAAY,uDACnC;KACD,CAAA;IACE;MACN,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,QAAD;IAAM,WAAU;cACb,KAAS,KAAK,MAAM,IAAW,IAAI;IAC/B,CAAA,EACN,MAAY,KAAA,IAET,OADF,kBAAC,QAAD;IAAM,WAAU;cAAgC;IAAe,CAAA,CAE7D;KACF"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { STATUS as e } from "./lib/palette.js";
|
|
2
|
+
import { cn as t } from "../../utils/cn.js";
|
|
3
|
+
import { clamp as n } from "./lib/geometry.js";
|
|
4
|
+
import { Star as r } from "lucide-react";
|
|
5
|
+
import { jsx as i, jsxs as a } from "react/jsx-runtime";
|
|
6
|
+
//#region src/crewfoundry/components/viz/RatingStars.tsx
|
|
7
|
+
function o({ value: o, max: s = 5, showValue: c = !1, count: l, size: u = 16, className: d }) {
|
|
8
|
+
let f = n(o, 0, s), p = f / s * 100, m = Math.round(u * .15), h = (t) => /* @__PURE__ */ i("span", {
|
|
9
|
+
className: "flex",
|
|
10
|
+
style: { gap: m },
|
|
11
|
+
"aria-hidden": "true",
|
|
12
|
+
children: Array.from({ length: s }, (n, a) => /* @__PURE__ */ i(r, {
|
|
13
|
+
width: u,
|
|
14
|
+
height: u,
|
|
15
|
+
strokeWidth: 1.5,
|
|
16
|
+
style: { color: t ? e.warning : "var(--color-border-strong)" },
|
|
17
|
+
fill: t ? "currentColor" : "none"
|
|
18
|
+
}, a))
|
|
19
|
+
});
|
|
20
|
+
return /* @__PURE__ */ a("span", {
|
|
21
|
+
className: t("inline-flex items-center gap-1.5", d),
|
|
22
|
+
role: "img",
|
|
23
|
+
"aria-label": `Rated ${f} out of ${s}${l === void 0 ? "" : ` from ${l} reviews`}`,
|
|
24
|
+
children: [
|
|
25
|
+
/* @__PURE__ */ a("span", {
|
|
26
|
+
className: "relative inline-flex",
|
|
27
|
+
children: [h(!1), /* @__PURE__ */ i("span", {
|
|
28
|
+
className: "absolute left-0 top-0 overflow-hidden",
|
|
29
|
+
style: { width: `${p}%` },
|
|
30
|
+
children: h(!0)
|
|
31
|
+
})]
|
|
32
|
+
}),
|
|
33
|
+
c ? /* @__PURE__ */ i("span", {
|
|
34
|
+
className: "text-sm font-medium tabular-nums text-text-secondary",
|
|
35
|
+
children: f.toFixed(1)
|
|
36
|
+
}) : null,
|
|
37
|
+
l === void 0 ? null : /* @__PURE__ */ a("span", {
|
|
38
|
+
className: "text-xs text-text-muted",
|
|
39
|
+
children: [
|
|
40
|
+
"(",
|
|
41
|
+
l,
|
|
42
|
+
")"
|
|
43
|
+
]
|
|
44
|
+
})
|
|
45
|
+
]
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
49
|
+
export { o as RatingStars };
|
|
50
|
+
|
|
51
|
+
//# sourceMappingURL=RatingStars.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RatingStars.js","names":[],"sources":["../../../../src/crewfoundry/components/viz/RatingStars.tsx"],"sourcesContent":["import { Star } from 'lucide-react';\nimport { cn } from '../../utils/cn';\nimport { STATUS } from './lib/palette';\nimport { clamp } from './lib/geometry';\n\nexport interface RatingStarsProps {\n value: number;\n max?: number;\n /** Show the numeric value next to the stars. */\n showValue?: boolean;\n /** Optional review count, e.g. \"(128)\". */\n count?: number;\n /** Star size in pixels. */\n size?: number;\n className?: string;\n}\n\n/** Filled / empty stars for a 0–max rating, with optional value + count. */\nexport function RatingStars({\n value,\n max = 5,\n showValue = false,\n count,\n size = 16,\n className,\n}: RatingStarsProps) {\n const clamped = clamp(value, 0, max);\n const fillPct = (clamped / max) * 100;\n const gap = Math.round(size * 0.15);\n\n const row = (filled: boolean) => (\n <span className=\"flex\" style={{ gap }} aria-hidden=\"true\">\n {Array.from({ length: max }, (_, i) => (\n <Star\n key={i}\n width={size}\n height={size}\n strokeWidth={1.5}\n style={{ color: filled ? STATUS.warning : 'var(--color-border-strong)' }}\n fill={filled ? 'currentColor' : 'none'}\n />\n ))}\n </span>\n );\n\n return (\n <span\n className={cn('inline-flex items-center gap-1.5', className)}\n role=\"img\"\n aria-label={`Rated ${clamped} out of ${max}${count !== undefined ? ` from ${count} reviews` : ''}`}\n >\n <span className=\"relative inline-flex\">\n {row(false)}\n <span\n className=\"absolute left-0 top-0 overflow-hidden\"\n style={{ width: `${fillPct}%` }}\n >\n {row(true)}\n </span>\n </span>\n {showValue ? (\n <span className=\"text-sm font-medium tabular-nums text-text-secondary\">\n {clamped.toFixed(1)}\n </span>\n ) : null}\n {count !== undefined ? (\n <span className=\"text-xs text-text-muted\">({count})</span>\n ) : null}\n </span>\n );\n}\n"],"mappings":";;;;;;AAkBA,SAAgB,EAAY,EAC1B,UACA,SAAM,GACN,eAAY,IACZ,UACA,UAAO,IACP,gBACmB;CACnB,IAAM,IAAU,EAAM,GAAO,GAAG,EAAI,EAC9B,IAAW,IAAU,IAAO,KAC5B,IAAM,KAAK,MAAM,IAAO,IAAK,EAE7B,KAAO,MACX,kBAAC,QAAD;EAAM,WAAU;EAAO,OAAO,EAAE,QAAK;EAAE,eAAY;YAChD,MAAM,KAAK,EAAE,QAAQ,GAAK,GAAG,GAAG,MAC/B,kBAAC,GAAD;GAEE,OAAO;GACP,QAAQ;GACR,aAAa;GACb,OAAO,EAAE,OAAO,IAAS,EAAO,UAAU,8BAA8B;GACxE,MAAM,IAAS,iBAAiB;GAChC,EANK,EAML,CACF;EACG,CAAA;AAGT,QACE,kBAAC,QAAD;EACE,WAAW,EAAG,oCAAoC,EAAU;EAC5D,MAAK;EACL,cAAY,SAAS,EAAQ,UAAU,IAAM,MAAU,KAAA,IAAuC,KAA3B,SAAS,EAAM;YAHpF;GAKE,kBAAC,QAAD;IAAM,WAAU;cAAhB,CACG,EAAI,GAAM,EACX,kBAAC,QAAD;KACE,WAAU;KACV,OAAO,EAAE,OAAO,GAAG,EAAQ,IAAI;eAE9B,EAAI,GAAK;KACL,CAAA,CACF;;GACN,IACC,kBAAC,QAAD;IAAM,WAAU;cACb,EAAQ,QAAQ,EAAE;IACd,CAAA,GACL;GACH,MAAU,KAAA,IAEP,OADF,kBAAC,QAAD;IAAM,WAAU;cAAhB;KAA0C;KAAE;KAAM;KAAQ;;GAEvD"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { ACCENT as e } from "./lib/palette.js";
|
|
2
|
+
import { cn as t } from "../../utils/cn.js";
|
|
3
|
+
import { useRef as n } from "react";
|
|
4
|
+
import { jsx as r, jsxs as i } from "react/jsx-runtime";
|
|
5
|
+
//#region src/crewfoundry/components/viz/SegmentedTabs.tsx
|
|
6
|
+
function a({ tabs: a, value: o, onChange: s, variant: c = "pill", fill: l = !1, className: u, "aria-label": d }) {
|
|
7
|
+
let f = n([]), p = (e) => {
|
|
8
|
+
let t = (e + a.length) % a.length, n = a[t];
|
|
9
|
+
n && (s(n.id), f.current[t]?.focus());
|
|
10
|
+
}, m = (e, t) => {
|
|
11
|
+
switch (e.key) {
|
|
12
|
+
case "ArrowRight":
|
|
13
|
+
case "ArrowDown":
|
|
14
|
+
e.preventDefault(), p(t + 1);
|
|
15
|
+
break;
|
|
16
|
+
case "ArrowLeft":
|
|
17
|
+
case "ArrowUp":
|
|
18
|
+
e.preventDefault(), p(t - 1);
|
|
19
|
+
break;
|
|
20
|
+
case "Home":
|
|
21
|
+
e.preventDefault(), p(0);
|
|
22
|
+
break;
|
|
23
|
+
case "End":
|
|
24
|
+
e.preventDefault(), p(a.length - 1);
|
|
25
|
+
break;
|
|
26
|
+
default: break;
|
|
27
|
+
}
|
|
28
|
+
}, h = c === "pill";
|
|
29
|
+
return /* @__PURE__ */ r("div", {
|
|
30
|
+
role: "tablist",
|
|
31
|
+
"aria-label": d,
|
|
32
|
+
className: t("flex items-center", h ? "gap-1 rounded-full border border-border-subtle bg-bg-sunken p-1" : "gap-1 border-b border-border-subtle", l && "w-full", u),
|
|
33
|
+
children: a.map((n, a) => {
|
|
34
|
+
let c = n.id === o;
|
|
35
|
+
return /* @__PURE__ */ i("button", {
|
|
36
|
+
ref: (e) => {
|
|
37
|
+
f.current[a] = e;
|
|
38
|
+
},
|
|
39
|
+
type: "button",
|
|
40
|
+
role: "tab",
|
|
41
|
+
"aria-selected": c,
|
|
42
|
+
"aria-controls": n.controls,
|
|
43
|
+
tabIndex: c ? 0 : -1,
|
|
44
|
+
onClick: () => s(n.id),
|
|
45
|
+
onKeyDown: (e) => m(e, a),
|
|
46
|
+
className: t("inline-flex items-center justify-center gap-1.5 whitespace-nowrap text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]", l && "flex-1", h ? t("rounded-full px-3 py-1.5", c ? "bg-bg-surface text-text-primary shadow-elevation-1" : "text-text-secondary hover:text-text-primary") : t("-mb-px border-b-2 px-3 py-2", c ? "text-text-primary" : "border-transparent text-text-secondary hover:text-text-primary")),
|
|
47
|
+
style: !h && c ? {
|
|
48
|
+
borderColor: e,
|
|
49
|
+
color: "var(--color-text-primary)"
|
|
50
|
+
} : void 0,
|
|
51
|
+
children: [
|
|
52
|
+
n.icon ? /* @__PURE__ */ r("span", {
|
|
53
|
+
"aria-hidden": "true",
|
|
54
|
+
children: n.icon
|
|
55
|
+
}) : null,
|
|
56
|
+
n.label,
|
|
57
|
+
n.count === void 0 ? null : /* @__PURE__ */ r("span", {
|
|
58
|
+
className: t("inline-flex min-w-5 items-center justify-center rounded-full px-1.5 text-xs tabular-nums", c ? "bg-bg-sunken text-text-secondary" : "bg-bg-elevated text-text-muted"),
|
|
59
|
+
children: n.count
|
|
60
|
+
})
|
|
61
|
+
]
|
|
62
|
+
}, n.id);
|
|
63
|
+
})
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
//#endregion
|
|
67
|
+
export { a as SegmentedTabs };
|
|
68
|
+
|
|
69
|
+
//# sourceMappingURL=SegmentedTabs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SegmentedTabs.js","names":[],"sources":["../../../../src/crewfoundry/components/viz/SegmentedTabs.tsx"],"sourcesContent":["import { type KeyboardEvent, type ReactNode, useRef } from 'react';\nimport { cn } from '../../utils/cn';\nimport { ACCENT } from './lib/palette';\n\nexport interface SegmentedTab {\n id: string;\n label: ReactNode;\n icon?: ReactNode;\n count?: number;\n /** id of the panel this tab controls (for aria-controls). */\n controls?: string;\n}\n\nexport interface SegmentedTabsProps {\n tabs: SegmentedTab[];\n value: string;\n onChange: (id: string) => void;\n variant?: 'pill' | 'underline';\n /** Stretch tabs to fill the available width. */\n fill?: boolean;\n className?: string;\n 'aria-label'?: string;\n}\n\n/**\n * A controlled pill / underline tab switcher with a proper tablist:\n * roving tab focus, arrow / Home / End navigation, and tokenized styling.\n */\nexport function SegmentedTabs({\n tabs,\n value,\n onChange,\n variant = 'pill',\n fill = false,\n className,\n 'aria-label': ariaLabel,\n}: SegmentedTabsProps) {\n const refs = useRef<(HTMLButtonElement | null)[]>([]);\n\n const focusTab = (index: number): void => {\n const clamped = (index + tabs.length) % tabs.length;\n const tab = tabs[clamped];\n if (tab) {\n onChange(tab.id);\n refs.current[clamped]?.focus();\n }\n };\n\n const handleKey = (event: KeyboardEvent<HTMLButtonElement>, index: number): void => {\n switch (event.key) {\n case 'ArrowRight':\n case 'ArrowDown':\n event.preventDefault();\n focusTab(index + 1);\n break;\n case 'ArrowLeft':\n case 'ArrowUp':\n event.preventDefault();\n focusTab(index - 1);\n break;\n case 'Home':\n event.preventDefault();\n focusTab(0);\n break;\n case 'End':\n event.preventDefault();\n focusTab(tabs.length - 1);\n break;\n default:\n break;\n }\n };\n\n const isPill = variant === 'pill';\n\n return (\n <div\n role=\"tablist\"\n aria-label={ariaLabel}\n className={cn(\n 'flex items-center',\n isPill\n ? 'gap-1 rounded-full border border-border-subtle bg-bg-sunken p-1'\n : 'gap-1 border-b border-border-subtle',\n fill && 'w-full',\n className,\n )}\n >\n {tabs.map((tab, i) => {\n const selected = tab.id === value;\n return (\n <button\n key={tab.id}\n ref={(node) => {\n refs.current[i] = node;\n }}\n type=\"button\"\n role=\"tab\"\n aria-selected={selected}\n aria-controls={tab.controls}\n tabIndex={selected ? 0 : -1}\n onClick={() => onChange(tab.id)}\n onKeyDown={(event) => handleKey(event, i)}\n className={cn(\n 'inline-flex items-center justify-center gap-1.5 whitespace-nowrap text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]',\n fill && 'flex-1',\n isPill\n ? cn(\n 'rounded-full px-3 py-1.5',\n selected\n ? 'bg-bg-surface text-text-primary shadow-elevation-1'\n : 'text-text-secondary hover:text-text-primary',\n )\n : cn(\n '-mb-px border-b-2 px-3 py-2',\n selected\n ? 'text-text-primary'\n : 'border-transparent text-text-secondary hover:text-text-primary',\n ),\n )}\n style={!isPill && selected ? { borderColor: ACCENT, color: 'var(--color-text-primary)' } : undefined}\n >\n {tab.icon ? <span aria-hidden=\"true\">{tab.icon}</span> : null}\n {tab.label}\n {tab.count !== undefined ? (\n <span\n className={cn(\n 'inline-flex min-w-5 items-center justify-center rounded-full px-1.5 text-xs tabular-nums',\n selected ? 'bg-bg-sunken text-text-secondary' : 'bg-bg-elevated text-text-muted',\n )}\n >\n {tab.count}\n </span>\n ) : null}\n </button>\n );\n })}\n </div>\n );\n}\n"],"mappings":";;;;;AA4BA,SAAgB,EAAc,EAC5B,SACA,UACA,aACA,aAAU,QACV,UAAO,IACP,cACA,cAAc,KACO;CACrB,IAAM,IAAO,EAAqC,EAAE,CAAC,EAE/C,KAAY,MAAwB;EACxC,IAAM,KAAW,IAAQ,EAAK,UAAU,EAAK,QACvC,IAAM,EAAK;AACjB,EAAI,MACF,EAAS,EAAI,GAAG,EAChB,EAAK,QAAQ,IAAU,OAAO;IAI5B,KAAa,GAAyC,MAAwB;AAClF,UAAQ,EAAM,KAAd;GACE,KAAK;GACL,KAAK;AAEH,IADA,EAAM,gBAAgB,EACtB,EAAS,IAAQ,EAAE;AACnB;GACF,KAAK;GACL,KAAK;AAEH,IADA,EAAM,gBAAgB,EACtB,EAAS,IAAQ,EAAE;AACnB;GACF,KAAK;AAEH,IADA,EAAM,gBAAgB,EACtB,EAAS,EAAE;AACX;GACF,KAAK;AAEH,IADA,EAAM,gBAAgB,EACtB,EAAS,EAAK,SAAS,EAAE;AACzB;GACF,QACE;;IAIA,IAAS,MAAY;AAE3B,QACE,kBAAC,OAAD;EACE,MAAK;EACL,cAAY;EACZ,WAAW,EACT,qBACA,IACI,oEACA,uCACJ,KAAQ,UACR,EACD;YAEA,EAAK,KAAK,GAAK,MAAM;GACpB,IAAM,IAAW,EAAI,OAAO;AAC5B,UACE,kBAAC,UAAD;IAEE,MAAM,MAAS;AACb,OAAK,QAAQ,KAAK;;IAEpB,MAAK;IACL,MAAK;IACL,iBAAe;IACf,iBAAe,EAAI;IACnB,UAAU,IAAW,IAAI;IACzB,eAAe,EAAS,EAAI,GAAG;IAC/B,YAAY,MAAU,EAAU,GAAO,EAAE;IACzC,WAAW,EACT,0LACA,KAAQ,UACR,IACI,EACE,4BACA,IACI,uDACA,8CACL,GACD,EACE,+BACA,IACI,sBACA,iEACL,CACN;IACD,OAAO,CAAC,KAAU,IAAW;KAAE,aAAa;KAAQ,OAAO;KAA6B,GAAG,KAAA;cA7B7F;KA+BG,EAAI,OAAO,kBAAC,QAAD;MAAM,eAAY;gBAAQ,EAAI;MAAY,CAAA,GAAG;KACxD,EAAI;KACJ,EAAI,UAAU,KAAA,IASX,OARF,kBAAC,QAAD;MACE,WAAW,EACT,4FACA,IAAW,qCAAqC,iCACjD;gBAEA,EAAI;MACA,CAAA;KAEF;MA1CF,EAAI,GA0CF;IAEX;EACE,CAAA"}
|