From 0ea0c116f62aac04d6cbf9c428dde4d5d717ecb6 Mon Sep 17 00:00:00 2001 From: Guy Tepper Date: Fri, 11 Sep 2026 13:35:37 +0200 Subject: [PATCH] fix(mobile): stretch context menu content to full width --- .../context-menu/context-menu.ios.tsx | 21 ++++++++++++++- .../components/context-menu/context-menu.tsx | 19 ++++++++++++- .../components/context-menu/fill-width.tsx | 27 +++++++++++++++++++ .../src/components/context-menu/types.ts | 2 ++ .../favorite-routes/favorite-route-box.tsx | 1 + .../src/components/route-card/route-card.tsx | 2 +- .../src/screens/route-list/fares-screen.tsx | 1 + 7 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 apps/mobile/src/components/context-menu/fill-width.tsx diff --git a/apps/mobile/src/components/context-menu/context-menu.ios.tsx b/apps/mobile/src/components/context-menu/context-menu.ios.tsx index bf5e22ca..a0febd02 100644 --- a/apps/mobile/src/components/context-menu/context-menu.ios.tsx +++ b/apps/mobile/src/components/context-menu/context-menu.ios.tsx @@ -5,17 +5,36 @@ import type { MenuAction } from "@expo/ui/community/menu" import { Button, ContextMenu as SwiftUIContextMenu, Group, Host, RNHostView, Section } from "@expo/ui/swift-ui" import type { ButtonProps } from "@expo/ui/swift-ui" import { contentShape, shapes } from "@expo/ui/swift-ui/modifiers" +import { FillWidth } from "./fill-width" import type { ContextMenuProps } from "./types" export type { ContextMenuAction, ContextMenuProps } from "./types" export function ContextMenu(props: ContextMenuProps) { - const { children, actions, mode = "longPress", title, previewBorderRadius, style, disabled = false, onPressAction } = props + const { children, actions, style, fillWidth = false, disabled = false } = props if (disabled || actions.length === 0) { return style ? {children} : <>{children} } + if (fillWidth) { + return ( + + {(width) => ( + + {children} + + )} + + ) + } + + return +} + +function NativeMenu(props: ContextMenuProps) { + const { children, actions, mode = "longPress", title, previewBorderRadius, style, onPressAction } = props + if (mode === "tap") { const menuActions: MenuAction[] = actions.map((action, index) => ({ id: String(index), diff --git a/apps/mobile/src/components/context-menu/context-menu.tsx b/apps/mobile/src/components/context-menu/context-menu.tsx index abca842e..22a8c28c 100644 --- a/apps/mobile/src/components/context-menu/context-menu.tsx +++ b/apps/mobile/src/components/context-menu/context-menu.tsx @@ -2,18 +2,35 @@ import React from "react" import { View } from "react-native" import { MenuView } from "@expo/ui/community/menu" import type { MenuAction } from "@expo/ui/community/menu" +import { FillWidth } from "./fill-width" import type { ContextMenuProps } from "./types" export type { ContextMenuAction, ContextMenuProps } from "./types" // Non-iOS: tap menus render a Material dropdown, long press is left to the caller. export function ContextMenu(props: ContextMenuProps) { - const { children, actions, mode = "longPress", style, disabled = false, onPressAction } = props + const { children, actions, mode = "longPress", style, fillWidth = false, disabled = false } = props if (disabled || mode !== "tap" || actions.length === 0) { return style ? {children} : <>{children} } + if (fillWidth) { + return ( + + {(width) => ( + + {children} + + )} + + ) + } + + return +} + +function NativeMenu({ children, actions, style, onPressAction }: ContextMenuProps) { const menuActions: MenuAction[] = actions.map((action, index) => ({ id: String(index), title: action.title, diff --git a/apps/mobile/src/components/context-menu/fill-width.tsx b/apps/mobile/src/components/context-menu/fill-width.tsx new file mode 100644 index 00000000..6d501c10 --- /dev/null +++ b/apps/mobile/src/components/context-menu/fill-width.tsx @@ -0,0 +1,27 @@ +import React, { useLayoutEffect, useRef, useState } from "react" +import { View, type StyleProp, type ViewStyle } from "react-native" + +interface FillWidthProps { + style?: StyleProp + children: (width: number | undefined) => React.ReactNode +} + +// Since @expo/ui 57.0.15 a matchContents RNHostView lays its RN child out at the child's own width, with no +// parent width to stretch to, so auto-width content shrinks to its content and sits at the leading edge. +// We measure the container and hand the hosted child an explicit width, as upstream does for bottom-sheet. +// Drop this once RNHostView supports per-axis matchContents (Host already does). +export function FillWidth({ style, children }: FillWidthProps) { + const ref = useRef(null) + const [width, setWidth] = useState() + + // Measured before paint, so the content doesn't flash at its narrow width + useLayoutEffect(() => { + setWidth(ref.current?.getBoundingClientRect().width || undefined) + }, []) + + return ( + setWidth(event.nativeEvent.layout.width)}> + {children(width)} + + ) +} diff --git a/apps/mobile/src/components/context-menu/types.ts b/apps/mobile/src/components/context-menu/types.ts index 9c13b49b..07e3efe8 100644 --- a/apps/mobile/src/components/context-menu/types.ts +++ b/apps/mobile/src/components/context-menu/types.ts @@ -19,6 +19,8 @@ export interface ContextMenuProps { /** Corner radius of the long-press preview */ previewBorderRadius?: number style?: StyleProp + /** Stretches the content to the available width; native menu hosts otherwise size it to its content */ + fillWidth?: boolean disabled?: boolean /** Runs before the selected action's `onPress` */ onPressAction?: () => void diff --git a/apps/mobile/src/components/favorite-routes/favorite-route-box.tsx b/apps/mobile/src/components/favorite-routes/favorite-route-box.tsx index 6c93f8e1..433703b7 100644 --- a/apps/mobile/src/components/favorite-routes/favorite-route-box.tsx +++ b/apps/mobile/src/components/favorite-routes/favorite-route-box.tsx @@ -80,6 +80,7 @@ export function FavoriteRouteBox(props: FavoriteRouteBoxProps) { ]} previewBorderRadius={12} style={styles.contextMenu} + fillWidth > + {cardContent} ) diff --git a/apps/mobile/src/screens/route-list/fares-screen.tsx b/apps/mobile/src/screens/route-list/fares-screen.tsx index 88f9736d..11ce5e74 100644 --- a/apps/mobile/src/screens/route-list/fares-screen.tsx +++ b/apps/mobile/src/screens/route-list/fares-screen.tsx @@ -82,6 +82,7 @@ export function FaresScreen() { {/* A native dropdown menu, so a Touchable child would swallow the tap — plain views only. */}