diff --git a/site/sidebar.config.ts b/site/sidebar.config.ts index d139d0e..25c1b67 100644 --- a/site/sidebar.config.ts +++ b/site/sidebar.config.ts @@ -154,6 +154,12 @@ export default [ path: '/components/tooltip', component: () => import('tdesign-web-components/tooltip/README.md'), }, + { + title: 'Loading 加载', + name: 'loading', + path: '/components/loading', + component: () => import('tdesign-web-components/loading/README.md'), + }, ], }, { diff --git a/src/_util/dom.ts b/src/_util/dom.ts new file mode 100644 index 0000000..799aee8 --- /dev/null +++ b/src/_util/dom.ts @@ -0,0 +1,146 @@ +import raf from 'raf'; + +import { AttachNode, AttachNodeReturnValue } from '../common'; +import { easeInOutCubic, EasingFunction } from './easing'; +// 用于判断是否可使用 dom +export const canUseDocument = !!(typeof window !== 'undefined' && window.document && window.document.createElement); + +// 获取 css vars +export const getCssVarsValue = (name: string, element?: HTMLElement) => { + if (!canUseDocument) return; + + const el = element || document.documentElement; + return getComputedStyle(el).getPropertyValue(name); +}; + +function isWindow(obj: any) { + return obj && obj === obj.window; +} + +type ScrollTarget = HTMLElement | Window | Document; + +export function getScroll(target: ScrollTarget, isLeft?: boolean): number { + // node环境或者target为空 + if (typeof window === 'undefined' || !target) { + return 0; + } + const method = isLeft ? 'scrollLeft' : 'scrollTop'; + let result = 0; + if (isWindow(target)) { + result = (target as Window)[isLeft ? 'pageXOffset' : 'pageYOffset']; + } else if (target instanceof Document) { + result = target.documentElement[method]; + } else if (target) { + result = (target as HTMLElement)[method]; + } + return result; +} + +interface ScrollTopOptions { + container?: ScrollTarget; + duration?: number; + easing?: EasingFunction; +} + +export const scrollTo = (target: number, opt: ScrollTopOptions) => { + const { container = window, duration = 450, easing = easeInOutCubic } = opt; + const scrollTop = getScroll(container); + const startTime = Date.now(); + return new Promise((res) => { + const fnc = () => { + const timestamp = Date.now(); + const time = timestamp - startTime; + const nextScrollTop = easing(Math.min(time, duration), scrollTop, target, duration); + if (isWindow(container)) { + (container as Window).scrollTo(window.pageXOffset, nextScrollTop); + } else if (container instanceof HTMLDocument || container.constructor.name === 'HTMLDocument') { + (container as HTMLDocument).documentElement.scrollTop = nextScrollTop; + } else { + (container as HTMLElement).scrollTop = nextScrollTop; + } + if (time < duration) { + raf(fnc); + } else { + // 由于上面步骤设置了 scrollTop, 滚动事件可能未触发完毕 + // 此时应该在下一帧再执行 res + raf(res); + } + }; + raf(fnc); + }); +}; + +export function getAttach(attach: AttachNode, 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') { + if ((attach as any) instanceof window.HTMLElement) { + el = attach; + } + } + + // fix el in iframe + if (el && el.nodeType === 1) return el; + + return document.body; +} + +export const addClass = function (el: Element, cls: string) { + if (!el) return; + let curClass = el.className; + const classes = (cls || '').split(' '); + + for (let i = 0, j = classes.length; i < j; i++) { + const clsName = classes[i]; + if (!clsName) continue; + + if (el.classList) { + el.classList.add(clsName); + } else if (!hasClass(el, clsName)) { + curClass += ` ${clsName}`; + } + } + if (!el.classList) { + // eslint-disable-next-line + el.className = curClass; + } +}; + +const trim = (str: string): string => (str || '').replace(/^[\s\uFEFF]+|[\s\uFEFF]+$/g, ''); + +export const removeClass = function (el: Element, cls: string) { + if (!el || !cls) return; + const classes = cls.split(' '); + let curClass = ` ${el.className} `; + + for (let i = 0, j = classes.length; i < j; i++) { + const clsName = classes[i]; + if (!clsName) continue; + + if (el.classList) { + el.classList.remove(clsName); + } else if (hasClass(el, clsName)) { + curClass = curClass.replace(` ${clsName} `, ' '); + } + } + if (!el.classList) { + // eslint-disable-next-line + el.className = trim(curClass); + } +}; + +export function hasClass(el: Element, cls: string) { + if (!el || !cls) return false; + if (cls.indexOf(' ') !== -1) throw new Error('className should not contain space.'); + if (el.classList) { + return el.classList.contains(cls); + } + return ` ${el.className} `.indexOf(` ${cls} `) > -1; +} diff --git a/src/_util/easing.ts b/src/_util/easing.ts new file mode 100644 index 0000000..8e608af --- /dev/null +++ b/src/_util/easing.ts @@ -0,0 +1,42 @@ +/** + * @file + * 缓动函数 + * 参考自: https://github.com/bameyrick/js-easing-functions/blob/master/src/index.ts + */ + +export interface EasingFunction { + (current: number, start: number, end: number, duration: number): number; +} + +/** + * @export + * @param {number} current 当前时间 + * @param {number} start 开始值 + * @param {number} end 结束值 + * @param {number} duration 持续时间 + * @returns + */ +export const linear: EasingFunction = function (current, start, end, duration) { + const change = end - start; + const offset = (change * current) / duration; + return offset + start; +}; + +/** + * @export + * @param {number} current 当前时间 + * @param {number} start 开始值 + * @param {number} end 结束值 + * @param {number} duration 持续时间 + * @returns + */ +export const easeInOutCubic: EasingFunction = function (current, start, end, duration) { + const change = (end - start) / 2; + let time = current / (duration / 2); + if (time < 1) { + return change * time * time * time + start; + } + time -= 2; + // eslint-disable-next-line no-return-assign + return change * (time * time * time + 2) + start; +}; diff --git a/src/common/index.ts b/src/common/index.ts new file mode 100644 index 0000000..b2af344 --- /dev/null +++ b/src/common/index.ts @@ -0,0 +1,5 @@ +import _Portal from './portal'; + +export type { PortalProps } from './portal'; + +export const Partal = _Portal; diff --git a/src/common/portal.tsx b/src/common/portal.tsx new file mode 100644 index 0000000..0962cc8 --- /dev/null +++ b/src/common/portal.tsx @@ -0,0 +1,70 @@ +import { Component, render, tag } from 'omi'; + +import { getClassPrefix } from '../_util/classname'; +import { canUseDocument } from '../_util/dom'; +import { AttachNode, AttachNodeReturnValue, TNode } from '../common'; + +export interface PortalProps { + /** + * 指定挂载的 HTML 节点, false 为挂载在 body + */ + attach?: AttachNode; + /** + * 触发元素 + */ + triggerNode?: HTMLElement; + children: TNode; +} + +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 as any) instanceof window.HTMLElement) { + el = attach; + } + + // fix el in iframe + if (el && el.nodeType === 1) return el; + + return document.body; +} + +@tag('t-portal') +export default class Portal extends Component { + static css = []; + + static defaultProps = { + attach: false, + }; + + parentElement: HTMLElement | null = null; + + container: HTMLElement = null; + + classPrefix = getClassPrefix(); + + getContainer() { + if (!canUseDocument) return null; + const el = document.createElement('div'); + el.className = `${this.classPrefix}-portal-wrapper`; + return el; + } + + install(): void { + this.container = this.getContainer(); + this.parentElement = getAttach(this.props.attach, this.props.triggerNode) as HTMLElement; + this.parentElement?.appendChild?.(this.container); + } + + render() { + const { children } = this.props; + return render(children, this.container); + } +} diff --git a/src/index.ts b/src/index.ts index 3fe8e78..5fe7556 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,10 @@ export * from './button'; +export * from './common'; export * from './divider'; export * from './icon'; export * from './image'; export * from './input'; +export * from './loading'; export * from './popup'; export * from './space'; export * from './switch'; diff --git a/src/loading/README.md b/src/loading/README.md new file mode 100644 index 0000000..248adc7 --- /dev/null +++ b/src/loading/README.md @@ -0,0 +1,89 @@ +--- +title: Loading 加载中 +description: 在网络较慢或数据较多时,表示数据正在加载的状态。 +isComponent: true +usage: { title: '', description: '' } +spline: message +--- + +### 图标加载 + +加载过程中只有图标显示。适用于打开页面或操作完成后模块内等待刷新的加载场景。 + +{{ base }} + +### 文字加载 + +加载过程中有文字显示。适用于打开页面或操作完成后模块内等待刷新的加载场景。 + +{{ text }} + +### 文字和图标共同显示加载 + +加载过程中有文字和图标共同显示。适用于打开页面或操作完成后页面内等待刷新的加载场景。 + +{{ icon-text }} + +### 不同尺寸的加载 +小尺寸适用于组件内加载场景,中尺寸适用于容器如卡片、表格等区域的加载场景,大尺寸适用于页面全屏加载场景。 + +{{ size }} + +### 有包裹的加载 +Loading 组件可以作为容器包裹需要显示加载状态的内容。 + +{{ wrap }} + +### 有延时的加载 +设置最短延迟响应时间,低于响应时间的操作不显示加载状态。 + +{{ delay }} + +### 全屏加载 +全屏展示加载状态,阻止用户操作。 + +{{ fullscreen }} + +### 挂载到指定元素 + +可通过 `attach` 挂载到指定元素。 + +注:被挂载元素(loading的父元素)需设置:`position: relative;` + +{{ attach }} + +### 函数方式调用 + +{{ service }} + + +## API +### Loading Props + +名称 | 类型 | 默认值 | 说明 | 必传 +-- | -- | -- | -- | -- +className | String | - | 类名 | N +style | Object | - | 样式,TS 类型:`Styles` | N +attach | String / Function | '' | 挂载元素,默认挂载到组件本身所在的位置。数据类型为 String 时,会被当作选择器处理,进行节点查询。示例:'body' 或 () => document.body。TS 类型:`AttachNode`。[通用类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N +children | TNode | - | 子元素,同 content。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N +content | TNode | - | 子元素。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N +delay | Number | 0 | 延迟显示加载效果的时间,用于防止请求速度过快引起的加载闪烁,单位:毫秒 | N +fullscreen | Boolean | false | 是否显示为全屏加载 | N +indicator | TNode | true | 加载指示符,值为 true 显示默认指示符,值为 false 则不显示,也可以自定义指示符。TS 类型:`boolean \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N +inheritColor | Boolean | false | 是否继承父元素颜色 | N +loading | Boolean | true | 是否处于加载状态 | N +preventScrollThrough | Boolean | true | 防止滚动穿透,全屏加载模式有效 | N +showOverlay | Boolean | true | 是否需要遮罩层,遮罩层对包裹元素才有效 | N +size | String | medium | 尺寸,示例:small/medium/large/12px/56px/0.3em | N +text | TNode | - | 加载提示文案。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N +zIndex | Number | - | 消息通知层级,样式默认为 3500 | N + +### loading 或 LoadingPlugin + +这是一个插件函数,参数形式为顺序参数(形如:(a, b, c)),而非对象参数(形如:`({ a, b, c })`)。顺序参数如下, + +参数名称 | 参数类型 | 参数默认值 | 参数说明 +-- | -- | -- | -- +options | Function | - | 必需。TS 类型:`boolean \| TdLoadingProps` + +插件返回值:`LoadingInstance【interface LoadingInstance { hide: () => void }】` \ No newline at end of file diff --git a/src/loading/_example/attach.tsx b/src/loading/_example/attach.tsx new file mode 100644 index 0000000..8702675 --- /dev/null +++ b/src/loading/_example/attach.tsx @@ -0,0 +1,29 @@ +import 'tdesign-web-components/loading'; + +import { Component } from 'omi'; + +export default class LoadingDelay extends Component { + loading = true; + + attach = null; + + installed(): void { + this.attach = this.shadowRoot.querySelector('#loading-service'); + this.update(); + } + + render() { + const { loading } = this; + return ( + <> +
+ 我是attach的容器 +
+ + + ); + } +} diff --git a/src/loading/_example/base.tsx b/src/loading/_example/base.tsx new file mode 100644 index 0000000..9d62c65 --- /dev/null +++ b/src/loading/_example/base.tsx @@ -0,0 +1,5 @@ +import 'tdesign-web-components/loading'; + +export default function BaseLoadingExample() { + return ; +} diff --git a/src/loading/_example/delay.tsx b/src/loading/_example/delay.tsx new file mode 100644 index 0000000..5ccfadd --- /dev/null +++ b/src/loading/_example/delay.tsx @@ -0,0 +1,54 @@ +import 'tdesign-web-components/loading'; +import 'tdesign-web-components/space'; +import 'tdesign-web-components/button'; + +import { Component } from 'omi'; + +export default class LoadingDelay extends Component { + data = ''; + + loading = false; + + loadingData = (time?: number) => { + this.loading = true; + this.data = ''; + this.update(); + const timer = setTimeout(() => { + this.loading = false; + this.data = '数据加载完成,短时间的数据加载并未出现 loading'; + clearTimeout(timer); + this.update(); + }, time || 100); + }; + + install() { + this.loadingData(); + } + + render() { + const { loading, data, loadingData } = this; + return ( + +
+ + {data ?
{`loading 作为独立元素:${data}`}
: null} +
+ +
+ + {
{data ? `loading 作为包裹元素:${data}` : ''}
} +
+
+ + + + 快速重新加载数据(无loading) + + loadingData(2000000)} size="small"> + 慢速重新加载数据 + + +
+ ); + } +} diff --git a/src/loading/_example/fullscreen.tsx b/src/loading/_example/fullscreen.tsx new file mode 100644 index 0000000..14a8810 --- /dev/null +++ b/src/loading/_example/fullscreen.tsx @@ -0,0 +1,34 @@ +import 'tdesign-web-components/loading'; +import 'tdesign-web-components/switch'; + +import { Component } from 'omi'; + +export default class LoadingFullScreen extends Component { + checked = false; + + loading = false; + + onChange = (value: boolean) => { + this.checked = value; + this.loading = value; + + if (value) { + setTimeout(() => { + this.checked = false; + this.loading = false; + this.update(); + }, 2000); + } + this.update(); + }; + + render() { + return ( + <> + + Loading state: {this.checked} + + + ); + } +} diff --git a/src/loading/_example/icon-text.tsx b/src/loading/_example/icon-text.tsx new file mode 100644 index 0000000..f0b09f2 --- /dev/null +++ b/src/loading/_example/icon-text.tsx @@ -0,0 +1,5 @@ +import 'tdesign-web-components/loading'; + +export default function IconTextExample() { + return ; +} diff --git a/src/loading/_example/service.tsx b/src/loading/_example/service.tsx new file mode 100644 index 0000000..287d1c1 --- /dev/null +++ b/src/loading/_example/service.tsx @@ -0,0 +1,39 @@ +import 'tdesign-web-components/button'; +import '../../button'; + +import { Component } from 'omi'; +import { loading } from 'tdesign-web-components/loading'; + +export default class LoadingService extends Component { + // static isLightDOM = true + handleFullscreen = () => { + const loadInstance = loading(true); + + setTimeout(() => loadInstance.hide(), 1000); + }; + + handleAttach = () => { + const loadInstance = loading({ attach: () => this.shadowRoot.querySelector('#loading-service') }); + setTimeout(() => loadInstance.hide(), 1000); + }; + + render() { + return ( + <> +
+ 我是service的容器 +
+ +
+ + 服务加载方式(全屏) + + 服务加载方式(局部) +
+ + ); + } +} diff --git a/src/loading/_example/size.tsx b/src/loading/_example/size.tsx new file mode 100644 index 0000000..7390f7d --- /dev/null +++ b/src/loading/_example/size.tsx @@ -0,0 +1,12 @@ +import 'tdesign-web-components/loading'; +import 'tdesign-web-components/space'; + +export default function LoadingSize() { + return ( + + + + + + ); +} diff --git a/src/loading/_example/text.tsx b/src/loading/_example/text.tsx new file mode 100644 index 0000000..7f61ff8 --- /dev/null +++ b/src/loading/_example/text.tsx @@ -0,0 +1,5 @@ +import 'tdesign-web-components/loading'; + +export default function TextExample() { + return ; +} diff --git a/src/loading/_example/wrap.tsx b/src/loading/_example/wrap.tsx new file mode 100644 index 0000000..a6cdf1d --- /dev/null +++ b/src/loading/_example/wrap.tsx @@ -0,0 +1,38 @@ +import 'tdesign-web-components/loading'; +import 'tdesign-web-components/space'; +import 'tdesign-web-components/button'; + +import { Component } from 'omi'; + +export default class WrapLoading extends Component { + loading = true; + + setLoading(loading) { + this.loading = loading; + this.update(); + } + + render() { + return ( + +
+ +
this is loading component
+
this is loading component
+
this is loading component
+
this is loading component
+
this is loading component
+
+
+ + this.setLoading(true)}> + 加载中 + + this.setLoading(false)}> + 加载完成 + + +
+ ); + } +} diff --git a/src/loading/gradient.tsx b/src/loading/gradient.tsx new file mode 100644 index 0000000..e139f72 --- /dev/null +++ b/src/loading/gradient.tsx @@ -0,0 +1,40 @@ +import { Component, createRef, tag } from 'omi'; + +import circleAdapter from '../_common/js/loading/circle-adapter'; +import classnames, { getClassPrefix } from '../_util/classname'; + +/** + * Loading组件 渐变部分实现 + */ +@tag('t-loading-gradient') +export default class GradientLoading extends Component { + conicRef = createRef(); + + static isLightDOM = true; + + installed(): void { + // settimeout的目的是为了拿到color的值,getComputedStyle 会拿不到最新的color + setTimeout(() => { + circleAdapter(this.conicRef.current as HTMLElement); + }); + } + + render() { + const classPrefix = getClassPrefix(); + const gradientClass = `${classPrefix}-loading__gradient`; + return ( + + +
+ + + ); + } +} diff --git a/src/loading/index.ts b/src/loading/index.ts new file mode 100644 index 0000000..733d1f9 --- /dev/null +++ b/src/loading/index.ts @@ -0,0 +1,13 @@ +import './style/index.js'; + +import _Loading from './loading'; +import { LoadingPlugin as _LoadingPlugin } from './plugin'; + +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.tsx b/src/loading/loading.tsx new file mode 100644 index 0000000..a54c03b --- /dev/null +++ b/src/loading/loading.tsx @@ -0,0 +1,201 @@ +import './gradient'; +import '../common/portal'; + +import { Component, tag } from 'omi'; + +import classnames, { getClassPrefix } from '../_util/classname'; +import { addClass, canUseDocument, removeClass } from '../_util/dom'; +import { StyledProps, Styles } from '../common'; +import { TdLoadingProps } from './type'; + +export interface LoadingProps extends TdLoadingProps, StyledProps {} + +const loadingDefaultProps: TdLoadingProps = { + delay: 0, + fullscreen: false, + indicator: true, + inheritColor: false, + loading: true, + preventScrollThrough: true, + showOverlay: true, + size: 'medium', +}; + +@tag('t-loading') +export default class Loading extends Component { + static defaultProps = loadingDefaultProps; + + showLoading = false; + + showLoadingFlag = false; + + lockClass = `${getClassPrefix()}-loading--lock`; + + timer: NodeJS.Timeout; + + get calcStyles() { + const { size, zIndex } = this.props; + const styles: Styles = {}; + + if (zIndex !== undefined) { + styles.zIndex = zIndex; + } + + if (!['small', 'medium', 'large'].includes(size)) { + styles.fontSize = size; + } + + return styles; + } + + openLoading() { + const { delay, loading } = this.props; + if (delay && loading) { + clearTimeout(this.timer); + this.timer = setTimeout(() => { + this.showLoading = true; + this.showLoadingFlag = true; + this.update(); + }, delay); + } else { + this.showLoading = true; + this.showLoadingFlag = true; + this.update(); + } + } + + install(): void { + this.openLoading(); + } + + beforeUpdate(): void { + const { preventScrollThrough, fullscreen, loading, delay } = this.props; + if (loading === true && this.showLoadingFlag === false) { + this.openLoading(); + } + if (this.showLoadingFlag === false || loading === false) { + this.showLoading = delay ? false : loading; + } else { + this.showLoadingFlag = false; + } + + if (preventScrollThrough && fullscreen && canUseDocument) { + if (loading) { + addClass(document.body, this.lockClass); + } else { + removeClass(document.body, this.lockClass); + } + } + } + + uninstall(): void { + clearTimeout(this.timer); + removeClass(document.body, this.lockClass); + } + + render(props) { + const { + indicator, + text, + loading, + size, + fullscreen, + showOverlay, + content, + children, + inheritColor, + className, + style, + attach, + } = props; + + const classPrefix = getClassPrefix(); + const name = `${classPrefix}-loading`; + const centerClass = `${classPrefix}-loading--center`; + const inheritColorClass = `${classPrefix}-loading--inherit-color`; + const fullClass = `${classPrefix}-loading--full`; + const fullscreenClass = `${classPrefix}-loading__fullscreen`; + const overlayClass = `${classPrefix}-loading__overlay`; + const relativeClass = `${classPrefix}-loading__parent`; + const textClass = `${classPrefix}-loading__text`; + + const sizeMap = { + large: `${classPrefix}-size-l`, + small: `${classPrefix}-size-s`, + medium: `${classPrefix}-size-m`, + }; + + const baseClasses = classnames( + centerClass, + sizeMap[size], + { + [inheritColorClass]: inheritColor, + }, + className, + ); + + const commonContent = () => { + let renderIndicator = ; + + if (indicator && typeof indicator !== 'boolean') { + renderIndicator = indicator as JSX.Element; + } + return ( + <> + {indicator ? renderIndicator : null} + {text ?
{text}
: null} + + ); + }; + + if (fullscreen) { + return loading ? ( +
+
{commonContent()}
+
+ ) : null; + } + + if (content || children) { + return ( +
+ {content || children} + {this.showLoading ? ( +
+ {commonContent()} +
+ ) : null} +
+ ); + } + + if (attach) { + return ( + + {loading ? ( +
+ {commonContent()} +
+ ) : null} +
+ ); + } + + return loading ? ( +
+ {commonContent()} +
+ ) : null; + } +} diff --git a/src/loading/plugin.tsx b/src/loading/plugin.tsx new file mode 100644 index 0000000..c530afe --- /dev/null +++ b/src/loading/plugin.tsx @@ -0,0 +1,41 @@ +import { render } from 'omi'; + +import Loading 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 | TdLoadingProps) => 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 as TdLoadingProps['attach'], + fullscreen: !attach, + showOverlay: !!attach, + }; + + render(, div); + + container.appendChild(div); + + return { + hide: () => { + // unmount(div) + div.remove(); + }, + }; +}; diff --git a/src/loading/style/index.js b/src/loading/style/index.js new file mode 100644 index 0000000..ebf46fc --- /dev/null +++ b/src/loading/style/index.js @@ -0,0 +1,10 @@ +import { css, globalCSS } from 'omi'; + +// 为了做主题切换 +import styles from '../../_common/style/web/components/loading/_index.less'; + +export const styleSheet = css` + ${styles} +`; + +globalCSS(styleSheet); diff --git a/src/loading/type.ts b/src/loading/type.ts new file mode 100644 index 0000000..5d5a08e --- /dev/null +++ b/src/loading/type.ts @@ -0,0 +1,77 @@ +/* eslint-disable */ + +/** + * 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC + * */ + +import { TNode, AttachNode } from '../common'; + +export interface TdLoadingProps { + /** + * 挂载元素,默认挂载到组件本身所在的位置。数据类型为 String 时,会被当作选择器处理,进行节点查询。示例:'body' 或 () => document.body + * @default '' + */ + attach?: AttachNode; + /** + * 子元素,同 content + */ + children?: TNode; + /** + * 子元素 + */ + content?: TNode; + /** + * 延迟显示加载效果的时间,用于防止请求速度过快引起的加载闪烁,单位:毫秒 + * @default 0 + */ + delay?: number; + /** + * 是否显示为全屏加载 + * @default false + */ + fullscreen?: boolean; + /** + * 加载指示符,值为 true 显示默认指示符,值为 false 则不显示,也可以自定义指示符 + * @default true + */ + indicator?: TNode; + /** + * 是否继承父元素颜色 + * @default false + */ + inheritColor?: boolean; + /** + * 是否处于加载状态 + * @default true + */ + loading?: boolean; + /** + * 防止滚动穿透,全屏加载模式有效 + * @default true + */ + preventScrollThrough?: boolean; + /** + * 是否需要遮罩层,遮罩层对包裹元素才有效 + * @default true + */ + showOverlay?: boolean; + /** + * 尺寸,示例:small/medium/large/12px/56px/0.3em + * @default medium + */ + size?: string; + /** + * 加载提示文案 + */ + text?: TNode; + /** + * 消息通知层级,样式默认为 3500 + */ + zIndex?: number; +} + +export interface LoadingInstance { + hide: () => void; +} + +export type LoadingMethod = (options: boolean | TdLoadingProps) => LoadingInstance;