forked from patternfly/patternfly-react
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathOverflowTab.tsx
More file actions
163 lines (145 loc) · 5.46 KB
/
OverflowTab.tsx
File metadata and controls
163 lines (145 loc) · 5.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
import { Fragment, useContext, useEffect, useRef, useState } from 'react';
import styles from '@patternfly/react-styles/css/components/Tabs/tabs';
import { css } from '@patternfly/react-styles';
import AngleRightIcon from '@patternfly/react-icons/dist/esm/icons/angle-right-icon';
import { Popper, PopperOptions } from '../../helpers';
import { Menu, MenuContent, MenuList, MenuItem } from '../Menu';
import { TabsContext } from './TabsContext';
import { TabProps } from './Tab';
import { TabTitleText } from './TabTitleText';
/** @deprecated Use PopperOptions instead */
export type HorizontalOverflowPopperProps = PopperOptions;
export interface OverflowTabProps extends React.HTMLProps<HTMLLIElement> {
/** Additional classes added to the overflow tab */
className?: string;
/** The tabs that should be displayed in the menu */
overflowingTabs?: TabProps[];
/** Flag which shows the count of overflowing tabs when enabled */
showTabCount?: boolean;
/** The text which displays when an overflowing tab isn't selected */
defaultTitleText?: string;
/** The aria label applied to the button which toggles the tab overflow menu */
toggleAriaLabel?: string;
/** z-index of the overflow tab */
zIndex?: number;
/** Flag indicating if scroll on focus of the first menu item should occur. */
shouldPreventScrollOnItemFocus?: boolean;
/** Time in ms to wait before firing the toggles' focus event. Defaults to 0 */
focusTimeoutDelay?: number;
/** Additional props to spread to the popper menu. */
popperProps?: PopperOptions;
}
export const OverflowTab: React.FunctionComponent<OverflowTabProps> = ({
className,
overflowingTabs = [],
showTabCount,
defaultTitleText = 'More',
toggleAriaLabel,
zIndex = 9999,
shouldPreventScrollOnItemFocus = true,
focusTimeoutDelay = 0,
popperProps,
...props
}: OverflowTabProps) => {
const menuRef = useRef<HTMLDivElement>(undefined);
const overflowTabRef = useRef<HTMLButtonElement>(undefined);
const overflowLIRef = useRef<HTMLLIElement>(undefined);
const [isExpanded, setIsExpanded] = useState(false);
const { localActiveKey, handleTabClick } = useContext(TabsContext);
const closeMenu = () => {
setIsExpanded(false);
overflowTabRef.current.focus();
};
const handleMenuKeys = (ev: KeyboardEvent) => {
const menuContainsEventTarget = menuRef?.current?.contains(ev.target as Node);
if (isExpanded && menuContainsEventTarget && ev.key === 'Escape') {
closeMenu();
}
};
const handleClick = (ev: MouseEvent) => {
const clickIsOutsideMenu = !menuRef?.current?.contains(ev.target as Node);
const clickIsOutsideOverflowTab = !overflowTabRef?.current?.contains(ev.target as Node);
if (isExpanded && clickIsOutsideMenu && clickIsOutsideOverflowTab) {
closeMenu();
}
};
useEffect(() => {
window.addEventListener('click', handleClick);
window.addEventListener('keydown', handleMenuKeys);
return () => {
window.removeEventListener('click', handleClick);
window.removeEventListener('keydown', handleMenuKeys);
};
}, [isExpanded, menuRef, overflowTabRef]);
const selectedTab = overflowingTabs.find((tab) => tab.eventKey === localActiveKey);
const tabTitle = selectedTab?.title ? selectedTab.title : defaultTitleText;
const toggleMenu = () => {
setIsExpanded((prevIsExpanded) => !prevIsExpanded);
setTimeout(() => {
if (menuRef?.current) {
const firstElement = menuRef.current.querySelector('li > button,input:not(:disabled)');
firstElement && (firstElement as HTMLElement).focus({ preventScroll: shouldPreventScrollOnItemFocus });
}
}, focusTimeoutDelay);
};
const overflowTab = (
<li
className={css(styles.tabsItem, styles.modifiers.overflow, selectedTab && styles.modifiers.current, className)}
role="presentation"
ref={overflowLIRef}
{...props}
>
<button
type="button"
className={css(styles.tabsLink, isExpanded && styles.modifiers.expanded)}
onClick={() => toggleMenu()}
aria-label={toggleAriaLabel}
aria-haspopup="menu"
aria-expanded={isExpanded}
role="tab"
ref={overflowTabRef}
>
<TabTitleText>
{tabTitle}
{showTabCount && tabTitle === defaultTitleText && ` (${overflowingTabs.length})`}
</TabTitleText>
<span className={styles.tabsLinkToggleIcon}>
<AngleRightIcon />
</span>
</button>
</li>
);
const tabs = overflowingTabs.map((tab) => (
<MenuItem key={tab.eventKey} itemId={tab.eventKey} isSelected={localActiveKey === tab.eventKey}>
{tab.title}
</MenuItem>
));
const onTabSelect = (event: React.MouseEvent<HTMLElement, MouseEvent>, key: number | string) => {
closeMenu();
const selectedTabRef = overflowingTabs.find((tab) => tab.eventKey === key).tabContentRef;
handleTabClick(event, key, selectedTabRef);
};
const overflowMenu = (
<Menu ref={menuRef} onSelect={(ev, itemId) => onTabSelect(ev as React.MouseEvent<HTMLElement, MouseEvent>, itemId)}>
<MenuContent>
<MenuList>{tabs}</MenuList>
</MenuContent>
</Menu>
);
return (
<Fragment>
{overflowTab}
<Popper
triggerRef={overflowTabRef}
popper={overflowMenu}
popperRef={menuRef}
isVisible={isExpanded}
minWidth="revert"
appendTo={overflowLIRef.current}
zIndex={zIndex}
{...popperProps}
/>
</Fragment>
);
};
OverflowTab.displayName = 'OverflowTab';