Skip to content

Commit

Permalink
Merge pull request #2260 from XiaoMi/feature/spin-loading
Browse files Browse the repository at this point in the history
feat(spinner&loading): 增加环形loading组件
  • Loading branch information
solarjoker authored Dec 1, 2022
2 parents de5046e + d847a26 commit b2d3da9
Show file tree
Hide file tree
Showing 12 changed files with 183 additions and 63 deletions.
1 change: 1 addition & 0 deletions packages/ui/hiui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions packages/ui/hiui/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
2 changes: 2 additions & 0 deletions packages/ui/loading/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
90 changes: 37 additions & 53 deletions packages/ui/loading/src/Loading.tsx
Original file line number Diff line number Diff line change
@@ -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')
Expand All @@ -34,47 +28,13 @@ export const Loading = forwardRef<null, LoadingProps>(
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 | DebounceReturn>(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),
Expand All @@ -88,6 +48,26 @@ export const Loading = forwardRef<null, LoadingProps>(
full && `${prefixCls}--full`
)

const defaultIconComponent = (
<div className={`${prefixCls}__icon`}>
<div />
<div />
</div>
)

const getIndicator = useLatestCallback(() => {
if (indicator) {
return indicator
}

switch (type) {
case 'spin':
return <Spinner size={size} />
default:
return defaultIconComponent
}
})

const loadingComponent = (
<CSSTransition
classNames={`${prefixCls}--motion`}
Expand All @@ -97,12 +77,7 @@ export const Loading = forwardRef<null, LoadingProps>(
>
<div ref={ref} role={role} className={cls} {...restProps}>
<div className={`${prefixCls}__mask`} />
<div className={`${prefixCls}__icon-wrapper`}>
<div className={`${prefixCls}__icon`}>
<div />
<div />
</div>
</div>
<div className={`${prefixCls}__icon-wrapper`}>{getIndicator()}</div>
{content ? <span className={`${prefixCls}__content`}>{content}</span> : null}
</div>
</CSSTransition>
Expand Down Expand Up @@ -173,6 +148,15 @@ export interface LoadingProps extends HiBaseHTMLProps<'div'> {
* @private
*/
part?: boolean
/**
* 自定义加载指示符
* @private
*/
indicator?: ReactNode
/**
* loading 效果类型
*/
type?: 'dot' | 'spin'
}

if (__DEV__) {
Expand Down
55 changes: 55 additions & 0 deletions packages/ui/loading/src/use-loading.ts
Original file line number Diff line number Diff line change
@@ -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 | DebounceReturn>(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<typeof useLoading>
1 change: 1 addition & 0 deletions packages/ui/loading/stories/index.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
30 changes: 30 additions & 0 deletions packages/ui/loading/stories/type.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react'
import Loading from '../src'

/**
* @title 设置加载指示符类型
*/
export const Type = () => {
return (
<>
<h1>Type</h1>
<div
className="loading-basic__wrap"
style={{ position: 'relative', width: 500, height: 300 }}
>
<Loading type="spin">
<div
style={{
width: 500,
height: 300,
boxSizing: 'border-box',
background: '#f5f7fa',
padding: 20,
border: '20px solid #5f6a7a',
}}
/>
</Loading>
</div>
</>
)
}
16 changes: 12 additions & 4 deletions packages/ui/spinner/src/Spinner.tsx
Original file line number Diff line number Diff line change
@@ -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')

Expand All @@ -13,10 +13,15 @@ export const Spinner = forwardRef<HTMLElement | null, SpinnerProps>(
{ 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 (
<i ref={ref} className={cls} {...rest} style={{ fontSize: size, ...rest.style }}>
<i
ref={ref}
className={cls}
{...rest}
style={{ ...(typeof size === 'number' ? { fontSize: size } : {}), ...rest.style }}
>
<svg
className={`${prefixCls}__icon`}
viewBox="0 0 18 18"
Expand All @@ -39,7 +44,10 @@ export const Spinner = forwardRef<HTMLElement | null, SpinnerProps>(
)

export interface SpinnerProps extends HiBaseHTMLProps<'i'> {
size?: string
/**
* 自定义尺寸
*/
size?: number | HiBaseSizeEnum
}

if (__DEV__) {
Expand Down
24 changes: 19 additions & 5 deletions packages/ui/spinner/src/styles/spinner.scss
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/spinner/stories/basic.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const Basic = () => {
<>
<h1>Basic</h1>
<div className="spinner-basic__wrap">
<Spinner></Spinner>
<Spinner />
</div>
</>
)
Expand Down
3 changes: 3 additions & 0 deletions packages/ui/spinner/stories/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -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) => <div>{story()}</div>],
}
21 changes: 21 additions & 0 deletions packages/ui/spinner/stories/size.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react'
import Space from '@hi-ui/space'
import Spinner from '../src'

/**
* @title 设置大小
*/
export const Size = () => {
return (
<>
<h1>Size</h1>
<div className="spinner-size__wrap">
<Space size={20}>
<Spinner size={12} />
<Spinner size={24} />
<Spinner size="lg" />
</Space>
</div>
</>
)
}

0 comments on commit b2d3da9

Please sign in to comment.