mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Add success/warning/error button variants
This commit is contained in:
@@ -1,12 +1,4 @@
|
||||
#foo {
|
||||
text-style: underline;
|
||||
background: rebeccapurple;
|
||||
}
|
||||
|
||||
*:focus {
|
||||
tint: yellow 50%;
|
||||
}
|
||||
|
||||
#foo:hover {
|
||||
background: greenyellow;
|
||||
Button {
|
||||
padding-left: 1;
|
||||
padding-right: 1;
|
||||
}
|
||||
|
||||
@@ -1,21 +1,30 @@
|
||||
from textual import layout, events
|
||||
from textual.app import App, ComposeResult
|
||||
|
||||
from textual.widgets import Button
|
||||
from textual import layout
|
||||
|
||||
|
||||
class ButtonsApp(App[str]):
|
||||
def compose(self) -> ComposeResult:
|
||||
yield layout.Vertical(
|
||||
Button("foo", id="foo"),
|
||||
Button("bar", id="bar"),
|
||||
Button("baz", id="baz"),
|
||||
Button("default", id="foo"),
|
||||
Button.success("success", id="bar"),
|
||||
Button.warning("warning", id="baz"),
|
||||
Button.error("error", id="baz"),
|
||||
)
|
||||
|
||||
def handle_pressed(self, event: Button.Pressed) -> None:
|
||||
self.app.bell()
|
||||
self.log("pressed", event.button.id)
|
||||
self.exit(event.button.id)
|
||||
print("pressed", event.button.id)
|
||||
|
||||
async def on_key(self, event: events.Key) -> None:
|
||||
await self.dispatch_key(event)
|
||||
|
||||
def key_a(self):
|
||||
print(f"text-success: {self.stylesheet.variables.get('text-success')}")
|
||||
print(
|
||||
f"text-success-darken-1: {self.stylesheet.variables.get('text-success-darken-1')}"
|
||||
)
|
||||
self.dark = not self.dark
|
||||
|
||||
|
||||
app = ButtonsApp(
|
||||
|
||||
@@ -416,7 +416,6 @@ class MessagePump:
|
||||
Args:
|
||||
event (events.Key): A key event.
|
||||
"""
|
||||
|
||||
key_method = getattr(self, f"key_{event.key}", None)
|
||||
if key_method is not None:
|
||||
if await invoke(key_method, event):
|
||||
|
||||
@@ -1,51 +1,130 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import cast
|
||||
from typing import cast, Literal
|
||||
|
||||
from rich.console import RenderableType
|
||||
from rich.style import Style
|
||||
from rich.text import Text, TextType
|
||||
|
||||
from .. import events
|
||||
from ..css._error_tools import friendly_list
|
||||
from ..message import Message
|
||||
from ..reactive import Reactive
|
||||
from ..widget import Widget
|
||||
|
||||
ButtonVariant = Literal["default", "success", "warning", "error"]
|
||||
_VALID_BUTTON_VARIANTS = {"default", "success", "warning", "error"}
|
||||
|
||||
|
||||
class InvalidButtonVariant(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Button(Widget, can_focus=True):
|
||||
"""A simple clickable button."""
|
||||
|
||||
CSS = """
|
||||
|
||||
|
||||
Button {
|
||||
width: auto;
|
||||
height: 3;
|
||||
|
||||
background: $primary;
|
||||
color: $text-primary;
|
||||
content-align: center middle;
|
||||
border: tall $primary-lighten-3;
|
||||
|
||||
|
||||
content-align: center middle;
|
||||
margin: 1 0;
|
||||
align: center middle;
|
||||
text-style: bold;
|
||||
}
|
||||
|
||||
Button:hover {
|
||||
background: $primary-darken-2;
|
||||
color: $text-primary-darken-2;
|
||||
border: tall $primary-lighten-1;
|
||||
}
|
||||
|
||||
.-dark-mode Button {
|
||||
border: tall white $primary-lighten-2;
|
||||
color: $primary-lighten-2;
|
||||
background: $background;
|
||||
color: $primary-lighten-2;
|
||||
border: tall white $primary-lighten-2;
|
||||
}
|
||||
|
||||
.-dark-mode Button:hover {
|
||||
background: $surface;
|
||||
}
|
||||
|
||||
/* Success variant */
|
||||
Button.-success {
|
||||
background: $success;
|
||||
color: $text-success;
|
||||
border: tall $success-lighten-3;
|
||||
}
|
||||
|
||||
Button:hover {
|
||||
background:$primary-darken-2;
|
||||
color: $text-primary-darken-2;
|
||||
border: tall $primary-lighten-1;
|
||||
Button.-success:hover {
|
||||
background: $success-darken-1;
|
||||
color: $text-success-darken-1;
|
||||
border: tall $success-lighten-2; /* TODO: This shouldn't be necessary?? */
|
||||
}
|
||||
|
||||
.-dark-mode Button.-success {
|
||||
background: $success;
|
||||
color: $text-success;
|
||||
border: tall $success-lighten-3;
|
||||
}
|
||||
|
||||
.-dark-mode Button.-success:hover {
|
||||
background: $success-darken-1;
|
||||
color: $text-success-darken-1;
|
||||
border: tall $success-lighten-3;
|
||||
}
|
||||
|
||||
/* Warning variant */
|
||||
Button.-warning {
|
||||
background: $warning;
|
||||
color: $text-warning;
|
||||
border: tall $warning-lighten-3;
|
||||
}
|
||||
|
||||
Button.-warning:hover {
|
||||
background: $warning-darken-1;
|
||||
color: $text-warning-darken-1;
|
||||
border: tall $warning-lighten-3;
|
||||
}
|
||||
|
||||
.-dark-mode Button.-warning {
|
||||
background: $warning;
|
||||
color: $text-warning;
|
||||
border: tall $warning-lighten-3;
|
||||
}
|
||||
|
||||
.-dark-mode Button.-warning:hover {
|
||||
background: $warning-darken-1;
|
||||
color: $text-warning-darken-1;
|
||||
border: tall $warning-lighten-3;
|
||||
}
|
||||
|
||||
Button.-error {
|
||||
background: $error;
|
||||
color: $text-error;
|
||||
border: tall $error-lighten-3;
|
||||
}
|
||||
|
||||
Button.-error:hover {
|
||||
background: $error-darken-1;
|
||||
color: $text-error-darken-1;
|
||||
border: tall $error-lighten-3;
|
||||
}
|
||||
|
||||
.-dark-mode Button.-error {
|
||||
background: $error;
|
||||
color: $text-error;
|
||||
border: tall $error-lighten-3;
|
||||
}
|
||||
|
||||
.-dark-mode Button.-error:hover {
|
||||
background: $error-darken-1;
|
||||
color: $text-error-darken-1;
|
||||
border: tall $error-lighten-3;
|
||||
}
|
||||
|
||||
App.-show-focus Button:focus {
|
||||
@@ -63,11 +142,22 @@ class Button(Widget, can_focus=True):
|
||||
self,
|
||||
label: TextType | None = None,
|
||||
disabled: bool = False,
|
||||
variant: ButtonVariant = "default",
|
||||
*,
|
||||
name: str | None = None,
|
||||
id: str | None = None,
|
||||
classes: str | None = None,
|
||||
):
|
||||
"""Create a Button widget.
|
||||
|
||||
Args:
|
||||
label (str): The text that appears within the button.
|
||||
disabled (bool): Whether the button is disabled or not.
|
||||
variant (ButtonVariant): The variant of the button.
|
||||
name: The name of the button.
|
||||
id: The ID of the button in the DOM.
|
||||
classes: The CSS classes of the button.
|
||||
"""
|
||||
super().__init__(name=name, id=id, classes=classes)
|
||||
|
||||
if label is None:
|
||||
@@ -79,6 +169,15 @@ class Button(Widget, can_focus=True):
|
||||
if disabled:
|
||||
self.add_class("-disabled")
|
||||
|
||||
if variant in _VALID_BUTTON_VARIANTS:
|
||||
if variant != "default":
|
||||
self.add_class(f"-{variant}")
|
||||
|
||||
else:
|
||||
raise InvalidButtonVariant(
|
||||
f"Valid button variants are {friendly_list(_VALID_BUTTON_VARIANTS)}"
|
||||
)
|
||||
|
||||
label: Reactive[RenderableType] = Reactive("")
|
||||
|
||||
def validate_label(self, label: RenderableType) -> RenderableType:
|
||||
@@ -100,3 +199,93 @@ class Button(Widget, can_focus=True):
|
||||
async def on_key(self, event: events.Key) -> None:
|
||||
if event.key == "enter" and not self.disabled:
|
||||
await self.emit(Button.Pressed(self))
|
||||
|
||||
@staticmethod
|
||||
def success(
|
||||
label: TextType | None = None,
|
||||
disabled: bool = False,
|
||||
*,
|
||||
name: str | None = None,
|
||||
id: str | None = None,
|
||||
classes: str | None = None,
|
||||
) -> Button:
|
||||
"""Utility constructor for creating a success Button variant.
|
||||
|
||||
Args:
|
||||
label (str): The text that appears within the button.
|
||||
disabled (bool): Whether the button is disabled or not.
|
||||
name: The name of the button.
|
||||
id: The ID of the button in the DOM.
|
||||
classes: The CSS classes of the button.
|
||||
|
||||
Returns:
|
||||
Button: A Button widget of the 'success' variant.
|
||||
"""
|
||||
return Button(
|
||||
label=label,
|
||||
disabled=disabled,
|
||||
variant="success",
|
||||
name=name,
|
||||
id=id,
|
||||
classes=classes,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def warning(
|
||||
label: TextType | None = None,
|
||||
disabled: bool = False,
|
||||
*,
|
||||
name: str | None = None,
|
||||
id: str | None = None,
|
||||
classes: str | None = None,
|
||||
) -> Button:
|
||||
"""Utility constructor for creating a warning Button variant.
|
||||
|
||||
Args:
|
||||
label (str): The text that appears within the button.
|
||||
disabled (bool): Whether the button is disabled or not.
|
||||
name: The name of the button.
|
||||
id: The ID of the button in the DOM.
|
||||
classes: The CSS classes of the button.
|
||||
|
||||
Returns:
|
||||
Button: A Button widget of the 'warning' variant.
|
||||
"""
|
||||
return Button(
|
||||
label=label,
|
||||
disabled=disabled,
|
||||
variant="warning",
|
||||
name=name,
|
||||
id=id,
|
||||
classes=classes,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def error(
|
||||
label: TextType | None = None,
|
||||
disabled: bool = False,
|
||||
*,
|
||||
name: str | None = None,
|
||||
id: str | None = None,
|
||||
classes: str | None = None,
|
||||
) -> Button:
|
||||
"""Utility constructor for creating an error Button variant.
|
||||
|
||||
Args:
|
||||
label (str): The text that appears within the button.
|
||||
disabled (bool): Whether the button is disabled or not.
|
||||
name: The name of the button.
|
||||
id: The ID of the button in the DOM.
|
||||
classes: The CSS classes of the button.
|
||||
|
||||
Returns:
|
||||
Button: A Button widget of the 'error' variant.
|
||||
"""
|
||||
return Button(
|
||||
label=label,
|
||||
disabled=disabled,
|
||||
variant="error",
|
||||
name=name,
|
||||
id=id,
|
||||
classes=classes,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user