react-modal-sheet 1.3.2 → 1.5.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/src/index.tsx CHANGED
@@ -1,3 +1,5 @@
1
+ import { MotionValue } from 'framer-motion';
2
+
1
3
  import Sheet from './sheet';
2
4
  import SheetContainer from './SheetContainer';
3
5
  import SheetContent from './SheetContent';
@@ -5,6 +7,7 @@ import SheetHeader from './SheetHeader';
5
7
  import SheetBackdrop from './SheetBackdrop';
6
8
 
7
9
  export type SheetRef = {
10
+ y: MotionValue<number>;
8
11
  snapTo: (index: number) => void;
9
12
  };
10
13
 
package/src/sheet.tsx CHANGED
@@ -4,12 +4,12 @@ import * as ReactDOM from 'react-dom';
4
4
  import {
5
5
  animate,
6
6
  useMotionValue,
7
- AnimatePresence,
8
7
  PanInfo,
8
+ AnimatePresence,
9
9
  } from 'framer-motion';
10
10
 
11
- import { SheetProps } from './types';
12
- import { getClosest, isBrowser } from './utils';
11
+ import { SheetContextType, SheetProps } from './types';
12
+ import { getClosest, inDescendingOrder, isSSR, useWindowHeight } from './utils';
13
13
  import { SheetContext } from './context';
14
14
  import { useModalEffect } from './hooks';
15
15
  import styles from './styles';
@@ -34,12 +34,28 @@ const Sheet = React.forwardRef<any, SheetProps>(
34
34
  },
35
35
  ref
36
36
  ) => {
37
- const [mounted, setMounted] = React.useState(false);
38
37
  const sheetRef = React.useRef<any>(null);
39
38
  const callbacks = React.useRef({ onOpenStart, onOpenEnd, onCloseStart, onCloseEnd }); // prettier-ignore
40
39
  const indicatorRotation = useMotionValue(0);
41
- const y = useMotionValue(isBrowser ? window.innerHeight : 0);
42
- const pointerEvents = isOpen ? 'auto' : 'none';
40
+ const windowHeight = useWindowHeight();
41
+
42
+ // NOTE: the inital value for `y` doesn't matter since it is overwritten by
43
+ // the value driven by the `AnimatePresence` component when the sheet is opened
44
+ // and after that it is driven by the gestures and/or snapping
45
+ const y = useMotionValue(0);
46
+
47
+ if (snapPoints) {
48
+ // Convert negative / percentage snap points to absolute values
49
+ snapPoints = snapPoints.map(point => {
50
+ if (point > 0 && point <= 1) return point * windowHeight; // percentage values e.g. between 0.0 and 1.0
51
+ return point < 0 ? windowHeight + point : point; // negative values
52
+ });
53
+
54
+ console.assert(
55
+ inDescendingOrder(snapPoints) || windowHeight === 0,
56
+ `Snap points need to be in descending order got: [${snapPoints}]`
57
+ );
58
+ }
43
59
 
44
60
  const handleDrag = React.useCallback((_, { delta }: PanInfo) => {
45
61
  // Update drag indicator rotation based on drag velocity
@@ -89,18 +105,15 @@ const Sheet = React.forwardRef<any, SheetProps>(
89
105
  callbacks.current = { onOpenStart, onOpenEnd, onCloseStart, onCloseEnd };
90
106
  });
91
107
 
92
- React.useEffect(() => {
93
- setMounted(true);
94
- }, []);
95
-
96
108
  // Trigger onSnap callback when sheet is opened or closed
97
109
  React.useEffect(() => {
98
- if (!snapPoints || !onSnap || !mounted) return;
110
+ if (!snapPoints || !onSnap) return;
99
111
  const snapIndex = isOpen ? initialSnap : snapPoints.length - 1;
100
112
  onSnap(snapIndex);
101
- }, [isOpen]);
113
+ }, [isOpen]); // eslint-disable-line
102
114
 
103
115
  React.useImperativeHandle(ref, () => ({
116
+ y,
104
117
  snapTo: (snapIndex: number) => {
105
118
  if (snapPoints && snapPoints[snapIndex] !== undefined) {
106
119
  const sheetEl = sheetRef.current as HTMLDivElement;
@@ -127,7 +140,7 @@ const Sheet = React.forwardRef<any, SheetProps>(
127
140
  onDragEnd: handleDragEnd,
128
141
  };
129
142
 
130
- const context = {
143
+ const context: SheetContextType = {
131
144
  y,
132
145
  sheetRef,
133
146
  isOpen,
@@ -136,38 +149,34 @@ const Sheet = React.forwardRef<any, SheetProps>(
136
149
  indicatorRotation,
137
150
  callbacks,
138
151
  dragProps,
152
+ windowHeight,
153
+ springConfig,
139
154
  };
140
155
 
141
156
  const wrapperProps = {
142
157
  ...rest,
143
158
  ref,
144
- style: {
145
- ...styles.wrapper,
146
- pointerEvents,
147
- } as any,
159
+ style: styles.wrapper,
148
160
  };
149
161
 
150
- return mounted ? (
151
- ReactDOM.createPortal(
152
- <SheetContext.Provider value={context}>
153
- <div {...wrapperProps}>
154
- <AnimatePresence>
155
- {/* NOTE: AnimatePresence requires us to set keys to children */}
156
- {isOpen
157
- ? React.Children.map(children, (child: any, i) =>
158
- React.cloneElement(child, { key: `sheet-child-${i}` })
159
- )
160
- : null}
161
- </AnimatePresence>
162
- </div>
163
- </SheetContext.Provider>,
164
- document.body
165
- )
166
- ) : (
167
- // NOTE: return the wrapper div so that SSR frameworks don't get confused
168
- // with the HTML structure ("Expected server HTML to contain a matching <div>...")
169
- <div {...wrapperProps} />
162
+ const sheet = (
163
+ <SheetContext.Provider value={context}>
164
+ <div {...wrapperProps}>
165
+ <AnimatePresence>
166
+ {/* NOTE: AnimatePresence requires us to set keys to children */}
167
+ {isOpen
168
+ ? React.Children.map(children, (child: any, i) =>
169
+ React.cloneElement(child, { key: `sheet-child-${i}` })
170
+ )
171
+ : null}
172
+ </AnimatePresence>
173
+ </div>
174
+ </SheetContext.Provider>
170
175
  );
176
+
177
+ if (isSSR) return sheet;
178
+
179
+ return ReactDOM.createPortal(sheet, document.body);
171
180
  }
172
181
  );
173
182
 
package/src/styles.ts CHANGED
@@ -9,6 +9,7 @@ const styles: { [key: string]: CSS.Properties } = {
9
9
  right: 0,
10
10
  zIndex: 9999999,
11
11
  overflow: 'hidden',
12
+ pointerEvents: 'none',
12
13
  },
13
14
  backdrop: {
14
15
  zIndex: 1,
@@ -32,6 +33,7 @@ const styles: { [key: string]: CSS.Properties } = {
32
33
  boxShadow: '0px -2px 16px rgba(0, 0, 0, 0.3)',
33
34
  display: 'flex',
34
35
  flexDirection: 'column',
36
+ pointerEvents: 'auto',
35
37
  },
36
38
  headerWrapper: {
37
39
  width: '100%',
package/src/types.tsx CHANGED
@@ -65,4 +65,6 @@ export type SheetContextType = {
65
65
  indicatorRotation: MotionValue<number>;
66
66
  callbacks: React.MutableRefObject<SheetEvents>;
67
67
  dragProps: SheetDragProps;
68
+ windowHeight: number;
69
+ springConfig: Parameters<typeof useSpring>[1];
68
70
  };
package/src/utils.ts CHANGED
@@ -1,4 +1,6 @@
1
- export const isBrowser = typeof window !== 'undefined';
1
+ import { useEffect, useLayoutEffect, useState } from 'react';
2
+
3
+ export const isSSR = typeof window === 'undefined';
2
4
 
3
5
  export const getClosest = (nums: number[], goal: number) => {
4
6
  return nums.reduce((prev, curr) => {
@@ -67,3 +69,26 @@ export function cleanupRootStyles(rootId: string) {
67
69
  root.addEventListener('transitionend', onTransitionEnd);
68
70
  }
69
71
  }
72
+
73
+ const useIsomorphicLayoutEffect = isSSR ? useEffect : useLayoutEffect;
74
+
75
+ export function useWindowHeight() {
76
+ const [windowHeight, setWindowHeight] = useState(0);
77
+
78
+ useIsomorphicLayoutEffect(() => {
79
+ const updateHeight = () => setWindowHeight(window.innerHeight);
80
+ window.addEventListener('resize', updateHeight);
81
+ updateHeight();
82
+ return () => window.removeEventListener('resize', updateHeight);
83
+ }, []);
84
+
85
+ return windowHeight;
86
+ }
87
+
88
+ export const inDescendingOrder = (arr: number[]) => {
89
+ for (let i = 0; i < arr.length; i++) {
90
+ if (arr[i + 1] > arr[i]) return false;
91
+ }
92
+
93
+ return true;
94
+ };