@allhailai/tempusmachina-react 1.0.0 → 1.1.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.
- package/dist/index.cjs +277 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -4
- package/dist/index.d.ts +34 -4
- package/dist/index.js +277 -19
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -194,6 +194,7 @@ function useCalendar(options) {
|
|
|
194
194
|
slotMinTime,
|
|
195
195
|
slotMaxTime,
|
|
196
196
|
resources = [],
|
|
197
|
+
resourceOverlays = [],
|
|
197
198
|
businessHours,
|
|
198
199
|
eventMaxStack = 3,
|
|
199
200
|
eventDefaults,
|
|
@@ -350,6 +351,7 @@ function useCalendar(options) {
|
|
|
350
351
|
viewLayout,
|
|
351
352
|
visibleRange,
|
|
352
353
|
events: eventStore.getAll(),
|
|
354
|
+
resourceOverlays,
|
|
353
355
|
next,
|
|
354
356
|
prev,
|
|
355
357
|
today: todayAction,
|
|
@@ -1576,8 +1578,57 @@ function TimeGrid({
|
|
|
1576
1578
|
stickyFooterScrollbar && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sticky bottom-0 h-3 bg-cal-bg border-t border-cal-border overflow-x-auto shrink-0" })
|
|
1577
1579
|
] });
|
|
1578
1580
|
}
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
+
var PATTERN_STYLES = {
|
|
1582
|
+
striped: {
|
|
1583
|
+
backgroundImage: "repeating-linear-gradient(135deg, transparent, transparent 4px, rgba(0,0,0,0.06) 4px, rgba(0,0,0,0.06) 8px)"
|
|
1584
|
+
},
|
|
1585
|
+
hatched: {
|
|
1586
|
+
backgroundImage: "repeating-linear-gradient(45deg, transparent, transparent 3px, rgba(0,0,0,0.08) 3px, rgba(0,0,0,0.08) 6px), repeating-linear-gradient(-45deg, transparent, transparent 3px, rgba(0,0,0,0.08) 3px, rgba(0,0,0,0.08) 6px)"
|
|
1587
|
+
}
|
|
1588
|
+
};
|
|
1589
|
+
function sanitizeCategory(category) {
|
|
1590
|
+
return category.toLowerCase().replace(/[^a-z0-9_-]/g, "-");
|
|
1591
|
+
}
|
|
1592
|
+
function computeBandPosition(band, rangeStartMs, rangeTotalMs) {
|
|
1593
|
+
if (rangeTotalMs <= 0) return null;
|
|
1594
|
+
const bandStartMs = band.start.epochMilliseconds;
|
|
1595
|
+
const bandEndMs = band.end.epochMilliseconds;
|
|
1596
|
+
const clampedStart = Math.max(bandStartMs, rangeStartMs);
|
|
1597
|
+
const clampedEnd = Math.min(bandEndMs, rangeStartMs + rangeTotalMs);
|
|
1598
|
+
if (clampedEnd <= clampedStart) return null;
|
|
1599
|
+
const left = (clampedStart - rangeStartMs) / rangeTotalMs * 100;
|
|
1600
|
+
const width = (clampedEnd - clampedStart) / rangeTotalMs * 100;
|
|
1601
|
+
return { left, width };
|
|
1602
|
+
}
|
|
1603
|
+
function ResourceLane({ lane, slotCount, overlays }) {
|
|
1604
|
+
const { slots, viewLayout, timezone } = useCalendarContext();
|
|
1605
|
+
const { rangeStartMs, rangeTotalMs } = React16.useMemo(() => {
|
|
1606
|
+
if (!viewLayout?.dateRange) return { rangeStartMs: 0, rangeTotalMs: 0 };
|
|
1607
|
+
const timeSlots = viewLayout.timeSlots;
|
|
1608
|
+
if (timeSlots && timeSlots.length > 0) {
|
|
1609
|
+
const firstSlot = timeSlots[0];
|
|
1610
|
+
const lastSlot = timeSlots[timeSlots.length - 1];
|
|
1611
|
+
const startMs2 = firstSlot.start.toZonedDateTime(timezone).epochMilliseconds;
|
|
1612
|
+
const endMs2 = lastSlot.end.toZonedDateTime(timezone).epochMilliseconds;
|
|
1613
|
+
return {
|
|
1614
|
+
rangeStartMs: startMs2,
|
|
1615
|
+
rangeTotalMs: Math.max(0, endMs2 - startMs2)
|
|
1616
|
+
};
|
|
1617
|
+
}
|
|
1618
|
+
const { start: rangeStart, end: rangeEnd } = viewLayout.dateRange;
|
|
1619
|
+
const startMs = rangeStart.toZonedDateTime({
|
|
1620
|
+
timeZone: timezone,
|
|
1621
|
+
plainTime: temporalPolyfill.Temporal.PlainTime.from("00:00")
|
|
1622
|
+
}).epochMilliseconds;
|
|
1623
|
+
const endMs = rangeEnd.toZonedDateTime({
|
|
1624
|
+
timeZone: timezone,
|
|
1625
|
+
plainTime: temporalPolyfill.Temporal.PlainTime.from("00:00")
|
|
1626
|
+
}).epochMilliseconds;
|
|
1627
|
+
return {
|
|
1628
|
+
rangeStartMs: startMs,
|
|
1629
|
+
rangeTotalMs: Math.max(0, endMs - startMs)
|
|
1630
|
+
};
|
|
1631
|
+
}, [viewLayout?.dateRange, viewLayout?.timeSlots, timezone]);
|
|
1581
1632
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1582
1633
|
"div",
|
|
1583
1634
|
{
|
|
@@ -1594,36 +1645,114 @@ function ResourceLane({ lane, slotCount }) {
|
|
|
1594
1645
|
),
|
|
1595
1646
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium truncate", children: lane.resource.title })
|
|
1596
1647
|
] }) }),
|
|
1597
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1648
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1598
1649
|
"div",
|
|
1599
1650
|
{
|
|
1600
1651
|
className: cn("relative col-span-full", `col-start-2`),
|
|
1601
1652
|
style: { gridColumn: `2 / -1`, minHeight: "var(--cal-slot-height)" },
|
|
1602
|
-
children:
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1653
|
+
children: [
|
|
1654
|
+
overlays && rangeTotalMs > 0 && overlays.map((band) => {
|
|
1655
|
+
const pos = computeBandPosition(band, rangeStartMs, rangeTotalMs);
|
|
1656
|
+
if (!pos) return null;
|
|
1657
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1658
|
+
OverlayBandElement,
|
|
1659
|
+
{
|
|
1660
|
+
band,
|
|
1661
|
+
resource: lane.resource,
|
|
1662
|
+
left: pos.left,
|
|
1663
|
+
width: pos.width
|
|
1664
|
+
},
|
|
1665
|
+
band.id
|
|
1666
|
+
);
|
|
1667
|
+
}),
|
|
1668
|
+
lane.events.map((posEvent) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1669
|
+
"div",
|
|
1670
|
+
{
|
|
1671
|
+
className: "tm-event",
|
|
1672
|
+
style: {
|
|
1673
|
+
left: `${posEvent.left}%`,
|
|
1674
|
+
width: `${posEvent.width}%`,
|
|
1675
|
+
top: "2px",
|
|
1676
|
+
height: "calc(100% - 4px)"
|
|
1677
|
+
},
|
|
1678
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(EventCard, { positionedEvent: posEvent, compact: true })
|
|
1611
1679
|
},
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
))
|
|
1680
|
+
posEvent.event.id
|
|
1681
|
+
))
|
|
1682
|
+
]
|
|
1616
1683
|
}
|
|
1617
1684
|
)
|
|
1618
1685
|
]
|
|
1619
1686
|
}
|
|
1620
1687
|
);
|
|
1621
1688
|
}
|
|
1622
|
-
function
|
|
1623
|
-
const {
|
|
1689
|
+
function OverlayBandElement({ band, resource, left, width }) {
|
|
1690
|
+
const { slots } = useCalendarContext();
|
|
1691
|
+
const safeCategory = sanitizeCategory(band.category);
|
|
1692
|
+
const bgColor = band.color ?? `var(--cal-overlay-${safeCategory}, var(--cal-overlay-default))`;
|
|
1693
|
+
const bandStyle = {
|
|
1694
|
+
position: "absolute",
|
|
1695
|
+
top: 0,
|
|
1696
|
+
bottom: 0,
|
|
1697
|
+
left: `${left}%`,
|
|
1698
|
+
width: `${width}%`,
|
|
1699
|
+
backgroundColor: bgColor,
|
|
1700
|
+
opacity: 0.3,
|
|
1701
|
+
pointerEvents: "none",
|
|
1702
|
+
...band.pattern && band.pattern !== "solid" ? PATTERN_STYLES[band.pattern] : {}
|
|
1703
|
+
};
|
|
1704
|
+
if (slots.overlayBandContent) {
|
|
1705
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: slots.overlayBandContent({ band, resource, style: bandStyle }) });
|
|
1706
|
+
}
|
|
1707
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1708
|
+
"div",
|
|
1709
|
+
{
|
|
1710
|
+
className: cn("tm-overlay-band", `tm-overlay-band--${safeCategory}`),
|
|
1711
|
+
style: bandStyle,
|
|
1712
|
+
"aria-hidden": "true",
|
|
1713
|
+
title: band.label ?? band.category
|
|
1714
|
+
}
|
|
1715
|
+
);
|
|
1716
|
+
}
|
|
1717
|
+
function Timeline({
|
|
1718
|
+
className,
|
|
1719
|
+
collapsibleGroups = true,
|
|
1720
|
+
expandedGroupIds: controlledExpandedIds,
|
|
1721
|
+
onGroupToggle
|
|
1722
|
+
}) {
|
|
1723
|
+
const { viewLayout, locale, slots, resourceOverlays } = useCalendarContext();
|
|
1724
|
+
const [internalExpanded, setInternalExpanded] = React16.useState({});
|
|
1725
|
+
const isGroupExpanded = React16.useCallback(
|
|
1726
|
+
(groupId) => {
|
|
1727
|
+
if (controlledExpandedIds) {
|
|
1728
|
+
return controlledExpandedIds.includes(groupId);
|
|
1729
|
+
}
|
|
1730
|
+
return internalExpanded[groupId] !== false;
|
|
1731
|
+
},
|
|
1732
|
+
[controlledExpandedIds, internalExpanded]
|
|
1733
|
+
);
|
|
1734
|
+
const handleGroupToggle = React16.useCallback(
|
|
1735
|
+
(groupId) => {
|
|
1736
|
+
const newState = !isGroupExpanded(groupId);
|
|
1737
|
+
if (!controlledExpandedIds) {
|
|
1738
|
+
setInternalExpanded((prev) => ({ ...prev, [groupId]: newState }));
|
|
1739
|
+
}
|
|
1740
|
+
onGroupToggle?.(groupId, newState);
|
|
1741
|
+
},
|
|
1742
|
+
[controlledExpandedIds, isGroupExpanded, onGroupToggle]
|
|
1743
|
+
);
|
|
1624
1744
|
if (!viewLayout?.lanes) return null;
|
|
1625
1745
|
const lanes = viewLayout.lanes;
|
|
1626
1746
|
const timeSlots = viewLayout.timeSlots ?? [];
|
|
1747
|
+
const resourceGroups = viewLayout.resourceGroups;
|
|
1748
|
+
const laneByResourceId = new Map(lanes.map((lane) => [lane.resource.id, lane]));
|
|
1749
|
+
const overlaysByResourceId = /* @__PURE__ */ new Map();
|
|
1750
|
+
if (resourceOverlays) {
|
|
1751
|
+
for (const overlay of resourceOverlays) {
|
|
1752
|
+
overlaysByResourceId.set(overlay.resourceId, overlay.bands);
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
const hasGroups = resourceGroups && resourceGroups.length > 0;
|
|
1627
1756
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex-1 overflow-hidden tm-timeline", className), children: [
|
|
1628
1757
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "tm-timeline__header border-b border-cal-border bg-cal-header-bg", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1629
1758
|
"div",
|
|
@@ -1645,7 +1774,135 @@ function Timeline({ className }) {
|
|
|
1645
1774
|
]
|
|
1646
1775
|
}
|
|
1647
1776
|
) }),
|
|
1648
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1777
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "overflow-y-auto tm-scrollable", children: [
|
|
1778
|
+
hasGroups ? resourceGroups.map((group) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1779
|
+
TimelineGroup,
|
|
1780
|
+
{
|
|
1781
|
+
group,
|
|
1782
|
+
laneByResourceId,
|
|
1783
|
+
overlaysByResourceId,
|
|
1784
|
+
slotCount: timeSlots.length,
|
|
1785
|
+
collapsible: collapsibleGroups,
|
|
1786
|
+
isExpanded: isGroupExpanded(group.id),
|
|
1787
|
+
onToggle: () => handleGroupToggle(group.id),
|
|
1788
|
+
slots
|
|
1789
|
+
},
|
|
1790
|
+
group.id
|
|
1791
|
+
)) : lanes.map((lane) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1792
|
+
ResourceLane,
|
|
1793
|
+
{
|
|
1794
|
+
lane,
|
|
1795
|
+
slotCount: timeSlots.length,
|
|
1796
|
+
overlays: overlaysByResourceId.get(lane.resource.id)
|
|
1797
|
+
},
|
|
1798
|
+
lane.resource.id
|
|
1799
|
+
)),
|
|
1800
|
+
hasGroups && lanes.filter(
|
|
1801
|
+
(lane) => !resourceGroups.some(
|
|
1802
|
+
(g) => g.resources.some((r) => r.id === lane.resource.id)
|
|
1803
|
+
) && lane.resource.id !== "__unassigned__"
|
|
1804
|
+
).map((lane) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1805
|
+
ResourceLane,
|
|
1806
|
+
{
|
|
1807
|
+
lane,
|
|
1808
|
+
slotCount: timeSlots.length,
|
|
1809
|
+
overlays: overlaysByResourceId.get(lane.resource.id)
|
|
1810
|
+
},
|
|
1811
|
+
lane.resource.id
|
|
1812
|
+
)),
|
|
1813
|
+
lanes.filter((lane) => lane.resource.id === "__unassigned__").map((lane) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1814
|
+
ResourceLane,
|
|
1815
|
+
{
|
|
1816
|
+
lane,
|
|
1817
|
+
slotCount: timeSlots.length,
|
|
1818
|
+
overlays: overlaysByResourceId.get(lane.resource.id)
|
|
1819
|
+
},
|
|
1820
|
+
lane.resource.id
|
|
1821
|
+
))
|
|
1822
|
+
] })
|
|
1823
|
+
] });
|
|
1824
|
+
}
|
|
1825
|
+
function TimelineGroup({
|
|
1826
|
+
group,
|
|
1827
|
+
laneByResourceId,
|
|
1828
|
+
overlaysByResourceId,
|
|
1829
|
+
slotCount,
|
|
1830
|
+
collapsible,
|
|
1831
|
+
isExpanded,
|
|
1832
|
+
onToggle,
|
|
1833
|
+
slots
|
|
1834
|
+
}) {
|
|
1835
|
+
const memberCount = group.resources.length;
|
|
1836
|
+
const groupSubtitle = group.subtitle;
|
|
1837
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "tm-timeline-group", children: [
|
|
1838
|
+
slots.resourceGroupContent ? slots.resourceGroupContent({
|
|
1839
|
+
group,
|
|
1840
|
+
isExpanded,
|
|
1841
|
+
memberCount,
|
|
1842
|
+
onToggle
|
|
1843
|
+
}) : /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1844
|
+
"div",
|
|
1845
|
+
{
|
|
1846
|
+
className: cn(
|
|
1847
|
+
"tm-timeline-group__header",
|
|
1848
|
+
"flex items-center gap-2 px-3 py-2",
|
|
1849
|
+
"bg-cal-header-bg border-b border-cal-border",
|
|
1850
|
+
collapsible && "cursor-pointer hover:bg-cal-slot-bg"
|
|
1851
|
+
),
|
|
1852
|
+
onClick: collapsible ? onToggle : void 0,
|
|
1853
|
+
onKeyDown: collapsible ? (e) => {
|
|
1854
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
1855
|
+
e.preventDefault();
|
|
1856
|
+
onToggle();
|
|
1857
|
+
}
|
|
1858
|
+
} : void 0,
|
|
1859
|
+
role: collapsible ? "button" : void 0,
|
|
1860
|
+
tabIndex: collapsible ? 0 : void 0,
|
|
1861
|
+
"aria-expanded": collapsible ? isExpanded : void 0,
|
|
1862
|
+
"aria-label": `${isExpanded ? "Collapse" : "Expand"} ${group.title}`,
|
|
1863
|
+
children: [
|
|
1864
|
+
collapsible && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1865
|
+
"svg",
|
|
1866
|
+
{
|
|
1867
|
+
width: "14",
|
|
1868
|
+
height: "14",
|
|
1869
|
+
viewBox: "0 0 14 14",
|
|
1870
|
+
fill: "none",
|
|
1871
|
+
className: cn(
|
|
1872
|
+
"transition-transform duration-200 text-cal-fg opacity-50",
|
|
1873
|
+
isExpanded ? "rotate-90" : ""
|
|
1874
|
+
),
|
|
1875
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1876
|
+
"path",
|
|
1877
|
+
{
|
|
1878
|
+
d: "M5 3L9 7L5 11",
|
|
1879
|
+
stroke: "currentColor",
|
|
1880
|
+
strokeWidth: "1.5",
|
|
1881
|
+
strokeLinecap: "round",
|
|
1882
|
+
strokeLinejoin: "round"
|
|
1883
|
+
}
|
|
1884
|
+
)
|
|
1885
|
+
}
|
|
1886
|
+
),
|
|
1887
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-semibold text-cal-fg", children: group.title }),
|
|
1888
|
+
groupSubtitle && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] text-cal-fg opacity-50", children: groupSubtitle }),
|
|
1889
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-auto text-[10px] font-medium text-cal-fg opacity-40 bg-cal-slot-bg rounded-full px-1.5 py-0.5", children: memberCount })
|
|
1890
|
+
]
|
|
1891
|
+
}
|
|
1892
|
+
),
|
|
1893
|
+
isExpanded && group.resources.map((resource) => {
|
|
1894
|
+
const lane = laneByResourceId.get(resource.id);
|
|
1895
|
+
if (!lane) return null;
|
|
1896
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1897
|
+
ResourceLane,
|
|
1898
|
+
{
|
|
1899
|
+
lane,
|
|
1900
|
+
slotCount,
|
|
1901
|
+
overlays: overlaysByResourceId.get(resource.id)
|
|
1902
|
+
},
|
|
1903
|
+
resource.id
|
|
1904
|
+
);
|
|
1905
|
+
})
|
|
1649
1906
|
] });
|
|
1650
1907
|
}
|
|
1651
1908
|
function Agenda({ className }) {
|
|
@@ -1838,6 +2095,7 @@ function Calendar({
|
|
|
1838
2095
|
const contextValue = {
|
|
1839
2096
|
...calendarState,
|
|
1840
2097
|
resources: options.resources ?? [],
|
|
2098
|
+
resourceOverlays: calendarState.resourceOverlays,
|
|
1841
2099
|
timezone: options.timezone ?? "UTC",
|
|
1842
2100
|
locale: options.locale ?? "en-US",
|
|
1843
2101
|
firstDayOfWeek: options.firstDayOfWeek ?? 0,
|