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/README.md +62 -17
- package/dist/SheetBackdrop.d.ts +1 -1
- package/dist/SheetContainer.d.ts +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/react-modal-sheet.cjs.development.js +72 -30
- package/dist/react-modal-sheet.cjs.development.js.map +1 -1
- package/dist/react-modal-sheet.cjs.production.min.js +1 -1
- package/dist/react-modal-sheet.cjs.production.min.js.map +1 -1
- package/dist/react-modal-sheet.esm.js +73 -31
- package/dist/react-modal-sheet.esm.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/utils.d.ts +3 -1
- package/package.json +9 -9
- package/src/SheetBackdrop.tsx +2 -1
- package/src/SheetContainer.tsx +8 -3
- package/src/index.tsx +3 -0
- package/src/sheet.tsx +46 -37
- package/src/styles.ts +2 -0
- package/src/types.tsx +2 -0
- package/src/utils.ts +26 -1
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,
|
|
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
|
|
42
|
-
|
|
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
|
|
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
|
-
|
|
151
|
-
|
|
152
|
-
<
|
|
153
|
-
<
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
package/src/utils.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
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
|
+
};
|