@patternfly/react-core 4.212.0 → 4.213.0
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 +11 -0
- package/dist/esm/components/Tabs/Tab.d.ts +5 -1
- package/dist/esm/components/Tabs/Tab.d.ts.map +1 -1
- package/dist/esm/components/Tabs/Tab.js +10 -3
- package/dist/esm/components/Tabs/Tab.js.map +1 -1
- package/dist/esm/components/Tabs/Tabs.d.ts +6 -0
- package/dist/esm/components/Tabs/Tabs.d.ts.map +1 -1
- package/dist/esm/components/Tabs/Tabs.js +14 -4
- package/dist/esm/components/Tabs/Tabs.js.map +1 -1
- package/dist/esm/components/Tabs/TabsContext.d.ts +1 -0
- package/dist/esm/components/Tabs/TabsContext.d.ts.map +1 -1
- package/dist/esm/components/Tabs/TabsContext.js +2 -1
- package/dist/esm/components/Tabs/TabsContext.js.map +1 -1
- package/dist/js/components/Tabs/Tab.d.ts +5 -1
- package/dist/js/components/Tabs/Tab.d.ts.map +1 -1
- package/dist/js/components/Tabs/Tab.js +10 -3
- package/dist/js/components/Tabs/Tab.js.map +1 -1
- package/dist/js/components/Tabs/Tabs.d.ts +6 -0
- package/dist/js/components/Tabs/Tabs.d.ts.map +1 -1
- package/dist/js/components/Tabs/Tabs.js +14 -4
- package/dist/js/components/Tabs/Tabs.js.map +1 -1
- package/dist/js/components/Tabs/TabsContext.d.ts +1 -0
- package/dist/js/components/Tabs/TabsContext.d.ts.map +1 -1
- package/dist/js/components/Tabs/TabsContext.js +2 -1
- package/dist/js/components/Tabs/TabsContext.js.map +1 -1
- package/dist/umd/react-core.js +36 -21
- package/dist/umd/react-core.min.js +1 -1
- package/package.json +5 -5
- package/src/components/Tabs/Tab.tsx +32 -2
- package/src/components/Tabs/Tabs.tsx +28 -2
- package/src/components/Tabs/TabsContext.ts +7 -1
- package/src/components/Tabs/__tests__/Tabs.test.tsx +22 -0
- package/src/components/Tabs/__tests__/__snapshots__/Tabs.test.tsx.snap +13 -13
- package/src/components/Tabs/examples/Tabs.md +7 -0
- package/src/components/Tabs/examples/TabsDynamic.tsx +64 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@patternfly/react-core",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.213.0",
|
|
4
4
|
"description": "This library provides a set of common React components for use with the PatternFly reference implementation.",
|
|
5
5
|
"main": "dist/js/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -34,9 +34,9 @@
|
|
|
34
34
|
"generate": "node scripts/copyStyles.js"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@patternfly/react-icons": "^4.
|
|
38
|
-
"@patternfly/react-styles": "^4.
|
|
39
|
-
"@patternfly/react-tokens": "^4.
|
|
37
|
+
"@patternfly/react-icons": "^4.64.0",
|
|
38
|
+
"@patternfly/react-styles": "^4.63.0",
|
|
39
|
+
"@patternfly/react-tokens": "^4.65.0",
|
|
40
40
|
"focus-trap": "6.2.2",
|
|
41
41
|
"react-dropzone": "9.0.0",
|
|
42
42
|
"tippy.js": "5.1.2",
|
|
@@ -60,5 +60,5 @@
|
|
|
60
60
|
"react": "^16.8.0 || ^17.0.0",
|
|
61
61
|
"react-dom": "^16.8.0 || ^17.0.0"
|
|
62
62
|
},
|
|
63
|
-
"gitHead": "
|
|
63
|
+
"gitHead": "a950a289d4471b439e256fb19d19480b1bb59d71"
|
|
64
64
|
}
|
|
@@ -5,6 +5,8 @@ import { TabButton } from './TabButton';
|
|
|
5
5
|
import { TabsContext } from './TabsContext';
|
|
6
6
|
import { css } from '@patternfly/react-styles';
|
|
7
7
|
import { Tooltip } from '../Tooltip';
|
|
8
|
+
import { Button } from '../Button';
|
|
9
|
+
import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon';
|
|
8
10
|
|
|
9
11
|
export interface TabProps extends Omit<React.HTMLProps<HTMLAnchorElement | HTMLButtonElement>, 'title'>, OUIAProps {
|
|
10
12
|
/** content rendered inside the Tab content area. */
|
|
@@ -33,6 +35,10 @@ export interface TabProps extends Omit<React.HTMLProps<HTMLAnchorElement | HTMLB
|
|
|
33
35
|
innerRef?: React.Ref<any>;
|
|
34
36
|
/** Optional Tooltip rendered to a Tab. Should be <Tooltip> with appropriate props for proper rendering. */
|
|
35
37
|
tooltip?: React.ReactElement<any>;
|
|
38
|
+
/** @beta Aria-label for the close button added by passing the onClose property to Tabs. */
|
|
39
|
+
closeButtonAriaLabel?: string;
|
|
40
|
+
/** @beta Flag indicating the close button should be disabled */
|
|
41
|
+
isCloseDisabled?: boolean;
|
|
36
42
|
}
|
|
37
43
|
|
|
38
44
|
const TabBase: React.FunctionComponent<TabProps> = ({
|
|
@@ -49,6 +55,8 @@ const TabBase: React.FunctionComponent<TabProps> = ({
|
|
|
49
55
|
href,
|
|
50
56
|
innerRef,
|
|
51
57
|
tooltip,
|
|
58
|
+
closeButtonAriaLabel,
|
|
59
|
+
isCloseDisabled = false,
|
|
52
60
|
...props
|
|
53
61
|
}: TabProps) => {
|
|
54
62
|
const preventedEvents = inoperableEvents.reduce(
|
|
@@ -60,7 +68,9 @@ const TabBase: React.FunctionComponent<TabProps> = ({
|
|
|
60
68
|
}),
|
|
61
69
|
{}
|
|
62
70
|
);
|
|
63
|
-
const { mountOnEnter, localActiveKey, unmountOnExit, uniqueId, handleTabClick } = React.useContext(
|
|
71
|
+
const { mountOnEnter, localActiveKey, unmountOnExit, uniqueId, handleTabClick, handleTabClose } = React.useContext(
|
|
72
|
+
TabsContext
|
|
73
|
+
);
|
|
64
74
|
let ariaControls = tabContentId ? `${tabContentId}` : `pf-tab-section-${eventKey}-${childId || uniqueId}`;
|
|
65
75
|
if ((mountOnEnter || unmountOnExit) && eventKey !== localActiveKey) {
|
|
66
76
|
ariaControls = undefined;
|
|
@@ -102,10 +112,30 @@ const TabBase: React.FunctionComponent<TabProps> = ({
|
|
|
102
112
|
|
|
103
113
|
return (
|
|
104
114
|
<li
|
|
105
|
-
className={css(
|
|
115
|
+
className={css(
|
|
116
|
+
styles.tabsItem,
|
|
117
|
+
eventKey === localActiveKey && styles.modifiers.current,
|
|
118
|
+
handleTabClose && styles.modifiers.action,
|
|
119
|
+
handleTabClose && (isDisabled || isAriaDisabled) && styles.modifiers.disabled,
|
|
120
|
+
childClassName
|
|
121
|
+
)}
|
|
106
122
|
role="presentation"
|
|
107
123
|
>
|
|
108
124
|
{tooltip ? <Tooltip {...tooltip.props}>{tabButton}</Tooltip> : tabButton}
|
|
125
|
+
{handleTabClose !== undefined && (
|
|
126
|
+
<span className={css(styles.tabsItemClose)}>
|
|
127
|
+
<Button
|
|
128
|
+
variant="plain"
|
|
129
|
+
aria-label={closeButtonAriaLabel || 'Close tab'}
|
|
130
|
+
onClick={(event: any) => handleTabClose(event, eventKey, tabContentRef)}
|
|
131
|
+
isDisabled={isCloseDisabled}
|
|
132
|
+
>
|
|
133
|
+
<span className={css(styles.tabsItemCloseIcon)}>
|
|
134
|
+
<TimesIcon />
|
|
135
|
+
</span>
|
|
136
|
+
</Button>
|
|
137
|
+
</span>
|
|
138
|
+
)}
|
|
109
139
|
</li>
|
|
110
140
|
);
|
|
111
141
|
};
|
|
@@ -5,6 +5,7 @@ import { css } from '@patternfly/react-styles';
|
|
|
5
5
|
import { PickOptional } from '../../helpers/typeUtils';
|
|
6
6
|
import AngleLeftIcon from '@patternfly/react-icons/dist/esm/icons/angle-left-icon';
|
|
7
7
|
import AngleRightIcon from '@patternfly/react-icons/dist/esm/icons/angle-right-icon';
|
|
8
|
+
import PlusIcon from '@patternfly/react-icons/dist/esm/icons/plus-icon';
|
|
8
9
|
import { getUniqueId, isElementInView, formatBreakpointMods } from '../../helpers/util';
|
|
9
10
|
import { TabContent } from './TabContent';
|
|
10
11
|
import { TabProps } from './Tab';
|
|
@@ -31,6 +32,12 @@ export interface TabsProps extends Omit<React.HTMLProps<HTMLElement | HTMLDivEle
|
|
|
31
32
|
defaultActiveKey?: number | string;
|
|
32
33
|
/** Callback to handle tab selection */
|
|
33
34
|
onSelect?: (event: React.MouseEvent<HTMLElement, MouseEvent>, eventKey: number | string) => void;
|
|
35
|
+
/** @beta Callback to handle tab closing */
|
|
36
|
+
onClose?: (event: React.MouseEvent<HTMLElement, MouseEvent>, eventKey: number | string) => void;
|
|
37
|
+
/** @beta Callback for the add button. Passing this property inserts the add button */
|
|
38
|
+
onAdd?: () => void;
|
|
39
|
+
/** @beta Aria-label for the add button */
|
|
40
|
+
addButtonAriaLabel?: string;
|
|
34
41
|
/** Uniquely identifies the tabs */
|
|
35
42
|
id?: string;
|
|
36
43
|
/** Enables the filled tab list layout */
|
|
@@ -273,13 +280,21 @@ export class Tabs extends React.Component<TabsProps, TabsState> {
|
|
|
273
280
|
}
|
|
274
281
|
|
|
275
282
|
componentDidUpdate(prevProps: TabsProps) {
|
|
276
|
-
const { activeKey, mountOnEnter } = this.props;
|
|
283
|
+
const { activeKey, mountOnEnter, children } = this.props;
|
|
277
284
|
const { shownKeys } = this.state;
|
|
278
285
|
if (prevProps.activeKey !== activeKey && mountOnEnter && shownKeys.indexOf(activeKey) < 0) {
|
|
279
286
|
this.setState({
|
|
280
287
|
shownKeys: shownKeys.concat(activeKey)
|
|
281
288
|
});
|
|
282
289
|
}
|
|
290
|
+
|
|
291
|
+
if (
|
|
292
|
+
prevProps.children &&
|
|
293
|
+
children &&
|
|
294
|
+
React.Children.toArray(prevProps.children).length !== React.Children.toArray(children).length
|
|
295
|
+
) {
|
|
296
|
+
this.handleScrollButtons();
|
|
297
|
+
}
|
|
283
298
|
}
|
|
284
299
|
|
|
285
300
|
render() {
|
|
@@ -311,7 +326,10 @@ export class Tabs extends React.Component<TabsProps, TabsState> {
|
|
|
311
326
|
defaultIsExpanded,
|
|
312
327
|
toggleText,
|
|
313
328
|
toggleAriaLabel,
|
|
329
|
+
addButtonAriaLabel,
|
|
314
330
|
onToggle,
|
|
331
|
+
onClose,
|
|
332
|
+
onAdd,
|
|
315
333
|
...props
|
|
316
334
|
} = this.props;
|
|
317
335
|
const {
|
|
@@ -348,7 +366,8 @@ export class Tabs extends React.Component<TabsProps, TabsState> {
|
|
|
348
366
|
unmountOnExit,
|
|
349
367
|
localActiveKey,
|
|
350
368
|
uniqueId,
|
|
351
|
-
handleTabClick: (...args) => this.handleTabClick(...args)
|
|
369
|
+
handleTabClick: (...args) => this.handleTabClick(...args),
|
|
370
|
+
handleTabClose: onClose
|
|
352
371
|
}}
|
|
353
372
|
>
|
|
354
373
|
<Component
|
|
@@ -421,6 +440,13 @@ export class Tabs extends React.Component<TabsProps, TabsState> {
|
|
|
421
440
|
>
|
|
422
441
|
<AngleRightIcon />
|
|
423
442
|
</button>
|
|
443
|
+
{onAdd !== undefined && (
|
|
444
|
+
<span className={css(styles.tabsAdd)}>
|
|
445
|
+
<Button variant="plain" aria-label={addButtonAriaLabel || 'Add tab'} onClick={onAdd}>
|
|
446
|
+
<PlusIcon />
|
|
447
|
+
</Button>
|
|
448
|
+
</span>
|
|
449
|
+
)}
|
|
424
450
|
</Component>
|
|
425
451
|
{filteredChildren
|
|
426
452
|
.filter(
|
|
@@ -11,6 +11,11 @@ export interface TabsContextProps {
|
|
|
11
11
|
eventKey: number | string,
|
|
12
12
|
tabContentRef: React.RefObject<any>
|
|
13
13
|
) => void;
|
|
14
|
+
handleTabClose: (
|
|
15
|
+
event: React.MouseEvent<HTMLElement, MouseEvent>,
|
|
16
|
+
eventKey: number | string,
|
|
17
|
+
tabContentRef?: React.RefObject<any>
|
|
18
|
+
) => void;
|
|
14
19
|
}
|
|
15
20
|
|
|
16
21
|
export const TabsContext = React.createContext<TabsContextProps>({
|
|
@@ -19,7 +24,8 @@ export const TabsContext = React.createContext<TabsContextProps>({
|
|
|
19
24
|
unmountOnExit: false,
|
|
20
25
|
localActiveKey: '',
|
|
21
26
|
uniqueId: '',
|
|
22
|
-
handleTabClick: () => null
|
|
27
|
+
handleTabClick: () => null,
|
|
28
|
+
handleTabClose: undefined
|
|
23
29
|
});
|
|
24
30
|
|
|
25
31
|
export const TabsContextProvider = TabsContext.Provider;
|
|
@@ -38,6 +38,28 @@ test('should render simple tabs', () => {
|
|
|
38
38
|
expect(asFragment()).toMatchSnapshot();
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
+
test('should render closeable tabs', () => {
|
|
42
|
+
const view = render(
|
|
43
|
+
<Tabs onClose={jest.fn()}>
|
|
44
|
+
<Tab eventKey={0} title={<TabTitleText>"Tab item 1"</TabTitleText>} closeButtonAriaLabel="close-label">
|
|
45
|
+
Tab 1 section
|
|
46
|
+
</Tab>
|
|
47
|
+
</Tabs>
|
|
48
|
+
);
|
|
49
|
+
expect(screen.getByLabelText('close-label')).toBeTruthy();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('should render add button', () => {
|
|
53
|
+
const view = render(
|
|
54
|
+
<Tabs onAdd={jest.fn()} addButtonAriaLabel="add-label">
|
|
55
|
+
<Tab eventKey={0} title={<TabTitleText>"Tab item 1"</TabTitleText>} closeButtonAriaLabel="close-label">
|
|
56
|
+
Tab 1 section
|
|
57
|
+
</Tab>
|
|
58
|
+
</Tabs>
|
|
59
|
+
);
|
|
60
|
+
expect(screen.getByLabelText('add-label')).toBeTruthy();
|
|
61
|
+
});
|
|
62
|
+
|
|
41
63
|
test('should render uncontrolled tabs', () => {
|
|
42
64
|
const { asFragment } = render(
|
|
43
65
|
<Tabs defaultActiveKey={0}>
|
|
@@ -5,7 +5,7 @@ exports[`should render accessible tabs 1`] = `
|
|
|
5
5
|
<nav
|
|
6
6
|
aria-label="accessible Tabs example"
|
|
7
7
|
class="pf-c-tabs"
|
|
8
|
-
data-ouia-component-id="OUIA-Generated-Tabs-
|
|
8
|
+
data-ouia-component-id="OUIA-Generated-Tabs-9"
|
|
9
9
|
data-ouia-component-type="PF4/Tabs"
|
|
10
10
|
data-ouia-safe="true"
|
|
11
11
|
id="accessibleTabs"
|
|
@@ -161,7 +161,7 @@ exports[`should render box tabs 1`] = `
|
|
|
161
161
|
<DocumentFragment>
|
|
162
162
|
<div
|
|
163
163
|
class="pf-c-tabs pf-m-box"
|
|
164
|
-
data-ouia-component-id="OUIA-Generated-Tabs-
|
|
164
|
+
data-ouia-component-id="OUIA-Generated-Tabs-8"
|
|
165
165
|
data-ouia-component-type="PF4/Tabs"
|
|
166
166
|
data-ouia-safe="true"
|
|
167
167
|
id="boxTabs"
|
|
@@ -355,7 +355,7 @@ exports[`should render box tabs of light variant 1`] = `
|
|
|
355
355
|
<DocumentFragment>
|
|
356
356
|
<div
|
|
357
357
|
class="pf-c-tabs pf-m-box pf-m-color-scheme--light-300"
|
|
358
|
-
data-ouia-component-id="OUIA-Generated-Tabs-
|
|
358
|
+
data-ouia-component-id="OUIA-Generated-Tabs-15"
|
|
359
359
|
data-ouia-component-type="PF4/Tabs"
|
|
360
360
|
data-ouia-safe="true"
|
|
361
361
|
id="boxLightVariantTabs"
|
|
@@ -508,7 +508,7 @@ exports[`should render expandable vertical tabs 1`] = `
|
|
|
508
508
|
<DocumentFragment>
|
|
509
509
|
<div
|
|
510
510
|
class="pf-c-tabs pf-m-vertical pf-m-expandable"
|
|
511
|
-
data-ouia-component-id="OUIA-Generated-Tabs-
|
|
511
|
+
data-ouia-component-id="OUIA-Generated-Tabs-6"
|
|
512
512
|
data-ouia-component-type="PF4/Tabs"
|
|
513
513
|
data-ouia-safe="true"
|
|
514
514
|
id="verticalTabs"
|
|
@@ -523,7 +523,7 @@ exports[`should render expandable vertical tabs 1`] = `
|
|
|
523
523
|
aria-disabled="false"
|
|
524
524
|
aria-labelledby="pf-random-id-0-text pf-random-id-0-button"
|
|
525
525
|
class="pf-c-button pf-m-plain"
|
|
526
|
-
data-ouia-component-id="OUIA-Generated-Button-plain-
|
|
526
|
+
data-ouia-component-id="OUIA-Generated-Button-plain-3"
|
|
527
527
|
data-ouia-component-type="PF4/Button"
|
|
528
528
|
data-ouia-safe="true"
|
|
529
529
|
id="pf-random-id-0-button"
|
|
@@ -745,7 +745,7 @@ exports[`should render filled tabs 1`] = `
|
|
|
745
745
|
<DocumentFragment>
|
|
746
746
|
<div
|
|
747
747
|
class="pf-c-tabs pf-m-fill"
|
|
748
|
-
data-ouia-component-id="OUIA-Generated-Tabs-
|
|
748
|
+
data-ouia-component-id="OUIA-Generated-Tabs-10"
|
|
749
749
|
data-ouia-component-type="PF4/Tabs"
|
|
750
750
|
data-ouia-safe="true"
|
|
751
751
|
id="filledTabs"
|
|
@@ -898,7 +898,7 @@ exports[`should render secondary tabs 1`] = `
|
|
|
898
898
|
<DocumentFragment>
|
|
899
899
|
<div
|
|
900
900
|
class="pf-c-tabs"
|
|
901
|
-
data-ouia-component-id="OUIA-Generated-Tabs-
|
|
901
|
+
data-ouia-component-id="OUIA-Generated-Tabs-11"
|
|
902
902
|
data-ouia-component-type="PF4/Tabs"
|
|
903
903
|
data-ouia-safe="true"
|
|
904
904
|
id="primarieTabs"
|
|
@@ -1020,7 +1020,7 @@ exports[`should render secondary tabs 1`] = `
|
|
|
1020
1020
|
>
|
|
1021
1021
|
<div
|
|
1022
1022
|
class="pf-c-tabs pf-m-secondary"
|
|
1023
|
-
data-ouia-component-id="OUIA-Generated-Tabs-
|
|
1023
|
+
data-ouia-component-id="OUIA-Generated-Tabs-12"
|
|
1024
1024
|
data-ouia-component-type="PF4/Tabs"
|
|
1025
1025
|
data-ouia-safe="true"
|
|
1026
1026
|
id="secondaryTabs"
|
|
@@ -1393,7 +1393,7 @@ exports[`should render tabs with eventKey Strings 1`] = `
|
|
|
1393
1393
|
<DocumentFragment>
|
|
1394
1394
|
<div
|
|
1395
1395
|
class="pf-c-tabs"
|
|
1396
|
-
data-ouia-component-id="OUIA-Generated-Tabs-
|
|
1396
|
+
data-ouia-component-id="OUIA-Generated-Tabs-13"
|
|
1397
1397
|
data-ouia-component-type="PF4/Tabs"
|
|
1398
1398
|
data-ouia-safe="true"
|
|
1399
1399
|
id="eventKeyTabs"
|
|
@@ -1547,7 +1547,7 @@ exports[`should render tabs with no bottom border 1`] = `
|
|
|
1547
1547
|
<DocumentFragment>
|
|
1548
1548
|
<div
|
|
1549
1549
|
class="pf-c-tabs pf-m-no-border-bottom"
|
|
1550
|
-
data-ouia-component-id="OUIA-Generated-Tabs-
|
|
1550
|
+
data-ouia-component-id="OUIA-Generated-Tabs-16"
|
|
1551
1551
|
data-ouia-component-type="PF4/Tabs"
|
|
1552
1552
|
data-ouia-safe="true"
|
|
1553
1553
|
id="noBottomBorderTabs"
|
|
@@ -1700,7 +1700,7 @@ exports[`should render tabs with separate content 1`] = `
|
|
|
1700
1700
|
<DocumentFragment>
|
|
1701
1701
|
<div
|
|
1702
1702
|
class="pf-c-tabs"
|
|
1703
|
-
data-ouia-component-id="OUIA-Generated-Tabs-
|
|
1703
|
+
data-ouia-component-id="OUIA-Generated-Tabs-14"
|
|
1704
1704
|
data-ouia-component-type="PF4/Tabs"
|
|
1705
1705
|
data-ouia-safe="true"
|
|
1706
1706
|
id="separateTabs"
|
|
@@ -1863,7 +1863,7 @@ exports[`should render uncontrolled tabs 1`] = `
|
|
|
1863
1863
|
<DocumentFragment>
|
|
1864
1864
|
<div
|
|
1865
1865
|
class="pf-c-tabs"
|
|
1866
|
-
data-ouia-component-id="OUIA-Generated-Tabs-
|
|
1866
|
+
data-ouia-component-id="OUIA-Generated-Tabs-4"
|
|
1867
1867
|
data-ouia-component-type="PF4/Tabs"
|
|
1868
1868
|
data-ouia-safe="true"
|
|
1869
1869
|
>
|
|
@@ -2056,7 +2056,7 @@ exports[`should render vertical tabs 1`] = `
|
|
|
2056
2056
|
<DocumentFragment>
|
|
2057
2057
|
<div
|
|
2058
2058
|
class="pf-c-tabs pf-m-vertical"
|
|
2059
|
-
data-ouia-component-id="OUIA-Generated-Tabs-
|
|
2059
|
+
data-ouia-component-id="OUIA-Generated-Tabs-5"
|
|
2060
2060
|
data-ouia-component-type="PF4/Tabs"
|
|
2061
2061
|
data-ouia-safe="true"
|
|
2062
2062
|
id="verticalTabs"
|
|
@@ -1617,3 +1617,10 @@ class ToggledSeparateContent extends React.Component {
|
|
|
1617
1617
|
}
|
|
1618
1618
|
}
|
|
1619
1619
|
```
|
|
1620
|
+
|
|
1621
|
+
### Dynamic
|
|
1622
|
+
|
|
1623
|
+
To enable closeable tabs, pass the `onClose` property to `Tabs`, and to enable the add button, pass the `onAdd` property to `Tabs`. Aria labels may be controlled manually by passing `closeButtonAriaLabel` to `Tab` and `addButtonAriaLabel` to `Tabs`.
|
|
1624
|
+
|
|
1625
|
+
```ts file="./TabsDynamic.tsx" isBeta
|
|
1626
|
+
```
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Tabs, Tab, TabTitleText } from '@patternfly/react-core';
|
|
3
|
+
|
|
4
|
+
export const TabsDynamic: React.FunctionComponent = () => {
|
|
5
|
+
const [activeTabKey, setActiveTabKey] = React.useState<number>(0);
|
|
6
|
+
const [tabs, setTabs] = React.useState<string[]>(['Terminal 1', 'Terminal 2', 'Terminal 3']);
|
|
7
|
+
const [newTabNumber, setNewTabNumber] = React.useState<number>(4);
|
|
8
|
+
const tabComponentRef = React.useRef<any>();
|
|
9
|
+
const firstMount = React.useRef(true);
|
|
10
|
+
|
|
11
|
+
const onClose = (event: any, tabIndex: string | number) => {
|
|
12
|
+
const tabIndexNum = tabIndex as number;
|
|
13
|
+
let nextTabIndex = activeTabKey;
|
|
14
|
+
if (tabIndexNum < activeTabKey) {
|
|
15
|
+
// if a preceding tab is closing, keep focus on the new index of the current tab
|
|
16
|
+
nextTabIndex = activeTabKey - 1 > 0 ? activeTabKey - 1 : 0;
|
|
17
|
+
} else if (activeTabKey === tabs.length - 1) {
|
|
18
|
+
// if the closing tab is the last tab, focus the preceding tab
|
|
19
|
+
nextTabIndex = tabs.length - 2 > 0 ? tabs.length - 2 : 0;
|
|
20
|
+
}
|
|
21
|
+
setActiveTabKey(nextTabIndex);
|
|
22
|
+
setTabs(tabs.filter((tab, index) => index !== tabIndex));
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const onAdd = () => {
|
|
26
|
+
setTabs([...tabs, `Terminal ${newTabNumber}`]);
|
|
27
|
+
setActiveTabKey(tabs.length);
|
|
28
|
+
setNewTabNumber(newTabNumber + 1);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
React.useEffect(() => {
|
|
32
|
+
if (firstMount.current) {
|
|
33
|
+
firstMount.current = false;
|
|
34
|
+
return;
|
|
35
|
+
} else {
|
|
36
|
+
const first = tabComponentRef.current.tabList.current.childNodes[activeTabKey];
|
|
37
|
+
first && first.firstChild.focus();
|
|
38
|
+
}
|
|
39
|
+
}, [tabs]);
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<Tabs
|
|
43
|
+
activeKey={activeTabKey}
|
|
44
|
+
onSelect={(event: any, tabIndex: string | number) => setActiveTabKey(tabIndex as number)}
|
|
45
|
+
onClose={onClose}
|
|
46
|
+
onAdd={onAdd}
|
|
47
|
+
aria-label="Tabs in the addable/closeable example"
|
|
48
|
+
addButtonAriaLabel="Add new tab"
|
|
49
|
+
ref={tabComponentRef}
|
|
50
|
+
>
|
|
51
|
+
{tabs.map((tab, index) => (
|
|
52
|
+
<Tab
|
|
53
|
+
key={index}
|
|
54
|
+
eventKey={index}
|
|
55
|
+
title={<TabTitleText>{tab}</TabTitleText>}
|
|
56
|
+
closeButtonAriaLabel={`Close ${tab}`}
|
|
57
|
+
isCloseDisabled={tabs.length === 1}
|
|
58
|
+
>
|
|
59
|
+
{tab}
|
|
60
|
+
</Tab>
|
|
61
|
+
))}
|
|
62
|
+
</Tabs>
|
|
63
|
+
);
|
|
64
|
+
};
|