refactor(locale-switcher): single flag-sized dropdown, drop 4-inline-flag UI
The nav previously showed 4 inline flag buttons (DE/EN/UK/RU) on desktop and a details-based dropdown on mobile. Martin asked for one dropdown with a trigger the size of a single flag, and the 4 inline flags to go away. - LocaleSwitcher: render a single <details class='locale-switcher'> everywhere; trigger is one flag + tiny caret; menu lists all 4 with labels. - Drop the 4-inline <ul> and the locale-switcher-mobile duplicate. - CSS: replace both blocks with one compact dropdown (flag-sized trigger, 44px touch target via padding, scrolled/transparent-nav variants). - Tests: assert 4 menu options, 5 flag SVGs, single <details> dropdown, active locale is a <span aria-current>, others are <a> with hreflang. - 141/141 PHPUnit green.
This commit is contained in:
@@ -11,13 +11,18 @@ use App\Core\Locale;
|
||||
* Renders the language switcher widget. Pure HTML generation — no
|
||||
* side effects, no header writing.
|
||||
*
|
||||
* Each option gets:
|
||||
* - an inline 24×16 SVG flag (sub-Issue D),
|
||||
* Single <details>-based dropdown shown at every viewport. The
|
||||
* trigger is a flag-sized button (24×16 SVG + tiny caret) that
|
||||
* opens to a menu of all 4 supported locales.
|
||||
*
|
||||
* Each menu option gets:
|
||||
* - an inline 24×16 SVG flag,
|
||||
* - `hreflang` and `lang` for SEO and screen readers,
|
||||
* - `aria-current="true"` on the active option.
|
||||
*
|
||||
* Active option is rendered as a <span> (not a link) so it cannot
|
||||
* be reactivated. All options are ≥44px touch targets via CSS.
|
||||
* The active option is rendered as a <span> (not a link) so it
|
||||
* cannot be reactivated. The trigger and every menu option are
|
||||
* ≥44px touch targets via CSS.
|
||||
*/
|
||||
final class LocaleSwitcher
|
||||
{
|
||||
@@ -36,46 +41,6 @@ final class LocaleSwitcher
|
||||
'UTF-8',
|
||||
);
|
||||
|
||||
$html = '<div class="locale-switcher-wrapper">';
|
||||
$html .= '<ul class="locale-switcher" role="list" aria-label="' . $ariaLabel . '">';
|
||||
foreach (Locale::SUPPORTED as $code) {
|
||||
$isCurrent = $code === $this->currentLocale;
|
||||
$name = htmlspecialchars(
|
||||
I18n::t('locale.' . $code, [], $this->currentLocale),
|
||||
ENT_QUOTES,
|
||||
'UTF-8',
|
||||
);
|
||||
$codeAttr = htmlspecialchars($code, ENT_QUOTES, 'UTF-8');
|
||||
$flag = self::flagSvg($code);
|
||||
|
||||
$classes = 'locale-switcher__option';
|
||||
if ($isCurrent) {
|
||||
$classes .= ' is-current';
|
||||
}
|
||||
|
||||
$html .= '<li class="locale-switcher__item">';
|
||||
if ($isCurrent) {
|
||||
$html .= '<span class="' . $classes . '" aria-current="true" lang="' . $codeAttr . '">'
|
||||
. $flag
|
||||
. '<span class="locale-switcher__label">' . $name . '</span>'
|
||||
. '</span>';
|
||||
} else {
|
||||
$url = '/locale?set=' . rawurlencode($code) . '&return=' . rawurlencode($path);
|
||||
$html .= '<a class="' . $classes . '"'
|
||||
. ' href="' . $url . '"'
|
||||
. ' hreflang="' . $codeAttr . '"'
|
||||
. ' lang="' . $codeAttr . '"'
|
||||
. ' rel="alternate"'
|
||||
. '>'
|
||||
. $flag
|
||||
. '<span class="locale-switcher__label">' . $name . '</span>'
|
||||
. '</a>';
|
||||
}
|
||||
$html .= '</li>';
|
||||
}
|
||||
$html .= '</ul>';
|
||||
|
||||
// Mobile dropdown — compact single-trigger switcher for narrow viewports
|
||||
$currentName = htmlspecialchars(
|
||||
I18n::t('locale.' . $this->currentLocale, [], $this->currentLocale),
|
||||
ENT_QUOTES,
|
||||
@@ -83,15 +48,18 @@ final class LocaleSwitcher
|
||||
);
|
||||
$currentCode = htmlspecialchars($this->currentLocale, ENT_QUOTES, 'UTF-8');
|
||||
$currentFlag = self::flagSvg($this->currentLocale);
|
||||
$html .= '<details class="locale-switcher-mobile">';
|
||||
$html .= '<summary class="locale-switcher-mobile__trigger" aria-label="' . $ariaLabel . '">';
|
||||
$html .= '<span class="locale-switcher-mobile__current" lang="' . $currentCode . '">';
|
||||
|
||||
$html = '<details class="locale-switcher">';
|
||||
$html .= '<summary class="locale-switcher__trigger"'
|
||||
. ' aria-label="' . $ariaLabel . '"'
|
||||
. ' title="' . $currentName . '"'
|
||||
. '>';
|
||||
$html .= '<span class="locale-switcher__current" lang="' . $currentCode . '">';
|
||||
$html .= $currentFlag;
|
||||
$html .= '<span class="locale-switcher-mobile__current-code">' . strtoupper($currentCode) . '</span>';
|
||||
$html .= '</span>';
|
||||
$html .= '<span class="locale-switcher-mobile__caret" aria-hidden="true">▾</span>';
|
||||
$html .= '<span class="locale-switcher__caret" aria-hidden="true">▾</span>';
|
||||
$html .= '</summary>';
|
||||
$html .= '<ul class="locale-switcher-mobile__menu" role="list">';
|
||||
$html .= '<ul class="locale-switcher__menu" role="list">';
|
||||
foreach (Locale::SUPPORTED as $code) {
|
||||
$isCurrent = $code === $this->currentLocale;
|
||||
$name = htmlspecialchars(
|
||||
@@ -104,23 +72,28 @@ final class LocaleSwitcher
|
||||
|
||||
$html .= '<li>';
|
||||
if ($isCurrent) {
|
||||
$html .= '<span class="locale-switcher-mobile__option is-current" aria-current="true" lang="' . $codeAttr . '">'
|
||||
. $flag . '<span>' . $name . '</span></span>';
|
||||
$html .= '<span class="locale-switcher__option is-current"'
|
||||
. ' aria-current="true"'
|
||||
. ' lang="' . $codeAttr . '">'
|
||||
. $flag
|
||||
. '<span class="locale-switcher__label">' . $name . '</span>'
|
||||
. '</span>';
|
||||
} else {
|
||||
$url = '/locale?set=' . rawurlencode($code) . '&return=' . rawurlencode($path);
|
||||
$html .= '<a class="locale-switcher-mobile__option"'
|
||||
$html .= '<a class="locale-switcher__option"'
|
||||
. ' href="' . $url . '"'
|
||||
. ' hreflang="' . $codeAttr . '"'
|
||||
. ' lang="' . $codeAttr . '"'
|
||||
. ' rel="alternate"'
|
||||
. '>'
|
||||
. $flag . '<span>' . $name . '</span></a>';
|
||||
. $flag
|
||||
. '<span class="locale-switcher__label">' . $name . '</span>'
|
||||
. '</a>';
|
||||
}
|
||||
$html .= '</li>';
|
||||
}
|
||||
$html .= '</ul>';
|
||||
$html .= '</details>';
|
||||
$html .= '</div>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user