[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:
diondiondion 2026-04-08 16:26:39 +02:00 committed by Claire
parent 2a5a64d057
commit 2ee6e447b4
22 changed files with 454 additions and 271 deletions

View File

@ -20,6 +20,7 @@ import { openModal } from 'flavours/glitch/actions/modal';
import { initMuteModal } from 'flavours/glitch/actions/mutes';
import { apiFollowAccount } from 'flavours/glitch/api/accounts';
import { Avatar } from 'flavours/glitch/components/avatar';
import { VerifiedBadge } from 'flavours/glitch/components/badge';
import { Button } from 'flavours/glitch/components/button';
import { FollowersCounter } from 'flavours/glitch/components/counters';
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 { ShortNumber } from 'flavours/glitch/components/short_number';
import { Skeleton } from 'flavours/glitch/components/skeleton';
import { VerifiedBadge } from 'flavours/glitch/components/verified_badge';
import { useIdentity } from 'flavours/glitch/identity_context';
import { me } from 'flavours/glitch/initial_state';
import type { MenuItem } from 'flavours/glitch/models/dropdown_menu';

View File

@ -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,
},
};

View File

@ -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} />
);

View File

@ -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;
}
}
}

View File

@ -35,6 +35,12 @@ export const Domain: Story = {
},
};
export const Verified: Story = {
render() {
return <badges.VerifiedBadge link='example.com' />;
},
};
export const CustomIcon: Story = {
args: {
...Default.args,

View File

@ -4,19 +4,23 @@ import { FormattedMessage, useIntl } from 'react-intl';
import classNames from 'classnames';
import type { OnAttributeHandler } from '@/flavours/glitch/utils/html';
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 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';
import { EmojiHTML } from '../emoji/html';
import { Icon } from '../icon';
import classes from './styles.module.scss';
interface BadgeProps {
interface BadgeProps extends React.ComponentPropsWithoutRef<'div'> {
label: ReactNode;
icon?: ReactNode;
className?: string;
domain?: ReactNode;
roleId?: string;
variant?:
@ -35,9 +39,16 @@ export const Badge: FC<BadgeProps> = ({
className,
domain,
roleId,
...otherProps
}) => (
<div
className={classNames(classes.badge, classes[variant], className)}
{...otherProps}
className={classNames(
classes.badge,
!icon && classes.badgeWithoutIcon,
classes[variant],
className,
)}
data-account-role-id={roleId}
>
{icon}
@ -134,3 +145,31 @@ export const BlockedBadge: FC<Partial<BadgeProps>> = ({ label, ...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}
/>
);

View File

@ -2,18 +2,28 @@
color: var(--color-text-primary);
font-size: 13px;
display: inline-flex;
max-width: 100%;
padding: 4px;
padding-inline-end: 8px;
gap: 4px;
border-radius: 6px;
border-radius: 8px;
align-items: center;
overflow-wrap: anywhere;
> svg {
width: auto;
height: 15px;
height: 17px;
fill: currentColor;
opacity: 0.85;
}
a {
color: inherit;
text-decoration: none;
}
&:not(.badgeWithoutIcon) {
padding-inline-end: 8px;
}
}
.domain {
@ -41,7 +51,6 @@
.success {
background-color: var(--color-bg-success-softest);
color: var(--color-text-success);
}
.warning {

View File

@ -1,11 +1,14 @@
import { FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import { Avatar } from '@/flavours/glitch/components/avatar';
import { AvatarGroup } from '@/flavours/glitch/components/avatar_group';
import { LinkedDisplayName } from '@/flavours/glitch/components/display_name';
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[] }> = ({
familiarFollowers,
@ -51,9 +54,10 @@ const FamiliarFollowersReadout: React.FC<{ familiarFollowers: Account[] }> = ({
}
};
export const FamiliarFollowers: React.FC<{ accountId: string }> = ({
accountId,
}) => {
export const FamiliarFollowers: React.FC<{
accountId: string;
className?: string;
}> = ({ accountId, className }) => {
const { familiarFollowers, isLoading } = useFetchFamiliarFollowers({
accountId,
});
@ -63,10 +67,10 @@ export const FamiliarFollowers: React.FC<{ accountId: string }> = ({
}
return (
<div className='account__header__familiar-followers'>
<AvatarGroup compact>
<div className={classNames(classes.wrapper, className)}>
<AvatarGroup compact avatarHeight={24}>
{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>
<span>

View File

@ -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);
}
}

View File

@ -4,6 +4,7 @@ import { FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import { useFetchFamiliarFollowers } from '@/flavours/glitch/components/familiar_followers/use_fetch_familiar_followers';
import { fetchAccount } from 'flavours/glitch/actions/accounts';
import { AccountBio } from 'flavours/glitch/components/account_bio';
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 { Permalink } from 'flavours/glitch/components/permalink';
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 { getAccountHidden } from 'flavours/glitch/selectors/accounts';
import { useAppSelector, useAppDispatch } from 'flavours/glitch/store';

View File

@ -1,22 +1,21 @@
import classNames from 'classnames';
import { NavLink } from 'react-router-dom';
import type { MastodonLocationDescriptor } from 'flavours/glitch/components/router';
import classes from './styles.module.scss';
interface WrapperProps {
children: React.ReactNode;
}
export const NumberFields: React.FC<WrapperProps> = ({ children }) => {
return <ul className={classes.list}>{children}</ul>;
export const NumberFields: React.FC<React.ComponentPropsWithoutRef<'ul'>> = ({
children,
className,
}) => {
return <ul className={classNames(classes.list, className)}>{children}</ul>;
};
interface ItemProps {
interface ItemProps extends React.ComponentPropsWithoutRef<'li'> {
label: React.ReactNode;
hint?: string;
link?: MastodonLocationDescriptor;
children: React.ReactNode;
}
export const NumberFieldsItem: React.FC<ItemProps> = ({
@ -24,9 +23,10 @@ export const NumberFieldsItem: React.FC<ItemProps> = ({
hint,
link,
children,
className,
}) => {
return (
<li className={classes.item} title={hint}>
<li className={classNames(classes.item, className)} title={hint}>
{label}
{link ? (
<NavLink exact to={link}>

View File

@ -1,9 +1,9 @@
.list {
display: flex;
flex-wrap: wrap;
margin: 8px 0;
margin: 0;
padding: 0;
gap: 4px 20px;
gap: 4px 24px;
font-size: 13px;
color: var(--color-text-secondary);
}
@ -19,6 +19,7 @@
font-weight: 600;
color: var(--color-text-primary);
font-size: 15px;
white-space: nowrap;
}
a {

View File

@ -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>
);

View File

@ -19,10 +19,11 @@ import type { Account } from '@/flavours/glitch/models/account';
import { getAccountHidden } from '@/flavours/glitch/selectors/accounts';
import { useAppSelector, useAppDispatch } from '@/flavours/glitch/store';
import { FamiliarFollowers } from '../../../components/familiar_followers';
import { AccountName } from './account_name';
import { AccountSubscriptionForm } from './account_subscription_form';
import { AccountButtons } from './buttons';
import { FamiliarFollowers } from './familiar_followers';
import { AccountHeaderFields } from './fields';
import { AccountInfo } from './info';
import { MemorialNote } from './memorial_note';
@ -165,7 +166,10 @@ export const AccountHeader: React.FC<{
<AccountNumberFields accountId={accountId} />
{!isMe && !suspendedOrHidden && (
<FamiliarFollowers accountId={accountId} />
<FamiliarFollowers
accountId={accountId}
className={classes.familiarFollowers}
/>
)}
{!suspendedOrHidden && (

View File

@ -3,22 +3,17 @@ import { useCallback, useRef, useState } from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
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 { 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 {
Article,
ItemList,
} 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 { 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<{
sensitive: boolean | undefined;
focusTargetRef: React.RefObject<HTMLHeadingElement>;
@ -158,6 +60,7 @@ const SensitiveScreen: React.FC<{
/>
}
onPrimary={showAnyway}
className={classes.sensitiveScreen}
>
<FormattedMessage
id='collections.detail.sensitive_note'
@ -176,14 +79,37 @@ export const CollectionAccountsList: React.FC<{
const listHeadingRef = useRef<HTMLHeadingElement>(null);
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 (
<ItemList
isLoading={isLoading}
emptyMessage={intl.formatMessage(messages.empty)}
className={classes.itemList}
>
<>
<h3
className={classes.columnSubheading}
tabIndex={-1}
@ -207,22 +133,27 @@ export const CollectionAccountsList: React.FC<{
sensitive={!isOwnCollection && collection.sensitive}
focusTargetRef={listHeadingRef}
>
{items.map(({ account_id }, index) => (
<Article
key={account_id}
aria-posinset={index + 1}
aria-setsize={items.length}
>
<AccountItem
withBorder={index !== items.length - 1}
accountId={account_id}
collectionOwnerId={collection.account_id}
onRevoke={confirmRevoke}
/>
</Article>
))}
<ItemList
isLoading={isLoading}
emptyMessage={intl.formatMessage(messages.empty)}
>
{items.map(({ account_id }, index) => (
<Article
key={account_id}
aria-posinset={index + 1}
aria-setsize={items.length}
>
<AccountListItem
accountId={account_id}
withBorder={index !== items.length - 1}
stats={['followers', 'last-active']}
renderButton={renderAccountItemButton}
/>
</Article>
))}
</ItemList>
</SensitiveScreen>
)}
</ItemList>
</>
);
};

View File

@ -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 ShareIcon from '@/material-icons/400-24px/share.svg?react';
import type { ApiCollectionJSON } from 'flavours/glitch/api_types/collections';
import { Badge } from 'flavours/glitch/components/badge';
import { Callout } from 'flavours/glitch/components/callout';
import { Column } from 'flavours/glitch/components/column';
import { ColumnHeader } from 'flavours/glitch/components/column_header';
@ -138,7 +139,7 @@ const CollectionHeader: React.FC<{ collection: ApiCollectionJSON }> = ({
<header className={classes.header}>
<div className={classes.titleWithMenu}>
<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>
<AuthorNote id={account_id} />
</div>

View File

@ -2,7 +2,8 @@
display: flex;
flex-direction: column;
gap: 12px;
padding: 24px;
padding: 16px;
padding-bottom: 24px;
}
.titleWithMenu {
@ -20,15 +21,6 @@
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 {
font-size: 22px;
font-weight: 500;
@ -71,45 +63,12 @@
}
}
.itemList {
padding-inline: 24px;
.sensitiveScreen {
margin: 16px;
}
.columnSubheading {
padding-bottom: 12px;
padding-inline: 16px;
font-size: 15px;
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;
}

View File

@ -1,7 +1,7 @@
import { useCallback, useMemo, useRef } 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 { Column } from '@/flavours/glitch/components/column';
import { LoadingIndicator } from '@/flavours/glitch/components/loading_indicator';
@ -55,12 +55,12 @@ export const AccountList: FC<AccountListProps> = ({
}
const children =
list?.items.map((followerId) => (
<Account key={followerId} id={followerId} />
<AccountListItem key={followerId} accountId={followerId} />
)) ?? [];
if (prependAccountId) {
children.unshift(
<Account key={prependAccountId} id={prependAccountId} minimal />,
<AccountListItem key={prependAccountId} accountId={prependAccountId} />,
);
}
return children;

View File

@ -16,12 +16,12 @@ import {
} from 'flavours/glitch/actions/suggestions';
import type { ApiSuggestionSourceJSON } from 'flavours/glitch/api_types/suggestions';
import { Avatar } from 'flavours/glitch/components/avatar';
import { Badge, VerifiedBadge } from 'flavours/glitch/components/badge';
import { DisplayName } from 'flavours/glitch/components/display_name';
import { FollowButton } from 'flavours/glitch/components/follow_button';
import { Icon } from 'flavours/glitch/components/icon';
import { IconButton } from 'flavours/glitch/components/icon_button';
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
import { VerifiedBadge } from 'flavours/glitch/components/verified_badge';
import { domain } from 'flavours/glitch/initial_state';
import { useAppDispatch, useAppSelector } from 'flavours/glitch/store';
@ -110,13 +110,12 @@ const Source: React.FC<{ id: ApiSuggestionSourceJSON }> = ({ id }) => {
}
return (
<div
<Badge
className='inline-follow-suggestions__body__scrollable__card__text-stack__source'
title={hint}
>
<Icon id='' icon={InfoIcon} />
<span>{label}</span>
</div>
label={label}
icon={<InfoIcon />}
/>
);
};

View File

@ -19,6 +19,7 @@ import {
apiRemoveAccountFromList,
} from 'flavours/glitch/api/lists';
import { Avatar } from 'flavours/glitch/components/avatar';
import { VerifiedBadge } from 'flavours/glitch/components/badge';
import { Button } from 'flavours/glitch/components/button';
import { Column } from 'flavours/glitch/components/column';
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 ScrollableList from 'flavours/glitch/components/scrollable_list';
import { ShortNumber } from 'flavours/glitch/components/short_number';
import { VerifiedBadge } from 'flavours/glitch/components/verified_badge';
import { useSearchAccounts } from 'flavours/glitch/hooks/useSearchAccounts';
import { me } from 'flavours/glitch/initial_state';
import { useAppDispatch, useAppSelector } from 'flavours/glitch/store';

View File

@ -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 {
@ -9045,6 +9031,7 @@ noscript {
.account__details {
display: flex;
flex-wrap: wrap;
align-items: center;
column-gap: 1em;
}
@ -11020,11 +11007,6 @@ noscript {
}
&__source {
display: inline-flex;
align-items: center;
max-width: 100%;
color: var(--color-text-tertiary);
gap: 4px;
overflow: hidden;
white-space: nowrap;
cursor: help;
@ -11033,11 +11015,6 @@ noscript {
overflow: hidden;
text-overflow: ellipsis;
}
.icon {
width: 16px;
height: 16px;
}
}
}
@ -11063,11 +11040,6 @@ noscript {
}
}
.verified-badge {
font-size: 14px;
max-width: 100%;
}
.button {
display: block;
width: 100%;