Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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 @@
{
"type": "patch",
"comment": "fix: Context menu close should restore focus",
"packageName": "@fluentui/react-menu",
"email": "lingfangao@hotmail.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -1069,7 +1069,9 @@ describe('Context menu', () => {
.get(menuTriggerSelector)
.click()
.get(menuSelector)
.should('not.exist');
.should('not.exist')
.get(menuTriggerSelector)
.should('have.focus');
});

it('should close on scroll outside', () => {
Expand All @@ -1081,6 +1083,21 @@ describe('Context menu', () => {
.get('body')
.trigger('wheel')
.get(menuSelector)
.should('not.exist');
.should('not.exist')
.get(menuTriggerSelector)
.should('have.focus');
});

it('should restore focus on escape', () => {
mount(<ContextMenuExample />);
cy.get(menuTriggerSelector)
.rightclick()
.get(menuSelector)
.should('exist')
.focused()
.type('{esc}')
.should('not.exist')
.get(menuTriggerSelector)
.should('have.focus');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ export const useMenuTrigger_unstable = (props: MenuTriggerProps): MenuTriggerSta
id: triggerId,
...child?.props,
ref: useMergedRefs(triggerRef, child?.ref),
...restoreFocusTargetAttribute,
onMouseEnter: useEventCallback(mergeCallbacks(child?.props.onMouseEnter, onMouseEnter)),
onMouseLeave: useEventCallback(mergeCallbacks(child?.props.onMouseLeave, onMouseLeave)),
onContextMenu: useEventCallback(mergeCallbacks(child?.props.onContextMenu, onContextMenu)),
Expand All @@ -136,7 +137,6 @@ export const useMenuTrigger_unstable = (props: MenuTriggerProps): MenuTriggerSta
const triggerChildProps = {
'aria-haspopup': 'menu',
'aria-expanded': !open && !isSubmenu ? undefined : open,
...restoreFocusTargetAttribute,
...contextMenuProps,
onClick: useEventCallback(mergeCallbacks(child?.props.onClick, onClick)),
onKeyDown: useEventCallback(mergeCallbacks(child?.props.onKeyDown, onKeyDown)),
Expand Down
Comment thread
ling1726 marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import * as React from 'react';

import { Button, Menu, MenuTrigger, MenuList, MenuItem, MenuPopover } from '@fluentui/react-components';
import {
Button,
Menu,
MenuTrigger,
MenuList,
MenuItem,
MenuPopover,
useRestoreFocusTarget,
} from '@fluentui/react-components';

export const Default = () => (
<Menu>
<Menu openOnContext>
<MenuTrigger disableButtonEnhancement>
<Button>Toggle menu</Button>
<Button {...useRestoreFocusTarget()}>Toggle menu</Button>
</MenuTrigger>

<MenuPopover>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,39 @@

```ts

import type { ComponentProps } from '@fluentui/react-utilities';
import type { ComponentState } from '@fluentui/react-utilities';
import type { ForwardRefComponent } from '@fluentui/react-utilities';
import * as React_2 from 'react';
import type { Slot } from '@fluentui/react-utilities';
import type { SlotClassNames } from '@fluentui/react-utilities';

// @public
export const Messagebar: ForwardRefComponent<MessagebarProps>;

// @public (undocumented)
export const messagebarClassNames: SlotClassNames<MessagebarSlots>;

// @public
export type MessagebarProps = ComponentProps<MessagebarSlots> & {};

// @public (undocumented)
export type MessagebarSlots = {
root: Slot<'div'>;
};

// @public
export type MessagebarState = ComponentState<MessagebarSlots>;

// @public
export const renderMessagebar_unstable: (state: MessagebarState) => JSX.Element;

// @public
export const useMessagebar_unstable: (props: MessagebarProps, ref: React_2.Ref<HTMLElement>) => MessagebarState;

// @public
export const useMessagebarStyles_unstable: (state: MessagebarState) => MessagebarState;

// (No @packageDocumentation comment for this package)

```
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './components/Messagebar/index';
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as React from 'react';
import { render } from '@testing-library/react';
import { isConformant } from '../../testing/isConformant';
import { Messagebar } from './Messagebar';

describe('Messagebar', () => {
isConformant({
Component: Messagebar,
displayName: 'Messagebar',
});

// TODO add more tests here, and create visual regression tests in /apps/vr-tests

it('renders a default state', () => {
const result = render(<Messagebar>Default Messagebar</Messagebar>);
expect(result.container).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as React from 'react';
import type { ForwardRefComponent } from '@fluentui/react-utilities';
import { useMessagebar_unstable } from './useMessagebar';
import { renderMessagebar_unstable } from './renderMessagebar';
import { useMessagebarStyles_unstable } from './useMessagebarStyles.styles';
import type { MessagebarProps } from './Messagebar.types';

/**
* Messagebar component - TODO: add more docs
*/
export const Messagebar: ForwardRefComponent<MessagebarProps> = React.forwardRef((props, ref) => {
const state = useMessagebar_unstable(props, ref);

useMessagebarStyles_unstable(state);
return renderMessagebar_unstable(state);
});

Messagebar.displayName = 'Messagebar';
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities';

export type MessagebarSlots = {
root: Slot<'div'>;
};

/**
* Messagebar Props
*/
export type MessagebarProps = ComponentProps<MessagebarSlots> & {};

/**
* State used in rendering Messagebar
*/
export type MessagebarState = ComponentState<MessagebarSlots>;
// TODO: Remove semicolon from previous line, uncomment next line, and provide union of props to pick from MessagebarProps.
// & Required<Pick<MessagebarProps, 'propName'>>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Messagebar renders a default state 1`] = `
<div>
<div
class="fui-Messagebar"
>
Default Messagebar
</div>
</div>
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * from './Messagebar';
export * from './Messagebar.types';
export * from './renderMessagebar';
export * from './useMessagebar';
export * from './useMessagebarStyles.styles';
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/** @jsxRuntime automatic */
/** @jsxImportSource @fluentui/react-jsx-runtime */

import { assertSlots } from '@fluentui/react-utilities';
import type { MessagebarState, MessagebarSlots } from './Messagebar.types';

/**
* Render the final JSX of Messagebar
*/
export const renderMessagebar_unstable = (state: MessagebarState) => {
assertSlots<MessagebarSlots>(state);

// TODO Add additional slots in the appropriate place
return <state.root />;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import * as React from 'react';
import { getNativeElementProps, slot } from '@fluentui/react-utilities';
import type { MessagebarProps, MessagebarState } from './Messagebar.types';

/**
* Create the state required to render Messagebar.
*
* The returned state can be modified with hooks such as useMessagebarStyles_unstable,
* before being passed to renderMessagebar_unstable.
*
* @param props - props from this instance of Messagebar
* @param ref - reference to root HTMLElement of Messagebar
*/
export const useMessagebar_unstable = (props: MessagebarProps, ref: React.Ref<HTMLElement>): MessagebarState => {
return {
// TODO add appropriate props/defaults
components: {
// TODO add each slot's element type or component
root: 'div',
},
// TODO add appropriate slots, for example:
// mySlot: resolveShorthand(props.mySlot),
root: slot.always(
getNativeElementProps('div', {
ref,
...props,
}),
{ elementType: 'div' },
),
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { makeStyles, mergeClasses } from '@griffel/react';
import type { SlotClassNames } from '@fluentui/react-utilities';
import type { MessagebarSlots, MessagebarState } from './Messagebar.types';

export const messagebarClassNames: SlotClassNames<MessagebarSlots> = {
root: 'fui-Messagebar',
// TODO: add class names for all slots on MessagebarSlots.
// Should be of the form `<slotName>: 'fui-Messagebar__<slotName>`
};

/**
* Styles for the root slot
*/
const useStyles = makeStyles({
root: {
// TODO Add default styles for the root element
},

// TODO add additional classes for different states and/or slots
});

/**
* Apply styling to the Messagebar slots based on the state
*/
export const useMessagebarStyles_unstable = (state: MessagebarState): MessagebarState => {
const styles = useStyles();
state.root.className = mergeClasses(messagebarClassNames.root, styles.root, state.root.className);

// TODO Add class names to slots, for example:
// state.mySlot.className = mergeClasses(styles.mySlot, state.mySlot.className);

return state;
};
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
export {};
export {
Messagebar,
messagebarClassNames,
renderMessagebar_unstable,
useMessagebarStyles_unstable,
useMessagebar_unstable,
} from './Messagebar';
export type { MessagebarProps, MessagebarSlots, MessagebarState } from './Messagebar';
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## Best practices

### Do

### Don't
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import * as React from 'react';
import { Messagebar, MessagebarProps } from '@fluentui/react-message-bar-preview';

export const Default = (props: Partial<MessagebarProps>) => <Messagebar {...props} />;
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Messagebar } from '@fluentui/react-message-bar-preview';

import descriptionMd from './MessagebarDescription.md';
import bestPracticesMd from './MessagebarBestPractices.md';

export { Default } from './MessagebarDefault.stories';

export default {
title: 'Preview Components/Messagebar',
component: Messagebar,
parameters: {
docs: {
description: {
component: [descriptionMd, bestPracticesMd].join('\n'),
},
},
},
};