Skip to content

Commit

Permalink
toggle group with poppers
Browse files Browse the repository at this point in the history
  • Loading branch information
kmcfaul committed Jul 17, 2023
1 parent e4e10c3 commit 512d9e7
Show file tree
Hide file tree
Showing 9 changed files with 108 additions and 172 deletions.
25 changes: 7 additions & 18 deletions packages/react-core/src/components/Toolbar/ToolbarContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import styles from '@patternfly/react-styles/css/components/Toolbar/toolbar';
import { css } from '@patternfly/react-styles';
import { ToolbarContentContext, ToolbarContext } from './ToolbarUtils';
import { formatBreakpointMods } from '../../helpers/util';
import { ToolbarExpandableContent } from './ToolbarExpandableContent';
import { PageContext } from '../Page/PageContext';

export interface ToolbarContentProps extends React.HTMLProps<HTMLDivElement> {
Expand Down Expand Up @@ -33,8 +32,6 @@ export interface ToolbarContentProps extends React.HTMLProps<HTMLDivElement> {
clearFiltersButtonText?: string;
/** Id of the parent Toolbar component */
toolbarId?: string;
/** Custom expandable content for the toolbar, for non-managed multiple toolbar toggle groups. */
expandableContent?: React.ReactNode;
}

export class ToolbarContent extends React.Component<ToolbarContentProps> {
Expand All @@ -60,7 +57,6 @@ export class ToolbarContent extends React.Component<ToolbarContentProps> {
showClearFiltersButton,
clearFiltersButtonText,
alignSelf,
expandableContent,
...props
} = this.props;

Expand All @@ -73,13 +69,15 @@ export class ToolbarContent extends React.Component<ToolbarContentProps> {
formatBreakpointMods(visibility, styles, '', getBreakpoint(width)),
className
)}
ref={this.expandableContentRef}
{...props}
>
<ToolbarContext.Consumer>
{({
clearAllFilters: clearAllFiltersContext,
clearFiltersButtonText: clearFiltersButtonContext,
showClearFiltersButton: showClearFiltersButtonContext,
isExpanded: isExpandedContext,
toolbarId: toolbarIdContext
}) => {
const expandableContentId = `${
Expand All @@ -90,7 +88,11 @@ export class ToolbarContent extends React.Component<ToolbarContentProps> {
value={{
expandableContentRef: this.expandableContentRef,
expandableContentId,
chipContainerRef: this.chipContainerRef
chipContainerRef: this.chipContainerRef,
isExpanded: isExpanded || isExpandedContext,
clearAllFilters: clearAllFilters || clearAllFiltersContext,
clearFiltersButtonText: clearFiltersButtonText || clearFiltersButtonContext,
showClearFiltersButton: showClearFiltersButton || showClearFiltersButtonContext
}}
>
<div
Expand All @@ -106,19 +108,6 @@ export class ToolbarContent extends React.Component<ToolbarContentProps> {
>
{children}
</div>
{expandableContent ? (
expandableContent
) : (
<ToolbarExpandableContent
id={expandableContentId}
isExpanded={isExpanded}
expandableContentRef={this.expandableContentRef}
chipContainerRef={this.chipContainerRef}
clearAllFilters={clearAllFilters || clearAllFiltersContext}
showClearFiltersButton={showClearFiltersButton || showClearFiltersButtonContext}
clearFiltersButtonText={clearFiltersButtonText || clearFiltersButtonContext}
/>
)}
</ToolbarContentContext.Provider>
);
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export class ToolbarExpandableContent extends React.Component<ToolbarExpandableC

render() {
const {
children,
className,
expandableContentRef,
chipContainerRef,
Expand All @@ -58,7 +59,7 @@ export class ToolbarExpandableContent extends React.Component<ToolbarExpandableC
ref={expandableContentRef}
{...props}
>
<ToolbarGroup />
<ToolbarGroup>{children}</ToolbarGroup>
{numberOfFilters > 0 && (
<ToolbarGroup className={styles.modifiers.chipContainer}>
<ToolbarGroup ref={chipContainerRef} />
Expand Down
108 changes: 68 additions & 40 deletions packages/react-core/src/components/Toolbar/ToolbarToggleGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import styles from '@patternfly/react-styles/css/components/Toolbar/toolbar';
import { css } from '@patternfly/react-styles';
import { ToolbarGroupProps } from './ToolbarGroup';
Expand All @@ -8,6 +7,8 @@ import { Button } from '../Button';
import globalBreakpointLg from '@patternfly/react-tokens/dist/esm/global_breakpoint_lg';
import { formatBreakpointMods, toCamel, canUseDOM } from '../../helpers/util';
import { PageContext } from '../Page/PageContext';
import { ToolbarExpandableContent } from './ToolbarExpandableContent';
import { Popper } from '../../helpers';

export interface ToolbarToggleGroupProps extends ToolbarGroupProps {
/** Flag indicating when toggle group is expanded for non-managed toolbar toggle groups. */
Expand Down Expand Up @@ -50,12 +51,19 @@ export interface ToolbarToggleGroupProps extends ToolbarGroupProps {
xl?: 'spaceItemsNone' | 'spaceItemsSm' | 'spaceItemsMd' | 'spaceItemsLg';
'2xl'?: 'spaceItemsNone' | 'spaceItemsSm' | 'spaceItemsMd' | 'spaceItemsLg';
};
/** Reference to a custom expandable content group, for non-managed multiple toolbar toggle groups. */
expandableContentRef?: React.RefObject<any>;
/** Reference to a chip container group for filters inside the toolbar toggle group */
chipContainerRef?: React.RefObject<any>;
/** Optional callback for clearing all filters in the toolbar toggle group */
clearAllFilters?: () => void;
/** Flag indicating that the clear all filters button should be visible in the toolbar toggle group */
showClearFiltersButton?: boolean;
/** Text to display in the clear all filters button of the toolbar toggle group */
clearFiltersButtonText?: string;
}

export class ToolbarToggleGroup extends React.Component<ToolbarToggleGroupProps> {
static displayName = 'ToolbarToggleGroup';
toggleRef = React.createRef<HTMLButtonElement>();
expandableContentRef = React.createRef<HTMLDivElement>();

isContentPopup = () => {
const viewportSize = canUseDOM ? window.innerWidth : 1200;
Expand All @@ -75,8 +83,11 @@ export class ToolbarToggleGroup extends React.Component<ToolbarToggleGroupProps>
className,
children,
isExpanded,
expandableContentRef,
onToggle,
chipContainerRef,
clearAllFilters,
showClearFiltersButton,
clearFiltersButtonText,
...props
} = this.props;

Expand All @@ -89,27 +100,23 @@ export class ToolbarToggleGroup extends React.Component<ToolbarToggleGroupProps>
<PageContext.Consumer>
{({ width, getBreakpoint }) => (
<ToolbarContext.Consumer>
{({ isExpanded: managedIsExpanded, toggleIsExpanded: managedOnToggle }) => {
const _isExpanded = isExpanded !== undefined ? isExpanded : managedIsExpanded;
{({ toggleIsExpanded: managedOnToggle }) => {
const _onToggle = onToggle !== undefined ? onToggle : managedOnToggle;

return (
<ToolbarContentContext.Consumer>
{({ expandableContentRef: managedExpandableContentRef, expandableContentId }) => {
const _contentRef =
expandableContentRef !== undefined ? expandableContentRef : managedExpandableContentRef;

if (
isExpanded === undefined &&
managedExpandableContentRef.current &&
managedExpandableContentRef.current.classList
) {
if (_isExpanded) {
managedExpandableContentRef.current.classList.add(styles.modifiers.expanded);
} else {
managedExpandableContentRef.current.classList.remove(styles.modifiers.expanded);
}
}
{({
expandableContentRef,
expandableContentId,
chipContainerRef: managedChipContainerRef,
isExpanded: managedIsExpanded,
clearAllFilters: clearAllFiltersContext,
clearFiltersButtonText: clearFiltersButtonContext,
showClearFiltersButton: showClearFiltersButtonContext
}) => {
const _isExpanded = isExpanded !== undefined ? isExpanded : managedIsExpanded;
const _chipContainerRef =
chipContainerRef !== undefined ? chipContainerRef : managedChipContainerRef;

const breakpointMod: {
md?: 'show';
Expand All @@ -119,6 +126,36 @@ export class ToolbarToggleGroup extends React.Component<ToolbarToggleGroupProps>
} = {};
breakpointMod[breakpoint] = 'show';

const expandableContent = (
<ToolbarExpandableContent
id={expandableContentId}
expandableContentRef={this.expandableContentRef}
isExpanded={_isExpanded}
clearAllFilters={clearAllFilters || clearAllFiltersContext}
showClearFiltersButton={showClearFiltersButton || showClearFiltersButtonContext}
clearFiltersButtonText={clearFiltersButtonText || clearFiltersButtonContext}
chipContainerRef={_chipContainerRef}
>
{children}
</ToolbarExpandableContent>
);

const toggleButton = (
<div className={css(styles.toolbarToggle)}>
<Button
variant="plain"
onClick={_onToggle}
aria-label="Show Filters"
{...(_isExpanded && { 'aria-expanded': true })}
aria-haspopup={_isExpanded && this.isContentPopup()}
aria-controls={expandableContentId}
ref={this.toggleRef}
>
{toggleIcon}
</Button>
</div>
);

return (
<div
className={css(
Expand All @@ -135,24 +172,15 @@ export class ToolbarToggleGroup extends React.Component<ToolbarToggleGroupProps>
)}
{...props}
>
<div className={css(styles.toolbarToggle)}>
<Button
variant="plain"
onClick={_onToggle}
aria-label="Show Filters"
{...(_isExpanded && { 'aria-expanded': true })}
aria-haspopup={_isExpanded && this.isContentPopup()}
aria-controls={expandableContentId}
>
{toggleIcon}
</Button>
</div>
{_isExpanded
? (ReactDOM.createPortal(
children,
_contentRef.current.firstElementChild
) as React.ReactElement)
: children}
<Popper
appendTo={expandableContentRef.current}
triggerRef={expandableContentRef}
trigger={toggleButton}
popper={expandableContent}
popperRef={this.expandableContentRef}
isVisible={_isExpanded}
/>
{!_isExpanded && children}
</div>
);
}}
Expand Down
7 changes: 6 additions & 1 deletion packages/react-core/src/components/Toolbar/ToolbarUtils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,17 @@ interface ToolbarContentContextProps {
expandableContentRef: RefObject<HTMLDivElement>;
expandableContentId: string;
chipContainerRef: RefObject<any>;
isExpanded?: boolean;
clearAllFilters?: () => void;
clearFiltersButtonText?: string;
showClearFiltersButton?: boolean;
}

export const ToolbarContentContext = React.createContext<ToolbarContentContextProps>({
expandableContentRef: null,
expandableContentId: '',
chipContainerRef: null
chipContainerRef: null,
clearAllFilters: () => {}
});

export const globalBreakpoints = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,6 @@ exports[`ToolbarContent should match snapshot (auto-generated) 1`] = `
ReactNode
</div>
</div>
<div
class="pf-v5-c-toolbar__expandable-content"
id="string-expandable-content-0"
>
<div
class="pf-v5-c-toolbar__group"
/>
</div>
</div>
</DocumentFragment>
`;
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ describe('Toolbar', () => {
<ToolbarFilter
chips={['New', 'Pending']}
deleteChip={(category, chip) => {}}
deleteChipGroup={category => {}}
deleteChipGroup={(category) => {}}
categoryName="Status"
>
test content
Expand Down Expand Up @@ -102,8 +102,7 @@ describe('Toolbar', () => {
);

expect(asFragment()).toMatchSnapshot();
// Expecting 2 matches for text because the buttons also exist in hidden expandable content for mobile view
expect(screen.getAllByRole('button', { name: 'Save filters' }).length).toBe(2);
expect(screen.getAllByRole('button', { name: 'Clear all filters' }).length).toBe(2);
expect(screen.getAllByRole('button', { name: 'Save filters' }).length).toBe(1);
expect(screen.getAllByRole('button', { name: 'Clear all filters' }).length).toBe(1);
});
});
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
import React from 'react';
import { render } from '@testing-library/react';
import { ToolbarToggleGroup } from '../ToolbarToggleGroup';
import { ToolbarContentContext } from '../ToolbarUtils';
import { Toolbar } from '../Toolbar';
import { ToolbarContent } from '../ToolbarContent';

describe('ToolbarToggleGroup', () => {
it('should warn on bad props', () => {
const myMock = jest.fn() as any;
global.console = { error: myMock } as any;

const items = (
<React.Fragment>
<ToolbarToggleGroup breakpoint={undefined as 'xl'} toggleIcon={null}>
test
</ToolbarToggleGroup>
</React.Fragment>
);

render(
<ToolbarContentContext.Provider
value={{
expandableContentId: 'some-id',
expandableContentRef: { current: undefined },
chipContainerRef: { current: undefined }
}}
>
<ToolbarToggleGroup breakpoint={undefined as 'xl'} toggleIcon={null} />
</ToolbarContentContext.Provider>
<Toolbar id="toolbar-with-filter" className="pf-m-toggle-group-container" collapseListedFiltersBreakpoint="xl">
<ToolbarContent>{items}</ToolbarContent>
</Toolbar>
);

expect(myMock).toHaveBeenCalled();
Expand Down
Loading

0 comments on commit 512d9e7

Please sign in to comment.