import randomname from textual import on, work from textual.app import App, ComposeResult from textual.events import Click from textual.screen import Screen from textual.message import Message from textual.css.query import NoMatches from textual.reactive import reactive from textual.containers import ( Container, Center, Horizontal, VerticalScroll ) from textual.widgets import ( Button, Footer, Input, Label, Rule, Static ) class CardSelected(Message): def __init__(self, card_id: int) -> None: super().__init__() self.card_id = card_id class RecipeCard(Static): visible_status: bool = reactive(True) def __init__(self, recipe_data: dict = {}): super().__init__() self.recipe_name = recipe_data.get('recipe_name', "") self.card_id = recipe_data.get('card_id', 0) self.border_title = f"{self.recipe_name} - {self.card_id}" try: self.id = f"recipe-{self.card_id}" except: pass def compose(self) -> ComposeResult: yield Label("Cook Time: 10m", classes="card") yield Rule(line_style="solid", classes="rule-color") yield Label("Description", classes="recipecard-label") yield Static( "This dish is full of nonsense and this is a long text label", classes="card" ) def on_click(self, event: Click) -> None: self.post_message(CardSelected(self.id)) def watch_visible_status(self, visible: bool) -> None: if visible: print(f"Card {self.id} is visible!") self.remove_class('invisible') else: print(f"Card {self.id} is invisible!") self.add_class('invisible') class SearchPageScreen(Screen): def __init__(self) -> None: super().__init__() self.selected_card_id = None def compose(self) -> ComposeResult: with Container(id="background-container"): with Center(id="header"): yield Horizontal( Button("Back", id="button-go-back"), Input(placeholder="Search a recipe..", id="search-input"), Button("Search Recipes", id="enter-button"), id="header-components" ) with VerticalScroll(id="recipe-list"): for x in range(1, 21): yield RecipeCard( recipe_data={ "recipe_name": randomname.generate(sep=" "), "card_id": x } ) yield Footer() @on(CardSelected) def card_selected(self, message: CardSelected) -> None: card_id = message.card_id card = self.query_one(f"#{card_id}", RecipeCard) if card_id == self.selected_card_id: card.remove_class("recipe-highlighted") self.selected_card_id = None else: try: old_card = self.query_one(f"#{self.selected_card_id}", RecipeCard) old_card.remove_class("recipe-highlighted") except NoMatches: print(f"Card not fount {self.selected_card_id}") self.selected_card_id = card_id card.add_class('recipe-highlighted') @on(Input.Changed) def filter_cards(self, event: Input.Changed) -> None: query = event.value.strip().lower() for card in self.query(RecipeCard): if query in card.recipe_name: card.visible_status = True else: card.visible_status = False self.refresh() class CookbookApp(App): CSS_PATH = "cookbook.css" def __init__(self) -> None: super().__init__() self.theme = "monokai" def on_mount(self) -> None: self.push_screen(SearchPageScreen()) if __name__ == "__main__": app = CookbookApp() app.run()