fix(app): align compact menu sheet content

Route composer attachments through the shared compact menu sheet so headers and rows use the same alignment rails. Base the final-row padding on the device safe area to keep actions clear of system navigation.
This commit is contained in:
Mohamed Boudra
2026-08-04 13:09:26 +02:00
parent 683f38443c
commit 238047eaa8
9 changed files with 114 additions and 152 deletions
+2 -145
View File
@@ -3,8 +3,6 @@ import {
View,
Text,
TextInput,
Pressable,
Platform,
useWindowDimensions,
NativeSyntheticEvent,
TextInputContentSizeChangeEventData,
@@ -48,8 +46,6 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { AdaptiveModalSheet, type SheetHeader } from "@/components/adaptive-modal-sheet";
import { useDismissKeyboardOnOpen } from "@/components/ui/keyboard-dismiss";
import { useShortcutKeys } from "@/hooks/use-shortcut-keys";
import { useIosHardwareKeyboardSubmit } from "@/hooks/use-ios-hardware-keyboard-submit";
import { formatShortcut, type ShortcutKey } from "@/utils/format-shortcut";
@@ -164,8 +160,6 @@ const MIN_INPUT_HEIGHT_DESKTOP = 46;
const DEFAULT_MAX_INPUT_HEIGHT = 160;
const MAX_INPUT_VIEWPORT_RATIO = 0.5;
const MIN_INPUT_HEIGHT = isWeb ? MIN_INPUT_HEIGHT_DESKTOP : MIN_INPUT_HEIGHT_MOBILE;
const ATTACHMENT_SHEET_SNAP_POINTS = ["34%", "45%"];
type WebTextInputKeyPressEvent = NativeSyntheticEvent<
TextInputKeyPressEventData & {
metaKey?: boolean;
@@ -225,55 +219,6 @@ function AttachmentMenuList({ items }: { items: AttachmentMenuItem[] }) {
);
}
function AttachmentSheetItem({
item,
onSelect,
}: {
item: AttachmentMenuItem;
onSelect: (item: AttachmentMenuItem) => void;
}) {
const handlePress = useCallback(() => {
onSelect(item);
}, [item, onSelect]);
const pressableStyle = useCallback(
({ pressed }: { pressed: boolean }) => [
styles.attachmentSheetItem,
pressed && styles.attachmentSheetItemPressed,
item.disabled && styles.buttonDisabled,
],
[item.disabled],
);
return (
<Pressable
testID={`message-input-attachment-menu-item-${item.id}`}
accessibilityRole="button"
disabled={item.disabled}
onPress={handlePress}
style={pressableStyle}
>
{item.icon ? <View style={styles.attachmentSheetItemIcon}>{item.icon}</View> : null}
<Text style={styles.attachmentSheetItemText}>{item.label}</Text>
</Pressable>
);
}
function AttachmentSheetList({
items,
onSelect,
}: {
items: AttachmentMenuItem[];
onSelect: (item: AttachmentMenuItem) => void;
}) {
return (
<View style={styles.attachmentSheetList}>
{items.map((item) => (
<AttachmentSheetItem key={item.id} item={item} onSelect={onSelect} />
))}
</View>
);
}
function AttachmentDropdown({
isConnected,
disabled,
@@ -289,73 +234,9 @@ function AttachmentDropdown({
attachmentMenuItems: AttachmentMenuItem[];
addAttachmentLabel: string;
}) {
const isCompact = useIsCompactFormFactor();
const [isSheetOpen, setIsSheetOpen] = useState(false);
useDismissKeyboardOnOpen(isSheetOpen, isCompact);
const isButtonDisabled = !isConnected || disabled;
const attachmentSheetHeader = useMemo<SheetHeader>(
() => ({ title: addAttachmentLabel }),
[addAttachmentLabel],
);
const handleOpenSheet = useCallback(() => {
if (isButtonDisabled) return;
setIsSheetOpen(true);
}, [isButtonDisabled]);
const handleCloseSheet = useCallback(() => {
setIsSheetOpen(false);
}, []);
const handleSheetItemSelect = useCallback((item: AttachmentMenuItem) => {
if (item.disabled) return;
setIsSheetOpen(false);
if (Platform.OS === "ios") {
setTimeout(item.onSelect, 250);
return;
}
item.onSelect();
}, []);
const mobileAttachButtonStyle = useCallback(
(state: { pressed: boolean; hovered?: boolean }) => {
if (typeof attachButtonStyle === "function") {
return attachButtonStyle({ ...state, hovered: Boolean(state.hovered), open: isSheetOpen });
}
return attachButtonStyle;
},
[attachButtonStyle, isSheetOpen],
);
const renderMobileAttachButtonIcon = useCallback(
({ hovered }: { hovered?: boolean }) => renderAttachButtonIcon({ hovered }),
[renderAttachButtonIcon],
);
if (isCompact) {
return (
<>
<Pressable
disabled={isButtonDisabled}
accessibilityLabel={addAttachmentLabel}
accessibilityRole="button"
testID="message-input-attach-button"
onPress={handleOpenSheet}
style={mobileAttachButtonStyle}
>
{renderMobileAttachButtonIcon}
</Pressable>
<AdaptiveModalSheet
header={attachmentSheetHeader}
visible={isSheetOpen}
onClose={handleCloseSheet}
snapPoints={ATTACHMENT_SHEET_SNAP_POINTS}
testID="message-input-attachment-menu"
>
<AttachmentSheetList items={attachmentMenuItems} onSelect={handleSheetItemSelect} />
</AdaptiveModalSheet>
</>
);
}
return (
<DropdownMenu>
<DropdownMenu compactMode="sheet">
<Tooltip delayDuration={0} enabledOnDesktop enabledOnMobile={false}>
<TooltipTrigger asChild>
<DropdownMenuTrigger
@@ -378,6 +259,7 @@ function AttachmentDropdown({
offset={8}
minWidth={220}
testID="message-input-attachment-menu"
sheetTitle={addAttachmentLabel}
>
<AttachmentMenuList items={attachmentMenuItems} />
</DropdownMenuContent>
@@ -1941,31 +1823,6 @@ const styles = StyleSheet.create((theme: Theme) => ({
buttonDisabled: {
opacity: 0.5,
},
attachmentSheetList: {
gap: theme.spacing[1],
},
attachmentSheetItem: {
minHeight: 44,
flexDirection: "row",
alignItems: "center",
gap: theme.spacing[3],
paddingHorizontal: theme.spacing[3],
paddingVertical: theme.spacing[2],
borderRadius: theme.borderRadius.xl,
},
attachmentSheetItemPressed: {
backgroundColor: theme.colors.surface2,
},
attachmentSheetItemIcon: {
width: 20,
alignItems: "center",
justifyContent: "center",
},
attachmentSheetItemText: {
color: theme.colors.foreground,
fontSize: theme.fontSize.base,
fontWeight: theme.fontWeight.normal,
},
overlayContainer: {
position: "absolute",
display: "flex",