123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251 |
- import React, { MouseEvent, ReactElement, ReactNode } from 'react';
- import PropTypes from 'prop-types';
- import cls from 'classnames';
- import { cssClasses, strings } from '@douyinfe/semi-foundation/tabs/constants';
- import getDataAttr from '@douyinfe/semi-foundation/utils/getDataAttr';
- import OverflowList from '../overflowList';
- import Dropdown from '../dropdown';
- import Button from '../button';
- import { TabBarProps, PlainTab } from './interface';
- import { isEmpty } from 'lodash';
- import { IconChevronRight, IconChevronLeft, IconClose } from '@douyinfe/semi-icons';
- import { getUuidv4 } from '@douyinfe/semi-foundation/utils/uuid';
- export interface TabBarState {
- endInd: number;
- rePosKey: number;
- startInd: number;
- }
- export interface OverflowItem extends PlainTab {
- key: string;
- active: boolean;
- }
- class TabBar extends React.Component<TabBarProps, TabBarState> {
- static propTypes = {
- activeKey: PropTypes.string,
- className: PropTypes.string,
- collapsible: PropTypes.bool,
- list: PropTypes.array,
- onTabClick: PropTypes.func,
- size: PropTypes.oneOf(strings.SIZE),
- style: PropTypes.object,
- tabBarExtraContent: PropTypes.node,
- tabPosition: PropTypes.oneOf(strings.POSITION_MAP),
- type: PropTypes.oneOf(strings.TYPE_MAP),
- closable: PropTypes.bool,
- deleteTabItem: PropTypes.func
- };
- uuid: string;
- constructor(props: TabBarProps) {
- super(props);
- this.state = {
- endInd: props.list.length,
- rePosKey: 0,
- startInd: 0,
- };
- this.uuid = getUuidv4();
- }
- renderIcon(icon: ReactNode): ReactNode {
- return (
- <span>
- {icon}
- </span>
- );
- }
- renderExtra(): ReactNode {
- const { tabBarExtraContent, type, size } = this.props;
- const tabBarExtraContentDefaultStyle = { float: 'right' };
- const tabBarExtraContentStyle =
- tabBarExtraContent && (tabBarExtraContent as ReactElement).props ? (tabBarExtraContent as ReactElement).props.style : {};
- const extraCls = cls(cssClasses.TABS_BAR_EXTRA, {
- [`${cssClasses.TABS_BAR}-${type}-extra`]: type,
- [`${cssClasses.TABS_BAR}-${type}-extra-${size}`]: size,
- });
- if (tabBarExtraContent) {
- const tabBarStyle = { ...tabBarExtraContentDefaultStyle, ...tabBarExtraContentStyle };
- return (
- <div className={extraCls} style={tabBarStyle}>
- {tabBarExtraContent}
- </div>
- );
- }
- return null;
- }
- handleItemClick = (itemKey: string, e: MouseEvent<Element>): void => {
- this.props.onTabClick(itemKey, e);
- if (this.props.collapsible) {
- const key = this._getItemKey(itemKey);
- // eslint-disable-next-line max-len
- const tabItem = document.querySelector(`[data-uuid="${this.uuid}"] .${cssClasses.TABS_TAB}[data-scrollkey="${key}"]`);
- tabItem.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' });
- }
- };
- renderTabItem = (panel: PlainTab): ReactNode => {
- const { size, type, deleteTabItem } = this.props;
- const panelIcon = panel.icon ? this.renderIcon(panel.icon) : null;
- const closableIcon = (type === 'card' && panel.closable) ? <IconClose aria-label="Close" role="button" className={`${cssClasses.TABS_TAB}-icon-close`} onClick={(e: React.MouseEvent<HTMLSpanElement>) => deleteTabItem(panel.itemKey, e)} /> : null;
- let events = {};
- const key = panel.itemKey;
- if (!panel.disabled) {
- events = {
- onClick: (e: MouseEvent<HTMLDivElement>): void => this.handleItemClick(key, e),
- };
- }
- const isSelected = this._isActive(key);
- const className = cls(cssClasses.TABS_TAB, {
- [cssClasses.TABS_TAB_ACTIVE]: isSelected,
- [cssClasses.TABS_TAB_DISABLED]: panel.disabled,
- [`${cssClasses.TABS_TAB}-small`]: size === 'small',
- [`${cssClasses.TABS_TAB}-medium`]: size === 'medium',
- });
- return (
- <div
- role="tab"
- id={`semiTab${key}`}
- aria-controls={`semiTabPanel${key}`}
- aria-disabled={panel.disabled ? 'true' : 'false'}
- aria-selected={isSelected ? 'true' : 'false'}
- {...events}
- className={className}
- key={this._getItemKey(key)}
- >
- {panelIcon}
- {panel.tab}
- {closableIcon}
- </div>
- );
- };
- renderTabComponents = (list: Array<PlainTab>): Array<ReactNode> => list.map(panel => this.renderTabItem(panel));
- handleArrowClick = (items: Array<OverflowItem>, pos: 'start' | 'end'): void => {
- const inline = pos === 'start' ? 'end' : 'start';
- const lastItem = pos === 'start' ? items.pop() : items.shift();
- if (!lastItem) {
- return;
- }
- const key = this._getItemKey(lastItem.itemKey);
- // eslint-disable-next-line max-len
- const tabItem = document.querySelector(`[data-uuid="${this.uuid}"] .${cssClasses.TABS_TAB}[data-scrollkey="${key}"]`);
- tabItem.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline });
- };
- renderCollapse = (items: Array<OverflowItem>, icon: ReactNode, pos: 'start' | 'end'): ReactNode => {
- if (isEmpty(items)) {
- return null;
- }
- const { dropdownClassName, dropdownStyle } = this.props;
- const { rePosKey } = this.state;
- const disabled = !items.length;
- const menu = (
- <Dropdown.Menu>
- {items.map(panel => {
- const { icon: i, tab, itemKey } = panel;
- const panelIcon = i ? this.renderIcon(panel.icon) : null;
- return (
- <Dropdown.Item
- key={itemKey}
- onClick={(e): void => this.handleItemClick(itemKey, e)}
- active={this._isActive(itemKey)}
- >
- {panelIcon}
- {tab}
- </Dropdown.Item>
- );
- })}
- </Dropdown.Menu>
- );
- const arrowCls = cls({
- [`${cssClasses.TABS_BAR}-arrow-${pos}`]: pos,
- [`${cssClasses.TABS_BAR}-arrow`]: true,
- });
- const dropdownCls = cls(dropdownClassName, {
- [`${cssClasses.TABS_BAR}-dropdown`]: true,
- });
- return (
- <Dropdown
- className={dropdownCls}
- clickToHide
- clickTriggerToHide
- key={`${rePosKey}-${pos}`}
- position={pos === 'start' ? 'bottomLeft' : 'bottomRight'}
- render={disabled ? null : menu}
- showTick
- style={dropdownStyle}
- trigger={'hover'}
- >
- <div role="presentation" className={arrowCls} onClick={(e): void => this.handleArrowClick(items, pos)}>
- <Button
- disabled={disabled}
- icon={icon}
- // size="small"
- theme="borderless"
- />
- </div>
- </Dropdown>
- );
- };
- renderOverflow = (items: any[]): Array<ReactNode> => items.map((item, ind) => {
- const icon = ind === 0 ? <IconChevronLeft /> : <IconChevronRight />;
- const pos = ind === 0 ? 'start' : 'end';
- return this.renderCollapse(item, icon, pos);
- });
- renderCollapsedTab = (): ReactNode => {
- const { list } = this.props;
- const renderedList = list.map(item => {
- const { itemKey } = item;
- return { key: this._getItemKey(itemKey), active: this._isActive(itemKey), ...item };
- });
- return (
- <OverflowList
- items={renderedList}
- overflowRenderer={this.renderOverflow}
- renderMode="scroll"
- className={`${cssClasses.TABS_BAR}-overflow-list`}
- visibleItemRenderer={this.renderTabItem as any}
- />
- );
- };
- render(): ReactNode {
- const { type, style, className, list, tabPosition, collapsible, ...restProps } = this.props;
- const classNames = cls(className, {
- [cssClasses.TABS_BAR]: true,
- [cssClasses.TABS_BAR_LINE]: type === 'line',
- [cssClasses.TABS_BAR_CARD]: type === 'card',
- [cssClasses.TABS_BAR_BUTTON]: type === 'button',
- [`${cssClasses.TABS_BAR}-${tabPosition}`]: tabPosition,
- [`${cssClasses.TABS_BAR}-collapse`]: collapsible,
- });
- const extra = this.renderExtra();
- const contents = collapsible ? this.renderCollapsedTab() : this.renderTabComponents(list);
- return (
- <div role="tablist" aria-orientation={tabPosition === "left" ? "vertical" : "horizontal"} className={classNames} style={style} {...getDataAttr(restProps)} data-uuid={this.uuid}>
- {contents}
- {extra}
- </div>
- );
- }
- private _isActive = (key: string): boolean => key === this.props.activeKey;
- private _getItemKey = (key: string): string => `${key}-bar`;
- }
- export default TabBar;
|