fix(locale-switcher): make flag the visual anchor (32x24, no border, no lazy load)
Martin feedback round 3: dropdown still looked 'fuerchterlich' even with the official flag-icons. Root cause: 14px vertical padding around an 18px-tall flag meant the flag occupied only 39% of the trigger height and was dwarfed by whitespace. Plus a 1px black box-shadow border made flags look 'boxy', and loading='lazy' caused empty boxes on the four menu flags the moment the <details> opened. Changes: - Flag size 24x18 -> 32x24 (+78% area, ~4:3 matches flag-icons) - Trigger padding 14px 8px -> 6px (flag now 73% of trigger width, 55% of trigger height, was 46%/39%) - Drop the artificial 1px black box-shadow outline on flags - Drop border-radius on flags (real flag-icons look better as crisp rectangles) - Drop object-fit: cover (no longer needed for SVG) - Drop loading='lazy' and decoding='async' (4 small SVGs, must be ready the moment <details> opens, not flash empty boxes) - min-height: 44px restored on trigger for WCAG 2.5.5 touch target - Menu border-radius 8 -> 10px, padding tightened, font-size 0.85 -> 0.9rem for label legibility - Two-layer box-shadow on menu for subtle elevation
This commit is contained in:
@@ -110,7 +110,11 @@ final class LocaleSwitcher
|
||||
public static function flagImg(string $locale): string
|
||||
{
|
||||
$src = self::flagSource($locale);
|
||||
return '<img class="flag" src="' . $src . '" alt="" width="24" height="18" loading="lazy" decoding="async">';
|
||||
// 32×24 = ~4:3, large enough that the flag is the visual
|
||||
// anchor of the option. No loading="lazy" — these are 4
|
||||
// small SVGs that must be ready the moment the <details>
|
||||
// opens (lazy would cause a flash of empty boxes).
|
||||
return '<img class="flag" src="' . $src . '" alt="" width="32" height="24">';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1476,7 +1476,14 @@ footer {
|
||||
}
|
||||
}
|
||||
|
||||
/* LOCALE SWITCHER — single <details> dropdown, flag-sized trigger */
|
||||
/* LOCALE SWITCHER — single <details> dropdown, flag-sized trigger.
|
||||
Design goals (after Martin-feedback round 3):
|
||||
- The flag is the visual anchor of every row, not a tiny icon
|
||||
drowning in padding.
|
||||
- Trigger is a 44×44 touch target with the flag centred, no
|
||||
artificial 1px outline (real flag-icons need no border).
|
||||
- Menu rows are 40px+ tall with comfortable flag+label spacing.
|
||||
- No FOUC on open: SVGs load eager (4 small files). */
|
||||
.locale-switcher {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
@@ -1485,15 +1492,14 @@ footer {
|
||||
.locale-switcher__trigger {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
padding: 14px 8px;
|
||||
gap: 4px;
|
||||
padding: 6px;
|
||||
min-height: 44px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
border-radius: 6px;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
list-style: none;
|
||||
min-height: 44px;
|
||||
min-width: 44px;
|
||||
color: var(--dark);
|
||||
transition:
|
||||
background 0.2s,
|
||||
@@ -1516,11 +1522,10 @@ footer {
|
||||
}
|
||||
|
||||
.locale-switcher__caret {
|
||||
font-size: 0.65rem;
|
||||
font-size: 0.7rem;
|
||||
line-height: 1;
|
||||
color: inherit;
|
||||
transition: transform 0.2s ease;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.locale-switcher[open] .locale-switcher__caret {
|
||||
@@ -1533,25 +1538,27 @@ footer {
|
||||
right: 0;
|
||||
min-width: 180px;
|
||||
margin: 0;
|
||||
padding: 0.4rem;
|
||||
padding: 6px;
|
||||
list-style: none;
|
||||
background: var(--white);
|
||||
border: 1px solid var(--warm);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 8px 24px rgb(0 0 0 / 14%);
|
||||
border-radius: 10px;
|
||||
box-shadow:
|
||||
0 1px 2px rgb(0 0 0 / 6%),
|
||||
0 8px 24px rgb(0 0 0 / 14%);
|
||||
z-index: 60;
|
||||
}
|
||||
|
||||
.locale-switcher__option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
padding: 0.6rem 0.7rem;
|
||||
border-radius: 4px;
|
||||
gap: 10px;
|
||||
padding: 8px 10px;
|
||||
border-radius: 6px;
|
||||
text-decoration: none;
|
||||
color: var(--dark);
|
||||
font-size: 0.85rem;
|
||||
min-height: 44px;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.locale-switcher__option.is-current {
|
||||
@@ -1566,24 +1573,13 @@ footer {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Flag is the visual anchor: 32×24, no border, no rounded corners
|
||||
(flags look better as crisp rectangles than as pills). */
|
||||
.locale-switcher .flag {
|
||||
width: 24px;
|
||||
height: 18px;
|
||||
flex: 0 0 24px;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 0 0 1px rgb(0 0 0 / 15%);
|
||||
width: 32px;
|
||||
height: 24px;
|
||||
flex: 0 0 32px;
|
||||
display: block;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.locale-switcher__option .flag {
|
||||
width: 24px;
|
||||
height: 18px;
|
||||
flex: 0 0 24px;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 0 0 1px rgb(0 0 0 / 15%);
|
||||
display: block;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.locale-switcher__label {
|
||||
|
||||
@@ -111,7 +111,7 @@ final class LocaleSwitcherTest extends TestCase
|
||||
$img = LocaleSwitcher::flagImg($code);
|
||||
self::assertStringStartsWith('<img', $img);
|
||||
self::assertStringContainsString('class="flag"', $img);
|
||||
self::assertStringContainsString('width="24" height="18"', $img);
|
||||
self::assertStringContainsString('width="32" height="24"', $img);
|
||||
self::assertStringContainsString('alt=""', $img);
|
||||
self::assertStringEndsWith('>', $img);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user