Files
prompts/src/personal_mcp/docs/skills/nicegui/examples/table_customization.py
T
2026-09-01 23:32:14 -05:00

254 lines
7.2 KiB
Python
Executable File

#!/usr/bin/env -S uv run --script
# /// script
# dependencies = [
# "nicegui==3.16.0",
# ]
# ///
from nicegui import events
from nicegui import ui
type TableValue = str | int | float
type TableRow = dict[str, TableValue]
STATUS_COLORS = {
"Ready": "positive",
"Low": "warning",
"Backorder": "negative",
}
COLUMNS = [
{
"name": "actions",
"label": "Actions",
"required": True,
"align": "center",
},
{
"name": "name",
"label": "Product",
"field": "name",
"required": True,
"sortable": True,
"align": "left",
"headerClasses": "bg-grey-2 text-grey-9 font-bold",
"classes": "font-medium",
"headerStyle": "width: 40%; min-width: 12rem",
"style": "width: 40%; min-width: 12rem",
},
{
"name": "category",
"label": "Category",
"field": "category",
"sortable": True,
"align": "left",
"headerStyle": "width: 9rem",
"style": "width: 9rem",
},
{
"name": "stock",
"label": "In stock",
"field": "stock",
"sortable": True,
"align": "right",
"headerStyle": "width: 7rem",
"style": "width: 7rem",
":classes": "row => row.stock < 10 ? 'text-negative font-bold' : ''",
":format": "value => value == null ? '' : `${value} units`",
},
{
"name": "price",
"label": "Unit price",
"field": "price",
"sortable": True,
"align": "right",
"headerStyle": "width: 8rem",
"style": "width: 8rem",
":format": "value => value == null ? '' : `$${value.toFixed(2)}`",
},
{
"name": "status",
"label": "Status",
"field": "status",
"sortable": True,
"align": "center",
"headerStyle": "width: 8rem",
"style": "width: 8rem",
"colorByValue": STATUS_COLORS,
},
{
"name": "updated_at",
"label": "Updated",
"field": "updated_at",
"sortable": True,
"align": "left",
"headerStyle": "width: 13rem",
"style": "width: 13rem",
":sort": "(left, right) => Date.parse(left) - Date.parse(right)",
":format": """(() => {
const formatter = new Intl.DateTimeFormat('en-US', {
dateStyle: 'medium',
timeStyle: 'short',
timeZone: 'UTC',
});
return value => {
if (!value) return '';
const timestamp = Date.parse(value);
return Number.isNaN(timestamp) ? 'Invalid date' : formatter.format(timestamp);
};
})()""",
},
]
ROWS: list[TableRow] = [
{
"id": 101,
"name": "Desk lamp",
"category": "Lighting",
"stock": 7,
"price": 42.5,
"status": "Low",
"updated_at": "2026-08-31T16:20:00Z",
},
{
"id": 102,
"name": "Task chair",
"category": "Seating",
"stock": 18,
"price": 289.0,
"status": "Ready",
"updated_at": "2026-09-01T08:45:00Z",
},
{
"id": 103,
"name": "Monitor arm",
"category": "Hardware",
"stock": 0,
"price": 119.95,
"status": "Backorder",
"updated_at": "2026-08-29T11:05:00Z",
},
{
"id": 104,
"name": "Cable tray",
"category": "Hardware",
"stock": 34,
"price": 31.25,
"status": "Ready",
"updated_at": "2026-09-01T14:30:00Z",
},
{
"id": 105,
"name": "Side table",
"category": "Furniture",
"stock": 9,
"price": 164.5,
"status": "Low",
"updated_at": "2026-08-30T19:15:00Z",
},
{
"id": 106,
"name": "Floor light",
"category": "Lighting",
"stock": 15,
"price": 98.0,
"status": "Ready",
"updated_at": "2026-09-01T10:10:00Z",
},
]
def render_table() -> ui.table:
table = ui.table(
columns=COLUMNS,
column_defaults={"headerClasses": "text-grey-8"},
rows=ROWS,
row_key="id",
pagination={
"sortBy": "stock",
"descending": True,
"rowsPerPage": 5,
},
).classes("w-full max-w-5xl")
(
table.props(
# Surface and cell layout.
"flat bordered separator=horizontal wrap-cells"
)
.props(
# Local sorting and page-size choices; zero means "all rows".
'binary-state-sort :rows-per-page-options="[5, 10, 0]"'
)
.props(
# Compact cells only below Quasar's medium breakpoint.
':dense="Quasar.Screen.lt.md"'
)
.props(
# Distinguish an empty dataset from a filter with no matches.
'no-data-label="No inventory items" no-results-label="No matching inventory items"'
)
)
with table.add_slot("top-left"), ui.row(align_items="center").classes("gap-2"):
ui.icon("inventory_2", size="sm", color="primary")
ui.label("Inventory").classes("text-xl font-medium")
ui.badge(str(len(ROWS)), color="grey-3", text_color="grey-9")
with table.add_slot("top-right"):
ui.input(placeholder="Search inventory").props("dense outlined clearable debounce=250").bind_value_to(
table,
"filter",
)
with table.add_slot("body-cell-status"), table.cell("status"):
ui.badge(outline=True).props(':label="props.value" :color="props.col.colorByValue[props.value] ?? \'grey\'"')
def open_product(event: events.GenericEventArguments) -> None:
product = next((row for row in table.rows if row[table.row_key] == event.args), None)
if product is None:
ui.notify("Product no longer exists", type="negative")
return
ui.notify(f"Opening {product['name']}")
with (
table.add_slot("body-cell-actions"),
table.cell("actions"),
ui.button(icon="open_in_new")
.props("round flat size='sm'")
.on(
"click.stop",
handler=open_product,
js_handler="() => emit(props.key)",
),
):
ui.tooltip("Open product")
with table.add_slot("no-data"), ui.row(align_items="center").classes("w-full justify-center gap-2 p-6 text-grey-7"):
ui.icon("inventory_2", size="2em").props(":name=\"props.filter ? 'filter_alt_off' : 'inventory_2'\"")
ui.element("span").props(':textContent="props.message"')
optional_columns = [column for column in table.columns if not column.get("required")]
def set_visible_columns(names: list[str]) -> None:
table.props["visible-columns"] = names
table.update()
ui.select(
{column["name"]: column["label"] for column in optional_columns},
value=[column["name"] for column in optional_columns],
label="Visible columns",
multiple=True,
clearable=True,
on_change=lambda event: set_visible_columns(event.value),
).props("outlined dense options-dense").classes("w-64")
return table
if __name__ in {"__main__", "__mp_main__"}:
with ui.column(align_items="center").classes("w-full gap-4 p-4"):
render_table()
ui.run(port=8888, reload=True)