Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(pageheader): compensate the width of the overflow menu #5929

Merged
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
26 changes: 24 additions & 2 deletions packages/ibm-products/src/components/ActionBar/ActionBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import React, {
useRef,
PropsWithChildren,
Ref,
ForwardedRef,
} from 'react';

// Other standard imports.
Expand Down Expand Up @@ -85,6 +86,10 @@ interface ActionBarProps extends PropsWithChildren {
* overflowAriaLabel label for open close button overflow used for action bar items that do nto fit.
*/
overflowAriaLabel: string;
/**
* overflowMenuRef for the overflow menu width that is needed to calculate the width of the action bar with overflow
*/
overflowMenuRef?: ForwardedRef<HTMLDivElement>;
/**
* align tags to right of available space
*/
Expand All @@ -107,6 +112,7 @@ export let ActionBar = React.forwardRef(
menuOptionsClass,
onWidthChange,
overflowAriaLabel,
overflowMenuRef,
rightAlign,

// Collect any other property values passed in.
Expand Down Expand Up @@ -139,6 +145,7 @@ export let ActionBar = React.forwardRef(
<ActionBarOverflowItems
className={`${blockClass}__hidden-sizing-item`}
overflowAriaLabel="hidden sizing overflow items"
overflowMenuRef={overflowMenuRef}
overflowItems={[]}
key="hidden-overflow-menu"
tabIndex={-1}
Expand All @@ -156,7 +163,7 @@ export let ActionBar = React.forwardRef(
</span>
</div>
);
}, [actions]);
}, [actions, overflowMenuRef]);

// creates displayed items based on actions, displayCount and alignment
useEffect(() => {
Expand All @@ -174,14 +181,21 @@ export let ActionBar = React.forwardRef(
<ActionBarOverflowItems
menuOptionsClass={menuOptionsClass}
overflowAriaLabel={overflowAriaLabel}
overflowMenuRef={overflowMenuRef}
overflowItems={newOverflowItems}
key={`overflow-menu-${internalId.current}`}
/>
);
}

setDisplayedItems(newDisplayedItems);
}, [actions, displayCount, overflowAriaLabel, menuOptionsClass]);
}, [
actions,
displayCount,
overflowAriaLabel,
menuOptionsClass,
overflowMenuRef,
]);

// determine display count based on space available and width of pageActions
const checkFullyVisibleItems = () => {
Expand Down Expand Up @@ -354,6 +368,14 @@ ActionBar.propTypes = {
* overflowAriaLabel label for open close button overflow used for action bar items that do nto fit.
*/
overflowAriaLabel: PropTypes.string.isRequired,
/**
* overflowMenuRef for the overflow menu width that is needed to calculate the width of the action bar with overflow
*/
/**@ts-ignore */
overflowMenuRef: PropTypes.oneOfType([
PropTypes.shape({ current: PropTypes.elementType }),
PropTypes.object,
]),
/**
* align tags to right of available space
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@
//

// Import portions of React that are needed.
import React, { useRef, PropsWithChildren, ReactElement } from 'react';
import React, {
useRef,
PropsWithChildren,
ReactElement,
ForwardedRef,
} from 'react';

// Other standard imports.
import PropTypes from 'prop-types';
Expand Down Expand Up @@ -38,9 +43,13 @@ interface ActionBarOverflowItemProps extends PropsWithChildren {
*/
menuOptionsClass?: string;
/**
* overflowAriaLabel label for open close button overflow used for action bar items that do nto fit.
* overflowAriaLabel label for open close button overflow used for action bar items that do not fit.
*/
overflowAriaLabel?: string;
/**
* overflowMenuRef for the overflow menu width that is needed to calculate the width of the action bar with overflow
*/
overflowMenuRef?: ForwardedRef<HTMLDivElement>;
/**
* overflowItems: items to bre shown in the ActionBar overflow menu
*/
Expand All @@ -57,48 +66,52 @@ export const ActionBarOverflowItems = ({
menuOptionsClass,
overflowItems,
overflowAriaLabel,
overflowMenuRef,
}: ActionBarOverflowItemProps) => {
const internalId = useRef(uuidv4());

return (
<OverflowMenu
aria-label={overflowAriaLabel}
className={cx(blockClass, className)}
direction="bottom"
flipped
iconDescription={overflowAriaLabel} // also needs setting to avoid a11y "Accessible name does not match or contain the visible label text"
menuOptionsClass={cx(`${blockClass}__options`, menuOptionsClass)}
>
{React.Children.map(overflowItems, (item, index) => {
// This uses a copy of a menu item option
// NOTE: Cannot use a real Tooltip icon below as it uses a <button /> the
// div equivalent below is based on Carbon 10.25.0
const ItemIcon = item?.props.renderIcon as React.ComponentType<any>;
return (
<OverflowMenuItem
className={`${blockClass}__item`}
onClick={item?.props.onClick}
itemText={
<div
className={`${blockClass}__item-content`}
aria-describedby={`${internalId.current}-${index}--item-label`}
>
<span
className={`${blockClass}__item-label`}
id={`${internalId.current}-${index}--item-label`}
<div ref={overflowMenuRef}>
<OverflowMenu
aria-label={overflowAriaLabel}
className={cx(blockClass, className)}
direction="bottom"
flipped
iconDescription={overflowAriaLabel} // also needs setting to avoid a11y "Accessible name does not match or contain the visible label text"
menuOptionsClass={cx(`${blockClass}__options`, menuOptionsClass)}
>
{React.Children.map(overflowItems, (item, index) => {
// This uses a copy of a menu item option
// NOTE: Cannot use a real Tooltip icon below as it uses a <button /> the
// div equivalent below is based on Carbon 10.25.0
const ItemIcon = item?.props.renderIcon as React.ComponentType<any>;
return (
<OverflowMenuItem
className={`${blockClass}__item`}
onClick={item?.props.onClick}
itemText={
<div
className={`${blockClass}__item-content`}
aria-describedby={`${internalId.current}-${index}--item-label`}
>
{item?.props.label}
</span>
{typeof item?.props.renderIcon === 'function' ? (
<ItemIcon />
) : (
item?.props.renderIcon
)}
</div>
}
/>
);
})}
</OverflowMenu>
<span
className={`${blockClass}__item-label`}
id={`${internalId.current}-${index}--item-label`}
>
{item?.props.label}
</span>
{typeof item?.props.renderIcon === 'function' ? (
<ItemIcon />
) : (
item?.props.renderIcon
)}
</div>
}
/>
);
})}
</OverflowMenu>
</div>
);
};

Expand All @@ -118,11 +131,21 @@ ActionBarOverflowItems.propTypes = {
* overflowAriaLabel label for open close button overflow used for action bar items that do nto fit.
*/
overflowAriaLabel: PropTypes.string,

/**
* overflowItems: items to bre shown in the ActionBar overflow menu
*/
overflowItems: PropTypes.arrayOf(PropTypes.element),

/**
* overflowMenuRef for the overflow menu width that is needed to calculate the width of the action bar with overflow
*/
/**@ts-ignore */
overflowMenuRef: PropTypes.oneOfType([
PropTypes.shape({ current: PropTypes.elementType }),
PropTypes.object,
]),

/**
* Optional tab index
*/
Expand Down
26 changes: 24 additions & 2 deletions packages/ibm-products/src/components/PageHeader/PageHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,15 @@
// LICENSE file in the root directory of this source tree.
//

import { Button, Column, FlexGrid, Row, Tag, Tooltip } from '@carbon/react';
import {
Button,
Column,
FlexGrid,
Row,
Tag,
Tooltip,
usePrefix,
} from '@carbon/react';
import { ButtonProps, PopoverAlignment, TagProps } from '@carbon/type';
import React, {
ForwardedRef,
Expand Down Expand Up @@ -454,6 +462,7 @@ export let PageHeader = React.forwardRef(
const sizingContainerRef: MutableRefObject<HTMLDivElement | null> =
useRef(null);
const offsetTopMeasuringRef = useRef(null);
const overflowMenuRef = useRef<HTMLDivElement>(null);

// state based on props only
const hasActionBar = actionBarItems && actionBarItems.length > 0;
Expand Down Expand Up @@ -508,11 +517,23 @@ export let PageHeader = React.forwardRef(
const [fullyCollapsed, setFullyCollapsed] = useState(false);
const [widthIsNarrow, setWidthIsNarrow] = useState(false);

const prefix = usePrefix();

// handlers
const handleActionBarWidthChange = ({ minWidth, maxWidth }) => {
let overflowMenuWidth = 0;

const overflowMenu = overflowMenuRef?.current?.querySelector(
`.${prefix}--overflow-menu`
);

if (overflowMenu) {
overflowMenuWidth = (overflowMenu as HTMLDivElement).offsetWidth;
}

/* don't know how to test resize */
/* istanbul ignore next */
setActionBarMaxWidth(maxWidth);
setActionBarMaxWidth(maxWidth + overflowMenuWidth);
/* don't know how to test resize */
/* istanbul ignore next */
setActionBarMinWidth(minWidth);
Expand Down Expand Up @@ -953,6 +974,7 @@ export let PageHeader = React.forwardRef(
)}`,
onWidthChange: handleActionBarWidthChange,
overflowAriaLabel: actionBarOverflowAriaLabel,
overflowMenuRef,
rightAlign: true,
} as any)}
/>
Expand Down
Loading