Unified · Split
| @@ -33,6 +33,7 @@ export function brandTitleOffset(nativeLeadingItem: boolean): number { · 32 unmodified lines | ||
| 33 | 33 | */ |
| 34 | 34 | export function CompactBrandTitle( |
| 35 | 35 | props: { |
| 36 | + readonly allowFontScaling?: boolean; | |
| 36 | 37 | readonly nativeLeadingItem?: boolean; |
| 37 | 38 | } = {}, |
| 38 | 39 | ) { |
| @@ -57,6 +58,7 @@ export function CompactBrandTitle( · 18 unmodified lines | ||
| 57 | 58 | > |
| 58 | 59 | <T3Wordmark color={iconColor} height={15} /> |
| 59 | 60 | <Text |
| 61 | + allowFontScaling={props.allowFontScaling} | |
| 60 | 62 | style={{ |
| 61 | 63 | color: mutedColor, |
| 62 | 64 | fontFamily: "DMSans-Medium", |
| @@ -75,6 +77,7 @@ export function CompactBrandTitle( · 12 unmodified lines | ||
| 75 | 77 | }} |
| 76 | 78 | > |
| 77 | 79 | <Text |
| 80 | + allowFontScaling={props.allowFontScaling} | |
| 78 | 81 | style={{ |
| 79 | 82 | color: mutedColor, |
| 80 | 83 | fontFamily: "DMSans-Bold", |
| @@ -91,7 +94,7 @@ export function CompactBrandTitle( · 10 unmodified lines | ||
| 91 | 94 | } |
| 92 | 95 | |
| 93 | 96 | export function renderCompactBrandTitle() { |
| 94 | − return <CompactBrandTitle />; | |
| 97 | + return <CompactBrandTitle allowFontScaling={Platform.OS === "ios"} />; | |
| 95 | 98 | } |
| 96 | 99 | |
| 97 | 100 | export function renderCompactBrandHeaderItems(): NativeStackHeaderItem[] { |
| @@ -6,8 +6,8 @@ import { SymbolView } from "../../components/AppSymbol"; · 5 unmodified lines | ||
| 6 | 6 | import { useThemeColor } from "../../lib/useThemeColor"; |
| 7 | 7 | |
| 8 | 8 | /** |
| 9 | − * Android-only wrapper that overlays a bottom-right new-task FAB on the home | |
| 10 | − * screen. Other platforms render children unchanged. | |
| 9 | + * Android-only wrapper that overlays a bottom-right new-task FAB on a thread | |
| 10 | + * list. Other platforms render children unchanged. | |
| 11 | 11 | */ |
| 12 | 12 | export function AndroidHomeFabLayout(props: { |
| 13 | 13 | readonly onStartNewTask: () => void; |
| @@ -2,6 +2,7 @@ import * as Arr from "effect/Array"; · 1 unmodified line | ||
| 2 | 2 | import * as Order from "effect/Order"; |
| 3 | 3 | import { useNavigation } from "@react-navigation/native"; |
| 4 | 4 | import { useEffect, useMemo, useState } from "react"; |
| 5 | +import { Platform } from "react-native"; | |
| 5 | 6 | |
| 6 | 7 | import { NativeHeaderToolbar, NativeStackScreenOptions } from "../../native/StackHeader"; |
| 7 | 8 | import { useProjects, useThreadShells } from "../../state/entities"; |
| @@ -106,7 +107,11 @@ export function HomeRouteScreen() { · 98 unmodified lines | ||
| 106 | 107 | return ( |
| 107 | 108 | <> |
| 108 | 109 | <NativeStackScreenOptions |
| 109 | − options={{ title: "", headerTitle: "", unstable_headerLeftItems: () => [] }} | |
| 110 | + options={ | |
| 111 | + Platform.OS === "android" | |
| 112 | + ? { headerShown: false } | |
| 113 | + : { title: "", headerTitle: "", unstable_headerLeftItems: () => [] } | |
| 114 | + } | |
| 110 | 115 | /> |
| 111 | 116 | <WorkspaceSidebarToolbar |
| 112 | 117 | afterSidebarButton={ |
| @@ -129,18 +134,20 @@ export function HomeRouteScreen() { · 16 unmodified lines | ||
| 129 | 134 | onStartNewTask={() => navigation.navigate("NewTaskSheet", { screen: "NewTask" })} |
| 130 | 135 | > |
| 131 | 136 | <> |
| 132 | − {/* Restore the compact title after the split branch blanks the detail | |
| 133 | − header. The brand slot doubles as the connection status surface: | |
| 134 | − while an environment reconnects, the lockup fades to a status label | |
| 135 | − in place (no layout shift in the list below). */} | |
| 137 | + {/* Restore the header after leaving split view; screen options are | |
| 138 | + shallow-merged. The brand slot also doubles as the connection | |
| 139 | + status surface while an environment reconnects. */} | |
| 136 | 140 | <NativeStackScreenOptions |
| 137 | − options={getConnectionAwareBrandHeaderOptions({ | |
| 138 | − onOpenEnvironments: () => | |
| 139 | − navigation.navigate("SettingsSheet", { | |
| 140 | − screen: "SettingsContent", | |
| 141 | − params: { screen: "SettingsEnvironments" }, | |
| 142 | − }), | |
| 143 | − })} | |
| 141 | + options={{ | |
| 142 | + ...getConnectionAwareBrandHeaderOptions({ | |
| 143 | + onOpenEnvironments: () => | |
| 144 | + navigation.navigate("SettingsSheet", { | |
| 145 | + screen: "SettingsContent", | |
| 146 | + params: { screen: "SettingsEnvironments" }, | |
| 147 | + }), | |
| 148 | + }), | |
| 149 | + headerShown: true, | |
| 150 | + }} | |
| 144 | 151 | /> |
| 145 | 152 | <HomeHeader |
| 146 | 153 | environments={environments} |
| @@ -45,6 +45,7 @@ import { · 44 unmodified lines | ||
| 45 | 45 | parseActiveThreadPath, |
| 46 | 46 | useHardwareKeyboardCommand, |
| 47 | 47 | } from "../keyboard/hardwareKeyboardCommands"; |
| 48 | +import { AndroidHomeFabLayout } from "../home/AndroidHomeFab"; | |
| 48 | 49 | import { HomeListOptionsProvider } from "../home/home-list-options"; |
| 49 | 50 | import { ThreadNavigationSidebar } from "../threads/ThreadNavigationSidebar"; |
| 50 | 51 | import { WORKSPACE_PANE_TIMING } from "./workspace-pane-animation"; |
| @@ -435,6 +436,10 @@ function AdaptiveWorkspaceLayoutContent( · 384 unmodified lines | ||
| 435 | 436 | }); |
| 436 | 437 | }, [navigation]); |
| 437 | 438 | |
| 439 | + const handleStartNewTask = useCallback(() => { | |
| 440 | + navigation.navigate("NewTaskSheet", { screen: "NewTask" }); | |
| 441 | + }, [navigation]); | |
| 442 | + | |
| 438 | 443 | // Minted here (root stack navigation) so the sidebar pane stays free of |
| 439 | 444 | // navigation hooks — on iOS it renders inside an independent nav tree. |
| 440 | 445 | const handleOpenEnvironmentSettings = useCallback(() => { |
| @@ -528,18 +533,22 @@ function AdaptiveWorkspaceLayoutContent( · 87 unmodified lines | ||
| 528 | 533 | pointerEvents={panes.primarySidebarVisible ? "auto" : "none"} |
| 529 | 534 | style={sidebarAnimatedStyle} |
| 530 | 535 | > |
| 531 | − <ThreadNavigationSidebar | |
| 532 | − width={layout.listPaneWidth} | |
| 533 | − visible={panes.primarySidebarVisible} | |
| 534 | − onRequestVisibility={revealPrimarySidebar} | |
| 535 | − selectedThreadKey={selectedThreadKey} | |
| 536 | − onOpenSettings={handleOpenSettings} | |
| 537 | − onOpenEnvironmentSettings={handleOpenEnvironmentSettings} | |
| 538 | − onNewThreadInProject={handleNewThreadInProject} | |
| 539 | − onSelectThread={handleSelectThread} | |
| 540 | − onSearchQueryChange={setPrimarySidebarSearchQuery} | |
| 541 | − searchQuery={primarySidebarSearchQuery} | |
| 542 | − /> | |
| 536 | + <View className="flex-1" style={{ width: layout.listPaneWidth }}> | |
| 537 | + <AndroidHomeFabLayout onStartNewTask={handleStartNewTask}> | |
| 538 | + <ThreadNavigationSidebar | |
| 539 | + width={layout.listPaneWidth} | |
| 540 | + visible={panes.primarySidebarVisible} | |
| 541 | + onRequestVisibility={revealPrimarySidebar} | |
| 542 | + selectedThreadKey={selectedThreadKey} | |
| 543 | + onOpenSettings={handleOpenSettings} | |
| 544 | + onOpenEnvironmentSettings={handleOpenEnvironmentSettings} | |
| 545 | + onNewThreadInProject={handleNewThreadInProject} | |
| 546 | + onSelectThread={handleSelectThread} | |
| 547 | + onSearchQueryChange={setPrimarySidebarSearchQuery} | |
| 548 | + searchQuery={primarySidebarSearchQuery} | |
| 549 | + /> | |
| 550 | + </AndroidHomeFabLayout> | |
| 551 | + </View> | |
| 543 | 552 | </Animated.View> |
| 544 | 553 | ) : null} |
| 545 | 554 | <View className="flex-1 overflow-hidden bg-screen" collapsable={false}> |
| @@ -1,4 +1,3 @@ | ||
| 1 | −import { isLiquidGlassSupported, LiquidGlassView } from "@callstack/liquid-glass"; | |
| 2 | 1 | import type { |
| 3 | 2 | EnvironmentProject, |
| 4 | 3 | EnvironmentThreadShell, |
| @@ -14,16 +13,16 @@ import { AsyncResult } from "effect/unstable/reactivity"; · 9 unmodified lines | ||
| 14 | 13 | import type { EnvironmentId } from "@t3tools/contracts"; |
| 15 | 14 | import { sortPinnedThreadsByOrderKey } from "@t3tools/client-runtime/state/thread-sort"; |
| 16 | 15 | import type { ChangeRequestSettleSource } from "@t3tools/client-runtime/state/thread-settled"; |
| 17 | −import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react"; | |
| 18 | −import type { LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent } from "react-native"; | |
| 16 | +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; | |
| 17 | +import type { LayoutChangeEvent } from "react-native"; | |
| 19 | 18 | import { Platform, Pressable, StyleSheet, TextInput, View } from "react-native"; |
| 20 | 19 | import { Gesture, GestureDetector } from "react-native-gesture-handler"; |
| 21 | 20 | import type { SwipeableMethods } from "react-native-gesture-handler/ReanimatedSwipeable"; |
| 22 | 21 | import { useSafeAreaInsets } from "react-native-safe-area-context"; |
| 23 | 22 | import type { SearchBarCommands } from "react-native-screens"; |
| 24 | −import Svg, { Defs, LinearGradient, Rect, Stop } from "react-native-svg"; | |
| 25 | 23 | |
| 26 | 24 | import { AppText as Text } from "../../components/AppText"; |
| 25 | +import { CompactBrandTitle } from "../../components/CompactBrandTitle"; | |
| 27 | 26 | import { ControlPillMenu } from "../../components/ControlPill"; |
| 28 | 27 | import { SymbolView } from "../../components/AppSymbol"; |
| 29 | 28 | import { NATIVE_LIQUID_GLASS_SUPPORTED } from "../../native/native-glass"; |
| @@ -40,7 +39,6 @@ import { usePendingNewTasks } from "../../state/use-pending-new-tasks"; · 10 unmodified lines | ||
| 40 | 39 | import { useWorkspaceState } from "../../state/workspace"; |
| 41 | 40 | import { useSavedRemoteConnections } from "../../state/use-remote-environment-registry"; |
| 42 | 41 | import { useHardwareKeyboardCommand } from "../keyboard/hardwareKeyboardCommands"; |
| 43 | −import { useAppearancePreferences } from "../settings/appearance/AppearancePreferencesProvider"; | |
| 44 | 42 | import { |
| 45 | 43 | hasCustomHomeListOptions, |
| 46 | 44 | PROJECT_SORT_OPTIONS, |
| @@ -97,48 +95,7 @@ type SidebarListItem = · 50 unmodified lines | ||
| 97 | 95 | | ThreadListV2ListItem |
| 98 | 96 | | { readonly type: "v2-show-more"; readonly key: string; readonly hiddenCount: number }; |
| 99 | 97 | |
| 100 | −/** | |
| 101 | − * Shared capsule behind the sidebar header buttons — a native liquid-glass | |
| 102 | − * surface on iOS 26+, a tinted pill everywhere else. | |
| 103 | − */ | |
| 104 | −function SidebarHeaderButtonGroup(props: { | |
| 105 | − readonly children: ReactNode; | |
| 106 | − readonly colorScheme: "light" | "dark"; | |
| 107 | −}) { | |
| 108 | − const fallbackBackground = useThemeColor("--color-glass-surface"); | |
| 109 | − const fallbackBorder = useThemeColor("--color-header-border"); | |
| 110 | − if (isLiquidGlassSupported) { | |
| 111 | − return ( | |
| 112 | − <LiquidGlassView | |
| 113 | − colorScheme={props.colorScheme} | |
| 114 | − effect="regular" | |
| 115 | − interactive | |
| 116 | − style={styles.headerButtonGroup} | |
| 117 | − > | |
| 118 | − {props.children} | |
| 119 | − </LiquidGlassView> | |
| 120 | − ); | |
| 121 | − } | |
| 122 | − | |
| 123 | − return ( | |
| 124 | − <View | |
| 125 | − style={[ | |
| 126 | − styles.headerButtonGroup, | |
| 127 | − { backgroundColor: fallbackBackground, borderColor: fallbackBorder }, | |
| 128 | − { borderWidth: StyleSheet.hairlineWidth }, | |
| 129 | − ]} | |
| 130 | − > | |
| 131 | − {props.children} | |
| 132 | − </View> | |
| 133 | − ); | |
| 134 | −} | |
| 135 | − | |
| 136 | 98 | const SIDEBAR_STICKY_HEADER_HEIGHT = 106; |
| 137 | −const SIDEBAR_STICKY_HEADER_FADE_HEIGHT = 44; | |
| 138 | −const SIDEBAR_HEADER_WASH_OPACITY = { | |
| 139 | − dark: [0.22, 0.14, 0.04], | |
| 140 | − light: [0.46, 0.3, 0.08], | |
| 141 | −} as const; | |
| 142 | 99 | |
| 143 | 100 | interface ThreadNavigationSidebarProps { |
| 144 | 101 | readonly width: number; |
| @@ -195,16 +152,13 @@ function ThreadNavigationSidebarPane( · 50 unmodified lines | ||
| 195 | 152 | props: ThreadNavigationSidebarProps & { readonly nativeChrome: boolean }, |
| 196 | 153 | ) { |
| 197 | 154 | const insets = useSafeAreaInsets(); |
| 198 | − const { themeAppearance: colorScheme } = useAppearancePreferences(); | |
| 199 | 155 | const projects = useProjects(); |
| 200 | 156 | const threads = useThreadShells(); |
| 201 | 157 | const { environments: workspaceEnvironments, state: catalogState } = useWorkspaceState(); |
| 202 | 158 | const { savedConnectionsById } = useSavedRemoteConnections(); |
| 203 | − const [headerIsOverContent, setHeaderIsOverContent] = useState(false); | |
| 204 | 159 | const searchInputRef = useRef<TextInput>(null); |
| 205 | 160 | const searchBarRef = useRef<SearchBarCommands>(null); |
| 206 | 161 | const openSwipeableRef = useRef<SwipeableMethods | null>(null); |
| 207 | − const headerIsOverContentRef = useRef(false); | |
| 208 | 162 | const sidebarScrollGesture = useMemo(() => Gesture.Native(), []); |
| 209 | 163 | const { |
| 210 | 164 | archiveThread, |
| @@ -780,8 +734,6 @@ function ThreadNavigationSidebarPane( · 569 unmodified lines | ||
| 780 | 734 | const borderColor = useThemeColor("--color-border"); |
| 781 | 735 | const mutedColor = useThemeColor("--color-foreground-muted"); |
| 782 | 736 | const placeholderColor = useThemeColor("--color-placeholder"); |
| 783 | − const headerFadeColor = String(backgroundColor); | |
| 784 | − const headerWashOpacity = SIDEBAR_HEADER_WASH_OPACITY[colorScheme]; | |
| 785 | 737 | const [measuredHeaderHeight, setMeasuredHeaderHeight] = useState<number | null>(null); |
| 786 | 738 | // The sticky header (title row, search field, optional connection status) |
| 787 | 739 | // is measured so the list inset always matches its real height — no |
| @@ -810,19 +762,10 @@ function ThreadNavigationSidebarPane( · 22 unmodified lines | ||
| 810 | 762 | }, |
| 811 | 763 | [props.onSelectThread], |
| 812 | 764 | ); |
| 813 | − const handleScroll = useCallback((event: NativeSyntheticEvent<NativeScrollEvent>) => { | |
| 814 | − const next = event.nativeEvent.contentOffset.y > 6; | |
| 815 | − if (headerIsOverContentRef.current === next) { | |
| 816 | − return; | |
| 817 | − } | |
| 818 | − headerIsOverContentRef.current = next; | |
| 819 | − setHeaderIsOverContent(next); | |
| 820 | − }, []); | |
| 821 | 765 | const handleScrollBeginDrag = useCallback(() => { |
| 822 | 766 | openSwipeableRef.current?.close(); |
| 823 | 767 | }, []); |
| 824 | 768 | const { swipeEnabled, scrollGateHandlers } = useSwipeableScrollGate({ |
| 825 | − onScroll: handleScroll, | |
| 826 | 769 | onScrollBeginDrag: handleScrollBeginDrag, |
| 827 | 770 | }); |
| 828 | 771 | // Project shells load after the first rows draw, so the maps they feed have |
| @@ -1321,7 +1264,10 @@ function ThreadNavigationSidebarPane( · 492 unmodified lines | ||
| 1321 | 1264 | contentContainerStyle={[ |
| 1322 | 1265 | styles.threadListContent, |
| 1323 | 1266 | { |
| 1324 | − paddingBottom: 16 + insets.bottom, | |
| 1267 | + paddingBottom: | |
| 1268 | + Platform.OS === "android" | |
| 1269 | + ? Math.max(insets.bottom, 16) + 88 - insets.bottom | |
| 1270 | + : 16 + insets.bottom, | |
| 1325 | 1271 | paddingTop: topListInset, |
| 1326 | 1272 | }, |
| 1327 | 1273 | ]} |
| @@ -1340,65 +1286,34 @@ function ThreadNavigationSidebarPane( · 12 unmodified lines | ||
| 1340 | 1286 | |
| 1341 | 1287 | <View |
| 1342 | 1288 | className="absolute inset-x-0 top-0 z-[4]" |
| 1289 | + collapsable={false} | |
| 1343 | 1290 | onLayout={handleStickyHeaderLayout} |
| 1344 | − pointerEvents="box-none" | |
| 1345 | − style={{ paddingTop: insets.top }} | |
| 1291 | + pointerEvents="auto" | |
| 1292 | + style={{ | |
| 1293 | + paddingTop: insets.top, | |
| 1294 | + backgroundColor, | |
| 1295 | + }} | |
| 1346 | 1296 | > |
| 1347 | − <View | |
| 1348 | − className="absolute inset-x-0 top-0" | |
| 1349 | − pointerEvents="none" | |
| 1350 | − accessibilityElementsHidden | |
| 1351 | − importantForAccessibility="no-hide-descendants" | |
| 1352 | − style={{ height: stickyHeaderHeight + SIDEBAR_STICKY_HEADER_FADE_HEIGHT }} | |
| 1353 | − > | |
| 1354 | − <Svg width="100%" height="100%"> | |
| 1355 | − <Defs> | |
| 1356 | − <LinearGradient id="sidebar-header-wash" x1="0%" x2="0%" y1="0%" y2="100%"> | |
| 1357 | − <Stop | |
| 1358 | − offset="0%" | |
| 1359 | − stopColor={headerFadeColor} | |
| 1360 | − stopOpacity={headerIsOverContent ? headerWashOpacity[0] : 0} | |
| 1361 | − /> | |
| 1362 | − <Stop | |
| 1363 | − offset="58%" | |
| 1364 | − stopColor={headerFadeColor} | |
| 1365 | − stopOpacity={headerIsOverContent ? headerWashOpacity[1] : 0} | |
| 1366 | − /> | |
| 1367 | − <Stop | |
| 1368 | − offset="88%" | |
| 1369 | − stopColor={headerFadeColor} | |
| 1370 | − stopOpacity={headerIsOverContent ? headerWashOpacity[2] : 0} | |
| 1371 | − /> | |
| 1372 | − <Stop offset="100%" stopColor={headerFadeColor} stopOpacity={0} /> | |
| 1373 | − </LinearGradient> | |
| 1374 | − </Defs> | |
| 1375 | − <Rect width="100%" height="100%" fill="url(#sidebar-header-wash)" /> | |
| 1376 | − </Svg> | |
| 1377 | − </View> | |
| 1378 | 1297 | <View className="h-[50px] flex-row items-end gap-0.5 pr-2 pl-5"> |
| 1379 | 1298 | {/* Title slot doubles as the connection status surface: while an |
| 1380 | − environment reconnects, "Threads" fades to a status label in | |
| 1299 | + environment reconnects, the brand fades to a status label in | |
| 1381 | 1300 | place (no layout shift in the list below). */} |
| 1382 | 1301 | <WorkspaceConnectionTitle |
| 1383 | 1302 | grow |
| 1384 | 1303 | onPress={props.onOpenEnvironmentSettings} |
| 1385 | 1304 | size="pageTitle" |
| 1386 | 1305 | brand={ |
| 1387 | − <Text className="flex-1 text-[34px] font-t3-bold text-foreground" numberOfLines={1}> | |
| 1388 | − Threads | |
| 1389 | − </Text> | |
| 1306 | + <View className="h-11 flex-1 justify-center"> | |
| 1307 | + <CompactBrandTitle allowFontScaling={false} /> | |
| 1308 | + </View> | |
| 1390 | 1309 | } |
| 1391 | 1310 | /> |
| 1392 | − <SidebarHeaderButtonGroup colorScheme={colorScheme}> | |
| 1311 | + <View className="flex-row items-center gap-2.5"> | |
| 1393 | 1312 | <ControlPillMenu actions={listMenuActions} onPressAction={handleListMenuAction}> |
| 1394 | − <SidebarFilterButton | |
| 1395 | − grouped | |
| 1396 | − accessibilityLabel="Filter and sort threads" | |
| 1397 | − icon={filterIcon} | |
| 1398 | − /> | |
| 1313 | + <SidebarFilterButton accessibilityLabel="Filter and sort threads" icon={filterIcon} /> | |
| 1399 | 1314 | </ControlPillMenu> |
| 1400 | − <SidebarHeaderActions grouped onOpenSettings={props.onOpenSettings} /> | |
| 1401 | − </SidebarHeaderButtonGroup> | |
| 1315 | + <SidebarHeaderActions onOpenSettings={props.onOpenSettings} /> | |
| 1316 | + </View> | |
| 1402 | 1317 | </View> |
| 1403 | 1318 | |
| 1404 | 1319 | <View className="mx-4 mt-[9px] h-[38px] flex-row items-center gap-1.5 rounded-xl bg-sidebar-search pr-2.5 pl-[11px]"> |
| @@ -1423,12 +1338,6 @@ function ThreadNavigationSidebarPane( · 18 unmodified lines | ||
| 1423 | 1338 | } |
| 1424 | 1339 | |
| 1425 | 1340 | const styles = StyleSheet.create({ |
| 1426 | − headerButtonGroup: { | |
| 1427 | − alignItems: "center", | |
| 1428 | − borderRadius: 22, | |
| 1429 | − flexDirection: "row", | |
| 1430 | − overflow: "hidden", | |
| 1431 | − }, | |
| 1432 | 1341 | threadList: { |
| 1433 | 1342 | flex: 1, |
| 1434 | 1343 | }, |
| @@ -1,5 +1,5 @@ | ||
| 1 | 1 | import { SymbolView } from "../../components/AppSymbol"; |
| 2 | −import { Pressable, StyleSheet } from "react-native"; | |
| 2 | +import { Pressable } from "react-native"; | |
| 3 | 3 | |
| 4 | 4 | import { useThemeColor } from "../../lib/useThemeColor"; |
| 5 | 5 | |
| @@ -10,31 +10,17 @@ export type SidebarFilterButtonIcon = · 4 unmodified lines | ||
| 10 | 10 | export function SidebarFilterButton(props: { |
| 11 | 11 | readonly accessibilityLabel: string; |
| 12 | 12 | readonly icon: SidebarFilterButtonIcon; |
| 13 | − /** Rendered inside a shared capsule group — no own background/border. */ | |
| 14 | − readonly grouped?: boolean; | |
| 15 | 13 | }) { |
| 16 | 14 | const iconColor = useThemeColor("--color-foreground"); |
| 17 | − const pressedBackgroundColor = useThemeColor("--color-subtle"); | |
| 18 | − const idleBackgroundColor = useThemeColor("--color-glass-surface"); | |
| 19 | − const borderColor = useThemeColor("--color-header-border"); | |
| 20 | 15 | |
| 21 | 16 | return ( |
| 22 | 17 | <Pressable |
| 23 | − className="h-11 w-[50px] cursor-pointer items-center justify-center rounded-[22px]" | |
| 18 | + className="size-11 cursor-pointer items-center justify-center rounded-full bg-subtle active:opacity-70" | |
| 24 | 19 | accessibilityLabel={props.accessibilityLabel} |
| 25 | 20 | accessibilityRole="button" |
| 26 | 21 | hitSlop={4} |
| 27 | − style={({ pressed }) => [ | |
| 28 | − props.grouped | |
| 29 | − ? { backgroundColor: pressed ? pressedBackgroundColor : "transparent", borderWidth: 0 } | |
| 30 | − : { | |
| 31 | − backgroundColor: pressed ? pressedBackgroundColor : idleBackgroundColor, | |
| 32 | − borderColor, | |
| 33 | − borderWidth: StyleSheet.hairlineWidth, | |
| 34 | − }, | |
| 35 | − ]} | |
| 36 | 22 | > |
| 37 | − <SymbolView name={props.icon} size={20} tintColor={iconColor} type="monochrome" /> | |
| 23 | + <SymbolView name={props.icon} size={16} tintColor={iconColor} type="monochrome" /> | |
| 38 | 24 | </Pressable> |
| 39 | 25 | ); |
| 40 | 26 | } |
| @@ -1,43 +1,28 @@ | ||
| 1 | 1 | import { SymbolView } from "../../components/AppSymbol"; |
| 2 | −import { Pressable, StyleSheet, View } from "react-native"; | |
| 2 | +import { Pressable, View } from "react-native"; | |
| 3 | 3 | |
| 4 | 4 | import { useThemeColor } from "../../lib/useThemeColor"; |
| 5 | 5 | |
| 6 | 6 | export interface SidebarHeaderActionsProps { |
| 7 | 7 | readonly onOpenSettings: () => void; |
| 8 | − /** Rendered inside a shared capsule group — buttons drop their own chrome. */ | |
| 9 | − readonly grouped?: boolean; | |
| 10 | 8 | } |
| 11 | 9 | |
| 12 | 10 | function FallbackHeaderButton(props: { |
| 13 | 11 | readonly accessibilityLabel: string; |
| 14 | 12 | readonly icon: "gearshape" | "square.and.pencil"; |
| 15 | − readonly grouped?: boolean; | |
| 16 | 13 | readonly onPress: () => void; |
| 17 | 14 | }) { |
| 18 | 15 | const iconColor = useThemeColor("--color-foreground"); |
| 19 | − const pressedBackgroundColor = useThemeColor("--color-subtle"); | |
| 20 | − const idleBackgroundColor = useThemeColor("--color-glass-surface"); | |
| 21 | − const borderColor = useThemeColor("--color-header-border"); | |
| 22 | 16 | |
| 23 | 17 | return ( |
| 24 | 18 | <Pressable |
| 25 | − className="h-11 w-[50px] items-center justify-center rounded-[22px]" | |
| 19 | + className="size-11 items-center justify-center rounded-full bg-subtle active:opacity-70" | |
| 26 | 20 | accessibilityLabel={props.accessibilityLabel} |
| 27 | 21 | accessibilityRole="button" |
| 28 | 22 | hitSlop={4} |
| 29 | 23 | onPress={props.onPress} |
| 30 | − style={({ pressed }) => [ | |
| 31 | − props.grouped | |
| 32 | − ? { backgroundColor: pressed ? pressedBackgroundColor : "transparent", borderWidth: 0 } | |
| 33 | − : { | |
| 34 | − backgroundColor: pressed ? pressedBackgroundColor : idleBackgroundColor, | |
| 35 | − borderColor, | |
| 36 | − borderWidth: StyleSheet.hairlineWidth, | |
| 37 | − }, | |
| 38 | − ]} | |
| 39 | 24 | > |
| 40 | − <SymbolView name={props.icon} size={20} tintColor={iconColor} type="monochrome" /> | |
| 25 | + <SymbolView name={props.icon} size={18} tintColor={iconColor} type="monochrome" /> | |
| 41 | 26 | </Pressable> |
| 42 | 27 | ); |
| 43 | 28 | } |
| @@ -47,7 +32,6 @@ export function SidebarHeaderActions(props: SidebarHeaderActionsProps) { · 3 unmodified lines | ||
| 47 | 32 | <View className="flex-row items-center gap-0.5"> |
| 48 | 33 | <FallbackHeaderButton |
| 49 | 34 | accessibilityLabel="Open settings" |
| 50 | − grouped={props.grouped} | |
| 51 | 35 | icon="gearshape" |
| 52 | 36 | onPress={props.onOpenSettings} |
| 53 | 37 | /> |