Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
134 changes: 134 additions & 0 deletions browser-tests/test_dark_mode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
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_color_scheme(page: Page) -> str:
return page.evaluate("""() => getComputedStyle(document.documentElement).colorScheme""")


def get_body_background(page: Page) -> str:
return page.evaluate("""() => getComputedStyle(document.body).backgroundColor""")


def select_theme(page: Page, theme: str) -> None:
page.locator("#theme-toggle-button").click()
page.locator(f'#theme-toggle-menu [data-theme-option="{theme}"]').click()


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_menu_updates_storage(self, server: str, page: Page):
page.emulate_media(color_scheme="light")
page.goto(f"{server}/Verso-Markup")

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

initial_theme, initial_stored = get_theme_state(page)
assert initial_theme is None
assert initial_stored is None
assert get_color_scheme(page) == "light"

select_theme(page, "dark")
first_theme, first_stored = get_theme_state(page)
assert first_theme == "dark"
assert first_stored == "dark"
assert get_color_scheme(page) == "dark"

select_theme(page, "light")
second_theme, second_stored = get_theme_state(page)
assert second_theme == "light"
assert second_stored == "light"
assert get_color_scheme(page) == "light"

select_theme(page, "system")
third_theme, third_stored = get_theme_state(page)
assert third_theme is None
assert third_stored is None
assert get_color_scheme(page) == "light"

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")
select_theme(first_page, "dark")

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_body_background(page) == "rgb(30, 30, 30)"
assert get_color_scheme(page) == "dark"

select_theme(page, "light")
first_theme, first_stored = get_theme_state(page)
assert first_theme == "light"
assert first_stored == "light"
assert get_body_background(page) == "rgb(255, 255, 255)"
assert get_color_scheme(page) == "light"

select_theme(page, "system")
second_theme, second_stored = get_theme_state(page)
assert second_theme is None
assert second_stored is None
assert get_body_background(page) == "rgb(30, 30, 30)"
assert get_color_scheme(page) == "dark"

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_body_background(page) == "rgb(30, 30, 30)"
assert get_color_scheme(page) == "dark"

page.evaluate("() => document.documentElement.removeAttribute('data-verso-dark-mode')")
assert get_body_background(page) == "rgb(255, 255, 255)"
assert get_color_scheme(page) == "light"

page.evaluate("() => document.documentElement.setAttribute('data-verso-dark-mode', 'true')")
assert get_body_background(page) == "rgb(30, 30, 30)"
assert get_color_scheme(page) == "dark"

def test_dark_mode_still_applies_without_javascript(self, server: str, browser: Browser):
context = browser.new_context(color_scheme="dark", java_script_enabled=False)
try:
page = context.new_page()
page.goto(f"{server}/Verso-Markup")

assert get_body_background(page) == "rgb(30, 30, 30)"
assert get_color_scheme(page) == "dark"
expect(page.locator("#theme-toggle")).to_be_hidden()
finally:
context.close()
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-secondary-background-color, 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
Loading