Compare commits

..

4 commits

Author SHA1 Message Date
bronsen
8999f5a52f feat(ui): Layout our search results more correctly 2025-12-04 19:28:14 +01:00
bronsen
4fbee9dfee feat(ui): Add Datatable layout 2025-12-04 19:25:18 +01:00
bronsen
65ee1e37c6 feat(ui): Display header, footer, and searchbar 2025-12-04 13:21:49 +01:00
bronsen
de8c467d7f docs: ✏️ Add link "textual" in readme 2025-12-04 07:01:32 +01:00
3 changed files with 115 additions and 0 deletions

View file

@ -4,3 +4,4 @@ This is a little TUI based on [Textual] for entering new Teilchen and for
searching for existing Teilchen. searching for existing Teilchen.
[Textual]: https://textual.textualize.io/

View file

@ -0,0 +1,8 @@
"""
This file exists so the directory becomes a package.
"""
from .main import main
if __name__ == "__main__":
main()

View file

@ -0,0 +1,106 @@
"""
This is where the application is implemented.
"""
from typing import Literal, final, override
from textual.app import App, ComposeResult
from textual.containers import HorizontalGroup
from textual.screen import Screen
from textual.widget import Widget
from textual.widgets import (
Button,
DataTable,
Footer,
Header,
Input,
Static,
)
@final
class SearchBar(Static):
DEFAULT_CSS = """
#teilchen-input {
width: 4fr;
}
#button-search, #button-add {
width: 1fr;
}
"""
@override
def compose(self) -> ComposeResult:
with HorizontalGroup(id="search-bar-widget"):
yield Input(
placeholder="Enter Teilchen information: name, description, #tags",
tooltip=(
"This is a free-form field: Enter a name and "
"description any way you like. You should use #hashtags for any "
"meta information."
),
id="teilchen-input",
type="text",
)
yield Button(
"Add", variant="success", classes="search-bar-buttons", id="button-add"
)
yield Button(
"Search",
variant="default",
classes="search-bar-buttons",
id="button-search",
)
TeilchenDatum = tuple[int, str, str, str, str]
TeilchenHeader = tuple[
Literal["pk"],
Literal["Name"],
Literal["Description"],
Literal["Number"],
Literal["Tags"],
]
FAKE_DATA: list[TeilchenHeader | TeilchenDatum] = [
("pk", "Name", "Description", "Number", "Tags"),
(0, "Name0", "Description0", "9000", "#tag0 #tag00 #tag000"),
(1, "Name1", "Description1", "9001", "#tag1 #tag11 #tag111"),
(2, "Name2", "Description2", "9002", "#tag2 #tag22 #tag222"),
(3, "Name3", "Description3", "9003", "#tag3 #tag33 #tag333"),
(4, "Name4", "Description4", "9004", "#tag4 #tag44 #tag444"),
(5, "Name5", "Description5", "9005", "#tag5 #tag55 #tag555"),
]
@final
class SearchResults(Widget):
@override
def compose(self) -> ComposeResult:
yield DataTable(id="table-search-result", cursor_type="row", zebra_stripes=True)
def on_mount(self) -> None:
table: DataTable[None] = self.query_one(DataTable[None])
_ = table.add_columns(*FAKE_DATA[0]) # pyright: ignore[reportArgumentType]
_ = table.add_rows(FAKE_DATA[1:]) # pyright: ignore[reportArgumentType]
class AddInventoryScreen(Screen[None]):
@override
def compose(self) -> ComposeResult:
yield Header()
yield SearchBar()
yield SearchResults()
yield Footer()
@final
class SammlerApp(App[None]):
def on_mount(self) -> None:
_ = self.push_screen(AddInventoryScreen())
def main() -> None:
app = SammlerApp()
app.run()