Composer redesign: Mobile updates (#40346)

This commit is contained in:
Echo
2026-09-01 17:21:57 +02:00
committed by GitHub
parent 3bd8929d50
commit eb03e214b3
6 changed files with 78 additions and 26 deletions

View File

@@ -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 (
<IconButton icon={ReadCvLogoIcon} onClick={onMinimize} size='lg'>
<FormattedMessage id='compose.expand' defaultMessage='Show composer' />
</IconButton>
);
}
return (
<header className={classes.header}>
<h2 id={id}>{intl.formatMessage(titleMessage)}</h2>

View File

@@ -52,12 +52,9 @@ interface RedesignComposeFormProps {
redirectOnSuccess?: boolean;
}
export const RedesignComposeForm: React.FC<RedesignComposeFormProps> = ({
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<RedesignComposeFormProps> = ({
return (
<form
{...props}
role='dialog'
onSubmit={onSubmit}
aria-labelledby={titleId}

View File

@@ -1,19 +1,21 @@
@use '@/styles/mastodon/mixins';
@use '@/styles/mastodon/variables';
.root {
@include mixins.elevation-2;
container-type: inline-size;
border-radius: var(--radius-xl);
background: var(--color-bg-primary);
display: flex;
flex-direction: column;
gap: var(--space-md);
padding: var(--space-md);
min-width: 300px;
position: relative;
overflow: hidden;
z-index: 1; // To cover the sidebar
@media (width >= #{variables.$mobile-menu-breakpoint}) {
@include mixins.elevation-2;
border-radius: var(--radius-xl);
overflow: hidden;
}
}
.background {

View File

@@ -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);
}
}

View File

@@ -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 | number>(null);
useEffect(() => {
const updateHeight = () => {
setViewportHeight(visualViewport?.height ?? null);
};
visualViewport?.addEventListener('resize', updateHeight);
return () => {
visualViewport?.removeEventListener('resize', updateHeight);
};
}, []);
const dispatch = useAppDispatch();
const handleComposerOpen: React.MouseEventHandler<HTMLButtonElement> =
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 (
<Suspense fallback={<CircularProgress strokeWidth={2} size={50} />}>
<ComposeLazyForm autoFocus className={classes.composer} />
<ComposeLazyForm autoFocus className={classes.composer} style={style} />
</Suspense>
);
}

View File

@@ -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}}",