feat(kuis): navigator soal horizontal di mobile (poin 3)
Satukan prev/dots/next-Selesai dalam satu baris scroll-x dan hapus kontrol duplikat di bawah soal.
This commit is contained in:
parent
6971d0a3c3
commit
ec34ffed72
|
|
@ -10,6 +10,15 @@
|
||||||
const questions = $derived(mgr.quizSession?.questions ?? []);
|
const questions = $derived(mgr.quizSession?.questions ?? []);
|
||||||
|
|
||||||
let submitting = $state(false);
|
let submitting = $state(false);
|
||||||
|
let navigatorEl = $state<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
|
// Jaga dot aktif tetap terlihat saat navigasi (terutama mobile, scroll-x).
|
||||||
|
$effect(() => {
|
||||||
|
const idx = mgr.quizCurrentIndex;
|
||||||
|
const dots = navigatorEl?.querySelectorAll<HTMLElement>('.nav-dot');
|
||||||
|
if (!dots || dots.length === 0) return;
|
||||||
|
dots[idx]?.scrollIntoView({ block: 'nearest', inline: 'nearest' });
|
||||||
|
});
|
||||||
|
|
||||||
function isAnswered(qId: string): boolean {
|
function isAnswered(qId: string): boolean {
|
||||||
const a = mgr.quizSession?.answers[qId];
|
const a = mgr.quizSession?.answers[qId];
|
||||||
|
|
@ -38,21 +47,52 @@
|
||||||
<div class="quiz-question-view">
|
<div class="quiz-question-view">
|
||||||
<div class="quiz-question-header">
|
<div class="quiz-question-header">
|
||||||
{#if questions.length > 0}
|
{#if questions.length > 0}
|
||||||
<div class="quiz-navigator" aria-label="Navigasi soal">
|
<div class="quiz-nav-row">
|
||||||
{#each questions as q, i}
|
<button
|
||||||
{@const answered = isAnswered(q.id)}
|
type="button"
|
||||||
|
class="btn btn-outline quiz-nav-btn"
|
||||||
|
onclick={() => mgr.prevQuizQuestion()}
|
||||||
|
disabled={mgr.quizCurrentIndex === 0}
|
||||||
|
aria-label="Soal sebelumnya"
|
||||||
|
>
|
||||||
|
← <span class="quiz-nav-label">Sebelumnya</span>
|
||||||
|
</button>
|
||||||
|
<div class="quiz-navigator" bind:this={navigatorEl} aria-label="Navigasi soal">
|
||||||
|
{#each questions as q, i}
|
||||||
|
{@const answered = isAnswered(q.id)}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="nav-dot"
|
||||||
|
class:active={i === mgr.quizCurrentIndex}
|
||||||
|
class:answered
|
||||||
|
data-dot-index={i}
|
||||||
|
aria-label={`Soal ${i + 1}${answered ? ' (dijawab)' : ' (belum dijawab)'}`}
|
||||||
|
aria-current={i === mgr.quizCurrentIndex ? 'step' : undefined}
|
||||||
|
onclick={() => mgr.goToQuizQuestion(i)}
|
||||||
|
>
|
||||||
|
{i + 1}
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{#if mgr.quizCurrentIndex < questions.length - 1}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="nav-dot"
|
class="btn btn-primary quiz-nav-btn"
|
||||||
class:active={i === mgr.quizCurrentIndex}
|
onclick={() => mgr.nextQuizQuestion()}
|
||||||
class:answered
|
aria-label="Soal selanjutnya"
|
||||||
aria-label={`Soal ${i + 1}${answered ? ' (dijawab)' : ' (belum dijawab)'}`}
|
|
||||||
aria-current={i === mgr.quizCurrentIndex ? 'step' : undefined}
|
|
||||||
onclick={() => mgr.goToQuizQuestion(i)}
|
|
||||||
>
|
>
|
||||||
{i + 1}
|
<span class="quiz-nav-label">Selanjutnya</span> →
|
||||||
</button>
|
</button>
|
||||||
{/each}
|
{:else}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-success quiz-nav-btn quiz-nav-finish"
|
||||||
|
onclick={handleFinish}
|
||||||
|
disabled={!mgr.quizAllAnswered || submitting}
|
||||||
|
>
|
||||||
|
{submitting ? 'Menyimpan…' : 'Selesai ✓'}
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<button type="button" class="btn-exit-quiz" onclick={() => mgr.submitQuiz()} disabled={submitting}>Keluar Kuis</button>
|
<button type="button" class="btn-exit-quiz" onclick={() => mgr.submitQuiz()} disabled={submitting}>Keluar Kuis</button>
|
||||||
|
|
@ -74,31 +114,4 @@
|
||||||
{:else}
|
{:else}
|
||||||
<p class="quiz-empty">Tidak ada soal.</p>
|
<p class="quiz-empty">Tidak ada soal.</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if questions.length > 0}
|
|
||||||
<div class="quiz-controls">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn btn-outline"
|
|
||||||
onclick={() => mgr.prevQuizQuestion()}
|
|
||||||
disabled={mgr.quizCurrentIndex === 0}
|
|
||||||
>
|
|
||||||
← Sebelumnya
|
|
||||||
</button>
|
|
||||||
{#if mgr.quizCurrentIndex < questions.length - 1}
|
|
||||||
<button type="button" class="btn btn-primary" onclick={() => mgr.nextQuizQuestion()}>
|
|
||||||
Selanjutnya →
|
|
||||||
</button>
|
|
||||||
{:else}
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn btn-success"
|
|
||||||
onclick={handleFinish}
|
|
||||||
disabled={!mgr.quizAllAnswered || submitting}
|
|
||||||
>
|
|
||||||
{submitting ? 'Menyimpan...' : 'Selesai Kuis'}
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -452,9 +452,29 @@
|
||||||
.quiz-question-header {
|
.quiz-question-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 1rem;
|
gap: 0.75rem;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
flex-wrap: wrap;
|
flex-wrap: nowrap;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quiz-nav-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.4rem;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quiz-nav-btn {
|
||||||
|
flex-shrink: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
padding: 0.35rem 0.6rem;
|
||||||
|
}
|
||||||
|
.quiz-nav-btn:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
.quiz-question-header .btn-exit-quiz {
|
.quiz-question-header .btn-exit-quiz {
|
||||||
|
|
@ -465,6 +485,7 @@
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.quiz-question-prose {
|
.quiz-question-prose {
|
||||||
|
|
@ -504,11 +525,21 @@
|
||||||
/* ── Quiz navigator & controls (dipindah dari QuizTab.svelte) ── */
|
/* ── Quiz navigator & controls (dipindah dari QuizTab.svelte) ── */
|
||||||
.quiz-navigator {
|
.quiz-navigator {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: nowrap;
|
||||||
gap: 0.4rem;
|
gap: 0.4rem;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
overflow-x: auto;
|
||||||
|
overscroll-behavior-x: contain;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
.quiz-navigator::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-dot {
|
.nav-dot {
|
||||||
|
flex-shrink: 0;
|
||||||
width: 34px;
|
width: 34px;
|
||||||
height: 34px;
|
height: 34px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
|
@ -534,29 +565,23 @@
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.quiz-controls {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 1rem;
|
|
||||||
position: relative;
|
|
||||||
z-index: 10;
|
|
||||||
background: var(--color-bg);
|
|
||||||
padding-top: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-outline {
|
.btn-outline {
|
||||||
background: white;
|
background: white;
|
||||||
border: 1px solid var(--color-border);
|
border: 1px solid var(--color-border);
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.quiz-controls .btn:disabled {
|
|
||||||
opacity: 0.5;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
.quiz-question-view {
|
.quiz-question-view {
|
||||||
padding: 1rem 0;
|
padding: 1rem 0;
|
||||||
}
|
}
|
||||||
|
.quiz-question-header {
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
.quiz-nav-label {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.quiz-nav-btn {
|
||||||
|
padding: 0.35rem 0.5rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue