@douyinfe/semi-ui
Version:
A modern, comprehensive, flexible design system and UI library. Connect DesignOps & DevOps. Quickly build beautiful React apps. Maintained by Douyin-fe team.
311 lines (310 loc) • 10.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _throttle2 = _interopRequireDefault(require("lodash/throttle"));
var _debounce2 = _interopRequireDefault(require("lodash/debounce"));
var _noop2 = _interopRequireDefault(require("lodash/noop"));
var _react = _interopRequireDefault(require("react"));
var _classnames = _interopRequireDefault(require("classnames"));
var _propTypes = _interopRequireDefault(require("prop-types"));
var _constants = require("@douyinfe/semi-foundation/lib/cjs/anchor/constants");
var _foundation = _interopRequireDefault(require("@douyinfe/semi-foundation/lib/cjs/anchor/foundation"));
var _baseComponent = _interopRequireDefault(require("../_base/baseComponent"));
var _link = _interopRequireDefault(require("./link"));
var _anchorContext = _interopRequireDefault(require("./anchor-context"));
require("@douyinfe/semi-foundation/lib/cjs/anchor/anchor.css");
var _uuid = _interopRequireDefault(require("@douyinfe/semi-foundation/lib/cjs/utils/uuid"));
var _context = _interopRequireDefault(require("../configProvider/context"));
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
const prefixCls = _constants.cssClasses.PREFIX;
class Anchor extends _baseComponent.default {
constructor(props) {
var _this;
super(props);
_this = this;
this.addLink = link => {
this.foundation.addLink(link);
};
this.removeLink = link => {
this.foundation.removeLink(link);
};
this.handleScroll = () => {
this.foundation.handleScroll();
};
this.handleClick = (e, link) => {
this.foundation.handleClick(e, link);
};
// Set click to false after scrolling
this.handleClickLink = () => {
this.foundation.handleClickLink();
};
this.setChildMap = () => {
this.foundation.setChildMap();
};
this.setScrollHeight = () => {
this.foundation.setScrollHeight();
};
this.updateScrollHeight = (prevState, state) => {
this.foundation.updateScrollHeight(prevState, state);
};
this.updateChildMap = (prevState, state) => {
this.foundation.updateChildMap(prevState, state);
};
this.renderChildren = () => {
const loop = function (children) {
let level = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
return _react.default.Children.map(children, child => {
if (/*#__PURE__*/_react.default.isValidElement(child)) {
const childProps = {
direction: _this.context.direction,
level,
children: []
};
const {
children
} = child.props;
const hasChildren = children && _react.default.Children.count(children) > 0;
if (hasChildren) {
childProps.children = loop(children, level + 1);
}
return /*#__PURE__*/_react.default.cloneElement(child, childProps);
}
return null;
});
};
return loop(this.props.children);
};
this.state = {
activeLink: '',
links: [],
clickLink: false,
scrollHeight: '100%',
slideBarTop: '0'
};
this.foundation = new _foundation.default(this.adapter);
this.childMap = {};
}
get adapter() {
return Object.assign(Object.assign({}, super.adapter), {
addLink: value => {
this.setState(prevState => ({
links: [...prevState.links, value]
}));
},
removeLink: link => {
this.setState(prevState => {
const links = prevState.links.slice();
const index = links.indexOf(link);
if (index !== -1) {
links.splice(index, 1);
return {
links
};
}
return undefined;
});
},
setChildMap: value => {
this.childMap = value;
},
setScrollHeight: height => {
this.setState({
scrollHeight: height
});
},
setSlideBarTop: height => {
this.setState({
slideBarTop: `${height}px`
});
},
setClickLink: value => {
this.setState({
clickLink: value
});
},
setActiveLink: (link, cb) => {
this.setState({
activeLink: link
}, () => {
cb();
});
},
setClickLinkWithCallBack: (value, link, cb) => {
this.setState({
clickLink: value
}, () => {
cb(link);
});
},
getContainer: () => {
const {
getContainer
} = this.props;
const container = getContainer();
return container ? container : window;
},
getContainerBoundingTop: () => {
const container = this.adapter.getContainer();
if ('getBoundingClientRect' in container) {
return container.getBoundingClientRect().top;
}
return 0;
},
getLinksBoundingTop: () => {
const {
links
} = this.state;
const {
offsetTop
} = this.props;
const containerTop = this.adapter.getContainerBoundingTop();
const elTop = links.map(link => {
let node = null;
try {
// Get links from containers
node = document.querySelector(link);
} catch (e) {}
return node && node.getBoundingClientRect().top - containerTop - offsetTop || -Infinity;
});
return elTop;
},
getAnchorNode: selector => {
const selectors = `#${this.anchorID} ${selector}`;
return document.querySelector(selectors);
},
getContentNode: selector => document.querySelector(selector),
notifyChange: (currentLink, previousLink) => this.props.onChange(currentLink, previousLink),
notifyClick: (e, link) => this.props.onClick(e, link),
canSmoothScroll: () => 'scrollBehavior' in document.body.style
});
}
componentDidMount() {
const {
defaultAnchor = ''
} = this.props;
this.anchorID = (0, _uuid.default)('semi-anchor').replace('.', '');
this.scrollContainer = this.adapter.getContainer();
this.handler = (0, _throttle2.default)(this.handleScroll, 100);
this.clickHandler = (0, _debounce2.default)(this.handleClickLink, 100);
this.scrollContainer.addEventListener('scroll', this.handler);
this.scrollContainer.addEventListener('scroll', this.clickHandler);
this.setScrollHeight();
this.setChildMap();
Boolean(defaultAnchor) && this.foundation.handleClick(null, defaultAnchor, false);
}
componentDidUpdate(prevProps, prevState) {
this.updateScrollHeight(prevState, this.state);
this.updateChildMap(prevState, this.state);
}
componentWillUnmount() {
this.scrollContainer.removeEventListener('scroll', this.handler);
this.scrollContainer.removeEventListener('scroll', this.clickHandler);
}
render() {
const {
size,
railTheme,
style,
className,
children,
maxWidth,
maxHeight,
showTooltip,
position,
autoCollapse
} = this.props;
const ariaLabel = this.props['aria-label'];
const {
activeLink,
scrollHeight,
slideBarTop
} = this.state;
const wrapperCls = (0, _classnames.default)(prefixCls, className, {
[`${prefixCls}-size-${size}`]: size
});
const slideCls = (0, _classnames.default)(`${prefixCls}-slide`, `${prefixCls}-slide-${railTheme}`);
const slideBarCls = (0, _classnames.default)(`${prefixCls}-slide-bar`, {
[`${prefixCls}-slide-bar-${size}`]: size,
[`${prefixCls}-slide-bar-${railTheme}`]: railTheme,
[`${prefixCls}-slide-bar-active`]: activeLink
});
const anchorWrapper = `${prefixCls}-link-wrapper`;
const wrapperStyle = Object.assign(Object.assign({}, style), {
maxWidth,
maxHeight
});
return /*#__PURE__*/_react.default.createElement(_anchorContext.default.Provider, {
value: {
activeLink,
showTooltip,
position,
childMap: this.childMap,
autoCollapse,
size,
onClick: (e, link) => this.handleClick(e, link),
addLink: this.addLink,
removeLink: this.removeLink
}
}, /*#__PURE__*/_react.default.createElement("div", Object.assign({
role: "navigation",
"aria-label": ariaLabel || 'Side navigation',
className: wrapperCls,
style: wrapperStyle,
id: this.anchorID
}, this.getDataAttr(this.props)), /*#__PURE__*/_react.default.createElement("div", {
"aria-hidden": true,
className: slideCls,
style: {
height: scrollHeight
}
}, /*#__PURE__*/_react.default.createElement("span", {
className: slideBarCls,
style: {
top: slideBarTop
}
})), /*#__PURE__*/_react.default.createElement("div", {
className: anchorWrapper,
role: "list"
}, this.renderChildren())));
}
}
Anchor.contextType = _context.default;
Anchor.Link = _link.default;
Anchor.PropTypes = {
size: _propTypes.default.oneOf(_constants.strings.SIZE),
railTheme: _propTypes.default.oneOf(_constants.strings.SLIDE_COLOR),
className: _propTypes.default.string,
style: _propTypes.default.object,
scrollMotion: _propTypes.default.bool,
autoCollapse: _propTypes.default.bool,
offsetTop: _propTypes.default.number,
targetOffset: _propTypes.default.number,
showTooltip: _propTypes.default.bool,
position: _propTypes.default.oneOf(_constants.strings.POSITION_SET),
maxWidth: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.number]),
maxHeight: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.number]),
getContainer: _propTypes.default.func,
onChange: _propTypes.default.func,
onClick: _propTypes.default.func,
defaultAnchor: _propTypes.default.string,
'aria-label': _propTypes.default.string
};
Anchor.defaultProps = {
size: 'default',
railTheme: 'primary',
className: '',
scrollMotion: false,
autoCollapse: false,
offsetTop: 0,
targetOffset: 0,
showTooltip: false,
maxWidth: _constants.strings.MAX_WIDTH,
maxHeight: _constants.strings.MAX_HEIGHT,
getContainer: _noop2.default,
onChange: _noop2.default,
onClick: _noop2.default,
defaultAnchor: ''
};
var _default = exports.default = Anchor;