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 Badge } from './badge'
|
||||
|
||||
export { default as Tabs } from './tabs'
|
||||
|
@ -8,3 +8,4 @@ import './rate/style'
|
||||
import './pagination/style'
|
||||
import './avatar/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 TabPane from './TabPane'
|
||||
import TabContent from './TabContent'
|
||||
|
||||
// import TabContent from './TabContent'
|
||||
Tabs.TabPane = TabPane
|
||||
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) {
|
||||
style.transform = v
|
||||
|
Loading…
Reference in New Issue
Block a user