diff --git a/solara/lab/components/confirmation_dialog.py b/solara/lab/components/confirmation_dialog.py index e1a0e83c3..d0b3d5b3b 100644 --- a/solara/lab/components/confirmation_dialog.py +++ b/solara/lab/components/confirmation_dialog.py @@ -1,15 +1,53 @@ -from typing import Callable, List, Union +from typing import Callable, List, Union, overload import reacton.ipyvuetify as v import solara +@overload +def ConfirmationDialog( + open: solara.Reactive[bool], + *, + on_close: Union[None, Callable[[], None]] = None, + content: Union[str, solara.Element] = "", + title: str = "Confirm action", + ok: Union[str, solara.Element] = "OK", + on_ok: Callable[[], None] = lambda: None, + cancel: Union[str, solara.Element] = "Cancel", + on_cancel: Callable[[], None] = lambda: None, + children: List[solara.Element] = [], + max_width: Union[int, str] = 500, + persistent: bool = False, +): + ... + + +# when open is a boolean, on_close should be given, otherwise a dialog can never be closed +# TODO: copy this pattern to many other components +@overload +def ConfirmationDialog( + open: bool, + *, + on_close: Callable[[], None], + content: Union[str, solara.Element] = "", + title: str = "Confirm action", + ok: Union[str, solara.Element] = "OK", + on_ok: Callable[[], None] = lambda: None, + cancel: Union[str, solara.Element] = "Cancel", + on_cancel: Callable[[], None] = lambda: None, + children: List[solara.Element] = [], + max_width: Union[int, str] = 500, + persistent: bool = False, +): + ... + + @solara.component def ConfirmationDialog( open: Union[solara.Reactive[bool], bool], *, - on_open: Callable[[bool], None] = lambda open: None, + on_close: Union[None, Callable[[], None]] = None, content: Union[str, solara.Element] = "", title: str = "Confirm action", ok: Union[str, solara.Element] = "OK", @@ -35,6 +73,7 @@ def ConfirmationDialog( open_delete_confirmation = solara.reactive(False) def delete_user(): + # put your code to perform the action here print("User being deleted...") @solara.component @@ -60,6 +99,12 @@ def Page(): * `persistent`: When False (the default), clicking outside of the element or pressing esc key will trigger cancel. """ + + def on_open(open_value): + if not open_value: + if on_close: + on_close() + open_reactive = solara.use_reactive(open, on_open) del open diff --git a/solara/website/pages/api/confirmation_dialog.py b/solara/website/pages/api/confirmation_dialog.py index 18e03ef24..3d9c67fc4 100644 --- a/solara/website/pages/api/confirmation_dialog.py +++ b/solara/website/pages/api/confirmation_dialog.py @@ -11,45 +11,41 @@ title = "ConfirmationDialog" users = solara.reactive("Alice Bob Cindy Dirk Eve Fred".split()) -user_to_be_deleted: solara.Reactive[Union[str, None]] = solara.reactive(users.value[0]) -is_open = solara.reactive(False) +user_to_be_deleted: solara.Reactive[Union[str, None]] = solara.reactive(None) -def confirm_delete(): - if user_to_be_deleted.value: - is_open.set(True) +def ask_to_delete_user(user): + user_to_be_deleted.value = user + + +def clear_user_to_be_deleted(): + user_to_be_deleted.value = None def delete_user(): - if user_to_be_deleted.value: - users.set([u for u in users.value if u != user_to_be_deleted.value]) - if users.value: - user_to_be_deleted.set(users.value[0]) - else: - user_to_be_deleted.set(None) + users.set([u for u in users.value if u != user_to_be_deleted.value]) + clear_user_to_be_deleted() @solara.component def Page(): - """Create a list of users, a dropdown to select one, and a button to delete the - selected user. A confirmation dialog will pop up first before deletion.""" + """Create a list of users with a button to delete them. + + A confirmation dialog will pop up first before deletion.""" solara.Markdown("#### Users:") - with solara.Column(): + with solara.Column(style={"max-width": "300px"}): for user in users.value: - bgcolor = user.lower()[0] * 3 - solara.Text(user, style=f"width: 300px; background-color: #{bgcolor}; padding: 10px;") + with solara.Row(style={"align-items": "center"}): + solara.Text(user) + solara.v.Spacer() + solara.Button(icon_name="mdi-delete", on_click=lambda user=user: ask_to_delete_user(user), icon=True) if not users.value: solara.Text("(no users left)") - solara.Select(label="User to be deleted:", value=user_to_be_deleted, values=users.value, style="max-width: 400px;") - solara.Button( - on_click=confirm_delete, - label=(f"Delete user: {user_to_be_deleted.value}" if user_to_be_deleted.value else "Delete user"), - style="max-width: 400px;", - disabled=not users.value, - ) + with ConfirmationDialog( - is_open, + user_to_be_deleted.value is not None, on_ok=delete_user, + on_close=clear_user_to_be_deleted, ok="Ok, Delete", title="Delete user", ): diff --git a/tests/unit/confirmation_dialog_test.py b/tests/unit/confirmation_dialog_test.py index 895dd4446..13fd869e1 100644 --- a/tests/unit/confirmation_dialog_test.py +++ b/tests/unit/confirmation_dialog_test.py @@ -9,28 +9,28 @@ def test_confirmation_dialog_ok(): is_open = solara.reactive(True) on_ok = MagicMock() - on_open = MagicMock() - el = ConfirmationDialog(is_open, on_ok=on_ok, on_open=on_open, content="Hello") + on_close = MagicMock() + el = ConfirmationDialog(is_open, on_ok=on_ok, on_close=on_close, content="Hello") _, rc = solara.render(el, handle_error=False) buttons = rc.find(vw.Btn) assert len(buttons) == 2 buttons[1].widget.click() assert on_ok.call_count == 1 # was OK button clicked? - assert on_open.call_count == 1 # always triggered + assert on_close.call_count == 1 # always triggered assert not is_open.value # is dialog closed? def test_confirmation_dialog_cancel(): is_open = solara.reactive(True) on_ok = MagicMock() - on_open = MagicMock() - el = ConfirmationDialog(is_open, on_ok=on_ok, on_open=on_open, content="Hello") + on_close = MagicMock() + el = ConfirmationDialog(is_open, on_ok=on_ok, on_close=on_close, content="Hello") _, rc = solara.render(el, handle_error=False) buttons = rc.find(vw.Btn) assert len(buttons) == 2 buttons[0].widget.click() assert on_ok.call_count == 0 # on_ok action should not have been executed - assert on_open.call_count == 1 # always triggered + assert on_close.call_count == 1 # always triggered assert not is_open.value # is dialog closed? @@ -39,8 +39,8 @@ def test_confirm_external_close(): is_open = solara.reactive(True) on_ok = MagicMock() on_cancel = MagicMock() - on_open = MagicMock() - el = ConfirmationDialog(is_open, on_ok=on_ok, on_cancel=on_cancel, on_open=on_open, content="Hello") + on_close = MagicMock() + el = ConfirmationDialog(is_open, on_ok=on_ok, on_cancel=on_cancel, on_close=on_close, content="Hello") _, rc = solara.render(el, handle_error=False) dialog = rc.find(vw.Dialog)[0].widget assert dialog.v_model @@ -48,7 +48,7 @@ def test_confirm_external_close(): assert not is_open.value # is dialog closed? assert on_ok.call_count == 0 # on_ok action should not have been executed assert on_cancel.call_count == 1 # on_cancel action should not have been executed - assert on_open.call_count == 1 # always triggered + assert on_close.call_count == 1 # always triggered def test_confirmation_dialog_custom_button_no_onclick():