Skip to content

Commit 392a3ca

Browse files
authored
ActionBar: Add stories for couple of edge cases (#4424)
* Add stories for couple of edge cases * Create an Actionmenu in the more button instead of bootlegged menu * Clean up * Create sweet-icons-stare.md
1 parent 4e281b2 commit 392a3ca

File tree

3 files changed

+200
-83
lines changed

3 files changed

+200
-83
lines changed

.changeset/sweet-icons-stare.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@primer/react": patch
3+
---
4+
5+
ActionBar: The overflow menu was earlier bootlegged with heavily customised ActionList. This is being replaced with ActionMenu which is cleaner and more robust.

packages/react/src/drafts/ActionBar/ActionBar.stories.tsx

Lines changed: 149 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import React from 'react'
22
import type {Meta} from '@storybook/react'
33
import ActionBar from '.'
4+
import Text from '../../Text'
45
import {
6+
PencilIcon,
57
BoldIcon,
68
CodeIcon,
79
ItalicIcon,
@@ -14,12 +16,14 @@ import {
1416
ListOrderedIcon,
1517
TasklistIcon,
1618
ReplyIcon,
19+
ThreeBarsIcon,
1720
} from '@primer/octicons-react'
1821
import {MarkdownInput} from '../MarkdownEditor/_MarkdownInput'
1922
import {ViewSwitch} from '../MarkdownEditor/_ViewSwitch'
2023
import type {MarkdownViewMode} from '../MarkdownEditor/_ViewSwitch'
21-
import {Box, Dialog, Button} from '../..'
24+
import {Box, Dialog, Button, Avatar, ActionMenu, IconButton, ActionList} from '../..'
2225
import {Divider} from '../../deprecated/ActionList/Divider'
26+
import mockData from '../SelectPanel2/mock-story-data'
2327

2428
export default {
2529
title: 'Drafts/Components/ActionBar',
@@ -169,3 +173,147 @@ export const ActionBarWithMenuTrigger = () => {
169173
</Box>
170174
)
171175
}
176+
177+
export const ActionbarToggle = () => {
178+
const descriptionStyles = {
179+
borderWidth: 1,
180+
borderStyle: 'solid',
181+
borderColor: 'border.default',
182+
p: 3,
183+
}
184+
const topSectionStyles = {
185+
bg: 'canvas.subtle',
186+
display: 'flex',
187+
flexDirection: 'row',
188+
justifyContent: 'space-between',
189+
borderBottomWidth: 1,
190+
borderStyle: 'solid',
191+
borderColor: 'border.default',
192+
p: 3,
193+
}
194+
const bottomSectionStyles = {
195+
p: 3,
196+
}
197+
const loginName = mockData.collaborators[1].login
198+
const [showEditView, setEditView] = React.useState(false)
199+
const [description /*, setDescription*/] = React.useState('')
200+
const anchorRef = React.useRef(null)
201+
return (
202+
<Box sx={descriptionStyles}>
203+
<Box sx={topSectionStyles}>
204+
<Box>
205+
<Avatar src={`https://github.com/${loginName}.png`} size={30} />
206+
<Text as="strong" sx={{marginLeft: 2, marginRight: 2}}>
207+
{loginName}
208+
</Text>
209+
<Text>opened this issue 2 hours ago</Text>
210+
</Box>
211+
<Box>
212+
<ActionMenu>
213+
<ActionMenu.Anchor ref={anchorRef}>
214+
<IconButton icon={ThreeBarsIcon} aria-label="Open Menu" />
215+
</ActionMenu.Anchor>
216+
<ActionMenu.Overlay>
217+
<ActionList>
218+
<ActionList.Item>
219+
<ActionList.LeadingVisual>
220+
<LinkIcon />
221+
</ActionList.LeadingVisual>
222+
Copy Link
223+
</ActionList.Item>
224+
<ActionList.Item>
225+
<ActionList.LeadingVisual>
226+
<QuoteIcon />
227+
</ActionList.LeadingVisual>
228+
Quote reply
229+
</ActionList.Item>
230+
<ActionList.Divider />
231+
<ActionList.Item onClick={() => setEditView(true)}>
232+
<ActionList.LeadingVisual>
233+
<PencilIcon />
234+
</ActionList.LeadingVisual>
235+
Edit
236+
</ActionList.Item>
237+
</ActionList>
238+
</ActionMenu.Overlay>
239+
</ActionMenu>
240+
</Box>
241+
</Box>
242+
<Box sx={bottomSectionStyles}>
243+
{showEditView ? (
244+
<Box>
245+
<CommentBox />
246+
<Box sx={{display: 'flex', flexDirection: 'row', justifyContent: 'flex-end', p: 2, gap: 2}}>
247+
<Button
248+
variant="primary"
249+
onClick={() => {
250+
setEditView(false)
251+
}}
252+
>
253+
Save
254+
</Button>
255+
<Button variant="danger" onClick={() => setEditView(false)}>
256+
Cancel
257+
</Button>
258+
</Box>
259+
</Box>
260+
) : (
261+
<Box>{description ? description : 'No description Provided'}</Box>
262+
)}
263+
</Box>
264+
</Box>
265+
)
266+
}
267+
268+
export const MultipleActionBars = () => {
269+
const [showFirstCommentBox, setShowFirstCommentBox] = React.useState(false)
270+
const [showSecondCommentBox, setShowSecondCommentBox] = React.useState(false)
271+
return (
272+
<Box>
273+
<Box sx={{p: 3}}>
274+
{showFirstCommentBox ? (
275+
<Box>
276+
<CommentBox key={1} />
277+
<Box sx={{display: 'flex', flexDirection: 'row', justifyContent: 'flex-end', p: 2, gap: 2}}>
278+
<Button
279+
variant="primary"
280+
onClick={() => {
281+
setShowFirstCommentBox(false)
282+
}}
283+
>
284+
Save
285+
</Button>
286+
<Button variant="danger" onClick={() => setShowFirstCommentBox(false)}>
287+
Cancel
288+
</Button>
289+
</Box>
290+
</Box>
291+
) : (
292+
<Button onClick={() => setShowFirstCommentBox(true)}>Show first commentBox</Button>
293+
)}
294+
</Box>
295+
<Box sx={{p: 3}}>
296+
{showSecondCommentBox ? (
297+
<Box>
298+
<CommentBox key={2} />
299+
<Box sx={{display: 'flex', flexDirection: 'row', justifyContent: 'flex-end', p: 2, gap: 2}}>
300+
<Button
301+
variant="primary"
302+
onClick={() => {
303+
setShowSecondCommentBox(false)
304+
}}
305+
>
306+
Save
307+
</Button>
308+
<Button variant="danger" onClick={() => setShowSecondCommentBox(false)}>
309+
Cancel
310+
</Button>
311+
</Box>
312+
</Box>
313+
) : (
314+
<Button onClick={() => setShowSecondCommentBox(true)}>Show second commentBox</Button>
315+
)}
316+
</Box>
317+
</Box>
318+
)
319+
}

packages/react/src/drafts/ActionBar/ActionBar.tsx

Lines changed: 46 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {useOnOutsideClick} from '../../hooks/useOnOutsideClick'
1313
import type {IconButtonProps} from '../../Button'
1414
import {IconButton} from '../../Button'
1515
import Box from '../../Box'
16+
import {ActionMenu} from '../..'
1617

1718
type ChildSize = {
1819
text: string
@@ -46,13 +47,7 @@ const NavigationList = styled.div`
4647
${sx};
4748
`
4849

49-
const MORE_BTN_HEIGHT = 45
5050
const GAP = 8
51-
const MoreMenuListItem = styled.li`
52-
display: flex;
53-
align-items: center;
54-
height: ${MORE_BTN_HEIGHT}px;
55-
`
5651

5752
const listStyles = {
5853
display: 'flex',
@@ -67,29 +62,15 @@ const listStyles = {
6762
position: 'relative',
6863
}
6964

70-
const menuStyles = {
71-
position: 'absolute',
72-
zIndex: 1,
73-
top: '90%',
74-
right: '0',
75-
boxShadow: '0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24)',
76-
borderRadius: '12px',
77-
backgroundColor: 'canvas.overlay',
78-
listStyle: 'none',
79-
// Values are from ActionMenu
80-
minWidth: '192px',
81-
maxWidth: '640px',
82-
}
83-
8465
const MORE_BTN_WIDTH = 86
85-
const getNavStyles = () => ({
66+
const navStyles = {
8667
display: 'flex',
8768
paddingX: 3,
8869
justifyContent: 'flex-end',
8970
align: 'row',
9071
alignItems: 'center',
9172
maxHeight: '32px',
92-
})
73+
}
9374

9475
const menuItemStyles = {
9576
textDecoration: 'none',
@@ -192,7 +173,6 @@ export const ActionBar: React.FC<React.PropsWithChildren<ActionBarProps>> = prop
192173
const moreMenuRef = useRef<HTMLLIElement>(null)
193174
const moreMenuBtnRef = useRef<HTMLButtonElement>(null)
194175
const containerRef = React.useRef<HTMLUListElement>(null)
195-
const disclosureWidgetId = React.useId()
196176

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

234-
const onAnchorClick = useCallback((event: React.MouseEvent<HTMLButtonElement>) => {
235-
if (event.defaultPrevented || event.button !== 0) {
236-
return
237-
}
238-
setIsWidgetOpen(isWidgetOpen => !isWidgetOpen)
239-
}, [])
240-
241214
useOnEscapePress(
242215
(event: KeyboardEvent) => {
243216
if (isWidgetOpen) {
@@ -253,61 +226,52 @@ export const ActionBar: React.FC<React.PropsWithChildren<ActionBarProps>> = prop
253226

254227
return (
255228
<ActionBarContext.Provider value={{size, setChildrenWidth}}>
256-
<Box ref={navRef} sx={getNavStyles()}>
229+
<Box ref={navRef} sx={navStyles}>
257230
<NavigationList sx={listStyles} ref={listRef} role="toolbar">
258231
{listItems}
259232
{menuItems.length > 0 && (
260-
<MoreMenuListItem ref={moreMenuRef}>
261-
<IconButton
262-
ref={moreMenuBtnRef}
263-
sx={moreBtnStyles}
264-
aria-controls={disclosureWidgetId}
265-
aria-expanded={isWidgetOpen}
266-
onClick={onAnchorClick}
267-
aria-label={`More ${ariaLabel} items`}
268-
icon={KebabHorizontalIcon}
269-
/>
270-
<ActionList
271-
ref={containerRef}
272-
id={disclosureWidgetId}
273-
sx={menuStyles}
274-
style={{display: isWidgetOpen ? 'block' : 'none'}}
275-
>
276-
{menuItems.map((menuItem, index) => {
277-
if (menuItem.type === ActionList.Divider) {
278-
return <ActionList.Divider key={index} />
279-
} else {
280-
const {
281-
children: menuItemChildren,
282-
//'aria-current': ariaCurrent,
283-
onClick,
284-
icon: Icon,
285-
'aria-label': ariaLabel,
286-
} = menuItem.props
287-
return (
288-
<ActionList.LinkItem
289-
key={menuItemChildren}
290-
sx={menuItemStyles}
291-
onClick={(
292-
event: React.MouseEvent<HTMLAnchorElement> | React.KeyboardEvent<HTMLAnchorElement>,
293-
) => {
294-
closeOverlay()
295-
focusOnMoreMenuBtn()
296-
typeof onClick === 'function' && onClick(event)
297-
}}
298-
>
299-
{Icon ? (
300-
<ActionList.LeadingVisual>
301-
<Icon />
302-
</ActionList.LeadingVisual>
303-
) : null}
304-
{ariaLabel}
305-
</ActionList.LinkItem>
306-
)
307-
}
308-
})}
309-
</ActionList>
310-
</MoreMenuListItem>
233+
<ActionMenu>
234+
<ActionMenu.Anchor>
235+
<IconButton sx={moreBtnStyles} aria-label={`More ${ariaLabel} items`} icon={KebabHorizontalIcon} />
236+
</ActionMenu.Anchor>
237+
<ActionMenu.Overlay>
238+
<ActionList>
239+
{menuItems.map((menuItem, index) => {
240+
if (menuItem.type === ActionList.Divider) {
241+
return <ActionList.Divider key={index} />
242+
} else {
243+
const {
244+
children: menuItemChildren,
245+
//'aria-current': ariaCurrent,
246+
onClick,
247+
icon: Icon,
248+
'aria-label': ariaLabel,
249+
} = menuItem.props
250+
return (
251+
<ActionList.LinkItem
252+
key={menuItemChildren}
253+
sx={menuItemStyles}
254+
onClick={(
255+
event: React.MouseEvent<HTMLAnchorElement> | React.KeyboardEvent<HTMLAnchorElement>,
256+
) => {
257+
closeOverlay()
258+
focusOnMoreMenuBtn()
259+
typeof onClick === 'function' && onClick(event)
260+
}}
261+
>
262+
{Icon ? (
263+
<ActionList.LeadingVisual>
264+
<Icon />
265+
</ActionList.LeadingVisual>
266+
) : null}
267+
{ariaLabel}
268+
</ActionList.LinkItem>
269+
)
270+
}
271+
})}
272+
</ActionList>
273+
</ActionMenu.Overlay>
274+
</ActionMenu>
311275
)}
312276
</NavigationList>
313277
</Box>

0 commit comments

Comments
 (0)