feat(spin): icon slot, rotate prop (#289)

* feat(spin): add icon slot

* doc(changelog

* fix(spin): icon slot logic

* test(spin): add icon slot test

* fix(spin): refact code

* feat(spin): add rotate prop

Co-authored-by: wanli.song@tusimple.ai <wanli.song@tusimple.ai>
Co-authored-by: 07akioni <07akioni2@gmail.com>
This commit is contained in:
Wanli Song 2021-07-01 23:57:16 +08:00 committed by GitHub
parent 1a354081ba
commit 21698de271
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 181 additions and 25 deletions

View File

@ -13,6 +13,8 @@
- `n-upload` add `before-upload` prop.
- `n-image` add `alt` prop.
- Support the enter key on the numeric keypad.
- `n-spin` support `icon` slot for icon customizing, closes[#260](https://github.com/TuSimple/naive-ui/issues/260).
- `n-spin` add `rotate` prop fro slot icon to rotate.
- `n-form` export `FormItemRule` & `FormRules` type.
### Fixes

View File

@ -13,6 +13,8 @@
- `n-upload` 新增 `before-upload` 属性
- `n-image` 新增 `alt` 属性.
- 支持小键盘的 enter 键
- `n-spin` 支持 `icon` 插槽为了自定义加载图标closes[#260](https://github.com/TuSimple/naive-ui/issues/260)
- `n-spin` 新增 `rotate` 属性控制自定义加载图标是否有旋转动画
- `n-form` 导出 `FormItemRule` & `FormRules` 类型
### Fixes

View File

@ -33,13 +33,17 @@ export default defineComponent({
show: {
type: Boolean,
default: true
},
rotate: {
type: Boolean,
default: true
}
},
setup (props) {
useStyle('BaseLoading', style, toRef(props, 'clsPrefix'))
},
render () {
const { clsPrefix, radius, strokeWidth, stroke, scale } = this
const { clsPrefix, radius, strokeWidth, stroke, scale, $slots } = this
const scaledRadius = radius / scale
return (
<div class={`${clsPrefix}-base-loading`} role="img" aria-label="loading">
@ -47,6 +51,13 @@ export default defineComponent({
{{
default: () =>
this.show ? (
$slots.icon ?
<div class={[
`${clsPrefix}-base-loading__icon-slot`,
this.rotate && `${clsPrefix}-base-loading__icon-slot--rotate`
]}>
{$slots.icon()}
</div> :
<svg
class={`${clsPrefix}-base-loading__icon`}
viewBox={`0 0 ${2 * scaledRadius} ${2 * scaledRadius}`}

View File

@ -1,28 +1,44 @@
import { cB, cE } from '../../../../_utils/cssr'
import { c, cB, cE, cM } from '../../../../_utils/cssr'
import iconSwitchTransition from '../../../../_styles/transitions/icon-switch.cssr'
export default cB('base-loading', `
position: relative;
line-height: 0;
width: 1em;
height: 1em;
`, [
cE('placeholder', {
position: 'absolute',
left: '50%',
top: '50%',
transform: 'translateX(-50%) translateY(-50%)'
}, [
iconSwitchTransition({
export default c([
c('@keyframes icon-rotate-animation', `
0% {
transform:rotate(0deg);
}
100% {
transform:rotate(360deg);
}
`),
cB('base-loading', `
position: relative;
line-height: 0;
width: 1em;
height: 1em;
`, [
cE('placeholder', {
position: 'absolute',
left: '50%',
top: '50%',
originalTransform: 'translateX(-50%) translateY(-50%)'
})
]),
cE('icon', `
height: 1em;
width: 1em;
`, [
iconSwitchTransition()
transform: 'translateX(-50%) translateY(-50%)'
}, [
iconSwitchTransition({
left: '50%',
top: '50%',
originalTransform: 'translateX(-50%) translateY(-50%)'
})
]),
cE('icon', `
height: 1em;
width: 1em;
`, [
iconSwitchTransition()
]),
cE('icon-slot', [
cM('rotate', `
display: inline-block;
animation: icon-rotate-animation 1s linear infinite;
`)
])
])
])

View File

@ -0,0 +1,40 @@
# Customize Icon
```html
<n-space vertical>
<n-spin size="small">
<template #icon>
<n-icon>
<Reload />
</n-icon>
</template>
</n-spin>
<n-spin :show="show">
<n-alert title="La La La" type="success">
Leave it till tomorrow to unpack my case. Honey disconnect the phone.
</n-alert>
<template #icon>
<n-icon>
<Reload />
</n-icon>
</template>
</n-spin>
<n-button @click="show = !show">Click to Spin</n-button>
</n-space>
```
```js
import { ref, defineComponent } from 'vue'
import { Reload } from '@vicons/ionicons5'
export default defineComponent({
components: {
Reload
},
setup () {
return {
show: ref(false)
}
}
})
```

View File

@ -7,12 +7,14 @@ It can be called loading, but why it's called loading? Because there is another
```demo
basic
wrap
customize-icon
```
## Props
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| rotate | `boolean` | `true` | If slot icon is rotate. |
| size | `'small' \| 'medium' \| 'large' \| number` | `'medium'` | |
| show | `boolean` | `true` | If spin is active. |
| stroke-width | `number` | `undefined` | Relative width of spin's stroke, you can assume the outer radius of spin is 100. |
@ -23,3 +25,4 @@ wrap
| Name | Parameters | Description |
| ------- | ---------- | ---------------------------------- |
| default | `()` | If set, spin will wrap the content |
| icon | `()` | Customize the spin icon |

View File

@ -0,0 +1,40 @@
# 自定义图标
```html
<n-space vertical>
<n-spin size="small" >
<template #icon>
<n-icon>
<Reload />
</n-icon>
</template>
</n-spin>
<n-spin :show="show">
<n-alert title="La La La" type="success">
明天再打开行李箱。宝贝,挂电话啦。
</n-alert>
<template #icon>
<n-icon>
<Reload />
</n-icon>
</template>
</n-spin>
<n-button @click="show = !show">点出来加载</n-button>
</n-space>
```
```js
import { ref, defineComponent } from 'vue'
import { Reload } from '@vicons/ionicons5'
export default defineComponent({
components: {
Reload
},
setup () {
return {
show: ref(false)
}
}
})
```

View File

@ -7,12 +7,14 @@
```demo
basic
wrap
customize-icon
```
## Props
| 名称 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| rotate | `boolean` | `true` | 自定义加载图标是否有旋转动画 |
| size | `'small' \| 'medium' \| 'large' \| number` | `'medium'` | |
| show | `boolean` | `true` | Spin 在填入内容的状态是否激活 |
| stroke-width | `number` | `undefined` | Spin 边缘的相对宽度,假定 Spin 的外侧半径是 100 |
@ -23,3 +25,4 @@ wrap
| 名称 | 参数 | 说明 |
| ------- | ---- | ------------------------------- |
| default | `()` | 如果填入Spin 会包裹填入的内容 |
| icon | `()` | 自定义加载图标 |

View File

@ -47,6 +47,10 @@ const spinProps = {
return true
},
default: undefined
},
rotate: {
type: Boolean,
default: true
}
}
@ -117,8 +121,11 @@ export default defineComponent({
clsPrefix={mergedClsPrefix}
stroke={this.stroke}
strokeWidth={this.mergedStrokeWidth}
rotate={this.rotate}
class={`${mergedClsPrefix}-spin`}
/>
>
{{ icon: $slots.icon }}
</NBaseLoading>
) : null
}}
</Transition>
@ -129,8 +136,11 @@ export default defineComponent({
style={this.cssVars as CSSProperties}
stroke={this.stroke}
stroke-width={this.mergedStrokeWidth}
rotate={this.rotate}
class={`${mergedClsPrefix}-spin`}
/>
>
{{ icon: $slots.icon }}
</NBaseLoading>
)
}
})

View File

@ -1,8 +1,37 @@
import { h } from '@vue/runtime-core'
import { mount } from '@vue/test-utils'
import { NSpin } from '../index'
import { Reload } from '@vicons/ionicons5'
import { NIcon } from '../../icon'
describe('n-spin', () => {
it('should work with import on demand', () => {
mount(NSpin)
})
it('should work with icon slot', () => {
const wrapper = mount(NSpin, {
slots: {
icon: () => h(NIcon, null, {
default: () => h(Reload)
})
}
})
expect(wrapper.findComponent(NIcon).exists()).toBe(true)
expect(wrapper.findComponent(Reload).exists()).toBe(true)
})
it('rotate should work on icon slot', async () => {
const wrapper = mount(NSpin, {
slots: {
icon: () => h(NIcon, null, {
default: () => h(Reload)
})
}
})
expect(wrapper.find('.n-base-loading__icon-slot--rotate').exists()).toBe(true)
await wrapper.setProps({
rotate: false
})
expect(wrapper.find('.n-base-loading__icon-slot--rotate').exists()).toBe(false)
})
})