sid-ui 0.1.2 → 0.1.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sid-ui",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "packageManager": "bun@1.4.2",
5
5
  "description": "React components, tokens, and theme used by Joined products.",
6
6
  "repository": {
@@ -0,0 +1,96 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+
5
+ /** How a day went: fine, degraded, down, or nothing measured yet. */
6
+ export type UptimeTone = "up" | "degraded" | "down" | "none";
7
+
8
+ export type UptimeDay = {
9
+ /** Shown as the tip's title, e.g. "Oct 11, 2026". */
10
+ label: string;
11
+ tone: UptimeTone;
12
+ /** One line per fact in the tip, e.g. "99.98% uptime", "Slow search". */
13
+ details?: string[];
14
+ };
15
+
16
+ const TONE_LABEL: Record<UptimeTone, string> = {
17
+ up: "No downtime",
18
+ degraded: "Degraded",
19
+ down: "Outage",
20
+ none: "No data",
21
+ };
22
+
23
+ /** Keeps the tip inside the bar near either end. */
24
+ const EDGE_SHARE = 0.15;
25
+
26
+ /**
27
+ * A status-page history: one slim bar per day, oldest on the left, coloured by how the
28
+ * day went. Each bar reads out its date and details on hover or focus.
29
+ */
30
+ export function UptimeBar({
31
+ days,
32
+ label,
33
+ startLabel,
34
+ endLabel,
35
+ summary,
36
+ }: {
37
+ days: UptimeDay[];
38
+ /** Accessible name for the row of days, e.g. "API uptime, last 90 days". */
39
+ label: string;
40
+ /** Under the left end, e.g. "90 days ago". */
41
+ startLabel?: string;
42
+ /** Under the right end, e.g. "Today". */
43
+ endLabel?: string;
44
+ /** Centered under the bars, e.g. "99.98% uptime". */
45
+ summary?: string;
46
+ }) {
47
+ const [active, setActive] = useState<number | null>(null);
48
+ const align = (index: number) => {
49
+ const share = days.length > 1 ? index / (days.length - 1) : 0.5;
50
+ if (share < EDGE_SHARE) return "start";
51
+ if (share > 1 - EDGE_SHARE) return "end";
52
+ return "center";
53
+ };
54
+
55
+ return (
56
+ <div className="os-uptime">
57
+ <div className="os-uptime-days" role="group" aria-label={label}>
58
+ {days.map((day, index) => {
59
+ const details = day.details?.length ? day.details : [TONE_LABEL[day.tone]];
60
+ return (
61
+ <span
62
+ key={`${day.label}-${index}`}
63
+ className="os-uptime-day"
64
+ data-tone={day.tone}
65
+ tabIndex={0}
66
+ role="img"
67
+ aria-label={`${day.label}: ${details.join(", ")}`}
68
+ onPointerEnter={() => setActive(index)}
69
+ onPointerLeave={() => setActive(null)}
70
+ onFocus={() => setActive(index)}
71
+ onBlur={() => setActive(null)}
72
+ >
73
+ {active === index ? (
74
+ <span className="os-chart-tip" data-align={align(index)} role="presentation">
75
+ <span className="os-chart-tip-title">{day.label}</span>
76
+ {details.map((detail) => (
77
+ <span key={detail} className="os-chart-tip-row">
78
+ {detail}
79
+ </span>
80
+ ))}
81
+ </span>
82
+ ) : null}
83
+ </span>
84
+ );
85
+ })}
86
+ </div>
87
+ {startLabel || endLabel || summary ? (
88
+ <div className="os-uptime-axis">
89
+ <span>{startLabel}</span>
90
+ <span className="os-uptime-summary">{summary}</span>
91
+ <span>{endLabel}</span>
92
+ </div>
93
+ ) : null}
94
+ </div>
95
+ );
96
+ }
@@ -184,6 +184,8 @@ export { FunnelChart } from "./FunnelChart";
184
184
  export type { FunnelStage } from "./FunnelChart";
185
185
  export { HeatmapCalendar } from "./HeatmapCalendar";
186
186
  export type { HeatmapDay } from "./HeatmapCalendar";
187
+ export { UptimeBar } from "./UptimeBar";
188
+ export type { UptimeDay, UptimeTone } from "./UptimeBar";
187
189
  export { ChartLegend, CHART_TONES } from "./chartParts";
188
190
  export type { ChartTone, LegendItem } from "./chartParts";
189
191
  export { compactNumber } from "./chartMath";
@@ -657,3 +657,66 @@
657
657
  transition: none;
658
658
  }
659
659
  }
660
+
661
+ /* ── UptimeBar ────────────────────────────────────────────────────── */
662
+
663
+ .os-uptime {
664
+ display: flex;
665
+ min-width: 0;
666
+ flex-direction: column;
667
+ gap: var(--spacing-2);
668
+ }
669
+
670
+ .os-uptime-days {
671
+ display: flex;
672
+ height: var(--spacing-8);
673
+ gap: var(--spacing-0-5);
674
+ }
675
+
676
+ .os-uptime-day {
677
+ position: relative;
678
+ flex: 1 1 0;
679
+ min-width: 0;
680
+ border-radius: var(--radius-inner);
681
+ background: var(--color-data-categorical-green);
682
+ outline: none;
683
+ }
684
+ .os-uptime-day[data-tone="degraded"] {
685
+ background: var(--color-data-categorical-orange);
686
+ }
687
+ .os-uptime-day[data-tone="down"] {
688
+ background: var(--color-data-categorical-red);
689
+ }
690
+ .os-uptime-day[data-tone="none"] {
691
+ background: var(--color-background-muted);
692
+ }
693
+ .os-uptime-day:hover,
694
+ .os-uptime-day:focus-visible {
695
+ box-shadow: 0 0 0 var(--spacing-0-5) var(--color-border-emphasized);
696
+ }
697
+
698
+ /* The tip sits above the hovered day, pulled inward at either end of the row. */
699
+ .os-uptime-day .os-chart-tip {
700
+ inset: auto auto calc(100% + var(--spacing-2)) 50%;
701
+ transform: translateX(-50%);
702
+ }
703
+ .os-uptime-day .os-chart-tip[data-align="start"] {
704
+ left: 0;
705
+ transform: none;
706
+ }
707
+ .os-uptime-day .os-chart-tip[data-align="end"] {
708
+ right: 0;
709
+ left: auto;
710
+ transform: none;
711
+ }
712
+
713
+ .os-uptime-axis {
714
+ display: grid;
715
+ grid-template-columns: 1fr auto 1fr;
716
+ gap: var(--spacing-2);
717
+ color: var(--color-text-secondary);
718
+ font-size: var(--font-size-xs);
719
+ }
720
+ .os-uptime-axis span:last-child {
721
+ text-align: end;
722
+ }