react-modal-sheet 5.6.0 → 5.7.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 CHANGED
@@ -100,7 +100,7 @@ Also, by constructing the sheet from smaller pieces makes it easier to apply any
100
100
  | `onSnap` | no | | Callback fn that is called with the current snap point index when the sheet snaps to a new snap point. Requires `snapPoints` prop. |
101
101
  | `snapPoints` | no | | Eg. `[0, 0.5, 100, 1]` - where positive values are pixels from the bottom of the sheet and negative from the top. Values between 0-1 represent percentages, eg. `0.5` means 50% of sheet height. **Must be in ascending order and should include 0 (closed) and 1 (fully open).** |
102
102
  | `initialSnap` | no | 0 | Initial snap point when sheet is opened (index from `snapPoints`). |
103
- | `modalEffectRootId` | no | | The id of the element where the main app is mounted, eg. "root". Enables [iOS modal effect](#-ios-modal-view-effect). |
103
+ | `modalEffectRootId` | no | | The id of the element where the main app is mounted, eg. "root". Enables [iOS modal effect](#-ios-modal-view-effect). Can also be the `id` of another sheet to [stack sheets](#stacking-sheets). |
104
104
  | `modalEffectThreshold` | no | 0 | Threshold value between 0-1 which determines when the iOS modal effect will start while dragging the sheet - `0` corresponding to the start of the drag (0% has been dragged into view) and `1` corresponding to the end of the drag (100% of the sheet is visible). |
105
105
  | `tweenConfig` | no | `{ ease: 'easeOut', duration: 0.2 }` | Overrides the config for the sheet [tween](https://motion.dev/docs/react-transitions#tween) transition when the sheet is opened, closed, or snapped to a point. |
106
106
  | `mountPoint` | no | `document.body` | HTML element that should be used as the mount point for the sheet. |
@@ -831,6 +831,47 @@ You can control the start threshold of the effect with the `modalEffectThreshold
831
831
  >
832
832
  > You can still override this behavior by providing a custom `modalEffectThreshold` value.
833
833
 
834
+ #### Stacking sheets
835
+
836
+ The modal effect can also be applied to another sheet which lets you stack sheets on top of each other. Give the underlying sheet an `id` and use it as the `modalEffectRootId` of the sheet that is opened on top of it. You can stack as many sheets as you want by always pointing `modalEffectRootId` to the sheet directly underneath.
837
+
838
+ ```tsx
839
+ function StackedExample() {
840
+ const [isOpen, setOpen] = useState(false);
841
+ const [isStackedOpen, setStackedOpen] = useState(false);
842
+
843
+ return (
844
+ <Sheet
845
+ id="parent-sheet"
846
+ isOpen={isOpen}
847
+ onClose={() => setOpen(false)}
848
+ modalEffectRootId="root"
849
+ >
850
+ <Sheet.Container>
851
+ <Sheet.Content>
852
+ <button onClick={() => setStackedOpen(true)}>Open stacked sheet</button>
853
+
854
+ <Sheet
855
+ isOpen={isStackedOpen}
856
+ onClose={() => setStackedOpen(false)}
857
+ modalEffectRootId="parent-sheet"
858
+ >
859
+ <Sheet.Container>{/*...*/}</Sheet.Container>
860
+ <Sheet.Backdrop onTap={() => setStackedOpen(false)} />
861
+ </Sheet>
862
+ </Sheet.Content>
863
+ </Sheet.Container>
864
+ <Sheet.Backdrop onTap={() => setOpen(false)} />
865
+ </Sheet>
866
+ );
867
+ }
868
+ ```
869
+
870
+ > [!NOTE]
871
+ > The stacked sheet should be rendered inside the underlying sheet (like above) so that it is mounted after it and placed on top of it. Give the stacked sheet a tappable `Sheet.Backdrop` so that the underlying sheet can't be tapped or dragged while the stacked sheet is open. Note that a stacked sheet is unmounted without a closing animation when the underlying sheet is closed.
872
+
873
+ See the [Stacked sheets example](https://temzasse.github.io/react-modal-sheet/#/stacked-sheets) for a working example of this.
874
+
834
875
  ## 🎨 Customization
835
876
 
836
877
  The default styles for the `Sheet` component somewhat follows the styles of the previously mentioned iOS Modal View. However, if these default styles are not to your liking it's easy to make changes to them: you can provide a custom header or you can overwrite any style with CSS via the exposed class names.
package/dist/index.js CHANGED
@@ -409,12 +409,7 @@ function useModalEffect({
409
409
  }
410
410
  }
411
411
  progress = Math.max(0, Math.min(1, progress));
412
- const pageWidth = window.innerWidth;
413
- updateModalEffect(rootId, {
414
- scale: motion.transform(progress, [0, 1], [1, (pageWidth - 16) / pageWidth]),
415
- translateY: motion.transform(progress, [0, 1], [0, 24 + insetTop]),
416
- borderRadius: motion.transform(progress, [0, 1], [0, 10])
417
- });
412
+ updateModalEffect(rootId, { progress, insetTop });
418
413
  });
419
414
  const handleYComplete = useStableCallback(() => {
420
415
  if (!rootId) return;
@@ -442,8 +437,14 @@ function useModalEffect({
442
437
  }, [rootId]);
443
438
  }
444
439
  function setupModalEffect(rootId) {
445
- const root = document.querySelector(`#${rootId}`);
446
- if (!root) return;
440
+ const target = getModalEffectTarget(rootId);
441
+ if (!target) return;
442
+ if (target.type === "sheet") {
443
+ const sheet = target.element;
444
+ sheet.style.transformOrigin = "center top";
445
+ return;
446
+ }
447
+ const root = target.element;
447
448
  const body = document.querySelector("body");
448
449
  body.style.backgroundColor = "#000000";
449
450
  root.style.overflow = "hidden";
@@ -453,16 +454,42 @@ function setupModalEffect(rootId) {
453
454
  root.style.transformOrigin = "center top";
454
455
  }
455
456
  function updateModalEffect(rootId, opts) {
456
- const root = document.querySelector(`#${rootId}`);
457
- if (!root) return;
458
- const { scale, translateY, borderRadius } = opts;
457
+ const target = getModalEffectTarget(rootId);
458
+ if (!target) return;
459
+ const { progress, insetTop } = opts;
460
+ if (target.type === "sheet") {
461
+ const sheet = target.element;
462
+ const sheetWidth = sheet.offsetWidth;
463
+ const sheetTop = sheet.offsetTop;
464
+ const scaleEnd2 = (sheetWidth - 16) / sheetWidth;
465
+ const scale2 = motion.transform(progress, [0, 1], [1, scaleEnd2]);
466
+ const translateYEnd = clamp(scaleEnd2 * (24 + insetTop) - sheetTop, -10, 0);
467
+ const translateY2 = motion.transform(progress, [0, 1], [0, translateYEnd]);
468
+ sheet.style.scale = `${scale2}`;
469
+ sheet.style.translate = `0 ${translateY2}px`;
470
+ return;
471
+ }
472
+ const root = target.element;
473
+ const pageWidth = window.innerWidth;
474
+ const scaleEnd = (pageWidth - 16) / pageWidth;
475
+ const scale = motion.transform(progress, [0, 1], [1, scaleEnd]);
476
+ const translateY = motion.transform(progress, [0, 1], [0, 24 + insetTop]);
477
+ const borderRadius = motion.transform(progress, [0, 1], [0, 10]);
459
478
  root.style.transform = `scale(${scale}) translate3d(0, ${translateY}px, 0)`;
460
479
  root.style.borderTopRightRadius = `${borderRadius}px`;
461
480
  root.style.borderTopLeftRadius = `${borderRadius}px`;
462
481
  }
463
482
  function cleanupModalEffect(rootId) {
464
- const root = document.querySelector(`#${rootId}`);
465
- if (!root) return;
483
+ const target = getModalEffectTarget(rootId);
484
+ if (!target) return;
485
+ if (target.type === "sheet") {
486
+ const sheet = target.element;
487
+ sheet.style.removeProperty("transform-origin");
488
+ sheet.style.removeProperty("scale");
489
+ sheet.style.removeProperty("translate");
490
+ return;
491
+ }
492
+ const root = target.element;
466
493
  const body = document.querySelector("body");
467
494
  body.style.removeProperty("background-color");
468
495
  root.style.removeProperty("overflow");
@@ -474,6 +501,19 @@ function cleanupModalEffect(rootId) {
474
501
  root.style.removeProperty("border-top-right-radius");
475
502
  root.style.removeProperty("border-top-left-radius");
476
503
  }
504
+ function getModalEffectTarget(rootId) {
505
+ const root = document.getElementById(rootId);
506
+ if (!root) return null;
507
+ const sheetRoot = root.closest(".react-modal-sheet-root");
508
+ if (sheetRoot) {
509
+ const container = sheetRoot.querySelector(
510
+ ".react-modal-sheet-container"
511
+ );
512
+ if (!container) return null;
513
+ return { type: "sheet", element: container };
514
+ }
515
+ return { type: "root", element: root };
516
+ }
477
517
 
478
518
  // src/hooks/use-prevent-scroll.ts
479
519
  var KEYBOARD_BUFFER = 24;
@@ -1051,7 +1091,7 @@ var Sheet = React.forwardRef(
1051
1091
  onDragEnd: onDragEndProp,
1052
1092
  ...rest
1053
1093
  }, ref) => {
1054
- const [sheetBoundsRef, sheetBounds] = useMeasure__default.default();
1094
+ const [sheetBoundsRef, sheetBounds] = useMeasure__default.default({ offsetSize: true });
1055
1095
  const sheetRef = React.useRef(null);
1056
1096
  const sheetHeight = Math.round(sheetBounds.height);
1057
1097
  const [currentSnap, setCurrentSnap] = React.useState(initialSnap);