diff options
author | Eric Bailey <git@esb.lol> | 2024-12-05 18:59:26 -0600 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-12-05 18:59:26 -0600 |
commit | 143e2c802d1d8d8498e6658c174ed1e657c4ec12 (patch) | |
tree | cbe937bec7e0a241774060ade7428180c4fe0aaf /src/view/com/util | |
parent | 8467dfd452b4cb1b62214b3abe87fd90d23a183b (diff) | |
download | voidsky-143e2c802d1d8d8498e6658c174ed1e657c4ec12.tar.zst |
[Layout] Base (#6907)
* Add common gutter styles as hook * Add computed scrollbar gutter CSS vars * Add new layout components * Replace layout components in settings screens * Remove old back button * Invert web border logic for easier migration * Clean up Slot API * Port over FF handling of scrollbar offset * Trade boilerplate for ease of use * Limit to one line * Allow two lines, fix wrapping * Fix alignment * sticky headers * set max with on header and center * [Layout] Notifications Header (#6910) * Replace notifications screen header * fix cropped indicator --------- Co-authored-by: Samuel Newman <mozzius@protonmail.com> * Replace Hashtag header (#6928) * [Layout] ChatList header (#6929) * Replace ChatList header * update chat settings as well --------- Co-authored-by: Samuel Newman <mozzius@protonmail.com> * Add web borders to Chat settings * Remove unused var * Move ChatList header outside center * Replace empty chat layout * fix breakpoints * [Layout] Scrollbar gutters (#6908) * Fix sidebar alignment * Make sure scrollbars don't hide * Gift left nav more space * Use stable one-edge, update logic in RightNav * Ope * Increase width * Reset * Add transform to sidebars * Remove bg in sidebars * Handle shifts in layout components * Replace scroll-removal handling * Make react-remove-scroll an explicit dep * Remove unused script * use correct scroll insets (#6950) * [Layout] Feeds headers (#6913) * Replace ViewHeader internals, duplicate old ViewHeader * Replace Feeds header * Replace SavedFeeds header * Visual alignment * Uglier but clear * Use old ViewHeader for SavedFeeds * use Layout.Center instead of Layout.Content * use left-aligned header for feed edit * delete unused old view header --------- Co-authored-by: Samuel Newman <mozzius@protonmail.com> * [Layout] Every other screen (#6953) * attempt to fix double borders on every other screen * delete ListHeaderDesktop * delete `SimpleViewHeader` and fix screens (#6956) * Make Layout.Center not full height * Refactor List to use Layout.Center, remove built-in borders * Fix Home screen * Refactor PagerWithHeader to use Layout components * Replace components in ProfileFeed and ProfileList * Borders on Profile * Search screen replacements * use new header for profile subpage header (#6958) * Search AutocompleteResults * use new header for starter pack wizard (#6957) * Fix post thread * Enable borders by default * Moderation muted and blocked accounts * Fix scrollbar offset on Labeler * Remove ScrollView from Moderation * Remove ScrollView from Deactivated * Remove ScrollView from onboarding * Remove ScrollView from SignupQueued * Mark deprecations * fix lint * Fix double borders on profile load * Remove unneeded CenteredView from noty Feed * Remove double Center layout on Notifications screen * Remove double Center layout on ChatList screen * Handle scrollbar offset in chat * Use new atom for other scrollbar offsets * Remove borders from old views * Better doc * Remove temp migration prop * Fix new atom usage on native * Clean up Hashtag screen * Layout docs * Clarify usage in Pager * Handle nested offset contexts * Clean up Layout * fix feeds page * asymmetric header on native (#6969) * Reusable header const * Fix up home header * Add back button to convo * Add hitslop to header buttons * Comment * Better handling on native for new atom * Format * Fix nested flatlist on mod screens * Use react-remove-scroll-bar directly * Fix notification count overflow on web * Clarify doc --------- Co-authored-by: Samuel Newman <mozzius@protonmail.com>
Diffstat (limited to 'src/view/com/util')
-rw-r--r-- | src/view/com/util/List.web.tsx | 115 | ||||
-rw-r--r-- | src/view/com/util/LoadingScreen.tsx | 9 | ||||
-rw-r--r-- | src/view/com/util/SimpleViewHeader.tsx | 114 | ||||
-rw-r--r-- | src/view/com/util/ViewHeader.tsx | 270 | ||||
-rw-r--r-- | src/view/com/util/Views.tsx | 7 | ||||
-rw-r--r-- | src/view/com/util/Views.web.tsx | 31 | ||||
-rw-r--r-- | src/view/com/util/error/ErrorScreen.tsx | 4 |
7 files changed, 86 insertions, 464 deletions
diff --git a/src/view/com/util/List.web.tsx b/src/view/com/util/List.web.tsx index f112d2d0a..18f7d6fa7 100644 --- a/src/view/com/util/List.web.tsx +++ b/src/view/com/util/List.web.tsx @@ -4,10 +4,9 @@ import {ReanimatedScrollEvent} from 'react-native-reanimated/lib/typescript/hook import {batchedUpdates} from '#/lib/batchedUpdates' import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' -import {usePalette} from '#/lib/hooks/usePalette' -import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' import {useScrollHandlers} from '#/lib/ScrollContext' import {addStyle} from '#/lib/styles' +import * as Layout from '#/components/Layout' export type ListMethods = any // TODO: Better types. export type ListProps<ItemT> = Omit< @@ -24,6 +23,9 @@ export type ListProps<ItemT> = Omit< desktopFixedHeight?: number | boolean // Web only prop to contain the scroll to the container rather than the window disableFullWindowScroll?: boolean + /** + * @deprecated Should be using Layout components + */ sideBorders?: boolean } export type ListRef = React.MutableRefObject<any | null> // TODO: Better types. @@ -56,20 +58,11 @@ function ListImpl<ItemT>( renderItem, extraData, style, - sideBorders = true, ...props }: ListProps<ItemT>, ref: React.Ref<ListMethods>, ) { const contextScrollHandlers = useScrollHandlers() - const pal = usePalette('default') - const {isMobile} = useWebMediaQueries() - if (!isMobile) { - contentContainerStyle = addStyle( - contentContainerStyle, - styles.containerScroll, - ) - } const isEmpty = !data || data.length === 0 @@ -326,53 +319,53 @@ function ListImpl<ItemT>( styles.parentTreeVisibilityDetector } /> - <View - ref={containerRef} - style={[ - !isMobile && sideBorders && styles.sideBorders, - contentContainerStyle, - desktopFixedHeight ? styles.minHeightViewport : null, - pal.border, - ]}> - <Visibility - root={disableFullWindowScroll ? nativeRef : null} - onVisibleChange={handleAboveTheFoldVisibleChange} - style={[styles.aboveTheFoldDetector, {height: headerOffset}]} - /> - {onStartReached && !isEmpty && ( - <EdgeVisibility - root={disableFullWindowScroll ? nativeRef : null} - onVisibleChange={onHeadVisibilityChange} - topMargin={(onStartReachedThreshold ?? 0) * 100 + '%'} - containerRef={containerRef} - /> - )} - {headerComponent} - {isEmpty - ? emptyComponent - : (data as Array<ItemT>)?.map((item, index) => { - const key = keyExtractor!(item, index) - return ( - <Row<ItemT> - key={key} - item={item} - index={index} - renderItem={renderItem} - extraData={extraData} - onItemSeen={onItemSeen} - /> - ) - })} - {onEndReached && !isEmpty && ( - <EdgeVisibility + <Layout.Center> + <View + ref={containerRef} + style={[ + contentContainerStyle, + desktopFixedHeight ? styles.minHeightViewport : null, + ]}> + <Visibility root={disableFullWindowScroll ? nativeRef : null} - onVisibleChange={onTailVisibilityChange} - bottomMargin={(onEndReachedThreshold ?? 0) * 100 + '%'} - containerRef={containerRef} + onVisibleChange={handleAboveTheFoldVisibleChange} + style={[styles.aboveTheFoldDetector, {height: headerOffset}]} /> - )} - {footerComponent} - </View> + {onStartReached && !isEmpty && ( + <EdgeVisibility + root={disableFullWindowScroll ? nativeRef : null} + onVisibleChange={onHeadVisibilityChange} + topMargin={(onStartReachedThreshold ?? 0) * 100 + '%'} + containerRef={containerRef} + /> + )} + {headerComponent} + {isEmpty + ? emptyComponent + : (data as Array<ItemT>)?.map((item, index) => { + const key = keyExtractor!(item, index) + return ( + <Row<ItemT> + key={key} + item={item} + index={index} + renderItem={renderItem} + extraData={extraData} + onItemSeen={onItemSeen} + /> + ) + })} + {onEndReached && !isEmpty && ( + <EdgeVisibility + root={disableFullWindowScroll ? nativeRef : null} + onVisibleChange={onTailVisibilityChange} + bottomMargin={(onEndReachedThreshold ?? 0) * 100 + '%'} + containerRef={containerRef} + /> + )} + {footerComponent} + </View> + </Layout.Center> </View> ) } @@ -558,16 +551,6 @@ export const List = memo(React.forwardRef(ListImpl)) as <ItemT>( // https://stackoverflow.com/questions/7944460/detect-safari-browser const styles = StyleSheet.create({ - sideBorders: { - borderLeftWidth: 1, - borderRightWidth: 1, - }, - containerScroll: { - width: '100%', - maxWidth: 600, - marginLeft: 'auto', - marginRight: 'auto', - }, minHeightViewport: { // @ts-ignore web only minHeight: '100vh', diff --git a/src/view/com/util/LoadingScreen.tsx b/src/view/com/util/LoadingScreen.tsx index 5d2aeb38f..1086c9d17 100644 --- a/src/view/com/util/LoadingScreen.tsx +++ b/src/view/com/util/LoadingScreen.tsx @@ -1,14 +1,17 @@ import {ActivityIndicator, View} from 'react-native' import {s} from '#/lib/styles' -import {CenteredView} from './Views' +import * as Layout from '#/components/Layout' +/** + * @deprecated use Layout compoenents directly + */ export function LoadingScreen() { return ( - <CenteredView> + <Layout.Content> <View style={s.p20}> <ActivityIndicator size="large" /> </View> - </CenteredView> + </Layout.Content> ) } diff --git a/src/view/com/util/SimpleViewHeader.tsx b/src/view/com/util/SimpleViewHeader.tsx deleted file mode 100644 index 78b66a929..000000000 --- a/src/view/com/util/SimpleViewHeader.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import React from 'react' -import { - StyleProp, - StyleSheet, - TouchableOpacity, - View, - ViewStyle, -} from 'react-native' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {useNavigation} from '@react-navigation/native' - -import {usePalette} from '#/lib/hooks/usePalette' -import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' -import {NavigationProp} from '#/lib/routes/types' -import {isWeb} from '#/platform/detection' -import {useSetDrawerOpen} from '#/state/shell' -import {Menu_Stroke2_Corner0_Rounded as Menu} from '#/components/icons/Menu' -import {CenteredView} from './Views' - -const BACK_HITSLOP = {left: 20, top: 20, right: 50, bottom: 20} - -export function SimpleViewHeader({ - showBackButton = true, - style, - children, -}: React.PropsWithChildren<{ - showBackButton?: boolean - style?: StyleProp<ViewStyle> -}>) { - const pal = usePalette('default') - const setDrawerOpen = useSetDrawerOpen() - const navigation = useNavigation<NavigationProp>() - const {isMobile} = useWebMediaQueries() - const canGoBack = navigation.canGoBack() - - const onPressBack = React.useCallback(() => { - if (navigation.canGoBack()) { - navigation.goBack() - } else { - navigation.navigate('Home') - } - }, [navigation]) - - const onPressMenu = React.useCallback(() => { - setDrawerOpen(true) - }, [setDrawerOpen]) - - const Container = isMobile ? View : CenteredView - return ( - <Container - style={[ - styles.header, - isMobile && styles.headerMobile, - isWeb && styles.headerWeb, - pal.view, - style, - ]}> - {showBackButton ? ( - <TouchableOpacity - testID="viewHeaderDrawerBtn" - onPress={canGoBack ? onPressBack : onPressMenu} - hitSlop={BACK_HITSLOP} - style={canGoBack ? styles.backBtn : styles.backBtnWide} - accessibilityRole="button" - accessibilityLabel={canGoBack ? 'Back' : 'Menu'} - accessibilityHint=""> - {canGoBack ? ( - <FontAwesomeIcon - size={18} - icon="angle-left" - style={[styles.backIcon, pal.text]} - /> - ) : ( - <Menu size="lg" style={[{marginTop: 4}, pal.textLight]} /> - )} - </TouchableOpacity> - ) : null} - {children} - </Container> - ) -} - -const styles = StyleSheet.create({ - header: { - flexDirection: 'row', - alignItems: 'center', - paddingHorizontal: 18, - paddingVertical: 12, - width: '100%', - }, - headerMobile: { - paddingHorizontal: 12, - paddingVertical: 10, - }, - headerWeb: { - // @ts-ignore web-only - position: 'sticky', - top: 0, - zIndex: 1, - }, - backBtn: { - width: 30, - height: 30, - }, - backBtnWide: { - width: 30, - height: 30, - paddingLeft: 4, - marginRight: 4, - }, - backIcon: { - marginTop: 6, - }, -}) diff --git a/src/view/com/util/ViewHeader.tsx b/src/view/com/util/ViewHeader.tsx index 1d4cf8ff0..2d413f782 100644 --- a/src/view/com/util/ViewHeader.tsx +++ b/src/view/com/util/ViewHeader.tsx @@ -1,271 +1,27 @@ -import React from 'react' -import {StyleSheet, TouchableOpacity, View} from 'react-native' -import Animated from 'react-native-reanimated' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import {useNavigation} from '@react-navigation/native' - -import {useMinimalShellHeaderTransform} from '#/lib/hooks/useMinimalShellTransform' -import {usePalette} from '#/lib/hooks/usePalette' -import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' -import {NavigationProp} from '#/lib/routes/types' -import {useSetDrawerOpen} from '#/state/shell' -import {useTheme} from '#/alf' -import {Menu_Stroke2_Corner0_Rounded as Menu} from '#/components/icons/Menu' -import {Text} from './text/Text' -import {CenteredView} from './Views' - -const BACK_HITSLOP = {left: 20, top: 20, right: 50, bottom: 20} +import {Header} from '#/components/Layout' +/** + * Legacy ViewHeader component. Use Layout.Header going forward. + * + * @deprecated + */ export function ViewHeader({ title, - subtitle, - canGoBack, - showBackButton = true, - hideOnScroll, - showOnDesktop, - showBorder, renderButton, }: { title: string subtitle?: string - canGoBack?: boolean - showBackButton?: boolean - hideOnScroll?: boolean showOnDesktop?: boolean showBorder?: boolean renderButton?: () => JSX.Element }) { - const pal = usePalette('default') - const {_} = useLingui() - const setDrawerOpen = useSetDrawerOpen() - const navigation = useNavigation<NavigationProp>() - const {isDesktop, isTablet} = useWebMediaQueries() - const t = useTheme() - - const onPressBack = React.useCallback(() => { - if (navigation.canGoBack()) { - navigation.goBack() - } else { - navigation.navigate('Home') - } - }, [navigation]) - - const onPressMenu = React.useCallback(() => { - setDrawerOpen(true) - }, [setDrawerOpen]) - - if (isDesktop) { - if (showOnDesktop) { - return ( - <DesktopWebHeader - title={title} - subtitle={subtitle} - renderButton={renderButton} - showBorder={showBorder} - /> - ) - } - return null - } else { - if (typeof canGoBack === 'undefined') { - canGoBack = navigation.canGoBack() - } - - return ( - <Container hideOnScroll={hideOnScroll || false} showBorder={showBorder}> - <View style={{flex: 1}}> - <View style={{flexDirection: 'row', alignItems: 'center'}}> - {showBackButton ? ( - <TouchableOpacity - testID="viewHeaderDrawerBtn" - onPress={canGoBack ? onPressBack : onPressMenu} - hitSlop={BACK_HITSLOP} - style={canGoBack ? styles.backBtn : styles.backBtnWide} - accessibilityRole="button" - accessibilityLabel={canGoBack ? _(msg`Back`) : _(msg`Menu`)} - accessibilityHint={ - canGoBack ? '' : _(msg`Access navigation links and settings`) - }> - {canGoBack ? ( - <FontAwesomeIcon - size={18} - icon="angle-left" - style={[styles.backIcon, pal.text]} - /> - ) : !isTablet ? ( - <Menu size="lg" style={[{marginTop: 3}, pal.textLight]} /> - ) : null} - </TouchableOpacity> - ) : null} - <View style={styles.titleContainer} pointerEvents="none"> - <Text emoji type="title" style={[pal.text, styles.title]}> - {title} - </Text> - </View> - {renderButton ? ( - renderButton() - ) : showBackButton ? ( - <View style={canGoBack ? styles.backBtn : styles.backBtnWide} /> - ) : null} - </View> - {subtitle ? ( - <View - style={[styles.titleContainer, {marginTop: -3}]} - pointerEvents="none"> - <Text - style={[ - pal.text, - styles.subtitle, - t.atoms.text_contrast_medium, - ]}> - {subtitle} - </Text> - </View> - ) : undefined} - </View> - </Container> - ) - } -} - -function DesktopWebHeader({ - title, - subtitle, - renderButton, - showBorder = true, -}: { - title: string - subtitle?: string - renderButton?: () => JSX.Element - showBorder?: boolean -}) { - const pal = usePalette('default') - const t = useTheme() - return ( - <CenteredView - style={[ - styles.header, - styles.desktopHeader, - pal.border, - { - borderBottomWidth: showBorder ? StyleSheet.hairlineWidth : 0, - }, - {display: 'flex', flexDirection: 'column'}, - ]}> - <View> - <View style={styles.titleContainer} pointerEvents="none"> - <Text type="title-lg" style={[pal.text, styles.title]}> - {title} - </Text> - </View> - {renderButton?.()} - </View> - {subtitle ? ( - <View> - <View style={[styles.titleContainer]} pointerEvents="none"> - <Text - style={[ - pal.text, - styles.subtitleDesktop, - t.atoms.text_contrast_medium, - ]}> - {subtitle} - </Text> - </View> - </View> - ) : null} - </CenteredView> - ) -} - -function Container({ - children, - hideOnScroll, - showBorder, -}: { - children: React.ReactNode - hideOnScroll: boolean - showBorder?: boolean -}) { - const pal = usePalette('default') - const headerMinimalShellTransform = useMinimalShellHeaderTransform() - - if (!hideOnScroll) { - return ( - <View - style={[ - styles.header, - pal.view, - pal.border, - showBorder && styles.border, - ]}> - {children} - </View> - ) - } return ( - <Animated.View - style={[ - styles.header, - styles.headerFloating, - pal.view, - pal.border, - headerMinimalShellTransform, - showBorder && styles.border, - ]}> - {children} - </Animated.View> + <Header.Outer> + <Header.BackButton /> + <Header.Content> + <Header.TitleText>{title}</Header.TitleText> + </Header.Content> + <Header.Slot>{renderButton?.() ?? null}</Header.Slot> + </Header.Outer> ) } - -const styles = StyleSheet.create({ - header: { - flexDirection: 'row', - paddingHorizontal: 12, - paddingVertical: 6, - width: '100%', - }, - headerFloating: { - position: 'absolute', - top: 0, - width: '100%', - }, - desktopHeader: { - paddingVertical: 12, - maxWidth: 600, - marginLeft: 'auto', - marginRight: 'auto', - }, - border: { - borderBottomWidth: StyleSheet.hairlineWidth, - }, - titleContainer: { - marginLeft: 'auto', - marginRight: 'auto', - alignItems: 'center', - }, - title: { - fontWeight: '600', - }, - subtitle: { - fontSize: 13, - }, - subtitleDesktop: { - fontSize: 15, - }, - backBtn: { - width: 30, - height: 30, - }, - backBtnWide: { - width: 30, - height: 30, - paddingLeft: 4, - marginRight: 4, - }, - backIcon: { - marginTop: 6, - }, -}) diff --git a/src/view/com/util/Views.tsx b/src/view/com/util/Views.tsx index 0d3f63794..c9ba0728c 100644 --- a/src/view/com/util/Views.tsx +++ b/src/view/com/util/Views.tsx @@ -15,9 +15,16 @@ export type FlatList_INTERNAL<ItemT = any> = Omit< FlatListComponent<ItemT, FlatListPropsWithLayout<ItemT>>, 'CellRendererComponent' > + +/** + * @deprecated use `Layout` components + */ export const ScrollView = Animated.ScrollView export type ScrollView = typeof Animated.ScrollView +/** + * @deprecated use `Layout` components + */ export const CenteredView = forwardRef< View, React.PropsWithChildren< diff --git a/src/view/com/util/Views.web.tsx b/src/view/com/util/Views.web.tsx index 1f030b408..e64b0ce9a 100644 --- a/src/view/com/util/Views.web.tsx +++ b/src/view/com/util/Views.web.tsx @@ -31,10 +31,12 @@ interface AddedProps { desktopFixedHeight?: boolean | number } +/** + * @deprecated use `Layout` components + */ export const CenteredView = React.forwardRef(function CenteredView( { style, - sideBorders, topBorder, ...props }: React.PropsWithChildren< @@ -47,13 +49,6 @@ export const CenteredView = React.forwardRef(function CenteredView( if (!isMobile) { style = addStyle(style, styles.container) } - if (sideBorders && !isMobile) { - style = addStyle(style, { - borderLeftWidth: StyleSheet.hairlineWidth, - borderRightWidth: StyleSheet.hairlineWidth, - }) - style = addStyle(style, pal.border) - } if (topBorder) { style = addStyle(style, { borderTopWidth: 1, @@ -75,7 +70,6 @@ export const FlatList_INTERNAL = React.forwardRef(function FlatListImpl<ItemT>( >, ref: React.Ref<FlatList<ItemT>>, ) { - const pal = usePalette('default') const {isMobile} = useWebMediaQueries() if (!isMobile) { contentContainerStyle = addStyle( @@ -123,11 +117,7 @@ export const FlatList_INTERNAL = React.forwardRef(function FlatListImpl<ItemT>( return ( <Animated.FlatList ref={ref} - contentContainerStyle={[ - styles.contentContainer, - contentContainerStyle, - pal.border, - ]} + contentContainerStyle={[styles.contentContainer, contentContainerStyle]} style={style} contentOffset={contentOffset} {...props} @@ -135,12 +125,13 @@ export const FlatList_INTERNAL = React.forwardRef(function FlatListImpl<ItemT>( ) }) +/** + * @deprecated use `Layout` components + */ export const ScrollView = React.forwardRef(function ScrollViewImpl( {contentContainerStyle, ...props}: React.PropsWithChildren<ScrollViewProps>, ref: React.Ref<Animated.ScrollView>, ) { - const pal = usePalette('default') - const {isMobile} = useWebMediaQueries() if (!isMobile) { contentContainerStyle = addStyle( @@ -150,11 +141,7 @@ export const ScrollView = React.forwardRef(function ScrollViewImpl( } return ( <Animated.ScrollView - contentContainerStyle={[ - styles.contentContainer, - contentContainerStyle, - pal.border, - ]} + contentContainerStyle={[styles.contentContainer, contentContainerStyle]} // @ts-ignore something is wrong with the reanimated types -prf ref={ref} {...props} @@ -164,8 +151,6 @@ export const ScrollView = React.forwardRef(function ScrollViewImpl( const styles = StyleSheet.create({ contentContainer: { - borderLeftWidth: StyleSheet.hairlineWidth, - borderRightWidth: StyleSheet.hairlineWidth, // @ts-ignore web only minHeight: '100vh', }, diff --git a/src/view/com/util/error/ErrorScreen.tsx b/src/view/com/util/error/ErrorScreen.tsx index b66f43789..846f4d295 100644 --- a/src/view/com/util/error/ErrorScreen.tsx +++ b/src/view/com/util/error/ErrorScreen.tsx @@ -36,7 +36,9 @@ export function ErrorScreen({ return ( <> - {showHeader && isMobile && <ViewHeader title="Error" showBorder />} + {showHeader && isMobile && ( + <ViewHeader title={_(msg`Error`)} showBorder /> + )} <CenteredView testID={testID} style={[styles.outer, pal.view]}> <View style={styles.errorIconContainer}> <View |