diff --git a/site/mobile/main.jsx b/site/mobile/main.jsx index 953e9aba..73a19955 100644 --- a/site/mobile/main.jsx +++ b/site/mobile/main.jsx @@ -4,7 +4,7 @@ import App from './App'; import '../style/mobile/index.less'; import '../../src/_common/style/mobile/_reset.less'; -import '../../src/_common/style/mobile/index.less'; +// import '../../src/_common/style/mobile/index.less'; ReactDOM.render( diff --git a/site/mobile/mobile.config.js b/site/mobile/mobile.config.js index b50fbac0..d4a2e331 100644 --- a/site/mobile/mobile.config.js +++ b/site/mobile/mobile.config.js @@ -130,7 +130,7 @@ export default { { title: 'Loading 加载中', name: 'loading', - component: () => import('tdesign-mobile-react/loading/_example/index.jsx'), + component: () => import('tdesign-mobile-react/loading/_example/index.tsx'), }, { title: 'Swiper 轮播', diff --git a/src/_common b/src/_common index 421fe8ee..dffea051 160000 --- a/src/_common +++ b/src/_common @@ -1 +1 @@ -Subproject commit 421fe8eefeb19792035704467e1af0840fff27cd +Subproject commit dffea05179951cf8ddd8efa33b492e446bfa6c33 diff --git a/src/_util/dom.ts b/src/_util/dom.ts new file mode 100644 index 00000000..a1c192c3 --- /dev/null +++ b/src/_util/dom.ts @@ -0,0 +1,2 @@ +// 用于判断是否可使用 dom +export const canUseDocument = !!(typeof window !== 'undefined' && window.document && window.document.createElement); diff --git a/src/common/Portal.tsx b/src/common/Portal.tsx new file mode 100644 index 00000000..d765393b --- /dev/null +++ b/src/common/Portal.tsx @@ -0,0 +1,68 @@ +import React, { forwardRef, useEffect, useMemo, useImperativeHandle } from 'react'; +import { createPortal } from 'react-dom'; +import { AttachNode, AttachNodeReturnValue } from '../common'; +import { canUseDocument } from '../_util/dom'; +import useConfig from '../hooks/useConfig'; +import useDefaultProps from '../hooks/useDefaultProps'; + +export interface PortalProps { + /** + * 指定挂载的 HTML 节点, false 为挂载在 body + */ + attach?: React.ReactElement | AttachNode | boolean; + /** + * 触发元素 + */ + triggerNode?: HTMLElement; + children: React.ReactNode; +} + +export function getAttach(attach: PortalProps['attach'], triggerNode?: HTMLElement): AttachNodeReturnValue { + if (!canUseDocument) return null; + + let el: AttachNodeReturnValue; + if (typeof attach === 'string') { + el = document.querySelector(attach); + } + if (typeof attach === 'function') { + el = attach(triggerNode); + } + if (typeof attach === 'object' && attach instanceof window.HTMLElement) { + el = attach; + } + + // fix el in iframe + if (el && el.nodeType === 1) return el; + + return document.body; +} + +const Portal = forwardRef((props, ref) => { + const { attach, children, triggerNode } = useDefaultProps(props, {}); + + const { classPrefix } = useConfig(); + + const container = useMemo(() => { + if (!canUseDocument) return null; + const el = document.createElement('div'); + el.className = `${classPrefix}-portal-wrapper`; + return el; + }, [classPrefix]); + + useEffect(() => { + const parentElement = getAttach(attach, triggerNode); + parentElement?.appendChild?.(container); + + return () => { + parentElement?.removeChild?.(container); + }; + }, [container, attach, triggerNode]); + + useImperativeHandle(ref, () => container); + + return canUseDocument ? createPortal(children, container) : null; +}); + +Portal.displayName = 'Portal'; + +export default Portal; diff --git a/src/hooks/useLockScroll.ts b/src/hooks/useLockScroll.ts index 61906fe4..b74d6e82 100644 --- a/src/hooks/useLockScroll.ts +++ b/src/hooks/useLockScroll.ts @@ -8,7 +8,7 @@ let totalLockCount = 0; // 移植自vant:https://github.com/youzan/vant/blob/HEAD/src/composables/use-lock-scroll.ts export function useLockScroll(rootRef: RefObject, shouldLock: boolean, componentName: string) { const touch = useTouch(); - const BODY_LOCK_CLASS = `${componentName}-overflow-hidden`; + const BODY_LOCK_CLASS = `${componentName}--lock`; const onTouchMove = useCallback( (event: TouchEvent) => { diff --git a/src/loading/Loading.tsx b/src/loading/Loading.tsx index d7a1536b..57639a7a 100644 --- a/src/loading/Loading.tsx +++ b/src/loading/Loading.tsx @@ -1,144 +1,171 @@ -import React, { useEffect, useMemo, useRef, useState } from 'react'; +import React, { forwardRef, useEffect, useMemo, useRef, useState } from 'react'; import classNames from 'classnames'; import { TdLoadingProps } from './type'; +import { loadingDefaultProps } from './defaultProps'; import { StyledProps } from '../common'; -import useConfig from '../_util/useConfig'; import Spinner from './icon/Spinner'; import Gradient from './icon/Gradient'; +import Portal from '../common/Portal'; +import { useLockScroll } from '../hooks/useLockScroll'; +import useDefaultProps from '../hooks/useDefaultProps'; +import { usePrefixClass } from '../hooks/useClass'; export interface LoadingProps extends TdLoadingProps, StyledProps {} -const Loading: React.FC = ({ - children, // 子元素,同 content - delay = 0, // 延迟显示加载效果的时间,用于防止请求速度过快引起的加载闪烁,单位:毫秒 - duration = 800, // 加载动画执行完成一次的时间,单位:毫秒 - indicator = true, // 是否显示加载指示符 - inheritColor = false, // 是否继承父元素颜色 - layout = 'horizontal', // 对齐方式 - loading = true, // 是否处于加载状态 - pause = false, // 是否暂停动画 - // preventScrollThrough = true, // 防止滚动穿透,全屏加载模式有效 - progress, // 加载进度 - reverse, // 加载动画是否反向 - size = '20px', // 尺寸,示例:40rpx/20px - text, // 加载提示文案 - theme = 'circular', // 加载组件类型 -}) => { - const { classPrefix } = useConfig(); - - const delayTimer = useRef(null); +const Loading = forwardRef((props) => { + const { + className, + style, + attach, + content, + children, + delay, + duration, + fullscreen, + indicator, + inheritColor, + layout, + loading, + pause, + preventScrollThrough, + reverse, + size, + text, + theme, + } = useDefaultProps(props, loadingDefaultProps); + + const loadingClass = usePrefixClass('loading'); + const loadingRef = useRef(); + + const childNode = content || children; + + const centerClass = `${loadingClass}--center`; + const fullClass = `${loadingClass}--full`; + const relativeClass = `${loadingClass}__parent`; + + useLockScroll(loadingRef, loading && fullscreen && preventScrollThrough, loadingClass); + // 当延时加载delay有值时,值会发生变化 const [reloading, setReloading] = useState(!delay && loading); - const textContent = useMemo(() => { - if (theme === 'error') { - return '加载失败'; + useEffect(() => { + let timer: NodeJS.Timeout | null = null; + if (delay && loading) { + timer = setTimeout(() => { + setReloading(loading); + }, delay); + } else { + setReloading(loading); } + return () => { + if (timer) { + clearTimeout(timer); + } + }; + }, [delay, loading]); - if (text) { - return text; - } + const baseClasses = classNames(loadingClass, centerClass, { + [`${loadingClass}--vertical`]: layout === 'vertical', + [`${loadingClass}--fullscreen`]: fullscreen, + [`${loadingClass}--full`]: !fullscreen && (!!attach || childNode), + }); - return null; - }, [theme, text]); + const rootStyle = useMemo( + () => ({ + color: inheritColor ? 'inherit' : undefined, + fontSize: size || undefined, + }), + [inheritColor, size], + ); - useEffect(() => { - setReloading(!delay && loading); - if (delayTimer.current) clearTimeout(delayTimer.current); - if (!delay || !loading) return; - - // 延时加载 - delayTimer.current = setTimeout(() => { - setReloading(true); - clearTimeout(delayTimer.current); - delayTimer.current = null; - }, delay); - }, [delay, loading]); + const textClass = classNames(`${loadingClass}__text`, { + [`${loadingClass}__text--only`]: !indicator, + }); - const progressCss = useMemo(() => { - if (!progress || progress <= 0) return -100; - if (progress > 1) return 0; - return (-1 + progress) * 100; - }, [progress]); - - const sizeClass = useMemo(() => { - const SIZE_CLASSNAMES = { - small: `${classPrefix}-size-s`, - medium: `${classPrefix}-size-m`, - large: `${classPrefix}-size-l`, - default: '', - xs: `${classPrefix}-size-xs`, - xl: `${classPrefix}-size-xl`, - block: `${classPrefix}-size-full-width`, + const dostLoading = () => ( +
+ {Array.from({ length: 3 }).map((val, i) => ( +
+ ))} +
+ ); + + const renderContent = () => { + if (!reloading) return null; + + const themeMap = { + circular: , + spinner: , + dots: dostLoading(), }; - if (size === 'large' || size === 'medium' || size === 'small') { - console.log(SIZE_CLASSNAMES[size]); - return SIZE_CLASSNAMES[size]; + let renderIndicator = themeMap[theme]; + + if (indicator && typeof indicator !== 'boolean') { + renderIndicator = indicator as JSX.Element; } - return ''; - }, [size, classPrefix]); + return ( + <> + {indicator && renderIndicator} + {text && {text}} + + ); + }; - return ( - <> -
+ {childNode} + {reloading && ( +
+ {renderContent()} +
)} - style={inheritColor ? { color: 'inherit' } : {}} - > - {/* theme = 'bar' 时 */} - {(theme === 'bar' && progress && ![0, 1].includes(progress) && ( -
-
+
+ ); + } + + if (attach) { + return ( + + {loading && ( +
+ {renderContent()}
- )) || - null} - {(theme !== 'bar' && ( - <> - {(indicator && reloading && ( - <> - {theme === 'circular' && } - {theme === 'spinner' && } - {theme === 'dots' && ( -
- )} - - )) || - null} - {(textContent && reloading && ( - - {textContent} - - )) || - null} - - )) || - null} - {children} + )} + + ); + } + + return ( + loading && ( +
+ {renderContent()}
- + ) ); -}; +}); + +Loading.displayName = 'Loading'; export default Loading; diff --git a/src/loading/_example/attach.tsx b/src/loading/_example/attach.tsx new file mode 100644 index 00000000..7fd4ed6c --- /dev/null +++ b/src/loading/_example/attach.tsx @@ -0,0 +1,20 @@ +import React, { useState } from 'react'; +import { Loading, Switch } from 'tdesign-mobile-react'; + +export default function AttachLoading() { + const [value, setValue] = useState(false); + + const onChange = (value) => { + setValue(value); + }; + + return ( +
+
+ Hello, I`'m Alice. I`'m going to be a front-end developer. +
+ + +
+ ); +} diff --git a/src/loading/_example/bar.jsx b/src/loading/_example/bar.jsx deleted file mode 100644 index 6adb6810..00000000 --- a/src/loading/_example/bar.jsx +++ /dev/null @@ -1,39 +0,0 @@ -import React, { useState, useCallback, useRef } from 'react'; -import { Loading, Button } from 'tdesign-mobile-react'; - -export default function () { - const [progress, setProgress] = useState(0); - const timer = useRef(null); - - const onPageLoading = useCallback(() => { - if (timer.current) { - return; - } - - let progressValue = 0; - setProgress(0); - - timer.current = setInterval(() => { - if (progressValue >= 1) { - setTimeout(() => { - setProgress(0); - }, 2000); - - clearInterval(timer.current); - timer.current = null; - return; - } - progressValue += 0.01; - setProgress((pre) => pre + 0.01); - }, 100); - }, [timer]); - - return ( -
- - -
- ); -} diff --git a/src/loading/_example/base.jsx b/src/loading/_example/base.jsx deleted file mode 100644 index 2b33c335..00000000 --- a/src/loading/_example/base.jsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react'; -import { Loading } from 'tdesign-mobile-react'; - -export default function () { - return ( -
- - -
- -
- ); -} diff --git a/src/loading/_example/base.tsx b/src/loading/_example/base.tsx new file mode 100644 index 00000000..964c0c21 --- /dev/null +++ b/src/loading/_example/base.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { Loading } from 'tdesign-mobile-react'; + +export default function BaseLoading() { + return ( + <> + + + + + ); +} diff --git a/src/loading/_example/delay.jsx b/src/loading/_example/delay.tsx similarity index 93% rename from src/loading/_example/delay.jsx rename to src/loading/_example/delay.tsx index 520bdada..b36cea3d 100644 --- a/src/loading/_example/delay.jsx +++ b/src/loading/_example/delay.tsx @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import { Loading, Switch } from 'tdesign-mobile-react'; -export default function () { +export default function DelayLoading() { const [showLoading, setShowLoading] = useState(false); const clickSwitch = (value) => { diff --git a/src/loading/_example/fullscreen.tsx b/src/loading/_example/fullscreen.tsx new file mode 100644 index 00000000..c0e81875 --- /dev/null +++ b/src/loading/_example/fullscreen.tsx @@ -0,0 +1,28 @@ +import React, { useState } from 'react'; +import { Loading, Switch } from 'tdesign-mobile-react'; + +export default function FullScreenLoading() { + const [value, setValue] = useState(false); + const [loading, setLoading] = useState(false); + + const onChange = (value) => { + setValue(value); + setLoading(value); + + if (value) + setTimeout(() => { + setValue(false); + setLoading(false); + }, 2000); + }; + + return ( +
+ +
+ 全局加载开关(开启加载1秒后自动归位): + +
+
+ ); +} diff --git a/src/loading/_example/horz.jsx b/src/loading/_example/horz.jsx deleted file mode 100644 index 11f0ddc7..00000000 --- a/src/loading/_example/horz.jsx +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react'; -import { Loading } from 'tdesign-mobile-react'; - -export default function () { - return ( -
- -
- -
- - 加载中... - -
- ); -} diff --git a/src/loading/_example/horz.tsx b/src/loading/_example/horz.tsx new file mode 100644 index 00000000..4fd0292e --- /dev/null +++ b/src/loading/_example/horz.tsx @@ -0,0 +1,11 @@ +import React from 'react'; +import { Loading } from 'tdesign-mobile-react'; + +export default function HorzLoading() { + return ( + <> + + + + ); +} diff --git a/src/loading/_example/index.jsx b/src/loading/_example/index.jsx deleted file mode 100644 index 5f726def..00000000 --- a/src/loading/_example/index.jsx +++ /dev/null @@ -1,49 +0,0 @@ -import React from 'react'; -import TDemoBlock from '../../../site/mobile/components/DemoBlock'; -import TDemoHeader from '../../../site/mobile/components/DemoHeader'; -import Base from './base'; -import Horz from './horz'; -import Vert from './vert'; -import PureText from './pure-text'; -import Bar from './bar'; -import Delay from './delay'; -import Size from './size'; -import Speed from './speed'; - -import './style/index.less'; - -export default function () { - return ( - <> - - - - - - - - - - - - - - - - - - - - - - - - - - - - ); -} diff --git a/src/loading/_example/index.tsx b/src/loading/_example/index.tsx new file mode 100644 index 00000000..797296a2 --- /dev/null +++ b/src/loading/_example/index.tsx @@ -0,0 +1,68 @@ +import React from 'react'; +import TDemoBlock from '../../../site/mobile/components/DemoBlock'; +import TDemoHeader from '../../../site/mobile/components/DemoHeader'; +import BaseLoading from './base'; +import HorzLoading from './horz'; +import VertLoading from './vert'; +import PureTextLoading from './pure-text'; +import SizeLoading from './size'; +import SpeedLoading from './speed'; +// import FullScreenLoading from './fullscreen'; +// import AttachLoading from './attach'; +// import PluginLoading from './service'; +// import DelayLoading from './delay'; + +import './style/index.less'; + +export default function () { + return ( + <> + + +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ + + + + + + {/* 视觉稿待定,暂时注释 */} + {/* + + + + + + + + + + + */} + + ); +} diff --git a/src/loading/_example/pure-text.jsx b/src/loading/_example/pure-text.jsx deleted file mode 100644 index 5ae5a082..00000000 --- a/src/loading/_example/pure-text.jsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; -import { Loading } from 'tdesign-mobile-react'; - -export default function () { - return ( -
- - -
- - 加载失败 - 刷新 - -
-
- ); -} diff --git a/src/loading/_example/pure-text.tsx b/src/loading/_example/pure-text.tsx new file mode 100644 index 00000000..c9f5e13d --- /dev/null +++ b/src/loading/_example/pure-text.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import { Loading } from 'tdesign-mobile-react'; + +export default function PureTextLoading() { + return ( + <> + + + ); +} diff --git a/src/loading/_example/service.tsx b/src/loading/_example/service.tsx new file mode 100644 index 00000000..e8e473f7 --- /dev/null +++ b/src/loading/_example/service.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import { LoadingPlugin, Button } from 'tdesign-mobile-react'; + +export default function PluginLoading() { + // 函数式:局部加载 + const showAttach = () => { + const loadingInstance = LoadingPlugin({ + attach: () => document.querySelector('#loading-service-demo'), // 等于 attach: '#loading-service-demo' + size: '20px', + }); + const timer = setTimeout(() => { + loadingInstance.hide(); + clearTimeout(timer); + }, 1000); + }; + + // 函数式:全屏加载,防止滚动穿透 + const showFullScreen = () => { + const loadingInstance = LoadingPlugin(true); + const timer = setTimeout(() => { + loadingInstance.hide(); + clearTimeout(timer); + }, 1000); + }; + + return ( +
+
+ Loading 挂载容器 +
+
+ + +
+
+ ); +} diff --git a/src/loading/_example/size.jsx b/src/loading/_example/size.jsx deleted file mode 100644 index d6abb80e..00000000 --- a/src/loading/_example/size.jsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; -import { Loading } from 'tdesign-mobile-react'; - -export default function () { - return ( -
- - - -
- ); -} diff --git a/src/loading/_example/size.tsx b/src/loading/_example/size.tsx new file mode 100644 index 00000000..e5e9a98e --- /dev/null +++ b/src/loading/_example/size.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { Loading } from 'tdesign-mobile-react'; + +export default function SizeLoading() { + return ( + <> +
+
大尺寸
+ +
+
+
中尺寸
+ +
+
+
小尺寸
+ +
+ + ); +} diff --git a/src/loading/_example/speed.jsx b/src/loading/_example/speed.jsx deleted file mode 100644 index 78c1785b..00000000 --- a/src/loading/_example/speed.jsx +++ /dev/null @@ -1,16 +0,0 @@ -import React, { useState } from 'react'; -import { Loading, Slider } from 'tdesign-mobile-react'; - -export default function () { - const [speed, setSpeed] = useState(300); - - return ( -
- -
- 速度调整 - -
-
- ); -} diff --git a/src/loading/_example/speed.tsx b/src/loading/_example/speed.tsx new file mode 100644 index 00000000..17b7abe7 --- /dev/null +++ b/src/loading/_example/speed.tsx @@ -0,0 +1,22 @@ +import React, { useState } from 'react'; +import { Loading, Slider } from 'tdesign-mobile-react'; + +export default function SpeedLoading() { + const [speed, setSpeed] = useState(5); + + const onChange = (value) => { + setSpeed(value); + }; + + return ( + <> +
+ +
+ +
+ +
+ + ); +} diff --git a/src/loading/_example/style/index.less b/src/loading/_example/style/index.less index 7567e095..119e5c34 100644 --- a/src/loading/_example/style/index.less +++ b/src/loading/_example/style/index.less @@ -1,50 +1,58 @@ -.demo-content { - padding: 0 16px; +.loading-demo--flex { display: flex; - // flex-direction: column; - &--column { - flex-direction: column; - } - .demo-loading { - margin-top: 20px; - } - .demo-loading-speed-slider { - width: 100%; - margin-top: 20px; - display: grid; - align-items: center; - grid-gap: 24px; - grid-template-columns: auto 1fr; - } - &.pure-text-demo-box { - display: grid; - align-items: center; - grid-gap: 40px; - grid-template-columns: repeat(3, auto); - } - &.demo-content-size { - .t-loading { - margin-bottom: 20px; - } - } + align-items: center; +} + +.t-loading { + margin-right: 64px; } -.normal-content { +.loading-demo { padding: 0 16px; } -.custom-error { +.loading-demo__summary { font-size: 14px; - .t-loading { - color: rgba(0, 0, 0, 0.9); - } - span { - margin-left: 10px; - color: rgba(0, 82, 217, 1); - cursor: pointer; - } + color: rgba(0, 0, 0, 0.6); + line-height: 22px; +} +.mt-24 { + margin-top: 24px; } -#app { - background-color: #fff; +.mb-16 { + margin-bottom: 16px; +} + +.slider-wrap { + // width: 100%; + // margin-top: 16px; + // padding: 20px 0; + margin: 16px; +} + +// attact demo +.loading-attach-demo__title { + /** `position: relative` is required as a parent node */ + position: relative; + width: 360px; + text-align: center; +} + +// PluginDemo +.space { + display: flex; + margin-top: 8px; + gap: 6px; +} +.loading-service-demo { + position: relative; + width: 100%; + height: 64px; + line-height: 64px; + text-align: center; + display: flex; + align-items: center; + justify-content: center; + border: 1px var(--component-border, #eee) solid; } diff --git a/src/loading/_example/vert.jsx b/src/loading/_example/vert.jsx deleted file mode 100644 index d5307672..00000000 --- a/src/loading/_example/vert.jsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react'; -import { Loading } from 'tdesign-mobile-react'; - -export default function () { - return ( -
- -
- ); -} diff --git a/src/loading/_example/vert.tsx b/src/loading/_example/vert.tsx new file mode 100644 index 00000000..c648c89c --- /dev/null +++ b/src/loading/_example/vert.tsx @@ -0,0 +1,11 @@ +import React from 'react'; +import { Loading } from 'tdesign-mobile-react'; + +export default function VertLoading() { + return ( + <> + + + + ); +} diff --git a/src/loading/defaultProps.ts b/src/loading/defaultProps.ts new file mode 100644 index 00000000..52f86dc8 --- /dev/null +++ b/src/loading/defaultProps.ts @@ -0,0 +1,20 @@ +/** + * 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC + * */ + +import { TdLoadingProps } from './type'; + +export const loadingDefaultProps: TdLoadingProps = { + attach: '', + delay: 0, + duration: 800, + fullscreen: false, + indicator: true, + inheritColor: false, + layout: 'horizontal', + loading: true, + pause: false, + preventScrollThrough: true, + size: '20px', + theme: 'circular', +}; diff --git a/src/loading/index.ts b/src/loading/index.ts index 75b4f141..2c1cfb43 100644 --- a/src/loading/index.ts +++ b/src/loading/index.ts @@ -1,4 +1,5 @@ import _Loading from './Loading'; +import { LoadingPlugin as _LoadingPlugin } from './plugin'; import './style/index.js'; @@ -6,4 +7,7 @@ export type { LoadingProps } from './Loading'; export * from './type'; export const Loading = _Loading; +export const loading = _LoadingPlugin; +export const LoadingPlugin = _LoadingPlugin; + export default Loading; diff --git a/src/loading/loading.en-US.md b/src/loading/loading.en-US.md new file mode 100644 index 00000000..51f0f3eb --- /dev/null +++ b/src/loading/loading.en-US.md @@ -0,0 +1,35 @@ +:: BASE_DOC :: + +## API + + +### Loading Props + +name | type | default | description | required +-- | -- | -- | -- | -- +className | String | - | className of component | N +style | Object | - | CSS(Cascading Style Sheets),Typescript:`React.CSSProperties` | N +attach | String / Function | '' | Typescript:`AttachNode`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N +children | TNode | - | Typescript:`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N +content | TNode | - | Typescript:`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N +delay | Number | 0 | \- | N +duration | Number | 800 | \- | N +fullscreen | Boolean | false | \- | N +indicator | TNode | true | Typescript:`boolean \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N +inheritColor | Boolean | false | \- | N +layout | String | horizontal | options: horizontal/vertical | N +loading | Boolean | true | \- | N +pause | Boolean | false | \- | N +preventScrollThrough | Boolean | true | \- | N +reverse | Boolean | - | \- | N +size | String | '20px' | \- | N +text | TNode | - | Typescript:`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N +theme | String | circular | options: circular/spinner/dots | N + +### loading 或 LoadingPlugin + +name | params | default | description +-- | -- | -- | -- +options | Function | - | required。Typescript:`boolean \| TdLoadingProps` + +插件返回值:`LoadingInstance【interface LoadingInstance { hide: () => void }】` diff --git a/src/loading/loading.md b/src/loading/loading.md index 6b3a1287..7e57f52e 100644 --- a/src/loading/loading.md +++ b/src/loading/loading.md @@ -4,33 +4,31 @@ ### Loading Props -名称 | 类型 | 默认值 | 说明 | 必传 +名称 | 类型 | 默认值 | 描述 | 必传 -- | -- | -- | -- | -- className | String | - | 类名 | N style | Object | - | 样式,TS 类型:`React.CSSProperties` | N -children | TNode | - | 子元素,同 content。TS 类型:`string | TNode`。[通用类型定义](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N +attach | String / Function | '' | 挂载元素,默认挂载到组件本身所在的位置。数据类型为 String 时,会被当作选择器处理,进行节点查询。示例:'body' 或 () => document.body。TS 类型:`AttachNode`。[通用类型定义](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N +children | TNode | - | 子元素,同 content。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N +content | TNode | - | 子元素。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N delay | Number | 0 | 延迟显示加载效果的时间,用于防止请求速度过快引起的加载闪烁,单位:毫秒 | N duration | Number | 800 | 加载动画执行完成一次的时间,单位:毫秒 | N -indicator | Boolean | true | 是否显示加载指示符 | N +fullscreen | Boolean | false | 是否显示为全屏加载 | N +indicator | TNode | true | 加载指示符,值为 true 显示默认指示符,值为 false 则不显示,也可以自定义指示符。TS 类型:`boolean \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N inheritColor | Boolean | false | 是否继承父元素颜色 | N layout | String | horizontal | 对齐方式。可选项:horizontal/vertical | N loading | Boolean | true | 是否处于加载状态 | N pause | Boolean | false | 是否暂停动画 | N preventScrollThrough | Boolean | true | 防止滚动穿透,全屏加载模式有效 | N -progress | Number | - | 加载进度 | N reverse | Boolean | - | 加载动画是否反向 | N -size | String | '40rpx' | 尺寸,示例:40rpx/20px | N -text | TNode | - | 加载提示文案。TS 类型:`string | TNode`。[通用类型定义](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N -theme | String | circular | 加载组件类型。可选项:circular/spinner/bar/error/dots | N +size | String | '20px' | 尺寸,示例:20px | N +text | TNode | - | 加载提示文案。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N +theme | String | circular | 加载组件类型。可选项:circular/spinner/dots | N ### loading 或 LoadingPlugin -这是一个插件函数,参数形式为顺序参数(形如:(a, b, c)),而非对象参数(形如:({ a, b, c }))。顺序参数如下, - -参数名称 | 参数类型 | 参数默认值 | 参数说明 +参数名称 | 参数类型 | 参数默认值 | 参数描述 -- | -- | -- | -- -className | String | - | 类名 | N -style | Object | - | 样式,TS 类型:`React.CSSProperties` | N -options | Function | - | 必需。TS 类型:`boolean | TdLoadingProps` +options | Function | - | 必需。TS 类型:`boolean \| TdLoadingProps` 插件返回值:`LoadingInstance【interface LoadingInstance { hide: () => void }】` diff --git a/src/loading/plugin.tsx b/src/loading/plugin.tsx new file mode 100644 index 00000000..a77e68fb --- /dev/null +++ b/src/loading/plugin.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import Loading, { LoadingProps } from './Loading'; +import { LoadingInstance, TdLoadingProps } from './type'; + +function createContainer(attach?: TdLoadingProps['attach']) { + if (typeof attach === 'string') return document.querySelector(attach); + if (typeof attach === 'function') return attach(); + return document.body; +} + +export type LoadingPluginMethod = (options: boolean | LoadingProps) => LoadingInstance; + +// loading plugin形式 +export const LoadingPlugin: LoadingPluginMethod = (options) => { + if (options === false) return { hide: () => null }; + + const props = typeof options === 'boolean' ? {} : options; + const { attach } = props; + const container = createContainer(attach); + const div = document.createElement('div'); + div.setAttribute('style', 'width: 100%; height: 100%; position: absolute; top: 0;'); + + const defaultProps = { + loading: true, + attach: null, + fullscreen: !attach, + ...props, + }; + + ReactDOM.render(, div); + + container.appendChild(div); + + return { + hide: () => { + ReactDOM.unmountComponentAtNode(div); + div.remove(); + }, + }; +}; diff --git a/src/loading/type.ts b/src/loading/type.ts index 8ce7ee23..817a7cb5 100644 --- a/src/loading/type.ts +++ b/src/loading/type.ts @@ -4,13 +4,22 @@ * 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC * */ -import { TNode } from '../common'; +import { TNode, AttachNode } from '../common'; export interface TdLoadingProps { + /** + * 挂载元素,默认挂载到组件本身所在的位置。数据类型为 String 时,会被当作选择器处理,进行节点查询。示例:'body' 或 () => document.body + * @default '' + */ + attach?: AttachNode; /** * 子元素,同 content */ children?: TNode; + /** + * 子元素 + */ + content?: TNode; /** * 延迟显示加载效果的时间,用于防止请求速度过快引起的加载闪烁,单位:毫秒 * @default 0 @@ -22,10 +31,15 @@ export interface TdLoadingProps { */ duration?: number; /** - * 是否显示加载指示符 + * 是否显示为全屏加载 + * @default false + */ + fullscreen?: boolean; + /** + * 加载指示符,值为 true 显示默认指示符,值为 false 则不显示,也可以自定义指示符 * @default true */ - indicator?: boolean; + indicator?: TNode; /** * 是否继承父元素颜色 * @default false @@ -51,17 +65,13 @@ export interface TdLoadingProps { * @default true */ preventScrollThrough?: boolean; - /** - * 加载进度 - */ - progress?: number; /** * 加载动画是否反向 */ reverse?: boolean; /** - * 尺寸,示例:40rpx/20px - * @default '40rpx' + * 尺寸,示例:20px + * @default '20px' */ size?: string; /** @@ -72,9 +82,11 @@ export interface TdLoadingProps { * 加载组件类型 * @default circular */ - theme?: 'circular' | 'spinner' | 'bar' | 'error' | 'dots'; + theme?: 'circular' | 'spinner' | 'dots'; } -export interface LoadingInstance { hide: () => void }; +export interface LoadingInstance { + hide: () => void; +} export type LoadingMethod = (options: boolean | TdLoadingProps) => LoadingInstance; diff --git a/test/snap/__snapshots__/csr.test.jsx.snap b/test/snap/__snapshots__/csr.test.jsx.snap index 94f8f30f..de46cfb4 100644 --- a/test/snap/__snapshots__/csr.test.jsx.snap +++ b/test/snap/__snapshots__/csr.test.jsx.snap @@ -2462,6 +2462,1188 @@ exports[`csr snapshot test > csr test src/grid/_example/badge.tsx 1`] = `
`; +exports[`csr snapshot test > csr test src/loading/_example/attach.tsx 1`] = ` +
+
+
+ Hello, I\`'m Alice. I\`'m going to be a front-end developer. +
+
+ +
+
+`; + +exports[`csr snapshot test > csr test src/loading/_example/base.tsx 1`] = ` +
+
+ + +
+ + +
+
+ + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
+`; + +exports[`csr snapshot test > csr test src/loading/_example/delay.tsx 1`] = ` +
+
+ +
+
+
+`; + +exports[`csr snapshot test > csr test src/loading/_example/fullscreen.tsx 1`] = ` +
+
+
+ 全局加载开关(开启加载1秒后自动归位): + +
+
+
+`; + +exports[`csr snapshot test > csr test src/loading/_example/horz.tsx 1`] = ` +
+
+ + +
+ + + + 加载中... + +
+
+ + + + + + + + + + + + + + + + 加载中... + +
+ +`; + +exports[`csr snapshot test > csr test src/loading/_example/index.tsx 1`] = ` +
+
+

+ Loading 加载 +

+

+ 用于表示页面或操作的加载状态,给予用户反馈的同时减缓等待的焦虑感,由一个或一组反馈动效组成。 +

+
+
+
+

+ 01 类型 +

+

+ 纯图标 +

+
+
+
+
+ + +
+ + +
+
+ + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+

+ 图标加文字横向 +

+
+
+
+
+ + +
+ + + + 加载中... + +
+
+ + + + + + + + + + + + + + + + 加载中... + +
+ + + +
+
+

+ 图标加文字竖向 +

+
+
+
+
+ + +
+ + + + 加载中 + +
+
+ + + + + + + + + + + + + + + + 加载中... + +
+ + + +
+
+

+ 纯文字 +

+
+
+
+
+ + 加载中... + +
+
+
+
+
+
+

+ 02 组件尺寸 +

+
+
+
+
+ 大尺寸 +
+
+ + +
+ + + + 加载中... + +
+ +
+
+ 中尺寸 +
+
+ + +
+ + + + 加载中... + +
+ +
+
+ 小尺寸 +
+
+ + +
+ + + + 加载中... + +
+ + + +
+
+

+ 03 加载速度 +

+

+ 加载速度调整 +

+
+
+
+
+ + +
+ + + + 加载中... + +
+ +
+
+
+
+
+
+
+
+ 5 +
+
+
+
+
+
+`; + +exports[`csr snapshot test > csr test src/loading/_example/pure-text.tsx 1`] = ` +
+
+ + 加载中... + +
+
+`; + +exports[`csr snapshot test > csr test src/loading/_example/service.tsx 1`] = ` +
+
+
+ Loading 挂载容器 +
+
+ + +
+
+
+`; + +exports[`csr snapshot test > csr test src/loading/_example/size.tsx 1`] = ` +
+
+
+ 大尺寸 +
+
+ + +
+ + + + 加载中... + +
+ +
+
+ 中尺寸 +
+
+ + +
+ + + + 加载中... + +
+ +
+
+ 小尺寸 +
+
+ + +
+ + + + 加载中... + +
+ + +`; + +exports[`csr snapshot test > csr test src/loading/_example/speed.tsx 1`] = ` +
+
+
+ + +
+ + + + 加载中... + +
+ +
+
+
+
+
+
+
+
+ 5 +
+
+
+
+`; + +exports[`csr snapshot test > csr test src/loading/_example/vert.tsx 1`] = ` +
+
+ + +
+ + + + 加载中 + +
+
+ + + + + + + + + + + + + + + + 加载中... + +
+ +`; + exports[`csr snapshot test > csr test src/overlay/_example/base.tsx 1`] = `
@@ -6293,6 +7475,28 @@ exports[`ssr snapshot test > ssr test src/divider/_example/theme.tsx 1`] = `" ssr test src/grid/_example/badge.tsx 1`] = `"
标题文字
8
标题文字
new
标题文字
···
标题文字
"`; +exports[`ssr snapshot test > ssr test src/loading/_example/attach.tsx 1`] = `"
Hello, I\`'m Alice. I\`'m going to be a front-end developer.
"`; + +exports[`ssr snapshot test > ssr test src/loading/_example/base.tsx 1`] = `"
"`; + +exports[`ssr snapshot test > ssr test src/loading/_example/delay.tsx 1`] = `"
"`; + +exports[`ssr snapshot test > ssr test src/loading/_example/fullscreen.tsx 1`] = `"
全局加载开关(开启加载1秒后自动归位):
"`; + +exports[`ssr snapshot test > ssr test src/loading/_example/horz.tsx 1`] = `"
加载中...
加载中...
"`; + +exports[`ssr snapshot test > ssr test src/loading/_example/index.tsx 1`] = `"

Loading 加载

用于表示页面或操作的加载状态,给予用户反馈的同时减缓等待的焦虑感,由一个或一组反馈动效组成。

01 类型

纯图标

图标加文字横向

加载中...
加载中...

图标加文字竖向

加载中
加载中...

纯文字

加载中...

02 组件尺寸

大尺寸
加载中...
中尺寸
加载中...
小尺寸
加载中...

03 加载速度

加载速度调整

加载中...
5
"`; + +exports[`ssr snapshot test > ssr test src/loading/_example/pure-text.tsx 1`] = `"
加载中...
"`; + +exports[`ssr snapshot test > ssr test src/loading/_example/service.tsx 1`] = `"
Loading 挂载容器
"`; + +exports[`ssr snapshot test > ssr test src/loading/_example/size.tsx 1`] = `"
大尺寸
加载中...
中尺寸
加载中...
小尺寸
加载中...
"`; + +exports[`ssr snapshot test > ssr test src/loading/_example/speed.tsx 1`] = `"
加载中...
5
"`; + +exports[`ssr snapshot test > ssr test src/loading/_example/vert.tsx 1`] = `"
加载中
加载中...
"`; + exports[`ssr snapshot test > ssr test src/overlay/_example/base.tsx 1`] = `"
"`; exports[`ssr snapshot test > ssr test src/overlay/_example/index.tsx 1`] = `"

Overlay 遮罩层

通过遮罩层,可以强调部分内容

01 组件

基础遮罩层

"`; diff --git a/test/snap/__snapshots__/ssr.test.jsx.snap b/test/snap/__snapshots__/ssr.test.jsx.snap index 28e86820..4d13abf9 100644 --- a/test/snap/__snapshots__/ssr.test.jsx.snap +++ b/test/snap/__snapshots__/ssr.test.jsx.snap @@ -20,6 +20,28 @@ exports[`ssr snapshot test > ssr test src/divider/_example/theme.tsx 1`] = `" ssr test src/grid/_example/badge.tsx 1`] = `"
标题文字
8
标题文字
new
标题文字
···
标题文字
"`; +exports[`ssr snapshot test > ssr test src/loading/_example/attach.tsx 1`] = `"
Hello, I\`'m Alice. I\`'m going to be a front-end developer.
"`; + +exports[`ssr snapshot test > ssr test src/loading/_example/base.tsx 1`] = `"
"`; + +exports[`ssr snapshot test > ssr test src/loading/_example/delay.tsx 1`] = `"
"`; + +exports[`ssr snapshot test > ssr test src/loading/_example/fullscreen.tsx 1`] = `"
全局加载开关(开启加载1秒后自动归位):
"`; + +exports[`ssr snapshot test > ssr test src/loading/_example/horz.tsx 1`] = `"
加载中...
加载中...
"`; + +exports[`ssr snapshot test > ssr test src/loading/_example/index.tsx 1`] = `"

Loading 加载

用于表示页面或操作的加载状态,给予用户反馈的同时减缓等待的焦虑感,由一个或一组反馈动效组成。

01 类型

纯图标

图标加文字横向

加载中...
加载中...

图标加文字竖向

加载中
加载中...

纯文字

加载中...

02 组件尺寸

大尺寸
加载中...
中尺寸
加载中...
小尺寸
加载中...

03 加载速度

加载速度调整

加载中...
5
"`; + +exports[`ssr snapshot test > ssr test src/loading/_example/pure-text.tsx 1`] = `"
加载中...
"`; + +exports[`ssr snapshot test > ssr test src/loading/_example/service.tsx 1`] = `"
Loading 挂载容器
"`; + +exports[`ssr snapshot test > ssr test src/loading/_example/size.tsx 1`] = `"
大尺寸
加载中...
中尺寸
加载中...
小尺寸
加载中...
"`; + +exports[`ssr snapshot test > ssr test src/loading/_example/speed.tsx 1`] = `"
加载中...
5
"`; + +exports[`ssr snapshot test > ssr test src/loading/_example/vert.tsx 1`] = `"
加载中
加载中...
"`; + exports[`ssr snapshot test > ssr test src/overlay/_example/base.tsx 1`] = `"
"`; exports[`ssr snapshot test > ssr test src/overlay/_example/index.tsx 1`] = `"

Overlay 遮罩层

通过遮罩层,可以强调部分内容

01 组件

基础遮罩层

"`;