Skip to content

Commit

Permalink
feat: add onOverflowChange callback (#33106)
Browse files Browse the repository at this point in the history
  • Loading branch information
ling1726 authored Oct 25, 2024
1 parent ccc2680 commit 84f915c
Show file tree
Hide file tree
Showing 7 changed files with 194 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "feat: add `onOverflowChange` callback",
"packageName": "@fluentui/react-overflow",
"email": "lingfangao@hotmail.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,14 @@ export const DATA_OVERFLOW_MENU = "data-overflow-menu";
// @public (undocumented)
export const DATA_OVERFLOWING = "data-overflowing";

// @public (undocumented)
export interface OnOverflowChangeData extends OverflowState {
}

// @public
export const Overflow: React_2.ForwardRefExoticComponent<Partial<Pick<ObserveOptions, "padding" | "overflowDirection" | "overflowAxis" | "minimumVisible">> & {
children: React_2.ReactElement;
onOverflowChange?: ((ev: null, data: OverflowState) => void) | undefined;
} & React_2.RefAttributes<unknown>>;

// @public
Expand All @@ -46,6 +51,7 @@ export type OverflowItemProps = {
// @public
export type OverflowProps = Partial<Pick<ObserveOptions, 'overflowAxis' | 'overflowDirection' | 'padding' | 'minimumVisible'>> & {
children: React_2.ReactElement;
onOverflowChange?: (ev: null, data: OverflowState) => void;
};

// @public (undocumented)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
useIsOverflowGroupVisible,
useOverflowMenu,
useOverflowContext,
type OnOverflowChangeData,
} from '@fluentui/react-overflow';
import { Portal } from '@fluentui/react-portal';
import { OverflowAxis } from '@fluentui/priority-overflow';
Expand Down Expand Up @@ -1066,4 +1067,47 @@ describe('Overflow', () => {
cy.get('#toggle').click();
}
});

it('shoud call onOverflowChange', () => {
const itemCount = 10;
const mapHelper = new Array(itemCount).fill(0).map((_, i) => i);
let latestUpdate: OnOverflowChangeData | undefined;
mount(
<Container onOverflowChange={(e, data) => (latestUpdate = data)}>
{mapHelper.map(i => (
<Item key={i} id={i.toString()}>
{i}
</Item>
))}
<Menu />
</Container>,
);
const overflowCases = [
{ containerSize: 450, overflowCount: 2 },
{ containerSize: 400, overflowCount: 3 },
{ containerSize: 350, overflowCount: 4 },
{ containerSize: 300, overflowCount: 5 },
{ containerSize: 250, overflowCount: 6 },
{ containerSize: 200, overflowCount: 7 },
{ containerSize: 150, overflowCount: 8 },
{ containerSize: 100, overflowCount: 9 },
{ containerSize: 50, overflowCount: 10 },
];

overflowCases.forEach(({ overflowCount, containerSize }) => {
setContainerWidth(containerSize);
cy.get(`[${selectors.menu}]`).should('have.text', `+${overflowCount}`);
cy.then(() => {
expect(latestUpdate?.hasOverflow).to.equal(true);
const visibleBoundary = itemCount - overflowCount;
for (let i = 0; i < visibleBoundary; i++) {
expect(latestUpdate?.itemVisibility[i.toString()]).to.equal(true);
}

for (let i = visibleBoundary; i < itemCount; i++) {
expect(latestUpdate?.itemVisibility[i.toString()]).to.equal(false);
}
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,19 @@ interface OverflowState {
groupVisibility: Record<string, OverflowGroupState>;
}

export interface OnOverflowChangeData extends OverflowState {}

/**
* Overflow Props
*/
export type OverflowProps = Partial<
Pick<ObserveOptions, 'overflowAxis' | 'overflowDirection' | 'padding' | 'minimumVisible'>
> & {
children: React.ReactElement;

// overflow is not caused by DOM event
// eslint-disable-next-line @nx/workspace-consistent-callback-type
onOverflowChange?: (ev: null, data: OverflowState) => void;
};

/**
Expand All @@ -28,7 +34,7 @@ export type OverflowProps = Partial<
export const Overflow = React.forwardRef((props: OverflowProps, ref) => {
const styles = useOverflowStyles();

const { children, minimumVisible, overflowAxis = 'horizontal', overflowDirection, padding } = props;
const { children, minimumVisible, overflowAxis = 'horizontal', overflowDirection, padding, onOverflowChange } = props;

const [overflowState, setOverflowState] = React.useState<OverflowState>({
hasOverflow: false,
Expand All @@ -45,14 +51,14 @@ export const Overflow = React.forwardRef((props: OverflowProps, ref) => {
itemVisibility[item.id] = true;
});
invisibleItems.forEach(x => (itemVisibility[x.id] = false));
const newState = {
hasOverflow: data.invisibleItems.length > 0,
itemVisibility,
groupVisibility,
};
onOverflowChange?.(null, { ...newState });

setOverflowState(() => {
return {
hasOverflow: data.invisibleItems.length > 0,
itemVisibility,
groupVisibility,
};
});
setOverflowState(newState);
};

const { containerRef, registerItem, updateOverflow, registerOverflowMenu, registerDivider } = useOverflowContainer(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export { Overflow } from './components/Overflow';
export type { OverflowProps } from './components/Overflow';
export type { OverflowProps, OnOverflowChangeData } from './components/Overflow';
export { DATA_OVERFLOWING, DATA_OVERFLOW_ITEM, DATA_OVERFLOW_MENU, DATA_OVERFLOW_DIVIDER } from './constants';
export type { UseOverflowContainerReturn } from './types';
export { useIsOverflowGroupVisible } from './useIsOverflowGroupVisible';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import * as React from 'react';
import {
makeStyles,
Button,
Menu,
MenuTrigger,
MenuPopover,
MenuList,
MenuItem,
MenuButton,
tokens,
mergeClasses,
Overflow,
OverflowItem,
OverflowItemProps,
useIsOverflowItemVisible,
useOverflowMenu,
} from '@fluentui/react-components';

const useStyles = makeStyles({
container: {
display: 'flex',
flexWrap: 'nowrap',
minWidth: 0,
overflow: 'hidden',
},

resizableArea: {
minWidth: '200px',
maxWidth: '800px',
border: `2px solid ${tokens.colorBrandBackground}`,
padding: '20px 10px 10px 10px',
position: 'relative',
resize: 'horizontal',
'::after': {
content: `'Resizable Area'`,
position: 'absolute',
padding: '1px 4px 1px',
top: '-2px',
left: '-2px',
fontFamily: 'monospace',
fontSize: '15px',
fontWeight: 900,
lineHeight: 1,
letterSpacing: '1px',
color: tokens.colorNeutralForegroundOnBrand,
backgroundColor: tokens.colorBrandBackground,
},
},
});

export const ListenToChanges = () => {
const styles = useStyles();

const itemIds = new Array(8).fill(0).map((_, i) => i.toString());
const [overflowState, setOverflowState] = React.useState<object>({});

return (
<>
<Overflow onOverflowChange={(e, data) => setOverflowState(data)}>
<div className={mergeClasses(styles.container, styles.resizableArea)}>
{itemIds.map(i => (
<OverflowItem key={i} id={i}>
<Button>Item {i}</Button>
</OverflowItem>
))}
<OverflowMenu itemIds={itemIds} />
</div>
</Overflow>
<pre>{JSON.stringify(overflowState, null, 2)}</pre>
</>
);
};

const OverflowMenuItem: React.FC<Pick<OverflowItemProps, 'id'>> = props => {
const { id } = props;
const isVisible = useIsOverflowItemVisible(id);

if (isVisible) {
return null;
}

// As an union between button props and div props may be conflicting, casting is required
return <MenuItem>Item {id}</MenuItem>;
};

const OverflowMenu: React.FC<{ itemIds: string[] }> = ({ itemIds }) => {
const { ref, overflowCount, isOverflowing } = useOverflowMenu<HTMLButtonElement>();

if (!isOverflowing) {
return null;
}

return (
<Menu>
<MenuTrigger disableButtonEnhancement>
<MenuButton ref={ref}>+{overflowCount} items</MenuButton>
</MenuTrigger>

<MenuPopover>
<MenuList>
{itemIds.map(i => {
return <OverflowMenuItem key={i} id={i} />;
})}
</MenuList>
</MenuPopover>
</Menu>
);
};

ListenToChanges.parameters = {
docs: {
description: {
story: [
'You can listen to changes with the `onOnverflowChange` prop which will return the overflow',
'state. This can be useful when you have other UI features that need to be triggered on changes',
'to item visibility.',
].join('\n'),
},
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export { Dividers } from './Dividers.stories';
export { LargerDividers } from './LargerDividers.stories';
export { PriorityWithDividers } from './PriorityWithDividers.stories';
export { CustomComponent } from './CustomComponent.stories';
export { ListenToChanges } from './ListenToChanges.stories';

// Typing with as Meta<typeof Overflow> generates a type error for the `subcomponents` property.
// https://github.com/storybookjs/storybook/issues/27535
Expand Down

0 comments on commit 84f915c

Please sign in to comment.