;
const label = ;
diff --git a/app/src/components/ui/ui-layout.cy.js b/app/src/components/ui/ui-layout.cy.js
index be7348df..13db0e9a 100644
--- a/app/src/components/ui/ui-layout.cy.js
+++ b/app/src/components/ui/ui-layout.cy.js
@@ -1,7 +1,7 @@
import React from 'react';
import {
ActionInput, Button, Card, Header, Icon, IconButton, IconStack, Loading, MultiSelect, Panel, PathInput,
- Message, Placeholder, Progress, SearchBox, Statistic, StatisticGroup, Status, TabBar, Table,
+ Message, Pagination, Placeholder, Progress, SearchBox, Statistic, StatisticGroup, Status, TabBar, Table,
tabClassName, TextInput,
} from './index';
import {MemoryRouter} from 'react-router';
@@ -2020,3 +2020,54 @@ describe('the search clear button matches the field it clears', () => {
});
});
});
+
+describe('pagination sits in the middle of its container', () => {
+ /*
+ * Three call sites wrap the pager in `
` or `text-align: center` and it still sat
+ * hard against the left edge. Both of those centre INLINE content, and Mantine's
+ * Pagination root is a flex container that fills its parent's width -- so the buttons
+ * were laid out at `flex-start` inside a box that was already the full width, and no
+ * amount of centring outside it could move them.
+ *
+ * Measured as the gap on each side rather than by reading `justify-content`, so the
+ * claim is about where the control actually lands.
+ */
+ it('leaves the same gap either side', () => {
+ cy.mountUI(
+ {}}/>
+
);
+
+ cy.get('.wrolpi-pagination').should(($pager) => {
+ const container = $pager[0].getBoundingClientRect();
+ const buttons = [...$pager[0].querySelectorAll('button')]
+ .map(button => button.getBoundingClientRect());
+ expect(buttons.length, 'the pager rendered its controls').to.be.greaterThan(3);
+
+ const left = Math.min(...buttons.map(b => b.left)) - container.left;
+ const right = container.right - Math.max(...buttons.map(b => b.right));
+ // The premise: the container really is wider than the strip, so there is slack
+ // to distribute. Without this the equality holds trivially at 0 and 0.
+ expect(left + right, 'the container is wider than the strip').to.be.greaterThan(40);
+ expect(left, 'gap left vs gap right').to.be.closeTo(right, 1);
+ });
+ });
+
+ it('does not push its first page off the left edge when space is tight', () => {
+ /*
+ * The risk centring introduces. Overflow in a `flex-start` row spills to the right,
+ * where it can at least be scrolled to; centred, it spills BOTH ways and the first
+ * page number is unreachable. 240px is narrower than any real layout gives it.
+ */
+ cy.mountUI(
+ {}} showFirstAndLast/>
+
);
+
+ cy.get('.wrolpi-pagination').should(($pager) => {
+ const container = $pager[0].getBoundingClientRect();
+ const buttons = [...$pager[0].querySelectorAll('button')]
+ .map(button => button.getBoundingClientRect());
+ expect(Math.min(...buttons.map(b => b.left)), 'nothing hangs off the left')
+ .to.be.at.least(container.left - 0.5);
+ });
+ });
+});
diff --git a/app/src/components/ui/ui.css b/app/src/components/ui/ui.css
index d9115edd..f398c087 100644
--- a/app/src/components/ui/ui.css
+++ b/app/src/components/ui/ui.css
@@ -190,6 +190,28 @@ html[data-theme="amber"] .wrolpi-theme-option-active {
/* ------------------------------------------------------------ Pagination */
+/*
+ * The pager is centred here rather than by whoever renders it. Three call sites wrapped it
+ * in `
` or `text-align: center` and it still sat hard against the left edge.
+ *
+ * Mantine builds a Pagination as a BLOCK root -- full width, so those wrappers had nothing
+ * to centre -- holding a flex Group that carries the controls and lays them out at
+ * `flex-start`. `justify-content` on the root therefore does nothing at all; it has to
+ * reach the Group.
+ *
+ * Setting `--group-justify` on the root does not work either: Group defaults that prop and
+ * writes the variable INLINE on its own element, where an inherited value cannot reach it.
+ * The property itself is fair game, though -- Mantine only ever reads the variable from a
+ * hashed class -- so this rule at (0,2,0) is what actually lands.
+ *
+ * The Group already wraps by default (`--group-wrap` falls back to `wrap`), which matters
+ * once the row is centred: centred overflow spills off BOTH edges, and the first page would
+ * be unreachable rather than merely off-screen to the right.
+ */
+.wrolpi-pagination .mantine-Group-root {
+ justify-content: center;
+}
+
.wrolpi-pagination button {
background: var(--panel);
color: var(--text);