{#- chirp-ui: Table component Responsive table with header configuration and row helper. Usage: from "chirpui/table.html" import table, row Slot-based (complex rows): call table(headers=["Name", "Email", "Role"]) row("Alice", "alice@example.com", "Admin") row("Bob", "bob@example.com", "User") end Data-driven (alignment flows to all cells automatically): table(headers=["Name", "Status", "Count"], rows=[("Alice", "Active", "42"), ("Bob", "Idle", "7")], align=["left", "center", "right"]) Per-column alignment (slot-based, auto-inherited): call table(headers=["Name", "Status", "Count"], align=["left", "center", "right"]) row("Alice", "Active", "42") end Column widths (CSS values or proportions): call table(headers=["Name", "Status"], widths=["2fr", "1fr"]) Slots: caption (table caption), row_actions (header cell for actions column). Sortable headers: call table(headers=["Name", "Email"], sortable=true, sort_url="/users", hx_target="#user-table") row(user.name, user.email) end Row with actions: pass row_actions(...) as last cell when actions_header=true Sticky header: call table(headers=[...], sticky_header=true) Sorting note (legacy): table(sortable=true, sort_url=...) emits a plain `hx-get`-on-`` sort using `header|lower` as the sort key — kept byte-identical for backward compatibility. For interactive grids with aria-sort, stable sort keys, server sort state, row selection, sticky first column, and HTMX load-more, use `data_grid` (chirpui/data_grid.html), which is backed by the typed `chirp_ui.grid_state` helper. Additive selection/sticky params (additive; default off = byte-identical): table(rows=..., selectable=true, row_id="id", selection=selection_state, sticky_first_col=true) `selection` is a chirp_ui.grid_state.SelectionState; `row_id` is the dict key (or index) used to resolve each row's stable id for the checkbox value. -#} {% def table(headers=none, rows=none, sortable=false, sort_url=none, hx_target=none, striped=false, sticky_header=false, actions_header=false, align=none, widths=none, compact=false, selectable=false, select_name="ids", selection=none, row_id=none, sticky_first_col=false, cls="") %}
{% if widths %} {% if selectable %}{% end %} {% for w in widths %} {% end %} {% if actions_header %}{% end %} {% end %} {% if headers %} {% if selectable %} {% end %} {% for header in headers %} {% set col_align = align[loop.index0] if align and loop.index0 < align | length else "" %} {% end %} {% if actions_header %} {% end %} {% end %} {% if rows %} {% for row_data in rows %} {% set _rid = (row_data[row_id] | string) if row_id is not none else (loop.index0 | string) %} {% if selectable %} {% end %} {% for cell in row_data %} {% set col_align = align[loop.index0] if align and loop.index0 < align | length else "" %} {% end %} {% end %} {% else %} {# @provides _table_align — consumed by: row #} {% provide _table_align = align %} {% slot %} {% end %} {% end %}
{% slot caption %}
{{ header }}
{{ cell }}
{% end %} {% def row(*cells) %} {# @consumes _table_align from: data_table — falls back to none #} {% set _align = consume("_table_align") %} {% for cell in cells %} {% set col_align = _align[loop.index0] if _align and loop.index0 < _align | length else "" %} {{ cell }} {% end %} {% end %} {# DEPRECATED: Use row() inside a table(align=...) call instead. Will be removed in 0.3.0. #} {% def aligned_row(cells, align) %} {% for cell in cells %} {% set col_align = align[loop.index0] if align and loop.index0 < align | length else "" %} {{ cell }} {% end %} {% end %} {% def table_empty(message="No data available", icon="◇") %} {{ message }} {% end %}