Files
tnsu-approval-route/frontend/src/webparts/approvalAdmin/components/app/TopNav.tsx
T
2026-07-31 15:09:15 +05:00

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>
);
};