table customization

This commit is contained in:
John Lancaster
2026-09-01 22:54:56 -05:00
parent be579c347e
commit bf11b7865d
5 changed files with 467 additions and 4 deletions
@@ -0,0 +1,158 @@
#!/usr/bin/env -S uv run --script
# /// script
# dependencies = [
# "nicegui==3.16.0",
# ]
# ///
from nicegui import ui
type TableValue = str | int | float
type TableRow = dict[str, TableValue]
STATUS_COLORS = {
"Ready": "positive",
"Low": "warning",
"Backorder": "negative",
}
COLUMNS = [
{
"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' : ''",
},
{
"name": "price",
"label": "Unit price",
"field": "price",
"sortable": True,
"align": "right",
"headerStyle": "width: 8rem",
"style": "width: 8rem",
":format": "value => `$${value.toFixed(2)}`",
},
{
"name": "status",
"label": "Status",
"field": "status",
"sortable": True,
"align": "center",
"headerStyle": "width: 8rem",
"style": "width: 8rem",
"colorByValue": STATUS_COLORS,
},
]
ROWS: list[TableRow] = [
{"id": 101, "name": "Desk lamp", "category": "Lighting", "stock": 7, "price": 42.5, "status": "Low"},
{"id": 102, "name": "Task chair", "category": "Seating", "stock": 18, "price": 289.0, "status": "Ready"},
{"id": 103, "name": "Monitor arm", "category": "Hardware", "stock": 0, "price": 119.95, "status": "Backorder"},
{"id": 104, "name": "Cable tray", "category": "Hardware", "stock": 34, "price": 31.25, "status": "Ready"},
{"id": 105, "name": "Side table", "category": "Furniture", "stock": 9, "price": 164.5, "status": "Low"},
{"id": 106, "name": "Floor light", "category": "Lighting", "stock": 15, "price": 98.0, "status": "Ready"},
]
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().classes("items-center gap-2"):
ui.icon("inventory_2", size="sm").classes("text-primary")
ui.label("Inventory").classes("text-xl font-medium")
ui.badge(str(len(ROWS)), color="grey-3").props("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().props(':label="props.value" :color="props.col.colorByValue[props.value] ?? \'grey\'" outline')
with table.add_slot("no-data"), ui.row().classes("w-full items-center 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:
visible = set(names)
for column in optional_columns:
hidden = column["name"] not in visible
column["classes"] = "hidden" if hidden else ""
column["headerClasses"] = "hidden" if hidden else "text-grey-8"
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,
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().classes("w-full items-center gap-4 p-4"):
render_table()
ui.run(port=8888, reload=True)