1
This commit is contained in:
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019-present react-component
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
# @rc-component/tour
|
||||
|
||||
React 18 supported Tour Component.
|
||||
|
||||
[![NPM version][npm-image]][npm-url] [](https://github.com/umijs/dumi) [![build status][github-actions-image]][github-actions-url] [![Codecov][codecov-image]][codecov-url] [![npm download][download-image]][download-url]
|
||||
|
||||
[npm-image]: http://img.shields.io/npm/v/@rc-component/tour.svg?style=flat-square
|
||||
[npm-url]: http://npmjs.org/package/@rc-component/tour
|
||||
[github-actions-image]: https://github.com/react-component/tour/workflows/CI/badge.svg
|
||||
[github-actions-url]: https://github.com/react-component/tour/actions
|
||||
[codecov-image]: https://img.shields.io/codecov/c/github/react-component/tour/master.svg?style=flat-square
|
||||
[codecov-url]: https://app.codecov.io/gh/react-component/tour
|
||||
[download-image]: https://img.shields.io/npm/dm/@rc-component/tour.svg?style=flat-square
|
||||
[download-url]: https://npmjs.org/package/@rc-component/tour
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm start
|
||||
open http://localhost:8000
|
||||
```
|
||||
|
||||
## Feature
|
||||
|
||||
- React life cycle support tour component
|
||||
|
||||
## Install
|
||||
|
||||
[](https://www.npmjs.com/package/@rc-component/tour)
|
||||
|
||||
## Usage
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```js | pure
|
||||
import { useRef } from 'react';
|
||||
import Tour from '@rc-component/tour';
|
||||
|
||||
const Demo = () => {
|
||||
const createBtnRef = useRef<HTMLButtonElement>(null);
|
||||
const updateBtnRef = useRef<HTMLButtonElement>(null);
|
||||
const deleteBtnRef = useRef<HTMLButtonElement>(null);
|
||||
return (
|
||||
<div style={{ margin: 20 }}>
|
||||
<div>
|
||||
<button
|
||||
className="ant-target"
|
||||
ref={createBtnRef}
|
||||
style={{ marginLeft: 100 }}
|
||||
>
|
||||
Create
|
||||
</button>
|
||||
<div style={{ height: 200 }} />
|
||||
<button className="ant-target" ref={updateBtnRef}>
|
||||
Update
|
||||
</button>
|
||||
<button className="ant-target" ref={deleteBtnRef}>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div style={{ height: 200 }} />
|
||||
|
||||
<Tour
|
||||
defaultCurrent={0}
|
||||
steps={[
|
||||
{
|
||||
title: '创建',
|
||||
description: '创建一条数据',
|
||||
target: () => createBtnRef.current,
|
||||
mask: true,
|
||||
},
|
||||
{
|
||||
title: '更新',
|
||||
description: (
|
||||
<div>
|
||||
<span>更新一条数据</span>
|
||||
<button>帮助文档</button>
|
||||
</div>
|
||||
),
|
||||
target: () => updateBtnRef.current,
|
||||
},
|
||||
{
|
||||
title: '删除',
|
||||
description: (
|
||||
<div>
|
||||
<span>危险操作:删除一条数据</span>
|
||||
<button>帮助文档</button>
|
||||
</div>
|
||||
),
|
||||
target: () => deleteBtnRef.current,
|
||||
mask: true,
|
||||
style: { color: 'red' },
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Demo;
|
||||
```
|
||||
|
||||
## 🔥 API
|
||||
|
||||
We use typescript to create the Type definition. You can view directly in IDE. But you can still check the type definition [here](https://github.com/react-component/tour/blob/master/src/interface.ts).
|
||||
|
||||
### Tour
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
| 属性 | 类型 | 默认值 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| closeIcon | `React.ReactNode` | - | 自定义关闭按钮 |
|
||||
| steps | `TourStepProps[]` | - | 引导步骤 |
|
||||
| open | `boolean` | `true` | 受控打开引导(与 `current` 受控分开) |
|
||||
| current | `number` | 0 | 受控当前处于哪一步 |
|
||||
| defaultCurrent | `number` | 0 | 默认处于哪一步 |
|
||||
| gap | `{ offset?: number \| [number, number]; radius?: number }` | - | 控制引导显示间距 |
|
||||
| onChange | `(current: number) => void` | - | 步骤改变时的回调,`current`为改变前的步骤,`next`为改变后的步骤 |
|
||||
| onClose | `(current: number) => void` | - | 关闭引导时的回调 |
|
||||
| onFinish | `() => void` | - | 完成引导时的回调 |
|
||||
| mask | `boolean \| { style?: React.CSSProperties; color?: string; }` | `true` | 整体是否启用蒙层,也可以传入自定义样式修改蒙层样式 |
|
||||
| animated | `boolean \| { placeholder: boolean }` | `false` | 是否启用目标遮罩动画 |
|
||||
| arrow | `boolean \| { pointAtCenter: boolean}` | `true` | 整体是否显示箭头,包含是否指向元素中心的配置 |
|
||||
| scrollIntoViewOptions | `boolean \| ScrollIntoViewOptions` | `true` | 是否支持当前元素滚动到视窗内,也可传入配置指定滚动视窗的相关参数 |
|
||||
| onPopupAlign | `function(popupDomNode, align)` | - | 当弹出框对齐后回调 |
|
||||
| zIndex | `number` | 1001 | 弹层的层级 |
|
||||
|
||||
### TourStep
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
| 属性 | 类型 | 默认值 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| closeIcon | `React.ReactNode` | - | 自定义关闭按钮 |
|
||||
| target | `() => HTMLElement` \| `HTMLElement` | - | 获取引导卡片指向的元素 |
|
||||
| arrow | `boolean` \| `{ pointAtCenter: boolean}` | `true` | 是否显示箭头,包含是否指向元素中心的配置 |
|
||||
| placement | `left` \| `leftTop` \| `leftBottom` \| `right` \| `rightTop` \| `rightBottom` \| `top` \| `topLeft` \| `topRight` \| `bottom` \| `bottomLeft` \| `bottomRight` | `bottom` | 引导卡片相对于目标元素的位置 |
|
||||
| onClose | `() => void` | - | 关闭引导时的回调函数 |
|
||||
| mask | `boolean \| { style?: React.CSSProperties; color?: string; }` | `true` | 是否启用蒙层,也可以传入自定义样式修改蒙层样式,默认跟随 Tour 的 `mask` 属性 |
|
||||
| renderPanel | `(props: TourStepProps, current: number) => ReactNode;` | | 渲染 popoup 弹窗方法 |
|
||||
| className | `string` | - | - |
|
||||
| style | `React.CSSProperties` | - | - |
|
||||
| scrollIntoViewOptions | `boolean \| ScrollIntoViewOptions` | `true` | 是否支持当前元素滚动到视窗内,也可传入配置指定滚动视窗的相关参数,默认跟随 Tour 的 `scrollIntoViewOptions` 属性 |
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
<a href="https://openomy.app/github/react-component/tour" target="_blank" style="display: block; width: 100%;" align="center">
|
||||
<img src="https://openomy.app/svg?repo=react-component/tour&chart=bubble&latestMonth=24" target="_blank" alt="Contribution Leaderboard" style="display: block; width: 100%;" />
|
||||
</a>
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import type { PosInfo } from './hooks/useTarget';
|
||||
import type { SemanticName, TourProps } from './interface';
|
||||
export interface MaskProps {
|
||||
prefixCls?: string;
|
||||
pos: PosInfo;
|
||||
rootClassName?: string;
|
||||
showMask?: boolean;
|
||||
style?: React.CSSProperties;
|
||||
fill?: string;
|
||||
open?: boolean;
|
||||
animated?: boolean | {
|
||||
placeholder: boolean;
|
||||
};
|
||||
zIndex?: number;
|
||||
disabledInteraction?: boolean;
|
||||
classNames?: Partial<Record<SemanticName, string>>;
|
||||
styles?: Partial<Record<SemanticName, React.CSSProperties>>;
|
||||
getPopupContainer?: TourProps['getPopupContainer'];
|
||||
onEsc?: (info: {
|
||||
top: boolean;
|
||||
event: KeyboardEvent;
|
||||
}) => void;
|
||||
}
|
||||
declare const Mask: React.FC<MaskProps>;
|
||||
export default Mask;
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
||||
import React from 'react';
|
||||
import { clsx } from 'clsx';
|
||||
import Portal from '@rc-component/portal';
|
||||
import useId from "@rc-component/util/es/hooks/useId";
|
||||
const COVER_PROPS = {
|
||||
fill: 'transparent',
|
||||
pointerEvents: 'auto'
|
||||
};
|
||||
const Mask = props => {
|
||||
const {
|
||||
prefixCls,
|
||||
rootClassName,
|
||||
pos,
|
||||
showMask,
|
||||
style = {},
|
||||
fill = 'rgba(0,0,0,0.5)',
|
||||
open,
|
||||
animated,
|
||||
zIndex,
|
||||
disabledInteraction,
|
||||
styles,
|
||||
classNames: tourClassNames,
|
||||
getPopupContainer,
|
||||
onEsc
|
||||
} = props;
|
||||
const id = useId();
|
||||
const maskId = `${prefixCls}-mask-${id}`;
|
||||
const mergedAnimated = typeof animated === 'object' ? animated?.placeholder : animated;
|
||||
const isSafari = typeof navigator !== 'undefined' && /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
||||
const maskRectSize = isSafari ? {
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
} : {
|
||||
width: '100vw',
|
||||
height: '100vh'
|
||||
};
|
||||
const inlineMode = getPopupContainer === false;
|
||||
return /*#__PURE__*/React.createElement(Portal, {
|
||||
open: open,
|
||||
autoLock: !inlineMode,
|
||||
getContainer: getPopupContainer,
|
||||
onEsc: onEsc
|
||||
}, /*#__PURE__*/React.createElement("div", {
|
||||
className: clsx(`${prefixCls}-mask`, rootClassName, tourClassNames?.mask),
|
||||
style: {
|
||||
position: inlineMode ? 'absolute' : 'fixed',
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
zIndex,
|
||||
pointerEvents: pos && !disabledInteraction ? 'none' : 'auto',
|
||||
...style,
|
||||
...styles?.mask
|
||||
}
|
||||
}, showMask ? /*#__PURE__*/React.createElement("svg", {
|
||||
style: {
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
}
|
||||
}, /*#__PURE__*/React.createElement("defs", null, /*#__PURE__*/React.createElement("mask", {
|
||||
id: maskId
|
||||
}, /*#__PURE__*/React.createElement("rect", _extends({
|
||||
x: "0",
|
||||
y: "0"
|
||||
}, maskRectSize, {
|
||||
fill: "white"
|
||||
})), pos && /*#__PURE__*/React.createElement("rect", {
|
||||
x: pos.left,
|
||||
y: pos.top,
|
||||
rx: pos.radius,
|
||||
width: pos.width,
|
||||
height: pos.height,
|
||||
fill: "black",
|
||||
className: mergedAnimated ? `${prefixCls}-placeholder-animated` : ''
|
||||
}))), /*#__PURE__*/React.createElement("rect", {
|
||||
x: "0",
|
||||
y: "0",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
fill: fill,
|
||||
mask: `url(#${maskId})`
|
||||
}), pos && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("rect", _extends({}, COVER_PROPS, {
|
||||
x: "0",
|
||||
y: "0",
|
||||
width: "100%",
|
||||
height: Math.max(pos.top, 0)
|
||||
})), /*#__PURE__*/React.createElement("rect", _extends({}, COVER_PROPS, {
|
||||
x: "0",
|
||||
y: "0",
|
||||
width: Math.max(pos.left, 0),
|
||||
height: "100%"
|
||||
})), /*#__PURE__*/React.createElement("rect", _extends({}, COVER_PROPS, {
|
||||
x: "0",
|
||||
y: pos.top + pos.height,
|
||||
width: "100%",
|
||||
height: `calc(100% - ${pos.top + pos.height}px)`
|
||||
})), /*#__PURE__*/React.createElement("rect", _extends({}, COVER_PROPS, {
|
||||
x: pos.left + pos.width,
|
||||
y: "0",
|
||||
width: `calc(100% - ${pos.left + pos.width}px)`,
|
||||
height: "100%"
|
||||
})))) : null));
|
||||
};
|
||||
export default Mask;
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import { type PortalProps } from '@rc-component/portal';
|
||||
import * as React from 'react';
|
||||
export interface PlaceholderProps extends Pick<PortalProps, 'open' | 'autoLock' | 'getContainer'> {
|
||||
domRef: React.RefObject<HTMLDivElement>;
|
||||
className: string;
|
||||
style: React.CSSProperties;
|
||||
fallbackDOM: () => HTMLElement | null;
|
||||
}
|
||||
declare const Placeholder: React.ForwardRefExoticComponent<PlaceholderProps & React.RefAttributes<HTMLElement>>;
|
||||
export default Placeholder;
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
import Portal from '@rc-component/portal';
|
||||
import * as React from 'react';
|
||||
const Placeholder = /*#__PURE__*/React.forwardRef((props, ref) => {
|
||||
const {
|
||||
open,
|
||||
autoLock,
|
||||
getContainer,
|
||||
domRef,
|
||||
className,
|
||||
style,
|
||||
fallbackDOM
|
||||
} = props;
|
||||
React.useImperativeHandle(ref, () => domRef.current || fallbackDOM());
|
||||
return /*#__PURE__*/React.createElement(Portal, {
|
||||
open: open,
|
||||
autoLock: autoLock,
|
||||
getContainer: getContainer
|
||||
}, /*#__PURE__*/React.createElement("div", {
|
||||
ref: domRef,
|
||||
className: className,
|
||||
style: style
|
||||
}));
|
||||
});
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
Placeholder.displayName = 'Placeholder';
|
||||
}
|
||||
export default Placeholder;
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import type { TourProps } from './interface';
|
||||
export type { TourProps };
|
||||
declare const Tour: React.FC<TourProps>;
|
||||
export default Tour;
|
||||
+243
@@ -0,0 +1,243 @@
|
||||
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
||||
import * as React from 'react';
|
||||
import Trigger from '@rc-component/trigger';
|
||||
import { clsx } from 'clsx';
|
||||
import useLayoutEffect from "@rc-component/util/es/hooks/useLayoutEffect";
|
||||
import useEvent from "@rc-component/util/es/hooks/useEvent";
|
||||
import KeyCode from "@rc-component/util/es/KeyCode";
|
||||
import useControlledState from "@rc-component/util/es/hooks/useControlledState";
|
||||
import { useMemo } from 'react';
|
||||
import { useClosable } from "./hooks/useClosable";
|
||||
import useTarget from "./hooks/useTarget";
|
||||
import Mask from "./Mask";
|
||||
import { getPlacements } from "./placements";
|
||||
import TourStep from "./TourStep";
|
||||
import { getPlacement } from "./util";
|
||||
import Placeholder from "./Placeholder";
|
||||
const CENTER_PLACEHOLDER = {
|
||||
left: '50%',
|
||||
top: '50%',
|
||||
width: 1,
|
||||
height: 1
|
||||
};
|
||||
const defaultScrollIntoViewOptions = {
|
||||
block: 'center',
|
||||
inline: 'center'
|
||||
};
|
||||
const Tour = props => {
|
||||
const {
|
||||
prefixCls = 'rc-tour',
|
||||
steps = [],
|
||||
defaultCurrent,
|
||||
current,
|
||||
keyboard = true,
|
||||
onChange,
|
||||
onClose,
|
||||
onFinish,
|
||||
open,
|
||||
defaultOpen,
|
||||
mask = true,
|
||||
arrow = true,
|
||||
rootClassName,
|
||||
placement,
|
||||
renderPanel,
|
||||
gap,
|
||||
animated,
|
||||
scrollIntoViewOptions = defaultScrollIntoViewOptions,
|
||||
zIndex = 1001,
|
||||
closeIcon,
|
||||
closable,
|
||||
builtinPlacements,
|
||||
disabledInteraction,
|
||||
styles,
|
||||
classNames: tourClassNames,
|
||||
className,
|
||||
style,
|
||||
getPopupContainer,
|
||||
...restProps
|
||||
} = props;
|
||||
const triggerRef = React.useRef();
|
||||
const [mergedCurrent, setMergedCurrent] = useControlledState(defaultCurrent || 0, current);
|
||||
const [internalOpen, setMergedOpen] = useControlledState(defaultOpen, open);
|
||||
const mergedOpen = mergedCurrent < 0 || mergedCurrent >= steps.length ? false : internalOpen ?? true;
|
||||
|
||||
// Record if already rended in the DOM to avoid `findDOMNode` issue
|
||||
const [hasOpened, setHasOpened] = React.useState(mergedOpen);
|
||||
const openRef = React.useRef(mergedOpen);
|
||||
useLayoutEffect(() => {
|
||||
if (mergedOpen) {
|
||||
if (!openRef.current) {
|
||||
setMergedCurrent(0);
|
||||
}
|
||||
setHasOpened(true);
|
||||
}
|
||||
openRef.current = mergedOpen;
|
||||
}, [mergedOpen, setMergedCurrent]);
|
||||
const {
|
||||
target,
|
||||
placement: stepPlacement,
|
||||
style: stepStyle,
|
||||
arrow: stepArrow,
|
||||
className: stepClassName,
|
||||
mask: stepMask,
|
||||
scrollIntoViewOptions: stepScrollIntoViewOptions = defaultScrollIntoViewOptions,
|
||||
closeIcon: stepCloseIcon,
|
||||
closable: stepClosable
|
||||
} = steps[mergedCurrent] || {};
|
||||
const mergedClosable = useClosable(stepClosable, stepCloseIcon, closable, closeIcon);
|
||||
const mergedMask = mergedOpen && (stepMask ?? mask);
|
||||
const mergedScrollIntoViewOptions = stepScrollIntoViewOptions ?? scrollIntoViewOptions;
|
||||
|
||||
// ====================== Align Target ======================
|
||||
const placeholderRef = React.useRef(null);
|
||||
const inlineMode = getPopupContainer === false;
|
||||
const [posInfo, targetElement] = useTarget(target, open, gap, mergedScrollIntoViewOptions, inlineMode, placeholderRef);
|
||||
const mergedPlacement = getPlacement(targetElement, placement, stepPlacement);
|
||||
|
||||
// ========================= arrow =========================
|
||||
const mergedArrow = targetElement ? typeof stepArrow === 'undefined' ? arrow : stepArrow : false;
|
||||
const arrowPointAtCenter = typeof mergedArrow === 'object' ? mergedArrow.pointAtCenter : false;
|
||||
useLayoutEffect(() => {
|
||||
triggerRef.current?.forceAlign();
|
||||
}, [arrowPointAtCenter, mergedCurrent]);
|
||||
|
||||
// ========================= Change =========================
|
||||
const onInternalChange = nextCurrent => {
|
||||
setMergedCurrent(nextCurrent);
|
||||
onChange?.(nextCurrent);
|
||||
};
|
||||
const mergedBuiltinPlacements = useMemo(() => {
|
||||
if (builtinPlacements) {
|
||||
return typeof builtinPlacements === 'function' ? builtinPlacements({
|
||||
arrowPointAtCenter
|
||||
}) : builtinPlacements;
|
||||
}
|
||||
return getPlacements(arrowPointAtCenter);
|
||||
}, [builtinPlacements, arrowPointAtCenter]);
|
||||
const handleClose = () => {
|
||||
setMergedOpen(false);
|
||||
onClose?.(mergedCurrent);
|
||||
};
|
||||
|
||||
// ========================= Esc Close =========================
|
||||
// Use Portal's onEsc to handle Escape key with proper stacking logic
|
||||
const handleEscClose = useEvent(({
|
||||
event
|
||||
}) => {
|
||||
if (keyboard && mergedClosable !== null) {
|
||||
event.preventDefault();
|
||||
handleClose();
|
||||
}
|
||||
});
|
||||
|
||||
// ========================= Keyboard =========================
|
||||
// Support ArrowLeft/ArrowRight to navigate steps.
|
||||
const keyboardHandler = useEvent(e => {
|
||||
// Ignore keyboard events from input-like elements to avoid interfering when typing
|
||||
if (KeyCode.isEditableTarget(e)) {
|
||||
return;
|
||||
}
|
||||
if (keyboard && e.key === 'ArrowLeft') {
|
||||
if (mergedCurrent > 0) {
|
||||
e.preventDefault();
|
||||
onInternalChange(mergedCurrent - 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (keyboard && e.key === 'ArrowRight') {
|
||||
if (mergedCurrent < steps.length - 1) {
|
||||
e.preventDefault();
|
||||
onInternalChange(mergedCurrent + 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
});
|
||||
useLayoutEffect(() => {
|
||||
if (!mergedOpen) return;
|
||||
window.addEventListener('keydown', keyboardHandler);
|
||||
return () => {
|
||||
window.removeEventListener('keydown', keyboardHandler);
|
||||
};
|
||||
}, [mergedOpen, keyboardHandler]);
|
||||
|
||||
// ========================= Render =========================
|
||||
// Skip if not init yet
|
||||
if (targetElement === undefined || !hasOpened) {
|
||||
return null;
|
||||
}
|
||||
const getPopupElement = () => /*#__PURE__*/React.createElement(TourStep, _extends({
|
||||
styles: styles,
|
||||
classNames: tourClassNames,
|
||||
arrow: mergedArrow,
|
||||
key: "content",
|
||||
prefixCls: prefixCls,
|
||||
total: steps.length,
|
||||
renderPanel: renderPanel,
|
||||
onPrev: () => {
|
||||
onInternalChange(mergedCurrent - 1);
|
||||
},
|
||||
onNext: () => {
|
||||
onInternalChange(mergedCurrent + 1);
|
||||
},
|
||||
onClose: handleClose,
|
||||
current: mergedCurrent,
|
||||
onFinish: () => {
|
||||
handleClose();
|
||||
onFinish?.();
|
||||
}
|
||||
}, steps[mergedCurrent], {
|
||||
closable: mergedClosable
|
||||
}));
|
||||
const mergedShowMask = typeof mergedMask === 'boolean' ? mergedMask : !!mergedMask;
|
||||
const mergedMaskStyle = typeof mergedMask === 'boolean' ? undefined : mergedMask;
|
||||
|
||||
// when targetElement is not exist, use body as triggerDOMNode
|
||||
const fallbackDOM = () => {
|
||||
return targetElement || document.body;
|
||||
};
|
||||
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Mask, {
|
||||
getPopupContainer: getPopupContainer,
|
||||
styles: styles,
|
||||
classNames: tourClassNames,
|
||||
zIndex: zIndex,
|
||||
prefixCls: prefixCls,
|
||||
pos: posInfo,
|
||||
showMask: mergedShowMask,
|
||||
style: mergedMaskStyle?.style,
|
||||
fill: mergedMaskStyle?.color,
|
||||
open: mergedOpen,
|
||||
animated: animated,
|
||||
rootClassName: rootClassName,
|
||||
disabledInteraction: disabledInteraction,
|
||||
onEsc: handleEscClose
|
||||
}), /*#__PURE__*/React.createElement(Trigger, _extends({}, restProps, {
|
||||
// `rc-portal` def bug not support `false` but does support and in used.
|
||||
getPopupContainer: getPopupContainer,
|
||||
builtinPlacements: mergedBuiltinPlacements,
|
||||
ref: triggerRef,
|
||||
popupStyle: stepStyle,
|
||||
popupPlacement: mergedPlacement,
|
||||
popupVisible: mergedOpen,
|
||||
popupClassName: clsx(rootClassName, stepClassName),
|
||||
prefixCls: prefixCls,
|
||||
popup: getPopupElement,
|
||||
forceRender: false,
|
||||
autoDestroy: true,
|
||||
zIndex: zIndex,
|
||||
arrow: !!mergedArrow
|
||||
}), /*#__PURE__*/React.createElement(Placeholder, {
|
||||
open: mergedOpen,
|
||||
autoLock: !inlineMode,
|
||||
getContainer: getPopupContainer,
|
||||
domRef: placeholderRef,
|
||||
fallbackDOM: fallbackDOM,
|
||||
className: clsx(className, rootClassName, `${prefixCls}-target-placeholder`),
|
||||
style: {
|
||||
...(posInfo || CENTER_PLACEHOLDER),
|
||||
position: inlineMode ? 'absolute' : 'fixed',
|
||||
pointerEvents: 'none',
|
||||
...style
|
||||
}
|
||||
})));
|
||||
};
|
||||
export default Tour;
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import type { TourStepProps } from '../interface';
|
||||
export type DefaultPanelProps = Exclude<TourStepProps, 'closable'> & {
|
||||
closable: Exclude<TourStepProps['closable'], boolean>;
|
||||
};
|
||||
export default function DefaultPanel(props: DefaultPanelProps): React.JSX.Element;
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
||||
import * as React from 'react';
|
||||
import { clsx } from 'clsx';
|
||||
import pickAttrs from "@rc-component/util/es/pickAttrs";
|
||||
export default function DefaultPanel(props) {
|
||||
const {
|
||||
prefixCls,
|
||||
current,
|
||||
total,
|
||||
title,
|
||||
description,
|
||||
onClose,
|
||||
onPrev,
|
||||
onNext,
|
||||
onFinish,
|
||||
className,
|
||||
closable,
|
||||
classNames: tourClassNames,
|
||||
styles
|
||||
} = props;
|
||||
const ariaProps = pickAttrs(closable || {}, true);
|
||||
const closeIcon = closable?.closeIcon ?? /*#__PURE__*/React.createElement("span", {
|
||||
className: `${prefixCls}-close-x`
|
||||
}, "\xD7");
|
||||
const mergedClosable = !!closable;
|
||||
return /*#__PURE__*/React.createElement("div", {
|
||||
className: clsx(`${prefixCls}-panel`, className)
|
||||
}, /*#__PURE__*/React.createElement("div", {
|
||||
className: clsx(`${prefixCls}-section`, tourClassNames?.section),
|
||||
style: styles?.section
|
||||
}, mergedClosable && /*#__PURE__*/React.createElement("button", _extends({
|
||||
type: "button",
|
||||
onClick: onClose,
|
||||
"aria-label": "Close"
|
||||
}, ariaProps, {
|
||||
className: `${prefixCls}-close`
|
||||
}), closeIcon), /*#__PURE__*/React.createElement("div", {
|
||||
className: clsx(`${prefixCls}-header`, tourClassNames?.header),
|
||||
style: styles?.header
|
||||
}, /*#__PURE__*/React.createElement("div", {
|
||||
className: clsx(`${prefixCls}-title`, tourClassNames?.title),
|
||||
style: styles?.title
|
||||
}, title)), /*#__PURE__*/React.createElement("div", {
|
||||
className: clsx(`${prefixCls}-description`, tourClassNames?.description),
|
||||
style: styles?.description
|
||||
}, description), /*#__PURE__*/React.createElement("div", {
|
||||
className: clsx(`${prefixCls}-footer`, tourClassNames?.footer),
|
||||
style: styles?.footer
|
||||
}, /*#__PURE__*/React.createElement("div", {
|
||||
className: `${prefixCls}-sliders`
|
||||
}, total > 1 ? [...Array.from({
|
||||
length: total
|
||||
}).keys()].map((item, index) => {
|
||||
return /*#__PURE__*/React.createElement("span", {
|
||||
key: item,
|
||||
className: index === current ? 'active' : ''
|
||||
});
|
||||
}) : null), /*#__PURE__*/React.createElement("div", {
|
||||
className: clsx(`${prefixCls}-actions`, tourClassNames?.actions),
|
||||
style: styles?.actions
|
||||
}, current !== 0 ? /*#__PURE__*/React.createElement("button", {
|
||||
className: `${prefixCls}-prev-btn`,
|
||||
onClick: onPrev
|
||||
}, "Prev") : null, current === total - 1 ? /*#__PURE__*/React.createElement("button", {
|
||||
className: `${prefixCls}-finish-btn`,
|
||||
onClick: onFinish
|
||||
}, "Finish") : /*#__PURE__*/React.createElement("button", {
|
||||
className: `${prefixCls}-next-btn`,
|
||||
onClick: onNext
|
||||
}, "Next")))));
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { type DefaultPanelProps } from './DefaultPanel';
|
||||
import type { TourStepProps, TourStepInfo } from '../interface';
|
||||
export type { TourStepProps, TourStepInfo, };
|
||||
declare const TourStep: (props: DefaultPanelProps) => React.JSX.Element;
|
||||
export default TourStep;
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import * as React from 'react';
|
||||
import DefaultPanel from "./DefaultPanel";
|
||||
const TourStep = props => {
|
||||
const {
|
||||
current,
|
||||
renderPanel
|
||||
} = props;
|
||||
return /*#__PURE__*/React.createElement(React.Fragment, null, typeof renderPanel === 'function' ? renderPanel(props, current) : /*#__PURE__*/React.createElement(DefaultPanel, props));
|
||||
};
|
||||
export default TourStep;
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import * as React from 'react';
|
||||
import type { TourProps } from '../interface';
|
||||
import type { TourStepInfo } from '../TourStep';
|
||||
export declare function useClosable(stepClosable: TourStepInfo['closable'], stepCloseIcon: TourStepInfo['closeIcon'], closable: TourProps['closable'], closeIcon: TourProps['closeIcon']): {
|
||||
closeIcon?: React.ReactNode;
|
||||
} & React.AriaAttributes & {
|
||||
[key: `data-${string}`]: unknown;
|
||||
} & Pick<React.HTMLAttributes<HTMLDivElement>, "role">;
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import * as React from 'react';
|
||||
function isConfigObj(closable) {
|
||||
return closable !== null && typeof closable === 'object';
|
||||
}
|
||||
/**
|
||||
* Convert `closable` to ClosableConfig.
|
||||
* When `preset` is true, will auto fill ClosableConfig with default value.
|
||||
*/
|
||||
function getClosableConfig(closable, closeIcon, preset) {
|
||||
if (closable === false || closeIcon === false && (!isConfigObj(closable) || !closable.closeIcon)) {
|
||||
return null;
|
||||
}
|
||||
const mergedCloseIcon = typeof closeIcon !== 'boolean' ? closeIcon : undefined;
|
||||
if (isConfigObj(closable)) {
|
||||
return {
|
||||
...closable,
|
||||
closeIcon: closable.closeIcon ?? mergedCloseIcon
|
||||
};
|
||||
}
|
||||
|
||||
// When StepClosable no need auto fill, but RootClosable need this.
|
||||
return preset || closable || closeIcon ? {
|
||||
closeIcon: mergedCloseIcon
|
||||
} : 'empty';
|
||||
}
|
||||
export function useClosable(stepClosable, stepCloseIcon, closable, closeIcon) {
|
||||
return React.useMemo(() => {
|
||||
const stepClosableConfig = getClosableConfig(stepClosable, stepCloseIcon, false);
|
||||
const rootClosableConfig = getClosableConfig(closable, closeIcon, true);
|
||||
if (stepClosableConfig !== 'empty') {
|
||||
return stepClosableConfig;
|
||||
}
|
||||
return rootClosableConfig;
|
||||
}, [closable, closeIcon, stepClosable, stepCloseIcon]);
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
/// <reference types="react" />
|
||||
import type { TourStepInfo } from '..';
|
||||
export interface Gap {
|
||||
offset?: number | [number, number];
|
||||
radius?: number;
|
||||
}
|
||||
export interface PosInfo {
|
||||
left: number;
|
||||
top: number;
|
||||
height: number;
|
||||
width: number;
|
||||
radius: number;
|
||||
}
|
||||
export default function useTarget(target: TourStepInfo['target'], open: boolean, gap?: Gap, scrollIntoViewOptions?: boolean | ScrollIntoViewOptions, inlineMode?: boolean, placeholderRef?: React.RefObject<HTMLDivElement>): [PosInfo, HTMLElement];
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
import useEvent from "@rc-component/util/es/hooks/useEvent";
|
||||
import useLayoutEffect from "@rc-component/util/es/hooks/useLayoutEffect";
|
||||
import { useMemo, useState } from 'react';
|
||||
import { isInViewPort } from "../util";
|
||||
function isValidNumber(val) {
|
||||
return typeof val === 'number' && !Number.isNaN(val);
|
||||
}
|
||||
export default function useTarget(target, open, gap, scrollIntoViewOptions, inlineMode, placeholderRef) {
|
||||
// ========================= Target =========================
|
||||
// We trade `undefined` as not get target by function yet.
|
||||
// `null` as empty target.
|
||||
const [targetElement, setTargetElement] = useState(undefined);
|
||||
useLayoutEffect(() => {
|
||||
const nextElement = typeof target === 'function' ? target() : target;
|
||||
setTargetElement(nextElement || null);
|
||||
});
|
||||
|
||||
// ========================= Align ==========================
|
||||
const [posInfo, setPosInfo] = useState(null);
|
||||
const updatePos = useEvent(() => {
|
||||
if (targetElement) {
|
||||
// Exist target element. We should scroll and get target position
|
||||
if (!inlineMode && !isInViewPort(targetElement) && open) {
|
||||
targetElement.scrollIntoView(scrollIntoViewOptions);
|
||||
}
|
||||
const {
|
||||
left,
|
||||
top,
|
||||
width,
|
||||
height
|
||||
} = targetElement.getBoundingClientRect();
|
||||
const nextPosInfo = {
|
||||
left,
|
||||
top,
|
||||
width,
|
||||
height,
|
||||
radius: 0
|
||||
};
|
||||
|
||||
// If `inlineMode` we need cut off parent offset
|
||||
if (inlineMode) {
|
||||
const parentRect = placeholderRef.current?.parentElement?.getBoundingClientRect();
|
||||
if (parentRect) {
|
||||
nextPosInfo.left -= parentRect.left;
|
||||
nextPosInfo.top -= parentRect.top;
|
||||
}
|
||||
}
|
||||
setPosInfo(origin => {
|
||||
if (JSON.stringify(origin) !== JSON.stringify(nextPosInfo)) {
|
||||
return nextPosInfo;
|
||||
}
|
||||
return origin;
|
||||
});
|
||||
} else {
|
||||
// Not exist target which means we just show in center
|
||||
setPosInfo(null);
|
||||
}
|
||||
});
|
||||
const getGapOffset = index => (Array.isArray(gap?.offset) ? gap?.offset[index] : gap?.offset) ?? 6;
|
||||
useLayoutEffect(() => {
|
||||
updatePos();
|
||||
// update when window resize
|
||||
window.addEventListener('resize', updatePos);
|
||||
// update when `document.body.style.overflow` is set to visible
|
||||
// by default, it will be set to hidden
|
||||
window.addEventListener('scroll', updatePos);
|
||||
return () => {
|
||||
window.removeEventListener('resize', updatePos);
|
||||
window.removeEventListener('scroll', updatePos);
|
||||
};
|
||||
}, [targetElement, open, updatePos]);
|
||||
|
||||
// ======================== PosInfo =========================
|
||||
const mergedPosInfo = useMemo(() => {
|
||||
if (!posInfo) {
|
||||
return posInfo;
|
||||
}
|
||||
const gapOffsetX = getGapOffset(0);
|
||||
const gapOffsetY = getGapOffset(1);
|
||||
const gapRadius = isValidNumber(gap?.radius) ? gap?.radius : 2;
|
||||
return {
|
||||
left: posInfo.left - gapOffsetX,
|
||||
top: posInfo.top - gapOffsetY,
|
||||
width: posInfo.width + gapOffsetX * 2,
|
||||
height: posInfo.height + gapOffsetY * 2,
|
||||
radius: gapRadius
|
||||
};
|
||||
}, [posInfo, gap]);
|
||||
return [mergedPosInfo, targetElement];
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
import Tour from './Tour';
|
||||
export type { TourProps, TourStepInfo, TourStepProps } from './interface';
|
||||
export default Tour;
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
import Tour from "./Tour";
|
||||
export default Tour;
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
import type { TriggerProps } from '@rc-component/trigger';
|
||||
import type { PlacementType } from './placements';
|
||||
import type { CSSProperties, ReactNode } from 'react';
|
||||
import type { Gap } from './hooks/useTarget';
|
||||
import { type DefaultPanelProps } from './TourStep/DefaultPanel';
|
||||
export type SemanticName = 'section' | 'footer' | 'actions' | 'header' | 'title' | 'description' | 'mask';
|
||||
export type HTMLAriaDataAttributes = React.AriaAttributes & {
|
||||
[key: `data-${string}`]: unknown;
|
||||
} & Pick<React.HTMLAttributes<HTMLDivElement>, 'role'>;
|
||||
export interface TourStepInfo {
|
||||
arrow?: boolean | {
|
||||
pointAtCenter: boolean;
|
||||
};
|
||||
target?: HTMLElement | (() => HTMLElement) | null | (() => null);
|
||||
title: ReactNode;
|
||||
description?: ReactNode;
|
||||
placement?: PlacementType;
|
||||
mask?: boolean | {
|
||||
style?: React.CSSProperties;
|
||||
color?: string;
|
||||
};
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
scrollIntoViewOptions?: boolean | ScrollIntoViewOptions;
|
||||
closeIcon?: ReactNode;
|
||||
closable?: boolean | ({
|
||||
closeIcon?: ReactNode;
|
||||
} & HTMLAriaDataAttributes);
|
||||
}
|
||||
export interface TourStepProps extends TourStepInfo {
|
||||
prefixCls?: string;
|
||||
total?: number;
|
||||
current?: number;
|
||||
onClose?: () => void;
|
||||
onFinish?: () => void;
|
||||
renderPanel?: (step: TourStepProps, current: number) => ReactNode;
|
||||
onPrev?: () => void;
|
||||
onNext?: () => void;
|
||||
classNames?: Partial<Record<SemanticName, string>>;
|
||||
styles?: Partial<Record<SemanticName, React.CSSProperties>>;
|
||||
}
|
||||
export interface TourProps extends Pick<TriggerProps, 'onPopupAlign'> {
|
||||
classNames?: Partial<Record<SemanticName, string>>;
|
||||
styles?: Partial<Record<SemanticName, React.CSSProperties>>;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
steps?: TourStepInfo[];
|
||||
open?: boolean;
|
||||
keyboard?: boolean;
|
||||
defaultOpen?: boolean;
|
||||
defaultCurrent?: number;
|
||||
current?: number;
|
||||
onChange?: (current: number) => void;
|
||||
onClose?: (current: number) => void;
|
||||
onFinish?: () => void;
|
||||
closeIcon?: TourStepProps['closeIcon'];
|
||||
closable?: TourStepProps['closable'];
|
||||
mask?: boolean | {
|
||||
style?: React.CSSProperties;
|
||||
color?: string;
|
||||
};
|
||||
arrow?: boolean | {
|
||||
pointAtCenter: boolean;
|
||||
};
|
||||
rootClassName?: string;
|
||||
placement?: PlacementType;
|
||||
prefixCls?: string;
|
||||
renderPanel?: (props: DefaultPanelProps, current: number) => ReactNode;
|
||||
gap?: Gap;
|
||||
animated?: boolean | {
|
||||
placeholder: boolean;
|
||||
};
|
||||
scrollIntoViewOptions?: boolean | ScrollIntoViewOptions;
|
||||
zIndex?: number;
|
||||
getPopupContainer?: TriggerProps['getPopupContainer'] | false;
|
||||
builtinPlacements?: TriggerProps['builtinPlacements'] | ((config?: {
|
||||
arrowPointAtCenter?: boolean;
|
||||
}) => TriggerProps['builtinPlacements']);
|
||||
disabledInteraction?: boolean;
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
export {};
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
import type { BuildInPlacements } from '@rc-component/trigger';
|
||||
export type PlacementType = 'left' | 'leftTop' | 'leftBottom' | 'right' | 'rightTop' | 'rightBottom' | 'top' | 'topLeft' | 'topRight' | 'bottom' | 'bottomLeft' | 'bottomRight' | 'center';
|
||||
export declare function getPlacements(arrowPointAtCenter?: boolean): BuildInPlacements;
|
||||
export declare const placements: BuildInPlacements;
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
const targetOffset = [0, 0];
|
||||
const basePlacements = {
|
||||
left: {
|
||||
points: ['cr', 'cl'],
|
||||
offset: [-8, 0]
|
||||
},
|
||||
right: {
|
||||
points: ['cl', 'cr'],
|
||||
offset: [8, 0]
|
||||
},
|
||||
top: {
|
||||
points: ['bc', 'tc'],
|
||||
offset: [0, -8]
|
||||
},
|
||||
bottom: {
|
||||
points: ['tc', 'bc'],
|
||||
offset: [0, 8]
|
||||
},
|
||||
topLeft: {
|
||||
points: ['bl', 'tl'],
|
||||
offset: [0, -8]
|
||||
},
|
||||
leftTop: {
|
||||
points: ['tr', 'tl'],
|
||||
offset: [-8, 0]
|
||||
},
|
||||
topRight: {
|
||||
points: ['br', 'tr'],
|
||||
offset: [0, -8]
|
||||
},
|
||||
rightTop: {
|
||||
points: ['tl', 'tr'],
|
||||
offset: [8, 0]
|
||||
},
|
||||
bottomRight: {
|
||||
points: ['tr', 'br'],
|
||||
offset: [0, 8]
|
||||
},
|
||||
rightBottom: {
|
||||
points: ['bl', 'br'],
|
||||
offset: [8, 0]
|
||||
},
|
||||
bottomLeft: {
|
||||
points: ['tl', 'bl'],
|
||||
offset: [0, 8]
|
||||
},
|
||||
leftBottom: {
|
||||
points: ['br', 'bl'],
|
||||
offset: [-8, 0]
|
||||
}
|
||||
};
|
||||
export function getPlacements(arrowPointAtCenter = false) {
|
||||
const placements = {};
|
||||
Object.keys(basePlacements).forEach(key => {
|
||||
placements[key] = {
|
||||
...basePlacements[key],
|
||||
autoArrow: arrowPointAtCenter,
|
||||
targetOffset
|
||||
};
|
||||
});
|
||||
return placements;
|
||||
}
|
||||
export const placements = getPlacements();
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
import type { PlacementType } from './placements';
|
||||
export declare function isInViewPort(element: HTMLElement): boolean;
|
||||
export declare function getPlacement(targetElement?: HTMLElement | null, placement?: PlacementType, stepPlacement?: PlacementType): PlacementType;
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
export function isInViewPort(element) {
|
||||
const viewWidth = window.innerWidth || document.documentElement.clientWidth;
|
||||
const viewHeight = window.innerHeight || document.documentElement.clientHeight;
|
||||
const {
|
||||
top,
|
||||
right,
|
||||
bottom,
|
||||
left
|
||||
} = element.getBoundingClientRect();
|
||||
return top >= 0 && left >= 0 && right <= viewWidth && bottom <= viewHeight;
|
||||
}
|
||||
export function getPlacement(targetElement, placement, stepPlacement) {
|
||||
return stepPlacement ?? placement ?? (targetElement === null ? 'center' : 'bottom');
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import type { PosInfo } from './hooks/useTarget';
|
||||
import type { SemanticName, TourProps } from './interface';
|
||||
export interface MaskProps {
|
||||
prefixCls?: string;
|
||||
pos: PosInfo;
|
||||
rootClassName?: string;
|
||||
showMask?: boolean;
|
||||
style?: React.CSSProperties;
|
||||
fill?: string;
|
||||
open?: boolean;
|
||||
animated?: boolean | {
|
||||
placeholder: boolean;
|
||||
};
|
||||
zIndex?: number;
|
||||
disabledInteraction?: boolean;
|
||||
classNames?: Partial<Record<SemanticName, string>>;
|
||||
styles?: Partial<Record<SemanticName, React.CSSProperties>>;
|
||||
getPopupContainer?: TourProps['getPopupContainer'];
|
||||
onEsc?: (info: {
|
||||
top: boolean;
|
||||
event: KeyboardEvent;
|
||||
}) => void;
|
||||
}
|
||||
declare const Mask: React.FC<MaskProps>;
|
||||
export default Mask;
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = void 0;
|
||||
var _react = _interopRequireDefault(require("react"));
|
||||
var _clsx = require("clsx");
|
||||
var _portal = _interopRequireDefault(require("@rc-component/portal"));
|
||||
var _useId = _interopRequireDefault(require("@rc-component/util/lib/hooks/useId"));
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
||||
const COVER_PROPS = {
|
||||
fill: 'transparent',
|
||||
pointerEvents: 'auto'
|
||||
};
|
||||
const Mask = props => {
|
||||
const {
|
||||
prefixCls,
|
||||
rootClassName,
|
||||
pos,
|
||||
showMask,
|
||||
style = {},
|
||||
fill = 'rgba(0,0,0,0.5)',
|
||||
open,
|
||||
animated,
|
||||
zIndex,
|
||||
disabledInteraction,
|
||||
styles,
|
||||
classNames: tourClassNames,
|
||||
getPopupContainer,
|
||||
onEsc
|
||||
} = props;
|
||||
const id = (0, _useId.default)();
|
||||
const maskId = `${prefixCls}-mask-${id}`;
|
||||
const mergedAnimated = typeof animated === 'object' ? animated?.placeholder : animated;
|
||||
const isSafari = typeof navigator !== 'undefined' && /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
||||
const maskRectSize = isSafari ? {
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
} : {
|
||||
width: '100vw',
|
||||
height: '100vh'
|
||||
};
|
||||
const inlineMode = getPopupContainer === false;
|
||||
return /*#__PURE__*/_react.default.createElement(_portal.default, {
|
||||
open: open,
|
||||
autoLock: !inlineMode,
|
||||
getContainer: getPopupContainer,
|
||||
onEsc: onEsc
|
||||
}, /*#__PURE__*/_react.default.createElement("div", {
|
||||
className: (0, _clsx.clsx)(`${prefixCls}-mask`, rootClassName, tourClassNames?.mask),
|
||||
style: {
|
||||
position: inlineMode ? 'absolute' : 'fixed',
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
zIndex,
|
||||
pointerEvents: pos && !disabledInteraction ? 'none' : 'auto',
|
||||
...style,
|
||||
...styles?.mask
|
||||
}
|
||||
}, showMask ? /*#__PURE__*/_react.default.createElement("svg", {
|
||||
style: {
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
}
|
||||
}, /*#__PURE__*/_react.default.createElement("defs", null, /*#__PURE__*/_react.default.createElement("mask", {
|
||||
id: maskId
|
||||
}, /*#__PURE__*/_react.default.createElement("rect", _extends({
|
||||
x: "0",
|
||||
y: "0"
|
||||
}, maskRectSize, {
|
||||
fill: "white"
|
||||
})), pos && /*#__PURE__*/_react.default.createElement("rect", {
|
||||
x: pos.left,
|
||||
y: pos.top,
|
||||
rx: pos.radius,
|
||||
width: pos.width,
|
||||
height: pos.height,
|
||||
fill: "black",
|
||||
className: mergedAnimated ? `${prefixCls}-placeholder-animated` : ''
|
||||
}))), /*#__PURE__*/_react.default.createElement("rect", {
|
||||
x: "0",
|
||||
y: "0",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
fill: fill,
|
||||
mask: `url(#${maskId})`
|
||||
}), pos && /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement("rect", _extends({}, COVER_PROPS, {
|
||||
x: "0",
|
||||
y: "0",
|
||||
width: "100%",
|
||||
height: Math.max(pos.top, 0)
|
||||
})), /*#__PURE__*/_react.default.createElement("rect", _extends({}, COVER_PROPS, {
|
||||
x: "0",
|
||||
y: "0",
|
||||
width: Math.max(pos.left, 0),
|
||||
height: "100%"
|
||||
})), /*#__PURE__*/_react.default.createElement("rect", _extends({}, COVER_PROPS, {
|
||||
x: "0",
|
||||
y: pos.top + pos.height,
|
||||
width: "100%",
|
||||
height: `calc(100% - ${pos.top + pos.height}px)`
|
||||
})), /*#__PURE__*/_react.default.createElement("rect", _extends({}, COVER_PROPS, {
|
||||
x: pos.left + pos.width,
|
||||
y: "0",
|
||||
width: `calc(100% - ${pos.left + pos.width}px)`,
|
||||
height: "100%"
|
||||
})))) : null));
|
||||
};
|
||||
var _default = exports.default = Mask;
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import { type PortalProps } from '@rc-component/portal';
|
||||
import * as React from 'react';
|
||||
export interface PlaceholderProps extends Pick<PortalProps, 'open' | 'autoLock' | 'getContainer'> {
|
||||
domRef: React.RefObject<HTMLDivElement>;
|
||||
className: string;
|
||||
style: React.CSSProperties;
|
||||
fallbackDOM: () => HTMLElement | null;
|
||||
}
|
||||
declare const Placeholder: React.ForwardRefExoticComponent<PlaceholderProps & React.RefAttributes<HTMLElement>>;
|
||||
export default Placeholder;
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = void 0;
|
||||
var _portal = _interopRequireDefault(require("@rc-component/portal"));
|
||||
var React = _interopRequireWildcard(require("react"));
|
||||
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
||||
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
const Placeholder = /*#__PURE__*/React.forwardRef((props, ref) => {
|
||||
const {
|
||||
open,
|
||||
autoLock,
|
||||
getContainer,
|
||||
domRef,
|
||||
className,
|
||||
style,
|
||||
fallbackDOM
|
||||
} = props;
|
||||
React.useImperativeHandle(ref, () => domRef.current || fallbackDOM());
|
||||
return /*#__PURE__*/React.createElement(_portal.default, {
|
||||
open: open,
|
||||
autoLock: autoLock,
|
||||
getContainer: getContainer
|
||||
}, /*#__PURE__*/React.createElement("div", {
|
||||
ref: domRef,
|
||||
className: className,
|
||||
style: style
|
||||
}));
|
||||
});
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
Placeholder.displayName = 'Placeholder';
|
||||
}
|
||||
var _default = exports.default = Placeholder;
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import type { TourProps } from './interface';
|
||||
export type { TourProps };
|
||||
declare const Tour: React.FC<TourProps>;
|
||||
export default Tour;
|
||||
+252
@@ -0,0 +1,252 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = void 0;
|
||||
var _react = _interopRequireWildcard(require("react"));
|
||||
var React = _react;
|
||||
var _trigger = _interopRequireDefault(require("@rc-component/trigger"));
|
||||
var _clsx = require("clsx");
|
||||
var _useLayoutEffect = _interopRequireDefault(require("@rc-component/util/lib/hooks/useLayoutEffect"));
|
||||
var _useEvent = _interopRequireDefault(require("@rc-component/util/lib/hooks/useEvent"));
|
||||
var _KeyCode = _interopRequireDefault(require("@rc-component/util/lib/KeyCode"));
|
||||
var _useControlledState = _interopRequireDefault(require("@rc-component/util/lib/hooks/useControlledState"));
|
||||
var _useClosable = require("./hooks/useClosable");
|
||||
var _useTarget = _interopRequireDefault(require("./hooks/useTarget"));
|
||||
var _Mask = _interopRequireDefault(require("./Mask"));
|
||||
var _placements = require("./placements");
|
||||
var _TourStep = _interopRequireDefault(require("./TourStep"));
|
||||
var _util = require("./util");
|
||||
var _Placeholder = _interopRequireDefault(require("./Placeholder"));
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
||||
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
||||
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
||||
const CENTER_PLACEHOLDER = {
|
||||
left: '50%',
|
||||
top: '50%',
|
||||
width: 1,
|
||||
height: 1
|
||||
};
|
||||
const defaultScrollIntoViewOptions = {
|
||||
block: 'center',
|
||||
inline: 'center'
|
||||
};
|
||||
const Tour = props => {
|
||||
const {
|
||||
prefixCls = 'rc-tour',
|
||||
steps = [],
|
||||
defaultCurrent,
|
||||
current,
|
||||
keyboard = true,
|
||||
onChange,
|
||||
onClose,
|
||||
onFinish,
|
||||
open,
|
||||
defaultOpen,
|
||||
mask = true,
|
||||
arrow = true,
|
||||
rootClassName,
|
||||
placement,
|
||||
renderPanel,
|
||||
gap,
|
||||
animated,
|
||||
scrollIntoViewOptions = defaultScrollIntoViewOptions,
|
||||
zIndex = 1001,
|
||||
closeIcon,
|
||||
closable,
|
||||
builtinPlacements,
|
||||
disabledInteraction,
|
||||
styles,
|
||||
classNames: tourClassNames,
|
||||
className,
|
||||
style,
|
||||
getPopupContainer,
|
||||
...restProps
|
||||
} = props;
|
||||
const triggerRef = React.useRef();
|
||||
const [mergedCurrent, setMergedCurrent] = (0, _useControlledState.default)(defaultCurrent || 0, current);
|
||||
const [internalOpen, setMergedOpen] = (0, _useControlledState.default)(defaultOpen, open);
|
||||
const mergedOpen = mergedCurrent < 0 || mergedCurrent >= steps.length ? false : internalOpen ?? true;
|
||||
|
||||
// Record if already rended in the DOM to avoid `findDOMNode` issue
|
||||
const [hasOpened, setHasOpened] = React.useState(mergedOpen);
|
||||
const openRef = React.useRef(mergedOpen);
|
||||
(0, _useLayoutEffect.default)(() => {
|
||||
if (mergedOpen) {
|
||||
if (!openRef.current) {
|
||||
setMergedCurrent(0);
|
||||
}
|
||||
setHasOpened(true);
|
||||
}
|
||||
openRef.current = mergedOpen;
|
||||
}, [mergedOpen, setMergedCurrent]);
|
||||
const {
|
||||
target,
|
||||
placement: stepPlacement,
|
||||
style: stepStyle,
|
||||
arrow: stepArrow,
|
||||
className: stepClassName,
|
||||
mask: stepMask,
|
||||
scrollIntoViewOptions: stepScrollIntoViewOptions = defaultScrollIntoViewOptions,
|
||||
closeIcon: stepCloseIcon,
|
||||
closable: stepClosable
|
||||
} = steps[mergedCurrent] || {};
|
||||
const mergedClosable = (0, _useClosable.useClosable)(stepClosable, stepCloseIcon, closable, closeIcon);
|
||||
const mergedMask = mergedOpen && (stepMask ?? mask);
|
||||
const mergedScrollIntoViewOptions = stepScrollIntoViewOptions ?? scrollIntoViewOptions;
|
||||
|
||||
// ====================== Align Target ======================
|
||||
const placeholderRef = React.useRef(null);
|
||||
const inlineMode = getPopupContainer === false;
|
||||
const [posInfo, targetElement] = (0, _useTarget.default)(target, open, gap, mergedScrollIntoViewOptions, inlineMode, placeholderRef);
|
||||
const mergedPlacement = (0, _util.getPlacement)(targetElement, placement, stepPlacement);
|
||||
|
||||
// ========================= arrow =========================
|
||||
const mergedArrow = targetElement ? typeof stepArrow === 'undefined' ? arrow : stepArrow : false;
|
||||
const arrowPointAtCenter = typeof mergedArrow === 'object' ? mergedArrow.pointAtCenter : false;
|
||||
(0, _useLayoutEffect.default)(() => {
|
||||
triggerRef.current?.forceAlign();
|
||||
}, [arrowPointAtCenter, mergedCurrent]);
|
||||
|
||||
// ========================= Change =========================
|
||||
const onInternalChange = nextCurrent => {
|
||||
setMergedCurrent(nextCurrent);
|
||||
onChange?.(nextCurrent);
|
||||
};
|
||||
const mergedBuiltinPlacements = (0, _react.useMemo)(() => {
|
||||
if (builtinPlacements) {
|
||||
return typeof builtinPlacements === 'function' ? builtinPlacements({
|
||||
arrowPointAtCenter
|
||||
}) : builtinPlacements;
|
||||
}
|
||||
return (0, _placements.getPlacements)(arrowPointAtCenter);
|
||||
}, [builtinPlacements, arrowPointAtCenter]);
|
||||
const handleClose = () => {
|
||||
setMergedOpen(false);
|
||||
onClose?.(mergedCurrent);
|
||||
};
|
||||
|
||||
// ========================= Esc Close =========================
|
||||
// Use Portal's onEsc to handle Escape key with proper stacking logic
|
||||
const handleEscClose = (0, _useEvent.default)(({
|
||||
event
|
||||
}) => {
|
||||
if (keyboard && mergedClosable !== null) {
|
||||
event.preventDefault();
|
||||
handleClose();
|
||||
}
|
||||
});
|
||||
|
||||
// ========================= Keyboard =========================
|
||||
// Support ArrowLeft/ArrowRight to navigate steps.
|
||||
const keyboardHandler = (0, _useEvent.default)(e => {
|
||||
// Ignore keyboard events from input-like elements to avoid interfering when typing
|
||||
if (_KeyCode.default.isEditableTarget(e)) {
|
||||
return;
|
||||
}
|
||||
if (keyboard && e.key === 'ArrowLeft') {
|
||||
if (mergedCurrent > 0) {
|
||||
e.preventDefault();
|
||||
onInternalChange(mergedCurrent - 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (keyboard && e.key === 'ArrowRight') {
|
||||
if (mergedCurrent < steps.length - 1) {
|
||||
e.preventDefault();
|
||||
onInternalChange(mergedCurrent + 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
});
|
||||
(0, _useLayoutEffect.default)(() => {
|
||||
if (!mergedOpen) return;
|
||||
window.addEventListener('keydown', keyboardHandler);
|
||||
return () => {
|
||||
window.removeEventListener('keydown', keyboardHandler);
|
||||
};
|
||||
}, [mergedOpen, keyboardHandler]);
|
||||
|
||||
// ========================= Render =========================
|
||||
// Skip if not init yet
|
||||
if (targetElement === undefined || !hasOpened) {
|
||||
return null;
|
||||
}
|
||||
const getPopupElement = () => /*#__PURE__*/React.createElement(_TourStep.default, _extends({
|
||||
styles: styles,
|
||||
classNames: tourClassNames,
|
||||
arrow: mergedArrow,
|
||||
key: "content",
|
||||
prefixCls: prefixCls,
|
||||
total: steps.length,
|
||||
renderPanel: renderPanel,
|
||||
onPrev: () => {
|
||||
onInternalChange(mergedCurrent - 1);
|
||||
},
|
||||
onNext: () => {
|
||||
onInternalChange(mergedCurrent + 1);
|
||||
},
|
||||
onClose: handleClose,
|
||||
current: mergedCurrent,
|
||||
onFinish: () => {
|
||||
handleClose();
|
||||
onFinish?.();
|
||||
}
|
||||
}, steps[mergedCurrent], {
|
||||
closable: mergedClosable
|
||||
}));
|
||||
const mergedShowMask = typeof mergedMask === 'boolean' ? mergedMask : !!mergedMask;
|
||||
const mergedMaskStyle = typeof mergedMask === 'boolean' ? undefined : mergedMask;
|
||||
|
||||
// when targetElement is not exist, use body as triggerDOMNode
|
||||
const fallbackDOM = () => {
|
||||
return targetElement || document.body;
|
||||
};
|
||||
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(_Mask.default, {
|
||||
getPopupContainer: getPopupContainer,
|
||||
styles: styles,
|
||||
classNames: tourClassNames,
|
||||
zIndex: zIndex,
|
||||
prefixCls: prefixCls,
|
||||
pos: posInfo,
|
||||
showMask: mergedShowMask,
|
||||
style: mergedMaskStyle?.style,
|
||||
fill: mergedMaskStyle?.color,
|
||||
open: mergedOpen,
|
||||
animated: animated,
|
||||
rootClassName: rootClassName,
|
||||
disabledInteraction: disabledInteraction,
|
||||
onEsc: handleEscClose
|
||||
}), /*#__PURE__*/React.createElement(_trigger.default, _extends({}, restProps, {
|
||||
// `rc-portal` def bug not support `false` but does support and in used.
|
||||
getPopupContainer: getPopupContainer,
|
||||
builtinPlacements: mergedBuiltinPlacements,
|
||||
ref: triggerRef,
|
||||
popupStyle: stepStyle,
|
||||
popupPlacement: mergedPlacement,
|
||||
popupVisible: mergedOpen,
|
||||
popupClassName: (0, _clsx.clsx)(rootClassName, stepClassName),
|
||||
prefixCls: prefixCls,
|
||||
popup: getPopupElement,
|
||||
forceRender: false,
|
||||
autoDestroy: true,
|
||||
zIndex: zIndex,
|
||||
arrow: !!mergedArrow
|
||||
}), /*#__PURE__*/React.createElement(_Placeholder.default, {
|
||||
open: mergedOpen,
|
||||
autoLock: !inlineMode,
|
||||
getContainer: getPopupContainer,
|
||||
domRef: placeholderRef,
|
||||
fallbackDOM: fallbackDOM,
|
||||
className: (0, _clsx.clsx)(className, rootClassName, `${prefixCls}-target-placeholder`),
|
||||
style: {
|
||||
...(posInfo || CENTER_PLACEHOLDER),
|
||||
position: inlineMode ? 'absolute' : 'fixed',
|
||||
pointerEvents: 'none',
|
||||
...style
|
||||
}
|
||||
})));
|
||||
};
|
||||
var _default = exports.default = Tour;
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import type { TourStepProps } from '../interface';
|
||||
export type DefaultPanelProps = Exclude<TourStepProps, 'closable'> & {
|
||||
closable: Exclude<TourStepProps['closable'], boolean>;
|
||||
};
|
||||
export default function DefaultPanel(props: DefaultPanelProps): React.JSX.Element;
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = DefaultPanel;
|
||||
var React = _interopRequireWildcard(require("react"));
|
||||
var _clsx = require("clsx");
|
||||
var _pickAttrs = _interopRequireDefault(require("@rc-component/util/lib/pickAttrs"));
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
||||
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
||||
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
||||
function DefaultPanel(props) {
|
||||
const {
|
||||
prefixCls,
|
||||
current,
|
||||
total,
|
||||
title,
|
||||
description,
|
||||
onClose,
|
||||
onPrev,
|
||||
onNext,
|
||||
onFinish,
|
||||
className,
|
||||
closable,
|
||||
classNames: tourClassNames,
|
||||
styles
|
||||
} = props;
|
||||
const ariaProps = (0, _pickAttrs.default)(closable || {}, true);
|
||||
const closeIcon = closable?.closeIcon ?? /*#__PURE__*/React.createElement("span", {
|
||||
className: `${prefixCls}-close-x`
|
||||
}, "\xD7");
|
||||
const mergedClosable = !!closable;
|
||||
return /*#__PURE__*/React.createElement("div", {
|
||||
className: (0, _clsx.clsx)(`${prefixCls}-panel`, className)
|
||||
}, /*#__PURE__*/React.createElement("div", {
|
||||
className: (0, _clsx.clsx)(`${prefixCls}-section`, tourClassNames?.section),
|
||||
style: styles?.section
|
||||
}, mergedClosable && /*#__PURE__*/React.createElement("button", _extends({
|
||||
type: "button",
|
||||
onClick: onClose,
|
||||
"aria-label": "Close"
|
||||
}, ariaProps, {
|
||||
className: `${prefixCls}-close`
|
||||
}), closeIcon), /*#__PURE__*/React.createElement("div", {
|
||||
className: (0, _clsx.clsx)(`${prefixCls}-header`, tourClassNames?.header),
|
||||
style: styles?.header
|
||||
}, /*#__PURE__*/React.createElement("div", {
|
||||
className: (0, _clsx.clsx)(`${prefixCls}-title`, tourClassNames?.title),
|
||||
style: styles?.title
|
||||
}, title)), /*#__PURE__*/React.createElement("div", {
|
||||
className: (0, _clsx.clsx)(`${prefixCls}-description`, tourClassNames?.description),
|
||||
style: styles?.description
|
||||
}, description), /*#__PURE__*/React.createElement("div", {
|
||||
className: (0, _clsx.clsx)(`${prefixCls}-footer`, tourClassNames?.footer),
|
||||
style: styles?.footer
|
||||
}, /*#__PURE__*/React.createElement("div", {
|
||||
className: `${prefixCls}-sliders`
|
||||
}, total > 1 ? [...Array.from({
|
||||
length: total
|
||||
}).keys()].map((item, index) => {
|
||||
return /*#__PURE__*/React.createElement("span", {
|
||||
key: item,
|
||||
className: index === current ? 'active' : ''
|
||||
});
|
||||
}) : null), /*#__PURE__*/React.createElement("div", {
|
||||
className: (0, _clsx.clsx)(`${prefixCls}-actions`, tourClassNames?.actions),
|
||||
style: styles?.actions
|
||||
}, current !== 0 ? /*#__PURE__*/React.createElement("button", {
|
||||
className: `${prefixCls}-prev-btn`,
|
||||
onClick: onPrev
|
||||
}, "Prev") : null, current === total - 1 ? /*#__PURE__*/React.createElement("button", {
|
||||
className: `${prefixCls}-finish-btn`,
|
||||
onClick: onFinish
|
||||
}, "Finish") : /*#__PURE__*/React.createElement("button", {
|
||||
className: `${prefixCls}-next-btn`,
|
||||
onClick: onNext
|
||||
}, "Next")))));
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { type DefaultPanelProps } from './DefaultPanel';
|
||||
import type { TourStepProps, TourStepInfo } from '../interface';
|
||||
export type { TourStepProps, TourStepInfo, };
|
||||
declare const TourStep: (props: DefaultPanelProps) => React.JSX.Element;
|
||||
export default TourStep;
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = void 0;
|
||||
var React = _interopRequireWildcard(require("react"));
|
||||
var _DefaultPanel = _interopRequireDefault(require("./DefaultPanel"));
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
||||
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
||||
const TourStep = props => {
|
||||
const {
|
||||
current,
|
||||
renderPanel
|
||||
} = props;
|
||||
return /*#__PURE__*/React.createElement(React.Fragment, null, typeof renderPanel === 'function' ? renderPanel(props, current) : /*#__PURE__*/React.createElement(_DefaultPanel.default, props));
|
||||
};
|
||||
var _default = exports.default = TourStep;
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import * as React from 'react';
|
||||
import type { TourProps } from '../interface';
|
||||
import type { TourStepInfo } from '../TourStep';
|
||||
export declare function useClosable(stepClosable: TourStepInfo['closable'], stepCloseIcon: TourStepInfo['closeIcon'], closable: TourProps['closable'], closeIcon: TourProps['closeIcon']): {
|
||||
closeIcon?: React.ReactNode;
|
||||
} & React.AriaAttributes & {
|
||||
[key: `data-${string}`]: unknown;
|
||||
} & Pick<React.HTMLAttributes<HTMLDivElement>, "role">;
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.useClosable = useClosable;
|
||||
var React = _interopRequireWildcard(require("react"));
|
||||
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
||||
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
||||
function isConfigObj(closable) {
|
||||
return closable !== null && typeof closable === 'object';
|
||||
}
|
||||
/**
|
||||
* Convert `closable` to ClosableConfig.
|
||||
* When `preset` is true, will auto fill ClosableConfig with default value.
|
||||
*/
|
||||
function getClosableConfig(closable, closeIcon, preset) {
|
||||
if (closable === false || closeIcon === false && (!isConfigObj(closable) || !closable.closeIcon)) {
|
||||
return null;
|
||||
}
|
||||
const mergedCloseIcon = typeof closeIcon !== 'boolean' ? closeIcon : undefined;
|
||||
if (isConfigObj(closable)) {
|
||||
return {
|
||||
...closable,
|
||||
closeIcon: closable.closeIcon ?? mergedCloseIcon
|
||||
};
|
||||
}
|
||||
|
||||
// When StepClosable no need auto fill, but RootClosable need this.
|
||||
return preset || closable || closeIcon ? {
|
||||
closeIcon: mergedCloseIcon
|
||||
} : 'empty';
|
||||
}
|
||||
function useClosable(stepClosable, stepCloseIcon, closable, closeIcon) {
|
||||
return React.useMemo(() => {
|
||||
const stepClosableConfig = getClosableConfig(stepClosable, stepCloseIcon, false);
|
||||
const rootClosableConfig = getClosableConfig(closable, closeIcon, true);
|
||||
if (stepClosableConfig !== 'empty') {
|
||||
return stepClosableConfig;
|
||||
}
|
||||
return rootClosableConfig;
|
||||
}, [closable, closeIcon, stepClosable, stepCloseIcon]);
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
/// <reference types="react" />
|
||||
import type { TourStepInfo } from '..';
|
||||
export interface Gap {
|
||||
offset?: number | [number, number];
|
||||
radius?: number;
|
||||
}
|
||||
export interface PosInfo {
|
||||
left: number;
|
||||
top: number;
|
||||
height: number;
|
||||
width: number;
|
||||
radius: number;
|
||||
}
|
||||
export default function useTarget(target: TourStepInfo['target'], open: boolean, gap?: Gap, scrollIntoViewOptions?: boolean | ScrollIntoViewOptions, inlineMode?: boolean, placeholderRef?: React.RefObject<HTMLDivElement>): [PosInfo, HTMLElement];
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = useTarget;
|
||||
var _useEvent = _interopRequireDefault(require("@rc-component/util/lib/hooks/useEvent"));
|
||||
var _useLayoutEffect = _interopRequireDefault(require("@rc-component/util/lib/hooks/useLayoutEffect"));
|
||||
var _react = require("react");
|
||||
var _util = require("../util");
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
function isValidNumber(val) {
|
||||
return typeof val === 'number' && !Number.isNaN(val);
|
||||
}
|
||||
function useTarget(target, open, gap, scrollIntoViewOptions, inlineMode, placeholderRef) {
|
||||
// ========================= Target =========================
|
||||
// We trade `undefined` as not get target by function yet.
|
||||
// `null` as empty target.
|
||||
const [targetElement, setTargetElement] = (0, _react.useState)(undefined);
|
||||
(0, _useLayoutEffect.default)(() => {
|
||||
const nextElement = typeof target === 'function' ? target() : target;
|
||||
setTargetElement(nextElement || null);
|
||||
});
|
||||
|
||||
// ========================= Align ==========================
|
||||
const [posInfo, setPosInfo] = (0, _react.useState)(null);
|
||||
const updatePos = (0, _useEvent.default)(() => {
|
||||
if (targetElement) {
|
||||
// Exist target element. We should scroll and get target position
|
||||
if (!inlineMode && !(0, _util.isInViewPort)(targetElement) && open) {
|
||||
targetElement.scrollIntoView(scrollIntoViewOptions);
|
||||
}
|
||||
const {
|
||||
left,
|
||||
top,
|
||||
width,
|
||||
height
|
||||
} = targetElement.getBoundingClientRect();
|
||||
const nextPosInfo = {
|
||||
left,
|
||||
top,
|
||||
width,
|
||||
height,
|
||||
radius: 0
|
||||
};
|
||||
|
||||
// If `inlineMode` we need cut off parent offset
|
||||
if (inlineMode) {
|
||||
const parentRect = placeholderRef.current?.parentElement?.getBoundingClientRect();
|
||||
if (parentRect) {
|
||||
nextPosInfo.left -= parentRect.left;
|
||||
nextPosInfo.top -= parentRect.top;
|
||||
}
|
||||
}
|
||||
setPosInfo(origin => {
|
||||
if (JSON.stringify(origin) !== JSON.stringify(nextPosInfo)) {
|
||||
return nextPosInfo;
|
||||
}
|
||||
return origin;
|
||||
});
|
||||
} else {
|
||||
// Not exist target which means we just show in center
|
||||
setPosInfo(null);
|
||||
}
|
||||
});
|
||||
const getGapOffset = index => (Array.isArray(gap?.offset) ? gap?.offset[index] : gap?.offset) ?? 6;
|
||||
(0, _useLayoutEffect.default)(() => {
|
||||
updatePos();
|
||||
// update when window resize
|
||||
window.addEventListener('resize', updatePos);
|
||||
// update when `document.body.style.overflow` is set to visible
|
||||
// by default, it will be set to hidden
|
||||
window.addEventListener('scroll', updatePos);
|
||||
return () => {
|
||||
window.removeEventListener('resize', updatePos);
|
||||
window.removeEventListener('scroll', updatePos);
|
||||
};
|
||||
}, [targetElement, open, updatePos]);
|
||||
|
||||
// ======================== PosInfo =========================
|
||||
const mergedPosInfo = (0, _react.useMemo)(() => {
|
||||
if (!posInfo) {
|
||||
return posInfo;
|
||||
}
|
||||
const gapOffsetX = getGapOffset(0);
|
||||
const gapOffsetY = getGapOffset(1);
|
||||
const gapRadius = isValidNumber(gap?.radius) ? gap?.radius : 2;
|
||||
return {
|
||||
left: posInfo.left - gapOffsetX,
|
||||
top: posInfo.top - gapOffsetY,
|
||||
width: posInfo.width + gapOffsetX * 2,
|
||||
height: posInfo.height + gapOffsetY * 2,
|
||||
radius: gapRadius
|
||||
};
|
||||
}, [posInfo, gap]);
|
||||
return [mergedPosInfo, targetElement];
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
import Tour from './Tour';
|
||||
export type { TourProps, TourStepInfo, TourStepProps } from './interface';
|
||||
export default Tour;
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = void 0;
|
||||
var _Tour = _interopRequireDefault(require("./Tour"));
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
var _default = exports.default = _Tour.default;
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
import type { TriggerProps } from '@rc-component/trigger';
|
||||
import type { PlacementType } from './placements';
|
||||
import type { CSSProperties, ReactNode } from 'react';
|
||||
import type { Gap } from './hooks/useTarget';
|
||||
import { type DefaultPanelProps } from './TourStep/DefaultPanel';
|
||||
export type SemanticName = 'section' | 'footer' | 'actions' | 'header' | 'title' | 'description' | 'mask';
|
||||
export type HTMLAriaDataAttributes = React.AriaAttributes & {
|
||||
[key: `data-${string}`]: unknown;
|
||||
} & Pick<React.HTMLAttributes<HTMLDivElement>, 'role'>;
|
||||
export interface TourStepInfo {
|
||||
arrow?: boolean | {
|
||||
pointAtCenter: boolean;
|
||||
};
|
||||
target?: HTMLElement | (() => HTMLElement) | null | (() => null);
|
||||
title: ReactNode;
|
||||
description?: ReactNode;
|
||||
placement?: PlacementType;
|
||||
mask?: boolean | {
|
||||
style?: React.CSSProperties;
|
||||
color?: string;
|
||||
};
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
scrollIntoViewOptions?: boolean | ScrollIntoViewOptions;
|
||||
closeIcon?: ReactNode;
|
||||
closable?: boolean | ({
|
||||
closeIcon?: ReactNode;
|
||||
} & HTMLAriaDataAttributes);
|
||||
}
|
||||
export interface TourStepProps extends TourStepInfo {
|
||||
prefixCls?: string;
|
||||
total?: number;
|
||||
current?: number;
|
||||
onClose?: () => void;
|
||||
onFinish?: () => void;
|
||||
renderPanel?: (step: TourStepProps, current: number) => ReactNode;
|
||||
onPrev?: () => void;
|
||||
onNext?: () => void;
|
||||
classNames?: Partial<Record<SemanticName, string>>;
|
||||
styles?: Partial<Record<SemanticName, React.CSSProperties>>;
|
||||
}
|
||||
export interface TourProps extends Pick<TriggerProps, 'onPopupAlign'> {
|
||||
classNames?: Partial<Record<SemanticName, string>>;
|
||||
styles?: Partial<Record<SemanticName, React.CSSProperties>>;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
steps?: TourStepInfo[];
|
||||
open?: boolean;
|
||||
keyboard?: boolean;
|
||||
defaultOpen?: boolean;
|
||||
defaultCurrent?: number;
|
||||
current?: number;
|
||||
onChange?: (current: number) => void;
|
||||
onClose?: (current: number) => void;
|
||||
onFinish?: () => void;
|
||||
closeIcon?: TourStepProps['closeIcon'];
|
||||
closable?: TourStepProps['closable'];
|
||||
mask?: boolean | {
|
||||
style?: React.CSSProperties;
|
||||
color?: string;
|
||||
};
|
||||
arrow?: boolean | {
|
||||
pointAtCenter: boolean;
|
||||
};
|
||||
rootClassName?: string;
|
||||
placement?: PlacementType;
|
||||
prefixCls?: string;
|
||||
renderPanel?: (props: DefaultPanelProps, current: number) => ReactNode;
|
||||
gap?: Gap;
|
||||
animated?: boolean | {
|
||||
placeholder: boolean;
|
||||
};
|
||||
scrollIntoViewOptions?: boolean | ScrollIntoViewOptions;
|
||||
zIndex?: number;
|
||||
getPopupContainer?: TriggerProps['getPopupContainer'] | false;
|
||||
builtinPlacements?: TriggerProps['builtinPlacements'] | ((config?: {
|
||||
arrowPointAtCenter?: boolean;
|
||||
}) => TriggerProps['builtinPlacements']);
|
||||
disabledInteraction?: boolean;
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
import type { BuildInPlacements } from '@rc-component/trigger';
|
||||
export type PlacementType = 'left' | 'leftTop' | 'leftBottom' | 'right' | 'rightTop' | 'rightBottom' | 'top' | 'topLeft' | 'topRight' | 'bottom' | 'bottomLeft' | 'bottomRight' | 'center';
|
||||
export declare function getPlacements(arrowPointAtCenter?: boolean): BuildInPlacements;
|
||||
export declare const placements: BuildInPlacements;
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.getPlacements = getPlacements;
|
||||
exports.placements = void 0;
|
||||
const targetOffset = [0, 0];
|
||||
const basePlacements = {
|
||||
left: {
|
||||
points: ['cr', 'cl'],
|
||||
offset: [-8, 0]
|
||||
},
|
||||
right: {
|
||||
points: ['cl', 'cr'],
|
||||
offset: [8, 0]
|
||||
},
|
||||
top: {
|
||||
points: ['bc', 'tc'],
|
||||
offset: [0, -8]
|
||||
},
|
||||
bottom: {
|
||||
points: ['tc', 'bc'],
|
||||
offset: [0, 8]
|
||||
},
|
||||
topLeft: {
|
||||
points: ['bl', 'tl'],
|
||||
offset: [0, -8]
|
||||
},
|
||||
leftTop: {
|
||||
points: ['tr', 'tl'],
|
||||
offset: [-8, 0]
|
||||
},
|
||||
topRight: {
|
||||
points: ['br', 'tr'],
|
||||
offset: [0, -8]
|
||||
},
|
||||
rightTop: {
|
||||
points: ['tl', 'tr'],
|
||||
offset: [8, 0]
|
||||
},
|
||||
bottomRight: {
|
||||
points: ['tr', 'br'],
|
||||
offset: [0, 8]
|
||||
},
|
||||
rightBottom: {
|
||||
points: ['bl', 'br'],
|
||||
offset: [8, 0]
|
||||
},
|
||||
bottomLeft: {
|
||||
points: ['tl', 'bl'],
|
||||
offset: [0, 8]
|
||||
},
|
||||
leftBottom: {
|
||||
points: ['br', 'bl'],
|
||||
offset: [-8, 0]
|
||||
}
|
||||
};
|
||||
function getPlacements(arrowPointAtCenter = false) {
|
||||
const placements = {};
|
||||
Object.keys(basePlacements).forEach(key => {
|
||||
placements[key] = {
|
||||
...basePlacements[key],
|
||||
autoArrow: arrowPointAtCenter,
|
||||
targetOffset
|
||||
};
|
||||
});
|
||||
return placements;
|
||||
}
|
||||
const placements = exports.placements = getPlacements();
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
import type { PlacementType } from './placements';
|
||||
export declare function isInViewPort(element: HTMLElement): boolean;
|
||||
export declare function getPlacement(targetElement?: HTMLElement | null, placement?: PlacementType, stepPlacement?: PlacementType): PlacementType;
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.getPlacement = getPlacement;
|
||||
exports.isInViewPort = isInViewPort;
|
||||
function isInViewPort(element) {
|
||||
const viewWidth = window.innerWidth || document.documentElement.clientWidth;
|
||||
const viewHeight = window.innerHeight || document.documentElement.clientHeight;
|
||||
const {
|
||||
top,
|
||||
right,
|
||||
bottom,
|
||||
left
|
||||
} = element.getBoundingClientRect();
|
||||
return top >= 0 && left >= 0 && right <= viewWidth && bottom <= viewHeight;
|
||||
}
|
||||
function getPlacement(targetElement, placement, stepPlacement) {
|
||||
return stepPlacement ?? placement ?? (targetElement === null ? 'center' : 'bottom');
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
{
|
||||
"name": "@rc-component/tour",
|
||||
"version": "2.3.0",
|
||||
"description": "React tour Component",
|
||||
"keywords": [
|
||||
"react",
|
||||
"react-component",
|
||||
"react-tour"
|
||||
],
|
||||
"homepage": "https://github.com/react-component/tour",
|
||||
"bugs": {
|
||||
"url": "https://github.com/react-component/tour/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/react-component/tour.git"
|
||||
},
|
||||
"license": "MIT",
|
||||
"author": "wangning4567@163.com",
|
||||
"main": "./lib/index",
|
||||
"module": "./es/index",
|
||||
"typings": "es/index.d.ts",
|
||||
"files": [
|
||||
"lib",
|
||||
"es"
|
||||
],
|
||||
"scripts": {
|
||||
"compile": "father build",
|
||||
"deploy": "npm run docs:build && npm run docs:deploy",
|
||||
"docs:build": "dumi build",
|
||||
"docs:deploy": "gh-pages -d docs-dist",
|
||||
"lint": "eslint src/ --ext .tsx,.ts",
|
||||
"lint:tsc": "tsc -p tsconfig.json --noEmit",
|
||||
"now-build": "npm run docs:build",
|
||||
"prepare": "dumi setup",
|
||||
"prepublishOnly": "npm run compile && rc-np",
|
||||
"prettier": "prettier --write \"**/*.{js,jsx,tsx,ts,less,md,json}\"",
|
||||
"start": "dumi dev",
|
||||
"test": "rc-test",
|
||||
"watch": "father dev"
|
||||
},
|
||||
"dependencies": {
|
||||
"@rc-component/portal": "^2.2.0",
|
||||
"@rc-component/trigger": "^3.0.0",
|
||||
"@rc-component/util": "^1.7.0",
|
||||
"clsx": "^2.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rc-component/father-plugin": "^2.0.3",
|
||||
"@rc-component/np": "^1.0.3",
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
"@testing-library/react": "^15.0.0",
|
||||
"@types/jest": "^29.0.0",
|
||||
"@types/minimatch": "^5.1.2",
|
||||
"@types/node": "^24.5.2",
|
||||
"@types/react": "^18.0.0",
|
||||
"@types/react-dom": "^18.0.0",
|
||||
"@umijs/fabric": "^4.0.0",
|
||||
"cheerio": "1.0.0-rc.12",
|
||||
"dumi": "^2.1.2",
|
||||
"eslint": "^8.0.0",
|
||||
"father": "^4.0.0",
|
||||
"gh-pages": "^3.1.0",
|
||||
"prettier": "^3.0.0",
|
||||
"rc-test": "^7.1.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"typescript": "^5.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.9.0",
|
||||
"react-dom": ">=16.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.x"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user