设置
将 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 传递状态时最简单直接的方法,特别是在父子模块之间。
但是,如果你的项目状态传递比较复杂的话,可以考虑使用 Context
或 Redux
。
使用 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)));
}
设置页效果如下: