From eb03e214b34062f662dc843c6d8dbd5a5eeaa278 Mon Sep 17 00:00:00 2001 From: Echo Date: Tue, 1 Sep 2026 17:21:57 +0200 Subject: [PATCH] Composer redesign: Mobile updates (#40346) --- .../features/compose/redesign/header.tsx | 13 ++++++ .../features/compose/redesign/index.tsx | 10 ++--- .../compose/redesign/styles.module.scss | 14 ++++--- .../compose/redesign/trigger.module.scss | 42 ++++++++++++++----- .../features/compose/redesign/trigger.tsx | 23 ++++++++-- app/javascript/mastodon/locales/en.json | 2 +- 6 files changed, 78 insertions(+), 26 deletions(-) diff --git a/app/javascript/mastodon/features/compose/redesign/header.tsx b/app/javascript/mastodon/features/compose/redesign/header.tsx index 7322978dfef..3ea207d2b6b 100644 --- a/app/javascript/mastodon/features/compose/redesign/header.tsx +++ b/app/javascript/mastodon/features/compose/redesign/header.tsx @@ -3,6 +3,7 @@ import { useCallback } from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { ArrowsOutSimpleIcon, MinusIcon, XIcon } from '@phosphor-icons/react'; +import { ReadCvLogoIcon } from '@phosphor-icons/react/dist/ssr'; import { IconButton } from '@/mastodon/components/button/redesign'; import { @@ -16,6 +17,8 @@ import { useAppSelector, } from '@/mastodon/store'; +import { useBreakpoint } from '../../ui/hooks/useBreakpoint'; + import { selectComposeType } from './selectors'; import classes from './styles.module.scss'; @@ -73,6 +76,16 @@ export const ComposeFormHeader: React.FC<{ dispatch(minimizeComposerToggle()); }, [dispatch]); + const isMobile = useBreakpoint('openable'); + + if (isMobile && isMinimized && !noMinimize) { + return ( + + + + ); + } + return (

{intl.formatMessage(titleMessage)}

diff --git a/app/javascript/mastodon/features/compose/redesign/index.tsx b/app/javascript/mastodon/features/compose/redesign/index.tsx index c505069eb3c..3902ef68248 100644 --- a/app/javascript/mastodon/features/compose/redesign/index.tsx +++ b/app/javascript/mastodon/features/compose/redesign/index.tsx @@ -52,12 +52,9 @@ interface RedesignComposeFormProps { redirectOnSuccess?: boolean; } -export const RedesignComposeForm: React.FC = ({ - autoFocus, - className, - noMinimize, - redirectOnSuccess, -}) => { +export const RedesignComposeForm: React.FC< + RedesignComposeFormProps & React.ComponentPropsWithRef<'form'> +> = ({ autoFocus, className, noMinimize, redirectOnSuccess, ...props }) => { const type = useAppSelector(selectComposeType); const { sensitive, sensitiveText } = useAppSelector(selectComposeSensitive); @@ -69,6 +66,7 @@ export const RedesignComposeForm: React.FC = ({ return (
= #{variables.$mobile-menu-breakpoint}) { + @include mixins.elevation-2; + + border-radius: var(--radius-xl); + overflow: hidden; + } } .background { diff --git a/app/javascript/mastodon/features/compose/redesign/trigger.module.scss b/app/javascript/mastodon/features/compose/redesign/trigger.module.scss index 7cccba24a64..a98d21d95d4 100644 --- a/app/javascript/mastodon/features/compose/redesign/trigger.module.scss +++ b/app/javascript/mastodon/features/compose/redesign/trigger.module.scss @@ -12,28 +12,50 @@ .composer, .composerMinimized { position: fixed; - bottom: 0; right: 0; - margin: var(--space-md); box-sizing: border-box; - border-radius: var(--radius-xl); } .composer { - width: calc(100vw - var(--space-md) * 2); - max-width: 500px; - min-height: 520px; + width: 100%; + top: env(safe-area-inset-top); + + // Set from the visualViewport API. + height: var(--viewport-height, 80vh); max-height: 80vh; + transition: height 200ms; + box-shadow: 0 2px var(--color-border-strong); + + @media (width >= #{variables.$mobile-menu-breakpoint}) { + top: auto; + bottom: 0; + min-height: 520px; + height: auto; + min-width: 300px; + width: calc(100vw - var(--space-md) * 2); + max-width: 500px; + margin: var(--space-md); + } +} + +// When in the mobile view and the composer is open, disable scroll on the body element. +// This prevents scrolling the textarea accidentally hitting the body instead. +@media (width < #{variables.$mobile-menu-breakpoint}) { + body:has(.composer) { + overflow-y: hidden !important; + } } .composerMinimized { + bottom: 0; width: min(320px, calc(100vw - var(--space-md) * 2)); padding: var(--space-md); -} + margin: var(--space-md); -@media (width < #{variables.$mobile-menu-breakpoint}) { - .composer, - .composerMinimized { + @media (width < #{variables.$mobile-menu-breakpoint}) { bottom: var(--mobile-bottom-nav-height); + width: auto; + padding: 0; + border-radius: var(--radius-round); } } diff --git a/app/javascript/mastodon/features/compose/redesign/trigger.tsx b/app/javascript/mastodon/features/compose/redesign/trigger.tsx index f53c98038fa..3e4b835c149 100644 --- a/app/javascript/mastodon/features/compose/redesign/trigger.tsx +++ b/app/javascript/mastodon/features/compose/redesign/trigger.tsx @@ -1,6 +1,5 @@ /* eslint-disable jsx-a11y/no-autofocus */ -import type React from 'react'; -import { lazy, Suspense, useCallback } from 'react'; +import { lazy, Suspense, useCallback, useEffect, useState } from 'react'; import { FormattedMessage } from 'react-intl'; @@ -42,6 +41,20 @@ export const ComposeRedesignButton: React.FC<{ }> = ({ inline }) => { const displayState = useAppSelector((state) => state.composer.displayState); + // Update viewport based on visual size in order to account for the virtual keyboard. + const [viewportHeight, setViewportHeight] = useState(null); + useEffect(() => { + const updateHeight = () => { + setViewportHeight(visualViewport?.height ?? null); + }; + + visualViewport?.addEventListener('resize', updateHeight); + + return () => { + visualViewport?.removeEventListener('resize', updateHeight); + }; + }, []); + const dispatch = useAppDispatch(); const handleComposerOpen: React.MouseEventHandler = useCallback( @@ -69,9 +82,13 @@ export const ComposeRedesignButton: React.FC<{ } if (displayState === 'showing') { + // Pass the viewport height as a CSS variable so it's only used for mobile. + const style = { + '--viewport-height': viewportHeight ? `${viewportHeight}px` : undefined, + } as React.CSSProperties; return ( }> - + ); } diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index d81c0977a84..dc3cb77cfa3 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -515,7 +515,7 @@ "compose.new.post": "Post", "compose.poll.add": "Add another option", "compose.poll.delete": "Delete poll", - "compose.poll.duration": "Duration: {button}", + "compose.poll.duration": "Duration:", "compose.poll.multiple": "Allow multiple selections", "compose.post.placeholder": "What would you like to say?", "compose.post.privacy.followers": "Followers {count, plural, =0 {} one {+ # other} other {+ # others}}",