Merge pull request #16857 from overleaf/ii-share-modal-click

[web] Clickable share modal buttons

GitOrigin-RevId: db6bef9b63e98007741d88b291a282d6ea4a68c5
This commit is contained in:
ilkin-overleaf 2024-02-05 13:47:01 +02:00 committed by Copybot
parent 33bc09cf09
commit 2f803292b4
5 changed files with 126 additions and 15 deletions

View file

@ -1,4 +1,4 @@
import { useState, useMemo } from 'react'
import { useState, useMemo, useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { Form, FormGroup, FormControl, Button } from 'react-bootstrap'
import { useMultipleSelection } from 'downshift'
@ -9,6 +9,7 @@ import { useUserContacts } from '../hooks/use-user-contacts'
import useIsMounted from '../../../shared/hooks/use-is-mounted'
import { useProjectContext } from '../../../shared/context/project-context'
import { sendMB } from '../../../infrastructure/event-tracking'
import ClickableElementEnhancer from '@/shared/components/clickable-element-enhancer'
export default function AddCollaborators() {
const [privileges, setPrivileges] = useState('readAndWrite')
@ -45,9 +46,7 @@ export default function AddCollaborators() {
const { reset, selectedItems } = multipleSelectionProps
async function handleSubmit(event) {
event.preventDefault()
const handleSubmit = useCallback(async () => {
if (!selectedItems.length) {
return
}
@ -125,10 +124,22 @@ export default function AddCollaborators() {
}
setInFlight(false)
}
}, [
currentMemberEmails,
invites,
isMounted,
members,
privileges,
projectId,
reset,
selectedItems,
setError,
setInFlight,
updateProject,
])
return (
<Form onSubmit={handleSubmit}>
<Form>
<FormGroup>
<SelectCollaborators
loading={!nonMemberContacts}
@ -151,9 +162,13 @@ export default function AddCollaborators() {
<option value="readOnly">{t('read_only')}</option>
</FormControl>
<span>&nbsp;&nbsp;</span>
<Button type="submit" bsStyle="primary">
<ClickableElementEnhancer
as={Button}
onClick={handleSubmit}
bsStyle="primary"
>
{t('share')}
</Button>
</ClickableElementEnhancer>
</div>
</FormGroup>
</Form>

View file

@ -5,6 +5,7 @@ import AccessibleModal from '../../../shared/components/accessible-modal'
import { useEditorContext } from '../../../shared/context/editor-context'
import { lazy, Suspense } from 'react'
import { FullSizeLoadingSpinner } from '@/shared/components/loading-spinner'
import ClickableElementEnhancer from '@/shared/components/clickable-element-enhancer'
const ReadOnlyTokenLink = lazy(() =>
import('./link-sharing').then(({ ReadOnlyTokenLink }) => ({
@ -63,15 +64,16 @@ export default function ShareProjectModalContent({
</div>
<div className="modal-footer-right">
<Button
type="button"
<ClickableElementEnhancer
onClick={cancel}
as={Button}
type="button"
bsStyle={null}
className="btn-secondary"
disabled={inFlight}
>
{t('close')}
</Button>
</ClickableElementEnhancer>
</div>
</Modal.Footer>
</AccessibleModal>

View file

@ -0,0 +1,75 @@
import { useRef, useEffect } from 'react'
import PolymorphicComponent, {
PolymorphicComponentProps,
} from '@/shared/components/polymorphic-component'
import { MergeAndOverride } from '../../../../types/utils'
// Performs a click event on elements that has been clicked,
// but when releasing the mouse button are no longer hovered
// by the cursor (which by default cancels the event).
type ClickableElementEnhancerOwnProps = {
onClick: () => void
onMouseDown?: (e: React.MouseEvent) => void
offset?: number
}
type ClickableElementEnhancerProps<E extends React.ElementType> =
MergeAndOverride<
PolymorphicComponentProps<E>,
ClickableElementEnhancerOwnProps
>
function ClickableElementEnhancer<E extends React.ElementType>({
onClick,
onMouseDown,
offset = 50, // the offset around the clicked element which should still trigger the click
...rest
}: ClickableElementEnhancerProps<E>) {
const isClickedRef = useRef(false)
const elRectRef = useRef<DOMRect>()
const restProps = rest as PolymorphicComponentProps<E>
const handleMouseDown = (e: React.MouseEvent) => {
isClickedRef.current = true
elRectRef.current = (e.target as HTMLElement).getBoundingClientRect()
onMouseDown?.(e)
}
useEffect(() => {
const handleMouseUp = (e: MouseEvent) => {
if (isClickedRef.current) {
isClickedRef.current = false
if (!elRectRef.current) {
return
}
const halfWidth = elRectRef.current.width / 2
const halfHeight = elRectRef.current.height / 2
const centerX = elRectRef.current.x + halfWidth
const centerY = elRectRef.current.y + halfHeight
const deltaX = Math.abs(e.clientX - centerX)
const deltaY = Math.abs(e.clientY - centerY)
// Check if the mouse has moved significantly from the element position
if (deltaX < halfWidth + offset && deltaY < halfHeight + offset) {
// If the mouse hasn't moved much, consider it a click
onClick()
}
}
}
document.addEventListener('mouseup', handleMouseUp)
return () => {
document.removeEventListener('mouseup', handleMouseUp)
}
}, [onClick, offset])
return <PolymorphicComponent onMouseDown={handleMouseDown} {...restProps} />
}
export default ClickableElementEnhancer

View file

@ -0,0 +1,19 @@
import { MergeAndOverride } from '../../../../types/utils'
type PolymorphicComponentOwnProps<E extends React.ElementType> = {
as?: E
}
export type PolymorphicComponentProps<E extends React.ElementType> =
MergeAndOverride<React.ComponentProps<E>, PolymorphicComponentOwnProps<E>>
function PolymorphicComponent<E extends React.ElementType = 'div'>({
as,
...props
}: PolymorphicComponentProps<E>) {
const Component = as || 'div'
return <Component {...props} />
}
export default PolymorphicComponent

View file

@ -124,8 +124,8 @@ describe('<ShareProjectModal/>', function () {
{ name: 'Close' }
)
fireEvent.click(headerCloseButton)
fireEvent.click(footerCloseButton)
await userEvent.click(headerCloseButton)
await userEvent.click(footerCloseButton)
expect(handleHide.callCount).to.equal(2)
})
@ -620,7 +620,7 @@ describe('<ShareProjectModal/>', function () {
fireEvent.change(privilegesElement, { target: { value: 'readOnly' } })
const submitButton = screen.getByRole('button', { name: 'Share' })
submitButton.click()
await userEvent.click(submitButton)
let calls
await waitFor(
@ -713,7 +713,7 @@ describe('<ShareProjectModal/>', function () {
)
expect(submitButton.disabled).to.be.false
submitButton.click()
await userEvent.click(submitButton)
await fetchMock.flush(true)
expect(fetchMock.done()).to.be.true
}