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-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 "./theme_picker.typ": theme_picker
|
||||
|
||||
/// Make the body of the webpage
|
||||
#let html_body(
|
||||
|
|
@ -10,9 +11,43 @@
|
|||
logo_alt,
|
||||
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({
|
||||
chtml.site-wrapper({
|
||||
chtml.site-container({
|
||||
html.nav({
|
||||
theme_picker()
|
||||
})
|
||||
html.header(style: "display: flex; flex-wrap: wrap-reverse;", {
|
||||
html.hgroup({
|
||||
header
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#import "./custom_html.typ" as chtml
|
||||
#import "./html_utils.typ": get-css
|
||||
|
||||
/// Generate the html <head> element for the page.
|
||||
#let html_head(
|
||||
|
|
@ -76,28 +77,12 @@
|
|||
}
|
||||
|
||||
--color-border: light-dark(#414868, #414868);
|
||||
}
|
||||
site-wrapper {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
--color-button-shadow: light-dark(#888, #000);
|
||||
--color-button-focus: light-dark(#000, #FFF);
|
||||
}
|
||||
```
|
||||
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) = {
|
||||
show raw.where(lang: "raw-css"): it => html.style(it.text)
|
||||
show raw.where(lang: "raw-css"): it => add-css(it.text)
|
||||
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