Repository: zauberzeug/nicegui
Pull request #2527 (2024-02-08 16:21:36)

Introduce `ui.list`, `ui.item`, `ui.item_section` and `ui.item_label`
This PR adds the Quasar List, List Item, List Section and List Label to NiceGUI.

---

Feature request: #512


Patch:
diff --git a/nicegui/elements/list.py b/nicegui/elements/list.py
new file mode 100644
index 00000000..9d6c0162
--- /dev/null
+++ b/nicegui/elements/list.py
@@ -0,0 +1,57 @@
+from typing import Any, Callable, Optional
+
+from ..element import Element
+from ..events import ClickEventArguments, handle_event
+from .mixins.disableable_element import DisableableElement
+from .mixins.text_element import TextElement
+
+
+class List(Element):
+
+    def __init__(self) -> None:
+        """List
+
+        A list element based on Quasar's `QList <https://quasar.dev/vue-components/list-and-list-items#qlist-api>`_ component.
+        It provides a container for list items.
+        """
+        super().__init__('q-list')
+
+
+class Item(DisableableElement):
+
+    def __init__(self, *, on_click: Optional[Callable[..., Any]] = None) -> None:
+        """List Item
+
+        Creates a list item based on Quasar's `QItem <https://quasar.dev/vue-components/list-and-list-items#qitem-api>`_ component.
+        The item should be placed inside a list element.
+        """
+        super().__init__(tag='q-item')
+
+        if on_click:
+            self._props['clickable'] = True
+            self.on('click', lambda _: handle_event(on_click, ClickEventArguments(sender=self, client=self.client)))
+
+
+class ItemSection(Element):
+
+    def __init__(self) -> None:
+        """
+        List Item Section
+
+        Creates an item section based on Quasar's `QItemList <https://quasar.dev/vue-components/list-and-list-items#qitemsection-api>`_ component.
+        The section should be placed inside a list item element.
+        """
+        super().__init__('q-item-section')
+
+
+class ItemLabel(TextElement):
+
+    def __init__(self, text: str = '') -> None:
+        """
+        List Item Label
+
+        Creates an item label based on Quasar's `QItemLabel <https://quasar.dev/vue-components/list-and-list-items#qitemlabel-api>`_ component.
+
+        :param text: text to be displayed (default: "")
+        """
+        super().__init__(tag='q-item-label', text=text)
diff --git a/nicegui/ui.py b/nicegui/ui.py
index 3e9aa40a..6d89fcdf 100644
--- a/nicegui/ui.py
+++ b/nicegui/ui.py
@@ -32,6 +32,9 @@ __all__ = [
     'image',
     'input',
     'interactive_image',
+    'item',
+    'item_label',
+    'item_section',
     'joystick',
     'json_editor',
     'keyboard',
@@ -41,6 +44,7 @@ __all__ = [
     'line_plot',
     'link',
     'link_target',
+    'list',
     'log',
     'markdown',
     'menu',
@@ -148,6 +152,10 @@ from .elements.leaflet import Leaflet as leaflet
 from .elements.line_plot import LinePlot as line_plot
 from .elements.link import Link as link
 from .elements.link import LinkTarget as link_target
+from .elements.list import Item as item
+from .elements.list import ItemLabel as item_label
+from .elements.list import ItemSection as item_section
+from .elements.list import List as list  # pylint: disable=redefined-builtin
 from .elements.log import Log as log
 from .elements.markdown import Markdown as markdown
 from .elements.menu import Menu as menu
diff --git a/website/documentation/content/list_documentation.py b/website/documentation/content/list_documentation.py
new file mode 100644
index 00000000..14742e6d
--- /dev/null
+++ b/website/documentation/content/list_documentation.py
@@ -0,0 +1,52 @@
+from nicegui import ui
+
+from . import doc
+
+
+@doc.demo(ui.list)
+def main_demo() -> None:
+    with ui.list().props('dense separator'):
+        with ui.item():
+            with ui.item_section():
+                ui.item_label('3 Apples')
+        with ui.item():
+            with ui.item_section():
+                ui.item_label('5 Bananas')
+        with ui.item():
+            with ui.item_section():
+                ui.item_label('8 Strawberries')
+        with ui.item():
+            with ui.item_section():
+                ui.item_label('13 Walnuts')
+
+
+@doc.demo('Items, Sections and Labels', '''
+    List items use item sections to structure their content.
+    Item labels take different positions depending on their props.
+''')
+def contact_list():
+    with ui.list().props('bordered separator'):
+        ui.item_label('Contacts').props('header').classes('text-bold')
+        ui.separator()
+        with ui.item(on_click=lambda: ui.notify('Selected contact 1')):
+            with ui.item_section().props('avatar'):
+                ui.icon('person')
+            with ui.item_section():
+                ui.item_label('Nice Guy')
+                ui.item_label('name').props('caption')
+            with ui.item_section().props('side'):
+                ui.icon('chat')
+        with ui.item(on_click=lambda: ui.notify('Selected contact 2')):
+            with ui.item_section().props('avatar'):
+                ui.icon('person')
+            with ui.item_section():
+                ui.item_label('Nice Person')
+                ui.item_label('name').props('caption')
+            with ui.item_section().props('side'):
+                ui.icon('chat')
+
+
+doc.reference(ui.list, title='Reference for ui.list')
+doc.reference(ui.item, title='Reference for ui.item')
+doc.reference(ui.item_section, title='Reference for ui.item_section')
+doc.reference(ui.item_label, title='Reference for ui.item_label')
diff --git a/website/documentation/content/section_page_layout.py b/website/documentation/content/section_page_layout.py
index 1870ef08..d5104b90 100644
--- a/website/documentation/content/section_page_layout.py
+++ b/website/documentation/content/section_page_layout.py
@@ -1,10 +1,11 @@
 from nicegui import ui
 
 from . import (card_documentation, carousel_documentation, column_documentation, context_menu_documentation,
-               dialog_documentation, doc, expansion_documentation, grid_documentation, menu_documentation,
-               notification_documentation, notify_documentation, pagination_documentation, row_documentation,
-               scroll_area_documentation, separator_documentation, space_documentation, splitter_documentation,
-               stepper_documentation, tabs_documentation, timeline_documentation, tooltip_documentation)
+               dialog_documentation, doc, expansion_documentation, grid_documentation, list_documentation,
+               menu_documentation, notification_documentation, notify_documentation, pagination_documentation,
+               row_documentation, scroll_area_documentation, separator_documentation, space_documentation,
+               splitter_documentation, stepper_documentation, tabs_documentation, timeline_documentation,
+               tooltip_documentation)
 
 doc.title('Page *Layout*')
 
@@ -33,6 +34,7 @@ doc.intro(card_documentation)
 doc.intro(column_documentation)
 doc.intro(row_documentation)
 doc.intro(grid_documentation)
+doc.intro(list_documentation)
 
 
 @doc.demo('Clear Containers', '''


Test patch:
diff --git a/tests/test_list.py b/tests/test_list.py
new file mode 100644
index 00000000..5a6d3a3a
--- /dev/null
+++ b/tests/test_list.py
@@ -0,0 +1,24 @@
+from nicegui import ui
+from nicegui.testing import Screen
+
+
+def test_clicking_items(screen: Screen):
+    with ui.list():
+        with ui.item(on_click=lambda: ui.notify('Clicked item 1')):
+            with ui.item_section():
+                ui.item_label('Item 1')
+        with ui.item(on_click=lambda: ui.notify('Clicked item 2')):
+            with ui.item_section():
+                ui.item_label('Item 2')
+            with ui.item_section():
+                ui.button('Button').on('click.stop', lambda: ui.notify('Clicked button!'))
+
+    screen.open('/')
+    screen.click('Item 1')
+    screen.should_contain('Clicked item 1')
+
+    screen.click('Item 2')
+    screen.should_contain('Clicked item 2')
+
+    screen.click('Button')
+    screen.should_contain('Clicked button!')