mirror of
https://github.com/mastodon/mastodon.git
synced 2026-09-12 20:06:18 -05:00
Status redesign: Link cards (#40436)
This commit is contained in:
@@ -107,15 +107,17 @@ type CardBodyProps<As extends React.ElementType> = PolymorphicProps<
|
||||
{
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
isDescription?: boolean;
|
||||
noClamp?: boolean;
|
||||
},
|
||||
As
|
||||
>;
|
||||
|
||||
export const CardBody = <As extends React.ElementType>({
|
||||
export const CardBody = <As extends React.ElementType = 'div'>({
|
||||
as: asComp,
|
||||
children,
|
||||
className,
|
||||
isDescription,
|
||||
noClamp,
|
||||
...props
|
||||
}: CardBodyProps<As>) => {
|
||||
@@ -123,7 +125,12 @@ export const CardBody = <As extends React.ElementType>({
|
||||
return (
|
||||
<Comp
|
||||
{...props}
|
||||
className={classNames(className, classes.body, !noClamp && classes.clamp)}
|
||||
className={classNames(
|
||||
className,
|
||||
classes.body,
|
||||
!noClamp && classes.clamp,
|
||||
isDescription && classes.description,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</Comp>
|
||||
|
||||
@@ -21,6 +21,9 @@
|
||||
|
||||
// Handle cases where the root is a link
|
||||
a.root {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
@@ -60,7 +63,7 @@ a.root {
|
||||
}
|
||||
|
||||
.body {
|
||||
@include mixins.type-body-compact;
|
||||
@include mixins.type-label-lg;
|
||||
|
||||
overflow-wrap: break-word;
|
||||
flex-grow: 1;
|
||||
@@ -84,12 +87,18 @@ a.root {
|
||||
a.body {
|
||||
padding-inline: var(--space-sm);
|
||||
margin-inline: calc(-1 * var(--space-sm));
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
|
||||
.root:has(&:hover) {
|
||||
background-color: var(--color-bg-highlight);
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
@include mixins.type-body-compact;
|
||||
}
|
||||
|
||||
.image {
|
||||
contain: size;
|
||||
grid-column: 2;
|
||||
|
||||
@@ -4,11 +4,13 @@ import { openModal } from '@/mastodon/actions/modal';
|
||||
import type { DeployPictureInPictureCallback } from '@/mastodon/actions/picture_in_picture';
|
||||
import { deployPictureInPicture } from '@/mastodon/actions/picture_in_picture';
|
||||
import { CollectionPreviewCard } from '@/mastodon/features/collections/components/collection_preview_card';
|
||||
import Card from '@/mastodon/features/status/components/card';
|
||||
import MediaCard from '@/mastodon/features/status/components/card';
|
||||
import { useExpandedStatus } from '@/mastodon/hooks/useStatus';
|
||||
import { useToggle } from '@/mastodon/hooks/useToggle';
|
||||
import { displayMedia } from '@/mastodon/initial_state';
|
||||
import type {
|
||||
CardShape,
|
||||
ExpandedStatusShape,
|
||||
MediaAttachment,
|
||||
MediaAttachmentShape,
|
||||
} from '@/mastodon/models/status';
|
||||
@@ -18,7 +20,9 @@ import { selectPictureInPicture } from '@/mastodon/selectors/statuses';
|
||||
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
||||
import { compareUrls } from '@/mastodon/utils/compare_urls';
|
||||
|
||||
import { Card, CardBody, CardTitle } from '../card';
|
||||
import { PictureInPicturePlaceholder } from '../picture_in_picture_placeholder';
|
||||
import { RelativeTimestamp } from '../relative_timestamp';
|
||||
|
||||
import { StatusQuote } from './quote';
|
||||
|
||||
@@ -58,13 +62,7 @@ export const StatusAttachments: React.FC<{
|
||||
? status.tagged_collections.find(({ url }) => compareUrls(url, card.url))
|
||||
: status.tagged_collections[0];
|
||||
if (card && !collection) {
|
||||
return (
|
||||
<Card
|
||||
key={`${status.id}-${status.edited_at}`}
|
||||
card={card}
|
||||
sensitive={status.sensitive}
|
||||
/>
|
||||
);
|
||||
return <LinkCard card={card} status={status} />;
|
||||
}
|
||||
|
||||
if (collection) {
|
||||
@@ -276,3 +274,56 @@ const MediaAttachments: React.FC<{
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
|
||||
const LinkCard: React.FC<{ card: CardShape; status: ExpandedStatusShape }> = ({
|
||||
card,
|
||||
status,
|
||||
}) => {
|
||||
// Use the old card if we have authors as the new design doesn't have attribution yet.
|
||||
if (card.type === 'video' || card.authors.length > 0) {
|
||||
return (
|
||||
<div>
|
||||
<MediaCard
|
||||
key={`${status.id}-${status.edited_at}`}
|
||||
card={card}
|
||||
sensitive={status.sensitive}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const providerUrl = new URL(card.provider_url || card.url);
|
||||
|
||||
const cardLinkProps = {
|
||||
as: 'a',
|
||||
href: card.url,
|
||||
target: '_blank',
|
||||
rel: 'noopener',
|
||||
} as const;
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardTitle
|
||||
afterContent={
|
||||
card.published_at && (
|
||||
<RelativeTimestamp timestamp={card.published_at} />
|
||||
)
|
||||
}
|
||||
>
|
||||
<a
|
||||
href={`${providerUrl.protocol}//${providerUrl.host}`}
|
||||
target='_blank'
|
||||
rel='noopener'
|
||||
>
|
||||
{card.author_name || card.provider_name || providerUrl.host}
|
||||
</a>
|
||||
</CardTitle>
|
||||
<CardBody {...cardLinkProps}>{card.title}</CardBody>
|
||||
{card.description && (
|
||||
<CardBody {...cardLinkProps} isDescription>
|
||||
{card.description}
|
||||
</CardBody>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user