pixelize-design-library 2.4.2-beta.59 → 2.4.2-beta.60
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/CHANGELOG.md +78 -9
- package/COMPONENT-GOTCHAS.md +118 -17
- package/DESIGN-GUIDE.md +17 -6
- package/dist/Components/AudioRecorder/AudioRecorder.d.ts +8 -0
- package/dist/Components/AudioRecorder/AudioRecorder.js +169 -0
- package/dist/Components/AudioRecorder/AudioRecorderProps.d.ts +20 -0
- package/dist/Components/AudioRecorder/AudioRecorderProps.js +2 -0
- package/dist/Components/BulkSelectionBar/BulkSelectionBar.d.ts +3 -0
- package/dist/Components/BulkSelectionBar/BulkSelectionBar.js +43 -0
- package/dist/Components/BulkSelectionBar/BulkSelectionBarProps.d.ts +35 -0
- package/dist/Components/BulkSelectionBar/BulkSelectionBarProps.js +2 -0
- package/dist/Components/BulkSelectionBar/index.d.ts +2 -0
- package/dist/Components/BulkSelectionBar/index.js +8 -0
- package/dist/Components/CalendarGrid/CalendarGrid.d.ts +3 -0
- package/dist/Components/CalendarGrid/CalendarGrid.js +173 -0
- package/dist/Components/CalendarGrid/CalendarGridProps.d.ts +54 -0
- package/dist/Components/CalendarGrid/CalendarGridProps.js +2 -0
- package/dist/Components/CalendarGrid/calendarMath.d.ts +49 -0
- package/dist/Components/CalendarGrid/calendarMath.js +178 -0
- package/dist/Components/CircularProgress/CircularProgress.d.ts +4 -0
- package/dist/Components/CircularProgress/CircularProgress.js +39 -0
- package/dist/Components/CircularProgress/CircularProgressProps.d.ts +22 -0
- package/dist/Components/CircularProgress/CircularProgressProps.js +2 -0
- package/dist/Components/CircularProgress/arc.d.ts +12 -0
- package/dist/Components/CircularProgress/arc.js +16 -0
- package/dist/Components/Common/fieldStyles.d.ts +1 -0
- package/dist/Components/Common/fieldStyles.js +2 -1
- package/dist/Components/Drawer/Drawer.d.ts +1 -1
- package/dist/Components/Drawer/Drawer.js +4 -2
- package/dist/Components/Drawer/DrawerProps.d.ts +3 -1
- package/dist/Components/EmptyState/EmptyState.js +2 -1
- package/dist/Components/EmptyState/EmptyStateProps.d.ts +2 -2
- package/dist/Components/FormDrawer/FormDrawer.d.ts +2 -0
- package/dist/Components/FormDrawer/FormDrawer.js +97 -0
- package/dist/Components/FormDrawer/FormDrawerProps.d.ts +55 -0
- package/dist/Components/FormDrawer/FormDrawerProps.js +2 -0
- package/dist/Components/LauncherTrigger/LauncherTrigger.d.ts +1 -1
- package/dist/Components/LauncherTrigger/LauncherTrigger.js +8 -1
- package/dist/Components/LauncherTrigger/LauncherTriggerProps.d.ts +2 -0
- package/dist/Components/Modal/Modal.d.ts +1 -1
- package/dist/Components/Modal/Modal.js +4 -2
- package/dist/Components/Modal/ModalProps.d.ts +3 -1
- package/dist/Components/Modal/warnDroppedSlotChildren.d.ts +2 -0
- package/dist/Components/Modal/warnDroppedSlotChildren.js +45 -0
- package/dist/Components/NavLauncher/NavLauncher.js +83 -26
- package/dist/Components/NavLauncher/NavLauncherProps.d.ts +6 -6
- package/dist/Components/NavLauncher/launcherParts.d.ts +60 -1
- package/dist/Components/NavLauncher/launcherParts.js +93 -11
- package/dist/Components/ProfilePhotoViewer/ProfilePhotoViewer.js +5 -0
- package/dist/Components/ProfilePhotoViewer/ProfilePhotoViewerProps.d.ts +1 -1
- package/dist/Components/QueryState/QueryStateBoundary.d.ts +2 -2
- package/dist/Components/QueryState/QueryStateBoundary.js +9 -3
- package/dist/Components/QueryState/QueryStateProps.d.ts +19 -2
- package/dist/Components/SectionRail/SectionRail.d.ts +3 -0
- package/dist/Components/SectionRail/SectionRail.js +49 -0
- package/dist/Components/SectionRail/SectionRailProps.d.ts +23 -0
- package/dist/Components/SectionRail/SectionRailProps.js +2 -0
- package/dist/Components/SectionRail/index.d.ts +2 -0
- package/dist/Components/SectionRail/index.js +8 -0
- package/dist/Components/SegmentedControl/SegmentedControl.js +9 -14
- package/dist/Components/SegmentedControl/segmentedInk.d.ts +0 -2
- package/dist/Components/SegmentedControl/segmentedInk.js +1 -5
- package/dist/Components/StatCard/StatCard.d.ts +1 -1
- package/dist/Components/StatCard/StatCard.js +6 -2
- package/dist/Components/StatCard/StatCardProps.d.ts +3 -0
- package/dist/Components/StatusTag/StatusTag.d.ts +4 -0
- package/dist/Components/StatusTag/StatusTag.js +24 -0
- package/dist/Components/StatusTag/StatusTagProps.d.ts +20 -0
- package/dist/Components/StatusTag/StatusTagProps.js +2 -0
- package/dist/Components/Table/Table.d.ts +1 -1
- package/dist/Components/Table/Table.js +6 -2
- package/dist/Components/Table/TableProps.d.ts +20 -0
- package/dist/Components/Table/components/TableBody.d.ts +1 -1
- package/dist/Components/Table/components/TableBody.js +43 -18
- package/dist/Components/Table/components/TableHeader.d.ts +1 -1
- package/dist/Components/Table/components/TableHeader.js +7 -5
- package/dist/Components/Table/components/rowDetail.d.ts +32 -0
- package/dist/Components/Table/components/rowDetail.js +20 -0
- package/dist/Components/Table/hooks/useRowExpansion.d.ts +7 -0
- package/dist/Components/Table/hooks/useRowExpansion.js +27 -0
- package/dist/Components/Tabs/Tabs.d.ts +1 -1
- package/dist/Components/Tabs/Tabs.js +15 -2
- package/dist/Components/Tabs/TabsProps.d.ts +6 -2
- package/dist/Components/Tabs/components/Tab.js +4 -33
- package/dist/Components/Tabs/components/TabLinkList.d.ts +7 -0
- package/dist/Components/Tabs/components/TabLinkList.js +34 -0
- package/dist/Components/Tabs/useTabAppearance.d.ts +17 -0
- package/dist/Components/Tabs/useTabAppearance.js +67 -0
- package/dist/Components/Tag/Tag.js +11 -1
- package/dist/Components/Tag/TagProps.d.ts +4 -0
- package/dist/Components/Tag/tagDot.d.ts +3 -0
- package/dist/Components/Tag/tagDot.js +21 -0
- package/dist/esm/Components/AudioRecorder/AudioRecorder.js +161 -0
- package/dist/esm/Components/AudioRecorder/AudioRecorderProps.js +1 -0
- package/dist/esm/Components/BulkSelectionBar/BulkSelectionBar.js +38 -0
- package/dist/esm/Components/BulkSelectionBar/BulkSelectionBarProps.js +1 -0
- package/dist/esm/Components/BulkSelectionBar/index.js +1 -0
- package/dist/esm/Components/CalendarGrid/CalendarGrid.js +168 -0
- package/dist/esm/Components/CalendarGrid/CalendarGridProps.js +1 -0
- package/dist/esm/Components/CalendarGrid/calendarMath.js +155 -0
- package/dist/esm/Components/CircularProgress/CircularProgress.js +37 -0
- package/dist/esm/Components/CircularProgress/CircularProgressProps.js +1 -0
- package/dist/esm/Components/CircularProgress/arc.js +11 -0
- package/dist/esm/Components/Common/fieldStyles.js +2 -1
- package/dist/esm/Components/Drawer/Drawer.js +4 -2
- package/dist/esm/Components/EmptyState/EmptyState.js +2 -1
- package/dist/esm/Components/FormDrawer/FormDrawer.js +58 -0
- package/dist/esm/Components/FormDrawer/FormDrawerProps.js +1 -0
- package/dist/esm/Components/LauncherTrigger/LauncherTrigger.js +5 -1
- package/dist/esm/Components/Modal/Modal.js +4 -2
- package/dist/esm/Components/Modal/warnDroppedSlotChildren.js +38 -0
- package/dist/esm/Components/NavLauncher/NavLauncher.js +85 -28
- package/dist/esm/Components/NavLauncher/launcherParts.js +88 -11
- package/dist/esm/Components/ProfilePhotoViewer/ProfilePhotoViewer.js +5 -0
- package/dist/esm/Components/QueryState/QueryStateBoundary.js +10 -4
- package/dist/esm/Components/SectionRail/SectionRail.js +44 -0
- package/dist/esm/Components/SectionRail/SectionRailProps.js +1 -0
- package/dist/esm/Components/SectionRail/index.js +1 -0
- package/dist/esm/Components/SegmentedControl/SegmentedControl.js +11 -16
- package/dist/esm/Components/SegmentedControl/segmentedInk.js +0 -3
- package/dist/esm/Components/StatCard/StatCard.js +6 -2
- package/dist/esm/Components/StatusTag/StatusTag.js +19 -0
- package/dist/esm/Components/StatusTag/StatusTagProps.js +1 -0
- package/dist/esm/Components/Table/Table.js +7 -3
- package/dist/esm/Components/Table/components/TableBody.js +43 -18
- package/dist/esm/Components/Table/components/TableHeader.js +7 -5
- package/dist/esm/Components/Table/components/rowDetail.js +14 -0
- package/dist/esm/Components/Table/hooks/useRowExpansion.js +23 -0
- package/dist/esm/Components/Tabs/Tabs.js +15 -2
- package/dist/esm/Components/Tabs/components/Tab.js +4 -33
- package/dist/esm/Components/Tabs/components/TabLinkList.js +29 -0
- package/dist/esm/Components/Tabs/useTabAppearance.js +63 -0
- package/dist/esm/Components/Tag/Tag.js +12 -2
- package/dist/esm/Components/Tag/tagDot.js +17 -0
- package/dist/esm/index.js +7 -0
- package/dist/index.d.ts +16 -2
- package/dist/index.js +18 -4
- package/package.json +1 -1
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
7
|
+
const react_1 = require("react");
|
|
8
|
+
const react_2 = require("@chakra-ui/react");
|
|
9
|
+
const lucide_react_1 = require("lucide-react");
|
|
10
|
+
const Button_1 = __importDefault(require("../Button/Button"));
|
|
11
|
+
const useCustomTheme_1 = require("../../Theme/useCustomTheme");
|
|
12
|
+
const FACT_SEPARATOR = " · ";
|
|
13
|
+
const VARIANT_BY_TONE = {
|
|
14
|
+
default: "outline",
|
|
15
|
+
primary: "solid",
|
|
16
|
+
danger: "outline",
|
|
17
|
+
};
|
|
18
|
+
const SCHEME_BY_TONE = {
|
|
19
|
+
default: "gray",
|
|
20
|
+
primary: "primary",
|
|
21
|
+
danger: "red",
|
|
22
|
+
};
|
|
23
|
+
const TOUCH_SX = { "@media (pointer: coarse)": { minHeight: "44px", minWidth: "44px" } };
|
|
24
|
+
const BulkSelectionBar = ({ selectedCount, facts, actions, onClearSelection, clearLabel = "Clear selection", countLabel, placement = "sticky", "aria-label": ariaLabel = "Selection actions", "data-testid": testId = "bulk-selection-bar", }) => {
|
|
25
|
+
const { colors } = (0, useCustomTheme_1.useCustomTheme)();
|
|
26
|
+
const isActive = selectedCount > 0;
|
|
27
|
+
const summary = countLabel !== null && countLabel !== void 0 ? countLabel : `${selectedCount} selected`;
|
|
28
|
+
const [announcement, setAnnouncement] = (0, react_1.useState)("");
|
|
29
|
+
(0, react_1.useEffect)(() => {
|
|
30
|
+
setAnnouncement(isActive ? summary : "");
|
|
31
|
+
}, [isActive, summary]);
|
|
32
|
+
if (!isActive)
|
|
33
|
+
return null;
|
|
34
|
+
const resolvedFacts = (facts !== null && facts !== void 0 ? facts : []).filter(Boolean);
|
|
35
|
+
const isSticky = placement === "sticky";
|
|
36
|
+
return ((0, jsx_runtime_1.jsxs)(react_2.Flex, { role: "region", "aria-label": ariaLabel, "data-testid": testId, align: "center", gap: 3, wrap: "wrap", px: 4, py: 2, w: "100%", borderRadius: "lg", borderWidth: "1px", borderColor: colors.boxborder[200], bg: colors.surfaceRaised, position: isSticky ? "sticky" : "relative", bottom: isSticky ? 0 : undefined, zIndex: isSticky ? 2 : undefined, children: [(0, jsx_runtime_1.jsx)(react_2.VisuallyHidden, { role: "status", "aria-live": "polite", "data-testid": "bulk-selection-announcement", children: announcement }), (0, jsx_runtime_1.jsxs)(react_2.Flex, { align: "baseline", gap: 1, flex: "1", minW: "180px", wrap: "wrap", children: [(0, jsx_runtime_1.jsx)(react_2.Text, { fontSize: "sm", fontWeight: 600, color: colors.text[800], "data-testid": "bulk-selection-count", children: summary }), resolvedFacts.length ? ((0, jsx_runtime_1.jsxs)(react_2.Text, { fontSize: "sm", color: colors.textMuted, "data-testid": "bulk-selection-facts", children: [FACT_SEPARATOR, resolvedFacts.join(FACT_SEPARATOR)] })) : null] }), (0, jsx_runtime_1.jsxs)(react_2.Flex, { align: "center", gap: 2, wrap: "wrap", children: [actions.map((action) => {
|
|
37
|
+
var _a;
|
|
38
|
+
const tone = (_a = action.tone) !== null && _a !== void 0 ? _a : "default";
|
|
39
|
+
const Icon = action.icon;
|
|
40
|
+
return ((0, jsx_runtime_1.jsx)(Button_1.default, { size: "sm", variant: VARIANT_BY_TONE[tone], colorScheme: SCHEME_BY_TONE[tone], onClick: action.onClick, isDisabled: action.isDisabled, isLoading: action.isLoading, loadingText: action.label, leftIcon: Icon ? (0, jsx_runtime_1.jsx)(Icon, { size: 16, "aria-hidden": "true" }) : undefined, sx: TOUCH_SX, "data-testid": `bulk-action-${action.id}`, "data-tone": tone, children: action.label }, action.id));
|
|
41
|
+
}), (0, jsx_runtime_1.jsx)(react_2.Box, { children: (0, jsx_runtime_1.jsx)(Button_1.default, { size: "sm", variant: "ghost", colorScheme: "gray", onClick: onClearSelection, leftIcon: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { size: 16, "aria-hidden": "true" }), sx: TOUCH_SX, "data-testid": "bulk-selection-clear", children: clearLabel }) })] })] }));
|
|
42
|
+
};
|
|
43
|
+
exports.default = BulkSelectionBar;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { ElementType } from "react";
|
|
2
|
+
/** Emphasis of a bulk action: "primary" is the affirmative one, "danger" the destructive one, "default" the rest. */
|
|
3
|
+
export type BulkActionTone = "default" | "primary" | "danger";
|
|
4
|
+
/** One button in the bar; actions render in array order. */
|
|
5
|
+
export interface BulkAction {
|
|
6
|
+
/** Stable key; also the suffix of the button's `data-testid` (`bulk-action-<id>`). */
|
|
7
|
+
id: string;
|
|
8
|
+
label: string;
|
|
9
|
+
onClick: () => void;
|
|
10
|
+
/** Defaults to "default". */
|
|
11
|
+
tone?: BulkActionTone;
|
|
12
|
+
/** Icon component (e.g. a lucide icon); rendered at 16px, decorative. */
|
|
13
|
+
icon?: ElementType;
|
|
14
|
+
isDisabled?: boolean;
|
|
15
|
+
/** Spinner on this action; the label stays as the loading text. */
|
|
16
|
+
isLoading?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/** Props for the selection bar shown while table rows are selected; it renders nothing at zero. */
|
|
19
|
+
export interface BulkSelectionBarProps {
|
|
20
|
+
/** Rows selected; the bar renders nothing at 0 and announces each change politely. */
|
|
21
|
+
selectedCount: number;
|
|
22
|
+
/** Short claims true of the whole selection, e.g. ["all leave", "no clashes"]; joined after the count with " · ". */
|
|
23
|
+
facts?: string[];
|
|
24
|
+
actions: BulkAction[];
|
|
25
|
+
onClearSelection: () => void;
|
|
26
|
+
/** Clear button text; defaults to "Clear selection". */
|
|
27
|
+
clearLabel?: string;
|
|
28
|
+
/** Replaces the default "{n} selected" text, and what is announced. */
|
|
29
|
+
countLabel?: string;
|
|
30
|
+
/** "sticky" (default) pins to the bottom of the nearest scroll container; "inline" stays in flow. */
|
|
31
|
+
placement?: "inline" | "sticky";
|
|
32
|
+
/** Accessible name of the region; defaults to "Selection actions". */
|
|
33
|
+
"aria-label"?: string;
|
|
34
|
+
"data-testid"?: string;
|
|
35
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.BulkSelectionBar = void 0;
|
|
7
|
+
var BulkSelectionBar_1 = require("./BulkSelectionBar");
|
|
8
|
+
Object.defineProperty(exports, "BulkSelectionBar", { enumerable: true, get: function () { return __importDefault(BulkSelectionBar_1).default; } });
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { CalendarGridProps } from "./CalendarGridProps";
|
|
2
|
+
declare const CalendarGrid: ({ view, date, events, onViewChange, onDateChange, onEventClick, renderDayCell, weekStartsOn, locale, isLoading, labels, }: CalendarGridProps) => import("react/jsx-runtime").JSX.Element;
|
|
3
|
+
export default CalendarGrid;
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
7
|
+
const react_1 = require("react");
|
|
8
|
+
const react_2 = require("@chakra-ui/react");
|
|
9
|
+
const lucide_react_1 = require("lucide-react");
|
|
10
|
+
const Button_1 = __importDefault(require("../Button/Button"));
|
|
11
|
+
const Skeletons_1 = __importDefault(require("../Skeletons/Skeletons"));
|
|
12
|
+
const useCustomTheme_1 = require("../../Theme/useCustomTheme");
|
|
13
|
+
const focusRing_styles_1 = require("../../Theme/chakra/focusRing.styles");
|
|
14
|
+
const tone_1 = require("../../Theme/tokens/builders/tone");
|
|
15
|
+
const calendarMath_1 = require("./calendarMath");
|
|
16
|
+
const plural = (n, one, many) => `${n} ${n === 1 ? one : many}`;
|
|
17
|
+
const DEFAULT_LABELS = {
|
|
18
|
+
today: "Today",
|
|
19
|
+
previous: "Previous",
|
|
20
|
+
next: "Next",
|
|
21
|
+
month: "Month",
|
|
22
|
+
week: "Week",
|
|
23
|
+
day: "Day",
|
|
24
|
+
more: (count) => `+${count} more`,
|
|
25
|
+
moreLabel: (count, date) => `${plural(count, "more event", "more events")} on ${date}`,
|
|
26
|
+
eventCount: (count) => plural(count, "event", "events"),
|
|
27
|
+
};
|
|
28
|
+
const VIEWS = ["month", "week", "day"];
|
|
29
|
+
const MAX_LANES = { month: 3, week: 6, day: Infinity };
|
|
30
|
+
const MIN_CELL_HEIGHT = { month: "6.5rem", week: "12rem", day: "16rem" };
|
|
31
|
+
const LANE_HEIGHT = "1.5rem";
|
|
32
|
+
const CHIP_HEIGHT = "1.375rem";
|
|
33
|
+
const CHIP_INSET = "2px";
|
|
34
|
+
const EventChip = ({ event, colors, surface, onEventClick, segment }) => {
|
|
35
|
+
var _a;
|
|
36
|
+
const tone = (0, tone_1.resolveSoftTone)(colors, (0, tone_1.toneScale)(colors, (_a = event.tone) !== null && _a !== void 0 ? _a : "neutral"), surface);
|
|
37
|
+
const left = (segment === null || segment === void 0 ? void 0 : segment.continuesBefore) ? "0px" : CHIP_INSET;
|
|
38
|
+
const right = (segment === null || segment === void 0 ? void 0 : segment.continuesAfter) ? "0px" : CHIP_INSET;
|
|
39
|
+
const span = segment ? segment.endCol - segment.startCol + 1 : 1;
|
|
40
|
+
const placement = segment
|
|
41
|
+
? {
|
|
42
|
+
position: "absolute",
|
|
43
|
+
zIndex: 1,
|
|
44
|
+
top: `calc(${segment.lane} * ${LANE_HEIGHT})`,
|
|
45
|
+
left,
|
|
46
|
+
width: `calc(${span * 100}% + ${span - 1}px - ${left} - ${right})`,
|
|
47
|
+
}
|
|
48
|
+
: { width: "100%" };
|
|
49
|
+
const common = {
|
|
50
|
+
...placement,
|
|
51
|
+
display: "flex",
|
|
52
|
+
alignItems: "center",
|
|
53
|
+
h: CHIP_HEIGHT,
|
|
54
|
+
px: 2,
|
|
55
|
+
fontSize: "0.75rem",
|
|
56
|
+
fontWeight: 500,
|
|
57
|
+
lineHeight: 1,
|
|
58
|
+
textAlign: "left",
|
|
59
|
+
whiteSpace: "nowrap",
|
|
60
|
+
overflow: "hidden",
|
|
61
|
+
textOverflow: "ellipsis",
|
|
62
|
+
textDecoration: "none",
|
|
63
|
+
cursor: "pointer",
|
|
64
|
+
bg: tone.bg,
|
|
65
|
+
color: tone.fg,
|
|
66
|
+
border: "1px solid",
|
|
67
|
+
borderColor: tone.border,
|
|
68
|
+
borderLeftRadius: (segment === null || segment === void 0 ? void 0 : segment.continuesBefore) ? 0 : "md",
|
|
69
|
+
borderRightRadius: (segment === null || segment === void 0 ? void 0 : segment.continuesAfter) ? 0 : "md",
|
|
70
|
+
title: event.label,
|
|
71
|
+
"data-event-id": event.id,
|
|
72
|
+
onClick: () => onEventClick === null || onEventClick === void 0 ? void 0 : onEventClick(event),
|
|
73
|
+
_hover: { bg: tone.hoverBg, borderColor: tone.hoverBorder },
|
|
74
|
+
_active: { bg: tone.activeBg, borderColor: tone.activeBorder },
|
|
75
|
+
sx: (0, focusRing_styles_1.focusVisibleStyles)(colors),
|
|
76
|
+
};
|
|
77
|
+
return event.href ? ((0, jsx_runtime_1.jsx)(react_2.Box, { as: "a", href: event.href, ...common, children: event.label })) : ((0, jsx_runtime_1.jsx)(react_2.Box, { as: "button", type: "button", ...common, children: event.label }));
|
|
78
|
+
};
|
|
79
|
+
const CalendarGrid = ({ view, date, events, onViewChange, onDateChange, onEventClick, renderDayCell, weekStartsOn = 0, locale, isLoading = false, labels, }) => {
|
|
80
|
+
const { colors } = (0, useCustomTheme_1.useCustomTheme)();
|
|
81
|
+
const text = { ...DEFAULT_LABELS, ...labels };
|
|
82
|
+
const today = new Date();
|
|
83
|
+
const anchor = (0, calendarMath_1.startOfDay)(date);
|
|
84
|
+
const anchorKey = (0, calendarMath_1.dayKey)(anchor);
|
|
85
|
+
const weeks = (0, calendarMath_1.visibleWeeks)(view, anchor, weekStartsOn);
|
|
86
|
+
const columns = weeks[0].length;
|
|
87
|
+
const visibleKeys = new Set(weeks.flat().map(calendarMath_1.dayKey));
|
|
88
|
+
const shownEvents = isLoading ? [] : events;
|
|
89
|
+
const maxLanes = MAX_LANES[view];
|
|
90
|
+
const [focusedKey, setFocusedKey] = (0, react_1.useState)(anchorKey);
|
|
91
|
+
const pendingFocus = (0, react_1.useRef)(false);
|
|
92
|
+
const cellRefs = (0, react_1.useRef)(new Map());
|
|
93
|
+
const activeKey = visibleKeys.has(focusedKey) ? focusedKey : visibleKeys.has(anchorKey) ? anchorKey : (0, calendarMath_1.dayKey)(weeks[0][0]);
|
|
94
|
+
(0, react_1.useEffect)(() => {
|
|
95
|
+
setFocusedKey(anchorKey);
|
|
96
|
+
}, [anchorKey]);
|
|
97
|
+
(0, react_1.useEffect)(() => {
|
|
98
|
+
if (!pendingFocus.current)
|
|
99
|
+
return;
|
|
100
|
+
const el = cellRefs.current.get(activeKey);
|
|
101
|
+
if (!el)
|
|
102
|
+
return;
|
|
103
|
+
pendingFocus.current = false;
|
|
104
|
+
el.focus();
|
|
105
|
+
});
|
|
106
|
+
const handleCellKeyDown = (e, cellDate) => {
|
|
107
|
+
if (e.target !== e.currentTarget)
|
|
108
|
+
return;
|
|
109
|
+
if ((0, calendarMath_1.isNavKey)(e.key)) {
|
|
110
|
+
e.preventDefault();
|
|
111
|
+
const next = (0, calendarMath_1.moveFocus)(view, cellDate, e.key, weekStartsOn);
|
|
112
|
+
const inRange = visibleKeys.has((0, calendarMath_1.dayKey)(next));
|
|
113
|
+
if (!inRange && !onDateChange)
|
|
114
|
+
return;
|
|
115
|
+
setFocusedKey((0, calendarMath_1.dayKey)(next));
|
|
116
|
+
pendingFocus.current = true;
|
|
117
|
+
if (!inRange)
|
|
118
|
+
onDateChange === null || onDateChange === void 0 ? void 0 : onDateChange(next);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if ((e.key === "Enter" || e.key === " ") && onDateChange) {
|
|
122
|
+
e.preventDefault();
|
|
123
|
+
onDateChange(cellDate);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
const drillIntoDay = (day) => {
|
|
127
|
+
onDateChange === null || onDateChange === void 0 ? void 0 : onDateChange(day);
|
|
128
|
+
onViewChange === null || onViewChange === void 0 ? void 0 : onViewChange("day");
|
|
129
|
+
};
|
|
130
|
+
const weekdayShort = (0, calendarMath_1.weekdayLabels)(locale, weekStartsOn, "short");
|
|
131
|
+
const weekdayLong = (0, calendarMath_1.weekdayLabels)(locale, weekStartsOn, "long");
|
|
132
|
+
const headerDays = view === "day" ? [(anchor.getDay() - weekStartsOn + 7) % 7] : weekdayShort.map((_, i) => i);
|
|
133
|
+
const title = (0, calendarMath_1.formatTitle)(view, anchor, locale, weekStartsOn);
|
|
134
|
+
const template = `repeat(${columns}, minmax(0, 1fr))`;
|
|
135
|
+
const border = colors.boxborder[200];
|
|
136
|
+
const renderCell = (day, col, weekIndex, layout) => {
|
|
137
|
+
const key = (0, calendarMath_1.dayKey)(day);
|
|
138
|
+
const isToday = (0, calendarMath_1.isSameDay)(day, today);
|
|
139
|
+
const inMonth = view !== "month" || day.getMonth() === anchor.getMonth();
|
|
140
|
+
const surface = (0, calendarMath_1.isWeekend)(day) ? colors.backgroundColor.secondary : colors.backgroundColor.main;
|
|
141
|
+
const dayEvents = (0, calendarMath_1.eventsOnDay)(shownEvents, day);
|
|
142
|
+
const hidden = layout.hiddenByCol[col];
|
|
143
|
+
const active = key === activeKey;
|
|
144
|
+
const label = `${(0, calendarMath_1.formatFullDate)(day, locale)}${dayEvents.length ? `, ${text.eventCount(dayEvents.length)}` : ""}`;
|
|
145
|
+
const ownSegments = layout.segments.filter((s) => s.startCol === col && s.lane < maxLanes);
|
|
146
|
+
let body = null;
|
|
147
|
+
if (isLoading) {
|
|
148
|
+
body = ((0, jsx_runtime_1.jsx)(react_2.Box, { mt: 1, "aria-hidden": true, children: (weekIndex * 7 + col) % 3 === 0 ? (0, jsx_runtime_1.jsx)(Skeletons_1.default, { type: "skeleton", height: CHIP_HEIGHT, borderRadius: "md" }) : null }));
|
|
149
|
+
}
|
|
150
|
+
else if (renderDayCell) {
|
|
151
|
+
body = (0, jsx_runtime_1.jsx)(react_2.Box, { mt: 1, children: renderDayCell(day, dayEvents) });
|
|
152
|
+
}
|
|
153
|
+
else if (view === "day") {
|
|
154
|
+
body = ((0, jsx_runtime_1.jsx)(react_2.Flex, { direction: "column", gap: 1, mt: 2, children: dayEvents.map((event) => ((0, jsx_runtime_1.jsx)(EventChip, { event: event, colors: colors, surface: surface, onEventClick: onEventClick }, event.id))) }));
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
body = ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(react_2.Box, { position: "relative", mt: 1, h: `calc(${layout.visibleLanes} * ${LANE_HEIGHT})`, children: ownSegments.map((s) => ((0, jsx_runtime_1.jsx)(EventChip, { event: s.event, colors: colors, surface: surface, onEventClick: onEventClick, segment: s }, s.event.id))) }), hidden > 0 ? ((0, jsx_runtime_1.jsx)(react_2.Box, { as: "button", type: "button", "aria-label": text.moreLabel(hidden, (0, calendarMath_1.formatFullDate)(day, locale)), onClick: () => drillIntoDay(day), px: 1, fontSize: "0.75rem", fontWeight: 600, color: colors.header[900], textAlign: "left", textDecoration: "underline", sx: (0, focusRing_styles_1.focusVisibleStyles)(colors), children: text.more(hidden) })) : null] }));
|
|
158
|
+
}
|
|
159
|
+
return ((0, jsx_runtime_1.jsxs)(react_2.Box, { role: "gridcell", ref: (el) => {
|
|
160
|
+
if (el)
|
|
161
|
+
cellRefs.current.set(key, el);
|
|
162
|
+
else
|
|
163
|
+
cellRefs.current.delete(key);
|
|
164
|
+
}, "data-date": key, "aria-label": label, "aria-current": isToday ? "date" : undefined, tabIndex: active ? 0 : -1, onFocus: () => setFocusedKey(key), onKeyDown: (e) => handleCellKeyDown(e, day), position: "relative", minH: MIN_CELL_HEIGHT[view], p: 1, bg: surface, borderRight: "1px solid", borderBottom: "1px solid", borderColor: border, outline: "none", _focusVisible: {
|
|
165
|
+
boxShadow: `inset 0 0 0 2px ${colors.focusRing.halo}, inset 0 0 0 4px ${colors.focusRing.ring}`,
|
|
166
|
+
}, children: [(0, jsx_runtime_1.jsx)(react_2.Text, { as: "span", display: "inline-flex", alignItems: "center", justifyContent: "center", minW: "1.5rem", h: "1.5rem", px: 1, borderRadius: "full", fontSize: "0.8125rem", fontWeight: isToday ? 700 : 500, color: isToday ? colors.accentText : inMonth ? colors.header[900] : colors.textMuted, boxShadow: isToday ? `inset 0 0 0 1.5px ${colors.accentText}` : undefined, "aria-hidden": true, children: day.getDate() }), body] }, key));
|
|
167
|
+
};
|
|
168
|
+
return ((0, jsx_runtime_1.jsxs)(react_2.Box, { children: [(0, jsx_runtime_1.jsxs)(react_2.Flex, { align: "center", justify: "space-between", wrap: "wrap", gap: 2, mb: 3, children: [(0, jsx_runtime_1.jsxs)(react_2.Flex, { align: "center", gap: 2, children: [onDateChange ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(Button_1.default, { label: text.today, size: "sm", variant: "outline", onClick: () => onDateChange((0, calendarMath_1.startOfDay)(new Date())) }), (0, jsx_runtime_1.jsx)(Button_1.default, { "aria-label": text.previous, size: "sm", variant: "ghost", leftIcon: (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronLeft, { size: 16, "aria-hidden": true }), onClick: () => onDateChange((0, calendarMath_1.shiftAnchor)(view, anchor, -1)) }), (0, jsx_runtime_1.jsx)(Button_1.default, { "aria-label": text.next, size: "sm", variant: "ghost", leftIcon: (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronRight, { size: 16, "aria-hidden": true }), onClick: () => onDateChange((0, calendarMath_1.shiftAnchor)(view, anchor, 1)) })] })) : null, (0, jsx_runtime_1.jsx)(react_2.Text, { as: "h2", fontSize: "1rem", fontWeight: 600, color: colors.header[900], children: title })] }), onViewChange ? ((0, jsx_runtime_1.jsx)(react_2.Flex, { role: "group", "aria-label": text[view], gap: 1, children: VIEWS.map((v) => ((0, jsx_runtime_1.jsx)(Button_1.default, { label: text[v], size: "sm", variant: v === view ? "solid" : "outline", "aria-pressed": v === view, onClick: () => onViewChange(v) }, v))) })) : null] }), (0, jsx_runtime_1.jsx)(react_2.Box, { overflowX: "auto", children: (0, jsx_runtime_1.jsxs)(react_2.Box, { role: "grid", "aria-label": title, "aria-busy": isLoading || undefined, "data-view": view, minW: view === "day" ? undefined : "42rem", borderTop: "1px solid", borderLeft: "1px solid", borderColor: border, borderRadius: "lg", overflow: "hidden", children: [(0, jsx_runtime_1.jsx)(react_2.Grid, { role: "row", templateColumns: template, children: headerDays.map((i) => ((0, jsx_runtime_1.jsx)(react_2.Box, { role: "columnheader", "aria-label": weekdayLong[i], px: 2, py: 1, fontSize: "0.75rem", fontWeight: 600, color: colors.textMuted, bg: colors.backgroundColor.main, borderRight: "1px solid", borderBottom: "1px solid", borderColor: border, children: weekdayShort[i] }, i))) }), weeks.map((week, weekIndex) => {
|
|
169
|
+
const layout = (0, calendarMath_1.layoutWeek)(week, shownEvents, maxLanes);
|
|
170
|
+
return ((0, jsx_runtime_1.jsx)(react_2.Grid, { role: "row", templateColumns: template, children: week.map((day, col) => renderCell(day, col, weekIndex, layout)) }, (0, calendarMath_1.dayKey)(week[0])));
|
|
171
|
+
})] }) })] }));
|
|
172
|
+
};
|
|
173
|
+
exports.default = CalendarGrid;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { Tone } from "../../Theme/tokens/builders/tone";
|
|
3
|
+
/** Which span the grid shows: a month of weeks, one week, or a single day. */
|
|
4
|
+
export type CalendarGridView = "month" | "week" | "day";
|
|
5
|
+
/** A date-granularity event; time of day is ignored. */
|
|
6
|
+
export type CalendarEvent = {
|
|
7
|
+
id: string;
|
|
8
|
+
/** First day covered; only the date part is read. */
|
|
9
|
+
start: Date;
|
|
10
|
+
/** Last day covered, inclusive. Omit for a single-day event; a value before `start` is swapped. */
|
|
11
|
+
end?: Date;
|
|
12
|
+
label: string;
|
|
13
|
+
/** Status tint of the chip, same tones as Tag. Defaults to "neutral". */
|
|
14
|
+
tone?: Tone;
|
|
15
|
+
/** Renders the chip as a link; without it the chip is a button that fires `onEventClick`. */
|
|
16
|
+
href?: string;
|
|
17
|
+
};
|
|
18
|
+
/** Visible text and accessible names; every key falls back to English. */
|
|
19
|
+
export type CalendarGridLabels = {
|
|
20
|
+
today: string;
|
|
21
|
+
previous: string;
|
|
22
|
+
next: string;
|
|
23
|
+
month: string;
|
|
24
|
+
week: string;
|
|
25
|
+
day: string;
|
|
26
|
+
/** Receives the number of hidden events, e.g. "+3 more". */
|
|
27
|
+
more: (count: number) => string;
|
|
28
|
+
/** Accessible name of the "more" button, e.g. "3 more events on Tuesday, 14 October 2026". */
|
|
29
|
+
moreLabel: (count: number, date: string) => string;
|
|
30
|
+
/** Appended to a day cell's accessible name, e.g. "3 events". */
|
|
31
|
+
eventCount: (count: number) => string;
|
|
32
|
+
};
|
|
33
|
+
/** Props for the month/week/day event grid; fully controlled through `view` and `date`. */
|
|
34
|
+
export interface CalendarGridProps {
|
|
35
|
+
view: CalendarGridView;
|
|
36
|
+
/** Anchor date: any day inside the month, week or day being shown. */
|
|
37
|
+
date: Date;
|
|
38
|
+
events: CalendarEvent[];
|
|
39
|
+
/** Called by the toolbar's view switcher and by a cell's "+N more" button (with "day"). */
|
|
40
|
+
onViewChange?: (view: CalendarGridView) => void;
|
|
41
|
+
/** Called when the anchor should move: toolbar, arrow keys leaving the visible range, Enter on a cell, "+N more". */
|
|
42
|
+
onDateChange?: (date: Date) => void;
|
|
43
|
+
/** Called when an event chip is activated; link chips still navigate to `href`. */
|
|
44
|
+
onEventClick?: (event: CalendarEvent) => void;
|
|
45
|
+
/** Replaces a cell's event chips (and the multi-day spanning) with your own content, under the day number. */
|
|
46
|
+
renderDayCell?: (date: Date, events: CalendarEvent[]) => ReactNode;
|
|
47
|
+
/** First column of the week; defaults to 0 (Sunday). */
|
|
48
|
+
weekStartsOn?: 0 | 1;
|
|
49
|
+
/** BCP 47 tag for month, weekday and date names via Intl; defaults to the runtime locale. */
|
|
50
|
+
locale?: string;
|
|
51
|
+
/** Keeps the grid's shape and swaps the events for skeleton bars; sets aria-busy. */
|
|
52
|
+
isLoading?: boolean;
|
|
53
|
+
labels?: Partial<CalendarGridLabels>;
|
|
54
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export type CalendarView = "month" | "week" | "day";
|
|
2
|
+
export type WeekStart = 0 | 1;
|
|
3
|
+
export type SpanEvent = {
|
|
4
|
+
id: string;
|
|
5
|
+
start: Date;
|
|
6
|
+
end?: Date;
|
|
7
|
+
label: string;
|
|
8
|
+
};
|
|
9
|
+
export type WeekSegment<E extends SpanEvent> = {
|
|
10
|
+
event: E;
|
|
11
|
+
startCol: number;
|
|
12
|
+
endCol: number;
|
|
13
|
+
lane: number;
|
|
14
|
+
continuesBefore: boolean;
|
|
15
|
+
continuesAfter: boolean;
|
|
16
|
+
};
|
|
17
|
+
export type WeekLayout<E extends SpanEvent> = {
|
|
18
|
+
segments: WeekSegment<E>[];
|
|
19
|
+
visibleLanes: number;
|
|
20
|
+
hiddenByCol: number[];
|
|
21
|
+
};
|
|
22
|
+
export declare const startOfDay: (d: Date) => Date;
|
|
23
|
+
export declare const addDays: (d: Date, n: number) => Date;
|
|
24
|
+
export declare const addMonths: (d: Date, n: number) => Date;
|
|
25
|
+
export declare const isSameDay: (a: Date, b: Date) => boolean;
|
|
26
|
+
export declare const dayKey: (d: Date) => string;
|
|
27
|
+
export declare const isWeekend: (d: Date) => boolean;
|
|
28
|
+
/** Whole calendar days from `a` to `b`, counted on date components so a DST day is still one day. */
|
|
29
|
+
export declare const diffDays: (a: Date, b: Date) => number;
|
|
30
|
+
export declare const startOfWeek: (d: Date, weekStartsOn: WeekStart) => Date;
|
|
31
|
+
export declare const weekDays: (d: Date, weekStartsOn: WeekStart) => Date[];
|
|
32
|
+
/** Whole weeks that cover the month of `d`, padded with the neighbouring months' days. */
|
|
33
|
+
export declare const monthWeeks: (d: Date, weekStartsOn: WeekStart) => Date[][];
|
|
34
|
+
export declare const visibleWeeks: (view: CalendarView, d: Date, weekStartsOn: WeekStart) => Date[][];
|
|
35
|
+
export declare const shiftAnchor: (view: CalendarView, d: Date, direction: 1 | -1) => Date;
|
|
36
|
+
export declare const eventRange: (e: Pick<SpanEvent, "start" | "end">) => {
|
|
37
|
+
start: Date;
|
|
38
|
+
end: Date;
|
|
39
|
+
};
|
|
40
|
+
export declare const eventsOnDay: <E extends SpanEvent>(events: readonly E[], day: Date) => E[];
|
|
41
|
+
/** Lays events out in lanes across one week row; lanes past `maxLanes` are counted per column, not drawn. */
|
|
42
|
+
export declare const layoutWeek: <E extends SpanEvent>(days: readonly Date[], events: readonly E[], maxLanes: number) => WeekLayout<E>;
|
|
43
|
+
export type NavKey = "ArrowLeft" | "ArrowRight" | "ArrowUp" | "ArrowDown" | "Home" | "End" | "PageUp" | "PageDown";
|
|
44
|
+
export declare const NAV_KEYS: readonly string[];
|
|
45
|
+
export declare const isNavKey: (key: string) => key is NavKey;
|
|
46
|
+
export declare const moveFocus: (view: CalendarView, d: Date, key: NavKey, weekStartsOn: WeekStart) => Date;
|
|
47
|
+
export declare const weekdayLabels: (locale: string | undefined, weekStartsOn: WeekStart, style?: "short" | "long") => string[];
|
|
48
|
+
export declare const formatTitle: (view: CalendarView, d: Date, locale: string | undefined, weekStartsOn: WeekStart) => string;
|
|
49
|
+
export declare const formatFullDate: (d: Date, locale: string | undefined) => string;
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatFullDate = exports.formatTitle = exports.weekdayLabels = exports.moveFocus = exports.isNavKey = exports.NAV_KEYS = exports.layoutWeek = exports.eventsOnDay = exports.eventRange = exports.shiftAnchor = exports.visibleWeeks = exports.monthWeeks = exports.weekDays = exports.startOfWeek = exports.diffDays = exports.isWeekend = exports.dayKey = exports.isSameDay = exports.addMonths = exports.addDays = exports.startOfDay = void 0;
|
|
4
|
+
const MS_PER_DAY = 86400000;
|
|
5
|
+
const startOfDay = (d) => new Date(d.getFullYear(), d.getMonth(), d.getDate());
|
|
6
|
+
exports.startOfDay = startOfDay;
|
|
7
|
+
const addDays = (d, n) => new Date(d.getFullYear(), d.getMonth(), d.getDate() + n);
|
|
8
|
+
exports.addDays = addDays;
|
|
9
|
+
const addMonths = (d, n) => {
|
|
10
|
+
const target = new Date(d.getFullYear(), d.getMonth() + n, 1);
|
|
11
|
+
const lastDay = new Date(target.getFullYear(), target.getMonth() + 1, 0).getDate();
|
|
12
|
+
return new Date(target.getFullYear(), target.getMonth(), Math.min(d.getDate(), lastDay));
|
|
13
|
+
};
|
|
14
|
+
exports.addMonths = addMonths;
|
|
15
|
+
const isSameDay = (a, b) => a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
|
|
16
|
+
exports.isSameDay = isSameDay;
|
|
17
|
+
const dayKey = (d) => `${String(d.getFullYear()).padStart(4, "0")}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
18
|
+
exports.dayKey = dayKey;
|
|
19
|
+
const isWeekend = (d) => d.getDay() === 0 || d.getDay() === 6;
|
|
20
|
+
exports.isWeekend = isWeekend;
|
|
21
|
+
/** Whole calendar days from `a` to `b`, counted on date components so a DST day is still one day. */
|
|
22
|
+
const diffDays = (a, b) => Math.round((Date.UTC(b.getFullYear(), b.getMonth(), b.getDate()) - Date.UTC(a.getFullYear(), a.getMonth(), a.getDate())) /
|
|
23
|
+
MS_PER_DAY);
|
|
24
|
+
exports.diffDays = diffDays;
|
|
25
|
+
const startOfWeek = (d, weekStartsOn) => (0, exports.addDays)(d, -((d.getDay() - weekStartsOn + 7) % 7));
|
|
26
|
+
exports.startOfWeek = startOfWeek;
|
|
27
|
+
const weekDays = (d, weekStartsOn) => {
|
|
28
|
+
const first = (0, exports.startOfWeek)(d, weekStartsOn);
|
|
29
|
+
return Array.from({ length: 7 }, (_, i) => (0, exports.addDays)(first, i));
|
|
30
|
+
};
|
|
31
|
+
exports.weekDays = weekDays;
|
|
32
|
+
/** Whole weeks that cover the month of `d`, padded with the neighbouring months' days. */
|
|
33
|
+
const monthWeeks = (d, weekStartsOn) => {
|
|
34
|
+
const first = new Date(d.getFullYear(), d.getMonth(), 1);
|
|
35
|
+
const last = new Date(d.getFullYear(), d.getMonth() + 1, 0);
|
|
36
|
+
const gridStart = (0, exports.startOfWeek)(first, weekStartsOn);
|
|
37
|
+
const weekCount = Math.ceil(((0, exports.diffDays)(gridStart, last) + 1) / 7);
|
|
38
|
+
return Array.from({ length: weekCount }, (_, w) => Array.from({ length: 7 }, (_, i) => (0, exports.addDays)(gridStart, w * 7 + i)));
|
|
39
|
+
};
|
|
40
|
+
exports.monthWeeks = monthWeeks;
|
|
41
|
+
const visibleWeeks = (view, d, weekStartsOn) => {
|
|
42
|
+
if (view === "month")
|
|
43
|
+
return (0, exports.monthWeeks)(d, weekStartsOn);
|
|
44
|
+
if (view === "week")
|
|
45
|
+
return [(0, exports.weekDays)(d, weekStartsOn)];
|
|
46
|
+
return [[(0, exports.startOfDay)(d)]];
|
|
47
|
+
};
|
|
48
|
+
exports.visibleWeeks = visibleWeeks;
|
|
49
|
+
const shiftAnchor = (view, d, direction) => {
|
|
50
|
+
if (view === "month")
|
|
51
|
+
return (0, exports.addMonths)(d, direction);
|
|
52
|
+
return (0, exports.addDays)(d, view === "week" ? 7 * direction : direction);
|
|
53
|
+
};
|
|
54
|
+
exports.shiftAnchor = shiftAnchor;
|
|
55
|
+
const eventRange = (e) => {
|
|
56
|
+
const start = (0, exports.startOfDay)(e.start);
|
|
57
|
+
const end = e.end ? (0, exports.startOfDay)(e.end) : start;
|
|
58
|
+
return end < start ? { start: end, end: start } : { start, end };
|
|
59
|
+
};
|
|
60
|
+
exports.eventRange = eventRange;
|
|
61
|
+
const eventsOnDay = (events, day) => {
|
|
62
|
+
const d = (0, exports.startOfDay)(day);
|
|
63
|
+
return events.filter((e) => {
|
|
64
|
+
const r = (0, exports.eventRange)(e);
|
|
65
|
+
return r.start <= d && d <= r.end;
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
exports.eventsOnDay = eventsOnDay;
|
|
69
|
+
/** Lays events out in lanes across one week row; lanes past `maxLanes` are counted per column, not drawn. */
|
|
70
|
+
const layoutWeek = (days, events, maxLanes) => {
|
|
71
|
+
const first = days[0];
|
|
72
|
+
const lastCol = days.length - 1;
|
|
73
|
+
const candidates = events
|
|
74
|
+
.map((event) => {
|
|
75
|
+
const r = (0, exports.eventRange)(event);
|
|
76
|
+
const rawStart = (0, exports.diffDays)(first, r.start);
|
|
77
|
+
const rawEnd = (0, exports.diffDays)(first, r.end);
|
|
78
|
+
if (rawEnd < 0 || rawStart > lastCol)
|
|
79
|
+
return null;
|
|
80
|
+
return {
|
|
81
|
+
event,
|
|
82
|
+
rawStart,
|
|
83
|
+
length: rawEnd - rawStart,
|
|
84
|
+
startCol: Math.max(0, rawStart),
|
|
85
|
+
endCol: Math.min(lastCol, rawEnd),
|
|
86
|
+
continuesBefore: rawStart < 0,
|
|
87
|
+
continuesAfter: rawEnd > lastCol,
|
|
88
|
+
};
|
|
89
|
+
})
|
|
90
|
+
.filter((c) => c !== null)
|
|
91
|
+
.sort((a, b) => a.rawStart - b.rawStart ||
|
|
92
|
+
b.length - a.length ||
|
|
93
|
+
a.event.label.localeCompare(b.event.label) ||
|
|
94
|
+
a.event.id.localeCompare(b.event.id));
|
|
95
|
+
const occupied = [];
|
|
96
|
+
const segments = [];
|
|
97
|
+
for (const c of candidates) {
|
|
98
|
+
let lane = occupied.findIndex((row) => row.slice(c.startCol, c.endCol + 1).every((taken) => !taken));
|
|
99
|
+
if (lane === -1) {
|
|
100
|
+
lane = occupied.length;
|
|
101
|
+
occupied.push(new Array(days.length).fill(false));
|
|
102
|
+
}
|
|
103
|
+
for (let col = c.startCol; col <= c.endCol; col += 1)
|
|
104
|
+
occupied[lane][col] = true;
|
|
105
|
+
segments.push({
|
|
106
|
+
event: c.event,
|
|
107
|
+
startCol: c.startCol,
|
|
108
|
+
endCol: c.endCol,
|
|
109
|
+
lane,
|
|
110
|
+
continuesBefore: c.continuesBefore,
|
|
111
|
+
continuesAfter: c.continuesAfter,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
const hiddenByCol = new Array(days.length).fill(0);
|
|
115
|
+
for (const s of segments) {
|
|
116
|
+
if (s.lane < maxLanes)
|
|
117
|
+
continue;
|
|
118
|
+
for (let col = s.startCol; col <= s.endCol; col += 1)
|
|
119
|
+
hiddenByCol[col] += 1;
|
|
120
|
+
}
|
|
121
|
+
return { segments, visibleLanes: Math.min(occupied.length, maxLanes), hiddenByCol };
|
|
122
|
+
};
|
|
123
|
+
exports.layoutWeek = layoutWeek;
|
|
124
|
+
exports.NAV_KEYS = [
|
|
125
|
+
"ArrowLeft",
|
|
126
|
+
"ArrowRight",
|
|
127
|
+
"ArrowUp",
|
|
128
|
+
"ArrowDown",
|
|
129
|
+
"Home",
|
|
130
|
+
"End",
|
|
131
|
+
"PageUp",
|
|
132
|
+
"PageDown",
|
|
133
|
+
];
|
|
134
|
+
const isNavKey = (key) => exports.NAV_KEYS.includes(key);
|
|
135
|
+
exports.isNavKey = isNavKey;
|
|
136
|
+
const moveFocus = (view, d, key, weekStartsOn) => {
|
|
137
|
+
const columns = view === "day" ? 1 : 7;
|
|
138
|
+
switch (key) {
|
|
139
|
+
case "ArrowLeft":
|
|
140
|
+
return (0, exports.addDays)(d, -1);
|
|
141
|
+
case "ArrowRight":
|
|
142
|
+
return (0, exports.addDays)(d, 1);
|
|
143
|
+
case "ArrowUp":
|
|
144
|
+
return (0, exports.addDays)(d, -columns);
|
|
145
|
+
case "ArrowDown":
|
|
146
|
+
return (0, exports.addDays)(d, columns);
|
|
147
|
+
case "Home":
|
|
148
|
+
return view === "day" ? d : (0, exports.startOfWeek)(d, weekStartsOn);
|
|
149
|
+
case "End":
|
|
150
|
+
return view === "day" ? d : (0, exports.addDays)((0, exports.startOfWeek)(d, weekStartsOn), 6);
|
|
151
|
+
case "PageUp":
|
|
152
|
+
return (0, exports.shiftAnchor)(view, d, -1);
|
|
153
|
+
default:
|
|
154
|
+
return (0, exports.shiftAnchor)(view, d, 1);
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
exports.moveFocus = moveFocus;
|
|
158
|
+
const probe = (weekday) => new Date(2024, 0, 7 + weekday);
|
|
159
|
+
const weekdayLabels = (locale, weekStartsOn, style = "short") => {
|
|
160
|
+
const fmt = new Intl.DateTimeFormat(locale, { weekday: style });
|
|
161
|
+
return Array.from({ length: 7 }, (_, i) => fmt.format(probe((i + weekStartsOn) % 7)));
|
|
162
|
+
};
|
|
163
|
+
exports.weekdayLabels = weekdayLabels;
|
|
164
|
+
const formatTitle = (view, d, locale, weekStartsOn) => {
|
|
165
|
+
if (view === "month")
|
|
166
|
+
return new Intl.DateTimeFormat(locale, { month: "long", year: "numeric" }).format(d);
|
|
167
|
+
if (view === "day")
|
|
168
|
+
return new Intl.DateTimeFormat(locale, { weekday: "long", day: "numeric", month: "long", year: "numeric" }).format(d);
|
|
169
|
+
const days = (0, exports.weekDays)(d, weekStartsOn);
|
|
170
|
+
const opts = { day: "numeric", month: "short", year: "numeric" };
|
|
171
|
+
const fmt = new Intl.DateTimeFormat(locale, opts);
|
|
172
|
+
return typeof fmt.formatRange === "function"
|
|
173
|
+
? fmt.formatRange(days[0], days[6])
|
|
174
|
+
: `${fmt.format(days[0])} - ${fmt.format(days[6])}`;
|
|
175
|
+
};
|
|
176
|
+
exports.formatTitle = formatTitle;
|
|
177
|
+
const formatFullDate = (d, locale) => new Intl.DateTimeFormat(locale, { weekday: "long", day: "numeric", month: "long", year: "numeric" }).format(d);
|
|
178
|
+
exports.formatFullDate = formatFullDate;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { CircularProgressProps } from "./CircularProgressProps";
|
|
2
|
+
/** Ring-shaped determinate progress with a centred label — for a linear bar use ProgressBar. */
|
|
3
|
+
declare const CircularProgress: ({ value, max, size, thickness, tone, label, "aria-label": ariaLabel, "data-testid": testId, }: CircularProgressProps) => import("react/jsx-runtime").JSX.Element;
|
|
4
|
+
export default CircularProgress;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
4
|
+
const react_1 = require("@chakra-ui/react");
|
|
5
|
+
const useCustomTheme_1 = require("../../Theme/useCustomTheme");
|
|
6
|
+
const tone_1 = require("../../Theme/tokens/builders/tone");
|
|
7
|
+
const motion_1 = require("../../Theme/tokens/primitives/motion");
|
|
8
|
+
const motion_2 = require("../../Theme/chakra/motion");
|
|
9
|
+
const arc_1 = require("./arc");
|
|
10
|
+
const Circle = (0, react_1.chakra)("circle");
|
|
11
|
+
const LABEL_FONT = {
|
|
12
|
+
sm: "0.625rem",
|
|
13
|
+
md: "0.75rem",
|
|
14
|
+
lg: "1rem",
|
|
15
|
+
};
|
|
16
|
+
const scaleFor = (colors, tone) => tone === "info" ? colors.primary : (0, tone_1.toneScale)(colors, tone);
|
|
17
|
+
/** Ring-shaped determinate progress with a centred label — for a linear bar use ProgressBar. */
|
|
18
|
+
const CircularProgress = ({ value, max = 100, size = "md", thickness, tone = "info", label, "aria-label": ariaLabel, "data-testid": testId, }) => {
|
|
19
|
+
const { colors } = (0, useCustomTheme_1.useCustomTheme)();
|
|
20
|
+
const diameter = arc_1.SIZE_PX[size];
|
|
21
|
+
const stroke = thickness !== null && thickness !== void 0 ? thickness : arc_1.THICKNESS_PX[size];
|
|
22
|
+
const { radius, circumference, fraction, clamped, safeMax, dashOffset } = (0, arc_1.arcGeometry)(diameter, stroke, value, max);
|
|
23
|
+
const ink = (0, tone_1.resolveSoftTone)(colors, scaleFor(colors, tone), colors.background[50]).fg;
|
|
24
|
+
const center = diameter / 2;
|
|
25
|
+
const valueText = typeof label === "string" || typeof label === "number" ? String(label) : undefined;
|
|
26
|
+
return ((0, jsx_runtime_1.jsxs)(react_1.Box, { role: "progressbar", "aria-label": ariaLabel, "aria-valuenow": clamped, "aria-valuemin": 0, "aria-valuemax": safeMax, "aria-valuetext": valueText, "data-testid": testId, position: "relative", display: "inline-flex", flexShrink: 0, boxSize: `${diameter}px`, children: [(0, jsx_runtime_1.jsxs)("svg", { width: diameter, height: diameter, viewBox: `0 0 ${diameter} ${diameter}`, "aria-hidden": true, focusable: "false", children: [(0, jsx_runtime_1.jsx)(Circle, { cx: center, cy: center, r: radius, fill: "none", sx: { stroke: colors.backgroundColor.subtle, strokeWidth: stroke } }), (0, jsx_runtime_1.jsx)(Circle, { cx: center, cy: center, r: radius, fill: "none", "data-testid": testId ? `${testId}-arc` : undefined, sx: {
|
|
27
|
+
stroke: ink,
|
|
28
|
+
strokeWidth: stroke,
|
|
29
|
+
strokeLinecap: "round",
|
|
30
|
+
strokeDasharray: circumference,
|
|
31
|
+
strokeDashoffset: dashOffset,
|
|
32
|
+
strokeOpacity: fraction > 0 ? 1 : 0,
|
|
33
|
+
transform: "rotate(-90deg)",
|
|
34
|
+
transformOrigin: "50% 50%",
|
|
35
|
+
transition: (0, motion_1.transitionFor)(["stroke-dashoffset"]),
|
|
36
|
+
...(0, motion_2.reducedMotionSx)(),
|
|
37
|
+
} })] }), label !== undefined && label !== null && ((0, jsx_runtime_1.jsx)(react_1.Flex, { position: "absolute", inset: 0, align: "center", justify: "center", fontSize: LABEL_FONT[size], fontWeight: 600, lineHeight: 1, color: colors.header[900], sx: { fontVariantNumeric: "tabular-nums" }, children: label }))] }));
|
|
38
|
+
};
|
|
39
|
+
exports.default = CircularProgress;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { Tone } from "../../Theme/tokens/builders/tone";
|
|
3
|
+
/** Diameter preset: sm 32px, md 48px, lg 72px. */
|
|
4
|
+
export type CircularProgressSize = "sm" | "md" | "lg";
|
|
5
|
+
/** Props for the SVG ring CircularProgress; the arc starts at 12 o'clock and fills clockwise. */
|
|
6
|
+
export interface CircularProgressProps {
|
|
7
|
+
/** Current progress, clamped to 0..max. */
|
|
8
|
+
value: number;
|
|
9
|
+
/** Upper bound of the range; default 100. */
|
|
10
|
+
max?: number;
|
|
11
|
+
/** Diameter preset (32 / 48 / 72px); default "md". */
|
|
12
|
+
size?: CircularProgressSize;
|
|
13
|
+
/** Ring stroke in px; defaults by size (4 / 5 / 7). */
|
|
14
|
+
thickness?: number;
|
|
15
|
+
/** Fill colour; default "info", which paints the brand primary rather than blue. */
|
|
16
|
+
tone?: Tone;
|
|
17
|
+
/** Centred content such as "7/10"; a string or number is also spoken as aria-valuetext. */
|
|
18
|
+
label?: ReactNode;
|
|
19
|
+
/** Required accessible name; the ring has no visible caption of its own. */
|
|
20
|
+
"aria-label": string;
|
|
21
|
+
"data-testid"?: string;
|
|
22
|
+
}
|