diff --git a/packages/react-core/package.json b/packages/react-core/package.json index 8e3fa53a5d8..ab447103153 100644 --- a/packages/react-core/package.json +++ b/packages/react-core/package.json @@ -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" diff --git a/packages/react-core/src/components/OverflowMenu/OverflowMenu.tsx b/packages/react-core/src/components/OverflowMenu/OverflowMenu.tsx index c1e10cda2e1..ef881427587 100644 --- a/packages/react-core/src/components/OverflowMenu/OverflowMenu.tsx +++ b/packages/react-core/src/components/OverflowMenu/OverflowMenu.tsx @@ -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 { /** 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; + /** Indicates the overflow menu orientation is vertical and should respond to height changes instead of width. */ + isVertical?: boolean; } export interface OverflowMenuState extends React.HTMLProps { @@ -24,6 +27,11 @@ export interface OverflowMenuState extends React.HTMLProps { class OverflowMenu extends Component { static displayName = 'OverflowMenu'; + + static defaultProps: PickOptional = { + isVertical: false + }; + constructor(props: OverflowMenuProps) { super(props); this.state = { @@ -69,6 +77,15 @@ class OverflowMenu extends Component { } 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 @@ -83,14 +100,35 @@ class OverflowMenu extends Component { } }; + 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 ( -
+
{children} diff --git a/packages/react-core/src/components/OverflowMenu/examples/OverflowMenu.md b/packages/react-core/src/components/OverflowMenu/examples/OverflowMenu.md index e73498c856d..9e4a9b46877 100644 --- a/packages/react-core/src/components/OverflowMenu/examples/OverflowMenu.md +++ b/packages/react-core/src/components/OverflowMenu/examples/OverflowMenu.md @@ -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" @@ -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. @@ -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" + +``` diff --git a/packages/react-core/src/components/OverflowMenu/examples/OverflowMenuBreakpointOnContainerHeight.tsx b/packages/react-core/src/components/OverflowMenu/examples/OverflowMenuBreakpointOnContainerHeight.tsx new file mode 100644 index 00000000000..ab35002cf44 --- /dev/null +++ b/packages/react-core/src/components/OverflowMenu/examples/OverflowMenuBreakpointOnContainerHeight.tsx @@ -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(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 = [ + + Item 1 + , + + Item 2 + , + + Item 3 + , + + Item 4 + , + + Item 5 + + ]; + + return ( + <> + Current container height:{' '} + {containerHeight}% + +
+
+ + + Item 1 + Item 2 + + Item 3 + Item 4 + Item 5 + + + + ( + } + /> + )} + isOpen={isOpen} + onOpenChange={(isOpen) => setIsOpen(isOpen)} + > + {dropdownItems} + + + +
+
+ + ); +}; diff --git a/packages/react-core/src/components/OverflowMenu/examples/OverflowMenuSimpleVertical.tsx b/packages/react-core/src/components/OverflowMenu/examples/OverflowMenuSimpleVertical.tsx new file mode 100644 index 00000000000..7a57868d54e --- /dev/null +++ b/packages/react-core/src/components/OverflowMenu/examples/OverflowMenuSimpleVertical.tsx @@ -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 = [ + + Item 1 + , + + Item 2 + , + + Item 3 + , + + Item 4 + , + + Item 5 + + ]; + + return ( + + + Item + Item + + Item + Item + Item + + + + ( + } + /> + )} + isOpen={isOpen} + onOpenChange={(isOpen) => setIsOpen(isOpen)} + > + {dropdownItems} + + + + ); +}; diff --git a/packages/react-core/src/components/Toolbar/ToolbarContent.tsx b/packages/react-core/src/components/Toolbar/ToolbarContent.tsx index ec722df5a42..ee547c58acf 100644 --- a/packages/react-core/src/components/Toolbar/ToolbarContent.tsx +++ b/packages/react-core/src/components/Toolbar/ToolbarContent.tsx @@ -8,7 +8,7 @@ import { PageContext } from '../Page/PageContext'; export interface ToolbarContentProps extends React.HTMLProps { /** 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'; @@ -16,6 +16,14 @@ export interface ToolbarContentProps extends React.HTMLProps { 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'; @@ -59,6 +67,7 @@ class ToolbarContent extends Component { isExpanded, toolbarId, visibility, + visibilityAtHeight, rowWrap, alignItems, clearAllFilters, @@ -69,11 +78,12 @@ class ToolbarContent extends Component { return ( - {({ width, getBreakpoint }) => ( + {({ width, getBreakpoint, height, getVerticalBreakpoint }) => (
, | 'action-group-inline' | 'action-group-plain' | 'label-group'; - /** Visibility at various breakpoints. */ + /** Visibility at various width breakpoints. */ visibility?: { default?: 'hidden' | 'visible'; md?: 'hidden' | 'visible'; @@ -31,6 +31,14 @@ export interface ToolbarGroupProps extends Omit, 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'; + }; /** Applies to a child of a flex layout, and aligns that child (and any adjacent children on the other side of it) to one side of the main axis */ align?: { default?: 'alignEnd' | 'alignStart' | 'alignCenter'; @@ -178,6 +186,7 @@ class ToolbarGroupWithRef extends Component { render() { const { visibility, + visibilityAtHeight, align, alignItems, alignSelf, @@ -195,7 +204,7 @@ class ToolbarGroupWithRef extends Component { return ( - {({ width, getBreakpoint }) => ( + {({ width, getBreakpoint, height, getVerticalBreakpoint }) => (
{ | 'labelGroup' ], formatBreakpointMods(visibility, styles, '', getBreakpoint(width)), + formatBreakpointMods(visibilityAtHeight, styles, '', getVerticalBreakpoint(height), true), formatBreakpointMods(align, styles, '', getBreakpoint(width)), formatBreakpointMods(gap, styles, '', getBreakpoint(width)), formatBreakpointMods(columnGap, styles, '', getBreakpoint(width)), diff --git a/packages/react-core/src/components/Toolbar/ToolbarItem.tsx b/packages/react-core/src/components/Toolbar/ToolbarItem.tsx index 687654455ac..454ea9476e7 100644 --- a/packages/react-core/src/components/Toolbar/ToolbarItem.tsx +++ b/packages/react-core/src/components/Toolbar/ToolbarItem.tsx @@ -17,7 +17,7 @@ export interface ToolbarItemProps extends React.HTMLProps { className?: string; /** A type modifier which modifies spacing specifically depending on the type of item */ variant?: ToolbarItemVariant | 'pagination' | 'label' | 'label-group' | 'separator' | 'expand-all'; - /** Visibility at various breakpoints. */ + /** Visibility at various width breakpoints. */ visibility?: { default?: 'hidden' | 'visible'; md?: 'hidden' | 'visible'; @@ -25,6 +25,14 @@ export interface ToolbarItemProps extends React.HTMLProps { 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'; + }; /** Applies to a child of a flex layout, and aligns that child (and any adjacent children on the other side of it) to one side of the main axis */ align?: { default?: 'alignEnd' | 'alignStart' | 'alignCenter'; @@ -174,6 +182,7 @@ export const ToolbarItem: React.FunctionComponent = ({ className, variant, visibility, + visibilityAtHeight, gap, columnGap, rowGap, @@ -202,7 +211,7 @@ export const ToolbarItem: React.FunctionComponent = ({ return ( - {({ width, getBreakpoint }) => ( + {({ width, getBreakpoint, height, getVerticalBreakpoint }) => (
= ({ isAllExpanded && styles.modifiers.expanded, isOverflowContainer && styles.modifiers.overflowContainer, formatBreakpointMods(visibility, styles, '', getBreakpoint(width)), + formatBreakpointMods(visibilityAtHeight, styles, '', getVerticalBreakpoint(height), true), formatBreakpointMods(align, styles, '', getBreakpoint(width)), formatBreakpointMods(gap, styles, '', getBreakpoint(width)), formatBreakpointMods(columnGap, styles, '', getBreakpoint(width)), diff --git a/packages/react-core/src/components/Toolbar/examples/Toolbar.md b/packages/react-core/src/components/Toolbar/examples/Toolbar.md index 89639b516c7..e6251177ae5 100644 --- a/packages/react-core/src/components/Toolbar/examples/Toolbar.md +++ b/packages/react-core/src/components/Toolbar/examples/Toolbar.md @@ -34,6 +34,14 @@ To adjust a toolbar’s inset, use the `inset` property. You can set the inset v ``` +### Vertical toolbar + +A toolbar's orientation may be changed using the `isVertical` property. Responsive behavior when height is adjusted may be customized for the `ToolbarContent`, `ToolbarGroup`, and `ToolbarItem` components using their respective `visibilityAtHeight` property. + +```ts file="./ToolbarVertical.tsx" + +``` + ### Sticky toolbar To lock a toolbar and prevent it from scrolling with other content, use a sticky toolbar. @@ -114,11 +122,13 @@ When all of a toolbar's required elements cannot fit in a single line, you can s ``` ## Examples with spacers and wrapping + You may adjust the space between toolbar items to arrange them into groups. Read our spacers documentation to learn more about using spacers. Items are spaced “16px” apart by default and can be modified by changing their or their parents' `gap`, `columnGap`, and `rowGap` properties. You can set the property values at multiple breakpoints, including "default", "md", "lg", "xl", and "2xl". ### Toolbar content wrapping + The toolbar content section will wrap by default, but you can set the `rowRap` property to `noWrap` to make it not wrap. ```ts file="./ToolbarContentWrap.tsx" diff --git a/packages/react-core/src/components/Toolbar/examples/ToolbarVertical.tsx b/packages/react-core/src/components/Toolbar/examples/ToolbarVertical.tsx new file mode 100644 index 00000000000..c550275d8c1 --- /dev/null +++ b/packages/react-core/src/components/Toolbar/examples/ToolbarVertical.tsx @@ -0,0 +1,22 @@ +import { Toolbar, ToolbarContent, ToolbarGroup, ToolbarItem } from '@patternfly/react-core'; + +export const ToolbarVertical: React.FunctionComponent = () => ( + + + + Item 1 + Item 2 + Item 3 + + + Item 4 (visible above md breakpoint) + + Item 5 (visible above lg breakpoint) + + + Item 6 (visible above md breakpoint) + + + + +); diff --git a/packages/react-docs/package.json b/packages/react-docs/package.json index 89a46395487..4fd566ccdff 100644 --- a/packages/react-docs/package.json +++ b/packages/react-docs/package.json @@ -23,7 +23,7 @@ "test:a11y": "patternfly-a11y --config patternfly-a11y.config" }, "dependencies": { - "@patternfly/patternfly": "6.5.0-prerelease.67", + "@patternfly/patternfly": "6.5.0-prerelease.68", "@patternfly/react-charts": "workspace:^", "@patternfly/react-code-editor": "workspace:^", "@patternfly/react-core": "workspace:^", diff --git a/packages/react-icons/package.json b/packages/react-icons/package.json index 1e2fac4da48..b2d60bbae2f 100644 --- a/packages/react-icons/package.json +++ b/packages/react-icons/package.json @@ -35,7 +35,7 @@ "@fortawesome/free-brands-svg-icons": "^5.15.4", "@fortawesome/free-regular-svg-icons": "^5.15.4", "@fortawesome/free-solid-svg-icons": "^5.15.4", - "@patternfly/patternfly": "6.5.0-prerelease.67", + "@patternfly/patternfly": "6.5.0-prerelease.68", "@rhds/icons": "^2.1.0", "fs-extra": "^11.3.3", "tslib": "^2.8.1" diff --git a/packages/react-styles/package.json b/packages/react-styles/package.json index 2b26426e53d..293b2fa6bab 100644 --- a/packages/react-styles/package.json +++ b/packages/react-styles/package.json @@ -19,7 +19,7 @@ "clean": "rimraf dist css" }, "devDependencies": { - "@patternfly/patternfly": "6.5.0-prerelease.67", + "@patternfly/patternfly": "6.5.0-prerelease.68", "change-case": "^5.4.4", "fs-extra": "^11.3.3" }, diff --git a/packages/react-tokens/package.json b/packages/react-tokens/package.json index 8b8e672dd44..9d260e8f299 100644 --- a/packages/react-tokens/package.json +++ b/packages/react-tokens/package.json @@ -30,7 +30,7 @@ }, "devDependencies": { "@adobe/css-tools": "^4.4.4", - "@patternfly/patternfly": "6.5.0-prerelease.67", + "@patternfly/patternfly": "6.5.0-prerelease.68", "fs-extra": "^11.3.3" } } diff --git a/yarn.lock b/yarn.lock index 7964640177c..a3af04741fb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5070,10 +5070,10 @@ __metadata: languageName: node linkType: hard -"@patternfly/patternfly@npm:6.5.0-prerelease.67": - version: 6.5.0-prerelease.67 - resolution: "@patternfly/patternfly@npm:6.5.0-prerelease.67" - checksum: 10c0/7e42179e955ef0b300a8814925d59482ca67c87e1018fe350be9875691da86c49d61d5fc1ffc1f37275dc524605351686dd462ee1d0d6703b477308ed75a7c88 +"@patternfly/patternfly@npm:6.5.0-prerelease.68": + version: 6.5.0-prerelease.68 + resolution: "@patternfly/patternfly@npm:6.5.0-prerelease.68" + checksum: 10c0/ed1f97fe31ec343a40df4b35255a487c87db6e51311aac79a0373e06851ea537b6658de7743cac2b7463f6496210d51ad017d7bb77e433922794d995b8df648c languageName: node linkType: hard @@ -5171,7 +5171,7 @@ __metadata: version: 0.0.0-use.local resolution: "@patternfly/react-core@workspace:packages/react-core" dependencies: - "@patternfly/patternfly": "npm:6.5.0-prerelease.67" + "@patternfly/patternfly": "npm:6.5.0-prerelease.68" "@patternfly/react-icons": "workspace:^" "@patternfly/react-styles": "workspace:^" "@patternfly/react-tokens": "workspace:^" @@ -5192,7 +5192,7 @@ __metadata: resolution: "@patternfly/react-docs@workspace:packages/react-docs" dependencies: "@patternfly/documentation-framework": "npm:^6.36.8" - "@patternfly/patternfly": "npm:6.5.0-prerelease.67" + "@patternfly/patternfly": "npm:6.5.0-prerelease.68" "@patternfly/patternfly-a11y": "npm:5.1.0" "@patternfly/react-charts": "workspace:^" "@patternfly/react-code-editor": "workspace:^" @@ -5232,7 +5232,7 @@ __metadata: "@fortawesome/free-brands-svg-icons": "npm:^5.15.4" "@fortawesome/free-regular-svg-icons": "npm:^5.15.4" "@fortawesome/free-solid-svg-icons": "npm:^5.15.4" - "@patternfly/patternfly": "npm:6.5.0-prerelease.67" + "@patternfly/patternfly": "npm:6.5.0-prerelease.68" "@rhds/icons": "npm:^2.1.0" fs-extra: "npm:^11.3.3" tslib: "npm:^2.8.1" @@ -5319,7 +5319,7 @@ __metadata: version: 0.0.0-use.local resolution: "@patternfly/react-styles@workspace:packages/react-styles" dependencies: - "@patternfly/patternfly": "npm:6.5.0-prerelease.67" + "@patternfly/patternfly": "npm:6.5.0-prerelease.68" change-case: "npm:^5.4.4" fs-extra: "npm:^11.3.3" languageName: unknown @@ -5361,7 +5361,7 @@ __metadata: resolution: "@patternfly/react-tokens@workspace:packages/react-tokens" dependencies: "@adobe/css-tools": "npm:^4.4.4" - "@patternfly/patternfly": "npm:6.5.0-prerelease.67" + "@patternfly/patternfly": "npm:6.5.0-prerelease.68" fs-extra: "npm:^11.3.3" languageName: unknown linkType: soft