UNPKG

19.7 kBJavaScriptView Raw
1"use strict";
2
3var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
5Object.defineProperty(exports, "__esModule", {
6 value: true
7});
8exports.default = exports.styles = void 0;
9
10var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
11
12var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties"));
13
14var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
15
16var _react = _interopRequireDefault(require("react"));
17
18var _reactIs = require("react-is");
19
20var _propTypes = _interopRequireDefault(require("prop-types"));
21
22var _clsx = _interopRequireDefault(require("clsx"));
23
24var _utils = require("@material-ui/utils");
25
26var _debounce = _interopRequireDefault(require("../utils/debounce"));
27
28var _ownerWindow = _interopRequireDefault(require("../utils/ownerWindow"));
29
30var _normalizeScrollLeft = require("normalize-scroll-left");
31
32var _animate = _interopRequireDefault(require("../internal/animate"));
33
34var _ScrollbarSize = _interopRequireDefault(require("./ScrollbarSize"));
35
36var _withStyles = _interopRequireDefault(require("../styles/withStyles"));
37
38var _TabIndicator = _interopRequireDefault(require("./TabIndicator"));
39
40var _TabScrollButton = _interopRequireDefault(require("./TabScrollButton"));
41
42var _useEventCallback = _interopRequireDefault(require("../utils/useEventCallback"));
43
44var _useTheme = _interopRequireDefault(require("../styles/useTheme"));
45
46var styles = function styles(theme) {
47 return {
48 /* Styles applied to the root element. */
49 root: {
50 overflow: 'hidden',
51 minHeight: 48,
52 WebkitOverflowScrolling: 'touch',
53 // Add iOS momentum scrolling.
54 display: 'flex'
55 },
56
57 /* Styles applied to the root element if `orientation="vertical"`. */
58 vertical: {
59 flexDirection: 'column'
60 },
61
62 /* Styles applied to the flex container element. */
63 flexContainer: {
64 display: 'flex'
65 },
66
67 /* Styles applied to the flex container element if `orientation="vertical"`. */
68 flexContainerVertical: {
69 flexDirection: 'column'
70 },
71
72 /* Styles applied to the flex container element if `centered={true}` & `!variant="scrollable"`. */
73 centered: {
74 justifyContent: 'center'
75 },
76
77 /* Styles applied to the tablist element. */
78 scroller: {
79 position: 'relative',
80 display: 'inline-block',
81 flex: '1 1 auto',
82 whiteSpace: 'nowrap'
83 },
84
85 /* Styles applied to the tablist element if `!variant="scrollable"`. */
86 fixed: {
87 overflowX: 'hidden',
88 width: '100%'
89 },
90
91 /* Styles applied to the tablist element if `variant="scrollable"`. */
92 scrollable: {
93 overflowX: 'scroll',
94 // Hide dimensionless scrollbar on MacOS
95 scrollbarWidth: 'none',
96 // Firefox
97 '&::-webkit-scrollbar': {
98 display: 'none' // Safari + Chrome
99
100 }
101 },
102
103 /* Styles applied to the `ScrollButtonComponent` component. */
104 scrollButtons: {},
105
106 /* Styles applied to the `ScrollButtonComponent` component if `scrollButtons="auto"` or scrollButtons="desktop"`. */
107 scrollButtonsDesktop: (0, _defineProperty2.default)({}, theme.breakpoints.down('xs'), {
108 display: 'none'
109 }),
110
111 /* Styles applied to the `TabIndicator` component. */
112 indicator: {}
113 };
114};
115
116exports.styles = styles;
117
118var Tabs = _react.default.forwardRef(function Tabs(props, ref) {
119 var action = props.action,
120 _props$centered = props.centered,
121 centered = _props$centered === void 0 ? false : _props$centered,
122 childrenProp = props.children,
123 classes = props.classes,
124 className = props.className,
125 _props$component = props.component,
126 Component = _props$component === void 0 ? 'div' : _props$component,
127 _props$indicatorColor = props.indicatorColor,
128 indicatorColor = _props$indicatorColor === void 0 ? 'secondary' : _props$indicatorColor,
129 onChange = props.onChange,
130 _props$orientation = props.orientation,
131 orientation = _props$orientation === void 0 ? 'horizontal' : _props$orientation,
132 _props$ScrollButtonCo = props.ScrollButtonComponent,
133 ScrollButtonComponent = _props$ScrollButtonCo === void 0 ? _TabScrollButton.default : _props$ScrollButtonCo,
134 _props$scrollButtons = props.scrollButtons,
135 scrollButtons = _props$scrollButtons === void 0 ? 'auto' : _props$scrollButtons,
136 _props$TabIndicatorPr = props.TabIndicatorProps,
137 TabIndicatorProps = _props$TabIndicatorPr === void 0 ? {} : _props$TabIndicatorPr,
138 _props$textColor = props.textColor,
139 textColor = _props$textColor === void 0 ? 'inherit' : _props$textColor,
140 value = props.value,
141 _props$variant = props.variant,
142 variant = _props$variant === void 0 ? 'standard' : _props$variant,
143 other = (0, _objectWithoutProperties2.default)(props, ["action", "centered", "children", "classes", "className", "component", "indicatorColor", "onChange", "orientation", "ScrollButtonComponent", "scrollButtons", "TabIndicatorProps", "textColor", "value", "variant"]);
144 var theme = (0, _useTheme.default)();
145 var scrollable = variant === 'scrollable';
146 var isRtl = theme.direction === 'rtl';
147 var vertical = orientation === 'vertical';
148 var scrollStart = vertical ? 'scrollTop' : 'scrollLeft';
149 var start = vertical ? 'top' : 'left';
150 var end = vertical ? 'bottom' : 'right';
151 var clientSize = vertical ? 'clientHeight' : 'clientWidth';
152 var size = vertical ? 'height' : 'width';
153
154 if (process.env.NODE_ENV !== 'production') {
155 if (centered && scrollable) {
156 console.error('Material-UI: you can not use the `centered={true}` and `variant="scrollable"` properties ' + 'at the same time on a `Tabs` component.');
157 }
158 }
159
160 var _React$useState = _react.default.useState(false),
161 mounted = _React$useState[0],
162 setMounted = _React$useState[1];
163
164 var _React$useState2 = _react.default.useState({}),
165 indicatorStyle = _React$useState2[0],
166 setIndicatorStyle = _React$useState2[1];
167
168 var _React$useState3 = _react.default.useState({
169 start: false,
170 end: false
171 }),
172 displayScroll = _React$useState3[0],
173 setDisplayScroll = _React$useState3[1];
174
175 var _React$useState4 = _react.default.useState({
176 overflow: 'hidden',
177 marginBottom: null
178 }),
179 scrollerStyle = _React$useState4[0],
180 setScrollerStyle = _React$useState4[1];
181
182 var valueToIndex = new Map();
183
184 var tabsRef = _react.default.useRef(null);
185
186 var childrenWrapperRef = _react.default.useRef(null);
187
188 var getTabsMeta = function getTabsMeta() {
189 var tabsNode = tabsRef.current;
190 var tabsMeta;
191
192 if (tabsNode) {
193 var rect = tabsNode.getBoundingClientRect(); // create a new object with ClientRect class props + scrollLeft
194
195 tabsMeta = {
196 clientWidth: tabsNode.clientWidth,
197 scrollLeft: tabsNode.scrollLeft,
198 scrollTop: tabsNode.scrollTop,
199 scrollLeftNormalized: (0, _normalizeScrollLeft.getNormalizedScrollLeft)(tabsNode, theme.direction),
200 scrollWidth: tabsNode.scrollWidth,
201 top: rect.top,
202 bottom: rect.bottom,
203 left: rect.left,
204 right: rect.right
205 };
206 }
207
208 var tabMeta;
209
210 if (tabsNode && value !== false) {
211 var _children = childrenWrapperRef.current.children;
212
213 if (_children.length > 0) {
214 var tab = _children[valueToIndex.get(value)];
215
216 if (process.env.NODE_ENV !== 'production') {
217 if (!tab) {
218 console.error(["Material-UI: the value provided `".concat(value, "` to the Tabs component is invalid."), 'None of the Tabs children have this value.', valueToIndex.keys ? "You can provide one of the following values: ".concat(Array.from(valueToIndex.keys()).join(', '), ".") : null].join('\n'));
219 }
220 }
221
222 tabMeta = tab ? tab.getBoundingClientRect() : null;
223 }
224 }
225
226 return {
227 tabsMeta: tabsMeta,
228 tabMeta: tabMeta
229 };
230 };
231
232 var updateIndicatorState = (0, _useEventCallback.default)(function () {
233 var _newIndicatorStyle;
234
235 var _getTabsMeta = getTabsMeta(),
236 tabsMeta = _getTabsMeta.tabsMeta,
237 tabMeta = _getTabsMeta.tabMeta;
238
239 var startValue = 0;
240
241 if (tabMeta && tabsMeta) {
242 if (vertical) {
243 startValue = tabMeta.top - tabsMeta.top + tabsMeta.scrollTop;
244 } else {
245 var correction = isRtl ? tabsMeta.scrollLeftNormalized + tabsMeta.clientWidth - tabsMeta.scrollWidth : tabsMeta.scrollLeft;
246 startValue = tabMeta.left - tabsMeta.left + correction;
247 }
248 }
249
250 var newIndicatorStyle = (_newIndicatorStyle = {}, (0, _defineProperty2.default)(_newIndicatorStyle, start, startValue), (0, _defineProperty2.default)(_newIndicatorStyle, size, tabMeta ? tabMeta[size] : 0), _newIndicatorStyle);
251
252 if (isNaN(indicatorStyle[start]) || isNaN(indicatorStyle[size])) {
253 setIndicatorStyle(newIndicatorStyle);
254 } else {
255 var dStart = Math.abs(indicatorStyle[start] - newIndicatorStyle[start]);
256 var dSize = Math.abs(indicatorStyle[size] - newIndicatorStyle[size]);
257
258 if (dStart >= 1 || dSize >= 1) {
259 setIndicatorStyle(newIndicatorStyle);
260 }
261 }
262 });
263
264 var scroll = function scroll(scrollValue) {
265 (0, _animate.default)(scrollStart, tabsRef.current, scrollValue);
266 };
267
268 var moveTabsScroll = function moveTabsScroll(delta) {
269 var scrollValue = tabsRef.current[scrollStart];
270
271 if (vertical) {
272 scrollValue += delta;
273 } else {
274 scrollValue += delta * (isRtl ? -1 : 1); // Fix for Edge
275
276 scrollValue *= isRtl && (0, _normalizeScrollLeft.detectScrollType)() === 'reverse' ? -1 : 1;
277 }
278
279 scroll(scrollValue);
280 };
281
282 var handleStartScrollClick = function handleStartScrollClick() {
283 moveTabsScroll(-tabsRef.current[clientSize]);
284 };
285
286 var handleEndScrollClick = function handleEndScrollClick() {
287 moveTabsScroll(tabsRef.current[clientSize]);
288 };
289
290 var handleScrollbarSizeChange = _react.default.useCallback(function (scrollbarHeight) {
291 setScrollerStyle({
292 overflow: null,
293 marginBottom: -scrollbarHeight
294 });
295 }, []);
296
297 var getConditionalElements = function getConditionalElements() {
298 var conditionalElements = {};
299 conditionalElements.scrollbarSizeListener = scrollable ? _react.default.createElement(_ScrollbarSize.default, {
300 className: classes.scrollable,
301 onChange: handleScrollbarSizeChange
302 }) : null;
303 var scrollButtonsActive = displayScroll.start || displayScroll.end;
304 var showScrollButtons = scrollable && (scrollButtons === 'auto' && scrollButtonsActive || scrollButtons === 'desktop' || scrollButtons === 'on');
305 conditionalElements.scrollButtonStart = showScrollButtons ? _react.default.createElement(ScrollButtonComponent, {
306 orientation: orientation,
307 direction: isRtl ? 'right' : 'left',
308 onClick: handleStartScrollClick,
309 visible: displayScroll.start,
310 className: (0, _clsx.default)(classes.scrollButtons, scrollButtons !== 'on' && classes.scrollButtonsDesktop)
311 }) : null;
312 conditionalElements.scrollButtonEnd = showScrollButtons ? _react.default.createElement(ScrollButtonComponent, {
313 orientation: orientation,
314 direction: isRtl ? 'left' : 'right',
315 onClick: handleEndScrollClick,
316 visible: displayScroll.end,
317 className: (0, _clsx.default)(classes.scrollButtons, scrollButtons !== 'on' && classes.scrollButtonsDesktop)
318 }) : null;
319 return conditionalElements;
320 };
321
322 var scrollSelectedIntoView = (0, _useEventCallback.default)(function () {
323 var _getTabsMeta2 = getTabsMeta(),
324 tabsMeta = _getTabsMeta2.tabsMeta,
325 tabMeta = _getTabsMeta2.tabMeta;
326
327 if (!tabMeta || !tabsMeta) {
328 return;
329 }
330
331 if (tabMeta[start] < tabsMeta[start]) {
332 // left side of button is out of view
333 var nextScrollStart = tabsMeta[scrollStart] + (tabMeta[start] - tabsMeta[start]);
334 scroll(nextScrollStart);
335 } else if (tabMeta[end] > tabsMeta[end]) {
336 // right side of button is out of view
337 var _nextScrollStart = tabsMeta[scrollStart] + (tabMeta[end] - tabsMeta[end]);
338
339 scroll(_nextScrollStart);
340 }
341 });
342 var updateScrollButtonState = (0, _useEventCallback.default)(function () {
343 if (scrollable && scrollButtons !== 'off') {
344 var _tabsRef$current = tabsRef.current,
345 scrollTop = _tabsRef$current.scrollTop,
346 scrollHeight = _tabsRef$current.scrollHeight,
347 clientHeight = _tabsRef$current.clientHeight,
348 scrollWidth = _tabsRef$current.scrollWidth,
349 clientWidth = _tabsRef$current.clientWidth;
350 var showStartScroll;
351 var showEndScroll;
352
353 if (vertical) {
354 showStartScroll = scrollTop > 1;
355 showEndScroll = scrollTop < scrollHeight - clientHeight - 1;
356 } else {
357 var scrollLeft = (0, _normalizeScrollLeft.getNormalizedScrollLeft)(tabsRef.current, theme.direction); // use 1 for the potential rounding error with browser zooms.
358
359 showStartScroll = isRtl ? scrollLeft < scrollWidth - clientWidth - 1 : scrollLeft > 1;
360 showEndScroll = !isRtl ? scrollLeft < scrollWidth - clientWidth - 1 : scrollLeft > 1;
361 }
362
363 if (showStartScroll !== displayScroll.start || showEndScroll !== displayScroll.end) {
364 setDisplayScroll({
365 start: showStartScroll,
366 end: showEndScroll
367 });
368 }
369 }
370 });
371
372 _react.default.useEffect(function () {
373 var handleResize = (0, _debounce.default)(function () {
374 updateIndicatorState();
375 updateScrollButtonState();
376 });
377 var win = (0, _ownerWindow.default)(tabsRef.current);
378 win.addEventListener('resize', handleResize);
379 return function () {
380 handleResize.clear();
381 win.removeEventListener('resize', handleResize);
382 };
383 }, [updateIndicatorState, updateScrollButtonState]);
384
385 var handleTabsScroll = _react.default.useCallback((0, _debounce.default)(function () {
386 updateScrollButtonState();
387 }));
388
389 _react.default.useEffect(function () {
390 return function () {
391 handleTabsScroll.clear();
392 };
393 }, [handleTabsScroll]);
394
395 _react.default.useEffect(function () {
396 setMounted(true);
397 }, []);
398
399 _react.default.useEffect(function () {
400 updateIndicatorState();
401 updateScrollButtonState();
402 });
403
404 _react.default.useEffect(function () {
405 scrollSelectedIntoView();
406 }, [scrollSelectedIntoView, indicatorStyle]);
407
408 _react.default.useImperativeHandle(action, function () {
409 return {
410 updateIndicator: updateIndicatorState,
411 updateScrollButtons: updateScrollButtonState
412 };
413 }, [updateIndicatorState, updateScrollButtonState]);
414
415 var indicator = _react.default.createElement(_TabIndicator.default, (0, _extends2.default)({
416 className: classes.indicator,
417 orientation: orientation,
418 color: indicatorColor
419 }, TabIndicatorProps, {
420 style: (0, _extends2.default)({}, indicatorStyle, {}, TabIndicatorProps.style)
421 }));
422
423 var childIndex = 0;
424
425 var children = _react.default.Children.map(childrenProp, function (child) {
426 if (!_react.default.isValidElement(child)) {
427 return null;
428 }
429
430 if (process.env.NODE_ENV !== 'production') {
431 if ((0, _reactIs.isFragment)(child)) {
432 console.error(["Material-UI: the Tabs component doesn't accept a Fragment as a child.", 'Consider providing an array instead.'].join('\n'));
433 }
434 }
435
436 var childValue = child.props.value === undefined ? childIndex : child.props.value;
437 valueToIndex.set(childValue, childIndex);
438 var selected = childValue === value;
439 childIndex += 1;
440 return _react.default.cloneElement(child, {
441 fullWidth: variant === 'fullWidth',
442 indicator: selected && !mounted && indicator,
443 selected: selected,
444 onChange: onChange,
445 textColor: textColor,
446 value: childValue
447 });
448 });
449
450 var conditionalElements = getConditionalElements();
451 return _react.default.createElement(Component, (0, _extends2.default)({
452 className: (0, _clsx.default)(classes.root, className, vertical && classes.vertical),
453 ref: ref
454 }, other), conditionalElements.scrollButtonStart, conditionalElements.scrollbarSizeListener, _react.default.createElement("div", {
455 className: (0, _clsx.default)(classes.scroller, scrollable ? classes.scrollable : classes.fixed),
456 style: scrollerStyle,
457 ref: tabsRef,
458 onScroll: handleTabsScroll
459 }, _react.default.createElement("div", {
460 className: (0, _clsx.default)(classes.flexContainer, vertical && classes.flexContainerVertical, centered && !scrollable && classes.centered),
461 ref: childrenWrapperRef,
462 role: "tablist"
463 }, children), mounted && indicator), conditionalElements.scrollButtonEnd);
464});
465
466process.env.NODE_ENV !== "production" ? Tabs.propTypes = {
467 /**
468 * Callback fired when the component mounts.
469 * This is useful when you want to trigger an action programmatically.
470 * It supports two actions: `updateIndicator()` and `updateScrollButtons()`
471 *
472 * @param {object} actions This object contains all possible actions
473 * that can be triggered programmatically.
474 */
475 action: _utils.refType,
476
477 /**
478 * If `true`, the tabs will be centered.
479 * This property is intended for large views.
480 */
481 centered: _propTypes.default.bool,
482
483 /**
484 * The content of the component.
485 */
486 children: _propTypes.default.node,
487
488 /**
489 * Override or extend the styles applied to the component.
490 * See [CSS API](#css) below for more details.
491 */
492 classes: _propTypes.default.object.isRequired,
493
494 /**
495 * @ignore
496 */
497 className: _propTypes.default.string,
498
499 /**
500 * The component used for the root node.
501 * Either a string to use a DOM element or a component.
502 */
503 component: _propTypes.default.elementType,
504
505 /**
506 * Determines the color of the indicator.
507 */
508 indicatorColor: _propTypes.default.oneOf(['secondary', 'primary']),
509
510 /**
511 * Callback fired when the value changes.
512 *
513 * @param {object} event The event source of the callback
514 * @param {any} value We default to the index of the child (number)
515 */
516 onChange: _propTypes.default.func,
517
518 /**
519 * The tabs orientation (layout flow direction).
520 */
521 orientation: _propTypes.default.oneOf(['horizontal', 'vertical']),
522
523 /**
524 * The component used to render the scroll buttons.
525 */
526 ScrollButtonComponent: _propTypes.default.elementType,
527
528 /**
529 * Determine behavior of scroll buttons when tabs are set to scroll:
530 *
531 * - `auto` will only present them when not all the items are visible.
532 * - `desktop` will only present them on medium and larger viewports.
533 * - `on` will always present them.
534 * - `off` will never present them.
535 */
536 scrollButtons: _propTypes.default.oneOf(['auto', 'desktop', 'on', 'off']),
537
538 /**
539 * Props applied to the tab indicator element.
540 */
541 TabIndicatorProps: _propTypes.default.object,
542
543 /**
544 * Determines the color of the `Tab`.
545 */
546 textColor: _propTypes.default.oneOf(['secondary', 'primary', 'inherit']),
547
548 /**
549 * The value of the currently selected `Tab`.
550 * If you don't want any selected `Tab`, you can set this property to `false`.
551 */
552 value: _propTypes.default.any,
553
554 /**
555 * Determines additional display behavior of the tabs:
556 *
557 * - `scrollable` will invoke scrolling properties and allow for horizontally
558 * scrolling (or swiping) of the tab bar.
559 * -`fullWidth` will make the tabs grow to use all the available space,
560 * which should be used for small views, like on mobile.
561 * - `standard` will render the default state.
562 */
563 variant: _propTypes.default.oneOf(['standard', 'scrollable', 'fullWidth'])
564} : void 0;
565
566var _default = (0, _withStyles.default)(styles, {
567 name: 'MuiTabs'
568})(Tabs);
569
570exports.default = _default;
\No newline at end of file