diff --git a/example_async_dialog.py b/example_async_dialog.py index 85f8b9a..ca18058 100644 --- a/example_async_dialog.py +++ b/example_async_dialog.py @@ -2,29 +2,30 @@ """Example demonstrating the AsyncElement.as_dialog() method""" from nicegui import ui -from src.niceguiex.async_elements import AsyncElement +from src.niceguiex.async_elements import AsyncColumn -class ConfirmDialog(AsyncElement[ui.column]): +class ConfirmDialog(AsyncColumn): """A confirmation dialog that can be awaited for a result""" async def build(self, message: str, title: str = "Confirm"): """Build the dialog content""" - with self._element: - ui.label(title).classes('text-h6') - ui.label(message) - ui.space() + ui.label(title).classes('text-h6') + ui.label(message) + ui.space() - with ui.row().classes('full-width justify-end'): - ui.button('Cancel', on_click=lambda: self._dialog.submit(False)) - ui.button('OK', on_click=lambda: self._dialog.submit(True)).props('color=primary') + with ui.row().classes('full-width justify-end'): + ui.button('Cancel', on_click=lambda: self._dialog.submit(False)) + ui.button('OK', on_click=lambda: self._dialog.submit(True)).props('color=primary') -class FormDialog(AsyncElement[ui.column]): +class FormDialog(AsyncColumn): """A form dialog that collects user input""" async def build(self, title: str = "Enter Information"): """Build the form dialog""" + ui.label(title).classes('text-h6') + # Create input fields name_input = ui.input('Name', placeholder='Enter your name') email_input = ui.input('Email', placeholder='Enter your email') @@ -32,11 +33,16 @@ class FormDialog(AsyncElement[ui.column]): ui.space() with ui.row().classes('full-width justify-end'): - ui.button('Cancel', on_click=lambda: self._dialog.submit(None)) - ui.button('Submit', on_click=lambda: self._dialog.submit({ - 'name': name_input.value, - 'email': email_input.value - })).props('color=primary') + # Check if we're in dialog mode (self._dialog exists) + if hasattr(self, '_dialog'): + ui.button('Cancel', on_click=lambda: self._dialog.submit(None)) + ui.button('Submit', on_click=lambda: self._dialog.submit({ + 'name': name_input.value, + 'email': email_input.value + })).props('color=primary') + else: + # Normal mode - just show submit button that displays the values + ui.button('Submit', on_click=lambda: ui.notify(f"Name: {name_input.value}, Email: {email_input.value}")).props('color=primary') @ui.page('/') @@ -44,6 +50,27 @@ async def main(): ui.label('AsyncElement Dialog Examples').classes('text-h4') ui.separator() + # Example: Same component used both normally and as dialog + with ui.row(): + with ui.column(): + ui.label('Normal usage (inline):').classes('text-subtitle1') + # Use FormDialog normally inline on the page + await FormDialog.create(title="Inline Form") + + with ui.column(): + ui.label('Dialog usage:').classes('text-subtitle1') + + async def show_form_dialog(): + result = await FormDialog.as_dialog(title="Dialog Form") + if result: + ui.notify(f'Form submitted: {result}', type='positive') + else: + ui.notify('Form cancelled', type='warning') + + ui.button('Open as Dialog', on_click=show_form_dialog) + + ui.separator() + async def show_confirm(): result = await ConfirmDialog.as_dialog( message="Are you sure you want to proceed?", @@ -62,9 +89,9 @@ async def main(): ui.button('Show Confirmation Dialog', on_click=show_confirm) ui.button('Show Form Dialog', on_click=show_form) - # Example with inline async element + # Example with inline async element using AsyncColumn async def show_inline_dialog(): - class QuickDialog(AsyncElement[ui.column]): + class QuickDialog(AsyncColumn): async def build(self): ui.label('Quick Question').classes('text-h6') ui.label('Do you like this feature?') diff --git a/src/niceguiex/async_elements/elements.py b/src/niceguiex/async_elements/elements.py index 74bc411..015912b 100644 --- a/src/niceguiex/async_elements/elements.py +++ b/src/niceguiex/async_elements/elements.py @@ -1,5 +1,5 @@ from abc import ABC, abstractmethod -from typing import Self +from typing import Self, Any from nicegui import ui @@ -21,6 +21,39 @@ class AsyncColumn(ui.column, ABC): await instance.build(*args, **kwargs) return instance + @classmethod + async def as_dialog(cls, *args, **kwargs) -> Any: + """Create element inside a dialog and await its result + + Works like create() but wraps the element in a dialog with a card that opens automatically + and returns the dialog result when submitted. + + The dialog can be submitted using dialog.submit(result) from within the build() method. + Access the dialog via self._dialog in your build() implementation. + + Returns the value passed to dialog.submit() or None if dismissed. + """ + # Create the dialog + dialog = ui.dialog() + dialog.open() + + # Build the element inside the dialog with a card + with dialog, ui.card(): + instance = cls() + + # Store dialog reference for potential use in build() + instance._dialog = dialog # pyright: ignore[reportAttributeAccessIssue] + + await instance.build(*args, **kwargs) + + # Await the dialog result + result = await dialog + + # Clean up the dialog after it's closed + dialog.clear() + + return result + class AsyncRow(ui.row, ABC): """Async row that inherits from ui.row for perfect typing""" @@ -40,6 +73,21 @@ class AsyncRow(ui.row, ABC): await instance.build(*args, **kwargs) return instance + @classmethod + async def as_dialog(cls, *args, **kwargs) -> Any: + """Create element inside a dialog and await its result""" + dialog = ui.dialog() + dialog.open() + + with dialog, ui.card(): + instance = cls() + instance._dialog = dialog # pyright: ignore[reportAttributeAccessIssue] + await instance.build(*args, **kwargs) + + result = await dialog + dialog.clear() + return result + class AsyncCard(ui.card, ABC): """Async card that inherits from ui.card for perfect typing""" @@ -59,6 +107,21 @@ class AsyncCard(ui.card, ABC): await instance.build(*args, **kwargs) return instance + @classmethod + async def as_dialog(cls, *args, **kwargs) -> Any: + """Create element inside a dialog and await its result""" + dialog = ui.dialog() + dialog.open() + + with dialog, ui.card(): + instance = cls() + instance._dialog = dialog # pyright: ignore[reportAttributeAccessIssue] + await instance.build(*args, **kwargs) + + result = await dialog + dialog.clear() + return result + class AsyncScrollArea(ui.scroll_area, ABC): """Async ScrollArea that inherits from ui.scrol_area for perfect typing""" @@ -77,3 +140,18 @@ class AsyncScrollArea(ui.scroll_area, ABC): instance = cls() await instance.build(*args, **kwargs) return instance + + @classmethod + async def as_dialog(cls, *args, **kwargs) -> Any: + """Create element inside a dialog and await its result""" + dialog = ui.dialog() + dialog.open() + + with dialog, ui.card(): + instance = cls() + instance._dialog = dialog # pyright: ignore[reportAttributeAccessIssue] + await instance.build(*args, **kwargs) + + result = await dialog + dialog.clear() + return result