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>zio/stable
parent
c133661768
commit
14c2d75d49
|
@ -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>
|
||||
)
|
||||
}
|
|
@ -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
|
||||
/>
|
||||
|
|
|
@ -214,7 +214,7 @@ function HeaderReady({
|
|||
]}>
|
||||
<PostAlerts
|
||||
modui={moderation.ui('contentList')}
|
||||
size="large"
|
||||
size="lg"
|
||||
style={[a.pt_xs]}
|
||||
/>
|
||||
</View>
|
||||
|
|
|
@ -165,9 +165,7 @@ export function ContentHider({
|
|||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
outer: {
|
||||
overflow: 'hidden',
|
||||
},
|
||||
outer: {},
|
||||
cover: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
|
|
|
@ -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]}>
|
||||
<Pills.Row size={size} style={[size === 'sm' && {marginLeft: -3}, style]}>
|
||||
{modui.alerts.map(cause => (
|
||||
<PostLabel
|
||||
<Pills.Label
|
||||
key={getModerationCauseKey(cause)}
|
||||
cause={cause}
|
||||
size={size}
|
||||
noBg={size === 'sm'}
|
||||
/>
|
||||
))}
|
||||
{modui.informs.map(cause => (
|
||||
<PostLabel
|
||||
<Pills.Label
|
||||
key={getModerationCauseKey(cause)}
|
||||
cause={cause}
|
||||
size={size}
|
||||
noBg={size === 'sm'}
|
||||
/>
|
||||
))}
|
||||
</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>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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]}>
|
||||
<Pills.Row size="lg">
|
||||
{modui.alerts.map(cause => (
|
||||
<ProfileLabel key={getModerationCauseKey(cause)} cause={cause} />
|
||||
<Pills.Label
|
||||
size="lg"
|
||||
key={getModerationCauseKey(cause)}
|
||||
cause={cause}
|
||||
/>
|
||||
))}
|
||||
{modui.informs.map(cause => (
|
||||
<ProfileLabel key={getModerationCauseKey(cause)} cause={cause} />
|
||||
<Pills.Label
|
||||
size="lg"
|
||||
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>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -315,7 +315,7 @@ function ChatListItemReady({
|
|||
|
||||
<PostAlerts
|
||||
modui={moderation.ui('contentList')}
|
||||
size="large"
|
||||
size="lg"
|
||||
style={[a.pt_xs]}
|
||||
/>
|
||||
</View>
|
||||
|
|
|
@ -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]}
|
||||
/>
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue