Skip to content

Commit

Permalink
[ButtonBase] Better keyboard focused story (#11090)
Browse files Browse the repository at this point in the history
  • Loading branch information
oliviertassinari authored Apr 21, 2018
1 parent d2ec00d commit 1d9e576
Show file tree
Hide file tree
Showing 34 changed files with 116 additions and 135 deletions.
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

0 comments on commit 1d9e576

Please sign in to comment.