Profile redesign: Additional badges (#37683)

This commit is contained in:
Echo
2026-02-02 11:23:08 +01:00
committed by GitHub
parent 65ccf89bfc
commit 1100035af4
6 changed files with 252 additions and 34 deletions

View File

@@ -0,0 +1,64 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
import CelebrationIcon from '@/material-icons/400-24px/celebration-fill.svg?react';
import * as badges from './badge';
const meta = {
component: badges.Badge,
title: 'Components/Badge',
args: {
label: 'Example',
},
} satisfies Meta<typeof badges.Badge>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {};
export const Domain: Story = {
args: {
domain: 'example.com',
},
};
export const CustomIcon: Story = {
args: {
icon: <CelebrationIcon />,
},
};
export const Admin: Story = {
args: {
roleId: '1',
},
render(args) {
return <badges.AdminBadge {...args} />;
},
};
export const Group: Story = {
render(args) {
return <badges.GroupBadge {...args} />;
},
};
export const Automated: Story = {
render(args) {
return <badges.AutomatedBadge {...args} />;
},
};
export const Muted: Story = {
render(args) {
return <badges.MutedBadge {...args} />;
},
};
export const Blocked: Story = {
render(args) {
return <badges.BlockedBadge {...args} />;
},
};

View File

@@ -4,17 +4,28 @@ import { FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import AdminIcon from '@/images/icons/icon_admin.svg?react';
import BlockIcon from '@/material-icons/400-24px/block.svg?react';
import GroupsIcon from '@/material-icons/400-24px/group.svg?react';
import PersonIcon from '@/material-icons/400-24px/person.svg?react';
import SmartToyIcon from '@/material-icons/400-24px/smart_toy.svg?react';
import VolumeOffIcon from '@/material-icons/400-24px/volume_off.svg?react';
export const Badge: FC<{
interface BadgeProps {
label: ReactNode;
icon?: ReactNode;
className?: string;
domain?: ReactNode;
roleId?: string;
}> = ({ icon = <PersonIcon />, label, className, domain, roleId }) => (
}
export const Badge: FC<BadgeProps> = ({
icon = <PersonIcon />,
label,
className,
domain,
roleId,
}) => (
<div
className={classNames('account-role', className)}
data-account-role-id={roleId}
@@ -25,13 +36,23 @@ export const Badge: FC<{
</div>
);
export const GroupBadge: FC<{ className?: string }> = ({ className }) => (
export const AdminBadge: FC<Partial<BadgeProps>> = (props) => (
<Badge
icon={<AdminIcon />}
label={
<FormattedMessage id='account.badges.admin' defaultMessage='Admin' />
}
{...props}
/>
);
export const GroupBadge: FC<Partial<BadgeProps>> = (props) => (
<Badge
icon={<GroupsIcon />}
label={
<FormattedMessage id='account.badges.group' defaultMessage='Group' />
}
className={className}
{...props}
/>
);
@@ -44,3 +65,23 @@ export const AutomatedBadge: FC<{ className?: string }> = ({ className }) => (
className={className}
/>
);
export const MutedBadge: FC<Partial<BadgeProps>> = (props) => (
<Badge
icon={<VolumeOffIcon />}
label={
<FormattedMessage id='account.badges.muted' defaultMessage='Muted' />
}
{...props}
/>
);
export const BlockedBadge: FC<Partial<BadgeProps>> = (props) => (
<Badge
icon={<BlockIcon />}
label={
<FormattedMessage id='account.badges.blocked' defaultMessage='Blocked' />
}
{...props}
/>
);

View File

@@ -101,7 +101,7 @@ export const AccountHeader: React.FC<{
)}
<div className='account__header__image'>
{me !== account.id && relationship && (
{me !== account.id && relationship && !isRedesignEnabled() && (
<AccountInfo relationship={relationship} />
)}

View File

@@ -1,11 +1,22 @@
import type { FC, ReactNode } from 'react';
import { useEffect } from 'react';
import type { FC } from 'react';
import IconAdmin from '@/images/icons/icon_admin.svg?react';
import { AutomatedBadge, Badge, GroupBadge } from '@/mastodon/components/badge';
import { Icon } from '@/mastodon/components/icon';
import { FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import { fetchRelationships } from '@/mastodon/actions/accounts';
import {
AdminBadge,
AutomatedBadge,
Badge,
BlockedBadge,
GroupBadge,
MutedBadge,
} from '@/mastodon/components/badge';
import { useAccount } from '@/mastodon/hooks/useAccount';
import type { AccountRole } from '@/mastodon/models/account';
import { useAppSelector } from '@/mastodon/store';
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
import { isRedesignEnabled } from '../common';
@@ -16,6 +27,17 @@ export const AccountBadges: FC<{ accountId: string }> = ({ accountId }) => {
const localDomain = useAppSelector(
(state) => state.meta.get('domain') as string,
);
const relationship = useAppSelector((state) =>
state.relationships.get(accountId),
);
const dispatch = useAppDispatch();
useEffect(() => {
if (!relationship) {
dispatch(fetchRelationships([accountId]));
}
}, [accountId, dispatch, relationship]);
const badges = [];
if (!account) {
@@ -24,39 +46,113 @@ export const AccountBadges: FC<{ accountId: string }> = ({ accountId }) => {
const className = isRedesignEnabled() ? classes.badge : '';
if (account.bot) {
badges.push(<AutomatedBadge key='bot-badge' className={className} />);
} else if (account.group) {
badges.push(<GroupBadge key='group-badge' className={className} />);
}
const domain = account.acct.includes('@')
? account.acct.split('@')[1]
: localDomain;
account.roles.forEach((role) => {
let icon: ReactNode = undefined;
if (isAdminBadge(role)) {
icon = (
<Icon
icon={IconAdmin}
id='badge-admin'
className={classes.badgeIcon}
noFill
/>
badges.push(
<AdminBadge
key={role.id}
label={role.name}
className={className}
domain={`(${domain})`}
roleId={role.id}
/>,
);
} else {
badges.push(
<Badge
key={role.id}
label={role.name}
className={className}
domain={isRedesignEnabled() ? `(${domain})` : domain}
roleId={role.id}
/>,
);
}
badges.push(
<Badge
key={role.id}
label={role.name}
className={className}
domain={isRedesignEnabled() ? `(${domain})` : domain}
roleId={role.id}
icon={icon}
/>,
);
});
if (account.bot) {
badges.push(<AutomatedBadge key='bot-badge' className={className} />);
}
if (account.group) {
badges.push(<GroupBadge key='group-badge' className={className} />);
}
if (isRedesignEnabled() && relationship) {
if (relationship.blocking) {
badges.push(
<BlockedBadge
key='blocking'
className={classNames(className, classes.badgeBlocked)}
/>,
);
} else if (relationship.domain_blocking) {
badges.push(
<BlockedBadge
key='domain-blocking'
className={classNames(className, classes.badgeBlocked)}
domain={domain}
label={
<FormattedMessage
id='account.badges.domain_blocked'
defaultMessage='Blocked domain'
/>
}
/>,
);
} else if (relationship.muting) {
badges.push(
<MutedBadge
key='muted-badge'
className={classNames(className, classes.badgeMuted)}
/>,
);
} else if (
relationship.followed_by &&
(relationship.following || relationship.requested)
) {
badges.push(
<Badge
key='mutuals-badge'
label={
<FormattedMessage
id='account.badges.mutuals'
defaultMessage='You follow each other'
/>
}
className={className}
/>,
);
} else if (relationship.followed_by) {
badges.push(
<Badge
key='follows-you-badge'
label={
<FormattedMessage
id='account.badges.follows_you'
defaultMessage='Follows you'
/>
}
className={className}
/>,
);
} else if (relationship.requested_by) {
badges.push(
<Badge
key='requested-to-follow-badge'
label={
<FormattedMessage
id='account.badges.requested_to_follow'
defaultMessage='Requested to follow you'
/>
}
className={className}
/>,
);
}
}
if (!badges.length) {
return null;
}

View File

@@ -57,6 +57,16 @@
}
}
.badgeMuted {
background-color: var(--color-bg-inverted);
color: var(--color-text-on-inverted);
}
.badgeBlocked {
background-color: var(--color-bg-error-base);
color: var(--color-text-on-error-base);
}
svg.badgeIcon {
opacity: 1;
}

View File

@@ -17,8 +17,15 @@
"account.activity": "Activity",
"account.add_note": "Add a personal note",
"account.add_or_remove_from_list": "Add or Remove from lists",
"account.badges.admin": "Admin",
"account.badges.blocked": "Blocked",
"account.badges.bot": "Automated",
"account.badges.domain_blocked": "Blocked domain",
"account.badges.follows_you": "Follows you",
"account.badges.group": "Group",
"account.badges.muted": "Muted",
"account.badges.mutuals": "You follow each other",
"account.badges.requested_to_follow": "Requested to follow you",
"account.block": "Block @{name}",
"account.block_domain": "Block domain {domain}",
"account.block_short": "Block",