@amiable-dev/docusaurus-plugin-stentorosaur 0.20.0 → 0.21.1
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/lib/context/StatusDataProvider.d.ts +101 -0
- package/lib/context/StatusDataProvider.d.ts.map +1 -0
- package/lib/context/StatusDataProvider.js +266 -0
- package/lib/options.d.ts.map +1 -1
- package/lib/options.js +1 -0
- package/lib/theme/StatusBadge/index.d.ts +54 -0
- package/lib/theme/StatusBadge/index.d.ts.map +1 -0
- package/lib/theme/StatusBadge/index.js +63 -0
- package/lib/theme/StatusBadge/styles.module.css +120 -0
- package/lib/theme/StatusPage/index.d.ts.map +1 -1
- package/lib/theme/StatusPage/index.js +12 -2
- package/lib/theme/StatusPage/styles.module.css +83 -0
- package/lib/theme/SystemCard/index.d.ts +98 -0
- package/lib/theme/SystemCard/index.d.ts.map +1 -0
- package/lib/theme/SystemCard/index.js +125 -0
- package/lib/theme/SystemCard/styles.module.css +208 -0
- package/lib/theme/SystemCardGroup/index.d.ts +47 -0
- package/lib/theme/SystemCardGroup/index.d.ts.map +1 -0
- package/lib/theme/SystemCardGroup/index.js +113 -0
- package/lib/theme/SystemCardGroup/styles.module.css +180 -0
- package/lib/theme/UptimeBar/index.d.ts +69 -0
- package/lib/theme/UptimeBar/index.d.ts.map +1 -0
- package/lib/theme/UptimeBar/index.js +177 -0
- package/lib/theme/UptimeBar/styles.module.css +217 -0
- package/lib/types.d.ts +15 -0
- package/lib/types.d.ts.map +1 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +1 -1
|
@@ -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;
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ADR-004: UptimeBar Styles
|
|
3
|
+
*
|
|
4
|
+
* 90-day horizontal uptime bar visualization.
|
|
5
|
+
* Uses CSS variables for theming and flexbox for layout.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/* CSS Variables for status colors */
|
|
9
|
+
:root {
|
|
10
|
+
--status-operational: #22c55e;
|
|
11
|
+
--status-degraded: #eab308;
|
|
12
|
+
--status-outage: #ef4444;
|
|
13
|
+
--status-maintenance: #3b82f6;
|
|
14
|
+
--status-no-data: #9ca3af;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/* Container */
|
|
18
|
+
.uptimeBarContainer {
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
gap: 0.5rem;
|
|
22
|
+
width: 100%;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* Uptime percentage display */
|
|
26
|
+
.uptimePercentage {
|
|
27
|
+
display: flex;
|
|
28
|
+
align-items: baseline;
|
|
29
|
+
gap: 0.25rem;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.percentageValue {
|
|
33
|
+
font-size: 1.25rem;
|
|
34
|
+
font-weight: 600;
|
|
35
|
+
color: var(--ifm-color-content);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.percentageLabel {
|
|
39
|
+
font-size: 0.875rem;
|
|
40
|
+
color: var(--ifm-color-content-secondary, #666);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* Main bar container */
|
|
44
|
+
.uptimeBar {
|
|
45
|
+
display: flex;
|
|
46
|
+
flex-direction: row;
|
|
47
|
+
align-items: stretch;
|
|
48
|
+
width: 100%;
|
|
49
|
+
border-radius: 4px;
|
|
50
|
+
overflow: hidden;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* Individual day cell */
|
|
54
|
+
.dayCell {
|
|
55
|
+
flex: 1;
|
|
56
|
+
min-width: 2px;
|
|
57
|
+
border-radius: 2px;
|
|
58
|
+
cursor: pointer;
|
|
59
|
+
transition: transform 0.15s ease, opacity 0.15s ease;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.dayCell:hover {
|
|
63
|
+
transform: scaleY(1.1);
|
|
64
|
+
z-index: 1;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.dayCell:focus {
|
|
68
|
+
outline: 2px solid var(--ifm-color-primary);
|
|
69
|
+
outline-offset: 1px;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.dayCell:focus:not(:focus-visible) {
|
|
73
|
+
outline: none;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* Status colors */
|
|
77
|
+
.statusOperational {
|
|
78
|
+
background-color: var(--status-operational);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.statusDegraded {
|
|
82
|
+
background-color: var(--status-degraded);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.statusOutage {
|
|
86
|
+
background-color: var(--status-outage);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.statusNoData {
|
|
90
|
+
background-color: var(--status-no-data);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* Date labels */
|
|
94
|
+
.dateLabels {
|
|
95
|
+
display: flex;
|
|
96
|
+
justify-content: space-between;
|
|
97
|
+
font-size: 0.75rem;
|
|
98
|
+
color: var(--ifm-color-content-secondary, #666);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.dateLabelStart,
|
|
102
|
+
.dateLabelEnd {
|
|
103
|
+
white-space: nowrap;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* Loading state */
|
|
107
|
+
.loading .skeleton {
|
|
108
|
+
background: linear-gradient(
|
|
109
|
+
90deg,
|
|
110
|
+
var(--ifm-color-emphasis-200) 25%,
|
|
111
|
+
var(--ifm-color-emphasis-300) 50%,
|
|
112
|
+
var(--ifm-color-emphasis-200) 75%
|
|
113
|
+
);
|
|
114
|
+
background-size: 200% 100%;
|
|
115
|
+
animation: shimmer 1.5s infinite;
|
|
116
|
+
border-radius: 4px;
|
|
117
|
+
width: 100%;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
@keyframes shimmer {
|
|
121
|
+
0% {
|
|
122
|
+
background-position: 200% 0;
|
|
123
|
+
}
|
|
124
|
+
100% {
|
|
125
|
+
background-position: -200% 0;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/* Error state */
|
|
130
|
+
.error {
|
|
131
|
+
display: flex;
|
|
132
|
+
align-items: center;
|
|
133
|
+
gap: 0.75rem;
|
|
134
|
+
padding: 0.75rem;
|
|
135
|
+
background-color: rgba(239, 68, 68, 0.1);
|
|
136
|
+
border-radius: 4px;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.errorMessage {
|
|
140
|
+
color: var(--status-outage);
|
|
141
|
+
font-size: 0.875rem;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.retryButton {
|
|
145
|
+
padding: 0.375rem 0.75rem;
|
|
146
|
+
font-size: 0.875rem;
|
|
147
|
+
background-color: var(--ifm-color-primary);
|
|
148
|
+
color: white;
|
|
149
|
+
border: none;
|
|
150
|
+
border-radius: 4px;
|
|
151
|
+
cursor: pointer;
|
|
152
|
+
transition: background-color 0.15s ease;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.retryButton:hover {
|
|
156
|
+
background-color: var(--ifm-color-primary-dark);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/* Empty state */
|
|
160
|
+
.empty {
|
|
161
|
+
display: flex;
|
|
162
|
+
align-items: center;
|
|
163
|
+
justify-content: center;
|
|
164
|
+
padding: 1rem;
|
|
165
|
+
background-color: var(--ifm-color-emphasis-100);
|
|
166
|
+
border-radius: 4px;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.emptyMessage {
|
|
170
|
+
color: var(--ifm-color-content-secondary);
|
|
171
|
+
font-size: 0.875rem;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/* Dark mode adjustments */
|
|
175
|
+
[data-theme='dark'] .statusOperational {
|
|
176
|
+
background-color: #16a34a;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
[data-theme='dark'] .statusDegraded {
|
|
180
|
+
background-color: #ca8a04;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
[data-theme='dark'] .statusOutage {
|
|
184
|
+
background-color: #dc2626;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
[data-theme='dark'] .error {
|
|
188
|
+
background-color: rgba(239, 68, 68, 0.2);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/* Reduced motion support */
|
|
192
|
+
@media (prefers-reduced-motion: reduce) {
|
|
193
|
+
.dayCell {
|
|
194
|
+
transition: none;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.loading .skeleton {
|
|
198
|
+
animation: none;
|
|
199
|
+
background: var(--ifm-color-emphasis-200);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/* Mobile responsiveness */
|
|
204
|
+
@media (max-width: 600px) {
|
|
205
|
+
.uptimePercentage {
|
|
206
|
+
flex-direction: column;
|
|
207
|
+
gap: 0;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.percentageValue {
|
|
211
|
+
font-size: 1.125rem;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.dateLabels {
|
|
215
|
+
font-size: 0.625rem;
|
|
216
|
+
}
|
|
217
|
+
}
|
package/lib/types.d.ts
CHANGED
|
@@ -401,6 +401,15 @@ export interface PluginOptions {
|
|
|
401
401
|
* - 'upptime': Upptime-style structured view with configurable sections
|
|
402
402
|
*/
|
|
403
403
|
statusView?: 'default' | 'upptime';
|
|
404
|
+
/**
|
|
405
|
+
* Status card layout style for individual system cards (ADR-004)
|
|
406
|
+
* - 'minimal': New simplified cards with 90-day uptime bars (default)
|
|
407
|
+
* - 'detailed': Original detailed status cards with more information
|
|
408
|
+
*
|
|
409
|
+
* @default 'minimal'
|
|
410
|
+
* @since 0.21.0
|
|
411
|
+
*/
|
|
412
|
+
statusCardLayout?: 'minimal' | 'detailed';
|
|
404
413
|
/**
|
|
405
414
|
* Configuration for Upptime-style status page (only used when statusView is 'upptime')
|
|
406
415
|
*/
|
|
@@ -472,6 +481,12 @@ export interface StatusData {
|
|
|
472
481
|
* Theme components use this to determine how to fetch live data
|
|
473
482
|
*/
|
|
474
483
|
dataSource?: DataSource;
|
|
484
|
+
/**
|
|
485
|
+
* Status card layout style (ADR-004)
|
|
486
|
+
* - 'minimal': Simplified cards with 90-day uptime bars (default)
|
|
487
|
+
* - 'detailed': Original detailed cards with more information
|
|
488
|
+
*/
|
|
489
|
+
statusCardLayout?: 'minimal' | 'detailed';
|
|
475
490
|
}
|
|
476
491
|
export interface UptimeStatusSection {
|
|
477
492
|
id: 'active-incidents' | 'live-status' | 'charts' | 'scheduled-maintenance' | 'past-maintenance' | 'past-incidents';
|