diff --git a/packages/ui/hiui/package.json b/packages/ui/hiui/package.json index 07c40ddb4..bc63d07dd 100644 --- a/packages/ui/hiui/package.json +++ b/packages/ui/hiui/package.json @@ -92,6 +92,7 @@ "@hi-ui/select": "^4.0.8", "@hi-ui/slider": "^4.0.5", "@hi-ui/space": "^4.0.6", + "@hi-ui/spinner": "^4.0.4", "@hi-ui/stepper": "^4.0.4", "@hi-ui/svg-icon": "^4.0.4", "@hi-ui/switch": "^4.0.6", diff --git a/packages/ui/hiui/src/index.ts b/packages/ui/hiui/src/index.ts index 4e4e3e19f..6f4c44154 100644 --- a/packages/ui/hiui/src/index.ts +++ b/packages/ui/hiui/src/index.ts @@ -152,6 +152,7 @@ export * from '@hi-ui/space' export { default as Space } from '@hi-ui/space' // export * from '@hi-ui/spinner' +// export { default as Spinner } from '@hi-ui/spinner' export * from '@hi-ui/stepper' export { default as Stepper } from '@hi-ui/stepper' diff --git a/packages/ui/loading/package.json b/packages/ui/loading/package.json index 357a938b4..3a2de8ac7 100644 --- a/packages/ui/loading/package.json +++ b/packages/ui/loading/package.json @@ -48,8 +48,10 @@ "@hi-ui/env": "^4.0.1", "@hi-ui/func-utils": "^4.0.1", "@hi-ui/portal": "^4.0.4", + "@hi-ui/spinner": "^4.0.4", "@hi-ui/type-assertion": "^4.0.1", "@hi-ui/use-id": "^4.0.1", + "@hi-ui/use-latest": "^4.0.1", "react-transition-group": "^4.4.2" }, "peerDependencies": { diff --git a/packages/ui/loading/src/Loading.tsx b/packages/ui/loading/src/Loading.tsx index 2d9759e24..fb70ab6de 100644 --- a/packages/ui/loading/src/Loading.tsx +++ b/packages/ui/loading/src/Loading.tsx @@ -1,18 +1,12 @@ -import React, { - useState, - useEffect, - useImperativeHandle, - useCallback, - forwardRef, - useRef, -} from 'react' -import { debounce } from '@hi-ui/func-utils' -import type { DebounceReturn } from '@hi-ui/func-utils' +import React, { useImperativeHandle, forwardRef, ReactNode } from 'react' import { CSSTransition } from 'react-transition-group' import { cx, getPrefixCls } from '@hi-ui/classname' import { __DEV__ } from '@hi-ui/env' import { Portal } from '@hi-ui/portal' import { HiBaseHTMLProps, HiBaseSizeEnum } from '@hi-ui/core' +import { useLatestCallback } from '@hi-ui/use-latest' +import Spinner from '@hi-ui/spinner' +import { useLoading } from './use-loading' const _role = 'loading' export const _prefix = getPrefixCls('loading') @@ -34,47 +28,13 @@ export const Loading = forwardRef( disabledPortal = false, innerRef, timeout = 300, + indicator, + type = 'dot', ...restProps }, ref ) => { - const [internalVisible, setInternalVisible] = useState(false) - - // Real trigger loading update - const updateLoadingStatus = useCallback(() => { - if (internalVisible === visible) return - setInternalVisible(visible) - }, [internalVisible, visible]) - - const prevDebouncedUpdateRef = useRef(null) - - const cancelWaitingLoading = () => { - prevDebouncedUpdateRef.current?.cancel() - } - - const shouldDelay = visible && delay >= 0 - - const debouncedLoadingUpdater = useCallback(() => { - cancelWaitingLoading() - - if (shouldDelay) { - const debouncedUpdateLoading = debounce(updateLoadingStatus, delay) - prevDebouncedUpdateRef.current = debouncedUpdateLoading - - debouncedUpdateLoading() - } else { - updateLoadingStatus() - prevDebouncedUpdateRef.current = null - } - }, [delay, shouldDelay, updateLoadingStatus]) - - useEffect(() => { - debouncedLoadingUpdater() - - return () => { - cancelWaitingLoading() - } - }, [debouncedLoadingUpdater]) + const { internalVisible, setInternalVisible } = useLoading({ visible, delay }) useImperativeHandle(innerRef, () => ({ close: () => setInternalVisible(false), @@ -88,6 +48,26 @@ export const Loading = forwardRef( full && `${prefixCls}--full` ) + const defaultIconComponent = ( +
+
+
+
+ ) + + const getIndicator = useLatestCallback(() => { + if (indicator) { + return indicator + } + + switch (type) { + case 'spin': + return + default: + return defaultIconComponent + } + }) + const loadingComponent = ( ( >
-
-
-
-
-
-
+
{getIndicator()}
{content ? {content} : null}
@@ -173,6 +148,15 @@ export interface LoadingProps extends HiBaseHTMLProps<'div'> { * @private */ part?: boolean + /** + * 自定义加载指示符 + * @private + */ + indicator?: ReactNode + /** + * loading 效果类型 + */ + type?: 'dot' | 'spin' } if (__DEV__) { diff --git a/packages/ui/loading/src/use-loading.ts b/packages/ui/loading/src/use-loading.ts new file mode 100644 index 000000000..ea45f3bc1 --- /dev/null +++ b/packages/ui/loading/src/use-loading.ts @@ -0,0 +1,55 @@ +import { useCallback, useEffect, useRef, useState } from 'react' +import { debounce } from '@hi-ui/func-utils' +import type { DebounceReturn } from '@hi-ui/func-utils' + +export const useLoading = ({ visible = true, delay = -1 }: UseLoadingProps) => { + const [internalVisible, setInternalVisible] = useState(false) + + // Real trigger loading update + const updateLoadingStatus = useCallback(() => { + if (internalVisible === visible) return + setInternalVisible(visible) + }, [internalVisible, visible]) + + const prevDebouncedUpdateRef = useRef(null) + + const cancelWaitingLoading = () => { + prevDebouncedUpdateRef.current?.cancel() + } + + const shouldDelay = visible && delay >= 0 + + const debouncedLoadingUpdater = useCallback(() => { + cancelWaitingLoading() + + if (shouldDelay) { + const debouncedUpdateLoading = debounce(updateLoadingStatus, delay) + prevDebouncedUpdateRef.current = debouncedUpdateLoading + + debouncedUpdateLoading() + } else { + updateLoadingStatus() + prevDebouncedUpdateRef.current = null + } + }, [delay, shouldDelay, updateLoadingStatus]) + + useEffect(() => { + debouncedLoadingUpdater() + + return () => { + cancelWaitingLoading() + } + }, [debouncedLoadingUpdater]) + + return { + internalVisible, + setInternalVisible, + } +} + +export interface UseLoadingProps { + visible?: boolean + delay?: number +} + +export type useInputReturn = ReturnType diff --git a/packages/ui/loading/stories/index.stories.tsx b/packages/ui/loading/stories/index.stories.tsx index 53cf51336..797308757 100644 --- a/packages/ui/loading/stories/index.stories.tsx +++ b/packages/ui/loading/stories/index.stories.tsx @@ -4,6 +4,7 @@ import Loading from '../src' export * from './basic.stories' export * from './duration.stories' export * from './visible.stories' +export * from './type.stories' export default { title: 'FeedBack/Loading', diff --git a/packages/ui/loading/stories/type.stories.tsx b/packages/ui/loading/stories/type.stories.tsx new file mode 100644 index 000000000..1191362a6 --- /dev/null +++ b/packages/ui/loading/stories/type.stories.tsx @@ -0,0 +1,30 @@ +import React from 'react' +import Loading from '../src' + +/** + * @title 设置加载指示符类型 + */ +export const Type = () => { + return ( + <> +

Type

+
+ +
+ +
+ + ) +} diff --git a/packages/ui/spinner/src/Spinner.tsx b/packages/ui/spinner/src/Spinner.tsx index 26ee76279..7c0f0aee7 100644 --- a/packages/ui/spinner/src/Spinner.tsx +++ b/packages/ui/spinner/src/Spinner.tsx @@ -1,7 +1,7 @@ import React, { forwardRef } from 'react' import { cx, getPrefixCls } from '@hi-ui/classname' import { __DEV__ } from '@hi-ui/env' -import { HiBaseHTMLProps } from '@hi-ui/core' +import { HiBaseHTMLProps, HiBaseSizeEnum } from '@hi-ui/core' const SPINNER_PREFIX = getPrefixCls('spinner') @@ -13,10 +13,15 @@ export const Spinner = forwardRef( { prefixCls = SPINNER_PREFIX, role = 'spinner', className, children, size = 14, ...rest }, ref ) => { - const cls = cx(prefixCls, className) + const cls = cx(prefixCls, className, typeof size === 'string' && prefixCls + `--size-${size}`) return ( - + ( ) export interface SpinnerProps extends HiBaseHTMLProps<'i'> { - size?: string + /** + * 自定义尺寸 + */ + size?: number | HiBaseSizeEnum } if (__DEV__) { diff --git a/packages/ui/spinner/src/styles/spinner.scss b/packages/ui/spinner/src/styles/spinner.scss index 19306b24d..d952c28f0 100644 --- a/packages/ui/spinner/src/styles/spinner.scss +++ b/packages/ui/spinner/src/styles/spinner.scss @@ -1,13 +1,27 @@ -@import '~@hi-ui/core-css/lib/index.scss'; +@import "~@hi-ui/core-css/lib/index.scss"; -$prefix: '#{$component-prefix}-spinner' !default; +$prefix: "#{$component-prefix}-spinner" !default; .#{$prefix} { width: 1em; height: 1em; display: inline-block; cursor: default; - color: use-color-mode('primary'); + color: use-color-mode("primary"); + + &--size { + &-sm { + font-size: use-height-size(4); + } + + &-md { + font-size: use-height-size(6); + } + + &-lg { + font-size: use-height-size(9); + } + } svg.#{$prefix}__icon { @keyframes #{$component-prefix}-rotate { @@ -16,8 +30,8 @@ $prefix: '#{$component-prefix}-spinner' !default; } } - width: 0.8rem; - height: 0.8rem; + width: 1em; + height: 1em; stroke: none; fill: currentColor; animation-name: #{$component-prefix}-rotate; diff --git a/packages/ui/spinner/stories/basic.stories.tsx b/packages/ui/spinner/stories/basic.stories.tsx index eb2df3fe9..5ccc6d3de 100644 --- a/packages/ui/spinner/stories/basic.stories.tsx +++ b/packages/ui/spinner/stories/basic.stories.tsx @@ -9,7 +9,7 @@ export const Basic = () => { <>

Basic

- +
) diff --git a/packages/ui/spinner/stories/index.stories.tsx b/packages/ui/spinner/stories/index.stories.tsx index 17fab6cda..13dd57af6 100644 --- a/packages/ui/spinner/stories/index.stories.tsx +++ b/packages/ui/spinner/stories/index.stories.tsx @@ -1,8 +1,11 @@ import React from 'react' +import Spinner from '../src' export * from './basic.stories' +export * from './size.stories' export default { title: 'Private(暂不对外)/Spinner', + component: Spinner, decorators: [(story: Function) =>
{story()}
], } diff --git a/packages/ui/spinner/stories/size.stories.tsx b/packages/ui/spinner/stories/size.stories.tsx new file mode 100644 index 000000000..ad711d269 --- /dev/null +++ b/packages/ui/spinner/stories/size.stories.tsx @@ -0,0 +1,21 @@ +import React from 'react' +import Space from '@hi-ui/space' +import Spinner from '../src' + +/** + * @title 设置大小 + */ +export const Size = () => { + return ( + <> +

Size

+
+ + + + + +
+ + ) +}