Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Actionbar tlc #5540

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
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
14 changes: 14 additions & 0 deletions packages/react/src/ActionBar/ActionBar.docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,20 @@
"required": false,
"description": "Size of the action bar",
"defaultValue": ""
},
{
"name": "flush",
"type": "boolean",
"required": false,
"description": "Allows ActionBar to be flush with the container",
"defaultValue": "false"
},
{
"name": "className",
"type": "string",
"required": false,
"description": "Custom className",
"defaultValue": ""
}
],
"subcomponents": [
Expand Down
35 changes: 35 additions & 0 deletions packages/react/src/ActionBar/ActionBar.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
.List {
position: relative;
display: flex;
min-width: 0;

/* wonder why this is here */
/* stylelint-disable-next-line primer/spacing */
margin-bottom: -1px;
white-space: nowrap;
list-style: none;
align-items: center;
gap: var(--base-size-8);
}

.Nav {
display: flex;
padding-inline: var(--base-size-16);
justify-content: flex-end;
align-items: center;

&:where([data-flush='true']) {
padding-inline: 0;
}
}

.Divider {
&::before {
display: block;
width: var(--borderWidth-thin);
height: var(--base-size-20);
content: '';
/* stylelint-disable-next-line primer/colors */
background: var(--borderColor-muted);
}
}
35 changes: 33 additions & 2 deletions packages/react/src/ActionBar/ActionBar.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React from 'react'
import type {Meta} from '@storybook/react'
import ActionBar from '.'
import {
BoldIcon,
Expand All @@ -13,11 +12,43 @@ import {
ListOrderedIcon,
TasklistIcon,
} from '@primer/octicons-react'
import type {Meta, StoryObj} from '@storybook/react'

export default {
const meta: Meta<typeof ActionBar> = {
title: 'Experimental/Components/ActionBar',
} as Meta<typeof ActionBar>

export default meta
type Story = StoryObj<typeof ActionBar>

export const Playground: Story = {
render: args => (
<ActionBar {...args}>
<ActionBar.IconButton icon={BoldIcon} aria-label="Bold"></ActionBar.IconButton>
<ActionBar.IconButton icon={ItalicIcon} aria-label="Italic"></ActionBar.IconButton>
<ActionBar.Divider />
<ActionBar.IconButton icon={CodeIcon} aria-label="Code"></ActionBar.IconButton>
</ActionBar>
),
}
Playground.argTypes = {
size: {
control: {
type: 'radio',
},
options: ['small', 'medium', 'large'],
},
flush: {
control: {
type: 'boolean',
},
},
}
Playground.args = {
size: 'medium',
flush: false,
}

export const Default = () => (
<ActionBar aria-label="Toolbar">
<ActionBar.IconButton icon={BoldIcon} aria-label="Bold"></ActionBar.IconButton>
Expand Down
101 changes: 16 additions & 85 deletions packages/react/src/ActionBar/ActionBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,17 @@ import React, {useState, useCallback, useRef, forwardRef} from 'react'
import {KebabHorizontalIcon} from '@primer/octicons-react'
import {ActionList} from '../ActionList'
import useIsomorphicLayoutEffect from '../utils/useIsomorphicLayoutEffect'
import styled from 'styled-components'
import sx from '../sx'
import {useOnEscapePress} from '../hooks/useOnEscapePress'
import type {ResizeObserverEntry} from '../hooks/useResizeObserver'
import {useResizeObserver} from '../hooks/useResizeObserver'

import {useOnOutsideClick} from '../hooks/useOnOutsideClick'
import type {IconButtonProps} from '../Button'
import {IconButton} from '../Button'
import Box from '../Box'
import {ActionMenu} from '../ActionMenu'
import {useFocusZone, FocusKeys} from '../hooks/useFocusZone'
import styles from './ActionBar.module.css'
import {clsx} from 'clsx'

type ChildSize = {
text: string
Expand Down Expand Up @@ -43,54 +42,13 @@ type A11yProps =
export type ActionBarProps = {
size?: Size
children: React.ReactNode
flush?: boolean
className?: string
} & A11yProps

export type ActionBarIconButtonProps = IconButtonProps

const NavigationList = styled.div`
${sx};
`

const GAP = 8

const listStyles = {
display: 'flex',
minWidth: 0,
listStyle: 'none',
whiteSpace: 'nowrap',
paddingY: 0,
paddingX: 0,
margin: 0,
marginBottom: '-1px',
alignItems: 'center',
gap: `${GAP}px`,
position: 'relative',
}

const MORE_BTN_WIDTH = 86
const navStyles = {
display: 'flex',
paddingX: 3,
justifyContent: 'flex-end',
align: 'row',
alignItems: 'center',
maxHeight: '32px',
}

const menuItemStyles = {
textDecoration: 'none',
}

const moreBtnStyles = {
//set margin 0 here because safari puts extra margin around the button, rest is to reset style to make it look like a list element
margin: 0,
border: 0,
background: 'transparent',
fontWeight: 'normal',
boxShadow: 'none',
paddingY: 1,
paddingX: 2,
}

const getValidChildren = (children: React.ReactNode) => {
return React.Children.toArray(children).filter(child => {
Expand Down Expand Up @@ -168,7 +126,7 @@ const overflowEffect = (
}

export const ActionBar: React.FC<React.PropsWithChildren<ActionBarProps>> = props => {
const {size = 'medium', children, 'aria-label': ariaLabel} = props
const {size = 'medium', children, 'aria-label': ariaLabel, flush = false, className} = props
const [childWidthArray, setChildWidthArray] = useState<ChildWidthArray>([])
const setChildrenWidth = useCallback((size: ChildSize) => {
setChildWidthArray(arr => {
Expand Down Expand Up @@ -243,34 +201,25 @@ export const ActionBar: React.FC<React.PropsWithChildren<ActionBarProps>> = prop

return (
<ActionBarContext.Provider value={{size, setChildrenWidth}}>
<Box ref={navRef} sx={navStyles}>
<NavigationList sx={listStyles} ref={listRef} role="toolbar">
<div ref={navRef} className={clsx(className, styles.Nav)} data-flush={flush}>
<div ref={listRef} role="toolbar" className={styles.List}>
{listItems}
{menuItems.length > 0 && (
<ActionMenu>
<ActionMenu.Anchor>
<IconButton sx={moreBtnStyles} aria-label={`More ${ariaLabel} items`} icon={KebabHorizontalIcon} />
<IconButton variant="invisible" 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
const {children: menuItemChildren, onClick, icon: Icon, 'aria-label': ariaLabel} = menuItem.props
return (
<ActionList.LinkItem
<ActionList.Item
key={menuItemChildren}
sx={menuItemStyles}
onClick={(
event: React.MouseEvent<HTMLAnchorElement> | React.KeyboardEvent<HTMLAnchorElement>,
) => {
onClick={(event: React.MouseEvent<HTMLLIElement, MouseEvent>) => {
closeOverlay()
focusOnMoreMenuBtn()
typeof onClick === 'function' && onClick(event)
Expand All @@ -282,16 +231,16 @@ export const ActionBar: React.FC<React.PropsWithChildren<ActionBarProps>> = prop
</ActionList.LeadingVisual>
) : null}
{ariaLabel}
</ActionList.LinkItem>
</ActionList.Item>
)
}
})}
</ActionList>
</ActionMenu.Overlay>
</ActionMenu>
)}
</NavigationList>
</Box>
</div>
</div>
</ActionBarContext.Provider>
)
}
Expand All @@ -308,31 +257,13 @@ export const ActionBarIconButton = forwardRef((props: ActionBarIconButtonProps,
return <IconButton ref={ref} size={size} {...props} variant="invisible" />
})

const sizeToHeight = {
small: '24px',
medium: '28px',
large: '32px',
}
export const VerticalDivider = () => {
const ref = useRef<HTMLDivElement>(null)
const {size, setChildrenWidth} = React.useContext(ActionBarContext)
const {setChildrenWidth} = React.useContext(ActionBarContext)
useIsomorphicLayoutEffect(() => {
const text = 'divider'
const domRect = (ref as MutableRefObject<HTMLElement>).current.getBoundingClientRect()
setChildrenWidth({text, width: domRect.width})
}, [ref, setChildrenWidth])
return (
<Box
ref={ref}
data-component="ActionBar.VerticalDivider"
aria-hidden="true"
sx={{
display: 'inline-block',
borderLeft: '1px solid',
borderColor: 'actionListItem.inlineDivider',
height: sizeToHeight[size],
mx: 2,
}}
/>
)
return <div ref={ref} data-component="ActionBar.VerticalDivider" aria-hidden="true" className={styles.Divider} />
}
Loading