repos
/ timelite-nextjs master

timelite-nextjs

mirror archived upstream

A dead simple time tracker that keeps everything in local storage. Next.js, no accounts and no server.

handcodedlocalstoragenextjsreactself-hostedserverlesstime-trackingtimer

2.2 KB · 86 lines · JavaScript Raw History
 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;