Bläddra i källkod

re-organize examples on main page

Falko Schindler 2 år sedan
förälder
incheckning
4c203f1611

+ 300 - 275
main.py

@@ -23,7 +23,7 @@ def example(content: Union[Callable, type, str]):
 
     def add_html_anchor(element: ui.html):
         html = element.content
-        match = re.search(r'<h3.*?>(.*?)</h3>', html)
+        match = re.search(r'<h4.*?>(.*?)</h4>', html)
         if not match:
             return
 
@@ -33,8 +33,8 @@ def example(content: Union[Callable, type, str]):
 
         icon = '<span class="material-icons">link</span>'
         anchor = f'<a href="#{headline_id}" class="text-gray-300 hover:text-black">{icon}</a>'
-        html = html.replace('<h3', f'<h3 id="{headline_id}"', 1)
-        html = html.replace('</h3>', f' {anchor}</h3>', 1)
+        html = html.replace('<h4', f'<h4 id="{headline_id}"', 1)
+        html = html.replace('</h4>', f' {anchor}</h4>', 1)
         element.view.inner_html = html
 
     with ui.row().classes('flex w-full'):
@@ -43,8 +43,8 @@ def example(content: Union[Callable, type, str]):
         else:
             doc = content.__doc__ or content.__init__.__doc__
             html = docutils.core.publish_parts(doc, writer_name='html')['html_body']
-            html = html.replace('<p>', '<h3>', 1)
-            html = html.replace('</p>', '</h3>', 1)
+            html = html.replace('<p>', '<h4>', 1)
+            html = html.replace('</p>', '</h4>', 1)
             html = ui.markdown.apply_tailwind(html)
             add_html_anchor(ui.html(html).classes('mr-8 w-4/12'))
 
@@ -108,62 +108,18 @@ with ui.row().classes('flex w-full'):
 
 ui.markdown('## API Documentation and Examples')
 
-with example(ui.label):
-    ui.label('some label')
-
-with example(ui.image):
-    ui.image('http://placeimg.com/640/360/tech')
-    base64 = 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAASABIAAD/4QCMRXhpZgAATU0AKgAAAAgABQESAAMAAAABAAEAAAEaAAUAAAABAAAASgEbAAUAAAABAAAAUgEoAAMAAAABAAIAAIdpAAQAAAABAAAAWgAAAAAAAABIAAAAAQAAAEgAAAABAAOgAQADAAAAAQABAACgAgAEAAAAAQAAACKgAwAEAAAAAQAAACMAAAAA/8IAEQgAIwAiAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAMCBAEFAAYHCAkKC//EAMMQAAEDAwIEAwQGBAcGBAgGcwECAAMRBBIhBTETIhAGQVEyFGFxIweBIJFCFaFSM7EkYjAWwXLRQ5I0ggjhU0AlYxc18JNzolBEsoPxJlQ2ZJR0wmDShKMYcOInRTdls1V1pJXDhfLTRnaA40dWZrQJChkaKCkqODk6SElKV1hZWmdoaWp3eHl6hoeIiYqQlpeYmZqgpaanqKmqsLW2t7i5usDExcbHyMnK0NTV1tfY2drg5OXm5+jp6vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAQIAAwQFBgcICQoL/8QAwxEAAgIBAwMDAgMFAgUCBASHAQACEQMQEiEEIDFBEwUwIjJRFEAGMyNhQhVxUjSBUCSRoUOxFgdiNVPw0SVgwUThcvEXgmM2cCZFVJInotIICQoYGRooKSo3ODk6RkdISUpVVldYWVpkZWZnaGlqc3R1dnd4eXqAg4SFhoeIiYqQk5SVlpeYmZqgo6SlpqeoqaqwsrO0tba3uLm6wMLDxMXGx8jJytDT1NXW19jZ2uDi4+Tl5ufo6ery8/T19vf4+fr/2wBDAAwMDAwMDBUMDBUeFRUVHikeHh4eKTQpKSkpKTQ+NDQ0NDQ0Pj4+Pj4+Pj5LS0tLS0tXV1dXV2JiYmJiYmJiYmL/2wBDAQ8QEBkXGSsXFytnRjlGZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2f/2gAMAwEAAhEDEQAAAeqBgCIareozvbaK3avBqa52teT6He3z0TqCUZa22r//2gAIAQEAAQUCaVVKTjGnLFqSqqlGuciX+87YgM8ScWhAx5KWUJUdJClKadMye6O//9oACAEDEQE/AUxI86A0ynfb/9oACAECEQE/ASaYZBLxpKNinFh2dv8A/9oACAEBAAY/AmUniHVXxfVx7ZIP9x0GlOJdfa+BeVentkSWR66jsI1HUfF+f4l1UykiqR/CypAorg6n/hvuH5nv/8QAMxABAAMAAgICAgIDAQEAAAILAREAITFBUWFxgZGhscHw0RDh8SAwQFBgcICQoLDA0OD/2gAIAQEAAT8hrchP08Nlp8V+7MHK/wCcEXw8q94vkT4K5DD0fpsJBFkwYvy/8cJBuuX7l82UhL9HmlzVKCOfi+3/ADe6Z2jgePxcMYN/xxYQtAu8UCj/ALXDvn/sBxRB/g3/AL//2gAMAwEAAhEDEQAAEE5gPHEUEAP/xAAzEQEBAQADAAECBQUBAQABAQkBABEhMRBBUWEgcfCRgaGx0cHh8TBAUGBwgJCgsMDQ4P/aAAgBAxEBPxAN4PZaNJuOW/g//9oACAECEQE/EAGt2fwmfzBp3X8P/9oACAEBAAE/ELGubg74j5M+RuAgxMrE4g5c4qAjQh1Oh9GL3/xggJDuHs5H2fY1rQIGDISTZ3KuGYzkk8dSkh4Ah8TJ8c0SsIco+yPRD76/486QSwOdnIpjvmvjAQ8pEx4ixlVcDldAdtawTzP5CSqs1wAPeJDMz0nwvHVlRSYTI1ic6b58RUC4kuSTXmFOJuxknJgsgDQMkjQgj/gCBHee6QjzflUA4/5//9k='
-    ui.image(base64).style('width:30px')
-
-svg = '''### SVG
-You can add Scalable Vector Graphics using the `ui.html` element.
-'''
-with example(svg):
-    content = '''
-        <svg viewBox="0 0 200 200" width="100" height="100" xmlns="http://www.w3.org/2000/svg">
-        <circle cx="100" cy="100" r="78" fill="#ffde34" stroke="black" stroke-width="3" />
-        <circle cx="80" cy="85" r="8" />
-        <circle cx="120" cy="85" r="8" />
-        <path d="m60,120 C75,150 125,150 140,120" style="fill:none; stroke:black; stroke-width:8; stroke-linecap:round" />
-        </svg>'''
-    ui.html(content)
-
-overlay = '''### Captions and Overlays
-
-By nesting elements inside a `ui.image` you can create augmentations.
-
-Use [Quasar classes](https://quasar.dev/vue-components/img) for positioning and styling captions.
-To overlay an svg, make the `viewBox` exactly the size of the image and provide `100%` width/height to match the actual rendered size.
-'''
-with example(overlay):
-    with ui.image('http://placeimg.com/640/360/nature'):
-        ui.label('nice').classes('absolute-bottom text-subtitle2 text-center')
-
-    with ui.image('https://cdn.pixabay.com/photo/2020/07/13/12/56/mute-swan-5400675__340.jpg'):
-        content = '''
-            <svg viewBox="0 0 510 340" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
-            <circle cx="200" cy="200" r="100" fill="none" stroke="red" stroke-width="10" />
-            </svg>'''
-        ui.html(content).style('background:transparent')
 
-with example(ui.interactive_image):
-    from nicegui.events import MouseEventArguments
+def h3(text: str) -> None:
+    ui.label(text).style('width: 100%; border-bottom: 1px solid silver; font-size: 200%; font-weight: 200')
 
-    def mouse_handler(e: MouseEventArguments):
-        color = 'green' if e.type == 'mousedown' else 'orange'
-        ii.svg_content += f'<circle cx="{e.image_x}" cy="{e.image_y}" r="10" fill="{color}"/>'
-        ui.notify(f'{e.type} at ({e.image_x:.1f}, {e.image_y:.1f})')
 
-    ii = ui.interactive_image('http://placeimg.com/640/360/arch',
-                              on_mouse=mouse_handler,
-                              events=['mousedown', 'mouseup'], cross=True)
+h3('Basic Elements')
 
-with example(ui.markdown):
-    ui.markdown('### Headline\nWith hyperlink to [GitHub](https://github.com/zauberzeug/nicegui).')
+with example(ui.label):
+    ui.label('some label')
 
-with example(ui.html):
-    ui.html('<p>demo paragraph in <strong>html</strong></p>')
+with example(ui.link):
+    ui.link('NiceGUI on GitHub', 'https://github.com/zauberzeug/nicegui')
 
 with example(ui.button):
     def button_increment():
@@ -175,18 +131,13 @@ with example(ui.button):
     ui.button('Button', on_click=button_increment)
     button_result = ui.label('pressed: 0')
 
-async_button = '''### Button with asynchronous action
-The button element does also support asynchronous action.
-
-Note: You can also pass a `functools.partial` into the `on_click` property to wrap async functions with parameters.
-'''
-with example(async_button):
-    async def async_task():
-        ui.notify('Asynchronous task started')
-        await asyncio.sleep(5)
-        ui.notify('Asynchronous task finished')
+with example(ui.toggle):
+    toggle = ui.toggle([1, 2, 3], value=1)
+    ui.toggle({1: 'A', 2: 'B', 3: 'C'}, value=1).bind_value(toggle, 'value')
 
-    ui.button('start async task', on_click=async_task)
+with example(ui.radio):
+    radio = ui.radio([1, 2, 3], value=1).props('inline')
+    ui.radio({1: 'A', 2: 'B', 3: 'C'}, value=1).props('inline').bind_value(radio, 'value')
 
 with example(ui.checkbox):
     ui.checkbox('check me', on_change=lambda e: checkbox_state.set_text(e.value))
@@ -204,6 +155,19 @@ with example(ui.slider):
     slider = ui.slider(min=0, max=100, value=50).props('label')
     ui.label().bind_text_from(slider, 'value')
 
+with example(ui.joystick):
+    ui.joystick(
+        color='blue',
+        size=50,
+        on_move=lambda msg: coordinates.set_text(f'{msg.data.vector.x:.3f}, {msg.data.vector.y:.3f}'),
+        on_end=lambda _: coordinates.set_text('0, 0'))
+    coordinates = ui.label('0, 0')
+
+with example(ui.select):
+    with ui.row():
+        select = ui.select([1, 2, 3], value=1).props('inline')
+        ui.select({1: 'One', 2: 'Two', 3: 'Three'}, value=1).props('inline').bind_value(select, 'value')
+
 with example(ui.input):
     ui.input(
         label='Text',
@@ -227,23 +191,107 @@ with example(ui.color_picker):
     picker = ui.color_picker(on_pick=lambda e: button.style(f'background-color:{e.color}!important'))
     button = ui.button(on_click=picker.open).props('icon=colorize')
 
-with example(ui.radio):
-    radio = ui.radio([1, 2, 3], value=1).props('inline')
-    ui.radio({1: 'A', 2: 'B', 3: 'C'}, value=1).props('inline').bind_value(radio, 'value')
-
-with example(ui.toggle):
-    toggle = ui.toggle([1, 2, 3], value=1)
-    ui.toggle({1: 'A', 2: 'B', 3: 'C'}, value=1).bind_value(toggle, 'value')
-
-with example(ui.select):
-    with ui.row():
-        select = ui.select([1, 2, 3], value=1).props('inline')
-        ui.select({1: 'One', 2: 'Two', 3: 'Three'}, value=1).props('inline').bind_value(select, 'value')
-
 with example(ui.upload):
     ui.upload(on_upload=lambda e: content.set_text(e.files))
     content = ui.label()
 
+h3('Markdown and HTML')
+
+with example(ui.markdown):
+    ui.markdown('### Headline\nWith hyperlink to [GitHub](https://github.com/zauberzeug/nicegui).')
+
+with example(ui.html):
+    ui.html('<p>demo paragraph in <strong>html</strong></p>')
+
+svg = '''#### SVG
+
+You can add Scalable Vector Graphics using the `ui.html` element.
+'''
+with example(svg):
+    content = '''
+        <svg viewBox="0 0 200 200" width="100" height="100" xmlns="http://www.w3.org/2000/svg">
+        <circle cx="100" cy="100" r="78" fill="#ffde34" stroke="black" stroke-width="3" />
+        <circle cx="80" cy="85" r="8" />
+        <circle cx="120" cy="85" r="8" />
+        <path d="m60,120 C75,150 125,150 140,120" style="fill:none; stroke:black; stroke-width:8; stroke-linecap:round" />
+        </svg>'''
+    ui.html(content)
+
+h3('Images')
+
+with example(ui.image):
+    ui.image('http://placeimg.com/640/360/tech')
+    base64 = 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAASABIAAD/4QCMRXhpZgAATU0AKgAAAAgABQESAAMAAAABAAEAAAEaAAUAAAABAAAASgEbAAUAAAABAAAAUgEoAAMAAAABAAIAAIdpAAQAAAABAAAAWgAAAAAAAABIAAAAAQAAAEgAAAABAAOgAQADAAAAAQABAACgAgAEAAAAAQAAACKgAwAEAAAAAQAAACMAAAAA/8IAEQgAIwAiAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAMCBAEFAAYHCAkKC//EAMMQAAEDAwIEAwQGBAcGBAgGcwECAAMRBBIhBTETIhAGQVEyFGFxIweBIJFCFaFSM7EkYjAWwXLRQ5I0ggjhU0AlYxc18JNzolBEsoPxJlQ2ZJR0wmDShKMYcOInRTdls1V1pJXDhfLTRnaA40dWZrQJChkaKCkqODk6SElKV1hZWmdoaWp3eHl6hoeIiYqQlpeYmZqgpaanqKmqsLW2t7i5usDExcbHyMnK0NTV1tfY2drg5OXm5+jp6vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAQIAAwQFBgcICQoL/8QAwxEAAgIBAwMDAgMFAgUCBASHAQACEQMQEiEEIDFBEwUwIjJRFEAGMyNhQhVxUjSBUCSRoUOxFgdiNVPw0SVgwUThcvEXgmM2cCZFVJInotIICQoYGRooKSo3ODk6RkdISUpVVldYWVpkZWZnaGlqc3R1dnd4eXqAg4SFhoeIiYqQk5SVlpeYmZqgo6SlpqeoqaqwsrO0tba3uLm6wMLDxMXGx8jJytDT1NXW19jZ2uDi4+Tl5ufo6ery8/T19vf4+fr/2wBDAAwMDAwMDBUMDBUeFRUVHikeHh4eKTQpKSkpKTQ+NDQ0NDQ0Pj4+Pj4+Pj5LS0tLS0tXV1dXV2JiYmJiYmJiYmL/2wBDAQ8QEBkXGSsXFytnRjlGZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2f/2gAMAwEAAhEDEQAAAeqBgCIareozvbaK3avBqa52teT6He3z0TqCUZa22r//2gAIAQEAAQUCaVVKTjGnLFqSqqlGuciX+87YgM8ScWhAx5KWUJUdJClKadMye6O//9oACAEDEQE/AUxI86A0ynfb/9oACAECEQE/ASaYZBLxpKNinFh2dv8A/9oACAEBAAY/AmUniHVXxfVx7ZIP9x0GlOJdfa+BeVentkSWR66jsI1HUfF+f4l1UykiqR/CypAorg6n/hvuH5nv/8QAMxABAAMAAgICAgIDAQEAAAILAREAITFBUWFxgZGhscHw0RDh8SAwQFBgcICQoLDA0OD/2gAIAQEAAT8hrchP08Nlp8V+7MHK/wCcEXw8q94vkT4K5DD0fpsJBFkwYvy/8cJBuuX7l82UhL9HmlzVKCOfi+3/ADe6Z2jgePxcMYN/xxYQtAu8UCj/ALXDvn/sBxRB/g3/AL//2gAMAwEAAhEDEQAAEE5gPHEUEAP/xAAzEQEBAQADAAECBQUBAQABAQkBABEhMRBBUWEgcfCRgaGx0cHh8TBAUGBwgJCgsMDQ4P/aAAgBAxEBPxAN4PZaNJuOW/g//9oACAECEQE/EAGt2fwmfzBp3X8P/9oACAEBAAE/ELGubg74j5M+RuAgxMrE4g5c4qAjQh1Oh9GL3/xggJDuHs5H2fY1rQIGDISTZ3KuGYzkk8dSkh4Ah8TJ8c0SsIco+yPRD76/486QSwOdnIpjvmvjAQ8pEx4ixlVcDldAdtawTzP5CSqs1wAPeJDMz0nwvHVlRSYTI1ic6b58RUC4kuSTXmFOJuxknJgsgDQMkjQgj/gCBHee6QjzflUA4/5//9k='
+    ui.image(base64).style('width:30px')
+
+captions_and_overlays = '''#### Captions and Overlays
+
+By nesting elements inside a `ui.image` you can create augmentations.
+
+Use [Quasar classes](https://quasar.dev/vue-components/img) for positioning and styling captions.
+To overlay an svg, make the `viewBox` exactly the size of the image and provide `100%` width/height to match the actual rendered size.
+'''
+with example(captions_and_overlays):
+    with ui.image('http://placeimg.com/640/360/nature'):
+        ui.label('nice').classes('absolute-bottom text-subtitle2 text-center')
+
+    with ui.image('https://cdn.pixabay.com/photo/2020/07/13/12/56/mute-swan-5400675__340.jpg'):
+        content = '''
+            <svg viewBox="0 0 510 340" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
+            <circle cx="200" cy="200" r="100" fill="none" stroke="red" stroke-width="10" />
+            </svg>'''
+        ui.html(content).style('background:transparent')
+
+with example(ui.interactive_image):
+    from nicegui.events import MouseEventArguments
+
+    def mouse_handler(e: MouseEventArguments):
+        color = 'green' if e.type == 'mousedown' else 'orange'
+        ii.svg_content += f'<circle cx="{e.image_x}" cy="{e.image_y}" r="10" fill="{color}"/>'
+        ui.notify(f'{e.type} at ({e.image_x:.1f}, {e.image_y:.1f})')
+
+    ii = ui.interactive_image('http://placeimg.com/640/360/arch',
+                              on_mouse=mouse_handler,
+                              events=['mousedown', 'mouseup'], cross=True)
+
+h3('Data Elements')
+
+with example(ui.table):
+    def update():
+        table.options.rowData[0].age += 1
+        table.update()
+
+    table = ui.table({
+        'columnDefs': [
+            {'headerName': 'Name', 'field': 'name'},
+            {'headerName': 'Age', 'field': 'age'},
+        ],
+        'rowData': [
+            {'name': 'Alice', 'age': 18},
+            {'name': 'Bob', 'age': 21},
+            {'name': 'Carol', 'age': 42},
+        ],
+    }).classes('max-h-40')
+    ui.button('Update', on_click=update)
+
+with example(ui.chart):
+    from numpy.random import random
+
+    def update():
+        chart.options.series[0].data[:] = random(2)
+        chart.update()
+
+    chart = ui.chart({
+        'title': False,
+        'chart': {'type': 'bar'},
+        'xAxis': {'categories': ['A', 'B']},
+        'series': [
+            {'name': 'Alpha', 'data': [0.1, 0.2]},
+            {'name': 'Beta', 'data': [0.3, 0.4]},
+        ],
+    }).classes('max-w-full h-64')
+    ui.button('Update', on_click=update)
+
 with example(ui.plot):
     import numpy as np
     from matplotlib import pyplot as plt
@@ -263,18 +311,6 @@ with example(ui.line_plot):
     ]), active=False)
     ui.checkbox('active').bind_value(line_updates, 'active')
 
-with example(ui.log):
-    from datetime import datetime
-
-    log = ui.log(max_lines=10).classes('h-16')
-    ui.button('Log time', on_click=lambda: log.push(datetime.now().strftime("%X.%f")[:-5]))
-
-with example(ui.tree):
-    ui.tree([
-        {'id': 'number', 'children': [{'id': '1'}, {'id': '2'}]},
-        {'id': 'letters', 'children': [{'id': 'A'}, {'id': 'B'}]},
-    ], label_key='id', on_select=lambda e: ui.notify(e.value))
-
 with example(ui.scene):
     with ui.scene(width=200, height=200) as scene:
         scene.sphere().material('#4488ff')
@@ -299,49 +335,79 @@ with example(ui.scene):
         scene.text('2D', 'background: rgba(0, 0, 0, 0.2); border-radius: 5px; padding: 5px').move(z=2)
         scene.text3d('3D', 'background: rgba(0, 0, 0, 0.2); border-radius: 5px; padding: 5px').move(y=-2).scale(.05)
 
-with example(ui.chart):
-    from numpy.random import random
+with example(ui.tree):
+    ui.tree([
+        {'id': 'number', 'children': [{'id': '1'}, {'id': '2'}]},
+        {'id': 'letters', 'children': [{'id': 'A'}, {'id': 'B'}]},
+    ], label_key='id', on_select=lambda e: ui.notify(e.value))
 
-    def update():
-        chart.options.series[0].data[:] = random(2)
-        chart.update()
+with example(ui.log):
+    from datetime import datetime
 
-    chart = ui.chart({
-        'title': False,
-        'chart': {'type': 'bar'},
-        'xAxis': {'categories': ['A', 'B']},
-        'series': [
-            {'name': 'Alpha', 'data': [0.1, 0.2]},
-            {'name': 'Beta', 'data': [0.3, 0.4]},
-        ],
-    }).classes('max-w-full h-64')
-    ui.button('Update', on_click=update)
+    log = ui.log(max_lines=10).classes('h-16')
+    ui.button('Log time', on_click=lambda: log.push(datetime.now().strftime("%X.%f")[:-5]))
 
-with example(ui.table):
-    def update():
-        table.options.rowData[0].age += 1
-        table.update()
+h3('Layout')
 
-    table = ui.table({
-        'columnDefs': [
-            {'headerName': 'Name', 'field': 'name'},
-            {'headerName': 'Age', 'field': 'age'},
-        ],
-        'rowData': [
-            {'name': 'Alice', 'age': 18},
-            {'name': 'Bob', 'age': 21},
-            {'name': 'Carol', 'age': 42},
-        ],
-    }).classes('max-h-40')
-    ui.button('Update', on_click=update)
+with example(ui.card):
+    with ui.card().tight():
+        ui.image('http://placeimg.com/640/360/nature')
+        with ui.card_section():
+            ui.label('Lorem ipsum dolor sit amet, consectetur adipiscing elit, ...')
 
-with example(ui.joystick):
-    ui.joystick(
-        color='blue',
-        size=50,
-        on_move=lambda msg: coordinates.set_text(f'{msg.data.vector.x:.3f}, {msg.data.vector.y:.3f}'),
-        on_end=lambda _: coordinates.set_text('0, 0'))
-    coordinates = ui.label('0, 0')
+with example(ui.column):
+    with ui.column():
+        ui.label('label 1')
+        ui.label('label 2')
+        ui.label('label 3')
+
+with example(ui.row):
+    with ui.row():
+        ui.label('label 1')
+        ui.label('label 2')
+        ui.label('label 3')
+
+clear_containers = '''#### Clear Containers
+
+To remove all elements from a row, column or card container, use the `clear()` method.
+'''
+with example(clear_containers):
+    container = ui.row()
+
+    def add_face():
+        with container:
+            ui.icon('face')
+    add_face()
+
+    ui.button('Add', on_click=add_face)
+    ui.button('Clear', on_click=container.clear)
+
+with example(ui.expansion):
+    with ui.expansion('Expand!', icon='work').classes('w-full'):
+        ui.label('inside the expansion')
+
+with example(ui.menu):
+    choice = ui.label('Try the menu.')
+    with ui.menu() as menu:
+        ui.menu_item('Menu item 1', lambda: choice.set_text('Selected item 1.'))
+        ui.menu_item('Menu item 2', lambda: choice.set_text('Selected item 2.'))
+        ui.menu_item('Menu item 3 (keep open)', lambda: choice.set_text('Selected item 3.'), auto_close=False)
+        ui.menu_separator()
+        ui.menu_item('Close', on_click=menu.close)
+
+    ui.button('Open menu', on_click=menu.open).props('color=secondary')
+
+tooltips = '''#### Tooltips
+
+Simply call the `tooltip(text:str)` method on UI elements to provide a tooltip.
+'''
+with example(tooltips):
+    with ui.row():
+        ui.button().props('icon=thumb_up').tooltip('I like this')
+        ui.label('tooltips').classes('q-mt-sm').tooltip('tooltips are shown on mouse over')
+
+with example(ui.notify):
+    ui.button('Show notification', on_click=lambda: ui.notify('Some message', close_button='OK'))
 
 with example(ui.dialog):
     with ui.dialog() as dialog, ui.card():
@@ -350,7 +416,8 @@ with example(ui.dialog):
 
     ui.button('Open a dialog', on_click=dialog.open)
 
-async_dialog = '''### Awaitable dialog
+async_dialog = '''#### Awaitable dialog
+
 Dialogs can be awaited.
 Use the `submit` method to close the dialog and return a result.
 Canceling the dialog by clicking in the background or pressing the escape key yields `None`.
@@ -368,33 +435,9 @@ with example(async_dialog):
 
     ui.button('Await a dialog', on_click=show)
 
-tooltip = '''### Tooltips
-Simply call the `tooltip(text:str)` method on UI elements to provide a tooltip.
-'''
-with example(tooltip):
-    with ui.row():
-        ui.button().props('icon=thumb_up').tooltip('I like this')
-        ui.label('tooltips').classes('q-mt-sm').tooltip('tooltips are shown on mouse over')
-
-with example(ui.menu):
-    choice = ui.label('Try the menu.')
-    with ui.menu() as menu:
-        ui.menu_item('Menu item 1', lambda: choice.set_text('Selected item 1.'))
-        ui.menu_item('Menu item 2', lambda: choice.set_text('Selected item 2.'))
-        ui.menu_item('Menu item 3 (keep open)', lambda: choice.set_text('Selected item 3.'), auto_close=False)
-        ui.menu_separator()
-        ui.menu_item('Close', on_click=menu.close)
-
-    ui.button('Open menu', on_click=menu.open).props('color=secondary')
-
-with example(ui.expansion):
-    with ui.expansion('Expand!', icon='work').classes('w-full'):
-        ui.label('inside the expansion')
-
-with example(ui.notify):
-    ui.button('Show notification', on_click=lambda: ui.notify('Some message', close_button='OK'))
+h3('Appearance')
 
-design = '''### Styling
+design = '''#### Styling
 
 NiceGUI uses the [Quasar Framework](https://quasar.dev/) version 1.0 and hence has its full design power.
 Each NiceGUI element provides a `props` method whose content is passed [to the Quasar component](https://justpy.io/quasar_tutorial/introduction/#props-of-quasar-components):
@@ -414,59 +457,33 @@ with example(ui.colors):
     ui.button('Default', on_click=lambda: ui.colors())
     ui.colors()
 
-with example(ui.card):
-    with ui.card().tight():
-        ui.image('http://placeimg.com/640/360/nature')
-        with ui.card_section():
-            ui.label('Lorem ipsum dolor sit amet, consectetur adipiscing elit, ...')
+h3('Action')
 
-with example(ui.column):
-    with ui.column():
-        ui.label('label 1')
-        ui.label('label 2')
-        ui.label('label 3')
+lifecycle = '''#### Lifecycle
 
-with example(ui.row):
-    with ui.row():
-        ui.label('label 1')
-        ui.label('label 2')
-        ui.label('label 3')
+You can run a function or coroutine as a parallel task by passing it to one of the following register methods:
 
-clear = '''### Clear Containers
+- `ui.on_startup`: Called when NiceGUI is started or restarted.
+- `ui.on_shutdown`: Called when NiceGUI is shut down or restarted.
+- `ui.on_connect`: Called when a client connects to NiceGUI. (Optional argument: Starlette request)
+- `ui.on_page_ready`: Called when the page is ready and the websocket is connected. (Optional argument: socket)
+- `ui.on_disconnect`: Called when a client disconnects from NiceGUI.
 
-To remove all elements from a row, column or card container, use the `clear()` method.
+When NiceGUI is shut down or restarted, the startup tasks will be automatically canceled.
 '''
-with example(clear):
-    container = ui.row()
-
-    def add_face():
-        with container:
-            ui.icon('face')
-    add_face()
-
-    ui.button('Add', on_click=add_face)
-    ui.button('Clear', on_click=container.clear)
-
-binding = '''### Bindings
+with example(lifecycle):
+    import asyncio
+    import time
 
-NiceGUI is able to directly bind UI elements to models.
-Binding is possible for UI element properties like text, value or visibility and for model properties that are (nested) class attributes.
+    l = ui.label()
 
-Each element provides methods like `bind_value` and `bind_visibility` to create a two-way binding with the corresponding property.
-To define a one-way binding use the `_from` and `_to` variants of these methods.
-Just pass a property of the model as parameter to these methods to create the binding.
-'''
-with example(binding):
-    class Demo:
-        def __init__(self):
-            self.number = 1
+    async def run_clock():
+        while True:
+            l.text = f'unix time: {time.time():.1f}'
+            await asyncio.sleep(1)
 
-    demo = Demo()
-    v = ui.checkbox('visible', value=True)
-    with ui.column().bind_visibility_from(v, 'value'):
-        ui.slider(min=1, max=3).bind_value(demo, 'number')
-        ui.toggle({1: 'a', 2: 'b', 3: 'c'}).bind_value(demo, 'number')
-        ui.number().bind_value(demo, 'number')
+    ui.on_startup(run_clock)
+    ui.on_connect(lambda: l.set_text('new connection'))
 
 with example(ui.timer):
     from datetime import datetime
@@ -485,39 +502,57 @@ with example(ui.timer):
         lazy_clock = ui.label()
         ui.timer(interval=0.1, callback=lazy_update)
 
-lifecycle = '''### Lifecycle
+with example(ui.keyboard):
+    from nicegui.events import KeyEventArguments
 
-You can run a function or coroutine as a parallel task by passing it to one of the following register methods:
+    def handle_key(e: KeyEventArguments):
+        if e.key == 'f' and not e.action.repeat:
+            if e.action.keyup:
+                ui.notify('f was just released')
+            elif e.action.keydown:
+                ui.notify('f was just pressed')
+        if e.modifiers.shift and e.action.keydown:
+            if e.key.arrow_left:
+                ui.notify('going left')
+            elif e.key.arrow_right:
+                ui.notify('going right')
+            elif e.key.arrow_up:
+                ui.notify('going up')
+            elif e.key.arrow_down:
+                ui.notify('going down')
 
-- `ui.on_startup`: Called when NiceGUI is started or restarted.
-- `ui.on_shutdown`: Called when NiceGUI is shut down or restarted.
-- `ui.on_connect`: Called when a client connects to NiceGUI. (Optional argument: Starlette request)
-- `ui.on_page_ready`: Called when the page is ready and the websocket is connected. (Optional argument: socket)
-- `ui.on_disconnect`: Called when a client disconnects from NiceGUI.
+    keyboard = ui.keyboard(on_key=handle_key)
+    ui.label('Key events can be caught globally by using the keyboard element.')
+    ui.checkbox('Track key events').bind_value_to(keyboard, 'active')
 
-When NiceGUI is shut down or restarted, the startup tasks will be automatically canceled.
-'''
-with example(lifecycle):
-    import asyncio
-    import time
+bindings = '''#### Bindings
 
-    l = ui.label()
+NiceGUI is able to directly bind UI elements to models.
+Binding is possible for UI element properties like text, value or visibility and for model properties that are (nested) class attributes.
 
-    async def run_clock():
-        while True:
-            l.text = f'unix time: {time.time():.1f}'
-            await asyncio.sleep(1)
+Each element provides methods like `bind_value` and `bind_visibility` to create a two-way binding with the corresponding property.
+To define a one-way binding use the `_from` and `_to` variants of these methods.
+Just pass a property of the model as parameter to these methods to create the binding.
+'''
+with example(bindings):
+    class Demo:
+        def __init__(self):
+            self.number = 1
 
-    ui.on_startup(run_clock)
-    ui.on_connect(lambda: l.set_text('new connection'))
+    demo = Demo()
+    v = ui.checkbox('visible', value=True)
+    with ui.column().bind_visibility_from(v, 'value'):
+        ui.slider(min=1, max=3).bind_value(demo, 'number')
+        ui.toggle({1: 'a', 2: 'b', 3: 'c'}).bind_value(demo, 'number')
+        ui.number().bind_value(demo, 'number')
 
-updates = '''### UI Updates
+ui_updates = '''#### UI Updates
 
 NiceGUI tries to automatically synchronize the state of UI elements with the client, e.g. when a label text, an input value or style/classes/props of an element have changed.
 In other cases, you can explicitly call `element.update()` or `ui.update(*elements)` to update.
 The example code shows both methods for a `ui.table`, where it is difficult to automatically detect changes in the `options` dictionary.
 '''
-with example(updates):
+with example(ui_updates):
     from random import randint
 
     def add():
@@ -532,8 +567,21 @@ with example(updates):
     ui.button('Add', on_click=add)
     ui.button('Clear', on_click=clear)
 
-with example(ui.link):
-    ui.link('NiceGUI on GitHub', 'https://github.com/zauberzeug/nicegui')
+async_handlers = '''#### Async event handlers
+
+Most elements also support asynchronous event handlers.
+
+Note: You can also pass a `functools.partial` into the `on_click` property to wrap async functions with parameters.
+'''
+with example(async_handlers):
+    async def async_task():
+        ui.notify('Asynchronous task started')
+        await asyncio.sleep(5)
+        ui.notify('Asynchronous task finished')
+
+    ui.button('start async task', on_click=async_task)
+
+h3('Pages and Routes')
 
 with example(ui.page):
     with ui.page('/other_page'):
@@ -554,37 +602,11 @@ with example(ui.open):
 
     ui.button('REDIRECT', on_click=lambda e: ui.open(other, e.socket))
 
-sessions = """### Sessions
-
-`ui.page` provides an optional `on_connect` argument to register a callback.
-It is invoked for each new connection to the page.
-
-The optional `request` argument provides insights about the clients URL parameters etc. (see [the JustPy docs](https://justpy.io/tutorial/request_object/) for more details).
-It also enables you to identify sessions over [longer time spans by configuring cookies](https://justpy.io/tutorial/sessions/).
-"""
-with example(sessions):
-    from collections import Counter
-    from datetime import datetime
-
-    from starlette.requests import Request
-
-    id_counter = Counter()
-    creation = datetime.now().strftime('%H:%M, %d %B %Y')
-
-    def handle_connection(request: Request):
-        id_counter[request.session_id] += 1
-        visits.set_text(f'{len(id_counter)} unique views ({sum(id_counter.values())} overall) since {creation}')
-
-    with ui.page('/session_demo', on_connect=handle_connection) as page:
-        visits = ui.label()
-
-    ui.link('Visit session demo', page)
-
-add_route = """### Route
+add_route = '''#### Route
 
 Add a new route by calling `ui.add_route` with a starlette route including a path and a function to be called.
 Routed paths must start with a `'/'`.
-"""
+'''
 with example(add_route):
     import starlette
 
@@ -594,7 +616,7 @@ with example(add_route):
 
     ui.link('Try the new route!', 'new/route')
 
-get_decorator = """### Get decorator
+get_decorator = '''#### Get decorator
 
 Syntactic sugar to add routes.
 Decorating a function with the `@ui.get` makes it available at the specified endpoint, e.g. `'/another/route/<id>'`.
@@ -602,7 +624,7 @@ Decorating a function with the `@ui.get` makes it available at the specified end
 Path parameters can be passed to the request handler like with [FastAPI](https://fastapi.tiangolo.com/tutorial/path-params/).
 If type-annotated, they are automatically converted to `bool`, `int`, `float` and `complex` values.
 An optional `request` argument gives access to the complete request object.
-"""
+'''
 with example(get_decorator):
     from starlette import requests, responses
 
@@ -612,27 +634,30 @@ with example(get_decorator):
 
     ui.link('Try yet another route!', 'another/route/42')
 
-with example(ui.keyboard):
-    from nicegui.events import KeyEventArguments
+sessions = '''#### Sessions
 
-    def handle_key(e: KeyEventArguments):
-        if e.key == 'f' and not e.action.repeat:
-            if e.action.keyup:
-                ui.notify('f was just released')
-            elif e.action.keydown:
-                ui.notify('f was just pressed')
-        if e.modifiers.shift and e.action.keydown:
-            if e.key.arrow_left:
-                ui.notify('going left')
-            elif e.key.arrow_right:
-                ui.notify('going right')
-            elif e.key.arrow_up:
-                ui.notify('going up')
-            elif e.key.arrow_down:
-                ui.notify('going down')
+`ui.page` provides an optional `on_connect` argument to register a callback.
+It is invoked for each new connection to the page.
 
-    keyboard = ui.keyboard(on_key=handle_key)
-    ui.label('Key events can be caught globally by using the keyboard element.')
-    ui.checkbox('Track key events').bind_value_to(keyboard, 'active')
+The optional `request` argument provides insights about the clients URL parameters etc. (see [the JustPy docs](https://justpy.io/tutorial/request_object/) for more details).
+It also enables you to identify sessions over [longer time spans by configuring cookies](https://justpy.io/tutorial/sessions/).
+'''
+with example(sessions):
+    from collections import Counter
+    from datetime import datetime
+
+    from starlette.requests import Request
+
+    id_counter = Counter()
+    creation = datetime.now().strftime('%H:%M, %d %B %Y')
+
+    def handle_connection(request: Request):
+        id_counter[request.session_id] += 1
+        visits.set_text(f'{len(id_counter)} unique views ({sum(id_counter.values())} overall) since {creation}')
+
+    with ui.page('/session_demo', on_connect=handle_connection) as page:
+        visits = ui.label()
+
+    ui.link('Visit session demo', page)
 
 ui.run()

+ 1 - 1
nicegui/elements/button.py

@@ -11,7 +11,7 @@ class Button(Element):
     text = BindableProperty()
 
     def __init__(self, text: str = '', *, on_click: Optional[Callable] = None):
-        """Button Element
+        """Button
 
         :param text: the label of the button
         :param on_click: callback which is invoked when button is pressed

+ 1 - 1
nicegui/elements/card.py

@@ -6,7 +6,7 @@ from .group import Group
 class Card(Group):
 
     def __init__(self):
-        """Card Element
+        """Card
 
         Provides a container with a dropped shadow.
         """

+ 1 - 1
nicegui/elements/checkbox.py

@@ -8,7 +8,7 @@ from .bool_element import BoolElement
 class Checkbox(BoolElement):
 
     def __init__(self, text: str = '', *, value: bool = False, on_change: Optional[Callable] = None):
-        """Checkbox Element
+        """Checkbox
 
         :param text: the label to display next to the checkbox
         :param value: whether it should be checked initially (default: `False`)

+ 1 - 1
nicegui/elements/color_input.py

@@ -9,7 +9,7 @@ class ColorInput(StringElement):
 
     def __init__(self, label: str = None, *,
                  placeholder: str = None, value: str = '', on_change: Optional[Callable] = None):
-        """Color Input Element
+        """Color Input
 
         :param label: displayed label for the color input
         :param placeholder: text to show if no color is selected

+ 1 - 1
nicegui/elements/image.py

@@ -8,7 +8,7 @@ class Image(Group):
     source = BindableProperty()
 
     def __init__(self, source: str = ''):
-        """Image Element
+        """Image
 
         Displays an image.
 

+ 1 - 1
nicegui/elements/input.py

@@ -9,7 +9,7 @@ class Input(StringElement):
 
     def __init__(self, label: str = None, *,
                  placeholder: str = None, value: str = '', on_change: Optional[Callable] = None):
-        """Text Input Element
+        """Text Input
 
         :param label: displayed label for the text input
         :param placeholder: text to show if no value is entered

+ 1 - 1
nicegui/elements/label.py

@@ -8,7 +8,7 @@ class Label(Element):
     text = BindableProperty()
 
     def __init__(self, text: str = ''):
-        """Label Element
+        """Label
 
         Displays some text.
 

+ 1 - 1
nicegui/elements/menu_item.py

@@ -10,7 +10,7 @@ from .element import Element
 class MenuItem(Element):
 
     def __init__(self, text: str = '', on_click: Optional[Callable] = None, *, auto_close: bool = True):
-        """Menu Item Element
+        """Menu Item
 
         A menu item to be added to a menu.
 

+ 1 - 1
nicegui/elements/number.py

@@ -10,7 +10,7 @@ class Number(FloatElement):
     def __init__(
             self, label: str = None, *,
             placeholder: str = None, value: float = None, format: str = None, on_change: Optional[Callable] = None):
-        """Number Input Element
+        """Number Input
 
         :param label: displayed name for the number input
         :param placeholder: text to show if no value is entered

+ 1 - 2
nicegui/elements/open.py

@@ -8,8 +8,7 @@ from ..task_logger import create_task
 
 
 def open(self, target: Union[Page, str], socket: Optional[WebSocket] = None):
-    """
-    Open
+    """Open
 
     Can be used to programmatically trigger redirects for a specific client.
 

+ 1 - 1
nicegui/elements/radio.py

@@ -8,7 +8,7 @@ from .choice_element import ChoiceElement
 class Radio(ChoiceElement):
 
     def __init__(self, options: Union[List, Dict], *, value: Any = None, on_change: Optional[Callable] = None):
-        """Radio Selection Element
+        """Radio Selection
 
         :param options: a list ['value1', ...] or dictionary `{'value1':'label1', ...}` specifying the options
         :param value: the initial value

+ 1 - 1
nicegui/elements/select.py

@@ -9,7 +9,7 @@ class Select(ChoiceElement):
 
     def __init__(self, options: Union[List, Dict], *,
                  label: Optional[str] = None, value: Any = None, on_change: Optional[Callable] = None):
-        """Dropdown Selection Element
+        """Dropdown Selection
 
         :param options: a list ['value1', ...] or dictionary `{'value1':'label1', ...}` specifying the options
         :param value: the initial value

+ 1 - 1
nicegui/elements/slider.py

@@ -13,7 +13,7 @@ class Slider(FloatElement):
                  step: float = 1,
                  value: float = None,
                  on_change: Optional[Callable] = None):
-        """Slider Element
+        """Slider
 
         :param min: lower bound of the slider
         :param max: upper bound of the slider

+ 1 - 1
nicegui/elements/switch.py

@@ -8,7 +8,7 @@ from .bool_element import BoolElement
 class Switch(BoolElement):
 
     def __init__(self, text: str = '', *, value: bool = False, on_change: Optional[Callable] = None):
-        """Switch Element
+        """Switch
 
         :param text: the label to display next to the switch
         :param value: whether it should be active initially (default: `False`)

+ 1 - 1
nicegui/elements/toggle.py

@@ -8,7 +8,7 @@ from .choice_element import ChoiceElement
 class Toggle(ChoiceElement):
 
     def __init__(self, options: Union[List, Dict], *, value: Any = None, on_change: Optional[Callable] = None):
-        """Toggle Element
+        """Toggle
 
         :param options: a list ['value1', ...] or dictionary `{'value1':'label1', ...}` specifying the options
         :param value: the initial value

+ 1 - 1
nicegui/elements/tree.py

@@ -12,7 +12,7 @@ class Tree(Element):
                  label_key: str = 'label',
                  children_key: str = 'children',
                  on_select: Optional[Callable] = None):
-        """Tree Element
+        """Tree
 
         :param nodes: hierarchical list of node objects
         :param node_key: property name of each node object that holds its unique id (default: "id")

+ 1 - 1
nicegui/elements/upload.py

@@ -14,7 +14,7 @@ class Upload(Element):
                  multiple: bool = False,
                  on_upload: Optional[Callable] = None,
                  upload_button_text: str = 'Upload') -> None:
-        """File Upload Element
+        """File Upload
 
         :param multiple: allow uploading multiple files at once (default: `False`)
         :param on_upload: callback to execute when a file is uploaded (list of bytearrays)