Skip to content
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions browser-tests/test_dark_mode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
from playwright.sync_api import Browser, Page, expect


def get_theme_state(page: Page) -> tuple[str | None, str | None]:
html_theme = page.evaluate("() => document.documentElement.getAttribute('data-theme')")
stored_theme = page.evaluate("() => localStorage.getItem('verso-theme')")
return html_theme, stored_theme


def get_css_var(page: Page, variable_name: str) -> str:
return page.evaluate(
"""([name]) => getComputedStyle(document.documentElement).getPropertyValue(name).trim()""",
[variable_name],
)


class TestDarkMode:
def test_theme_toggle_asset_is_present_and_served(self, server: str, page: Page):
page.goto(f"{server}/Verso-Markup")

script = page.locator('script[src="theme-toggle.js"]')
expect(script).to_have_count(1)

response = page.request.get(f"{server}/theme-toggle.js")
assert response.ok
assert response.status == 200

def test_theme_toggle_cycles_and_updates_storage(self, server: str, page: Page):
page.emulate_media(color_scheme="light")
page.goto(f"{server}/Verso-Markup")

toggle = page.locator("#theme-toggle")
expect(toggle).to_have_count(1)

initial_theme, initial_stored = get_theme_state(page)
assert initial_theme is None
assert initial_stored is None

toggle.click()
first_theme, first_stored = get_theme_state(page)
assert first_theme == "dark"
assert first_stored == "dark"

toggle.click()
second_theme, second_stored = get_theme_state(page)
assert second_theme == "light"
assert second_stored == "light"

toggle.click()
third_theme, third_stored = get_theme_state(page)
assert third_theme is None
assert third_stored is None

def test_theme_preference_persists_across_page_loads(self, server: str, browser: Browser):
context = browser.new_context(color_scheme="light")
try:
first_page = context.new_page()
first_page.goto(f"{server}/Verso-Markup")
first_page.locator("#theme-toggle").click()

first_theme, first_stored = get_theme_state(first_page)
assert first_theme == "dark"
assert first_stored == "dark"

second_page = context.new_page()
second_page.goto(f"{server}/Verso-Markup")

second_theme, second_stored = get_theme_state(second_page)
assert second_theme == "dark"
assert second_stored == "dark"
finally:
context.close()

def test_dark_system_preference_can_be_overridden_to_light(self, server: str, page: Page):
page.emulate_media(color_scheme="dark")
page.goto(f"{server}/Verso-Markup")

initial_theme, initial_stored = get_theme_state(page)
assert initial_theme is None
assert initial_stored is None
assert get_css_var(page, "--verso-background-color") == "#1e1e1e"

page.locator("#theme-toggle").click()
first_theme, first_stored = get_theme_state(page)
assert first_theme == "light"
assert first_stored == "light"
assert get_css_var(page, "--verso-background-color") == "#ffffff"

def test_dark_mode_styles_require_opt_in_attribute(self, server: str, page: Page):
page.emulate_media(color_scheme="dark")
page.goto(f"{server}/Verso-Markup")

attr = page.evaluate("() => document.documentElement.hasAttribute('data-verso-dark-mode')")
assert attr is True
assert get_css_var(page, "--verso-background-color") == "#1e1e1e"

page.evaluate("() => document.documentElement.removeAttribute('data-verso-dark-mode')")
assert get_css_var(page, "--verso-background-color") == "#ffffff"

page.evaluate("() => document.documentElement.setAttribute('data-verso-dark-mode', 'true')")
assert get_css_var(page, "--verso-background-color") == "#1e1e1e"
55 changes: 28 additions & 27 deletions src/verso-html/code.css
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
body {
font-family: var(--verso-text-font-family);
line-height: 1.6;
color: #333;
color: var(--verso-text-color, #333);
background-color: var(--verso-background-color, #fff);
height: 100vh;
overflow: hidden;
}
Expand All @@ -29,8 +30,8 @@ body {
/* Sidebar */
.sidebar {
width: 300px;
background: #f8f9fa;
border-right: 1px solid #e9ecef;
background: var(--verso-surface-color, #f8f9fa);
border-right: 1px solid var(--verso-border-color, #e9ecef);
overflow-y: auto;
flex-shrink: 0;
}
Expand Down Expand Up @@ -73,11 +74,11 @@ body {
}

.module-tree summary:hover {
background-color: #e9ecef;
background-color: var(--verso-border-color, #e9ecef);
}

.module-tree summary a {
color: #0066cc;
color: var(--verso-link-color, #0066cc);
text-decoration: none;
}

Expand All @@ -86,12 +87,12 @@ body {
}

.module-tree summary.current {
background-color: #0066cc;
color: white;
background-color: var(--verso-link-color, #0066cc);
color: var(--verso-background-color, white);
}

.module-tree summary.current a {
color: white;
color: var(--verso-background-color, white);
}

.module-tree summary::before {
Expand All @@ -100,7 +101,7 @@ body {
left: -0.75rem;
transition: transform 0.2s ease;
font-size: 0.75rem;
color: #6c757d;
color: var(--verso-text-color-light, #6c757d);
}

.module-tree details[open] > summary::before {
Expand All @@ -114,7 +115,7 @@ body {
}

.module-tree .leaf a {
color: #0066cc;
color: var(--verso-link-color, #0066cc);
text-decoration: none;
}

Expand All @@ -123,12 +124,12 @@ body {
}

.module-tree .current {
background-color: #0066cc;
color: white;
background-color: var(--verso-link-color, #0066cc);
color: var(--verso-background-color, white);
}

.module-tree .current a {
color: white;
color: var(--verso-background-color, white);
}

/* Main content area */
Expand All @@ -141,8 +142,8 @@ body {

/* Title bar */
.title-bar {
background: #fff;
border-bottom: 1px solid #e9ecef;
background: var(--verso-background-color, #fff);
border-bottom: 1px solid var(--verso-border-color, #e9ecef);
padding: 1rem 1.5rem;
flex-shrink: 0;
position: relative;
Expand All @@ -165,26 +166,26 @@ body {
.breadcrumbs li:not(:last-child)::after {
content: "·";
margin: 0;
color: #6c757d;
color: var(--verso-text-color-light, #6c757d);
font-weight: bold;
}

.breadcrumbs a {
color: #0066cc;
color: var(--verso-link-color, #0066cc);
text-decoration: none;
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
transition: background-color 0.2s ease;
}

.breadcrumbs a:hover {
background-color: #e3f2fd;
background-color: var(--verso-selected-color, #e3f2fd);
text-decoration: underline;
}

.breadcrumbs .current {
font-weight: 600;
color: #495057;
color: var(--verso-text-color, #495057);
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
}
Expand All @@ -194,8 +195,8 @@ body {
flex: 1;
overflow-y: auto;
padding: 2rem;
background: #ffffff;
color: #24292e;
background: var(--verso-background-color, #ffffff);
color: var(--verso-text-color, #24292e);
font-family: "Monaco", "Menlo", "Ubuntu Mono", monospace;
font-size: 1rem;
}
Expand All @@ -206,7 +207,7 @@ body {
margin-left: calc(var(--indent, 0) * 1ch);
padding: 0.25rem 0.5rem;
max-width: 40em;
border: 1px solid #ddd;
border: 1px solid var(--verso-border-color-light, #ddd);
border-radius: 1rem;
width: max-content;
}
Expand Down Expand Up @@ -297,19 +298,19 @@ pre,
top: 1rem;
left: 1rem;
z-index: 1001;
background: #fff;
border: 1px solid #dee2e6;
background: var(--verso-background-color, #fff);
border: 1px solid var(--verso-border-color, #dee2e6);
border-radius: 0.375rem;
padding: 0.5rem;
cursor: pointer;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
box-shadow: 0 2px 4px var(--verso-shadow-color, rgba(0, 0, 0, 0.1));
}

.hamburger span {
display: block;
width: 20px;
height: 2px;
background: #333;
background: var(--verso-text-color, #333);
margin: 4px 0;
transition: 0.3s;
}
Expand All @@ -327,7 +328,7 @@ pre,
height: 100vh;
z-index: 1000;
transition: left 0.3s ease;
box-shadow: 2px 0 10px rgba(0, 0, 0, 0.1);
box-shadow: 2px 0 10px var(--verso-shadow-color, rgba(0, 0, 0, 0.1));
}

.menu-toggle:checked + .hamburger + .layout .sidebar {
Expand Down
4 changes: 4 additions & 0 deletions src/verso-manual/VersoManual.lean
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,8 @@ where
emitFindHtml toc dir state xrefJson config.toConfig
IO.FS.withFile (dir.join "verso-vars.css") .write fun h => do
h.putStrLn Html.«verso-vars.css»
IO.FS.withFile (dir.join "theme-toggle.js") .write fun h => do
h.putStrLn themeToggle.js
IO.FS.withFile (dir.join "book.css") .write fun h => do
h.putStrLn Html.Css.pageStyle
for (src, dest) in config.extraFiles do
Expand Down Expand Up @@ -741,6 +743,8 @@ where
else titleHtml
IO.FS.withFile (root / "verso-vars.css") .write fun h => do
h.putStrLn Html.«verso-vars.css»
IO.FS.withFile (root / "theme-toggle.js") .write fun h => do
h.putStrLn themeToggle.js
IO.FS.withFile (root / "book.css") .write fun h => do
h.putStrLn Html.Css.pageStyle
for (src, dest) in config.extraFiles do
Expand Down
10 changes: 5 additions & 5 deletions src/verso-manual/VersoManual/Docstring.lean
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ instance [BEq α] [Hashable α] [ToJson α] : ToJson (HashSet α) where
def docstringStyle := r#"
.namedocs {
position: relative;
border: solid 1px #98B2C0;
border: solid 1px var(--verso-border-color-light, #98B2C0);
border-radius: .5rem;
padding-top: var(--verso--box-padding);
margin-top: var(--verso--box-vertical-margin);
Expand All @@ -361,7 +361,7 @@ def docstringStyle := r#"
/* Add a padding. this is the same as the margin applied to the first and last child.
The effect is that the padding looks the same size on all sides. */
padding: 0 var(--verso--box-padding);
border-top: 1px solid #98B2C0;
border-top: 1px solid var(--verso-border-color-light, #98B2C0);
}

.namedocs .text > pre {
Expand All @@ -382,11 +382,11 @@ def docstringStyle := r#"
position: absolute;
top: -0.65rem;
left: 1rem;
background: #fff;
background: var(--verso-background-color, #fff);
padding: 0 .5rem .125rem;
border: 1px solid #98B2C0;
border: 1px solid var(--verso-border-color-light, #98B2C0);
border-radius: 1rem;
color: #555;
color: var(--verso-text-color-light, #555);
}

.namedocs h1 {
Expand Down
4 changes: 3 additions & 1 deletion src/verso-manual/VersoManual/Html.lean
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,7 @@ public def page
let relativeRoot := String.join <| "./" :: path.toList.map (fun _ => "../")
let defer := #[("defer", "defer")]
{{
<html>
<html data-verso-dark-mode="true">
<head>
<script>
{{addSlashJs}}
Expand All @@ -444,6 +444,7 @@ public def page
<link rel="stylesheet" href="-verso-search/search-box.css"/>
<link rel="stylesheet" href="-verso-search/search-highlight.css"/>
<link rel="stylesheet" href="-verso-search/domain-display.css"/>
<script src="theme-toggle.js"></script>
{{extraJsFiles.map fun f => ({{<script src=s!"{f.1}" {{if f.2 then defer else #[]}}></script>}})}}
{{extraStylesheets.map (fun url => {{<link rel="stylesheet" href={{url}}/> }})}}
{{extraCss.toArray.map ({{<style>{{Html.text false ·.css}}</style>}})}}
Expand All @@ -465,6 +466,7 @@ public def page
<div class="header-title-wrapper">
<a href={{if let some dest := logoLink then dest else "/"}} class="header-title"><h1>{{bookTitle}}</h1></a>
</div>
<button id="theme-toggle" type="button" aria-label="Toggle dark mode"></button>
</header>
<label for="toggle-toc" id="toggle-toc-click">
<span class="line line1"/>
Expand Down
Loading