Skip to content

Commit 1f8117d

Browse files
committed
feat(web): honor elevation, insetAdjustment, initialDetentAnimated, scrollingExpandsSheet
1 parent 7013608 commit 1f8117d

3 files changed

Lines changed: 34 additions & 3 deletions

File tree

src/TrueSheet.web.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,12 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
7474
footer,
7575
footerStyle,
7676
scrollable = false,
77+
scrollableOptions,
7778
detached = false,
7879
detachedOffset = DEFAULT_DETACHED_OFFSET,
80+
elevation = 4,
81+
insetAdjustment = 'automatic',
82+
initialDetentAnimated = true,
7983
onPositionChange,
8084
onWillPresent,
8185
onDidPresent,
@@ -568,6 +572,12 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
568572

569573
const effectiveCornerRadius = cornerRadius ?? DEFAULT_CORNER_RADIUS;
570574

575+
// Shadow cast upward from the sheet's top edge toward the background. Matches
576+
// Android's `elevation` semantics roughly — the sheet "lifts" off whatever is
577+
// behind it. Scales linearly so higher elevation reads as more separation.
578+
const boxShadow =
579+
elevation > 0 ? `0 ${-elevation}px ${elevation * 3}px rgba(0, 0, 0, 0.15)` : undefined;
580+
571581
const mergedContentStyle = useMemo<React.CSSProperties>(
572582
() => ({
573583
position: 'fixed',
@@ -580,8 +590,11 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
580590
borderTopLeftRadius: effectiveCornerRadius,
581591
borderTopRightRadius: effectiveCornerRadius,
582592
backgroundColor: backgroundColor as string,
593+
boxShadow,
594+
// Lift content above iOS home indicator / bottom safe area when enabled.
595+
paddingBottom: insetAdjustment === 'automatic' ? 'env(safe-area-inset-bottom, 0px)' : 0,
583596
}),
584-
[backgroundColor, effectiveCornerRadius]
597+
[backgroundColor, effectiveCornerRadius, boxShadow, insetAdjustment]
585598
);
586599

587600
const defaultGrabberColor =
@@ -641,13 +654,15 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
641654
onRelease={handleRelease}
642655
dismissible={dismissible}
643656
draggable={draggable}
657+
handleOnly={scrollable && scrollableOptions?.scrollingExpandsSheet === false}
644658
repositionInputs={false}
645659
modal={dimmed}
646660
nested={isNested}
647661
detached={detached}
648662
detachedOffset={detachedOffset}
649663
detachedRadius={effectiveCornerRadius}
650664
maxContentHeight={maxContentHeight}
665+
initialAnimated={initialDetentAnimated}
651666
detachedWrapperStyle={wrapperStyle}
652667
activeSnapPoint={activeSnapPoint}
653668
setActiveSnapPoint={handleSetActiveSnapPoint}

src/web/vaul/index.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,11 @@ export type DialogProps = {
177177
* respect a user-specified ceiling.
178178
*/
179179
maxContentHeight?: number;
180+
/**
181+
* When `false` the first snap on mount is applied without a transition so
182+
* the sheet appears at its target detent instantly. Defaults to `true`.
183+
*/
184+
initialAnimated?: boolean;
180185
} & (WithFadeFromProps | WithoutFadeFromProps);
181186

182187
export function Root({
@@ -216,6 +221,7 @@ export function Root({
216221
detachedRadius = 0,
217222
detachedWrapperStyle,
218223
maxContentHeight,
224+
initialAnimated = true,
219225
}: DialogProps) {
220226
const [isOpen = false, setIsOpen] = useControllableState({
221227
defaultProp: defaultOpen,
@@ -295,6 +301,7 @@ export function Root({
295301
contentHeight,
296302
detachedOffset: detached ? detachedOffset : 0,
297303
maxContentHeight,
304+
initialAnimated,
298305
});
299306

300307
usePreventScroll({

src/web/vaul/use-snap-points.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export function useSnapPoints({
2020
contentHeight,
2121
detachedOffset = 0,
2222
maxContentHeight,
23+
initialAnimated = true,
2324
}: {
2425
activeSnapPointProp?: number | string | null;
2526
setActiveSnapPointProp?(snapPoint: number | null | string): void;
@@ -35,6 +36,7 @@ export function useSnapPoints({
3536
contentHeight?: number;
3637
detachedOffset?: number;
3738
maxContentHeight?: number;
39+
initialAnimated?: boolean;
3840
}) {
3941
const [activeSnapPoint, setActiveSnapPoint] = useControllableState<string | number | null>({
4042
prop: activeSnapPointProp,
@@ -172,21 +174,28 @@ export function useSnapPoints({
172174
});
173175
};
174176

177+
// Skip the transition on the very first snap when the consumer opts out of
178+
// the initial presentation animation. All subsequent snaps still animate.
179+
const hasSnappedRef = React.useRef(false);
175180
const snapToPoint = React.useCallback(
176181
(dimension: number) => {
177182
const newSnapPointIndex =
178183
snapPointsOffset?.findIndex((snapPointDim) => snapPointDim === dimension) ?? null;
179184
onSnapPointChange(newSnapPointIndex);
180185

186+
const animateThisSnap = hasSnappedRef.current || initialAnimated;
187+
hasSnappedRef.current = true;
181188
set(drawerRef.current, {
182-
transition: `transform ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(',')})`,
189+
transition: animateThisSnap
190+
? `transform ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(',')})`
191+
: 'none',
183192
transform: isVertical(direction)
184193
? `translate3d(0, ${dimension}px, 0)`
185194
: `translate3d(${dimension}px, 0, 0)`,
186195
});
187196

188197
// Snapping implies drag overshoot (if any) should be undone.
189-
setDetachedWrapperTransform(0, true);
198+
setDetachedWrapperTransform(0, animateThisSnap);
190199

191200
if (
192201
snapPointsOffset &&

0 commit comments

Comments
 (0)