[Glitch] Implement Collection list item design

Port c44cc1f5c3bafb49a324f8f72a42a91d09eecfe3 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
This commit is contained in:
diondiondion 2026-02-12 17:30:10 +01:00 committed by Claire
parent 629bb4989d
commit f40f529905
2 changed files with 98 additions and 14 deletions

View File

@ -1,7 +1,8 @@
import { useEffect, useMemo, useCallback } from 'react'; import { useEffect, useMemo, useCallback, useId } from 'react';
import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
@ -10,10 +11,12 @@ import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
import SquigglyArrow from '@/svg-icons/squiggly_arrow.svg?react'; import SquigglyArrow from '@/svg-icons/squiggly_arrow.svg?react';
import { openModal } from 'flavours/glitch/actions/modal'; import { openModal } from 'flavours/glitch/actions/modal';
import type { ApiCollectionJSON } from 'flavours/glitch/api_types/collections';
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';
import { Dropdown } from 'flavours/glitch/components/dropdown_menu'; import { Dropdown } from 'flavours/glitch/components/dropdown_menu';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { RelativeTimestamp } from 'flavours/glitch/components/relative_timestamp';
import ScrollableList from 'flavours/glitch/components/scrollable_list'; import ScrollableList from 'flavours/glitch/components/scrollable_list';
import { import {
fetchAccountCollections, fetchAccountCollections,
@ -22,6 +25,7 @@ import {
import { useAppSelector, useAppDispatch } from 'flavours/glitch/store'; import { useAppSelector, useAppDispatch } from 'flavours/glitch/store';
import { messages as editorMessages } from './editor'; import { messages as editorMessages } from './editor';
import classes from './styles.module.scss';
const messages = defineMessages({ const messages = defineMessages({
heading: { id: 'column.collections', defaultMessage: 'My collections' }, heading: { id: 'column.collections', defaultMessage: 'My collections' },
@ -36,13 +40,14 @@ const messages = defineMessages({
more: { id: 'status.more', defaultMessage: 'More' }, more: { id: 'status.more', defaultMessage: 'More' },
}); });
const ListItem: React.FC<{ const CollectionItem: React.FC<{
id: string; collection: ApiCollectionJSON;
name: string; }> = ({ collection }) => {
}> = ({ id, name }) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const intl = useIntl(); const intl = useIntl();
const { id, name } = collection;
const handleDeleteClick = useCallback(() => { const handleDeleteClick = useCallback(() => {
dispatch( dispatch(
openModal({ openModal({
@ -81,14 +86,45 @@ const ListItem: React.FC<{
[intl, id, handleDeleteClick], [intl, id, handleDeleteClick],
); );
const linkId = useId();
return ( return (
<div className='lists__item'> <article
<Link className={classNames(classes.collectionItemWrapper, 'focusable')}
to={`/collections/${id}/edit/details`} tabIndex={-1}
className='lists__item__title' aria-labelledby={linkId}
> >
<span>{name}</span> <div className={classes.collectionItemContent}>
</Link> <h2 id={linkId}>
<Link
to={`/collections/${id}/edit/details`}
className={classes.collectionItemLink}
>
{name}
</Link>
</h2>
<ul className={classes.collectionItemInfo}>
<FormattedMessage
id='collections.account_count'
defaultMessage='{count, plural, one {# account} other {# accounts}}'
values={{ count: collection.item_count }}
tagName='li'
/>
<FormattedMessage
id='collections.last_updated_at'
defaultMessage='Last updated: {date}'
values={{
date: (
<RelativeTimestamp
timestamp={collection.updated_at}
short={false}
/>
),
}}
tagName='li'
/>
</ul>
</div>
<Dropdown <Dropdown
scrollKey='collections' scrollKey='collections'
@ -97,7 +133,7 @@ const ListItem: React.FC<{
iconComponent={MoreHorizIcon} iconComponent={MoreHorizIcon}
title={intl.formatMessage(messages.more)} title={intl.formatMessage(messages.more)}
/> />
</div> </article>
); );
}; };
@ -166,7 +202,7 @@ export const Collections: React.FC<{
bindToDocument={!multiColumn} bindToDocument={!multiColumn}
> >
{collections.map((item) => ( {collections.map((item) => (
<ListItem key={item.id} id={item.id} name={item.name} /> <CollectionItem key={item.id} collection={item} />
))} ))}
</ScrollableList> </ScrollableList>

View File

@ -0,0 +1,48 @@
.collectionItemWrapper {
display: flex;
align-items: center;
gap: 16px;
margin-inline: 10px;
padding-inline-end: 5px;
border-bottom: 1px solid var(--color-border-primary);
}
.collectionItemContent {
position: relative;
flex-grow: 1;
padding: 15px 5px;
}
.collectionItemLink {
display: block;
margin-bottom: 2px;
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;
}
}
.collectionItemInfo {
--gap: 0.75ch;
display: flex;
gap: var(--gap);
font-size: 13px;
color: var(--color-text-secondary);
& > li:not(:first-child)::before {
content: '·';
margin-inline-end: var(--gap);
}
}