UNPKG

13.6 kBJavaScriptView Raw
1import { __rest } from "tslib";
2import * as React from 'react';
3import styles from '@patternfly/react-styles/css/components/Tabs/tabs';
4import buttonStyles from '@patternfly/react-styles/css/components/Button/button';
5import { css } from '@patternfly/react-styles';
6import AngleLeftIcon from '@patternfly/react-icons/dist/esm/icons/angle-left-icon';
7import AngleRightIcon from '@patternfly/react-icons/dist/esm/icons/angle-right-icon';
8import PlusIcon from '@patternfly/react-icons/dist/esm/icons/plus-icon';
9import { getUniqueId, isElementInView, formatBreakpointMods } from '../../helpers/util';
10import { TabContent } from './TabContent';
11import { TabsContextProvider } from './TabsContext';
12import { Button } from '../Button';
13import { getOUIAProps, getDefaultOUIAId, canUseDOM } from '../../helpers';
14import { GenerateId } from '../../helpers/GenerateId/GenerateId';
15export var TabsComponent;
16(function (TabsComponent) {
17 TabsComponent["div"] = "div";
18 TabsComponent["nav"] = "nav";
19})(TabsComponent || (TabsComponent = {}));
20const variantStyle = {
21 default: '',
22 light300: styles.modifiers.colorSchemeLight_300
23};
24export class Tabs extends React.Component {
25 constructor(props) {
26 super(props);
27 this.tabList = React.createRef();
28 this.scrollTimeout = null;
29 this.handleScrollButtons = () => {
30 // add debounce to the scroll event
31 clearTimeout(this.scrollTimeout);
32 this.scrollTimeout = setTimeout(() => {
33 const container = this.tabList.current;
34 let disableLeftScrollButton = true;
35 let disableRightScrollButton = true;
36 let showScrollButtons = false;
37 if (container && !this.props.isVertical) {
38 // get first element and check if it is in view
39 const overflowOnLeft = !isElementInView(container, container.firstChild, false);
40 // get last element and check if it is in view
41 const overflowOnRight = !isElementInView(container, container.lastChild, false);
42 showScrollButtons = overflowOnLeft || overflowOnRight;
43 disableLeftScrollButton = !overflowOnLeft;
44 disableRightScrollButton = !overflowOnRight;
45 }
46 this.setState({
47 showScrollButtons,
48 disableLeftScrollButton,
49 disableRightScrollButton
50 });
51 }, 100);
52 };
53 this.scrollLeft = () => {
54 // find first Element that is fully in view on the left, then scroll to the element before it
55 if (this.tabList.current) {
56 const container = this.tabList.current;
57 const childrenArr = Array.from(container.children);
58 let firstElementInView;
59 let lastElementOutOfView;
60 let i;
61 for (i = 0; i < childrenArr.length && !firstElementInView; i++) {
62 if (isElementInView(container, childrenArr[i], false)) {
63 firstElementInView = childrenArr[i];
64 lastElementOutOfView = childrenArr[i - 1];
65 }
66 }
67 if (lastElementOutOfView) {
68 container.scrollLeft -= lastElementOutOfView.scrollWidth;
69 }
70 }
71 };
72 this.scrollRight = () => {
73 // find last Element that is fully in view on the right, then scroll to the element after it
74 if (this.tabList.current) {
75 const container = this.tabList.current;
76 const childrenArr = Array.from(container.children);
77 let lastElementInView;
78 let firstElementOutOfView;
79 for (let i = childrenArr.length - 1; i >= 0 && !lastElementInView; i--) {
80 if (isElementInView(container, childrenArr[i], false)) {
81 lastElementInView = childrenArr[i];
82 firstElementOutOfView = childrenArr[i + 1];
83 }
84 }
85 if (firstElementOutOfView) {
86 container.scrollLeft += firstElementOutOfView.scrollWidth;
87 }
88 }
89 };
90 this.state = {
91 showScrollButtons: false,
92 disableLeftScrollButton: true,
93 disableRightScrollButton: true,
94 shownKeys: this.props.defaultActiveKey !== undefined ? [this.props.defaultActiveKey] : [this.props.activeKey],
95 uncontrolledActiveKey: this.props.defaultActiveKey,
96 uncontrolledIsExpandedLocal: this.props.defaultIsExpanded,
97 ouiaStateId: getDefaultOUIAId(Tabs.displayName)
98 };
99 if (this.props.isVertical && this.props.expandable !== undefined) {
100 if (!this.props.toggleAriaLabel && !this.props.toggleText) {
101 // eslint-disable-next-line no-console
102 console.error('Tabs:', 'toggleAriaLabel or the toggleText prop is required to make the toggle button accessible');
103 }
104 }
105 }
106 handleTabClick(event, eventKey, tabContentRef) {
107 const { shownKeys } = this.state;
108 const { onSelect, defaultActiveKey } = this.props;
109 // if defaultActiveKey Tabs are uncontrolled, set new active key internally
110 if (defaultActiveKey !== undefined) {
111 this.setState({
112 uncontrolledActiveKey: eventKey
113 });
114 }
115 else {
116 onSelect(event, eventKey);
117 }
118 // process any tab content sections outside of the component
119 if (tabContentRef) {
120 React.Children.toArray(this.props.children)
121 .map(child => child)
122 .filter(child => child.props && child.props.tabContentRef && child.props.tabContentRef.current)
123 .forEach(child => (child.props.tabContentRef.current.hidden = true));
124 // most recently selected tabContent
125 if (tabContentRef.current) {
126 tabContentRef.current.hidden = false;
127 }
128 }
129 if (this.props.mountOnEnter) {
130 this.setState({
131 shownKeys: shownKeys.concat(eventKey)
132 });
133 }
134 }
135 componentDidMount() {
136 if (!this.props.isVertical) {
137 if (canUseDOM) {
138 window.addEventListener('resize', this.handleScrollButtons, false);
139 }
140 // call the handle resize function to check if scroll buttons should be shown
141 this.handleScrollButtons();
142 }
143 }
144 componentWillUnmount() {
145 if (!this.props.isVertical) {
146 if (canUseDOM) {
147 window.removeEventListener('resize', this.handleScrollButtons, false);
148 }
149 }
150 clearTimeout(this.scrollTimeout);
151 }
152 componentDidUpdate(prevProps) {
153 const { activeKey, mountOnEnter, children } = this.props;
154 const { shownKeys } = this.state;
155 if (prevProps.activeKey !== activeKey && mountOnEnter && shownKeys.indexOf(activeKey) < 0) {
156 this.setState({
157 shownKeys: shownKeys.concat(activeKey)
158 });
159 }
160 if (prevProps.children &&
161 children &&
162 React.Children.toArray(prevProps.children).length !== React.Children.toArray(children).length) {
163 this.handleScrollButtons();
164 }
165 }
166 render() {
167 const _a = this.props, { className, children, activeKey, defaultActiveKey, id, isFilled, isSecondary, isVertical, isBox, hasBorderBottom, hasSecondaryBorderBottom, leftScrollAriaLabel, rightScrollAriaLabel, 'aria-label': ariaLabel, component, ouiaId, ouiaSafe, mountOnEnter, unmountOnExit, usePageInsets, inset, variant, expandable, isExpanded, defaultIsExpanded, toggleText, toggleAriaLabel, addButtonAriaLabel, onToggle, onClose, onAdd } = _a, props = __rest(_a, ["className", "children", "activeKey", "defaultActiveKey", "id", "isFilled", "isSecondary", "isVertical", "isBox", "hasBorderBottom", "hasSecondaryBorderBottom", "leftScrollAriaLabel", "rightScrollAriaLabel", 'aria-label', "component", "ouiaId", "ouiaSafe", "mountOnEnter", "unmountOnExit", "usePageInsets", "inset", "variant", "expandable", "isExpanded", "defaultIsExpanded", "toggleText", "toggleAriaLabel", "addButtonAriaLabel", "onToggle", "onClose", "onAdd"]);
168 const { showScrollButtons, disableLeftScrollButton, disableRightScrollButton, shownKeys, uncontrolledActiveKey, uncontrolledIsExpandedLocal } = this.state;
169 const filteredChildren = React.Children.toArray(children)
170 .filter(Boolean)
171 .filter(child => !child.props.isHidden);
172 const uniqueId = id || getUniqueId();
173 const Component = component === TabsComponent.nav ? 'nav' : 'div';
174 const localActiveKey = defaultActiveKey !== undefined ? uncontrolledActiveKey : activeKey;
175 const isExpandedLocal = defaultIsExpanded !== undefined ? uncontrolledIsExpandedLocal : isExpanded;
176 /* Uncontrolled expandable tabs */
177 const toggleTabs = (newValue) => {
178 if (isExpanded === undefined) {
179 this.setState({ uncontrolledIsExpandedLocal: newValue });
180 }
181 else {
182 onToggle(newValue);
183 }
184 };
185 return (React.createElement(TabsContextProvider, { value: {
186 variant,
187 mountOnEnter,
188 unmountOnExit,
189 localActiveKey,
190 uniqueId,
191 handleTabClick: (...args) => this.handleTabClick(...args),
192 handleTabClose: onClose
193 } },
194 React.createElement(Component, Object.assign({ "aria-label": ariaLabel, className: css(styles.tabs, isFilled && styles.modifiers.fill, isSecondary && styles.modifiers.secondary, isVertical && styles.modifiers.vertical, isVertical && expandable && formatBreakpointMods(expandable, styles), isVertical && expandable && isExpandedLocal && styles.modifiers.expanded, isBox && styles.modifiers.box, showScrollButtons && !isVertical && styles.modifiers.scrollable, usePageInsets && styles.modifiers.pageInsets, !hasBorderBottom && styles.modifiers.noBorderBottom, hasSecondaryBorderBottom && styles.modifiers.borderBottom, formatBreakpointMods(inset, styles), variantStyle[variant], className) }, getOUIAProps(Tabs.displayName, ouiaId !== undefined ? ouiaId : this.state.ouiaStateId, ouiaSafe), { id: id && id }, props),
195 expandable && isVertical && (React.createElement(GenerateId, null, randomId => (React.createElement("div", { className: css(styles.tabsToggle) },
196 React.createElement("div", { className: css(styles.tabsToggleButton) },
197 React.createElement(Button, { onClick: () => toggleTabs(!isExpandedLocal), variant: "plain", "aria-label": toggleAriaLabel, "aria-expanded": isExpandedLocal, id: `${randomId}-button`, "aria-labelledby": `${randomId}-text ${randomId}-button` },
198 React.createElement("span", { className: css(styles.tabsToggleIcon) },
199 React.createElement(AngleRightIcon, { "arian-hidden": "true" })),
200 toggleText && (React.createElement("span", { className: css('pf-c-tabs__toggle-text'), id: `${randomId}-text` }, toggleText)))))))),
201 React.createElement("button", { className: css(styles.tabsScrollButton, isSecondary && buttonStyles.modifiers.secondary), "aria-label": leftScrollAriaLabel, onClick: this.scrollLeft, disabled: disableLeftScrollButton, "aria-hidden": disableLeftScrollButton },
202 React.createElement(AngleLeftIcon, null)),
203 React.createElement("ul", { className: css(styles.tabsList), ref: this.tabList, onScroll: this.handleScrollButtons, role: "tablist" }, filteredChildren),
204 React.createElement("button", { className: css(styles.tabsScrollButton, isSecondary && buttonStyles.modifiers.secondary), "aria-label": rightScrollAriaLabel, onClick: this.scrollRight, disabled: disableRightScrollButton, "aria-hidden": disableRightScrollButton },
205 React.createElement(AngleRightIcon, null)),
206 onAdd !== undefined && (React.createElement("span", { className: css(styles.tabsAdd) },
207 React.createElement(Button, { variant: "plain", "aria-label": addButtonAriaLabel || 'Add tab', onClick: onAdd },
208 React.createElement(PlusIcon, null))))),
209 filteredChildren
210 .filter(child => child.props.children &&
211 !(unmountOnExit && child.props.eventKey !== localActiveKey) &&
212 !(mountOnEnter && shownKeys.indexOf(child.props.eventKey) === -1))
213 .map(child => (React.createElement(TabContent, { key: child.props.eventKey, activeKey: localActiveKey, child: child, id: child.props.id || uniqueId, ouiaId: child.props.ouiaId })))));
214 }
215}
216Tabs.displayName = 'Tabs';
217Tabs.defaultProps = {
218 activeKey: 0,
219 onSelect: () => undefined,
220 isFilled: false,
221 isSecondary: false,
222 isVertical: false,
223 isBox: false,
224 hasBorderBottom: true,
225 leftScrollAriaLabel: 'Scroll left',
226 rightScrollAriaLabel: 'Scroll right',
227 component: TabsComponent.div,
228 mountOnEnter: false,
229 unmountOnExit: false,
230 ouiaSafe: true,
231 variant: 'default',
232 // eslint-disable-next-line @typescript-eslint/no-unused-vars
233 onToggle: (isExpanded) => undefined
234};
235//# sourceMappingURL=Tabs.js.map
\No newline at end of file