refactor: skeleton
parent
5096ee4d70
commit
c8898828d6
|
@ -2,7 +2,7 @@ import { App } from 'vue';
|
|||
import Avatar from './Avatar';
|
||||
import Group from './Group';
|
||||
|
||||
export { AvatarProps, AvatarSize } from './Avatar';
|
||||
export { AvatarProps, AvatarSize, avatarProps } from './Avatar';
|
||||
export { AvatarGroupProps } from './Group';
|
||||
|
||||
Avatar.Group = Group;
|
||||
|
|
|
@ -1,50 +1,40 @@
|
|||
import { defineComponent, ExtractPropTypes } from 'vue';
|
||||
import { computed, defineComponent } from 'vue';
|
||||
import classNames from '../_util/classNames';
|
||||
import PropTypes from '../_util/vue-types';
|
||||
import { tuple } from '../_util/type';
|
||||
import initDefaultProps from '../_util/props-util/initDefaultProps';
|
||||
import useConfigInject from '../_util/hooks/useConfigInject';
|
||||
import Element, { skeletonElementProps, SkeletonElementProps } from './Element';
|
||||
|
||||
const skeletonAvatarProps = {
|
||||
prefixCls: PropTypes.string,
|
||||
size: PropTypes.oneOfType([
|
||||
PropTypes.oneOf(tuple('large', 'small', 'default')),
|
||||
PropTypes.number,
|
||||
]),
|
||||
shape: PropTypes.oneOf(tuple('circle', 'square')),
|
||||
};
|
||||
export interface AvatarProps extends Omit<SkeletonElementProps, 'shape'> {
|
||||
shape?: 'circle' | 'square';
|
||||
}
|
||||
|
||||
export const SkeletonAvatarProps = PropTypes.shape(skeletonAvatarProps).loose;
|
||||
|
||||
export type ISkeletonAvatarProps = Partial<ExtractPropTypes<typeof skeletonAvatarProps>>;
|
||||
|
||||
const Avatar = defineComponent({
|
||||
props: initDefaultProps(skeletonAvatarProps, {
|
||||
export const avatarProps = initDefaultProps(
|
||||
{ ...skeletonElementProps(), shape: PropTypes.oneOf(tuple('circle', 'square')) },
|
||||
{
|
||||
size: 'large',
|
||||
}),
|
||||
render() {
|
||||
const { prefixCls, size, shape } = this.$props;
|
||||
},
|
||||
);
|
||||
|
||||
const sizeCls = classNames({
|
||||
[`${prefixCls}-lg`]: size === 'large',
|
||||
[`${prefixCls}-sm`]: size === 'small',
|
||||
});
|
||||
|
||||
const shapeCls = classNames({
|
||||
[`${prefixCls}-circle`]: shape === 'circle',
|
||||
[`${prefixCls}-square`]: shape === 'square',
|
||||
});
|
||||
|
||||
const sizeStyle =
|
||||
typeof size === 'number'
|
||||
? {
|
||||
width: `${size}px`,
|
||||
height: `${size}px`,
|
||||
lineHeight: `${size}px`,
|
||||
}
|
||||
: {};
|
||||
|
||||
return <span class={classNames(prefixCls, sizeCls, shapeCls)} style={sizeStyle} />;
|
||||
const SkeletonAvatar = defineComponent({
|
||||
name: 'ASkeletonAvatar',
|
||||
props: avatarProps,
|
||||
setup(props) {
|
||||
const { prefixCls } = useConfigInject('skeleton', props);
|
||||
const cls = computed(() =>
|
||||
classNames(prefixCls.value, `${prefixCls.value}-element`, {
|
||||
[`${prefixCls.value}-active`]: props.active,
|
||||
}),
|
||||
);
|
||||
return () => {
|
||||
return (
|
||||
<div class={cls.value}>
|
||||
<Element {...props} prefixCls={`${prefixCls.value}-avatar`} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default Avatar;
|
||||
export default SkeletonAvatar;
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
import { computed, defineComponent } from 'vue';
|
||||
import classNames from '../_util/classNames';
|
||||
import PropTypes from '../_util/vue-types';
|
||||
import { tuple } from '../_util/type';
|
||||
import useConfigInject from '../_util/hooks/useConfigInject';
|
||||
import Element, { skeletonElementProps, SkeletonElementProps } from './Element';
|
||||
|
||||
export interface SkeletonButtonProps extends Omit<SkeletonElementProps, 'size'> {
|
||||
size?: 'large' | 'small' | 'default';
|
||||
}
|
||||
|
||||
const SkeletonButton = defineComponent({
|
||||
name: 'ASkeletonButton',
|
||||
props: { ...skeletonElementProps(), size: PropTypes.oneOf(tuple('large', 'small', 'default')) },
|
||||
setup(props) {
|
||||
const { prefixCls } = useConfigInject('skeleton', props);
|
||||
const cls = computed(() =>
|
||||
classNames(prefixCls.value, `${prefixCls.value}-element`, {
|
||||
[`${prefixCls.value}-active`]: props.active,
|
||||
}),
|
||||
);
|
||||
return () => {
|
||||
return (
|
||||
<div class={cls.value}>
|
||||
<Element {...props} prefixCls={`${prefixCls.value}-button`} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default SkeletonButton;
|
|
@ -0,0 +1,47 @@
|
|||
import { CSSProperties, ExtractPropTypes, FunctionalComponent } from '@vue/runtime-dom';
|
||||
import classNames from '../_util/classNames';
|
||||
import { tuple } from '../_util/type';
|
||||
import PropTypes from '../_util/vue-types';
|
||||
|
||||
export const skeletonElementProps = () => ({
|
||||
prefixCls: PropTypes.string,
|
||||
size: PropTypes.oneOfType([
|
||||
PropTypes.oneOf(tuple('large', 'small', 'default')),
|
||||
PropTypes.number,
|
||||
]),
|
||||
shape: PropTypes.oneOf(tuple('circle', 'square', 'round')),
|
||||
active: PropTypes.looseBool,
|
||||
});
|
||||
|
||||
export type SkeletonElementProps = Partial<
|
||||
ExtractPropTypes<ReturnType<typeof skeletonElementProps>>
|
||||
>;
|
||||
|
||||
const Element: FunctionalComponent<SkeletonElementProps> = props => {
|
||||
const { prefixCls, size, shape } = props;
|
||||
|
||||
const sizeCls = classNames({
|
||||
[`${prefixCls}-lg`]: size === 'large',
|
||||
[`${prefixCls}-sm`]: size === 'small',
|
||||
});
|
||||
|
||||
const shapeCls = classNames({
|
||||
[`${prefixCls}-circle`]: shape === 'circle',
|
||||
[`${prefixCls}-square`]: shape === 'square',
|
||||
[`${prefixCls}-round`]: shape === 'round',
|
||||
});
|
||||
|
||||
const sizeStyle: CSSProperties =
|
||||
typeof size === 'number'
|
||||
? {
|
||||
width: `${size}px`,
|
||||
height: `${size}px`,
|
||||
lineHeight: `${size}px`,
|
||||
}
|
||||
: {};
|
||||
|
||||
return <span class={classNames(prefixCls, sizeCls, shapeCls)} style={sizeStyle} />;
|
||||
};
|
||||
Element.displayName = 'SkeletonElement';
|
||||
|
||||
export default Element;
|
|
@ -0,0 +1,36 @@
|
|||
import { computed, defineComponent } from 'vue';
|
||||
import classNames from '../_util/classNames';
|
||||
import useConfigInject from '../_util/hooks/useConfigInject';
|
||||
import { skeletonElementProps, SkeletonElementProps } from './Element';
|
||||
|
||||
export interface SkeletonImageProps
|
||||
extends Omit<SkeletonElementProps, 'size' | 'shape' | 'active'> {}
|
||||
|
||||
const path =
|
||||
'M365.714286 329.142857q0 45.714286-32.036571 77.677714t-77.677714 32.036571-77.677714-32.036571-32.036571-77.677714 32.036571-77.677714 77.677714-32.036571 77.677714 32.036571 32.036571 77.677714zM950.857143 548.571429l0 256-804.571429 0 0-109.714286 182.857143-182.857143 91.428571 91.428571 292.571429-292.571429zM1005.714286 146.285714l-914.285714 0q-7.460571 0-12.873143 5.412571t-5.412571 12.873143l0 694.857143q0 7.460571 5.412571 12.873143t12.873143 5.412571l914.285714 0q7.460571 0 12.873143-5.412571t5.412571-12.873143l0-694.857143q0-7.460571-5.412571-12.873143t-12.873143-5.412571zM1097.142857 164.571429l0 694.857143q0 37.741714-26.843429 64.585143t-64.585143 26.843429l-914.285714 0q-37.741714 0-64.585143-26.843429t-26.843429-64.585143l0-694.857143q0-37.741714 26.843429-64.585143t64.585143-26.843429l914.285714 0q37.741714 0 64.585143 26.843429t26.843429 64.585143z';
|
||||
|
||||
const SkeletonImage = defineComponent({
|
||||
name: 'ASkeletonImage',
|
||||
props: skeletonElementProps(),
|
||||
setup(props) {
|
||||
const { prefixCls } = useConfigInject('skeleton', props);
|
||||
const cls = computed(() => classNames(prefixCls.value, `${prefixCls.value}-element`));
|
||||
return () => {
|
||||
return (
|
||||
<div class={cls.value}>
|
||||
<div class={`${prefixCls.value}-image`}>
|
||||
<svg
|
||||
viewBox="0 0 1098 1024"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class={`${prefixCls.value}-image-svg`}
|
||||
>
|
||||
<path d={path} class={`${prefixCls.value}-image-path`} />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default SkeletonImage;
|
|
@ -0,0 +1,36 @@
|
|||
import { computed, defineComponent } from 'vue';
|
||||
import classNames from '../_util/classNames';
|
||||
import PropTypes from '../_util/vue-types';
|
||||
import { tuple } from '../_util/type';
|
||||
import useConfigInject from '../_util/hooks/useConfigInject';
|
||||
import Element, { skeletonElementProps, SkeletonElementProps } from './Element';
|
||||
import Omit from 'omit.js';
|
||||
|
||||
export interface SkeletonInputProps extends Omit<SkeletonElementProps, 'size' | 'shape'> {
|
||||
size?: 'large' | 'small' | 'default';
|
||||
}
|
||||
|
||||
const SkeletonInput = defineComponent({
|
||||
name: 'ASkeletonInput',
|
||||
props: {
|
||||
...Omit(skeletonElementProps(), 'shape'),
|
||||
size: PropTypes.oneOf(tuple('large', 'small', 'default')),
|
||||
},
|
||||
setup(props) {
|
||||
const { prefixCls } = useConfigInject('skeleton', props);
|
||||
const cls = computed(() =>
|
||||
classNames(prefixCls.value, `${prefixCls.value}-element`, {
|
||||
[`${prefixCls.value}-active`]: props.active,
|
||||
}),
|
||||
);
|
||||
return () => {
|
||||
return (
|
||||
<div class={cls.value}>
|
||||
<Element {...props} prefixCls={`${prefixCls.value}-input`} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default SkeletonInput;
|
|
@ -3,21 +3,20 @@ import PropTypes from '../_util/vue-types';
|
|||
|
||||
const widthUnit = PropTypes.oneOfType([PropTypes.number, PropTypes.string]);
|
||||
|
||||
const skeletonParagraphProps = {
|
||||
export const skeletonParagraphProps = {
|
||||
prefixCls: PropTypes.string,
|
||||
width: PropTypes.oneOfType([widthUnit, PropTypes.arrayOf(widthUnit)]),
|
||||
rows: PropTypes.number,
|
||||
};
|
||||
|
||||
export const SkeletonParagraphProps = PropTypes.shape(skeletonParagraphProps).loose;
|
||||
export type SkeletonParagraphProps = Partial<ExtractPropTypes<typeof skeletonParagraphProps>>;
|
||||
|
||||
export type ISkeletonParagraphProps = Partial<ExtractPropTypes<typeof skeletonParagraphProps>>;
|
||||
|
||||
const Paragraph = defineComponent({
|
||||
const SkeletonParagraph = defineComponent({
|
||||
props: skeletonParagraphProps,
|
||||
methods: {
|
||||
getWidth(index: number) {
|
||||
const { width, rows = 2 } = this;
|
||||
name: 'SkeletonParagraph',
|
||||
setup(props) {
|
||||
const getWidth = (index: number) => {
|
||||
const { width, rows = 2 } = props;
|
||||
if (Array.isArray(width)) {
|
||||
return width[index];
|
||||
}
|
||||
|
@ -26,16 +25,18 @@ const Paragraph = defineComponent({
|
|||
return width;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
},
|
||||
render() {
|
||||
const { prefixCls, rows } = this.$props;
|
||||
const rowList = [...Array(rows)].map((_, index) => {
|
||||
const width = this.getWidth(index);
|
||||
return <li key={index} style={{ width: typeof width === 'number' ? `${width}px` : width }} />;
|
||||
});
|
||||
return <ul class={prefixCls}>{rowList}</ul>;
|
||||
};
|
||||
return () => {
|
||||
const { prefixCls, rows } = props;
|
||||
const rowList = [...Array(rows)].map((_, index) => {
|
||||
const width = getWidth(index);
|
||||
return (
|
||||
<li key={index} style={{ width: typeof width === 'number' ? `${width}px` : width }} />
|
||||
);
|
||||
});
|
||||
return <ul class={prefixCls}>{rowList}</ul>;
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default Paragraph;
|
||||
export default SkeletonParagraph;
|
||||
|
|
|
@ -0,0 +1,175 @@
|
|||
import { defineComponent, ExtractPropTypes } from 'vue';
|
||||
import classNames from '../_util/classNames';
|
||||
import PropTypes, { withUndefined } from '../_util/vue-types';
|
||||
import { initDefaultProps } from '../_util/props-util';
|
||||
import { AvatarProps, avatarProps } from './Avatar';
|
||||
import Title, { SkeletonTitleProps, skeletonTitleProps } from './Title';
|
||||
import Paragraph, { SkeletonParagraphProps, skeletonParagraphProps } from './Paragraph';
|
||||
import Omit from 'omit.js';
|
||||
import useConfigInject from '../_util/hooks/useConfigInject';
|
||||
import Element from './Element';
|
||||
|
||||
/* This only for skeleton internal. */
|
||||
interface SkeletonAvatarProps extends Omit<AvatarProps, 'active'> {}
|
||||
|
||||
export const skeletonProps = {
|
||||
active: PropTypes.looseBool,
|
||||
loading: PropTypes.looseBool,
|
||||
prefixCls: PropTypes.string,
|
||||
avatar: withUndefined(
|
||||
PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.shape(Omit(avatarProps, ['active'])).loose,
|
||||
PropTypes.looseBool,
|
||||
]),
|
||||
),
|
||||
title: withUndefined(
|
||||
PropTypes.oneOfType([
|
||||
PropTypes.looseBool,
|
||||
PropTypes.string,
|
||||
PropTypes.shape(skeletonTitleProps).loose,
|
||||
]),
|
||||
),
|
||||
paragraph: withUndefined(
|
||||
PropTypes.oneOfType([
|
||||
PropTypes.looseBool,
|
||||
PropTypes.string,
|
||||
PropTypes.shape(skeletonParagraphProps).loose,
|
||||
]),
|
||||
),
|
||||
};
|
||||
|
||||
export type SkeletonProps = Partial<ExtractPropTypes<typeof skeletonProps>>;
|
||||
|
||||
function getComponentProps<T>(prop: T | boolean | undefined): T | {} {
|
||||
if (prop && typeof prop === 'object') {
|
||||
return prop;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
function getAvatarBasicProps(hasTitle: boolean, hasParagraph: boolean): SkeletonAvatarProps {
|
||||
if (hasTitle && !hasParagraph) {
|
||||
// Square avatar
|
||||
return { size: 'large', shape: 'square' };
|
||||
}
|
||||
|
||||
return { size: 'large', shape: 'circle' };
|
||||
}
|
||||
|
||||
function getTitleBasicProps(hasAvatar: boolean, hasParagraph: boolean): SkeletonTitleProps {
|
||||
if (!hasAvatar && hasParagraph) {
|
||||
return { width: '38%' };
|
||||
}
|
||||
|
||||
if (hasAvatar && hasParagraph) {
|
||||
return { width: '50%' };
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
function getParagraphBasicProps(hasAvatar: boolean, hasTitle: boolean): SkeletonParagraphProps {
|
||||
const basicProps: SkeletonParagraphProps = {};
|
||||
|
||||
// Width
|
||||
if (!hasAvatar || !hasTitle) {
|
||||
basicProps.width = '61%';
|
||||
}
|
||||
|
||||
// Rows
|
||||
if (!hasAvatar && hasTitle) {
|
||||
basicProps.rows = 3;
|
||||
} else {
|
||||
basicProps.rows = 2;
|
||||
}
|
||||
|
||||
return basicProps;
|
||||
}
|
||||
|
||||
const Skeleton = defineComponent({
|
||||
name: 'ASkeleton',
|
||||
props: initDefaultProps(skeletonProps, {
|
||||
avatar: false,
|
||||
title: true,
|
||||
paragraph: true,
|
||||
}),
|
||||
setup(props, { slots }) {
|
||||
const { prefixCls, direction } = useConfigInject('skeleton', props);
|
||||
return () => {
|
||||
const { loading, avatar, title, paragraph, active, round } = props;
|
||||
const pre = prefixCls.value;
|
||||
if (loading || props.loading === undefined) {
|
||||
const hasAvatar = !!avatar || avatar === '';
|
||||
const hasTitle = !!title || title === '';
|
||||
const hasParagraph = !!paragraph || paragraph === '';
|
||||
|
||||
// Avatar
|
||||
let avatarNode;
|
||||
if (hasAvatar) {
|
||||
const avatarProps = {
|
||||
prefixCls: `${pre}-avatar`,
|
||||
...getAvatarBasicProps(hasTitle, hasParagraph),
|
||||
...getComponentProps(avatar),
|
||||
};
|
||||
|
||||
avatarNode = (
|
||||
<div class={`${pre}-header`}>
|
||||
<Element {...avatarProps} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let contentNode;
|
||||
if (hasTitle || hasParagraph) {
|
||||
// Title
|
||||
let $title;
|
||||
if (hasTitle) {
|
||||
const titleProps = {
|
||||
prefixCls: `${pre}-title`,
|
||||
...getTitleBasicProps(hasAvatar, hasParagraph),
|
||||
...getComponentProps(title),
|
||||
};
|
||||
|
||||
$title = <Title {...titleProps} />;
|
||||
}
|
||||
|
||||
// Paragraph
|
||||
let paragraphNode;
|
||||
if (hasParagraph) {
|
||||
const paragraphProps = {
|
||||
prefixCls: `${pre}-paragraph`,
|
||||
...getParagraphBasicProps(hasAvatar, hasTitle),
|
||||
...getComponentProps(paragraph),
|
||||
};
|
||||
|
||||
paragraphNode = <Paragraph {...paragraphProps} />;
|
||||
}
|
||||
|
||||
contentNode = (
|
||||
<div class={`${pre}-content`}>
|
||||
{$title}
|
||||
{paragraphNode}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const cls = classNames(pre, {
|
||||
[`${pre}-with-avatar`]: hasAvatar,
|
||||
[`${pre}-active`]: active,
|
||||
[`${pre}-rtl`]: direction.value === 'rtl',
|
||||
[`${pre}-round`]: round,
|
||||
});
|
||||
|
||||
return (
|
||||
<div class={cls}>
|
||||
{avatarNode}
|
||||
{contentNode}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return slots.default?.();
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default Skeleton;
|
|
@ -1,22 +1,23 @@
|
|||
import { defineComponent, ExtractPropTypes } from 'vue';
|
||||
import PropTypes from '../_util/vue-types';
|
||||
|
||||
const skeletonTitleProps = {
|
||||
export const skeletonTitleProps = {
|
||||
prefixCls: PropTypes.string,
|
||||
width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
||||
};
|
||||
|
||||
export const SkeletonTitleProps = PropTypes.shape(skeletonTitleProps).loose;
|
||||
export type SkeletonTitleProps = Partial<ExtractPropTypes<typeof skeletonTitleProps>>;
|
||||
|
||||
export type ISkeletonTitleProps = Partial<ExtractPropTypes<typeof skeletonTitleProps>>;
|
||||
|
||||
const Title = defineComponent({
|
||||
const SkeletonTitle = defineComponent({
|
||||
props: skeletonTitleProps,
|
||||
render() {
|
||||
const { prefixCls, width } = this.$props;
|
||||
const zWidth = typeof width === 'number' ? `${width}px` : width;
|
||||
return <h3 class={prefixCls} style={{ width: zWidth }} />;
|
||||
name: 'SkeletonTitle',
|
||||
setup(props) {
|
||||
return () => {
|
||||
const { prefixCls, width } = props;
|
||||
const zWidth = typeof width === 'number' ? `${width}px` : width;
|
||||
return <h3 class={prefixCls} style={{ width: zWidth }} />;
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default Title;
|
||||
export default SkeletonTitle;
|
||||
|
|
|
@ -1,167 +1,31 @@
|
|||
import { defineComponent, inject } from 'vue';
|
||||
import classNames from '../_util/classNames';
|
||||
import PropTypes, { withUndefined } from '../_util/vue-types';
|
||||
import { initDefaultProps, hasProp } from '../_util/props-util';
|
||||
import { defaultConfigProvider } from '../config-provider';
|
||||
import Avatar, { SkeletonAvatarProps, ISkeletonAvatarProps } from './Avatar';
|
||||
import Title, { SkeletonTitleProps, ISkeletonTitleProps } from './Title';
|
||||
import Paragraph, { SkeletonParagraphProps, ISkeletonParagraphProps } from './Paragraph';
|
||||
import { withInstall } from '../_util/type';
|
||||
import { App } from 'vue';
|
||||
import Skeleton from './Skeleton';
|
||||
import SkeletonButton from './Button';
|
||||
import SkeletonInput from './Input';
|
||||
import SkeletonImage from './Image';
|
||||
import SkeletonAvatar from './Avatar';
|
||||
|
||||
export const SkeletonProps = {
|
||||
active: PropTypes.looseBool,
|
||||
loading: PropTypes.looseBool,
|
||||
prefixCls: PropTypes.string,
|
||||
children: PropTypes.any,
|
||||
avatar: withUndefined(
|
||||
PropTypes.oneOfType([PropTypes.string, SkeletonAvatarProps, PropTypes.looseBool]),
|
||||
),
|
||||
title: withUndefined(
|
||||
PropTypes.oneOfType([PropTypes.looseBool, PropTypes.string, SkeletonTitleProps]),
|
||||
),
|
||||
paragraph: withUndefined(
|
||||
PropTypes.oneOfType([PropTypes.looseBool, PropTypes.string, SkeletonParagraphProps]),
|
||||
),
|
||||
export { SkeletonProps, skeletonProps } from './Skeleton';
|
||||
|
||||
Skeleton.Button = SkeletonButton;
|
||||
Skeleton.Avatar = SkeletonAvatar;
|
||||
Skeleton.Input = SkeletonInput;
|
||||
Skeleton.Image = SkeletonImage;
|
||||
|
||||
/* istanbul ignore next */
|
||||
Skeleton.install = function(app: App) {
|
||||
app.component(Skeleton.name, Skeleton);
|
||||
app.component(Skeleton.Button.name, SkeletonButton);
|
||||
app.component(Skeleton.Avatar.name, SkeletonAvatar);
|
||||
app.component(Skeleton.Input.name, SkeletonInput);
|
||||
app.component(Skeleton.Image.name, SkeletonImage);
|
||||
return app;
|
||||
};
|
||||
|
||||
function getComponentProps<T>(prop: T | boolean | undefined): T | {} {
|
||||
if (prop && typeof prop === 'object') {
|
||||
return prop;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
function getAvatarBasicProps(hasTitle: boolean, hasParagraph: boolean): ISkeletonAvatarProps {
|
||||
if (hasTitle && !hasParagraph) {
|
||||
return { shape: 'square' };
|
||||
}
|
||||
|
||||
return { shape: 'circle' };
|
||||
}
|
||||
|
||||
function getTitleBasicProps(hasAvatar: boolean, hasParagraph: boolean): ISkeletonTitleProps {
|
||||
if (!hasAvatar && hasParagraph) {
|
||||
return { width: '38%' };
|
||||
}
|
||||
|
||||
if (hasAvatar && hasParagraph) {
|
||||
return { width: '50%' };
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
function getParagraphBasicProps(hasAvatar: boolean, hasTitle: boolean): ISkeletonParagraphProps {
|
||||
const basicProps: ISkeletonParagraphProps = {};
|
||||
|
||||
// Width
|
||||
if (!hasAvatar || !hasTitle) {
|
||||
basicProps.width = '61%';
|
||||
}
|
||||
|
||||
// Rows
|
||||
if (!hasAvatar && hasTitle) {
|
||||
basicProps.rows = 3;
|
||||
} else {
|
||||
basicProps.rows = 2;
|
||||
}
|
||||
|
||||
return basicProps;
|
||||
}
|
||||
|
||||
const Skeleton = defineComponent({
|
||||
name: 'ASkeleton',
|
||||
props: initDefaultProps(SkeletonProps, {
|
||||
avatar: false,
|
||||
title: true,
|
||||
paragraph: true,
|
||||
}),
|
||||
setup() {
|
||||
return {
|
||||
configProvider: inject('configProvider', defaultConfigProvider),
|
||||
};
|
||||
},
|
||||
render() {
|
||||
const {
|
||||
prefixCls: customizePrefixCls,
|
||||
loading,
|
||||
avatar,
|
||||
title,
|
||||
paragraph,
|
||||
active,
|
||||
} = this.$props;
|
||||
const { getPrefixCls } = this.configProvider;
|
||||
const prefixCls = getPrefixCls('skeleton', customizePrefixCls);
|
||||
|
||||
if (loading || !hasProp(this, 'loading')) {
|
||||
const hasAvatar = !!avatar || avatar === '';
|
||||
const hasTitle = !!title;
|
||||
const hasParagraph = !!paragraph;
|
||||
|
||||
// Avatar
|
||||
let avatarNode;
|
||||
if (hasAvatar) {
|
||||
const avatarProps = {
|
||||
prefixCls: `${prefixCls}-avatar`,
|
||||
...getAvatarBasicProps(hasTitle, hasParagraph),
|
||||
...getComponentProps(avatar),
|
||||
};
|
||||
|
||||
avatarNode = (
|
||||
<div class={`${prefixCls}-header`}>
|
||||
<Avatar {...avatarProps} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let contentNode;
|
||||
if (hasTitle || hasParagraph) {
|
||||
// Title
|
||||
let $title;
|
||||
if (hasTitle) {
|
||||
const titleProps = {
|
||||
prefixCls: `${prefixCls}-title`,
|
||||
...getTitleBasicProps(hasAvatar, hasParagraph),
|
||||
...getComponentProps(title),
|
||||
};
|
||||
|
||||
$title = <Title {...titleProps} />;
|
||||
}
|
||||
|
||||
// Paragraph
|
||||
let paragraphNode;
|
||||
if (hasParagraph) {
|
||||
const paragraphProps = {
|
||||
prefixCls: `${prefixCls}-paragraph`,
|
||||
...getParagraphBasicProps(hasAvatar, hasTitle),
|
||||
...getComponentProps(paragraph),
|
||||
};
|
||||
|
||||
paragraphNode = <Paragraph {...paragraphProps} />;
|
||||
}
|
||||
|
||||
contentNode = (
|
||||
<div class={`${prefixCls}-content`}>
|
||||
{$title}
|
||||
{paragraphNode}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const cls = classNames(prefixCls, {
|
||||
[`${prefixCls}-with-avatar`]: hasAvatar,
|
||||
[`${prefixCls}-active`]: active,
|
||||
});
|
||||
|
||||
return (
|
||||
<div class={cls}>
|
||||
{avatarNode}
|
||||
{contentNode}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return this.$slots.default?.();
|
||||
},
|
||||
});
|
||||
|
||||
export default withInstall(Skeleton);
|
||||
export default Skeleton as typeof Skeleton &
|
||||
Plugin & {
|
||||
readonly Button: typeof SkeletonButton;
|
||||
readonly Avatar: typeof SkeletonAvatar;
|
||||
readonly Input: typeof SkeletonInput;
|
||||
readonly Image: typeof SkeletonImage;
|
||||
};
|
||||
|
|
|
@ -5,8 +5,10 @@
|
|||
@skeleton-avatar-prefix-cls: ~'@{skeleton-prefix-cls}-avatar';
|
||||
@skeleton-title-prefix-cls: ~'@{skeleton-prefix-cls}-title';
|
||||
@skeleton-paragraph-prefix-cls: ~'@{skeleton-prefix-cls}-paragraph';
|
||||
|
||||
@skeleton-to-color: shade(@skeleton-color, 5%);
|
||||
@skeleton-button-prefix-cls: ~'@{skeleton-prefix-cls}-button';
|
||||
@skeleton-input-prefix-cls: ~'@{skeleton-prefix-cls}-input';
|
||||
@skeleton-image-prefix-cls: ~'@{skeleton-prefix-cls}-image';
|
||||
@skeleton-block-radius: 4px;
|
||||
|
||||
.@{skeleton-prefix-cls} {
|
||||
display: table;
|
||||
|
@ -14,24 +16,12 @@
|
|||
|
||||
&-header {
|
||||
display: table-cell;
|
||||
padding-right: 16px;
|
||||
padding-right: @padding-md;
|
||||
vertical-align: top;
|
||||
|
||||
// Avatar
|
||||
.@{skeleton-avatar-prefix-cls} {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
background: @skeleton-color;
|
||||
|
||||
.avatar-size(@avatar-size-base);
|
||||
|
||||
&-lg {
|
||||
.avatar-size(@avatar-size-lg);
|
||||
}
|
||||
|
||||
&-sm {
|
||||
.avatar-size(@avatar-size-sm);
|
||||
}
|
||||
.skeleton-element-avatar();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -43,12 +33,13 @@
|
|||
// Title
|
||||
.@{skeleton-title-prefix-cls} {
|
||||
width: 100%;
|
||||
height: 16px;
|
||||
margin-top: 16px;
|
||||
height: @skeleton-title-height;
|
||||
margin-top: @margin-md;
|
||||
background: @skeleton-color;
|
||||
border-radius: @skeleton-block-radius;
|
||||
|
||||
+ .@{skeleton-paragraph-prefix-cls} {
|
||||
margin-top: 24px;
|
||||
margin-top: @skeleton-title-paragraph-margin-top;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -58,16 +49,17 @@
|
|||
|
||||
> li {
|
||||
width: 100%;
|
||||
height: 16px;
|
||||
height: @skeleton-paragraph-li-height;
|
||||
list-style: none;
|
||||
background: @skeleton-color;
|
||||
border-radius: @skeleton-block-radius;
|
||||
|
||||
&:last-child:not(:first-child):not(:nth-child(2)) {
|
||||
width: 61%;
|
||||
}
|
||||
|
||||
+ li {
|
||||
margin-top: 16px;
|
||||
margin-top: @skeleton-paragraph-li-margin-top;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -76,14 +68,21 @@
|
|||
&-with-avatar &-content {
|
||||
// Title
|
||||
.@{skeleton-title-prefix-cls} {
|
||||
margin-top: 12px;
|
||||
margin-top: @margin-sm;
|
||||
|
||||
+ .@{skeleton-paragraph-prefix-cls} {
|
||||
margin-top: 28px;
|
||||
margin-top: @skeleton-paragraph-margin-top;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-round &-content {
|
||||
.@{skeleton-title-prefix-cls},
|
||||
.@{skeleton-paragraph-prefix-cls} > li {
|
||||
border-radius: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
// With active animation
|
||||
&.@{skeleton-prefix-cls}-active {
|
||||
& .@{skeleton-prefix-cls}-content {
|
||||
|
@ -96,19 +95,156 @@
|
|||
.@{skeleton-avatar-prefix-cls} {
|
||||
.skeleton-color();
|
||||
}
|
||||
|
||||
.@{skeleton-button-prefix-cls} {
|
||||
.skeleton-color();
|
||||
}
|
||||
|
||||
.@{skeleton-input-prefix-cls} {
|
||||
.skeleton-color();
|
||||
}
|
||||
|
||||
.@{skeleton-image-prefix-cls} {
|
||||
.skeleton-color();
|
||||
}
|
||||
}
|
||||
|
||||
// Skeleton element
|
||||
&-element {
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
|
||||
.@{skeleton-button-prefix-cls} {
|
||||
.skeleton-element-button();
|
||||
}
|
||||
|
||||
.@{skeleton-avatar-prefix-cls} {
|
||||
.skeleton-element-avatar();
|
||||
}
|
||||
|
||||
.@{skeleton-input-prefix-cls} {
|
||||
.skeleton-element-input();
|
||||
}
|
||||
|
||||
.@{skeleton-image-prefix-cls} {
|
||||
.skeleton-element-image();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Button
|
||||
.skeleton-element-button() {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
background: @skeleton-color;
|
||||
border-radius: @border-radius-base;
|
||||
|
||||
.skeleton-element-button-size(@btn-height-base);
|
||||
|
||||
&-lg {
|
||||
.skeleton-element-button-size(@btn-height-lg);
|
||||
}
|
||||
|
||||
&-sm {
|
||||
.skeleton-element-button-size(@btn-height-sm);
|
||||
}
|
||||
}
|
||||
// Avatar
|
||||
.skeleton-element-avatar() {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
background: @skeleton-color;
|
||||
|
||||
.skeleton-element-avatar-size(@avatar-size-base);
|
||||
|
||||
&-lg {
|
||||
.skeleton-element-avatar-size(@avatar-size-lg);
|
||||
}
|
||||
|
||||
&-sm {
|
||||
.skeleton-element-avatar-size(@avatar-size-sm);
|
||||
}
|
||||
}
|
||||
|
||||
.avatar-size(@size) {
|
||||
// Input
|
||||
.skeleton-element-input() {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
background: @skeleton-color;
|
||||
|
||||
.skeleton-element-input-size(@input-height-base);
|
||||
|
||||
&-lg {
|
||||
.skeleton-element-input-size(@input-height-lg);
|
||||
}
|
||||
|
||||
&-sm {
|
||||
.skeleton-element-input-size(@input-height-sm);
|
||||
}
|
||||
}
|
||||
|
||||
// Image
|
||||
.skeleton-element-image() {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
vertical-align: top;
|
||||
background: @skeleton-color;
|
||||
|
||||
.skeleton-element-image-size(@image-size-base*2);
|
||||
|
||||
&-path {
|
||||
fill: #bfbfbf;
|
||||
}
|
||||
|
||||
&-svg {
|
||||
.skeleton-element-image-size(@image-size-base);
|
||||
max-width: @image-size-base * 4;
|
||||
max-height: @image-size-base * 4;
|
||||
}
|
||||
}
|
||||
|
||||
.skeleton-element-avatar-size(@size) {
|
||||
width: @size;
|
||||
height: @size;
|
||||
line-height: @size;
|
||||
.skeleton-element-common-size(@size);
|
||||
|
||||
&.@{skeleton-avatar-prefix-cls}-circle {
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.skeleton-element-button-size(@size) {
|
||||
width: @size * 2;
|
||||
.skeleton-element-common-size(@size);
|
||||
|
||||
&.@{skeleton-button-prefix-cls}-circle {
|
||||
width: @size;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
&.@{skeleton-button-prefix-cls}-round {
|
||||
border-radius: @size;
|
||||
}
|
||||
}
|
||||
|
||||
.skeleton-element-input-size(@size) {
|
||||
width: 100%;
|
||||
.skeleton-element-common-size(@size);
|
||||
}
|
||||
|
||||
.skeleton-element-image-size(@size) {
|
||||
width: @size;
|
||||
.skeleton-element-common-size(@size);
|
||||
|
||||
&.@{skeleton-image-prefix-cls}-circle {
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.skeleton-element-common-size(@size) {
|
||||
height: @size;
|
||||
line-height: @size;
|
||||
}
|
||||
|
||||
.skeleton-color() {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
|
@ -128,3 +264,5 @@
|
|||
background-position: 0 50%;
|
||||
}
|
||||
}
|
||||
|
||||
@import './rtl';
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
@import '../../style/themes/index';
|
||||
@import '../../style/mixins/index';
|
||||
|
||||
@skeleton-prefix-cls: ~'@{ant-prefix}-skeleton';
|
||||
@skeleton-avatar-prefix-cls: ~'@{skeleton-prefix-cls}-avatar';
|
||||
@skeleton-title-prefix-cls: ~'@{skeleton-prefix-cls}-title';
|
||||
@skeleton-paragraph-prefix-cls: ~'@{skeleton-prefix-cls}-paragraph';
|
||||
|
||||
.@{skeleton-prefix-cls} {
|
||||
&-rtl {
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
&-header {
|
||||
.@{skeleton-prefix-cls}-rtl & {
|
||||
padding-right: 0;
|
||||
padding-left: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
// With active animation
|
||||
&.@{skeleton-prefix-cls}-active {
|
||||
& .@{skeleton-prefix-cls}-content {
|
||||
.@{skeleton-title-prefix-cls},
|
||||
.@{skeleton-paragraph-prefix-cls} > li {
|
||||
.@{skeleton-prefix-cls}-rtl& {
|
||||
animation-name: ~'@{skeleton-prefix-cls}-loading-rtl';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.@{skeleton-avatar-prefix-cls} {
|
||||
.@{skeleton-prefix-cls}-rtl& {
|
||||
animation-name: ~'@{skeleton-prefix-cls}-loading-rtl';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes ~"@{skeleton-prefix-cls}-loading-rtl" {
|
||||
0% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
100% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
}
|
|
@ -755,8 +755,13 @@
|
|||
|
||||
// Skeleton
|
||||
// ---
|
||||
@skeleton-color: #f2f2f2;
|
||||
@skeleton-color: rgba(190, 190, 190, 0.2);
|
||||
@skeleton-to-color: shade(@skeleton-color, 5%);
|
||||
@skeleton-paragraph-margin-top: 28px;
|
||||
@skeleton-paragraph-li-margin-top: @margin-md;
|
||||
@skeleton-paragraph-li-height: 16px;
|
||||
@skeleton-title-height: 16px;
|
||||
@skeleton-title-paragraph-margin-top: @margin-lg;
|
||||
|
||||
// Transfer
|
||||
// ---
|
||||
|
|
Loading…
Reference in New Issue