» React创建番茄工作法Pomodoro Web应用 » 2. 开发 » 2.5 设置

设置

Settings 模块导入到 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>
   );
 }

如你所见,父模块传递了大量参数给 Settings 模块。通过 props 传递状态时最简单直接的方法,特别是在父子模块之间。

但是,如果你的项目状态传递比较复杂的话,可以考虑使用 ContextRedux

使用 css 变量增强可复用性,App.css

@@ -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;
 }

新增组件 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;

对应的样式文件 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;
}

Settings.jsx 中,我们使用了自制的 Switch 控件模块。

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;

其对应样式文件 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)));
}

设置页效果如下:

Settings