Skip to content

Commit

Permalink
[PAY-1534] Allow Popup to be mounted inside a container (#3669)
Browse files Browse the repository at this point in the history
  • Loading branch information
rickyrombo committed Jul 7, 2023
1 parent deb71db commit 237aab5
Show file tree
Hide file tree
Showing 12 changed files with 73 additions and 38 deletions.
45 changes: 31 additions & 14 deletions apps/audius-client/packages/stems/src/components/Popup/Popup.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {
forwardRef,
MutableRefObject,
RefObject,
useCallback,
useEffect,
useRef,
Expand Down Expand Up @@ -106,7 +106,7 @@ const getComputedOrigins = (
transformOrigin: Origin,
anchorRect: DOMRect,
wrapperRect: DOMRect,
containerRef?: MutableRefObject<HTMLDivElement | undefined>
containerRef?: RefObject<HTMLElement>
) => {
if (!anchorRect || !wrapperRect) return { anchorOrigin, transformOrigin }

Expand Down Expand Up @@ -152,6 +152,7 @@ const getComputedOrigins = (
anchorOrigin.vertical = 'top'
transformOrigin.vertical = 'bottom'
}

return { anchorOrigin, transformOrigin }
}

Expand All @@ -162,12 +163,13 @@ const getComputedOrigins = (
const getAdjustedPosition = (
top: number,
left: number,
wrapperRect: DOMRect
wrapperRect: DOMRect,
containerRect: Pick<DOMRect, 'width' | 'height'>
): { adjustedTop: number; adjustedLeft: number } => {
if (!wrapperRect) return { adjustedTop: 0, adjustedLeft: 0 }

const containerWidth = window.innerWidth - CONTAINER_INSET_PADDING
const containerHeight = window.innerHeight - CONTAINER_INSET_PADDING
const containerWidth = containerRect.width - CONTAINER_INSET_PADDING
const containerHeight = containerRect.height - CONTAINER_INSET_PADDING

const overflowRight = left + wrapperRect.width > containerWidth
const overflowLeft = left < 0
Expand Down Expand Up @@ -256,8 +258,10 @@ export const Popup = forwardRef<HTMLDivElement, PopupProps>(function Popup(
className,
wrapperClassName,
zIndex,
containerRef
containerRef,
mountRef
} = props

const handleClose = useCallback(() => {
onClose()
setTimeout(() => {
Expand Down Expand Up @@ -289,11 +293,19 @@ export const Popup = forwardRef<HTMLDivElement, PopupProps>(function Popup(
// On visible, set the position
useEffect(() => {
if (isVisible) {
const [anchorRect, wrapperRect] = [anchorRef, wrapperRef].map((r) =>
r?.current?.getBoundingClientRect()
)
const [anchorRect, wrapperRect, mountRect] = [
anchorRef,
wrapperRef,
mountRef
].map((r) => r?.current?.getBoundingClientRect())
if (!anchorRect || !wrapperRect) return

// If using a mount source, subtract off the position of the containing mount
if (mountRect) {
anchorRect.x -= mountRect.x
anchorRect.y -= mountRect.y
}

const {
anchorOrigin: anchorOriginComputed,
transformOrigin: transformOriginComputed
Expand Down Expand Up @@ -321,7 +333,11 @@ export const Popup = forwardRef<HTMLDivElement, PopupProps>(function Popup(
const { adjustedTop, adjustedLeft } = getAdjustedPosition(
top,
left,
wrapperRect
wrapperRect,
{
width: containerRef?.current?.clientWidth ?? window.innerWidth,
height: containerRef?.current?.clientHeight ?? window.innerHeight
}
)

if (wrapperRef.current) {
Expand All @@ -339,7 +355,8 @@ export const Popup = forwardRef<HTMLDivElement, PopupProps>(function Popup(
transformOrigin,
setComputedTransformOrigin,
originalTopPosition,
containerRef
containerRef,
mountRef
])

// Callback invoked on each scroll. Uses original top position to scroll with content.
Expand All @@ -348,13 +365,13 @@ export const Popup = forwardRef<HTMLDivElement, PopupProps>(function Popup(
const watchScroll = useCallback(
(scrollParent: Element, initialScrollPosition: number) => {
const scrollTop = scrollParent.scrollTop
if (wrapperRef.current) {
if (wrapperRef.current && !mountRef?.current) {
wrapperRef.current.style.top = `${
originalTopPosition.current - scrollTop + initialScrollPosition
}px`
}
},
[wrapperRef, originalTopPosition]
[wrapperRef, originalTopPosition, mountRef]
)

// Set up scroll listeners
Expand Down Expand Up @@ -452,7 +469,7 @@ export const Popup = forwardRef<HTMLDivElement, PopupProps>(function Popup(
) : null
)}
</div>,
document.body
mountRef?.current ?? document.body
)}
</>
)
Expand Down
13 changes: 9 additions & 4 deletions apps/audius-client/packages/stems/src/components/Popup/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { MutableRefObject, ReactChild } from 'react'
import { ReactNode, RefObject } from 'react'

export enum Position {
TOP_LEFT = 'topLeft',
Expand All @@ -18,7 +18,7 @@ export type PopupProps = {
/**
* A ref to the element whose position will be used to anchor the Popup
*/
anchorRef: MutableRefObject<HTMLElement | null>
anchorRef: RefObject<HTMLElement>

/**
* Duration of the animations in ms
Expand All @@ -35,7 +35,7 @@ export type PopupProps = {
/**
* Children to render inside the Popup
*/
children: ReactChild
children: ReactNode

/**
* Class name to apply to the popup itself
Expand All @@ -52,7 +52,7 @@ export type PopupProps = {
* to be the size of the container it belongs to. If the popup expands outside
* the bounds of the container, it repositions itself.
*/
containerRef?: MutableRefObject<HTMLDivElement | undefined>
containerRef?: RefObject<HTMLElement>

/**
* Boolean representing whether the Popup is visible
Expand Down Expand Up @@ -110,6 +110,11 @@ export type PopupProps = {
* An optional z-index to override the default of 10000
*/
zIndex?: number

/**
* The element to portal to
*/
mountRef?: RefObject<HTMLElement>
}

export const popupDefaultProps: Partial<PopupProps> = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ export const PopupMenu = forwardRef<HTMLDivElement, PopupMenuProps>(
containerRef,
anchorOrigin,
transformOrigin,
id
id,
mountRef
} = props
const clickInsideRef = useRef<any>()
const anchorRef = useRef<HTMLElement>(null)
Expand Down Expand Up @@ -83,6 +84,7 @@ export const PopupMenu = forwardRef<HTMLDivElement, PopupMenuProps>(
transformOrigin={transformOrigin}
anchorOrigin={anchorOrigin}
wrapperClassName={styles.popup}
mountRef={mountRef}
>
<ul
className={styles.menu}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type ApplicablePopupProps = Pick<
| 'containerRef'
| 'transformOrigin'
| 'anchorOrigin'
| 'mountRef'
>

export type PopupMenuProps = {
Expand Down
4 changes: 3 additions & 1 deletion apps/audius-client/packages/stems/src/utils/scrollParent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ export const getScrollParent = (node: HTMLElement): Element | null => {
style(_node, 'overflow') +
style(_node, 'overflow-y') +
style(_node, 'overflow-x')
const scroll = (_node: HTMLElement) => regex.test(overflow(_node))
const scroll = (_node: HTMLElement) =>
regex.test(overflow(_node)) ||
_node.classList.contains('scrollbar-container') // Perfect scrollbar does overflow: hidden

const scrollParent = (_node: HTMLElement) => {
if (!_node.parentNode) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ type AudioTransactionsTableProps = {
tableClassName?: string
wrapperClassName?: string
totalRowCount: number
scrollRef?: React.MutableRefObject<HTMLDivElement | undefined>
scrollRef?: React.RefObject<HTMLDivElement>
fetchBatchSize: number
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ type TableProps = {
onSort?: (...props: any[]) => void
isEmptyRow?: (row: any) => boolean
pageSize?: number
scrollRef?: React.MutableRefObject<HTMLDivElement | undefined>
scrollRef?: React.RefObject<HTMLDivElement>
showMoreLimit?: number
tableClassName?: string
totalRowCount?: number
Expand Down Expand Up @@ -663,7 +663,7 @@ export const Table = ({
minimumBatchSize={fetchBatchSize}
>
{({ onRowsRendered, registerChild: registerListChild }) => (
<WindowScroller scrollElement={scrollRef?.current}>
<WindowScroller scrollElement={scrollRef?.current ?? undefined}>
{({
height,
registerChild,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { MouseEvent, useCallback, useMemo, useRef } from 'react'
import React, { MouseEvent, useCallback, useMemo, useRef } from 'react'

import {
formatCount,
Expand Down Expand Up @@ -96,7 +96,7 @@ type TracksTableProps = {
playing?: boolean
playingIndex?: number
removeText?: string
scrollRef?: React.MutableRefObject<HTMLDivElement | undefined>
scrollRef?: React.RefObject<HTMLDivElement>
showMoreLimit?: number
tableClassName?: string
totalRowCount?: number
Expand Down
4 changes: 2 additions & 2 deletions apps/audius-client/packages/web/src/pages/App.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { MutableRefObject } from 'react'
import { RefObject } from 'react'

type AppProps = {
mainContentRef: MutableRefObject<HTMLDivElement | undefined>
mainContentRef: RefObject<HTMLDivElement>
}

const App: (props: AppProps) => JSX.Element
Expand Down
7 changes: 2 additions & 5 deletions apps/audius-client/packages/web/src/pages/App.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createRef, MutableRefObject } from 'react'
import { createRef } from 'react'

import { ConnectedRouter } from 'connected-react-router'
import ReactDOM from 'react-dom'
Expand Down Expand Up @@ -30,10 +30,7 @@ jest.mock('services/solana-client/SolanaClient', () => ({
describe('smoke test', () => {
it('renders without crashing', () => {
const rootNode = document.createElement('div')
const mainContentRef =
createRef<HTMLDivElement | null>() as MutableRefObject<
HTMLDivElement | undefined
>
const mainContentRef = createRef<HTMLDivElement>()
ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}>
Expand Down
10 changes: 6 additions & 4 deletions apps/audius-client/packages/web/src/pages/MainContentContext.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { createContext, memo, useRef, MutableRefObject } from 'react'
import { createContext, memo, useRef, RefObject } from 'react'

export const MainContentContext = createContext({
mainContentRef: {} as MutableRefObject<HTMLDivElement | undefined>
export const MainContentContext = createContext<{
mainContentRef: RefObject<HTMLDivElement>
}>({
mainContentRef: { current: null }
})

export const MainContentContextProvider = memo(
(props: { children: JSX.Element }) => {
const mainContentRef = useRef<HTMLDivElement>()
const mainContentRef = useRef<HTMLDivElement>(null)
return (
<MainContentContext.Provider
value={{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useEffect } from 'react'
import { useCallback, useEffect, useRef } from 'react'

import {
accountSelectors,
Expand Down Expand Up @@ -104,6 +104,8 @@ export const MessageUserSearchResult = (props: UserResultComposeProps) => {
getCanCreateChat(state, { userId: user.user_id })
)

const ref = useRef<HTMLDivElement | null>(null)

const handleComposeClicked = useCallback(() => {
if (canCreateChat) {
closeParentModal()
Expand Down Expand Up @@ -173,7 +175,12 @@ export const MessageUserSearchResult = (props: UserResultComposeProps) => {
}

return (
<div className={styles.root}>
<div
className={styles.root}
ref={(rootRef) =>
(ref.current = (rootRef?.parentElement as HTMLDivElement) ?? null)
}
>
<ArtistChip
className={styles.artistChip}
user={user}
Expand All @@ -188,6 +195,8 @@ export const MessageUserSearchResult = (props: UserResultComposeProps) => {
zIndex={zIndex.MODAL_OVERFLOW_MENU_POPUP}
anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}
transformOrigin={{ horizontal: 'right', vertical: 'top' }}
mountRef={ref}
containerRef={ref}
/>
</div>
)
Expand Down

0 comments on commit 237aab5

Please sign in to comment.