diff options
author | Paul Frazee <pfrazee@gmail.com> | 2022-12-28 14:06:01 -0600 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-12-28 14:06:01 -0600 |
commit | 7e31645e9a355f2a0b3c8d62430a53dbb4714674 (patch) | |
tree | 24db1b09b9065472f5c7e08f9e2798d63fee8b1a /src | |
parent | cc63660982199a989859d3b5328ba43a4edec755 (diff) | |
download | voidsky-7e31645e9a355f2a0b3c8d62430a53dbb4714674.tar.zst |
Add a design system (#34)
* Add theming system * Add standard Button control and update RadioButtons * Unify radiobutton with design system * Update debug screen to have multiple views * Add ToggleButton * Update error controls to use design system * Add typography to <Text> element * Move DropdownButton into the design system * Clean out old code * Move Text into design system * Add 'inverted' color palette * Move LoadingPlaceholder into the design system
Diffstat (limited to 'src')
78 files changed, 1431 insertions, 375 deletions
diff --git a/src/App.native.tsx b/src/App.native.tsx index a532a08d4..fa523cd81 100644 --- a/src/App.native.tsx +++ b/src/App.native.tsx @@ -5,6 +5,7 @@ import {RootSiblingParent} from 'react-native-root-siblings' import {GestureHandlerRootView} from 'react-native-gesture-handler' import SplashScreen from 'react-native-splash-screen' import {SafeAreaProvider} from 'react-native-safe-area-context' +import {ThemeProvider} from './view/lib/ThemeContext' import * as view from './view/index' import {RootStoreModel, setupState, RootStoreProvider} from './state' import {MobileShell} from './view/shell/mobile' @@ -40,9 +41,11 @@ function App() { <GestureHandlerRootView style={{flex: 1}}> <RootSiblingParent> <RootStoreProvider value={rootStore}> - <SafeAreaProvider> - <MobileShell /> - </SafeAreaProvider> + <ThemeProvider> + <SafeAreaProvider> + <MobileShell /> + </SafeAreaProvider> + </ThemeProvider> </RootStoreProvider> </RootSiblingParent> </GestureHandlerRootView> diff --git a/src/lib/functions.ts b/src/lib/functions.ts new file mode 100644 index 000000000..d6fbf5b92 --- /dev/null +++ b/src/lib/functions.ts @@ -0,0 +1,6 @@ +export function choose<U, T extends Record<string, U>>( + value: keyof T, + choices: T, +): U { + return choices[value] +} diff --git a/src/view/com/composer/Autocomplete.tsx b/src/view/com/composer/Autocomplete.tsx index 1637108f8..b151e0d9e 100644 --- a/src/view/com/composer/Autocomplete.tsx +++ b/src/view/com/composer/Autocomplete.tsx @@ -5,8 +5,8 @@ import { StyleSheet, useWindowDimensions, } from 'react-native' -import {useAnimatedValue} from '../../lib/useAnimatedValue' -import {Text} from '../util/Text' +import {useAnimatedValue} from '../../lib/hooks/useAnimatedValue' +import {Text} from '../util/text/Text' import {colors} from '../../lib/styles' interface AutocompleteItem { diff --git a/src/view/com/composer/ComposePost.tsx b/src/view/com/composer/ComposePost.tsx index baa931105..fe310f19b 100644 --- a/src/view/com/composer/ComposePost.tsx +++ b/src/view/com/composer/ComposePost.tsx @@ -15,7 +15,7 @@ import LinearGradient from 'react-native-linear-gradient' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {UserAutocompleteViewModel} from '../../../state/models/user-autocomplete-view' import {Autocomplete} from './Autocomplete' -import {Text} from '../util/Text' +import {Text} from '../util/text/Text' import * as Toast from '../util/Toast' // @ts-ignore no type definition -prf import ProgressCircle from 'react-native-progress/Circle' diff --git a/src/view/com/composer/Prompt.tsx b/src/view/com/composer/Prompt.tsx index 2b1559df4..ec63d9501 100644 --- a/src/view/com/composer/Prompt.tsx +++ b/src/view/com/composer/Prompt.tsx @@ -3,7 +3,7 @@ import {StyleSheet, TouchableOpacity, View} from 'react-native' import {colors} from '../../lib/styles' import {useStores} from '../../../state' import {UserAvatar} from '../util/UserAvatar' -import {Text} from '../util/Text' +import {Text} from '../util/text/Text' export function ComposePrompt({ noAvi = false, diff --git a/src/view/com/discover/SuggestedFollows.tsx b/src/view/com/discover/SuggestedFollows.tsx index b78bae889..77bd94d5a 100644 --- a/src/view/com/discover/SuggestedFollows.tsx +++ b/src/view/com/discover/SuggestedFollows.tsx @@ -10,9 +10,9 @@ import LinearGradient from 'react-native-linear-gradient' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {observer} from 'mobx-react-lite' import _omit from 'lodash.omit' -import {ErrorScreen} from '../util/ErrorScreen' +import {ErrorScreen} from '../util/error/ErrorScreen' import {Link} from '../util/Link' -import {Text} from '../util/Text' +import {Text} from '../util/text/Text' import {UserAvatar} from '../util/UserAvatar' import * as Toast from '../util/Toast' import {useStores} from '../../../state' diff --git a/src/view/com/lightbox/Lightbox.tsx b/src/view/com/lightbox/Lightbox.tsx index 36c51764f..849354aea 100644 --- a/src/view/com/lightbox/Lightbox.tsx +++ b/src/view/com/lightbox/Lightbox.tsx @@ -10,7 +10,7 @@ import {observer} from 'mobx-react-lite' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {SwipeAndZoom, Dir} from '../util/gestures/SwipeAndZoom' import {useStores} from '../../../state' -import {useAnimatedValue} from '../../lib/useAnimatedValue' +import {useAnimatedValue} from '../../lib/hooks/useAnimatedValue' import * as models from '../../../state/models/shell-ui' diff --git a/src/view/com/login/CreateAccount.tsx b/src/view/com/login/CreateAccount.tsx index f97eb7a0f..689a4f384 100644 --- a/src/view/com/login/CreateAccount.tsx +++ b/src/view/com/login/CreateAccount.tsx @@ -15,7 +15,7 @@ import * as EmailValidator from 'email-validator' import {Logo} from './Logo' import {Picker} from '../util/Picker' import {TextLink} from '../util/Link' -import {Text} from '../util/Text' +import {Text} from '../util/text/Text' import {s, colors} from '../../lib/styles' import { makeValidHandle, diff --git a/src/view/com/login/Signin.tsx b/src/view/com/login/Signin.tsx index 45728d3b3..f76507d71 100644 --- a/src/view/com/login/Signin.tsx +++ b/src/view/com/login/Signin.tsx @@ -12,7 +12,7 @@ import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import * as EmailValidator from 'email-validator' import {sessionClient as AtpApi, SessionServiceClient} from '@atproto/api' import {Logo} from './Logo' -import {Text} from '../util/Text' +import {Text} from '../util/text/Text' import {s, colors} from '../../lib/styles' import {createFullHandle, toNiceDomain} from '../../../lib/strings' import {useStores, RootStoreModel, DEFAULT_SERVICE} from '../../../state' diff --git a/src/view/com/modals/Confirm.tsx b/src/view/com/modals/Confirm.tsx index a18043f1a..7545e36a6 100644 --- a/src/view/com/modals/Confirm.tsx +++ b/src/view/com/modals/Confirm.tsx @@ -6,10 +6,10 @@ import { View, } from 'react-native' import LinearGradient from 'react-native-linear-gradient' -import {Text} from '../util/Text' +import {Text} from '../util/text/Text' import {useStores} from '../../../state' import {s, colors, gradients} from '../../lib/styles' -import {ErrorMessage} from '../util/ErrorMessage' +import {ErrorMessage} from '../util/error/ErrorMessage' export const snapPoints = ['50%'] diff --git a/src/view/com/modals/CreateScene.tsx b/src/view/com/modals/CreateScene.tsx index 0d47aa4bd..60c240546 100644 --- a/src/view/com/modals/CreateScene.tsx +++ b/src/view/com/modals/CreateScene.tsx @@ -9,8 +9,8 @@ import { import LinearGradient from 'react-native-linear-gradient' import {BottomSheetScrollView, BottomSheetTextInput} from '@gorhom/bottom-sheet' import {AppBskyActorCreateScene} from '@atproto/api' -import {ErrorMessage} from '../util/ErrorMessage' -import {Text} from '../util/Text' +import {ErrorMessage} from '../util/error/ErrorMessage' +import {Text} from '../util/text/Text' import {useStores} from '../../../state' import {s, colors, gradients} from '../../lib/styles' import { diff --git a/src/view/com/modals/EditProfile.tsx b/src/view/com/modals/EditProfile.tsx index bd97ced53..8a3f016ad 100644 --- a/src/view/com/modals/EditProfile.tsx +++ b/src/view/com/modals/EditProfile.tsx @@ -9,8 +9,8 @@ import { import LinearGradient from 'react-native-linear-gradient' import {BottomSheetScrollView, BottomSheetTextInput} from '@gorhom/bottom-sheet' import {Image as PickedImage} from 'react-native-image-crop-picker' -import {Text} from '../util/Text' -import {ErrorMessage} from '../util/ErrorMessage' +import {Text} from '../util/text/Text' +import {ErrorMessage} from '../util/error/ErrorMessage' import {useStores} from '../../../state' import {ProfileViewModel} from '../../../state/models/profile-view' import {s, colors, gradients} from '../../lib/styles' diff --git a/src/view/com/modals/InviteToScene.tsx b/src/view/com/modals/InviteToScene.tsx index 28380b6a8..a73440179 100644 --- a/src/view/com/modals/InviteToScene.tsx +++ b/src/view/com/modals/InviteToScene.tsx @@ -19,8 +19,8 @@ import { import _omit from 'lodash.omit' import {AtUri} from '../../../third-party/uri' import {ProfileCard} from '../profile/ProfileCard' -import {ErrorMessage} from '../util/ErrorMessage' -import {Text} from '../util/Text' +import {ErrorMessage} from '../util/error/ErrorMessage' +import {Text} from '../util/text/Text' import {useStores} from '../../../state' import * as apilib from '../../../state/lib/api' import {ProfileViewModel} from '../../../state/models/profile-view' diff --git a/src/view/com/modals/ReportAccount.tsx b/src/view/com/modals/ReportAccount.tsx index 582e24238..bf4d5f5a0 100644 --- a/src/view/com/modals/ReportAccount.tsx +++ b/src/view/com/modals/ReportAccount.tsx @@ -9,8 +9,8 @@ import LinearGradient from 'react-native-linear-gradient' import {useStores} from '../../../state' import {s, colors, gradients} from '../../lib/styles' import {RadioGroup, RadioGroupItem} from '../util/forms/RadioGroup' -import {Text} from '../util/Text' -import {ErrorMessage} from '../util/ErrorMessage' +import {Text} from '../util/text/Text' +import {ErrorMessage} from '../util/error/ErrorMessage' const ITEMS: RadioGroupItem[] = [ {key: 'spam', label: 'Spam or excessive repeat posts'}, diff --git a/src/view/com/modals/ReportPost.tsx b/src/view/com/modals/ReportPost.tsx index 6f134032a..d4684069a 100644 --- a/src/view/com/modals/ReportPost.tsx +++ b/src/view/com/modals/ReportPost.tsx @@ -9,8 +9,8 @@ import LinearGradient from 'react-native-linear-gradient' import {useStores} from '../../../state' import {s, colors, gradients} from '../../lib/styles' import {RadioGroup, RadioGroupItem} from '../util/forms/RadioGroup' -import {Text} from '../util/Text' -import {ErrorMessage} from '../util/ErrorMessage' +import {Text} from '../util/text/Text' +import {ErrorMessage} from '../util/error/ErrorMessage' const ITEMS: RadioGroupItem[] = [ {key: 'spam', label: 'Spam or excessive repeat posts'}, diff --git a/src/view/com/modals/ServerInput.tsx b/src/view/com/modals/ServerInput.tsx index 0d1e0e911..884fb91e6 100644 --- a/src/view/com/modals/ServerInput.tsx +++ b/src/view/com/modals/ServerInput.tsx @@ -2,7 +2,7 @@ import React, {useState} from 'react' import {Platform, StyleSheet, TouchableOpacity, View} from 'react-native' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {BottomSheetScrollView, BottomSheetTextInput} from '@gorhom/bottom-sheet' -import {Text} from '../util/Text' +import {Text} from '../util/text/Text' import {useStores} from '../../../state' import {s, colors} from '../../lib/styles' import { diff --git a/src/view/com/notifications/Feed.tsx b/src/view/com/notifications/Feed.tsx index c986bca57..91a01db4d 100644 --- a/src/view/com/notifications/Feed.tsx +++ b/src/view/com/notifications/Feed.tsx @@ -4,9 +4,9 @@ import {View, FlatList} from 'react-native' import {NotificationsViewModel} from '../../../state/models/notifications-view' import {FeedItem} from './FeedItem' import {NotificationFeedLoadingPlaceholder} from '../util/LoadingPlaceholder' -import {ErrorMessage} from '../util/ErrorMessage' +import {ErrorMessage} from '../util/error/ErrorMessage' import {EmptyState} from '../util/EmptyState' -import {OnScrollCb} from '../../lib/useOnMainScroll' +import {OnScrollCb} from '../../lib/hooks/useOnMainScroll' const EMPTY_FEED_ITEM = {_reactKey: '__empty__'} @@ -54,7 +54,6 @@ export const Feed = observer(function Feed({ {view.isLoading && !data && <NotificationFeedLoadingPlaceholder />} {view.hasError && ( <ErrorMessage - dark message={view.error} style={{margin: 6}} onPressTryAgain={onPressTryAgain} diff --git a/src/view/com/notifications/FeedItem.tsx b/src/view/com/notifications/FeedItem.tsx index 16374a9f9..c6b286cdb 100644 --- a/src/view/com/notifications/FeedItem.tsx +++ b/src/view/com/notifications/FeedItem.tsx @@ -8,9 +8,9 @@ import {PostThreadViewModel} from '../../../state/models/post-thread-view' import {s, colors} from '../../lib/styles' import {ago, pluralize} from '../../../lib/strings' import {UpIconSolid} from '../../lib/icons' -import {Text} from '../util/Text' +import {Text} from '../util/text/Text' import {UserAvatar} from '../util/UserAvatar' -import {ErrorMessage} from '../util/ErrorMessage' +import {ErrorMessage} from '../util/error/ErrorMessage' import {Post} from '../post/Post' import {Link} from '../util/Link' import {InviteAccepter} from './InviteAccepter' diff --git a/src/view/com/notifications/InviteAccepter.tsx b/src/view/com/notifications/InviteAccepter.tsx index 4df8b2e4f..eefe7a273 100644 --- a/src/view/com/notifications/InviteAccepter.tsx +++ b/src/view/com/notifications/InviteAccepter.tsx @@ -8,7 +8,7 @@ import {ConfirmModal} from '../../../state/models/shell-ui' import {useStores} from '../../../state' import {ProfileCard} from '../profile/ProfileCard' import * as Toast from '../util/Toast' -import {Text} from '../util/Text' +import {Text} from '../util/text/Text' import {s, colors, gradients} from '../../lib/styles' export function InviteAccepter({item}: {item: NotificationsViewItemModel}) { diff --git a/src/view/com/onboard/FeatureExplainer.tsx b/src/view/com/onboard/FeatureExplainer.tsx index 31863ad50..ecc1b9692 100644 --- a/src/view/com/onboard/FeatureExplainer.tsx +++ b/src/view/com/onboard/FeatureExplainer.tsx @@ -10,7 +10,7 @@ import { } from 'react-native' import {TabView, SceneMap, Route, TabBarProps} from 'react-native-tab-view' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {Text} from '../util/Text' +import {Text} from '../util/text/Text' import {UserGroupIcon} from '../../lib/icons' import {useStores} from '../../../state' import {s} from '../../lib/styles' diff --git a/src/view/com/onboard/Follows.tsx b/src/view/com/onboard/Follows.tsx index 4026c879a..76eff3f4b 100644 --- a/src/view/com/onboard/Follows.tsx +++ b/src/view/com/onboard/Follows.tsx @@ -2,7 +2,7 @@ import React from 'react' import {SafeAreaView, StyleSheet, TouchableOpacity, View} from 'react-native' import {observer} from 'mobx-react-lite' import {SuggestedFollows} from '../discover/SuggestedFollows' -import {Text} from '../util/Text' +import {Text} from '../util/text/Text' import {useStores} from '../../../state' import {s} from '../../lib/styles' diff --git a/src/view/com/post-thread/PostRepostedBy.tsx b/src/view/com/post-thread/PostRepostedBy.tsx index 6328b34e7..0efdfe2e4 100644 --- a/src/view/com/post-thread/PostRepostedBy.tsx +++ b/src/view/com/post-thread/PostRepostedBy.tsx @@ -6,9 +6,9 @@ import { RepostedByViewItemModel, } from '../../../state/models/reposted-by-view' import {UserAvatar} from '../util/UserAvatar' -import {ErrorMessage} from '../util/ErrorMessage' +import {ErrorMessage} from '../util/error/ErrorMessage' import {Link} from '../util/Link' -import {Text} from '../util/Text' +import {Text} from '../util/text/Text' import {useStores} from '../../../state' import {s, colors} from '../../lib/styles' @@ -57,7 +57,6 @@ export const PostRepostedBy = observer(function PostRepostedBy({ return ( <View> <ErrorMessage - dark message={view.error} style={{margin: 6}} onPressTryAgain={onRefresh} diff --git a/src/view/com/post-thread/PostThread.tsx b/src/view/com/post-thread/PostThread.tsx index ecc0d48f5..8c22cc8b7 100644 --- a/src/view/com/post-thread/PostThread.tsx +++ b/src/view/com/post-thread/PostThread.tsx @@ -6,7 +6,7 @@ import { PostThreadViewPostModel, } from '../../../state/models/post-thread-view' import {PostThreadItem} from './PostThreadItem' -import {ErrorMessage} from '../util/ErrorMessage' +import {ErrorMessage} from '../util/error/ErrorMessage' export const PostThread = observer(function PostThread({ uri, @@ -57,7 +57,6 @@ export const PostThread = observer(function PostThread({ return ( <View> <ErrorMessage - dark message={view.error} style={{margin: 6}} onPressTryAgain={onRefresh} diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx index 1a0b0ff87..c39bebbbe 100644 --- a/src/view/com/post-thread/PostThreadItem.tsx +++ b/src/view/com/post-thread/PostThreadItem.tsx @@ -7,9 +7,9 @@ import {AppBskyFeedPost} from '@atproto/api' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {PostThreadViewPostModel} from '../../../state/models/post-thread-view' import {Link} from '../util/Link' -import {RichText} from '../util/RichText' -import {Text} from '../util/Text' -import {PostDropdownBtn} from '../util/DropdownBtn' +import {RichText} from '../util/text/RichText' +import {Text} from '../util/text/Text' +import {PostDropdownBtn} from '../util/forms/DropdownButton' import * as Toast from '../util/Toast' import {UserAvatar} from '../util/UserAvatar' import {s, colors} from '../../lib/styles' diff --git a/src/view/com/post-thread/PostVotedBy.tsx b/src/view/com/post-thread/PostVotedBy.tsx index f3773e47b..96a335919 100644 --- a/src/view/com/post-thread/PostVotedBy.tsx +++ b/src/view/com/post-thread/PostVotedBy.tsx @@ -6,8 +6,8 @@ import { VotesViewItemModel, } from '../../../state/models/votes-view' import {Link} from '../util/Link' -import {Text} from '../util/Text' -import {ErrorMessage} from '../util/ErrorMessage' +import {Text} from '../util/text/Text' +import {ErrorMessage} from '../util/error/ErrorMessage' import {UserAvatar} from '../util/UserAvatar' import {useStores} from '../../../state' import {s, colors} from '../../lib/styles' @@ -57,7 +57,6 @@ export const PostVotedBy = observer(function PostVotedBy({ return ( <View> <ErrorMessage - dark message={view.error} style={{margin: 6}} onPressTryAgain={onRefresh} diff --git a/src/view/com/post/Post.tsx b/src/view/com/post/Post.tsx index d9cc94315..a058acf8e 100644 --- a/src/view/com/post/Post.tsx +++ b/src/view/com/post/Post.tsx @@ -17,8 +17,8 @@ import {UserInfoText} from '../util/UserInfoText' import {PostMeta} from '../util/PostMeta' import {PostEmbeds} from '../util/PostEmbeds' import {PostCtrls} from '../util/PostCtrls' -import {Text} from '../util/Text' -import {RichText} from '../util/RichText' +import {Text} from '../util/text/Text' +import {RichText} from '../util/text/RichText' import * as Toast from '../util/Toast' import {UserAvatar} from '../util/UserAvatar' import {useStores} from '../../../state' diff --git a/src/view/com/post/PostText.tsx b/src/view/com/post/PostText.tsx index 90765a836..436768292 100644 --- a/src/view/com/post/PostText.tsx +++ b/src/view/com/post/PostText.tsx @@ -2,8 +2,8 @@ import React, {useState, useEffect} from 'react' import {observer} from 'mobx-react-lite' import {View} from 'react-native' import {LoadingPlaceholder} from '../util/LoadingPlaceholder' -import {ErrorMessage} from '../util/ErrorMessage' -import {Text} from '../util/Text' +import {ErrorMessage} from '../util/error/ErrorMessage' +import {Text} from '../util/text/Text' import {PostModel} from '../../../state/models/post' import {useStores} from '../../../state' diff --git a/src/view/com/posts/Feed.tsx b/src/view/com/posts/Feed.tsx index e34513794..02141acef 100644 --- a/src/view/com/posts/Feed.tsx +++ b/src/view/com/posts/Feed.tsx @@ -9,11 +9,11 @@ import { } from 'react-native' import {PostFeedLoadingPlaceholder} from '../util/LoadingPlaceholder' import {EmptyState} from '../util/EmptyState' -import {ErrorMessage} from '../util/ErrorMessage' +import {ErrorMessage} from '../util/error/ErrorMessage' import {FeedModel} from '../../../state/models/feed-view' import {FeedItem} from './FeedItem' import {ComposePrompt} from '../composer/Prompt' -import {OnScrollCb} from '../../lib/useOnMainScroll' +import {OnScrollCb} from '../../lib/hooks/useOnMainScroll' const COMPOSE_PROMPT_ITEM = {_reactKey: '__prompt__'} const EMPTY_FEED_ITEM = {_reactKey: '__empty__'} @@ -80,7 +80,6 @@ export const Feed = observer(function Feed({ {feed.isLoading && !data && <PostFeedLoadingPlaceholder />} {feed.hasError && ( <ErrorMessage - dark message={feed.error} style={{margin: 6}} onPressTryAgain={onPressTryAgain} diff --git a/src/view/com/posts/FeedItem.tsx b/src/view/com/posts/FeedItem.tsx index 9fd9d46b2..8670ed9a3 100644 --- a/src/view/com/posts/FeedItem.tsx +++ b/src/view/com/posts/FeedItem.tsx @@ -8,12 +8,12 @@ import {AppBskyFeedPost} from '@atproto/api' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FeedItemModel} from '../../../state/models/feed-view' import {Link} from '../util/Link' -import {Text} from '../util/Text' +import {Text} from '../util/text/Text' import {UserInfoText} from '../util/UserInfoText' import {PostMeta} from '../util/PostMeta' import {PostCtrls} from '../util/PostCtrls' import {PostEmbeds} from '../util/PostEmbeds' -import {RichText} from '../util/RichText' +import {RichText} from '../util/text/RichText' import * as Toast from '../util/Toast' import {UserAvatar} from '../util/UserAvatar' import {s, colors} from '../../lib/styles' diff --git a/src/view/com/profile/ProfileCard.tsx b/src/view/com/profile/ProfileCard.tsx index 1d5b93a4c..6ce5f29e5 100644 --- a/src/view/com/profile/ProfileCard.tsx +++ b/src/view/com/profile/ProfileCard.tsx @@ -1,7 +1,7 @@ import React from 'react' import {StyleSheet, TouchableOpacity, View} from 'react-native' import {Link} from '../util/Link' -import {Text} from '../util/Text' +import {Text} from '../util/text/Text' import {UserAvatar} from '../util/UserAvatar' import {s, colors} from '../../lib/styles' diff --git a/src/view/com/profile/ProfileFollowers.tsx b/src/view/com/profile/ProfileFollowers.tsx index 280173f9e..0d088d7a3 100644 --- a/src/view/com/profile/ProfileFollowers.tsx +++ b/src/view/com/profile/ProfileFollowers.tsx @@ -6,8 +6,8 @@ import { FollowerItem, } from '../../../state/models/user-followers-view' import {Link} from '../util/Link' -import {Text} from '../util/Text' -import {ErrorMessage} from '../util/ErrorMessage' +import {Text} from '../util/text/Text' +import {ErrorMessage} from '../util/error/ErrorMessage' import {UserAvatar} from '../util/UserAvatar' import {useStores} from '../../../state' import {s, colors} from '../../lib/styles' @@ -57,7 +57,6 @@ export const ProfileFollowers = observer(function ProfileFollowers({ return ( <View> <ErrorMessage - dark message={view.error} style={{margin: 6}} onPressTryAgain={onRefresh} diff --git a/src/view/com/profile/ProfileFollows.tsx b/src/view/com/profile/ProfileFollows.tsx index 5f8cdd68c..2cd471b05 100644 --- a/src/view/com/profile/ProfileFollows.tsx +++ b/src/view/com/profile/ProfileFollows.tsx @@ -7,8 +7,8 @@ import { } from '../../../state/models/user-follows-view' import {useStores} from '../../../state' import {Link} from '../util/Link' -import {Text} from '../util/Text' -import {ErrorMessage} from '../util/ErrorMessage' +import {Text} from '../util/text/Text' +import {ErrorMessage} from '../util/error/ErrorMessage' import {UserAvatar} from '../util/UserAvatar' import {s, colors} from '../../lib/styles' @@ -57,7 +57,6 @@ export const ProfileFollows = observer(function ProfileFollows({ return ( <View> <ErrorMessage - dark message={view.error} style={{margin: 6}} onPressTryAgain={onRefresh} diff --git a/src/view/com/profile/ProfileHeader.tsx b/src/view/com/profile/ProfileHeader.tsx index c9da0d96c..131c82b75 100644 --- a/src/view/com/profile/ProfileHeader.tsx +++ b/src/view/com/profile/ProfileHeader.tsx @@ -16,11 +16,11 @@ import { import {pluralize} from '../../../lib/strings' import {s, colors} from '../../lib/styles' import {getGradient} from '../../lib/asset-gen' -import {DropdownBtn, DropdownItem} from '../util/DropdownBtn' +import {DropdownButton, DropdownItem} from '../util/forms/DropdownButton' import * as Toast from '../util/Toast' import {LoadingPlaceholder} from '../util/LoadingPlaceholder' -import {Text} from '../util/Text' -import {RichText} from '../util/RichText' +import {Text} from '../util/text/Text' +import {RichText} from '../util/text/RichText' import {UserAvatar} from '../util/UserAvatar' import {UserBanner} from '../util/UserBanner' import {UserInfoText} from '../util/UserInfoText' @@ -195,11 +195,11 @@ export const ProfileHeader = observer(function ProfileHeader({ </> )} {dropdownItems?.length ? ( - <DropdownBtn + <DropdownButton items={dropdownItems} style={[styles.btn, styles.secondaryBtn]}> <FontAwesomeIcon icon="ellipsis" style={[s.gray5]} /> - </DropdownBtn> + </DropdownButton> ) : undefined} </View> <View style={styles.displayNameLine}> diff --git a/src/view/com/profile/ProfileMembers.tsx b/src/view/com/profile/ProfileMembers.tsx index 251ece41a..0e34865b9 100644 --- a/src/view/com/profile/ProfileMembers.tsx +++ b/src/view/com/profile/ProfileMembers.tsx @@ -3,7 +3,7 @@ import {observer} from 'mobx-react-lite' import {ActivityIndicator, FlatList, View} from 'react-native' import {MembersViewModel, MemberItem} from '../../../state/models/members-view' import {ProfileCard} from './ProfileCard' -import {ErrorMessage} from '../util/ErrorMessage' +import {ErrorMessage} from '../util/error/ErrorMessage' import {useStores} from '../../../state' export const ProfileMembers = observer(function ProfileMembers({ @@ -49,7 +49,6 @@ export const ProfileMembers = observer(function ProfileMembers({ return ( <View> <ErrorMessage - dark message={view.error} style={{margin: 6}} onPressTryAgain={onRefresh} diff --git a/src/view/com/util/EmptyState.tsx b/src/view/com/util/EmptyState.tsx index 8d98807e3..d9a317fae 100644 --- a/src/view/com/util/EmptyState.tsx +++ b/src/view/com/util/EmptyState.tsx @@ -2,9 +2,9 @@ import React from 'react' import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native' import {IconProp} from '@fortawesome/fontawesome-svg-core' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {Text} from './Text' +import {Text} from './text/Text' import {UserGroupIcon} from '../../lib/icons' -import {colors} from '../../lib/styles' +import {usePalette} from '../../lib/hooks/usePalette' export function EmptyState({ icon, @@ -15,16 +15,23 @@ export function EmptyState({ message: string style?: StyleProp<ViewStyle> }) { + const pal = usePalette('default') return ( <View style={[styles.container, style]}> <View style={styles.iconContainer}> {icon === 'user-group' ? ( <UserGroupIcon size="64" style={styles.icon} /> ) : ( - <FontAwesomeIcon icon={icon} size={64} style={styles.icon} /> + <FontAwesomeIcon + icon={icon} + size={64} + style={[styles.icon, pal.textLight]} + /> )} </View> - <Text style={styles.text}>{message}</Text> + <Text type="body1" style={[pal.textLight, styles.text]}> + {message} + </Text> </View> ) } @@ -40,12 +47,9 @@ const styles = StyleSheet.create({ icon: { marginLeft: 'auto', marginRight: 'auto', - color: colors.gray3, }, text: { textAlign: 'center', - color: colors.gray5, paddingTop: 16, - fontSize: 16, }, }) diff --git a/src/view/com/util/ErrorMessage.tsx b/src/view/com/util/ErrorMessage.tsx deleted file mode 100644 index b87b77baa..000000000 --- a/src/view/com/util/ErrorMessage.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import React from 'react' -import { - StyleSheet, - TouchableOpacity, - StyleProp, - View, - ViewStyle, -} from 'react-native' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import LinearGradient from 'react-native-linear-gradient' -import {Text} from './Text' -import {colors, gradients} from '../../lib/styles' - -export function ErrorMessage({ - message, - numberOfLines, - dark, - style, - onPressTryAgain, -}: { - message: string - numberOfLines?: number - dark?: boolean - style?: StyleProp<ViewStyle> - onPressTryAgain?: () => void -}) { - const inner = ( - <> - <View style={[styles.errorIcon, dark ? styles.darkErrorIcon : undefined]}> - <FontAwesomeIcon - icon="exclamation" - style={{color: dark ? colors.red3 : colors.white}} - size={16} - /> - </View> - <Text - style={[styles.message, dark ? styles.darkMessage : undefined]} - numberOfLines={numberOfLines}> - {message} - </Text> - {onPressTryAgain && ( - <TouchableOpacity style={styles.btn} onPress={onPressTryAgain}> - <FontAwesomeIcon - icon="arrows-rotate" - style={{color: dark ? colors.white : colors.red4}} - size={16} - /> - </TouchableOpacity> - )} - </> - ) - if (dark) { - return ( - <LinearGradient - colors={[gradients.error.start, gradients.error.end]} - start={{x: 0.5, y: 0}} - end={{x: 1, y: 1}} - style={[styles.outer, style]}> - {inner} - </LinearGradient> - ) - } - return <View style={[styles.outer, style]}>{inner}</View> -} - -const styles = StyleSheet.create({ - outer: { - flexDirection: 'row', - alignItems: 'center', - backgroundColor: colors.red1, - borderWidth: 1, - borderColor: colors.red3, - borderRadius: 6, - paddingVertical: 8, - paddingHorizontal: 8, - }, - errorIcon: { - backgroundColor: colors.red4, - borderRadius: 12, - width: 24, - height: 24, - alignItems: 'center', - justifyContent: 'center', - marginRight: 8, - }, - darkErrorIcon: { - backgroundColor: colors.white, - }, - message: { - flex: 1, - color: colors.red4, - paddingRight: 10, - }, - darkMessage: { - color: colors.white, - fontWeight: '600', - }, - btn: { - paddingHorizontal: 4, - paddingVertical: 4, - }, -}) diff --git a/src/view/com/util/FloatingActionButton.tsx b/src/view/com/util/FloatingActionButton.tsx deleted file mode 100644 index 21c4fba6c..000000000 --- a/src/view/com/util/FloatingActionButton.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import React from 'react' -import { - GestureResponderEvent, - StyleSheet, - TouchableWithoutFeedback, - View, -} from 'react-native' -import LinearGradient from 'react-native-linear-gradient' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {IconProp} from '@fortawesome/fontawesome-svg-core' -import {colors, gradients} from '../../lib/styles' -import * as zIndex from '../../lib/z-index' - -type OnPress = ((event: GestureResponderEvent) => void) | undefined -export function FAB({icon, onPress}: {icon: IconProp; onPress: OnPress}) { - return ( - <TouchableWithoutFeedback onPress={onPress}> - <View style={styles.outer}> - <LinearGradient - colors={[gradients.purple.start, gradients.purple.end]} - start={{x: 0, y: 0}} - end={{x: 1, y: 1}} - style={styles.inner}> - <FontAwesomeIcon - size={24} - icon={icon} - color={colors.white} - style={styles.icon} - /> - </LinearGradient> - </View> - </TouchableWithoutFeedback> - ) -} - -const styles = StyleSheet.create({ - outer: { - position: 'absolute', - zIndex: zIndex.FAB, - right: 22, - bottom: 14, - width: 60, - height: 60, - borderRadius: 30, - shadowColor: '#000', - shadowOpacity: 0.3, - shadowOffset: {width: 0, height: 1}, - }, - inner: { - width: 60, - height: 60, - borderRadius: 30, - justifyContent: 'center', - alignItems: 'center', - }, - icon: {}, -}) diff --git a/src/view/com/util/Link.tsx b/src/view/com/util/Link.tsx index 2bb553575..05573d999 100644 --- a/src/view/com/util/Link.tsx +++ b/src/view/com/util/Link.tsx @@ -9,7 +9,8 @@ import { View, ViewStyle, } from 'react-native' -import {Text} from './Text' +import {Text} from './text/Text' +import {TypographyVariant} from '../../lib/ThemeContext' import {useStores, RootStoreModel} from '../../../state' import {convertBskyAppUrlIfNeeded} from '../../../lib/strings' @@ -57,14 +58,14 @@ export const Link = observer(function Link({ }) export const TextLink = observer(function Link({ + type = 'body1', style, href, - title, text, }: { + type: TypographyVariant style?: StyleProp<TextStyle> href: string - title?: string text: string }) { const store = useStores() @@ -75,7 +76,7 @@ export const TextLink = observer(function Link({ handleLink(store, href, true) } return ( - <Text style={style} onPress={onPress} onLongPress={onLongPress}> + <Text type={type} style={style} onPress={onPress} onLongPress={onLongPress}> {text} </Text> ) diff --git a/src/view/com/util/LoadingPlaceholder.tsx b/src/view/com/util/LoadingPlaceholder.tsx index 9c2d0398f..15488167f 100644 --- a/src/view/com/util/LoadingPlaceholder.tsx +++ b/src/view/com/util/LoadingPlaceholder.tsx @@ -3,6 +3,7 @@ import {StyleSheet, StyleProp, View, ViewStyle} from 'react-native' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {UpIcon} from '../../lib/icons' import {s, colors} from '../../lib/styles' +import {useTheme} from '../../lib/ThemeContext' export function LoadingPlaceholder({ width, @@ -13,13 +14,14 @@ export function LoadingPlaceholder({ height: string | number style?: StyleProp<ViewStyle> }) { + const theme = useTheme() return ( <View style={[ { width, height, - backgroundColor: '#e7e9ea', + backgroundColor: theme.palette.default.backgroundLight, borderRadius: 6, overflow: 'hidden', }, @@ -29,7 +31,7 @@ export function LoadingPlaceholder({ style={{ width, height, - backgroundColor: '#e7e9ea', + backgroundColor: theme.palette.default.backgroundLight, }} /> </View> @@ -41,6 +43,7 @@ export function PostLoadingPlaceholder({ }: { style?: StyleProp<ViewStyle> }) { + const theme = useTheme() return ( <View style={[styles.post, style]}> <LoadingPlaceholder width={50} height={50} style={styles.avatar} /> @@ -52,16 +55,24 @@ export function PostLoadingPlaceholder({ <View style={s.flexRow}> <View style={s.flex1}> <FontAwesomeIcon - style={s.gray3} + style={{color: theme.palette.default.icon}} icon={['far', 'comment']} size={14} /> </View> <View style={s.flex1}> - <FontAwesomeIcon style={s.gray3} icon="retweet" size={18} /> + <FontAwesomeIcon + style={{color: theme.palette.default.icon}} + icon="retweet" + size={18} + /> </View> <View style={s.flex1}> - <UpIcon style={s.gray3} size={17} strokeWidth={1.7} /> + <UpIcon + style={{color: theme.palette.default.icon}} + size={17} + strokeWidth={1.7} + /> </View> <View style={s.flex1}></View> </View> @@ -125,8 +136,6 @@ export function NotificationFeedLoadingPlaceholder() { const styles = StyleSheet.create({ post: { flexDirection: 'row', - backgroundColor: colors.white, - borderRadius: 6, padding: 10, margin: 1, }, @@ -135,8 +144,6 @@ const styles = StyleSheet.create({ marginRight: 10, }, notification: { - backgroundColor: colors.white, - borderRadius: 6, padding: 10, paddingLeft: 46, margin: 1, diff --git a/src/view/com/util/Picker.tsx b/src/view/com/util/Picker.tsx index 84a627b6d..208ec0492 100644 --- a/src/view/com/util/Picker.tsx +++ b/src/view/com/util/Picker.tsx @@ -1,3 +1,5 @@ +// TODO: replaceme with something in the design system + import React, {useRef} from 'react' import { StyleProp, @@ -13,7 +15,7 @@ import { FontAwesomeIconStyle, } from '@fortawesome/react-native-fontawesome' import RootSiblings from 'react-native-root-siblings' -import {Text} from './Text' +import {Text} from './text/Text' import {colors} from '../../lib/styles' interface PickerItem { diff --git a/src/view/com/util/PostCtrls.tsx b/src/view/com/util/PostCtrls.tsx index 264210768..ac10e92fe 100644 --- a/src/view/com/util/PostCtrls.tsx +++ b/src/view/com/util/PostCtrls.tsx @@ -2,10 +2,10 @@ import React from 'react' import {Animated, StyleSheet, TouchableOpacity, View} from 'react-native' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import ReactNativeHapticFeedback from 'react-native-haptic-feedback' -import {Text} from './Text' +import {Text} from './text/Text' import {UpIcon, UpIconSolid} from '../../lib/icons' import {s, colors} from '../../lib/styles' -import {useAnimatedValue} from '../../lib/useAnimatedValue' +import {useAnimatedValue} from '../../lib/hooks/useAnimatedValue' interface PostCtrlsOpts { big?: boolean diff --git a/src/view/com/util/PostEmbeds.tsx b/src/view/com/util/PostEmbeds.tsx index 1c980465a..839110a21 100644 --- a/src/view/com/util/PostEmbeds.tsx +++ b/src/view/com/util/PostEmbeds.tsx @@ -2,7 +2,7 @@ import React from 'react' import {ImageStyle, StyleSheet, StyleProp, View, ViewStyle} from 'react-native' import {AppBskyEmbedImages, AppBskyEmbedExternal} from '@atproto/api' import {Link} from '../util/Link' -import {Text} from '../util/Text' +import {Text} from './text/Text' import {colors} from '../../lib/styles' import {AutoSizedImage} from './images/AutoSizedImage' import {ImagesLightbox} from '../../../state/models/shell-ui' diff --git a/src/view/com/util/PostMeta.tsx b/src/view/com/util/PostMeta.tsx index 77dfbb485..fae3a4c83 100644 --- a/src/view/com/util/PostMeta.tsx +++ b/src/view/com/util/PostMeta.tsx @@ -2,8 +2,8 @@ import React from 'react' import {StyleSheet, View} from 'react-native' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {Link} from '../util/Link' -import {Text} from '../util/Text' -import {PostDropdownBtn} from '../util/DropdownBtn' +import {Text} from './text/Text' +import {PostDropdownBtn} from './forms/DropdownButton' import {s} from '../../lib/styles' import {ago} from '../../../lib/strings' diff --git a/src/view/com/util/Selector.tsx b/src/view/com/util/Selector.tsx index 954360b32..211c5b902 100644 --- a/src/view/com/util/Selector.tsx +++ b/src/view/com/util/Selector.tsx @@ -5,7 +5,7 @@ import { TouchableWithoutFeedback, View, } from 'react-native' -import {Text} from './Text' +import {Text} from './text/Text' import {colors} from '../../lib/styles' interface Layout { diff --git a/src/view/com/util/Text.tsx b/src/view/com/util/Text.tsx deleted file mode 100644 index acf7589e0..000000000 --- a/src/view/com/util/Text.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react' -import {Text as RNText, TextProps} from 'react-native' -import {s} from '../../lib/styles' - -export function Text({ - children, - style, - ...props -}: React.PropsWithChildren<TextProps>) { - return ( - <RNText style={[s.black, style]} {...props}> - {children} - </RNText> - ) -} diff --git a/src/view/com/util/UserInfoText.tsx b/src/view/com/util/UserInfoText.tsx index cdd1f4d91..f5ed07d63 100644 --- a/src/view/com/util/UserInfoText.tsx +++ b/src/view/com/util/UserInfoText.tsx @@ -2,7 +2,7 @@ import React, {useState, useEffect} from 'react' import {AppBskyActorGetProfile as GetProfile} from '@atproto/api' import {StyleProp, TextStyle} from 'react-native' import {Link} from './Link' -import {Text} from './Text' +import {Text} from './text/Text' import {LoadingPlaceholder} from './LoadingPlaceholder' import {useStores} from '../../../state' diff --git a/src/view/com/util/ViewHeader.tsx b/src/view/com/util/ViewHeader.tsx index e14c2412d..c6eaba5dd 100644 --- a/src/view/com/util/ViewHeader.tsx +++ b/src/view/com/util/ViewHeader.tsx @@ -8,7 +8,7 @@ import { } from 'react-native' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {UserAvatar} from './UserAvatar' -import {Text} from './Text' +import {Text} from './text/Text' import {s, colors} from '../../lib/styles' import {MagnifyingGlassIcon} from '../../lib/icons' import {useStores} from '../../../state' diff --git a/src/view/com/util/ViewSelector.tsx b/src/view/com/util/ViewSelector.tsx index e436e41b2..a29ee9d26 100644 --- a/src/view/com/util/ViewSelector.tsx +++ b/src/view/com/util/ViewSelector.tsx @@ -7,8 +7,8 @@ import { } from 'react-native' import {Selector} from './Selector' import {HorzSwipe} from './gestures/HorzSwipe' -import {useAnimatedValue} from '../../lib/useAnimatedValue' -import {OnScrollCb} from '../../lib/useOnMainScroll' +import {useAnimatedValue} from '../../lib/hooks/useAnimatedValue' +import {OnScrollCb} from '../../lib/hooks/useOnMainScroll' const HEADER_ITEM = {_reactKey: '__header__'} const SELECTOR_ITEM = {_reactKey: '__selector__'} diff --git a/src/view/com/util/error/ErrorMessage.tsx b/src/view/com/util/error/ErrorMessage.tsx new file mode 100644 index 000000000..905268d3e --- /dev/null +++ b/src/view/com/util/error/ErrorMessage.tsx @@ -0,0 +1,76 @@ +import React from 'react' +import { + StyleSheet, + TouchableOpacity, + StyleProp, + View, + ViewStyle, +} from 'react-native' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {Text} from '../text/Text' +import {colors} from '../../../lib/styles' +import {useTheme} from '../../../lib/ThemeContext' +import {usePalette} from '../../../lib/hooks/usePalette' + +export function ErrorMessage({ + message, + numberOfLines, + style, + onPressTryAgain, +}: { + message: string + numberOfLines?: number + style?: StyleProp<ViewStyle> + onPressTryAgain?: () => void +}) { + const theme = useTheme() + const pal = usePalette('error') + return ( + <View style={[styles.outer, pal.view, style]}> + <View + style={[styles.errorIcon, {backgroundColor: theme.palette.error.icon}]}> + <FontAwesomeIcon icon="exclamation" style={pal.text} size={16} /> + </View> + <Text + type="body2" + style={[styles.message, pal.text]} + numberOfLines={numberOfLines}> + {message} + </Text> + {onPressTryAgain && ( + <TouchableOpacity style={styles.btn} onPress={onPressTryAgain}> + <FontAwesomeIcon + icon="arrows-rotate" + style={{color: theme.palette.error.icon}} + size={18} + /> + </TouchableOpacity> + )} + </View> + ) +} + +const styles = StyleSheet.create({ + outer: { + flexDirection: 'row', + alignItems: 'center', + paddingVertical: 8, + paddingHorizontal: 8, + }, + errorIcon: { + borderRadius: 12, + width: 24, + height: 24, + alignItems: 'center', + justifyContent: 'center', + marginRight: 8, + }, + message: { + flex: 1, + paddingRight: 10, + }, + btn: { + paddingHorizontal: 4, + paddingVertical: 4, + }, +}) diff --git a/src/view/com/util/ErrorScreen.tsx b/src/view/com/util/error/ErrorScreen.tsx index d0e1e2755..6db54a9f2 100644 --- a/src/view/com/util/ErrorScreen.tsx +++ b/src/view/com/util/error/ErrorScreen.tsx @@ -1,8 +1,10 @@ import React from 'react' import {StyleSheet, TouchableOpacity, View} from 'react-native' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {Text} from './Text' -import {colors} from '../../lib/styles' +import {Text} from '../text/Text' +import {colors} from '../../../lib/styles' +import {useTheme} from '../../../lib/ThemeContext' +import {usePalette} from '../../../lib/hooks/usePalette' export function ErrorScreen({ title, @@ -15,10 +17,16 @@ export function ErrorScreen({ details?: string onPressTryAgain?: () => void }) { + const theme = useTheme() + const pal = usePalette('error') return ( - <View style={styles.outer}> + <View style={[styles.outer, pal.view]}> <View style={styles.errorIconContainer}> - <View style={styles.errorIcon}> + <View + style={[ + styles.errorIcon, + {backgroundColor: theme.palette.error.icon}, + ]}> <FontAwesomeIcon icon="exclamation" style={{color: colors.white}} @@ -26,18 +34,30 @@ export function ErrorScreen({ /> </View> </View> - <Text style={styles.title}>{title}</Text> - <Text style={styles.message}>{message}</Text> - {details && <Text style={styles.details}>{details}</Text>} + <Text type="h3" style={[styles.title, pal.text]}> + {title} + </Text> + <Text style={[styles.message, pal.textLight]}>{message}</Text> + {details && ( + <Text + type="body2" + style={[ + styles.details, + pal.textInverted, + {backgroundColor: theme.palette.default.background}, + ]}> + {details} + </Text> + )} {onPressTryAgain && ( <View style={styles.btnContainer}> - <TouchableOpacity style={styles.btn} onPress={onPressTryAgain}> - <FontAwesomeIcon - icon="arrows-rotate" - style={{color: colors.white}} - size={16} - /> - <Text style={styles.btnText}>Try again</Text> + <TouchableOpacity + style={[styles.btn, {backgroundColor: theme.palette.error.icon}]} + onPress={onPressTryAgain}> + <FontAwesomeIcon icon="arrows-rotate" style={pal.text} size={16} /> + <Text type="button" style={[styles.btnText, pal.text]}> + Try again + </Text> </TouchableOpacity> </View> )} @@ -48,32 +68,19 @@ export function ErrorScreen({ const styles = StyleSheet.create({ outer: { flex: 1, - backgroundColor: colors.red1, - borderWidth: 1, - borderColor: colors.red3, - borderRadius: 6, paddingVertical: 30, paddingHorizontal: 14, - margin: 10, }, title: { textAlign: 'center', - color: colors.red4, - fontSize: 24, marginBottom: 10, }, message: { textAlign: 'center', - color: colors.red4, marginBottom: 20, }, details: { textAlign: 'center', - color: colors.black, - backgroundColor: colors.white, - borderWidth: 1, - borderColor: colors.gray5, - borderRadius: 6, paddingVertical: 10, paddingHorizontal: 14, overflow: 'hidden', @@ -85,23 +92,17 @@ const styles = StyleSheet.create({ btn: { flexDirection: 'row', alignItems: 'center', - backgroundColor: colors.red4, - borderRadius: 6, paddingHorizontal: 16, paddingVertical: 10, }, btnText: { marginLeft: 5, - color: colors.white, - fontSize: 16, - fontWeight: 'bold', }, errorIconContainer: { alignItems: 'center', marginBottom: 10, }, errorIcon: { - backgroundColor: colors.red4, borderRadius: 30, width: 50, height: 50, diff --git a/src/view/com/util/forms/Button.tsx b/src/view/com/util/forms/Button.tsx new file mode 100644 index 000000000..b5c4da19d --- /dev/null +++ b/src/view/com/util/forms/Button.tsx @@ -0,0 +1,120 @@ +import React from 'react' +import { + StyleProp, + StyleSheet, + TextStyle, + TouchableOpacity, + ViewStyle, +} from 'react-native' +import {Text} from '../text/Text' +import {useTheme} from '../../../lib/ThemeContext' +import {choose} from '../../../../lib/functions' + +export type ButtonType = + | 'primary' + | 'secondary' + | 'inverted' + | 'primary-outline' + | 'secondary-outline' + | 'primary-light' + | 'secondary-light' + | 'default-light' + +export function Button({ + type = 'primary', + label, + style, + onPress, + children, +}: React.PropsWithChildren<{ + type?: ButtonType + label?: string + style?: StyleProp<ViewStyle> + onPress?: () => void +}>) { + const theme = useTheme() + const outerStyle = choose<ViewStyle, Record<ButtonType, ViewStyle>>(type, { + primary: { + backgroundColor: theme.palette.primary.background, + }, + secondary: { + backgroundColor: theme.palette.secondary.background, + }, + inverted: { + backgroundColor: theme.palette.inverted.background, + }, + 'primary-outline': { + backgroundColor: theme.palette.default.background, + borderWidth: 1, + borderColor: theme.palette.primary.border, + }, + 'secondary-outline': { + backgroundColor: theme.palette.default.background, + borderWidth: 1, + borderColor: theme.palette.secondary.border, + }, + 'primary-light': { + backgroundColor: theme.palette.default.background, + }, + 'secondary-light': { + backgroundColor: theme.palette.default.background, + }, + 'default-light': { + backgroundColor: theme.palette.default.background, + }, + }) + const labelStyle = choose<TextStyle, Record<ButtonType, TextStyle>>(type, { + primary: { + color: theme.palette.primary.text, + fontWeight: theme.palette.primary.isLowContrast ? '500' : undefined, + }, + secondary: { + color: theme.palette.secondary.text, + fontWeight: theme.palette.secondary.isLowContrast ? '500' : undefined, + }, + inverted: { + color: theme.palette.inverted.text, + fontWeight: theme.palette.inverted.isLowContrast ? '500' : undefined, + }, + 'primary-outline': { + color: theme.palette.primary.textInverted, + fontWeight: theme.palette.primary.isLowContrast ? '500' : undefined, + }, + 'secondary-outline': { + color: theme.palette.secondary.textInverted, + fontWeight: theme.palette.secondary.isLowContrast ? '500' : undefined, + }, + 'primary-light': { + color: theme.palette.primary.textInverted, + fontWeight: theme.palette.primary.isLowContrast ? '500' : undefined, + }, + 'secondary-light': { + color: theme.palette.secondary.textInverted, + fontWeight: theme.palette.secondary.isLowContrast ? '500' : undefined, + }, + 'default-light': { + color: theme.palette.default.text, + fontWeight: theme.palette.default.isLowContrast ? '500' : undefined, + }, + }) + return ( + <TouchableOpacity + style={[outerStyle, styles.outer, style]} + onPress={onPress}> + {label ? ( + <Text type="button" style={[labelStyle]}> + {label} + </Text> + ) : ( + children + )} + </TouchableOpacity> + ) +} + +const styles = StyleSheet.create({ + outer: { + paddingHorizontal: 10, + paddingVertical: 8, + }, +}) diff --git a/src/view/com/util/DropdownBtn.tsx b/src/view/com/util/forms/DropdownButton.tsx index 3c6421934..c81ccf6c5 100644 --- a/src/view/com/util/DropdownBtn.tsx +++ b/src/view/com/util/forms/DropdownButton.tsx @@ -11,12 +11,13 @@ import { import {IconProp} from '@fortawesome/fontawesome-svg-core' import RootSiblings from 'react-native-root-siblings' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {Text} from './Text' -import {colors} from '../../lib/styles' -import {toShareUrl} from '../../../lib/strings' -import {useStores} from '../../../state' -import {ReportPostModal, ConfirmModal} from '../../../state/models/shell-ui' -import {TABS_ENABLED} from '../../../build-flags' +import {Text} from '../text/Text' +import {Button, ButtonType} from './Button' +import {colors} from '../../../lib/styles' +import {toShareUrl} from '../../../../lib/strings' +import {useStores} from '../../../../state' +import {ReportPostModal, ConfirmModal} from '../../../../state/models/shell-ui' +import {TABS_ENABLED} from '../../../../build-flags' const HITSLOP = {left: 10, top: 10, right: 10, bottom: 10} @@ -26,14 +27,20 @@ export interface DropdownItem { onPress: () => void } -export function DropdownBtn({ +export type DropdownButtonType = ButtonType | 'bare' + +export function DropdownButton({ + type = 'bare', style, items, + label, menuWidth, children, }: { + type: DropdownButtonType style?: StyleProp<ViewStyle> items: DropdownItem[] + label?: string menuWidth?: number children?: React.ReactNode }) { @@ -62,14 +69,23 @@ export function DropdownBtn({ ) } + if (type === 'bare') { + return ( + <TouchableOpacity + style={style} + onPress={onPress} + hitSlop={HITSLOP} + ref={ref}> + {children} + </TouchableOpacity> + ) + } return ( - <TouchableOpacity - style={style} - onPress={onPress} - hitSlop={HITSLOP} - ref={ref}> - {children} - </TouchableOpacity> + <View ref={ref}> + <Button onPress={onPress} style={style} label={label}> + {children} + </Button> + </View> ) } @@ -77,7 +93,6 @@ export function PostDropdownBtn({ style, children, itemHref, - itemTitle, isAuthor, onCopyPostText, onDeletePost, @@ -141,9 +156,9 @@ export function PostDropdownBtn({ ].filter(Boolean) as DropdownItem[] return ( - <DropdownBtn style={style} items={dropdownItems} menuWidth={200}> + <DropdownButton style={style} items={dropdownItems} menuWidth={200}> {children} - </DropdownBtn> + </DropdownButton> ) } diff --git a/src/view/com/util/forms/RadioButton.tsx b/src/view/com/util/forms/RadioButton.tsx index 9da404bea..81489c447 100644 --- a/src/view/com/util/forms/RadioButton.tsx +++ b/src/view/com/util/forms/RadioButton.tsx @@ -1,24 +1,126 @@ import React from 'react' -import {StyleSheet, TouchableOpacity, View} from 'react-native' -import {Text} from '../Text' -import {colors} from '../../../lib/styles' +import {StyleProp, StyleSheet, TextStyle, View, ViewStyle} from 'react-native' +import {Text} from '../text/Text' +import {Button, ButtonType} from './Button' +import {useTheme} from '../../../lib/ThemeContext' +import {choose} from '../../../../lib/functions' export function RadioButton({ + type = 'default-light', label, isSelected, + style, onPress, }: { + type?: ButtonType label: string isSelected: boolean + style?: StyleProp<ViewStyle> onPress: () => void }) { + const theme = useTheme() + const circleStyle = choose<TextStyle, Record<ButtonType, TextStyle>>(type, { + primary: { + borderColor: theme.palette.primary.text, + }, + secondary: { + borderColor: theme.palette.secondary.text, + }, + inverted: { + borderColor: theme.palette.inverted.text, + }, + 'primary-outline': { + borderColor: theme.palette.primary.border, + }, + 'secondary-outline': { + borderColor: theme.palette.secondary.border, + }, + 'primary-light': { + borderColor: theme.palette.primary.border, + }, + 'secondary-light': { + borderColor: theme.palette.secondary.border, + }, + 'default-light': { + borderColor: theme.palette.default.border, + }, + }) + const circleFillStyle = choose<TextStyle, Record<ButtonType, TextStyle>>( + type, + { + primary: { + backgroundColor: theme.palette.primary.text, + }, + secondary: { + backgroundColor: theme.palette.secondary.text, + }, + inverted: { + backgroundColor: theme.palette.inverted.text, + }, + 'primary-outline': { + backgroundColor: theme.palette.primary.background, + }, + 'secondary-outline': { + backgroundColor: theme.palette.secondary.background, + }, + 'primary-light': { + backgroundColor: theme.palette.primary.background, + }, + 'secondary-light': { + backgroundColor: theme.palette.secondary.background, + }, + 'default-light': { + backgroundColor: theme.palette.primary.background, + }, + }, + ) + const labelStyle = choose<TextStyle, Record<ButtonType, TextStyle>>(type, { + primary: { + color: theme.palette.primary.text, + fontWeight: theme.palette.primary.isLowContrast ? '500' : undefined, + }, + secondary: { + color: theme.palette.secondary.text, + fontWeight: theme.palette.secondary.isLowContrast ? '500' : undefined, + }, + inverted: { + color: theme.palette.inverted.text, + fontWeight: theme.palette.inverted.isLowContrast ? '500' : undefined, + }, + 'primary-outline': { + color: theme.palette.primary.textInverted, + fontWeight: theme.palette.primary.isLowContrast ? '500' : undefined, + }, + 'secondary-outline': { + color: theme.palette.secondary.textInverted, + fontWeight: theme.palette.secondary.isLowContrast ? '500' : undefined, + }, + 'primary-light': { + color: theme.palette.primary.textInverted, + fontWeight: theme.palette.primary.isLowContrast ? '500' : undefined, + }, + 'secondary-light': { + color: theme.palette.secondary.textInverted, + fontWeight: theme.palette.secondary.isLowContrast ? '500' : undefined, + }, + 'default-light': { + color: theme.palette.default.text, + fontWeight: theme.palette.default.isLowContrast ? '500' : undefined, + }, + }) return ( - <TouchableOpacity style={styles.outer} onPress={onPress}> - <View style={styles.circle}> - {isSelected ? <View style={styles.circleFill} /> : undefined} + <Button type={type} onPress={onPress} style={style}> + <View style={styles.outer}> + <View style={[circleStyle, styles.circle]}> + {isSelected ? ( + <View style={[circleFillStyle, styles.circleFill]} /> + ) : undefined} + </View> + <Text type="button" style={[labelStyle, styles.label]}> + {label} + </Text> </View> - <Text style={styles.label}>{label}</Text> - </TouchableOpacity> + </Button> ) } @@ -26,30 +128,21 @@ const styles = StyleSheet.create({ outer: { flexDirection: 'row', alignItems: 'center', - marginBottom: 5, - borderRadius: 8, - borderWidth: 1, - borderColor: colors.gray2, - paddingHorizontal: 10, - paddingVertical: 8, }, circle: { - width: 30, - height: 30, + width: 26, + height: 26, borderRadius: 15, padding: 4, borderWidth: 1, - borderColor: colors.gray3, marginRight: 10, }, circleFill: { - width: 20, - height: 20, + width: 16, + height: 16, borderRadius: 10, - backgroundColor: colors.blue3, }, label: { flex: 1, - fontSize: 17, }, }) diff --git a/src/view/com/util/forms/RadioGroup.tsx b/src/view/com/util/forms/RadioGroup.tsx index 6684cde5c..9abc2345f 100644 --- a/src/view/com/util/forms/RadioGroup.tsx +++ b/src/view/com/util/forms/RadioGroup.tsx @@ -1,6 +1,7 @@ import React, {useState} from 'react' import {View} from 'react-native' import {RadioButton} from './RadioButton' +import {ButtonType} from './Button' export interface RadioGroupItem { label: string @@ -8,22 +9,28 @@ export interface RadioGroupItem { } export function RadioGroup({ + type, items, + initialSelection = '', onSelect, }: { + type?: ButtonType items: RadioGroupItem[] + initialSelection?: string onSelect: (key: string) => void }) { - const [selection, setSelection] = useState<string>('') + const [selection, setSelection] = useState<string>(initialSelection) const onSelectInner = (key: string) => { setSelection(key) onSelect(key) } return ( <View> - {items.map(item => ( + {items.map((item, i) => ( <RadioButton key={item.key} + style={i !== 0 ? {marginTop: 2} : undefined} + type={type} label={item.label} isSelected={item.key === selection} onPress={() => onSelectInner(item.key)} diff --git a/src/view/com/util/forms/ToggleButton.tsx b/src/view/com/util/forms/ToggleButton.tsx new file mode 100644 index 000000000..77e8fa203 --- /dev/null +++ b/src/view/com/util/forms/ToggleButton.tsx @@ -0,0 +1,165 @@ +import React from 'react' +import {StyleProp, StyleSheet, TextStyle, View, ViewStyle} from 'react-native' +import {Text} from '../text/Text' +import {Button, ButtonType} from './Button' +import {useTheme} from '../../../lib/ThemeContext' +import {choose} from '../../../../lib/functions' +import {colors} from '../../../lib/styles' + +export function ToggleButton({ + type = 'default-light', + label, + isSelected, + style, + onPress, +}: { + type?: ButtonType + label: string + isSelected: boolean + style?: StyleProp<ViewStyle> + onPress: () => void +}) { + const theme = useTheme() + const circleStyle = choose<TextStyle, Record<ButtonType, TextStyle>>(type, { + primary: { + borderColor: theme.palette.primary.text, + }, + secondary: { + borderColor: theme.palette.secondary.text, + }, + inverted: { + borderColor: theme.palette.inverted.text, + }, + 'primary-outline': { + borderColor: theme.palette.primary.border, + }, + 'secondary-outline': { + borderColor: theme.palette.secondary.border, + }, + 'primary-light': { + borderColor: theme.palette.primary.border, + }, + 'secondary-light': { + borderColor: theme.palette.secondary.border, + }, + 'default-light': { + borderColor: theme.palette.default.border, + }, + }) + const circleFillStyle = choose<TextStyle, Record<ButtonType, TextStyle>>( + type, + { + primary: { + backgroundColor: theme.palette.primary.text, + opacity: isSelected ? 1 : 0.33, + }, + secondary: { + backgroundColor: theme.palette.secondary.text, + opacity: isSelected ? 1 : 0.33, + }, + inverted: { + backgroundColor: theme.palette.inverted.text, + opacity: isSelected ? 1 : 0.33, + }, + 'primary-outline': { + backgroundColor: theme.palette.primary.background, + opacity: isSelected ? 1 : 0.5, + }, + 'secondary-outline': { + backgroundColor: theme.palette.secondary.background, + opacity: isSelected ? 1 : 0.5, + }, + 'primary-light': { + backgroundColor: theme.palette.primary.background, + opacity: isSelected ? 1 : 0.5, + }, + 'secondary-light': { + backgroundColor: theme.palette.secondary.background, + opacity: isSelected ? 1 : 0.5, + }, + 'default-light': { + backgroundColor: isSelected + ? theme.palette.primary.background + : colors.gray3, + }, + }, + ) + const labelStyle = choose<TextStyle, Record<ButtonType, TextStyle>>(type, { + primary: { + color: theme.palette.primary.text, + fontWeight: theme.palette.primary.isLowContrast ? '500' : undefined, + }, + secondary: { + color: theme.palette.secondary.text, + fontWeight: theme.palette.secondary.isLowContrast ? '500' : undefined, + }, + inverted: { + color: theme.palette.inverted.text, + fontWeight: theme.palette.inverted.isLowContrast ? '500' : undefined, + }, + 'primary-outline': { + color: theme.palette.primary.textInverted, + fontWeight: theme.palette.primary.isLowContrast ? '500' : undefined, + }, + 'secondary-outline': { + color: theme.palette.secondary.textInverted, + fontWeight: theme.palette.secondary.isLowContrast ? '500' : undefined, + }, + 'primary-light': { + color: theme.palette.primary.textInverted, + fontWeight: theme.palette.primary.isLowContrast ? '500' : undefined, + }, + 'secondary-light': { + color: theme.palette.secondary.textInverted, + fontWeight: theme.palette.secondary.isLowContrast ? '500' : undefined, + }, + 'default-light': { + color: theme.palette.default.text, + fontWeight: theme.palette.default.isLowContrast ? '500' : undefined, + }, + }) + return ( + <Button type={type} onPress={onPress} style={style}> + <View style={styles.outer}> + <View style={[circleStyle, styles.circle]}> + <View + style={[ + circleFillStyle, + styles.circleFill, + isSelected ? styles.circleFillSelected : undefined, + ]} + /> + </View> + <Text type="button" style={[labelStyle, styles.label]}> + {label} + </Text> + </View> + </Button> + ) +} + +const styles = StyleSheet.create({ + outer: { + flexDirection: 'row', + alignItems: 'center', + }, + circle: { + width: 42, + height: 26, + borderRadius: 15, + padding: 4, + borderWidth: 1, + marginRight: 10, + }, + circleFill: { + width: 16, + height: 16, + borderRadius: 10, + }, + circleFillSelected: { + marginLeft: 16, + }, + label: { + flex: 1, + }, +}) diff --git a/src/view/com/util/images/AutoSizedImage.tsx b/src/view/com/util/images/AutoSizedImage.tsx index 05425eb31..9de443b7f 100644 --- a/src/view/com/util/images/AutoSizedImage.tsx +++ b/src/view/com/util/images/AutoSizedImage.tsx @@ -9,7 +9,7 @@ import { View, ViewStyle, } from 'react-native' -import {Text} from '../Text' +import {Text} from '../text/Text' import {colors} from '../../../lib/styles' const MAX_HEIGHT = 300 diff --git a/src/view/com/util/RichText.tsx b/src/view/com/util/text/RichText.tsx index d6f193f9d..c9ed4b58e 100644 --- a/src/view/com/util/RichText.tsx +++ b/src/view/com/util/text/RichText.tsx @@ -1,9 +1,11 @@ import React from 'react' import {TextStyle, StyleProp} from 'react-native' -import {TextLink} from './Link' +import {TextLink} from '../Link' import {Text} from './Text' -import {s} from '../../lib/styles' -import {toShortUrl} from '../../../lib/strings' +import {s} from '../../../lib/styles' +import {toShortUrl} from '../../../../lib/strings' +import {TypographyVariant} from '../../../lib/ThemeContext' +import {usePalette} from '../../../lib/hooks/usePalette' type TextSlice = {start: number; end: number} type Entity = { @@ -13,16 +15,19 @@ type Entity = { } export function RichText({ + type = 'body1', text, entities, style, numberOfLines, }: { + type: TypographyVariant text: string entities?: Entity[] style?: StyleProp<TextStyle> numberOfLines?: number }) { + const pal = usePalette('default') if (!entities?.length) { if (/^\p{Extended_Pictographic}+$/u.test(text) && text.length <= 5) { style = { @@ -47,18 +52,20 @@ export function RichText({ els.push( <TextLink key={key} + type={type} text={segment.text} href={`/profile/${segment.entity.value}`} - style={[style, s.blue3]} + style={[style, pal.link]} />, ) } else if (segment.entity.type === 'link') { els.push( <TextLink key={key} + type={type} text={toShortUrl(segment.text)} href={segment.entity.value} - style={[style, s.blue3]} + style={[style, pal.link]} />, ) } @@ -66,7 +73,7 @@ export function RichText({ key++ } return ( - <Text style={style} numberOfLines={numberOfLines}> + <Text type={type} style={[pal.text, style]} numberOfLines={numberOfLines}> {els} </Text> ) diff --git a/src/view/com/util/text/Text.tsx b/src/view/com/util/text/Text.tsx new file mode 100644 index 000000000..549eb2901 --- /dev/null +++ b/src/view/com/util/text/Text.tsx @@ -0,0 +1,23 @@ +import React from 'react' +import {Text as RNText, TextProps} from 'react-native' +import {s} from '../../../lib/styles' +import {useTheme, TypographyVariant} from '../../../lib/ThemeContext' + +export type CustomTextProps = TextProps & { + type?: TypographyVariant +} + +export function Text({ + type = 'body1', + children, + style, + ...props +}: React.PropsWithChildren<CustomTextProps>) { + const theme = useTheme() + const typography = theme.typography[type] + return ( + <RNText style={[s.black, typography, style]} {...props}> + {children} + </RNText> + ) +} diff --git a/src/view/lib/ThemeContext.tsx b/src/view/lib/ThemeContext.tsx new file mode 100644 index 000000000..57f758c53 --- /dev/null +++ b/src/view/lib/ThemeContext.tsx @@ -0,0 +1,70 @@ +import React, {createContext, useContext, useMemo} from 'react' +import {TextStyle, useColorScheme, ViewStyle} from 'react-native' +import {darkTheme, defaultTheme} from './themes' + +export type ColorScheme = 'light' | 'dark' + +export type PaletteColorName = + | 'default' + | 'primary' + | 'secondary' + | 'inverted' + | 'error' +export type PaletteColor = { + isLowContrast: boolean + background: string + backgroundLight: string + text: string + textLight: string + textInverted: string + link: string + border: string + icon: string +} +export type Palette = Record<PaletteColorName, PaletteColor> + +export type ShapeName = 'button' | 'bigButton' | 'smallButton' +export type Shapes = Record<ShapeName, ViewStyle> + +export type TypographyVariant = + | 'h1' + | 'h2' + | 'h3' + | 'h4' + | 'subtitle1' + | 'subtitle2' + | 'body1' + | 'body2' + | 'button' + | 'caption' + | 'overline' +export type Typography = Record<TypographyVariant, TextStyle> + +export interface Theme { + colorScheme: ColorScheme + palette: Palette + shapes: Shapes + typography: Typography +} + +export interface ThemeProviderProps { + theme?: ColorScheme +} + +export const ThemeContext = createContext<Theme>(defaultTheme) + +export const useTheme = () => useContext(ThemeContext) + +export const ThemeProvider: React.FC<ThemeProviderProps> = ({ + theme, + children, +}) => { + const colorScheme = useColorScheme() + + const value = useMemo( + () => ((theme || colorScheme) === 'dark' ? darkTheme : defaultTheme), + [colorScheme, theme], + ) + + return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider> +} diff --git a/src/view/lib/useAnimatedValue.ts b/src/view/lib/hooks/useAnimatedValue.ts index 1307ef952..1307ef952 100644 --- a/src/view/lib/useAnimatedValue.ts +++ b/src/view/lib/hooks/useAnimatedValue.ts diff --git a/src/view/lib/useOnMainScroll.ts b/src/view/lib/hooks/useOnMainScroll.ts index ee0081226..c3c16ff83 100644 --- a/src/view/lib/useOnMainScroll.ts +++ b/src/view/lib/hooks/useOnMainScroll.ts @@ -1,6 +1,6 @@ import {useState} from 'react' import {NativeSyntheticEvent, NativeScrollEvent} from 'react-native' -import {RootStoreModel} from '../../state' +import {RootStoreModel} from '../../../state' export type OnScrollCb = ( event: NativeSyntheticEvent<NativeScrollEvent>, diff --git a/src/view/lib/hooks/usePalette.ts b/src/view/lib/hooks/usePalette.ts new file mode 100644 index 000000000..e9af4ae16 --- /dev/null +++ b/src/view/lib/hooks/usePalette.ts @@ -0,0 +1,41 @@ +import {TextStyle, ViewStyle} from 'react-native' +import {useTheme, PaletteColorName, PaletteColor} from '../ThemeContext' + +export interface UsePaletteValue { + colors: PaletteColor + view: ViewStyle + border: ViewStyle + text: TextStyle + textLight: TextStyle + textInverted: TextStyle + link: TextStyle +} +export function usePalette(color: PaletteColorName): UsePaletteValue { + const palette = useTheme().palette[color] + return { + colors: palette, + view: { + backgroundColor: palette.background, + }, + border: { + borderWidth: 1, + borderColor: palette.border, + }, + text: { + color: palette.text, + fontWeight: palette.isLowContrast ? '500' : undefined, + }, + textLight: { + color: palette.textLight, + fontWeight: palette.isLowContrast ? '500' : undefined, + }, + textInverted: { + color: palette.textInverted, + fontWeight: palette.isLowContrast ? '500' : undefined, + }, + link: { + color: palette.link, + fontWeight: palette.isLowContrast ? '500' : undefined, + }, + } +} diff --git a/src/view/lib/themes.ts b/src/view/lib/themes.ts new file mode 100644 index 000000000..3851ee9d0 --- /dev/null +++ b/src/view/lib/themes.ts @@ -0,0 +1,163 @@ +import type {Theme} from './ThemeContext' +import {colors} from './styles' + +export const defaultTheme: Theme = { + colorScheme: 'light', + palette: { + default: { + isLowContrast: false, + background: colors.white, + backgroundLight: colors.gray2, + text: colors.black, + textLight: colors.gray5, + textInverted: colors.white, + link: colors.blue3, + border: colors.gray3, + icon: colors.gray2, + }, + primary: { + isLowContrast: true, + background: colors.blue3, + backgroundLight: colors.blue2, + text: colors.white, + textLight: colors.blue0, + textInverted: colors.blue3, + link: colors.blue0, + border: colors.blue4, + icon: colors.blue4, + }, + secondary: { + isLowContrast: true, + background: colors.green3, + backgroundLight: colors.green2, + text: colors.white, + textLight: colors.green1, + textInverted: colors.green4, + link: colors.green1, + border: colors.green4, + icon: colors.green4, + }, + inverted: { + isLowContrast: true, + background: colors.black, + backgroundLight: colors.gray6, + text: colors.white, + textLight: colors.gray3, + textInverted: colors.black, + link: colors.blue2, + border: colors.gray3, + icon: colors.gray5, + }, + error: { + isLowContrast: true, + background: colors.red3, + backgroundLight: colors.red2, + text: colors.white, + textLight: colors.red1, + textInverted: colors.red3, + link: colors.red1, + border: colors.red4, + icon: colors.red4, + }, + }, + shapes: { + button: { + // TODO + }, + bigButton: { + // TODO + }, + smallButton: { + // TODO + }, + }, + typography: { + h1: { + fontSize: 48, + fontWeight: '500', + }, + h2: { + fontSize: 34, + letterSpacing: 0.25, + fontWeight: '500', + }, + h3: { + fontSize: 24, + fontWeight: '500', + }, + h4: { + fontWeight: '500', + fontSize: 20, + letterSpacing: 0.15, + }, + subtitle1: { + fontSize: 16, + letterSpacing: 0.15, + }, + subtitle2: { + fontWeight: '500', + fontSize: 14, + letterSpacing: 0.1, + }, + body1: { + fontSize: 16, + letterSpacing: 0.5, + }, + body2: { + fontSize: 14, + letterSpacing: 0.25, + }, + button: { + fontWeight: '500', + fontSize: 14, + letterSpacing: 0.5, + }, + caption: { + fontSize: 12, + letterSpacing: 0.4, + }, + overline: { + fontSize: 10, + letterSpacing: 1.5, + textTransform: 'uppercase', + }, + }, +} + +export const darkTheme: Theme = { + ...defaultTheme, + colorScheme: 'dark', + palette: { + ...defaultTheme.palette, + default: { + isLowContrast: true, + background: colors.black, + backgroundLight: colors.gray6, + text: colors.white, + textLight: colors.gray3, + textInverted: colors.black, + link: colors.blue2, + border: colors.gray3, + icon: colors.gray5, + }, + primary: { + ...defaultTheme.palette.primary, + textInverted: colors.blue2, + }, + secondary: { + ...defaultTheme.palette.secondary, + textInverted: colors.green2, + }, + inverted: { + isLowContrast: false, + background: colors.white, + backgroundLight: colors.gray2, + text: colors.black, + textLight: colors.gray5, + textInverted: colors.white, + link: colors.blue3, + border: colors.gray3, + icon: colors.gray1, + }, + }, +} diff --git a/src/view/lib/z-index.ts b/src/view/lib/z-index.ts deleted file mode 100644 index 872027d3f..000000000 --- a/src/view/lib/z-index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const FAB = 1 -export const BASE = 0 diff --git a/src/view/routes.ts b/src/view/routes.ts index 272a1b096..3717e0f05 100644 --- a/src/view/routes.ts +++ b/src/view/routes.ts @@ -15,6 +15,7 @@ import {ProfileFollowers} from './screens/ProfileFollowers' import {ProfileFollows} from './screens/ProfileFollows' import {ProfileMembers} from './screens/ProfileMembers' import {Settings} from './screens/Settings' +import {Debug} from './screens/Debug' export type ScreenParams = { navIdx: [number, number] @@ -71,6 +72,7 @@ export const routes: Route[] = [ 'retweet', r('/profile/(?<name>[^/]+)/post/(?<rkey>[^/]+)/reposted-by'), ], + [Debug, 'Debug', 'house', r('/debug')], ] export function match(url: string): MatchResult { diff --git a/src/view/screens/Contacts.tsx b/src/view/screens/Contacts.tsx index bcfb47782..8de56d79a 100644 --- a/src/view/screens/Contacts.tsx +++ b/src/view/screens/Contacts.tsx @@ -3,11 +3,11 @@ import {StyleSheet, TextInput, View} from 'react-native' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {ProfileFollows as ProfileFollowsComponent} from '../com/profile/ProfileFollows' import {Selector} from '../com/util/Selector' -import {Text} from '../com/util/Text' +import {Text} from '../com/util/text/Text' import {colors} from '../lib/styles' import {ScreenParams} from '../routes' import {useStores} from '../../state' -import {useAnimatedValue} from '../lib/useAnimatedValue' +import {useAnimatedValue} from '../lib/hooks/useAnimatedValue' export const Contacts = ({navIdx, visible, params}: ScreenParams) => { const store = useStores() diff --git a/src/view/screens/Debug.tsx b/src/view/screens/Debug.tsx new file mode 100644 index 000000000..f34bcc17b --- /dev/null +++ b/src/view/screens/Debug.tsx @@ -0,0 +1,432 @@ +import React from 'react' +import {ScrollView, View} from 'react-native' +import {ViewHeader} from '../com/util/ViewHeader' +import {ThemeProvider} from '../lib/ThemeContext' +import {PaletteColorName} from '../lib/ThemeContext' +import {usePalette} from '../lib/hooks/usePalette' + +import {Text} from '../com/util/text/Text' +import {ViewSelector} from '../com/util/ViewSelector' +import {EmptyState} from '../com/util/EmptyState' +import * as LoadingPlaceholder from '../com/util/LoadingPlaceholder' +import {Button} from '../com/util/forms/Button' +import {DropdownButton, DropdownItem} from '../com/util/forms/DropdownButton' +import {ToggleButton} from '../com/util/forms/ToggleButton' +import {RadioGroup} from '../com/util/forms/RadioGroup' +import {ErrorScreen} from '../com/util/error/ErrorScreen' +import {ErrorMessage} from '../com/util/error/ErrorMessage' + +const MAIN_VIEWS = ['Base', 'Controls', 'Error'] + +export const Debug = () => { + const [colorScheme, setColorScheme] = React.useState<'light' | 'dark'>( + 'light', + ) + const onToggleColorScheme = () => { + setColorScheme(colorScheme === 'light' ? 'dark' : 'light') + } + return ( + <ThemeProvider theme={colorScheme}> + <DebugInner + colorScheme={colorScheme} + onToggleColorScheme={onToggleColorScheme} + /> + </ThemeProvider> + ) +} + +function DebugInner({ + colorScheme, + onToggleColorScheme, +}: { + colorScheme: 'light' | 'dark' + onToggleColorScheme: () => void +}) { + const [currentView, setCurrentView] = React.useState<number>(0) + const pal = usePalette('default') + + const renderItem = item => { + return ( + <View> + <View style={{paddingTop: 10, paddingHorizontal: 10}}> + <ToggleButton + type="default-light" + onPress={onToggleColorScheme} + isSelected={colorScheme === 'dark'} + label="Dark mode" + /> + </View> + {item.currentView === 2 ? ( + <ErrorView key="error" /> + ) : item.currentView === 1 ? ( + <ControlsView key="controls" /> + ) : ( + <BaseView key="base" /> + )} + </View> + ) + } + + const items = [{currentView}] + + return ( + <View style={[{flex: 1}, pal.view]}> + <ViewHeader title="Debug panel" /> + <ViewSelector + swipeEnabled + sections={MAIN_VIEWS} + items={items} + renderItem={renderItem} + onSelectView={setCurrentView} + /> + </View> + ) +} + +function Heading({label}: {label: string}) { + const pal = usePalette('default') + return ( + <View style={{paddingTop: 10, paddingBottom: 5}}> + <Text type="h3" style={pal.text}> + {label} + </Text> + </View> + ) +} + +function BaseView() { + return ( + <View style={{paddingHorizontal: 10}}> + <Heading label="Palettes" /> + <PaletteView palette="default" /> + <PaletteView palette="primary" /> + <PaletteView palette="secondary" /> + <PaletteView palette="inverted" /> + <PaletteView palette="error" /> + <Heading label="Typography" /> + <TypographyView /> + <Heading label="Empty state" /> + <EmptyStateView /> + <Heading label="Loading placeholders" /> + <LoadingPlaceholderView /> + <View style={{height: 200}} /> + </View> + ) +} + +function ControlsView() { + return ( + <ScrollView style={{paddingHorizontal: 10}}> + <Heading label="Buttons" /> + <ButtonsView /> + <Heading label="Dropdown Buttons" /> + <DropdownButtonsView /> + <Heading label="Toggle Buttons" /> + <ToggleButtonsView /> + <Heading label="Radio Buttons" /> + <RadioButtonsView /> + <View style={{height: 200}} /> + </ScrollView> + ) +} + +function ErrorView() { + return ( + <View style={{padding: 10}}> + <View style={{marginBottom: 5}}> + <ErrorScreen + title="Error screen" + message="A major error occurred that led the entire screen to fail" + details="Here are some details" + onPressTryAgain={() => {}} + /> + </View> + <View style={{marginBottom: 5}}> + <ErrorMessage message="This is an error that occurred while things were being done" /> + </View> + <View style={{marginBottom: 5}}> + <ErrorMessage + message="This is an error that occurred while things were being done" + numberOfLines={1} + /> + </View> + <View style={{marginBottom: 5}}> + <ErrorMessage + message="This is an error that occurred while things were being done" + onPressTryAgain={() => {}} + /> + </View> + <View style={{marginBottom: 5}}> + <ErrorMessage + message="This is an error that occurred while things were being done" + onPressTryAgain={() => {}} + numberOfLines={1} + /> + </View> + </View> + ) +} + +function PaletteView({palette}: {palette: PaletteColorName}) { + const defaultPal = usePalette('default') + const pal = usePalette(palette) + return ( + <View + style={[ + pal.view, + pal.border, + { + padding: 10, + marginBottom: 5, + }, + ]}> + <Text style={[pal.text]}>{palette} colors</Text> + <Text style={[pal.textLight]}>Light text</Text> + <Text style={[pal.link]}>Link text</Text> + {palette !== 'default' && ( + <View style={[defaultPal.view]}> + <Text style={[pal.textInverted]}>Inverted text</Text> + </View> + )} + </View> + ) +} + +function TypographyView() { + const pal = usePalette('default') + return ( + <View style={[pal.view]}> + <Text type="h1" style={[pal.text]}> + Heading 1 + </Text> + <Text type="h2" style={[pal.text]}> + Heading 2 + </Text> + <Text type="h3" style={[pal.text]}> + Heading 3 + </Text> + <Text type="h4" style={[pal.text]}> + Heading 4 + </Text> + <Text type="subtitle1" style={[pal.text]}> + Subtitle 1 + </Text> + <Text type="subtitle2" style={[pal.text]}> + Subtitle 2 + </Text> + <Text type="body1" style={[pal.text]}> + Body 1 + </Text> + <Text type="body2" style={[pal.text]}> + Body 2 + </Text> + <Text type="button" style={[pal.text]}> + Button + </Text> + <Text type="caption" style={[pal.text]}> + Caption + </Text> + <Text type="overline" style={[pal.text]}> + Overline + </Text> + </View> + ) +} + +function EmptyStateView() { + return <EmptyState icon="bars" message="This is an empty state" /> +} + +function LoadingPlaceholderView() { + return ( + <> + <LoadingPlaceholder.PostLoadingPlaceholder /> + <LoadingPlaceholder.NotificationLoadingPlaceholder /> + </> + ) +} + +function ButtonsView() { + const defaultPal = usePalette('default') + const buttonStyles = {marginRight: 5} + return ( + <View style={[defaultPal.view]}> + <View + style={{ + flexDirection: 'row', + marginBottom: 5, + }}> + <Button type="primary" label="Primary solid" style={buttonStyles} /> + <Button type="secondary" label="Secondary solid" style={buttonStyles} /> + <Button type="inverted" label="Inverted solid" style={buttonStyles} /> + </View> + <View style={{flexDirection: 'row'}}> + <Button + type="primary-outline" + label="Primary outline" + style={buttonStyles} + /> + <Button + type="secondary-outline" + label="Secondary outline" + style={buttonStyles} + /> + </View> + <View style={{flexDirection: 'row'}}> + <Button + type="primary-light" + label="Primary light" + style={buttonStyles} + /> + <Button + type="secondary-light" + label="Secondary light" + style={buttonStyles} + /> + </View> + <View style={{flexDirection: 'row'}}> + <Button + type="default-light" + label="Default light" + style={buttonStyles} + /> + </View> + </View> + ) +} + +const DROPDOWN_ITEMS: DropdownItem[] = [ + { + icon: ['far', 'paste'], + label: 'Copy post text', + onPress() {}, + }, + { + icon: 'share', + label: 'Share...', + onPress() {}, + }, + { + icon: 'circle-exclamation', + label: 'Report post', + onPress() {}, + }, +] +function DropdownButtonsView() { + const defaultPal = usePalette('default') + return ( + <View style={[defaultPal.view]}> + <View + style={{ + marginBottom: 5, + }}> + <DropdownButton + type="primary" + items={DROPDOWN_ITEMS} + menuWidth={200} + label="Primary button" + /> + </View> + <View + style={{ + marginBottom: 5, + }}> + <DropdownButton type="bare" items={DROPDOWN_ITEMS} menuWidth={200}> + <Text>Bare</Text> + </DropdownButton> + </View> + </View> + ) +} + +function ToggleButtonsView() { + const defaultPal = usePalette('default') + const buttonStyles = {marginBottom: 5} + const [isSelected, setIsSelected] = React.useState(false) + const onToggle = () => setIsSelected(!isSelected) + return ( + <View style={[defaultPal.view]}> + <ToggleButton + type="primary" + label="Primary solid" + style={buttonStyles} + isSelected={isSelected} + onPress={onToggle} + /> + <ToggleButton + type="secondary" + label="Secondary solid" + style={buttonStyles} + isSelected={isSelected} + onPress={onToggle} + /> + <ToggleButton + type="inverted" + label="Inverted solid" + style={buttonStyles} + isSelected={isSelected} + onPress={onToggle} + /> + <ToggleButton + type="primary-outline" + label="Primary outline" + style={buttonStyles} + isSelected={isSelected} + onPress={onToggle} + /> + <ToggleButton + type="secondary-outline" + label="Secondary outline" + style={buttonStyles} + isSelected={isSelected} + onPress={onToggle} + /> + <ToggleButton + type="primary-light" + label="Primary light" + style={buttonStyles} + isSelected={isSelected} + onPress={onToggle} + /> + <ToggleButton + type="secondary-light" + label="Secondary light" + style={buttonStyles} + isSelected={isSelected} + onPress={onToggle} + /> + <ToggleButton + type="default-light" + label="Default light" + style={buttonStyles} + isSelected={isSelected} + onPress={onToggle} + /> + </View> + ) +} + +const RADIO_BUTTON_ITEMS = [ + {key: 'default-light', label: 'Default Light'}, + {key: 'primary', label: 'Primary'}, + {key: 'secondary', label: 'Secondary'}, + {key: 'inverted', label: 'Inverted'}, + {key: 'primary-outline', label: 'Primary Outline'}, + {key: 'secondary-outline', label: 'Secondary Outline'}, + {key: 'primary-light', label: 'Primary Light'}, + {key: 'secondary-light', label: 'Secondary Light'}, +] +function RadioButtonsView() { + const defaultPal = usePalette('default') + const [rgType, setRgType] = React.useState('default-light') + return ( + <View style={[defaultPal.view]}> + <RadioGroup + type={rgType} + items={RADIO_BUTTON_ITEMS} + initialSelection="default-light" + onSelect={setRgType} + /> + </View> + ) +} diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx index 834010b0a..118ba9ed8 100644 --- a/src/view/screens/Home.tsx +++ b/src/view/screens/Home.tsx @@ -6,11 +6,11 @@ import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {useSafeAreaInsets} from 'react-native-safe-area-context' import {ViewHeader} from '../com/util/ViewHeader' import {Feed} from '../com/posts/Feed' -import {Text} from '../com/util/Text' +import {Text} from '../com/util/text/Text' import {useStores} from '../../state' import {ScreenParams} from '../routes' import {s, colors} from '../lib/styles' -import {useOnMainScroll} from '../lib/useOnMainScroll' +import {useOnMainScroll} from '../lib/hooks/useOnMainScroll' import {clamp} from 'lodash' const HITSLOP = {left: 20, top: 20, right: 20, bottom: 20} diff --git a/src/view/screens/Login.tsx b/src/view/screens/Login.tsx index 734903d7e..0315e287e 100644 --- a/src/view/screens/Login.tsx +++ b/src/view/screens/Login.tsx @@ -10,7 +10,7 @@ import {observer} from 'mobx-react-lite' import {Signin} from '../com/login/Signin' import {Logo} from '../com/login/Logo' import {CreateAccount} from '../com/login/CreateAccount' -import {Text} from '../com/util/Text' +import {Text} from '../com/util/text/Text' import {s, colors} from '../lib/styles' enum ScreenState { diff --git a/src/view/screens/NotFound.tsx b/src/view/screens/NotFound.tsx index 16d75c386..3591b696c 100644 --- a/src/view/screens/NotFound.tsx +++ b/src/view/screens/NotFound.tsx @@ -1,7 +1,7 @@ import React from 'react' import {Button, View} from 'react-native' import {ViewHeader} from '../com/util/ViewHeader' -import {Text} from '../com/util/Text' +import {Text} from '../com/util/text/Text' import {useStores} from '../../state' export const NotFound = () => { diff --git a/src/view/screens/Notifications.tsx b/src/view/screens/Notifications.tsx index fe4a78721..2257dd221 100644 --- a/src/view/screens/Notifications.tsx +++ b/src/view/screens/Notifications.tsx @@ -4,7 +4,7 @@ import {ViewHeader} from '../com/util/ViewHeader' import {Feed} from '../com/notifications/Feed' import {useStores} from '../../state' import {ScreenParams} from '../routes' -import {useOnMainScroll} from '../lib/useOnMainScroll' +import {useOnMainScroll} from '../lib/hooks/useOnMainScroll' export const Notifications = ({navIdx, visible}: ScreenParams) => { const store = useStores() diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx index 8d41d9ad1..437f5f4a7 100644 --- a/src/view/screens/Profile.tsx +++ b/src/view/screens/Profile.tsx @@ -12,14 +12,14 @@ import {ProfileHeader} from '../com/profile/ProfileHeader' import {FeedItem} from '../com/posts/FeedItem' import {ProfileCard} from '../com/profile/ProfileCard' import {PostFeedLoadingPlaceholder} from '../com/util/LoadingPlaceholder' -import {ErrorScreen} from '../com/util/ErrorScreen' -import {ErrorMessage} from '../com/util/ErrorMessage' +import {ErrorScreen} from '../com/util/error/ErrorScreen' +import {ErrorMessage} from '../com/util/error/ErrorMessage' import {EmptyState} from '../com/util/EmptyState' -import {Text} from '../com/util/Text' +import {Text} from '../com/util/text/Text' import {ViewHeader} from '../com/util/ViewHeader' import * as Toast from '../com/util/Toast' import {s, colors} from '../lib/styles' -import {useOnMainScroll} from '../lib/useOnMainScroll' +import {useOnMainScroll} from '../lib/hooks/useOnMainScroll' const LOADING_ITEM = {_reactKey: '__loading__'} const END_ITEM = {_reactKey: '__end__'} @@ -116,7 +116,6 @@ export const Profile = observer(({navIdx, visible, params}: ScreenParams) => { renderItem = (item: any) => ( <View style={s.p5}> <ErrorMessage - dark message={item.error} onPressTryAgain={onPressTryAgain} /> diff --git a/src/view/screens/Search.tsx b/src/view/screens/Search.tsx index c909f50e6..4ab1436a6 100644 --- a/src/view/screens/Search.tsx +++ b/src/view/screens/Search.tsx @@ -10,7 +10,7 @@ import { import {ViewHeader} from '../com/util/ViewHeader' import {SuggestedFollows} from '../com/discover/SuggestedFollows' import {UserAvatar} from '../com/util/UserAvatar' -import {Text} from '../com/util/Text' +import {Text} from '../com/util/text/Text' import {ScreenParams} from '../routes' import {useStores} from '../../state' import {UserAutocompleteViewModel} from '../../state/models/user-autocomplete-view' diff --git a/src/view/screens/Settings.tsx b/src/view/screens/Settings.tsx index 1656d3b68..d3fcdfdff 100644 --- a/src/view/screens/Settings.tsx +++ b/src/view/screens/Settings.tsx @@ -6,7 +6,7 @@ import {ScreenParams} from '../routes' import {s, colors} from '../lib/styles' import {ViewHeader} from '../com/util/ViewHeader' import {Link} from '../com/util/Link' -import {Text} from '../com/util/Text' +import {Text} from '../com/util/text/Text' import {UserAvatar} from '../com/util/UserAvatar' export const Settings = observer(function Settings({ @@ -57,6 +57,9 @@ export const Settings = observer(function Settings({ </View> </View> </Link> + <Link href="/debug" title="Debug tools"> + <Text style={s.blue3}>Debug tools</Text> + </Link> </View> </View> ) diff --git a/src/view/shell/mobile/Composer.tsx b/src/view/shell/mobile/Composer.tsx index c586bc871..1a2d2d24d 100644 --- a/src/view/shell/mobile/Composer.tsx +++ b/src/view/shell/mobile/Composer.tsx @@ -3,7 +3,7 @@ import {observer} from 'mobx-react-lite' import {Animated, Easing, Platform, StyleSheet, View} from 'react-native' import {ComposePost} from '../../com/composer/ComposePost' import {ComposerOpts} from '../../../state/models/shell-ui' -import {useAnimatedValue} from '../../lib/useAnimatedValue' +import {useAnimatedValue} from '../../lib/hooks/useAnimatedValue' export const Composer = observer( ({ diff --git a/src/view/shell/mobile/Menu.tsx b/src/view/shell/mobile/Menu.tsx index 8c11e3e8e..a8a81b4dd 100644 --- a/src/view/shell/mobile/Menu.tsx +++ b/src/view/shell/mobile/Menu.tsx @@ -17,7 +17,7 @@ import { MagnifyingGlassIcon, } from '../../lib/icons' import {UserAvatar} from '../../com/util/UserAvatar' -import {Text} from '../../com/util/Text' +import {Text} from '../../com/util/text/Text' import {CreateSceneModal} from '../../../state/models/shell-ui' export const Menu = ({ diff --git a/src/view/shell/mobile/TabsSelector.tsx b/src/view/shell/mobile/TabsSelector.tsx index 41b18a337..71aaa200d 100644 --- a/src/view/shell/mobile/TabsSelector.tsx +++ b/src/view/shell/mobile/TabsSelector.tsx @@ -10,13 +10,13 @@ import { } from 'react-native' import {useSafeAreaInsets} from 'react-native-safe-area-context' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {Text} from '../../com/util/Text' +import {Text} from '../../com/util/text/Text' import Swipeable from 'react-native-gesture-handler/Swipeable' import {useStores} from '../../../state' import {s, colors} from '../../lib/styles' import {toShareUrl} from '../../../lib/strings' import {match} from '../../routes' -import {useAnimatedValue} from '../../lib/useAnimatedValue' +import {useAnimatedValue} from '../../lib/hooks/useAnimatedValue' const TAB_HEIGHT = 42 diff --git a/src/view/shell/mobile/index.tsx b/src/view/shell/mobile/index.tsx index 6437d6969..673c0fbe9 100644 --- a/src/view/shell/mobile/index.tsx +++ b/src/view/shell/mobile/index.tsx @@ -29,7 +29,7 @@ import {Onboard} from '../../screens/Onboard' import {HorzSwipe} from '../../com/util/gestures/HorzSwipe' import {Modal} from '../../com/modals/Modal' import {Lightbox} from '../../com/lightbox/Lightbox' -import {Text} from '../../com/util/Text' +import {Text} from '../../com/util/text/Text' import {TabsSelector} from './TabsSelector' import {Composer} from './Composer' import {s, colors} from '../../lib/styles' @@ -42,7 +42,7 @@ import { BellIcon, BellIconSolid, } from '../../lib/icons' -import {useAnimatedValue} from '../../lib/useAnimatedValue' +import {useAnimatedValue} from '../../lib/hooks/useAnimatedValue' const Btn = ({ icon, |