diff --git a/docs/architecture.md b/docs/architecture.md
index c9b1cc3..6633f1d 100644
--- a/docs/architecture.md
+++ b/docs/architecture.md
@@ -1217,6 +1217,29 @@ There is no theme registry, Tailwind theme, CSS asset package or icon provider a
The current strategy and limitations are documented in `docs/theming.md`.
+### Configurable datatable control layout
+
+Datatable controls support a layout option.
+
+Current modes:
+
+```text
+default
+split
+```
+
+Default layout keeps page size and column visibility controls in the top toolbar.
+
+Split layout keeps search, filters, export and global actions near the top, while moving page size, column visibility and summary below the table.
+
+Example:
+
+```twig
+{{ zhortein_datatable('users', {
+ controlsLayout: 'split'
+}) }}
+```
+
---
## 9. Action rendering layer
diff --git a/docs/table-controls.md b/docs/table-controls.md
index d293ed9..e92928a 100644
--- a/docs/table-controls.md
+++ b/docs/table-controls.md
@@ -342,6 +342,24 @@ The default fragments URL uses the bundle route.
Applications can override the fragments URL at runtime, but route prefix configuration is not implemented yet.
+## Control layout
+
+The datatable controls can use a split layout.
+
+```twig
+{{ zhortein_datatable('users', {
+ controlsLayout: 'split'
+}) }}
+```
+
+Split layout moves these controls below the table:
+
+- column visibility;
+- page size selector;
+- summary.
+
+Search, filters, export and global actions remain in the top toolbar.
+
## Related documentation
- [`stimulus-assetmapper.md`](stimulus-assetmapper.md)
diff --git a/templates/bootstrap/_bottom_controls.html.twig b/templates/bootstrap/_bottom_controls.html.twig
new file mode 100644
index 0000000..3baa04b
--- /dev/null
+++ b/templates/bootstrap/_bottom_controls.html.twig
@@ -0,0 +1,44 @@
+{% set page_size = options.pageSize is defined ? options.pageSize : 25 %}
+{% set page_size_selector_enabled = options.pageSizeSelector is defined ? options.pageSizeSelector : true %}
+{% set allowed_page_sizes = options.allowedPageSizes is defined ? options.allowedPageSizes : [10, 25, 50, 100] %}
+{% set column_visibility_enabled = options.columnVisibility is defined ? options.columnVisibility : true %}
+{% set runtime_visible_columns = options.visibleColumns is defined ? options.visibleColumns : [] %}
+{% set runtime_hidden_columns = options.hiddenColumns is defined ? options.hiddenColumns : [] %}
+
+
+
+ {% if column_visibility_enabled %}
+ {% include '@ZhorteinDatatable/bootstrap/_column_visibility.html.twig' with {
+ definition: definition,
+ htmlId: htmlId,
+ runtime_visible_columns: runtime_visible_columns,
+ runtime_hidden_columns: runtime_hidden_columns
+ } only %}
+ {% endif %}
+
+ {% if page_size_selector_enabled %}
+
+
+ {% endif %}
+
+
+
+
diff --git a/templates/bootstrap/_toolbar.html.twig b/templates/bootstrap/_toolbar.html.twig
index df77635..e0724d6 100644
--- a/templates/bootstrap/_toolbar.html.twig
+++ b/templates/bootstrap/_toolbar.html.twig
@@ -5,6 +5,7 @@
{% set runtime_visible_columns = options.visibleColumns is defined ? options.visibleColumns : [] %}
{% set runtime_hidden_columns = options.hiddenColumns is defined ? options.hiddenColumns : [] %}
{% set export_enabled = options.export is defined ? options.export : true %}
+{% set is_split_layout = controlsLayout is defined and controlsLayout == 'split' %}
@@ -54,7 +55,7 @@
- {% if column_visibility_enabled %}
+ {% if not is_split_layout and column_visibility_enabled %}
{% include '@ZhorteinDatatable/bootstrap/_column_visibility.html.twig' with {
definition: definition,
htmlId: htmlId,
@@ -70,7 +71,7 @@
} only %}
{% endif %}
- {% if page_size_selector_enabled %}
+ {% if not is_split_layout and page_size_selector_enabled %}
diff --git a/templates/bootstrap/datatable.html.twig b/templates/bootstrap/datatable.html.twig
index 630de1c..788947b 100644
--- a/templates/bootstrap/datatable.html.twig
+++ b/templates/bootstrap/datatable.html.twig
@@ -3,6 +3,8 @@
{% set page_size = options.pageSize is defined ? options.pageSize : 25 %}
{% set sort_field = options.sortField is defined ? options.sortField : '' %}
{% set sort_direction = options.sortDirection is defined ? options.sortDirection : 'asc' %}
+{% set auto_load = options.autoLoad is defined ? options.autoLoad : true %}
+{% set controls_layout = options.controlsLayout is defined ? options.controlsLayout : 'default' %}
{% set table_striped = options.tableStriped is defined ? options.tableStriped : true %}
{% set table_hover = options.tableHover is defined ? options.tableHover : true %}
{% set table_bordered = options.tableBordered is defined ? options.tableBordered : false %}
@@ -10,7 +12,6 @@
{% set table_small = options.tableSmall is defined ? options.tableSmall : false %}
{% set table_responsive = options.tableResponsive is defined ? options.tableResponsive : true %}
{% set table_classes = ['table', 'align-middle', 'mb-0'] %}
-{% set auto_load = options.autoLoad is defined ? options.autoLoad : true %}
{% if table_striped %}
{% set table_classes = table_classes|merge(['table-striped']) %}
@@ -46,7 +47,13 @@
data-zhortein--datatable-bundle--datatable-auto-load-value="{{ auto_load ? 'true' : 'false' }}"
aria-busy="false"
>
- {% include '@ZhorteinDatatable/bootstrap/_toolbar.html.twig' %}
+ {% include '@ZhorteinDatatable/bootstrap/_toolbar.html.twig' with {
+ definition: definition,
+ htmlId: htmlId,
+ globalActions: globalActions,
+ options: options,
+ controlsLayout: controls_layout
+ } only %}