From 85cb11968028672643f31b7d61054eef18b35d80 Mon Sep 17 00:00:00 2001 From: Patrick O'Sullivan Date: Mon, 6 Jan 2025 15:12:56 -0600 Subject: [PATCH 1/4] desktop: bring back leap --- .../app/navigation/desktop/TopLevelDrawer.tsx | 56 ++- .../ui/src/components/BareChatInput/index.tsx | 36 +- packages/ui/src/components/ChatList.tsx | 10 +- packages/ui/src/components/Form/inputs.tsx | 67 ++-- .../ui/src/components/GlobalSearch/index.tsx | 361 ++++++++++++++++++ packages/ui/src/contexts/globalSearch.tsx | 25 ++ packages/ui/src/index.tsx | 3 + 7 files changed, 491 insertions(+), 67 deletions(-) create mode 100644 packages/ui/src/components/GlobalSearch/index.tsx create mode 100644 packages/ui/src/contexts/globalSearch.tsx diff --git a/packages/app/navigation/desktop/TopLevelDrawer.tsx b/packages/app/navigation/desktop/TopLevelDrawer.tsx index 04c4956945..d99bb7e7c2 100644 --- a/packages/app/navigation/desktop/TopLevelDrawer.tsx +++ b/packages/app/navigation/desktop/TopLevelDrawer.tsx @@ -3,12 +3,20 @@ import { createDrawerNavigator, } from '@react-navigation/drawer'; import * as store from '@tloncorp/shared/store'; -import { AvatarNavIcon, NavIcon, YStack, useWebAppUpdate } from '@tloncorp/ui'; +import { + AvatarNavIcon, + GlobalSearch, + GlobalSearchProvider, + NavIcon, + YStack, + useWebAppUpdate, +} from '@tloncorp/ui'; import { getVariableValue, useTheme } from 'tamagui'; import { ActivityScreen } from '../../features/top/ActivityScreen'; import { useCurrentUserId } from '../../hooks/useCurrentUser'; import { RootDrawerParamList } from '../types'; +import { useRootNavigation } from '../utils'; import { HomeNavigator } from './HomeNavigator'; import { ProfileScreenNavigator } from './ProfileScreenNavigator'; @@ -68,25 +76,33 @@ const DrawerContent = (props: DrawerContentComponentProps) => { }; export const TopLevelDrawer = () => { + const { navigateToGroup, navigateToChannel } = useRootNavigation(); + return ( - { - return ; - }} - initialRouteName="Home" - screenOptions={{ - drawerType: 'permanent', - headerShown: false, - drawerStyle: { - width: 48, - backgroundColor: getVariableValue(useTheme().background), - borderRightColor: getVariableValue(useTheme().border), - }, - }} - > - - - - + + + { + return ; + }} + initialRouteName="Home" + screenOptions={{ + drawerType: 'permanent', + headerShown: false, + drawerStyle: { + width: 48, + backgroundColor: getVariableValue(useTheme().background), + borderRightColor: getVariableValue(useTheme().border), + }, + }} + > + + + + + ); }; diff --git a/packages/ui/src/components/BareChatInput/index.tsx b/packages/ui/src/components/BareChatInput/index.tsx index 12290637f4..c2c3f0e36f 100644 --- a/packages/ui/src/components/BareChatInput/index.tsx +++ b/packages/ui/src/components/BareChatInput/index.tsx @@ -36,6 +36,7 @@ import { UploadedImageAttachment, useAttachmentContext, } from '../../contexts'; +import { useGlobalSearch } from '../../contexts/globalSearch'; import { DEFAULT_MESSAGE_INPUT_HEIGHT } from '../MessageInput'; import { AttachmentPreviewList } from '../MessageInput/AttachmentPreviewList'; import { @@ -642,10 +643,35 @@ export default function BareChatInput({ } }; + const { setIsOpen } = useGlobalSearch(); + const handleBlur = useCallback(() => { setShouldBlur(true); }, [setShouldBlur]); + const handleKeyPress = useCallback( + (e: any) => { + const keyEvent = e.nativeEvent as unknown as KeyboardEvent; + if (!isWeb) return; + + if ( + (keyEvent.metaKey || keyEvent.ctrlKey) && + keyEvent.key.toLowerCase() === 'k' + ) { + e.preventDefault(); + inputRef.current?.blur(); + setIsOpen(true); + return; + } + + if (keyEvent.key === 'Enter' && !keyEvent.shiftKey) { + e.preventDefault(); + handleSend(); + } + }, + [setIsOpen, handleSend] + ); + return ( { - if (isWeb && e.nativeEvent.key === 'Enter') { - const keyEvent = e.nativeEvent as unknown as KeyboardEvent; - if (!keyEvent.shiftKey) { - e.preventDefault(); - handleSend(); - } - } - }} + onKeyPress={handleKeyPress} multiline placeholder={placeholder} {...(!isWeb ? placeholderTextColor : {})} diff --git a/packages/ui/src/components/ChatList.tsx b/packages/ui/src/components/ChatList.tsx index 97f0dc9f25..4c659154ce 100644 --- a/packages/ui/src/components/ChatList.tsx +++ b/packages/ui/src/components/ChatList.tsx @@ -31,7 +31,7 @@ import { Tabs } from './Tabs'; export type TabName = 'all' | 'groups' | 'messages'; type SectionHeaderData = { type: 'sectionHeader'; title: string }; -type ChatListItemData = db.Chat | SectionHeaderData; +export type ChatListItemData = db.Chat | SectionHeaderData; export const ChatList = React.memo(function ChatListComponent({ pinned, @@ -187,15 +187,15 @@ export const ChatList = React.memo(function ChatListComponent({ ); }); -function getItemType(item: ChatListItemData) { +export function getItemType(item: ChatListItemData) { return isSectionHeader(item) ? 'sectionHeader' : item.type; } -function isSectionHeader(data: ChatListItemData): data is SectionHeaderData { +export function isSectionHeader(data: ChatListItemData): data is SectionHeaderData { return 'type' in data && data.type === 'sectionHeader'; } -function getChatKey(chatItem: ChatListItemData) { +export function getChatKey(chatItem: ChatListItemData) { if (!chatItem || typeof chatItem !== 'object') { return 'invalid-item'; } @@ -309,7 +309,7 @@ const ChatListSearch = React.memo(function ChatListSearchComponent({ ); }); -function useFilteredChats({ +export function useFilteredChats({ pinned, unpinned, pending, diff --git a/packages/ui/src/components/Form/inputs.tsx b/packages/ui/src/components/Form/inputs.tsx index 8542956ddf..2466db4f43 100644 --- a/packages/ui/src/components/Form/inputs.tsx +++ b/packages/ui/src/components/Form/inputs.tsx @@ -304,40 +304,41 @@ interface TextInputWithIconAndButtonProps } export const TextInputWithIconAndButton = React.memo( - function TextInputWithIconAndButtonRaw({ - icon, - buttonText, - onButtonPress, - ...textInputProps - }: TextInputWithIconAndButtonProps) { - return ( - - - - - - ); - } + + + + + ); + } + ) ); // Toggle group diff --git a/packages/ui/src/components/GlobalSearch/index.tsx b/packages/ui/src/components/GlobalSearch/index.tsx new file mode 100644 index 0000000000..b90d3f2c56 --- /dev/null +++ b/packages/ui/src/components/GlobalSearch/index.tsx @@ -0,0 +1,361 @@ +import { FlashList, ListRenderItem } from '@shopify/flash-list'; +import type * as db from '@tloncorp/shared/db'; +import * as store from '@tloncorp/shared/store'; +import { + ChatListItem, + ChatListItemData, + LoadingSpinner, + SectionListHeader, + Text, + TextInputWithIconAndButton, + View, + XStack, + YStack, + getChatKey, + getItemType, + isSectionHeader, + useFilteredChats, +} from '@tloncorp/ui'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { + LayoutChangeEvent, + NativeSyntheticEvent, + TextInput, + TextInputKeyPressEventData, +} from 'react-native'; +import { getTokenValue } from 'tamagui'; + +import { useGlobalSearch } from '../../contexts/globalSearch'; + +export interface GlobalSearchProps { + navigateToGroup: (id: string) => void; + navigateToChannel: (channel: db.Channel) => void; +} + +export function GlobalSearch({ navigateToGroup, navigateToChannel }: GlobalSearchProps) { + const { isOpen, setIsOpen } = useGlobalSearch(); + const [searchQuery, setSearchQuery] = useState(''); + const [selectedIndex, setSelectedIndex] = useState(0); + const inputRef = useRef(null); + const groupsQuery = store.useGroups({}); + const contactsQuery = store.useContacts(); + + const isLoading = groupsQuery.isLoading || contactsQuery.isLoading; + const hasError = groupsQuery.error || contactsQuery.error; + + const { data: chats } = store.useCurrentChats({ + enabled: isOpen, + }); + + const resolvedChats = useMemo(() => { + return { + pinned: chats?.pinned ?? [], + unpinned: chats?.unpinned ?? [], + }; + }, [chats]); + + const displayData = useFilteredChats({ + pinned: resolvedChats.pinned, + unpinned: resolvedChats.unpinned, + pending: [], + searchQuery, + activeTab: 'all', + }); + + const listItems: ChatListItemData[] = useMemo( + () => + displayData.flatMap((section) => { + return [ + { title: section.title, type: 'sectionHeader' }, + ...section.data, + ]; + }), + [displayData] + ); + + // Find first non-header item when search query changes + // Only reset selection when search query changes + useEffect(() => { + const firstItemIndex = listItems.findIndex( + (item) => !isSectionHeader(item) + ); + if (firstItemIndex >= 0) { + setSelectedIndex(firstItemIndex); + } + }, [searchQuery]); // Only run when search query changes + + const contentContainerStyle = { + padding: getTokenValue('$l', 'size'), + paddingBottom: 100, // bottom nav height + some cushion + }; + + const sizeRefs = useRef({ + sectionHeader: 28, + chatListItem: 72, + }); + + const handleHeaderLayout = useCallback((e: LayoutChangeEvent) => { + sizeRefs.current.sectionHeader = e.nativeEvent.layout.height; + }, []); + + const handleItemLayout = useCallback((e: LayoutChangeEvent) => { + sizeRefs.current.chatListItem = e.nativeEvent.layout.height; + }, []); + + const handleOverrideLayout = useCallback( + (layout: { span?: number; size?: number }, item: ChatListItemData) => { + layout.size = isSectionHeader(item) + ? sizeRefs.current.sectionHeader + : sizeRefs.current.chatListItem; + }, + [] + ); + + const onPressItem = useCallback( + async (item: db.Chat) => { + if (item.type === 'group') { + navigateToGroup(item.group.id); + } else { + navigateToChannel(item.channel); + } + + setIsOpen(false); + }, + [navigateToGroup, navigateToChannel, setIsOpen] + ); + + const renderItem: ListRenderItem = useCallback( + ({ item }) => { + if (isSectionHeader(item)) { + return ( + + {item.title} + + ); + } else { + return ( + + ); + } + }, + [ + handleHeaderLayout, + onPressItem, + handleItemLayout, + listItems, + selectedIndex, + ] + ); + + const handleNavigationKey = useCallback( + (key: string) => { + let nextIndex = selectedIndex; + const selectedItem = listItems[selectedIndex]; + + switch (key) { + case 'ArrowDown': + nextIndex = selectedIndex + 1; + while ( + nextIndex < listItems.length && + isSectionHeader(listItems[nextIndex]) + ) { + nextIndex++; + } + if (nextIndex < listItems.length) { + setSelectedIndex(nextIndex); + } + break; + case 'ArrowUp': + nextIndex = selectedIndex - 1; + while (nextIndex >= 0 && isSectionHeader(listItems[nextIndex])) { + nextIndex--; + } + if (nextIndex >= 0) { + setSelectedIndex(nextIndex); + } + break; + case 'Enter': + if (selectedItem && !isSectionHeader(selectedItem)) { + onPressItem(selectedItem); + } + break; + } + }, + [selectedIndex, listItems, onPressItem] + ); + + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'k') { + event.preventDefault(); + setIsOpen(!isOpen); + } else if (event.key === 'Escape') { + event.preventDefault(); + setIsOpen(false); + } else if (isOpen) { + // Handle navigation keys + switch (event.key) { + case 'ArrowDown': + case 'ArrowUp': + case 'Enter': + event.preventDefault(); + handleNavigationKey(event.key); + break; + } + } + }; + + document.addEventListener('keydown', handleKeyDown); + return () => document.removeEventListener('keydown', handleKeyDown); + }, [isOpen, handleNavigationKey, setIsOpen]); + + useEffect(() => { + if (isOpen) { + inputRef.current?.focus(); + setSearchQuery(''); + } + }, [isOpen]); + + if (!isOpen) return null; + + return ( + <> + { + console.log('pressed'); + setIsOpen(false); + }} + style={{ + position: 'fixed', + top: 0, + left: 0, + right: 0, + bottom: 0, + backgroundColor: 'rgba(0, 0, 0, 0.5)', + zIndex: 50, + }} + /> + + + ) => { + const key = e.nativeEvent.key; + const metaKey = (e.nativeEvent as any).metaKey; + const ctrlKey = (e.nativeEvent as any).ctrlKey; + + if ((metaKey || ctrlKey) && key.toLowerCase() === 'k') { + e.preventDefault(); + setIsOpen(false); + } else if ( + key === 'ArrowDown' || + key === 'ArrowUp' || + key === 'Enter' || + key === 'Escape' + ) { + e.preventDefault(); + handleNavigationKey(key); + } + }} + onButtonPress={() => setIsOpen(false)} + buttonText="Close" + spellCheck={false} + autoCorrect={false} + autoCapitalize="none" + /> + + {isLoading ? ( + + + + ) : hasError ? ( + + Error loading results. Please try again. + + ) : searchQuery !== '' && !displayData[0]?.data.length ? ( + + No results found + + ) : ( + + )} + + + + + + ↑↓ + + + to navigate + + + + + enter + + + to select + + + + + esc + + + or + + + {navigator.platform.includes('Mac') ? '⌘K' : 'Ctrl+K'} + + + to close + + + + + + ); +} diff --git a/packages/ui/src/contexts/globalSearch.tsx b/packages/ui/src/contexts/globalSearch.tsx new file mode 100644 index 0000000000..5e609f6a9c --- /dev/null +++ b/packages/ui/src/contexts/globalSearch.tsx @@ -0,0 +1,25 @@ +import React, { createContext, useContext, useState } from 'react'; + +interface GlobalSearchContextType { + isOpen: boolean; + setIsOpen: (open: boolean) => void; +} + +const GlobalSearchContext = createContext({ + isOpen: false, + setIsOpen: () => {}, +}); + +export function GlobalSearchProvider({ children }: { children: React.ReactNode }) { + const [isOpen, setIsOpen] = useState(false); + + return ( + + {children} + + ); +} + +export function useGlobalSearch() { + return useContext(GlobalSearchContext); +} diff --git a/packages/ui/src/index.tsx b/packages/ui/src/index.tsx index 81c23e3642..15e264c63b 100644 --- a/packages/ui/src/index.tsx +++ b/packages/ui/src/index.tsx @@ -38,6 +38,7 @@ export * from './components/FeatureFlagScreenView'; export * from './components/FloatingActionButton'; export * from './components/FormInput'; export * from './components/Form'; +export * from './components/GlobalSearch'; export * from './components/GalleryPost'; export * from './components/GroupChannelsScreenView'; export * from './components/GroupMembersScreenView'; @@ -59,6 +60,7 @@ export * from './components/NavBarView'; export * from './components/Modal'; export * from './components/NavBar'; export * from './components/Onboarding'; +export * from './components/Overlay'; export * from './components/ParentAgnosticKeyboardAvoidingView'; export * from './components/PostScreenView'; export { default as Pressable } from './components/Pressable'; @@ -78,6 +80,7 @@ export * from './components/ArvosDiscussing'; export * from './components/PersonalInviteSheet'; export * as Form from './components/Form'; export * from './contexts'; +export { GlobalSearchProvider } from './contexts/globalSearch'; export * from './tamagui.config'; export * from './types'; export * from './utils'; From 7a47d74f5f6fe736fa5b19dc98c76bc2cda58805 Mon Sep 17 00:00:00 2001 From: Patrick O'Sullivan Date: Tue, 7 Jan 2025 09:38:18 -0600 Subject: [PATCH 2/4] Address @latter-bolden feedback --- .../ui/src/components/GlobalSearch/index.tsx | 80 +++++++++++-------- 1 file changed, 48 insertions(+), 32 deletions(-) diff --git a/packages/ui/src/components/GlobalSearch/index.tsx b/packages/ui/src/components/GlobalSearch/index.tsx index b90d3f2c56..5dc1c32fc1 100644 --- a/packages/ui/src/components/GlobalSearch/index.tsx +++ b/packages/ui/src/components/GlobalSearch/index.tsx @@ -6,6 +6,7 @@ import { ChatListItemData, LoadingSpinner, SectionListHeader, + TabName, Text, TextInputWithIconAndButton, View, @@ -32,7 +33,10 @@ export interface GlobalSearchProps { navigateToChannel: (channel: db.Channel) => void; } -export function GlobalSearch({ navigateToGroup, navigateToChannel }: GlobalSearchProps) { +export function GlobalSearch({ + navigateToGroup, + navigateToChannel, +}: GlobalSearchProps) { const { isOpen, setIsOpen } = useGlobalSearch(); const [searchQuery, setSearchQuery] = useState(''); const [selectedIndex, setSelectedIndex] = useState(0); @@ -54,13 +58,18 @@ export function GlobalSearch({ navigateToGroup, navigateToChannel }: GlobalSearc }; }, [chats]); - const displayData = useFilteredChats({ - pinned: resolvedChats.pinned, - unpinned: resolvedChats.unpinned, - pending: [], - searchQuery, - activeTab: 'all', - }); + const filteredChatsConfig = useMemo( + () => ({ + pinned: resolvedChats.pinned, + unpinned: resolvedChats.unpinned, + pending: [], + searchQuery, + activeTab: 'all' as TabName, + }), + [resolvedChats, searchQuery, selectedIndex] // We need to include selectedIndex to trigger re-render when it changes + ); + + const displayData = useFilteredChats(filteredChatsConfig); const listItems: ChatListItemData[] = useMemo( () => @@ -84,11 +93,6 @@ export function GlobalSearch({ navigateToGroup, navigateToChannel }: GlobalSearc } }, [searchQuery]); // Only run when search query changes - const contentContainerStyle = { - padding: getTokenValue('$l', 'size'), - paddingBottom: 100, // bottom nav height + some cushion - }; - const sizeRefs = useRef({ sectionHeader: 28, chatListItem: 72, @@ -193,6 +197,36 @@ export function GlobalSearch({ navigateToGroup, navigateToChannel }: GlobalSearc [selectedIndex, listItems, onPressItem] ); + const contentContainerStyle = useMemo( + () => ({ + padding: getTokenValue('$l', 'size'), + paddingBottom: 100, // bottom nav height + some cushion + }), + [] + ); + + const handleKeyPress = useCallback( + (e: NativeSyntheticEvent) => { + const key = e.nativeEvent.key; + const metaKey = (e.nativeEvent as any).metaKey; + const ctrlKey = (e.nativeEvent as any).ctrlKey; + + if ((metaKey || ctrlKey) && key.toLowerCase() === 'k') { + e.preventDefault(); + setIsOpen(false); + } else if ( + key === 'ArrowDown' || + key === 'ArrowUp' || + key === 'Enter' || + key === 'Escape' + ) { + e.preventDefault(); + handleNavigationKey(key); + } + }, + [handleNavigationKey, setIsOpen] + ); + useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'k') { @@ -232,7 +266,6 @@ export function GlobalSearch({ navigateToGroup, navigateToChannel }: GlobalSearc { - console.log('pressed'); setIsOpen(false); }} style={{ @@ -267,24 +300,7 @@ export function GlobalSearch({ navigateToGroup, navigateToChannel }: GlobalSearc icon="Search" value={searchQuery} onChangeText={setSearchQuery} - onKeyPress={(e: NativeSyntheticEvent) => { - const key = e.nativeEvent.key; - const metaKey = (e.nativeEvent as any).metaKey; - const ctrlKey = (e.nativeEvent as any).ctrlKey; - - if ((metaKey || ctrlKey) && key.toLowerCase() === 'k') { - e.preventDefault(); - setIsOpen(false); - } else if ( - key === 'ArrowDown' || - key === 'ArrowUp' || - key === 'Enter' || - key === 'Escape' - ) { - e.preventDefault(); - handleNavigationKey(key); - } - }} + onKeyPress={handleKeyPress} onButtonPress={() => setIsOpen(false)} buttonText="Close" spellCheck={false} From 6086994701f9eab09b1be1a754106c959251d616 Mon Sep 17 00:00:00 2001 From: Patrick O'Sullivan Date: Tue, 7 Jan 2025 10:15:52 -0600 Subject: [PATCH 3/4] Fix escape issue in firefox and safari, scroll the list when the user selects a different item --- packages/ui/src/components/GlobalSearch/index.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/components/GlobalSearch/index.tsx b/packages/ui/src/components/GlobalSearch/index.tsx index 5dc1c32fc1..3e90a9e3f1 100644 --- a/packages/ui/src/components/GlobalSearch/index.tsx +++ b/packages/ui/src/components/GlobalSearch/index.tsx @@ -41,6 +41,7 @@ export function GlobalSearch({ const [searchQuery, setSearchQuery] = useState(''); const [selectedIndex, setSelectedIndex] = useState(0); const inputRef = useRef(null); + const listRef = useRef>(null); const groupsQuery = store.useGroups({}); const contactsQuery = store.useContacts(); @@ -176,6 +177,7 @@ export function GlobalSearch({ } if (nextIndex < listItems.length) { setSelectedIndex(nextIndex); + listRef.current?.scrollToIndex({ index: nextIndex, animated: true }); } break; case 'ArrowUp': @@ -185,8 +187,12 @@ export function GlobalSearch({ } if (nextIndex >= 0) { setSelectedIndex(nextIndex); + listRef.current?.scrollToIndex({ index: nextIndex, animated: true }); } break; + case 'Escape': + setIsOpen(false); + break; case 'Enter': if (selectedItem && !isSectionHeader(selectedItem)) { onPressItem(selectedItem); @@ -194,7 +200,7 @@ export function GlobalSearch({ break; } }, - [selectedIndex, listItems, onPressItem] + [selectedIndex, listItems, onPressItem, setIsOpen] ); const contentContainerStyle = useMemo( @@ -322,6 +328,7 @@ export function GlobalSearch({ ) : ( Date: Tue, 7 Jan 2025 10:34:20 -0600 Subject: [PATCH 4/4] Adjust placeholder copy, use Search icon on desktop sidebar to open leap --- packages/app/features/top/ChatListScreen.tsx | 17 +++++++++++++---- .../ui/src/components/GlobalSearch/index.tsx | 2 +- packages/ui/src/index.tsx | 1 + 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/app/features/top/ChatListScreen.tsx b/packages/app/features/top/ChatListScreen.tsx index e6aab94420..0c9ce6f62a 100644 --- a/packages/app/features/top/ChatListScreen.tsx +++ b/packages/app/features/top/ChatListScreen.tsx @@ -20,6 +20,8 @@ import { ScreenHeader, View, WelcomeSheet, + useGlobalSearch, + useIsWindowNarrow, } from '@tloncorp/ui'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { ColorTokens, useTheme } from 'tamagui'; @@ -56,6 +58,7 @@ export function ChatListScreenView({ const [inviteSheetGroup, setInviteSheetGroup] = useState(); const personalInvite = db.personalInviteLink.useValue(); const viewedPersonalInvite = db.hasViewedPersonalInvite.useValue(); + const { isOpen, setIsOpen } = useGlobalSearch(); const theme = useTheme(); const inviteButtonColor = useMemo( () => @@ -229,12 +232,18 @@ export function ChatListScreenView({ const [searchQuery, setSearchQuery] = useState(''); + const isWindowNarrow = useIsWindowNarrow(); + const handleSearchInputToggled = useCallback(() => { - if (showSearchInput) { - setSearchQuery(''); + if (isWindowNarrow) { + if (showSearchInput) { + setSearchQuery(''); + } + setShowSearchInput(!showSearchInput); + } else { + setIsOpen(!isOpen); } - setShowSearchInput(!showSearchInput); - }, [showSearchInput]); + }, [showSearchInput, isWindowNarrow, isOpen, setIsOpen]); const handleGroupAction = useCallback( (action: GroupPreviewAction, group: db.Group) => { diff --git a/packages/ui/src/components/GlobalSearch/index.tsx b/packages/ui/src/components/GlobalSearch/index.tsx index 3e90a9e3f1..8f74e09975 100644 --- a/packages/ui/src/components/GlobalSearch/index.tsx +++ b/packages/ui/src/components/GlobalSearch/index.tsx @@ -300,7 +300,7 @@ export function GlobalSearch({ >