From ffac9e53c6650efc0ae349b76edc5e54b05ede26 Mon Sep 17 00:00:00 2001 From: diondiondion Date: Thu, 26 Mar 2026 15:56:36 +0100 Subject: [PATCH] [Glitch] Update collection list item design Port 2124be8a819a0d645dd8438ec50fd698f749936f to glitch-soc Signed-off-by: Claire --- .../flavours/glitch/components/avatar.tsx | 2 +- .../components/display_name/default.tsx | 15 +- .../features/account_featured/index.tsx | 1 + .../detail/collection_list_item.module.scss | 54 +++++-- .../detail/collection_list_item.tsx | 149 +++++++++++------- .../features/collections/detail/index.tsx | 51 +++++- .../collections/detail/styles.module.scss | 12 +- .../glitch/features/collections/index.tsx | 2 + .../glitch/styles/mastodon/components.scss | 5 +- 9 files changed, 214 insertions(+), 77 deletions(-) diff --git a/app/javascript/flavours/glitch/components/avatar.tsx b/app/javascript/flavours/glitch/components/avatar.tsx index 0f4e597634..f16ac8a4ac 100644 --- a/app/javascript/flavours/glitch/components/avatar.tsx +++ b/app/javascript/flavours/glitch/components/avatar.tsx @@ -96,7 +96,7 @@ export const Avatar: React.FC = ({ }; export const AvatarById: React.FC< - { accountId: string } & Omit + { accountId: string | undefined } & Omit > = ({ accountId, ...otherProps }) => { const account = useAccount(accountId); return ; diff --git a/app/javascript/flavours/glitch/components/display_name/default.tsx b/app/javascript/flavours/glitch/components/display_name/default.tsx index 57ae24ab26..ec42c9ada9 100644 --- a/app/javascript/flavours/glitch/components/display_name/default.tsx +++ b/app/javascript/flavours/glitch/components/display_name/default.tsx @@ -6,10 +6,11 @@ import { Skeleton } from '../skeleton'; import type { DisplayNameProps } from './index'; import { DisplayNameWithoutDomain } from './no-domain'; -export const DisplayNameDefault: FC< - Omit & ComponentPropsWithoutRef<'span'> -> = ({ account, localDomain, className, ...props }) => { - const username = useMemo(() => { +export function useAccountHandle( + account: DisplayNameProps['account'], + localDomain: DisplayNameProps['localDomain'], +) { + return useMemo(() => { if (!account) { return null; } @@ -20,6 +21,12 @@ export const DisplayNameDefault: FC< } return `@${acct}`; }, [account, localDomain]); +} + +export const DisplayNameDefault: FC< + Omit & ComponentPropsWithoutRef<'span'> +> = ({ account, localDomain, className, ...props }) => { + const username = useAccountHandle(account, localDomain); return ( = ({ key={item.id} collection={item} withoutBorder={index === listedCollections.length - 1} + withAuthorHandle={false} positionInList={index + 1} listSize={listedCollections.length} /> diff --git a/app/javascript/flavours/glitch/features/collections/detail/collection_list_item.module.scss b/app/javascript/flavours/glitch/features/collections/detail/collection_list_item.module.scss index 3c71e90f48..7cdf9b8541 100644 --- a/app/javascript/flavours/glitch/features/collections/detail/collection_list_item.module.scss +++ b/app/javascript/flavours/glitch/features/collections/detail/collection_list_item.module.scss @@ -1,8 +1,9 @@ .wrapper { display: flex; - align-items: center; + align-items: start; + margin-inline: 24px; + padding-block: 12px; gap: 16px; - padding-inline: 16px; &:not(.wrapperWithoutBorder) { border-bottom: 1px solid var(--color-border-primary); @@ -12,12 +13,42 @@ .content { position: relative; flex-grow: 1; - padding-block: 15px; + display: flex; + align-items: center; + column-gap: 12px; +} + +.avatarGrid { + position: relative; + display: grid; + grid-template-columns: repeat(2, min-content); + gap: 2px; + + &.avatarGridSensitive { + .avatar { + filter: blur(4px); + } + } +} + +.avatar { + background: var(--color-bg-brand-softest); +} + +.avatarSensitiveBadge { + position: absolute; + inset: 0; + margin: auto; + padding: 3px; + width: 18px; + height: 18px; + border-radius: 8px; + color: var(--color-text-primary); + background: var(--color-bg-warning-softest); } .link { display: block; - margin-bottom: 2px; font-size: 15px; font-weight: 500; text-decoration: none; @@ -40,15 +71,12 @@ color: var(--color-text-secondary); } -.metaList { - --gap: 0.75ch; +.menuButton { + padding: 4px; + margin-top: -2px; - display: flex; - flex-wrap: wrap; - gap: var(--gap); - - & > li:not(:last-child)::after { - content: '·'; - margin-inline-start: var(--gap); + svg { + width: 20px; + height: 20px; } } diff --git a/app/javascript/flavours/glitch/features/collections/detail/collection_list_item.tsx b/app/javascript/flavours/glitch/features/collections/detail/collection_list_item.tsx index 82d7faae1a..2e29a8a009 100644 --- a/app/javascript/flavours/glitch/features/collections/detail/collection_list_item.tsx +++ b/app/javascript/flavours/glitch/features/collections/detail/collection_list_item.tsx @@ -5,70 +5,65 @@ import { FormattedMessage } from 'react-intl'; import classNames from 'classnames'; import { Link } from 'react-router-dom'; +import WarningIcon from '@/material-icons/400-24px/warning.svg?react'; import type { ApiCollectionJSON } from 'flavours/glitch/api_types/collections'; +import { AvatarById } from 'flavours/glitch/components/avatar'; +import { useAccountHandle } from 'flavours/glitch/components/display_name/default'; import { RelativeTimestamp } from 'flavours/glitch/components/relative_timestamp'; import { Article } from 'flavours/glitch/components/scrollable_list/components'; +import { useAccount } from 'flavours/glitch/hooks/useAccount'; +import { domain } from 'flavours/glitch/initial_state'; import classes from './collection_list_item.module.scss'; import { CollectionMenu } from './collection_menu'; -export const CollectionMetaData: React.FC<{ - collection: ApiCollectionJSON; - extended?: boolean; - className?: string; -}> = ({ collection, extended, className }) => { +export const AvatarGrid: React.FC<{ + accountIds: (string | undefined)[]; + sensitive?: boolean; +}> = ({ accountIds: ids, sensitive }) => { + const avatarIds = [ids[0], ids[1], ids[2], ids[3]]; return ( -
    - - {extended && ( - <> - {collection.discoverable ? ( - - ) : ( - - )} - {collection.sensitive && ( - - )} - +
    , - }} - tagName='li' - /> -
+ > + {avatarIds.map((id) => ( + + ))} + {sensitive && } + ); }; export const CollectionListItem: React.FC<{ collection: ApiCollectionJSON; withoutBorder?: boolean; + withAuthorHandle?: boolean; + withTimestamp?: boolean; positionInList: number; listSize: number; -}> = ({ collection, withoutBorder, positionInList, listSize }) => { +}> = ({ + collection, + withoutBorder, + withAuthorHandle = true, + withTimestamp, + positionInList, + listSize, +}) => { const { id, name } = collection; - const linkId = useId(); + const uniqueId = useId(); + const linkId = `${uniqueId}-link`; + const infoId = `${uniqueId}-info`; + const authorAccount = useAccount(collection.account_id); + const authorHandle = useAccountHandle(authorAccount, domain); return (
-

- - {name} - -

- + item.account_id)} + sensitive={collection.sensitive} + /> +
+

+ + {name} + +

+
    + {collection.sensitive && ( +
  • + +
  • + )} + {withAuthorHandle && authorAccount && ( + + )} + + {withTimestamp && ( + + ), + }} + tagName='li' + /> + )} +
+
- +
); }; diff --git a/app/javascript/flavours/glitch/features/collections/detail/index.tsx b/app/javascript/flavours/glitch/features/collections/detail/index.tsx index 85f7844cb2..f5c5e863f5 100644 --- a/app/javascript/flavours/glitch/features/collections/detail/index.tsx +++ b/app/javascript/flavours/glitch/features/collections/detail/index.tsx @@ -6,6 +6,7 @@ import { Helmet } from 'react-helmet'; import { useHistory, useLocation, useParams } from 'react-router'; import { openModal } from '@/flavours/glitch/actions/modal'; +import { RelativeTimestamp } from '@/flavours/glitch/components/relative_timestamp'; 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'; @@ -25,7 +26,6 @@ import { fetchCollection } from 'flavours/glitch/reducers/slices/collections'; import { useAppDispatch, useAppSelector } from 'flavours/glitch/store'; import { CollectionAccountsList } from './accounts_list'; -import { CollectionMetaData } from './collection_list_item'; import { CollectionMenu } from './collection_menu'; import classes from './styles.module.scss'; @@ -40,6 +40,54 @@ const messages = defineMessages({ }, }); +const CollectionMetaData: React.FC<{ + collection: ApiCollectionJSON; + extended?: boolean; +}> = ({ collection, extended }) => { + return ( +
    + + {extended && ( + <> + {collection.discoverable ? ( + + ) : ( + + )} + {collection.sensitive && ( + + )} + + )} + , + }} + tagName='li' + /> +
+ ); +}; + export const AuthorNote: React.FC<{ id: string; previewMode?: boolean }> = ({ id, // When previewMode is enabled, your own display name @@ -137,7 +185,6 @@ const CollectionHeader: React.FC<{ collection: ApiCollectionJSON }> = ({ ); diff --git a/app/javascript/flavours/glitch/features/collections/detail/styles.module.scss b/app/javascript/flavours/glitch/features/collections/detail/styles.module.scss index 786c0e7000..89bad584b6 100644 --- a/app/javascript/flavours/glitch/features/collections/detail/styles.module.scss +++ b/app/javascript/flavours/glitch/features/collections/detail/styles.module.scss @@ -52,9 +52,19 @@ font-size: 13px; } -.metaData { +.metaList { + --gap: 0.75ch; + + display: flex; + flex-wrap: wrap; margin-top: 16px; + gap: var(--gap); font-size: 15px; + + & > li:not(:last-child)::after { + content: '·'; + margin-inline-start: var(--gap); + } } .columnSubheading { diff --git a/app/javascript/flavours/glitch/features/collections/index.tsx b/app/javascript/flavours/glitch/features/collections/index.tsx index 28f9fa8d32..b98c2c5dad 100644 --- a/app/javascript/flavours/glitch/features/collections/index.tsx +++ b/app/javascript/flavours/glitch/features/collections/index.tsx @@ -93,6 +93,8 @@ export const Collections: React.FC<{ {collections.map((item, index) => (