react-modal-sheet 1.11.0 → 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 +81 -51
- 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 +137 -63
- 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 +138 -64
- package/dist/react-modal-sheet.esm.js.map +1 -1
- package/dist/sheet.d.ts +1 -1
- package/dist/types.d.ts +17 -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 +19 -13
- package/src/styles.ts +9 -3
- package/src/types.tsx +24 -3
- package/src/utils.ts +20 -26
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';
|
|
@@ -48,9 +50,9 @@ const Sheet = React.forwardRef<any, SheetProps>(
|
|
|
48
50
|
style,
|
|
49
51
|
detent = 'full-height',
|
|
50
52
|
initialSnap = 0,
|
|
51
|
-
springConfig = DEFAULT_SPRING_CONFIG,
|
|
52
53
|
disableDrag = false,
|
|
53
54
|
prefersReducedMotion = false,
|
|
55
|
+
tweenConfig = DEFAULT_TWEEN_CONFIG,
|
|
54
56
|
...rest
|
|
55
57
|
},
|
|
56
58
|
ref
|
|
@@ -60,9 +62,10 @@ const Sheet = React.forwardRef<any, SheetProps>(
|
|
|
60
62
|
const windowHeight = useWindowHeight();
|
|
61
63
|
const shouldReduceMotion = useReducedMotion();
|
|
62
64
|
const reduceMotion = Boolean(prefersReducedMotion || shouldReduceMotion);
|
|
63
|
-
const animationOptions: Transition =
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
const animationOptions: Transition = {
|
|
66
|
+
type: 'tween',
|
|
67
|
+
...(reduceMotion ? REDUCED_MOTION_TWEEN_CONFIG : tweenConfig),
|
|
68
|
+
};
|
|
66
69
|
|
|
67
70
|
// NOTE: the inital value for `y` doesn't matter since it is overwritten by
|
|
68
71
|
// the value driven by the `AnimatePresence` component when the sheet is opened
|
|
@@ -132,9 +135,9 @@ const Sheet = React.forwardRef<any, SheetProps>(
|
|
|
132
135
|
let snapTo = 0;
|
|
133
136
|
|
|
134
137
|
if (snapPoints) {
|
|
135
|
-
const snapToValues = snapPoints
|
|
136
|
-
|
|
137
|
-
|
|
138
|
+
const snapToValues = snapPoints.map(
|
|
139
|
+
p => sheetHeight - Math.min(p, sheetHeight)
|
|
140
|
+
);
|
|
138
141
|
|
|
139
142
|
// Allow snapping to the top of the sheet if detent is set to `content-height`
|
|
140
143
|
if (detent === 'content-height' && !snapToValues.includes(0)) {
|
|
@@ -233,6 +236,7 @@ const Sheet = React.forwardRef<any, SheetProps>(
|
|
|
233
236
|
windowHeight,
|
|
234
237
|
animationOptions,
|
|
235
238
|
reduceMotion,
|
|
239
|
+
disableDrag,
|
|
236
240
|
};
|
|
237
241
|
|
|
238
242
|
const sheet = (
|
|
@@ -244,11 +248,13 @@ const Sheet = React.forwardRef<any, SheetProps>(
|
|
|
244
248
|
>
|
|
245
249
|
<AnimatePresence>
|
|
246
250
|
{/* NOTE: AnimatePresence requires us to set keys to children */}
|
|
247
|
-
{isOpen
|
|
248
|
-
|
|
251
|
+
{isOpen ? (
|
|
252
|
+
<SheetScrollerContextProvider>
|
|
253
|
+
{React.Children.map(children, (child: any, i) =>
|
|
249
254
|
React.cloneElement(child, { key: `sheet-child-${i}` })
|
|
250
|
-
)
|
|
251
|
-
|
|
255
|
+
)}
|
|
256
|
+
</SheetScrollerContextProvider>
|
|
257
|
+
) : null}
|
|
252
258
|
</AnimatePresence>
|
|
253
259
|
</motion.div>
|
|
254
260
|
</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,7 +35,7 @@ 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;
|
|
35
40
|
prefersReducedMotion?: boolean;
|
|
36
41
|
} & SheetEvents &
|
|
@@ -62,6 +67,10 @@ export type SheetBackdropProps = Omit<
|
|
|
62
67
|
'initial' | 'animate' | 'exit'
|
|
63
68
|
>;
|
|
64
69
|
|
|
70
|
+
export type SheetScrollerProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
71
|
+
draggableAt?: 'top' | 'bottom' | 'both';
|
|
72
|
+
};
|
|
73
|
+
|
|
65
74
|
export type SheetDragProps = {
|
|
66
75
|
drag: 'y';
|
|
67
76
|
dragElastic: number;
|
|
@@ -85,6 +94,13 @@ export type SheetContextType = {
|
|
|
85
94
|
windowHeight: number;
|
|
86
95
|
animationOptions: Transition;
|
|
87
96
|
reduceMotion: boolean;
|
|
97
|
+
disableDrag: boolean;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export type SheetScrollerContextType = {
|
|
101
|
+
disableDrag: boolean;
|
|
102
|
+
setDragDisabled: () => void;
|
|
103
|
+
setDragEnabled: () => void;
|
|
88
104
|
};
|
|
89
105
|
|
|
90
106
|
type ContainerComponent = React.ForwardRefExoticComponent<
|
|
@@ -103,11 +119,16 @@ type SheetComponent = React.ForwardRefExoticComponent<
|
|
|
103
119
|
SheetProps & React.RefAttributes<any>
|
|
104
120
|
>;
|
|
105
121
|
|
|
122
|
+
type ScrollerComponent = React.ForwardRefExoticComponent<
|
|
123
|
+
SheetScrollerProps & React.RefAttributes<any>
|
|
124
|
+
>;
|
|
125
|
+
|
|
106
126
|
type SheetCompoundComponent = {
|
|
107
127
|
Container: ContainerComponent;
|
|
108
128
|
Header: DraggableComponent;
|
|
109
129
|
Content: DraggableComponent;
|
|
110
130
|
Backdrop: BackdropComponent;
|
|
131
|
+
Scroller: ScrollerComponent;
|
|
111
132
|
};
|
|
112
133
|
|
|
113
134
|
export type SheetCompound = SheetComponent & SheetCompoundComponent;
|
package/src/utils.ts
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
|
|
3
3
|
export function getClosest(nums: number[], goal: number) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
});
|
|
7
|
-
}
|
|
4
|
+
let closest = nums[0];
|
|
5
|
+
let minDifference = Math.abs(nums[0] - goal);
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
for (let i = 1; i < nums.length; i++) {
|
|
8
|
+
const difference = Math.abs(nums[i] - goal);
|
|
9
|
+
if (difference < minDifference) {
|
|
10
|
+
closest = nums[i];
|
|
11
|
+
minDifference = difference;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return closest;
|
|
16
|
+
}
|
|
10
17
|
|
|
11
18
|
export function applyRootStyles(rootId: string) {
|
|
12
19
|
const body = document.querySelector('body') as HTMLBodyElement;
|
|
@@ -19,33 +26,17 @@ export function applyRootStyles(rootId: string) {
|
|
|
19
26
|
body.style.backgroundColor = '#000';
|
|
20
27
|
root.style.overflow = 'hidden';
|
|
21
28
|
root.style.willChange = 'transform';
|
|
22
|
-
root.style.transition =
|
|
29
|
+
root.style.transition =
|
|
30
|
+
'transform 200ms ease-in-out, border-radius 200ms linear';
|
|
23
31
|
root.style.transform = `translateY(calc(env(safe-area-inset-top) + ${p / 2}px)) scale(${s})`; // prettier-ignore
|
|
24
32
|
root.style.borderTopRightRadius = '10px';
|
|
25
33
|
root.style.borderTopLeftRadius = '10px';
|
|
26
|
-
|
|
27
|
-
// Add highlighed overlay to emphasize the modality effect
|
|
28
|
-
const highlight = document.createElement('div');
|
|
29
|
-
highlight.setAttribute('aria-hidden', 'true');
|
|
30
|
-
highlight.id = highlightId;
|
|
31
|
-
highlight.style.position = 'absolute';
|
|
32
|
-
highlight.style.top = '0px';
|
|
33
|
-
highlight.style.left = '0px';
|
|
34
|
-
highlight.style.bottom = '0px';
|
|
35
|
-
highlight.style.right = '0px';
|
|
36
|
-
highlight.style.opacity = '0';
|
|
37
|
-
highlight.style.transition = 'opacity 200ms linear';
|
|
38
|
-
highlight.style.backgroundColor = 'rgba(150, 150, 150, 0.1)';
|
|
39
|
-
|
|
40
|
-
root.appendChild(highlight);
|
|
41
|
-
requestAnimationFrame(() => (highlight.style.opacity = '1'));
|
|
42
34
|
}
|
|
43
35
|
}
|
|
44
36
|
|
|
45
37
|
export function cleanupRootStyles(rootId: string) {
|
|
46
38
|
const body = document.querySelector('body') as HTMLBodyElement;
|
|
47
39
|
const root = document.getElementById(rootId) as HTMLDivElement;
|
|
48
|
-
const highlight = document.getElementById(highlightId) as HTMLDivElement;
|
|
49
40
|
|
|
50
41
|
function onTransitionEnd() {
|
|
51
42
|
root.style.removeProperty('overflow');
|
|
@@ -53,15 +44,13 @@ export function cleanupRootStyles(rootId: string) {
|
|
|
53
44
|
root.style.removeProperty('transition');
|
|
54
45
|
body.style.removeProperty('background-color');
|
|
55
46
|
root.removeEventListener('transitionend', onTransitionEnd);
|
|
56
|
-
highlight.parentNode?.removeChild(highlight);
|
|
57
47
|
}
|
|
58
48
|
|
|
59
|
-
if (root
|
|
49
|
+
if (root) {
|
|
60
50
|
// Start animating back
|
|
61
51
|
root.style.removeProperty('border-top-right-radius');
|
|
62
52
|
root.style.removeProperty('border-top-left-radius');
|
|
63
53
|
root.style.removeProperty('transform');
|
|
64
|
-
highlight.style.opacity = '0';
|
|
65
54
|
|
|
66
55
|
// Remove temp properties after animation is finished
|
|
67
56
|
root.addEventListener('transitionend', onTransitionEnd);
|
|
@@ -106,3 +95,8 @@ export function mergeRefs<T = any>(
|
|
|
106
95
|
});
|
|
107
96
|
};
|
|
108
97
|
}
|
|
98
|
+
|
|
99
|
+
export function isTouchDevice() {
|
|
100
|
+
if (typeof window === 'undefined') return false;
|
|
101
|
+
return 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
|
102
|
+
}
|