[Glitch] Update collection account item design
Port 7d9b1e6d1edcadcbe8a62ede3d94860be785be13 to glitch-soc Signed-off-by: Claire <claire.github-309c@sitedethib.com>
This commit is contained in:
parent
2a5a64d057
commit
2ee6e447b4
@ -20,6 +20,7 @@ import { openModal } from 'flavours/glitch/actions/modal';
|
|||||||
import { initMuteModal } from 'flavours/glitch/actions/mutes';
|
import { initMuteModal } from 'flavours/glitch/actions/mutes';
|
||||||
import { apiFollowAccount } from 'flavours/glitch/api/accounts';
|
import { apiFollowAccount } from 'flavours/glitch/api/accounts';
|
||||||
import { Avatar } from 'flavours/glitch/components/avatar';
|
import { Avatar } from 'flavours/glitch/components/avatar';
|
||||||
|
import { VerifiedBadge } from 'flavours/glitch/components/badge';
|
||||||
import { Button } from 'flavours/glitch/components/button';
|
import { Button } from 'flavours/glitch/components/button';
|
||||||
import { FollowersCounter } from 'flavours/glitch/components/counters';
|
import { FollowersCounter } from 'flavours/glitch/components/counters';
|
||||||
import { DisplayName } from 'flavours/glitch/components/display_name';
|
import { DisplayName } from 'flavours/glitch/components/display_name';
|
||||||
@ -28,7 +29,6 @@ import { FollowButton } from 'flavours/glitch/components/follow_button';
|
|||||||
import { RelativeTimestamp } from 'flavours/glitch/components/relative_timestamp';
|
import { RelativeTimestamp } from 'flavours/glitch/components/relative_timestamp';
|
||||||
import { ShortNumber } from 'flavours/glitch/components/short_number';
|
import { ShortNumber } from 'flavours/glitch/components/short_number';
|
||||||
import { Skeleton } from 'flavours/glitch/components/skeleton';
|
import { Skeleton } from 'flavours/glitch/components/skeleton';
|
||||||
import { VerifiedBadge } from 'flavours/glitch/components/verified_badge';
|
|
||||||
import { useIdentity } from 'flavours/glitch/identity_context';
|
import { useIdentity } from 'flavours/glitch/identity_context';
|
||||||
import { me } from 'flavours/glitch/initial_state';
|
import { me } from 'flavours/glitch/initial_state';
|
||||||
import type { MenuItem } from 'flavours/glitch/models/dropdown_menu';
|
import type { MenuItem } from 'flavours/glitch/models/dropdown_menu';
|
||||||
|
|||||||
@ -0,0 +1,45 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
||||||
|
|
||||||
|
import { accountFactoryState } from '@/testing/factories';
|
||||||
|
|
||||||
|
import { AccountListItem } from './index';
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Components/AccountListItem',
|
||||||
|
component: AccountListItem,
|
||||||
|
args: {
|
||||||
|
accountId: '1',
|
||||||
|
withBorder: false,
|
||||||
|
},
|
||||||
|
parameters: {
|
||||||
|
state: {
|
||||||
|
accounts: {
|
||||||
|
'1': accountFactoryState(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} satisfies Meta<typeof AccountListItem>;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Default: Story = {};
|
||||||
|
|
||||||
|
export const WithCustomStats: Story = {
|
||||||
|
args: {
|
||||||
|
stats: ['posts', 'last-active'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithBorder: Story = {
|
||||||
|
args: {
|
||||||
|
withBorder: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithoutButton: Story = {
|
||||||
|
args: {
|
||||||
|
renderButton: () => null,
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -0,0 +1,184 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
import { FormattedMessage, useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import { Account } from 'flavours/glitch/components/account';
|
||||||
|
import { VerifiedBadge } from 'flavours/glitch/components/badge';
|
||||||
|
import { useAccount } from 'flavours/glitch/hooks/useAccount';
|
||||||
|
import { useRelationship } from 'flavours/glitch/hooks/useRelationship';
|
||||||
|
import type { Relationship } from 'flavours/glitch/models/relationship';
|
||||||
|
|
||||||
|
import { EmojiHTML } from '../emoji/html';
|
||||||
|
import { FamiliarFollowers } from '../familiar_followers';
|
||||||
|
import { FollowButton } from '../follow_button';
|
||||||
|
import { FormattedDateWrapper } from '../formatted_date';
|
||||||
|
import { NumberFields, NumberFieldsItem } from '../number_fields';
|
||||||
|
import { RelativeTimestamp } from '../relative_timestamp';
|
||||||
|
import { ShortNumber } from '../short_number';
|
||||||
|
|
||||||
|
import classes from './styles.module.scss';
|
||||||
|
|
||||||
|
export interface RenderButtonOptions {
|
||||||
|
accountId: string | undefined;
|
||||||
|
relationship: Relationship | null | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Stat = 'followers' | 'following' | 'posts' | 'joined' | 'last-active';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
accountId: string | undefined;
|
||||||
|
stats?: Stat[];
|
||||||
|
renderButton?: (options: RenderButtonOptions) => React.ReactNode;
|
||||||
|
withBorder?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_STATS: Stat[] = ['followers', 'following', 'joined'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extended account list item with bio, verified link badge,
|
||||||
|
* and familiar follower widget.
|
||||||
|
*
|
||||||
|
* The displayed account stats can be customised using the `stats` prop,
|
||||||
|
* and button rendering can be customised via the `renderButton` prop.
|
||||||
|
*/
|
||||||
|
export const AccountListItem: React.FC<Props> = ({
|
||||||
|
accountId,
|
||||||
|
stats = DEFAULT_STATS,
|
||||||
|
withBorder = true,
|
||||||
|
renderButton = defaultRenderButton,
|
||||||
|
}) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const account = useAccount(accountId);
|
||||||
|
const relationship = useRelationship(accountId);
|
||||||
|
|
||||||
|
const createdThisYear = useMemo(
|
||||||
|
() => account?.created_at.includes(new Date().getFullYear().toString()),
|
||||||
|
[account?.created_at],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!accountId || !account) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstVerifiedField = account.fields.find((item) => !!item.verified_at);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.wrapper} data-with-border={withBorder}>
|
||||||
|
<div className={classes.header}>
|
||||||
|
<Account
|
||||||
|
id={accountId}
|
||||||
|
minimal
|
||||||
|
size={40}
|
||||||
|
withMenu={false}
|
||||||
|
withBorder={false}
|
||||||
|
className={classes.account}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{renderButton({ accountId, relationship })}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<NumberFields>
|
||||||
|
{stats.includes('followers') && (
|
||||||
|
<NumberFieldsItem
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='account.followers'
|
||||||
|
defaultMessage='Followers'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
hint={intl.formatNumber(account.followers_count)}
|
||||||
|
>
|
||||||
|
<ShortNumber value={account.followers_count} />
|
||||||
|
</NumberFieldsItem>
|
||||||
|
)}
|
||||||
|
{stats.includes('following') && (
|
||||||
|
<NumberFieldsItem
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='account.following'
|
||||||
|
defaultMessage='Following'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
hint={intl.formatNumber(account.following_count)}
|
||||||
|
link={`/@${account.acct}/following`}
|
||||||
|
>
|
||||||
|
<ShortNumber value={account.following_count} />
|
||||||
|
</NumberFieldsItem>
|
||||||
|
)}
|
||||||
|
{stats.includes('posts') && (
|
||||||
|
<NumberFieldsItem
|
||||||
|
label={
|
||||||
|
<FormattedMessage id='account.posts' defaultMessage='Posts' />
|
||||||
|
}
|
||||||
|
hint={intl.formatNumber(account.statuses_count)}
|
||||||
|
>
|
||||||
|
<ShortNumber value={account.statuses_count} />
|
||||||
|
</NumberFieldsItem>
|
||||||
|
)}
|
||||||
|
{stats.includes('joined') && (
|
||||||
|
<NumberFieldsItem
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='account.joined_short'
|
||||||
|
defaultMessage='Joined'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
hint={intl.formatDate(account.created_at)}
|
||||||
|
>
|
||||||
|
{createdThisYear ? (
|
||||||
|
<FormattedDateWrapper
|
||||||
|
value={account.created_at}
|
||||||
|
month='short'
|
||||||
|
day='2-digit'
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<FormattedDateWrapper value={account.created_at} year='numeric' />
|
||||||
|
)}
|
||||||
|
</NumberFieldsItem>
|
||||||
|
)}
|
||||||
|
{stats.includes('last-active') && (
|
||||||
|
<NumberFieldsItem
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='account.last_active'
|
||||||
|
defaultMessage='Last active'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<RelativeTimestamp
|
||||||
|
long
|
||||||
|
timestamp={account.last_status_at}
|
||||||
|
noFuture
|
||||||
|
/>
|
||||||
|
</NumberFieldsItem>
|
||||||
|
)}
|
||||||
|
{firstVerifiedField && (
|
||||||
|
<VerifiedBadge
|
||||||
|
link={firstVerifiedField.value}
|
||||||
|
className={classes.verifiedBadge}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</NumberFields>
|
||||||
|
<FamiliarFollowers accountId={accountId} />
|
||||||
|
{account.note.length > 0 && (
|
||||||
|
<EmojiHTML
|
||||||
|
className={classNames(classes.bio, 'translate')}
|
||||||
|
htmlString={account.note_emojified}
|
||||||
|
extraEmojis={account.emojis}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultRenderButton = ({ accountId }: RenderButtonOptions) => (
|
||||||
|
<AccountListItemFollowButton accountId={accountId} />
|
||||||
|
);
|
||||||
|
|
||||||
|
export const AccountListItemFollowButton: React.FC<{
|
||||||
|
accountId: string | undefined;
|
||||||
|
}> = ({ accountId }) => (
|
||||||
|
<FollowButton compact labelLength='short' accountId={accountId} />
|
||||||
|
);
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
.wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: start;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 16px;
|
||||||
|
|
||||||
|
&[data-with-border='true'] {
|
||||||
|
border-bottom: 1px solid var(--color-border-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.account__note) {
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 3;
|
||||||
|
line-clamp: 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
align-items: start;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account {
|
||||||
|
--account-name-size: 15px;
|
||||||
|
--account-handle-color: var(--color-text-secondary);
|
||||||
|
--account-handle-size: 13px;
|
||||||
|
--account-bio-color: var(--color-text-primary);
|
||||||
|
--account-bio-size: 13px;
|
||||||
|
--account-outer-spacing: 0;
|
||||||
|
|
||||||
|
flex-grow: 1;
|
||||||
|
margin-inline-end: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verifiedBadge {
|
||||||
|
align-self: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bio {
|
||||||
|
:any-link {
|
||||||
|
color: var(--color-text-status-links);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -35,6 +35,12 @@ export const Domain: Story = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const Verified: Story = {
|
||||||
|
render() {
|
||||||
|
return <badges.VerifiedBadge link='example.com' />;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const CustomIcon: Story = {
|
export const CustomIcon: Story = {
|
||||||
args: {
|
args: {
|
||||||
...Default.args,
|
...Default.args,
|
||||||
|
|||||||
@ -4,19 +4,23 @@ import { FormattedMessage, useIntl } from 'react-intl';
|
|||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
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 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';
|
||||||
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';
|
import VolumeOffIcon from '@/material-icons/400-24px/volume_off.svg?react';
|
||||||
|
|
||||||
|
import { EmojiHTML } from '../emoji/html';
|
||||||
|
import { Icon } from '../icon';
|
||||||
|
|
||||||
import classes from './styles.module.scss';
|
import classes from './styles.module.scss';
|
||||||
|
|
||||||
interface BadgeProps {
|
interface BadgeProps extends React.ComponentPropsWithoutRef<'div'> {
|
||||||
label: ReactNode;
|
label: ReactNode;
|
||||||
icon?: ReactNode;
|
icon?: ReactNode;
|
||||||
className?: string;
|
|
||||||
domain?: ReactNode;
|
domain?: ReactNode;
|
||||||
roleId?: string;
|
roleId?: string;
|
||||||
variant?:
|
variant?:
|
||||||
@ -35,9 +39,16 @@ export const Badge: FC<BadgeProps> = ({
|
|||||||
className,
|
className,
|
||||||
domain,
|
domain,
|
||||||
roleId,
|
roleId,
|
||||||
|
...otherProps
|
||||||
}) => (
|
}) => (
|
||||||
<div
|
<div
|
||||||
className={classNames(classes.badge, classes[variant], className)}
|
{...otherProps}
|
||||||
|
className={classNames(
|
||||||
|
classes.badge,
|
||||||
|
!icon && classes.badgeWithoutIcon,
|
||||||
|
classes[variant],
|
||||||
|
className,
|
||||||
|
)}
|
||||||
data-account-role-id={roleId}
|
data-account-role-id={roleId}
|
||||||
>
|
>
|
||||||
{icon}
|
{icon}
|
||||||
@ -134,3 +145,31 @@ export const BlockedBadge: FC<Partial<BadgeProps>> = ({ label, ...props }) => (
|
|||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const onAttribute: OnAttributeHandler = (name, value, tagName) => {
|
||||||
|
if (name === 'rel' && tagName === 'a') {
|
||||||
|
if (value === 'me') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
name,
|
||||||
|
value
|
||||||
|
.split(' ')
|
||||||
|
.filter((x) => x !== 'me')
|
||||||
|
.join(' '),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const VerifiedBadge: React.FC<{ link: string; className?: string }> = ({
|
||||||
|
link,
|
||||||
|
className,
|
||||||
|
}) => (
|
||||||
|
<Badge
|
||||||
|
variant='success'
|
||||||
|
icon={<Icon id='verified' icon={IconVerified} noFill />}
|
||||||
|
label={<EmojiHTML as='span' htmlString={link} onAttribute={onAttribute} />}
|
||||||
|
className={className}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|||||||
@ -2,18 +2,28 @@
|
|||||||
color: var(--color-text-primary);
|
color: var(--color-text-primary);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
max-width: 100%;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
padding-inline-end: 8px;
|
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
border-radius: 6px;
|
border-radius: 8px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
|
||||||
> svg {
|
> svg {
|
||||||
width: auto;
|
width: auto;
|
||||||
height: 15px;
|
height: 17px;
|
||||||
fill: currentColor;
|
fill: currentColor;
|
||||||
opacity: 0.85;
|
opacity: 0.85;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.badgeWithoutIcon) {
|
||||||
|
padding-inline-end: 8px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.domain {
|
.domain {
|
||||||
@ -41,7 +51,6 @@
|
|||||||
|
|
||||||
.success {
|
.success {
|
||||||
background-color: var(--color-bg-success-softest);
|
background-color: var(--color-bg-success-softest);
|
||||||
color: var(--color-text-success);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.warning {
|
.warning {
|
||||||
|
|||||||
@ -1,11 +1,14 @@
|
|||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { Avatar } from '@/flavours/glitch/components/avatar';
|
import { Avatar } from '@/flavours/glitch/components/avatar';
|
||||||
import { AvatarGroup } from '@/flavours/glitch/components/avatar_group';
|
import { AvatarGroup } from '@/flavours/glitch/components/avatar_group';
|
||||||
import { LinkedDisplayName } from '@/flavours/glitch/components/display_name';
|
import { LinkedDisplayName } from '@/flavours/glitch/components/display_name';
|
||||||
import type { Account } from '@/flavours/glitch/models/account';
|
import type { Account } from '@/flavours/glitch/models/account';
|
||||||
|
|
||||||
import { useFetchFamiliarFollowers } from '../hooks/familiar_followers';
|
import classes from './styles.module.scss';
|
||||||
|
import { useFetchFamiliarFollowers } from './use_fetch_familiar_followers';
|
||||||
|
|
||||||
const FamiliarFollowersReadout: React.FC<{ familiarFollowers: Account[] }> = ({
|
const FamiliarFollowersReadout: React.FC<{ familiarFollowers: Account[] }> = ({
|
||||||
familiarFollowers,
|
familiarFollowers,
|
||||||
@ -51,9 +54,10 @@ const FamiliarFollowersReadout: React.FC<{ familiarFollowers: Account[] }> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FamiliarFollowers: React.FC<{ accountId: string }> = ({
|
export const FamiliarFollowers: React.FC<{
|
||||||
accountId,
|
accountId: string;
|
||||||
}) => {
|
className?: string;
|
||||||
|
}> = ({ accountId, className }) => {
|
||||||
const { familiarFollowers, isLoading } = useFetchFamiliarFollowers({
|
const { familiarFollowers, isLoading } = useFetchFamiliarFollowers({
|
||||||
accountId,
|
accountId,
|
||||||
});
|
});
|
||||||
@ -63,10 +67,10 @@ export const FamiliarFollowers: React.FC<{ accountId: string }> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='account__header__familiar-followers'>
|
<div className={classNames(classes.wrapper, className)}>
|
||||||
<AvatarGroup compact>
|
<AvatarGroup compact avatarHeight={24}>
|
||||||
{familiarFollowers.slice(0, 3).map((account) => (
|
{familiarFollowers.slice(0, 3).map((account) => (
|
||||||
<Avatar withLink key={account.id} account={account} size={28} />
|
<Avatar withLink key={account.id} account={account} size={24} />
|
||||||
))}
|
))}
|
||||||
</AvatarGroup>
|
</AvatarGroup>
|
||||||
<span>
|
<span>
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
.wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
a:any-link {
|
||||||
|
font-weight: 500;
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,6 +4,7 @@ import { FormattedMessage } from 'react-intl';
|
|||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import { useFetchFamiliarFollowers } from '@/flavours/glitch/components/familiar_followers/use_fetch_familiar_followers';
|
||||||
import { fetchAccount } from 'flavours/glitch/actions/accounts';
|
import { fetchAccount } from 'flavours/glitch/actions/accounts';
|
||||||
import { AccountBio } from 'flavours/glitch/components/account_bio';
|
import { AccountBio } from 'flavours/glitch/components/account_bio';
|
||||||
import { AccountFields } from 'flavours/glitch/components/account_fields';
|
import { AccountFields } from 'flavours/glitch/components/account_fields';
|
||||||
@ -18,7 +19,6 @@ import { FollowButton } from 'flavours/glitch/components/follow_button';
|
|||||||
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
|
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
|
||||||
import { Permalink } from 'flavours/glitch/components/permalink';
|
import { Permalink } from 'flavours/glitch/components/permalink';
|
||||||
import { ShortNumber } from 'flavours/glitch/components/short_number';
|
import { ShortNumber } from 'flavours/glitch/components/short_number';
|
||||||
import { useFetchFamiliarFollowers } from 'flavours/glitch/features/account_timeline/hooks/familiar_followers';
|
|
||||||
import { domain } from 'flavours/glitch/initial_state';
|
import { domain } from 'flavours/glitch/initial_state';
|
||||||
import { getAccountHidden } from 'flavours/glitch/selectors/accounts';
|
import { getAccountHidden } from 'flavours/glitch/selectors/accounts';
|
||||||
import { useAppSelector, useAppDispatch } from 'flavours/glitch/store';
|
import { useAppSelector, useAppDispatch } from 'flavours/glitch/store';
|
||||||
|
|||||||
@ -1,22 +1,21 @@
|
|||||||
|
import classNames from 'classnames';
|
||||||
import { NavLink } from 'react-router-dom';
|
import { NavLink } from 'react-router-dom';
|
||||||
|
|
||||||
import type { MastodonLocationDescriptor } from 'flavours/glitch/components/router';
|
import type { MastodonLocationDescriptor } from 'flavours/glitch/components/router';
|
||||||
|
|
||||||
import classes from './styles.module.scss';
|
import classes from './styles.module.scss';
|
||||||
|
|
||||||
interface WrapperProps {
|
export const NumberFields: React.FC<React.ComponentPropsWithoutRef<'ul'>> = ({
|
||||||
children: React.ReactNode;
|
children,
|
||||||
}
|
className,
|
||||||
|
}) => {
|
||||||
export const NumberFields: React.FC<WrapperProps> = ({ children }) => {
|
return <ul className={classNames(classes.list, className)}>{children}</ul>;
|
||||||
return <ul className={classes.list}>{children}</ul>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
interface ItemProps {
|
interface ItemProps extends React.ComponentPropsWithoutRef<'li'> {
|
||||||
label: React.ReactNode;
|
label: React.ReactNode;
|
||||||
hint?: string;
|
hint?: string;
|
||||||
link?: MastodonLocationDescriptor;
|
link?: MastodonLocationDescriptor;
|
||||||
children: React.ReactNode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NumberFieldsItem: React.FC<ItemProps> = ({
|
export const NumberFieldsItem: React.FC<ItemProps> = ({
|
||||||
@ -24,9 +23,10 @@ export const NumberFieldsItem: React.FC<ItemProps> = ({
|
|||||||
hint,
|
hint,
|
||||||
link,
|
link,
|
||||||
children,
|
children,
|
||||||
|
className,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<li className={classes.item} title={hint}>
|
<li className={classNames(classes.item, className)} title={hint}>
|
||||||
{label}
|
{label}
|
||||||
{link ? (
|
{link ? (
|
||||||
<NavLink exact to={link}>
|
<NavLink exact to={link}>
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
.list {
|
.list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
margin: 8px 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
gap: 4px 20px;
|
gap: 4px 24px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
}
|
}
|
||||||
@ -19,6 +19,7 @@
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--color-text-primary);
|
color: var(--color-text-primary);
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
|
|||||||
@ -1,32 +0,0 @@
|
|||||||
import { EmojiHTML } from '@/flavours/glitch/components/emoji/html';
|
|
||||||
import CheckIcon from '@/material-icons/400-24px/check.svg?react';
|
|
||||||
|
|
||||||
import type { OnAttributeHandler } from '../utils/html';
|
|
||||||
|
|
||||||
import { Icon } from './icon';
|
|
||||||
|
|
||||||
const onAttribute: OnAttributeHandler = (name, value, tagName) => {
|
|
||||||
if (name === 'rel' && tagName === 'a') {
|
|
||||||
if (value === 'me') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return [
|
|
||||||
name,
|
|
||||||
value
|
|
||||||
.split(' ')
|
|
||||||
.filter((x) => x !== 'me')
|
|
||||||
.join(' '),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
link: string;
|
|
||||||
}
|
|
||||||
export const VerifiedBadge: React.FC<Props> = ({ link }) => (
|
|
||||||
<span className='verified-badge'>
|
|
||||||
<Icon id='check' icon={CheckIcon} className='verified-badge__mark' />
|
|
||||||
<EmojiHTML as='span' htmlString={link} onAttribute={onAttribute} />
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
@ -19,10 +19,11 @@ import type { Account } from '@/flavours/glitch/models/account';
|
|||||||
import { getAccountHidden } from '@/flavours/glitch/selectors/accounts';
|
import { getAccountHidden } from '@/flavours/glitch/selectors/accounts';
|
||||||
import { useAppSelector, useAppDispatch } from '@/flavours/glitch/store';
|
import { useAppSelector, useAppDispatch } from '@/flavours/glitch/store';
|
||||||
|
|
||||||
|
import { FamiliarFollowers } from '../../../components/familiar_followers';
|
||||||
|
|
||||||
import { AccountName } from './account_name';
|
import { AccountName } from './account_name';
|
||||||
import { AccountSubscriptionForm } from './account_subscription_form';
|
import { AccountSubscriptionForm } from './account_subscription_form';
|
||||||
import { AccountButtons } from './buttons';
|
import { AccountButtons } from './buttons';
|
||||||
import { FamiliarFollowers } from './familiar_followers';
|
|
||||||
import { AccountHeaderFields } from './fields';
|
import { AccountHeaderFields } from './fields';
|
||||||
import { AccountInfo } from './info';
|
import { AccountInfo } from './info';
|
||||||
import { MemorialNote } from './memorial_note';
|
import { MemorialNote } from './memorial_note';
|
||||||
@ -165,7 +166,10 @@ export const AccountHeader: React.FC<{
|
|||||||
<AccountNumberFields accountId={accountId} />
|
<AccountNumberFields accountId={accountId} />
|
||||||
|
|
||||||
{!isMe && !suspendedOrHidden && (
|
{!isMe && !suspendedOrHidden && (
|
||||||
<FamiliarFollowers accountId={accountId} />
|
<FamiliarFollowers
|
||||||
|
accountId={accountId}
|
||||||
|
className={classes.familiarFollowers}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!suspendedOrHidden && (
|
{!suspendedOrHidden && (
|
||||||
|
|||||||
@ -3,22 +3,17 @@ import { useCallback, useRef, useState } from 'react';
|
|||||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||||
|
|
||||||
import type { ApiCollectionJSON } from 'flavours/glitch/api_types/collections';
|
import type { ApiCollectionJSON } from 'flavours/glitch/api_types/collections';
|
||||||
import { Account } from 'flavours/glitch/components/account';
|
import type { RenderButtonOptions } from 'flavours/glitch/components/account_list_item';
|
||||||
|
import {
|
||||||
|
AccountListItem,
|
||||||
|
AccountListItemFollowButton,
|
||||||
|
} from 'flavours/glitch/components/account_list_item';
|
||||||
import { Button } from 'flavours/glitch/components/button';
|
import { Button } from 'flavours/glitch/components/button';
|
||||||
import { Callout } from 'flavours/glitch/components/callout';
|
import { Callout } from 'flavours/glitch/components/callout';
|
||||||
import { FollowButton } from 'flavours/glitch/components/follow_button';
|
|
||||||
import {
|
|
||||||
NumberFields,
|
|
||||||
NumberFieldsItem,
|
|
||||||
} from 'flavours/glitch/components/number_fields';
|
|
||||||
import { RelativeTimestamp } from 'flavours/glitch/components/relative_timestamp';
|
|
||||||
import {
|
import {
|
||||||
Article,
|
Article,
|
||||||
ItemList,
|
ItemList,
|
||||||
} from 'flavours/glitch/components/scrollable_list/components';
|
} from 'flavours/glitch/components/scrollable_list/components';
|
||||||
import { ShortNumber } from 'flavours/glitch/components/short_number';
|
|
||||||
import { useAccount } from 'flavours/glitch/hooks/useAccount';
|
|
||||||
import { useRelationship } from 'flavours/glitch/hooks/useRelationship';
|
|
||||||
import { me } from 'flavours/glitch/initial_state';
|
import { me } from 'flavours/glitch/initial_state';
|
||||||
|
|
||||||
import { useConfirmRevoke } from './revoke_collection_inclusion_modal';
|
import { useConfirmRevoke } from './revoke_collection_inclusion_modal';
|
||||||
@ -31,99 +26,6 @@ const messages = defineMessages({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const AccountItem: React.FC<{
|
|
||||||
accountId: string | undefined;
|
|
||||||
collectionOwnerId: string;
|
|
||||||
onRevoke: () => void;
|
|
||||||
withBio?: boolean;
|
|
||||||
withBorder?: boolean;
|
|
||||||
}> = ({
|
|
||||||
accountId,
|
|
||||||
collectionOwnerId,
|
|
||||||
onRevoke,
|
|
||||||
withBio = true,
|
|
||||||
withBorder = true,
|
|
||||||
}) => {
|
|
||||||
const intl = useIntl();
|
|
||||||
const account = useAccount(accountId);
|
|
||||||
const relationship = useRelationship(accountId);
|
|
||||||
|
|
||||||
if (!accountId || !account) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// When viewing your own collection, only show the Follow button
|
|
||||||
// for accounts you're not following (anymore).
|
|
||||||
// Otherwise, always show the follow button in its various states.
|
|
||||||
const isOwnAccount = accountId === me;
|
|
||||||
const withoutButton =
|
|
||||||
isOwnAccount ||
|
|
||||||
!relationship ||
|
|
||||||
(collectionOwnerId === me &&
|
|
||||||
(relationship.following || relationship.requested));
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classes.accountItemWrapper} data-with-border={withBorder}>
|
|
||||||
<Account
|
|
||||||
minimal
|
|
||||||
id={accountId}
|
|
||||||
withBio={withBio}
|
|
||||||
withBorder={false}
|
|
||||||
withMenu={false}
|
|
||||||
className={classes.accountItem}
|
|
||||||
extraAccountInfo={
|
|
||||||
<NumberFields>
|
|
||||||
<NumberFieldsItem
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id='account.followers'
|
|
||||||
defaultMessage='Followers'
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
hint={intl.formatNumber(account.followers_count)}
|
|
||||||
>
|
|
||||||
<ShortNumber value={account.followers_count} />
|
|
||||||
</NumberFieldsItem>
|
|
||||||
|
|
||||||
<NumberFieldsItem
|
|
||||||
label={
|
|
||||||
<FormattedMessage id='account.posts' defaultMessage='Posts' />
|
|
||||||
}
|
|
||||||
hint={intl.formatNumber(account.statuses_count)}
|
|
||||||
>
|
|
||||||
<ShortNumber value={account.statuses_count} />
|
|
||||||
</NumberFieldsItem>
|
|
||||||
|
|
||||||
<NumberFieldsItem
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id='account.last_active'
|
|
||||||
defaultMessage='Last active'
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<RelativeTimestamp
|
|
||||||
long
|
|
||||||
timestamp={account.last_status_at}
|
|
||||||
noFuture
|
|
||||||
/>
|
|
||||||
</NumberFieldsItem>
|
|
||||||
</NumberFields>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
{!withoutButton && <FollowButton compact accountId={accountId} />}
|
|
||||||
{isOwnAccount && (
|
|
||||||
<Button secondary compact onClick={onRevoke}>
|
|
||||||
<FormattedMessage
|
|
||||||
id='collections.detail.revoke_inclusion'
|
|
||||||
defaultMessage='Remove me'
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const SensitiveScreen: React.FC<{
|
const SensitiveScreen: React.FC<{
|
||||||
sensitive: boolean | undefined;
|
sensitive: boolean | undefined;
|
||||||
focusTargetRef: React.RefObject<HTMLHeadingElement>;
|
focusTargetRef: React.RefObject<HTMLHeadingElement>;
|
||||||
@ -158,6 +60,7 @@ const SensitiveScreen: React.FC<{
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
onPrimary={showAnyway}
|
onPrimary={showAnyway}
|
||||||
|
className={classes.sensitiveScreen}
|
||||||
>
|
>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='collections.detail.sensitive_note'
|
id='collections.detail.sensitive_note'
|
||||||
@ -176,14 +79,37 @@ export const CollectionAccountsList: React.FC<{
|
|||||||
const listHeadingRef = useRef<HTMLHeadingElement>(null);
|
const listHeadingRef = useRef<HTMLHeadingElement>(null);
|
||||||
|
|
||||||
const isOwnCollection = collection?.account_id === me;
|
const isOwnCollection = collection?.account_id === me;
|
||||||
const { items = [] } = collection ?? {};
|
const { items = [], account_id: collectionOwnerId } = collection ?? {};
|
||||||
|
|
||||||
|
const renderAccountItemButton = useCallback(
|
||||||
|
({ relationship, accountId }: RenderButtonOptions) => {
|
||||||
|
// When viewing your own collection, only show the Follow button
|
||||||
|
// for accounts you're not following anymore.
|
||||||
|
const withoutButton =
|
||||||
|
!relationship ||
|
||||||
|
(collectionOwnerId === me &&
|
||||||
|
(relationship.following || relationship.requested));
|
||||||
|
|
||||||
|
if (withoutButton) return null;
|
||||||
|
|
||||||
|
if (accountId === me) {
|
||||||
|
return (
|
||||||
|
<Button secondary compact onClick={confirmRevoke}>
|
||||||
|
<FormattedMessage
|
||||||
|
id='collections.detail.revoke_inclusion'
|
||||||
|
defaultMessage='Remove me'
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <AccountListItemFollowButton accountId={accountId} />;
|
||||||
|
},
|
||||||
|
[collectionOwnerId, confirmRevoke],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ItemList
|
<>
|
||||||
isLoading={isLoading}
|
|
||||||
emptyMessage={intl.formatMessage(messages.empty)}
|
|
||||||
className={classes.itemList}
|
|
||||||
>
|
|
||||||
<h3
|
<h3
|
||||||
className={classes.columnSubheading}
|
className={classes.columnSubheading}
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
@ -207,22 +133,27 @@ export const CollectionAccountsList: React.FC<{
|
|||||||
sensitive={!isOwnCollection && collection.sensitive}
|
sensitive={!isOwnCollection && collection.sensitive}
|
||||||
focusTargetRef={listHeadingRef}
|
focusTargetRef={listHeadingRef}
|
||||||
>
|
>
|
||||||
{items.map(({ account_id }, index) => (
|
<ItemList
|
||||||
<Article
|
isLoading={isLoading}
|
||||||
key={account_id}
|
emptyMessage={intl.formatMessage(messages.empty)}
|
||||||
aria-posinset={index + 1}
|
>
|
||||||
aria-setsize={items.length}
|
{items.map(({ account_id }, index) => (
|
||||||
>
|
<Article
|
||||||
<AccountItem
|
key={account_id}
|
||||||
withBorder={index !== items.length - 1}
|
aria-posinset={index + 1}
|
||||||
accountId={account_id}
|
aria-setsize={items.length}
|
||||||
collectionOwnerId={collection.account_id}
|
>
|
||||||
onRevoke={confirmRevoke}
|
<AccountListItem
|
||||||
/>
|
accountId={account_id}
|
||||||
</Article>
|
withBorder={index !== items.length - 1}
|
||||||
))}
|
stats={['followers', 'last-active']}
|
||||||
|
renderButton={renderAccountItemButton}
|
||||||
|
/>
|
||||||
|
</Article>
|
||||||
|
))}
|
||||||
|
</ItemList>
|
||||||
</SensitiveScreen>
|
</SensitiveScreen>
|
||||||
)}
|
)}
|
||||||
</ItemList>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import { useAccountHandle } from '@/flavours/glitch/components/display_name/defa
|
|||||||
import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
|
import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
|
||||||
import ShareIcon from '@/material-icons/400-24px/share.svg?react';
|
import ShareIcon from '@/material-icons/400-24px/share.svg?react';
|
||||||
import type { ApiCollectionJSON } from 'flavours/glitch/api_types/collections';
|
import type { ApiCollectionJSON } from 'flavours/glitch/api_types/collections';
|
||||||
|
import { Badge } from 'flavours/glitch/components/badge';
|
||||||
import { Callout } from 'flavours/glitch/components/callout';
|
import { Callout } from 'flavours/glitch/components/callout';
|
||||||
import { Column } from 'flavours/glitch/components/column';
|
import { Column } from 'flavours/glitch/components/column';
|
||||||
import { ColumnHeader } from 'flavours/glitch/components/column_header';
|
import { ColumnHeader } from 'flavours/glitch/components/column_header';
|
||||||
@ -138,7 +139,7 @@ const CollectionHeader: React.FC<{ collection: ApiCollectionJSON }> = ({
|
|||||||
<header className={classes.header}>
|
<header className={classes.header}>
|
||||||
<div className={classes.titleWithMenu}>
|
<div className={classes.titleWithMenu}>
|
||||||
<div className={classes.titleWrapper}>
|
<div className={classes.titleWrapper}>
|
||||||
{tag && <span className={classes.tag}>#{tag.name}</span>}
|
{tag && <Badge label={`#${tag.name}`} icon={null} />}
|
||||||
<h2 className={classes.name}>{name}</h2>
|
<h2 className={classes.name}>{name}</h2>
|
||||||
<AuthorNote id={account_id} />
|
<AuthorNote id={account_id} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -2,7 +2,8 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
padding: 24px;
|
padding: 16px;
|
||||||
|
padding-bottom: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.titleWithMenu {
|
.titleWithMenu {
|
||||||
@ -20,15 +21,6 @@
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 4px;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
background: var(--color-bg-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.name {
|
.name {
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
@ -71,45 +63,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.itemList {
|
.sensitiveScreen {
|
||||||
padding-inline: 24px;
|
margin: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.columnSubheading {
|
.columnSubheading {
|
||||||
padding-bottom: 12px;
|
padding-inline: 16px;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.accountItemWrapper {
|
|
||||||
display: flex;
|
|
||||||
align-items: start;
|
|
||||||
padding-block: 16px;
|
|
||||||
|
|
||||||
&[data-with-border='true'] {
|
|
||||||
border-bottom: 1px solid var(--color-border-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.account__note) {
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
-webkit-line-clamp: 3;
|
|
||||||
line-clamp: 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hide 'No description provided' message added by `Account` component
|
|
||||||
:global(.account__note--missing) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.accountItem {
|
|
||||||
--account-name-size: 15px;
|
|
||||||
--account-handle-color: var(--color-text-secondary);
|
|
||||||
--account-handle-size: 13px;
|
|
||||||
--account-bio-color: var(--color-text-primary);
|
|
||||||
--account-bio-size: 13px;
|
|
||||||
--account-outer-spacing: 0;
|
|
||||||
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { useCallback, useMemo, useRef } from 'react';
|
import { useCallback, useMemo, useRef } from 'react';
|
||||||
import type { FC, ReactNode } from 'react';
|
import type { FC, ReactNode } from 'react';
|
||||||
|
|
||||||
import { Account } from '@/flavours/glitch/components/account';
|
import { AccountListItem } from '@/flavours/glitch/components/account_list_item';
|
||||||
import type { ColumnRef } from '@/flavours/glitch/components/column';
|
import type { ColumnRef } from '@/flavours/glitch/components/column';
|
||||||
import { Column } from '@/flavours/glitch/components/column';
|
import { Column } from '@/flavours/glitch/components/column';
|
||||||
import { LoadingIndicator } from '@/flavours/glitch/components/loading_indicator';
|
import { LoadingIndicator } from '@/flavours/glitch/components/loading_indicator';
|
||||||
@ -55,12 +55,12 @@ export const AccountList: FC<AccountListProps> = ({
|
|||||||
}
|
}
|
||||||
const children =
|
const children =
|
||||||
list?.items.map((followerId) => (
|
list?.items.map((followerId) => (
|
||||||
<Account key={followerId} id={followerId} />
|
<AccountListItem key={followerId} accountId={followerId} />
|
||||||
)) ?? [];
|
)) ?? [];
|
||||||
|
|
||||||
if (prependAccountId) {
|
if (prependAccountId) {
|
||||||
children.unshift(
|
children.unshift(
|
||||||
<Account key={prependAccountId} id={prependAccountId} minimal />,
|
<AccountListItem key={prependAccountId} accountId={prependAccountId} />,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return children;
|
return children;
|
||||||
|
|||||||
@ -16,12 +16,12 @@ import {
|
|||||||
} from 'flavours/glitch/actions/suggestions';
|
} from 'flavours/glitch/actions/suggestions';
|
||||||
import type { ApiSuggestionSourceJSON } from 'flavours/glitch/api_types/suggestions';
|
import type { ApiSuggestionSourceJSON } from 'flavours/glitch/api_types/suggestions';
|
||||||
import { Avatar } from 'flavours/glitch/components/avatar';
|
import { Avatar } from 'flavours/glitch/components/avatar';
|
||||||
|
import { Badge, VerifiedBadge } from 'flavours/glitch/components/badge';
|
||||||
import { DisplayName } from 'flavours/glitch/components/display_name';
|
import { DisplayName } from 'flavours/glitch/components/display_name';
|
||||||
import { FollowButton } from 'flavours/glitch/components/follow_button';
|
import { FollowButton } from 'flavours/glitch/components/follow_button';
|
||||||
import { Icon } from 'flavours/glitch/components/icon';
|
import { Icon } from 'flavours/glitch/components/icon';
|
||||||
import { IconButton } from 'flavours/glitch/components/icon_button';
|
import { IconButton } from 'flavours/glitch/components/icon_button';
|
||||||
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
|
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
|
||||||
import { VerifiedBadge } from 'flavours/glitch/components/verified_badge';
|
|
||||||
import { domain } from 'flavours/glitch/initial_state';
|
import { domain } from 'flavours/glitch/initial_state';
|
||||||
import { useAppDispatch, useAppSelector } from 'flavours/glitch/store';
|
import { useAppDispatch, useAppSelector } from 'flavours/glitch/store';
|
||||||
|
|
||||||
@ -110,13 +110,12 @@ const Source: React.FC<{ id: ApiSuggestionSourceJSON }> = ({ id }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<Badge
|
||||||
className='inline-follow-suggestions__body__scrollable__card__text-stack__source'
|
className='inline-follow-suggestions__body__scrollable__card__text-stack__source'
|
||||||
title={hint}
|
title={hint}
|
||||||
>
|
label={label}
|
||||||
<Icon id='' icon={InfoIcon} />
|
icon={<InfoIcon />}
|
||||||
<span>{label}</span>
|
/>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import {
|
|||||||
apiRemoveAccountFromList,
|
apiRemoveAccountFromList,
|
||||||
} from 'flavours/glitch/api/lists';
|
} from 'flavours/glitch/api/lists';
|
||||||
import { Avatar } from 'flavours/glitch/components/avatar';
|
import { Avatar } from 'flavours/glitch/components/avatar';
|
||||||
|
import { VerifiedBadge } from 'flavours/glitch/components/badge';
|
||||||
import { Button } from 'flavours/glitch/components/button';
|
import { Button } from 'flavours/glitch/components/button';
|
||||||
import { Column } from 'flavours/glitch/components/column';
|
import { Column } from 'flavours/glitch/components/column';
|
||||||
import { ColumnHeader } from 'flavours/glitch/components/column_header';
|
import { ColumnHeader } from 'flavours/glitch/components/column_header';
|
||||||
@ -27,7 +28,6 @@ import { FollowersCounter } from 'flavours/glitch/components/counters';
|
|||||||
import { DisplayName } from 'flavours/glitch/components/display_name';
|
import { DisplayName } from 'flavours/glitch/components/display_name';
|
||||||
import ScrollableList from 'flavours/glitch/components/scrollable_list';
|
import ScrollableList from 'flavours/glitch/components/scrollable_list';
|
||||||
import { ShortNumber } from 'flavours/glitch/components/short_number';
|
import { ShortNumber } from 'flavours/glitch/components/short_number';
|
||||||
import { VerifiedBadge } from 'flavours/glitch/components/verified_badge';
|
|
||||||
import { useSearchAccounts } from 'flavours/glitch/hooks/useSearchAccounts';
|
import { useSearchAccounts } from 'flavours/glitch/hooks/useSearchAccounts';
|
||||||
import { me } from 'flavours/glitch/initial_state';
|
import { me } from 'flavours/glitch/initial_state';
|
||||||
import { useAppDispatch, useAppSelector } from 'flavours/glitch/store';
|
import { useAppDispatch, useAppSelector } from 'flavours/glitch/store';
|
||||||
|
|||||||
@ -9022,20 +9022,6 @@ noscript {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__familiar-followers {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
margin-block: 16px;
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
|
|
||||||
a:any-link {
|
|
||||||
font-weight: 500;
|
|
||||||
text-decoration: none;
|
|
||||||
color: var(--color-text-primary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.account__contents {
|
.account__contents {
|
||||||
@ -9045,6 +9031,7 @@ noscript {
|
|||||||
.account__details {
|
.account__details {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
column-gap: 1em;
|
column-gap: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -11020,11 +11007,6 @@ noscript {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__source {
|
&__source {
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
max-width: 100%;
|
|
||||||
color: var(--color-text-tertiary);
|
|
||||||
gap: 4px;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
cursor: help;
|
cursor: help;
|
||||||
@ -11033,11 +11015,6 @@ noscript {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -11063,11 +11040,6 @@ noscript {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.verified-badge {
|
|
||||||
font-size: 14px;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user