diff --git a/frontend/src/components/FacetPicker.jsx b/frontend/src/components/FacetPicker.jsx index 32cffeb..2eb25f0 100644 --- a/frontend/src/components/FacetPicker.jsx +++ b/frontend/src/components/FacetPicker.jsx @@ -1,15 +1,20 @@ import { useEffect, useState } from 'react' -/** Collapsed facet row: label on the left, current selection on the right. */ -export function FacetRow({ label, summary, extra, onOpen }) { +/** + * Collapsed facet row: a + affordance, the label, and the current selection on + * the right — plain "All" when nothing is picked, a chip (plus a +N chip) once + * something is. + */ +export function FacetRow({ label, summary, extra, chip, onOpen }) { + const selected = chip ?? summary !== 'All' return ( ) } diff --git a/frontend/src/pages/CustomQuizPage.css b/frontend/src/pages/CustomQuizPage.css index 11df2c6..18fe683 100644 --- a/frontend/src/pages/CustomQuizPage.css +++ b/frontend/src/pages/CustomQuizPage.css @@ -1,71 +1,159 @@ /* Custom test builder — AMBOSS-style facet rows that open a picker panel, instead of a cramped sidebar of nested scrolling checkbox lists. */ -.custom-test { max-width: 720px; margin: 0 auto; padding-bottom: 40px; } -.custom-test-back { font-size: 0.85rem; color: var(--primary); text-decoration: none; } -.custom-test h1 { margin: 10px 0 4px; font-size: 1.5rem; } -.custom-test-intro { margin: 0 0 20px; color: var(--text-muted); font-size: 0.9rem; } +.custom-test { max-width: 1120px; margin: 0 auto; padding-bottom: 100px; } -/* ── Section headings ─────────────────────────────────────────────── */ -.custom-test-section { margin-bottom: 22px; } -.custom-test-section-head { - display: flex; align-items: center; gap: 12px; margin: 0 0 10px; +.custom-test-top { + display: grid; grid-template-columns: 1fr auto; align-items: end; + gap: 6px 16px; margin-bottom: 16px; } -.custom-test-section-head h2 { margin: 0; font-size: 1rem; font-weight: 650; white-space: nowrap; } -.custom-test-section-head::after { content: ''; flex: 1; height: 1px; background: var(--border); } +.custom-test-back { grid-column: 1 / -1; font-size: 0.85rem; color: var(--primary); text-decoration: none; } +.custom-test-top h1 { margin: 8px 0 4px; font-size: 1.5rem; } +.custom-test-intro { margin: 0; color: var(--text-muted); font-size: 0.9rem; } .custom-test-reset { - background: none; border: none; padding: 0; cursor: pointer; flex-shrink: 0; + display: inline-flex; align-items: center; gap: 6px; align-self: end; + background: none; border: none; padding: 4px; cursor: pointer; font: inherit; font-size: 0.74rem; font-weight: 700; letter-spacing: 0.06em; - text-transform: uppercase; color: var(--primary); + text-transform: uppercase; color: var(--text-muted); white-space: nowrap; } +.custom-test-reset:hover { color: var(--primary); } + +/* ── Two cards side by side ───────────────────────────────────────── */ +.custom-test-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; align-items: start; } +.custom-test-card { + background: var(--card-bg); border: 1px solid var(--border); + border-radius: 12px; overflow: hidden; +} +.custom-test-card > h2 { + margin: 0; padding: 16px 18px; font-size: 1.05rem; font-weight: 650; + border-bottom: 1px solid var(--border); +} +.custom-test-card-pad { padding: 14px 18px; } .custom-test-label { display: block; font-size: 0.68rem; font-weight: 700; letter-spacing: 0.07em; text-transform: uppercase; color: var(--text-subtle); margin: 0 0 6px; } /* ── Global facet search ──────────────────────────────────────────── */ -.custom-test-search { position: relative; margin-bottom: 12px; } +.custom-test-search { position: relative; } .custom-test-search input { width: 100%; padding: 10px 13px 10px 34px; font-size: 0.9rem; border: 1px solid var(--border); border-radius: 8px; - background: var(--input-bg); color: var(--text); + background: var(--bg); color: var(--text); } .custom-test-search-icon { position: absolute; left: 11px; top: 50%; transform: translateY(-50%); color: var(--text-subtle); } -.custom-test-hits { border: 1px solid var(--border); border-radius: 10px; overflow: hidden; margin-bottom: 12px; } +.custom-test-hits { border-top: 1px solid var(--border); } .custom-test-hits label { - display: flex; align-items: center; gap: 10px; padding: 10px 13px; + display: flex; align-items: center; gap: 10px; padding: 10px 18px; font-size: 0.88rem; cursor: pointer; border-bottom: 1px solid var(--border); } -.custom-test-hits label:last-child { border-bottom: 0; } .custom-test-hits label:hover { background: var(--bg); } .custom-test-hit-facet { margin-left: auto; font-size: 0.68rem; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; color: var(--text-subtle); } -.custom-test-hits-empty { padding: 14px; color: var(--text-muted); font-size: 0.86rem; } +.custom-test-hits-empty { padding: 12px 18px; color: var(--text-muted); font-size: 0.86rem; margin: 0; border-top: 1px solid var(--border); } /* ── Facet rows ───────────────────────────────────────────────────── */ -.facet-list { border: 1px solid var(--border); border-radius: 10px; overflow: hidden; background: var(--card-bg); } +.facet-list { border-top: 1px solid var(--border); } .facet-row { display: flex; align-items: center; gap: 12px; width: 100%; - padding: 13px 15px; background: none; border: none; border-bottom: 1px solid var(--border); + padding: 14px 18px; background: none; border: none; border-bottom: 1px solid var(--border); font: inherit; text-align: left; cursor: pointer; color: var(--text); } .facet-row:last-child { border-bottom: 0; } .facet-row:hover { background: var(--bg); } -.facet-row-label { font-size: 0.9rem; font-weight: 600; white-space: nowrap; } +.facet-row-icon { color: var(--text-subtle); font-size: 1.15rem; line-height: 1; flex-shrink: 0; width: 14px; } +.facet-row:hover .facet-row-icon { color: var(--primary); } +.facet-row-label { font-size: 0.95rem; } .facet-row-summary { margin-left: auto; display: flex; align-items: center; gap: 6px; min-width: 0; } -.facet-row-value { - font-size: 0.86rem; color: var(--text-muted); - overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 46vw; +.facet-row-value { font-size: 0.9rem; color: var(--text-muted); white-space: nowrap; } +.facet-chip { + font-size: 0.82rem; border-radius: 6px; padding: 3px 9px; + background: var(--option-sel-bg); color: var(--primary); white-space: nowrap; + overflow: hidden; text-overflow: ellipsis; max-width: 190px; } -.facet-row-more { - flex-shrink: 0; font-size: 0.72rem; font-weight: 700; - background: var(--primary); color: var(--primary-fg); border-radius: 20px; padding: 1px 8px; -} -.facet-row-chevron { color: var(--text-subtle); font-size: 1.1rem; line-height: 1; flex-shrink: 0; } +.facet-chip.is-more { flex-shrink: 0; font-weight: 700; } .facet-row.is-fixed { cursor: default; } .facet-row.is-fixed:hover { background: none; } +.facet-row.is-fixed .facet-row-icon { color: var(--text-subtle); } + +/* ── Criteria ─────────────────────────────────────────────────────── */ +.custom-test-field { display: block; } +.custom-test-field > span { display: block; font-size: 0.68rem; font-weight: 700; letter-spacing: 0.07em; text-transform: uppercase; color: var(--text-subtle); margin-bottom: 6px; } +.custom-test-field input { + width: 100%; padding: 10px 13px; font-size: 0.95rem; + border: 1px solid var(--border); border-radius: 8px; + background: var(--input-bg); color: var(--text); +} +.custom-test-adaptive { + display: flex; align-items: center; gap: 12px; + padding: 14px 18px; border-top: 1px solid var(--border); +} +.custom-test-adaptive > div { flex: 1; min-width: 0; } +.custom-test-adaptive strong { display: block; font-size: 0.98rem; font-weight: 650; color: #6d28d9; } +.custom-test-adaptive p { margin: 2px 0 0; font-size: 0.82rem; color: var(--text-muted); } +.custom-test-spark { margin-right: 4px; } +.custom-test-toggle { display: inline-flex; align-items: center; cursor: pointer; flex-shrink: 0; } +.custom-test-toggle input { position: absolute; opacity: 0; width: 0; height: 0; } +.custom-test-switch { + width: 40px; height: 22px; border-radius: 999px; background: var(--border); + position: relative; flex-shrink: 0; transition: background 0.15s; +} +.custom-test-switch > span { + position: absolute; top: 3px; left: 3px; width: 16px; height: 16px; + border-radius: 50%; background: #fff; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); transition: transform 0.15s; +} +.custom-test-toggle input:checked + .custom-test-switch { background: #7c3aed; } +.custom-test-toggle input:checked + .custom-test-switch > span { transform: translateX(18px); } +.custom-test-toggle input:focus-visible + .custom-test-switch { outline: 2px solid var(--primary); outline-offset: 2px; } + +.custom-test-more { + display: flex; align-items: center; gap: 6px; width: 100%; + padding: 12px 18px; background: none; border: none; border-top: 1px solid var(--border); + font: inherit; font-size: 0.76rem; font-weight: 700; letter-spacing: 0.06em; + text-transform: uppercase; color: var(--text-muted); cursor: pointer; +} +.custom-test-more:hover { color: var(--primary); } +.custom-test-check { display: flex; align-items: center; gap: 9px; font-size: 0.88rem; cursor: pointer; } + +.custom-test-countbox { padding: 14px 18px; border-top: 1px solid var(--border); } +.custom-test-count-row { display: flex; align-items: center; gap: 10px; } +.custom-test-count-row input { + width: 92px; padding: 10px 12px; font-size: 1rem; + border: 1px solid var(--border); border-radius: 8px; + background: var(--input-bg); color: var(--text); +} +.custom-test-count-of { color: var(--text-muted); font-size: 1rem; } +.custom-test-count-note { margin: 8px 0 0; font-size: 0.8rem; color: var(--text-subtle); } +.custom-test-note { color: var(--text-muted); font-size: 0.8rem; line-height: 1.6; margin: 8px 0 0; } +.custom-test-error { color: var(--wrong-fg); font-size: 0.84rem; margin: 8px 0 0; } + +/* ── Sticky bottom bar: mode and Start stay reachable without scrolling ── */ +.custom-test-bar { + grid-column: 1 / -1; + position: sticky; bottom: 0; z-index: 40; + margin: 4px -16px -100px; padding: 0 16px; + background: var(--card-bg); border-top: 1px solid var(--border); + box-shadow: 0 -6px 20px rgba(0, 0, 0, 0.06); +} +.custom-test-bar-inner { + max-width: 1120px; margin: 0 auto; + display: flex; align-items: center; gap: 10px; flex-wrap: wrap; + padding: 12px 0 calc(12px + env(safe-area-inset-bottom)); +} +.custom-test-bar-label { margin: 0; } +.custom-test-modes { display: flex; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; } +.custom-test-modes label { cursor: pointer; } +.custom-test-modes input { position: absolute; opacity: 0; width: 0; height: 0; } +.custom-test-modes span { + display: block; padding: 9px 18px; font-size: 0.9rem; font-weight: 600; + color: var(--text-muted); background: var(--card-bg); white-space: nowrap; +} +.custom-test-modes label + label span { border-left: 1px solid var(--border); } +.custom-test-modes input:checked + span { background: var(--option-sel-bg); color: var(--primary); } +.custom-test-modes input:focus-visible + span { outline: 2px solid var(--primary); outline-offset: -2px; } +.custom-test-start { margin-left: auto; min-width: 150px; } /* ── Picker panel ─────────────────────────────────────────────────── */ .facet-overlay { @@ -153,10 +241,20 @@ .custom-test-actions { display: flex; gap: 8px; margin-top: 18px; } .custom-test-actions .btn:last-child { flex: 1; } +@media (max-width: 900px) { + .custom-test-grid { grid-template-columns: 1fr; } +} @media (max-width: 640px) { - .custom-test-modes label { min-width: 100%; } + .custom-test { padding-bottom: 120px; } + .custom-test-top { grid-template-columns: 1fr; } + .custom-test-reset { justify-self: start; padding-left: 0; } .facet-overlay { align-items: flex-end; } .facet-panel { width: 100%; height: 88vh; border-radius: 16px 16px 0 0; } - .facet-row-value { max-width: 40vw; } - .custom-test-actions { flex-direction: column; } + .facet-chip { max-width: 40vw; } + .custom-test-bar-label { display: none; } + .custom-test-bar-inner { padding: 10px 0 calc(10px + env(safe-area-inset-bottom)); } + .custom-test-modes { flex: 1; } + .custom-test-modes label { flex: 1; } + .custom-test-modes span { text-align: center; padding: 9px 10px; } + .custom-test-start { width: 100%; margin-left: 0; } } diff --git a/frontend/src/pages/CustomQuizPage.jsx b/frontend/src/pages/CustomQuizPage.jsx index ca4fc74..d5a65c2 100644 --- a/frontend/src/pages/CustomQuizPage.jsx +++ b/frontend/src/pages/CustomQuizPage.jsx @@ -49,6 +49,7 @@ export default function CustomQuizPage() { const [collections, setCollections] = useState([]) const [openFacet, setOpenFacet] = useState(null) const [globalSearch, setGlobalSearch] = useState('') + const [moreOpen, setMoreOpen] = useState(false) const filterKey = JSON.stringify([categoryIds, state, shared, difficulty, articleIds, tagIds, refresh]) @@ -211,28 +212,34 @@ export default function CustomQuizPage() { return (
Choose questions from your bank, {user?.name || 'learner'}.
+Choose questions from your bank, {user?.name || 'learner'}.
+