Coverage for src / lilbee / cli / tui / widgets / confirm_dialog.py: 100%
35 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-05-15 20:55 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-05-15 20:55 +0000
1"""Reusable confirmation modal dialog."""
3from __future__ import annotations
5from typing import ClassVar
7from textual import events
8from textual.app import ComposeResult
9from textual.binding import Binding, BindingType
10from textual.containers import Center, Horizontal, Vertical
11from textual.screen import ModalScreen
12from textual.widgets import Label, Static
15class _ConfirmPill(Static, can_focus=True):
16 """Pill-styled clickable label used as Yes/No in :class:`ConfirmDialog`."""
18 def __init__(self, label: str, *, dialog_id: str) -> None:
19 super().__init__(label, id=dialog_id, classes="confirm-pill")
20 self._dialog_id = dialog_id
22 def on_click(self, event: events.Click) -> None:
23 event.stop()
24 screen = self.screen
25 if isinstance(screen, ConfirmDialog):
26 screen.dismiss(self._dialog_id == "confirm-yes")
29class ConfirmDialog(ModalScreen[bool]):
30 """Modal yes/no dialog that returns True (confirmed) or False (cancelled)."""
32 CSS_PATH = "confirm_dialog.tcss"
34 BINDINGS: ClassVar[list[BindingType]] = [
35 Binding("y", "confirm", "Yes", show=True),
36 Binding("enter", "confirm", "Confirm", show=False),
37 Binding("n", "cancel", "No", show=True),
38 Binding("escape", "cancel", "Cancel", show=False),
39 ]
41 def __init__(self, title: str, message: str) -> None:
42 super().__init__()
43 self._title = title
44 self._message = message
46 def compose(self) -> ComposeResult:
47 with Vertical():
48 yield Static(self._title, id="confirm-title")
49 yield Label(self._message, id="confirm-message")
50 with Center(), Horizontal(id="confirm-buttons"):
51 yield _ConfirmPill("Yes (y)", dialog_id="confirm-yes")
52 yield _ConfirmPill("No (n)", dialog_id="confirm-no")
54 def action_confirm(self) -> None:
55 self.dismiss(True)
57 def action_cancel(self) -> None:
58 self.dismiss(False)