autogenerating sidebar links
This commit is contained in:
parent
1546fa8199
commit
6f5cbd21aa
13 changed files with 522 additions and 34 deletions
79
src/components/Box.astro
Normal file
79
src/components/Box.astro
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
---
|
||||
import { getImage } from "astro:assets";
|
||||
import HoustonGrad from "~/assets/houston_grad.png";
|
||||
|
||||
export interface Props {
|
||||
icon?: "puzzle-piece" | "question-mark" | "check-list";
|
||||
}
|
||||
|
||||
const { icon } = Astro.props;
|
||||
---
|
||||
|
||||
<div
|
||||
class={`highlight-box content ${icon}`}
|
||||
style={icon === "question-mark" ? `--box-icon-url: url('nah');` : ""}
|
||||
>
|
||||
<section>
|
||||
<slot />
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
:global(section > *) + .highlight-box {
|
||||
margin: 4rem 0;
|
||||
}
|
||||
|
||||
.highlight-box {
|
||||
position: relative;
|
||||
padding-block: 2rem;
|
||||
padding-inline-start: 2rem;
|
||||
padding-inline-end: 4rem;
|
||||
border: 1px solid var(--sl-color-blue);
|
||||
background-color: var(--sl-color-blue-low);
|
||||
margin-top: 2.5rem !important;
|
||||
}
|
||||
|
||||
.highlight-box.check-list {
|
||||
border: 1px solid var(--sl-color-green);
|
||||
background-color: var(--sl-color-green-low);
|
||||
}
|
||||
@supports (mask: var(--box-icon-url) no-repeat center bottom / 100%) or
|
||||
(-webkit-mask: var(--box-icon-url) no-repeat center bottom / 100%) {
|
||||
.highlight-box::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -2.5rem;
|
||||
inset-inline-end: 1.5rem;
|
||||
width: 8rem;
|
||||
height: 8rem;
|
||||
background: var(--box-icon-url) no-repeat center center / 100%;
|
||||
}
|
||||
}
|
||||
|
||||
:global(ol) {
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
|
||||
.highlight-box :global(details) {
|
||||
--sl-details-border-color: var(--sl-color-purple);
|
||||
--sl-details-border-color--hover: var(--sl-color-purple-high);
|
||||
}
|
||||
|
||||
@media (max-width: 50em) {
|
||||
.highlight-box::before {
|
||||
width: 5rem;
|
||||
height: 5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style is:global>
|
||||
.highlight-box.question-mark .heading-wrapper:first-child {
|
||||
margin-inline-end: 6rem !important;
|
||||
}
|
||||
@media (max-width: 50em) {
|
||||
.highlight-box.question-mark .heading-wrapper:first-child {
|
||||
margin-inline-end: 3rem !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
108
src/components/Checklist.astro
Normal file
108
src/components/Checklist.astro
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
<div class="checklist"><slot /></div>
|
||||
{
|
||||
Astro.slots.has('alternative') && (
|
||||
<>
|
||||
<p class="or">
|
||||
foo
|
||||
</p>
|
||||
<div class="checklist alternative">
|
||||
<slot name="alternative" />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
<style>
|
||||
check-list > * + * {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.checklist > :global(ul) {
|
||||
list-style: none;
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
|
||||
check-list :global(label) {
|
||||
display: block;
|
||||
position: relative;
|
||||
margin: 0 -1rem !important;
|
||||
border-radius: 0.5rem;
|
||||
padding-block: 0.5rem;
|
||||
padding-inline: 3.5rem 0.5rem;
|
||||
cursor: pointer;
|
||||
font-size: var(--sl-text-lg);
|
||||
color: var(--sl-color-green-high);
|
||||
}
|
||||
|
||||
check-list :global(label span) {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
check-list :global(input[type='checkbox']) {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
check-list :global(input[type='checkbox']:focus) {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
check-list :global(input[type='checkbox']::after) {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset-inline-start: 1rem;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
transform-origin: center;
|
||||
text-align: center;
|
||||
line-height: 1.25;
|
||||
width: 1.25em;
|
||||
height: 1.25em;
|
||||
font-size: 1.25em;
|
||||
border: 1px solid var(--sl-color-green-high);
|
||||
border-radius: 0.25rem;
|
||||
color: var(--sl-color-green);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
check-list :global(input[type='checkbox']:focus::after) {
|
||||
outline: 3px solid var(--sl-color-green);
|
||||
outline-offset: 0.5rem;
|
||||
}
|
||||
|
||||
check-list :global(input[type='checkbox']:not(:focus-visible)::after) {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
check-list :global(label:has(> input[type='checkbox']:checked)) {
|
||||
color: var(--sl-color-white);
|
||||
}
|
||||
|
||||
check-list :global(input[type='checkbox']:checked::after) {
|
||||
content: url("data:image/svg+xml,%3Csvg width='28' height='28' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect width='24' height='24' rx='4' fill='white'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M20.0946 6.02558C20.6992 6.592 20.7301 7.54124 20.1636 8.14578L10.3868 18.5806C9.55548 19.468 8.14726 19.468 7.3159 18.5806L4.02558 15.0689C3.45917 14.4643 3.49008 13.5151 4.09461 12.9487C4.69915 12.3822 5.6484 12.4132 6.21481 13.0177L8.85137 15.8317L17.9744 6.09461C18.5408 5.49007 19.4901 5.45917 20.0946 6.02558Z' fill='%232F4D22'/%3E%3C/svg%3E%0A");
|
||||
border: none;
|
||||
/* color: var(--sl-color-green-low); */
|
||||
/* background-color: var(--sl-color-gray-1); */
|
||||
}
|
||||
|
||||
.or {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
gap: 1em;
|
||||
text-transform: uppercase;
|
||||
font-weight: bold;
|
||||
color: var(--theme-text-lighter);
|
||||
}
|
||||
|
||||
.or::before,
|
||||
.or::after {
|
||||
--divider-width: 4px;
|
||||
content: '';
|
||||
background-image: linear-gradient(
|
||||
to bottom,
|
||||
transparent calc(50% - var(--divider-width) / 2),
|
||||
var(--theme-divider) calc(50% - var(--divider-width) / 2) calc(50% + var(--divider-width) / 2),
|
||||
transparent calc(50% + var(--divider-width) / 2) 100%
|
||||
);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -6,7 +6,7 @@ import Default from "@astrojs/starlight/components/Footer.astro";
|
|||
<footer>
|
||||
<Default {...Astro.props}><slot /></Default>
|
||||
<div class="myfooter">
|
||||
Copyright © 2003-2024 Christoph Haas. All content on workaround.org can be used under the terms of the
|
||||
This guide is maintained by Christoph Haas since 2003. All content on workaround.org can be used under the terms of the
|
||||
<a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank">Creative Commons BY-NC-SA license</a>. Penguin images
|
||||
based on AI art because I suck at drawing.
|
||||
</div>
|
||||
|
|
|
|||
167
src/components/MultipleChoice.astro
Normal file
167
src/components/MultipleChoice.astro
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
<multiple-choice data-correct-label={"Correct"} data-incorrect-label={"Wrong"}>
|
||||
<form onsubmit="event.preventDefault()">
|
||||
<ol class="opt-list not-content"><slot /></ol>
|
||||
<div class="footer">
|
||||
<button class="submit" type="submit" disabled>
|
||||
{"Submit"}
|
||||
</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;
|
||||
line-height: 1.4;
|
||||
max-width: 15em;
|
||||
border: 2px solid var(--sl-color-gray-4);
|
||||
}
|
||||
|
||||
.submit {
|
||||
cursor: pointer;
|
||||
color: var(--sl-color-accent);
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.submit:focus {
|
||||
background-color: hsl(224, 20%, 94%);
|
||||
}
|
||||
|
||||
.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;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.correct {
|
||||
color: var(--sl-color-green-low);
|
||||
background-color: var(--sl-color-green);
|
||||
}
|
||||
|
||||
.wrong {
|
||||
color: var(--sl-color-red-low);
|
||||
background-color: var(--sl-color-red);
|
||||
}
|
||||
</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>
|
||||
87
src/components/Option.astro
Normal file
87
src/components/Option.astro
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
---
|
||||
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'] {
|
||||
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: 1em;
|
||||
height: 1em;
|
||||
font-size: 1em;
|
||||
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>
|
||||
10
src/components/UIString.astro
Normal file
10
src/components/UIString.astro
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
import type { UIDictionaryKeys } from '../i18n/translation-checkers';
|
||||
import { useTranslations } from '../i18n/util';
|
||||
|
||||
export interface Props {
|
||||
key: UIDictionaryKeys;
|
||||
}
|
||||
---
|
||||
|
||||
{useTranslations(Astro)(Astro.props.key)}
|
||||
Loading…
Add table
Add a link
Reference in a new issue