@checkstack/ui 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # @checkstack/ui
2
2
 
3
+ ## 1.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - c842373: ## Animated Numbers & Availability Stats Live Updates
8
+
9
+ ### Features
10
+
11
+ - **AnimatedNumber component** (`@checkstack/ui`): New reusable component that displays numbers with a smooth "rolling" animation when values change. Uses `requestAnimationFrame` with eased interpolation for a polished effect.
12
+ - **useAnimatedNumber hook** (`@checkstack/ui`): Underlying hook for the animation logic, can be used directly for custom implementations.
13
+ - **Live availability updates**: Availability stats (31-day and 365-day) now automatically refresh when new health check runs are received via signals.
14
+
15
+ ### Usage
16
+
17
+ ```tsx
18
+ import { AnimatedNumber } from "@checkstack/ui";
19
+
20
+ <AnimatedNumber
21
+ value={99.95}
22
+ suffix="%"
23
+ decimals={2}
24
+ duration={500}
25
+ className="text-2xl font-bold text-green-500"
26
+ />;
27
+ ```
28
+
3
29
  ## 1.0.0
4
30
 
5
31
  ### Major Changes
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@checkstack/ui",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "dependencies": {
7
- "@checkstack/common": "0.6.1",
8
- "@checkstack/frontend-api": "0.3.4",
7
+ "@checkstack/common": "0.6.2",
8
+ "@checkstack/frontend-api": "0.3.5",
9
9
  "@monaco-editor/react": "^4.7.0",
10
10
  "@radix-ui/react-accordion": "^1.2.12",
11
11
  "@radix-ui/react-dialog": "^1.1.15",
@@ -0,0 +1,48 @@
1
+ import { useAnimatedNumber } from "../hooks/useAnimatedNumber";
2
+
3
+ interface AnimatedNumberProps {
4
+ /** The number value to display (undefined for N/A) */
5
+ value: number | undefined;
6
+ /** Animation duration in milliseconds (default: 500ms) */
7
+ duration?: number;
8
+ /** Number of decimal places (default: 2) */
9
+ decimals?: number;
10
+ /** Suffix to append after the number (e.g., "%", "ms") */
11
+ suffix?: string;
12
+ /** CSS classes for the number span */
13
+ className?: string;
14
+ /** CSS classes for the suffix span */
15
+ suffixClassName?: string;
16
+ }
17
+
18
+ /**
19
+ * Component that displays an animated number with smooth rolling effect.
20
+ * Numbers smoothly interpolate from their previous value to the new value.
21
+ *
22
+ * @example
23
+ * ```tsx
24
+ * <AnimatedNumber
25
+ * value={99.95}
26
+ * suffix="%"
27
+ * className="text-2xl font-bold text-green-500"
28
+ * />
29
+ * ```
30
+ */
31
+ export function AnimatedNumber({
32
+ value,
33
+ duration = 500,
34
+ decimals = 2,
35
+ suffix,
36
+ className = "",
37
+ suffixClassName = "",
38
+ }: AnimatedNumberProps) {
39
+ const displayValue = useAnimatedNumber(value, duration, decimals);
40
+ const isNA = value === undefined;
41
+
42
+ return (
43
+ <span className={`tabular-nums ${className}`}>
44
+ {displayValue}
45
+ {!isNA && suffix && <span className={suffixClassName}>{suffix}</span>}
46
+ </span>
47
+ );
48
+ }
@@ -0,0 +1,83 @@
1
+ import { useState, useEffect, useRef } from "react";
2
+
3
+ /**
4
+ * Hook that animates a number from its previous value to the new value.
5
+ * Creates a smooth "rolling numbers" effect.
6
+ *
7
+ * @param targetValue - The value to animate towards (undefined for N/A)
8
+ * @param duration - Animation duration in milliseconds (default: 500ms)
9
+ * @param decimals - Number of decimal places to display (default: 2)
10
+ */
11
+ export function useAnimatedNumber(
12
+ targetValue: number | undefined,
13
+ duration = 500,
14
+ decimals = 2,
15
+ ): string {
16
+ const [displayValue, setDisplayValue] = useState(targetValue);
17
+ const animationRef = useRef<number>();
18
+ const startTimeRef = useRef<number>();
19
+ const startValueRef = useRef<number | undefined>(undefined);
20
+
21
+ useEffect(() => {
22
+ if (targetValue === undefined) {
23
+ setDisplayValue(undefined);
24
+ return;
25
+ }
26
+
27
+ // If this is the first value, just set it immediately
28
+ if (startValueRef.current === undefined) {
29
+ startValueRef.current = targetValue;
30
+ setDisplayValue(targetValue);
31
+ return;
32
+ }
33
+
34
+ const startValue = startValueRef.current;
35
+
36
+ // If target is the same, no animation needed
37
+ if (startValue === targetValue) {
38
+ return;
39
+ }
40
+
41
+ // Cancel any existing animation
42
+ if (animationRef.current) {
43
+ cancelAnimationFrame(animationRef.current);
44
+ }
45
+
46
+ const animate = (timestamp: number) => {
47
+ if (!startTimeRef.current) {
48
+ startTimeRef.current = timestamp;
49
+ }
50
+
51
+ const elapsed = timestamp - startTimeRef.current;
52
+ const progress = Math.min(elapsed / duration, 1);
53
+
54
+ // Ease out cubic for smooth deceleration
55
+ const eased = 1 - Math.pow(1 - progress, 3);
56
+
57
+ const current = startValue + (targetValue - startValue) * eased;
58
+ setDisplayValue(current);
59
+
60
+ if (progress < 1) {
61
+ animationRef.current = requestAnimationFrame(animate);
62
+ } else {
63
+ // Animation complete - update the start value for next animation
64
+ startValueRef.current = targetValue;
65
+ startTimeRef.current = undefined;
66
+ }
67
+ };
68
+
69
+ animationRef.current = requestAnimationFrame(animate);
70
+
71
+ return () => {
72
+ if (animationRef.current) {
73
+ cancelAnimationFrame(animationRef.current);
74
+ }
75
+ };
76
+ }, [targetValue, duration]);
77
+
78
+ if (displayValue === undefined) {
79
+ return "N/A";
80
+ }
81
+
82
+ return displayValue.toFixed(decimals);
83
+ }
package/src/index.ts CHANGED
@@ -51,3 +51,5 @@ export * from "./components/CommandPalette";
51
51
  export * from "./components/TerminalFeed";
52
52
  export * from "./components/AmbientBackground";
53
53
  export * from "./components/CodeEditor";
54
+ export * from "./components/AnimatedNumber";
55
+ export * from "./hooks/useAnimatedNumber";