@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.
485 lines (484 loc) • 18.4 kB
JavaScript
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _react = _interopRequireDefault(require("react"));
var _classnames = _interopRequireDefault(require("classnames"));
var _propTypes = _interopRequireDefault(require("prop-types"));
var _constants = require("@douyinfe/semi-foundation/lib/cjs/userGuide/constants");
var _foundation = _interopRequireDefault(require("@douyinfe/semi-foundation/lib/cjs/userGuide/foundation"));
var _baseComponent = _interopRequireDefault(require("../_base/baseComponent"));
var _popover = _interopRequireDefault(require("../popover"));
var _button = _interopRequireDefault(require("../button"));
var _modal = _interopRequireDefault(require("../modal"));
var _function = require("@douyinfe/semi-foundation/lib/cjs/utils/function");
require("@douyinfe/semi-foundation/lib/cjs/userGuide/userGuide.css");
var _isNullOrUndefined = _interopRequireDefault(require("@douyinfe/semi-foundation/lib/cjs/utils/isNullOrUndefined"));
var _uuid = require("@douyinfe/semi-foundation/lib/cjs/utils/uuid");
var _localeConsumer = _interopRequireDefault(require("../locale/localeConsumer"));
var _utils = require("../_utils");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
var __awaiter = void 0 && (void 0).__awaiter || function (thisArg, _arguments, P, generator) {
function adopt(value) {
return value instanceof P ? value : new P(function (resolve) {
resolve(value);
});
}
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) {
try {
step(generator.next(value));
} catch (e) {
reject(e);
}
}
function rejected(value) {
try {
step(generator["throw"](value));
} catch (e) {
reject(e);
}
}
function step(result) {
result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
}
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
const prefixCls = _constants.cssClasses.PREFIX;
class UserGuide extends _baseComponent.default {
constructor(props) {
super(props);
this.renderStep = (step, index) => {
const {
theme,
position,
visible,
className,
style,
spotlightPadding
} = this.props;
const {
current
} = this.state;
const isCurrentStep = current === index;
if (!step.target) {
return null;
}
const basePopoverStyle = {
padding: 0
};
const target = typeof step.target === 'function' ? step.target() : step.target;
const rect = target.getBoundingClientRect();
const padding = (step === null || step === void 0 ? void 0 : step.spotlightPadding) || spotlightPadding || _constants.numbers.DEFAULT_SPOTLIGHT_PADDING;
const isPrimaryTheme = theme === 'primary' || (step === null || step === void 0 ? void 0 : step.theme) === 'primary';
const primaryStyle = isPrimaryTheme ? {
backgroundColor: 'var(--semi-color-primary)'
} : {};
return /*#__PURE__*/_react.default.createElement(_popover.default, {
key: `userGuide-popup-${index}`,
className: (0, _classnames.default)(`${prefixCls}-popover`, className),
style: Object.assign(Object.assign(Object.assign({}, basePopoverStyle), primaryStyle), style),
content: this.renderPopupContent(step, index),
position: step.position || position,
trigger: "custom",
visible: visible && isCurrentStep,
showArrow: step.showArrow !== false
}, /*#__PURE__*/_react.default.createElement("div", {
style: {
position: 'fixed',
left: rect.x - padding,
top: rect.y - padding,
width: rect.width + padding * 2,
height: rect.height + padding * 2,
pointerEvents: 'none'
}
}));
};
this.renderIndicator = () => {
const {
steps
} = this.props;
const {
current
} = this.state;
const indicatorContent = [];
for (let i = 0; i < steps.length; i++) {
indicatorContent.push(/*#__PURE__*/_react.default.createElement("span", {
key: i,
"data-index": i,
className: (0, _classnames.default)([`${_constants.cssClasses.PREFIX_MODAL}-indicator-item`], {
[`${_constants.cssClasses.PREFIX_MODAL}-indicator-item-active`]: i === current
})
}));
}
return indicatorContent;
};
this.renderModal = () => {
const {
visible,
steps,
showSkipButton,
showPrevButton,
finishText,
nextButtonProps,
prevButtonProps,
mask
} = this.props;
const {
current
} = this.state;
const step = steps[current];
const isFirst = current === 0;
const isLast = current === steps.length - 1;
const {
cover,
title,
description
} = step;
return /*#__PURE__*/_react.default.createElement(_localeConsumer.default, {
componentName: "UserGuide"
}, (locale, localeCode) => (/*#__PURE__*/_react.default.createElement(_modal.default, {
className: _constants.cssClasses.PREFIX_MODAL,
bodyStyle: {
padding: 0
},
header: null,
visible: visible,
maskClosable: false,
mask: mask,
centered: true,
footer: null
}, cover && /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement("div", {
className: `${_constants.cssClasses.PREFIX_MODAL}-cover`
}, cover), /*#__PURE__*/_react.default.createElement("div", {
className: `${_constants.cssClasses.PREFIX_MODAL}-indicator`
}, this.renderIndicator())), (title || description) && (/*#__PURE__*/_react.default.createElement("div", {
className: `${_constants.cssClasses.PREFIX_MODAL}-body`
}, title && /*#__PURE__*/_react.default.createElement("div", {
className: `${_constants.cssClasses.PREFIX_MODAL}-body-title`
}, title), description && /*#__PURE__*/_react.default.createElement("div", {
className: `${_constants.cssClasses.PREFIX_MODAL}-body-description`
}, description))), /*#__PURE__*/_react.default.createElement("div", {
className: `${_constants.cssClasses.PREFIX_MODAL}-footer`
}, showSkipButton && !isLast && (/*#__PURE__*/_react.default.createElement(_button.default, {
type: 'tertiary',
onClick: this.foundation.handleSkip
}, locale.skip)), showPrevButton && !isFirst && (/*#__PURE__*/_react.default.createElement(_button.default, Object.assign({
type: 'tertiary',
onClick: this.foundation.handlePrev
}, prevButtonProps), (prevButtonProps === null || prevButtonProps === void 0 ? void 0 : prevButtonProps.children) || locale.prev)), /*#__PURE__*/_react.default.createElement(_button.default, Object.assign({
theme: 'solid',
onClick: this.foundation.handleNext
}, nextButtonProps), isLast ? finishText || locale.finish : (nextButtonProps === null || nextButtonProps === void 0 ? void 0 : nextButtonProps.children) || locale.next)))));
};
this.foundation = new _foundation.default(this.adapter);
this.state = {
current: props.current || _constants.numbers.DEFAULT_CURRENT,
spotlightRect: null
};
this.scrollBarWidth = 0;
this.userGuideId = '';
}
get adapter() {
return Object.assign(Object.assign({}, super.adapter), {
disabledBodyScroll: () => {
const {
getPopupContainer
} = this.props;
this.bodyOverflow = document.body.style.overflow || '';
if (!getPopupContainer && this.bodyOverflow !== 'hidden') {
document.body.style.overflow = 'hidden';
document.body.style.width = `calc(${this.originBodyWidth || '100%'} - ${this.scrollBarWidth}px)`;
}
},
enabledBodyScroll: () => {
const {
getPopupContainer
} = this.props;
if (!getPopupContainer && this.bodyOverflow !== 'hidden') {
document.body.style.overflow = this.bodyOverflow;
document.body.style.width = this.originBodyWidth;
}
},
notifyChange: current => {
this.props.onChange(current);
},
notifyFinish: () => {
this.props.onFinish();
},
notifyNext: current => {
this.props.onNext(current);
},
notifyPrev: current => {
this.props.onPrev(current);
},
notifySkip: () => {
this.props.onSkip();
},
setCurrent: current => {
this.setState({
current
});
}
});
}
static getDerivedStateFromProps(props, state) {
const states = {};
if (!(0, _isNullOrUndefined.default)(props.current) && props.current !== state.current) {
states.current = props.current;
}
return states;
}
componentDidMount() {
this.foundation.init();
this.scrollBarWidth = (0, _utils.getScrollbarWidth)();
this.userGuideId = (0, _uuid.getUuidShort)();
}
componentDidUpdate(prevProps, prevStates) {
const {
steps,
mode,
visible
} = this.props;
const {
current
} = this.state;
if (visible !== prevProps.visible) {
if (visible) {
this.foundation.beforeShow();
this.setState({
current: 0
});
} else {
this.foundation.afterHide();
}
}
if (mode === 'popup' && prevStates.current !== current && steps[current] || prevProps.visible !== visible) {
this.updateSpotlightRect();
}
}
componentWillUnmount() {
this.foundation.destroy();
}
scrollTargetIntoViewIfNeeded(target) {
if (!target) {
return;
}
const rect = target.getBoundingClientRect();
const isInViewport = rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth);
if (!isInViewport) {
target.scrollIntoView({
behavior: 'auto',
block: 'center'
});
}
}
updateSpotlightRect() {
return __awaiter(this, void 0, void 0, function* () {
const {
steps,
spotlightPadding
} = this.props;
const {
current
} = this.state;
const step = steps[current];
if (step.target) {
const target = typeof step.target === 'function' ? step.target() : step.target;
// Checks if the target element is within the viewport, and scrolls it into view if not
this.scrollTargetIntoViewIfNeeded(target);
const rect = target === null || target === void 0 ? void 0 : target.getBoundingClientRect();
const padding = (step === null || step === void 0 ? void 0 : step.spotlightPadding) || spotlightPadding || _constants.numbers.DEFAULT_SPOTLIGHT_PADDING;
const newRects = new DOMRect(rect.x - padding, rect.y - padding, rect.width + padding * 2, rect.height + padding * 2);
requestAnimationFrame(() => {
this.setState({
spotlightRect: newRects
});
});
}
});
}
renderPopupContent(step, index) {
const {
showPrevButton,
showSkipButton,
theme,
steps,
finishText,
nextButtonProps,
prevButtonProps
} = this.props;
const {
current
} = this.state;
const isFirst = index === 0;
const isLast = index === steps.length - 1;
const popupPrefixCls = `${prefixCls}-popup-content`;
const isPrimaryTheme = theme === 'primary' || (step === null || step === void 0 ? void 0 : step.theme) === 'primary';
const {
cover,
title,
description
} = step;
return /*#__PURE__*/_react.default.createElement(_localeConsumer.default, {
componentName: "UserGuide"
}, (locale, localeCode) => (/*#__PURE__*/_react.default.createElement("div", {
className: (0, _classnames.default)(`${popupPrefixCls}`, {
[`${popupPrefixCls}-primary`]: isPrimaryTheme
})
}, cover && /*#__PURE__*/_react.default.createElement("div", {
className: `${popupPrefixCls}-cover`
}, cover), /*#__PURE__*/_react.default.createElement("div", {
className: `${popupPrefixCls}-body`
}, title && /*#__PURE__*/_react.default.createElement("div", {
className: `${popupPrefixCls}-title`
}, title), description && /*#__PURE__*/_react.default.createElement("div", {
className: `${popupPrefixCls}-description`
}, description), /*#__PURE__*/_react.default.createElement("div", {
className: `${popupPrefixCls}-footer`
}, steps.length > 1 && (/*#__PURE__*/_react.default.createElement("div", {
className: `${popupPrefixCls}-indicator`
}, current + 1, "/", steps.length)), /*#__PURE__*/_react.default.createElement("div", {
className: `${popupPrefixCls}-buttons`
}, showSkipButton && !isLast && (/*#__PURE__*/_react.default.createElement(_button.default, {
style: isPrimaryTheme ? {
backgroundColor: 'var(--semi-color-fill-2)'
} : {},
theme: isPrimaryTheme ? 'solid' : 'light',
type: isPrimaryTheme ? 'primary' : 'tertiary',
onClick: this.foundation.handleSkip
}, locale.skip)), showPrevButton && !isFirst && (/*#__PURE__*/_react.default.createElement(_button.default, Object.assign({
style: isPrimaryTheme ? {
backgroundColor: 'var(--semi-color-fill-2)'
} : {},
theme: isPrimaryTheme ? 'solid' : 'light',
type: isPrimaryTheme ? 'primary' : 'tertiary',
onClick: this.foundation.handlePrev
}, prevButtonProps), (prevButtonProps === null || prevButtonProps === void 0 ? void 0 : prevButtonProps.children) || locale.prev)), /*#__PURE__*/_react.default.createElement(_button.default, Object.assign({
style: isPrimaryTheme ? {
backgroundColor: '#FFF'
} : {},
theme: isPrimaryTheme ? 'borderless' : 'solid',
type: 'primary',
onClick: this.foundation.handleNext
}, nextButtonProps), isLast ? finishText || locale.finish : (nextButtonProps === null || nextButtonProps === void 0 ? void 0 : nextButtonProps.children) || locale.next)))))));
}
renderSpotlight() {
const {
steps,
mask,
zIndex
} = this.props;
const {
spotlightRect,
current
} = this.state;
const step = steps[current];
if (!step.target) {
return null;
}
if (!spotlightRect) {
this.updateSpotlightRect();
}
return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, spotlightRect ? (/*#__PURE__*/_react.default.createElement("svg", {
className: `${prefixCls}-spotlight`,
style: {
zIndex
}
}, /*#__PURE__*/_react.default.createElement("defs", null, /*#__PURE__*/_react.default.createElement("mask", {
id: `spotlight-${this.userGuideId}`
}, /*#__PURE__*/_react.default.createElement("rect", {
width: "100%",
height: "100%",
fill: "white"
}), /*#__PURE__*/_react.default.createElement("rect", {
className: `${prefixCls}-spotlight-rect`,
x: spotlightRect.x,
y: spotlightRect.y,
width: spotlightRect.width,
height: spotlightRect.height,
rx: 4,
fill: "black"
}))), mask && (/*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement("rect", {
width: "100%",
height: "100%",
fill: "var(--semi-color-overlay-bg)",
mask: `url(#spotlight-${this.userGuideId})`
}), /*#__PURE__*/_react.default.createElement("rect", {
x: 0,
y: 0,
width: "100%",
height: spotlightRect.y,
fill: "transparent",
className: `${prefixCls}-spotlight-transparent-rect`
}), /*#__PURE__*/_react.default.createElement("rect", {
x: 0,
y: spotlightRect.y,
width: spotlightRect.x,
height: spotlightRect.height,
fill: "transparent",
className: `${prefixCls}-spotlight-transparent-rect`
}), /*#__PURE__*/_react.default.createElement("rect", {
x: spotlightRect.x + spotlightRect.width,
y: spotlightRect.y,
width: `calc(100% - ${spotlightRect.x + spotlightRect.width}px)`,
height: spotlightRect.height,
fill: "transparent",
className: `${prefixCls}-spotlight-transparent-rect`
}), /*#__PURE__*/_react.default.createElement("rect", {
y: spotlightRect.y + spotlightRect.height,
width: "100%",
height: `calc(100% - ${spotlightRect.y + spotlightRect.height}px)`,
fill: "transparent",
className: `${prefixCls}-spotlight-transparent-rect`
}))))) : null);
}
render() {
const {
mode,
steps,
visible
} = this.props;
if (!visible || !steps.length) {
return null;
}
return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, mode === 'popup' ? (/*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, steps === null || steps === void 0 ? void 0 : steps.map((step, index) => this.renderStep(step, index)), this.renderSpotlight())) : null, mode === 'modal' && this.renderModal());
}
}
UserGuide.propTypes = {
mask: _propTypes.default.bool,
mode: _propTypes.default.oneOf(_constants.strings.MODE),
onChange: _propTypes.default.func,
onFinish: _propTypes.default.func,
onNext: _propTypes.default.func,
onPrev: _propTypes.default.func,
onSkip: _propTypes.default.func,
position: _propTypes.default.oneOf(_constants.strings.POSITION_SET),
showPrevButton: _propTypes.default.bool,
showSkipButton: _propTypes.default.bool,
theme: _propTypes.default.oneOf(_constants.strings.THEME),
visible: _propTypes.default.bool,
getPopupContainer: _propTypes.default.func,
zIndex: _propTypes.default.number
};
UserGuide.defaultProps = {
mask: true,
mode: 'popup',
nextButtonProps: {},
onChange: _function.noop,
onFinish: _function.noop,
onNext: _function.noop,
onPrev: _function.noop,
onSkip: _function.noop,
position: 'bottom',
prevButtonProps: {},
showPrevButton: true,
showSkipButton: true,
steps: [],
theme: 'default',
visible: false,
zIndex: _constants.numbers.DEFAULT_Z_INDEX
};
var _default = exports.default = UserGuide;
;