react-modal-sheet 1.11.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -56,7 +56,6 @@ function Example() {
56
56
  <Sheet.Header />
57
57
  <Sheet.Content>{/* Your sheet content goes here */}</Sheet.Content>
58
58
  </Sheet.Container>
59
-
60
59
  <Sheet.Backdrop />
61
60
  </Sheet>
62
61
  </>
@@ -66,42 +65,30 @@ function Example() {
66
65
 
67
66
  The `Sheet` component follows the [Compound Component pattern](https://kentcdodds.com/blog/compound-components-with-react-hooks) in order to provide a flexible yet powerful API for creating highly customizable bottom sheet components.
68
67
 
69
- Since the final bottom sheet is composed from smaller building blocks (`Container`, `Content`, `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? 😎
68
+ 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? 😎
70
69
 
71
70
  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](#Accessibility) section.
72
71
 
73
72
  ## Props
74
73
 
75
- | Name | Required | Default | Description |
76
- | ---------------------- | -------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
77
- | `children` | yes | | Use `Sheet.Container/Content/Header/Backdrop` to compose your bottom sheet. |
78
- | `isOpen` | yes | | Boolean that indicates whether the sheet is open or not. |
79
- | `onClose` | yes | | Callback fn that is called when the sheet is closed by the user. |
80
- | `disableDrag` | no | false | Disable drag for the whole sheet. |
81
- | `detent` | no | `'full-height'` | The [detent](https://developer.apple.com/design/human-interface-guidelines/components/presentation/sheets#ios-ipados) in which the sheet should be in when opened. Available values: `'full-height'` or `'content-height'`. |
82
- | `onOpenStart` | no | | Callback fn that is called when the sheet opening animation starts. |
83
- | `onOpenEnd` | no | | Callback fn that is called when the sheet opening animation is completed. |
84
- | `onCloseStart` | no | | Callback fn that is called when the sheet closing animation starts. |
85
- | `onCloseEnd` | no | | Callback fn that is called when the sheet closing animation is completed. |
86
- | `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. |
87
- | `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. |
88
- | `initialSnap` | no | 0 | Initial snap point when sheet is opened (index from `snapPoints`). |
89
- | `rootId` | no | | The id of the element where the main app is mounted, eg. "root". Enables iOS modal effect. |
90
- | `springConfig` | no | `DEFAULT_SPRING` | Overrides the config for the [spring animation](https://www.framer.com/api/motion/types/#spring). |
91
- | `mountPoint` | no | `document.body` | HTML element that should be used as the mount point for the sheet. |
92
- | `prefersReducedMotion` | no | false | Skip sheet animations (sheet instantly snaps to desired location). |
93
-
94
- **`DEFAULT_SPRING`**
95
-
96
- Spring animation happens when the sheet is opened/closed or when it snaps to a snap point.
97
-
98
- ```js
99
- {
100
- stiffness: 300,
101
- damping: 30,
102
- mass: 0.2,
103
- }
104
- ```
74
+ | Name | Required | Default | Description |
75
+ | ---------------------- | -------- | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
76
+ | `children` | yes | | Use `Sheet.Container/Content/Header/Backdrop` to compose your bottom sheet. |
77
+ | `isOpen` | yes | | Boolean that indicates whether the sheet is open or not. |
78
+ | `onClose` | yes | | Callback fn that is called when the sheet is closed by the user. |
79
+ | `disableDrag` | no | false | Disable drag for the whole sheet. |
80
+ | `detent` | no | `'full-height'` | The [detent](https://developer.apple.com/design/human-interface-guidelines/components/presentation/sheets#ios-ipados) in which the sheet should be in when opened. Available values: `'full-height'` or `'content-height'`. |
81
+ | `onOpenStart` | no | | Callback fn that is called when the sheet opening animation starts. |
82
+ | `onOpenEnd` | no | | Callback fn that is called when the sheet opening animation is completed. |
83
+ | `onCloseStart` | no | | Callback fn that is called when the sheet closing animation starts. |
84
+ | `onCloseEnd` | no | | Callback fn that is called when the sheet closing animation is completed. |
85
+ | `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. |
86
+ | `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. |
87
+ | `initialSnap` | no | 0 | Initial snap point when sheet is opened (index from `snapPoints`). |
88
+ | `rootId` | no | | The id of the element where the main app is mounted, eg. "root". Enables iOS modal effect. |
89
+ | `tweenConfig` | no | `{ ease: 'easeOut', duration: 0.2 }` | Overrides the config for the sheet [tween](https://www.framer.com/motion/transition/#tween) transition when the sheet is opened, closed, or snapped to a point. |
90
+ | `mountPoint` | no | `document.body` | HTML element that should be used as the mount point for the sheet. |
91
+ | `prefersReducedMotion` | no | false | Skip sheet animations (sheet instantly snaps to desired location). |
105
92
 
106
93
  ## Methods and properties
107
94
 
@@ -183,7 +170,9 @@ function Example() {
183
170
  * way to the bottom in every snap point.
184
171
  */}
185
172
  <Sheet.Content style={{ paddingBottom: ref.current?.y }}>
186
- {/* Some content here that makes the sheet content scrollable */}
173
+ <Sheet.Scroller draggableAt="both">
174
+ {/* Some content here that makes the sheet content scrollable */}
175
+ </Sheet.Scroller>
187
176
  </Sheet.Content>
188
177
  </Sheet.Container>
189
178
  </Sheet>
@@ -208,7 +197,9 @@ function Example() {
208
197
  >
209
198
  <Sheet.Container>
210
199
  <Sheet.Content>
211
- <div style={{ height: 200 }}>Some content</div>
200
+ <Sheet.Scroller>
201
+ <div style={{ height: 200 }}>Some content</div>
202
+ </Sheet.Scroller>
212
203
  </Sheet.Content>
213
204
  </Sheet.Container>
214
205
  </Sheet>
@@ -236,9 +227,21 @@ Sheet container is positioned above the sheet backdrop and by default adds a sma
236
227
 
237
228
  > 🖥 Rendered element: `motion.div`.
238
229
 
230
+ ### `Sheet.Header`
231
+
232
+ Sheet header acts as a drag target and has a dragging direction indicator. Rendering any children inside `Sheet.Header` replaces the default header.
233
+
234
+ > 🖥 Rendered element: `motion.div`.
235
+
236
+ #### Header props
237
+
238
+ | Name | Required | Default | Description |
239
+ | ------------- | -------- | ------- | ---------------------------------- |
240
+ | `disableDrag` | no | false | Disable drag for the sheet header. |
241
+
239
242
  ### `Sheet.Content`
240
243
 
241
- Sheet content acts as a drag target and makes sure that content which doesn't fit inside the sheet becomes scrollable.
244
+ 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.
242
245
 
243
246
  > 🖥 Rendered element: `motion.div`.
244
247
 
@@ -248,26 +251,56 @@ Sheet content acts as a drag target and makes sure that content which doesn't fi
248
251
  | ------------- | -------- | ------- | ----------------------------------- |
249
252
  | `disableDrag` | no | false | Disable drag for the sheet content. |
250
253
 
251
- ### `Sheet.Header`
254
+ ### `Sheet.Scroller`
252
255
 
253
- Sheet header acts as a drag target and has a dragging direction indicator. Rendering any children inside `Sheet.Header` replaces the default header.
256
+ 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](#scrolling-on-touch-devices) section for more details.
254
257
 
255
258
  > 🖥 Rendered element: `motion.div`.
256
259
 
257
- #### Header props
260
+ #### Scroller props
258
261
 
259
- | Name | Required | Default | Description |
260
- | ------------- | -------- | ------- | ---------------------------------- |
261
- | `disableDrag` | no | false | Disable drag for the sheet header. |
262
+ | Name | Required | Default | Description |
263
+ | ------------- | -------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------- |
264
+ | `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`. |
262
265
 
263
266
  ### `Sheet.Backdrop`
264
267
 
265
- 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 click/tap handlers to it which will change the rendered element from `div` to `button`.
268
+ 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`.
269
+
270
+ ⚠️ **Note:** as the element is a motion component you need to use [`onTap`](https://www.framer.com/motion/gestures/#tap) instead of `onClick` if you want to add a click handler to it.
266
271
 
267
272
  > 🖥 Rendered element: `motion.div` or `motion.button`.
268
273
 
269
274
  ## Advanced usage
270
275
 
276
+ ### Scrolling on touch devices
277
+
278
+ 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:
279
+
280
+ 1. Enable dragging when the scroller is not yet scrolled - it is at the `top` position.
281
+ 2. Enable dragging when the scroller is scrolled all the way to the bottom - it is at the `bottom` position.
282
+ 3. Enable dragging in `both` positions.
283
+
284
+ 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.
285
+
286
+ 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.
287
+
288
+ ```jsx
289
+ function ScrollableExample() {
290
+ return (
291
+ <Sheet>
292
+ <Sheet.Container>
293
+ <Sheet.Header />
294
+ <Sheet.Content>
295
+ <Sheet.Scroller>{/*...*/}</Sheet.Scroller>
296
+ </Sheet.Content>
297
+ </Sheet.Container>
298
+ <Sheet.Backdrop />
299
+ </Sheet>
300
+ );
301
+ }
302
+ ```
303
+
271
304
  ### iOS Modal View effect
272
305
 
273
306
  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](https://developer.apple.com/design/human-interface-guidelines/ios/app-architecture/modality/) presentation style to bring more focus to the sheet and add some delight to the user experience.
@@ -302,10 +335,8 @@ function Example() {
302
335
  <Sheet.Header>
303
336
  <YourCustomSheetHeader />
304
337
  </Sheet.Header>
305
-
306
338
  <Sheet.Content>{/*...*/}</Sheet.Content>
307
339
  </Sheet.Container>
308
-
309
340
  <Sheet.Backdrop />
310
341
  </Sheet>
311
342
  );
@@ -369,12 +400,11 @@ function Example() {
369
400
  <button onClick={() => setOpen(true)}>Open sheet</button>
370
401
 
371
402
  <CustomSheet isOpen={isOpen} onClose={() => setOpen(false)}>
372
- <CustomSheet.Container>
373
- <CustomSheet.Header />
374
- <CustomSheet.Content>{/*...*/}</CustomSheet.Content>
375
- </CustomSheet.Container>
376
-
377
- <CustomSheet.Backdrop />
403
+ <Sheet.Container>
404
+ <Sheet.Header />
405
+ <Sheet.Content>{/*...*/}</Sheet.Content>
406
+ </Sheet.Container>
407
+ <Sheet.Backdrop />
378
408
  </CustomSheet>
379
409
  </>
380
410
  );
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
2
  declare const SheetBackdrop: React.ForwardRefExoticComponent<Pick<import("framer-motion").MotionProps & {
3
3
  className?: string | undefined;
4
- }, "style" | "transformTemplate" | "transformValues" | "children" | "variants" | "transition" | "onBeforeLayoutMeasure" | "onLayoutMeasure" | "onUpdate" | "onAnimationStart" | "onAnimationComplete" | "onPan" | "onPanStart" | "onPanSessionStart" | "onPanEnd" | "onTap" | "onTapStart" | "onTapCancel" | "whileTap" | "whileHover" | "onHoverStart" | "onHoverEnd" | "whileFocus" | "whileInView" | "onViewportEnter" | "onViewportLeave" | "viewport" | "drag" | "whileDrag" | "dragDirectionLock" | "dragPropagation" | "dragConstraints" | "dragElastic" | "dragMomentum" | "dragTransition" | "dragControls" | "dragSnapToOrigin" | "dragListener" | "onMeasureDragConstraints" | "_dragX" | "_dragY" | "onDragStart" | "onDragEnd" | "onDrag" | "onDirectionLock" | "onDragTransitionEnd" | "layout" | "layoutId" | "onLayoutAnimationStart" | "onLayoutAnimationComplete" | "layoutDependency" | "layoutScroll" | "custom" | "inherit" | "className"> & React.RefAttributes<any>>;
4
+ }, "style" | "transformTemplate" | "transformValues" | "children" | "variants" | "transition" | "onBeforeLayoutMeasure" | "onLayoutMeasure" | "onUpdate" | "onAnimationStart" | "onAnimationComplete" | "onPan" | "onPanStart" | "onPanSessionStart" | "onPanEnd" | "onTap" | "onTapStart" | "onTapCancel" | "whileTap" | "whileHover" | "onHoverStart" | "onHoverEnd" | "whileFocus" | "whileInView" | "onViewportEnter" | "onViewportLeave" | "viewport" | "drag" | "whileDrag" | "dragDirectionLock" | "dragPropagation" | "dragConstraints" | "dragElastic" | "dragMomentum" | "dragTransition" | "dragControls" | "dragSnapToOrigin" | "dragListener" | "onMeasureDragConstraints" | "_dragX" | "_dragY" | "onDragStart" | "onDragEnd" | "onDrag" | "onDirectionLock" | "onDragTransitionEnd" | "layout" | "layoutId" | "onLayoutAnimationStart" | "onLayoutAnimationComplete" | "layoutDependency" | "layoutScroll" | "layoutRoot" | "custom" | "inherit" | "ignoreStrict" | "className"> & React.RefAttributes<any>>;
5
5
  export default SheetBackdrop;
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  declare const SheetContainer: React.ForwardRefExoticComponent<Pick<import("framer-motion").MotionProps & {
3
3
  className?: string | undefined;
4
- }, "style" | "transformTemplate" | "transformValues" | "children" | "variants" | "transition" | "onBeforeLayoutMeasure" | "onLayoutMeasure" | "onUpdate" | "onAnimationStart" | "onPan" | "onPanStart" | "onPanSessionStart" | "onPanEnd" | "onTap" | "onTapStart" | "onTapCancel" | "whileTap" | "whileHover" | "onHoverStart" | "onHoverEnd" | "whileFocus" | "whileInView" | "onViewportEnter" | "onViewportLeave" | "viewport" | "drag" | "whileDrag" | "dragDirectionLock" | "dragPropagation" | "dragConstraints" | "dragElastic" | "dragMomentum" | "dragTransition" | "dragControls" | "dragSnapToOrigin" | "dragListener" | "onMeasureDragConstraints" | "_dragX" | "_dragY" | "onDragStart" | "onDragEnd" | "onDrag" | "onDirectionLock" | "onDragTransitionEnd" | "layout" | "layoutId" | "onLayoutAnimationStart" | "onLayoutAnimationComplete" | "layoutDependency" | "layoutScroll" | "custom" | "inherit" | "className"> & {
4
+ }, "style" | "transformTemplate" | "transformValues" | "children" | "variants" | "transition" | "onBeforeLayoutMeasure" | "onLayoutMeasure" | "onUpdate" | "onAnimationStart" | "onPan" | "onPanStart" | "onPanSessionStart" | "onPanEnd" | "onTap" | "onTapStart" | "onTapCancel" | "whileTap" | "whileHover" | "onHoverStart" | "onHoverEnd" | "whileFocus" | "whileInView" | "onViewportEnter" | "onViewportLeave" | "viewport" | "drag" | "whileDrag" | "dragDirectionLock" | "dragPropagation" | "dragConstraints" | "dragElastic" | "dragMomentum" | "dragTransition" | "dragControls" | "dragSnapToOrigin" | "dragListener" | "onMeasureDragConstraints" | "_dragX" | "_dragY" | "onDragStart" | "onDragEnd" | "onDrag" | "onDirectionLock" | "onDragTransitionEnd" | "layout" | "layoutId" | "onLayoutAnimationStart" | "onLayoutAnimationComplete" | "layoutDependency" | "layoutScroll" | "layoutRoot" | "custom" | "inherit" | "ignoreStrict" | "className"> & {
5
5
  children: React.ReactNode;
6
6
  } & React.RefAttributes<any>>;
7
7
  export default SheetContainer;
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  declare const SheetContent: React.ForwardRefExoticComponent<Pick<import("framer-motion").MotionProps & {
3
3
  className?: string | undefined;
4
- }, "style" | "transformTemplate" | "transformValues" | "children" | "initial" | "animate" | "exit" | "variants" | "transition" | "onBeforeLayoutMeasure" | "onLayoutMeasure" | "onUpdate" | "onAnimationStart" | "onAnimationComplete" | "onPan" | "onPanStart" | "onPanSessionStart" | "onPanEnd" | "onTap" | "onTapStart" | "onTapCancel" | "whileTap" | "whileHover" | "onHoverStart" | "onHoverEnd" | "whileFocus" | "whileInView" | "onViewportEnter" | "onViewportLeave" | "viewport" | "whileDrag" | "dragDirectionLock" | "dragPropagation" | "dragTransition" | "dragControls" | "dragSnapToOrigin" | "dragListener" | "onMeasureDragConstraints" | "_dragX" | "_dragY" | "onDirectionLock" | "onDragTransitionEnd" | "layout" | "layoutId" | "onLayoutAnimationStart" | "onLayoutAnimationComplete" | "layoutDependency" | "layoutScroll" | "custom" | "inherit" | "className"> & {
4
+ }, "style" | "transformTemplate" | "transformValues" | "children" | "initial" | "animate" | "exit" | "variants" | "transition" | "onBeforeLayoutMeasure" | "onLayoutMeasure" | "onUpdate" | "onAnimationStart" | "onAnimationComplete" | "onPan" | "onPanStart" | "onPanSessionStart" | "onPanEnd" | "onTap" | "onTapStart" | "onTapCancel" | "whileTap" | "whileHover" | "onHoverStart" | "onHoverEnd" | "whileFocus" | "whileInView" | "onViewportEnter" | "onViewportLeave" | "viewport" | "whileDrag" | "dragDirectionLock" | "dragPropagation" | "dragTransition" | "dragControls" | "dragSnapToOrigin" | "dragListener" | "onMeasureDragConstraints" | "_dragX" | "_dragY" | "onDirectionLock" | "onDragTransitionEnd" | "layout" | "layoutId" | "onLayoutAnimationStart" | "onLayoutAnimationComplete" | "layoutDependency" | "layoutScroll" | "layoutRoot" | "custom" | "inherit" | "ignoreStrict" | "className"> & {
5
5
  children?: React.ReactNode;
6
6
  disableDrag?: boolean | undefined;
7
7
  } & React.RefAttributes<any>>;
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  declare const SheetHeader: React.ForwardRefExoticComponent<Pick<import("framer-motion").MotionProps & {
3
3
  className?: string | undefined;
4
- }, "style" | "transformTemplate" | "transformValues" | "children" | "initial" | "animate" | "exit" | "variants" | "transition" | "onBeforeLayoutMeasure" | "onLayoutMeasure" | "onUpdate" | "onAnimationStart" | "onAnimationComplete" | "onPan" | "onPanStart" | "onPanSessionStart" | "onPanEnd" | "onTap" | "onTapStart" | "onTapCancel" | "whileTap" | "whileHover" | "onHoverStart" | "onHoverEnd" | "whileFocus" | "whileInView" | "onViewportEnter" | "onViewportLeave" | "viewport" | "whileDrag" | "dragDirectionLock" | "dragPropagation" | "dragTransition" | "dragControls" | "dragSnapToOrigin" | "dragListener" | "onMeasureDragConstraints" | "_dragX" | "_dragY" | "onDirectionLock" | "onDragTransitionEnd" | "layout" | "layoutId" | "onLayoutAnimationStart" | "onLayoutAnimationComplete" | "layoutDependency" | "layoutScroll" | "custom" | "inherit" | "className"> & {
4
+ }, "style" | "transformTemplate" | "transformValues" | "children" | "initial" | "animate" | "exit" | "variants" | "transition" | "onBeforeLayoutMeasure" | "onLayoutMeasure" | "onUpdate" | "onAnimationStart" | "onAnimationComplete" | "onPan" | "onPanStart" | "onPanSessionStart" | "onPanEnd" | "onTap" | "onTapStart" | "onTapCancel" | "whileTap" | "whileHover" | "onHoverStart" | "onHoverEnd" | "whileFocus" | "whileInView" | "onViewportEnter" | "onViewportLeave" | "viewport" | "whileDrag" | "dragDirectionLock" | "dragPropagation" | "dragTransition" | "dragControls" | "dragSnapToOrigin" | "dragListener" | "onMeasureDragConstraints" | "_dragX" | "_dragY" | "onDirectionLock" | "onDragTransitionEnd" | "layout" | "layoutId" | "onLayoutAnimationStart" | "onLayoutAnimationComplete" | "layoutDependency" | "layoutScroll" | "layoutRoot" | "custom" | "inherit" | "ignoreStrict" | "className"> & {
5
5
  children?: React.ReactNode;
6
6
  disableDrag?: boolean | undefined;
7
7
  } & React.RefAttributes<any>>;
@@ -0,0 +1,5 @@
1
+ import * as React from 'react';
2
+ declare const SheetScroller: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & {
3
+ draggableAt?: "top" | "bottom" | "both" | undefined;
4
+ } & React.RefAttributes<any>>;
5
+ export default SheetScroller;
@@ -1,9 +1,12 @@
1
1
  export declare const MAX_HEIGHT = "calc(100% - env(safe-area-inset-top) - 34px)";
2
2
  export declare const IS_SSR: boolean;
3
- export declare const DEFAULT_SPRING_CONFIG: {
4
- stiffness: number;
5
- damping: number;
6
- mass: number;
3
+ export declare const DEFAULT_TWEEN_CONFIG: {
4
+ ease: string;
5
+ duration: number;
6
+ };
7
+ export declare const REDUCED_MOTION_TWEEN_CONFIG: {
8
+ ease: string;
9
+ duration: number;
7
10
  };
8
11
  export declare const DRAG_CLOSE_THRESHOLD = 0.6;
9
12
  export declare const DRAG_VELOCITY_THRESHOLD = 500;
package/dist/context.d.ts CHANGED
@@ -1,4 +1,9 @@
1
1
  import * as React from 'react';
2
- import { SheetContextType } from './types';
2
+ import { SheetScrollerContextType, SheetContextType } from './types';
3
3
  export declare const SheetContext: React.Context<SheetContextType | undefined>;
4
4
  export declare const useSheetContext: () => SheetContextType;
5
+ export declare const SheetScrollerContext: React.Context<SheetScrollerContextType | undefined>;
6
+ export declare function SheetScrollerContextProvider({ children, }: {
7
+ children: React.ReactNode;
8
+ }): React.JSX.Element;
9
+ export declare const useSheetScrollerContext: () => SheetScrollerContextType;
package/dist/hooks.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import * as React from 'react';
2
2
  import { SheetEvents } from './types';
3
3
  export declare const useIsomorphicLayoutEffect: typeof React.useEffect;
4
- export declare const useModalEffect: (isOpen: boolean, rootId?: string | undefined) => void;
5
- export declare const useEventCallbacks: (isOpen: boolean, callbacks: React.MutableRefObject<SheetEvents>) => {
4
+ export declare function useModalEffect(isOpen: boolean, rootId?: string): void;
5
+ export declare function useEventCallbacks(isOpen: boolean, callbacks: React.MutableRefObject<SheetEvents>): {
6
6
  handleAnimationComplete: () => void;
7
7
  };
8
8
  export declare function useWindowHeight(): number;
@@ -42,10 +42,13 @@ function _objectWithoutPropertiesLoose(source, excluded) {
42
42
 
43
43
  var MAX_HEIGHT = 'calc(100% - env(safe-area-inset-top) - 34px)';
44
44
  var IS_SSR = typeof window === 'undefined';
45
- var DEFAULT_SPRING_CONFIG = {
46
- stiffness: 300,
47
- damping: 30,
48
- mass: 0.2
45
+ var DEFAULT_TWEEN_CONFIG = {
46
+ ease: 'easeOut',
47
+ duration: 0.2
48
+ };
49
+ var REDUCED_MOTION_TWEEN_CONFIG = {
50
+ ease: 'linear',
51
+ duration: 0.01
49
52
  };
50
53
  var DRAG_CLOSE_THRESHOLD = 0.6;
51
54
  var DRAG_VELOCITY_THRESHOLD = 500;
@@ -65,7 +68,6 @@ function getClosest(nums, goal) {
65
68
 
66
69
  return closest;
67
70
  }
68
- var highlightId = 'react-modal-sheet-highlight';
69
71
  function applyRootStyles(rootId) {
70
72
  var body = document.querySelector('body');
71
73
  var root = document.querySelector("#" + rootId);
@@ -77,51 +79,30 @@ function applyRootStyles(rootId) {
77
79
  body.style.backgroundColor = '#000';
78
80
  root.style.overflow = 'hidden';
79
81
  root.style.willChange = 'transform';
80
- root.style.transition = 'transform 200ms linear';
82
+ root.style.transition = 'transform 200ms ease-in-out, border-radius 200ms linear';
81
83
  root.style.transform = "translateY(calc(env(safe-area-inset-top) + " + p / 2 + "px)) scale(" + s + ")"; // prettier-ignore
82
84
 
83
85
  root.style.borderTopRightRadius = '10px';
84
- root.style.borderTopLeftRadius = '10px'; // Add highlighed overlay to emphasize the modality effect
85
-
86
- var highlight = document.createElement('div');
87
- highlight.setAttribute('aria-hidden', 'true');
88
- highlight.id = highlightId;
89
- highlight.style.position = 'absolute';
90
- highlight.style.top = '0px';
91
- highlight.style.left = '0px';
92
- highlight.style.bottom = '0px';
93
- highlight.style.right = '0px';
94
- highlight.style.opacity = '0';
95
- highlight.style.transition = 'opacity 200ms linear';
96
- highlight.style.backgroundColor = 'rgba(150, 150, 150, 0.1)';
97
- root.appendChild(highlight);
98
- requestAnimationFrame(function () {
99
- return highlight.style.opacity = '1';
100
- });
86
+ root.style.borderTopLeftRadius = '10px';
101
87
  }
102
88
  }
103
89
  function cleanupRootStyles(rootId) {
104
90
  var body = document.querySelector('body');
105
91
  var root = document.getElementById(rootId);
106
- var highlight = document.getElementById(highlightId);
107
92
 
108
93
  function onTransitionEnd() {
109
- var _highlight$parentNode;
110
-
111
94
  root.style.removeProperty('overflow');
112
95
  root.style.removeProperty('will-change');
113
96
  root.style.removeProperty('transition');
114
97
  body.style.removeProperty('background-color');
115
98
  root.removeEventListener('transitionend', onTransitionEnd);
116
- (_highlight$parentNode = highlight.parentNode) == null ? void 0 : _highlight$parentNode.removeChild(highlight);
117
99
  }
118
100
 
119
- if (root && highlight) {
101
+ if (root) {
120
102
  // Start animating back
121
103
  root.style.removeProperty('border-top-right-radius');
122
104
  root.style.removeProperty('border-top-left-radius');
123
- root.style.removeProperty('transform');
124
- highlight.style.opacity = '0'; // Remove temp properties after animation is finished
105
+ root.style.removeProperty('transform'); // Remove temp properties after animation is finished
125
106
 
126
107
  root.addEventListener('transitionend', onTransitionEnd);
127
108
  }
@@ -154,9 +135,13 @@ function mergeRefs(refs) {
154
135
  });
155
136
  };
156
137
  }
138
+ function isTouchDevice() {
139
+ if (typeof window === 'undefined') return false;
140
+ return 'ontouchstart' in window || navigator.maxTouchPoints > 0;
141
+ }
157
142
 
158
143
  var useIsomorphicLayoutEffect = IS_SSR ? React.useEffect : React.useLayoutEffect;
159
- var useModalEffect = function useModalEffect(isOpen, rootId) {
144
+ function useModalEffect(isOpen, rootId) {
160
145
  var prevOpen = usePrevious(isOpen); // Automatically apply the iOS modal effect to the body when sheet opens/closes
161
146
 
162
147
  React.useEffect(function () {
@@ -173,8 +158,8 @@ var useModalEffect = function useModalEffect(isOpen, rootId) {
173
158
  if (rootId && isOpen) cleanupRootStyles(rootId);
174
159
  };
175
160
  }, [isOpen]); // eslint-disable-line
176
- };
177
- var useEventCallbacks = function useEventCallbacks(isOpen, callbacks) {
161
+ }
162
+ function useEventCallbacks(isOpen, callbacks) {
178
163
  var prevOpen = usePrevious(isOpen);
179
164
  var didOpen = React.useRef(false); // Because of AnimatePrecence we don't have access to latest isOpen value
180
165
  // so we need to read and write to a ref to determine if we are
@@ -201,7 +186,7 @@ var useEventCallbacks = function useEventCallbacks(isOpen, callbacks) {
201
186
  return {
202
187
  handleAnimationComplete: handleAnimationComplete
203
188
  };
204
- };
189
+ }
205
190
  function useWindowHeight() {
206
191
  var _React$useState = React.useState(0),
207
192
  windowHeight = _React$useState[0],
@@ -251,6 +236,36 @@ var useSheetContext = function useSheetContext() {
251
236
  if (!context) throw Error('Sheet context error');
252
237
  return context;
253
238
  };
239
+ var SheetScrollerContext = /*#__PURE__*/React.createContext(undefined);
240
+ function SheetScrollerContextProvider(_ref) {
241
+ var children = _ref.children;
242
+ var sheetContext = useSheetContext();
243
+
244
+ var _React$useState = React.useState(!!sheetContext.disableDrag),
245
+ disableDrag = _React$useState[0],
246
+ setDisableDrag = _React$useState[1];
247
+
248
+ function setDragEnabled() {
249
+ if (!sheetContext.disableDrag) setDisableDrag(false);
250
+ }
251
+
252
+ function setDragDisabled() {
253
+ if (!disableDrag) setDisableDrag(true);
254
+ }
255
+
256
+ return React.createElement(SheetScrollerContext.Provider, {
257
+ value: {
258
+ disableDrag: disableDrag,
259
+ setDragEnabled: setDragEnabled,
260
+ setDragDisabled: setDragDisabled
261
+ }
262
+ }, children);
263
+ }
264
+ var useSheetScrollerContext = function useSheetScrollerContext() {
265
+ var context = React.useContext(SheetScrollerContext);
266
+ if (!context) throw Error('Sheet scroller context error');
267
+ return context;
268
+ };
254
269
 
255
270
  // Source: https://github.com/adobe/react-spectrum/blob/main/packages/@react-aria/overlays/src/usePreventScroll.ts
256
271
 
@@ -510,7 +525,7 @@ var styles = {
510
525
  left: 0,
511
526
  width: '100%',
512
527
  height: '100%',
513
- backgroundColor: 'rgba(51, 51, 51, 0.5)',
528
+ backgroundColor: 'rgba(0, 0, 0, 0.2)',
514
529
  touchAction: 'none',
515
530
  border: 'none'
516
531
  },
@@ -546,9 +561,15 @@ var styles = {
546
561
  backgroundColor: '#ddd'
547
562
  },
548
563
  content: {
549
- flex: 1,
550
- overflow: 'auto',
564
+ flexGrow: 1,
565
+ display: 'flex',
566
+ flexDirection: 'column',
567
+ minHeight: '0px',
551
568
  position: 'relative'
569
+ },
570
+ scroller: {
571
+ height: '100%',
572
+ overflowY: 'auto'
552
573
  }
553
574
  };
554
575
 
@@ -569,28 +590,27 @@ var Sheet = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
569
590
  detent = _ref$detent === void 0 ? 'full-height' : _ref$detent,
570
591
  _ref$initialSnap = _ref.initialSnap,
571
592
  initialSnap = _ref$initialSnap === void 0 ? 0 : _ref$initialSnap,
572
- _ref$springConfig = _ref.springConfig,
573
- springConfig = _ref$springConfig === void 0 ? DEFAULT_SPRING_CONFIG : _ref$springConfig,
574
593
  _ref$disableDrag = _ref.disableDrag,
575
594
  disableDrag = _ref$disableDrag === void 0 ? false : _ref$disableDrag,
576
595
  _ref$prefersReducedMo = _ref.prefersReducedMotion,
577
596
  prefersReducedMotion = _ref$prefersReducedMo === void 0 ? false : _ref$prefersReducedMo,
578
- rest = _objectWithoutPropertiesLoose(_ref, ["onOpenStart", "onOpenEnd", "onClose", "onCloseStart", "onCloseEnd", "onSnap", "children", "isOpen", "snapPoints", "rootId", "mountPoint", "style", "detent", "initialSnap", "springConfig", "disableDrag", "prefersReducedMotion"]);
597
+ _ref$tweenConfig = _ref.tweenConfig,
598
+ tweenConfig = _ref$tweenConfig === void 0 ? DEFAULT_TWEEN_CONFIG : _ref$tweenConfig,
599
+ rest = _objectWithoutPropertiesLoose(_ref, ["onOpenStart", "onOpenEnd", "onClose", "onCloseStart", "onCloseEnd", "onSnap", "children", "isOpen", "snapPoints", "rootId", "mountPoint", "style", "detent", "initialSnap", "disableDrag", "prefersReducedMotion", "tweenConfig"]);
579
600
 
580
601
  var sheetRef = React.useRef(null);
581
602
  var indicatorRotation = framerMotion.useMotionValue(0);
582
603
  var windowHeight = useWindowHeight();
583
604
  var shouldReduceMotion = framerMotion.useReducedMotion();
584
605
  var reduceMotion = Boolean(prefersReducedMotion || shouldReduceMotion);
585
- var animationOptions = reduceMotion ? {
586
- type: 'tween',
587
- duration: 0.01
588
- } : _extends({
589
- type: 'spring'
590
- }, springConfig); // NOTE: the inital value for `y` doesn't matter since it is overwritten by
606
+
607
+ var animationOptions = _extends({
608
+ type: 'tween'
609
+ }, reduceMotion ? REDUCED_MOTION_TWEEN_CONFIG : tweenConfig); // NOTE: the inital value for `y` doesn't matter since it is overwritten by
591
610
  // the value driven by the `AnimatePresence` component when the sheet is opened
592
611
  // and after that it is driven by the gestures and/or snapping
593
612
 
613
+
594
614
  var y = framerMotion.useMotionValue(0);
595
615
  var zIndex = framerMotion.useTransform(y, function (value) {
596
616
  return value >= windowHeight ? -1 : 9999999;
@@ -745,7 +765,8 @@ var Sheet = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
745
765
  dragProps: dragProps,
746
766
  windowHeight: windowHeight,
747
767
  animationOptions: animationOptions,
748
- reduceMotion: reduceMotion
768
+ reduceMotion: reduceMotion,
769
+ disableDrag: disableDrag
749
770
  };
750
771
  var sheet = React.createElement(SheetContext.Provider, {
751
772
  value: context
@@ -755,11 +776,11 @@ var Sheet = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
755
776
  zIndex: zIndex,
756
777
  visibility: visibility
757
778
  }, style)
758
- }), React.createElement(framerMotion.AnimatePresence, null, isOpen ? React.Children.map(children, function (child, i) {
779
+ }), React.createElement(framerMotion.AnimatePresence, null, isOpen ? React.createElement(SheetScrollerContextProvider, null, React.Children.map(children, function (child, i) {
759
780
  return React.cloneElement(child, {
760
781
  key: "sheet-child-" + i
761
782
  });
762
- }) : null)));
783
+ })) : null)));
763
784
  if (IS_SSR) return sheet;
764
785
  return ReactDOM.createPortal(sheet, mountPoint != null ? mountPoint : document.body);
765
786
  });
@@ -824,16 +845,14 @@ var SheetContent = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
824
845
  className = _ref$className === void 0 ? '' : _ref$className,
825
846
  rest = _objectWithoutPropertiesLoose(_ref, ["children", "style", "disableDrag", "className"]);
826
847
 
827
- var _useSheetContext = useSheetContext(),
828
- dragProps = _useSheetContext.dragProps;
829
-
830
- var _dragProps = disableDrag ? undefined : dragProps;
831
-
848
+ var sheetContext = useSheetContext();
849
+ var sheetScrollerContext = useSheetScrollerContext();
850
+ var dragProps = disableDrag || sheetScrollerContext.disableDrag ? undefined : sheetContext.dragProps;
832
851
  return React.createElement(framerMotion.motion.div, Object.assign({}, rest, {
833
852
  ref: ref,
834
853
  className: "react-modal-sheet-content " + className,
835
854
  style: _extends({}, styles.content, style)
836
- }, _dragProps), children);
855
+ }, dragProps), children);
837
856
  });
838
857
 
839
858
  var SheetHeader = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
@@ -904,11 +923,59 @@ var SheetBackdrop = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
904
923
  }));
905
924
  });
906
925
 
926
+ var SheetScroller = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
927
+ var _ref$draggableAt = _ref.draggableAt,
928
+ draggableAt = _ref$draggableAt === void 0 ? 'top' : _ref$draggableAt,
929
+ children = _ref.children,
930
+ style = _ref.style,
931
+ _ref$className = _ref.className,
932
+ className = _ref$className === void 0 ? '' : _ref$className,
933
+ rest = _objectWithoutPropertiesLoose(_ref, ["draggableAt", "children", "style", "className"]);
934
+
935
+ var sheetScrollerContext = useSheetScrollerContext();
936
+
937
+ function determineDragState(element) {
938
+ var scrollTop = element.scrollTop,
939
+ scrollHeight = element.scrollHeight,
940
+ clientHeight = element.clientHeight;
941
+ var isScrollable = scrollHeight > clientHeight;
942
+ if (!isScrollable) return;
943
+ var isAtTop = scrollTop <= 0;
944
+ var isAtBottom = scrollHeight - scrollTop === clientHeight;
945
+ var shouldEnable = draggableAt === 'top' && isAtTop || draggableAt === 'bottom' && isAtBottom || draggableAt === 'both' && (isAtTop || isAtBottom);
946
+
947
+ if (shouldEnable) {
948
+ sheetScrollerContext.setDragEnabled();
949
+ } else {
950
+ sheetScrollerContext.setDragDisabled();
951
+ }
952
+ }
953
+
954
+ function onScroll(e) {
955
+ determineDragState(e.currentTarget);
956
+ }
957
+
958
+ function onTouchStart(e) {
959
+ determineDragState(e.currentTarget);
960
+ }
961
+
962
+ var scrollProps = isTouchDevice() ? {
963
+ onScroll: onScroll,
964
+ onTouchStart: onTouchStart
965
+ } : undefined;
966
+ return React.createElement("div", Object.assign({}, rest, {
967
+ ref: ref,
968
+ className: "react-modal-sheet-scroller " + className,
969
+ style: _extends({}, styles.scroller, style)
970
+ }, scrollProps), children);
971
+ });
972
+
907
973
  var _SheetCompound = Sheet;
908
974
  _SheetCompound.Container = SheetContainer;
909
975
  _SheetCompound.Header = SheetHeader;
910
976
  _SheetCompound.Content = SheetContent;
911
977
  _SheetCompound.Backdrop = SheetBackdrop;
978
+ _SheetCompound.Scroller = SheetScroller;
912
979
 
913
980
  exports.default = _SheetCompound;
914
981
  //# sourceMappingURL=react-modal-sheet.cjs.development.js.map