mirror of
https://github.com/overleaf/overleaf.git
synced 2025-03-22 02:04:31 +00:00
Merge pull request #16857 from overleaf/ii-share-modal-click
[web] Clickable share modal buttons GitOrigin-RevId: db6bef9b63e98007741d88b291a282d6ea4a68c5
This commit is contained in:
parent
33bc09cf09
commit
2f803292b4
5 changed files with 126 additions and 15 deletions
|
@ -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> </span>
|
||||
<Button type="submit" bsStyle="primary">
|
||||
<ClickableElementEnhancer
|
||||
as={Button}
|
||||
onClick={handleSubmit}
|
||||
bsStyle="primary"
|
||||
>
|
||||
{t('share')}
|
||||
</Button>
|
||||
</ClickableElementEnhancer>
|
||||
</div>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue