학습 내용 선택 화면 추가
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
import { useGameStore } from "@/store/gameStore";
|
||||
import type { ProblemCount } from "@/store/gameStore";
|
||||
|
||||
const problemCounts: ProblemCount[] = [5, 10];
|
||||
|
||||
const bracketKinds = [
|
||||
{
|
||||
label: "()",
|
||||
name: "소괄호",
|
||||
selected: true,
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
label: "{}",
|
||||
name: "중괄호",
|
||||
selected: false,
|
||||
disabled: true,
|
||||
},
|
||||
{
|
||||
label: "[]",
|
||||
name: "대괄호",
|
||||
selected: false,
|
||||
disabled: true,
|
||||
},
|
||||
];
|
||||
|
||||
export default function NumberParenthesesClient() {
|
||||
const startLinkRef = useRef<HTMLAnchorElement>(null);
|
||||
const selectedProblemCount = useGameStore(
|
||||
(state) => state.selectedProblemCount,
|
||||
);
|
||||
const setProblemCount = useGameStore((state) => state.setProblemCount);
|
||||
const initSession = useGameStore((state) => state.initSession);
|
||||
|
||||
useEffect(() => {
|
||||
startLinkRef.current?.focus();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<main className="flex min-h-screen items-center bg-slate-50 px-6 py-12 text-slate-950">
|
||||
<section className="mx-auto flex w-full max-w-3xl flex-col gap-8">
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm font-semibold text-emerald-700">
|
||||
숫자 괄호 학습 준비
|
||||
</p>
|
||||
<h1 className="text-4xl font-bold tracking-normal sm:text-5xl">
|
||||
숫자 괄호 풀기
|
||||
</h1>
|
||||
<p className="max-w-2xl text-lg leading-8 text-slate-700">
|
||||
정수로 이루어진 괄호식을 풀며 음수 분배와 부호 변화를
|
||||
연습합니다.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-6">
|
||||
<fieldset className="space-y-3">
|
||||
<legend className="text-sm font-semibold text-slate-700">
|
||||
문제 수
|
||||
</legend>
|
||||
<div
|
||||
className="inline-flex gap-1 rounded-md border border-slate-300 bg-white p-1 shadow-sm"
|
||||
role="group"
|
||||
aria-label="문제 수 선택"
|
||||
>
|
||||
{problemCounts.map((count) => {
|
||||
const selected = selectedProblemCount === count;
|
||||
|
||||
return (
|
||||
<button
|
||||
key={count}
|
||||
type="button"
|
||||
onClick={() => setProblemCount(count)}
|
||||
aria-pressed={selected}
|
||||
className={`h-10 min-w-16 rounded px-4 text-base font-semibold transition focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:ring-offset-2 ${
|
||||
selected
|
||||
? "bg-emerald-700 text-white"
|
||||
: "text-slate-700 hover:bg-slate-100"
|
||||
}`}
|
||||
>
|
||||
{count}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset className="space-y-3">
|
||||
<legend className="text-sm font-semibold text-slate-700">
|
||||
괄호 종류
|
||||
</legend>
|
||||
<div
|
||||
className="inline-flex gap-1 rounded-md border border-slate-300 bg-white p-1 shadow-sm"
|
||||
role="group"
|
||||
aria-label="괄호 종류 선택"
|
||||
>
|
||||
{bracketKinds.map((bracketKind) => (
|
||||
<button
|
||||
key={bracketKind.label}
|
||||
type="button"
|
||||
disabled={bracketKind.disabled}
|
||||
aria-pressed={bracketKind.selected}
|
||||
aria-label={`${bracketKind.name} ${bracketKind.disabled ? "준비 중" : "선택됨"}`}
|
||||
className={`h-10 min-w-16 rounded px-4 text-base font-semibold transition focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:ring-offset-2 ${
|
||||
bracketKind.selected
|
||||
? "bg-emerald-700 text-white"
|
||||
: "text-slate-700 hover:bg-slate-100"
|
||||
} ${
|
||||
bracketKind.disabled
|
||||
? "cursor-not-allowed bg-slate-100 text-slate-400 hover:bg-slate-100"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
{bracketKind.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div>
|
||||
<Link
|
||||
ref={startLinkRef}
|
||||
href={`/play?count=${selectedProblemCount}`}
|
||||
onClick={() => initSession(selectedProblemCount)}
|
||||
className="inline-flex h-12 items-center justify-center rounded-md bg-emerald-700 px-6 text-base font-semibold text-white transition hover:bg-emerald-800 focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:ring-offset-2"
|
||||
>
|
||||
연습 시작
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-l-4 border-emerald-700 pl-4 text-sm leading-6 text-slate-600">
|
||||
<p>현재는 소괄호 숫자 문제만 선택할 수 있습니다.</p>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import NumberParenthesesClient from "./NumberParenthesesClient";
|
||||
|
||||
export default function NumberParenthesesPage() {
|
||||
return <NumberParenthesesClient />;
|
||||
}
|
||||
@@ -121,7 +121,7 @@ export default function PlayClient({ problemCount }: PlayClientProps) {
|
||||
const rect = katexElement.getBoundingClientRect();
|
||||
const scrollElement =
|
||||
document.scrollingElement ?? document.documentElement;
|
||||
const elementDocumentTop = window.pageYOffset + rect.top;
|
||||
const elementDocumentTop = window.scrollY + rect.top;
|
||||
const maxScrollTop = Math.max(
|
||||
0,
|
||||
scrollElement.scrollHeight - window.innerHeight,
|
||||
@@ -131,7 +131,7 @@ export default function PlayClient({ problemCount }: PlayClientProps) {
|
||||
Math.max(0, elementDocumentTop),
|
||||
);
|
||||
|
||||
if (Math.abs(window.pageYOffset - scrollTop) < 4) return;
|
||||
if (Math.abs(window.scrollY - scrollTop) < 4) return;
|
||||
|
||||
scrollElement.scrollTop = scrollTop;
|
||||
document.body.scrollTop = scrollTop;
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
import { useGameStore } from "@/store/gameStore";
|
||||
import type { ProblemCount } from "@/store/gameStore";
|
||||
|
||||
const problemCounts: ProblemCount[] = [5, 10];
|
||||
|
||||
export default function StartControls() {
|
||||
const startLinkRef = useRef<HTMLAnchorElement>(null);
|
||||
const selectedProblemCount = useGameStore(
|
||||
(state) => state.selectedProblemCount,
|
||||
);
|
||||
const setProblemCount = useGameStore((state) => state.setProblemCount);
|
||||
const initSession = useGameStore((state) => state.initSession);
|
||||
|
||||
useEffect(() => {
|
||||
startLinkRef.current?.focus();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-5">
|
||||
<div>
|
||||
<Link
|
||||
ref={startLinkRef}
|
||||
href={`/play?count=${selectedProblemCount}`}
|
||||
onClick={() => initSession(selectedProblemCount)}
|
||||
className="inline-flex h-12 items-center justify-center rounded-md bg-emerald-700 px-6 text-base font-semibold text-white transition hover:bg-emerald-800 focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:ring-offset-2"
|
||||
>
|
||||
연습 시작
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<fieldset className="space-y-3">
|
||||
<legend className="text-sm font-semibold text-slate-700">
|
||||
문제 수
|
||||
</legend>
|
||||
<div
|
||||
className="inline-flex gap-1 rounded-md border border-slate-300 bg-white p-1 shadow-sm"
|
||||
role="group"
|
||||
aria-label="문제 수 선택"
|
||||
>
|
||||
{problemCounts.map((count) => {
|
||||
const selected = selectedProblemCount === count;
|
||||
|
||||
return (
|
||||
<button
|
||||
key={count}
|
||||
type="button"
|
||||
onClick={() => setProblemCount(count)}
|
||||
aria-pressed={selected}
|
||||
className={`h-10 min-w-16 rounded px-4 text-base font-semibold transition focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:ring-offset-2 ${
|
||||
selected
|
||||
? "bg-emerald-700 text-white"
|
||||
: "text-slate-700 hover:bg-slate-100"
|
||||
}`}
|
||||
>
|
||||
{count}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+77
-7
@@ -1,4 +1,41 @@
|
||||
import StartControls from "./StartControls";
|
||||
import Link from "next/link";
|
||||
|
||||
type Subject =
|
||||
| {
|
||||
label: string;
|
||||
description: string;
|
||||
href: string;
|
||||
disabled: false;
|
||||
}
|
||||
| {
|
||||
label: string;
|
||||
description: string;
|
||||
disabled: true;
|
||||
};
|
||||
|
||||
const subjects: Subject[] = [
|
||||
{
|
||||
label: "숫자 괄호 풀기",
|
||||
description: "정수로 이루어진 괄호식을 풀며 부호 변화를 연습합니다.",
|
||||
href: "/number-parentheses",
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
label: "분수 괄호 풀기",
|
||||
description: "분수가 들어간 괄호식은 이후 단계에서 추가됩니다.",
|
||||
disabled: true,
|
||||
},
|
||||
{
|
||||
label: "실수 괄호 풀기",
|
||||
description: "실수가 들어간 괄호식은 이후 단계에서 추가됩니다.",
|
||||
disabled: true,
|
||||
},
|
||||
{
|
||||
label: "미지수 괄호 풀기",
|
||||
description: "미지수가 들어간 괄호식은 이후 단계에서 추가됩니다.",
|
||||
disabled: true,
|
||||
},
|
||||
];
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
@@ -6,21 +43,54 @@ export default function Home() {
|
||||
<section className="mx-auto flex w-full max-w-3xl flex-col gap-8">
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm font-semibold text-emerald-700">
|
||||
중학교 2학년 수학 - 괄호 풀기 연습
|
||||
중학교 2학년 수학 - 학습 내용 선택
|
||||
</p>
|
||||
<h1 className="text-4xl font-bold tracking-normal sm:text-5xl">
|
||||
괄호 음수 분배 연습
|
||||
오늘 연습할 괄호 유형을 고르세요
|
||||
</h1>
|
||||
<p className="max-w-2xl text-lg leading-8 text-slate-700">
|
||||
괄호 앞의 음수를 분배할 때 부호가 어떻게 바뀌는지 차근차근
|
||||
연습합니다.
|
||||
괄호 앞의 음수를 분배할 때 부호가 어떻게 바뀌는지 학습
|
||||
내용별로 차근차근 연습합니다.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<StartControls />
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
{subjects.map((subject) => {
|
||||
if (!subject.disabled) {
|
||||
return (
|
||||
<Link
|
||||
key={subject.label}
|
||||
href={subject.href}
|
||||
className="flex min-h-32 flex-col justify-between rounded-md bg-white p-5 text-left shadow-sm ring-1 ring-slate-200 transition hover:-translate-y-0.5 hover:shadow-md focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:ring-offset-2"
|
||||
>
|
||||
<span className="text-xl font-bold text-slate-950">
|
||||
{subject.label}
|
||||
</span>
|
||||
<span className="mt-4 text-sm leading-6 text-slate-600">
|
||||
{subject.description}
|
||||
</span>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
key={subject.label}
|
||||
type="button"
|
||||
disabled
|
||||
className="flex min-h-32 cursor-not-allowed flex-col justify-between rounded-md bg-slate-100 p-5 text-left text-slate-400 ring-1 ring-slate-200"
|
||||
>
|
||||
<span className="text-xl font-bold">{subject.label}</span>
|
||||
<span className="mt-4 text-sm leading-6">
|
||||
{subject.description}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="border-l-4 border-emerald-700 pl-4 text-sm leading-6 text-slate-600">
|
||||
<p>반복할 문제 수를 선택한 뒤, [연습 시작] 버튼을 클릭하세요.</p>
|
||||
<p>지금은 숫자 괄호 학습만 사용할 수 있습니다.</p>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
Reference in New Issue
Block a user