@instructure/ui-tabs 9.0.2-snapshot-11 → 9.0.2-snapshot-15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,14 +3,20 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
- ## [9.0.2-snapshot-11](https://github.com/instructure/instructure-ui/compare/v9.0.1...v9.0.2-snapshot-11) (2024-06-12)
6
+ ## [9.0.2-snapshot-15](https://github.com/instructure/instructure-ui/compare/v9.0.1...v9.0.2-snapshot-15) (2024-06-13)
7
7
 
8
8
 
9
9
  ### Bug Fixes
10
10
 
11
+ * **ui-tabs:** fix horizontal scrolling with keyboard navigation ([a25c7db](https://github.com/instructure/instructure-ui/commit/a25c7db1ebede622f489dd65872ed8fc5a1b9651))
11
12
  * **ui-tabs:** fix id generation when null is present as children ([85765ae](https://github.com/instructure/instructure-ui/commit/85765ae3183ac121714cd814a322dcc012ed2f72))
12
13
 
13
14
 
15
+ ### Features
16
+
17
+ * **ui-tabs:** add option for persisting tabpanels ([6fe73a3](https://github.com/instructure/instructure-ui/commit/6fe73a3ec76c88fcc7baf2f587276de595316dbc))
18
+
19
+
14
20
 
15
21
 
16
22
 
@@ -1,5 +1,5 @@
1
1
  import _objectWithoutProperties from "@babel/runtime/helpers/esm/objectWithoutProperties";
2
- const _excluded = ["labelledBy", "variant", "id", "maxHeight", "minHeight", "padding", "textAlign", "children", "elementRef", "isDisabled", "isSelected", "styles", "active"];
2
+ const _excluded = ["labelledBy", "variant", "id", "maxHeight", "minHeight", "padding", "textAlign", "children", "elementRef", "isDisabled", "isSelected", "styles", "active", "unmountOnExit"];
3
3
  var _dec, _class, _Panel;
4
4
  /*
5
5
  * The MIT License (MIT)
@@ -55,40 +55,50 @@ let Panel = (_dec = withStyle(generateStyle, generateComponentTheme), _dec(_clas
55
55
  }
56
56
  componentDidMount() {
57
57
  var _this$props$makeStyle, _this$props;
58
- (_this$props$makeStyle = (_this$props = this.props).makeStyles) === null || _this$props$makeStyle === void 0 ? void 0 : _this$props$makeStyle.call(_this$props);
58
+ (_this$props$makeStyle = (_this$props = this.props).makeStyles) === null || _this$props$makeStyle === void 0 ? void 0 : _this$props$makeStyle.call(_this$props, {
59
+ isHidden: this.isHidden
60
+ });
59
61
  }
60
62
  componentDidUpdate() {
61
63
  var _this$props$makeStyle2, _this$props2;
62
- (_this$props$makeStyle2 = (_this$props2 = this.props).makeStyles) === null || _this$props$makeStyle2 === void 0 ? void 0 : _this$props$makeStyle2.call(_this$props2);
64
+ (_this$props$makeStyle2 = (_this$props2 = this.props).makeStyles) === null || _this$props$makeStyle2 === void 0 ? void 0 : _this$props$makeStyle2.call(_this$props2, {
65
+ isHidden: this.isHidden
66
+ });
63
67
  }
64
- render() {
68
+ get isHidden() {
65
69
  const _this$props3 = this.props,
66
- labelledBy = _this$props3.labelledBy,
67
- variant = _this$props3.variant,
68
- id = _this$props3.id,
69
- maxHeight = _this$props3.maxHeight,
70
- minHeight = _this$props3.minHeight,
71
- padding = _this$props3.padding,
72
- textAlign = _this$props3.textAlign,
73
- children = _this$props3.children,
74
- elementRef = _this$props3.elementRef,
75
70
  isDisabled = _this$props3.isDisabled,
76
- isSelected = _this$props3.isSelected,
77
- styles = _this$props3.styles,
78
- active = _this$props3.active,
79
- props = _objectWithoutProperties(_this$props3, _excluded);
80
- const isHidden = !isSelected || !!isDisabled;
71
+ isSelected = _this$props3.isSelected;
72
+ return !isSelected || !!isDisabled;
73
+ }
74
+ render() {
75
+ const _this$props4 = this.props,
76
+ labelledBy = _this$props4.labelledBy,
77
+ variant = _this$props4.variant,
78
+ id = _this$props4.id,
79
+ maxHeight = _this$props4.maxHeight,
80
+ minHeight = _this$props4.minHeight,
81
+ padding = _this$props4.padding,
82
+ textAlign = _this$props4.textAlign,
83
+ children = _this$props4.children,
84
+ elementRef = _this$props4.elementRef,
85
+ isDisabled = _this$props4.isDisabled,
86
+ isSelected = _this$props4.isSelected,
87
+ styles = _this$props4.styles,
88
+ active = _this$props4.active,
89
+ unmountOnExit = _this$props4.unmountOnExit,
90
+ props = _objectWithoutProperties(_this$props4, _excluded);
81
91
  return jsx("div", Object.assign({}, passthroughProps(props), {
82
92
  css: styles === null || styles === void 0 ? void 0 : styles.panel,
83
93
  role: "tabpanel",
84
94
  id: id,
85
95
  "aria-labelledby": labelledBy,
86
- "aria-hidden": isHidden ? 'true' : void 0,
96
+ "aria-hidden": this.isHidden ? 'true' : void 0,
87
97
  ref: this.handleRef
88
98
  }), jsx(Transition, {
89
99
  type: "fade",
90
- in: !isHidden,
91
- unmountOnExit: true,
100
+ in: !this.isHidden,
101
+ unmountOnExit: unmountOnExit,
92
102
  transitionExit: false
93
103
  }, jsx(View, {
94
104
  css: styles === null || styles === void 0 ? void 0 : styles.content,
@@ -105,7 +115,8 @@ let Panel = (_dec = withStyle(generateStyle, generateComponentTheme), _dec(_clas
105
115
  variant: 'default',
106
116
  isSelected: false,
107
117
  padding: 'small',
108
- active: false
118
+ active: false,
119
+ unmountOnExit: true
109
120
  }, _Panel)) || _class);
110
121
  export default Panel;
111
122
  export { Panel };
@@ -37,7 +37,8 @@ const propTypes = {
37
37
  padding: ThemeablePropTypes.spacing,
38
38
  textAlign: PropTypes.oneOf(['start', 'center', 'end']),
39
39
  elementRef: PropTypes.func,
40
- active: PropTypes.bool
40
+ active: PropTypes.bool,
41
+ unmountOnExit: PropTypes.bool
41
42
  };
42
- const allowedProps = ['renderTitle', 'children', 'variant', 'isSelected', 'isDisabled', 'maxHeight', 'minHeight', 'id', 'labelledBy', 'padding', 'textAlign', 'elementRef', 'active'];
43
+ const allowedProps = ['renderTitle', 'children', 'variant', 'isSelected', 'isDisabled', 'maxHeight', 'minHeight', 'id', 'labelledBy', 'padding', 'textAlign', 'elementRef', 'active', 'unmountOnExit'];
43
44
  export { propTypes, allowedProps };
@@ -32,9 +32,10 @@
32
32
  * @param {Object} state the state of the component, the style is applied to
33
33
  * @return {Object} The final style object, which will be used in the component
34
34
  */
35
- const generateStyle = (componentTheme, props) => {
35
+ const generateStyle = (componentTheme, props, state) => {
36
36
  const maxHeight = props.maxHeight,
37
37
  isSelected = props.isSelected;
38
+ const isHidden = state.isHidden;
38
39
  return {
39
40
  panel: {
40
41
  label: 'panel',
@@ -48,6 +49,9 @@ const generateStyle = (componentTheme, props) => {
48
49
  ...(isSelected && {
49
50
  flexGrow: 1,
50
51
  height: '100%'
52
+ }),
53
+ ...(isHidden && {
54
+ display: 'none'
51
55
  })
52
56
  },
53
57
  content: {
package/es/Tabs/index.js CHANGED
@@ -26,7 +26,7 @@ var _dec, _dec2, _class, _Tabs;
26
26
  */
27
27
 
28
28
  /** @jsx jsx */
29
- import React, { Component, createElement } from 'react';
29
+ import React, { Component } from 'react';
30
30
  import keycode from 'keycode';
31
31
  import { View } from '@instructure/ui-view';
32
32
  import { matchComponentTypes, safeCloneElement, passthroughProps } from '@instructure/ui-react-utils';
@@ -252,22 +252,26 @@ let Tabs = (_dec = withStyle(generateStyle, generateComponentTheme), _dec2 = tes
252
252
  id
253
253
  });
254
254
  }
255
- this.state.withTabListOverflow && this.showActiveTabIfOverlayed(this._tabList.querySelector(`#tab-${id}`));
255
+
256
+ // this is needed because keypress cancels scrolling. So we have to trigger the scrolling
257
+ // one "tick" later than the keypress
258
+ setTimeout(() => {
259
+ this.state.withTabListOverflow && this.showActiveTabIfOverlayed(this._tabList.querySelector(`#tab-${id}`));
260
+ }, 0);
256
261
  }
257
262
  createTab(index, generatedId, selected, panel) {
258
263
  const id = panel.props.id || generatedId;
259
- return /*#__PURE__*/createElement(Tab, {
264
+ return jsx(Tab, {
260
265
  variant: this.props.variant,
261
266
  key: `tab-${index}`,
262
267
  id: `tab-${id}`,
263
268
  controls: panel.props.id || `panel-${id}`,
264
- index,
269
+ index: index,
265
270
  isSelected: selected,
266
271
  isDisabled: panel.props.isDisabled,
267
- children: panel.props.renderTitle,
268
272
  onClick: this.handleTabClick,
269
273
  onKeyDown: this.handleTabKeyDown
270
- });
274
+ }, panel.props.renderTitle);
271
275
  }
272
276
  clonePanel(index, generatedId, selected, panel, activePanel) {
273
277
  const id = panel.props.id || generatedId;
@@ -14,7 +14,7 @@ var _emotion = require("@instructure/emotion");
14
14
  var _styles = _interopRequireDefault(require("./styles"));
15
15
  var _theme = _interopRequireDefault(require("./theme"));
16
16
  var _props = require("./props");
17
- const _excluded = ["labelledBy", "variant", "id", "maxHeight", "minHeight", "padding", "textAlign", "children", "elementRef", "isDisabled", "isSelected", "styles", "active"];
17
+ const _excluded = ["labelledBy", "variant", "id", "maxHeight", "minHeight", "padding", "textAlign", "children", "elementRef", "isDisabled", "isSelected", "styles", "active", "unmountOnExit"];
18
18
  var _dec, _class, _Panel;
19
19
  /*
20
20
  * The MIT License (MIT)
@@ -60,40 +60,50 @@ let Panel = exports.Panel = (_dec = (0, _emotion.withStyle)(_styles.default, _th
60
60
  }
61
61
  componentDidMount() {
62
62
  var _this$props$makeStyle, _this$props;
63
- (_this$props$makeStyle = (_this$props = this.props).makeStyles) === null || _this$props$makeStyle === void 0 ? void 0 : _this$props$makeStyle.call(_this$props);
63
+ (_this$props$makeStyle = (_this$props = this.props).makeStyles) === null || _this$props$makeStyle === void 0 ? void 0 : _this$props$makeStyle.call(_this$props, {
64
+ isHidden: this.isHidden
65
+ });
64
66
  }
65
67
  componentDidUpdate() {
66
68
  var _this$props$makeStyle2, _this$props2;
67
- (_this$props$makeStyle2 = (_this$props2 = this.props).makeStyles) === null || _this$props$makeStyle2 === void 0 ? void 0 : _this$props$makeStyle2.call(_this$props2);
69
+ (_this$props$makeStyle2 = (_this$props2 = this.props).makeStyles) === null || _this$props$makeStyle2 === void 0 ? void 0 : _this$props$makeStyle2.call(_this$props2, {
70
+ isHidden: this.isHidden
71
+ });
68
72
  }
69
- render() {
73
+ get isHidden() {
70
74
  const _this$props3 = this.props,
71
- labelledBy = _this$props3.labelledBy,
72
- variant = _this$props3.variant,
73
- id = _this$props3.id,
74
- maxHeight = _this$props3.maxHeight,
75
- minHeight = _this$props3.minHeight,
76
- padding = _this$props3.padding,
77
- textAlign = _this$props3.textAlign,
78
- children = _this$props3.children,
79
- elementRef = _this$props3.elementRef,
80
75
  isDisabled = _this$props3.isDisabled,
81
- isSelected = _this$props3.isSelected,
82
- styles = _this$props3.styles,
83
- active = _this$props3.active,
84
- props = (0, _objectWithoutProperties2.default)(_this$props3, _excluded);
85
- const isHidden = !isSelected || !!isDisabled;
76
+ isSelected = _this$props3.isSelected;
77
+ return !isSelected || !!isDisabled;
78
+ }
79
+ render() {
80
+ const _this$props4 = this.props,
81
+ labelledBy = _this$props4.labelledBy,
82
+ variant = _this$props4.variant,
83
+ id = _this$props4.id,
84
+ maxHeight = _this$props4.maxHeight,
85
+ minHeight = _this$props4.minHeight,
86
+ padding = _this$props4.padding,
87
+ textAlign = _this$props4.textAlign,
88
+ children = _this$props4.children,
89
+ elementRef = _this$props4.elementRef,
90
+ isDisabled = _this$props4.isDisabled,
91
+ isSelected = _this$props4.isSelected,
92
+ styles = _this$props4.styles,
93
+ active = _this$props4.active,
94
+ unmountOnExit = _this$props4.unmountOnExit,
95
+ props = (0, _objectWithoutProperties2.default)(_this$props4, _excluded);
86
96
  return (0, _emotion.jsx)("div", Object.assign({}, (0, _passthroughProps.passthroughProps)(props), {
87
97
  css: styles === null || styles === void 0 ? void 0 : styles.panel,
88
98
  role: "tabpanel",
89
99
  id: id,
90
100
  "aria-labelledby": labelledBy,
91
- "aria-hidden": isHidden ? 'true' : void 0,
101
+ "aria-hidden": this.isHidden ? 'true' : void 0,
92
102
  ref: this.handleRef
93
103
  }), (0, _emotion.jsx)(_Transition.Transition, {
94
104
  type: "fade",
95
- in: !isHidden,
96
- unmountOnExit: true,
105
+ in: !this.isHidden,
106
+ unmountOnExit: unmountOnExit,
97
107
  transitionExit: false
98
108
  }, (0, _emotion.jsx)(_View.View, {
99
109
  css: styles === null || styles === void 0 ? void 0 : styles.content,
@@ -110,6 +120,7 @@ let Panel = exports.Panel = (_dec = (0, _emotion.withStyle)(_styles.default, _th
110
120
  variant: 'default',
111
121
  isSelected: false,
112
122
  padding: 'small',
113
- active: false
123
+ active: false,
124
+ unmountOnExit: true
114
125
  }, _Panel)) || _class);
115
126
  var _default = exports.default = Panel;
@@ -44,6 +44,7 @@ const propTypes = exports.propTypes = {
44
44
  padding: _emotion.ThemeablePropTypes.spacing,
45
45
  textAlign: _propTypes.default.oneOf(['start', 'center', 'end']),
46
46
  elementRef: _propTypes.default.func,
47
- active: _propTypes.default.bool
47
+ active: _propTypes.default.bool,
48
+ unmountOnExit: _propTypes.default.bool
48
49
  };
49
- const allowedProps = exports.allowedProps = ['renderTitle', 'children', 'variant', 'isSelected', 'isDisabled', 'maxHeight', 'minHeight', 'id', 'labelledBy', 'padding', 'textAlign', 'elementRef', 'active'];
50
+ const allowedProps = exports.allowedProps = ['renderTitle', 'children', 'variant', 'isSelected', 'isDisabled', 'maxHeight', 'minHeight', 'id', 'labelledBy', 'padding', 'textAlign', 'elementRef', 'active', 'unmountOnExit'];
@@ -38,9 +38,10 @@ exports.default = void 0;
38
38
  * @param {Object} state the state of the component, the style is applied to
39
39
  * @return {Object} The final style object, which will be used in the component
40
40
  */
41
- const generateStyle = (componentTheme, props) => {
41
+ const generateStyle = (componentTheme, props, state) => {
42
42
  const maxHeight = props.maxHeight,
43
43
  isSelected = props.isSelected;
44
+ const isHidden = state.isHidden;
44
45
  return {
45
46
  panel: {
46
47
  label: 'panel',
@@ -54,6 +55,9 @@ const generateStyle = (componentTheme, props) => {
54
55
  ...(isSelected && {
55
56
  flexGrow: 1,
56
57
  height: '100%'
58
+ }),
59
+ ...(isHidden && {
60
+ display: 'none'
57
61
  })
58
62
  },
59
63
  content: {
package/lib/Tabs/index.js CHANGED
@@ -267,22 +267,26 @@ let Tabs = exports.Tabs = (_dec = (0, _emotion.withStyle)(_styles.default, _them
267
267
  id
268
268
  });
269
269
  }
270
- this.state.withTabListOverflow && this.showActiveTabIfOverlayed(this._tabList.querySelector(`#tab-${id}`));
270
+
271
+ // this is needed because keypress cancels scrolling. So we have to trigger the scrolling
272
+ // one "tick" later than the keypress
273
+ setTimeout(() => {
274
+ this.state.withTabListOverflow && this.showActiveTabIfOverlayed(this._tabList.querySelector(`#tab-${id}`));
275
+ }, 0);
271
276
  }
272
277
  createTab(index, generatedId, selected, panel) {
273
278
  const id = panel.props.id || generatedId;
274
- return /*#__PURE__*/(0, _react.createElement)(_Tab.Tab, {
279
+ return (0, _emotion.jsx)(_Tab.Tab, {
275
280
  variant: this.props.variant,
276
281
  key: `tab-${index}`,
277
282
  id: `tab-${id}`,
278
283
  controls: panel.props.id || `panel-${id}`,
279
- index,
284
+ index: index,
280
285
  isSelected: selected,
281
286
  isDisabled: panel.props.isDisabled,
282
- children: panel.props.renderTitle,
283
287
  onClick: this.handleTabClick,
284
288
  onKeyDown: this.handleTabKeyDown
285
- });
289
+ }, panel.props.renderTitle);
286
290
  }
287
291
  clonePanel(index, generatedId, selected, panel, activePanel) {
288
292
  const id = panel.props.id || generatedId;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instructure/ui-tabs",
3
- "version": "9.0.2-snapshot-11",
3
+ "version": "9.0.2-snapshot-15",
4
4
  "description": "A UI component library made by Instructure Inc.",
5
5
  "author": "Instructure, Inc. Engineering and Product Design",
6
6
  "module": "./es/index.js",
@@ -23,30 +23,30 @@
23
23
  },
24
24
  "license": "MIT",
25
25
  "devDependencies": {
26
- "@instructure/ui-babel-preset": "9.0.2-snapshot-11",
27
- "@instructure/ui-color-utils": "9.0.2-snapshot-11",
28
- "@instructure/ui-test-locator": "9.0.2-snapshot-11",
29
- "@instructure/ui-test-utils": "9.0.2-snapshot-11",
30
- "@instructure/ui-themes": "9.0.2-snapshot-11",
26
+ "@instructure/ui-babel-preset": "9.0.2-snapshot-15",
27
+ "@instructure/ui-color-utils": "9.0.2-snapshot-15",
28
+ "@instructure/ui-test-locator": "9.0.2-snapshot-15",
29
+ "@instructure/ui-test-utils": "9.0.2-snapshot-15",
30
+ "@instructure/ui-themes": "9.0.2-snapshot-15",
31
31
  "@testing-library/jest-dom": "^6.4.5",
32
32
  "@testing-library/react": "^15.0.7"
33
33
  },
34
34
  "dependencies": {
35
35
  "@babel/runtime": "^7.24.5",
36
- "@instructure/console": "9.0.2-snapshot-11",
37
- "@instructure/debounce": "9.0.2-snapshot-11",
38
- "@instructure/emotion": "9.0.2-snapshot-11",
39
- "@instructure/shared-types": "9.0.2-snapshot-11",
40
- "@instructure/ui-dom-utils": "9.0.2-snapshot-11",
41
- "@instructure/ui-focusable": "9.0.2-snapshot-11",
42
- "@instructure/ui-i18n": "9.0.2-snapshot-11",
43
- "@instructure/ui-motion": "9.0.2-snapshot-11",
44
- "@instructure/ui-prop-types": "9.0.2-snapshot-11",
45
- "@instructure/ui-react-utils": "9.0.2-snapshot-11",
46
- "@instructure/ui-testable": "9.0.2-snapshot-11",
47
- "@instructure/ui-utils": "9.0.2-snapshot-11",
48
- "@instructure/ui-view": "9.0.2-snapshot-11",
49
- "@instructure/uid": "9.0.2-snapshot-11",
36
+ "@instructure/console": "9.0.2-snapshot-15",
37
+ "@instructure/debounce": "9.0.2-snapshot-15",
38
+ "@instructure/emotion": "9.0.2-snapshot-15",
39
+ "@instructure/shared-types": "9.0.2-snapshot-15",
40
+ "@instructure/ui-dom-utils": "9.0.2-snapshot-15",
41
+ "@instructure/ui-focusable": "9.0.2-snapshot-15",
42
+ "@instructure/ui-i18n": "9.0.2-snapshot-15",
43
+ "@instructure/ui-motion": "9.0.2-snapshot-15",
44
+ "@instructure/ui-prop-types": "9.0.2-snapshot-15",
45
+ "@instructure/ui-react-utils": "9.0.2-snapshot-15",
46
+ "@instructure/ui-testable": "9.0.2-snapshot-15",
47
+ "@instructure/ui-utils": "9.0.2-snapshot-15",
48
+ "@instructure/ui-view": "9.0.2-snapshot-15",
49
+ "@instructure/uid": "9.0.2-snapshot-15",
50
50
  "keycode": "^2.2.1",
51
51
  "prop-types": "^15.8.1"
52
52
  },
@@ -55,19 +55,25 @@ class Panel extends Component<TabsPanelProps> {
55
55
  variant: 'default',
56
56
  isSelected: false,
57
57
  padding: 'small',
58
- active: false
58
+ active: false,
59
+ unmountOnExit: true
59
60
  }
60
61
 
61
62
  componentDidMount() {
62
- this.props.makeStyles?.()
63
+ this.props.makeStyles?.({ isHidden: this.isHidden })
63
64
  }
64
65
 
65
66
  componentDidUpdate() {
66
- this.props.makeStyles?.()
67
+ this.props.makeStyles?.({ isHidden: this.isHidden })
67
68
  }
68
69
 
69
70
  ref: HTMLDivElement | null = null
70
71
 
72
+ get isHidden() {
73
+ const { isDisabled, isSelected } = this.props
74
+ return !isSelected || !!isDisabled
75
+ }
76
+
71
77
  handleRef = (el: HTMLDivElement | null) => {
72
78
  const { elementRef } = this.props
73
79
 
@@ -93,9 +99,9 @@ class Panel extends Component<TabsPanelProps> {
93
99
  isSelected,
94
100
  styles,
95
101
  active,
102
+ unmountOnExit,
96
103
  ...props
97
104
  } = this.props
98
- const isHidden = !isSelected || !!isDisabled
99
105
 
100
106
  return (
101
107
  <div
@@ -104,13 +110,13 @@ class Panel extends Component<TabsPanelProps> {
104
110
  role="tabpanel"
105
111
  id={id}
106
112
  aria-labelledby={labelledBy}
107
- aria-hidden={isHidden ? 'true' : undefined}
113
+ aria-hidden={this.isHidden ? 'true' : undefined}
108
114
  ref={this.handleRef}
109
115
  >
110
116
  <Transition
111
117
  type="fade"
112
- in={!isHidden}
113
- unmountOnExit
118
+ in={!this.isHidden}
119
+ unmountOnExit={unmountOnExit}
114
120
  transitionExit={false}
115
121
  >
116
122
  <View
@@ -63,6 +63,10 @@ type TabsPanelOwnProps = {
63
63
  * for all the `<Tabs.Panel />`s.
64
64
  */
65
65
  active?: boolean
66
+ /**
67
+ * When set to false, the tabPanel only will be hidden, but not dismounted when not active
68
+ */
69
+ unmountOnExit?: boolean
66
70
  }
67
71
 
68
72
  type PropKeys = keyof TabsPanelOwnProps
@@ -88,7 +92,8 @@ const propTypes: PropValidators<PropKeys> = {
88
92
  padding: ThemeablePropTypes.spacing,
89
93
  textAlign: PropTypes.oneOf(['start', 'center', 'end']),
90
94
  elementRef: PropTypes.func,
91
- active: PropTypes.bool
95
+ active: PropTypes.bool,
96
+ unmountOnExit: PropTypes.bool
92
97
  }
93
98
 
94
99
  const allowedProps: AllowedPropKeys = [
@@ -104,7 +109,8 @@ const allowedProps: AllowedPropKeys = [
104
109
  'padding',
105
110
  'textAlign',
106
111
  'elementRef',
107
- 'active'
112
+ 'active',
113
+ 'unmountOnExit'
108
114
  ]
109
115
 
110
116
  export type { TabsPanelProps, TabsPanelStyle }
@@ -37,9 +37,11 @@ import type { TabsPanelProps, TabsPanelStyle } from './props'
37
37
  */
38
38
  const generateStyle = (
39
39
  componentTheme: TabsPanelTheme,
40
- props: TabsPanelProps
40
+ props: TabsPanelProps,
41
+ state: { isHidden: boolean }
41
42
  ): TabsPanelStyle => {
42
43
  const { maxHeight, isSelected } = props
44
+ const { isHidden } = state
43
45
 
44
46
  return {
45
47
  panel: {
@@ -54,6 +56,9 @@ const generateStyle = (
54
56
  ...(isSelected && {
55
57
  flexGrow: 1,
56
58
  height: '100%'
59
+ }),
60
+ ...(isHidden && {
61
+ display: 'none'
57
62
  })
58
63
  },
59
64
  content: {
@@ -322,6 +322,79 @@ class Example extends React.Component {
322
322
  render(<Example />)
323
323
  ```
324
324
 
325
+ ### Persisting the selected tab
326
+
327
+ If you need to persist the rendered content of the tabpanels between tabbing, you can set the `unmountOnExit` prop to `false` on the `<Tabs.Panel>` component. It works case by case, so you can set it to `false` only on the tabpanels you want to persist.
328
+
329
+ ```js
330
+ ---
331
+ type: example
332
+ ---
333
+ class Counter extends React.Component{
334
+ state = {
335
+ counter: 0
336
+ }
337
+ handleIncrement = () => {
338
+ this.setState({
339
+ counter: this.state.counter + 1
340
+ })
341
+ }
342
+
343
+ render () {
344
+ return (
345
+ <div>
346
+ <Button onClick={this.handleIncrement}>Increment</Button>
347
+ <hr/>
348
+ <Text>{this.state.counter}</Text>
349
+ </div>
350
+ )
351
+ }
352
+ }
353
+ class Example extends React.Component {
354
+ state = {
355
+ selectedIndex: 0
356
+ }
357
+ handleTabChange = (event, { index, id }) => {
358
+ this.setState({
359
+ selectedIndex: index
360
+ })
361
+ }
362
+ render () {
363
+ const { selectedIndex } = this.state
364
+ return (
365
+ <Tabs
366
+ margin="large auto"
367
+ padding="medium"
368
+ onRequestTabChange={this.handleTabChange}
369
+ >
370
+ <Tabs.Panel
371
+ id="tabA"
372
+ renderTitle="I will persist"
373
+ textAlign="center"
374
+ padding="large"
375
+ isSelected={selectedIndex === 0}
376
+ unmountOnExit={false}
377
+ >
378
+ <Counter/>
379
+ </Tabs.Panel>
380
+ <Tabs.Panel id="tabB" renderTitle="I will unmount" isSelected={selectedIndex === 1} textAlign="center"
381
+ padding="large">
382
+ <Counter/>
383
+ </Tabs.Panel>
384
+ <Tabs.Panel id="tabC" renderTitle="Tab C" isSelected={selectedIndex === 2}>
385
+ Tab C
386
+ </Tabs.Panel>
387
+ <Tabs.Panel id="tabD" renderTitle="Tab D" isSelected={selectedIndex === 3}>
388
+ Tab D
389
+ </Tabs.Panel>
390
+ </Tabs>
391
+ )
392
+ }
393
+ }
394
+
395
+ render(<Example />)
396
+ ```
397
+
325
398
  ### Guidelines
326
399
 
327
400
  ```js
@@ -23,13 +23,7 @@
23
23
  */
24
24
 
25
25
  /** @jsx jsx */
26
- import React, {
27
- Component,
28
- ComponentClass,
29
- ComponentElement,
30
- createElement,
31
- ReactElement
32
- } from 'react'
26
+ import React, { Component, ComponentElement, ReactElement } from 'react'
33
27
 
34
28
  import keycode from 'keycode'
35
29
 
@@ -329,8 +323,14 @@ class Tabs extends Component<TabsProps, TabsState> {
329
323
  this.props.onRequestTabChange(event, { index, id })
330
324
  }
331
325
 
332
- this.state.withTabListOverflow &&
333
- this.showActiveTabIfOverlayed(this._tabList!.querySelector(`#tab-${id}`))
326
+ // this is needed because keypress cancels scrolling. So we have to trigger the scrolling
327
+ // one "tick" later than the keypress
328
+ setTimeout(() => {
329
+ this.state.withTabListOverflow &&
330
+ this.showActiveTabIfOverlayed(
331
+ this._tabList!.querySelector(`#tab-${id}`)
332
+ )
333
+ }, 0)
334
334
  }
335
335
 
336
336
  createTab(
@@ -341,18 +341,21 @@ class Tabs extends Component<TabsProps, TabsState> {
341
341
  ): TabChild {
342
342
  const id = panel.props.id || generatedId
343
343
 
344
- return createElement(Tab as ComponentClass<TabsTabProps>, {
345
- variant: this.props.variant,
346
- key: `tab-${index}`,
347
- id: `tab-${id}`,
348
- controls: panel.props.id || `panel-${id}`,
349
- index,
350
- isSelected: selected,
351
- isDisabled: panel.props.isDisabled,
352
- children: panel.props.renderTitle,
353
- onClick: this.handleTabClick,
354
- onKeyDown: this.handleTabKeyDown
355
- })
344
+ return (
345
+ <Tab
346
+ variant={this.props.variant}
347
+ key={`tab-${index}`}
348
+ id={`tab-${id}`}
349
+ controls={panel.props.id || `panel-${id}`}
350
+ index={index}
351
+ isSelected={selected}
352
+ isDisabled={panel.props.isDisabled}
353
+ onClick={this.handleTabClick}
354
+ onKeyDown={this.handleTabKeyDown}
355
+ >
356
+ {panel.props.renderTitle}
357
+ </Tab>
358
+ )
356
359
  }
357
360
 
358
361
  clonePanel(