Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/react-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
"tslib": "^2.8.1"
},
"devDependencies": {
"@patternfly/patternfly": "6.5.0-prerelease.67",
"@patternfly/patternfly": "6.5.0-prerelease.68",
"case-anything": "^3.1.2",
"css": "^3.0.0",
"fs-extra": "^11.3.3"
Expand Down
46 changes: 42 additions & 4 deletions packages/react-core/src/components/OverflowMenu/OverflowMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,21 @@ import styles from '@patternfly/react-styles/css/components/OverflowMenu/overflo
import { css } from '@patternfly/react-styles';
import { OverflowMenuContext } from './OverflowMenuContext';
import { debounce } from '../../helpers/util';
import { globalWidthBreakpoints } from '../../helpers/constants';
import { globalWidthBreakpoints, globalHeightBreakpoints } from '../../helpers/constants';
import { getResizeObserver } from '../../helpers/resizeObserver';
import { PickOptional } from '../../helpers/typeUtils';

export interface OverflowMenuProps extends React.HTMLProps<HTMLDivElement> {
/** Any elements that can be rendered in the menu */
children?: any;
/** Additional classes added to the OverflowMenu. */
className?: string;
/** Indicates breakpoint at which to switch between horizontal menu and vertical dropdown */
/** Indicates breakpoint at which to switch between expanded and collapsed states. The "sm" breakpoint does not apply to vertical overflow menus. */
breakpoint: 'sm' | 'md' | 'lg' | 'xl' | '2xl';
/** A container reference to base the specified breakpoint on instead of the viewport width. */
breakpointReference?: HTMLElement | (() => HTMLElement) | React.RefObject<any>;
/** Indicates the overflow menu orientation is vertical and should respond to height changes instead of width. */
isVertical?: boolean;
}

export interface OverflowMenuState extends React.HTMLProps<HTMLDivElement> {
Expand All @@ -24,6 +27,11 @@ export interface OverflowMenuState extends React.HTMLProps<HTMLDivElement> {

class OverflowMenu extends Component<OverflowMenuProps, OverflowMenuState> {
static displayName = 'OverflowMenu';

static defaultProps: PickOptional<OverflowMenuProps> = {
isVertical: false
};

constructor(props: OverflowMenuProps) {
super(props);
this.state = {
Expand Down Expand Up @@ -69,6 +77,15 @@ class OverflowMenu extends Component<OverflowMenuProps, OverflowMenuState> {
}

handleResize = () => {
const { isVertical } = this.props;
if (isVertical) {
this.handleResizeHeight();
} else {
this.handleResizeWidth();
}
};

handleResizeWidth = () => {
const breakpointWidth = globalWidthBreakpoints[this.props.breakpoint];
if (!breakpointWidth) {
// eslint-disable-next-line no-console
Expand All @@ -83,14 +100,35 @@ class OverflowMenu extends Component<OverflowMenuProps, OverflowMenuState> {
}
};

handleResizeHeight = () => {
const breakpointHeight = globalHeightBreakpoints[this.props.breakpoint];
if (breakpointHeight === 0) {
// eslint-disable-next-line no-console
console.warn('The "sm" breakpoint does not apply to vertical overflow menus.');
return;
}

if (!breakpointHeight) {
// eslint-disable-next-line no-console
console.error('OverflowMenu will not be visible without a valid breakpoint.');
return;
}

const relativeHeight = this.state.breakpointRef ? this.state.breakpointRef.clientHeight : window.innerHeight;
const isBelowBreakpoint = relativeHeight < breakpointHeight;
if (this.state.isBelowBreakpoint !== isBelowBreakpoint) {
this.setState({ isBelowBreakpoint });
}
};

handleResizeWithDelay = debounce(this.handleResize, 250);

render() {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { className, breakpoint, children, breakpointReference, ...props } = this.props;
const { className, breakpoint, children, breakpointReference, isVertical, ...props } = this.props;

return (
<div {...props} className={css(styles.overflowMenu, className)}>
<div {...props} className={css(styles.overflowMenu, isVertical && styles.modifiers.vertical, className)}>
<OverflowMenuContext.Provider value={{ isBelowBreakpoint: this.state.isBelowBreakpoint }}>
{children}
</OverflowMenuContext.Provider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-ico

```

### Vertical

Passing `isVertical` to `OverflowMenu` will change its behavior to respond to breakpoints based on window height instead of width.

```ts file="./OverflowMenuSimpleVertical.tsx"

```

### Group types

```ts file="./OverflowMenuGroupTypes.tsx"
Expand All @@ -45,7 +53,7 @@ import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-ico

```

### Breakpoint on container
### Breakpoint on container width

By passing in the `breakpointReference` property, the overflow menu's breakpoint will be relative to the width of the reference container rather than the viewport width.

Expand All @@ -54,3 +62,11 @@ You can change the container width in this example by adjusting the slider. As t
```ts file="./OverflowMenuBreakpointOnContainer.tsx"

```

### Breakpoint on container height

By passing in the `breakpointReference` and `isVertical` properties, the overflow menu's breakpoint will be determined relative to the height of the reference container rather than the window height.

```ts isFullscreen file="./OverflowMenuBreakpointOnContainerHeight.tsx"

```
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { useRef, useState } from 'react';
import {
OverflowMenu,
OverflowMenuControl,
OverflowMenuContent,
OverflowMenuGroup,
OverflowMenuItem,
OverflowMenuDropdownItem,
MenuToggle,
Slider,
SliderOnChangeEvent,
Dropdown,
DropdownList
} from '@patternfly/react-core';
import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon';

export const OverflowMenuBreakpointOnContainerHeight: React.FunctionComponent = () => {
const [isOpen, setIsOpen] = useState(false);
const [containerHeight, setContainerHeight] = useState(100);
const containerRef = useRef<HTMLDivElement>(null);

const onToggle = () => {
setIsOpen(!isOpen);
};

const onSelect = () => {
setIsOpen(!isOpen);
};

const onChange = (_event: SliderOnChangeEvent, value: number) => {
setContainerHeight(value);
};

const containerStyles = {
height: `${containerHeight}%`,
padding: '1rem',
borderWidth: '2px',
borderStyle: 'dashed'
};

const dropdownItems = [
<OverflowMenuDropdownItem itemId={0} key="item1" isShared>
Item 1
</OverflowMenuDropdownItem>,
<OverflowMenuDropdownItem itemId={1} key="item2" isShared>
Item 2
</OverflowMenuDropdownItem>,
<OverflowMenuDropdownItem itemId={2} key="item3" isShared>
Item 3
</OverflowMenuDropdownItem>,
<OverflowMenuDropdownItem itemId={3} key="item4" isShared>
Item 4
</OverflowMenuDropdownItem>,
<OverflowMenuDropdownItem itemId={4} key="item5" isShared>
Item 5
</OverflowMenuDropdownItem>
];

return (
<>
<span id="overflowMenu-hasBreakpointOnContainer-height-slider-label">Current container height</span>:{' '}
{containerHeight}%
<Slider
value={containerHeight}
onChange={onChange}
max={100}
min={20}
step={20}
showTicks
showBoundaries={false}
aria-labelledby="overflowMenu-hasBreakpointOnContainer-height-slider-label"
/>
<div style={{ height: '100%' }}>
<div ref={containerRef} id="height-breakpoint-reference-container" style={containerStyles}>
<OverflowMenu breakpointReference={containerRef} breakpoint="md" isVertical>
<OverflowMenuContent>
<OverflowMenuItem>Item 1</OverflowMenuItem>
<OverflowMenuItem>Item 2</OverflowMenuItem>
<OverflowMenuGroup>
<OverflowMenuItem>Item 3</OverflowMenuItem>
<OverflowMenuItem>Item 4</OverflowMenuItem>
<OverflowMenuItem>Item 5</OverflowMenuItem>
</OverflowMenuGroup>
</OverflowMenuContent>
<OverflowMenuControl>
<Dropdown
onSelect={onSelect}
toggle={(toggleRef) => (
<MenuToggle
ref={toggleRef}
aria-label="Height breakpoint on container example overflow menu"
variant="plain"
onClick={onToggle}
isExpanded={isOpen}
icon={<EllipsisVIcon />}
/>
)}
isOpen={isOpen}
onOpenChange={(isOpen) => setIsOpen(isOpen)}
>
<DropdownList>{dropdownItems}</DropdownList>
</Dropdown>
</OverflowMenuControl>
</OverflowMenu>
</div>
</div>
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { useState } from 'react';
import {
OverflowMenu,
OverflowMenuControl,
OverflowMenuContent,
OverflowMenuGroup,
OverflowMenuItem,
OverflowMenuDropdownItem,
MenuToggle,
Dropdown,
DropdownList
} from '@patternfly/react-core';
import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon';

export const OverflowMenuSimpleVertical: React.FunctionComponent = () => {
const [isOpen, setIsOpen] = useState(false);

const onToggle = () => {
setIsOpen(!isOpen);
};

const onSelect = () => {
setIsOpen(!isOpen);
};

const dropdownItems = [
<OverflowMenuDropdownItem itemId={0} key="item1" isShared>
Item 1
</OverflowMenuDropdownItem>,
<OverflowMenuDropdownItem itemId={1} key="item2" isShared>
Item 2
</OverflowMenuDropdownItem>,
<OverflowMenuDropdownItem itemId={2} key="item3" isShared>
Item 3
</OverflowMenuDropdownItem>,
<OverflowMenuDropdownItem itemId={3} key="item4" isShared>
Item 4
</OverflowMenuDropdownItem>,
<OverflowMenuDropdownItem itemId={5} key="item5" isShared>
Item 5
</OverflowMenuDropdownItem>
];

return (
<OverflowMenu breakpoint="lg" isVertical>
<OverflowMenuContent>
<OverflowMenuItem>Item</OverflowMenuItem>
<OverflowMenuItem>Item</OverflowMenuItem>
<OverflowMenuGroup>
<OverflowMenuItem>Item</OverflowMenuItem>
<OverflowMenuItem>Item</OverflowMenuItem>
<OverflowMenuItem>Item</OverflowMenuItem>
</OverflowMenuGroup>
</OverflowMenuContent>
<OverflowMenuControl>
<Dropdown
onSelect={onSelect}
toggle={(toggleRef) => (
<MenuToggle
ref={toggleRef}
aria-label="Simple example overflow menu"
variant="plain"
onClick={onToggle}
isExpanded={isOpen}
icon={<EllipsisVIcon />}
/>
)}
isOpen={isOpen}
onOpenChange={(isOpen) => setIsOpen(isOpen)}
>
<DropdownList>{dropdownItems}</DropdownList>
</Dropdown>
</OverflowMenuControl>
</OverflowMenu>
);
};
14 changes: 12 additions & 2 deletions packages/react-core/src/components/Toolbar/ToolbarContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,22 @@ import { PageContext } from '../Page/PageContext';
export interface ToolbarContentProps extends React.HTMLProps<HTMLDivElement> {
/** Classes applied to root element of the data toolbar content row */
className?: string;
/** Visibility at various breakpoints. */
/** Visibility at various width breakpoints. */
visibility?: {
default?: 'hidden' | 'visible';
md?: 'hidden' | 'visible';
lg?: 'hidden' | 'visible';
xl?: 'hidden' | 'visible';
'2xl'?: 'hidden' | 'visible';
};
/** Visibility at various height breakpoints. */
visibilityAtHeight?: {
default?: 'hidden' | 'visible';
md?: 'hidden' | 'visible';
lg?: 'hidden' | 'visible';
xl?: 'hidden' | 'visible';
'2xl'?: 'hidden' | 'visible';
};
/** Value to set for content wrapping at various breakpoints */
rowWrap?: {
default?: 'wrap' | 'nowrap';
Expand Down Expand Up @@ -59,6 +67,7 @@ class ToolbarContent extends Component<ToolbarContentProps> {
isExpanded,
toolbarId,
visibility,
visibilityAtHeight,
rowWrap,
alignItems,
clearAllFilters,
Expand All @@ -69,11 +78,12 @@ class ToolbarContent extends Component<ToolbarContentProps> {

return (
<PageContext.Consumer>
{({ width, getBreakpoint }) => (
{({ width, getBreakpoint, height, getVerticalBreakpoint }) => (
<div
className={css(
styles.toolbarContent,
formatBreakpointMods(visibility, styles, '', getBreakpoint(width)),
formatBreakpointMods(visibilityAtHeight, styles, '', getVerticalBreakpoint(height), true),
className
)}
ref={this.expandableContentRef}
Expand Down
Loading
Loading