feat(tabs): add label slot for pane & function + vnode typed label

This commit is contained in:
07akioni 2021-05-25 22:41:06 +08:00
parent 64b1715526
commit 826bf1dccb
8 changed files with 71 additions and 34 deletions

View File

@ -6,6 +6,8 @@
- `n-tabs` add `on-close` prop.
- `n-tabs` add `on-add` prop.
- `n-tabs` add `label` slot.
- `n-tab-pane`'s `label` prop support render function & VNode.
## 2.9.0

View File

@ -6,6 +6,8 @@
- `n-tabs` 新增 `on-close` 属性
- `n-tabs` 新增 `on-add` 属性
- `n-tab-pane` 新增 `label` slot
- `n-tab-pane``label` 属性支持渲染函数和 VNode
## 2.9.0

View File

@ -7,6 +7,7 @@ export {
getVNodeChildren,
keysOf,
render,
render as Render,
getFirstSlotVNode
} from './vue'
export type { MaybeArray } from './vue'

View File

@ -40,15 +40,22 @@ addable
| closable | `boolean` | `false` | Whether to allow tab to close, Only works when tabs type is `'card'`. |
| disabled | `boolean` | `false` | |
| display-directive | `'if' \| 'show'` | `'if'` | The directive to use in conditionally rendering. 'if' will use 'v-if' and 'show' will use 'v-show'. When use show directive, the status of tab won't be reset after tab changes. |
| label | `string` | `undefined` | |
| label | `string \| VNode \| () => VNodeChild` | `undefined` | |
| name | `string \| number` | required | |
## Slots
### Tabs, Tab Pane Slots
### Tabs Slots
| Name | Parameters | Description |
| ------- | ---------- | ----------- |
| default | `()` | |
| prefix | `()` | |
| suffux | `()` | |
### Tab Pane Slots
| Name | Parameters | Description |
| ------- | ---------- | ----------- |
| default | `()` | |
| label | `()` | |

View File

@ -41,15 +41,22 @@ line-debug
| closable | `boolean` | `false` | 是否允许 tab 关闭,只在 tabs type 为 `'card'` 时生效 |
| disabled | `boolean` | `false` | |
| display-directive | `'if' \| 'show'` | `'if'` | 选择性渲染使用的指令。if 对应 v-ifshow 对应 v-show使用 show 的时候标签页状态切换后不会被重置 |
| label | `string` | `undefined` | |
| label | `string \| VNode \| () => VNodeChild` | `undefined` | |
| name | `string \| number` | required | |
## Slots
### Tabs, Tab Pane Slots
### Tabs
| 名称 | 参数 | 说明 |
| ------- | ---- | ---- |
| default | `()` | |
| prefix | `()` | |
| suffux | `()` | |
### Tab Pane Slots
| 名称 | 参数 | 说明 |
| ------- | ---- | ---- |
| default | `()` | |
| label | `()` | |

View File

@ -1,6 +1,7 @@
import { h, defineComponent, inject, computed } from 'vue'
import { NBaseClose, NBaseIcon } from '../../_internal'
import { AddIcon } from '../../_internal/icons'
import { NBaseClose, NBaseIcon } from '../../_internal'
import { Render } from '../../_utils'
import { tabsInjectionKey } from './interface'
import { tabPaneProps } from './TabPane'
@ -60,7 +61,8 @@ export default defineComponent({
label,
value,
mergedClosable,
style
style,
$slots: { default: defaultSlot }
} = this
return (
<div class={`${clsPrefix}-tabs-tab-wrapper`}>
@ -90,8 +92,12 @@ export default defineComponent({
default: () => <AddIcon />
}}
</NBaseIcon>
) : defaultSlot ? (
defaultSlot()
) : typeof label === 'object' ? (
label // VNode
) : (
label ?? name
<Render render={label ?? name} />
)}
</span>
{mergedClosable && this.type === 'card' ? (

View File

@ -1,17 +1,12 @@
import {
h,
withDirectives,
vShow,
defineComponent,
inject,
PropType
} from 'vue'
import { h, defineComponent, inject, PropType, VNodeChild, VNode } from 'vue'
import { throwError } from '../../_utils'
import { tabsInjectionKey } from './interface'
import type { ExtractPublicPropTypes } from '../../_utils'
export const tabPaneProps = {
label: [String, Number] as PropType<string | number>,
label: [String, Number, Object, Function] as PropType<
string | number | VNode | (() => VNodeChild)
>,
name: {
type: [String, Number] as PropType<string | number>,
required: true
@ -39,22 +34,10 @@ export default defineComponent({
throwError('tab-pane', '`n-tab-pane` must be placed inside `n-tabs`.')
}
return {
mergedClsPrefix: NTab.mergedClsPrefixRef,
type: NTab.typeRef,
value: NTab.valueRef
mergedClsPrefix: NTab.mergedClsPrefixRef
}
},
render () {
const { name } = this
const useVShow = this.displayDirective === 'show'
const show = this.value === name
return useVShow || show
? withDirectives(
<div class={`${this.mergedClsPrefix}-tab-panel`} key={name}>
{this.$slots}
</div>,
[[vShow, !useVShow || show]]
)
: null
return <div class={`${this.mergedClsPrefix}-tab-panel`}>{this.$slots}</div>
}
})

View File

@ -10,7 +10,9 @@ import {
toRef,
ComponentPublicInstance,
VNode,
nextTick
nextTick,
withDirectives,
vShow
} from 'vue'
import { VResizeObserver, VXScroll } from 'vueuc'
import { throttle } from 'lodash-es'
@ -205,7 +207,6 @@ export default defineComponent({
} = entry
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const containerWidth = target.parentElement!.offsetWidth
console.log(width, containerWidth)
if (!addTabFixedRef.value) {
if (containerWidth < width) {
addTabFixedRef.value = true
@ -365,7 +366,11 @@ export default defineComponent({
leftPadded={
index !== 0 && !mergedJustifyContent
}
/>
>
{{
default: tabPaneVNode.children.label
}}
</Tab>
)
})}
{!addTabFixed && addable && !isLine
@ -419,12 +424,36 @@ export default defineComponent({
<div class={`${mergedClsPrefix}-tabs-nav__suffix`}>{suffix}</div>
) : null}
</div>
{children}
{filterMapTabPanes(children, this.mergedValue)}
</div>
)
}
})
function filterMapTabPanes (
tabPaneVNodes: VNode[],
value: string | number | undefined
): VNode[] {
const children: VNode[] = []
tabPaneVNodes.forEach((vNode) => {
const { name, displayDirective } = vNode.props as {
name: string | number
displayDirective: 'show' | 'if' | undefined
}
const useVShow = displayDirective === 'show'
const show = value === name
if (vNode.key !== undefined) {
vNode.key = name
}
if (useVShow) {
children.push(withDirectives(vNode, [[vShow, show]]))
} else if (show) {
children.push(vNode)
}
})
return children
}
function createAddTag (addable: Addable, leftPadded: boolean): VNode {
return (
<Tab