mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2025-08-10 19:26:03 +02:00
feat: question page ui
This commit is contained in:
89
src/components/Questions/QuestionCard.tsx
Normal file
89
src/components/Questions/QuestionCard.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
export function QuestionCard() {
|
||||
const [isAnswerVisible, setIsAnswerVisible] = useState<boolean>(false);
|
||||
const answerRef = useRef<HTMLDivElement>(null);
|
||||
const questionRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
// set the height of the question width to the height of the answer
|
||||
// width if the answer is visible and the question height is less than
|
||||
// the answer height
|
||||
if (isAnswerVisible) {
|
||||
const answerHeight = answerRef.current?.clientHeight || 0;
|
||||
const questionHeight = questionRef.current?.clientHeight || 0;
|
||||
|
||||
if (answerHeight > questionHeight) {
|
||||
questionRef.current!.style.height = `${answerHeight}px`;
|
||||
}
|
||||
|
||||
// if the user has scrolled down and the top of the answer is not
|
||||
// visible, scroll to the top of the answer
|
||||
const questionTop = questionRef.current?.getBoundingClientRect().top || 0;
|
||||
if (questionTop < 0) {
|
||||
window.scrollTo({
|
||||
top: window.scrollY + questionTop - 100,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
questionRef.current!.style.height = `auto`;
|
||||
}
|
||||
}, [isAnswerVisible]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
ref={questionRef}
|
||||
className={`flex flex-grow flex-col items-center justify-center py-8`}
|
||||
>
|
||||
<div className="text-gray-400">
|
||||
<span>Frontend</span>
|
||||
<span className="mx-3">·</span>
|
||||
<span className="capitalize">Easy Question</span>
|
||||
</div>
|
||||
|
||||
<div className="mx-auto flex max-w-[550px] flex-1 items-center justify-center py-8">
|
||||
<p className="text-3xl leading-normal text-black">
|
||||
What do you think is the output of the following code?
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="text-center">
|
||||
<button
|
||||
onClick={() => {
|
||||
setIsAnswerVisible(true);
|
||||
}}
|
||||
className="cursor-pointer text-gray-500 underline underline-offset-4 transition-colors hover:text-black"
|
||||
>
|
||||
Click to Reveal the Answer
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
ref={answerRef}
|
||||
className={`absolute left-0 right-0 flex flex-col items-center justify-center rounded-[7px] bg-neutral-100 py-8 text-xl leading-normal text-black transition-all duration-300 ${
|
||||
isAnswerVisible ? 'top-0 min-h-[398px]' : 'top-full'
|
||||
}`}
|
||||
>
|
||||
<div className="mx-auto flex max-w-[600px] flex-grow items-center py-0 px-5 text-2xl leading-normal">
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam
|
||||
voluptatum, quod, quas, quia, voluptates voluptate quibusdam
|
||||
voluptatibus quos quae quidem. Quisqu
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-center mt-7">
|
||||
<button
|
||||
onClick={() => {
|
||||
setIsAnswerVisible(false);
|
||||
}}
|
||||
className="cursor-pointer text-base text-gray-500 underline underline-offset-4 transition-colors hover:text-black"
|
||||
>
|
||||
Hide the Answer
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
9
src/components/Questions/QuestionLoader.tsx
Normal file
9
src/components/Questions/QuestionLoader.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
export function QuestionLoader() {
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<p className="animate-pulse text-2xl text-black duration-100">
|
||||
Please wait ..
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
@@ -1,12 +1,22 @@
|
||||
import { QuestionsProgress } from './QuestionsProgress';
|
||||
import { CheckCircle, SkipForward, Sparkles } from 'lucide-react';
|
||||
import { useRef, useState } from 'react';
|
||||
import ReactConfetti from 'react-confetti';
|
||||
import { QuestionsProgress } from './QuestionsProgress';
|
||||
import { CheckCircle, SkipForward, Sparkles } from 'lucide-react';
|
||||
import { QuestionCard } from './QuestionCard';
|
||||
import { QuestionLoader } from './QuestionLoader';
|
||||
|
||||
type ConfettiPosition = {
|
||||
x: number;
|
||||
y: number;
|
||||
w: number;
|
||||
h: number;
|
||||
};
|
||||
|
||||
export function QuestionsList() {
|
||||
const [confettiPos, setConfettiPos] = useState<
|
||||
undefined | { x: number; y: number; w: number; h: number }
|
||||
>(undefined);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [confettiPos, setConfettiPos] = useState<undefined | ConfettiPosition>(
|
||||
undefined
|
||||
);
|
||||
|
||||
const alreadyKnowRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
@@ -16,12 +26,14 @@ export function QuestionsList() {
|
||||
|
||||
{confettiPos && (
|
||||
<ReactConfetti
|
||||
numberOfPieces={20}
|
||||
height={document.body.scrollHeight}
|
||||
numberOfPieces={40}
|
||||
recycle={false}
|
||||
onConfettiComplete={() => {
|
||||
onConfettiComplete={(confettiInstance) => {
|
||||
confettiInstance?.reset();
|
||||
setConfettiPos(undefined);
|
||||
}}
|
||||
initialVelocityX={2}
|
||||
initialVelocityX={4}
|
||||
initialVelocityY={8}
|
||||
tweenDuration={25}
|
||||
confettiSource={{
|
||||
@@ -33,12 +45,9 @@ export function QuestionsList() {
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="relative mb-4 h-[400px] w-full overflow-hidden rounded-lg border border-gray-300 bg-white">
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<p className="animate-pulse text-2xl text-black duration-100">
|
||||
Please wait ..
|
||||
</p>
|
||||
</div>
|
||||
<div className="relative mb-4 flex min-h-[400px] w-full overflow-hidden rounded-lg border border-gray-300 bg-white">
|
||||
<QuestionCard />
|
||||
{isLoading && <QuestionLoader />}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-3 sm:flex-row">
|
||||
|
Reference in New Issue
Block a user