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
zio/stable
Paul Frazee 2022-12-28 14:06:01 -06:00 committed by GitHub
parent cc63660982
commit 7e31645e9a
78 changed files with 1431 additions and 375 deletions

View File

@ -5,6 +5,7 @@ import {RootSiblingParent} from 'react-native-root-siblings'
import {GestureHandlerRootView} from 'react-native-gesture-handler' import {GestureHandlerRootView} from 'react-native-gesture-handler'
import SplashScreen from 'react-native-splash-screen' import SplashScreen from 'react-native-splash-screen'
import {SafeAreaProvider} from 'react-native-safe-area-context' import {SafeAreaProvider} from 'react-native-safe-area-context'
import {ThemeProvider} from './view/lib/ThemeContext'
import * as view from './view/index' import * as view from './view/index'
import {RootStoreModel, setupState, RootStoreProvider} from './state' import {RootStoreModel, setupState, RootStoreProvider} from './state'
import {MobileShell} from './view/shell/mobile' import {MobileShell} from './view/shell/mobile'
@ -40,9 +41,11 @@ function App() {
<GestureHandlerRootView style={{flex: 1}}> <GestureHandlerRootView style={{flex: 1}}>
<RootSiblingParent> <RootSiblingParent>
<RootStoreProvider value={rootStore}> <RootStoreProvider value={rootStore}>
<SafeAreaProvider> <ThemeProvider>
<MobileShell /> <SafeAreaProvider>
</SafeAreaProvider> <MobileShell />
</SafeAreaProvider>
</ThemeProvider>
</RootStoreProvider> </RootStoreProvider>
</RootSiblingParent> </RootSiblingParent>
</GestureHandlerRootView> </GestureHandlerRootView>

View File

@ -0,0 +1,6 @@
export function choose<U, T extends Record<string, U>>(
value: keyof T,
choices: T,
): U {
return choices[value]
}

View File

@ -5,8 +5,8 @@ import {
StyleSheet, StyleSheet,
useWindowDimensions, useWindowDimensions,
} from 'react-native' } from 'react-native'
import {useAnimatedValue} from '../../lib/useAnimatedValue' import {useAnimatedValue} from '../../lib/hooks/useAnimatedValue'
import {Text} from '../util/Text' import {Text} from '../util/text/Text'
import {colors} from '../../lib/styles' import {colors} from '../../lib/styles'
interface AutocompleteItem { interface AutocompleteItem {

View File

@ -15,7 +15,7 @@ import LinearGradient from 'react-native-linear-gradient'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {UserAutocompleteViewModel} from '../../../state/models/user-autocomplete-view' import {UserAutocompleteViewModel} from '../../../state/models/user-autocomplete-view'
import {Autocomplete} from './Autocomplete' import {Autocomplete} from './Autocomplete'
import {Text} from '../util/Text' import {Text} from '../util/text/Text'
import * as Toast from '../util/Toast' import * as Toast from '../util/Toast'
// @ts-ignore no type definition -prf // @ts-ignore no type definition -prf
import ProgressCircle from 'react-native-progress/Circle' import ProgressCircle from 'react-native-progress/Circle'

View File

@ -3,7 +3,7 @@ import {StyleSheet, TouchableOpacity, View} from 'react-native'
import {colors} from '../../lib/styles' import {colors} from '../../lib/styles'
import {useStores} from '../../../state' import {useStores} from '../../../state'
import {UserAvatar} from '../util/UserAvatar' import {UserAvatar} from '../util/UserAvatar'
import {Text} from '../util/Text' import {Text} from '../util/text/Text'
export function ComposePrompt({ export function ComposePrompt({
noAvi = false, noAvi = false,

View File

@ -10,9 +10,9 @@ import LinearGradient from 'react-native-linear-gradient'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {observer} from 'mobx-react-lite' import {observer} from 'mobx-react-lite'
import _omit from 'lodash.omit' import _omit from 'lodash.omit'
import {ErrorScreen} from '../util/ErrorScreen' import {ErrorScreen} from '../util/error/ErrorScreen'
import {Link} from '../util/Link' import {Link} from '../util/Link'
import {Text} from '../util/Text' import {Text} from '../util/text/Text'
import {UserAvatar} from '../util/UserAvatar' import {UserAvatar} from '../util/UserAvatar'
import * as Toast from '../util/Toast' import * as Toast from '../util/Toast'
import {useStores} from '../../../state' import {useStores} from '../../../state'

View File

@ -10,7 +10,7 @@ import {observer} from 'mobx-react-lite'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {SwipeAndZoom, Dir} from '../util/gestures/SwipeAndZoom' import {SwipeAndZoom, Dir} from '../util/gestures/SwipeAndZoom'
import {useStores} from '../../../state' import {useStores} from '../../../state'
import {useAnimatedValue} from '../../lib/useAnimatedValue' import {useAnimatedValue} from '../../lib/hooks/useAnimatedValue'
import * as models from '../../../state/models/shell-ui' import * as models from '../../../state/models/shell-ui'

View File

@ -15,7 +15,7 @@ import * as EmailValidator from 'email-validator'
import {Logo} from './Logo' import {Logo} from './Logo'
import {Picker} from '../util/Picker' import {Picker} from '../util/Picker'
import {TextLink} from '../util/Link' import {TextLink} from '../util/Link'
import {Text} from '../util/Text' import {Text} from '../util/text/Text'
import {s, colors} from '../../lib/styles' import {s, colors} from '../../lib/styles'
import { import {
makeValidHandle, makeValidHandle,

View File

@ -12,7 +12,7 @@ import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import * as EmailValidator from 'email-validator' import * as EmailValidator from 'email-validator'
import {sessionClient as AtpApi, SessionServiceClient} from '@atproto/api' import {sessionClient as AtpApi, SessionServiceClient} from '@atproto/api'
import {Logo} from './Logo' import {Logo} from './Logo'
import {Text} from '../util/Text' import {Text} from '../util/text/Text'
import {s, colors} from '../../lib/styles' import {s, colors} from '../../lib/styles'
import {createFullHandle, toNiceDomain} from '../../../lib/strings' import {createFullHandle, toNiceDomain} from '../../../lib/strings'
import {useStores, RootStoreModel, DEFAULT_SERVICE} from '../../../state' import {useStores, RootStoreModel, DEFAULT_SERVICE} from '../../../state'

View File

@ -6,10 +6,10 @@ import {
View, View,
} from 'react-native' } from 'react-native'
import LinearGradient from 'react-native-linear-gradient' import LinearGradient from 'react-native-linear-gradient'
import {Text} from '../util/Text' import {Text} from '../util/text/Text'
import {useStores} from '../../../state' import {useStores} from '../../../state'
import {s, colors, gradients} from '../../lib/styles' import {s, colors, gradients} from '../../lib/styles'
import {ErrorMessage} from '../util/ErrorMessage' import {ErrorMessage} from '../util/error/ErrorMessage'
export const snapPoints = ['50%'] export const snapPoints = ['50%']

View File

@ -9,8 +9,8 @@ import {
import LinearGradient from 'react-native-linear-gradient' import LinearGradient from 'react-native-linear-gradient'
import {BottomSheetScrollView, BottomSheetTextInput} from '@gorhom/bottom-sheet' import {BottomSheetScrollView, BottomSheetTextInput} from '@gorhom/bottom-sheet'
import {AppBskyActorCreateScene} from '@atproto/api' import {AppBskyActorCreateScene} from '@atproto/api'
import {ErrorMessage} from '../util/ErrorMessage' import {ErrorMessage} from '../util/error/ErrorMessage'
import {Text} from '../util/Text' import {Text} from '../util/text/Text'
import {useStores} from '../../../state' import {useStores} from '../../../state'
import {s, colors, gradients} from '../../lib/styles' import {s, colors, gradients} from '../../lib/styles'
import { import {

View File

@ -9,8 +9,8 @@ import {
import LinearGradient from 'react-native-linear-gradient' import LinearGradient from 'react-native-linear-gradient'
import {BottomSheetScrollView, BottomSheetTextInput} from '@gorhom/bottom-sheet' import {BottomSheetScrollView, BottomSheetTextInput} from '@gorhom/bottom-sheet'
import {Image as PickedImage} from 'react-native-image-crop-picker' import {Image as PickedImage} from 'react-native-image-crop-picker'
import {Text} from '../util/Text' import {Text} from '../util/text/Text'
import {ErrorMessage} from '../util/ErrorMessage' import {ErrorMessage} from '../util/error/ErrorMessage'
import {useStores} from '../../../state' import {useStores} from '../../../state'
import {ProfileViewModel} from '../../../state/models/profile-view' import {ProfileViewModel} from '../../../state/models/profile-view'
import {s, colors, gradients} from '../../lib/styles' import {s, colors, gradients} from '../../lib/styles'

View File

@ -19,8 +19,8 @@ import {
import _omit from 'lodash.omit' import _omit from 'lodash.omit'
import {AtUri} from '../../../third-party/uri' import {AtUri} from '../../../third-party/uri'
import {ProfileCard} from '../profile/ProfileCard' import {ProfileCard} from '../profile/ProfileCard'
import {ErrorMessage} from '../util/ErrorMessage' import {ErrorMessage} from '../util/error/ErrorMessage'
import {Text} from '../util/Text' import {Text} from '../util/text/Text'
import {useStores} from '../../../state' import {useStores} from '../../../state'
import * as apilib from '../../../state/lib/api' import * as apilib from '../../../state/lib/api'
import {ProfileViewModel} from '../../../state/models/profile-view' import {ProfileViewModel} from '../../../state/models/profile-view'

View File

@ -9,8 +9,8 @@ import LinearGradient from 'react-native-linear-gradient'
import {useStores} from '../../../state' import {useStores} from '../../../state'
import {s, colors, gradients} from '../../lib/styles' import {s, colors, gradients} from '../../lib/styles'
import {RadioGroup, RadioGroupItem} from '../util/forms/RadioGroup' import {RadioGroup, RadioGroupItem} from '../util/forms/RadioGroup'
import {Text} from '../util/Text' import {Text} from '../util/text/Text'
import {ErrorMessage} from '../util/ErrorMessage' import {ErrorMessage} from '../util/error/ErrorMessage'
const ITEMS: RadioGroupItem[] = [ const ITEMS: RadioGroupItem[] = [
{key: 'spam', label: 'Spam or excessive repeat posts'}, {key: 'spam', label: 'Spam or excessive repeat posts'},

View File

@ -9,8 +9,8 @@ import LinearGradient from 'react-native-linear-gradient'
import {useStores} from '../../../state' import {useStores} from '../../../state'
import {s, colors, gradients} from '../../lib/styles' import {s, colors, gradients} from '../../lib/styles'
import {RadioGroup, RadioGroupItem} from '../util/forms/RadioGroup' import {RadioGroup, RadioGroupItem} from '../util/forms/RadioGroup'
import {Text} from '../util/Text' import {Text} from '../util/text/Text'
import {ErrorMessage} from '../util/ErrorMessage' import {ErrorMessage} from '../util/error/ErrorMessage'
const ITEMS: RadioGroupItem[] = [ const ITEMS: RadioGroupItem[] = [
{key: 'spam', label: 'Spam or excessive repeat posts'}, {key: 'spam', label: 'Spam or excessive repeat posts'},

View File

@ -2,7 +2,7 @@ import React, {useState} from 'react'
import {Platform, StyleSheet, TouchableOpacity, View} from 'react-native' import {Platform, StyleSheet, TouchableOpacity, View} from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {BottomSheetScrollView, BottomSheetTextInput} from '@gorhom/bottom-sheet' import {BottomSheetScrollView, BottomSheetTextInput} from '@gorhom/bottom-sheet'
import {Text} from '../util/Text' import {Text} from '../util/text/Text'
import {useStores} from '../../../state' import {useStores} from '../../../state'
import {s, colors} from '../../lib/styles' import {s, colors} from '../../lib/styles'
import { import {

View File

@ -4,9 +4,9 @@ import {View, FlatList} from 'react-native'
import {NotificationsViewModel} from '../../../state/models/notifications-view' import {NotificationsViewModel} from '../../../state/models/notifications-view'
import {FeedItem} from './FeedItem' import {FeedItem} from './FeedItem'
import {NotificationFeedLoadingPlaceholder} from '../util/LoadingPlaceholder' import {NotificationFeedLoadingPlaceholder} from '../util/LoadingPlaceholder'
import {ErrorMessage} from '../util/ErrorMessage' import {ErrorMessage} from '../util/error/ErrorMessage'
import {EmptyState} from '../util/EmptyState' import {EmptyState} from '../util/EmptyState'
import {OnScrollCb} from '../../lib/useOnMainScroll' import {OnScrollCb} from '../../lib/hooks/useOnMainScroll'
const EMPTY_FEED_ITEM = {_reactKey: '__empty__'} const EMPTY_FEED_ITEM = {_reactKey: '__empty__'}
@ -54,7 +54,6 @@ export const Feed = observer(function Feed({
{view.isLoading && !data && <NotificationFeedLoadingPlaceholder />} {view.isLoading && !data && <NotificationFeedLoadingPlaceholder />}
{view.hasError && ( {view.hasError && (
<ErrorMessage <ErrorMessage
dark
message={view.error} message={view.error}
style={{margin: 6}} style={{margin: 6}}
onPressTryAgain={onPressTryAgain} onPressTryAgain={onPressTryAgain}

View File

@ -8,9 +8,9 @@ import {PostThreadViewModel} from '../../../state/models/post-thread-view'
import {s, colors} from '../../lib/styles' import {s, colors} from '../../lib/styles'
import {ago, pluralize} from '../../../lib/strings' import {ago, pluralize} from '../../../lib/strings'
import {UpIconSolid} from '../../lib/icons' import {UpIconSolid} from '../../lib/icons'
import {Text} from '../util/Text' import {Text} from '../util/text/Text'
import {UserAvatar} from '../util/UserAvatar' import {UserAvatar} from '../util/UserAvatar'
import {ErrorMessage} from '../util/ErrorMessage' import {ErrorMessage} from '../util/error/ErrorMessage'
import {Post} from '../post/Post' import {Post} from '../post/Post'
import {Link} from '../util/Link' import {Link} from '../util/Link'
import {InviteAccepter} from './InviteAccepter' import {InviteAccepter} from './InviteAccepter'

View File

@ -8,7 +8,7 @@ import {ConfirmModal} from '../../../state/models/shell-ui'
import {useStores} from '../../../state' import {useStores} from '../../../state'
import {ProfileCard} from '../profile/ProfileCard' import {ProfileCard} from '../profile/ProfileCard'
import * as Toast from '../util/Toast' import * as Toast from '../util/Toast'
import {Text} from '../util/Text' import {Text} from '../util/text/Text'
import {s, colors, gradients} from '../../lib/styles' import {s, colors, gradients} from '../../lib/styles'
export function InviteAccepter({item}: {item: NotificationsViewItemModel}) { export function InviteAccepter({item}: {item: NotificationsViewItemModel}) {

View File

@ -10,7 +10,7 @@ import {
} from 'react-native' } from 'react-native'
import {TabView, SceneMap, Route, TabBarProps} from 'react-native-tab-view' import {TabView, SceneMap, Route, TabBarProps} from 'react-native-tab-view'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {Text} from '../util/Text' import {Text} from '../util/text/Text'
import {UserGroupIcon} from '../../lib/icons' import {UserGroupIcon} from '../../lib/icons'
import {useStores} from '../../../state' import {useStores} from '../../../state'
import {s} from '../../lib/styles' import {s} from '../../lib/styles'

View File

@ -2,7 +2,7 @@ import React from 'react'
import {SafeAreaView, StyleSheet, TouchableOpacity, View} from 'react-native' import {SafeAreaView, StyleSheet, TouchableOpacity, View} from 'react-native'
import {observer} from 'mobx-react-lite' import {observer} from 'mobx-react-lite'
import {SuggestedFollows} from '../discover/SuggestedFollows' import {SuggestedFollows} from '../discover/SuggestedFollows'
import {Text} from '../util/Text' import {Text} from '../util/text/Text'
import {useStores} from '../../../state' import {useStores} from '../../../state'
import {s} from '../../lib/styles' import {s} from '../../lib/styles'

View File

@ -6,9 +6,9 @@ import {
RepostedByViewItemModel, RepostedByViewItemModel,
} from '../../../state/models/reposted-by-view' } from '../../../state/models/reposted-by-view'
import {UserAvatar} from '../util/UserAvatar' import {UserAvatar} from '../util/UserAvatar'
import {ErrorMessage} from '../util/ErrorMessage' import {ErrorMessage} from '../util/error/ErrorMessage'
import {Link} from '../util/Link' import {Link} from '../util/Link'
import {Text} from '../util/Text' import {Text} from '../util/text/Text'
import {useStores} from '../../../state' import {useStores} from '../../../state'
import {s, colors} from '../../lib/styles' import {s, colors} from '../../lib/styles'
@ -57,7 +57,6 @@ export const PostRepostedBy = observer(function PostRepostedBy({
return ( return (
<View> <View>
<ErrorMessage <ErrorMessage
dark
message={view.error} message={view.error}
style={{margin: 6}} style={{margin: 6}}
onPressTryAgain={onRefresh} onPressTryAgain={onRefresh}

View File

@ -6,7 +6,7 @@ import {
PostThreadViewPostModel, PostThreadViewPostModel,
} from '../../../state/models/post-thread-view' } from '../../../state/models/post-thread-view'
import {PostThreadItem} from './PostThreadItem' import {PostThreadItem} from './PostThreadItem'
import {ErrorMessage} from '../util/ErrorMessage' import {ErrorMessage} from '../util/error/ErrorMessage'
export const PostThread = observer(function PostThread({ export const PostThread = observer(function PostThread({
uri, uri,
@ -57,7 +57,6 @@ export const PostThread = observer(function PostThread({
return ( return (
<View> <View>
<ErrorMessage <ErrorMessage
dark
message={view.error} message={view.error}
style={{margin: 6}} style={{margin: 6}}
onPressTryAgain={onRefresh} onPressTryAgain={onRefresh}

View File

@ -7,9 +7,9 @@ import {AppBskyFeedPost} from '@atproto/api'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {PostThreadViewPostModel} from '../../../state/models/post-thread-view' import {PostThreadViewPostModel} from '../../../state/models/post-thread-view'
import {Link} from '../util/Link' import {Link} from '../util/Link'
import {RichText} from '../util/RichText' import {RichText} from '../util/text/RichText'
import {Text} from '../util/Text' import {Text} from '../util/text/Text'
import {PostDropdownBtn} from '../util/DropdownBtn' import {PostDropdownBtn} from '../util/forms/DropdownButton'
import * as Toast from '../util/Toast' import * as Toast from '../util/Toast'
import {UserAvatar} from '../util/UserAvatar' import {UserAvatar} from '../util/UserAvatar'
import {s, colors} from '../../lib/styles' import {s, colors} from '../../lib/styles'

View File

@ -6,8 +6,8 @@ import {
VotesViewItemModel, VotesViewItemModel,
} from '../../../state/models/votes-view' } from '../../../state/models/votes-view'
import {Link} from '../util/Link' import {Link} from '../util/Link'
import {Text} from '../util/Text' import {Text} from '../util/text/Text'
import {ErrorMessage} from '../util/ErrorMessage' import {ErrorMessage} from '../util/error/ErrorMessage'
import {UserAvatar} from '../util/UserAvatar' import {UserAvatar} from '../util/UserAvatar'
import {useStores} from '../../../state' import {useStores} from '../../../state'
import {s, colors} from '../../lib/styles' import {s, colors} from '../../lib/styles'
@ -57,7 +57,6 @@ export const PostVotedBy = observer(function PostVotedBy({
return ( return (
<View> <View>
<ErrorMessage <ErrorMessage
dark
message={view.error} message={view.error}
style={{margin: 6}} style={{margin: 6}}
onPressTryAgain={onRefresh} onPressTryAgain={onRefresh}

View File

@ -17,8 +17,8 @@ import {UserInfoText} from '../util/UserInfoText'
import {PostMeta} from '../util/PostMeta' import {PostMeta} from '../util/PostMeta'
import {PostEmbeds} from '../util/PostEmbeds' import {PostEmbeds} from '../util/PostEmbeds'
import {PostCtrls} from '../util/PostCtrls' import {PostCtrls} from '../util/PostCtrls'
import {Text} from '../util/Text' import {Text} from '../util/text/Text'
import {RichText} from '../util/RichText' import {RichText} from '../util/text/RichText'
import * as Toast from '../util/Toast' import * as Toast from '../util/Toast'
import {UserAvatar} from '../util/UserAvatar' import {UserAvatar} from '../util/UserAvatar'
import {useStores} from '../../../state' import {useStores} from '../../../state'

View File

@ -2,8 +2,8 @@ import React, {useState, useEffect} from 'react'
import {observer} from 'mobx-react-lite' import {observer} from 'mobx-react-lite'
import {View} from 'react-native' import {View} from 'react-native'
import {LoadingPlaceholder} from '../util/LoadingPlaceholder' import {LoadingPlaceholder} from '../util/LoadingPlaceholder'
import {ErrorMessage} from '../util/ErrorMessage' import {ErrorMessage} from '../util/error/ErrorMessage'
import {Text} from '../util/Text' import {Text} from '../util/text/Text'
import {PostModel} from '../../../state/models/post' import {PostModel} from '../../../state/models/post'
import {useStores} from '../../../state' import {useStores} from '../../../state'

View File

@ -9,11 +9,11 @@ import {
} from 'react-native' } from 'react-native'
import {PostFeedLoadingPlaceholder} from '../util/LoadingPlaceholder' import {PostFeedLoadingPlaceholder} from '../util/LoadingPlaceholder'
import {EmptyState} from '../util/EmptyState' import {EmptyState} from '../util/EmptyState'
import {ErrorMessage} from '../util/ErrorMessage' import {ErrorMessage} from '../util/error/ErrorMessage'
import {FeedModel} from '../../../state/models/feed-view' import {FeedModel} from '../../../state/models/feed-view'
import {FeedItem} from './FeedItem' import {FeedItem} from './FeedItem'
import {ComposePrompt} from '../composer/Prompt' import {ComposePrompt} from '../composer/Prompt'
import {OnScrollCb} from '../../lib/useOnMainScroll' import {OnScrollCb} from '../../lib/hooks/useOnMainScroll'
const COMPOSE_PROMPT_ITEM = {_reactKey: '__prompt__'} const COMPOSE_PROMPT_ITEM = {_reactKey: '__prompt__'}
const EMPTY_FEED_ITEM = {_reactKey: '__empty__'} const EMPTY_FEED_ITEM = {_reactKey: '__empty__'}
@ -80,7 +80,6 @@ export const Feed = observer(function Feed({
{feed.isLoading && !data && <PostFeedLoadingPlaceholder />} {feed.isLoading && !data && <PostFeedLoadingPlaceholder />}
{feed.hasError && ( {feed.hasError && (
<ErrorMessage <ErrorMessage
dark
message={feed.error} message={feed.error}
style={{margin: 6}} style={{margin: 6}}
onPressTryAgain={onPressTryAgain} onPressTryAgain={onPressTryAgain}

View File

@ -8,12 +8,12 @@ import {AppBskyFeedPost} from '@atproto/api'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {FeedItemModel} from '../../../state/models/feed-view' import {FeedItemModel} from '../../../state/models/feed-view'
import {Link} from '../util/Link' import {Link} from '../util/Link'
import {Text} from '../util/Text' import {Text} from '../util/text/Text'
import {UserInfoText} from '../util/UserInfoText' import {UserInfoText} from '../util/UserInfoText'
import {PostMeta} from '../util/PostMeta' import {PostMeta} from '../util/PostMeta'
import {PostCtrls} from '../util/PostCtrls' import {PostCtrls} from '../util/PostCtrls'
import {PostEmbeds} from '../util/PostEmbeds' import {PostEmbeds} from '../util/PostEmbeds'
import {RichText} from '../util/RichText' import {RichText} from '../util/text/RichText'
import * as Toast from '../util/Toast' import * as Toast from '../util/Toast'
import {UserAvatar} from '../util/UserAvatar' import {UserAvatar} from '../util/UserAvatar'
import {s, colors} from '../../lib/styles' import {s, colors} from '../../lib/styles'

View File

@ -1,7 +1,7 @@
import React from 'react' import React from 'react'
import {StyleSheet, TouchableOpacity, View} from 'react-native' import {StyleSheet, TouchableOpacity, View} from 'react-native'
import {Link} from '../util/Link' import {Link} from '../util/Link'
import {Text} from '../util/Text' import {Text} from '../util/text/Text'
import {UserAvatar} from '../util/UserAvatar' import {UserAvatar} from '../util/UserAvatar'
import {s, colors} from '../../lib/styles' import {s, colors} from '../../lib/styles'

View File

@ -6,8 +6,8 @@ import {
FollowerItem, FollowerItem,
} from '../../../state/models/user-followers-view' } from '../../../state/models/user-followers-view'
import {Link} from '../util/Link' import {Link} from '../util/Link'
import {Text} from '../util/Text' import {Text} from '../util/text/Text'
import {ErrorMessage} from '../util/ErrorMessage' import {ErrorMessage} from '../util/error/ErrorMessage'
import {UserAvatar} from '../util/UserAvatar' import {UserAvatar} from '../util/UserAvatar'
import {useStores} from '../../../state' import {useStores} from '../../../state'
import {s, colors} from '../../lib/styles' import {s, colors} from '../../lib/styles'
@ -57,7 +57,6 @@ export const ProfileFollowers = observer(function ProfileFollowers({
return ( return (
<View> <View>
<ErrorMessage <ErrorMessage
dark
message={view.error} message={view.error}
style={{margin: 6}} style={{margin: 6}}
onPressTryAgain={onRefresh} onPressTryAgain={onRefresh}

View File

@ -7,8 +7,8 @@ import {
} from '../../../state/models/user-follows-view' } from '../../../state/models/user-follows-view'
import {useStores} from '../../../state' import {useStores} from '../../../state'
import {Link} from '../util/Link' import {Link} from '../util/Link'
import {Text} from '../util/Text' import {Text} from '../util/text/Text'
import {ErrorMessage} from '../util/ErrorMessage' import {ErrorMessage} from '../util/error/ErrorMessage'
import {UserAvatar} from '../util/UserAvatar' import {UserAvatar} from '../util/UserAvatar'
import {s, colors} from '../../lib/styles' import {s, colors} from '../../lib/styles'
@ -57,7 +57,6 @@ export const ProfileFollows = observer(function ProfileFollows({
return ( return (
<View> <View>
<ErrorMessage <ErrorMessage
dark
message={view.error} message={view.error}
style={{margin: 6}} style={{margin: 6}}
onPressTryAgain={onRefresh} onPressTryAgain={onRefresh}

View File

@ -16,11 +16,11 @@ import {
import {pluralize} from '../../../lib/strings' import {pluralize} from '../../../lib/strings'
import {s, colors} from '../../lib/styles' import {s, colors} from '../../lib/styles'
import {getGradient} from '../../lib/asset-gen' 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 * as Toast from '../util/Toast'
import {LoadingPlaceholder} from '../util/LoadingPlaceholder' import {LoadingPlaceholder} from '../util/LoadingPlaceholder'
import {Text} from '../util/Text' import {Text} from '../util/text/Text'
import {RichText} from '../util/RichText' import {RichText} from '../util/text/RichText'
import {UserAvatar} from '../util/UserAvatar' import {UserAvatar} from '../util/UserAvatar'
import {UserBanner} from '../util/UserBanner' import {UserBanner} from '../util/UserBanner'
import {UserInfoText} from '../util/UserInfoText' import {UserInfoText} from '../util/UserInfoText'
@ -195,11 +195,11 @@ export const ProfileHeader = observer(function ProfileHeader({
</> </>
)} )}
{dropdownItems?.length ? ( {dropdownItems?.length ? (
<DropdownBtn <DropdownButton
items={dropdownItems} items={dropdownItems}
style={[styles.btn, styles.secondaryBtn]}> style={[styles.btn, styles.secondaryBtn]}>
<FontAwesomeIcon icon="ellipsis" style={[s.gray5]} /> <FontAwesomeIcon icon="ellipsis" style={[s.gray5]} />
</DropdownBtn> </DropdownButton>
) : undefined} ) : undefined}
</View> </View>
<View style={styles.displayNameLine}> <View style={styles.displayNameLine}>

View File

@ -3,7 +3,7 @@ import {observer} from 'mobx-react-lite'
import {ActivityIndicator, FlatList, View} from 'react-native' import {ActivityIndicator, FlatList, View} from 'react-native'
import {MembersViewModel, MemberItem} from '../../../state/models/members-view' import {MembersViewModel, MemberItem} from '../../../state/models/members-view'
import {ProfileCard} from './ProfileCard' import {ProfileCard} from './ProfileCard'
import {ErrorMessage} from '../util/ErrorMessage' import {ErrorMessage} from '../util/error/ErrorMessage'
import {useStores} from '../../../state' import {useStores} from '../../../state'
export const ProfileMembers = observer(function ProfileMembers({ export const ProfileMembers = observer(function ProfileMembers({
@ -49,7 +49,6 @@ export const ProfileMembers = observer(function ProfileMembers({
return ( return (
<View> <View>
<ErrorMessage <ErrorMessage
dark
message={view.error} message={view.error}
style={{margin: 6}} style={{margin: 6}}
onPressTryAgain={onRefresh} onPressTryAgain={onRefresh}

View File

@ -2,9 +2,9 @@ import React from 'react'
import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native' import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native'
import {IconProp} from '@fortawesome/fontawesome-svg-core' import {IconProp} from '@fortawesome/fontawesome-svg-core'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {Text} from './Text' import {Text} from './text/Text'
import {UserGroupIcon} from '../../lib/icons' import {UserGroupIcon} from '../../lib/icons'
import {colors} from '../../lib/styles' import {usePalette} from '../../lib/hooks/usePalette'
export function EmptyState({ export function EmptyState({
icon, icon,
@ -15,16 +15,23 @@ export function EmptyState({
message: string message: string
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
}) { }) {
const pal = usePalette('default')
return ( return (
<View style={[styles.container, style]}> <View style={[styles.container, style]}>
<View style={styles.iconContainer}> <View style={styles.iconContainer}>
{icon === 'user-group' ? ( {icon === 'user-group' ? (
<UserGroupIcon size="64" style={styles.icon} /> <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> </View>
<Text style={styles.text}>{message}</Text> <Text type="body1" style={[pal.textLight, styles.text]}>
{message}
</Text>
</View> </View>
) )
} }
@ -40,12 +47,9 @@ const styles = StyleSheet.create({
icon: { icon: {
marginLeft: 'auto', marginLeft: 'auto',
marginRight: 'auto', marginRight: 'auto',
color: colors.gray3,
}, },
text: { text: {
textAlign: 'center', textAlign: 'center',
color: colors.gray5,
paddingTop: 16, paddingTop: 16,
fontSize: 16,
}, },
}) })

View File

@ -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,
},
})

View File

@ -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: {},
})

View File

@ -9,7 +9,8 @@ import {
View, View,
ViewStyle, ViewStyle,
} from 'react-native' } from 'react-native'
import {Text} from './Text' import {Text} from './text/Text'
import {TypographyVariant} from '../../lib/ThemeContext'
import {useStores, RootStoreModel} from '../../../state' import {useStores, RootStoreModel} from '../../../state'
import {convertBskyAppUrlIfNeeded} from '../../../lib/strings' import {convertBskyAppUrlIfNeeded} from '../../../lib/strings'
@ -57,14 +58,14 @@ export const Link = observer(function Link({
}) })
export const TextLink = observer(function Link({ export const TextLink = observer(function Link({
type = 'body1',
style, style,
href, href,
title,
text, text,
}: { }: {
type: TypographyVariant
style?: StyleProp<TextStyle> style?: StyleProp<TextStyle>
href: string href: string
title?: string
text: string text: string
}) { }) {
const store = useStores() const store = useStores()
@ -75,7 +76,7 @@ export const TextLink = observer(function Link({
handleLink(store, href, true) handleLink(store, href, true)
} }
return ( return (
<Text style={style} onPress={onPress} onLongPress={onLongPress}> <Text type={type} style={style} onPress={onPress} onLongPress={onLongPress}>
{text} {text}
</Text> </Text>
) )

View File

@ -3,6 +3,7 @@ import {StyleSheet, StyleProp, View, ViewStyle} from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {UpIcon} from '../../lib/icons' import {UpIcon} from '../../lib/icons'
import {s, colors} from '../../lib/styles' import {s, colors} from '../../lib/styles'
import {useTheme} from '../../lib/ThemeContext'
export function LoadingPlaceholder({ export function LoadingPlaceholder({
width, width,
@ -13,13 +14,14 @@ export function LoadingPlaceholder({
height: string | number height: string | number
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
}) { }) {
const theme = useTheme()
return ( return (
<View <View
style={[ style={[
{ {
width, width,
height, height,
backgroundColor: '#e7e9ea', backgroundColor: theme.palette.default.backgroundLight,
borderRadius: 6, borderRadius: 6,
overflow: 'hidden', overflow: 'hidden',
}, },
@ -29,7 +31,7 @@ export function LoadingPlaceholder({
style={{ style={{
width, width,
height, height,
backgroundColor: '#e7e9ea', backgroundColor: theme.palette.default.backgroundLight,
}} }}
/> />
</View> </View>
@ -41,6 +43,7 @@ export function PostLoadingPlaceholder({
}: { }: {
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
}) { }) {
const theme = useTheme()
return ( return (
<View style={[styles.post, style]}> <View style={[styles.post, style]}>
<LoadingPlaceholder width={50} height={50} style={styles.avatar} /> <LoadingPlaceholder width={50} height={50} style={styles.avatar} />
@ -52,16 +55,24 @@ export function PostLoadingPlaceholder({
<View style={s.flexRow}> <View style={s.flexRow}>
<View style={s.flex1}> <View style={s.flex1}>
<FontAwesomeIcon <FontAwesomeIcon
style={s.gray3} style={{color: theme.palette.default.icon}}
icon={['far', 'comment']} icon={['far', 'comment']}
size={14} size={14}
/> />
</View> </View>
<View style={s.flex1}> <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>
<View style={s.flex1}> <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>
<View style={s.flex1}></View> <View style={s.flex1}></View>
</View> </View>
@ -125,8 +136,6 @@ export function NotificationFeedLoadingPlaceholder() {
const styles = StyleSheet.create({ const styles = StyleSheet.create({
post: { post: {
flexDirection: 'row', flexDirection: 'row',
backgroundColor: colors.white,
borderRadius: 6,
padding: 10, padding: 10,
margin: 1, margin: 1,
}, },
@ -135,8 +144,6 @@ const styles = StyleSheet.create({
marginRight: 10, marginRight: 10,
}, },
notification: { notification: {
backgroundColor: colors.white,
borderRadius: 6,
padding: 10, padding: 10,
paddingLeft: 46, paddingLeft: 46,
margin: 1, margin: 1,

View File

@ -1,3 +1,5 @@
// TODO: replaceme with something in the design system
import React, {useRef} from 'react' import React, {useRef} from 'react'
import { import {
StyleProp, StyleProp,
@ -13,7 +15,7 @@ import {
FontAwesomeIconStyle, FontAwesomeIconStyle,
} from '@fortawesome/react-native-fontawesome' } from '@fortawesome/react-native-fontawesome'
import RootSiblings from 'react-native-root-siblings' import RootSiblings from 'react-native-root-siblings'
import {Text} from './Text' import {Text} from './text/Text'
import {colors} from '../../lib/styles' import {colors} from '../../lib/styles'
interface PickerItem { interface PickerItem {

View File

@ -2,10 +2,10 @@ import React from 'react'
import {Animated, StyleSheet, TouchableOpacity, View} from 'react-native' import {Animated, StyleSheet, TouchableOpacity, View} from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import ReactNativeHapticFeedback from 'react-native-haptic-feedback' import ReactNativeHapticFeedback from 'react-native-haptic-feedback'
import {Text} from './Text' import {Text} from './text/Text'
import {UpIcon, UpIconSolid} from '../../lib/icons' import {UpIcon, UpIconSolid} from '../../lib/icons'
import {s, colors} from '../../lib/styles' import {s, colors} from '../../lib/styles'
import {useAnimatedValue} from '../../lib/useAnimatedValue' import {useAnimatedValue} from '../../lib/hooks/useAnimatedValue'
interface PostCtrlsOpts { interface PostCtrlsOpts {
big?: boolean big?: boolean

View File

@ -2,7 +2,7 @@ import React from 'react'
import {ImageStyle, StyleSheet, StyleProp, View, ViewStyle} from 'react-native' import {ImageStyle, StyleSheet, StyleProp, View, ViewStyle} from 'react-native'
import {AppBskyEmbedImages, AppBskyEmbedExternal} from '@atproto/api' import {AppBskyEmbedImages, AppBskyEmbedExternal} from '@atproto/api'
import {Link} from '../util/Link' import {Link} from '../util/Link'
import {Text} from '../util/Text' import {Text} from './text/Text'
import {colors} from '../../lib/styles' import {colors} from '../../lib/styles'
import {AutoSizedImage} from './images/AutoSizedImage' import {AutoSizedImage} from './images/AutoSizedImage'
import {ImagesLightbox} from '../../../state/models/shell-ui' import {ImagesLightbox} from '../../../state/models/shell-ui'

View File

@ -2,8 +2,8 @@ import React from 'react'
import {StyleSheet, View} from 'react-native' import {StyleSheet, View} from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {Link} from '../util/Link' import {Link} from '../util/Link'
import {Text} from '../util/Text' import {Text} from './text/Text'
import {PostDropdownBtn} from '../util/DropdownBtn' import {PostDropdownBtn} from './forms/DropdownButton'
import {s} from '../../lib/styles' import {s} from '../../lib/styles'
import {ago} from '../../../lib/strings' import {ago} from '../../../lib/strings'

View File

@ -5,7 +5,7 @@ import {
TouchableWithoutFeedback, TouchableWithoutFeedback,
View, View,
} from 'react-native' } from 'react-native'
import {Text} from './Text' import {Text} from './text/Text'
import {colors} from '../../lib/styles' import {colors} from '../../lib/styles'
interface Layout { interface Layout {

View File

@ -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>
)
}

View File

@ -2,7 +2,7 @@ import React, {useState, useEffect} from 'react'
import {AppBskyActorGetProfile as GetProfile} from '@atproto/api' import {AppBskyActorGetProfile as GetProfile} from '@atproto/api'
import {StyleProp, TextStyle} from 'react-native' import {StyleProp, TextStyle} from 'react-native'
import {Link} from './Link' import {Link} from './Link'
import {Text} from './Text' import {Text} from './text/Text'
import {LoadingPlaceholder} from './LoadingPlaceholder' import {LoadingPlaceholder} from './LoadingPlaceholder'
import {useStores} from '../../../state' import {useStores} from '../../../state'

View File

@ -8,7 +8,7 @@ import {
} from 'react-native' } from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {UserAvatar} from './UserAvatar' import {UserAvatar} from './UserAvatar'
import {Text} from './Text' import {Text} from './text/Text'
import {s, colors} from '../../lib/styles' import {s, colors} from '../../lib/styles'
import {MagnifyingGlassIcon} from '../../lib/icons' import {MagnifyingGlassIcon} from '../../lib/icons'
import {useStores} from '../../../state' import {useStores} from '../../../state'

View File

@ -7,8 +7,8 @@ import {
} from 'react-native' } from 'react-native'
import {Selector} from './Selector' import {Selector} from './Selector'
import {HorzSwipe} from './gestures/HorzSwipe' import {HorzSwipe} from './gestures/HorzSwipe'
import {useAnimatedValue} from '../../lib/useAnimatedValue' import {useAnimatedValue} from '../../lib/hooks/useAnimatedValue'
import {OnScrollCb} from '../../lib/useOnMainScroll' import {OnScrollCb} from '../../lib/hooks/useOnMainScroll'
const HEADER_ITEM = {_reactKey: '__header__'} const HEADER_ITEM = {_reactKey: '__header__'}
const SELECTOR_ITEM = {_reactKey: '__selector__'} const SELECTOR_ITEM = {_reactKey: '__selector__'}

View File

@ -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,
},
})

View File

@ -1,8 +1,10 @@
import React from 'react' import React from 'react'
import {StyleSheet, TouchableOpacity, View} from 'react-native' import {StyleSheet, TouchableOpacity, View} from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {Text} from './Text' import {Text} from '../text/Text'
import {colors} from '../../lib/styles' import {colors} from '../../../lib/styles'
import {useTheme} from '../../../lib/ThemeContext'
import {usePalette} from '../../../lib/hooks/usePalette'
export function ErrorScreen({ export function ErrorScreen({
title, title,
@ -15,10 +17,16 @@ export function ErrorScreen({
details?: string details?: string
onPressTryAgain?: () => void onPressTryAgain?: () => void
}) { }) {
const theme = useTheme()
const pal = usePalette('error')
return ( return (
<View style={styles.outer}> <View style={[styles.outer, pal.view]}>
<View style={styles.errorIconContainer}> <View style={styles.errorIconContainer}>
<View style={styles.errorIcon}> <View
style={[
styles.errorIcon,
{backgroundColor: theme.palette.error.icon},
]}>
<FontAwesomeIcon <FontAwesomeIcon
icon="exclamation" icon="exclamation"
style={{color: colors.white}} style={{color: colors.white}}
@ -26,18 +34,30 @@ export function ErrorScreen({
/> />
</View> </View>
</View> </View>
<Text style={styles.title}>{title}</Text> <Text type="h3" style={[styles.title, pal.text]}>
<Text style={styles.message}>{message}</Text> {title}
{details && <Text style={styles.details}>{details}</Text>} </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 && ( {onPressTryAgain && (
<View style={styles.btnContainer}> <View style={styles.btnContainer}>
<TouchableOpacity style={styles.btn} onPress={onPressTryAgain}> <TouchableOpacity
<FontAwesomeIcon style={[styles.btn, {backgroundColor: theme.palette.error.icon}]}
icon="arrows-rotate" onPress={onPressTryAgain}>
style={{color: colors.white}} <FontAwesomeIcon icon="arrows-rotate" style={pal.text} size={16} />
size={16} <Text type="button" style={[styles.btnText, pal.text]}>
/> Try again
<Text style={styles.btnText}>Try again</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
)} )}
@ -48,32 +68,19 @@ export function ErrorScreen({
const styles = StyleSheet.create({ const styles = StyleSheet.create({
outer: { outer: {
flex: 1, flex: 1,
backgroundColor: colors.red1,
borderWidth: 1,
borderColor: colors.red3,
borderRadius: 6,
paddingVertical: 30, paddingVertical: 30,
paddingHorizontal: 14, paddingHorizontal: 14,
margin: 10,
}, },
title: { title: {
textAlign: 'center', textAlign: 'center',
color: colors.red4,
fontSize: 24,
marginBottom: 10, marginBottom: 10,
}, },
message: { message: {
textAlign: 'center', textAlign: 'center',
color: colors.red4,
marginBottom: 20, marginBottom: 20,
}, },
details: { details: {
textAlign: 'center', textAlign: 'center',
color: colors.black,
backgroundColor: colors.white,
borderWidth: 1,
borderColor: colors.gray5,
borderRadius: 6,
paddingVertical: 10, paddingVertical: 10,
paddingHorizontal: 14, paddingHorizontal: 14,
overflow: 'hidden', overflow: 'hidden',
@ -85,23 +92,17 @@ const styles = StyleSheet.create({
btn: { btn: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
backgroundColor: colors.red4,
borderRadius: 6,
paddingHorizontal: 16, paddingHorizontal: 16,
paddingVertical: 10, paddingVertical: 10,
}, },
btnText: { btnText: {
marginLeft: 5, marginLeft: 5,
color: colors.white,
fontSize: 16,
fontWeight: 'bold',
}, },
errorIconContainer: { errorIconContainer: {
alignItems: 'center', alignItems: 'center',
marginBottom: 10, marginBottom: 10,
}, },
errorIcon: { errorIcon: {
backgroundColor: colors.red4,
borderRadius: 30, borderRadius: 30,
width: 50, width: 50,
height: 50, height: 50,

View File

@ -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,
},
})

View File

@ -11,12 +11,13 @@ import {
import {IconProp} from '@fortawesome/fontawesome-svg-core' import {IconProp} from '@fortawesome/fontawesome-svg-core'
import RootSiblings from 'react-native-root-siblings' import RootSiblings from 'react-native-root-siblings'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {Text} from './Text' import {Text} from '../text/Text'
import {colors} from '../../lib/styles' import {Button, ButtonType} from './Button'
import {toShareUrl} from '../../../lib/strings' import {colors} from '../../../lib/styles'
import {useStores} from '../../../state' import {toShareUrl} from '../../../../lib/strings'
import {ReportPostModal, ConfirmModal} from '../../../state/models/shell-ui' import {useStores} from '../../../../state'
import {TABS_ENABLED} from '../../../build-flags' import {ReportPostModal, ConfirmModal} from '../../../../state/models/shell-ui'
import {TABS_ENABLED} from '../../../../build-flags'
const HITSLOP = {left: 10, top: 10, right: 10, bottom: 10} const HITSLOP = {left: 10, top: 10, right: 10, bottom: 10}
@ -26,14 +27,20 @@ export interface DropdownItem {
onPress: () => void onPress: () => void
} }
export function DropdownBtn({ export type DropdownButtonType = ButtonType | 'bare'
export function DropdownButton({
type = 'bare',
style, style,
items, items,
label,
menuWidth, menuWidth,
children, children,
}: { }: {
type: DropdownButtonType
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
items: DropdownItem[] items: DropdownItem[]
label?: string
menuWidth?: number menuWidth?: number
children?: React.ReactNode 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 ( return (
<TouchableOpacity <View ref={ref}>
style={style} <Button onPress={onPress} style={style} label={label}>
onPress={onPress} {children}
hitSlop={HITSLOP} </Button>
ref={ref}> </View>
{children}
</TouchableOpacity>
) )
} }
@ -77,7 +93,6 @@ export function PostDropdownBtn({
style, style,
children, children,
itemHref, itemHref,
itemTitle,
isAuthor, isAuthor,
onCopyPostText, onCopyPostText,
onDeletePost, onDeletePost,
@ -141,9 +156,9 @@ export function PostDropdownBtn({
].filter(Boolean) as DropdownItem[] ].filter(Boolean) as DropdownItem[]
return ( return (
<DropdownBtn style={style} items={dropdownItems} menuWidth={200}> <DropdownButton style={style} items={dropdownItems} menuWidth={200}>
{children} {children}
</DropdownBtn> </DropdownButton>
) )
} }

View File

@ -1,24 +1,126 @@
import React from 'react' import React from 'react'
import {StyleSheet, TouchableOpacity, View} from 'react-native' import {StyleProp, StyleSheet, TextStyle, View, ViewStyle} from 'react-native'
import {Text} from '../Text' import {Text} from '../text/Text'
import {colors} from '../../../lib/styles' import {Button, ButtonType} from './Button'
import {useTheme} from '../../../lib/ThemeContext'
import {choose} from '../../../../lib/functions'
export function RadioButton({ export function RadioButton({
type = 'default-light',
label, label,
isSelected, isSelected,
style,
onPress, onPress,
}: { }: {
type?: ButtonType
label: string label: string
isSelected: boolean isSelected: boolean
style?: StyleProp<ViewStyle>
onPress: () => void 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 ( return (
<TouchableOpacity style={styles.outer} onPress={onPress}> <Button type={type} onPress={onPress} style={style}>
<View style={styles.circle}> <View style={styles.outer}>
{isSelected ? <View style={styles.circleFill} /> : undefined} <View style={[circleStyle, styles.circle]}>
{isSelected ? (
<View style={[circleFillStyle, styles.circleFill]} />
) : undefined}
</View>
<Text type="button" style={[labelStyle, styles.label]}>
{label}
</Text>
</View> </View>
<Text style={styles.label}>{label}</Text> </Button>
</TouchableOpacity>
) )
} }
@ -26,30 +128,21 @@ const styles = StyleSheet.create({
outer: { outer: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
marginBottom: 5,
borderRadius: 8,
borderWidth: 1,
borderColor: colors.gray2,
paddingHorizontal: 10,
paddingVertical: 8,
}, },
circle: { circle: {
width: 30, width: 26,
height: 30, height: 26,
borderRadius: 15, borderRadius: 15,
padding: 4, padding: 4,
borderWidth: 1, borderWidth: 1,
borderColor: colors.gray3,
marginRight: 10, marginRight: 10,
}, },
circleFill: { circleFill: {
width: 20, width: 16,
height: 20, height: 16,
borderRadius: 10, borderRadius: 10,
backgroundColor: colors.blue3,
}, },
label: { label: {
flex: 1, flex: 1,
fontSize: 17,
}, },
}) })

View File

@ -1,6 +1,7 @@
import React, {useState} from 'react' import React, {useState} from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import {RadioButton} from './RadioButton' import {RadioButton} from './RadioButton'
import {ButtonType} from './Button'
export interface RadioGroupItem { export interface RadioGroupItem {
label: string label: string
@ -8,22 +9,28 @@ export interface RadioGroupItem {
} }
export function RadioGroup({ export function RadioGroup({
type,
items, items,
initialSelection = '',
onSelect, onSelect,
}: { }: {
type?: ButtonType
items: RadioGroupItem[] items: RadioGroupItem[]
initialSelection?: string
onSelect: (key: string) => void onSelect: (key: string) => void
}) { }) {
const [selection, setSelection] = useState<string>('') const [selection, setSelection] = useState<string>(initialSelection)
const onSelectInner = (key: string) => { const onSelectInner = (key: string) => {
setSelection(key) setSelection(key)
onSelect(key) onSelect(key)
} }
return ( return (
<View> <View>
{items.map(item => ( {items.map((item, i) => (
<RadioButton <RadioButton
key={item.key} key={item.key}
style={i !== 0 ? {marginTop: 2} : undefined}
type={type}
label={item.label} label={item.label}
isSelected={item.key === selection} isSelected={item.key === selection}
onPress={() => onSelectInner(item.key)} onPress={() => onSelectInner(item.key)}

View File

@ -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,
},
})

View File

@ -9,7 +9,7 @@ import {
View, View,
ViewStyle, ViewStyle,
} from 'react-native' } from 'react-native'
import {Text} from '../Text' import {Text} from '../text/Text'
import {colors} from '../../../lib/styles' import {colors} from '../../../lib/styles'
const MAX_HEIGHT = 300 const MAX_HEIGHT = 300

View File

@ -1,9 +1,11 @@
import React from 'react' import React from 'react'
import {TextStyle, StyleProp} from 'react-native' import {TextStyle, StyleProp} from 'react-native'
import {TextLink} from './Link' import {TextLink} from '../Link'
import {Text} from './Text' import {Text} from './Text'
import {s} from '../../lib/styles' import {s} from '../../../lib/styles'
import {toShortUrl} from '../../../lib/strings' import {toShortUrl} from '../../../../lib/strings'
import {TypographyVariant} from '../../../lib/ThemeContext'
import {usePalette} from '../../../lib/hooks/usePalette'
type TextSlice = {start: number; end: number} type TextSlice = {start: number; end: number}
type Entity = { type Entity = {
@ -13,16 +15,19 @@ type Entity = {
} }
export function RichText({ export function RichText({
type = 'body1',
text, text,
entities, entities,
style, style,
numberOfLines, numberOfLines,
}: { }: {
type: TypographyVariant
text: string text: string
entities?: Entity[] entities?: Entity[]
style?: StyleProp<TextStyle> style?: StyleProp<TextStyle>
numberOfLines?: number numberOfLines?: number
}) { }) {
const pal = usePalette('default')
if (!entities?.length) { if (!entities?.length) {
if (/^\p{Extended_Pictographic}+$/u.test(text) && text.length <= 5) { if (/^\p{Extended_Pictographic}+$/u.test(text) && text.length <= 5) {
style = { style = {
@ -47,18 +52,20 @@ export function RichText({
els.push( els.push(
<TextLink <TextLink
key={key} key={key}
type={type}
text={segment.text} text={segment.text}
href={`/profile/${segment.entity.value}`} href={`/profile/${segment.entity.value}`}
style={[style, s.blue3]} style={[style, pal.link]}
/>, />,
) )
} else if (segment.entity.type === 'link') { } else if (segment.entity.type === 'link') {
els.push( els.push(
<TextLink <TextLink
key={key} key={key}
type={type}
text={toShortUrl(segment.text)} text={toShortUrl(segment.text)}
href={segment.entity.value} href={segment.entity.value}
style={[style, s.blue3]} style={[style, pal.link]}
/>, />,
) )
} }
@ -66,7 +73,7 @@ export function RichText({
key++ key++
} }
return ( return (
<Text style={style} numberOfLines={numberOfLines}> <Text type={type} style={[pal.text, style]} numberOfLines={numberOfLines}>
{els} {els}
</Text> </Text>
) )

View File

@ -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>
)
}

View File

@ -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>
}

View File

@ -1,6 +1,6 @@
import {useState} from 'react' import {useState} from 'react'
import {NativeSyntheticEvent, NativeScrollEvent} from 'react-native' import {NativeSyntheticEvent, NativeScrollEvent} from 'react-native'
import {RootStoreModel} from '../../state' import {RootStoreModel} from '../../../state'
export type OnScrollCb = ( export type OnScrollCb = (
event: NativeSyntheticEvent<NativeScrollEvent>, event: NativeSyntheticEvent<NativeScrollEvent>,

View File

@ -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,
},
}
}

View File

@ -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,
},
},
}

View File

@ -1,2 +0,0 @@
export const FAB = 1
export const BASE = 0

View File

@ -15,6 +15,7 @@ import {ProfileFollowers} from './screens/ProfileFollowers'
import {ProfileFollows} from './screens/ProfileFollows' import {ProfileFollows} from './screens/ProfileFollows'
import {ProfileMembers} from './screens/ProfileMembers' import {ProfileMembers} from './screens/ProfileMembers'
import {Settings} from './screens/Settings' import {Settings} from './screens/Settings'
import {Debug} from './screens/Debug'
export type ScreenParams = { export type ScreenParams = {
navIdx: [number, number] navIdx: [number, number]
@ -71,6 +72,7 @@ export const routes: Route[] = [
'retweet', 'retweet',
r('/profile/(?<name>[^/]+)/post/(?<rkey>[^/]+)/reposted-by'), r('/profile/(?<name>[^/]+)/post/(?<rkey>[^/]+)/reposted-by'),
], ],
[Debug, 'Debug', 'house', r('/debug')],
] ]
export function match(url: string): MatchResult { export function match(url: string): MatchResult {

View File

@ -3,11 +3,11 @@ import {StyleSheet, TextInput, View} from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {ProfileFollows as ProfileFollowsComponent} from '../com/profile/ProfileFollows' import {ProfileFollows as ProfileFollowsComponent} from '../com/profile/ProfileFollows'
import {Selector} from '../com/util/Selector' 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 {colors} from '../lib/styles'
import {ScreenParams} from '../routes' import {ScreenParams} from '../routes'
import {useStores} from '../../state' import {useStores} from '../../state'
import {useAnimatedValue} from '../lib/useAnimatedValue' import {useAnimatedValue} from '../lib/hooks/useAnimatedValue'
export const Contacts = ({navIdx, visible, params}: ScreenParams) => { export const Contacts = ({navIdx, visible, params}: ScreenParams) => {
const store = useStores() const store = useStores()

View File

@ -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>
)
}

View File

@ -6,11 +6,11 @@ import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {useSafeAreaInsets} from 'react-native-safe-area-context' import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {ViewHeader} from '../com/util/ViewHeader' import {ViewHeader} from '../com/util/ViewHeader'
import {Feed} from '../com/posts/Feed' import {Feed} from '../com/posts/Feed'
import {Text} from '../com/util/Text' import {Text} from '../com/util/text/Text'
import {useStores} from '../../state' import {useStores} from '../../state'
import {ScreenParams} from '../routes' import {ScreenParams} from '../routes'
import {s, colors} from '../lib/styles' import {s, colors} from '../lib/styles'
import {useOnMainScroll} from '../lib/useOnMainScroll' import {useOnMainScroll} from '../lib/hooks/useOnMainScroll'
import {clamp} from 'lodash' import {clamp} from 'lodash'
const HITSLOP = {left: 20, top: 20, right: 20, bottom: 20} const HITSLOP = {left: 20, top: 20, right: 20, bottom: 20}

View File

@ -10,7 +10,7 @@ import {observer} from 'mobx-react-lite'
import {Signin} from '../com/login/Signin' import {Signin} from '../com/login/Signin'
import {Logo} from '../com/login/Logo' import {Logo} from '../com/login/Logo'
import {CreateAccount} from '../com/login/CreateAccount' 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' import {s, colors} from '../lib/styles'
enum ScreenState { enum ScreenState {

View File

@ -1,7 +1,7 @@
import React from 'react' import React from 'react'
import {Button, View} from 'react-native' import {Button, View} from 'react-native'
import {ViewHeader} from '../com/util/ViewHeader' import {ViewHeader} from '../com/util/ViewHeader'
import {Text} from '../com/util/Text' import {Text} from '../com/util/text/Text'
import {useStores} from '../../state' import {useStores} from '../../state'
export const NotFound = () => { export const NotFound = () => {

View File

@ -4,7 +4,7 @@ import {ViewHeader} from '../com/util/ViewHeader'
import {Feed} from '../com/notifications/Feed' import {Feed} from '../com/notifications/Feed'
import {useStores} from '../../state' import {useStores} from '../../state'
import {ScreenParams} from '../routes' import {ScreenParams} from '../routes'
import {useOnMainScroll} from '../lib/useOnMainScroll' import {useOnMainScroll} from '../lib/hooks/useOnMainScroll'
export const Notifications = ({navIdx, visible}: ScreenParams) => { export const Notifications = ({navIdx, visible}: ScreenParams) => {
const store = useStores() const store = useStores()

View File

@ -12,14 +12,14 @@ import {ProfileHeader} from '../com/profile/ProfileHeader'
import {FeedItem} from '../com/posts/FeedItem' import {FeedItem} from '../com/posts/FeedItem'
import {ProfileCard} from '../com/profile/ProfileCard' import {ProfileCard} from '../com/profile/ProfileCard'
import {PostFeedLoadingPlaceholder} from '../com/util/LoadingPlaceholder' import {PostFeedLoadingPlaceholder} from '../com/util/LoadingPlaceholder'
import {ErrorScreen} from '../com/util/ErrorScreen' import {ErrorScreen} from '../com/util/error/ErrorScreen'
import {ErrorMessage} from '../com/util/ErrorMessage' import {ErrorMessage} from '../com/util/error/ErrorMessage'
import {EmptyState} from '../com/util/EmptyState' 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 {ViewHeader} from '../com/util/ViewHeader'
import * as Toast from '../com/util/Toast' import * as Toast from '../com/util/Toast'
import {s, colors} from '../lib/styles' import {s, colors} from '../lib/styles'
import {useOnMainScroll} from '../lib/useOnMainScroll' import {useOnMainScroll} from '../lib/hooks/useOnMainScroll'
const LOADING_ITEM = {_reactKey: '__loading__'} const LOADING_ITEM = {_reactKey: '__loading__'}
const END_ITEM = {_reactKey: '__end__'} const END_ITEM = {_reactKey: '__end__'}
@ -116,7 +116,6 @@ export const Profile = observer(({navIdx, visible, params}: ScreenParams) => {
renderItem = (item: any) => ( renderItem = (item: any) => (
<View style={s.p5}> <View style={s.p5}>
<ErrorMessage <ErrorMessage
dark
message={item.error} message={item.error}
onPressTryAgain={onPressTryAgain} onPressTryAgain={onPressTryAgain}
/> />

View File

@ -10,7 +10,7 @@ import {
import {ViewHeader} from '../com/util/ViewHeader' import {ViewHeader} from '../com/util/ViewHeader'
import {SuggestedFollows} from '../com/discover/SuggestedFollows' import {SuggestedFollows} from '../com/discover/SuggestedFollows'
import {UserAvatar} from '../com/util/UserAvatar' import {UserAvatar} from '../com/util/UserAvatar'
import {Text} from '../com/util/Text' import {Text} from '../com/util/text/Text'
import {ScreenParams} from '../routes' import {ScreenParams} from '../routes'
import {useStores} from '../../state' import {useStores} from '../../state'
import {UserAutocompleteViewModel} from '../../state/models/user-autocomplete-view' import {UserAutocompleteViewModel} from '../../state/models/user-autocomplete-view'

View File

@ -6,7 +6,7 @@ import {ScreenParams} from '../routes'
import {s, colors} from '../lib/styles' import {s, colors} from '../lib/styles'
import {ViewHeader} from '../com/util/ViewHeader' import {ViewHeader} from '../com/util/ViewHeader'
import {Link} from '../com/util/Link' 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' import {UserAvatar} from '../com/util/UserAvatar'
export const Settings = observer(function Settings({ export const Settings = observer(function Settings({
@ -57,6 +57,9 @@ export const Settings = observer(function Settings({
</View> </View>
</View> </View>
</Link> </Link>
<Link href="/debug" title="Debug tools">
<Text style={s.blue3}>Debug tools</Text>
</Link>
</View> </View>
</View> </View>
) )

View File

@ -3,7 +3,7 @@ import {observer} from 'mobx-react-lite'
import {Animated, Easing, Platform, StyleSheet, View} from 'react-native' import {Animated, Easing, Platform, StyleSheet, View} from 'react-native'
import {ComposePost} from '../../com/composer/ComposePost' import {ComposePost} from '../../com/composer/ComposePost'
import {ComposerOpts} from '../../../state/models/shell-ui' import {ComposerOpts} from '../../../state/models/shell-ui'
import {useAnimatedValue} from '../../lib/useAnimatedValue' import {useAnimatedValue} from '../../lib/hooks/useAnimatedValue'
export const Composer = observer( export const Composer = observer(
({ ({

View File

@ -17,7 +17,7 @@ import {
MagnifyingGlassIcon, MagnifyingGlassIcon,
} from '../../lib/icons' } from '../../lib/icons'
import {UserAvatar} from '../../com/util/UserAvatar' 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' import {CreateSceneModal} from '../../../state/models/shell-ui'
export const Menu = ({ export const Menu = ({

View File

@ -10,13 +10,13 @@ import {
} from 'react-native' } from 'react-native'
import {useSafeAreaInsets} from 'react-native-safe-area-context' import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 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 Swipeable from 'react-native-gesture-handler/Swipeable'
import {useStores} from '../../../state' import {useStores} from '../../../state'
import {s, colors} from '../../lib/styles' import {s, colors} from '../../lib/styles'
import {toShareUrl} from '../../../lib/strings' import {toShareUrl} from '../../../lib/strings'
import {match} from '../../routes' import {match} from '../../routes'
import {useAnimatedValue} from '../../lib/useAnimatedValue' import {useAnimatedValue} from '../../lib/hooks/useAnimatedValue'
const TAB_HEIGHT = 42 const TAB_HEIGHT = 42

View File

@ -29,7 +29,7 @@ import {Onboard} from '../../screens/Onboard'
import {HorzSwipe} from '../../com/util/gestures/HorzSwipe' import {HorzSwipe} from '../../com/util/gestures/HorzSwipe'
import {Modal} from '../../com/modals/Modal' import {Modal} from '../../com/modals/Modal'
import {Lightbox} from '../../com/lightbox/Lightbox' import {Lightbox} from '../../com/lightbox/Lightbox'
import {Text} from '../../com/util/Text' import {Text} from '../../com/util/text/Text'
import {TabsSelector} from './TabsSelector' import {TabsSelector} from './TabsSelector'
import {Composer} from './Composer' import {Composer} from './Composer'
import {s, colors} from '../../lib/styles' import {s, colors} from '../../lib/styles'
@ -42,7 +42,7 @@ import {
BellIcon, BellIcon,
BellIconSolid, BellIconSolid,
} from '../../lib/icons' } from '../../lib/icons'
import {useAnimatedValue} from '../../lib/useAnimatedValue' import {useAnimatedValue} from '../../lib/hooks/useAnimatedValue'
const Btn = ({ const Btn = ({
icon, icon,