правка navbara

This commit is contained in:
gorbenko.pavel
2026-07-29 15:22:25 +05:00
parent ae940a9240
commit 8ef7acd93f
2 changed files with 96 additions and 21 deletions
@@ -8,7 +8,11 @@
height: 54px;
box-shadow: 0 1px 0 rgba(212,160,30,.35);
border-radius: $r-md $r-md 0 0;
flex-wrap: wrap;
// Без переноса: высота шапки фиксирована (54px), поэтому перенесённая
// строка вкладок вылезала бы за её пределы вместе с подчёркиванием.
// Вместо переноса вкладки скроллятся по горизонтали.
flex-wrap: nowrap;
overflow: hidden;
}
.brand {
@@ -49,10 +53,27 @@
.tabs {
display: flex;
height: 100%;
flex-wrap: wrap;
flex-wrap: nowrap;
// min-width: 0 обязателен — иначе flex-элемент не сожмётся уже своего
// содержимого и overflow-x никогда не сработает.
min-width: 0;
overflow-x: auto;
overflow-y: hidden;
// Делает контейнер offsetParent'ом для вкладок: автопрокрутка в TopNav.tsx
// считает offsetLeft относительно него. Без этого offsetLeft отсчитывался
// бы от body и прокрутка уезжала бы не туда.
position: relative;
// Полоса прокрутки на тёмной шапке выглядит инородно, скрываем её —
// вкладки остаются доступны свайпом/колесом.
scrollbar-width: none;
-ms-overflow-style: none;
&::-webkit-scrollbar { display: none; }
}
.tab {
flex-shrink: 0;
white-space: nowrap;
padding: 0 16px;
border: none;
background: 0;
@@ -19,22 +19,76 @@ export interface ITopNavProps {
onSelect: (key: ScreenKey) => void;
}
export const TopNav: React.FC<ITopNavProps> = ({ active, onSelect }) => (
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}>T</div>
<div className={styles.brandText}>TNSU <em>Approval Route</em></div>
</div>
<div className={styles.tabs}>
{TABS.map(tab => (
<div className={styles.tabs} ref={tabsRef}>
{TABS.map(tab => {
const on = isTabActive(active, tab.key);
return (
<button
key={tab.key}
className={`${styles.tab} ${(active === tab.key || (active === 'routeSteps' && tab.key === 'routes')) ? styles.on : ''}`}
ref={on ? activeTabRef : undefined}
className={`${styles.tab} ${on ? styles.on : ''}`}
onClick={() => onSelect(tab.key)}
>
{tab.label}
</button>
))}
);
})}
</div>
</nav>
);
);
};