소스 검색

Merge pull request #1164 from zauberzeug/dark

Dark mode for NiceGUI documentation
Rodja Trappe 1 년 전
부모
커밋
c100482ad4

+ 34 - 9
main.py

@@ -47,6 +47,11 @@ def logo_square() -> FileResponse:
     return FileResponse(svg.PATH / 'logo_square.png', media_type='image/png')
     return FileResponse(svg.PATH / 'logo_square.png', media_type='image/png')
 
 
 
 
+@app.post('/dark_mode')
+async def dark_mode(request: Request) -> None:
+    app.storage.browser['dark_mode'] = (await request.json()).get('value')
+
+
 @app.middleware('http')
 @app.middleware('http')
 async def redirect_reference_to_documentation(request: Request,
 async def redirect_reference_to_documentation(request: Request,
                                               call_next: Callable[[Request], Awaitable[Response]]) -> Response:
                                               call_next: Callable[[Request], Awaitable[Response]]) -> Response:
@@ -74,6 +79,13 @@ def add_header(menu: Optional[ui.left_drawer] = None) -> None:
         'Examples': '/#examples',
         'Examples': '/#examples',
         'Why?': '/#why',
         'Why?': '/#why',
     }
     }
+    dark_mode = ui.dark_mode(value=app.storage.browser.get('dark_mode'), on_change=lambda e: ui.run_javascript(f'''
+        fetch('/dark_mode', {{
+            method: 'POST',
+            headers: {{'Content-Type': 'application/json'}},
+            body: JSON.stringify({{value: {e.value}}}),
+        }});
+    ''', respond=False))
     with ui.header() \
     with ui.header() \
             .classes('items-center duration-200 p-0 px-4 no-wrap') \
             .classes('items-center duration-200 p-0 px-4 no-wrap') \
             .style('box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1)'):
             .style('box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1)'):
@@ -82,19 +94,32 @@ def add_header(menu: Optional[ui.left_drawer] = None) -> None:
         with ui.link(target=index_page).classes('row gap-4 items-center no-wrap mr-auto'):
         with ui.link(target=index_page).classes('row gap-4 items-center no-wrap mr-auto'):
             svg.face().classes('w-8 stroke-white stroke-2 max-[550px]:hidden')
             svg.face().classes('w-8 stroke-white stroke-2 max-[550px]:hidden')
             svg.word().classes('w-24')
             svg.word().classes('w-24')
-        with ui.row().classes('max-lg:hidden'):
+
+        with ui.row().classes('max-[1050px]:hidden'):
             for title, target in menu_items.items():
             for title, target in menu_items.items():
                 ui.link(title, target).classes(replace='text-lg text-white')
                 ui.link(title, target).classes(replace='text-lg text-white')
+
         search = Search()
         search = Search()
         search.create_button()
         search.create_button()
-        with ui.link(target='https://discord.gg/TEpFeAaF4f').classes('max-[445px]:hidden').tooltip('Discord'):
+
+        with ui.element().classes('max-[360px]:hidden'):
+            ui.button(icon='dark_mode', on_click=lambda: dark_mode.set_value(None)) \
+                .props('flat fab-mini color=white').bind_visibility_from(dark_mode, 'value', value=True)
+            ui.button(icon='light_mode', on_click=lambda: dark_mode.set_value(True)) \
+                .props('flat fab-mini color=white').bind_visibility_from(dark_mode, 'value', value=False)
+            ui.button(icon='brightness_auto', on_click=lambda: dark_mode.set_value(False)) \
+                .props('flat fab-mini color=white').bind_visibility_from(dark_mode, 'value', lambda mode: mode is None)
+
+        with ui.link(target='https://discord.gg/TEpFeAaF4f').classes('max-[455px]:hidden').tooltip('Discord'):
             svg.discord().classes('fill-white scale-125 m-1')
             svg.discord().classes('fill-white scale-125 m-1')
-        with ui.link(target='https://www.reddit.com/r/nicegui/').classes('max-[395px]:hidden').tooltip('Reddit'):
+        with ui.link(target='https://www.reddit.com/r/nicegui/').classes('max-[405px]:hidden').tooltip('Reddit'):
             svg.reddit().classes('fill-white scale-125 m-1')
             svg.reddit().classes('fill-white scale-125 m-1')
-        with ui.link(target='https://github.com/zauberzeug/nicegui/').tooltip('GitHub'):
+        with ui.link(target='https://github.com/zauberzeug/nicegui/').classes('max-[305px]:hidden').tooltip('GitHub'):
             svg.github().classes('fill-white scale-125 m-1')
             svg.github().classes('fill-white scale-125 m-1')
+
         add_star().classes('max-[490px]:hidden')
         add_star().classes('max-[490px]:hidden')
-        with ui.row().classes('lg:hidden'):
+
+        with ui.row().classes('min-[1051px]:hidden'):
             with ui.button(icon='more_vert').props('flat color=white round'):
             with ui.button(icon='more_vert').props('flat color=white round'):
                 with ui.menu().classes('bg-primary text-white text-lg'):
                 with ui.menu().classes('bg-primary text-white text-lg'):
                     for title, target in menu_items.items():
                     for title, target in menu_items.items():
@@ -108,7 +133,7 @@ async def index_page(client: Client) -> None:
     add_header()
     add_header()
 
 
     with ui.row().classes('w-full h-screen items-center gap-8 pr-4 no-wrap into-section'):
     with ui.row().classes('w-full h-screen items-center gap-8 pr-4 no-wrap into-section'):
-        svg.face(half=True).classes('stroke-black w-[200px] md:w-[230px] lg:w-[300px]')
+        svg.face(half=True).classes('stroke-black dark:stroke-white w-[200px] md:w-[230px] lg:w-[300px]')
         with ui.column().classes('gap-4 md:gap-8 pt-32'):
         with ui.column().classes('gap-4 md:gap-8 pt-32'):
             title('Meet the *NiceGUI*.')
             title('Meet the *NiceGUI*.')
             subtitle('And let any browser be the frontend of your Python code.') \
             subtitle('And let any browser be the frontend of your Python code.') \
@@ -244,8 +269,8 @@ async def index_page(client: Client) -> None:
                     .classes('text-white text-2xl md:text-3xl font-medium')
                     .classes('text-white text-2xl md:text-3xl font-medium')
                 ui.html('Fun-Fact: This whole website is also coded with NiceGUI.') \
                 ui.html('Fun-Fact: This whole website is also coded with NiceGUI.') \
                     .classes('text-white text-lg md:text-xl')
                     .classes('text-white text-lg md:text-xl')
-            ui.link('Documentation', '/documentation') \
-                .classes('rounded-full mx-auto px-12 py-2 text-white bg-white font-medium text-lg md:text-xl')
+            ui.link('Documentation', '/documentation').style('color: black !important') \
+                .classes('rounded-full mx-auto px-12 py-2 bg-white font-medium text-lg md:text-xl')
 
 
     with ui.column().classes('w-full p-8 lg:p-16 max-w-[1600px] mx-auto'):
     with ui.column().classes('w-full p-8 lg:p-16 max-w-[1600px] mx-auto'):
         link_target('examples', '-50px')
         link_target('examples', '-50px')
@@ -286,7 +311,7 @@ async def index_page(client: Client) -> None:
             example_link('Lightbox', 'A thumbnail gallery where each image can be clicked to enlarge')
             example_link('Lightbox', 'A thumbnail gallery where each image can be clicked to enlarge')
             example_link('ROS2', 'Using NiceGUI as web interface for a ROS2 robot')
             example_link('ROS2', 'Using NiceGUI as web interface for a ROS2 robot')
 
 
-    with ui.row().classes('bg-primary w-full min-h-screen mt-16'):
+    with ui.row().classes('dark-box min-h-screen mt-16'):
         link_target('why')
         link_target('why')
         with ui.column().classes('''
         with ui.column().classes('''
                 max-w-[1600px] m-auto
                 max-w-[1600px] m-auto

+ 1 - 1
nicegui/elements/dark_mode.js

@@ -9,7 +9,7 @@ export default {
     update() {
     update() {
       Quasar.Dark.set(this.value === null ? "auto" : this.value);
       Quasar.Dark.set(this.value === null ? "auto" : this.value);
       if (window.tailwind) {
       if (window.tailwind) {
-        tailwind.config.darkMode = this.auto ? "media" : "class";
+        tailwind.config.darkMode = this.value === null ? "media" : "class";
         if (this.value) document.body.classList.add("dark");
         if (this.value) document.body.classList.add("dark");
         else document.body.classList.remove("dark");
         else document.body.classList.remove("dark");
       }
       }

+ 4 - 3
nicegui/elements/dark_mode.py

@@ -1,4 +1,4 @@
-from typing import Optional
+from typing import Any, Callable, Optional
 
 
 from .mixins.value_element import ValueElement
 from .mixins.value_element import ValueElement
 
 
@@ -6,7 +6,7 @@ from .mixins.value_element import ValueElement
 class DarkMode(ValueElement, component='dark_mode.js'):
 class DarkMode(ValueElement, component='dark_mode.js'):
     VALUE_PROP = 'value'
     VALUE_PROP = 'value'
 
 
-    def __init__(self, value: Optional[bool] = False) -> None:
+    def __init__(self, value: Optional[bool] = False, *, on_change: Optional[Callable[..., Any]] = None) -> None:
         """Dark mode
         """Dark mode
 
 
         You can use this element to enable, disable or toggle dark mode on the page.
         You can use this element to enable, disable or toggle dark mode on the page.
@@ -15,8 +15,9 @@ class DarkMode(ValueElement, component='dark_mode.js'):
         Note that this element overrides the `dark` parameter of the `ui.run` function and page decorators.
         Note that this element overrides the `dark` parameter of the `ui.run` function and page decorators.
 
 
         :param value: Whether dark mode is enabled. If None, dark mode is set to auto.
         :param value: Whether dark mode is enabled. If None, dark mode is set to auto.
+        :param on_change: Callback that is invoked when the value changes.
         """
         """
-        super().__init__(value=value, on_value_change=None)
+        super().__init__(value=value, on_value_change=on_change)
 
 
     def enable(self) -> None:
     def enable(self) -> None:
         """Enable dark mode."""
         """Enable dark mode."""

+ 4 - 1
nicegui/elements/markdown.py

@@ -23,7 +23,10 @@ class Markdown(ContentElement, component='markdown.js'):
         self.extras = extras
         self.extras = extras
         super().__init__(content=content)
         super().__init__(content=content)
         self._classes = ['nicegui-markdown']
         self._classes = ['nicegui-markdown']
-        self._props['codehilite_css'] = HtmlFormatter(nobackground=True).get_style_defs('.codehilite')
+        self._props['codehilite_css'] = (
+            HtmlFormatter(nobackground=True).get_style_defs('.codehilite') +
+            HtmlFormatter(nobackground=True, style='github-dark').get_style_defs('.body--dark .codehilite')
+        )
         if 'mermaid' in extras:
         if 'mermaid' in extras:
             self._props['use_mermaid'] = True
             self._props['use_mermaid'] = True
             self.libraries.append(Mermaid.exposed_libraries[0])
             self.libraries.append(Mermaid.exposed_libraries[0])

+ 29 - 39
website/demo.py

@@ -1,6 +1,6 @@
 import inspect
 import inspect
 import re
 import re
-from typing import Callable, Optional, Union
+from typing import Callable, Literal, Optional, Union
 
 
 import isort
 import isort
 
 
@@ -8,20 +8,20 @@ from nicegui import ui
 
 
 from .intersection_observer import IntersectionObserver as intersection_observer
 from .intersection_observer import IntersectionObserver as intersection_observer
 
 
-PYTHON_BGCOLOR = '#00000010'
-PYTHON_COLOR = '#eef5fb'
-BASH_BGCOLOR = '#00000010'
-BASH_COLOR = '#e8e8e8'
-BROWSER_BGCOLOR = '#00000010'
-BROWSER_COLOR = '#ffffff'
+WindowType = Literal['python', 'bash', 'browser']
 
 
+UNCOMMENT_PATTERN = re.compile(r'^(\s*)# ?')
 
 
-uncomment_pattern = re.compile(r'^(\s*)# ?')
+WINDOW_BG_COLORS = {
+    'python': ('#eef5fb', '#2b323b'),
+    'bash': ('#e8e8e8', '#2b323b'),
+    'browser': ('#ffffff', '#181c21'),
+}
 
 
 
 
 def uncomment(text: str) -> str:
 def uncomment(text: str) -> str:
     """non-executed lines should be shown in the code examples"""
     """non-executed lines should be shown in the code examples"""
-    return uncomment_pattern.sub(r'\1', text)
+    return UNCOMMENT_PATTERN.sub(r'\1', text)
 
 
 
 
 def demo(f: Callable) -> Callable:
 def demo(f: Callable) -> Callable:
@@ -57,10 +57,6 @@ def demo(f: Callable) -> Callable:
     return f
     return f
 
 
 
 
-def _window_header(bgcolor: str) -> ui.row():
-    return ui.row().classes(f'w-full h-8 p-2 bg-[{bgcolor}]')
-
-
 def _dots() -> None:
 def _dots() -> None:
     with ui.row().classes('gap-1 relative left-[1px] top-[1px]'):
     with ui.row().classes('gap-1 relative left-[1px] top-[1px]'):
         ui.icon('circle').classes('text-[13px] text-red-400')
         ui.icon('circle').classes('text-[13px] text-red-400')
@@ -68,43 +64,37 @@ def _dots() -> None:
         ui.icon('circle').classes('text-[13px] text-green-400')
         ui.icon('circle').classes('text-[13px] text-green-400')
 
 
 
 
-def _title(title: str) -> None:
-    ui.label(title).classes('text-sm text-gray-600 absolute left-1/2 top-[6px]').style('transform: translateX(-50%)')
-
-
-def _tab(content: Union[str, Callable], color: str, bgcolor: str) -> None:
-    with ui.row().classes('gap-0'):
-        with ui.label().classes(f'w-2 h-[24px] bg-[{color}]'):
-            ui.label().classes(f'w-full h-full bg-[{bgcolor}] rounded-br-[6px]')
-        with ui.row().classes(f'text-sm text-gray-600 px-6 py-1 h-[24px] rounded-t-[6px] bg-[{color}] items-center gap-2'):
-            if callable(content):
-                content()
-            else:
-                ui.label(content)
-        with ui.label().classes(f'w-2 h-[24px] bg-[{color}]'):
-            ui.label().classes(f'w-full h-full bg-[{bgcolor}] rounded-bl-[6px]')
-
-
-def window(color: str, bgcolor: str, *,
-           title: str = '', tab: Union[str, Callable] = '', classes: str = '') -> ui.column:
-    with ui.card().classes(f'no-wrap bg-[{color}] rounded-xl p-0 gap-0 {classes}') \
+def window(type: WindowType, *, title: str = '', tab: Union[str, Callable] = '', classes: str = '') -> ui.column:
+    bar_color = ('#00000010', '#ffffff10')
+    color = WINDOW_BG_COLORS[type]
+    with ui.card().classes(f'no-wrap bg-[{color[0]}] dark:bg-[{color[1]}] rounded-xl p-0 gap-0 {classes}') \
             .style('box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1)'):
             .style('box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1)'):
-        with _window_header(bgcolor):
+        with ui.row().classes(f'w-full h-8 p-2 bg-[{bar_color[0]}] dark:bg-[{bar_color[1]}]'):
             _dots()
             _dots()
             if title:
             if title:
-                _title(title)
+                ui.label(title) \
+                    .classes('text-sm text-gray-600 dark:text-gray-400 absolute left-1/2 top-[6px]') \
+                    .style('transform: translateX(-50%)')
             if tab:
             if tab:
-                _tab(tab, color, bgcolor)
+                with ui.row().classes('gap-0'):
+                    with ui.label().classes(f'w-2 h-[24px] bg-[{color[0]}] dark:bg-[{color[1]}]'):
+                        ui.label().classes(
+                            f'w-full h-full bg-[{bar_color[0]}] dark:bg-[{bar_color[1]}] rounded-br-[6px]')
+                    with ui.row().classes(f'text-sm text-gray-600 dark:text-gray-400 px-6 py-1 h-[24px] rounded-t-[6px] bg-[{color[0]}] dark:bg-[{color[1]}] items-center gap-2'):
+                        tab() if callable(tab) else ui.label(tab)
+                    with ui.label().classes(f'w-2 h-[24px] bg-[{color[0]}] dark:bg-[{color[1]}]'):
+                        ui.label().classes(
+                            f'w-full h-full bg-[{bar_color[0]}] dark:bg-[{bar_color[1]}] rounded-bl-[6px]')
         return ui.column().classes('w-full h-full overflow-auto')
         return ui.column().classes('w-full h-full overflow-auto')
 
 
 
 
 def python_window(title: Optional[str] = None, *, classes: str = '') -> ui.card:
 def python_window(title: Optional[str] = None, *, classes: str = '') -> ui.card:
-    return window(PYTHON_COLOR, PYTHON_BGCOLOR, title=title or 'main.py', classes=classes).classes('p-2 python-window')
+    return window('python', title=title or 'main.py', classes=classes).classes('p-2 python-window')
 
 
 
 
 def bash_window(*, classes: str = '') -> ui.card:
 def bash_window(*, classes: str = '') -> ui.card:
-    return window(BASH_COLOR, BASH_BGCOLOR, title='bash', classes=classes).classes('p-2 bash-window')
+    return window('bash', title='bash', classes=classes).classes('p-2 bash-window')
 
 
 
 
 def browser_window(title: Optional[Union[str, Callable]] = None, *, classes: str = '') -> ui.card:
 def browser_window(title: Optional[Union[str, Callable]] = None, *, classes: str = '') -> ui.card:
-    return window(BROWSER_COLOR, BROWSER_BGCOLOR, tab=title or 'NiceGUI', classes=classes).classes('p-4 browser-window')
+    return window('browser', tab=title or 'NiceGUI', classes=classes).classes('p-4 browser-window')

+ 0 - 6
website/documentation.py

@@ -549,10 +549,6 @@ def create_full() -> None:
 
 
     load_demo(ui.run)
     load_demo(ui.run)
 
 
-    # HACK: switch color to white for the next demo
-    demo_BROWSER_BGCOLOR = demo.BROWSER_BGCOLOR
-    demo.BROWSER_BGCOLOR = '#ffffff'
-
     @text_demo('Native Mode', '''
     @text_demo('Native Mode', '''
         You can enable native mode for NiceGUI by specifying `native=True` in the `ui.run` function.
         You can enable native mode for NiceGUI by specifying `native=True` in the `ui.run` function.
         To customize the initial window size and display mode, use the `window_size` and `fullscreen` parameters respectively.
         To customize the initial window size and display mode, use the `window_size` and `fullscreen` parameters respectively.
@@ -576,8 +572,6 @@ def create_full() -> None:
         # ui.run(native=True, window_size=(400, 300), fullscreen=False)
         # ui.run(native=True, window_size=(400, 300), fullscreen=False)
         # END OF DEMO
         # END OF DEMO
         ui.button('enlarge', on_click=lambda: ui.notify('window will be set to 1000x700 in native mode'))
         ui.button('enlarge', on_click=lambda: ui.notify('window will be set to 1000x700 in native mode'))
-    # HACK: restore color
-    demo.BROWSER_BGCOLOR = demo_BROWSER_BGCOLOR
 
 
     # Show a helpful workaround until issue is fixed upstream.
     # Show a helpful workaround until issue is fixed upstream.
     # For more info see: https://github.com/r0x0r/pywebview/issues/1078
     # For more info see: https://github.com/r0x0r/pywebview/issues/1078

+ 5 - 5
website/example_card.py

@@ -8,7 +8,7 @@ def create() -> None:
         with ui.card().style(r'clip-path: polygon(0 0, 100% 0, 100% 90%, 0 100%)') \
         with ui.card().style(r'clip-path: polygon(0 0, 100% 0, 100% 90%, 0 100%)') \
                 .classes('pb-16 no-shadow'), ui.row().classes('no-wrap'):
                 .classes('pb-16 no-shadow'), ui.row().classes('no-wrap'):
             with ui.column().classes('items-center'):
             with ui.column().classes('items-center'):
-                svg.face().classes('w-16 mx-6 stroke-black stroke-2') \
+                svg.face().classes('w-16 mx-6 stroke-black dark:stroke-gray-100 stroke-2') \
                     .on('click', lambda _: output.set_text("That's my face!"), [])
                     .on('click', lambda _: output.set_text("That's my face!"), [])
                 ui.button('Click me!', on_click=lambda: output.set_text('Clicked')).classes('w-full')
                 ui.button('Click me!', on_click=lambda: output.set_text('Clicked')).classes('w-full')
                 ui.input('Text', value='abc', on_change=lambda e: output.set_text(e.value))
                 ui.input('Text', value='abc', on_change=lambda e: output.set_text(e.value))
@@ -16,8 +16,8 @@ def create() -> None:
                 ui.switch('Switch', on_change=lambda e: output.set_text('Switched on' if e.value else 'Switched off'))
                 ui.switch('Switch', on_change=lambda e: output.set_text('Switched on' if e.value else 'Switched off'))
 
 
             with ui.column().classes('items-center'):
             with ui.column().classes('items-center'):
-                output = ui.label('Try it out!') \
-                    .classes('w-44 my-6 h-8 text-xl text-grey-9 overflow-hidden text-ellipsis text-center')
+                output = ui.label('Try it out!').classes(
+                    'w-44 my-6 h-8 text-xl text-gray-800 dark:text-gray-200 overflow-hidden text-ellipsis text-center')
                 ui.slider(min=0, max=100, value=50, step=0.1, on_change=lambda e: output.set_text(e.value)) \
                 ui.slider(min=0, max=100, value=50, step=0.1, on_change=lambda e: output.set_text(e.value)) \
                     .style('width: 150px; margin-bottom: 2px')
                     .style('width: 150px; margin-bottom: 2px')
                 with ui.row():
                 with ui.row():
@@ -34,8 +34,8 @@ def create_narrow() -> None:
                 .classes('pb-16 no-shadow'), ui.row().classes('no-wrap'):
                 .classes('pb-16 no-shadow'), ui.row().classes('no-wrap'):
             with ui.column().classes('items-center'):
             with ui.column().classes('items-center'):
                 svg.face().classes('w-16 mx-6 stroke-black stroke-2').on('click', lambda _: output.set_text("That's my face!"))
                 svg.face().classes('w-16 mx-6 stroke-black stroke-2').on('click', lambda _: output.set_text("That's my face!"))
-                output = ui.label('Try it out!') \
-                    .classes('w-44 my-6 h-8 text-xl text-grey-9 overflow-hidden text-ellipsis text-center')
+                output = ui.label('Try it out!').classes(
+                    'w-44 my-6 h-8 text-xl text-gray-800 dark:text-gray-200 overflow-hidden text-ellipsis text-center')
                 ui.button('Click me!', on_click=lambda: output.set_text('Clicked')).classes('w-full')
                 ui.button('Click me!', on_click=lambda: output.set_text('Clicked')).classes('w-full')
                 ui.input('Text', value='abc', on_change=lambda e: output.set_text(e.value))
                 ui.input('Text', value='abc', on_change=lambda e: output.set_text(e.value))
 
 

+ 10 - 2
website/more_documentation/dark_mode_documentation.py

@@ -1,5 +1,7 @@
 from nicegui import ui
 from nicegui import ui
 
 
+from ..demo import WINDOW_BG_COLORS
+
 
 
 def main_demo() -> None:
 def main_demo() -> None:
     # dark = ui.dark_mode()
     # dark = ui.dark_mode()
@@ -9,5 +11,11 @@ def main_demo() -> None:
     # END OF DEMO
     # END OF DEMO
     l = ui.label('Switch mode:')
     l = ui.label('Switch mode:')
     c = l.parent_slot.parent
     c = l.parent_slot.parent
-    ui.button('Dark', on_click=lambda: (l.style('color: white'), c.style('background-color: var(--q-dark-page)')))
-    ui.button('Light', on_click=lambda: (l.style('color: default'), c.style('background-color: default')))
+    ui.button('Dark', on_click=lambda: (
+        l.style('color: white'),
+        c.style(f'background-color: {WINDOW_BG_COLORS["browser"][1]}'),
+    ))
+    ui.button('Light', on_click=lambda: (
+        l.style('color: black'),
+        c.style(f'background-color: {WINDOW_BG_COLORS["browser"][0]}'),
+    ))

+ 17 - 0
website/static/style.css

@@ -4,6 +4,10 @@ body {
   overflow-x: hidden;
   overflow-x: hidden;
   background-color: #f8f8f8;
   background-color: #f8f8f8;
   font-family: "Fira Sans", Roboto, -apple-system, "Helvetica Neue", Helvetica, Arial, sans-serif;
   font-family: "Fira Sans", Roboto, -apple-system, "Helvetica Neue", Helvetica, Arial, sans-serif;
+  --q-dark-page: #222;
+}
+html:has(.body--dark) {
+  background-color: #222;
 }
 }
 .browser-window {
 .browser-window {
   font-family: Roboto, -apple-system, "Helvetica Neue", Helvetica, Arial, sans-serif;
   font-family: Roboto, -apple-system, "Helvetica Neue", Helvetica, Arial, sans-serif;
@@ -46,6 +50,12 @@ a:active:not(.browser-window *) {
   background-color: #5898d4d0;
   background-color: #5898d4d0;
   backdrop-filter: blur(5px);
   backdrop-filter: blur(5px);
 }
 }
+.body--dark .q-header {
+  background-color: #3e6a94;
+}
+.body--dark .q-header.fade {
+  background-color: #3e6a94d0;
+}
 
 
 .scroll-indicator:after {
 .scroll-indicator:after {
   content: "";
   content: "";
@@ -61,6 +71,10 @@ a:active:not(.browser-window *) {
   animation: sdb04 1.5s infinite;
   animation: sdb04 1.5s infinite;
   transition-timing-function: ease;
   transition-timing-function: ease;
 }
 }
+.body--dark .scroll-indicator:after {
+  border-left: 3px solid #bbb;
+  border-bottom: 3px solid #bbb;
+}
 @-webkit-keyframes sdb04 {
 @-webkit-keyframes sdb04 {
   0% {
   0% {
     -webkit-transform: rotate(-45deg) translate(0, 0);
     -webkit-transform: rotate(-45deg) translate(0, 0);
@@ -109,6 +123,9 @@ dl.docinfo p {
   background-color: #5898d4;
   background-color: #5898d4;
   width: 100%;
   width: 100%;
 }
 }
+.body--dark .dark-box {
+  background-color: #3e6a94;
+}
 
 
 @media only screen and (min-width: 1024px) {
 @media only screen and (min-width: 1024px) {
   html {
   html {

+ 3 - 3
website/style.py

@@ -35,8 +35,8 @@ def example_link(title: str, description: str) -> None:
     with ui.link(target=f'https://github.com/zauberzeug/nicegui/tree/main/examples/{name}/{filename}') \
     with ui.link(target=f'https://github.com/zauberzeug/nicegui/tree/main/examples/{name}/{filename}') \
             .classes('bg-[#5898d420] p-4 self-stretch rounded flex flex-col gap-2') \
             .classes('bg-[#5898d420] p-4 self-stretch rounded flex flex-col gap-2') \
             .style('box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1)'):
             .style('box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1)'):
-        ui.label(title).classes(replace='text-black font-bold')
-        ui.markdown(description).classes(replace='text-black bold-links arrow-links')
+        ui.label(title).classes(replace='font-bold')
+        ui.markdown(description).classes(replace='bold-links arrow-links')
 
 
 
 
 def features(icon: str, title: str, items: List[str]) -> None:
 def features(icon: str, title: str, items: List[str]) -> None:
@@ -49,5 +49,5 @@ def features(icon: str, title: str, items: List[str]) -> None:
 
 
 def side_menu() -> ui.left_drawer:
 def side_menu() -> ui.left_drawer:
     return ui.left_drawer() \
     return ui.left_drawer() \
-        .classes('column no-wrap gap-1 bg-[#eee] mt-[-20px] px-8 py-20') \
+        .classes('column no-wrap gap-1 bg-[#eee] dark:bg-[#1b1b1b] mt-[-20px] px-8 py-20') \
         .style('height: calc(100% + 20px) !important')
         .style('height: calc(100% + 20px) !important')