refactor: spin (#6222)

* fix: typo (#6218)

* fix:  typo

* docs<upload>: docs update

* refactor: spin

* refactor: spin

* refactor: spin

* refactor: spinnn

* refactor: spin

---------

Co-authored-by: lyn <76365499@qq.com>
pull/6226/head
Zev Zhu 2 years ago committed by GitHub
parent 34373ca05d
commit 7939eb1718
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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;
if (delay) {
this.cancelExistingSpin();
this.updateSpinning = debounce(this.originalUpdateSpinning, delay);
}
},
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;
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;
}
if (Array.isArray(indicator)) {
indicator = indicator.length === 1 ? indicator[0] : indicator;
}
if (isVNode(indicator)) {
return cloneVNode(indicator, { class: dotClassName });
}
function cancelExistingSpin() {
if (updateSpinning && updateSpinning.cancel) {
updateSpinning.cancel();
}
if (defaultIndicator && isVNode(defaultIndicator())) {
return cloneVNode(defaultIndicator(), { class: dotClassName });
}
function debouncifyUpdateSpinning() {
const { delay } = props;
if (delay) {
cancelExistingSpin();
updateSpinning = debounce(originalUpdateSpinning, delay);
} else {
updateSpinning = originalUpdateSpinning;
}
return (
<span class={`${dotClassName} ${prefixCls}-dot-spin`}>
<i class={`${prefixCls}-dot-item`} />
<i class={`${prefixCls}-dot-item`} />
<i class={`${prefixCls}-dot-item`} />
<i class={`${prefixCls}-dot-item`} />
</span>
);
},
},
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 = (
<div
{...divProps}
style={style}
class={spinClassName}
aria-live="polite"
aria-busy={sSpinning}
>
{this.renderIndicator(prefixCls)}
{tip ? <div class={`${prefixCls}-text`}>{tip}</div> : null}
</div>
}
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 (
<div class={[`${prefixCls}-nested-loading`, wrapperClassName]}>
{sSpinning && <div key="loading">{spinElement}</div>}
<div class={containerClassName} key="container">
{children}
</div>
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 (
<span class={`${dotClassName} ${prefixCls}-dot-spin`}>
<i class={`${prefixCls}-dot-item`} />
<i class={`${prefixCls}-dot-item`} />
<i class={`${prefixCls}-dot-item`} />
<i class={`${prefixCls}-dot-item`} />
</span>
);
}
const spinElement = (
<div {...divProps} class={spinClassName} aria-live="polite" aria-busy={sSpinning.value}>
{renderIndicator(prefixCls.value)}
{tip ? <div class={`${prefixCls.value}-text`}>{tip}</div> : null}
</div>
);
}
return spinElement;
if (children && filterEmpty(children).length) {
const containerClassName = {
[`${prefixCls.value}-container`]: true,
[`${prefixCls.value}-blur`]: sSpinning.value,
};
return wrapSSR(
<div class={[`${prefixCls.value}-nested-loading`, props.wrapperClassName, hashId.value]}>
{sSpinning.value && <div key="loading">{spinElement}</div>}
<div class={containerClassName} key="container">
{children}
</div>
</div>,
);
}
return wrapSSR(spinElement);
};
},
});

@ -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';

@ -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<SpinToken> = (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<SpinToken>(token, {
spinDotDefault: token.colorTextDescription,
spinDotSize: token.controlHeightLG / 2,
spinDotSizeSM: token.controlHeightLG * 0.35,
spinDotSizeLG: token.controlHeight,
});
return [genSpinStyle(spinToken)];
},
{
contentHeight: 400,
},
);

@ -1,2 +0,0 @@
import '../../style/index.less';
import './index.less';

@ -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);
}
}

@ -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';

@ -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;

Loading…
Cancel
Save