refactor: swap Button ghost/outline semantics, clean up ImageLightbox thumbnail

ghost now means transparent bg (no fill); outline keeps cream bg with subtle border.
Remove magnify icon overlay from ImageLightbox thumbnail — hover cursor-zoom-in is sufficient.
Close button updated to variant="outline" for cream-on-blue contrast in the dialog.
This commit is contained in:
Ilia Mashkov
2026-05-22 13:30:28 +03:00
parent 7a06d42d20
commit 43242c3bed
5 changed files with 18 additions and 16 deletions
+1
View File
@@ -439,4 +439,5 @@
/* Lightbox dialog backdrop */ /* Lightbox dialog backdrop */
dialog.lightbox::backdrop { dialog.lightbox::backdrop {
background-color: rgba(4, 28, 243, 0.25); background-color: rgba(4, 28, 243, 0.25);
backdrop-filter: blur(4px);
} }
+2 -2
View File
@@ -24,11 +24,11 @@ describe('Button', () => {
}); });
it('applies outline variant', () => { it('applies outline variant', () => {
render(<Button variant="outline">Go</Button>); render(<Button variant="outline">Go</Button>);
expect(screen.getByRole('button')).toHaveClass('bg-transparent'); expect(screen.getByRole('button')).toHaveClass('bg-cream');
}); });
it('applies ghost variant', () => { it('applies ghost variant', () => {
render(<Button variant="ghost">Go</Button>); render(<Button variant="ghost">Go</Button>);
expect(screen.getByRole('button')).toHaveClass('bg-cream'); expect(screen.getByRole('button')).toHaveClass('bg-transparent');
}); });
}); });
describe('sizes', () => { describe('sizes', () => {
+2 -2
View File
@@ -46,9 +46,9 @@ const VARIANTS = {
secondary: secondary:
'brutal-border bg-blue text-cream shadow-brutal hover:-translate-x-0.5 hover:-translate-y-0.5 hover:shadow-brutal-md active:translate-x-0.5 active:translate-y-0.5 active:shadow-brutal-xs', 'brutal-border bg-blue text-cream shadow-brutal hover:-translate-x-0.5 hover:-translate-y-0.5 hover:shadow-brutal-md active:translate-x-0.5 active:translate-y-0.5 active:shadow-brutal-xs',
outline: outline:
'brutal-border bg-transparent text-blue shadow-brutal hover:-translate-x-0.5 hover:-translate-y-0.5 hover:shadow-brutal-md active:translate-x-0.5 active:translate-y-0.5 active:shadow-brutal-xs',
ghost:
'brutal-border border-blue/35 bg-cream text-blue hover:border-blue hover:bg-blue/10 active:bg-blue active:text-cream', 'brutal-border border-blue/35 bg-cream text-blue hover:border-blue hover:bg-blue/10 active:bg-blue active:text-cream',
ghost:
'brutal-border bg-transparent text-blue hover:-translate-x-0.5 hover:-translate-y-0.5 active:translate-x-0.5 active:translate-y-0.5',
} as const satisfies Record<ButtonVariant, string>; } as const satisfies Record<ButtonVariant, string>;
const SIZES = { const SIZES = {
@@ -56,7 +56,7 @@ describe('ImageLightbox', () => {
it('clicking inside the dialog (not backdrop) does not close', () => { it('clicking inside the dialog (not backdrop) does not close', () => {
render(<ImageLightbox {...DEFAULT_PROPS} />); render(<ImageLightbox {...DEFAULT_PROPS} />);
const dialog = document.querySelector('dialog') as HTMLDialogElement; const dialog = document.querySelector('dialog') as HTMLDialogElement;
const inner = dialog.querySelector('div') as HTMLDivElement; const inner = dialog.querySelector('img') as HTMLImageElement;
fireEvent.click(inner); fireEvent.click(inner);
expect(HTMLDialogElement.prototype.close).not.toHaveBeenCalled(); expect(HTMLDialogElement.prototype.close).not.toHaveBeenCalled();
}); });
@@ -2,7 +2,7 @@
import Image from 'next/image'; import Image from 'next/image';
import { useRef } from 'react'; import { useRef } from 'react';
import { CloseIcon, MagnifyIcon } from '$shared/assets/icons'; import { CloseIcon } from '$shared/assets/icons';
import { cn } from '$shared/lib'; import { cn } from '$shared/lib';
import { Button } from '$shared/ui/Button'; import { Button } from '$shared/ui/Button';
@@ -66,10 +66,6 @@ export function ImageLightbox({ src, alt, className }: Props) {
className={cn('relative block w-full aspect-video overflow-hidden cursor-zoom-in', className)} className={cn('relative block w-full aspect-video overflow-hidden cursor-zoom-in', className)}
> >
<Image src={src} alt={alt} fill className="object-cover" /> <Image src={src} alt={alt} fill className="object-cover" />
{/* Magnify hint — pointer-events-none so clicks pass through to the button */}
<span aria-hidden={true} className="absolute bottom-2 right-2 bg-cream text-blue p-1 pointer-events-none">
<MagnifyIcon />
</span>
</button> </button>
<dialog <dialog
@@ -77,13 +73,18 @@ export function ImageLightbox({ src, alt, className }: Props) {
aria-label={alt} aria-label={alt}
onClick={handleBackdropClick} onClick={handleBackdropClick}
onKeyUp={handleBackdropKeyUp} onKeyUp={handleBackdropKeyUp}
className="lightbox relative bg-blue brutal-border shadow-brutal-xl p-0 max-w-5xl w-full" className="lightbox fixed inset-0 m-auto relative bg-blue brutal-border p-0 overflow-hidden"
> >
<div className="relative aspect-video w-full"> {/* Native img so the dialog sizes to the image — next/image fill requires a pre-sized container */}
{/* aria-hidden: the dialog element itself carries the accessible label */} {/* aria-hidden: the dialog element itself carries the accessible label */}
<Image src={src} alt={alt} fill className="object-contain" aria-hidden={true} /> {/* eslint-disable-next-line @next/next/no-img-element */}
</div> <img
<Button variant="ghost" size="sm" onClick={close} aria-label="Close image" className="absolute top-3 right-3"> src={src}
alt={alt}
aria-hidden={true}
className="block max-w-[calc(100vw-2rem)] max-h-[calc(100vh-2rem)] w-auto h-auto"
/>
<Button variant="outline" size="sm" onClick={close} aria-label="Close image" className="absolute top-3 right-3">
<CloseIcon /> <CloseIcon />
</Button> </Button>
</dialog> </dialog>