Skip to content

Commit

Permalink
Merge pull request #68 from jys125773/feature/pull-down-refresh
Browse files Browse the repository at this point in the history
feat(pull-down-refresh): support new component
  • Loading branch information
LeeJim authored Mar 31, 2022
2 parents 48cc504 + 074ab7e commit 15d7d2c
Show file tree
Hide file tree
Showing 16 changed files with 370 additions and 7 deletions.
5 changes: 5 additions & 0 deletions site/mobile/mobile.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,5 +132,10 @@ export default {
name: 'Collapse',
component: () => import('tdesign-mobile-react/collapse/_example/index.jsx'),
},
{
title: 'PullDownRefresh 下拉刷新',
name: 'pull-down-refresh',
component: () => import('tdesign-mobile-react/pull-down-refresh/_example/index.jsx'),
},
],
};
12 changes: 6 additions & 6 deletions site/web/site.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -336,12 +336,12 @@ export default {
// path: '/mobile-react/components/progress',
// component: () => import('tdesign-mobile-react/progress/progress.md'),
// },
// {
// title: 'PullDownRefresh 下拉刷新',
// name: 'pull-down-refresh',
// path: '/mobile-react/components/pull-down-refresh',
// component: () => import('tdesign-mobile-react/pull-down-refresh/pull-down-refresh.md'),
// },
{
title: 'PullDownRefresh 下拉刷新',
name: 'pull-down-refresh',
path: '/mobile-react/components/pull-down-refresh',
component: () => import('tdesign-mobile-react/pull-down-refresh/pull-down-refresh.md'),
},
],
},
],
Expand Down
5 changes: 5 additions & 0 deletions src/_util/delay.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default function delay(time: number) {
return new Promise((resolve) => {
setTimeout(resolve, time);
});
}
20 changes: 20 additions & 0 deletions src/_util/getScrollParent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
type ScrollElement = HTMLElement | Window;

const overflowScrollReg = /scroll|auto|overlay/i;

export default function getScrollParent(
el: Element | null | undefined,
root: ScrollElement | null | undefined = window,
): Window | Element | null | undefined {
let node = el;

while (node && node !== root && node.nodeType === 1) {
const { overflowY } = window.getComputedStyle(node);
if (overflowScrollReg.test(overflowY)) {
return node;
}
node = node.parentNode as Element;
}

return root;
}
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@ export * from './swipe-cell';
export * from './tag';
export * from './toast';
export * from './collapse';

export * from './pull-down-refresh';
164 changes: 164 additions & 0 deletions src/pull-down-refresh/PullDownRefresh.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import React, { useRef, useState, ReactNode } from 'react';
import identity from 'lodash/identity';
import uniqueId from 'lodash/uniqueId';
import { useDrag } from '@use-gesture/react';
import { useSpring, animated } from '@react-spring/web';
import { Loading } from 'tdesign-mobile-react';
import useConfig from '../_util/useConfig';
import withNativeProps, { NativeProps } from '../_util/withNativeProps';
import getScrollParent from '../_util/getScrollParent';
import delay from '../_util/delay';
import { TdPullDownRefreshProps } from './type';

export enum PullStatusEnum {
normal,
loading,
loosing,
pulling,
success,
}

function getStatusText(status: PullStatusEnum, loadingTexts: string[]) {
switch (status) {
case PullStatusEnum.pulling:
return loadingTexts[0];
case PullStatusEnum.loosing:
return loadingTexts[1];
case PullStatusEnum.loading:
return loadingTexts[2];
case PullStatusEnum.success:
return loadingTexts[3];
default:
return '';
}
}

export interface PullDownRefreshProps extends TdPullDownRefreshProps, NativeProps {
disabled?: boolean;
threshold?: number;
onRefresh?: () => Promise<unknown>;
}

const defaultProps = {
loadingBarHeight: 50,
loadingTexts: ['下拉刷新', '松手刷新', '正在刷新', '刷新完成'],
maxBarHeight: 80,
threshold: 50,
refreshTimeout: 3000,
disabled: false,
onRefresh: () => delay(2000),
onTimeout: identity,
};

const PullDownRefresh: React.FC<PullDownRefreshProps> = (props) => {
const {
children,
disabled,
loadingTexts,
loadingProps,
loadingBarHeight,
maxBarHeight,
threshold,
refreshTimeout,
onRefresh,
onTimeout,
} = props;
const [status, originalSetStatus] = useState(PullStatusEnum.normal);
const rootRef = useRef<HTMLDivElement>(null);
const scrollParentRef = useRef<Element | Window>(null);
const { classPrefix } = useConfig();
const name = `${classPrefix}-pull-down-refresh`;
const setStatus = (nextStatus: PullStatusEnum) => {
if (nextStatus !== status) originalSetStatus(nextStatus);
};

const [{ y }, api] = useSpring(
() => ({
y: 0,
config: { tension: 300, friction: 30, clamp: true },
}),
[],
);

const doRefresh = async () => {
setStatus(PullStatusEnum.loading);
api.start({ y: loadingBarHeight });
try {
const timeoutId = uniqueId(`${name}-timeout_`);
let timeoutTid: any;
const res = await Promise.race([
onRefresh(),
new Promise((resolve) => {
timeoutTid = setTimeout(() => {
resolve(timeoutId);
onTimeout();
}, refreshTimeout);
}),
]);
clearTimeout(timeoutTid);
if (res !== timeoutId) {
setStatus(PullStatusEnum.success);
}
} finally {
api.start({
to: async (next) => {
await next({ y: 0 });
setStatus(PullStatusEnum.normal);
},
});
}
};

useDrag(
(state) => {
const [, offsetY] = state.offset;
if (state.first) {
scrollParentRef.current = getScrollParent(rootRef.current);
setStatus(PullStatusEnum.pulling);
}
if (!scrollParentRef.current) return;
if (state.last) {
if (status === PullStatusEnum.loosing) {
doRefresh();
} else {
setStatus(PullStatusEnum.normal);
api.start({ y: 0 });
}
} else {
setStatus(offsetY >= threshold ? PullStatusEnum.loosing : PullStatusEnum.pulling);
api.start({ y: offsetY, immediate: true });
}
},
{
target: rootRef,
from: [0, y.get()],
bounds: { top: 0, bottom: maxBarHeight },
pointer: { touch: true },
axis: 'y',
enabled: !disabled && status !== PullStatusEnum.loading,
},
);

const statusText = getStatusText(status, loadingTexts);
let statusNode: ReactNode = statusText;
if (status === PullStatusEnum.loading) {
statusNode = <Loading {...loadingProps} text={statusText} className={`${name}__loading-icon`} />;
}

return withNativeProps(
props,
<div className={name} ref={rootRef}>
<animated.div className={`${name}__track`} style={{ y }}>
<div className={`${name}__tips`} style={{ height: loadingBarHeight }}>
{statusNode}
</div>
{children}
</animated.div>
</div>,
);
};

PullDownRefresh.defaultProps = defaultProps;
PullDownRefresh.displayName = 'PullDownRefresh';

export default PullDownRefresh;
22 changes: 22 additions & 0 deletions src/pull-down-refresh/_example/base.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React, { useState } from 'react';
import { PullDownRefresh } from 'tdesign-mobile-react';

export default function Demo() {
const [count, setCount] = useState(0);
return (
<div>
<PullDownRefresh
onRefresh={() =>
new Promise((resolve) => {
setCount(count + 1);
setTimeout(() => {
resolve();
}, 1000);
})
}
>
<div className="pull-down-refresh-content">已下拉{count}</div>
</PullDownRefresh>
</div>
);
}
22 changes: 22 additions & 0 deletions src/pull-down-refresh/_example/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';
import { Tabs, TabPanel } from 'tdesign-mobile-react/tabs';
import BaseDemo from './base';
import TimieoutDemo from './timeout';
import LoadingTextsDemo from './loading-texts';
import './style/index.less';

export default function Demo() {
return (
<Tabs>
<TabPanel value={'1'} label="基础用法">
<BaseDemo />
</TabPanel>
<TabPanel value={'2'} label="自定义文案">
<LoadingTextsDemo />
</TabPanel>
<TabPanel value={'3'} label="超时事件">
<TimieoutDemo />
</TabPanel>
</Tabs>
);
}
25 changes: 25 additions & 0 deletions src/pull-down-refresh/_example/loading-texts.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React, { useState } from 'react';
import { PullDownRefresh } from 'tdesign-mobile-react';

export default function Demo() {
const [count, setCount] = useState(0);
return (
<div>
<PullDownRefresh
loadingBarHeight={70}
maxBarHeight={100}
loadingTexts={['下拉即可刷新...', '释放即可刷新...', '加载中...', '刷新成功']}
onRefresh={() =>
new Promise((resolve) => {
setCount(count + 1);
setTimeout(() => {
resolve();
}, 1000);
})
}
>
<div className="pull-down-refresh-content">已下拉{count}</div>
</PullDownRefresh>
</div>
);
}
5 changes: 5 additions & 0 deletions src/pull-down-refresh/_example/style/index.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.pull-down-refresh-content {
height: calc(100vh - 55px);
background: #fff;
padding: 20px 16px;
}
26 changes: 26 additions & 0 deletions src/pull-down-refresh/_example/timeout.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React, { useState } from 'react';
import { PullDownRefresh, Toast } from 'tdesign-mobile-react';

export default function Demo() {
const [count, setCount] = useState(0);
return (
<div>
<PullDownRefresh
refreshTimeout={1000}
onTimeout={() => {
Toast({ message: '已超时' });
}}
onRefresh={() =>
new Promise((resolve) => {
setCount(count + 1);
setTimeout(() => {
resolve();
}, 2000);
})
}
>
<div className="pull-down-refresh-content">已下拉{count}</div>
</PullDownRefresh>
</div>
);
}
8 changes: 8 additions & 0 deletions src/pull-down-refresh/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import _PullDownRefresh from './PullDownRefresh';
import './style';

export type { PullDownRefreshProps } from './PullDownRefresh';
export * from './type';

export const PullDownRefresh = _PullDownRefresh;
export default PullDownRefresh;
17 changes: 17 additions & 0 deletions src/pull-down-refresh/pull-down-refresh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
:: BASE_DOC ::

## API

### PullDownRefresh Props

名称 | 类型 | 默认值 | 说明 | 必传
-- | -- | -- | -- | --
className | String | - | 类名 | N
style | Object | - | 样式,TS 类型:`React.CSSProperties` | N
loadingBarHeight | Number | 50 | 加载中下拉高度 | N
loadingProps | Object | - | 加载loading样式。TS 类型:`TdLoadingProps`[Loading API Documents](./loading?tab=api)[详细类型定义](https://github.com/TDesignOteam/tdesign-mobile-react/tree/develop/src/pull-down-refresh/type.ts) | N
loadingTexts | Array | [] | 提示语,组件内部默认值为 ['下拉刷新', '松手刷新', '正在刷新', '刷新完成']。TS 类型:`string[]` | N
maxBarHeight | Number | 80 | 最大下拉高度 | N
refreshTimeout | Number | 3000 | 刷新超时时间 | N
onRefresh | Function | | TS 类型:`() => void`<br/>结束下拉时触发 | N
onTimeout | Function | | TS 类型:`() => void`<br/>刷新超时触发 | N
1 change: 1 addition & 0 deletions src/pull-down-refresh/style/css.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import './index.css';
1 change: 1 addition & 0 deletions src/pull-down-refresh/style/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import '@common/style/mobile/components/pull-down-refresh/_index.less';
Loading

0 comments on commit 15d7d2c

Please sign in to comment.