diff --git a/src/App.css b/src/App.css index e5deef95..83ec1edd 100644 --- a/src/App.css +++ b/src/App.css @@ -1,71 +1,40 @@ -html, body, #root, .stopwatch, .stopwatch-content, .stopwatch-buttons { - height: -webkit-fill-available; -} - body { - background-color: #f1f1f1; - margin: 0px; -} - -.stopwatch-title { - background-color: #303030; - margin: 0px; - color: white; - padding-left: 16px; - padding: 10px 0px 10px 16px; -} - -.stopwatch-content { - display: flex; + margin: 0; + background-color: lightgreen; } -.stopwatch-buttons { +.stopwatch-container { + height: 100vh; display: flex; flex-direction: column; - background-color: #ebebeb; - padding: 16px 12px; - width: 200px; -} - -.stopwatch-buttons button:focus { - outline: none; - border: 2px solid #000000; + justify-content: center; + align-items: center; } -.stopwatch-buttons button { - margin: 7px 0px; - background-color: #fafafa; - border: 0px solid #fafafa; - text-align: left; - border-radius: 0.5rem; - padding: 7px 0px 7px 15px; - box-shadow: 0.5px 0.5px gray; -} - -.stopwatch-time { - margin-left: auto; - margin-right: auto; - margin-top: 20px; - padding: 50px; - background-color: #ffffff; - height: fit-content; - border-radius: 0.75rem; - width: 50%; - text-align: -webkit-center; - box-shadow: 0.5px 0.5px gray; +.timer-display { + display: flex; + color: #ccc; } -.stopwatch-time p { - font-size: xxx-large; +.timer-display p { + margin: 0; + font-size: 4rem; + letter-spacing: 2px; } -.stopwatch-laptimes ul { - list-style: none; - padding: 0px; +.timer-display span { + font-size: 3rem; + position: relative; + top: 8px; + letter-spacing: 2px; } -.stopwatch-laptimes li { - padding: 10px 0px; - border-bottom: 1px solid #ebebeb; - font-size: x-large; -} \ No newline at end of file +.stopwatch-controls-container button { + margin: 15px; + border: none; + background-color: #e5ad3d; + padding: 3px 12px; + font-size: 1.3rem; + font-weight: 400; + cursor: pointer; +} \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index 8c90fc53..0d868082 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,9 +1,105 @@ -import React from 'react' -import './App.css' -import StopWatch from './StopWatch' +import React, { useEffect, useState } from 'react'; +import StopWatch from './StopWatch'; +import './css/App.css'; +import StopWatchButton from './StopWatchButton'; export default function App() { - return( - + // State variables for managing stopwatch functionality + const [isRunning, setIsRunning] = useState(false); + const [reset, setReset] = useState(false); + const [time, setTime] = useState(0); + const [timeDisplay, setTimeDisplay] = useState>([]); + const [laps, setLaps] = useState>([]); + + // Converts time in seconds to a displayable format (HH:MM:SS) + const convertTimeToDisplay = (time: number): (number | string)[] => { + const hours = Math.floor(time / 3600); + const minutes = Math.floor((time - hours * 3600) / 60); + const seconds = time - minutes * 60 - hours * 3600; + return [ + hours < 10 ? `0${hours}` : hours, + minutes < 10 ? `0${minutes}` : minutes, + seconds < 10 ? `0${seconds}` : seconds + ]; + } + + // Handles the start button click, sets isRunning to true and initializes laps if the timer has not started + const handleOnStart = () => { + setIsRunning(true); + if (time === 0) { + initializeLaps(); + } + } + + // Handles the stop button click, sets isRunning to false to pause the timer + const handleOnStop = () => { + setIsRunning(false); + } + + // Handles the reset button click, sets reset to true triggering a reset in useEffect + const handleReset = () => { + setReset(true); + } + + // Initializes laps with time set to 0 + const initializeLaps = () => { + setLaps([0]); + } + + // Handles the lap button click, adds the current time to the laps array + const handleLap = () => { + if (time !== 0) { + setLaps((prevLaps) => [...prevLaps, time]); + } + } + + // useEffect to manage timer logic and updates + useEffect(() => { + let intervalId: NodeJS.Timeout; + + // Increment time every 1000ms if isRunning is true + if (isRunning) { + intervalId = setInterval(() => setTime((prevTime) => prevTime + 1), 1000); + } else { + clearInterval(intervalId); // Stop the timer if isRunning is false + } + + // Reset the timer, laps, and display when reset is true + if (reset) { + setIsRunning(false); + setTime(0); + setReset(false); + setLaps([]); + } + + // Update time display + setTimeDisplay(convertTimeToDisplay(time)); + + // Cleanup: clear the interval to avoid memory leaks + return () => { + clearInterval(intervalId); + }; + }, [reset, isRunning, time]); + + // App display with Stopwatch, buttons, and lap list + return ( +
+ +
+ + + + +
+
+
    + {laps.map((lap, index, array) => ( +
  • + {index > 0 ? `Lap ${index}: ${lap - array[index - 1]} seconds` : ''} +
  • + ))} +
+
+
) -} \ No newline at end of file +} diff --git a/src/StopWatch.tsx b/src/StopWatch.tsx index 2e06473c..11d9773d 100644 --- a/src/StopWatch.tsx +++ b/src/StopWatch.tsx @@ -1,77 +1,15 @@ -import React, { useState, useEffect, useCallback } from 'react' -import StopWatchButton from './StopWatchButton' - -// Function to format the time. This is necessary since both the time and lap times need to be formatted -export function formatTime(time: number): string { - // Format the time in mm:ss:ms. Display hours only if reached - const hours = Math.floor(time / 360000); - const minutes = Math.floor((time % 360000) / 6000); - const seconds = Math.floor((time % 6000) / 100); - const milliseconds = time % 100; - // Format the minutes, seconds, and milliseconds to be two digits - const formattedMinutes = minutes.toString().padStart(2, '0'); - const formattedSeconds = seconds.toString().padStart(2, '0'); - const formattedMilliseconds = milliseconds.toString().padStart(2, '0'); - // If stopwatch reaches at least an hour, display the hours - if (hours > 0) { - const formattedHours = hours.toString().padStart(2, '0'); - return `${formattedHours}:${formattedMinutes}:${formattedSeconds}:${formattedMilliseconds}`; - } - // Combine the values into a string - const formattedTime = `${formattedMinutes}:${formattedSeconds}:${formattedMilliseconds}`; - return formattedTime; -} - -export default function StopWatch() { - // State to track the time, whether the timer is on/off, and the lap times - const [time, setTime] = useState(0); - const [timerOn, setTimerOn] = useState(false); - const [lapTimes, setLapTimes] = useState([]); - - // Stops the timer, resets the time, and clears the lap times. useCallback is used to prevent unnecessary re-renders - const handleReset = useCallback(() => { - setTimerOn(false); - setTime(0); - setLapTimes([]); - }, []); - - // Every time timerOn changes, we start or stop the timer - // useEffect is necessary since setInterval changes the state and we don't want to create an infinite loop - useEffect(() => { - let interval: ReturnType | null = null; - - if (timerOn) { - interval = setInterval(() => setTime(time => time + 1), 10) - } - - return () => {clearInterval(interval)} // Clears the interval when the component unmounts or timerOn changes - }, [timerOn]) - - return( -
-

StopWatch

-
-
- setTimerOn(true)}> - setTimerOn(false)}> - setLapTimes([...lapTimes, time])} timerOn={timerOn} lapTimes={lapTimes}> - -
-
-

{formatTime(time)}

- {/* Display the numbered lap times */} - {lapTimes.length > 0 && ( -
-

Lap times

-
    - {lapTimes.map((lapTime, index) => { - return
  • {(index + 1)+'.'} {formatTime(lapTime)}
  • - })} -
-
- )} -
-
+import React from 'react'; +import './css/App.css'; +import { IStopWatchProps } from './type/InterfaceSWT'; + +export default function StopWatch(props: IStopWatchProps) { + return ( +
+

{props.timeDisplay[0]}

+ : +

{props.timeDisplay[1]}

+ : +

{props.timeDisplay[2]}

) } \ No newline at end of file diff --git a/src/StopWatchButton.tsx b/src/StopWatchButton.tsx index 7f12a5f1..98abebe2 100644 --- a/src/StopWatchButton.tsx +++ b/src/StopWatchButton.tsx @@ -1,54 +1,17 @@ -import React from 'react' +import React from 'react'; +import './css/App.css'; +import { IStopWatchProps } from './type/InterfaceSWT'; -// Maximum number of laps that can be recorded -const maxLaps = 25; +export default function StopWatch(props: IStopWatchProps) { -// Define the props for the StopWatchButton component -type StopWatchButtonProps = { - type: 'start' | 'stop' | 'lap' | 'reset'; - onClick?: () => void; - timerOn?: boolean; - time?: number; - lapTimes?: number[]; -}; - - export default function StopWatchButton({ type, onClick, timerOn, time, lapTimes }: StopWatchButtonProps) { - // Determine the button text based on the type and add corresponding tabIndex - let buttonText, tabIndex; - switch(type) { - case 'start': - buttonText = 'Start'; - tabIndex = 1; - break; - case 'stop': - buttonText = 'Stop'; - tabIndex = 2; - break; - case 'lap': - buttonText = 'Record Lap'; - tabIndex = 3; - break; - case 'reset': - buttonText = 'Reset'; - tabIndex = 4; - break; - default: - buttonText = ''; - tabIndex = 0; - } - // Determine whether the reset or lap buttons should be disabled - const isLapDisabled = !timerOn || (lapTimes && lapTimes.length === 25); - const isResetDisabled = time === 0; - return( - + // Stopwatch Display with hours, minutes, seconds + return ( +
+

{props.timeDisplay[0]}

+ : +

{props.timeDisplay[1]}

+ : +

{props.timeDisplay[2]}

+
) } \ No newline at end of file diff --git a/src/type/InterfaceSWBP.tsx b/src/type/InterfaceSWBP.tsx new file mode 100644 index 00000000..82dd601d --- /dev/null +++ b/src/type/InterfaceSWBP.tsx @@ -0,0 +1,4 @@ +export interface IStopWatchButtonProps { + type: string, + onClick: (e:any) => void, +} \ No newline at end of file diff --git a/src/type/InterfaceSWT.tsx b/src/type/InterfaceSWT.tsx new file mode 100644 index 00000000..0cad3aaf --- /dev/null +++ b/src/type/InterfaceSWT.tsx @@ -0,0 +1,3 @@ +export interface IStopWatchProps { + timeDisplay: (string | number)[] +}