add theme picker
This commit is contained in:
parent
a4337b3514
commit
49f457cda7
5 changed files with 122 additions and 21 deletions
|
|
@ -5,3 +5,9 @@
|
||||||
|
|
||||||
#let site-wrapper = html.elem.with("site-wrapper")
|
#let site-wrapper = html.elem.with("site-wrapper")
|
||||||
#let site-container = html.elem.with("site-container")
|
#let site-container = html.elem.with("site-container")
|
||||||
|
/// Element used to group radio buttons for theme selection.
|
||||||
|
/// label is the aria-label, for accessibility (ie screen reader)
|
||||||
|
#let theme-picker(label: "Theme Picker", body) = {
|
||||||
|
// Role 'radiogroup' is needed because of bug in chromium with screenreaders (https://lyra.horse/blog/2025/08/you-dont-need-js/#fn:10)
|
||||||
|
html.elem("theme-picker", attrs: (aria-label: label, role: "radiogroup"), body)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
#import "./custom_html.typ" as chtml
|
#import "./custom_html.typ" as chtml
|
||||||
|
#import "./theme_picker.typ": theme_picker
|
||||||
|
|
||||||
/// Make the body of the webpage
|
/// Make the body of the webpage
|
||||||
#let html_body(
|
#let html_body(
|
||||||
|
|
@ -10,9 +11,43 @@
|
||||||
logo_alt,
|
logo_alt,
|
||||||
body
|
body
|
||||||
) = {
|
) = {
|
||||||
|
```raw-css
|
||||||
|
|
||||||
|
site-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
nav {
|
||||||
|
display: flex;
|
||||||
|
theme-picker {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
site-container {
|
||||||
|
width: 1050px;
|
||||||
|
padding: 0 20px;
|
||||||
|
|
||||||
|
header {
|
||||||
|
// display: flex
|
||||||
|
// flex-wrap: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 15px 5px;
|
||||||
|
border-bottom: solid 1px var(--color-border);
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
html.body({
|
html.body({
|
||||||
chtml.site-wrapper({
|
chtml.site-wrapper({
|
||||||
chtml.site-container({
|
chtml.site-container({
|
||||||
|
html.nav({
|
||||||
|
theme_picker()
|
||||||
|
})
|
||||||
html.header(style: "display: flex; flex-wrap: wrap-reverse;", {
|
html.header(style: "display: flex; flex-wrap: wrap-reverse;", {
|
||||||
html.hgroup({
|
html.hgroup({
|
||||||
header
|
header
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
#import "./custom_html.typ" as chtml
|
#import "./custom_html.typ" as chtml
|
||||||
|
#import "./html_utils.typ": get-css
|
||||||
|
|
||||||
/// Generate the html <head> element for the page.
|
/// Generate the html <head> element for the page.
|
||||||
#let html_head(
|
#let html_head(
|
||||||
|
|
@ -76,28 +77,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
--color-border: light-dark(#414868, #414868);
|
--color-border: light-dark(#414868, #414868);
|
||||||
}
|
|
||||||
site-wrapper {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
width: 100%;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
site-container {
|
--color-button-shadow: light-dark(#888, #000);
|
||||||
width: 1050px;
|
--color-button-focus: light-dark(#000, #FFF);
|
||||||
padding: 0 20px;
|
|
||||||
|
|
||||||
header {
|
|
||||||
// display: flex
|
|
||||||
// flex-wrap: wrap;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 15px 5px;
|
|
||||||
border-bottom: solid 1px var(--color-border);
|
|
||||||
margin-top: 15px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
html.style(get-css())
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,16 @@
|
||||||
|
#let css-list = state("css-list", ())
|
||||||
|
|
||||||
|
/// Add string `css` to `css-list` if not already present
|
||||||
|
#let add-css(css) = context {
|
||||||
|
css-list.update(x => if css in x { x } else { x + (css,) })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Concatenate all css found in css-list at the end of the document
|
||||||
|
#let get-css() = context {
|
||||||
|
css-list.final().join("\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
#let html_show(body) = {
|
#let html_show(body) = {
|
||||||
show raw.where(lang: "raw-css"): it => html.style(it.text)
|
show raw.where(lang: "raw-css"): it => add-css(it.text)
|
||||||
body
|
body
|
||||||
}
|
}
|
||||||
|
|
|
||||||
63
lib/theme_picker.typ
Normal file
63
lib/theme_picker.typ
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
#import "./custom_html.typ" as chtml
|
||||||
|
|
||||||
|
/// Buttons to chose the theme to use (Light/Dark)
|
||||||
|
/// label is the aria-label of the html element (for screenreader).
|
||||||
|
/// label-auto, label-light and label-dark are the content of each button.
|
||||||
|
#let theme_picker(
|
||||||
|
label: "Theme Picker",
|
||||||
|
label-auto: [Auto],
|
||||||
|
label-light: [Light],
|
||||||
|
label-dark: [Dark],
|
||||||
|
) = {
|
||||||
|
```raw-css
|
||||||
|
/* Thanks to Lyra Rebane for no-js theme picker implementation: https://lyra.horse/blog/2025/08/you-dont-need-js/ */
|
||||||
|
theme-picker {
|
||||||
|
display: flex;
|
||||||
|
padding: 5px;
|
||||||
|
label {
|
||||||
|
transition: background 0.08s;
|
||||||
|
&:first-child { border-radius: 8px 0 0 8px; }
|
||||||
|
&:last-child { border-radius: 0 8px 8px 0; }
|
||||||
|
&:has(input:checked) {
|
||||||
|
box-shadow: inset 0px 0px 8px 0px var(--color-button-shadow);
|
||||||
|
}
|
||||||
|
&:has(input:focus-visible) {
|
||||||
|
outline: 2px solid var(--color-button-focus);
|
||||||
|
}
|
||||||
|
&:hover { background: #0004; }
|
||||||
|
&:active { background: #0006; }
|
||||||
|
box-shadow: inset 0px 0px 1.2px 0px #000;
|
||||||
|
padding: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
background: #0002;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
/* To allow screen reader to still access these. */
|
||||||
|
opacity: 0;
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
/* Typst like to insert unecessary <p> elements */
|
||||||
|
p {
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
chtml.theme-picker(
|
||||||
|
label: label,
|
||||||
|
{
|
||||||
|
html.label({
|
||||||
|
html.input(type: "radio", name: "theme", id: "theme-auto", checked: true)
|
||||||
|
label-auto
|
||||||
|
})
|
||||||
|
html.label({
|
||||||
|
html.input(type: "radio", name: "theme", id: "theme-light")
|
||||||
|
label-light
|
||||||
|
})
|
||||||
|
html.label({
|
||||||
|
html.input(type: "radio", name: "theme", id: "theme-dark")
|
||||||
|
label-dark
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue