diff options
Diffstat (limited to 'src/view/shell')
-rw-r--r-- | src/view/shell/Composer.web.tsx | 34 | ||||
-rw-r--r-- | src/view/shell/Drawer.tsx | 11 | ||||
-rw-r--r-- | src/view/shell/bottom-bar/BottomBarStyles.tsx | 4 | ||||
-rw-r--r-- | src/view/shell/bottom-bar/BottomBarWeb.tsx | 1 | ||||
-rw-r--r-- | src/view/shell/createNativeStackNavigatorWithAuth.tsx | 3 | ||||
-rw-r--r-- | src/view/shell/desktop/Feeds.tsx | 4 | ||||
-rw-r--r-- | src/view/shell/desktop/LeftNav.tsx | 46 | ||||
-rw-r--r-- | src/view/shell/desktop/RightNav.tsx | 37 | ||||
-rw-r--r-- | src/view/shell/desktop/Search.tsx | 104 | ||||
-rw-r--r-- | src/view/shell/index.tsx | 2 | ||||
-rw-r--r-- | src/view/shell/index.web.tsx | 24 |
11 files changed, 198 insertions, 72 deletions
diff --git a/src/view/shell/Composer.web.tsx b/src/view/shell/Composer.web.tsx index 73f9f540e..99e659d62 100644 --- a/src/view/shell/Composer.web.tsx +++ b/src/view/shell/Composer.web.tsx @@ -5,6 +5,11 @@ import {ComposePost} from '../com/composer/Composer' import {useComposerState} from 'state/shell/composer' import {usePalette} from 'lib/hooks/usePalette' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' +import {useWebBodyScrollLock} from '#/lib/hooks/useWebBodyScrollLock' +import { + EmojiPicker, + EmojiPickerState, +} from 'view/com/composer/text-input/web/EmojiPicker.web.tsx' const BOTTOM_BAR_HEIGHT = 61 @@ -12,11 +17,33 @@ export function Composer({}: {winHeight: number}) { const pal = usePalette('default') const {isMobile} = useWebMediaQueries() const state = useComposerState() + const isActive = !!state + useWebBodyScrollLock(isActive) + + const [pickerState, setPickerState] = React.useState<EmojiPickerState>({ + isOpen: false, + pos: {top: 0, left: 0, right: 0, bottom: 0}, + }) + + const onOpenPicker = React.useCallback((pos: DOMRect | undefined) => { + if (!pos) return + setPickerState({ + isOpen: true, + pos, + }) + }, []) + + const onClosePicker = React.useCallback(() => { + setPickerState(prev => ({ + ...prev, + isOpen: false, + })) + }, []) // rendering // = - if (!state) { + if (!isActive) { return <View /> } @@ -41,15 +68,18 @@ export function Composer({}: {winHeight: number}) { quote={state.quote} onPost={state.onPost} mention={state.mention} + openPicker={onOpenPicker} /> </Animated.View> + <EmojiPicker state={pickerState} close={onClosePicker} /> </Animated.View> ) } const styles = StyleSheet.create({ mask: { - position: 'absolute', + // @ts-ignore + position: 'fixed', top: 0, left: 0, width: '100%', diff --git a/src/view/shell/Drawer.tsx b/src/view/shell/Drawer.tsx index 14bc6af26..c30874c2f 100644 --- a/src/view/shell/Drawer.tsx +++ b/src/view/shell/Drawer.tsx @@ -53,6 +53,8 @@ import {useInviteCodesQuery} from '#/state/queries/invites' import {NavSignupCard} from '#/view/shell/NavSignupCard' import {TextLink} from '../com/util/Link' +import {useTheme as useAlfTheme} from '#/alf' + let DrawerProfileCard = ({ account, onPressProfile, @@ -68,7 +70,7 @@ let DrawerProfileCard = ({ <TouchableOpacity testID="profileCardButton" accessibilityLabel={_(msg`Profile`)} - accessibilityHint="Navigates to your profile" + accessibilityHint={_(msg`Navigates to your profile`)} onPress={onPressProfile}> <UserAvatar size={80} @@ -106,6 +108,7 @@ export {DrawerProfileCard} let DrawerContent = ({}: {}): React.ReactNode => { const theme = useTheme() + const t = useAlfTheme() const pal = usePalette('default') const {_} = useLingui() const setDrawerOpen = useSetDrawerOpen() @@ -208,7 +211,7 @@ let DrawerContent = ({}: {}): React.ReactNode => { testID="drawer" style={[ styles.view, - theme.colorScheme === 'light' ? pal.view : styles.viewDarkMode, + theme.colorScheme === 'light' ? pal.view : t.atoms.bg_contrast_25, ]}> <SafeAreaView style={s.flex1}> <ScrollView style={styles.main}> @@ -435,7 +438,9 @@ let NotificationsMenuItem = ({ label={_(msg`Notifications`)} accessibilityLabel={_(msg`Notifications`)} accessibilityHint={ - numUnreadNotifications === '' ? '' : `${numUnreadNotifications} unread` + numUnreadNotifications === '' + ? '' + : _(msg`${numUnreadNotifications} unread`) } count={numUnreadNotifications} bold={isActive} diff --git a/src/view/shell/bottom-bar/BottomBarStyles.tsx b/src/view/shell/bottom-bar/BottomBarStyles.tsx index ae9381440..f226406f5 100644 --- a/src/view/shell/bottom-bar/BottomBarStyles.tsx +++ b/src/view/shell/bottom-bar/BottomBarStyles.tsx @@ -12,6 +12,10 @@ export const styles = StyleSheet.create({ paddingLeft: 5, paddingRight: 10, }, + bottomBarWeb: { + // @ts-ignore web-only + position: 'fixed', + }, ctrl: { flex: 1, paddingTop: 13, diff --git a/src/view/shell/bottom-bar/BottomBarWeb.tsx b/src/view/shell/bottom-bar/BottomBarWeb.tsx index c5dc376b7..b330c4b80 100644 --- a/src/view/shell/bottom-bar/BottomBarWeb.tsx +++ b/src/view/shell/bottom-bar/BottomBarWeb.tsx @@ -57,6 +57,7 @@ export function BottomBarWeb() { <Animated.View style={[ styles.bottomBar, + styles.bottomBarWeb, pal.view, pal.border, {paddingBottom: clamp(safeAreaInsets.bottom, 15, 30)}, diff --git a/src/view/shell/createNativeStackNavigatorWithAuth.tsx b/src/view/shell/createNativeStackNavigatorWithAuth.tsx index 43dc28159..9fea6e49f 100644 --- a/src/view/shell/createNativeStackNavigatorWithAuth.tsx +++ b/src/view/shell/createNativeStackNavigatorWithAuth.tsx @@ -124,6 +124,7 @@ function NativeStackNavigator({ }, } } + return ( <NavigationContent> <NativeStackView @@ -136,7 +137,7 @@ function NativeStackNavigator({ {isWeb && !isMobile && ( <> <DesktopLeftNav /> - <DesktopRightNav /> + <DesktopRightNav routeName={activeRoute.name} /> </> )} </NavigationContent> diff --git a/src/view/shell/desktop/Feeds.tsx b/src/view/shell/desktop/Feeds.tsx index 93b96e704..a8f5f1c66 100644 --- a/src/view/shell/desktop/Feeds.tsx +++ b/src/view/shell/desktop/Feeds.tsx @@ -21,7 +21,7 @@ export function DesktopFeeds() { }) return ( - <View style={[styles.container, pal.view, pal.border]}> + <View style={[styles.container, pal.view]}> <FeedItem href="/" title="Following" current={route.name === 'Home'} /> {feeds .filter(f => f.displayName !== 'Following') @@ -91,7 +91,5 @@ const styles = StyleSheet.create({ width: 300, paddingHorizontal: 12, paddingVertical: 18, - borderTopWidth: 1, - borderBottomWidth: 1, }, }) diff --git a/src/view/shell/desktop/LeftNav.tsx b/src/view/shell/desktop/LeftNav.tsx index f3c8c1d11..b27898828 100644 --- a/src/view/shell/desktop/LeftNav.tsx +++ b/src/view/shell/desktop/LeftNav.tsx @@ -239,24 +239,26 @@ function ComposeBtn() { return null } return ( - <TouchableOpacity - disabled={isFetchingHandle} - style={[styles.newPostBtn]} - onPress={onPressCompose} - accessibilityRole="button" - accessibilityLabel={_(msg`New post`)} - accessibilityHint=""> - <View style={styles.newPostBtnIconWrapper}> - <ComposeIcon2 - size={19} - strokeWidth={2} - style={styles.newPostBtnLabel} - /> - </View> - <Text type="button" style={styles.newPostBtnLabel}> - <Trans>New Post</Trans> - </Text> - </TouchableOpacity> + <View style={styles.newPostBtnContainer}> + <TouchableOpacity + disabled={isFetchingHandle} + style={styles.newPostBtn} + onPress={onPressCompose} + accessibilityRole="button" + accessibilityLabel={_(msg`New post`)} + accessibilityHint=""> + <View style={styles.newPostBtnIconWrapper}> + <ComposeIcon2 + size={19} + strokeWidth={2} + style={styles.newPostBtnLabel} + /> + </View> + <Text type="button" style={styles.newPostBtnLabel}> + <Trans context="action">New Post</Trans> + </Text> + </TouchableOpacity> + </View> ) } @@ -440,10 +442,11 @@ export function DesktopLeftNav() { const styles = StyleSheet.create({ leftNav: { - position: 'absolute', + // @ts-ignore web only + position: 'fixed', top: 10, // @ts-ignore web only - right: 'calc(50vw + 312px)', + left: 'calc(50vw - 300px - 220px - 20px)', width: 220, // @ts-ignore web only maxHeight: 'calc(100vh - 10px)', @@ -512,6 +515,9 @@ const styles = StyleSheet.create({ fontSize: 14, }, + newPostBtnContainer: { + flexDirection: 'row', + }, newPostBtn: { flexDirection: 'row', alignItems: 'center', diff --git a/src/view/shell/desktop/RightNav.tsx b/src/view/shell/desktop/RightNav.tsx index 8d9961a5f..328c527e4 100644 --- a/src/view/shell/desktop/RightNav.tsx +++ b/src/view/shell/desktop/RightNav.tsx @@ -16,7 +16,7 @@ import {Plural, Trans, msg, plural} from '@lingui/macro' import {useSession} from '#/state/session' import {useInviteCodesQuery} from '#/state/queries/invites' -export function DesktopRightNav() { +export function DesktopRightNav({routeName}: {routeName: string}) { const pal = usePalette('default') const palError = usePalette('error') const {_} = useLingui() @@ -30,12 +30,20 @@ export function DesktopRightNav() { return ( <View style={[styles.rightNav, pal.view]}> <View style={{paddingVertical: 20}}> - <DesktopSearch /> - - {hasSession && ( - <View style={{paddingTop: 18, marginBottom: 18}}> + {routeName === 'Search' ? ( + <View style={{marginBottom: 18}}> <DesktopFeeds /> </View> + ) : ( + <> + <DesktopSearch /> + + {hasSession && ( + <View style={[pal.border, styles.desktopFeedsContainer]}> + <DesktopFeeds /> + </View> + )} + </> )} <View @@ -48,7 +56,7 @@ export function DesktopRightNav() { {isSandbox ? ( <View style={[palError.view, styles.messageLine, s.p10]}> <Text type="md" style={[palError.text, s.bold]}> - SANDBOX. Posts and accounts are not permanent. + <Trans>SANDBOX. Posts and accounts are not permanent.</Trans> </Text> </View> ) : undefined} @@ -169,17 +177,18 @@ function InviteCodes() { const styles = StyleSheet.create({ rightNav: { - position: 'absolute', // @ts-ignore web only - left: 'calc(50vw + 320px)', - width: 304, + position: 'fixed', + // @ts-ignore web only + left: 'calc(50vw + 300px + 20px)', + width: 300, maxHeight: '100%', overflowY: 'auto', }, message: { paddingVertical: 18, - paddingHorizontal: 10, + paddingHorizontal: 12, }, messageLine: { marginBottom: 10, @@ -187,7 +196,7 @@ const styles = StyleSheet.create({ inviteCodes: { borderTopWidth: 1, - paddingHorizontal: 16, + paddingHorizontal: 12, paddingVertical: 12, flexDirection: 'row', }, @@ -196,4 +205,10 @@ const styles = StyleSheet.create({ marginRight: 6, flexShrink: 0, }, + desktopFeedsContainer: { + borderTopWidth: 1, + borderBottomWidth: 1, + marginTop: 18, + marginBottom: 18, + }, }) diff --git a/src/view/shell/desktop/Search.tsx b/src/view/shell/desktop/Search.tsx index 6201f828f..4a9483733 100644 --- a/src/view/shell/desktop/Search.tsx +++ b/src/view/shell/desktop/Search.tsx @@ -29,13 +29,63 @@ import {UserAvatar} from '#/view/com/util/UserAvatar' import {useActorAutocompleteFn} from '#/state/queries/actor-autocomplete' import {useModerationOpts} from '#/state/queries/preferences' -export function SearchResultCard({ - profile, +export const MATCH_HANDLE = + /@?([a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*(?:\.[a-zA-Z]{2,}))/ + +export function SearchLinkCard({ + label, + to, + onPress, style, +}: { + label: string + to?: string + onPress?: () => void + style?: ViewStyle +}) { + const pal = usePalette('default') + + const inner = ( + <View + style={[pal.border, {paddingVertical: 16, paddingHorizontal: 12}, style]}> + <Text type="md" style={[pal.text]}> + {label} + </Text> + </View> + ) + + if (onPress) { + return ( + <TouchableOpacity + onPress={onPress} + accessibilityLabel={label} + accessibilityHint=""> + {inner} + </TouchableOpacity> + ) + } + + return ( + <Link href={to} asAnchor anchorNoUnderline> + <View + style={[ + pal.border, + {paddingVertical: 16, paddingHorizontal: 12}, + style, + ]}> + <Text type="md" style={[pal.text]}> + {label} + </Text> + </View> + </Link> + ) +} + +export function SearchProfileCard({ + profile, moderation, }: { profile: AppBskyActorDefs.ProfileViewBasic - style: ViewStyle moderation: ProfileModeration }) { const pal = usePalette('default') @@ -50,9 +100,7 @@ export function SearchResultCard({ <View style={[ pal.border, - style, { - borderTopWidth: 1, flexDirection: 'row', alignItems: 'center', gap: 12, @@ -147,6 +195,11 @@ export function DesktopSearch() { navigation.dispatch(StackActions.push('Search', {q: query})) }, [query, navigation, setSearchResults]) + const queryMaybeHandle = React.useMemo(() => { + const match = MATCH_HANDLE.exec(query) + return match && match[1] + }, [query]) + return ( <View style={[styles.container, pal.view]}> <View @@ -169,6 +222,9 @@ export function DesktopSearch() { accessibilityRole="search" accessibilityLabel={_(msg`Search`)} accessibilityHint="" + autoCorrect={false} + autoComplete="off" + autoCapitalize="none" /> {query ? ( <View style={styles.cancelBtn}> @@ -176,7 +232,7 @@ export function DesktopSearch() { onPress={onPressCancelSearch} accessibilityRole="button" accessibilityLabel={_(msg`Cancel search`)} - accessibilityHint="Exits inputting search query" + accessibilityHint={_(msg`Exits inputting search query`)} onAccessibilityEscape={onPressCancelSearch}> <Text type="lg" style={[pal.link]}> <Trans>Cancel</Trans> @@ -195,22 +251,26 @@ export function DesktopSearch() { </View> ) : ( <> - {searchResults.length ? ( - searchResults.map((item, i) => ( - <SearchResultCard - key={item.did} - profile={item} - moderation={moderateProfile(item, moderationOpts)} - style={i === 0 ? {borderTopWidth: 0} : {}} - /> - )) - ) : ( - <View> - <Text style={[pal.textLight, styles.noResults]}> - <Trans>No results found for {query}</Trans> - </Text> - </View> - )} + <SearchLinkCard + label={_(msg`Search for "${query}"`)} + to={`/search?q=${encodeURIComponent(query)}`} + style={{borderBottomWidth: 1}} + /> + + {queryMaybeHandle ? ( + <SearchLinkCard + label={_(msg`Go to @${queryMaybeHandle}`)} + to={`/profile/${queryMaybeHandle}`} + /> + ) : null} + + {searchResults.map(item => ( + <SearchProfileCard + key={item.did} + profile={item} + moderation={moderateProfile(item, moderationOpts)} + /> + ))} </> )} </View> diff --git a/src/view/shell/index.tsx b/src/view/shell/index.tsx index 51c03ae3d..5320aebfc 100644 --- a/src/view/shell/index.tsx +++ b/src/view/shell/index.tsx @@ -28,6 +28,7 @@ import {isAndroid} from 'platform/detection' import {useSession} from '#/state/session' import {useCloseAnyActiveElement} from '#/state/util' import * as notifications from 'lib/notifications/notifications' +import {Outlet as PortalOutlet} from '#/components/Portal' function ShellInner() { const isDrawerOpen = useIsDrawerOpen() @@ -94,6 +95,7 @@ function ShellInner() { </View> <Composer winHeight={winDim.height} /> <ModalsContainer /> + <PortalOutlet /> <Lightbox /> </> ) diff --git a/src/view/shell/index.web.tsx b/src/view/shell/index.web.tsx index 38da860bd..76f4f5c9b 100644 --- a/src/view/shell/index.web.tsx +++ b/src/view/shell/index.web.tsx @@ -15,6 +15,8 @@ import {useAuxClick} from 'lib/hooks/useAuxClick' import {t} from '@lingui/macro' import {useIsDrawerOpen, useSetDrawerOpen} from '#/state/shell' import {useCloseAllActiveElements} from '#/state/util' +import {useWebBodyScrollLock} from '#/lib/hooks/useWebBodyScrollLock' +import {Outlet as PortalOutlet} from '#/components/Portal' function ShellInner() { const isDrawerOpen = useIsDrawerOpen() @@ -23,6 +25,7 @@ function ShellInner() { const navigator = useNavigation<NavigationProp>() const closeAllActiveElements = useCloseAllActiveElements() + useWebBodyScrollLock(isDrawerOpen) useAuxClick() useEffect(() => { @@ -33,27 +36,26 @@ function ShellInner() { }, [navigator, closeAllActiveElements]) return ( - <View style={[s.hContentRegion, {overflow: 'hidden'}]}> - <View style={s.hContentRegion}> - <ErrorBoundary> - <FlatNavigator /> - </ErrorBoundary> - </View> + <> + <ErrorBoundary> + <FlatNavigator /> + </ErrorBoundary> <Composer winHeight={0} /> <ModalsContainer /> + <PortalOutlet /> <Lightbox /> {!isDesktop && isDrawerOpen && ( <TouchableOpacity onPress={() => setDrawerOpen(false)} style={styles.drawerMask} accessibilityLabel={t`Close navigation footer`} - accessibilityHint="Closes bottom navigation bar"> + accessibilityHint={t`Closes bottom navigation bar`}> <View style={styles.drawerContainer}> <DrawerContent /> </View> </TouchableOpacity> )} - </View> + </> ) } @@ -76,7 +78,8 @@ const styles = StyleSheet.create({ backgroundColor: colors.black, // TODO }, drawerMask: { - position: 'absolute', + // @ts-ignore web only + position: 'fixed', width: '100%', height: '100%', top: 0, @@ -85,7 +88,8 @@ const styles = StyleSheet.create({ }, drawerContainer: { display: 'flex', - position: 'absolute', + // @ts-ignore web only + position: 'fixed', top: 0, left: 0, height: '100%', |