From 767a5dbd00d0a2050745a2ae2e29050c5282b8c0 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Thu, 17 Sep 2026 20:19:56 -0700 Subject: [PATCH] refactor(mobile): share thread list section controls (#12363) --- apps/mobile/src/features/home/HomeScreen.tsx | 17 +- .../threads/ThreadNavigationSidebar.tsx | 15 +- .../features/threads/thread-list-v2-items.tsx | 169 +++++++++++------- 3 files changed, 110 insertions(+), 91 deletions(-) diff --git a/apps/mobile/src/features/home/HomeScreen.tsx b/apps/mobile/src/features/home/HomeScreen.tsx index 80642147052f85c2ec557dac46c11f4be1367e04..c0afbfed208902dd98efa03d19448621ed5056a9 100644 --- a/apps/mobile/src/features/home/HomeScreen.tsx +++ b/apps/mobile/src/features/home/HomeScreen.tsx @@ -23,12 +23,11 @@ import { useAtomSet, useAtomValue } from "@effect/atom-react"; import { AsyncResult } from "effect/unstable/reactivity"; import { useFocusEffect } from "@react-navigation/native"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { ActivityIndicator, FlatList, Platform, Pressable, View } from "react-native"; +import { ActivityIndicator, FlatList, Platform, View } from "react-native"; import type { SwipeableMethods } from "react-native-gesture-handler/ReanimatedSwipeable"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { cn } from "../../lib/cn"; -import { AppText as Text } from "../../components/AppText"; import { EmptyState } from "../../components/EmptyState"; import { MaterialFloatingActionButton } from "../../components/MaterialFloatingActionButton"; import type { WorkspaceEnvironment, WorkspaceState } from "../../state/workspaceModel"; @@ -53,6 +52,7 @@ import { ThreadListV2PendingRow, ThreadListV2Row, ThreadListV2SettledShelfHeader, + ThreadListV2ShowMoreRow, ThreadListV2SnoozedShelfHeader, } from "../threads/thread-list-v2-items"; import { resolveThreadProviderInstance } from "../threads/thread-provider-instance"; @@ -1234,17 +1234,10 @@ export function HomeScreen(props: HomeScreenProps) { ListHeaderComponent={v2ListHeader} ListFooterComponent={ settledShelfExpanded && threadListV2Layout.hiddenSettledCount > 0 ? ( - ({ opacity: pressed ? 0.6 : 1 })} - > - - Show more ({threadListV2Layout.hiddenSettledCount} settled hidden) - - + /> ) : null } ListEmptyComponent={v2ListEmpty} diff --git a/apps/mobile/src/features/threads/ThreadNavigationSidebar.tsx b/apps/mobile/src/features/threads/ThreadNavigationSidebar.tsx index df04439a94b7e42e2a0d7c4cbfb0ac84ccfb0f00..f6ea5dcf48ea06624133b57015fcf770474785d3 100644 --- a/apps/mobile/src/features/threads/ThreadNavigationSidebar.tsx +++ b/apps/mobile/src/features/threads/ThreadNavigationSidebar.tsx @@ -14,7 +14,7 @@ import { useAtomValue } from "@effect/atom-react"; import { type EnvironmentId, resolveEnvironmentMachineKind } from "@t3tools/contracts"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import type { LayoutChangeEvent } from "react-native"; -import { Platform, Pressable, StyleSheet, TextInput, View } from "react-native"; +import { Platform, StyleSheet, TextInput, View } from "react-native"; import { Gesture, GestureDetector } from "react-native-gesture-handler"; import type { SwipeableMethods } from "react-native-gesture-handler/ReanimatedSwipeable"; import { useSafeAreaInsets } from "react-native-safe-area-context"; @@ -81,6 +81,7 @@ import { ThreadListV2PendingRow, ThreadListV2Row, ThreadListV2SettledShelfHeader, + ThreadListV2ShowMoreRow, ThreadListV2SnoozedShelfHeader, } from "./thread-list-v2-items"; import { resolveThreadProviderInstance } from "./thread-provider-instance"; @@ -990,17 +991,7 @@ function ThreadNavigationSidebarPane( ); case "v2-show-more": return ( - ({ opacity: pressed ? 0.6 : 1 })} - > - - Show more ({item.hiddenCount} settled hidden) - - + ); case "header": return ( diff --git a/apps/mobile/src/features/threads/thread-list-v2-items.tsx b/apps/mobile/src/features/threads/thread-list-v2-items.tsx index 64c865cae809e1084b747e986f1c43e1579d3dbf..fa406271198f12ab4a330b31a679d7a8241e32ff 100644 --- a/apps/mobile/src/features/threads/thread-list-v2-items.tsx +++ b/apps/mobile/src/features/threads/thread-list-v2-items.tsx @@ -31,6 +31,7 @@ import { useThreadPr } from "../../state/use-thread-pr"; import { ThreadSwipeable } from "../home/thread-swipe-actions"; import { buildThreadTitleRegenerationMenuItems } from "./thread-title-regeneration-menu"; import { + THREAD_LIST_V2_SETTLED_PAGE_COUNT, resolveThreadListV2SnoozeMenuSelection, resolveThreadListV2SnoozeGateExpiryMs, resolveThreadListV2Status, @@ -95,96 +96,130 @@ const LEGACY_MENU_ACTIONS: MenuAction[] = [ /** Rounded-row radius shared with the v1 sidebar rows. */ const SIDEBAR_V2_ROW_RADIUS = 12; +function ThreadListV2Section(props: { + readonly label: string; + readonly pane?: "screen" | "sidebar"; + readonly tone?: "default" | "snoozed"; + readonly disclosure?: { + readonly expanded: boolean; + readonly disabled?: boolean; + readonly onToggle: () => void; + readonly accessibilityLabel: string; + readonly accessibilityHint: string; + }; +}) { + const snoozed = props.tone === "snoozed"; + const className = cn( + "mb-1.5 mt-4 flex-row items-center gap-2.5", + props.pane === "sidebar" ? "px-3" : "px-5", + ); + const content = ( + <> + + {props.label} + + + {props.disclosure ? ( + + ) : null} + + ); + + return props.disclosure ? ( + ({ opacity: pressed ? 0.6 : 1 })} + > + {content} + + ) : ( + {content} + ); +} + /** Section label + rule: the only structure in an otherwise flat list. */ export const ThreadListV2SectionDivider = memo(function ThreadListV2SectionDivider(props: { readonly label: string; readonly pane?: "screen" | "sidebar"; }) { - return ( - - {props.label} - - - ); + return ; }); -export const ThreadListV2SnoozedShelfHeader = memo(function ThreadListV2SnoozedShelfHeader(props: { +type ThreadListV2ShelfHeaderProps = { readonly count: number; readonly disabled?: boolean; readonly expanded: boolean; readonly onToggle: () => void; readonly pane?: "screen" | "sidebar"; -}) { +}; + +function ThreadListV2ShelfHeader( + props: ThreadListV2ShelfHeaderProps & { readonly kind: "snoozed" | "settled" }, +) { + const label = props.kind === "snoozed" ? "Snoozed" : "Settled"; return ( - ({ opacity: pressed ? 0.6 : 1 })} - > - - {props.expanded ? "Snoozed" : `Snoozed (${props.count})`} - - - - + ); +} + +export const ThreadListV2SnoozedShelfHeader = memo(function ThreadListV2SnoozedShelfHeader( + props: ThreadListV2ShelfHeaderProps, +) { + return ; }); -export const ThreadListV2SettledShelfHeader = memo(function ThreadListV2SettledShelfHeader(props: { - readonly count: number; - readonly disabled?: boolean; - readonly expanded: boolean; - readonly onToggle: () => void; - readonly pane?: "screen" | "sidebar"; +export const ThreadListV2SettledShelfHeader = memo(function ThreadListV2SettledShelfHeader( + props: ThreadListV2ShelfHeaderProps, +) { + return ; +}); + +export const ThreadListV2ShowMoreRow = memo(function ThreadListV2ShowMoreRow(props: { + readonly hiddenCount: number; + readonly onPress: () => void; }) { return ( ({ opacity: pressed ? 0.6 : 1 })} > - - {props.expanded ? "Settled" : `Settled (${props.count})`} + + Show more ({props.hiddenCount} settled hidden) - - ); }); -- 2.54.0