[Glitch] Update Profile Featured tab to latest designs

Port 05bed6f3d8fc57edfa699e3aac614d2989ef1f73 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
This commit is contained in:
diondiondion 2026-04-09 11:15:05 +02:00 committed by Claire
parent ef1a207558
commit f84df36bd1
16 changed files with 162 additions and 118 deletions

View File

@ -3,16 +3,20 @@
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
max-width: 600px; max-width: 600px;
padding: 20px; padding: 24px;
gap: 16px; gap: 16px;
text-align: center; text-align: center;
color: var(--color-text-primary); color: var(--color-text-primary);
} }
.content { .content {
max-width: 300px;
svg, svg,
img { img {
width: 240px; width: 200px;
aspect-ratio: 1;
object-fit: contain;
max-width: 100%; max-width: 100%;
margin-bottom: 16px; margin-bottom: 16px;
} }
@ -20,11 +24,14 @@
h3 { h3 {
font-size: 17px; font-size: 17px;
font-weight: 500; font-weight: 500;
text-wrap: balance;
line-height: 1.2;
} }
p { p {
font-size: 15px;
margin-top: 8px; margin-top: 8px;
font-size: 15px;
color: var(--color-text-secondary); color: var(--color-text-secondary);
text-wrap: pretty;
} }
} }

View File

@ -49,8 +49,14 @@ export const EmptyMessage: React.FC<EmptyMessageProps> = ({
image={image} image={image}
title={ title={
<FormattedMessage <FormattedMessage
id='empty_column.account_featured_self.no_collections' id='empty_column.account_featured_self.showcase_accounts'
defaultMessage='No collections yet' defaultMessage='Showcase your favorite accounts'
/>
}
message={
<FormattedMessage
id='empty_column.account_featured_self.showcase_accounts_desc'
defaultMessage='Collections are curated lists of accounts to help others discover more of the Fediverse.'
/> />
} }
> >
@ -72,7 +78,7 @@ export const EmptyMessage: React.FC<EmptyMessageProps> = ({
message = ( message = (
<FormattedMessage <FormattedMessage
id='empty_column.account_featured_self.pre_collections_desc' id='empty_column.account_featured_self.pre_collections_desc'
defaultMessage='Collections (coming in Mastodon 4.6) allows you to create your own curated lists of accounts to recommend to others.' defaultMessage='Collections (coming in Mastodon 4.6) allow you to create your own curated lists of accounts to recommend to others.'
/> />
); );
} }
@ -93,47 +99,21 @@ export const EmptyMessage: React.FC<EmptyMessageProps> = ({
/> />
); );
} else { } else {
// Standard other account empty state. if (acct) {
title = ( title = (
<FormattedMessage <FormattedMessage
id='empty_column.account_featured_other.title' id='empty_column.account_featured.other'
defaultMessage='Nothing to see here' defaultMessage='{acct} has not featured anything yet.'
/> values={{ acct }}
); />
if (hasCollections) { );
if (acct) {
message = (
<FormattedMessage
id='empty_column.account_featured_other.no_collections_desc'
defaultMessage='{acct} hasnt created any collections yet.'
values={{ acct }}
/>
);
} else {
message = (
<FormattedMessage
id='empty_column.account_featured_unknown.no_collections_desc'
defaultMessage='This account hasnt created any collections yet.'
/>
);
}
} else { } else {
if (acct) { title = (
message = ( <FormattedMessage
<FormattedMessage id='empty_column.account_featured_unknown.other'
id='empty_column.account_featured.other' defaultMessage='This account has not featured anything yet.'
defaultMessage='{acct} hasnt featured anything yet.' />
values={{ acct }} );
/>
);
} else {
message = (
<FormattedMessage
id='empty_column.account_featured_unknown.other'
defaultMessage='This account hasnt featured anything yet.'
/>
);
}
} }
} }

View File

@ -0,0 +1,37 @@
.subheading {
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;
min-height: 52px;
padding: 12px 16px;
font-size: 13px;
font-weight: 500;
text-transform: uppercase;
color: var(--color-text-primary);
background: var(--color-bg-secondary);
}
.link {
display: flex;
padding-block: 4px;
font-size: 13px;
font-weight: 500;
text-transform: none;
text-decoration: none;
color: inherit;
&:hover {
text-decoration: underline;
}
&:focus-visible {
outline: var(--outline-focus-default);
outline-offset: 2px;
}
:global(.icon) {
width: 18px;
height: 18px;
}
}

View File

@ -0,0 +1,42 @@
import type { ComponentPropsWithoutRef } from 'react';
import classNames from 'classnames';
import { Link } from 'react-router-dom';
import type { IconProp } from '@/flavours/glitch/components/icon';
import { Icon } from '@/flavours/glitch/components/icon';
import { polymorphicForwardRef } from '@/types/polymorphic';
import classes from './subheading.module.scss';
export const Subheading = polymorphicForwardRef<'h2'>(
({ as: Component = 'h2', children, className, ...props }, ref) => {
return (
<Component
ref={ref}
className={classNames(classes.subheading, className)}
{...props}
>
{children}
</Component>
);
},
);
interface SubheadingLinkProps extends ComponentPropsWithoutRef<typeof Link> {
icon: IconProp;
}
export const SubheadingLink: React.FC<SubheadingLinkProps> = ({
icon,
children,
className,
...props
}) => {
return (
<Link className={classNames(classes.link, className)} {...props}>
<Icon id='subheading-icon' icon={icon} />
{children}
</Link>
);
};

View File

@ -6,9 +6,10 @@ import { useHistory } from 'react-router';
import { List as ImmutableList } from 'immutable'; import { List as ImmutableList } from 'immutable';
import { AccountListItem } from '@/flavours/glitch/components/account_list_item';
import { useAccount } from '@/flavours/glitch/hooks/useAccount'; import { useAccount } from '@/flavours/glitch/hooks/useAccount';
import AddIcon from '@/material-icons/400-24px/add.svg?react';
import { fetchEndorsedAccounts } from 'flavours/glitch/actions/accounts'; import { fetchEndorsedAccounts } from 'flavours/glitch/actions/accounts';
import { Account } from 'flavours/glitch/components/account';
import { ColumnBackButton } from 'flavours/glitch/components/column_back_button'; import { ColumnBackButton } from 'flavours/glitch/components/column_back_button';
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator'; import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
import { RemoteHint } from 'flavours/glitch/components/remote_hint'; import { RemoteHint } from 'flavours/glitch/components/remote_hint';
@ -28,10 +29,11 @@ import {
} from 'flavours/glitch/reducers/slices/collections'; } from 'flavours/glitch/reducers/slices/collections';
import { useAppDispatch, useAppSelector } from 'flavours/glitch/store'; import { useAppDispatch, useAppSelector } from 'flavours/glitch/store';
import { CollectionListItem } from '../collections/detail/collection_list_item'; import { CollectionListItem } from '../collections/components/collection_list_item';
import { areCollectionsEnabled } from '../collections/utils'; import { areCollectionsEnabled } from '../collections/utils';
import { EmptyMessage } from './components/empty_message'; import { EmptyMessage } from './components/empty_message';
import { Subheading, SubheadingLink } from './components/subheading';
const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
multiColumn, multiColumn,
@ -114,14 +116,44 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
{accountId && ( {accountId && (
<AccountHeader accountId={accountId} hideTabs={forceEmptyState} /> <AccountHeader accountId={accountId} hideTabs={forceEmptyState} />
)} )}
{!featuredAccountIds.isEmpty() && (
<>
<Subheading as='h2'>
<FormattedMessage
id='account.featured.accounts'
defaultMessage='Profiles'
/>
</Subheading>
<ItemList>
{featuredAccountIds.map((featuredAccountId, index) => (
<Article
focusable
key={featuredAccountId}
aria-posinset={index + 1}
aria-setsize={featuredAccountIds.size}
>
<AccountListItem accountId={featuredAccountId} />
</Article>
))}
</ItemList>
</>
)}
{listedCollections.length > 0 && status === 'idle' && ( {listedCollections.length > 0 && status === 'idle' && (
<> <>
<h4 className='column-subheading'> <Subheading as='header'>
<FormattedMessage <h2>
id='account.featured.collections' <FormattedMessage
defaultMessage='Collections' id='account.featured.collections'
/> defaultMessage='Collections'
</h4> />
</h2>
<SubheadingLink to='/collections/new' icon={AddIcon}>
<FormattedMessage
id='account.featured.new_collection'
defaultMessage='New collection'
/>
</SubheadingLink>
</Subheading>
<ItemList> <ItemList>
{listedCollections.map((item, index) => ( {listedCollections.map((item, index) => (
<CollectionListItem <CollectionListItem
@ -136,28 +168,6 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
</ItemList> </ItemList>
</> </>
)} )}
{!featuredAccountIds.isEmpty() && (
<>
<h4 className='column-subheading'>
<FormattedMessage
id='account.featured.accounts'
defaultMessage='Profiles'
/>
</h4>
<ItemList>
{featuredAccountIds.map((featuredAccountId, index) => (
<Article
focusable
key={featuredAccountId}
aria-posinset={index + 1}
aria-setsize={featuredAccountIds.size}
>
<Account id={featuredAccountId} />
</Article>
))}
</ItemList>
</>
)}
<RemoteHint accountId={accountId} /> <RemoteHint accountId={accountId} />
</Scrollable> </Scrollable>
</Column> </Column>

View File

@ -8,8 +8,6 @@ import { NavLink } from 'react-router-dom';
import { useAccount } from '@/flavours/glitch/hooks/useAccount'; import { useAccount } from '@/flavours/glitch/hooks/useAccount';
import { useAccountId } from '@/flavours/glitch/hooks/useAccountId'; import { useAccountId } from '@/flavours/glitch/hooks/useAccountId';
import { areCollectionsEnabled } from '../../collections/utils';
import classes from './styles.module.scss'; import classes from './styles.module.scss';
const isActive: Required<NavLinkProps>['isActive'] = (match, location) => const isActive: Required<NavLinkProps>['isActive'] = (match, location) =>
@ -41,14 +39,7 @@ export const AccountTabs: FC = () => {
)} )}
{show_featured && ( {show_featured && (
<NavLink exact to={`/@${acct}/featured`}> <NavLink exact to={`/@${acct}/featured`}>
{areCollectionsEnabled() ? ( <FormattedMessage id='account.featured' defaultMessage='Featured' />
<FormattedMessage
id='account.featured.collections'
defaultMessage='Collections'
/>
) : (
<FormattedMessage id='account.featured' defaultMessage='Featured' />
)}
</NavLink> </NavLink>
)} )}
</div> </div>

View File

@ -1,8 +1,7 @@
.wrapper { .wrapper {
display: flex; display: flex;
align-items: start; align-items: start;
margin-inline: 24px; padding: 12px 16px;
padding-block: 12px;
gap: 16px; gap: 16px;
&:not(.wrapperWithoutBorder) { &:not(.wrapperWithoutBorder) {

View File

@ -5,6 +5,7 @@ import { FormattedMessage } from 'react-intl';
import classNames from 'classnames'; import classNames from 'classnames';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { CollectionMenu } from '@/flavours/glitch/features/collections/components/collection_menu';
import WarningIcon from '@/material-icons/400-24px/warning.svg?react'; import WarningIcon from '@/material-icons/400-24px/warning.svg?react';
import type { ApiCollectionJSON } from 'flavours/glitch/api_types/collections'; import type { ApiCollectionJSON } from 'flavours/glitch/api_types/collections';
import { AvatarById } from 'flavours/glitch/components/avatar'; import { AvatarById } from 'flavours/glitch/components/avatar';
@ -15,7 +16,6 @@ import { useAccount } from 'flavours/glitch/hooks/useAccount';
import { domain } from 'flavours/glitch/initial_state'; import { domain } from 'flavours/glitch/initial_state';
import classes from './collection_list_item.module.scss'; import classes from './collection_list_item.module.scss';
import { CollectionMenu } from './collection_menu';
export const AvatarGrid: React.FC<{ export const AvatarGrid: React.FC<{
accountIds: (string | undefined)[]; accountIds: (string | undefined)[];

View File

@ -20,7 +20,8 @@ import {
} from 'flavours/glitch/components/modal_shell'; } from 'flavours/glitch/components/modal_shell';
import { useAppDispatch } from 'flavours/glitch/store'; import { useAppDispatch } from 'flavours/glitch/store';
import { AuthorNote } from '.'; import { AuthorNote } from '../detail';
import classes from './share_modal.module.scss'; import classes from './share_modal.module.scss';
const messages = defineMessages({ const messages = defineMessages({

View File

@ -23,8 +23,9 @@ import { domain, me } from 'flavours/glitch/initial_state';
import { fetchCollection } from 'flavours/glitch/reducers/slices/collections'; import { fetchCollection } from 'flavours/glitch/reducers/slices/collections';
import { useAppDispatch, useAppSelector } from 'flavours/glitch/store'; import { useAppDispatch, useAppSelector } from 'flavours/glitch/store';
import { CollectionMenu } from '../components/collection_menu';
import { CollectionAccountsList } from './accounts_list'; import { CollectionAccountsList } from './accounts_list';
import { CollectionMenu } from './collection_menu';
import { useConfirmRevoke } from './revoke_collection_inclusion_modal'; import { useConfirmRevoke } from './revoke_collection_inclusion_modal';
import classes from './styles.module.scss'; import classes from './styles.module.scss';
@ -66,7 +67,7 @@ export const AuthorNote: React.FC<{ id: string }> = ({ id }) => {
); );
}; };
export const RevokeControls: React.FC<{ const RevokeControls: React.FC<{
collection: ApiCollectionJSON; collection: ApiCollectionJSON;
}> = ({ collection }) => { }> = ({ collection }) => {
const authorAccount = useAccount(collection.account_id); const authorAccount = useAccount(collection.account_id);

View File

@ -21,7 +21,7 @@ import {
} from 'flavours/glitch/reducers/slices/collections'; } from 'flavours/glitch/reducers/slices/collections';
import { useAppSelector, useAppDispatch } from 'flavours/glitch/store'; import { useAppSelector, useAppDispatch } from 'flavours/glitch/store';
import { CollectionListItem } from './detail/collection_list_item'; import { CollectionListItem } from './components/collection_list_item';
import { messages as editorMessages } from './editor'; import { messages as editorMessages } from './editor';
const messages = defineMessages({ const messages = defineMessages({

View File

@ -1,15 +0,0 @@
import PropTypes from 'prop-types';
const ColumnSubheading = ({ text }) => {
return (
<div className='column-subheading'>
{text}
</div>
);
};
ColumnSubheading.propTypes = {
text: PropTypes.string.isRequired,
};
export default ColumnSubheading;

View File

@ -85,7 +85,7 @@ export const MODAL_COMPONENTS = {
'DOMAIN_BLOCK': DomainBlockModal, 'DOMAIN_BLOCK': DomainBlockModal,
'REPORT': ReportModal, 'REPORT': ReportModal,
'REPORT_COLLECTION': ReportCollectionModal, 'REPORT_COLLECTION': ReportCollectionModal,
'SHARE_COLLECTION': () => import('@/flavours/glitch/features/collections/detail/share_modal').then(module => ({ default: module.CollectionShareModal })), 'SHARE_COLLECTION': () => import('@/flavours/glitch/features/collections/components/share_modal').then(module => ({ default: module.CollectionShareModal })),
'REVOKE_COLLECTION_INCLUSION': () => import('@/flavours/glitch/features/collections/detail/revoke_collection_inclusion_modal').then(module => ({ default: module.RevokeCollectionInclusionModal })), 'REVOKE_COLLECTION_INCLUSION': () => import('@/flavours/glitch/features/collections/detail/revoke_collection_inclusion_modal').then(module => ({ default: module.RevokeCollectionInclusionModal })),
'SETTINGS': SettingsModal, 'SETTINGS': SettingsModal,
'DEPRECATED_SETTINGS': () => Promise.resolve({ default: DeprecatedSettingsModal }), 'DEPRECATED_SETTINGS': () => Promise.resolve({ default: DeprecatedSettingsModal }),

View File

@ -4170,15 +4170,6 @@ a.account__display-name {
margin: -6px 10px; margin: -6px 10px;
} }
.column-subheading {
background: var(--color-bg-secondary);
color: var(--color-text-secondary);
padding: 12px 24px;
font-size: 13px;
font-weight: 500;
text-transform: uppercase;
}
.getting-started__wrapper { .getting-started__wrapper {
flex: 0 0 auto; flex: 0 0 auto;
} }