[SpeedDial] New component#9539
Conversation
| dialog: 1500, | ||
| layer: 2000, | ||
| popover: 2100, | ||
| speedDial: 2800, |
|
Support both! |
oliviertassinari
left a comment
There was a problem hiding this comment.
A first batch of comment.
| @@ -0,0 +1,83 @@ | |||
| // @flow weak | |||
There was a problem hiding this comment.
We have removed flow from the demos.
| import PropTypes from 'prop-types'; | ||
| import { withStyles } from 'material-ui/styles'; | ||
| import Button from 'material-ui/Button'; | ||
| import SpeedDialButton from 'material-ui/SpeedDial/SpeedDialButton'; |
There was a problem hiding this comment.
It's a good occasion to think of:
import SpeedDial from 'material-ui/SpeedDial';
import SpeedDialButton from 'material-ui/SpeedDialButton';There was a problem hiding this comment.
Sure, if that's agreed. (I'm supporting it, but lets give others a chance to have a say).
There was a problem hiding this comment.
I like the way it's exported below, so it can be imported like:
import SpeedDial, { SpeedDialButton } from 'material-ui/SpeedDial';This still groups imports by the component they're used with.
| import ShareIcon from 'material-ui-icons/Share'; | ||
| import DeleteIcon from 'material-ui-icons/Delete'; | ||
|
|
||
| const styles = () => ({ |
There was a problem hiding this comment.
No need for the function, it can be an object.
| .readFileSync(require.resolve('docs/src/pages/demos/buttons/FloatingActionButtons'), 'utf8') | ||
| `, | ||
| }, | ||
|
|
| import React from 'react'; | ||
| import PropTypes from 'prop-types'; | ||
| import { withStyles } from 'material-ui/styles'; | ||
| import classNames from 'classnames'; |
There was a problem hiding this comment.
I try to sort the import by scope. Maybe you would want to do the same and to keep it before the material-ui imports.
| width: 56, | ||
| textAlign: 'center', | ||
| zIndex: theme.zIndex.speedDial, | ||
| transition: 'transform 0.1s ease-in-out', |
There was a problem hiding this comment.
Shouldn't we use our transition creator (for animation curve consistency)?
| transition: 'transform 0.1s ease-in-out', | ||
| }, | ||
| hidden: { | ||
| transform: 'scale(0, 0)', |
There was a problem hiding this comment.
Does it need to be 2D?
|
|
||
| return ( | ||
| <div className={classes.root} {...other}> | ||
| <Tooltip title={tooltip} placement="left"> |
There was a problem hiding this comment.
We might want to add a TooltipProps and a ButtonProps property.
|
|
||
| const children = React.Children.map(childrenProp, (child, childIndex) => { | ||
| if (!React.isValidElement(child)) return null; | ||
| const delay = 30 * (open ? childrenProp.length - childIndex : childIndex); |
There was a problem hiding this comment.
Humm. I would rather be incrementing a counter to account for the null children that can be provided.
There was a problem hiding this comment.
Could you explain a bit?
| const rootClassName = classNames( | ||
| classes.root, | ||
| hidden && classes.hidden, | ||
| hidden && classes.hidden, |
|
Did you benchmark any other implementations? |
| @@ -0,0 +1,144 @@ | |||
| // @flow weak | |||
| import { withStyles } from 'material-ui/styles'; | ||
| import Button from 'material-ui/Button'; | ||
| import Icon from 'material-ui/Icon'; | ||
| import AddIcon from 'material-ui-icons/Add'; |
There was a problem hiding this comment.
No, we don't rely on material-ui-icons. It needs to be an internal svg icons. We already have ~6 of them.
There was a problem hiding this comment.
Oops, I should know that! (SpeedDial started as a docs demo, and grew from there, so was using originally material-ui-icons)
|
|
||
| const children = React.Children.map(childrenProp, (child, childIndex) => { | ||
| if (!React.isValidElement(child)) return null; | ||
| const delay = 30 * (open ? childIndex : childrenProp.length - childIndex); |
There was a problem hiding this comment.
My point was that the index can jump from 0, 1 to 3,4,5 if we have two null children along the way.
Hence, the delay will be wrong.
There was a problem hiding this comment.
Struggling to imagine a situation where that would be the case? If somone's using other than SpeedDialButton, that's an error. Could you give an example?
| return ( | ||
| <div className={rootClassName} {...other}> | ||
| <Button | ||
| id={label} |
There was a problem hiding this comment.
Humm. I'm wondering about id escaping here. string -> id might not be as straightforward.
There was a problem hiding this comment.
Mmm... good point. I originally had a separate prop, but was trying to keep the API surface down. I've added a regex to filter the label.
| /** | ||
| * If `true`, the SpeedDial is open. | ||
| */ | ||
| open: PropTypes.bool, |
There was a problem hiding this comment.
Should it be required?
| @@ -0,0 +1,99 @@ | |||
| // @flow weak | |||
There was a problem hiding this comment.
Oops, thought I had done. Done now.
| import MenuList from 'src/Menu/MenuList'; | ||
| import MenuItem from 'src/Menu/MenuItem'; | ||
| import MenuList from 'src/MenuList/MenuList'; | ||
| import MenuItem from 'src/MenuItem/MenuItem'; |
There was a problem hiding this comment.
Bah, sorry, stray change from the flat-src-dir experiment. I need to be more careful with what git add . adds (or doesn't - I see how that got missed from the other commit...)
|
@mbrooks This is true benchmarking 💃 ! |
|
@oliviertassinari I've added up/down arrow key support. I solved the accessibility dilemma (should up/down follow the visual presentation, or the DOM structure) by supporting both. Whichever key ('up', or 'down') is used to move focus from the base FAB to the first action is thereafter the "next" key, with the opposite focusing the previous action, (or the base FAB if there is no previous action). It's pretty intuitive in use, and worth the extra few lines of code, and use of state IMHO. Additional review feedback is also fixed (I hope!) in a657b7b . Regarding the API, are you happy with Also, should Edit 2: This is done: ~~Edit: also, should the SpeedDialButton icon be its child, as it is now, or an |
| const styles = { | ||
| speedDial: { | ||
| position: 'fixed', | ||
| bottom: 20, |
There was a problem hiding this comment.
bottom: theme.spacing.unit, so we get as much geometric symmetry as possible. Same for right.
| root: { | ||
| width: 56, | ||
| textAlign: 'center', | ||
| zIndex: theme.zIndex.speedDial, |
|
|
||
| const styles = theme => ({ | ||
| root: { | ||
| width: 56, |
There was a problem hiding this comment.
I don't think that it's needed.
| const styles = theme => ({ | ||
| root: { | ||
| width: 56, | ||
| textAlign: 'center', |
There was a problem hiding this comment.
I don't think that it's needed.
| flexDirection: 'column-reverse', // Display the first action at the bottom. | ||
| marginBottom: 16, | ||
| }, | ||
| closedActions: { |
There was a problem hiding this comment.
For consistency with the other classes names on the codebase: actionsClosed
| const actionsClassName = classNames(classes.actions, { [classes.closedActions]: !open }); | ||
| const children = React.Children.map(childrenProp, (child, childIndex) => { | ||
| if (!React.isValidElement(child)) return null; | ||
| const delay = 30 * (open ? childIndex : childrenProp.length - childIndex); |
There was a problem hiding this comment.
Missing index counter to counterbalance the potential null children.
| <div | ||
| id={`${id}-actions`} | ||
| className={actionsClassName} | ||
| aria-labelledby={id} |
There was a problem hiding this comment.
From what I can see on the Inbox implementation, the aria-labeledby should be targeting an element with some text. Here. I think that you can remove this property and the regexp that comes with it.
There was a problem hiding this comment.
I may be mistaken, but I understood that this would in turn respect the label applied with aria-label to the element referenced by id - in our case the aria-label appied to the base Button based on the label prop.
There was a problem hiding this comment.
@oliviertassinari Reading around, I think you're right. I've removed it.
| root: { | ||
| position: 'relative', | ||
| }, | ||
| actionButton: { |
There was a problem hiding this comment.
maybe button is enough.
| opacity: 1, | ||
| color: theme.palette.text.secondary, | ||
| }, | ||
| closedActionButton: { |
| }), | ||
| }, | ||
| hiddenFab: { | ||
| transform: 'scale3d(0, 0, 1)', |
There was a problem hiding this comment.
Why using scale 3d and not?
transform: 'scale(0)',There was a problem hiding this comment.
Bacause I used scale, and you asked why I wasn't using scale3d. LOL!
|
@oliviertassinari I've made an attempt to deal with null children. It required two counters, as we need the total number of valid children to calculate the reverse timing for the closing animation. I'm wide-open to alternative implementations. |
| /** | ||
| * @ignore - internal component. | ||
| */ | ||
| let Add = props => ( |
| import PropTypes from 'prop-types'; | ||
| import { withStyles } from 'material-ui/styles'; | ||
| import Button from 'material-ui/Button'; | ||
| import SpeedDialButton from 'material-ui/SpeedDial/SpeedDialButton'; |
There was a problem hiding this comment.
I like the way it's exported below, so it can be imported like:
import SpeedDial, { SpeedDialButton } from 'material-ui/SpeedDial';This still groups imports by the component they're used with.
| icon = <Icon>{iconProp}</Icon>; | ||
| } | ||
| } else { | ||
| icon = <AddIcon className={fabIconClassName} />; |
There was a problem hiding this comment.
It would be nice to be able to use a different icon when hovered (e.g. a plus when not hovered and a pen icon when hovered, like in Google Inbox). Maybe by adding an activeIcon prop? 🤔
| const actionsClassName = classNames(classes.actions, { [classes.actionsClosed]: !open }); | ||
|
|
||
| // Filter the label for valid id characters. | ||
| const id = label.replace(/^[^a-z]+|[^\w:.-]+/gi, ''); |
There was a problem hiding this comment.
So... a unique label is required?
There was a problem hiding this comment.
Yes. The alternative was an explicit id prop, but I was trying to minimize the API surface. Given that you'd only ever have one SpeedDial visible, and if there's more than one they presumably have different purposes, it didn't seem like a major limitation, but I should probably add a note to the prop docs.
| Add = pure(Add); | ||
| Add.muiName = 'SvgIcon'; | ||
|
|
||
| export default Add; |
There was a problem hiding this comment.
Unrelated to this PR, but would it be possible to import these icons from material-ui-icons instead of copying them?
There was a problem hiding this comment.
We'd end up with a circular dependancy (material-ui-icons depends on material-uo/SvgIcon), so not sure how that would work out in practice.
There was a problem hiding this comment.
Oh, you're right. I guess this would be possible by npm linking packages/material-ui into packages/material-ui-icons (tools like lerna might even do this automagically, but I'm not sure). Maybe we should keep it as is then.
There was a problem hiding this comment.
yarn workspace is another option.
|
@leMaik |
| }); | ||
|
|
||
| return ( | ||
| <div> |
There was a problem hiding this comment.
For the demos. Have you considered the option to embedded the element in the demo block?
Right now, we have two demos. I can show both speed dial. They overlap.
| @@ -0,0 +1,2 @@ | |||
| export { default } from './SpeedDial'; | |||
| export { default as SpeedDialButton } from './SpeedDialButton'; | |||
| data-mui-test="SpeedDialButton" | ||
| {...ButtonProps} | ||
| > | ||
| <SpeedDialIcon icon={icon} openIcon={openIcon} open={open} {...SpeedDialIconProps} /> |
There was a problem hiding this comment.
What about reversing the abstraction: exposing SpeedDialIcon in the demos instead of the SpeedDialIconProps property? So people keep full control over what they add.
| @@ -1,5 +1,5 @@ | |||
| --- | |||
| components: Button, IconButton, ButtonBase | |||
| components: Button, IconButton, ButtonBase, SpeedDial, SpeedDialButton | |||
|
Regarding down = up: as I mentioned when you raised this concern before, this is by design. Please see: #9539 (comment) When I saw your screenshot I thought you were now saying it loops from top to bottom, but just realise when I couldn't reproduce it that it's just your gif looping! (How are you taking a screenshot that doesn't inclue the time it takes to stop the recording? 😄) |
|
@oliviertassinari Regarding the API, are you happy with |
|
@mbrookes The material specification is using the "action" wording on the bottom navigation page too. I would vote for |
|
Cool, thanks! I was leaning towards Action for SpeedDial, so great that BottomNav is the same. I'll change that in a separate PR. |
…ction (#9692) This is for consistency with the spec, and with the upcoming SpeedDial component. Motivated by: #9539 (comment) ### Breaking change -Rename the `BottomNavigationButton` component ```diff -import BottomNavigation, { BottomNavigationButton } from 'material-ui/BottomNavigation'; +import BottomNavigation, { BottomNavigationAction } from 'material-ui/BottomNavigation'; ```
[SpeedDial] Fix index.js [SpeedDial] Add to supported components [SpeedDial] Fix most review feedback, reinstate lost commit for flex-direction [SpeedDial] Improve the ARIA labelling Follow: https://www.w3.org/TR/wai-aria-practices/examples/menu-button/menu-button-links.html [SpeedDial] Simplify layout [SpeedDial] Add keyboard support [SpeedDial] Support bi-directional arrow keys [SpeedDial] Use relative imports [SpeedDialButton] Give delay a default and make it optional (easier to test) [SpeedDial] Use icon prop instead of children [SpeedDial] Add some basic tests [SpeedDial] Add regression test [SpeedDial] Revert some unintended changes [SpeedDial] add defaults for bool props [SpeedDialButton] Handle enter / space key with onClick The Tooltip wasn't closing when a SpeedDialButton was selected using the enter / space key. By usiing a controlled Tooltip, linking the open state of the Tooltip to the open state of the actions, and triggering the onClick handler when a relevent key event occurs, we can ensure that the Tooltip closes when an action is initiated by key press. [SpeedDial] Add support for open on hover / focus [SpeedDial] Add API docs [SpeedDial] Tidy up tests Note: 'SpeedDialButton should call handleTooltipOpen & handleTooltipClose on mouseOver & blur' test is failing. Not sure why, or how to debug... [SpeedDial] Add support for null children [SpeedDial] Move Add icon to internal/svg-icons [SpeedDial] Move Add icon to internal/svg-icons [docs] Use theme.spacing.unit for SpeedDial fixed positioning [SpeedDialButton] Simplify class names {SpeedDial] Fix additional review feedback. Thanks @oliviertassinari! [SpeedDial] Improve ARIA labelling [SpeedDial] Add escape key support Follows: https://www.w3.org/TR/wai-aria-practices/examples/menu-button/menu-button-links.html [SpeedDial] Test for fab ref I have *no* clue if this is the right way to go about this! [SpeedDial] Update API docs [SpeedDial] Add openIcon prop & docs demo [SpeedDial] Use Fade transition This prevents focus conflicts when more than one SpeedDial is present but hidden. [SpeedDial] Refactor icon code into spearate component, and fix tests [SpeedDial] Update API docs [SpeedDial] Expose the SpeedDialIcon component [SpeedDial] Rename label prop to ariaLabel to better reflect its purpose Also remove the API docs comment regarding unique names, as using the Fade transition with mitigates this issue. [SpeedDial] Render within Demo container [SpeedDial] Add Zoom transition component [SpeedDial] Persist the chosen key direction [SpeedDial] better zIndex [SpeedDial] Fix tests failing after rebase, and code changes [SpeedDial] Rename SpeedDialButton to SpeedDialAction
|
I was looking at what components still needed to be done for v1 and I saw that the Floating action Button - Toolbar: https://material.io/guidelines/components/buttons-floating-action-button.html#buttons-floating-action-button-transitions I didn't want to try to work on something that was being done so I searched and came across this thread. Now I know it's not the same exact component, but I was wondering though, since these all fold under FOB what if it had one general component to call that you could apply which type it was? Or, is it better to just keep these separate? If so, I would like to work on that Toolbar one if it's ok |
|
@gregnb Thanks for raising this. I could very easily have missed one (it's not like I've checked every app, so please correct me here!), but I'm not aware of any applications that use the FAB Toolbar approach. |
|
@mbrookes Ah ok, I will have to defer to you on this one. I really have not done the research on the FAB Toolbar usage. Was just browsing what was on the todo list for V1 :) |
|
Todo list? |
|
Sorry, this list here of what is supported and what is not: |
|
Ah, right. We don't aim to (directly) support every use case, that's just to show what is supported. |
|
any news on when this gets merged? |
|
post v1 milestone just been added to this issue. That means we won't see it released any soon ? Any help we could provide to accelerate the process of releasing this feature ? |
|
@oliviertassinari Shall I open a PR for the WIP lab package? |
|
@mbrookes Sure :) |
|
Closed for #10288. |
|
I grabbed the pull request and used in my project. Seems there is performance issue in Safari. |
|
@raymondsze The component was pushed on npm under |
|
@raymondsze I'm looking at docs demo in Safari, and not seeing an issue. I'd suggest using the published version as @oliviertassinari mentioned. |



Closes #2851
Thought I would give this its first public airing for feedback and initial code review.
To do:
Complete the keyboard navigation story.d.ts