feat: Added a conflict resolution dialog and a Conflicts view for easier management of conflicts (#2337)

This commit is contained in:
Aman Harwara
2023-06-25 14:27:51 +05:30
committed by GitHub
parent 49d43fd14b
commit e0e9249334
48 changed files with 1201 additions and 94 deletions

View File

@@ -7,13 +7,18 @@ import { getAppRect, getPopoverMaxHeight, getPositionedPopoverRect } from './Uti
const percentOf = (percent: number, value: number) => (percent / 100) * value
export type PopoverCSSProperties = CSSProperties & {
'--translate-x': string
'--translate-y': string
}
const getStylesFromRect = (
rect: DOMRect,
options: {
disableMobileFullscreenTakeover?: boolean
maxHeight?: number | 'none'
},
): CSSProperties => {
): PopoverCSSProperties => {
const { disableMobileFullscreenTakeover = false, maxHeight = 'none' } = options
const canApplyMaxHeight = maxHeight !== 'none' && (!isMobileScreen() || disableMobileFullscreenTakeover)
@@ -22,7 +27,9 @@ const getStylesFromRect = (
return {
willChange: 'transform',
transform: `translate(${shouldApplyMobileWidth ? marginForMobile / 2 : rect.x}px, ${rect.y}px)`,
'--translate-x': `${shouldApplyMobileWidth ? marginForMobile / 2 : rect.x}px`,
'--translate-y': `${rect.y}px`,
transform: 'translate(var(--translate-x), var(--translate-y))',
visibility: 'visible',
...(canApplyMaxHeight && {
maxHeight: `${maxHeight}px`,
@@ -53,7 +60,7 @@ export const getPositionedPopoverStyles = ({
disableMobileFullscreenTakeover,
maxHeightFunction,
offset,
}: Options): CSSProperties | null => {
}: Options): PopoverCSSProperties | null => {
if (!popoverRect || !anchorRect) {
return null
}

View File

@@ -46,6 +46,8 @@ const Popover = ({
disableMobileFullscreenTakeover,
maxHeight,
portal,
offset,
hideOnClickInModal,
}: Props) => {
const popoverId = useRef(UuidGenerator.GenerateUuid())
@@ -126,6 +128,8 @@ const Popover = ({
title={title}
togglePopover={togglePopover}
portal={portal}
offset={offset}
hideOnClickInModal={hideOnClickInModal}
>
{children}
</PositionedPopoverContent>

View File

@@ -1,14 +1,15 @@
import { useDocumentRect } from '@/Hooks/useDocumentRect'
import { useAutoElementRect } from '@/Hooks/useElementRect'
import { classNames } from '@standardnotes/utils'
import { useCallback, useLayoutEffect, useState } from 'react'
import { CSSProperties, useCallback, useLayoutEffect, useState } from 'react'
import Portal from '../Portal/Portal'
import { getPositionedPopoverStyles } from './GetPositionedPopoverStyles'
import { PopoverCSSProperties, getPositionedPopoverStyles } from './GetPositionedPopoverStyles'
import { PopoverContentProps } from './Types'
import { usePopoverCloseOnClickOutside } from './Utils/usePopoverCloseOnClickOutside'
import { useDisableBodyScrollOnMobile } from '@/Hooks/useDisableBodyScrollOnMobile'
import { MediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery'
import { KeyboardKey } from '@standardnotes/ui-services'
import { getAdjustedStylesForNonPortalPopover } from './Utils/getAdjustedStylesForNonPortal'
const PositionedPopoverContent = ({
align = 'end',
@@ -25,6 +26,8 @@ const PositionedPopoverContent = ({
disableMobileFullscreenTakeover,
maxHeight,
portal = true,
offset,
hideOnClickInModal = false,
}: PopoverContentProps) => {
const [popoverElement, setPopoverElement] = useState<HTMLDivElement | null>(null)
const popoverRect = useAutoElementRect(popoverElement)
@@ -47,13 +50,21 @@ const PositionedPopoverContent = ({
side,
disableMobileFullscreenTakeover: disableMobileFullscreenTakeover,
maxHeightFunction: maxHeight,
offset,
})
let adjustedStyles: PopoverCSSProperties | undefined = undefined
if (!portal && popoverElement && styles) {
adjustedStyles = getAdjustedStylesForNonPortalPopover(popoverElement, styles)
}
usePopoverCloseOnClickOutside({
popoverElement,
anchorElement,
togglePopover,
childPopovers,
hideOnClickInModal,
disabled: disableClickOutside,
})
@@ -83,9 +94,12 @@ const PositionedPopoverContent = ({
isDesktopScreen || disableMobileFullscreenTakeover ? 'invisible' : '',
className,
)}
style={{
...styles,
}}
style={
{
...styles,
...adjustedStyles,
} as CSSProperties
}
ref={setPopoverElement}
data-popover={id}
onKeyDown={(event) => {

View File

@@ -42,6 +42,8 @@ type CommonPopoverProps = {
disableMobileFullscreenTakeover?: boolean
title: string
portal?: boolean
offset?: number
hideOnClickInModal?: boolean
}
export type PopoverContentProps = CommonPopoverProps & {

View File

@@ -95,25 +95,25 @@ export const getPositionedPopoverRect = (
if (side === 'top' || side === 'bottom') {
switch (align) {
case 'start':
positionPopoverRect.x = buttonRect.left - finalOffset
positionPopoverRect.x = buttonRect.left
break
case 'center':
positionPopoverRect.x = buttonRect.left - width / 2 + buttonRect.width / 2 - finalOffset
positionPopoverRect.x = buttonRect.left - width / 2 + buttonRect.width / 2
break
case 'end':
positionPopoverRect.x = buttonRect.right - width + finalOffset
positionPopoverRect.x = buttonRect.right - width
break
}
} else {
switch (align) {
case 'start':
positionPopoverRect.y = buttonRect.top - finalOffset
positionPopoverRect.y = buttonRect.top
break
case 'center':
positionPopoverRect.y = buttonRect.top - height / 2 + buttonRect.height / 2 - finalOffset
positionPopoverRect.y = buttonRect.top - height / 2 + buttonRect.height / 2
break
case 'end':
positionPopoverRect.y = buttonRect.bottom - height + finalOffset
positionPopoverRect.y = buttonRect.bottom - height
break
}
}

View File

@@ -0,0 +1,19 @@
export function getAbsolutePositionedParent(element: HTMLElement | null): HTMLElement | null {
if (!element) {
return null
}
const parent = element.parentElement
if (!parent) {
return null
}
const position = window.getComputedStyle(parent).getPropertyValue('position')
if (position === 'absolute') {
return parent
}
return getAbsolutePositionedParent(parent)
}

View File

@@ -0,0 +1,26 @@
import { PopoverCSSProperties } from '../GetPositionedPopoverStyles'
import { getAbsolutePositionedParent } from './getAbsolutePositionedParent'
export const getAdjustedStylesForNonPortalPopover = (popoverElement: HTMLElement, styles: PopoverCSSProperties) => {
const absoluteParent = getAbsolutePositionedParent(popoverElement)
const translateXProperty = styles?.['--translate-x']
const translateYProperty = styles?.['--translate-y']
const parsedTranslateX = translateXProperty ? parseInt(translateXProperty) : 0
const parsedTranslateY = translateYProperty ? parseInt(translateYProperty) : 0
if (!absoluteParent) {
return styles
}
const parentRect = absoluteParent.getBoundingClientRect()
const adjustedTranslateX = parsedTranslateX - parentRect.left
const adjustedTranslateY = parsedTranslateY - parentRect.top
return {
...styles,
'--translate-x': `${adjustedTranslateX}px`,
'--translate-y': `${adjustedTranslateY}px`,
} as PopoverCSSProperties
}

View File

@@ -6,6 +6,7 @@ type Options = {
togglePopover?: () => void
childPopovers: Set<string>
disabled?: boolean
hideOnClickInModal?: boolean
}
export const usePopoverCloseOnClickOutside = ({
@@ -14,6 +15,7 @@ export const usePopoverCloseOnClickOutside = ({
togglePopover,
childPopovers,
disabled,
hideOnClickInModal = false,
}: Options) => {
useEffect(() => {
const closeIfClickedOutside = (event: MouseEvent) => {
@@ -26,7 +28,12 @@ export const usePopoverCloseOnClickOutside = ({
const isPopoverInModal = popoverElement?.closest('[data-dialog]')
const isDescendantOfModal = isPopoverInModal ? false : !!target.closest('[data-dialog]')
if (!isDescendantOfMenu && !isAnchorElement && !isDescendantOfChildPopover && !isDescendantOfModal) {
if (
!isDescendantOfMenu &&
!isAnchorElement &&
!isDescendantOfChildPopover &&
(!isDescendantOfModal || (isDescendantOfModal && hideOnClickInModal))
) {
if (!disabled) {
togglePopover?.()
}
@@ -39,5 +46,5 @@ export const usePopoverCloseOnClickOutside = ({
document.removeEventListener('click', closeIfClickedOutside, { capture: true })
document.removeEventListener('contextmenu', closeIfClickedOutside, { capture: true })
}
}, [anchorElement, childPopovers, popoverElement, togglePopover, disabled])
}, [anchorElement, childPopovers, popoverElement, togglePopover, disabled, hideOnClickInModal])
}