diff --git a/site/mobile/mobile.config.js b/site/mobile/mobile.config.js index 167d0fb3..082cdf59 100644 --- a/site/mobile/mobile.config.js +++ b/site/mobile/mobile.config.js @@ -23,7 +23,7 @@ export default { { title: 'Tabs 选项卡', name: 'tabs', - component: () => import('tdesign-mobile-react/tabs/_example/index.jsx'), + component: () => import('tdesign-mobile-react/tabs/_example/index.tsx'), }, { title: 'Input 输入框', diff --git a/src/sticky/index.tsx b/src/sticky/index.tsx index 6e372730..dd827781 100644 --- a/src/sticky/index.tsx +++ b/src/sticky/index.tsx @@ -1,8 +1,8 @@ import _Sticky from './Sticky'; - +import { TdStickyProps } from './type'; import './style'; export const Sticky = _Sticky; +export type StickyProps = TdStickyProps; export default Sticky; - diff --git a/src/tabs/TabPanel.tsx b/src/tabs/TabPanel.tsx index d83e601e..d2336781 100644 --- a/src/tabs/TabPanel.tsx +++ b/src/tabs/TabPanel.tsx @@ -1,48 +1,40 @@ -import React, { FC, useContext } from 'react'; -import classnames from 'classnames'; -import useConfig from '../_util/useConfig'; +import React, { FC, useContext, useEffect, useMemo, useState } from 'react'; import { TdTabPanelProps } from './type'; import TabContext from './context'; +import { usePrefixClass } from '../hooks/useClass'; +import parseTNode from '../_util/parseTNode'; const TabPanel: FC = (props) => { - const { value, label, disabled } = props; + const { value, lazy, destroyOnHide, children, panel } = props; - const { classPrefix } = useConfig(); - const tabPrefix = classPrefix || ''; + const tabPanelClass = usePrefixClass('tab-panel'); + const tabClass = usePrefixClass('tabs'); const tabProps = useContext(TabContext); - const { activeKey, horiRef, vetiRef, onChange } = tabProps; + const { activeKey } = tabProps; + const isActive = useMemo(() => value === activeKey, [activeKey, value]); + const [isMount, setIsMount] = useState(lazy ? isActive : true); + + useEffect(() => { + if (isActive) { + if (!isMount) { + setIsMount(true); + } + } else if (destroyOnHide) { + setIsMount(false); + } + }, [destroyOnHide, isActive, isMount, lazy]); - const change = () => { - if (disabled) return; - onChange && onChange(value); - }; return ( -
{ - if (activeKey === value) { - vetiRef.current = ref; - } - }} - className={classnames( - `${tabPrefix}-tabs__nav-item`, - activeKey === value && `${tabPrefix}-is-active`, - disabled && `${tabPrefix}-is-disabled`, - )} - key={value} - onClick={() => change()} - > - { - if (activeKey === value) { - horiRef.current = ref; - } - }} - className={`${tabPrefix}-tabs__nav-item-btn`} + isMount && ( +
- {label} - -
+ {parseTNode(children, panel)} +
+ ) ); }; - +TabPanel.displayName = 'TabPanel'; export default TabPanel; diff --git a/src/tabs/Tabs.tsx b/src/tabs/Tabs.tsx index edbb19f4..aa83caee 100644 --- a/src/tabs/Tabs.tsx +++ b/src/tabs/Tabs.tsx @@ -1,8 +1,15 @@ -import React, { FC, HTMLAttributes, useEffect, useRef, useState } from 'react'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; import classnames from 'classnames'; -import useConfig from '../_util/useConfig'; -import { TdTabsProps } from './type'; +import type { FC, HTMLAttributes, CSSProperties } from 'react'; +import Sticky from '../sticky'; +import Badge from '../badge'; +import { TdTabPanelProps, TdTabsProps } from './type'; import TabPanel from './TabPanel'; +import useConfig from '../_util/useConfig'; +import { usePrefixClass } from '../hooks/useClass'; +import useDefaultProps from '../hooks/useDefaultProps'; +import { tabsDefaultProps } from './defaultProps'; +import parseTNode from '../_util/parseTNode'; import TabContext from './context'; type TabsHTMLAttrs = Pick, 'className' | 'style'>; @@ -10,149 +17,255 @@ export interface TabsProps extends TdTabsProps, TabsHTMLAttrs {} const Tabs: FC = (props) => { const { - className = '', - style, + bottomLineMode, children, - content, - defaultValue = '', - list = [], + list, animation, - placement, - showBottomLine = true, + spaceEvenly, + showBottomLine, size, - change, - } = props; - const [activeKey, setActiveKey] = useState(''); - const [lineStyle, setLineStyle] = useState({}); - const [wrapWidth, setWrapWidth] = useState(0); + theme, + stickyProps, + swipeable, + onChange, + onClick, + onScroll, + value, + defaultValue, + } = useDefaultProps(props, tabsDefaultProps); + const tabsClass = usePrefixClass('tabs'); + const tabsClasses = useMemo( + () => + classnames({ + [tabsClass]: true, + [`${size}`]: size, + }), + [size, tabsClass], + ); + + const itemProps = useMemo>(() => { + if (list && list.length > 0) { + return list; + } + + const propsArr = []; + React.Children.forEach(children, (child: JSX.Element) => { + if (child.type.displayName === TabPanel.displayName) { + propsArr.push(child.props); + } + }); + + return propsArr; + }, [list, children]); + + const [activeKey, setActiveKey] = useState( + defaultValue || defaultValue === 0 ? defaultValue : value, + ); + const [lineStyle, setLineStyle] = useState({}); const { classPrefix } = useConfig(); - const tabPrefix = classPrefix || 't'; - const horiRef = useRef(null); - const wrapRef = useRef(null); - const vetiRef = useRef(null); - - const onChange = (value) => { - setActiveKey(value); - change && change(value); + const activeClass = `${tabsClass}__item--active`; + const navScrollRef = useRef(null); + const navWrapRef = useRef(null); + const navLineRef = useRef(null); + + const [startX, setStartX] = useState(0); + const [startY, setStartY] = useState(0); + const [endX, setEndX] = useState(0); + const [endY, setEndY] = useState(0); + const [canMove, setCanMove] = useState(true); + const tabIndex = useMemo(() => { + let index = 0; + for (let i = 0; i < itemProps.length; i++) { + if (itemProps[i].value === activeKey) { + index = i; + break; + } + } + return index; + }, [activeKey, itemProps]); + + const currentIndex = useMemo(() => itemProps.map((p) => p.value).indexOf(activeKey), [activeKey, itemProps]); + + const moveToActiveTab = () => { + if (navWrapRef.current && navLineRef.current && showBottomLine) { + const tab = navWrapRef.current.querySelector(`.${activeClass}`); + if (!tab) return; + const line = navLineRef.current; + const tabInner = tab.querySelector(`.${classPrefix}-badge`); + const style: CSSProperties = {}; + if (bottomLineMode === 'auto') { + style.width = `${Number(tabInner?.offsetWidth)}px`; + style.transform = `translateX(${Number(tab?.offsetLeft) + Number(tabInner?.offsetLeft)}px)`; + } else if (bottomLineMode === 'full') { + style.width = `${Number(tab?.offsetWidth)}px`; + style.transform = `translateX(${Number(tab?.offsetLeft)}px)`; + } else { + style.transform = `translateX(${ + Number(tab?.offsetLeft) + (Number(tab?.offsetWidth) - Number(line?.offsetWidth)) / 2 + }px)`; + } + + if (animation) { + style.transitionDuration = `${animation.duration}ms`; + } + + setLineStyle(style); + } }; - useEffect(() => { - if (placement === 'left' || placement === 'right') { - const vetiIndfo = vetiRef.current?.getBoundingClientRect(); - if (vetiIndfo) { - const { height } = vetiIndfo || {}; - const offsetTop = vetiRef.current?.offsetTop; - const sTop = wrapRef.current?.scrollTop; - const linePosition = offsetTop + sTop; - const lStyle = { - width: '2px', - height: `${height}px`, - left: 0, - top: `${linePosition}px`, - }; - const rStyle = { - width: '2px', - height: `${height}px`, - right: 0, - top: `${linePosition}px`, - }; - if (placement === 'left') { - setLineStyle(lStyle); - } - if (placement === 'right') { - setLineStyle(rStyle); + const handleTabClick = (item: TdTabPanelProps) => { + const { value, disabled } = item; + if (disabled || activeKey === value) { + return false; + } + setActiveKey(item.value); + if (onChange) { + onChange(item.value, parseTNode(item.label).toString()); + } + if (onClick) { + onClick(item.value, parseTNode(item.label).toString()); + } + setTimeout(() => { + moveToActiveTab(); + }, 0); + }; + + const handlerScroll = (context: { scrollTop: number; isFixed: boolean }) => { + const { scrollTop, isFixed } = context; + if (stickyProps) { + onScroll?.(scrollTop, isFixed); + } + }; + + // 手势滑动开始 + const handleTouchstart = (e: React.TouchEvent) => { + if (!swipeable) return; + setStartX(e.targetTouches[0].clientX); + setStartY(e.targetTouches[0].clientY); + }; + + const handleTouchmove = (e: React.TouchEvent) => { + if (!swipeable) return; + if (!canMove) return; + setEndX(e.targetTouches[0].clientX); + setEndY(e.targetTouches[0].clientY); + + const dValueX = Math.abs(startX - endX); + const dValueY = Math.abs(startY - endY); + if (tabIndex >= 0 && tabIndex < itemProps.length) { + if (dValueX > dValueY) { + // if (typeof e.cancelable !== 'boolean' || e.cancelable) { + // e.preventDefault(); + // } + if (dValueX <= 40) return; + if (startX > endX) { + // 向左划 + if (tabIndex >= itemProps.length - 1) return; + setCanMove(false); + handleTabClick(itemProps[tabIndex + 1]); + } else if (startX < endX) { + // 向右划 + if (tabIndex <= 0) return; + setCanMove(false); + handleTabClick(itemProps[tabIndex - 1]); } } - } else { - const eleInfo = horiRef.current?.getBoundingClientRect(); - if (eleInfo) { - const sLeft = wrapRef.current?.scrollLeft; - const { width, left } = eleInfo || {}; - const linePosition = left + sLeft; - const warpEle = wrapRef?.current; - warpEle.scrollLeft = linePosition - wrapWidth / 2; - - const tbStyle = { - width: `${width}px`, - left: `${linePosition}px`, - }; - setLineStyle(tbStyle); - } } - }, [activeKey, wrapWidth, placement, defaultValue, tabPrefix]); + }; + + // 手势滑动结束 + const handleTouchend = () => { + if (!swipeable) return; + setCanMove(true); + setStartX(0); + setEndX(0); + setStartY(0); + setEndY(0); + }; useEffect(() => { - const warapWidth = wrapRef.current?.getBoundingClientRect().width; - setWrapWidth(warapWidth); + window.addEventListener('resize', moveToActiveTab, false); + const timeout = setTimeout(() => { + moveToActiveTab(); + }, 300); + + return () => { + window.removeEventListener('resize', moveToActiveTab); + clearTimeout(timeout); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); useEffect(() => { - if (activeKey) return; - if (defaultValue) { - onChange(defaultValue); - return; - } - if (list.length > 0) { - onChange(list[0]?.value); - return; - } - if (!children) return; - if (Array.isArray(children)) { - onChange(children[0]?.props?.value); - } else { - onChange(children?.props.value); - } + setTimeout(() => { + moveToActiveTab(); + }, 0); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [tabPrefix, defaultValue]); + }, [value]); - return ( -
-
-
- - {children} - {list && - list.length > 0 && - list.map((item) => ( - - ))} - - {showBottomLine && ( -
- )} -
-
- {(children || content) && ( + const renderNav = () => + itemProps.map((item, index) => { + const { badgeProps } = item; + return (
handleTabClick(item)} > - {children && - Array.isArray(children) && - children.map((item) => <>{activeKey === item?.props?.value && <>{item.props.children}})} - {children && typeof children === 'object' && <>{children?.props?.children}} - {content} + +
+
{parseTNode(item.label)}
+
+
+ {theme === 'card' && index === currentIndex - 1 &&
} + {theme === 'card' && index === currentIndex + 1 &&
} +
+ ); + }); + + return ( +
+ +
+
+
+ {renderNav()} + {theme === 'line' && showBottomLine && ( +
+ )} +
+
- )} +
+
+ {children} +
); }; - +Tabs.displayName = 'Tabs'; export default Tabs; diff --git a/src/tabs/_example/badge.tsx b/src/tabs/_example/badge.tsx new file mode 100644 index 00000000..8f7a8672 --- /dev/null +++ b/src/tabs/_example/badge.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { Tabs, TabPanel } from 'tdesign-mobile-react'; + +export default () => ( +
+ + + + + +
+); diff --git a/src/tabs/_example/base.jsx b/src/tabs/_example/base.jsx deleted file mode 100644 index eb6f47e2..00000000 --- a/src/tabs/_example/base.jsx +++ /dev/null @@ -1,49 +0,0 @@ -import React from 'react'; -import { Tabs, TabPanel } from 'tdesign-mobile-react'; -import './style.less'; - -export default function () { - return ( -
    -
  • - - -
    标签一内容
    -
    - -
    标签二内容
    -
    -
    -
  • -
  • - - -
    标签一内容
    -
    - -
    标签二内容
    -
    - -
    标签三内容
    -
    -
    -
  • -
  • - - -
    标签页一内容
    -
    - -
    标签二内容
    -
    - -
    标签三内容
    -
    - -
    标签四内容
    -
    -
    -
  • -
- ); -} diff --git a/src/tabs/_example/bottom.jsx b/src/tabs/_example/bottom.jsx deleted file mode 100644 index ad350bc1..00000000 --- a/src/tabs/_example/bottom.jsx +++ /dev/null @@ -1,55 +0,0 @@ -import React, { useState } from 'react'; -import { Tabs } from 'tdesign-mobile-react'; -import TDemoBlock from '../../../site/mobile/components/DemoBlock'; -import './style.less'; - -export default function () { - const list2 = [ - { - label: '标签页一', - value: 'tab1', - }, - { - label: '标签页二', - value: 'tab2', - }, - { - label: '标签页三', - value: 'tab3', - disabled: true, - }, - { - label: '标签页四', - value: 'tab4', - }, - ]; - const content = { - tab1: '标签一内容', - tab2: '标签二内容', - tab3: '标签三内容', - tab4: '标签四内容', - tab5: '标签五内容', - tab6: '标签六内容', - }; - const [key, setKey] = useState('tab1'); - const onChange = (value) => { - setKey(value); - }; - - return ( -
- -
    -
  • - {content[key]}
} - change={onChange} - > - - - -
- ); -} diff --git a/src/tabs/_example/content.tsx b/src/tabs/_example/content.tsx new file mode 100644 index 00000000..5f64a040 --- /dev/null +++ b/src/tabs/_example/content.tsx @@ -0,0 +1,39 @@ +import React, { useState } from 'react'; +import { Tabs, TabPanel } from 'tdesign-mobile-react'; + +export default () => { + const list = [ + { + value: '1', + label: '选项', + panel: '内容区1', + }, + { + value: '2', + label: '选项', + panel: '内容区2', + }, + { + value: '3', + label: '上限六个文字', + panel: '内容区3', + }, + ]; + + const [currentValue, setCurrentValue] = useState('1'); + const onChange = (value: string) => { + setCurrentValue(value); + console.log(value); + }; + return ( +
+ + {list.map((item) => ( + +

{item.panel}

+
+ ))} +
+
+ ); +}; diff --git a/src/tabs/_example/evenly.tsx b/src/tabs/_example/evenly.tsx new file mode 100644 index 00000000..c311f1ca --- /dev/null +++ b/src/tabs/_example/evenly.tsx @@ -0,0 +1,81 @@ +import React from 'react'; +import { Tabs, TabPanel } from 'tdesign-mobile-react'; + +export default () => { + const stickyProps = { + zIndex: 999, + }; + const tabPanels = [ + { + value: 'first', + label: '选项', + }, + { + value: 'second', + label: '选项', + }, + { + value: 'third', + label: '选项', + }, + { + value: 'four', + label: '选项', + }, + ]; + + const tabPanelsNext = [ + { + value: '1', + label: '选项', + }, + { + value: '2', + label: '选项', + }, + { + value: '3', + label: '选项', + }, + { + value: '4', + label: '选项', + }, + { + value: '5', + label: '选项', + }, + ]; + + const onChange = ($event: number, label: string) => { + console.log(`change to ${$event}`, label); + }; + + const onNextChange = ($event: number) => { + console.log(`changeNext to ${$event}`); + }; + + return ( +
+ + 选项
}> + 选项}> + + + + + + + + {tabPanels.map((item, index) => ( + + ))} + + + {tabPanelsNext.map((item, index) => ( + + ))} + + + ); +}; diff --git a/src/tabs/_example/horizontal.jsx b/src/tabs/_example/horizontal.jsx deleted file mode 100644 index 467b4bc4..00000000 --- a/src/tabs/_example/horizontal.jsx +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react'; -import { Tabs, TabPanel } from 'tdesign-mobile-react'; -import TDemoBlock from '../../../site/mobile/components/DemoBlock'; -import './style.less'; - -export default function () { - return ( - -
    -
  • - - -
    标签一内容
    -
    - -
    标签二内容
    -
    - -
    标签三内容
    -
    -
    -
  • -
-
- ); -} diff --git a/src/tabs/_example/icon.tsx b/src/tabs/_example/icon.tsx new file mode 100644 index 00000000..62f6d3d9 --- /dev/null +++ b/src/tabs/_example/icon.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { Tabs, TabPanel } from 'tdesign-mobile-react'; +import { IconFont } from 'tdesign-icons-react'; + +export default () => ( +
+ + + + 选项 +
+ } + > + + + 选项 + + } + > + + + 选项 + + } + > + + +); diff --git a/src/tabs/_example/index.jsx b/src/tabs/_example/index.jsx deleted file mode 100644 index 48c1f213..00000000 --- a/src/tabs/_example/index.jsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; -import TDemoHeader from '../../../site/mobile/components/DemoHeader'; -import TDemoBlock from '../../../site/mobile/components/DemoBlock'; - -import Base from './base'; -import Horizontal from './horizontal'; -import Scroll from './scroll'; -import Vertical from './vertical'; -import Size from './size'; -import NoLine from './noline'; -import Bottom from './bottom'; -import './style.less'; - -export default function () { - return ( -
- - - - - - - - - - - - - - -
- ); -} diff --git a/src/tabs/_example/index.tsx b/src/tabs/_example/index.tsx new file mode 100644 index 00000000..17fca211 --- /dev/null +++ b/src/tabs/_example/index.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import TDemoHeader from '../../../site/mobile/components/DemoHeader'; +import TDemoBlock from '../../../site/mobile/components/DemoBlock'; +import EvenlyDemo from './evenly'; +import IsometricDemo from './isometric'; +import IconDemo from './icon'; +import BadgeDemo from './badge'; +import ContentDemo from './content'; +import StatusDemo from './status'; +import SizeDemo from './size'; +import ThemeDemo from './theme'; +import './style/index.less'; + +export default function () { + return ( +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ ); +} diff --git a/src/tabs/_example/isometric.tsx b/src/tabs/_example/isometric.tsx new file mode 100644 index 00000000..b5f6d21f --- /dev/null +++ b/src/tabs/_example/isometric.tsx @@ -0,0 +1,55 @@ +import React, { useState } from 'react'; +import { Tabs } from 'tdesign-mobile-react'; + +export default () => { + const list = [ + { + value: '1', + label: '选项', + }, + { + value: '2', + label: '选项', + }, + { + value: '3', + label: '选项', + }, + { + value: '4', + label: '选项', + }, + { + value: '5', + label: '选项', + }, + { + value: '6', + label: '选项', + }, + + { + value: '7', + label: '选项', + }, + { + value: '8', + label: '选项', + }, + { + value: '9', + label: '选项', + }, + ]; + + const [currentValue, setCurrentValue] = useState('1'); + const onChange = (value: string) => { + setCurrentValue(value); + console.log(value); + }; + return ( +
+ +
+ ); +}; diff --git a/src/tabs/_example/noline.jsx b/src/tabs/_example/noline.jsx deleted file mode 100644 index bf373e63..00000000 --- a/src/tabs/_example/noline.jsx +++ /dev/null @@ -1,57 +0,0 @@ -import React, { useState } from 'react'; -import { Tabs } from 'tdesign-mobile-react'; -import TDemoBlock from '../../../site/mobile/components/DemoBlock'; -import './style.less'; - -export default function () { - const [key, setKey] = useState('tab1'); - const list3 = [ - { - label: '标签页一', - value: 'tab1', - }, - { - label: '标签页二', - value: 'tab2', - }, - { - label: '标签页三', - value: 'tab3', - }, - { - label: '标签页四', - value: 'tab4', - }, - { - label: '标签页五', - value: 'tab5', - }, - { - label: '标签页六', - value: 'tab6', - }, - ]; - const content = { - tab1: '标签一内容', - tab2: '标签二内容', - tab3: '标签三内容', - tab4: '标签四内容', - tab5: '标签五内容', - tab6: '标签六内容', - }; - - const onChange = (value) => { - setKey(value); - }; - - return ( - -
    -
  • - -
    {content[key]}
    -
  • -
-
- ); -} diff --git a/src/tabs/_example/scroll.jsx b/src/tabs/_example/scroll.jsx deleted file mode 100644 index fd8b5fbf..00000000 --- a/src/tabs/_example/scroll.jsx +++ /dev/null @@ -1,55 +0,0 @@ -import React, { useState } from 'react'; -import { Tabs } from 'tdesign-mobile-react'; -import './style.less'; - -export default function () { - const [key, setKey] = useState('tab1'); - const list4 = [ - { - label: '标签页一', - value: 'tab1', - }, - { - label: '标签页二', - value: 'tab2', - }, - { - label: '标签页三', - value: 'tab3', - }, - { - label: '标签页四', - value: 'tab4', - }, - { - label: '标签页五', - value: 'tab5', - }, - { - label: '标签页六', - value: 'tab6', - }, - ]; - - const content = { - tab1: '标签一内容', - tab2: '标签二内容', - tab3: '标签三内容', - tab4: '标签四内容', - tab5: '标签五内容', - tab6: '标签六内容', - }; - - const onChange = (value) => { - setKey(value); - }; - - return ( -
    -
  • - -
    {content[key]}
    -
  • -
- ); -} diff --git a/src/tabs/_example/size.jsx b/src/tabs/_example/size.jsx deleted file mode 100644 index 4cdef3a6..00000000 --- a/src/tabs/_example/size.jsx +++ /dev/null @@ -1,45 +0,0 @@ -import React from 'react'; -import { Tabs, TabPanel } from 'tdesign-mobile-react'; -import TDemoBlock from '../../../site/mobile/components/DemoBlock'; -import './style.less'; - -export default function () { - return ( - -
    -
  • - - -
    标签一内容
    -
    - -
    标签二内容
    -
    - -
    标签三内容
    -
    - -
    标签四内容
    -
    -
    -
  • -
  • - - -
    标签一内容
    -
    - -
    标签二内容
    -
    - -
    标签三内容
    -
    - -
    标签四内容
    -
    -
    -
  • -
-
- ); -} diff --git a/src/tabs/_example/size.tsx b/src/tabs/_example/size.tsx new file mode 100644 index 00000000..a28fc7bd --- /dev/null +++ b/src/tabs/_example/size.tsx @@ -0,0 +1,57 @@ +import React from 'react'; +import { Tabs, TabPanel } from 'tdesign-mobile-react'; + +export default () => { + const mediumTabPanels = [ + { + value: '1', + label: '小尺寸', + }, + { + value: '2', + label: '选项', + }, + { + value: '3', + label: '选项', + }, + { + value: '4', + label: '选项', + }, + ]; + + const largeTabPanels = [ + { + value: '1', + label: '大尺寸', + }, + { + value: '2', + label: '选项', + }, + { + value: '3', + label: '选项', + }, + { + value: '4', + label: '选项', + }, + ]; + + return ( +
+ + {mediumTabPanels.map((item, index) => ( + + ))} + + + {largeTabPanels.map((item, index) => ( + + ))} + +
+ ); +}; diff --git a/src/tabs/_example/status.tsx b/src/tabs/_example/status.tsx new file mode 100644 index 00000000..633c5244 --- /dev/null +++ b/src/tabs/_example/status.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { Tabs, TabPanel } from 'tdesign-mobile-react'; + +export default () => ( +
+ + + + + +
+); diff --git a/src/tabs/_example/style.less b/src/tabs/_example/style.less deleted file mode 100644 index 751e41a1..00000000 --- a/src/tabs/_example/style.less +++ /dev/null @@ -1,47 +0,0 @@ -.hori-wrap { - li { - margin-bottom: 38px; - } - - li:last-child { - margin-bottom: 12px; - } -} - -.veti-wrap { - li { - margin-bottom: 38px; - } -} - -.common-text { - font-size: 14px; - color: #111111; - font-weight: 400; -} - -.tdesign-mobile-demo { - padding-bottom: 38px; -} - -.tab-content { - width: 100%; - height: 86px; - background: #fff; - display: flex; - justify-content: center; - align-items: center; - font-size: 14px; - color: rgba(0, 0, 0, 0.26); -} - -.veti-content { - width: 100%; - height: 100%; - background: #fff; - display: flex; - justify-content: center; - align-items: center; - font-size: 14px; - color: rgba(0, 0, 0, 0.26); -} diff --git a/src/tabs/_example/style/index.less b/src/tabs/_example/style/index.less new file mode 100644 index 00000000..6898b1b7 --- /dev/null +++ b/src/tabs/_example/style/index.less @@ -0,0 +1,23 @@ +.t-tabs { + margin-bottom: 16px; +} +.label-content { + display: flex; + align-items: center; + justify-content: center; + + span { + margin-left: 3px; + } +} + +.t-tabs__panel p { + height: 120px; + align-items: center; + justify-content: center; + display: flex; + color: var(--td-text-color-secondary, rgba(#000000, 0.66)); + margin: 0; + font-size: 14px; + font-weight: 400; +} diff --git a/src/tabs/_example/test.jsx b/src/tabs/_example/test.jsx deleted file mode 100644 index f93151c0..00000000 --- a/src/tabs/_example/test.jsx +++ /dev/null @@ -1,6 +0,0 @@ -import React from 'react'; -import { Tabs } from 'tdesign-mobile-react'; - -export default function () { - return test1; -} diff --git a/src/tabs/_example/theme.tsx b/src/tabs/_example/theme.tsx new file mode 100644 index 00000000..e9a40676 --- /dev/null +++ b/src/tabs/_example/theme.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { Tabs, TabPanel } from 'tdesign-mobile-react'; + +export default () => { + const tabPanels = [ + { + value: '1', + label: '选项', + }, + { + value: '2', + label: '选项', + }, + { + value: '3', + label: '选项', + }, + { + value: '4', + label: '选项', + }, + ]; + + return ( +
+ + {tabPanels.map((item, index) => ( + + ))} + + + {tabPanels.map((item, index) => ( + + ))} + +
+ ); +}; diff --git a/src/tabs/_example/vertical.jsx b/src/tabs/_example/vertical.jsx deleted file mode 100644 index e32e4141..00000000 --- a/src/tabs/_example/vertical.jsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react'; -import { Tabs, TabPanel } from 'tdesign-mobile-react'; -import './style.less'; - -export default function () { - return ( -
    -
  • - - -
    标签一内容
    -
    - -
    标签二内容
    -
    - -
    标签三内容
    -
    - -
    标签四内容
    -
    - -
    标签五内容
    -
    -
    -
  • -
- ); -} diff --git a/src/tabs/defaultProps.ts b/src/tabs/defaultProps.ts new file mode 100755 index 00000000..8294a998 --- /dev/null +++ b/src/tabs/defaultProps.ts @@ -0,0 +1,17 @@ +/** + * 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC + * */ + +import { TdTabsProps, TdTabPanelProps } from './type'; + +export const tabsDefaultProps: TdTabsProps = { + bottomLineMode: 'fixed', + showBottomLine: true, + size: 'medium', + spaceEvenly: true, + sticky: false, + swipeable: true, + theme: 'line', +}; + +export const tabPanelDefaultProps: TdTabPanelProps = { destroyOnHide: true, disabled: false, lazy: false }; diff --git a/src/tabs/style/index.js b/src/tabs/style/index.js index becd90e2..2c0e69bc 100644 --- a/src/tabs/style/index.js +++ b/src/tabs/style/index.js @@ -1 +1 @@ -import '../../_common/style/mobile/components/tabs/_index.less'; +import '../../_common/style/mobile/components/tabs/v2/_index.less'; diff --git a/src/tabs/tabs.en-US.md b/src/tabs/tabs.en-US.md new file mode 100644 index 00000000..572eef6d --- /dev/null +++ b/src/tabs/tabs.en-US.md @@ -0,0 +1,64 @@ +:: BASE_DOC :: + +## API + +### Tabs Props + +name | type | default | description | required +-- | -- | -- | -- | -- +className | String | - | className of component | N +style | Object | - | CSS(Cascading Style Sheets),Typescript:`React.CSSProperties` | N +animation | Object | - | Typescript:`TabAnimation` `type TabAnimation = { duration: number } & Record`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/tabs/type.ts) | N +bottomLineMode | String | fixed | options: fixed/auto/full | N +children | TElement | - | Typescript:`TNode`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N +list | Array | - | Typescript:`Array` | N +showBottomLine | Boolean | true | \- | N +size | String | medium | options: medium/large | N +spaceEvenly | Boolean | true | \- | N +sticky | Boolean | false | \- | N +stickyProps | Object | - | Typescript:`StickyProps`,[Sticky API Documents](./sticky?tab=api)。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/tabs/type.ts) | N +swipeable | Boolean | true | \- | N +theme | String | line | options: line/tag/card | N +value | String / Number | - | Typescript:`TabValue` `type TabValue = string \| number`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/tabs/type.ts) | N +defaultValue | String / Number | - | uncontrolled property。Typescript:`TabValue` `type TabValue = string \| number`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/tabs/type.ts) | N +onChange | Function | | Typescript:`(value: TabValue, label: string) => void`
| N +onClick | Function | | Typescript:`(value: TabValue, label: string) => void`
| N +onScroll | Function | | Typescript:`(scrollTop: number, isFixed: boolean) => void`
| N + + +### TabPanel Props + +name | type | default | description | required +-- | -- | -- | -- | -- +className | String | - | className of component | N +style | Object | - | CSS(Cascading Style Sheets),Typescript:`React.CSSProperties` | N +badgeProps | Object | - | \- | N +destroyOnHide | Boolean | true | \- | N +disabled | Boolean | false | \- | N +label | TNode | - | Typescript:`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N +lazy | Boolean | false | Enable tab lazy loading | N +panel | TNode | - | Typescript:`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N +value | String / Number | - | Typescript:`TabValue` | N + +### CSS Variables + +The component provides the following CSS variables, which can be used to customize styles. +Name | Default Value | Description +-- | -- | -- +--td-tab-border-color | @component-stroke | - +--td-tab-font-size | 14px | - +--td-tab-icon-size | 16px | - +--td-tab-track-color | @brand-color | - +--td-tab-track-radius | 4px | - +--td-tab-track-thickness | 3px | - +--td-tab-track-width | 16px | - +--td-tab-nav-bg-color | @bg-color-container | - +--td-tab-item-active-color | @brand-color | - +--td-tab-item-color | @font-gray-1 | - +--td-tab-item-disabled-color | @font-gray-4 | - +--td-tab-item-height | 48px | - +--td-tab-item-tag-active-bg | @brand-color-light | - +--td-tab-item-tag-bg | @bg-color-secondarycontainer | - +--td-tab-item-tag-height | 32px | - +--td-tab-item-vertical-height | 54px | - +--td-tab-item-vertical-width | 104px | - \ No newline at end of file diff --git a/src/tabs/tabs.md b/src/tabs/tabs.md index 82a3b7b7..def57c8d 100644 --- a/src/tabs/tabs.md +++ b/src/tabs/tabs.md @@ -4,22 +4,61 @@ ### Tabs Props -名称 | 类型 | 默认值 | 说明 | 必传 +名称 | 类型 | 默认值 | 描述 | 必传 -- | -- | -- | -- | -- className | String | - | 类名 | N style | Object | - | 样式,TS 类型:`React.CSSProperties` | N -list | Array | - | 选项卡列表 | N -animation | Object | - | 动画效果设置{transition-timing-function, transition-duration}。 | N -placement | String | top | 选项卡位置,可选值'left'\|'right'\|'top'\|'bottom' | N -showBottomLine | Boolean | true | 是否展示底部激活条 | N -value | String/Number | - | 激活的选项卡值 | N -defaultValue | String/number | - | 默认选中的值 | N -disabled | Boolean | - | 是否禁用选项卡 | N -content | \\\ | - | 传到容器中的内容 | N +animation | Object | - | 动画效果设置。其中 duration 表示动画时长。TS 类型:`TabAnimation` `type TabAnimation = { duration: number } & Record`。[详细类型定义](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/tabs/type.ts) | N +bottomLineMode | String | fixed | 激活下划线的模式。可选项:fixed/auto/full | N +children | TElement | - | 组件子元素。TS 类型:`TNode`。[通用类型定义](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N +list | Array | - | 选项卡列表。TS 类型:`Array` | N +showBottomLine | Boolean | true | 是否展示底部激活线条 | N +size | String | medium | 组件尺寸。可选项:medium/large | N +spaceEvenly | Boolean | true | 选项卡头部空间是否均分 | N +sticky | Boolean | false | 是否开启粘性布局 | N +stickyProps | Object | - | 透传至 Sticky 组件。TS 类型:`StickyProps`,[Sticky API Documents](./sticky?tab=api)。[详细类型定义](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/tabs/type.ts) | N +swipeable | Boolean | true | 是否可以滑动切换 | N +theme | String | line | 标签的样式。可选项:line/tag/card | N +value | String / Number | - | 激活的选项卡值。TS 类型:`TabValue` `type TabValue = string \| number`。[详细类型定义](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/tabs/type.ts) | N +defaultValue | String / Number | - | 激活的选项卡值。非受控属性。TS 类型:`TabValue` `type TabValue = string \| number`。[详细类型定义](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/tabs/type.ts) | N +onChange | Function | | TS 类型:`(value: TabValue, label: string) => void`
激活的选项卡发生变化时触发 | N +onClick | Function | | TS 类型:`(value: TabValue, label: string) => void`
点击选项卡时触发 | N +onScroll | Function | | TS 类型:`(scrollTop: number, isFixed: boolean) => void`
页面滚动时触发 | N -### Tabs Events +### TabPanel Props -名称 | 描述 --- | -- -change | 激活的选项卡时触发,参数:(value: TabValue) \ No newline at end of file +名称 | 类型 | 默认值 | 描述 | 必传 +-- | -- | -- | -- | -- +className | String | - | 类名 | N +style | Object | - | 样式,TS 类型:`React.CSSProperties` | N +badgeProps | Object | - | 透传至 Badge 组件 | N +destroyOnHide | Boolean | true | 选项卡内容隐藏时是否销毁 | N +disabled | Boolean | false | 是否禁用当前选项卡 | N +label | TNode | - | 选项卡名称,可自定义选项卡导航内容。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N +lazy | Boolean | false | 是否启用选项卡懒加载 | N +panel | TNode | - | 用于自定义选项卡面板内容。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N +value | String / Number | - | 选项卡的值,唯一标识。TS 类型:`TabValue` | N + +### CSS Variables + +组件提供了下列 CSS 变量,可用于自定义样式。 +名称 | 默认值 | 描述 +-- | -- | -- +--td-tab-border-color | @component-stroke | - +--td-tab-font-size | 14px | - +--td-tab-icon-size | 16px | - +--td-tab-track-color | @brand-color | - +--td-tab-track-radius | 4px | - +--td-tab-track-thickness | 3px | - +--td-tab-track-width | 16px | 当前激活 tab 下划线的宽度,仅在 bottomLineMode 为 'fixed' 时有效 +--td-tab-nav-bg-color | @bg-color-container | - +--td-tab-item-active-color | @brand-color | - +--td-tab-item-color | @font-gray-1 | - +--td-tab-item-disabled-color | @font-gray-4 | - +--td-tab-item-height | 48px | - +--td-tab-item-tag-active-bg | @brand-color-light | - +--td-tab-item-tag-bg | @bg-color-secondarycontainer | - +--td-tab-item-tag-height | 32px | - +--td-tab-item-vertical-height | 54px | - +--td-tab-item-vertical-width | 104px | - \ No newline at end of file diff --git a/src/tabs/type.ts b/src/tabs/type.ts index b04cf223..be08963e 100644 --- a/src/tabs/type.ts +++ b/src/tabs/type.ts @@ -2,68 +2,119 @@ /** * 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC - * updated at 2022-01-07 16:30:49 * */ -import { CSSProperties } from 'react'; -import { TNode } from '../common'; -export interface TdTabPanelProps { +import { StickyProps } from '../sticky'; +import { TNode, TElement } from '../common'; + +export interface TdTabsProps { /** - * 触发元素,同 triggerElement + * 动画效果设置。其中 duration 表示动画时长 */ - children?: TNode; + animation?: TabAnimation; /** - * 标签名 + * 激活下划线的模式 + * @default fixed */ - label: string; + bottomLineMode?: 'fixed' | 'auto' | 'full'; /** - * 标签值 + * 组件子元素 */ - value: string | number; + children?: TElement; /** - * 是否禁用 + * 选项卡列表 */ - disabled?: boolean; -} - -export interface TdTabsProps { + list?: Array; + /** + * 是否展示底部激活线条 + * @default true + */ + showBottomLine?: boolean; /** - * 触发元素,同 triggerElement + * 组件尺寸 + * @default medium */ - children?: any; + size?: 'medium' | 'large'; /** - * 容器内容 + * 选项卡头部空间是否均分 + * @default true */ - content?: TNode; + spaceEvenly?: boolean; /** - * 默认选中值 + * 是否开启粘性布局 + * @default false */ - defaultValue?: number | string; + sticky?: boolean; /** - * 选项卡列表 - * @default [] + * 透传至 Sticky 组件 */ - list?: Array; + stickyProps?: StickyProps; /** - * 动画效果设置 + * 是否可以滑动切换 + * @default true */ - animation?: CSSProperties; + swipeable?: boolean; /** - * 选项卡位置 - * @default 'top' + * 标签的样式 + * @default line */ - placement?: 'left' | 'right' | 'bottom'; + theme?: 'line' | 'tag' | 'card'; /** - * 是否展示底部激活条 - * @default true + * 激活的选项卡值 */ - showBottomLine?: boolean; + value?: TabValue; /** - * 尺寸大小 + * 激活的选项卡值,非受控属性 */ - size?: 'large' | 'small' | 'default'; + defaultValue?: TabValue; /** * 激活的选项卡发生变化时触发 */ - change?: (value) => void; + onChange?: (value: TabValue, label: string) => void; + /** + * 点击选项卡时触发 + */ + onClick?: (value: TabValue, label: string) => void; + /** + * 页面滚动时触发 + */ + onScroll?: (scrollTop: number, isFixed: boolean) => void; +} + +export interface TdTabPanelProps { + /** + * 透传至 Badge 组件 + */ + badgeProps?: object; + /** + * 选项卡内容隐藏时是否销毁 + * @default true + */ + destroyOnHide?: boolean; + /** + * 是否禁用当前选项卡 + * @default false + */ + disabled?: boolean; + /** + * 选项卡名称,可自定义选项卡导航内容 + */ + label?: TNode; + /** + * 是否启用选项卡懒加载 + * @default false + */ + lazy?: boolean; + /** + * 用于自定义选项卡面板内容 + */ + panel?: TNode; + /** + * 选项卡的值,唯一标识 + */ + value?: TabValue; } + +export type TabAnimation = { duration: number } & Record; + +export type TabValue = string | number; diff --git a/test/snap/__snapshots__/csr.test.jsx.snap b/test/snap/__snapshots__/csr.test.jsx.snap index 928afa03..7f634efe 100644 --- a/test/snap/__snapshots__/csr.test.jsx.snap +++ b/test/snap/__snapshots__/csr.test.jsx.snap @@ -36413,6 +36413,3490 @@ exports[`csr snapshot test > csr test src/table/_example/stripe.tsx 1`] = ` `; +exports[`csr snapshot test > csr test src/tabs/_example/badge.tsx 1`] = ` +
+
+
+
+
+
+
+
+
+
+
+
+
+ 选项 +
+
+
+
+
+
+
+
+
+
+
+ 选项 +
+
+
+
+ 8 +
+
+
+
+
+
+
+
+ 选项 +
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+`; + +exports[`csr snapshot test > csr test src/tabs/_example/content.tsx 1`] = ` +
+
+
+
+
+
+
+
+
+
+
+
+
+ 选项 +
+
+
+
+
+
+
+
+
+
+ 选项 +
+
+
+
+
+
+
+
+
+
+ 上限六个文字 +
+
+
+
+
+
+
+
+
+
+
+
+
+

+ 内容区1 +

+
+ + +
+
+
+
+`; + +exports[`csr snapshot test > csr test src/tabs/_example/evenly.tsx 1`] = ` +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 选项 +
+
+
+
+
+
+
+
+
+
+
+
+ 选项 +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ 选项 +
+
+
+
+
+
+
+
+
+
+ 选项 +
+
+
+
+
+
+
+
+
+
+ 选项 +
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+ 选项 +
+
+
+
+
+
+
+
+
+
+ 选项 +
+
+
+
+
+
+
+
+
+
+ 选项 +
+
+
+
+
+
+
+
+
+
+ 选项 +
+
+
+
+
+
+
+
+
+
+
+
+
+