-
Notifications
You must be signed in to change notification settings - Fork 538
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
9481cfb
commit e786e59
Showing
12 changed files
with
349 additions
and
3 deletions.
There are no files selected for viewing
25 changes: 25 additions & 0 deletions
25
apps/react/src/challenges/advanced-counter/advanced-counter.module.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
.main { | ||
text-align: center; | ||
font-size: 1.5rem; | ||
|
||
section { | ||
margin: 1rem 0; | ||
|
||
div { | ||
margin: 1rem 0; | ||
} | ||
|
||
button { | ||
padding: 0.25rem 1rem; | ||
margin: 0 0.5rem; | ||
font-size: 1.5rem; | ||
} | ||
} | ||
|
||
input { | ||
width: 5rem; | ||
padding: 0.15rem; | ||
margin-left: 1rem; | ||
font-size: 1.5rem; | ||
} | ||
} |
65 changes: 65 additions & 0 deletions
65
apps/react/src/challenges/advanced-counter/advanced-counter.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import { MutableRefObject, useRef, useState } from 'react'; | ||
import { AsyncControls } from './components/async-controls'; | ||
import { SyncControls } from './components/sync-controls'; | ||
import { StepControl } from './components/step-control'; | ||
import { LimitControls } from './components/limit-controls'; | ||
import { DelayControl } from './components/delay-control'; | ||
|
||
import styles from './advanced-counter.module.scss'; | ||
import { maxLimit, minLimit } from './constants'; | ||
|
||
function AdvancedCounter() { | ||
const [value, setValue] = useState(0); | ||
const [step, setStep] = useState(1); | ||
const [delay, setDelay] = useState(1); | ||
const [lowerLimit, setLowerLimit] = useState(minLimit); | ||
const [upperLimit, setUpperLimit] = useState(maxLimit); | ||
const ref = useRef<{ reset: () => void }>({ reset: () => {} }); | ||
|
||
function reset() { | ||
ref.current.reset(); | ||
setValue(0); | ||
} | ||
|
||
function stepBy(stepValue: number) { | ||
setValue((prev) => { | ||
const newValue = prev + stepValue; | ||
if (lowerLimit <= newValue && newValue <= upperLimit) { | ||
return newValue; | ||
} | ||
|
||
return prev; | ||
}); | ||
} | ||
|
||
return ( | ||
<main className={styles.main}> | ||
<h2>{value}</h2> | ||
|
||
<SyncControls stepBy={stepBy} step={step} /> | ||
<AsyncControls | ||
delay={delay} | ||
stepBy={stepBy} | ||
step={step} | ||
ref={ref as MutableRefObject<{ reset: () => void }>} | ||
/> | ||
<DelayControl delay={delay} setDelay={setDelay} /> | ||
|
||
<section> | ||
<StepControl step={step} setStep={setStep} /> | ||
|
||
<LimitControls | ||
value={value} | ||
lowerLimit={lowerLimit} | ||
upperLimit={upperLimit} | ||
setLowerLimit={setLowerLimit} | ||
setUpperLimit={setUpperLimit} | ||
/> | ||
|
||
<button onClick={reset}>Reset</button> | ||
</section> | ||
</main> | ||
); | ||
} | ||
|
||
export default AdvancedCounter; |
74 changes: 74 additions & 0 deletions
74
apps/react/src/challenges/advanced-counter/components/async-controls.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import { forwardRef, MutableRefObject, useImperativeHandle, useState } from 'react'; | ||
import styles from '../advanced-counter.module.scss'; | ||
|
||
interface Props { | ||
delay: number; | ||
step: number; | ||
stepBy: (value: number) => void; | ||
} | ||
|
||
export const AsyncControls = forwardRef(function AsyncControls( | ||
{ delay, step, stepBy }: Props, | ||
ref: MutableRefObject<{ reset: () => void }> | ||
) { | ||
const [timerIds, setTimerIds] = useState<{ | ||
decrement: NodeJS.Timeout | null; | ||
increment: NodeJS.Timeout | null; | ||
}>({ | ||
decrement: null, | ||
increment: null, | ||
}); | ||
|
||
function decrementAsync() { | ||
const stepValue = step; | ||
const timerId = setTimeout(() => { | ||
stepBy(-stepValue); | ||
setTimerIds((state) => ({ ...state, decrement: null })); | ||
}, delay * 1000); | ||
|
||
setTimerIds((state) => ({ ...state, decrement: timerId })); | ||
} | ||
|
||
function incrementAsync() { | ||
const stepValue = step; | ||
const timerId = setTimeout(() => { | ||
stepBy(+stepValue); | ||
setTimerIds((state) => ({ ...state, increment: null })); | ||
}, delay * 1000); | ||
|
||
setTimerIds((state) => ({ ...state, increment: timerId })); | ||
} | ||
|
||
useImperativeHandle(ref, () => ({ | ||
reset: () => { | ||
timerIds.decrement && clearTimeout(timerIds.decrement); | ||
timerIds.increment && clearTimeout(timerIds.increment); | ||
setTimerIds({ | ||
decrement: null, | ||
increment: null, | ||
}); | ||
}, | ||
})); | ||
|
||
return ( | ||
<section className={styles.async}> | ||
<button | ||
onClick={decrementAsync} | ||
aria-label="Async Decrement" | ||
disabled={!!timerIds.decrement} | ||
className={styles.asyncButton} | ||
> | ||
async - | ||
</button> | ||
|
||
<button | ||
onClick={incrementAsync} | ||
aria-label="Async Increment" | ||
disabled={!!timerIds.increment} | ||
className={styles.asyncButton} | ||
> | ||
+ async | ||
</button> | ||
</section> | ||
); | ||
}); |
32 changes: 32 additions & 0 deletions
32
apps/react/src/challenges/advanced-counter/components/delay-control.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { maxDelay, minDelay } from '../constants'; | ||
|
||
interface Props { | ||
delay: number; | ||
setDelay: React.Dispatch<React.SetStateAction<number>>; | ||
} | ||
|
||
export function DelayControl({ delay, setDelay }: Props) { | ||
function handleChange(e: React.ChangeEvent<HTMLInputElement>) { | ||
const inputDelay = (e.target as HTMLInputElement).valueAsNumber; | ||
setDelay(inputDelay); | ||
} | ||
|
||
return ( | ||
<div className="flex-center"> | ||
<label htmlFor="delay">Delay</label> | ||
|
||
<input | ||
type="range" | ||
id="delay" | ||
title="Delay value" | ||
value={delay} | ||
onChange={handleChange} | ||
min={minDelay} | ||
max={maxDelay} | ||
step="1" | ||
/> | ||
|
||
<output htmlFor="delay">{delay}s</output> | ||
</div> | ||
); | ||
} |
75 changes: 75 additions & 0 deletions
75
apps/react/src/challenges/advanced-counter/components/limit-controls.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import { maxLimit, minLimit } from '../constants'; | ||
|
||
interface Props { | ||
value: number; | ||
lowerLimit: number; | ||
upperLimit: number; | ||
setLowerLimit: React.Dispatch<React.SetStateAction<number>>; | ||
setUpperLimit: React.Dispatch<React.SetStateAction<number>>; | ||
} | ||
|
||
export function LimitControls({ | ||
value, | ||
lowerLimit, | ||
upperLimit, | ||
setLowerLimit, | ||
setUpperLimit, | ||
}: Props) { | ||
function lowerLimitHandler(e: React.ChangeEvent<HTMLInputElement>) { | ||
const inputValue = e.target.valueAsNumber; | ||
|
||
if (Number.isNaN(inputValue)) { | ||
return setLowerLimit(minLimit); | ||
} else if (inputValue > upperLimit) { | ||
setLowerLimit(upperLimit); | ||
} else if (inputValue < minLimit) { | ||
setLowerLimit(minLimit); | ||
} else if (inputValue > value) { | ||
setLowerLimit(value); | ||
} else { | ||
setLowerLimit(inputValue); | ||
} | ||
} | ||
|
||
function upperLimitHandler(e: React.ChangeEvent<HTMLInputElement>) { | ||
const inputValue = e.target.valueAsNumber; | ||
|
||
if (Number.isNaN(inputValue)) { | ||
return setUpperLimit(maxLimit); | ||
} else if (inputValue < lowerLimit) { | ||
setUpperLimit(lowerLimit); | ||
} else if (inputValue > maxLimit) { | ||
setUpperLimit(maxLimit); | ||
} else if (inputValue < value) { | ||
setLowerLimit(value); | ||
} else { | ||
setUpperLimit(inputValue); | ||
} | ||
} | ||
|
||
return ( | ||
<> | ||
<div> | ||
<label htmlFor="lowerLimit">Lower Limit</label> | ||
<input | ||
type="number" | ||
id="lowerLimit" | ||
title="Lower Limit" | ||
value={lowerLimit} | ||
onChange={lowerLimitHandler} | ||
/> | ||
</div> | ||
|
||
<div> | ||
<label htmlFor="upperLimit">Upper Limit</label> | ||
<input | ||
type="number" | ||
id="upperLimit" | ||
title="Upper Limit" | ||
value={upperLimit} | ||
onChange={upperLimitHandler} | ||
/> | ||
</div> | ||
</> | ||
); | ||
} |
37 changes: 37 additions & 0 deletions
37
apps/react/src/challenges/advanced-counter/components/step-control.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { maxStep, minStep } from '../constants'; | ||
|
||
interface Props { | ||
step: number; | ||
setStep: React.Dispatch<React.SetStateAction<number>>; | ||
} | ||
|
||
export const StepControl = function Step({ step, setStep }: Props) { | ||
function handleChange(e: React.ChangeEvent<HTMLInputElement>) { | ||
const value = e.target.valueAsNumber; | ||
|
||
if (Number.isNaN(value)) { | ||
return setStep(1); | ||
} else if (value > maxStep) { | ||
setStep(maxStep); | ||
} else if (value < minStep) { | ||
setStep(minStep); | ||
} else { | ||
setStep(value); | ||
} | ||
} | ||
|
||
return ( | ||
<div> | ||
<label htmlFor="step">Increment/Decrement by</label> | ||
<input | ||
id="step" | ||
type="number" | ||
title="Step value" | ||
min={minStep} | ||
max={maxStep} | ||
value={step} | ||
onChange={handleChange} | ||
/> | ||
</div> | ||
); | ||
}; |
19 changes: 19 additions & 0 deletions
19
apps/react/src/challenges/advanced-counter/components/sync-controls.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import styles from '../advanced-counter.module.scss'; | ||
|
||
interface Props { | ||
stepBy: (value: number) => void; | ||
step: number; | ||
} | ||
|
||
export function SyncControls({ stepBy, step }: Props) { | ||
return ( | ||
<section className={styles.async}> | ||
<button onClick={() => stepBy(-step)} aria-label="Decrement"> | ||
- | ||
</button> | ||
<button onClick={() => stepBy(step)} aria-label="Increment"> | ||
+ | ||
</button> | ||
</section> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
export const minStep = 1; | ||
export const maxStep = 100; | ||
export const minDelay = 1; | ||
export const maxDelay = 3; | ||
export const minLimit = -1000; | ||
export const maxLimit = 1000; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters