From 79a1432db89664e4c3d786290ce629d42d6a5e1e Mon Sep 17 00:00:00 2001 From: MechaCat02 Date: Thu, 25 Jun 2026 19:46:54 +0200 Subject: [PATCH] refactor(ui): fold the 36px header/search buttons into IconButton MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Completes the IconButton extraction by adding `size` and `radius` props and converting the two remaining outliers that were left out of the first pass: the desktop header logout button (+layout) and the home search submit button (+page), both 36px with the medium radius. IconButton now drives every icon button in the app (except profile/history's, which the shared-HistoryList change removes separately). Defaults stay 32px / small radius, so the four already-converted sites are untouched. Rendered size, radius, and variant match the old markup — no behaviour change, no version bump. This also sets up applying the --tap-min touch floor in one place once the touch-target change lands. Co-Authored-By: Claude Opus 4.8 (1M context) --- frontend/src/lib/components/IconButton.svelte | 40 ++++++++++++++----- .../lib/components/IconButton.svelte.test.ts | 17 ++++++++ frontend/src/routes/+layout.svelte | 27 +++---------- frontend/src/routes/+page.svelte | 32 +++++---------- 4 files changed, 62 insertions(+), 54 deletions(-) diff --git a/frontend/src/lib/components/IconButton.svelte b/frontend/src/lib/components/IconButton.svelte index 6826f05..08a1852 100644 --- a/frontend/src/lib/components/IconButton.svelte +++ b/frontend/src/lib/components/IconButton.svelte @@ -2,23 +2,36 @@ import type { Snippet } from 'svelte'; import type { HTMLButtonAttributes } from 'svelte/elements'; - // Shared square icon button — extracted from five near-identical - // `.icon-btn` copies (collections, manga edit, upload, the chapter-pages - // editor) so the size, hover, and variant treatment live in one place. - // Callers pass the lucide icon as the child and any button attributes - // (onclick, disabled, aria-label, title, data-testid) straight through. + // Shared square icon button — extracted from the near-identical + // `.icon-btn` copies across the app so the size, hover, and variant + // treatment live in one place. Callers pass the lucide icon as the child + // and any button attributes (onclick, disabled, aria-label, title, + // data-testid) straight through. `size`/`radius` cover the two larger + // header/search buttons without forcing the 32px default everywhere. type Variant = 'plain' | 'primary' | 'danger'; let { variant = 'plain', + size = 32, + radius = 'sm', children, ...rest - }: { variant?: Variant; children: Snippet } & HTMLButtonAttributes = $props(); + }: { + variant?: Variant; + size?: number; + radius?: 'sm' | 'md'; + children: Snippet; + } & HTMLButtonAttributes = $props(); - @@ -27,16 +40,23 @@ display: inline-flex; align-items: center; justify-content: center; - width: 32px; - height: 32px; + width: var(--ib-size, 32px); + height: var(--ib-size, 32px); padding: 0; background: transparent; color: var(--text-muted); border: 1px solid transparent; - border-radius: var(--radius-sm); cursor: pointer; } + .radius-sm { + border-radius: var(--radius-sm); + } + + .radius-md { + border-radius: var(--radius-md); + } + .icon-btn:hover:not(:disabled) { background: var(--surface-elevated); color: var(--text); diff --git a/frontend/src/lib/components/IconButton.svelte.test.ts b/frontend/src/lib/components/IconButton.svelte.test.ts index 0be4512..46e1769 100644 --- a/frontend/src/lib/components/IconButton.svelte.test.ts +++ b/frontend/src/lib/components/IconButton.svelte.test.ts @@ -37,6 +37,23 @@ describe('IconButton', () => { expect(c2.querySelector('button.icon-btn.danger')).toBeTruthy(); }); + it('defaults to a 32px square with the small radius', () => { + const { container } = render(IconButton, { props: { children: icon } }); + const btn = container.querySelector('button.icon-btn') as HTMLButtonElement; + expect(btn.classList.contains('radius-sm')).toBe(true); + expect(btn.style.getPropertyValue('--ib-size')).toBe('32px'); + }); + + it('honours an explicit size and radius (the larger header/search buttons)', () => { + const { container } = render(IconButton, { + props: { size: 36, radius: 'md', children: icon } + }); + const btn = container.querySelector('button.icon-btn') as HTMLButtonElement; + expect(btn.style.getPropertyValue('--ib-size')).toBe('36px'); + expect(btn.classList.contains('radius-md')).toBe(true); + expect(btn.classList.contains('radius-sm')).toBe(false); + }); + it('forwards onclick', async () => { const onclick = vi.fn(); render(IconButton, { props: { onclick, children: icon } }); diff --git a/frontend/src/routes/+layout.svelte b/frontend/src/routes/+layout.svelte index c8d4095..c621b41 100644 --- a/frontend/src/routes/+layout.svelte +++ b/frontend/src/routes/+layout.svelte @@ -9,6 +9,7 @@ import { theme } from '$lib/theme.svelte'; import AppBar from '$lib/components/AppBar.svelte'; import BottomNav, { type BottomNavTab } from '$lib/components/BottomNav.svelte'; + import IconButton from '$lib/components/IconButton.svelte'; import Upload from '@lucide/svelte/icons/upload'; import UserCircle from '@lucide/svelte/icons/user-circle'; import Bookmark from '@lucide/svelte/icons/bookmark'; @@ -232,9 +233,9 @@ {:else if session.user} {session.user.username} - + {:else} Login {#if authConfig.self_register_enabled} @@ -348,24 +349,6 @@ padding: 0 var(--space-2); } - .icon-btn { - display: inline-flex; - align-items: center; - justify-content: center; - width: 36px; - height: 36px; - padding: 0; - background: transparent; - color: var(--text-muted); - border: 1px solid transparent; - border-radius: var(--radius-md); - } - - .icon-btn:hover:not(:disabled) { - background: var(--surface-elevated); - color: var(--text); - } - .logging-out { font-size: var(--font-base); line-height: 1; diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte index a89a384..88f6661 100644 --- a/frontend/src/routes/+page.svelte +++ b/frontend/src/routes/+page.svelte @@ -27,6 +27,7 @@ import Pager from '$lib/components/Pager.svelte'; import SegmentedControl from '$lib/components/SegmentedControl.svelte'; import Sheet from '$lib/components/Sheet.svelte'; + import IconButton from '$lib/components/IconButton.svelte'; import Search from '@lucide/svelte/icons/search'; import SlidersHorizontal from '@lucide/svelte/icons/sliders-horizontal'; import ArrowUpDown from '@lucide/svelte/icons/arrow-up-down'; @@ -467,9 +468,16 @@ placeholder="Search by title or author" data-testid="search-input" /> - +