» Make Pomodoro Web App in React » 2. Development » 2.5 Settings

Settings

Import the component named Settings in App.js:

@@ -1,16 +1,23 @@
 import { useEffect, useRef, useState } from "react";
 import "./App.css";
 import { formatTime } from "./util";
+import Settings from "./components/Settings";
 
 const POMODORO_SECONDS = 25 * 60;
 const BREAK_SECONDS = 5 * 60;
 const PHASE_POMODORO = 0;
 const PHASE_BREAK = 1;
+const DEFAULT_SETTING = {
+  useCircle: false,
+  soundOn: true,
+};
 
 function App() {
   const [seconds, setSeconds] = useState(POMODORO_SECONDS);
   const [ticking, setTicking] = useState(false);
   const [phase, setPhase] = useState(PHASE_POMODORO);
+  const [showSettings, setShowSettings] = useState(false);
+  const [settings, setSettings] = useState(DEFAULT_SETTING);
 
   useEffect(() => {
     if (seconds === 0) {
@@ -122,8 +129,18 @@ function App() {
         <span className="setting-btn" onClick={() => resetTimer(phase)}>
           Reset
         </span>
-        <span className="setting-btn">Settings</span>
+        <span className="setting-btn" onClick={() => setShowSettings(true)}>
+          Settings
+        </span>
       </div>
+      <Settings
+        show={showSettings}
+        settings={settings}
+        setSettings={setSettings}
+        hideIt={() => {
+          setShowSettings(false);
+        }}
+      />
     </div>
   );
 }

As you can see there, it passs a bunch of properties to the Settings component. Passing state through props is the most straightforward way, especially when dealing with parent and child components.

When your project gets more complicated, you may try Context or Redux.

Use css variables in App.css for better reusability:

@@ -1,6 +1,7 @@
 :root {
-  --primary-color: #F28585;
-  --secondary-color: #FFA447;
+  --primary-color: #f28585;
+  --secondary-color: #ffa447;
+  --button-bg: coral;
 }
 
 body {
@@ -14,7 +15,7 @@ body {
 
 .app {
   margin: 15vh auto;
-  width: 24rem;
+  width: 22rem;
   text-align: center;
   color: white;
 }
@@ -56,7 +57,7 @@ body {
 }
 
 .picked {
-  background-color: coral;
+  background-color: var(--button-bg);
 }
 
 .card {
@@ -71,7 +72,7 @@ body {
   font-size: 100px;
   margin-top: 2rem;
   margin-bottom: 2rem;
-  font-family: monospace, 'Courier New', Courier;
+  font-family: monospace, "Courier New", Courier;
 }

Newly added components/Settings.jsx:

import React from "react";
import "./Settings.css";
import Switch from "./Switch";

const Settings = (props) => {
  return (
    <div className="wrapper" hidden={!props.show}>
      <div className="settings-container">
        <h2>Settings</h2>
        <div className="setting-item-container">
          <span className="item-title">Timer</span>
          <div>
            <label>
              <input
                type="radio"
                name="timerStyle"
                value="numbers"
                checked={!props.settings.useCircle}
                onChange={(e) =>
                  props.setSettings({ ...props.settings, useCircle: false })
                }
              />
              Numbers
            </label>
            <label>
              <input
                type="radio"
                name="timerStyle"
                value="circles"
                checked={props.settings.useCircle}
                onChange={(e) =>
                  props.setSettings({ ...props.settings, useCircle: true })
                }
              />
              Circles
            </label>
          </div>
        </div>
        <div className="setting-item-container">
          <span className="item-title">Sound</span>
          <div>
            <Switch
              isSwitchOn={props.settings.soundOn}
              toggleSwitch={() => {
                props.setSettings({
                  ...props.settings,
                  soundOn: !props.settings.soundOn,
                });
              }}
            />
          </div>
        </div>
        <button
          className="control-btn small-btn"
          onClick={() => props.hideIt()}
        >
          Done
        </button>
      </div>
    </div>
  );
};

export default Settings;

And its style file components/Settings.css:

.wrapper {
  width: 100%;
  height: 100vh;
  position: absolute;
  top: 0;
  left: 0;
  background-color: rgba(0, 0, 0, 0.5);
}

.settings-container {
  background-color: rgba(255, 255, 255, 1);
  color: black;
  border-radius: 0.5rem;
  padding: 1rem;
  position: absolute;
  width: 20rem;
  top: 50%;
  left: 50%;
  border-bottom: solid 3px coral;
  transform: translate(-50%, -50%);
}

.settings-container .item-title {
  font-weight: bold;
}

.setting-item-container {
  margin-top: 1rem;
  display: flex;
  justify-content: space-between;
}

.small-btn {
  margin-top: 2rem;
  padding: 3px 10px;
  font-size: 24px;
}

In Settings.jsx, we use a self-made Switch control.

components/Switch.jsx:

import React from "react";
import "./Swtich.css";

const Switch = (props) => {
  return (
    <label className="switch-container">
      <input
        type="checkbox"
        className="switch-checkbox"
        checked={props.isSwitchOn}
        onChange={props.toggleSwitch}
      />
      <div className={`switch-label ${props.isSwitchOn ? "switch-on" : ""}`}>
        <div className="switch-handle"></div>
      </div>
    </label>
  );
};

export default Switch;

And its style file components/Switch.css:

.switch-container {
  --width: 50px;
  --radius: 24px;
  --duration: 0.5s;
}

/* Container for styling purposes */
.switch-container {
  position: relative;
  display: inline-block;
  width: var(--width);
  height: var(--radius);
}

/* Hidden default checkbox input */
.switch-checkbox {
  opacity: 0;
  width: 0;
  height: 0;
}

/* The switch background */
.switch-label {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: #ccc;
  border-radius: calc(var(--radius) / 2);
  cursor: pointer;
  transition: background-color var(--duration) ease;
}

.switch-on {
  background-color: var(--button-bg);
  transition: background-color var(--duration) ease;
}

/* The switch "thumb" or handle */
.switch-handle {
  position: absolute;
  top: 0;
  left: 0;
  width: var(--radius);
  height: var(--radius);
  background-color: #fff;
  border-radius: 50%;
  transition: transform var(--duration) ease;
}

/* Move the switch handle to the right when the checkbox is checked */
.switch-checkbox:checked + .switch-label .switch-handle {
  transform: translateX(calc(var(--width) - var(--radius)));
}

The settings page looks like this:

Settings