Feature/brutalism overhaul #10

Merged
ilia merged 9 commits from feature/brutalism-overhaul into main 2026-07-14 07:40:18 +00:00
3 changed files with 18 additions and 3 deletions
Showing only changes of commit fd15374134 - Show all commits
+13
View File
@@ -12,6 +12,7 @@ import { Link } from './Link';
const PRIMARY = 'underline underline-offset-2 hover:opacity-60 transition-opacity'; const PRIMARY = 'underline underline-offset-2 hover:opacity-60 transition-opacity';
const SECONDARY = 'no-underline opacity-60 hover:opacity-100 sm:brutal-border-bottom transition-opacity'; const SECONDARY = 'no-underline opacity-60 hover:opacity-100 sm:brutal-border-bottom transition-opacity';
const TERTIARY = 'no-underline opacity-100 hover:opacity-60 sm:brutal-border-bottom transition-opacity';
describe('internal link', () => { describe('internal link', () => {
it('renders an anchor element', () => { it('renders an anchor element', () => {
@@ -92,6 +93,18 @@ describe('variant', () => {
} }
}); });
it('tertiary applies reversed-hover classes', () => {
render(
<Link href="/about" variant="tertiary">
About
</Link>,
);
const link = screen.getByRole('link', { name: 'About' });
for (const cls of TERTIARY.split(' ')) {
expect(link).toHaveClass(cls);
}
});
it('secondary does not apply underline', () => { it('secondary does not apply underline', () => {
render( render(
<Link href="/about" variant="secondary"> <Link href="/about" variant="secondary">
+3 -1
View File
@@ -2,7 +2,7 @@ import NextLink from 'next/link';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import { cn } from '$shared/lib'; import { cn } from '$shared/lib';
export type LinkVariant = 'primary' | 'secondary'; export type LinkVariant = 'primary' | 'secondary' | 'tertiary';
/** /**
* Props for Link. * Props for Link.
@@ -24,6 +24,7 @@ interface Props {
* Visual variant. * Visual variant.
* primary — text-decoration underline. * primary — text-decoration underline.
* secondary — border-bottom at sm+, no underline on mobile (for icon+label links). * secondary — border-bottom at sm+, no underline on mobile (for icon+label links).
* tertiary — like secondary but reversed: bright by default, dims on hover.
* @default 'primary' * @default 'primary'
*/ */
variant?: LinkVariant; variant?: LinkVariant;
@@ -37,6 +38,7 @@ interface Props {
const VARIANTS = { const VARIANTS = {
primary: 'underline underline-offset-2 hover:opacity-60 transition-opacity', primary: 'underline underline-offset-2 hover:opacity-60 transition-opacity',
secondary: 'no-underline opacity-60 hover:opacity-100 sm:brutal-border-bottom transition-opacity', secondary: 'no-underline opacity-60 hover:opacity-100 sm:brutal-border-bottom transition-opacity',
tertiary: 'no-underline opacity-100 hover:opacity-60 sm:brutal-border-bottom transition-opacity',
} as const satisfies Record<LinkVariant, string>; } as const satisfies Record<LinkVariant, string>;
/** /**
+2 -2
View File
@@ -23,7 +23,7 @@ export async function Footer() {
<div className="w-full flex flex-row justify-between gap-4"> <div className="w-full flex flex-row justify-between gap-4">
<div className="flex flex-wrap items-center gap-6 sm:gap-4"> <div className="flex flex-wrap items-center gap-6 sm:gap-4">
{email && ( {email && (
<Link href={email.url} external variant="secondary" className="flex items-center gap-1.5 text-sm"> <Link href={email.url} external variant="tertiary" className="flex items-center gap-1.5 text-sm">
{email.icon && <InlineSvg svg={email.icon} className="inline-flex w-8 h-8 sm:hidden" />} {email.icon && <InlineSvg svg={email.icon} className="inline-flex w-8 h-8 sm:hidden" />}
<span className="hidden sm:block">{email.label}</span> <span className="hidden sm:block">{email.label}</span>
</Link> </Link>
@@ -33,7 +33,7 @@ export async function Footer() {
key={social.id} key={social.id}
href={social.url} href={social.url}
external external
variant="secondary" variant="tertiary"
className="flex items-center gap-1.5 text-sm" className="flex items-center gap-1.5 text-sm"
> >
{social.icon && <InlineSvg svg={social.icon} className="inline-flex w-8 h-8 sm:hidden" />} {social.icon && <InlineSvg svg={social.icon} className="inline-flex w-8 h-8 sm:hidden" />}