feat: add skeleton button & input

feat-css-var
tangjinzhou 2022-03-06 21:25:21 +08:00
parent 741ec4c835
commit 2bd5fc15ff
19 changed files with 296 additions and 50 deletions

View File

@ -150,13 +150,21 @@ export { default as Row } from './row';
export type { SelectProps } from './select';
export { default as Select, SelectOptGroup, SelectOption } from './select';
export type { SkeletonProps } from './skeleton';
export type {
SkeletonProps,
SkeletonButtonProps,
SkeletonInputProps,
SkeletonImageProps,
SkeletonAvatarProps,
SkeletonTitleProps,
} from './skeleton';
export {
default as Skeleton,
SkeletonButton,
SkeletonAvatar,
SkeletonInput,
SkeletonImage,
SkeletonTitle,
} from './skeleton';
export type { SliderProps } from './slider';

View File

@ -1,26 +1,25 @@
import type { ExtractPropTypes, PropType } 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 type { SkeletonElementProps } from './Element';
import Element, { skeletonElementProps } from './Element';
export interface AvatarProps extends Omit<SkeletonElementProps, 'shape'> {
shape?: 'circle' | 'square';
}
export const avatarProps = () => {
return {
...skeletonElementProps(),
shape: String as PropType<'circle' | 'square'>,
};
};
export const avatarProps = initDefaultProps(
{ ...skeletonElementProps(), shape: PropTypes.oneOf(tuple('circle', 'square')) },
{
size: 'large',
},
);
export type SkeletonAvatarProps = Partial<ExtractPropTypes<ReturnType<typeof avatarProps>>>;
const SkeletonAvatar = defineComponent({
name: 'ASkeletonAvatar',
props: avatarProps,
props: initDefaultProps(avatarProps(), {
size: 'default',
shape: 'circle',
}),
setup(props) {
const { prefixCls } = useConfigInject('skeleton', props);
const cls = computed(() =>

View File

@ -1,23 +1,31 @@
import type { ExtractPropTypes, PropType } from 'vue';
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 type { SkeletonElementProps } from './Element';
import { initDefaultProps } from '../_util/props-util';
import Element, { skeletonElementProps } from './Element';
export interface SkeletonButtonProps extends Omit<SkeletonElementProps, 'size'> {
size?: 'large' | 'small' | 'default';
}
export const skeletonButtonProps = () => {
return {
...skeletonElementProps(),
size: String as PropType<'large' | 'small' | 'default'>,
block: Boolean,
};
};
export type SkeletonButtonProps = Partial<ExtractPropTypes<ReturnType<typeof skeletonButtonProps>>>;
const SkeletonButton = defineComponent({
name: 'ASkeletonButton',
props: { ...skeletonElementProps(), size: PropTypes.oneOf(tuple('large', 'small', 'default')) },
props: initDefaultProps(skeletonButtonProps(), {
size: 'default',
}),
setup(props) {
const { prefixCls } = useConfigInject('skeleton', props);
const cls = computed(() =>
classNames(prefixCls.value, `${prefixCls.value}-element`, {
[`${prefixCls.value}-active`]: props.active,
[`${prefixCls.value}-block`]: props.block,
}),
);
return () => {

View File

@ -1,16 +1,11 @@
import type { CSSProperties, ExtractPropTypes, FunctionalComponent } from 'vue';
import type { CSSProperties, ExtractPropTypes, FunctionalComponent, PropType } from 'vue';
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,
prefixCls: String,
size: [String, Number] as PropType<'large' | 'small' | 'default' | number>,
shape: String as PropType<'circle' | 'square' | 'round' | 'default'>,
active: { type: Boolean, default: undefined },
});
export type SkeletonElementProps = Partial<

View File

@ -1,6 +1,7 @@
import { computed, defineComponent } from 'vue';
import classNames from '../_util/classNames';
import useConfigInject from '../_util/hooks/useConfigInject';
import omit from '../_util/omit';
import type { SkeletonElementProps } from './Element';
import { skeletonElementProps } from './Element';
@ -11,7 +12,7 @@ const path =
const SkeletonImage = defineComponent({
name: 'ASkeletonImage',
props: skeletonElementProps(),
props: omit(skeletonElementProps(), ['size', 'shape', 'active']),
setup(props) {
const { prefixCls } = useConfigInject('skeleton', props);
const cls = computed(() => classNames(prefixCls.value, `${prefixCls.value}-element`));

View File

@ -1,7 +1,6 @@
import type { PropType } from 'vue';
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 type { SkeletonElementProps } from './Element';
import Element, { skeletonElementProps } from './Element';
@ -15,7 +14,7 @@ const SkeletonInput = defineComponent({
name: 'ASkeletonInput',
props: {
...omit(skeletonElementProps(), ['shape']),
size: PropTypes.oneOf(tuple('large', 'small', 'default')),
size: String as PropType<'large' | 'small' | 'default'>,
},
setup(props) {
const { prefixCls } = useConfigInject('skeleton', props);

View File

@ -2,17 +2,19 @@ import type { ExtractPropTypes, PropType } from 'vue';
import { defineComponent } from 'vue';
type widthUnit = number | string;
export const skeletonParagraphProps = {
export const skeletonParagraphProps = () => ({
prefixCls: String,
width: { type: [Number, String, Array] as PropType<widthUnit[] | widthUnit> },
rows: Number,
};
});
export type SkeletonParagraphProps = Partial<ExtractPropTypes<typeof skeletonParagraphProps>>;
export type SkeletonParagraphProps = Partial<
ExtractPropTypes<ReturnType<typeof skeletonParagraphProps>>
>;
const SkeletonParagraph = defineComponent({
name: 'SkeletonParagraph',
props: skeletonParagraphProps,
props: skeletonParagraphProps(),
setup(props) {
const getWidth = (index: number) => {
const { width, rows = 2 } = props;

View File

@ -2,7 +2,7 @@ import type { ExtractPropTypes, PropType } from 'vue';
import { defineComponent } from 'vue';
import classNames from '../_util/classNames';
import { initDefaultProps } from '../_util/props-util';
import type { AvatarProps } from './Avatar';
import type { SkeletonAvatarProps as AvatarProps } from './Avatar';
import type { SkeletonTitleProps } from './Title';
import Title from './Title';
import type { SkeletonParagraphProps } from './Paragraph';
@ -13,7 +13,7 @@ import Element from './Element';
/* This only for skeleton internal. */
type SkeletonAvatarProps = Omit<AvatarProps, 'active'>;
export const skeletonProps = {
export const skeletonProps = () => ({
active: { type: Boolean, default: undefined },
loading: { type: Boolean, default: undefined },
prefixCls: String,
@ -30,9 +30,9 @@ export const skeletonProps = {
default: undefined as SkeletonParagraphProps | boolean,
},
round: { type: Boolean, default: undefined },
};
});
export type SkeletonProps = Partial<ExtractPropTypes<typeof skeletonProps>>;
export type SkeletonProps = Partial<ExtractPropTypes<ReturnType<typeof skeletonProps>>>;
function getComponentProps<T>(prop: T | boolean | undefined): T | {} {
if (prop && typeof prop === 'object') {
@ -81,7 +81,7 @@ function getParagraphBasicProps(hasAvatar: boolean, hasTitle: boolean): Skeleton
const Skeleton = defineComponent({
name: 'ASkeleton',
props: initDefaultProps(skeletonProps, {
props: initDefaultProps(skeletonProps(), {
avatar: false,
title: true,
paragraph: true,

View File

@ -1,16 +1,16 @@
import type { ExtractPropTypes, PropType } from 'vue';
import { defineComponent } from 'vue';
export const skeletonTitleProps = {
export const skeletonTitleProps = () => ({
prefixCls: String,
width: { type: [Number, String] as PropType<string | number> },
};
});
export type SkeletonTitleProps = Partial<ExtractPropTypes<typeof skeletonTitleProps>>;
export type SkeletonTitleProps = Partial<ExtractPropTypes<ReturnType<typeof skeletonTitleProps>>>;
const SkeletonTitle = defineComponent({
name: 'SkeletonTitle',
props: skeletonTitleProps,
props: skeletonTitleProps(),
setup(props) {
return () => {
const { prefixCls, width } = props;

View File

@ -54,6 +54,109 @@ exports[`renders ./components/skeleton/demo/complex.vue correctly 1`] = `
</div>
`;
exports[`renders ./components/skeleton/demo/element.vue correctly 1`] = `
<div class="ant-space ant-space-horizontal ant-space-align-center">
<div class="ant-space-item" style="margin-right: 8px;">
<div class="ant-skeleton ant-skeleton-element"><span class="ant-skeleton-button"></span></div>
</div>
<!---->
<div class="ant-space-item" style="margin-right: 8px;">
<div class="ant-skeleton ant-skeleton-element"><span class="ant-skeleton-avatar ant-skeleton-avatar-circle"></span></div>
</div>
<!---->
<div class="ant-space-item">
<div class="ant-skeleton ant-skeleton-element" style="width: 200px;"><span class="ant-skeleton-input"></span></div>
</div>
<!---->
</div><br><br>
<div class="ant-skeleton ant-skeleton-element"><span class="ant-skeleton-button"></span></div><br><br>
<div class="ant-skeleton ant-skeleton-element">
<div class="ant-skeleton-image"><svg viewBox="0 0 1098 1024" xmlns="http://www.w3.org/2000/svg" class="ant-skeleton-image-svg">
<path d="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" class="ant-skeleton-image-path"></path>
</svg></div>
</div>
<div class="ant-divider ant-divider-horizontal" role="separator">
<!---->
</div>
<form style="margin: 16px 0px;" class="ant-form ant-form-inline">
<div class="ant-row ant-form-item">
<div class="ant-col ant-form-item-label"><label class="" title="Active">Active
<!---->
</label></div>
<div class="ant-col ant-form-item-control">
<div class="ant-form-item-control-input">
<div class="ant-form-item-control-input-content"><button type="button" role="switch" aria-checked="false" class="ant-switch">
<!----><span class="ant-switch-inner"><!----></span>
</button></div>
<!---->
</div>
<div class="ant-form-item-explain ant-form-item-explain-connected"></div>
<!---->
</div>
</div>
<div class="ant-row ant-form-item">
<div class="ant-col ant-form-item-label"><label class="" title="Button Block">Button Block
<!---->
</label></div>
<div class="ant-col ant-form-item-control">
<div class="ant-form-item-control-input">
<div class="ant-form-item-control-input-content"><button type="button" role="switch" aria-checked="false" class="ant-switch">
<!----><span class="ant-switch-inner"><!----></span>
</button></div>
<!---->
</div>
<div class="ant-form-item-explain ant-form-item-explain-connected"></div>
<!---->
</div>
</div>
<div class="ant-row ant-form-item">
<div class="ant-col ant-form-item-label"><label class="" title="Size">Size
<!---->
</label></div>
<div class="ant-col ant-form-item-control">
<div class="ant-form-item-control-input">
<div class="ant-form-item-control-input-content">
<div class="ant-radio-group ant-radio-group-outline ant-radio-group-default"><label class="ant-radio-button-wrapper ant-radio-button-wrapper-checked"><span class="ant-radio-button ant-radio-button-checked"><input type="radio" class="ant-radio-button-input" value="default"><span class="ant-radio-button-inner"></span></span><span>Default</span></label><label class="ant-radio-button-wrapper"><span class="ant-radio-button"><input type="radio" class="ant-radio-button-input" value="large"><span class="ant-radio-button-inner"></span></span><span>Large</span></label><label class="ant-radio-button-wrapper"><span class="ant-radio-button"><input type="radio" class="ant-radio-button-input" value="small"><span class="ant-radio-button-inner"></span></span><span>Small</span></label></div>
</div>
<!---->
</div>
<div class="ant-form-item-explain ant-form-item-explain-connected"></div>
<!---->
</div>
</div>
<div class="ant-row ant-form-item">
<div class="ant-col ant-form-item-label"><label class="" title="Button Shape">Button Shape
<!---->
</label></div>
<div class="ant-col ant-form-item-control">
<div class="ant-form-item-control-input">
<div class="ant-form-item-control-input-content">
<div class="ant-radio-group ant-radio-group-outline ant-radio-group-default"><label class="ant-radio-button-wrapper ant-radio-button-wrapper-checked"><span class="ant-radio-button ant-radio-button-checked"><input type="radio" class="ant-radio-button-input" value="default"><span class="ant-radio-button-inner"></span></span><span>Default</span></label><label class="ant-radio-button-wrapper"><span class="ant-radio-button"><input type="radio" class="ant-radio-button-input" value="round"><span class="ant-radio-button-inner"></span></span><span>Round</span></label><label class="ant-radio-button-wrapper"><span class="ant-radio-button"><input type="radio" class="ant-radio-button-input" value="circle"><span class="ant-radio-button-inner"></span></span><span>Circle</span></label></div>
</div>
<!---->
</div>
<div class="ant-form-item-explain ant-form-item-explain-connected"></div>
<!---->
</div>
</div>
<div class="ant-row ant-form-item">
<div class="ant-col ant-form-item-label"><label class="" title="Avatar Shape">Avatar Shape
<!---->
</label></div>
<div class="ant-col ant-form-item-control">
<div class="ant-form-item-control-input">
<div class="ant-form-item-control-input-content">
<div class="ant-radio-group ant-radio-group-outline ant-radio-group-default"><label class="ant-radio-button-wrapper"><span class="ant-radio-button"><input type="radio" class="ant-radio-button-input" value="square"><span class="ant-radio-button-inner"></span></span><span>Square</span></label><label class="ant-radio-button-wrapper ant-radio-button-wrapper-checked"><span class="ant-radio-button ant-radio-button-checked"><input type="radio" class="ant-radio-button-input" value="circle"><span class="ant-radio-button-inner"></span></span><span>Circle</span></label></div>
</div>
<!---->
</div>
<div class="ant-form-item-explain ant-form-item-explain-connected"></div>
<!---->
</div>
</div>
</form>
`;
exports[`renders ./components/skeleton/demo/list.vue correctly 1`] = `
<div><button type="button" role="switch" aria-checked="false" class="ant-switch">
<!----><span class="ant-switch-inner"><!----></span>

View File

@ -0,0 +1,75 @@
<docs>
---
order: 2.1
title:
zh-CN: 按钮/头像/输入框/图像
en-US: Button/Avatar/Input/Image
---
## zh-CN
骨架按钮头像输入框和图像
## en-US
Skeleton Button, Avatar, Input and Image.
</docs>
<template>
<a-space>
<a-skeleton-button :active="active" :size="size" :shape="buttonShape" :block="block" />
<a-skeleton-avatar :active="active" :size="size" :shape="avatarShape" />
<a-skeleton-input style="width: 200px" :active="active" :size="size" />
</a-space>
<br />
<br />
<a-skeleton-button :active="active" :size="size" :shape="buttonShape" :block="block" />
<br />
<br />
<a-skeleton-image />
<a-divider />
<a-form layout="inline" style="margin: 16px 0">
<a-form-item label="Active">
<a-switch v-model:checked="active" />
</a-form-item>
<a-form-item label="Button Block">
<a-switch v-model:checked="block" />
</a-form-item>
<a-form-item label="Size">
<a-radio-group v-model:value="size">
<a-radio-button value="default">Default</a-radio-button>
<a-radio-button value="large">Large</a-radio-button>
<a-radio-button value="small">Small</a-radio-button>
</a-radio-group>
</a-form-item>
<a-form-item label="Button Shape">
<a-radio-group v-model:value="buttonShape">
<a-radio-button value="default">Default</a-radio-button>
<a-radio-button value="round">Round</a-radio-button>
<a-radio-button value="circle">Circle</a-radio-button>
</a-radio-group>
</a-form-item>
<a-form-item label="Avatar Shape">
<a-radio-group v-model:value="avatarShape">
<a-radio-button value="square">Square</a-radio-button>
<a-radio-button value="circle">Circle</a-radio-button>
</a-radio-group>
</a-form-item>
</a-form>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
import type { SkeletonButtonProps, SkeletonAvatarProps } from 'ant-design-vue';
export default defineComponent({
setup() {
return {
active: ref(false),
block: ref(false),
size: ref<SkeletonButtonProps['size']>('default'),
buttonShape: ref<SkeletonButtonProps['shape']>('default'),
avatarShape: ref<SkeletonAvatarProps['shape']>('circle'),
};
},
});
</script>

View File

@ -4,6 +4,7 @@
<active />
<children />
<list />
<elementVue />
</template>
<script lang="ts">
import Basic from './basic.vue';
@ -11,6 +12,7 @@ import Complex from './complex.vue';
import Active from './active.vue';
import Children from './children.vue';
import List from './list.vue';
import elementVue from './element.vue';
import CN from '../index.zh-CN.md';
import US from '../index.en-US.md';
import { defineComponent } from 'vue';
@ -24,6 +26,7 @@ export default defineComponent({
Active,
List,
Children,
elementVue,
},
setup() {
return {};

View File

@ -45,3 +45,19 @@ Provide a placeholder while you wait for content to load, or to visualise conten
| --- | --- | --- | --- |
| rows | Set the row count of paragraph | number | - |
| width | Set the width of paragraph. When width is an Array, it can set the width of each row. Otherwise only set the last row width | number \| string \| Array<number \| string> | - |
### SkeletonButtonProps (3.0+)
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| active | Show animation effect | boolean | false | |
| block | Option to fit button width to its parent width | boolean | false | |
| shape | Set the shape of button | `circle` \| `round` \| `default` | - | |
| size | Set the size of button | `large` \| `small` \| `default` | - | |
### SkeletonInputProps (3.0+)
| Property | Description | Type | Default |
| -------- | --------------------- | ------------------------------- | ------- |
| active | Show animation effect | boolean | false |
| size | Set the size of input | `large` \| `small` \| `default` | - |

View File

@ -4,6 +4,12 @@ import SkeletonButton from './Button';
import SkeletonInput from './Input';
import SkeletonImage from './Image';
import SkeletonAvatar from './Avatar';
import SkeletonTitle from './Title';
export type { SkeletonButtonProps } from './Button';
export type { SkeletonInputProps } from './Input';
export type { SkeletonImageProps } from './Image';
export type { SkeletonAvatarProps } from './Avatar';
export type { SkeletonTitleProps } from './Title';
export type { SkeletonProps } from './Skeleton';
export { skeletonProps } from './Skeleton';
@ -12,6 +18,7 @@ Skeleton.Button = SkeletonButton;
Skeleton.Avatar = SkeletonAvatar;
Skeleton.Input = SkeletonInput;
Skeleton.Image = SkeletonImage;
Skeleton.Title = SkeletonTitle;
/* istanbul ignore next */
Skeleton.install = function (app: App) {
@ -20,9 +27,10 @@ Skeleton.install = function (app: App) {
app.component(Skeleton.Avatar.name, SkeletonAvatar);
app.component(Skeleton.Input.name, SkeletonInput);
app.component(Skeleton.Image.name, SkeletonImage);
app.component(Skeleton.Title.name, SkeletonTitle);
return app;
};
export { SkeletonButton, SkeletonAvatar, SkeletonInput, SkeletonImage };
export { SkeletonButton, SkeletonAvatar, SkeletonInput, SkeletonImage, SkeletonTitle };
export default Skeleton as typeof Skeleton &
Plugin & {
readonly Button: typeof SkeletonButton;

View File

@ -46,3 +46,19 @@ cover: https://gw.alipayobjects.com/zos/alicdn/KpcciCJgv/Skeleton.svg
| --- | --- | --- | --- |
| rows | 设置段落占位图的行数 | number | - |
| width | 设置段落占位图的宽度,若为数组时则为对应的每行宽度,反之则是最后一行的宽度 | number \| string \| Array<number \| string> | - |
### SkeletonButtonProps (3.0+)
| 属性 | 说明 | 类型 | 默认值 | 版本 |
| ------ | ------------------------------ | -------------------------------- | ------ | ---- |
| active | 是否展示动画效果 | boolean | false | |
| block | 将按钮宽度调整为其父宽度的选项 | boolean | false | |
| shape | 指定按钮的形状 | `circle` \| `round` \| `default` | - | |
| size | 设置按钮的大小 | `large` \| `small` \| `default` | - | |
### SkeletonInputProps (3.0+)
| 属性 | 说明 | 类型 | 默认值 |
| ------ | ---------------- | ------------------------------- | ------ |
| active | 是否展示动画效果 | boolean | false |
| size | 设置输入框的大小 | `large` \| `small` \| `default` | - |

View File

@ -109,6 +109,15 @@
}
}
// Skeleton Block Button
&.@{skeleton-prefix-cls}-block {
width: 100%;
.@{skeleton-button-prefix-cls} {
width: 100%;
}
}
// Skeleton element
&-element {
display: inline-block;
@ -214,10 +223,12 @@
.skeleton-element-button-size(@size) {
width: @size * 2;
min-width: @size * 2;
.skeleton-element-common-size(@size);
&.@{skeleton-button-prefix-cls}-circle {
width: @size;
min-width: @size;
border-radius: 50%;
}
@ -260,6 +271,7 @@
0% {
background-position: 100% 50%;
}
100% {
background-position: 0 50%;
}

View File

@ -41,6 +41,7 @@
0% {
background-position: 0% 50%;
}
100% {
background-position: 100% 50%;
}

View File

@ -1,5 +1,5 @@
// debugger tsx
import Demo from '../../components/upload/demo/defaultFileList.vue';
import Demo from '../../components/select/demo/field-names.vue';
// import Demo from './demo/demo.vue';
export default {