diff options
Diffstat (limited to 'src/components/LabelingServiceCard/index.tsx')
-rw-r--r-- | src/components/LabelingServiceCard/index.tsx | 182 |
1 files changed, 182 insertions, 0 deletions
diff --git a/src/components/LabelingServiceCard/index.tsx b/src/components/LabelingServiceCard/index.tsx new file mode 100644 index 000000000..f924f0f59 --- /dev/null +++ b/src/components/LabelingServiceCard/index.tsx @@ -0,0 +1,182 @@ +import React from 'react' +import {View} from 'react-native' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {AppBskyLabelerDefs} from '@atproto/api' + +import {getLabelingServiceTitle} from '#/lib/moderation' +import {Link as InternalLink, LinkProps} from '#/components/Link' +import {Text} from '#/components/Typography' +import {useLabelerInfoQuery} from '#/state/queries/labeler' +import {atoms as a, useTheme, ViewStyleProp} from '#/alf' +import {RichText} from '#/components/RichText' +import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '../icons/Chevron' +import {UserAvatar} from '#/view/com/util/UserAvatar' +import {sanitizeHandle} from '#/lib/strings/handles' +import {pluralize} from '#/lib/strings/helpers' + +type LabelingServiceProps = { + labeler: AppBskyLabelerDefs.LabelerViewDetailed +} + +export function Outer({ + children, + style, +}: React.PropsWithChildren<ViewStyleProp>) { + return ( + <View + style={[ + a.flex_row, + a.gap_md, + a.w_full, + a.p_lg, + a.pr_md, + a.overflow_hidden, + style, + ]}> + {children} + </View> + ) +} + +export function Avatar({avatar}: {avatar?: string}) { + return <UserAvatar type="labeler" size={40} avatar={avatar} /> +} + +export function Title({value}: {value: string}) { + return <Text style={[a.text_md, a.font_bold]}>{value}</Text> +} + +export function Description({value, handle}: {value?: string; handle: string}) { + return value ? ( + <Text numberOfLines={2}> + <RichText value={value} style={[]} /> + </Text> + ) : ( + <Text> + <Trans>By {sanitizeHandle(handle, '@')}</Trans> + </Text> + ) +} + +export function LikeCount({count}: {count: number}) { + const t = useTheme() + return ( + <Text + style={[ + a.mt_sm, + a.text_sm, + t.atoms.text_contrast_medium, + {fontWeight: '500'}, + ]}> + <Trans> + Liked by {count} {pluralize(count, 'user')} + </Trans> + </Text> + ) +} + +export function Content({children}: React.PropsWithChildren<{}>) { + const t = useTheme() + + return ( + <View + style={[ + a.flex_1, + a.flex_row, + a.gap_md, + a.align_center, + a.justify_between, + ]}> + <View style={[a.gap_xs, a.flex_1]}>{children}</View> + + <ChevronRight size="md" style={[a.z_10, t.atoms.text_contrast_low]} /> + </View> + ) +} + +/** + * The canonical view for a labeling service. Use this or compose your own. + */ +export function Default({ + labeler, + style, +}: LabelingServiceProps & ViewStyleProp) { + return ( + <Outer style={style}> + <Avatar avatar={labeler.creator.avatar} /> + <Content> + <Title + value={getLabelingServiceTitle({ + displayName: labeler.creator.displayName, + handle: labeler.creator.handle, + })} + /> + <Description + value={labeler.creator.description} + handle={labeler.creator.handle} + /> + {labeler.likeCount ? <LikeCount count={labeler.likeCount} /> : null} + </Content> + </Outer> + ) +} + +export function Link({ + children, + labeler, +}: LabelingServiceProps & Pick<LinkProps, 'children'>) { + const {_} = useLingui() + + return ( + <InternalLink + to={{ + screen: 'Profile', + params: { + name: labeler.creator.handle, + }, + }} + label={_( + msg`View the labeling service provided by @${labeler.creator.handle}`, + )}> + {children} + </InternalLink> + ) +} + +// TODO not finished yet +export function DefaultSkeleton() { + return ( + <View> + <Text>Loading</Text> + </View> + ) +} + +export function Loader({ + did, + loading: LoadingComponent = DefaultSkeleton, + error: ErrorComponent, + component: Component, +}: { + did: string + loading?: React.ComponentType<{}> + error?: React.ComponentType<{error: string}> + component: React.ComponentType<{ + labeler: AppBskyLabelerDefs.LabelerViewDetailed + }> +}) { + const {isLoading, data, error} = useLabelerInfoQuery({did}) + + return isLoading ? ( + LoadingComponent ? ( + <LoadingComponent /> + ) : null + ) : error || !data ? ( + ErrorComponent ? ( + <ErrorComponent error={error?.message || 'Unknown error'} /> + ) : null + ) : ( + <Component labeler={data} /> + ) +} |