From 7939eb17185cced99cf150dd9b6da5a9e8a712c9 Mon Sep 17 00:00:00 2001 From: Zev Zhu <45655660+aibayanyu20@users.noreply.github.com> Date: Wed, 1 Feb 2023 17:17:56 +0800 Subject: [PATCH] refactor: spin (#6222) * fix: typo (#6218) * fix: typo * docs: docs update * refactor: spin * refactor: spin * refactor: spin * refactor: spinnn * refactor: spin --------- Co-authored-by: lyn <76365499@qq.com> --- components/spin/Spin.tsx | 219 +++++++++----------- components/spin/style/index.less | 218 -------------------- components/spin/style/index.ts | 241 +++++++++++++++++++++++ components/spin/style/index.tsx | 2 - components/spin/style/rtl.less | 20 -- components/style.ts | 2 +- components/theme/interface/components.ts | 4 +- 7 files changed, 339 insertions(+), 367 deletions(-) delete mode 100644 components/spin/style/index.less create mode 100644 components/spin/style/index.ts delete mode 100644 components/spin/style/index.tsx delete mode 100644 components/spin/style/rtl.less diff --git a/components/spin/Spin.tsx b/components/spin/Spin.tsx index f0500f973..379e3c427 100644 --- a/components/spin/Spin.tsx +++ b/components/spin/Spin.tsx @@ -1,10 +1,11 @@ import type { VNode, ExtractPropTypes, PropType } from 'vue'; -import { inject, cloneVNode, isVNode, defineComponent, nextTick } from 'vue'; +import { cloneVNode, isVNode, defineComponent, shallowRef, watch } from 'vue'; import debounce from 'lodash-es/debounce'; import PropTypes from '../_util/vue-types'; -import { getComponent, getSlot } from '../_util/props-util'; +import { filterEmpty, getPropsSlot } from '../_util/props-util'; import initDefaultProps from '../_util/props-util/initDefaultProps'; -import { defaultConfigProvider, configProviderKey } from '../config-provider/context'; +import useStyle from './style'; +import useConfigInject from '../config-provider/hooks/useConfigInject'; export type SpinSize = 'small' | 'default' | 'large'; export const spinProps = () => ({ @@ -40,133 +41,103 @@ export default defineComponent({ spinning: true, wrapperClassName: '', }), - setup() { - return { - originalUpdateSpinning: null, - configProvider: inject(configProviderKey, defaultConfigProvider), - }; - }, - data() { - const { spinning, delay } = this; - const shouldBeDelayed = shouldDelay(spinning, delay); - return { - sSpinning: spinning && !shouldBeDelayed, - }; - }, - created() { - this.originalUpdateSpinning = this.updateSpinning; - this.debouncifyUpdateSpinning(this.$props); - }, - mounted() { - this.updateSpinning(); - }, - updated() { - nextTick(() => { - this.debouncifyUpdateSpinning(); - this.updateSpinning(); - }); - }, - beforeUnmount() { - this.cancelExistingSpin(); - }, - methods: { - debouncifyUpdateSpinning(props?: any) { - const { delay } = props || this.$props; + setup(props, { attrs, slots }) { + const { prefixCls, size, direction } = useConfigInject('spin', props); + const [wrapSSR, hashId] = useStyle(prefixCls); + const sSpinning = shallowRef(props.spinning && shouldDelay(props.spinning, props.delay)); + let updateSpinning: any; + function originalUpdateSpinning() { + if (sSpinning.value !== props.spinning) { + sSpinning.value = props.spinning; + } + } + function cancelExistingSpin() { + if (updateSpinning && updateSpinning.cancel) { + updateSpinning.cancel(); + } + } + function debouncifyUpdateSpinning() { + const { delay } = props; if (delay) { - this.cancelExistingSpin(); - this.updateSpinning = debounce(this.originalUpdateSpinning, delay); + cancelExistingSpin(); + updateSpinning = debounce(originalUpdateSpinning, delay); + } else { + updateSpinning = originalUpdateSpinning; } - }, - updateSpinning() { - const { spinning, sSpinning } = this; - if (sSpinning !== spinning) { - this.sSpinning = spinning; - } - }, - cancelExistingSpin() { - const { updateSpinning } = this; - if (updateSpinning && (updateSpinning as any).cancel) { - (updateSpinning as any).cancel(); - } - }, - renderIndicator(prefixCls: string) { - const dotClassName = `${prefixCls}-dot`; - let indicator = getComponent(this, 'indicator'); - // should not be render default indicator when indicator value is null - if (indicator === null) { - return null; - } - if (Array.isArray(indicator)) { - indicator = indicator.length === 1 ? indicator[0] : indicator; - } - if (isVNode(indicator)) { - return cloneVNode(indicator, { class: dotClassName }); - } - - if (defaultIndicator && isVNode(defaultIndicator())) { - return cloneVNode(defaultIndicator(), { class: dotClassName }); - } - - return ( - - - - - - - ); - }, - }, - render() { - const { - size, - prefixCls: customizePrefixCls, - tip = this.$slots.tip?.(), - wrapperClassName, - } = this.$props; - const { class: cls, style, ...divProps } = this.$attrs; - const { getPrefixCls, direction } = this.configProvider; - const prefixCls = getPrefixCls('spin', customizePrefixCls); - - const { sSpinning } = this; - const spinClassName = { - [prefixCls]: true, - [`${prefixCls}-sm`]: size === 'small', - [`${prefixCls}-lg`]: size === 'large', - [`${prefixCls}-spinning`]: sSpinning, - [`${prefixCls}-show-text`]: !!tip, - [`${prefixCls}-rtl`]: direction === 'rtl', - [cls as string]: !!cls, - }; - - const spinElement = ( -
- {this.renderIndicator(prefixCls)} - {tip ?
{tip}
: null} -
+ } + watch( + () => [props.spinning, props.delay], + () => { + debouncifyUpdateSpinning(); + updateSpinning?.(); + }, + { + immediate: true, + }, ); - const children = getSlot(this); - if (children && children.length) { - const containerClassName = { - [`${prefixCls}-container`]: true, - [`${prefixCls}-blur`]: sSpinning, + return () => { + const { class: cls, ...divProps } = attrs; + const { tip = slots.tip?.() } = props; + const children = slots.default?.(); + const spinClassName = { + [hashId.value]: true, + [prefixCls.value]: true, + [`${prefixCls.value}-sm`]: size.value === 'small', + [`${prefixCls.value}-lg`]: size.value === 'large', + [`${prefixCls.value}-spinning`]: sSpinning.value, + [`${prefixCls.value}-show-text`]: !!tip, + [`${prefixCls.value}-rtl`]: direction.value === 'rtl', + [cls as string]: !!cls, }; - return ( -
- {sSpinning &&
{spinElement}
} -
- {children} -
+ function renderIndicator(prefixCls: string) { + const dotClassName = `${prefixCls}-dot`; + let indicator = getPropsSlot(slots, props, 'indicator'); + // should not be render default indicator when indicator value is null + if (indicator === null) { + return null; + } + if (Array.isArray(indicator)) { + indicator = indicator.length === 1 ? indicator[0] : indicator; + } + if (isVNode(indicator)) { + return cloneVNode(indicator, { class: dotClassName }); + } + + if (defaultIndicator && isVNode(defaultIndicator())) { + return cloneVNode(defaultIndicator(), { class: dotClassName }); + } + + return ( + + + + + + + ); + } + const spinElement = ( +
+ {renderIndicator(prefixCls.value)} + {tip ?
{tip}
: null}
); - } - return spinElement; + if (children && filterEmpty(children).length) { + const containerClassName = { + [`${prefixCls.value}-container`]: true, + [`${prefixCls.value}-blur`]: sSpinning.value, + }; + return wrapSSR( +
+ {sSpinning.value &&
{spinElement}
} +
+ {children} +
+
, + ); + } + return wrapSSR(spinElement); + }; }, }); diff --git a/components/spin/style/index.less b/components/spin/style/index.less deleted file mode 100644 index 2dee02e28..000000000 --- a/components/spin/style/index.less +++ /dev/null @@ -1,218 +0,0 @@ -@import '../../style/themes/index'; -@import '../../style/mixins/index'; - -@spin-prefix-cls: ~'@{ant-prefix}-spin'; -@spin-dot-default: @text-color-secondary; - -.@{spin-prefix-cls} { - .reset-component(); - - position: absolute; - display: none; - color: @primary-color; - text-align: center; - vertical-align: middle; - opacity: 0; - transition: transform 0.3s @ease-in-out-circ; - - &-spinning { - position: static; - display: inline-block; - opacity: 1; - } - - &-nested-loading { - position: relative; - > div > .@{spin-prefix-cls} { - position: absolute; - top: 0; - left: 0; - z-index: 4; - display: block; - width: 100%; - height: 100%; - max-height: 400px; - .@{spin-prefix-cls}-dot { - position: absolute; - top: 50%; - left: 50%; - margin: -(@spin-dot-size / 2); - } - .@{spin-prefix-cls}-text { - position: absolute; - top: 50%; - width: 100%; - padding-top: ((@spin-dot-size - @font-size-base) / 2) + 2px; - text-shadow: 0 1px 2px @shadow-color-inverse; - } - &.@{spin-prefix-cls}-show-text .@{spin-prefix-cls}-dot { - margin-top: -(@spin-dot-size / 2) - 10px; - } - } - - > div > .@{spin-prefix-cls}-sm { - .@{spin-prefix-cls}-dot { - margin: -(@spin-dot-size-sm / 2); - } - .@{spin-prefix-cls}-text { - padding-top: ((@spin-dot-size-sm - @font-size-base) / 2) + 2px; - } - &.@{spin-prefix-cls}-show-text .@{spin-prefix-cls}-dot { - margin-top: -(@spin-dot-size-sm / 2) - 10px; - } - } - - > div > .@{spin-prefix-cls}-lg { - .@{spin-prefix-cls}-dot { - margin: -(@spin-dot-size-lg / 2); - } - .@{spin-prefix-cls}-text { - padding-top: ((@spin-dot-size-lg - @font-size-base) / 2) + 2px; - } - &.@{spin-prefix-cls}-show-text .@{spin-prefix-cls}-dot { - margin-top: -(@spin-dot-size-lg / 2) - 10px; - } - } - } - - &-container { - position: relative; - transition: opacity 0.3s; - - &::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 10; - display: ~'none \9'; - width: 100%; - height: 100%; - background: @component-background; - opacity: 0; - transition: all 0.3s; - content: ''; - pointer-events: none; - } - } - - &-blur { - clear: both; - opacity: 0.5; - user-select: none; - pointer-events: none; - - &::after { - opacity: 0.4; - pointer-events: auto; - } - } - - // tip - // ------------------------------ - &-tip { - color: @spin-dot-default; - } - - // dots - // ------------------------------ - - &-dot { - position: relative; - display: inline-block; - font-size: @spin-dot-size; - - .square(1em); - - &-item { - position: absolute; - display: block; - width: 9px; - height: 9px; - background-color: @primary-color; - border-radius: 100%; - transform: scale(0.75); - transform-origin: 50% 50%; - opacity: 0.3; - animation: antSpinMove 1s infinite linear alternate; - - &:nth-child(1) { - top: 0; - left: 0; - } - - &:nth-child(2) { - top: 0; - right: 0; - animation-delay: 0.4s; - } - - &:nth-child(3) { - right: 0; - bottom: 0; - animation-delay: 0.8s; - } - - &:nth-child(4) { - bottom: 0; - left: 0; - animation-delay: 1.2s; - } - } - - &-spin { - transform: rotate(0deg); - animation: antRotate 1.2s infinite linear; - } - } - - // Sizes - // ------------------------------ - - // small - &-sm &-dot { - font-size: @spin-dot-size-sm; - - i { - width: 6px; - height: 6px; - } - } - - // large - &-lg &-dot { - font-size: @spin-dot-size-lg; - - i { - width: 14px; - height: 14px; - } - } - - &&-show-text &-text { - display: block; - } -} - -@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) { - /* IE10+ */ - .@{spin-prefix-cls}-blur { - background: @component-background; - opacity: 0.5; - } -} - -@keyframes antSpinMove { - to { - opacity: 1; - } -} - -@keyframes antRotate { - to { - transform: rotate(360deg); - } -} - -@import './rtl'; diff --git a/components/spin/style/index.ts b/components/spin/style/index.ts new file mode 100644 index 000000000..52187ca7d --- /dev/null +++ b/components/spin/style/index.ts @@ -0,0 +1,241 @@ +import type { CSSObject } from '../../_util/cssinjs'; +import { Keyframes } from '../../_util/cssinjs'; +import type { FullToken, GenerateStyle } from '../../theme/internal'; +import { genComponentStyleHook, mergeToken } from '../../theme/internal'; +import { resetComponent } from '../../_style'; + +export interface ComponentToken { + contentHeight: number; +} + +interface SpinToken extends FullToken<'Spin'> { + spinDotDefault: string; + spinDotSize: number; + spinDotSizeSM: number; + spinDotSizeLG: number; +} + +const antSpinMove = new Keyframes('antSpinMove', { + to: { opacity: 1 }, +}); + +const antRotate = new Keyframes('antRotate', { + to: { transform: 'rotate(405deg)' }, +}); + +const genSpinStyle: GenerateStyle = (token: SpinToken): CSSObject => ({ + [`${token.componentCls}`]: { + ...resetComponent(token), + position: 'absolute', + display: 'none', + color: token.colorPrimary, + textAlign: 'center', + verticalAlign: 'middle', + opacity: 0, + transition: `transform ${token.motionDurationSlow} ${token.motionEaseInOutCirc}`, + + '&-spinning': { + position: 'static', + display: 'inline-block', + opacity: 1, + }, + + '&-nested-loading': { + position: 'relative', + [`> div > ${token.componentCls}`]: { + position: 'absolute', + top: 0, + insetInlineStart: 0, + zIndex: 4, + display: 'block', + width: '100%', + height: '100%', + maxHeight: token.contentHeight, + + [`${token.componentCls}-dot`]: { + position: 'absolute', + top: '50%', + insetInlineStart: '50%', + margin: -token.spinDotSize / 2, + }, + + [`${token.componentCls}-text`]: { + position: 'absolute', + top: '50%', + width: '100%', + paddingTop: (token.spinDotSize - token.fontSize) / 2 + 2, + textShadow: `0 1px 2px ${token.colorBgContainer}`, // FIXME: shadow + }, + + [`&${token.componentCls}-show-text ${token.componentCls}-dot`]: { + marginTop: -(token.spinDotSize / 2) - 10, + }, + + '&-sm': { + [`${token.componentCls}-dot`]: { + margin: -token.spinDotSizeSM / 2, + }, + [`${token.componentCls}-text`]: { + paddingTop: (token.spinDotSizeSM - token.fontSize) / 2 + 2, + }, + [`&${token.componentCls}-show-text ${token.componentCls}-dot`]: { + marginTop: -(token.spinDotSizeSM / 2) - 10, + }, + }, + + '&-lg': { + [`${token.componentCls}-dot`]: { + margin: -(token.spinDotSizeLG / 2), + }, + [`${token.componentCls}-text`]: { + paddingTop: (token.spinDotSizeLG - token.fontSize) / 2 + 2, + }, + [`&${token.componentCls}-show-text ${token.componentCls}-dot`]: { + marginTop: -(token.spinDotSizeLG / 2) - 10, + }, + }, + }, + + [`${token.componentCls}-container`]: { + position: 'relative', + transition: `opacity ${token.motionDurationSlow}`, + + '&::after': { + position: 'absolute', + top: 0, + insetInlineEnd: 0, + bottom: 0, + insetInlineStart: 0, + zIndex: 10, + width: '100%', + height: '100%', + background: token.colorBgContainer, + opacity: 0, + transition: `all ${token.motionDurationSlow}`, + content: '""', + pointerEvents: 'none', + }, + }, + + [`${token.componentCls}-blur`]: { + clear: 'both', + opacity: 0.5, + userSelect: 'none', + pointerEvents: 'none', + + [`&::after`]: { + opacity: 0.4, + pointerEvents: 'auto', + }, + }, + }, + + // tip + // ------------------------------ + [`&-tip`]: { + color: token.spinDotDefault, + }, + + // dots + // ------------------------------ + [`${token.componentCls}-dot`]: { + position: 'relative', + display: 'inline-block', + fontSize: token.spinDotSize, + width: '1em', + height: '1em', + + '&-item': { + position: 'absolute', + display: 'block', + width: (token.spinDotSize - token.marginXXS / 2) / 2, + height: (token.spinDotSize - token.marginXXS / 2) / 2, + backgroundColor: token.colorPrimary, + borderRadius: '100%', + transform: 'scale(0.75)', + transformOrigin: '50% 50%', + opacity: 0.3, + animationName: antSpinMove, + animationDuration: '1s', + animationIterationCount: 'infinite', + animationTimingFunction: 'linear', + animationDirection: 'alternate', + + '&:nth-child(1)': { + top: 0, + insetInlineStart: 0, + }, + + '&:nth-child(2)': { + top: 0, + insetInlineEnd: 0, + animationDelay: '0.4s', + }, + + '&:nth-child(3)': { + insetInlineEnd: 0, + bottom: 0, + animationDelay: '0.8s', + }, + + '&:nth-child(4)': { + bottom: 0, + insetInlineStart: 0, + animationDelay: '1.2s', + }, + }, + + '&-spin': { + transform: 'rotate(45deg)', + animationName: antRotate, + animationDuration: '1.2s', + animationIterationCount: 'infinite', + animationTimingFunction: 'linear', + }, + }, + + // Sizes + // ------------------------------ + + // small + [`&-sm ${token.componentCls}-dot`]: { + fontSize: token.spinDotSizeSM, + + i: { + width: (token.spinDotSizeSM - token.marginXXS / 2) / 2, + height: (token.spinDotSizeSM - token.marginXXS / 2) / 2, + }, + }, + + // large + [`&-lg ${token.componentCls}-dot`]: { + fontSize: token.spinDotSizeLG, + + i: { + width: (token.spinDotSizeLG - token.marginXXS) / 2, + height: (token.spinDotSizeLG - token.marginXXS) / 2, + }, + }, + + [`&${token.componentCls}-show-text ${token.componentCls}-text`]: { + display: 'block', + }, + }, +}); + +// ============================== Export ============================== +export default genComponentStyleHook( + 'Spin', + token => { + const spinToken = mergeToken(token, { + spinDotDefault: token.colorTextDescription, + spinDotSize: token.controlHeightLG / 2, + spinDotSizeSM: token.controlHeightLG * 0.35, + spinDotSizeLG: token.controlHeight, + }); + return [genSpinStyle(spinToken)]; + }, + { + contentHeight: 400, + }, +); diff --git a/components/spin/style/index.tsx b/components/spin/style/index.tsx deleted file mode 100644 index 3a3ab0de5..000000000 --- a/components/spin/style/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -import '../../style/index.less'; -import './index.less'; diff --git a/components/spin/style/rtl.less b/components/spin/style/rtl.less deleted file mode 100644 index 03fb9b257..000000000 --- a/components/spin/style/rtl.less +++ /dev/null @@ -1,20 +0,0 @@ -.@{spin-prefix-cls} { - &-rtl { - direction: rtl; - } - - &-dot { - &-spin { - .@{spin-prefix-cls}-rtl & { - transform: rotate(-45deg); - animation-name: antRotateRtl; - } - } - } -} - -@keyframes antRotateRtl { - to { - transform: rotate(-405deg); - } -} diff --git a/components/style.ts b/components/style.ts index 6bfc80e3c..606436b77 100644 --- a/components/style.ts +++ b/components/style.ts @@ -22,7 +22,7 @@ import './collapse/style'; import './carousel/style'; // import './notification/style'; // import './message/style'; -import './spin/style'; +// import './spin/style'; import './select/style'; import './switch/style'; import './auto-complete/style'; diff --git a/components/theme/interface/components.ts b/components/theme/interface/components.ts index f285f04db..cb19bb379 100644 --- a/components/theme/interface/components.ts +++ b/components/theme/interface/components.ts @@ -35,7 +35,7 @@ import type { ComponentToken as NotificationComponentToken } from '../../notific // import type { ComponentToken as SkeletonComponentToken } from '../../skeleton/style'; // import type { ComponentToken as SliderComponentToken } from '../../slider/style'; // import type { ComponentToken as SpaceComponentToken } from '../../space/style'; -// import type { ComponentToken as SpinComponentToken } from '../../spin/style'; +import type { ComponentToken as SpinComponentToken } from '../../spin/style'; // import type { ComponentToken as StepsComponentToken } from '../../steps/style'; // import type { ComponentToken as TableComponentToken } from '../../table/style'; // import type { ComponentToken as TabsComponentToken } from '../../tabs/style'; @@ -90,7 +90,7 @@ export interface ComponentTokenMap { // Select?: SelectComponentToken; // Skeleton?: SkeletonComponentToken; // Slider?: SliderComponentToken; - // Spin?: SpinComponentToken; + Spin?: SpinComponentToken; Statistic?: {}; Switch?: {}; // Tag?: TagComponentToken;