[Glitch] Increase clickable area around collection items, refactor ListItem component

Port 2b93a2211f to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
This commit is contained in:
diondiondion
2026-04-27 20:44:53 +02:00
committed by Claire
parent 57017329a4
commit 83ea9b94de
10 changed files with 240 additions and 174 deletions
@@ -1,8 +1,7 @@
.wrapper {
display: flex;
align-items: start;
padding: 12px 16px;
gap: 16px;
--list-item-padding: 16px;
--list-item-padding-block: 12px;
--list-item-gap: 16px;
&:not(.wrapperWithoutBorder) {
border-bottom: 1px solid var(--color-border-primary);
@@ -10,8 +9,10 @@
}
.menuButton {
align-self: start;
padding: 4px;
margin-top: -2px;
margin-top: -4px;
margin-inline-end: -4px;
svg {
width: 20px;
@@ -9,7 +9,10 @@ import { CollectionMenu } from 'flavours/glitch/features/collections/components/
import classes from './collection_list_item.module.scss';
interface CollectionListItemProps extends CollectionLockupProps {
interface CollectionListItemProps extends Omit<
CollectionLockupProps,
'sideContent'
> {
withoutBorder?: boolean;
positionInList: number;
listSize: number;
@@ -20,6 +23,7 @@ export const CollectionListItem: React.FC<CollectionListItemProps> = ({
withoutBorder,
positionInList,
listSize,
className,
...otherProps
}) => {
const uniqueId = useId();
@@ -29,21 +33,26 @@ export const CollectionListItem: React.FC<CollectionListItemProps> = ({
return (
<Article
focusable
className={classNames(
classes.wrapper,
withoutBorder && classes.wrapperWithoutBorder,
)}
aria-labelledby={linkId}
aria-describedby={infoId}
aria-posinset={positionInList}
aria-setsize={listSize}
>
<CollectionLockup collection={collection} {...otherProps} />
<CollectionMenu
context='list'
<CollectionLockup
collection={collection}
className={classes.menuButton}
className={classNames(
classes.wrapper,
withoutBorder && classes.wrapperWithoutBorder,
className,
)}
sideContent={
<CollectionMenu
context='list'
collection={collection}
className={classes.menuButton}
/>
}
{...otherProps}
/>
</Article>
);
@@ -1,11 +1,5 @@
.content {
position: relative;
flex-grow: 1;
display: flex;
align-items: center;
column-gap: 12px;
color: var(--color-text-primary);
line-height: 1.3;
.wrapper {
--list-item-padding: 0px;
}
.avatarGrid {
@@ -36,27 +30,3 @@
fill: var(--color-text-primary);
background: var(--color-bg-warning-softest);
}
.link {
display: block;
font-size: 15px;
font-weight: 500;
text-decoration: none;
color: var(--color-text-primary);
&:hover {
color: var(--color-text-brand);
}
&::after {
// Increase clickable area by extending link across parent
content: '';
position: absolute;
inset: 0;
}
}
.info {
font-size: 13px;
color: var(--color-text-secondary);
}
@@ -1,10 +1,11 @@
import { useId } from 'react';
import { FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import { Link } from 'react-router-dom';
import {
ListItemLink,
ListItemWrapper,
} from '@/flavours/glitch/components/list_item';
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';
@@ -47,71 +48,78 @@ export interface CollectionLockupProps {
collection: ApiCollectionJSON;
withAuthorHandle?: boolean;
withTimestamp?: boolean;
sideContent?: React.ReactNode;
className?: string;
}
export const CollectionLockup: React.FC<CollectionLockupProps> = ({
collection,
withAuthorHandle = true,
withTimestamp,
sideContent,
className,
}) => {
const { id, name } = collection;
const uniqueId = useId();
const linkId = `${uniqueId}-link`;
const infoId = `${uniqueId}-info`;
const authorAccount = useAccount(collection.account_id);
const authorHandle = useAccountHandle(authorAccount, domain);
return (
<div className={classes.content}>
<AvatarGrid
accountIds={collection.items.map((item) => item.account_id)}
sensitive={collection.sensitive}
/>
<div>
<h2 id={linkId}>
<Link to={getCollectionPath(id)} className={classes.link}>
{name}
</Link>
</h2>
<ul className={classes.info} id={infoId}>
{collection.sensitive && (
<li className='sr-only'>
<FormattedMessage
id='collections.sensitive'
defaultMessage='Sensitive'
/>
</li>
)}
{withAuthorHandle && authorAccount && (
<FormattedMessage
id='collections.by_account'
defaultMessage='by {account_handle}'
values={{
account_handle: authorHandle,
}}
tagName='li'
/>
)}
const collectionInfo = (
<ul>
{collection.sensitive && (
<li className='sr-only'>
<FormattedMessage
id='collections.account_count'
defaultMessage='{count, plural, one {# account} other {# accounts}}'
values={{ count: collection.item_count }}
tagName='li'
id='collections.sensitive'
defaultMessage='Sensitive'
/>
{withTimestamp && (
<FormattedMessage
id='collections.last_updated_at'
defaultMessage='Last updated: {date}'
values={{
date: (
<RelativeTimestamp timestamp={collection.updated_at} long />
),
}}
tagName='li'
/>
)}
</ul>
</div>
</div>
</li>
)}
{withAuthorHandle && authorAccount && (
<FormattedMessage
id='collections.by_account'
defaultMessage='by {account_handle}'
values={{
account_handle: authorHandle,
}}
tagName='li'
/>
)}
<FormattedMessage
id='collections.account_count'
defaultMessage='{count, plural, one {# account} other {# accounts}}'
values={{ count: collection.item_count }}
tagName='li'
/>
{withTimestamp && (
<FormattedMessage
id='collections.last_updated_at'
defaultMessage='Last updated: {date}'
values={{
date: <RelativeTimestamp timestamp={collection.updated_at} long />,
}}
tagName='li'
/>
)}
</ul>
);
return (
<ListItemWrapper
className={classNames(classes.wrapper, className)}
icon={
<AvatarGrid
accountIds={collection.items.map((item) => item.account_id)}
sensitive={collection.sensitive}
/>
}
sideContent={sideContent}
>
<ListItemLink
as='h2'
to={getCollectionPath(id)}
subtitle={collectionInfo}
>
{name}
</ListItemLink>
</ListItemWrapper>
);
};
@@ -1,8 +1,10 @@
.wrapper {
display: flex;
align-items: start;
padding: 12px;
gap: 12px;
--list-item-padding: 12px;
border-radius: 12px;
border: 1px solid var(--color-border-primary);
}
.removeButton {
align-self: start;
}
@@ -1,5 +1,9 @@
import { useIntl } from 'react-intl';
import classNames from 'classnames';
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
import { IconButton } from 'flavours/glitch/components/icon_button';
import type { CollectionLockupProps } from 'flavours/glitch/features/collections/components/collection_lockup';
import { CollectionLockup } from 'flavours/glitch/features/collections/components/collection_lockup';
@@ -14,9 +18,26 @@ export const CollectionPreviewCard: React.FC<CollectionPreviewCardProps> = ({
onRemove,
...otherProps
}) => {
const intl = useIntl();
const removeButton = onRemove && (
<IconButton
icon='remove'
iconComponent={CloseIcon}
onClick={onRemove}
title={intl.formatMessage({
id: 'tag.remove',
defaultMessage: 'Remove',
})}
className={classes.removeButton}
/>
);
return (
<div className={classNames(classes.wrapper, 'collection-preview')}>
<CollectionLockup collection={collection} {...otherProps} />
</div>
<CollectionLockup
collection={collection}
className={classNames(classes.wrapper, 'collection-preview')}
sideContent={removeButton}
{...otherProps}
/>
);
};