UNPKG

@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
"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;