Merge branch 'fe/quizFormToggle' into 'frontend'
[Front-End] feat: 퀴즈 생성 카드에 토글 추가 See merge request s11-webmobile1-sub2/S11P12A701!185
This commit is contained in:
commit
f5a0abd9ea
@ -1,17 +1,23 @@
|
|||||||
import { useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import styles from './QuizCard.module.css';
|
import styles from './QuizCard.module.css';
|
||||||
import CloseIcon from '/src/assets/icons/close.svg?react';
|
import CloseIcon from '/src/assets/icons/close.svg?react';
|
||||||
import PlusIcon from '/src/assets/icons/plus.svg?react';
|
import PlusIcon from '/src/assets/icons/plus.svg?react';
|
||||||
|
import { Toggle } from '../Toggle';
|
||||||
|
import { STATIC_URL } from '../../constants';
|
||||||
|
|
||||||
export default function QuizCard({ quiz, updateQuiz, deleteQuiz }) {
|
export default function QuizCard({ quiz, updateQuiz, deleteQuiz }) {
|
||||||
// TODO: 카드 디자인 완성 및 이쁘게 바꾸기
|
|
||||||
const [question, setQuestion] = useState(quiz.question || '');
|
const [question, setQuestion] = useState(quiz.question || '');
|
||||||
const [answer, setAnswer] = useState(Number(quiz.answer) || '');
|
const [answer, setAnswer] = useState(Number(quiz.answer) || '');
|
||||||
const [choices, setChoices] = useState(quiz.choices || []);
|
const [choices, setChoices] = useState(quiz.choices || [{ num: 1, content: '' }]);
|
||||||
const [image, setImage] = useState(quiz.image || null);
|
const [image, setImage] = useState(quiz.image || null);
|
||||||
const [imagePreview, setImagePreview] = useState(
|
const [imagePreview, setImagePreview] = useState(quiz.image ? `${STATIC_URL}${quiz.image}` : null);
|
||||||
quiz.image ? `${import.meta.env.VITE_STATIC_URL}${quiz.image}` : null
|
const [quizType, setQuizType] = useState('단답식');
|
||||||
);
|
|
||||||
|
const clearImage = () => {
|
||||||
|
setImage(null);
|
||||||
|
setImagePreview(null);
|
||||||
|
};
|
||||||
|
|
||||||
const handleChoiceChange = (num, content) => {
|
const handleChoiceChange = (num, content) => {
|
||||||
const updatedChoices = choices.map((choice) => (choice.num === num ? { ...choice, content } : choice));
|
const updatedChoices = choices.map((choice) => (choice.num === num ? { ...choice, content } : choice));
|
||||||
setChoices(updatedChoices);
|
setChoices(updatedChoices);
|
||||||
@ -19,32 +25,36 @@ export default function QuizCard({ quiz, updateQuiz, deleteQuiz }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleAddChoice = () => {
|
const handleAddChoice = () => {
|
||||||
if (choices.length < 4) {
|
if (choices.length >= 4) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const newChoice = { num: choices.length + 1, content: '' };
|
const newChoice = { num: choices.length + 1, content: '' };
|
||||||
const updatedChoices = [...choices, newChoice];
|
const updatedChoices = [...choices, newChoice];
|
||||||
setChoices(updatedChoices);
|
setChoices(updatedChoices);
|
||||||
updateQuiz(quiz.id, { ...quiz, question, answer, choices: updatedChoices, image });
|
updateQuiz(quiz.id, { ...quiz, question, answer, choices: updatedChoices, image });
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePopChoice = () => {
|
const handlePopChoice = () => {
|
||||||
if (choices.length > 0) {
|
if (choices.length <= 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const updatedChoices = choices.slice(0, -1);
|
const updatedChoices = choices.slice(0, -1);
|
||||||
setChoices(updatedChoices);
|
setChoices(updatedChoices);
|
||||||
if (updatedChoices.length < answer) {
|
if (updatedChoices.length < answer) {
|
||||||
setAnswer('');
|
setAnswer(1);
|
||||||
}
|
}
|
||||||
updateQuiz(quiz.id, { ...quiz, question, answer, choices: updatedChoices, image });
|
updateQuiz(quiz.id, { ...quiz, question, answer, choices: updatedChoices, image });
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFileChange = (e) => {
|
const handleFileChange = (e) => {
|
||||||
const file = e.target.files[0] ?? null;
|
const file = e.target.files[0];
|
||||||
if (!file || !file.type.startsWith('image/')) {
|
if (!file) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!file.type.startsWith('image/')) {
|
||||||
alert('이미지 파일만 업로드 해주세요');
|
alert('이미지 파일만 업로드 해주세요');
|
||||||
e.target.value = null;
|
e.target.value = null;
|
||||||
setImage(null);
|
clearImage();
|
||||||
setImagePreview(null);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setImage(file);
|
setImage(file);
|
||||||
@ -61,15 +71,31 @@ export default function QuizCard({ quiz, updateQuiz, deleteQuiz }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleChoiceSelect = (choiceContent) => {
|
const handleChoiceSelect = (choiceContent) => {
|
||||||
console.log(choiceContent);
|
|
||||||
setAnswer(choiceContent);
|
setAnswer(choiceContent);
|
||||||
updateQuiz(quiz.id, { ...quiz, question, answer: choiceContent, choices, image });
|
updateQuiz(quiz.id, { ...quiz, question, answer: choiceContent, choices, image });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
quizType === '단답식' ? setAnswer('') : setAnswer(1);
|
||||||
|
}, [quizType]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.card}>
|
<div className={styles.card}>
|
||||||
<div className={styles.header}>
|
<div className={styles.header}>
|
||||||
<span className={styles.heading}>퀴즈 생성 카드</span>
|
<div className={styles.titleGroup}>
|
||||||
|
<span>Q.</span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={question}
|
||||||
|
maxLength={200}
|
||||||
|
autoFocus
|
||||||
|
onChange={(e) => {
|
||||||
|
setQuestion(e.target.value);
|
||||||
|
updateQuiz(quiz.id, { ...quiz, question: e.target.value, answer, choices, image });
|
||||||
|
}}
|
||||||
|
placeholder="질문 내용을 입력하세요"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<button
|
<button
|
||||||
className={`${styles.cardRemove}`}
|
className={`${styles.cardRemove}`}
|
||||||
onClick={() => deleteQuiz(quiz.id)}
|
onClick={() => deleteQuiz(quiz.id)}
|
||||||
@ -77,20 +103,37 @@ export default function QuizCard({ quiz, updateQuiz, deleteQuiz }) {
|
|||||||
<CloseIcon />
|
<CloseIcon />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<label htmlFor={`file-input-${quiz.id}`}>
|
<div className={styles.content}>
|
||||||
{imagePreview ? (
|
{imagePreview ? (
|
||||||
|
<div className={styles.imageArea}>
|
||||||
|
<label htmlFor={`file-input-${quiz.id}`}>
|
||||||
<img
|
<img
|
||||||
src={imagePreview}
|
src={imagePreview}
|
||||||
alt="Preview"
|
alt="Preview"
|
||||||
className={styles.imagePreview}
|
className={styles.imagePreview}
|
||||||
/>
|
/>
|
||||||
|
</label>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={clearImage}
|
||||||
|
>
|
||||||
|
<CloseIcon />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
<label htmlFor={`file-input-${quiz.id}`}>
|
||||||
<div className={styles.imagePreview}>
|
<div className={styles.imagePreview}>
|
||||||
<PlusIcon />
|
<PlusIcon />
|
||||||
<span>퀴즈 이미지 추가</span>
|
<span>퀴즈 이미지 추가</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</label>
|
</label>
|
||||||
|
)}
|
||||||
|
<div className={styles.answerArea}>
|
||||||
|
<Toggle
|
||||||
|
active={quizType}
|
||||||
|
setActive={setQuizType}
|
||||||
|
choices={['단답식', '객관식']}
|
||||||
|
/>
|
||||||
<input
|
<input
|
||||||
id={`file-input-${quiz.id}`}
|
id={`file-input-${quiz.id}`}
|
||||||
type="file"
|
type="file"
|
||||||
@ -98,32 +141,49 @@ export default function QuizCard({ quiz, updateQuiz, deleteQuiz }) {
|
|||||||
onChange={handleFileChange}
|
onChange={handleFileChange}
|
||||||
className={styles.hiddenInput}
|
className={styles.hiddenInput}
|
||||||
/>
|
/>
|
||||||
<label className={styles.label}>질문</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={question}
|
|
||||||
maxLength={200}
|
|
||||||
onChange={(e) => {
|
|
||||||
setQuestion(e.target.value);
|
|
||||||
updateQuiz(quiz.id, { ...quiz, question: e.target.value, answer, choices, image });
|
|
||||||
}}
|
|
||||||
className={styles.input}
|
|
||||||
placeholder="질문 내용을 입력하세요"
|
|
||||||
/>
|
|
||||||
<label className={styles.label}>정답</label>
|
|
||||||
{choices.length > 0 ? (
|
|
||||||
<div className={styles.choicesWrapper}>
|
<div className={styles.choicesWrapper}>
|
||||||
|
<div className={styles.label}>정답</div>
|
||||||
|
{quizType === '객관식' ? (
|
||||||
|
<>
|
||||||
{choices.map((choice, index) => (
|
{choices.map((choice, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className={styles.choice}
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
key={index + 1}
|
|
||||||
onClick={() => handleChoiceSelect(index + 1)}
|
onClick={() => handleChoiceSelect(index + 1)}
|
||||||
className={`${styles.choiceButton} ${answer === index + 1 ? styles.selected : ''}`}
|
className={`${styles.choiceButton} ${answer === index + 1 ? styles.selected : ''}`}
|
||||||
>
|
>
|
||||||
{index + 1}
|
{index + 1}
|
||||||
</button>
|
</button>
|
||||||
))}
|
<input
|
||||||
|
className={`${styles.input} ${styles.choiceInput}`}
|
||||||
|
type="text"
|
||||||
|
maxLength={200}
|
||||||
|
value={choice.content}
|
||||||
|
onChange={(e) => handleChoiceChange(choice.num, e.target.value)}
|
||||||
|
placeholder={`보기 ${choice.num}`}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
))}
|
||||||
|
<div className={styles.buttonsWrapper}>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handlePopChoice}
|
||||||
|
className={`${styles.button} ${styles.remove} ${choices.length <= 1 ? styles.hidden : ''}`}
|
||||||
|
>
|
||||||
|
보기 줄이기
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleAddChoice}
|
||||||
|
className={`${styles.button} ${styles.add} ${choices.length >= 4 ? styles.hidden : ''}`}
|
||||||
|
>
|
||||||
|
보기 추가
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@ -137,43 +197,9 @@ export default function QuizCard({ quiz, updateQuiz, deleteQuiz }) {
|
|||||||
placeholder="정답을 입력하세요"
|
placeholder="정답을 입력하세요"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div>
|
|
||||||
<span>Tip: 선택지를 넣지 않는다면 단답형 문제가 됩니다</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.buttonsWrapper}>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={handleAddChoice}
|
|
||||||
className={`${styles.button} ${styles.add}`}
|
|
||||||
>
|
|
||||||
선택지 추가하기
|
|
||||||
</button>
|
|
||||||
{choices.length > 0 && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={handlePopChoice}
|
|
||||||
className={`${styles.button} ${styles.remove}`}
|
|
||||||
>
|
|
||||||
선택지 줄이기
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
{choices.map?.((choice, idx) => (
|
|
||||||
<div
|
|
||||||
className={styles.choiceDiv}
|
|
||||||
key={idx}
|
|
||||||
>
|
|
||||||
<label>선택지 {choice.num} </label>
|
|
||||||
<input
|
|
||||||
className={`${styles.input} ${styles.choiceInput}`}
|
|
||||||
type="text"
|
|
||||||
maxLength={200}
|
|
||||||
value={choice.content}
|
|
||||||
onChange={(e) => handleChoiceChange(choice.num, e.target.value)}
|
|
||||||
placeholder={`Choice ${choice.num}`}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,31 +1,81 @@
|
|||||||
.card {
|
.card {
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 16px 12px;
|
|
||||||
width: 416px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
gap: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.heading {
|
.titleGroup {
|
||||||
|
flex-grow: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
color: var(--text-color);
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
|
||||||
|
& > input {
|
||||||
|
flex-grow: 1;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px;
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 1.2;
|
||||||
|
font-weight: 700;
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: var(--text-color-tertiary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
align-items: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
font-size: 14px;
|
font-size: 12px;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
margin-bottom: 4px;
|
}
|
||||||
|
|
||||||
|
.imageArea {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
& > button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: absolute;
|
||||||
|
top: 4px;
|
||||||
|
right: 4px;
|
||||||
|
padding: 4px;
|
||||||
|
background-color: transparent;
|
||||||
|
stroke: var(--error-color);
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition:
|
||||||
|
background-color 0.1s,
|
||||||
|
stroke 0.1s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--error-color);
|
||||||
|
stroke: var(--on-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.imagePreview {
|
.imagePreview {
|
||||||
@ -37,7 +87,6 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
width: 295px;
|
width: 295px;
|
||||||
height: 220px;
|
height: 220px;
|
||||||
margin: 10px auto;
|
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background-color: var(--background-secondary);
|
background-color: var(--background-secondary);
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
@ -49,8 +98,15 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.answerArea {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.input {
|
.input {
|
||||||
padding: 14px;
|
padding: 12px;
|
||||||
background: var(--background);
|
background: var(--background);
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
@ -69,7 +125,7 @@
|
|||||||
|
|
||||||
.choiceInput {
|
.choiceInput {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
padding: 7px;
|
padding: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input::placeholder {
|
.input::placeholder {
|
||||||
@ -77,6 +133,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.buttonsWrapper {
|
.buttonsWrapper {
|
||||||
|
align-self: end;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
@ -85,27 +142,28 @@
|
|||||||
.button {
|
.button {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 12px 16px;
|
padding: 8px 12px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
font-weight: 700;
|
font-weight: 500;
|
||||||
align-self: end;
|
align-self: end;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.add {
|
.add {
|
||||||
border: 1px solid var(--primary-color);
|
border: none;
|
||||||
background-color: var(--primary-color);
|
background-color: var(--primary-color);
|
||||||
color: var(--on-primary);
|
color: var(--on-primary);
|
||||||
stroke: var(--on-primary);
|
stroke: var(--on-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.remove {
|
.remove {
|
||||||
border: 1px solid var(--blue100);
|
/* border: 1px solid var(--blue100); */
|
||||||
background-color: var(--blue100);
|
border: none;
|
||||||
color: var(--info-color);
|
background-color: var(--background-tertiary);
|
||||||
stroke: var(--info-color);
|
color: var(--text-color-);
|
||||||
|
stroke: var(--text-color-);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cardRemove {
|
.cardRemove {
|
||||||
@ -119,22 +177,27 @@
|
|||||||
|
|
||||||
.choicesWrapper {
|
.choicesWrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.choice {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.choiceButton {
|
.choiceButton {
|
||||||
padding: 10px 16px;
|
padding: 12px 16px;
|
||||||
background-color: var(--background-secondary);
|
background-color: var(--background-secondary);
|
||||||
border: 1px solid var(--background-tertiary);
|
border: 1px solid var(--background-tertiary);
|
||||||
border-radius: 4px;
|
border-radius: 6px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition:
|
transition:
|
||||||
background-color 0.25s,
|
background-color 0.1s,
|
||||||
border-color 0.25s,
|
border-color 0.1s,
|
||||||
stroke 0.25s,
|
stroke 0.1s,
|
||||||
color 0.25s;
|
color 0.1s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.choiceButton:hover {
|
.choiceButton:hover {
|
||||||
@ -152,3 +215,7 @@
|
|||||||
border: 1px solid var(--primary-color);
|
border: 1px solid var(--primary-color);
|
||||||
background-color: var(--primary-color);
|
background-color: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
@ -20,7 +20,7 @@ export default function QuizsetForm({ headerTitle, topic, to, onSubmit, initialV
|
|||||||
}, [initialValue]);
|
}, [initialValue]);
|
||||||
|
|
||||||
const handleAddQuiz = () => {
|
const handleAddQuiz = () => {
|
||||||
setQuizzes([...quizzes, { id: quizId, question: '', answer: '', choices: [], image: null }]);
|
setQuizzes([...quizzes, { id: quizId, question: '', answer: '', choices: [{ num: 1, content: '' }], image: null }]);
|
||||||
setQuizId(quizId + 1);
|
setQuizId(quizId + 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ export default function QuizsetForm({ headerTitle, topic, to, onSubmit, initialV
|
|||||||
className={styles.form}
|
className={styles.form}
|
||||||
onSubmit={(e) => onSubmit(e, title, quizzes)}
|
onSubmit={(e) => onSubmit(e, title, quizzes)}
|
||||||
>
|
>
|
||||||
<label className={styles.label}>퀴즈셋 제목</label>
|
<label className={styles.label}>제목</label>
|
||||||
<input
|
<input
|
||||||
className={styles.input}
|
className={styles.input}
|
||||||
type="text"
|
type="text"
|
||||||
@ -58,7 +58,7 @@ export default function QuizsetForm({ headerTitle, topic, to, onSubmit, initialV
|
|||||||
onChange={(e) => setTitle(e.target.value)}
|
onChange={(e) => setTitle(e.target.value)}
|
||||||
placeholder="퀴즈셋 제목을 입력해주세요"
|
placeholder="퀴즈셋 제목을 입력해주세요"
|
||||||
/>
|
/>
|
||||||
<div className={styles.grid}>
|
<div className={styles.quizList}>
|
||||||
{quizzes.map((quiz) => (
|
{quizzes.map((quiz) => (
|
||||||
<QuizCard
|
<QuizCard
|
||||||
key={quiz.id}
|
key={quiz.id}
|
||||||
|
@ -63,18 +63,20 @@
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid {
|
.quizList {
|
||||||
display: grid;
|
/* display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, 440px);
|
grid-template-columns: repeat(auto-fill, 440px); */
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
justify-content: start;
|
justify-content: start;
|
||||||
margin-bottom: 40px;
|
margin-bottom: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.addCard {
|
.addCard {
|
||||||
width: 440px;
|
/* width: 440px; */
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
height: 592px;
|
height: 120px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@ -83,6 +85,7 @@
|
|||||||
stroke: var(--text-color);
|
stroke: var(--text-color);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
transition: background-color 0.1s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.addCard:hover {
|
.addCard:hover {
|
||||||
|
18
frontend/src/components/Toggle/Toggle.jsx
Normal file
18
frontend/src/components/Toggle/Toggle.jsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import styles from './Toggle.module.css';
|
||||||
|
|
||||||
|
export default function Toggle({ choices, active, setActive }) {
|
||||||
|
return (
|
||||||
|
<div className={styles.toggle}>
|
||||||
|
{choices?.map((choice) => (
|
||||||
|
<button
|
||||||
|
key={choice}
|
||||||
|
type="button"
|
||||||
|
className={choice === active ? styles.active : ''}
|
||||||
|
onClick={() => setActive(choice)}
|
||||||
|
>
|
||||||
|
{choice}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
28
frontend/src/components/Toggle/Toggle.module.css
Normal file
28
frontend/src/components/Toggle/Toggle.module.css
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
.toggle {
|
||||||
|
display: flex;
|
||||||
|
justify-content: stretch;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
background-color: var(--background);
|
||||||
|
|
||||||
|
& > button {
|
||||||
|
border: none;
|
||||||
|
background-color: var(--background);
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
color: var(--text-color);
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.4;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
color: var(--on-primary);
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
frontend/src/components/Toggle/index.js
Normal file
1
frontend/src/components/Toggle/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default as Toggle } from './Toggle';
|
@ -112,7 +112,7 @@
|
|||||||
--whiteOpacity900: #fff;
|
--whiteOpacity900: #fff;
|
||||||
|
|
||||||
/* semantic colors */
|
/* semantic colors */
|
||||||
--primary-color: #05f;
|
--primary-color: #36f;
|
||||||
--accent-color: #f50;
|
--accent-color: #f50;
|
||||||
|
|
||||||
--background: #fff;
|
--background: #fff;
|
||||||
|
Loading…
Reference in New Issue
Block a user