diff --git a/packages/react-core/src/components/Tabs/Tab.tsx b/packages/react-core/src/components/Tabs/Tab.tsx index 686eb28f6ad..bf0cf777c5b 100644 --- a/packages/react-core/src/components/Tabs/Tab.tsx +++ b/packages/react-core/src/components/Tabs/Tab.tsx @@ -4,6 +4,7 @@ import { OUIAProps } from '../../helpers'; import { TabButton } from './TabButton'; import { TabsContext } from './TabsContext'; import { css } from '@patternfly/react-styles'; +import { Tooltip } from '../Tooltip'; export interface TabProps extends Omit, 'title'>, OUIAProps { /** content rendered inside the Tab content area. */ @@ -30,6 +31,8 @@ export interface TabProps extends Omit; + /** Optional Tooltip rendered to a Tab. Should be with appropriate props for proper rendering. */ + tooltip?: React.ReactElement; } const TabBase: React.FunctionComponent = ({ @@ -45,6 +48,7 @@ const TabBase: React.FunctionComponent = ({ inoperableEvents = ['onClick', 'onKeyPress'], href, innerRef, + tooltip, ...props }: TabProps) => { const preventedEvents = inoperableEvents.reduce( @@ -69,31 +73,34 @@ const TabBase: React.FunctionComponent = ({ return null; } }; - return ( -
  • handleTabClick(event, eventKey, tabContentRef)} + {...(isAriaDisabled ? preventedEvents : null)} + id={`pf-tab-${eventKey}-${childId || uniqueId}`} + aria-controls={ariaControls} + tabContentRef={tabContentRef} + ouiaId={childOuiaId} + href={href} + {...props} > - handleTabClick(event, eventKey, tabContentRef)} - {...(isAriaDisabled ? preventedEvents : null)} - id={`pf-tab-${eventKey}-${childId || uniqueId}`} - aria-controls={ariaControls} - tabContentRef={tabContentRef} - ouiaId={childOuiaId} - href={href} - {...props} - > - {title} - + {title} + + ); + + return ( +
  • + {tooltip ? {tabButton} : tabButton}
  • ); }; diff --git a/packages/react-core/src/components/Tabs/TabButton.tsx b/packages/react-core/src/components/Tabs/TabButton.tsx index 11338d09298..a5f5d74d1d9 100644 --- a/packages/react-core/src/components/Tabs/TabButton.tsx +++ b/packages/react-core/src/components/Tabs/TabButton.tsx @@ -10,6 +10,8 @@ export interface TabButtonProps extends Omit; + /** Parents' innerRef passed down for properly displaying Tooltips */ + parentInnerRef?: React.Ref; } export const TabButton: React.FunctionComponent = ({ @@ -17,12 +19,14 @@ export const TabButton: React.FunctionComponent = ({ // eslint-disable-next-line @typescript-eslint/no-unused-vars tabContentRef, ouiaId, + parentInnerRef, ouiaSafe, ...props }: TabButtonProps) => { const Component = (props.href ? 'a' : 'button') as any; + return ( - + {children} ); diff --git a/packages/react-core/src/components/Tabs/examples/Tabs.md b/packages/react-core/src/components/Tabs/examples/Tabs.md index 29cb024e031..f72694f9d17 100644 --- a/packages/react-core/src/components/Tabs/examples/Tabs.md +++ b/packages/react-core/src/components/Tabs/examples/Tabs.md @@ -2,9 +2,10 @@ id: Tabs section: components cssPrefix: pf-c-tabs -propComponents: ['Tabs', 'TabProps', 'TabContent', 'TabTitleText', 'TabTitleIcon' ] +propComponents: ['Tabs', 'TabProps', 'TabContent', 'TabTitleText', 'TabTitleIcon'] ouia: true --- + import UsersIcon from '@patternfly/react-icons/dist/esm/icons/users-icon'; import BoxIcon from '@patternfly/react-icons/dist/esm/icons/box-icon'; import DatabaseIcon from '@patternfly/react-icons/dist/esm/icons/database-icon'; @@ -19,6 +20,87 @@ Similarly the 'Tab content light variation' checkbox affects the TabContent back ## Examples ### Default + +When passing in a Tooltip component to the Tab component, the Tooltip can also be passed in directly to the `tooltip` prop. + +```js +import React from 'react'; +import { Tabs, Tab, TabTitleText, Checkbox, Tooltip } from '@patternfly/react-core'; + +class SimpleTabs extends React.Component { + constructor(props) { + super(props); + this.state = { + activeTabKey: 0, + isBox: false + }; + // Toggle currently active tab + this.handleTabClick = (event, tabIndex) => { + this.setState({ + activeTabKey: tabIndex + }); + }; + + this.toggleBox = checked => { + this.setState({ + isBox: checked + }); + }; + } + + render() { + const { activeTabKey, isBox } = this.state; + const tooltip = ( + + ); + + return ( +
    + + Users}> + Users + + Containers}> + Containers + + Database}> + Database + + Disabled} isDisabled> + Disabled + + ARIA Disabled} isAriaDisabled> + ARIA Disabled + + ARIA Disabled (Tooltip)} + isAriaDisabled + > + ARIA Disabled (Tooltip) + + +
    + +
    +
    + ); + } +} +``` + +### With tooltip react ref + +When using a React ref to link a Tooltip to a Tab component, an `id` must be manually set on the Tooltip component, and the Tab component must have a matching `aria-describedby` attribute so that screen readers are able to announce the Tooltip contents. + ```js import React from 'react'; import { Tabs, Tab, TabTitleText, Checkbox, Tooltip } from '@patternfly/react-core'; @@ -28,7 +110,7 @@ class SimpleTabs extends React.Component { super(props); this.state = { activeTabKey: 0, - isBox: false, + isBox: false }; // Toggle currently active tab this.handleTabClick = (event, tabIndex) => { @@ -45,7 +127,7 @@ class SimpleTabs extends React.Component { } render() { - const {activeTabKey, isBox } = this.state; + const { activeTabKey, isBox } = this.state; const tooltipRef = React.createRef(); return ( @@ -66,20 +148,30 @@ class SimpleTabs extends React.Component { ARIA Disabled} isAriaDisabled> ARIA Disabled - ARIA Disabled (Tooltip)} isAriaDisabled ref={tooltipRef}> + ARIA Disabled (Tooltip)} + isAriaDisabled + ref={tooltipRef} + aria-describedby="tooltip-tab-5" + > ARIA Disabled (Tooltip) - -
    + +
    + label="isBox" + isChecked={isBox} + onChange={this.toggleBox} + aria-label="show box variation checkbox" + id="toggle-box" + name="toggle-box" + />
    ); @@ -88,14 +180,16 @@ class SimpleTabs extends React.Component { ``` ### Uncontrolled + ```js import React from 'react'; import { Tabs, Tab, TabTitleText, Checkbox, Tooltip } from '@patternfly/react-core'; class UncontrolledSimpleTabs extends React.Component { - render() { - const tooltipRef = React.createRef(); + const tooltip = ( + + ); return ( <> @@ -110,16 +204,20 @@ class UncontrolledSimpleTabs extends React.Component { Database
    Disabled} isDisabled> - Disabled + Disabled ARIA Disabled} isAriaDisabled> ARIA Disabled - ARIA Disabled (Tooltip)} isAriaDisabled ref={tooltipRef}> + ARIA Disabled (Tooltip)} + isAriaDisabled + tooltip={tooltip} + > ARIA Disabled (Tooltip) - ); } @@ -127,6 +225,7 @@ class UncontrolledSimpleTabs extends React.Component { ``` ### Box light variation + ```js import React from 'react'; import { Tabs, Tab, TabTitleText, Checkbox, Tooltip } from '@patternfly/react-core'; @@ -136,7 +235,7 @@ class SimpleTabs extends React.Component { super(props); this.state = { activeTabKey: 0, - isTabsLightScheme: true, + isTabsLightScheme: true }; // Toggle currently active tab this.handleTabClick = (event, tabIndex) => { @@ -153,12 +252,19 @@ class SimpleTabs extends React.Component { } render() { - const {activeTabKey, isBox, isTabsLightScheme } = this.state; - const tooltipRef = React.createRef(); + const { activeTabKey, isBox, isTabsLightScheme } = this.state; + const tooltip = ( + + ); return (
    - + Users}> Users @@ -174,20 +280,24 @@ class SimpleTabs extends React.Component { ARIA Disabled} isAriaDisabled> ARIA Disabled - ARIA Disabled (Tooltip)} isAriaDisabled ref={tooltipRef}> + ARIA Disabled (Tooltip)} + isAriaDisabled + tooltip={tooltip} + > ARIA Disabled (Tooltip) - -
    +
    + label="Tabs light variation" + isChecked={isTabsLightScheme} + onChange={this.toggleScheme} + aria-label="show light scheme variation checkbox" + id="toggle-scheme" + name="toggle-scheme" + />
    ); @@ -196,6 +306,7 @@ class SimpleTabs extends React.Component { ``` ### Default overflow + ```js import React from 'react'; import { Tabs, Tab, TabTitleText, Checkbox } from '@patternfly/react-core'; @@ -223,7 +334,7 @@ class ScrollButtonsPrimaryTabs extends React.Component { } render() { - const {activeTabKey, isBox} = this.state; + const { activeTabKey, isBox } = this.state; return (
    @@ -261,15 +372,15 @@ class ScrollButtonsPrimaryTabs extends React.Component { Tab 11 section -
    +
    + label="isBox" + isChecked={isBox} + onChange={this.toggleBox} + aria-label="show box variation checkbox on overflow" + id="toggle-box-overflow" + name="toggle-box-overflow" + />
    ); @@ -278,6 +389,7 @@ class ScrollButtonsPrimaryTabs extends React.Component { ``` ### Vertical + ```js import React from 'react'; import { Tabs, Tab, TabTitleText, Checkbox, Tooltip } from '@patternfly/react-core'; @@ -304,8 +416,11 @@ class VerticalTabs extends React.Component { } render() { - const {activeTabKey, isBox} = this.state; - const tooltipRef = React.createRef(); + const { activeTabKey, isBox } = this.state; + const tooltip = ( + + ); + return (
    @@ -324,20 +439,24 @@ class VerticalTabs extends React.Component { ARIA Disabled} isAriaDisabled> ARIA Disabled - ARIA Disabled (Tooltip)} isAriaDisabled ref={tooltipRef}> + ARIA Disabled (Tooltip)} + isAriaDisabled + tooltip={tooltip} + > ARIA Disabled (Tooltip) - -
    +
    + label="isBox" + isChecked={isBox} + onChange={this.toggleBox} + aria-label="show box variation checkbox with vertical" + id="toggle-box-vertical" + name="toggle-box-vertical" + />
    ); @@ -346,6 +465,7 @@ class VerticalTabs extends React.Component { ``` ### Vertical expandable + ```js import React from 'react'; import { Tabs, Tab, TabTitleText, Checkbox } from '@patternfly/react-core'; @@ -369,34 +489,43 @@ class VerticalExpandableTabs extends React.Component { }; } render() { - const {activeTabKey, isExpanded} = this.state; + const { activeTabKey, isExpanded } = this.state; return ( - - Users}> - Users - - Containers}> - Containers - - Database}> - Database - - Server}> - Server - - System}> - System - - Network}> - Network - - + + Users}> + Users + + Containers}> + Containers + + Database}> + Database + + Server}> + Server + + System}> + System + + Network}> + Network + + ); } } ``` ### Vertical expandable uncontrolled + ```js import React from 'react'; import { Tabs, Tab, TabTitleText, Checkbox } from '@patternfly/react-core'; @@ -414,34 +543,42 @@ class VerticalExpandableUncontrolledTabs extends React.Component { }; } render() { - const {activeTabKey} = this.state; + const { activeTabKey } = this.state; return ( - - Users}> - Users - - Containers}> - Containers - - Database}> - Database - - Server}> - Server - - System}> - System - - Network}> - Network - - + + Users}> + Users + + Containers}> + Containers + + Database}> + Database + + Server}> + Server + + System}> + System + + Network}> + Network + + ); } } ``` ### Inset + ```js import React from 'react'; import { Tabs, Tab, TabTitleText, Checkbox } from '@patternfly/react-core'; @@ -468,16 +605,20 @@ class InsetTabs extends React.Component { } render() { - const {activeTabKey, isBox} = this.state; + const { activeTabKey, isBox } = this.state; return (
    - + Users}> Users @@ -497,15 +638,15 @@ class InsetTabs extends React.Component { Network -
    +
    + label="isBox" + isChecked={isBox} + onChange={this.toggleBox} + aria-label="show box variation checkbox with inset" + id="toggle-box-inset" + name="toggle-box-inset" + />
    ); @@ -514,6 +655,7 @@ class InsetTabs extends React.Component { ``` ### Page Insets + ```js import React from 'react'; import { Tabs, Tab, TabTitleText, Checkbox } from '@patternfly/react-core'; @@ -540,11 +682,10 @@ class PageInsetsTabs extends React.Component { } render() { - const {activeTabKey, isBox} = this.state; + const { activeTabKey, isBox } = this.state; return (
    - + Users}> Users @@ -564,15 +705,15 @@ class PageInsetsTabs extends React.Component { Network -
    +
    + label="isBox" + isChecked={isBox} + onChange={this.toggleBox} + aria-label="show box variation checkbox with inset" + id="toggle-box-inset" + name="toggle-box-inset" + />
    ); @@ -581,6 +722,7 @@ class PageInsetsTabs extends React.Component { ``` ### Icons and text + ```js import React from 'react'; import { Tabs, Tab, TabTitleText, TabTitleIcon } from '@patternfly/react-core'; @@ -608,22 +750,82 @@ class IconAndTextTabs extends React.Component { render() { return ( - Users }> + + + + {' '} + Users{' '} + + } + > Users - Containers }> + + + + {' '} + Containers{' '} + + } + > Containers - Database }> + + + + {' '} + Database{' '} + + } + > Database - Server }> + + + + {' '} + Server{' '} + + } + > Server - System }> + + + + {' '} + System{' '} + + } + > System - Network }> + + + + {' '} + Network{' '} + + } + > Network @@ -633,6 +835,7 @@ class IconAndTextTabs extends React.Component { ``` ### Tabs with sub tabs + ```js import React from 'react'; import { Tabs, Tab, TabTitleText, Checkbox } from '@patternfly/react-core'; @@ -666,78 +869,78 @@ class SecondaryTabs extends React.Component { } render() { - const {activeTabKey1, activeTabKey2, isBox} = this.state; + const { activeTabKey1, activeTabKey2, isBox } = this.state; return (
    - - Users}> - - Secondary tab item 1}> - Secondary tab item 1 item section - - Secondary tab item 2}> - Secondary tab item 2 section - - Secondary tab item 3}> - Secondary tab item 3 section - - Secondary tab item 4}> - Secondary tab item 4 section - - Secondary tab item 5}> - Secondary tab item 5 section - - Secondary tab item 6}> - Secondary tab item 6 section - - Secondary tab item 7}> - Secondary tab item 7 section - - Secondary tab item 8}> - Secondary tab item 8 section - - - - Containers}> - Containers - - Database}> - Database - - Server}> - Server - - System}> - System - - Network}> - Network - - Tab item 7}> - Tab 7 section - - Tab item 8}> - Tab 8 section - - Tab item 9}> - Tab 9 section - - Tab item 10}> - Tab 10 section - - Tab item 11}> - Tab 11 section - - -
    - + + Users}> + + Secondary tab item 1}> + Secondary tab item 1 item section + + Secondary tab item 2}> + Secondary tab item 2 section + + Secondary tab item 3}> + Secondary tab item 3 section + + Secondary tab item 4}> + Secondary tab item 4 section + + Secondary tab item 5}> + Secondary tab item 5 section + + Secondary tab item 6}> + Secondary tab item 6 section + + Secondary tab item 7}> + Secondary tab item 7 section + + Secondary tab item 8}> + Secondary tab item 8 section + + + + Containers}> + Containers + + Database}> + Database + + Server}> + Server + + System}> + System + + Network}> + Network + + Tab item 7}> + Tab 7 section + + Tab item 8}> + Tab 8 section + + Tab item 9}> + Tab 9 section + + Tab item 10}> + Tab 10 section + + Tab item 11}> + Tab 11 section + + +
    +
    ); @@ -746,6 +949,7 @@ class SecondaryTabs extends React.Component { ``` ### Filled + ```js import React from 'react'; import { Tabs, Tab, TabTitleText, Checkbox } from '@patternfly/react-core'; @@ -772,29 +976,29 @@ class FilledTabs extends React.Component { } render() { - const {activeTabKey, isBox} = this.state; + const { activeTabKey, isBox } = this.state; return (
    - - Users}> - Users - - Containers}> - Containers - - Database}> - Database - - -
    - + + Users}> + Users + + Containers}> + Containers + + Database}> + Database + + +
    +
    ); @@ -803,6 +1007,7 @@ class FilledTabs extends React.Component { ``` ### Filled with icons + ```js import React from 'react'; import { Tabs, Tab, TabTitleText, TabTitleIcon, Checkbox } from '@patternfly/react-core'; @@ -832,29 +1037,59 @@ class FilledTabsWithIcons extends React.Component { } render() { - const {activeTabKey, isBox} = this.state; + const { activeTabKey, isBox } = this.state; return (
    - - Users }> - Users - - Containers }> - Containers - - Database }> - Database - - -
    - + + + + + {' '} + Users{' '} + + } + > + Users + + + + + {' '} + Containers{' '} + + } + > + Containers + + + + + {' '} + Database{' '} + + } + > + Database + + +
    +
    ); @@ -863,6 +1098,7 @@ class FilledTabsWithIcons extends React.Component { ``` ### Using the nav element + ```js import React from 'react'; import { Tabs, Tab, TabsComponent, TabTitleText } from '@patternfly/react-core'; @@ -890,7 +1126,7 @@ class TabsNav extends React.Component { aria-label="Local" component={TabsComponent.nav} > - Users} href="#users"> + Users} href="#users"> Users Containers} href="#containers"> @@ -914,8 +1150,8 @@ class TabsNav extends React.Component { } ``` - ### Sub nav using the nav element + ```js import React from 'react'; import { Tabs, Tab, TabsComponent, TabTitleText } from '@patternfly/react-core'; @@ -1001,6 +1237,7 @@ class SecondaryTabsNav extends React.Component { ``` ### Separate content + ```js import React from 'react'; import { Tabs, Tab, TabTitleText, TabContent } from '@patternfly/react-core'; @@ -1028,9 +1265,24 @@ class SeparateTabContent extends React.Component { return ( - Tab item 1} tabContentId="refTab1Section" tabContentRef={this.contentRef1} /> - Tab item 2}tabContentId="refTab2Section" tabContentRef={this.contentRef2} /> - Tab item 3} tabContentId="refTab3Section" tabContentRef={this.contentRef3} /> + Tab item 1} + tabContentId="refTab1Section" + tabContentRef={this.contentRef1} + /> + Tab item 2} + tabContentId="refTab2Section" + tabContentRef={this.contentRef2} + /> + Tab item 3} + tabContentId="refTab3Section" + tabContentRef={this.contentRef3} + />
    @@ -1050,13 +1302,14 @@ class SeparateTabContent extends React.Component { ``` ### Tab content with body and padding + ```js import React from 'react'; import { Tabs, Tab, TabTitleText, TabContent, TabContentBody } from '@patternfly/react-core'; const TabContentWithBody = () => { const [activeTabKey, setActiveTabKey] = React.useState(0); - + // Toggle currently active tab const handleTabClick = (event, tabIndex) => { setActiveTabKey(tabIndex); @@ -1069,9 +1322,24 @@ const TabContentWithBody = () => { return ( - Tab item 1} tabContentId="refTab1Section" tabContentRef={contentRef1} /> - Tab item 2}tabContentId="refTab2Section" tabContentRef={contentRef2} /> - Tab item 3} tabContentId="refTab3Section" tabContentRef={contentRef3} /> + Tab item 1} + tabContentId="refTab1Section" + tabContentRef={contentRef1} + /> + Tab item 2} + tabContentId="refTab2Section" + tabContentRef={contentRef2} + /> + Tab item 3} + tabContentId="refTab3Section" + tabContentRef={contentRef3} + />
    @@ -1086,10 +1354,11 @@ const TabContentWithBody = () => {
    ); -} +}; ``` ### Children mounting on click + ```js import React from 'react'; import { Tabs, Tab, TabTitleText } from '@patternfly/react-core'; @@ -1111,13 +1380,13 @@ class MountingSimpleTabs extends React.Component { render() { return ( - Tab item 1} > + Tab item 1}> Tab 1 section - Tab item 2} > + Tab item 2}> Tab 2 section - Tab item 3} > + Tab item 3}> Tab 3 section @@ -1127,6 +1396,7 @@ class MountingSimpleTabs extends React.Component { ``` ### Unmounting invisible children + ```js import React from 'react'; import { Tabs, Tab, TabTitleText } from '@patternfly/react-core'; @@ -1148,13 +1418,13 @@ class UnmountingSimpleTabs extends React.Component { render() { return ( - Tab item 1} > + Tab item 1}> Tab 1 section - Tab item 2} > + Tab item 2}> Tab 2 section - Tab item 3} > + Tab item 3}> Tab 3 section @@ -1164,6 +1434,7 @@ class UnmountingSimpleTabs extends React.Component { ``` ### Toggled separate content + ```js import React from 'react'; import { Tabs, Tab, TabContent, Button, Divider } from '@patternfly/react-core';