Skip to content

Commit

Permalink
Menu typescript story (#815)
Browse files Browse the repository at this point in the history
* ♻️ renamed story

* ♻️ Tweaked component

* ♻️ Updated story

* Forced view mode to canvas in story
  • Loading branch information
Michael Marszalek authored and vnys committed Nov 13, 2020
1 parent d3c014d commit 677093f
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 200 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import React, { useEffect } from 'react'
import { withKnobs } from '@storybook/addon-knobs'
import React, { EventHandler, useEffect } from 'react'
import { action } from '@storybook/addon-actions'
import styled from 'styled-components'
import { Menu, Typography, Button, Icon, TopBar } from '@equinor/eds-core-react'
import {
Menu,
MenuProps,
Typography,
Button,
Icon,
TopBar,
} from '@equinor/eds-core-react'
import { Story, Meta } from '@storybook/react'

import { tokens } from '@equinor/eds-tokens'

Expand Down Expand Up @@ -54,29 +61,11 @@ const Anchor = styled.div.attrs({ tabIndex: 0 })`
width: fit-content;
`

const FloatingAnchor = styled(Anchor)`
position: absolute;
`

const onClick = (event) => {
const onClick = (event: React.MouseEvent) => {
action('clicked')(event)
event.stopPropagation()
}

export default {
title: 'Components/Menu',
component: Menu,
decorators: [withKnobs],
}

const simpleMenuTemplate = (
<>
<MenuItem onClick={onClick}>Item 1</MenuItem>
<MenuItem onClick={onClick}>Item 2</MenuItem>
<MenuItem onClick={onClick}>Item 3</MenuItem>
</>
)

const bigMenuTemplate = (
<>
<MenuItem onClick={onClick}>
Expand Down Expand Up @@ -191,97 +180,19 @@ const bigMenuTemplate = (
</>
)

export const EdgeDetection = () => {
const [state, setState] = React.useState({
topLeft: null,
topRight: null,
bottomLeft: null,
bottomRight: null,
})

const topLeftRef = React.useRef()
const topRightRef = React.useRef()
const bottomLeftRef = React.useRef()
const bottomRightRef = React.useRef()

useEffect(() => {
setState({
topLeft: topLeftRef.current,
topRight: topRightRef.current,
bottomLeft: bottomLeftRef.current,
bottomRight: bottomRightRef.current,
})
}, [state.topLeft])

const { topLeft, topRight, bottomLeft, bottomRight } = state

return (
<Grid>
<FloatingAnchor
id="anchor-topleft"
aria-controls="menu-topleft"
aria-haspopup="true"
ref={topLeftRef}
style={{ left: 0, top: 0 }}
>
Top left
</FloatingAnchor>
<Menu id="menu-topleft" open={Boolean(topLeft)} anchorEl={topLeft}>
{simpleMenuTemplate}
</Menu>

<FloatingAnchor
id="anchor-topright"
aria-controls="menu-topright"
aria-haspopup="true"
ref={topRightRef}
style={{ top: 0, right: 0 }}
>
Top Right
</FloatingAnchor>
<Menu id="menu-topright" open={Boolean(topRight)} anchorEl={topRight}>
{simpleMenuTemplate}
</Menu>

<FloatingAnchor
id="anchor-bottomleft"
aria-controls="menu-bottomleft"
aria-haspopup="true"
ref={bottomLeftRef}
style={{ bottom: 0, left: 0 }}
>
Bottom Left
</FloatingAnchor>
<Menu
id="menu-bottomleft"
open={Boolean(bottomLeft)}
anchorEl={bottomLeft}
>
{simpleMenuTemplate}
</Menu>

<FloatingAnchor
id="anchor-bottomright"
aria-controls="menu-bottomright"
aria-haspopup="true"
ref={bottomRightRef}
style={{ bottom: 0, right: 0 }}
>
Bottom right
</FloatingAnchor>
<Menu
id="menu-bottomright"
open={Boolean(bottomRight)}
anchorEl={bottomRight}
>
{simpleMenuTemplate}
</Menu>
</Grid>
)
}

export const ButtonToggle = () => {
const [state, setState] = React.useState({
export default {
title: 'Components/Menu',
component: Menu,
parameters: {
viewMode: 'story',
},
} as Meta

export const ButtonToggle: Story<MenuProps> = () => {
const [state, setState] = React.useState<{
buttonEl: HTMLButtonElement
focus: any
}>({
focus: 'first',
buttonEl: null,
})
Expand All @@ -297,7 +208,7 @@ export const ButtonToggle = () => {
setState({ ...state, buttonEl: null, focus })
}

const onKeyPress = (e) => {
const onKeyPress = (e: React.KeyboardEvent<HTMLButtonElement>) => {
const { key } = e
e.preventDefault()
switch (key) {
Expand All @@ -317,14 +228,14 @@ export const ButtonToggle = () => {

return (
<Grid style={{ gridAutoFlow: 'row' }}>
<Typography variant="h4">Opened with Button</Typography>
<Typography variant="h4">Click button to open Menu</Typography>
<Button
variant="ghost_icon"
id="menuButton"
aria-controls="menu-on-button"
aria-haspopup="true"
aria-expanded={isOpen}
onClick={(e) => (isOpen ? closeMenu() : openMenu(e))}
onClick={(e) => (isOpen ? closeMenu() : openMenu(e, null))}
onKeyDown={onKeyPress}
>
Menu
Expand All @@ -343,8 +254,11 @@ export const ButtonToggle = () => {
)
}

export const InTopbar = () => {
const [state, setState] = React.useState({
export const InTopbar: Story<MenuProps> = () => {
const [state, setState] = React.useState<{
buttonEl: HTMLButtonElement
focus: any
}>({
focus: 'first',
buttonEl: null,
})
Expand All @@ -356,14 +270,14 @@ export const InTopbar = () => {

const closeMenu = () => setState({ ...state, buttonEl: null })

const onKeyPress = (e) => {
const onKeyPress = (e: React.KeyboardEvent<HTMLButtonElement>) => {
const { key } = e
switch (key) {
case 'ArrowDown':
isOpen ? closeMenu() : openMenu(e, 'first')
isOpen ? closeMenu() : openMenu(e)
break
case 'ArrowUp':
isOpen ? closeMenu() : openMenu(e, 'last')
isOpen ? closeMenu() : openMenu(e)
break
case 'Escape':
closeMenu()
Expand Down Expand Up @@ -405,7 +319,7 @@ export const InTopbar = () => {
)
}

export const Examples = () => {
export const Examples: Story<MenuProps> = () => {
const [state, setState] = React.useState({
one: null,
two: null,
Expand Down
124 changes: 58 additions & 66 deletions libraries/core-react/src/Menu/Menu.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useRef, ReactNode } from 'react'
import React, { useEffect, useRef, ReactNode, HTMLAttributes } from 'react'
import styled, { css } from 'styled-components'
import { useMenu } from './Menu.context'
import { Paper } from '../Paper'
Expand Down Expand Up @@ -43,76 +43,68 @@ export type MenuProps = {
focus?: FocusTarget
/** onClose handler */
onClose?: (e?: React.MouseEvent<ReactNode, MouseEvent>) => void
} & React.HTMLAttributes<HTMLDivElement>

export const Menu = React.forwardRef<HTMLDivElement, MenuProps>(
function EdsMenu(
{ children, anchorEl, onClose: onCloseCallback, open = false, ...rest },
ref,
) {
const listRef = useRef<HTMLLIElement>(null)

const {
setPosition,
position,
isPositioned,
setOnClose,
onClose,
} = useMenu()
useOutsideClick(listRef, () => {
if (open && onClose !== null) {
onClose()
}
})

useEffect(() => {
if (anchorEl && listRef.current) {
const menuRect = listRef.current.getBoundingClientRect()
const anchorRect = anchorEl.getBoundingClientRect()
setPosition(anchorRect, menuRect, window)
}

if (onClose === null && onCloseCallback) {
setOnClose(onCloseCallback)
}

document.addEventListener('keydown', handleGlobalKeyPress, true)

return () => {
document.removeEventListener('keydown', handleGlobalKeyPress, true)
}
}, [anchorEl, listRef.current])

const handleGlobalKeyPress = (e: KeyboardEvent) => {
const { key } = e

switch (key) {
case 'Escape':
onClose()
break
default:
break
}
} & HTMLAttributes<HTMLDivElement>

export const Menu = React.forwardRef<HTMLDivElement, MenuProps>(function Menu(
{ children, anchorEl, onClose: onCloseCallback, open = false, ...rest },
ref,
) {
const listRef = useRef<HTMLLIElement>(null)

const { setPosition, position, isPositioned, setOnClose, onClose } = useMenu()
useOutsideClick(listRef, () => {
if (open && onClose !== null) {
onClose()
}
})

useEffect(() => {
if (anchorEl && listRef.current) {
const menuRect = listRef.current.getBoundingClientRect()
const anchorRect = anchorEl.getBoundingClientRect()
setPosition(anchorRect, menuRect, window)
}

const paperProps = {
...position,
open,
isPositioned,
if (onClose === null && onCloseCallback) {
setOnClose(onCloseCallback)
}

const menuProps = {
...rest,
document.addEventListener('keydown', handleGlobalKeyPress, true)

return () => {
document.removeEventListener('keydown', handleGlobalKeyPress, true)
}
}, [anchorEl, listRef.current])

return (
<StyledPaper {...paperProps} elevation="raised">
<MenuList {...menuProps} ref={useCombinedRefs(ref, listRef)}>
{children}
</MenuList>
</StyledPaper>
)
},
)
const handleGlobalKeyPress = (e: KeyboardEvent) => {
const { key } = e

switch (key) {
case 'Escape':
onClose()
break
default:
break
}
}

const paperProps = {
...position,
open,
isPositioned,
}

const menuProps = {
...rest,
}

return (
<StyledPaper {...paperProps} elevation="raised">
<MenuList {...menuProps} ref={useCombinedRefs(ref, listRef)}>
{children}
</MenuList>
</StyledPaper>
)
})

// Menu.displayName = 'EdsMenu'
4 changes: 2 additions & 2 deletions libraries/core-react/src/Menu/MenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ const Content = styled.div`
align-items: center;
`

export type Props = {
export type MenuItemProps = {
/** @ignore */
index?: number
/** Is active */
Expand All @@ -98,7 +98,7 @@ export type Props = {
} & React.HTMLAttributes<HTMLLIElement>

export const MenuItem = React.memo(
React.forwardRef<HTMLLIElement, Props>(function EdsMenuItem(
React.forwardRef<HTMLLIElement, MenuItemProps>(function MenuItem(
{ children, disabled, index = 0, onClick, ...rest },
ref,
) {
Expand Down
Loading

0 comments on commit 677093f

Please sign in to comment.