diff options
Diffstat (limited to 'src/view')
-rw-r--r-- | src/view/com/auth/create/Step1.tsx | 27 | ||||
-rw-r--r-- | src/view/com/home/HomeHeaderLayout.web.tsx | 19 | ||||
-rw-r--r-- | src/view/com/modals/Modal.tsx | 4 | ||||
-rw-r--r-- | src/view/com/modals/Modal.web.tsx | 3 | ||||
-rw-r--r-- | src/view/com/modals/Waitlist.tsx | 190 | ||||
-rw-r--r-- | src/view/com/notifications/FeedItem.tsx | 1 | ||||
-rw-r--r-- | src/view/com/posts/Feed.tsx | 4 | ||||
-rw-r--r-- | src/view/com/util/EventStopper.tsx | 11 | ||||
-rw-r--r-- | src/view/com/util/Views.d.ts | 4 | ||||
-rw-r--r-- | src/view/com/util/Views.web.tsx | 11 | ||||
-rw-r--r-- | src/view/com/util/forms/DateInput.tsx | 32 | ||||
-rw-r--r-- | src/view/com/util/forms/PostDropdownBtn.tsx | 376 | ||||
-rw-r--r-- | src/view/com/util/forms/SelectableBtn.tsx | 1 | ||||
-rw-r--r-- | src/view/com/util/post-ctrls/PostCtrls.tsx | 5 | ||||
-rw-r--r-- | src/view/screens/Storybook/Menus.tsx | 79 | ||||
-rw-r--r-- | src/view/screens/Storybook/index.tsx | 2 | ||||
-rw-r--r-- | src/view/shell/desktop/LeftNav.tsx | 2 | ||||
-rw-r--r-- | src/view/shell/index.tsx | 9 |
18 files changed, 360 insertions, 420 deletions
diff --git a/src/view/com/auth/create/Step1.tsx b/src/view/com/auth/create/Step1.tsx index 4c7018485..1f6852f8c 100644 --- a/src/view/com/auth/create/Step1.tsx +++ b/src/view/com/auth/create/Step1.tsx @@ -4,7 +4,6 @@ import { Keyboard, StyleSheet, TouchableOpacity, - TouchableWithoutFeedback, View, } from 'react-native' import {CreateAccountState, CreateAccountDispatch, is18} from './state' @@ -19,7 +18,6 @@ import {ErrorMessage} from 'view/com/util/error/ErrorMessage' import {isWeb} from 'platform/detection' import {Trans, msg} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {useModalControls} from '#/state/modals' import {logger} from '#/logger' import { FontAwesomeIcon, @@ -49,7 +47,6 @@ export function Step1({ }) { const pal = usePalette('default') const {_} = useLingui() - const {openModal} = useModalControls() const serverInputControl = useDialogControl() const onPressSelectService = React.useCallback(() => { @@ -57,10 +54,6 @@ export function Step1({ Keyboard.dismiss() }, [serverInputControl]) - const onPressWaitlist = React.useCallback(() => { - openModal({name: 'waitlist'}) - }, [openModal]) - const birthDate = React.useMemo(() => { return sanitizeDate(uiState.birthDate) }, [uiState.birthDate]) @@ -164,23 +157,7 @@ export function Step1({ </View> )} - {!uiState.inviteCode && uiState.isInviteCodeRequired ? ( - <View style={[s.flexRow, s.alignCenter]}> - <Text style={pal.text}> - <Trans>Don't have an invite code?</Trans>{' '} - </Text> - <TouchableWithoutFeedback - onPress={onPressWaitlist} - accessibilityLabel={_(msg`Join the waitlist.`)} - accessibilityHint=""> - <View style={styles.touchable}> - <Text style={pal.link}> - <Trans>Join the waitlist.</Trans> - </Text> - </View> - </TouchableWithoutFeedback> - </View> - ) : ( + {!uiState.isInviteCodeRequired || uiState.inviteCode ? ( <> <View style={s.pb20}> <Text @@ -260,7 +237,7 @@ export function Step1({ /> )} </> - )} + ) : undefined} </> )} </View> diff --git a/src/view/com/home/HomeHeaderLayout.web.tsx b/src/view/com/home/HomeHeaderLayout.web.tsx index 6145081af..9818b56f6 100644 --- a/src/view/com/home/HomeHeaderLayout.web.tsx +++ b/src/view/com/home/HomeHeaderLayout.web.tsx @@ -1,5 +1,6 @@ import React from 'react' import {StyleSheet, View} from 'react-native' +import Animated from 'react-native-reanimated' import {usePalette} from 'lib/hooks/usePalette' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {HomeHeaderLayoutMobile} from './HomeHeaderLayoutMobile' @@ -12,6 +13,8 @@ import { import {useLingui} from '@lingui/react' import {msg} from '@lingui/macro' import {CogIcon} from '#/lib/icons' +import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode' +import {useShellLayout} from '#/state/shell/shell-layout' export function HomeHeaderLayout(props: { children: React.ReactNode @@ -33,6 +36,8 @@ function HomeHeaderLayoutDesktopAndTablet({ tabBarAnchor: JSX.Element | null | undefined }) { const pal = usePalette('default') + const {headerMinimalShellTransform} = useMinimalShellMode() + const {headerHeight} = useShellLayout() const {_} = useLingui() return ( @@ -60,9 +65,19 @@ function HomeHeaderLayoutDesktopAndTablet({ </Link> </View> {tabBarAnchor} - <View style={[pal.view, pal.border, styles.bar, styles.tabBar]}> + <Animated.View + onLayout={e => { + headerHeight.value = e.nativeEvent.layout.height + }} + style={[ + pal.view, + pal.border, + styles.bar, + styles.tabBar, + headerMinimalShellTransform, + ]}> {children} - </View> + </Animated.View> </> ) } diff --git a/src/view/com/modals/Modal.tsx b/src/view/com/modals/Modal.tsx index 8da91c75c..100444ff5 100644 --- a/src/view/com/modals/Modal.tsx +++ b/src/view/com/modals/Modal.tsx @@ -20,7 +20,6 @@ import * as ReportModal from './report/Modal' import * as AppealLabelModal from './AppealLabel' import * as DeleteAccountModal from './DeleteAccount' import * as ChangeHandleModal from './ChangeHandle' -import * as WaitlistModal from './Waitlist' import * as InviteCodesModal from './InviteCodes' import * as AddAppPassword from './AddAppPasswords' import * as ContentFilteringSettingsModal from './ContentFilteringSettings' @@ -109,9 +108,6 @@ export function ModalsContainer() { } else if (activeModal?.name === 'change-handle') { snapPoints = ChangeHandleModal.snapPoints element = <ChangeHandleModal.Component {...activeModal} /> - } else if (activeModal?.name === 'waitlist') { - snapPoints = WaitlistModal.snapPoints - element = <WaitlistModal.Component /> } else if (activeModal?.name === 'invite-codes') { snapPoints = InviteCodesModal.snapPoints element = <InviteCodesModal.Component /> diff --git a/src/view/com/modals/Modal.web.tsx b/src/view/com/modals/Modal.web.tsx index 97a60be91..0ced894a1 100644 --- a/src/view/com/modals/Modal.web.tsx +++ b/src/view/com/modals/Modal.web.tsx @@ -22,7 +22,6 @@ import * as CropImageModal from './crop-image/CropImage.web' import * as AltTextImageModal from './AltImage' import * as EditImageModal from './EditImage' import * as ChangeHandleModal from './ChangeHandle' -import * as WaitlistModal from './Waitlist' import * as InviteCodesModal from './InviteCodes' import * as AddAppPassword from './AddAppPasswords' import * as ContentFilteringSettingsModal from './ContentFilteringSettings' @@ -105,8 +104,6 @@ function Modal({modal}: {modal: ModalIface}) { element = <ThreadgateModal.Component {...modal} /> } else if (modal.name === 'change-handle') { element = <ChangeHandleModal.Component {...modal} /> - } else if (modal.name === 'waitlist') { - element = <WaitlistModal.Component /> } else if (modal.name === 'invite-codes') { element = <InviteCodesModal.Component /> } else if (modal.name === 'add-app-password') { diff --git a/src/view/com/modals/Waitlist.tsx b/src/view/com/modals/Waitlist.tsx deleted file mode 100644 index 263dd27a2..000000000 --- a/src/view/com/modals/Waitlist.tsx +++ /dev/null @@ -1,190 +0,0 @@ -import React from 'react' -import { - ActivityIndicator, - StyleSheet, - TouchableOpacity, - View, -} from 'react-native' -import {TextInput} from './util' -import { - FontAwesomeIcon, - FontAwesomeIconStyle, -} from '@fortawesome/react-native-fontawesome' -import LinearGradient from 'react-native-linear-gradient' -import {Text} from '../util/text/Text' -import {s, gradients} from 'lib/styles' -import {usePalette} from 'lib/hooks/usePalette' -import {useTheme} from 'lib/ThemeContext' -import {ErrorMessage} from '../util/error/ErrorMessage' -import {cleanError} from 'lib/strings/errors' -import {Trans, msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import {useModalControls} from '#/state/modals' - -export const snapPoints = ['80%'] - -export function Component({}: {}) { - const pal = usePalette('default') - const theme = useTheme() - const {_} = useLingui() - const {closeModal} = useModalControls() - const [email, setEmail] = React.useState<string>('') - const [isEmailSent, setIsEmailSent] = React.useState<boolean>(false) - const [isProcessing, setIsProcessing] = React.useState<boolean>(false) - const [error, setError] = React.useState<string>('') - - const onPressSignup = async () => { - setError('') - setIsProcessing(true) - try { - const res = await fetch('https://bsky.app/api/waitlist', { - method: 'POST', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify({email}), - }) - const resBody = await res.json() - if (resBody.success) { - setIsEmailSent(true) - } else { - setError( - resBody.error || - _(msg`Something went wrong. Check your email and try again.`), - ) - } - } catch (e: any) { - setError(cleanError(e)) - } - setIsProcessing(false) - } - const onCancel = () => { - closeModal() - } - - return ( - <View style={[styles.container, pal.view]}> - <View style={[styles.innerContainer, pal.view]}> - <Text type="title-xl" style={[styles.title, pal.text]}> - <Trans>Join the waitlist</Trans> - </Text> - <Text type="lg" style={[styles.description, pal.text]}> - <Trans> - Bluesky uses invites to build a healthier community. If you don't - know anybody with an invite, you can sign up for the waitlist and - we'll send one soon. - </Trans> - </Text> - <TextInput - style={[styles.textInput, pal.borderDark, pal.text, s.mb10, s.mt10]} - placeholder={_(msg`Enter your email`)} - placeholderTextColor={pal.textLight.color} - autoCapitalize="none" - autoCorrect={false} - keyboardAppearance={theme.colorScheme} - value={email} - onChangeText={setEmail} - onSubmitEditing={onPressSignup} - enterKeyHint="done" - accessible={true} - accessibilityLabel={_(msg`Email`)} - accessibilityHint={_( - msg`Input your email to get on the Bluesky waitlist`, - )} - /> - {error ? ( - <View style={s.mt10}> - <ErrorMessage message={error} style={styles.error} /> - </View> - ) : undefined} - {isProcessing ? ( - <View style={[styles.btn, s.mt10]}> - <ActivityIndicator /> - </View> - ) : isEmailSent ? ( - <View style={[styles.btn, s.mt10]}> - <FontAwesomeIcon - icon="check" - style={pal.text as FontAwesomeIconStyle} - /> - <Text style={[s.ml10, pal.text]}> - <Trans> - Your email has been saved! We'll be in touch soon. - </Trans> - </Text> - </View> - ) : ( - <> - <TouchableOpacity - onPress={onPressSignup} - accessibilityRole="button" - accessibilityHint={_( - msg`Confirms signing up ${email} to the waitlist`, - )}> - <LinearGradient - colors={[gradients.blueLight.start, gradients.blueLight.end]} - start={{x: 0, y: 0}} - end={{x: 1, y: 1}} - style={[styles.btn]}> - <Text type="button-lg" style={[s.white, s.bold]}> - <Trans>Join Waitlist</Trans> - </Text> - </LinearGradient> - </TouchableOpacity> - <TouchableOpacity - style={[styles.btn, s.mt10]} - onPress={onCancel} - accessibilityRole="button" - accessibilityLabel={_(msg`Cancel waitlist signup`)} - accessibilityHint={_( - msg`Exits signing up for waitlist with ${email}`, - )} - onAccessibilityEscape={onCancel}> - <Text type="button-lg" style={pal.textLight}> - <Trans>Cancel</Trans> - </Text> - </TouchableOpacity> - </> - )} - </View> - </View> - ) -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - }, - innerContainer: { - paddingBottom: 20, - }, - title: { - textAlign: 'center', - marginTop: 12, - marginBottom: 12, - }, - description: { - textAlign: 'center', - paddingHorizontal: 22, - marginBottom: 10, - }, - textInput: { - borderWidth: 1, - borderRadius: 6, - paddingHorizontal: 16, - paddingVertical: 12, - fontSize: 20, - marginHorizontal: 20, - }, - btn: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'center', - borderRadius: 32, - padding: 14, - marginHorizontal: 20, - }, - error: { - borderRadius: 6, - marginHorizontal: 20, - marginBottom: 20, - }, -}) diff --git a/src/view/com/notifications/FeedItem.tsx b/src/view/com/notifications/FeedItem.tsx index f037097df..45166fe3c 100644 --- a/src/view/com/notifications/FeedItem.tsx +++ b/src/view/com/notifications/FeedItem.tsx @@ -228,6 +228,7 @@ let FeedItem = ({ text={sanitizeDisplayName( authors[0].displayName || authors[0].handle, )} + disableMismatchWarning /> {authors.length > 1 ? ( <> diff --git a/src/view/com/posts/Feed.tsx b/src/view/com/posts/Feed.tsx index 54d8aa224..cd3e98785 100644 --- a/src/view/com/posts/Feed.tsx +++ b/src/view/com/posts/Feed.tsx @@ -32,6 +32,7 @@ import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {DiscoverFallbackHeader} from './DiscoverFallbackHeader' import {FALLBACK_MARKER_POST} from '#/lib/api/feed/home' +import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender' const LOADING_ITEM = {_reactKey: '__loading__'} const EMPTY_FEED_ITEM = {_reactKey: '__empty__'} @@ -84,6 +85,7 @@ let Feed = ({ const {_} = useLingui() const queryClient = useQueryClient() const {currentAccount} = useSession() + const initialNumToRender = useInitialNumToRender() const [isPTRing, setIsPTRing] = React.useState(false) const checkForNewRef = React.useRef<(() => void) | null>(null) const lastFetchRef = React.useRef<number>(Date.now()) @@ -327,6 +329,8 @@ let Feed = ({ desktopFixedHeight={ desktopFixedHeightOffset ? desktopFixedHeightOffset : true } + initialNumToRender={initialNumToRender} + windowSize={11} /> </View> ) diff --git a/src/view/com/util/EventStopper.tsx b/src/view/com/util/EventStopper.tsx index e743e89bb..8f5f5cf54 100644 --- a/src/view/com/util/EventStopper.tsx +++ b/src/view/com/util/EventStopper.tsx @@ -8,7 +8,14 @@ import {View, ViewStyle} from 'react-native' export function EventStopper({ children, style, -}: React.PropsWithChildren<{style?: ViewStyle | ViewStyle[]}>) { + onKeyDown = true, +}: React.PropsWithChildren<{ + style?: ViewStyle | ViewStyle[] + /** + * Default `true`. Set to `false` to allow onKeyDown to propagate + */ + onKeyDown?: boolean +}>) { const stop = (e: any) => { e.stopPropagation() } @@ -18,7 +25,7 @@ export function EventStopper({ onTouchEnd={stop} // @ts-ignore web only -prf onClick={stop} - onKeyDown={stop} + onKeyDown={onKeyDown ? stop : undefined} style={style}> {children} </View> diff --git a/src/view/com/util/Views.d.ts b/src/view/com/util/Views.d.ts index 6a90cc229..16713921f 100644 --- a/src/view/com/util/Views.d.ts +++ b/src/view/com/util/Views.d.ts @@ -5,4 +5,6 @@ export function CenteredView({ style, sideBorders, ...props -}: React.PropsWithChildren<ViewProps & {sideBorders?: boolean}>) +}: React.PropsWithChildren< + ViewProps & {sideBorders?: boolean; topBorder?: boolean} +>) diff --git a/src/view/com/util/Views.web.tsx b/src/view/com/util/Views.web.tsx index db3b9de0d..ae165077c 100644 --- a/src/view/com/util/Views.web.tsx +++ b/src/view/com/util/Views.web.tsx @@ -32,8 +32,11 @@ interface AddedProps { export function CenteredView({ style, sideBorders, + topBorder, ...props -}: React.PropsWithChildren<ViewProps & {sideBorders?: boolean}>) { +}: React.PropsWithChildren< + ViewProps & {sideBorders?: boolean; topBorder?: boolean} +>) { const pal = usePalette('default') const {isMobile} = useWebMediaQueries() if (!isMobile) { @@ -46,6 +49,12 @@ export function CenteredView({ }) style = addStyle(style, pal.border) } + if (topBorder) { + style = addStyle(style, { + borderTopWidth: 1, + }) + style = addStyle(style, pal.border) + } return <View style={style} {...props} /> } diff --git a/src/view/com/util/forms/DateInput.tsx b/src/view/com/util/forms/DateInput.tsx index c5f0afc8f..0104562aa 100644 --- a/src/view/com/util/forms/DateInput.tsx +++ b/src/view/com/util/forms/DateInput.tsx @@ -1,8 +1,5 @@ import React, {useState, useCallback} from 'react' import {StyleProp, StyleSheet, TextStyle, View, ViewStyle} from 'react-native' -import DateTimePicker, { - DateTimePickerEvent, -} from '@react-native-community/datetimepicker' import { FontAwesomeIcon, FontAwesomeIconStyle, @@ -14,6 +11,7 @@ import {TypographyVariant} from 'lib/ThemeContext' import {useTheme} from 'lib/ThemeContext' import {usePalette} from 'lib/hooks/usePalette' import {getLocales} from 'expo-localization' +import DatePicker from 'react-native-date-picker' const LOCALE = getLocales()[0] @@ -43,11 +41,9 @@ export function DateInput(props: Props) { }, [props.handleAsUTC]) const onChangeInternal = useCallback( - (event: DateTimePickerEvent, date: Date | undefined) => { + (date: Date) => { setShow(false) - if (date) { - props.onChange(date) - } + props.onChange(date) }, [setShow, props], ) @@ -56,6 +52,10 @@ export function DateInput(props: Props) { setShow(true) }, [setShow]) + const onCancel = useCallback(() => { + setShow(false) + }, []) + return ( <View> {isAndroid && ( @@ -80,15 +80,17 @@ export function DateInput(props: Props) { </Button> )} {(isIOS || show) && ( - <DateTimePicker - testID={props.testID ? `${props.testID}-datepicker` : undefined} + <DatePicker + timeZoneOffsetInMinutes={0} + modal={isAndroid} + open={isAndroid} + theme={theme.colorScheme} + date={props.value} + onDateChange={onChangeInternal} + onConfirm={onChangeInternal} + onCancel={onCancel} mode="date" - timeZoneName={props.handleAsUTC ? 'Etc/UTC' : undefined} - display="spinner" - // @ts-ignore applies in iOS only -prf - themeVariant={theme.colorScheme} - value={props.value} - onChange={onChangeInternal} + testID={props.testID ? `${props.testID}-datepicker` : undefined} accessibilityLabel={props.accessibilityLabel} accessibilityHint={props.accessibilityHint} accessibilityLabelledBy={props.accessibilityLabelledBy} diff --git a/src/view/com/util/forms/PostDropdownBtn.tsx b/src/view/com/util/forms/PostDropdownBtn.tsx index 09850a7f5..6f2ae55b2 100644 --- a/src/view/com/util/forms/PostDropdownBtn.tsx +++ b/src/view/com/util/forms/PostDropdownBtn.tsx @@ -1,5 +1,11 @@ import React, {memo} from 'react' -import {StyleProp, View, ViewStyle} from 'react-native' +import { + StyleProp, + ViewStyle, + Pressable, + View, + PressableProps, +} from 'react-native' import Clipboard from '@react-native-clipboard/clipboard' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {useNavigation} from '@react-navigation/native' @@ -12,10 +18,6 @@ import { import {toShareUrl} from 'lib/strings/url-helpers' import {useTheme} from 'lib/ThemeContext' import {shareUrl} from 'lib/sharing' -import { - NativeDropdown, - DropdownItem as NativeDropdownItem, -} from './NativeDropdown' import * as Toast from '../Toast' import {EventStopper} from '../EventStopper' import {useModalControls} from '#/state/modals' @@ -36,6 +38,19 @@ import {isWeb} from '#/platform/detection' import {richTextToString} from '#/lib/strings/rich-text-helpers' import {useGlobalDialogsControlContext} from '#/components/dialogs/Context' +import {atoms as a, useTheme as useAlf, web} from '#/alf' +import * as Menu from '#/components/Menu' +import {Clipboard_Stroke2_Corner2_Rounded as ClipboardIcon} from '#/components/icons/Clipboard' +import {Filter_Stroke2_Corner0_Rounded as Filter} from '#/components/icons/Filter' +import {ArrowOutOfBox_Stroke2_Corner0_Rounded as Share} from '#/components/icons/ArrowOutOfBox' +import {EyeSlash_Stroke2_Corner0_Rounded as EyeSlash} from '#/components/icons/EyeSlash' +import {Mute_Stroke2_Corner0_Rounded as Mute} from '#/components/icons/Mute' +import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as Unmute} from '#/components/icons/Speaker' +import {BubbleQuestion_Stroke2_Corner0_Rounded as Translate} from '#/components/icons/Bubble' +import {Warning_Stroke2_Corner0_Rounded as Warning} from '#/components/icons/Warning' +import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash' +import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' + let PostDropdownBtn = ({ testID, postAuthor, @@ -45,6 +60,7 @@ let PostDropdownBtn = ({ richText, style, showAppealLabelItem, + hitSlop, }: { testID: string postAuthor: AppBskyActorDefs.ProfileViewBasic @@ -54,9 +70,11 @@ let PostDropdownBtn = ({ richText: RichTextAPI style?: StyleProp<ViewStyle> showAppealLabelItem?: boolean + hitSlop?: PressableProps['hitSlop'] }): React.ReactNode => { const {hasSession, currentAccount} = useSession() const theme = useTheme() + const alf = useAlf() const {_} = useLingui() const defaultCtrlColor = theme.palette.default.postCtrl const {openModal} = useModalControls() @@ -151,173 +169,189 @@ let PostDropdownBtn = ({ hidePost({uri: postUri}) }, [postUri, hidePost]) - const dropdownItems: NativeDropdownItem[] = [ - { - label: _(msg`Translate`), - onPress() { - onOpenTranslate() - }, - testID: 'postDropdownTranslateBtn', - icon: { - ios: { - name: 'character.book.closed', - }, - android: 'ic_menu_sort_alphabetically', - web: 'language', - }, - }, - { - label: _(msg`Copy post text`), - onPress() { - onCopyPostText() - }, - testID: 'postDropdownCopyTextBtn', - icon: { - ios: { - name: 'doc.on.doc', - }, - android: 'ic_menu_edit', - web: ['far', 'paste'], - }, - }, - { - label: isWeb ? _(msg`Copy link to post`) : _(msg`Share`), - onPress() { - const url = toShareUrl(href) - shareUrl(url) - }, - testID: 'postDropdownShareBtn', - icon: { - ios: { - name: 'square.and.arrow.up', - }, - android: 'ic_menu_share', - web: 'share', - }, - }, - hasSession && { - label: 'separator', - }, - hasSession && { - label: isThreadMuted ? _(msg`Unmute thread`) : _(msg`Mute thread`), - onPress() { - onToggleThreadMute() - }, - testID: 'postDropdownMuteThreadBtn', - icon: { - ios: { - name: 'speaker.slash', - }, - android: 'ic_lock_silent_mode', - web: 'comment-slash', - }, - }, - hasSession && { - label: _(msg`Mute words & tags`), - onPress() { - mutedWordsDialogControl.open() - }, - testID: 'postDropdownMuteWordsBtn', - icon: { - ios: { - name: 'speaker.slash', - }, - android: 'ic_lock_silent_mode', - web: 'filter', - }, - }, - hasSession && - !isAuthor && - !isPostHidden && { - label: _(msg`Hide post`), - onPress() { - openModal({ - name: 'confirm', - title: _(msg`Hide this post?`), - message: _(msg`This will hide this post from your feeds.`), - onPressConfirm: onHidePost, - }) - }, - testID: 'postDropdownHideBtn', - icon: { - ios: { - name: 'eye.slash', - }, - android: 'ic_menu_delete', - web: ['far', 'eye-slash'], - }, - }, - { - label: 'separator', - }, - !isAuthor && - hasSession && { - label: _(msg`Report post`), - onPress() { - openModal({ - name: 'report', - uri: postUri, - cid: postCid, - }) - }, - testID: 'postDropdownReportBtn', - icon: { - ios: { - name: 'exclamationmark.triangle', - }, - android: 'ic_menu_report_image', - web: 'circle-exclamation', - }, - }, - isAuthor && { - label: _(msg`Delete post`), - onPress() { - openModal({ - name: 'confirm', - title: _(msg`Delete this post?`), - message: _(msg`Are you sure? This cannot be undone.`), - onPressConfirm: onDeletePost, - }) - }, - testID: 'postDropdownDeleteBtn', - icon: { - ios: { - name: 'trash', - }, - android: 'ic_menu_delete', - web: ['far', 'trash-can'], - }, - }, - showAppealLabelItem && { - label: 'separator', - }, - showAppealLabelItem && { - label: _(msg`Appeal content warning`), - onPress() { - openModal({name: 'appeal-label', uri: postUri, cid: postCid}) - }, - testID: 'postDropdownAppealBtn', - icon: { - ios: { - name: 'exclamationmark.triangle', - }, - android: 'ic_menu_report_image', - web: 'circle-exclamation', - }, - }, - ].filter(Boolean) as NativeDropdownItem[] - return ( - <EventStopper> - <NativeDropdown - testID={testID} - items={dropdownItems} - accessibilityLabel={_(msg`More post options`)} - accessibilityHint=""> - <View style={style}> - <FontAwesomeIcon icon="ellipsis" size={20} color={defaultCtrlColor} /> - </View> - </NativeDropdown> + <EventStopper onKeyDown={false}> + <Menu.Root> + <Menu.Trigger label={_(msg`Open post options menu`)}> + {({props, state}) => { + const styles = [ + style, + a.rounded_full, + (state.hovered || state.focused || state.pressed) && [ + web({outline: 0}), + alf.atoms.bg_contrast_25, + ], + ] + return isWeb ? ( + <View {...props} testID={testID} style={styles}> + <FontAwesomeIcon + icon="ellipsis" + size={20} + color={defaultCtrlColor} + style={{pointerEvents: 'none'}} + /> + </View> + ) : ( + <Pressable + {...props} + hitSlop={hitSlop} + testID={testID} + style={styles}> + <FontAwesomeIcon + icon="ellipsis" + size={20} + color={defaultCtrlColor} + style={{pointerEvents: 'none'}} + /> + </Pressable> + ) + }} + </Menu.Trigger> + + <Menu.Outer> + <Menu.Group> + <Menu.Item + testID="postDropdownTranslateBtn" + label={_(msg`Translate`)} + onPress={onOpenTranslate}> + <Menu.ItemText>{_(msg`Translate`)}</Menu.ItemText> + <Menu.ItemIcon icon={Translate} position="right" /> + </Menu.Item> + + <Menu.Item + testID="postDropdownCopyTextBtn" + label={_(msg`Copy post text`)} + onPress={onCopyPostText}> + <Menu.ItemText>{_(msg`Copy post text`)}</Menu.ItemText> + <Menu.ItemIcon icon={ClipboardIcon} position="right" /> + </Menu.Item> + + <Menu.Item + testID="postDropdownShareBtn" + label={isWeb ? _(msg`Copy link to post`) : _(msg`Share`)} + onPress={() => { + const url = toShareUrl(href) + shareUrl(url) + }}> + <Menu.ItemText> + {isWeb ? _(msg`Copy link to post`) : _(msg`Share`)} + </Menu.ItemText> + <Menu.ItemIcon icon={Share} position="right" /> + </Menu.Item> + </Menu.Group> + + {hasSession && ( + <> + <Menu.Divider /> + + <Menu.Group> + <Menu.Item + testID="postDropdownMuteThreadBtn" + label={ + isThreadMuted ? _(msg`Unmute thread`) : _(msg`Mute thread`) + } + onPress={onToggleThreadMute}> + <Menu.ItemText> + {isThreadMuted + ? _(msg`Unmute thread`) + : _(msg`Mute thread`)} + </Menu.ItemText> + <Menu.ItemIcon + icon={isThreadMuted ? Unmute : Mute} + position="right" + /> + </Menu.Item> + + <Menu.Item + testID="postDropdownMuteWordsBtn" + label={_(msg`Mute words & tags`)} + onPress={() => mutedWordsDialogControl.open()}> + <Menu.ItemText>{_(msg`Mute words & tags`)}</Menu.ItemText> + <Menu.ItemIcon icon={Filter} position="right" /> + </Menu.Item> + + {!isAuthor && !isPostHidden && ( + <Menu.Item + testID="postDropdownHideBtn" + label={_(msg`Hide post`)} + onPress={() => { + openModal({ + name: 'confirm', + title: _(msg`Hide this post?`), + message: _( + msg`This will hide this post from your feeds.`, + ), + onPressConfirm: onHidePost, + }) + }}> + <Menu.ItemText>{_(msg`Hide post`)}</Menu.ItemText> + <Menu.ItemIcon icon={EyeSlash} position="right" /> + </Menu.Item> + )} + </Menu.Group> + </> + )} + + <Menu.Divider /> + + <Menu.Group> + {!isAuthor && ( + <Menu.Item + testID="postDropdownReportBtn" + label={_(msg`Report post`)} + onPress={() => { + openModal({ + name: 'report', + uri: postUri, + cid: postCid, + }) + }}> + <Menu.ItemText>{_(msg`Report post`)}</Menu.ItemText> + <Menu.ItemIcon icon={Warning} position="right" /> + </Menu.Item> + )} + + {isAuthor && ( + <Menu.Item + testID="postDropdownDeleteBtn" + label={_(msg`Delete post`)} + onPress={() => { + openModal({ + name: 'confirm', + title: _(msg`Delete this post?`), + message: _(msg`Are you sure? This cannot be undone.`), + onPressConfirm: onDeletePost, + }) + }}> + <Menu.ItemText>{_(msg`Delete post`)}</Menu.ItemText> + <Menu.ItemIcon icon={Trash} position="right" /> + </Menu.Item> + )} + + {showAppealLabelItem && ( + <> + <Menu.Divider /> + + <Menu.Item + testID="postDropdownAppealBtn" + label={_(msg`Appeal content warning`)} + onPress={() => { + openModal({ + name: 'appeal-label', + uri: postUri, + cid: postCid, + }) + }}> + <Menu.ItemText> + {_(msg`Appeal content warning`)} + </Menu.ItemText> + <Menu.ItemIcon icon={CircleInfo} position="right" /> + </Menu.Item> + </> + )} + </Menu.Group> + </Menu.Outer> + </Menu.Root> </EventStopper> ) } diff --git a/src/view/com/util/forms/SelectableBtn.tsx b/src/view/com/util/forms/SelectableBtn.tsx index f09d063a1..e577e155d 100644 --- a/src/view/com/util/forms/SelectableBtn.tsx +++ b/src/view/com/util/forms/SelectableBtn.tsx @@ -57,6 +57,7 @@ const styles = StyleSheet.create({ btn: { flexDirection: 'row', justifyContent: 'center', + flexGrow: 1, borderWidth: 1, borderLeftWidth: 0, paddingHorizontal: 10, diff --git a/src/view/com/util/post-ctrls/PostCtrls.tsx b/src/view/com/util/post-ctrls/PostCtrls.tsx index bd21ddda2..1e26eecce 100644 --- a/src/view/com/util/post-ctrls/PostCtrls.tsx +++ b/src/view/com/util/post-ctrls/PostCtrls.tsx @@ -212,9 +212,7 @@ let PostCtrls = ({ style={[styles.btn]} onPress={onShare} accessibilityRole="button" - accessibilityLabel={`${ - post.viewer?.like ? _(msg`Unlike`) : _(msg`Like`) - } (${post.likeCount} ${pluralize(post.likeCount || 0, 'like')})`} + accessibilityLabel={`${_(msg`Share`)}`} accessibilityHint="" hitSlop={big ? HITSLOP_20 : HITSLOP_10}> <ArrowOutOfBox style={[defaultCtrlColor, styles.mt1]} width={22} /> @@ -231,6 +229,7 @@ let PostCtrls = ({ richText={richText} showAppealLabelItem={showAppealLabelItem} style={styles.btnPad} + hitSlop={big ? HITSLOP_20 : HITSLOP_10} /> </View> </View> diff --git a/src/view/screens/Storybook/Menus.tsx b/src/view/screens/Storybook/Menus.tsx new file mode 100644 index 000000000..082fb2b6e --- /dev/null +++ b/src/view/screens/Storybook/Menus.tsx @@ -0,0 +1,79 @@ +import React from 'react' +import {View} from 'react-native' + +import {atoms as a, useTheme} from '#/alf' +import {Text} from '#/components/Typography' +import * as Menu from '#/components/Menu' +import {MagnifyingGlass2_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass2' +// import {useDialogStateControlContext} from '#/state/dialogs' + +export function Menus() { + const t = useTheme() + const menuControl = Menu.useMenuControl() + // const {closeAllDialogs} = useDialogStateControlContext() + + return ( + <View style={[a.gap_md]}> + <View style={[a.flex_row, a.align_start]}> + <Menu.Root control={menuControl}> + <Menu.Trigger label="Open basic menu" style={[a.flex_1]}> + {({state, props}) => { + return ( + <Text + {...props} + style={[ + a.py_sm, + a.px_md, + a.rounded_sm, + t.atoms.bg_contrast_50, + (state.hovered || state.focused || state.pressed) && [ + t.atoms.bg_contrast_200, + ], + ]}> + Open + </Text> + ) + }} + </Menu.Trigger> + + <Menu.Outer> + <Menu.Group> + <Menu.Item label="Click me" onPress={() => {}}> + <Menu.ItemIcon icon={Search} /> + <Menu.ItemText>Click me</Menu.ItemText> + </Menu.Item> + + <Menu.Item + label="Another item" + onPress={() => menuControl.close()}> + <Menu.ItemText>Another item</Menu.ItemText> + </Menu.Item> + </Menu.Group> + + <Menu.Divider /> + + <Menu.Group> + <Menu.Item label="Click me" onPress={() => {}}> + <Menu.ItemIcon icon={Search} /> + <Menu.ItemText>Click me</Menu.ItemText> + </Menu.Item> + + <Menu.Item + label="Another item" + onPress={() => menuControl.close()}> + <Menu.ItemText>Another item</Menu.ItemText> + </Menu.Item> + </Menu.Group> + + <Menu.Divider /> + + <Menu.Item label="Click me" onPress={() => {}}> + <Menu.ItemIcon icon={Search} /> + <Menu.ItemText>Click me</Menu.ItemText> + </Menu.Item> + </Menu.Outer> + </Menu.Root> + </View> + </View> + ) +} diff --git a/src/view/screens/Storybook/index.tsx b/src/view/screens/Storybook/index.tsx index 40929555e..e43d756de 100644 --- a/src/view/screens/Storybook/index.tsx +++ b/src/view/screens/Storybook/index.tsx @@ -16,6 +16,7 @@ import {Dialogs} from './Dialogs' import {Breakpoints} from './Breakpoints' import {Shadows} from './Shadows' import {Icons} from './Icons' +import {Menus} from './Menus' export function Storybook() { const t = useTheme() @@ -84,6 +85,7 @@ export function Storybook() { <Links /> <Forms /> <Dialogs /> + <Menus /> <Breakpoints /> </View> </CenteredView> diff --git a/src/view/shell/desktop/LeftNav.tsx b/src/view/shell/desktop/LeftNav.tsx index def0333c7..c56ba941e 100644 --- a/src/view/shell/desktop/LeftNav.tsx +++ b/src/view/shell/desktop/LeftNav.tsx @@ -391,7 +391,7 @@ export function DesktopLeftNav() { <FontAwesomeIcon icon="hand" style={pal.text as FontAwesomeIconStyle} - size={isDesktop ? 20 : 26} + size={isDesktop ? 23 : 26} /> } label={_(msg`Moderation`)} diff --git a/src/view/shell/index.tsx b/src/view/shell/index.tsx index d895d8851..76a7f8fb3 100644 --- a/src/view/shell/index.tsx +++ b/src/view/shell/index.tsx @@ -30,6 +30,8 @@ import {useCloseAnyActiveElement} from '#/state/util' import * as notifications from 'lib/notifications/notifications' import {Outlet as PortalOutlet} from '#/components/Portal' import {MutedWordsDialog} from '#/components/dialogs/MutedWords' +import {useDialogStateContext} from 'state/dialogs' +import Animated from 'react-native-reanimated' function ShellInner() { const isDrawerOpen = useIsDrawerOpen() @@ -53,6 +55,7 @@ function ShellInner() { const canGoBack = useNavigationState(state => !isStateAtTabRoot(state)) const {hasSession, currentAccount} = useSession() const closeAnyActiveElement = useCloseAnyActiveElement() + const {importantForAccessibility} = useDialogStateContext() // start undefined const currentAccountDid = React.useRef<string | undefined>(undefined) @@ -80,7 +83,9 @@ function ShellInner() { return ( <> - <View style={containerPadding}> + <Animated.View + style={containerPadding} + importantForAccessibility={importantForAccessibility}> <ErrorBoundary> <Drawer renderDrawerContent={renderDrawerContent} @@ -92,7 +97,7 @@ function ShellInner() { <TabsNavigator /> </Drawer> </ErrorBoundary> - </View> + </Animated.View> <Composer winHeight={winDim.height} /> <ModalsContainer /> <MutedWordsDialog /> |