mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
Fix compact sheet bottom spacing
This commit is contained in:
@@ -94,6 +94,8 @@ Five primitives. The pick is determined by option count, the need to search, and
|
|||||||
|
|
||||||
`<AdaptiveModalSheet>` is for a focused task. Multi-field forms (`packages/app/src/components/add-host-modal.tsx`, `packages/app/src/components/pair-link-modal.tsx`, `packages/app/src/components/project-picker-modal.tsx`), confirmations with detail, anything that earns a backdrop. Bottom sheet on compact, centered card on desktop. Raw `Modal` is wrong for any of these.
|
`<AdaptiveModalSheet>` is for a focused task. Multi-field forms (`packages/app/src/components/add-host-modal.tsx`, `packages/app/src/components/pair-link-modal.tsx`, `packages/app/src/components/project-picker-modal.tsx`), confirmations with detail, anything that earns a backdrop. Bottom sheet on compact, centered card on desktop. Raw `Modal` is wrong for any of these.
|
||||||
|
|
||||||
|
`<AdaptiveModalSheet>` owns compact bottom safe-area padding inside the sheet so the sheet background still reaches the screen bottom. If a sheet's first snap point is shorter than its header, content, and safe-area clearance, raise that snap point rather than moving the sheet container.
|
||||||
|
|
||||||
`confirmDialog` is for destructive yes/no and imperative confirmation. Promise-based: `await confirmDialog({ destructive: true, ... })`. Anything where a wrong click loses work.
|
`confirmDialog` is for destructive yes/no and imperative confirmation. Promise-based: `await confirmDialog({ destructive: true, ... })`. Anything where a wrong click loses work.
|
||||||
|
|
||||||
Three themes is `DropdownMenu`. Thirty hosts is `Combobox`. A label and a value is `AdaptiveModalSheet`. "Are you sure?" is `confirmDialog`.
|
Three themes is `DropdownMenu`. Thirty hosts is `Combobox`. A label and a value is `AdaptiveModalSheet`. "Are you sure?" is `confirmDialog`.
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { getCompactSheetSafeAreaPadding } from "@/components/adaptive-modal-sheet-layout";
|
||||||
|
|
||||||
|
describe("getCompactSheetSafeAreaPadding", () => {
|
||||||
|
it("adds the bottom inset to compact sheet footers", () => {
|
||||||
|
expect(
|
||||||
|
getCompactSheetSafeAreaPadding({
|
||||||
|
isCompact: true,
|
||||||
|
hasFooter: true,
|
||||||
|
baseContentPadding: 24,
|
||||||
|
baseFooterPadding: 12,
|
||||||
|
safeAreaBottom: 34,
|
||||||
|
}),
|
||||||
|
).toEqual({ footerPaddingBottom: 46 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("adds the bottom inset to compact sheet content when there is no footer", () => {
|
||||||
|
expect(
|
||||||
|
getCompactSheetSafeAreaPadding({
|
||||||
|
isCompact: true,
|
||||||
|
hasFooter: false,
|
||||||
|
baseContentPadding: 24,
|
||||||
|
baseFooterPadding: 12,
|
||||||
|
safeAreaBottom: 34,
|
||||||
|
}),
|
||||||
|
).toEqual({ contentPaddingBottom: 58 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not inset desktop sheets", () => {
|
||||||
|
expect(
|
||||||
|
getCompactSheetSafeAreaPadding({
|
||||||
|
isCompact: false,
|
||||||
|
hasFooter: false,
|
||||||
|
baseContentPadding: 24,
|
||||||
|
baseFooterPadding: 12,
|
||||||
|
safeAreaBottom: 34,
|
||||||
|
}),
|
||||||
|
).toEqual({});
|
||||||
|
});
|
||||||
|
});
|
||||||
30
packages/app/src/components/adaptive-modal-sheet-layout.ts
Normal file
30
packages/app/src/components/adaptive-modal-sheet-layout.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
export interface CompactSheetSafeAreaPaddingInput {
|
||||||
|
isCompact: boolean;
|
||||||
|
hasFooter: boolean;
|
||||||
|
baseContentPadding: number;
|
||||||
|
baseFooterPadding: number;
|
||||||
|
safeAreaBottom: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CompactSheetSafeAreaPadding {
|
||||||
|
contentPaddingBottom?: number;
|
||||||
|
footerPaddingBottom?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCompactSheetSafeAreaPadding({
|
||||||
|
isCompact,
|
||||||
|
hasFooter,
|
||||||
|
baseContentPadding,
|
||||||
|
baseFooterPadding,
|
||||||
|
safeAreaBottom,
|
||||||
|
}: CompactSheetSafeAreaPaddingInput): CompactSheetSafeAreaPadding {
|
||||||
|
if (!isCompact || safeAreaBottom <= 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasFooter) {
|
||||||
|
return { footerPaddingBottom: baseFooterPadding + safeAreaBottom };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { contentPaddingBottom: baseContentPadding + safeAreaBottom };
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
IsolatedBottomSheetModal,
|
IsolatedBottomSheetModal,
|
||||||
useIsolatedBottomSheetVisibility,
|
useIsolatedBottomSheetVisibility,
|
||||||
} from "@/components/ui/isolated-bottom-sheet-modal";
|
} from "@/components/ui/isolated-bottom-sheet-modal";
|
||||||
|
import { getCompactSheetSafeAreaPadding } from "@/components/adaptive-modal-sheet-layout";
|
||||||
import { isNative, isWeb } from "@/constants/platform";
|
import { isNative, isWeb } from "@/constants/platform";
|
||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
|
|
||||||
@@ -461,9 +462,43 @@ export function AdaptiveModalSheet({
|
|||||||
const isMobile = useIsCompactFormFactor();
|
const isMobile = useIsCompactFormFactor();
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const resolvedSnapPoints = useMemo(() => snapPoints ?? ["65%", "90%"], [snapPoints]);
|
const resolvedSnapPoints = useMemo(() => snapPoints ?? ["65%", "90%"], [snapPoints]);
|
||||||
|
const compactSafeAreaPadding = useMemo(
|
||||||
|
() =>
|
||||||
|
getCompactSheetSafeAreaPadding({
|
||||||
|
isCompact: isMobile,
|
||||||
|
hasFooter: Boolean(footer),
|
||||||
|
baseContentPadding: theme.spacing[SHEET_HORIZONTAL_PADDING_SCALE],
|
||||||
|
baseFooterPadding: theme.spacing[3],
|
||||||
|
safeAreaBottom: insets.bottom,
|
||||||
|
}),
|
||||||
|
[footer, insets.bottom, isMobile, theme.spacing],
|
||||||
|
);
|
||||||
|
const bottomSheetContentStyle = useMemo(
|
||||||
|
() => [
|
||||||
|
styles.bottomSheetContent,
|
||||||
|
compactSafeAreaPadding.contentPaddingBottom != null
|
||||||
|
? { paddingBottom: compactSafeAreaPadding.contentPaddingBottom }
|
||||||
|
: null,
|
||||||
|
],
|
||||||
|
[compactSafeAreaPadding.contentPaddingBottom],
|
||||||
|
);
|
||||||
|
const bottomSheetStaticContentStyle = useMemo(
|
||||||
|
() => [
|
||||||
|
styles.bottomSheetStaticContent,
|
||||||
|
compactSafeAreaPadding.contentPaddingBottom != null
|
||||||
|
? { paddingBottom: compactSafeAreaPadding.contentPaddingBottom }
|
||||||
|
: null,
|
||||||
|
],
|
||||||
|
[compactSafeAreaPadding.contentPaddingBottom],
|
||||||
|
);
|
||||||
const footerStyle = useMemo(
|
const footerStyle = useMemo(
|
||||||
() => [styles.footer, isMobile ? { paddingBottom: theme.spacing[3] + insets.bottom } : null],
|
() => [
|
||||||
[insets.bottom, isMobile, theme.spacing],
|
styles.footer,
|
||||||
|
compactSafeAreaPadding.footerPaddingBottom != null
|
||||||
|
? { paddingBottom: compactSafeAreaPadding.footerPaddingBottom }
|
||||||
|
: null,
|
||||||
|
],
|
||||||
|
[compactSafeAreaPadding.footerPaddingBottom],
|
||||||
);
|
);
|
||||||
const handleIndicatorStyle = useMemo(
|
const handleIndicatorStyle = useMemo(
|
||||||
() => ({ backgroundColor: theme.colors.surface2 }),
|
() => ({ backgroundColor: theme.colors.surface2 }),
|
||||||
@@ -512,14 +547,14 @@ export function AdaptiveModalSheet({
|
|||||||
<SheetHeaderView header={header} onClose={onClose} testID={testID} />
|
<SheetHeaderView header={header} onClose={onClose} testID={testID} />
|
||||||
{scrollable ? (
|
{scrollable ? (
|
||||||
<BottomSheetScrollView
|
<BottomSheetScrollView
|
||||||
contentContainerStyle={styles.bottomSheetContent}
|
contentContainerStyle={bottomSheetContentStyle}
|
||||||
keyboardShouldPersistTaps="handled"
|
keyboardShouldPersistTaps="handled"
|
||||||
showsVerticalScrollIndicator={false}
|
showsVerticalScrollIndicator={false}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</BottomSheetScrollView>
|
</BottomSheetScrollView>
|
||||||
) : (
|
) : (
|
||||||
<View style={styles.bottomSheetStaticContent}>{children}</View>
|
<View style={bottomSheetStaticContentStyle}>{children}</View>
|
||||||
)}
|
)}
|
||||||
{footer ? <View style={footerStyle}>{footer}</View> : null}
|
{footer ? <View style={footerStyle}>{footer}</View> : null}
|
||||||
</IsolatedBottomSheetModal>
|
</IsolatedBottomSheetModal>
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ const DEFAULT_MAX_INPUT_HEIGHT = 160;
|
|||||||
const MAX_INPUT_VIEWPORT_RATIO = 0.5;
|
const MAX_INPUT_VIEWPORT_RATIO = 0.5;
|
||||||
const MIN_INPUT_HEIGHT = isWeb ? MIN_INPUT_HEIGHT_DESKTOP : MIN_INPUT_HEIGHT_MOBILE;
|
const MIN_INPUT_HEIGHT = isWeb ? MIN_INPUT_HEIGHT_DESKTOP : MIN_INPUT_HEIGHT_MOBILE;
|
||||||
const ATTACHMENT_SHEET_HEADER: SheetHeader = { title: "Add attachment" };
|
const ATTACHMENT_SHEET_HEADER: SheetHeader = { title: "Add attachment" };
|
||||||
const ATTACHMENT_SHEET_SNAP_POINTS = ["28%", "45%"];
|
const ATTACHMENT_SHEET_SNAP_POINTS = ["34%", "45%"];
|
||||||
|
|
||||||
type WebTextInputKeyPressEvent = NativeSyntheticEvent<
|
type WebTextInputKeyPressEvent = NativeSyntheticEvent<
|
||||||
TextInputKeyPressEventData & {
|
TextInputKeyPressEventData & {
|
||||||
|
|||||||
Reference in New Issue
Block a user