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
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🕵🏾‍♀️ visual changes to review in the Visual Change Report

vr-tests-react-components/Avatar Converged 1 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/Avatar Converged.badgeMask - RTL.normal.chromium.png 3 Changed
vr-tests-react-components/Charts-DonutChart 1 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/Charts-DonutChart.Dynamic - RTL.default.chromium.png 2375 Changed
vr-tests-react-components/Menu Converged - submenuIndicator slotted content 1 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/Menu Converged - submenuIndicator slotted content.default.submenus open.chromium.png 413 Changed
vr-tests-react-components/Positioning 2 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/Positioning.Positioning end.chromium.png 883 Changed
vr-tests-react-components/Positioning.Positioning end.updated 2 times.chromium.png 861 Changed
vr-tests-react-components/ProgressBar converged 3 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/ProgressBar converged.Indeterminate + thickness - Dark Mode.default.chromium.png 48 Changed
vr-tests-react-components/ProgressBar converged.Indeterminate + thickness.default.chromium.png 187 Changed
vr-tests-react-components/ProgressBar converged.Indeterminate + thickness - High Contrast.default.chromium.png 171 Changed
vr-tests-react-components/TagPicker 1 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/TagPicker.disabled - Dark Mode.chromium.png 658 Changed

There were 3 duplicate changes discarded. Check the build logs for more information.

"type": "patch",
"comment": "feat: add headless Overflow component",
"packageName": "@fluentui/react-headless-components-preview",
"email": "vgenaev@gmail.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import * as Link from '@fluentui/react-headless-components-preview/link';
import * as Menu from '@fluentui/react-headless-components-preview/menu';
import * as MessageBar from '@fluentui/react-headless-components-preview/message-bar';
import * as Nav from '@fluentui/react-headless-components-preview/nav';
import * as Overflow from '@fluentui/react-headless-components-preview/overflow';
import * as Persona from '@fluentui/react-headless-components-preview/persona';
import * as Popover from '@fluentui/react-headless-components-preview/popover';
import * as ProgressBar from '@fluentui/react-headless-components-preview/progress-bar';
Expand Down Expand Up @@ -67,6 +68,7 @@ console.log({
Menu,
MessageBar,
Nav,
Overflow,
Persona,
Popover,
ProgressBar,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
## API Report File for "@fluentui/react-headless-components-preview"

> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).

```ts

import { DATA_OVERFLOW_DIVIDER } from '@fluentui/react-overflow';
import { DATA_OVERFLOW_ITEM } from '@fluentui/react-overflow';
import { DATA_OVERFLOW_MENU } from '@fluentui/react-overflow';
import { DATA_OVERFLOWING } from '@fluentui/react-overflow';
import type { ForwardRefComponent } from '@fluentui/react-utilities';
import { OnOverflowChangeData } from '@fluentui/react-overflow';
import { OverflowComponentState } from '@fluentui/react-overflow';
import { OverflowContextValues } from '@fluentui/react-overflow';
import { OverflowDivider } from '@fluentui/react-overflow';
import { OverflowDividerProps } from '@fluentui/react-overflow';
import { OverflowItem } from '@fluentui/react-overflow';
import { OverflowItemProps } from '@fluentui/react-overflow';
import { OverflowProps } from '@fluentui/react-overflow';
import { OverflowReorderObserver } from '@fluentui/react-overflow';
import { OverflowState } from '@fluentui/react-overflow';
import { renderOverflow_unstable as renderOverflow } from '@fluentui/react-overflow';
import { useIsOverflowGroupVisible } from '@fluentui/react-overflow';
import { useIsOverflowItemVisible } from '@fluentui/react-overflow';
import { useOverflow_unstable as useOverflow } from '@fluentui/react-overflow';
import { useOverflowContext } from '@fluentui/react-overflow';
import { useOverflowContextValues_unstable as useOverflowContextValues } from '@fluentui/react-overflow';
import { useOverflowCount } from '@fluentui/react-overflow';
import { useOverflowDivider } from '@fluentui/react-overflow';
import { useOverflowItem } from '@fluentui/react-overflow';
import { useOverflowMenu } from '@fluentui/react-overflow';
import { useOverflowVisibility } from '@fluentui/react-overflow';

export { DATA_OVERFLOW_DIVIDER }

export { DATA_OVERFLOW_ITEM }

export { DATA_OVERFLOW_MENU }

export { DATA_OVERFLOWING }

export { OnOverflowChangeData }

// @public
export const Overflow: ForwardRefComponent<OverflowProps>;

export { OverflowComponentState }

export { OverflowContextValues }

export { OverflowDivider }

export { OverflowDividerProps }

export { OverflowItem }

export { OverflowItemProps }

export { OverflowProps }

export { OverflowReorderObserver }

export { OverflowState }

export { renderOverflow }

export { useIsOverflowGroupVisible }

export { useIsOverflowItemVisible }

export { useOverflow }

export { useOverflowContext }

export { useOverflowContextValues }

export { useOverflowCount }

export { useOverflowDivider }

export { useOverflowItem }

export { useOverflowMenu }

export { useOverflowVisibility }

// (No @packageDocumentation comment for this package)

```
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"@fluentui/react-message-bar": "^9.7.1",
"@fluentui/react-teaching-popover": "^9.7.0",
"@fluentui/react-nav": "^9.4.0",
"@fluentui/react-overflow": "^9.8.0",
"@fluentui/react-persona": "^9.7.4",
"@fluentui/react-popover": "^9.14.3",
"@fluentui/react-positioning": "^9.22.2",
Expand Down Expand Up @@ -206,6 +207,12 @@
"import": "./lib/nav.js",
"require": "./lib-commonjs/nav.js"
},
"./overflow": {
"types": "./dist/overflow.d.ts",
"node": "./lib-commonjs/overflow.js",
"import": "./lib/overflow.js",
"require": "./lib-commonjs/overflow.js"
},
"./persona": {
"types": "./dist/persona.d.ts",
"node": "./lib-commonjs/persona.js",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import * as React from 'react';
import { render } from '@testing-library/react';
import { isConformant } from '../../testing/isConformant';
import { Overflow } from './Overflow';
import { OverflowItem, useOverflowMenu } from './index';

describe('Overflow', () => {
isConformant({
Component: Overflow,
displayName: 'Overflow',
// Overflow is renderless (clones its single child) and provides context, so the slot/render
// based conformance assertions do not apply.
disabledTests: ['component-handles-ref', 'component-has-root-ref', 'component-handles-classname'],
requiredProps: { children: <div /> } as object,
});

it('forwards the ref to the single child', () => {
const overflowRef = jest.fn();
const childRef = jest.fn();

render(
<Overflow ref={overflowRef}>
<div id="child" ref={childRef} />
</Overflow>,
);

expect(overflowRef).toHaveBeenCalledWith(expect.objectContaining({ id: 'child', tagName: 'DIV' }));
expect(childRef).toHaveBeenCalledWith(expect.objectContaining({ id: 'child', tagName: 'DIV' }));
});

it('does not add any built-in className to the cloned child', () => {
const { container } = render(
<Overflow>
<div className="my-container" />
</Overflow>,
);

// The headless Overflow must preserve only the child's own className — no Griffel/fui-* classes.
expect((container.firstChild as HTMLElement).className).toBe('my-container');
});

it('registers overflow items on the child element', () => {
const { getByText } = render(
<Overflow>
<div>
<OverflowItem id="1">
<button>foo</button>
</OverflowItem>
</div>
</Overflow>,
);

expect(getByText('foo')).toHaveAttribute('data-overflow-item');
});

it('marks the overflow menu element with data-overflow-menu for styling', () => {
const Menu: React.FC = () => {
const { ref } = useOverflowMenu<HTMLButtonElement>();
return (
<button ref={ref} data-testid="menu">
menu
</button>
);
};

const { getByTestId } = render(
<Overflow>
<div>
<Menu />
</div>
</Overflow>,
);

// Headless: the engine sets the data attribute; consumers style it (e.g. `flex-shrink: 0`).
expect(getByTestId('menu')).toHaveAttribute('data-overflow-menu');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use client';

import * as React from 'react';
import type { ForwardRefComponent } from '@fluentui/react-utilities';
import type { OverflowProps } from './Overflow.types';
import { useOverflow } from './useOverflow';
import { useOverflowContextValues } from './useOverflowContextValues';
import { renderOverflow } from './renderOverflow';

/**
* Provides an overflow context for `OverflowItem` descendants without any built-in styling.
*/
export const Overflow: ForwardRefComponent<OverflowProps> = React.forwardRef((props, ref) => {
const state = useOverflow(props, ref);
const contextValues = useOverflowContextValues(state);

return renderOverflow(state, contextValues);
});

Overflow.displayName = 'Overflow';
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export type {
OverflowProps,
OnOverflowChangeData,
OverflowItemProps,
OverflowDividerProps,
OverflowState,
OverflowComponentState,
OverflowContextValues,
} from '@fluentui/react-overflow';
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
export { Overflow } from './Overflow';
export { useOverflow } from './useOverflow';
export { useOverflowContextValues } from './useOverflowContextValues';
export { renderOverflow } from './renderOverflow';
export type {
OverflowProps,
OverflowState,
OverflowComponentState,
OverflowContextValues,
OnOverflowChangeData,
OverflowItemProps,
OverflowDividerProps,
} from './Overflow.types';

export {
OverflowItem,
OverflowDivider,
OverflowReorderObserver,
useOverflowMenu,
useOverflowContext,
useOverflowCount,
useOverflowItem,
useOverflowDivider,
useIsOverflowItemVisible,
useIsOverflowGroupVisible,
useOverflowVisibility,
DATA_OVERFLOWING,
DATA_OVERFLOW_ITEM,
DATA_OVERFLOW_MENU,
DATA_OVERFLOW_DIVIDER,
} from '@fluentui/react-overflow';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { renderOverflow_unstable as renderOverflow } from '@fluentui/react-overflow';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { useOverflow_unstable as useOverflow } from '@fluentui/react-overflow';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { useOverflowContextValues_unstable as useOverflowContextValues } from '@fluentui/react-overflow';
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export {
Overflow,
useOverflow,
useOverflowContextValues,
renderOverflow,
useOverflowMenu,
OverflowItem,
OverflowDivider,
OverflowReorderObserver,
useOverflowContext,
useOverflowCount,
useOverflowItem,
useOverflowDivider,
useIsOverflowItemVisible,
useIsOverflowGroupVisible,
useOverflowVisibility,
DATA_OVERFLOWING,
DATA_OVERFLOW_ITEM,
DATA_OVERFLOW_MENU,
DATA_OVERFLOW_DIVIDER,
} from './components/Overflow';
export type {
OverflowProps,
OverflowState,
OverflowComponentState,
OverflowContextValues,
OnOverflowChangeData,
OverflowItemProps,
OverflowDividerProps,
} from './components/Overflow';
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import * as React from 'react';
import {
Overflow,
OverflowItem,
useOverflowMenu,
useIsOverflowItemVisible,
} from '@fluentui/react-headless-components-preview/overflow';
import { Menu, MenuTrigger, MenuPopover, MenuList, MenuItem } from '@fluentui/react-headless-components-preview/menu';

import styles from './overflow.module.css';

const itemIds = Array.from({ length: 10 }, (_, i) => (i + 1).toString());

const OverflowMenuItem = ({ id }: { id: string }): React.ReactNode => {
// Only the overflowed (hidden) items are listed in the menu.
const isVisible = useIsOverflowItemVisible(id);
return isVisible ? null : <MenuItem className={styles.menuItem}>Item {id}</MenuItem>;
};

/**
* `+N` button that opens a headless `Menu` listing the overflowed items. `MenuPopover` renders
* through a portal but preserves React context, so the overflow hooks inside still read the
* `Overflow` root's context.
*/
const OverflowMenu = ({ ids }: { ids: string[] }): React.ReactNode => {
const { ref, overflowCount, isOverflowing } = useOverflowMenu<HTMLButtonElement>();

if (!isOverflowing) {
return null;
}

return (
<Menu>
<MenuTrigger>
<button ref={ref} type="button" className={styles.menu} aria-label={`${overflowCount} more items`}>
+{overflowCount}
</button>
</MenuTrigger>
<MenuPopover className={styles.menuPopover}>
<MenuList className={styles.menuList}>
{ids.map(id => (
<OverflowMenuItem key={id} id={id} />
))}
</MenuList>
</MenuPopover>
</Menu>
);
};

/**
* Drag the dashed box's right edge to resize. Items that no longer fit are hidden and the `+N`
* button reflects the overflow count; click it to see the overflowed items.
*/
export const Default = (): React.ReactNode => (
<div className={styles.resizer}>
<Overflow>
<div className={styles.container}>
{itemIds.map(id => (
<OverflowItem key={id} id={id}>
<button className={styles.item}>Item {id}</button>
</OverflowItem>
))}
<OverflowMenu ids={itemIds} />
</div>
</Overflow>
</div>
);
Loading
Loading