From 87de690f9b250a40f66930cbe4e295b02b530a01 Mon Sep 17 00:00:00 2001 From: diondiondion Date: Fri, 25 Sep 2026 12:01:30 +0000 Subject: [PATCH] Redesign: Hide mobile compose button on pages that aren't primarily post feeds (#40698) --- .../features/compose/redesign/header.tsx | 16 +-- .../compose/redesign/trigger.module.scss | 16 ++- .../features/compose/redesign/trigger.tsx | 108 +++++++++++++++--- .../navigation_panel/redesign/mobile_nav.tsx | 1 + 4 files changed, 110 insertions(+), 31 deletions(-) diff --git a/app/javascript/mastodon/features/compose/redesign/header.tsx b/app/javascript/mastodon/features/compose/redesign/header.tsx index aab54ebd41d..5eb27639973 100644 --- a/app/javascript/mastodon/features/compose/redesign/header.tsx +++ b/app/javascript/mastodon/features/compose/redesign/header.tsx @@ -3,7 +3,6 @@ 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 { @@ -17,8 +16,6 @@ import { useAppSelector, } from '@/mastodon/store'; -import { useBreakpoint } from '../../ui/hooks/useBreakpoint'; - import { selectComposeType } from './selectors'; import classes from './styles.module.scss'; @@ -73,27 +70,18 @@ export const ComposeFormHeader: React.FC<{ noMinimize?: boolean; }> = ({ id, noMinimize }) => { const intl = useIntl(); + const dispatch = useAppDispatch(); const titleMessage = useAppSelector(selectComposeFormTitle); const isMinimized = useAppSelector(selectIsMinimized); - const dispatch = useAppDispatch(); const onClose = useCallback(() => { dispatch(closeComposer()); }, [dispatch]); + const onMinimize = useCallback(() => { 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/trigger.module.scss b/app/javascript/mastodon/features/compose/redesign/trigger.module.scss index 2ed05078571..61045f66ab2 100644 --- a/app/javascript/mastodon/features/compose/redesign/trigger.module.scss +++ b/app/javascript/mastodon/features/compose/redesign/trigger.module.scss @@ -1,7 +1,13 @@ @use '@/styles/mastodon/variables'; -.button { - &:not(.buttonInline) { +.buttonWrapper { + position: relative; + + @media (prefers-reduced-motion: no-preference) { + transition: all 200ms ease-out; + } + + &:not(.buttonWrapperInline) { position: fixed; bottom: var(--space-4); right: var(--space-4); @@ -9,6 +15,12 @@ } } +.buttonWrapperHidden { + transform: translateX(calc(100% + var(--space-4))); + opacity: 0; + transition-timing-function: ease-in; +} + .composer, .composerMinimized { position: fixed; diff --git a/app/javascript/mastodon/features/compose/redesign/trigger.tsx b/app/javascript/mastodon/features/compose/redesign/trigger.tsx index 4764093a48e..0e99a16b7fc 100644 --- a/app/javascript/mastodon/features/compose/redesign/trigger.tsx +++ b/app/javascript/mastodon/features/compose/redesign/trigger.tsx @@ -4,13 +4,16 @@ import { lazy, Suspense, useCallback, useEffect, useState } from 'react'; import { FormattedMessage } from 'react-intl'; import classNames from 'classnames'; +import { useRouteMatch } from 'react-router'; import { ChatCircleDotsIcon, NewspaperIcon, PenNibIcon, + ReadCvLogoIcon, } from '@phosphor-icons/react'; +import type { IconButtonProps } from '@/mastodon/components/button/redesign'; import { IconButton } from '@/mastodon/components/button/redesign'; import { Menu, @@ -20,10 +23,15 @@ import { } from '@/mastodon/components/menu'; import { MenuCard } from '@/mastodon/components/menu/card'; import { useIdentity } from '@/mastodon/identity_context'; -import { openNewComposer } from '@/mastodon/reducers/slices/composer'; +import { + minimizeComposerToggle, + openNewComposer, +} from '@/mastodon/reducers/slices/composer'; import { useAppDispatch, useAppSelector } from '@/mastodon/store'; import { isRedesignEnabled } from '@/mastodon/utils/environment'; +import { useBreakpoint } from '../../ui/hooks/useBreakpoint'; + import { ComposeFormHeader } from './header'; import classes from './trigger.module.scss'; @@ -38,8 +46,13 @@ export const ComposeRedesignButton: React.FC<{ * Render the button in regular document flow instead of fixed positioning for mobile layout */ inline?: boolean; -}> = ({ inline }) => { +}> = ({ inline = false }) => { const displayState = useAppSelector((state) => state.composer.displayState); + const isMobile = useBreakpoint('openable'); + + const hasMobileFloatingActionButton = useHasMobileFloatingActionButton({ + isMobile, + }); // Update viewport based on visual size in order to account for the virtual keyboard. const [viewportHeight, setViewportHeight] = useState(null); @@ -69,14 +82,32 @@ export const ComposeRedesignButton: React.FC<{ [dispatch], ); + const toggleMinimize = useCallback(() => { + dispatch(minimizeComposerToggle()); + }, [dispatch]); + const { signedIn } = useIdentity(); if (!isRedesignEnabled() || !signedIn) { return null; } + const floatingButtonProps = { + inline, + hidden: !hasMobileFloatingActionButton, + } as const; + if (displayState === 'minimized') { - return ( + return isMobile ? ( + + ) : ( @@ -91,21 +122,16 @@ export const ComposeRedesignButton: React.FC<{ return ( - + } > @@ -116,11 +142,9 @@ export const ComposeRedesignButton: React.FC<{ return ( ); }; + +const FloatingActionButton: React.FC< + { + inline: boolean; + hidden: boolean; + } & IconButtonProps +> = ({ inline, hidden, ...otherProps }) => { + return ( + // This component uses a wrapper element to prevent its + // CSS transitions from messing with the button's own transitions + + ); +}; + +function includeMultiColumnPaths(paths: string[]) { + return [...paths, ...paths.map((path) => `/deck${path}`)]; +} + +const MOBILE_COMPOSE_BUTTON_ALLOW_ROUTES = includeMultiColumnPaths([ + '/home', + '/public', + '/lists', + '/tags', +]); +const MOBILE_COMPOSE_BUTTON_BLOCK_ROUTES = includeMultiColumnPaths([ + '/lists/new', +]); + +function useHasMobileFloatingActionButton({ isMobile }: { isMobile: boolean }) { + const isRouteWithMobileComposeButton = !!useRouteMatch({ + path: MOBILE_COMPOSE_BUTTON_ALLOW_ROUTES, + exact: false, + }); + + const isRouteWithoutMobileComposeButton = !!useRouteMatch({ + path: MOBILE_COMPOSE_BUTTON_BLOCK_ROUTES, + exact: true, + }); + + const shouldHideMobileComposeButton = + isMobile && + (!isRouteWithMobileComposeButton || isRouteWithoutMobileComposeButton); + + return !shouldHideMobileComposeButton; +} diff --git a/app/javascript/mastodon/features/navigation_panel/redesign/mobile_nav.tsx b/app/javascript/mastodon/features/navigation_panel/redesign/mobile_nav.tsx index 815eeda48a8..f1256e2637c 100644 --- a/app/javascript/mastodon/features/navigation_panel/redesign/mobile_nav.tsx +++ b/app/javascript/mastodon/features/navigation_panel/redesign/mobile_nav.tsx @@ -247,6 +247,7 @@ const SlideOutNavigation: React.FC = () => { className={classes.slideOutWrapper} data-is-open={isOpen} ref={overlayRef} + inert={!isOpen} >