d7fb7b7a7b
Build & Deploy / Build & Push Docker Image (push) Failing after 47s
Build & Deploy / Deploy to VPS (push) Has been skipped
Test / API Unit Tests (push) Failing after 5m4s
Test / Marketplace Unit Tests (push) Failing after 4m55s
Test / Admin Unit Tests (push) Successful in 9m37s
Test / Dashboard Unit Tests (push) Successful in 9m37s
Test / API Integration Tests (push) Successful in 9m54s
147 lines
4.2 KiB
TypeScript
147 lines
4.2 KiB
TypeScript
'use client';
|
||
|
||
import { ActionLink } from '@/components/actions/ActionLink';
|
||
import { DemoTrigger } from '@/components/integrations/DemoTrigger';
|
||
import type { ShellMessages } from '@/lib/localization/messages';
|
||
import { localizedPath, type Locale } from '@/lib/localization/config';
|
||
import type { ThemePreference } from '@/lib/theme/config';
|
||
import { useEffect, useRef, useState } from 'react';
|
||
import { HeaderNavigation } from './HeaderNavigation';
|
||
import { LocaleSelector } from './LocaleSelector';
|
||
import styles from './SiteHeader.module.css';
|
||
import { ThemeSelector } from './ThemeSelector';
|
||
|
||
function signInUrl(locale: Locale): string {
|
||
return localizedPath('sign-in', locale);
|
||
}
|
||
|
||
interface MobileNavigationProps {
|
||
locale: Locale;
|
||
messages: ShellMessages;
|
||
themePreference: ThemePreference;
|
||
demoEnabled: boolean;
|
||
}
|
||
|
||
export function MobileNavigation({
|
||
locale,
|
||
messages,
|
||
themePreference,
|
||
demoEnabled,
|
||
}: MobileNavigationProps) {
|
||
const [open, setOpen] = useState(false);
|
||
const dialogRef = useRef<HTMLDialogElement>(null);
|
||
const triggerRef = useRef<HTMLButtonElement>(null);
|
||
const closeRef = useRef<HTMLButtonElement>(null);
|
||
|
||
useEffect(() => {
|
||
const dialog = dialogRef.current;
|
||
if (!dialog) return;
|
||
|
||
if (open && !dialog.open) {
|
||
dialog.showModal();
|
||
document.body.dataset.navigationOpen = 'true';
|
||
closeRef.current?.focus();
|
||
} else if (!open && dialog.open) {
|
||
dialog.close();
|
||
}
|
||
|
||
return () => {
|
||
delete document.body.dataset.navigationOpen;
|
||
};
|
||
}, [open]);
|
||
|
||
const close = () => {
|
||
dialogRef.current?.close();
|
||
setOpen(false);
|
||
delete document.body.dataset.navigationOpen;
|
||
window.requestAnimationFrame(() => triggerRef.current?.focus());
|
||
};
|
||
|
||
return (
|
||
<div className={styles.mobileNavigation}>
|
||
<button
|
||
ref={triggerRef}
|
||
type="button"
|
||
className={styles.menuButton}
|
||
aria-label={messages.header.menuOpen}
|
||
aria-haspopup="dialog"
|
||
aria-expanded={open}
|
||
onClick={() => setOpen(true)}
|
||
>
|
||
<span aria-hidden="true" className={styles.menuIcon}>
|
||
<span />
|
||
<span />
|
||
<span />
|
||
</span>
|
||
</button>
|
||
|
||
<dialog
|
||
ref={dialogRef}
|
||
className={styles.drawer}
|
||
aria-label={messages.header.navigationLabel}
|
||
onClose={() => {
|
||
setOpen(false);
|
||
delete document.body.dataset.navigationOpen;
|
||
}}
|
||
onCancel={(event) => {
|
||
event.preventDefault();
|
||
close();
|
||
}}
|
||
>
|
||
<div className={styles.drawerPanel}>
|
||
<div className={styles.drawerHeader}>
|
||
<strong>RentalDriveGo</strong>
|
||
<button
|
||
ref={closeRef}
|
||
type="button"
|
||
className={styles.closeButton}
|
||
aria-label={messages.header.menuClose}
|
||
onClick={close}
|
||
>
|
||
<span aria-hidden="true">×</span>
|
||
</button>
|
||
</div>
|
||
|
||
<HeaderNavigation
|
||
locale={locale}
|
||
label={messages.header.navigationLabel}
|
||
labels={messages.header.nav}
|
||
mobile
|
||
onNavigate={close}
|
||
/>
|
||
|
||
<div
|
||
className={styles.drawerControls}
|
||
role="group"
|
||
aria-label={messages.header.controlsLabel}
|
||
>
|
||
<LocaleSelector
|
||
currentLocale={locale}
|
||
label={messages.controls.language}
|
||
localeNames={messages.controls.localeNames}
|
||
/>
|
||
<ThemeSelector
|
||
initialPreference={themePreference}
|
||
label={messages.controls.theme}
|
||
statusTemplate={messages.controls.themeStatus}
|
||
optionLabels={messages.controls.themes}
|
||
/>
|
||
</div>
|
||
|
||
<div className={styles.drawerActions}>
|
||
<ActionLink href={signInUrl(locale)} variant="button-primary">
|
||
{messages.header.login}
|
||
</ActionLink>
|
||
<DemoTrigger
|
||
label={messages.header.demo}
|
||
source="mobile-header"
|
||
fullWidth
|
||
disabledReason={demoEnabled ? undefined : messages.header.demoUnavailable}
|
||
/>
|
||
</div>
|
||
</div>
|
||
</dialog>
|
||
</div>
|
||
);
|
||
}
|