From b1bddf2ad74ec8a66ff158ec008c9819cfb413d1 Mon Sep 17 00:00:00 2001 From: YilunSun Date: Mon, 10 Oct 2022 23:37:21 +0800 Subject: [PATCH 01/14] feat(guide): support guide component --- site/site.config.mjs | 6 + src/_util/dom.ts | 85 +++++ src/guide/Guide.tsx | 413 +++++++++++++++++++++++++ src/guide/__tests__/guide.test.tsx | 4 + src/guide/_example/base.css | 40 +++ src/guide/_example/base.jsx | 105 +++++++ src/guide/_example/custom-popup.css | 40 +++ src/guide/_example/custom-popup.jsx | 109 +++++++ src/guide/_example/dialog-body.css | 12 + src/guide/_example/dialog-body.jsx | 11 + src/guide/_example/dialog.css | 55 ++++ src/guide/_example/dialog.jsx | 107 +++++++ src/guide/_example/my-popup.css | 28 ++ src/guide/_example/my-popup.jsx | 35 +++ src/guide/_example/no-mask.css | 40 +++ src/guide/_example/no-mask.jsx | 106 +++++++ src/guide/_example/popup-dialog.css | 40 +++ src/guide/_example/popup-dialog.jsx | 107 +++++++ src/guide/defaultProps.ts | 16 + src/guide/index.ts | 9 + src/guide/style/css.js | 1 + src/guide/style/index.js | 1 + src/guide/type.ts | 197 ++++++++++++ src/guide/utils/getRelativePosition.ts | 34 ++ src/guide/utils/getScrollParent.ts | 28 ++ src/guide/utils/getTargetElm.ts | 20 ++ src/guide/utils/index.ts | 7 + src/guide/utils/scrollToElm.ts | 18 ++ src/guide/utils/useWatch.ts | 20 ++ src/index.ts | 1 + 30 files changed, 1695 insertions(+) create mode 100644 src/guide/Guide.tsx create mode 100644 src/guide/__tests__/guide.test.tsx create mode 100644 src/guide/_example/base.css create mode 100644 src/guide/_example/base.jsx create mode 100644 src/guide/_example/custom-popup.css create mode 100644 src/guide/_example/custom-popup.jsx create mode 100644 src/guide/_example/dialog-body.css create mode 100644 src/guide/_example/dialog-body.jsx create mode 100644 src/guide/_example/dialog.css create mode 100644 src/guide/_example/dialog.jsx create mode 100644 src/guide/_example/my-popup.css create mode 100644 src/guide/_example/my-popup.jsx create mode 100644 src/guide/_example/no-mask.css create mode 100644 src/guide/_example/no-mask.jsx create mode 100644 src/guide/_example/popup-dialog.css create mode 100644 src/guide/_example/popup-dialog.jsx create mode 100644 src/guide/defaultProps.ts create mode 100644 src/guide/index.ts create mode 100644 src/guide/style/css.js create mode 100644 src/guide/style/index.js create mode 100644 src/guide/type.ts create mode 100644 src/guide/utils/getRelativePosition.ts create mode 100644 src/guide/utils/getScrollParent.ts create mode 100644 src/guide/utils/getTargetElm.ts create mode 100644 src/guide/utils/index.ts create mode 100644 src/guide/utils/scrollToElm.ts create mode 100644 src/guide/utils/useWatch.ts diff --git a/site/site.config.mjs b/site/site.config.mjs index 69640b5a8d..3bca02d10c 100644 --- a/site/site.config.mjs +++ b/site/site.config.mjs @@ -452,6 +452,12 @@ export default { path: '/react/components/drawer', component: () => import('tdesign-react/drawer/drawer.md'), }, + { + title: 'Guide 引导', + name: 'guide', + path: '/react/components/guide', + component: () => import('tdesign-react/guide/guide.md'), + }, { title: 'Message 全局提醒', name: 'message', diff --git a/src/_util/dom.ts b/src/_util/dom.ts index cd8a210e57..a8b846b44b 100644 --- a/src/_util/dom.ts +++ b/src/_util/dom.ts @@ -208,3 +208,88 @@ export const getCssVarsValue = (name: string, element?: HTMLElement) => { const el = element || document.documentElement; return getComputedStyle(el).getPropertyValue(name); }; + +/** + * 检查元素是否在父元素视图 + * http://stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport + * @param elm 元素 + * @param parent + * @returns boolean + */ +export function elementInViewport(elm: HTMLElement, parent?: HTMLElement): boolean { + const rect = elm.getBoundingClientRect(); + if (parent) { + const parentRect = parent.getBoundingClientRect(); + return ( + rect.top >= parentRect.top && + rect.left >= parentRect.left && + rect.bottom <= parentRect.bottom && + rect.right <= parentRect.right + ); + } + return rect.top >= 0 && rect.left >= 0 && rect.bottom + 80 <= window.innerHeight && rect.right <= window.innerWidth; +} + +/** + * 获取元素某个 css 对应的值 + * @param element 元素 + * @param propName css 名 + * @returns string + */ +export function getElmCssPropValue(element: HTMLElement, propName: string): string { + let propValue = ''; + + if (document.defaultView && document.defaultView.getComputedStyle) { + propValue = document.defaultView.getComputedStyle(element, null).getPropertyValue(propName); + } + + if (propValue && propValue.toLowerCase) { + return propValue.toLowerCase(); + } + + return propValue; +} + +/** + * 判断元素是否处在 position fixed 中 + * @param element 元素 + * @returns boolean + */ +export function isFixed(element: HTMLElement): boolean { + const p = element.parentNode as HTMLElement; + + if (!p || p.nodeName === 'HTML') { + return false; + } + + if (getElmCssPropValue(element, 'position') === 'fixed') { + return true; + } + + return isFixed(p); +} + +/** + * 获取当前视图滑动的距离 + * @returns { scrollTop: number, scrollLeft: number } + */ +export function getWindowScroll(): { scrollTop: number; scrollLeft: number } { + const { body } = document; + const docElm = document.documentElement; + const scrollTop = window.pageYOffset || docElm.scrollTop || body.scrollTop; + const scrollLeft = window.pageXOffset || docElm.scrollLeft || body.scrollLeft; + + return { scrollTop, scrollLeft }; +} + +/** + * 获取当前视图的大小 + * @returns { width: number, height: number } + */ +export function getWindowSize(): { width: number; height: number } { + if (window.innerWidth !== undefined) { + return { width: window.innerWidth, height: window.innerHeight }; + } + const doc = document.documentElement; + return { width: doc.clientWidth, height: doc.clientHeight }; +} diff --git a/src/guide/Guide.tsx b/src/guide/Guide.tsx new file mode 100644 index 0000000000..c73df9efa5 --- /dev/null +++ b/src/guide/Guide.tsx @@ -0,0 +1,413 @@ +import React, { useEffect, useMemo, useRef, useState } from 'react'; +import cx from 'classnames'; +import { createPortal } from 'react-dom'; +import Button from '../button'; +import useConfig from '../hooks/useConfig'; +import Popup from '../popup'; +import { GuideCrossProps, StepPopupPlacement, TdGuideProps } from './type'; +import { addClass, removeClass, isFixed, getWindowScroll } from '../_util/dom'; +import { scrollToParentVisibleArea, getRelativePosition, getTargetElm, scrollToElm, useWatch } from './utils'; +import setStyle from '../_common/js/utils/set-style'; +import useControlled from '../hooks/useControlled'; +import { guideDefaultProps } from './defaultProps'; + +export type GuideProps = TdGuideProps; + +const Guide = (props: GuideProps) => { + const { counter, hideCounter, hidePrev, hideSkip, steps, zIndex } = props; + + const { classPrefix } = useConfig(); + const prefixCls = `${classPrefix}-guide`; + const lockCls = `${prefixCls}--lock`; + + const [innerCurrent, setInnerCurrent] = useControlled(props, 'current', props.onChange); + + // 覆盖层,用于覆盖所有元素 + const overlayLayerRef = useRef(null); + // 高亮层,用于高亮元素 + const highlightLayerRef = useRef(null); + // 提示层,用于高亮元素 + const referenceLayerRef = useRef(null); + // 当前高亮的元素 + const currentHighlightLayerElm = useRef(null); + // 下一个高亮的元素 + const nextHighlightLayerElm = useRef(null); + // dialog wrapper ref + const dialogWrapperRef = useRef(null); + // dialog ref + const dialogTooltipRef = useRef(null); + // 是否开始展示 + const [actived, setActive] = useState(false); + // 步骤总数 + const stepsTotal = steps.length; + // 当前步骤的信息 + const currentStepInfo = useMemo(() => steps[innerCurrent], [steps, innerCurrent]); + // 获取当前步骤的所有属性 用户当前步骤设置 > 用户全局设置的 > 默认值 + const getCurrentCrossProps = (propsName: Key) => + currentStepInfo?.[propsName] ?? props[propsName]; + // 当前是否为 popup + const isPopup = getCurrentCrossProps('mode') === 'popup'; + // 当前元素位置状态 + const currentElmIsFixed = isFixed(currentHighlightLayerElm.current || document.body); + + // 设置高亮层的位置 + const setHighlightLayerPosition = (highlighLayer: HTMLElement) => { + let { top, left } = getRelativePosition(nextHighlightLayerElm.current, currentHighlightLayerElm.current); + let { width, height } = nextHighlightLayerElm.current.getBoundingClientRect(); + const highlightPadding = getCurrentCrossProps('highlightPadding'); + + if (isPopup) { + width += highlightPadding * 2; + height += highlightPadding * 2; + top -= highlightPadding; + left -= highlightPadding; + } else { + const { scrollTop, scrollLeft } = getWindowScroll(); + top += scrollTop; + left += scrollLeft; + } + + setStyle(highlighLayer, { + width: `${width}px`, + height: `${height}px`, + top: `${top}px`, + left: `${left}px`, + }); + }; + + const showPopupGuide = () => { + const currentElement = getTargetElm(currentStepInfo.element); + nextHighlightLayerElm.current = currentElement; + currentHighlightLayerElm.current = currentElement; + + setTimeout(() => { + scrollToParentVisibleArea(nextHighlightLayerElm.current); + setHighlightLayerPosition(highlightLayerRef.current); + setHighlightLayerPosition(referenceLayerRef.current); + scrollToElm(nextHighlightLayerElm.current); + }); + }; + + const destroyTooltipElm = () => { + referenceLayerRef.current?.parentNode.removeChild(referenceLayerRef.current); + }; + + const showDialogGuide = () => { + setTimeout(() => { + const currentElement = dialogTooltipRef.current; + nextHighlightLayerElm.current = currentElement; + currentHighlightLayerElm.current = currentElement; + scrollToParentVisibleArea(nextHighlightLayerElm.current); + setHighlightLayerPosition(highlightLayerRef.current); + scrollToElm(nextHighlightLayerElm.current); + }); + }; + + const destroyDialogTooltipElm = () => { + dialogTooltipRef.current?.parentNode.removeChild(dialogTooltipRef.current); + dialogWrapperRef.current?.parentNode.removeChild(dialogWrapperRef.current); + }; + + const showGuide = () => { + if (isPopup) { + destroyDialogTooltipElm(); + showPopupGuide(); + } else { + destroyTooltipElm(); + showDialogGuide(); + } + }; + + const destroyGuide = () => { + destroyTooltipElm(); + destroyDialogTooltipElm(); + highlightLayerRef.current?.parentNode.removeChild(highlightLayerRef.current); + overlayLayerRef.current?.parentNode.removeChild(overlayLayerRef.current); + removeClass(document.body, lockCls); + }; + + const handleSkip = (e) => { + const total = stepsTotal; + setActive(false); + setInnerCurrent(-1, { e, total }); + props.onSkip?.({ e, current: -1, total }); + }; + + const handlePrev = (e) => { + const total = stepsTotal; + setInnerCurrent(innerCurrent - 1, { e, total }); + props.onPrevStepClick?.({ + e, + prev: innerCurrent - 1, + current: innerCurrent, + total, + }); + }; + + const handleNext = (e) => { + const total = stepsTotal; + setInnerCurrent(innerCurrent + 1, { e, total }); + props.onNextStepClick?.({ + e, + next: innerCurrent + 1, + current: innerCurrent, + total, + }); + }; + + const handleFinish = (e) => { + const total = stepsTotal; + setActive(false); + setInnerCurrent(-1, { e, total }); + props.onFinish?.({ e, current: -1, total }); + }; + + const initGuide = () => { + if (innerCurrent >= 0 && innerCurrent < steps.length) { + if (!actived) { + setActive(true); + + addClass(document.body, lockCls); + } + showGuide(); + } + }; + + useWatch(innerCurrent, (newValue) => { + if (newValue >= 0 && newValue < steps.length) { + initGuide(); + } else { + setActive(false); + destroyGuide(); + } + }); + + const useMount = (callback) => { + useEffect(() => { + callback(); + }, []); + }; + + useMount(() => { + initGuide(); + }); + + const renderOverlayLayer = () => + createPortal( +
, + document.body, + ); + + const renderHighlightLayer = () => { + const style = { zIndex: zIndex - 1 }; + const highlightClass = [ + `${prefixCls}__highlight`, + `${prefixCls}__highlight--${isPopup ? 'popup' : 'dialog'}`, + `${prefixCls}--${currentElmIsFixed && isPopup ? 'fixed' : 'absolute'}`, + ]; + const showOverlay = getCurrentCrossProps('showOverlay'); + const maskClass = [`${prefixCls}__highlight--${showOverlay ? 'mask' : 'nomask'}`]; + const { highlightContent } = currentStepInfo; + const showHighlightContent = highlightContent && isPopup; + + return createPortal( +
+ {showHighlightContent && + React.cloneElement(highlightContent as any, { + className: cx(highlightClass.concat(maskClass)), + style, + })} +
, + document.body, + ); + }; + + const renderCounter = () => { + let popupSlotCounter; + + if (React.isValidElement(counter)) { + popupSlotCounter = React.cloneElement(counter, { total: stepsTotal, current: innerCurrent } as any); + } + + const popupDefaultCounter = ( +
+ {popupSlotCounter || ( + + {innerCurrent + 1}/{stepsTotal} + + )} +
+ ); + return <>{!hideCounter && popupDefaultCounter}; + }; + + const renderAction = (mode: TdGuideProps['mode']) => { + const isLast = innerCurrent === stepsTotal - 1; + const isFirst = innerCurrent === 0; + const buttonSize = mode === 'popup' ? 'small' : 'medium'; + + return ( +
+ {!hideSkip && !isLast && ( +
+ ); + }; + + const renderTooltipBody = () => { + const title =
{currentStepInfo.title}
; + const { body: descBody } = currentStepInfo; + + const desc =
{descBody}
; + + return ( + <> + {title} + {desc} + + ); + }; + + const renderPopupContent = () => { + const footerClasses = [`${prefixCls}__footer`, `${prefixCls}__footer--popup`]; + const action = ( +
+ {renderCounter()} + {renderAction('popup')} +
+ ); + + return ( +
+ {renderTooltipBody()} + {action} +
+ ); + }; + + const renderPopupGuide = () => { + const { content } = currentStepInfo; + let renderBody; + if (React.isValidElement(content)) { + const contentProps = { + handlePrev, + handleNext, + handleSkip, + handleFinish, + current: innerCurrent, + total: stepsTotal, + }; + renderBody = React.cloneElement(content as any, contentProps); + } else { + renderBody = renderPopupContent(); + } + + const classes = [`${prefixCls}__reference`, `${prefixCls}--${currentElmIsFixed ? 'fixed' : 'absolute'}`]; + + return createPortal( + +
+ , + document.body, + ); + }; + + const renderDialogGuide = () => { + const style = { zIndex }; + const wrapperClasses = [ + `${prefixCls}__wrapper`, + { [`${prefixCls}__wrapper--center`]: currentStepInfo.placement === 'center' }, + ]; + const dialogClasses = [ + `${prefixCls}__reference`, + `${prefixCls}--absolute`, + `${prefixCls}__dialog`, + { + [`${prefixCls}__dialog--nomask`]: !getCurrentCrossProps('showOverlay'), + [currentStepInfo.stepOverlayClass]: !!currentStepInfo.stepOverlayClass, + }, + ]; + const footerClasses = [`${prefixCls}__footer`, `${prefixCls}__footer--popup`]; + return ( + <> + {createPortal( +
+
+ {renderTooltipBody()} +
+ {renderCounter()} + {renderAction('dialog')} +
+
+
, + document.body, + )} + + ); + }; + + const renderGuide = () => ( + <> + {renderOverlayLayer()} + {renderHighlightLayer()} + {isPopup ? renderPopupGuide() : renderDialogGuide()} + + ); + + return <>{actived && renderGuide()}; +}; + +Guide.displayName = 'Guide'; + +Guide.defaultProps = guideDefaultProps; + +export default Guide; diff --git a/src/guide/__tests__/guide.test.tsx b/src/guide/__tests__/guide.test.tsx new file mode 100644 index 0000000000..5b4e80e854 --- /dev/null +++ b/src/guide/__tests__/guide.test.tsx @@ -0,0 +1,4 @@ +import { testExamples } from '@test/utils'; + +// 测试组件代码 Example 快照 +testExamples(__dirname); diff --git a/src/guide/_example/base.css b/src/guide/_example/base.css new file mode 100644 index 0000000000..f771d5eef3 --- /dev/null +++ b/src/guide/_example/base.css @@ -0,0 +1,40 @@ +.guide-container { + max-width: 600px; + padding: 40px; +} + +.title-major { + color: var(--td-text-color-primary); + font-size: 36px; + font-weight: 700; + line-height: 44px; +} + +.title-sub { + margin-top: 8px; + color: var(--td-text-color-secondary); + font-size: 14px; + font-weight: 400; + line-height: 22px; +} + +.field { + margin-top: 50px; +} + +.label { + margin-bottom: 8px; + color: var(--td-text-color-primary); + font-size: 14px; + font-weight: 400; + line-height: 22px; +} + +.action { + display: inline-flex; + margin-top: 50px; +} + +.action button { + margin-right: 10px; +} diff --git a/src/guide/_example/base.jsx b/src/guide/_example/base.jsx new file mode 100644 index 0000000000..cc775b6d5b --- /dev/null +++ b/src/guide/_example/base.jsx @@ -0,0 +1,105 @@ +import React from 'react'; +import { Button, Drawer, Guide, Input, Row } from 'tdesign-react'; +import './base.css'; + +export default function BasicGuide() { + const steps = [ + { + element: '.main-title', + title: '新手引导标题', + body: '新手引导的说明文案', + placement: 'bottom-right', + }, + { + element: '.label-field', + title: '新手引导标题', + body: '新手引导的说明文案', + placement: 'bottom', + }, + { + element: '.action', + title: '新手引导标题', + body: '新手引导的说明文案', + placement: 'right', + }, + ]; + + const [visible, setVisible] = React.useState(false); + const [current, setCurrent] = React.useState(-1); + + const handleClick = () => { + setVisible(true); + setTimeout(() => { + setCurrent(0); + }, 800); + }; + + const handleChange = (current, { e, total }) => { + setCurrent(current); + console.log(current, e, total); + }; + + const handlePrevStepClick = ({ e, prev, current, total }) => { + console.log(e, prev, current, total); + }; + + const handleNextStepClick = ({ e, next, current, total }) => { + console.log(e, next, current, total); + }; + + const handleFinish = ({ e, current, total }) => { + setVisible(false); + console.log(e, current, total); + }; + + const handleSkip = ({ e, current, total }) => { + console.log('skip'); + setVisible(false); + console.log(e, current, total); + }; + + return ( + + + setVisible(false)}> 关闭抽屉 } + visible={visible} + header="演示新手引导" + size="60%" + showOverlay={false} + destroyOnClose={true} + > +
+
+
Guide 用户引导
+
按钮用于开启一个闭环的操作任务,如“删除”对象、“购买”商品等。
+
+
+
Label
+ +
+
+
Label
+ +
+ + + + +
+ + +
+
+ ); +} diff --git a/src/guide/_example/custom-popup.css b/src/guide/_example/custom-popup.css new file mode 100644 index 0000000000..f771d5eef3 --- /dev/null +++ b/src/guide/_example/custom-popup.css @@ -0,0 +1,40 @@ +.guide-container { + max-width: 600px; + padding: 40px; +} + +.title-major { + color: var(--td-text-color-primary); + font-size: 36px; + font-weight: 700; + line-height: 44px; +} + +.title-sub { + margin-top: 8px; + color: var(--td-text-color-secondary); + font-size: 14px; + font-weight: 400; + line-height: 22px; +} + +.field { + margin-top: 50px; +} + +.label { + margin-bottom: 8px; + color: var(--td-text-color-primary); + font-size: 14px; + font-weight: 400; + line-height: 22px; +} + +.action { + display: inline-flex; + margin-top: 50px; +} + +.action button { + margin-right: 10px; +} diff --git a/src/guide/_example/custom-popup.jsx b/src/guide/_example/custom-popup.jsx new file mode 100644 index 0000000000..ac10b71b55 --- /dev/null +++ b/src/guide/_example/custom-popup.jsx @@ -0,0 +1,109 @@ +import React from 'react'; +import { Button, Drawer, Guide, Input, Row } from 'tdesign-react'; +import './custom-popup.css'; +import MyPopup from './my-popup'; + +export default function CustomPopupGuide() { + const [visible, setVisible] = React.useState(false); + const [current, setCurrent] = React.useState(-1); + + const handleClick = () => { + setVisible(true); + setTimeout(() => { + setCurrent(0); + }, 800); + }; + + const handleChange = (current, { e, total }) => { + setCurrent(current); + console.log(current, e, total); + }; + + const handlePrevStepClick = ({ e, prev, current, total }) => { + console.log(e, prev, current, total); + }; + + const handleNextStepClick = ({ e, next, current, total }) => { + console.log(e, next, current, total); + }; + + const handleFinish = ({ e, current, total }) => { + setVisible(false); + console.log(e, current, total); + }; + + const handleSkip = ({ e, current, total }) => { + console.log('skip'); + setVisible(false); + console.log(e, current, total); + }; + + const steps = [ + { + element: '.main-title', + title: '新手引导标题', + description: '新手引导的说明文案', + placement: 'bottom-right', + content: , + }, + { + element: '.label-field-1', + title: '新手引导标题', + description: '新手引导的说明文案', + placement: 'bottom', + content: , + }, + { + element: '.label-field-2', + title: '新手引导标题', + description: '新手引导的说明文案', + placement: 'bottom-left', + content: , + }, + ]; + + return ( + + + setVisible(false)}> 关闭抽屉 } + visible={visible} + header="演示新手引导" + size="60%" + showOverlay={false} + destroyOnClose={true} + > +
+
+
Guide 用户引导
+
按钮用于开启一个闭环的操作任务,如“删除”对象、“购买”商品等。
+
+
+
Label
+ +
+
+
Label
+ +
+ + + + +
+ + +
+
+ ); +} diff --git a/src/guide/_example/dialog-body.css b/src/guide/_example/dialog-body.css new file mode 100644 index 0000000000..55a00ac294 --- /dev/null +++ b/src/guide/_example/dialog-body.css @@ -0,0 +1,12 @@ +.dialog-img { + width: 100%; +} + +p { + margin-top: 16px; + color: var(--td-text-color-secondary); + font-size: 14px; + font-weight: 400; + text-align: left; + line-height: 22px; +} diff --git a/src/guide/_example/dialog-body.jsx b/src/guide/_example/dialog-body.jsx new file mode 100644 index 0000000000..e096e3ce8c --- /dev/null +++ b/src/guide/_example/dialog-body.jsx @@ -0,0 +1,11 @@ +import React from 'react'; +import './dialog-body.css'; + +export default function DialogBody() { + return ( +
+ demo +

此处显示本页引导的说明文案,可按需要撰写,如内容过多可折行显示。图文也可按需自由设计。

+
+ ); +} diff --git a/src/guide/_example/dialog.css b/src/guide/_example/dialog.css new file mode 100644 index 0000000000..249e8c71e6 --- /dev/null +++ b/src/guide/_example/dialog.css @@ -0,0 +1,55 @@ +.guide-container { + max-width: 600px; + padding: 40px; +} + +.title-major { + color: var(--td-text-color-primary); + font-size: 36px; + font-weight: 700; + line-height: 44px; +} + +.title-sub { + margin-top: 8px; + color: var(--td-text-color-secondary); + font-size: 14px; + font-weight: 400; + line-height: 22px; +} + +.field { + margin-top: 50px; +} + +.label { + margin-bottom: 8px; + color: var(--td-text-color-primary); + font-size: 14px; + font-weight: 400; + line-height: 22px; +} + +.action { + display: inline-flex; + margin-top: 50px; +} + +.action button { + margin-right: 10px; +} + +/* dialog body */ + +.dialog-img { + width: 100%; +} + +p { + margin-top: 16px; + color: var(--td-text-color-secondary); + font-size: 14px; + font-weight: 400; + text-align: left; + line-height: 22px; +} diff --git a/src/guide/_example/dialog.jsx b/src/guide/_example/dialog.jsx new file mode 100644 index 0000000000..14aa723368 --- /dev/null +++ b/src/guide/_example/dialog.jsx @@ -0,0 +1,107 @@ +import React from 'react'; +import { Button, Drawer, Guide, Input, Row } from 'tdesign-react'; +import './base.css'; +import DialogBody from './dialog-body'; + +export default function DialogGuide() { + const steps = [ + { + element: '.main-title', + title: '新手引导标题', + body: DialogBody(), + placement: 'bottom-right', + }, + { + element: '.label-field', + title: '新手引导标题', + body: DialogBody(), + placement: 'bottom', + }, + { + element: '.action', + title: '新手引导标题', + body: DialogBody(), + placement: 'right', + }, + ]; + + const [visible, setVisible] = React.useState(false); + const [current, setCurrent] = React.useState(-1); + + const handleClick = () => { + setVisible(true); + setTimeout(() => { + setCurrent(0); + }, 800); + }; + + const handleChange = (current, { e, total }) => { + setCurrent(current); + console.log(current, e, total); + }; + + const handlePrevStepClick = ({ e, prev, current, total }) => { + console.log(e, prev, current, total); + }; + + const handleNextStepClick = ({ e, next, current, total }) => { + console.log(e, next, current, total); + }; + + const handleFinish = ({ e, current, total }) => { + setVisible(false); + console.log(e, current, total); + }; + + const handleSkip = ({ e, current, total }) => { + console.log('skip'); + setVisible(false); + console.log(e, current, total); + }; + + return ( + + + setVisible(false)}> 关闭抽屉 } + visible={visible} + header="演示新手引导" + size="60%" + showOverlay={false} + destroyOnClose={true} + > +
+
+
Guide 用户引导
+
按钮用于开启一个闭环的操作任务,如“删除”对象、“购买”商品等。
+
+
+
Label
+ +
+
+
Label
+ +
+ + + + +
+ + +
+
+ ); +} diff --git a/src/guide/_example/my-popup.css b/src/guide/_example/my-popup.css new file mode 100644 index 0000000000..716c8b6cc0 --- /dev/null +++ b/src/guide/_example/my-popup.css @@ -0,0 +1,28 @@ +.my-popup { + width: 240px; +} + +.pop-icon { + margin-top: 10px; + color: white; + font-size: 30px; + font-weight: bold; +} + +.popup-desc { + margin-top: 10px; + color: rgba(255, 255, 255, 0.9); + font-size: 12px; + font-weight: 400; + text-align: left; + line-height: 20px; +} + +.popup-action { + margin-top: 10px; + text-align: right; +} + +.popup-action button { + margin-left: 8px; +} diff --git a/src/guide/_example/my-popup.jsx b/src/guide/_example/my-popup.jsx new file mode 100644 index 0000000000..8ba3b21b62 --- /dev/null +++ b/src/guide/_example/my-popup.jsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { ArrowUpIcon } from 'tdesign-icons-react'; +import Button from '../../button'; +import './my-popup.css'; + +export default function MyPopup(props) { + const { handlePrev, handleNext, handleSkip, handleFinish, current, total } = props; + + return ( +
+ +

自定义的图形或说明文案,用来解释或指导该功能使用。

+
+ + {current !== 0 && ( + + )} + {current + 1 < total && ( + + )} + {current + 1 === total && ( + + )} +
+
+ ); +} diff --git a/src/guide/_example/no-mask.css b/src/guide/_example/no-mask.css new file mode 100644 index 0000000000..f771d5eef3 --- /dev/null +++ b/src/guide/_example/no-mask.css @@ -0,0 +1,40 @@ +.guide-container { + max-width: 600px; + padding: 40px; +} + +.title-major { + color: var(--td-text-color-primary); + font-size: 36px; + font-weight: 700; + line-height: 44px; +} + +.title-sub { + margin-top: 8px; + color: var(--td-text-color-secondary); + font-size: 14px; + font-weight: 400; + line-height: 22px; +} + +.field { + margin-top: 50px; +} + +.label { + margin-bottom: 8px; + color: var(--td-text-color-primary); + font-size: 14px; + font-weight: 400; + line-height: 22px; +} + +.action { + display: inline-flex; + margin-top: 50px; +} + +.action button { + margin-right: 10px; +} diff --git a/src/guide/_example/no-mask.jsx b/src/guide/_example/no-mask.jsx new file mode 100644 index 0000000000..4a67c0e810 --- /dev/null +++ b/src/guide/_example/no-mask.jsx @@ -0,0 +1,106 @@ +import React from 'react'; +import { Button, Drawer, Guide, Input, Row } from 'tdesign-react'; +import './no-mask.css'; + +export default function NoMaskGuide() { + const steps = [ + { + element: '.main-title-no-mask', + title: '新手引导标题', + body: '新手引导的说明文案', + placement: 'bottom-right', + }, + { + element: '.label-field', + title: '新手引导标题', + body: '新手引导的说明文案', + placement: 'bottom', + }, + { + element: '.action', + title: '新手引导标题', + body: '新手引导的说明文案', + placement: 'right', + }, + ]; + + const [visible, setVisible] = React.useState(false); + const [current, setCurrent] = React.useState(-1); + + const handleClick = () => { + setVisible(true); + setTimeout(() => { + setCurrent(0); + }, 800); + }; + + const handleChange = (current, { e, total }) => { + setCurrent(current); + console.log(current, e, total); + }; + + const handlePrevStepClick = ({ e, prev, current, total }) => { + console.log(e, prev, current, total); + }; + + const handleNextStepClick = ({ e, next, current, total }) => { + console.log(e, next, current, total); + }; + + const handleFinish = ({ e, current, total }) => { + setVisible(false); + console.log(e, current, total); + }; + + const handleSkip = ({ e, current, total }) => { + console.log('skip'); + setVisible(false); + console.log(e, current, total); + }; + + return ( + + + setVisible(false)}> 关闭抽屉 } + visible={visible} + header="演示新手引导" + size="60%" + showOverlay={false} + destroyOnClose={true} + > +
+
+
Guide 用户引导
+
按钮用于开启一个闭环的操作任务,如“删除”对象、“购买”商品等。
+
+
+
Label
+ +
+
+
Label
+ +
+ + + + +
+ + +
+
+ ); +} diff --git a/src/guide/_example/popup-dialog.css b/src/guide/_example/popup-dialog.css new file mode 100644 index 0000000000..f771d5eef3 --- /dev/null +++ b/src/guide/_example/popup-dialog.css @@ -0,0 +1,40 @@ +.guide-container { + max-width: 600px; + padding: 40px; +} + +.title-major { + color: var(--td-text-color-primary); + font-size: 36px; + font-weight: 700; + line-height: 44px; +} + +.title-sub { + margin-top: 8px; + color: var(--td-text-color-secondary); + font-size: 14px; + font-weight: 400; + line-height: 22px; +} + +.field { + margin-top: 50px; +} + +.label { + margin-bottom: 8px; + color: var(--td-text-color-primary); + font-size: 14px; + font-weight: 400; + line-height: 22px; +} + +.action { + display: inline-flex; + margin-top: 50px; +} + +.action button { + margin-right: 10px; +} diff --git a/src/guide/_example/popup-dialog.jsx b/src/guide/_example/popup-dialog.jsx new file mode 100644 index 0000000000..7a928cc321 --- /dev/null +++ b/src/guide/_example/popup-dialog.jsx @@ -0,0 +1,107 @@ +import React from 'react'; +import { Button, Drawer, Guide, Input, Row } from 'tdesign-react'; +import './popup-dialog.css'; +import DialogBody from './dialog-body'; + +export default function PopupDialogGuide() { + const steps = [ + { + element: '.main-title', + title: '新手引导标题', + body: '新手引导的说明文案', + placement: 'bottom-right', + }, + { + element: '.label-field', + title: '新手引导标题', + body: DialogBody, + placement: 'bottom', + mode: 'dialog', + }, + { + element: '.action', + title: '新手引导标题', + body: '新手引导的说明文案', + placement: 'right', + }, + ]; + + const [visible, setVisible] = React.useState(false); + const [current, setCurrent] = React.useState(-1); + + const handleClick = () => { + setVisible(true); + setTimeout(() => { + setCurrent(0); + }, 800); + }; + + const handleChange = (current, { e, total }) => { + setCurrent(current); + console.log(current, e, total); + }; + + const handlePrevStepClick = ({ e, prev, current, total }) => { + console.log(e, prev, current, total); + }; + + const handleNextStepClick = ({ e, next, current, total }) => { + console.log(e, next, current, total); + }; + + const handleFinish = ({ e, current, total }) => { + setVisible(false); + console.log(e, current, total); + }; + + const handleSkip = ({ e, current, total }) => { + console.log('skip'); + setVisible(false); + console.log(e, current, total); + }; + + return ( + + + setVisible(false)}> 关闭抽屉 } + visible={visible} + header="演示新手引导" + size="60%" + showOverlay={false} + destroyOnClose={true} + > +
+
+
Guide 用户引导
+
按钮用于开启一个闭环的操作任务,如“删除”对象、“购买”商品等。
+
+
+
Label
+ +
+
+
Label
+ +
+ + + + +
+ + +
+
+ ); +} diff --git a/src/guide/defaultProps.ts b/src/guide/defaultProps.ts new file mode 100644 index 0000000000..fe840e4b1f --- /dev/null +++ b/src/guide/defaultProps.ts @@ -0,0 +1,16 @@ +/** + * 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC + * */ + +import { TdGuideProps } from './type'; + +export const guideDefaultProps: TdGuideProps = { + finishButtonProps: { content: '完成', theme: 'primary' }, + highlightPadding: 8, + mode: 'popup', + nextButtonProps: { content: '下一步', theme: 'primary' }, + prevButtonProps: { content: '上一步', theme: 'default' }, + showOverlay: true, + skipButtonProps: { content: '跳过', theme: 'default' }, + zIndex: 999999, +}; diff --git a/src/guide/index.ts b/src/guide/index.ts new file mode 100644 index 0000000000..341b0a6dae --- /dev/null +++ b/src/guide/index.ts @@ -0,0 +1,9 @@ +import _Guide from './Guide'; + +import './style/index.js'; + +export type { GuideProps } from './Guide'; +export * from './type'; + +export const Guide = _Guide; +export default Guide; diff --git a/src/guide/style/css.js b/src/guide/style/css.js new file mode 100644 index 0000000000..6a9a4b1328 --- /dev/null +++ b/src/guide/style/css.js @@ -0,0 +1 @@ +import './index.css'; diff --git a/src/guide/style/index.js b/src/guide/style/index.js new file mode 100644 index 0000000000..6ff0f4f84c --- /dev/null +++ b/src/guide/style/index.js @@ -0,0 +1 @@ +import '../../_common/style/web/components/guide/_index.less'; diff --git a/src/guide/type.ts b/src/guide/type.ts new file mode 100644 index 0000000000..2569d034da --- /dev/null +++ b/src/guide/type.ts @@ -0,0 +1,197 @@ +/* eslint-disable */ + +/** + * 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC + * */ + +import { ButtonProps } from '../button'; +import { TNode, AttachNode } from '../common'; + +export interface TdGuideProps { + /** + * 用于自定义渲染计数部分 + */ + counter?: TNode; + /** + * null + * @default 当前步骤,即整个引导的进度。-1 则不展示,用于需要中断展示的场景 + */ + current?: number; + /** + * null,非受控属性 + * @default 当前步骤,即整个引导的进度。-1 则不展示,用于需要中断展示的场景 + */ + defaultCurrent?: number; + /** + * null + * @default 当前步骤,即整个引导的进度。-1 则不展示,用于需要中断展示的场景 + */ + modelValue?: number; + /** + * 透传 完成 的全部属性 + * @default { content: '完成', theme: 'primary' } + */ + finishButtonProps?: ButtonProps; + /** + * 是否隐藏计数 + * @default false + */ + hideCounter?: boolean; + /** + * 是否隐藏上一步按钮 + * @default false + */ + hidePrev?: boolean; + /** + * 是否隐藏跳过按钮 + * @default false + */ + hideSkip?: boolean; + /** + * 高亮框的内边距 + * @default 8 + */ + highlightPadding?: number; + /** + * 引导框的类型 + * @default popup + */ + mode?: 'popup' | 'dialog'; + /** + * 透传 下一步按钮 的全部属性 + * @default { content: '下一步', theme: 'primary' } + */ + nextButtonProps?: ButtonProps; + /** + * 透传 上一步按钮 的全部属性 + * @default { content: '上一步', theme: 'primary' } + */ + prevButtonProps?: ButtonProps; + /** + * 是否出现遮罩层 + * @default true + */ + showOverlay?: boolean; + /** + * 透传 跳过按钮 的全部属性 + * @default { content: '跳过', theme: 'default' } + */ + skipButtonProps?: ButtonProps; + /** + * 用于定义每个步骤的内容,包括高亮的节点、相对位置和具体的文案内容等。 + */ + steps?: Array; + /** + * 提示框的层级 + * @default 999999 + */ + zIndex?: number; + /** + * 当前步骤发生变化时触发 + */ + onChange?: (current: number, context?: { e: MouseEvent; total: number }) => void; + /** + * 点击完成按钮时触发 + */ + onFinish?: (context: { e: MouseEvent; current: number; total: number }) => void; + /** + * 点击下一步时触发 + */ + onNextStepClick?: (context: { e: MouseEvent; next: number; current: number; total: number }) => void; + /** + * 点击上一步时触发 + */ + onPrevStepClick?: (context: { e: MouseEvent; prev: number; current: number; total: number }) => void; + /** + * 点击跳过按钮时触发 + */ + onSkip?: (context: { e: MouseEvent; current: number; total: number }) => void; +} + +export interface TdGuideStepProps { + /** + * 当前步骤提示框的内容 + */ + body?: string | TNode; + /** + * 自定义内容,同 content + */ + children?: string | TNode; + /** + * 用户自定义引导弹框的内容,一旦存在,此时除 `placement`、`offset`和`element` 外,其它属性全部失效) + */ + content?: TNode; + /** + * 高亮的节点。数据类型为 String 时,会被当作选择器处理,进行节点查询。示例:'#tdesign' 或 () => document.querySelector('#tdesign') + */ + element: AttachNode; + /** + * 用户自定义的高亮框 (仅当 `mode` 为 `popup` 时生效) + */ + highlightContent?: TNode; + /** + * 高亮框的内边距 + */ + highlightPadding?: number; + /** + * 引导框的类型 + */ + mode?: 'popup' | 'dialog'; + /** + * 用于自定义当前引导框的下一步按钮的内容 + */ + nextButtonProps?: ButtonProps; + /** + * 相对于 placement 的偏移量,示例:[-10, 20] 或 ['10px', '8px'] + */ + offset?: Array; + /** + * 引导框相对于高亮元素出现的位置 + * @default 'top' + */ + placement?: StepPopupPlacement | StepDialogPlacement; + /** + * 用于自定义当前引导框的上一步按钮的内容 + */ + prevButtonProps?: ButtonProps; + /** + * 是否出现遮罩层 + * @default true + */ + showOverlay?: boolean; + /** + * 用于自定义当前步骤引导框的跳过按钮的内容 + */ + skipButtonProps?: ButtonProps; + /** + * 覆盖引导框的类名 + * @default '' + */ + stepOverlayClass?: string; + /** + * 当前步骤的标题内容 + * @default '' + */ + title?: string; +} + +export type StepPopupPlacement = + | 'top' + | 'left' + | 'right' + | 'bottom' + | 'top-left' + | 'top-right' + | 'bottom-left' + | 'bottom-right' + | 'left-top' + | 'left-bottom' + | 'right-top' + | 'right-bottom'; + +export type StepDialogPlacement = 'top' | 'center'; + +export type GuideCrossProps = Pick< + TdGuideStepProps, + 'mode' | 'skipButtonProps' | 'prevButtonProps' | 'nextButtonProps' | 'showOverlay' | 'highlightPadding' +>; diff --git a/src/guide/utils/getRelativePosition.ts b/src/guide/utils/getRelativePosition.ts new file mode 100644 index 0000000000..89603219f6 --- /dev/null +++ b/src/guide/utils/getRelativePosition.ts @@ -0,0 +1,34 @@ +import { getElmCssPropValue, isFixed, getWindowScroll } from '../../_util/dom'; + +/** + * 获取元素相对于另一个元素的位置(或者说相对于 body) + * 感谢 `meouw`: http://stackoverflow.com/a/442474/375966 + */ +export default function getRelativePosition(elm: HTMLElement, relativeElm: HTMLElement = document.body) { + const { scrollTop, scrollLeft } = getWindowScroll(); + const { top: elmTop, left: elmLeft } = elm.getBoundingClientRect(); + const { top: relElmTop, left: relElmLeft } = relativeElm.getBoundingClientRect(); + const relativeElmPosition = getElmCssPropValue(relativeElm, 'position'); + + if ( + (relativeElm.tagName.toLowerCase() !== 'body' && relativeElmPosition === 'relative') || + relativeElmPosition === 'sticky' + ) { + return { + top: elmTop - relElmTop, + left: elmLeft - relElmLeft, + }; + } + + if (isFixed(elm)) { + return { + top: elmTop, + left: elmLeft, + }; + } + + return { + top: elmTop + scrollTop, + left: elmLeft + scrollLeft, + }; +} diff --git a/src/guide/utils/getScrollParent.ts b/src/guide/utils/getScrollParent.ts new file mode 100644 index 0000000000..0fb577f4af --- /dev/null +++ b/src/guide/utils/getScrollParent.ts @@ -0,0 +1,28 @@ +import { elementInViewport } from '../../_util/dom'; + +export function getScrollParent(element: HTMLElement) { + let style = getComputedStyle(element); + const excludeStaticParent = style.position === 'absolute'; + const overflowRegex = /(auto|scroll)/; + + if (style.position === 'fixed') return document.body; + + for (let parent = element; parent.parentElement; ) { + parent = parent.parentElement; + style = getComputedStyle(parent); + if (excludeStaticParent && style.position === 'static') { + continue; + } + if (overflowRegex.test(style.overflow + style.overflowY + style.overflowX)) return parent; + } + + return document.body; +} + +export function scrollToParentVisibleArea(element: HTMLElement) { + const parent = getScrollParent(element); + if (parent === document.body) return; + // !todo 逻辑待验证 + if (elementInViewport(element, parent)) return; + parent.scrollTop = element.offsetTop - parent.offsetTop; +} diff --git a/src/guide/utils/getTargetElm.ts b/src/guide/utils/getTargetElm.ts new file mode 100644 index 0000000000..cd7379892a --- /dev/null +++ b/src/guide/utils/getTargetElm.ts @@ -0,0 +1,20 @@ +import { AttachNode } from '../../common'; + +export default function getTargetElm(elm: AttachNode): HTMLElement { + if (elm) { + let targetElement: HTMLElement = null; + if (typeof elm === 'string') { + targetElement = document.querySelector(elm); + } else if (typeof elm === 'function') { + targetElement = elm() as HTMLElement; + } else { + throw new Error('elm should be string or function'); + } + if (targetElement) { + return targetElement as HTMLElement; + } + throw new Error('There is no element with given.'); + } else { + return document.body; + } +} diff --git a/src/guide/utils/index.ts b/src/guide/utils/index.ts new file mode 100644 index 0000000000..ef6b33e57d --- /dev/null +++ b/src/guide/utils/index.ts @@ -0,0 +1,7 @@ +import { scrollToParentVisibleArea } from './getScrollParent'; +import getRelativePosition from './getRelativePosition'; +import getTargetElm from './getTargetElm'; +import scrollToElm from './scrollToElm'; +import useWatch from './useWatch'; + +export { scrollToParentVisibleArea, getRelativePosition, getTargetElm, scrollToElm, useWatch }; diff --git a/src/guide/utils/scrollToElm.ts b/src/guide/utils/scrollToElm.ts new file mode 100644 index 0000000000..170389ed3a --- /dev/null +++ b/src/guide/utils/scrollToElm.ts @@ -0,0 +1,18 @@ +import { getWindowSize, elementInViewport, scrollTo } from '../../_util/dom'; + +export default function scrollToElm(elm: HTMLElement) { + const rect = elm.getBoundingClientRect(); + + if (!elementInViewport(elm)) { + const winHeight = getWindowSize().height; + // const top = rect.bottom - (rect.bottom - rect.top); + scrollTo(rect.top - (winHeight / 2 - rect.height / 2), {}); + + // todo 先暂时保留这里的逻辑 + // if (top < 0 || element.clientHeight > winHeight) { + // window.scrollBy(0, rect.top - (winHeight / 2 - rect.height / 2)); + // } else { + // window.scrollBy(0, rect.top - (winHeight / 2 - rect.height / 2)); + // } + } +} diff --git a/src/guide/utils/useWatch.ts b/src/guide/utils/useWatch.ts new file mode 100644 index 0000000000..a9fb0d7441 --- /dev/null +++ b/src/guide/utils/useWatch.ts @@ -0,0 +1,20 @@ +import { useEffect, useRef } from 'react'; + +export default function useWatch(value, fn, config = { immediate: false }) { + const oldValue = useRef(); + const isFirst = useRef(false); + useEffect(() => { + if (isFirst.current) { + fn(value, oldValue.current); + } else { + isFirst.current = true; + + // 是否要立即执行 fn 回调函数 + if (config.immediate) { + fn(value, oldValue.current); + } + } + + oldValue.current = value; + }, [value]); +} diff --git a/src/index.ts b/src/index.ts index baa3eea5ac..92901e41fd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -61,3 +61,4 @@ export * from './timeline'; export * from './image'; export * from './rate'; export * from './link'; +export * from './guide'; From 7bb007999fbd468ae94b3848741c2a29d94876de Mon Sep 17 00:00:00 2001 From: YilunSun Date: Wed, 12 Oct 2022 19:10:17 +0800 Subject: [PATCH 02/14] fix(guide): handle pr --- src/guide/Guide.tsx | 3 +- .../__snapshots__/guide.test.tsx.snap | 874 ++++++++++++++++++ src/guide/_example/base.jsx | 12 +- src/guide/_example/custom-popup.jsx | 12 +- src/guide/_example/dialog.jsx | 12 +- src/guide/_example/no-mask.jsx | 8 +- src/guide/_example/popup-dialog.jsx | 14 +- src/guide/utils/useWatch.ts | 2 +- test/ssr/__snapshots__/ssr.test.js.snap | 4 + 9 files changed, 910 insertions(+), 31 deletions(-) create mode 100644 src/guide/__tests__/__snapshots__/guide.test.tsx.snap diff --git a/src/guide/Guide.tsx b/src/guide/Guide.tsx index c73df9efa5..52bf797e74 100644 --- a/src/guide/Guide.tsx +++ b/src/guide/Guide.tsx @@ -185,7 +185,7 @@ const Guide = (props: GuideProps) => { const useMount = (callback) => { useEffect(() => { callback(); - }, []); + }, [callback]); }; useMount(() => { @@ -353,6 +353,7 @@ const Guide = (props: GuideProps) => { showArrow={!content} zIndex={zIndex} overlayClassName={currentStepInfo.stepOverlayClass} + overlayInnerClassName={{ [`${prefixCls}__popup--content`]: !!content }} placement={currentStepInfo.placement as StepPopupPlacement} >
diff --git a/src/guide/__tests__/__snapshots__/guide.test.tsx.snap b/src/guide/__tests__/__snapshots__/guide.test.tsx.snap new file mode 100644 index 0000000000..84822deed7 --- /dev/null +++ b/src/guide/__tests__/__snapshots__/guide.test.tsx.snap @@ -0,0 +1,874 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`base.jsx 1`] = ` + +
+ +
+
+
+
+ + + +
+
+ 演示新手引导 +
+
+
+
+
+ Guide 用户引导 +
+
+ 按钮用于开启一个闭环的操作任务,如“删除”对象、“购买”商品等。 +
+
+
+
+ Label +
+
+
+ +
+
+
+
+
+ Label +
+
+
+ +
+
+
+
+ + +
+
+
+ +
+
+
+
+
+`; + +exports[`custom-popup.jsx 1`] = ` + +
+ +
+
+
+
+ + + +
+
+ 演示新手引导 +
+
+
+
+
+ Guide 用户引导 +
+
+ 按钮用于开启一个闭环的操作任务,如“删除”对象、“购买”商品等。 +
+
+
+
+ Label +
+
+
+ +
+
+
+
+
+ Label +
+
+
+ +
+
+
+
+ + +
+
+
+ +
+
+
+
+
+`; + +exports[`dialog.jsx 1`] = ` + +
+ +
+
+
+
+ + + +
+
+ 演示新手引导 +
+
+
+
+
+ Guide 用户引导 +
+
+ 按钮用于开启一个闭环的操作任务,如“删除”对象、“购买”商品等。 +
+
+
+
+ Label +
+
+
+ +
+
+
+
+
+ Label +
+
+
+ +
+
+
+
+ + +
+
+
+ +
+
+
+
+
+`; + +exports[`dialog-body.jsx 1`] = ` + +
+ demo +

+ 此处显示本页引导的说明文案,可按需要撰写,如内容过多可折行显示。图文也可按需自由设计。 +

+
+
+`; + +exports[`my-popup.jsx 1`] = ` + +
+ + + + + +
+
+`; + +exports[`no-mask.jsx 1`] = ` + +
+ +
+
+
+
+ + + +
+
+ 演示新手引导 +
+
+
+
+
+ Guide 用户引导 +
+
+ 按钮用于开启一个闭环的操作任务,如“删除”对象、“购买”商品等。 +
+
+
+
+ Label +
+
+
+ +
+
+
+
+
+ Label +
+
+
+ +
+
+
+
+ + +
+
+
+ +
+
+
+
+
+`; + +exports[`popup-dialog.jsx 1`] = ` + +
+ +
+
+
+
+ + + +
+
+ 演示新手引导 +
+
+
+
+
+ Guide 用户引导 +
+
+ 按钮用于开启一个闭环的操作任务,如“删除”对象、“购买”商品等。 +
+
+
+
+ Label +
+
+
+ +
+
+
+
+
+ Label +
+
+
+ +
+
+
+
+ + +
+
+
+ +
+
+
+
+
+`; diff --git a/src/guide/_example/base.jsx b/src/guide/_example/base.jsx index cc775b6d5b..41926c8606 100644 --- a/src/guide/_example/base.jsx +++ b/src/guide/_example/base.jsx @@ -5,19 +5,19 @@ import './base.css'; export default function BasicGuide() { const steps = [ { - element: '.main-title', + element: '.main-title-base', title: '新手引导标题', body: '新手引导的说明文案', placement: 'bottom-right', }, { - element: '.label-field', + element: '.label-field-base', title: '新手引导标题', body: '新手引导的说明文案', placement: 'bottom', }, { - element: '.action', + element: '.action-base', title: '新手引导标题', body: '新手引导的说明文案', placement: 'right', @@ -70,11 +70,11 @@ export default function BasicGuide() { destroyOnClose={true} >
-
+
Guide 用户引导
按钮用于开启一个闭环的操作任务,如“删除”对象、“购买”商品等。
-
+
Label
@@ -82,7 +82,7 @@ export default function BasicGuide() {
Label
- +
-
+
Label
@@ -82,7 +82,7 @@ export default function NoMaskGuide() {
Label
- +
"`; + exports[`ssr snapshot test renders ./src/icon/_example/Enhanced.jsx correctly 1`] = `"


"`; exports[`ssr snapshot test renders ./src/icon/_example/IconExample.jsx correctly 1`] = `"
LoadingIcon
CloseIcon
CheckCircleFilledIcon
"`; From 0292a6d74a58723891cab7a00056bc3ce8415c2d Mon Sep 17 00:00:00 2001 From: YilunSun Date: Wed, 12 Oct 2022 19:28:31 +0800 Subject: [PATCH 03/14] fix: handle pr --- src/guide/__tests__/__snapshots__/guide.test.tsx.snap | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/guide/__tests__/__snapshots__/guide.test.tsx.snap b/src/guide/__tests__/__snapshots__/guide.test.tsx.snap index 84822deed7..1d007133ae 100644 --- a/src/guide/__tests__/__snapshots__/guide.test.tsx.snap +++ b/src/guide/__tests__/__snapshots__/guide.test.tsx.snap @@ -114,7 +114,7 @@ exports[`base.jsx 1`] = `
+
+ +
csr test src/grid/_example/valign.jsx 1`] = ` } `; +exports[`csr snapshot test > csr test src/guide/_example/base.jsx 1`] = ` +{ + "asFragment": [Function], + "baseElement": +