Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/sweet-icons-stare.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@primer/react": patch
---

ActionBar: The overflow menu was earlier bootlegged with heavily customised ActionList. This is being replaced with ActionMenu which is cleaner and more robust.
150 changes: 149 additions & 1 deletion packages/react/src/drafts/ActionBar/ActionBar.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import React from 'react'
import type {Meta} from '@storybook/react'
import ActionBar from '.'
import Text from '../../Text'
import {
PencilIcon,
BoldIcon,
CodeIcon,
ItalicIcon,
Expand All @@ -14,12 +16,14 @@ 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, Avatar, ActionMenu, IconButton, ActionList} from '../..'
import {Divider} from '../../deprecated/ActionList/Divider'
import mockData from '../SelectPanel2/mock-story-data'

export default {
title: 'Drafts/Components/ActionBar',
Expand Down Expand Up @@ -169,3 +173,147 @@ export const ActionBarWithMenuTrigger = () => {
</Box>
)
}

export const ActionbarToggle = () => {
const descriptionStyles = {
borderWidth: 1,
borderStyle: 'solid',
borderColor: 'border.default',
p: 3,
}
const topSectionStyles = {
bg: 'canvas.subtle',
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
borderBottomWidth: 1,
borderStyle: 'solid',
borderColor: 'border.default',
p: 3,
}
const bottomSectionStyles = {
p: 3,
}
const loginName = mockData.collaborators[1].login
const [showEditView, setEditView] = React.useState(false)
const [description /*, setDescription*/] = React.useState('')
const anchorRef = React.useRef(null)
return (
<Box sx={descriptionStyles}>
<Box sx={topSectionStyles}>
<Box>
<Avatar src={`https://github.com/${loginName}.png`} size={30} />
<Text as="strong" sx={{marginLeft: 2, marginRight: 2}}>
{loginName}
</Text>
<Text>opened this issue 2 hours ago</Text>
</Box>
<Box>
<ActionMenu>
<ActionMenu.Anchor ref={anchorRef}>
<IconButton icon={ThreeBarsIcon} aria-label="Open Menu" />
</ActionMenu.Anchor>
<ActionMenu.Overlay>
<ActionList>
<ActionList.Item>
<ActionList.LeadingVisual>
<LinkIcon />
</ActionList.LeadingVisual>
Copy Link
</ActionList.Item>
<ActionList.Item>
<ActionList.LeadingVisual>
<QuoteIcon />
</ActionList.LeadingVisual>
Quote reply
</ActionList.Item>
<ActionList.Divider />
<ActionList.Item onClick={() => setEditView(true)}>
<ActionList.LeadingVisual>
<PencilIcon />
</ActionList.LeadingVisual>
Edit
</ActionList.Item>
</ActionList>
</ActionMenu.Overlay>
</ActionMenu>
</Box>
</Box>
<Box sx={bottomSectionStyles}>
{showEditView ? (
<Box>
<CommentBox />
<Box sx={{display: 'flex', flexDirection: 'row', justifyContent: 'flex-end', p: 2, gap: 2}}>
<Button
variant="primary"
onClick={() => {
setEditView(false)
}}
>
Save
</Button>
<Button variant="danger" onClick={() => setEditView(false)}>
Cancel
</Button>
</Box>
</Box>
) : (
<Box>{description ? description : 'No description Provided'}</Box>
)}
</Box>
</Box>
)
}

export const MultipleActionBars = () => {
const [showFirstCommentBox, setShowFirstCommentBox] = React.useState(false)
const [showSecondCommentBox, setShowSecondCommentBox] = React.useState(false)
return (
<Box>
<Box sx={{p: 3}}>
{showFirstCommentBox ? (
<Box>
<CommentBox key={1} />
<Box sx={{display: 'flex', flexDirection: 'row', justifyContent: 'flex-end', p: 2, gap: 2}}>
<Button
variant="primary"
onClick={() => {
setShowFirstCommentBox(false)
}}
>
Save
</Button>
<Button variant="danger" onClick={() => setShowFirstCommentBox(false)}>
Cancel
</Button>
</Box>
</Box>
) : (
<Button onClick={() => setShowFirstCommentBox(true)}>Show first commentBox</Button>
)}
</Box>
<Box sx={{p: 3}}>
{showSecondCommentBox ? (
<Box>
<CommentBox key={2} />
<Box sx={{display: 'flex', flexDirection: 'row', justifyContent: 'flex-end', p: 2, gap: 2}}>
<Button
variant="primary"
onClick={() => {
setShowSecondCommentBox(false)
}}
>
Save
</Button>
<Button variant="danger" onClick={() => setShowSecondCommentBox(false)}>
Cancel
</Button>
</Box>
</Box>
) : (
<Button onClick={() => setShowSecondCommentBox(true)}>Show second commentBox</Button>
)}
</Box>
</Box>
)
}
128 changes: 46 additions & 82 deletions packages/react/src/drafts/ActionBar/ActionBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {useOnOutsideClick} from '../../hooks/useOnOutsideClick'
import type {IconButtonProps} from '../../Button'
import {IconButton} from '../../Button'
import Box from '../../Box'
import {ActionMenu} from '../..'

type ChildSize = {
text: string
Expand Down Expand Up @@ -46,13 +47,7 @@ const NavigationList = styled.div`
${sx};
`

const MORE_BTN_HEIGHT = 45
const GAP = 8
const MoreMenuListItem = styled.li`
display: flex;
align-items: center;
height: ${MORE_BTN_HEIGHT}px;
`

const listStyles = {
display: 'flex',
Expand All @@ -67,29 +62,15 @@ const listStyles = {
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 = () => ({
const navStyles = {
display: 'flex',
paddingX: 3,
justifyContent: 'flex-end',
align: 'row',
alignItems: 'center',
maxHeight: '32px',
})
}

const menuItemStyles = {
textDecoration: 'none',
Expand Down Expand Up @@ -192,7 +173,6 @@ export const ActionBar: React.FC<React.PropsWithChildren<ActionBarProps>> = prop
const moreMenuRef = useRef<HTMLLIElement>(null)
const moreMenuBtnRef = useRef<HTMLButtonElement>(null)
const containerRef = React.useRef<HTMLUListElement>(null)
const disclosureWidgetId = React.useId()

const validChildren = getValidChildren(children)
// Responsive props object manages which items are in the list and which items are in the menu.
Expand Down Expand Up @@ -231,13 +211,6 @@ export const ActionBar: React.FC<React.PropsWithChildren<ActionBarProps>> = prop
moreMenuBtnRef.current?.focus()
}, [])

const onAnchorClick = useCallback((event: React.MouseEvent<HTMLButtonElement>) => {
if (event.defaultPrevented || event.button !== 0) {
return
}
setIsWidgetOpen(isWidgetOpen => !isWidgetOpen)
}, [])

useOnEscapePress(
(event: KeyboardEvent) => {
if (isWidgetOpen) {
Expand All @@ -253,61 +226,52 @@ export const ActionBar: React.FC<React.PropsWithChildren<ActionBarProps>> = prop

return (
<ActionBarContext.Provider value={{size, setChildrenWidth}}>
<Box ref={navRef} sx={getNavStyles()}>
<Box ref={navRef} sx={navStyles}>
<NavigationList sx={listStyles} ref={listRef} role="toolbar">
{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>
</MoreMenuListItem>
<ActionMenu>
<ActionMenu.Anchor>
<IconButton sx={moreBtnStyles} aria-label={`More ${ariaLabel} items`} icon={KebabHorizontalIcon} />
</ActionMenu.Anchor>
<ActionMenu.Overlay>
<ActionList>
{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.Overlay>
</ActionMenu>
)}
</NavigationList>
</Box>
Expand Down