[Glitch] Increase clickable area around collection items, refactor ListItem component
Port 2b93a2211fbf8bceb521b8f2bc4f5c768c8ad9ed to glitch-soc Signed-off-by: Claire <claire.github-309c@sitedethib.com>
This commit is contained in:
parent
57017329a4
commit
83ea9b94de
@ -1,6 +1,8 @@
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { polymorphicForwardRef } from '@/types/polymorphic';
|
||||||
|
|
||||||
import classes from './styles.module.scss';
|
import classes from './styles.module.scss';
|
||||||
|
|
||||||
interface WrapperProps extends Omit<
|
interface WrapperProps extends Omit<
|
||||||
@ -8,7 +10,7 @@ interface WrapperProps extends Omit<
|
|||||||
'title'
|
'title'
|
||||||
> {
|
> {
|
||||||
icon?: React.ReactNode;
|
icon?: React.ReactNode;
|
||||||
iconEnd?: React.ReactNode;
|
sideContent?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -20,7 +22,7 @@ interface WrapperProps extends Omit<
|
|||||||
*/
|
*/
|
||||||
export const ListItemWrapper: React.FC<WrapperProps> = ({
|
export const ListItemWrapper: React.FC<WrapperProps> = ({
|
||||||
icon,
|
icon,
|
||||||
iconEnd,
|
sideContent,
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
...otherProps
|
...otherProps
|
||||||
@ -29,69 +31,68 @@ export const ListItemWrapper: React.FC<WrapperProps> = ({
|
|||||||
<div {...otherProps} className={classNames(classes.wrapper, className)}>
|
<div {...otherProps} className={classNames(classes.wrapper, className)}>
|
||||||
{icon}
|
{icon}
|
||||||
<div>{children}</div>
|
<div>{children}</div>
|
||||||
{iconEnd && <span className={classes.iconEnd}>{iconEnd}</span>}
|
{sideContent && (
|
||||||
|
<span className={classes.sideContent}>{sideContent}</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
interface WithSubtitle {
|
interface ContentProps {
|
||||||
subtitle?: React.ReactNode;
|
subtitle?: React.ReactNode;
|
||||||
|
subtitleId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ContentProps
|
export const ListItemContent = polymorphicForwardRef<'h3', ContentProps>(
|
||||||
extends React.ComponentPropsWithoutRef<'h3'>, WithSubtitle {}
|
(
|
||||||
|
{ as: Component = 'h3', subtitle, subtitleId, children, ...otherProps },
|
||||||
export const ListItemContent: React.FC<ContentProps> = ({
|
ref,
|
||||||
subtitle,
|
) => {
|
||||||
children,
|
return (
|
||||||
...otherProps
|
<>
|
||||||
}) => {
|
<Component className={classes.title} ref={ref} {...otherProps}>
|
||||||
return (
|
{children}
|
||||||
<>
|
</Component>
|
||||||
<h3 className={classes.title} {...otherProps}>
|
{subtitle && (
|
||||||
{children}
|
<div className={classes.subtitle} id={subtitleId}>
|
||||||
</h3>
|
{subtitle}
|
||||||
{subtitle && <div className={classes.subtitle}>{subtitle}</div>}
|
</div>
|
||||||
</>
|
)}
|
||||||
);
|
</>
|
||||||
};
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
interface LinkProps
|
interface LinkProps
|
||||||
extends React.ComponentPropsWithoutRef<typeof Link>, WithSubtitle {}
|
extends React.ComponentPropsWithoutRef<typeof Link>, ContentProps {}
|
||||||
|
|
||||||
export const ListItemLink: React.FC<LinkProps> = ({
|
export const ListItemLink = polymorphicForwardRef<'h3', LinkProps>(
|
||||||
subtitle,
|
({ as, subtitle, children, className, ...otherProps }, ref) => {
|
||||||
children,
|
return (
|
||||||
className,
|
<ListItemContent ref={ref} as={as} subtitle={subtitle}>
|
||||||
...otherProps
|
<Link className={classNames(className, 'focusable')} {...otherProps}>
|
||||||
}) => {
|
{children}
|
||||||
return (
|
</Link>
|
||||||
<ListItemContent subtitle={subtitle}>
|
</ListItemContent>
|
||||||
<Link className={classNames(className, 'focusable')} {...otherProps}>
|
);
|
||||||
{children}
|
},
|
||||||
</Link>
|
);
|
||||||
</ListItemContent>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface ButtonProps
|
interface ButtonProps
|
||||||
extends React.ComponentPropsWithoutRef<'button'>, WithSubtitle {}
|
extends React.ComponentPropsWithoutRef<'button'>, ContentProps {}
|
||||||
|
|
||||||
export const ListItemButton: React.FC<ButtonProps> = ({
|
export const ListItemButton = polymorphicForwardRef<'h3', ButtonProps>(
|
||||||
subtitle,
|
({ as, subtitle, children, className, ...otherProps }, ref) => {
|
||||||
children,
|
return (
|
||||||
className,
|
<ListItemContent as={as} ref={ref} subtitle={subtitle}>
|
||||||
...otherProps
|
<button
|
||||||
}) => {
|
type='button'
|
||||||
return (
|
className={classNames(className, 'focusable')}
|
||||||
<ListItemContent subtitle={subtitle}>
|
{...otherProps}
|
||||||
<button
|
>
|
||||||
type='button'
|
{children}
|
||||||
className={classNames(className, 'focusable')}
|
</button>
|
||||||
{...otherProps}
|
</ListItemContent>
|
||||||
>
|
);
|
||||||
{children}
|
},
|
||||||
</button>
|
);
|
||||||
</ListItemContent>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import KeyboardArrowDownIcon from '@/material-icons/400-24px/keyboard_arrow_down
|
|||||||
import VisibilityIcon from '@/material-icons/400-24px/visibility.svg?react';
|
import VisibilityIcon from '@/material-icons/400-24px/visibility.svg?react';
|
||||||
import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?react';
|
import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?react';
|
||||||
|
|
||||||
|
import { AvatarById } from '../avatar';
|
||||||
|
import { Button } from '../button';
|
||||||
import { Icon } from '../icon';
|
import { Icon } from '../icon';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -36,7 +38,7 @@ export const WithButton: Story = {
|
|||||||
render: () => (
|
render: () => (
|
||||||
<ListItemWrapper
|
<ListItemWrapper
|
||||||
icon={<Icon icon={VisibilityOffIcon} id='visibility' />}
|
icon={<Icon icon={VisibilityOffIcon} id='visibility' />}
|
||||||
iconEnd={<Icon icon={KeyboardArrowDownIcon} id='down' />}
|
sideContent={<Icon icon={KeyboardArrowDownIcon} id='down' />}
|
||||||
>
|
>
|
||||||
<ListItemButton subtitle='You’ve blocked or muted these users'>
|
<ListItemButton subtitle='You’ve blocked or muted these users'>
|
||||||
3 hidden accounts
|
3 hidden accounts
|
||||||
@ -49,9 +51,22 @@ export const WithLink: Story = {
|
|||||||
render: () => (
|
render: () => (
|
||||||
<ListItemWrapper
|
<ListItemWrapper
|
||||||
icon={<Icon icon={VisibilityIcon} id='visibility' />}
|
icon={<Icon icon={VisibilityIcon} id='visibility' />}
|
||||||
iconEnd={<Icon icon={ChevronRightIcon} id='right' />}
|
sideContent={<Icon icon={ChevronRightIcon} id='right' />}
|
||||||
>
|
>
|
||||||
<ListItemLink to='/'>View more</ListItemLink>
|
<ListItemLink to='/'>View more</ListItemLink>
|
||||||
</ListItemWrapper>
|
</ListItemWrapper>
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const WithInteractiveSideContent: Story = {
|
||||||
|
render: () => (
|
||||||
|
<ListItemWrapper
|
||||||
|
icon={<AvatarById accountId='1' size={40} />}
|
||||||
|
sideContent={<Button compact>Follow</Button>}
|
||||||
|
>
|
||||||
|
<ListItemLink to='/' subtitle='@test@example.com'>
|
||||||
|
Test account
|
||||||
|
</ListItemLink>
|
||||||
|
</ListItemWrapper>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|||||||
@ -1,11 +1,15 @@
|
|||||||
.wrapper {
|
.wrapper {
|
||||||
|
--list-item-padding: 16px;
|
||||||
|
--list-item-padding-block: var(--list-item-padding);
|
||||||
|
--list-item-gap: 12px;
|
||||||
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
gap: 12px;
|
gap: var(--list-item-gap);
|
||||||
padding: 16px;
|
padding: var(--list-item-padding-block) var(--list-item-padding);
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
color: var(--color-text-primary);
|
color: var(--color-text-primary);
|
||||||
}
|
}
|
||||||
@ -29,7 +33,7 @@
|
|||||||
&::before {
|
&::before {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: var(--clickable-area-spread, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -39,6 +43,32 @@
|
|||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconEnd {
|
.sideContent {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--list-item-gap);
|
||||||
|
align-self: stretch;
|
||||||
|
align-items: center;
|
||||||
margin-inline-start: auto;
|
margin-inline-start: auto;
|
||||||
|
|
||||||
|
&:has(button, a) {
|
||||||
|
// If the sideContent has interactive children, move it
|
||||||
|
// above the clickable area of the .title content
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
// Cover up the .title content's clickable area in the
|
||||||
|
// padding box around the sideContent
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset-block: calc(-1 * var(--list-item-padding-block));
|
||||||
|
inset-inline: calc(-1 * var(--list-item-padding));
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Improve vertical alignment of slotted icons
|
||||||
|
& > .icon {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useId, useState } from 'react';
|
||||||
|
|
||||||
import { Article } from '@/flavours/glitch/components/scrollable_list/components';
|
import { Article } from '@/flavours/glitch/components/scrollable_list/components';
|
||||||
import KeyboardArrowDownIcon from '@/material-icons/400-24px/keyboard_arrow_down.svg?react';
|
import KeyboardArrowDownIcon from '@/material-icons/400-24px/keyboard_arrow_down.svg?react';
|
||||||
@ -42,6 +42,8 @@ export const TruncatedListItems = <TListItem,>({
|
|||||||
toggleButton,
|
toggleButton,
|
||||||
renderListItem,
|
renderListItem,
|
||||||
}: TruncatedListProps<TListItem>) => {
|
}: TruncatedListProps<TListItem>) => {
|
||||||
|
const toggleButtonId = useId();
|
||||||
|
const toggleButtonDescId = `${toggleButtonId}-desc`;
|
||||||
const [showTruncatedItems, setShowTruncatedItems] = useState(false);
|
const [showTruncatedItems, setShowTruncatedItems] = useState(false);
|
||||||
const toggleTruncatedItems = useCallback(() => {
|
const toggleTruncatedItems = useCallback(() => {
|
||||||
setShowTruncatedItems((prev) => !prev);
|
setShowTruncatedItems((prev) => !prev);
|
||||||
@ -65,14 +67,19 @@ export const TruncatedListItems = <TListItem,>({
|
|||||||
});
|
});
|
||||||
})}
|
})}
|
||||||
{hasHiddenAccounts && (
|
{hasHiddenAccounts && (
|
||||||
<Article aria-posinset={initialListSize} aria-setsize={totalListLength}>
|
<Article
|
||||||
|
aria-posinset={initialListSize}
|
||||||
|
aria-setsize={totalListLength}
|
||||||
|
aria-labelledby={toggleButtonId}
|
||||||
|
aria-describedby={toggleButtonDescId}
|
||||||
|
>
|
||||||
<ListItemWrapper
|
<ListItemWrapper
|
||||||
icon={
|
icon={
|
||||||
toggleButton.icon && (
|
toggleButton.icon && (
|
||||||
<Icon id='toggle-icon' icon={toggleButton.icon} />
|
<Icon id='toggle-icon' icon={toggleButton.icon} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
iconEnd={
|
sideContent={
|
||||||
<Icon
|
<Icon
|
||||||
id='open-status'
|
id='open-status'
|
||||||
icon={
|
icon={
|
||||||
@ -84,6 +91,8 @@ export const TruncatedListItems = <TListItem,>({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
|
id={toggleButtonId}
|
||||||
|
subtitleId={toggleButtonDescId}
|
||||||
aria-expanded={showTruncatedItems}
|
aria-expanded={showTruncatedItems}
|
||||||
onClick={toggleTruncatedItems}
|
onClick={toggleTruncatedItems}
|
||||||
subtitle={toggleButton.subtitle}
|
subtitle={toggleButton.subtitle}
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
.wrapper {
|
.wrapper {
|
||||||
display: flex;
|
--list-item-padding: 16px;
|
||||||
align-items: start;
|
--list-item-padding-block: 12px;
|
||||||
padding: 12px 16px;
|
--list-item-gap: 16px;
|
||||||
gap: 16px;
|
|
||||||
|
|
||||||
&:not(.wrapperWithoutBorder) {
|
&:not(.wrapperWithoutBorder) {
|
||||||
border-bottom: 1px solid var(--color-border-primary);
|
border-bottom: 1px solid var(--color-border-primary);
|
||||||
@ -10,8 +9,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.menuButton {
|
.menuButton {
|
||||||
|
align-self: start;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
margin-top: -2px;
|
margin-top: -4px;
|
||||||
|
margin-inline-end: -4px;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
|
|||||||
@ -9,7 +9,10 @@ import { CollectionMenu } from 'flavours/glitch/features/collections/components/
|
|||||||
|
|
||||||
import classes from './collection_list_item.module.scss';
|
import classes from './collection_list_item.module.scss';
|
||||||
|
|
||||||
interface CollectionListItemProps extends CollectionLockupProps {
|
interface CollectionListItemProps extends Omit<
|
||||||
|
CollectionLockupProps,
|
||||||
|
'sideContent'
|
||||||
|
> {
|
||||||
withoutBorder?: boolean;
|
withoutBorder?: boolean;
|
||||||
positionInList: number;
|
positionInList: number;
|
||||||
listSize: number;
|
listSize: number;
|
||||||
@ -20,6 +23,7 @@ export const CollectionListItem: React.FC<CollectionListItemProps> = ({
|
|||||||
withoutBorder,
|
withoutBorder,
|
||||||
positionInList,
|
positionInList,
|
||||||
listSize,
|
listSize,
|
||||||
|
className,
|
||||||
...otherProps
|
...otherProps
|
||||||
}) => {
|
}) => {
|
||||||
const uniqueId = useId();
|
const uniqueId = useId();
|
||||||
@ -29,21 +33,26 @@ export const CollectionListItem: React.FC<CollectionListItemProps> = ({
|
|||||||
return (
|
return (
|
||||||
<Article
|
<Article
|
||||||
focusable
|
focusable
|
||||||
className={classNames(
|
|
||||||
classes.wrapper,
|
|
||||||
withoutBorder && classes.wrapperWithoutBorder,
|
|
||||||
)}
|
|
||||||
aria-labelledby={linkId}
|
aria-labelledby={linkId}
|
||||||
aria-describedby={infoId}
|
aria-describedby={infoId}
|
||||||
aria-posinset={positionInList}
|
aria-posinset={positionInList}
|
||||||
aria-setsize={listSize}
|
aria-setsize={listSize}
|
||||||
>
|
>
|
||||||
<CollectionLockup collection={collection} {...otherProps} />
|
<CollectionLockup
|
||||||
|
|
||||||
<CollectionMenu
|
|
||||||
context='list'
|
|
||||||
collection={collection}
|
collection={collection}
|
||||||
className={classes.menuButton}
|
className={classNames(
|
||||||
|
classes.wrapper,
|
||||||
|
withoutBorder && classes.wrapperWithoutBorder,
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
sideContent={
|
||||||
|
<CollectionMenu
|
||||||
|
context='list'
|
||||||
|
collection={collection}
|
||||||
|
className={classes.menuButton}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
{...otherProps}
|
||||||
/>
|
/>
|
||||||
</Article>
|
</Article>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,11 +1,5 @@
|
|||||||
.content {
|
.wrapper {
|
||||||
position: relative;
|
--list-item-padding: 0px;
|
||||||
flex-grow: 1;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
column-gap: 12px;
|
|
||||||
color: var(--color-text-primary);
|
|
||||||
line-height: 1.3;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatarGrid {
|
.avatarGrid {
|
||||||
@ -36,27 +30,3 @@
|
|||||||
fill: var(--color-text-primary);
|
fill: var(--color-text-primary);
|
||||||
background: var(--color-bg-warning-softest);
|
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 { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
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 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';
|
||||||
@ -47,71 +48,78 @@ export interface CollectionLockupProps {
|
|||||||
collection: ApiCollectionJSON;
|
collection: ApiCollectionJSON;
|
||||||
withAuthorHandle?: boolean;
|
withAuthorHandle?: boolean;
|
||||||
withTimestamp?: boolean;
|
withTimestamp?: boolean;
|
||||||
|
sideContent?: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CollectionLockup: React.FC<CollectionLockupProps> = ({
|
export const CollectionLockup: React.FC<CollectionLockupProps> = ({
|
||||||
collection,
|
collection,
|
||||||
withAuthorHandle = true,
|
withAuthorHandle = true,
|
||||||
withTimestamp,
|
withTimestamp,
|
||||||
|
sideContent,
|
||||||
|
className,
|
||||||
}) => {
|
}) => {
|
||||||
const { id, name } = collection;
|
const { id, name } = collection;
|
||||||
const uniqueId = useId();
|
|
||||||
const linkId = `${uniqueId}-link`;
|
|
||||||
const infoId = `${uniqueId}-info`;
|
|
||||||
const authorAccount = useAccount(collection.account_id);
|
const authorAccount = useAccount(collection.account_id);
|
||||||
const authorHandle = useAccountHandle(authorAccount, domain);
|
const authorHandle = useAccountHandle(authorAccount, domain);
|
||||||
|
|
||||||
return (
|
const collectionInfo = (
|
||||||
<div className={classes.content}>
|
<ul>
|
||||||
<AvatarGrid
|
{collection.sensitive && (
|
||||||
accountIds={collection.items.map((item) => item.account_id)}
|
<li className='sr-only'>
|
||||||
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'
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='collections.account_count'
|
id='collections.sensitive'
|
||||||
defaultMessage='{count, plural, one {# account} other {# accounts}}'
|
defaultMessage='Sensitive'
|
||||||
values={{ count: collection.item_count }}
|
|
||||||
tagName='li'
|
|
||||||
/>
|
/>
|
||||||
{withTimestamp && (
|
</li>
|
||||||
<FormattedMessage
|
)}
|
||||||
id='collections.last_updated_at'
|
{withAuthorHandle && authorAccount && (
|
||||||
defaultMessage='Last updated: {date}'
|
<FormattedMessage
|
||||||
values={{
|
id='collections.by_account'
|
||||||
date: (
|
defaultMessage='by {account_handle}'
|
||||||
<RelativeTimestamp timestamp={collection.updated_at} long />
|
values={{
|
||||||
),
|
account_handle: authorHandle,
|
||||||
}}
|
}}
|
||||||
tagName='li'
|
tagName='li'
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</ul>
|
<FormattedMessage
|
||||||
</div>
|
id='collections.account_count'
|
||||||
</div>
|
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 {
|
.wrapper {
|
||||||
display: flex;
|
--list-item-padding: 12px;
|
||||||
align-items: start;
|
|
||||||
padding: 12px;
|
|
||||||
gap: 12px;
|
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
border: 1px solid var(--color-border-primary);
|
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 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 type { CollectionLockupProps } from 'flavours/glitch/features/collections/components/collection_lockup';
|
||||||
import { CollectionLockup } 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,
|
onRemove,
|
||||||
...otherProps
|
...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 (
|
return (
|
||||||
<div className={classNames(classes.wrapper, 'collection-preview')}>
|
<CollectionLockup
|
||||||
<CollectionLockup collection={collection} {...otherProps} />
|
collection={collection}
|
||||||
</div>
|
className={classNames(classes.wrapper, 'collection-preview')}
|
||||||
|
sideContent={removeButton}
|
||||||
|
{...otherProps}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user