diff --git a/packages/react-core/src/components/Tabs/Tabs.tsx b/packages/react-core/src/components/Tabs/Tabs.tsx index b7fb44dc119..6fcd378a919 100644 --- a/packages/react-core/src/components/Tabs/Tabs.tsx +++ b/packages/react-core/src/components/Tabs/Tabs.tsx @@ -41,6 +41,8 @@ export interface TabsProps extends Omit { isSecondary: false, isVertical: false, isBox: false, + hasBorderBottom: true, leftScrollAriaLabel: 'Scroll left', rightScrollAriaLabel: 'Scroll right', component: TabsComponent.div, @@ -288,6 +291,7 @@ export class Tabs extends React.Component { isSecondary, isVertical, isBox, + hasBorderBottom, leftScrollAriaLabel, rightScrollAriaLabel, 'aria-label': ariaLabel, @@ -356,6 +360,7 @@ export class Tabs extends React.Component { isBox && styles.modifiers.box, showScrollButtons && !isVertical && styles.modifiers.scrollable, usePageInsets && styles.modifiers.pageInsets, + !hasBorderBottom && styles.modifiers.noBorderBottom, formatBreakpointMods(inset, styles), variantStyle[variant], className diff --git a/packages/react-core/src/components/Tabs/__tests__/Tabs.test.tsx b/packages/react-core/src/components/Tabs/__tests__/Tabs.test.tsx index a976c5d27a2..cde52f0f575 100644 --- a/packages/react-core/src/components/Tabs/__tests__/Tabs.test.tsx +++ b/packages/react-core/src/components/Tabs/__tests__/Tabs.test.tsx @@ -276,3 +276,20 @@ test('should render box tabs of light variant', () => { expect(view.container).toMatchSnapshot(); }); +test('should render tabs with no bottom border', () => { + const view = render( + + "Tab item 1"}> + Tab 1 section + + "Tab item 2"}> + Tab 2 section + + "Tab item 3"}> + Tab 3 section + + + ); + expect(view.container).toMatchSnapshot(); +}); + diff --git a/packages/react-core/src/components/Tabs/__tests__/__snapshots__/Tabs.test.tsx.snap b/packages/react-core/src/components/Tabs/__tests__/__snapshots__/Tabs.test.tsx.snap index cddc40088a3..8a527839e1d 100644 --- a/packages/react-core/src/components/Tabs/__tests__/__snapshots__/Tabs.test.tsx.snap +++ b/packages/react-core/src/components/Tabs/__tests__/__snapshots__/Tabs.test.tsx.snap @@ -1444,6 +1444,149 @@ exports[`should render tabs with eventKey Strings 1`] = ` `; +exports[`should render tabs with no bottom border 1`] = ` +
+
+ +
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+ +
+
+ Tab 1 section +
+ + +
+`; + exports[`should render tabs with separate content 1`] = `
{ ```js isFullscreen file="./examples/Tabs/TabsAndTable.tsx" ``` +### Tables and tabs, auto width tabs + +```js isFullscreen file="./examples/Tabs/TabsAndTablesAutoWidthTabs.tsx" +``` + ### Modal tabs ```js isFullscreen file="./examples/Tabs/ModalTabs.tsx" diff --git a/packages/react-core/src/demos/examples/Tabs/TabsAndTablesAutoWidthTabs.tsx b/packages/react-core/src/demos/examples/Tabs/TabsAndTablesAutoWidthTabs.tsx new file mode 100644 index 00000000000..ad56cedbdf1 --- /dev/null +++ b/packages/react-core/src/demos/examples/Tabs/TabsAndTablesAutoWidthTabs.tsx @@ -0,0 +1,442 @@ +/* eslint-disable no-console */ +import React from 'react'; +import { + Button, + Divider, + Drawer, + DrawerContent, + DrawerContentBody, + DrawerPanelContent, + DrawerHead, + DrawerActions, + DrawerCloseButton, + DrawerPanelBody, + Dropdown, + Flex, + FlexItem, + KebabToggle, + Label, + LabelGroup, + OptionsMenu, + OptionsMenuToggle, + OverflowMenu, + OverflowMenuContent, + OverflowMenuControl, + OverflowMenuGroup, + OverflowMenuItem, + PageSection, + PageSectionVariants, + Pagination, + PaginationVariant, + Progress, + ProgressSize, + Select, + SelectVariant, + Tabs, + Tab, + TabContent, + TabContentBody, + TabTitleText, + Title, + Toolbar, + ToolbarItem, + ToolbarContent, + ToolbarToggleGroup +} from '@patternfly/react-core'; +import { + TableComposable, + Thead, + Tbody, + Tr, + Th, + Td, + IAction, + ActionsColumn, + CustomActionsToggleProps +} from '@patternfly/react-table'; +import DashboardWrapper from '../DashboardWrapper'; +import CodeIcon from '@patternfly/react-icons/dist/esm/icons/code-icon'; +import CodeBranchIcon from '@patternfly/react-icons/dist/esm/icons/code-branch-icon'; +import CubeIcon from '@patternfly/react-icons/dist/esm/icons/cube-icon'; +import FilterIcon from '@patternfly/react-icons/dist/esm/icons/filter-icon'; +import SortAmountDownIcon from '@patternfly/react-icons/dist/esm/icons/sort-amount-down-icon'; + +interface Repository { + name: string; + branches: number | null; + prs: number | null; + workspaces: number; + lastCommit: string; +} + +export const TablesAndTabs = () => { + // tab properties + const [activeTabKey, setActiveTabKey] = React.useState(0); + // Toggle currently active tab + const handleTabClick = (tabIndex: number) => { + setActiveTabKey(tabIndex); + }; + + // secondary tab properties + const [secondaryActiveTabKey, setSecondaryActiveTabKey] = React.useState(10); + const handleSecondaryTabClick = (tabIndex: number) => { + setSecondaryActiveTabKey(tabIndex); + }; + + // drawer properties + const [isExpanded, setIsExpanded] = React.useState(false); + + // table properties + // In real usage, this data would come from some external source like an API via props. + const repositories: Repository[] = [ + { name: 'Node 1', branches: 10, prs: 25, workspaces: 5, lastCommit: '2 days ago' }, + { name: 'Node 2', branches: 8, prs: 30, workspaces: 2, lastCommit: '2 days ago' }, + { name: 'Node 3', branches: 12, prs: 48, workspaces: 13, lastCommit: '30 days ago' }, + { name: 'Node 4', branches: 3, prs: 8, workspaces: 20, lastCommit: '8 days ago' }, + { name: 'Node 5', branches: 33, prs: 21, workspaces: 2, lastCommit: '26 days ago' } + ]; + + const columnNames = { + name: 'Repositories', + branches: 'Branches', + prs: 'Pull requests', + workspaces: 'Workspaces', + lastCommit: 'Last commit' + }; + + const [selectedRepoNames, setSelectedRepoNames] = React.useState([]); + const setRepoSelected = (event: React.FormEvent, repo: Repository, isSelecting = true) => { + setSelectedRepoNames(prevSelected => { + const otherSelectedRepoNames = prevSelected.filter(r => r !== repo.name); + return isSelecting ? [...otherSelectedRepoNames, repo.name] : otherSelectedRepoNames; + }); + event.stopPropagation(); + }; + const onSelectAll = (isSelecting = true) => setSelectedRepoNames(isSelecting ? repositories.map(r => r.name) : []); + const allRowsSelected = selectedRepoNames.length === repositories.length; + const isRepoSelected = (repo: Repository) => selectedRepoNames.includes(repo.name); + + const [rowClicked, setRowClicked] = React.useState(null); + const isRowClicked = (repo: Repository) => rowClicked === repo.name; + + const defaultActions: IAction[] = [ + { + title: 'Some action', + onClick: event => { + event.stopPropagation(); + console.log('clicked on Some action'); + } + }, + { + title: Link action, + onClick: event => { + event.stopPropagation(); + console.log('clicked on Link action'); + } + }, + { + isSeparator: true + }, + { + title: 'Third action', + onClick: event => { + event.stopPropagation(); + console.log('clicked on Third action'); + } + } + ]; + + const customActionsToggle = (props: CustomActionsToggleProps) => ( + { + props.onToggle(value); + event.stopPropagation(); + }} + /> + ); + + const toolbar = ( + + + } breakpoint="xl"> + +