2023-07-20 19:27:33 +08:00
|
|
|
import { createStyles } from 'antd-style';
|
2023-02-03 13:39:46 +08:00
|
|
|
import { Tabs } from 'antd';
|
|
|
|
import throttle from 'lodash/throttle';
|
2023-05-06 15:49:37 +08:00
|
|
|
import * as React from 'react';
|
2023-07-20 19:27:33 +08:00
|
|
|
import classNames from 'classnames';
|
2020-02-27 10:53:30 +08:00
|
|
|
import scrollTo from '../../../../components/_util/scrollTo';
|
|
|
|
|
2023-05-29 20:44:45 +08:00
|
|
|
const listenerEvents = ['scroll', 'resize'] as const;
|
|
|
|
|
2023-07-20 19:27:33 +08:00
|
|
|
const useStyle = createStyles(({ token, css }) => {
|
2022-11-09 12:28:04 +08:00
|
|
|
const { boxShadowSecondary, antCls } = token;
|
|
|
|
|
|
|
|
return {
|
|
|
|
affixTabs: css`
|
|
|
|
position: fixed;
|
|
|
|
top: 0;
|
|
|
|
right: 0;
|
|
|
|
left: 0;
|
|
|
|
z-index: 11;
|
|
|
|
padding: 0 40px;
|
|
|
|
background: #fff;
|
|
|
|
box-shadow: ${boxShadowSecondary};
|
2022-11-24 10:08:08 +08:00
|
|
|
transform: translate3d(0, -100%, 0);
|
2022-11-09 12:28:04 +08:00
|
|
|
opacity: 0;
|
|
|
|
transition: opacity 0.3s, transform 0.3s;
|
|
|
|
|
|
|
|
${antCls}-tabs {
|
|
|
|
max-width: 1208px;
|
|
|
|
margin: 0 auto;
|
|
|
|
|
|
|
|
${antCls}-tabs-nav {
|
|
|
|
margin: 0;
|
|
|
|
|
|
|
|
&::before {
|
|
|
|
border-bottom-color: transparent;
|
|
|
|
}
|
|
|
|
|
|
|
|
${antCls}-tabs-tab {
|
|
|
|
padding: 21px 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
affixTabsFixed: css`
|
2022-11-24 10:08:08 +08:00
|
|
|
transform: translate3d(0, 0, 0);
|
2022-11-09 12:28:04 +08:00
|
|
|
opacity: 1;
|
|
|
|
`,
|
2023-02-03 13:39:46 +08:00
|
|
|
span: css`
|
|
|
|
text-transform: capitalize;
|
|
|
|
`,
|
2022-11-09 12:28:04 +08:00
|
|
|
};
|
2023-07-20 19:27:33 +08:00
|
|
|
});
|
2020-02-27 10:53:30 +08:00
|
|
|
|
|
|
|
const VIEW_BALANCE = 32;
|
|
|
|
|
2023-02-03 13:39:46 +08:00
|
|
|
const AffixTabs: React.FC = () => {
|
2020-02-27 10:53:30 +08:00
|
|
|
const containerRef = React.useRef<HTMLDivElement>(null);
|
|
|
|
const idsRef = React.useRef<string[]>([]);
|
2020-02-28 17:59:06 +08:00
|
|
|
const [loaded, setLoaded] = React.useState(false);
|
2020-02-27 10:53:30 +08:00
|
|
|
const [fixedId, setFixedId] = React.useState<string | null>(null);
|
|
|
|
|
2023-07-20 19:27:33 +08:00
|
|
|
const {
|
|
|
|
styles: { affixTabs, affixTabsFixed, span },
|
|
|
|
} = useStyle();
|
2022-11-09 12:28:04 +08:00
|
|
|
|
2020-02-28 17:59:06 +08:00
|
|
|
function scrollToId(id: string) {
|
2021-03-19 23:03:04 +08:00
|
|
|
const targetNode = document.getElementById(id);
|
|
|
|
|
|
|
|
if (targetNode) {
|
|
|
|
const newTop = targetNode.offsetTop - containerRef.current!.offsetHeight - VIEW_BALANCE;
|
|
|
|
scrollTo(newTop);
|
|
|
|
}
|
2020-02-28 17:59:06 +08:00
|
|
|
}
|
|
|
|
|
2020-02-27 10:53:30 +08:00
|
|
|
React.useEffect(() => {
|
|
|
|
idsRef.current = Array.from(document.querySelectorAll('h2[id]')).map(({ id }) => id);
|
2020-02-28 17:59:06 +08:00
|
|
|
setLoaded(true);
|
2020-02-27 10:53:30 +08:00
|
|
|
}, []);
|
|
|
|
|
2020-02-28 17:59:06 +08:00
|
|
|
React.useEffect(() => {
|
|
|
|
const hashId = decodeURIComponent((location.hash || '').slice(1));
|
|
|
|
if (hashId) {
|
|
|
|
scrollToId(hashId);
|
|
|
|
}
|
|
|
|
}, [loaded]);
|
|
|
|
|
2020-02-27 10:53:30 +08:00
|
|
|
const onSyncAffix = React.useMemo(() => {
|
|
|
|
function doSync() {
|
2020-06-08 18:01:50 +08:00
|
|
|
const { scrollY } = window;
|
2020-02-27 10:53:30 +08:00
|
|
|
const containerHeight = containerRef.current!.offsetHeight;
|
|
|
|
|
|
|
|
for (let i = idsRef.current.length - 1; i >= 0; i -= 1) {
|
|
|
|
const id = idsRef.current[i];
|
|
|
|
const current = document.getElementById(id)!;
|
|
|
|
const offsetTop = current.offsetTop - containerHeight - VIEW_BALANCE;
|
|
|
|
|
|
|
|
if (offsetTop <= scrollY) {
|
|
|
|
setFixedId(id);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
setFixedId(null);
|
|
|
|
}
|
|
|
|
|
|
|
|
return throttle(doSync);
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
React.useEffect(() => {
|
2023-05-29 20:44:45 +08:00
|
|
|
listenerEvents.forEach((event) => window.addEventListener(event, onSyncAffix));
|
2020-02-27 10:53:30 +08:00
|
|
|
onSyncAffix();
|
|
|
|
return () => {
|
2023-05-29 20:44:45 +08:00
|
|
|
listenerEvents.forEach((event) => window.removeEventListener(event, onSyncAffix));
|
2020-02-27 10:53:30 +08:00
|
|
|
};
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
return (
|
2023-07-20 19:27:33 +08:00
|
|
|
<div className={classNames(affixTabs, fixedId && affixTabsFixed)} ref={containerRef}>
|
2020-02-27 10:53:30 +08:00
|
|
|
<Tabs
|
2023-01-16 09:55:52 +08:00
|
|
|
activeKey={fixedId}
|
2023-02-03 13:39:46 +08:00
|
|
|
onChange={scrollToId}
|
2022-11-19 13:47:33 +08:00
|
|
|
items={idsRef.current.map((id) => ({
|
2022-11-09 12:28:04 +08:00
|
|
|
key: id,
|
2023-07-20 19:27:33 +08:00
|
|
|
label: <span className={span}>{id.replace(/-/g, ' ')}</span>,
|
2022-11-09 12:28:04 +08:00
|
|
|
}))}
|
|
|
|
/>
|
2020-02-27 10:53:30 +08:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
};
|
2023-02-03 13:39:46 +08:00
|
|
|
|
|
|
|
export default AffixTabs;
|