diff --git a/wax-prosemirror-components/package.json b/wax-prosemirror-components/package.json index 32140789d22a471c2e2c28ad4085f98a0e134922..d6d6d7007b6dc094c2306b9f28ce0f991939e621 100644 --- a/wax-prosemirror-components/package.json +++ b/wax-prosemirror-components/package.json @@ -27,7 +27,8 @@ "use-deep-compare-effect": "^1.3.1", "uuid": "^7.0.3", "wax-prosemirror-core": "^0.0.20", - "wax-prosemirror-utilities": "^0.0.20" + "wax-prosemirror-utilities": "^0.0.20", + "@pubsweet/ui-toolkit": "^2.3.1" }, "resolutions": { "prosemirror-model": "1.11.0", diff --git a/wax-prosemirror-components/src/components/Button.js b/wax-prosemirror-components/src/components/Button.js index 6817892542777b3758a8687da0cff1a1ca86dd9e..fa2e0d6efdf838a56b2adf9ed84dd48d5d8b8276 100644 --- a/wax-prosemirror-components/src/components/Button.js +++ b/wax-prosemirror-components/src/components/Button.js @@ -1,6 +1,6 @@ /* eslint react/prop-types: 0 */ -import React, { useContext } from 'react'; +import React, { useContext, useMemo } from 'react'; import { WaxContext } from 'wax-prosemirror-core'; import MenuButton from '../ui/buttons/MenuButton'; @@ -16,9 +16,9 @@ const Button = ({ view = {}, item }) => { const { dispatch, state } = view; - const handleMouseDown = e => { + const handleMouseDown = (e, editorState, editorDispatch) => { e.preventDefault(); - run(state, dispatch); + run(editorState, dispatch); }; const isActive = active && active(state, activeViewId); @@ -26,16 +26,21 @@ const Button = ({ view = {}, item }) => { const isDisabled = enable && !enable(state) && !(select && select(state, activeViewId)); - return ( - <MenuButton - active={isActive || false} - disabled={isDisabled} - iconName={icon} - label={label} - onMouseDown={handleMouseDown} - title={title} - /> + const MenuButtonComponent = useMemo( + () => ( + <MenuButton + active={isActive || false} + disabled={isDisabled} + iconName={icon} + label={label} + onMouseDown={e => handleMouseDown(e, view.state, view.dispatch)} + title={title} + /> + ), + [isActive, isDisabled], ); + + return MenuButtonComponent; }; export default Button; diff --git a/wax-prosemirror-components/src/components/ImageUpload.js b/wax-prosemirror-components/src/components/ImageUpload.js index f34fca7bc4131967f0f6b1999c1c4c45c3449c82..b94c0a7e544cd9a2c0c8c322de878770755537f4 100644 --- a/wax-prosemirror-components/src/components/ImageUpload.js +++ b/wax-prosemirror-components/src/components/ImageUpload.js @@ -1,5 +1,5 @@ /* eslint react/prop-types: 0 */ -import React, { useContext, useRef } from 'react'; +import React, { useContext, useRef, useMemo } from 'react'; import { WaxContext } from 'wax-prosemirror-core'; import styled from 'styled-components'; @@ -17,25 +17,30 @@ const ImageUpload = ({ item, fileUpload, view }) => { const inputRef = useRef(null); const handleMouseDown = () => inputRef.current.click(); - return ( - <Wrapper> - <label htmlFor="file-upload"> - <MenuButton - active={false} - disabled={!(item.select && item.select(view.state, activeViewId))} - iconName={item.icon} - onMouseDown={handleMouseDown} - title="Upload Image" - /> + const ImageUploadComponent = useMemo( + () => ( + <Wrapper> + <label htmlFor="file-upload"> + <MenuButton + active={false} + disabled={!(item.select && item.select(view.state, activeViewId))} + iconName={item.icon} + onMouseDown={handleMouseDown} + title="Upload Image" + /> - <input - id="file-upload" - ref={inputRef} - onChange={e => fileUpload(e.target.files[0])} - type="file" - /> - </label> - </Wrapper> + <input + id="file-upload" + ref={inputRef} + onChange={e => fileUpload(e.target.files[0])} + type="file" + /> + </label> + </Wrapper> + ), + [], ); + + return ImageUploadComponent; }; export default ImageUpload; diff --git a/wax-prosemirror-components/src/components/ToolGroupComponent.js b/wax-prosemirror-components/src/components/ToolGroupComponent.js index cae23c1c40fde6c45061267f150ca4e6cc0b2808..6ab71762b38941a1852d6d3461a88ad90a00c694 100644 --- a/wax-prosemirror-components/src/components/ToolGroupComponent.js +++ b/wax-prosemirror-components/src/components/ToolGroupComponent.js @@ -1,5 +1,5 @@ /* eslint react/prop-types: 0 */ -import React from 'react'; +import React, { useMemo } from 'react'; import styled from 'styled-components'; import Dropdown from '../ui/buttons/Dropdown'; @@ -30,18 +30,22 @@ const ToolGroupComponent = ({ view, tools, name }) => { : toolsShown.push(tool.renderTool(view)); }); - return ( - <Wrapper data-name={name}> - {toolsShown} - {rest.length > 0 && ( - <Dropdown - iconName="more" - dropComponent={<DropWrapper>{rest}</DropWrapper>} - title="Show more tools" - /> - )} - </Wrapper> + const MemorizedToolGroupComponent = useMemo( + () => ( + <Wrapper data-name={name}> + {toolsShown} + {rest.length > 0 && ( + <Dropdown + iconName="more" + dropComponent={<DropWrapper>{rest}</DropWrapper>} + title="Show more tools" + /> + )} + </Wrapper> + ), + [], ); + return MemorizedToolGroupComponent; }; export default ToolGroupComponent; diff --git a/wax-prosemirror-components/src/components/tables/CreateTable.js b/wax-prosemirror-components/src/components/tables/CreateTable.js index c7795ea5e002299834161628a12b86e4053b89cd..31b2ec3821afb9b71e4b82f2ff0f9350016a3b39 100644 --- a/wax-prosemirror-components/src/components/tables/CreateTable.js +++ b/wax-prosemirror-components/src/components/tables/CreateTable.js @@ -1,11 +1,25 @@ /* eslint react/prop-types: 0 */ -import React, { useState, useContext } from 'react'; +import React, { useState, useContext, useMemo, useEffect, useRef } from 'react'; import { WaxContext } from 'wax-prosemirror-core'; +import styled from 'styled-components'; +import { grid } from '@pubsweet/ui-toolkit'; -import Dropdown from '../../ui/buttons/Dropdown'; +import MenuButton from '../../ui/buttons/MenuButton'; import InsertTableTool from '../../ui/tables/InsertTableTool'; +const Wrapper = styled.div` + font-size: 0; + position: relative; + z-index: 2; +`; + +const DropWrapper = styled.div` + margin-top: ${grid(1)}; + position: absolute; + background: white; +`; + const CreateTable = ({ view = {}, item }) => { const { view: { main }, @@ -15,33 +29,68 @@ const CreateTable = ({ view = {}, item }) => { view = main; } - const [showTool, setShowTool] = useState(false); - const toggleShowTool = () => setShowTool(!showTool); - - const { state, dispatch } = view; + const { state } = view; const { enable, icon, run, select, title } = item; + const ref = useRef(); + const [isOpen, setIsOpen] = useState(false); const dropComponent = ( <InsertTableTool - onGridSelect={colRows => { - run(colRows, state, dispatch); - }} + onGridSelect={colRows => handleSelect(colRows, view.state, view.dispatch)} /> ); + const handleSelect = (colRows, editorState, editorDispatch) => { + run(colRows, editorState, editorDispatch); + setIsOpen(!isOpen); + }; + const isDisabled = enable && !enable(state) && !(select && select(state, activeViewId)); - return ( - <Dropdown - active={showTool} - dropComponent={dropComponent} - iconName={icon} - disabled={isDisabled} - onClick={toggleShowTool} - title={title} - /> + useOnClickOutside(ref, () => setIsOpen(false)); + + const MemorizedDropdown = useMemo( + () => ( + <Wrapper ref={ref}> + <MenuButton + active={isOpen} + disabled={isDisabled} + iconName={icon} + onMouseDown={() => { + setIsOpen(!isOpen); + }} + title={title} + /> + + {isOpen && <DropWrapper>{dropComponent}</DropWrapper>} + </Wrapper> + ), + [isDisabled, isOpen], ); + + return MemorizedDropdown; }; +// Hook +const useOnClickOutside = (ref, handler) => { + useEffect(() => { + const listener = event => { + /* Do nothing if clicking ref's element or descendent elements */ + if (!ref.current || ref.current.contains(event.target)) { + return; + } + + handler(event); + }; + + document.addEventListener('mousedown', listener); + document.addEventListener('touchstart', listener); + + return () => { + document.removeEventListener('mousedown', listener); + document.removeEventListener('touchstart', listener); + }; + }, [ref, handler]); +}; export default CreateTable; diff --git a/wax-prosemirror-components/src/components/trackChanges/TrackChangeEnable.js b/wax-prosemirror-components/src/components/trackChanges/TrackChangeEnable.js index 4615cdf3ab0f61f43cfedca04d370951303b9f8b..8ee8b7c181a5523a88a79b7bdd8dc3eadfa57266 100644 --- a/wax-prosemirror-components/src/components/trackChanges/TrackChangeEnable.js +++ b/wax-prosemirror-components/src/components/trackChanges/TrackChangeEnable.js @@ -1,5 +1,5 @@ /* eslint react/prop-types: 0 */ -import React, { useState } from 'react'; +import React, { useState, useMemo } from 'react'; import MenuButton from '../../ui/buttons/MenuButton'; const TrackChangeEnable = ({ view = {}, item, enabled }) => { @@ -11,15 +11,20 @@ const TrackChangeEnable = ({ view = {}, item, enabled }) => { item.run(view.state, view.dispatch); }; - return ( - <MenuButton - active={isEnabled} - disabled={item.enable && !item.enable(view.state)} - label="Track Changes" - onMouseDown={e => handleMouseDown(e)} - title={item.title} - /> + const TrackChangeEnableComponent = useMemo( + () => ( + <MenuButton + active={isEnabled} + disabled={item.enable && !item.enable(view.state)} + label="Track Changes" + onMouseDown={e => handleMouseDown(e)} + title={item.title} + /> + ), + [isEnabled], ); + + return TrackChangeEnableComponent; }; export default TrackChangeEnable; diff --git a/wax-prosemirror-services/src/WaxToolGroups/DisplayTextToolGroupService/DisplayText.js b/wax-prosemirror-services/src/WaxToolGroups/DisplayTextToolGroupService/DisplayText.js index 5b1c4263191ffae80b5f61c50cd2ba9935c5d52b..0ad922f8c9cf261aed1c0b1ef3694a3d45c6afbc 100644 --- a/wax-prosemirror-services/src/WaxToolGroups/DisplayTextToolGroupService/DisplayText.js +++ b/wax-prosemirror-services/src/WaxToolGroups/DisplayTextToolGroupService/DisplayText.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useMemo } from 'react'; import { injectable, inject } from 'inversify'; import { BlockLevelTools, Tabs, ToolGroups } from 'wax-prosemirror-components'; import { isEmpty } from 'lodash'; @@ -50,7 +50,12 @@ class DisplayText extends ToolGroup { }; const tabList = [first, second]; - return <Tabs key={uuidv4()} tabList={tabList} />; + + const TabsComponent = useMemo( + () => <Tabs key={uuidv4()} tabList={tabList} />, + [], + ); + return TabsComponent; } } diff --git a/wax-prosemirror-services/src/lib/ToolGroup.js b/wax-prosemirror-services/src/lib/ToolGroup.js index 60d6393c9d175109594c22baae10fbf9b7bc48d9..efe1c529b940c81b97e3a2768e27aa53302ea0e2 100644 --- a/wax-prosemirror-services/src/lib/ToolGroup.js +++ b/wax-prosemirror-services/src/lib/ToolGroup.js @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useMemo } from 'react'; import { injectable } from 'inversify'; import { ToolGroupComponent, ToolGroups } from 'wax-prosemirror-components'; import { v4 as uuidv4 } from 'uuid'; @@ -61,14 +61,20 @@ class ToolGroup { if (this._toolGroups > 0) { return <ToolGroups toolGroups={this._toolGroups} view={view} />; } - return ( - <ToolGroupComponent - key={uuidv4()} - view={view} - tools={this._tools} - title={this.title} - name={name} - /> + + const MemorizedToolGroupComponent = useMemo( + () => ( + <ToolGroupComponent + key={uuidv4()} + view={view} + tools={this._tools} + title={this.title} + name={name} + /> + ), + [view], ); + + return MemorizedToolGroupComponent; } }