mirror of
https://github.com/mastodon/mastodon.git
synced 2026-09-12 16:35:57 -05:00
Composer redesign: Mobile updates (#40346)
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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}}",
|
||||
|
||||
Reference in New Issue
Block a user