feat(tabs): clsPrefix

This commit is contained in:
07akioni 2021-04-17 23:40:57 +08:00
parent e70a6d977a
commit 707f29e370
3 changed files with 137 additions and 113 deletions

View File

@ -1,2 +1,4 @@
export { default as NTabs } from './src/Tabs'
export type { TabsProps } from './src/Tabs'
export { default as NTabPane } from './src/TabPane'
export type { TabPaneProps } from './src/TabPane'

View File

@ -6,10 +6,12 @@ import {
ExtractPropTypes,
inject,
onBeforeUnmount,
computed,
PropType
PropType,
InjectionKey,
Ref
} from 'vue'
import { getSlot } from '../../_utils'
import { getSlot, throwError } from '../../_utils'
import type { ExtractPublicPropTypes } from '../../_utils'
const tabPaneProps = {
label: [String, Number] as PropType<string | number>,
@ -27,43 +29,52 @@ const tabPaneProps = {
}
} as const
export type TabPaneProps = ExtractPropTypes<typeof tabPaneProps>
export type TabPaneSetupProps = ExtractPropTypes<typeof tabPaneProps>
export type TabPaneProps = ExtractPublicPropTypes<typeof tabPaneProps>
export interface TabsInjection {
value: string | number | null
type: 'line' | 'card'
addPanel: (props: TabPaneProps) => void
removePanel: (props: TabPaneProps) => void
cPrefixRef: Ref<string>
valueRef: Ref<string | number | null>
typeRef: Ref<'line' | 'card'>
addPanel: (props: TabPaneSetupProps) => void
removePanel: (props: TabPaneSetupProps) => void
}
export const tabsInjectionKey: InjectionKey<TabsInjection> = Symbol('tabs')
export default defineComponent({
name: 'TabPane',
alias: ['TabPanel'],
props: tabPaneProps,
setup (props) {
const NTab = inject<TabsInjection>('NTabs') as TabsInjection
const NTab = inject(tabsInjectionKey, null)
if (!NTab) {
throwError('tab-pane', '`n-tab-pane` must be placed inside `n-tabs`.')
}
NTab.addPanel(props)
onBeforeUnmount(() => {
NTab.removePanel(props)
})
return {
type: computed(() => NTab.type),
show: computed(() => props.name === NTab.value)
cPrefix: NTab.cPrefixRef,
type: NTab.typeRef,
value: NTab.valueRef
}
},
render () {
const useVShow = this.displayDirective === 'show'
return useVShow || this.show
const show = this.value === this.name
return useVShow || show
? withDirectives(
h(
'div',
{
class: 'n-tab-panel',
class: `${this.cPrefix}-tab-panel`,
key: this.name
},
getSlot(this)
),
[[vShow, !useVShow || this.show]]
[[vShow, !useVShow || show]]
)
: null
}

View File

@ -8,98 +8,105 @@ import {
CSSProperties,
watch,
nextTick,
reactive,
toRef,
Ref,
renderSlot
} from 'vue'
import { VResizeObserver, VXScroll } from 'vueuc'
import { throttle } from 'lodash-es'
import { useCompitable, onFontsReady, useMergedState } from 'vooks'
import { NBaseClose } from '../../_internal'
import { useTheme } from '../../_mixins'
import { useConfig, useTheme } from '../../_mixins'
import type { ThemeProps } from '../../_mixins'
import { warn, createKey, call } from '../../_utils'
import type { MaybeArray } from '../../_utils'
import type { MaybeArray, ExtractPublicPropTypes } from '../../_utils'
import { tabsLight } from '../styles'
import type { TabsTheme } from '../styles'
import type { TabsInjection, TabPaneProps } from './TabPane'
import { TabPaneSetupProps, tabsInjectionKey } from './TabPane'
import type { OnUpdateValueImpl } from './interface'
import style from './styles/index.cssr'
const tabsProps = {
...(useTheme.props as ThemeProps<TabsTheme>),
value: [String, Number] as PropType<string | number>,
defaultValue: {
type: [String, Number] as PropType<string | number | null>,
default: null
},
type: {
type: String as PropType<'line' | 'card'>,
default: 'line'
},
closable: {
type: Boolean,
default: false
},
justifyContent: String as PropType<
'space-between' | 'space-around' | 'space-evenly'
>,
labelSize: {
type: String as PropType<'small' | 'medium' | 'large' | 'huge'>,
default: 'medium'
},
navStyle: [String, Object] as PropType<string | CSSProperties>,
onScrollableChange: [Function, Array] as PropType<
MaybeArray<(value: boolean) => void>
>,
// eslint-disable-next-line vue/prop-name-casing
'onUpdate:value': [Function, Array] as PropType<
MaybeArray<(value: string & number) => void>
>,
onUpdateValue: [Function, Array] as PropType<
MaybeArray<(value: string & number) => void>
>,
onClose: [Function, Array] as PropType<MaybeArray<() => void>>,
// deprecated
activeName: {
type: [String, Number] as PropType<string | number | undefined>,
validator: () => {
if (__DEV__) {
warn('tabs', '`active-name` is deprecated, please use `value` instead.')
}
return true
},
default: undefined
},
onActiveNameChange: {
type: [Function, Array] as PropType<
MaybeArray<(value: string & number) => void> | undefined
>,
validator: () => {
if (__DEV__) {
warn(
'tabs',
'`on-active-name-change` is deprecated, please use `on-update:value` instead.'
)
}
return true
},
default: undefined
}
} as const
export type TabsProps = ExtractPublicPropTypes<typeof tabsProps>
export default defineComponent({
name: 'Tabs',
props: {
...(useTheme.props as ThemeProps<TabsTheme>),
value: [String, Number] as PropType<string | number>,
defaultValue: {
type: [String, Number] as PropType<string | number | null>,
default: null
},
type: {
type: String as PropType<'line' | 'card'>,
default: 'line'
},
closable: {
type: Boolean,
default: false
},
justifyContent: String as PropType<
'space-between' | 'space-around' | 'space-evenly'
>,
labelSize: {
type: String as PropType<'small' | 'medium' | 'large' | 'huge'>,
default: 'medium'
},
navStyle: [String, Object] as PropType<string | CSSProperties>,
onScrollableChange: [Function, Array] as PropType<
MaybeArray<(value: boolean) => void>
>,
// eslint-disable-next-line vue/prop-name-casing
'onUpdate:value': [Function, Array] as PropType<
MaybeArray<(value: string & number) => void>
>,
onUpdateValue: [Function, Array] as PropType<
MaybeArray<(value: string & number) => void>
>,
onClose: [Function, Array] as PropType<MaybeArray<() => void>>,
// deprecated
activeName: {
type: [String, Number] as PropType<string | number | undefined>,
validator: () => {
if (__DEV__) {
warn(
'tabs',
'`active-name` is deprecated, please use `value` instead.'
)
}
return true
},
default: undefined
},
onActiveNameChange: {
type: [Function, Array] as PropType<
MaybeArray<(value: string & number) => void> | undefined
>,
validator: () => {
if (__DEV__) {
warn(
'tabs',
'`on-active-name-change` is deprecated, please use `on-update:value` instead.'
)
}
return true
},
default: undefined
}
},
props: tabsProps,
setup (props) {
const themeRef = useTheme('Tabs', 'Tabs', style, tabsLight, props)
const { mergedClsPrefix } = useConfig(props)
const themeRef = useTheme(
'Tabs',
'Tabs',
style,
tabsLight,
props,
mergedClsPrefix
)
const labelWrapperRef = ref<HTMLElement | null>(null)
const labelBarRef = ref<HTMLElement | null>(null)
const panelsRef = ref<TabPaneProps[]>([])
const panelsRef = ref<TabPaneSetupProps[]>([])
const transitionDisabledRef = ref(false)
const compitableValueRef = useCompitable(props, ['activeName', 'value'])
const uncontrolledValueRef = ref(props.defaultValue)
@ -120,10 +127,10 @@ export default defineComponent({
updateCurrentBarPosition()
})
function addPanel (panelProps: TabPaneProps): void {
function addPanel (panelProps: TabPaneSetupProps): void {
panelsRef.value.push(panelProps)
}
function removePanel (panelProps: TabPaneProps): void {
function removePanel (panelProps: TabPaneSetupProps): void {
const index = panelsRef.value.findIndex(
(panel) => panel.name === panelProps.name
)
@ -171,11 +178,13 @@ export default defineComponent({
onUpdateValue,
'onUpdate:value': _onUpdateValue
} = props
if (onActiveNameChange) { call(onActiveNameChange as OnUpdateValueImpl, panelName) }
if (onActiveNameChange) {
call(onActiveNameChange as OnUpdateValueImpl, panelName)
}
if (onUpdateValue) call(onUpdateValue as OnUpdateValueImpl, panelName)
if (_onUpdateValue) call(_onUpdateValue as OnUpdateValueImpl, panelName)
}
function handleCloseClick (e: MouseEvent, panel: TabPaneProps): void {
function handleCloseClick (e: MouseEvent, panel: TabPaneSetupProps): void {
const { onClose } = props
if (onClose) call(onClose, panel.name)
e.stopPropagation()
@ -191,19 +200,18 @@ export default defineComponent({
})
}
}, 64)
provide<TabsInjection>(
'NTabs',
reactive({
type: toRef(props, 'type') as Ref<'line' | 'card'>,
value: mergedValueRef,
removePanel,
addPanel
})
)
provide(tabsInjectionKey, {
cPrefixRef: mergedClsPrefix,
typeRef: toRef(props, 'type'),
valueRef: mergedValueRef,
removePanel,
addPanel
})
onFontsReady(() => {
updateCurrentBarPosition()
})
return {
cPrefix: mergedClsPrefix,
mergedValue: mergedValueRef,
labelWrapperRef,
labelBarRef,
@ -263,48 +271,51 @@ export default defineComponent({
}
},
render () {
const { cPrefix } = this
return (
<div
class={[
'n-tabs',
`n-tabs--${this.type}-type`,
`n-tabs--${this.labelSize}-size`,
{
'n-tabs--flex': this.justifyContent
}
`${cPrefix}-tabs`,
`${cPrefix}-tabs--${this.type}-type`,
`${cPrefix}-tabs--${this.labelSize}-size`,
this.justifyContent && `${cPrefix}-tabs--flex`
]}
style={this.cssVars as CSSProperties}
>
<VResizeObserver onResize={this.handleNavResize}>
{{
default: () => (
<VXScroll class="n-tabs-nav" style={this.navStyle}>
<VXScroll class={`${cPrefix}-tabs-nav`} style={this.navStyle}>
{{
default: () => (
<div ref="labelWrapperRef" class="n-tabs-label-wrapper">
<div
ref="labelWrapperRef"
class={`${cPrefix}-tabs-label-wrapper`}
>
<div style={this.labelWrapperStyle}>
{this.panels.map((panel, i) => (
<div
key={i}
data-name={panel.name}
class={[
'n-tabs-label',
`${cPrefix}-tabs-label`,
{
'n-tabs-label--active':
[`${cPrefix}-tabs-label--active`]:
this.mergedValue === panel.name,
'n-tabs-label--disabled': panel.disabled
[`${cPrefix}-tabs-label--disabled`]: panel.disabled
}
]}
onClick={(e) =>
this.handleTabClick(e, panel.name, panel.disabled)
}
>
<span class="n-tabs-label__label">
<span class={`${cPrefix}-tabs-label__label`}>
{panel.label}
</span>
{this.closable && this.type === 'card' ? (
<NBaseClose
class="n-tabs-label__close"
clsPrefix={cPrefix}
class={`${cPrefix}-tabs-label__close`}
onClick={(e) => this.handleCloseClick(e, panel)}
/>
) : null}
@ -315,9 +326,9 @@ export default defineComponent({
<div
ref="labelBarRef"
class={[
'n-tabs-label-bar',
`${cPrefix}-tabs-label-bar`,
{
'n-tabs-label-bar--transition-disabled': this
[`${cPrefix}-tabs-label-bar--transition-disabled`]: this
.transitionDisabled
}
]}