Skip to content

Commit

Permalink
feat(Tabs): add TabAction, update core ver (#8348)
Browse files Browse the repository at this point in the history
* feat(Tabs): add TabAction, update core ver

* desc updates, prop update

* adjust actions default order, add missing flag

* update description, add classname to span
  • Loading branch information
kmcfaul committed Nov 16, 2022
1 parent 7568f81 commit 8c584b4
Show file tree
Hide file tree
Showing 17 changed files with 351 additions and 48 deletions.
2 changes: 1 addition & 1 deletion packages/react-catalog-view-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"clean": "rimraf dist"
},
"dependencies": {
"@patternfly/patternfly": "4.219.2",
"@patternfly/patternfly": "4.220.0",
"@patternfly/react-core": "^4.263.0",
"@patternfly/react-styles": "^4.91.10"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/react-console/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
},
"dependencies": {
"@novnc/novnc": "^1.2.0",
"@patternfly/patternfly": "4.219.2",
"@patternfly/patternfly": "4.220.0",
"@patternfly/react-core": "^4.263.0",
"@spice-project/spice-html5": "^0.2.1",
"@types/file-saver": "^2.0.1",
Expand Down
2 changes: 1 addition & 1 deletion packages/react-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"tslib": "^2.0.0"
},
"devDependencies": {
"@patternfly/patternfly": "4.219.2",
"@patternfly/patternfly": "4.220.0",
"@rollup/plugin-commonjs": "^21.0.0",
"@rollup/plugin-node-resolve": "^13.0.0",
"@rollup/plugin-replace": "^3.0.0",
Expand Down
33 changes: 17 additions & 16 deletions packages/react-core/src/components/Tabs/Tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import { TabButton } from './TabButton';
import { TabsContext } from './TabsContext';
import { css } from '@patternfly/react-styles';
import { Tooltip } from '../Tooltip';
import { Button } from '../Button';
import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon';
import { TabAction } from './TabAction';

export interface TabProps extends Omit<React.HTMLProps<HTMLAnchorElement | HTMLButtonElement>, 'title'>, OUIAProps {
export interface TabProps
extends Omit<React.HTMLProps<HTMLAnchorElement | HTMLButtonElement>, 'title' | 'action'>,
OUIAProps {
/** content rendered inside the Tab content area. */
children?: React.ReactNode;
/** additional classes added to the Tab */
Expand Down Expand Up @@ -39,6 +41,8 @@ export interface TabProps extends Omit<React.HTMLProps<HTMLAnchorElement | HTMLB
closeButtonAriaLabel?: string;
/** @beta Flag indicating the close button should be disabled */
isCloseDisabled?: boolean;
/** @beta Actions rendered beside the tab content */
actions?: React.ReactNode;
/** Value to set the data-ouia-component-id for the tab button.*/
ouiaId?: number | string;
}
Expand All @@ -59,6 +63,7 @@ const TabBase: React.FunctionComponent<TabProps> = ({
tooltip,
closeButtonAriaLabel,
isCloseDisabled = false,
actions,
...props
}: TabProps) => {
const preventedEvents = inoperableEvents.reduce(
Expand Down Expand Up @@ -117,26 +122,22 @@ const TabBase: React.FunctionComponent<TabProps> = ({
className={css(
styles.tabsItem,
eventKey === localActiveKey && styles.modifiers.current,
handleTabClose && styles.modifiers.action,
handleTabClose && (isDisabled || isAriaDisabled) && styles.modifiers.disabled,
(handleTabClose || actions) && styles.modifiers.action,
(isDisabled || isAriaDisabled) && styles.modifiers.disabled,
childClassName
)}
role="presentation"
>
{tooltip ? <Tooltip {...tooltip.props}>{tabButton}</Tooltip> : tabButton}
{actions && actions}
{handleTabClose !== undefined && (
<span className={css(styles.tabsItemClose)}>
<Button
variant="plain"
aria-label={closeButtonAriaLabel || 'Close tab'}
onClick={(event: any) => handleTabClose(event, eventKey, tabContentRef)}
isDisabled={isCloseDisabled}
>
<span className={css(styles.tabsItemCloseIcon)}>
<TimesIcon />
</span>
</Button>
</span>
<TabAction
aria-label={closeButtonAriaLabel || 'Close tab'}
onClick={(event: any) => handleTabClose(event, eventKey, tabContentRef)}
isDisabled={isCloseDisabled}
>
<TimesIcon />
</TabAction>
)}
</li>
);
Expand Down
57 changes: 57 additions & 0 deletions packages/react-core/src/components/Tabs/TabAction.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import * as React from 'react';
import { css } from '@patternfly/react-styles';
import styles from '@patternfly/react-styles/css/components/Tabs/tabs';
import { Button } from '../Button';
import { getOUIAProps, OUIAProps } from '../../helpers';

export interface TabActionProps extends Omit<React.HTMLProps<HTMLButtonElement>, 'ref' | 'type'>, OUIAProps {
/** Content rendered in the tab action */
children?: React.ReactNode;
/** Additional classes added to the tab action span */
className?: string;
/** Click callback for tab action button */
onClick?: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void;
/** Flag indicating if the tab action is a help action */
isHelpAction?: boolean;
/** Flag indicating if the tab action is disabled */
isDisabled?: boolean;
/** Accessible label for the tab action */
'aria-label'?: string;
/** @hide Callback for the section ref */
innerRef?: React.Ref<any>;
}

const TabActionBase: React.FunctionComponent<TabActionProps> = ({
children,
className,
onClick,
isHelpAction,
isDisabled,
'aria-label': ariaLabel = 'Tab action',
innerRef,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
ouiaId,
ouiaSafe,
...props
}: TabActionProps) => (
<span className={css(styles.tabsItemAction, isHelpAction && styles.modifiers.help, className)}>
<Button
ref={innerRef}
type="button"
variant="plain"
aria-label={ariaLabel}
onClick={onClick}
isDisabled={isDisabled}
{...getOUIAProps(TabAction.displayName, ouiaId, ouiaSafe)}
{...props}
>
<span className={css(styles.tabsItemActionIcon)}>{children}</span>
</Button>
</span>
);

export const TabAction = React.forwardRef((props: TabActionProps, ref: React.Ref<HTMLElement>) => (
<TabActionBase {...props} innerRef={ref} />
));

TabAction.displayName = 'TabAction';
2 changes: 1 addition & 1 deletion packages/react-core/src/components/Tabs/Tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export interface TabsProps extends Omit<React.HTMLProps<HTMLElement | HTMLDivEle
defaultActiveKey?: number | string;
/** Callback to handle tab selection */
onSelect?: (event: React.MouseEvent<HTMLElement, MouseEvent>, eventKey: number | string) => void;
/** @beta Callback to handle tab closing */
/** @beta Callback to handle tab closing and adds a basic close button to all tabs. This is overridden by the tab actions property. */
onClose?: (event: React.MouseEvent<HTMLElement, MouseEvent>, eventKey: number | string) => void;
/** @beta Callback for the add button. Passing this property inserts the add button */
onAdd?: () => void;
Expand Down
10 changes: 10 additions & 0 deletions packages/react-core/src/components/Tabs/__tests__/Tab.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import { render } from '@testing-library/react';
import { Tab } from '../Tab';
import { TabAction } from '../TabAction';
import { TabTitleText } from '../TabTitleText';

test('should not render anything', () => {
Expand All @@ -11,3 +12,12 @@ test('should not render anything', () => {
);
expect(asFragment()).toMatchSnapshot();
});

test('renders tab action', () => {
const { asFragment } = render(
<Tab eventKey={1} title={<TabTitleText>"Tab item 2"</TabTitleText>} actions={<TabAction>test</TabAction>}>
Tab 2 section
</Tab>
);
expect(asFragment()).toMatchSnapshot();
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,50 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`renders tab action 1`] = `
<DocumentFragment>
<li
class="pf-c-tabs__item pf-m-action"
role="presentation"
>
<button
aria-controls="pf-tab-section-1-"
aria-selected="false"
class="pf-c-tabs__link"
data-ouia-component-type="PF4/TabButton"
data-ouia-safe="true"
id="pf-tab-1-"
role="tab"
type="button"
>
<span
class="pf-c-tabs__item-text"
>
"Tab item 2"
</span>
</button>
<span
class="pf-c-tabs__item-action"
>
<button
aria-disabled="false"
aria-label="Tab action"
class="pf-c-button pf-m-plain"
data-ouia-component-id="OUIA-Generated-Button-plain-1"
data-ouia-component-type="PF4/Button"
data-ouia-safe="true"
type="button"
>
<span
class="pf-c-tabs__item-action-icon"
>
test
</span>
</button>
</span>
</li>
</DocumentFragment>
`;

exports[`should not render anything 1`] = `
<DocumentFragment>
<li
Expand Down
84 changes: 64 additions & 20 deletions packages/react-core/src/components/Tabs/examples/Tabs.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import DatabaseIcon from '@patternfly/react-icons/dist/esm/icons/database-icon';
import ServerIcon from '@patternfly/react-icons/dist/esm/icons/server-icon';
import LaptopIcon from '@patternfly/react-icons/dist/esm/icons/laptop-icon';
import ProjectDiagramIcon from '@patternfly/react-icons/dist/esm/icons/project-diagram-icon';
import HelpIcon from '@patternfly/react-icons/dist/esm/icons/help-icon';
import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon';

Most tab variations are available as open (default) or box style tabs. Select the 'isBox' checkbox to preview an example with box styled tabs.
The Tabs items background can be also toggled with 'Tab light variation' checkbox.
Expand Down Expand Up @@ -458,7 +460,15 @@ class VerticalTabs extends React.Component {
aria-label="Tabs in the vertical example"
role="region"
>
<Tab eventKey={0} title={<TabTitleText aria-label="vertical" role="region">Users</TabTitleText>} aria-label="Vertical example content users">
<Tab
eventKey={0}
title={
<TabTitleText aria-label="vertical" role="region">
Users
</TabTitleText>
}
aria-label="Vertical example content users"
>
Users
</Tab>
<Tab eventKey={1} title={<TabTitleText>Containers</TabTitleText>}>
Expand Down Expand Up @@ -591,7 +601,11 @@ class VerticalExpandableUncontrolledTabs extends React.Component {
aria-label="Tabs in the vertical expandable uncontrolled example"
role="region"
>
<Tab eventKey={0} title={<TabTitleText>Users</TabTitleText>} aria-label="Vertical expandable uncontrolled content users">
<Tab
eventKey={0}
title={<TabTitleText>Users</TabTitleText>}
aria-label="Vertical expandable uncontrolled content users"
>
Users
</Tab>
<Tab eventKey={1} title={<TabTitleText>Containers</TabTitleText>}>
Expand Down Expand Up @@ -1206,7 +1220,12 @@ class TabsNav extends React.Component {
component={TabsComponent.nav}
aria-label="Tabs in the nav element example"
>
<Tab eventKey={0} title={<TabTitleText>Users</TabTitleText>} href="#users" aria-label="Nav element content users">
<Tab
eventKey={0}
title={<TabTitleText>Users</TabTitleText>}
href="#users"
aria-label="Nav element content users"
>
Users
</Tab>
<Tab eventKey={1} title={<TabTitleText>Containers</TabTitleText>} href="#containers">
Expand Down Expand Up @@ -1267,7 +1286,12 @@ class SecondaryTabsNav extends React.Component {
component={TabsComponent.nav}
aria-label="Tabs in the sub tabs with nav element example"
>
<Tab eventKey={0} title={<TabTitleText>Users</TabTitleText>} href="#" aria-label="Subtabs with nav content users">
<Tab
eventKey={0}
title={<TabTitleText>Users</TabTitleText>}
href="#"
aria-label="Subtabs with nav content users"
>
<Tabs
activeKey={this.state.activeTabKey2}
isSecondary
Expand Down Expand Up @@ -1423,7 +1447,12 @@ const TabContentWithBody = () => {

return (
<React.Fragment>
<Tabs activeKey={activeTabKey} onSelect={handleTabClick} aria-label="Tabs in the body and padding example" role="region">
<Tabs
activeKey={activeTabKey}
onSelect={handleTabClick}
aria-label="Tabs in the body and padding example"
role="region"
>
<Tab
eventKey={0}
title={<TabTitleText>Tab item 1</TabTitleText>}
Expand All @@ -1447,20 +1476,10 @@ const TabContentWithBody = () => {
<TabContent eventKey={0} id="tab1SectionBodyPadding" ref={contentRef1}>
<TabContentBody hasPadding> Tab 1 section with body and padding </TabContentBody>
</TabContent>
<TabContent
eventKey={1}
id="tab2SectionBodyPadding"
ref={contentRef2}
hidden
>
<TabContent eventKey={1} id="tab2SectionBodyPadding" ref={contentRef2} hidden>
<TabContentBody hasPadding> Tab 2 section with body and padding </TabContentBody>
</TabContent>
<TabContent
eventKey={2}
id="tab3SectionBodyPadding"
ref={contentRef3}
hidden
>
<TabContent eventKey={2} id="tab3SectionBodyPadding" ref={contentRef3} hidden>
<TabContentBody hasPadding> Tab 3 section with body and padding </TabContentBody>
</TabContent>
</div>
Expand Down Expand Up @@ -1597,11 +1616,26 @@ class ToggledSeparateContent extends React.Component {
aria-label="Tabs in the toggled separate content example"
role="region"
>
<Tab eventKey={0} title="Tab item 1" tabContentId="tab1SectionSeparateContent" tabContentRef={this.contentRef1} />
<Tab
eventKey={0}
title="Tab item 1"
tabContentId="tab1SectionSeparateContent"
tabContentRef={this.contentRef1}
/>
{!isTab2Hidden && (
<Tab eventKey={1} title="Tab item 2" tabContentId="tab2SectionSeparateContent" tabContentRef={this.contentRef2} />
<Tab
eventKey={1}
title="Tab item 2"
tabContentId="tab2SectionSeparateContent"
tabContentRef={this.contentRef2}
/>
)}
<Tab eventKey={2} title="Tab item 3" tabContentId="tab3SectionSeparateContent" tabContentRef={this.contentRef3} />
<Tab
eventKey={2}
title="Tab item 3"
tabContentId="tab3SectionSeparateContent"
tabContentRef={this.contentRef3}
/>
</Tabs>
<div>
<TabContent
Expand Down Expand Up @@ -1645,3 +1679,13 @@ To enable closeable tabs, pass the `onClose` property to `Tabs`, and to enable t

```ts file="./TabsDynamic.tsx" isBeta
```

### Help action

```ts file="./TabsHelp.tsx" isBeta
```

### Help and close actions

```ts file="./TabsHelpAndClose.tsx" isBeta
```
Loading

0 comments on commit 8c584b4

Please sign in to comment.