A dead simple time tracker that keeps everything in local storage. Next.js, no accounts and no server.
handcodedlocalstoragenextjsreactself-hostedserverlesstime-trackingtimer
1import React, { useEffect } from "react";
2import PropTypes from "prop-types";
3
4import styles from "../styles/components/keyHelpOverlay.module.css";
5
6const ROWS = [
7 { section: "Timer", keys: [
8 ["⎇+A", "Add log entry"],
9 ["⎇+R", "Reset timer"],
10 ["⎇+P", "Pause / resume"],
11 ]},
12 { section: "Navigation", keys: [
13 ["⎇+T", "Timer page"],
14 ["⎇+L", "Log page"],
15 ["⎇+S", "Summary page"],
16 ["⎇+O", "About page"],
17 ]},
18 { section: "Log", keys: [
19 ["↓", "Next entry"],
20 ["↑", "Previous entry"],
21 ["⎇+E", "Edit entry"],
22 ["⎇+D", "Delete entry"],
23 ["⎇+C", "Clear all"],
24 ]},
25 { section: "Help", keys: [
26 ["?", "Toggle this overlay"],
27 ["Esc", "Close overlay"],
28 ]},
29];
30
31const KeyHelpOverlay = ({ open, onClose }) => {
32 useEffect(() => {
33 if (!open) return;
34 const onKey = (e) => {
35 if (e.key === "Escape") onClose();
36 };
37 window.addEventListener("keydown", onKey);
38 return () => window.removeEventListener("keydown", onKey);
39 }, [open, onClose]);
40
41 if (!open) return null;
42
43 return (
44 <div className={styles.backdrop} onClick={onClose}>
45 <div
46 className={styles.panel}
47 role="dialog"
48 aria-label="Keyboard shortcuts"
49 onClick={(e) => e.stopPropagation()}
50 >
51 <div className={styles.header}>
52 <span className={styles.title}>Shortcuts</span>
53 <button
54 className={styles.close}
55 onClick={onClose}
56 aria-label="Close"
57 title="Esc"
58 >
59 ✕
60 </button>
61 </div>
62 <div className={styles.grid}>
63 {ROWS.map(({ section, keys }) => (
64 <div key={section} className={styles.column}>
65 <div className={styles.sectionLabel}>{section}</div>
66 {keys.map(([k, label]) => (
67 <div key={k} className={styles.row}>
68 <span className={styles.key}>{k}</span>
69 <span className={styles.label}>{label}</span>
70 </div>
71 ))}
72 </div>
73 ))}
74 </div>
75 </div>
76 </div>
77 );
78};
79
80KeyHelpOverlay.propTypes = {
81 open: PropTypes.bool.isRequired,
82 onClose: PropTypes.func.isRequired,
83};
84
85export default KeyHelpOverlay;