@amiable-dev/docusaurus-plugin-stentorosaur 0.19.0 → 0.21.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.
@@ -0,0 +1,113 @@
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.SystemCardGroup = SystemCardGroup;
7
+ const jsx_runtime_1 = require("react/jsx-runtime");
8
+ /**
9
+ * Copyright (c) Your Organization
10
+ *
11
+ * This source code is licensed under the MIT license found in the
12
+ * LICENSE file in the root directory of this source tree.
13
+ */
14
+ /**
15
+ * ADR-004: SystemCardGroup Component
16
+ *
17
+ * Container for grouping multiple SystemCards with collapsible behavior.
18
+ * Shows aggregate status (worst-of) for the group.
19
+ *
20
+ * @see docs/adrs/ADR-004-simplified-status-card-ux.md
21
+ */
22
+ const react_1 = require("react");
23
+ const styles_module_css_1 = __importDefault(require("./styles.module.css"));
24
+ /**
25
+ * Status priority for aggregation (higher = worse)
26
+ */
27
+ const STATUS_PRIORITY = {
28
+ up: 0,
29
+ maintenance: 1,
30
+ degraded: 2,
31
+ down: 3,
32
+ };
33
+ /**
34
+ * Status labels for group header
35
+ */
36
+ const GROUP_STATUS_LABELS = {
37
+ up: 'All Operational',
38
+ degraded: 'Partial Issues',
39
+ down: 'Service Outage',
40
+ maintenance: 'Maintenance',
41
+ };
42
+ /**
43
+ * Status class mapping
44
+ */
45
+ const GROUP_STATUS_CLASS_MAP = {
46
+ up: styles_module_css_1.default.groupStatusUp,
47
+ degraded: styles_module_css_1.default.groupStatusDegraded,
48
+ down: styles_module_css_1.default.groupStatusDown,
49
+ maintenance: styles_module_css_1.default.groupStatusMaintenance,
50
+ };
51
+ /**
52
+ * Extract status from SystemCard children
53
+ */
54
+ function getWorstStatus(children) {
55
+ let worstStatus = 'up';
56
+ let worstPriority = STATUS_PRIORITY.up;
57
+ react_1.Children.forEach(children, (child) => {
58
+ if ((0, react_1.isValidElement)(child) && child.props.status) {
59
+ const childStatus = child.props.status;
60
+ const priority = STATUS_PRIORITY[childStatus] ?? 0;
61
+ if (priority > worstPriority) {
62
+ worstPriority = priority;
63
+ worstStatus = childStatus;
64
+ }
65
+ }
66
+ });
67
+ return worstStatus;
68
+ }
69
+ /**
70
+ * Count valid children
71
+ */
72
+ function countChildren(children) {
73
+ let count = 0;
74
+ react_1.Children.forEach(children, (child) => {
75
+ if ((0, react_1.isValidElement)(child)) {
76
+ count++;
77
+ }
78
+ });
79
+ return count;
80
+ }
81
+ /**
82
+ * SystemCardGroup Component
83
+ *
84
+ * Groups multiple SystemCards together with a collapsible header.
85
+ * Displays aggregate status based on worst child status.
86
+ */
87
+ function SystemCardGroup({ name, displayName, defaultCollapsed = false, status: explicitStatus, headingLevel = 2, children, className, }) {
88
+ const [collapsed, setCollapsed] = (0, react_1.useState)(defaultCollapsed);
89
+ // Derive status from children or use explicit override
90
+ const aggregatedStatus = (0, react_1.useMemo)(() => {
91
+ if (explicitStatus)
92
+ return explicitStatus;
93
+ return getWorstStatus(children);
94
+ }, [explicitStatus, children]);
95
+ // Count children
96
+ const childCount = (0, react_1.useMemo)(() => countChildren(children), [children]);
97
+ const handleToggle = (0, react_1.useCallback)(() => {
98
+ setCollapsed((prev) => !prev);
99
+ }, []);
100
+ const handleKeyDown = (0, react_1.useCallback)((event) => {
101
+ if (event.key === 'Enter' || event.key === ' ') {
102
+ event.preventDefault();
103
+ handleToggle();
104
+ }
105
+ }, [handleToggle]);
106
+ const Heading = `h${headingLevel}`;
107
+ const statusLabel = GROUP_STATUS_LABELS[aggregatedStatus];
108
+ const statusClass = GROUP_STATUS_CLASS_MAP[aggregatedStatus];
109
+ const serviceWord = childCount === 1 ? 'service' : 'services';
110
+ return ((0, jsx_runtime_1.jsxs)("section", { className: `${styles_module_css_1.default.systemCardGroup} ${collapsed ? styles_module_css_1.default.collapsed : ''} ${statusClass} ${className || ''}`, "data-group": name, children: [(0, jsx_runtime_1.jsxs)("div", { className: styles_module_css_1.default.groupHeader, children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: styles_module_css_1.default.toggleButton, onClick: handleToggle, onKeyDown: handleKeyDown, "aria-expanded": !collapsed, "aria-label": `Toggle ${displayName} group`, children: (0, jsx_runtime_1.jsx)("span", { className: `${styles_module_css_1.default.expandIcon} ${!collapsed ? styles_module_css_1.default.expandIconOpen : ''}`, children: "\u25BC" }) }), (0, jsx_runtime_1.jsxs)("div", { className: styles_module_css_1.default.groupInfo, children: [(0, jsx_runtime_1.jsx)(Heading, { className: styles_module_css_1.default.groupTitle, children: displayName }), (0, jsx_runtime_1.jsxs)("span", { className: styles_module_css_1.default.groupMeta, children: [(0, jsx_runtime_1.jsxs)("span", { className: styles_module_css_1.default.serviceCount, children: [childCount, " ", serviceWord] }), (0, jsx_runtime_1.jsx)("span", { className: styles_module_css_1.default.statusDot }), (0, jsx_runtime_1.jsx)("span", { className: styles_module_css_1.default.statusLabel, children: statusLabel })] })] })] }), (0, jsx_runtime_1.jsx)("div", { className: `${styles_module_css_1.default.groupContent} ${collapsed ? styles_module_css_1.default.contentHidden : ''}`, "aria-hidden": collapsed, children: children })] }));
111
+ }
112
+ // Default export
113
+ exports.default = SystemCardGroup;
@@ -0,0 +1,180 @@
1
+ /**
2
+ * ADR-004: SystemCardGroup Styles
3
+ *
4
+ * Container for grouping multiple SystemCards.
5
+ */
6
+
7
+ /* Group container */
8
+ .systemCardGroup {
9
+ margin-bottom: 1.5rem;
10
+ border: 1px solid var(--ifm-color-emphasis-200);
11
+ border-radius: 8px;
12
+ overflow: hidden;
13
+ }
14
+
15
+ /* Group header */
16
+ .groupHeader {
17
+ display: flex;
18
+ align-items: center;
19
+ gap: 0.75rem;
20
+ padding: 0.75rem 1rem;
21
+ background: var(--ifm-color-emphasis-100);
22
+ border-bottom: 1px solid var(--ifm-color-emphasis-200);
23
+ }
24
+
25
+ .collapsed .groupHeader {
26
+ border-bottom: none;
27
+ }
28
+
29
+ /* Toggle button */
30
+ .toggleButton {
31
+ display: flex;
32
+ align-items: center;
33
+ justify-content: center;
34
+ width: 28px;
35
+ height: 28px;
36
+ background: var(--ifm-color-emphasis-200);
37
+ border: none;
38
+ border-radius: 4px;
39
+ cursor: pointer;
40
+ transition: background-color 0.15s ease;
41
+ }
42
+
43
+ .toggleButton:hover {
44
+ background: var(--ifm-color-emphasis-300);
45
+ }
46
+
47
+ .toggleButton:focus-visible {
48
+ outline: 2px solid var(--ifm-color-primary);
49
+ outline-offset: 2px;
50
+ }
51
+
52
+ .expandIcon {
53
+ font-size: 0.625rem;
54
+ color: var(--ifm-color-content-secondary);
55
+ transition: transform 0.2s ease;
56
+ transform: rotate(-90deg);
57
+ }
58
+
59
+ .expandIconOpen {
60
+ transform: rotate(0deg);
61
+ }
62
+
63
+ /* Group info */
64
+ .groupInfo {
65
+ display: flex;
66
+ flex-direction: column;
67
+ gap: 0.125rem;
68
+ flex: 1;
69
+ }
70
+
71
+ .groupTitle {
72
+ margin: 0;
73
+ font-size: 1rem;
74
+ font-weight: 600;
75
+ color: var(--ifm-color-content);
76
+ }
77
+
78
+ .groupMeta {
79
+ display: flex;
80
+ align-items: center;
81
+ gap: 0.5rem;
82
+ font-size: 0.75rem;
83
+ color: var(--ifm-color-content-secondary);
84
+ }
85
+
86
+ .serviceCount {
87
+ color: var(--ifm-color-content-secondary);
88
+ }
89
+
90
+ .statusDot {
91
+ width: 6px;
92
+ height: 6px;
93
+ border-radius: 50%;
94
+ background: var(--status-operational, #22c55e);
95
+ }
96
+
97
+ .groupStatusDegraded .statusDot {
98
+ background: var(--status-degraded, #eab308);
99
+ }
100
+
101
+ .groupStatusDown .statusDot {
102
+ background: var(--status-outage, #ef4444);
103
+ }
104
+
105
+ .groupStatusMaintenance .statusDot {
106
+ background: var(--status-maintenance, #3b82f6);
107
+ }
108
+
109
+ .statusLabel {
110
+ font-weight: 500;
111
+ }
112
+
113
+ /* Status colors for labels */
114
+ .groupStatusUp .statusLabel {
115
+ color: var(--status-operational, #22c55e);
116
+ }
117
+
118
+ .groupStatusDegraded .statusLabel {
119
+ color: var(--status-degraded, #eab308);
120
+ }
121
+
122
+ .groupStatusDown .statusLabel {
123
+ color: var(--status-outage, #ef4444);
124
+ }
125
+
126
+ .groupStatusMaintenance .statusLabel {
127
+ color: var(--status-maintenance, #3b82f6);
128
+ }
129
+
130
+ /* Group content */
131
+ .groupContent {
132
+ display: flex;
133
+ flex-direction: column;
134
+ gap: 0.75rem;
135
+ padding: 1rem;
136
+ transition: max-height 0.3s ease, opacity 0.2s ease, padding 0.3s ease;
137
+ }
138
+
139
+ .contentHidden {
140
+ max-height: 0;
141
+ opacity: 0;
142
+ padding: 0 1rem;
143
+ overflow: hidden;
144
+ }
145
+
146
+ /* Dark mode */
147
+ [data-theme='dark'] .groupHeader {
148
+ background: var(--ifm-color-emphasis-200);
149
+ }
150
+
151
+ [data-theme='dark'] .toggleButton {
152
+ background: var(--ifm-color-emphasis-300);
153
+ }
154
+
155
+ [data-theme='dark'] .toggleButton:hover {
156
+ background: var(--ifm-color-emphasis-400);
157
+ }
158
+
159
+ /* Reduced motion */
160
+ @media (prefers-reduced-motion: reduce) {
161
+ .expandIcon,
162
+ .groupContent {
163
+ transition: none;
164
+ }
165
+
166
+ .contentHidden {
167
+ display: none;
168
+ }
169
+ }
170
+
171
+ /* Mobile responsiveness */
172
+ @media (max-width: 600px) {
173
+ .groupHeader {
174
+ padding: 0.5rem 0.75rem;
175
+ }
176
+
177
+ .groupContent {
178
+ padding: 0.75rem;
179
+ }
180
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Copyright (c) Your Organization
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+ /**
8
+ * ADR-004: UptimeBar Component
9
+ *
10
+ * 90-day horizontal uptime bar visualization.
11
+ * Displays daily uptime status as colored cells.
12
+ *
13
+ * @see docs/adrs/ADR-004-simplified-status-card-ux.md
14
+ */
15
+ import React from 'react';
16
+ import { type DayStatus } from '../../context/StatusDataProvider';
17
+ /**
18
+ * Custom thresholds for status determination
19
+ */
20
+ export interface UptimeThresholds {
21
+ /** Uptime >= this is operational (default: 99) */
22
+ operational: number;
23
+ /** Uptime >= this is degraded, < is outage (default: 95) */
24
+ degraded: number;
25
+ }
26
+ /**
27
+ * Props for UptimeBar component
28
+ */
29
+ export interface UptimeBarProps {
30
+ /** Service name for data lookup (uses context) */
31
+ serviceName: string;
32
+ /** Override data for testing/Storybook */
33
+ data?: DayStatus[];
34
+ /** Number of days to display (default: 90) */
35
+ days?: number;
36
+ /** Height of the bar in pixels (default: 34) */
37
+ height?: number;
38
+ /** Gap between bars in pixels (default: 2) */
39
+ gap?: number;
40
+ /** Show uptime percentage text (default: true) */
41
+ showPercentage?: boolean;
42
+ /** Show date labels (default: true) */
43
+ showDateLabels?: boolean;
44
+ /** Customizable thresholds */
45
+ thresholds?: UptimeThresholds;
46
+ /** Loading state */
47
+ loading?: boolean;
48
+ /** Error state */
49
+ error?: Error | null;
50
+ /** Retry callback */
51
+ onRetry?: () => void;
52
+ /** Accessibility label */
53
+ ariaLabel?: string;
54
+ /** Interaction callbacks */
55
+ onDayClick?: (date: string, status: DayStatus) => void;
56
+ onDayHover?: (date: string, status: DayStatus | null) => void;
57
+ /** Optional additional className */
58
+ className?: string;
59
+ }
60
+ /**
61
+ * UptimeBar Component
62
+ *
63
+ * Displays a horizontal bar of daily uptime status cells.
64
+ * Each cell represents one day, colored by uptime percentage.
65
+ */
66
+ export declare function UptimeBar({ serviceName, data: providedData, days, height, gap, showPercentage, showDateLabels, thresholds, loading, error, onRetry, ariaLabel, onDayClick, onDayHover, className, }: UptimeBarProps): React.ReactElement;
67
+ export default UptimeBar;
68
+ export type { DayStatus };
69
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/theme/UptimeBar/index.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;;;GAOG;AAEH,OAAO,KAAqE,MAAM,OAAO,CAAC;AAC1F,OAAO,EAAiB,KAAK,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAGjF;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,kDAAkD;IAClD,WAAW,EAAE,MAAM,CAAC;IACpB,4DAA4D;IAC5D,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,kDAAkD;IAClD,WAAW,EAAE,MAAM,CAAC;IACpB,0CAA0C;IAC1C,IAAI,CAAC,EAAE,SAAS,EAAE,CAAC;IACnB,8CAA8C;IAC9C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gDAAgD;IAChD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8CAA8C;IAC9C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,kDAAkD;IAClD,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,uCAAuC;IACvC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,8BAA8B;IAC9B,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,oBAAoB;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,kBAAkB;IAClB,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;IACrB,qBAAqB;IACrB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,0BAA0B;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4BAA4B;IAC5B,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,KAAK,IAAI,CAAC;IACvD,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,GAAG,IAAI,KAAK,IAAI,CAAC;IAC9D,oCAAoC;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAqED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,EACxB,WAAW,EACX,IAAI,EAAE,YAAY,EAClB,IAAS,EACT,MAAW,EACX,GAAO,EACP,cAAqB,EACrB,cAAqB,EACrB,UAA+B,EAC/B,OAAe,EACf,KAAK,EACL,OAAO,EACP,SAAS,EACT,UAAU,EACV,UAAU,EACV,SAAS,GACV,EAAE,cAAc,GAAG,KAAK,CAAC,YAAY,CA8LrC;AAGD,eAAe,SAAS,CAAC;AAGzB,YAAY,EAAE,SAAS,EAAE,CAAC"}
@@ -0,0 +1,177 @@
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.UptimeBar = UptimeBar;
7
+ const jsx_runtime_1 = require("react/jsx-runtime");
8
+ /**
9
+ * Copyright (c) Your Organization
10
+ *
11
+ * This source code is licensed under the MIT license found in the
12
+ * LICENSE file in the root directory of this source tree.
13
+ */
14
+ /**
15
+ * ADR-004: UptimeBar Component
16
+ *
17
+ * 90-day horizontal uptime bar visualization.
18
+ * Displays daily uptime status as colored cells.
19
+ *
20
+ * @see docs/adrs/ADR-004-simplified-status-card-ux.md
21
+ */
22
+ const react_1 = require("react");
23
+ const StatusDataProvider_1 = require("../../context/StatusDataProvider");
24
+ const styles_module_css_1 = __importDefault(require("./styles.module.css"));
25
+ /**
26
+ * Default thresholds per ADR-004
27
+ */
28
+ const DEFAULT_THRESHOLDS = {
29
+ operational: 99,
30
+ degraded: 95,
31
+ };
32
+ /**
33
+ * Get status class based on uptime and thresholds
34
+ */
35
+ function getStatusClass(uptimePercent, thresholds, originalStatus) {
36
+ // If original status is no-data, keep it
37
+ if (originalStatus === 'no-data') {
38
+ return styles_module_css_1.default.statusNoData;
39
+ }
40
+ // Apply threshold-based status
41
+ if (uptimePercent >= thresholds.operational) {
42
+ return styles_module_css_1.default.statusOperational;
43
+ }
44
+ if (uptimePercent >= thresholds.degraded) {
45
+ return styles_module_css_1.default.statusDegraded;
46
+ }
47
+ return styles_module_css_1.default.statusOutage;
48
+ }
49
+ /**
50
+ * Calculate average uptime percentage
51
+ */
52
+ function calculateAverageUptime(days) {
53
+ if (days.length === 0)
54
+ return 0;
55
+ const sum = days.reduce((acc, day) => acc + day.uptimePercent, 0);
56
+ return sum / days.length;
57
+ }
58
+ /**
59
+ * Generate summary for screen readers
60
+ */
61
+ function generateAriaLabel(serviceName, days, averageUptime) {
62
+ const operationalDays = days.filter((d) => d.status === 'operational').length;
63
+ const degradedDays = days.filter((d) => d.status === 'degraded').length;
64
+ const outageDays = days.filter((d) => d.status === 'outage').length;
65
+ let summary = `${days.length}-day uptime for ${serviceName}: ${averageUptime.toFixed(1)}%.`;
66
+ if (operationalDays > 0) {
67
+ summary += ` ${operationalDays} days operational.`;
68
+ }
69
+ if (degradedDays > 0) {
70
+ summary += ` ${degradedDays} days degraded.`;
71
+ }
72
+ if (outageDays > 0) {
73
+ summary += ` ${outageDays} days with outage.`;
74
+ }
75
+ return summary;
76
+ }
77
+ /**
78
+ * UptimeBar Component
79
+ *
80
+ * Displays a horizontal bar of daily uptime status cells.
81
+ * Each cell represents one day, colored by uptime percentage.
82
+ */
83
+ function UptimeBar({ serviceName, data: providedData, days = 90, height = 34, gap = 2, showPercentage = true, showDateLabels = true, thresholds = DEFAULT_THRESHOLDS, loading = false, error, onRetry, ariaLabel, onDayClick, onDayHover, className, }) {
84
+ // Get data from context or use provided data
85
+ const { getMerged90Days } = (0, StatusDataProvider_1.useStatusData)();
86
+ // Roving tabindex state - track which day has focus
87
+ const [focusedIndex, setFocusedIndex] = (0, react_1.useState)(0);
88
+ const dayCellRefs = (0, react_1.useRef)([]);
89
+ const dayData = (0, react_1.useMemo)(() => {
90
+ if (providedData) {
91
+ return providedData.slice(0, days);
92
+ }
93
+ return getMerged90Days(serviceName).slice(0, days);
94
+ }, [providedData, serviceName, days, getMerged90Days]);
95
+ // Calculate average uptime
96
+ const averageUptime = (0, react_1.useMemo)(() => calculateAverageUptime(dayData), [dayData]);
97
+ // Generate aria label
98
+ const computedAriaLabel = (0, react_1.useMemo)(() => {
99
+ if (ariaLabel)
100
+ return ariaLabel;
101
+ return generateAriaLabel(serviceName, dayData, averageUptime);
102
+ }, [ariaLabel, serviceName, dayData, averageUptime]);
103
+ // Event handlers
104
+ const handleDayClick = (0, react_1.useCallback)((day, index) => {
105
+ setFocusedIndex(index);
106
+ if (onDayClick) {
107
+ onDayClick(day.date, day);
108
+ }
109
+ }, [onDayClick]);
110
+ // Roving tabindex keyboard navigation
111
+ const handleBarKeyDown = (0, react_1.useCallback)((event) => {
112
+ const displayDays = [...dayData].reverse();
113
+ const maxIndex = displayDays.length - 1;
114
+ switch (event.key) {
115
+ case 'ArrowRight':
116
+ event.preventDefault();
117
+ setFocusedIndex((prev) => {
118
+ const newIndex = Math.min(prev + 1, maxIndex);
119
+ dayCellRefs.current[newIndex]?.focus();
120
+ return newIndex;
121
+ });
122
+ break;
123
+ case 'ArrowLeft':
124
+ event.preventDefault();
125
+ setFocusedIndex((prev) => {
126
+ const newIndex = Math.max(prev - 1, 0);
127
+ dayCellRefs.current[newIndex]?.focus();
128
+ return newIndex;
129
+ });
130
+ break;
131
+ case 'Home':
132
+ event.preventDefault();
133
+ setFocusedIndex(0);
134
+ dayCellRefs.current[0]?.focus();
135
+ break;
136
+ case 'End':
137
+ event.preventDefault();
138
+ setFocusedIndex(maxIndex);
139
+ dayCellRefs.current[maxIndex]?.focus();
140
+ break;
141
+ }
142
+ }, [dayData]);
143
+ const handleDayKeyDown = (0, react_1.useCallback)((event, day, index) => {
144
+ if (event.key === 'Enter' || event.key === ' ') {
145
+ event.preventDefault();
146
+ if (onDayClick) {
147
+ onDayClick(day.date, day);
148
+ }
149
+ }
150
+ }, [onDayClick]);
151
+ const handleDayHover = (0, react_1.useCallback)((day) => {
152
+ if (onDayHover && day) {
153
+ onDayHover(day.date, day);
154
+ }
155
+ else if (onDayHover && !day) {
156
+ // Pass the last hovered date (or empty string) with null
157
+ onDayHover('', null);
158
+ }
159
+ }, [onDayHover]);
160
+ // Loading state
161
+ if (loading) {
162
+ return ((0, jsx_runtime_1.jsx)("div", { className: `${styles_module_css_1.default.uptimeBarContainer} ${styles_module_css_1.default.loading} ${className || ''}`, children: (0, jsx_runtime_1.jsx)("div", { className: styles_module_css_1.default.skeleton, style: { height: `${height}px` } }) }));
163
+ }
164
+ // Error state
165
+ if (error) {
166
+ return ((0, jsx_runtime_1.jsxs)("div", { className: `${styles_module_css_1.default.uptimeBarContainer} ${styles_module_css_1.default.error} ${className || ''}`, children: [(0, jsx_runtime_1.jsx)("span", { className: styles_module_css_1.default.errorMessage, children: "Failed to load uptime data" }), onRetry && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: styles_module_css_1.default.retryButton, onClick: onRetry, "aria-label": "Retry loading uptime data", children: "Retry" }))] }));
167
+ }
168
+ // Empty state
169
+ if (dayData.length === 0) {
170
+ return ((0, jsx_runtime_1.jsx)("div", { className: `${styles_module_css_1.default.uptimeBarContainer} ${styles_module_css_1.default.empty} ${className || ''}`, role: "img", "aria-label": `No uptime data available for ${serviceName}`, children: (0, jsx_runtime_1.jsx)("span", { className: styles_module_css_1.default.emptyMessage, children: "No data available" }) }));
171
+ }
172
+ // Reverse for display (oldest first, newest last)
173
+ const displayDays = [...dayData].reverse();
174
+ return ((0, jsx_runtime_1.jsxs)("div", { className: `${styles_module_css_1.default.uptimeBarContainer} ${className || ''}`, children: [showPercentage && ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_css_1.default.uptimePercentage, children: [(0, jsx_runtime_1.jsxs)("span", { className: styles_module_css_1.default.percentageValue, children: [averageUptime.toFixed(2), "%"] }), (0, jsx_runtime_1.jsx)("span", { className: styles_module_css_1.default.percentageLabel, children: "uptime" })] })), (0, jsx_runtime_1.jsx)("div", { className: styles_module_css_1.default.uptimeBar, role: "group", "aria-label": computedAriaLabel, style: { height: `${height}px`, gap: `${gap}px` }, onKeyDown: handleBarKeyDown, children: displayDays.map((day, index) => ((0, jsx_runtime_1.jsx)("button", { ref: (el) => { dayCellRefs.current[index] = el; }, type: "button", className: `${styles_module_css_1.default.dayCell} ${getStatusClass(day.uptimePercent, thresholds, day.status)}`, tabIndex: index === focusedIndex ? 0 : -1, "aria-label": `${day.date}: ${day.uptimePercent.toFixed(1)}% uptime, ${day.incidents} incidents`, onClick: () => handleDayClick(day, index), onKeyDown: (e) => handleDayKeyDown(e, day, index), onMouseEnter: () => handleDayHover(day), onMouseLeave: () => handleDayHover(null), onFocus: () => setFocusedIndex(index) }, day.date))) }), showDateLabels && ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_css_1.default.dateLabels, children: [(0, jsx_runtime_1.jsxs)("span", { className: styles_module_css_1.default.dateLabelStart, children: [days, " days ago"] }), (0, jsx_runtime_1.jsx)("span", { className: styles_module_css_1.default.dateLabelEnd, children: "Today" })] }))] }));
175
+ }
176
+ // Default export for easier imports
177
+ exports.default = UptimeBar;