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

[ButtonBase] Better keyboard focused story #11090

Merged
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
22 changes: 12 additions & 10 deletions docs/src/pages/demos/buttons/ButtonBases.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,20 @@ const styles = theme => ({
width: '100% !important', // Overrides inline-style
height: 100,
},
'&:hover': {
'&:hover, &$focusVisible': {
zIndex: 1,
},
'&:hover $imageBackdrop': {
opacity: 0.15,
},
'&:hover $imageMarked': {
opacity: 0,
},
'&:hover $imageTitle': {
border: '4px solid currentColor',
'& $imageBackdrop': {
opacity: 0.15,
},
'& $imageMarked': {
opacity: 0,
},
'& $imageTitle': {
border: '4px solid currentColor',
},
},
},
focusVisible: {},
imageButton: {
position: 'absolute',
left: 0,
Expand Down Expand Up @@ -104,6 +105,7 @@ function ButtonBases(props) {
focusRipple
key={image.title}
className={classes.image}
focusVisibleClassName={classes.focusVisible}
style={{
width: image.width,
}}
Expand Down
4 changes: 2 additions & 2 deletions packages/material-ui/src/AppBar/AppBar.d.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { PropTypes, StandardProps } from '..';
import { PaperProps, PaperClassKey } from '../Paper';
import { PaperProps } from '../Paper';

export interface AppBarProps extends StandardProps<PaperProps, AppBarClassKey> {
color?: PropTypes.Color;
position?: 'fixed' | 'absolute' | 'sticky' | 'static';
}

export type AppBarClassKey =
| PaperClassKey
| 'root'
| 'positionFixed'
| 'positionAbsolute'
| 'positionSticky'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react';
import { StandardProps } from '..';
import { ButtonBaseProps, ButtonBaseClassKey } from '../ButtonBase';
import { ButtonBaseProps } from '../ButtonBase';

export interface BottomNavigationActionProps
extends StandardProps<ButtonBaseProps, BottomNavigationActionClassKey, 'onChange'> {
Expand All @@ -13,12 +13,7 @@ export interface BottomNavigationActionProps
value?: any;
}

export type BottomNavigationActionClassKey =
| ButtonBaseClassKey
| 'selected'
| 'iconOnly'
| 'wrapper'
| 'label';
export type BottomNavigationActionClassKey = 'root' | 'selected' | 'iconOnly' | 'wrapper' | 'label';

declare const BottomNavigationAction: React.ComponentType<BottomNavigationActionProps>;

Expand Down
6 changes: 3 additions & 3 deletions packages/material-ui/src/Button/Button.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react';
import { StandardProps, PropTypes } from '..';
import { ButtonBaseProps, ButtonBaseClassKey } from '../ButtonBase';
import { ButtonBaseProps } from '../ButtonBase';

export interface ButtonProps extends StandardProps<ButtonBaseProps, ButtonClassKey, 'component'> {
color?: PropTypes.Color;
Expand All @@ -17,13 +17,13 @@ export interface ButtonProps extends StandardProps<ButtonBaseProps, ButtonClassK
}

export type ButtonClassKey =
| ButtonBaseClassKey
| 'root'
| 'label'
| 'flatPrimary'
| 'flatSecondary'
| 'colorInherit'
| 'raised'
| 'keyboardFocused'
| 'focusVisible'
| 'raisedPrimary'
| 'raisedSecondary'
| 'disabled'
Expand Down
13 changes: 8 additions & 5 deletions packages/material-ui/src/Button/Button.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export const styles = theme => ({
color: theme.palette.getContrastText(theme.palette.grey[300]),
backgroundColor: theme.palette.grey[300],
boxShadow: theme.shadows[2],
'&$keyboardFocused': {
'&$focusVisible': {
boxShadow: theme.shadows[6],
},
'&:active': {
Expand Down Expand Up @@ -113,7 +113,7 @@ export const styles = theme => ({
},
},
},
keyboardFocused: {},
focusVisible: {},
disabled: {},
fab: {
borderRadius: '50%',
Expand Down Expand Up @@ -157,6 +157,7 @@ function Button(props) {
disabled,
disableFocusRipple,
fullWidth,
focusVisibleClassName,
mini,
size,
variant,
Expand Down Expand Up @@ -189,9 +190,7 @@ function Button(props) {
className={className}
disabled={disabled}
focusRipple={!disableFocusRipple}
classes={{
keyboardFocused: classes.keyboardFocused,
}}
focusVisibleClassName={classNames(classes.focusVisible, focusVisibleClassName)}
{...other}
>
<span className={classes.label}>{children}</span>
Expand Down Expand Up @@ -235,6 +234,10 @@ Button.propTypes = {
* If `true`, the ripple effect will be disabled.
*/
disableRipple: PropTypes.bool,
/**
* @ignore
*/
focusVisibleClassName: PropTypes.string,
/**
* If `true`, the button will take up the full width of its container.
*/
Expand Down
3 changes: 2 additions & 1 deletion packages/material-ui/src/ButtonBase/ButtonBase.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ export interface ButtonBaseProps
component?: React.ReactType<ButtonBaseProps>;
disableRipple?: boolean;
focusRipple?: boolean;
focusVisibleClassName?: string;
onKeyboardFocus?: React.FocusEventHandler<any>;
TouchRippleProps?: Partial<TouchRippleProps>;
}

export type ButtonBaseClassKey = 'root' | 'disabled' | 'keyboardFocused';
export type ButtonBaseClassKey = 'root' | 'disabled';

declare const ButtonBase: React.ComponentType<ButtonBaseProps>;

Expand Down
45 changes: 23 additions & 22 deletions packages/material-ui/src/ButtonBase/ButtonBase.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ export const styles = {
},
},
disabled: {},
keyboardFocused: {},
};

/**
Expand All @@ -51,18 +50,18 @@ export const styles = {
*/
class ButtonBase extends React.Component {
static getDerivedStateFromProps(nextProps, prevState) {
if (typeof prevState.keyboardFocused === 'undefined') {
if (typeof prevState.focusVisible === 'undefined') {
return {
keyboardFocused: false,
focusVisible: false,
lastDisabled: nextProps.disabled,
};
}

// The blur won't fire when the disabled state is set on a focused input.
// We need to book keep the focused state manually.
if (!prevState.prevState && nextProps.disabled && prevState.keyboardFocused) {
if (!prevState.prevState && nextProps.disabled && prevState.focusVisible) {
return {
keyboardFocused: false,
focusVisible: false,
lastDisabled: nextProps.disabled,
};
}
Expand All @@ -83,8 +82,8 @@ class ButtonBase extends React.Component {
if (
this.props.focusRipple &&
!this.props.disableRipple &&
!prevState.keyboardFocused &&
this.state.keyboardFocused
!prevState.focusVisible &&
this.state.focusVisible
) {
this.ripple.pulsate();
}
Expand All @@ -97,7 +96,7 @@ class ButtonBase extends React.Component {

onKeyboardFocusHandler = event => {
this.keyDown = false;
this.setState({ keyboardFocused: true });
this.setState({ focusVisible: true });

if (this.props.onKeyboardFocus) {
this.props.onKeyboardFocus(event);
Expand All @@ -120,13 +119,7 @@ class ButtonBase extends React.Component {
const key = keycode(event);

// Check if key is already down to avoid repeats being counted as multiple activations
if (
focusRipple &&
!this.keyDown &&
this.state.keyboardFocused &&
this.ripple &&
key === 'space'
) {
if (focusRipple && !this.keyDown && this.state.focusVisible && this.ripple && key === 'space') {
this.keyDown = true;
event.persist();
this.ripple.stop(event, () => {
Expand Down Expand Up @@ -157,7 +150,7 @@ class ButtonBase extends React.Component {
this.props.focusRipple &&
keycode(event) === 'space' &&
this.ripple &&
this.state.keyboardFocused
this.state.focusVisible
) {
this.keyDown = false;
event.persist();
Expand All @@ -170,15 +163,15 @@ class ButtonBase extends React.Component {

handleMouseDown = createRippleHandler(this, 'MouseDown', 'start', () => {
clearTimeout(this.keyboardFocusTimeout);
if (this.state.keyboardFocused) {
this.setState({ keyboardFocused: false });
if (this.state.focusVisible) {
this.setState({ focusVisible: false });
}
});

handleMouseUp = createRippleHandler(this, 'MouseUp', 'stop');

handleMouseLeave = createRippleHandler(this, 'MouseLeave', 'stop', event => {
if (this.state.keyboardFocused) {
if (this.state.focusVisible) {
event.preventDefault();
}
});
Expand All @@ -191,8 +184,8 @@ class ButtonBase extends React.Component {

handleBlur = createRippleHandler(this, 'Blur', 'stop', () => {
clearTimeout(this.keyboardFocusTimeout);
if (this.state.keyboardFocused) {
this.setState({ keyboardFocused: false });
if (this.state.focusVisible) {
this.setState({ focusVisible: false });
}
});

Expand Down Expand Up @@ -227,6 +220,7 @@ class ButtonBase extends React.Component {
disabled,
disableRipple,
focusRipple,
focusVisibleClassName,
onBlur,
onFocus,
onKeyboardFocus,
Expand All @@ -248,7 +242,7 @@ class ButtonBase extends React.Component {
classes.root,
{
[classes.disabled]: disabled,
[classes.keyboardFocused]: this.state.keyboardFocused,
[focusVisibleClassName]: this.state.focusVisible,
},
classNameProp,
);
Expand Down Expand Up @@ -340,6 +334,13 @@ ButtonBase.propTypes = {
* `disableRipple` must also be `false`.
*/
focusRipple: PropTypes.bool,
/**
* This property can help a person know which element has the keyboard focus.
* The class name will be applied when the element gain the focus throught a keyboard interaction.
* It's a polyfill for the [CSS :focus-visible feature](https://drafts.csswg.org/selectors-4/#the-focus-visible-pseudo).
* The rational for using this feature [is explain here](https://github.com/WICG/focus-visible/blob/master/explainer.md).
*/
focusVisibleClassName: PropTypes.string,
/**
* @ignore
*/
Expand Down
Loading