diff --git a/babel.config.js b/babel.config.js index 6ba90e98..fa49ff86 100644 --- a/babel.config.js +++ b/babel.config.js @@ -20,6 +20,7 @@ module.exports = { alias: { // This needs to be mirrored in tsconfig.json lib: './src/lib', + platform: './src/platform', state: './src/state', view: './src/view', }, diff --git a/src/view/com/util/ViewHeader.web.tsx b/src/view/com/util/ViewHeader.web.tsx index 5c0869e8..ef70ecab 100644 --- a/src/view/com/util/ViewHeader.web.tsx +++ b/src/view/com/util/ViewHeader.web.tsx @@ -1,69 +1,51 @@ import React from 'react' import {observer} from 'mobx-react-lite' import {StyleSheet, TouchableOpacity, View} from 'react-native' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {CenteredView} from './Views' import {Text} from './text/Text' -import {useStores} from 'state/index' +import {Link} from './Link' import {usePalette} from 'lib/hooks/usePalette' +import {useStores} from 'state/index' +import {ComposeIcon, MagnifyingGlassIcon} from 'lib/icons' import {colors} from 'lib/styles' -const BACK_HITSLOP = {left: 10, top: 10, right: 30, bottom: 10} - export const ViewHeader = observer(function ViewHeader({ title, - subtitle, - canGoBack, }: { title: string - subtitle?: string canGoBack?: boolean }) { - const pal = usePalette('default') const store = useStores() - const onPressBack = () => { - store.nav.tab.goBack() - } - if (typeof canGoBack === 'undefined') { - canGoBack = store.nav.tab.canGoBack - } + const pal = usePalette('default') + const onPressCompose = () => store.shell.openComposer({}) return ( - - {canGoBack ? ( - <> - - - - - - {title} - - {subtitle ? ( - - {subtitle} - - ) : undefined} - - - ) : ( - - - Home - + + + + {title} + + + + + - )} - + + New Post + + + + + + Search + + + ) }) @@ -71,44 +53,52 @@ const styles = StyleSheet.create({ header: { flexDirection: 'row', alignItems: 'center', - paddingHorizontal: 16, - paddingVertical: 12, + paddingTop: 24, + paddingBottom: 18, + paddingLeft: 30, + paddingRight: 40, + marginLeft: 300, + borderBottomWidth: 1, }, titleContainer: { - flexDirection: 'row', - alignItems: 'baseline', marginRight: 'auto', }, title: { fontWeight: 'bold', }, - subtitle: { - marginLeft: 4, - maxWidth: 200, - fontWeight: 'normal', + + search: { + flexDirection: 'row', + alignItems: 'center', + width: 300, + borderRadius: 20, + paddingVertical: 8, + paddingHorizontal: 10, + borderWidth: 1, + }, + searchIconWrapper: { + flexDirection: 'row', + width: 30, + justifyContent: 'center', + marginRight: 2, }, - backBtn: { - width: 30, - }, - backIcon: { - position: 'relative', - top: -1, - }, - btn: { + newPostBtn: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', - width: 36, - height: 36, - borderRadius: 20, - marginLeft: 4, + borderRadius: 24, + paddingTop: 8, + paddingBottom: 9, + paddingHorizontal: 18, + backgroundColor: colors.blue3, + marginRight: 10, }, - littleXIcon: { - color: colors.red3, - position: 'absolute', - right: 7, - bottom: 7, + newPostBtnIconWrapper: { + marginRight: 8, + }, + newPostBtnLabel: { + color: colors.white, }, }) diff --git a/src/view/com/util/Views.web.tsx b/src/view/com/util/Views.web.tsx index f00d3c07..3d9abd89 100644 --- a/src/view/com/util/Views.web.tsx +++ b/src/view/com/util/Views.web.tsx @@ -22,7 +22,6 @@ import { View, ViewProps, } from 'react-native' -import {useTheme} from 'lib/ThemeContext' import {addStyle, colors} from 'lib/styles' export function CenteredView({ @@ -40,15 +39,10 @@ export const FlatList = React.forwardRef(function ( }: React.PropsWithChildren>, ref: React.Ref, ) { - const theme = useTheme() contentContainerStyle = addStyle( contentContainerStyle, styles.containerScroll, ) - contentContainerStyle = addStyle( - contentContainerStyle, - theme.colorScheme === 'dark' ? styles.containerDark : styles.containerLight, - ) return ( ( export const ScrollView = React.forwardRef(function ( {contentContainerStyle, ...props}: React.PropsWithChildren, - ref: React.Ref, + ref: React.Ref, ) { - const theme = useTheme() contentContainerStyle = addStyle( contentContainerStyle, styles.containerScroll, ) - contentContainerStyle = addStyle( - contentContainerStyle, - theme.colorScheme === 'dark' ? styles.containerDark : styles.containerLight, - ) return ( + {isWeb && } - + {!isWeb && } {store.me.mainFeed.hasNewLatest && !store.me.mainFeed.isRefreshing && ( )} diff --git a/src/view/screens/Search.web.tsx b/src/view/screens/Search.web.tsx new file mode 100644 index 00000000..38f7cefb --- /dev/null +++ b/src/view/screens/Search.web.tsx @@ -0,0 +1,217 @@ +import React from 'react' +import { + Keyboard, + StyleSheet, + TextInput, + TouchableOpacity, + View, +} from 'react-native' +import {ScrollView} from '../com/util/Views' +import {observer} from 'mobx-react-lite' +import {UserAvatar} from '../com/util/UserAvatar' +import {Text} from '../com/util/text/Text' +import {ScreenParams} from '../routes' +import {useStores} from 'state/index' +import {UserAutocompleteViewModel} from 'state/models/user-autocomplete-view' +import {s} from 'lib/styles' +import {MagnifyingGlassIcon} from 'lib/icons' +import {ViewHeader} from '../com/util/ViewHeader' +import {WhoToFollow} from '../com/discover/WhoToFollow' +import {SuggestedPosts} from '../com/discover/SuggestedPosts' +import {ProfileCard} from '../com/profile/ProfileCard' +import {usePalette} from 'lib/hooks/usePalette' +import {useOnMainScroll} from 'lib/hooks/useOnMainScroll' +import {useAnalytics} from 'lib/analytics' + +const MENU_HITSLOP = {left: 10, top: 10, right: 30, bottom: 10} +const FIVE_MIN = 5 * 60 * 1e3 + +export const Search = observer(({navIdx, visible, params}: ScreenParams) => { + const pal = usePalette('default') + const store = useStores() + const {track} = useAnalytics() + const scrollElRef = React.useRef(null) + const onMainScroll = useOnMainScroll(store) + const textInput = React.useRef(null) + const [lastRenderTime, setRenderTime] = React.useState(Date.now()) // used to trigger reloads + const [isInputFocused, setIsInputFocused] = React.useState(false) + const [query, setQuery] = React.useState('') + const autocompleteView = React.useMemo( + () => new UserAutocompleteViewModel(store), + [store], + ) + const {name} = params + + const onSoftReset = () => { + scrollElRef.current?.scrollTo({x: 0, y: 0}) + } + + React.useEffect(() => { + const softResetSub = store.onScreenSoftReset(onSoftReset) + const cleanup = () => { + softResetSub.remove() + } + + if (visible) { + const now = Date.now() + if (now - lastRenderTime > FIVE_MIN) { + setRenderTime(Date.now()) // trigger reload of suggestions + } + store.shell.setMinimalShellMode(false) + autocompleteView.setup() + store.nav.setTitle(navIdx, 'Search') + } + return cleanup + }, [store, visible, name, navIdx, autocompleteView, lastRenderTime]) + + const onPressMenu = () => { + track('ViewHeader:MenuButtonClicked') + store.shell.setMainMenuOpen(true) + } + + const onChangeQuery = (text: string) => { + setQuery(text) + if (text.length > 0) { + autocompleteView.setActive(true) + autocompleteView.setPrefix(text) + } else { + autocompleteView.setActive(false) + } + } + const onPressCancelSearch = () => { + setQuery('') + autocompleteView.setActive(false) + } + + return ( + + + + + + + + + + setIsInputFocused(true)} + onBlur={() => setIsInputFocused(false)} + onChangeText={onChangeQuery} + /> + + {query ? ( + + + Cancel + + + ) : undefined} + + {query && autocompleteView.searchRes.length ? ( + <> + {autocompleteView.searchRes.map(item => ( + + ))} + + ) : query && !autocompleteView.searchRes.length ? ( + + + No results found for {autocompleteView.prefix} + + + ) : isInputFocused ? ( + + + Search for users on the network + + + ) : ( + + + + + + )} + + + + ) +}) + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + + header: { + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: 12, + paddingTop: 4, + marginBottom: 14, + }, + headerMenuBtn: { + width: 40, + height: 30, + marginLeft: 6, + }, + headerSearchContainer: { + flex: 1, + flexDirection: 'row', + alignItems: 'center', + borderRadius: 30, + paddingHorizontal: 12, + paddingVertical: 8, + }, + headerSearchIcon: { + marginRight: 6, + alignSelf: 'center', + }, + headerSearchInput: { + flex: 1, + fontSize: 17, + }, + headerCancelBtn: { + width: 60, + paddingLeft: 10, + }, + + searchPrompt: { + textAlign: 'center', + paddingTop: 10, + }, +}) diff --git a/src/view/shell/web/DesktopLeftColumn.tsx b/src/view/shell/web/DesktopLeftColumn.tsx index 819bcba6..54e3e93e 100644 --- a/src/view/shell/web/DesktopLeftColumn.tsx +++ b/src/view/shell/web/DesktopLeftColumn.tsx @@ -70,12 +70,7 @@ export const DesktopLeftColumn = observer(() => { styles.containerBgLight, styles.containerBgDark, ) - const hoverBg = useColorSchemeStyle( - styles.navItemHoverBgLight, - styles.navItemHoverBgDark, - ) const pal = usePalette('default') - const onPressCompose = () => store.shell.openComposer({}) const avi = ( { Bluesky - - - - Search - - { icon={} iconFilled={} /> - - [ - // @ts-ignore Pressable state differs for RNW -prf - state.hovered && hoverBg, - ]}> - - - - - New Post - - { - const store = useStores() return ( @@ -21,6 +19,7 @@ const styles = StyleSheet.create({ container: { position: 'absolute', right: 0, + top: 90, width: '400px', paddingHorizontal: 16, paddingRight: 32, diff --git a/tsconfig.json b/tsconfig.json index 2b93dd0f..cace91f5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,6 +3,7 @@ "compilerOptions": { "paths": { "lib/*": ["./src/lib/*"], + "platform/*": ["./src/platform/*"], "state/*": ["./src/state/*"], "view/*": ["./src/view/*"] }