From 444741070266e06b65cef54b2a849cbf94ddb849 Mon Sep 17 00:00:00 2001 From: Echo Date: Wed, 9 Sep 2026 11:39:14 +0000 Subject: [PATCH] Status redesign: Link cards (#40436) --- .../mastodon/components/card/index.tsx | 11 ++- .../components/card/styles.module.scss | 11 ++- .../components/status/attachments.tsx | 67 ++++++++++++++++--- 3 files changed, 78 insertions(+), 11 deletions(-) diff --git a/app/javascript/mastodon/components/card/index.tsx b/app/javascript/mastodon/components/card/index.tsx index dfe79a012ef..7306d4d1fc7 100644 --- a/app/javascript/mastodon/components/card/index.tsx +++ b/app/javascript/mastodon/components/card/index.tsx @@ -107,15 +107,17 @@ type CardBodyProps = PolymorphicProps< { children: React.ReactNode; className?: string; + isDescription?: boolean; noClamp?: boolean; }, As >; -export const CardBody = ({ +export const CardBody = ({ as: asComp, children, className, + isDescription, noClamp, ...props }: CardBodyProps) => { @@ -123,7 +125,12 @@ export const CardBody = ({ return ( {children} diff --git a/app/javascript/mastodon/components/card/styles.module.scss b/app/javascript/mastodon/components/card/styles.module.scss index aaa9bb84b6d..ef8011f4a33 100644 --- a/app/javascript/mastodon/components/card/styles.module.scss +++ b/app/javascript/mastodon/components/card/styles.module.scss @@ -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; diff --git a/app/javascript/mastodon/components/status/attachments.tsx b/app/javascript/mastodon/components/status/attachments.tsx index e59e6deed5c..02f2f178075 100644 --- a/app/javascript/mastodon/components/status/attachments.tsx +++ b/app/javascript/mastodon/components/status/attachments.tsx @@ -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 ( - - ); + return ; } if (collection) { @@ -276,3 +274,56 @@ const MediaAttachments: React.FC<{ ); }; + +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 ( +
+ +
+ ); + } + + const providerUrl = new URL(card.provider_url || card.url); + + const cardLinkProps = { + as: 'a', + href: card.url, + target: '_blank', + rel: 'noopener', + } as const; + + return ( + + + ) + } + > + + {card.author_name || card.provider_name || providerUrl.host} + + + {card.title} + {card.description && ( + + {card.description} + + )} + + ); +};