diff --git a/docs/src/modules/components/AppDrawer.js b/docs/src/modules/components/AppDrawer.js index 8ac2fee42967b0..34f1c0942e3e65 100644 --- a/docs/src/modules/components/AppDrawer.js +++ b/docs/src/modules/components/AppDrawer.js @@ -8,6 +8,7 @@ import Toolbar from 'material-ui/Toolbar'; import Drawer from 'material-ui/Drawer'; import Typography from 'material-ui/Typography'; import Divider from 'material-ui/Divider'; +import Hidden from 'material-ui/Hidden'; import AppDrawerNavItem from 'docs/src/modules/components/AppDrawerNavItem'; import Link from 'docs/src/modules/components/Link'; import { pageToTitle } from 'docs/src/modules/utils/helpers'; @@ -78,57 +79,73 @@ function reduceChildRoutes(props, activePage, items, childPage, index) { return items; } +const GITHUB_RELEASE_BASE_URL = 'https://github.com/callemall/material-ui/releases/tag/'; + function AppDrawer(props, context) { - const { classes, className, docked, onRequestClose } = props; - const GITHUB_RELEASE_BASE_URL = 'https://github.com/callemall/material-ui/releases/tag/'; - let other = {}; - - if (!docked) { - other = { - keepMounted: true, - }; - } + const { classes, className, disablePermanent, mobileOpen, onRequestClose } = props; + + const drawer = ( +
+ + + + Material-UI + + + {process.env.MATERIAL_UI_VERSION + ? + {`v${process.env.MATERIAL_UI_VERSION}`} + + : null} + + + {renderNavItems(props, context.pages, context.activePage)} +
+ ); return ( - -
- - - - Material-UI - - - {process.env.MATERIAL_UI_VERSION - ? - {`v${process.env.MATERIAL_UI_VERSION}`} - - : null} - - - {renderNavItems(props, context.pages, context.activePage)} -
-
+
+ + + {drawer} + + + {disablePermanent + ? null + : + + {drawer} + + } +
); } AppDrawer.propTypes = { classes: PropTypes.object.isRequired, className: PropTypes.string, - docked: PropTypes.bool.isRequired, + disablePermanent: PropTypes.bool.isRequired, + mobileOpen: PropTypes.bool.isRequired, onRequestClose: PropTypes.func.isRequired, - open: PropTypes.bool.isRequired, }; AppDrawer.contextTypes = { diff --git a/docs/src/modules/components/AppFrame.js b/docs/src/modules/components/AppFrame.js index a4fa88684e5ba1..99ac545a24082e 100644 --- a/docs/src/modules/components/AppFrame.js +++ b/docs/src/modules/components/AppFrame.js @@ -11,7 +11,6 @@ import Typography from 'material-ui/Typography'; import AppBar from 'material-ui/AppBar'; import Toolbar from 'material-ui/Toolbar'; import IconButton from 'material-ui/IconButton'; -import withWidth, { isWidthUp } from 'material-ui/utils/withWidth'; import MenuIcon from 'material-ui-icons/Menu'; import LightbulbOutline from 'material-ui-icons/LightbulbOutline'; import Github from 'docs/src/modules/components/Github'; @@ -20,7 +19,14 @@ import AppSearch from 'docs/src/modules/components/AppSearch'; import { pageToTitle } from 'docs/src/modules/utils/helpers'; // Disaply a progress bar between route transitions -NProgress.configure({ showSpinner: false }); +NProgress.configure({ + template: ` +
+
+
+
+ `, +}); Router.onRouteChangeStart = () => { NProgress.start(); @@ -49,23 +55,45 @@ const styles = theme => ({ '#nprogress': { pointerEvents: 'none', '& .bar': { - background: '#000', position: 'fixed', + background: '#000', + borderRadius: 1, zIndex: theme.zIndex.tooltip, top: 0, left: 0, width: '100%', height: 2, }, - '& .peg': { - display: 'block', + '& dd, & dt': { position: 'absolute', + top: 0, + height: 2, + boxShadow: '#000 1px 0 6px 1px', + borderRadius: '100%', + animation: 'nprogress-pulse 2s ease-out 0s infinite', + }, + '& dd': { + opacity: 0.6, + width: 20, right: 0, - width: 100, - height: '100%', - boxShadow: '0 0 10px #000, 0 0 5px #000', - opacity: 1, - transform: 'rotate(3deg) translate(0px, -4px)', + clip: 'rect(-6px,22px,14px,10px)', + }, + '& dt': { + opacity: 0.6, + width: 180, + right: -80, + clip: 'rect(-6px,90px,14px,-6px)', + }, + }, + '@keyframes nprogress-pulse': { + '30%': { + opacity: 0.6, + }, + '60%': { + opacity: 0, + }, + to: { + opacity: 0.6, }, }, }, @@ -89,31 +117,34 @@ const styles = theme => ({ backgroundColor: 'transparent', boxShadow: 'none', }, - [theme.breakpoints.up('lg')]: { - drawer: { - width: 250, - }, - appBarShift: { + appBarShift: { + [theme.breakpoints.up('lg')]: { width: 'calc(100% - 250px)', }, - navIconHide: { + }, + drawer: { + [theme.breakpoints.up('lg')]: { + width: 250, + }, + }, + navIconHide: { + [theme.breakpoints.up('lg')]: { display: 'none', }, }, }); class AppFrame extends React.Component { - static defaultProps: $FlowFixMeProps; state = { - drawerOpen: false, + mobileOpen: false, }; handleDrawerClose = () => { - this.setState({ drawerOpen: false }); + this.setState({ mobileOpen: false }); }; handleDrawerToggle = () => { - this.setState({ drawerOpen: !this.state.drawerOpen }); + this.setState({ mobileOpen: !this.state.mobileOpen }); }; handleToggleShade = () => { @@ -121,17 +152,17 @@ class AppFrame extends React.Component { }; render() { - const { children, classes, width } = this.props; + const { children, classes } = this.props; const title = this.context.activePage.title !== false ? pageToTitle(this.context.activePage) : null; - let drawerDocked = isWidthUp('lg', width); + let disablePermanent = false; let navIconClassName = ''; let appBarClassName = classes.appBar; if (title === null) { // home route, don't shift app bar or dock drawer - drawerDocked = false; + disablePermanent = true; appBarClassName += ` ${classes.appBarHome}`; } else { navIconClassName += ` ${classes.navIconHide}`; @@ -176,9 +207,9 @@ class AppFrame extends React.Component { {children} @@ -190,7 +221,6 @@ AppFrame.propTypes = { children: PropTypes.node.isRequired, classes: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, - width: PropTypes.string.isRequired, }; AppFrame.contextTypes = { @@ -203,6 +233,5 @@ export default compose( withStyles(styles, { name: 'AppFrame', }), - withWidth(), connect(), )(AppFrame); diff --git a/docs/src/modules/components/AppWrapper.js b/docs/src/modules/components/AppWrapper.js index f21873c388339a..919bd697a48637 100644 --- a/docs/src/modules/components/AppWrapper.js +++ b/docs/src/modules/components/AppWrapper.js @@ -26,7 +26,6 @@ if (process.browser && !global.__INSERTION_POINT__) { } class AppWrapper extends React.Component { - static defaultProps: $FlowFixMeProps; componentWillMount() { this.styleContext = getContext(); } diff --git a/docs/src/modules/components/Demo.js b/docs/src/modules/components/Demo.js index b5b0649c0b6bdc..56012e99ecd7e3 100644 --- a/docs/src/modules/components/Demo.js +++ b/docs/src/modules/components/Demo.js @@ -55,7 +55,6 @@ const styles = theme => ({ }); class Demo extends React.Component { - static defaultProps: $FlowFixMeProps; state = { codeOpen: false, }; diff --git a/docs/src/modules/components/Link.js b/docs/src/modules/components/Link.js index 3afcca35858b66..b8a6aa58a93a71 100644 --- a/docs/src/modules/components/Link.js +++ b/docs/src/modules/components/Link.js @@ -31,7 +31,6 @@ const styles = theme => ({ }); class OnClick extends React.Component { - static defaultProps: $FlowFixMeProps; handleClick = event => { if (this.props.onClick) { this.props.onClick(event); diff --git a/docs/src/modules/components/MarkdownDocs.js b/docs/src/modules/components/MarkdownDocs.js index fcd6151a8d022d..de70c0b305ecc8 100644 --- a/docs/src/modules/components/MarkdownDocs.js +++ b/docs/src/modules/components/MarkdownDocs.js @@ -49,7 +49,7 @@ function MarkdownDocs(props, context) { - {getTitle(markdown)} + {`${getTitle(markdown)} - Material-UI`}
diff --git a/docs/src/pages/demos/app-bar/ButtonAppBar.js b/docs/src/pages/demos/app-bar/ButtonAppBar.js index 2d5839bdeb662c..2d8cf07526d0e5 100644 --- a/docs/src/pages/demos/app-bar/ButtonAppBar.js +++ b/docs/src/pages/demos/app-bar/ButtonAppBar.js @@ -18,6 +18,10 @@ const styles = { flex: { flex: 1, }, + menuButton: { + marginLeft: 12, + marginRight: 20, + }, }; function ButtonAppBar(props) { @@ -25,8 +29,8 @@ function ButtonAppBar(props) { return (
- - + + diff --git a/docs/src/pages/demos/drawers/MiniDrawer.js b/docs/src/pages/demos/drawers/MiniDrawer.js new file mode 100644 index 00000000000000..81776d90178dd7 --- /dev/null +++ b/docs/src/pages/demos/drawers/MiniDrawer.js @@ -0,0 +1,173 @@ +/* eslint-disable flowtype/require-valid-file-annotation */ + +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from 'material-ui/styles'; +import classNames from 'classnames'; +import Drawer from 'material-ui/Drawer'; +import AppBar from 'material-ui/AppBar'; +import Toolbar from 'material-ui/Toolbar'; +import List from 'material-ui/List'; +import Typography from 'material-ui/Typography'; +import Divider from 'material-ui/Divider'; +import IconButton from 'material-ui/IconButton'; +import MenuIcon from 'material-ui-icons/Menu'; +import ChevronLeftIcon from 'material-ui-icons/ChevronLeft'; +import { mailFolderListItems, otherMailFolderListItems } from './tileData'; + +const drawerWidth = 240; + +const styles = theme => ({ + root: { + width: '100%', + height: 430, + marginTop: theme.spacing.unit * 3, + zIndex: 1, + overflow: 'hidden', + }, + appFrame: { + position: 'relative', + display: 'flex', + width: '100%', + height: '100%', + }, + appBar: { + position: 'absolute', + zIndex: theme.zIndex.navDrawer + 1, + transition: theme.transitions.create(['width', 'margin'], { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen, + }), + }, + appBarShift: { + marginLeft: drawerWidth, + width: `calc(100% - ${drawerWidth}px)`, + transition: theme.transitions.create(['width', 'margin'], { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.enteringScreen, + }), + }, + menuButton: { + marginLeft: 12, + marginRight: 36, + }, + hide: { + display: 'none', + }, + drawerPaper: { + position: 'relative', + height: 'auto', + width: drawerWidth, + transition: theme.transitions.create('width', { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.enteringScreen, + }), + }, + drawerPaperClose: { + width: 60, + transition: theme.transitions.create('width', { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen, + }), + }, + drawerInner: { + // Make the items inside not wrap when transitioning: + width: drawerWidth, + }, + drawerHeader: { + display: 'flex', + alignItems: 'center', + justifyContent: 'flex-end', + padding: '0 8px', + height: 56, + [theme.breakpoints.up('sm')]: { + height: 64, + }, + }, + content: { + width: '100%', + flexGrow: 1, + backgroundColor: theme.palette.background.default, + padding: 24, + height: 'calc(100% - 56px)', + marginTop: 56, + [theme.breakpoints.up('sm')]: { + height: 'calc(100% - 64px)', + marginTop: 64, + }, + }, +}); + +class MiniDrawer extends React.Component { + state = { + open: false, + }; + + handleDrawerOpen = () => { + this.setState({ open: true }); + }; + + handleDrawerClose = () => { + this.setState({ open: false }); + }; + + render() { + const classes = this.props.classes; + + return ( +
+
+ + + + + + + Mini variant drawer + + + + +
+
+ + + +
+ + + {mailFolderListItems} + + + + {otherMailFolderListItems} + +
+
+
+ + {'You think water moves fast? You should see ice.'} + +
+
+
+ ); + } +} + +MiniDrawer.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(MiniDrawer); diff --git a/docs/src/pages/demos/drawers/PermanentDrawer.js b/docs/src/pages/demos/drawers/PermanentDrawer.js new file mode 100644 index 00000000000000..08ec8a290ebcab --- /dev/null +++ b/docs/src/pages/demos/drawers/PermanentDrawer.js @@ -0,0 +1,103 @@ +// @flow weak + +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from 'material-ui/styles'; +import Drawer from 'material-ui/Drawer'; +import AppBar from 'material-ui/AppBar'; +import Toolbar from 'material-ui/Toolbar'; +import List from 'material-ui/List'; +import Typography from 'material-ui/Typography'; +import Divider from 'material-ui/Divider'; +import { mailFolderListItems, otherMailFolderListItems } from './tileData'; + +const drawerWidth = 240; + +const styles = theme => ({ + root: { + width: '100%', + height: 430, + marginTop: theme.spacing.unit * 3, + zIndex: 1, + overflow: 'hidden', + }, + appFrame: { + position: 'relative', + display: 'flex', + width: '100%', + height: '100%', + }, + appBar: { + position: 'absolute', + width: `calc(100% - ${drawerWidth}px)`, + marginLeft: drawerWidth, + order: 1, + }, + drawerPaper: { + position: 'relative', + height: 'auto', + width: drawerWidth, + }, + drawerHeader: { + height: 56, + [theme.breakpoints.up('sm')]: { + height: 64, + }, + }, + content: { + backgroundColor: theme.palette.background.default, + width: '100%', + padding: theme.spacing.unit * 3, + height: 'calc(100% - 56px)', + marginTop: 56, + [theme.breakpoints.up('sm')]: { + height: 'calc(100% - 64px)', + marginTop: 64, + }, + }, +}); + +function PermanentDrawer(props) { + const { classes } = props; + + return ( +
+
+ + + + Permanent drawer + + + + +
+ + + {mailFolderListItems} + + + + {otherMailFolderListItems} + + +
+ + {'You think water moves fast? You should see ice.'} + +
+
+
+ ); +} + +PermanentDrawer.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(PermanentDrawer); diff --git a/docs/src/pages/demos/drawers/PersistentDrawer.js b/docs/src/pages/demos/drawers/PersistentDrawer.js new file mode 100644 index 00000000000000..9387169b047395 --- /dev/null +++ b/docs/src/pages/demos/drawers/PersistentDrawer.js @@ -0,0 +1,171 @@ +/* eslint-disable flowtype/require-valid-file-annotation */ + +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from 'material-ui/styles'; +import classNames from 'classnames'; +import Drawer from 'material-ui/Drawer'; +import AppBar from 'material-ui/AppBar'; +import Toolbar from 'material-ui/Toolbar'; +import List from 'material-ui/List'; +import Typography from 'material-ui/Typography'; +import Divider from 'material-ui/Divider'; +import IconButton from 'material-ui/IconButton'; +import MenuIcon from 'material-ui-icons/Menu'; +import ChevronLeftIcon from 'material-ui-icons/ChevronLeft'; +import { mailFolderListItems, otherMailFolderListItems } from './tileData'; + +const drawerWidth = 240; + +const styles = theme => ({ + root: { + width: '100%', + height: 430, + marginTop: theme.spacing.unit * 3, + zIndex: 1, + overflow: 'hidden', + }, + appFrame: { + position: 'relative', + display: 'flex', + width: '100%', + height: '100%', + }, + appBar: { + position: 'absolute', + transition: theme.transitions.create(['margin', 'width'], { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen, + }), + }, + appBarShift: { + marginLeft: drawerWidth, + width: `calc(100% - ${drawerWidth}px)`, + transition: theme.transitions.create(['margin', 'width'], { + easing: theme.transitions.easing.easeOut, + duration: theme.transitions.duration.enteringScreen, + }), + }, + menuButton: { + marginLeft: 12, + marginRight: 20, + }, + hide: { + display: 'none', + }, + drawerPaper: { + position: 'relative', + height: 'auto', + width: drawerWidth, + }, + drawerHeader: { + display: 'flex', + alignItems: 'center', + justifyContent: 'flex-end', + padding: '0 8px', + height: 56, + [theme.breakpoints.up('sm')]: { + height: 64, + }, + }, + content: { + width: '100%', + marginLeft: -drawerWidth, + flexGrow: 1, + backgroundColor: theme.palette.background.default, + padding: theme.spacing.unit * 3, + transition: theme.transitions.create('margin', { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen, + }), + height: 'calc(100% - 56px)', + marginTop: 56, + [theme.breakpoints.up('sm')]: { + content: { + height: 'calc(100% - 64px)', + marginTop: 64, + }, + }, + }, + contentShift: { + marginLeft: 0, + transition: theme.transitions.create('margin', { + easing: theme.transitions.easing.easeOut, + duration: theme.transitions.duration.enteringScreen, + }), + }, +}); + +class PersistentDrawer extends React.Component { + state = { + open: false, + }; + + handleDrawerOpen = () => { + this.setState({ open: true }); + }; + + handleDrawerClose = () => { + this.setState({ open: false }); + }; + + render() { + const { classes } = this.props; + + return ( +
+
+ + + + + + + Persistent drawer + + + + +
+
+ + + +
+ + + {mailFolderListItems} + + + + {otherMailFolderListItems} + +
+
+
+ + {'You think water moves fast? You should see ice.'} + +
+
+
+ ); + } +} + +PersistentDrawer.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(PersistentDrawer); diff --git a/docs/src/pages/demos/drawers/TemporaryDrawer.js b/docs/src/pages/demos/drawers/TemporaryDrawer.js new file mode 100644 index 00000000000000..6199363149a8f5 --- /dev/null +++ b/docs/src/pages/demos/drawers/TemporaryDrawer.js @@ -0,0 +1,144 @@ +/* eslint-disable flowtype/require-valid-file-annotation */ + +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from 'material-ui/styles'; +import Drawer from 'material-ui/Drawer'; +import Button from 'material-ui/Button'; +import List from 'material-ui/List'; +import Divider from 'material-ui/Divider'; +import { mailFolderListItems, otherMailFolderListItems } from './tileData'; + +const styles = { + list: { + width: 250, + flex: 'initial', + }, + listFull: { + width: 'auto', + flex: 'initial', + }, +}; + +class TemporaryDrawer extends React.Component { + state = { + open: { + top: false, + left: false, + bottom: false, + right: false, + }, + }; + + toggleDrawer = (side, open) => { + const drawerState = {}; + drawerState[side] = open; + this.setState({ open: drawerState }); + }; + + handleTopOpen = () => { + this.toggleDrawer('top', true); + }; + + handleTopClose = () => { + this.toggleDrawer('top', false); + }; + + handleLeftOpen = () => { + this.toggleDrawer('left', true); + }; + + handleLeftClose = () => { + this.toggleDrawer('left', false); + }; + + handleBottomOpen = () => { + this.toggleDrawer('bottom', true); + }; + + handleBottomClose = () => { + this.toggleDrawer('bottom', false); + }; + + handleRightOpen = () => { + this.toggleDrawer('right', true); + }; + + handleRightClose = () => { + this.toggleDrawer('right', false); + }; + + render() { + const classes = this.props.classes; + + const sideList = ( +
+ + {mailFolderListItems} + + + + {otherMailFolderListItems} + +
+ ); + + const fullList = ( +
+ + {mailFolderListItems} + + + + {otherMailFolderListItems} + +
+ ); + + return ( +
+ + + + + + {sideList} + + + {fullList} + + + {fullList} + + + {sideList} + +
+ ); + } +} + +TemporaryDrawer.propTypes = { + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(TemporaryDrawer); diff --git a/docs/src/pages/demos/drawers/UndockedDrawer.js b/docs/src/pages/demos/drawers/UndockedDrawer.js deleted file mode 100644 index 2ec18674354d6a..00000000000000 --- a/docs/src/pages/demos/drawers/UndockedDrawer.js +++ /dev/null @@ -1,179 +0,0 @@ -/* eslint-disable flowtype/require-valid-file-annotation */ - -import React from 'react'; -import PropTypes from 'prop-types'; -import { withStyles } from 'material-ui/styles'; -import Drawer from 'material-ui/Drawer'; -import Button from 'material-ui/Button'; -import List, { ListItem, ListItemIcon, ListItemText } from 'material-ui/List'; -import Divider from 'material-ui/Divider'; -import InboxIcon from 'material-ui-icons/Inbox'; -import DraftsIcon from 'material-ui-icons/Drafts'; -import StarIcon from 'material-ui-icons/Star'; -import SendIcon from 'material-ui-icons/Send'; -import MailIcon from 'material-ui-icons/Mail'; -import DeleteIcon from 'material-ui-icons/Delete'; -import ReportIcon from 'material-ui-icons/Report'; - -const styles = { - list: { - width: 250, - flex: 'initial', - }, - listFull: { - width: 'auto', - flex: 'initial', - }, -}; - -class UndockedDrawer extends React.Component { - state = { - open: { - top: false, - left: false, - bottom: false, - right: false, - }, - }; - - toggleDrawer = (side, open) => { - const drawerState = {}; - drawerState[side] = open; - this.setState({ open: drawerState }); - }; - - handleTopOpen = () => this.toggleDrawer('top', true); - handleTopClose = () => this.toggleDrawer('top', false); - handleLeftOpen = () => this.toggleDrawer('left', true); - handleLeftClose = () => this.toggleDrawer('left', false); - handleBottomOpen = () => this.toggleDrawer('bottom', true); - handleBottomClose = () => this.toggleDrawer('bottom', false); - handleRightOpen = () => this.toggleDrawer('right', true); - handleRightClose = () => this.toggleDrawer('right', false); - - render() { - const classes = this.props.classes; - - const mailFolderListItems = ( -
- - - - - - - - - - - - - - - - - - - - - - - - -
- ); - - const otherMailFolderListItems = ( -
- - - - - - - - - - - - - - - - - - -
- ); - - const sideList = ( -
- - {mailFolderListItems} - - - - {otherMailFolderListItems} - -
- ); - - const fullList = ( -
- - {mailFolderListItems} - - - - {otherMailFolderListItems} - -
- ); - - return ( -
- - - - - - {sideList} - - - {fullList} - - - {fullList} - - - {sideList} - -
- ); - } -} - -UndockedDrawer.propTypes = { - classes: PropTypes.object.isRequired, -}; - -export default withStyles(styles)(UndockedDrawer); diff --git a/docs/src/pages/demos/drawers/drawers.md b/docs/src/pages/demos/drawers/drawers.md index 51fddbee104849..68219550310b30 100644 --- a/docs/src/pages/demos/drawers/drawers.md +++ b/docs/src/pages/demos/drawers/drawers.md @@ -6,8 +6,37 @@ components: Drawer The [Drawer](https://material.io/guidelines/patterns/navigation-drawer.html) slides in from the side. It is a common pattern found in Google apps and follows the keylines and metrics for lists. -## Undocked example +## Temporary drawer -An undocked controlled `Drawer` with custom width. The Drawer can be cancelled by clicking the overlay or pressing the Esc key. It closes when an item is selected, handled by controlling the `open` prop. +Temporary navigation drawers can toggle open or closed. Closed by default, the drawer opens temporarily above all other content until a section is selected. -{{demo='pages/demos/drawers/UndockedDrawer.js'}} +The Drawer can be cancelled by clicking the overlay or pressing the Esc key. +It closes when an item is selected, handled by controlling the `open` prop. + +{{demo='pages/demos/drawers/TemporaryDrawer.js'}} + +## Permanent drawer + +Permanent navigation drawers are always visible and pinned to the left edge, at the same elevation as the content or background. They cannot be closed. + +Permanent navigation drawers are the recommended default for desktop. + +{{demo='pages/demos/drawers/PermanentDrawer.js'}} + +## Persistent drawer + +Persistent navigation drawers can toggle open or closed. The drawer sits on the same surface elevation as the content. It is closed by default and opens by selecting the menu icon, and stays open until closed by the user. The state of the drawer is remembered from action to action and session to session. + +When the drawer is outside of the page grid and opens, the drawer forces other content to change size and adapt to the smaller viewport. + +Persistent navigation drawers are acceptable for all sizes larger than mobile. They are not recommended for apps with multiple levels of hierarchy that require using an up arrow for navigation. + +{{demo='pages/demos/drawers/PersistentDrawer.js'}} + +## Mini variant drawer + +In this variation, the persistent navigation drawer changes its width. Its resting state is as a mini-drawer at the same elevation as the content, clipped by the app bar. When expanded, it appears as the standard persistent navigation drawer. + +The mini variant is recommended for apps sections that need quick selection access alongside content. + +{{demo='pages/demos/drawers/MiniDrawer.js'}} diff --git a/docs/src/pages/demos/drawers/tileData.js b/docs/src/pages/demos/drawers/tileData.js new file mode 100644 index 00000000000000..92032c9ea61563 --- /dev/null +++ b/docs/src/pages/demos/drawers/tileData.js @@ -0,0 +1,64 @@ +// @flow +// This file is shared across the demos. + +import React from 'react'; +import { ListItem, ListItemIcon, ListItemText } from 'material-ui/List'; +import InboxIcon from 'material-ui-icons/MoveToInbox'; +import DraftsIcon from 'material-ui-icons/Drafts'; +import StarIcon from 'material-ui-icons/Star'; +import SendIcon from 'material-ui-icons/Send'; +import MailIcon from 'material-ui-icons/Mail'; +import DeleteIcon from 'material-ui-icons/Delete'; +import ReportIcon from 'material-ui-icons/Report'; + +export const mailFolderListItems = ( +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+); + +export const otherMailFolderListItems = ( +
+ + + + + + + + + + + + + + + + + + +
+); diff --git a/next.config.js b/next.config.js index 4892f7aa391b9d..33c55f5050725e 100644 --- a/next.config.js +++ b/next.config.js @@ -5,13 +5,14 @@ const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); const { findPages } = require('./docs/src/modules/utils/find'); const ENABLE_STATS = false; +process.env.MATERIAL_UI_VERSION = pkg.version, module.exports = { webpack: config => { const plugins = config.plugins.concat([ new webpack.DefinePlugin({ 'process.env': { - MATERIAL_UI_VERSION: JSON.stringify(pkg.version), + MATERIAL_UI_VERSION: JSON.stringify(process.env.MATERIAL_UI_VERSION), }, }), ]); diff --git a/package.json b/package.json index 255004eb05364f..3fc06c2a0e1b51 100644 --- a/package.json +++ b/package.json @@ -175,7 +175,7 @@ "size-limit": [ { "path": "build/index.js", - "limit": "122 KB" + "limit": "123 KB" } ], "nyc": { diff --git a/pages/demos/drawers.js b/pages/demos/drawers.js index efc715f51522c2..e0e84654bb80be 100644 --- a/pages/demos/drawers.js +++ b/pages/demos/drawers.js @@ -10,11 +10,32 @@ function Page() { ({ type DefaultProps = { anchor: 'left', - docked: boolean, classes: Object, + elevation: number, enterTransitionDuration: number, leaveTransitionDuration: number, open: boolean, - elevation: number, }; export type Props = { /** - * Side which will the drawer will appear from. + * Side from which the drawer will appear. */ anchor?: 'left' | 'top' | 'right' | 'bottom', /** * The contents of the drawer. */ - children?: Node, + children: Node, /** * Useful to extend the style applied to components. */ @@ -100,22 +99,21 @@ export type Props = { */ className?: string, /** - * If `true`, the drawer will dock itself - * and will no longer slide in with an overlay. + * Customizes duration of enter animation (ms) */ - docked?: boolean, + enterTransitionDuration?: number, /** * The elevation of the drawer. */ elevation?: number, - /** - * Customizes duration of enter animation (ms) - */ - enterTransitionDuration?: number, /** * Customizes duration of leave animation (ms) */ leaveTransitionDuration?: number, + /** + * Properties applied to the `Modal` element. + */ + ModalProps?: Object, /** * Callback fired when the component requests to be closed. * @@ -134,6 +132,10 @@ export type Props = { * @ignore */ theme: Object, + /** + * The type of drawer. + */ + type: 'permanent' | 'persistent' | 'temporary', }; type AllProps = DefaultProps & Props; @@ -144,14 +146,15 @@ type State = { class Drawer extends React.Component { props: AllProps; + static defaultProps = { anchor: 'left', - docked: false, classes: {}, + elevation: 16, enterTransitionDuration: duration.enteringScreen, leaveTransitionDuration: duration.leavingScreen, open: false, - elevation: 16, + type: 'temporary', // Mobile first. }; state = { @@ -173,13 +176,15 @@ class Drawer extends React.Component { children, classes, className, - docked, + elevation, enterTransitionDuration, leaveTransitionDuration, - elevation, + ModalProps, + onRequestClose, open, SlideProps, theme, + type, ...other } = this.props; @@ -190,6 +195,24 @@ class Drawer extends React.Component { } const drawer = ( + + {children} + + ); + + if (type === 'permanent') { + return ( +
+ {drawer} +
+ ); + } + + const slidingDrawer = ( { transitionAppear={!this.state.firstMount} {...SlideProps} > - - {children} - + {drawer} ); - if (docked) { - const { onRequestClose, ...otherDocked } = other; - + if (type === 'persistent') { return ( -
- {drawer} +
+ {slidingDrawer}
); } + // type === temporary return ( - {drawer} + {slidingDrawer} ); } diff --git a/src/Drawer/Drawer.spec.js b/src/Drawer/Drawer.spec.js index 1e79984b7391fc..03dc6a0eefafe3 100644 --- a/src/Drawer/Drawer.spec.js +++ b/src/Drawer/Drawer.spec.js @@ -14,155 +14,208 @@ describe('', () => { before(() => { shallow = createShallow({ dive: true }); - classes = getClasses(); + classes = getClasses( + +
+ , + ); }); - it('should render a Modal', () => { - const wrapper = shallow(); - assert.strictEqual(wrapper.name(), 'withStyles(Modal)'); - }); + describe('prop: type=temporary', () => { + it('should render a Modal', () => { + const wrapper = shallow( + +
+ , + ); + assert.strictEqual(wrapper.name(), 'withStyles(Modal)'); + }); - it('should render Slide > Paper inside the Modal', () => { - const wrapper = shallow(); + it('should render Slide > Paper inside the Modal', () => { + const wrapper = shallow( + +
+ , + ); - const slide = wrapper.childAt(0); - assert.strictEqual( - slide.length === 1 && slide.is(Slide), - true, - 'immediate wrapper child should be Slide', - ); + const slide = wrapper.childAt(0); + assert.strictEqual( + slide.length === 1 && slide.is(Slide), + true, + 'immediate wrapper child should be Slide', + ); - const paper = slide.childAt(0); - assert.strictEqual(paper.length === 1 && paper.name(), 'withStyles(Paper)'); + const paper = slide.childAt(0); + assert.strictEqual(paper.length === 1 && paper.name(), 'withStyles(Paper)'); - assert.strictEqual(paper.hasClass(classes.paper), true, 'should have the paper class'); - }); + assert.strictEqual(paper.hasClass(classes.paper), true, 'should have the paper class'); + }); + + describe('enterTransitionDuration property', () => { + const enterDuration = 854; + const leaveDuration = 2967; + + it('should be passed to Slide', () => { + const wrapper = shallow( + +
+ , + ); + assert.strictEqual(wrapper.find(Slide).prop('enterTransitionDuration'), enterDuration); + }); - describe('enterTransitionDuration property', () => { - const enterDuration = 854; - const leaveDuration = 2967; + it("should be passed to to Modal's backdropTransitionDuration when open=true", () => { + const wrapper = shallow( + +
+ , + ); + assert.strictEqual(wrapper.find(Modal).prop('backdropTransitionDuration'), enterDuration); + }); + }); + + describe('leaveTransitionDuration property', () => { + const enterDuration = 6577; + const leaveDuration = 1889; + + it('should be passed to Slide', () => { + const wrapper = shallow( + +
+ , + ); + assert.strictEqual(wrapper.find(Slide).props().leaveTransitionDuration, leaveDuration); + }); - it('should be passed to Slide', () => { - const wrapper = shallow(); - assert.strictEqual(wrapper.find(Slide).prop('enterTransitionDuration'), enterDuration); + it("should be passed to to Modal's backdropTransitionDuration when open=false", () => { + const wrapper = shallow( + +
+ , + ); + assert.strictEqual(wrapper.find(Modal).props().backdropTransitionDuration, leaveDuration); + }); }); - it("should be passed to to Modal's backdropTransitionDuration when open=true", () => { + it("should override Modal's backdropTransitionDuration from property when specified", () => { + const testDuration = 335; const wrapper = shallow( - , + +
+ , ); - assert.strictEqual(wrapper.find(Modal).prop('backdropTransitionDuration'), enterDuration); + assert.strictEqual(wrapper.find(Modal).props().backdropTransitionDuration, testDuration); }); - }); - describe('leaveTransitionDuration property', () => { - const enterDuration = 6577; - const leaveDuration = 1889; - - it('should be passed to Slide', () => { - const wrapper = shallow(); - assert.strictEqual(wrapper.find(Slide).props().leaveTransitionDuration, leaveDuration); + it('should set the Paper className', () => { + const wrapper = shallow( + +

Hello

+
, + ); + const paper = wrapper.find(Paper); + assert.strictEqual(paper.hasClass(classes.paper), true, 'should have the paper class'); + assert.strictEqual(paper.hasClass('woofDrawer'), true, 'should have the woofDrawer class'); }); - it("should be passed to to Modal's backdropTransitionDuration when open=false", () => { + it('should be closed by default', () => { const wrapper = shallow( - , + +

Hello

+
, ); - assert.strictEqual(wrapper.find(Modal).props().backdropTransitionDuration, leaveDuration); + + const modal = wrapper; + const slide = modal.find(Slide); + + assert.strictEqual(modal.prop('show'), false, 'should not show the modal'); + assert.strictEqual(slide.prop('in'), false, 'should not transition in'); }); - }); - it("should override Modal's backdropTransitionDuration from property when specified", () => { - const testDuration = 335; - const wrapper = shallow(); - assert.strictEqual(wrapper.find(Modal).props().backdropTransitionDuration, testDuration); - }); + describe('opening and closing', () => { + let wrapper; - it('should set the Paper className', () => { - const wrapper = shallow( - -

Hello

-
, - ); - const paper = wrapper.find(Paper); - assert.strictEqual(paper.hasClass(classes.paper), true, 'should have the paper class'); - assert.strictEqual(paper.hasClass('woofDrawer'), true, 'should have the woofDrawer class'); - }); + before(() => { + wrapper = shallow( + +

Hello

+
, + ); + }); - it('should be closed by default', () => { - const wrapper = shallow( - -

Hello

-
, - ); + it('should start closed', () => { + assert.strictEqual(wrapper.props().show, false, 'should not show the modal'); + assert.strictEqual(wrapper.find(Slide).prop('in'), false, 'should not transition in'); + }); - const modal = wrapper; - const slide = modal.find(Slide); + it('should open', () => { + wrapper.setProps({ open: true }); + assert.strictEqual(wrapper.props().show, true, 'should show the modal'); + assert.strictEqual(wrapper.find(Slide).prop('in'), true, 'should transition in'); + }); - assert.strictEqual(modal.prop('show'), false, 'should not show the modal'); - assert.strictEqual(slide.prop('in'), false, 'should not transition in'); + it('should close', () => { + wrapper.setProps({ open: false }); + assert.strictEqual(wrapper.props().show, false, 'should not show the modal'); + assert.strictEqual(wrapper.find(Slide).prop('in'), false, 'should not transition in'); + }); + }); }); - describe('opening and closing', () => { + describe('prop: type=persistent', () => { let wrapper; before(() => { wrapper = shallow( - +

Hello

, ); }); - it('should start closed', () => { - assert.strictEqual(wrapper.props().show, false, 'should not show the modal'); - assert.strictEqual(wrapper.find(Slide).prop('in'), false, 'should not transition in'); + it('should render a div instead of a Modal when persistent', () => { + assert.strictEqual(wrapper.name(), 'div'); + assert.strictEqual(wrapper.hasClass(classes.docked), true, 'should have the docked class'); }); - it('should open', () => { - wrapper.setProps({ open: true }); - assert.strictEqual(wrapper.props().show, true, 'should show the modal'); - assert.strictEqual(wrapper.find(Slide).prop('in'), true, 'should transition in'); - }); + it('should render Slide > Paper inside the div', () => { + const slide = wrapper.childAt(0); + assert.strictEqual(slide.length, 1); + assert.strictEqual(slide.name(), 'withTheme(Slide)'); - it('should close', () => { - wrapper.setProps({ open: false }); - assert.strictEqual(wrapper.props().show, false, 'should not show the modal'); - assert.strictEqual(wrapper.find(Slide).prop('in'), false, 'should not transition in'); + const paper = slide.childAt(0); + assert.strictEqual(paper.length === 1 && paper.name(), 'withStyles(Paper)'); }); }); - describe('docked', () => { + describe('prop: type=permanent', () => { let wrapper; before(() => { wrapper = shallow( - +

Hello

, ); }); - it('should render a div instead of a Modal when docked', () => { + it('should render a div instead of a Modal when permanent', () => { assert.strictEqual(wrapper.name(), 'div'); assert.strictEqual(wrapper.hasClass(classes.docked), true, 'should have the docked class'); }); - it('should render Slide > Paper inside the div', () => { - const slide = wrapper.childAt(0); - assert.strictEqual( - slide.length === 1 && slide.is(Slide), - true, - 'immediate wrapper child should be Slide', - ); + it('should render div > Paper inside the div', () => { + const slide = wrapper; + assert.strictEqual(slide.length, 1); + assert.strictEqual(slide.name(), 'div'); const paper = slide.childAt(0); assert.strictEqual(paper.length === 1 && paper.name(), 'withStyles(Paper)'); @@ -173,7 +226,11 @@ describe('', () => { let wrapper; before(() => { - wrapper = shallow(); + wrapper = shallow( + +
+ , + ); }); it('should return the opposing slide direction', () => { @@ -203,7 +260,11 @@ describe('', () => { let wrapper; before(() => { - wrapper = shallow(); + wrapper = shallow( + +
+ , + ); wrapper.instance().props.theme.dir = 'rtl'; }); diff --git a/src/Grid/Grid.js b/src/Grid/Grid.js index 9abbfcee3b0286..33f105f0cfc730 100644 --- a/src/Grid/Grid.js +++ b/src/Grid/Grid.js @@ -1,16 +1,14 @@ // @flow -/** - * A grid component using the following libs as inspiration. - * - * For the implementation: - * - http://v4-alpha.getbootstrap.com/layout/flexbox-grid/ - * - https://github.com/kristoferjoseph/flexboxgrid/blob/master/src/css/flexboxgrid.css - * - https://github.com/roylee0704/react-flexbox-grid - * - https://material.angularjs.org/latest/layout/introduction - * - * Follow this flexbox Guide to better understand the underlying model: - * - https://css-tricks.com/snippets/css/a-guide-to-flexbox/ - */ +// A grid component using the following libs as inspiration. +// +// For the implementation: +// - http://v4-alpha.getbootstrap.com/layout/flexbox-grid/ +// - https://github.com/kristoferjoseph/flexboxgrid/blob/master/src/css/flexboxgrid.css +// - https://github.com/roylee0704/react-flexbox-grid +// - https://material.angularjs.org/latest/layout/introduction +// +// Follow this flexbox Guide to better understand the underlying model: +// - https://css-tricks.com/snippets/css/a-guide-to-flexbox/ import React from 'react'; import type { ComponentType, Node } from 'react'; diff --git a/src/Hidden/Hidden.js b/src/Hidden/Hidden.js index 485a246fc875c9..0cddbd106fd799 100644 --- a/src/Hidden/Hidden.js +++ b/src/Hidden/Hidden.js @@ -3,6 +3,7 @@ import React from 'react'; import type { Node } from 'react'; import HiddenJs from './HiddenJs'; +import HiddenCss from './HiddenCss'; import type { Breakpoint } from '../styles/breakpoints'; export type Props = { @@ -75,7 +76,7 @@ function Hidden(props: Props) { return ; } - throw new Error(' is not yet implemented'); + return ; } Hidden.defaultProps = { diff --git a/src/Hidden/Hidden.spec.js b/src/Hidden/Hidden.spec.js index 8de52e33726398..d9019f922d96d1 100644 --- a/src/Hidden/Hidden.spec.js +++ b/src/Hidden/Hidden.spec.js @@ -5,6 +5,7 @@ import { assert } from 'chai'; import { createShallow } from '../test-utils'; import Hidden from './Hidden'; import HiddenJs from './HiddenJs'; +import HiddenCss from './HiddenCss'; describe('', () => { let shallow; @@ -23,14 +24,13 @@ describe('', () => { assert.strictEqual(wrapper.find(HiddenJs).length, 1); }); - it('should use change the implementation', () => { - assert.throws(() => { - shallow( - - {'Hello'} - , - ); - }, 'is not yet implemented'); + it('should change the implementation', () => { + const wrapper = shallow( + + {'Hello'} + , + ); + assert.strictEqual(wrapper.find(HiddenCss).length, 1); }); }); }); diff --git a/src/Hidden/HiddenCss.js b/src/Hidden/HiddenCss.js new file mode 100644 index 00000000000000..6717d354073d14 --- /dev/null +++ b/src/Hidden/HiddenCss.js @@ -0,0 +1,101 @@ +/* eslint-disable flowtype/require-valid-file-annotation */ + +import React from 'react'; +import warning from 'warning'; +import classNames from 'classnames'; +import { keys as breakpoints } from '../styles/breakpoints'; +import { capitalizeFirstLetter } from '../utils/helpers'; +import withStyles from '../styles/withStyles'; +import type { HiddenProps } from './types'; + +type DefaultProps = { + classes: Object, +}; + +export type Props = HiddenProps & { + /** + * Useful to extend the style applied to components. + */ + classes?: Object, +}; + +function generateStyles(theme) { + const hidden = { + display: 'none', + }; + + return theme.breakpoints.keys.reduce((styles, key) => { + styles[`only${capitalizeFirstLetter(key)}`] = { + [theme.breakpoints.only(key)]: hidden, + }; + styles[`${key}Up`] = { + [theme.breakpoints.up(key)]: hidden, + }; + styles[`${key}Down`] = { + [theme.breakpoints.down(key)]: hidden, + }; + + return styles; + }, {}); +} + +const styles = (theme: Object) => generateStyles(theme); + +type AllProps = DefaultProps & Props; + +/** + * @ignore - internal component. + */ +function HiddenCss(props: AllProps) { + const { + children, + classes, + only, + xsUp, + smUp, + mdUp, + lgUp, + xlUp, + xsDown, + smDown, + mdDown, + lgDown, + xlDown, + ...other + } = props; + + warning( + Object.keys(other).length === 0 || + (Object.keys(other).length === 1 && other.hasOwnProperty('ref')), + `Material-UI: unsupported properties received ${JSON.stringify(other)} by \`\`.`, + ); + + const className = []; + + for (let i = 0; i < breakpoints.length; i += 1) { + const breakpoint = breakpoints[i]; + const breakpointUp = props[`${breakpoint}Up`]; + const breakpointDown = props[`${breakpoint}Down`]; + + if (breakpointUp) { + className.push(classes[`${breakpoint}Up`]); + } + if (breakpointDown) { + className.push(classes[`${breakpoint}Down`]); + } + } + + if (only) { + className.push(classes[`only${capitalizeFirstLetter(only)}`]); + } + + if (!React.isValidElement(children)) { + return null; + } + + return React.cloneElement(children, { + className: classNames(children.props.className, className.join(' ')), + }); +} + +export default withStyles(styles, { name: 'MuiHiddenCss' })(HiddenCss); diff --git a/src/Hidden/HiddenCss.spec.js b/src/Hidden/HiddenCss.spec.js new file mode 100644 index 00000000000000..b0aaf3322ff6f5 --- /dev/null +++ b/src/Hidden/HiddenCss.spec.js @@ -0,0 +1,55 @@ +// @flow + +import React from 'react'; +import { assert } from 'chai'; +import { createShallow, getClasses } from '../test-utils'; +import HiddenCss from './HiddenCss'; + +describe('', () => { + let shallow; + let classes; + + before(() => { + shallow = createShallow(); + classes = getClasses( + +
+ , + ); + }); + + describe('the generated class names', () => { + it('should be ok with only', () => { + const wrapper = shallow( + +
+ , + ); + assert.strictEqual(wrapper.props().className, `foo ${classes.onlySm}`); + }); + + it('should be ok with mdDown', () => { + const wrapper = shallow( + +
+ , + ); + assert.strictEqual(wrapper.props().className, `foo ${classes.mdDown}`); + }); + + it('should be ok with mdUp', () => { + const wrapper = shallow( + +
+ , + ); + assert.strictEqual(wrapper.props().className, `foo ${classes.mdUp}`); + }); + }); + + describe('prop: children', () => { + it('should work when empty', () => { + shallow(); + }); + }); +}); diff --git a/src/Hidden/HiddenJs.js b/src/Hidden/HiddenJs.js index 1a43ba283045a4..db2871f77e542e 100644 --- a/src/Hidden/HiddenJs.js +++ b/src/Hidden/HiddenJs.js @@ -34,6 +34,11 @@ function HiddenJs(props: Props) { ...other } = props; + warning( + Object.keys(other).length === 0, + `Material-UI: unsupported properties received ${JSON.stringify(other)} by \`\`.`, + ); + let visible = true; // `only` check is faster to get out sooner if used. @@ -72,11 +77,6 @@ function HiddenJs(props: Props) { return null; } - warning( - Object.keys(other).length === 0, - `Material-UI: unsupported properties received ${JSON.stringify(other)}`, - ); - return children; } diff --git a/src/Hidden/HiddenJs.spec.js b/src/Hidden/HiddenJs.spec.js index 22d91825919981..de2fd9dc789b6b 100644 --- a/src/Hidden/HiddenJs.spec.js +++ b/src/Hidden/HiddenJs.spec.js @@ -42,19 +42,11 @@ describe('', () => { const props = { width, [prop]: breakpoint }; // children - let wrapper = shallow( - - foo - , - ); + let wrapper = shallow(foo); assert.isNull(wrapper.type(), 'should render null'); // element - wrapper = shallow( - foo} {...props}> - foo - , - ); + wrapper = shallow(foo); assert.isNull(wrapper.type(), 'should render null'); }); }); diff --git a/src/Hidden/types.js b/src/Hidden/types.js index 8c5410ecc083a1..77eccf1c9152bd 100644 --- a/src/Hidden/types.js +++ b/src/Hidden/types.js @@ -1,12 +1,13 @@ // @flow +import type { Element } from 'react'; import type { Breakpoint } from '../styles/breakpoints'; export type HiddenProps = { /** * The content of the component. */ - children?: React$Element, + children?: Element<*>, /** * @ignore */ diff --git a/src/internal/Modal.js b/src/internal/Modal.js index 435d5b1b389f5d..5a22643e1fe7ff 100644 --- a/src/internal/Modal.js +++ b/src/internal/Modal.js @@ -77,7 +77,7 @@ export type Props = { className?: string, /** * Always keep the children in the DOM. - * That property can be useful in SEO situation or + * This property can be useful in SEO situation or * when you want to maximize the responsiveness of the Modal. */ keepMounted?: boolean, diff --git a/test/regressions/index.js b/test/regressions/index.js index a796f8c6dcc461..a5cdb4574d9b75 100644 --- a/test/regressions/index.js +++ b/test/regressions/index.js @@ -27,7 +27,6 @@ const blacklistSuite = [ // Needs interaction 'docs-demos-dialogs', - 'docs-demos-drawers', 'docs-demos-menus', // Useless