95 lines
3.4 KiB
TypeScript
95 lines
3.4 KiB
TypeScript
import * as React from 'react';
|
|
import styles from './TopNav.module.scss';
|
|
import { ScreenKey } from './types';
|
|
import { texts } from '../../texts';
|
|
|
|
interface ITab {
|
|
key: ScreenKey;
|
|
label: string;
|
|
}
|
|
|
|
const TABS: ITab[] = [
|
|
{ key: 'dashboard', label: texts.nav.dashboard },
|
|
{ key: 'requestTypes', label: texts.nav.requestTypes },
|
|
{ key: 'routes', label: texts.nav.routes },
|
|
{ key: 'resolve', label: texts.nav.resolve }
|
|
];
|
|
|
|
export interface ITopNavProps {
|
|
active: ScreenKey;
|
|
onSelect: (key: ScreenKey) => void;
|
|
}
|
|
|
|
function isTabActive(active: ScreenKey, tabKey: ScreenKey): boolean {
|
|
// Экран шагов маршрута — вложенный, подсвечиваем вкладку «Маршруты».
|
|
return active === tabKey || (active === 'routeSteps' && tabKey === 'routes');
|
|
}
|
|
|
|
export const TopNav: React.FC<ITopNavProps> = ({ active, onSelect }) => {
|
|
const tabsRef = React.useRef<HTMLDivElement>(null);
|
|
const activeTabRef = React.useRef<HTMLButtonElement>(null);
|
|
|
|
// Вкладки скроллятся по горизонтали, поэтому активная может оказаться за
|
|
// краем. Подкручиваем контейнер вручную (не scrollIntoView — тот способен
|
|
// утащить за собой прокрутку всей страницы SharePoint).
|
|
React.useEffect(() => {
|
|
const reveal = (): void => {
|
|
const container = tabsRef.current;
|
|
const tab = activeTabRef.current;
|
|
if (!container || !tab) return;
|
|
|
|
const left = tab.offsetLeft;
|
|
const width = tab.offsetWidth;
|
|
|
|
if (width > container.clientWidth) {
|
|
// Вкладка шире видимой полосы — показываем её начало, чтобы подпись
|
|
// читалась с первого слова.
|
|
container.scrollLeft = left;
|
|
} else if (left < container.scrollLeft) {
|
|
container.scrollLeft = left;
|
|
} else if (left + width > container.scrollLeft + container.clientWidth) {
|
|
container.scrollLeft = left + width - container.clientWidth;
|
|
}
|
|
};
|
|
|
|
reveal();
|
|
|
|
// Шрифты (Lora/Inter) подключаются рантаймом: пока они не применились,
|
|
// ширины вкладок другие и позиция прокрутки получается устаревшей.
|
|
const fonts = (document as Document & { fonts?: FontFaceSet }).fonts;
|
|
let cancelled = false;
|
|
if (fonts?.ready) {
|
|
fonts.ready.then(() => { if (!cancelled) reveal(); }).catch(() => { /* не критично */ });
|
|
}
|
|
|
|
window.addEventListener('resize', reveal);
|
|
return () => {
|
|
cancelled = true;
|
|
window.removeEventListener('resize', reveal);
|
|
};
|
|
}, [active]);
|
|
|
|
return (
|
|
<nav className={styles.topnav}>
|
|
<div className={styles.brand}>
|
|
<div className={styles.brandText}>{texts.nav.title}</div>
|
|
</div>
|
|
<div className={styles.tabs} ref={tabsRef}>
|
|
{TABS.map(tab => {
|
|
const on = isTabActive(active, tab.key);
|
|
return (
|
|
<button
|
|
key={tab.key}
|
|
ref={on ? activeTabRef : undefined}
|
|
className={`${styles.tab} ${on ? styles.on : ''}`}
|
|
onClick={() => onSelect(tab.key)}
|
|
>
|
|
{tab.label}
|
|
</button>
|
|
);
|
|
})}
|
|
</div>
|
|
</nav>
|
|
);
|
|
};
|