Cookbook App Demo

This commit is contained in:
2025-11-15 22:05:55 -05:00
parent 9dbae9a7b2
commit 73fa621400
3 changed files with 188 additions and 0 deletions

0
__init__.py Normal file
View File

130
app.py Normal file
View File

@@ -0,0 +1,130 @@
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', "<no 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()

58
cookbook.css Normal file
View File

@@ -0,0 +1,58 @@
#background-container {
background: $background;
}
#header {
width: 100%;
background: $warning;
border: thick $warning-darken-1;
padding: 0 0;
}
#header-components {
width: auto;
height: auto;
}
#search-input {
width: 50;
}
#recipe-list {
layout: grid;
grid-size: 3;
grid-gutter: 1;
grid-rows: 15;
margin: 1 0;
}
RecipeCard {
height: 100%;
width: 100%;
border: thick $accent;
background: $primary-background;
margin: 0 1;
}
.invisible {
visibility: hidden;
}
.recipecard-label {
text-style: bold italic underline;
padding: 0 0 1 1;
}
.rule-color {
color: $accent;
}
.card {
padding: 0 2;
}
.recipe-highlighted {
background: $primary-background-lighten-2;
}