Co-authored-by: gorbenko.pavel <gorbenko.pavel@tansu.kz> Reviewed-on: http://git.tnsu.kz/gorbenko.pavel/tnsu-approval-route/pulls/5
This commit was merged in pull request #5.
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
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.mark}>{texts.nav.markLetter}</div>
|
||||
<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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user