From 834fcf0a844e43fa4906e5291cd7407a4e2a3ff3 Mon Sep 17 00:00:00 2001
From: Echo
Date: Mon, 7 Sep 2026 15:46:09 +0000
Subject: [PATCH] Status redesign: Fix padding and adds variants (#40400)
---
.../mastodon/components/status/meta.tsx | 103 ++++++++++++++++++
.../components/status/status.stories.tsx | 10 ++
.../mastodon/components/status/status.tsx | 68 +++++++++---
.../components/status/styles.module.scss | 38 ++++++-
.../mastodon/components/status/utils.ts | 4 +-
.../features/compose/redesign/index.tsx | 1 +
.../compose/redesign/modal_switch.tsx | 1 +
app/javascript/mastodon/locales/en.json | 2 +
app/javascript/mastodon/models/status.ts | 5 +
9 files changed, 210 insertions(+), 22 deletions(-)
create mode 100644 app/javascript/mastodon/components/status/meta.tsx
diff --git a/app/javascript/mastodon/components/status/meta.tsx b/app/javascript/mastodon/components/status/meta.tsx
new file mode 100644
index 00000000000..4af81defb6d
--- /dev/null
+++ b/app/javascript/mastodon/components/status/meta.tsx
@@ -0,0 +1,103 @@
+import type React from 'react';
+import { useMemo } from 'react';
+
+import { FormattedDate, FormattedMessage } from 'react-intl';
+
+import { Link } from 'react-router-dom';
+
+import type {
+ AnyStatusShape,
+ StatusVisibility,
+} from '@/mastodon/models/status';
+
+import { statusLink } from './utils';
+
+export const StatusMeta: React.FC<
+ {
+ status: Pick<
+ AnyStatusShape,
+ 'account' | 'application' | 'created_at' | 'id' | 'visibility'
+ >;
+ } & React.ComponentPropsWithRef<'span'>
+> = ({ status, ...props }) => {
+ const { created_at, application } = status;
+
+ const createdAt = useMemo(() => {
+ try {
+ return new Date(created_at);
+ } catch {
+ return null;
+ }
+ }, [created_at]);
+ const visibility = useMemo(
+ () => statusVisibilityText(status.visibility),
+ [status.visibility],
+ );
+
+ if (!createdAt) {
+ return null;
+ }
+
+ let applicationLink: React.ReactNode = application.name;
+ if (application.website) {
+ applicationLink = (
+
+ {status.application.name}
+
+ );
+ }
+
+ return (
+
+
+
+
+ ),
+ source: applicationLink,
+ visibility,
+ }}
+ />
+
+ );
+};
+
+function statusVisibilityText(visibility: StatusVisibility) {
+ switch (visibility) {
+ case 'private':
+ return (
+
+ );
+ case 'direct':
+ return (
+
+ );
+
+ default:
+ return (
+
+ );
+ }
+}
diff --git a/app/javascript/mastodon/components/status/status.stories.tsx b/app/javascript/mastodon/components/status/status.stories.tsx
index 77083b34ca8..b61d38e79e1 100644
--- a/app/javascript/mastodon/components/status/status.stories.tsx
+++ b/app/javascript/mastodon/components/status/status.stories.tsx
@@ -13,6 +13,7 @@ import {
statusFactoryImmutable,
} from '@/testing/factories';
+import type { StatusVariant } from './status';
import { StatusRedesign } from './status';
import type { AttachmentArgs } from './testing';
import { attachmentArgTypes, attachmentFactory } from './testing';
@@ -42,6 +43,7 @@ interface StatusStoryProps extends AttachmentArgs {
// Display
showThread?: boolean;
contextType?: StatusContextType;
+ variant?: StatusVariant;
showCounters?: boolean;
favouriteCount?: number;
reblogCount?: number;
@@ -67,6 +69,7 @@ const StatusStoryComponent: FC = (props) => {
disableActions = false,
contextType,
+ variant,
showThread,
showCounters,
hidden,
@@ -81,6 +84,7 @@ const StatusStoryComponent: FC = (props) => {
isQuotedPost={isQuote}
showActions={!disableActions}
contextType={contextType}
+ variant={variant}
withCounters={showCounters}
// Either we are showing a thread (in a timeline) or it's a full reply chain view.
showThread={isReply && showThread}
@@ -201,6 +205,11 @@ const meta = {
showTranslate: categoryInteraction,
// Display
+ variant: {
+ ...categoryDisplay,
+ control: 'inline-radio',
+ options: ['feed', 'thread', 'page'] satisfies StatusVariant[],
+ },
showCounters: categoryDisplay,
favouriteCount: categoryDisplay,
reblogCount: categoryDisplay,
@@ -253,6 +262,7 @@ const meta = {
disableActions: false,
showTranslate: false,
+ variant: 'feed',
favouriteCount: 0,
reblogCount: 0,
replyCount: 0,
diff --git a/app/javascript/mastodon/components/status/status.tsx b/app/javascript/mastodon/components/status/status.tsx
index 014375f8369..1f2a4a2b9c7 100644
--- a/app/javascript/mastodon/components/status/status.tsx
+++ b/app/javascript/mastodon/components/status/status.tsx
@@ -19,6 +19,7 @@ import { StatusAttachments } from './attachments';
import { StatusContent } from './content';
import type { StatusHandlers } from './hooks';
import { useStatusHandlers, useTextForScreenReader } from './hooks';
+import { StatusMeta } from './meta';
import { StatusPrepend } from './prepend';
import { StatusRedesignHeader } from './redesign/header';
import classes from './styles.module.scss';
@@ -30,9 +31,12 @@ type StatusRedesignProps = Merge<
accountId?: string;
contextType?: StatusContextType;
headerContents?: React.ReactNode;
+ variant?: StatusVariant;
}
>;
+export type StatusVariant = 'feed' | 'thread' | 'page';
+
const selectStatusReblog = createAppSelector(
[(state, id?: string | null) => selectExpandedStatus(state, id ?? undefined)],
(status) => {
@@ -68,6 +72,7 @@ export const StatusRedesign: React.FC = ({
onOpen,
showThread,
headerContents,
+ variant = contextToVariant(contextType),
}) => {
// Select data from store
const { status, parent } = useAppSelector((state) =>
@@ -129,10 +134,20 @@ export const StatusRedesign: React.FC = ({
);
}
+ const showFooter =
+ (expanded && hashtagsInBar.length > 0) ||
+ variant === 'page' ||
+ (showActions && !isQuotedPost);
+
return (
= ({
)}
-
+ )}
);
};
@@ -256,3 +280,17 @@ const StatusHotkeys = ({
);
};
+
+function contextToVariant(contextType?: StatusContextType): StatusVariant {
+ switch (contextType) {
+ case 'composer':
+ case 'detailed':
+ case 'notifications':
+ case undefined:
+ return 'page';
+ case 'thread':
+ return 'thread';
+ default:
+ return 'feed';
+ }
+}
diff --git a/app/javascript/mastodon/components/status/styles.module.scss b/app/javascript/mastodon/components/status/styles.module.scss
index 7bb405182e9..fe1627c0f69 100644
--- a/app/javascript/mastodon/components/status/styles.module.scss
+++ b/app/javascript/mastodon/components/status/styles.module.scss
@@ -3,13 +3,33 @@
.root {
--col-gap: var(--space-xs);
--avatar-size: 40px;
+ --content-col: 2;
display: grid;
grid-template-columns: calc(var(--avatar-size) + var(--col-gap)) auto;
- grid-template-rows:
- minmax(calc(var(--avatar-size) + var(--col-gap)), auto)
- 1fr auto;
+ grid-template-rows: minmax(calc(var(--avatar-size) + var(--col-gap)), auto);
+ grid-auto-rows: auto;
row-gap: var(--space-xs);
+ padding: var(--space-md);
+}
+
+.variantThread {
+ --avatar-size: 32px;
+
+ padding: var(--space-xs) 0;
+}
+
+.variantPage {
+ padding: 0;
+}
+
+.isQuote {
+ padding: var(--space-sm);
+}
+
+.variantPage,
+.isQuote {
+ --content-col: span 2;
}
.header {
@@ -17,8 +37,10 @@
}
.content,
-.footer {
- grid-column: 2;
+.footer,
+.root :global(.status__prepend),
+.root :global(.content-warning) {
+ grid-column: var(--content-col);
}
.content {
@@ -60,6 +82,12 @@
z-index: 1;
}
+.meta {
+ @include mixins.type-micro;
+
+ color: var(--color-text-secondary);
+}
+
.actions {
display: flex;
diff --git a/app/javascript/mastodon/components/status/utils.ts b/app/javascript/mastodon/components/status/utils.ts
index dddb47be5ef..f69f266c89b 100644
--- a/app/javascript/mastodon/components/status/utils.ts
+++ b/app/javascript/mastodon/components/status/utils.ts
@@ -1,8 +1,8 @@
-import type { AccountStatusShape, StatusShape } from '@/mastodon/models/status';
+import type { AnyStatusShape } from '@/mastodon/models/status';
export function statusLink({
account,
id,
-}: Pick) {
+}: Pick) {
return `/@${typeof account === 'string' ? account : account.acct}/${id}`;
}
diff --git a/app/javascript/mastodon/features/compose/redesign/index.tsx b/app/javascript/mastodon/features/compose/redesign/index.tsx
index 3902ef68248..2ea9bac3ca6 100644
--- a/app/javascript/mastodon/features/compose/redesign/index.tsx
+++ b/app/javascript/mastodon/features/compose/redesign/index.tsx
@@ -99,6 +99,7 @@ export const RedesignComposeForm: React.FC<
)}
diff --git a/app/javascript/mastodon/features/compose/redesign/modal_switch.tsx b/app/javascript/mastodon/features/compose/redesign/modal_switch.tsx
index 1e3d87a8c00..d1230f7780d 100644
--- a/app/javascript/mastodon/features/compose/redesign/modal_switch.tsx
+++ b/app/javascript/mastodon/features/compose/redesign/modal_switch.tsx
@@ -45,6 +45,7 @@ const ComposerModalSwitch: React.FC = () => {
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index 790fbcacef1..bbee2e1646e 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -1203,6 +1203,7 @@
"privacy.change": "Change post privacy",
"privacy.direct.long": "Everyone mentioned in the post",
"privacy.direct.short": "Private mention",
+ "privacy.message.short": "Message",
"privacy.private.long": "Only your followers",
"privacy.private.short": "Followers",
"privacy.public.long": "Anyone on and off Mastodon",
@@ -1375,6 +1376,7 @@
"status.media.show": "Click to show",
"status.media_hidden": "Media hidden",
"status.mention": "Mention @{name}",
+ "status.meta": "{createdAt} on {source} • {visibility}",
"status.more": "More",
"status.mute": "Mute @{name}",
"status.mute_conversation": "Mute conversation",
diff --git a/app/javascript/mastodon/models/status.ts b/app/javascript/mastodon/models/status.ts
index fd6154424b2..ab07e104bad 100644
--- a/app/javascript/mastodon/models/status.ts
+++ b/app/javascript/mastodon/models/status.ts
@@ -93,6 +93,11 @@ export type ExpandedStatusShape = Omit & {
reblog?: Omit;
};
+export type AnyStatusShape =
+ | StatusShape
+ | AccountStatusShape
+ | ExpandedStatusShape;
+
export type CardShape = Omit & {
authors: (Omit & {
accountId?: string;