forked from microsoft/fluentui
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Menu and MenuTrigger API (microsoft#17271)
* Menu and MenuTrigger API Adds the Menu and MenuTrigger compoenents to render popup and trigger element to control the popup Menu renders a wrapper slot around expected `MenuList` for popup positioning MenuTrigger renders no DOM but clones an only child with correct popup event handling * fix types * fixes * useCallback * Change files * fix controlled example * fix tests
- Loading branch information
1 parent
5e56178
commit d4a1508
Showing
24 changed files
with
667 additions
and
0 deletions.
There are no files selected for viewing
7 changes: 7 additions & 0 deletions
7
change/@fluentui-react-examples-0d85fcc7-7b97-4345-a454-5305248864d3.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"type": "minor", | ||
"comment": "Add MenuTrigger examples", | ||
"packageName": "@fluentui/react-examples", | ||
"email": "lingfan.gao@microsoft.com", | ||
"dependentChangeType": "patch" | ||
} |
7 changes: 7 additions & 0 deletions
7
change/@fluentui-react-menu-15048b94-4b75-4c7c-9baf-721fadced51c.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"type": "minor", | ||
"comment": "Add Menu, MenuTrigger components", | ||
"packageName": "@fluentui/react-menu", | ||
"email": "lingfan.gao@microsoft.com", | ||
"dependentChangeType": "patch" | ||
} |
86 changes: 86 additions & 0 deletions
86
packages/react-examples/src/react-menu/Menu/Menu.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
import * as React from 'react'; | ||
import { | ||
Menu, | ||
MenuTrigger, | ||
MenuList, | ||
MenuItem, | ||
MenuItemRadio, | ||
MenuItemCheckbox, | ||
MenuGroup, | ||
MenuDivider, | ||
MenuGroupHeader, | ||
} from '@fluentui/react-menu'; | ||
import { CutIcon, PasteIcon, EditIcon, AcceptIcon } from '@fluentui/react-icons-mdl2'; | ||
|
||
export const MenuExample = () => ( | ||
<> | ||
<Menu> | ||
<MenuTrigger> | ||
<button>Toggle menu</button> | ||
</MenuTrigger> | ||
|
||
<MenuList> | ||
<MenuItem>Item 1</MenuItem> | ||
<MenuItem>Item 1</MenuItem> | ||
<MenuItem>Item 1</MenuItem> | ||
</MenuList> | ||
</Menu> | ||
</> | ||
); | ||
|
||
export const MenuControlledExample = () => { | ||
const [open, setOpen] = React.useState(false); | ||
return ( | ||
<> | ||
<Menu open={open}> | ||
<MenuTrigger> | ||
<button onClick={() => setOpen(s => !s)}>Toggle menu</button> | ||
</MenuTrigger> | ||
|
||
<MenuList> | ||
<MenuItem>Item 1</MenuItem> | ||
<MenuItem>Item 1</MenuItem> | ||
<MenuItem>Item 1</MenuItem> | ||
</MenuList> | ||
</Menu> | ||
</> | ||
); | ||
}; | ||
|
||
export const MenuSelectionExample = () => ( | ||
<> | ||
<Menu> | ||
<MenuTrigger> | ||
<button>Toggle menu</button> | ||
</MenuTrigger> | ||
|
||
<MenuList> | ||
<MenuGroup> | ||
<MenuGroupHeader>Checkbox group</MenuGroupHeader> | ||
<MenuItemCheckbox icon={<CutIcon />} name="edit" value="cut" checkmark={<AcceptIcon />}> | ||
Cut | ||
</MenuItemCheckbox> | ||
<MenuItemCheckbox icon={<PasteIcon />} name="edit" value="paste" checkmark={<AcceptIcon />}> | ||
Paste | ||
</MenuItemCheckbox> | ||
<MenuItemCheckbox icon={<EditIcon />} name="edit" value="edit" checkmark={<AcceptIcon />}> | ||
Edit | ||
</MenuItemCheckbox> | ||
</MenuGroup> | ||
<MenuDivider /> | ||
<MenuGroup> | ||
<MenuGroupHeader>Radio group</MenuGroupHeader> | ||
<MenuItemRadio icon={<CutIcon />} name="font" value="segoe" checkmark={<AcceptIcon />}> | ||
Segoe | ||
</MenuItemRadio> | ||
<MenuItemRadio icon={<PasteIcon />} name="font" value="calibri" checkmark={<AcceptIcon />}> | ||
Caliri | ||
</MenuItemRadio> | ||
<MenuItemRadio icon={<EditIcon />} name="font" value="arial" checkmark={<AcceptIcon />}> | ||
Arial | ||
</MenuItemRadio> | ||
</MenuGroup> | ||
</MenuList> | ||
</Menu> | ||
</> | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './components/Menu/index'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './components/MenuTrigger/index'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import * as React from 'react'; | ||
import { Menu } from './Menu'; | ||
import * as renderer from 'react-test-renderer'; | ||
import { ReactWrapper } from 'enzyme'; | ||
import { isConformant } from '../../common/isConformant'; | ||
import { MenuTrigger } from '../MenuTrigger/index'; | ||
import { MenuList } from '../MenuList/index'; | ||
import { MenuItem } from '../MenuItem/index'; | ||
|
||
describe('Menu', () => { | ||
isConformant({ | ||
disabledTests: [ | ||
'as-renders-html', | ||
'as-renders-fc', | ||
'component-handles-ref', | ||
'component-has-root-ref', | ||
'component-handles-classname', | ||
'as-passes-as-value', | ||
], | ||
Component: Menu, | ||
displayName: 'Menu', | ||
requiredProps: { | ||
children: [ | ||
<MenuTrigger key="trigger"> | ||
<button>MenuTrigger</button> | ||
</MenuTrigger>, | ||
<MenuList key="item"> | ||
<MenuItem>Item</MenuItem> | ||
</MenuList>, | ||
], | ||
}, | ||
}); | ||
|
||
let wrapper: ReactWrapper | undefined; | ||
|
||
afterEach(() => { | ||
if (wrapper) { | ||
wrapper.unmount(); | ||
wrapper = undefined; | ||
} | ||
}); | ||
|
||
/** | ||
* Note: see more visual regression tests for Menu in /apps/vr-tests. | ||
*/ | ||
it('renders a default state', () => { | ||
const component = renderer.create( | ||
<Menu> | ||
<MenuTrigger> | ||
<button>Menu trigger</button> | ||
</MenuTrigger> | ||
<MenuList> | ||
<MenuItem>Item</MenuItem> | ||
</MenuList> | ||
</Menu>, | ||
); | ||
const tree = component.toJSON(); | ||
expect(tree).toMatchSnapshot(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import * as React from 'react'; | ||
import { useMenu } from './useMenu'; | ||
import { MenuProps } from './Menu.types'; | ||
import { renderMenu } from './renderMenu'; | ||
import { useMenuStyles } from './useMenuStyles'; | ||
|
||
/** | ||
* Wrapper component that manages state for a popup MenuList and a MenuTrigger | ||
* {@docCategory Menu } | ||
*/ | ||
export const Menu = React.forwardRef<HTMLElement, MenuProps>((props, ref) => { | ||
const state = useMenu(props, ref); | ||
|
||
useMenuStyles(state); | ||
return renderMenu(state); | ||
}); | ||
|
||
Menu.displayName = 'Menu'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import * as React from 'react'; | ||
import { ObjectShorthandProps, ShorthandProps } from '@fluentui/react-utilities'; | ||
import { MenuListProps } from '../MenuList/index'; | ||
|
||
/** | ||
* Extends and drills down Menulist props to simplify API | ||
* {@docCategory Menu } | ||
*/ | ||
export interface MenuProps extends MenuListProps { | ||
/** | ||
* Explicitly require children | ||
*/ | ||
|
||
children: React.ReactNode; | ||
/** | ||
* Whether the popup is open | ||
*/ | ||
open?: boolean; | ||
|
||
/** | ||
* Whether the popup is open by default | ||
*/ | ||
defaultOpen?: boolean; | ||
|
||
/** | ||
* Wrapper to style and add events for the popup | ||
*/ | ||
menuPopup?: ShorthandProps<React.HTMLAttributes<HTMLElement>>; | ||
} | ||
|
||
/** | ||
* {@docCategory Menu } | ||
*/ | ||
export interface MenuState extends MenuProps { | ||
/** | ||
* Ref to the root slot | ||
*/ | ||
ref: React.MutableRefObject<HTMLElement>; | ||
|
||
/** | ||
* Whether the popup is open | ||
*/ | ||
open: boolean; | ||
|
||
/** | ||
* Callback to open/close the popup | ||
*/ | ||
setOpen: React.Dispatch<React.SetStateAction<boolean>>; | ||
|
||
/** | ||
* Internal react node that just simplifies handling children | ||
*/ | ||
menuList: React.ReactNode; | ||
|
||
/** | ||
* Internal react node that just simplifies handling children | ||
*/ | ||
menuTrigger: React.ReactNode; | ||
|
||
/** | ||
* Wrapper to style and add events for the popup | ||
*/ | ||
menuPopup: ObjectShorthandProps<React.HTMLAttributes<HTMLElement>>; | ||
} |
9 changes: 9 additions & 0 deletions
9
packages/react-menu/src/components/Menu/__snapshots__/Menu.test.tsx.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`Menu renders a default state 1`] = ` | ||
<button | ||
onClick={[Function]} | ||
> | ||
Menu trigger | ||
</button> | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export * from './Menu'; | ||
export * from './Menu.types'; | ||
export * from './renderMenu'; | ||
export * from './useMenu'; | ||
export * from './useMenuStyles'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import * as React from 'react'; | ||
import { getSlots } from '@fluentui/react-utilities'; | ||
import { MenuState } from './Menu.types'; | ||
import { menuShorthandProps } from './useMenu'; | ||
import { MenuProvider } from '../../menuContext'; | ||
|
||
/** | ||
* Render the final JSX of Menu | ||
* {@docCategory Menu } | ||
*/ | ||
export const renderMenu = (state: MenuState) => { | ||
const { slots, slotProps } = getSlots(state, menuShorthandProps); | ||
const { open, setOpen, onCheckedValueChange, checkedValues, defaultCheckedValues } = state; | ||
|
||
return ( | ||
<MenuProvider | ||
value={{ open, setOpen, onCheckedValueChange, checkedValues, defaultCheckedValues, hasMenuContext: true }} | ||
> | ||
{state.menuTrigger} | ||
{/** TODO use open state to control a real popup */} | ||
{state.open && <slots.menuPopup {...slotProps.menuPopup} />} | ||
</MenuProvider> | ||
); | ||
}; |
Oops, something went wrong.