Repository: Textualize/textual

Issue:
Button.watch_variant does not remove old_variant class
When changing the variant of a Button widget, the new variant gets added to the classes, but the old one is not removed.
Minimal example:
```python
from textual.app import App, ComposeResult
from textual.widgets import Button


class MyApp(App):
	def __init__(self):
		super().__init__()
		self.button = Button()
		self.button.variant = "warning"
		self.button.variant = "default"

	def compose(self) -> ComposeResult:
		yield self.button

MyApp().run()
```
This still displays the button in "warning" style as it has both classes.
I believe the problem is the underscore instead of a dash in the watch_variant method.
https://github.com/Textualize/textual/blob/main/src/textual/widgets/_button.py#L218

textual version 0.2.1
terminal: xterm-256color on linux ubuntu 22.04 (Regolith)


Discussion:
Good catch. Slightly more interactive display of this, allowing watching the classes develop:

```python
from textual.app import App, ComposeResult
from textual.widgets import Button, Static


class MyApp( App[None] ):

    def compose( self ) -> ComposeResult:
        yield Button( "Push Me" )
        yield Static( "Push the above!", id="classes" )

    def on_button_pressed( self, event: Button.Pressed ) -> None:
        event.button.variant = (
            "warning" if event.button.variant == "default" else "default"
        )
        self.query_one( "#classes", Static ).update(
            ", ".join( event.button.classes )
        )

if __name__ == "__main__":
    MyApp().run()
```

Resolving patch:
diff --git a/src/textual/widgets/_button.py b/src/textual/widgets/_button.py
--- a/src/textual/widgets/_button.py
+++ b/src/textual/widgets/_button.py
@@ -36,12 +36,12 @@ class Button(Static, can_focus=True):
         min-width: 16;
         height: 3;
         background: $panel;
-        color: $text;      
-        border: none;        
+        color: $text;
+        border: none;
         border-top: tall $panel-lighten-2;
         border-bottom: tall $panel-darken-3;
-        content-align: center middle;        
-        text-style: bold;      
+        content-align: center middle;
+        text-style: bold;
     }
 
     Button.-disabled {
@@ -50,7 +50,7 @@ class Button(Static, can_focus=True):
     }
 
     Button:focus {
-        text-style: bold reverse;        
+        text-style: bold reverse;
     }
 
     Button:hover {
@@ -62,7 +62,7 @@ class Button(Static, can_focus=True):
     Button.-active {
         background: $panel;
         border-bottom: tall $panel-lighten-2;
-        border-top: tall $panel-darken-2;                
+        border-top: tall $panel-darken-2;
         tint: $background 30%;
     }
 
@@ -72,7 +72,7 @@ class Button(Static, can_focus=True):
         color: $text;
         border-top: tall $primary-lighten-3;
         border-bottom: tall $primary-darken-3;
-      
+
     }
 
     Button.-primary:hover {
@@ -107,7 +107,7 @@ class Button(Static, can_focus=True):
         border-top: tall $success-darken-2;
     }
 
-   
+
     /* Warning variant */
     Button.-warning {
         background: $warning;
@@ -119,7 +119,7 @@ class Button(Static, can_focus=True):
     Button.-warning:hover {
         background: $warning-darken-2;
         color: $text;
-       
+
     }
 
     Button.-warning.-active {
@@ -127,7 +127,7 @@ class Button(Static, can_focus=True):
         border-bottom: tall $warning-lighten-2;
         border-top: tall $warning-darken-2;
     }
-   
+
 
     /* Error variant */
     Button.-error {
@@ -135,7 +135,7 @@ class Button(Static, can_focus=True):
         color: $text;
         border-top: tall $error-lighten-2;
         border-bottom: tall $error-darken-3;
-     
+
     }
 
     Button.-error:hover {
@@ -215,7 +215,7 @@ def validate_variant(self, variant: str) -> str:
         return variant
 
     def watch_variant(self, old_variant: str, variant: str):
-        self.remove_class(f"_{old_variant}")
+        self.remove_class(f"-{old_variant}")
         self.add_class(f"-{variant}")
 
     def watch_disabled(self, disabled: bool) -> None:


Test patch: