@arbidocs/blocks 0.3.142 → 0.3.143

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.
@@ -587,6 +587,506 @@ function tid(...parts) {
587
587
  (p) => String(p).trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "")
588
588
  ).join("-");
589
589
  }
590
+
591
+ // src/timeline/span.ts
592
+ var MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
593
+ var MONTH_NAMES = [
594
+ "January",
595
+ "February",
596
+ "March",
597
+ "April",
598
+ "May",
599
+ "June",
600
+ "July",
601
+ "August",
602
+ "September",
603
+ "October",
604
+ "November",
605
+ "December"
606
+ ];
607
+ var WEEKDAY_NAMES = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
608
+ var DAY_MS = 864e5;
609
+ function monthLabel(d) {
610
+ return `${MONTH_NAMES[d.getUTCMonth()]} ${d.getUTCFullYear()}`;
611
+ }
612
+ function utcDay(d) {
613
+ return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
614
+ }
615
+ function dayKey(d) {
616
+ return d.toISOString().slice(0, 10);
617
+ }
618
+ function parseIso2(iso) {
619
+ if (!iso) return null;
620
+ const d = new Date(iso);
621
+ return isNaN(d.getTime()) ? null : d;
622
+ }
623
+ function isMidnight(d) {
624
+ return d.getUTCHours() === 0 && d.getUTCMinutes() === 0 && d.getUTCSeconds() === 0;
625
+ }
626
+ function isEndOfDay(d) {
627
+ return d.getUTCHours() === 23 && d.getUTCMinutes() === 59;
628
+ }
629
+ function lastDayOfMonth(d) {
630
+ return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth() + 1, 0)).getUTCDate();
631
+ }
632
+ function day(d) {
633
+ return `${d.getUTCDate()} ${MONTHS[d.getUTCMonth()]} ${d.getUTCFullYear()}`;
634
+ }
635
+ function formatSpan(startIso, endIso) {
636
+ if (!startIso) return "";
637
+ const start = new Date(startIso);
638
+ const end = endIso ? new Date(endIso) : start;
639
+ if (startIso === endIso) {
640
+ const hh = String(start.getUTCHours()).padStart(2, "0");
641
+ const mm = String(start.getUTCMinutes()).padStart(2, "0");
642
+ return `${day(start)}, ${hh}:${mm} UTC`;
643
+ }
644
+ if (isMidnight(start) && isEndOfDay(end)) {
645
+ const sameDay = start.getUTCFullYear() === end.getUTCFullYear() && start.getUTCMonth() === end.getUTCMonth() && start.getUTCDate() === end.getUTCDate();
646
+ if (sameDay) return day(start);
647
+ const firstOfMonth = start.getUTCDate() === 1;
648
+ const endsMonth = end.getUTCDate() === lastDayOfMonth(end);
649
+ const sameMonth = start.getUTCFullYear() === end.getUTCFullYear() && start.getUTCMonth() === end.getUTCMonth();
650
+ if (firstOfMonth && endsMonth && sameMonth) {
651
+ return `${MONTHS[start.getUTCMonth()]} ${start.getUTCFullYear()}`;
652
+ }
653
+ const wholeYear = firstOfMonth && endsMonth && start.getUTCMonth() === 0 && end.getUTCMonth() === 11 && start.getUTCFullYear() === end.getUTCFullYear();
654
+ if (wholeYear) return String(start.getUTCFullYear());
655
+ return `${day(start)} \u2013 ${day(end)}`;
656
+ }
657
+ return `${day(start)} \u2013 ${day(end)}`;
658
+ }
659
+ var DEFAULT_MAX_LANES = 3;
660
+ var MAX_WEEKS = 520;
661
+ function isPrecise(item) {
662
+ if (!item.start || !item.end) return false;
663
+ return item.start.slice(0, 10) === item.end.slice(0, 10);
664
+ }
665
+ function TimelineCalendar({
666
+ items,
667
+ onSelect,
668
+ maxLanes = DEFAULT_MAX_LANES,
669
+ emptyState,
670
+ className,
671
+ testId = "timeline-calendar"
672
+ }) {
673
+ const dated = react.useMemo(() => items.filter((i) => i.start), [items]);
674
+ const weeks = react.useMemo(() => {
675
+ if (dated.length === 0) return [];
676
+ const times = dated.flatMap((i) => [
677
+ new Date(i.start).getTime(),
678
+ new Date(i.end ?? i.start).getTime()
679
+ ]);
680
+ const first = utcDay(new Date(Math.min(...times)));
681
+ const last = utcDay(new Date(Math.max(...times)));
682
+ const gridStart = new Date(first);
683
+ gridStart.setUTCDate(first.getUTCDate() - (first.getUTCDay() + 6) % 7);
684
+ const out = [];
685
+ const cursor = new Date(gridStart);
686
+ while (cursor <= last && out.length < MAX_WEEKS) {
687
+ out.push(
688
+ Array.from({ length: 7 }, (_, d) => {
689
+ const day2 = new Date(cursor);
690
+ day2.setUTCDate(cursor.getUTCDate() + d);
691
+ return day2;
692
+ })
693
+ );
694
+ cursor.setUTCDate(cursor.getUTCDate() + 7);
695
+ }
696
+ return out;
697
+ }, [dated]);
698
+ const segmentsByWeek = react.useMemo(() => {
699
+ return weeks.map((week) => {
700
+ const weekStart = week[0];
701
+ const weekEnd = week[6];
702
+ const overlapping = dated.map((item) => ({
703
+ item,
704
+ start: utcDay(new Date(item.start)),
705
+ end: utcDay(new Date(item.end ?? item.start))
706
+ })).filter(({ start, end }) => start <= weekEnd && end >= weekStart).sort((a, b) => {
707
+ const lenA = b.end.getTime() - b.start.getTime();
708
+ const lenB = a.end.getTime() - a.start.getTime();
709
+ return lenA - lenB || a.start.getTime() - b.start.getTime();
710
+ });
711
+ const laneEnds = [];
712
+ const segments = [];
713
+ let overflow = 0;
714
+ for (const { item, start, end } of overlapping) {
715
+ const from = start < weekStart ? weekStart : start;
716
+ const to = end > weekEnd ? weekEnd : end;
717
+ const startCol = Math.round((from.getTime() - weekStart.getTime()) / DAY_MS);
718
+ const endCol = Math.round((to.getTime() - weekStart.getTime()) / DAY_MS);
719
+ let lane = laneEnds.findIndex((last) => last < startCol);
720
+ if (lane === -1) lane = laneEnds.length;
721
+ if (lane >= maxLanes) {
722
+ overflow += 1;
723
+ continue;
724
+ }
725
+ laneEnds[lane] = endCol;
726
+ segments.push({
727
+ item,
728
+ startCol,
729
+ span: endCol - startCol + 1,
730
+ isStart: start >= weekStart,
731
+ isEnd: end <= weekEnd,
732
+ lane
733
+ });
734
+ }
735
+ return { segments, overflow };
736
+ });
737
+ }, [weeks, dated, maxLanes]);
738
+ if (weeks.length === 0 && emptyState) {
739
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: emptyState });
740
+ }
741
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: ui.cn("flex flex-col h-full", className), "data-testid": testId, children: [
742
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-7 border-b border-border bg-background", children: WEEKDAY_NAMES.map((d) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-2 py-1 text-[11px] text-muted-foreground text-center", children: d }, d)) }),
743
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 overflow-auto", "data-testid": `${testId}-scroll`, children: weeks.map((week, wi) => {
744
+ const { segments, overflow } = segmentsByWeek[wi];
745
+ const monthStartDay = week.find((d) => d.getUTCDate() === 1);
746
+ const marker = monthStartDay ?? (wi === 0 ? week[0] : void 0);
747
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
748
+ marker && // Sticky, so the month you are LOOKING at names itself: it
749
+ // pins to the top of the scroller while you are inside it and
750
+ // is pushed up by the next one. No scroll listener needed.
751
+ /* @__PURE__ */ jsxRuntime.jsx(
752
+ "div",
753
+ {
754
+ className: "sticky top-0 z-10 px-3 py-1 text-xs font-medium text-foreground bg-background/95 backdrop-blur border-y border-border/60",
755
+ "data-testid": `${testId}-month-${marker.getUTCFullYear()}-${String(
756
+ marker.getUTCMonth() + 1
757
+ ).padStart(2, "0")}`,
758
+ children: monthLabel(marker)
759
+ }
760
+ ),
761
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-b border-border/40", children: [
762
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-7 bg-background", children: week.map((day2) => /* @__PURE__ */ jsxRuntime.jsx(
763
+ "div",
764
+ {
765
+ "data-testid": `${testId}-day-${dayKey(day2)}`,
766
+ className: ui.cn(
767
+ "px-1 pt-1 text-[11px] border-r border-border/40",
768
+ day2.getUTCDate() === 1 ? "text-foreground font-medium" : "text-muted-foreground/60"
769
+ ),
770
+ children: day2.getUTCDate()
771
+ },
772
+ dayKey(day2)
773
+ )) }),
774
+ /* @__PURE__ */ jsxRuntime.jsxs(
775
+ "div",
776
+ {
777
+ className: "grid grid-cols-7 gap-y-0.5 px-0.5 pb-1 bg-muted/40",
778
+ style: { gridAutoRows: "minmax(18px, auto)", minHeight: 62 },
779
+ children: [
780
+ segments.map((seg) => /* @__PURE__ */ jsxRuntime.jsx(
781
+ "button",
782
+ {
783
+ type: "button",
784
+ onClick: () => onSelect?.(seg.item),
785
+ title: `${formatSpan(seg.item.start, seg.item.end)} \u2014 ${seg.item.label}`,
786
+ "data-testid": `${testId}-entry-${seg.item.id}`,
787
+ style: {
788
+ gridColumn: `${seg.startCol + 1} / span ${seg.span}`,
789
+ gridRow: seg.lane + 1
790
+ },
791
+ className: ui.cn(
792
+ "text-left text-[10px] leading-[13px] py-[2px] px-1 min-h-[18px] whitespace-normal break-words line-clamp-3",
793
+ isPrecise(seg.item) ? "bg-primary/20 text-foreground" : "bg-muted text-muted-foreground",
794
+ seg.isStart && "rounded-l",
795
+ seg.isEnd && "rounded-r"
796
+ ),
797
+ children: seg.isStart ? seg.item.label : " "
798
+ },
799
+ `${seg.item.id}-${seg.startCol}`
800
+ )),
801
+ overflow > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
802
+ "span",
803
+ {
804
+ className: "text-[10px] text-muted-foreground px-1",
805
+ style: { gridColumn: "1 / span 7", gridRow: maxLanes + 1 },
806
+ children: [
807
+ "+",
808
+ overflow,
809
+ " more"
810
+ ]
811
+ }
812
+ )
813
+ ]
814
+ }
815
+ )
816
+ ] })
817
+ ] }, dayKey(week[0]));
818
+ }) })
819
+ ] });
820
+ }
821
+ var BAR_PALETTE = [
822
+ {
823
+ gradient: "linear-gradient(90deg, #6366f1, #818cf8)",
824
+ shadow: "0 2px 6px rgba(99,102,241,.35)"
825
+ },
826
+ {
827
+ gradient: "linear-gradient(90deg, #8b5cf6, #a78bfa)",
828
+ shadow: "0 2px 6px rgba(139,92,246,.35)"
829
+ },
830
+ { gradient: "linear-gradient(90deg, #ef4444, #f87171)", shadow: "0 2px 6px rgba(239,68,68,.35)" },
831
+ {
832
+ gradient: "linear-gradient(90deg, #f59e0b, #fbbf24)",
833
+ shadow: "0 2px 6px rgba(245,158,11,.35)"
834
+ },
835
+ { gradient: "linear-gradient(90deg, #22c55e, #4ade80)", shadow: "0 2px 6px rgba(34,197,94,.35)" },
836
+ { gradient: "linear-gradient(90deg, #06b6d4, #22d3ee)", shadow: "0 2px 6px rgba(6,182,212,.35)" }
837
+ ];
838
+ var WEEK_MS = 7 * 24 * 60 * 60 * 1e3;
839
+ function formatAxisDate(d) {
840
+ return d.toLocaleDateString("en-US", { month: "short", year: "numeric" });
841
+ }
842
+ function formatBarLabel(start, end) {
843
+ const fmt = (d) => d.toLocaleDateString("en-US", { month: "short", year: "2-digit" });
844
+ if (start.getTime() === end.getTime()) return fmt(start);
845
+ return `${fmt(start)} \u2013 ${fmt(end)}`;
846
+ }
847
+ function TimelineGantt({
848
+ items,
849
+ onSelect,
850
+ renderDetail,
851
+ labelWidth = 240,
852
+ emptyMessage = "Nothing dated yet.",
853
+ className,
854
+ testId = "timeline-gantt"
855
+ }) {
856
+ const [activeIndex, setActiveIndex] = react.useState(null);
857
+ const rows = items.filter((i) => parseIso2(i.start) !== null).slice().sort((a, b) => parseIso2(a.start).getTime() - parseIso2(b.start).getTime());
858
+ if (rows.length === 0) {
859
+ return /* @__PURE__ */ jsxRuntime.jsxs(
860
+ "div",
861
+ {
862
+ "data-testid": `${testId}-empty-state`,
863
+ className: ui.cn(
864
+ "flex flex-col items-center justify-center py-16 text-muted-foreground",
865
+ className
866
+ ),
867
+ children: [
868
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Calendar, { className: "w-12 h-12 mb-3 opacity-30" }),
869
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm", children: emptyMessage })
870
+ ]
871
+ }
872
+ );
873
+ }
874
+ const allDates = rows.flatMap((i) => {
875
+ const s = parseIso2(i.start);
876
+ return [s, parseIso2(i.end ?? null) ?? s];
877
+ });
878
+ const minTime = Math.min(...allDates.map((d) => d.getTime()));
879
+ const maxTime = Math.max(...allDates.map((d) => d.getTime()));
880
+ const pad = Math.max((maxTime - minTime) * 0.04, WEEK_MS);
881
+ const axisMin = new Date(minTime - pad);
882
+ const axisMax = new Date(maxTime + pad);
883
+ const axisRange = axisMax.getTime() - axisMin.getTime();
884
+ const toPercent = (d) => Math.min(100, Math.max(0, (d.getTime() - axisMin.getTime()) / axisRange * 100));
885
+ const ticks = Array.from(
886
+ { length: 5 },
887
+ (_, i) => new Date(axisMin.getTime() + axisRange * i / 4)
888
+ );
889
+ const today = /* @__PURE__ */ new Date();
890
+ const todayPct = toPercent(today);
891
+ const showToday = today > axisMin && today < axisMax;
892
+ const activeItem = activeIndex !== null ? rows[activeIndex] : null;
893
+ const detail = activeItem ? renderDetail?.(activeItem) : null;
894
+ const select = (index) => {
895
+ setActiveIndex(index);
896
+ onSelect?.(index === null ? null : rows[index]);
897
+ };
898
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: ui.cn("flex h-full min-h-0", className), children: [
899
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0 p-5 overflow-x-auto", children: [
900
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex mb-1", children: [
901
+ /* @__PURE__ */ jsxRuntime.jsx(
902
+ "div",
903
+ {
904
+ style: { width: labelWidth, flexShrink: 0 },
905
+ className: "text-[10px] font-semibold uppercase tracking-wider text-muted-foreground pb-1",
906
+ children: "Event"
907
+ }
908
+ ),
909
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 relative h-6 border-b border-border", children: ticks.map((tick, i) => /* @__PURE__ */ jsxRuntime.jsx(
910
+ "span",
911
+ {
912
+ style: { left: `${toPercent(tick)}%` },
913
+ className: "absolute bottom-1 text-[10px] text-muted-foreground -translate-x-1/2 whitespace-nowrap select-none",
914
+ children: formatAxisDate(tick)
915
+ },
916
+ i
917
+ )) })
918
+ ] }),
919
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative mt-2", children: [
920
+ showToday && /* @__PURE__ */ jsxRuntime.jsxs(
921
+ "div",
922
+ {
923
+ className: "absolute top-0 bottom-0 pointer-events-none z-10",
924
+ style: {
925
+ left: `calc(${labelWidth}px + (100% - ${labelWidth}px) * ${todayPct / 100})`
926
+ },
927
+ children: [
928
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full border-l-2 border-dashed border-muted-foreground/50" }),
929
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute -top-5 -translate-x-1/2 text-[9px] text-muted-foreground bg-background border rounded px-1", children: "today" })
930
+ ]
931
+ }
932
+ ),
933
+ rows.map((item, i) => {
934
+ const startDate = parseIso2(item.start);
935
+ const endDate = parseIso2(item.end ?? null) ?? startDate;
936
+ const leftPct = toPercent(startDate);
937
+ const widthPct = Math.max(1, toPercent(endDate) - toPercent(startDate));
938
+ const color = BAR_PALETTE[i % BAR_PALETTE.length];
939
+ const isActive = activeIndex === i;
940
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center mb-2", children: [
941
+ /* @__PURE__ */ jsxRuntime.jsx(
942
+ "div",
943
+ {
944
+ style: { width: labelWidth, flexShrink: 0 },
945
+ className: "text-xs font-medium text-muted-foreground pr-3 break-words leading-snug",
946
+ children: item.label
947
+ }
948
+ ),
949
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 h-7 bg-muted/30 border border-border rounded-md relative overflow-visible", children: /* @__PURE__ */ jsxRuntime.jsx(
950
+ "button",
951
+ {
952
+ "data-testid": `${testId}-bar-${i}`,
953
+ "aria-label": item.label,
954
+ "aria-pressed": isActive,
955
+ style: {
956
+ left: `${leftPct}%`,
957
+ width: `${widthPct}%`,
958
+ background: color.gradient,
959
+ boxShadow: isActive ? "0 0 0 2px white, 0 0 0 4px #6366f1" : color.shadow
960
+ },
961
+ className: "absolute top-0.5 h-6 rounded text-white text-[9px] font-semibold px-2 overflow-hidden whitespace-nowrap cursor-pointer transition-[filter] hover:brightness-110 focus:outline-none",
962
+ onClick: () => select(isActive ? null : i),
963
+ children: formatBarLabel(startDate, endDate)
964
+ }
965
+ ) })
966
+ ] }, item.id);
967
+ })
968
+ ] })
969
+ ] }),
970
+ /* @__PURE__ */ jsxRuntime.jsx(
971
+ "div",
972
+ {
973
+ "data-testid": `${testId}-side-panel`,
974
+ className: ui.cn(
975
+ "border-l bg-muted/20 flex flex-col shrink-0 overflow-hidden transition-all duration-300",
976
+ activeItem ? "w-60 open" : "w-0"
977
+ ),
978
+ children: /* @__PURE__ */ jsxRuntime.jsx(
979
+ "div",
980
+ {
981
+ className: ui.cn(
982
+ "w-60 p-4 flex flex-col gap-3 transition-opacity duration-200",
983
+ activeItem ? "opacity-100" : "opacity-0"
984
+ ),
985
+ children: activeItem && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
986
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxRuntime.jsx(
987
+ "button",
988
+ {
989
+ "data-testid": `${testId}-panel-close`,
990
+ className: "w-6 h-6 rounded flex items-center justify-center text-muted-foreground hover:bg-muted",
991
+ onClick: () => select(null),
992
+ "aria-label": "Close panel",
993
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-3.5 h-3.5" })
994
+ }
995
+ ) }),
996
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
997
+ /* @__PURE__ */ jsxRuntime.jsx(
998
+ "div",
999
+ {
1000
+ "data-testid": `${testId}-panel-title`,
1001
+ className: "text-sm font-semibold text-foreground leading-snug",
1002
+ children: activeItem.label
1003
+ }
1004
+ ),
1005
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 mt-1 text-xs text-muted-foreground", children: [
1006
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Calendar, { className: "w-3 h-3 shrink-0" }),
1007
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatSpan(activeItem.start, activeItem.end) || "Date unknown" })
1008
+ ] })
1009
+ ] }),
1010
+ activeItem.description && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1011
+ /* @__PURE__ */ jsxRuntime.jsx("hr", { className: "border-border" }),
1012
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1013
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-[10px] font-semibold uppercase tracking-wider text-muted-foreground mb-1", children: "Summary" }),
1014
+ /* @__PURE__ */ jsxRuntime.jsx(
1015
+ "p",
1016
+ {
1017
+ "data-testid": `${testId}-panel-desc`,
1018
+ className: "text-xs text-muted-foreground leading-relaxed",
1019
+ children: activeItem.description
1020
+ }
1021
+ )
1022
+ ] })
1023
+ ] }),
1024
+ detail && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1025
+ /* @__PURE__ */ jsxRuntime.jsx("hr", { className: "border-border" }),
1026
+ detail
1027
+ ] })
1028
+ ] })
1029
+ }
1030
+ )
1031
+ }
1032
+ )
1033
+ ] });
1034
+ }
1035
+ var TOGGLE = [
1036
+ { mode: "calendar", label: "Calendar", Icon: lucideReact.CalendarDays },
1037
+ { mode: "gantt", label: "Gantt", Icon: lucideReact.GanttChartSquare }
1038
+ ];
1039
+ function Timeline({
1040
+ items,
1041
+ view,
1042
+ defaultView = "calendar",
1043
+ onSelect,
1044
+ renderDetail,
1045
+ emptyMessage,
1046
+ className,
1047
+ testId = "timeline"
1048
+ }) {
1049
+ const [picked, setPicked] = react.useState(defaultView);
1050
+ const mode = view ?? picked;
1051
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: ui.cn("flex flex-col h-full min-h-0", className), "data-testid": testId, children: [
1052
+ !view && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-1 p-2 border-b border-border", children: TOGGLE.map(({ mode: m, label, Icon }) => /* @__PURE__ */ jsxRuntime.jsxs(
1053
+ "button",
1054
+ {
1055
+ type: "button",
1056
+ onClick: () => setPicked(m),
1057
+ "aria-pressed": mode === m,
1058
+ "data-testid": `${testId}-view-${m}`,
1059
+ className: ui.cn(
1060
+ "flex items-center gap-1.5 rounded px-2 py-1 text-xs",
1061
+ mode === m ? "bg-muted text-foreground" : "text-muted-foreground hover:text-foreground"
1062
+ ),
1063
+ children: [
1064
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "w-3.5 h-3.5" }),
1065
+ label
1066
+ ]
1067
+ },
1068
+ m
1069
+ )) }),
1070
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-h-0", children: mode === "calendar" ? /* @__PURE__ */ jsxRuntime.jsx(
1071
+ TimelineCalendar,
1072
+ {
1073
+ items,
1074
+ onSelect,
1075
+ testId: `${testId}-calendar`,
1076
+ emptyState: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center p-8 text-sm text-muted-foreground", children: emptyMessage ?? "Nothing dated yet." })
1077
+ }
1078
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
1079
+ TimelineGantt,
1080
+ {
1081
+ items,
1082
+ onSelect: (item) => item && onSelect?.(item),
1083
+ renderDetail,
1084
+ emptyMessage,
1085
+ testId: `${testId}-gantt`
1086
+ }
1087
+ ) })
1088
+ ] });
1089
+ }
590
1090
  function SectionHeading({
591
1091
  eyebrow,
592
1092
  title,
@@ -1527,6 +2027,9 @@ exports.TeamGrid = TeamGrid;
1527
2027
  exports.Testimonial = Testimonial;
1528
2028
  exports.TestimonialGrid = TestimonialGrid;
1529
2029
  exports.ThemeEditor = ThemeEditor;
2030
+ exports.Timeline = Timeline;
2031
+ exports.TimelineCalendar = TimelineCalendar;
2032
+ exports.TimelineGantt = TimelineGantt;
1530
2033
  exports.VideoEmbed = VideoEmbed;
1531
2034
  exports.applyFontVars = applyFontVars;
1532
2035
  exports.applyStoredTheme = applyStoredTheme;
@@ -1536,11 +2039,13 @@ exports.assetRefId = assetRefId;
1536
2039
  exports.clearFontVars = clearFontVars;
1537
2040
  exports.clearThemeVars = clearThemeVars;
1538
2041
  exports.createThemeStore = createThemeStore;
2042
+ exports.dayKey = dayKey;
1539
2043
  exports.daysUntil = daysUntil;
1540
2044
  exports.fmtDate = fmtDate;
1541
2045
  exports.fmtDateTime = fmtDateTime;
1542
2046
  exports.fontPackages = fontPackages;
1543
2047
  exports.formatPageRefs = formatPageRefs;
2048
+ exports.formatSpan = formatSpan;
1544
2049
  exports.fromNow = fromNow;
1545
2050
  exports.gbp = gbp;
1546
2051
  exports.getFontPairing = getFontPairing;
@@ -1548,11 +2053,14 @@ exports.hrs = hrs;
1548
2053
  exports.initials = initials;
1549
2054
  exports.isAssetRef = isAssetRef;
1550
2055
  exports.matchFontPairing = matchFontPairing;
2056
+ exports.monthLabel = monthLabel;
2057
+ exports.parseIso = parseIso2;
1551
2058
  exports.pct = pct;
1552
2059
  exports.tid = tid;
1553
2060
  exports.toEmbedUrl = toEmbedUrl;
1554
2061
  exports.toHslTriplet = toHslTriplet;
1555
2062
  exports.useAssetRenderer = useAssetRenderer;
1556
2063
  exports.useAssetResolverActive = useAssetResolverActive;
1557
- //# sourceMappingURL=chunk-6MFI2HPX.cjs.map
1558
- //# sourceMappingURL=chunk-6MFI2HPX.cjs.map
2064
+ exports.utcDay = utcDay;
2065
+ //# sourceMappingURL=chunk-YNGRSAGP.cjs.map
2066
+ //# sourceMappingURL=chunk-YNGRSAGP.cjs.map