table customization
This commit is contained in:
@@ -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)
|
||||
Reference in New Issue
Block a user