Profile redesign: Additional badges (#37683)
This commit is contained in:
parent
65ccf89bfc
commit
1100035af4
64
app/javascript/mastodon/components/badge.stories.tsx
Normal file
64
app/javascript/mastodon/components/badge.stories.tsx
Normal 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} />;
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -4,17 +4,28 @@ import { FormattedMessage } from 'react-intl';
|
|||||||
|
|
||||||
import classNames from 'classnames';
|
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 GroupsIcon from '@/material-icons/400-24px/group.svg?react';
|
||||||
import PersonIcon from '@/material-icons/400-24px/person.svg?react';
|
import PersonIcon from '@/material-icons/400-24px/person.svg?react';
|
||||||
import SmartToyIcon from '@/material-icons/400-24px/smart_toy.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;
|
label: ReactNode;
|
||||||
icon?: ReactNode;
|
icon?: ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
domain?: ReactNode;
|
domain?: ReactNode;
|
||||||
roleId?: string;
|
roleId?: string;
|
||||||
}> = ({ icon = <PersonIcon />, label, className, domain, roleId }) => (
|
}
|
||||||
|
|
||||||
|
export const Badge: FC<BadgeProps> = ({
|
||||||
|
icon = <PersonIcon />,
|
||||||
|
label,
|
||||||
|
className,
|
||||||
|
domain,
|
||||||
|
roleId,
|
||||||
|
}) => (
|
||||||
<div
|
<div
|
||||||
className={classNames('account-role', className)}
|
className={classNames('account-role', className)}
|
||||||
data-account-role-id={roleId}
|
data-account-role-id={roleId}
|
||||||
@ -25,13 +36,23 @@ export const Badge: FC<{
|
|||||||
</div>
|
</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
|
<Badge
|
||||||
icon={<GroupsIcon />}
|
icon={<GroupsIcon />}
|
||||||
label={
|
label={
|
||||||
<FormattedMessage id='account.badges.group' defaultMessage='Group' />
|
<FormattedMessage id='account.badges.group' defaultMessage='Group' />
|
||||||
}
|
}
|
||||||
className={className}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -44,3 +65,23 @@ export const AutomatedBadge: FC<{ className?: string }> = ({ className }) => (
|
|||||||
className={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}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|||||||
@ -101,7 +101,7 @@ export const AccountHeader: React.FC<{
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className='account__header__image'>
|
<div className='account__header__image'>
|
||||||
{me !== account.id && relationship && (
|
{me !== account.id && relationship && !isRedesignEnabled() && (
|
||||||
<AccountInfo relationship={relationship} />
|
<AccountInfo relationship={relationship} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@ -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 { FormattedMessage } from 'react-intl';
|
||||||
import { AutomatedBadge, Badge, GroupBadge } from '@/mastodon/components/badge';
|
|
||||||
import { Icon } from '@/mastodon/components/icon';
|
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 { useAccount } from '@/mastodon/hooks/useAccount';
|
||||||
import type { AccountRole } from '@/mastodon/models/account';
|
import type { AccountRole } from '@/mastodon/models/account';
|
||||||
import { useAppSelector } from '@/mastodon/store';
|
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
||||||
|
|
||||||
import { isRedesignEnabled } from '../common';
|
import { isRedesignEnabled } from '../common';
|
||||||
|
|
||||||
@ -16,6 +27,17 @@ export const AccountBadges: FC<{ accountId: string }> = ({ accountId }) => {
|
|||||||
const localDomain = useAppSelector(
|
const localDomain = useAppSelector(
|
||||||
(state) => state.meta.get('domain') as string,
|
(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 = [];
|
const badges = [];
|
||||||
|
|
||||||
if (!account) {
|
if (!account) {
|
||||||
@ -24,39 +46,113 @@ export const AccountBadges: FC<{ accountId: string }> = ({ accountId }) => {
|
|||||||
|
|
||||||
const className = isRedesignEnabled() ? classes.badge : '';
|
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('@')
|
const domain = account.acct.includes('@')
|
||||||
? account.acct.split('@')[1]
|
? account.acct.split('@')[1]
|
||||||
: localDomain;
|
: localDomain;
|
||||||
account.roles.forEach((role) => {
|
account.roles.forEach((role) => {
|
||||||
let icon: ReactNode = undefined;
|
|
||||||
if (isAdminBadge(role)) {
|
if (isAdminBadge(role)) {
|
||||||
icon = (
|
badges.push(
|
||||||
<Icon
|
<AdminBadge
|
||||||
icon={IconAdmin}
|
key={role.id}
|
||||||
id='badge-admin'
|
label={role.name}
|
||||||
className={classes.badgeIcon}
|
className={className}
|
||||||
noFill
|
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) {
|
if (!badges.length) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 {
|
svg.badgeIcon {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,8 +17,15 @@
|
|||||||
"account.activity": "Activity",
|
"account.activity": "Activity",
|
||||||
"account.add_note": "Add a personal note",
|
"account.add_note": "Add a personal note",
|
||||||
"account.add_or_remove_from_list": "Add or Remove from lists",
|
"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.bot": "Automated",
|
||||||
|
"account.badges.domain_blocked": "Blocked domain",
|
||||||
|
"account.badges.follows_you": "Follows you",
|
||||||
"account.badges.group": "Group",
|
"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": "Block @{name}",
|
||||||
"account.block_domain": "Block domain {domain}",
|
"account.block_domain": "Block domain {domain}",
|
||||||
"account.block_short": "Block",
|
"account.block_short": "Block",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user