1 | import React, { forwardRef, useRef, useState, useImperativeHandle } from 'react';
|
2 | import classNames from 'classnames';
|
3 | import { withNativeProps } from '../../utils/native-props';
|
4 | import { useThrottleFn } from 'ahooks';
|
5 | import { mergeProps } from '../../utils/with-default-props';
|
6 | import { Sidebar } from './sidebar';
|
7 | import { convertPx } from '../../utils/convert-px';
|
8 | import { Panel } from './panel';
|
9 | import { devWarning } from '../../utils/dev-log';
|
10 | import { traverseReactNode } from '../../utils/traverse-react-node';
|
11 | const classPrefix = `adm-index-bar`;
|
12 | const defaultProps = {
|
13 | sticky: true
|
14 | };
|
15 | export 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 |