Browse Source

homework20 done

holevchuk.evgeny 1 year ago
parent
commit
c0da1530b6

+ 326 - 6
my-app/src/App.js

@@ -1,4 +1,11 @@
+import ClockFace from "./assets/images/ClockFace.png";
+import ClockFace_H from "./assets/images/ClockFace_H.png";
+import ClockFace_M from "./assets/images/ClockFace_M.png";
+import ClockFace_S from "./assets/images/ClockFace_S.png";
 import './assets/scss/_page-styles/rickandmortyapi.scss';
+import './assets/scss/_page-styles/common.scss';
+import './assets/scss/_page-styles/clock-face.scss';
+import React, {useState, useEffect, useRef} from 'react';
 
 // hw19 (22.10.2022)
 
@@ -24,7 +31,9 @@ const EpisodeItem = ({name, air_date, children}) =>
 
 const Episode = () =>
     dataEpisodes.map(item => (
-        <div className="episode" key={Math.random()}>
+        <div className="episode"
+             key={Math.random()}
+        >
             <EpisodeItem name={item.name}
                          air_date={item.air_date}
             >
@@ -41,11 +50,322 @@ const Episode = () =>
         </div>
     ))
 
-const App = () =>
-    <div className="App">
-        <section className="episodes">
-            <Episode />
+
+// React JSX Homework - hw20 (22.10.2022)
+
+// Spoiler
+const Spoiler = ({header="+", open, children}) => {
+    const [isOpen, setIsOpen] = useState(open)
+    return (
+        <section>
+            <div onClick={() => setIsOpen(!isOpen)}>{header}</div>
+            {isOpen && children}
         </section>
-    </div>
+    )
+}
+
+// RangeInput
+const RangeInput = ({min, max}) => {
+    const [text, setText] = useState('');
+    return (
+        <input type="text"
+               value={text}
+               style={{borderColor: (text.length > max || text.length < min) ? 'red' : 'rgb(118, 118, 118)'}}
+               onChange={event => setText(event.target.value)}
+        />
+    )
+}
+
+// LoginForm
+const LoginForm = ({onLogin}) => {
+    const [login, setLogin] = useState('');
+    const [password, setPassword] = useState('');
+
+    return(
+        <fieldset>
+            <input placeholder="login"
+                   type="text"
+                   name="login"
+                   value={login}
+                   onChange={e => setLogin(e.target.value)}
+            /><br/>
+            <input placeholder="password"
+                   type="password"
+                   name="password"
+                   value={password}
+                   onChange={e => setPassword(e.target.value)}
+            /><br/>
+            <button className="login-button"
+                    onClick={() => onLogin(login, password)}
+                    type="button"
+                    disabled={!(login.length > 0 && password.length > 0)}
+            >Login</button>
+        </fieldset>
+    )
+}
+
+// PasswordConfirm
+
+const PasswordConfirm = ({min}) => {
+    const [password, setPassword] = useState('');
+    const [confirmPassword, setConfirmPassword] = useState('');
+
+    return(
+        <fieldset>
+            <input placeholder="password"
+                   type="password"
+                   name="password"
+                   value={password}
+                   style={{borderColor: (password.length >= min && password === confirmPassword) ? 'gray' : 'red'}}
+                   onChange={e => setPassword(e.target.value)}
+            /><br/>
+            <input placeholder="confirm password"
+                   type="password"
+                   name="confirmPassword"
+                   value={confirmPassword}
+                   style={{borderColor: (confirmPassword.length >= min && confirmPassword === password) ? 'gray' : 'red'}}
+                   onChange={e => setConfirmPassword(e.target.value)}
+            />
+        </fieldset>
+    )
+}
+
+// Timer
+const Timer = ({seconds}) => {
+    const [count, setCount] = useState(seconds);
+    const intervalRef = useRef(null)
+    useEffect(() => {
+        intervalRef.current = setInterval(() => {
+            setCount(count => count - 1)
+        }, 1000);
+        return () => {
+            clearInterval(intervalRef.current);
+        }
+    }, [seconds]);
+    const pause = () => {
+        clearInterval(intervalRef.current);
+    }
+    if(count === 0) pause();
+    const ISODate = (start, end) => new Date(count * 1000).toISOString().slice(start, end);
+    return(
+        <div>
+            <span>{ISODate(11, 13)}</span>:
+            <span>{ISODate(14, 16)}</span>:
+            <span>{ISODate(17, 19)}</span>
+            <button className="default-button"
+                    type="button"
+                    onClick={pause}
+            >Pause</button>
+        </div>
+    )
+}
+
+// TimerControl
+const TimerControl = () => {
+    const [hoursVal, setHoursVal] = useState(10);
+    const [minutesVal, setMinutesVal] = useState(0);
+    const [secondsVal, setSecondsVal] = useState(0);
+    const [isStart, setStart] = useState(false);
+    const totalSeconds = (hoursVal * 3600) + (minutesVal * 60) + secondsVal;
+
+    return(
+        <fieldset>
+            <label>
+                часы
+                <input type="text"
+                       name="hours"
+                       value={hoursVal}
+                       onChange={event => setHoursVal(+event.target.value)}
+                />
+            </label>
+            <label>
+                минуты
+                <input type="text"
+                       name="minutes"
+                       value={minutesVal}
+                       onChange={event => setMinutesVal(+event.target.value)}
+                />
+            </label>
+            <label>
+                секунды
+                <input type="text"
+                       name="seconds"
+                       value={secondsVal}
+                       onChange={event => setSecondsVal(+event.target.value)}
+                />
+            </label>
+            <button type="button"
+                    className="default-button"
+                    onClick={() => setStart(!isStart)}
+            >Start Timer</button>
+            {isStart && <Timer seconds={totalSeconds}/>}
+        </fieldset>
+    )
+}
+
+// TimerContainer
+const SecondsTimer = ({seconds}) => <h2>{seconds}</h2>
+
+const TimerContainer = ({seconds, refresh, render}) => {
+    const [count, setCount] = useState(seconds);
+    useEffect(() => {
+        const intervalID = setInterval(() => {
+            setCount(count => count - 1)
+        }, refresh);
+        return () => {
+            clearInterval(intervalID);
+        }
+    }, [seconds]);
+    const RenderProps = render;
+    return(
+        <RenderProps seconds={count}/>
+    );
+}
+
+
+// LCD
+
+const TimerPresentation  = ({seconds}) => {
+    const ISODate = (start, end) => new Date(seconds * 1000).toISOString().slice(start, end);
+    return(
+        <div>
+            <span>{ISODate(11, 13)}</span>:
+            <span>{ISODate(14, 16)}</span>:
+            <span>{ISODate(17, 19)}</span>
+        </div>
+    )
+}
+
+
+// Watch
+
+const Watch = ({seconds}) => {
+    const ISODate = (start, end) => new Date(seconds * 1000).toISOString().slice(start, end);
+
+    return (
+        <figure className="clock-face">
+            <img className="clock-face__bg"
+                 src={ClockFace}
+                 width="400"
+                 height="400"
+                 alt="циферблат"
+            />
+            <img className="clock-face__element"
+                 src={ClockFace_H}
+                 width="400"
+                 height="400"
+                 alt="стрелка часовая"
+                 style={{rotate: `${ISODate(11, 13)*30}deg`}}
+            />
+            <img className="clock-face__element"
+                 src={ClockFace_M}
+                 width="400"
+                 height="400"
+                 alt="стрелка минутная"
+                 style={{rotate: `${ISODate(14, 16)*6}deg`}}
+            />
+            <img className="clock-face__element"
+                 src={ClockFace_S}
+                 width="400"
+                 height="400"
+                 alt="стрелка секундная"
+                 style={{rotate: `${ISODate(17, 19)*6}deg`}}
+            />
+        </figure>
+    )
+}
+
+
+// TimerControl + TimerContainer
+const TimerControlContainer = ({render}) => {
+    const [hoursVal, setHoursVal] = useState(0);
+    const [minutesVal, setMinutesVal] = useState(0);
+    const [secondsVal, setSecondsVal] = useState(0);
+    const [isStart, setStart] = useState(false);
+    const totalSeconds = (hoursVal * 3600) + (minutesVal * 60) + secondsVal;
+
+    return(
+        <fieldset>
+            <label>
+                часы
+                <input type="text"
+                       name="hours"
+                       value={hoursVal}
+                       onChange={event => setHoursVal(+event.target.value)}
+                />
+            </label>
+            <label>
+                минуты
+                <input type="text"
+                       name="minutes"
+                       value={minutesVal}
+                       onChange={event => setMinutesVal(+event.target.value)}
+                />
+            </label>
+            <label>
+                секунды
+                <input type="text"
+                       name="seconds"
+                       value={secondsVal}
+                       onChange={event => setSecondsVal(+event.target.value)}
+                />
+            </label>
+            <button type="button"
+                    className="default-button"
+                    onClick={() => setStart(!isStart)}
+            >Start Timer</button>
+            {isStart && <TimerContainer seconds={totalSeconds} render={render} refresh={150}/>}
+        </fieldset>
+    )
+}
+
+
+const App = () => {
+
+    return (
+        <div className="App">
+            {/*<section className="episodes">
+                <Episode />
+            </section>*/}
+
+            <Spoiler header={<h1>Заголовок</h1>} open>
+                Контент 1
+                <p>
+                    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Commodi, eaque?
+                </p>
+            </Spoiler>
+            <br/>
+            <Spoiler>
+                <h2>Контент 2</h2>
+                <p>
+                    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aperiam consectetur consequatur corporis dolores, laborum minus repellat sequi! Aliquam, aspernatur, ipsam?
+                </p>
+            </Spoiler>
+            <br/>
+            <RangeInput min={5} max={20} />
+            <br/><br/>
+            <LoginForm onLogin={(login, password) => console.log({login, password})}/>
+            <br/>
+            <PasswordConfirm min={2} />
+            <br/>
+            <Timer seconds={50}/>
+            <TimerControl />
+            <TimerContainer seconds={1800}
+                            refresh={1000}
+                            render={SecondsTimer}
+            />
+            <TimerContainer seconds={500}
+                            refresh={500}
+                            render={TimerPresentation}
+            />
+            <TimerContainer seconds={30800}
+                            refresh={1000}
+                            render={Watch}
+            />
+            <TimerControlContainer render={Watch} />
+        </div>
+    )
+}
+
 
 export default App;

BIN
my-app/src/assets/images/ClockFace.png


BIN
my-app/src/assets/images/ClockFace_H.png


BIN
my-app/src/assets/images/ClockFace_M.png


BIN
my-app/src/assets/images/ClockFace_S.png


+ 9 - 0
my-app/src/assets/scss/_page-styles/clock-face.scss

@@ -0,0 +1,9 @@
+.clock-face {
+	position: relative;
+
+	&__element {
+		position: absolute;
+		top: 0;
+		left: 0;
+	}
+}

+ 55 - 0
my-app/src/assets/scss/_page-styles/common.scss

@@ -0,0 +1,55 @@
+.login-button {
+	justify-self: start;
+	height: 40px;
+	min-width: 150px;
+	display: inline-flex;
+	align-items: center;
+	justify-content: center;
+	padding: 0 12px;
+	font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+	font-size: 16px;
+	font-weight: 400;
+	color:  rgb(255, 255, 255);
+	text-align: center;
+	text-decoration: none;
+	vertical-align: middle;
+	cursor: pointer;
+	user-select: none;
+	border: 1px solid #0d6efd;;
+	border-radius: 6px;
+	background-color: #0d6efd;
+	transition: color .15s ease-in-out,
+	background-color .15s ease-in-out,
+	border-color .15s ease-in-out,
+	box-shadow .15s ease-in-out;
+
+	&:focus {
+		color: #fff;
+		background-color: #0b5ed7;
+		border-color: #0a58ca;
+		outline: 0;
+		box-shadow: 0 0 0 0.25rem rgba(49,132,253,.5);
+	}
+
+	&:disabled {
+		pointer-events: none;
+		color: #fff;
+		background-color: #64686e;
+		border-color: #64686e;
+	}
+}
+
+.default-button {
+	color: black;
+	padding: 8px 16px;
+	border-radius: 4px;
+	background-color: gray;
+
+	&:hover {
+		background-color: lighten(gray, 20%);
+	}
+
+	&:active {
+		background-color: darken(gray, 1%);
+	}
+}