Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add autoScroll option #39

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# Introduction

English | [简体中文](./README.zh-CN.md)

When we onboard new users or ship new features, how do we make sure that our audience knows what's in it and get them excited to use our apps? That's where an onboarding sequence comes into play. This React library `guide` offers an effective way to construct a smooth onboarding experience.
![mask](./public/light_mode.png)
![no mask](./public/dark_mode.png)

## Installation

```javascript
Expand Down Expand Up @@ -52,27 +54,28 @@ import Guide from 'byte-guide';
### Component API's

| props | definition | type | required | defalut value |
| :-------------------- | :------------------------------------------------------------------------------------------------------------------- | :--------------------------------------- | :------- | :------------------------------------------------------------ |
| :-------------------- | :------------------------------------------------------------------------------------------------------------------- | :--------------------------------------- | :------- | :------------------------------------------------------------ | --- |
| steps | An array of info of each step of the onboarding sequence | IStep[] | ✓ | -- |
| localKey | A unique key that will be stored in localStorage to indicate if the guide has finished | string | ✓ | -- |
| expireDate | The expire date of the guide when it will not be displayed anymore | string,YYYY-mm-hh | | -- |
| closable | If the guide can be closed before the last step. If false, the close button `x` will not be displayed on each modal. | bool | | true |
| closeEle | Customize the element that skips the guide | string, reactNode | |
| closeEle | Customize the element that skips the guide | string, reactNode | |
| modalClassName | The class name of the modal | string | | -- |
| maskClassName | The class name of the mask | string | | -- |
| mask | Whether or not to display the mask | bool | | false |
| arrow | Whether or not to display the arrow | bool | | true |
| hotspot | Whether or not to display the hotspot | bool | | false |
| stepText | The custom text for the step info | (stepIndex, stepCount): string => {} | | (stepIndex, stepCount) => `Step ${stepIndex} of ${stepCount}` |
| nextText | The custom text for the `Next Step` button | string | | Next |
| prevText | The custom text for the `Previous step` button | string | | Previous |
| showPreviousBtn | Whether or not to display the previous button | bool | | true |
| prevText | The custom text for the `Previous step` button | string | | Previous |
| showPreviousBtn | Whether or not to display the previous button | bool | | true |
| okText | The custom text for the confirm button at the last step | string | | I know |
| visible | If the guide is visible | bool | | true |
| lang | The language of use | 'zh' | 'en' | 'ja' | | 'zh' |
| step | The first step's number | number | | 0 |
| autoScroll | auto scroll to the guide on load | bool | | true |
| afterStepChange | The callback function when the step changes | (nextIndex, nextStep): void=>{} | | -- |
| beforeStepChange | The callback function when the user is about to move to the next step | (stepIndex: number, step: IStep) => void | | -- |
| beforeStepChange | The callback function when the user is about to move to the next step | (stepIndex: number, step: IStep) => void | | -- | |
| onClose | The callback function when the guide closes \*/ |
| onClose?: () => void; | ():void=> {} | | -- |

Expand Down
9 changes: 6 additions & 3 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# 使用指南

[English](./README.md) | 简体中文

## 简介
基于react实现的新手引导组件,用于新功能引导,支持带蒙层/不带蒙层两种模式。

基于 react 实现的新手引导组件,用于新功能引导,支持带蒙层/不带蒙层两种模式。
![带蒙层](./public/light_mode.png)
![不带蒙层](./public/dark_mode.png)

Expand Down Expand Up @@ -44,7 +46,7 @@ import Guide from 'byte-guide'
| localKey | 本地缓存 key,缓存是否展示过该引导页,需确保系统内 localKey 唯一性 | string | ✓ | -- |
| expireDate | 过期时间,大于等于该时间都不展示引导页 | string,YYYY-mm-hh | | -- |
| closable | 是否可以跳过引导 | bool | | true |
| closeEle | 自定义跳过引导的元素 | string, reactNode | |
| closeEle | 自定义跳过引导的元素 | string, reactNode | |
| modalClassName | 弹窗类名 | string | | -- |
| maskClassName | 蒙层类名 | string | | -- |
| mask | 是否展示蒙层 | bool | | false |
Expand All @@ -53,11 +55,12 @@ import Guide from 'byte-guide'
| stepText | modal 的步骤信息文案 | (stepIndex, stepCount): string => {} | | (stepIndex, stepCount) => { return `第${stepIndex}步,共${stepCount}步`; } |
| nextText | modal 的'下一步'按钮文案 | string | | 下一步 |
| prevText | modal 的'上一步'按钮文案 | string | | 下一步 |
| showPreviousBtn | 是否显示'上一步'按钮 | bool | | true |
| showPreviousBtn | 是否显示'上一步'按钮 | bool | | true |
| okText | modal 的确认按钮文案 | string | | 我知道了 |
| visible | 控制 guide 显示隐藏,用于异步渲染 | bool | | true |
| lang | 多语言 | 'zh' ,'en' , 'ja' | | 'zh' |
| step | 初始步骤,步骤可受控,为-1 则不展示组件 | number | | 0 |
| autoScroll | 自动跳转到 Guide | bool | | true |
| afterStepChange | 点击下一步的回调 | (nextIndex, nextStep): void=>{} | | -- |
| beforeStepChange | 点击下一步之前的回调 | (stepIndex: number, step: IStep) => void | | -- |
| onClose | 引导结束的回调 | ():void=> {} | | -- |
Expand Down
28 changes: 17 additions & 11 deletions src/Guide/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import i18n from '../constant/lang';
import Mask from '../Mask';
import Modal from '../Modal';
import { IGuide } from '../typings/guide';
import { CUSTOM_ELEMENT_CLASS } from '../constant/className'
import { CUSTOM_ELEMENT_CLASS } from '../constant/className';

const Guide: React.FC<IGuide> = (props) => {
const {
Expand All @@ -34,6 +34,7 @@ const Guide: React.FC<IGuide> = (props) => {
nextText,
okText,
lang = 'zh',
autoScroll = true,
showPreviousBtn = false,
showSkipBtn = false,
closeEle,
Expand All @@ -55,12 +56,12 @@ const Guide: React.FC<IGuide> = (props) => {

const anchorEl = useMemo(() => {
if (stepIndex >= 0 && stepIndex < steps.length) {
const { targetPos, selector } = steps[stepIndex]
const { targetPos, selector } = steps[stepIndex];

if (selector) return getAnchorEl(selector)
if (selector) return getAnchorEl(selector);

if (targetPos) {
return getCusAnchorEl(targetPos)
return getCusAnchorEl(targetPos);
}
}
return null;
Expand All @@ -79,18 +80,22 @@ const Guide: React.FC<IGuide> = (props) => {
/* To cater the cases of using iframe where the anchorEl
* is not in the same window scope as the code running
*/
const realWindow = useMemo(() => (anchorEl ? getWindow(anchorEl) : null), [
anchorEl,
]);
const realWindow = useMemo(
() => (anchorEl ? getWindow(anchorEl) : null),
[anchorEl],
);

const realDocument = useMemo(
() => (anchorEl ? getDocumentElement(anchorEl as Element) : null),
[anchorEl],
);

const handleChange = (direction: number): void => {
const nextStepIndex = Math.min(Math.max(stepIndex + direction, 0), steps.length)
if (nextStepIndex === stepIndex) return
const nextStepIndex = Math.min(
Math.max(stepIndex + direction, 0),
steps.length,
);
if (nextStepIndex === stepIndex) return;
if (nextStepIndex === steps.length) handleClose();
else if (stepIndex >= 0) beforeStepChange?.(stepIndex, steps[stepIndex]);
setStepIndex(nextStepIndex);
Expand All @@ -104,9 +109,9 @@ const Guide: React.FC<IGuide> = (props) => {
(realDocument as HTMLElement).style.overflow = initOverflowVal;
}

const cusAnchor = document.querySelector(CUSTOM_ELEMENT_CLASS)
const cusAnchor = document.querySelector(CUSTOM_ELEMENT_CLASS);
if (cusAnchor) {
document.body.removeChild(cusAnchor)
document.body.removeChild(cusAnchor);
}

setStepIndex(-1);
Expand Down Expand Up @@ -209,6 +214,7 @@ const Guide: React.FC<IGuide> = (props) => {
skipText={skipText}
className={modalClassName}
TEXT={i18nTEXT}
autoScroll={autoScroll}
showPreviousBtn={showPreviousBtn}
showSkipBtn={showSkipBtn}
/>
Expand Down
32 changes: 18 additions & 14 deletions src/Modal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ const Modal: React.FC<IModal> = ({
showPreviousBtn,
showSkipBtn,
skipText,
closeEle
closeEle,
autoScroll,
}) => {
const stepInfo = steps[stepIndex];

Expand All @@ -57,15 +58,16 @@ const Modal: React.FC<IModal> = ({
const [arrowStyle, setArrowStyle] = useState({});
const [hotspotStyle, setHotspotStyle] = useState({});

const scrollContainer = useMemo(() => getScrollContainer(anchorEl), [
anchorEl,
]);
const scrollContainer = useMemo(
() => getScrollContainer(anchorEl),
[anchorEl],
);

const _okText =
stepIndex !== steps.length - 1
? nextText || TEXT('NEXT_STEP')
: okText || TEXT('I_KNOW');
const _prevText = prevText || TEXT('PREV_STEP')
const _prevText = prevText || TEXT('PREV_STEP');
const _skipText = skipText || TEXT('SKIP_STEP');
const _stepText =
stepText || (TEXT('STEP_NUMBER') as (idx: number, len: number) => string);
Expand Down Expand Up @@ -120,11 +122,11 @@ const Modal: React.FC<IModal> = ({
if (
// Modal is below the viewport
anchorPos.top -
scrollContainerTop +
anchorPos.height +
modalPos.height +
MARGIN >=
visibleHeight ||
scrollContainerTop +
anchorPos.height +
modalPos.height +
MARGIN >=
visibleHeight ||
// Modal is above the viewport
anchorPos.top <= modalPos.height + MARGIN
) {
Expand Down Expand Up @@ -207,8 +209,9 @@ const Modal: React.FC<IModal> = ({
onChange(1);
} else if (visible) {
focusedIdxRef.current = 0;

handleScroll();
if (autoScroll) {
handleScroll();
}
handleKeydown({ keyCode: 9 });
calculateStyle();

Expand Down Expand Up @@ -237,7 +240,9 @@ const Modal: React.FC<IModal> = ({
)}
{/* CLOSE BUTTON */}
{closeEle ? (
<div className={`${PREFIX}-close-icon`} onClick={onClose}>{closeEle}</div>
<div className={`${PREFIX}-close-icon`} onClick={onClose}>
{closeEle}
</div>
) : closable ? (
<CloseSmall className={`${PREFIX}-close-icon`} onClick={onClose} />
) : null}
Expand All @@ -255,7 +260,6 @@ const Modal: React.FC<IModal> = ({
{_stepText(stepIndex + 1, steps.length)}
</span>
<div className={`${PREFIX}-footer-btn-group`}>

{showSkipBtn && stepIndex != steps.length - 1 && (
<button
className={`${PREFIX}-footer-btn ${PREFIX}-footer-skip-btn`}
Expand Down
Loading