UNPKG

3.56 kBJavaScriptView Raw
1import React, { forwardRef, useRef, useState, useImperativeHandle } from 'react';
2import classNames from 'classnames';
3import { withNativeProps } from '../../utils/native-props';
4import { useThrottleFn } from 'ahooks';
5import { mergeProps } from '../../utils/with-default-props';
6import { Sidebar } from './sidebar';
7import { convertPx } from '../../utils/convert-px';
8import { Panel } from './panel';
9import { devWarning } from '../../utils/dev-log';
10import { traverseReactNode } from '../../utils/traverse-react-node';
11const classPrefix = `adm-index-bar`;
12const defaultProps = {
13 sticky: true
14};
15export const IndexBar = forwardRef((p, ref) => {
16 const props = mergeProps(defaultProps, p);
17 const titleHeight = convertPx(35);
18 const bodyRef = useRef(null);
19 const indexItems = [];
20 const panels = [];
21 traverseReactNode(props.children, child => {
22 var _a;
23 if (!React.isValidElement(child)) return;
24 if (child.type !== Panel) {
25 devWarning('IndexBar', 'The children of `IndexBar` must be `IndexBar.Panel` components.');
26 return;
27 }
28 indexItems.push({
29 index: child.props.index,
30 brief: (_a = child.props.brief) !== null && _a !== void 0 ? _a : child.props.index.charAt(0)
31 });
32 panels.push(withNativeProps(child.props, React.createElement("div", {
33 key: child.props.index,
34 "data-index": child.props.index,
35 className: `${classPrefix}-anchor`
36 }, React.createElement("div", {
37 className: `${classPrefix}-anchor-title`
38 }, child.props.title || child.props.index), child.props.children)));
39 });
40 const [activeIndex, setActiveIndex] = useState(() => {
41 const firstItem = indexItems[0];
42 return firstItem ? firstItem.index : null;
43 });
44 useImperativeHandle(ref, () => ({
45 scrollTo
46 }));
47 function scrollTo(index) {
48 var _a;
49 const body = bodyRef.current;
50 if (!body) return;
51 const children = body.children;
52 for (let i = 0; i < children.length; i++) {
53 const panel = children.item(i);
54 if (!panel) continue;
55 const panelIndex = panel.dataset['index'];
56 if (panelIndex === index) {
57 body.scrollTop = panel.offsetTop;
58 setActiveIndex(index);
59 activeIndex !== index && ((_a = props.onIndexChange) === null || _a === void 0 ? void 0 : _a.call(props, index));
60 return;
61 }
62 }
63 }
64 const {
65 run: checkActiveIndex
66 } = useThrottleFn(() => {
67 var _a;
68 const body = bodyRef.current;
69 if (!body) return;
70 const scrollTop = body.scrollTop;
71 const elements = body.getElementsByClassName(`${classPrefix}-anchor`);
72 for (let i = 0; i < elements.length; i++) {
73 const panel = elements.item(i);
74 if (!panel) continue;
75 const panelIndex = panel.dataset['index'];
76 if (!panelIndex) continue;
77 if (panel.offsetTop + panel.clientHeight - titleHeight > scrollTop) {
78 setActiveIndex(panelIndex);
79 activeIndex !== panelIndex && ((_a = props.onIndexChange) === null || _a === void 0 ? void 0 : _a.call(props, panelIndex));
80 return;
81 }
82 }
83 }, {
84 wait: 50,
85 trailing: true,
86 leading: true
87 });
88 return withNativeProps(props, React.createElement("div", {
89 className: classNames(`${classPrefix}`, {
90 [`${classPrefix}-sticky`]: props.sticky
91 })
92 }, React.createElement(Sidebar, {
93 indexItems: indexItems,
94 activeIndex: activeIndex,
95 onActive: index => {
96 scrollTo(index);
97 }
98 }), React.createElement("div", {
99 className: `${classPrefix}-body`,
100 ref: bodyRef,
101 onScroll: checkActiveIndex
102 }, panels)));
103});
\No newline at end of file