Skip to content

Commit

Permalink
Merge pull request #1164 from zauberzeug/dark
Browse files Browse the repository at this point in the history
Dark mode for NiceGUI documentation
  • Loading branch information
rodja authored Jul 17, 2023
2 parents 48c964f + 73de652 commit c100482
Show file tree
Hide file tree
Showing 10 changed files with 107 additions and 69 deletions.
43 changes: 34 additions & 9 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ def logo_square() -> FileResponse:
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')
async def redirect_reference_to_documentation(request: Request,
call_next: Callable[[Request], Awaitable[Response]]) -> Response:
Expand Down Expand Up @@ -74,6 +79,13 @@ def add_header(menu: Optional[ui.left_drawer] = None) -> None:
'Examples': '/#examples',
'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() \
.classes('items-center duration-200 p-0 px-4 no-wrap') \
.style('box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1)'):
Expand All @@ -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'):
svg.face().classes('w-8 stroke-white stroke-2 max-[550px]:hidden')
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():
ui.link(title, target).classes(replace='text-lg text-white')

search = Search()
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')
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')
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')

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.menu().classes('bg-primary text-white text-lg'):
for title, target in menu_items.items():
Expand All @@ -108,7 +133,7 @@ async def index_page(client: Client) -> None:
add_header()

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'):
title('Meet the *NiceGUI*.')
subtitle('And let any browser be the frontend of your Python code.') \
Expand Down Expand Up @@ -244,8 +269,8 @@ async def index_page(client: Client) -> None:
.classes('text-white text-2xl md:text-3xl font-medium')
ui.html('Fun-Fact: This whole website is also coded with NiceGUI.') \
.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'):
link_target('examples', '-50px')
Expand Down Expand Up @@ -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('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')
with ui.column().classes('''
max-w-[1600px] m-auto
Expand Down
2 changes: 1 addition & 1 deletion nicegui/elements/dark_mode.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export default {
update() {
Quasar.Dark.set(this.value === null ? "auto" : this.value);
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");
else document.body.classList.remove("dark");
}
Expand Down
7 changes: 4 additions & 3 deletions nicegui/elements/dark_mode.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
from typing import Optional
from typing import Any, Callable, Optional

from .mixins.value_element import ValueElement


class DarkMode(ValueElement, component='dark_mode.js'):
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
You can use this element to enable, disable or toggle dark mode on the page.
Expand All @@ -15,8 +15,9 @@ def __init__(self, value: Optional[bool] = False) -> None:
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 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:
"""Enable dark mode."""
Expand Down
5 changes: 4 additions & 1 deletion nicegui/elements/markdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ def __init__(self, content: str = '', *, extras: List[str] = ['fenced-code-block
self.extras = extras
super().__init__(content=content)
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:
self._props['use_mermaid'] = True
self.libraries.append(Mermaid.exposed_libraries[0])
Expand Down
68 changes: 29 additions & 39 deletions website/demo.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
import inspect
import re
from typing import Callable, Optional, Union
from typing import Callable, Literal, Optional, Union

import isort

from nicegui import ui

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:
"""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:
Expand Down Expand Up @@ -57,54 +57,44 @@ async def copy_code():
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:
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-yellow-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)'):
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()
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:
_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')


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:
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:
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')
6 changes: 0 additions & 6 deletions website/documentation.py
Original file line number Diff line number Diff line change
Expand Up @@ -549,10 +549,6 @@ def auto_context_demo():

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', '''
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.
Expand All @@ -576,8 +572,6 @@ def native_mode_demo():
# ui.run(native=True, window_size=(400, 300), fullscreen=False)
# END OF DEMO
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.
# For more info see: https://github.com/r0x0r/pywebview/issues/1078
Expand Down
10 changes: 5 additions & 5 deletions website/example_card.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@ def create() -> None:
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'):
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!"), [])
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.checkbox('Check', on_change=lambda e: output.set_text('Checked' if e.value else 'Unchecked'))
ui.switch('Switch', on_change=lambda e: output.set_text('Switched on' if e.value else 'Switched off'))

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)) \
.style('width: 150px; margin-bottom: 2px')
with ui.row():
Expand All @@ -34,8 +34,8 @@ def create_narrow() -> None:
.classes('pb-16 no-shadow'), ui.row().classes('no-wrap'):
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!"))
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.input('Text', value='abc', on_change=lambda e: output.set_text(e.value))

Expand Down
12 changes: 10 additions & 2 deletions website/more_documentation/dark_mode_documentation.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from nicegui import ui

from ..demo import WINDOW_BG_COLORS


def main_demo() -> None:
# dark = ui.dark_mode()
Expand All @@ -9,5 +11,11 @@ def main_demo() -> None:
# END OF DEMO
l = ui.label('Switch mode:')
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 changes: 17 additions & 0 deletions website/static/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ body {
overflow-x: hidden;
background-color: #f8f8f8;
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 {
font-family: Roboto, -apple-system, "Helvetica Neue", Helvetica, Arial, sans-serif;
Expand Down Expand Up @@ -46,6 +50,12 @@ a:active:not(.browser-window *) {
background-color: #5898d4d0;
backdrop-filter: blur(5px);
}
.body--dark .q-header {
background-color: #3e6a94;
}
.body--dark .q-header.fade {
background-color: #3e6a94d0;
}

.scroll-indicator:after {
content: "";
Expand All @@ -61,6 +71,10 @@ a:active:not(.browser-window *) {
animation: sdb04 1.5s infinite;
transition-timing-function: ease;
}
.body--dark .scroll-indicator:after {
border-left: 3px solid #bbb;
border-bottom: 3px solid #bbb;
}
@-webkit-keyframes sdb04 {
0% {
-webkit-transform: rotate(-45deg) translate(0, 0);
Expand Down Expand Up @@ -109,6 +123,9 @@ dl.docinfo p {
background-color: #5898d4;
width: 100%;
}
.body--dark .dark-box {
background-color: #3e6a94;
}

@media only screen and (min-width: 1024px) {
html {
Expand Down
6 changes: 3 additions & 3 deletions website/style.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}') \
.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)'):
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:
Expand All @@ -49,5 +49,5 @@ def features(icon: str, title: str, items: List[str]) -> None:

def side_menu() -> 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')

0 comments on commit c100482

Please sign in to comment.