mirror of
https://gitee.com/ant-design-vue/ant-design-vue.git
synced 2024-11-30 02:57:50 +08:00
tabs
This commit is contained in:
parent
b0a21a5b08
commit
3b6008151b
@ -23,3 +23,5 @@ export { default as Tag } from './tag'
|
|||||||
export { default as Avatar } from './avatar'
|
export { default as Avatar } from './avatar'
|
||||||
|
|
||||||
export { default as Badge } from './badge'
|
export { default as Badge } from './badge'
|
||||||
|
|
||||||
|
export { default as Tabs } from './tabs'
|
||||||
|
@ -8,3 +8,4 @@ import './rate/style'
|
|||||||
import './pagination/style'
|
import './pagination/style'
|
||||||
import './avatar/style'
|
import './avatar/style'
|
||||||
import './badge/style'
|
import './badge/style'
|
||||||
|
import './tabs/style'
|
||||||
|
18
components/tabs/KeyCode.js
Normal file
18
components/tabs/KeyCode.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
export default {
|
||||||
|
/**
|
||||||
|
* LEFT
|
||||||
|
*/
|
||||||
|
LEFT: 37, // also NUM_WEST
|
||||||
|
/**
|
||||||
|
* UP
|
||||||
|
*/
|
||||||
|
UP: 38, // also NUM_NORTH
|
||||||
|
/**
|
||||||
|
* RIGHT
|
||||||
|
*/
|
||||||
|
RIGHT: 39, // also NUM_EAST
|
||||||
|
/**
|
||||||
|
* DOWN
|
||||||
|
*/
|
||||||
|
DOWN: 40, // also NUM_SOUTH
|
||||||
|
}
|
7
components/tabs/RefMixin.js
Normal file
7
components/tabs/RefMixin.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export default {
|
||||||
|
saveRef (name) {
|
||||||
|
return node => {
|
||||||
|
this[name] = node
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
75
components/tabs/TabBar.vue
Normal file
75
components/tabs/TabBar.vue
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
<template>
|
||||||
|
<button :class="classes" :disabled="disabled"
|
||||||
|
@click="handleClick" @mouseout="mouseout" @mouseover="mouseover">
|
||||||
|
{{tab}}
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'TabBar',
|
||||||
|
props: {
|
||||||
|
prefixCls: {
|
||||||
|
default: 'ant-tabs',
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
tabBarPosition: {
|
||||||
|
default: 'top',
|
||||||
|
validator (value) {
|
||||||
|
return ['top', 'bottom'].includes(value)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
disabled: Boolean,
|
||||||
|
onKeyDown: Function,
|
||||||
|
onTabClick: Function,
|
||||||
|
activeKey: String,
|
||||||
|
tab: String,
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
sizeMap: {
|
||||||
|
large: 'lg',
|
||||||
|
small: 'sm',
|
||||||
|
},
|
||||||
|
clicked: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
classes () {
|
||||||
|
const { prefixCls, type, shape, size, loading, ghost, clicked, sizeMap } = this
|
||||||
|
const sizeCls = sizeMap[size] || ''
|
||||||
|
return {
|
||||||
|
[`${prefixCls}`]: true,
|
||||||
|
[`${prefixCls}-${type}`]: type,
|
||||||
|
[`${prefixCls}-${shape}`]: shape,
|
||||||
|
[`${prefixCls}-${sizeCls}`]: sizeCls,
|
||||||
|
[`${prefixCls}-loading`]: loading,
|
||||||
|
[`${prefixCls}-clicked`]: clicked,
|
||||||
|
[`${prefixCls}-background-ghost`]: ghost || type === 'ghost',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleClick (event) {
|
||||||
|
if (this.clicked) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.clicked = true
|
||||||
|
clearTimeout(this.timeout)
|
||||||
|
this.timeout = setTimeout(() => (this.clicked = false), 500)
|
||||||
|
this.$emit('click', event)
|
||||||
|
},
|
||||||
|
mouseover (event) {
|
||||||
|
this.$emit('mouseover', event)
|
||||||
|
},
|
||||||
|
mouseout (event) {
|
||||||
|
this.$emit('mouseout', event)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
beforeDestroy () {
|
||||||
|
if (this.timeout) {
|
||||||
|
clearTimeout(this.timeout)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
@ -0,0 +1,66 @@
|
|||||||
|
<script>
|
||||||
|
import {
|
||||||
|
getTransformByIndex,
|
||||||
|
getActiveIndex,
|
||||||
|
getTransformPropValue,
|
||||||
|
getMarginStyle,
|
||||||
|
} from './utils'
|
||||||
|
export default {
|
||||||
|
name: 'TabContent',
|
||||||
|
props: {
|
||||||
|
animated: { type: Boolean, default: true },
|
||||||
|
animatedWithMargin: { type: Boolean, default: true },
|
||||||
|
prefixCls: {
|
||||||
|
default: 'ant-tabs',
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
activeKey: String,
|
||||||
|
tabBarPosition: String,
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
classes () {
|
||||||
|
const { animated, prefixCls } = this
|
||||||
|
return {
|
||||||
|
[`${prefixCls}-content`]: true,
|
||||||
|
[animated
|
||||||
|
? `${prefixCls}-content-animated`
|
||||||
|
: `${prefixCls}-content-no-animated`]: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
},
|
||||||
|
render () {
|
||||||
|
const {
|
||||||
|
activeKey,
|
||||||
|
tabBarPosition, animated, animatedWithMargin, classes,
|
||||||
|
} = this
|
||||||
|
let style = {}
|
||||||
|
if (animated && this.$slots.default) {
|
||||||
|
const activeIndex = getActiveIndex(this.$slots.default, activeKey)
|
||||||
|
if (activeIndex !== -1) {
|
||||||
|
const animatedStyle = animatedWithMargin
|
||||||
|
? getMarginStyle(activeIndex, tabBarPosition)
|
||||||
|
: getTransformPropValue(getTransformByIndex(activeIndex, tabBarPosition))
|
||||||
|
style = animatedStyle
|
||||||
|
} else {
|
||||||
|
style = {
|
||||||
|
display: 'none',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
class={classes}
|
||||||
|
style={style}
|
||||||
|
>
|
||||||
|
{this.$slots.default}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
@ -0,0 +1,48 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
role="tabpanel"
|
||||||
|
:aria-hidden="active ? 'false' : 'true'"
|
||||||
|
:class="classes"
|
||||||
|
>
|
||||||
|
<slot v-if="isRender || forceRender">
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'TabPane',
|
||||||
|
props: {
|
||||||
|
pKey: [String, Number],
|
||||||
|
forceRender: Boolean,
|
||||||
|
// placeholder: [Function, String, Number],
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
const { prefixCls, destroyInactiveTabPane, activeKey } = this.$parent
|
||||||
|
return {
|
||||||
|
rootPrefixCls: prefixCls,
|
||||||
|
destroyInactiveTabPane,
|
||||||
|
active: this.pKey === activeKey,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
classes () {
|
||||||
|
const { rootPrefixCls, active } = this
|
||||||
|
const prefixCls = `${rootPrefixCls}-tabpane`
|
||||||
|
return {
|
||||||
|
[`${prefixCls}`]: true,
|
||||||
|
[`${prefixCls}-inactive`]: !active,
|
||||||
|
[`${prefixCls}-active`]: active,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isRender () {
|
||||||
|
const {
|
||||||
|
destroyInactiveTabPane, active,
|
||||||
|
} = this
|
||||||
|
this._isActived = this._isActived || active
|
||||||
|
return destroyInactiveTabPane ? active : this._isActived
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
@ -0,0 +1,174 @@
|
|||||||
|
<script>
|
||||||
|
import Icon from '../icon'
|
||||||
|
import KeyCode from './KeyCode'
|
||||||
|
import TabBar from './TabBar'
|
||||||
|
import TabContent from './TabContent'
|
||||||
|
function getDefaultActiveKey (t) {
|
||||||
|
let activeKey
|
||||||
|
t.$slot.default.forEach((child) => {
|
||||||
|
if (child && !activeKey && !child.disabled) {
|
||||||
|
activeKey = child.pKey
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return activeKey
|
||||||
|
}
|
||||||
|
function activeKeyIsValid (t, key) {
|
||||||
|
const keys = t.$slot.default.map(child => child && child.pKey)
|
||||||
|
return keys.indexOf(key) >= 0
|
||||||
|
}
|
||||||
|
export default {
|
||||||
|
name: 'Tabs',
|
||||||
|
components: { Icon },
|
||||||
|
props: {
|
||||||
|
prefixCls: {
|
||||||
|
default: 'ant-tabs',
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
tabBarPosition: {
|
||||||
|
default: 'top',
|
||||||
|
validator (value) {
|
||||||
|
return ['top', 'bottom'].includes(value)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
destroyInactiveTabPane: Boolean,
|
||||||
|
activeKey: String,
|
||||||
|
defaultActiveKey: String,
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
stateActiveKey: this.getStateActiveKey(),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
classes () {
|
||||||
|
const { prefixCls, tabBarPosition } = this
|
||||||
|
return {
|
||||||
|
[`${prefixCls}`]: true,
|
||||||
|
[`${prefixCls}-${tabBarPosition}`]: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
beforeUpdate () {
|
||||||
|
if ('activeKey' in this) {
|
||||||
|
this.stateActiveKey = this.activeKey
|
||||||
|
} else if (!activeKeyIsValid(this, this.stateActiveKey)) {
|
||||||
|
this.stateActiveKey = getDefaultActiveKey(this)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getStateActiveKey () {
|
||||||
|
let activeKey
|
||||||
|
if ('activeKey' in this) {
|
||||||
|
activeKey = this.activeKey
|
||||||
|
} else if ('defaultActiveKey' in this) {
|
||||||
|
activeKey = this.defaultActiveKey
|
||||||
|
} else {
|
||||||
|
activeKey = getDefaultActiveKey(this)
|
||||||
|
}
|
||||||
|
return activeKey
|
||||||
|
},
|
||||||
|
onTabClick (activeKey) {
|
||||||
|
if (this.tabBar.props.onTabClick) {
|
||||||
|
this.tabBar.props.onTabClick(activeKey)
|
||||||
|
}
|
||||||
|
this.setActiveKey(activeKey)
|
||||||
|
},
|
||||||
|
|
||||||
|
onNavKeyDown (e) {
|
||||||
|
const eventKeyCode = e.keyCode
|
||||||
|
if (eventKeyCode === KeyCode.RIGHT || eventKeyCode === KeyCode.DOWN) {
|
||||||
|
e.preventDefault()
|
||||||
|
const nextKey = this.getNextActiveKey(true)
|
||||||
|
this.onTabClick(nextKey)
|
||||||
|
} else if (eventKeyCode === KeyCode.LEFT || eventKeyCode === KeyCode.UP) {
|
||||||
|
e.preventDefault()
|
||||||
|
const previousKey = this.getNextActiveKey(false)
|
||||||
|
this.onTabClick(previousKey)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
setActiveKey (activeKey) {
|
||||||
|
if (this.stateActiveKey !== activeKey) {
|
||||||
|
if (!('activeKey' in this)) {
|
||||||
|
this.stateActiveKey = activeKey
|
||||||
|
}
|
||||||
|
this.$emit('change', activeKey)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getNextActiveKey (next) {
|
||||||
|
const activeKey = this.stateActiveKey
|
||||||
|
const children = []
|
||||||
|
this.$slot.default.forEach((c) => {
|
||||||
|
if (c && !c.disabled) {
|
||||||
|
if (next) {
|
||||||
|
children.push(c)
|
||||||
|
} else {
|
||||||
|
children.unshift(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const length = children.length
|
||||||
|
let ret = length && children[0].key
|
||||||
|
children.forEach((child, i) => {
|
||||||
|
if (child.pKey === activeKey) {
|
||||||
|
if (i === length - 1) {
|
||||||
|
ret = children[0].key
|
||||||
|
} else {
|
||||||
|
ret = children[i + 1].key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return ret
|
||||||
|
},
|
||||||
|
},
|
||||||
|
beforeDestroy () {
|
||||||
|
},
|
||||||
|
render () {
|
||||||
|
const {
|
||||||
|
prefixCls,
|
||||||
|
tabBarPosition,
|
||||||
|
destroyInactiveTabPane,
|
||||||
|
onNavKeyDown,
|
||||||
|
onTabClick,
|
||||||
|
stateActiveKey,
|
||||||
|
classes,
|
||||||
|
setActiveKey,
|
||||||
|
$slots,
|
||||||
|
} = this
|
||||||
|
const hasSlot = !!$slots.default
|
||||||
|
const tabBarProps = []
|
||||||
|
if (hasSlot) {
|
||||||
|
$slots.default.forEach(tab => {
|
||||||
|
tab.data && tabBarProps.push(
|
||||||
|
<TabBar
|
||||||
|
{...tab.data}
|
||||||
|
prefixCls={prefixCls}
|
||||||
|
onKeyDown={onNavKeyDown}
|
||||||
|
tabBarPosition={tabBarPosition}
|
||||||
|
onTabClick={onTabClick}
|
||||||
|
activeKey={stateActiveKey} />)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const tabContentProps = {
|
||||||
|
props: {
|
||||||
|
prefixCls,
|
||||||
|
tabBarPosition,
|
||||||
|
activeKey: stateActiveKey,
|
||||||
|
destroyInactiveTabPane,
|
||||||
|
onChange: setActiveKey,
|
||||||
|
key: 'tabContent',
|
||||||
|
}}
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
class={classes}
|
||||||
|
>
|
||||||
|
{tabBarProps}
|
||||||
|
<TabContent {...tabContentProps}>
|
||||||
|
{$slots.default}
|
||||||
|
</TabContent>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
17
components/tabs/demo/basic.vue
Normal file
17
components/tabs/demo/basic.vue
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<Tabs activeKey="test1">
|
||||||
|
<TabPane pKey="test1" tab="tab1">hello</TabPane>
|
||||||
|
<TabPane pKey="test2" tab="tab2">world</TabPane>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import { Tabs } from 'antd'
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
Tabs,
|
||||||
|
TabPane: Tabs.TabPane,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
@ -1,6 +1,6 @@
|
|||||||
import Tabs from './Tabs'
|
import Tabs from './Tabs'
|
||||||
import TabPane from './TabPane'
|
import TabPane from './TabPane'
|
||||||
import TabContent from './TabContent'
|
// import TabContent from './TabContent'
|
||||||
|
Tabs.TabPane = TabPane
|
||||||
export default Tabs
|
export default Tabs
|
||||||
export { TabPane, TabContent }
|
export { TabPane }
|
||||||
|
@ -1,3 +1,29 @@
|
|||||||
|
export function toArray (children) {
|
||||||
|
// allow [c,[a,b]]
|
||||||
|
const c = []
|
||||||
|
children.forEach(child => {
|
||||||
|
if (child.data) {
|
||||||
|
c.push(child)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getActiveIndex (children, activeKey) {
|
||||||
|
const c = toArray(children)
|
||||||
|
for (let i = 0; i < c.length; i++) {
|
||||||
|
const pKey = c[i].pKey || c[i].componentOptions.propsData.pKey
|
||||||
|
if (pKey === activeKey) {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getActiveKey (children, index) {
|
||||||
|
const c = toArray(children)
|
||||||
|
return c[index].pKey
|
||||||
|
}
|
||||||
|
|
||||||
export function setTransform (style, v) {
|
export function setTransform (style, v) {
|
||||||
style.transform = v
|
style.transform = v
|
||||||
|
Loading…
Reference in New Issue
Block a user