Skip to content

Commit

Permalink
Merge branch 'unit/29-countdown'
Browse files Browse the repository at this point in the history
  • Loading branch information
ElynnaChuang committed May 28, 2023
2 parents b860a7f + 7b95db2 commit c199734
Show file tree
Hide file tree
Showing 15 changed files with 438 additions and 0 deletions.
14 changes: 14 additions & 0 deletions src/Components/29Timer/Button/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import styles from './styles.module.scss';

export const Button = ({ text, icon, btnStyle = 'normal', disabled, onClick }) => {
const btnStyleClass = styles[btnStyle];
return (
<button
className={`${styles.btn} ${btnStyleClass}`}
onClick={onClick}
disabled={disabled}
>
{icon} {text}
</button>
);
};
39 changes: 39 additions & 0 deletions src/Components/29Timer/Button/styles.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
@use '../color' as c;

.btn {
cursor: pointer;

display: flex;
align-items: center;
justify-content: center;

width: 100%;
padding: 0.5rem 1rem;

font-weight: 500;
color: #fff;

border-radius: 0.5rem;
box-shadow: inset 0 0 0 2px c.$normal;

transition: all 0.2s ease-out;

&:hover {
background-color: rgba($color: c.$normal, $alpha: 20%);
box-shadow: inset 0 0 0 2px c.$normal, 0 0 8px 0 c.$normal;
}

&:disabled {
cursor: not-allowed;
opacity: 0.3;
}
}

.primary {
box-shadow: inset 0 0 0 2px c.$primary_500;

&:hover {
background-color: rgba($color: c.$primary_500, $alpha: 20%);
box-shadow: inset 0 0 0 2px c.$primary_500, 0 0 8px 0 c.$primary_500;
}
}
10 changes: 10 additions & 0 deletions src/Components/29Timer/Model/ModelContent/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import styles from './style.module.scss';

export const ModalContent = () => {
return (
<div className={styles.container}>
<div className={styles.backDrop} />
<div className={styles.card}>Times Up!</div>
</div>
);
};
41 changes: 41 additions & 0 deletions src/Components/29Timer/Model/ModelContent/style.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
@use '../../color' as c;

.container {
position: absolute;
z-index: 99;
top: 0;
left: 0;

width: 100vw;
height: 100vh;
}

.backDrop {
width: 100%;
height: 100%;
background-color: rgba($color: #000, $alpha: 60%);
}

.card {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);

display: flex;
align-items: center;
justify-content: center;

width: 80%;
max-width: 500px;
height: 20%;
padding: 2rem;

font-size: 2rem;
font-weight: 700;
color: c.$primary_500;

background-color: rgba($color: c.$black, $alpha: 80%);
border: 4px solid c.$primary_100;
border-radius: 0.5rem;
}
18 changes: 18 additions & 0 deletions src/Components/29Timer/Model/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useEffect, useState } from 'react';
import { createPortal } from 'react-dom';
import { ModalContent } from './ModelContent';

export const TimeNotification = ({ open }) => {
const [showModal, setShowModal] = useState(false);

useEffect(() => {
setShowModal(open);
}, [open]);

return (
<>
<p style={{ display: 'none' }}>Result</p>
{showModal && createPortal(<ModalContent />, document.body)}
</>
);
};
27 changes: 27 additions & 0 deletions src/Components/29Timer/TimeInput/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import styles from './styles.module.scss';

export const TimeInput = ({
id,
min,
max,
label,
startCount,
countdownValue,
inputValue,
onChange,
}) => {
return (
<div className={styles.time_input}>
<label htmlFor='min'>{label}</label>
<input
id={id}
type='number'
min={min}
max={max}
value={startCount ? countdownValue : inputValue}
onChange={startCount ? () => {} : onChange}
disabled={startCount}
/>
</div>
);
};
54 changes: 54 additions & 0 deletions src/Components/29Timer/TimeInput/styles.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
@use '../color' as c;

@import 'https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@300;400;500;600;700&display=swap';

$input_border: c.$primary_500;

.time_input {
overflow: hidden;
display: flex;
flex: 1;
flex-direction: column;

border: 2px solid $input_border;
border-radius: 1rem;
}

label {
padding: 0.5rem 1rem;

font-size: 1.25rem;
font-weight: 500;
color: c.$black;
text-align: center;

background-color: c.$primary_500;
border-bottom: 2px solid $input_border;
}

input[type='number'] {
cursor: pointer;

padding: 1rem;

font-family: 'Roboto Mono', monospace;
font-size: 3rem;
color: c.$normal;
text-align: center;

appearance: none;
background-color: rgba($color: c.$primary_500, $alpha: 5%);
border: none;

&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button {
margin: 0;
appearance: none;
}

&:disabled {
cursor: not-allowed;
color: c.$primary_900;
background-color: rgba($color: c.$primary_500, $alpha: 20%);
}
}
5 changes: 5 additions & 0 deletions src/Components/29Timer/_color.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
$primary_100: #affffc;
$primary_500: #7cfffd;
$primary_900: #2ceeeb;
$normal: #c7c7c7;
$black: #0f1b2a;
11 changes: 11 additions & 0 deletions src/Components/29Timer/helpers.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
function secToMin(secLeft) {
const min = Math.floor(secLeft / 60);
const sec = secLeft % 60;
return { min, sec };
}

function padStart(num) {
return num >= 10 ? `${num}` : `0${num}`;
}

export { secToMin, padStart };
148 changes: 148 additions & 0 deletions src/Components/29Timer/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { useEffect, useState } from 'react';
import { PlayArrowRounded, PauseRounded, RestartAltRounded } from '@mui/icons-material';
import styles from './styles.module.scss';

import { Button } from './Button';
import { TimeInput } from './TimeInput';
import { secToMin, padStart } from './helpers';
import { TimeNotification } from './Model';

export const Timer = () => {
const [start, setStart] = useState(false);
const [end, setEnd] = useState(false);

const [timeValue, setTimeValue] = useState({ min: 0, sec: 0 });
const [timeLeft, setTimeLeft] = useState({ min: 0, sec: 0 });

useEffect(() => {
let countDown;
if (start) {
let secLeft = timeValue.min * 60 + timeValue.sec;
setTimeLeft(() => secToMin(secLeft));

countDown = setInterval(() => {
secLeft -= 1;

if (secLeft < 0) {
setStart(false);

setEnd(true);
setTimeout(() => setEnd(false), 1500);

return clearInterval(countDown);
}

return setTimeLeft(() => secToMin(secLeft));
}, 1000);
}

return () => clearInterval(countDown);
}, [start]);

const handleInputValue = e => {
const { id, value, max, min } = e.target;
if (value > Number(max))
return setTimeValue(prev => ({ ...prev, [id]: Number(max) }));

if (value < Number(min))
return setTimeValue(prev => ({ ...prev, [id]: Number(min) }));

return setTimeValue(prev => ({ ...prev, [id]: Number(value) }));
};

const handleStart = () => {
setStart(true);
};

const handlePause = () => {
setTimeValue(timeLeft);
setStart(false);
};

const handleReset = () => {
setTimeValue({ min: 0, sec: 0 });
setStart(false);
};

const handleAdjustTime = secAdjust => {
const maxTotalSec = 59 * 60 + 59;
const nowTotalSec = timeValue.min * 60 + timeValue.sec;

if (nowTotalSec === 0 && secAdjust < 0) return;
if (nowTotalSec === maxTotalSec && secAdjust > 0) return;

const newTotalSec = nowTotalSec + secAdjust;

if (newTotalSec < 0) {
setTimeValue({ min: 0, sec: 0 });
} else if (newTotalSec > maxTotalSec) {
setTimeValue({ min: 59, sec: 59 });
} else {
setTimeValue(secToMin(newTotalSec));
}
};

return (
<div className={styles.timer}>
<div className={styles.controls}>
<div className={styles.time_value}>
<Button text='-2m' onClick={() => handleAdjustTime(-120)} disabled={start} />
<Button text='-15s' onClick={() => handleAdjustTime(-15)} disabled={start} />
<Button text='-1s' onClick={() => handleAdjustTime(-1)} disabled={start} />
<Button text='+1s' onClick={() => handleAdjustTime(1)} disabled={start} />
<Button text='+15s' onClick={() => handleAdjustTime(15)} disabled={start} />
<Button text='+2m' onClick={() => handleAdjustTime(120)} disabled={start} />
</div>

<div className={styles.time_run}>
<Button
icon={<RestartAltRounded />}
text='Reset'
onClick={handleReset}
btnStyle='primary'
/>
<Button
btnStyle='primary'
icon={<PlayArrowRounded />}
text='Start'
onClick={handleStart}
disabled={start}
/>
<Button
btnStyle='primary'
icon={<PauseRounded />}
text='Pause'
onClick={handlePause}
disabled={!start}
/>
</div>
</div>

<div className={styles.inputs}>
<TimeInput
id='min'
label='Minutes'
min={0}
max={59}
startCount={start}
countdownValue={padStart(timeLeft.min)}
inputValue={padStart(timeValue.min)}
onChange={handleInputValue}
/>
<span>:</span>
<TimeInput
id='sec'
label='Seconds'
min={0}
max={59}
startCount={start}
countdownValue={padStart(timeLeft.sec)}
inputValue={padStart(timeValue.sec)}
onChange={handleInputValue}
/>
</div>

<TimeNotification open={end} />
</div>
);
};
Loading

0 comments on commit c199734

Please sign in to comment.