react-modal-sheet 1.11.1 → 2.1.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 +81 -50
- package/dist/SheetBackdrop.d.ts +1 -1
- package/dist/SheetContainer.d.ts +1 -1
- package/dist/SheetContent.d.ts +1 -1
- package/dist/SheetHeader.d.ts +1 -1
- package/dist/SheetScroller.d.ts +5 -0
- package/dist/constants.d.ts +7 -4
- package/dist/context.d.ts +6 -1
- package/dist/hooks.d.ts +2 -2
- package/dist/react-modal-sheet.cjs.development.js +231 -238
- 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 +232 -239
- package/dist/react-modal-sheet.esm.js.map +1 -1
- package/dist/sheet.d.ts +2 -1
- package/dist/types.d.ts +18 -2
- package/dist/utils.d.ts +1 -0
- package/package.json +2 -3
- package/src/SheetContent.tsx +9 -4
- package/src/SheetScroller.tsx +59 -0
- package/src/constants.ts +8 -4
- package/src/context.tsx +38 -1
- package/src/hooks.tsx +5 -5
- package/src/index.tsx +2 -0
- package/src/sheet.tsx +18 -11
- package/src/styles.ts +9 -3
- package/src/types.tsx +25 -3
- package/src/utils.ts +8 -23
package/src/index.tsx
CHANGED
|
@@ -5,6 +5,7 @@ import SheetContainer from './SheetContainer';
|
|
|
5
5
|
import SheetContent from './SheetContent';
|
|
6
6
|
import SheetHeader from './SheetHeader';
|
|
7
7
|
import SheetBackdrop from './SheetBackdrop';
|
|
8
|
+
import SheetScroller from './SheetScroller';
|
|
8
9
|
import { SheetCompound } from './types';
|
|
9
10
|
|
|
10
11
|
export type SheetRef = {
|
|
@@ -18,5 +19,6 @@ _SheetCompound.Container = SheetContainer;
|
|
|
18
19
|
_SheetCompound.Header = SheetHeader;
|
|
19
20
|
_SheetCompound.Content = SheetContent;
|
|
20
21
|
_SheetCompound.Backdrop = SheetBackdrop;
|
|
22
|
+
_SheetCompound.Scroller = SheetScroller;
|
|
21
23
|
|
|
22
24
|
export default _SheetCompound as SheetCompound;
|
package/src/sheet.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import * as ReactDOM from 'react-dom';
|
|
3
|
+
|
|
3
4
|
import {
|
|
4
5
|
animate,
|
|
5
6
|
AnimatePresence,
|
|
@@ -19,14 +20,15 @@ import {
|
|
|
19
20
|
} from './hooks';
|
|
20
21
|
|
|
21
22
|
import {
|
|
22
|
-
|
|
23
|
+
REDUCED_MOTION_TWEEN_CONFIG,
|
|
24
|
+
DEFAULT_TWEEN_CONFIG,
|
|
23
25
|
DRAG_CLOSE_THRESHOLD,
|
|
24
26
|
DRAG_VELOCITY_THRESHOLD,
|
|
25
27
|
IS_SSR,
|
|
26
28
|
} from './constants';
|
|
27
29
|
|
|
28
30
|
import { SheetContextType, SheetProps } from './types';
|
|
29
|
-
import { SheetContext } from './context';
|
|
31
|
+
import { SheetScrollerContextProvider, SheetContext } from './context';
|
|
30
32
|
import { getClosest, inDescendingOrder, validateSnapTo } from './utils';
|
|
31
33
|
import { usePreventScroll } from './use-prevent-scroll';
|
|
32
34
|
import styles from './styles';
|
|
@@ -41,6 +43,7 @@ const Sheet = React.forwardRef<any, SheetProps>(
|
|
|
41
43
|
onCloseEnd,
|
|
42
44
|
onSnap,
|
|
43
45
|
children,
|
|
46
|
+
disableScrollLocking = false,
|
|
44
47
|
isOpen,
|
|
45
48
|
snapPoints,
|
|
46
49
|
rootId,
|
|
@@ -48,9 +51,9 @@ const Sheet = React.forwardRef<any, SheetProps>(
|
|
|
48
51
|
style,
|
|
49
52
|
detent = 'full-height',
|
|
50
53
|
initialSnap = 0,
|
|
51
|
-
springConfig = DEFAULT_SPRING_CONFIG,
|
|
52
54
|
disableDrag = false,
|
|
53
55
|
prefersReducedMotion = false,
|
|
56
|
+
tweenConfig = DEFAULT_TWEEN_CONFIG,
|
|
54
57
|
...rest
|
|
55
58
|
},
|
|
56
59
|
ref
|
|
@@ -60,9 +63,10 @@ const Sheet = React.forwardRef<any, SheetProps>(
|
|
|
60
63
|
const windowHeight = useWindowHeight();
|
|
61
64
|
const shouldReduceMotion = useReducedMotion();
|
|
62
65
|
const reduceMotion = Boolean(prefersReducedMotion || shouldReduceMotion);
|
|
63
|
-
const animationOptions: Transition =
|
|
64
|
-
|
|
65
|
-
|
|
66
|
+
const animationOptions: Transition = {
|
|
67
|
+
type: 'tween',
|
|
68
|
+
...(reduceMotion ? REDUCED_MOTION_TWEEN_CONFIG : tweenConfig),
|
|
69
|
+
};
|
|
66
70
|
|
|
67
71
|
// NOTE: the inital value for `y` doesn't matter since it is overwritten by
|
|
68
72
|
// the value driven by the `AnimatePresence` component when the sheet is opened
|
|
@@ -204,7 +208,7 @@ const Sheet = React.forwardRef<any, SheetProps>(
|
|
|
204
208
|
|
|
205
209
|
// Framer Motion should handle body scroll locking but it's not working
|
|
206
210
|
// properly on iOS. Scroll locking from React Aria seems to work much better.
|
|
207
|
-
usePreventScroll({ isDisabled: !isOpen });
|
|
211
|
+
usePreventScroll({ isDisabled: disableScrollLocking === true || !isOpen });
|
|
208
212
|
|
|
209
213
|
const dragProps = React.useMemo(() => {
|
|
210
214
|
const dragProps: SheetContextType['dragProps'] = {
|
|
@@ -233,6 +237,7 @@ const Sheet = React.forwardRef<any, SheetProps>(
|
|
|
233
237
|
windowHeight,
|
|
234
238
|
animationOptions,
|
|
235
239
|
reduceMotion,
|
|
240
|
+
disableDrag,
|
|
236
241
|
};
|
|
237
242
|
|
|
238
243
|
const sheet = (
|
|
@@ -244,11 +249,13 @@ const Sheet = React.forwardRef<any, SheetProps>(
|
|
|
244
249
|
>
|
|
245
250
|
<AnimatePresence>
|
|
246
251
|
{/* NOTE: AnimatePresence requires us to set keys to children */}
|
|
247
|
-
{isOpen
|
|
248
|
-
|
|
252
|
+
{isOpen ? (
|
|
253
|
+
<SheetScrollerContextProvider>
|
|
254
|
+
{React.Children.map(children, (child: any, i) =>
|
|
249
255
|
React.cloneElement(child, { key: `sheet-child-${i}` })
|
|
250
|
-
)
|
|
251
|
-
|
|
256
|
+
)}
|
|
257
|
+
</SheetScrollerContextProvider>
|
|
258
|
+
) : null}
|
|
252
259
|
</AnimatePresence>
|
|
253
260
|
</motion.div>
|
|
254
261
|
</SheetContext.Provider>
|
package/src/styles.ts
CHANGED
|
@@ -17,7 +17,7 @@ const styles: { [key: string]: CSS.Properties } = {
|
|
|
17
17
|
left: 0,
|
|
18
18
|
width: '100%',
|
|
19
19
|
height: '100%',
|
|
20
|
-
backgroundColor: 'rgba(
|
|
20
|
+
backgroundColor: 'rgba(0, 0, 0, 0.2)',
|
|
21
21
|
touchAction: 'none', // Disable iOS body scrolling
|
|
22
22
|
border: 'none',
|
|
23
23
|
},
|
|
@@ -53,10 +53,16 @@ const styles: { [key: string]: CSS.Properties } = {
|
|
|
53
53
|
backgroundColor: '#ddd',
|
|
54
54
|
},
|
|
55
55
|
content: {
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
flexGrow: 1,
|
|
57
|
+
display: 'flex',
|
|
58
|
+
flexDirection: 'column',
|
|
59
|
+
minHeight: '0px',
|
|
58
60
|
position: 'relative',
|
|
59
61
|
},
|
|
62
|
+
scroller: {
|
|
63
|
+
height: '100%',
|
|
64
|
+
overflowY: 'auto',
|
|
65
|
+
},
|
|
60
66
|
};
|
|
61
67
|
|
|
62
68
|
export default styles;
|
package/src/types.tsx
CHANGED
|
@@ -2,9 +2,9 @@ import {
|
|
|
2
2
|
DragHandlers,
|
|
3
3
|
MotionValue,
|
|
4
4
|
MotionProps,
|
|
5
|
-
Transition,
|
|
6
|
-
Spring,
|
|
7
5
|
motion,
|
|
6
|
+
EasingDefinition,
|
|
7
|
+
Transition,
|
|
8
8
|
} from 'framer-motion';
|
|
9
9
|
|
|
10
10
|
export type SheetEvents = {
|
|
@@ -21,6 +21,11 @@ type CommonProps = MotionProps & {
|
|
|
21
21
|
className?: string;
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
+
export type SheetTweenConfig = {
|
|
25
|
+
ease: EasingDefinition;
|
|
26
|
+
duration: number;
|
|
27
|
+
};
|
|
28
|
+
|
|
24
29
|
export type SheetProps = {
|
|
25
30
|
isOpen: boolean;
|
|
26
31
|
children: React.ReactNode;
|
|
@@ -30,8 +35,9 @@ export type SheetProps = {
|
|
|
30
35
|
snapPoints?: number[];
|
|
31
36
|
detent?: SheetDetent;
|
|
32
37
|
initialSnap?: number; // index of snap points array
|
|
33
|
-
|
|
38
|
+
tweenConfig?: SheetTweenConfig;
|
|
34
39
|
disableDrag?: boolean;
|
|
40
|
+
disableScrollLocking?: boolean;
|
|
35
41
|
prefersReducedMotion?: boolean;
|
|
36
42
|
} & SheetEvents &
|
|
37
43
|
React.ComponentPropsWithoutRef<typeof motion.div>;
|
|
@@ -62,6 +68,10 @@ export type SheetBackdropProps = Omit<
|
|
|
62
68
|
'initial' | 'animate' | 'exit'
|
|
63
69
|
>;
|
|
64
70
|
|
|
71
|
+
export type SheetScrollerProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
72
|
+
draggableAt?: 'top' | 'bottom' | 'both';
|
|
73
|
+
};
|
|
74
|
+
|
|
65
75
|
export type SheetDragProps = {
|
|
66
76
|
drag: 'y';
|
|
67
77
|
dragElastic: number;
|
|
@@ -85,6 +95,13 @@ export type SheetContextType = {
|
|
|
85
95
|
windowHeight: number;
|
|
86
96
|
animationOptions: Transition;
|
|
87
97
|
reduceMotion: boolean;
|
|
98
|
+
disableDrag: boolean;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export type SheetScrollerContextType = {
|
|
102
|
+
disableDrag: boolean;
|
|
103
|
+
setDragDisabled: () => void;
|
|
104
|
+
setDragEnabled: () => void;
|
|
88
105
|
};
|
|
89
106
|
|
|
90
107
|
type ContainerComponent = React.ForwardRefExoticComponent<
|
|
@@ -103,11 +120,16 @@ type SheetComponent = React.ForwardRefExoticComponent<
|
|
|
103
120
|
SheetProps & React.RefAttributes<any>
|
|
104
121
|
>;
|
|
105
122
|
|
|
123
|
+
type ScrollerComponent = React.ForwardRefExoticComponent<
|
|
124
|
+
SheetScrollerProps & React.RefAttributes<any>
|
|
125
|
+
>;
|
|
126
|
+
|
|
106
127
|
type SheetCompoundComponent = {
|
|
107
128
|
Container: ContainerComponent;
|
|
108
129
|
Header: DraggableComponent;
|
|
109
130
|
Content: DraggableComponent;
|
|
110
131
|
Backdrop: BackdropComponent;
|
|
132
|
+
Scroller: ScrollerComponent;
|
|
111
133
|
};
|
|
112
134
|
|
|
113
135
|
export type SheetCompound = SheetComponent & SheetCompoundComponent;
|
package/src/utils.ts
CHANGED
|
@@ -15,8 +15,6 @@ export function getClosest(nums: number[], goal: number) {
|
|
|
15
15
|
return closest;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
const highlightId = 'react-modal-sheet-highlight';
|
|
19
|
-
|
|
20
18
|
export function applyRootStyles(rootId: string) {
|
|
21
19
|
const body = document.querySelector('body') as HTMLBodyElement;
|
|
22
20
|
const root = document.querySelector(`#${rootId}`) as HTMLDivElement;
|
|
@@ -28,33 +26,17 @@ export function applyRootStyles(rootId: string) {
|
|
|
28
26
|
body.style.backgroundColor = '#000';
|
|
29
27
|
root.style.overflow = 'hidden';
|
|
30
28
|
root.style.willChange = 'transform';
|
|
31
|
-
root.style.transition =
|
|
29
|
+
root.style.transition =
|
|
30
|
+
'transform 200ms ease-in-out, border-radius 200ms linear';
|
|
32
31
|
root.style.transform = `translateY(calc(env(safe-area-inset-top) + ${p / 2}px)) scale(${s})`; // prettier-ignore
|
|
33
32
|
root.style.borderTopRightRadius = '10px';
|
|
34
33
|
root.style.borderTopLeftRadius = '10px';
|
|
35
|
-
|
|
36
|
-
// Add highlighed overlay to emphasize the modality effect
|
|
37
|
-
const highlight = document.createElement('div');
|
|
38
|
-
highlight.setAttribute('aria-hidden', 'true');
|
|
39
|
-
highlight.id = highlightId;
|
|
40
|
-
highlight.style.position = 'absolute';
|
|
41
|
-
highlight.style.top = '0px';
|
|
42
|
-
highlight.style.left = '0px';
|
|
43
|
-
highlight.style.bottom = '0px';
|
|
44
|
-
highlight.style.right = '0px';
|
|
45
|
-
highlight.style.opacity = '0';
|
|
46
|
-
highlight.style.transition = 'opacity 200ms linear';
|
|
47
|
-
highlight.style.backgroundColor = 'rgba(150, 150, 150, 0.1)';
|
|
48
|
-
|
|
49
|
-
root.appendChild(highlight);
|
|
50
|
-
requestAnimationFrame(() => (highlight.style.opacity = '1'));
|
|
51
34
|
}
|
|
52
35
|
}
|
|
53
36
|
|
|
54
37
|
export function cleanupRootStyles(rootId: string) {
|
|
55
38
|
const body = document.querySelector('body') as HTMLBodyElement;
|
|
56
39
|
const root = document.getElementById(rootId) as HTMLDivElement;
|
|
57
|
-
const highlight = document.getElementById(highlightId) as HTMLDivElement;
|
|
58
40
|
|
|
59
41
|
function onTransitionEnd() {
|
|
60
42
|
root.style.removeProperty('overflow');
|
|
@@ -62,15 +44,13 @@ export function cleanupRootStyles(rootId: string) {
|
|
|
62
44
|
root.style.removeProperty('transition');
|
|
63
45
|
body.style.removeProperty('background-color');
|
|
64
46
|
root.removeEventListener('transitionend', onTransitionEnd);
|
|
65
|
-
highlight.parentNode?.removeChild(highlight);
|
|
66
47
|
}
|
|
67
48
|
|
|
68
|
-
if (root
|
|
49
|
+
if (root) {
|
|
69
50
|
// Start animating back
|
|
70
51
|
root.style.removeProperty('border-top-right-radius');
|
|
71
52
|
root.style.removeProperty('border-top-left-radius');
|
|
72
53
|
root.style.removeProperty('transform');
|
|
73
|
-
highlight.style.opacity = '0';
|
|
74
54
|
|
|
75
55
|
// Remove temp properties after animation is finished
|
|
76
56
|
root.addEventListener('transitionend', onTransitionEnd);
|
|
@@ -115,3 +95,8 @@ export function mergeRefs<T = any>(
|
|
|
115
95
|
});
|
|
116
96
|
};
|
|
117
97
|
}
|
|
98
|
+
|
|
99
|
+
export function isTouchDevice() {
|
|
100
|
+
if (typeof window === 'undefined') return false;
|
|
101
|
+
return 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
|
102
|
+
}
|