mirror of https://github.com/gtitov/wcwb.git
parent
9a54c78f0b
commit
53c1d0e0e1
@ -0,0 +1,236 @@
|
|||||||
|
<multiple-choice
|
||||||
|
data-correct-label="Верно 😸"
|
||||||
|
data-incorrect-label="Надо поразмыслить 🐱"
|
||||||
|
>
|
||||||
|
<form onsubmit="event.preventDefault()">
|
||||||
|
<ol class="opt-list not-content"><slot /></ol>
|
||||||
|
<div class="footer">
|
||||||
|
<button class="submit" type="submit" disabled> Проверить </button>
|
||||||
|
<div class="answer sr-only" role="alert"></div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</multiple-choice>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
multiple-choice {
|
||||||
|
display: block;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
color: var(--sl-color-text-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
form > * + * {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.opt-list {
|
||||||
|
list-style: none;
|
||||||
|
padding-inline-start: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit,
|
||||||
|
.answer {
|
||||||
|
padding: 0.8rem;
|
||||||
|
border-radius: 1.5rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit {
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--sl-color-accent);
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit:focus {
|
||||||
|
background-color: hsl(224, 20%, 94%);
|
||||||
|
outline: 3px solid var(--sl-color-accent);
|
||||||
|
outline-offset: -3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit:not(:focus-visible) {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit:hover {
|
||||||
|
background: var(--sl-color-text-accent);
|
||||||
|
color: var(--sl-color-black);
|
||||||
|
border-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit:disabled,
|
||||||
|
.submit:active {
|
||||||
|
transform: translateY(0.25rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit:disabled {
|
||||||
|
background-color: hsl(224, 20%, 94%);
|
||||||
|
color: hsl(224, 7%, 36%);
|
||||||
|
opacity: 0.65;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.answer {
|
||||||
|
text-align: center;
|
||||||
|
transition-property: color, background-color;
|
||||||
|
transition-duration: 250ms;
|
||||||
|
transition-timing-function: ease-out;
|
||||||
|
border-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.correct {
|
||||||
|
color: var(--sl-color-green-low);
|
||||||
|
background-color: var(--sl-color-green);
|
||||||
|
animation: tada 1s 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrong {
|
||||||
|
color: var(--sl-color-red-low);
|
||||||
|
background-color: var(--sl-color-red);
|
||||||
|
animation: nope 0.3s 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
.submit {
|
||||||
|
transition-property: box-shadow, transform;
|
||||||
|
transition-duration: 0.15s;
|
||||||
|
transition-timing-function: cubic-bezier(0.4, 2.5, 0.6, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.correct,
|
||||||
|
.wrong {
|
||||||
|
animation-iteration-count: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes tada {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
transform: scale3d(1, 1, 1);
|
||||||
|
}
|
||||||
|
10%,
|
||||||
|
20% {
|
||||||
|
transform: scale3d(0.8, 0.8, 0.8) rotate3d(0, 0, 1, -3deg);
|
||||||
|
}
|
||||||
|
30%,
|
||||||
|
50%,
|
||||||
|
70%,
|
||||||
|
90% {
|
||||||
|
transform: scale3d(1.05, 1.05, 1.05) rotate3d(0, 0, 1, 3deg);
|
||||||
|
}
|
||||||
|
40%,
|
||||||
|
60%,
|
||||||
|
80% {
|
||||||
|
transform: scale3d(1.05, 1.05, 1.05) rotate3d(0, 0, 1, -3deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes nope {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
transform: translate3d(0, 0, 0);
|
||||||
|
}
|
||||||
|
20%,
|
||||||
|
60% {
|
||||||
|
transform: translate3d(-0.5rem, 0, 0);
|
||||||
|
}
|
||||||
|
40%,
|
||||||
|
80% {
|
||||||
|
transform: translate3d(0.5rem, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
class MultipleChoice extends HTMLElement {
|
||||||
|
#defaultCorrectLabel: string;
|
||||||
|
#defaultIncorrectLabel: string;
|
||||||
|
#key: string;
|
||||||
|
#submitEl: HTMLButtonElement;
|
||||||
|
#answerEl: HTMLParagraphElement;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.#defaultCorrectLabel = this.dataset.correctLabel!;
|
||||||
|
this.#defaultIncorrectLabel = this.dataset.incorrectLabel!;
|
||||||
|
this.#key = Math.random().toString();
|
||||||
|
this.#submitEl = this.querySelector(".submit")!;
|
||||||
|
this.#answerEl = this.querySelector(".answer")!;
|
||||||
|
this.querySelectorAll(".opt-list > li").forEach((li) =>
|
||||||
|
this.#upgradeListItem(li)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#upgradeListItem(li: Element) {
|
||||||
|
const option = li.querySelector(
|
||||||
|
'input[type="radio"]'
|
||||||
|
) as HTMLInputElement | null;
|
||||||
|
if (!option) return;
|
||||||
|
|
||||||
|
option.removeAttribute("disabled");
|
||||||
|
option.setAttribute("name", this.#key);
|
||||||
|
option.addEventListener("change", () => {
|
||||||
|
this.#clearAnswer();
|
||||||
|
this.#enableSubmit();
|
||||||
|
});
|
||||||
|
if (option.checked) this.#enableSubmit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Clear the answer text and hide its container visually. */
|
||||||
|
#clearAnswer() {
|
||||||
|
this.#answerEl.innerText = "";
|
||||||
|
this.#answerEl.classList.remove("wrong", "correct");
|
||||||
|
this.#answerEl.classList.add("sr-only");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Show the answer result to the user. */
|
||||||
|
#setAnswer(isCorrect: boolean) {
|
||||||
|
const answerTemplate = this.querySelector(
|
||||||
|
"input:checked ~ template"
|
||||||
|
) as HTMLTemplateElement | null;
|
||||||
|
if (answerTemplate) {
|
||||||
|
this.#answerEl.replaceChildren(answerTemplate.content.cloneNode(true));
|
||||||
|
} else {
|
||||||
|
this.#answerEl.innerText = isCorrect
|
||||||
|
? this.#defaultCorrectLabel
|
||||||
|
: this.#defaultIncorrectLabel;
|
||||||
|
}
|
||||||
|
this.#answerEl.classList.remove("sr-only", "wrong", "correct");
|
||||||
|
this.#answerEl.classList.add(isCorrect ? "correct" : "wrong");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Activate the submit button, preparing it to evaluate the form when clicked. */
|
||||||
|
#enableSubmit() {
|
||||||
|
this.#submitEl.removeAttribute("disabled");
|
||||||
|
this.#submitEl.classList.remove("sr-only");
|
||||||
|
this.#submitEl.onclick = () => this.#submitAnswer();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Disable the submit button and hide it visually. */
|
||||||
|
#disableSubmit() {
|
||||||
|
this.#submitEl.setAttribute("disabled", "");
|
||||||
|
this.#submitEl.classList.add("sr-only");
|
||||||
|
this.#submitEl.onclick = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Check if the selected option is correct and display the result. */
|
||||||
|
#submitAnswer() {
|
||||||
|
const selection = this.querySelector(
|
||||||
|
"input:checked"
|
||||||
|
) as HTMLInputElement | null;
|
||||||
|
if (!selection) return;
|
||||||
|
|
||||||
|
this.#disableSubmit();
|
||||||
|
const isCorrect =
|
||||||
|
selection.dataset.isCorrect !== undefined &&
|
||||||
|
["", "true"].includes(selection.dataset.isCorrect);
|
||||||
|
this.#setAnswer(isCorrect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("multiple-choice", MultipleChoice);
|
||||||
|
</script>
|
||||||
@ -0,0 +1,94 @@
|
|||||||
|
---
|
||||||
|
export interface Props {
|
||||||
|
isCorrect?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { isCorrect } = Astro.props as Props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<label>
|
||||||
|
<input type="radio" disabled data-is-correct={isCorrect} /><span
|
||||||
|
><slot /></span
|
||||||
|
>
|
||||||
|
{
|
||||||
|
Astro.slots.has("feedback") && (
|
||||||
|
<template>
|
||||||
|
<slot name="feedback" />
|
||||||
|
</template>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
label {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto 1fr;
|
||||||
|
gap: 1rem;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 1rem;
|
||||||
|
margin-inline: -0.5rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
label:hover {
|
||||||
|
background-color: var(--sl-color-accent-low);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="radio"] {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="radio"] ~ * {
|
||||||
|
color: var(--sl-color-text-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="radio"]:checked ~ * {
|
||||||
|
color: var(--sl-color-white);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="radio"]:focus::after {
|
||||||
|
outline: 3px solid var(--sl-color-text-accent);
|
||||||
|
outline-offset: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="radio"]:focus,
|
||||||
|
input[type="radio"]:not(:focus-visible)::after {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="radio"]::after {
|
||||||
|
display: block;
|
||||||
|
content: "";
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.5;
|
||||||
|
width: 1.5em;
|
||||||
|
height: 1.5em;
|
||||||
|
font-size: 1.25em;
|
||||||
|
font-weight: 900;
|
||||||
|
border: 2px solid var(--sl-color-accent-high);
|
||||||
|
border-radius: 100%;
|
||||||
|
color: var(--sl-color-white);
|
||||||
|
box-shadow: -3px 3px var(--theme-shade-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="radio"]:checked::after {
|
||||||
|
color: hsl(var(--color-base-white), 100%);
|
||||||
|
background-image: radial-gradient(
|
||||||
|
var(--sl-color-white) 50%,
|
||||||
|
transparent 0%,
|
||||||
|
transparent
|
||||||
|
);
|
||||||
|
border-color: var(--sl-color-white);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (forced-colors: active) {
|
||||||
|
input[type="radio"]:checked::after {
|
||||||
|
background-color: Highlight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
Reference in new issue