{% load brickwork_components brickwork_icons i18n %} {% comment %} Data table: STRUCTURE ONLY (BR-BW-TPL-005, never virtualisation/AG-Grid). It renders columns with server-side sortable headers, rows with STABLE per-row ids (BR-BW-HTMX-005, so a consumer's hx-target can address a row), an empty state at exactly zero rows (STA-001), and a loading skeleton (STA-004). Sort/filter/ paginate are server-driven: the view computes the order and passes the current sort; a header link carries the sort param, no client-side sorting. Two shapes, selected by ``variant``: - ``variant="records"`` (default): a columnar table of same-shaped records. columns: a list of dicts: {label, sortable(bool), sort_key(str)}. For a SORTABLE column supply ``sortable=True`` and ``sort_key`` (the base ascending order key, e.g. "name"); the template derives the descending key ("-name") and the next-click toggle itself from ``sort_key`` + the shared ``current_sort``, so a consumer does NOT compute ``sort_key_desc`` / ``next_sort`` (#23). Those two keys may still be passed to override the convention, but are optional. rows: a list of dicts: {id, cells: [rendered strings], url(optional), selected(optional bool)} A row with a ``url`` becomes a clickable row: its first cell is an anchor to ``url`` (the whole row is keyboard-reachable via that link), so a documented per-row link actually renders (#10). A row with truthy ``selected`` carries the ``bw-data-table__row--selected`` class (the selected-row styling hook, e.g. the row a bulk action or detail pane refers to). - ``variant="definition"``: a key/value fact table for ONE entity (a detail screen's "facts about this thing", the
-shaped case, #18). No column headers, no sorting. rows: a list of dicts: {label, value} (each row is one fact). ``columns`` is ignored. The label is a row header (````), so the table is semantically a definition table, not a fake two-column grid. Required context: table_id: the stable id for the table container (an hx-swap target). rows: as above, by variant. columns: required for ``records``, ignored for ``definition``. Optional: variant ("records" default | "definition"), loading (bool), current_sort (str, e.g. "name" or "-name"), empty_heading, empty_body, querystring, selectable (bool, records only, see below), scroll_container / sticky_header (bool, either name, see below), responsive ("scroll" default | "stack", see below). --- Bulk selection (brickwork#54, records variant only) -------------------- selectable=True adds a leading checkbox column. THE SELECTION CONTRACT (the load-bearing part, #47: "must live in brickwork or every consumer reinvents it"): every row checkbox is a plain native ````. It carries NO form of its own (BR-BW-TPL-005: structure only); it relies on sitting inside a ``
`` THE CONSUMER OWNS (e.g. wrapping this include, or wrapping the whole patterns/list.html list_body block). The no-JS floor is a normal multi-value POST: a consumer's view reads the selection with ``request.POST.getlist("selected")``, exactly what a plain HTML checkbox group has always given you, zero JS required. A "select all" checkbox appears in the header; it carries an accessible name and gets JS-progressive-enhanced (below) but works as a plain unchecked box with no JS (it does not natively check every row without Alpine, which is why the bulk-actions bar - see _bulk_actions_bar.html - documents pairing it with a "select all" fallback link for the true no-JS case). Every checkbox has a visually-hidden accessible label ("Select row" / "Select all rows"), never a bare unlabelled control (WCAG 4.1.2). Enhanced (optional, opt-in by wrapping the table AND the bulk-actions bar in one ``x-data="bwTableSelection()"`` root, see components/_bulk_actions_bar.html's docstring for the full wiring): the ``bwTableSelection`` Alpine component reads the checkboxes as the SOLE source of truth (it holds no separate selection store that could desync from what will actually submit), toggles the header checkbox's indeterminate state, and reveals the bulk-actions bar with a live selected-count. scroll_container / sticky_header (CSS only, either name works, both wire the same treatment): sticky_header=True pins the (position: sticky) while the wrap scrolls, so long lists keep their column headers in view. responsive ("scroll" default | "stack"): "scroll" is the existing horizontal overflow (no change). "stack" additionally stamps each with data-label="" (read from ``columns`` by position via the ``list_item`` filter, BR-BW-TPL-005: no data reshaping, just a positional read) so a narrow-viewport CSS rule can render each row as a labelled card. {% endcomment %}