@zendir/ui 0.1.15 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dist/index.js +6 -0
  3. package/dist/index.js.map +1 -1
  4. package/dist/react/astro/SimulationControls.js +3 -3
  5. package/dist/react/astro/SimulationControls.js.map +1 -1
  6. package/dist/react/astro/UnifiedTimeline.d.ts +56 -6
  7. package/dist/react/astro/UnifiedTimeline.js +628 -428
  8. package/dist/react/astro/UnifiedTimeline.js.map +1 -1
  9. package/dist/react/astro/index.d.ts +2 -2
  10. package/dist/react/charts/GroundTrackMap.d.ts +40 -1
  11. package/dist/react/charts/GroundTrackMap.js +98 -47
  12. package/dist/react/charts/GroundTrackMap.js.map +1 -1
  13. package/dist/react/charts/GroundTrackMapLeaflet.d.ts +11 -2
  14. package/dist/react/charts/GroundTrackMapLeaflet.js +128 -15
  15. package/dist/react/charts/GroundTrackMapLeaflet.js.map +1 -1
  16. package/dist/react/charts/index.d.ts +1 -1
  17. package/dist/react/charts/unified/theme.d.ts +7 -7
  18. package/dist/react/context/CategoryContext.d.ts +51 -0
  19. package/dist/react/context/CategoryContext.js +36 -0
  20. package/dist/react/context/CategoryContext.js.map +1 -0
  21. package/dist/react/context/index.d.ts +2 -0
  22. package/dist/react/index.d.ts +6 -4
  23. package/dist/react/types.d.ts +26 -0
  24. package/dist/react/types.js.map +1 -1
  25. package/dist/react/utils/categoryPalette.d.ts +43 -0
  26. package/dist/react/utils/categoryPalette.js +104 -0
  27. package/dist/react/utils/categoryPalette.js.map +1 -0
  28. package/dist/react/utils/index.d.ts +1 -0
  29. package/dist/react/utils/index.js.map +1 -1
  30. package/dist/react.js +6 -0
  31. package/dist/react.js.map +1 -1
  32. package/dist/style.css +49 -0
  33. package/package.json +1 -1
@@ -1,11 +1,53 @@
1
1
  import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
2
  import { memo, useState, useCallback, useMemo, useRef } from "react";
3
3
  import { classNames, safeAccentText } from "../utils/index.js";
4
+ import { useCategoryPalette } from "../context/CategoryContext.js";
4
5
  import { Icon } from "../core/Icon.js";
5
6
  import { Badge } from "../core/Badge.js";
6
7
  import { Tooltip } from "../core/Tooltip.js";
7
- import { Checkbox } from "../core/Checkbox.js";
8
+ import { Button } from "../core/Button.js";
9
+ import { useBreakpoint } from "../core/layout/useBreakpoint.js";
8
10
  import { useTheme } from "../theme/ThemeProvider.js";
11
+ const Z = {
12
+ /** Base layer for track events / scatter dots */
13
+ trackEvent: 1,
14
+ /** Hovered track event raised above siblings */
15
+ trackEventHovered: 20,
16
+ /** Sticky column elements (playhead, track labels) */
17
+ sticky: 15,
18
+ /** Floating day-marker lines above track content */
19
+ dayMarker: 5,
20
+ /** Loading overlay covering chart content */
21
+ loadingOverlay: 100,
22
+ /** Floating tooltips — above all chart chrome */
23
+ tooltip: 1e3
24
+ };
25
+ const TIMELINE_FILTER_TEAM_NONE = "__zendir_team_none__";
26
+ const TIMELINE_FILTER_TEAM_LEGACY = "__zendir_team_legacy__";
27
+ function getTimelineTeamAccent(event) {
28
+ const t = event.team;
29
+ if (t == null ? void 0 : t.color) {
30
+ return { color: t.color, id: t.id, label: t.label };
31
+ }
32
+ if (event.color) {
33
+ return { color: event.color };
34
+ }
35
+ return null;
36
+ }
37
+ function getTimelineTeamDisplayLabel(event, accent) {
38
+ if (accent.label) return accent.label;
39
+ if (accent.id !== void 0 && accent.id !== "") return String(accent.id);
40
+ const args = event.arguments;
41
+ const argsTeam = args == null ? void 0 : args.Team;
42
+ if (argsTeam !== void 0 && argsTeam !== "") return String(argsTeam);
43
+ return event.badge || "Team";
44
+ }
45
+ function getTimelineTeamFilterKey(event) {
46
+ const accent = getTimelineTeamAccent(event);
47
+ if (!accent) return TIMELINE_FILTER_TEAM_NONE;
48
+ if (accent.id === void 0 || accent.id === "") return TIMELINE_FILTER_TEAM_LEGACY;
49
+ return String(accent.id);
50
+ }
9
51
  const ALL_TIMELINE_STATUSES = [
10
52
  "off",
11
53
  "standby",
@@ -22,22 +64,23 @@ function getTimelineEventDisplayStatus(event) {
22
64
  }
23
65
  function isTimelineFilterActive(filter) {
24
66
  return Boolean(
25
- filter.search && filter.search.trim() || filter.tracks && filter.tracks.length > 0 || filter.status && filter.status.length > 0 || filter.eventShape && filter.eventShape !== "all" || filter.teamColoredOnly
67
+ filter.search && filter.search.trim() || filter.tracks && filter.tracks.length > 0 || filter.status && filter.status.length > 0 || filter.eventShape && filter.eventShape !== "all" || filter.teams && filter.teams.length > 0 || filter.teamColoredOnly
26
68
  );
27
69
  }
28
70
  function normalizeTimelineFilter(f) {
29
- var _a, _b, _c;
71
+ var _a, _b, _c, _d;
30
72
  const o = {};
31
73
  const s = (_a = f.search) == null ? void 0 : _a.trim();
32
74
  if (s) o.search = s;
33
75
  if ((_b = f.tracks) == null ? void 0 : _b.length) o.tracks = [...f.tracks];
34
76
  if ((_c = f.status) == null ? void 0 : _c.length) o.status = [...f.status];
35
77
  if (f.eventShape && f.eventShape !== "all") o.eventShape = f.eventShape;
78
+ if ((_d = f.teams) == null ? void 0 : _d.length) o.teams = [...f.teams];
36
79
  if (f.teamColoredOnly) o.teamColoredOnly = true;
37
80
  return o;
38
81
  }
39
82
  function matchesTimelineFilter(event, filter) {
40
- var _a, _b, _c, _d, _e;
83
+ var _a, _b, _c, _d, _e, _f;
41
84
  if (!isTimelineFilterActive(filter)) return true;
42
85
  if ((_a = filter.tracks) == null ? void 0 : _a.length) {
43
86
  const tid = event.track ?? "default";
@@ -50,12 +93,15 @@ function matchesTimelineFilter(event, filter) {
50
93
  const q = (_c = filter.search) == null ? void 0 : _c.trim().toLowerCase();
51
94
  if (q) {
52
95
  const argStr = event.arguments ? Object.values(event.arguments).map((v) => String(v)).join(" ") : "";
96
+ const accent = getTimelineTeamAccent(event);
97
+ const teamHay = accent ? [accent.label, accent.id !== void 0 ? String(accent.id) : ""].filter(Boolean).join(" ") : "";
53
98
  const hay = [
54
99
  event.title,
55
100
  event.subtitle,
56
101
  event.badge,
57
102
  event.track,
58
- argStr
103
+ argStr,
104
+ teamHay
59
105
  ].filter(Boolean).join(" ").toLowerCase();
60
106
  if (!hay.includes(q)) return false;
61
107
  }
@@ -69,7 +115,13 @@ function matchesTimelineFilter(event, filter) {
69
115
  const st = event.start.getTime();
70
116
  if (endT == null || endT <= st) return false;
71
117
  }
72
- if (filter.teamColoredOnly && !event.color) return false;
118
+ if ((_f = filter.teams) == null ? void 0 : _f.length) {
119
+ const teamKey = getTimelineTeamFilterKey(event);
120
+ if (!filter.teams.includes(teamKey)) return false;
121
+ }
122
+ if (filter.teamColoredOnly && !(filter.teams && filter.teams.length > 0) && !getTimelineTeamAccent(event)) {
123
+ return false;
124
+ }
73
125
  return true;
74
126
  }
75
127
  function countTimelineFilterChips(filter) {
@@ -79,7 +131,8 @@ function countTimelineFilterChips(filter) {
79
131
  if ((_b = filter.tracks) == null ? void 0 : _b.length) n++;
80
132
  if ((_c = filter.status) == null ? void 0 : _c.length) n++;
81
133
  if (filter.eventShape && filter.eventShape !== "all") n++;
82
- if (filter.teamColoredOnly) n++;
134
+ if (filter.teams && filter.teams.length > 0) n++;
135
+ if (filter.teamColoredOnly && !(filter.teams && filter.teams.length > 0)) n++;
83
136
  return n;
84
137
  }
85
138
  function TimelineStatusMarker({
@@ -121,6 +174,7 @@ const EventListItem = memo(function EventListItem2({
121
174
  ];
122
175
  const displayStatus = event.badgeVariant && event.badgeVariant !== "default" && event.badgeVariant !== "primary" ? event.badgeVariant : event.status ?? "normal";
123
176
  const markerColor = tokens.colors.status[displayStatus];
177
+ const teamAccent = getTimelineTeamAccent(event);
124
178
  return /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "16px" }, children: [
125
179
  /* @__PURE__ */ jsxs(
126
180
  "div",
@@ -158,7 +212,7 @@ const EventListItem = memo(function EventListItem2({
158
212
  position: "relative",
159
213
  flex: 1,
160
214
  marginBottom: "16px",
161
- padding: event.color ? "19px 20px 16px 20px" : "16px 20px",
215
+ padding: teamAccent ? "19px 20px 16px 20px" : "16px 20px",
162
216
  backgroundColor: tokens.colors.background.surface,
163
217
  border: `1px solid ${hovered ? tokens.colors.accent.primary : tokens.colors.border.muted}`,
164
218
  borderRadius: tokens.borderRadius.lg,
@@ -168,7 +222,7 @@ const EventListItem = memo(function EventListItem2({
168
222
  boxShadow: hovered ? `0 4px 20px ${tokens.colors.accent.primary}15, 0 0 0 1px ${tokens.colors.accent.primary}20` : "none"
169
223
  },
170
224
  children: [
171
- event.color && /* @__PURE__ */ jsx(
225
+ teamAccent && /* @__PURE__ */ jsx(
172
226
  "div",
173
227
  {
174
228
  style: {
@@ -177,7 +231,7 @@ const EventListItem = memo(function EventListItem2({
177
231
  left: 0,
178
232
  right: 0,
179
233
  height: 3,
180
- backgroundColor: event.color
234
+ backgroundColor: teamAccent.color
181
235
  }
182
236
  }
183
237
  ),
@@ -390,6 +444,7 @@ const ChartView = memo(function ChartView2({
390
444
  const [hoveredEvent, setHoveredEvent] = useState(null);
391
445
  const [tooltipPos, setTooltipPos] = useState(null);
392
446
  const scrollContainerRef = useRef(null);
447
+ const [scrollX, setScrollX] = useState(0);
393
448
  const totalDuration = end.getTime() - start.getTime();
394
449
  const trackLabelWidth = 130;
395
450
  const timeLabels = useMemo(() => {
@@ -493,6 +548,22 @@ const ChartView = memo(function ChartView2({
493
548
  if (!status) return tokens.colors.accent.primary;
494
549
  return tokens.colors.status[status] || tokens.colors.accent.primary;
495
550
  };
551
+ const isEventVisible = useCallback(
552
+ (event) => {
553
+ const container = scrollContainerRef.current;
554
+ if (!container) return true;
555
+ const viewportWidth = container.clientWidth - trackLabelWidth;
556
+ if (viewportWidth <= 0) return true;
557
+ const totalWidth = container.scrollWidth - trackLabelWidth;
558
+ const eventStartMs = event.start.getTime();
559
+ const eventEndMs = (event.end ?? new Date(eventStartMs + 3e5)).getTime();
560
+ const leftPx = (eventStartMs - start.getTime()) / totalDuration * totalWidth;
561
+ const rightPx = (eventEndMs - start.getTime()) / totalDuration * totalWidth;
562
+ const overscan = 50;
563
+ return rightPx >= scrollX - overscan && leftPx <= scrollX + viewportWidth + overscan;
564
+ },
565
+ [start, totalDuration, scrollX, trackLabelWidth]
566
+ );
496
567
  const getStatusLabel = (status) => {
497
568
  const labels = {
498
569
  normal: "Normal",
@@ -514,6 +585,7 @@ const ChartView = memo(function ChartView2({
514
585
  {
515
586
  ref: scrollContainerRef,
516
587
  className: "zendir-timeline-scroll",
588
+ onScroll: (e) => setScrollX(e.currentTarget.scrollLeft),
517
589
  style: {
518
590
  overflowX: "auto",
519
591
  overflowY: "hidden",
@@ -537,7 +609,7 @@ const ChartView = memo(function ChartView2({
537
609
  borderBottom: `1px solid ${tokens.colors.accent.primary}20`,
538
610
  position: "sticky",
539
611
  top: 0,
540
- zIndex: 20,
612
+ zIndex: Z.trackEventHovered,
541
613
  backgroundColor: isTransparentTheme ? "transparent" : tokens.colors.background.surface,
542
614
  ...isTransparentTheme && { backdropFilter: "blur(12px)", WebkitBackdropFilter: "blur(12px)" }
543
615
  },
@@ -550,7 +622,7 @@ const ChartView = memo(function ChartView2({
550
622
  flexShrink: 0,
551
623
  position: "sticky",
552
624
  left: 0,
553
- zIndex: 25,
625
+ zIndex: Z.trackEventHovered + 5,
554
626
  backgroundColor: isTransparentTheme ? "transparent" : tokens.colors.background.surface,
555
627
  ...isTransparentTheme && { backdropFilter: "blur(12px)", WebkitBackdropFilter: "blur(12px)" },
556
628
  borderRight: `1px solid ${tokens.colors.border.muted}30`,
@@ -579,7 +651,7 @@ const ChartView = memo(function ChartView2({
579
651
  display: "flex",
580
652
  flexDirection: "column",
581
653
  alignItems: "center",
582
- zIndex: 5
654
+ zIndex: Z.dayMarker
583
655
  },
584
656
  children: /* @__PURE__ */ jsx(
585
657
  "span",
@@ -639,7 +711,7 @@ const ChartView = memo(function ChartView2({
639
711
  flexShrink: 0,
640
712
  position: "sticky",
641
713
  left: 0,
642
- zIndex: 15,
714
+ zIndex: Z.sticky,
643
715
  display: "flex",
644
716
  alignItems: "center",
645
717
  paddingLeft: 12,
@@ -676,19 +748,20 @@ const ChartView = memo(function ChartView2({
676
748
  bottom: 0,
677
749
  width: 1,
678
750
  backgroundColor: `${tokens.colors.status.caution}25`,
679
- zIndex: 1,
751
+ zIndex: Z.trackEvent,
680
752
  pointerEvents: "none"
681
753
  }
682
754
  },
683
755
  `day-line-${index}`
684
756
  )),
685
- (eventsByTrack[track.id] || []).map((event) => {
757
+ (eventsByTrack[track.id] || []).filter(isEventVisible).map((event) => {
686
758
  const { left, width } = getEventPosition(event);
687
759
  const displayStatus = event.badgeVariant && event.badgeVariant !== "default" && event.badgeVariant !== "primary" ? event.badgeVariant : event.status ?? "normal";
688
760
  const color = getStatusColor(displayStatus);
689
761
  const isHovered = hoveredEvent === event.id;
690
762
  const overlap = eventOverlaps[event.id];
691
763
  const hasOverlap = overlap && overlap.overlapCount > 0;
764
+ const teamAccent = getTimelineTeamAccent(event);
692
765
  const cardHeight = hasOverlap ? 30 : 38;
693
766
  const stackOffset = overlap ? overlap.stackIndex * 30 : 0;
694
767
  const baseTop = ((track.height || trackHeight) - cardHeight) / 2;
@@ -696,6 +769,7 @@ const ChartView = memo(function ChartView2({
696
769
  "button",
697
770
  {
698
771
  type: "button",
772
+ "aria-label": `${event.title}${event.status ? `, status: ${event.status}` : ""}`,
699
773
  onClick: () => onEventClick == null ? void 0 : onEventClick(event),
700
774
  onMouseEnter: (e) => {
701
775
  setHoveredEvent(event.id);
@@ -706,6 +780,15 @@ const ChartView = memo(function ChartView2({
706
780
  setHoveredEvent(null);
707
781
  setTooltipPos(null);
708
782
  },
783
+ onFocus: (e) => {
784
+ setHoveredEvent(event.id);
785
+ const rect = e.currentTarget.getBoundingClientRect();
786
+ setTooltipPos({ x: rect.left + rect.width / 2, y: rect.top - 8 });
787
+ },
788
+ onBlur: () => {
789
+ setHoveredEvent(null);
790
+ setTooltipPos(null);
791
+ },
709
792
  className: "zendir-timeline-region",
710
793
  style: {
711
794
  position: "absolute",
@@ -721,13 +804,13 @@ const ChartView = memo(function ChartView2({
721
804
  display: "flex",
722
805
  alignItems: "center",
723
806
  overflow: "hidden",
724
- zIndex: isHovered ? 100 : 1,
807
+ zIndex: isHovered ? Z.trackEventHovered : Z.trackEvent,
725
808
  boxShadow: isHovered ? `0 8px 24px rgba(0, 0, 0, 0.5), 0 0 0 1px ${color}60` : "0 2px 6px rgba(0, 0, 0, 0.25)",
726
809
  transition: "all 150ms cubic-bezier(0.4, 0, 0.2, 1)",
727
810
  transform: isHovered ? "translateY(-3px) scale(1.02)" : "none"
728
811
  },
729
812
  children: [
730
- event.color ? /* @__PURE__ */ jsx(
813
+ teamAccent ? /* @__PURE__ */ jsx(
731
814
  "div",
732
815
  {
733
816
  style: {
@@ -736,7 +819,7 @@ const ChartView = memo(function ChartView2({
736
819
  left: 0,
737
820
  right: 0,
738
821
  height: 2,
739
- backgroundColor: event.color,
822
+ backgroundColor: teamAccent.color,
740
823
  borderRadius: "4px 4px 0 0",
741
824
  zIndex: 1,
742
825
  pointerEvents: "none"
@@ -749,7 +832,7 @@ const ChartView = memo(function ChartView2({
749
832
  {
750
833
  style: {
751
834
  flex: 1,
752
- padding: `${event.color ? 6 : 4}px 8px 4px 8px`,
835
+ padding: `${teamAccent ? 6 : 4}px 8px 4px 8px`,
753
836
  overflow: "hidden",
754
837
  minWidth: 0,
755
838
  display: "flex",
@@ -830,7 +913,7 @@ const ChartView = memo(function ChartView2({
830
913
  fontWeight: 700,
831
914
  color: "#000",
832
915
  boxShadow: "0 2px 6px rgba(0, 0, 0, 0.3)",
833
- zIndex: 10
916
+ zIndex: Z.sticky - 5
834
917
  },
835
918
  title: `${overlap.overlapCount + 1} overlapping events`,
836
919
  children: [
@@ -862,7 +945,7 @@ const ChartView = memo(function ChartView2({
862
945
  width: 2,
863
946
  backgroundColor: tokens.colors.accent.primary,
864
947
  boxShadow: `0 0 12px ${tokens.colors.accent.primary}`,
865
- zIndex: 20,
948
+ zIndex: Z.trackEventHovered,
866
949
  pointerEvents: "none"
867
950
  },
868
951
  children: [
@@ -913,8 +996,8 @@ const ChartView = memo(function ChartView2({
913
996
  }
914
997
  ),
915
998
  hoveredEventData && tooltipPos && (() => {
916
- var _a;
917
999
  const tooltipColor = getStatusColor(hoveredEventData.status);
1000
+ const teamTip = getTimelineTeamAccent(hoveredEventData);
918
1001
  return /* @__PURE__ */ jsxs(
919
1002
  "div",
920
1003
  {
@@ -923,7 +1006,7 @@ const ChartView = memo(function ChartView2({
923
1006
  left: tooltipPos.x,
924
1007
  top: tooltipPos.y,
925
1008
  transform: "translate(-50%, -100%)",
926
- zIndex: 1e3,
1009
+ zIndex: Z.tooltip,
927
1010
  pointerEvents: "none",
928
1011
  animation: "zendir-tooltip-appear 150ms ease"
929
1012
  },
@@ -982,9 +1065,9 @@ const ChartView = memo(function ChartView2({
982
1065
  }
983
1066
  )
984
1067
  ] }),
985
- hoveredEventData.color && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6, marginBottom: 6 }, children: [
986
- /* @__PURE__ */ jsx("div", { style: { width: 8, height: 8, borderRadius: 2, backgroundColor: hoveredEventData.color, flexShrink: 0 } }),
987
- /* @__PURE__ */ jsx("span", { style: { fontSize: "0.6875rem", color: hoveredEventData.color, fontWeight: 500 }, children: ((_a = hoveredEventData.arguments) == null ? void 0 : _a.Team) || hoveredEventData.badge || "Team" })
1068
+ teamTip && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6, marginBottom: 6 }, children: [
1069
+ /* @__PURE__ */ jsx("div", { style: { width: 8, height: 8, borderRadius: 2, backgroundColor: teamTip.color, flexShrink: 0 } }),
1070
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "0.6875rem", color: teamTip.color, fontWeight: 500 }, children: getTimelineTeamDisplayLabel(hoveredEventData, teamTip) })
988
1071
  ] }),
989
1072
  /* @__PURE__ */ jsx(
990
1073
  "div",
@@ -1265,11 +1348,14 @@ const ScatterView = memo(function ScatterView2({
1265
1348
  const color = getStatusColor(event.status);
1266
1349
  const isHovered = hoveredEvent === event.id;
1267
1350
  const dotSize = isHovered ? 16 : 12;
1351
+ const hitboxPad = Math.max(0, (24 - dotSize) / 2);
1268
1352
  const effectiveStatus = event.status ?? "normal";
1353
+ const scatterTeamAccent = getTimelineTeamAccent(event);
1269
1354
  return /* @__PURE__ */ jsx(
1270
1355
  "button",
1271
1356
  {
1272
1357
  type: "button",
1358
+ "aria-label": `${event.title}${event.status ? `, status: ${event.status}` : ""}`,
1273
1359
  onClick: () => onEventClick == null ? void 0 : onEventClick(event),
1274
1360
  onMouseEnter: (e) => {
1275
1361
  setHoveredEvent(event.id);
@@ -1280,23 +1366,33 @@ const ScatterView = memo(function ScatterView2({
1280
1366
  setHoveredEvent(null);
1281
1367
  setTooltipPosition(null);
1282
1368
  },
1369
+ onFocus: (e) => {
1370
+ setHoveredEvent(event.id);
1371
+ const rect = e.currentTarget.getBoundingClientRect();
1372
+ setTooltipPosition({ x: rect.left + rect.width / 2, y: rect.top });
1373
+ },
1374
+ onBlur: () => {
1375
+ setHoveredEvent(null);
1376
+ setTooltipPosition(null);
1377
+ },
1283
1378
  style: {
1284
1379
  position: "absolute",
1285
1380
  left: `${leftPercent}%`,
1286
1381
  transform: "translateX(-50%)",
1382
+ /* visible size + transparent padding = ≥24px touch/click target */
1287
1383
  width: dotSize,
1288
1384
  height: dotSize,
1289
- padding: 0,
1385
+ padding: hitboxPad,
1290
1386
  background: "none",
1291
1387
  border: "none",
1292
1388
  cursor: "pointer",
1293
1389
  transition: "all 200ms cubic-bezier(0.34, 1.56, 0.64, 1)",
1294
- zIndex: isHovered ? 20 : 1,
1390
+ zIndex: isHovered ? Z.trackEventHovered : Z.trackEvent,
1295
1391
  lineHeight: 0,
1296
1392
  borderRadius: "50%",
1297
- boxShadow: event.color ? `0 0 0 2px ${event.color}` : void 0
1393
+ boxSizing: "content-box",
1394
+ boxShadow: scatterTeamAccent ? `0 0 0 2px ${scatterTeamAccent.color}` : void 0
1298
1395
  },
1299
- "aria-label": event.title,
1300
1396
  children: /* @__PURE__ */ jsx(TimelineStatusMarker, { status: effectiveStatus, fillColor: color, size: dotSize })
1301
1397
  },
1302
1398
  event.id
@@ -1313,8 +1409,8 @@ const ScatterView = memo(function ScatterView2({
1313
1409
  }
1314
1410
  ),
1315
1411
  hoveredEventData && tooltipPosition && (() => {
1316
- var _a;
1317
1412
  const scatterTipColor = getStatusColor(hoveredEventData.status);
1413
+ const scatterTeamTip = getTimelineTeamAccent(hoveredEventData);
1318
1414
  return /* @__PURE__ */ jsxs(
1319
1415
  "div",
1320
1416
  {
@@ -1330,7 +1426,7 @@ const ScatterView = memo(function ScatterView2({
1330
1426
  padding: "12px 16px",
1331
1427
  minWidth: 200,
1332
1428
  maxWidth: 320,
1333
- zIndex: 1e3,
1429
+ zIndex: Z.tooltip,
1334
1430
  boxShadow: `0 12px 40px rgba(0, 0, 0, 0.5), 0 0 0 1px ${scatterTipColor}20`,
1335
1431
  backdropFilter: "blur(12px)",
1336
1432
  animation: "zendir-tooltip-in 150ms cubic-bezier(0.34, 1.56, 0.64, 1)"
@@ -1390,9 +1486,9 @@ const ScatterView = memo(function ScatterView2({
1390
1486
  }
1391
1487
  )
1392
1488
  ] }),
1393
- hoveredEventData.color && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6, marginBottom: 6 }, children: [
1394
- /* @__PURE__ */ jsx("div", { style: { width: 8, height: 8, borderRadius: 2, backgroundColor: hoveredEventData.color, flexShrink: 0 } }),
1395
- /* @__PURE__ */ jsx("span", { style: { fontSize: "0.6875rem", color: hoveredEventData.color, fontWeight: 500 }, children: ((_a = hoveredEventData.arguments) == null ? void 0 : _a.Team) || hoveredEventData.badge || "Team" })
1489
+ scatterTeamTip && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6, marginBottom: 6 }, children: [
1490
+ /* @__PURE__ */ jsx("div", { style: { width: 8, height: 8, borderRadius: 2, backgroundColor: scatterTeamTip.color, flexShrink: 0 } }),
1491
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "0.6875rem", color: scatterTeamTip.color, fontWeight: 500 }, children: getTimelineTeamDisplayLabel(hoveredEventData, scatterTeamTip) })
1396
1492
  ] }),
1397
1493
  /* @__PURE__ */ jsx(
1398
1494
  "div",
@@ -1489,16 +1585,39 @@ const ScatterView = memo(function ScatterView2({
1489
1585
  ` })
1490
1586
  ] });
1491
1587
  });
1492
- const TimelineFiltersToolbar = memo(function TimelineFiltersToolbar2({
1588
+ function buildTeamFilterOptions(events, tokens, categoryLabel = "team") {
1589
+ const map = /* @__PURE__ */ new Map();
1590
+ for (const event of events) {
1591
+ const key = getTimelineTeamFilterKey(event);
1592
+ if (map.has(key)) continue;
1593
+ const accent = getTimelineTeamAccent(event);
1594
+ const isNone = key === TIMELINE_FILTER_TEAM_NONE;
1595
+ const isLegacy = key === TIMELINE_FILTER_TEAM_LEGACY;
1596
+ const label = isNone ? `No ${categoryLabel.toLowerCase()}` : isLegacy ? "Accent only" : accent ? getTimelineTeamDisplayLabel(event, accent) : key;
1597
+ const color = isNone ? "" : (accent == null ? void 0 : accent.color) ?? tokens.colors.text.tertiary;
1598
+ map.set(key, { key, label, color, isNoneBucket: isNone });
1599
+ }
1600
+ const list = [...map.values()];
1601
+ list.sort((a, b) => {
1602
+ if (a.isNoneBucket !== b.isNoneBucket) return a.isNoneBucket ? 1 : -1;
1603
+ if (a.key === TIMELINE_FILTER_TEAM_LEGACY) return b.key === TIMELINE_FILTER_TEAM_NONE ? -1 : 1;
1604
+ if (b.key === TIMELINE_FILTER_TEAM_LEGACY) return a.key === TIMELINE_FILTER_TEAM_NONE ? 1 : -1;
1605
+ return a.label.localeCompare(b.label, void 0, { sensitivity: "base" });
1606
+ });
1607
+ return list;
1608
+ }
1609
+ const TimelineFiltersPanel = memo(function TimelineFiltersPanel2({
1493
1610
  filter,
1494
1611
  onFilterChange,
1495
1612
  trackOptions,
1496
- defaultExpanded
1613
+ events,
1614
+ expanded,
1615
+ teamLabel
1497
1616
  }) {
1498
1617
  const { tokens, theme } = useTheme();
1499
1618
  const isTransparentTheme = theme === "transparent" || theme === "transparent-bold" || theme === "transparent-minimal";
1500
- const [expanded, setExpanded] = useState(defaultExpanded);
1501
- const chipCount = countTimelineFilterChips(filter);
1619
+ const accentColor = tokens.colors.accent.primary;
1620
+ const teamOptions = useMemo(() => buildTeamFilterOptions(events, tokens, teamLabel), [events, tokens, teamLabel]);
1502
1621
  const patch = useCallback(
1503
1622
  (partial) => {
1504
1623
  onFilterChange(normalizeTimelineFilter({ ...filter, ...partial }));
@@ -1524,234 +1643,181 @@ const TimelineFiltersToolbar = memo(function TimelineFiltersToolbar2({
1524
1643
  },
1525
1644
  [filter.status, patch]
1526
1645
  );
1527
- const pillBase = (active) => ({
1528
- padding: "4px 10px",
1529
- borderRadius: tokens.borderRadius.full ?? 999,
1530
- fontSize: "0.6875rem",
1531
- fontWeight: 500,
1532
- border: `1px solid ${active ? tokens.colors.accent.primary : tokens.colors.border.muted}`,
1533
- backgroundColor: active ? `${tokens.colors.accent.primary}22` : tokens.colors.background.elevated,
1534
- color: active ? tokens.colors.accent.primary : tokens.colors.text.secondary,
1535
- cursor: "pointer",
1536
- transition: "background-color 150ms ease, border-color 150ms ease, color 150ms ease"
1537
- });
1646
+ const toggleTeam = useCallback(
1647
+ (teamKey) => {
1648
+ const cur = filter.teams ?? [];
1649
+ const next = cur.includes(teamKey) ? cur.filter((k) => k !== teamKey) : [...cur, teamKey];
1650
+ patch({ teams: next.length ? next : void 0 });
1651
+ },
1652
+ [filter.teams, patch]
1653
+ );
1654
+ const pillStyle = useCallback(
1655
+ (active, tintColor) => {
1656
+ const c = tintColor || accentColor;
1657
+ return {
1658
+ padding: "2px 8px",
1659
+ borderRadius: tokens.borderRadius.md,
1660
+ fontSize: tokens.typography.fontSize.xs,
1661
+ fontWeight: 500,
1662
+ textTransform: "uppercase",
1663
+ letterSpacing: "0.06em",
1664
+ lineHeight: 1.4,
1665
+ border: `1px solid ${active ? `${c}66` : tokens.colors.border.muted}`,
1666
+ backgroundColor: active ? `${c}22` : "transparent",
1667
+ color: active ? c : tokens.colors.text.secondary,
1668
+ cursor: "pointer",
1669
+ transition: tokens.animation.fast
1670
+ };
1671
+ },
1672
+ [accentColor, tokens]
1673
+ );
1674
+ const sectionHeader = (text) => /* @__PURE__ */ jsx(
1675
+ "div",
1676
+ {
1677
+ style: {
1678
+ fontSize: tokens.typography.fontSize.xs,
1679
+ fontWeight: 700,
1680
+ letterSpacing: "0.06em",
1681
+ textTransform: "uppercase",
1682
+ color: tokens.colors.text.tertiary,
1683
+ marginBottom: 8
1684
+ },
1685
+ children: text
1686
+ }
1687
+ );
1688
+ if (!expanded) return null;
1538
1689
  return /* @__PURE__ */ jsxs(
1539
1690
  "div",
1540
1691
  {
1692
+ id: "zendir-timeline-filters-panel",
1541
1693
  style: {
1542
- padding: "12px 20px",
1694
+ padding: `${tokens.spacing.smd} ${tokens.spacing.md}`,
1543
1695
  borderBottom: `1px solid ${tokens.colors.border.muted}`,
1544
- backgroundColor: isTransparentTheme ? "transparent" : tokens.colors.background.elevated,
1545
- ...isTransparentTheme && { backdropFilter: "blur(8px)", WebkitBackdropFilter: "blur(8px)" }
1696
+ backgroundColor: isTransparentTheme ? "transparent" : tokens.colors.background.surface,
1697
+ ...isTransparentTheme && { backdropFilter: "blur(8px)", WebkitBackdropFilter: "blur(8px)" },
1698
+ display: "flex",
1699
+ flexDirection: "column",
1700
+ gap: tokens.spacing.smd
1546
1701
  },
1547
1702
  children: [
1703
+ trackOptions.length > 0 ? /* @__PURE__ */ jsxs("div", { role: "group", "aria-label": "Filter by track", children: [
1704
+ sectionHeader("Tracks"),
1705
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: tokens.spacing.xs }, children: trackOptions.map((t) => {
1706
+ var _a;
1707
+ const active = ((_a = filter.tracks) == null ? void 0 : _a.includes(t.id)) ?? false;
1708
+ return /* @__PURE__ */ jsx("button", { type: "button", "aria-pressed": active, onClick: () => toggleTrack(t.id), style: pillStyle(active), children: t.label }, t.id);
1709
+ }) })
1710
+ ] }) : null,
1711
+ /* @__PURE__ */ jsxs("div", { role: "group", "aria-label": "Filter by status", children: [
1712
+ sectionHeader("Status"),
1713
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: tokens.spacing.xs }, children: ALL_TIMELINE_STATUSES.map((st) => {
1714
+ var _a;
1715
+ const active = ((_a = filter.status) == null ? void 0 : _a.includes(st)) ?? false;
1716
+ const statusColor = st ? tokens.colors.status[st] : accentColor;
1717
+ return /* @__PURE__ */ jsxs(
1718
+ "button",
1719
+ {
1720
+ type: "button",
1721
+ "aria-pressed": active,
1722
+ onClick: () => toggleStatus(st),
1723
+ style: {
1724
+ ...pillStyle(active, statusColor),
1725
+ display: "inline-flex",
1726
+ alignItems: "center",
1727
+ gap: 6
1728
+ },
1729
+ children: [
1730
+ /* @__PURE__ */ jsx(
1731
+ "span",
1732
+ {
1733
+ style: {
1734
+ width: 8,
1735
+ height: 8,
1736
+ borderRadius: "50%",
1737
+ backgroundColor: active ? statusColor : `${statusColor}50`,
1738
+ flexShrink: 0,
1739
+ transition: tokens.animation.fast
1740
+ }
1741
+ }
1742
+ ),
1743
+ st
1744
+ ]
1745
+ },
1746
+ st
1747
+ );
1748
+ }) })
1749
+ ] }),
1548
1750
  /* @__PURE__ */ jsxs(
1549
1751
  "div",
1550
1752
  {
1551
1753
  style: {
1552
1754
  display: "flex",
1553
1755
  flexWrap: "wrap",
1554
- alignItems: "center",
1555
- gap: 10
1756
+ gap: `${tokens.spacing.smd} ${tokens.spacing.lg}`,
1757
+ alignItems: "flex-start"
1556
1758
  },
1557
1759
  children: [
1558
- /* @__PURE__ */ jsxs("div", { style: { flex: "1 1 220px", minWidth: 0, position: "relative" }, children: [
1559
- /* @__PURE__ */ jsx(
1560
- Icon,
1561
- {
1562
- name: "search",
1563
- size: 16,
1564
- style: {
1565
- position: "absolute",
1566
- left: 12,
1567
- top: "50%",
1568
- transform: "translateY(-50%)",
1569
- color: tokens.colors.text.tertiary,
1570
- pointerEvents: "none"
1571
- }
1572
- }
1573
- ),
1574
- /* @__PURE__ */ jsx(
1575
- "input",
1576
- {
1577
- type: "search",
1578
- value: filter.search ?? "",
1579
- onChange: (e) => patch({ search: e.target.value || void 0 }),
1580
- placeholder: "Search title, badge, track, fields…",
1581
- "aria-label": "Search timeline events",
1582
- style: {
1583
- width: "100%",
1584
- boxSizing: "border-box",
1585
- height: 36,
1586
- paddingLeft: 40,
1587
- paddingRight: 12,
1588
- borderRadius: tokens.borderRadius.md,
1589
- border: `1px solid ${tokens.colors.border.muted}`,
1590
- backgroundColor: tokens.colors.background.surface,
1591
- color: tokens.colors.text.primary,
1592
- fontSize: "0.8125rem",
1593
- outline: "none"
1594
- }
1595
- }
1596
- )
1760
+ /* @__PURE__ */ jsxs("div", { role: "group", "aria-label": "Filter by duration", style: { minWidth: 0 }, children: [
1761
+ sectionHeader("Duration"),
1762
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: tokens.spacing.xs }, children: ["all", "point", "range"].map((key) => {
1763
+ const active = (filter.eventShape ?? "all") === key;
1764
+ return /* @__PURE__ */ jsx("button", { type: "button", "aria-pressed": active, onClick: () => patch({ eventShape: key }), style: pillStyle(active), children: key === "all" ? "All" : key === "point" ? "Instant" : "Range" }, key);
1765
+ }) })
1597
1766
  ] }),
1598
- /* @__PURE__ */ jsxs(
1599
- "button",
1600
- {
1601
- type: "button",
1602
- onClick: () => setExpanded((v) => !v),
1603
- "aria-expanded": expanded,
1604
- "aria-controls": "zendir-timeline-filters-panel",
1605
- style: {
1606
- display: "inline-flex",
1607
- alignItems: "center",
1608
- gap: 6,
1609
- height: 36,
1610
- padding: "0 14px",
1611
- borderRadius: tokens.borderRadius.md,
1612
- border: `1px solid ${tokens.colors.border.muted}`,
1613
- backgroundColor: tokens.colors.background.surface,
1614
- color: tokens.colors.text.primary,
1615
- cursor: "pointer",
1616
- fontSize: "0.8125rem",
1617
- fontWeight: 500
1618
- },
1619
- children: [
1620
- "Filters",
1621
- chipCount > 0 ? /* @__PURE__ */ jsx(Badge, { variant: "filled", size: "small", status: "normal", children: chipCount }) : null,
1622
- /* @__PURE__ */ jsx(Icon, { name: expanded ? "chevron-up" : "chevron-down", size: 18 })
1623
- ]
1624
- }
1625
- ),
1626
- isTimelineFilterActive(filter) ? /* @__PURE__ */ jsx(
1627
- "button",
1628
- {
1629
- type: "button",
1630
- onClick: clearAll,
1631
- style: {
1632
- height: 36,
1633
- padding: "0 12px",
1634
- borderRadius: tokens.borderRadius.md,
1635
- border: `1px solid ${tokens.colors.border.muted}`,
1636
- backgroundColor: "transparent",
1637
- color: tokens.colors.text.secondary,
1638
- cursor: "pointer",
1639
- fontSize: "0.75rem",
1640
- fontWeight: 500
1641
- },
1642
- children: "Clear all"
1643
- }
1644
- ) : null
1645
- ]
1646
- }
1647
- ),
1648
- expanded ? /* @__PURE__ */ jsxs(
1649
- "div",
1650
- {
1651
- id: "zendir-timeline-filters-panel",
1652
- style: {
1653
- marginTop: 14,
1654
- display: "flex",
1655
- flexDirection: "column",
1656
- gap: 14
1657
- },
1658
- children: [
1659
- trackOptions.length > 0 ? /* @__PURE__ */ jsxs("div", { children: [
1660
- /* @__PURE__ */ jsx(
1661
- "div",
1662
- {
1663
- style: {
1664
- fontSize: "0.625rem",
1665
- fontWeight: 600,
1666
- letterSpacing: "0.06em",
1667
- textTransform: "uppercase",
1668
- color: tokens.colors.text.tertiary,
1669
- marginBottom: 8
1670
- },
1671
- children: "Tracks"
1672
- }
1673
- ),
1674
- /* @__PURE__ */ jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: 6 }, children: trackOptions.map((t) => {
1675
- var _a;
1676
- const active = ((_a = filter.tracks) == null ? void 0 : _a.includes(t.id)) ?? false;
1677
- return /* @__PURE__ */ jsx(
1767
+ teamOptions.length > 0 ? /* @__PURE__ */ jsxs("div", { role: "group", "aria-label": `Filter by ${teamLabel.toLowerCase()}`, style: { flex: 1, minWidth: 180 }, children: [
1768
+ sectionHeader(teamLabel),
1769
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: tokens.spacing.xs }, children: teamOptions.map((opt) => {
1770
+ var _a, _b, _c;
1771
+ const active = ((_a = filter.teams) == null ? void 0 : _a.includes(opt.key)) ?? false;
1772
+ return /* @__PURE__ */ jsxs(
1678
1773
  "button",
1679
1774
  {
1680
1775
  type: "button",
1681
- onClick: () => toggleTrack(t.id),
1682
- style: pillBase(active),
1683
- children: t.label
1776
+ "aria-pressed": active,
1777
+ onClick: () => toggleTeam(opt.key),
1778
+ style: {
1779
+ ...pillStyle(active, opt.color || void 0),
1780
+ display: "inline-flex",
1781
+ alignItems: "center",
1782
+ gap: 6,
1783
+ maxWidth: "100%"
1784
+ },
1785
+ children: [
1786
+ opt.isNoneBucket ? /* @__PURE__ */ jsx(
1787
+ "span",
1788
+ {
1789
+ style: {
1790
+ width: 8,
1791
+ height: 8,
1792
+ borderRadius: "50%",
1793
+ border: `${((_c = (_b = tokens.borders) == null ? void 0 : _b.width) == null ? void 0 : _c.thick) ?? "2px"} solid ${tokens.colors.border.muted}`,
1794
+ flexShrink: 0,
1795
+ boxSizing: "border-box"
1796
+ }
1797
+ }
1798
+ ) : /* @__PURE__ */ jsx(
1799
+ "span",
1800
+ {
1801
+ style: {
1802
+ width: 8,
1803
+ height: 8,
1804
+ borderRadius: "50%",
1805
+ backgroundColor: opt.color,
1806
+ flexShrink: 0
1807
+ }
1808
+ }
1809
+ ),
1810
+ /* @__PURE__ */ jsx("span", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: opt.label })
1811
+ ]
1684
1812
  },
1685
- t.id
1813
+ opt.key
1686
1814
  );
1687
1815
  }) })
1688
- ] }) : null,
1689
- /* @__PURE__ */ jsxs("div", { children: [
1690
- /* @__PURE__ */ jsx(
1691
- "div",
1692
- {
1693
- style: {
1694
- fontSize: "0.625rem",
1695
- fontWeight: 600,
1696
- letterSpacing: "0.06em",
1697
- textTransform: "uppercase",
1698
- color: tokens.colors.text.tertiary,
1699
- marginBottom: 8
1700
- },
1701
- children: "Status"
1702
- }
1703
- ),
1704
- /* @__PURE__ */ jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: 6 }, children: ALL_TIMELINE_STATUSES.map((st) => {
1705
- var _a;
1706
- const active = ((_a = filter.status) == null ? void 0 : _a.includes(st)) ?? false;
1707
- return /* @__PURE__ */ jsx("button", { type: "button", onClick: () => toggleStatus(st), style: pillBase(active), children: st }, st);
1708
- }) })
1709
- ] }),
1710
- /* @__PURE__ */ jsxs(
1711
- "div",
1712
- {
1713
- style: {
1714
- display: "flex",
1715
- flexWrap: "wrap",
1716
- alignItems: "center",
1717
- gap: 16
1718
- },
1719
- children: [
1720
- /* @__PURE__ */ jsxs("div", { children: [
1721
- /* @__PURE__ */ jsx(
1722
- "div",
1723
- {
1724
- style: {
1725
- fontSize: "0.625rem",
1726
- fontWeight: 600,
1727
- letterSpacing: "0.06em",
1728
- textTransform: "uppercase",
1729
- color: tokens.colors.text.tertiary,
1730
- marginBottom: 8
1731
- },
1732
- children: "Duration"
1733
- }
1734
- ),
1735
- /* @__PURE__ */ jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: 8 }, children: ["all", "point", "range"].map((key) => {
1736
- const active = (filter.eventShape ?? "all") === key;
1737
- return /* @__PURE__ */ jsx("button", { type: "button", onClick: () => patch({ eventShape: key }), style: pillBase(active), children: key === "all" ? "All" : key === "point" ? "Instant" : "Range" }, key);
1738
- }) })
1739
- ] }),
1740
- /* @__PURE__ */ jsx(
1741
- Checkbox,
1742
- {
1743
- label: "Team color only",
1744
- checked: Boolean(filter.teamColoredOnly),
1745
- onChange: (checked) => patch({ teamColoredOnly: checked || void 0 }),
1746
- size: "small"
1747
- }
1748
- )
1749
- ]
1750
- }
1751
- )
1816
+ ] }) : null
1752
1817
  ]
1753
1818
  }
1754
- ) : null
1819
+ ),
1820
+ isTimelineFilterActive(filter) ? /* @__PURE__ */ jsx("div", { style: { display: "flex", justifyContent: "flex-end" }, children: /* @__PURE__ */ jsx(Button, { variant: "borderless", size: "small", onClick: clearAll, children: "Clear all" }) }) : null
1755
1821
  ]
1756
1822
  }
1757
1823
  );
@@ -1773,7 +1839,7 @@ const UnifiedTimeline = memo(function UnifiedTimeline2({
1773
1839
  timeFormat = "absolute",
1774
1840
  zoomable = false,
1775
1841
  initialZoom = 1,
1776
- showFilters: _showFilters = false,
1842
+ showFilters: _showFilters = true,
1777
1843
  filtersDefaultExpanded = false,
1778
1844
  hideEmptyTracksWhenFiltered = true,
1779
1845
  filter: _filter,
@@ -1784,14 +1850,20 @@ const UnifiedTimeline = memo(function UnifiedTimeline2({
1784
1850
  showPlayhead = true,
1785
1851
  playheadTime,
1786
1852
  onPlayheadChange: _onPlayheadChangeTimeline,
1787
- showDayMarkers = true
1853
+ showDayMarkers = true,
1854
+ teamLabel: teamLabelProp
1788
1855
  }) {
1789
1856
  const { tokens, theme } = useTheme();
1790
1857
  const isTransparentTheme = theme === "transparent" || theme === "transparent-bold" || theme === "transparent-minimal";
1858
+ const { isMobile } = useBreakpoint();
1859
+ const categoryCtx = useCategoryPalette();
1860
+ const teamLabel = teamLabelProp ?? (categoryCtx == null ? void 0 : categoryCtx.categoryLabel) ?? "Teams";
1791
1861
  const [internalViewMode, setInternalViewMode] = useState(defaultView);
1792
1862
  const [zoom, setZoom] = useState(initialZoom);
1793
1863
  const [_internalFilter, _setInternalFilter] = useState({});
1864
+ const [filtersExpanded, setFiltersExpanded] = useState(filtersDefaultExpanded);
1794
1865
  const filter = _filter !== void 0 ? _filter : _internalFilter;
1866
+ const filterChipCount = _showFilters ? countTimelineFilterChips(filter) : 0;
1795
1867
  const setFilter = useCallback(
1796
1868
  (next) => {
1797
1869
  const n = normalizeTimelineFilter(next);
@@ -1800,6 +1872,12 @@ const UnifiedTimeline = memo(function UnifiedTimeline2({
1800
1872
  },
1801
1873
  [_filter, _onFilterChange]
1802
1874
  );
1875
+ const patchFilter = useCallback(
1876
+ (partial) => {
1877
+ setFilter(normalizeTimelineFilter({ ...filter, ...partial }));
1878
+ },
1879
+ [filter, setFilter]
1880
+ );
1803
1881
  const viewMode = controlledViewMode ?? internalViewMode;
1804
1882
  const handleViewModeChange = useCallback((mode) => {
1805
1883
  setInternalViewMode(mode);
@@ -1883,204 +1961,321 @@ const UnifiedTimeline = memo(function UnifiedTimeline2({
1883
1961
  {
1884
1962
  style: {
1885
1963
  display: "flex",
1886
- alignItems: "center",
1887
- justifyContent: "space-between",
1888
- padding: "16px 20px",
1964
+ flexDirection: isMobile ? "column" : "row",
1965
+ alignItems: isMobile ? "stretch" : "center",
1966
+ padding: `${tokens.spacing.smd} ${tokens.spacing.md}`,
1967
+ gap: isMobile ? tokens.spacing.xs : tokens.spacing.sm,
1889
1968
  borderBottom: `1px solid ${tokens.colors.border.muted}`
1890
1969
  },
1891
1970
  children: [
1892
- /* @__PURE__ */ jsx(
1893
- "h3",
1894
- {
1895
- style: {
1896
- margin: 0,
1897
- fontSize: "1rem",
1898
- fontWeight: 500,
1899
- // AstroUXDS medium (was 600)
1900
- color: tokens.colors.text.primary
1901
- },
1902
- children: countLabel
1903
- }
1904
- ),
1905
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 12 }, children: [
1906
- zoomable && (viewMode === "chart" || viewMode === "scatter") && /* @__PURE__ */ jsxs(
1907
- "div",
1971
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: tokens.spacing.sm }, children: [
1972
+ /* @__PURE__ */ jsx(
1973
+ "h3",
1908
1974
  {
1909
1975
  style: {
1910
- display: "flex",
1911
- alignItems: "center",
1912
- gap: 4,
1913
- paddingRight: 12,
1914
- borderRight: `1px solid ${tokens.colors.border.muted}`
1976
+ margin: 0,
1977
+ fontSize: "1rem",
1978
+ fontWeight: 500,
1979
+ // AstroUXDS medium
1980
+ color: tokens.colors.text.primary,
1981
+ whiteSpace: "nowrap",
1982
+ flexShrink: 0
1915
1983
  },
1916
- children: [
1917
- /* @__PURE__ */ jsx(Tooltip, { content: "Zoom out", children: /* @__PURE__ */ jsx(
1918
- "button",
1919
- {
1920
- type: "button",
1921
- "aria-label": "Zoom out",
1922
- onClick: () => setZoom(Math.max(zoom - 0.5, 1)),
1923
- disabled: zoom <= 1,
1924
- style: {
1925
- width: 28,
1926
- height: 28,
1927
- display: "flex",
1928
- alignItems: "center",
1929
- justifyContent: "center",
1930
- backgroundColor: zoom <= 1 ? "transparent" : tokens.colors.background.elevated,
1931
- border: `1px solid ${tokens.colors.border.muted}`,
1932
- borderRadius: tokens.borderRadius.sm,
1933
- color: zoom <= 1 ? tokens.colors.text.tertiary : tokens.colors.text.primary,
1934
- cursor: zoom <= 1 ? "not-allowed" : "pointer",
1935
- fontSize: "1rem",
1936
- fontWeight: 500,
1937
- // AstroUXDS medium (was 600)
1938
- opacity: zoom <= 1 ? 0.5 : 1
1939
- },
1940
- children: "−"
1941
- }
1942
- ) }),
1943
- /* @__PURE__ */ jsxs(
1944
- "span",
1945
- {
1946
- style: {
1947
- minWidth: 40,
1948
- textAlign: "center",
1949
- fontSize: "0.75rem",
1950
- fontFamily: tokens.typography.fontFamily.mono,
1951
- color: tokens.colors.text.secondary
1952
- },
1953
- children: [
1954
- Math.round(zoom * 100),
1955
- "%"
1956
- ]
1957
- }
1958
- ),
1959
- /* @__PURE__ */ jsx(Tooltip, { content: "Zoom in", children: /* @__PURE__ */ jsx(
1960
- "button",
1961
- {
1962
- type: "button",
1963
- "aria-label": "Zoom in",
1964
- onClick: () => setZoom(Math.min(zoom + 0.5, 4)),
1965
- disabled: zoom >= 4,
1966
- style: {
1967
- width: 28,
1968
- height: 28,
1969
- display: "flex",
1970
- alignItems: "center",
1971
- justifyContent: "center",
1972
- backgroundColor: zoom >= 4 ? "transparent" : tokens.colors.background.elevated,
1973
- border: `1px solid ${tokens.colors.border.muted}`,
1974
- borderRadius: tokens.borderRadius.sm,
1975
- color: zoom >= 4 ? tokens.colors.text.tertiary : tokens.colors.text.primary,
1976
- cursor: zoom >= 4 ? "not-allowed" : "pointer",
1977
- fontSize: "1rem",
1978
- fontWeight: 500,
1979
- // AstroUXDS medium (was 600)
1980
- opacity: zoom >= 4 ? 0.5 : 1
1981
- },
1982
- children: "+"
1983
- }
1984
- ) }),
1985
- /* @__PURE__ */ jsx(Tooltip, { content: "Reset zoom", children: /* @__PURE__ */ jsx(
1986
- "button",
1987
- {
1988
- type: "button",
1989
- "aria-label": "Reset zoom",
1990
- onClick: () => setZoom(1),
1991
- style: {
1992
- height: 28,
1993
- padding: "0 10px",
1994
- display: "flex",
1995
- alignItems: "center",
1996
- justifyContent: "center",
1997
- backgroundColor: tokens.colors.background.elevated,
1998
- border: `1px solid ${tokens.colors.border.muted}`,
1999
- borderRadius: tokens.borderRadius.sm,
2000
- color: tokens.colors.text.secondary,
2001
- cursor: "pointer",
2002
- fontSize: "0.6875rem",
2003
- fontWeight: 500
2004
- },
2005
- children: "FIT"
2006
- }
2007
- ) })
2008
- ]
1984
+ children: countLabel
2009
1985
  }
2010
1986
  ),
2011
- showViewToggle && /* @__PURE__ */ jsx(
2012
- "div",
2013
- {
2014
- style: {
2015
- display: "flex",
2016
- gap: "2px",
2017
- backgroundColor: isTransparentTheme ? "transparent" : tokens.colors.background.elevated,
2018
- ...isTransparentTheme && { backdropFilter: "blur(12px)", WebkitBackdropFilter: "blur(12px)" },
2019
- padding: "4px",
2020
- borderRadius: tokens.borderRadius.lg
2021
- },
2022
- children: ["chart", "list", "scatter"].map((mode) => {
2023
- const isActive = viewMode === mode;
2024
- const icons = {
2025
- chart: "chart",
2026
- list: "list",
2027
- scatter: "timeline"
2028
- };
2029
- const labels = {
2030
- chart: "Gantt Chart",
2031
- list: "List View",
2032
- scatter: "Scatter View"
2033
- };
2034
- return /* @__PURE__ */ jsx(Tooltip, { content: labels[mode], children: /* @__PURE__ */ jsx(
2035
- "button",
2036
- {
2037
- type: "button",
2038
- "aria-label": labels[mode],
2039
- onClick: () => handleViewModeChange(mode),
2040
- style: {
2041
- display: "flex",
2042
- alignItems: "center",
2043
- justifyContent: "center",
2044
- width: "32px",
2045
- height: "32px",
2046
- backgroundColor: isActive ? tokens.colors.accent.primary : "transparent",
2047
- border: "none",
2048
- borderRadius: tokens.borderRadius.md,
2049
- color: isActive ? "#ffffff" : tokens.colors.text.secondary,
2050
- cursor: "pointer",
2051
- transition: "all 200ms cubic-bezier(0.4, 0, 0.2, 1)",
2052
- transform: isActive ? "scale(1)" : "scale(0.95)",
2053
- boxShadow: isActive ? `0 2px 8px ${tokens.colors.accent.primary}40` : "none"
2054
- },
2055
- onMouseEnter: (e) => {
2056
- if (!isActive) {
2057
- e.currentTarget.style.backgroundColor = `${tokens.colors.accent.primary}20`;
2058
- e.currentTarget.style.transform = "scale(1)";
2059
- }
2060
- },
2061
- onMouseLeave: (e) => {
2062
- if (!isActive) {
2063
- e.currentTarget.style.backgroundColor = "transparent";
2064
- e.currentTarget.style.transform = "scale(0.95)";
2065
- }
2066
- },
2067
- children: /* @__PURE__ */ jsx(Icon, { name: icons[mode], size: 16 })
1987
+ !isMobile && /* @__PURE__ */ jsx("div", { style: { flex: 1 } })
1988
+ ] }),
1989
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: tokens.spacing.sm, flex: isMobile ? void 0 : 1, justifyContent: isMobile ? "space-between" : "flex-end" }, children: [
1990
+ _showFilters && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: tokens.spacing.xs, flex: isMobile ? 1 : void 0, flexShrink: 0 }, children: [
1991
+ /* @__PURE__ */ jsxs("div", { style: { position: "relative", width: isMobile ? "100%" : 180 }, children: [
1992
+ /* @__PURE__ */ jsx(
1993
+ Icon,
1994
+ {
1995
+ name: "search",
1996
+ size: 14,
1997
+ style: {
1998
+ position: "absolute",
1999
+ left: 8,
2000
+ top: "50%",
2001
+ transform: "translateY(-50%)",
2002
+ color: tokens.colors.text.tertiary,
2003
+ pointerEvents: "none"
2068
2004
  }
2069
- ) }, mode);
2070
- })
2071
- }
2072
- )
2073
- ] })
2005
+ }
2006
+ ),
2007
+ /* @__PURE__ */ jsx(
2008
+ "input",
2009
+ {
2010
+ type: "search",
2011
+ value: filter.search ?? "",
2012
+ onChange: (e) => patchFilter({ search: e.target.value || void 0 }),
2013
+ placeholder: "Search…",
2014
+ "aria-label": "Search timeline events",
2015
+ style: {
2016
+ width: "100%",
2017
+ boxSizing: "border-box",
2018
+ height: tokens.elementSize.sm,
2019
+ paddingLeft: 28,
2020
+ paddingRight: 8,
2021
+ borderRadius: tokens.borderRadius.md,
2022
+ border: `1px solid ${tokens.colors.border.muted}`,
2023
+ backgroundColor: tokens.colors.background.base,
2024
+ color: tokens.colors.text.primary,
2025
+ fontSize: tokens.typography.fontSize.sm,
2026
+ fontFamily: "inherit",
2027
+ outline: "none",
2028
+ transition: "all 200ms cubic-bezier(0.4, 0, 0.2, 1)"
2029
+ },
2030
+ onFocus: (e) => {
2031
+ e.target.style.borderColor = tokens.colors.accent.primary;
2032
+ e.target.style.boxShadow = `0 0 0 3px ${tokens.colors.accent.primary}50, 0 0 20px ${tokens.colors.accent.primary}15`;
2033
+ },
2034
+ onBlur: (e) => {
2035
+ e.target.style.borderColor = tokens.colors.border.muted;
2036
+ e.target.style.boxShadow = "none";
2037
+ }
2038
+ }
2039
+ )
2040
+ ] }),
2041
+ /* @__PURE__ */ jsx(Tooltip, { content: filtersExpanded ? "Hide filters" : "Show filters", children: /* @__PURE__ */ jsxs(
2042
+ "button",
2043
+ {
2044
+ type: "button",
2045
+ "aria-label": filtersExpanded ? "Hide filters" : "Show filters",
2046
+ "aria-expanded": filtersExpanded,
2047
+ "aria-controls": "zendir-timeline-filters-panel",
2048
+ onClick: () => setFiltersExpanded((v) => !v),
2049
+ style: {
2050
+ position: "relative",
2051
+ display: "flex",
2052
+ alignItems: "center",
2053
+ justifyContent: "center",
2054
+ width: 28,
2055
+ height: 28,
2056
+ border: `1px solid ${filterChipCount > 0 ? `${tokens.colors.accent.primary}80` : tokens.colors.border.muted}`,
2057
+ borderRadius: tokens.borderRadius.sm,
2058
+ backgroundColor: filterChipCount > 0 ? `${tokens.colors.accent.primary}12` : "transparent",
2059
+ color: filterChipCount > 0 ? tokens.colors.accent.primary : tokens.colors.text.secondary,
2060
+ cursor: "pointer",
2061
+ transition: tokens.animation.fast
2062
+ },
2063
+ children: [
2064
+ /* @__PURE__ */ jsx(Icon, { name: "filter", size: 16 }),
2065
+ filterChipCount > 0 ? /* @__PURE__ */ jsx(
2066
+ "span",
2067
+ {
2068
+ style: {
2069
+ position: "absolute",
2070
+ top: -4,
2071
+ right: -4,
2072
+ minWidth: 14,
2073
+ height: 14,
2074
+ borderRadius: "50%",
2075
+ backgroundColor: tokens.colors.accent.primary,
2076
+ color: "#fff",
2077
+ fontSize: "0.5625rem",
2078
+ fontWeight: 700,
2079
+ display: "flex",
2080
+ alignItems: "center",
2081
+ justifyContent: "center",
2082
+ lineHeight: 1
2083
+ },
2084
+ children: filterChipCount
2085
+ }
2086
+ ) : null
2087
+ ]
2088
+ }
2089
+ ) })
2090
+ ] }),
2091
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 12, flexShrink: 0 }, children: [
2092
+ zoomable && (viewMode === "chart" || viewMode === "scatter") && /* @__PURE__ */ jsxs(
2093
+ "div",
2094
+ {
2095
+ style: {
2096
+ display: "flex",
2097
+ alignItems: "center",
2098
+ gap: 4,
2099
+ paddingRight: 12,
2100
+ borderRight: `1px solid ${tokens.colors.border.muted}`
2101
+ },
2102
+ children: [
2103
+ /* @__PURE__ */ jsx(Tooltip, { content: "Zoom out", children: /* @__PURE__ */ jsx(
2104
+ "button",
2105
+ {
2106
+ type: "button",
2107
+ "aria-label": "Zoom out",
2108
+ onClick: () => setZoom(Math.max(zoom - 0.5, 1)),
2109
+ disabled: zoom <= 1,
2110
+ style: {
2111
+ width: 28,
2112
+ height: 28,
2113
+ display: "flex",
2114
+ alignItems: "center",
2115
+ justifyContent: "center",
2116
+ backgroundColor: zoom <= 1 ? "transparent" : tokens.colors.background.elevated,
2117
+ border: `1px solid ${tokens.colors.border.muted}`,
2118
+ borderRadius: tokens.borderRadius.sm,
2119
+ color: zoom <= 1 ? tokens.colors.text.tertiary : tokens.colors.text.primary,
2120
+ cursor: zoom <= 1 ? "not-allowed" : "pointer",
2121
+ fontSize: "1rem",
2122
+ fontWeight: 500,
2123
+ // AstroUXDS medium (was 600)
2124
+ opacity: zoom <= 1 ? 0.5 : 1
2125
+ },
2126
+ children: "−"
2127
+ }
2128
+ ) }),
2129
+ /* @__PURE__ */ jsxs(
2130
+ "span",
2131
+ {
2132
+ style: {
2133
+ minWidth: 40,
2134
+ textAlign: "center",
2135
+ fontSize: "0.75rem",
2136
+ fontFamily: tokens.typography.fontFamily.mono,
2137
+ color: tokens.colors.text.secondary
2138
+ },
2139
+ children: [
2140
+ Math.round(zoom * 100),
2141
+ "%"
2142
+ ]
2143
+ }
2144
+ ),
2145
+ /* @__PURE__ */ jsx(Tooltip, { content: "Zoom in", children: /* @__PURE__ */ jsx(
2146
+ "button",
2147
+ {
2148
+ type: "button",
2149
+ "aria-label": "Zoom in",
2150
+ onClick: () => setZoom(Math.min(zoom + 0.5, 4)),
2151
+ disabled: zoom >= 4,
2152
+ style: {
2153
+ width: 28,
2154
+ height: 28,
2155
+ display: "flex",
2156
+ alignItems: "center",
2157
+ justifyContent: "center",
2158
+ backgroundColor: zoom >= 4 ? "transparent" : tokens.colors.background.elevated,
2159
+ border: `1px solid ${tokens.colors.border.muted}`,
2160
+ borderRadius: tokens.borderRadius.sm,
2161
+ color: zoom >= 4 ? tokens.colors.text.tertiary : tokens.colors.text.primary,
2162
+ cursor: zoom >= 4 ? "not-allowed" : "pointer",
2163
+ fontSize: "1rem",
2164
+ fontWeight: 500,
2165
+ // AstroUXDS medium (was 600)
2166
+ opacity: zoom >= 4 ? 0.5 : 1
2167
+ },
2168
+ children: "+"
2169
+ }
2170
+ ) }),
2171
+ /* @__PURE__ */ jsx(Tooltip, { content: "Reset zoom", children: /* @__PURE__ */ jsx(
2172
+ "button",
2173
+ {
2174
+ type: "button",
2175
+ "aria-label": "Reset zoom",
2176
+ onClick: () => setZoom(1),
2177
+ style: {
2178
+ height: 28,
2179
+ padding: "0 10px",
2180
+ display: "flex",
2181
+ alignItems: "center",
2182
+ justifyContent: "center",
2183
+ backgroundColor: tokens.colors.background.elevated,
2184
+ border: `1px solid ${tokens.colors.border.muted}`,
2185
+ borderRadius: tokens.borderRadius.sm,
2186
+ color: tokens.colors.text.secondary,
2187
+ cursor: "pointer",
2188
+ fontSize: "0.6875rem",
2189
+ fontWeight: 500
2190
+ },
2191
+ children: "FIT"
2192
+ }
2193
+ ) })
2194
+ ]
2195
+ }
2196
+ ),
2197
+ showViewToggle && /* @__PURE__ */ jsx(
2198
+ "div",
2199
+ {
2200
+ role: "group",
2201
+ "aria-label": "View mode",
2202
+ style: {
2203
+ display: "flex",
2204
+ gap: "2px",
2205
+ backgroundColor: isTransparentTheme ? "transparent" : tokens.colors.background.elevated,
2206
+ ...isTransparentTheme && { backdropFilter: "blur(12px)", WebkitBackdropFilter: "blur(12px)" },
2207
+ padding: "4px",
2208
+ borderRadius: tokens.borderRadius.lg
2209
+ },
2210
+ children: ["chart", "list", "scatter"].map((mode) => {
2211
+ const isActive = viewMode === mode;
2212
+ const icons = {
2213
+ chart: "chart",
2214
+ list: "list",
2215
+ scatter: "timeline"
2216
+ };
2217
+ const labels = {
2218
+ chart: "Gantt Chart",
2219
+ list: "List View",
2220
+ scatter: "Scatter View"
2221
+ };
2222
+ return /* @__PURE__ */ jsx(Tooltip, { content: labels[mode], children: /* @__PURE__ */ jsx(
2223
+ "button",
2224
+ {
2225
+ type: "button",
2226
+ "aria-label": labels[mode],
2227
+ "aria-pressed": isActive,
2228
+ onClick: () => handleViewModeChange(mode),
2229
+ style: {
2230
+ display: "flex",
2231
+ alignItems: "center",
2232
+ justifyContent: "center",
2233
+ width: "32px",
2234
+ height: "32px",
2235
+ backgroundColor: isActive ? tokens.colors.accent.primary : "transparent",
2236
+ border: "none",
2237
+ borderRadius: tokens.borderRadius.md,
2238
+ color: isActive ? "#ffffff" : tokens.colors.text.secondary,
2239
+ cursor: "pointer",
2240
+ transition: "all 200ms cubic-bezier(0.4, 0, 0.2, 1)",
2241
+ transform: isActive ? "scale(1)" : "scale(0.95)",
2242
+ boxShadow: isActive ? `0 2px 8px ${tokens.colors.accent.primary}40` : "none"
2243
+ },
2244
+ onMouseEnter: (e) => {
2245
+ if (!isActive) {
2246
+ e.currentTarget.style.backgroundColor = `${tokens.colors.accent.primary}20`;
2247
+ e.currentTarget.style.transform = "scale(1)";
2248
+ }
2249
+ },
2250
+ onMouseLeave: (e) => {
2251
+ if (!isActive) {
2252
+ e.currentTarget.style.backgroundColor = "transparent";
2253
+ e.currentTarget.style.transform = "scale(0.95)";
2254
+ }
2255
+ },
2256
+ children: /* @__PURE__ */ jsx(Icon, { name: icons[mode], size: 16 })
2257
+ }
2258
+ ) }, mode);
2259
+ })
2260
+ }
2261
+ )
2262
+ ] }),
2263
+ " "
2264
+ ] }),
2265
+ " "
2074
2266
  ]
2075
2267
  }
2076
2268
  ),
2269
+ " ",
2077
2270
  _showFilters ? /* @__PURE__ */ jsx(
2078
- TimelineFiltersToolbar,
2271
+ TimelineFiltersPanel,
2079
2272
  {
2080
2273
  filter,
2081
2274
  onFilterChange: setFilter,
2082
2275
  trackOptions: trackOptionsForToolbar,
2083
- defaultExpanded: filtersDefaultExpanded
2276
+ events,
2277
+ expanded: filtersExpanded,
2278
+ teamLabel
2084
2279
  }
2085
2280
  ) : null,
2086
2281
  loading && /* @__PURE__ */ jsx(
@@ -2094,7 +2289,7 @@ const UnifiedTimeline = memo(function UnifiedTimeline2({
2094
2289
  justifyContent: "center",
2095
2290
  backgroundColor: `${tokens.colors.background.overlay}80`,
2096
2291
  backdropFilter: "blur(4px)",
2097
- zIndex: 100
2292
+ zIndex: Z.loadingOverlay
2098
2293
  },
2099
2294
  children: /* @__PURE__ */ jsx(
2100
2295
  "div",
@@ -2233,8 +2428,13 @@ const UnifiedTimeline = memo(function UnifiedTimeline2({
2233
2428
  );
2234
2429
  });
2235
2430
  export {
2431
+ TIMELINE_FILTER_TEAM_LEGACY,
2432
+ TIMELINE_FILTER_TEAM_NONE,
2236
2433
  UnifiedTimeline,
2237
2434
  getTimelineEventDisplayStatus,
2435
+ getTimelineTeamAccent,
2436
+ getTimelineTeamDisplayLabel,
2437
+ getTimelineTeamFilterKey,
2238
2438
  isTimelineFilterActive,
2239
2439
  matchesTimelineFilter
2240
2440
  };