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

[SpeedDial] Allow tooltip to always be displayed #12590

Merged
merged 14 commits into from
Sep 3, 2018
110 changes: 110 additions & 0 deletions docs/src/pages/lab/speed-dial/SpeedDialTooltipOpen.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
import Button from '@material-ui/core/Button';
import SpeedDial from '@material-ui/lab/SpeedDial';
import SpeedDialIcon from '@material-ui/lab/SpeedDialIcon';
import SpeedDialAction from '@material-ui/lab/SpeedDialAction';
import FileCopyIcon from '@material-ui/icons/FileCopyOutlined';
import SaveIcon from '@material-ui/icons/Save';
import PrintIcon from '@material-ui/icons/Print';
import ShareIcon from '@material-ui/icons/Share';
import DeleteIcon from '@material-ui/icons/Delete';

const styles = theme => ({
root: {
height: 380,
},
speedDial: {
position: 'absolute',
bottom: theme.spacing.unit * 2,
right: theme.spacing.unit * 3,
},
});

const actions = [
{ icon: <FileCopyIcon />, name: 'Copy' },
{ icon: <SaveIcon />, name: 'Save' },
{ icon: <PrintIcon />, name: 'Print' },
{ icon: <ShareIcon />, name: 'Share' },
{ icon: <DeleteIcon />, name: 'Delete' },
];

class SpeedDialTooltipOpen extends React.Component {
state = {
open: false,
hidden: false,
};

handleVisibility = () => {
this.setState(state => ({
open: false,
hidden: !state.hidden,
}));
};

handleClick = () => {
this.setState(state => ({
open: !state.open,
}));
};

handleOpen = () => {
if (!this.state.hidden) {
this.setState({
open: true,
});
}
};

handleClose = () => {
this.setState({
open: false,
});
};

render() {
const { classes } = this.props;
const { hidden, open } = this.state;

let isTouch;
if (typeof document !== 'undefined') {
isTouch = 'ontouchstart' in document.documentElement;
}

return (
<div className={classes.root}>
<Button onClick={this.handleVisibility}>Toggle Speed Dial</Button>
<SpeedDial
ariaLabel="SpeedDial example"
className={classes.speedDial}
hidden={hidden}
icon={<SpeedDialIcon />}
onBlur={this.handleClose}
onClick={this.handleClick}
onClose={this.handleClose}
onFocus={isTouch ? undefined : this.handleOpen}
onMouseEnter={isTouch ? undefined : this.handleOpen}
onMouseLeave={this.handleClose}
open={open}
>
{actions.map(action => (
<SpeedDialAction
key={action.name}
icon={action.icon}
tooltipTitle={action.name}
tooltipOpen
onClick={this.handleClick}
/>
))}
</SpeedDial>
</div>
);
}
}

SpeedDialTooltipOpen.propTypes = {
classes: PropTypes.object.isRequired,
};

export default withStyles(styles)(SpeedDialTooltipOpen);
8 changes: 8 additions & 0 deletions docs/src/pages/lab/speed-dial/speed-dial.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,11 @@ You can provide an alternate icon for the closed and open states using the `icon
of the `SpeedDialIcon` component.

{{"demo": "pages/lab/speed-dial/OpenIconSpeedDial.js"}}

## Persistant action tooltips

The SpeedDialActions tooltips can be be displayed persistently so that users don't have to long-press in order to see the tooltip on touch devices.

It is enabled here across all devices for demo purposes, but in production it could use the `isTouch` logic to conditionally set the property.

{{"demo": "pages/lab/speed-dial/SpeedDialTooltipOpen.js"}}
18 changes: 18 additions & 0 deletions packages/material-ui-lab/src/SpeedDialAction/SpeedDialAction.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,26 @@ class SpeedDialAction extends React.Component {
};

handleTooltipClose = () => {
if (this.props.tooltipOpen) return;
this.setState({ tooltipOpen: false });
};

handleTooltipOpen = () => {
if (this.props.tooltipOpen) return;
this.setState({ tooltipOpen: true });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hashwin I was just looking at this and this seems brittle since other methods can still change the state of tooltipOpen. Not sure if we can do anything about this though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see your point, but yeah I don't really see another way of handling this without the state and state itself can be changed from anywhere in the component. In fact, we need to manually set the state here for this feature to work: https://github.com/mui-org/material-ui/pull/12590/files#diff-3c8c197bf9114d5f5a42f05daae04497R59

One thing we can maybe do here is to change the method name from handleTooltipOpen to something like onHover so that it explicitly says that nothing should be done on hover if the prop tooltipOpen is given. Thoughts?

};

componentDidUpdate = prevProps => {
if (!this.props.tooltipOpen || prevProps.open === this.props.open) return;
if (!this.props.open && this.state.tooltipOpen) {
this.setState({ tooltipOpen: false });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you see a way to move this to getDerivedStateFromProps and save a rendercycle?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

} else if (!this.state.tooltipOpen) {
this.timeout = setTimeout(() => this.setState({ tooltipOpen: true }), this.props.delay + 100);
}
};

componentWillUnmount = () => this.timeout && clearTimeout(this.timeout);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just call clearTimeout. It won't complain about non-ids but will cause issues in browsers that actually use 0 as ids.


render() {
const {
ButtonProps,
Expand All @@ -56,6 +69,7 @@ class SpeedDialAction extends React.Component {
open,
tooltipTitle,
tooltipPlacement,
tooltipOpen,
...other
} = this.props;

Expand Down Expand Up @@ -125,6 +139,10 @@ SpeedDialAction.propTypes = {
* @ignore
*/
open: PropTypes.bool,
/**
* Make the tooltip always visible, regardless of hover.
*/
tooltipOpen: PropTypes.bool,
/**
* Placement of the tooltip.
*/
Expand Down
1 change: 1 addition & 0 deletions pages/lab/api/speed-dial-action.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ title: SpeedDialAction API
| <span class="prop-name">classes</span> | <span class="prop-type">object |   | Useful to extend the style applied to components. |
| <span class="prop-name">delay</span> | <span class="prop-type">number | <span class="prop-default">0</span> | Adds a transition delay, to allow a series of SpeedDialActions to be animated. |
| <span class="prop-name required">icon *</span> | <span class="prop-type">node |   | The Icon to display in the SpeedDial Floating Action Button. |
| <span class="prop-name">tooltipOpen</span> | <span class="prop-type">bool |   | Make the tooltip always visible, regardless of hover. |
| <span class="prop-name">tooltipPlacement</span> | <span class="prop-type">string | <span class="prop-default">'left'</span> | Placement of the tooltip. |
| <span class="prop-name">tooltipTitle</span> | <span class="prop-type">node |   | Label to display in the tooltip. |

Expand Down
7 changes: 7 additions & 0 deletions pages/lab/speed-dial.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ module.exports = require('fs')
raw: preval`
module.exports = require('fs')
.readFileSync(require.resolve('docs/src/pages/lab/speed-dial/OpenIconSpeedDial'), 'utf8')
`,
},
'pages/lab/speed-dial/SpeedDialTooltipOpen.js': {
js: require('docs/src/pages/lab/speed-dial/SpeedDialTooltipOpen').default,
raw: preval`
module.exports = require('fs')
.readFileSync(require.resolve('docs/src/pages/lab/speed-dial/SpeedDialTooltipOpen'), 'utf8')
`,
},
}}
Expand Down