Profile redesign: Adds a "Follows you" badge (#38549)
This commit is contained in:
parent
627023b452
commit
759e97fd36
1
app/javascript/images/icons/icon_follower.svg
Normal file
1
app/javascript/images/icons/icon_follower.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 16 16"><path fill="currentColor" d="M7.467 7.266 11.533 3.2a.586.586 0 0 1 .85-.001.585.585 0 0 1 0 .851L8.317 8.116zm1.416 1.417 3.5-3.5a.586.586 0 0 1 .85 0 .586.586 0 0 1 0 .85l-3.5 3.5zM3.35 12.267Q2 10.917 1.983 9.017q-.015-1.9 1.334-3.25L5.2 3.882l.883.9q.135.133.25.283.117.15.15.35l2.5-2.5a.586.586 0 0 1 .85 0 .586.586 0 0 1 0 .85L6.8 6.8l-.75.766.15.15q.65.65.642 1.55a2.15 2.15 0 0 1-.659 1.55l-.416.417-.85-.85.416-.417a.98.98 0 0 0 .292-.7.94.94 0 0 0-.276-.705l-.582-.578a.586.586 0 0 1 0-.85l.433-.417a.8.8 0 0 0 .233-.57.77.77 0 0 0-.233-.563L4.15 6.616a3.2 3.2 0 0 0-.975 2.409A3.37 3.37 0 0 0 4.2 11.433q1 1 2.387 1t2.38-1L12.68 7.72a.583.583 0 0 1 .853-.002.586.586 0 0 1 0 .85l-3.716 3.7q-1.342 1.35-3.23 1.35t-3.237-1.35M11.2 15.2V14a2.7 2.7 0 0 0 1.983-.817A2.7 2.7 0 0 0 14 11.2h1.2q0 1.66-1.17 2.83T11.2 15.2M.8 4.8q0-1.66 1.17-2.83T4.8.8V2a2.7 2.7 0 0 0-1.983.816A2.7 2.7 0 0 0 2 4.8z"/></svg>
|
||||
|
After Width: | Height: | Size: 984 B |
@ -2,14 +2,20 @@ import type { Meta, StoryObj } from '@storybook/react-vite';
|
||||
|
||||
import CelebrationIcon from '@/material-icons/400-24px/celebration-fill.svg?react';
|
||||
|
||||
import * as badges from './badge';
|
||||
import * as badges from '.';
|
||||
|
||||
const meta = {
|
||||
component: badges.Badge,
|
||||
title: 'Components/Badge',
|
||||
args: {
|
||||
domain: '',
|
||||
label: undefined,
|
||||
},
|
||||
argTypes: {
|
||||
domain: {
|
||||
control: 'text',
|
||||
},
|
||||
},
|
||||
} satisfies Meta<typeof badges.Badge>;
|
||||
|
||||
export default meta;
|
||||
@ -11,28 +11,38 @@ 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';
|
||||
|
||||
import classes from './styles.module.scss';
|
||||
|
||||
interface BadgeProps {
|
||||
label: ReactNode;
|
||||
icon?: ReactNode;
|
||||
className?: string;
|
||||
domain?: ReactNode;
|
||||
roleId?: string;
|
||||
variant?:
|
||||
| 'default'
|
||||
| 'subtle'
|
||||
| 'inverted'
|
||||
| 'success'
|
||||
| 'warning'
|
||||
| 'danger';
|
||||
}
|
||||
|
||||
export const Badge: FC<BadgeProps> = ({
|
||||
icon = <PersonIcon />,
|
||||
variant = 'default',
|
||||
label,
|
||||
className,
|
||||
domain,
|
||||
roleId,
|
||||
}) => (
|
||||
<div
|
||||
className={classNames('account-role', className)}
|
||||
className={classNames(classes.badge, classes[variant], className)}
|
||||
data-account-role-id={roleId}
|
||||
>
|
||||
{icon}
|
||||
<span>{label}</span>
|
||||
{domain && <span className='account-role__domain'>{domain}</span>}
|
||||
{domain && <span className={classes.domain}>{domain}</span>}
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -89,6 +99,7 @@ export const MutedBadge: FC<
|
||||
return (
|
||||
<Badge
|
||||
icon={<VolumeOffIcon />}
|
||||
variant='inverted'
|
||||
label={
|
||||
label ??
|
||||
(formattedDate ? (
|
||||
@ -111,6 +122,7 @@ export const MutedBadge: FC<
|
||||
export const BlockedBadge: FC<Partial<BadgeProps>> = ({ label, ...props }) => (
|
||||
<Badge
|
||||
icon={<BlockIcon />}
|
||||
variant='danger'
|
||||
label={
|
||||
label ?? (
|
||||
<FormattedMessage
|
||||
55
app/javascript/mastodon/components/badge/styles.module.scss
Normal file
55
app/javascript/mastodon/components/badge/styles.module.scss
Normal file
@ -0,0 +1,55 @@
|
||||
.badge {
|
||||
color: var(--color-text-primary);
|
||||
font-size: 13px;
|
||||
display: inline-flex;
|
||||
padding: 4px;
|
||||
padding-inline-end: 8px;
|
||||
gap: 4px;
|
||||
border-radius: 6px;
|
||||
align-items: center;
|
||||
|
||||
> svg {
|
||||
width: auto;
|
||||
height: 15px;
|
||||
fill: currentColor;
|
||||
opacity: 0.85;
|
||||
}
|
||||
}
|
||||
|
||||
.domain {
|
||||
opacity: 0.75;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.default {
|
||||
background-color: var(--color-bg-secondary);
|
||||
}
|
||||
|
||||
.subtle {
|
||||
background-color: var(--color-bg-brand-softest);
|
||||
}
|
||||
|
||||
.feature {
|
||||
background-color: var(--color-bg-brand-base);
|
||||
color: var(--color-text-on-brand-base);
|
||||
}
|
||||
|
||||
.inverted {
|
||||
background-color: var(--color-bg-inverted);
|
||||
color: var(--color-text-inverted);
|
||||
}
|
||||
|
||||
.success {
|
||||
background-color: var(--color-bg-success-softest);
|
||||
color: var(--color-text-success);
|
||||
}
|
||||
|
||||
.warning {
|
||||
background-color: var(--color-bg-warning-softest);
|
||||
color: var(--color-text-warning);
|
||||
}
|
||||
|
||||
.danger {
|
||||
background-color: var(--color-bg-error-base);
|
||||
color: var(--color-text-on-error-base);
|
||||
}
|
||||
@ -156,7 +156,7 @@ export const AccountHeader: React.FC<{
|
||||
<div
|
||||
className={classNames(
|
||||
'account__header__tabs__name',
|
||||
redesignClasses.nameWrapper,
|
||||
redesignClasses.displayNameWrapper,
|
||||
)}
|
||||
>
|
||||
<AccountName accountId={accountId} />
|
||||
|
||||
@ -7,9 +7,12 @@ import classNames from 'classnames';
|
||||
|
||||
import Overlay from 'react-overlays/esm/Overlay';
|
||||
|
||||
import FollowerIcon from '@/images/icons/icon_follower.svg?react';
|
||||
import { Badge } from '@/mastodon/components/badge';
|
||||
import { DisplayName } from '@/mastodon/components/display_name';
|
||||
import { Icon } from '@/mastodon/components/icon';
|
||||
import { useAccount } from '@/mastodon/hooks/useAccount';
|
||||
import { useRelationship } from '@/mastodon/hooks/useRelationship';
|
||||
import { useAppSelector } from '@/mastodon/store';
|
||||
import AtIcon from '@/material-icons/400-24px/alternate_email.svg?react';
|
||||
import HelpIcon from '@/material-icons/400-24px/help.svg?react';
|
||||
@ -35,6 +38,7 @@ export const AccountName: FC<{ accountId: string }> = ({ accountId }) => {
|
||||
const localDomain = useAppSelector(
|
||||
(state) => state.meta.get('domain') as string,
|
||||
);
|
||||
const relationship = useRelationship(accountId);
|
||||
|
||||
if (!account) {
|
||||
return null;
|
||||
@ -43,10 +47,23 @@ export const AccountName: FC<{ accountId: string }> = ({ accountId }) => {
|
||||
const [username = '', domain = localDomain] = account.acct.split('@');
|
||||
|
||||
return (
|
||||
<div className={classes.name}>
|
||||
<h1>
|
||||
<DisplayName account={account} variant='simple' />
|
||||
</h1>
|
||||
<div className={classes.nameWrapper}>
|
||||
<div className={classes.name}>
|
||||
<h1>
|
||||
<DisplayName account={account} variant='simple' />
|
||||
</h1>
|
||||
{relationship?.followed_by && (
|
||||
<Badge
|
||||
icon={<FollowerIcon className={classes.followerBadgeIcon} />}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='account.follows_you'
|
||||
defaultMessage='Follows you'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<p className={classes.username}>
|
||||
@{username}@{domain}
|
||||
<AccountNameHelp
|
||||
|
||||
@ -3,9 +3,6 @@ import type { FC } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import IconPinned from '@/images/icons/icon_pinned.svg?react';
|
||||
import { fetchRelationships } from '@/mastodon/actions/accounts';
|
||||
import {
|
||||
AdminBadge,
|
||||
@ -15,13 +12,10 @@ import {
|
||||
GroupBadge,
|
||||
MutedBadge,
|
||||
} from '@/mastodon/components/badge';
|
||||
import { Icon } from '@/mastodon/components/icon';
|
||||
import { useAccount } from '@/mastodon/hooks/useAccount';
|
||||
import type { AccountRole } from '@/mastodon/models/account';
|
||||
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
||||
|
||||
import classes from './redesign.module.scss';
|
||||
|
||||
export const AccountBadges: FC<{ accountId: string }> = ({ accountId }) => {
|
||||
const account = useAccount(accountId);
|
||||
const localDomain = useAppSelector(
|
||||
@ -53,7 +47,6 @@ export const AccountBadges: FC<{ accountId: string }> = ({ accountId }) => {
|
||||
<AdminBadge
|
||||
key={role.id}
|
||||
label={role.name}
|
||||
className={classes.badge}
|
||||
domain={`(${domain})`}
|
||||
roleId={role.id}
|
||||
/>,
|
||||
@ -63,7 +56,6 @@ export const AccountBadges: FC<{ accountId: string }> = ({ accountId }) => {
|
||||
<Badge
|
||||
key={role.id}
|
||||
label={role.name}
|
||||
className={classes.badge}
|
||||
domain={`(${domain})`}
|
||||
roleId={role.id}
|
||||
/>,
|
||||
@ -72,25 +64,19 @@ export const AccountBadges: FC<{ accountId: string }> = ({ accountId }) => {
|
||||
});
|
||||
|
||||
if (account.bot) {
|
||||
badges.push(<AutomatedBadge key='bot-badge' className={classes.badge} />);
|
||||
badges.push(<AutomatedBadge key='bot-badge' />);
|
||||
}
|
||||
if (account.group) {
|
||||
badges.push(<GroupBadge key='group-badge' className={classes.badge} />);
|
||||
badges.push(<GroupBadge key='group-badge' />);
|
||||
}
|
||||
if (relationship) {
|
||||
if (relationship.blocking) {
|
||||
badges.push(
|
||||
<BlockedBadge
|
||||
key='blocking'
|
||||
className={classNames(classes.badge, classes.badgeBlocked)}
|
||||
/>,
|
||||
);
|
||||
badges.push(<BlockedBadge key='blocking' />);
|
||||
}
|
||||
if (relationship.domain_blocking) {
|
||||
badges.push(
|
||||
<BlockedBadge
|
||||
key='domain-blocking'
|
||||
className={classNames(classes.badge, classes.badgeBlocked)}
|
||||
domain={domain}
|
||||
label={
|
||||
<FormattedMessage
|
||||
@ -105,7 +91,6 @@ export const AccountBadges: FC<{ accountId: string }> = ({ accountId }) => {
|
||||
badges.push(
|
||||
<MutedBadge
|
||||
key='muted-badge'
|
||||
className={classNames(classes.badge, classes.badgeMuted)}
|
||||
expiresAt={relationship.muting_expires_at}
|
||||
/>,
|
||||
);
|
||||
@ -119,16 +104,6 @@ export const AccountBadges: FC<{ accountId: string }> = ({ accountId }) => {
|
||||
return <div className={'account__header__badges'}>{badges}</div>;
|
||||
};
|
||||
|
||||
export const PinnedBadge: FC = () => (
|
||||
<Badge
|
||||
className={classes.badge}
|
||||
icon={<Icon id='pinned' icon={IconPinned} />}
|
||||
label={
|
||||
<FormattedMessage id='account.timeline.pinned' defaultMessage='Pinned' />
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
function isAdminBadge(role: AccountRole) {
|
||||
const name = role.name.toLowerCase();
|
||||
return name === 'admin' || name === 'owner';
|
||||
|
||||
@ -17,14 +17,20 @@
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.nameWrapper {
|
||||
.displayNameWrapper {
|
||||
display: flex;
|
||||
align-items: start;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.name {
|
||||
.nameWrapper {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.name {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
align-items: baseline;
|
||||
|
||||
> h1 {
|
||||
font-size: 22px;
|
||||
@ -33,6 +39,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.followerBadgeIcon {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.username {
|
||||
display: flex;
|
||||
font-size: 13px;
|
||||
@ -165,38 +175,6 @@ $button-fallback-breakpoint: $button-breakpoint + 55px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.badge {
|
||||
background-color: var(--color-bg-secondary);
|
||||
border: none;
|
||||
color: var(--color-text-secondary);
|
||||
font-weight: 500;
|
||||
padding: 4px;
|
||||
font-size: 13px;
|
||||
|
||||
:global(.account__header__badges) > & {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
> span {
|
||||
font-weight: unset;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.badgeMuted {
|
||||
background-color: var(--color-bg-inverted);
|
||||
color: var(--color-text-inverted);
|
||||
}
|
||||
|
||||
.badgeBlocked {
|
||||
background-color: var(--color-bg-error-base);
|
||||
color: var(--color-text-on-error-base);
|
||||
}
|
||||
|
||||
svg.badgeIcon {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.note {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
@ -11,6 +11,7 @@ import {
|
||||
expandTimelineByKey,
|
||||
timelineKey,
|
||||
} from '@/mastodon/actions/timelines_typed';
|
||||
import { Badge } from '@/mastodon/components/badge';
|
||||
import { Button } from '@/mastodon/components/button';
|
||||
import { Icon } from '@/mastodon/components/icon';
|
||||
import { StatusHeader } from '@/mastodon/components/status/header';
|
||||
@ -18,8 +19,6 @@ import type { StatusHeaderRenderFn } from '@/mastodon/components/status/header';
|
||||
import { selectTimelineByKey } from '@/mastodon/selectors/timelines';
|
||||
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
||||
|
||||
import { PinnedBadge } from '../components/badges';
|
||||
|
||||
import { useAccountContext } from './context';
|
||||
import classes from './styles.module.scss';
|
||||
|
||||
@ -79,7 +78,16 @@ export const renderPinnedStatusHeader: StatusHeaderRenderFn = ({
|
||||
}
|
||||
return (
|
||||
<StatusHeader {...args} className={classes.pinnedStatusHeader}>
|
||||
<PinnedBadge />
|
||||
<Badge
|
||||
className={classes.pinnedBadge}
|
||||
icon={<Icon id='pinned' icon={IconPinned} />}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='account.timeline.pinned'
|
||||
defaultMessage='Pinned'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</StatusHeader>
|
||||
);
|
||||
};
|
||||
|
||||
@ -118,8 +118,8 @@
|
||||
> :global(.status__display-name) {
|
||||
grid-row: span 2;
|
||||
}
|
||||
|
||||
> :global(.account-role) {
|
||||
justify-self: end;
|
||||
}
|
||||
}
|
||||
|
||||
.pinnedBadge {
|
||||
justify-self: end;
|
||||
}
|
||||
|
||||
@ -225,34 +225,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.account-role {
|
||||
display: inline-flex;
|
||||
padding: 4px;
|
||||
padding-inline-end: 8px;
|
||||
border: 1px solid var(--color-border-brand);
|
||||
color: var(--color-text-brand);
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.5px;
|
||||
line-height: 16px;
|
||||
gap: 4px;
|
||||
border-radius: 6px;
|
||||
align-items: center;
|
||||
|
||||
svg {
|
||||
width: auto;
|
||||
height: 15px;
|
||||
opacity: 0.85;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
&__domain {
|
||||
font-weight: 400;
|
||||
opacity: 0.75;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.simple_form .not_recommended {
|
||||
color: var(--color-text-error);
|
||||
background-color: var(--color-bg-error-softest);
|
||||
|
||||
@ -1109,10 +1109,6 @@ a.name-tag,
|
||||
&:hover {
|
||||
color: var(--color-text-brand);
|
||||
}
|
||||
|
||||
.account-role {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
|
||||
@ -8405,16 +8405,6 @@ noscript {
|
||||
}
|
||||
}
|
||||
|
||||
&__badges {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
|
||||
.account-role {
|
||||
line-height: unset;
|
||||
}
|
||||
}
|
||||
|
||||
&__tabs {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user