initial commit

This commit is contained in:
2026-09-11 02:46:56 +02:00
commit 20b384039c
288 changed files with 46557 additions and 0 deletions
@@ -0,0 +1,121 @@
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 the desktop bar, "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 Figma navigation has no language switcher - a gap recorded in
* docs/SHARP-ER_Website Recommendations.docx - so this is designed to sit in
* the existing bar: label-md on a pill outline, matching the EU badge height.
*/
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 />
// Wired from a route, using the router's own location:
const { pathname, search, hash } = useLocation();
<LanguageSwitcher locale={locale} pathname={pathname} search={search} hash={hash} />
------------------------------------------------------------------------ */