правка navbara
This commit is contained in:
@@ -8,7 +8,11 @@
|
|||||||
height: 54px;
|
height: 54px;
|
||||||
box-shadow: 0 1px 0 rgba(212,160,30,.35);
|
box-shadow: 0 1px 0 rgba(212,160,30,.35);
|
||||||
border-radius: $r-md $r-md 0 0;
|
border-radius: $r-md $r-md 0 0;
|
||||||
flex-wrap: wrap;
|
// Без переноса: высота шапки фиксирована (54px), поэтому перенесённая
|
||||||
|
// строка вкладок вылезала бы за её пределы вместе с подчёркиванием.
|
||||||
|
// Вместо переноса вкладки скроллятся по горизонтали.
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.brand {
|
.brand {
|
||||||
@@ -49,10 +53,27 @@
|
|||||||
.tabs {
|
.tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 100%;
|
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 {
|
.tab {
|
||||||
|
flex-shrink: 0;
|
||||||
|
white-space: nowrap;
|
||||||
padding: 0 16px;
|
padding: 0 16px;
|
||||||
border: none;
|
border: none;
|
||||||
background: 0;
|
background: 0;
|
||||||
|
|||||||
@@ -19,22 +19,76 @@ export interface ITopNavProps {
|
|||||||
onSelect: (key: ScreenKey) => void;
|
onSelect: (key: ScreenKey) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TopNav: React.FC<ITopNavProps> = ({ active, onSelect }) => (
|
function isTabActive(active: ScreenKey, tabKey: ScreenKey): boolean {
|
||||||
<nav className={styles.topnav}>
|
// Экран шагов маршрута — вложенный, подсвечиваем вкладку «Маршруты».
|
||||||
<div className={styles.brand}>
|
return active === tabKey || (active === 'routeSteps' && tabKey === 'routes');
|
||||||
<div className={styles.mark}>T</div>
|
}
|
||||||
<div className={styles.brandText}>TNSU <em>Approval Route</em></div>
|
|
||||||
</div>
|
export const TopNav: React.FC<ITopNavProps> = ({ active, onSelect }) => {
|
||||||
<div className={styles.tabs}>
|
const tabsRef = React.useRef<HTMLDivElement>(null);
|
||||||
{TABS.map(tab => (
|
const activeTabRef = React.useRef<HTMLButtonElement>(null);
|
||||||
<button
|
|
||||||
key={tab.key}
|
// Вкладки скроллятся по горизонтали, поэтому активная может оказаться за
|
||||||
className={`${styles.tab} ${(active === tab.key || (active === 'routeSteps' && tab.key === 'routes')) ? styles.on : ''}`}
|
// краем. Подкручиваем контейнер вручную (не scrollIntoView — тот способен
|
||||||
onClick={() => onSelect(tab.key)}
|
// утащить за собой прокрутку всей страницы SharePoint).
|
||||||
>
|
React.useEffect(() => {
|
||||||
{tab.label}
|
const reveal = (): void => {
|
||||||
</button>
|
const container = tabsRef.current;
|
||||||
))}
|
const tab = activeTabRef.current;
|
||||||
</div>
|
if (!container || !tab) return;
|
||||||
</nav>
|
|
||||||
);
|
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} 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