Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/mobile/src/features/home/HomeScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -809,6 +809,8 @@ export function HomeScreen(props: HomeScreenProps) {
recycleItems
extraData={v2ExtraData}
ListHeaderComponent={v2ListHeader}
// Lifted above the rows so an iOS filter menu's dismissal morph is not covered.
ListHeaderComponentStyle={{ zIndex: 1 }}
ListFooterComponent={
settledShelfExpanded && threadListV2Layout.hiddenSettledCount > 0 ? (
<ThreadListV2ShowMoreRow
Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/src/features/home/ThreadListFilters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export function ThreadListFilters(props: {
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
style={{ marginHorizontal: -props.gutter }}
style={{ marginHorizontal: -props.gutter, overflow: "visible" }}
contentContainerStyle={{ paddingHorizontal: 20, paddingTop: 2, paddingBottom: 10, gap: 8 }}
>
<ListFilterMenu
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,7 @@ export function PullRequestListFilters(props: {
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
style={{ overflow: "visible" }}
// In the Android sidebar the first pill lines up with the rows' content instead
// (8dp row margin + 12dp padding).
contentContainerStyle={
Expand Down
2 changes: 2 additions & 0 deletions apps/mobile/src/features/pull-requests/PullRequestsScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,8 @@ export function PullRequestsScreen(
keyboardDismissMode="on-drag"
keyboardShouldPersistTaps="handled"
renderItem={renderPullRequest}
// Lifted above the rows so an iOS filter menu's dismissal morph is not covered.
ListHeaderComponentStyle={{ zIndex: 1 }}
ListHeaderComponent={
<>
<PullRequestListFilters sidebar={sidebar} filters={filters} />
Expand Down
71 changes: 66 additions & 5 deletions patches/@react-native-menu__menu@2.0.0.patch
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
diff --git a/ios/MenuViewManager.mm b/ios/MenuViewManager.mm
index 3d149b3e7426b8f7c999603ddf1e15e12331c33a..e80fe2caae86e339fcb03408419a7d64b3e5b04f 100644
index 3d149b3e7426b8f7c999603ddf1e15e12331c33a..73b9e8bbe30e6647b03cc1890806e8d48a1cbd8c 100644
--- a/ios/MenuViewManager.mm
+++ b/ios/MenuViewManager.mm
@@ -1,6 +1,6 @@
Expand Down Expand Up @@ -178,10 +178,19 @@ index 6a2a205f95c1001f3fdf6fb835815fa7c4c02c16..4f42b64349966fb1438f72439cec8256
+
}
diff --git a/ios/Shared/MenuViewImplementation.swift b/ios/Shared/MenuViewImplementation.swift
index 5c4e0da4292b15d3a27b5ea1555f11452a470815..475a99b2a3b1800c9d3e3e5f77a64f552fa35828 100644
index 5c4e0da4292b15d3a27b5ea1555f11452a470815..381aa8e2900aeb0d2453dba514360304a79e95f1 100644
--- a/ios/Shared/MenuViewImplementation.swift
+++ b/ios/Shared/MenuViewImplementation.swift
@@ -60,17 +60,46 @@ public class MenuViewImplementation: UIButton {
@@ -17,7 +17,7 @@ public class MenuViewImplementation: UIButton {
}
_actions.removeAll()
actions.forEach { menuAction in
- _actions.append(RCTMenuAction(details: menuAction).createUIMenuElement({action in self.sendButtonAction(action)}))
+ _actions.append(RCTMenuAction(details: menuAction).createUIMenuElement({action in self.didSelectAction = true; self.sendButtonAction(action)}))
}
self.setup()
}
@@ -60,17 +60,98 @@ public class MenuViewImplementation: UIButton {
}

public override func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
Expand All @@ -195,6 +204,7 @@ index 5c4e0da4292b15d3a27b5ea1555f11452a470815..475a99b2a3b1800c9d3e3e5f77a64f55
+ self.setup()
+ }
+ isMenuPresented = true
+ didSelectAction = false
+ sendMenuInteractionStart()
return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { [weak self] _ in
guard let self = self else { return nil }
Expand Down Expand Up @@ -222,15 +232,66 @@ index 5c4e0da4292b15d3a27b5ea1555f11452a470815..475a99b2a3b1800c9d3e3e5f77a64f55
+ pendingMenu = nil
+ self.setup()
+ }
+ }
+
+ public override func contextMenuInteraction(_ interaction: UIContextMenuInteraction, previewForHighlightingMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? {
+ return contentPreview() ?? super.contextMenuInteraction(interaction, previewForHighlightingMenuWithConfiguration: configuration)
+ }
+
+ public override func contextMenuInteraction(_ interaction: UIContextMenuInteraction, previewForDismissingMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? {
+ guard let preview = contentPreview() else {
+ return super.contextMenuInteraction(interaction, previewForDismissingMenuWithConfiguration: configuration)
+ }
+ // A selection usually re-renders the trigger, so fade instead of landing on its old look.
+ return didSelectAction ? nil : preview
}

+ // The button is a transparent overlay: morph from the React content so UIKit hides and restores it.
+ // iOS 26 only, and only for content with its own corner radius; otherwise keep UIButton's default.
+ private func contentPreview() -> UITargetedPreview? {
+ guard #available(iOS 26.0, *) else {
+ return nil
+ }
+ guard !shouldOpenOnLongPress, let siblings = self.superview?.subviews.filter({ $0 !== self }), siblings.count == 1, let content = siblings.first, content.window != nil, let (target, radius) = previewTarget(content) else {
+ return nil
+ }
+ let bounds = target.bounds
+ let parameters = UIPreviewParameters()
+ parameters.backgroundColor = .clear
+ parameters.visiblePath = UIBezierPath(roundedRect: bounds, cornerRadius: min(radius, bounds.height / 2, bounds.width / 2))
+ return UITargetedPreview(view: target, parameters: parameters)
+ }
+
+ // Walks the content's full-size single-child chain for its corner radius. A preview of a view
+ // above a glass effect view drops the glass until UIKit restores the source, so target the glass view.
+ private func previewTarget(_ content: UIView) -> (UIView, CGFloat)? {
+ var radius: CGFloat?
+ var view = content
+ for depth in 0...4 {
+ if radius == nil, view.layer.cornerRadius > 0 {
+ radius = view.layer.cornerRadius
+ }
+ if let radius, view is UIVisualEffectView {
+ return (view, radius)
+ }
+ guard depth < 4, view.subviews.count == 1, let child = view.subviews.first,
+ abs(child.frame.size.width - view.bounds.size.width) <= 0.5,
+ abs(child.frame.size.height - view.bounds.size.height) <= 0.5 else {
+ break
+ }
+ view = child
+ }
+ return radius.map { (content, $0) }
+ }
+
+ private var didSelectAction = false
+ private var isMenuPresented = false
+ private var pendingMenu: UIMenu?
+
func setup () {
let menu = UIMenu(title: _title,
identifier: nil,
@@ -86,8 +115,102 @@ public class MenuViewImplementation: UIButton {
@@ -86,8 +167,102 @@ public class MenuViewImplementation: UIButton {
}
}

Expand Down Expand Up @@ -333,7 +394,7 @@ index 5c4e0da4292b15d3a27b5ea1555f11452a470815..475a99b2a3b1800c9d3e3e5f77a64f55
}

public override func reactSetFrame(_ frame: CGRect) {
@@ -124,4 +247,8 @@ public class MenuViewImplementation: UIButton {
@@ -124,4 +299,8 @@ public class MenuViewImplementation: UIButton {
@objc func sendMenuOpen() {
// NO-OP (should be overriden by parent)
}
Expand Down
6 changes: 3 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading