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/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
- DEFAULT_SPRING_CONFIG,
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 = reduceMotion
64
- ? { type: 'tween', duration: 0.01 }
65
- : { type: 'spring', ...springConfig };
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
- .map(p => sheetHeight - p)
137
- .filter(p => p >= 0); // negative values can occur with `content-height` detent
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
- ? React.Children.map(children, (child: any, i) =>
251
+ {isOpen ? (
252
+ <SheetScrollerContextProvider>
253
+ {React.Children.map(children, (child: any, i) =>
249
254
  React.cloneElement(child, { key: `sheet-child-${i}` })
250
- )
251
- : null}
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(51, 51, 51, 0.5)',
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
- flex: 1,
57
- overflow: 'auto',
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
- springConfig?: Omit<Spring, 'type'>;
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
- return nums.reduce((prev, curr) => {
5
- return Math.abs(curr - goal) < Math.abs(prev - goal) ? curr : prev;
6
- });
7
- }
4
+ let closest = nums[0];
5
+ let minDifference = Math.abs(nums[0] - goal);
8
6
 
9
- const highlightId = 'react-modal-sheet-highlight';
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 = 'transform 200ms linear';
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 && highlight) {
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
+ }