@burdenoff/microfe-atheletebridge 2026.705.2 → 2026.706.1
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/atheletebridge/AthleteBridgeRoot.js +55 -39
- package/dist/atheletebridge/AthleteBridgeRoot.js.map +1 -1
- package/dist/atheletebridge/AthleteBridgeRoutes.js +18 -6
- package/dist/atheletebridge/AthleteBridgeRoutes.js.map +1 -1
- package/dist/atheletebridge/components/Crest.js +70 -0
- package/dist/atheletebridge/components/Crest.js.map +1 -0
- package/dist/atheletebridge/components/FixtureCard.js +8 -72
- package/dist/atheletebridge/components/FixtureCard.js.map +1 -1
- package/dist/atheletebridge/components/MatchupRow.js +74 -0
- package/dist/atheletebridge/components/MatchupRow.js.map +1 -0
- package/dist/atheletebridge/components/MetricsCard.js +61 -21
- package/dist/atheletebridge/components/MetricsCard.js.map +1 -1
- package/dist/atheletebridge/components/ProgramCard.js +75 -43
- package/dist/atheletebridge/components/ProgramCard.js.map +1 -1
- package/dist/atheletebridge/components/Scoreboard.js +163 -0
- package/dist/atheletebridge/components/Scoreboard.js.map +1 -0
- package/dist/atheletebridge/components/SessionCard.js +64 -41
- package/dist/atheletebridge/components/SessionCard.js.map +1 -1
- package/dist/atheletebridge/components/SessionStatusTimeline.js +80 -0
- package/dist/atheletebridge/components/SessionStatusTimeline.js.map +1 -0
- package/dist/atheletebridge/components/StatusBadge.js +5 -5
- package/dist/atheletebridge/components/StatusBadge.js.map +1 -1
- package/dist/atheletebridge/components/TeamCard.js +65 -35
- package/dist/atheletebridge/components/TeamCard.js.map +1 -1
- package/dist/atheletebridge/components/UserAvatar.js +33 -0
- package/dist/atheletebridge/components/UserAvatar.js.map +1 -0
- package/dist/atheletebridge/components/domain.js +5 -2
- package/dist/atheletebridge/components/domain.js.map +1 -1
- package/dist/atheletebridge/components/viz/AvatarStack.js +2 -0
- package/dist/atheletebridge/components/viz/BarChart.js +83 -0
- package/dist/atheletebridge/components/viz/BarChart.js.map +1 -0
- package/dist/atheletebridge/components/viz/DonutChart.js +109 -0
- package/dist/atheletebridge/components/viz/DonutChart.js.map +1 -0
- package/dist/atheletebridge/components/viz/LineChart.js +123 -0
- package/dist/atheletebridge/components/viz/LineChart.js.map +1 -0
- package/dist/atheletebridge/components/viz/ProgressRing.js +6 -0
- package/dist/atheletebridge/components/viz/ProgressRing.js.map +1 -0
- package/dist/atheletebridge/components/viz/RatingStars.js +92 -0
- package/dist/atheletebridge/components/viz/RatingStars.js.map +1 -0
- package/dist/atheletebridge/components/viz/Sparkline.js +74 -0
- package/dist/atheletebridge/components/viz/Sparkline.js.map +1 -0
- package/dist/atheletebridge/components/viz/StatCard.js +60 -0
- package/dist/atheletebridge/components/viz/StatCard.js.map +1 -0
- package/dist/atheletebridge/components/viz/StepProgress.js +3 -0
- package/dist/atheletebridge/components/viz/Timeline.js +66 -0
- package/dist/atheletebridge/components/viz/Timeline.js.map +1 -0
- package/dist/atheletebridge/components/viz/index.js +13 -0
- package/dist/atheletebridge/components/viz/lib/Avatar.js +1 -0
- package/dist/atheletebridge/components/viz/lib/CartesianFrame.js +119 -0
- package/dist/atheletebridge/components/viz/lib/CartesianFrame.js.map +1 -0
- package/dist/atheletebridge/components/viz/lib/ResponsiveSvg.js +35 -0
- package/dist/atheletebridge/components/viz/lib/ResponsiveSvg.js.map +1 -0
- package/dist/atheletebridge/components/viz/lib/format.js +17 -0
- package/dist/atheletebridge/components/viz/lib/format.js.map +1 -0
- package/dist/atheletebridge/components/viz/lib/geometry.js +61 -0
- package/dist/atheletebridge/components/viz/lib/geometry.js.map +1 -0
- package/dist/atheletebridge/components/viz/lib/hooks.js +39 -0
- package/dist/atheletebridge/components/viz/lib/hooks.js.map +1 -0
- package/dist/atheletebridge/components/viz/lib/palette.js +27 -0
- package/dist/atheletebridge/components/viz/lib/palette.js.map +1 -0
- package/dist/atheletebridge/hooks/useAthleteBridgeApi.js +369 -163
- package/dist/atheletebridge/hooks/useAthleteBridgeApi.js.map +1 -1
- package/dist/atheletebridge/pages/DashboardPage.js +182 -117
- package/dist/atheletebridge/pages/DashboardPage.js.map +1 -1
- package/dist/atheletebridge/pages/StandingsPage.js +119 -95
- package/dist/atheletebridge/pages/StandingsPage.js.map +1 -1
- package/dist/atheletebridge/pages/analytics/CoachAnalyticsPage.js +372 -0
- package/dist/atheletebridge/pages/analytics/CoachAnalyticsPage.js.map +1 -0
- package/dist/atheletebridge/pages/athletes/AthleteProfilePage.js +511 -0
- package/dist/atheletebridge/pages/athletes/AthleteProfilePage.js.map +1 -0
- package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.js +120 -131
- package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/fixtures/FixtureFormPage.js +264 -176
- package/dist/atheletebridge/pages/fixtures/FixtureFormPage.js.map +1 -1
- package/dist/atheletebridge/pages/fixtures/FixturesListPage.js +84 -47
- package/dist/atheletebridge/pages/fixtures/FixturesListPage.js.map +1 -1
- package/dist/atheletebridge/pages/performance/PerformancePage.js +432 -281
- package/dist/atheletebridge/pages/performance/PerformancePage.js.map +1 -1
- package/dist/atheletebridge/pages/programs/ProgramDetailPage.js +187 -123
- package/dist/atheletebridge/pages/programs/ProgramDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/programs/ProgramsListPage.js +56 -39
- package/dist/atheletebridge/pages/programs/ProgramsListPage.js.map +1 -1
- package/dist/atheletebridge/pages/sessions/SessionDetailPage.js +267 -139
- package/dist/atheletebridge/pages/sessions/SessionDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/sessions/SessionFormPage.js +253 -162
- package/dist/atheletebridge/pages/sessions/SessionFormPage.js.map +1 -1
- package/dist/atheletebridge/pages/sessions/SessionsListPage.js +38 -36
- package/dist/atheletebridge/pages/sessions/SessionsListPage.js.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamDetailPage.js +437 -189
- package/dist/atheletebridge/pages/teams/TeamDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamFormPage.js +368 -177
- package/dist/atheletebridge/pages/teams/TeamFormPage.js.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamsListPage.js +3 -0
- package/dist/atheletebridge/pages/teams/TeamsListPage.js.map +1 -1
- package/dist/atheletebridge/pages/tournaments/TournamentBracketPage.js +298 -0
- package/dist/atheletebridge/pages/tournaments/TournamentBracketPage.js.map +1 -0
- package/dist/atheletebridge/utils/identity.js +31 -0
- package/dist/atheletebridge/utils/identity.js.map +1 -0
- package/dist/atheletebridge/utils/timeAgo.js +20 -0
- package/dist/atheletebridge/utils/timeAgo.js.map +1 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SessionCard.js","names":[],"sources":["../../../src/atheletebridge/components/SessionCard.tsx"],"sourcesContent":["/**\n * Session Card\n * @module atheletebridge/components\n */\n\nimport type { FC } from 'react';\nimport { CalendarClock, Clock,
|
|
1
|
+
{"version":3,"file":"SessionCard.js","names":[],"sources":["../../../src/atheletebridge/components/SessionCard.tsx"],"sourcesContent":["/**\n * Session Card\n * @module atheletebridge/components\n *\n * A coaching-session tile showing the athlete identity (monogram avatar +\n * resolved name when available), session type, schedule, and a colored status\n * badge (Requested amber / Confirmed blue / Completed green / Cancelled grey).\n * A stored rating renders as read-only stars. Semantic tokens only.\n */\n\nimport type { FC } from 'react';\nimport { CalendarClock, Clock, Video, MapPin } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { StatusBadge } from './StatusBadge';\nimport { UserAvatar } from './UserAvatar';\nimport { RatingStars } from './viz';\nimport { SESSION_TYPE_LABELS } from './domain';\nimport { formatDate, formatDuration } from '../utils/formatters';\nimport { cn } from '../utils/cn';\nimport type { AthSession } from '../types';\n\ninterface SessionCardProps {\n session: AthSession;\n onClick?: () => void;\n /** Resolved athlete display name (else a monogram from the id is shown). */\n athleteName?: string | null;\n /** Resolved coach display name (optional sub-label). */\n coachName?: string | null;\n}\n\nexport const SessionCard: FC<SessionCardProps> = ({\n session,\n onClick,\n athleteName,\n coachName,\n}) => (\n <GlassCard\n treatment=\"metric\"\n role=\"button\"\n tabIndex={0}\n onClick={onClick}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onClick?.();\n }\n }}\n className={cn(\n 'cursor-pointer p-5 transition-transform duration-200 hover:-translate-y-0.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]'\n )}\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex min-w-0 items-center gap-3\">\n <UserAvatar name={athleteName} id={session.athleteId} size=\"md\" />\n <div className=\"min-w-0\">\n <h3 className=\"truncate text-base font-semibold text-text-primary\">{session.title}</h3>\n <p className=\"truncate text-sm text-text-secondary\">\n {athleteName?.trim() || 'Athlete'} · {SESSION_TYPE_LABELS[session.type]}\n {coachName?.trim() ? ` · ${coachName.trim()}` : ''}\n </p>\n </div>\n </div>\n <StatusBadge status={session.status} />\n </div>\n\n <div className=\"mt-4 flex flex-wrap items-center gap-x-4 gap-y-1.5 text-xs text-text-secondary\">\n <span className=\"flex items-center gap-1\">\n <CalendarClock className=\"h-3.5 w-3.5\" aria-hidden=\"true\" /> {formatDate(session.scheduledAt)}\n </span>\n <span className=\"flex items-center gap-1\">\n <Clock className=\"h-3.5 w-3.5\" aria-hidden=\"true\" /> {formatDuration(session.durationMin)}\n </span>\n {session.type === 'IN_PERSON' && session.location && (\n <span className=\"flex items-center gap-1 truncate\">\n <MapPin className=\"h-3.5 w-3.5 shrink-0\" aria-hidden=\"true\" /> {session.location}\n </span>\n )}\n {session.type !== 'IN_PERSON' && session.meetingUrl && (\n <span className=\"flex items-center gap-1\">\n <Video className=\"h-3.5 w-3.5\" aria-hidden=\"true\" /> Online\n </span>\n )}\n {session.rating !== null && (\n <RatingStars value={session.rating} size={14} showValue />\n )}\n </div>\n </GlassCard>\n);\n"],"mappings":";;;;;;;;;;;AA8BA,IAAa,KAAqC,EAChD,YACA,YACA,gBACA,mBAEA,kBAAC,GAAD;CACE,WAAU;CACV,MAAK;CACL,UAAU;CACD;CACT,YAAY,MAAM;AAChB,GAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,SACjC,EAAE,gBAAgB,EAClB,KAAW;;CAGf,WAAW,EACT,2KACD;WAbH,CAeE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD;IAAY,MAAM;IAAa,IAAI,EAAQ;IAAW,MAAK;IAAO,CAAA,EAClE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,MAAD;KAAI,WAAU;eAAsD,EAAQ;KAAW,CAAA,EACvF,kBAAC,KAAD;KAAG,WAAU;eAAb;MACG,GAAa,MAAM,IAAI;MAAU;MAAI,EAAoB,EAAQ;MACjE,GAAW,MAAM,GAAG,MAAM,EAAU,MAAM,KAAK;MAC9C;OACA;MACF;MACN,kBAAC,GAAD,EAAa,QAAQ,EAAQ,QAAU,CAAA,CACnC;KAEN,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,QAAD;IAAM,WAAU;cAAhB;KACE,kBAAC,GAAD;MAAe,WAAU;MAAc,eAAY;MAAS,CAAA;;KAAE,EAAW,EAAQ,YAAY;KACxF;;GACP,kBAAC,QAAD;IAAM,WAAU;cAAhB;KACE,kBAAC,GAAD;MAAO,WAAU;MAAc,eAAY;MAAS,CAAA;;KAAE,EAAe,EAAQ,YAAY;KACpF;;GACN,EAAQ,SAAS,eAAe,EAAQ,YACvC,kBAAC,QAAD;IAAM,WAAU;cAAhB;KACE,kBAAC,GAAD;MAAQ,WAAU;MAAuB,eAAY;MAAS,CAAA;;KAAE,EAAQ;KACnE;;GAER,EAAQ,SAAS,eAAe,EAAQ,cACvC,kBAAC,QAAD;IAAM,WAAU;cAAhB,CACE,kBAAC,GAAD;KAAO,WAAU;KAAc,eAAY;KAAS,CAAA,EAAA,UAC/C;;GAER,EAAQ,WAAW,QAClB,kBAAC,GAAD;IAAa,OAAO,EAAQ;IAAQ,MAAM;IAAI,WAAA;IAAY,CAAA;GAExD;IACI"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { cn as e } from "../utils/cn.js";
|
|
2
|
+
import { formatDate as t } from "../utils/formatters.js";
|
|
3
|
+
import { Check as n, X as r } from "lucide-react";
|
|
4
|
+
import { jsx as i, jsxs as a } from "react/jsx-runtime";
|
|
5
|
+
import { Stepper as o } from "@burdenoff/fe-libs/ui";
|
|
6
|
+
//#region src/atheletebridge/components/SessionStatusTimeline.tsx
|
|
7
|
+
var s = (e) => {
|
|
8
|
+
if (e) return t(e, {
|
|
9
|
+
weekday: "short",
|
|
10
|
+
year: void 0
|
|
11
|
+
});
|
|
12
|
+
}, c = ({ status: t, createdAt: c, completedAt: l, className: u }) => {
|
|
13
|
+
if (t === "CANCELLED" || t === "NO_SHOW") {
|
|
14
|
+
let o = t === "CANCELLED" ? "Cancelled" : "No-show";
|
|
15
|
+
return /* @__PURE__ */ a("ol", {
|
|
16
|
+
className: e("flex items-start gap-2", u),
|
|
17
|
+
"aria-label": "Session lifecycle",
|
|
18
|
+
children: [/* @__PURE__ */ a("li", {
|
|
19
|
+
className: "flex flex-1 items-center gap-3",
|
|
20
|
+
children: [
|
|
21
|
+
/* @__PURE__ */ i("span", {
|
|
22
|
+
"aria-hidden": "true",
|
|
23
|
+
className: "inline-flex size-7 shrink-0 items-center justify-center rounded-full border border-action-primary-bg bg-action-primary-bg text-action-primary-text",
|
|
24
|
+
children: /* @__PURE__ */ i(n, { className: "size-4" })
|
|
25
|
+
}),
|
|
26
|
+
/* @__PURE__ */ a("span", {
|
|
27
|
+
className: "min-w-0",
|
|
28
|
+
children: [/* @__PURE__ */ i("span", {
|
|
29
|
+
className: "block text-sm font-medium text-text-primary",
|
|
30
|
+
children: "Requested"
|
|
31
|
+
}), s(c) ? /* @__PURE__ */ i("span", {
|
|
32
|
+
className: "block text-xs text-text-muted",
|
|
33
|
+
children: s(c)
|
|
34
|
+
}) : null]
|
|
35
|
+
}),
|
|
36
|
+
/* @__PURE__ */ i("span", {
|
|
37
|
+
"aria-hidden": "true",
|
|
38
|
+
className: "h-px flex-1 bg-status-error-border"
|
|
39
|
+
})
|
|
40
|
+
]
|
|
41
|
+
}), /* @__PURE__ */ a("li", {
|
|
42
|
+
className: "flex items-center gap-3",
|
|
43
|
+
"aria-current": "step",
|
|
44
|
+
children: [/* @__PURE__ */ i("span", {
|
|
45
|
+
"aria-hidden": "true",
|
|
46
|
+
className: "inline-flex size-7 shrink-0 items-center justify-center rounded-full border border-status-error-border bg-status-error-bg-subtle text-status-error-text",
|
|
47
|
+
children: /* @__PURE__ */ i(r, { className: "size-4" })
|
|
48
|
+
}), /* @__PURE__ */ i("span", {
|
|
49
|
+
className: "text-sm font-medium text-status-error-text",
|
|
50
|
+
children: o
|
|
51
|
+
})]
|
|
52
|
+
})]
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
let d = [
|
|
56
|
+
{
|
|
57
|
+
key: "requested",
|
|
58
|
+
label: "Requested",
|
|
59
|
+
description: s(c)
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
key: "confirmed",
|
|
63
|
+
label: "Confirmed"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
key: "completed",
|
|
67
|
+
label: "Completed",
|
|
68
|
+
description: s(l)
|
|
69
|
+
}
|
|
70
|
+
];
|
|
71
|
+
return /* @__PURE__ */ i(o, {
|
|
72
|
+
steps: d,
|
|
73
|
+
active: t === "REQUESTED" ? 0 : t === "CONFIRMED" ? 1 : d.length,
|
|
74
|
+
className: u
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
//#endregion
|
|
78
|
+
export { c as SessionStatusTimeline };
|
|
79
|
+
|
|
80
|
+
//# sourceMappingURL=SessionStatusTimeline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SessionStatusTimeline.js","names":[],"sources":["../../../src/atheletebridge/components/SessionStatusTimeline.tsx"],"sourcesContent":["/**\n * SessionStatusTimeline\n * @module atheletebridge/components\n *\n * A horizontal lifecycle stepper for a coaching session:\n * Requested → Confirmed → Completed\n * with a distinct terminal variant when the session was Cancelled / No-show.\n * The happy path reuses the fe-libs Stepper; timestamps are shown ONLY where the\n * schema actually has them (createdAt for Requested, completedAt for Completed)\n * — Confirmed has no dedicated timestamp field, so none is fabricated.\n * Semantic tokens only.\n */\n\nimport type { FC } from 'react';\nimport { Check, X } from 'lucide-react';\nimport { Stepper, type StepperStep } from '@burdenoff/fe-libs/ui';\nimport { formatDate } from '../utils/formatters';\nimport { cn } from '../utils/cn';\nimport type { AthSessionStatus } from '../types';\n\nexport interface SessionStatusTimelineProps {\n status: AthSessionStatus;\n /** Requested-at timestamp (session.createdAt). */\n createdAt?: string | null;\n /** Completed-at timestamp (session.completedAt). */\n completedAt?: string | null;\n className?: string;\n}\n\nconst shortWhen = (iso: string | null | undefined): string | undefined => {\n if (!iso) return undefined;\n return formatDate(iso, { weekday: 'short', year: undefined });\n};\n\n/** Lifecycle stepper for a coaching session (with cancelled/no-show terminal). */\nexport const SessionStatusTimeline: FC<SessionStatusTimelineProps> = ({\n status,\n createdAt,\n completedAt,\n className,\n}) => {\n // ── Terminal (cancelled / no-show) ─────────────────────────────────────────\n if (status === 'CANCELLED' || status === 'NO_SHOW') {\n const terminalLabel = status === 'CANCELLED' ? 'Cancelled' : 'No-show';\n return (\n <ol\n className={cn('flex items-start gap-2', className)}\n aria-label=\"Session lifecycle\"\n >\n <li className=\"flex flex-1 items-center gap-3\">\n <span\n aria-hidden=\"true\"\n className=\"inline-flex size-7 shrink-0 items-center justify-center rounded-full border border-action-primary-bg bg-action-primary-bg text-action-primary-text\"\n >\n <Check className=\"size-4\" />\n </span>\n <span className=\"min-w-0\">\n <span className=\"block text-sm font-medium text-text-primary\">Requested</span>\n {shortWhen(createdAt) ? (\n <span className=\"block text-xs text-text-muted\">{shortWhen(createdAt)}</span>\n ) : null}\n </span>\n <span\n aria-hidden=\"true\"\n className=\"h-px flex-1 bg-status-error-border\"\n />\n </li>\n <li className=\"flex items-center gap-3\" aria-current=\"step\">\n <span\n aria-hidden=\"true\"\n className=\"inline-flex size-7 shrink-0 items-center justify-center rounded-full border border-status-error-border bg-status-error-bg-subtle text-status-error-text\"\n >\n <X className=\"size-4\" />\n </span>\n <span className=\"text-sm font-medium text-status-error-text\">{terminalLabel}</span>\n </li>\n </ol>\n );\n }\n\n // ── Happy path ─────────────────────────────────────────────────────────────\n const steps: StepperStep[] = [\n { key: 'requested', label: 'Requested', description: shortWhen(createdAt) },\n { key: 'confirmed', label: 'Confirmed' },\n { key: 'completed', label: 'Completed', description: shortWhen(completedAt) },\n ];\n\n // active = index of the in-progress step; past `steps.length` marks all done.\n const active = status === 'REQUESTED' ? 0 : status === 'CONFIRMED' ? 1 : steps.length;\n\n return <Stepper steps={steps} active={active} className={className} />;\n};\n"],"mappings":";;;;;;AA6BA,IAAM,KAAa,MAAuD;AACnE,OACL,QAAO,EAAW,GAAK;EAAE,SAAS;EAAS,MAAM,KAAA;EAAW,CAAC;GAIlD,KAAyD,EACpE,WACA,cACA,gBACA,mBACI;AAEJ,KAAI,MAAW,eAAe,MAAW,WAAW;EAClD,IAAM,IAAgB,MAAW,cAAc,cAAc;AAC7D,SACE,kBAAC,MAAD;GACE,WAAW,EAAG,0BAA0B,EAAU;GAClD,cAAW;aAFb,CAIE,kBAAC,MAAD;IAAI,WAAU;cAAd;KACE,kBAAC,QAAD;MACE,eAAY;MACZ,WAAU;gBAEV,kBAAC,GAAD,EAAO,WAAU,UAAW,CAAA;MACvB,CAAA;KACP,kBAAC,QAAD;MAAM,WAAU;gBAAhB,CACE,kBAAC,QAAD;OAAM,WAAU;iBAA8C;OAAgB,CAAA,EAC7E,EAAU,EAAU,GACnB,kBAAC,QAAD;OAAM,WAAU;iBAAiC,EAAU,EAAU;OAAQ,CAAA,GAC3E,KACC;;KACP,kBAAC,QAAD;MACE,eAAY;MACZ,WAAU;MACV,CAAA;KACC;OACL,kBAAC,MAAD;IAAI,WAAU;IAA0B,gBAAa;cAArD,CACE,kBAAC,QAAD;KACE,eAAY;KACZ,WAAU;eAEV,kBAAC,GAAD,EAAG,WAAU,UAAW,CAAA;KACnB,CAAA,EACP,kBAAC,QAAD;KAAM,WAAU;eAA8C;KAAqB,CAAA,CAChF;MACF;;;CAKT,IAAM,IAAuB;EAC3B;GAAE,KAAK;GAAa,OAAO;GAAa,aAAa,EAAU,EAAU;GAAE;EAC3E;GAAE,KAAK;GAAa,OAAO;GAAa;EACxC;GAAE,KAAK;GAAa,OAAO;GAAa,aAAa,EAAU,EAAY;GAAE;EAC9E;AAKD,QAAO,kBAAC,GAAD;EAAgB;EAAe,QAFvB,MAAW,cAAc,IAAI,MAAW,cAAc,IAAI,EAAM;EAEtB;EAAa,CAAA"}
|
|
@@ -17,7 +17,7 @@ var r = {
|
|
|
17
17
|
forming: "warning",
|
|
18
18
|
active: "success",
|
|
19
19
|
inactive: "secondary",
|
|
20
|
-
archived: "
|
|
20
|
+
archived: "secondary",
|
|
21
21
|
trial: "info",
|
|
22
22
|
injured: "warning",
|
|
23
23
|
suspended: "destructive",
|
|
@@ -38,11 +38,11 @@ var r = {
|
|
|
38
38
|
function o(e) {
|
|
39
39
|
return e.replace(/_/g, " ").toLowerCase().replace(/\b\w/g, (e) => e.toUpperCase());
|
|
40
40
|
}
|
|
41
|
-
var s = ({ status: s,
|
|
42
|
-
let
|
|
41
|
+
var s = ({ status: s, variant: c, className: l }) => {
|
|
42
|
+
let u = s.toLowerCase(), d = c ?? a[u] ?? "secondary", f = i.has(u);
|
|
43
43
|
return /* @__PURE__ */ n("span", {
|
|
44
|
-
className: e("inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium", r[
|
|
45
|
-
children: [
|
|
44
|
+
className: e("inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium", r[d], l),
|
|
45
|
+
children: [f && /* @__PURE__ */ t("span", {
|
|
46
46
|
"aria-hidden": "true",
|
|
47
47
|
className: "size-1.5 rounded-full bg-current motion-safe:animate-vc-glow-pulse"
|
|
48
48
|
}), o(s)]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StatusBadge.js","names":[],"sources":["../../../src/atheletebridge/components/StatusBadge.tsx"],"sourcesContent":["/**\n * Status Badge Component\n * @module atheletebridge/components\n *\n * A colored badge based on a domain status value, with semantic-token color\n * mapping for the AthleteBridge lifecycle states (teams, members, fixtures,\n * programs, sessions, workouts). Uses semantic tokens only — never raw colors.\n */\n\nimport type { FC } from 'react';\nimport { cn } from '../utils/cn';\n\
|
|
1
|
+
{"version":3,"file":"StatusBadge.js","names":[],"sources":["../../../src/atheletebridge/components/StatusBadge.tsx"],"sourcesContent":["/**\n * Status Badge Component\n * @module atheletebridge/components\n *\n * A colored badge based on a domain status value, with semantic-token color\n * mapping for the AthleteBridge lifecycle states (teams, members, fixtures,\n * programs, sessions, workouts). Uses semantic tokens only — never raw colors.\n */\n\nimport type { FC } from 'react';\nimport { cn } from '../utils/cn';\n\ntype BadgeVariant = 'success' | 'warning' | 'destructive' | 'secondary' | 'info' | 'outline';\n\ninterface StatusBadgeProps {\n status: string;\n /**\n * Force a specific variant, overriding the auto status→color mapping. Useful\n * where the same status string reads differently by context (e.g. a cancelled\n * coaching session shown grey rather than the default destructive red).\n */\n variant?: BadgeVariant;\n className?: string;\n}\n\nconst variantStyles: Record<BadgeVariant, string> = {\n success: 'bg-status-success-bg-subtle text-status-success-text',\n warning: 'bg-status-warning-bg-subtle text-status-warning-text',\n destructive: 'bg-status-error-bg-subtle text-status-error-text',\n secondary: 'bg-bg-sunken text-text-secondary',\n info: 'bg-[var(--color-accent-soft)] text-text-primary',\n outline: 'border border-border-seam text-text-primary',\n};\n\n// Statuses that represent a \"live\" / in-progress condition get a subtle,\n// opacity-only glow pulse (reduced-motion safe via motion-safe:).\nconst liveStatuses = new Set(['live', 'active', 'confirmed', 'in_progress']);\n\nconst statusVariantMap: Record<string, BadgeVariant> = {\n // Team status\n forming: 'warning',\n active: 'success',\n inactive: 'secondary',\n archived: 'secondary',\n\n // Member status\n trial: 'info',\n injured: 'warning',\n suspended: 'destructive',\n released: 'secondary',\n\n // Fixture status\n scheduled: 'info',\n live: 'success',\n completed: 'success',\n postponed: 'warning',\n cancelled: 'destructive',\n\n // Program status\n draft: 'secondary',\n published: 'success',\n\n // Session status\n requested: 'warning',\n confirmed: 'info',\n no_show: 'destructive',\n\n // Workout status\n planned: 'info',\n skipped: 'secondary',\n};\n\nfunction formatStatusLabel(status: string): string {\n return status\n .replace(/_/g, ' ')\n .toLowerCase()\n .replace(/\\b\\w/g, (char) => char.toUpperCase());\n}\n\nexport const StatusBadge: FC<StatusBadgeProps> = ({ status, variant: variantOverride, className }) => {\n const key = status.toLowerCase();\n const variant = variantOverride ?? statusVariantMap[key] ?? 'secondary';\n const isLive = liveStatuses.has(key);\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[variant],\n className\n )}\n >\n {isLive && (\n <span\n aria-hidden=\"true\"\n className=\"size-1.5 rounded-full bg-current motion-safe:animate-vc-glow-pulse\"\n />\n )}\n {formatStatusLabel(status)}\n </span>\n );\n};\n"],"mappings":";;;AAyBA,IAAM,IAA8C;CAClD,SAAS;CACT,SAAS;CACT,aAAa;CACb,WAAW;CACX,MAAM;CACN,SAAS;CACV,EAIK,IAAe,IAAI,IAAI;CAAC;CAAQ;CAAU;CAAa;CAAc,CAAC,EAEtE,IAAiD;CAErD,SAAS;CACT,QAAQ;CACR,UAAU;CACV,UAAU;CAGV,OAAO;CACP,SAAS;CACT,WAAW;CACX,UAAU;CAGV,WAAW;CACX,MAAM;CACN,WAAW;CACX,WAAW;CACX,WAAW;CAGX,OAAO;CACP,WAAW;CAGX,WAAW;CACX,WAAW;CACX,SAAS;CAGT,SAAS;CACT,SAAS;CACV;AAED,SAAS,EAAkB,GAAwB;AACjD,QAAO,EACJ,QAAQ,MAAM,IAAI,CAClB,aAAa,CACb,QAAQ,UAAU,MAAS,EAAK,aAAa,CAAC;;AAGnD,IAAa,KAAqC,EAAE,WAAQ,SAAS,GAAiB,mBAAgB;CACpG,IAAM,IAAM,EAAO,aAAa,EAC1B,IAAU,KAAmB,EAAiB,MAAQ,aACtD,IAAS,EAAa,IAAI,EAAI;AAEpC,QACE,kBAAC,QAAD;EACE,WAAW,EACT,mFACA,EAAc,IACd,EACD;YALH,CAOG,KACC,kBAAC,QAAD;GACE,eAAY;GACZ,WAAU;GACV,CAAA,EAEH,EAAkB,EAAO,CACrB"}
|
|
@@ -1,81 +1,111 @@
|
|
|
1
1
|
import { cn as e } from "../utils/cn.js";
|
|
2
2
|
import { StatusBadge as t } from "./StatusBadge.js";
|
|
3
3
|
import { SPORT_ICONS as n, SPORT_LABELS as r } from "./domain.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { Crest as i } from "./Crest.js";
|
|
5
|
+
import { CalendarDays as a, MapPin as o, Users as s } from "lucide-react";
|
|
6
|
+
import { jsx as c, jsxs as l } from "react/jsx-runtime";
|
|
7
|
+
import { Badge as u, GlassCard as d } from "@burdenoff/fe-libs/ui";
|
|
7
8
|
//#region src/atheletebridge/components/TeamCard.tsx
|
|
8
|
-
var
|
|
9
|
-
let
|
|
10
|
-
return /* @__PURE__ */
|
|
9
|
+
var f = ({ team: f, onClick: p, memberCount: m }) => {
|
|
10
|
+
let h = n[f.sport], g = m ?? f.members?.length;
|
|
11
|
+
return /* @__PURE__ */ l(d, {
|
|
11
12
|
treatment: "metric",
|
|
12
13
|
role: "button",
|
|
13
14
|
tabIndex: 0,
|
|
14
|
-
onClick:
|
|
15
|
+
onClick: p,
|
|
15
16
|
onKeyDown: (e) => {
|
|
16
|
-
(e.key === "Enter" || e.key === " ") && (e.preventDefault(),
|
|
17
|
+
(e.key === "Enter" || e.key === " ") && (e.preventDefault(), p?.());
|
|
17
18
|
},
|
|
18
|
-
className: e("cursor-pointer p-5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]"),
|
|
19
|
+
className: e("cursor-pointer p-5 transition-transform duration-200 hover:-translate-y-0.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]"),
|
|
19
20
|
children: [
|
|
20
|
-
/* @__PURE__ */
|
|
21
|
+
/* @__PURE__ */ l("div", {
|
|
21
22
|
className: "flex items-start justify-between gap-3",
|
|
22
|
-
children: [/* @__PURE__ */
|
|
23
|
-
className: "flex items-center gap-3
|
|
24
|
-
children: [/* @__PURE__ */
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
children: [/* @__PURE__ */ l("div", {
|
|
24
|
+
className: "flex min-w-0 items-center gap-3",
|
|
25
|
+
children: [/* @__PURE__ */ c(i, {
|
|
26
|
+
name: f.name,
|
|
27
|
+
sport: f.sport,
|
|
28
|
+
size: 44
|
|
29
|
+
}), /* @__PURE__ */ l("div", {
|
|
28
30
|
className: "min-w-0",
|
|
29
|
-
children: [/* @__PURE__ */
|
|
31
|
+
children: [/* @__PURE__ */ c("h3", {
|
|
30
32
|
className: "truncate text-base font-semibold text-text-primary",
|
|
31
|
-
children:
|
|
32
|
-
}), /* @__PURE__ */
|
|
33
|
-
className: "
|
|
34
|
-
children:
|
|
33
|
+
children: f.name
|
|
34
|
+
}), /* @__PURE__ */ c("div", {
|
|
35
|
+
className: "mt-1",
|
|
36
|
+
children: /* @__PURE__ */ l(u, {
|
|
37
|
+
variant: "secondary",
|
|
38
|
+
className: "gap-1",
|
|
39
|
+
children: [/* @__PURE__ */ c(h, {
|
|
40
|
+
className: "h-3 w-3",
|
|
41
|
+
"aria-hidden": "true"
|
|
42
|
+
}), r[f.sport]]
|
|
43
|
+
})
|
|
35
44
|
})]
|
|
36
45
|
})]
|
|
37
|
-
}), /* @__PURE__ */
|
|
46
|
+
}), /* @__PURE__ */ c(t, { status: f.status })]
|
|
38
47
|
}),
|
|
39
|
-
/* @__PURE__ */
|
|
48
|
+
/* @__PURE__ */ l("div", {
|
|
40
49
|
className: "mt-4 flex flex-wrap items-center gap-x-4 gap-y-1.5 text-xs text-text-secondary",
|
|
41
50
|
children: [
|
|
42
|
-
|
|
51
|
+
f.ageGroup && /* @__PURE__ */ l("span", {
|
|
43
52
|
className: "flex items-center gap-1",
|
|
44
53
|
children: [
|
|
45
|
-
/* @__PURE__ */ s
|
|
54
|
+
/* @__PURE__ */ c(s, {
|
|
55
|
+
className: "h-3.5 w-3.5",
|
|
56
|
+
"aria-hidden": "true"
|
|
57
|
+
}),
|
|
46
58
|
" ",
|
|
47
|
-
|
|
59
|
+
f.ageGroup
|
|
48
60
|
]
|
|
49
61
|
}),
|
|
50
|
-
|
|
62
|
+
f.season && /* @__PURE__ */ l("span", {
|
|
51
63
|
className: "flex items-center gap-1",
|
|
52
64
|
children: [
|
|
53
|
-
/* @__PURE__ */
|
|
65
|
+
/* @__PURE__ */ c(a, {
|
|
66
|
+
className: "h-3.5 w-3.5",
|
|
67
|
+
"aria-hidden": "true"
|
|
68
|
+
}),
|
|
54
69
|
" ",
|
|
55
|
-
|
|
70
|
+
f.season
|
|
56
71
|
]
|
|
57
72
|
}),
|
|
58
|
-
|
|
73
|
+
f.homeVenue && /* @__PURE__ */ l("span", {
|
|
59
74
|
className: "flex items-center gap-1 truncate",
|
|
60
75
|
children: [
|
|
61
|
-
/* @__PURE__ */
|
|
76
|
+
/* @__PURE__ */ c(o, {
|
|
77
|
+
className: "h-3.5 w-3.5 shrink-0",
|
|
78
|
+
"aria-hidden": "true"
|
|
79
|
+
}),
|
|
62
80
|
" ",
|
|
63
|
-
|
|
81
|
+
f.homeVenue
|
|
64
82
|
]
|
|
65
83
|
})
|
|
66
84
|
]
|
|
67
85
|
}),
|
|
68
|
-
|
|
86
|
+
f.tags.length > 0 && /* @__PURE__ */ c("div", {
|
|
69
87
|
className: "mt-3 flex flex-wrap gap-1.5",
|
|
70
|
-
children:
|
|
88
|
+
children: f.tags.slice(0, 4).map((e) => /* @__PURE__ */ c("span", {
|
|
71
89
|
className: "inline-flex items-center rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary",
|
|
72
90
|
children: e
|
|
73
91
|
}, e))
|
|
92
|
+
}),
|
|
93
|
+
g !== void 0 && /* @__PURE__ */ l("div", {
|
|
94
|
+
className: "mt-4 flex items-center gap-1.5 border-t border-border-seam pt-3 text-xs font-medium text-text-secondary",
|
|
95
|
+
children: [
|
|
96
|
+
/* @__PURE__ */ c(s, {
|
|
97
|
+
className: "h-3.5 w-3.5",
|
|
98
|
+
"aria-hidden": "true"
|
|
99
|
+
}),
|
|
100
|
+
g,
|
|
101
|
+
" ",
|
|
102
|
+
g === 1 ? "member" : "members"
|
|
103
|
+
]
|
|
74
104
|
})
|
|
75
105
|
]
|
|
76
106
|
});
|
|
77
107
|
};
|
|
78
108
|
//#endregion
|
|
79
|
-
export {
|
|
109
|
+
export { f as TeamCard };
|
|
80
110
|
|
|
81
111
|
//# sourceMappingURL=TeamCard.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TeamCard.js","names":[],"sources":["../../../src/atheletebridge/components/TeamCard.tsx"],"sourcesContent":["/**\n * Team Card\n * @module atheletebridge/components\n */\n\nimport type { FC } from 'react';\nimport { Users, MapPin, CalendarDays } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { StatusBadge } from './StatusBadge';\nimport { SPORT_ICONS, SPORT_LABELS } from './domain';\nimport { cn } from '../utils/cn';\nimport type { AthTeam } from '../types';\n\ninterface TeamCardProps {\n team: AthTeam;\n onClick?: () => void;\n}\n\nexport const TeamCard: FC<TeamCardProps> = ({ team, onClick }) => {\n const SportIcon = SPORT_ICONS[team.sport];\n\n return (\n <GlassCard\n treatment=\"metric\"\n role=\"button\"\n tabIndex={0}\n onClick={onClick}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onClick?.();\n }\n }}\n className={cn(\n 'cursor-pointer p-5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]'\n )}\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex
|
|
1
|
+
{"version":3,"file":"TeamCard.js","names":[],"sources":["../../../src/atheletebridge/components/TeamCard.tsx"],"sourcesContent":["/**\n * Team Card\n * @module atheletebridge/components\n *\n * A team tile fronted by a deterministic gradient monogram crest, with a sport\n * Badge, a StatusBadge (green Active / amber Forming / grey Inactive·Archived),\n * a member-count chip and a hover lift. Semantic tokens only.\n */\n\nimport type { FC } from 'react';\nimport { Users, MapPin, CalendarDays } from 'lucide-react';\nimport { GlassCard, Badge } from '@burdenoff/fe-libs/ui';\nimport { StatusBadge } from './StatusBadge';\nimport { Crest } from './Crest';\nimport { SPORT_ICONS, SPORT_LABELS } from './domain';\nimport { cn } from '../utils/cn';\nimport type { AthTeam } from '../types';\n\ninterface TeamCardProps {\n team: AthTeam;\n onClick?: () => void;\n /**\n * Member count for the footer chip. Falls back to `team.members?.length`\n * when the list query selected members; omit the chip entirely if neither is\n * available (never guess).\n */\n memberCount?: number;\n}\n\nexport const TeamCard: FC<TeamCardProps> = ({ team, onClick, memberCount }) => {\n const SportIcon = SPORT_ICONS[team.sport];\n const count = memberCount ?? team.members?.length;\n\n return (\n <GlassCard\n treatment=\"metric\"\n role=\"button\"\n tabIndex={0}\n onClick={onClick}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onClick?.();\n }\n }}\n className={cn(\n 'cursor-pointer p-5 transition-transform duration-200 hover:-translate-y-0.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]'\n )}\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex min-w-0 items-center gap-3\">\n <Crest name={team.name} sport={team.sport} size={44} />\n <div className=\"min-w-0\">\n <h3 className=\"truncate text-base font-semibold text-text-primary\">{team.name}</h3>\n <div className=\"mt-1\">\n <Badge variant=\"secondary\" className=\"gap-1\">\n <SportIcon className=\"h-3 w-3\" aria-hidden=\"true\" />\n {SPORT_LABELS[team.sport]}\n </Badge>\n </div>\n </div>\n </div>\n <StatusBadge status={team.status} />\n </div>\n\n <div className=\"mt-4 flex flex-wrap items-center gap-x-4 gap-y-1.5 text-xs text-text-secondary\">\n {team.ageGroup && (\n <span className=\"flex items-center gap-1\">\n <Users className=\"h-3.5 w-3.5\" aria-hidden=\"true\" /> {team.ageGroup}\n </span>\n )}\n {team.season && (\n <span className=\"flex items-center gap-1\">\n <CalendarDays className=\"h-3.5 w-3.5\" aria-hidden=\"true\" /> {team.season}\n </span>\n )}\n {team.homeVenue && (\n <span className=\"flex items-center gap-1 truncate\">\n <MapPin className=\"h-3.5 w-3.5 shrink-0\" aria-hidden=\"true\" /> {team.homeVenue}\n </span>\n )}\n </div>\n\n {team.tags.length > 0 && (\n <div className=\"mt-3 flex flex-wrap gap-1.5\">\n {team.tags.slice(0, 4).map((tag) => (\n <span\n key={tag}\n className=\"inline-flex items-center rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\"\n >\n {tag}\n </span>\n ))}\n </div>\n )}\n\n {count !== undefined && (\n <div className=\"mt-4 flex items-center gap-1.5 border-t border-border-seam pt-3 text-xs font-medium text-text-secondary\">\n <Users className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n {count} {count === 1 ? 'member' : 'members'}\n </div>\n )}\n </GlassCard>\n );\n};\n"],"mappings":";;;;;;;;AA6BA,IAAa,KAA+B,EAAE,SAAM,YAAS,qBAAkB;CAC7E,IAAM,IAAY,EAAY,EAAK,QAC7B,IAAQ,KAAe,EAAK,SAAS;AAE3C,QACE,kBAAC,GAAD;EACE,WAAU;EACV,MAAK;EACL,UAAU;EACD;EACT,YAAY,MAAM;AAChB,IAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,SACjC,EAAE,gBAAgB,EAClB,KAAW;;EAGf,WAAW,EACT,2KACD;YAbH;GAeE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD;MAAO,MAAM,EAAK;MAAM,OAAO,EAAK;MAAO,MAAM;MAAM,CAAA,EACvD,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,MAAD;OAAI,WAAU;iBAAsD,EAAK;OAAU,CAAA,EACnF,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,GAAD;QAAO,SAAQ;QAAY,WAAU;kBAArC,CACE,kBAAC,GAAD;SAAW,WAAU;SAAU,eAAY;SAAS,CAAA,EACnD,EAAa,EAAK,OACb;;OACJ,CAAA,CACF;QACF;QACN,kBAAC,GAAD,EAAa,QAAQ,EAAK,QAAU,CAAA,CAChC;;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf;KACG,EAAK,YACJ,kBAAC,QAAD;MAAM,WAAU;gBAAhB;OACE,kBAAC,GAAD;QAAO,WAAU;QAAc,eAAY;QAAS,CAAA;;OAAE,EAAK;OACtD;;KAER,EAAK,UACJ,kBAAC,QAAD;MAAM,WAAU;gBAAhB;OACE,kBAAC,GAAD;QAAc,WAAU;QAAc,eAAY;QAAS,CAAA;;OAAE,EAAK;OAC7D;;KAER,EAAK,aACJ,kBAAC,QAAD;MAAM,WAAU;gBAAhB;OACE,kBAAC,GAAD;QAAQ,WAAU;QAAuB,eAAY;QAAS,CAAA;;OAAE,EAAK;OAChE;;KAEL;;GAEL,EAAK,KAAK,SAAS,KAClB,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAK,KAAK,MAAM,GAAG,EAAE,CAAC,KAAK,MAC1B,kBAAC,QAAD;KAEE,WAAU;eAET;KACI,EAJA,EAIA,CACP;IACE,CAAA;GAGP,MAAU,KAAA,KACT,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD;MAAO,WAAU;MAAc,eAAY;MAAS,CAAA;KACnD;KAAM;KAAE,MAAU,IAAI,WAAW;KAC9B;;GAEE"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { cn as e } from "../utils/cn.js";
|
|
2
|
+
import { gradientCss as t, initials as n } from "../utils/identity.js";
|
|
3
|
+
import { jsx as r } from "react/jsx-runtime";
|
|
4
|
+
//#region src/atheletebridge/components/UserAvatar.tsx
|
|
5
|
+
var i = {
|
|
6
|
+
xs: 24,
|
|
7
|
+
sm: 32,
|
|
8
|
+
md: 40,
|
|
9
|
+
lg: 56,
|
|
10
|
+
xl: 72
|
|
11
|
+
}, a = ({ name: a, id: o, jersey: s, size: c = "md", ring: l = !1, className: u }) => {
|
|
12
|
+
let d = typeof c == "number" ? c : i[c], f = a?.trim() || (o ? `User ${o.slice(0, 6)}` : "Unknown user"), p = a?.trim() || o || f, m = typeof s == "number" && Number.isFinite(s) ? String(s) : n(f);
|
|
13
|
+
return /* @__PURE__ */ r("span", {
|
|
14
|
+
role: "img",
|
|
15
|
+
"aria-label": f,
|
|
16
|
+
title: f,
|
|
17
|
+
className: e("inline-flex shrink-0 items-center justify-center rounded-full font-semibold leading-none text-[var(--color-action-primary-text)]", l && "ring-2 ring-[var(--color-bg-surface)]", u),
|
|
18
|
+
style: {
|
|
19
|
+
width: d,
|
|
20
|
+
height: d,
|
|
21
|
+
fontSize: Math.max(9, Math.round(d * .4)),
|
|
22
|
+
background: t(p)
|
|
23
|
+
},
|
|
24
|
+
children: /* @__PURE__ */ r("span", {
|
|
25
|
+
"aria-hidden": "true",
|
|
26
|
+
children: m
|
|
27
|
+
})
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
//#endregion
|
|
31
|
+
export { a as UserAvatar };
|
|
32
|
+
|
|
33
|
+
//# sourceMappingURL=UserAvatar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UserAvatar.js","names":[],"sources":["../../../src/atheletebridge/components/UserAvatar.tsx"],"sourcesContent":["/**\n * User avatar (monogram)\n * @module atheletebridge/components\n *\n * A circular avatar for a user / athlete / coach. There is no photo field on the\n * schema, so this is an honest initials monogram over a deterministic gradient\n * derived from the resolved name (or the id as a stable fallback). Semantic\n * tokens only.\n */\n\nimport type { FC } from 'react';\nimport { cn } from '../utils/cn';\nimport { gradientCss, initials } from '../utils/identity';\n\nconst SIZE_MAP = { xs: 24, sm: 32, md: 40, lg: 56, xl: 72 } as const;\n\nexport type UserAvatarSize = keyof typeof SIZE_MAP | number;\n\nexport interface UserAvatarProps {\n /** Resolved display name (drives initials + gradient seed). */\n name?: string | null;\n /** Stable id — used as the gradient seed and initials fallback when no name. */\n id?: string | null;\n /** Optional jersey number rendered instead of initials (roster context). */\n jersey?: number | null;\n /** Named size token or explicit pixel diameter. */\n size?: UserAvatarSize;\n /** Extra ring (used inside overlapping stacks / on colored surfaces). */\n ring?: boolean;\n className?: string;\n}\n\n/** Circular monogram avatar for a user/athlete/coach (initials or jersey #). */\nexport const UserAvatar: FC<UserAvatarProps> = ({\n name,\n id,\n jersey,\n size = 'md',\n ring = false,\n className,\n}) => {\n const px = typeof size === 'number' ? size : SIZE_MAP[size];\n const label = name?.trim() || (id ? `User ${id.slice(0, 6)}` : 'Unknown user');\n const seed = name?.trim() || id || label;\n const content =\n typeof jersey === 'number' && Number.isFinite(jersey) ? String(jersey) : initials(label);\n\n return (\n <span\n role=\"img\"\n aria-label={label}\n title={label}\n className={cn(\n 'inline-flex shrink-0 items-center justify-center rounded-full font-semibold leading-none text-[var(--color-action-primary-text)]',\n ring && 'ring-2 ring-[var(--color-bg-surface)]',\n className\n )}\n style={{\n width: px,\n height: px,\n fontSize: Math.max(9, Math.round(px * 0.4)),\n background: gradientCss(seed),\n }}\n >\n <span aria-hidden=\"true\">{content}</span>\n </span>\n );\n};\n"],"mappings":";;;;AAcA,IAAM,IAAW;CAAE,IAAI;CAAI,IAAI;CAAI,IAAI;CAAI,IAAI;CAAI,IAAI;CAAI,EAmB9C,KAAmC,EAC9C,SACA,OACA,WACA,UAAO,MACP,UAAO,IACP,mBACI;CACJ,IAAM,IAAK,OAAO,KAAS,WAAW,IAAO,EAAS,IAChD,IAAQ,GAAM,MAAM,KAAK,IAAK,QAAQ,EAAG,MAAM,GAAG,EAAE,KAAK,iBACzD,IAAO,GAAM,MAAM,IAAI,KAAM,GAC7B,IACJ,OAAO,KAAW,YAAY,OAAO,SAAS,EAAO,GAAG,OAAO,EAAO,GAAG,EAAS,EAAM;AAE1F,QACE,kBAAC,QAAD;EACE,MAAK;EACL,cAAY;EACZ,OAAO;EACP,WAAW,EACT,oIACA,KAAQ,yCACR,EACD;EACD,OAAO;GACL,OAAO;GACP,QAAQ;GACR,UAAU,KAAK,IAAI,GAAG,KAAK,MAAM,IAAK,GAAI,CAAC;GAC3C,YAAY,EAAY,EAAK;GAC9B;YAED,kBAAC,QAAD;GAAM,eAAY;aAAQ;GAAe,CAAA;EACpC,CAAA"}
|
|
@@ -49,8 +49,11 @@ var s = {
|
|
|
49
49
|
WEIGHT: "Weight",
|
|
50
50
|
HEART_RATE: "Heart Rate",
|
|
51
51
|
CUSTOM: "Custom"
|
|
52
|
-
};
|
|
52
|
+
}, m = Object.keys(d);
|
|
53
|
+
function h(e) {
|
|
54
|
+
return e.replace(/_/g, " ").toLowerCase().replace(/\b\w/g, (e) => e.toUpperCase());
|
|
55
|
+
}
|
|
53
56
|
//#endregion
|
|
54
|
-
export { d as FIXTURE_TYPE_LABELS, u as MEMBER_ROLE_LABELS, p as METRIC_TYPE_LABELS, l as PROGRAM_LEVEL_LABELS, f as SESSION_TYPE_LABELS, s as SPORT_ICONS, c as SPORT_LABELS };
|
|
57
|
+
export { d as FIXTURE_TYPE_LABELS, m as FIXTURE_TYPE_OPTIONS, u as MEMBER_ROLE_LABELS, p as METRIC_TYPE_LABELS, l as PROGRAM_LEVEL_LABELS, f as SESSION_TYPE_LABELS, s as SPORT_ICONS, c as SPORT_LABELS, h as humanizeEnum };
|
|
55
58
|
|
|
56
59
|
//# sourceMappingURL=domain.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"domain.js","names":[],"sources":["../../../src/atheletebridge/components/domain.ts"],"sourcesContent":["/**\n * Domain display helpers\n * @module atheletebridge/components\n *\n * Centralized, type-safe labels and icon mappings for the AthleteBridge\n * enums so list/detail/form pages render them consistently.\n */\n\nimport {\n Activity,\n Dumbbell,\n Trophy,\n Volleyball,\n Waves,\n Gamepad2,\n Footprints,\n type LucideIcon,\n} from 'lucide-react';\nimport type {\n AthFixtureType,\n AthMemberRole,\n AthMetricType,\n AthProgramLevel,\n AthSessionType,\n AthSport,\n} from '../types';\n\nexport const SPORT_ICONS: Record<AthSport, LucideIcon> = {\n ATHLETICS: Footprints,\n BASKETBALL: Volleyball,\n CRICKET: Trophy,\n ESPORTS: Gamepad2,\n FOOTBALL: Volleyball,\n OTHER: Activity,\n SWIMMING: Waves,\n TENNIS: Dumbbell,\n};\n\nexport const SPORT_LABELS: Record<AthSport, string> = {\n ATHLETICS: 'Athletics',\n BASKETBALL: 'Basketball',\n CRICKET: 'Cricket',\n ESPORTS: 'Esports',\n FOOTBALL: 'Football',\n OTHER: 'Other',\n SWIMMING: 'Swimming',\n TENNIS: 'Tennis',\n};\n\nexport const PROGRAM_LEVEL_LABELS: Record<AthProgramLevel, string> = {\n BEGINNER: 'Beginner',\n INTERMEDIATE: 'Intermediate',\n ADVANCED: 'Advanced',\n ELITE: 'Elite',\n};\n\nexport const MEMBER_ROLE_LABELS: Record<AthMemberRole, string> = {\n PLAYER: 'Player',\n CAPTAIN: 'Captain',\n COACH: 'Coach',\n ASSISTANT_COACH: 'Assistant Coach',\n PHYSIO: 'Physio',\n MANAGER: 'Manager',\n};\n\nexport const FIXTURE_TYPE_LABELS: Record<AthFixtureType, string> = {\n FRIENDLY: 'Friendly',\n LEAGUE: 'League',\n CUP: 'Cup',\n TOURNAMENT: 'Tournament',\n SCRIMMAGE: 'Scrimmage',\n};\n\nexport const SESSION_TYPE_LABELS: Record<AthSessionType, string> = {\n IN_PERSON: 'In Person',\n ONLINE: 'Online',\n HYBRID: 'Hybrid',\n};\n\nexport const METRIC_TYPE_LABELS: Record<AthMetricType, string> = {\n SPEED: 'Speed',\n ENDURANCE: 'Endurance',\n STRENGTH: 'Strength',\n AGILITY: 'Agility',\n ACCURACY: 'Accuracy',\n WEIGHT: 'Weight',\n HEART_RATE: 'Heart Rate',\n CUSTOM: 'Custom',\n};\n\nexport const SPORT_OPTIONS = Object.keys(SPORT_LABELS) as AthSport[];\nexport const PROGRAM_LEVEL_OPTIONS = Object.keys(PROGRAM_LEVEL_LABELS) as AthProgramLevel[];\nexport const MEMBER_ROLE_OPTIONS = Object.keys(MEMBER_ROLE_LABELS) as AthMemberRole[];\nexport const FIXTURE_TYPE_OPTIONS = Object.keys(FIXTURE_TYPE_LABELS) as AthFixtureType[];\nexport const SESSION_TYPE_OPTIONS = Object.keys(SESSION_TYPE_LABELS) as AthSessionType[];\n\n/** Humanize an enum value for display (fallback when no explicit label exists). */\nexport function humanizeEnum(value: string): string {\n return value\n .replace(/_/g, ' ')\n .toLowerCase()\n .replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n"],"mappings":";;AA2BA,IAAa,IAA4C;CACvD,WAAW;CACX,YAAY;CACZ,SAAS;CACT,SAAS;CACT,UAAU;CACV,OAAO;CACP,UAAU;CACV,QAAQ;CACT,EAEY,IAAyC;CACpD,WAAW;CACX,YAAY;CACZ,SAAS;CACT,SAAS;CACT,UAAU;CACV,OAAO;CACP,UAAU;CACV,QAAQ;CACT,EAEY,IAAwD;CACnE,UAAU;CACV,cAAc;CACd,UAAU;CACV,OAAO;CACR,EAEY,IAAoD;CAC/D,QAAQ;CACR,SAAS;CACT,OAAO;CACP,iBAAiB;CACjB,QAAQ;CACR,SAAS;CACV,EAEY,IAAsD;CACjE,UAAU;CACV,QAAQ;CACR,KAAK;CACL,YAAY;CACZ,WAAW;CACZ,EAEY,IAAsD;CACjE,WAAW;CACX,QAAQ;CACR,QAAQ;CACT,EAEY,IAAoD;CAC/D,OAAO;CACP,WAAW;CACX,UAAU;CACV,SAAS;CACT,UAAU;CACV,QAAQ;CACR,YAAY;CACZ,QAAQ;CACT"}
|
|
1
|
+
{"version":3,"file":"domain.js","names":[],"sources":["../../../src/atheletebridge/components/domain.ts"],"sourcesContent":["/**\n * Domain display helpers\n * @module atheletebridge/components\n *\n * Centralized, type-safe labels and icon mappings for the AthleteBridge\n * enums so list/detail/form pages render them consistently.\n */\n\nimport {\n Activity,\n Dumbbell,\n Trophy,\n Volleyball,\n Waves,\n Gamepad2,\n Footprints,\n type LucideIcon,\n} from 'lucide-react';\nimport type {\n AthFixtureType,\n AthMemberRole,\n AthMetricType,\n AthProgramLevel,\n AthSessionType,\n AthSport,\n} from '../types';\n\nexport const SPORT_ICONS: Record<AthSport, LucideIcon> = {\n ATHLETICS: Footprints,\n BASKETBALL: Volleyball,\n CRICKET: Trophy,\n ESPORTS: Gamepad2,\n FOOTBALL: Volleyball,\n OTHER: Activity,\n SWIMMING: Waves,\n TENNIS: Dumbbell,\n};\n\nexport const SPORT_LABELS: Record<AthSport, string> = {\n ATHLETICS: 'Athletics',\n BASKETBALL: 'Basketball',\n CRICKET: 'Cricket',\n ESPORTS: 'Esports',\n FOOTBALL: 'Football',\n OTHER: 'Other',\n SWIMMING: 'Swimming',\n TENNIS: 'Tennis',\n};\n\nexport const PROGRAM_LEVEL_LABELS: Record<AthProgramLevel, string> = {\n BEGINNER: 'Beginner',\n INTERMEDIATE: 'Intermediate',\n ADVANCED: 'Advanced',\n ELITE: 'Elite',\n};\n\nexport const MEMBER_ROLE_LABELS: Record<AthMemberRole, string> = {\n PLAYER: 'Player',\n CAPTAIN: 'Captain',\n COACH: 'Coach',\n ASSISTANT_COACH: 'Assistant Coach',\n PHYSIO: 'Physio',\n MANAGER: 'Manager',\n};\n\nexport const FIXTURE_TYPE_LABELS: Record<AthFixtureType, string> = {\n FRIENDLY: 'Friendly',\n LEAGUE: 'League',\n CUP: 'Cup',\n TOURNAMENT: 'Tournament',\n SCRIMMAGE: 'Scrimmage',\n};\n\nexport const SESSION_TYPE_LABELS: Record<AthSessionType, string> = {\n IN_PERSON: 'In Person',\n ONLINE: 'Online',\n HYBRID: 'Hybrid',\n};\n\nexport const METRIC_TYPE_LABELS: Record<AthMetricType, string> = {\n SPEED: 'Speed',\n ENDURANCE: 'Endurance',\n STRENGTH: 'Strength',\n AGILITY: 'Agility',\n ACCURACY: 'Accuracy',\n WEIGHT: 'Weight',\n HEART_RATE: 'Heart Rate',\n CUSTOM: 'Custom',\n};\n\nexport const SPORT_OPTIONS = Object.keys(SPORT_LABELS) as AthSport[];\nexport const PROGRAM_LEVEL_OPTIONS = Object.keys(PROGRAM_LEVEL_LABELS) as AthProgramLevel[];\nexport const MEMBER_ROLE_OPTIONS = Object.keys(MEMBER_ROLE_LABELS) as AthMemberRole[];\nexport const FIXTURE_TYPE_OPTIONS = Object.keys(FIXTURE_TYPE_LABELS) as AthFixtureType[];\nexport const SESSION_TYPE_OPTIONS = Object.keys(SESSION_TYPE_LABELS) as AthSessionType[];\n\n/** Humanize an enum value for display (fallback when no explicit label exists). */\nexport function humanizeEnum(value: string): string {\n return value\n .replace(/_/g, ' ')\n .toLowerCase()\n .replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n"],"mappings":";;AA2BA,IAAa,IAA4C;CACvD,WAAW;CACX,YAAY;CACZ,SAAS;CACT,SAAS;CACT,UAAU;CACV,OAAO;CACP,UAAU;CACV,QAAQ;CACT,EAEY,IAAyC;CACpD,WAAW;CACX,YAAY;CACZ,SAAS;CACT,SAAS;CACT,UAAU;CACV,OAAO;CACP,UAAU;CACV,QAAQ;CACT,EAEY,IAAwD;CACnE,UAAU;CACV,cAAc;CACd,UAAU;CACV,OAAO;CACR,EAEY,IAAoD;CAC/D,QAAQ;CACR,SAAS;CACT,OAAO;CACP,iBAAiB;CACjB,QAAQ;CACR,SAAS;CACV,EAEY,IAAsD;CACjE,UAAU;CACV,QAAQ;CACR,KAAK;CACL,YAAY;CACZ,WAAW;CACZ,EAEY,IAAsD;CACjE,WAAW;CACX,QAAQ;CACR,QAAQ;CACT,EAEY,IAAoD;CAC/D,OAAO;CACP,WAAW;CACX,UAAU;CACV,SAAS;CACT,UAAU;CACV,QAAQ;CACR,YAAY;CACZ,QAAQ;CACT,EAKY,IAAuB,OAAO,KAAK,EAAoB;AAIpE,SAAgB,EAAa,GAAuB;AAClD,QAAO,EACJ,QAAQ,MAAM,IAAI,CAClB,aAAa,CACb,QAAQ,UAAU,MAAM,EAAE,aAAa,CAAC"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { chartPalette as e } from "./lib/palette.js";
|
|
2
|
+
import { useMounted as t, usePrefersReducedMotion as n } from "./lib/hooks.js";
|
|
3
|
+
import { ResponsiveSvg as r } from "./lib/ResponsiveSvg.js";
|
|
4
|
+
import { scaleLinear as i } from "./lib/geometry.js";
|
|
5
|
+
import { formatCompact as a } from "./lib/format.js";
|
|
6
|
+
import { jsx as o, jsxs as s } from "react/jsx-runtime";
|
|
7
|
+
//#region src/atheletebridge/components/viz/BarChart.tsx
|
|
8
|
+
var c = {
|
|
9
|
+
top: 20,
|
|
10
|
+
bottom: 26,
|
|
11
|
+
left: 8,
|
|
12
|
+
right: 8
|
|
13
|
+
};
|
|
14
|
+
function l({ series: l, height: u = 220, valueFormatter: d = a, colorful: f = !1, hideValues: p = !1, title: m, className: h }) {
|
|
15
|
+
let g = t(), _ = n();
|
|
16
|
+
if (l.length === 0) return null;
|
|
17
|
+
let v = l.map((e) => e.value), y = Math.max(...v, 0) || 1, b = e(l.length);
|
|
18
|
+
return /* @__PURE__ */ o(r, {
|
|
19
|
+
height: u,
|
|
20
|
+
title: m ?? `Bar chart of ${l.length} categories`,
|
|
21
|
+
className: h,
|
|
22
|
+
desc: l.map((e) => `${e.label}: ${d(e.value)}`).join(", "),
|
|
23
|
+
children: ({ width: e }) => {
|
|
24
|
+
let t = c.left, n = e - c.right, r = c.top, a = u - c.bottom, m = a - r, h = (n - t) / l.length, v = Math.min(56, h * .62);
|
|
25
|
+
return /* @__PURE__ */ s("g", { children: [/* @__PURE__ */ o("line", {
|
|
26
|
+
x1: t,
|
|
27
|
+
x2: n,
|
|
28
|
+
y1: a,
|
|
29
|
+
y2: a,
|
|
30
|
+
stroke: "var(--color-chart-axis)",
|
|
31
|
+
strokeWidth: 1,
|
|
32
|
+
vectorEffect: "non-scaling-stroke",
|
|
33
|
+
opacity: .5
|
|
34
|
+
}), l.map((e, n) => {
|
|
35
|
+
let r = i(e.value, 0, y, 0, m), c = t + h * n + h / 2, l = c - v / 2, x = a - r, S = e.color ?? (f ? b[n] : "var(--color-accent-primary)");
|
|
36
|
+
return /* @__PURE__ */ s("g", { children: [
|
|
37
|
+
/* @__PURE__ */ o("title", { children: `${e.label}: ${d(e.value)}` }),
|
|
38
|
+
/* @__PURE__ */ o("rect", {
|
|
39
|
+
x: l,
|
|
40
|
+
y: x,
|
|
41
|
+
width: v,
|
|
42
|
+
height: Math.max(0, r),
|
|
43
|
+
rx: 4,
|
|
44
|
+
fill: S,
|
|
45
|
+
style: {
|
|
46
|
+
transformBox: "fill-box",
|
|
47
|
+
transformOrigin: "bottom",
|
|
48
|
+
transform: g || _ ? "scaleY(1)" : "scaleY(0)",
|
|
49
|
+
transition: _ ? void 0 : "transform 620ms cubic-bezier(0.22,1,0.36,1)",
|
|
50
|
+
transitionDelay: _ ? void 0 : `${n * 45}ms`
|
|
51
|
+
}
|
|
52
|
+
}),
|
|
53
|
+
p ? null : /* @__PURE__ */ o("text", {
|
|
54
|
+
x: c,
|
|
55
|
+
y: x - 6,
|
|
56
|
+
textAnchor: "middle",
|
|
57
|
+
fontSize: 10,
|
|
58
|
+
fontWeight: 600,
|
|
59
|
+
fill: "var(--color-text-secondary)",
|
|
60
|
+
style: {
|
|
61
|
+
opacity: g || _ ? 1 : 0,
|
|
62
|
+
transition: _ ? void 0 : "opacity 400ms ease",
|
|
63
|
+
transitionDelay: _ ? void 0 : `${n * 45 + 220}ms`
|
|
64
|
+
},
|
|
65
|
+
children: d(e.value)
|
|
66
|
+
}),
|
|
67
|
+
/* @__PURE__ */ o("text", {
|
|
68
|
+
x: c,
|
|
69
|
+
y: u - 8,
|
|
70
|
+
textAnchor: "middle",
|
|
71
|
+
fontSize: 10,
|
|
72
|
+
fill: "var(--color-chart-axis)",
|
|
73
|
+
children: e.label
|
|
74
|
+
})
|
|
75
|
+
] }, `${e.label}-${n}`);
|
|
76
|
+
})] });
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
81
|
+
export { l as BarChart };
|
|
82
|
+
|
|
83
|
+
//# sourceMappingURL=BarChart.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BarChart.js","names":[],"sources":["../../../../src/atheletebridge/components/viz/BarChart.tsx"],"sourcesContent":["import { ACCENT, chartPalette } from './lib/palette';\nimport { ResponsiveSvg } from './lib/ResponsiveSvg';\nimport { scaleLinear } from './lib/geometry';\nimport { formatCompact } from './lib/format';\nimport { useMounted, usePrefersReducedMotion } from './lib/hooks';\nimport type { Segment } from './lib/types';\n\nexport interface BarChartProps {\n /** Bars to render. A `color` on a datum overrides the palette. */\n series: Segment[];\n height?: number;\n valueFormatter?: (value: number) => string;\n /** Color each bar from the categorical palette instead of a single accent. */\n colorful?: boolean;\n /** Hide the value label above each bar. */\n hideValues?: boolean;\n title?: string;\n className?: string;\n}\n\nconst PAD = { top: 20, bottom: 26, left: 8, right: 8 };\n\n/** Vertical bar chart with value labels and an animated grow-in. */\nexport function BarChart({\n series,\n height = 220,\n valueFormatter = formatCompact,\n colorful = false,\n hideValues = false,\n title,\n className,\n}: BarChartProps) {\n const mounted = useMounted();\n const reduced = usePrefersReducedMotion();\n\n if (series.length === 0) {\n return null;\n }\n\n const values = series.map((s) => s.value);\n const max = Math.max(...values, 0) || 1;\n const palette = chartPalette(series.length);\n const chartTitle = title ?? `Bar chart of ${series.length} categories`;\n\n return (\n <ResponsiveSvg\n height={height}\n title={chartTitle}\n className={className}\n desc={series.map((s) => `${s.label}: ${valueFormatter(s.value)}`).join(', ')}\n >\n {({ width }) => {\n const plotLeft = PAD.left;\n const plotRight = width - PAD.right;\n const plotTop = PAD.top;\n const plotBottom = height - PAD.bottom;\n const plotH = plotBottom - plotTop;\n const slot = (plotRight - plotLeft) / series.length;\n const barWidth = Math.min(56, slot * 0.62);\n return (\n <g>\n <line\n x1={plotLeft}\n x2={plotRight}\n y1={plotBottom}\n y2={plotBottom}\n stroke=\"var(--color-chart-axis)\"\n strokeWidth={1}\n vectorEffect=\"non-scaling-stroke\"\n opacity={0.5}\n />\n {series.map((datum, i) => {\n const barH = scaleLinear(datum.value, 0, max, 0, plotH);\n const cx = plotLeft + slot * i + slot / 2;\n const x = cx - barWidth / 2;\n const y = plotBottom - barH;\n const color = datum.color ?? (colorful ? palette[i] : ACCENT);\n return (\n <g key={`${datum.label}-${i}`}>\n <title>{`${datum.label}: ${valueFormatter(datum.value)}`}</title>\n <rect\n x={x}\n y={y}\n width={barWidth}\n height={Math.max(0, barH)}\n rx={4}\n fill={color}\n style={{\n transformBox: 'fill-box',\n transformOrigin: 'bottom',\n transform: mounted || reduced ? 'scaleY(1)' : 'scaleY(0)',\n transition: reduced\n ? undefined\n : 'transform 620ms cubic-bezier(0.22,1,0.36,1)',\n transitionDelay: reduced ? undefined : `${i * 45}ms`,\n }}\n />\n {hideValues ? null : (\n <text\n x={cx}\n y={y - 6}\n textAnchor=\"middle\"\n fontSize={10}\n fontWeight={600}\n fill=\"var(--color-text-secondary)\"\n style={{\n opacity: mounted || reduced ? 1 : 0,\n transition: reduced ? undefined : 'opacity 400ms ease',\n transitionDelay: reduced ? undefined : `${i * 45 + 220}ms`,\n }}\n >\n {valueFormatter(datum.value)}\n </text>\n )}\n <text\n x={cx}\n y={height - 8}\n textAnchor=\"middle\"\n fontSize={10}\n fill=\"var(--color-chart-axis)\"\n >\n {datum.label}\n </text>\n </g>\n );\n })}\n </g>\n );\n }}\n </ResponsiveSvg>\n );\n}\n"],"mappings":";;;;;;;AAoBA,IAAM,IAAM;CAAE,KAAK;CAAI,QAAQ;CAAI,MAAM;CAAG,OAAO;CAAG;AAGtD,SAAgB,EAAS,EACvB,WACA,YAAS,KACT,oBAAiB,GACjB,cAAW,IACX,gBAAa,IACb,UACA,gBACgB;CAChB,IAAM,IAAU,GAAY,EACtB,IAAU,GAAyB;AAEzC,KAAI,EAAO,WAAW,EACpB,QAAO;CAGT,IAAM,IAAS,EAAO,KAAK,MAAM,EAAE,MAAM,EACnC,IAAM,KAAK,IAAI,GAAG,GAAQ,EAAE,IAAI,GAChC,IAAU,EAAa,EAAO,OAAO;AAG3C,QACE,kBAAC,GAAD;EACU;EACR,OALe,KAAS,gBAAgB,EAAO,OAAO;EAM3C;EACX,MAAM,EAAO,KAAK,MAAM,GAAG,EAAE,MAAM,IAAI,EAAe,EAAE,MAAM,GAAG,CAAC,KAAK,KAAK;aAE1E,EAAE,eAAY;GACd,IAAM,IAAW,EAAI,MACf,IAAY,IAAQ,EAAI,OACxB,IAAU,EAAI,KACd,IAAa,IAAS,EAAI,QAC1B,IAAQ,IAAa,GACrB,KAAQ,IAAY,KAAY,EAAO,QACvC,IAAW,KAAK,IAAI,IAAI,IAAO,IAAK;AAC1C,UACE,kBAAC,KAAD,EAAA,UAAA,CACE,kBAAC,QAAD;IACE,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,QAAO;IACP,aAAa;IACb,cAAa;IACb,SAAS;IACT,CAAA,EACD,EAAO,KAAK,GAAO,MAAM;IACxB,IAAM,IAAO,EAAY,EAAM,OAAO,GAAG,GAAK,GAAG,EAAM,EACjD,IAAK,IAAW,IAAO,IAAI,IAAO,GAClC,IAAI,IAAK,IAAW,GACpB,IAAI,IAAa,GACjB,IAAQ,EAAM,UAAU,IAAW,EAAQ,KAAA;AACjD,WACE,kBAAC,KAAD,EAAA,UAAA;KACE,kBAAC,SAAD,EAAA,UAAQ,GAAG,EAAM,MAAM,IAAI,EAAe,EAAM,MAAM,IAAW,CAAA;KACjE,kBAAC,QAAD;MACK;MACA;MACH,OAAO;MACP,QAAQ,KAAK,IAAI,GAAG,EAAK;MACzB,IAAI;MACJ,MAAM;MACN,OAAO;OACL,cAAc;OACd,iBAAiB;OACjB,WAAW,KAAW,IAAU,cAAc;OAC9C,YAAY,IACR,KAAA,IACA;OACJ,iBAAiB,IAAU,KAAA,IAAY,GAAG,IAAI,GAAG;OAClD;MACD,CAAA;KACD,IAAa,OACZ,kBAAC,QAAD;MACE,GAAG;MACH,GAAG,IAAI;MACP,YAAW;MACX,UAAU;MACV,YAAY;MACZ,MAAK;MACL,OAAO;OACL,SAAS,KAAW,IAAU,IAAI;OAClC,YAAY,IAAU,KAAA,IAAY;OAClC,iBAAiB,IAAU,KAAA,IAAY,GAAG,IAAI,KAAK,IAAI;OACxD;gBAEA,EAAe,EAAM,MAAM;MACvB,CAAA;KAET,kBAAC,QAAD;MACE,GAAG;MACH,GAAG,IAAS;MACZ,YAAW;MACX,UAAU;MACV,MAAK;gBAEJ,EAAM;MACF,CAAA;KACL,EAAA,EA7CI,GAAG,EAAM,MAAM,GAAG,IA6CtB;KAEN,CACA,EAAA,CAAA;;EAGM,CAAA"}
|