Update threads to use design system

zio/stable
Paul Frazee 2022-12-30 12:19:45 -06:00
parent 55500e2f66
commit 55ca7dcce1
6 changed files with 63 additions and 137 deletions

View File

@ -16,18 +16,6 @@ function* reactKeyGenerator(): Generator<string> {
} }
} }
interface ReplyingTo {
author: {
handle: string
displayName?: string
avatar?: string
}
text: string
}
interface OriginalRecord {
text: string
}
function isThreadViewPost( function isThreadViewPost(
v: GetPostThread.ThreadViewPost | GetPostThread.NotFoundPost | UnknownPost, v: GetPostThread.ThreadViewPost | GetPostThread.NotFoundPost | UnknownPost,
): v is GetPostThread.ThreadViewPost { ): v is GetPostThread.ThreadViewPost {
@ -51,9 +39,6 @@ export class PostThreadViewPostModel {
parent?: PostThreadViewPostModel | GetPostThread.NotFoundPost parent?: PostThreadViewPostModel | GetPostThread.NotFoundPost
replies?: (PostThreadViewPostModel | GetPostThread.NotFoundPost)[] replies?: (PostThreadViewPostModel | GetPostThread.NotFoundPost)[]
// added data
replyingTo?: ReplyingTo
constructor( constructor(
public rootStore: RootStoreModel, public rootStore: RootStoreModel,
reactKey: string, reactKey: string,
@ -70,7 +55,6 @@ export class PostThreadViewPostModel {
v: GetPostThread.ThreadViewPost, v: GetPostThread.ThreadViewPost,
includeParent = true, includeParent = true,
includeChildren = true, includeChildren = true,
isFirstChild = true,
) { ) {
// parents // parents
if (includeParent && v.parent) { if (includeParent && v.parent) {
@ -89,25 +73,9 @@ export class PostThreadViewPostModel {
this.parent = v.parent this.parent = v.parent
} }
} }
if (
!includeParent &&
v.parent &&
isThreadViewPost(v.parent) &&
!isFirstChild
) {
this.replyingTo = {
author: {
handle: v.parent.post.author.handle,
displayName: v.parent.post.author.displayName,
avatar: v.parent.post.author.avatar,
},
text: (v.parent.record as OriginalRecord).text,
}
}
// replies // replies
if (includeChildren && v.replies) { if (includeChildren && v.replies) {
const replies = [] const replies = []
let isChildFirstChild = true
for (const item of v.replies) { for (const item of v.replies) {
if (isThreadViewPost(item)) { if (isThreadViewPost(item)) {
const itemModel = new PostThreadViewPostModel( const itemModel = new PostThreadViewPostModel(
@ -117,15 +85,8 @@ export class PostThreadViewPostModel {
) )
itemModel._depth = this._depth + 1 itemModel._depth = this._depth + 1
if (item.replies) { if (item.replies) {
itemModel.assignTreeModels( itemModel.assignTreeModels(keyGen, item, false, true)
keyGen,
item,
false,
true,
isChildFirstChild,
)
} }
isChildFirstChild = false
replies.push(itemModel) replies.push(itemModel)
} else if (isNotFoundPost(item)) { } else if (isNotFoundPost(item)) {
replies.push(item) replies.push(item)

View File

@ -12,16 +12,16 @@ import {Text} from '../util/text/Text'
import {PostDropdownBtn} from '../util/forms/DropdownButton' 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} from '../../lib/styles'
import {ago, pluralize} from '../../../lib/strings' import {ago, pluralize} from '../../../lib/strings'
import {useStores} from '../../../state' import {useStores} from '../../../state'
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 {ComposePrompt} from '../composer/Prompt' import {ComposePrompt} from '../composer/Prompt'
import {usePalette} from '../../lib/hooks/usePalette'
const PARENT_REPLY_LINE_LENGTH = 8 const PARENT_REPLY_LINE_LENGTH = 8
const REPLYING_TO_LINE_LENGTH = 6
export const PostThreadItem = observer(function PostThreadItem({ export const PostThreadItem = observer(function PostThreadItem({
item, item,
@ -30,6 +30,7 @@ export const PostThreadItem = observer(function PostThreadItem({
item: PostThreadViewPostModel item: PostThreadViewPostModel
onPostReply: () => void onPostReply: () => void
}) { }) {
const pal = usePalette('default')
const store = useStores() const store = useStores()
const [deleted, setDeleted] = useState(false) const [deleted, setDeleted] = useState(false)
const record = item.post.record as unknown as AppBskyFeedPost.Record const record = item.post.record as unknown as AppBskyFeedPost.Record
@ -97,9 +98,12 @@ export const PostThreadItem = observer(function PostThreadItem({
if (deleted) { if (deleted) {
return ( return (
<View style={[styles.outer, s.p20, s.flexRow]}> <View style={[styles.outer, pal.view, s.p20, s.flexRow]}>
<FontAwesomeIcon icon={['far', 'trash-can']} style={[s.gray4]} /> <FontAwesomeIcon
<Text style={[s.gray5, s.ml10]}>This post has been deleted.</Text> icon={['far', 'trash-can']}
style={{color: pal.colors.icon}}
/>
<Text style={[pal.textLight, s.ml10]}>This post has been deleted.</Text>
</View> </View>
) )
} }
@ -107,7 +111,8 @@ export const PostThreadItem = observer(function PostThreadItem({
if (item._isHighlightedPost) { if (item._isHighlightedPost) {
return ( return (
<> <>
<View style={styles.outer}> <View
style={[styles.outer, {borderTopColor: pal.colors.border}, pal.view]}>
<View style={styles.layout}> <View style={styles.layout}>
<View style={styles.layoutAvi}> <View style={styles.layoutAvi}>
<Link href={authorHref} title={authorTitle}> <Link href={authorHref} title={authorTitle}>
@ -125,11 +130,11 @@ export const PostThreadItem = observer(function PostThreadItem({
style={styles.metaItem} style={styles.metaItem}
href={authorHref} href={authorHref}
title={authorTitle}> title={authorTitle}>
<Text style={[s.f16, s.bold, s.black]} numberOfLines={1}> <Text type="h5" style={[pal.text]} numberOfLines={1}>
{item.post.author.displayName || item.post.author.handle} {item.post.author.displayName || item.post.author.handle}
</Text> </Text>
</Link> </Link>
<Text style={[styles.metaItem, s.f15, s.gray5]}> <Text type="h6" style={[styles.metaItem, pal.textLight]}>
&middot; {ago(item.post.indexedAt)} &middot; {ago(item.post.indexedAt)}
</Text> </Text>
<View style={s.flex1} /> <View style={s.flex1} />
@ -152,7 +157,7 @@ export const PostThreadItem = observer(function PostThreadItem({
style={styles.metaItem} style={styles.metaItem}
href={authorHref} href={authorHref}
title={authorTitle}> title={authorTitle}>
<Text style={[s.f15, s.gray5]} numberOfLines={1}> <Text type="h6" style={[pal.textLight]} numberOfLines={1}>
@{item.post.author.handle} @{item.post.author.handle}
</Text> </Text>
</Link> </Link>
@ -167,22 +172,23 @@ export const PostThreadItem = observer(function PostThreadItem({
styles.postTextLargeContainer, styles.postTextLargeContainer,
]}> ]}>
<RichText <RichText
type="h3"
text={record.text} text={record.text}
entities={record.entities} entities={record.entities}
style={[styles.postText, styles.postTextLarge]}
/> />
</View> </View>
) : undefined} ) : undefined}
<PostEmbeds embed={item.post.embed} style={s.mb10} /> <PostEmbeds embed={item.post.embed} style={s.mb10} />
{item._isHighlightedPost && hasEngagement ? ( {item._isHighlightedPost && hasEngagement ? (
<View style={styles.expandedInfo}> <View
style={[styles.expandedInfo, {borderColor: pal.colors.border}]}>
{item.post.repostCount ? ( {item.post.repostCount ? (
<Link <Link
style={styles.expandedInfoItem} style={styles.expandedInfoItem}
href={repostsHref} href={repostsHref}
title={repostsTitle}> title={repostsTitle}>
<Text style={[s.gray5, s.semiBold, s.f17]}> <Text type="h6" style={pal.textLight}>
<Text style={[s.bold, s.black, s.f17]}> <Text type="h5" style={pal.text}>
{item.post.repostCount} {item.post.repostCount}
</Text>{' '} </Text>{' '}
{pluralize(item.post.repostCount, 'repost')} {pluralize(item.post.repostCount, 'repost')}
@ -196,8 +202,8 @@ export const PostThreadItem = observer(function PostThreadItem({
style={styles.expandedInfoItem} style={styles.expandedInfoItem}
href={upvotesHref} href={upvotesHref}
title={upvotesTitle}> title={upvotesTitle}>
<Text style={[s.gray5, s.semiBold, s.f17]}> <Text type="h6" style={pal.textLight}>
<Text style={[s.bold, s.black, s.f17]}> <Text type="h5" style={pal.text}>
{item.post.upvoteCount} {item.post.upvoteCount}
</Text>{' '} </Text>{' '}
{pluralize(item.post.upvoteCount, 'upvote')} {pluralize(item.post.upvoteCount, 'upvote')}
@ -233,27 +239,27 @@ export const PostThreadItem = observer(function PostThreadItem({
} else { } else {
return ( return (
<> <>
<Link style={styles.outer} href={itemHref} title={itemTitle} noFeedback> <Link
{!item.replyingTo && record.reply && ( style={[styles.outer, {borderTopColor: pal.colors.border}, pal.view]}
<View style={styles.parentReplyLine} /> href={itemHref}
title={itemTitle}
noFeedback>
{record.reply && (
<View
style={[
styles.parentReplyLine,
{borderColor: pal.colors.replyLine},
]}
/>
)}
{item.replies?.length !== 0 && (
<View
style={[
styles.childReplyLine,
{borderColor: pal.colors.replyLine},
]}
/>
)} )}
{item.replies?.length !== 0 && <View style={styles.childReplyLine} />}
{item.replyingTo ? (
<View style={styles.replyingTo}>
<View style={styles.replyingToLine} />
<View style={styles.replyingToAvatar}>
<UserAvatar
handle={item.replyingTo.author.handle}
displayName={item.replyingTo.author.displayName}
avatar={item.replyingTo.author.avatar}
size={30}
/>
</View>
<Text style={styles.replyingToText} numberOfLines={2}>
{item.replyingTo.text}
</Text>
</View>
) : undefined}
<View style={styles.layout}> <View style={styles.layout}>
<View style={styles.layoutAvi}> <View style={styles.layoutAvi}>
<Link href={authorHref} title={authorTitle}> <Link href={authorHref} title={authorTitle}>
@ -282,7 +288,7 @@ export const PostThreadItem = observer(function PostThreadItem({
<RichText <RichText
text={record.text} text={record.text}
entities={record.entities} entities={record.entities}
style={[styles.postText]} style={pal.text}
/> />
</View> </View>
) : ( ) : (
@ -304,11 +310,15 @@ export const PostThreadItem = observer(function PostThreadItem({
</Link> </Link>
{item._hasMore ? ( {item._hasMore ? (
<Link <Link
style={styles.loadMore} style={[
styles.loadMore,
{borderTopColor: pal.colors.border},
pal.view,
]}
href={itemHref} href={itemHref}
title={itemTitle} title={itemTitle}
noFeedback> noFeedback>
<Text style={styles.loadMoreText}>Load more</Text> <Text style={pal.link}>Load more</Text>
</Link> </Link>
) : undefined} ) : undefined}
</> </>
@ -318,9 +328,7 @@ export const PostThreadItem = observer(function PostThreadItem({
const styles = StyleSheet.create({ const styles = StyleSheet.create({
outer: { outer: {
backgroundColor: colors.white,
borderTopWidth: 1, borderTopWidth: 1,
borderTopColor: colors.gray2,
}, },
parentReplyLine: { parentReplyLine: {
position: 'absolute', position: 'absolute',
@ -328,7 +336,6 @@ const styles = StyleSheet.create({
top: -1 * PARENT_REPLY_LINE_LENGTH + 6, top: -1 * PARENT_REPLY_LINE_LENGTH + 6,
height: PARENT_REPLY_LINE_LENGTH, height: PARENT_REPLY_LINE_LENGTH,
borderLeftWidth: 2, borderLeftWidth: 2,
borderLeftColor: colors.gray2,
}, },
childReplyLine: { childReplyLine: {
position: 'absolute', position: 'absolute',
@ -336,31 +343,6 @@ const styles = StyleSheet.create({
top: 65, top: 65,
bottom: 0, bottom: 0,
borderLeftWidth: 2, borderLeftWidth: 2,
borderLeftColor: colors.gray2,
},
replyingToLine: {
position: 'absolute',
left: 34,
bottom: -1 * REPLYING_TO_LINE_LENGTH,
height: REPLYING_TO_LINE_LENGTH,
borderLeftWidth: 2,
borderLeftColor: colors.gray2,
},
replyingTo: {
flexDirection: 'row',
backgroundColor: colors.white,
paddingLeft: 8,
paddingTop: 12,
paddingBottom: 0,
paddingRight: 24,
},
replyingToAvatar: {
marginLeft: 12,
marginRight: 20,
},
replyingToText: {
flex: 1,
color: colors.gray5,
}, },
layout: { layout: {
flexDirection: 'row', flexDirection: 'row',
@ -386,12 +368,6 @@ const styles = StyleSheet.create({
paddingRight: 5, paddingRight: 5,
maxWidth: 240, maxWidth: 240,
}, },
postText: {
fontFamily: 'System',
fontSize: 16,
lineHeight: 20.8, // 1.3 of 16px
color: 'black',
},
postTextContainer: { postTextContainer: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
@ -399,10 +375,6 @@ const styles = StyleSheet.create({
paddingBottom: 8, paddingBottom: 8,
minHeight: 36, minHeight: 36,
}, },
postTextLarge: {
fontSize: 24,
lineHeight: 32,
},
postTextLargeContainer: { postTextLargeContainer: {
paddingLeft: 4, paddingLeft: 4,
paddingBottom: 20, paddingBottom: 20,
@ -410,7 +382,6 @@ const styles = StyleSheet.create({
expandedInfo: { expandedInfo: {
flexDirection: 'row', flexDirection: 'row',
padding: 10, padding: 10,
borderColor: colors.gray2,
borderTopWidth: 1, borderTopWidth: 1,
borderBottomWidth: 1, borderBottomWidth: 1,
marginTop: 5, marginTop: 5,
@ -420,15 +391,8 @@ const styles = StyleSheet.create({
marginRight: 10, marginRight: 10,
}, },
loadMore: { loadMore: {
borderTopWidth: 1,
paddingLeft: 28, paddingLeft: 28,
paddingVertical: 10, paddingVertical: 10,
backgroundColor: colors.white,
borderRadius: 6,
margin: 2,
marginBottom: 0,
},
loadMoreText: {
fontSize: 17,
color: colors.blue3,
}, },
}) })

View File

@ -16,7 +16,7 @@ import {PostEmbeds} from '../util/PostEmbeds'
import {RichText} from '../util/text/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, lh} from '../../lib/styles' import {s, colors} from '../../lib/styles'
import {useStores} from '../../../state' import {useStores} from '../../../state'
import {useTheme} from '../../lib/ThemeContext' import {useTheme} from '../../lib/ThemeContext'
import {usePalette} from '../../lib/hooks/usePalette' import {usePalette} from '../../lib/hooks/usePalette'
@ -114,17 +114,14 @@ export const FeedItem = observer(function ({
<Link style={outerStyles} href={itemHref} title={itemTitle} noFeedback> <Link style={outerStyles} href={itemHref} title={itemTitle} noFeedback>
{item._isThreadChild && ( {item._isThreadChild && (
<View <View
style={[ style={[styles.topReplyLine, {borderColor: pal.colors.replyLine}]}
styles.topReplyLine,
{borderLeftColor: pal.colors.replyLine},
]}
/> />
)} )}
{(showReplyLine || item._isThreadParent) && ( {(showReplyLine || item._isThreadParent) && (
<View <View
style={[ style={[
styles.bottomReplyLine, styles.bottomReplyLine,
{borderLeftColor: pal.colors.replyLine}, {borderColor: pal.colors.replyLine},
isNoTop ? {top: 64} : undefined, isNoTop ? {top: 64} : undefined,
]} ]}
/> />

View File

@ -48,16 +48,16 @@ export function PostMeta(opts: PostMetaOpts) {
style={styles.metaItem} style={styles.metaItem}
href={opts.authorHref} href={opts.authorHref}
title={opts.authorHandle}> title={opts.authorHandle}>
<Text style={[pal.text, theme.typography.h5]} numberOfLines={1}> <Text type="h5" style={[pal.text]} numberOfLines={1}>
{displayName} {displayName}
{handle ? ( {handle ? (
<Text style={[pal.textLight, theme.typography.h6]}> <Text type="h6" style={[pal.textLight]}>
&nbsp;{handle} &nbsp;{handle}
</Text> </Text>
) : undefined} ) : undefined}
</Text> </Text>
</Link> </Link>
<Text style={[styles.metaItem, pal.textLight, theme.typography.h6]}> <Text type="h6" style={[styles.metaItem, pal.textLight]}>
&middot; {ago(opts.timestamp)} &middot; {ago(opts.timestamp)}
</Text> </Text>
<View style={s.flex1} /> <View style={s.flex1} />

View File

@ -29,7 +29,7 @@ export function RichText({
}) { }) {
const theme = useTheme() const theme = useTheme()
const pal = usePalette('default') const pal = usePalette('default')
const lineHeightStyle = lh(theme, 'body1', 1.2) const lineHeightStyle = lh(theme, type, 1.2)
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 = {
@ -38,7 +38,11 @@ export function RichText({
} }
return <Text style={[style, pal.text]}>{text}</Text> return <Text style={[style, pal.text]}>{text}</Text>
} }
return <Text style={[style, pal.text, lineHeightStyle]}>{text}</Text> return (
<Text type={type} style={[style, pal.text, lineHeightStyle]}>
{text}
</Text>
)
} }
if (!style) style = [] if (!style) style = []
else if (!Array.isArray(style)) style = [style] else if (!Array.isArray(style)) style = [style]

View File

@ -199,6 +199,6 @@ export function lh(
height: number, height: number,
): TextStyle { ): TextStyle {
return { return {
lineHeight: (theme.typography[type].lineHeight || 16) * height, lineHeight: (theme.typography[type].fontSize || 16) * height,
} }
} }