refactor(components): refactor button (#5933)

* refactor(components): refactor button

* refactor: rename

* test: apply jsx

* feat: expose

* test: fix
This commit is contained in:
三咲智子 2022-02-12 18:37:16 +08:00 committed by GitHub
parent 0000686bbf
commit ea812ae622
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 141 additions and 178 deletions

View File

@ -1,8 +1,9 @@
import { ref, h, nextTick, defineComponent } from 'vue'
import { ref, nextTick, defineComponent } from 'vue'
import { mount } from '@vue/test-utils'
import { Loading, Search } from '@element-plus/icons-vue'
import Button from '../src/button.vue'
import ButtonGroup from '../src/button-group.vue'
import type { ComponentSize } from '@element-plus/constants'
const AXIOM = 'Rem is the best girl'
@ -83,7 +84,7 @@ describe('Button.vue', () => {
default: '<span class="inner-slot"></span>',
},
})
await (<HTMLElement>wrapper.element.querySelector('.inner-slot')).click()
wrapper.element.querySelector<HTMLElement>('.inner-slot')!.click()
expect(wrapper.emitted()).toBeDefined()
})
@ -119,25 +120,15 @@ describe('Button.vue', () => {
it('loading slot', () => {
const App = defineComponent({
setup() {
return () =>
h(
Button,
{
loading: true,
},
{
default: 'Loading',
loading: h(
'span',
{
class: 'custom-loading',
},
['111']
),
}
)
},
setup: () => () =>
(
<Button
v-slots={{ loading: <span class="custom-loading">111</span> }}
loading={true}
>
Loading
</Button>
),
})
const wrapper = mount(App)
expect(wrapper.find('.custom-loading').exists()).toBeTruthy()
@ -146,30 +137,28 @@ describe('Button.vue', () => {
describe('Button Group', () => {
it('create', () => {
const wrapper = mount({
template: `
<el-button-group>
<el-button type="primary">Prev</el-button>
<el-button type="primary">Next</el-button>
</el-button-group>`,
components: {
'el-button-group': ButtonGroup,
'el-button': Button,
},
setup: () => () =>
(
<ButtonGroup>
<Button type="primary">Prev</Button>
<Button type="primary">Next</Button>
</ButtonGroup>
),
})
expect(wrapper.classes()).toContain('el-button-group')
expect(wrapper.findAll('button').length).toBe(2)
})
it('button group reactive size', async () => {
const size = ref('small')
const size = ref<ComponentSize>('small')
const wrapper = mount({
setup() {
return () =>
h(ButtonGroup, { size: size.value }, () => [
h(Button, { type: 'primary' }, () => 'Prev'),
h(Button, { type: 'primary' }, () => 'Next'),
])
},
setup: () => () =>
(
<ButtonGroup size={size.value}>
<Button type="primary">Prev</Button>
<Button type="primary">Next</Button>
</ButtonGroup>
),
})
expect(wrapper.classes()).toContain('el-button-group')
expect(
@ -186,13 +175,13 @@ describe('Button Group', () => {
it('button group type', async () => {
const wrapper = mount({
setup() {
return () =>
h(ButtonGroup, { type: 'warning' }, () => [
h(Button, { type: 'primary' }, () => 'Prev'),
h(Button, {}, () => 'Next'),
])
},
setup: () => () =>
(
<ButtonGroup type="warning">
<Button type="primary">Prev</Button>
<Button>Next</Button>
</ButtonGroup>
),
})
expect(wrapper.classes()).toContain('el-button-group')
expect(

View File

@ -4,7 +4,7 @@ import { Loading } from '@element-plus/icons-vue'
import type { ExtractPropTypes } from 'vue'
import type button from './button.vue'
export const buttonType = [
export const buttonTypes = [
'default',
'primary',
'success',
@ -14,14 +14,14 @@ export const buttonType = [
'text',
'',
] as const
export const buttonNativeType = ['button', 'submit', 'reset'] as const
export const buttonNativeTypes = ['button', 'submit', 'reset'] as const
export const buttonProps = buildProps({
size: useSizeProp,
disabled: Boolean,
type: {
type: String,
values: buttonType,
values: buttonTypes,
default: '',
},
icon: {
@ -30,7 +30,7 @@ export const buttonProps = buildProps({
},
nativeType: {
type: String,
values: buttonNativeType,
values: buttonNativeTypes,
default: 'button',
},
loading: Boolean,
@ -48,11 +48,6 @@ export const buttonProps = buildProps({
default: undefined,
},
} as const)
export interface ButtonConfigContext {
autoInsertSpace?: boolean
}
export const buttonEmits = {
click: (evt: MouseEvent) => evt instanceof MouseEvent,
}
@ -64,3 +59,7 @@ export type ButtonType = ButtonProps['type']
export type ButtonNativeType = ButtonProps['nativeType']
export type ButtonInstance = InstanceType<typeof button>
export interface ButtonConfigContext {
autoInsertSpace?: boolean
}

View File

@ -1,17 +1,17 @@
<template>
<button
ref="buttonRef"
ref="_ref"
:class="[
ns.b(),
ns.m(buttonType),
ns.m(buttonSize),
ns.is('disabled', buttonDisabled),
ns.m(_type),
ns.m(_size),
ns.is('disabled', _disabled),
ns.is('loading', loading),
ns.is('plain', plain),
ns.is('round', round),
ns.is('circle', circle),
]"
:disabled="buttonDisabled || loading"
:disabled="_disabled || loading"
:autofocus="autofocus"
:type="nativeType"
:style="buttonStyle"
@ -35,8 +35,8 @@
</button>
</template>
<script lang="ts">
import { computed, inject, defineComponent, Text, ref } from 'vue'
<script lang="ts" setup>
import { computed, inject, Text, ref, useSlots } from 'vue'
import { useCssVar } from '@vueuse/core'
import { TinyColor } from '@ctrl/tinycolor'
import { ElIcon } from '@element-plus/components/icon'
@ -48,120 +48,102 @@ import {
useSize,
} from '@element-plus/hooks'
import { buttonGroupContextKey } from '@element-plus/tokens'
import { Loading } from '@element-plus/icons-vue'
import { buttonEmits, buttonProps } from './button'
export default defineComponent({
defineOptions({
name: 'ElButton',
})
components: {
ElIcon,
Loading,
},
const props = defineProps(buttonProps)
const emit = defineEmits(buttonEmits)
const slots = useSlots()
props: buttonProps,
emits: buttonEmits,
const buttonGroupContext = inject(buttonGroupContextKey, undefined)
const globalConfig = useGlobalConfig('button')
const ns = useNamespace('button')
const { form } = useFormItem()
const _size = useSize(computed(() => buttonGroupContext?.size))
const _disabled = useDisabled()
const _ref = ref<HTMLButtonElement>()
setup(props, { emit, slots }) {
const buttonRef = ref()
const buttonGroupContext = inject(buttonGroupContextKey, undefined)
const globalConfig = useGlobalConfig('button')
const ns = useNamespace('button')
const autoInsertSpace = computed(
() =>
props.autoInsertSpace ?? globalConfig.value?.autoInsertSpace ?? false
)
const _type = computed(() => props.type || buttonGroupContext?.type || '')
const autoInsertSpace = computed(
() => props.autoInsertSpace ?? globalConfig.value?.autoInsertSpace ?? false
)
// add space between two characters in Chinese
const shouldAddSpace = computed(() => {
const defaultSlot = slots.default?.()
if (autoInsertSpace.value && defaultSlot?.length === 1) {
const slot = defaultSlot[0]
if (slot?.type === Text) {
const text = slot.children
return /^\p{Unified_Ideograph}{2}$/u.test(text as string)
}
// add space between two characters in Chinese
const shouldAddSpace = computed(() => {
const defaultSlot = slots.default?.()
if (autoInsertSpace.value && defaultSlot?.length === 1) {
const slot = defaultSlot[0]
if (slot?.type === Text) {
const text = slot.children
return /^\p{Unified_Ideograph}{2}$/u.test(text as string)
}
}
return false
})
// calculate hover & active color by color
const typeColor = computed(() => useCssVar(`--el-color-${props.type}`).value)
const buttonStyle = computed(() => {
let styles: Record<string, string> = {}
const buttonColor = props.color || typeColor.value
if (buttonColor) {
const color = new TinyColor(buttonColor)
const shadeBgColor = color.shade(10).toString()
if (props.plain) {
styles = {
'--el-button-bg-color': color.tint(90).toString(),
'--el-button-text-color': buttonColor,
'--el-button-hover-text-color': 'var(--el-color-white)',
'--el-button-hover-bg-color': buttonColor,
'--el-button-hover-border-color': buttonColor,
'--el-button-active-bg-color': shadeBgColor,
'--el-button-active-text-color': 'var(--el-color-white)',
'--el-button-active-border-color': shadeBgColor,
}
return false
})
const { form } = useFormItem()
const buttonSize = useSize(computed(() => buttonGroupContext?.size))
const buttonDisabled = useDisabled()
const buttonType = computed(
() => props.type || buttonGroupContext?.type || ''
)
// calculate hover & active color by color
const typeColor = computed(
() => useCssVar(`--el-color-${props.type}`).value
)
const buttonStyle = computed(() => {
let styles = {}
const buttonColor = props.color || typeColor.value
if (buttonColor) {
const shadeBgColor = new TinyColor(buttonColor).shade(10).toString()
if (props.plain) {
styles = {
'--el-button-bg-color': new TinyColor(buttonColor)
.tint(90)
.toString(),
'--el-button-text-color': buttonColor,
'--el-button-hover-text-color': 'var(--el-color-white)',
'--el-button-hover-bg-color': buttonColor,
'--el-button-hover-border-color': buttonColor,
'--el-button-active-bg-color': shadeBgColor,
'--el-button-active-text-color': 'var(--el-color-white)',
'--el-button-active-border-color': shadeBgColor,
}
} else {
const tintBgColor = new TinyColor(buttonColor).tint(20).toString()
styles = {
'--el-button-bg-color': buttonColor,
'--el-button-border-color': buttonColor,
'--el-button-hover-bg-color': tintBgColor,
'--el-button-hover-border-color': tintBgColor,
'--el-button-active-bg-color': shadeBgColor,
'--el-button-active-border-color': shadeBgColor,
}
}
if (buttonDisabled.value) {
const disabledButtonColor = new TinyColor(buttonColor)
.tint(50)
.toString()
styles['--el-button-disabled-bg-color'] = disabledButtonColor
styles['--el-button-disabled-border-color'] = disabledButtonColor
}
} else {
const tintBgColor = color.tint(20).toString()
styles = {
'--el-button-bg-color': buttonColor,
'--el-button-border-color': buttonColor,
'--el-button-hover-bg-color': tintBgColor,
'--el-button-hover-border-color': tintBgColor,
'--el-button-active-bg-color': shadeBgColor,
'--el-button-active-border-color': shadeBgColor,
}
return styles
})
const handleClick = (evt: MouseEvent) => {
if (props.nativeType === 'reset') {
form?.resetFields()
}
emit('click', evt)
}
return {
buttonRef,
buttonStyle,
buttonSize,
buttonType,
buttonDisabled,
shouldAddSpace,
handleClick,
ns,
if (_disabled.value) {
const disabledButtonColor = color.tint(50).toString()
styles['--el-button-disabled-bg-color'] = disabledButtonColor
styles['--el-button-disabled-border-color'] = disabledButtonColor
}
},
}
return styles
})
const handleClick = (evt: MouseEvent) => {
if (props.nativeType === 'reset') {
form?.resetFields()
}
emit('click', evt)
}
defineExpose({
/** @description button html element */
ref: _ref,
/** @description button size */
size: _size,
/** @description button type */
type: _type,
/** @description button disabled */
disabled: _disabled,
/** @description whether adding space */
shouldAddSpace,
})
</script>

View File

@ -1,4 +1,4 @@
import { buttonType } from '@element-plus/components/button'
import { buttonTypes } from '@element-plus/components/button'
import { QuestionFilled } from '@element-plus/icons-vue'
import { buildProps, definePropType, iconPropType } from '@element-plus/utils'
import { useTooltipContentProps } from '@element-plus/components/tooltip'
@ -16,12 +16,12 @@ export const popconfirmProps = buildProps({
},
confirmButtonType: {
type: String,
values: buttonType,
values: buttonTypes,
default: 'primary',
},
cancelButtonType: {
type: String,
values: buttonType,
values: buttonTypes,
default: 'text',
},
icon: {

View File

@ -34,17 +34,10 @@ const mountComponent = (setup = NOOP, options = {}) => {
)
}
const getButtonVm = (wrapper: ReturnType<typeof mountComponent>) => {
return wrapper.findComponent(ElButton).vm as any as {
buttonSize: string
buttonDisabled: boolean
}
}
describe('use-form-item', () => {
it('should return local value', () => {
const wrapper = mountComponent()
expect(getButtonVm(wrapper).buttonSize).toBe('default')
expect(wrapper.find('.el-button--default').exists()).toBe(true)
})
it('should return props.size instead of injected.size', () => {
@ -62,7 +55,7 @@ describe('use-form-item', () => {
}
)
expect(getButtonVm(wrapper).buttonSize).toBe(propSize)
expect(wrapper.find(`.el-button--${propSize}`).exists()).toBe(true)
})
it('should return fallback.size instead inject.size', () => {
@ -77,7 +70,7 @@ describe('use-form-item', () => {
} as ElFormItemContext)
})
expect(getButtonVm(wrapper).buttonSize).toBe(fallbackSize)
expect(wrapper.find(`.el-button--${fallbackSize}`).exists()).toBe(true)
})
it('should return formItem.size instead form.size', () => {
@ -92,6 +85,6 @@ describe('use-form-item', () => {
} as ElFormContext)
})
expect(getButtonVm(wrapper).buttonSize).toBe(itemSize)
expect(wrapper.find(`.el-button--${itemSize}`).exists()).toBe(true)
})
})