Bläddra i källkod

Merge pull request #1033 from zauberzeug/stepper

Implement QStepper element
Rodja Trappe 1 år sedan
förälder
incheckning
fd0bbca92d

+ 69 - 0
nicegui/elements/stepper.py

@@ -0,0 +1,69 @@
+from __future__ import annotations
+
+from typing import Any, Callable, Optional, Union, cast
+
+from .. import globals
+from ..element import Element
+from .mixins.disableable_element import DisableableElement
+from .mixins.value_element import ValueElement
+
+
+class Stepper(ValueElement):
+
+    def __init__(self, *,
+                 value: Union[str, Step, None] = None,
+                 on_value_change: Optional[Callable[..., Any]] = None,
+                 ) -> None:
+        """Stepper
+
+        This element represents `Quasar's QStepper <https://quasar.dev/vue-components/stepper#qstepper-api>`_ component.
+        It contains individual steps.
+
+        :param value: `ui.step` or name of the step to be initially selected (default: `None` meaning the first step)
+        :param on_value_change: callback to be executed when the selected step changes
+        """
+        super().__init__(tag='q-stepper', value=value, on_value_change=on_value_change)
+
+    def _value_to_model_value(self, value: Any) -> Any:
+        return value._props['name'] if isinstance(value, Step) else value
+
+    def on_value_change(self, value: Any) -> None:
+        super().on_value_change(value)
+        names = [step._props['name'] for step in self]
+        for i, step in enumerate(self):
+            done = i < names.index(value) if value in names else False
+            step.props(f':done={done}')
+
+    def next(self) -> None:
+        self.run_method('next')
+
+    def previous(self) -> None:
+        self.run_method('previous')
+
+
+class Step(DisableableElement):
+
+    def __init__(self, name: str, title: Optional[str] = None, icon: Optional[str] = None) -> None:
+        """Step
+
+        This element represents `Quasar's QStep <https://quasar.dev/vue-components/stepper#qstep-api>`_ component.
+        It is a child of a `ui.stepper` element.
+
+        :param name: name of the step (will be the value of the `ui.stepper` element)
+        :param title: title of the step (default: `None`, meaning the same as `name`)
+        :param icon: icon of the step (default: `None`)
+        """
+        super().__init__(tag='q-step')
+        self._props['name'] = name
+        self._props['title'] = title if title is not None else name
+        if icon:
+            self._props['icon'] = icon
+        self.stepper = cast(ValueElement, globals.get_slot().parent)
+        if self.stepper.value is None:
+            self.stepper.value = name
+
+
+class StepperNavigation(Element):
+
+    def __init__(self) -> None:
+        super().__init__('q-stepper-navigation')

+ 6 - 0
nicegui/ui.py

@@ -52,6 +52,9 @@ __all__ = [
     'slider',
     'spinner',
     'splitter',
+    'step',
+    'stepper',
+    'stepper_navigation',
     'switch',
     'table',
     'tab',
@@ -136,6 +139,9 @@ from .elements.separator import Separator as separator
 from .elements.slider import Slider as slider
 from .elements.spinner import Spinner as spinner
 from .elements.splitter import Splitter as splitter
+from .elements.stepper import Step as step
+from .elements.stepper import Stepper as stepper
+from .elements.stepper import StepperNavigation as stepper_navigation
 from .elements.switch import Switch as switch
 from .elements.table import Table as table
 from .elements.tabs import Tab as tab

+ 27 - 0
tests/test_stepper.py

@@ -0,0 +1,27 @@
+from nicegui import ui
+
+from .screen import Screen
+
+
+def test_stepper(screen: Screen):
+    with ui.stepper() as stepper:
+        with ui.step('One'):
+            ui.label('First step')
+            with ui.stepper_navigation():
+                ui.button('Next', on_click=stepper.next)
+                ui.button('Back', on_click=stepper.previous)
+        with ui.step('Two'):
+            ui.label('Second step')
+            with ui.stepper_navigation():
+                ui.button('Next', on_click=stepper.next)
+                ui.button('Back', on_click=stepper.previous)
+
+    screen.open('/')
+    screen.should_contain('First step')
+    screen.should_not_contain('Second step')
+    screen.click('Next')
+    screen.should_contain('Second step')
+    screen.should_not_contain('First step')
+    screen.click('Back')
+    screen.should_contain('First step')
+    screen.should_not_contain('Second step')

+ 1 - 0
website/documentation.py

@@ -167,6 +167,7 @@ def create_full() -> None:
     load_demo(ui.expansion)
     load_demo(ui.splitter)
     load_demo('tabs')
+    load_demo(ui.stepper)
     load_demo(ui.menu)
 
     @text_demo('Tooltips', '''

+ 19 - 0
website/more_documentation/stepper_documentation.py

@@ -0,0 +1,19 @@
+from nicegui import ui
+
+
+def main_demo() -> None:
+    with ui.stepper().props('vertical').classes('w-full') as stepper:
+        with ui.step('Preheat'):
+            ui.label('Preheat the oven to 350 degrees')
+            with ui.stepper_navigation():
+                ui.button('Next', on_click=stepper.next)
+        with ui.step('Ingredients'):
+            ui.label('Mix the ingredients')
+            with ui.stepper_navigation():
+                ui.button('Next', on_click=stepper.next)
+                ui.button('Back', on_click=stepper.previous).props('flat')
+        with ui.step('Bake'):
+            ui.label('Bake for 20 minutes')
+            with ui.stepper_navigation():
+                ui.button('Done', on_click=lambda: ui.notify('Yay!', type='positive'))
+                ui.button('Back', on_click=stepper.previous).props('flat')