Skip to content

Commit

Permalink
chore(Tabs): 隐藏当pane过长时的滚动条,优化下拉选项样式 (#634)
Browse files Browse the repository at this point in the history
  • Loading branch information
cuilanxin committed Mar 8, 2022
1 parent 20c9c3d commit 99f4007
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 40 deletions.
4 changes: 4 additions & 0 deletions packages/react-form/src/style/form-item.less
Expand Up @@ -10,6 +10,10 @@
color: #dc3545;
}
.w-input-inner,
.w-select,
.w-select-default,
.w-select:hover,
.w-select-default:hover,
.w-input-inner:hover,
.w-input-inner:focus.w-input-inner:hover,
.w-textarea,
Expand Down
44 changes: 41 additions & 3 deletions packages/react-tabs/README.md
Expand Up @@ -146,18 +146,56 @@ class Demo extends React.Component {
ReactDOM.render(<Demo />, _mount_);
```

## Tabs.Porps
### 超出收缩

当pane过多,超出宽度度时,会将超出部分收缩到下拉选项

<!--rehype:bgWhite=true&codeSandbox=true&codePen=true-->
```jsx
import React from 'react';
import ReactDOM from 'react-dom';
import { Tabs } from 'uiw';

class Demo extends React.Component {

render() {
const panes=[]
for (let index = 0; index < 20; index++) {
panes.push({label:`Tabs-${index}`,key:index})
}

return (
<Tabs
style={{width:700}}
activeKey="1"
onTabClick={(tab, key, e) => {
console.log("=>", key, tab);
}}>
{panes.map(item=>{
return(
<Tabs.Pane label={item.label} key={item.key}>{item.label}</Tabs.Pane>
)
})}
</Tabs>
);
}
}
ReactDOM.render(<Demo />, _mount_);
```


## Tabs.Porps

| 参数 | 说明 | 类型 | 默认值 |
|------ |-------- |---------- |-------- |
| type | 页签的基本样式 | Enum{`line``card``default`} | `default` |
| activeKey | 当前激活 `tab` 面板的 `key` | String | - |
| onTabClick | `tab` 被点击的回调 | Function | `(item, key, e)=>{}` |

## Tabs.Pane.Porps
## Tabs.Pane.Porps

| 参数 | 说明 | 类型 | 默认值 |
|------ |-------- |---------- |-------- |
| label | 选项卡标题 | String,Node | - |
| key | 对应 activeKey | String,Node | - |
| disabled | 标签是禁用不可点击 | Boolean | `false` |
| disabled | 标签是禁用不可点击 | Boolean | `false` |
1 change: 1 addition & 0 deletions packages/react-tabs/package.json
Expand Up @@ -44,6 +44,7 @@
"react-dom": ">=16.9.0"
},
"dependencies": {
"@uiw/react-popover": "^4.13.7",
"@uiw/utils": "^4.13.7"
}
}
156 changes: 119 additions & 37 deletions packages/react-tabs/src/index.tsx
@@ -1,7 +1,8 @@
import React, { useEffect, useState, useRef } from 'react';
import React, { useEffect, useState, useRef, useCallback, useMemo } from 'react';
import { IProps, HTMLDivProps } from '@uiw/utils';
import Pane from './Pane';
import './style/index.less';
import Popover from '@uiw/react-popover';

export * from './Pane';

Expand All @@ -15,6 +16,14 @@ export interface TabsProps extends IProps, HTMLDivProps {
onTabClick?: (key: string, item: React.ReactElement, e: React.MouseEvent) => void;
}

type FlowNavType = {
content: number;
nav: Array<Record<'index' | 'width' | 'curWidth', number>>;
flowLeft: number;
displayStart: number;
displayEnd: number;
};

export default function Tabs(props: TabsProps) {
const {
prefixCls = 'w-tabs',
Expand All @@ -25,11 +34,70 @@ export default function Tabs(props: TabsProps) {
onTabClick,
...elementProps
} = props;

const [activeKey, setActiveKey] = useState(props.activeKey);
const [slideStyle, setSlideStyle] = useState({ width: 0, left: 0 });
const activeItem = useRef<HTMLDivElement | undefined>();
const cls = [prefixCls, className, type ? `${prefixCls}-${type}` : null].filter(Boolean).join(' ').trim();

const [flowNav, flowNavSet] = useState<FlowNavType>({
content: 0,
nav: [],
flowLeft: -1,
displayStart: 0,
displayEnd: 0,
});
const [hiddenNav, hiddenNavSet] = useState<Array<number>>([]);
const deviation = 15;

const [nodes, nodesSet] = useState<any>();
const divContentRef = useCallback((node) => {
if (node !== null) {
nodesSet(nodes);
node.addEventListener('scroll', (e: any) => {
const { clientWidth, scrollLeft } = e.target;
flowNav.displayStart = scrollLeft;
flowNav.displayEnd = clientWidth + scrollLeft;
flowNavSet({ ...flowNav });
});
flowNav.displayEnd = node.getBoundingClientRect().width;
flowNavSet({ ...flowNav });
}
}, []);

const divNavRef = useCallback((node, key: number) => {
if (node !== null) {
node.addEventListener('click', (e: any) => {
activeItem.current = node;
});
divNavWidthChange(node.getBoundingClientRect().width, key);
}
}, []);

const divNavWidthChange = (width: number, index: number) => {
let curWidth = 0;
flowNav.nav.slice(0, index + 1).forEach((nav) => (curWidth += nav.width));
flowNav.nav[index] = { width, curWidth: Math.floor(curWidth), index };
flowNavSet(flowNav);
};

useEffect(() => {
showHideenNav();
}, [flowNav.displayEnd > flowNav.nav[flowNav.nav.length - 1]?.curWidth]);

const showHideenNav = () => {
const hiddenNav: Array<number> = [];
if (flowNav.nav.length > 0) {
flowNav.nav.forEach((item) => {
const curWidth = item.curWidth - deviation;
if (curWidth < flowNav.displayStart || curWidth > flowNav.displayEnd) {
hiddenNav.push(item.index);
}
});
hiddenNavSet(hiddenNav);
}
};

useEffect(() => setActiveKey(props.activeKey), [props.activeKey]);
useEffect(() => calcSlideStyle(), [activeKey]);

Expand All @@ -44,44 +112,31 @@ export default function Tabs(props: TabsProps) {

return (
<div className={cls} {...elementProps}>
<div className={`${prefixCls}-bar`}>
<div className={`${prefixCls}-nav`}>
{React.Children.map(children as React.ReactElement[], (item: React.ReactElement, key: number) => {
if (!item) {
return null;
}
const divProps: HTMLDivProps = {
className: [
`${prefixCls}-item`,
item.key === activeKey ? 'active' : null,
item.props.disabled ? 'disabled' : null,
]
.filter(Boolean)
.join(' ')
.trim(),
children: item.props.label,
};
if (!item.props.disabled) {
divProps.onClick = (e: React.MouseEvent) => {
setActiveKey(item.key as string);
onTabClick && onTabClick(item.key as string, item, e);
calcSlideStyle();
};
}
return (
<div
key={key}
ref={(node) => {
if (node && item.key === activeKey) {
activeItem.current = node;
}
}}
{...divProps}
/>
);
})}
<div style={{ display: 'flex' }}>
<div style={{ overflow: 'hidden' }}>
<div className={`${prefixCls}-bar`} ref={divContentRef}>
<div className={`${prefixCls}-nav`} style={{ width: 'max-content' }}>
{renderNav(children)}
</div>
</div>
</div>
<div style={slideStyle} className={`${prefixCls}-slide`} />
{hiddenNav.length > 0 && (
<Popover
trigger="click"
placement="bottomRight"
visibleArrow={false}
content={
<div className={`${prefixCls}-nav-hidden`}>
{renderNav(hiddenNav.map((idx) => (children as Array<React.ReactElement>)[idx]))}
</div>
}
>
<div onClick={showHideenNav} className={`${prefixCls}-flow-content`}>
<span></span>
</div>
</Popover>
)}
</div>
{React.Children.map(children, (item: any) => {
if (!item || activeKey !== item.key) {
Expand All @@ -91,4 +146,31 @@ export default function Tabs(props: TabsProps) {
})}
</div>
);

function renderNav(children: React.ReactNode): React.ReactNode {
return React.Children.map(children as React.ReactElement[], (item: React.ReactElement, key: number) => {
if (!item) {
return null;
}
const divProps: HTMLDivProps = {
className: [
`${prefixCls}-item`,
item.key === activeKey ? 'active' : null,
item.props.disabled ? 'disabled' : null,
]
.filter(Boolean)
.join(' ')
.trim(),
children: item.props.label,
};
if (!item.props.disabled) {
divProps.onClick = (e: React.MouseEvent) => {
setActiveKey(item.key as string);
onTabClick && onTabClick(item.key as string, item, e);
calcSlideStyle();
};
}
return <div key={key} ref={(ref) => divNavRef(ref, key)} {...divProps} />;
});
}
}
15 changes: 15 additions & 0 deletions packages/react-tabs/src/style/index.less
Expand Up @@ -3,10 +3,19 @@
.@{w-tabs} {
&-bar {
position: relative;
overflow-x: auto;
height: calc(100% + 17px);
}
&-nav {
position: relative;
}
&-nav-hidden {
display: flex;
overflow-x: scroll;
padding: 5px 30px 5px 5px;
max-height: 200px;
flex-direction: column;
}
&-item {
padding: 7px 10px;
display: inline-block;
Expand All @@ -26,6 +35,12 @@
color: rgba(0, 0, 0, 0.25);
}
}
&-flow-content {
margin-left: 5px;
padding: 0px 10px 0px 10px;
box-shadow: 1px 0px 0px #d9d9d9 inset;
cursor: pointer;
}
}

.@{w-tabs} {
Expand Down

0 comments on commit 99f4007

Please sign in to comment.