[Glitch] Add "Follows you" badge to AccountListItem component
Port ffd7160980a98111323c8608a3e557b7fba00c59 to glitch-soc Signed-off-by: Claire <claire.github-309c@sitedethib.com>
This commit is contained in:
parent
11f3393825
commit
20e9618e6d
@ -1,6 +1,8 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/react-vite';
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
||||||
|
|
||||||
import { accountFactoryState } from '@/testing/factories';
|
import { accountFactoryState, relationshipsFactory } from '@/testing/factories';
|
||||||
|
|
||||||
|
import { PendingBadge } from '../badge';
|
||||||
|
|
||||||
import { AccountListItem } from './index';
|
import { AccountListItem } from './index';
|
||||||
|
|
||||||
@ -26,12 +28,30 @@ type Story = StoryObj<typeof meta>;
|
|||||||
|
|
||||||
export const Default: Story = {};
|
export const Default: Story = {};
|
||||||
|
|
||||||
|
export const FollowsYou: Story = {
|
||||||
|
parameters: {
|
||||||
|
state: {
|
||||||
|
relationships: {
|
||||||
|
'1': relationshipsFactory({
|
||||||
|
followed_by: true,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const WithCustomStats: Story = {
|
export const WithCustomStats: Story = {
|
||||||
args: {
|
args: {
|
||||||
stats: ['posts', 'last-active'],
|
stats: ['posts', 'last-active'],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const WithCustomBadge: Story = {
|
||||||
|
args: {
|
||||||
|
badge: <PendingBadge />,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const WithBorder: Story = {
|
export const WithBorder: Story = {
|
||||||
args: {
|
args: {
|
||||||
withBorder: true,
|
withBorder: true,
|
||||||
|
|||||||
@ -1,18 +1,26 @@
|
|||||||
|
import type { ReactNode } from 'react';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
import { FormattedMessage, useIntl } from 'react-intl';
|
import { FormattedMessage, useIntl } from 'react-intl';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { Account } from 'flavours/glitch/components/account';
|
import {
|
||||||
import { VerifiedBadge } from 'flavours/glitch/components/badge';
|
FollowsYouBadge,
|
||||||
|
VerifiedBadge,
|
||||||
|
} from 'flavours/glitch/components/badge';
|
||||||
import { useAccount } from 'flavours/glitch/hooks/useAccount';
|
import { useAccount } from 'flavours/glitch/hooks/useAccount';
|
||||||
import { useRelationship } from 'flavours/glitch/hooks/useRelationship';
|
import { useRelationship } from 'flavours/glitch/hooks/useRelationship';
|
||||||
|
import { domain } from 'flavours/glitch/initial_state';
|
||||||
import type { Relationship } from 'flavours/glitch/models/relationship';
|
import type { Relationship } from 'flavours/glitch/models/relationship';
|
||||||
|
|
||||||
|
import { Avatar } from '../avatar';
|
||||||
|
import { useAccountHandle } from '../display_name/default';
|
||||||
|
import { DisplayNameSimple } from '../display_name/simple';
|
||||||
import { EmojiHTML } from '../emoji/html';
|
import { EmojiHTML } from '../emoji/html';
|
||||||
import { FollowButton } from '../follow_button';
|
import { FollowButton } from '../follow_button';
|
||||||
import { FormattedDateWrapper } from '../formatted_date';
|
import { FormattedDateWrapper } from '../formatted_date';
|
||||||
|
import { ListItemLink, ListItemWrapper } from '../list_item';
|
||||||
import { NumberFields, NumberFieldsItem } from '../number_fields';
|
import { NumberFields, NumberFieldsItem } from '../number_fields';
|
||||||
import { RelativeTimestamp } from '../relative_timestamp';
|
import { RelativeTimestamp } from '../relative_timestamp';
|
||||||
import { ShortNumber } from '../short_number';
|
import { ShortNumber } from '../short_number';
|
||||||
@ -29,9 +37,10 @@ type Stat = 'followers' | 'following' | 'posts' | 'joined' | 'last-active';
|
|||||||
interface Props {
|
interface Props {
|
||||||
accountId: string | undefined;
|
accountId: string | undefined;
|
||||||
stats?: Stat[];
|
stats?: Stat[];
|
||||||
renderButton?: (options: RenderButtonOptions) => React.ReactNode;
|
|
||||||
withBio?: boolean;
|
withBio?: boolean;
|
||||||
withBorder?: boolean;
|
withBorder?: boolean;
|
||||||
|
badge?: ReactNode;
|
||||||
|
renderButton?: (options: RenderButtonOptions) => React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_STATS: Stat[] = ['followers', 'posts', 'last-active'];
|
const DEFAULT_STATS: Stat[] = ['followers', 'posts', 'last-active'];
|
||||||
@ -48,10 +57,12 @@ export const AccountListItem: React.FC<Props> = ({
|
|||||||
stats = DEFAULT_STATS,
|
stats = DEFAULT_STATS,
|
||||||
withBio = true,
|
withBio = true,
|
||||||
withBorder = true,
|
withBorder = true,
|
||||||
|
badge: badgeProp,
|
||||||
renderButton = defaultRenderButton,
|
renderButton = defaultRenderButton,
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const account = useAccount(accountId);
|
const account = useAccount(accountId);
|
||||||
|
const handle = useAccountHandle(account, domain);
|
||||||
const relationship = useRelationship(accountId);
|
const relationship = useRelationship(accountId);
|
||||||
|
|
||||||
const createdThisYear = useMemo(
|
const createdThisYear = useMemo(
|
||||||
@ -63,22 +74,34 @@ export const AccountListItem: React.FC<Props> = ({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const badge =
|
||||||
|
badgeProp ?? (relationship?.followed_by ? <FollowsYouBadge /> : null);
|
||||||
|
|
||||||
const firstVerifiedField = account.fields.find((item) => !!item.verified_at);
|
const firstVerifiedField = account.fields.find((item) => !!item.verified_at);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.wrapper} data-with-border={withBorder}>
|
<div className={classes.wrapper} data-with-border={withBorder}>
|
||||||
<div className={classes.header}>
|
<ListItemWrapper
|
||||||
<Account
|
className={classes.main}
|
||||||
id={accountId}
|
icon={<Avatar account={account} size={40} />}
|
||||||
minimal
|
sideContent={
|
||||||
size={40}
|
<span className={classes.button}>
|
||||||
withMenu={false}
|
{renderButton({ accountId, relationship })}
|
||||||
withBorder={false}
|
</span>
|
||||||
className={classes.account}
|
}
|
||||||
/>
|
>
|
||||||
|
<ListItemLink
|
||||||
{renderButton({ accountId, relationship })}
|
to={`/@${account.acct}`}
|
||||||
</div>
|
data-hover-card-account={accountId}
|
||||||
|
subtitle={handle}
|
||||||
|
>
|
||||||
|
<DisplayNameSimple
|
||||||
|
account={account}
|
||||||
|
className={classes.displayName}
|
||||||
|
/>
|
||||||
|
{badge && <span className={classes.badge}>{badge}</span>}
|
||||||
|
</ListItemLink>
|
||||||
|
</ListItemWrapper>
|
||||||
|
|
||||||
<NumberFields>
|
<NumberFields>
|
||||||
{stats.includes('followers') && (
|
{stats.includes('followers') && (
|
||||||
|
|||||||
@ -10,22 +10,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.main {
|
||||||
display: flex;
|
--list-item-padding: 0;
|
||||||
align-items: start;
|
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.account {
|
.displayName {
|
||||||
--account-name-size: 15px;
|
// Spacing for badge
|
||||||
--account-handle-color: var(--color-text-secondary);
|
margin-inline-end: 6px;
|
||||||
--account-handle-size: 13px;
|
}
|
||||||
--account-bio-color: var(--color-text-primary);
|
|
||||||
--account-bio-size: 13px;
|
|
||||||
--account-outer-spacing: 0;
|
|
||||||
|
|
||||||
flex-grow: 1;
|
.badge {
|
||||||
margin-inline-end: 16px;
|
// Sort out vertical alignment next to name
|
||||||
|
vertical-align: -4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
align-self: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.verifiedBadge {
|
.verifiedBadge {
|
||||||
|
|||||||
@ -6,6 +6,8 @@ import classNames from 'classnames';
|
|||||||
|
|
||||||
import type { OnAttributeHandler } from '@/flavours/glitch/utils/html';
|
import type { OnAttributeHandler } from '@/flavours/glitch/utils/html';
|
||||||
import AdminIcon from '@/images/icons/icon_admin.svg?react';
|
import AdminIcon from '@/images/icons/icon_admin.svg?react';
|
||||||
|
import ClockIcon from '@/images/icons/icon_clock.svg?react';
|
||||||
|
import FollowerIcon from '@/images/icons/icon_follower.svg?react';
|
||||||
import IconVerified from '@/images/icons/icon_verified.svg?react';
|
import IconVerified from '@/images/icons/icon_verified.svg?react';
|
||||||
import BlockIcon from '@/material-icons/400-24px/block.svg?react';
|
import BlockIcon from '@/material-icons/400-24px/block.svg?react';
|
||||||
import GroupsIcon from '@/material-icons/400-24px/group.svg?react';
|
import GroupsIcon from '@/material-icons/400-24px/group.svg?react';
|
||||||
@ -32,6 +34,11 @@ interface BadgeProps extends React.ComponentPropsWithoutRef<'div'> {
|
|||||||
| 'danger';
|
| 'danger';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PresetBadgeProps = Omit<
|
||||||
|
BadgeProps,
|
||||||
|
'label' | 'icon' | 'domain' | 'roleId'
|
||||||
|
>;
|
||||||
|
|
||||||
export const Badge: FC<BadgeProps> = ({
|
export const Badge: FC<BadgeProps> = ({
|
||||||
icon = <PersonIcon />,
|
icon = <PersonIcon />,
|
||||||
variant = 'default',
|
variant = 'default',
|
||||||
@ -83,13 +90,32 @@ export const GroupBadge: FC<Partial<BadgeProps>> = ({ label, ...props }) => (
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const AutomatedBadge: FC<{ className?: string }> = ({ className }) => (
|
export const AutomatedBadge: FC<PresetBadgeProps> = (props) => (
|
||||||
<Badge
|
<Badge
|
||||||
icon={<SmartToyIcon />}
|
icon={<SmartToyIcon />}
|
||||||
label={
|
label={
|
||||||
<FormattedMessage id='account.badges.bot' defaultMessage='Automated' />
|
<FormattedMessage id='account.badges.bot' defaultMessage='Automated' />
|
||||||
}
|
}
|
||||||
className={className}
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const FollowsYouBadge: FC<PresetBadgeProps> = (props) => (
|
||||||
|
<Badge
|
||||||
|
icon={<FollowerIcon />}
|
||||||
|
label={
|
||||||
|
<FormattedMessage id='account.follows_you' defaultMessage='Follows you' />
|
||||||
|
}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const PendingBadge: FC<PresetBadgeProps> = (props) => (
|
||||||
|
<Badge
|
||||||
|
variant='warning'
|
||||||
|
icon={<ClockIcon />}
|
||||||
|
label={<FormattedMessage id='account.pending' defaultMessage='Pending' />}
|
||||||
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
.badge {
|
.badge {
|
||||||
color: var(--color-text-primary);
|
color: var(--color-text-primary);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
font-weight: 400;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
@ -61,7 +62,6 @@
|
|||||||
|
|
||||||
.warning {
|
.warning {
|
||||||
background-color: var(--color-bg-warning-softest);
|
background-color: var(--color-bg-warning-softest);
|
||||||
color: var(--color-text-warning);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.danger {
|
.danger {
|
||||||
|
|||||||
@ -8,14 +8,13 @@ import classNames from 'classnames';
|
|||||||
import Overlay from 'react-overlays/esm/Overlay';
|
import Overlay from 'react-overlays/esm/Overlay';
|
||||||
|
|
||||||
import { showAlert } from '@/flavours/glitch/actions/alerts';
|
import { showAlert } from '@/flavours/glitch/actions/alerts';
|
||||||
import { Badge } from '@/flavours/glitch/components/badge';
|
import { FollowsYouBadge } from '@/flavours/glitch/components/badge';
|
||||||
import { Button } from '@/flavours/glitch/components/button';
|
import { Button } from '@/flavours/glitch/components/button';
|
||||||
import { DisplayName } from '@/flavours/glitch/components/display_name';
|
import { DisplayName } from '@/flavours/glitch/components/display_name';
|
||||||
import { Icon } from '@/flavours/glitch/components/icon';
|
import { Icon } from '@/flavours/glitch/components/icon';
|
||||||
import { useAccount } from '@/flavours/glitch/hooks/useAccount';
|
import { useAccount } from '@/flavours/glitch/hooks/useAccount';
|
||||||
import { useRelationship } from '@/flavours/glitch/hooks/useRelationship';
|
import { useRelationship } from '@/flavours/glitch/hooks/useRelationship';
|
||||||
import { useAppDispatch, useAppSelector } from '@/flavours/glitch/store';
|
import { useAppDispatch, useAppSelector } from '@/flavours/glitch/store';
|
||||||
import FollowerIcon from '@/images/icons/icon_follower.svg?react';
|
|
||||||
import AtIcon from '@/material-icons/400-24px/alternate_email.svg?react';
|
import AtIcon from '@/material-icons/400-24px/alternate_email.svg?react';
|
||||||
import ContentCopyIcon from '@/material-icons/400-24px/content_copy.svg?react';
|
import ContentCopyIcon from '@/material-icons/400-24px/content_copy.svg?react';
|
||||||
import HelpIcon from '@/material-icons/400-24px/help.svg?react';
|
import HelpIcon from '@/material-icons/400-24px/help.svg?react';
|
||||||
@ -60,17 +59,7 @@ export const AccountName: FC<{ accountId: string }> = ({ accountId }) => {
|
|||||||
<h1>
|
<h1>
|
||||||
<DisplayName account={account} variant='simple' />
|
<DisplayName account={account} variant='simple' />
|
||||||
</h1>
|
</h1>
|
||||||
{relationship?.followed_by && (
|
{relationship?.followed_by && <FollowsYouBadge />}
|
||||||
<Badge
|
|
||||||
icon={<FollowerIcon className={classes.followerBadgeIcon} />}
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id='account.follows_you'
|
|
||||||
defaultMessage='Follows you'
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<AccountNameHelp
|
<AccountNameHelp
|
||||||
|
|||||||
@ -37,10 +37,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.followerBadgeIcon {
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.badges {
|
.badges {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user