From 32427a20f699bf87648ec29b76632065981144df Mon Sep 17 00:00:00 2001
From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com>
Date: Sat, 24 Oct 2026 14:04:43 +0200
Subject: [PATCH] fix(ui): soften list separators and fix the page size control
- Row and section separators use a softer translucent divider token on all themes
- Page size trigger is an aligned bordered button ("10 / page")
- Pagination footer stays visible when a larger page size fits all rows
- Custom page size warns above the 100 maximum and the menu closes on select
Co-Authored-By: Claude Opus 5.5
---
resources/css/app.css | 15 +++++++++++++
.../components/client-pagination.blade.php | 3 ++-
.../components/page-size-select.blade.php | 22 +++++++++++++------
.../components/table-pagination.blade.php | 6 ++++-
.../views/livewire/project/index.blade.php | 4 ++--
.../livewire/project/resource/index.blade.php | 4 ++--
.../views/livewire/project/show.blade.php | 4 ++--
.../livewire/security/api-tokens.blade.php | 2 +-
.../livewire/team/member/index.blade.php | 2 +-
tests/Feature/TablePaginationLoadingTest.php | 20 +++++++++++++++++
10 files changed, 65 insertions(+), 17 deletions(-)
diff --git a/resources/css/app.css b/resources/css/app.css
index fe0e30c837..ad9c56a420 100644
--- a/resources/css/app.css
+++ b/resources/css/app.css
@@ -1348,6 +1348,9 @@ html:not(.dark) .application-console-shell[data-console-theme="system"] .termina
/* Card/table edge ring. Crisped from 93.5% (1.17:1 on canvas, invisible)
to 85.5% (1.51:1) so surfaces read as bordered cards, not flat fills. */
--coollabs-hairline: oklch(85.5% 0 0);
+ /* Row and section separators inside cards. Softer than the card edge.
+ Translucent, so it stays visible on any card or theme surface. */
+ --coollabs-divider: oklch(0% 0 0 / 0.04);
/* Muted labels/titles. Darkened from 55.6% (failed WCAG AA on elevated
4.46, recessed 4.24, fill 3.76) to 50% so muted text clears 4.5:1 on
every light surface above white. Dark stays 70.8% (already passes). */
@@ -1386,6 +1389,7 @@ html:not(.dark) .application-console-shell[data-console-theme="system"] .termina
/* Kept about 1.6:1 on the canvas so dark cards
and tables show a visible edge instead of blending into the canvas. */
--coollabs-hairline: oklch(36% 0 0);
+ --coollabs-divider: oklch(100% 0 0 / 0.045);
--coollabs-subtle: oklch(70.8% 0 0);
/* Neutral (was #a8a8b0 / #7a7a84, both cool-tinted). Sidebar text now shares
the same neutral temperature as the content text, so the sidebar no longer
@@ -1492,6 +1496,7 @@ html[data-theme="custom"] :is(.app-sidebar, .app-topbar-brand, .mobile-sidebar-s
--coollabs-fill: color-mix(in oklab, var(--t-text) calc(8% + (1 - var(--t-light)) * 4%), var(--t-bg));
--coollabs-line: color-mix(in oklab, var(--t-text) calc(16% + (1 - var(--t-light)) * 6%), var(--t-bg));
--coollabs-hairline: color-mix(in oklab, var(--t-text) calc(11% + (1 - var(--t-light)) * 3%), var(--t-bg));
+ --coollabs-divider: color-mix(in oklab, var(--t-text) calc(5% + (1 - var(--t-light)) * 1%), transparent);
--color-hairline: color-mix(in oklab, var(--t-text) 8%, transparent);
--color-fg: var(--t-text);
--color-fg-dim: color-mix(in oklab, var(--t-text) 74%, var(--t-bg));
@@ -1626,6 +1631,16 @@ html[data-sidebar-scheme="light"] :where(.app-sidebar, .app-topbar-brand, .mobil
border-color: var(--coollabs-line) !important;
}
+/* Row and section separators (one-sided borders and divide-y) use the softer
+ divider token, so lists read as one card instead of a stack of boxes. Card
+ edges (full borders) keep the hairline/line tokens. */
+html:not(.dark) :is([class~="border-b"], [class~="border-t"], [class~="border-y"])[class~="border-neutral-200"],
+html:not(.dark) [class~="divide-neutral-200"] > :not(:last-child),
+.dark :is([class~="border-b"], [class~="border-t"], [class~="border-y"]):is([class~="dark:border-white/[0.06]"], [class~="dark:border-white/[0.07]"], [class~="dark:border-white/[0.08]"]),
+.dark :is([class~="dark:divide-white/[0.06]"], [class~="dark:divide-white/[0.07]"], [class~="dark:divide-white/[0.08]"]) > :not(:last-child) {
+ border-color: var(--coollabs-divider) !important;
+}
+
html[data-theme="custom"] input:read-only,
html[data-theme="custom"] textarea:read-only,
html[data-theme="custom"] input:disabled,
diff --git a/resources/views/components/client-pagination.blade.php b/resources/views/components/client-pagination.blade.php
index f017a53a59..c73214c65e 100644
--- a/resources/views/components/client-pagination.blade.php
+++ b/resources/views/components/client-pagination.blade.php
@@ -1,5 +1,6 @@
@props([
'summary',
+ 'total',
'pageSizeModel',
'storageKey',
'options' => [10, 25, 50, 100],
@@ -9,7 +10,7 @@
'nextDisabled' => 'page >= totalPages',
])
-
-
@@ -309,7 +309,7 @@
Try a different search or filter.
-
diff --git a/resources/views/livewire/project/show.blade.php b/resources/views/livewire/project/show.blade.php
index d682ee0033..6c74bc3b30 100644
--- a/resources/views/livewire/project/show.blade.php
+++ b/resources/views/livewire/project/show.blade.php
@@ -139,7 +139,7 @@
-
@@ -190,7 +190,7 @@
-
diff --git a/resources/views/livewire/security/api-tokens.blade.php b/resources/views/livewire/security/api-tokens.blade.php
index d91072e77b..9054bac22b 100644
--- a/resources/views/livewire/security/api-tokens.blade.php
+++ b/resources/views/livewire/security/api-tokens.blade.php
@@ -238,7 +238,7 @@
description="Try a different description or permission." />
-
- not->toContain('wire:target')
->not->toContain('Loading page…');
});
+
+it('keeps the footer on one page while a larger page size is selected', function (int $total, bool $hidden) {
+ $html = Blade::render(
+ '
+
+ ',
+ ['total' => $total]
+ );
+
+ expect((bool) preg_match('/]*\shidden[\s>]/', $html))->toBe($hidden);
+})->with([
+ 'more rows than the smallest page size' => [15, false],
+ 'all rows fit in the smallest page size' => [10, true],
+]);
+
+it('hides the footer on one page when there is no page size control', function () {
+ $html = Blade::render('');
+
+ expect($html)->toMatch('/