Skip to content

Commit a7391f4

Browse files
authored
Merge pull request #29 from ChicoState/battery
Battery widget
2 parents d59f44e + c62feff commit a7391f4

File tree

3 files changed

+136
-0
lines changed

3 files changed

+136
-0
lines changed

src/App.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { Notepad } from "./widgets/Notepad";
99
import { Search } from "./widgets/Search";
1010
import { Shortcut } from "./widgets/Shortcut";
1111
import styles from "./App.css";
12+
import BatteryWidget from "./widgets/BatteryWidget";
1213
import {
1314
PencilSimpleIcon,
1415
CheckIcon,
@@ -130,6 +131,13 @@ const App = () => {
130131
>
131132
<Shortcut url="https://stackoverflow.com"></Shortcut>
132133
</Widget>
134+
135+
<Widget
136+
size={{ width: 2, height: 1 }}
137+
position={{ gridX: 20, gridY: 0 }}
138+
>
139+
<BatteryWidget />
140+
</Widget>
133141
</Grid>
134142

135143
<Menu active={menuOpen}></Menu>

src/widgets/BatteryWidget.css

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
.row {
2+
--ok: #34c759;
3+
--low: #ff3b30;
4+
--stroke: rgba(255, 255, 255, 0.35);
5+
--level: rgba(255, 255, 255, 0.25);
6+
7+
display: flex;
8+
flex-flow: row wrap;
9+
align-items: center;
10+
justify-content: center;
11+
gap: 4px;
12+
height: 100%;
13+
color: #fffc;
14+
font-variant-numeric: tabular-nums;
15+
}
16+
17+
@media (prefers-color-scheme: light) {
18+
.row {
19+
--stroke: rgba(232, 225, 225, 0.35);
20+
color: #a9a9b8;
21+
}
22+
}
23+
24+
.value {
25+
font-size: 1rem;
26+
font-weight: 600;
27+
letter-spacing: -0.02em;
28+
line-height: 1;
29+
}
30+
31+
.icon {
32+
width: 48px;
33+
display: block;
34+
}
35+
36+
.body {
37+
fill: none;
38+
stroke: var(--stroke);
39+
stroke-width: 2;
40+
}
41+
42+
.cap {
43+
fill: var(--stroke);
44+
}
45+
46+
.level {
47+
fill: var(--level); /* overridden by state below */
48+
transition: width 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
49+
}
50+
51+
.ok .level {
52+
fill: var(--ok);
53+
} /* > 20% */
54+
.low .level {
55+
fill: var(--low);
56+
} /* <= 20% */
57+
58+
.neutral .level {
59+
fill: var(--level);
60+
}

src/widgets/BatteryWidget.tsx

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import React, { useEffect, useState } from "react";
2+
import styles from "./BatteryWidget.css";
3+
4+
export default function BatteryWidget() {
5+
const [percent, setPercent] = useState<number | null>(null);
6+
7+
useEffect(() => {
8+
let cleanup: (() => void) | undefined;
9+
10+
(async () => {
11+
const nav: any = navigator as any;
12+
if (!nav.getBattery) return; // non-Chromium browsers: show "—%"
13+
14+
const mgr = await nav.getBattery();
15+
16+
const update = () => {
17+
const lvl = typeof mgr.level === "number" ? mgr.level : 0; // 0..1
18+
setPercent(Math.round(lvl * 100));
19+
};
20+
21+
update(); // initial value
22+
mgr.addEventListener("levelchange", update);
23+
24+
cleanup = () => {
25+
mgr.removeEventListener("levelchange", update);
26+
};
27+
})();
28+
29+
return () => cleanup?.();
30+
}, []);
31+
32+
const clamped = percent == null ? 0 : Math.max(0, Math.min(100, percent));
33+
const tier = percent == null ? "neutral" : percent <= 20 ? "low" : "ok";
34+
35+
const INNER_MAX = 40;
36+
const innerWidth = Math.round((clamped / 100) * INNER_MAX);
37+
38+
return (
39+
<div className={`${styles.row} ${styles[tier]}`}>
40+
<span className={styles.value}>
41+
{percent != null ? `${percent}%` : "—%"}
42+
</span>
43+
44+
<svg className={styles.icon} viewBox="0 0 52 24" aria-hidden="true">
45+
{/* outline */}
46+
<rect
47+
x="1"
48+
y="3"
49+
width="44"
50+
height="18"
51+
rx="5"
52+
className={styles.body}
53+
/>
54+
{/* cap */}
55+
<rect x="46" y="8" width="5" height="8" rx="2" className={styles.cap} />
56+
{/* fill */}
57+
<rect
58+
x="3"
59+
y="5"
60+
width={innerWidth}
61+
height="14"
62+
rx="4"
63+
className={styles.level}
64+
/>
65+
</svg>
66+
</div>
67+
);
68+
}

0 commit comments

Comments
 (0)