From fd5bcf6189dd9d61f1b820b57bb6c8b06d58938a Mon Sep 17 00:00:00 2001 From: Echo Date: Thu, 3 Sep 2026 10:04:22 +0000 Subject: [PATCH] Composer redesign: Quotes (#40364) Co-authored-by: diondiondion --- .../components/card/styles.module.scss | 7 +- .../mastodon/components/emoji/html.tsx | 62 ++-- app/javascript/mastodon/components/icon.tsx | 3 + .../compose/redesign/attachments.module.scss | 59 +++- .../features/compose/redesign/poll.tsx | 7 +- .../features/compose/redesign/quote.tsx | 276 +++++++++++++++--- .../features/compose/redesign/upload.tsx | 83 ++++-- app/javascript/mastodon/locales/en.json | 2 + 8 files changed, 404 insertions(+), 95 deletions(-) diff --git a/app/javascript/mastodon/components/card/styles.module.scss b/app/javascript/mastodon/components/card/styles.module.scss index d22032836b9..020d8ae5fae 100644 --- a/app/javascript/mastodon/components/card/styles.module.scss +++ b/app/javascript/mastodon/components/card/styles.module.scss @@ -39,7 +39,6 @@ a.root { color: var(--color-text-secondary); display: flex; - margin-bottom: var(--space-2xs); a { color: inherit; @@ -66,6 +65,7 @@ a.root { overflow-wrap: break-word; flex-grow: 1; + margin-block-start: var(--space-xs); a { color: var(--color-text-brand); @@ -80,9 +80,8 @@ a.root { // Linked body a.body { - padding: 0 var(--space-sm) var(--space-sm); - margin: calc(-1 * var(--space-sm)); - margin-top: 0; + padding-inline: var(--space-sm); + margin-inline: calc(-1 * var(--space-sm)); .root:has(&:hover) { background-color: var(--color-bg-highlight); diff --git a/app/javascript/mastodon/components/emoji/html.tsx b/app/javascript/mastodon/components/emoji/html.tsx index a9f2b64e337..1f392548de0 100644 --- a/app/javascript/mastodon/components/emoji/html.tsx +++ b/app/javascript/mastodon/components/emoji/html.tsx @@ -6,38 +6,50 @@ import type { OnElementHandler, } from '@/mastodon/utils/html'; import { htmlStringToComponents } from '@/mastodon/utils/html'; -import { polymorphicForwardRef } from '@/types/polymorphic'; +import type { PolymorphicProps } from '@/types/polymorphic'; import { AnimateEmojiProvider, CustomEmojiProvider } from './context'; import { textToEmojis } from './index'; -export interface EmojiHTMLProps { +export interface EmojiHTMLProps< + Arg extends Record = Record, +> { htmlString: string; extraEmojis?: CustomEmojiMapArg; className?: string; - onElement?: OnElementHandler; - onAttribute?: OnAttributeHandler; + onElement?: OnElementHandler; + onAttribute?: OnAttributeHandler; + extraArgs?: Arg; } -export const EmojiHTML = polymorphicForwardRef<'div', EmojiHTMLProps>( - ({ extraEmojis, htmlString, onElement, onAttribute, ...props }, ref) => { - const contents = useMemo( - () => - htmlStringToComponents(htmlString, { - onText: textToEmojis, - onElement, - onAttribute, - }), - [htmlString, onAttribute, onElement], - ); +export const EmojiHTML = < + As extends React.ElementType = 'div', + Arg extends Record = Record, +>({ + extraEmojis, + htmlString, + onElement, + onAttribute, + extraArgs, + ref, + ...props +}: PolymorphicProps, As>) => { + const contents = useMemo( + () => + htmlStringToComponents(htmlString, { + onText: textToEmojis, + onElement, + onAttribute, + extraArgs, + }), + [extraArgs, htmlString, onAttribute, onElement], + ); - return ( - - - {contents} - - - ); - }, -); -EmojiHTML.displayName = 'EmojiHTML'; + return ( + + + {contents} + + + ); +}; diff --git a/app/javascript/mastodon/components/icon.tsx b/app/javascript/mastodon/components/icon.tsx index 688a5a5781f..0a801e7af7d 100644 --- a/app/javascript/mastodon/components/icon.tsx +++ b/app/javascript/mastodon/components/icon.tsx @@ -1,5 +1,7 @@ import classNames from 'classnames'; +import type { IconWeight } from '@phosphor-icons/react'; + import CheckBoxOutlineBlankIcon from '@/material-icons/400-24px/check_box_outline_blank.svg?react'; import { isProduction } from 'mastodon/utils/environment'; @@ -14,6 +16,7 @@ interface Props extends React.SVGProps { id?: string; icon: IconProp; noFill?: boolean; + weight?: IconWeight; } export const Icon: React.FC = ({ diff --git a/app/javascript/mastodon/features/compose/redesign/attachments.module.scss b/app/javascript/mastodon/features/compose/redesign/attachments.module.scss index cb512e0d3f4..c51162fc615 100644 --- a/app/javascript/mastodon/features/compose/redesign/attachments.module.scss +++ b/app/javascript/mastodon/features/compose/redesign/attachments.module.scss @@ -6,6 +6,7 @@ min-width: 120px; max-width: min(100%, 400px); max-height: var(--max-media-height-large); + aspect-ratio: var(--aspect); } .mediaGrid { @@ -168,11 +169,6 @@ // Quotes -.quoteBody { - color: inherit; - text-decoration: none; -} - // If hovering over an account link, underline all account links. .quoteTitle:has(.quoteAccountLink:hover) { .quoteAccountLink { @@ -186,3 +182,56 @@ margin: var(--space-2xs) 0; padding: var(--space-xs); } + +.quoteBody { + color: inherit; + text-decoration: none; + + // If a body only has an empty div (such as when quoting just a link), just hide it. + &:has(> div:only-child:empty) { + display: none; + } + + strong { + font-weight: bold; + } +} + +.quoteReply { + color: var(--color-text-tertiary); + margin-bottom: var(--space-xs); +} + +.quoteMedia { + margin-top: var(--space-2xs); + + &.mediaGrid { + height: 260px; + } + + .mediaUpload { + max-height: 260px; + border-radius: var(--radius-xs); + } +} + +.quoteVideoDuration { + @include mixins.type-label-md; + + display: flex; + gap: var(--space-2xs); + align-items: center; + position: absolute; + inset-block-end: var(--space-xs); + inset-inline-start: var(--space-xs); + padding: var(--space-2xs) var(--space-3xs); + background: var(--color-bg-highlight); + border-radius: var(--radius-xs); + color: var(--color-text-primary); + + svg { + width: 16px; + height: 16px; + opacity: 0.7; + } +} diff --git a/app/javascript/mastodon/features/compose/redesign/poll.tsx b/app/javascript/mastodon/features/compose/redesign/poll.tsx index 6c6163ac3ec..b82dec1a4b2 100644 --- a/app/javascript/mastodon/features/compose/redesign/poll.tsx +++ b/app/javascript/mastodon/features/compose/redesign/poll.tsx @@ -113,7 +113,10 @@ export const ComposePoll: React.FC = () => { useCallback( (event) => { dispatch( - changePollSettings(Number.parseInt(event.target.value), multiple), + changePollSettings( + Number.parseInt(event.target.value) / 1000, + multiple, + ), ); }, [dispatch, multiple], @@ -189,7 +192,7 @@ export const ComposePoll: React.FC = () => { {pollDurationOptions.map((duration) => { const { message, multiplier } = durationToMessage(duration); return ( -