diff --git a/apps/react/src/challenges/timeline/data.jsx b/apps/css/src/challenges/timeline/data.js similarity index 100% rename from apps/react/src/challenges/timeline/data.jsx rename to apps/css/src/challenges/timeline/data.js diff --git a/apps/css/src/challenges/timeline/index.html b/apps/css/src/challenges/timeline/index.html new file mode 100644 index 000000000..4d6b5245c --- /dev/null +++ b/apps/css/src/challenges/timeline/index.html @@ -0,0 +1,27 @@ + + + + + + + + +
+
+
+
    +
    +
    +
    + + + + diff --git a/apps/css/src/challenges/timeline/script.ts b/apps/css/src/challenges/timeline/script.ts new file mode 100644 index 000000000..1300c875d --- /dev/null +++ b/apps/css/src/challenges/timeline/script.ts @@ -0,0 +1,12 @@ +import DATA from './data.js'; + +const timelineTemplate = document.getElementById('timeline-template')! as HTMLTemplateElement; +const timelineListEl = document.querySelector('.timeline_list')!; + +DATA.forEach((item) => { + const listItem = timelineTemplate.content.cloneNode(true) as HTMLElement; + listItem.querySelector('.date')!.textContent = item.date; + listItem.querySelector('.title')!.textContent = item.title; + listItem.querySelector('.content')!.textContent = item.content; + timelineListEl.appendChild(listItem); +}); diff --git a/apps/react/src/challenges/timeline/timeline.module.css b/apps/css/src/challenges/timeline/style.css similarity index 82% rename from apps/react/src/challenges/timeline/timeline.module.css rename to apps/css/src/challenges/timeline/style.css index 00141d474..0ba99cee5 100644 --- a/apps/react/src/challenges/timeline/timeline.module.css +++ b/apps/css/src/challenges/timeline/style.css @@ -1,120 +1,120 @@ -.AppContainer { +.wrapper { display: flex; - flex-direction: column; - gap: 10px; align-items: center; justify-content: center; - padding: 10px 20px; -} -.container { - min-height: 100vh; width: 100%; - display: flex; - align-items: center; - justify-content: center; - padding: 100px 0; + min-height: 100vh; + padding: 2rem 0; } + .timeline { - width: 80%; - height: auto; + position: relative; + width: 90%; max-width: 800px; + height: auto; margin: 0 auto; - position: relative; } .timeline ul { list-style: none; } + .timeline ul li { + position: relative; + width: 50%; padding: 20px; - background-color: #1e1f22; + margin-bottom: 50px; color: white; + background-color: #1e1f22; border-radius: 10px; - margin-bottom: 20px; } + .timeline ul li:last-child { margin-bottom: 0; } + .date { - color: black; font-size: 12px; + color: black; } + .timeline_content h1 { - font-weight: 500; + margin-bottom: 10px; font-size: 25px; + font-weight: 500; line-height: 30px; - margin-bottom: 10px; color: white; } + .timeline_content p { font-size: 16px; - line-height: 30px; font-weight: 300; + line-height: 30px; } + .timeline_content .date { + position: absolute; + top: -30px; + margin-bottom: 10px; font-size: 12px; font-weight: 300; - margin-bottom: 10px; letter-spacing: 2px; } -.timeline:before { - content: ''; +.timeline::before { position: absolute; top: 0; left: 50%; - transform: translateX(-50%); width: 2px; height: 100%; + content: ''; background-color: gray; + transform: translateX(-50%); } -.timeline ul li { - width: 50%; - position: relative; - margin-bottom: 50px; -} + .timeline ul li:nth-child(odd) { float: left; clear: right; + border-radius: 20px 0 20px 20px; transform: translateX(-60px); - border-radius: 20px 0px 20px 20px; } + .timeline ul li:nth-child(even) { float: right; clear: left; + border-radius: 0 20px 20px; transform: translateX(30px); - border-radius: 0px 20px 20px 20px; } + .timeline ul li::before { - content: ''; position: absolute; - height: 20px; + top: -10px; width: 20px; - border-radius: 50%; + height: 20px; + content: ''; background-color: gray; - top: -10px; + border-radius: 50%; } + .timeline ul li:nth-child(odd)::before { - transform: translate(50%, -50%); right: -40px; + transform: translate(50%, -50%); } + .timeline ul li:nth-child(even)::before { - transform: translate(-50%, -50%); left: -48px; + transform: translate(-50%, -50%); } -.timeline_content .date { - position: absolute; - top: -30px; -} + .timeline ul li:hover::before { background-color: aqua; } -@media only screen and (max-width: 420px) { +@media only screen and (width <= 420px) { .timeline_content h1 { + padding-right: 10px; font-size: 20px; line-height: 25px; - padding-right: 10px; } .timeline_content p { @@ -123,7 +123,7 @@ } .timeline_content .date { - font-size: 10px; margin-bottom: 5px; + font-size: 10px; } } diff --git a/apps/react/src/challenges/grid-lights/App.tsx b/apps/react/src/challenges/grid-lights/App.tsx new file mode 100644 index 000000000..a59be0f37 --- /dev/null +++ b/apps/react/src/challenges/grid-lights/App.tsx @@ -0,0 +1,83 @@ +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import classes from './styles.module.scss'; +import { Leva, useControls } from 'leva'; + +function App() { + const [selections, setSelections] = useState([]); + const { gridSize } = useControls({ gridSize: { value: 3, min: 2, max: 4, step: 1 } }); + const { delay } = useControls({ delay: { value: 300, min: 100, max: 700, step: 100 } }); + const cellCount = gridSize * gridSize; + + const isUndoing = useRef(false); + + function onCellClick(e: React.MouseEvent) { + const target = e.target as HTMLButtonElement; + const row = Number(target.dataset.row); + const col = Number(target.dataset.col); + setSelections(selections.concat([[row, col]])); + } + + const undoSelections = useCallback( + async function () { + isUndoing.current = true; + for (let i = selections.length - 1; i >= 0; i--) { + await new Promise((resolve) => setTimeout(resolve, delay)); + setSelections(selections.slice(0, i)); + } + isUndoing.current = false; + }, + [selections, delay] + ); + + useEffect(() => { + if (selections.length === cellCount) { + undoSelections(); + } + }, [undoSelections, selections.length, cellCount]); + + useEffect(() => { + setSelections([]); + }, [gridSize]); + + return ( +
    + + +

    + Click on cells to select them. Once all cells are selected, they will be unselected one by + one in the reverse order they were selected. +

    + +
    + {Array.from({ length: gridSize }, (_, row) => + Array.from({ length: gridSize }, (_, col) => { + const isSelected = selections.some((s) => s[0] === row && s[1] === col); + return ( +
    +
    + ); +} + +export default App; diff --git a/apps/react/src/challenges/grid-lights/styles.module.scss b/apps/react/src/challenges/grid-lights/styles.module.scss new file mode 100644 index 000000000..371bfbcae --- /dev/null +++ b/apps/react/src/challenges/grid-lights/styles.module.scss @@ -0,0 +1,24 @@ +:root { + --size: min(90vw, 100vh - 10rem); + --grid-size: 3; +} + +.wrapper { + text-align: center; +} + +.grid { + display: inline-grid; + grid-template-columns: repeat(var(--grid-size), 1fr); + width: var(--size); + gap: calc(var(--size) / 20); + + button { + border: 1px solid #000; + aspect-ratio: 1; + } +} + +.selected { + background-color: green; +} diff --git a/apps/react/src/challenges/timeline/App.jsx b/apps/react/src/challenges/timeline/App.jsx deleted file mode 100644 index 60f75b974..000000000 --- a/apps/react/src/challenges/timeline/App.jsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react'; -import styles from './timeline.module.css'; -import TimelineData from './data'; - -const App = () => { - return ( - <> -
    -
    -
    -
      - {TimelineData.map((item, index) => ( -
    • -
      -

      {item.date}

      -

      {item.title}

      -

      {item.content}

      -
      -
    • - ))} -
    -
    -
    -
    - - ); -}; - -export default App; diff --git a/apps/react/src/pages/Challenge.tsx b/apps/react/src/pages/Challenge.tsx index 05af475cb..ada3fb5aa 100644 --- a/apps/react/src/pages/Challenge.tsx +++ b/apps/react/src/pages/Challenge.tsx @@ -66,6 +66,7 @@ import AnalogClock from '@/challenges/analog-clock/analog-clock'; import AdvancedCounter from '@/challenges/advanced-counter/advanced-counter'; import NestedCheckbox from '@/challenges/nested-checkbox/App'; import MeetingCalendar from '@/challenges/meeting-calendar/App'; +import GridLights from '@/challenges/grid-lights/App'; const reactChallengesMap = { 'transfer-list': , @@ -132,6 +133,7 @@ const reactChallengesMap = { 'analog-clock': , 'nested-checkbox': , 'meeting-calendar': , + 'grid-lights': , }; function Challenge() { diff --git a/shared/data/content/css-challenges.ts b/shared/data/content/css-challenges.ts index e917ab1b2..be343176c 100644 --- a/shared/data/content/css-challenges.ts +++ b/shared/data/content/css-challenges.ts @@ -143,6 +143,17 @@ const challenges: Map = new Map([ isNew: true, }, ], + [ + 'timeline', + { + title: 'Timeline', + link: 'timeline/', + difficulty: EDifficulty.Medium, + developer: 'Vivek7038', + tags: [], + isNew: true, + }, + ], ]); export const cssChallenges = sortChallengesByDifficulty(challenges); diff --git a/shared/data/content/react-challenges.ts b/shared/data/content/react-challenges.ts index 1159b4e86..c63ab5ed9 100644 --- a/shared/data/content/react-challenges.ts +++ b/shared/data/content/react-challenges.ts @@ -294,13 +294,13 @@ const challenges = new Map([ }, ], [ - 'timeline', + 'grid-lights', { - title: 'Timeline', - link: 'timeline', + title: 'Grid Lights', + link: 'grid-lights', difficulty: EDifficulty.Medium, - developer: 'Vivek7038', - tags: [], + developer: 'sadanandpai', + tags: [ETag.interview], isNew: true, }, ],