Merge pull request #1502 from BradGroux/fix/loading-button-centering-1501

fix: center loading buttons with reduced motion
This commit is contained in:
Brad Groux 2026-09-04 12:31:16 -05:00 • committed by GitHub
commit 8d90ef4aca
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 43 additions and 0 deletions

View file

@ -124,4 +124,6 @@ The initial component regression reproduced immediate dismissal after dispatch.
## Remaining acceptance
Shared loading buttons retain a static centering transform when reduced-motion transitions omit their styles. Normal-motion transitions continue to own their animated transform. The HTML artifact Refresh regression checks a held request at normal and enlarged text sizes, requiring the loading indicator to remain centered and fully inside its button. The original packaged-candidate clipping remains historical evidence; fresh affected native verification is required after integration and rebuilding.
Complete every family in both themes, normal and enlarged text, minimum native window size, keyboard entry/dismissal, reduced motion, pending-operation states, and viewport/footer reachability. Rebuild the packaged application with the complete family and inspect native captures. Reconcile the consumer inventory only against that evidence. Final installed-app verification and the maintained documentation screenshots/GIF refresh remain separate, unfinished work.

View file

@ -231,6 +231,42 @@ for (const kind of ['image', 'pdf', 'html'] as const) {
await expect(dialog).toBeVisible();
await expect(dialog.getByRole('button', { name: 'Close dialog' })).toBeDisabled();
await expect(dialog.getByRole('button', { name: 'Causal event' })).toBeDisabled();
// Reduced-motion transitions must not remove the loader's layout transform.
for (const size of [
{ width: 1700, height: 900, fontSize: '16px' },
{ width: 1180, height: 760, fontSize: '20px' },
{ width: 900, height: 480, fontSize: '20px' },
]) {
await page.setViewportSize(size);
await page.evaluate((fontSize) => {
document.documentElement.style.fontSize = fontSize;
}, size.fontSize);
const refresh = dialog.getByRole('button', { name: 'Refresh', exact: true });
await expect(refresh).toBeDisabled();
await expect
.poll(
() =>
refresh.evaluate((button) => {
const loader = button.querySelector('.mantine-Button-loader');
if (!loader) return false;
const outer = button.getBoundingClientRect();
const inner = loader.getBoundingClientRect();
return (
inner.width > 0 &&
inner.height > 0 &&
inner.left >= outer.left &&
inner.right <= outer.right &&
inner.top >= outer.top &&
inner.bottom <= outer.bottom &&
Math.abs(inner.left + inner.width / 2 - outer.left - outer.width / 2) <=
1 &&
Math.abs(inner.top + inner.height / 2 - outer.top - outer.height / 2) <= 2
);
}),
{ message: 'Loading indicator must be centered and fully inside its button' }
)
.toBe(true);
}
expect(
audits.filter((entry) => (entry as { action: string }).action === 'refresh')
).toHaveLength(1);

View file

@ -100,6 +100,11 @@ export const veritasMantineTheme = createTheme({
radius: 'sm',
},
styles: {
// Reduced-motion Transition omits styles, including loader positioning.
// Normal-motion transition styles override this static layout fallback.
loader: {
transform: 'translate(-50%, -50%)',
},
root: {
minHeight: `${VERITAS_UI_METRICS.actionMinHeight}px`,
minWidth: `${VERITAS_UI_METRICS.actionMinHeight}px`,