Files
Illusion c0daacf1ba more fixes
- Drop the globe icon in the language switcher
- Only show RightArrow on nav menuitem hover
- Fix EU logo colors in light mode
2026-09-23 22:33:58 +02:00

117 lines
4.0 KiB
TypeScript

import { Link } from "react-router";
import {
getCounterpartLocale,
getCounterpartPath,
getDirection,
LOCALE_ENDONYM,
LOCALE_SHORT_LABEL,
type Locale,
} from "./locale-path";
/* Reinforces the global :focus-visible rule in app.css with the same tier-2
focus tokens, so the ring is visible even if a caller overrides `outline`. */
const FOCUS_RING =
"focus-visible:outline-[color:var(--focus-ring-color)] " +
"focus-visible:outline-[length:var(--focus-ring-width)] " +
"focus-visible:outline-offset-[var(--focus-ring-offset)]";
/** Padding presets. Kept as a prop rather than an overridable class, because
two padding utilities on one element are resolved by the order Tailwind
emits them in, not by the order they appear in the class attribute. */
const SIZE_CLASSES = {
sm: "px-sm py-2xs",
md: "min-h-[44px] px-md py-xs",
} as const;
export type LanguageSwitcherSize = keyof typeof SIZE_CLASSES;
export interface LanguageSwitcherProps {
/** The locale the reader is currently on. */
locale: Locale;
/** Current pathname, e.g. "/en/news-and-updates". */
pathname: string;
/** Query string to carry across the switch. Usually `location.search`. */
search?: string;
/** Fragment to carry across the switch. Usually `location.hash`. */
hash?: string;
/**
* Navigate with a full document request instead of a client transition.
* Off by default: the root route re-renders <html lang>/<html dir> on a
* client navigation, so a soft transition already lands on a correct
* document. Turn it on if a page needs the server to re-render first.
*/
reloadDocument?: boolean;
/** "sm" for a compact pill, "md" for a 44px touch target in the drawer. */
size?: LanguageSwitcherSize;
className?: string;
}
/**
* Compact language toggle: shows the two-letter code of the OTHER language
* ("AR" while reading English, "EN" while reading Arabic) and links to the
* same page under that locale, preserving the rest of the path.
*
* The mobile drawer uses this, where a spelled-out target is clearer than an
* icon.
*/
export function LanguageSwitcher({
locale,
pathname,
search,
hash,
reloadDocument = false,
size = "sm",
className = "",
}: LanguageSwitcherProps) {
const target = getCounterpartLocale(locale);
const href = getCounterpartPath(pathname, locale, { search, hash });
return (
<Link
to={href}
reloadDocument={reloadDocument}
hrefLang={target}
lang={target}
dir={getDirection(target)}
aria-label={LOCALE_ENDONYM[target]}
title={LOCALE_ENDONYM[target]}
data-language-switcher={target}
className={[
"label-md inline-flex shrink-0 items-center justify-center rounded-pill",
"border border-[var(--border-default)]",
SIZE_CLASSES[size],
"text-[var(--text-primary)] no-underline",
"transition-colors duration-[var(--motion-duration-fast)]",
"ease-[var(--motion-ease-standard)]",
"hover:border-[var(--border-strong)] hover:bg-[var(--surface-muted)]",
FOCUS_RING,
className,
]
.filter(Boolean)
.join(" ")}
>
<span aria-hidden="true">{LOCALE_SHORT_LABEL[target]}</span>
</Link>
);
}
export default LanguageSwitcher;
/* ---------------------------------------------------------------------------
USAGE
---------------------------------------------------------------------------
// Reading English at /en/news-and-updates?page=2 -> links to
// /ar/news-and-updates?page=2 and renders the pill "AR".
<LanguageSwitcher locale="en" pathname="/en/news-and-updates" search="?page=2" />
// Reading Arabic -> renders "EN", links to /en/...
<LanguageSwitcher locale="ar" pathname="/ar/contact" />
// Inside the mobile drawer, where a 44px touch target is wanted:
<LanguageSwitcher locale={locale} pathname={pathname} size="md" />
// Force a document navigation (server re-renders <html lang>/<html dir>):
<LanguageSwitcher locale={locale} pathname={pathname} reloadDocument />
------------------------------------------------------------------------ */