Upgrade React to v17 (#4201)

* Upgrade react and react-dom
* Fix test
* Ensure that the "history:toggle" event is broadcast when switching in or out of history view
* Add ControlledDropdown
* Remove DropdownButton stories

GitOrigin-RevId: 3810f6986bb60e59af31f960f431c31be16554f5
This commit is contained in:
Alf Eaton
2021-06-18 10:29:06 +01:00
committed by Copybot
parent 15fff6fd7f
commit 1760d93fc4
10 changed files with 113 additions and 105 deletions

View File

@@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next'
import { Dropdown, MenuItem, OverlayTrigger, Tooltip } from 'react-bootstrap'
import Icon from '../../../shared/components/icon'
import { getHueForUserId } from '../../../shared/utils/colors'
import ControlledDropdown from '../../../shared/components/controlled-dropdown'
function OnlineUsersWidget({ onlineUsers, goToUser }) {
const { t } = useTranslation()
@@ -12,7 +13,7 @@ function OnlineUsersWidget({ onlineUsers, goToUser }) {
if (shouldDisplayDropdown) {
return (
<Dropdown id="online-users" className="online-users" pullRight>
<ControlledDropdown id="online-users" className="online-users" pullRight>
<DropDownToggleButton
bsRole="toggle"
onlineUserCount={onlineUsers.length}
@@ -30,7 +31,7 @@ function OnlineUsersWidget({ onlineUsers, goToUser }) {
</MenuItem>
))}
</Dropdown.Menu>
</Dropdown>
</ControlledDropdown>
)
} else {
return (

View File

@@ -4,6 +4,7 @@ import { Dropdown, OverlayTrigger, Tooltip } from 'react-bootstrap'
import { useTranslation } from 'react-i18next'
import PreviewDownloadFileList from './preview-download-file-list'
import Icon from '../../../shared/components/icon'
import ControlledDropdown from '../../../shared/components/controlled-dropdown'
function PreviewDownloadButton({
isCompiling,
@@ -40,7 +41,7 @@ function PreviewDownloadButton({
const hideTooltip = showText && pdfDownloadUrl
return (
<Dropdown
<ControlledDropdown
id="download-dropdown"
className="toolbar-item"
disabled={isCompiling}
@@ -69,7 +70,7 @@ function PreviewDownloadButton({
<Dropdown.Menu id="download-dropdown-list">
<PreviewDownloadFileList fileList={outputFiles} />
</Dropdown.Menu>
</Dropdown>
</ControlledDropdown>
)
}

View File

@@ -8,6 +8,7 @@ import PreviewDownloadFileList from './preview-download-file-list'
import PreviewError from './preview-error'
import Icon from '../../../shared/components/icon'
import usePersistedState from '../../../shared/hooks/use-persisted-state'
import ControlledDropdown from '../../../shared/components/controlled-dropdown'
function PreviewLogsPane({
logEntries = { all: [], errors: [], warnings: [], typesetting: [] },
@@ -84,7 +85,7 @@ function PreviewLogsPane({
&nbsp;
<span>{t('clear_cached_files')}</span>
</button>
<Dropdown
<ControlledDropdown
id="dropdown-files-logs-pane"
dropup
pullRight
@@ -98,7 +99,7 @@ function PreviewLogsPane({
<Dropdown.Menu id="dropdown-files-logs-pane-list">
<PreviewDownloadFileList fileList={outputFiles} />
</Dropdown.Menu>
</Dropdown>
</ControlledDropdown>
</div>
)

View File

@@ -4,6 +4,7 @@ import { Dropdown, MenuItem, OverlayTrigger, Tooltip } from 'react-bootstrap'
import { useTranslation } from 'react-i18next'
import classNames from 'classnames'
import Icon from '../../../shared/components/icon'
import ControlledDropdown from '../../../shared/components/controlled-dropdown'
function PreviewRecompileButton({
compilerState: {
@@ -80,7 +81,7 @@ function PreviewRecompileButton({
)
const buttonElement = (
<Dropdown
<ControlledDropdown
id="pdf-recompile-dropdown"
className={recompileButtonGroupClasses}
>
@@ -151,7 +152,7 @@ function PreviewRecompileButton({
{t('recompile_from_scratch')}
</MenuItem>
</Dropdown.Menu>
</Dropdown>
</ControlledDropdown>
)
return showText ? (

View File

@@ -0,0 +1,47 @@
import React, { useCallback, useState } from 'react'
import { Dropdown } from 'react-bootstrap'
import PropTypes from 'prop-types'
export default function ControlledDropdown(props) {
const [open, setOpen] = useState(Boolean(props.defaultOpen))
const handleClick = useCallback(event => {
event.stopPropagation()
}, [])
const handleToggle = useCallback(value => {
setOpen(value)
}, [])
return (
<Dropdown
{...props}
open={open}
onToggle={handleToggle}
onClick={handleClick}
>
{React.Children.map(props.children, child => {
if (!React.isValidElement(child)) {
return child
}
// Dropdown.Menu
if ('open' in child.props) {
return React.cloneElement(child, { open })
}
// Overlay
if ('show' in child.props) {
return React.cloneElement(child, { show: open })
}
// anything else
return React.cloneElement(child)
})}
</Dropdown>
)
}
ControlledDropdown.propTypes = {
children: PropTypes.any,
defaultOpen: PropTypes.bool,
}

View File

@@ -26,10 +26,14 @@ export function LayoutProvider({ children }) {
const setView = useCallback(
value => {
_setView(value)
if (value === 'history') {
$scope.toggleHistory()
}
_setView(oldValue => {
// ensure that the "history:toggle" event is broadcast when switching in or out of history view
if (value === 'history' || oldValue === 'history') {
$scope.toggleHistory()
}
return value
})
},
[$scope, _setView]
)

View File

@@ -1,77 +1,10 @@
import React from 'react'
import { Dropdown, DropdownButton, MenuItem } from 'react-bootstrap'
import Icon from '../js/shared/components/icon'
const MenuItems = () => (
<>
<MenuItem eventKey="1">Action</MenuItem>
<MenuItem eventKey="2">Another action</MenuItem>
<MenuItem eventKey="3" active>
Active Item
</MenuItem>
<MenuItem divider />
<MenuItem eventKey="4">Separated link</MenuItem>
</>
)
const defaultArgs = {
bsStyle: 'default',
title: 'Dropdown',
pullRight: false,
noCaret: false,
className: '',
defaultOpen: true,
}
export const Default = args => {
return (
<DropdownButton {...args}>
<MenuItems />
</DropdownButton>
)
}
Default.args = { ...defaultArgs }
export const Primary = args => {
return (
<DropdownButton {...args}>
<MenuItems />
</DropdownButton>
)
}
Primary.args = { ...defaultArgs, bsStyle: 'primary' }
export const RightAligned = args => {
return (
<div style={{ textAlign: 'right' }}>
<DropdownButton {...args}>
<MenuItems />
</DropdownButton>
</div>
)
}
RightAligned.args = { ...defaultArgs, pullRight: true }
export const SingleIconTransparent = args => {
return (
<div style={{ textAlign: 'right' }}>
<DropdownButton {...args}>
<MenuItems />
</DropdownButton>
</div>
)
}
SingleIconTransparent.args = {
...defaultArgs,
pullRight: true,
title: <Icon type="ellipsis-v" />,
noCaret: true,
className: 'dropdown-toggle-no-background',
}
import { Dropdown, MenuItem } from 'react-bootstrap'
import ControlledDropdown from '../js/shared/components/controlled-dropdown'
export const Customized = args => {
return (
<Dropdown
<ControlledDropdown
pullRight={args.pullRight}
defaultOpen={args.defaultOpen}
id="dropdown-story"
@@ -84,18 +17,30 @@ export const Customized = args => {
{args.title}
</Dropdown.Toggle>
<Dropdown.Menu>
<MenuItems />
<MenuItem eventKey="1">Action</MenuItem>
<MenuItem eventKey="2">Another action</MenuItem>
<MenuItem eventKey="3" active>
Active Item
</MenuItem>
<MenuItem divider />
<MenuItem eventKey="4">Separated link</MenuItem>
</Dropdown.Menu>
</Dropdown>
</ControlledDropdown>
)
}
Customized.args = {
...defaultArgs,
component: Dropdown,
title: 'Toggle & Menu used separately',
}
export default {
title: 'Dropdown',
component: DropdownButton,
component: ControlledDropdown,
args: {
bsStyle: 'default',
title: 'Dropdown',
pullRight: false,
noCaret: false,
className: '',
defaultOpen: true,
},
}

View File

@@ -21275,7 +21275,17 @@
"string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"requires": {
"safe-buffer": "~5.2.0"
},
"dependencies": {
"safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
}
}
}
}
},
@@ -30243,13 +30253,12 @@
}
},
"react": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react/-/react-16.13.1.tgz",
"integrity": "sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==",
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
"integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==",
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
"prop-types": "^15.6.2"
"object-assign": "^4.1.1"
}
},
"react-bootstrap": {
@@ -30677,14 +30686,13 @@
}
},
"react-dom": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.13.1.tgz",
"integrity": "sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag==",
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
"integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==",
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
"prop-types": "^15.6.2",
"scheduler": "^0.19.1"
"scheduler": "^0.20.2"
}
},
"react-draggable": {
@@ -32423,9 +32431,9 @@
}
},
"scheduler": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz",
"integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==",
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz",
"integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==",
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"

View File

@@ -141,11 +141,11 @@
"pug": "^3.0.1",
"pug-runtime": "^3.0.1",
"qrcode": "^1.4.4",
"react": "^16.13.1",
"react": "^17.0.2",
"react-bootstrap": "^0.33.1",
"react-dnd": "^11.1.3",
"react-dnd-html5-backend": "^11.1.3",
"react-dom": "^16.13.1",
"react-dom": "^17.0.2",
"react-error-boundary": "^2.3.1",
"react-i18next": "^11.7.1",
"react-linkify": "^1.0.0-alpha",

View File

@@ -658,11 +658,11 @@ describe('<ShareProjectModal/>', function () {
const submitButton = screen.getByRole('button', { name: 'Share' })
const respondWithError = async function (errorReason) {
inputElement.focus()
fireEvent.focus(inputElement)
fireEvent.change(inputElement, {
target: { value: 'invited-author-1@example.com' },
})
inputElement.blur()
fireEvent.blur(inputElement)
fetchMock.postOnce(
'express:/project/:projectId/invite',