From 2af3c82e4b51d00ff02818c314c59cd67683b7e8 Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Mon, 9 Jul 2018 00:36:01 +0200 Subject: [PATCH] [Tooltip] Rework the implementation (#12085) --- .size-limit.js | 4 +- docs/src/modules/components/AppFrame.js | 18 +- docs/src/modules/components/Demo.js | 28 +- docs/src/modules/components/withRoot.js | 7 +- .../autocomplete/IntegrationDownshift.js | 33 ++ .../pages/demos/autocomplete/autocomplete.md | 2 +- docs/src/pages/demos/buttons/ButtonSizes.js | 4 +- .../demos/buttons/FloatingActionButtons.js | 8 +- .../pages/demos/dialogs/ConfirmationDialog.js | 2 +- docs/src/pages/demos/drawers/MiniDrawer.js | 2 +- .../pages/demos/drawers/PersistentDrawer.js | 2 +- .../pages/demos/drawers/ResponsiveDrawer.js | 2 +- .../pages/demos/menus/MenuListComposition.js | 99 ++---- docs/src/pages/demos/menus/menus.md | 6 +- .../selection-controls/RadioButtonsGroup.js | 2 +- docs/src/pages/demos/tables/tables.md | 2 +- .../demos/tooltips/ControlledTooltips.js | 13 +- .../demos/tooltips/CustomizedTooltips.js | 126 +++++++ .../src/pages/demos/tooltips/DelayTooltips.js | 13 + .../demos/tooltips/PositionedTooltips.js | 24 +- .../pages/demos/tooltips/SimpleTooltips.js | 6 +- .../demos/tooltips/TransitionsTooltips.js | 23 ++ .../pages/demos/tooltips/TriggersTooltips.js | 64 ++++ docs/src/pages/demos/tooltips/tooltips.md | 22 +- docs/src/pages/discover-more/team/Team.js | 4 +- docs/src/pages/layout/grid/InteractiveGrid.js | 6 +- docs/src/pages/layout/grid/SpacingGrid.js | 2 +- .../utils/click-away-listener/ClickAway.js | 32 +- .../click-away-listener.md | 2 + .../utils/{modals => modal}/SimpleModal.js | 0 .../{modals/modals.md => modal/modal.md} | 4 +- .../{popovers => popover}/AnchorPlayground.js | 14 +- .../pages/utils/popover/MouseOverPopover.js | 70 ++++ .../{popovers => popover}/SimplePopover.js | 0 .../popovers.md => popover/popover.md} | 14 +- .../pages/utils/popovers/MouseOverPopover.js | 113 ------ .../pages/utils/popper/NoTransitionPopper.js | 52 +++ .../pages/utils/popper/PositionedPopper.js | 93 +++++ .../pages/utils/popper/ScrollPlayground.js | 329 ++++++++++++++++++ docs/src/pages/utils/popper/SimplePopper.js | 57 +++ docs/src/pages/utils/popper/popper.md | 30 ++ .../pages/utils/transitions/SimpleCollapse.js | 2 +- .../src/pages/utils/transitions/SimpleGrow.js | 2 +- .../pages/utils/transitions/SimpleSlide.js | 2 +- .../src/pages/utils/transitions/SimpleZoom.js | 2 +- flow-typed/npm/react-popper_vx.x.x.js | 95 ----- .../src/SpeedDial/SpeedDial.js | 2 +- .../src/SpeedDialAction/SpeedDialAction.js | 2 +- packages/material-ui/package.json | 2 +- .../ClickAwayListener/ClickAwayListener.js | 16 +- packages/material-ui/src/Dialog/Dialog.js | 2 +- packages/material-ui/src/Drawer/Drawer.js | 6 +- .../src/ExpansionPanel/ExpansionPanel.js | 2 +- packages/material-ui/src/Menu/Menu.d.ts | 2 +- packages/material-ui/src/Menu/Menu.js | 5 +- packages/material-ui/src/Modal/Modal.d.ts | 7 +- packages/material-ui/src/Modal/Modal.js | 36 +- packages/material-ui/src/Popover/Popover.js | 14 +- packages/material-ui/src/Popper/Popper.d.ts | 29 ++ packages/material-ui/src/Popper/Popper.js | 253 ++++++++++++++ .../material-ui/src/Popper/Popper.test.js | 152 ++++++++ packages/material-ui/src/Popper/index.d.ts | 2 + packages/material-ui/src/Popper/index.js | 1 + packages/material-ui/src/Portal/Portal.d.ts | 2 + packages/material-ui/src/Portal/Portal.js | 49 ++- .../material-ui/src/Portal/Portal.test.js | 11 + packages/material-ui/src/Select/Select.js | 2 +- .../material-ui/src/Select/SelectInput.js | 2 +- packages/material-ui/src/Snackbar/Snackbar.js | 2 +- .../material-ui/src/StepLabel/StepLabel.js | 2 +- .../src/TablePagination/TablePagination.js | 6 +- .../TablePaginationActions.js | 4 +- .../material-ui/src/TextField/TextField.js | 6 +- packages/material-ui/src/Tooltip/Tooltip.d.ts | 14 +- packages/material-ui/src/Tooltip/Tooltip.js | 236 +++++-------- .../material-ui/src/Tooltip/Tooltip.test.js | 219 +++--------- packages/material-ui/src/index.d.ts | 1 + packages/material-ui/src/index.js | 1 + pages/api/click-away-listener.md | 8 +- pages/api/dialog.md | 2 +- pages/api/drawer.md | 6 +- pages/api/expansion-panel.md | 2 +- pages/api/grow.md | 2 +- pages/api/menu.md | 4 +- pages/api/modal.md | 12 +- pages/api/popover.md | 4 +- pages/api/popper.js | 10 + pages/api/popper.md | 35 ++ pages/api/portal.md | 7 +- pages/api/select.md | 2 +- pages/api/snackbar.md | 2 +- pages/api/step-label.md | 2 +- pages/api/table-pagination.md | 6 +- pages/api/text-field.md | 6 +- pages/api/tooltip.md | 9 +- pages/demos/tooltips.js | 28 ++ pages/lab/api/speed-dial-action.md | 2 +- pages/lab/api/speed-dial.md | 2 +- pages/utils/{modals.js => modal.js} | 8 +- pages/utils/popover.js | 37 ++ pages/utils/popovers.js | 37 -- pages/utils/popper.js | 44 +++ test/utils/createDOM.js | 5 +- yarn.lock | 9 +- 104 files changed, 1933 insertions(+), 885 deletions(-) create mode 100644 docs/src/pages/demos/tooltips/CustomizedTooltips.js create mode 100644 docs/src/pages/demos/tooltips/DelayTooltips.js create mode 100644 docs/src/pages/demos/tooltips/TransitionsTooltips.js create mode 100644 docs/src/pages/demos/tooltips/TriggersTooltips.js rename docs/src/pages/utils/{modals => modal}/SimpleModal.js (100%) rename docs/src/pages/utils/{modals/modals.md => modal/modal.md} (93%) rename docs/src/pages/utils/{popovers => popover}/AnchorPlayground.js (96%) create mode 100644 docs/src/pages/utils/popover/MouseOverPopover.js rename docs/src/pages/utils/{popovers => popover}/SimplePopover.js (100%) rename docs/src/pages/utils/{popovers/popovers.md => popover/popover.md} (52%) delete mode 100644 docs/src/pages/utils/popovers/MouseOverPopover.js create mode 100644 docs/src/pages/utils/popper/NoTransitionPopper.js create mode 100644 docs/src/pages/utils/popper/PositionedPopper.js create mode 100644 docs/src/pages/utils/popper/ScrollPlayground.js create mode 100644 docs/src/pages/utils/popper/SimplePopper.js create mode 100644 docs/src/pages/utils/popper/popper.md delete mode 100644 flow-typed/npm/react-popper_vx.x.x.js create mode 100644 packages/material-ui/src/Popper/Popper.d.ts create mode 100644 packages/material-ui/src/Popper/Popper.js create mode 100644 packages/material-ui/src/Popper/Popper.test.js create mode 100644 packages/material-ui/src/Popper/index.d.ts create mode 100644 packages/material-ui/src/Popper/index.js create mode 100644 pages/api/popper.js create mode 100644 pages/api/popper.md rename pages/utils/{modals.js => modal.js} (58%) create mode 100644 pages/utils/popover.js delete mode 100644 pages/utils/popovers.js create mode 100644 pages/utils/popper.js diff --git a/.size-limit.js b/.size-limit.js index 96333c79f22769..547af5fa8291b6 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -27,13 +27,13 @@ module.exports = [ name: 'The size of all the modules of material-ui.', webpack: true, path: 'packages/material-ui/build/index.js', - limit: '95.3 KB', + limit: '94.3 KB', }, { name: 'The main bundle of the docs', webpack: false, path: getMainFile().path, - limit: '177.1 KB', + limit: '175 KB', }, { name: 'The home page of the docs', diff --git a/docs/src/modules/components/AppFrame.js b/docs/src/modules/components/AppFrame.js index 521bc2b12dbb88..4768013d708181 100644 --- a/docs/src/modules/components/AppFrame.js +++ b/docs/src/modules/components/AppFrame.js @@ -133,7 +133,7 @@ class AppFrame extends React.Component { @@ -146,24 +146,20 @@ class AppFrame extends React.Component { )}
- + {uiTheme.paletteType === 'light' ? : } - + {uiTheme.direction === 'rtl' ? ( @@ -172,12 +168,12 @@ class AppFrame extends React.Component { )} - + diff --git a/docs/src/modules/components/Demo.js b/docs/src/modules/components/Demo.js index 5d152b82dc7113..4e64c0a08d237e 100644 --- a/docs/src/modules/components/Demo.js +++ b/docs/src/modules/components/Demo.js @@ -97,7 +97,6 @@ const styles = theme => ({ [theme.breakpoints.up('sm')]: { display: 'flex', flip: false, - zIndex: 10, position: 'absolute', top: 0, right: theme.spacing.unit, @@ -207,37 +206,22 @@ class Demo extends React.Component { {demoOptions.hideHeader ? null : (
- - + + {demoOptions.hideEditButton ? null : ( - - + + )} - + diff --git a/docs/src/modules/components/withRoot.js b/docs/src/modules/components/withRoot.js index cdb4bb4bb0e79a..32a72e55748eb8 100644 --- a/docs/src/modules/components/withRoot.js +++ b/docs/src/modules/components/withRoot.js @@ -87,10 +87,13 @@ const pages = [ pathname: '/utils', children: [ { - pathname: '/utils/modals', + pathname: '/utils/modal', }, { - pathname: '/utils/popovers', + pathname: '/utils/popover', + }, + { + pathname: '/utils/popper', }, { pathname: '/utils/portal', diff --git a/docs/src/pages/demos/autocomplete/IntegrationDownshift.js b/docs/src/pages/demos/autocomplete/IntegrationDownshift.js index b16eef6fa24597..869f12360cf375 100644 --- a/docs/src/pages/demos/autocomplete/IntegrationDownshift.js +++ b/docs/src/pages/demos/autocomplete/IntegrationDownshift.js @@ -4,6 +4,7 @@ import keycode from 'keycode'; import Downshift from 'downshift'; import { withStyles } from '@material-ui/core/styles'; import TextField from '@material-ui/core/TextField'; +import Popper from '@material-ui/core/Popper'; import Paper from '@material-ui/core/Paper'; import MenuItem from '@material-ui/core/MenuItem'; import Chip from '@material-ui/core/Chip'; @@ -226,6 +227,8 @@ const styles = theme => ({ }, }); +let popperNode; + function IntegrationDownshift(props) { const { classes } = props; @@ -259,6 +262,36 @@ function IntegrationDownshift(props) { )} + + {({ getInputProps, getItemProps, isOpen, inputValue, selectedItem, highlightedIndex }) => ( +
+ {renderInput({ + fullWidth: true, + classes, + InputProps: getInputProps({ + placeholder: 'With Popper', + id: 'integration-downshift-popper', + }), + ref: node => { + popperNode = node; + }, + })} + + + {getSuggestions(inputValue).map((suggestion, index) => + renderSuggestion({ + suggestion, + index, + itemProps: getItemProps({ item: suggestion.label }), + highlightedIndex, + selectedItem, + }), + )} + + +
+ )} +
); } diff --git a/docs/src/pages/demos/autocomplete/autocomplete.md b/docs/src/pages/demos/autocomplete/autocomplete.md index 406f02a9dcf3c6..4ca7aa163b1fa8 100644 --- a/docs/src/pages/demos/autocomplete/autocomplete.md +++ b/docs/src/pages/demos/autocomplete/autocomplete.md @@ -1,6 +1,6 @@ --- title: Autocomplete React component -components: TextField, Paper, MenuItem +components: TextField, Paper, MenuItem, Popper --- # Autocomplete diff --git a/docs/src/pages/demos/buttons/ButtonSizes.js b/docs/src/pages/demos/buttons/ButtonSizes.js index 74916b28ea249e..443466c31ebed1 100644 --- a/docs/src/pages/demos/buttons/ButtonSizes.js +++ b/docs/src/pages/demos/buttons/ButtonSizes.js @@ -48,10 +48,10 @@ function ButtonSizes(props) {
- -
diff --git a/docs/src/pages/demos/buttons/FloatingActionButtons.js b/docs/src/pages/demos/buttons/FloatingActionButtons.js index ae3fe4236de4e7..816e031bb38e39 100644 --- a/docs/src/pages/demos/buttons/FloatingActionButtons.js +++ b/docs/src/pages/demos/buttons/FloatingActionButtons.js @@ -20,17 +20,17 @@ function FloatingActionButtons(props) { const { classes } = props; return (
- - - -
diff --git a/docs/src/pages/demos/dialogs/ConfirmationDialog.js b/docs/src/pages/demos/dialogs/ConfirmationDialog.js index 26a6996c17aa8e..444e9fbdf5174b 100644 --- a/docs/src/pages/demos/dialogs/ConfirmationDialog.js +++ b/docs/src/pages/demos/dialogs/ConfirmationDialog.js @@ -84,7 +84,7 @@ class ConfirmationDialogRaw extends React.Component { ref={node => { this.radioGroup = node; }} - aria-label="ringtone" + aria-label="Ringtone" name="ringtone" value={this.state.value} onChange={this.handleChange} diff --git a/docs/src/pages/demos/drawers/MiniDrawer.js b/docs/src/pages/demos/drawers/MiniDrawer.js index 7d5797cdfd573c..f89167cbf30604 100644 --- a/docs/src/pages/demos/drawers/MiniDrawer.js +++ b/docs/src/pages/demos/drawers/MiniDrawer.js @@ -106,7 +106,7 @@ class MiniDrawer extends React.Component { diff --git a/docs/src/pages/demos/drawers/PersistentDrawer.js b/docs/src/pages/demos/drawers/PersistentDrawer.js index 637ec247ef97c3..e28002e9e757a8 100644 --- a/docs/src/pages/demos/drawers/PersistentDrawer.js +++ b/docs/src/pages/demos/drawers/PersistentDrawer.js @@ -174,7 +174,7 @@ class PersistentDrawer extends React.Component { diff --git a/docs/src/pages/demos/drawers/ResponsiveDrawer.js b/docs/src/pages/demos/drawers/ResponsiveDrawer.js index a5319285429f19..080cbc6f85d0db 100644 --- a/docs/src/pages/demos/drawers/ResponsiveDrawer.js +++ b/docs/src/pages/demos/drawers/ResponsiveDrawer.js @@ -78,7 +78,7 @@ class ResponsiveDrawer extends React.Component { diff --git a/docs/src/pages/demos/menus/MenuListComposition.js b/docs/src/pages/demos/menus/MenuListComposition.js index 62e3fcfb1d5efb..cfa0175c51b380 100644 --- a/docs/src/pages/demos/menus/MenuListComposition.js +++ b/docs/src/pages/demos/menus/MenuListComposition.js @@ -1,13 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; -import classNames from 'classnames'; -import { Manager, Target, Popper } from 'react-popper'; import Button from '@material-ui/core/Button'; import ClickAwayListener from '@material-ui/core/ClickAwayListener'; -import Collapse from '@material-ui/core/Collapse'; import Grow from '@material-ui/core/Grow'; import Paper from '@material-ui/core/Paper'; -import Portal from '@material-ui/core/Portal'; +import Popper from '@material-ui/core/Popper'; import MenuItem from '@material-ui/core/MenuItem'; import MenuList from '@material-ui/core/MenuList'; import { withStyles } from '@material-ui/core/styles'; @@ -19,9 +16,6 @@ const styles = theme => ({ paper: { marginRight: theme.spacing.unit * 2, }, - popperClose: { - pointerEvents: 'none', - }, }); class MenuListComposition extends React.Component { @@ -34,7 +28,7 @@ class MenuListComposition extends React.Component { }; handleClose = event => { - if (this.target1.contains(event.target) || this.target2.contains(event.target)) { + if (this.anchorEl.contains(event.target)) { return; } @@ -54,76 +48,37 @@ class MenuListComposition extends React.Component { Logout - - -
{ - this.target1 = node; - }} - > - -
-
- + -
- - - - - - - + + + Profile My account Logout - - - - - - + + + + )} + + ); } diff --git a/docs/src/pages/demos/menus/menus.md b/docs/src/pages/demos/menus/menus.md index bc67d06ac9f2a2..03587b2745068d 100644 --- a/docs/src/pages/demos/menus/menus.md +++ b/docs/src/pages/demos/menus/menus.md @@ -1,6 +1,6 @@ --- title: Menu React component -components: Menu, MenuItem, MenuList, ClickAwayListener +components: Menu, MenuItem, MenuList, ClickAwayListener, Popper --- # Menus @@ -37,13 +37,13 @@ If the height of a menu prevents all menu items from being displayed, the menu c The `Menu` component uses the `Popover` component internally. However, you might want to use a different positioning strategy, or not blocking the scroll. -For answering those needs, we expose a `MenuList` component that you can compose, with [react-popper](https://github.com/FezVrasta/react-popper) in this example. +For answering those needs, we expose a `MenuList` component that you can compose, with `Popper` in this example. The primary responsibility of the `MenuList` component is to handle the focus. {{"demo": "pages/demos/menus/MenuListComposition.js"}} -## MenuItem composition +## Customized MenuItem The `MenuItem` is a wrapper around `ListItem` with some additional styles. You can use the same list composition features with the `MenuItem` component: diff --git a/docs/src/pages/demos/selection-controls/RadioButtonsGroup.js b/docs/src/pages/demos/selection-controls/RadioButtonsGroup.js index d34a197e2573b7..6dbf224d81d710 100644 --- a/docs/src/pages/demos/selection-controls/RadioButtonsGroup.js +++ b/docs/src/pages/demos/selection-controls/RadioButtonsGroup.js @@ -37,7 +37,7 @@ class RadioButtonsGroup extends React.Component { Gender - - - + ); } diff --git a/docs/src/pages/demos/tooltips/CustomizedTooltips.js b/docs/src/pages/demos/tooltips/CustomizedTooltips.js new file mode 100644 index 00000000000000..979a901c144c87 --- /dev/null +++ b/docs/src/pages/demos/tooltips/CustomizedTooltips.js @@ -0,0 +1,126 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import Button from '@material-ui/core/Button'; +import Tooltip from '@material-ui/core/Tooltip'; + +const styles = theme => ({ + lightTooltip: { + background: theme.palette.common.white, + color: theme.palette.text.primary, + boxShadow: theme.shadows[1], + fontSize: 11, + }, + arrowPopper: { + '&[x-placement*="bottom"] $arrowArrow': { + top: 0, + left: 0, + marginTop: '-0.9em', + width: '3em', + height: '1em', + '&::before': { + borderWidth: '0 1em 1em 1em', + borderColor: `transparent transparent ${theme.palette.grey[700]} transparent`, + }, + }, + '&[x-placement*="top"] $arrowArrow': { + bottom: 0, + left: 0, + marginBottom: '-0.9em', + width: '3em', + height: '1em', + '&::before': { + borderWidth: '1em 1em 0 1em', + borderColor: `${theme.palette.grey[700]} transparent transparent transparent`, + }, + }, + '&[x-placement*="right"] $arrowArrow': { + left: 0, + marginLeft: '-0.9em', + height: '3em', + width: '1em', + '&::before': { + borderWidth: '1em 1em 1em 0', + borderColor: `transparent ${theme.palette.grey[700]} transparent transparent`, + }, + }, + '&[x-placement*="left"] $arrowArrow': { + right: 0, + marginRight: '-0.9em', + height: '3em', + width: '1em', + '&::before': { + borderWidth: '1em 0 1em 1em', + borderColor: `transparent transparent transparent ${theme.palette.grey[700]}`, + }, + }, + }, + arrowArrow: { + position: 'absolute', + fontSize: 7, + width: '3em', + height: '3em', + '&::before': { + content: '""', + margin: 'auto', + display: 'block', + width: 0, + height: 0, + borderStyle: 'solid', + }, + }, +}); + +class CustomizedTooltips extends React.Component { + state = { + arrowRef: null, + }; + + handleArrowRef = node => { + this.setState({ + arrowRef: node, + }); + }; + + render() { + const { classes } = this.props; + + return ( +
+ + + + + + + + Add + + + } + classes={{ popper: classes.arrowPopper }} + PopperProps={{ + popperOptions: { + modifiers: { + arrow: { + enabled: Boolean(this.state.arrowRef), + element: this.state.arrowRef, + }, + }, + }, + }} + > + + +
+ ); + } +} + +CustomizedTooltips.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(CustomizedTooltips); diff --git a/docs/src/pages/demos/tooltips/DelayTooltips.js b/docs/src/pages/demos/tooltips/DelayTooltips.js new file mode 100644 index 00000000000000..7b6005e97dfa64 --- /dev/null +++ b/docs/src/pages/demos/tooltips/DelayTooltips.js @@ -0,0 +1,13 @@ +import React from 'react'; +import Button from '@material-ui/core/Button'; +import Tooltip from '@material-ui/core/Tooltip'; + +function DelayTooltips() { + return ( + + + + ); +} + +export default DelayTooltips; diff --git a/docs/src/pages/demos/tooltips/PositionedTooltips.js b/docs/src/pages/demos/tooltips/PositionedTooltips.js index cddf6869fd36f7..95e5a0864b084c 100644 --- a/docs/src/pages/demos/tooltips/PositionedTooltips.js +++ b/docs/src/pages/demos/tooltips/PositionedTooltips.js @@ -17,44 +17,44 @@ function PositionedTooltips(props) {
- + - + - + - +
- +
- +
- + - + - + @@ -62,13 +62,13 @@ function PositionedTooltips(props) { - + - + - + diff --git a/docs/src/pages/demos/tooltips/SimpleTooltips.js b/docs/src/pages/demos/tooltips/SimpleTooltips.js index 23c14d83754b77..5f7eaba3574e66 100644 --- a/docs/src/pages/demos/tooltips/SimpleTooltips.js +++ b/docs/src/pages/demos/tooltips/SimpleTooltips.js @@ -22,18 +22,16 @@ function SimpleTooltips(props) { const { classes } = props; return (
- + - + -
-
+ + + + + + + +
+ ); +} + +export default TransitionsTooltips; diff --git a/docs/src/pages/demos/tooltips/TriggersTooltips.js b/docs/src/pages/demos/tooltips/TriggersTooltips.js new file mode 100644 index 00000000000000..b1b91445ec2fb9 --- /dev/null +++ b/docs/src/pages/demos/tooltips/TriggersTooltips.js @@ -0,0 +1,64 @@ +import React from 'react'; +import Grid from '@material-ui/core/Grid'; +import Button from '@material-ui/core/Button'; +import Tooltip from '@material-ui/core/Tooltip'; +import ClickAwayListener from '@material-ui/core/ClickAwayListener'; + +class TriggersTooltips extends React.Component { + state = { + open: false, + }; + + handleTooltipClose = () => { + this.setState({ open: false }); + }; + + handleTooltipOpen = () => { + this.setState({ open: true }); + }; + + render() { + return ( +
+ + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
+
+ ); + } +} + +export default TriggersTooltips; diff --git a/docs/src/pages/demos/tooltips/tooltips.md b/docs/src/pages/demos/tooltips/tooltips.md index 4a414faa91437e..b5bc40cbff2d0f 100644 --- a/docs/src/pages/demos/tooltips/tooltips.md +++ b/docs/src/pages/demos/tooltips/tooltips.md @@ -15,17 +15,37 @@ When activated, [Tooltips](https://material.io/design/components/tooltips.html) ## Positioned Tooltips -The `Tooltip` has 12 placements choice. +The `Tooltip` has 12 **placements** choice. They don’t have directional arrows; instead, they rely on motion emanating from the source to convey direction. {{"demo": "pages/demos/tooltips/PositionedTooltips.js"}} ## Controlled Tooltips +You can use the `open`, `onOpen` and `onClose` properties to control the behavior of the tooltip. + {{"demo": "pages/demos/tooltips/ControlledTooltips.js"}} +## Triggers + +You can define the types of events that cause a tooltip to show. + +{{"demo": "pages/demos/tooltips/TriggersTooltips.js"}} + +## Transitions + +Use a different transition. + +{{"demo": "pages/demos/tooltips/TransitionsTooltips.js"}} + ## Showing and hiding The tooltip is normally shown immediately when the user's mouse hovers over the element, and hides immediately when the user's mouse leaves. A delay in showing or hiding the tooltip can be added through the properties `enterDelay` and `leaveDelay`, as shown in the Controlled Tooltips demo above. On mobile, the tooltip is displayed when the user longpresses the element and hides after a delay of 1500ms. You can disable this feature with the `disableTouchListener` property. + +{{"demo": "pages/demos/tooltips/DelayTooltips.js"}} + +## Customized Tooltips + +{{"demo": "pages/demos/tooltips/CustomizedTooltips.js"}} diff --git a/docs/src/pages/discover-more/team/Team.js b/docs/src/pages/discover-more/team/Team.js index 51ae66bedc6d9b..fa679a1bd3d55f 100644 --- a/docs/src/pages/discover-more/team/Team.js +++ b/docs/src/pages/discover-more/team/Team.js @@ -133,7 +133,7 @@ function Team(props) {
{member.github && ( direction @@ -93,7 +93,7 @@ class InteractiveGrid extends React.Component { justify @@ -118,7 +118,7 @@ class InteractiveGrid extends React.Component { alignItems diff --git a/docs/src/pages/layout/grid/SpacingGrid.js b/docs/src/pages/layout/grid/SpacingGrid.js index cf3a7f12956387..d7c019155b654f 100644 --- a/docs/src/pages/layout/grid/SpacingGrid.js +++ b/docs/src/pages/layout/grid/SpacingGrid.js @@ -54,7 +54,7 @@ class GuttersGrid extends React.Component { spacing { - this.setState({ - open: true, - }); + this.setState(state => ({ + open: !state.open, + })); }; handleClickAway = () => { @@ -51,18 +51,20 @@ class ClickAway extends React.Component { return (
- - {open ? ( - - - {fake} - {fake} - {fake} - {fake} - {fake} - - - ) : null} + +
+ + {open ? ( + + {fake} + {fake} + {fake} + {fake} + {fake} + + ) : null} +
+
); } diff --git a/docs/src/pages/utils/click-away-listener/click-away-listener.md b/docs/src/pages/utils/click-away-listener/click-away-listener.md index 9f9e008286e2b3..25ef805f42647b 100644 --- a/docs/src/pages/utils/click-away-listener/click-away-listener.md +++ b/docs/src/pages/utils/click-away-listener/click-away-listener.md @@ -7,6 +7,8 @@ components: ClickAwayListener

Listen for click events that occur somewhere in the document, outside of the element itself.

+## Simple menu + For instance, if you need to hide a menu when people click anywhere else on your page: {{"demo": "pages/utils/click-away-listener/ClickAway.js"}} diff --git a/docs/src/pages/utils/modals/SimpleModal.js b/docs/src/pages/utils/modal/SimpleModal.js similarity index 100% rename from docs/src/pages/utils/modals/SimpleModal.js rename to docs/src/pages/utils/modal/SimpleModal.js diff --git a/docs/src/pages/utils/modals/modals.md b/docs/src/pages/utils/modal/modal.md similarity index 93% rename from docs/src/pages/utils/modals/modals.md rename to docs/src/pages/utils/modal/modal.md index cf6164397cd737..442b8c44b5b624 100644 --- a/docs/src/pages/utils/modals/modals.md +++ b/docs/src/pages/utils/modal/modal.md @@ -3,7 +3,7 @@ title: Modal React component components: Modal --- -# Modals +# Modal

The modal component provides a solid foundation for creating dialogs, popovers, lightboxes, or whatever else.

@@ -22,4 +22,4 @@ This component shares many concepts with [react-overlays](https://react-bootstra ## Simple modal -{{"demo": "pages/utils/modals/SimpleModal.js"}} +{{"demo": "pages/utils/modal/SimpleModal.js"}} diff --git a/docs/src/pages/utils/popovers/AnchorPlayground.js b/docs/src/pages/utils/popover/AnchorPlayground.js similarity index 96% rename from docs/src/pages/utils/popovers/AnchorPlayground.js rename to docs/src/pages/utils/popover/AnchorPlayground.js index 9398a1227730a4..be7c581f39638a 100644 --- a/docs/src/pages/utils/popovers/AnchorPlayground.js +++ b/docs/src/pages/utils/popover/AnchorPlayground.js @@ -189,7 +189,7 @@ class AnchorPlayground extends React.Component { row aria-label="anchorReference" name="anchorReference" - value={this.state.anchorReference} + value={anchorReference} onChange={this.handleChange('anchorReference')} > } label="anchorEl" /> @@ -207,7 +207,7 @@ class AnchorPlayground extends React.Component { @@ -217,7 +217,7 @@ class AnchorPlayground extends React.Component { @@ -228,7 +228,7 @@ class AnchorPlayground extends React.Component { } label="Top" /> @@ -279,7 +279,7 @@ class AnchorPlayground extends React.Component { row aria-label="anchorOriginHorizontal" name="anchorOriginHorizontal" - value={this.state.anchorOriginHorizontal} + value={anchorOriginHorizontal} onChange={this.handleChange('anchorOriginHorizontal')} > } label="Left" /> diff --git a/docs/src/pages/utils/popover/MouseOverPopover.js b/docs/src/pages/utils/popover/MouseOverPopover.js new file mode 100644 index 00000000000000..ba00447826b0d5 --- /dev/null +++ b/docs/src/pages/utils/popover/MouseOverPopover.js @@ -0,0 +1,70 @@ +/* eslint-disable jsx-a11y/mouse-events-have-key-events */ + +import React from 'react'; +import PropTypes from 'prop-types'; +import Popover from '@material-ui/core/Popover'; +import Typography from '@material-ui/core/Typography'; +import { withStyles } from '@material-ui/core/styles'; + +const styles = theme => ({ + popover: { + pointerEvents: 'none', + }, + paper: { + padding: theme.spacing.unit, + }, +}); + +class MouseOverPopover extends React.Component { + state = { + anchorEl: null, + }; + + handlePopoverOpen = event => { + this.setState({ anchorEl: event.target }); + }; + + handlePopoverClose = () => { + this.setState({ anchorEl: null }); + }; + + render() { + const { classes } = this.props; + const { anchorEl } = this.state; + const open = Boolean(anchorEl); + + return ( +
+ + Hover with a Popover. + + + I use Popover. + +
+ ); + } +} + +MouseOverPopover.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(MouseOverPopover); diff --git a/docs/src/pages/utils/popovers/SimplePopover.js b/docs/src/pages/utils/popover/SimplePopover.js similarity index 100% rename from docs/src/pages/utils/popovers/SimplePopover.js rename to docs/src/pages/utils/popover/SimplePopover.js diff --git a/docs/src/pages/utils/popovers/popovers.md b/docs/src/pages/utils/popover/popover.md similarity index 52% rename from docs/src/pages/utils/popovers/popovers.md rename to docs/src/pages/utils/popover/popover.md index 3f3254b1583b27..dda4aecec7eb01 100644 --- a/docs/src/pages/utils/popovers/popovers.md +++ b/docs/src/pages/utils/popover/popover.md @@ -3,13 +3,17 @@ title: Popover React component components: Grow, Popover --- -# Popovers +# Popover

A Popover can be used to display some content on top of another.

+Things to know when using the `Popover` component: +- The component is build on top of the [`Modal`](/utils/modal) component. +- The scroll and click away are blocked unlike with the [`Popper`](/utils/popper) component. + ## Simple Popover -{{"demo": "pages/utils/popovers/SimplePopover.js" }} +{{"demo": "pages/utils/popover/SimplePopover.js" }} ## Anchor playground @@ -19,10 +23,10 @@ When it is `anchorPosition`, the component will, instead of `anchorEl`, refer to the `anchorPosition` prop which you can adjust to set the position of the popover. -{{"demo": "pages/utils/popovers/AnchorPlayground.js"}} +{{"demo": "pages/utils/popover/AnchorPlayground.js"}} ## Mouse over interaction -We demonstrate how to use the `Popover` component as well as the [react-popper](https://github.com/FezVrasta/react-popper) package to implement a popover behavior based on the mouse over event. +We demonstrate how to use the `Popover` component to implement a popover behavior based on the mouse over event. -{{"demo": "pages/utils/popovers/MouseOverPopover.js"}} +{{"demo": "pages/utils/popover/MouseOverPopover.js"}} diff --git a/docs/src/pages/utils/popovers/MouseOverPopover.js b/docs/src/pages/utils/popovers/MouseOverPopover.js deleted file mode 100644 index 6d764d005510e1..00000000000000 --- a/docs/src/pages/utils/popovers/MouseOverPopover.js +++ /dev/null @@ -1,113 +0,0 @@ -/* eslint-disable jsx-a11y/mouse-events-have-key-events */ - -import React from 'react'; -import PropTypes from 'prop-types'; -import Popover from '@material-ui/core/Popover'; -import Typography from '@material-ui/core/Typography'; -import { withStyles } from '@material-ui/core/styles'; -import Grow from '@material-ui/core/Grow'; -import Paper from '@material-ui/core/Paper'; -import { Manager, Target, Popper } from 'react-popper'; - -const styles = theme => ({ - paper: { - padding: theme.spacing.unit, - }, - popover: { - pointerEvents: 'none', - }, - popperClose: { - pointerEvents: 'none', - }, -}); - -class MouseOverPopover extends React.Component { - state = { - anchorEl: null, - popperOpen: false, - }; - - handlePopoverOpen = event => { - this.setState({ anchorEl: event.target }); - }; - - handlePopoverClose = () => { - this.setState({ anchorEl: null }); - }; - - handlePopperOpen = () => { - this.setState({ popperOpen: true }); - }; - - handlePopperClose = () => { - this.setState({ popperOpen: false }); - }; - - render() { - const { classes } = this.props; - const { anchorEl, popperOpen } = this.state; - const open = !!anchorEl; - - return ( -
- - Hover with a Popover. - - - I use Popover. - - - - - Hover with react-popper. - - - - - - I use react-popper. - - - - -
- ); - } -} - -MouseOverPopover.propTypes = { - classes: PropTypes.object.isRequired, -}; - -export default withStyles(styles)(MouseOverPopover); diff --git a/docs/src/pages/utils/popper/NoTransitionPopper.js b/docs/src/pages/utils/popper/NoTransitionPopper.js new file mode 100644 index 00000000000000..cffc9d89553988 --- /dev/null +++ b/docs/src/pages/utils/popper/NoTransitionPopper.js @@ -0,0 +1,52 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import Popper from '@material-ui/core/Popper'; +import Typography from '@material-ui/core/Typography'; +import Button from '@material-ui/core/Button'; +import Paper from '@material-ui/core/Paper'; + +const styles = theme => ({ + typography: { + padding: theme.spacing.unit * 2, + }, +}); + +class NoTransitionPopper extends React.Component { + state = { + anchorEl: null, + }; + + handleClick = event => { + const { currentTarget } = event; + this.setState(state => ({ + anchorEl: state.anchorEl ? null : currentTarget, + })); + }; + + render() { + const { classes } = this.props; + const { anchorEl } = this.state; + const open = Boolean(anchorEl); + const id = open ? 'no-transition-popper' : null; + + return ( +
+ + + + The content of the Popper. + + +
+ ); + } +} + +NoTransitionPopper.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(NoTransitionPopper); diff --git a/docs/src/pages/utils/popper/PositionedPopper.js b/docs/src/pages/utils/popper/PositionedPopper.js new file mode 100644 index 00000000000000..342cd2d4c33b07 --- /dev/null +++ b/docs/src/pages/utils/popper/PositionedPopper.js @@ -0,0 +1,93 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import Popper from '@material-ui/core/Popper'; +import Typography from '@material-ui/core/Typography'; +import Grid from '@material-ui/core/Grid'; +import Button from '@material-ui/core/Button'; +import Fade from '@material-ui/core/Fade'; +import Paper from '@material-ui/core/Paper'; + +const styles = theme => ({ + root: { + width: 500, + }, + typography: { + padding: theme.spacing.unit * 2, + }, +}); + +class PositionedPopper extends React.Component { + state = { + anchorEl: null, + placement: null, + }; + + handleClick = placement => event => { + const { currentTarget } = event; + this.setState(state => ({ + anchorEl: state.placement === placement && state.anchorEl ? null : currentTarget, + placement, + })); + }; + + render() { + const { classes } = this.props; + const { anchorEl, placement } = this.state; + const open = Boolean(anchorEl); + + return ( +
+ + {({ TransitionProps }) => ( + + + The content of the Popper. + + + )} + + + + + + + + + + + +
+ +
+ +
+ + + + + + + + + + + +
+ + + + + + + +
+ ); + } +} + +PositionedPopper.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(PositionedPopper); diff --git a/docs/src/pages/utils/popper/ScrollPlayground.js b/docs/src/pages/utils/popper/ScrollPlayground.js new file mode 100644 index 00000000000000..3ec767d96ba3ef --- /dev/null +++ b/docs/src/pages/utils/popper/ScrollPlayground.js @@ -0,0 +1,329 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import FormControlLabel from '@material-ui/core/FormControlLabel'; +import MarkdownElement from '@material-ui/docs/MarkdownElement'; +import Grid from '@material-ui/core/Grid'; +import RootRef from '@material-ui/core/RootRef'; +import Typography from '@material-ui/core/Typography'; +import Button from '@material-ui/core/Button'; +import Popper from '@material-ui/core/Popper'; +import Paper from '@material-ui/core/Paper'; +import DialogActions from '@material-ui/core/DialogActions'; +import DialogContent from '@material-ui/core/DialogContent'; +import DialogContentText from '@material-ui/core/DialogContentText'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import Switch from '@material-ui/core/Switch'; +import TextField from '@material-ui/core/TextField'; + +const styles = theme => ({ + root: { + flexGrow: 1, + }, + scrollContainer: { + height: 400, + overflow: 'auto', + marginBottom: theme.spacing.unit * 3, + }, + scroll: { + position: 'relative', + width: '230%', + backgroundColor: theme.palette.background.paper, + height: '230%', + }, + legend: { + marginTop: theme.spacing.unit * 2, + maxWidth: 300, + }, + paper: { + maxWidth: 400, + overflow: 'auto', + }, + select: { + width: 200, + }, + popper: { + zIndex: 1, + '&[x-placement*="bottom"] $arrow': { + top: 0, + left: 0, + marginTop: '-0.9em', + width: '3em', + height: '1em', + '&::before': { + borderWidth: '0 1em 1em 1em', + borderColor: `transparent transparent ${theme.palette.common.white} transparent`, + }, + }, + '&[x-placement*="top"] $arrow': { + bottom: 0, + left: 0, + marginBottom: '-0.9em', + width: '3em', + height: '1em', + '&::before': { + borderWidth: '1em 1em 0 1em', + borderColor: `${theme.palette.common.white} transparent transparent transparent`, + }, + }, + '&[x-placement*="right"] $arrow': { + left: 0, + marginLeft: '-0.9em', + height: '3em', + width: '1em', + '&::before': { + borderWidth: '1em 1em 1em 0', + borderColor: `transparent ${theme.palette.common.white} transparent transparent`, + }, + }, + '&[x-placement*="left"] $arrow': { + right: 0, + marginRight: '-0.9em', + height: '3em', + width: '1em', + '&::before': { + borderWidth: '1em 0 1em 1em', + borderColor: `transparent transparent transparent ${theme.palette.common.white}`, + }, + }, + }, + arrow: { + position: 'absolute', + fontSize: 7, + width: '3em', + height: '3em', + '&::before': { + content: '""', + margin: 'auto', + display: 'block', + width: 0, + height: 0, + borderStyle: 'solid', + }, + }, +}); + +class AnchorPlayground extends React.Component { + anchorEl = null; + + state = { + arrow: false, + arrowRef: null, + disablePortal: false, + flip: true, + open: false, + placement: 'bottom', + preventOverflow: 'scrollParent', + }; + + handleChange = key => (event, value) => { + this.setState({ + [key]: value, + }); + }; + + handleChangeTarget = key => event => { + this.setState({ + [key]: event.target.value, + }); + }; + + handleClickButton = () => { + this.setState(state => ({ + open: !state.open, + })); + }; + + handleArrowRef = node => { + this.setState({ + arrowRef: node, + }); + }; + + centerScroll = node => { + if (!node) { + return; + } + + const container = node.parentElement; + container.scrollTop = node.clientHeight / 4; + container.scrollLeft = node.clientWidth / 4; + }; + + render() { + const { classes } = this.props; + const { open, placement, disablePortal, flip, preventOverflow, arrow, arrowRef } = this.state; + + const code = ` +\`\`\`jsx + +\`\`\` +`; + + return ( +
+
+ + +
+ + + Scroll around this container to experiment with flip and preventOverflow + modifiers. + + + {arrow ? : null} + + {"Use Google's location service?"} + + + Let Google help apps determine location. + + + + + + + + +
+
+
+
+ + + + Appearance + +
+ + + + + + + + + + + + + + +
+ + } + label="Disable portal (the children stay within it's parent DOM hierarchy)" + /> +
+ + + Modifiers (options from Popper.js) + +
+ + + + + + +
+ } + label={[ + 'Flip', + '(flip the popper’s placement when it starts to overlap its reference element)', + ].join(' ')} + /> + + } + label="Arrow" + /> +
+
+ +
+ ); + } +} + +AnchorPlayground.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(AnchorPlayground); diff --git a/docs/src/pages/utils/popper/SimplePopper.js b/docs/src/pages/utils/popper/SimplePopper.js new file mode 100644 index 00000000000000..a77d513aa5de14 --- /dev/null +++ b/docs/src/pages/utils/popper/SimplePopper.js @@ -0,0 +1,57 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core/styles'; +import Popper from '@material-ui/core/Popper'; +import Typography from '@material-ui/core/Typography'; +import Button from '@material-ui/core/Button'; +import Fade from '@material-ui/core/Fade'; +import Paper from '@material-ui/core/Paper'; + +const styles = theme => ({ + typography: { + padding: theme.spacing.unit * 2, + }, +}); + +class SimplePopper extends React.Component { + state = { + anchorEl: null, + }; + + handleClick = event => { + const { currentTarget } = event; + this.setState(state => ({ + anchorEl: state.anchorEl ? null : currentTarget, + })); + }; + + render() { + const { classes } = this.props; + const { anchorEl } = this.state; + const open = Boolean(anchorEl); + const id = open ? 'simple-popper' : null; + + return ( +
+ + + {({ TransitionProps }) => ( + + + The content of the Popper. + + + )} + +
+ ); + } +} + +SimplePopper.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(SimplePopper); diff --git a/docs/src/pages/utils/popper/popper.md b/docs/src/pages/utils/popper/popper.md new file mode 100644 index 00000000000000..56b1dc6c6da59e --- /dev/null +++ b/docs/src/pages/utils/popper/popper.md @@ -0,0 +1,30 @@ +--- +title: Popper React component +components: Popper +--- + +# Popper + +

A Popper can be used to display some content on top of another.

+ +Things to know when using the `Popper` component: +- Poppers rely on the 3rd party library [Popper.js](https://github.com/FezVrasta/popper.js) for positioning. +- The children is [`Portal`](/utils/portal) to the body of the document to avoid rendering problems. You can disable this behavior with `disablePortal`. +- The scroll and click away aren't blocked like with the [`Popover`](/utils/popover) component. +The placement of the popper updates with the available area in the viewport. + +## Simple Popper + +{{"demo": "pages/utils/popper/SimplePopper.js" }} + +## Scroll playground + +{{"demo": "pages/utils/popper/ScrollPlayground.js"}} + +## Positioned Popper + +{{"demo": "pages/utils/popper/PositionedPopper.js"}} + +## Without transition Popper + +{{"demo": "pages/utils/popper/NoTransitionPopper.js"}} diff --git a/docs/src/pages/utils/transitions/SimpleCollapse.js b/docs/src/pages/utils/transitions/SimpleCollapse.js index 433fae0e662073..13460b73e9f769 100644 --- a/docs/src/pages/utils/transitions/SimpleCollapse.js +++ b/docs/src/pages/utils/transitions/SimpleCollapse.js @@ -41,7 +41,7 @@ class SimpleCollapse extends React.Component { return (
- +
diff --git a/docs/src/pages/utils/transitions/SimpleGrow.js b/docs/src/pages/utils/transitions/SimpleGrow.js index 0674c42ab56608..c1cde84522aec5 100644 --- a/docs/src/pages/utils/transitions/SimpleGrow.js +++ b/docs/src/pages/utils/transitions/SimpleGrow.js @@ -41,7 +41,7 @@ class SimpleGrow extends React.Component { return (
- +
diff --git a/docs/src/pages/utils/transitions/SimpleSlide.js b/docs/src/pages/utils/transitions/SimpleSlide.js index b0ee2e3c107a2c..cc6414cdde1ba0 100644 --- a/docs/src/pages/utils/transitions/SimpleSlide.js +++ b/docs/src/pages/utils/transitions/SimpleSlide.js @@ -44,7 +44,7 @@ class SimpleSlide extends React.Component { return (
- + diff --git a/docs/src/pages/utils/transitions/SimpleZoom.js b/docs/src/pages/utils/transitions/SimpleZoom.js index c51328de47817e..6b20cc4ac5bf76 100644 --- a/docs/src/pages/utils/transitions/SimpleZoom.js +++ b/docs/src/pages/utils/transitions/SimpleZoom.js @@ -41,7 +41,7 @@ class SimpleZoom extends React.Component { return (
- +
diff --git a/flow-typed/npm/react-popper_vx.x.x.js b/flow-typed/npm/react-popper_vx.x.x.js deleted file mode 100644 index c6687375edb0d3..00000000000000 --- a/flow-typed/npm/react-popper_vx.x.x.js +++ /dev/null @@ -1,95 +0,0 @@ -// flow-typed signature: e96f654f0d655a4b9cfac3c5119e367a -// flow-typed version: <>/react-popper_v^0.7.4/flow_v0.59.0 - -/** - * This is an autogenerated libdef stub for: - * - * 'react-popper' - * - * Fill this stub out by replacing all the `any` types. - * - * Once filled out, we encourage you to share your work with the - * community by sending a pull request to: - * https://github.com/flowtype/flow-typed - */ - -declare module 'react-popper' { - declare module.exports: any; -} - -/** - * We include stubs for each file inside this npm package in case you need to - * require those files directly. Feel free to delete any files that aren't - * needed. - */ -declare module 'react-popper/dist/react-popper' { - declare module.exports: any; -} - -declare module 'react-popper/dist/react-popper.min' { - declare module.exports: any; -} - -declare module 'react-popper/lib/Arrow' { - declare module.exports: any; -} - -declare module 'react-popper/lib/Manager' { - declare module.exports: any; -} - -declare module 'react-popper/lib/Popper' { - declare module.exports: any; -} - -declare module 'react-popper/lib/PopperArrow' { - declare module.exports: any; -} - -declare module 'react-popper/lib/PopperComponent' { - declare module.exports: any; -} - -declare module 'react-popper/lib/PopperManager' { - declare module.exports: any; -} - -declare module 'react-popper/lib/react-popper' { - declare module.exports: any; -} - -declare module 'react-popper/lib/Target' { - declare module.exports: any; -} - -// Filename aliases -declare module 'react-popper/dist/react-popper.js' { - declare module.exports: $Exports<'react-popper/dist/react-popper'>; -} -declare module 'react-popper/dist/react-popper.min.js' { - declare module.exports: $Exports<'react-popper/dist/react-popper.min'>; -} -declare module 'react-popper/lib/Arrow.js' { - declare module.exports: $Exports<'react-popper/lib/Arrow'>; -} -declare module 'react-popper/lib/Manager.js' { - declare module.exports: $Exports<'react-popper/lib/Manager'>; -} -declare module 'react-popper/lib/Popper.js' { - declare module.exports: $Exports<'react-popper/lib/Popper'>; -} -declare module 'react-popper/lib/PopperArrow.js' { - declare module.exports: $Exports<'react-popper/lib/PopperArrow'>; -} -declare module 'react-popper/lib/PopperComponent.js' { - declare module.exports: $Exports<'react-popper/lib/PopperComponent'>; -} -declare module 'react-popper/lib/PopperManager.js' { - declare module.exports: $Exports<'react-popper/lib/PopperManager'>; -} -declare module 'react-popper/lib/react-popper.js' { - declare module.exports: $Exports<'react-popper/lib/react-popper'>; -} -declare module 'react-popper/lib/Target.js' { - declare module.exports: $Exports<'react-popper/lib/Target'>; -} diff --git a/packages/material-ui-lab/src/SpeedDial/SpeedDial.js b/packages/material-ui-lab/src/SpeedDial/SpeedDial.js index de495f66786325..2fd0bd7f94ad69 100644 --- a/packages/material-ui-lab/src/SpeedDial/SpeedDial.js +++ b/packages/material-ui-lab/src/SpeedDial/SpeedDial.js @@ -193,7 +193,7 @@ SpeedDial.propTypes = { */ ariaLabel: PropTypes.string.isRequired, /** - * Properties applied to the `Button` element. + * Properties applied to the [`Button`](/api/button) element. */ ButtonProps: PropTypes.object, /** diff --git a/packages/material-ui-lab/src/SpeedDialAction/SpeedDialAction.js b/packages/material-ui-lab/src/SpeedDialAction/SpeedDialAction.js index e67655c30017e7..6b1ac4bda4e3f3 100644 --- a/packages/material-ui-lab/src/SpeedDialAction/SpeedDialAction.js +++ b/packages/material-ui-lab/src/SpeedDialAction/SpeedDialAction.js @@ -81,7 +81,7 @@ class SpeedDialAction extends React.Component { SpeedDialAction.propTypes = { /** - * Properties applied to the `Button` component. + * Properties applied to the [`Button`](/api/button) component. */ ButtonProps: PropTypes.object, /** diff --git a/packages/material-ui/package.json b/packages/material-ui/package.json index 3570b604b32b3b..27774b64ca9b4c 100644 --- a/packages/material-ui/package.json +++ b/packages/material-ui/package.json @@ -53,10 +53,10 @@ "jss-vendor-prefixer": "^7.0.0", "keycode": "^2.1.9", "normalize-scroll-left": "^0.1.2", + "popper.js": "^1.0.0", "prop-types": "^15.6.0", "react-event-listener": "^0.6.0", "react-jss": "^8.1.0", - "react-popper": "^0.10.0", "react-transition-group": "^2.2.1", "recompose": "^0.27.0", "scroll": "^2.0.3", diff --git a/packages/material-ui/src/ClickAwayListener/ClickAwayListener.js b/packages/material-ui/src/ClickAwayListener/ClickAwayListener.js index a73e00dd594804..bfe9cd06bceaed 100644 --- a/packages/material-ui/src/ClickAwayListener/ClickAwayListener.js +++ b/packages/material-ui/src/ClickAwayListener/ClickAwayListener.js @@ -16,6 +16,8 @@ class ClickAwayListener extends React.Component { mounted = null; componentDidMount() { + // Finds the first child when a component returns a fragment. + // https://github.com/facebook/react/blob/036ae3c6e2f056adffc31dfb78d1b6f0c63272f0/packages/react-dom/src/__tests__/ReactDOMFiber-test.js#L105 this.node = ReactDOM.findDOMNode(this); this.mounted = true; } @@ -70,9 +72,21 @@ class ClickAwayListener extends React.Component { } ClickAwayListener.propTypes = { - children: PropTypes.node.isRequired, + /** + * The wrapped element. + */ + children: PropTypes.element.isRequired, + /** + * The mouse event to listen to. You can disable the listener by providing `false`. + */ mouseEvent: PropTypes.oneOf(['onClick', 'onMouseDown', 'onMouseUp', false]), + /** + * Callback fired when a "click away" event is detected. + */ onClickAway: PropTypes.func.isRequired, + /** + * The touch event to listen to. You can disable the listener by providing `false`. + */ touchEvent: PropTypes.oneOf(['onTouchStart', 'onTouchEnd', false]), }; diff --git a/packages/material-ui/src/Dialog/Dialog.js b/packages/material-ui/src/Dialog/Dialog.js index 40323ba3c3bd91..ae106954221737 100644 --- a/packages/material-ui/src/Dialog/Dialog.js +++ b/packages/material-ui/src/Dialog/Dialog.js @@ -235,7 +235,7 @@ Dialog.propTypes = { */ open: PropTypes.bool.isRequired, /** - * Properties applied to the `Paper` element. + * Properties applied to the [`Paper`](/api/paper) element. */ PaperProps: PropTypes.object, /** diff --git a/packages/material-ui/src/Drawer/Drawer.js b/packages/material-ui/src/Drawer/Drawer.js index 7761c61325e7d4..d376cca1d48bfb 100644 --- a/packages/material-ui/src/Drawer/Drawer.js +++ b/packages/material-ui/src/Drawer/Drawer.js @@ -200,7 +200,7 @@ Drawer.propTypes = { */ elevation: PropTypes.number, /** - * Properties applied to the `Modal` element. + * Properties applied to the [`Modal`](/api/modal) element. */ ModalProps: PropTypes.object, /** @@ -214,11 +214,11 @@ Drawer.propTypes = { */ open: PropTypes.bool, /** - * Properties applied to the `Paper` element. + * Properties applied to the [`Paper`](/api/paper) element. */ PaperProps: PropTypes.object, /** - * Properties applied to the `Slide` element. + * Properties applied to the [`Slide`](/api/slide) element. */ SlideProps: PropTypes.object, /** diff --git a/packages/material-ui/src/ExpansionPanel/ExpansionPanel.js b/packages/material-ui/src/ExpansionPanel/ExpansionPanel.js index 5af37242cd5c07..9ec7824e1b0e51 100644 --- a/packages/material-ui/src/ExpansionPanel/ExpansionPanel.js +++ b/packages/material-ui/src/ExpansionPanel/ExpansionPanel.js @@ -186,7 +186,7 @@ ExpansionPanel.propTypes = { */ className: PropTypes.string, /** - * Properties applied to the `Collapse` element. + * Properties applied to the [`Collapse`](/api/collapse) element. */ CollapseProps: PropTypes.object, /** diff --git a/packages/material-ui/src/Menu/Menu.d.ts b/packages/material-ui/src/Menu/Menu.d.ts index 493fa207127317..c36e60aefd6d70 100644 --- a/packages/material-ui/src/Menu/Menu.d.ts +++ b/packages/material-ui/src/Menu/Menu.d.ts @@ -8,7 +8,7 @@ import { ClassNameMap } from '../styles/withStyles'; export interface MenuProps extends StandardProps, MenuClassKey> { - anchorEl?: HTMLElement; + anchorEl?: HTMLElement | ((element: HTMLElement) => HTMLElement); disableAutoFocusItem?: boolean; MenuListProps?: Partial; PaperProps?: Partial; diff --git a/packages/material-ui/src/Menu/Menu.js b/packages/material-ui/src/Menu/Menu.js index 61754e154ed0a4..caa3a1e05e4fbc 100644 --- a/packages/material-ui/src/Menu/Menu.js +++ b/packages/material-ui/src/Menu/Menu.js @@ -123,7 +123,6 @@ class Menu extends React.Component { > { @@ -141,7 +140,7 @@ Menu.propTypes = { /** * The DOM element used to set the position of the menu. */ - anchorEl: PropTypes.object, + anchorEl: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), /** * Menu contents, normally `MenuItem`s. */ @@ -156,7 +155,7 @@ Menu.propTypes = { */ disableAutoFocusItem: PropTypes.bool, /** - * Properties applied to the `MenuList` element. + * Properties applied to the [`MenuList`](/api/menu-list) element. */ MenuListProps: PropTypes.object, /** diff --git a/packages/material-ui/src/Modal/Modal.d.ts b/packages/material-ui/src/Modal/Modal.d.ts index e39db6f652f1e4..dcd7ae12003a4e 100644 --- a/packages/material-ui/src/Modal/Modal.d.ts +++ b/packages/material-ui/src/Modal/Modal.d.ts @@ -4,16 +4,15 @@ import { BackdropProps } from '../Backdrop'; import { PortalProps } from '../Portal'; export interface ModalProps - extends StandardProps< - React.HtmlHTMLAttributes & Partial, - ModalClassKey - > { + extends StandardProps, ModalClassKey> { BackdropComponent?: React.ReactType; BackdropProps?: Partial; + container?: PortalProps['container']; disableAutoFocus?: boolean; disableBackdropClick?: boolean; disableEnforceFocus?: boolean; disableEscapeKeyDown?: boolean; + disablePortal?: PortalProps['disablePortal']; disableRestoreFocus?: boolean; hideBackdrop?: boolean; keepMounted?: boolean; diff --git a/packages/material-ui/src/Modal/Modal.js b/packages/material-ui/src/Modal/Modal.js index 69bf4bfd8e5b02..da78b9cdc0252e 100644 --- a/packages/material-ui/src/Modal/Modal.js +++ b/packages/material-ui/src/Modal/Modal.js @@ -1,5 +1,3 @@ -// @inheritedComponent Portal - import React from 'react'; import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; @@ -45,9 +43,9 @@ if (process.env.NODE_ENV !== 'production' && !React.createContext) { class Modal extends React.Component { mountNode = null; - modalNode = null; + modalRef = null; - dialogNode = null; + dialogRef = null; mounted = false; @@ -108,7 +106,7 @@ class Modal extends React.Component { this.autoFocus(); // Fix a bug on Chrome where the scroll isn't initially 0. - this.modalNode.scrollTop = 0; + this.modalRef.scrollTop = 0; if (this.props.onRendered) { this.props.onRendered(); @@ -176,8 +174,8 @@ class Modal extends React.Component { const currentActiveElement = ownerDocument(this.mountNode).activeElement; - if (this.dialogNode && !this.dialogNode.contains(currentActiveElement)) { - this.dialogNode.focus(); + if (this.dialogRef && !this.dialogRef.contains(currentActiveElement)) { + this.dialogRef.focus(); } }; @@ -188,10 +186,10 @@ class Modal extends React.Component { const currentActiveElement = ownerDocument(this.mountNode).activeElement; - if (this.dialogNode && !this.dialogNode.contains(currentActiveElement)) { + if (this.dialogRef && !this.dialogRef.contains(currentActiveElement)) { this.lastFocus = currentActiveElement; - if (!this.dialogNode.hasAttribute('tabIndex')) { + if (!this.dialogRef.hasAttribute('tabIndex')) { warning( false, [ @@ -200,10 +198,10 @@ class Modal extends React.Component { 'the tabIndex of the node is being set to "-1".', ].join('\n'), ); - this.dialogNode.setAttribute('tabIndex', -1); + this.dialogRef.setAttribute('tabIndex', -1); } - this.dialogNode.focus(); + this.dialogRef.focus(); } } @@ -240,15 +238,16 @@ class Modal extends React.Component { disableBackdropClick, disableEnforceFocus, disableEscapeKeyDown, + disablePortal, disableRestoreFocus, hideBackdrop, keepMounted, + manager, onBackdropClick, onClose, onEscapeKeyDown, onRendered, open, - manager, ...other } = this.props; const { exited } = this.state; @@ -278,12 +277,13 @@ class Modal extends React.Component { this.mountNode = node ? node.getMountNode() : node; }} container={container} + disablePortal={disablePortal} onRendered={this.handleRendered} >
{ - this.modalNode = node; + this.modalRef = node; }} className={classNames(classes.root, className, { [classes.hidden]: exited, @@ -295,7 +295,7 @@ class Modal extends React.Component { )} { - this.dialogNode = node; + this.dialogRef = node; }} > {React.cloneElement(children, childProps)} @@ -312,7 +312,7 @@ Modal.propTypes = { */ BackdropComponent: PropTypes.oneOfType([PropTypes.string, PropTypes.func, PropTypes.object]), /** - * Properties applied to the `Backdrop` element. + * Properties applied to the [`Backdrop`](/api/backdrop) element. */ BackdropProps: PropTypes.object, /** @@ -357,6 +357,11 @@ Modal.propTypes = { * If `true`, hitting escape will not fire any callback. */ disableEscapeKeyDown: PropTypes.bool, + /** + * Disable the portal behavior. + * The children stay within it's parent DOM hierarchy. + */ + disablePortal: PropTypes.bool, /** * If `true`, the modal will not restore focus to previously focused element once * modal is hidden. @@ -410,6 +415,7 @@ Modal.defaultProps = { disableBackdropClick: false, disableEnforceFocus: false, disableEscapeKeyDown: false, + disablePortal: false, disableRestoreFocus: false, hideBackdrop: false, keepMounted: false, diff --git a/packages/material-ui/src/Popover/Popover.js b/packages/material-ui/src/Popover/Popover.js index 3dc5179f748886..dc13e8b456c071 100644 --- a/packages/material-ui/src/Popover/Popover.js +++ b/packages/material-ui/src/Popover/Popover.js @@ -82,14 +82,14 @@ export const styles = { }; class Popover extends React.Component { - transitionEl = null; + paperRef = null; handleGetOffsetTop = getOffsetTop; handleGetOffsetLeft = getOffsetLeft; handleResize = debounce(() => { - const element = ReactDOM.findDOMNode(this.transitionEl); + const element = ReactDOM.findDOMNode(this.paperRef); this.setPositioningStyles(element); }, 166); // Corresponds to 10 frames at 60 Hz. @@ -209,7 +209,7 @@ class Popover extends React.Component { // If an anchor element wasn't provided, just use the parent body element of this Popover const anchorElement = - getAnchorEl(anchorEl) || ownerDocument(ReactDOM.findDOMNode(this.transitionEl)).body; + getAnchorEl(anchorEl) || ownerDocument(ReactDOM.findDOMNode(this.paperRef)).body; const anchorRect = anchorElement.getBoundingClientRect(); const anchorVertical = contentAnchorOffset === 0 ? anchorOrigin.vertical : 'center'; @@ -319,9 +319,6 @@ class Popover extends React.Component { onExited={onExited} onExiting={onExiting} role={role} - ref={node => { - this.transitionEl = node; - }} timeout={transitionDuration} {...TransitionProps} > @@ -329,6 +326,9 @@ class Popover extends React.Component { className={classes.paper} data-mui-test="Popover" elevation={elevation} + ref={node => { + this.paperRef = node; + }} {...PaperProps} > @@ -454,7 +454,7 @@ Popover.propTypes = { */ open: PropTypes.bool.isRequired, /** - * Properties applied to the `Paper` element. + * Properties applied to the [`Paper`](/api/paper) element. */ PaperProps: PropTypes.object, /** diff --git a/packages/material-ui/src/Popper/Popper.d.ts b/packages/material-ui/src/Popper/Popper.d.ts new file mode 100644 index 00000000000000..eba18c9c11bc18 --- /dev/null +++ b/packages/material-ui/src/Popper/Popper.d.ts @@ -0,0 +1,29 @@ +import * as React from 'react'; +import { PortalProps } from '../Portal'; + +export interface PopperProps extends React.HTMLAttributes { + anchorEl?: HTMLElement | ((element: HTMLElement) => HTMLElement); + children: () => React.ReactElement | React.ReactElement; + container?: PortalProps['container']; + disablePortal?: PortalProps['disablePortal']; + keepMounted?: boolean; + open: boolean; + placement?: + | 'bottom-end' + | 'bottom-start' + | 'bottom' + | 'left-end' + | 'left-start' + | 'left' + | 'right-end' + | 'right-start' + | 'right' + | 'top-end' + | 'top-start' + | 'top'; + popperOptions?: Object; +} + +declare const Popper: React.ComponentType; + +export default Popper; diff --git a/packages/material-ui/src/Popper/Popper.js b/packages/material-ui/src/Popper/Popper.js new file mode 100644 index 00000000000000..a2be105dd57db1 --- /dev/null +++ b/packages/material-ui/src/Popper/Popper.js @@ -0,0 +1,253 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import PropTypes from 'prop-types'; +import PopperJS from 'popper.js'; +import withTheme from '../styles/withTheme'; +import Portal from '../Portal'; + +function flipPlacement(theme, placement) { + if (theme.direction !== 'rtl') { + return placement; + } + + switch (placement) { + case 'bottom-end': + return 'bottom-start'; + case 'bottom-start': + return 'bottom-end'; + case 'top-end': + return 'top-start'; + case 'top-start': + return 'top-end'; + default: + return placement; + } +} + +function getAnchorEl(anchorEl) { + return typeof anchorEl === 'function' ? anchorEl() : anchorEl; +} + +class Popper extends React.Component { + popper = null; + + constructor(props) { + super(props); + + this.state = { + exited: !this.props.open, + }; + } + + componentDidUpdate(prevProps) { + if (prevProps.open && !this.props.open && !this.props.transition) { + // Otherwise handleExited will call this. + this.handleClose(); + } + + // Let's update the popper position. + if ( + prevProps.anchorEl !== this.props.anchorEl || + prevProps.popperOptions !== this.props.popperOptions || + prevProps.disablePortal !== this.props.disablePortal || + prevProps.placement !== this.props.placement + ) { + this.handleRendered(); + } + } + + componentWillUnmount() { + this.handleClose(); + } + + static getDerivedStateFromProps(nextProps) { + if (nextProps.open) { + return { + exited: false, + }; + } + + if (!nextProps.transition) { + // Otherwise let handleExited take care of marking exited. + return { + exited: true, + }; + } + + return null; + } + + handleRendered = () => { + const { anchorEl, open, placement, popperOptions = {}, theme, disablePortal } = this.props; + const popperNode = ReactDOM.findDOMNode(this); + + if (this.popper) { + this.popper.destroy(); + this.popper = null; + } + + if (!popperNode || !anchorEl || !open) { + return; + } + + this.popper = new PopperJS(getAnchorEl(anchorEl), popperNode, { + placement: flipPlacement(theme, placement), + ...popperOptions, + modifiers: { + ...(disablePortal + ? {} + : { + // It's using scrollParent by default, we can use the viewport when using a portal. + preventOverflow: { + boundariesElement: 'viewport', + }, + }), + ...popperOptions.modifiers, + }, + // We could have been using a custom modifier like react-popper is doing. + // But it seems this is the best public API for this use case. + onCreate: this.handlePopperUpdate, + onUpdate: this.handlePopperUpdate, + }); + }; + + handlePopperUpdate = data => { + if (data.placement !== this.state.placement) { + this.setState({ + placement: data.placement, + }); + } + }; + + handleExited = () => { + this.setState({ exited: true }); + this.handleClose(); + }; + + handleClose = () => { + if (!this.popper) { + return; + } + + this.popper.destroy(); + this.popper = null; + }; + + render() { + const { + anchorEl, + children, + container, + disablePortal, + keepMounted, + open, + placement: placementProps, + popperOptions, + theme, + transition, + ...other + } = this.props; + const { exited, placement } = this.state; + + if (!keepMounted && !open && (!transition || exited)) { + return null; + } + + const childProps = { + placement: placement || flipPlacement(theme, placementProps), + }; + + if (transition) { + childProps.TransitionProps = { + in: open, + onExited: this.handleExited, + }; + } + + return ( + +
+ {typeof children === 'function' ? children(childProps) : children} +
+
+ ); + } +} + +Popper.propTypes = { + /** + * This is the DOM element, or a function that returns the DOM element, + * that may be used to set the position of the popover. + */ + anchorEl: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), + /** + * Popper render function or node. + */ + children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]), + /** + * A node, component instance, or function that returns either. + * The `container` will passed to the Modal component. + * By default, it uses the body of the anchorEl's top-level document object, + * so it's simply `document.body` most of the time. + */ + container: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), + /** + * Disable the portal behavior. + * The children stay within it's parent DOM hierarchy. + */ + disablePortal: PropTypes.bool, + /** + * Always keep the children in the DOM. + * This property can be useful in SEO situation or + * when you want to maximize the responsiveness of the Popper. + */ + keepMounted: PropTypes.bool, + /** + * If `true`, the popper is visible. + */ + open: PropTypes.bool.isRequired, + /** + * Popper placement. + */ + placement: PropTypes.oneOf([ + 'bottom-end', + 'bottom-start', + 'bottom', + 'left-end', + 'left-start', + 'left', + 'right-end', + 'right-start', + 'right', + 'top-end', + 'top-start', + 'top', + ]), + /** + * Options provided to the [`popper.js`](https://github.com/FezVrasta/popper.js) instance. + */ + popperOptions: PropTypes.object, + /** + * @ignore + */ + theme: PropTypes.object.isRequired, + /** + * Help supporting a react-transition-group/Transition component. + */ + transition: PropTypes.bool, +}; + +Popper.defaultProps = { + disablePortal: false, + placement: 'bottom', + transition: false, +}; + +export default withTheme()(Popper); diff --git a/packages/material-ui/src/Popper/Popper.test.js b/packages/material-ui/src/Popper/Popper.test.js new file mode 100644 index 00000000000000..55d4be67cccf4f --- /dev/null +++ b/packages/material-ui/src/Popper/Popper.test.js @@ -0,0 +1,152 @@ +/* eslint-disable no-underscore-dangle */ + +import React from 'react'; +import { assert } from 'chai'; +import { spy } from 'sinon'; +import { createShallow, createMount, unwrap } from '../test-utils'; +import createMuiTheme from '../styles/createMuiTheme'; +import Grow from '../Grow'; +import Popper from './Popper'; + +const PopperNaked = unwrap(Popper); + +describe('', () => { + const defaultProps = { + open: true, + theme: createMuiTheme(), + children: Hello World, + }; + let shallow; + let mount; + let anchorEl; + + before(() => { + anchorEl = window.document.createElement('div'); + window.document.body.appendChild(anchorEl); + shallow = createShallow({ dive: true }); + mount = createMount(); + }); + + after(() => { + mount.cleanUp(); + window.document.body.removeChild(anchorEl); + }); + + it('should render the correct structure', () => { + const wrapper = shallow(); + assert.strictEqual(wrapper.name(), 'Portal'); + assert.strictEqual(wrapper.childAt(0).name(), 'div'); + }); + + describe('prop: placement', () => { + it('should have top placement', () => { + const renderSpy = spy(); + shallow( + + {({ placement }) => { + renderSpy(placement); + return null; + }} + , + ); + assert.strictEqual(renderSpy.callCount, 1); + assert.strictEqual(renderSpy.args[0][0], 'top'); + }); + + const theme = createMuiTheme({ + direction: 'rtl', + }); + + [ + { + in: 'bottom-end', + out: 'bottom-start', + }, + { + in: 'bottom-start', + out: 'bottom-end', + }, + { + in: 'top-end', + out: 'top-start', + }, + { + in: 'top-start', + out: 'top-end', + }, + { + in: 'top', + out: 'top', + }, + ].forEach(test => { + it(`should flip ${test.in} when direction=rtl is used`, () => { + const renderSpy = spy(); + shallow( + + {({ placement }) => { + renderSpy(placement); + return null; + }} + , + ); + assert.strictEqual(renderSpy.callCount, 1); + assert.strictEqual(renderSpy.args[0][0], test.out); + }); + }); + }); + + describe('mount', () => { + it('should mount without any issue', () => { + const wrapper = mount(); + assert.strictEqual(wrapper.find('span').length, 0); + wrapper.setProps({ open: true }); + wrapper.update(); + assert.strictEqual(wrapper.find('span').length, 1); + assert.strictEqual(wrapper.find('span').text(), 'Hello World'); + wrapper.setProps({ open: false }); + assert.strictEqual(wrapper.find('span').length, 0); + }); + }); + + describe('prop: transition', () => { + it('should work', () => { + const wrapper = mount( + + {({ TransitionProps }) => ( + + Hello World + + )} + , + ); + const instance = wrapper.instance(); + assert.strictEqual(wrapper.find('span').length, 1); + assert.strictEqual(wrapper.find('span').text(), 'Hello World'); + assert.strictEqual(instance.popper !== null, true); + wrapper.setProps({ anchorEl: null }); + assert.strictEqual(instance.popper, null); + }); + }); + + describe('prop: onExited', () => { + it('should update the exited state', () => { + const wrapper = mount( + + {({ TransitionProps }) => ( + + Hello World + + )} + , + ); + wrapper.setProps({ + open: false, + }); + wrapper + .find(Grow) + .props() + .onExited(); + assert.strictEqual(wrapper.state().exited, true); + }); + }); +}); diff --git a/packages/material-ui/src/Popper/index.d.ts b/packages/material-ui/src/Popper/index.d.ts new file mode 100644 index 00000000000000..47c6e54bd64cdd --- /dev/null +++ b/packages/material-ui/src/Popper/index.d.ts @@ -0,0 +1,2 @@ +export { default } from './Popper'; +export * from './Popper'; diff --git a/packages/material-ui/src/Popper/index.js b/packages/material-ui/src/Popper/index.js new file mode 100644 index 00000000000000..30e5f42e8efc86 --- /dev/null +++ b/packages/material-ui/src/Popper/index.js @@ -0,0 +1 @@ +export { default } from './Popper'; diff --git a/packages/material-ui/src/Portal/Portal.d.ts b/packages/material-ui/src/Portal/Portal.d.ts index ba03aa2d46e2dc..f9575cd16193e2 100644 --- a/packages/material-ui/src/Portal/Portal.d.ts +++ b/packages/material-ui/src/Portal/Portal.d.ts @@ -1,8 +1,10 @@ import * as React from 'react'; +import { PortalProps } from '../Portal'; export interface PortalProps { children: React.ReactElement; container?: React.ReactInstance | (() => React.ReactInstance); + disablePortal?: boolean; onRendered?: () => void; } diff --git a/packages/material-ui/src/Portal/Portal.js b/packages/material-ui/src/Portal/Portal.js index 47954e6aed9879..317b5a920c70b8 100644 --- a/packages/material-ui/src/Portal/Portal.js +++ b/packages/material-ui/src/Portal/Portal.js @@ -14,21 +14,30 @@ function getOwnerDocument(element) { } /** - * This component shares many concepts with - * [react-overlays](https://react-bootstrap.github.io/react-overlays/#portals) - * But has been forked in order to fix some bugs, reduce the number of dependencies - * and take the control of our destiny. + * Portals provide a first-class way to render children into a DOM node + * that exists outside the DOM hierarchy of the parent component. */ class Portal extends React.Component { componentDidMount() { - this.setContainer(this.props.container); - this.forceUpdate(this.props.onRendered); + this.setMountNode(this.props.container); + + // Only rerender if needed + if (!this.props.disablePortal) { + this.forceUpdate(this.props.onRendered); + } } componentDidUpdate(prevProps) { - if (prevProps.container !== this.props.container) { - this.setContainer(this.props.container); - this.forceUpdate(); + if ( + prevProps.container !== this.props.container || + prevProps.disablePortal !== this.props.disablePortal + ) { + this.setMountNode(this.props.container); + + // Only rerender if needed + if (!this.props.disablePortal) { + this.forceUpdate(this.props.onRendered); + } } } @@ -36,7 +45,12 @@ class Portal extends React.Component { this.mountNode = null; } - setContainer(container) { + setMountNode(container) { + if (this.props.disablePortal) { + this.mountNode = ReactDOM.findDOMNode(this).parentElement; + return; + } + this.mountNode = getContainer(container, getOwnerDocument(this).body); } @@ -48,7 +62,11 @@ class Portal extends React.Component { }; render() { - const { children } = this.props; + const { children, disablePortal } = this.props; + + if (disablePortal) { + return children; + } return this.mountNode ? ReactDOM.createPortal(children, this.mountNode) : null; } @@ -66,12 +84,21 @@ Portal.propTypes = { * so it's simply `document.body` most of the time. */ container: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), + /** + * Disable the portal behavior. + * The children stay within it's parent DOM hierarchy. + */ + disablePortal: PropTypes.bool, /** * Callback fired once the children has been mounted into the `container`. */ onRendered: PropTypes.func, }; +Portal.defaultProps = { + disablePortal: false, +}; + Portal.propTypes = exactProp(Portal.propTypes); export default Portal; diff --git a/packages/material-ui/src/Portal/Portal.test.js b/packages/material-ui/src/Portal/Portal.test.js index df60ed9256cfb0..28e5bba1b36980 100644 --- a/packages/material-ui/src/Portal/Portal.test.js +++ b/packages/material-ui/src/Portal/Portal.test.js @@ -38,6 +38,17 @@ describe('', () => { assert.strictEqual(wrapper.find(MenuItem).length, 2); }); + describe('prop: disablePortal', () => { + it('should work as expected', () => { + const wrapper = mount( + +

Foo

+
, + ); + assert.strictEqual(wrapper.children().length, 1, 'should have one children'); + }); + }); + describe('mount', () => { let cleanUp; diff --git a/packages/material-ui/src/Select/Select.js b/packages/material-ui/src/Select/Select.js index a16d4fc67bb268..69b00a205aabca 100644 --- a/packages/material-ui/src/Select/Select.js +++ b/packages/material-ui/src/Select/Select.js @@ -104,7 +104,7 @@ Select.propTypes = { */ inputProps: PropTypes.object, /** - * Properties applied to the `Menu` element. + * Properties applied to the [`Menu`](/api/menu) element. */ MenuProps: PropTypes.object, /** diff --git a/packages/material-ui/src/Select/SelectInput.js b/packages/material-ui/src/Select/SelectInput.js index a50eb4d5bc52c6..8133f56de9b100 100644 --- a/packages/material-ui/src/Select/SelectInput.js +++ b/packages/material-ui/src/Select/SelectInput.js @@ -367,7 +367,7 @@ SelectInput.propTypes = { */ inputRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), /** - * Properties applied to the `Menu` element. + * Properties applied to the [`Menu`](/api/menu) element. */ MenuProps: PropTypes.object, /** diff --git a/packages/material-ui/src/Snackbar/Snackbar.js b/packages/material-ui/src/Snackbar/Snackbar.js index d2f7b9ef6a9f0e..9ca1c6b7469bdb 100644 --- a/packages/material-ui/src/Snackbar/Snackbar.js +++ b/packages/material-ui/src/Snackbar/Snackbar.js @@ -303,7 +303,7 @@ Snackbar.propTypes = { */ className: PropTypes.string, /** - * Properties applied to the `SnackbarContent` element. + * Properties applied to the [`SnackbarContent`](/api/snackbar-content) element. */ ContentProps: PropTypes.object, /** diff --git a/packages/material-ui/src/StepLabel/StepLabel.js b/packages/material-ui/src/StepLabel/StepLabel.js index 79e0954ee53c3d..3a067c0c5226bb 100644 --- a/packages/material-ui/src/StepLabel/StepLabel.js +++ b/packages/material-ui/src/StepLabel/StepLabel.js @@ -173,7 +173,7 @@ StepLabel.propTypes = { */ orientation: PropTypes.oneOf(['horizontal', 'vertical']), /** - * Properties applied to the `StepIcon` element. + * Properties applied to the [`StepIcon`](/api/step-icon) element. */ StepIconProps: PropTypes.object, }; diff --git a/packages/material-ui/src/TablePagination/TablePagination.js b/packages/material-ui/src/TablePagination/TablePagination.js index f366f9d24c37a2..2be691a93e7ef4 100644 --- a/packages/material-ui/src/TablePagination/TablePagination.js +++ b/packages/material-ui/src/TablePagination/TablePagination.js @@ -156,7 +156,7 @@ TablePagination.propTypes = { */ ActionsComponent: PropTypes.oneOfType([PropTypes.string, PropTypes.func, PropTypes.object]), /** - * Properties applied to the back arrow `IconButton` component. + * Properties applied to the back arrow [`IconButton`](/api/icon-button) component. */ backIconButtonProps: PropTypes.object, /** @@ -187,7 +187,7 @@ TablePagination.propTypes = { */ labelRowsPerPage: PropTypes.node, /** - * Properties applied to the next arrow `IconButton` element. + * Properties applied to the next arrow [`IconButton`](/api/icon-button) element. */ nextIconButtonProps: PropTypes.object, /** @@ -217,7 +217,7 @@ TablePagination.propTypes = { */ rowsPerPageOptions: PropTypes.array, /** - * Properties applied to the rows per page `Select` element. + * Properties applied to the rows per page [`Select`](/api/select) element. */ SelectProps: PropTypes.object, }; diff --git a/packages/material-ui/src/TablePaginationActions/TablePaginationActions.js b/packages/material-ui/src/TablePaginationActions/TablePaginationActions.js index e3df35df1b120a..9bb5b86904a8c2 100644 --- a/packages/material-ui/src/TablePaginationActions/TablePaginationActions.js +++ b/packages/material-ui/src/TablePaginationActions/TablePaginationActions.js @@ -52,7 +52,7 @@ class TablePaginationActions extends React.Component { TablePaginationActions.propTypes = { /** - * Properties applied to the back arrow `IconButton` element. + * Properties applied to the back arrow [`IconButton`](/api/icon-button) element. */ backIconButtonProps: PropTypes.object, /** @@ -60,7 +60,7 @@ TablePaginationActions.propTypes = { */ count: PropTypes.number.isRequired, /** - * Properties applied to the next arrow `IconButton` element. + * Properties applied to the next arrow [`IconButton`](/api/icon-button) element. */ nextIconButtonProps: PropTypes.object, /** diff --git a/packages/material-ui/src/TextField/TextField.js b/packages/material-ui/src/TextField/TextField.js index 6caf6f4642fa22..3ba1e64cfca052 100644 --- a/packages/material-ui/src/TextField/TextField.js +++ b/packages/material-ui/src/TextField/TextField.js @@ -164,7 +164,7 @@ TextField.propTypes = { */ error: PropTypes.bool, /** - * Properties applied to the `FormHelperText` element. + * Properties applied to the [`FormHelperText`](/api/form-helper-text) element. */ FormHelperTextProps: PropTypes.object, /** @@ -181,7 +181,7 @@ TextField.propTypes = { */ id: PropTypes.string, /** - * Properties applied to the `InputLabel` element. + * Properties applied to the [`InputLabel`](/api/input-label) element. */ InputLabelProps: PropTypes.object, /** @@ -249,7 +249,7 @@ TextField.propTypes = { */ select: PropTypes.bool, /** - * Properties applied to the `Select` element. + * Properties applied to the [`Select`](/api/select) element. */ SelectProps: PropTypes.object, /** diff --git a/packages/material-ui/src/Tooltip/Tooltip.d.ts b/packages/material-ui/src/Tooltip/Tooltip.d.ts index 330bc0bb4a2e17..90b9d4584fe4b1 100644 --- a/packages/material-ui/src/Tooltip/Tooltip.d.ts +++ b/packages/material-ui/src/Tooltip/Tooltip.d.ts @@ -1,6 +1,7 @@ import * as React from 'react'; -import { IPopperProps } from 'react-popper'; import { StandardProps } from '..'; +import { TransitionProps } from '../transitions/transition'; +import { PortalProps } from '../Portal'; export interface TooltipProps extends StandardProps, TooltipClassKey, 'title'> { @@ -29,24 +30,21 @@ export interface TooltipProps | 'top-end' | 'top-start' | 'top'; - PopperProps?: Partial; + PopperProps?: Object; title: React.ReactNode; + TransitionComponent?: React.ReactType; + TransitionProps?: TransitionProps; } export type TooltipClassKey = - | 'root' | 'popper' - | 'open' | 'tooltip' + | 'touch' | 'tooltipPlacementLeft' | 'tooltipPlacementRight' | 'tooltipPlacementTop' | 'tooltipPlacementBottom'; -interface PopperProps extends IPopperProps { - PopperClassName: string; -} - declare const Tooltip: React.ComponentType; export default Tooltip; diff --git a/packages/material-ui/src/Tooltip/Tooltip.js b/packages/material-ui/src/Tooltip/Tooltip.js index 532602e467020f..8bbb2992db8bb1 100644 --- a/packages/material-ui/src/Tooltip/Tooltip.js +++ b/packages/material-ui/src/Tooltip/Tooltip.js @@ -1,50 +1,27 @@ -/* eslint-disable react/no-multi-comp, no-underscore-dangle */ - import React from 'react'; import PropTypes from 'prop-types'; -import EventListener from 'react-event-listener'; -import debounce from 'debounce'; // < 1kb payload overhead when lodash/debounce is > 3kb. import warning from 'warning'; import classNames from 'classnames'; -import { Manager, Popper, Target } from 'react-popper'; -import { capitalize } from '../utils/helpers'; import RootRef from '../RootRef'; -import Portal from '../Portal'; -import common from '../colors/common'; import withStyles from '../styles/withStyles'; +import { capitalize } from '../utils/helpers'; +import exactProp from '../utils/exactProp'; +import Grow from '../Grow'; +import Popper from '../Popper'; export const styles = theme => ({ popper: { zIndex: theme.zIndex.tooltip, - pointerEvents: 'none', - '&$open': { - pointerEvents: 'auto', - }, + opacity: 0.9, }, - open: {}, tooltip: { backgroundColor: theme.palette.grey[700], borderRadius: theme.shape.borderRadius, - color: common.white, + color: theme.palette.common.white, fontFamily: theme.typography.fontFamily, - opacity: 0, - transform: 'scale(0)', - transition: theme.transitions.create(['opacity', 'transform'], { - duration: theme.transitions.duration.shortest, - easing: theme.transitions.easing.easeIn, - }), - minHeight: 0, padding: '4px 8px', fontSize: theme.typography.pxToRem(10), lineHeight: `${theme.typography.round(14 / 10)}em`, - '&$open': { - opacity: 0.9, - transform: 'scale(1)', - transition: theme.transitions.create(['opacity', 'transform'], { - duration: theme.transitions.duration.shortest, - easing: theme.transitions.easing.easeOut, - }), - }, }, touch: { padding: '8px 16px', @@ -53,7 +30,7 @@ export const styles = theme => ({ }, tooltipPlacementLeft: { transformOrigin: 'right center', - margin: '0 24px', + margin: '0 24px ', [theme.breakpoints.up('sm')]: { margin: '0 14px', }, @@ -81,21 +58,6 @@ export const styles = theme => ({ }, }); -function flipPlacement(placement) { - switch (placement) { - case 'bottom-end': - return 'bottom-start'; - case 'bottom-start': - return 'bottom-end'; - case 'top-end': - return 'top-start'; - case 'top-start': - return 'top-end'; - default: - return placement; - } -} - class Tooltip extends React.Component { enterTimer = null; @@ -105,37 +67,33 @@ class Tooltip extends React.Component { closeTimer = null; - isControlled = null; - - popper = null; + childrenRef = null; - children = null; + isControlled = null; ignoreNonTouchEvents = false; - handleResize = debounce(() => { - if (this.popper) { - this.popper._popper.scheduleUpdate(); - } - }, 166); // Corresponds to 10 frames at 60 Hz. + defaultId = null; constructor(props) { super(props); this.isControlled = props.open != null; + this.state = { + open: null, + }; + if (!this.isControlled) { // not controlled, use internal state this.state.open = false; } } - state = {}; - componentDidMount() { warning( - !this.children || - !this.children.disabled || - !this.children.tagName.toLowerCase() === 'button', + !this.childrenRef || + !this.childrenRef.disabled || + !this.childrenRef.tagName.toLowerCase() === 'button', [ 'Material-UI: you are providing a disabled `button` child to the Tooltip component.', 'A disabled element does not fire events.', @@ -144,6 +102,16 @@ class Tooltip extends React.Component { 'Place a `div` container on top of the element.', ].join('\n'), ); + + // Fallback to this default id when possible. + // Use the random value for client side rendering only. + // We can't use it server side. + this.defaultId = `mui-tooltip-${Math.round(Math.random() * 1e5)}`; + + // Rerender with this.defaultId and this.childrenRef. + if (this.props.open) { + this.forceUpdate(); + } } componentWillUnmount() { @@ -151,7 +119,6 @@ class Tooltip extends React.Component { clearTimeout(this.leaveTimer); clearTimeout(this.touchTimer); clearTimeout(this.closeTimer); - this.handleResize.clear(); } handleEnter = event => { @@ -162,8 +129,8 @@ class Tooltip extends React.Component { childrenProps.onFocus(event); } - if (event.type === 'mouseover' && childrenProps.onMouseOver) { - childrenProps.onMouseOver(event); + if (event.type === 'mouseenter' && childrenProps.onMouseEnter) { + childrenProps.onMouseEnter(event); } if (this.ignoreNonTouchEvents && event.type !== 'touchstart') { @@ -234,10 +201,9 @@ class Tooltip extends React.Component { handleTouchStart = event => { this.ignoreNonTouchEvents = true; const { children, enterTouchDelay } = this.props; - const childrenProps = children.props; - if (childrenProps.onTouchStart) { - childrenProps.onTouchStart(event); + if (children.props.onTouchStart) { + children.props.onTouchStart(event); } clearTimeout(this.leaveTimer); @@ -251,10 +217,9 @@ class Tooltip extends React.Component { handleTouchEnd = event => { const { children, leaveTouchDelay } = this.props; - const childrenProps = children.props; - if (childrenProps.onTouchEnd) { - childrenProps.onTouchEnd(event); + if (children.props.onTouchEnd) { + children.props.onTouchEnd(event); } clearTimeout(this.touchTimer); @@ -269,41 +234,38 @@ class Tooltip extends React.Component { const { children, classes, - className, disableFocusListener, disableHoverListener, disableTouchListener, - enterDelay, - enterTouchDelay, id, - leaveDelay, - leaveTouchDelay, - onClose, - onOpen, open: openProp, - placement: placementProp, - PopperProps: { className: PopperClassName, ...PopperProps } = {}, + placement, + PopperProps, theme, title, - ...other + TransitionComponent, + TransitionProps, } = this.props; - const placement = theme.direction === 'rtl' ? flipPlacement(placementProp) : placementProp; let open = this.isControlled ? openProp : this.state.open; - const childrenProps = { 'aria-describedby': id }; // There is no point at displaying an empty tooltip. if (title === '') { open = false; } + const childrenProps = { + 'aria-describedby': open ? id || this.defaultId : null, + title: !open && typeof title === 'string' ? title : null, + }; + if (!disableTouchListener) { childrenProps.onTouchStart = this.handleTouchStart; childrenProps.onTouchEnd = this.handleTouchEnd; } if (!disableHoverListener) { - childrenProps.onMouseOver = this.handleEnter; + childrenProps.onMouseEnter = this.handleEnter; childrenProps.onMouseLeave = this.handleLeave; } @@ -321,62 +283,44 @@ class Tooltip extends React.Component { ); return ( - - - - {({ targetProps }) => ( - { - this.children = node; - targetProps.ref(this.children); - }} + + { + this.childrenRef = node; + }} + > + {React.cloneElement(children, childrenProps)} + + + {({ placement: placementInner, TransitionProps: TransitionPropsInner }) => ( + - {React.cloneElement(children, childrenProps)} - +
+ {title} +
+ )} -
- - { - this.popper = node; - }} - {...PopperProps} - > - {({ popperProps, restProps }) => { - const actualPlacement = (popperProps['data-placement'] || placement).split('-')[0]; - return ( -
- -
- ); - }} -
-
-
+
+ ); } } @@ -391,10 +335,6 @@ Tooltip.propTypes = { * See [CSS API](#css-api) below for more details. */ classes: PropTypes.object.isRequired, - /** - * @ignore - */ - className: PropTypes.string, /** * Do not respond to focus events. */ @@ -418,7 +358,8 @@ Tooltip.propTypes = { enterTouchDelay: PropTypes.number, /** * The relationship between the tooltip and the wrapper component is not clear from the DOM. - * By providing this property, we can use aria-describedby to solve the accessibility issue. + * This property is used with aria-describedby to solve the accessibility issue. + * If you don't provide this property. It fallback to a random generated id. */ id: PropTypes.string, /** @@ -447,7 +388,7 @@ Tooltip.propTypes = { */ open: PropTypes.bool, /** - * Tooltip placement + * Tooltip placement. */ placement: PropTypes.oneOf([ 'bottom-end', @@ -464,7 +405,7 @@ Tooltip.propTypes = { 'top', ]), /** - * Properties applied to the `Popper` element. + * Properties applied to the [`Popper`](/api/popper) element. */ PopperProps: PropTypes.object, /** @@ -475,8 +416,18 @@ Tooltip.propTypes = { * Tooltip title. Zero-length titles string are never displayed. */ title: PropTypes.node.isRequired, + /** + * Transition component. + */ + TransitionComponent: PropTypes.oneOfType([PropTypes.string, PropTypes.func, PropTypes.object]), + /** + * Properties applied to the `Transition` element. + */ + TransitionProps: PropTypes.object, }; +Tooltip.propTypes = exactProp(Tooltip.propTypes); + Tooltip.defaultProps = { disableFocusListener: false, disableHoverListener: false, @@ -486,6 +437,7 @@ Tooltip.defaultProps = { leaveDelay: 0, leaveTouchDelay: 1500, placement: 'bottom', + TransitionComponent: Grow, }; export default withStyles(styles, { name: 'MuiTooltip', withTheme: true })(Tooltip); diff --git a/packages/material-ui/src/Tooltip/Tooltip.test.js b/packages/material-ui/src/Tooltip/Tooltip.test.js index ceddcaaf58adba..46d481914a22a9 100644 --- a/packages/material-ui/src/Tooltip/Tooltip.test.js +++ b/packages/material-ui/src/Tooltip/Tooltip.test.js @@ -3,149 +3,64 @@ import React from 'react'; import { assert } from 'chai'; import { spy, useFakeTimers } from 'sinon'; -import { Popper, Target } from 'react-popper'; -import { ShallowWrapper } from 'enzyme'; import consoleErrorMock from 'test/utils/consoleErrorMock'; -import { createShallow, createMount, getClasses, unwrap } from '../test-utils'; -import createMuiTheme from '../styles/createMuiTheme'; +import { createShallow, createMount, getClasses } from '../test-utils'; +import Popper from '../Popper'; import Tooltip from './Tooltip'; function persist() {} -// Remove the style from the DOM element. -// eslint-disable-next-line react/prop-types -const Hack = ({ style, innerRef, ...other }) =>
; - -function getTargetChildren(wrapper) { - return new ShallowWrapper( - wrapper - .find(Target) - .props() - .children({}).props.children, - wrapper, - ); -} - -function getPopperChildren(wrapper) { - return new ShallowWrapper( - wrapper - .find(Popper) - .props() - .children({ popperProps: { style: {} }, restProps: {} }), - null, - ); -} - describe('', () => { let shallow; let mount; let classes; - const TooltipNaked = unwrap(Tooltip); + const defaultProps = { + title: 'Hello World', + children: Hello World, + }; before(() => { shallow = createShallow({ dive: true }); mount = createMount(); - classes = getClasses( - - Hello World - , - ); + classes = getClasses(); }); after(() => { mount.cleanUp(); }); - it('should render a Manager', () => { - const wrapper = shallow( - - Hello World - , - ); - assert.strictEqual(wrapper.name(), 'Manager'); - assert.strictEqual(wrapper.childAt(0).name(), 'EventListener'); - }); - - it('should render with the user, tooltip classes', () => { - const wrapper = shallow( - - Hello World - , - ); - const popperChildren = getPopperChildren(wrapper); - assert.strictEqual(popperChildren.childAt(0).hasClass(classes.tooltip), true); + it('should render the correct structure', () => { + const wrapper = shallow(); + assert.strictEqual(wrapper.type(), React.Fragment); + assert.strictEqual(wrapper.childAt(0).name(), 'RootRef'); + assert.strictEqual(wrapper.childAt(1).name(), 'WithTheme(Popper)'); + assert.strictEqual(wrapper.childAt(1).hasClass(classes.popper), true); }); describe('prop: title', () => { + it('should display if the title is presetn', () => { + const wrapper = shallow(); + assert.strictEqual(wrapper.find(Popper).props().open, true); + }); + it('should not display if the title is an empty string', () => { - const wrapper = shallow( - - Hello World - , - ); - assert.strictEqual(wrapper.find(Popper).hasClass(classes.open), false); + const wrapper = shallow(); + assert.strictEqual(wrapper.find(Popper).props().open, false); }); }); describe('prop: placement', () => { it('should have top placement', () => { - const wrapper = shallow( - - Hello World - , - ); - const popperChildren = getPopperChildren(wrapper); - assert.strictEqual(popperChildren.childAt(0).hasClass(classes.tooltip), true); - wrapper.childAt(0).simulate('click'); - assert.strictEqual(popperChildren.childAt(0).hasClass(classes.tooltipPlacementTop), true); - }); - - const theme = createMuiTheme({ - direction: 'rtl', - }); - - [ - { - in: 'bottom-end', - out: 'bottom-start', - }, - { - in: 'bottom-start', - out: 'bottom-end', - }, - { - in: 'top-end', - out: 'top-start', - }, - { - in: 'top-start', - out: 'top-end', - }, - { - in: 'top', - out: 'top', - }, - ].forEach(test => { - it(`should flip ${test.in} when direction=rtl is used`, () => { - const wrapper = shallow( - - Hello World - , - ); - assert.strictEqual(wrapper.find(Popper).props().placement, test.out); - }); + const wrapper = shallow(); + assert.strictEqual(wrapper.find(Popper).props().placement, 'top'); }); }); it('should respond to external events', () => { - const wrapper = shallow( - - - , - ); - const children = getTargetChildren(wrapper); + const wrapper = shallow(); + const children = wrapper.childAt(0).childAt(0); assert.strictEqual(wrapper.state().open, false); - children.simulate('mouseOver', {}); + children.simulate('mouseEnter', {}); assert.strictEqual(wrapper.state().open, true); children.simulate('blur', {}); assert.strictEqual(wrapper.state().open, false); @@ -156,20 +71,12 @@ describe('', () => { const handleClose = spy(); const wrapper = shallow( - - - , + , ); - const children = getTargetChildren(wrapper); + const children = wrapper.childAt(0).childAt(0); assert.strictEqual(handleRequestOpen.callCount, 0); assert.strictEqual(handleClose.callCount, 0); - children.simulate('mouseOver', { type: 'mouseover' }); + children.simulate('mouseEnter', { type: 'mouseover' }); assert.strictEqual(handleRequestOpen.callCount, 1); assert.strictEqual(handleClose.callCount, 0); children.simulate('blur', { type: 'blur' }); @@ -189,12 +96,8 @@ describe('', () => { }); it('should not respond to quick events', () => { - const wrapper = shallow( - - - , - ); - const children = getTargetChildren(wrapper); + const wrapper = shallow(); + const children = wrapper.childAt(0).childAt(0); children.simulate('touchStart', { type: 'touchstart', persist }); children.simulate('touchEnd', { type: 'touchend', persist }); children.simulate('focus', { type: 'focus' }); @@ -203,12 +106,8 @@ describe('', () => { }); it('should open on long press', () => { - const wrapper = shallow( - - - , - ); - const children = getTargetChildren(wrapper); + const wrapper = shallow(); + const children = wrapper.childAt(0).childAt(0); children.simulate('touchStart', { type: 'touchstart', persist }); children.simulate('focus', { type: 'focus' }); children.simulate('mouseover', { type: 'mouseover' }); @@ -222,11 +121,7 @@ describe('', () => { describe('mount', () => { it('should mount without any issue', () => { - mount( - - - , - ); + mount(); }); }); @@ -242,12 +137,8 @@ describe('', () => { }); it('should take the enterDelay into account', () => { - const wrapper = shallow( - - - , - ); - const children = getTargetChildren(wrapper); + const wrapper = shallow(); + const children = wrapper.childAt(0).childAt(0); children.simulate('focus', { type: 'focus', persist }); assert.strictEqual(wrapper.state().open, false); clock.tick(111); @@ -255,12 +146,8 @@ describe('', () => { }); it('should take the leaveDelay into account', () => { - const wrapper = shallow( - - - , - ); - const children = getTargetChildren(wrapper); + const wrapper = shallow(); + const children = wrapper.childAt(0).childAt(0); children.simulate('focus', { type: 'focus' }); assert.strictEqual(wrapper.state().open, true); children.simulate('blur', { type: 'blur', persist }); @@ -271,7 +158,7 @@ describe('', () => { }); describe('prop: overrides', () => { - ['onTouchStart', 'onTouchEnd', 'onMouseOver', 'onMouseLeave', 'onFocus', 'onBlur'].forEach( + ['onTouchStart', 'onTouchEnd', 'onMouseEnter', 'onMouseLeave', 'onFocus', 'onBlur'].forEach( name => { it(`should be transparent for the ${name} event`, () => { const handler = spy(); @@ -282,7 +169,7 @@ describe('', () => { , ); - const children = getTargetChildren(wrapper); + const children = wrapper.childAt(0).childAt(0); const type = name.slice(2).toLowerCase(); children.simulate(type, { type, persist }); assert.strictEqual(handler.callCount, 1); @@ -291,34 +178,6 @@ describe('', () => { ); }); - describe('resize', () => { - let clock; - - before(() => { - clock = useFakeTimers(); - }); - - after(() => { - clock.restore(); - }); - - it('should recompute the correct position', () => { - const handleUpdate = spy(); - const wrapper = mount( - -
Foo
-
, - ); - const instance = wrapper.instance(); - instance.handleResize(); - assert.strictEqual(handleUpdate.callCount, 0); - clock.tick(1); - instance.popper._popper.scheduleUpdate = handleUpdate; - clock.tick(165); - assert.strictEqual(handleUpdate.callCount, 1); - }); - }); - describe('disabled button warning', () => { before(() => { consoleErrorMock.spy(); diff --git a/packages/material-ui/src/index.d.ts b/packages/material-ui/src/index.d.ts index 16d32dd7c499be..dcc6080becc9d8 100644 --- a/packages/material-ui/src/index.d.ts +++ b/packages/material-ui/src/index.d.ts @@ -143,6 +143,7 @@ export { default as Modal, ModalManager } from './Modal'; export { default as NativeSelect } from './NativeSelect'; export { default as Paper } from './Paper'; export { default as Popover } from './Popover'; +export { default as Popper } from './Popper'; export { default as Portal } from './Portal'; export { default as Radio } from './Radio'; export { default as RadioGroup } from './RadioGroup'; diff --git a/packages/material-ui/src/index.js b/packages/material-ui/src/index.js index 5c3296476f8029..92734bcc3e30c2 100644 --- a/packages/material-ui/src/index.js +++ b/packages/material-ui/src/index.js @@ -76,6 +76,7 @@ export { default as Modal, ModalManager } from './Modal'; export { default as NativeSelect } from './NativeSelect'; export { default as Paper } from './Paper'; export { default as Popover } from './Popover'; +export { default as Popper } from './Popper'; export { default as Portal } from './Portal'; export { default as Radio } from './Radio'; export { default as RadioGroup } from './RadioGroup'; diff --git a/pages/api/click-away-listener.md b/pages/api/click-away-listener.md index d175468d2fc383..e724aaf223058a 100644 --- a/pages/api/click-away-listener.md +++ b/pages/api/click-away-listener.md @@ -16,10 +16,10 @@ For instance, if you need to hide a menu when people click anywhere else on your | Name | Type | Default | Description | |:-----|:-----|:--------|:------------| -| children * | node |   | | -| mouseEvent | enum: 'onClick' |
 'onMouseDown' |
 'onMouseUp' |
 false
| 'onMouseUp' | | -| onClickAway * | func |   | | -| touchEvent | enum: 'onTouchStart' |
 'onTouchEnd' |
 false
| 'onTouchEnd' | | +| children * | element |   | The wrapped element. | +| mouseEvent | enum: 'onClick' |
 'onMouseDown' |
 'onMouseUp' |
 false
| 'onMouseUp' | The mouse event to listen to. You can disable the listener by providing `false`. | +| onClickAway * | func |   | Callback fired when a "click away" event is detected. | +| touchEvent | enum: 'onTouchStart' |
 'onTouchEnd' |
 false
| 'onTouchEnd' | The touch event to listen to. You can disable the listener by providing `false`. | Any other properties supplied will be spread to the root element ([EventListener](https://github.com/oliviertassinari/react-event-listener)). diff --git a/pages/api/dialog.md b/pages/api/dialog.md index e2520724a55c3f..49e822819304c3 100644 --- a/pages/api/dialog.md +++ b/pages/api/dialog.md @@ -32,7 +32,7 @@ Dialogs are overlaid modal paper based components with a backdrop. | onExited | func |   | Callback fired when the dialog has exited. | | onExiting | func |   | Callback fired when the dialog is exiting. | | open * | bool |   | If `true`, the Dialog is open. | -| PaperProps | object |   | Properties applied to the `Paper` element. | +| PaperProps | object |   | Properties applied to the [`Paper`](/api/paper) element. | | scroll | enum: 'body' |
 'paper'
| 'paper' | Determine the container for scrolling the dialog. | | TransitionComponent | union: string |
 func |
 object
| Fade | Transition component. | | transitionDuration | union: number |
 {enter?: number, exit?: number}
| { enter: duration.enteringScreen, exit: duration.leavingScreen } | The duration for the transition, in milliseconds. You may specify a single timeout for all transitions, or individually with an object. | diff --git a/pages/api/drawer.md b/pages/api/drawer.md index 61921d3a2ce170..1cc53aa7740d22 100644 --- a/pages/api/drawer.md +++ b/pages/api/drawer.md @@ -20,11 +20,11 @@ when `variant="temporary"` is set. | children | node |   | The contents of the drawer. | | classes | object |   | Override or extend the styles applied to the component. See [CSS API](#css-api) below for more details. | | elevation | number | 16 | The elevation of the drawer. | -| ModalProps | object |   | Properties applied to the `Modal` element. | +| ModalProps | object |   | Properties applied to the [`Modal`](/api/modal) element. | | onClose | func |   | Callback fired when the component requests to be closed.

**Signature:**
`function(event: object) => void`
*event:* The event source of the callback | | open | bool | false | If `true`, the drawer is open. | -| PaperProps | object |   | Properties applied to the `Paper` element. | -| SlideProps | object |   | Properties applied to the `Slide` element. | +| PaperProps | object |   | Properties applied to the [`Paper`](/api/paper) element. | +| SlideProps | object |   | Properties applied to the [`Slide`](/api/slide) element. | | transitionDuration | union: number |
 {enter?: number, exit?: number}
| { enter: duration.enteringScreen, exit: duration.leavingScreen } | The duration for the transition, in milliseconds. You may specify a single timeout for all transitions, or individually with an object. | | variant | enum: 'permanent' |
 'persistent' |
 'temporary'
| 'temporary' | The variant of drawer. | diff --git a/pages/api/expansion-panel.md b/pages/api/expansion-panel.md index 19452a270c21a6..bdb281de6160b2 100644 --- a/pages/api/expansion-panel.md +++ b/pages/api/expansion-panel.md @@ -17,7 +17,7 @@ title: ExpansionPanel API |:-----|:-----|:--------|:------------| | children * | node |   | The content of the expansion panel. | | classes | object |   | Override or extend the styles applied to the component. See [CSS API](#css-api) below for more details. | -| CollapseProps | object |   | Properties applied to the `Collapse` element. | +| CollapseProps | object |   | Properties applied to the [`Collapse`](/api/collapse) element. | | defaultExpanded | bool | false | If `true`, expands the panel by default. | | disabled | bool | false | If `true`, the panel will be displayed in a disabled state. | | expanded | bool |   | If `true`, expands the panel, otherwise collapse it. Setting this prop enables control over the panel. | diff --git a/pages/api/grow.md b/pages/api/grow.md index bec19dadea346f..773f7115457cf0 100644 --- a/pages/api/grow.md +++ b/pages/api/grow.md @@ -29,6 +29,6 @@ You can take advantage of this behavior to [target nested components](/guides/ap ## Demos -- [Popovers](/utils/popovers) +- [Popover](/utils/popover) - [Transitions](/utils/transitions) diff --git a/pages/api/menu.md b/pages/api/menu.md index dfb55b5228bcb3..f1c7a6987d8e79 100644 --- a/pages/api/menu.md +++ b/pages/api/menu.md @@ -15,11 +15,11 @@ title: Menu API | Name | Type | Default | Description | |:-----|:-----|:--------|:------------| -| anchorEl | object |   | The DOM element used to set the position of the menu. | +| anchorEl | union: object |
 func
|   | The DOM element used to set the position of the menu. | | children | node |   | Menu contents, normally `MenuItem`s. | | classes | object |   | Override or extend the styles applied to the component. See [CSS API](#css-api) below for more details. | | disableAutoFocusItem | bool | false | If `true`, the selected / first menu item will not be auto focused. | -| MenuListProps | object |   | Properties applied to the `MenuList` element. | +| MenuListProps | object |   | Properties applied to the [`MenuList`](/api/menu-list) element. | | onClose | func |   | Callback fired when the component requests to be closed.

**Signature:**
`function(event: object) => void`
*event:* The event source of the callback | | onEnter | func |   | Callback fired before the Menu enters. | | onEntered | func |   | Callback fired when the Menu has entered. | diff --git a/pages/api/modal.md b/pages/api/modal.md index e811e443da09a8..6e1c3d2c893ab6 100644 --- a/pages/api/modal.md +++ b/pages/api/modal.md @@ -16,7 +16,7 @@ title: Modal API | Name | Type | Default | Description | |:-----|:-----|:--------|:------------| | BackdropComponent | union: string |
 func |
 object
| Backdrop | A backdrop component. This property enables custom backdrop rendering. | -| BackdropProps | object |   | Properties applied to the `Backdrop` element. | +| BackdropProps | object |   | Properties applied to the [`Backdrop`](/api/backdrop) element. | | children | element |   | A single child content element. | | classes | object |   | Override or extend the styles applied to the component. See [CSS API](#css-api) below for more details. | | container | union: object |
 func
|   | A node, component instance, or function that returns either. The `container` will have the portal children appended to it. | @@ -24,6 +24,7 @@ title: Modal API | disableBackdropClick | bool | false | If `true`, clicking the backdrop will not fire any callback. | | disableEnforceFocus | bool | false | If `true`, the modal will not prevent focus from leaving the modal while open.
Generally this should never be set to `true` as it makes the modal less accessible to assistive technologies, like screen readers. | | disableEscapeKeyDown | bool | false | If `true`, hitting escape will not fire any callback. | +| disablePortal | bool | false | Disable the portal behavior. The children stay within it's parent DOM hierarchy. | | disableRestoreFocus | bool | false | If `true`, the modal will not restore focus to previously focused element once modal is hidden. | | hideBackdrop | bool | false | If `true`, the backdrop is not rendered. | | keepMounted | bool | false | Always keep the children in the DOM. This property can be useful in SEO situation or when you want to maximize the responsiveness of the Modal. | @@ -34,7 +35,7 @@ title: Modal API | onRendered | func |   | Callback fired once the children has been mounted into the `container`. It signals that the `open={true}` property took effect. | | open * | bool |   | If `true`, the modal is open. | -Any other properties supplied will be spread to the root element ([Portal](/api/portal)). +Any other properties supplied will be spread to the root element (native element). ## CSS API @@ -51,12 +52,7 @@ If using the `overrides` key of the theme as documented [here](/customization/themes#customizing-all-instances-of-a-component-type), you need to use the following style sheet name: `MuiModal`. -## Inheritance - -The properties of the [Portal](/api/portal) component are also available. -You can take advantage of this behavior to [target nested components](/guides/api#spread). - ## Demos -- [Modals](/utils/modals) +- [Modal](/utils/modal) diff --git a/pages/api/popover.md b/pages/api/popover.md index 946172eb6a46ed..4288d3decac71e 100644 --- a/pages/api/popover.md +++ b/pages/api/popover.md @@ -34,7 +34,7 @@ title: Popover API | onExited | func |   | Callback fired when the component has exited. | | onExiting | func |   | Callback fired when the component is exiting. | | open * | bool |   | If `true`, the popover is visible. | -| PaperProps | object |   | Properties applied to the `Paper` element. | +| PaperProps | object |   | Properties applied to the [`Paper`](/api/paper) element. | | transformOrigin | {horizontal?: union: number |
 enum: 'left' |
 'center' |
 'right'

, vertical?: union: number |
 enum: 'top' |
 'center' |
 'bottom'

} | { vertical: 'top', horizontal: 'left',} | This is the point on the popover which will attach to the anchor's origin.
Options: vertical: [top, center, bottom, x(px)]; horizontal: [left, center, right, x(px)]. | | TransitionComponent | union: string |
 func |
 object
| Grow | Transition component. | | transitionDuration | union: number |
 {enter?: number, exit?: number} |
 enum: 'auto'

| 'auto' | Set to 'auto' to automatically calculate transition time based on height. | @@ -63,5 +63,5 @@ You can take advantage of this behavior to [target nested components](/guides/ap ## Demos -- [Popovers](/utils/popovers) +- [Popover](/utils/popover) diff --git a/pages/api/popper.js b/pages/api/popper.js new file mode 100644 index 00000000000000..9b0ff73d2faa70 --- /dev/null +++ b/pages/api/popper.js @@ -0,0 +1,10 @@ +import React from 'react'; +import withRoot from 'docs/src/modules/components/withRoot'; +import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs'; +import markdown from './popper.md'; + +function Page() { + return ; +} + +export default withRoot(Page); diff --git a/pages/api/popper.md b/pages/api/popper.md new file mode 100644 index 00000000000000..caba79a0d8ee8b --- /dev/null +++ b/pages/api/popper.md @@ -0,0 +1,35 @@ +--- +filename: /packages/material-ui/src/Popper/Popper.js +title: Popper API +--- + + + +# Popper + +

The API documentation of the Popper React component.

+ + + +## Props + +| Name | Type | Default | Description | +|:-----|:-----|:--------|:------------| +| anchorEl | union: object |
 func
|   | This is the DOM element, or a function that returns the DOM element, that may be used to set the position of the popover. | +| children | union: node |
 func
|   | Popper render function or node. | +| container | union: object |
 func
|   | A node, component instance, or function that returns either. The `container` will passed to the Modal component. By default, it uses the body of the anchorEl's top-level document object, so it's simply `document.body` most of the time. | +| disablePortal | bool | false | Disable the portal behavior. The children stay within it's parent DOM hierarchy. | +| keepMounted | bool |   | Always keep the children in the DOM. This property can be useful in SEO situation or when you want to maximize the responsiveness of the Popper. | +| open * | bool |   | If `true`, the popper is visible. | +| placement | enum: 'bottom-end', 'bottom-start', 'bottom', 'left-end', 'left-start', 'left', 'right-end', 'right-start', 'right', 'top-end', 'top-start', 'top'
| 'bottom' | Popper placement. | +| popperOptions | object |   | Options provided to the [`popper.js`](https://github.com/FezVrasta/popper.js) instance. | +| transition | bool | false | Help supporting a react-transition-group/Transition component. | + +Any other properties supplied will be spread to the root element (native element). + +## Demos + +- [Autocomplete](/demos/autocomplete) +- [Menus](/demos/menus) +- [Popper](/utils/popper) + diff --git a/pages/api/portal.md b/pages/api/portal.md index 968b428edc353c..ac0ebd36ef7f04 100644 --- a/pages/api/portal.md +++ b/pages/api/portal.md @@ -9,10 +9,8 @@ title: Portal API

The API documentation of the Portal React component.

-This component shares many concepts with -[react-overlays](https://react-bootstrap.github.io/react-overlays/#portals) -But has been forked in order to fix some bugs, reduce the number of dependencies -and take the control of our destiny. +Portals provide a first-class way to render children into a DOM node +that exists outside the DOM hierarchy of the parent component. ## Props @@ -20,6 +18,7 @@ and take the control of our destiny. |:-----|:-----|:--------|:------------| | children * | node |   | The children to render into the `container`. | | container | union: object |
 func
|   | A node, component instance, or function that returns either. The `container` will have the portal children appended to it. By default, it uses the body of the top-level document object, so it's simply `document.body` most of the time. | +| disablePortal | bool | false | Disable the portal behavior. The children stay within it's parent DOM hierarchy. | | onRendered | func |   | Callback fired once the children has been mounted into the `container`. | Any other properties supplied will be spread to the root element (native element). diff --git a/pages/api/select.md b/pages/api/select.md index db6b1a14584009..519a33022499d3 100644 --- a/pages/api/select.md +++ b/pages/api/select.md @@ -22,7 +22,7 @@ title: Select API | IconComponent | union: string |
 func |
 object
| ArrowDropDownIcon | The icon that displays the arrow. | | input | element | <Input /> | An `Input` element; does not have to be a material-ui specific `Input`. | | inputProps | object |   | Attributes applied to the `input` element. When `native` is `true`, the attributes are applied on the `select` element. | -| MenuProps | object |   | Properties applied to the `Menu` element. | +| MenuProps | object |   | Properties applied to the [`Menu`](/api/menu) element. | | multiple | bool | false | If true, `value` must be an array and the menu will support multiple selections. You can only use it when the `native` property is `false` (default). | | native | bool | false | If `true`, the component will be using a native `select` element. | | onChange | func |   | Callback function fired when a menu item is selected.

**Signature:**
`function(event: object, child?: object) => void`
*event:* The event source of the callback. You can pull out the new value by accessing `event.target.value`.
*child:* The react element that was selected when `native` is `false` (default). | diff --git a/pages/api/snackbar.md b/pages/api/snackbar.md index 36951fc1850bd8..63bc4af59469dd 100644 --- a/pages/api/snackbar.md +++ b/pages/api/snackbar.md @@ -20,7 +20,7 @@ title: Snackbar API | autoHideDuration | number |   | The number of milliseconds to wait before automatically calling the `onClose` function. `onClose` should then set the state of the `open` prop to hide the Snackbar. This behavior is disabled by default with the `null` value. | | children | element |   | If you wish the take control over the children of the component you can use this property. When used, you replace the `SnackbarContent` component with the children. | | classes | object |   | Override or extend the styles applied to the component. See [CSS API](#css-api) below for more details. | -| ContentProps | object |   | Properties applied to the `SnackbarContent` element. | +| ContentProps | object |   | Properties applied to the [`SnackbarContent`](/api/snackbar-content) element. | | disableWindowBlurListener | bool | false | If `true`, the `autoHideDuration` timer will expire even if the window is not focused. | | key | any |   | When displaying multiple consecutive Snackbars from a parent rendering a single <Snackbar/>, add the key property to ensure independent treatment of each message. e.g. <Snackbar key={message} />, otherwise, the message may update-in-place and features such as autoHideDuration may be canceled. | | message | node |   | The message to display. | diff --git a/pages/api/step-label.md b/pages/api/step-label.md index 2aba0366138f71..4930f40f6c6a3a 100644 --- a/pages/api/step-label.md +++ b/pages/api/step-label.md @@ -21,7 +21,7 @@ title: StepLabel API | error | bool | false | Mark the step as failed. | | icon | node |   | Override the default icon. | | optional | node |   | The optional node to display. | -| StepIconProps | object |   | Properties applied to the `StepIcon` element. | +| StepIconProps | object |   | Properties applied to the [`StepIcon`](/api/step-icon) element. | Any other properties supplied will be spread to the root element (native element). diff --git a/pages/api/table-pagination.md b/pages/api/table-pagination.md index ed350c40e1e368..dce7b8a04d3b9f 100644 --- a/pages/api/table-pagination.md +++ b/pages/api/table-pagination.md @@ -16,19 +16,19 @@ A `TableCell` based component for placing inside `TableFooter` for pagination. | Name | Type | Default | Description | |:-----|:-----|:--------|:------------| | ActionsComponent | union: string |
 func |
 object
| TablePaginationActions | The component used for displaying the actions. Either a string to use a DOM element or a component. | -| backIconButtonProps | object |   | Properties applied to the back arrow `IconButton` component. | +| backIconButtonProps | object |   | Properties applied to the back arrow [`IconButton`](/api/icon-button) component. | | classes | object |   | Override or extend the styles applied to the component. See [CSS API](#css-api) below for more details. | | component | union: string |
 func |
 object
| TableCell | The component used for the root node. Either a string to use a DOM element or a component. | | count * | number |   | The total number of rows. | | labelDisplayedRows | func | ({ from, to, count }) => `${from}-${to} of ${count}` | Customize the displayed rows label. | | labelRowsPerPage | node | 'Rows per page:' | Customize the rows per page label. Invoked with a `{ from, to, count, page }` object. | -| nextIconButtonProps | object |   | Properties applied to the next arrow `IconButton` element. | +| nextIconButtonProps | object |   | Properties applied to the next arrow [`IconButton`](/api/icon-button) element. | | onChangePage * | func |   | Callback fired when the page is changed.

**Signature:**
`function(event: object, page: number) => void`
*event:* The event source of the callback
*page:* The page selected | | onChangeRowsPerPage | func |   | Callback fired when the number of rows per page is changed.

**Signature:**
`function(event: object) => void`
*event:* The event source of the callback | | page * | number |   | The zero-based index of the current page. | | rowsPerPage * | number |   | The number of rows per page. | | rowsPerPageOptions | array | [5, 10, 25] | Customizes the options of the rows per page select field. If less than two options are available, no select field will be displayed. | -| SelectProps | object |   | Properties applied to the rows per page `Select` element. | +| SelectProps | object |   | Properties applied to the rows per page [`Select`](/api/select) element. | Any other properties supplied will be spread to the root element ([TableCell](/api/table-cell)). diff --git a/pages/api/text-field.md b/pages/api/text-field.md index a22e4c274a2927..1024456b2f4728 100644 --- a/pages/api/text-field.md +++ b/pages/api/text-field.md @@ -45,11 +45,11 @@ For advanced cases, please look at the source of TextField by clicking on the | defaultValue | union: string |
 number
|   | The default value of the `Input` element. | | disabled | bool |   | If `true`, the input will be disabled. | | error | bool |   | If `true`, the label will be displayed in an error state. | -| FormHelperTextProps | object |   | Properties applied to the `FormHelperText` element. | +| FormHelperTextProps | object |   | Properties applied to the [`FormHelperText`](/api/form-helper-text) element. | | fullWidth | bool |   | If `true`, the input will take up the full width of its container. | | helperText | node |   | The helper text content. | | id | string |   | The id of the `input` element. Use that property to make `label` and `helperText` accessible for screen readers. | -| InputLabelProps | object |   | Properties applied to the `InputLabel` element. | +| InputLabelProps | object |   | Properties applied to the [`InputLabel`](/api/input-label) element. | | InputProps | object |   | Properties applied to the `Input` element. | | inputProps | object |   | Attributes applied to the native `input` element. | | inputRef | union: func |
 object
|   | Use that property to pass a ref callback to the native input component. | @@ -63,7 +63,7 @@ For advanced cases, please look at the source of TextField by clicking on the | rows | union: string |
 number
|   | Number of rows to display when multiline option is set to true. | | rowsMax | union: string |
 number
|   | Maximum number of rows to display when multiline option is set to true. | | select | bool | false | Render a `Select` element while passing the `Input` element to `Select` as `input` parameter. If this option is set you must pass the options of the select as children. | -| SelectProps | object |   | Properties applied to the `Select` element. | +| SelectProps | object |   | Properties applied to the [`Select`](/api/select) element. | | type | string |   | Type attribute of the `Input` element. It should be a valid HTML5 input type. | | value | union: string |
 number |
 arrayOf
|   | The value of the `Input` element, required for a controlled component. | diff --git a/pages/api/tooltip.md b/pages/api/tooltip.md index 6d1c6594673a31..ed60aed19d3f00 100644 --- a/pages/api/tooltip.md +++ b/pages/api/tooltip.md @@ -22,15 +22,17 @@ title: Tooltip API | disableTouchListener | bool | false | Do not respond to long press touch events. | | enterDelay | number | 0 | The number of milliseconds to wait before showing the tooltip. This property won't impact the enter touch delay (`enterTouchDelay`). | | enterTouchDelay | number | 1000 | The number of milliseconds a user must touch the element before showing the tooltip. | -| id | string |   | The relationship between the tooltip and the wrapper component is not clear from the DOM. By providing this property, we can use aria-describedby to solve the accessibility issue. | +| id | string |   | The relationship between the tooltip and the wrapper component is not clear from the DOM. This property is used with aria-describedby to solve the accessibility issue. If you don't provide this property. It fallback to a random generated id. | | leaveDelay | number | 0 | The number of milliseconds to wait before hiding the tooltip. This property won't impact the leave touch delay (`leaveTouchDelay`). | | leaveTouchDelay | number | 1500 | The number of milliseconds after the user stops touching an element before hiding the tooltip. | | onClose | func |   | Callback fired when the tooltip requests to be closed.

**Signature:**
`function(event: object) => void`
*event:* The event source of the callback | | onOpen | func |   | Callback fired when the tooltip requests to be open.

**Signature:**
`function(event: object) => void`
*event:* The event source of the callback | | open | bool |   | If `true`, the tooltip is shown. | -| placement | enum: 'bottom-end', 'bottom-start', 'bottom', 'left-end', 'left-start', 'left', 'right-end', 'right-start', 'right', 'top-end', 'top-start', 'top'
| 'bottom' | Tooltip placement | -| PopperProps | object |   | Properties applied to the `Popper` element. | +| placement | enum: 'bottom-end', 'bottom-start', 'bottom', 'left-end', 'left-start', 'left', 'right-end', 'right-start', 'right', 'top-end', 'top-start', 'top'
| 'bottom' | Tooltip placement. | +| PopperProps | object |   | Properties applied to the [`Popper`](/api/popper) element. | | title * | node |   | Tooltip title. Zero-length titles string are never displayed. | +| TransitionComponent | union: string |
 func |
 object
| Grow | Transition component. | +| TransitionProps | object |   | Properties applied to the `Transition` element. | Any other properties supplied will be spread to the root element (native element). @@ -39,7 +41,6 @@ Any other properties supplied will be spread to the root element (native element You can override all the class names injected by Material-UI thanks to the `classes` property. This property accepts the following keys: - `popper` -- `open` - `tooltip` - `touch` - `tooltipPlacementLeft` diff --git a/pages/demos/tooltips.js b/pages/demos/tooltips.js index 636f882923d9e8..4b303049d5ab07 100644 --- a/pages/demos/tooltips.js +++ b/pages/demos/tooltips.js @@ -27,6 +27,34 @@ module.exports = require('fs') raw: preval` module.exports = require('fs') .readFileSync(require.resolve('docs/src/pages/demos/tooltips/ControlledTooltips'), 'utf8') +`, + }, + 'pages/demos/tooltips/TriggersTooltips.js': { + js: require('docs/src/pages/demos/tooltips/TriggersTooltips').default, + raw: preval` +module.exports = require('fs') + .readFileSync(require.resolve('docs/src/pages/demos/tooltips/TriggersTooltips'), 'utf8') +`, + }, + 'pages/demos/tooltips/TransitionsTooltips.js': { + js: require('docs/src/pages/demos/tooltips/TransitionsTooltips').default, + raw: preval` +module.exports = require('fs') + .readFileSync(require.resolve('docs/src/pages/demos/tooltips/TransitionsTooltips'), 'utf8') +`, + }, + 'pages/demos/tooltips/DelayTooltips.js': { + js: require('docs/src/pages/demos/tooltips/DelayTooltips').default, + raw: preval` +module.exports = require('fs') + .readFileSync(require.resolve('docs/src/pages/demos/tooltips/DelayTooltips'), 'utf8') +`, + }, + 'pages/demos/tooltips/CustomizedTooltips.js': { + js: require('docs/src/pages/demos/tooltips/CustomizedTooltips').default, + raw: preval` +module.exports = require('fs') + .readFileSync(require.resolve('docs/src/pages/demos/tooltips/CustomizedTooltips'), 'utf8') `, }, }} diff --git a/pages/lab/api/speed-dial-action.md b/pages/lab/api/speed-dial-action.md index f374d0d9746ad5..238777531425a9 100644 --- a/pages/lab/api/speed-dial-action.md +++ b/pages/lab/api/speed-dial-action.md @@ -15,7 +15,7 @@ title: SpeedDialAction API | Name | Type | Default | Description | |:-----|:-----|:--------|:------------| -| ButtonProps | object |   | Properties applied to the `Button` component. | +| ButtonProps | object |   | Properties applied to the [`Button`](/api/button) component. | | classes | object |   | Useful to extend the style applied to components. | | delay | number | 0 | Adds a transition delay, to allow a series of SpeedDialActions to be animated. | | icon * | node |   | The Icon to display in the SpeedDial Floating Action Button. | diff --git a/pages/lab/api/speed-dial.md b/pages/lab/api/speed-dial.md index 10e64fb9c54bb0..22a391d84346dd 100644 --- a/pages/lab/api/speed-dial.md +++ b/pages/lab/api/speed-dial.md @@ -16,7 +16,7 @@ title: SpeedDial API | Name | Type | Default | Description | |:-----|:-----|:--------|:------------| | ariaLabel * | string |   | The aria-label of the `Button` element. Also used to provide the `id` for the `SpeedDial` element and its children. | -| ButtonProps | object |   | Properties applied to the `Button` element. | +| ButtonProps | object |   | Properties applied to the [`Button`](/api/button) element. | | children * | node |   | SpeedDialActions to display when the SpeedDial is `open`. | | classes | object |   | Override or extend the styles applied to the component. See [CSS API](#css-api) below for more details. | | hidden | bool | false | If `true`, the SpeedDial will be hidden. | diff --git a/pages/utils/modals.js b/pages/utils/modal.js similarity index 58% rename from pages/utils/modals.js rename to pages/utils/modal.js index 79c9f0d62a528b..724f6ce67e6c6e 100644 --- a/pages/utils/modals.js +++ b/pages/utils/modal.js @@ -1,18 +1,18 @@ import React from 'react'; import withRoot from 'docs/src/modules/components/withRoot'; import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs'; -import markdown from 'docs/src/pages/utils/modals/modals.md'; +import markdown from 'docs/src/pages/utils/modal/modal.md'; function Page() { return ( + ); +} + +export default withRoot(Page); diff --git a/pages/utils/popovers.js b/pages/utils/popovers.js deleted file mode 100644 index 7b5a797e9a4a88..00000000000000 --- a/pages/utils/popovers.js +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react'; -import withRoot from 'docs/src/modules/components/withRoot'; -import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs'; -import markdown from 'docs/src/pages/utils/popovers/popovers.md'; - -function Page() { - return ( - - ); -} - -export default withRoot(Page); diff --git a/pages/utils/popper.js b/pages/utils/popper.js new file mode 100644 index 00000000000000..ca7d20eb532f6f --- /dev/null +++ b/pages/utils/popper.js @@ -0,0 +1,44 @@ +import React from 'react'; +import withRoot from 'docs/src/modules/components/withRoot'; +import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs'; +import markdown from 'docs/src/pages/utils/popper/popper.md'; + +function Page() { + return ( + + ); +} + +export default withRoot(Page); diff --git a/test/utils/createDOM.js b/test/utils/createDOM.js index bfd464e764a3b5..8052713e192fd2 100644 --- a/test/utils/createDOM.js +++ b/test/utils/createDOM.js @@ -7,7 +7,7 @@ const Node = require('jsdom/lib/jsdom/living/node-document-position'); const KEYS = ['HTMLElement', 'Performance']; function createDOM() { - const dom = new JSDOM(''); + const dom = new JSDOM('', { pretendToBeVisual: true }); global.window = dom.window; global.document = undefined; global.Node = Node; @@ -35,9 +35,6 @@ function createDOM() { KEYS.forEach(key => { global[key] = window[key]; }); - - global.requestAnimationFrame = setTimeout; - global.window.cancelAnimationFrame = () => {}; } module.exports = createDOM; diff --git a/yarn.lock b/yarn.lock index e0024290d5ce66..926af641ac461a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8124,7 +8124,7 @@ pngjs@^2.2.0: version "2.3.1" resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-2.3.1.tgz#11d1e12b9cb64d63e30c143a330f4c1f567da85f" -popper.js@^1.14.1: +popper.js@^1.0.0: version "1.14.3" resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.14.3.tgz#1438f98d046acf7b4d78cd502bf418ac64d4f095" @@ -8782,13 +8782,6 @@ react-number-format@^3.0.2: babel-runtime "^6.26.0" prop-types "^15.6.0" -react-popper@^0.10.0: - version "0.10.4" - resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-0.10.4.tgz#af2a415ea22291edd504678d7afda8a6ee3295aa" - dependencies: - popper.js "^1.14.1" - prop-types "^15.6.1" - react-reconciler@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/react-reconciler/-/react-reconciler-0.7.0.tgz#9614894103e5f138deeeb5eabaf3ee80eb1d026d"