diff --git a/src/components/AddObsModal/AddObsButton.js b/src/components/AddObsModal/AddObsButton.js index fccd87884..14d649b44 100644 --- a/src/components/AddObsModal/AddObsButton.js +++ b/src/components/AddObsModal/AddObsButton.js @@ -76,7 +76,7 @@ const AddObsButton = (): React.Node => { modal={addObsModal} /> void; // Inserts a white or colored view under the icon so an holes in the shape show as // white @@ -35,6 +37,15 @@ interface Props extends PropsWithChildren { const MIN_ACCESSIBLE_DIM = 44; +const WRAPPER_STYLE: ViewStyle = { + alignItems: "center", + justifyContent: "center" +}; + +const CONTAINED_WRAPPER_STYLE: ViewStyle = { + borderRadius: 9999 +}; + // Similar to IconButton in react-native-paper, except this allows independent // control over touchable area with `width` and `height` *and* the size of // the icon with `size` @@ -46,6 +57,7 @@ const INatIconButton = ( { disabled = false, height = MIN_ACCESSIBLE_DIM, icon, + iconOnly, onPress, preventTransparency, size = 18, @@ -67,12 +79,12 @@ const INatIconButton = ( { `Height cannot be less than ${MIN_ACCESSIBLE_DIM}. Use IconButton for smaller buttons.` ); } - if ( !accessibilityLabel ) { + if ( !accessibilityLabel && !iconOnly ) { throw new Error( "Button needs an accessibility label" ); } - const opacity = ( pressed: boolean ) => { + const getOpacity = React.useCallback( ( pressed: boolean ) => { if ( disabled ) { return 0.5; } @@ -80,7 +92,84 @@ const INatIconButton = ( { return 0.95; } return 1; - }; + }, [disabled] ); + + const wrapperStyle = React.useMemo( ( ) => ( [ + { width, height }, + WRAPPER_STYLE, + mode === "contained" && { + backgroundColor: preventTransparency + ? undefined + : backgroundColor, + ...CONTAINED_WRAPPER_STYLE + }, + style + ] ), [ + backgroundColor, + height, + mode, + preventTransparency, + style, + width + ] ); + + const content = ( + + { backgroundColor && preventTransparency && ( + + )} + { + children || ( + + ) + } + + ); + + if ( iconOnly ) { + return ( + + { content } + + ); + } return ( [ - { - opacity: opacity( pressed ), - width, - height, - justifyContent: "center", - alignItems: "center" - }, - mode === "contained" && { - backgroundColor: preventTransparency - ? undefined - : backgroundColor, - borderRadius: 9999 - }, - style + ...wrapperStyle, + { opacity: getOpacity( pressed ) } ]} testID={testID} > - - { backgroundColor && preventTransparency && ( - - )} - { - children || ( - - ) - } - + { content } ); }; diff --git a/src/i18n/l10n/en.ftl b/src/i18n/l10n/en.ftl index c734c9072..22f485429 100644 --- a/src/i18n/l10n/en.ftl +++ b/src/i18n/l10n/en.ftl @@ -722,6 +722,12 @@ Most-faved = Most faved # created by the viewer. Should be 16 characters or fewer or it will be ellipsized. MY-CONTENT--notifications = MY CONTENT My-Observations = My Observations +# Label for the bottom tab that shows your observations. Feel free to be +# flexible in translating this to keep it as short as possible. "My +# Observations" would be our preference in English, but it won't really fit, +# so we went with "Me". You have about ~7-20 characters before it gets cut +# off on the smallest screen sizes. +My-Observations--bottom-tab = Me Native = Native Native-to-place = Native to { $place } Navigates-to-AI-camera = Navigates to AI camera @@ -785,7 +791,12 @@ NOTES = NOTES # Label for section in ObsDetails with notes/description of observation Notes = Notes NOTIFICATIONS = NOTIFICATIONS -Notifications = Notifications +# Label for the bottom tab that shows notifications. Feel free to be flexible +# in translating this to keep it as short as possible. "Notifications" would +# be our preference in English, but it won't really fit, so we went +# with "Activity". You have about ~7-20 characters before it gets cut off on +# the smallest screen sizes. +Notifications--bottom-tab = Activity # notification when someone adds a comment to your observation notifications-user-added-comment-to-observation-by-you = <0>{ $userName } added a comment to an observation by you # notification when someone adds an identification to your observation @@ -857,7 +868,6 @@ Once-you-create-and-upload-observations = Once you create & upload observations, One-last-step = One last step! # Adjective, as in geoprivacy Open = Open -Open-drawer = Open drawer OPEN-EMAIL = OPEN EMAIL Open-menu = Open menu. # Text for a button that opens the operating system Settings app diff --git a/src/i18n/l10n/en.ftl.json b/src/i18n/l10n/en.ftl.json index 9fb0d427c..612265508 100644 --- a/src/i18n/l10n/en.ftl.json +++ b/src/i18n/l10n/en.ftl.json @@ -432,6 +432,7 @@ "Most-faved": "Most faved", "MY-CONTENT--notifications": "MY CONTENT", "My-Observations": "My Observations", + "My-Observations--bottom-tab": "Me", "Native": "Native", "Native-to-place": "Native to { $place }", "Navigates-to-AI-camera": "Navigates to AI camera", @@ -475,7 +476,7 @@ "NOTES": "NOTES", "Notes": "Notes", "NOTIFICATIONS": "NOTIFICATIONS", - "Notifications": "Notifications", + "Notifications--bottom-tab": "Activity", "notifications-user-added-comment-to-observation-by-you": "<0>{ $userName } added a comment to an observation by you", "notifications-user-added-identification-to-observation-by-you": "<0>{ $userName } added an identification to an observation by you", "notifications-user1-added-comment-to-observation-by-user2": "<0>{ $user1 } added a comment to an observation by { $user2 }", @@ -519,7 +520,6 @@ "Once-you-create-and-upload-observations": "Once you create & upload observations, other members of our community can add identifications to help your observations reach research grade.", "One-last-step": "One last step!", "Open": "Open", - "Open-drawer": "Open drawer", "OPEN-EMAIL": "OPEN EMAIL", "Open-menu": "Open menu.", "OPEN-SETTINGS": "OPEN SETTINGS", diff --git a/src/i18n/strings.ftl b/src/i18n/strings.ftl index c734c9072..22f485429 100644 --- a/src/i18n/strings.ftl +++ b/src/i18n/strings.ftl @@ -722,6 +722,12 @@ Most-faved = Most faved # created by the viewer. Should be 16 characters or fewer or it will be ellipsized. MY-CONTENT--notifications = MY CONTENT My-Observations = My Observations +# Label for the bottom tab that shows your observations. Feel free to be +# flexible in translating this to keep it as short as possible. "My +# Observations" would be our preference in English, but it won't really fit, +# so we went with "Me". You have about ~7-20 characters before it gets cut +# off on the smallest screen sizes. +My-Observations--bottom-tab = Me Native = Native Native-to-place = Native to { $place } Navigates-to-AI-camera = Navigates to AI camera @@ -785,7 +791,12 @@ NOTES = NOTES # Label for section in ObsDetails with notes/description of observation Notes = Notes NOTIFICATIONS = NOTIFICATIONS -Notifications = Notifications +# Label for the bottom tab that shows notifications. Feel free to be flexible +# in translating this to keep it as short as possible. "Notifications" would +# be our preference in English, but it won't really fit, so we went +# with "Activity". You have about ~7-20 characters before it gets cut off on +# the smallest screen sizes. +Notifications--bottom-tab = Activity # notification when someone adds a comment to your observation notifications-user-added-comment-to-observation-by-you = <0>{ $userName } added a comment to an observation by you # notification when someone adds an identification to your observation @@ -857,7 +868,6 @@ Once-you-create-and-upload-observations = Once you create & upload observations, One-last-step = One last step! # Adjective, as in geoprivacy Open = Open -Open-drawer = Open drawer OPEN-EMAIL = OPEN EMAIL Open-menu = Open menu. # Text for a button that opens the operating system Settings app diff --git a/src/navigation/BottomTabNavigator/CustomTabBar.js b/src/navigation/BottomTabNavigator/CustomTabBar.js index 943d2c044..b442c6408 100644 --- a/src/navigation/BottomTabNavigator/CustomTabBar.js +++ b/src/navigation/BottomTabNavigator/CustomTabBar.js @@ -23,14 +23,26 @@ type Props = { const CustomTabBar = ( { tabs }: Props ): Node => { const tabList = tabs.map( tab => ); - tabList.splice( -2, 0, ); + tabList.splice( + -2, + 0, + // Absolutely position the AddObsButton so it can float outside of the tab + // bar + ( + + + + + + ) + ); const insets = useSafeAreaInsets( ); return ( 0 } )} style={DROP_SHADOW} diff --git a/src/navigation/BottomTabNavigator/CustomTabBarContainer.js b/src/navigation/BottomTabNavigator/CustomTabBarContainer.js index 6e7001005..9d18fc223 100644 --- a/src/navigation/BottomTabNavigator/CustomTabBarContainer.js +++ b/src/navigation/BottomTabNavigator/CustomTabBarContainer.js @@ -43,7 +43,7 @@ const CustomTabBarContainer = ( { navigation }: Props ): Node => { { icon: "hamburger-menu", testID: DRAWER_ID, - accessibilityLabel: t( "Open-drawer" ), + accessibilityLabel: t( "Menu" ), accessibilityHint: t( "Opens-the-side-drawer-menu" ), size: 32, onPress: ( ) => { @@ -66,7 +66,7 @@ const CustomTabBarContainer = ( { navigation }: Props ): Node => { icon: "person", userIconUri: User.uri( currentUser ), testID: "NavButton.personIcon", - accessibilityLabel: t( "Observations" ), + accessibilityLabel: t( "My-Observations--bottom-tab" ), accessibilityHint: t( "Navigates-to-your-observations" ), size: 40, onPress: ( ) => { @@ -77,7 +77,7 @@ const CustomTabBarContainer = ( { navigation }: Props ): Node => { { icon: "notifications-bell", testID: SCREEN_NAME_NOTIFICATIONS, - accessibilityLabel: t( "Notifications" ), + accessibilityLabel: t( "Notifications--bottom-tab" ), accessibilityHint: t( "Navigates-to-notifications" ), size: 32, onPress: ( ) => { diff --git a/src/navigation/BottomTabNavigator/NavButton.js b/src/navigation/BottomTabNavigator/NavButton.js index 440b70bb2..62358c02f 100644 --- a/src/navigation/BottomTabNavigator/NavButton.js +++ b/src/navigation/BottomTabNavigator/NavButton.js @@ -1,6 +1,6 @@ // @flow -import { INatIconButton, UserIcon } from "components/SharedComponents"; -import { Pressable } from "components/styledComponents"; +import { Body3, INatIconButton, UserIcon } from "components/SharedComponents"; +import { Pressable, View } from "components/styledComponents"; import NotificationsIconContainer from "navigation/BottomTabNavigator/NotificationsIconContainer"; import * as React from "react"; import colors from "styles/tailwindColors"; @@ -28,67 +28,73 @@ const NavButton = ( { active, accessibilityLabel, accessibilityHint, - accessibilityRole = "tab", width = 44, height = 44 }: Props ): React.Node => { /* eslint-disable react/jsx-props-no-spreading */ const sharedProps = { testID, - onPress, - accessibilityRole, - accessibilityLabel, - accessibilityHint, - accessibilityState: { - selected: active, - expanded: active, - disabled: false - }, - width, - height - }; - - const notificationProps = { - testID, - onPress, - accessibilityRole, - accessibilityLabel, - accessibilityHint, width, height }; + let iconComponent; if ( userIconUri ) { - return ( - - + ); - } - - if ( icon === "notifications-bell" ) { - return ( + } else if ( icon === "notifications-bell" ) { + iconComponent = ( + ); + } else { + iconComponent = ( + ); } return ( - + + {iconComponent} + + {accessibilityLabel} + + ); }; diff --git a/src/navigation/BottomTabNavigator/NotificationsIcon.js b/src/navigation/BottomTabNavigator/NotificationsIcon.js index 699cbc006..d2fef1adb 100644 --- a/src/navigation/BottomTabNavigator/NotificationsIcon.js +++ b/src/navigation/BottomTabNavigator/NotificationsIcon.js @@ -1,9 +1,7 @@ // @flow import classnames from "classnames"; import { INatIcon, INatIconButton } from "components/SharedComponents"; -import { - Pressable, View -} from "components/styledComponents"; +import { View } from "components/styledComponents"; import * as React from "react"; import colors from "styles/tailwindColors"; @@ -11,11 +9,7 @@ type Props = { unread: boolean, icon: string, testID: string, - onPress: Function, active:boolean, - accessibilityLabel: string, - accessibilityRole?: string, - accessibilityHint?: string, size: number, width?: number, height?: number @@ -26,33 +20,20 @@ const NotificationsIcon = ( { testID, size, icon, - onPress, active, - accessibilityLabel, - accessibilityHint, - accessibilityRole = "tab", width, height }: Props ): React.Node => { /* eslint-disable react/jsx-props-no-spreading */ const sharedProps = { testID, - onPress, - accessibilityRole, - accessibilityLabel, - accessibilityHint, - accessibilityState: { - selected: active, - expanded: active, - disabled: false - }, width, height }; if ( unread ) { return ( - @@ -88,7 +69,7 @@ const NotificationsIcon = ( { )} /> - + ); } @@ -100,6 +81,7 @@ const NotificationsIcon = ( { : colors.darkGray} size={size} {...sharedProps} + iconOnly /> ); }; diff --git a/src/navigation/BottomTabNavigator/NotificationsIconContainer.js b/src/navigation/BottomTabNavigator/NotificationsIconContainer.js index 0d46b91f7..a8eb722c7 100644 --- a/src/navigation/BottomTabNavigator/NotificationsIconContainer.js +++ b/src/navigation/BottomTabNavigator/NotificationsIconContainer.js @@ -13,11 +13,7 @@ import useStore from "stores/useStore"; type Props = { testID: string, icon: string, - onPress: Function, active:boolean, - accessibilityLabel: string, - accessibilityRole?: string, - accessibilityHint?: string, size: number, width?: number, height?: number @@ -27,11 +23,7 @@ const NotificationsIconContainer = ( { testID, size, icon, - onPress, active, - accessibilityLabel, - accessibilityHint, - accessibilityRole = "tab", width, height }: Props ): Node => { @@ -73,10 +65,6 @@ const NotificationsIconContainer = ( { active={active} size={size} testID={testID} - onPress={onPress} - accessibilityRole={accessibilityRole} - accessibilityLabel={accessibilityLabel} - accessibilityHint={accessibilityHint} width={width} height={height} /> diff --git a/tests/integration/navigation/Explore.test.js b/tests/integration/navigation/Explore.test.js index ec15d15c7..486bf95e9 100644 --- a/tests/integration/navigation/Explore.test.js +++ b/tests/integration/navigation/Explore.test.js @@ -102,7 +102,7 @@ async function navigateToRootExplore( ) { const welcomeBack = await screen.findByText( /Welcome back/ ); await waitFor( ( ) => expect( welcomeBack ).toBeVisible( ) ); const tabBar = await screen.findByTestId( "CustomTabBar" ); - const exploreButton = await within( tabBar ).findByLabelText( "Explore" ); + const exploreButton = await within( tabBar ).findByText( "Explore" ); await actor.press( exploreButton ); } diff --git a/tests/unit/components/BottomTabNavigator/__snapshots__/CustomTabBar.test.js.snap b/tests/unit/components/BottomTabNavigator/__snapshots__/CustomTabBar.test.js.snap index 85a0566e5..08eaee744 100644 --- a/tests/unit/components/BottomTabNavigator/__snapshots__/CustomTabBar.test.js.snap +++ b/tests/unit/components/BottomTabNavigator/__snapshots__/CustomTabBar.test.js.snap @@ -32,9 +32,6 @@ exports[`CustomTabBar with advanced user layout should render correctly 1`] = ` { "justifyContent": "space-evenly", }, - { - "alignItems": "center", - }, { "paddingBottom": 4, "paddingLeft": 4, @@ -64,191 +61,15 @@ exports[`CustomTabBar with advanced user layout should render correctly 1`] = ` > - - -  - - - - - - -  - - - - - -  +  - + + + Menu + + + + + + +  + + + + + Explore + + + + + + + + +  + + + + + + + +  + + + + - -  - - + Me + + + +  + + + + - -  - - + Activity + diff --git a/tests/unit/components/SharedComponents/Buttons/__snapshots__/INatIconButton.test.js.snap b/tests/unit/components/SharedComponents/Buttons/__snapshots__/INatIconButton.test.js.snap index f923c8fbf..39cf20e91 100644 --- a/tests/unit/components/SharedComponents/Buttons/__snapshots__/INatIconButton.test.js.snap +++ b/tests/unit/components/SharedComponents/Buttons/__snapshots__/INatIconButton.test.js.snap @@ -36,14 +36,18 @@ exports[`INatIconButton renders correctly 1`] = ` style={ [ { - "alignItems": "center", "height": 44, - "justifyContent": "center", - "opacity": 1, "width": 44, }, + { + "alignItems": "center", + "justifyContent": "center", + }, false, undefined, + { + "opacity": 1, + }, ] } > diff --git a/tests/unit/components/SharedComponents/UploadStatus/__snapshots__/UploadStatus.test.js.snap b/tests/unit/components/SharedComponents/UploadStatus/__snapshots__/UploadStatus.test.js.snap index 3a0f5573f..f5b0aa57d 100644 --- a/tests/unit/components/SharedComponents/UploadStatus/__snapshots__/UploadStatus.test.js.snap +++ b/tests/unit/components/SharedComponents/UploadStatus/__snapshots__/UploadStatus.test.js.snap @@ -447,14 +447,18 @@ exports[`UploadStatus displays start icon when upload is unsynced and not queued style={ [ { - "alignItems": "center", "height": 44, - "justifyContent": "center", - "opacity": 1, "width": 44, }, + { + "alignItems": "center", + "justifyContent": "center", + }, false, undefined, + { + "opacity": 1, + }, ] } testID="UploadIcon.start.undefined" diff --git a/tests/unit/components/SharedComponents/__snapshots__/TaxonResult.test.js.snap b/tests/unit/components/SharedComponents/__snapshots__/TaxonResult.test.js.snap index 7a9561c13..bc573fb54 100644 --- a/tests/unit/components/SharedComponents/__snapshots__/TaxonResult.test.js.snap +++ b/tests/unit/components/SharedComponents/__snapshots__/TaxonResult.test.js.snap @@ -460,14 +460,18 @@ exports[`TaxonResult should render correctly 1`] = ` style={ [ { - "alignItems": "center", "height": 44, - "justifyContent": "center", - "opacity": 1, "width": 44, }, + { + "alignItems": "center", + "justifyContent": "center", + }, false, undefined, + { + "opacity": 1, + }, ] } > @@ -546,18 +550,22 @@ exports[`TaxonResult should render correctly 1`] = ` style={ [ { - "alignItems": "center", "height": 44, - "justifyContent": "center", - "opacity": 1, "width": 44, }, + { + "alignItems": "center", + "justifyContent": "center", + }, false, [ { "marginLeft": 8, }, ], + { + "opacity": 1, + }, ] } testID="undefined.checkmark"