Giter Site home page Giter Site logo

stipsan / react-spring-bottom-sheet Goto Github PK

View Code? Open in Web Editor NEW
910.0 7.0 121.0 1.69 MB

Accessible โ™ฟ๏ธ, Delightful โœจ, & Fast ๐Ÿš€

Home Page: https://react-spring.bottom-sheet.dev/

License: MIT License

TypeScript 94.15% JavaScript 2.43% CSS 3.41%
react animation ux bottom-sheet bottomsheet sheet modal overlay popup dialog

react-spring-bottom-sheet's Introduction

npm stat npm version gzip size size module formats: cjs, es, and modern

Logo with the text Accessible, Delightful and Performant

react-spring-bottom-sheet is built on top of react-spring and react-use-gesture. It busts the myth that accessibility and supporting keyboard navigation and screen readers are allegedly at odds with delightful, beautiful, and highly animated UIs. Every animation and transition use CSS custom properties instead of manipulating them directly, allowing complete control over the experience from CSS alone.

Installation

npm i react-spring-bottom-sheet

Getting started

Basic usage

import { useState } from 'react'
import { BottomSheet } from 'react-spring-bottom-sheet'

// if setting up the CSS is tricky, you can add this to your page somewhere:
// <link rel="stylesheet" href="https://unpkg.com/react-spring-bottom-sheet/dist/style.css" crossorigin="anonymous">
import 'react-spring-bottom-sheet/dist/style.css'

export default function Example() {
  const [open, setOpen] = useState(false)
  return (
    <>
      <button onClick={() => setOpen(true)}>Open</button>
      <BottomSheet open={open}>My awesome content here</BottomSheet>
    </>
  )
}

TypeScript

TS support is baked in, and if you're using the snapTo API use BottomSheetRef:

import { useRef } from 'react'
import { BottomSheet, BottomSheetRef } from 'react-spring-bottom-sheet'

export default function Example() {
  const sheetRef = useRef<BottomSheetRef>()
  return (
    <BottomSheet open ref={sheetRef}>
      <button
        onClick={() => {
          // Full typing for the arguments available in snapTo, yay!!
          sheetRef.current.snapTo(({ maxHeight }) => maxHeight)
        }}
      >
        Expand to full height
      </button>
    </BottomSheet>
  )
}

Customizing the CSS

Using CSS Custom Properties

These are all the variables available to customize the look and feel when using the provided CSS.

:root {
  --rsbs-backdrop-bg: rgba(0, 0, 0, 0.6);
  --rsbs-bg: #fff;
  --rsbs-handle-bg: hsla(0, 0%, 0%, 0.14);
  --rsbs-max-w: auto;
  --rsbs-ml: env(safe-area-inset-left);
  --rsbs-mr: env(safe-area-inset-right);
  --rsbs-overlay-rounded: 16px;
}

Custom CSS

It's recommended that you copy from style.css into your own project, and add this to your postcss.config.js setup (npm i postcss-custom-properties-fallback):

module.exports = {
  plugins: {
    // Ensures the default variables are available
    'postcss-custom-properties-fallback': {
      importFrom: require.resolve('react-spring-bottom-sheet/defaults.json'),
    },
  },
}

View demo code

MVP example, showing what you get by implementing open, onDismiss and a single snap point always set to minHeight.

View demo code

A more elaborate example that showcases how snap points work. It also shows how it behaves if you want it to be open by default, and not closable. Notice how it responds if you resize the window, or scroll to the bottom and starts adjusting the height of the sheet without scrolling back up first.

View demo code

If you provide either a header or footer prop you'll enable the special behavior seen in this example. And they're not just sticky positioned, both areas support touch gestures.

View demo code

In most cases you use a bottom sheet the same way you do with a dialog: you want it to overlay the page and block out distractions. But there are times when you want a bottom sheet but without it taking all the attention and overlaying the entire page. Providing blocking={false} helps this use case. By doing so you disable a couple of behaviors that are there for accessibility (focus-locking and more) that prevents a screen reader or a keyboard user from accidentally leaving the bottom sheet.

API

props

All props you provide, like className, style props or whatever else are spread onto the underlying <animated.div> instance, that you can style in your custom CSS using this selector: [data-rsbs-root]. Just note that the component is mounted in a @reach/portal at the bottom of <body>, and not in the DOM hierarchy you render it in.

open

Type: boolean

The only required prop, beyond children. And it's controlled, so if you don't set this to false then it's not possible to close the bottom sheet. It's worth knowing that the bottom sheet won't render anything but a @reach/dialog placeholder while open is false. Thus ensure your components behave as expected with being unmounted when the sheet closed. We can't really allow it to render and mount while in a closed/hidden position as there's no stable way of preventing keyboard users or screen readers from accidentally interacting with the closed bottom sheet as long as it's in the dom. This is especially problematic given it implements ARIA to optimize for a11y.

onDismiss

Type: () => void

Called when the user do something that signal they want to dismiss the sheet:

  • hit the esc key.
  • tap on the backdrop.
  • swipes the sheet to the bottom of the viewport.

snapPoints

Type: (state) => number | number[]

This function should be pure as it's called often. You can choose to provide a single value or an array of values to customize the behavior. The state contains these values:

  • headerHeight โ€“ the current measured height of the header.
  • footerHeight โ€“ if a footer prop is provided then this is its height.
  • height โ€“ the current height of the sheet.
  • minHeight โ€“ the minimum height needed to avoid a scrollbar. If there's not enough height available to avoid it then this will be the same as maxHeight.
  • maxHeight โ€“ the maximum available height on the page, equivalent to window.innerHeight and 100vh.
<BottomSheet
  // Allow the user to select between minimun height to avoid a scrollbar, and fullscren
  snapPoints={({ minHeight, maxHeight }) => [minHeight, maxHeight]}
/>

defaultSnap

Type: number | (state) => number

Provide either a number, or a callback returning a number for the default position of the sheet when it opens. state use the same arguments as snapPoints, plus two more values: snapPoints and lastSnap.

<BottomSheet
  // the first snap points height depends on the content, while the second one is equivalent to 60vh
  snapPoints={({ minHeight, maxHeight }) => [minHeight, maxHeight / 0.6]}
  // Opens the largest snap point by default, unless the user selected one previously
  defaultSnap={({ lastSnap, snapPoints }) =>
    lastSnap ?? Math.max(...snapPoints)
  }
/>

header

Type: ReactNode

Supports the same value type as the children prop.

footer

Type: ReactNode

Supports the same value type as the children prop.

sibling

Type: ReactNode

Supports the same value type as the sibling prop. Renders the node as a child of [data-rsbs-root], but as a sibling to [data-rsbs-backdrop] and [data-rsbs-overlay]. This allows you to access the animation state and render elements on top of the bottom sheet, while being outside the overlay itself.

initialFocusRef

Type: React.Ref | false

A react ref to the element you want to get keyboard focus when opening. If not provided it's automatically selecting the first interactive element it finds. If set to false keyboard focus when opening is disabled.

blocking

Type: boolean

Enabled by default. Enables focus trapping of keyboard navigation, so you can't accidentally tab out of the bottom sheet and into the background. Also sets aria-hidden on the rest of the page to prevent Screen Readers from escaping as well.

scrollLocking

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.

onSpringStart

Type: (event: SpringEvent) => void

Fired on: OPEN | RESIZE | SNAP | CLOSE.

If you need to delay the open animation until you're ready, perhaps you're loading some data and showing an inline spinner meanwhile. You can return a Promise or use an async function to make the bottom sheet wait for your work to finish before it starts the open transition.

function Example() {
  const [data, setData] = useState([])
  return (
    <BottomSheet
      onSpringStart={async (event) => {
        if (event.type === 'OPEN') {
          // the bottom sheet gently waits
          const data = await fetch(/* . . . */)
          setData(data)
          // and now we can proceed
        }
      }}
    >
      {data.map(/* . . . */)}
    </BottomSheet>
  )
}

onSpringCancel

Type: (event: SpringEvent) => void

Fired on: OPEN | CLOSE.

OPEN

In order to be as fluid and delightful as possible, the open state can be interrupted and redirected by the user without waiting for the open transition to complete. Maybe they changed their mind and decided to close the sheet because they tapped a button by mistake. This interruption can happen in a number of ways:

  • the user swipes the sheet below the fold, triggering an onDismiss event.
  • the user hits the esc key, triggering an onDismiss event.
  • the parent component sets open to false before finishing the animation.
  • a RESIZE event happens, like when an Android device shows its soft keyboard when an text editable input receives focus, as it changes the viewport height.

CLOSE

If the user reopens the sheet before it's done animating it'll trigger this event. Most importantly though it can fire if the bottom sheet is unmounted without enough time to clean animate itself out of the view before it rolls back things like body-scroll-lock, focus-trap and more. It'll still clean itself up even if React decides to be rude about it. But this also means that the event can fire after the component is unmounted, so you should avoid calling setState or similar without checking for the mounted status of your own wrapper component.

RESIZE

Type: { source: 'window' | 'maxheightprop' | 'element }

Fires whenever there's been a window resize event, or if the header, footer or content have changed its height in such a way that the valid snap points have changed. source tells you what caused the resize. If the resize comes from a window.onresize event it's set to 'window'. 'maxheightprop' is if the maxHeight prop is used, and is fired whenever it changes. And 'element' is whenever the header, footer or content resize observers detect a change.

SNAP

Type: { source: 'dragging' | 'custom' | string }

Fired after dragging ends, or when calling ref.snapTo, and a transition to a valid snap point is happening.

source is 'dragging' if the snapping is responding to a drag gesture that just ended. And it's set to 'custom' when using ref.snapTo.

function Example() {
  return (
    <BottomSheet
      onSpringStart={(event) => {
        if (event.type === 'SNAP' && event.source === 'dragging') {
          console.log('Starting a spring animation to user selected snap point')
        }
      }}
    />
  )
}

When using snapTo it's possible to use a different source than 'custom':

function Example() {
  const sheetRef = useRef()
  return (
    <BottomSheet
      ref={sheetRef}
      snapPoints={({ minHeight, maxHeight }) => [minHeight, maxHeight]}
      onSpringEnd={(event) => {
        if (event.type === 'SNAP' && event.source === 'snap-to-bottom') {
          console.log(
            'Just finished an imperativ transition to the bottom snap point'
          )
        }
      }}
    >
      <button
        onClick={() => sheetRef.current.snapTo(0, { source: 'snap-to-bottom' })}
      >
        Snap to bottom
      </button>
    </BottomSheet>
  )
}

onSpringEnd

Type: (event: SpringEvent) => void

Fired on: CLOSE.

The yin to onSpringStart's yang. It has the same characteristics. Including async/await and Promise support for delaying a transition. For CLOSE it gives you a hook into the step right after it has cleaned up everything after itself, and right before it unmounts itself. This can be useful if you have some logic that needs to perform some work before it's safe to unmount.

skipInitialTransition

Type: boolean

By default the initial open state is always transitioned to using an spring animation. Set skipInitialTransition to true and the initial open state will render as if it were the default state. Useful to avoid scenarios where the opening transition would be distracting.

ref

Methods available when setting a ref on the sheet:

export default function Example() {
  const sheetRef = React.useRef()
  return <BottomSheet open ref={sheetRef} />
}

snapTo

Type: (numberOrCallback: number | (state => number)) => void, options?: {source?: string, velocity?: number}

Same signature as the defaultSnap prop, calling it will animate the sheet to the new snap point you return. You can either call it with a number, which is the height in px (it'll select the closest snap point that matches your value): ref.current.snapTo(200). Or:

ref.current.snapTo(({ // Showing all the available props
  headerHeight, footerHeight, height, minHeight, maxHeight, snapPoints, lastSnap }) =>
  // Selecting the largest snap point, if you give it a number that doesn't match a snap point then it'll
  // select whichever snap point is nearest the value you gave
  Math.max(...snapPoints)
)

There's an optional second argument you can use to override event.source, as well as changing the velocity:

ref.current.snapTo(({ snapPoints }) => Math.min(...snapPoints), {
  // Each property is optional, here showing their default values
  source: 'custom',
  velocity: 1,
})

height

Type: number

The current snap point, in other words the height, of the bottom sheet. This value is updated outside the React render cycle, for performance reasons.

export default function Example() {
  const sheetRef = React.useRef()
  return (
    <BottomSheet
      ref={sheetRef}
      onSpringStart={() => {
        console.log('Transition from:', sheetRef.current.height)
        requestAnimationFrame(() =>
          console.log('Transition to:', sheetRef.current.height)
        )
      }}
      onSpringEnd={() =>
        console.log('Finished transition to:', sheetRef.current.height)
      }
    />
  )
}

Credits

react-spring-bottom-sheet's People

Contributors

fullstackprincess avatar imgbot[bot] avatar mekedron avatar mikemajara avatar renovate-bot avatar renovate[bot] avatar semantic-release-bot avatar stipsan avatar varyoo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

react-spring-bottom-sheet's Issues

Question - Dynamic Blocking Prop

Hi, I want to change the blocking prop based on whether the bottom sheet is showing only the header or not. Essentially, I consider the bottom sheet "open" if more than just the header is shown and "closed" if only the header is shown. When the bottom sheet is "open" in my case, I want it blocking the background, but I don't want it blocking when it is "closed".

If I use the onSpringEnd event, the blocking change is very delayed.

If I use a combination of onSpringStart, onSpringCancel and onSpringEnd, I get a very janky blocking transition.

Demo Code

**try barely dragging the header to get the weird effects.

What is the better way to accomplish this?

The bottom sheet works perfectly like I expect, if there is no header and open prop is toggled.

drag events going "through" the bottom sheet

I have elements on my page that can be dragged. When the bottom sheet is open and I want to close it, those events still pass through the bottom sheet and move elements.

I am unsure to see how I can stop this behavior. I've tried blocking={true} and various other event.preventDefault/stopPropagation related things :)

TypeError: u is not a function

Hi there,

I'm losing my mind over an issue that I've managed to track back to react-spring-bottom-sheet, but I haven't been able to solve yet, preventing the usage of this library, even if using demo code.

Framework: NextJS 12.1.2
react-spring-bottom-sheet version: 3.4.0
The issue is in production only using Vercel.

The stack trace is very poor, but that's all I have.

instrument.js:110 TypeError: u is not a function
    at _app-08db8fe46b3557c0.js:1:603078
    at _app-08db8fe46b3557c0.js:1:607445
    at Object.useMemo (framework-737ce2f1f8953932.js:1:65099)
    at a.useMemo (framework-737ce2f1f8953932.js:1:125050)
    at _app-08db8fe46b3557c0.js:1:607426
    at zg (framework-737ce2f1f8953932.js:1:59215)
    at Sg (framework-737ce2f1f8953932.js:1:67286)
    at $ (framework-737ce2f1f8953932.js:1:113610)
    at Ei (framework-737ce2f1f8953932.js:1:98685)
    at Ci (framework-737ce2f1f8953932.js:1:98613)
    at Bi (framework-737ce2f1f8953932.js:1:98476)
    at ui (framework-737ce2f1f8953932.js:1:95444)
    at framework-737ce2f1f8953932.js:1:45104
    at a.unstable_runWithPriority (framework-737ce2f1f8953932.js:1:129100)
    at pf (framework-737ce2f1f8953932.js:1:44878)
    at sf (framework-737ce2f1f8953932.js:1:45050)
    at rf (framework-737ce2f1f8953932.js:1:44985)
    at pi (framework-737ce2f1f8953932.js:1:92809)
    at Kg (framework-737ce2f1f8953932.js:1:64489)
    at authCheck (_app-08db8fe46b3557c0.js:1:403690)
    at main-31e654215812aa2c.js:1:33919
    at Array.map (<anonymous>)
    at Object.emit (main-31e654215812aa2c.js:1:33901)
    at a.<anonymous> (main-31e654215812aa2c.js:1:47767)
    at u (main-31e654215812aa2c.js:1:77399)
    at Generator._invoke (main-31e654215812aa2c.js:1:78689)
    at Generator.a.<computed> [as next] (main-31e654215812aa2c.js:1:77802)
    at b (main-31e654215812aa2c.js:1:70988)
    at e (main-31e654215812aa2c.js:1:71193)

I tried to debug and examine the stack trace and the call stack, but it's all garbage.

This is what's at _app-08db8fe46b3557c0.js:1:603078 and around it. As you can see it seems like it from this library as there's expandOnContentDrag a few characters before.
...te?X:te,ie=e.expandOnContentDrag,ae=void 0!==ie&&ie,ce=C(e,L),ue=(n=(e=(0,react.useState)(!1))[0],r=e[1],t=(0,react.useState)({}),i=t[0],u=t[1],s=(0,react.useCallback)(function(e){return u(function(n){var r;return x({},n,((r={})[e]=!1,r))}),function(){u(function(n){var r;return x({},n,((r={})[e]=!0,r))})}},[]),(0,react.useEffect)(function(){var e=Object.values(i);0!==e.length...

Does anyone have a clue?

Thanks a lot

Can't change background color of the sheet

I can't change the background color of the sheet.

I tried:
[data-rsbs-root] { background-color: blue !important; }

`
.background {
background-color: green !important;
}

<BottomSheet
className={'background'}
style={{background: 'green'}}

.....
`

Is there a way to change this?

Dragging the bottom sheet does not work on iOS 12

A bug found by someone with an old phone who hasn't been able to update past iOS 12.4 - there seems to be no response to dragging at all in iOS 12. Similarly, tapping the overlay background will also not close the bottom sheet.

I've checked the useGesture website and gestures seem to be fine there, so it must be something to do with the library. If I can provide any more details to help, I will.

Do the animations not work on mobile?

Hi, so here's a screen recording of the sheet demo on a Pixel 3 XL, Android 11, on the latest version of Chrome (90.0.4430.210).

t_video6116301220714906174.mp4

None of the animations work, and instead everything is snappy as though there are no smooth transitions/animations at all. This probably isn't an issue with this package itself but rather with react-spring, although I'm not sure exactly what it could be or how to fix this. The animations work perfectly fine in a non-mobile setting, and when I view it as a mobile device through Chrome Dev Tools. I used the Code Sandbox demo to show this but it's the same in my app as well even with all the most recent versions of the respective libraries.

I have this same issue with Bootstrap modals which typically have a fading effect, but that animation is non-existent on mobile devices for some reason.

Scrolling on iOS is blocked even when blocking is set to false

If you open the BottomSheet it will block any scrolling containers on iOS even if the BottomSheet is configured as false.
Example link: https://react-spring-blocking-ios-scroll.vercel.app/
Example code:

import { useState } from 'react';
import { BottomSheet } from 'react-spring-bottom-sheet';
import 'react-spring-bottom-sheet/dist/style.css';

export default function Home() {
	const [open, setOpen] = useState(false);
	return (
		<>
			<button onClick={() => setOpen(true)}>Open</button>
			<div style={{ border: '3px solid red', overflowY: 'scroll', height: '100px' }}>
				<div>1</div>
				<div>2</div>
				<div>3</div>
				<div>4</div>
				<div>5</div>
				<div>1</div>
				<div>1</div>
				<div>1</div>
				<div>1</div>
				<div>1</div>
				<div>1</div>
				<div>1</div>
				<div>1</div>
				<div>1</div>
				<div>1</div>
				<div>1</div>
				<div>1</div>
				<div>1</div>
				<div>1</div>
				<div>1</div>
				<div>1</div>
				<div>1</div>
				<div>1</div>
				<div>1</div>
				<div>1</div>
				<div>1</div>
				<div>1</div>
				<div>1</div>
			</div>
			<BottomSheet open={open} blocking={false}>
				<div>Bottom Sheet Content</div>
				<div>Bottom Sheet Content</div>
				<div>Bottom Sheet Content</div>
				<div>Bottom Sheet Content</div>
				<div>Bottom Sheet Content</div>
				<div>Bottom Sheet Content</div>
				<div>Bottom Sheet Content</div>
				<div>Bottom Sheet Content</div>
			</BottomSheet>
		</>
	);
}

Optionally disable touch gestures on the header

First of all, thank you for this great project!

This may be a niche use case, but it is as follows:

  • I want to use tabs inside a sheet;
  • I want the tab navigation to be fixed, like a header;
  • I want the tab navigation to overflow horizontally, with the user needing to scroll it to see all the tabs;
  • If I put the tab navigation in the header prop, it will be hard for the user to interact with it since it supports touch gestures. If the user does not scroll the tab navigation perfectly horizontally, the sheet goes slightly up and down, which is annoying.

After many failed attempts to make it work without opening an issue, I've come to the conclusion that there is only two possible solutions:

  • The enableTouchOnHeader prop, which optionally disables touch gestures on the header;
  • The beforeContent prop, which inserts a div between [data-rsbs-header] and [data-rsbs-scroll].

The names are up for debate, of course. And if we add this for the header, I think the footer should have it too (enableTouchOnFooter/afterContent).

Here's an example of what I want to achieve:

screenshot

I'll be happy to open a pull request once this is discussed.

Add the ability to customize the rendered overlay component

Really like this project but one thing I find missing is to render a custom overlay component.

Why I would like this is because we use Material UI for styling which uses CSS-in-JS with themes. All of our dynamic CSS is controlled through CSS-in-JS and their theme system except this component. The reason is because we can't inject one of their components as the overlay.

I propose a new prop called overlayComponent (or similar) which defaults to 'div' (to keep backwards compatibility) which is rendered instead of the explicit div-component referenced below.

<div
key="overlay"
aria-modal="true"
role="dialog"
data-rsbs-overlay

For example, it would look something like this:

// BottomSheet.tsx
>(function BottomSheetInternal(
  ...
  overlayComponent: OverlayComponent = 'div',
  ...
},
  forwardRef
) {
...
// Lines 649 - 653:
<OverlayComponent
  key="overlay"
  aria-modal="true"
  role="dialog"
  data-rsbs-overlay
  ...
>
...

// MyApp.js
import { Paper } from '@mui/material';
import { BottomSheet } from 'react-spring-bottom-sheet';
...
<BottomSheet
  overlayComponent={Paper}
  ...
>

If you think this seems like a good idea, I can implement this and submit a PR.

Sheet not resizing in certain cases

When I'm using snapPoints={({ minHeight, maxHeight }) => [minHeight, maxHeight]}, everything works fine : the bottom sheet is resized.

But in my case, I want my BottomSheet to be always open, but very small (30px), and when it's sliding to minHeight, sometimes I need the BottomSheet to fit the content, but the BottomSheet is failing to resize.
snapPoints={({ minHeight, maxHeight }) => [30, minHeight, maxHeight]}

I kind of understand why this is needed, but is there a solution ?

Demo code

[Suggestion] Code Sandbox demos

Hi, this is a great package, but I was wondering, are there any self-contained Code Sandbox demos available? The demo page is great, with the code as well, but I think a Code Sandbox demo would be very helpful as well. I'm trying to get this working but I'm not sure if there's some CSS issue on my end or if there's some extra code I've missed out. A Code Sandbox demo would be wonderful as it would definitely help us debug on the fly.

EDIT: I've gotten it working on my end due to a z-index mismatch between this package and another component.

TypeError: Cannot read properties of undefined (reading 'getValue')

I'm using your great module on my all projects but i couldn't implement to new one.

I got this error when "open state" to true. -> "TypeError: Cannot read properties of undefined (reading 'getValue')"

my dependencies:
"react-spring-bottom-sheet": "^3.4.0",
"next": "12.1.4",
"react-dom": "18.0.0",

Code:

 const [open, setOpen] = useState(false)
    return (
        <>
            <button onClick={() => setOpen(true)}>Open</button>
            <BottomSheet open={open}>My awesome content here</BottomSheet>
        </>
    )

image

Interactive elements beneath overlay are not "blocked" in iOS

Hi,

Great little library. I really love the bottom sheet. However, I'm seeing an issue in Safari on iOS, where clicking the overlay to close the bottom sheet is not being stopPropagated. Any interactive element under the overlay is triggered instead.

In your Basic example, you can see this by tapping the overlay atop the Open button to close the bottom sheet. It closes and immediately reopens the sheet, because the Open button is also being tapped. So, really easy to observe the issue. Hopefully also easy to fix.

"Warning: Event "done.invoke.onCloseEnd" was sent to stopped service "overlay" when redirecting to another page after animation has ended

Hi! First of all, thank you for such an amazing library. It makes my PWAs feel like native!
I'm facing a use case in which I need to redirect the user to another page when the bottom sheet is dismissed. First I implemented redirecting the user directly on the onDismiss event, but as expected this approach doesn't wait for the animation to be finished. So I used the onSpringEnd event like this:

const onAnimationEnd = (e: SpringEvent) => {
        if (e.type === 'CLOSE') navigate('/schedule');
}

While it works fine, I get two warnings in the console: the yellow one, that always appears and says Event "done.invoke.onCloseEnd" was sent to stopped service "overlay". This service has already reached its final state, and will not transition., and sometimes the red one, as can be seen in this video:

ezgif-4-d1feb2f14647.mp4

Am I using onSpringEnd wrong? I expected it to tell me when the animation has ended, so I can safely do other stuff. How can I do this properly?

Thanks for all the help!

Bottom sheet can't be dragged on Firefox for Android 91

It seems that some change in specifically version 91 of Firefox for Android makes dragging of the sheet impossible. The dragging works great in versions <91, and is again working in the current 92 beta (92.0.0-beta.8). We are using v3.2.1 of RSBS, but the behaviour is the same on the official website.

setup integration tests

I'm dying to rewrite more internals, but all the manual testing is too time consuming so it's time to figure out what the best way to test highly interactive, drag gesture-y and animated packages like this one.

Anything using jsdom is automatically a no go as it doesn't even try to deal with getBoundingClientRect, scrolling or animations. And cypress, puppeteer, all these e2e solutions... I have yet to find true love but maybe playwright is the one I've been looking for all along? Open for any suggestions but if nobody interjects then I think I'll try playwright ๐Ÿ˜„

Make escapeDeactivates, clickOutsideDeactivates as configurable props

Currently showing any interactable model above the bottomsheet will not accept any click inputs.
It's a big drawback considering we have scenarios which shows error popup on the screen.

After some analysis it looks like it's because of the focus trap. Please make it configurable

fix build warning from microbundle and the TS setup

rpt2: options error TS5069: Option 'declarationDir' cannot be specified without specifying option 'declaration' or option 'composite'.

Microbundle is currently not generating declaration files, as TS have this annoying behavior where the types.ts file will be empty and types missing because it's only containing types. Will probably have to refactor it so that there's no type-only files, that way there's no separate TS step to generate declarations needed.

Backdrop removal lag after dismissal of the bottom sheet

Hi, on a vanilla install of the component, using the CSS in the getting started guide there seems to be a lag after the bottom sheet is dismissed before the dark overlay disappears. I can not see this issue in the demo examples however. Here is an example code sandbox showing the issue. Any ideas what's going on, are the demo's configured differently?

Awesome repo keep up the work!

CSS Vars with no values

I copied the stylesheet into my project and tweaked it a bit.
I added the postcss-custom-properties-fallback plugin and set it up in my postcss config.
When I refresh the page the css vars seem to have no values.
If I add a dummy css property (see GIF) to the root's direct child and remove it the css vars suddenly have values and I see the panel.
I even tried to copy the css vars themselves from defaults.json.
I've experienced this on Desktop Chrome and Android Chrome but IPhone Chrome/Safari works fine. ๐Ÿคท๐Ÿป

bottom-panel

Test clean-publish on a test branch

I like the idea of clean-publish as it resolves the issue where it's convenient to put config info in one place (package.json) but the cost is undesirable.

There's already a ton of unnecessary and fragmented stuff in packages today (like publishing cjs, esm, umd both minified and unminified for everything). But it's also not very nice to have a ton of config files at root (postcss.config.js, .babelrc, .eslintrc, release.config.js, .prettierrc etc etc etc). If it's possible to combine clean-publish with semantic-release it would allow putting whatever I want in the package json without shipping more bloat on npm ๐Ÿ˜

RESIZE should differentiate between the source of the resize

Right now a RESIZE event is dispatched if any of maxHeight || maxSnap || minSnap changes. These can change depending on a number of causes. Could be the window was resized, which happens every time an Android device shows the soft keyboard when an input is in focus. Or it could be the content changed.

If the content in the bottom sheet changed in some way that changes its height, it should be animated. Like it already is.

But if the window is resized, like when a device is rotating between landscape and portrait, it's better if it's immediate. The device is usually doing its own animation under these circumstances so the UX is way better if the sheet isn't doing an extra animation. To support this the sheet needs to track the source of a RESIZE event.

defaultSnap - snapPoints return bad value

I set up my BottomSheet like this:

<BottomSheet
  open={true}
  ref={bottomSheetRef}
  blocking={false}
  snapPoints={({ maxHeight }) => [200, maxHeight / 2, maxHeight - 150]}
  defaultSnap={({ snapPoints }) =>
    snapPoints[someComponentStateValue]
  }
>

The snapPoints in defaultSnap seem to have a bad value: an array with only 1 member and the value 0.

Upgrade to react-spring v9

Is this project likely to be updated to use react-spring v9? There are some new features which would be nice to use, namely I am having trouble with testing with React Testing Library as the sheet is always rendered with opactiy: 0, there is an option to skipAnimations which is a global option, but setting globals like this is not possible in v8.

https://react-spring.io/guides/testing#skipping-animations

How can I change default animation speed

Hello.
I have trouble with changing animation speed.
There is no options for changing animation velocity and other animation options.
not snapTo Velocity options. I want to know default animation options.
Please let me know. Thanks!

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.


Using a curated preset maintained by


Sanity: The Composable Content Cloud

Pending Approval

These branches will be created by Renovate only once you click their checkbox below.

  • chore(deps): update actions/checkout action to v4
  • chore(deps): update actions/setup-node action to v4
  • chore(deps): update dependency @semantic-release/changelog to v6
  • chore(deps): update dependency @semantic-release/git to v10
  • chore(deps): update dependency postcss-cli to v10
  • chore(deps): update dependency postcss-cli to v11
  • chore(deps): update dependency postcss-preset-env to v7
  • chore(deps): update dependency postcss-preset-env to v8
  • chore(deps): update dependency prettier to v3
  • chore(deps): update dependency rimraf to v4
  • chore(deps): update dependency smooth-scroll-into-view-if-needed to v2
  • chore(deps): update dependency svgo to v2
  • chore(deps): update dependency typescript to v5
  • chore(deps): update linters to v5 (major) (@typescript-eslint/eslint-plugin, @typescript-eslint/parser)
  • chore(deps): update linters to v6 (major) (@typescript-eslint/eslint-plugin, @typescript-eslint/parser, eslint-plugin-flowtype)
  • chore(deps): update linters to v7 (major) (@typescript-eslint/eslint-plugin, @typescript-eslint/parser, eslint-config-react-app, eslint-plugin-flowtype)
  • chore(deps): update linters to v8 (major) (eslint, eslint-plugin-flowtype)
  • chore(deps): update postcss packages to v9 (major) (postcss-cli, postcss-preset-env)
  • chore(deps): update react monorepo to v18 (major) (@types/react, react, react-dom)
  • fix(deps): Update dependency @xstate/react to v4
  • fix(deps): Update dependency xstate to v5
  • chore(deps): lock file maintenance
  • ๐Ÿ” Create all pending approval PRs at once ๐Ÿ”

Edited/Blocked

These updates have been manually edited so Renovate will no longer make changes. To discard all commits and start over, click on a checkbox.

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Ignored or Blocked

These are blocked by an existing closed PR and will not be recreated unless you click a checkbox below.

Detected dependencies

github-actions
.github/workflows/nodejs.yml
  • actions/checkout v2
  • actions/setup-node v2
.github/workflows/publish.yml
  • actions/checkout v2
  • actions/setup-node v2
  • actions/checkout v2
  • actions/setup-node v2
npm
package.json
  • @juggle/resize-observer ^3.2.0
  • @reach/portal ^0.13.0
  • @xstate/react ^1.2.0
  • body-scroll-lock ^3.1.5
  • focus-trap ^6.2.2
  • react-spring ^8.0.27
  • react-use-gesture ^8.0.1
  • xstate ^4.15.1
  • @rooks/use-raf ^4.5.0
  • @semantic-release/changelog ^5.0.1
  • @semantic-release/git ^9.0.0
  • @tailwindcss/forms ^0.4.0
  • @types/classnames ^2.2.11
  • @types/react ^17.0.0
  • @typescript-eslint/eslint-plugin ^4.9.0
  • @typescript-eslint/parser ^4.9.0
  • @use-it/interval ^1.0.0
  • @xstate/inspect ^0.6.0
  • autoprefixer ^10.0.4
  • babel-eslint ^10.1.0
  • babel-plugin-transform-remove-console ^6.9.4
  • classnames ^2.2.6
  • eslint ^7.14.0
  • eslint-config-react-app ^6.0.0
  • eslint-plugin-flowtype ^5.2.0
  • eslint-plugin-import ^2.22.1
  • eslint-plugin-jsx-a11y ^6.4.1
  • eslint-plugin-react ^7.21.5
  • eslint-plugin-react-hooks ^4.2.0
  • microbundle ^0.14.0
  • next ^10.0.3
  • postcss ^8.1.14
  • postcss-cli ^8.3.0
  • postcss-custom-properties-fallback ^1.0.1
  • postcss-import-svg ^1.0.1
  • postcss-preset-env ^6.7.0
  • prettier ^2.2.1
  • prettier-package-json ^2.1.3
  • react ^17.0.0
  • react-dom ^17.0.0
  • rimraf ^3.0.2
  • semantic-release ^17.3.0
  • smooth-scroll-into-view-if-needed ^1.1.29
  • svgo ^1.3.2
  • tailwindcss ^2.0.1
  • typescript ^4.1.2
  • react ^16.14.0 || 17 || 18

  • Check this box to trigger a request for Renovate to run again on this repository

"exaggerated scroll"

Aloha!

What an excellent project. My app is a PWA, so this is perfect.

Now to the sad news, I must have something in my app that messes with this one.

It's a bit hard to describe, so I've made a video that shows the issue..

Basically, whenever I scroll just a tiny bit, the Y coordinates seems to be overshoot by a factor of 10 or more. (In the video I have some custom content in the div, but I've also tried with just a

test
in the BottomSheet which has the same problem.

Now I know that it might be hard to pinpoint the issue because of all my custom CSS etc., but maybe you already know where I could look and troubleshoot?

custom opacity

hi
i want to set custom opacity for the overlay in the blocking mode. the issue i am facing is that when i set custom opacity i can only handle it in the open and closing situations using onSpringStart event. i can not manage the opacity animation when user drag the bottom sheet using gesture handlers.
please help me out

Is possible to modify Header css color dinamically?

I want to know if is possible to change dinamically (using props) the color of the header ([data-rsbs-header] and [data-rsbs-header]:before )

In the following example i have modified the css properties, but i want to do this from React, so i can use the same component with different aspect based on events.

[data-rsbs-header] {
  text-align: center;
  -webkit-user-select: none;
     -moz-user-select: none;
      -ms-user-select: none;
          user-select: none;
  box-shadow: 0 1px 0
    rgba(46, 59, 66, calc(var(--rsbs-content-opacity,1) * 0.125));
  z-index: 1;
  padding-top: calc(20px + env(safe-area-inset-top));
  padding-bottom: 8px;
  background-color: black;   /* CUSTOM */
  border-top-left-radius: 12px;   /* CUSTOM */
  border-top-right-radius: 12px;   /* CUSTOM */
}

[data-rsbs-header]:before {
  position: absolute;
  content: '';
  display: block;
  width: 36px;
  height: 4px;
  top: calc(8px + env(safe-area-inset-top));
  left: 50%;
  transform: translateX(-50%);
  border-radius: 2px;
  /* background-color: var(--rsbs-handle-bg,hsla(0, 0%, 0%, 0.14)); */
  background-color: #eceff1; /* CUSTOM */``

Your component is awesome.

Thank you,
Gabriel

SNAP event should support snapPoint(s)

Like the issue #53 , Is there a way to know the snapPoint that triggers the SNAP event?

I want to perform an action whenever the snapPoint changes.
I thought of triggering the action after ref.current.snapTo() but I realized dragging also fires the SNAP event.

Thanks for the library!

Control speed of animation

Is there a way to control the speed of the animation?
Specifically using the snapTo method.

(Awesome, awesome library!)

Sheet is changing snapPoint when keyboard is close

I'm trying to find a workaround to this case, when I focus on a text input that's on sheet the soft keyboard shows up and the RESIZE event is being dispatched, so

  1. open the sheet till the maxHeight
  2. focus on the input
  3. press "done"/enter/submit key from the keyboard

hopefully a blur event hides the keyboard (not sure why this is not happening, but in the sample I added onkeypress === 'enter' to blur the input and hide the keyboard) then the resize is dispatched but the bottom sheet is going to another snap point (seems to be the closest one to 50% maxHeight since keyboard shrinks the window size to that).
I was expecting as result that the sheet should be keeping the current snapPoint you already chose, with or without the keyboard on the screen.

Another thing I intended to do was cancelling the animation stopping the spring event for certain cases like this but I couldn't find the way to do that. Even note that the height probably would have to change to handle the scroll when keyboard is present

https://1lps5.csb.app/

Action Required: Fix Renovate Configuration

There is an error with this repository's Renovate configuration that needs to be fixed. As a precaution, Renovate will stop PRs until it is resolved.

Error type: undefined. Note: this is a nested preset so please contact the preset author if you are unable to fix it yourself.

[Need Help] Sheet to appear behind a footer

Here's a quick video of the problem I'm trying to solve:
https://user-images.githubusercontent.com/917752/104208742-cea3b080-546b-11eb-9c62-e334cd10ac5c.mp4

My sheet has a z-index of 1150 and the bar below it has a z-index of 1200. However, when I try to close the sheet it appears in front of the bottom bar, even though the sheet's z-index is 1150 (some parts are 1151, 1153, basically all the z-indexes defined in the default CSS have been incremented by 1150). Also worth noting that I made all the elements in the bottom bar have a z-index of > 1200 for extra measure.

The sheet renders at that position when it's opened, as my bar has a height of 7vh and I changed the bottom offset to bottom 7vh for

[data-rsbs-overlay],
[data-rsbs-backdrop],
[data-rsbs-root]:after

I understand that there's an option to have a footer within the sheet, but here I'm trying to get my footer to be outside the sheet, and depending on what icon you tap in the bottom bar, a different sheet shows up, but renders behind the bar.

How could I accomplish this? Thank you!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.