Coder Social home page Coder Social logo

temzasse / react-modal-sheet Goto Github PK

View Code? Open in Web Editor NEW
735.0 5.0 70.0 45.81 MB

Flexible bottom sheet component built with Framer Motion to provide buttery smooth UX while keeping accessibility in mind ๐Ÿช

Home Page: https://temzasse.github.io/react-modal-sheet/

License: MIT License

TypeScript 97.85% CSS 1.42% HTML 0.67% JavaScript 0.06%
reactjs framer-motion bottom-sheet modal accessibility

react-modal-sheet's Introduction

React Modal Sheet logo

ยท Flexible bottom sheet component for your React apps ยท

npm version npm license

Installation

npm install react-modal-sheet

or if you use yarn:

yarn add react-modal-sheet

Peer dependencies

The gestures and animations are handled by the excellent Framer Motion library so before you can start using this library you need to install framer-motion:

npm install framer-motion

Usage

import Sheet from 'react-modal-sheet';
import { useState } from 'react';

function Example() {
  const [isOpen, setOpen] = useState(false);

  return (
    <>
      <button onClick={() => setOpen(true)}>Open sheet</button>

      <Sheet isOpen={isOpen} onClose={() => setOpen(false)}>
        <Sheet.Container>
          <Sheet.Header />
          <Sheet.Content>{/* Your sheet content goes here */}</Sheet.Content>
        </Sheet.Container>
        <Sheet.Backdrop />
      </Sheet>
    </>
  );
}

The Sheet component follows the Compound Component pattern in order to provide a flexible yet powerful API for creating highly customizable bottom sheet components.

Since the final bottom sheet is composed from smaller building blocks (Container, Content, Scroller, Header, and Backdrop) you are in total control over the rendering output. So for example, if you don't want to have any backdrop in your sheet then you can just skip rendering it instead of passing some prop like renderBackdrop={false} to the main sheet component. Cool huh? ๐Ÿ˜Ž

Also, by constructing the sheet from smaller pieces makes it easier to apply any necessary accessibility related properties to the right components without requiring the main sheet component to be aware of them. You can read more about accessibility in the Accessibility section.

Props

Name Required Default Description
children yes Use Sheet.Container/Content/Header/Backdrop to compose your bottom sheet.
isOpen yes Boolean that indicates whether the sheet is open or not.
onClose yes Callback fn that is called when the sheet is closed by the user.
disableDrag no false Disable drag for the whole sheet.
disableScrollLocking no false Disable scroll locking for the body element while sheet is open. Can be useful if you face issues with input elements and the iOS virtual keyboard. See related issue.
detent no 'full-height' The detent in which the sheet should be in when opened. Available values: 'full-height' or 'content-height'.
onOpenStart no Callback fn that is called when the sheet opening animation starts.
onOpenEnd no Callback fn that is called when the sheet opening animation is completed.
onCloseStart no Callback fn that is called when the sheet closing animation starts.
onCloseEnd no Callback fn that is called when the sheet closing animation is completed.
onSnap no Callback fn that is called with the current snap point index when the sheet snaps to a new snap point. Requires snapPoints prop.
snapPoints no Eg. [-50, 0.5, 100, 0] - where positive values are pixels from the bottom of the screen and negative from the top. Values between 0-1 represent percentages, eg. 0.5 means 50% of window height from the bottom of the sceen.
initialSnap no 0 Initial snap point when sheet is opened (index from snapPoints).
rootId no The id of the element where the main app is mounted, eg. "root". Enables iOS modal effect.
tweenConfig no { ease: 'easeOut', duration: 0.2 } Overrides the config for the sheet tween transition when the sheet is opened, closed, or snapped to a point.
mountPoint no document.body HTML element that should be used as the mount point for the sheet.
prefersReducedMotion no false Skip sheet animations (sheet instantly snaps to desired location).

Methods and properties

snapTo(index)

Imperative method that can be accessed via a ref for snapping to a snap point in given index.

import Sheet, { SheetRef } from 'react-modal-sheet';
import { useState, useRef } from 'react';

function Example() {
  const [isOpen, setOpen] = useState(false);
  const ref = useRef<SheetRef>();
  const snapTo = (i: number) => ref.current?.snapTo(i);

  return (
    <>
      <button onClick={() => setOpen(true)}>Open sheet</button>

      {/* Opens to 400 since initial index is 1 */}
      <Sheet
        ref={ref}
        isOpen={isOpen}
        onClose={() => setOpen(false)}
        snapPoints={[600, 400, 100, 0]}
        initialSnap={1}
        onSnap={snapIndex =>
          console.log('> Current snap point index:', snapIndex)
        }
      >
        <Sheet.Container>
          <Sheet.Content>
            <button onClick={() => snapTo(0)}>Snap to index 0</button>
            <button onClick={() => snapTo(1)}>Snap to index 1</button>
            <button onClick={() => snapTo(2)}>Snap to index 2</button>
            <button onClick={() => snapTo(3)}>Snap to index 3</button>
          </Sheet.Content>
        </Sheet.Container>
      </Sheet>
    </>
  );
}

Motion value y

The y value is an internal MotionValue that represents the distance to the top most position of the sheet when it is fully open. So for example the y value is zero when the sheet is completely open.

Similarly to the snapTo method the y value can be accessed via a ref.

The y value can be useful for certain situtation eg. when you want to combine snap points with scrollable sheet content and ensure that the content stays properly scrollable in any snap point. Below you can see a simplified example of this situation and for a more detailed example take a look at the ScrollableSnapPoints component in the example app.

import Sheet, { SheetRef } from 'react-modal-sheet';
import { useState, useRef } from 'react';

function Example() {
  const [isOpen, setOpen] = useState(false);
  const ref = useRef<SheetRef>();

  return (
    <>
      <button onClick={() => setOpen(true)}>Open sheet</button>

      <Sheet
        isOpen={isOpen}
        onClose={() => setOpen(false)}
        snapPoints={[600, 400, 100, 0]}
        initialSnap={1}
      >
        <Sheet.Container>
          {/**
           * Since `Sheet.Content` is a `motion.div` it can receive motion values
           * in it's style prop which allows us to utilise the exposed `y` value.
           *
           * By syncing the padding bottom with the `y` motion value we introduce
           * an offset that ensures that the sheet content can be scrolled all the
           * way to the bottom in every snap point.
           */}
          <Sheet.Content style={{ paddingBottom: ref.current?.y }}>
            <Sheet.Scroller draggableAt="both">
              {/* Some content here that makes the sheet content scrollable */}
            </Sheet.Scroller>
          </Sheet.Content>
        </Sheet.Container>
      </Sheet>
    </>
  );
}

Detents

By default the sheet will take the full height of the page minus top padding and safe area inset. If you want the sheet height to be based on it's content you can pass detent="content-height" prop to the Sheet component:

function Example() {
  const [isOpen, setOpen] = useState(false);

  return (
    <Sheet
      isOpen={isOpen}
      onClose={() => setOpen(false)}
      detent="content-height"
    >
      <Sheet.Container>
        <Sheet.Content>
          <Sheet.Scroller>
            <div style={{ height: 200 }}>Some content</div>
          </Sheet.Scroller>
        </Sheet.Content>
      </Sheet.Container>
    </Sheet>
  );
}

If the sheet height changes dynamically the sheet will grow until it hits the maximum full height after which it becomes scrollable.

It is possible to use snap points with detent="content-height" but the snap points are restricted by the content height. For example if one of the snap points is 800px and the sheet height is only 700px then snapping to the 800px snap point would only snap to 700px since otherwise the sheet would become detached from the bottom.

โ„น๏ธ If you are wondering where the term detent comes from it's from Apple's Human Interface Guidelines.

Compound Components

Sheet

Sheet is the root element that wraps the whole sheet. It renders a fixed positioned wrapper element that covers the whole screen to contain the actual sheet that is animated in (don't worry the root element is not interactive when the sheet is closed). All sheet compound components should be rendered within Sheet.

๐Ÿ–ฅ Rendered element: motion.div.

Sheet.Container

Sheet container is positioned above the sheet backdrop and by default adds a small shadow and rounded corners to the sheet. Sheet.Content and Sheet.Header should be rendered inside Sheet.Container.

๐Ÿ–ฅ Rendered element: motion.div.

Sheet.Header

Sheet header acts as a drag target and has a dragging direction indicator. Rendering any children inside Sheet.Header replaces the default header.

๐Ÿ–ฅ Rendered element: motion.div.

Header props

Name Required Default Description
disableDrag no false Disable drag for the sheet header.

Sheet.Content

Sheet content acts as a drag target and can be used in conjunction with Sheet.Scroller to make sure that content which doesn't fit inside the sheet becomes scrollable.

๐Ÿ–ฅ Rendered element: motion.div.

Content props

Name Required Default Description
disableDrag no false Disable drag for the sheet content.

Sheet.Scroller

Sheet scroller can be used to make the whole sheet content or parts of it scrollable in a way that drag gestures are properly disabled and enabled based on the scroll state. See the Scrolling on touch devices section for more details.

๐Ÿ–ฅ Rendered element: motion.div.

Scroller props

Name Required Default Description
draggableAt no "top" Should the drag be enabled when the element is scrolled either to the top, bottom, or both. Available values: top, bottom, both.

Sheet.Backdrop

Sheet backdrop is a translucent overlay that helps to separate the sheet from it's background. By default the backdrop doesn't have any interaction attached to it but if you, for example, want to close the sheet when the backdrop is clicked you can provide tap handler to it which will change the rendered element from div to button.

โš ๏ธ Note: as the element is a motion component you need to use onTap instead of onClick if you want to add a click handler to it.

๐Ÿ–ฅ Rendered element: motion.div or motion.button.

Advanced usage

Scrolling on touch devices

Scrolling and dragging are the same gesture on touch devices which can create problems when you want to have scrollable content inside the sheet. React Modal Sheet provides a Sheet.Scroller component that is able to automatically disable and enable dragging inside the Sheet.Content component based on the scroll state. There are three modes for the Sheet.Scroller reflected in the draggableAt prop:

  1. Enable dragging when the scroller is not yet scrolled - it is at the top position.
  2. Enable dragging when the scroller is scrolled all the way to the bottom - it is at the bottom position.
  3. Enable dragging in both positions.

The scroller component is in-between these states when the user has scrolled only some amount. Dragging is always disabled in this in-between state in order to avoid it getting mixed with dragging gestures. The Sheet.Scroller component applies the special handling only for touch devices.

The default value for the draggableAt prop is top which should be a good default for most use cases. You shouldn't need bottom or both unless you have scrollable content inside a sheet that also has snap points.

function ScrollableExample() {
  return (
    <Sheet>
      <Sheet.Container>
        <Sheet.Header />
        <Sheet.Content>
          <Sheet.Scroller>{/*...*/}</Sheet.Scroller>
        </Sheet.Content>
      </Sheet.Container>
      <Sheet.Backdrop />
    </Sheet>
  );
}

iOS Modal View effect

In addition to the Sheet.Backdrop it's possible to apply a scaling effect to the main app element to highlight the modality of the bottom sheet. This effect mimics the iOS Modal View presentation style to bring more focus to the sheet and add some delight to the user experience.

To enable this effect you can provide the id of the root element where your application is mounted:

function Example() {
  return <Sheet rootId="root">{/*...*/}</Sheet>;
}

โš ๏ธ Limitations: Since the effect is applied to the root element it will NOT work as desired if the HTML body element is scrolled down at all. One way to avoid this is to use something like height: 100vh; and overflow: auto; on the root element to make it fill the whole screen and be scrollable instead of the body element.

Customization

The default styles for the Sheet component somewhat follows the styles of the previously mentioned iOS Modal View. However, if these default styles are not to your liking it's easy to make changes to them: you can provide a custom header or you can overwrite any style with CSS via the exposed class names.

Custom header

Adding a custom header is as simple as providing your own header as the child component to Sheet.Header:

function Example() {
  return (
    <Sheet>
      <Sheet.Container>
        <Sheet.Header>
          <YourCustomSheetHeader />
        </Sheet.Header>
        <Sheet.Content>{/*...*/}</Sheet.Content>
      </Sheet.Container>
      <Sheet.Backdrop />
    </Sheet>
  );
}

Custom styles

You can add your own styles or override the default sheet styles via the exposed class names. Note that you might need to use !important for style overrides since the inner styles are applied as inline styles which have higher specificity.

Vanilla CSS

.react-modal-sheet-backdrop {
  /* custom styles */
}
.react-modal-sheet-container {
  /* custom styles */
}
.react-modal-sheet-header {
  /* custom styles */
}
.react-modal-sheet-drag-indicator {
  /* custom styles */
}
.react-modal-sheet-content {
  /* custom styles */
}

CSS-in-JS

import Sheet from 'react-modal-sheet';
import styled from 'styled-components';
import { useState } from 'react';

const CustomSheet = styled(Sheet)`
  .react-modal-sheet-backdrop {
    /* custom styles */
  }
  .react-modal-sheet-container {
    /* custom styles */
  }
  .react-modal-sheet-header {
    /* custom styles */
  }
  .react-modal-sheet-drag-indicator {
    /* custom styles */
  }
  .react-modal-sheet-content {
    /* custom styles */
  }
`;

function Example() {
  const [isOpen, setOpen] = useState(false);

  return (
    <>
      <button onClick={() => setOpen(true)}>Open sheet</button>

      <CustomSheet isOpen={isOpen} onClose={() => setOpen(false)}>
        <Sheet.Container>
          <Sheet.Header />
          <Sheet.Content>{/*...*/}</Sheet.Content>
        </Sheet.Container>
        <Sheet.Backdrop />
      </CustomSheet>
    </>
  );
}

Accessibility

By default, react-modal-sheet doesn't include any built-in accessibility properties in order to not make any assumptions and to support a wide range of use cases. Additionally, not including 3rd party libraries for features like focus trapping or screen reader support makes it possible to utilize any accessibility libraries that your project may already use, eg. React Aria. This also helps to reduce JS bloat by not including similar libraries multiple times in your app bundle.

The example below utilizes React Aria to achieve an accessible modal-like bottom sheet that can be closed via a button rendered inside a custom sheet header.

โ„น๏ธ The example was built by following the React Aria's useDialog documentation.

import Sheet from 'react-modal-sheet';
import { useRef } from 'react';
import { useOverlayTriggerState } from 'react-stately';

import {
  useOverlay,
  useModal,
  OverlayProvider,
  FocusScope,
  useButton,
  useDialog,
} from 'react-aria';

const A11yExample = () => {
  const sheetState = useOverlayTriggerState({});
  const openButtonRef = useRef(null);
  const openButton = useButton({ onPress: sheetState.open }, openButtonRef);

  return (
    <div>
      <button {...openButton.buttonProps} ref={openButtonRef}>
        Open sheet
      </button>

      <Sheet isOpen={sheetState.isOpen} onClose={sheetState.close}>
        <OverlayProvider>
          <FocusScope contain autoFocus restoreFocus>
            <SheetComp sheetState={sheetState} />
          </FocusScope>
        </OverlayProvider>
      </Sheet>
    </div>
  );
};

const SheetComp = ({ sheetState }) => {
  const containerRef = useRef(null);
  const dialog = useDialog({}, containerRef);
  const overlay = useOverlay(
    { onClose: sheetState.close, isOpen: true, isDismissable: true },
    containerRef
  );

  const closeButtonRef = useRef(null);
  const closeButton = useButton(
    { onPress: sheetState.close, 'aria-label': 'Close sheet' },
    closeButtonRef
  );

  useModal();

  // In real world usage this would be a separate React component
  const customHeader = (
    <div>
      <span {...dialog.titleProps}>Some title for sheet</span>
      <button {...closeButton.buttonProps}>๐Ÿ…ง</button>
    </div>
  );

  return (
    <>
      <Sheet.Container
        {...overlay.overlayProps}
        {...dialog.dialogProps}
        ref={containerRef}
      >
        <Sheet.Header>{customHeader}</Sheet.Header>
        <Sheet.Content>{/*...*/}</Sheet.Content>
      </Sheet.Container>
      <Sheet.Backdrop />
    </>
  );
};

If you want to see a more real-world-like implementation you can take a look at the Slack example and try out the related demo (optimized for mobile).

Building a reusable sheet

In your projects it might make sense to build a reusable bottom sheet that has all the accessibility features included and can then be easily used in various places in the project. Take a look at the A11ySheet example to get some insight on how to build such a component. By incorporating all the accessibility features inside your own reusable component you don't need to repeat them every time you want to use a bottom sheet in your app.

react-modal-sheet's People

Contributors

adamstambouli avatar blackmoja avatar bonhaenglee avatar eimerreis avatar elsonel avatar erhnml avatar gsporto avatar joaquinwojcik avatar kirbymckenzie avatar mattddean avatar mgalante avatar temzasse 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

react-modal-sheet's Issues

Property 'onClick' does not exist on Sheet.Backdrop

Typescript is complaining that onClick property does not exist when I try to use with the Sheet.Backdrop component.

Here's the full error

Type '{ onClick: () => any; }' is not assignable to type 'IntrinsicAttributes & Pick<MotionProps, "style" | "transformTemplate" | "transformValues" | "variants" | "transition" | "onViewportBoxUpdate" | ... 39 more ... | "onAnimationComplete"> & RefAttributes<...>'.
  Property 'onClick' does not exist on type 'IntrinsicAttributes & Pick<MotionProps, "style" | "transformTemplate" | "transformValues" | "variants" | "transition" | "onViewportBoxUpdate" | ... 39 more ... | "onAnimationComplete"> & RefAttributes<...>'.ts(2322)

Here's the code snippet I'm using in my project

import React from 'react';
import Sheet from 'react-modal-sheet';

export function ShareEpisodeModal({ isOpen, setIsOpen }) {

  return (
    <>
      <Sheet isOpen={isOpen} onClose={() => setIsOpen(false)}>
        <Sheet.Container>
          <Sheet.Header />
          <Sheet.Content>{/* Your sheet content goes here */}</Sheet.Content>
        </Sheet.Container>

        <Sheet.Backdrop onClick={() => setIsOpen(false)} />
      </Sheet>
    </>
  );
}

Maybe I'm just missing something here? I'd appreciate any help. Thanks

LazyMotion Support/Integration

Hi!

first of all: Great work and thank you for this lib.

I was wondering if you see any way to support LazyMotion (https://www.framer.com/api/motion/guide-reduce-bundle-size/) inside your package.

I tested an initial implementation (really just replacing the motion import with m) and published it for my personal project.
https://www.npmjs.com/package/react-lazy-modal-sheet

The caveat is of course the requirement to use LazyMotion in a root component which needs to be handled in user space (afaik).

Do you think there is any way to support both lazy and non-lazy users? Or is this a bad idea?
Maybe the solution would be to have a build step which replaces the import and publish two separated packages?

If you don't care about this, I would try to regularly fetch the upstream changes and update my published package.

Greetings

do-wa

Type Error

Getting an error like this when using TypeScript.

Property 'onViewportBoxUpdate' is missing in type '{ children: Element[]; }' but required in type 'Pick<MotionProps, "style" | "transformTemplate" | "transformValues" | "variants" | "transition" | "onViewportBoxUpdate" | "onBeforeLayoutMeasure" |

Animation Not Working in Safari

Using it with FocusTrap


header: {
    opacity: 0,
    [theme.breakpoints.up('xl')]: {
      '& .react-modal-sheet-header': {
        height: '32px !important',
      },
      '& + .react-modal-sheet-content': {
        flex: '0 1 auto !important',
        overflow: 'visible !important',
      },
    },
  },
  content: {
    display: 'flex',
    flexDirection: 'column',
    justifyContent: 'space-between',
    height: '100%',
    [theme.breakpoints.up('xl')]: {
      flexDirection: 'row',
      flexWrap: 'wrap',
    },

<Sheet
        isOpen={props.isOpen}
        onClose={() => props.setOpen(false)}
        snapPoints={width > widthBreakpoint ? [195] : [600]}>
        <Sheet.Container>
          <Sheet.Header className={classes.header} />
          <FocusTrap active={props.isOpen}>
            <Sheet.Content>
         {...Content}
            </Sheet.Content>
          </FocusTrap>
        </Sheet.Container>
        <Sheet.Backdrop />
      </Sheet>

Without Focus Trap Its working as expected
ezgif com-gif-maker

SSR Support for Sheet.Content

Hi there,

would it be possible to output the Sheet.Content to the html on the server-side?
Currently it is not possible because createPortal references part of the document (not available on the server), right?.

If the user could decide how/where to mount the Sheet this would be possible right? i.e. opting out of the createPortal logic and putting it somewhere else in the regular React tree.

The A11ySheet example seems not working

code:

import { useButton } from '@react-aria/button'
import { useDialog } from '@react-aria/dialog'
import { FocusScope } from '@react-aria/focus'
import { OverlayProvider, useModal, useOverlay } from '@react-aria/overlays'
import { OverlayTriggerState } from '@react-stately/overlays'
import { styled } from 'linaria/react'
import React from 'react'
import ReactModalSheet from 'react-modal-sheet'

type Props = {
    sheetState: OverlayTriggerState
}

const Sheet = ({ sheetState, children, ...rest }: Props) => {
    return (
        <StyledSheet {...rest} isOpen={sheetState.isOpen} onClose={sheetState.close}>
            <OverlayProvider>
                <FocusScope contain autoFocus={false} restoreFocus>
                    <SheetComp sheetState={sheetState}>{children}</SheetComp>
                </FocusScope>
            </OverlayProvider>
        </StyledSheet>
    )
}

export default Sheet

const SheetComp = ({ sheetState, children }) => {
    const containerRef = React.useRef(null)
    const dialog = useDialog({}, containerRef)
    const overlay = useOverlay({ onClose: sheetState.close, isOpen: true, isDismissable: true }, containerRef)

    useModal()

    return (
        <>
            <StyledSheet.Container {...overlay.overlayProps} {...dialog.dialogProps} ref={containerRef}>
                <StyledSheet.Content>{children}</StyledSheet.Content>
            </StyledSheet.Container>
        </>
    )
}

const StyledSheet = styled(ReactModalSheet)`
    .react-modal-sheet-container {
        background-color: var(--color-background);
    }
    .react-modal-sheet-header {
        background-color: var(--color-background);
        border-bottom: 0.5px solid var(--color-border);
        border-radius: var(--border-radius) var(--border-radius) 0 0;
    }
    .react-modal-sheet-drag-indicator {
        background-color: var(--color-background-opacity);
    }
    .react-modal-sheet-content {
        /* custom styles */
    }
    .react-modal-sheet-backdrop {
        /* custom styles */
    }
`

Got error:

Uncaught Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.

Check the render method of `SheetComp`.

Is not scrollable when tried with actual mobile phone.

Hi there,

So I tried to use this for the next project. And it worked perfectly when I tried here in the browser but when I tried with the actual phone, I was unable to scroll. I tried exactly what it stated here. Could you please take a look and let me know please?
Thank you.

UPDATE: I was able to scroll when I disable the drag to close function.

My code:

<Sheet isOpen={isMSOpen} onClose={() => setMSOpen(false)}>
          <Sheet.Container>
            <Sheet.Header />
            <Sheet.Content>
              {posts.map((post) => (
                <div style={{ color: "#000", background: "#aaa" }}>
                  <h5 key={post.id}>{post.title}</h5>
                  <p>{post.body}</p>
                </div>
              ))}
            </Sheet.Content>
          </Sheet.Container>
        </Sheet>

How to get current active snap?

I am trying to adjust the wrapping container height based on which is the active current point, onSnap, returns negative values. So hard to identify which is the active snap

How to disable close modal-sheet

Hello friends
how to make my modal-sheet always be displayed on screen as 100px drag max 500px and can't close modal-sheet when height is 100px

How to get y value of sheet on drag

Is it possible to invoke a function as the user drags the modal to get the y position of the sheet? Readme mentions using refs, but unclear on how to go about getting the latest values. I tried the following

const ref = useRef();

useEffect(() => {
// Use the latest y value
},[ref.current.y] ]

<Sheet ref={ref}> ... </Sheet>

renderBackdrop={false} not working

how can i access this layouts
but react-modal-sheet block all layouts

Untitled

    <Sheet
        ref={ref}
        isOpen={true}
        initialSnap={0}
        snapPoints={snapPoints}
        onClose={disableOnClose}
        className={'player-bottom-sheet'}
        onSnap={(si) => setSnapIndex(si)}
        renderbackdrop="false"
        renderBackdrop={false}
      >
        <Sheet.Container
          style={{
            backgroundColor: '#151617',
          }}
        >
          <Sheet.Content>
            <Sheet.Header />
            <Sheet.Content>
              <Box bgcolor="background.paper" className="bottom-sheet-content">
              some elements
              </Box>
            </Sheet.Content>
          </Sheet.Content>
        </Sheet.Container>
      </Sheet>

Couldn't run with Next js

Hi @Temzasse
I intended to use your library with Nextjs and server side rendering but it seems it can't be done.
Even I importing using dynamic and ssr : false

Do you have any direction?

its too slow

This library has many options, it is perfect, but it is very slow when there is a lot of data in it and it creates problems especially in the performance of other component.

can you fix that and update library ? ๐Ÿ™

Issue, app breaking: strange dependency errors

Installing this in my project caused weird typescript errors in many components. Uninstalling the library and reinstalling yarn.lock and node_modules fixed the errors. It looks like the newest version of this library probably has some weird dependency bugs that need fixing ASAP to be usable for many users.

EDIT: After some debugging, I found that installing this library upgraded @types/react-dom in yarn.lock to v18 even though my react version was still v17. This was the cause of the bug.

image

allow for relative snappoints

Hey again,

noticed a bug:
If i set the snappoint to e.g 600 (which is the height i want to have on desktop) causes the mobile ui to "oversnap" the sheet out of the viewport (because the phone im testing this with has actually <600 px viewport height)

any ideas how to prevent this from happening?

Scroll conflicts when using inside a scrollable view

Hello,

first I would like to thank you for providing this nice and easy library, we never tell it enough to OpenSource contributors so thank you ๐Ÿ™๐Ÿป.


Now I would like to share two problems I've encountered using this library, to provide a bit of context I'm trying to build a marketplace and I would like at the same time I'm using the Infinite scroll to be able to pop this modal on top and having a scroll on it.

On that first problem, sometimes I'm taking control of the background instead of the first plan.

fixed.mp4

On that second one, you can see on IOS15 with the latest safari on an iPhone 12 pro-Max that I've some issue with the address bar, the utile fact is that I don't encounter this issue with https://temzasse.github.io/react-modal-sheet/#/scrollable for some reason, I've tried to compare my code and yours a bit seems not so different.

RPReplay_Final1632992159.mp4

I guess something might conflict somewhere, maybe you could help me or give me some insight ? :)

Thanks

set id for sheet for custom styling

could you allow us to set an id for the sheet container?

Right now this assumes that only one sheet would exist on the page, however i am using multiple (and want to have them different widths on desktop) but custom styling is always applied to the classes.

Something like <Sheet id="custom-sheet"></Sheet> should be enough

Edit: Seems like setting an id for sheet.container actually works, however typescript is unable to identify the props. Maybe you could update the PropTypes with React.ButtonHTMLAttributes and Framer-Motion Props?

Content in Sheet.Content does not scroll

In my use case, the content of the sheet exceeds the vertical view of the sheet. When I try to scroll on a phone or PC, the sheet refuses to allow me to scroll down and view the rest of the content. On PC scroll works 70% of the time. However, on Android or iOS scroll does not work at all.

Upon disablingDrag the content became scrollable again, however I am no longer able to close the sheet.
Furthermore, I noticed that the page behind the sheet scrolls as well when I try to scroll the content in the sheet itself.

Is there a solution that would allow me to be able to scroll the content and to also close the sheet?

Thanks.

Close immediately upon backdrop onTap

Is there a setting to make the bottom sheet close immediately when backdrop is tapped?

It seems there is a default delay in the animation when closing.
Opening is much quicker.

Thank you!

Feature request: add support for from top direction?

Hello,

I want to use the modal sheet as a swippable header, which is basically a mirrored version but probably also needs to handle the point events (like the header in the Playstation App).

Any idea or advice on how to make it? Thanks.

Hiding header at Full snap.

I used 3 snap points i.e [0.925, 0.53, 0.19], the starting snap point is 0.19.

in the modal I used all the elements like this...

I was trying to hide the header at full snap. How do I do that? I tried to get the snapIndex and use it but it is not giving the right index at each snap point.

translateY(calc(env(safe-area-inset-top) + 12px)) scale(0.954023); in rootId it does not work correctly

THANK'S!

first of all, give thanks for the great work

PROBLEM:

when I select rootId to do the iOS effect, the div generates excessive top margin, and it doesn't do the effect like this in the demo , I would like to know what I am doing wrong ?, embed my Tag <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, viewport-fit=cover"/>,
I am using Nextjs, and I try to use env () in the official site of next and it seems to have a similar behavior, maybe something specific to the DOMVirtual, maybe they should look for an alternative to that effect ...

image

env() CSS in Nextjs Page:

image

is it possible dynamic snap add point ?

how can i make dynamic size ?

image

image

and I found another issue !
when I set snap points like [0.9, 0.3,0] on Snap method not called correctly and return -1 or not called

please check this issue

Typescript Implementation Issue

Hi, I am really love your work for this react-modal-sheet. However, I face implementation issue. Would be great if you can take a look.

I know you are busy with your job, any help will be appreciated.

Typescript implementation with "react-modal-sheet": "^0.1.3",

Type '{}' is missing the following properties from type 'Pick<MotionProps, "inherit" | "transition" | "style" | "static" | "drag" | "onDrag" | "onDragEnd" | "onDragStart" | "onAnimationStart" | "transformTemplate" | "transformValues" | ... 31 more ... | "custom">': static, positionTransition, layoutTransition, _dragValueX, and 4 more.

    17 |       <Sheet isOpen={true} onClose={() => {}}>
  > 18 |         <Sheet.Backdrop />
       |          ^
    19 |       </Sheet>

Any idea for this issue?

Thanks

Request: allow users to pass in onDragEnd, onDragStart, onDrag callbacks

Use case: After drag I would like my component to internally update its state and track that the Sheet is opened at 600.

Interface could be something like ...

const Component = () => {
  // closed initially
  const [sheetPosition, setSheetPosition] = useState(0);

  return (
    <Sheet
      snapPoints={[600, 400, 100])
      onDragEnd={({snapPoint}) => {
        setSheetPosition(snapPoint);
      }}
    > 
      ... 
    </Sheet>
  );
}

On https://github.com/Temzasse/react-modal-sheet/blob/master/src/sheet.tsx#L82 we can add a

if (props.onDragEnd) {
  props.onDragEnd({snapPoint})
}

What do you think? Happy to contribute if you think this makes sense or if there's potentially a way for me to know the snappoint without passing in a callback!

Black space appears when mobile keyboard is visible

If we will try to pull the sheet upwards forcefully Black space appears when the mobile keyboard is visible. Black space is visible just above the keyboard.

Browser(Android)- Chrome(92.0.4515.159)
Browser(iOS)- Safari
react-model-sheet (v1.4.1)

In iOS
IMG_2338

In Android
Screenshot_2021-09-10-13-41-07-648_com android chrome

Syntax Suggestions

What do you think about syntax like this?

import { useSheet, SheetProvider } from 'react-modal-sheet'

// root of app
<SheetProvider>
  <XSheet />
  <SomewhereElseInApp />
</SheetProvider>


const SomewhereElseInApp = (props) => {
  const {
    openSheet, // this would default to opening sheet-1
	setSnapPoint,
    closeSheet,
  // all the options below are optional including the ID since you can pass the ID to openSheet
  } = useSheet('x-sheet-id', {
	onClose,
	onOpenStart,
	onOpenEnd,
	onCloseStart,
	onCloseEnd,
    onSnap,
  })
  return (
    <>
      <div onClick={() => openSheet('sheet-id-2')}>Opens Sheet 2 with no props</div>
      <div onClick={() => openSheet('sheet-id-1', props)}>Opens Sheet 1 and passes props to it</div>
      <div onClick={() => openSheet({ name: 'cool', ...props })}>Opens X Sheet and passes props to it</div>
    </>
  )
}


import { Sheet } from 'react-modal-sheet'

const XSheet = () => {
  const {
    isOpen,
    ...props // these are the props passed from `openSheet`
  } = useSheet('x-sheet-id')
  return (
    <Sheet
      id={'x-sheet-id'}
	  isOpen={isOpen}
      {...props}
    >
      {/* whatever content for the sheet */}
    </Sheet>
  )
}

This allows us to reuse the same action sheet throughout the app only having to import it once, and passing different props to it wherever we want to use it.

Scroll overflow content without modal moving

There are two issues I want to bring up:

I have a modal with limited height, and the content inside is overflowing. I want to be able to scroll the content inside using touch on mobile, but the modal moves everything I try to scroll the content. Ideally only when I drag the header should the modal move.

I expect that setting disableDrag={true} prop on <Sheet.Content/> should fix the issue by preventing the entire modal from moving when pointer events are directed towards the content, but it doesn't seem to change anything.

I am only able to scroll the content using touch when disableDrag={true} is set on the parent <Sheet/>. This is my attempted temporary solution using a hook. The hook is updating properly, but <Sheet> doesn't update in response to the updated prop?

const [isDragDisabled, setIsDragDisabled] = useState(true);

...

<Sheet disableDrag={isDragDisabled} onClose={onClose} isOpen={isOpen}>
	<Sheet.Container>
		<div onPointerEnter={() => setIsDragDisabled(false)} onPointerLeave={() => setIsDragDisabled(true)}>
			<Sheet.Header />
		</div>
		<Sheet.Content>{children}</Sheet.Content>
	</Sheet.Container>
</Sheet>

Server Side Rendering

Hey,

currently using this component for SSR is impossible because src/sheet.tsx uses ReactDOM.createPortal(... document.body)
document is undefined inside SSR (in my case next.js)

Scrolling & Snap points: Unable to scroll to bottom while snap

Hi! First of all, thank you for this awesome library ๐Ÿค˜ I'm having an issue with the scroll behaviour and was wondering if you have any idea on how to solve this.

I have a modal-sheet with 2 positions: 'Full' screen and half screen. snapPoints = [-50, 0.5]. Inside the modal-sheet there's content with a height that's smaller than the 'full' screen modal-sheet, but bigger than the half screen modal-sheet. This results in the content not being scrollable while the modal-sheet is in half screen since the content isn't really overflowing.

Do you have any idea on how to make that work?

Thank you in advance.

animation or smooth opening transition does not occur when opening the modal

problem can be seen here

my code is like this :
BottomSheetModal.js

import React from 'react';
import Sheet from 'react-modal-sheet';
import { isMobile } from 'helpers/CommonHelper';
import BottomContentModal from './BottomContentModal';

function BottomSheetModal ( {
modalRef,
addClass,
modalClass,
header,
content,
isOpen,
onClose,
snapPoints,
initialSnap,
springConfig } )
{
const mainClass= "modal-main ";
const ref = React.useRef();
const mobile = ( window.innerWidth <= 960 ) ;

const modalClose = () => {
$('html').removeClass('open-bottom-sheet-modal');
onClose();
}
React.useEffect(()=>{
if(mobile){
$('html').removeClass('popup-open');
$('html').addClass('open-bottom-sheet-modal');
}
},[])

if (mobile)
{
return (

<Sheet.Container>
{ header ?
<Sheet.Header>
{header}
</Sheet.Header> :
<Sheet.Header/>
}
<Sheet.Content style={{ paddingBottom: ref.current?.y }} >
<BottomContentModal
content={content && content}
modalClasseName={"modal "+modalClass +" "+ ( addClass ? addClass:" ")}
ref={modalRef} />
</Sheet.Content>
</Sheet.Container>
<Sheet.Backdrop />

)
}
if(!mobile)
{
let newContent = <React.Fragment>
{header && header}
{content && content}
</React.Fragment>
return (
<BottomContentModal content={newContent} modalClasseName={mainClass + modalClass + " " + ( addClass ? addClass:" ")} ref={modalRef} />
);
}
}
export default BottomSheetModal;

any idea what it could be?

Navigate to a page where sheet is already open skips opening animation

I tried to open the bottom sheet as soon as the user navigates to a page with the following code:

//on PageOne.tsx
<Link href="/book/123"> Go to next page (PageTwo)</Link>

//PageTwo.tsx
export const PageTwo: NextPage = () => {
    const [isOpen, setIsOpen] = useState(true);
    return(
        <>
            <BottomSheet isOpen={isOpen} onClose={setIsOpen} >
                Hello!
            </BottomSheet>
        </>
    )
}

But when PageTwo is reached, the opening animation has already happened and can't be seen. Here's a small video I recorded to illustrate:

RPReplay_Final1656884433.mp4

How can I make the user see this animation? I've worked with another bottom sheet lib in the past and that was possible, is there any animation config I'm missing?
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.