Skip to content

Commit

Permalink
feat: expand on content drag (#141)
Browse files Browse the repository at this point in the history
  • Loading branch information
mekedron committed Jun 23, 2021
1 parent 309f95b commit ec733a5
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 4 deletions.
6 changes: 6 additions & 0 deletions README.md
Expand Up @@ -140,6 +140,12 @@ Type: `boolean`
iOS Safari, and some other mobile culprits, can be tricky if you're on a page that has scrolling overflow on `document.body`. Mobile browsers often prefer scrolling the page in these cases instead of letting you handle the touch interaction for UI such as the bottom sheet. Thus it's enabled by default. However it can be a bit agressive and can affect cases where you're putting a drag and drop element inside the bottom sheet. Such as `<input type="range" />` and more. For these cases you can wrap them in a container and give them this data attribute `[data-body-scroll-lock-ignore]` to prevent intervention. Really handy if you're doing crazy stuff like putting mapbox-gl widgets inside bottom sheets.
### expandOnContentDrag
Type: `boolean`
Disabled by default. By default, a user can expand the bottom sheet only by dragging a header or the overlay. This option enables expanding the bottom sheet on the content dragging.
## Events
All events receive `SpringEvent` as their argument. The payload varies, but `type` is always present, which can be `'OPEN' | 'RESIZE' | 'SNAP' | 'CLOSE'` depending on the scenario.
Expand Down
15 changes: 14 additions & 1 deletion pages/fixtures/scrollable.tsx
@@ -1,6 +1,6 @@
import cx from 'classnames'
import type { NextPage } from 'next'
import { useRef } from 'react'
import { useRef, useState } from 'react'
import scrollIntoView from 'smooth-scroll-into-view-if-needed'
import Button from '../../docs/fixtures/Button'
import CloseExample from '../../docs/fixtures/CloseExample'
Expand Down Expand Up @@ -55,6 +55,7 @@ const ScrollableFixturePage: NextPage<GetStaticProps> = ({
meta,
name,
}) => {
const [expandOnContentDrag, setExpandOnContentDrag] = useState(true)
const focusRef = useRef<HTMLButtonElement>()
const sheetRef = useRef<BottomSheetRef>()

Expand Down Expand Up @@ -95,6 +96,7 @@ const ScrollableFixturePage: NextPage<GetStaticProps> = ({
maxHeight / 4,
maxHeight * 0.6,
]}
expandOnContentDrag={expandOnContentDrag}
>
<SheetContent>
<div className="grid grid-cols-3 w-full gap-4">
Expand Down Expand Up @@ -137,6 +139,17 @@ const ScrollableFixturePage: NextPage<GetStaticProps> = ({
Bottom
</Button>
</div>
<div className="grid w-full">
<Button
className={[
' text-sm px-2 py-1',
{ 'text-xl': false, 'px-7': false, 'py-3': false },
]}
onClick={() => setExpandOnContentDrag(!expandOnContentDrag)}
>
{expandOnContentDrag ? 'Disable' : 'Enable'} expand on content drag
</Button>
</div>
<p>
The sheet will always try to set initial focus on the first
interactive element it finds.
Expand Down
52 changes: 50 additions & 2 deletions src/BottomSheet.tsx
Expand Up @@ -68,6 +68,7 @@ export const BottomSheet = React.forwardRef<
onSpringCancel,
onSpringEnd,
reserveScrollBarGap = blocking,
expandOnContentDrag = false,
...props
},
forwardRef
Expand Down Expand Up @@ -102,6 +103,7 @@ export const BottomSheet = React.forwardRef<
// Keeps track of the current height, or the height transitioning to
const heightRef = useRef(0)
const resizeSourceRef = useRef<ResizeSource>()
const preventScrollingRef = useRef(false)

const prefersReducedMotion = useReducedMotion()

Expand Down Expand Up @@ -445,8 +447,40 @@ export const BottomSheet = React.forwardRef<
[send]
)

useEffect(() => {
const elem = scrollRef.current

const preventScrolling = e => {
if (preventScrollingRef.current) {
e.preventDefault()
}
}

const preventSafariOverscroll = e => {
if (elem.scrollTop < 0) {
requestAnimationFrame(() => {
elem.style.overflow = 'hidden'
elem.scrollTop = 0
elem.style.removeProperty('overflow')
})
e.preventDefault()
}
}

if (expandOnContentDrag) {
elem.addEventListener('scroll', preventScrolling)
elem.addEventListener('touchmove', preventScrolling)
elem.addEventListener('touchstart', preventSafariOverscroll)
}
return () => {
elem.removeEventListener('scroll', preventScrolling)
elem.removeEventListener('touchmove', preventScrolling)
elem.removeEventListener('touchstart', preventSafariOverscroll)
}
}, [expandOnContentDrag, scrollRef])

const handleDrag = ({
args: [{ closeOnTap = false } = {}] = [],
args: [{ closeOnTap = false, isContentDragging = false } = {}] = [],
cancel,
direction: [, direction],
down,
Expand Down Expand Up @@ -520,6 +554,20 @@ export const BottomSheet = React.forwardRef<
)
: predictedY

if (expandOnContentDrag && isContentDragging) {
if (newY >= maxSnapRef.current) {
newY = maxSnapRef.current
}

if (memo === maxSnapRef.current && scrollRef.current.scrollTop > 0) {
newY = maxSnapRef.current
}

preventScrollingRef.current = newY < maxSnapRef.current;
} else {
preventScrollingRef.current = false
}

if (first) {
send('DRAG')
}
Expand Down Expand Up @@ -618,7 +666,7 @@ export const BottomSheet = React.forwardRef<
{header}
</div>
)}
<div key="scroll" data-rsbs-scroll ref={scrollRef}>
<div key="scroll" data-rsbs-scroll ref={scrollRef} {...(expandOnContentDrag ? bind({ isContentDragging: true }) : {})}>
<div data-rsbs-content ref={contentRef}>
{children}
</div>
Expand Down
8 changes: 7 additions & 1 deletion src/types.ts
Expand Up @@ -143,7 +143,13 @@ export type Props = {
/**
* Open immediatly instead of initially animating from a closed => open state, useful if the bottom sheet is visible by default and the animation would be distracting
*/
skipInitialTransition?: boolean
skipInitialTransition?: boolean,

/**
* Expand the bottom sheet on the content dragging. By default user can expand the bottom sheet only by dragging the header or overlay. This option enables expanding on dragging the content.
* @default expandOnContentDrag === false
*/
expandOnContentDrag?: boolean,
} & Omit<React.PropsWithoutRef<JSX.IntrinsicElements['div']>, 'children'>

export interface RefHandles {
Expand Down

0 comments on commit ec733a5

Please sign in to comment.