Skip to content

Commit

Permalink
Use ActionMenu.MenuItemAnchor to implement submenus in ActionBar
Browse files Browse the repository at this point in the history
  • Loading branch information
pksjce committed Mar 21, 2024
1 parent e8223b1 commit 440aa28
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 77 deletions.
11 changes: 10 additions & 1 deletion packages/react/src/drafts/ActionBar/ActionBar.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ import {
ListOrderedIcon,
TasklistIcon,
ReplyIcon,
ThreeBarsIcon,
} from '@primer/octicons-react'
import {MarkdownInput} from '../MarkdownEditor/_MarkdownInput'
import {ViewSwitch} from '../MarkdownEditor/_ViewSwitch'
import type {MarkdownViewMode} from '../MarkdownEditor/_ViewSwitch'
import {Box, Dialog, Button} from '../..'
import {Box, Dialog, Button, ActionList, ActionMenu} from '../..'

Check failure on line 22 in packages/react/src/drafts/ActionBar/ActionBar.stories.tsx

View workflow job for this annotation

GitHub Actions / lint

'ActionMenu' is defined but never used
import {Divider} from '../../deprecated/ActionList/Divider'

export default {
Expand Down Expand Up @@ -63,6 +64,7 @@ export const CommentBox = () => {
const [value, setValue] = React.useState('')
const [isOpen, setIsOpen] = React.useState(false)
const buttonRef = React.useRef(null)
const actionMenuRef = React.useRef(null)

Check failure on line 67 in packages/react/src/drafts/ActionBar/ActionBar.stories.tsx

View workflow job for this annotation

GitHub Actions / lint

'actionMenuRef' is assigned a value but never used
return (
<Box
sx={{
Expand Down Expand Up @@ -116,6 +118,13 @@ export const CommentBox = () => {
icon={ReplyIcon}
aria-label="Saved Replies"
></ActionBar.IconButton>
<ActionBar.SubMenuButton aria-label="Open Random Menu" icon={ThreeBarsIcon}>
<ActionList>
<ActionList.Item>First Item</ActionList.Item>
<ActionList.Item>Second Item</ActionList.Item>
<ActionList.Item>Third Item</ActionList.Item>
</ActionList>
</ActionBar.SubMenuButton>
</ActionBar>
</Box>
</Box>
Expand Down
168 changes: 93 additions & 75 deletions packages/react/src/drafts/ActionBar/ActionBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import type {RefObject, MutableRefObject} from 'react'
import React, {useState, useCallback, useRef, forwardRef} from 'react'
import {KebabHorizontalIcon} from '@primer/octicons-react'
import {ActionList} from '../../ActionList'
import {ActionMenu} from '../../ActionMenu'

import useIsomorphicLayoutEffect from '../../utils/useIsomorphicLayoutEffect'
import styled from 'styled-components'
import sx from '../../sx'
Expand Down Expand Up @@ -67,20 +69,6 @@ const ulStyles = {
position: 'relative',
}

const menuStyles = {
position: 'absolute',
zIndex: 1,
top: '90%',
right: '0',
boxShadow: '0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24)',
borderRadius: '12px',
backgroundColor: 'canvas.overlay',
listStyle: 'none',
// Values are from ActionMenu
minWidth: '192px',
maxWidth: '640px',
}

const MORE_BTN_WIDTH = 86
const getNavStyles = () => ({
display: 'flex',
Expand Down Expand Up @@ -177,6 +165,35 @@ const overflowEffect = (
}
}

export const ActionBarIconButton = forwardRef((props: ActionBarIconButtonProps, forwardedRef) => {
const backupRef = useRef<HTMLElement>(null)
const ref = (forwardedRef ?? backupRef) as RefObject<HTMLAnchorElement>
const {size, setChildrenWidth} = React.useContext(ActionBarContext)
useIsomorphicLayoutEffect(() => {
const text = props['aria-label'] ? props['aria-label'] : ''
const domRect = (ref as MutableRefObject<HTMLElement>).current.getBoundingClientRect()
setChildrenWidth({text, width: domRect.width})
}, [ref, setChildrenWidth])
return <IconButton ref={ref} size={size} {...props} variant="invisible" />
})

type ActionBarSubMenuButtonProps = ActionBarIconButtonProps

export const ActionBarSubMenuButton = forwardRef((props: ActionBarSubMenuButtonProps, forwardedRef) => {
const backupRef = useRef<HTMLElement>(null)
const ref = (forwardedRef ?? backupRef) as RefObject<HTMLAnchorElement>
const actionMenuRef = useRef<HTMLElement>(null)
const {children, ...rest} = props
return (
<ActionMenu>
<ActionMenu.Anchor ref={actionMenuRef}>
<ActionBarIconButton ref={ref} {...rest}></ActionBarIconButton>
</ActionMenu.Anchor>
<ActionMenu.Overlay>{children}</ActionMenu.Overlay>
</ActionMenu>
)
})

export const ActionBar: React.FC<React.PropsWithChildren<ActionBarProps>> = props => {
const {size = 'medium', children, 'aria-label': ariaLabel} = props
const [childWidthArray, setChildWidthArray] = useState<ChildWidthArray>([])
Expand Down Expand Up @@ -258,55 +275,68 @@ export const ActionBar: React.FC<React.PropsWithChildren<ActionBarProps>> = prop
{listItems}
{menuItems.length > 0 && (
<MoreMenuListItem ref={moreMenuRef}>
<IconButton
ref={moreMenuBtnRef}
sx={moreBtnStyles}
aria-controls={disclosureWidgetId}
aria-expanded={isWidgetOpen}
onClick={onAnchorClick}
aria-label={`More ${ariaLabel} items`}
icon={KebabHorizontalIcon}
/>
<ActionList
ref={containerRef}
id={disclosureWidgetId}
sx={menuStyles}
style={{display: isWidgetOpen ? 'block' : 'none'}}
>
{menuItems.map((menuItem, index) => {
if (menuItem.type === ActionList.Divider) {
return <ActionList.Divider key={index} />
} else {
const {
children: menuItemChildren,
//'aria-current': ariaCurrent,
onClick,
icon: Icon,
'aria-label': ariaLabel,
} = menuItem.props
return (
<ActionList.LinkItem
key={menuItemChildren}
sx={menuItemStyles}
onClick={(
event: React.MouseEvent<HTMLAnchorElement> | React.KeyboardEvent<HTMLAnchorElement>,
) => {
closeOverlay()
focusOnMoreMenuBtn()
typeof onClick === 'function' && onClick(event)
}}
>
{Icon ? (
<ActionList.LeadingVisual>
<Icon />
</ActionList.LeadingVisual>
) : null}
{ariaLabel}
</ActionList.LinkItem>
)
}
})}
</ActionList>
<ActionMenu>
<ActionMenu.Anchor ref={moreMenuBtnRef}>
<ActionBarIconButton
aria-label={`More ${ariaLabel} items`}
icon={KebabHorizontalIcon}
></ActionBarIconButton>
</ActionMenu.Anchor>
<ActionMenu.Overlay>
<ActionList>
{menuItems.map((menuItem, index) => {
if (menuItem.type === ActionList.Divider) {
return <ActionList.Divider key={index} />
}
if (menuItem.type === ActionBarSubMenuButton) {
const {children} = menuItem.props
debugger

Check failure on line 293 in packages/react/src/drafts/ActionBar/ActionBar.tsx

View workflow job for this annotation

GitHub Actions / lint

Unexpected 'debugger' statement
const text = menuItem.props['aria-label']
const Icon = menuItem.props['icon']
return (
<ActionMenu>

Check failure on line 297 in packages/react/src/drafts/ActionBar/ActionBar.tsx

View workflow job for this annotation

GitHub Actions / lint

Missing "key" prop for element in iterator
<ActionMenu.MenuItemAnchor>
<ActionList.LeadingVisual>

Check failure on line 299 in packages/react/src/drafts/ActionBar/ActionBar.tsx

View workflow job for this annotation

GitHub Actions / lint

ActionList.LeadingVisual must be a direct child of ActionList.Item or ActionList.LinkItem
<Icon />
</ActionList.LeadingVisual>
{text}
</ActionMenu.MenuItemAnchor>
<ActionMenu.Overlay>{children}</ActionMenu.Overlay>
</ActionMenu>
)
} else {
const {
children: menuItemChildren,
//'aria-current': ariaCurrent,
onClick,
icon: Icon,
'aria-label': ariaLabel,
} = menuItem.props
return (
<ActionList.LinkItem
key={menuItemChildren}
sx={menuItemStyles}
onClick={(
event: React.MouseEvent<HTMLAnchorElement> | React.KeyboardEvent<HTMLAnchorElement>,
) => {
closeOverlay()
focusOnMoreMenuBtn()
typeof onClick === 'function' && onClick(event)
}}
>
{Icon ? (
<ActionList.LeadingVisual>
<Icon />
</ActionList.LeadingVisual>
) : null}
{ariaLabel}
</ActionList.LinkItem>
)
}
})}
</ActionList>
</ActionMenu.Overlay>
</ActionMenu>
</MoreMenuListItem>
)}
</NavigationList>
Expand All @@ -315,18 +345,6 @@ export const ActionBar: React.FC<React.PropsWithChildren<ActionBarProps>> = prop
)
}

export const ActionBarIconButton = forwardRef((props: ActionBarIconButtonProps, forwardedRef) => {
const backupRef = useRef<HTMLElement>(null)
const ref = (forwardedRef ?? backupRef) as RefObject<HTMLAnchorElement>
const {size, setChildrenWidth} = React.useContext(ActionBarContext)
useIsomorphicLayoutEffect(() => {
const text = props['aria-label'] ? props['aria-label'] : ''
const domRect = (ref as MutableRefObject<HTMLElement>).current.getBoundingClientRect()
setChildrenWidth({text, width: domRect.width})
}, [ref, setChildrenWidth])
return <IconButton ref={ref} size={size} {...props} variant="invisible" />
})

const sizeToHeight = {
small: '24px',
medium: '28px',
Expand Down
3 changes: 2 additions & 1 deletion packages/react/src/drafts/ActionBar/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import {ActionBar as Bar, ActionBarIconButton, VerticalDivider} from './ActionBar'
import {ActionBar as Bar, ActionBarIconButton, ActionBarSubMenuButton, VerticalDivider} from './ActionBar'
export type {ActionBarProps} from './ActionBar'

const ActionBar = Object.assign(Bar, {
IconButton: ActionBarIconButton,
Divider: VerticalDivider,
SubMenuButton: ActionBarSubMenuButton,
})

export default ActionBar

0 comments on commit 440aa28

Please sign in to comment.