瀏覽代碼

done hw react 2

unknown 3 年之前
父節點
當前提交
0a6f91eb44

File diff suppressed because it is too large
+ 1 - 1
.eslintcache


+ 20 - 1
src/App.tsx

@@ -1,11 +1,30 @@
 import { ToastContainer } from 'react-toastify';
 
 import s from './App.module.css';
+import Spoiler from './components/Spoiler/Spoiler';
+import RangeInput from './components/RangeInput/RangeInput';
+import PasswordConfirm from './components/PasswordConfirm/PasswordConfirm';
 
+import TimerContainer from './components/TimerContainer/TimerContainer';
+interface ISecondsTimerProps {
+  seconds: number;
+}
+const SecondsTimer = ({ seconds }: ISecondsTimerProps) => {
+  return <h2>{seconds}</h2>;
+};
 function App() {
   return (
     <div className={s.appWrapper}>
-      <div>Home Work react 2</div>
+      <Spoiler header={<h1>Spoiler</h1>} open>
+        <p>It works !</p>
+      </Spoiler>
+      <RangeInput min={2} max={10} />
+      <PasswordConfirm min={5} />
+      <TimerContainer
+        amountSeconds={1800}
+        refresh={100}
+        renderEl={SecondsTimer}
+      />
       <ToastContainer
         position="top-right"
         autoClose={3000}

+ 24 - 0
src/components/PasswordConfirm/PasswordConfirm.module.css

@@ -0,0 +1,24 @@
+.passwordConfirmWrapper {
+  display: block;
+  margin: 0 auto;
+  width: 15%;
+  display: flex;
+  flex-direction: column;
+  margin-bottom: 40px;
+}
+
+.password,
+.passwordConfirm,
+.valid {
+  padding: 5px;
+  color: rgb(45, 255, 8);
+}
+
+.notValid {
+  color: red;
+}
+
+.confirmPasswordValue,
+.passwordValue {
+  color: rgb(17, 247, 28);
+}

+ 73 - 0
src/components/PasswordConfirm/PasswordConfirm.tsx

@@ -0,0 +1,73 @@
+import { number } from 'prop-types';
+import React, { useState } from 'react';
+import s from './PasswordConfirm.module.css';
+
+interface IPasswordConfirmProps {
+  min: number;
+}
+const PasswordConfirm = ({ min }: IPasswordConfirmProps) => {
+  const [password, setPassword] = useState<string>('');
+  const [passwordConfirm, setPasswordConfirm] = useState<string>('');
+  const handlePassword = (e: React.ChangeEvent<HTMLInputElement>) => {
+    const value = e.target.value;
+    const name = e.target.name;
+    switch (name) {
+      case 'password':
+        setPassword(value);
+        break;
+      case 'confirmPassword':
+        setPasswordConfirm(value);
+        break;
+      default:
+        break;
+    }
+  };
+
+  const isValid = (password: string) => {
+    const reg = '[^wd]*(([0-9]+.*[A-Za-z]+.*)|[A-Za-z]+.*([0-9]+.*))';
+    return password && password.length >= min && new RegExp(reg).test(password)
+      ? true
+      : false;
+  };
+
+  const isConfirmed = () => {
+    return password && passwordConfirm && password === passwordConfirm
+      ? true
+      : false;
+  };
+
+  return (
+    <div className={s.passwordConfirmWrapper}>
+      <input
+        onChange={handlePassword}
+        className={s.password}
+        name="password"
+        type="password"
+      ></input>
+      <p className={isValid(password) ? s.passwordValue : s.notValid}>
+        password : {isValid(password) ? password : 'Password not valid'}
+      </p>
+      <input
+        onChange={handlePassword}
+        className={s.passwordConfirm}
+        name="confirmPassword"
+        type="password"
+      ></input>
+      <p
+        className={
+          isValid(passwordConfirm) ? s.confirmPasswordValue : s.notValid
+        }
+      >
+        passwordConfirm :{' '}
+        {isValid(passwordConfirm)
+          ? passwordConfirm
+          : ' PasswordConfirm not valid'}
+      </p>
+      <p className={isConfirmed() ? s.valid : s.notValid}>
+        isPassword confirmed :{isConfirmed() ? ' confirmed!' : ' not confirmed'}
+      </p>
+    </div>
+  );
+};
+
+export default PasswordConfirm;

+ 17 - 0
src/components/RangeInput/RangeInput.module.css

@@ -0,0 +1,17 @@
+.rageInputWrapper {
+  display: block;
+  margin: 0 auto;
+  width: 15%;
+  display: flex;
+  flex-direction: column;
+  margin-bottom: 40px;
+}
+
+.rageInput {
+  padding: 5px;
+}
+
+.rageP {
+  color: rgb(9, 250, 29);
+  font-size: 20px;
+}

+ 36 - 0
src/components/RangeInput/RangeInput.tsx

@@ -0,0 +1,36 @@
+import React, { useState } from 'react';
+import s from './RangeInput.module.css';
+
+interface IRangeInputProps {
+  min: number;
+  max: number;
+}
+
+const RangeInput = ({ min, max }: IRangeInputProps) => {
+  const [value, setValue] = useState<string>('');
+
+  const handleInput = (e: React.ChangeEvent<HTMLInputElement>) => {
+    const input = e.target;
+    const value = input.value;
+
+    setValue(value);
+    if (value.length <= min || value.length >= max) {
+      input.style.backgroundColor = 'red';
+    } else {
+      input.style.backgroundColor = 'white';
+    }
+  };
+
+  return (
+    <div className={s.rageInputWrapper}>
+      <input
+        className={s.rageInput}
+        onChange={handleInput}
+        placeholder="Write down your email"
+      ></input>
+      <p className={s.rageP}>value : {value}</p>
+    </div>
+  );
+};
+
+export default RangeInput;

+ 11 - 0
src/components/Spoiler/Spoiler.module.css

@@ -0,0 +1,11 @@
+.spoilerWrapper {
+  width: 20%;
+  display: flex;
+  margin: 0 auto;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  align-content: center;
+  color: antiquewhite;
+  margin-bottom: 40px;
+}

+ 19 - 0
src/components/Spoiler/Spoiler.tsx

@@ -0,0 +1,19 @@
+import s from './Spoiler.module.css';
+
+interface ISpoilerProps {
+  header?: React.ReactNode;
+  open?: boolean;
+  children?: React.ReactNode;
+}
+
+const Spoiler = ({ header, open, children }: ISpoilerProps) => {
+  return open ? (
+    <div className={s.spoilerWrapper}>
+      {header && header}
+      <h2>This is hidden content in Spoiler component</h2>
+      {children && children}
+    </div>
+  ) : null;
+};
+
+export default Spoiler;

+ 21 - 0
src/components/TimerContainer/Timer/Timer.module.css

@@ -0,0 +1,21 @@
+.timerWrapper {
+  width: 25%;
+  margin: 0 auto;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+}
+
+.timerBtn {
+  width: 150px;
+  background-color: rgb(163, 163, 163);
+  color: white;
+  margin-bottom: 10px;
+}
+
+.timerP {
+  font-size: 20px;
+  color: white;
+  font-family: 'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif;
+}

+ 42 - 0
src/components/TimerContainer/Timer/Timer.tsx

@@ -0,0 +1,42 @@
+import { useState, useEffect } from 'react';
+
+import s from './Timer.module.css';
+
+interface TimerProps {
+  time: number;
+  flag: string;
+  handleTimer: () => void;
+  refresh: number;
+  renderEl: ({ seconds }: { seconds: number }) => JSX.Element;
+}
+
+const Timer = ({
+  time,
+  flag,
+  handleTimer,
+  refresh,
+  renderEl: RenderEl,
+}: TimerProps) => {
+  const [timeLeft, setTimeLeft] = useState<number>(0);
+
+  useEffect(() => {
+    setTimeLeft(time);
+  }, [time]);
+
+  useEffect(() => {
+    timeLeft > 0 &&
+      flag === 'Pause' &&
+      setTimeout(() => setTimeLeft(timeLeft - 1), refresh);
+  }, [timeLeft, refresh, flag]);
+
+  return (
+    <div className={s.timerWrapper}>
+      <button className={s.timerBtn} onClick={handleTimer}>
+        {flag}
+      </button>
+      <RenderEl seconds={timeLeft} />
+    </div>
+  );
+};
+
+export default Timer;

+ 7 - 0
src/components/TimerContainer/TimerContainer.module.css

@@ -0,0 +1,7 @@
+.timerContainerWrapper {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  align-content: center;
+  padding-bottom: 40px;
+}

+ 19 - 0
src/components/TimerContainer/TimerContainer.tsx

@@ -0,0 +1,19 @@
+import s from './TimerContainer.module.css';
+import TimerControl from './TimerControl/TimerControl';
+import TimerPresentation from './TimerPresentation/TimerPresentation';
+
+interface ITimerContainerProps {
+  amountSeconds: number;
+  refresh: number;
+  renderEl: ({ seconds }: { seconds: number }) => JSX.Element;
+}
+const TimerContainer = (props: ITimerContainerProps) => {
+  return (
+    <div className={s.timerContainerWrapper}>
+      <TimerControl {...props} />
+      <TimerPresentation />
+    </div>
+  );
+};
+
+export default TimerContainer;

+ 14 - 0
src/components/TimerContainer/TimerControl/TimerControl.module.css

@@ -0,0 +1,14 @@
+.timerControlWrapper {
+  width: 15%;
+  margin: 0 auto;
+  margin-bottom: 30px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  align-content: center;
+}
+
+.timerInput {
+  padding: 5px;
+  margin-bottom: 10px;
+}

+ 92 - 0
src/components/TimerContainer/TimerControl/TimerControl.tsx

@@ -0,0 +1,92 @@
+import React, { useState } from 'react';
+
+import s from './TimerControl.module.css';
+import Timer from '../Timer/Timer';
+
+interface ITimerControlProps {
+  amountSeconds: number;
+  refresh: number;
+  renderEl: ({ seconds }: { seconds: number }) => JSX.Element;
+}
+
+const TimerControl = ({
+  amountSeconds,
+  refresh,
+  renderEl,
+}: ITimerControlProps) => {
+  const [flag, setFlag] = useState<string>('Start');
+  const [hours, setHours] = useState<number>(0);
+  const [minutes, setMinutes] = useState<number>(0);
+  const [seconds, setSeconds] = useState<number>(0);
+
+  const handleTimer = () => {
+    switch (flag) {
+      case 'Start':
+        setFlag('Pause');
+        break;
+      case 'Pause':
+        setFlag('Start');
+        break;
+      default:
+        break;
+    }
+  };
+
+  const handleTimeInput = (e: React.ChangeEvent<HTMLInputElement>) => {
+    const name = e.target.name;
+    const value = Number(e.target.value);
+    if (flag === 'Pause') return;
+    switch (name) {
+      case 'hours':
+        setHours(value);
+        break;
+      case 'minutes':
+        setMinutes(value);
+        break;
+      case 'seconds':
+        setSeconds(value);
+        break;
+      default:
+        break;
+    }
+  };
+  const time = (hours * 60 + minutes) * 60 + seconds;
+
+  return (
+    <div className={s.timerControlWrapper}>
+      <input
+        onChange={handleTimeInput}
+        name="hours"
+        className={s.timerInput}
+        type="number"
+        placeholder="Hours"
+      ></input>
+      <input
+        onChange={handleTimeInput}
+        name="minutes"
+        className={s.timerInput}
+        type="number"
+        placeholder="Minutes"
+      ></input>
+      <input
+        onChange={handleTimeInput}
+        name="seconds"
+        className={s.timerInput}
+        type="number"
+        placeholder="Seconds"
+      ></input>
+      <p>
+        Hours : {hours} . Minutes : {minutes} .Seconds : {seconds}
+      </p>
+      <Timer
+        time={time > 0 ? time : amountSeconds}
+        flag={flag}
+        handleTimer={handleTimer}
+        refresh={refresh}
+        renderEl={renderEl}
+      />
+    </div>
+  );
+};
+
+export default TimerControl;

+ 96 - 0
src/components/TimerContainer/TimerPresentation/TimerPresentation.module.css

@@ -0,0 +1,96 @@
+.clock {
+  width: 300px;
+  height: 300px;
+  background-color: rgba(255, 255, 255, 0.8);
+  border-radius: 50%;
+  border: 2px solid black;
+  position: relative;
+}
+
+.clock .number {
+  --rotation: 0;
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  text-align: center;
+  transform: rotate(var(--rotation));
+  font-size: 1.5rem;
+}
+
+.clock .number1 {
+  --rotation: 30deg;
+}
+.clock .number2 {
+  --rotation: 60deg;
+}
+.clock .number3 {
+  --rotation: 90deg;
+}
+.clock .number4 {
+  --rotation: 120deg;
+}
+.clock .number5 {
+  --rotation: 150deg;
+}
+.clock .number6 {
+  --rotation: 180deg;
+}
+.clock .number7 {
+  --rotation: 210deg;
+}
+.clock .number8 {
+  --rotation: 240deg;
+}
+.clock .number9 {
+  --rotation: 270deg;
+}
+.clock .number10 {
+  --rotation: 300deg;
+}
+.clock .number11 {
+  --rotation: 330deg;
+}
+
+.clock .hand {
+  --rotation: 0;
+  position: absolute;
+  bottom: 50%;
+  left: 50%;
+  border: 1px solid white;
+  border-top-left-radius: 10px;
+  border-top-right-radius: 10px;
+  transform-origin: bottom;
+  z-index: 10;
+  transform: translateX(-50%) rotate(calc(var(--rotation) * 1deg));
+}
+
+.clock::after {
+  content: '';
+  position: absolute;
+  background-color: black;
+  z-index: 11;
+  width: 15px;
+  height: 15px;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  border-radius: 50%;
+}
+
+.clock .hand.second {
+  width: 3px;
+  height: 45%;
+  background-color: red;
+}
+
+.clock .hand.minute {
+  width: 7px;
+  height: 40%;
+  background-color: black;
+}
+
+.clock .hand.hour {
+  width: 10px;
+  height: 35%;
+  background-color: black;
+}

+ 54 - 0
src/components/TimerContainer/TimerPresentation/TimerPresentation.tsx

@@ -0,0 +1,54 @@
+import React, { useEffect } from 'react';
+
+import s from './TimerPresentation.module.css';
+
+const TimerPresentation = () => {
+  useEffect(() => {
+    function setRotation(el: HTMLDivElement, rotationRatio: number) {
+      el.style.setProperty('--rotation', String(rotationRatio * 360));
+    }
+    const hourHand = document.querySelector(
+      '[data-hour-hand]',
+    ) as HTMLDivElement;
+    const minuteHand = document.querySelector(
+      '[data-minute-hand]',
+    ) as HTMLDivElement;
+    const secondHand = document.querySelector(
+      '[data-second-hand]',
+    ) as HTMLDivElement;
+    function setClock() {
+      const currentDate = new Date();
+      const secondsRatio = currentDate.getSeconds() / 60;
+      const minutesRatio = (secondsRatio + currentDate.getMinutes()) / 60;
+      const hoursRatio = (minutesRatio + currentDate.getHours()) / 12;
+      setRotation(secondHand, secondsRatio);
+      setRotation(minuteHand, minutesRatio);
+      setRotation(hourHand, hoursRatio);
+    }
+    setClock();
+    const idInterval = setInterval(setClock, 1000);
+    return () => clearInterval(idInterval);
+  }, []);
+
+  return (
+    <div className={s.clock}>
+      <div className={s.hand + ' ' + s.hour} data-hour-hand></div>
+      <div className={s.hand + ' ' + s.minute} data-minute-hand></div>
+      <div className={s.hand + ' ' + s.second} data-second-hand></div>
+      <div className={s.number + ' ' + s.number1}>1</div>
+      <div className={s.number + ' ' + s.number2}>2</div>
+      <div className={s.number + ' ' + s.number3}>3</div>
+      <div className={s.number + ' ' + s.number4}>4</div>
+      <div className={s.number + ' ' + s.number5}>5</div>
+      <div className={s.number + ' ' + s.number6}>6</div>
+      <div className={s.number + ' ' + s.number7}>7</div>
+      <div className={s.number + ' ' + s.number8}>8</div>
+      <div className={s.number + ' ' + s.number9}>9</div>
+      <div className={s.number + ' ' + s.number10}>10</div>
+      <div className={s.number + ' ' + s.number11}>11</div>
+      <div className={s.number + ' ' + s.number12}>12</div>
+    </div>
+  );
+};
+
+export default TimerPresentation;