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 ( = ({ )} -
- {expanded && hashtagsInBar.length > 0 && ( - - )} + {showFooter && ( +
+ {expanded && hashtagsInBar.length > 0 && ( + + )} - {showActions && !isQuotedPost && ( - - )} -
+ {variant === 'page' && ( + + )} + + {showActions && !isQuotedPost && ( + + )} +
+ )}
); }; @@ -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;