diff --git a/CHANGELOG.en-US.md b/CHANGELOG.en-US.md index 6c26bee3c..98dd28a69 100644 --- a/CHANGELOG.en-US.md +++ b/CHANGELOG.en-US.md @@ -18,6 +18,7 @@ - `n-tree-select` add `indeterminate-keys` prop. - `n-tree` add `on-update:indeterminate-keys` prop. - `n-tree-select` add `on-update:indeterminate-keys` prop. +- `n-tabs` `type` prop add `'segment'` option, closes [#1133](https://github.com/TuSimple/naive-ui/issues/1133). ### Fixes diff --git a/CHANGELOG.zh-CN.md b/CHANGELOG.zh-CN.md index afa6022f8..564674528 100644 --- a/CHANGELOG.zh-CN.md +++ b/CHANGELOG.zh-CN.md @@ -18,6 +18,7 @@ - `n-tree-select` 新增 `indeterminate-keys` 属性 - `n-tree` 新增 `on-update:indeterminate-keys` 属性 - `n-tree-select` 新增 `on-update:indeterminate-keys` 属性 +- `n-tabs` `type` 属性新增 `'segment'` 选项,关闭 [#1133](https://github.com/TuSimple/naive-ui/issues/1133) ### Fixes diff --git a/src/_styles/common/light.ts b/src/_styles/common/light.ts index 051e4d923..0dd83dcfc 100644 --- a/src/_styles/common/light.ts +++ b/src/_styles/common/light.ts @@ -171,7 +171,7 @@ const derived = { inputColor: neutral(base.alphaInput), codeColor: 'rgb(244, 244, 248)', - tabColor: 'rgb(250, 250, 252)', + tabColor: 'rgb(247, 247, 250)', actionColor: 'rgb(250, 250, 252)', tableHeaderColor: 'rgb(250, 250, 252)', diff --git a/src/tabs/demos/enUS/index.demo-entry.md b/src/tabs/demos/enUS/index.demo-entry.md index 19d5e9749..1fd9f493b 100644 --- a/src/tabs/demos/enUS/index.demo-entry.md +++ b/src/tabs/demos/enUS/index.demo-entry.md @@ -6,6 +6,7 @@ Switch contents in same area. ```demo basic +segment flex-label card size @@ -29,7 +30,7 @@ line-debug | pane-style | `string \| object` | `undefined` | Style of the pane. | | tab-style | `string \| object` | `undefined` | Style of the tab. | | tabs-padding | `number` | `0` | Left & right `padding` of the group of tabs. | -| type | `'bar' \| 'line' \| 'card'` | `'bar'` | Tabs type. | +| type | `'bar' \| 'line' \| 'card' \| 'segment'` | `'bar'` | Tabs type. | | value | `string \| number` | `undefined` | Value in controlled mode. | | on-add | `() => void` | `undefined` | Callback function triggered when add tag. | | on-close | `(name: string \| number) => void` | `undefined` | Callback function triggered when close tag. | diff --git a/src/tabs/demos/enUS/segment.demo.md b/src/tabs/demos/enUS/segment.demo.md new file mode 100644 index 000000000..1c16c6089 --- /dev/null +++ b/src/tabs/demos/enUS/segment.demo.md @@ -0,0 +1,11 @@ +# Segment + +Tabs of segment type. + +```html + + Wonderwall + Hey Jude + Qilixiang + +``` diff --git a/src/tabs/demos/zhCN/index.demo-entry.md b/src/tabs/demos/zhCN/index.demo-entry.md index 239a800af..38b48b09e 100644 --- a/src/tabs/demos/zhCN/index.demo-entry.md +++ b/src/tabs/demos/zhCN/index.demo-entry.md @@ -6,6 +6,7 @@ ```demo basic +segment flex-label card size @@ -30,7 +31,7 @@ style-inherit-debug | pane-style | `string \| object` | `undefined` | 面板的样式 | | tab-style | `string \| object` | `undefined` | 标签的样式 | | tabs-padding | `number` | `0` | 全部标签最左和最右的 `padding` | -| type | `'bar' \| 'line' \| 'card'` | `'bar'` | 标签类型 | +| type | `'bar' \| 'line' \| 'card' \| 'segment'` | `'bar'` | 标签类型 | | value | `string \| number` | `undefined` | 受控模式下的值 | | on-add | `() => void` | `undefined` | 添加标签的回调函数 | | on-close | `(name: string \| number) => void` | `undefined` | 关闭标签的回调函数 | diff --git a/src/tabs/demos/zhCN/segment.demo.md b/src/tabs/demos/zhCN/segment.demo.md new file mode 100644 index 000000000..fc1422081 --- /dev/null +++ b/src/tabs/demos/zhCN/segment.demo.md @@ -0,0 +1,32 @@ +# 分段 + +分段类型的标签页。 + +```html + +五美金的礼品卡 + + + 当我是 Amazon 的软件工程师的时候,发生过一件最疯狂的事,故事是这样的:

+ 那时我正在家里远程工作,我和女朋友住在一起。忽然我的同事给我发来了紧急消息:”我们的服务出现了 + SEV 2 级别的故障!我们需要所有的人马上协助!“我们组的应用都挂掉了。

+ 当我还在费力的寻找修复方法的时候,我忽然闻到隔壁房间的的焦味,防火报警器开始鸣叫。 +
+ + “威尔!着火了!快来帮忙!”我听到女朋友大喊。我现在遇到了一个难题,是恢复一个重要的 + Amazon 服务,还是救公寓的火。

+ 那时我忽然记起了亚马逊著名的领导力准则”客户至上“,有很多的客户还依赖我们的服务,我不能让他们失望!所以着火也不管了,女朋友喊我也无所谓,我开始 + debug 这个线上问题。 +
+ + 但是忽然,公寓的烟味消失,火警也停了。我的女朋友走进了我的房间,让我震惊的是,她摘下了自己的假发,她是 + Jeff Bezos(Amazon CEO)假扮的!

+ “我对你坚持顾客至上感到十分骄傲”,他说,然后递给我一张 5 + 美金的亚马逊礼品卡,从我家窗户翻了出去,跳上了一辆 Amazon + 会员服务的小货车,一溜烟的离开了。

虽然现在我已经不在 Amazon + 了,我还是非常感激我在哪里学的到的经验,这些经验我终身难忘。你也是这么想的么? +
+
+``` + + diff --git a/src/tabs/src/Tab.tsx b/src/tabs/src/Tab.tsx index a5bad5bcf..4743f5deb 100644 --- a/src/tabs/src/Tab.tsx +++ b/src/tabs/src/Tab.tsx @@ -77,12 +77,10 @@ export default defineComponent({ data-disabled={disabled ? true : undefined} class={[ `${clsPrefix}-tabs-tab`, - { - [`${clsPrefix}-tabs-tab--active`]: value === name, - [`${clsPrefix}-tabs-tab--disabled`]: disabled, - [`${clsPrefix}-tabs-tab--closable`]: mergedClosable, - [`${clsPrefix}-tabs-tab--addable`]: addable - } + value === name && `${clsPrefix}-tabs-tab--active`, + disabled && `${clsPrefix}-tabs-tab--disabled`, + mergedClosable && `${clsPrefix}-tabs-tab--closable`, + addable && `${clsPrefix}-tabs-tab--addable` ]} onClick={this.handleClick} style={addable ? undefined : style} diff --git a/src/tabs/src/Tabs.tsx b/src/tabs/src/Tabs.tsx index 38aaa7bdd..80efeafae 100644 --- a/src/tabs/src/Tabs.tsx +++ b/src/tabs/src/Tabs.tsx @@ -20,11 +20,17 @@ import { throttle } from 'lodash-es' import { useCompitable, onFontsReady, useMergedState } from 'vooks' import { useConfig, useTheme } from '../../_mixins' import type { ThemeProps } from '../../_mixins' -import { warn, createKey, call, flatten } from '../../_utils' +import { createKey, call, flatten, warnOnce } from '../../_utils' import type { MaybeArray, ExtractPublicPropTypes } from '../../_utils' import { tabsLight } from '../styles' import type { TabsTheme } from '../styles' -import { Addable, OnClose, OnCloseImpl, tabsInjectionKey } from './interface' +import { + Addable, + OnClose, + OnCloseImpl, + tabsInjectionKey, + TabsType +} from './interface' import type { OnUpdateValue, OnUpdateValueImpl } from './interface' import style from './styles/index.cssr' import Tab from './Tab' @@ -34,24 +40,13 @@ const tabsProps = { value: [String, Number] as PropType, defaultValue: [String, Number] as PropType, type: { - type: String as PropType<'bar' | 'line' | 'card'>, + type: String as PropType, default: 'bar' }, closable: Boolean, justifyContent: String as PropType< 'space-between' | 'space-around' | 'space-evenly' >, - /** deprecated */ - labelSize: { - type: String as PropType<'small' | 'medium' | 'large'>, - validator: () => { - if (__DEV__) { - warn('tabs', '`label-size` is deprecated, please use `size` instead.') - } - return true - }, - default: undefined - }, size: { type: String as PropType<'small' | 'medium' | 'large'>, default: 'medium' @@ -68,31 +63,11 @@ const tabsProps = { onUpdateValue: [Function, Array] as PropType>, onClose: [Function, Array] as PropType>, // deprecated - activeName: { - type: [String, Number] as PropType, - 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 - } + labelSize: String as PropType<'small' | 'medium' | 'large'>, + activeName: [String, Number] as PropType, + onActiveNameChange: [Function, Array] as PropType< + MaybeArray<(value: string & number) => void> + > } as const export type TabsProps = ExtractPublicPropTypes @@ -101,6 +76,29 @@ export default defineComponent({ name: 'Tabs', props: tabsProps, setup (props, { slots }) { + if (__DEV__) { + watchEffect(() => { + if (props.labelSize !== undefined) { + warnOnce( + 'tabs', + '`label-size` is deprecated, please use `size` instead.' + ) + } + if (props.activeName !== undefined) { + warnOnce( + 'tabs', + '`active-name` is deprecated, please use `value` instead.' + ) + } + if (props.onActiveNameChange !== undefined) { + warnOnce( + 'tabs', + '`on-active-name-change` is deprecated, please use `on-update:value` instead.' + ) + } + }) + } + const { mergedClsPrefixRef } = useConfig(props) const themeRef = useTheme( 'Tabs', @@ -318,8 +316,14 @@ export default defineComponent({ cssVars: computed(() => { const { value: size } = compitableSizeRef const { type } = props - const typeSuffix = - type === 'card' ? 'Card' : type === 'bar' ? 'Bar' : 'Line' + const typeSuffix = ( + { + card: 'Card', + bar: 'Bar', + line: 'Line', + segment: 'Segment' + } as const + )[type] const sizeType = `${size}${typeSuffix}` as const const { self: { @@ -333,6 +337,9 @@ export default defineComponent({ tabFontWeight, tabBorderRadius, tabFontWeightActive, + colorSegment, + fontWeightStrong, + tabColorSegment, [createKey('panePadding', size)]: panePadding, [createKey('tabPadding', sizeType)]: tabPadding, [createKey('tabGap', sizeType)]: tabGap, @@ -346,6 +353,7 @@ export default defineComponent({ } = themeRef.value return { '--bezier': cubicBezierEaseInOut, + '--color-segment': colorSegment, '--bar-color': barColor, '--tab-font-size': tabFontSize, '--tab-text-color': tabTextColor, @@ -363,7 +371,9 @@ export default defineComponent({ '--tab-font-weight-active': tabFontWeightActive, '--tab-padding': tabPadding, '--tab-gap': tabGap, - '--pane-padding': panePadding + '--pane-padding': panePadding, + '--font-weight-strong': fontWeightStrong, + '--tab-color-segment': tabColorSegment } }) } @@ -385,7 +395,8 @@ export default defineComponent({ const prefix = prefixSlot ? prefixSlot() : null const suffix = suffixSlot ? suffixSlot() : null const isCard = type === 'card' - const mergedJustifyContent = !isCard && this.justifyContent + const isSegment = type === 'segment' + const mergedJustifyContent = !isCard && !isSegment && this.justifyContent return (
{prefix}
) : null} - - {{ - default: () => ( -
- - {{ - default: () => { - const rawWrappedTabs = ( -
- {mergedJustifyContent ? null : ( -
- )} - {children.map( - (tabPaneVNode: any, index: number) => { - return ( - - {tabPaneVNode.children - ? { - default: tabPaneVNode.children.tab - } - : undefined} - - ) - } - )} - {!addTabFixed && addable && isCard - ? createAddTag(addable, children.length !== 0) - : null} - {mergedJustifyContent ? null : ( -
- )} -
- ) - let wrappedTabs = rawWrappedTabs - if (isCard && addable) { - wrappedTabs = ( - - {{ - default: () => rawWrappedTabs - }} - + {isSegment ? ( +
+ {children.map((tabPaneVNode: any, index: number) => { + return ( + + {tabPaneVNode.children + ? { + default: tabPaneVNode.children.tab + } + : undefined} + + ) + })} +
+ ) : ( + + {{ + default: () => ( +
+ + {{ + default: () => { + const rawWrappedTabs = ( +
+ {mergedJustifyContent ? null : ( +
+ )} + {children.map( + (tabPaneVNode: any, index: number) => { + return ( + + {tabPaneVNode.children + ? { + default: tabPaneVNode.children.tab + } + : undefined} + + ) + } + )} + {!addTabFixed && addable && isCard + ? createAddTag(addable, children.length !== 0) + : null} + {mergedJustifyContent ? null : ( +
+ )} +
+ ) + let wrappedTabs = rawWrappedTabs + if (isCard && addable) { + wrappedTabs = ( + + {{ + default: () => rawWrappedTabs + }} + + ) + } + return ( +
+ {wrappedTabs} + {isCard ? ( +
+ ) : null} + {isCard ? null : ( +
+ )} +
) } - return ( -
- {wrappedTabs} - {isCard ? ( -
- ) : null} - {isCard ? null : ( -
- )} -
- ) - } - }} - -
- ) - }} - + }} + +
+ ) + }} + + )} {addTabFixed && addable && isCard ? createAddTag(addable, true) : null} diff --git a/src/tabs/src/interface.ts b/src/tabs/src/interface.ts index 8f962a62f..29269fae8 100644 --- a/src/tabs/src/interface.ts +++ b/src/tabs/src/interface.ts @@ -1,5 +1,7 @@ import { Ref, InjectionKey, CSSProperties } from 'vue' +export type TabsType = 'line' | 'card' | 'bar' | 'segment' + export type OnUpdateValue = (value: string & number) => void export type OnUpdateValueImpl = (value: string | number) => void @@ -9,7 +11,7 @@ export type OnCloseImpl = (name: string | number) => void export interface TabsInjection { mergedClsPrefixRef: Ref valueRef: Ref - typeRef: Ref<'line' | 'card' | 'bar'> + typeRef: Ref closableRef: Ref tabStyleRef: Ref paneStyleRef: Ref diff --git a/src/tabs/src/styles/index.cssr.ts b/src/tabs/src/styles/index.cssr.ts index 496ba178b..00ea356d7 100644 --- a/src/tabs/src/styles/index.cssr.ts +++ b/src/tabs/src/styles/index.cssr.ts @@ -20,12 +20,47 @@ import { c, cM, cB, cE, cNotM } from '../../../_utils/cssr' // --tab-gap // --tab-padding // --pane-padding +// --color-segment +// --font-weight-strong +// --tab-color-segment export default cB('tabs', ` width: 100%; transition: background-color .3s var(--bezier), border-color .3s var(--bezier); `, [ + cB('tabs-rail', ` + padding: 3px; + border-radius: var(--tab-border-radius); + width: 100%; + background-color: var(--color-segment); + transition: background-color .3s var(--bezier); + display: flex; + align-items: center; + `, [ + cB('tabs-tab-wrapper', ` + flex-basis: 0; + flex-grow: 1; + display: flex; + align-items: center; + justify-content: center; + `, [ + cB('tabs-tab', ` + overflow: hidden; + border-radius: var(--tab-border-radius); + width: 100%; + display: flex; + align-items: center; + justify-content: center; + `, [ + cM('active', ` + font-weight: var(--font-weight-strong); + background-color: var(--tab-color-segment); + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .08); + `) + ]) + ]) + ]), cM('flex', [ cB('tabs-nav', { width: '100%' @@ -110,6 +145,7 @@ export default cB('tabs', ` background-clip: padding-box; padding: var(--tab-padding); transition: + box-shadow .3s var(--bezier), color .3s var(--bezier), background-color .3s var(--bezier), border-color .3s var(--bezier); diff --git a/src/tabs/styles/_common.ts b/src/tabs/styles/_common.ts index ac7e2dea9..906f18580 100644 --- a/src/tabs/styles/_common.ts +++ b/src/tabs/styles/_common.ts @@ -20,6 +20,12 @@ export default { tabPaddingSmallCard: '6px 10px', tabPaddingMediumCard: '8px 12px', tabPaddingLargeCard: '8px 16px', + tabPaddingSmallSegment: '4px 0', + tabPaddingMediumSegment: '6px 0', + tabPaddingLargeSegment: '8px 0', + tabGapSmallSegment: '0', + tabGapMediumSegment: '0', + tabGapLargeSegment: '0', panePaddingSmall: '8px 0 0 0', panePaddingMedium: '12px 0 0 0', panePaddingLarge: '16px 0 0 0' diff --git a/src/tabs/styles/dark.ts b/src/tabs/styles/dark.ts index b5d3e15d2..ef08ff40a 100644 --- a/src/tabs/styles/dark.ts +++ b/src/tabs/styles/dark.ts @@ -5,7 +5,13 @@ import { self } from './light' const tabsDark: TabsTheme = { name: 'Tabs', common: commonDark, - self + self (vars) { + const commonSelf = self(vars) + const { inputColor } = vars + commonSelf.colorSegment = inputColor + commonSelf.tabColorSegment = inputColor + return commonSelf + } } export default tabsDark diff --git a/src/tabs/styles/light.ts b/src/tabs/styles/light.ts index 71d049203..ac582f71d 100644 --- a/src/tabs/styles/light.ts +++ b/src/tabs/styles/light.ts @@ -12,19 +12,26 @@ export const self = (vars: ThemeCommonVars) => { closeColorHover, closeColorPressed, tabColor, + baseColor, dividerColor, fontWeight, textColor1, borderRadius, - fontSize + fontSize, + fontWeightStrong } = vars return { ...sizeVariables, + colorSegment: tabColor, tabFontSizeCard: fontSize, tabTextColorLine: textColor1, tabTextColorActiveLine: primaryColor, tabTextColorHoverLine: primaryColor, tabTextColorDisabledLine: textColorDisabled, + tabTextColorSegment: textColor1, + tabTextColorActiveSegment: primaryColor, + tabTextColorHoverSegment: primaryColor, + tabTextColorDisabledSegment: textColorDisabled, tabTextColorBar: textColor1, tabTextColorActiveBar: primaryColor, tabTextColorHoverBar: primaryColor, @@ -38,11 +45,13 @@ export const self = (vars: ThemeCommonVars) => { closeColorHover, closeColorPressed, tabColor, + tabColorSegment: baseColor, tabBorderColor: dividerColor, tabFontWeightActive: fontWeight, tabFontWeight: fontWeight, tabBorderRadius: borderRadius, - paneTextColor: textColor2 + paneTextColor: textColor2, + fontWeightStrong } }