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

Fix action bar design #9503

Merged
merged 5 commits into from
Apr 2, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Fix style of action item that is not a link
Links need empty href to be styled properly.
Rewrite component to function component.
  • Loading branch information
felixfbecker committed Apr 2, 2020
commit 06c4064a7a996bd389baa587455bb3d0a142eb5a
33 changes: 22 additions & 11 deletions shared/src/actions/__snapshots__/ActionItem.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
exports[`ActionItem actionItem variant 1`] = `
<a
aria-label="d"
className="action-item action-item--variant-action-item "
className="action-item action-item--variant-action-item"
data-tooltip="d"
href=""
onAuxClick={[Function]}
onClick={[Function]}
onKeyPress={[Function]}
Expand All @@ -24,8 +25,9 @@ exports[`ActionItem actionItem variant 1`] = `
exports[`ActionItem non-actionItem variant 1`] = `
<a
aria-label="d"
className="action-item "
className="action-item"
data-tooltip="d"
href=""
onAuxClick={[Function]}
onClick={[Function]}
onKeyPress={[Function]}
Expand All @@ -45,7 +47,8 @@ exports[`ActionItem non-actionItem variant 1`] = `
exports[`ActionItem non-pressed actionItem 1`] = `
<a
aria-pressed={false}
className="action-item action-item--variant-action-item "
className="action-item action-item--variant-action-item"
href=""
onAuxClick={[Function]}
onClick={[Function]}
onKeyPress={[Function]}
Expand Down Expand Up @@ -75,7 +78,8 @@ exports[`ActionItem noop command 1`] = `
exports[`ActionItem pressed toggle actionItem 1`] = `
<a
aria-pressed={true}
className="action-item action-item--variant-action-item action-item--pressed "
className="action-item action-item--variant-action-item action-item--pressed"
href=""
onAuxClick={[Function]}
onClick={[Function]}
onKeyPress={[Function]}
Expand All @@ -89,7 +93,7 @@ exports[`ActionItem pressed toggle actionItem 1`] = `

exports[`ActionItem render as link for "open" command 1`] = `
<a
className="action-item "
className="action-item"
href="https://example.com/bar"
onClick={[Function]}
onKeyPress={[Function]}
Expand All @@ -103,7 +107,7 @@ exports[`ActionItem render as link for "open" command 1`] = `

exports[`ActionItem render as link with icon for "open" command with different origin 1`] = `
<a
className="action-item "
className="action-item"
href="https://other.com/foo"
onClick={[Function]}
onKeyPress={[Function]}
Expand All @@ -121,6 +125,7 @@ exports[`ActionItem run command 1`] = `
aria-label="d"
className="action-item action-item--variant-action-item disabled"
data-tooltip="d"
href=""
onAuxClick={[Function]}
onClick={[Function]}
onKeyPress={[Function]}
Expand All @@ -140,8 +145,9 @@ exports[`ActionItem run command 1`] = `
exports[`ActionItem run command 2`] = `
<a
aria-label="d"
className="action-item action-item--variant-action-item "
className="action-item action-item--variant-action-item"
data-tooltip="d"
href=""
onAuxClick={[Function]}
onClick={[Function]}
onKeyPress={[Function]}
Expand All @@ -161,8 +167,9 @@ exports[`ActionItem run command 2`] = `
exports[`ActionItem run command with error 1`] = `
<a
aria-label="d"
className="action-item action-item--variant-action-item "
className="action-item action-item--variant-action-item"
data-tooltip="d"
href=""
onAuxClick={[Function]}
onClick={[Function]}
onKeyPress={[Function]}
Expand All @@ -182,8 +189,9 @@ exports[`ActionItem run command with error 1`] = `
exports[`ActionItem run command with error with showInlineError 1`] = `
<a
aria-label="Error: x"
className="action-item action-item--variant-action-item "
className="action-item action-item--variant-action-item"
data-tooltip="Error: x"
href=""
onAuxClick={[Function]}
onClick={[Function]}
onKeyPress={[Function]}
Expand All @@ -205,6 +213,7 @@ exports[`ActionItem run command with showLoadingSpinnerDuringExecution 1`] = `
aria-label="d"
className="action-item action-item--loading action-item--variant-action-item disabled"
data-tooltip="d"
href=""
onAuxClick={[Function]}
onClick={[Function]}
onKeyPress={[Function]}
Expand All @@ -231,8 +240,9 @@ exports[`ActionItem run command with showLoadingSpinnerDuringExecution 1`] = `
exports[`ActionItem run command with showLoadingSpinnerDuringExecution 2`] = `
<a
aria-label="d"
className="action-item action-item--variant-action-item "
className="action-item action-item--variant-action-item"
data-tooltip="d"
href=""
onAuxClick={[Function]}
onClick={[Function]}
onKeyPress={[Function]}
Expand All @@ -252,8 +262,9 @@ exports[`ActionItem run command with showLoadingSpinnerDuringExecution 2`] = `
exports[`ActionItem title element 1`] = `
<a
aria-label="d"
className="action-item action-item--variant-action-item "
className="action-item action-item--variant-action-item"
data-tooltip="d"
href=""
onAuxClick={[Function]}
onClick={[Function]}
onKeyPress={[Function]}
Expand Down
105 changes: 57 additions & 48 deletions shared/src/components/LinkOrButton.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import * as H from 'history'
import * as React from 'react'
import React, { useCallback } from 'react'
import { Key } from 'ts-key-enum'
import { Link } from './Link'
import classNames from 'classnames'
import { noop } from 'lodash'

const isSelectKeyPress = (event: React.KeyboardEvent): boolean =>
event.key === Key.Enter && !event.ctrlKey && !event.shiftKey && !event.metaKey && !event.altKey

interface Props {
/** The link destination URL. */
Expand Down Expand Up @@ -41,58 +46,62 @@ interface Props {
* It is keyboard accessible: unlike <Link> or <a>, pressing the enter key triggers it. Unlike
* <button>, it shows a focus ring.
*/
export class LinkOrButton extends React.PureComponent<Props> {
public render(): JSX.Element | null {
const className = `${this.props.className === undefined ? 'nav-link' : this.props.className} ${
this.props.disabled ? 'disabled' : ''
}`

const commonProps: React.AnchorHTMLAttributes<HTMLAnchorElement> & {
'data-tooltip': string | undefined
onAuxClick?: React.MouseEventHandler<HTMLAnchorElement>
} = {
className,
'data-tooltip': this.props['data-tooltip'],
'aria-label': this.props['data-tooltip'],
role: typeof this.props.pressed === 'boolean' ? 'button' : undefined,
'aria-pressed': this.props.pressed,
tabIndex: 0,
onClick: this.onAnchorClick,
onKeyPress: this.onAnchorKeyPress,
}
export const LinkOrButton: React.FunctionComponent<Props> = ({
className = 'nav-link',
to,
target,
disabled,
pressed,
'data-tooltip': tooltip,
onSelect = noop,
children,
}) => {
// We need to set up a keypress listener because <a onclick> doesn't get
// triggered by enter.
const onAnchorKeyPress: React.KeyboardEventHandler<HTMLAnchorElement> = useCallback(
event => {
if (isSelectKeyPress(event)) {
onSelect(event)
}
},
[onSelect]
)

if (!this.props.to) {
// Use onAuxClick so that middle-clicks are caught.
commonProps.onAuxClick = this.onAnchorClick
const commonProps: React.AnchorHTMLAttributes<HTMLAnchorElement> & {
'data-tooltip': string | undefined
} = {
className: classNames(className, disabled && 'disabled'),
'data-tooltip': tooltip,
'aria-label': tooltip,
role: typeof pressed === 'boolean' ? 'button' : undefined,
'aria-pressed': pressed,
tabIndex: 0,
onClick: onSelect,
onKeyPress: onAnchorKeyPress,
}

// Render using an <a> with no href, so that we get a focus ring (when using Bootstrap).
// We need to set up a keypress listener because <a onclick> doesn't get triggered by
// enter.
return <a {...commonProps}>{this.props.children}</a>
}
const onClickPreventDefault: React.MouseEventHandler<HTMLAnchorElement> = useCallback(
event => {
// Prevent default action of reloading page because of empty href
event.preventDefault()
onSelect(event)
},
[onSelect]
)

if (!to) {
return (
<Link {...commonProps} to={this.props.to} target={this.props.target}>
{this.props.children}
</Link>
// Need empty href for styling reasons
// Use onAuxClick so that middle-clicks are caught.
<a href="" {...commonProps} onClick={onClickPreventDefault} onAuxClick={onClickPreventDefault}>
{children}
</a>
)
}

private onAnchorClick: React.MouseEventHandler<HTMLAnchorElement> = e => {
if (this.props.onSelect) {
this.props.onSelect(e)
}
}

private onAnchorKeyPress: React.KeyboardEventHandler<HTMLAnchorElement> = e => {
if (isSelectKeyPress(e)) {
if (this.props.onSelect) {
this.props.onSelect(e)
}
}
}
}

function isSelectKeyPress(e: React.KeyboardEvent): boolean {
return e.key === Key.Enter && !e.ctrlKey && !e.shiftKey && !e.metaKey && !e.altKey
return (
<Link {...commonProps} to={to} target={target}>
{children}
</Link>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

exports[`LinkOrButton render a button when "to" is undefined 1`] = `
<a
className="nav-link "
className="nav-link"
href=""
onAuxClick={[Function]}
onClick={[Function]}
onKeyPress={[Function]}
Expand All @@ -14,7 +15,7 @@ exports[`LinkOrButton render a button when "to" is undefined 1`] = `

exports[`LinkOrButton render a link when "to" is set 1`] = `
<a
className="nav-link "
className="nav-link"
href="http://example.com"
onClick={[Function]}
onKeyPress={[Function]}
Expand Down