@burdenoff/microfe-timecampus 2026.801.1 → 2026.820.2
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/generated/wspace-operations.d.ts +484 -861
- package/dist/generated/wspace-operations.d.ts.map +1 -1
- package/dist/generated/wspace-operations.js +1377 -1377
- package/dist/generated/wspace-operations.js.map +1 -1
- package/dist/generated/wspace-types.js.map +1 -1
- package/dist/index.js +5 -3
- package/dist/services/createSandboxAssistantTransport.js.map +1 -1
- package/dist/stories/EmptyState.stories.d.ts +0 -1
- package/dist/stories/LoadingSkeleton.stories.d.ts +0 -1
- package/dist/stories/StatCard.stories.d.ts +0 -1
- package/dist/stories/StatusBadge.stories.d.ts +0 -1
- package/dist/timecampus/AdminRoot.d.ts +8 -0
- package/dist/timecampus/AdminRoot.d.ts.map +1 -0
- package/dist/timecampus/AdminRoot.js +12 -0
- package/dist/timecampus/AdminRoot.js.map +1 -0
- package/dist/timecampus/AdminRoutes.d.ts +3 -0
- package/dist/timecampus/AdminRoutes.d.ts.map +1 -0
- package/dist/timecampus/AdminRoutes.js +8 -0
- package/dist/timecampus/AdminRoutes.js.map +1 -0
- package/dist/timecampus/TimeCampusRoot.d.ts +0 -1
- package/dist/timecampus/TimeCampusRoot.js +4 -2
- package/dist/timecampus/TimeCampusRoot.js.map +1 -1
- package/dist/timecampus/TimeCampusRoutes.js.map +1 -1
- package/dist/timecampus/components/ChartCard.d.ts +0 -1
- package/dist/timecampus/components/ChartCard.js.map +1 -1
- package/dist/timecampus/components/DataTable.d.ts +0 -1
- package/dist/timecampus/components/EmptyState.d.ts +0 -1
- package/dist/timecampus/components/EmptyState.js.map +1 -1
- package/dist/timecampus/components/ErrorBoundary.d.ts +0 -1
- package/dist/timecampus/components/ErrorBoundary.js.map +1 -1
- package/dist/timecampus/components/FilterBar.d.ts +0 -1
- package/dist/timecampus/components/LoadingSkeleton.d.ts +0 -1
- package/dist/timecampus/components/LoadingSkeleton.js.map +1 -1
- package/dist/timecampus/components/Modal.d.ts +0 -1
- package/dist/timecampus/components/Modal.js +5 -3
- package/dist/timecampus/components/Modal.js.map +1 -1
- package/dist/timecampus/components/PageLayout.d.ts +0 -1
- package/dist/timecampus/components/PageLayout.js.map +1 -1
- package/dist/timecampus/components/StatCard.d.ts +0 -1
- package/dist/timecampus/components/StatCard.js.map +1 -1
- package/dist/timecampus/components/StatusBadge.d.ts +0 -1
- package/dist/timecampus/components/StatusBadge.js.map +1 -1
- package/dist/timecampus/components/Toast.d.ts +0 -1
- package/dist/timecampus/components/Toast.js.map +1 -1
- package/dist/timecampus/components/charts/BalanceWheel.js.map +1 -1
- package/dist/timecampus/components/charts/BarsChart.d.ts +0 -1
- package/dist/timecampus/components/charts/BarsChart.js.map +1 -1
- package/dist/timecampus/components/charts/ChartParts.d.ts +0 -1
- package/dist/timecampus/components/charts/ChartParts.js.map +1 -1
- package/dist/timecampus/components/charts/DonutChart.d.ts +0 -1
- package/dist/timecampus/components/charts/DonutChart.js.map +1 -1
- package/dist/timecampus/components/charts/Heatmap.d.ts +0 -1
- package/dist/timecampus/components/charts/Heatmap.js +29 -29
- package/dist/timecampus/components/charts/Heatmap.js.map +1 -1
- package/dist/timecampus/components/charts/ProgressMeter.d.ts +0 -1
- package/dist/timecampus/components/charts/ProgressMeter.js +2 -2
- package/dist/timecampus/components/charts/ProgressMeter.js.map +1 -1
- package/dist/timecampus/components/charts/RadialGauge.d.ts +0 -1
- package/dist/timecampus/components/charts/RadialGauge.js +2 -2
- package/dist/timecampus/components/charts/RadialGauge.js.map +1 -1
- package/dist/timecampus/components/charts/TrendChart.d.ts +0 -1
- package/dist/timecampus/components/charts/TrendChart.js.map +1 -1
- package/dist/timecampus/components/charts/WeeksGrid.js +2 -2
- package/dist/timecampus/components/charts/WeeksGrid.js.map +1 -1
- package/dist/timecampus/components/charts/chartTheme.js.map +1 -1
- package/dist/timecampus/components/charts/index.js +10 -9
- package/dist/timecampus/components/index.js +18 -17
- package/dist/timecampus/index.d.ts +3 -1
- package/dist/timecampus/index.d.ts.map +1 -1
- package/dist/timecampus/index.js +8 -5
- package/dist/timecampus/pages/HomePage.d.ts +0 -1
- package/dist/timecampus/pages/HomePage.js.map +1 -1
- package/dist/timecampus/pages/admin/AdminPage.d.ts +3 -0
- package/dist/timecampus/pages/admin/AdminPage.d.ts.map +1 -0
- package/dist/timecampus/pages/admin/AdminPage.js +235 -0
- package/dist/timecampus/pages/admin/AdminPage.js.map +1 -0
- package/dist/timecampus/pages/application-usage/ApplicationUsagePage.d.ts.map +1 -1
- package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js.map +1 -1
- package/dist/timecampus/pages/client-portal/ClientPortalPage.d.ts.map +1 -1
- package/dist/timecampus/pages/client-portal/ClientPortalPage.js.map +1 -1
- package/dist/timecampus/pages/dashboard/AnalyticsPage.js.map +1 -1
- package/dist/timecampus/pages/dashboard/ReportsPage.js.map +1 -1
- package/dist/timecampus/pages/email-tracking/EmailAnalyticsPage.js.map +1 -1
- package/dist/timecampus/pages/email-tracking/EmailTrackingPage.d.ts.map +1 -1
- package/dist/timecampus/pages/email-tracking/EmailTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.d.ts.map +1 -1
- package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/focus-score/FocusScorePage.js.map +1 -1
- package/dist/timecampus/pages/geofencing/GeofencingPage.d.ts.map +1 -1
- package/dist/timecampus/pages/geofencing/GeofencingPage.js.map +1 -1
- package/dist/timecampus/pages/invoicing/InvoicingPage.d.ts.map +1 -1
- package/dist/timecampus/pages/invoicing/InvoicingPage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/DeadTimeRadarPage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/FiniteLifePage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/LifeBalancePage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/LifeTimelinePage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/PersonalCorporatePage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/RecommendationsPage.js +1 -1
- package/dist/timecampus/pages/lifetime/RecommendationsPage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/TimeWellSpentPage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/ValuesGoalsPage.js +9 -6
- package/dist/timecampus/pages/lifetime/ValuesGoalsPage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/domains.d.ts +0 -1
- package/dist/timecampus/pages/lifetime/domains.js +1 -1
- package/dist/timecampus/pages/lifetime/domains.js.map +1 -1
- package/dist/timecampus/pages/location-tracking/LocationTrackingPage.d.ts.map +1 -1
- package/dist/timecampus/pages/location-tracking/LocationTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/meetings/ActionItemFormModal.d.ts +0 -1
- package/dist/timecampus/pages/meetings/ActionItemFormModal.js.map +1 -1
- package/dist/timecampus/pages/meetings/MeetingAnalyticsPage.js +1 -1
- package/dist/timecampus/pages/meetings/MeetingAnalyticsPage.js.map +1 -1
- package/dist/timecampus/pages/meetings/MeetingCreatePage.js.map +1 -1
- package/dist/timecampus/pages/meetings/MeetingDetailPage.js.map +1 -1
- package/dist/timecampus/pages/meetings/MeetingFormModal.d.ts +0 -1
- package/dist/timecampus/pages/meetings/MeetingFormModal.js.map +1 -1
- package/dist/timecampus/pages/meetings/MeetingsPage.js.map +1 -1
- package/dist/timecampus/pages/meetings/ParticipantFormModal.d.ts +0 -1
- package/dist/timecampus/pages/meetings/ParticipantFormModal.js.map +1 -1
- package/dist/timecampus/pages/pomodoro/PomodoroAnalyticsPage.js.map +1 -1
- package/dist/timecampus/pages/pomodoro/PomodoroPage.js.map +1 -1
- package/dist/timecampus/pages/pomodoro/PomodoroSettingsPage.js.map +1 -1
- package/dist/timecampus/pages/projects/ProjectCreatePage.js.map +1 -1
- package/dist/timecampus/pages/projects/ProjectDetailPage.js.map +1 -1
- package/dist/timecampus/pages/projects/ProjectsPage.js.map +1 -1
- package/dist/timecampus/pages/projects/TasksPage.js.map +1 -1
- package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.d.ts.map +1 -1
- package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.js.map +1 -1
- package/dist/timecampus/pages/team/TeamAnalyticsPage.js.map +1 -1
- package/dist/timecampus/pages/team/TeamMembersPage.js.map +1 -1
- package/dist/timecampus/pages/team/TeamPage.js.map +1 -1
- package/dist/timecampus/pages/team-activity/TeamActivityPage.js.map +1 -1
- package/dist/timecampus/pages/time/TimeEntriesPage.d.ts.map +1 -1
- package/dist/timecampus/pages/time/TimeEntriesPage.js.map +1 -1
- package/dist/timecampus/pages/time/TimeTrackingPage.js +1 -1
- package/dist/timecampus/pages/time/TimeTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/time/TimerPage.js +4 -4
- package/dist/timecampus/pages/time/TimerPage.js.map +1 -1
- package/dist/timecampus/pages/time-approval/TimeApprovalPage.d.ts.map +1 -1
- package/dist/timecampus/pages/time-approval/TimeApprovalPage.js.map +1 -1
- package/dist/timecampus/pages/timeline/TimelinePage.js.map +1 -1
- package/dist/timecampus/pages/work-patterns/WorkPatternsPage.js +1 -1
- package/dist/timecampus/pages/work-patterns/WorkPatternsPage.js.map +1 -1
- package/dist/timecampus/providers/TimeCampusProvider.d.ts +0 -1
- package/dist/timecampus/providers/TimeCampusProvider.d.ts.map +1 -1
- package/dist/timecampus/providers/TimeCampusProvider.js +38 -35
- package/dist/timecampus/providers/TimeCampusProvider.js.map +1 -1
- package/dist/timecampus/store/timecampusStore.js.map +1 -1
- package/dist/timecampus/types/index.d.ts +4 -1
- package/dist/timecampus/types/index.d.ts.map +1 -1
- package/dist/timecampus/utils/helpers.d.ts +0 -1
- package/dist/timecampus/utils/helpers.js.map +1 -1
- package/dist/types/assistant.d.ts +0 -1
- package/dist/utils/pageContext.js +1 -1
- package/dist/utils/pageContext.js.map +1 -1
- package/package.json +60 -57
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LoadingSkeleton.js","names":[],"sources":["../../../src/timecampus/components/LoadingSkeleton.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { cn } from \"../utils/helpers\";\n\ninterface SkeletonProps {\n className?: string;\n}\n\nconst Skeleton: FC<SkeletonProps> = ({ className }) => (\n <div\n className={cn(\n \"relative overflow-hidden rounded-md bg-bg-sunken\",\n \"after:absolute after:inset-0 after:-translate-x-full after:bg-gradient-to-r after:from-transparent after:via-bg-surface/60 after:to-transparent motion-safe:after:animate-vc-shimmer\",\n className,\n )}\n />\n);\n\ninterface LoadingSkeletonProps {\n variant?: \"card\" | \"table\" | \"list\" | \"detail\";\n count?: number;\n className?: string;\n}\n\nexport const LoadingSkeleton: FC<LoadingSkeletonProps> = ({\n variant = \"card\",\n count = 3,\n className,\n}) => {\n if (variant === \"table\") {\n return (\n <div className={cn(\"space-y-3\", className)}>\n <div className=\"overflow-hidden rounded-lg border border-border-subtle\">\n <div className=\"border-b border-border-subtle bg-bg-sunken/50 px-4 py-3\">\n <div className=\"flex gap-4\">\n {Array.from({ length: 5 }).map((_, i) => (\n <Skeleton key={i} className=\"h-4 w-20\" />\n ))}\n </div>\n </div>\n {Array.from({ length: count }).map((_, i) => (\n <div\n key={i}\n className=\"flex gap-4 border-b border-border-subtle px-4 py-3 last:border-b-0\"\n >\n {Array.from({ length: 5 }).map((_, j) => (\n <Skeleton key={j} className=\"h-4 w-20\" />\n ))}\n </div>\n ))}\n </div>\n </div>\n );\n }\n\n if (variant === \"list\") {\n return (\n <div className={cn(\"space-y-2\", className)}>\n {Array.from({ length: count }).map((_, i) => (\n <div\n key={i}\n className=\"flex items-center gap-3 rounded-lg border border-border-subtle p-4\"\n >\n <Skeleton className=\"h-10 w-10 rounded-full\" />\n <div className=\"flex-1 space-y-2\">\n <Skeleton className=\"h-4 w-1/3\" />\n <Skeleton className=\"h-3 w-2/3\" />\n </div>\n <Skeleton className=\"h-6 w-16 rounded-full\" />\n </div>\n ))}\n </div>\n );\n }\n\n if (variant === \"detail\") {\n return (\n <div className={cn(\"space-y-6\", className)}>\n <div className=\"space-y-2\">\n <Skeleton className=\"h-8 w-1/3\" />\n <Skeleton className=\"h-4 w-1/2\" />\n </div>\n <div className=\"grid gap-4 sm:grid-cols-3\">\n {Array.from({ length: 3 }).map((_, i) => (\n <div key={i} className=\"rounded-lg border border-border-subtle p-4\">\n <Skeleton className=\"h-3 w-20\" />\n <Skeleton className=\"mt-2 h-7 w-16\" />\n </div>\n ))}\n </div>\n <div className=\"rounded-lg border border-border-subtle p-6\">\n <Skeleton className=\"h-5 w-1/4\" />\n <div className=\"mt-4 space-y-3\">\n <Skeleton className=\"h-4 w-full\" />\n <Skeleton className=\"h-4 w-5/6\" />\n <Skeleton className=\"h-4 w-4/6\" />\n </div>\n </div>\n </div>\n );\n }\n\n // Default: card grid\n return (\n <div className={cn(\"grid gap-4 sm:grid-cols-2 lg:grid-cols-3\", className)}>\n {Array.from({ length: count }).map((_, i) => (\n <div key={i} className=\"rounded-lg border border-border-subtle p-4\">\n <Skeleton className=\"h-4 w-2/3\" />\n <Skeleton className=\"mt-2 h-3 w-1/2\" />\n <Skeleton className=\"mt-4 h-8 w-24\" />\n <div className=\"mt-3 flex justify-between\">\n <Skeleton className=\"h-5 w-16 rounded-full\" />\n <Skeleton className=\"h-7 w-14 rounded-md\" />\n </div>\n </div>\n ))}\n </div>\n );\n};\n"],"mappings":";;;AAOA,IAAM,KAA+B,EAAE,mBACrC,kBAAC,OAAD,EACE,WAAW,EACT,oDACA,wLACA,
|
|
1
|
+
{"version":3,"file":"LoadingSkeleton.js","names":[],"sources":["../../../src/timecampus/components/LoadingSkeleton.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { cn } from \"../utils/helpers\";\n\ninterface SkeletonProps {\n className?: string;\n}\n\nconst Skeleton: FC<SkeletonProps> = ({ className }) => (\n <div\n className={cn(\n \"relative overflow-hidden rounded-md bg-bg-sunken\",\n \"after:absolute after:inset-0 after:-translate-x-full after:bg-gradient-to-r after:from-transparent after:via-bg-surface/60 after:to-transparent motion-safe:after:animate-vc-shimmer\",\n className,\n )}\n />\n);\n\ninterface LoadingSkeletonProps {\n variant?: \"card\" | \"table\" | \"list\" | \"detail\";\n count?: number;\n className?: string;\n}\n\nexport const LoadingSkeleton: FC<LoadingSkeletonProps> = ({\n variant = \"card\",\n count = 3,\n className,\n}) => {\n if (variant === \"table\") {\n return (\n <div className={cn(\"space-y-3\", className)}>\n <div className=\"overflow-hidden rounded-lg border border-border-subtle\">\n <div className=\"border-b border-border-subtle bg-bg-sunken/50 px-4 py-3\">\n <div className=\"flex gap-4\">\n {Array.from({ length: 5 }).map((_, i) => (\n <Skeleton key={i} className=\"h-4 w-20\" />\n ))}\n </div>\n </div>\n {Array.from({ length: count }).map((_, i) => (\n <div\n key={i}\n className=\"flex gap-4 border-b border-border-subtle px-4 py-3 last:border-b-0\"\n >\n {Array.from({ length: 5 }).map((_, j) => (\n <Skeleton key={j} className=\"h-4 w-20\" />\n ))}\n </div>\n ))}\n </div>\n </div>\n );\n }\n\n if (variant === \"list\") {\n return (\n <div className={cn(\"space-y-2\", className)}>\n {Array.from({ length: count }).map((_, i) => (\n <div\n key={i}\n className=\"flex items-center gap-3 rounded-lg border border-border-subtle p-4\"\n >\n <Skeleton className=\"h-10 w-10 rounded-full\" />\n <div className=\"flex-1 space-y-2\">\n <Skeleton className=\"h-4 w-1/3\" />\n <Skeleton className=\"h-3 w-2/3\" />\n </div>\n <Skeleton className=\"h-6 w-16 rounded-full\" />\n </div>\n ))}\n </div>\n );\n }\n\n if (variant === \"detail\") {\n return (\n <div className={cn(\"space-y-6\", className)}>\n <div className=\"space-y-2\">\n <Skeleton className=\"h-8 w-1/3\" />\n <Skeleton className=\"h-4 w-1/2\" />\n </div>\n <div className=\"grid gap-4 sm:grid-cols-3\">\n {Array.from({ length: 3 }).map((_, i) => (\n <div key={i} className=\"rounded-lg border border-border-subtle p-4\">\n <Skeleton className=\"h-3 w-20\" />\n <Skeleton className=\"mt-2 h-7 w-16\" />\n </div>\n ))}\n </div>\n <div className=\"rounded-lg border border-border-subtle p-6\">\n <Skeleton className=\"h-5 w-1/4\" />\n <div className=\"mt-4 space-y-3\">\n <Skeleton className=\"h-4 w-full\" />\n <Skeleton className=\"h-4 w-5/6\" />\n <Skeleton className=\"h-4 w-4/6\" />\n </div>\n </div>\n </div>\n );\n }\n\n // Default: card grid\n return (\n <div className={cn(\"grid gap-4 sm:grid-cols-2 lg:grid-cols-3\", className)}>\n {Array.from({ length: count }).map((_, i) => (\n <div key={i} className=\"rounded-lg border border-border-subtle p-4\">\n <Skeleton className=\"h-4 w-2/3\" />\n <Skeleton className=\"mt-2 h-3 w-1/2\" />\n <Skeleton className=\"mt-4 h-8 w-24\" />\n <div className=\"mt-3 flex justify-between\">\n <Skeleton className=\"h-5 w-16 rounded-full\" />\n <Skeleton className=\"h-7 w-14 rounded-md\" />\n </div>\n </div>\n ))}\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -11,7 +11,7 @@ var s = {
|
|
|
11
11
|
xl: "max-w-2xl"
|
|
12
12
|
}, c = ({ open: c, onClose: l, title: u, description: d, children: f, size: p = "md", footer: m }) => {
|
|
13
13
|
let h = n(null);
|
|
14
|
-
|
|
14
|
+
if (t(() => {
|
|
15
15
|
if (!c) return;
|
|
16
16
|
let e = (e) => {
|
|
17
17
|
e.key === "Escape" && l();
|
|
@@ -19,7 +19,8 @@ var s = {
|
|
|
19
19
|
return document.addEventListener("keydown", e), document.body.style.overflow = "hidden", () => {
|
|
20
20
|
document.removeEventListener("keydown", e), document.body.style.overflow = "";
|
|
21
21
|
};
|
|
22
|
-
}, [c, l]), c
|
|
22
|
+
}, [c, l]), !c) return null;
|
|
23
|
+
let g = /* @__PURE__ */ i("div", {
|
|
23
24
|
ref: h,
|
|
24
25
|
className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4",
|
|
25
26
|
onClick: (e) => {
|
|
@@ -57,7 +58,8 @@ var s = {
|
|
|
57
58
|
})
|
|
58
59
|
]
|
|
59
60
|
})
|
|
60
|
-
})
|
|
61
|
+
});
|
|
62
|
+
return o(g, document.body);
|
|
61
63
|
};
|
|
62
64
|
//#endregion
|
|
63
65
|
export { c as Modal };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Modal.js","names":[],"sources":["../../../src/timecampus/components/Modal.tsx"],"sourcesContent":["import { useEffect, useRef, type FC, type ReactNode } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { X } from \"lucide-react\";\nimport { cn } from \"../utils/helpers\";\n\ninterface ModalProps {\n open: boolean;\n onClose: () => void;\n title: string;\n description?: string;\n children: ReactNode;\n size?: \"sm\" | \"md\" | \"lg\" | \"xl\";\n footer?: ReactNode;\n}\n\nconst sizeClasses = {\n sm: \"max-w-sm\",\n md: \"max-w-md\",\n lg: \"max-w-lg\",\n xl: \"max-w-2xl\",\n};\n\nexport const Modal: FC<ModalProps> = ({\n open,\n onClose,\n title,\n description,\n children,\n size = \"md\",\n footer,\n}) => {\n const overlayRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n if (!open) return;\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") onClose();\n };\n document.addEventListener(\"keydown\", handleKeyDown);\n document.body.style.overflow = \"hidden\";\n return () => {\n document.removeEventListener(\"keydown\", handleKeyDown);\n document.body.style.overflow = \"\";\n };\n }, [open, onClose]);\n\n if (!open) return null;\n\n const modalContent = (\n <div\n ref={overlayRef}\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4\"\n onClick={(e) => {\n if (e.target === overlayRef.current) onClose();\n }}\n >\n <div\n className={cn(\n \"w-full rounded-xl border border-border-subtle bg-bg-surface shadow-xl animate-in fade-in-0 zoom-in-95\",\n sizeClasses[size],\n )}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"modal-title\"\n >\n {/* Header */}\n <div className=\"flex items-start justify-between border-b border-border-subtle px-6 py-4\">\n <div>\n <h2\n id=\"modal-title\"\n className=\"text-lg font-semibold text-text-primary\"\n >\n {title}\n </h2>\n {description && (\n <p className=\"mt-0.5 text-sm text-text-secondary\">\n {description}\n </p>\n )}\n </div>\n <button\n onClick={onClose}\n className=\"rounded-md p-1 text-text-secondary hover:bg-bg-sunken hover:text-text-primary\"\n aria-label=\"Close\"\n >\n <X className=\"h-5 w-5\" />\n </button>\n </div>\n\n {/* Body */}\n <div className=\"max-h-[60vh] overflow-y-auto px-6 py-4\">{children}</div>\n\n {/* Footer */}\n {footer && (\n <div className=\"flex items-center justify-end gap-2 border-t border-border-subtle px-6 py-4\">\n {footer}\n </div>\n )}\n </div>\n </div>\n );\n\n return createPortal(modalContent, document.body);\n};\n"],"mappings":";;;;;;AAeA,IAAM,IAAc;CAClB,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;
|
|
1
|
+
{"version":3,"file":"Modal.js","names":[],"sources":["../../../src/timecampus/components/Modal.tsx"],"sourcesContent":["import { useEffect, useRef, type FC, type ReactNode } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { X } from \"lucide-react\";\nimport { cn } from \"../utils/helpers\";\n\ninterface ModalProps {\n open: boolean;\n onClose: () => void;\n title: string;\n description?: string;\n children: ReactNode;\n size?: \"sm\" | \"md\" | \"lg\" | \"xl\";\n footer?: ReactNode;\n}\n\nconst sizeClasses = {\n sm: \"max-w-sm\",\n md: \"max-w-md\",\n lg: \"max-w-lg\",\n xl: \"max-w-2xl\",\n};\n\nexport const Modal: FC<ModalProps> = ({\n open,\n onClose,\n title,\n description,\n children,\n size = \"md\",\n footer,\n}) => {\n const overlayRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n if (!open) return;\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") onClose();\n };\n document.addEventListener(\"keydown\", handleKeyDown);\n document.body.style.overflow = \"hidden\";\n return () => {\n document.removeEventListener(\"keydown\", handleKeyDown);\n document.body.style.overflow = \"\";\n };\n }, [open, onClose]);\n\n if (!open) return null;\n\n const modalContent = (\n <div\n ref={overlayRef}\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4\"\n onClick={(e) => {\n if (e.target === overlayRef.current) onClose();\n }}\n >\n <div\n className={cn(\n \"w-full rounded-xl border border-border-subtle bg-bg-surface shadow-xl animate-in fade-in-0 zoom-in-95\",\n sizeClasses[size],\n )}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"modal-title\"\n >\n {/* Header */}\n <div className=\"flex items-start justify-between border-b border-border-subtle px-6 py-4\">\n <div>\n <h2\n id=\"modal-title\"\n className=\"text-lg font-semibold text-text-primary\"\n >\n {title}\n </h2>\n {description && (\n <p className=\"mt-0.5 text-sm text-text-secondary\">\n {description}\n </p>\n )}\n </div>\n <button\n onClick={onClose}\n className=\"rounded-md p-1 text-text-secondary hover:bg-bg-sunken hover:text-text-primary\"\n aria-label=\"Close\"\n >\n <X className=\"h-5 w-5\" />\n </button>\n </div>\n\n {/* Body */}\n <div className=\"max-h-[60vh] overflow-y-auto px-6 py-4\">{children}</div>\n\n {/* Footer */}\n {footer && (\n <div className=\"flex items-center justify-end gap-2 border-t border-border-subtle px-6 py-4\">\n {footer}\n </div>\n )}\n </div>\n </div>\n );\n\n return createPortal(modalContent, document.body);\n};\n"],"mappings":";;;;;;AAeA,IAAM,IAAc;CAClB,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;AACN,GAEa,KAAyB,EACpC,SACA,YACA,UACA,gBACA,aACA,UAAO,MACP,gBACI;CACJ,IAAM,IAAa,EAAuB,IAAI;CAe9C,IAbA,QAAgB;EACd,IAAI,CAAC,GAAM;EACX,IAAM,KAAiB,MAAqB;GAC1C,AAAI,EAAE,QAAQ,YAAU,EAAQ;EAClC;EAGA,OAFA,SAAS,iBAAiB,WAAW,CAAa,GAClD,SAAS,KAAK,MAAM,WAAW,gBAClB;GAEX,AADA,SAAS,oBAAoB,WAAW,CAAa,GACrD,SAAS,KAAK,MAAM,WAAW;EACjC;CACF,GAAG,CAAC,GAAM,CAAO,CAAC,GAEd,CAAC,GAAM,OAAO;CAElB,IAAM,IACJ,kBAAC,OAAD;EACE,KAAK;EACL,WAAU;EACV,UAAU,MAAM;GACd,AAAI,EAAE,WAAW,EAAW,WAAS,EAAQ;EAC/C;EAEA,UAAA,kBAAC,OAAD;GACE,WAAW,EACT,yGACA,EAAY,EACd;GACA,MAAK;GACL,cAAW;GACX,mBAAgB;GAPlB,UAAA;IAUE,kBAAC,OAAD;KAAK,WAAU;KAAf,UAAA,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;MACE,IAAG;MACH,WAAU;MAET,UAAA;KACC,CAAA,GACH,KACC,kBAAC,KAAD;MAAG,WAAU;MACV,UAAA;KACA,CAAA,CAEF,EAAA,CAAA,GACL,kBAAC,UAAD;MACE,SAAS;MACT,WAAU;MACV,cAAW;MAEX,UAAA,kBAAC,GAAD,EAAG,WAAU,UAAW,CAAA;KAClB,CAAA,CACL;;IAGL,kBAAC,OAAD;KAAK,WAAU;KAA0C;IAAc,CAAA;IAGtE,KACC,kBAAC,OAAD;KAAK,WAAU;KACZ,UAAA;IACE,CAAA;GAEJ;;CACF,CAAA;CAGP,OAAO,EAAa,GAAc,SAAS,IAAI;AACjD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PageLayout.js","names":[],"sources":["../../../src/timecampus/components/PageLayout.tsx"],"sourcesContent":["import type { FC, ReactNode } from \"react\";\nimport { GradientText, PagePurpose } from \"@burdenoff/fe-libs/ui\";\nimport { cn } from \"../utils/helpers\";\n\ninterface PageLayoutProps {\n title: string;\n description?: string;\n /**\n * The page's \"story\": a concise, plain-language explanation of what this\n * page/feature is for and the real-world use-case. Rendered just under the\n * header so every page is self-explanatory to a new user. Prefer this over a\n * terse `description` for primary pages.\n */\n purpose?: ReactNode;\n /** Optional \"Learn more\" link rendered inline with the purpose copy. */\n purposeLearnMoreHref?: string;\n actions?: ReactNode;\n children: ReactNode;\n className?: string;\n}\n\nexport const PageLayout: FC<PageLayoutProps> = ({\n title,\n description,\n purpose,\n purposeLearnMoreHref,\n actions,\n children,\n className,\n}) => {\n return (\n <div className={cn(\"min-h-full p-4 sm:p-6\", className)}>\n <div className=\"mb-6 flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"min-w-0\">\n <h1 className=\"text-2xl font-bold text-text-primary\">\n <GradientText>{title}</GradientText>\n </h1>\n {description && (\n <p className=\"mt-1 text-sm text-text-secondary\">{description}</p>\n )}\n {purpose && (\n <PagePurpose\n className=\"mt-2 max-w-3xl\"\n learnMoreHref={purposeLearnMoreHref}\n >\n {purpose}\n </PagePurpose>\n )}\n </div>\n {actions && (\n <div className=\"flex flex-wrap items-center gap-2\">{actions}</div>\n )}\n </div>\n {children}\n </div>\n );\n};\n"],"mappings":";;;;AAqBA,IAAa,KAAmC,EAC9C,UACA,gBACA,YACA,yBACA,YACA,aACA,mBAGE,kBAAC,OAAD;CAAK,WAAW,EAAG,yBAAyB,
|
|
1
|
+
{"version":3,"file":"PageLayout.js","names":[],"sources":["../../../src/timecampus/components/PageLayout.tsx"],"sourcesContent":["import type { FC, ReactNode } from \"react\";\nimport { GradientText, PagePurpose } from \"@burdenoff/fe-libs/ui\";\nimport { cn } from \"../utils/helpers\";\n\ninterface PageLayoutProps {\n title: string;\n description?: string;\n /**\n * The page's \"story\": a concise, plain-language explanation of what this\n * page/feature is for and the real-world use-case. Rendered just under the\n * header so every page is self-explanatory to a new user. Prefer this over a\n * terse `description` for primary pages.\n */\n purpose?: ReactNode;\n /** Optional \"Learn more\" link rendered inline with the purpose copy. */\n purposeLearnMoreHref?: string;\n actions?: ReactNode;\n children: ReactNode;\n className?: string;\n}\n\nexport const PageLayout: FC<PageLayoutProps> = ({\n title,\n description,\n purpose,\n purposeLearnMoreHref,\n actions,\n children,\n className,\n}) => {\n return (\n <div className={cn(\"min-h-full p-4 sm:p-6\", className)}>\n <div className=\"mb-6 flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"min-w-0\">\n <h1 className=\"text-2xl font-bold text-text-primary\">\n <GradientText>{title}</GradientText>\n </h1>\n {description && (\n <p className=\"mt-1 text-sm text-text-secondary\">{description}</p>\n )}\n {purpose && (\n <PagePurpose\n className=\"mt-2 max-w-3xl\"\n learnMoreHref={purposeLearnMoreHref}\n >\n {purpose}\n </PagePurpose>\n )}\n </div>\n {actions && (\n <div className=\"flex flex-wrap items-center gap-2\">{actions}</div>\n )}\n </div>\n {children}\n </div>\n );\n};\n"],"mappings":";;;;AAqBA,IAAa,KAAmC,EAC9C,UACA,gBACA,YACA,yBACA,YACA,aACA,mBAGE,kBAAC,OAAD;CAAK,WAAW,EAAG,yBAAyB,CAAS;CAArD,UAAA,CACE,kBAAC,OAAD;EAAK,WAAU;EAAf,UAAA,CACE,kBAAC,OAAD;GAAK,WAAU;GAAf,UAAA;IACE,kBAAC,MAAD;KAAI,WAAU;KACZ,UAAA,kBAAC,GAAD,EAAA,UAAe,EAAoB,CAAA;IACjC,CAAA;IACH,KACC,kBAAC,KAAD;KAAG,WAAU;KAAoC,UAAA;IAAe,CAAA;IAEjE,KACC,kBAAC,GAAD;KACE,WAAU;KACV,eAAe;KAEd,UAAA;IACU,CAAA;GAEZ;EACJ,CAAA,GAAA,KACC,kBAAC,OAAD;GAAK,WAAU;GAAqC,UAAA;EAAa,CAAA,CAEhE;CACJ,CAAA,GAAA,CACE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StatCard.js","names":[],"sources":["../../../src/timecampus/components/StatCard.tsx"],"sourcesContent":["import type { FC, ReactNode } from \"react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { cn } from \"../utils/helpers\";\nimport { ArrowUp, ArrowDown } from \"lucide-react\";\n\ninterface StatCardProps {\n title: string;\n value: string | number;\n icon?: ReactNode;\n trend?: { value: number; label: string };\n description?: string;\n className?: string;\n iconBgColor?: string;\n /** Render the featured/hero metric with a richer glass + glow treatment. */\n featured?: boolean;\n}\n\nexport const StatCard: FC<StatCardProps> = ({\n title,\n value,\n icon,\n trend,\n description,\n className,\n iconBgColor,\n featured = false,\n}) => {\n return (\n <GlassCard\n treatment={featured ? \"glass\" : \"metric\"}\n glow={featured}\n className={cn(\"p-4 sm:p-6\", className)}\n >\n <div className=\"flex items-start justify-between\">\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm font-medium text-text-secondary truncate\">\n {title}\n </p>\n <p className=\"mt-2 text-2xl font-bold tabular-nums text-text-primary\">\n {value}\n </p>\n {trend && (\n <div className=\"mt-2 flex items-center gap-1\">\n {trend.value >= 0 ? (\n <ArrowUp className=\"h-3 w-3 text-status-success-text\" />\n ) : (\n <ArrowDown className=\"h-3 w-3 text-status-error-text\" />\n )}\n <span\n className={cn(\n \"text-xs font-medium tabular-nums\",\n trend.value >= 0\n ? \"text-status-success-text\"\n : \"text-status-error-text\",\n )}\n >\n {Math.abs(trend.value)}%\n </span>\n <span className=\"text-xs text-text-secondary\">{trend.label}</span>\n </div>\n )}\n {description && (\n <p className=\"mt-1 text-xs text-text-secondary\">{description}</p>\n )}\n </div>\n {icon && (\n <div\n className={cn(\n \"flex h-10 w-10 shrink-0 items-center justify-center rounded-lg\",\n iconBgColor || \"bg-action-primary-bg/10 text-primary\",\n )}\n >\n {icon}\n </div>\n )}\n </div>\n </GlassCard>\n );\n};\n"],"mappings":";;;;;AAiBA,IAAa,KAA+B,EAC1C,UACA,UACA,SACA,UACA,gBACA,cACA,gBACA,cAAW,SAGT,kBAAC,GAAD;CACE,WAAW,IAAW,UAAU;CAChC,MAAM;CACN,WAAW,EAAG,cAAc,
|
|
1
|
+
{"version":3,"file":"StatCard.js","names":[],"sources":["../../../src/timecampus/components/StatCard.tsx"],"sourcesContent":["import type { FC, ReactNode } from \"react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { cn } from \"../utils/helpers\";\nimport { ArrowUp, ArrowDown } from \"lucide-react\";\n\ninterface StatCardProps {\n title: string;\n value: string | number;\n icon?: ReactNode;\n trend?: { value: number; label: string };\n description?: string;\n className?: string;\n iconBgColor?: string;\n /** Render the featured/hero metric with a richer glass + glow treatment. */\n featured?: boolean;\n}\n\nexport const StatCard: FC<StatCardProps> = ({\n title,\n value,\n icon,\n trend,\n description,\n className,\n iconBgColor,\n featured = false,\n}) => {\n return (\n <GlassCard\n treatment={featured ? \"glass\" : \"metric\"}\n glow={featured}\n className={cn(\"p-4 sm:p-6\", className)}\n >\n <div className=\"flex items-start justify-between\">\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm font-medium text-text-secondary truncate\">\n {title}\n </p>\n <p className=\"mt-2 text-2xl font-bold tabular-nums text-text-primary\">\n {value}\n </p>\n {trend && (\n <div className=\"mt-2 flex items-center gap-1\">\n {trend.value >= 0 ? (\n <ArrowUp className=\"h-3 w-3 text-status-success-text\" />\n ) : (\n <ArrowDown className=\"h-3 w-3 text-status-error-text\" />\n )}\n <span\n className={cn(\n \"text-xs font-medium tabular-nums\",\n trend.value >= 0\n ? \"text-status-success-text\"\n : \"text-status-error-text\",\n )}\n >\n {Math.abs(trend.value)}%\n </span>\n <span className=\"text-xs text-text-secondary\">{trend.label}</span>\n </div>\n )}\n {description && (\n <p className=\"mt-1 text-xs text-text-secondary\">{description}</p>\n )}\n </div>\n {icon && (\n <div\n className={cn(\n \"flex h-10 w-10 shrink-0 items-center justify-center rounded-lg\",\n iconBgColor || \"bg-action-primary-bg/10 text-primary\",\n )}\n >\n {icon}\n </div>\n )}\n </div>\n </GlassCard>\n );\n};\n"],"mappings":";;;;;AAiBA,IAAa,KAA+B,EAC1C,UACA,UACA,SACA,UACA,gBACA,cACA,gBACA,cAAW,SAGT,kBAAC,GAAD;CACE,WAAW,IAAW,UAAU;CAChC,MAAM;CACN,WAAW,EAAG,cAAc,CAAS;CAErC,UAAA,kBAAC,OAAD;EAAK,WAAU;EAAf,UAAA,CACE,kBAAC,OAAD;GAAK,WAAU;GAAf,UAAA;IACE,kBAAC,KAAD;KAAG,WAAU;KACV,UAAA;IACA,CAAA;IACH,kBAAC,KAAD;KAAG,WAAU;KACV,UAAA;IACA,CAAA;IACF,KACC,kBAAC,OAAD;KAAK,WAAU;KAAf,UAAA;MACG,EAAM,SAAS,IACd,kBAAC,GAAD,EAAS,WAAU,mCAAoC,CAAA,IAEvD,kBAAC,GAAD,EAAW,WAAU,iCAAkC,CAAA;MAEzD,kBAAC,QAAD;OACE,WAAW,EACT,oCACA,EAAM,SAAS,IACX,6BACA,wBACN;OANF,UAAA,CAQG,KAAK,IAAI,EAAM,KAAK,GAAE,GACnB;;MACN,kBAAC,QAAD;OAAM,WAAU;OAA+B,UAAA,EAAM;MAAY,CAAA;KAC9D;;IAEN,KACC,kBAAC,KAAD;KAAG,WAAU;KAAoC,UAAA;IAAe,CAAA;GAE/D;EACJ,CAAA,GAAA,KACC,kBAAC,OAAD;GACE,WAAW,EACT,kEACA,KAAe,sCACjB;GAEC,UAAA;EACE,CAAA,CAEJ;;AACI,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StatusBadge.js","names":[],"sources":["../../../src/timecampus/components/StatusBadge.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { cn, getStatusColor, getPriorityColor } from \"../utils/helpers\";\n\ntype BadgeVariant =\n \"success\" | \"warning\" | \"error\" | \"info\" | \"default\" | \"muted\";\n\ninterface StatusBadgeProps {\n label: string;\n variant?: BadgeVariant;\n /** Auto-detect variant from a status string using getStatusColor */\n status?: string;\n /** Auto-detect variant from a priority string using getPriorityColor */\n priority?: string;\n dot?: boolean;\n className?: string;\n}\n\nconst variantStyles: Record<BadgeVariant, string> = {\n success: \"bg-status-success-bg text-status-success-text\",\n warning: \"bg-status-warning-bg text-status-warning-text\",\n error: \"bg-status-error-bg text-status-error-text\",\n info: \"bg-status-info-bg text-status-info-text\",\n default: \"bg-action-primary-bg/10 text-primary\",\n muted: \"bg-bg-sunken text-text-secondary\",\n};\n\nconst dotStyles: Record<BadgeVariant, string> = {\n success: \"bg-status-success-text\",\n warning: \"bg-status-warning-text\",\n error: \"bg-status-error-text\",\n info: \"bg-status-info-text\",\n default: \"bg-action-primary-bg\",\n muted: \"bg-muted-foreground\",\n};\n\nexport const StatusBadge: FC<StatusBadgeProps> = ({\n label,\n variant,\n status,\n priority,\n dot = false,\n className,\n}) => {\n // Resolve variant: explicit > status-based > priority-based > default\n const resolvedVariant: BadgeVariant =\n variant ??\n (status\n ? (getStatusColor(status) as BadgeVariant)\n : priority\n ? (getPriorityColor(priority) as BadgeVariant)\n : \"default\");\n\n return (\n <span\n className={cn(\n \"inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium\",\n variantStyles[resolvedVariant],\n className,\n )}\n >\n {dot && (\n <span\n className={cn(\n \"h-1.5 w-1.5 rounded-full motion-safe:animate-vc-glow-pulse\",\n dotStyles[resolvedVariant],\n )}\n />\n )}\n {label}\n </span>\n );\n};\n"],"mappings":";;;AAiBA,IAAM,IAA8C;CAClD,SAAS;CACT,SAAS;CACT,OAAO;CACP,MAAM;CACN,SAAS;CACT,OAAO;
|
|
1
|
+
{"version":3,"file":"StatusBadge.js","names":[],"sources":["../../../src/timecampus/components/StatusBadge.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { cn, getStatusColor, getPriorityColor } from \"../utils/helpers\";\n\ntype BadgeVariant =\n \"success\" | \"warning\" | \"error\" | \"info\" | \"default\" | \"muted\";\n\ninterface StatusBadgeProps {\n label: string;\n variant?: BadgeVariant;\n /** Auto-detect variant from a status string using getStatusColor */\n status?: string;\n /** Auto-detect variant from a priority string using getPriorityColor */\n priority?: string;\n dot?: boolean;\n className?: string;\n}\n\nconst variantStyles: Record<BadgeVariant, string> = {\n success: \"bg-status-success-bg text-status-success-text\",\n warning: \"bg-status-warning-bg text-status-warning-text\",\n error: \"bg-status-error-bg text-status-error-text\",\n info: \"bg-status-info-bg text-status-info-text\",\n default: \"bg-action-primary-bg/10 text-primary\",\n muted: \"bg-bg-sunken text-text-secondary\",\n};\n\nconst dotStyles: Record<BadgeVariant, string> = {\n success: \"bg-status-success-text\",\n warning: \"bg-status-warning-text\",\n error: \"bg-status-error-text\",\n info: \"bg-status-info-text\",\n default: \"bg-action-primary-bg\",\n muted: \"bg-muted-foreground\",\n};\n\nexport const StatusBadge: FC<StatusBadgeProps> = ({\n label,\n variant,\n status,\n priority,\n dot = false,\n className,\n}) => {\n // Resolve variant: explicit > status-based > priority-based > default\n const resolvedVariant: BadgeVariant =\n variant ??\n (status\n ? (getStatusColor(status) as BadgeVariant)\n : priority\n ? (getPriorityColor(priority) as BadgeVariant)\n : \"default\");\n\n return (\n <span\n className={cn(\n \"inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium\",\n variantStyles[resolvedVariant],\n className,\n )}\n >\n {dot && (\n <span\n className={cn(\n \"h-1.5 w-1.5 rounded-full motion-safe:animate-vc-glow-pulse\",\n dotStyles[resolvedVariant],\n )}\n />\n )}\n {label}\n </span>\n );\n};\n"],"mappings":";;;AAiBA,IAAM,IAA8C;CAClD,SAAS;CACT,SAAS;CACT,OAAO;CACP,MAAM;CACN,SAAS;CACT,OAAO;AACT,GAEM,IAA0C;CAC9C,SAAS;CACT,SAAS;CACT,OAAO;CACP,MAAM;CACN,SAAS;CACT,OAAO;AACT,GAEa,KAAqC,EAChD,UACA,YACA,WACA,aACA,SAAM,IACN,mBACI;CAEJ,IAAM,IACJ,MACC,IACI,EAAe,CAAM,IACtB,IACG,EAAiB,CAAQ,IAC1B;CAER,OACE,kBAAC,QAAD;EACE,WAAW,EACT,mFACA,EAAc,IACd,CACF;EALF,UAAA,CAOG,KACC,kBAAC,QAAD,EACE,WAAW,EACT,8DACA,EAAU,EACZ,EACD,CAAA,GAEF,CACG;;AAEV"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Toast.js","names":[],"sources":["../../../src/timecampus/components/Toast.tsx"],"sourcesContent":["import { useEffect, useCallback, useState, type FC } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { X, CheckCircle, AlertTriangle, Info, XCircle } from \"lucide-react\";\nimport { cn } from \"../utils/helpers\";\n\nexport type ToastVariant = \"success\" | \"error\" | \"warning\" | \"info\";\n\nexport interface ToastData {\n id: string;\n title: string;\n description?: string;\n variant: ToastVariant;\n duration?: number;\n}\n\ninterface ToastProps extends ToastData {\n onDismiss: (id: string) => void;\n}\n\nconst variantConfig: Record<\n ToastVariant,\n { icon: typeof CheckCircle; className: string }\n> = {\n success: {\n icon: CheckCircle,\n className:\n \"border-status-success-bg/40 bg-status-success-bg text-status-success-text\",\n },\n error: {\n icon: XCircle,\n className:\n \"border-status-error-bg/40 bg-status-error-bg text-status-error-text\",\n },\n warning: {\n icon: AlertTriangle,\n className:\n \"border-status-warning-bg/40 bg-status-warning-bg text-status-warning-text\",\n },\n info: {\n icon: Info,\n className:\n \"border-status-info-bg/40 bg-status-info-bg text-status-info-text\",\n },\n};\n\nexport const Toast: FC<ToastProps> = ({\n id,\n title,\n description,\n variant,\n duration = 4000,\n onDismiss,\n}) => {\n const { icon: Icon, className } = variantConfig[variant];\n\n useEffect(() => {\n if (duration > 0) {\n const timer = setTimeout(() => onDismiss(id), duration);\n return () => clearTimeout(timer);\n }\n }, [id, duration, onDismiss]);\n\n return (\n <div\n className={cn(\n \"flex items-start gap-3 rounded-lg border p-4 shadow-pop animate-in slide-in-from-right-full\",\n className,\n )}\n role=\"alert\"\n >\n <Icon className=\"mt-0.5 h-5 w-5 shrink-0\" />\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm font-medium\">{title}</p>\n {description && (\n <p className=\"mt-0.5 text-xs opacity-80\">{description}</p>\n )}\n </div>\n <button\n onClick={() => onDismiss(id)}\n className=\"shrink-0 opacity-60 hover:opacity-100\"\n aria-label=\"Dismiss\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n </div>\n );\n};\n\ninterface ToastContainerProps {\n toasts: ToastData[];\n onDismiss: (id: string) => void;\n}\n\nexport const ToastContainer: FC<ToastContainerProps> = ({\n toasts,\n onDismiss,\n}) => {\n if (toasts.length === 0) return null;\n return createPortal(\n <div className=\"fixed bottom-4 right-4 z-50 flex w-80 flex-col gap-2\">\n {toasts.map((t) => (\n <Toast key={t.id} {...t} onDismiss={onDismiss} />\n ))}\n </div>,\n document.body,\n );\n};\n\n/** Hook for managing toast state */\nexport function useToast() {\n const [toasts, setToasts] = useState<ToastData[]>([]);\n\n const addToast = useCallback((toast: Omit<ToastData, \"id\">) => {\n const id = `toast-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;\n setToasts((prev) => [...prev, { ...toast, id }]);\n return id;\n }, []);\n\n const dismissToast = useCallback((id: string) => {\n setToasts((prev) => prev.filter((t) => t.id !== id));\n }, []);\n\n const clearToasts = useCallback(() => {\n setToasts([]);\n }, []);\n\n return { toasts, addToast, dismissToast, clearToasts };\n}\n"],"mappings":";;;;;;AAmBA,IAAM,IAGF;CACF,SAAS;EACP,MAAM;EACN,WACE;
|
|
1
|
+
{"version":3,"file":"Toast.js","names":[],"sources":["../../../src/timecampus/components/Toast.tsx"],"sourcesContent":["import { useEffect, useCallback, useState, type FC } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { X, CheckCircle, AlertTriangle, Info, XCircle } from \"lucide-react\";\nimport { cn } from \"../utils/helpers\";\n\nexport type ToastVariant = \"success\" | \"error\" | \"warning\" | \"info\";\n\nexport interface ToastData {\n id: string;\n title: string;\n description?: string;\n variant: ToastVariant;\n duration?: number;\n}\n\ninterface ToastProps extends ToastData {\n onDismiss: (id: string) => void;\n}\n\nconst variantConfig: Record<\n ToastVariant,\n { icon: typeof CheckCircle; className: string }\n> = {\n success: {\n icon: CheckCircle,\n className:\n \"border-status-success-bg/40 bg-status-success-bg text-status-success-text\",\n },\n error: {\n icon: XCircle,\n className:\n \"border-status-error-bg/40 bg-status-error-bg text-status-error-text\",\n },\n warning: {\n icon: AlertTriangle,\n className:\n \"border-status-warning-bg/40 bg-status-warning-bg text-status-warning-text\",\n },\n info: {\n icon: Info,\n className:\n \"border-status-info-bg/40 bg-status-info-bg text-status-info-text\",\n },\n};\n\nexport const Toast: FC<ToastProps> = ({\n id,\n title,\n description,\n variant,\n duration = 4000,\n onDismiss,\n}) => {\n const { icon: Icon, className } = variantConfig[variant];\n\n useEffect(() => {\n if (duration > 0) {\n const timer = setTimeout(() => onDismiss(id), duration);\n return () => clearTimeout(timer);\n }\n }, [id, duration, onDismiss]);\n\n return (\n <div\n className={cn(\n \"flex items-start gap-3 rounded-lg border p-4 shadow-pop animate-in slide-in-from-right-full\",\n className,\n )}\n role=\"alert\"\n >\n <Icon className=\"mt-0.5 h-5 w-5 shrink-0\" />\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm font-medium\">{title}</p>\n {description && (\n <p className=\"mt-0.5 text-xs opacity-80\">{description}</p>\n )}\n </div>\n <button\n onClick={() => onDismiss(id)}\n className=\"shrink-0 opacity-60 hover:opacity-100\"\n aria-label=\"Dismiss\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n </div>\n );\n};\n\ninterface ToastContainerProps {\n toasts: ToastData[];\n onDismiss: (id: string) => void;\n}\n\nexport const ToastContainer: FC<ToastContainerProps> = ({\n toasts,\n onDismiss,\n}) => {\n if (toasts.length === 0) return null;\n return createPortal(\n <div className=\"fixed bottom-4 right-4 z-50 flex w-80 flex-col gap-2\">\n {toasts.map((t) => (\n <Toast key={t.id} {...t} onDismiss={onDismiss} />\n ))}\n </div>,\n document.body,\n );\n};\n\n/** Hook for managing toast state */\nexport function useToast() {\n const [toasts, setToasts] = useState<ToastData[]>([]);\n\n const addToast = useCallback((toast: Omit<ToastData, \"id\">) => {\n const id = `toast-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;\n setToasts((prev) => [...prev, { ...toast, id }]);\n return id;\n }, []);\n\n const dismissToast = useCallback((id: string) => {\n setToasts((prev) => prev.filter((t) => t.id !== id));\n }, []);\n\n const clearToasts = useCallback(() => {\n setToasts([]);\n }, []);\n\n return { toasts, addToast, dismissToast, clearToasts };\n}\n"],"mappings":";;;;;;AAmBA,IAAM,IAGF;CACF,SAAS;EACP,MAAM;EACN,WACE;CACJ;CACA,OAAO;EACL,MAAM;EACN,WACE;CACJ;CACA,SAAS;EACP,MAAM;EACN,WACE;CACJ;CACA,MAAM;EACJ,MAAM;EACN,WACE;CACJ;AACF,GAEa,KAAyB,EACpC,OACA,UACA,gBACA,YACA,cAAW,KACX,mBACI;CACJ,IAAM,EAAE,MAAM,GAAM,iBAAc,EAAc;CAShD,OAPA,QAAgB;EACd,IAAI,IAAW,GAAG;GAChB,IAAM,IAAQ,iBAAiB,EAAU,CAAE,GAAG,CAAQ;GACtD,aAAa,aAAa,CAAK;EACjC;CACF,GAAG;EAAC;EAAI;EAAU;CAAS,CAAC,GAG1B,kBAAC,OAAD;EACE,WAAW,EACT,+FACA,CACF;EACA,MAAK;EALP,UAAA;GAOE,kBAAC,GAAD,EAAM,WAAU,0BAA2B,CAAA;GAC3C,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,kBAAC,KAAD;KAAG,WAAU;KAAuB,UAAA;IAAS,CAAA,GAC5C,KACC,kBAAC,KAAD;KAAG,WAAU;KAA6B,UAAA;IAAe,CAAA,CAExD;;GACL,kBAAC,UAAD;IACE,eAAe,EAAU,CAAE;IAC3B,WAAU;IACV,cAAW;IAEX,UAAA,kBAAC,GAAD,EAAG,WAAU,UAAW,CAAA;GAClB,CAAA;EACL;;AAET,GAOa,KAA2C,EACtD,WACA,mBAEI,EAAO,WAAW,IAAU,OACzB,EACL,kBAAC,OAAD;CAAK,WAAU;CACZ,UAAA,EAAO,KAAK,MACX,kBAAC,GAAD;EAAkB,GAAI;EAAc;CAAY,GAApC,EAAE,EAAkC,CACjD;AACE,CAAA,GACL,SAAS,IACX;AAIF,SAAgB,IAAW;CACzB,IAAM,CAAC,GAAQ,KAAa,EAAsB,CAAC,CAAC;CAgBpD,OAAO;EAAE;EAAQ,UAdA,GAAa,MAAiC;GAC7D,IAAM,IAAK,SAAS,KAAK,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC;GAEvE,OADA,GAAW,MAAS,CAAC,GAAG,GAAM;IAAE,GAAG;IAAO;GAAG,CAAC,CAAC,GACxC;EACT,GAAG,CAAC,CAUa;EAAU,cARN,GAAa,MAAe;GAC/C,GAAW,MAAS,EAAK,QAAQ,MAAM,EAAE,OAAO,CAAE,CAAC;EACrD,GAAG,CAAC,CAMuB;EAAc,aAJrB,QAAkB;GACpC,EAAU,CAAC,CAAC;EACd,GAAG,CAAC,CAEqC;CAAY;AACvD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BalanceWheel.js","names":[],"sources":["../../../../src/timecampus/components/charts/BalanceWheel.tsx"],"sourcesContent":["import {\n PolarAngleAxis,\n PolarGrid,\n Radar,\n RadarChart,\n ResponsiveContainer,\n Tooltip,\n} from \"recharts\";\nimport { chartTokens, usePrefersReducedMotion } from \"./chartTheme\";\nimport { ChartEmpty, ChartTooltip, srOnlyStyle } from \"./ChartParts\";\n\n/** One life-domain axis of the wheel: the actual vs the desired share of time. */\nexport interface BalanceWheelDatum {\n /** Stable domain key (e.g. \"FAMILY\"). */\n domain: string;\n /** Human label shown on the axis (e.g. \"Family\"). */\n label: string;\n /** Actual share of covered time, 0..1. */\n actualShare: number;\n /** Desired share the user declared, 0..1. Omit when no target is set. */\n desiredShare?: number | null;\n}\n\nexport interface BalanceWheelProps {\n data: BalanceWheelDatum[];\n /** Pixel height of the wheel. Default 320. */\n height?: number;\n /** Message when there is no covered time to plot. */\n emptyLabel?: string;\n ariaLabel?: string;\n className?: string;\n}\n\n/**\n * BalanceWheel — the signature life-balance visual: a radar of how your finite\n * time actually splits across life domains (solid brand polygon) overlaid with\n * the balance you said you WANT (dashed muted polygon). The gap between the two\n * is the insight. Token-only, theme-aware, and renders an honest empty state —\n * never a fake polygon — when there is no covered time. Values are real shares\n * passed in by the page; this component never fabricates data.\n */\nexport function BalanceWheel({\n data,\n height = 320,\n emptyLabel,\n ariaLabel,\n className,\n}: BalanceWheelProps) {\n const reducedMotion = usePrefersReducedMotion();\n\n const rows = data.filter(\n (d) => Number.isFinite(d.actualShare) && d.actualShare >= 0,\n );\n const total = rows.reduce((sum, d) => sum + d.actualShare, 0);\n const hasDesired = rows.some(\n (d) =>\n typeof d.desiredShare === \"number\" && Number.isFinite(d.desiredShare),\n );\n\n if (rows.length === 0 || total <= 0) {\n return (\n <ChartEmpty height={height} label={emptyLabel} className={className} />\n );\n }\n\n const chartData = rows.map((d) => ({\n label: d.label,\n Actual: Math.round(d.actualShare * 1000) / 10,\n Desired:\n typeof d.desiredShare === \"number\" && Number.isFinite(d.desiredShare)\n ? Math.round(d.desiredShare * 1000) / 10\n : 0,\n }));\n\n const label =\n ariaLabel ?? `Life balance across ${rows.map((d) => d.label).join(\", \")}`;\n\n return (\n <figure role=\"img\" aria-label={label} className={className}>\n <div style={{ height }}>\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n <RadarChart data={chartData} outerRadius=\"72%\">\n <PolarGrid stroke={chartTokens.grid} />\n <PolarAngleAxis\n dataKey=\"label\"\n tick={{ fill: chartTokens.textSecondary, fontSize: 11 }}\n />\n <Tooltip\n cursor={false}\n content={<ChartTooltip valueFormat={(v) => `${v}%`} />}\n />\n {hasDesired && (\n <Radar\n name=\"Desired\"\n dataKey=\"Desired\"\n stroke={chartTokens.textMuted}\n strokeDasharray=\"4 3\"\n strokeWidth={1.5}\n fill=\"transparent\"\n isAnimationActive={!reducedMotion}\n />\n )}\n <Radar\n name=\"Actual\"\n dataKey=\"Actual\"\n stroke={chartTokens.brand}\n strokeWidth={2}\n fill={chartTokens.brand}\n fillOpacity={0.28}\n isAnimationActive={!reducedMotion}\n />\n </RadarChart>\n </ResponsiveContainer>\n </div>\n\n <ul className=\"mt-2 flex flex-wrap items-center justify-center gap-x-4 gap-y-1.5 text-xs text-text-secondary\">\n <li className=\"flex items-center gap-1.5\">\n <span\n aria-hidden=\"true\"\n className=\"h-2.5 w-2.5 rounded-[3px]\"\n style={{ backgroundColor: chartTokens.brand }}\n />\n Actual\n </li>\n {hasDesired && (\n <li className=\"flex items-center gap-1.5\">\n <span\n aria-hidden=\"true\"\n className=\"h-0 w-3.5 border-t-2 border-dashed\"\n style={{ borderColor: chartTokens.textMuted }}\n />\n Desired\n </li>\n )}\n </ul>\n\n <table style={srOnlyStyle}>\n <caption>{label}</caption>\n <thead>\n <tr>\n <th scope=\"col\">Domain</th>\n <th scope=\"col\">Actual share</th>\n {hasDesired && <th scope=\"col\">Desired share</th>}\n </tr>\n </thead>\n <tbody>\n {rows.map((d) => (\n <tr key={d.domain}>\n <th scope=\"row\">{d.label}</th>\n <td>{`${Math.round(d.actualShare * 100)}%`}</td>\n {hasDesired && (\n <td>\n {typeof d.desiredShare === \"number\"\n ? `${Math.round(d.desiredShare * 100)}%`\n : \"—\"}\n </td>\n )}\n </tr>\n ))}\n </tbody>\n </table>\n </figure>\n );\n}\n"],"mappings":";;;;;AAyCA,SAAgB,EAAa,EAC3B,SACA,YAAS,KACT,eACA,cACA,gBACoB;CACpB,IAAM,IAAgB,
|
|
1
|
+
{"version":3,"file":"BalanceWheel.js","names":[],"sources":["../../../../src/timecampus/components/charts/BalanceWheel.tsx"],"sourcesContent":["import {\n PolarAngleAxis,\n PolarGrid,\n Radar,\n RadarChart,\n ResponsiveContainer,\n Tooltip,\n} from \"recharts\";\nimport { chartTokens, usePrefersReducedMotion } from \"./chartTheme\";\nimport { ChartEmpty, ChartTooltip, srOnlyStyle } from \"./ChartParts\";\n\n/** One life-domain axis of the wheel: the actual vs the desired share of time. */\nexport interface BalanceWheelDatum {\n /** Stable domain key (e.g. \"FAMILY\"). */\n domain: string;\n /** Human label shown on the axis (e.g. \"Family\"). */\n label: string;\n /** Actual share of covered time, 0..1. */\n actualShare: number;\n /** Desired share the user declared, 0..1. Omit when no target is set. */\n desiredShare?: number | null;\n}\n\nexport interface BalanceWheelProps {\n data: BalanceWheelDatum[];\n /** Pixel height of the wheel. Default 320. */\n height?: number;\n /** Message when there is no covered time to plot. */\n emptyLabel?: string;\n ariaLabel?: string;\n className?: string;\n}\n\n/**\n * BalanceWheel — the signature life-balance visual: a radar of how your finite\n * time actually splits across life domains (solid brand polygon) overlaid with\n * the balance you said you WANT (dashed muted polygon). The gap between the two\n * is the insight. Token-only, theme-aware, and renders an honest empty state —\n * never a fake polygon — when there is no covered time. Values are real shares\n * passed in by the page; this component never fabricates data.\n */\nexport function BalanceWheel({\n data,\n height = 320,\n emptyLabel,\n ariaLabel,\n className,\n}: BalanceWheelProps) {\n const reducedMotion = usePrefersReducedMotion();\n\n const rows = data.filter(\n (d) => Number.isFinite(d.actualShare) && d.actualShare >= 0,\n );\n const total = rows.reduce((sum, d) => sum + d.actualShare, 0);\n const hasDesired = rows.some(\n (d) =>\n typeof d.desiredShare === \"number\" && Number.isFinite(d.desiredShare),\n );\n\n if (rows.length === 0 || total <= 0) {\n return (\n <ChartEmpty height={height} label={emptyLabel} className={className} />\n );\n }\n\n const chartData = rows.map((d) => ({\n label: d.label,\n Actual: Math.round(d.actualShare * 1000) / 10,\n Desired:\n typeof d.desiredShare === \"number\" && Number.isFinite(d.desiredShare)\n ? Math.round(d.desiredShare * 1000) / 10\n : 0,\n }));\n\n const label =\n ariaLabel ?? `Life balance across ${rows.map((d) => d.label).join(\", \")}`;\n\n return (\n <figure role=\"img\" aria-label={label} className={className}>\n <div style={{ height }}>\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n <RadarChart data={chartData} outerRadius=\"72%\">\n <PolarGrid stroke={chartTokens.grid} />\n <PolarAngleAxis\n dataKey=\"label\"\n tick={{ fill: chartTokens.textSecondary, fontSize: 11 }}\n />\n <Tooltip\n cursor={false}\n content={<ChartTooltip valueFormat={(v) => `${v}%`} />}\n />\n {hasDesired && (\n <Radar\n name=\"Desired\"\n dataKey=\"Desired\"\n stroke={chartTokens.textMuted}\n strokeDasharray=\"4 3\"\n strokeWidth={1.5}\n fill=\"transparent\"\n isAnimationActive={!reducedMotion}\n />\n )}\n <Radar\n name=\"Actual\"\n dataKey=\"Actual\"\n stroke={chartTokens.brand}\n strokeWidth={2}\n fill={chartTokens.brand}\n fillOpacity={0.28}\n isAnimationActive={!reducedMotion}\n />\n </RadarChart>\n </ResponsiveContainer>\n </div>\n\n <ul className=\"mt-2 flex flex-wrap items-center justify-center gap-x-4 gap-y-1.5 text-xs text-text-secondary\">\n <li className=\"flex items-center gap-1.5\">\n <span\n aria-hidden=\"true\"\n className=\"h-2.5 w-2.5 rounded-[3px]\"\n style={{ backgroundColor: chartTokens.brand }}\n />\n Actual\n </li>\n {hasDesired && (\n <li className=\"flex items-center gap-1.5\">\n <span\n aria-hidden=\"true\"\n className=\"h-0 w-3.5 border-t-2 border-dashed\"\n style={{ borderColor: chartTokens.textMuted }}\n />\n Desired\n </li>\n )}\n </ul>\n\n <table style={srOnlyStyle}>\n <caption>{label}</caption>\n <thead>\n <tr>\n <th scope=\"col\">Domain</th>\n <th scope=\"col\">Actual share</th>\n {hasDesired && <th scope=\"col\">Desired share</th>}\n </tr>\n </thead>\n <tbody>\n {rows.map((d) => (\n <tr key={d.domain}>\n <th scope=\"row\">{d.label}</th>\n <td>{`${Math.round(d.actualShare * 100)}%`}</td>\n {hasDesired && (\n <td>\n {typeof d.desiredShare === \"number\"\n ? `${Math.round(d.desiredShare * 100)}%`\n : \"—\"}\n </td>\n )}\n </tr>\n ))}\n </tbody>\n </table>\n </figure>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BarsChart.js","names":[],"sources":["../../../../src/timecampus/components/charts/BarsChart.tsx"],"sourcesContent":["import {\n Bar,\n BarChart,\n CartesianGrid,\n Legend,\n ResponsiveContainer,\n Tooltip,\n XAxis,\n YAxis,\n} from \"recharts\";\nimport {\n chartTokens,\n defaultSeriesColor,\n formatNumber,\n usePrefersReducedMotion,\n type ChartDatum,\n type ChartSeries,\n type ValueFormatter,\n} from \"./chartTheme\";\nimport {\n ChartDataTable,\n ChartEmpty,\n ChartFigure,\n ChartLegend,\n ChartTooltip,\n} from \"./ChartParts\";\n\nexport interface BarsChartProps {\n /** Rows of `{ [xKey]: label, [series.key]: number }`. */\n data: ChartDatum[];\n /** Field holding the category label. */\n xKey: string;\n /** One or more numeric series. */\n series: ChartSeries[];\n /** `vertical` = columns growing up (default); `horizontal` = bars growing right. */\n orientation?: \"vertical\" | \"horizontal\";\n /** Stack the series instead of grouping side-by-side. Default false. */\n stacked?: boolean;\n /** Pixel height. Default 260. */\n height?: number;\n /** Formats numeric values in the axis + tooltip. Default grouped integer. */\n valueFormat?: ValueFormatter;\n /** Formats the category tick + tooltip header. */\n xTickFormat?: (value: string | number) => string;\n /** Show the value-axis grid. Default true. */\n showGrid?: boolean;\n /** Force the legend on/off. Default: on when there are ≥ 2 series. */\n showLegend?: boolean;\n /** Width of the category label column in `horizontal` mode. Default 96. */\n categoryWidth?: number;\n /** Message shown when `data` is empty. */\n emptyLabel?: string;\n /** Accessible name. */\n ariaLabel?: string;\n className?: string;\n}\n\n/**\n * BarsChart — categorical magnitude as columns or horizontal bars, grouped or\n * stacked. Thin (≤ 24px) marks with 4px rounded data-ends, a hairline grid, a\n * per-mark tooltip, a multi-series legend, an empty state and a screen-reader\n * table. Colour is identity only (never re-encoding the bar's length).\n */\nexport function BarsChart({\n data,\n xKey,\n series,\n orientation = \"vertical\",\n stacked = false,\n height = 260,\n valueFormat = formatNumber,\n xTickFormat,\n showGrid = true,\n showLegend,\n categoryWidth = 96,\n emptyLabel,\n ariaLabel,\n className,\n}: BarsChartProps) {\n const reducedMotion = usePrefersReducedMotion();\n\n const resolved = series.map((s, index) => ({\n ...s,\n label: s.label ?? s.key,\n color: s.color ?? defaultSeriesColor(index, series.length),\n }));\n const label =\n ariaLabel ?? `${resolved.map((s) => s.label).join(\", \")} by ${xKey}`;\n\n if (data.length === 0) {\n return (\n <ChartEmpty height={height} label={emptyLabel} className={className} />\n );\n }\n\n const isColumns = orientation === \"vertical\";\n const withLegend = showLegend ?? resolved.length > 1;\n const axisTick = { fill: chartTokens.textMuted, fontSize: 12 };\n const lastIndex = resolved.length - 1;\n\n /** Rounded data-end: columns round the top, horizontal bars the right; a stack\n * rounds only its outermost (last) segment. */\n const radiusFor = (index: number): [number, number, number, number] => {\n const rounded = !stacked || index === lastIndex;\n if (!rounded) return [0, 0, 0, 0];\n return isColumns ? [4, 4, 0, 0] : [0, 4, 4, 0];\n };\n\n const stackId = stacked ? \"stack\" : undefined;\n\n return (\n <ChartFigure\n ariaLabel={label}\n height={height}\n className={className}\n a11y={\n <ChartDataTable\n caption={label}\n xLabel={xKey}\n xKey={xKey}\n data={data}\n series={resolved}\n valueFormat={valueFormat}\n />\n }\n >\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n <BarChart\n data={data}\n layout={isColumns ? \"horizontal\" : \"vertical\"}\n margin={{ top: 8, right: 16, bottom: 4, left: 4 }}\n barCategoryGap={isColumns ? \"24%\" : \"28%\"}\n >\n {showGrid ? (\n <CartesianGrid\n vertical={!isColumns}\n horizontal={isColumns}\n stroke={chartTokens.grid}\n strokeWidth={1}\n />\n ) : null}\n\n {isColumns ? (\n <>\n <XAxis\n dataKey={xKey}\n type=\"category\"\n tickLine={false}\n axisLine={false}\n tick={axisTick}\n tickMargin={8}\n tickFormatter={\n xTickFormat\n ? (value) => xTickFormat(value as string | number)\n : undefined\n }\n />\n <YAxis\n type=\"number\"\n tickLine={false}\n axisLine={false}\n tick={axisTick}\n width={48}\n tickFormatter={(value) => valueFormat(Number(value))}\n />\n </>\n ) : (\n <>\n <XAxis\n type=\"number\"\n tickLine={false}\n axisLine={false}\n tick={axisTick}\n tickFormatter={(value) => valueFormat(Number(value))}\n />\n <YAxis\n dataKey={xKey}\n type=\"category\"\n tickLine={false}\n axisLine={false}\n tick={axisTick}\n width={categoryWidth}\n tickFormatter={\n xTickFormat\n ? (value) => xTickFormat(value as string | number)\n : undefined\n }\n />\n </>\n )}\n\n <Tooltip\n cursor={{ fill: chartTokens.track, opacity: 0.5 }}\n content={\n <ChartTooltip\n valueFormat={valueFormat}\n labelFormat={xTickFormat}\n />\n }\n />\n\n {withLegend ? <Legend content={<ChartLegend />} /> : null}\n\n {resolved.map((s, index) => (\n <Bar\n key={s.key}\n dataKey={s.key}\n name={s.label}\n fill={s.color}\n stackId={stackId}\n maxBarSize={24}\n radius={radiusFor(index)}\n isAnimationActive={!reducedMotion}\n />\n ))}\n </BarChart>\n </ResponsiveContainer>\n </ChartFigure>\n );\n}\n"],"mappings":";;;;;AA+DA,SAAgB,EAAU,EACxB,SACA,SACA,WACA,iBAAc,YACd,aAAU,IACV,YAAS,KACT,iBAAc,GACd,gBACA,cAAW,IACX,eACA,mBAAgB,IAChB,eACA,cACA,gBACiB;CACjB,IAAM,IAAgB,
|
|
1
|
+
{"version":3,"file":"BarsChart.js","names":[],"sources":["../../../../src/timecampus/components/charts/BarsChart.tsx"],"sourcesContent":["import {\n Bar,\n BarChart,\n CartesianGrid,\n Legend,\n ResponsiveContainer,\n Tooltip,\n XAxis,\n YAxis,\n} from \"recharts\";\nimport {\n chartTokens,\n defaultSeriesColor,\n formatNumber,\n usePrefersReducedMotion,\n type ChartDatum,\n type ChartSeries,\n type ValueFormatter,\n} from \"./chartTheme\";\nimport {\n ChartDataTable,\n ChartEmpty,\n ChartFigure,\n ChartLegend,\n ChartTooltip,\n} from \"./ChartParts\";\n\nexport interface BarsChartProps {\n /** Rows of `{ [xKey]: label, [series.key]: number }`. */\n data: ChartDatum[];\n /** Field holding the category label. */\n xKey: string;\n /** One or more numeric series. */\n series: ChartSeries[];\n /** `vertical` = columns growing up (default); `horizontal` = bars growing right. */\n orientation?: \"vertical\" | \"horizontal\";\n /** Stack the series instead of grouping side-by-side. Default false. */\n stacked?: boolean;\n /** Pixel height. Default 260. */\n height?: number;\n /** Formats numeric values in the axis + tooltip. Default grouped integer. */\n valueFormat?: ValueFormatter;\n /** Formats the category tick + tooltip header. */\n xTickFormat?: (value: string | number) => string;\n /** Show the value-axis grid. Default true. */\n showGrid?: boolean;\n /** Force the legend on/off. Default: on when there are ≥ 2 series. */\n showLegend?: boolean;\n /** Width of the category label column in `horizontal` mode. Default 96. */\n categoryWidth?: number;\n /** Message shown when `data` is empty. */\n emptyLabel?: string;\n /** Accessible name. */\n ariaLabel?: string;\n className?: string;\n}\n\n/**\n * BarsChart — categorical magnitude as columns or horizontal bars, grouped or\n * stacked. Thin (≤ 24px) marks with 4px rounded data-ends, a hairline grid, a\n * per-mark tooltip, a multi-series legend, an empty state and a screen-reader\n * table. Colour is identity only (never re-encoding the bar's length).\n */\nexport function BarsChart({\n data,\n xKey,\n series,\n orientation = \"vertical\",\n stacked = false,\n height = 260,\n valueFormat = formatNumber,\n xTickFormat,\n showGrid = true,\n showLegend,\n categoryWidth = 96,\n emptyLabel,\n ariaLabel,\n className,\n}: BarsChartProps) {\n const reducedMotion = usePrefersReducedMotion();\n\n const resolved = series.map((s, index) => ({\n ...s,\n label: s.label ?? s.key,\n color: s.color ?? defaultSeriesColor(index, series.length),\n }));\n const label =\n ariaLabel ?? `${resolved.map((s) => s.label).join(\", \")} by ${xKey}`;\n\n if (data.length === 0) {\n return (\n <ChartEmpty height={height} label={emptyLabel} className={className} />\n );\n }\n\n const isColumns = orientation === \"vertical\";\n const withLegend = showLegend ?? resolved.length > 1;\n const axisTick = { fill: chartTokens.textMuted, fontSize: 12 };\n const lastIndex = resolved.length - 1;\n\n /** Rounded data-end: columns round the top, horizontal bars the right; a stack\n * rounds only its outermost (last) segment. */\n const radiusFor = (index: number): [number, number, number, number] => {\n const rounded = !stacked || index === lastIndex;\n if (!rounded) return [0, 0, 0, 0];\n return isColumns ? [4, 4, 0, 0] : [0, 4, 4, 0];\n };\n\n const stackId = stacked ? \"stack\" : undefined;\n\n return (\n <ChartFigure\n ariaLabel={label}\n height={height}\n className={className}\n a11y={\n <ChartDataTable\n caption={label}\n xLabel={xKey}\n xKey={xKey}\n data={data}\n series={resolved}\n valueFormat={valueFormat}\n />\n }\n >\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n <BarChart\n data={data}\n layout={isColumns ? \"horizontal\" : \"vertical\"}\n margin={{ top: 8, right: 16, bottom: 4, left: 4 }}\n barCategoryGap={isColumns ? \"24%\" : \"28%\"}\n >\n {showGrid ? (\n <CartesianGrid\n vertical={!isColumns}\n horizontal={isColumns}\n stroke={chartTokens.grid}\n strokeWidth={1}\n />\n ) : null}\n\n {isColumns ? (\n <>\n <XAxis\n dataKey={xKey}\n type=\"category\"\n tickLine={false}\n axisLine={false}\n tick={axisTick}\n tickMargin={8}\n tickFormatter={\n xTickFormat\n ? (value) => xTickFormat(value as string | number)\n : undefined\n }\n />\n <YAxis\n type=\"number\"\n tickLine={false}\n axisLine={false}\n tick={axisTick}\n width={48}\n tickFormatter={(value) => valueFormat(Number(value))}\n />\n </>\n ) : (\n <>\n <XAxis\n type=\"number\"\n tickLine={false}\n axisLine={false}\n tick={axisTick}\n tickFormatter={(value) => valueFormat(Number(value))}\n />\n <YAxis\n dataKey={xKey}\n type=\"category\"\n tickLine={false}\n axisLine={false}\n tick={axisTick}\n width={categoryWidth}\n tickFormatter={\n xTickFormat\n ? (value) => xTickFormat(value as string | number)\n : undefined\n }\n />\n </>\n )}\n\n <Tooltip\n cursor={{ fill: chartTokens.track, opacity: 0.5 }}\n content={\n <ChartTooltip\n valueFormat={valueFormat}\n labelFormat={xTickFormat}\n />\n }\n />\n\n {withLegend ? <Legend content={<ChartLegend />} /> : null}\n\n {resolved.map((s, index) => (\n <Bar\n key={s.key}\n dataKey={s.key}\n name={s.label}\n fill={s.color}\n stackId={stackId}\n maxBarSize={24}\n radius={radiusFor(index)}\n isAnimationActive={!reducedMotion}\n />\n ))}\n </BarChart>\n </ResponsiveContainer>\n </ChartFigure>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { CSSProperties, ReactNode } from 'react';
|
|
2
2
|
import { ValueFormatter, ChartDatum, ChartSeries } from './chartTheme';
|
|
3
|
-
|
|
4
3
|
/**
|
|
5
4
|
* Shared chart chrome — the tooltip, legend, empty state and screen-reader data
|
|
6
5
|
* table used by every recharts primitive in this folder. Kept token-only so the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChartParts.js","names":[],"sources":["../../../../src/timecampus/components/charts/ChartParts.tsx"],"sourcesContent":["import type { CSSProperties, ReactNode } from \"react\";\nimport { Inbox } from \"lucide-react\";\nimport { cn } from \"../../utils/helpers\";\nimport {\n chartTokens,\n renderValue,\n type ValueFormatter,\n type ChartDatum,\n type ChartSeries,\n} from \"./chartTheme\";\n\n/**\n * Shared chart chrome — the tooltip, legend, empty state and screen-reader data\n * table used by every recharts primitive in this folder. Kept token-only so the\n * whole set recolours with the theme.\n */\n\n/* -------------------------------------------------------------------------- */\n/* Tooltip */\n/* -------------------------------------------------------------------------- */\n\ninterface TooltipPayloadEntry {\n name?: string | number;\n value?: number | string;\n color?: string;\n dataKey?: string | number;\n}\n\nexport interface ChartTooltipProps {\n /** Injected by recharts. */\n active?: boolean;\n /** Injected by recharts. */\n payload?: TooltipPayloadEntry[];\n /** Injected by recharts. */\n label?: string | number;\n valueFormat?: ValueFormatter;\n labelFormat?: (label: string | number) => string;\n /** Hide the header row (used by single-series / donut tooltips). */\n hideLabel?: boolean;\n}\n\n/**\n * Token-styled tooltip card. Identity rides a colour swatch beside the name;\n * every text run stays in an ink token, never the series colour.\n */\nexport function ChartTooltip({\n active,\n payload,\n label,\n valueFormat,\n labelFormat,\n hideLabel,\n}: ChartTooltipProps) {\n if (!active || !payload || payload.length === 0) return null;\n\n return (\n <div className=\"min-w-32 rounded-lg border border-border-seam bg-bg-surface/95 px-3 py-2 shadow-lg backdrop-blur-sm\">\n {!hideLabel && label !== undefined && label !== \"\" ? (\n <p className=\"mb-1.5 text-xs font-medium text-text-secondary\">\n {labelFormat ? labelFormat(label) : String(label)}\n </p>\n ) : null}\n <ul className=\"space-y-1\">\n {payload.map((entry, index) => (\n <li\n key={`${String(entry.dataKey ?? entry.name ?? index)}`}\n className=\"flex items-center justify-between gap-4 text-sm\"\n >\n <span className=\"flex min-w-0 items-center gap-1.5\">\n <span\n aria-hidden=\"true\"\n className=\"h-2.5 w-2.5 shrink-0 rounded-[3px]\"\n style={{ backgroundColor: entry.color ?? chartTokens.brand }}\n />\n <span className=\"truncate text-text-secondary\">\n {entry.name ?? entry.dataKey}\n </span>\n </span>\n <span className=\"font-semibold tabular-nums text-text-primary\">\n {renderValue(entry.value, valueFormat)}\n </span>\n </li>\n ))}\n </ul>\n </div>\n );\n}\n\n/* -------------------------------------------------------------------------- */\n/* Legend */\n/* -------------------------------------------------------------------------- */\n\ninterface LegendPayloadEntry {\n value?: string | number;\n color?: string;\n}\n\nexport interface ChartLegendProps {\n /** Injected by recharts. */\n payload?: LegendPayloadEntry[];\n}\n\n/**\n * Token-styled legend — always present for ≥ 2 series so identity is never\n * carried by colour alone. Text is muted ink; the colour lives in the swatch.\n */\nexport function ChartLegend({ payload }: ChartLegendProps) {\n if (!payload || payload.length === 0) return null;\n return (\n <ul className=\"flex flex-wrap items-center justify-center gap-x-4 gap-y-1.5 pt-3\">\n {payload.map((entry, index) => (\n <li\n key={`${String(entry.value ?? index)}`}\n className=\"flex items-center gap-1.5 text-xs text-text-secondary\"\n >\n <span\n aria-hidden=\"true\"\n className=\"h-2.5 w-2.5 rounded-[3px]\"\n style={{ backgroundColor: entry.color ?? chartTokens.brand }}\n />\n {entry.value}\n </li>\n ))}\n </ul>\n );\n}\n\n/* -------------------------------------------------------------------------- */\n/* Empty state */\n/* -------------------------------------------------------------------------- */\n\nexport interface ChartEmptyProps {\n /** Height of the placeholder, matched to the chart it stands in for. */\n height?: number;\n /** Muted message. Never fabricate data — show this instead. */\n label?: string;\n className?: string;\n}\n\n/** Calm \"no data yet\" placeholder. Rendered whenever a series is empty. */\nexport function ChartEmpty({\n height = 240,\n label = \"No data yet\",\n className,\n}: ChartEmptyProps) {\n return (\n <div\n className={cn(\n \"flex w-full flex-col items-center justify-center gap-2 rounded-lg border border-dashed border-border-seam bg-bg-sunken/40 text-center\",\n className,\n )}\n style={{ height }}\n >\n <Inbox className=\"h-6 w-6 text-text-muted\" aria-hidden=\"true\" />\n <p className=\"text-sm text-text-muted\">{label}</p>\n </div>\n );\n}\n\n/* -------------------------------------------------------------------------- */\n/* Screen-reader data table (accessibility relief channel) */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Visually-hidden style, applied inline so it never depends on the host's\n * Tailwind build emitting the `sr-only` utility. Keeps the a11y data tables out\n * of sight while readable by assistive tech in every environment.\n */\nexport const srOnlyStyle: CSSProperties = {\n position: \"absolute\",\n width: \"1px\",\n height: \"1px\",\n padding: 0,\n margin: \"-1px\",\n overflow: \"hidden\",\n clipPath: \"inset(50%)\",\n whiteSpace: \"nowrap\",\n borderWidth: 0,\n};\n\nexport interface ChartDataTableProps {\n caption: string;\n xLabel: string;\n xKey: string;\n data: ChartDatum[];\n series: ChartSeries[];\n valueFormat?: ValueFormatter;\n}\n\n/**\n * Visually-hidden table mirroring the plotted data. Satisfies the data-viz\n * \"a table view exists\" accessibility requirement and gives assistive tech the\n * exact numbers behind the marks.\n */\nexport function ChartDataTable({\n caption,\n xLabel,\n xKey,\n data,\n series,\n valueFormat,\n}: ChartDataTableProps) {\n return (\n <table style={srOnlyStyle}>\n <caption>{caption}</caption>\n <thead>\n <tr>\n <th scope=\"col\">{xLabel}</th>\n {series.map((s) => (\n <th scope=\"col\" key={s.key}>\n {s.label ?? s.key}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {data.map((row, index) => (\n <tr key={`${String(row[xKey] ?? index)}`}>\n <th scope=\"row\">{String(row[xKey] ?? \"\")}</th>\n {series.map((s) => (\n <td key={s.key}>\n {renderValue(row[s.key] as number | string, valueFormat)}\n </td>\n ))}\n </tr>\n ))}\n </tbody>\n </table>\n );\n}\n\n/* -------------------------------------------------------------------------- */\n/* Figure wrapper */\n/* -------------------------------------------------------------------------- */\n\nexport interface ChartFigureProps {\n /** Accessible name for the whole figure. */\n ariaLabel: string;\n height: number;\n children: ReactNode;\n /** Screen-reader table + any extra off-screen content. */\n a11y?: ReactNode;\n className?: string;\n}\n\n/** `role=\"img\"` wrapper giving the chart a single accessible name + fixed height. */\nexport function ChartFigure({\n ariaLabel,\n height,\n children,\n a11y,\n className,\n}: ChartFigureProps) {\n return (\n <figure\n role=\"img\"\n aria-label={ariaLabel}\n className={cn(\"w-full\", className)}\n >\n <div style={{ height }}>{children}</div>\n {a11y}\n </figure>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ChartParts.js","names":[],"sources":["../../../../src/timecampus/components/charts/ChartParts.tsx"],"sourcesContent":["import type { CSSProperties, ReactNode } from \"react\";\nimport { Inbox } from \"lucide-react\";\nimport { cn } from \"../../utils/helpers\";\nimport {\n chartTokens,\n renderValue,\n type ValueFormatter,\n type ChartDatum,\n type ChartSeries,\n} from \"./chartTheme\";\n\n/**\n * Shared chart chrome — the tooltip, legend, empty state and screen-reader data\n * table used by every recharts primitive in this folder. Kept token-only so the\n * whole set recolours with the theme.\n */\n\n/* -------------------------------------------------------------------------- */\n/* Tooltip */\n/* -------------------------------------------------------------------------- */\n\ninterface TooltipPayloadEntry {\n name?: string | number;\n value?: number | string;\n color?: string;\n dataKey?: string | number;\n}\n\nexport interface ChartTooltipProps {\n /** Injected by recharts. */\n active?: boolean;\n /** Injected by recharts. */\n payload?: TooltipPayloadEntry[];\n /** Injected by recharts. */\n label?: string | number;\n valueFormat?: ValueFormatter;\n labelFormat?: (label: string | number) => string;\n /** Hide the header row (used by single-series / donut tooltips). */\n hideLabel?: boolean;\n}\n\n/**\n * Token-styled tooltip card. Identity rides a colour swatch beside the name;\n * every text run stays in an ink token, never the series colour.\n */\nexport function ChartTooltip({\n active,\n payload,\n label,\n valueFormat,\n labelFormat,\n hideLabel,\n}: ChartTooltipProps) {\n if (!active || !payload || payload.length === 0) return null;\n\n return (\n <div className=\"min-w-32 rounded-lg border border-border-seam bg-bg-surface/95 px-3 py-2 shadow-lg backdrop-blur-sm\">\n {!hideLabel && label !== undefined && label !== \"\" ? (\n <p className=\"mb-1.5 text-xs font-medium text-text-secondary\">\n {labelFormat ? labelFormat(label) : String(label)}\n </p>\n ) : null}\n <ul className=\"space-y-1\">\n {payload.map((entry, index) => (\n <li\n key={`${String(entry.dataKey ?? entry.name ?? index)}`}\n className=\"flex items-center justify-between gap-4 text-sm\"\n >\n <span className=\"flex min-w-0 items-center gap-1.5\">\n <span\n aria-hidden=\"true\"\n className=\"h-2.5 w-2.5 shrink-0 rounded-[3px]\"\n style={{ backgroundColor: entry.color ?? chartTokens.brand }}\n />\n <span className=\"truncate text-text-secondary\">\n {entry.name ?? entry.dataKey}\n </span>\n </span>\n <span className=\"font-semibold tabular-nums text-text-primary\">\n {renderValue(entry.value, valueFormat)}\n </span>\n </li>\n ))}\n </ul>\n </div>\n );\n}\n\n/* -------------------------------------------------------------------------- */\n/* Legend */\n/* -------------------------------------------------------------------------- */\n\ninterface LegendPayloadEntry {\n value?: string | number;\n color?: string;\n}\n\nexport interface ChartLegendProps {\n /** Injected by recharts. */\n payload?: LegendPayloadEntry[];\n}\n\n/**\n * Token-styled legend — always present for ≥ 2 series so identity is never\n * carried by colour alone. Text is muted ink; the colour lives in the swatch.\n */\nexport function ChartLegend({ payload }: ChartLegendProps) {\n if (!payload || payload.length === 0) return null;\n return (\n <ul className=\"flex flex-wrap items-center justify-center gap-x-4 gap-y-1.5 pt-3\">\n {payload.map((entry, index) => (\n <li\n key={`${String(entry.value ?? index)}`}\n className=\"flex items-center gap-1.5 text-xs text-text-secondary\"\n >\n <span\n aria-hidden=\"true\"\n className=\"h-2.5 w-2.5 rounded-[3px]\"\n style={{ backgroundColor: entry.color ?? chartTokens.brand }}\n />\n {entry.value}\n </li>\n ))}\n </ul>\n );\n}\n\n/* -------------------------------------------------------------------------- */\n/* Empty state */\n/* -------------------------------------------------------------------------- */\n\nexport interface ChartEmptyProps {\n /** Height of the placeholder, matched to the chart it stands in for. */\n height?: number;\n /** Muted message. Never fabricate data — show this instead. */\n label?: string;\n className?: string;\n}\n\n/** Calm \"no data yet\" placeholder. Rendered whenever a series is empty. */\nexport function ChartEmpty({\n height = 240,\n label = \"No data yet\",\n className,\n}: ChartEmptyProps) {\n return (\n <div\n className={cn(\n \"flex w-full flex-col items-center justify-center gap-2 rounded-lg border border-dashed border-border-seam bg-bg-sunken/40 text-center\",\n className,\n )}\n style={{ height }}\n >\n <Inbox className=\"h-6 w-6 text-text-muted\" aria-hidden=\"true\" />\n <p className=\"text-sm text-text-muted\">{label}</p>\n </div>\n );\n}\n\n/* -------------------------------------------------------------------------- */\n/* Screen-reader data table (accessibility relief channel) */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Visually-hidden style, applied inline so it never depends on the host's\n * Tailwind build emitting the `sr-only` utility. Keeps the a11y data tables out\n * of sight while readable by assistive tech in every environment.\n */\nexport const srOnlyStyle: CSSProperties = {\n position: \"absolute\",\n width: \"1px\",\n height: \"1px\",\n padding: 0,\n margin: \"-1px\",\n overflow: \"hidden\",\n clipPath: \"inset(50%)\",\n whiteSpace: \"nowrap\",\n borderWidth: 0,\n};\n\nexport interface ChartDataTableProps {\n caption: string;\n xLabel: string;\n xKey: string;\n data: ChartDatum[];\n series: ChartSeries[];\n valueFormat?: ValueFormatter;\n}\n\n/**\n * Visually-hidden table mirroring the plotted data. Satisfies the data-viz\n * \"a table view exists\" accessibility requirement and gives assistive tech the\n * exact numbers behind the marks.\n */\nexport function ChartDataTable({\n caption,\n xLabel,\n xKey,\n data,\n series,\n valueFormat,\n}: ChartDataTableProps) {\n return (\n <table style={srOnlyStyle}>\n <caption>{caption}</caption>\n <thead>\n <tr>\n <th scope=\"col\">{xLabel}</th>\n {series.map((s) => (\n <th scope=\"col\" key={s.key}>\n {s.label ?? s.key}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {data.map((row, index) => (\n <tr key={`${String(row[xKey] ?? index)}`}>\n <th scope=\"row\">{String(row[xKey] ?? \"\")}</th>\n {series.map((s) => (\n <td key={s.key}>\n {renderValue(row[s.key] as number | string, valueFormat)}\n </td>\n ))}\n </tr>\n ))}\n </tbody>\n </table>\n );\n}\n\n/* -------------------------------------------------------------------------- */\n/* Figure wrapper */\n/* -------------------------------------------------------------------------- */\n\nexport interface ChartFigureProps {\n /** Accessible name for the whole figure. */\n ariaLabel: string;\n height: number;\n children: ReactNode;\n /** Screen-reader table + any extra off-screen content. */\n a11y?: ReactNode;\n className?: string;\n}\n\n/** `role=\"img\"` wrapper giving the chart a single accessible name + fixed height. */\nexport function ChartFigure({\n ariaLabel,\n height,\n children,\n a11y,\n className,\n}: ChartFigureProps) {\n return (\n <figure\n role=\"img\"\n aria-label={ariaLabel}\n className={cn(\"w-full\", className)}\n >\n <div style={{ height }}>{children}</div>\n {a11y}\n </figure>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DonutChart.js","names":[],"sources":["../../../../src/timecampus/components/charts/DonutChart.tsx"],"sourcesContent":["import { Cell, Pie, PieChart, ResponsiveContainer, Tooltip } from \"recharts\";\nimport { cn } from \"../../utils/helpers\";\nimport {\n chartTokens,\n formatNumber,\n renderValue,\n seriesColor,\n usePrefersReducedMotion,\n type DonutSlice,\n type ValueFormatter,\n} from \"./chartTheme\";\nimport { ChartEmpty, ChartTooltip, srOnlyStyle } from \"./ChartParts\";\n\nexport interface DonutChartProps {\n /** Slices `{ name, value, color? }`. */\n data: DonutSlice[];\n /** Small caption under the centre value (e.g. \"Total assets\"). */\n centerLabel?: string;\n /**\n * Big centre value. If omitted while `centerLabel` is set, the summed total is\n * shown. A number is run through `valueFormat`.\n */\n centerValue?: string | number;\n /** Pixel height of the ring. Default 240. */\n height?: number;\n /** Ring thickness as a fraction of the radius (0–1). Default 0.42. */\n thickness?: number;\n /** Formats slice + centre values. Default grouped integer. */\n valueFormat?: ValueFormatter;\n /** Show the legend list under the ring. Default true. */\n showLegend?: boolean;\n /** Message shown when there are no slices (or all zero). */\n emptyLabel?: string;\n /** Accessible name. */\n ariaLabel?: string;\n className?: string;\n}\n\n/**\n * DonutChart — a category/status mix as a ring with a center total. Slices take\n * the categorical ramp in fixed order, identity is carried by an always-present\n * legend (never colour alone), and hover reveals a token-styled tooltip.\n * Renders an empty state — never a fake ring — when there is nothing to show.\n */\nexport function DonutChart({\n data,\n centerLabel,\n centerValue,\n height = 240,\n thickness = 0.42,\n valueFormat = formatNumber,\n showLegend = true,\n emptyLabel,\n ariaLabel,\n className,\n}: DonutChartProps) {\n const reducedMotion = usePrefersReducedMotion();\n\n const slices = data\n .filter(\n (slice) =>\n typeof slice.value === \"number\" &&\n Number.isFinite(slice.value) &&\n slice.value > 0,\n )\n .map((slice, index) => ({\n ...slice,\n color: slice.color ?? seriesColor(index),\n }));\n\n const total = slices.reduce((sum, slice) => sum + slice.value, 0);\n\n if (slices.length === 0 || total <= 0) {\n return (\n <ChartEmpty height={height} label={emptyLabel} className={className} />\n );\n }\n\n const label =\n ariaLabel ??\n `${centerLabel ?? \"Distribution\"}: ${slices.map((s) => s.name).join(\", \")}`;\n const outerPct = 82;\n const innerPct = Math.max(0, Math.round(outerPct * (1 - thickness)));\n const showCenter = centerLabel !== undefined || centerValue !== undefined;\n const resolvedCenter =\n centerValue !== undefined\n ? renderValue(centerValue, valueFormat)\n : valueFormat(total);\n\n return (\n <figure role=\"img\" aria-label={label} className={cn(\"w-full\", className)}>\n <div className=\"relative mx-auto\" style={{ height }}>\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n <PieChart>\n <Tooltip\n cursor={false}\n content={<ChartTooltip valueFormat={valueFormat} hideLabel />}\n />\n <Pie\n data={slices}\n dataKey=\"value\"\n nameKey=\"name\"\n cx=\"50%\"\n cy=\"50%\"\n innerRadius={`${innerPct}%`}\n outerRadius={`${outerPct}%`}\n paddingAngle={1.5}\n stroke={chartTokens.surface}\n strokeWidth={2}\n isAnimationActive={!reducedMotion}\n >\n {slices.map((slice) => (\n <Cell key={slice.name} fill={slice.color} />\n ))}\n </Pie>\n </PieChart>\n </ResponsiveContainer>\n\n {showCenter ? (\n <div className=\"pointer-events-none absolute inset-0 flex flex-col items-center justify-center text-center\">\n <span className=\"text-2xl font-bold text-text-primary\">\n {resolvedCenter}\n </span>\n {centerLabel ? (\n <span className=\"mt-0.5 max-w-[70%] text-xs text-text-secondary\">\n {centerLabel}\n </span>\n ) : null}\n </div>\n ) : null}\n </div>\n\n {showLegend ? (\n <ul className=\"mt-3 flex flex-wrap items-center justify-center gap-x-4 gap-y-1.5\">\n {slices.map((slice) => (\n <li\n key={slice.name}\n className=\"flex items-center gap-1.5 text-xs text-text-secondary\"\n >\n <span\n aria-hidden=\"true\"\n className=\"h-2.5 w-2.5 rounded-[3px]\"\n style={{ backgroundColor: slice.color }}\n />\n <span>{slice.name}</span>\n <span className=\"tabular-nums text-text-muted\">\n {Math.round((slice.value / total) * 100)}%\n </span>\n </li>\n ))}\n </ul>\n ) : null}\n\n <table style={srOnlyStyle}>\n <caption>{label}</caption>\n <thead>\n <tr>\n <th scope=\"col\">Category</th>\n <th scope=\"col\">Value</th>\n <th scope=\"col\">Share</th>\n </tr>\n </thead>\n <tbody>\n {slices.map((slice) => (\n <tr key={slice.name}>\n <th scope=\"row\">{slice.name}</th>\n <td>{valueFormat(slice.value)}</td>\n <td>{Math.round((slice.value / total) * 100)}%</td>\n </tr>\n ))}\n </tbody>\n </table>\n </figure>\n );\n}\n"],"mappings":";;;;;;AA4CA,SAAgB,EAAW,EACzB,SACA,gBACA,gBACA,YAAS,KACT,eAAY,KACZ,iBAAc,GACd,gBAAa,IACb,eACA,cACA,gBACkB;CAClB,IAAM,IAAgB,
|
|
1
|
+
{"version":3,"file":"DonutChart.js","names":[],"sources":["../../../../src/timecampus/components/charts/DonutChart.tsx"],"sourcesContent":["import { Cell, Pie, PieChart, ResponsiveContainer, Tooltip } from \"recharts\";\nimport { cn } from \"../../utils/helpers\";\nimport {\n chartTokens,\n formatNumber,\n renderValue,\n seriesColor,\n usePrefersReducedMotion,\n type DonutSlice,\n type ValueFormatter,\n} from \"./chartTheme\";\nimport { ChartEmpty, ChartTooltip, srOnlyStyle } from \"./ChartParts\";\n\nexport interface DonutChartProps {\n /** Slices `{ name, value, color? }`. */\n data: DonutSlice[];\n /** Small caption under the centre value (e.g. \"Total assets\"). */\n centerLabel?: string;\n /**\n * Big centre value. If omitted while `centerLabel` is set, the summed total is\n * shown. A number is run through `valueFormat`.\n */\n centerValue?: string | number;\n /** Pixel height of the ring. Default 240. */\n height?: number;\n /** Ring thickness as a fraction of the radius (0–1). Default 0.42. */\n thickness?: number;\n /** Formats slice + centre values. Default grouped integer. */\n valueFormat?: ValueFormatter;\n /** Show the legend list under the ring. Default true. */\n showLegend?: boolean;\n /** Message shown when there are no slices (or all zero). */\n emptyLabel?: string;\n /** Accessible name. */\n ariaLabel?: string;\n className?: string;\n}\n\n/**\n * DonutChart — a category/status mix as a ring with a center total. Slices take\n * the categorical ramp in fixed order, identity is carried by an always-present\n * legend (never colour alone), and hover reveals a token-styled tooltip.\n * Renders an empty state — never a fake ring — when there is nothing to show.\n */\nexport function DonutChart({\n data,\n centerLabel,\n centerValue,\n height = 240,\n thickness = 0.42,\n valueFormat = formatNumber,\n showLegend = true,\n emptyLabel,\n ariaLabel,\n className,\n}: DonutChartProps) {\n const reducedMotion = usePrefersReducedMotion();\n\n const slices = data\n .filter(\n (slice) =>\n typeof slice.value === \"number\" &&\n Number.isFinite(slice.value) &&\n slice.value > 0,\n )\n .map((slice, index) => ({\n ...slice,\n color: slice.color ?? seriesColor(index),\n }));\n\n const total = slices.reduce((sum, slice) => sum + slice.value, 0);\n\n if (slices.length === 0 || total <= 0) {\n return (\n <ChartEmpty height={height} label={emptyLabel} className={className} />\n );\n }\n\n const label =\n ariaLabel ??\n `${centerLabel ?? \"Distribution\"}: ${slices.map((s) => s.name).join(\", \")}`;\n const outerPct = 82;\n const innerPct = Math.max(0, Math.round(outerPct * (1 - thickness)));\n const showCenter = centerLabel !== undefined || centerValue !== undefined;\n const resolvedCenter =\n centerValue !== undefined\n ? renderValue(centerValue, valueFormat)\n : valueFormat(total);\n\n return (\n <figure role=\"img\" aria-label={label} className={cn(\"w-full\", className)}>\n <div className=\"relative mx-auto\" style={{ height }}>\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n <PieChart>\n <Tooltip\n cursor={false}\n content={<ChartTooltip valueFormat={valueFormat} hideLabel />}\n />\n <Pie\n data={slices}\n dataKey=\"value\"\n nameKey=\"name\"\n cx=\"50%\"\n cy=\"50%\"\n innerRadius={`${innerPct}%`}\n outerRadius={`${outerPct}%`}\n paddingAngle={1.5}\n stroke={chartTokens.surface}\n strokeWidth={2}\n isAnimationActive={!reducedMotion}\n >\n {slices.map((slice) => (\n <Cell key={slice.name} fill={slice.color} />\n ))}\n </Pie>\n </PieChart>\n </ResponsiveContainer>\n\n {showCenter ? (\n <div className=\"pointer-events-none absolute inset-0 flex flex-col items-center justify-center text-center\">\n <span className=\"text-2xl font-bold text-text-primary\">\n {resolvedCenter}\n </span>\n {centerLabel ? (\n <span className=\"mt-0.5 max-w-[70%] text-xs text-text-secondary\">\n {centerLabel}\n </span>\n ) : null}\n </div>\n ) : null}\n </div>\n\n {showLegend ? (\n <ul className=\"mt-3 flex flex-wrap items-center justify-center gap-x-4 gap-y-1.5\">\n {slices.map((slice) => (\n <li\n key={slice.name}\n className=\"flex items-center gap-1.5 text-xs text-text-secondary\"\n >\n <span\n aria-hidden=\"true\"\n className=\"h-2.5 w-2.5 rounded-[3px]\"\n style={{ backgroundColor: slice.color }}\n />\n <span>{slice.name}</span>\n <span className=\"tabular-nums text-text-muted\">\n {Math.round((slice.value / total) * 100)}%\n </span>\n </li>\n ))}\n </ul>\n ) : null}\n\n <table style={srOnlyStyle}>\n <caption>{label}</caption>\n <thead>\n <tr>\n <th scope=\"col\">Category</th>\n <th scope=\"col\">Value</th>\n <th scope=\"col\">Share</th>\n </tr>\n </thead>\n <tbody>\n {slices.map((slice) => (\n <tr key={slice.name}>\n <th scope=\"row\">{slice.name}</th>\n <td>{valueFormat(slice.value)}</td>\n <td>{Math.round((slice.value / total) * 100)}%</td>\n </tr>\n ))}\n </tbody>\n </table>\n </figure>\n );\n}\n"],"mappings":"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"}
|
|
@@ -30,39 +30,39 @@ function f({ data: e, rowLabels: l, columnLabels: f, max: m, rowsLabel: h = "Row
|
|
|
30
30
|
label: b,
|
|
31
31
|
className: S
|
|
32
32
|
});
|
|
33
|
-
let O = (e) => e === null ? -1 : e / E, k = `auto repeat(${w}, minmax(1.5rem, 1fr))`, A = `auto repeat(${C}, minmax(1.25rem, 1fr))
|
|
33
|
+
let O = (e) => e === null ? -1 : e / E, k = `auto repeat(${w}, minmax(1.5rem, 1fr))`, A = `auto repeat(${C}, minmax(1.25rem, 1fr))`, j = /* @__PURE__ */ c(o, { children: [v ? /* @__PURE__ */ c("div", {
|
|
34
|
+
className: "mt-3 flex items-center justify-end gap-1.5 text-xs text-text-secondary",
|
|
35
|
+
children: [
|
|
36
|
+
/* @__PURE__ */ s("span", { children: _[0] }),
|
|
37
|
+
d.map((e) => /* @__PURE__ */ s("span", {
|
|
38
|
+
"aria-hidden": "true",
|
|
39
|
+
className: "h-2.5 w-2.5 rounded-[3px]",
|
|
40
|
+
style: { backgroundColor: u(e) }
|
|
41
|
+
}, e)),
|
|
42
|
+
/* @__PURE__ */ s("span", { children: _[1] })
|
|
43
|
+
]
|
|
44
|
+
}) : null, /* @__PURE__ */ c("table", {
|
|
45
|
+
style: a,
|
|
46
|
+
children: [
|
|
47
|
+
/* @__PURE__ */ s("caption", { children: D }),
|
|
48
|
+
/* @__PURE__ */ s("thead", { children: /* @__PURE__ */ c("tr", { children: [/* @__PURE__ */ s("th", {
|
|
49
|
+
scope: "col",
|
|
50
|
+
children: h
|
|
51
|
+
}), f.map((e, t) => /* @__PURE__ */ s("th", {
|
|
52
|
+
scope: "col",
|
|
53
|
+
children: e
|
|
54
|
+
}, `${e}-${t}`))] }) }),
|
|
55
|
+
/* @__PURE__ */ s("tbody", { children: l.map((e, t) => /* @__PURE__ */ c("tr", { children: [/* @__PURE__ */ s("th", {
|
|
56
|
+
scope: "row",
|
|
57
|
+
children: e
|
|
58
|
+
}), f.map((e, r) => /* @__PURE__ */ s("td", { children: n(T(t, r), g) }, `${e}-${r}`))] }, `${e}-${t}`)) })
|
|
59
|
+
]
|
|
60
|
+
})] });
|
|
34
61
|
return /* @__PURE__ */ s(i, {
|
|
35
62
|
ariaLabel: D,
|
|
36
63
|
height: y,
|
|
37
64
|
className: S,
|
|
38
|
-
a11y:
|
|
39
|
-
className: "mt-3 flex items-center justify-end gap-1.5 text-xs text-text-secondary",
|
|
40
|
-
children: [
|
|
41
|
-
/* @__PURE__ */ s("span", { children: _[0] }),
|
|
42
|
-
d.map((e) => /* @__PURE__ */ s("span", {
|
|
43
|
-
"aria-hidden": "true",
|
|
44
|
-
className: "h-2.5 w-2.5 rounded-[3px]",
|
|
45
|
-
style: { backgroundColor: u(e) }
|
|
46
|
-
}, e)),
|
|
47
|
-
/* @__PURE__ */ s("span", { children: _[1] })
|
|
48
|
-
]
|
|
49
|
-
}) : null, /* @__PURE__ */ c("table", {
|
|
50
|
-
style: a,
|
|
51
|
-
children: [
|
|
52
|
-
/* @__PURE__ */ s("caption", { children: D }),
|
|
53
|
-
/* @__PURE__ */ s("thead", { children: /* @__PURE__ */ c("tr", { children: [/* @__PURE__ */ s("th", {
|
|
54
|
-
scope: "col",
|
|
55
|
-
children: h
|
|
56
|
-
}), f.map((e, t) => /* @__PURE__ */ s("th", {
|
|
57
|
-
scope: "col",
|
|
58
|
-
children: e
|
|
59
|
-
}, `${e}-${t}`))] }) }),
|
|
60
|
-
/* @__PURE__ */ s("tbody", { children: l.map((e, t) => /* @__PURE__ */ c("tr", { children: [/* @__PURE__ */ s("th", {
|
|
61
|
-
scope: "row",
|
|
62
|
-
children: e
|
|
63
|
-
}), f.map((e, r) => /* @__PURE__ */ s("td", { children: n(T(t, r), g) }, `${e}-${r}`))] }, `${e}-${t}`)) })
|
|
64
|
-
]
|
|
65
|
-
})] }),
|
|
65
|
+
a11y: j,
|
|
66
66
|
children: /* @__PURE__ */ s("div", {
|
|
67
67
|
className: "h-full w-full overflow-x-auto",
|
|
68
68
|
children: /* @__PURE__ */ c("div", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Heatmap.js","names":[],"sources":["../../../../src/timecampus/components/charts/Heatmap.tsx"],"sourcesContent":["import {\n chartTokens,\n formatNumber,\n renderValue,\n type ValueFormatter,\n} from \"./chartTheme\";\nimport { ChartEmpty, ChartFigure, srOnlyStyle } from \"./ChartParts\";\n\n/** A single heatmap value: a number, or `null`/`undefined` for a no-data cell. */\nexport type HeatmapValue = number | null | undefined;\n\nexport interface HeatmapProps {\n /**\n * Row-major matrix of values — one inner array per row (e.g. day), each entry\n * a column (e.g. hour or bucket). Ragged rows are tolerated: missing entries\n * read as no-data cells. `null`/`undefined` render as an empty track cell.\n */\n data: HeatmapValue[][];\n /** Row labels, top → bottom (e.g. `['Mon', … 'Sun']`). Drives the row count. */\n rowLabels: string[];\n /** Column labels, left → right (e.g. hours or buckets). Drives the column count. */\n columnLabels: string[];\n /**\n * Value mapped to full intensity. Defaults to the largest finite value in\n * `data`. Cells scale linearly from the track (0) to the brand token (`max`).\n */\n max?: number;\n /** Header for the row axis — used in the sr-only table's corner cell. Default `Row`. */\n rowsLabel?: string;\n /** Formats a cell value in the native tooltip + sr-only table. Default grouped integer. */\n valueFormat?: ValueFormatter;\n /** Low → high captions bracketing the intensity legend. Default `['Less', 'More']`. */\n legendLabels?: [string, string];\n /** Show the intensity legend under the grid. Default true. */\n showLegend?: boolean;\n /** Pixel height of the grid area. Cells size to the grid — never fixed px. Default 260. */\n height?: number;\n /** Message shown when there is nothing to plot. */\n emptyLabel?: string;\n /** Accessible name for the figure. Default derived from the axis labels. */\n ariaLabel?: string;\n className?: string;\n}\n\nconst clamp01 = (value: number): number => Math.min(1, Math.max(0, value));\n\n/**\n * Blend the brand token into the sunken track by `intensity` (0–1) using\n * `color-mix` — both operands are live design-system tokens, so the ramp is\n * theme-aware and never a raw colour. A no-data cell (`intensity < 0`) stays on\n * the bare track; any positive value gets a visible floor so faint counts read.\n */\nfunction cellBackground(intensity: number): string {\n if (intensity < 0 || intensity === 0) return chartTokens.track;\n const pct = Math.max(8, Math.round(clamp01(intensity) * 100));\n return `color-mix(in oklab, ${chartTokens.brand} ${pct}%, ${chartTokens.track})`;\n}\n\nconst LEGEND_STOPS = [0, 0.25, 0.5, 0.75, 1] as const;\n\n/**\n * Heatmap — a token-only matrix of intensity cells (e.g. activity by day × hour\n * for the Work Patterns page). Cell colour blends the brand token into the\n * sunken track by value, so it recolours with the theme and never carries a raw\n * colour. Cells are sized entirely by CSS grid (no fixed px) and the grid scrolls\n * horizontally when there are many columns. Ships a legend, a native per-cell\n * tooltip, an empty state and a screen-reader data table, wrapped in a\n * `ChartFigure` for a single accessible name — matching the other primitives.\n */\nexport function Heatmap({\n data,\n rowLabels,\n columnLabels,\n max,\n rowsLabel = \"Row\",\n valueFormat = formatNumber,\n legendLabels = [\"Less\", \"More\"],\n showLegend = true,\n height = 260,\n emptyLabel,\n ariaLabel,\n className,\n}: HeatmapProps) {\n const rows = rowLabels.length;\n const cols = columnLabels.length;\n\n const valueAt = (r: number, c: number): number | null => {\n const cell = data[r]?.[c];\n return typeof cell === \"number\" && Number.isFinite(cell) ? cell : null;\n };\n\n let resolvedMax = max ?? 0;\n if (max === undefined) {\n for (let r = 0; r < rows; r += 1) {\n for (let c = 0; c < cols; c += 1) {\n const value = valueAt(r, c);\n if (value !== null && value > resolvedMax) resolvedMax = value;\n }\n }\n }\n\n const label =\n ariaLabel ??\n `Heatmap of ${rowLabels.join(\", \")} by ${columnLabels.join(\", \")}`;\n\n if (rows === 0 || cols === 0 || resolvedMax <= 0) {\n return (\n <ChartEmpty height={height} label={emptyLabel} className={className} />\n );\n }\n\n const intensityOf = (value: number | null): number =>\n value === null ? -1 : value / resolvedMax;\n\n const gridTemplateColumns = `auto repeat(${cols}, minmax(1.5rem, 1fr))`;\n const gridTemplateRows = `auto repeat(${rows}, minmax(1.25rem, 1fr))`;\n\n const a11y = (\n <>\n {showLegend ? (\n <div className=\"mt-3 flex items-center justify-end gap-1.5 text-xs text-text-secondary\">\n <span>{legendLabels[0]}</span>\n {LEGEND_STOPS.map((stop) => (\n <span\n key={stop}\n aria-hidden=\"true\"\n className=\"h-2.5 w-2.5 rounded-[3px]\"\n style={{ backgroundColor: cellBackground(stop) }}\n />\n ))}\n <span>{legendLabels[1]}</span>\n </div>\n ) : null}\n\n <table style={srOnlyStyle}>\n <caption>{label}</caption>\n <thead>\n <tr>\n <th scope=\"col\">{rowsLabel}</th>\n {columnLabels.map((columnLabel, c) => (\n <th scope=\"col\" key={`${columnLabel}-${c}`}>\n {columnLabel}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {rowLabels.map((rowLabel, r) => (\n <tr key={`${rowLabel}-${r}`}>\n <th scope=\"row\">{rowLabel}</th>\n {columnLabels.map((columnLabel, c) => (\n <td key={`${columnLabel}-${c}`}>\n {renderValue(valueAt(r, c), valueFormat)}\n </td>\n ))}\n </tr>\n ))}\n </tbody>\n </table>\n </>\n );\n\n return (\n <ChartFigure\n ariaLabel={label}\n height={height}\n className={className}\n a11y={a11y}\n >\n <div className=\"h-full w-full overflow-x-auto\">\n <div\n className=\"grid h-full min-w-full gap-1\"\n style={{ gridTemplateColumns, gridTemplateRows }}\n >\n {/* Header row: empty corner + column labels. */}\n <span aria-hidden=\"true\" />\n {columnLabels.map((columnLabel, c) => (\n <span\n key={`col-${columnLabel}-${c}`}\n className=\"truncate px-0.5 text-center text-[10px] leading-none text-text-muted\"\n >\n {columnLabel}\n </span>\n ))}\n\n {/* Body rows: row label + its cells. */}\n {rowLabels.map((rowLabel, r) => (\n <FragmentRow\n key={`row-${rowLabel}-${r}`}\n rowLabel={rowLabel}\n cells={columnLabels.map((columnLabel, c) => {\n const value = valueAt(r, c);\n return {\n key: `${columnLabel}-${c}`,\n value,\n intensity: intensityOf(value),\n title: `${rowLabel} · ${columnLabel}: ${renderValue(value, valueFormat)}`,\n };\n })}\n />\n ))}\n </div>\n </div>\n </ChartFigure>\n );\n}\n\ninterface HeatmapRowCell {\n key: string;\n value: number | null;\n intensity: number;\n title: string;\n}\n\ninterface FragmentRowProps {\n rowLabel: string;\n cells: HeatmapRowCell[];\n}\n\n/** One grid row: a right-aligned row label followed by its intensity cells. */\nfunction FragmentRow({ rowLabel, cells }: FragmentRowProps) {\n return (\n <>\n <span className=\"flex items-center justify-end pr-1.5 text-[10px] leading-none text-text-muted\">\n {rowLabel}\n </span>\n {cells.map((cell) => (\n <div\n key={cell.key}\n title={cell.title}\n aria-hidden=\"true\"\n className=\"min-h-0 min-w-0 rounded-[3px] motion-safe:transition-colors\"\n style={{ backgroundColor: cellBackground(cell.intensity) }}\n />\n ))}\n </>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Heatmap.js","names":[],"sources":["../../../../src/timecampus/components/charts/Heatmap.tsx"],"sourcesContent":["import {\n chartTokens,\n formatNumber,\n renderValue,\n type ValueFormatter,\n} from \"./chartTheme\";\nimport { ChartEmpty, ChartFigure, srOnlyStyle } from \"./ChartParts\";\n\n/** A single heatmap value: a number, or `null`/`undefined` for a no-data cell. */\nexport type HeatmapValue = number | null | undefined;\n\nexport interface HeatmapProps {\n /**\n * Row-major matrix of values — one inner array per row (e.g. day), each entry\n * a column (e.g. hour or bucket). Ragged rows are tolerated: missing entries\n * read as no-data cells. `null`/`undefined` render as an empty track cell.\n */\n data: HeatmapValue[][];\n /** Row labels, top → bottom (e.g. `['Mon', … 'Sun']`). Drives the row count. */\n rowLabels: string[];\n /** Column labels, left → right (e.g. hours or buckets). Drives the column count. */\n columnLabels: string[];\n /**\n * Value mapped to full intensity. Defaults to the largest finite value in\n * `data`. Cells scale linearly from the track (0) to the brand token (`max`).\n */\n max?: number;\n /** Header for the row axis — used in the sr-only table's corner cell. Default `Row`. */\n rowsLabel?: string;\n /** Formats a cell value in the native tooltip + sr-only table. Default grouped integer. */\n valueFormat?: ValueFormatter;\n /** Low → high captions bracketing the intensity legend. Default `['Less', 'More']`. */\n legendLabels?: [string, string];\n /** Show the intensity legend under the grid. Default true. */\n showLegend?: boolean;\n /** Pixel height of the grid area. Cells size to the grid — never fixed px. Default 260. */\n height?: number;\n /** Message shown when there is nothing to plot. */\n emptyLabel?: string;\n /** Accessible name for the figure. Default derived from the axis labels. */\n ariaLabel?: string;\n className?: string;\n}\n\nconst clamp01 = (value: number): number => Math.min(1, Math.max(0, value));\n\n/**\n * Blend the brand token into the sunken track by `intensity` (0–1) using\n * `color-mix` — both operands are live design-system tokens, so the ramp is\n * theme-aware and never a raw colour. A no-data cell (`intensity < 0`) stays on\n * the bare track; any positive value gets a visible floor so faint counts read.\n */\nfunction cellBackground(intensity: number): string {\n if (intensity < 0 || intensity === 0) return chartTokens.track;\n const pct = Math.max(8, Math.round(clamp01(intensity) * 100));\n return `color-mix(in oklab, ${chartTokens.brand} ${pct}%, ${chartTokens.track})`;\n}\n\nconst LEGEND_STOPS = [0, 0.25, 0.5, 0.75, 1] as const;\n\n/**\n * Heatmap — a token-only matrix of intensity cells (e.g. activity by day × hour\n * for the Work Patterns page). Cell colour blends the brand token into the\n * sunken track by value, so it recolours with the theme and never carries a raw\n * colour. Cells are sized entirely by CSS grid (no fixed px) and the grid scrolls\n * horizontally when there are many columns. Ships a legend, a native per-cell\n * tooltip, an empty state and a screen-reader data table, wrapped in a\n * `ChartFigure` for a single accessible name — matching the other primitives.\n */\nexport function Heatmap({\n data,\n rowLabels,\n columnLabels,\n max,\n rowsLabel = \"Row\",\n valueFormat = formatNumber,\n legendLabels = [\"Less\", \"More\"],\n showLegend = true,\n height = 260,\n emptyLabel,\n ariaLabel,\n className,\n}: HeatmapProps) {\n const rows = rowLabels.length;\n const cols = columnLabels.length;\n\n const valueAt = (r: number, c: number): number | null => {\n const cell = data[r]?.[c];\n return typeof cell === \"number\" && Number.isFinite(cell) ? cell : null;\n };\n\n let resolvedMax = max ?? 0;\n if (max === undefined) {\n for (let r = 0; r < rows; r += 1) {\n for (let c = 0; c < cols; c += 1) {\n const value = valueAt(r, c);\n if (value !== null && value > resolvedMax) resolvedMax = value;\n }\n }\n }\n\n const label =\n ariaLabel ??\n `Heatmap of ${rowLabels.join(\", \")} by ${columnLabels.join(\", \")}`;\n\n if (rows === 0 || cols === 0 || resolvedMax <= 0) {\n return (\n <ChartEmpty height={height} label={emptyLabel} className={className} />\n );\n }\n\n const intensityOf = (value: number | null): number =>\n value === null ? -1 : value / resolvedMax;\n\n const gridTemplateColumns = `auto repeat(${cols}, minmax(1.5rem, 1fr))`;\n const gridTemplateRows = `auto repeat(${rows}, minmax(1.25rem, 1fr))`;\n\n const a11y = (\n <>\n {showLegend ? (\n <div className=\"mt-3 flex items-center justify-end gap-1.5 text-xs text-text-secondary\">\n <span>{legendLabels[0]}</span>\n {LEGEND_STOPS.map((stop) => (\n <span\n key={stop}\n aria-hidden=\"true\"\n className=\"h-2.5 w-2.5 rounded-[3px]\"\n style={{ backgroundColor: cellBackground(stop) }}\n />\n ))}\n <span>{legendLabels[1]}</span>\n </div>\n ) : null}\n\n <table style={srOnlyStyle}>\n <caption>{label}</caption>\n <thead>\n <tr>\n <th scope=\"col\">{rowsLabel}</th>\n {columnLabels.map((columnLabel, c) => (\n <th scope=\"col\" key={`${columnLabel}-${c}`}>\n {columnLabel}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {rowLabels.map((rowLabel, r) => (\n <tr key={`${rowLabel}-${r}`}>\n <th scope=\"row\">{rowLabel}</th>\n {columnLabels.map((columnLabel, c) => (\n <td key={`${columnLabel}-${c}`}>\n {renderValue(valueAt(r, c), valueFormat)}\n </td>\n ))}\n </tr>\n ))}\n </tbody>\n </table>\n </>\n );\n\n return (\n <ChartFigure\n ariaLabel={label}\n height={height}\n className={className}\n a11y={a11y}\n >\n <div className=\"h-full w-full overflow-x-auto\">\n <div\n className=\"grid h-full min-w-full gap-1\"\n style={{ gridTemplateColumns, gridTemplateRows }}\n >\n {/* Header row: empty corner + column labels. */}\n <span aria-hidden=\"true\" />\n {columnLabels.map((columnLabel, c) => (\n <span\n key={`col-${columnLabel}-${c}`}\n className=\"truncate px-0.5 text-center text-[10px] leading-none text-text-muted\"\n >\n {columnLabel}\n </span>\n ))}\n\n {/* Body rows: row label + its cells. */}\n {rowLabels.map((rowLabel, r) => (\n <FragmentRow\n key={`row-${rowLabel}-${r}`}\n rowLabel={rowLabel}\n cells={columnLabels.map((columnLabel, c) => {\n const value = valueAt(r, c);\n return {\n key: `${columnLabel}-${c}`,\n value,\n intensity: intensityOf(value),\n title: `${rowLabel} · ${columnLabel}: ${renderValue(value, valueFormat)}`,\n };\n })}\n />\n ))}\n </div>\n </div>\n </ChartFigure>\n );\n}\n\ninterface HeatmapRowCell {\n key: string;\n value: number | null;\n intensity: number;\n title: string;\n}\n\ninterface FragmentRowProps {\n rowLabel: string;\n cells: HeatmapRowCell[];\n}\n\n/** One grid row: a right-aligned row label followed by its intensity cells. */\nfunction FragmentRow({ rowLabel, cells }: FragmentRowProps) {\n return (\n <>\n <span className=\"flex items-center justify-end pr-1.5 text-[10px] leading-none text-text-muted\">\n {rowLabel}\n </span>\n {cells.map((cell) => (\n <div\n key={cell.key}\n title={cell.title}\n aria-hidden=\"true\"\n className=\"min-h-0 min-w-0 rounded-[3px] motion-safe:transition-colors\"\n style={{ backgroundColor: cellBackground(cell.intensity) }}\n />\n ))}\n </>\n );\n}\n"],"mappings":"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"}
|