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 +42 -1
- package/dist/index.js +54 -14
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +54 -14
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/hooks/use-modal-effect.ts +72 -15
- package/src/sheet.tsx +1 -1
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
|
-
|
|
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
|
|
446
|
-
if (!
|
|
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
|
|
457
|
-
if (!
|
|
458
|
-
const {
|
|
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
|
|
465
|
-
if (!
|
|
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);
|