|
@@ -0,0 +1,301 @@
|
|
|
+import logo from "./logo.svg";
|
|
|
+import "./App.scss";
|
|
|
+import React, { useState, useEffect } from "react";
|
|
|
+
|
|
|
+import ClockFace from "./ClockFace.png";
|
|
|
+import ClockFace_H from "./ClockFace_H.png";
|
|
|
+import ClockFace_M from "./ClockFace_M.png";
|
|
|
+import ClockFace_S from "./ClockFace_S.png";
|
|
|
+
|
|
|
+const RangeInput = ({ min, max }) => {
|
|
|
+ const [inputValue, setInputValue] = useState("");
|
|
|
+
|
|
|
+ return (
|
|
|
+ <input
|
|
|
+ className={`RangeInput ${inputValue.length > +max || inputValue.length < +min ? "error" : ""}`}
|
|
|
+ value={inputValue}
|
|
|
+ onChange={(e) => setInputValue(e.target.value)}
|
|
|
+ />
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+const Spoiler = ({ header = "+", open = true, children }) => {
|
|
|
+ const [isOpen, setIsOpen] = useState(open);
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className="Spoiler">
|
|
|
+ <div className="header" onClick={() => setIsOpen((isOpen) => setIsOpen(!isOpen))}>
|
|
|
+ {header}
|
|
|
+ </div>
|
|
|
+ <div className="body">{isOpen && children}</div>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+class PasswordConfirm extends React.Component {
|
|
|
+ constructor(props) {
|
|
|
+ super(props);
|
|
|
+ this.state = { passwordValue1: "", passwordValue2: "", isError: false };
|
|
|
+ this.handlePasswordValue1OnChange = this.handlePasswordValue1OnChange.bind(this);
|
|
|
+ this.handlePasswordValue2OnChange = this.handlePasswordValue2OnChange.bind(this);
|
|
|
+ this.validatePasswords = this.validatePasswords.bind(this);
|
|
|
+ }
|
|
|
+
|
|
|
+ validatePasswords({ password1 = "", password2 = "" } = {}) {
|
|
|
+ const { min = 0 } = this.props;
|
|
|
+ const regex = new RegExp(this.props.regex || "");
|
|
|
+
|
|
|
+ if (password1.length < this.props.min || !password1.match(regex) || password1 !== password2) {
|
|
|
+ this.setState({ isError: true });
|
|
|
+ } else {
|
|
|
+ this.setState({ isError: false });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ handlePasswordValue1OnChange(newValue) {
|
|
|
+ this.setState({ passwordValue1: newValue });
|
|
|
+ this.validatePasswords({ password1: newValue, password2: this.state.passwordValue2 });
|
|
|
+ }
|
|
|
+
|
|
|
+ handlePasswordValue2OnChange(newValue) {
|
|
|
+ this.setState({ passwordValue2: newValue });
|
|
|
+ this.validatePasswords({ password1: this.state.passwordValue1, password2: newValue });
|
|
|
+ }
|
|
|
+
|
|
|
+ render() {
|
|
|
+ return (
|
|
|
+ <div className="PasswordConfirm">
|
|
|
+ <input
|
|
|
+ className={`${this.state.isError ? "error" : ""}`}
|
|
|
+ type="password"
|
|
|
+ value={this.state.passwordValue1}
|
|
|
+ onChange={(e) => this.handlePasswordValue1OnChange(e.target.value)}
|
|
|
+ />
|
|
|
+ <input
|
|
|
+ className={`${this.state.isError ? "error" : ""}`}
|
|
|
+ type="password"
|
|
|
+ value={this.state.passwordValue2}
|
|
|
+ onChange={(e) => this.handlePasswordValue2OnChange(e.target.value)}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const Timer = ({ seconds: initialSeconds = 0 } = {}) => {
|
|
|
+ const [seconds, setSeconds] = useState(initialSeconds || 0);
|
|
|
+ let timerInterval;
|
|
|
+ useEffect(() => {
|
|
|
+ timerInterval = setInterval(() => {
|
|
|
+ (+seconds === 0 || +seconds < 0) && clearInterval(timerInterval);
|
|
|
+ setSeconds((seconds) => +seconds - 1);
|
|
|
+ }, 1000);
|
|
|
+ return () => {
|
|
|
+ clearInterval(timerInterval);
|
|
|
+ };
|
|
|
+ }, [seconds]);
|
|
|
+ return (
|
|
|
+ <div className="Timer">
|
|
|
+ <span>{`00${Math.floor(Math.floor(+seconds / 60) / 60) % 24}`.slice(-2)}</span>:
|
|
|
+ <span>{`00${Math.floor(+seconds / 60) % 60}`.slice(-2)}</span>:<span>{`00${+seconds % 60}`.slice(-2)}</span>
|
|
|
+ <button onClick={() => timerInterval && clearInterval(timerInterval)}>Stop</button>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+const TimerPresentation = ({ seconds = 0, onStop }) => (
|
|
|
+ <div className="TimerPresentation">
|
|
|
+ <div className="Timer">
|
|
|
+ <span>{`00${Math.floor(Math.floor(+seconds / 60) / 60) % 24}`.slice(-2)}</span>:
|
|
|
+ <span>{`00${Math.floor(+seconds / 60) % 60}`.slice(-2)}</span>:<span>{`00${+seconds % 60}`.slice(-2)}</span>
|
|
|
+ <button onClick={onStop}>Stop</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+);
|
|
|
+
|
|
|
+const TimerControl = () => {
|
|
|
+ const [seconds, setSeconds] = useState(0);
|
|
|
+ const [secondsValue, setSecondsValue] = useState(0);
|
|
|
+ const [minutesValue, setMinutesValue] = useState(0);
|
|
|
+ const [hoursValue, setHoursValue] = useState(0);
|
|
|
+
|
|
|
+ const valueValidator = (value) => {
|
|
|
+ return +value < 0 ? false : true;
|
|
|
+ };
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className="TimerControl">
|
|
|
+ Hours:
|
|
|
+ <input
|
|
|
+ type="number"
|
|
|
+ value={hoursValue}
|
|
|
+ onChange={(e) => valueValidator(+e.target.value) && setHoursValue(+e.target.value)}
|
|
|
+ />
|
|
|
+ Minutes:
|
|
|
+ <input
|
|
|
+ type="number"
|
|
|
+ value={minutesValue}
|
|
|
+ onChange={(e) => valueValidator(+e.target.value) && setMinutesValue(+e.target.value)}
|
|
|
+ />
|
|
|
+ Seconds:
|
|
|
+ <input
|
|
|
+ type="number"
|
|
|
+ value={secondsValue}
|
|
|
+ onChange={(e) => valueValidator(+e.target.value) && setSecondsValue(+e.target.value)}
|
|
|
+ />
|
|
|
+ <button
|
|
|
+ onClick={() =>
|
|
|
+ valueValidator(secondsValue + minutesValue * 60 + hoursValue * 60 * 60) &&
|
|
|
+ setSeconds(secondsValue + minutesValue * 60 + hoursValue * 60 * 60)
|
|
|
+ }
|
|
|
+ >
|
|
|
+ Start
|
|
|
+ </button>
|
|
|
+ {!!seconds && <Timer seconds={seconds} />}
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+const SecondsTimer = ({ seconds }) => <h2>{seconds}</h2>;
|
|
|
+const TimerContainer = ({ ...props }) => {
|
|
|
+ const { seconds: initialSeconds = 0, refresh = 1, render = "" } = props || {};
|
|
|
+ const Render = render;
|
|
|
+ const [seconds, setSeconds] = useState(initialSeconds || 0);
|
|
|
+ let interval;
|
|
|
+ useEffect(() => {
|
|
|
+ const startTime = performance.now();
|
|
|
+ interval = setInterval(() => {
|
|
|
+ (+seconds === 0 || +seconds < 0) && clearInterval(interval);
|
|
|
+ setSeconds((seconds) => seconds - Math.floor((performance.now() - startTime) / 1000));
|
|
|
+ }, refresh);
|
|
|
+ return () => {
|
|
|
+ clearInterval(interval);
|
|
|
+ };
|
|
|
+ }, [seconds, refresh]);
|
|
|
+ return <Render seconds={seconds} onStop={() => clearInterval(interval)} />;
|
|
|
+};
|
|
|
+
|
|
|
+const TimerControlContainer = ({ ...props }) => {
|
|
|
+ const [seconds, setSeconds] = useState(0);
|
|
|
+ const [secondsValue, setSecondsValue] = useState(0);
|
|
|
+ const [minutesValue, setMinutesValue] = useState(0);
|
|
|
+ const [hoursValue, setHoursValue] = useState(0);
|
|
|
+
|
|
|
+ const valueValidator = (value) => {
|
|
|
+ return +value < 0 ? false : true;
|
|
|
+ };
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className="TimerControlContainer">
|
|
|
+ <div className="controls">
|
|
|
+ Hours:
|
|
|
+ <input
|
|
|
+ type="number"
|
|
|
+ value={hoursValue}
|
|
|
+ onChange={(e) => valueValidator(+e.target.value) && setHoursValue(+e.target.value)}
|
|
|
+ />
|
|
|
+ Minutes:
|
|
|
+ <input
|
|
|
+ type="number"
|
|
|
+ value={minutesValue}
|
|
|
+ onChange={(e) => valueValidator(+e.target.value) && setMinutesValue(+e.target.value)}
|
|
|
+ />
|
|
|
+ Seconds:
|
|
|
+ <input
|
|
|
+ type="number"
|
|
|
+ value={secondsValue}
|
|
|
+ onChange={(e) => valueValidator(+e.target.value) && setSecondsValue(+e.target.value)}
|
|
|
+ />
|
|
|
+ <button
|
|
|
+ onClick={() =>
|
|
|
+ valueValidator(secondsValue + minutesValue * 60 + hoursValue * 60 * 60) &&
|
|
|
+ setSeconds(secondsValue + minutesValue * 60 + hoursValue * 60 * 60)
|
|
|
+ }
|
|
|
+ >
|
|
|
+ Start
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className="body">{seconds && <TimerContainer {...props} seconds={seconds} />}</div>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+};
|
|
|
+const Watch = ({ seconds }) => (
|
|
|
+ <div className="Watch">
|
|
|
+ <img src={ClockFace} className="clockFace" alt="" />
|
|
|
+ <img
|
|
|
+ src={ClockFace_H}
|
|
|
+ style={{ transform: "rotate(" + (Math.floor(Math.floor(+seconds / 60) / 60) % 24) * 6 + "deg)" }}
|
|
|
+ className="arrowHour"
|
|
|
+ alt=""
|
|
|
+ />
|
|
|
+ <img
|
|
|
+ src={ClockFace_M}
|
|
|
+ style={{ transform: "rotate(" + (Math.floor(+seconds / 60) % 60) * 6 + "deg)" }}
|
|
|
+ className="arrowMin"
|
|
|
+ alt=""
|
|
|
+ />
|
|
|
+ <img
|
|
|
+ src={ClockFace_S}
|
|
|
+ className="arrowSec"
|
|
|
+ style={{ transform: "rotate(" + (seconds % 60) * 6 + "deg)" }}
|
|
|
+ alt=""
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+);
|
|
|
+
|
|
|
+const Header = ({ children }) => <div className="Header">{children}</div>;
|
|
|
+const Main = ({ children }) => <div className="Main">{children}</div>;
|
|
|
+const Footer = ({ children }) => <div className="Footer">{children}</div>;
|
|
|
+
|
|
|
+function App() {
|
|
|
+ return (
|
|
|
+ <div className="App">
|
|
|
+ <Header />
|
|
|
+ <Main>
|
|
|
+ <Spoiler header={<h3>Spoiler</h3>} open={false}>
|
|
|
+ <Spoiler header={<h1>Заголовок</h1>} open>
|
|
|
+ Контент 1<p>лорем ипсум траливали и тп.</p>
|
|
|
+ </Spoiler>
|
|
|
+
|
|
|
+ <Spoiler>
|
|
|
+ <h2>Контент 2</h2>
|
|
|
+ <p>лорем ипсум траливали и тп.</p>
|
|
|
+ </Spoiler>
|
|
|
+ </Spoiler>
|
|
|
+
|
|
|
+ <Spoiler header={<h3>RangeInput</h3>} open={false}>
|
|
|
+ <RangeInput min={2} max={10} />
|
|
|
+ </Spoiler>
|
|
|
+
|
|
|
+ <Spoiler header={<h3>PasswordConfirm</h3>} open={false}>
|
|
|
+ <PasswordConfirm min={2} />
|
|
|
+ </Spoiler>
|
|
|
+
|
|
|
+ <Spoiler header={<h3>Timer</h3>} open={false}>
|
|
|
+ <Timer seconds={60 * 60 * 3} />
|
|
|
+ </Spoiler>
|
|
|
+
|
|
|
+ <Spoiler header={<h3>TimerControl</h3>} open={false}>
|
|
|
+ <TimerControl />
|
|
|
+ </Spoiler>
|
|
|
+ <Spoiler header={<h3>TimerContainer</h3>} open={false}>
|
|
|
+ <TimerContainer seconds={3} refresh={100} render={SecondsTimer} />
|
|
|
+ </Spoiler>
|
|
|
+ <Spoiler header={<h3>LCD</h3>} open={false}>
|
|
|
+ <TimerContainer seconds={1800} refresh={100} render={TimerPresentation} />
|
|
|
+ </Spoiler>
|
|
|
+
|
|
|
+ <Spoiler header={<h3>Watch</h3>} open={false}>
|
|
|
+ <TimerContainer seconds={7000} refresh={100} render={Watch} />
|
|
|
+ </Spoiler>
|
|
|
+ <Spoiler header={<h3>TimerControl + TimerContainer</h3>} open={false}>
|
|
|
+ <TimerControlContainer refresh={100} render={Watch} />
|
|
|
+ </Spoiler>
|
|
|
+ </Main>
|
|
|
+ <Footer />
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+}
|
|
|
+
|
|
|
+export default App;
|