Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
40 changes: 36 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 */
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,29 @@ class OverflowMenu extends Component<OverflowMenuProps, OverflowMenuState> {
}
};

handleResizeHeight = () => {
const breakpointHeight = globalHeightBreakpoints[this.props.breakpoint];
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,12 @@ import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-ico

```

### Simple vertical (responsive)

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

```

### Group types

```ts file="./OverflowMenuGroupTypes.tsx"
Expand All @@ -45,7 +51,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 +60,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 relative to the height of the reference container rather than the viewport height.

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

```
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
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 (
<>
<div style={{ height: '100%', maxHeight: '400px' }}>
<div>
<span id="overflowMenu-hasBreakpointOnContainer-height-slider-label">Current container height</span>:{' '}
{containerHeight}%
</div>
<Slider
value={containerHeight}
onChange={onChange}
max={100}
min={20}
step={20}
showTicks
showBoundaries={false}
aria-labelledby="overflowMenu-hasBreakpointOnContainer-height-slider-label"
/>
</div>
<div ref={containerRef} id="height-breakpoint-reference-container" style={containerStyles}>
<OverflowMenu breakpointReference={containerRef} breakpoint="sm" 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>
</>
);
};
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