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 systemzio/stable
parent
cc63660982
commit
7e31645e9a
|
@ -5,6 +5,7 @@ import {RootSiblingParent} from 'react-native-root-siblings'
|
|||
import {GestureHandlerRootView} from 'react-native-gesture-handler'
|
||||
import SplashScreen from 'react-native-splash-screen'
|
||||
import {SafeAreaProvider} from 'react-native-safe-area-context'
|
||||
import {ThemeProvider} from './view/lib/ThemeContext'
|
||||
import * as view from './view/index'
|
||||
import {RootStoreModel, setupState, RootStoreProvider} from './state'
|
||||
import {MobileShell} from './view/shell/mobile'
|
||||
|
@ -40,9 +41,11 @@ function App() {
|
|||
<GestureHandlerRootView style={{flex: 1}}>
|
||||
<RootSiblingParent>
|
||||
<RootStoreProvider value={rootStore}>
|
||||
<SafeAreaProvider>
|
||||
<MobileShell />
|
||||
</SafeAreaProvider>
|
||||
<ThemeProvider>
|
||||
<SafeAreaProvider>
|
||||
<MobileShell />
|
||||
</SafeAreaProvider>
|
||||
</ThemeProvider>
|
||||
</RootStoreProvider>
|
||||
</RootSiblingParent>
|
||||
</GestureHandlerRootView>
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
export function choose<U, T extends Record<string, U>>(
|
||||
value: keyof T,
|
||||
choices: T,
|
||||
): U {
|
||||
return choices[value]
|
||||
}
|
|
@ -5,8 +5,8 @@ import {
|
|||
StyleSheet,
|
||||
useWindowDimensions,
|
||||
} from 'react-native'
|
||||
import {useAnimatedValue} from '../../lib/useAnimatedValue'
|
||||
import {Text} from '../util/Text'
|
||||
import {useAnimatedValue} from '../../lib/hooks/useAnimatedValue'
|
||||
import {Text} from '../util/text/Text'
|
||||
import {colors} from '../../lib/styles'
|
||||
|
||||
interface AutocompleteItem {
|
||||
|
|
|
@ -15,7 +15,7 @@ import LinearGradient from 'react-native-linear-gradient'
|
|||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {UserAutocompleteViewModel} from '../../../state/models/user-autocomplete-view'
|
||||
import {Autocomplete} from './Autocomplete'
|
||||
import {Text} from '../util/Text'
|
||||
import {Text} from '../util/text/Text'
|
||||
import * as Toast from '../util/Toast'
|
||||
// @ts-ignore no type definition -prf
|
||||
import ProgressCircle from 'react-native-progress/Circle'
|
||||
|
|
|
@ -3,7 +3,7 @@ import {StyleSheet, TouchableOpacity, View} from 'react-native'
|
|||
import {colors} from '../../lib/styles'
|
||||
import {useStores} from '../../../state'
|
||||
import {UserAvatar} from '../util/UserAvatar'
|
||||
import {Text} from '../util/Text'
|
||||
import {Text} from '../util/text/Text'
|
||||
|
||||
export function ComposePrompt({
|
||||
noAvi = false,
|
||||
|
|
|
@ -10,9 +10,9 @@ import LinearGradient from 'react-native-linear-gradient'
|
|||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {observer} from 'mobx-react-lite'
|
||||
import _omit from 'lodash.omit'
|
||||
import {ErrorScreen} from '../util/ErrorScreen'
|
||||
import {ErrorScreen} from '../util/error/ErrorScreen'
|
||||
import {Link} from '../util/Link'
|
||||
import {Text} from '../util/Text'
|
||||
import {Text} from '../util/text/Text'
|
||||
import {UserAvatar} from '../util/UserAvatar'
|
||||
import * as Toast from '../util/Toast'
|
||||
import {useStores} from '../../../state'
|
||||
|
|
|
@ -10,7 +10,7 @@ import {observer} from 'mobx-react-lite'
|
|||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {SwipeAndZoom, Dir} from '../util/gestures/SwipeAndZoom'
|
||||
import {useStores} from '../../../state'
|
||||
import {useAnimatedValue} from '../../lib/useAnimatedValue'
|
||||
import {useAnimatedValue} from '../../lib/hooks/useAnimatedValue'
|
||||
|
||||
import * as models from '../../../state/models/shell-ui'
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ import * as EmailValidator from 'email-validator'
|
|||
import {Logo} from './Logo'
|
||||
import {Picker} from '../util/Picker'
|
||||
import {TextLink} from '../util/Link'
|
||||
import {Text} from '../util/Text'
|
||||
import {Text} from '../util/text/Text'
|
||||
import {s, colors} from '../../lib/styles'
|
||||
import {
|
||||
makeValidHandle,
|
||||
|
|
|
@ -12,7 +12,7 @@ import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
|||
import * as EmailValidator from 'email-validator'
|
||||
import {sessionClient as AtpApi, SessionServiceClient} from '@atproto/api'
|
||||
import {Logo} from './Logo'
|
||||
import {Text} from '../util/Text'
|
||||
import {Text} from '../util/text/Text'
|
||||
import {s, colors} from '../../lib/styles'
|
||||
import {createFullHandle, toNiceDomain} from '../../../lib/strings'
|
||||
import {useStores, RootStoreModel, DEFAULT_SERVICE} from '../../../state'
|
||||
|
|
|
@ -6,10 +6,10 @@ import {
|
|||
View,
|
||||
} from 'react-native'
|
||||
import LinearGradient from 'react-native-linear-gradient'
|
||||
import {Text} from '../util/Text'
|
||||
import {Text} from '../util/text/Text'
|
||||
import {useStores} from '../../../state'
|
||||
import {s, colors, gradients} from '../../lib/styles'
|
||||
import {ErrorMessage} from '../util/ErrorMessage'
|
||||
import {ErrorMessage} from '../util/error/ErrorMessage'
|
||||
|
||||
export const snapPoints = ['50%']
|
||||
|
||||
|
|
|
@ -9,8 +9,8 @@ import {
|
|||
import LinearGradient from 'react-native-linear-gradient'
|
||||
import {BottomSheetScrollView, BottomSheetTextInput} from '@gorhom/bottom-sheet'
|
||||
import {AppBskyActorCreateScene} from '@atproto/api'
|
||||
import {ErrorMessage} from '../util/ErrorMessage'
|
||||
import {Text} from '../util/Text'
|
||||
import {ErrorMessage} from '../util/error/ErrorMessage'
|
||||
import {Text} from '../util/text/Text'
|
||||
import {useStores} from '../../../state'
|
||||
import {s, colors, gradients} from '../../lib/styles'
|
||||
import {
|
||||
|
|
|
@ -9,8 +9,8 @@ import {
|
|||
import LinearGradient from 'react-native-linear-gradient'
|
||||
import {BottomSheetScrollView, BottomSheetTextInput} from '@gorhom/bottom-sheet'
|
||||
import {Image as PickedImage} from 'react-native-image-crop-picker'
|
||||
import {Text} from '../util/Text'
|
||||
import {ErrorMessage} from '../util/ErrorMessage'
|
||||
import {Text} from '../util/text/Text'
|
||||
import {ErrorMessage} from '../util/error/ErrorMessage'
|
||||
import {useStores} from '../../../state'
|
||||
import {ProfileViewModel} from '../../../state/models/profile-view'
|
||||
import {s, colors, gradients} from '../../lib/styles'
|
||||
|
|
|
@ -19,8 +19,8 @@ import {
|
|||
import _omit from 'lodash.omit'
|
||||
import {AtUri} from '../../../third-party/uri'
|
||||
import {ProfileCard} from '../profile/ProfileCard'
|
||||
import {ErrorMessage} from '../util/ErrorMessage'
|
||||
import {Text} from '../util/Text'
|
||||
import {ErrorMessage} from '../util/error/ErrorMessage'
|
||||
import {Text} from '../util/text/Text'
|
||||
import {useStores} from '../../../state'
|
||||
import * as apilib from '../../../state/lib/api'
|
||||
import {ProfileViewModel} from '../../../state/models/profile-view'
|
||||
|
|
|
@ -9,8 +9,8 @@ import LinearGradient from 'react-native-linear-gradient'
|
|||
import {useStores} from '../../../state'
|
||||
import {s, colors, gradients} from '../../lib/styles'
|
||||
import {RadioGroup, RadioGroupItem} from '../util/forms/RadioGroup'
|
||||
import {Text} from '../util/Text'
|
||||
import {ErrorMessage} from '../util/ErrorMessage'
|
||||
import {Text} from '../util/text/Text'
|
||||
import {ErrorMessage} from '../util/error/ErrorMessage'
|
||||
|
||||
const ITEMS: RadioGroupItem[] = [
|
||||
{key: 'spam', label: 'Spam or excessive repeat posts'},
|
||||
|
|
|
@ -9,8 +9,8 @@ import LinearGradient from 'react-native-linear-gradient'
|
|||
import {useStores} from '../../../state'
|
||||
import {s, colors, gradients} from '../../lib/styles'
|
||||
import {RadioGroup, RadioGroupItem} from '../util/forms/RadioGroup'
|
||||
import {Text} from '../util/Text'
|
||||
import {ErrorMessage} from '../util/ErrorMessage'
|
||||
import {Text} from '../util/text/Text'
|
||||
import {ErrorMessage} from '../util/error/ErrorMessage'
|
||||
|
||||
const ITEMS: RadioGroupItem[] = [
|
||||
{key: 'spam', label: 'Spam or excessive repeat posts'},
|
||||
|
|
|
@ -2,7 +2,7 @@ import React, {useState} from 'react'
|
|||
import {Platform, StyleSheet, TouchableOpacity, View} from 'react-native'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {BottomSheetScrollView, BottomSheetTextInput} from '@gorhom/bottom-sheet'
|
||||
import {Text} from '../util/Text'
|
||||
import {Text} from '../util/text/Text'
|
||||
import {useStores} from '../../../state'
|
||||
import {s, colors} from '../../lib/styles'
|
||||
import {
|
||||
|
|
|
@ -4,9 +4,9 @@ import {View, FlatList} from 'react-native'
|
|||
import {NotificationsViewModel} from '../../../state/models/notifications-view'
|
||||
import {FeedItem} from './FeedItem'
|
||||
import {NotificationFeedLoadingPlaceholder} from '../util/LoadingPlaceholder'
|
||||
import {ErrorMessage} from '../util/ErrorMessage'
|
||||
import {ErrorMessage} from '../util/error/ErrorMessage'
|
||||
import {EmptyState} from '../util/EmptyState'
|
||||
import {OnScrollCb} from '../../lib/useOnMainScroll'
|
||||
import {OnScrollCb} from '../../lib/hooks/useOnMainScroll'
|
||||
|
||||
const EMPTY_FEED_ITEM = {_reactKey: '__empty__'}
|
||||
|
||||
|
@ -54,7 +54,6 @@ export const Feed = observer(function Feed({
|
|||
{view.isLoading && !data && <NotificationFeedLoadingPlaceholder />}
|
||||
{view.hasError && (
|
||||
<ErrorMessage
|
||||
dark
|
||||
message={view.error}
|
||||
style={{margin: 6}}
|
||||
onPressTryAgain={onPressTryAgain}
|
||||
|
|
|
@ -8,9 +8,9 @@ import {PostThreadViewModel} from '../../../state/models/post-thread-view'
|
|||
import {s, colors} from '../../lib/styles'
|
||||
import {ago, pluralize} from '../../../lib/strings'
|
||||
import {UpIconSolid} from '../../lib/icons'
|
||||
import {Text} from '../util/Text'
|
||||
import {Text} from '../util/text/Text'
|
||||
import {UserAvatar} from '../util/UserAvatar'
|
||||
import {ErrorMessage} from '../util/ErrorMessage'
|
||||
import {ErrorMessage} from '../util/error/ErrorMessage'
|
||||
import {Post} from '../post/Post'
|
||||
import {Link} from '../util/Link'
|
||||
import {InviteAccepter} from './InviteAccepter'
|
||||
|
|
|
@ -8,7 +8,7 @@ import {ConfirmModal} from '../../../state/models/shell-ui'
|
|||
import {useStores} from '../../../state'
|
||||
import {ProfileCard} from '../profile/ProfileCard'
|
||||
import * as Toast from '../util/Toast'
|
||||
import {Text} from '../util/Text'
|
||||
import {Text} from '../util/text/Text'
|
||||
import {s, colors, gradients} from '../../lib/styles'
|
||||
|
||||
export function InviteAccepter({item}: {item: NotificationsViewItemModel}) {
|
||||
|
|
|
@ -10,7 +10,7 @@ import {
|
|||
} from 'react-native'
|
||||
import {TabView, SceneMap, Route, TabBarProps} from 'react-native-tab-view'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {Text} from '../util/Text'
|
||||
import {Text} from '../util/text/Text'
|
||||
import {UserGroupIcon} from '../../lib/icons'
|
||||
import {useStores} from '../../../state'
|
||||
import {s} from '../../lib/styles'
|
||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react'
|
|||
import {SafeAreaView, StyleSheet, TouchableOpacity, View} from 'react-native'
|
||||
import {observer} from 'mobx-react-lite'
|
||||
import {SuggestedFollows} from '../discover/SuggestedFollows'
|
||||
import {Text} from '../util/Text'
|
||||
import {Text} from '../util/text/Text'
|
||||
import {useStores} from '../../../state'
|
||||
import {s} from '../../lib/styles'
|
||||
|
||||
|
|
|
@ -6,9 +6,9 @@ import {
|
|||
RepostedByViewItemModel,
|
||||
} from '../../../state/models/reposted-by-view'
|
||||
import {UserAvatar} from '../util/UserAvatar'
|
||||
import {ErrorMessage} from '../util/ErrorMessage'
|
||||
import {ErrorMessage} from '../util/error/ErrorMessage'
|
||||
import {Link} from '../util/Link'
|
||||
import {Text} from '../util/Text'
|
||||
import {Text} from '../util/text/Text'
|
||||
import {useStores} from '../../../state'
|
||||
import {s, colors} from '../../lib/styles'
|
||||
|
||||
|
@ -57,7 +57,6 @@ export const PostRepostedBy = observer(function PostRepostedBy({
|
|||
return (
|
||||
<View>
|
||||
<ErrorMessage
|
||||
dark
|
||||
message={view.error}
|
||||
style={{margin: 6}}
|
||||
onPressTryAgain={onRefresh}
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
PostThreadViewPostModel,
|
||||
} from '../../../state/models/post-thread-view'
|
||||
import {PostThreadItem} from './PostThreadItem'
|
||||
import {ErrorMessage} from '../util/ErrorMessage'
|
||||
import {ErrorMessage} from '../util/error/ErrorMessage'
|
||||
|
||||
export const PostThread = observer(function PostThread({
|
||||
uri,
|
||||
|
@ -57,7 +57,6 @@ export const PostThread = observer(function PostThread({
|
|||
return (
|
||||
<View>
|
||||
<ErrorMessage
|
||||
dark
|
||||
message={view.error}
|
||||
style={{margin: 6}}
|
||||
onPressTryAgain={onRefresh}
|
||||
|
|
|
@ -7,9 +7,9 @@ import {AppBskyFeedPost} from '@atproto/api'
|
|||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {PostThreadViewPostModel} from '../../../state/models/post-thread-view'
|
||||
import {Link} from '../util/Link'
|
||||
import {RichText} from '../util/RichText'
|
||||
import {Text} from '../util/Text'
|
||||
import {PostDropdownBtn} from '../util/DropdownBtn'
|
||||
import {RichText} from '../util/text/RichText'
|
||||
import {Text} from '../util/text/Text'
|
||||
import {PostDropdownBtn} from '../util/forms/DropdownButton'
|
||||
import * as Toast from '../util/Toast'
|
||||
import {UserAvatar} from '../util/UserAvatar'
|
||||
import {s, colors} from '../../lib/styles'
|
||||
|
|
|
@ -6,8 +6,8 @@ import {
|
|||
VotesViewItemModel,
|
||||
} from '../../../state/models/votes-view'
|
||||
import {Link} from '../util/Link'
|
||||
import {Text} from '../util/Text'
|
||||
import {ErrorMessage} from '../util/ErrorMessage'
|
||||
import {Text} from '../util/text/Text'
|
||||
import {ErrorMessage} from '../util/error/ErrorMessage'
|
||||
import {UserAvatar} from '../util/UserAvatar'
|
||||
import {useStores} from '../../../state'
|
||||
import {s, colors} from '../../lib/styles'
|
||||
|
@ -57,7 +57,6 @@ export const PostVotedBy = observer(function PostVotedBy({
|
|||
return (
|
||||
<View>
|
||||
<ErrorMessage
|
||||
dark
|
||||
message={view.error}
|
||||
style={{margin: 6}}
|
||||
onPressTryAgain={onRefresh}
|
||||
|
|
|
@ -17,8 +17,8 @@ import {UserInfoText} from '../util/UserInfoText'
|
|||
import {PostMeta} from '../util/PostMeta'
|
||||
import {PostEmbeds} from '../util/PostEmbeds'
|
||||
import {PostCtrls} from '../util/PostCtrls'
|
||||
import {Text} from '../util/Text'
|
||||
import {RichText} from '../util/RichText'
|
||||
import {Text} from '../util/text/Text'
|
||||
import {RichText} from '../util/text/RichText'
|
||||
import * as Toast from '../util/Toast'
|
||||
import {UserAvatar} from '../util/UserAvatar'
|
||||
import {useStores} from '../../../state'
|
||||
|
|
|
@ -2,8 +2,8 @@ import React, {useState, useEffect} from 'react'
|
|||
import {observer} from 'mobx-react-lite'
|
||||
import {View} from 'react-native'
|
||||
import {LoadingPlaceholder} from '../util/LoadingPlaceholder'
|
||||
import {ErrorMessage} from '../util/ErrorMessage'
|
||||
import {Text} from '../util/Text'
|
||||
import {ErrorMessage} from '../util/error/ErrorMessage'
|
||||
import {Text} from '../util/text/Text'
|
||||
import {PostModel} from '../../../state/models/post'
|
||||
import {useStores} from '../../../state'
|
||||
|
||||
|
|
|
@ -9,11 +9,11 @@ import {
|
|||
} from 'react-native'
|
||||
import {PostFeedLoadingPlaceholder} from '../util/LoadingPlaceholder'
|
||||
import {EmptyState} from '../util/EmptyState'
|
||||
import {ErrorMessage} from '../util/ErrorMessage'
|
||||
import {ErrorMessage} from '../util/error/ErrorMessage'
|
||||
import {FeedModel} from '../../../state/models/feed-view'
|
||||
import {FeedItem} from './FeedItem'
|
||||
import {ComposePrompt} from '../composer/Prompt'
|
||||
import {OnScrollCb} from '../../lib/useOnMainScroll'
|
||||
import {OnScrollCb} from '../../lib/hooks/useOnMainScroll'
|
||||
|
||||
const COMPOSE_PROMPT_ITEM = {_reactKey: '__prompt__'}
|
||||
const EMPTY_FEED_ITEM = {_reactKey: '__empty__'}
|
||||
|
@ -80,7 +80,6 @@ export const Feed = observer(function Feed({
|
|||
{feed.isLoading && !data && <PostFeedLoadingPlaceholder />}
|
||||
{feed.hasError && (
|
||||
<ErrorMessage
|
||||
dark
|
||||
message={feed.error}
|
||||
style={{margin: 6}}
|
||||
onPressTryAgain={onPressTryAgain}
|
||||
|
|
|
@ -8,12 +8,12 @@ import {AppBskyFeedPost} from '@atproto/api'
|
|||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {FeedItemModel} from '../../../state/models/feed-view'
|
||||
import {Link} from '../util/Link'
|
||||
import {Text} from '../util/Text'
|
||||
import {Text} from '../util/text/Text'
|
||||
import {UserInfoText} from '../util/UserInfoText'
|
||||
import {PostMeta} from '../util/PostMeta'
|
||||
import {PostCtrls} from '../util/PostCtrls'
|
||||
import {PostEmbeds} from '../util/PostEmbeds'
|
||||
import {RichText} from '../util/RichText'
|
||||
import {RichText} from '../util/text/RichText'
|
||||
import * as Toast from '../util/Toast'
|
||||
import {UserAvatar} from '../util/UserAvatar'
|
||||
import {s, colors} from '../../lib/styles'
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react'
|
||||
import {StyleSheet, TouchableOpacity, View} from 'react-native'
|
||||
import {Link} from '../util/Link'
|
||||
import {Text} from '../util/Text'
|
||||
import {Text} from '../util/text/Text'
|
||||
import {UserAvatar} from '../util/UserAvatar'
|
||||
import {s, colors} from '../../lib/styles'
|
||||
|
||||
|
|
|
@ -6,8 +6,8 @@ import {
|
|||
FollowerItem,
|
||||
} from '../../../state/models/user-followers-view'
|
||||
import {Link} from '../util/Link'
|
||||
import {Text} from '../util/Text'
|
||||
import {ErrorMessage} from '../util/ErrorMessage'
|
||||
import {Text} from '../util/text/Text'
|
||||
import {ErrorMessage} from '../util/error/ErrorMessage'
|
||||
import {UserAvatar} from '../util/UserAvatar'
|
||||
import {useStores} from '../../../state'
|
||||
import {s, colors} from '../../lib/styles'
|
||||
|
@ -57,7 +57,6 @@ export const ProfileFollowers = observer(function ProfileFollowers({
|
|||
return (
|
||||
<View>
|
||||
<ErrorMessage
|
||||
dark
|
||||
message={view.error}
|
||||
style={{margin: 6}}
|
||||
onPressTryAgain={onRefresh}
|
||||
|
|
|
@ -7,8 +7,8 @@ import {
|
|||
} from '../../../state/models/user-follows-view'
|
||||
import {useStores} from '../../../state'
|
||||
import {Link} from '../util/Link'
|
||||
import {Text} from '../util/Text'
|
||||
import {ErrorMessage} from '../util/ErrorMessage'
|
||||
import {Text} from '../util/text/Text'
|
||||
import {ErrorMessage} from '../util/error/ErrorMessage'
|
||||
import {UserAvatar} from '../util/UserAvatar'
|
||||
import {s, colors} from '../../lib/styles'
|
||||
|
||||
|
@ -57,7 +57,6 @@ export const ProfileFollows = observer(function ProfileFollows({
|
|||
return (
|
||||
<View>
|
||||
<ErrorMessage
|
||||
dark
|
||||
message={view.error}
|
||||
style={{margin: 6}}
|
||||
onPressTryAgain={onRefresh}
|
||||
|
|
|
@ -16,11 +16,11 @@ import {
|
|||
import {pluralize} from '../../../lib/strings'
|
||||
import {s, colors} from '../../lib/styles'
|
||||
import {getGradient} from '../../lib/asset-gen'
|
||||
import {DropdownBtn, DropdownItem} from '../util/DropdownBtn'
|
||||
import {DropdownButton, DropdownItem} from '../util/forms/DropdownButton'
|
||||
import * as Toast from '../util/Toast'
|
||||
import {LoadingPlaceholder} from '../util/LoadingPlaceholder'
|
||||
import {Text} from '../util/Text'
|
||||
import {RichText} from '../util/RichText'
|
||||
import {Text} from '../util/text/Text'
|
||||
import {RichText} from '../util/text/RichText'
|
||||
import {UserAvatar} from '../util/UserAvatar'
|
||||
import {UserBanner} from '../util/UserBanner'
|
||||
import {UserInfoText} from '../util/UserInfoText'
|
||||
|
@ -195,11 +195,11 @@ export const ProfileHeader = observer(function ProfileHeader({
|
|||
</>
|
||||
)}
|
||||
{dropdownItems?.length ? (
|
||||
<DropdownBtn
|
||||
<DropdownButton
|
||||
items={dropdownItems}
|
||||
style={[styles.btn, styles.secondaryBtn]}>
|
||||
<FontAwesomeIcon icon="ellipsis" style={[s.gray5]} />
|
||||
</DropdownBtn>
|
||||
</DropdownButton>
|
||||
) : undefined}
|
||||
</View>
|
||||
<View style={styles.displayNameLine}>
|
||||
|
|
|
@ -3,7 +3,7 @@ import {observer} from 'mobx-react-lite'
|
|||
import {ActivityIndicator, FlatList, View} from 'react-native'
|
||||
import {MembersViewModel, MemberItem} from '../../../state/models/members-view'
|
||||
import {ProfileCard} from './ProfileCard'
|
||||
import {ErrorMessage} from '../util/ErrorMessage'
|
||||
import {ErrorMessage} from '../util/error/ErrorMessage'
|
||||
import {useStores} from '../../../state'
|
||||
|
||||
export const ProfileMembers = observer(function ProfileMembers({
|
||||
|
@ -49,7 +49,6 @@ export const ProfileMembers = observer(function ProfileMembers({
|
|||
return (
|
||||
<View>
|
||||
<ErrorMessage
|
||||
dark
|
||||
message={view.error}
|
||||
style={{margin: 6}}
|
||||
onPressTryAgain={onRefresh}
|
||||
|
|
|
@ -2,9 +2,9 @@ import React from 'react'
|
|||
import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native'
|
||||
import {IconProp} from '@fortawesome/fontawesome-svg-core'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {Text} from './Text'
|
||||
import {Text} from './text/Text'
|
||||
import {UserGroupIcon} from '../../lib/icons'
|
||||
import {colors} from '../../lib/styles'
|
||||
import {usePalette} from '../../lib/hooks/usePalette'
|
||||
|
||||
export function EmptyState({
|
||||
icon,
|
||||
|
@ -15,16 +15,23 @@ export function EmptyState({
|
|||
message: string
|
||||
style?: StyleProp<ViewStyle>
|
||||
}) {
|
||||
const pal = usePalette('default')
|
||||
return (
|
||||
<View style={[styles.container, style]}>
|
||||
<View style={styles.iconContainer}>
|
||||
{icon === 'user-group' ? (
|
||||
<UserGroupIcon size="64" style={styles.icon} />
|
||||
) : (
|
||||
<FontAwesomeIcon icon={icon} size={64} style={styles.icon} />
|
||||
<FontAwesomeIcon
|
||||
icon={icon}
|
||||
size={64}
|
||||
style={[styles.icon, pal.textLight]}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
<Text style={styles.text}>{message}</Text>
|
||||
<Text type="body1" style={[pal.textLight, styles.text]}>
|
||||
{message}
|
||||
</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
@ -40,12 +47,9 @@ const styles = StyleSheet.create({
|
|||
icon: {
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
color: colors.gray3,
|
||||
},
|
||||
text: {
|
||||
textAlign: 'center',
|
||||
color: colors.gray5,
|
||||
paddingTop: 16,
|
||||
fontSize: 16,
|
||||
},
|
||||
})
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
})
|
|
@ -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: {},
|
||||
})
|
|
@ -9,7 +9,8 @@ import {
|
|||
View,
|
||||
ViewStyle,
|
||||
} from 'react-native'
|
||||
import {Text} from './Text'
|
||||
import {Text} from './text/Text'
|
||||
import {TypographyVariant} from '../../lib/ThemeContext'
|
||||
import {useStores, RootStoreModel} from '../../../state'
|
||||
import {convertBskyAppUrlIfNeeded} from '../../../lib/strings'
|
||||
|
||||
|
@ -57,14 +58,14 @@ export const Link = observer(function Link({
|
|||
})
|
||||
|
||||
export const TextLink = observer(function Link({
|
||||
type = 'body1',
|
||||
style,
|
||||
href,
|
||||
title,
|
||||
text,
|
||||
}: {
|
||||
type: TypographyVariant
|
||||
style?: StyleProp<TextStyle>
|
||||
href: string
|
||||
title?: string
|
||||
text: string
|
||||
}) {
|
||||
const store = useStores()
|
||||
|
@ -75,7 +76,7 @@ export const TextLink = observer(function Link({
|
|||
handleLink(store, href, true)
|
||||
}
|
||||
return (
|
||||
<Text style={style} onPress={onPress} onLongPress={onLongPress}>
|
||||
<Text type={type} style={style} onPress={onPress} onLongPress={onLongPress}>
|
||||
{text}
|
||||
</Text>
|
||||
)
|
||||
|
|
|
@ -3,6 +3,7 @@ import {StyleSheet, StyleProp, View, ViewStyle} from 'react-native'
|
|||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {UpIcon} from '../../lib/icons'
|
||||
import {s, colors} from '../../lib/styles'
|
||||
import {useTheme} from '../../lib/ThemeContext'
|
||||
|
||||
export function LoadingPlaceholder({
|
||||
width,
|
||||
|
@ -13,13 +14,14 @@ export function LoadingPlaceholder({
|
|||
height: string | number
|
||||
style?: StyleProp<ViewStyle>
|
||||
}) {
|
||||
const theme = useTheme()
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
{
|
||||
width,
|
||||
height,
|
||||
backgroundColor: '#e7e9ea',
|
||||
backgroundColor: theme.palette.default.backgroundLight,
|
||||
borderRadius: 6,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
|
@ -29,7 +31,7 @@ export function LoadingPlaceholder({
|
|||
style={{
|
||||
width,
|
||||
height,
|
||||
backgroundColor: '#e7e9ea',
|
||||
backgroundColor: theme.palette.default.backgroundLight,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
|
@ -41,6 +43,7 @@ export function PostLoadingPlaceholder({
|
|||
}: {
|
||||
style?: StyleProp<ViewStyle>
|
||||
}) {
|
||||
const theme = useTheme()
|
||||
return (
|
||||
<View style={[styles.post, style]}>
|
||||
<LoadingPlaceholder width={50} height={50} style={styles.avatar} />
|
||||
|
@ -52,16 +55,24 @@ export function PostLoadingPlaceholder({
|
|||
<View style={s.flexRow}>
|
||||
<View style={s.flex1}>
|
||||
<FontAwesomeIcon
|
||||
style={s.gray3}
|
||||
style={{color: theme.palette.default.icon}}
|
||||
icon={['far', 'comment']}
|
||||
size={14}
|
||||
/>
|
||||
</View>
|
||||
<View style={s.flex1}>
|
||||
<FontAwesomeIcon style={s.gray3} icon="retweet" size={18} />
|
||||
<FontAwesomeIcon
|
||||
style={{color: theme.palette.default.icon}}
|
||||
icon="retweet"
|
||||
size={18}
|
||||
/>
|
||||
</View>
|
||||
<View style={s.flex1}>
|
||||
<UpIcon style={s.gray3} size={17} strokeWidth={1.7} />
|
||||
<UpIcon
|
||||
style={{color: theme.palette.default.icon}}
|
||||
size={17}
|
||||
strokeWidth={1.7}
|
||||
/>
|
||||
</View>
|
||||
<View style={s.flex1}></View>
|
||||
</View>
|
||||
|
@ -125,8 +136,6 @@ export function NotificationFeedLoadingPlaceholder() {
|
|||
const styles = StyleSheet.create({
|
||||
post: {
|
||||
flexDirection: 'row',
|
||||
backgroundColor: colors.white,
|
||||
borderRadius: 6,
|
||||
padding: 10,
|
||||
margin: 1,
|
||||
},
|
||||
|
@ -135,8 +144,6 @@ const styles = StyleSheet.create({
|
|||
marginRight: 10,
|
||||
},
|
||||
notification: {
|
||||
backgroundColor: colors.white,
|
||||
borderRadius: 6,
|
||||
padding: 10,
|
||||
paddingLeft: 46,
|
||||
margin: 1,
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
// TODO: replaceme with something in the design system
|
||||
|
||||
import React, {useRef} from 'react'
|
||||
import {
|
||||
StyleProp,
|
||||
|
@ -13,7 +15,7 @@ import {
|
|||
FontAwesomeIconStyle,
|
||||
} from '@fortawesome/react-native-fontawesome'
|
||||
import RootSiblings from 'react-native-root-siblings'
|
||||
import {Text} from './Text'
|
||||
import {Text} from './text/Text'
|
||||
import {colors} from '../../lib/styles'
|
||||
|
||||
interface PickerItem {
|
||||
|
|
|
@ -2,10 +2,10 @@ import React from 'react'
|
|||
import {Animated, StyleSheet, TouchableOpacity, View} from 'react-native'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import ReactNativeHapticFeedback from 'react-native-haptic-feedback'
|
||||
import {Text} from './Text'
|
||||
import {Text} from './text/Text'
|
||||
import {UpIcon, UpIconSolid} from '../../lib/icons'
|
||||
import {s, colors} from '../../lib/styles'
|
||||
import {useAnimatedValue} from '../../lib/useAnimatedValue'
|
||||
import {useAnimatedValue} from '../../lib/hooks/useAnimatedValue'
|
||||
|
||||
interface PostCtrlsOpts {
|
||||
big?: boolean
|
||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react'
|
|||
import {ImageStyle, StyleSheet, StyleProp, View, ViewStyle} from 'react-native'
|
||||
import {AppBskyEmbedImages, AppBskyEmbedExternal} from '@atproto/api'
|
||||
import {Link} from '../util/Link'
|
||||
import {Text} from '../util/Text'
|
||||
import {Text} from './text/Text'
|
||||
import {colors} from '../../lib/styles'
|
||||
import {AutoSizedImage} from './images/AutoSizedImage'
|
||||
import {ImagesLightbox} from '../../../state/models/shell-ui'
|
||||
|
|
|
@ -2,8 +2,8 @@ import React from 'react'
|
|||
import {StyleSheet, View} from 'react-native'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {Link} from '../util/Link'
|
||||
import {Text} from '../util/Text'
|
||||
import {PostDropdownBtn} from '../util/DropdownBtn'
|
||||
import {Text} from './text/Text'
|
||||
import {PostDropdownBtn} from './forms/DropdownButton'
|
||||
import {s} from '../../lib/styles'
|
||||
import {ago} from '../../../lib/strings'
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import {
|
|||
TouchableWithoutFeedback,
|
||||
View,
|
||||
} from 'react-native'
|
||||
import {Text} from './Text'
|
||||
import {Text} from './text/Text'
|
||||
import {colors} from '../../lib/styles'
|
||||
|
||||
interface Layout {
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
|
@ -2,7 +2,7 @@ import React, {useState, useEffect} from 'react'
|
|||
import {AppBskyActorGetProfile as GetProfile} from '@atproto/api'
|
||||
import {StyleProp, TextStyle} from 'react-native'
|
||||
import {Link} from './Link'
|
||||
import {Text} from './Text'
|
||||
import {Text} from './text/Text'
|
||||
import {LoadingPlaceholder} from './LoadingPlaceholder'
|
||||
import {useStores} from '../../../state'
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
} from 'react-native'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {UserAvatar} from './UserAvatar'
|
||||
import {Text} from './Text'
|
||||
import {Text} from './text/Text'
|
||||
import {s, colors} from '../../lib/styles'
|
||||
import {MagnifyingGlassIcon} from '../../lib/icons'
|
||||
import {useStores} from '../../../state'
|
||||
|
|
|
@ -7,8 +7,8 @@ import {
|
|||
} from 'react-native'
|
||||
import {Selector} from './Selector'
|
||||
import {HorzSwipe} from './gestures/HorzSwipe'
|
||||
import {useAnimatedValue} from '../../lib/useAnimatedValue'
|
||||
import {OnScrollCb} from '../../lib/useOnMainScroll'
|
||||
import {useAnimatedValue} from '../../lib/hooks/useAnimatedValue'
|
||||
import {OnScrollCb} from '../../lib/hooks/useOnMainScroll'
|
||||
|
||||
const HEADER_ITEM = {_reactKey: '__header__'}
|
||||
const SELECTOR_ITEM = {_reactKey: '__selector__'}
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
})
|
|
@ -1,8 +1,10 @@
|
|||
import React from 'react'
|
||||
import {StyleSheet, TouchableOpacity, View} from 'react-native'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {Text} from './Text'
|
||||
import {colors} from '../../lib/styles'
|
||||
import {Text} from '../text/Text'
|
||||
import {colors} from '../../../lib/styles'
|
||||
import {useTheme} from '../../../lib/ThemeContext'
|
||||
import {usePalette} from '../../../lib/hooks/usePalette'
|
||||
|
||||
export function ErrorScreen({
|
||||
title,
|
||||
|
@ -15,10 +17,16 @@ export function ErrorScreen({
|
|||
details?: string
|
||||
onPressTryAgain?: () => void
|
||||
}) {
|
||||
const theme = useTheme()
|
||||
const pal = usePalette('error')
|
||||
return (
|
||||
<View style={styles.outer}>
|
||||
<View style={[styles.outer, pal.view]}>
|
||||
<View style={styles.errorIconContainer}>
|
||||
<View style={styles.errorIcon}>
|
||||
<View
|
||||
style={[
|
||||
styles.errorIcon,
|
||||
{backgroundColor: theme.palette.error.icon},
|
||||
]}>
|
||||
<FontAwesomeIcon
|
||||
icon="exclamation"
|
||||
style={{color: colors.white}}
|
||||
|
@ -26,18 +34,30 @@ export function ErrorScreen({
|
|||
/>
|
||||
</View>
|
||||
</View>
|
||||
<Text style={styles.title}>{title}</Text>
|
||||
<Text style={styles.message}>{message}</Text>
|
||||
{details && <Text style={styles.details}>{details}</Text>}
|
||||
<Text type="h3" style={[styles.title, pal.text]}>
|
||||
{title}
|
||||
</Text>
|
||||
<Text style={[styles.message, pal.textLight]}>{message}</Text>
|
||||
{details && (
|
||||
<Text
|
||||
type="body2"
|
||||
style={[
|
||||
styles.details,
|
||||
pal.textInverted,
|
||||
{backgroundColor: theme.palette.default.background},
|
||||
]}>
|
||||
{details}
|
||||
</Text>
|
||||
)}
|
||||
{onPressTryAgain && (
|
||||
<View style={styles.btnContainer}>
|
||||
<TouchableOpacity style={styles.btn} onPress={onPressTryAgain}>
|
||||
<FontAwesomeIcon
|
||||
icon="arrows-rotate"
|
||||
style={{color: colors.white}}
|
||||
size={16}
|
||||
/>
|
||||
<Text style={styles.btnText}>Try again</Text>
|
||||
<TouchableOpacity
|
||||
style={[styles.btn, {backgroundColor: theme.palette.error.icon}]}
|
||||
onPress={onPressTryAgain}>
|
||||
<FontAwesomeIcon icon="arrows-rotate" style={pal.text} size={16} />
|
||||
<Text type="button" style={[styles.btnText, pal.text]}>
|
||||
Try again
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)}
|
||||
|
@ -48,32 +68,19 @@ export function ErrorScreen({
|
|||
const styles = StyleSheet.create({
|
||||
outer: {
|
||||
flex: 1,
|
||||
backgroundColor: colors.red1,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.red3,
|
||||
borderRadius: 6,
|
||||
paddingVertical: 30,
|
||||
paddingHorizontal: 14,
|
||||
margin: 10,
|
||||
},
|
||||
title: {
|
||||
textAlign: 'center',
|
||||
color: colors.red4,
|
||||
fontSize: 24,
|
||||
marginBottom: 10,
|
||||
},
|
||||
message: {
|
||||
textAlign: 'center',
|
||||
color: colors.red4,
|
||||
marginBottom: 20,
|
||||
},
|
||||
details: {
|
||||
textAlign: 'center',
|
||||
color: colors.black,
|
||||
backgroundColor: colors.white,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.gray5,
|
||||
borderRadius: 6,
|
||||
paddingVertical: 10,
|
||||
paddingHorizontal: 14,
|
||||
overflow: 'hidden',
|
||||
|
@ -85,23 +92,17 @@ const styles = StyleSheet.create({
|
|||
btn: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: colors.red4,
|
||||
borderRadius: 6,
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 10,
|
||||
},
|
||||
btnText: {
|
||||
marginLeft: 5,
|
||||
color: colors.white,
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
errorIconContainer: {
|
||||
alignItems: 'center',
|
||||
marginBottom: 10,
|
||||
},
|
||||
errorIcon: {
|
||||
backgroundColor: colors.red4,
|
||||
borderRadius: 30,
|
||||
width: 50,
|
||||
height: 50,
|
|
@ -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,
|
||||
},
|
||||
})
|
|
@ -11,12 +11,13 @@ import {
|
|||
import {IconProp} from '@fortawesome/fontawesome-svg-core'
|
||||
import RootSiblings from 'react-native-root-siblings'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {Text} from './Text'
|
||||
import {colors} from '../../lib/styles'
|
||||
import {toShareUrl} from '../../../lib/strings'
|
||||
import {useStores} from '../../../state'
|
||||
import {ReportPostModal, ConfirmModal} from '../../../state/models/shell-ui'
|
||||
import {TABS_ENABLED} from '../../../build-flags'
|
||||
import {Text} from '../text/Text'
|
||||
import {Button, ButtonType} from './Button'
|
||||
import {colors} from '../../../lib/styles'
|
||||
import {toShareUrl} from '../../../../lib/strings'
|
||||
import {useStores} from '../../../../state'
|
||||
import {ReportPostModal, ConfirmModal} from '../../../../state/models/shell-ui'
|
||||
import {TABS_ENABLED} from '../../../../build-flags'
|
||||
|
||||
const HITSLOP = {left: 10, top: 10, right: 10, bottom: 10}
|
||||
|
||||
|
@ -26,14 +27,20 @@ export interface DropdownItem {
|
|||
onPress: () => void
|
||||
}
|
||||
|
||||
export function DropdownBtn({
|
||||
export type DropdownButtonType = ButtonType | 'bare'
|
||||
|
||||
export function DropdownButton({
|
||||
type = 'bare',
|
||||
style,
|
||||
items,
|
||||
label,
|
||||
menuWidth,
|
||||
children,
|
||||
}: {
|
||||
type: DropdownButtonType
|
||||
style?: StyleProp<ViewStyle>
|
||||
items: DropdownItem[]
|
||||
label?: string
|
||||
menuWidth?: number
|
||||
children?: React.ReactNode
|
||||
}) {
|
||||
|
@ -62,14 +69,23 @@ export function DropdownBtn({
|
|||
)
|
||||
}
|
||||
|
||||
if (type === 'bare') {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={style}
|
||||
onPress={onPress}
|
||||
hitSlop={HITSLOP}
|
||||
ref={ref}>
|
||||
{children}
|
||||
</TouchableOpacity>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={style}
|
||||
onPress={onPress}
|
||||
hitSlop={HITSLOP}
|
||||
ref={ref}>
|
||||
{children}
|
||||
</TouchableOpacity>
|
||||
<View ref={ref}>
|
||||
<Button onPress={onPress} style={style} label={label}>
|
||||
{children}
|
||||
</Button>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -77,7 +93,6 @@ export function PostDropdownBtn({
|
|||
style,
|
||||
children,
|
||||
itemHref,
|
||||
itemTitle,
|
||||
isAuthor,
|
||||
onCopyPostText,
|
||||
onDeletePost,
|
||||
|
@ -141,9 +156,9 @@ export function PostDropdownBtn({
|
|||
].filter(Boolean) as DropdownItem[]
|
||||
|
||||
return (
|
||||
<DropdownBtn style={style} items={dropdownItems} menuWidth={200}>
|
||||
<DropdownButton style={style} items={dropdownItems} menuWidth={200}>
|
||||
{children}
|
||||
</DropdownBtn>
|
||||
</DropdownButton>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,24 +1,126 @@
|
|||
import React from 'react'
|
||||
import {StyleSheet, TouchableOpacity, View} from 'react-native'
|
||||
import {Text} from '../Text'
|
||||
import {colors} from '../../../lib/styles'
|
||||
import {StyleProp, StyleSheet, TextStyle, View, ViewStyle} from 'react-native'
|
||||
import {Text} from '../text/Text'
|
||||
import {Button, ButtonType} from './Button'
|
||||
import {useTheme} from '../../../lib/ThemeContext'
|
||||
import {choose} from '../../../../lib/functions'
|
||||
|
||||
export function RadioButton({
|
||||
type = 'default-light',
|
||||
label,
|
||||
isSelected,
|
||||
style,
|
||||
onPress,
|
||||
}: {
|
||||
type?: ButtonType
|
||||
label: string
|
||||
isSelected: boolean
|
||||
style?: StyleProp<ViewStyle>
|
||||
onPress: () => void
|
||||
}) {
|
||||
const theme = useTheme()
|
||||
const circleStyle = choose<TextStyle, Record<ButtonType, TextStyle>>(type, {
|
||||
primary: {
|
||||
borderColor: theme.palette.primary.text,
|
||||
},
|
||||
secondary: {
|
||||
borderColor: theme.palette.secondary.text,
|
||||
},
|
||||
inverted: {
|
||||
borderColor: theme.palette.inverted.text,
|
||||
},
|
||||
'primary-outline': {
|
||||
borderColor: theme.palette.primary.border,
|
||||
},
|
||||
'secondary-outline': {
|
||||
borderColor: theme.palette.secondary.border,
|
||||
},
|
||||
'primary-light': {
|
||||
borderColor: theme.palette.primary.border,
|
||||
},
|
||||
'secondary-light': {
|
||||
borderColor: theme.palette.secondary.border,
|
||||
},
|
||||
'default-light': {
|
||||
borderColor: theme.palette.default.border,
|
||||
},
|
||||
})
|
||||
const circleFillStyle = choose<TextStyle, Record<ButtonType, TextStyle>>(
|
||||
type,
|
||||
{
|
||||
primary: {
|
||||
backgroundColor: theme.palette.primary.text,
|
||||
},
|
||||
secondary: {
|
||||
backgroundColor: theme.palette.secondary.text,
|
||||
},
|
||||
inverted: {
|
||||
backgroundColor: theme.palette.inverted.text,
|
||||
},
|
||||
'primary-outline': {
|
||||
backgroundColor: theme.palette.primary.background,
|
||||
},
|
||||
'secondary-outline': {
|
||||
backgroundColor: theme.palette.secondary.background,
|
||||
},
|
||||
'primary-light': {
|
||||
backgroundColor: theme.palette.primary.background,
|
||||
},
|
||||
'secondary-light': {
|
||||
backgroundColor: theme.palette.secondary.background,
|
||||
},
|
||||
'default-light': {
|
||||
backgroundColor: theme.palette.primary.background,
|
||||
},
|
||||
},
|
||||
)
|
||||
const labelStyle = choose<TextStyle, Record<ButtonType, TextStyle>>(type, {
|
||||
primary: {
|
||||
color: theme.palette.primary.text,
|
||||
fontWeight: theme.palette.primary.isLowContrast ? '500' : undefined,
|
||||
},
|
||||
secondary: {
|
||||
color: theme.palette.secondary.text,
|
||||
fontWeight: theme.palette.secondary.isLowContrast ? '500' : undefined,
|
||||
},
|
||||
inverted: {
|
||||
color: theme.palette.inverted.text,
|
||||
fontWeight: theme.palette.inverted.isLowContrast ? '500' : undefined,
|
||||
},
|
||||
'primary-outline': {
|
||||
color: theme.palette.primary.textInverted,
|
||||
fontWeight: theme.palette.primary.isLowContrast ? '500' : undefined,
|
||||
},
|
||||
'secondary-outline': {
|
||||
color: theme.palette.secondary.textInverted,
|
||||
fontWeight: theme.palette.secondary.isLowContrast ? '500' : undefined,
|
||||
},
|
||||
'primary-light': {
|
||||
color: theme.palette.primary.textInverted,
|
||||
fontWeight: theme.palette.primary.isLowContrast ? '500' : undefined,
|
||||
},
|
||||
'secondary-light': {
|
||||
color: theme.palette.secondary.textInverted,
|
||||
fontWeight: theme.palette.secondary.isLowContrast ? '500' : undefined,
|
||||
},
|
||||
'default-light': {
|
||||
color: theme.palette.default.text,
|
||||
fontWeight: theme.palette.default.isLowContrast ? '500' : undefined,
|
||||
},
|
||||
})
|
||||
return (
|
||||
<TouchableOpacity style={styles.outer} onPress={onPress}>
|
||||
<View style={styles.circle}>
|
||||
{isSelected ? <View style={styles.circleFill} /> : undefined}
|
||||
<Button type={type} onPress={onPress} style={style}>
|
||||
<View style={styles.outer}>
|
||||
<View style={[circleStyle, styles.circle]}>
|
||||
{isSelected ? (
|
||||
<View style={[circleFillStyle, styles.circleFill]} />
|
||||
) : undefined}
|
||||
</View>
|
||||
<Text type="button" style={[labelStyle, styles.label]}>
|
||||
{label}
|
||||
</Text>
|
||||
</View>
|
||||
<Text style={styles.label}>{label}</Text>
|
||||
</TouchableOpacity>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -26,30 +128,21 @@ const styles = StyleSheet.create({
|
|||
outer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginBottom: 5,
|
||||
borderRadius: 8,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.gray2,
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 8,
|
||||
},
|
||||
circle: {
|
||||
width: 30,
|
||||
height: 30,
|
||||
width: 26,
|
||||
height: 26,
|
||||
borderRadius: 15,
|
||||
padding: 4,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.gray3,
|
||||
marginRight: 10,
|
||||
},
|
||||
circleFill: {
|
||||
width: 20,
|
||||
height: 20,
|
||||
width: 16,
|
||||
height: 16,
|
||||
borderRadius: 10,
|
||||
backgroundColor: colors.blue3,
|
||||
},
|
||||
label: {
|
||||
flex: 1,
|
||||
fontSize: 17,
|
||||
},
|
||||
})
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React, {useState} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {RadioButton} from './RadioButton'
|
||||
import {ButtonType} from './Button'
|
||||
|
||||
export interface RadioGroupItem {
|
||||
label: string
|
||||
|
@ -8,22 +9,28 @@ export interface RadioGroupItem {
|
|||
}
|
||||
|
||||
export function RadioGroup({
|
||||
type,
|
||||
items,
|
||||
initialSelection = '',
|
||||
onSelect,
|
||||
}: {
|
||||
type?: ButtonType
|
||||
items: RadioGroupItem[]
|
||||
initialSelection?: string
|
||||
onSelect: (key: string) => void
|
||||
}) {
|
||||
const [selection, setSelection] = useState<string>('')
|
||||
const [selection, setSelection] = useState<string>(initialSelection)
|
||||
const onSelectInner = (key: string) => {
|
||||
setSelection(key)
|
||||
onSelect(key)
|
||||
}
|
||||
return (
|
||||
<View>
|
||||
{items.map(item => (
|
||||
{items.map((item, i) => (
|
||||
<RadioButton
|
||||
key={item.key}
|
||||
style={i !== 0 ? {marginTop: 2} : undefined}
|
||||
type={type}
|
||||
label={item.label}
|
||||
isSelected={item.key === selection}
|
||||
onPress={() => onSelectInner(item.key)}
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
})
|
|
@ -9,7 +9,7 @@ import {
|
|||
View,
|
||||
ViewStyle,
|
||||
} from 'react-native'
|
||||
import {Text} from '../Text'
|
||||
import {Text} from '../text/Text'
|
||||
import {colors} from '../../../lib/styles'
|
||||
|
||||
const MAX_HEIGHT = 300
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import React from 'react'
|
||||
import {TextStyle, StyleProp} from 'react-native'
|
||||
import {TextLink} from './Link'
|
||||
import {TextLink} from '../Link'
|
||||
import {Text} from './Text'
|
||||
import {s} from '../../lib/styles'
|
||||
import {toShortUrl} from '../../../lib/strings'
|
||||
import {s} from '../../../lib/styles'
|
||||
import {toShortUrl} from '../../../../lib/strings'
|
||||
import {TypographyVariant} from '../../../lib/ThemeContext'
|
||||
import {usePalette} from '../../../lib/hooks/usePalette'
|
||||
|
||||
type TextSlice = {start: number; end: number}
|
||||
type Entity = {
|
||||
|
@ -13,16 +15,19 @@ type Entity = {
|
|||
}
|
||||
|
||||
export function RichText({
|
||||
type = 'body1',
|
||||
text,
|
||||
entities,
|
||||
style,
|
||||
numberOfLines,
|
||||
}: {
|
||||
type: TypographyVariant
|
||||
text: string
|
||||
entities?: Entity[]
|
||||
style?: StyleProp<TextStyle>
|
||||
numberOfLines?: number
|
||||
}) {
|
||||
const pal = usePalette('default')
|
||||
if (!entities?.length) {
|
||||
if (/^\p{Extended_Pictographic}+$/u.test(text) && text.length <= 5) {
|
||||
style = {
|
||||
|
@ -47,18 +52,20 @@ export function RichText({
|
|||
els.push(
|
||||
<TextLink
|
||||
key={key}
|
||||
type={type}
|
||||
text={segment.text}
|
||||
href={`/profile/${segment.entity.value}`}
|
||||
style={[style, s.blue3]}
|
||||
style={[style, pal.link]}
|
||||
/>,
|
||||
)
|
||||
} else if (segment.entity.type === 'link') {
|
||||
els.push(
|
||||
<TextLink
|
||||
key={key}
|
||||
type={type}
|
||||
text={toShortUrl(segment.text)}
|
||||
href={segment.entity.value}
|
||||
style={[style, s.blue3]}
|
||||
style={[style, pal.link]}
|
||||
/>,
|
||||
)
|
||||
}
|
||||
|
@ -66,7 +73,7 @@ export function RichText({
|
|||
key++
|
||||
}
|
||||
return (
|
||||
<Text style={style} numberOfLines={numberOfLines}>
|
||||
<Text type={type} style={[pal.text, style]} numberOfLines={numberOfLines}>
|
||||
{els}
|
||||
</Text>
|
||||
)
|
|
@ -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>
|
||||
)
|
||||
}
|
|
@ -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>
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import {useState} from 'react'
|
||||
import {NativeSyntheticEvent, NativeScrollEvent} from 'react-native'
|
||||
import {RootStoreModel} from '../../state'
|
||||
import {RootStoreModel} from '../../../state'
|
||||
|
||||
export type OnScrollCb = (
|
||||
event: NativeSyntheticEvent<NativeScrollEvent>,
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
export const FAB = 1
|
||||
export const BASE = 0
|
|
@ -15,6 +15,7 @@ import {ProfileFollowers} from './screens/ProfileFollowers'
|
|||
import {ProfileFollows} from './screens/ProfileFollows'
|
||||
import {ProfileMembers} from './screens/ProfileMembers'
|
||||
import {Settings} from './screens/Settings'
|
||||
import {Debug} from './screens/Debug'
|
||||
|
||||
export type ScreenParams = {
|
||||
navIdx: [number, number]
|
||||
|
@ -71,6 +72,7 @@ export const routes: Route[] = [
|
|||
'retweet',
|
||||
r('/profile/(?<name>[^/]+)/post/(?<rkey>[^/]+)/reposted-by'),
|
||||
],
|
||||
[Debug, 'Debug', 'house', r('/debug')],
|
||||
]
|
||||
|
||||
export function match(url: string): MatchResult {
|
||||
|
|
|
@ -3,11 +3,11 @@ import {StyleSheet, TextInput, View} from 'react-native'
|
|||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {ProfileFollows as ProfileFollowsComponent} from '../com/profile/ProfileFollows'
|
||||
import {Selector} from '../com/util/Selector'
|
||||
import {Text} from '../com/util/Text'
|
||||
import {Text} from '../com/util/text/Text'
|
||||
import {colors} from '../lib/styles'
|
||||
import {ScreenParams} from '../routes'
|
||||
import {useStores} from '../../state'
|
||||
import {useAnimatedValue} from '../lib/useAnimatedValue'
|
||||
import {useAnimatedValue} from '../lib/hooks/useAnimatedValue'
|
||||
|
||||
export const Contacts = ({navIdx, visible, params}: ScreenParams) => {
|
||||
const store = useStores()
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
|
@ -6,11 +6,11 @@ import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
|||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||
import {ViewHeader} from '../com/util/ViewHeader'
|
||||
import {Feed} from '../com/posts/Feed'
|
||||
import {Text} from '../com/util/Text'
|
||||
import {Text} from '../com/util/text/Text'
|
||||
import {useStores} from '../../state'
|
||||
import {ScreenParams} from '../routes'
|
||||
import {s, colors} from '../lib/styles'
|
||||
import {useOnMainScroll} from '../lib/useOnMainScroll'
|
||||
import {useOnMainScroll} from '../lib/hooks/useOnMainScroll'
|
||||
import {clamp} from 'lodash'
|
||||
|
||||
const HITSLOP = {left: 20, top: 20, right: 20, bottom: 20}
|
||||
|
|
|
@ -10,7 +10,7 @@ import {observer} from 'mobx-react-lite'
|
|||
import {Signin} from '../com/login/Signin'
|
||||
import {Logo} from '../com/login/Logo'
|
||||
import {CreateAccount} from '../com/login/CreateAccount'
|
||||
import {Text} from '../com/util/Text'
|
||||
import {Text} from '../com/util/text/Text'
|
||||
import {s, colors} from '../lib/styles'
|
||||
|
||||
enum ScreenState {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react'
|
||||
import {Button, View} from 'react-native'
|
||||
import {ViewHeader} from '../com/util/ViewHeader'
|
||||
import {Text} from '../com/util/Text'
|
||||
import {Text} from '../com/util/text/Text'
|
||||
import {useStores} from '../../state'
|
||||
|
||||
export const NotFound = () => {
|
||||
|
|
|
@ -4,7 +4,7 @@ import {ViewHeader} from '../com/util/ViewHeader'
|
|||
import {Feed} from '../com/notifications/Feed'
|
||||
import {useStores} from '../../state'
|
||||
import {ScreenParams} from '../routes'
|
||||
import {useOnMainScroll} from '../lib/useOnMainScroll'
|
||||
import {useOnMainScroll} from '../lib/hooks/useOnMainScroll'
|
||||
|
||||
export const Notifications = ({navIdx, visible}: ScreenParams) => {
|
||||
const store = useStores()
|
||||
|
|
|
@ -12,14 +12,14 @@ import {ProfileHeader} from '../com/profile/ProfileHeader'
|
|||
import {FeedItem} from '../com/posts/FeedItem'
|
||||
import {ProfileCard} from '../com/profile/ProfileCard'
|
||||
import {PostFeedLoadingPlaceholder} from '../com/util/LoadingPlaceholder'
|
||||
import {ErrorScreen} from '../com/util/ErrorScreen'
|
||||
import {ErrorMessage} from '../com/util/ErrorMessage'
|
||||
import {ErrorScreen} from '../com/util/error/ErrorScreen'
|
||||
import {ErrorMessage} from '../com/util/error/ErrorMessage'
|
||||
import {EmptyState} from '../com/util/EmptyState'
|
||||
import {Text} from '../com/util/Text'
|
||||
import {Text} from '../com/util/text/Text'
|
||||
import {ViewHeader} from '../com/util/ViewHeader'
|
||||
import * as Toast from '../com/util/Toast'
|
||||
import {s, colors} from '../lib/styles'
|
||||
import {useOnMainScroll} from '../lib/useOnMainScroll'
|
||||
import {useOnMainScroll} from '../lib/hooks/useOnMainScroll'
|
||||
|
||||
const LOADING_ITEM = {_reactKey: '__loading__'}
|
||||
const END_ITEM = {_reactKey: '__end__'}
|
||||
|
@ -116,7 +116,6 @@ export const Profile = observer(({navIdx, visible, params}: ScreenParams) => {
|
|||
renderItem = (item: any) => (
|
||||
<View style={s.p5}>
|
||||
<ErrorMessage
|
||||
dark
|
||||
message={item.error}
|
||||
onPressTryAgain={onPressTryAgain}
|
||||
/>
|
||||
|
|
|
@ -10,7 +10,7 @@ import {
|
|||
import {ViewHeader} from '../com/util/ViewHeader'
|
||||
import {SuggestedFollows} from '../com/discover/SuggestedFollows'
|
||||
import {UserAvatar} from '../com/util/UserAvatar'
|
||||
import {Text} from '../com/util/Text'
|
||||
import {Text} from '../com/util/text/Text'
|
||||
import {ScreenParams} from '../routes'
|
||||
import {useStores} from '../../state'
|
||||
import {UserAutocompleteViewModel} from '../../state/models/user-autocomplete-view'
|
||||
|
|
|
@ -6,7 +6,7 @@ import {ScreenParams} from '../routes'
|
|||
import {s, colors} from '../lib/styles'
|
||||
import {ViewHeader} from '../com/util/ViewHeader'
|
||||
import {Link} from '../com/util/Link'
|
||||
import {Text} from '../com/util/Text'
|
||||
import {Text} from '../com/util/text/Text'
|
||||
import {UserAvatar} from '../com/util/UserAvatar'
|
||||
|
||||
export const Settings = observer(function Settings({
|
||||
|
@ -57,6 +57,9 @@ export const Settings = observer(function Settings({
|
|||
</View>
|
||||
</View>
|
||||
</Link>
|
||||
<Link href="/debug" title="Debug tools">
|
||||
<Text style={s.blue3}>Debug tools</Text>
|
||||
</Link>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
|
|
|
@ -3,7 +3,7 @@ import {observer} from 'mobx-react-lite'
|
|||
import {Animated, Easing, Platform, StyleSheet, View} from 'react-native'
|
||||
import {ComposePost} from '../../com/composer/ComposePost'
|
||||
import {ComposerOpts} from '../../../state/models/shell-ui'
|
||||
import {useAnimatedValue} from '../../lib/useAnimatedValue'
|
||||
import {useAnimatedValue} from '../../lib/hooks/useAnimatedValue'
|
||||
|
||||
export const Composer = observer(
|
||||
({
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
MagnifyingGlassIcon,
|
||||
} from '../../lib/icons'
|
||||
import {UserAvatar} from '../../com/util/UserAvatar'
|
||||
import {Text} from '../../com/util/Text'
|
||||
import {Text} from '../../com/util/text/Text'
|
||||
import {CreateSceneModal} from '../../../state/models/shell-ui'
|
||||
|
||||
export const Menu = ({
|
||||
|
|
|
@ -10,13 +10,13 @@ import {
|
|||
} from 'react-native'
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {Text} from '../../com/util/Text'
|
||||
import {Text} from '../../com/util/text/Text'
|
||||
import Swipeable from 'react-native-gesture-handler/Swipeable'
|
||||
import {useStores} from '../../../state'
|
||||
import {s, colors} from '../../lib/styles'
|
||||
import {toShareUrl} from '../../../lib/strings'
|
||||
import {match} from '../../routes'
|
||||
import {useAnimatedValue} from '../../lib/useAnimatedValue'
|
||||
import {useAnimatedValue} from '../../lib/hooks/useAnimatedValue'
|
||||
|
||||
const TAB_HEIGHT = 42
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ import {Onboard} from '../../screens/Onboard'
|
|||
import {HorzSwipe} from '../../com/util/gestures/HorzSwipe'
|
||||
import {Modal} from '../../com/modals/Modal'
|
||||
import {Lightbox} from '../../com/lightbox/Lightbox'
|
||||
import {Text} from '../../com/util/Text'
|
||||
import {Text} from '../../com/util/text/Text'
|
||||
import {TabsSelector} from './TabsSelector'
|
||||
import {Composer} from './Composer'
|
||||
import {s, colors} from '../../lib/styles'
|
||||
|
@ -42,7 +42,7 @@ import {
|
|||
BellIcon,
|
||||
BellIconSolid,
|
||||
} from '../../lib/icons'
|
||||
import {useAnimatedValue} from '../../lib/useAnimatedValue'
|
||||
import {useAnimatedValue} from '../../lib/hooks/useAnimatedValue'
|
||||
|
||||
const Btn = ({
|
||||
icon,
|
||||
|
|
Loading…
Reference in New Issue