Skip to content

Commit

Permalink
React: Analog Clock
Browse files Browse the repository at this point in the history
  • Loading branch information
sadanandpai committed Jul 11, 2024
1 parent 41ffdac commit 8026359
Show file tree
Hide file tree
Showing 8 changed files with 231 additions and 6 deletions.
8 changes: 4 additions & 4 deletions apps/javascript/src/challenges/analog-clock/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,17 @@
}

.tick:nth-child(5n + 1) {
width: 4px;
width: 0.25%;
height: 1.5%;
}

.digits {
width: 93%;
height: 93%;
position: absolute;
width: 90%;
height: 90%;
color: var(--digit-color);
font-weight: 600;
font-size: min(3vh, 3vw);
font-size: min(4vh, 4vw);
}

.digit {
Expand Down
46 changes: 46 additions & 0 deletions apps/react/src/challenges/analog-clock/analog-clock.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { useEffect, useState } from 'react';
import { getTimeInAngles } from './utils';
import { Digits } from './components/digits';
import { Ticks } from './components/ticks';

import styles from './style.module.css';

const AnalogClock = () => {
const [angles, setAngles] = useState({
hours: 0,
minutes: 0,
seconds: 0,
});

useEffect(() => {
const setClockHands = () => {
setAngles(getTimeInAngles(new Date()));
};

setClockHands();
setInterval(setClockHands, 1000);

return () => {
clearInterval(setClockHands);
};
}, []);

return (
<div className={styles.clock} style={{ overflow: 'hidden' }}>
<img
src="https://images.pexels.com/photos/7895278/pexels-photo-7895278.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1"
alt=""
className={styles.bgImg}
/>
<Ticks />
<Digits />

<div className={styles.centerConnector}></div>
<div className={styles.hoursHand} style={{ rotate: `${angles.hours}deg` }}></div>
<div className={styles.minutesHand} style={{ rotate: `${angles.minutes}deg` }}></div>
<div className={styles.secondsHand} style={{ rotate: `${angles.seconds}deg` }}></div>
</div>
);
};

export default AnalogClock;
24 changes: 24 additions & 0 deletions apps/react/src/challenges/analog-clock/components/digits.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react';
import { clockDigits } from '../utils';
import styles from '../style.module.css';

export const Digits = React.memo(() => {
return (
<div className={styles.digits}>
{clockDigits.map((digit, idx) => (
<div
key={digit}
className={styles.digit}
style={{
left: `${50 + Math.sin(((Math.PI * 2) / 12) * idx) * 50}%`,
top: `${50 - Math.cos(((Math.PI * 2) / 12) * idx) * 50}%`,
}}
>
{digit}
</div>
))}
</div>
);
});

Digits.displayName = 'Digits';
23 changes: 23 additions & 0 deletions apps/react/src/challenges/analog-clock/components/ticks.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';
import { ticksCount } from '../utils';
import styles from '../style.module.css';

export const Ticks = React.memo(() => {
return (
<div className={styles.ticks}>
{Array.from({ length: ticksCount }).map((_, i) => (
<div
key={i}
className={styles.tick}
style={{
left: `${50 + Math.sin(((Math.PI * 2) / 60) * i) * 50}%`,
top: `${50 - Math.cos(((Math.PI * 2) / 60) * i) * 50}%`,
rotate: `${i * 6}deg`,
}}
></div>
))}
</div>
);
});

Ticks.displayName = 'Ticks';
111 changes: 111 additions & 0 deletions apps/react/src/challenges/analog-clock/style.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
:root {
--clock-size: min(100vh - 140px, 90vw);
--clock-border: min(2vh, 2vw);
--hand-color: black;
--tick-color: black;
--clock-color: black;
--digit-color: brown;
}

.clock {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: var(--clock-size);
height: var(--clock-size);
margin: 2rem auto 0;
border: min(2vh, 2vw) solid var(--clock-color);
border-radius: 50%;
}

.ticks {
position: absolute;
width: 100%;
height: 100%;
}

.tick {
position: absolute;
height: 1%;
background-color: var(--tick-color);
border: 1px solid var(--tick-color);
transform: translate(-50%, -50%);
transform-origin: top left;
}

.tick:nth-child(5n + 1) {
width: 0.25%;
height: 1.5%;
}

.digits {
position: absolute;
width: 90%;
height: 90%;
font-size: min(4vh, 4vw);
font-weight: 600;
color: var(--digit-color);
}

.digit {
position: absolute;
text-shadow: 0 2px rgb(0, 0, 0, 15%);
transform: translate(-50%, -50%);
}

.bgImg {
width: 100%;
height: 100%;
object-fit: cover;
opacity: 0.75;
}

.centerConnector {
position: absolute;
z-index: 100;
width: 5%;
height: 5%;
background-color: var(--clock-color);
border-radius: 50%;
}

.secondsHand,
.minutesHand,
.hoursHand {
position: absolute;
background-color: var(--hand-color);
transform-origin: 50% 100%;
}

.hoursHand {
top: 20%;
left: calc(50% - 2.5px);
width: 0.5%;
height: 30%;
}

.minutesHand {
top: 5%;
left: calc(50% - 1.5px);
width: 0.3%;
height: 45%;
}

.secondsHand {
top: 15%;
left: calc(50% - 0.5px);
width: 0.1%;
height: 55%;
transform-origin: 50% 63.5%;
}

.secondsHand::before {
position: absolute;
bottom: 0;
left: -250%;
width: 600%;
height: 25%;
content: '';
background-color: var(--hand-color);
}
8 changes: 8 additions & 0 deletions apps/react/src/challenges/analog-clock/utils.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const ticksCount = 60;
export const clockDigits = [12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];

export const getTimeInAngles = (date) => ({
seconds: (clockDigits.length / 2) * date.getSeconds(),
minutes: (clockDigits.length / 2) * date.getMinutes() + date.getSeconds() / 10,
hours: date.getMinutes() / 2 + (date.getHours() % clockDigits.length) * 30,
});
6 changes: 4 additions & 2 deletions apps/react/src/pages/Challenge.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { useParams } from 'react-router-dom';
import { reactChallenges } from '@fmc/data/content';
import FifteenPuzzle from '@/challenges/15puzzle/App.tsx';
import Accordion from '@/challenges/accordion/App';
import AutocompleteOffline from '@/challenges/autocomplete/autocompleteOffline';
Expand Down Expand Up @@ -47,8 +49,6 @@ import Otp from '@/challenges/otp/App';
import TrafficLights from '@/challenges/traffic-light/App';
import QuizApp from '@/challenges/quiz-app/App';
import ChessBoard from '@/challenges/chess-board/App';
import { reactChallenges } from '@fmc/data/content';
import { useParams } from 'react-router-dom';
import Calculator from '@/challenges/calculator/App';
import WaterBalancer from '@/challenges/water-balancer/App';
import TransferListApp from '@/challenges/transfer-list/TransferListApp';
Expand All @@ -63,6 +63,7 @@ import ChipsInput from '@/challenges/chip-input/App';
import Tab from '@/challenges/tab/App';
import DraggableList from '@/challenges/drag-drop/DraggableList';
import Circles from '@/challenges/circles/circles';
import AnalogClock from '@/challenges/analog-clock/analog-clock';

const reactChallengesMap = {
'transfer-list': <TransferListApp />,
Expand Down Expand Up @@ -127,6 +128,7 @@ const reactChallengesMap = {
tabs: <Tab />,
'drag-drop': <DraggableList />,
circles: <Circles />,
'analog-clock': <AnalogClock />,
};

function Challenge() {
Expand Down
11 changes: 11 additions & 0 deletions shared/data/content/react-challenges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,17 @@ const challenges = new Map<string, IChallenge>([
isNew: true,
},
],
[
'analog-clock',
{
title: 'Analog Clock',
link: 'analog-clock',
difficulty: EDifficulty.Medium,
developer: 'sadanandpai',
tags: [],
isNew: true,
},
],
]);

export const reactChallenges = sortChallengesByDifficulty(challenges);

0 comments on commit 8026359

Please sign in to comment.