diff --git a/packages/site/demo/styles/TabsPage.scss b/packages/site/demo/styles/TabsPage.scss index 1201c657e..9506b3a7a 100644 --- a/packages/site/demo/styles/TabsPage.scss +++ b/packages/site/demo/styles/TabsPage.scss @@ -1,25 +1,15 @@ @import '@/styles/index'; .tabs-page { - .za-tabs + .za-tabs { - margin-top: r(10); - } - .content { - display: flex; - align-items: center; - justify-content: center; padding: r(10); - height: r(150); } - .za-tabs--vertical { - .content { - height: 100%; - } + .custom-width { + width: r(250); + } - &.custom-height { - height: r(200); - } + .custom-height { + height: r(200); } } diff --git a/packages/zarm/src/tabs/Tabs.tsx b/packages/zarm/src/tabs/Tabs.tsx index af831f8bc..3f041ba18 100644 --- a/packages/zarm/src/tabs/Tabs.tsx +++ b/packages/zarm/src/tabs/Tabs.tsx @@ -1,14 +1,15 @@ import { createBEM } from '@zarm-design/bem'; import * as React from 'react'; -import TabPanel from './TabPanel'; -import Carousel from '../carousel'; import type { CarouselHTMLElement } from '../carousel'; -import { getTransformPropValue, getPxStyle } from './util/index'; -import { scrollTo } from '../utils/dom'; +import Carousel from '../carousel'; import { ConfigContext } from '../config-provider'; -import type { TabPanelProps } from './TabPanel'; -import type { BaseTabsProps } from './interface'; +import { useTypeChangeWarning } from '../utils/deprecationWarning'; +import { scrollTo } from '../utils/dom'; import type { HTMLProps } from '../utils/utilityTypes'; +import type { BaseTabsProps } from './interface'; +import type { TabPanelProps } from './TabPanel'; +import TabPanel from './TabPanel'; +import { getPxStyle, getTransformPropValue } from './util/index'; const getChildChecked = (children: TabPanelProps['children']) => { let selectIndex; @@ -79,6 +80,10 @@ const Tabs = React.forwardRef((props, ref) => { children, } = props; + // TODO: remove this warning in next major version + ['vertical', 'horizontal'].includes(direction) && + useTypeChangeWarning('Tabs', 'direction', direction, "'top' | 'right' | 'bottom' | 'left'"); + const carouselRef = React.useRef(null); const tablistRef = React.useRef(null); const [itemWidth, setItemWidth] = React.useState(0); @@ -89,17 +94,21 @@ const Tabs = React.forwardRef((props, ref) => { const { prefixCls } = React.useContext(ConfigContext); const bem = createBEM('tabs', { prefixCls }); - const isVertical: boolean = direction === 'vertical'; + // TODO: direction='vertical' 暂作兼容 + const isVertical: boolean = ['left', 'right', 'vertical'].includes(direction); const parseValue = React.useCallback( (inputValue) => parseValueBoundary(inputValue, children), [children], ); - const classes = bem([{ - [`${direction}`]: true, - scroll: scrollable, - }, className]) + const classes = bem([ + { + [`${direction}`]: true, + scroll: scrollable, + }, + className, + ]); // 计算 line 大小和位置 const caclLineSizePos = () => { @@ -160,7 +169,7 @@ const Tabs = React.forwardRef((props, ref) => { contentRender = ( ((props, ref) => { } const renderTabs = (tab: React.ReactElement, index: number) => { - const itemCls = bem('tab', [{ - disabled: disabled || tab.props.disabled, - active: parseValue(currentValue) === index, - }, tab.props.className]) + const itemCls = bem('tab', [ + { + disabled: disabled || tab.props.disabled, + active: parseValue(currentValue) === index, + }, + tab.props.className, + ]); return (
  • onTabClick(tab, index)}> @@ -264,7 +276,7 @@ Tabs.defaultProps = { disabled: false, swipeable: false, scrollable: false, - direction: 'horizontal', + direction: 'top', }; export default Tabs; diff --git a/packages/zarm/src/tabs/demo.md b/packages/zarm/src/tabs/demo.md index 74c8e92aa..c085828a8 100644 --- a/packages/zarm/src/tabs/demo.md +++ b/packages/zarm/src/tabs/demo.md @@ -74,47 +74,6 @@ ReactDOM.render( ); ``` -## 标签栏滚动 - -```jsx -import { useState } from 'react'; -import { Tabs } from 'zarm'; - -const { Panel } = Tabs; - -const Demo = () => { - const [value, setValue] = useState(0); - - return ( - - -
    选项卡1内容
    -
    - -
    选项卡2内容
    -
    - -
    选项卡3内容
    -
    - -
    选项卡4内容
    -
    - -
    选项卡5内容
    -
    - -
    选项卡6内容
    -
    - -
    选项卡7内容
    -
    -
    - ); -}; - -ReactDOM.render(, mountNode); -``` - ## 指定线条宽度 ```jsx @@ -161,7 +120,59 @@ ReactDOM.render( ); ``` -## 垂直用法 +## 方向 + +```jsx +import { useState } from 'react'; +import { Tabs, List, Radio } from 'zarm'; + +const { Panel } = Tabs; + +const Demo = () => { + const [value, setValue] = useState(0); + const [direction, setDirection] = useState('top'); + + return ( + + + + top + bottom + left + right + + + + +
    选项卡1内容
    +
    + +
    选项卡2内容
    +
    + +
    选项卡3内容
    +
    + +
    选项卡4内容
    +
    + +
    选项卡5内容
    +
    + +
    选项卡6内容
    +
    + +
    选项卡7内容
    +
    +
    +
    + ); +}; + +ReactDOM.render(, mountNode); +``` + +## 水平限宽 ```jsx import { useState } from 'react'; @@ -173,7 +184,13 @@ const Demo = () => { const [value, setValue] = useState(0); return ( - +
    选项卡1内容
    @@ -214,13 +231,7 @@ const Demo = () => { const [value, setValue] = useState(0); return ( - +
    选项卡1内容
    @@ -253,16 +264,16 @@ ReactDOM.render(, mountNode); ### Tabs -| 属性 | 类型 | 默认值 | 说明 | -| :----------- | :---------------------- | :----------- | :------------------------------------- | -| value | number | - | 值 | -| defaultValue | number | - | 初始值 | -| disabled | boolean | false | 是否禁用 | -| direction | string | 'horizontal' | 方向,可选值为 `horizontal` `vertical` | -| swipeable | boolean | false | 是否支持滑动切换 | -| scrollable | boolean | false | 是否支持滚动 | -| lineWidth | number \| string | - | 线条宽度 | -| onChange | (index: number) => void | - | 值变化时触发的回调函数 | +| 属性 | 类型 | 默认值 | 说明 | +| :----------- | :---------------------- | :----- | :------------------------------------------- | +| value | number | - | 值 | +| defaultValue | number | - | 初始值 | +| disabled | boolean | false | 是否禁用 | +| direction | string | 'top' | 方向,可选值为 `top` `bottom` `left` `right` | +| swipeable | boolean | false | 是否支持滑动切换 | +| scrollable | boolean | false | 是否支持滚动 | +| lineWidth | number \| string | - | 线条宽度 | +| onChange | (index: number) => void | - | 值变化时触发的回调函数 | ### Panel diff --git a/packages/zarm/src/tabs/interface.ts b/packages/zarm/src/tabs/interface.ts index 5ed062cd4..930c020b8 100644 --- a/packages/zarm/src/tabs/interface.ts +++ b/packages/zarm/src/tabs/interface.ts @@ -7,7 +7,7 @@ export interface BaseTabsProps { disabled?: boolean; swipeable?: boolean; scrollable?: boolean; - direction?: 'horizontal' | 'vertical'; + direction?: 'vertical' | 'horizontal' | 'top' | 'right' | 'bottom' | 'left'; // TODO: 'vertical'、'horizontal' 暂作兼容 onChange?: (index: number) => void; children?: React.ReactNode; } diff --git a/packages/zarm/src/tabs/style/component.scss b/packages/zarm/src/tabs/style/component.scss index 94510492b..2c98cbbba 100644 --- a/packages/zarm/src/tabs/style/component.scss +++ b/packages/zarm/src/tabs/style/component.scss @@ -68,10 +68,13 @@ } } - @include m(horizontal) { + // TODO: direction='horizontal' 暂作兼容 + @include m(top, bottom, horizontal) { + display: flex; + flex-direction: column; + @include e(header) { width: 100%; - @include onepx(bottom); } @include e(tablist) { @@ -83,7 +86,6 @@ @include e(line) { height: var(--active-line-height); left: 0; - bottom: 0; } @include m(scroll) { @@ -98,14 +100,11 @@ } } - @include m(vertical) { + // TODO: direction='vertical' 暂作兼容 + @include m(left, right, vertical) { display: flex; flex-direction: row; - @include e(header) { - @include onepx(right); - } - @include e(tablist) { height: 100%; } @@ -118,7 +117,6 @@ @include e(line) { width: var(--active-line-height); top: 0; - right: 0; } @include e(body) { @@ -132,4 +130,52 @@ } } } + + @include m(right, bottom) { + @include e(header) { + order: 1; + } + } + + // TODO: direction='vertical' 暂作兼容 + @include m(left, vertical) { + @include e(header) { + @include onepx(right); + } + + @include e(line) { + right: 0; + } + } + + @include m(right) { + @include e(header) { + @include onepx(left); + } + + @include e(line) { + left: 0; + } + } + + // TODO: direction='horizontal' 暂作兼容 + @include m(top, horizontal) { + @include e(header) { + @include onepx(bottom); + } + + @include e(line) { + bottom: 0; + } + } + + @include m(bottom) { + @include e(header) { + @include onepx(top); + } + + @include e(line) { + top: 0; + } + } } diff --git a/packages/zarm/src/utils/deprecationWarning.ts b/packages/zarm/src/utils/deprecationWarning.ts new file mode 100644 index 000000000..69d9d23be --- /dev/null +++ b/packages/zarm/src/utils/deprecationWarning.ts @@ -0,0 +1,40 @@ +import { useEffect } from 'react'; + +/** + * 自定义 Hook,用于在非生产环境下对废弃的 props 提示警告。 + * @param {string} componentName - 发生类型变更的组件名称。 + * @param {string} oldProp - 废弃的 prop 名称。 + * @param {string} newProp - 新的 prop 名称。 + */ +export const useDeprecationWarning = (componentName, oldProp, newProp) => { + useEffect(() => { + if (process.env.NODE_ENV !== 'production' && oldProp !== undefined) { + console.warn( + `Warning: The prop "${oldProp}" is deprecated. ` + + `you can use "${newProp}" instead.` + + `\n\nPlease update the following components: ${componentName}`, + ); + } + }, [componentName, oldProp, newProp]); +}; + +/** + * 自定义 Hook,用于在非生产环境下对类型变更的 props 提示警告。 + * @param {string} componentName - 发生类型变更的组件名称。 + * @param {string} propName - 发生类型变更的 prop 名称。 + * @param {string} propType - 传入的 prop 类型。 + * @param {string} expectedType - 预期的新类型(如 'object', 'number' 等)。 + */ +export const useTypeChangeWarning = (componentName, propName, propType, expectedType) => { + useEffect(() => { + if (process.env.NODE_ENV !== 'production') { + if (propType !== expectedType) { + console.warn( + `Warning: The prop "${propName}" is expected to be of type "${expectedType}", ` + + `but received type "${propType}". ` + + `\n\nPlease update the following components: ${componentName}`, + ); + } + } + }, [componentName, propName, propType, expectedType]); +};