리펙토링: 문제 준비 화면 공통 요소 묶기
This commit is contained in:
@@ -1,148 +1,25 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Link from "next/link";
|
import ParenthesesPreparationClient from "@/components/game/ParenthesesPreparationClient";
|
||||||
import { useEffect, useRef } from "react";
|
|
||||||
|
|
||||||
import { useGameStore } from "@/store/gameStore";
|
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() {
|
export default function NumberParenthesesClient() {
|
||||||
const startLinkRef = useRef<HTMLAnchorElement>(null);
|
|
||||||
const selectedProblemCount = useGameStore(
|
const selectedProblemCount = useGameStore(
|
||||||
(state) => state.selectedProblemCount,
|
(state) => state.selectedProblemCount,
|
||||||
);
|
);
|
||||||
const setProblemCount = useGameStore((state) => state.setProblemCount);
|
const setProblemCount = useGameStore((state) => state.setProblemCount);
|
||||||
const initSession = useGameStore((state) => state.initSession);
|
const initSession = useGameStore((state) => state.initSession);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
startLinkRef.current?.focus();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="flex min-h-screen items-center bg-slate-50 px-6 py-12 text-slate-950">
|
<ParenthesesPreparationClient
|
||||||
<section className="mx-auto flex w-full max-w-3xl flex-col gap-8">
|
eyebrow="숫자 괄호 학습 준비"
|
||||||
<div className="space-y-4">
|
title="숫자 괄호 풀기"
|
||||||
<p className="text-sm font-semibold text-emerald-700">
|
description="정수로 이루어진 괄호식을 풀며 음수 분배와 부호 변화를 연습합니다."
|
||||||
숫자 괄호 학습 준비
|
notice="현재는 소괄호 숫자 문제만 선택할 수 있습니다."
|
||||||
</p>
|
playHref={`/number-parentheses/play?count=${selectedProblemCount}`}
|
||||||
<h1 className="text-4xl font-bold tracking-normal sm:text-5xl">
|
selectedProblemCount={selectedProblemCount}
|
||||||
숫자 괄호 풀기
|
onProblemCountChange={setProblemCount}
|
||||||
</h1>
|
onStart={() => initSession(selectedProblemCount)}
|
||||||
<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 className="flex flex-wrap gap-3">
|
|
||||||
<Link
|
|
||||||
ref={startLinkRef}
|
|
||||||
href={`/number-parentheses/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>
|
|
||||||
<Link
|
|
||||||
href="/"
|
|
||||||
className="inline-flex h-12 items-center justify-center rounded-md border border-slate-300 bg-white px-6 text-base font-semibold text-slate-700 transition hover:bg-slate-100 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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +1,9 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Link from "next/link";
|
import ParenthesesPreparationClient from "@/components/game/ParenthesesPreparationClient";
|
||||||
import { useEffect, useRef } from "react";
|
import { useVariableGameStore } from "@/store/variableGameStore";
|
||||||
|
|
||||||
import {
|
|
||||||
useVariableGameStore,
|
|
||||||
type VariableProblemCount,
|
|
||||||
} from "@/store/variableGameStore";
|
|
||||||
|
|
||||||
const problemCounts: VariableProblemCount[] = [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 VariableParenthesesClient() {
|
export default function VariableParenthesesClient() {
|
||||||
const startLinkRef = useRef<HTMLAnchorElement>(null);
|
|
||||||
const selectedProblemCount = useVariableGameStore(
|
const selectedProblemCount = useVariableGameStore(
|
||||||
(state) => state.selectedProblemCount,
|
(state) => state.selectedProblemCount,
|
||||||
);
|
);
|
||||||
@@ -41,112 +12,16 @@ export default function VariableParenthesesClient() {
|
|||||||
);
|
);
|
||||||
const initSession = useVariableGameStore((state) => state.initSession);
|
const initSession = useVariableGameStore((state) => state.initSession);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
startLinkRef.current?.focus();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="flex min-h-screen items-center bg-slate-50 px-6 py-12 text-slate-950">
|
<ParenthesesPreparationClient
|
||||||
<section className="mx-auto flex w-full max-w-3xl flex-col gap-8">
|
eyebrow="미지수 괄호 학습 준비"
|
||||||
<div className="space-y-4">
|
title="미지수 괄호 풀기"
|
||||||
<p className="text-sm font-semibold text-emerald-700">
|
description="괄호 안의 한 항에 미지수 x가 들어간 식을 풀며 부호 변화를 연습합니다."
|
||||||
미지수 괄호 학습 준비
|
notice="현재는 소괄호와 미지수 x 문제만 선택할 수 있습니다."
|
||||||
</p>
|
playHref={`/variable-parentheses/play?count=${selectedProblemCount}`}
|
||||||
<h1 className="text-4xl font-bold tracking-normal sm:text-5xl">
|
selectedProblemCount={selectedProblemCount}
|
||||||
미지수 괄호 풀기
|
onProblemCountChange={setProblemCount}
|
||||||
</h1>
|
onStart={() => initSession(selectedProblemCount)}
|
||||||
<p className="max-w-2xl text-lg leading-8 text-slate-700">
|
/>
|
||||||
괄호 안의 한 항에 미지수 x가 들어간 식을 풀며 부호 변화를
|
|
||||||
연습합니다.
|
|
||||||
</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 className="flex flex-wrap gap-3">
|
|
||||||
<Link
|
|
||||||
ref={startLinkRef}
|
|
||||||
href={`/variable-parentheses/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>
|
|
||||||
<Link
|
|
||||||
href="/"
|
|
||||||
className="inline-flex h-12 items-center justify-center rounded-md border border-slate-300 bg-white px-6 text-base font-semibold text-slate-700 transition hover:bg-slate-100 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>현재는 소괄호와 미지수 x 문제만 선택할 수 있습니다.</p>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,161 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useEffect, useRef } from "react";
|
||||||
|
|
||||||
|
export type PreparationProblemCount = 5 | 10;
|
||||||
|
|
||||||
|
interface ParenthesesPreparationClientProps {
|
||||||
|
eyebrow: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
notice: string;
|
||||||
|
playHref: string;
|
||||||
|
selectedProblemCount: PreparationProblemCount;
|
||||||
|
onProblemCountChange: (count: PreparationProblemCount) => void;
|
||||||
|
onStart: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const problemCounts: PreparationProblemCount[] = [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 ParenthesesPreparationClient({
|
||||||
|
eyebrow,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
notice,
|
||||||
|
playHref,
|
||||||
|
selectedProblemCount,
|
||||||
|
onProblemCountChange,
|
||||||
|
onStart,
|
||||||
|
}: ParenthesesPreparationClientProps) {
|
||||||
|
const startLinkRef = useRef<HTMLAnchorElement>(null);
|
||||||
|
|
||||||
|
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">{eyebrow}</p>
|
||||||
|
<h1 className="text-4xl font-bold tracking-normal sm:text-5xl">
|
||||||
|
{title}
|
||||||
|
</h1>
|
||||||
|
<p className="max-w-2xl text-lg leading-8 text-slate-700">
|
||||||
|
{description}
|
||||||
|
</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={() => onProblemCountChange(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 className="flex flex-wrap gap-3">
|
||||||
|
<Link
|
||||||
|
ref={startLinkRef}
|
||||||
|
href={playHref}
|
||||||
|
onClick={onStart}
|
||||||
|
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>
|
||||||
|
<Link
|
||||||
|
href="/"
|
||||||
|
className="inline-flex h-12 items-center justify-center rounded-md border border-slate-300 bg-white px-6 text-base font-semibold text-slate-700 transition hover:bg-slate-100 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>{notice}</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user