diff options
author | Eric Bailey <git@esb.lol> | 2024-07-02 18:15:20 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-07-03 00:15:20 +0100 |
commit | 14c2d75d49c492e9625a6e7b139f2e8dbc66668f (patch) | |
tree | 154c60c485302f8dc66dbb2181d46067125f784e | |
parent | c13366176845d20775fd1d15d1a15bcfb160b39e (diff) | |
download | voidsky-14c2d75d49c492e9625a6e7b139f2e8dbc66668f.tar.zst |
Unify label pills (#4676)
* New label pills * Fix type errors, add default case * Remove negative margin, only works in some places * Fix alignment edge case * Add a bit of padding --------- Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
-rw-r--r-- | src/components/Pills.tsx | 169 | ||||
-rw-r--r-- | src/components/ProfileHoverCard/index.web.tsx | 5 | ||||
-rw-r--r-- | src/components/dms/MessagesListHeader.tsx | 2 | ||||
-rw-r--r-- | src/components/moderation/ContentHider.tsx | 4 | ||||
-rw-r--r-- | src/components/moderation/PostAlerts.tsx | 121 | ||||
-rw-r--r-- | src/components/moderation/ProfileHeaderAlerts.tsx | 103 | ||||
-rw-r--r-- | src/screens/Messages/List/ChatListItem.tsx | 2 | ||||
-rw-r--r-- | src/view/com/post-thread/PostThreadItem.tsx | 2 | ||||
-rw-r--r-- | src/view/com/profile/ProfileCard.tsx | 52 |
9 files changed, 226 insertions, 234 deletions
diff --git a/src/components/Pills.tsx b/src/components/Pills.tsx new file mode 100644 index 000000000..2fff99937 --- /dev/null +++ b/src/components/Pills.tsx @@ -0,0 +1,169 @@ +import React from 'react' +import {View} from 'react-native' +import {BSKY_LABELER_DID, ModerationCause} from '@atproto/api' +import {Trans} from '@lingui/macro' + +import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription' +import {UserAvatar} from '#/view/com/util/UserAvatar' +import {atoms as a, useTheme, ViewStyleProp} from '#/alf' +import {Button} from '#/components/Button' +import { + ModerationDetailsDialog, + useModerationDetailsDialogControl, +} from '#/components/moderation/ModerationDetailsDialog' +import {Text} from '#/components/Typography' + +export type CommonProps = { + size?: 'sm' | 'lg' +} + +export function Row({ + children, + style, + size = 'sm', +}: {children: React.ReactNode | React.ReactNode[]} & CommonProps & + ViewStyleProp) { + const styles = React.useMemo(() => { + switch (size) { + case 'lg': + return [{gap: 5}] + case 'sm': + default: + return [{gap: 3}] + } + }, [size]) + return ( + <View style={[a.flex_row, a.flex_wrap, a.gap_xs, styles, style]}> + {children} + </View> + ) +} + +export type LabelProps = { + cause: ModerationCause + disableDetailsDialog?: boolean + noBg?: boolean +} & CommonProps + +export function Label({ + cause, + size = 'sm', + disableDetailsDialog, + noBg, +}: LabelProps) { + const t = useTheme() + const control = useModerationDetailsDialogControl() + const desc = useModerationCauseDescription(cause) + const isLabeler = Boolean(desc.sourceType && desc.sourceDid) + const isBlueskyLabel = + desc.sourceType === 'labeler' && desc.sourceDid === BSKY_LABELER_DID + + const {outer, avi, text} = React.useMemo(() => { + switch (size) { + case 'lg': { + return { + outer: [ + t.atoms.bg_contrast_25, + { + gap: 5, + paddingHorizontal: 5, + paddingVertical: 5, + }, + ], + avi: 16, + text: [a.text_sm], + } + } + case 'sm': + default: { + return { + outer: [ + !noBg && t.atoms.bg_contrast_25, + { + gap: 3, + paddingHorizontal: 3, + paddingVertical: 3, + }, + ], + avi: 12, + text: [a.text_xs], + } + } + } + }, [t, size, noBg]) + + return ( + <> + <Button + disabled={disableDetailsDialog} + label={desc.name} + onPress={e => { + e.preventDefault() + e.stopPropagation() + control.open() + }}> + {({hovered, pressed}) => ( + <View + style={[ + a.flex_row, + a.align_center, + a.rounded_full, + outer, + (hovered || pressed) && t.atoms.bg_contrast_50, + ]}> + {isBlueskyLabel || !isLabeler ? ( + <desc.icon + width={avi} + fill={t.atoms.text_contrast_medium.color} + /> + ) : ( + <UserAvatar avatar={desc.sourceAvi} size={avi} /> + )} + + <Text + style={[ + text, + a.font_semibold, + a.leading_tight, + t.atoms.text_contrast_medium, + {paddingRight: 3}, + ]}> + {desc.name} + </Text> + </View> + )} + </Button> + + {!disableDetailsDialog && ( + <ModerationDetailsDialog control={control} modcause={cause} /> + )} + </> + ) +} + +export function FollowsYou({size = 'sm'}: CommonProps) { + const t = useTheme() + + const variantStyles = React.useMemo(() => { + switch (size) { + case 'sm': + case 'lg': + default: + return [ + { + paddingHorizontal: 6, + paddingVertical: 3, + borderRadius: 4, + }, + ] + } + }, [size]) + + return ( + <View style={[variantStyles, a.justify_center, t.atoms.bg_contrast_25]}> + <Text style={[a.text_xs, a.leading_tight]}> + <Trans>Follows You</Trans> + </Text> + </View> + ) +} diff --git a/src/components/ProfileHoverCard/index.web.tsx b/src/components/ProfileHoverCard/index.web.tsx index 4db9c4f8e..84b1d6d24 100644 --- a/src/components/ProfileHoverCard/index.web.tsx +++ b/src/components/ProfileHoverCard/index.web.tsx @@ -29,10 +29,10 @@ import { } from '#/components/KnownFollowers' import {InlineLinkText, Link} from '#/components/Link' import {Loader} from '#/components/Loader' +import * as Pills from '#/components/Pills' import {Portal} from '#/components/Portal' import {RichText} from '#/components/RichText' import {Text} from '#/components/Typography' -import {ProfileLabel} from '../moderation/ProfileHeaderAlerts' import {ProfileHoverCardProps} from './types' const floatingMiddlewares = [ @@ -476,8 +476,9 @@ function Inner({ {isBlockedUser && ( <View style={[a.flex_row, a.flex_wrap, a.gap_xs]}> {moderation.ui('profileView').alerts.map(cause => ( - <ProfileLabel + <Pills.Label key={getModerationCauseKey(cause)} + size="lg" cause={cause} disableDetailsDialog /> diff --git a/src/components/dms/MessagesListHeader.tsx b/src/components/dms/MessagesListHeader.tsx index 0aeac3628..8bf673d30 100644 --- a/src/components/dms/MessagesListHeader.tsx +++ b/src/components/dms/MessagesListHeader.tsx @@ -214,7 +214,7 @@ function HeaderReady({ ]}> <PostAlerts modui={moderation.ui('contentList')} - size="large" + size="lg" style={[a.pt_xs]} /> </View> diff --git a/src/components/moderation/ContentHider.tsx b/src/components/moderation/ContentHider.tsx index fd71ec838..45122a4ef 100644 --- a/src/components/moderation/ContentHider.tsx +++ b/src/components/moderation/ContentHider.tsx @@ -165,9 +165,7 @@ export function ContentHider({ } const styles = StyleSheet.create({ - outer: { - overflow: 'hidden', - }, + outer: {}, cover: { flexDirection: 'row', alignItems: 'center', diff --git a/src/components/moderation/PostAlerts.tsx b/src/components/moderation/PostAlerts.tsx index ec7529a4f..efbf18219 100644 --- a/src/components/moderation/PostAlerts.tsx +++ b/src/components/moderation/PostAlerts.tsx @@ -1,25 +1,17 @@ import React from 'react' -import {StyleProp, View, ViewStyle} from 'react-native' -import {BSKY_LABELER_DID, ModerationCause, ModerationUI} from '@atproto/api' +import {StyleProp, ViewStyle} from 'react-native' +import {ModerationUI} from '@atproto/api' import {getModerationCauseKey} from '#/lib/moderation' -import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription' -import {UserAvatar} from '#/view/com/util/UserAvatar' -import {atoms as a, useTheme} from '#/alf' -import {Button} from '#/components/Button' -import { - ModerationDetailsDialog, - useModerationDetailsDialogControl, -} from '#/components/moderation/ModerationDetailsDialog' -import {Text} from '#/components/Typography' +import * as Pills from '#/components/Pills' export function PostAlerts({ modui, - size, + size = 'sm', style, }: { modui: ModerationUI - size?: 'medium' | 'large' + size?: Pills.CommonProps['size'] includeMute?: boolean style?: StyleProp<ViewStyle> }) { @@ -28,90 +20,23 @@ export function PostAlerts({ } return ( - <View style={[a.flex_col, a.gap_xs, style]}> - <View style={[a.flex_row, a.flex_wrap, a.gap_xs]}> - {modui.alerts.map(cause => ( - <PostLabel - key={getModerationCauseKey(cause)} - cause={cause} - size={size} - /> - ))} - {modui.informs.map(cause => ( - <PostLabel - key={getModerationCauseKey(cause)} - cause={cause} - size={size} - /> - ))} - </View> - </View> - ) -} - -function PostLabel({ - cause, - size, -}: { - cause: ModerationCause - size?: 'medium' | 'large' -}) { - const control = useModerationDetailsDialogControl() - const desc = useModerationCauseDescription(cause) - const t = useTheme() - - return ( - <> - <Button - label={desc.name} - onPress={e => { - e.preventDefault() - e.stopPropagation() - control.open() - }}> - {({hovered, pressed}) => ( - <View - style={[ - a.flex_row, - a.align_center, - a.gap_xs, - a.rounded_sm, - hovered || pressed - ? size === 'large' - ? t.atoms.bg_contrast_50 - : t.atoms.bg_contrast_25 - : size === 'large' - ? t.atoms.bg_contrast_25 - : undefined, - size === 'large' - ? {paddingLeft: 4, paddingRight: 6, paddingVertical: 2} - : {paddingRight: 4, paddingVertical: 1}, - ]}> - {desc.sourceType === 'labeler' && - desc.sourceDid !== BSKY_LABELER_DID ? ( - <UserAvatar - avatar={desc.sourceAvi} - size={size === 'large' ? 16 : 12} - type="labeler" - shape="circle" - /> - ) : ( - <desc.icon size="sm" fill={t.atoms.text_contrast_medium.color} /> - )} - <Text - style={[ - a.text_left, - a.leading_snug, - size === 'large' ? {fontSize: 13} : a.text_xs, - size === 'large' ? t.atoms.text : t.atoms.text_contrast_high, - ]}> - {desc.name} - </Text> - </View> - )} - </Button> - - <ModerationDetailsDialog control={control} modcause={cause} /> - </> + <Pills.Row size={size} style={[size === 'sm' && {marginLeft: -3}, style]}> + {modui.alerts.map(cause => ( + <Pills.Label + key={getModerationCauseKey(cause)} + cause={cause} + size={size} + noBg={size === 'sm'} + /> + ))} + {modui.informs.map(cause => ( + <Pills.Label + key={getModerationCauseKey(cause)} + cause={cause} + size={size} + noBg={size === 'sm'} + /> + ))} + </Pills.Row> ) } diff --git a/src/components/moderation/ProfileHeaderAlerts.tsx b/src/components/moderation/ProfileHeaderAlerts.tsx index 4b48b142d..94779697f 100644 --- a/src/components/moderation/ProfileHeaderAlerts.tsx +++ b/src/components/moderation/ProfileHeaderAlerts.tsx @@ -1,25 +1,12 @@ import React from 'react' -import {StyleProp, View, ViewStyle} from 'react-native' -import { - BSKY_LABELER_DID, - ModerationCause, - ModerationDecision, -} from '@atproto/api' +import {StyleProp, ViewStyle} from 'react-native' +import {ModerationDecision} from '@atproto/api' -import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription' import {getModerationCauseKey} from 'lib/moderation' -import {UserAvatar} from '#/view/com/util/UserAvatar' -import {atoms as a, useTheme} from '#/alf' -import {Button} from '#/components/Button' -import { - ModerationDetailsDialog, - useModerationDetailsDialogControl, -} from '#/components/moderation/ModerationDetailsDialog' -import {Text} from '#/components/Typography' +import * as Pills from '#/components/Pills' export function ProfileHeaderAlerts({ moderation, - style, }: { moderation: ModerationDecision style?: StyleProp<ViewStyle> @@ -30,73 +17,21 @@ export function ProfileHeaderAlerts({ } return ( - <View style={[a.flex_col, a.gap_xs, style]}> - <View style={[a.flex_row, a.flex_wrap, a.gap_xs]}> - {modui.alerts.map(cause => ( - <ProfileLabel key={getModerationCauseKey(cause)} cause={cause} /> - ))} - {modui.informs.map(cause => ( - <ProfileLabel key={getModerationCauseKey(cause)} cause={cause} /> - ))} - </View> - </View> - ) -} - -export function ProfileLabel({ - cause, - disableDetailsDialog, -}: { - cause: ModerationCause - disableDetailsDialog?: boolean -}) { - const t = useTheme() - const control = useModerationDetailsDialogControl() - const desc = useModerationCauseDescription(cause) - - return ( - <> - <Button - disabled={disableDetailsDialog} - label={desc.name} - onPress={() => { - control.open() - }}> - {({hovered, pressed}) => ( - <View - style={[ - a.flex_row, - a.align_center, - {paddingLeft: 6, paddingRight: 8, paddingVertical: 4}, - a.gap_xs, - a.rounded_md, - hovered || pressed - ? t.atoms.bg_contrast_50 - : t.atoms.bg_contrast_25, - ]}> - {desc.sourceType === 'labeler' && - desc.sourceDid !== BSKY_LABELER_DID ? ( - <UserAvatar avatar={desc.sourceAvi} size={16} /> - ) : ( - <desc.icon size="sm" fill={t.atoms.text_contrast_medium.color} /> - )} - <Text - style={[ - a.text_left, - a.leading_snug, - a.text_sm, - t.atoms.text_contrast_medium, - a.font_semibold, - ]}> - {desc.name} - </Text> - </View> - )} - </Button> - - {!disableDetailsDialog && ( - <ModerationDetailsDialog control={control} modcause={cause} /> - )} - </> + <Pills.Row size="lg"> + {modui.alerts.map(cause => ( + <Pills.Label + size="lg" + key={getModerationCauseKey(cause)} + cause={cause} + /> + ))} + {modui.informs.map(cause => ( + <Pills.Label + size="lg" + key={getModerationCauseKey(cause)} + cause={cause} + /> + ))} + </Pills.Row> ) } diff --git a/src/screens/Messages/List/ChatListItem.tsx b/src/screens/Messages/List/ChatListItem.tsx index 8ebf8b00b..c45cc28d7 100644 --- a/src/screens/Messages/List/ChatListItem.tsx +++ b/src/screens/Messages/List/ChatListItem.tsx @@ -315,7 +315,7 @@ function ChatListItemReady({ <PostAlerts modui={moderation.ui('contentList')} - size="large" + size="lg" style={[a.pt_xs]} /> </View> diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx index 46c6c958e..0f5350e79 100644 --- a/src/view/com/post-thread/PostThreadItem.tsx +++ b/src/view/com/post-thread/PostThreadItem.tsx @@ -313,7 +313,7 @@ let PostThreadItemLoaded = ({ childContainerStyle={styles.contentHiderChild}> <PostAlerts modui={moderation.ui('contentView')} - size="large" + size="lg" includeMute style={[a.pt_2xs, a.pb_sm]} /> diff --git a/src/view/com/profile/ProfileCard.tsx b/src/view/com/profile/ProfileCard.tsx index d7ed0dd6a..7332d452a 100644 --- a/src/view/com/profile/ProfileCard.tsx +++ b/src/view/com/profile/ProfileCard.tsx @@ -3,13 +3,11 @@ import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native' import { AppBskyActorDefs, moderateProfile, - ModerationCause, ModerationDecision, } from '@atproto/api' import {Trans} from '@lingui/macro' import {useQueryClient} from '@tanstack/react-query' -import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription' import {useProfileShadow} from '#/state/cache/profile-shadow' import {Shadow} from '#/state/cache/types' import {useModerationOpts} from '#/state/preferences/moderation-opts' @@ -26,6 +24,8 @@ import {Text} from '../util/text/Text' import {PreviewableUserAvatar} from '../util/UserAvatar' import {FollowButton} from './FollowButton' import hairlineWidth = StyleSheet.hairlineWidth +import {atoms as a} from '#/alf' +import * as Pills from '#/components/Pills' export function ProfileCard({ testID, @@ -137,58 +137,21 @@ export function ProfileCardPills({ followedBy: boolean moderation: ModerationDecision }) { - const pal = usePalette('default') - const modui = moderation.ui('profileList') if (!followedBy && !modui.inform && !modui.alert) { return null } return ( - <View style={styles.pills}> - {followedBy && ( - <View style={[s.mt5, pal.btn, styles.pill]}> - <Text type="xs" style={pal.text}> - <Trans>Follows You</Trans> - </Text> - </View> - )} + <Pills.Row style={[a.pt_xs]}> + {followedBy && <Pills.FollowsYou />} {modui.alerts.map(alert => ( - <ProfileCardPillModerationCause - key={getModerationCauseKey(alert)} - cause={alert} - severity="alert" - /> + <Pills.Label key={getModerationCauseKey(alert)} cause={alert} /> ))} {modui.informs.map(inform => ( - <ProfileCardPillModerationCause - key={getModerationCauseKey(inform)} - cause={inform} - severity="inform" - /> + <Pills.Label key={getModerationCauseKey(inform)} cause={inform} /> ))} - </View> - ) -} - -function ProfileCardPillModerationCause({ - cause, - severity, -}: { - cause: ModerationCause - severity: 'alert' | 'inform' -}) { - const pal = usePalette('default') - const {name} = useModerationCauseDescription(cause) - return ( - <View - style={[s.mt5, pal.btn, styles.pill]} - key={getModerationCauseKey(cause)}> - <Text type="xs" style={pal.text}> - {severity === 'alert' ? '⚠ ' : ''} - {name} - </Text> - </View> + </Pills.Row> ) } @@ -322,6 +285,7 @@ const styles = StyleSheet.create({ paddingBottom: 10, }, pills: { + alignItems: 'flex-start', flexDirection: 'row', flexWrap: 'wrap', columnGap: 6, |