Skip to content

Commit 3d467a3

Browse files
committed
fix(web): clip detached sheet corners on desktop
1 parent c27da6e commit 3d467a3

3 files changed

Lines changed: 43 additions & 4 deletions

File tree

src/TrueSheet.web.tsx

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -264,9 +264,16 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
264264
borderBottomLeftRadius: detached ? DEFAULT_CORNER_RADIUS : 0,
265265
borderBottomRightRadius: detached ? DEFAULT_CORNER_RADIUS : 0,
266266
backgroundColor: backgroundColor as string,
267-
maxWidth: isLandscapeOrTablet ? (maxContentWidth ?? DEFAULT_MAX_WIDTH) : undefined,
268-
marginLeft: isLandscapeOrTablet ? (anchor === 'left' ? anchorOffset : 'auto') : undefined,
269-
marginRight: isLandscapeOrTablet ? (anchor === 'right' ? anchorOffset : 'auto') : undefined,
267+
// When detached on desktop, the clip wrapper already handles horizontal
268+
// sizing/anchoring — letting the drawer fill the wrapper keeps its bottom
269+
// corners aligned with the wrapper's rounded clip (double-margin would
270+
// leave the drawer inset from the wrapper's rounded edge).
271+
maxWidth:
272+
isLandscapeOrTablet && !detached ? (maxContentWidth ?? DEFAULT_MAX_WIDTH) : undefined,
273+
marginLeft:
274+
isLandscapeOrTablet && !detached ? (anchor === 'left' ? anchorOffset : 'auto') : undefined,
275+
marginRight:
276+
isLandscapeOrTablet && !detached ? (anchor === 'right' ? anchorOffset : 'auto') : undefined,
270277
}),
271278
[backgroundColor, isLandscapeOrTablet, maxContentWidth, anchor, anchorOffset, detached]
272279
);
@@ -285,10 +292,30 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
285292
maxWidth: isLandscapeOrTablet ? (maxContentWidth ?? DEFAULT_MAX_WIDTH) : undefined,
286293
marginLeft: isLandscapeOrTablet ? (anchor === 'left' ? anchorOffset : 'auto') : undefined,
287294
marginRight: isLandscapeOrTablet ? (anchor === 'right' ? anchorOffset : 'auto') : undefined,
295+
// Footer lives outside the drawer's detached clip wrapper, so match its
296+
// rounded bottom here instead of inheriting it.
297+
borderBottomLeftRadius: detached ? DEFAULT_CORNER_RADIUS : undefined,
298+
borderBottomRightRadius: detached ? DEFAULT_CORNER_RADIUS : undefined,
299+
overflow: detached ? 'hidden' : undefined,
288300
}),
289301
[isLandscapeOrTablet, maxContentWidth, anchor, anchorOffset, detached, detachedOffset]
290302
);
291303

304+
// On desktop the drawer is narrower than the viewport, so the clip wrapper's
305+
// rounded bottom must match the drawer's horizontal bounds — otherwise its
306+
// corners sit at the far viewport edges and the drawer appears flat-bottomed.
307+
const detachedWrapperStyle = useMemo<React.CSSProperties | undefined>(
308+
() =>
309+
detached && isLandscapeOrTablet
310+
? {
311+
maxWidth: maxContentWidth ?? DEFAULT_MAX_WIDTH,
312+
marginLeft: anchor === 'left' ? anchorOffset : 'auto',
313+
marginRight: anchor === 'right' ? anchorOffset : 'auto',
314+
}
315+
: undefined,
316+
[detached, isLandscapeOrTablet, maxContentWidth, anchor, anchorOffset]
317+
);
318+
292319
const handleStyle = useMemo<React.CSSProperties>(
293320
() => ({
294321
height: grabberHeight,
@@ -313,6 +340,7 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
313340
detached={detached}
314341
detachedOffset={detachedOffset}
315342
detachedRadius={DEFAULT_CORNER_RADIUS}
343+
detachedWrapperStyle={detachedWrapperStyle}
316344
activeSnapPoint={activeSnapPoint}
317345
setActiveSnapPoint={handleSetActiveSnapPoint}
318346
{...snapPointsProps}

src/web/vaul/context.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ interface DrawerContextValue {
3838
detached: boolean;
3939
detachedOffset: number;
4040
detachedRadius: number;
41+
detachedWrapperStyle?: React.CSSProperties;
4142
}
4243

4344
export const DrawerContext = React.createContext<DrawerContextValue>({

src/web/vaul/index.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,12 @@ export type DialogProps = {
161161
* @default 0
162162
*/
163163
detachedRadius?: number;
164+
/**
165+
* Extra styles merged into the detached clip wrapper. Use this to match the
166+
* wrapper's horizontal bounds to the drawer's on desktop (e.g. `maxWidth` +
167+
* auto margins) so its rounded-bottom clip aligns with the drawer.
168+
*/
169+
detachedWrapperStyle?: React.CSSProperties;
164170
} & (WithFadeFromProps | WithoutFadeFromProps);
165171

166172
export function Root({
@@ -197,6 +203,7 @@ export function Root({
197203
detached = false,
198204
detachedOffset = 0,
199205
detachedRadius = 0,
206+
detachedWrapperStyle,
200207
}: DialogProps) {
201208
const [isOpen = false, setIsOpen] = useControllableState({
202209
defaultProp: defaultOpen,
@@ -891,6 +898,7 @@ export function Root({
891898
detached,
892899
detachedOffset: detached ? detachedOffset : 0,
893900
detachedRadius: detached ? detachedRadius : 0,
901+
detachedWrapperStyle: detached ? detachedWrapperStyle : undefined,
894902
}}
895903
>
896904
{children}
@@ -993,6 +1001,7 @@ export const Content = React.forwardRef<HTMLDivElement, ContentProps>(function (
9931001
detached,
9941002
detachedOffset,
9951003
detachedRadius,
1004+
detachedWrapperStyle: detachedWrapperStyleProp,
9961005
} = useDrawerContext();
9971006
const hasAutoSnapPoint = React.useMemo(
9981007
() => !!snapPoints?.some((p) => p === 'auto'),
@@ -1191,9 +1200,10 @@ export const Content = React.forwardRef<HTMLDivElement, ContentProps>(function (
11911200
pointerEvents: 'none',
11921201
borderBottomLeftRadius: detachedRadius,
11931202
borderBottomRightRadius: detachedRadius,
1203+
...detachedWrapperStyleProp,
11941204
}
11951205
: null,
1196-
[detached, detachedOffset, detachedRadius]
1206+
[detached, detachedOffset, detachedRadius, detachedWrapperStyleProp]
11971207
);
11981208

11991209
const contentNode = (

0 commit comments

Comments
 (0)