From cc507f1c0f2ae90b0fc632c241ebdba6f0ef40ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20J=C3=B8rgensen?= Date: Tue, 1 Sep 2020 10:39:07 +0200 Subject: [PATCH 01/14] Fix issue #22368 --- .../BottomNavigationAction.js | 40 +++++++++++++++++++ .../BottomNavigationAction.test.js | 39 +++++++++++++++++- 2 files changed, 78 insertions(+), 1 deletion(-) diff --git a/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.js b/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.js index a1956c844d451c..1c5ab1d9f3bcf9 100644 --- a/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.js +++ b/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.js @@ -68,7 +68,35 @@ const BottomNavigationAction = React.forwardRef(function BottomNavigationAction( ...other } = props; + const touchStartPos = React.useRef(); + const touchTimer = React.useRef(); + + const handleTouchStart = React.useCallback(event => { + const { clientX, clientY } = event.touches[0]; + + touchStartPos.current = { + clientX, + clientY, + }; + }, [touchStartPos]) + + const handleTouchEnd = React.useCallback(event => { + const target = event.target; + const { clientX, clientY } = event.changedTouches[0]; + + if ( + Math.abs(clientX - touchStartPos.current.clientX) < 10 && + Math.abs(clientY - touchStartPos.current.clientY) < 10 + ) { + touchTimer.current = setTimeout(() => { + target.dispatchEvent(new Event('click', { bubbles: true })); + }, 10); + } + }, [touchTimer, touchStartPos]) + const handleChange = (event) => { + clearTimeout(touchTimer.current); + if (onChange) { onChange(event, value); } @@ -78,6 +106,16 @@ const BottomNavigationAction = React.forwardRef(function BottomNavigationAction( } }; + React.useImperativeHandle( + ref, + () => ({ + handleTouchStart, + handleTouchEnd, + innerRef: ref + }), + [handleTouchStart, handleTouchEnd, ref], + ); + return ( diff --git a/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.test.js b/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.test.js index 647b9a3dc91ce6..5228407003e38e 100644 --- a/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.test.js +++ b/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.test.js @@ -8,6 +8,7 @@ import { createClientRender, within, } from 'test/utils'; +import { act } from 'react-test-renderer'; import ButtonBase from '../ButtonBase'; import BottomNavigationAction from './BottomNavigationAction'; @@ -24,7 +25,7 @@ describe('', () => { classes, inheritComponent: ButtonBase, mount, - refInstanceof: window.HTMLButtonElement, + refInstanceof: Object, skip: ['componentProp'], })); @@ -80,4 +81,40 @@ describe('', () => { expect(handleClick.callCount).to.equal(1); }); }); + + it('should fire onClick on touch tap', done => { + const handleClick = spy(); + const bottomNavigationActionRef = React.createRef(); + + const { container } = render(); + const instance = bottomNavigationActionRef.current; + + act(() => { + instance.handleTouchStart({ + touches: [ + { + clientX: 42, + clientY: 42, + }, + ], + }); + }); + + act(() => { + instance.handleTouchEnd({ + target: container.firstChild, + changedTouches: [ + { + clientX: 42, + clientY: 42, + }, + ], + }); + }); + + setTimeout(() => { + expect(handleClick.callCount).to.equal(1) + done() + }, 15) + }); }); From e163abd3240d5fd6b915394b75d42fdfe190e30b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20J=C3=B8rgensen?= Date: Tue, 1 Sep 2020 16:40:23 +0200 Subject: [PATCH 02/14] Testing for issue #22368 --- .../BottomNavigationAction.js | 36 +++-- .../BottomNavigationAction.test.js | 147 ++++++++++++++++-- 2 files changed, 154 insertions(+), 29 deletions(-) diff --git a/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.js b/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.js index 1c5ab1d9f3bcf9..49170aba0de3ca 100644 --- a/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.js +++ b/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.js @@ -60,6 +60,8 @@ const BottomNavigationAction = React.forwardRef(function BottomNavigationAction( icon, label, onChange, + onTouchStart, + onTouchEnd, onClick, // eslint-disable-next-line react/prop-types -- private, always overridden by BottomNavigation selected, @@ -71,16 +73,24 @@ const BottomNavigationAction = React.forwardRef(function BottomNavigationAction( const touchStartPos = React.useRef(); const touchTimer = React.useRef(); - const handleTouchStart = React.useCallback(event => { + React.useEffect(() => { + return () => clearTimeout(touchTimer.current); + }, [touchTimer]); + + function handleTouchStart(event) { + if (onTouchStart) onTouchStart(event); + const { clientX, clientY } = event.touches[0]; touchStartPos.current = { clientX, clientY, }; - }, [touchStartPos]) + } + + function handleTouchEnd(event) { + if (onTouchEnd) onTouchEnd(event); - const handleTouchEnd = React.useCallback(event => { const target = event.target; const { clientX, clientY } = event.changedTouches[0]; @@ -92,7 +102,7 @@ const BottomNavigationAction = React.forwardRef(function BottomNavigationAction( target.dispatchEvent(new Event('click', { bubbles: true })); }, 10); } - }, [touchTimer, touchStartPos]) + } const handleChange = (event) => { clearTimeout(touchTimer.current); @@ -106,16 +116,6 @@ const BottomNavigationAction = React.forwardRef(function BottomNavigationAction( } }; - React.useImperativeHandle( - ref, - () => ({ - handleTouchStart, - handleTouchEnd, - innerRef: ref - }), - [handleTouchStart, handleTouchEnd, ref], - ); - return ( ', () => { }); }); - it('should fire onClick on touch tap', done => { + it('should fire onClick on touch tap', (done) => { const handleClick = spy(); - const bottomNavigationActionRef = React.createRef(); - const { container } = render(); - const instance = bottomNavigationActionRef.current; + // If disableTouchRipple is not applied, errors are thrown due to "code that updates TouchRiple is not wrapped in act()" + const { container } = render( + , + ); act(() => { - instance.handleTouchStart({ + fireEvent.touchStart(container.firstChild, { touches: [ - { + new Touch({ clientX: 42, clientY: 42, - }, + }), ], }); }); act(() => { - instance.handleTouchEnd({ - target: container.firstChild, + fireEvent.touchEnd(container.firstChild, { changedTouches: [ - { + new Touch({ clientX: 42, clientY: 42, - }, + }), ], }); }); setTimeout(() => { - expect(handleClick.callCount).to.equal(1) - done() - }, 15) + expect(handleClick.callCount).to.equal(1); + done(); + }, 15); + }); + + it('should not fire onClick twice on touch tap', (done) => { + const handleClick = spy(); + + // If disableTouchRipple is not applied, errors are thrown due to "code that updates TouchRiple is not wrapped in act()" + const { getByRole, container } = render( + , + ); + + act(() => { + fireEvent.touchStart(container.firstChild, { + touches: [ + new Touch({ + clientX: 42, + clientY: 42, + }), + ], + }); + }); + + act(() => { + fireEvent.touchEnd(container.firstChild, { + changedTouches: [ + new Touch({ + clientX: 42, + clientY: 42, + }), + ], + }); + }); + + act(() => { + getByRole('button').click(); + }); + + setTimeout(() => { + expect(handleClick.callCount).to.equal(1); + done(); + }, 15); + }); + + it('should not fire onClick if swiping', (done) => { + const handleClick = spy(); + + // If disableTouchRipple is not applied, errors are thrown due to "code that updates TouchRiple is not wrapped in act()" + const { container } = render( + , + ); + + act(() => { + fireEvent.touchStart(container.firstChild, { + touches: [ + new Touch({ + clientX: 42, + clientY: 42, + }), + ], + }); + }); + + act(() => { + fireEvent.touchEnd(container.firstChild, { + changedTouches: [ + new Touch({ + clientX: 84, + clientY: 84, + }), + ], + }); + }); + + setTimeout(() => { + expect(handleClick.callCount).to.equal(0); + done(); + }, 15); + }); + + it('should forward onTouchStart and onTouchEnd events', () => { + const handleTouchStart = spy(); + const handleTouchEnd = spy(); + + // If disableTouchRipple is not applied, errors are thrown due to "code that updates TouchRiple is not wrapped in act()" + const { container } = render( + , + ); + + act(() => { + fireEvent.touchStart(container.firstChild, { + touches: [ + new Touch({ + clientX: 42, + clientY: 42, + }), + ], + }); + }); + + expect(handleTouchStart.callCount).to.be.equals(1); + + act(() => { + fireEvent.touchEnd(container.firstChild, { + changedTouches: [ + new Touch({ + clientX: 84, + clientY: 84, + }), + ], + }); + }); + + expect(handleTouchEnd.callCount).to.be.equals(1); }); }); From 1a3d9d95c8ac09950dcd789c296b37f222baf89e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20J=C3=B8rgensen?= Date: Mon, 7 Sep 2020 20:20:30 +0200 Subject: [PATCH 03/14] Add demo to BottomNavigation page --- .../FixedBottomNavigation.js | 120 +++++++++++++++++ .../FixedBottomNavigation.tsx | 126 ++++++++++++++++++ .../bottom-navigation/bottom-navigation.md | 6 + 3 files changed, 252 insertions(+) create mode 100644 docs/src/pages/components/bottom-navigation/FixedBottomNavigation.js create mode 100644 docs/src/pages/components/bottom-navigation/FixedBottomNavigation.tsx diff --git a/docs/src/pages/components/bottom-navigation/FixedBottomNavigation.js b/docs/src/pages/components/bottom-navigation/FixedBottomNavigation.js new file mode 100644 index 00000000000000..52e13c46cd31b0 --- /dev/null +++ b/docs/src/pages/components/bottom-navigation/FixedBottomNavigation.js @@ -0,0 +1,120 @@ +import React from 'react'; +import { makeStyles } from '@material-ui/core/styles'; +import BottomNavigation from '@material-ui/core/BottomNavigation'; +import BottomNavigationAction from '@material-ui/core/BottomNavigationAction'; +import RestoreIcon from '@material-ui/icons/Restore'; +import FavoriteIcon from '@material-ui/icons/Favorite'; +import ArchiveIcon from '@material-ui/icons/Archive'; +import Typography from '@material-ui/core/Typography'; +import List from '@material-ui/core/List'; +import ListItem from '@material-ui/core/ListItem'; +import ListItemAvatar from '@material-ui/core/ListItemAvatar'; +import ListItemText from '@material-ui/core/ListItemText'; +import Avatar from '@material-ui/core/Avatar'; + +const useStyles = makeStyles({ + bottomNav: { + position: 'fixed', + bottom: 0, + left: 0, + right: 0, + }, +}); + +function getRandomInt(max) { + return Math.floor(Math.random() * Math.floor(max)); +} + +const messageExamples = [ + { + primary: 'Brunch this week?', + secondary: + "I'll be in the neighbourhood this week. Let's grab a bite to eat", + person: '/static/images/avatar/5.jpg', + }, + { + primary: 'Birthday Gift', + secondary: `Do you have a suggestion for a good present for John on his work + anniversary. I am really confused & would love your thoughts on it.`, + person: '/static/images/avatar/1.jpg', + }, + { + primary: 'Recipe to try', + secondary: + 'I am try out this new BBQ recipe, I think this might be amazing', + person: '/static/images/avatar/2.jpg', + }, + { + primary: 'Yes!', + secondary: 'I have the tickets to the ReactConf for this year.', + person: '/static/images/avatar/3.jpg', + }, + { + primary: "Doctor's Appointment", + secondary: + 'My appointment for the doctor was rescheduled for next Saturday.', + person: '/static/images/avatar/4.jpg', + }, + { + primary: 'Discussion', + secondary: `Menus that are generated by the bottom app bar (such as a bottom + navigation drawer or overflow menu) open as bottom sheets at a higher elevation + than the bar.`, + person: '/static/images/avatar/5.jpg', + }, + { + primary: 'Summer BBQ', + secondary: `Who wants to have a cookout this weekend? I just got some furniture + for my backyard and would love to fire up the grill.`, + person: '/static/images/avatar/1.jpg', + }, +]; + +function refreshMessages() { + return Array.from(new Array(100)).map( + () => messageExamples[getRandomInt(messageExamples.length)], + ); +} + +export default function FixedBottomNavigation() { + const classes = useStyles(); + const [value, setValue] = React.useState(0); + const [messages, setMessages] = React.useState(() => refreshMessages()); + + React.useEffect(() => { + setMessages(refreshMessages()); + }, [value, setMessages]); + + return ( +
+
+ + Threads + + + {messages.map(({ primary, secondary, person }, index) => ( + + + + + + + ))} + +
+ + { + setValue(newValue); + }} + showLabels + className={classes.bottomNav} + > + } /> + } /> + } /> + +
+ ); +} diff --git a/docs/src/pages/components/bottom-navigation/FixedBottomNavigation.tsx b/docs/src/pages/components/bottom-navigation/FixedBottomNavigation.tsx new file mode 100644 index 00000000000000..4978e310d9c7c7 --- /dev/null +++ b/docs/src/pages/components/bottom-navigation/FixedBottomNavigation.tsx @@ -0,0 +1,126 @@ +import React from 'react'; +import { makeStyles } from '@material-ui/core/styles'; +import BottomNavigation from '@material-ui/core/BottomNavigation'; +import BottomNavigationAction from '@material-ui/core/BottomNavigationAction'; +import RestoreIcon from '@material-ui/icons/Restore'; +import FavoriteIcon from '@material-ui/icons/Favorite'; +import ArchiveIcon from '@material-ui/icons/Archive'; +import Typography from '@material-ui/core/Typography'; +import List from '@material-ui/core/List'; +import ListItem from '@material-ui/core/ListItem'; +import ListItemAvatar from '@material-ui/core/ListItemAvatar'; +import ListItemText from '@material-ui/core/ListItemText'; +import Avatar from '@material-ui/core/Avatar'; + +const useStyles = makeStyles({ + bottomNav: { + position: 'fixed', + bottom: 0, + left: 0, + right: 0, + }, +}); + +function getRandomInt(max: int) { + return Math.floor(Math.random() * Math.floor(max)); +} + +interface MessageExample { + primary: string; + secondary: string; + person: string; +} + +const messageExamples: MessageExample[] = [ + { + primary: 'Brunch this week?', + secondary: + "I'll be in the neighbourhood this week. Let's grab a bite to eat", + person: '/static/images/avatar/5.jpg', + }, + { + primary: 'Birthday Gift', + secondary: `Do you have a suggestion for a good present for John on his work + anniversary. I am really confused & would love your thoughts on it.`, + person: '/static/images/avatar/1.jpg', + }, + { + primary: 'Recipe to try', + secondary: + 'I am try out this new BBQ recipe, I think this might be amazing', + person: '/static/images/avatar/2.jpg', + }, + { + primary: 'Yes!', + secondary: 'I have the tickets to the ReactConf for this year.', + person: '/static/images/avatar/3.jpg', + }, + { + primary: "Doctor's Appointment", + secondary: + 'My appointment for the doctor was rescheduled for next Saturday.', + person: '/static/images/avatar/4.jpg', + }, + { + primary: 'Discussion', + secondary: `Menus that are generated by the bottom app bar (such as a bottom + navigation drawer or overflow menu) open as bottom sheets at a higher elevation + than the bar.`, + person: '/static/images/avatar/5.jpg', + }, + { + primary: 'Summer BBQ', + secondary: `Who wants to have a cookout this weekend? I just got some furniture + for my backyard and would love to fire up the grill.`, + person: '/static/images/avatar/1.jpg', + }, +]; + +function refreshMessages(): MessageExample[] { + return Array.from(new Array(100)).map( + () => messageExamples[getRandomInt(messageExamples.length)], + ); +} + +export default function FixedBottomNavigation() { + const classes = useStyles(); + const [value, setValue] = React.useState(0); + const [messages, setMessages] = React.useState(() => refreshMessages()); + + React.useEffect(() => { + setMessages(refreshMessages()); + }, [value, setMessages]); + + return ( +
+
+ + Threads + + + {messages.map(({ primary, secondary, person }, index) => ( + + + + + + + ))} + +
+ + { + setValue(newValue); + }} + showLabels + className={classes.bottomNav} + > + } /> + } /> + } /> + +
+ ); +} diff --git a/docs/src/pages/components/bottom-navigation/bottom-navigation.md b/docs/src/pages/components/bottom-navigation/bottom-navigation.md index 7a8c39e8bf44ee..a99f720201267f 100644 --- a/docs/src/pages/components/bottom-navigation/bottom-navigation.md +++ b/docs/src/pages/components/bottom-navigation/bottom-navigation.md @@ -24,3 +24,9 @@ When there are only **three** actions, display both icons and text labels at all If there are **four** or **five** actions, display inactive views as icons only. {{"demo": "pages/components/bottom-navigation/LabelBottomNavigation.js", "bg": true}} + +## Fixed Bottom Navigation + +Keep Bottom Navigation fixed to the bottom, no matter the amount of content on-screen. + +{{"demo": "pages/components/bottom-navigation/FixedBottomNavigation.js", "bg": true, "iframe": true, "maxWidth": 600}} \ No newline at end of file From 180a741444b3d8a9a485fe491ff08acedf562401 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20J=C3=B8rgensen?= Date: Mon, 7 Sep 2020 20:29:20 +0200 Subject: [PATCH 04/14] Keep using HTMLButtonElement as BottomNavigationAction test refInstanceOf --- .../src/BottomNavigationAction/BottomNavigationAction.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.test.js b/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.test.js index c32fedb05de815..37223e56c3f4e5 100644 --- a/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.test.js +++ b/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.test.js @@ -26,7 +26,7 @@ describe('', () => { classes, inheritComponent: ButtonBase, mount, - refInstanceof: Object, + refInstanceof: window.HTMLButtonElement, skip: ['componentProp'], })); From b1568ae58002542d562587a4ff8114761fadff96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20J=C3=B8rgensen?= Date: Mon, 7 Sep 2020 20:34:43 +0200 Subject: [PATCH 05/14] Demo formatting --- .../components/bottom-navigation/FixedBottomNavigation.tsx | 2 +- .../src/pages/components/bottom-navigation/bottom-navigation.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/pages/components/bottom-navigation/FixedBottomNavigation.tsx b/docs/src/pages/components/bottom-navigation/FixedBottomNavigation.tsx index 4978e310d9c7c7..4200e7d83da3d1 100644 --- a/docs/src/pages/components/bottom-navigation/FixedBottomNavigation.tsx +++ b/docs/src/pages/components/bottom-navigation/FixedBottomNavigation.tsx @@ -21,7 +21,7 @@ const useStyles = makeStyles({ }, }); -function getRandomInt(max: int) { +function getRandomInt(max: number) { return Math.floor(Math.random() * Math.floor(max)); } diff --git a/docs/src/pages/components/bottom-navigation/bottom-navigation.md b/docs/src/pages/components/bottom-navigation/bottom-navigation.md index a99f720201267f..fccf489b0eefb1 100644 --- a/docs/src/pages/components/bottom-navigation/bottom-navigation.md +++ b/docs/src/pages/components/bottom-navigation/bottom-navigation.md @@ -29,4 +29,4 @@ If there are **four** or **five** actions, display inactive views as icons only. Keep Bottom Navigation fixed to the bottom, no matter the amount of content on-screen. -{{"demo": "pages/components/bottom-navigation/FixedBottomNavigation.js", "bg": true, "iframe": true, "maxWidth": 600}} \ No newline at end of file +{{"demo": "pages/components/bottom-navigation/FixedBottomNavigation.js", "bg": true, "iframe": true, "maxWidth": 600}} From e8c23d5bee1b8dcaadd23d3e0640e42e11a8ad7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20J=C3=B8rgensen?= Date: Mon, 7 Sep 2020 20:55:56 +0200 Subject: [PATCH 06/14] Fix browser tests --- .../BottomNavigationAction.test.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.test.js b/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.test.js index 37223e56c3f4e5..1cdf48a87c5b3d 100644 --- a/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.test.js +++ b/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.test.js @@ -95,6 +95,8 @@ describe('', () => { fireEvent.touchStart(container.firstChild, { touches: [ new Touch({ + identifier: 1, + target: container, clientX: 42, clientY: 42, }), @@ -106,6 +108,8 @@ describe('', () => { fireEvent.touchEnd(container.firstChild, { changedTouches: [ new Touch({ + identifier: 1, + target: container, clientX: 42, clientY: 42, }), @@ -131,6 +135,8 @@ describe('', () => { fireEvent.touchStart(container.firstChild, { touches: [ new Touch({ + identifier: 1, + target: container, clientX: 42, clientY: 42, }), @@ -142,6 +148,8 @@ describe('', () => { fireEvent.touchEnd(container.firstChild, { changedTouches: [ new Touch({ + identifier: 1, + target: container, clientX: 42, clientY: 42, }), @@ -171,6 +179,8 @@ describe('', () => { fireEvent.touchStart(container.firstChild, { touches: [ new Touch({ + identifier: 1, + target: container, clientX: 42, clientY: 42, }), @@ -182,6 +192,8 @@ describe('', () => { fireEvent.touchEnd(container.firstChild, { changedTouches: [ new Touch({ + identifier: 1, + target: container, clientX: 84, clientY: 84, }), @@ -212,6 +224,8 @@ describe('', () => { fireEvent.touchStart(container.firstChild, { touches: [ new Touch({ + identifier: 1, + target: container, clientX: 42, clientY: 42, }), @@ -225,6 +239,8 @@ describe('', () => { fireEvent.touchEnd(container.firstChild, { changedTouches: [ new Touch({ + identifier: 1, + target: container, clientX: 84, clientY: 84, }), From 6fc0f859923106191d422e2536d5a235513c3631 Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Tue, 8 Sep 2020 00:31:22 +0200 Subject: [PATCH 07/14] polish documentation --- .../FixedBottomNavigation.js | 64 +++---- .../FixedBottomNavigation.tsx | 64 +++---- .../bottom-navigation/bottom-navigation.md | 4 +- .../BottomNavigationAction.js | 9 +- .../BottomNavigationAction.test.js | 173 ++++++++---------- 5 files changed, 152 insertions(+), 162 deletions(-) diff --git a/docs/src/pages/components/bottom-navigation/FixedBottomNavigation.js b/docs/src/pages/components/bottom-navigation/FixedBottomNavigation.js index 52e13c46cd31b0..0002d2aec37ff9 100644 --- a/docs/src/pages/components/bottom-navigation/FixedBottomNavigation.js +++ b/docs/src/pages/components/bottom-navigation/FixedBottomNavigation.js @@ -1,11 +1,12 @@ import React from 'react'; import { makeStyles } from '@material-ui/core/styles'; +import CssBaseline from '@material-ui/core/CssBaseline'; import BottomNavigation from '@material-ui/core/BottomNavigation'; import BottomNavigationAction from '@material-ui/core/BottomNavigationAction'; import RestoreIcon from '@material-ui/icons/Restore'; import FavoriteIcon from '@material-ui/icons/Favorite'; import ArchiveIcon from '@material-ui/icons/Archive'; -import Typography from '@material-ui/core/Typography'; +import Paper from '@material-ui/core/Paper'; import List from '@material-ui/core/List'; import ListItem from '@material-ui/core/ListItem'; import ListItemAvatar from '@material-ui/core/ListItemAvatar'; @@ -13,6 +14,9 @@ import ListItemText from '@material-ui/core/ListItemText'; import Avatar from '@material-ui/core/Avatar'; const useStyles = makeStyles({ + root: { + paddingBottom: 56, + }, bottomNav: { position: 'fixed', bottom: 0, @@ -71,7 +75,7 @@ const messageExamples = [ ]; function refreshMessages() { - return Array.from(new Array(100)).map( + return Array.from(new Array(50)).map( () => messageExamples[getRandomInt(messageExamples.length)], ); } @@ -79,42 +83,40 @@ function refreshMessages() { export default function FixedBottomNavigation() { const classes = useStyles(); const [value, setValue] = React.useState(0); + const ref = React.useRef(null); const [messages, setMessages] = React.useState(() => refreshMessages()); React.useEffect(() => { + ref.current.ownerDocument.body.scrollTop = 0; setMessages(refreshMessages()); }, [value, setMessages]); return ( -
-
- - Threads - - - {messages.map(({ primary, secondary, person }, index) => ( - - - - - - - ))} - -
- - { - setValue(newValue); - }} - showLabels - className={classes.bottomNav} - > - } /> - } /> - } /> - +
+ + + {messages.map(({ primary, secondary, person }, index) => ( + + + + + + + ))} + + + { + setValue(newValue); + }} + > + } /> + } /> + } /> + +
); } diff --git a/docs/src/pages/components/bottom-navigation/FixedBottomNavigation.tsx b/docs/src/pages/components/bottom-navigation/FixedBottomNavigation.tsx index 4200e7d83da3d1..989cd800ffee1b 100644 --- a/docs/src/pages/components/bottom-navigation/FixedBottomNavigation.tsx +++ b/docs/src/pages/components/bottom-navigation/FixedBottomNavigation.tsx @@ -1,11 +1,12 @@ import React from 'react'; import { makeStyles } from '@material-ui/core/styles'; +import CssBaseline from '@material-ui/core/CssBaseline'; import BottomNavigation from '@material-ui/core/BottomNavigation'; import BottomNavigationAction from '@material-ui/core/BottomNavigationAction'; import RestoreIcon from '@material-ui/icons/Restore'; import FavoriteIcon from '@material-ui/icons/Favorite'; import ArchiveIcon from '@material-ui/icons/Archive'; -import Typography from '@material-ui/core/Typography'; +import Paper from '@material-ui/core/Paper'; import List from '@material-ui/core/List'; import ListItem from '@material-ui/core/ListItem'; import ListItemAvatar from '@material-ui/core/ListItemAvatar'; @@ -13,6 +14,9 @@ import ListItemText from '@material-ui/core/ListItemText'; import Avatar from '@material-ui/core/Avatar'; const useStyles = makeStyles({ + root: { + paddingBottom: 56, + }, bottomNav: { position: 'fixed', bottom: 0, @@ -77,7 +81,7 @@ const messageExamples: MessageExample[] = [ ]; function refreshMessages(): MessageExample[] { - return Array.from(new Array(100)).map( + return Array.from(new Array(50)).map( () => messageExamples[getRandomInt(messageExamples.length)], ); } @@ -85,42 +89,40 @@ function refreshMessages(): MessageExample[] { export default function FixedBottomNavigation() { const classes = useStyles(); const [value, setValue] = React.useState(0); + const ref = React.useRef(null); const [messages, setMessages] = React.useState(() => refreshMessages()); React.useEffect(() => { + (ref.current as HTMLDivElement).ownerDocument.body.scrollTop = 0; setMessages(refreshMessages()); }, [value, setMessages]); return ( -
-
- - Threads - - - {messages.map(({ primary, secondary, person }, index) => ( - - - - - - - ))} - -
- - { - setValue(newValue); - }} - showLabels - className={classes.bottomNav} - > - } /> - } /> - } /> - +
+ + + {messages.map(({ primary, secondary, person }, index) => ( + + + + + + + ))} + + + { + setValue(newValue); + }} + > + } /> + } /> + } /> + +
); } diff --git a/docs/src/pages/components/bottom-navigation/bottom-navigation.md b/docs/src/pages/components/bottom-navigation/bottom-navigation.md index fccf489b0eefb1..aadbc0e2c46bc5 100644 --- a/docs/src/pages/components/bottom-navigation/bottom-navigation.md +++ b/docs/src/pages/components/bottom-navigation/bottom-navigation.md @@ -25,8 +25,8 @@ If there are **four** or **five** actions, display inactive views as icons only. {{"demo": "pages/components/bottom-navigation/LabelBottomNavigation.js", "bg": true}} -## Fixed Bottom Navigation +## Fixed positioning -Keep Bottom Navigation fixed to the bottom, no matter the amount of content on-screen. +This demo keeps bottom navigation fixed to the bottom, no matter the amount of content on-screen. {{"demo": "pages/components/bottom-navigation/FixedBottomNavigation.js", "bg": true, "iframe": true, "maxWidth": 600}} diff --git a/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.js b/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.js index 49170aba0de3ca..3bc1d346597eed 100644 --- a/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.js +++ b/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.js @@ -77,7 +77,7 @@ const BottomNavigationAction = React.forwardRef(function BottomNavigationAction( return () => clearTimeout(touchTimer.current); }, [touchTimer]); - function handleTouchStart(event) { + const handleTouchStart = (event) => { if (onTouchStart) onTouchStart(event); const { clientX, clientY } = event.touches[0]; @@ -88,7 +88,7 @@ const BottomNavigationAction = React.forwardRef(function BottomNavigationAction( }; } - function handleTouchEnd(event) { + const handleTouchEnd = (event) => { if (onTouchEnd) onTouchEnd(event); const target = event.target; @@ -99,6 +99,11 @@ const BottomNavigationAction = React.forwardRef(function BottomNavigationAction( Math.abs(clientY - touchStartPos.current.clientY) < 10 ) { touchTimer.current = setTimeout(() => { + // Simulate the native tap behavior on mobile. + // On the web, a tap won't trigger a click if a container is scrolling. + // + // Note that the synthetic behavior won't trigger a native nor + // it will trigger a click at all on iOS. target.dispatchEvent(new Event('click', { bubbles: true })); }, 10); } diff --git a/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.test.js b/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.test.js index 1cdf48a87c5b3d..14df4802324e03 100644 --- a/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.test.js +++ b/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.test.js @@ -8,7 +8,6 @@ import { createClientRender, within, fireEvent, - act, } from 'test/utils'; import ButtonBase from '../ButtonBase'; import BottomNavigationAction from './BottomNavigationAction'; @@ -86,35 +85,31 @@ describe('', () => { it('should fire onClick on touch tap', (done) => { const handleClick = spy(); - // If disableTouchRipple is not applied, errors are thrown due to "code that updates TouchRiple is not wrapped in act()" + // Need disableTouchRipple to avoid ripple missing act (async setState after touchEnd) const { container } = render( , ); - act(() => { - fireEvent.touchStart(container.firstChild, { - touches: [ - new Touch({ - identifier: 1, - target: container, - clientX: 42, - clientY: 42, - }), - ], - }); + fireEvent.touchStart(container.firstChild, { + touches: [ + new Touch({ + identifier: 1, + target: container, + clientX: 42, + clientY: 42, + }), + ], }); - act(() => { - fireEvent.touchEnd(container.firstChild, { - changedTouches: [ - new Touch({ - identifier: 1, - target: container, - clientX: 42, - clientY: 42, - }), - ], - }); + fireEvent.touchEnd(container.firstChild, { + changedTouches: [ + new Touch({ + identifier: 1, + target: container, + clientX: 42, + clientY: 42, + }), + ], }); setTimeout(() => { @@ -126,40 +121,34 @@ describe('', () => { it('should not fire onClick twice on touch tap', (done) => { const handleClick = spy(); - // If disableTouchRipple is not applied, errors are thrown due to "code that updates TouchRiple is not wrapped in act()" + // Need disableTouchRipple to avoid ripple missing act (async setState after touchEnd) const { getByRole, container } = render( , ); - act(() => { - fireEvent.touchStart(container.firstChild, { - touches: [ - new Touch({ - identifier: 1, - target: container, - clientX: 42, - clientY: 42, - }), - ], - }); + fireEvent.touchStart(container.firstChild, { + touches: [ + new Touch({ + identifier: 1, + target: container, + clientX: 42, + clientY: 42, + }), + ], }); - act(() => { - fireEvent.touchEnd(container.firstChild, { - changedTouches: [ - new Touch({ - identifier: 1, - target: container, - clientX: 42, - clientY: 42, - }), - ], - }); + fireEvent.touchEnd(container.firstChild, { + changedTouches: [ + new Touch({ + identifier: 1, + target: container, + clientX: 42, + clientY: 42, + }), + ], }); - act(() => { - getByRole('button').click(); - }); + getByRole('button').click(); setTimeout(() => { expect(handleClick.callCount).to.equal(1); @@ -170,35 +159,31 @@ describe('', () => { it('should not fire onClick if swiping', (done) => { const handleClick = spy(); - // If disableTouchRipple is not applied, errors are thrown due to "code that updates TouchRiple is not wrapped in act()" + // Need disableTouchRipple to avoid ripple missing act (async setState after touchEnd) const { container } = render( , ); - act(() => { - fireEvent.touchStart(container.firstChild, { - touches: [ - new Touch({ - identifier: 1, - target: container, - clientX: 42, - clientY: 42, - }), - ], - }); + fireEvent.touchStart(container.firstChild, { + touches: [ + new Touch({ + identifier: 1, + target: container, + clientX: 42, + clientY: 42, + }), + ], }); - act(() => { - fireEvent.touchEnd(container.firstChild, { - changedTouches: [ - new Touch({ - identifier: 1, - target: container, - clientX: 84, - clientY: 84, - }), - ], - }); + fireEvent.touchEnd(container.firstChild, { + changedTouches: [ + new Touch({ + identifier: 1, + target: container, + clientX: 84, + clientY: 84, + }), + ], }); setTimeout(() => { @@ -211,7 +196,7 @@ describe('', () => { const handleTouchStart = spy(); const handleTouchEnd = spy(); - // If disableTouchRipple is not applied, errors are thrown due to "code that updates TouchRiple is not wrapped in act()" + // Need disableTouchRipple to avoid ripple missing act (async setState after touchEnd). const { container } = render( ', () => { />, ); - act(() => { - fireEvent.touchStart(container.firstChild, { - touches: [ - new Touch({ - identifier: 1, - target: container, - clientX: 42, - clientY: 42, - }), - ], - }); + fireEvent.touchStart(container.firstChild, { + touches: [ + new Touch({ + identifier: 1, + target: container, + clientX: 42, + clientY: 42, + }), + ], }); expect(handleTouchStart.callCount).to.be.equals(1); - act(() => { - fireEvent.touchEnd(container.firstChild, { - changedTouches: [ - new Touch({ - identifier: 1, - target: container, - clientX: 84, - clientY: 84, - }), - ], - }); + fireEvent.touchEnd(container.firstChild, { + changedTouches: [ + new Touch({ + identifier: 1, + target: container, + clientX: 84, + clientY: 84, + }), + ], }); expect(handleTouchEnd.callCount).to.be.equals(1); From ef80bcf5541896d07efeecbcceab5870e38ee3de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20J=C3=B8rgensen?= Date: Tue, 8 Sep 2020 09:12:35 +0200 Subject: [PATCH 08/14] Update packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.js Co-authored-by: Olivier Tassinari --- .../src/BottomNavigationAction/BottomNavigationAction.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.js b/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.js index 3bc1d346597eed..45b6e3bbdc84b7 100644 --- a/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.js +++ b/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.js @@ -74,7 +74,9 @@ const BottomNavigationAction = React.forwardRef(function BottomNavigationAction( const touchTimer = React.useRef(); React.useEffect(() => { - return () => clearTimeout(touchTimer.current); + return () => { + clearTimeout(touchTimer.current); + }; }, [touchTimer]); const handleTouchStart = (event) => { From bb6b9010d888f2e89f16a1b7443c31cbb48934ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20J=C3=B8rgensen?= Date: Tue, 8 Sep 2020 09:13:18 +0200 Subject: [PATCH 09/14] Update packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.js Co-authored-by: Olivier Tassinari --- .../src/BottomNavigationAction/BottomNavigationAction.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.js b/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.js index 45b6e3bbdc84b7..1323dfdf83deda 100644 --- a/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.js +++ b/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.js @@ -80,7 +80,9 @@ const BottomNavigationAction = React.forwardRef(function BottomNavigationAction( }, [touchTimer]); const handleTouchStart = (event) => { - if (onTouchStart) onTouchStart(event); + if (onTouchStart) { + onTouchStart(event); + } const { clientX, clientY } = event.touches[0]; From 8a0491ec9298745d38778b0a1cb8d5c03874507e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20J=C3=B8rgensen?= Date: Tue, 8 Sep 2020 09:40:34 +0200 Subject: [PATCH 10/14] Use fake timers instead of setTimeout and only run Touch tests in supported browsers --- .../BottomNavigationAction.test.js | 299 ++++++++++-------- 1 file changed, 164 insertions(+), 135 deletions(-) diff --git a/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.test.js b/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.test.js index 14df4802324e03..a9206b891d3d75 100644 --- a/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.test.js +++ b/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.test.js @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { spy } from 'sinon'; +import { spy, useFakeTimers } from 'sinon'; import { getClasses, createMount, @@ -82,153 +82,182 @@ describe('', () => { }); }); - it('should fire onClick on touch tap', (done) => { - const handleClick = spy(); - - // Need disableTouchRipple to avoid ripple missing act (async setState after touchEnd) - const { container } = render( - , - ); - - fireEvent.touchStart(container.firstChild, { - touches: [ - new Touch({ - identifier: 1, - target: container, - clientX: 42, - clientY: 42, - }), - ], - }); + describe('touch functionality', () => { + let clock; - fireEvent.touchEnd(container.firstChild, { - changedTouches: [ - new Touch({ - identifier: 1, - target: container, - clientX: 42, - clientY: 42, - }), - ], - }); + beforeEach(() => { + clock = useFakeTimers(); + }) - setTimeout(() => { - expect(handleClick.callCount).to.equal(1); - done(); - }, 15); - }); + afterEach(() => { + clock.restore(); + }) - it('should not fire onClick twice on touch tap', (done) => { - const handleClick = spy(); - - // Need disableTouchRipple to avoid ripple missing act (async setState after touchEnd) - const { getByRole, container } = render( - , - ); - - fireEvent.touchStart(container.firstChild, { - touches: [ - new Touch({ - identifier: 1, - target: container, - clientX: 42, - clientY: 42, - }), - ], - }); + it('should fire onClick on touch tap', () => { + // Only run in supported browsers + if (typeof Touch === "undefined") { + return; + } - fireEvent.touchEnd(container.firstChild, { - changedTouches: [ - new Touch({ - identifier: 1, - target: container, - clientX: 42, - clientY: 42, - }), - ], - }); + const handleClick = spy(); - getByRole('button').click(); + // Need disableTouchRipple to avoid ripple missing act (async setState after touchEnd) + const { container } = render( + , + ); + + fireEvent.touchStart(container.firstChild, { + touches: [ + new Touch({ + identifier: 1, + target: container, + clientX: 42, + clientY: 42, + }), + ], + }); + + fireEvent.touchEnd(container.firstChild, { + changedTouches: [ + new Touch({ + identifier: 1, + target: container, + clientX: 42, + clientY: 42, + }), + ], + }); + + clock.tick(15); - setTimeout(() => { expect(handleClick.callCount).to.equal(1); - done(); - }, 15); - }); - - it('should not fire onClick if swiping', (done) => { - const handleClick = spy(); - - // Need disableTouchRipple to avoid ripple missing act (async setState after touchEnd) - const { container } = render( - , - ); - - fireEvent.touchStart(container.firstChild, { - touches: [ - new Touch({ - identifier: 1, - target: container, - clientX: 42, - clientY: 42, - }), - ], }); - fireEvent.touchEnd(container.firstChild, { - changedTouches: [ - new Touch({ - identifier: 1, - target: container, - clientX: 84, - clientY: 84, - }), - ], - }); + it('should not fire onClick twice on touch tap', () => { + // Only run in supported browsers + if (typeof Touch === "undefined") { + return; + } - setTimeout(() => { - expect(handleClick.callCount).to.equal(0); - done(); - }, 15); - }); + const handleClick = spy(); - it('should forward onTouchStart and onTouchEnd events', () => { - const handleTouchStart = spy(); - const handleTouchEnd = spy(); - - // Need disableTouchRipple to avoid ripple missing act (async setState after touchEnd). - const { container } = render( - , - ); - - fireEvent.touchStart(container.firstChild, { - touches: [ - new Touch({ - identifier: 1, - target: container, - clientX: 42, - clientY: 42, - }), - ], + // Need disableTouchRipple to avoid ripple missing act (async setState after touchEnd) + const { getByRole, container } = render( + , + ); + + fireEvent.touchStart(container.firstChild, { + touches: [ + new Touch({ + identifier: 1, + target: container, + clientX: 42, + clientY: 42, + }), + ], + }); + + fireEvent.touchEnd(container.firstChild, { + changedTouches: [ + new Touch({ + identifier: 1, + target: container, + clientX: 42, + clientY: 42, + }), + ], + }); + + getByRole('button').click(); + + clock.tick(15); + + expect(handleClick.callCount).to.equal(1); }); - expect(handleTouchStart.callCount).to.be.equals(1); - - fireEvent.touchEnd(container.firstChild, { - changedTouches: [ - new Touch({ - identifier: 1, - target: container, - clientX: 84, - clientY: 84, - }), - ], + it('should not fire onClick if swiping', () => { + // Only run in supported browsers + if (typeof Touch === "undefined") { + return; + } + + const handleClick = spy(); + + // Need disableTouchRipple to avoid ripple missing act (async setState after touchEnd) + const { container } = render( + , + ); + + fireEvent.touchStart(container.firstChild, { + touches: [ + new Touch({ + identifier: 1, + target: container, + clientX: 42, + clientY: 42, + }), + ], + }); + + fireEvent.touchEnd(container.firstChild, { + changedTouches: [ + new Touch({ + identifier: 1, + target: container, + clientX: 84, + clientY: 84, + }), + ], + }); + + clock.tick(15); + + expect(handleClick.callCount).to.equal(0); }); - expect(handleTouchEnd.callCount).to.be.equals(1); - }); + it('should forward onTouchStart and onTouchEnd events', () => { + // Only run in supported browsers + if (typeof Touch === "undefined") { + return; + } + + const handleTouchStart = spy(); + const handleTouchEnd = spy(); + + // Need disableTouchRipple to avoid ripple missing act (async setState after touchEnd). + const { container } = render( + , + ); + + fireEvent.touchStart(container.firstChild, { + touches: [ + new Touch({ + identifier: 1, + target: container, + clientX: 42, + clientY: 42, + }), + ], + }); + + expect(handleTouchStart.callCount).to.be.equals(1); + + fireEvent.touchEnd(container.firstChild, { + changedTouches: [ + new Touch({ + identifier: 1, + target: container, + clientX: 84, + clientY: 84, + }), + ], + }); + + expect(handleTouchEnd.callCount).to.be.equals(1); + }); + }) }); From 01fc7ab12f43c0b20cd10d35096ec2e1e245497b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20J=C3=B8rgensen?= Date: Tue, 8 Sep 2020 09:45:52 +0200 Subject: [PATCH 11/14] Formatting --- .../BottomNavigationAction.js | 4 ++-- .../BottomNavigationAction.test.js | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.js b/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.js index 1323dfdf83deda..589272cb81a11f 100644 --- a/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.js +++ b/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.js @@ -90,7 +90,7 @@ const BottomNavigationAction = React.forwardRef(function BottomNavigationAction( clientX, clientY, }; - } + }; const handleTouchEnd = (event) => { if (onTouchEnd) onTouchEnd(event); @@ -111,7 +111,7 @@ const BottomNavigationAction = React.forwardRef(function BottomNavigationAction( target.dispatchEvent(new Event('click', { bubbles: true })); }, 10); } - } + }; const handleChange = (event) => { clearTimeout(touchTimer.current); diff --git a/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.test.js b/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.test.js index a9206b891d3d75..699ebe52d05933 100644 --- a/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.test.js +++ b/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.test.js @@ -87,15 +87,15 @@ describe('', () => { beforeEach(() => { clock = useFakeTimers(); - }) + }); afterEach(() => { clock.restore(); - }) + }); it('should fire onClick on touch tap', () => { // Only run in supported browsers - if (typeof Touch === "undefined") { + if (typeof Touch === 'undefined') { return; } @@ -135,7 +135,7 @@ describe('', () => { it('should not fire onClick twice on touch tap', () => { // Only run in supported browsers - if (typeof Touch === "undefined") { + if (typeof Touch === 'undefined') { return; } @@ -177,7 +177,7 @@ describe('', () => { it('should not fire onClick if swiping', () => { // Only run in supported browsers - if (typeof Touch === "undefined") { + if (typeof Touch === 'undefined') { return; } @@ -217,7 +217,7 @@ describe('', () => { it('should forward onTouchStart and onTouchEnd events', () => { // Only run in supported browsers - if (typeof Touch === "undefined") { + if (typeof Touch === 'undefined') { return; } @@ -259,5 +259,5 @@ describe('', () => { expect(handleTouchEnd.callCount).to.be.equals(1); }); - }) + }); }); From 8bc9621dc79ebd3334fface7a2b14dc66706463c Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Tue, 8 Sep 2020 14:02:13 +0200 Subject: [PATCH 12/14] Simplify demo --- .../FixedBottomNavigation.js | 97 +++++++++--------- .../FixedBottomNavigation.tsx | 98 +++++++++---------- .../SimpleBottomNavigation.js | 25 ++--- .../SimpleBottomNavigation.tsx | 25 ++--- 4 files changed, 123 insertions(+), 122 deletions(-) diff --git a/docs/src/pages/components/bottom-navigation/FixedBottomNavigation.js b/docs/src/pages/components/bottom-navigation/FixedBottomNavigation.js index 0002d2aec37ff9..4d55bbf80ec97d 100644 --- a/docs/src/pages/components/bottom-navigation/FixedBottomNavigation.js +++ b/docs/src/pages/components/bottom-navigation/FixedBottomNavigation.js @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-use-before-define */ import React from 'react'; import { makeStyles } from '@material-ui/core/styles'; import CssBaseline from '@material-ui/core/CssBaseline'; @@ -25,56 +26,9 @@ const useStyles = makeStyles({ }, }); -function getRandomInt(max) { - return Math.floor(Math.random() * Math.floor(max)); -} - -const messageExamples = [ - { - primary: 'Brunch this week?', - secondary: - "I'll be in the neighbourhood this week. Let's grab a bite to eat", - person: '/static/images/avatar/5.jpg', - }, - { - primary: 'Birthday Gift', - secondary: `Do you have a suggestion for a good present for John on his work - anniversary. I am really confused & would love your thoughts on it.`, - person: '/static/images/avatar/1.jpg', - }, - { - primary: 'Recipe to try', - secondary: - 'I am try out this new BBQ recipe, I think this might be amazing', - person: '/static/images/avatar/2.jpg', - }, - { - primary: 'Yes!', - secondary: 'I have the tickets to the ReactConf for this year.', - person: '/static/images/avatar/3.jpg', - }, - { - primary: "Doctor's Appointment", - secondary: - 'My appointment for the doctor was rescheduled for next Saturday.', - person: '/static/images/avatar/4.jpg', - }, - { - primary: 'Discussion', - secondary: `Menus that are generated by the bottom app bar (such as a bottom - navigation drawer or overflow menu) open as bottom sheets at a higher elevation - than the bar.`, - person: '/static/images/avatar/5.jpg', - }, - { - primary: 'Summer BBQ', - secondary: `Who wants to have a cookout this weekend? I just got some furniture - for my backyard and would love to fire up the grill.`, - person: '/static/images/avatar/1.jpg', - }, -]; - function refreshMessages() { + const getRandomInt = (max) => Math.floor(Math.random() * Math.floor(max)); + return Array.from(new Array(50)).map( () => messageExamples[getRandomInt(messageExamples.length)], ); @@ -120,3 +74,48 @@ export default function FixedBottomNavigation() {
); } + +const messageExamples = [ + { + primary: 'Brunch this week?', + secondary: + "I'll be in the neighbourhood this week. Let's grab a bite to eat", + person: '/static/images/avatar/5.jpg', + }, + { + primary: 'Birthday Gift', + secondary: `Do you have a suggestion for a good present for John on his work + anniversary. I am really confused & would love your thoughts on it.`, + person: '/static/images/avatar/1.jpg', + }, + { + primary: 'Recipe to try', + secondary: + 'I am try out this new BBQ recipe, I think this might be amazing', + person: '/static/images/avatar/2.jpg', + }, + { + primary: 'Yes!', + secondary: 'I have the tickets to the ReactConf for this year.', + person: '/static/images/avatar/3.jpg', + }, + { + primary: "Doctor's Appointment", + secondary: + 'My appointment for the doctor was rescheduled for next Saturday.', + person: '/static/images/avatar/4.jpg', + }, + { + primary: 'Discussion', + secondary: `Menus that are generated by the bottom app bar (such as a bottom + navigation drawer or overflow menu) open as bottom sheets at a higher elevation + than the bar.`, + person: '/static/images/avatar/5.jpg', + }, + { + primary: 'Summer BBQ', + secondary: `Who wants to have a cookout this weekend? I just got some furniture + for my backyard and would love to fire up the grill.`, + person: '/static/images/avatar/1.jpg', + }, +]; diff --git a/docs/src/pages/components/bottom-navigation/FixedBottomNavigation.tsx b/docs/src/pages/components/bottom-navigation/FixedBottomNavigation.tsx index 989cd800ffee1b..75a677c288b18b 100644 --- a/docs/src/pages/components/bottom-navigation/FixedBottomNavigation.tsx +++ b/docs/src/pages/components/bottom-navigation/FixedBottomNavigation.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-use-before-define */ import React from 'react'; import { makeStyles } from '@material-ui/core/styles'; import CssBaseline from '@material-ui/core/CssBaseline'; @@ -25,8 +26,54 @@ const useStyles = makeStyles({ }, }); -function getRandomInt(max: number) { - return Math.floor(Math.random() * Math.floor(max)); +function refreshMessages(): MessageExample[] { + const getRandomInt = (max: number) => + Math.floor(Math.random() * Math.floor(max)); + + return Array.from(new Array(50)).map( + () => messageExamples[getRandomInt(messageExamples.length)], + ); +} + +export default function FixedBottomNavigation() { + const classes = useStyles(); + const [value, setValue] = React.useState(0); + const ref = React.useRef(null); + const [messages, setMessages] = React.useState(() => refreshMessages()); + + React.useEffect(() => { + (ref.current as HTMLDivElement).ownerDocument.body.scrollTop = 0; + setMessages(refreshMessages()); + }, [value, setMessages]); + + return ( +
+ + + {messages.map(({ primary, secondary, person }, index) => ( + + + + + + + ))} + + + { + setValue(newValue); + }} + > + } /> + } /> + } /> + + +
+ ); } interface MessageExample { @@ -79,50 +126,3 @@ const messageExamples: MessageExample[] = [ person: '/static/images/avatar/1.jpg', }, ]; - -function refreshMessages(): MessageExample[] { - return Array.from(new Array(50)).map( - () => messageExamples[getRandomInt(messageExamples.length)], - ); -} - -export default function FixedBottomNavigation() { - const classes = useStyles(); - const [value, setValue] = React.useState(0); - const ref = React.useRef(null); - const [messages, setMessages] = React.useState(() => refreshMessages()); - - React.useEffect(() => { - (ref.current as HTMLDivElement).ownerDocument.body.scrollTop = 0; - setMessages(refreshMessages()); - }, [value, setMessages]); - - return ( -
- - - {messages.map(({ primary, secondary, person }, index) => ( - - - - - - - ))} - - - { - setValue(newValue); - }} - > - } /> - } /> - } /> - - -
- ); -} diff --git a/docs/src/pages/components/bottom-navigation/SimpleBottomNavigation.js b/docs/src/pages/components/bottom-navigation/SimpleBottomNavigation.js index 845c7fea7e7ea1..30664ed92d2f83 100644 --- a/docs/src/pages/components/bottom-navigation/SimpleBottomNavigation.js +++ b/docs/src/pages/components/bottom-navigation/SimpleBottomNavigation.js @@ -17,17 +17,18 @@ export default function SimpleBottomNavigation() { const [value, setValue] = React.useState(0); return ( - { - setValue(newValue); - }} - showLabels - className={classes.root} - > - } /> - } /> - } /> - +
+ { + setValue(newValue); + }} + > + } /> + } /> + } /> + +
); } diff --git a/docs/src/pages/components/bottom-navigation/SimpleBottomNavigation.tsx b/docs/src/pages/components/bottom-navigation/SimpleBottomNavigation.tsx index 845c7fea7e7ea1..30664ed92d2f83 100644 --- a/docs/src/pages/components/bottom-navigation/SimpleBottomNavigation.tsx +++ b/docs/src/pages/components/bottom-navigation/SimpleBottomNavigation.tsx @@ -17,17 +17,18 @@ export default function SimpleBottomNavigation() { const [value, setValue] = React.useState(0); return ( - { - setValue(newValue); - }} - showLabels - className={classes.root} - > - } /> - } /> - } /> - +
+ { + setValue(newValue); + }} + > + } /> + } /> + } /> + +
); } From 3c3c1caa011fe49462a25456b753e80afece6e6f Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Tue, 8 Sep 2020 14:19:10 +0200 Subject: [PATCH 13/14] try skip tests --- .../BottomNavigationAction.test.js | 29 +++++-------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.test.js b/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.test.js index 699ebe52d05933..68b2e1b8682339 100644 --- a/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.test.js +++ b/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.test.js @@ -83,6 +83,13 @@ describe('', () => { }); describe('touch functionality', () => { + before(function test () { + // Only run in supported browsers + if (typeof Touch === 'undefined') { + this.skip(); + } + }); + let clock; beforeEach(() => { @@ -94,11 +101,6 @@ describe('', () => { }); it('should fire onClick on touch tap', () => { - // Only run in supported browsers - if (typeof Touch === 'undefined') { - return; - } - const handleClick = spy(); // Need disableTouchRipple to avoid ripple missing act (async setState after touchEnd) @@ -134,11 +136,6 @@ describe('', () => { }); it('should not fire onClick twice on touch tap', () => { - // Only run in supported browsers - if (typeof Touch === 'undefined') { - return; - } - const handleClick = spy(); // Need disableTouchRipple to avoid ripple missing act (async setState after touchEnd) @@ -176,11 +173,6 @@ describe('', () => { }); it('should not fire onClick if swiping', () => { - // Only run in supported browsers - if (typeof Touch === 'undefined') { - return; - } - const handleClick = spy(); // Need disableTouchRipple to avoid ripple missing act (async setState after touchEnd) @@ -210,17 +202,12 @@ describe('', () => { ], }); - clock.tick(15); + clock.tick(10); expect(handleClick.callCount).to.equal(0); }); it('should forward onTouchStart and onTouchEnd events', () => { - // Only run in supported browsers - if (typeof Touch === 'undefined') { - return; - } - const handleTouchStart = spy(); const handleTouchEnd = spy(); From 079c34c466aa2ca0341c73a29147dbabfaa20040 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20J=C3=B8rgensen?= Date: Tue, 8 Sep 2020 14:26:39 +0200 Subject: [PATCH 14/14] Code formatting --- .../src/BottomNavigationAction/BottomNavigationAction.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.test.js b/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.test.js index 68b2e1b8682339..21981d291330da 100644 --- a/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.test.js +++ b/packages/material-ui/src/BottomNavigationAction/BottomNavigationAction.test.js @@ -83,7 +83,7 @@ describe('', () => { }); describe('touch functionality', () => { - before(function test () { + before(function test() { // Only run in supported browsers if (typeof Touch === 'undefined') { this.skip();