diff --git a/package.json b/package.json
index d607953f603..142830eb123 100644
--- a/package.json
+++ b/package.json
@@ -114,6 +114,7 @@
"@braze/react-native-sdk": "11.0.0",
"@expo/react-native-action-sheet": "4.0.1",
"@gorhom/bottom-sheet": "5.0.5",
+ "@gorhom/portal": "1.0.14",
"@invertase/react-native-apple-authentication": "2.1.5",
"@kesha-antonov/react-native-action-cable": "1.1.4",
"@ptomasroos/react-native-multi-slider": "2.2.2",
diff --git a/src/app/Components/GlobalSearchInput.tests.tsx b/src/app/Components/GlobalSearchInput.tests.tsx
new file mode 100644
index 00000000000..1c9580898b4
--- /dev/null
+++ b/src/app/Components/GlobalSearchInput.tests.tsx
@@ -0,0 +1,10 @@
+import { GlobalSearchInput } from "app/Components/GlobalSearchInput"
+import { renderWithWrappers } from "app/utils/tests/renderWithWrappers"
+
+describe("GlobalSearchInput", () => {
+ it("renders the search label properly", () => {
+ renderWithWrappers()
+
+ expect(/Search artists, artworks, etc/).toBeTruthy()
+ })
+})
diff --git a/src/app/Components/GlobalSearchInput.tsx b/src/app/Components/GlobalSearchInput.tsx
new file mode 100644
index 00000000000..48cc6bafc0e
--- /dev/null
+++ b/src/app/Components/GlobalSearchInput.tsx
@@ -0,0 +1,34 @@
+import { Flex, RoundSearchInput, Touchable } from "@artsy/palette-mobile"
+import { GlobalSearchInputOverlay } from "app/Components/GlobalSearchInputOverlay"
+import { SEARCH_INPUT_PLACEHOLDER } from "app/Scenes/Search/Search"
+import { Fragment, useState } from "react"
+
+export const GlobalSearchInput: React.FC<{}> = () => {
+ const [isVisible, setIsVisible] = useState(false)
+
+ return (
+
+ {
+ setIsVisible(true)
+ }}
+ >
+ {/* In order to make the search input behave like a button here, we wrapped it with a
+ Touchable and set pointerEvents to none. This will prevent the input from receiving
+ touch events and make sure they are being handled by the Touchable.
+ */}
+
+
+
+
+ setIsVisible(false)} />
+
+ )
+}
diff --git a/src/app/Components/GlobalSearchInputOverlay.tsx b/src/app/Components/GlobalSearchInputOverlay.tsx
new file mode 100644
index 00000000000..31eb0e9a0f5
--- /dev/null
+++ b/src/app/Components/GlobalSearchInputOverlay.tsx
@@ -0,0 +1,43 @@
+import { Flex, RoundSearchInput, Spacer } from "@artsy/palette-mobile"
+import { Portal } from "@gorhom/portal"
+import { FadeIn } from "app/Components/FadeIn"
+import { SEARCH_INPUT_PLACEHOLDER } from "app/Scenes/Search/Search"
+import { StyleSheet } from "react-native"
+import { SafeAreaView } from "react-native-safe-area-context"
+
+export const GlobalSearchInputOverlay: React.FC<{ visible: boolean; hideModal: () => void }> = ({
+ visible,
+ hideModal,
+}) => {
+ if (!visible) {
+ return null
+ }
+
+ return (
+
+
+
+
+ {
+ hideModal()
+ }}
+ />
+
+
+
+
+
+
+ )
+}
diff --git a/src/app/Providers.tsx b/src/app/Providers.tsx
index 7e91fb99067..30e94a972a0 100644
--- a/src/app/Providers.tsx
+++ b/src/app/Providers.tsx
@@ -1,5 +1,6 @@
import { Theme, Spinner, ScreenDimensionsProvider, Screen } from "@artsy/palette-mobile"
import { ActionSheetProvider } from "@expo/react-native-action-sheet"
+import { PortalProvider } from "@gorhom/portal"
import { ArtworkListsProvider } from "app/Components/ArtworkLists/ArtworkListsContext"
import { ShareSheetProvider } from "app/Components/ShareSheet/ShareSheetContext"
import { getRelayEnvironment } from "app/system/relay/defaultEnvironment"
@@ -58,6 +59,7 @@ export const TestProviders: React.FC<{ skipRelay?: boolean }> = ({
TrackingProvider,
GlobalStoreProvider,
SafeAreaProvider,
+ PortalProvider,
ProvideScreenDimensions,
// FIXME: Only use one from palette-mobile
// @ts-ignore
diff --git a/src/app/Scenes/HomeView/Components/HomeHeader.tsx b/src/app/Scenes/HomeView/Components/HomeHeader.tsx
index b2ff142a17c..5540162c339 100644
--- a/src/app/Scenes/HomeView/Components/HomeHeader.tsx
+++ b/src/app/Scenes/HomeView/Components/HomeHeader.tsx
@@ -1,4 +1,5 @@
-import { ArtsyLogoBlackIcon, Flex, Box } from "@artsy/palette-mobile"
+import { ArtsyLogoBlackIcon, Box, Flex } from "@artsy/palette-mobile"
+import { GlobalSearchInput } from "app/Components/GlobalSearchInput"
import { PaymentFailureBanner } from "app/Scenes/HomeView/Components/PaymentFailureBanner"
import { GlobalStore } from "app/store/GlobalStore"
import { useFeatureFlag } from "app/utils/hooks/useFeatureFlag"
@@ -6,11 +7,40 @@ import { Suspense } from "react"
import { ActivityIndicator } from "./ActivityIndicator"
export const HomeHeader: React.FC = () => {
+ const enableNewSearchModal = useFeatureFlag("AREnableNewSearchModal")
const showPaymentFailureBanner = useFeatureFlag("AREnablePaymentFailureBanner")
const hasUnseenNotifications = GlobalStore.useAppState(
(state) => state.bottomTabs.hasUnseenNotifications
)
+ if (enableNewSearchModal) {
+ return (
+
+ {!!showPaymentFailureBanner && (
+
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+ )
+ }
+
return (
<>
{!!showPaymentFailureBanner && (
@@ -18,7 +48,7 @@ export const HomeHeader: React.FC = () => {
)}
-
+
diff --git a/src/app/Scenes/HomeView/HomeView.tsx b/src/app/Scenes/HomeView/HomeView.tsx
index 1e13e78a4f7..c7756884143 100644
--- a/src/app/Scenes/HomeView/HomeView.tsx
+++ b/src/app/Scenes/HomeView/HomeView.tsx
@@ -1,5 +1,6 @@
import { ContextModule, OwnerType } from "@artsy/cohesion"
import { Flex, Screen, Spinner } from "@artsy/palette-mobile"
+import { PortalHost } from "@gorhom/portal"
import { useFocusEffect } from "@react-navigation/native"
import { HomeViewFetchMeQuery } from "__generated__/HomeViewFetchMeQuery.graphql"
import { HomeViewQuery } from "__generated__/HomeViewQuery.graphql"
@@ -18,6 +19,7 @@ import { searchQueryDefaultVariables } from "app/Scenes/Search/Search"
import { getRelayEnvironment } from "app/system/relay/defaultEnvironment"
import { useBottomTabsScrollToTop } from "app/utils/bottomTabsHelper"
import { extractNodes } from "app/utils/extractNodes"
+import { useFeatureFlag } from "app/utils/hooks/useFeatureFlag"
import { ProvidePlaceholderContext } from "app/utils/placeholders"
import { usePrefetch } from "app/utils/queryPrefetching"
import { requestPushNotificationsPermission } from "app/utils/requestPushNotificationsPermission"
@@ -33,6 +35,7 @@ export const homeViewScreenQueryVariables = () => ({
})
export const HomeView: React.FC = () => {
+ const enableNewSearchModal = useFeatureFlag("AREnableNewSearchModal")
const flashlistRef = useBottomTabsScrollToTop("home")
const [isRefreshing, setIsRefreshing] = useState(false)
@@ -128,10 +131,20 @@ export const HomeView: React.FC = () => {
})
}
+ const stickyHeaderProps = enableNewSearchModal
+ ? {
+ stickyHeaderHiddenOnScroll: true,
+ stickyHeaderIndices: [0],
+ }
+ : {}
+
return (
}
data={sections}
@@ -150,6 +163,7 @@ export const HomeView: React.FC = () => {
}
refreshControl={}
onEndReachedThreshold={2}
+ {...stickyHeaderProps}
/>
{!!data?.me && }
@@ -178,6 +192,7 @@ export const HomeViewScreen: React.FC = () => {
}>
+
)
diff --git a/src/app/Scenes/Search/Search.tests.tsx b/src/app/Scenes/Search/Search.tests.tsx
index 2aa1b3c387c..d6674d00ae5 100644
--- a/src/app/Scenes/Search/Search.tests.tsx
+++ b/src/app/Scenes/Search/Search.tests.tsx
@@ -17,7 +17,7 @@ describe("Search", () => {
it("should render a text input with placeholder and no pills", async () => {
const { env } = renderWithRelay()
- const searchInput = screen.getByPlaceholderText("Search artists, artworks, galleries, etc")
+ const searchInput = screen.getByPlaceholderText("Search artists, artworks, etc")
expect(searchInput).toBeTruthy()
@@ -43,7 +43,7 @@ describe("Search", () => {
it("Top pill should be selected by default", async () => {
const { env } = renderWithRelay()
- const searchInput = screen.getByPlaceholderText("Search artists, artworks, galleries, etc")
+ const searchInput = screen.getByPlaceholderText("Search artists, artworks, etc")
fireEvent.changeText(searchInput, "text")
@@ -56,7 +56,7 @@ describe("Search", () => {
it("when clear button is pressed", async () => {
const { env } = renderWithRelay()
- const searchInput = screen.getByPlaceholderText("Search artists, artworks, galleries, etc")
+ const searchInput = screen.getByPlaceholderText("Search artists, artworks, etc")
fireEvent(searchInput, "changeText", "prev value")
@@ -77,7 +77,7 @@ describe("Search", () => {
it("when cancel button is pressed", async () => {
const { env } = renderWithRelay()
- const searchInput = screen.getByPlaceholderText("Search artists, artworks, galleries, etc")
+ const searchInput = screen.getByPlaceholderText("Search artists, artworks, etc")
fireEvent(searchInput, "changeText", "prev value")
// needed to resolve the relay operation triggered for the text change
@@ -98,7 +98,7 @@ describe("Search", () => {
it("should render all the default pills", async () => {
const { env } = renderWithRelay()
- const searchInput = screen.getByPlaceholderText("Search artists, artworks, galleries, etc")
+ const searchInput = screen.getByPlaceholderText("Search artists, artworks, etc")
fireEvent(searchInput, "changeText", "Ba")
diff --git a/src/app/Scenes/Search/Search.tsx b/src/app/Scenes/Search/Search.tsx
index 7cfc51b49a5..7e4d1064b25 100644
--- a/src/app/Scenes/Search/Search.tsx
+++ b/src/app/Scenes/Search/Search.tsx
@@ -1,14 +1,17 @@
import { ActionType, ContextModule, OwnerType } from "@artsy/cohesion"
import { Spacer, Flex, Box, Screen } from "@artsy/palette-mobile"
+import { PortalHost } from "@gorhom/portal"
import { useNavigation } from "@react-navigation/native"
import { StackScreenProps } from "@react-navigation/stack"
import { SearchQuery, SearchQuery$variables } from "__generated__/SearchQuery.graphql"
+import { GlobalSearchInput } from "app/Components/GlobalSearchInput"
import { SearchInput } from "app/Components/SearchInput"
import { SearchPills } from "app/Scenes/Search/SearchPills"
import { useRefetchWhenQueryChanged } from "app/Scenes/Search/useRefetchWhenQueryChanged"
import { useSearchQuery } from "app/Scenes/Search/useSearchQuery"
import { ArtsyKeyboardAvoidingView } from "app/utils/ArtsyKeyboardAvoidingView"
import { useBottomTabsScrollToTop } from "app/utils/bottomTabsHelper"
+import { useFeatureFlag } from "app/utils/hooks/useFeatureFlag"
import { Schema } from "app/utils/track"
import { throttle } from "lodash"
import { Suspense, useEffect, useMemo, useRef, useState } from "react"
@@ -28,8 +31,7 @@ import { SEARCH_PILLS, SEARCH_THROTTLE_INTERVAL, TOP_PILL } from "./constants"
import { getContextModuleByPillName } from "./helpers"
import { PillType } from "./types"
-const SEARCH_INPUT_PLACEHOLDER = [
- "Search artists, artworks, galleries, etc",
+export const SEARCH_INPUT_PLACEHOLDER = [
"Search artists, artworks, etc",
"Search artworks, etc",
"Search",
@@ -41,6 +43,7 @@ export const searchQueryDefaultVariables: SearchQuery$variables = {
}
export const Search: React.FC = () => {
+ const enableNewSearchModal = useFeatureFlag("AREnableNewSearchModal")
const searchPillsRef = useRef(null)
const [searchQuery, setSearchQuery] = useState("")
const [selectedPill, setSelectedPill] = useState(TOP_PILL)
@@ -130,11 +133,15 @@ export const Search: React.FC = () => {
-
+ {enableNewSearchModal ? (
+
+ ) : (
+
+ )}
{shouldStartSearching(searchQuery) && !!queryData.viewer ? (
@@ -158,11 +165,15 @@ export const Search: React.FC = () => {
>
) : (
-
-
-
+ {!enableNewSearchModal && (
+ <>
+
+
+
+
+ >
+ )}
-
@@ -189,13 +200,18 @@ export const SearchScreenQuery = graphql`
type SearchScreenProps = StackScreenProps
-export const SearchScreen: React.FC = () => (
-
- }>
-
-
-
-)
+export const SearchScreen: React.FC = () => {
+ return (
+ <>
+
+ }>
+
+
+
+
+ >
+ )
+}
const Scrollable = styled(ScrollView).attrs(() => ({
keyboardDismissMode: "on-drag",
diff --git a/src/app/Scenes/Search/components/placeholders/SearchPlaceholder.tsx b/src/app/Scenes/Search/components/placeholders/SearchPlaceholder.tsx
index ccbe99e687e..3b55bdb45af 100644
--- a/src/app/Scenes/Search/components/placeholders/SearchPlaceholder.tsx
+++ b/src/app/Scenes/Search/components/placeholders/SearchPlaceholder.tsx
@@ -1,7 +1,15 @@
-import { Spacer, Flex, Box, Join } from "@artsy/palette-mobile"
+import {
+ Box,
+ Flex,
+ Join,
+ SEARCH_INPUT_CONTAINER_BORDER_RADIUS,
+ SEARCH_INPUT_CONTAINER_HEIGHT,
+ Spacer,
+} from "@artsy/palette-mobile"
import { CARD_WIDTH } from "app/Components/Home/CardRailCard"
import { MAX_SHOWN_RECENT_SEARCHES, useRecentSearches } from "app/Scenes/Search/SearchModel"
import { IMAGE_SIZE } from "app/Scenes/Search/components/SearchResultImage"
+import { useFeatureFlag } from "app/utils/hooks/useFeatureFlag"
import {
PlaceholderBox,
PlaceholderRaggedText,
@@ -110,15 +118,28 @@ const CuratedCollectionsPlaceholder = () => {
}
export const SearchPlaceholder: React.FC = () => {
+ const enableNewSearchModal = useFeatureFlag("AREnableNewSearchModal")
return (
{/* Search input */}
-
+ {enableNewSearchModal ? (
+
+ ) : (
+
+ )}
+
-
-
+ {!enableNewSearchModal && (
+ <>
+
+
+ >
+ )}
diff --git a/src/app/store/config/features.ts b/src/app/store/config/features.ts
index d2401873721..88a0b9aee92 100644
--- a/src/app/store/config/features.ts
+++ b/src/app/store/config/features.ts
@@ -306,6 +306,11 @@ export const features = {
showInDevMenu: true,
echoFlagKey: "AREnablePaymentFailureBanner",
},
+ AREnableNewSearchModal: {
+ description: "Enable new search modal",
+ readyForRelease: false,
+ showInDevMenu: true,
+ },
} satisfies { [key: string]: FeatureDescriptor }
export interface DevToggleDescriptor {