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

4.2 KB · 138 lines · JavaScript Raw History
  1import React, { useContext } from "react";
  2import Link from "next/link";
  3import { useRouter } from "next/router";
  4
  5import { Context } from "./context";
  6import strings from "../l10n/sidebar";
  7
  8import styles from "../styles/components/sidebar.module.css";
  9
 10const Sidebar = () => {
 11  const { state } = useContext(Context);
 12  strings.setLanguage(state.language);
 13  const router = useRouter();
 14
 15  return (
 16    <aside className={styles.side}>
 17      <Link href="/" className={styles.brand} aria-label={strings.name}>
 18        <span className={styles.brandMark} aria-hidden="true">
 19          <svg viewBox="0 0 24 24" width="22" height="22">
 20            <circle
 21              cx="12"
 22              cy="12"
 23              r="9.5"
 24              fill="none"
 25              stroke="currentColor"
 26              strokeWidth="1.5"
 27              opacity="0.9"
 28            />
 29            <circle cx="12" cy="12" r="4" fill="currentColor" />
 30          </svg>
 31        </span>
 32        <span className={styles.brandText}>{strings.name}</span>
 33      </Link>
 34      <div className={styles.pages}>
 35        <Link
 36          href="/"
 37          className={`${styles.pageLink} ${router.pathname === "/" ? styles.pageLinkActive : ""}`.trim()}
 38          aria-label={strings.timer}
 39          title={`${strings.timer} (⎇+T)`}
 40        >
 41          <TimerIcon />
 42        </Link>
 43        <Link
 44          href="/log"
 45          className={`${styles.pageLink} ${router.pathname === "/log" ? styles.pageLinkActive : ""}`.trim()}
 46          aria-label={strings.log}
 47          title={`${strings.log} (⎇+L)`}
 48        >
 49          <LogIcon />
 50        </Link>
 51        <Link
 52          href="/summary"
 53          className={`${styles.pageLink} ${router.pathname === "/summary" ? styles.pageLinkActive : ""}`.trim()}
 54          aria-label={strings.summary}
 55          title={`${strings.summary} (⎇+S)`}
 56        >
 57          <SummaryIcon />
 58        </Link>
 59      </div>
 60      <Link
 61        href="/about"
 62        className={styles.about}
 63        aria-label={strings.about}
 64        title={`${strings.about} (⎇+O) — press ? for shortcuts`}
 65      >
 66        <HelpIcon />
 67      </Link>
 68    </aside>
 69  );
 70};
 71
 72export default Sidebar;
 73
 74const HelpIcon = () => (
 75  <svg
 76    xmlns="http://www.w3.org/2000/svg"
 77    height="22"
 78    viewBox="0 0 24 24"
 79    width="22"
 80    fill="currentColor"
 81    aria-hidden="true"
 82  >
 83    <path d="M0 0h24v24H0V0z" fill="none" />
 84    <path d="M11 18h2v-2h-2v2zm1-16C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm0-14c-2.21 0-4 1.79-4 4h2c0-1.1.9-2 2-2s2 .9 2 2c0 2-3 1.75-3 5h2c0-2.25 3-2.5 3-5 0-2.21-1.79-4-4-4z" />
 85  </svg>
 86);
 87
 88const TimerIcon = () => (
 89  <svg
 90    xmlns="http://www.w3.org/2000/svg"
 91    height="22"
 92    viewBox="0 0 24 24"
 93    width="22"
 94    fill="currentColor"
 95    aria-hidden="true"
 96  >
 97    <path d="M0 0h24v24H0V0z" fill="none" />
 98    <path d="M15.07 1.01h-6v2h6v-2zm-4 13h2v-6h-2v6zm8.03-6.62l1.42-1.42c-.43-.51-.9-.99-1.41-1.41l-1.42 1.42C16.14 4.74 14.19 4 12.07 4c-4.97 0-9 4.03-9 9s4.02 9 9 9 9-4.03 9-9c0-2.11-.74-4.06-1.97-5.61zm-7.03 12.62c-3.87 0-7-3.13-7-7s3.13-7 7-7 7 3.13 7 7-3.13 7-7 7z" />
 99  </svg>
100);
101
102const LogIcon = () => (
103  <svg
104    xmlns="http://www.w3.org/2000/svg"
105    height="22"
106    viewBox="0 0 24 24"
107    width="22"
108    fill="currentColor"
109    aria-hidden="true"
110  >
111    <path d="M0 0h24v24H0V0z" fill="none" />
112    <path d="M4 10.5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm0-6c-.83 0-1.5.67-1.5 1.5S3.17 7.5 4 7.5 5.5 6.83 5.5 6 4.83 4.5 4 4.5zm0 12c-.83 0-1.5.68-1.5 1.5s.68 1.5 1.5 1.5 1.5-.68 1.5-1.5-.67-1.5-1.5-1.5zM7 19h14v-2H7v2zm0-6h14v-2H7v2zm0-8v2h14V5H7z" />
113  </svg>
114);
115
116const SummaryIcon = () => (
117  <svg
118    xmlns="http://www.w3.org/2000/svg"
119    enableBackground="new 0 0 24 24"
120    height="22"
121    viewBox="0 0 24 24"
122    width="22"
123    fill="currentColor"
124    aria-hidden="true"
125  >
126    <g>
127      <rect fill="none" height="24" width="24" />
128      <g>
129        <path d="M19,3H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3z M19,19H5V5h14V19z" />
130        <rect height="5" width="2" x="7" y="12" />
131        <rect height="10" width="2" x="15" y="7" />
132        <rect height="3" width="2" x="11" y="14" />
133        <rect height="2" width="2" x="11" y="10" />
134      </g>
135    </g>
136  </svg>
137);