Redesign: Re-add navigation callouts for forced-single-column mode and moved account (#40589)

This commit is contained in:
diondiondion
2026-09-18 14:25:54 +00:00
committed by GitHub
parent ee699278b8
commit cdb26a40d4
6 changed files with 173 additions and 29 deletions

View File

@@ -3,6 +3,7 @@
.rootLarge,
.rootSmall {
align-items: center;
color: var(--color-text-primary);
background-color: var(--color-bg-highlight);
border-radius: var(--radius-3);
box-sizing: border-box;
@@ -20,6 +21,15 @@
.textWrapper {
flex-grow: 1;
a {
color: inherit;
&:hover,
&:focus-visible {
text-decoration: none;
}
}
}
.icon {

View File

@@ -19,9 +19,12 @@ import FediIcon from '@/images/icons/icon_fediverse.svg?react';
import { fetchLists } from '@/mastodon/actions/lists';
import { closeNavigation } from '@/mastodon/actions/navigation';
import { fetchFollowedHashtags } from '@/mastodon/actions/tags_typed';
import { Callout } from '@/mastodon/components/callout/redesign';
import { FOCUS_TARGET } from '@/mastodon/components/navigation_focus_target';
import { useScrollSensor } from '@/mastodon/hooks/useScrollSensor';
import { useIdentity } from '@/mastodon/identity_context';
import { disabledAccountId } from '@/mastodon/initial_state';
import { transientSingleColumn } from '@/mastodon/is_mobile';
import { openNewComposer } from '@/mastodon/reducers/slices/composer';
import { getOrderedLists } from '@/mastodon/selectors/lists';
import { selectUnreadNotificationGroupsCount } from '@/mastodon/selectors/notifications';
@@ -31,7 +34,7 @@ import { NavigationAccountCardAndMenu } from './account_card_and_menu';
import { NavigationFooterLinks } from './footer_links';
import { NavigationHeader } from './header';
import { ListSection } from './list_section';
import { LoggedOutInfo } from './logged_out_info';
import { DisabledAccountBanner, LoggedOutInfo } from './logged_out_info';
import { NavigationLink } from './navigation_link';
import classes from './styles.module.scss';
@@ -74,6 +77,13 @@ function useFollowedHashtags() {
return { followedHashtags: tags };
}
const isFediverseFeedsLinkActive = (
match: unknown,
{ pathname }: { pathname: string },
) => {
return !!match || pathname.startsWith('/public');
};
const MAX_HASHTAG_COUNT = 5;
export const RedesignNavigationPanel: React.FC<{
@@ -121,6 +131,7 @@ export const RedesignNavigationPanel: React.FC<{
<NavigationHeader siteName={siteName} isStuck={!isScrolledToTop} />
{signedIn && (
<>
{transientSingleColumn && <TransientSingleColumnCallout />}
<ul className={classes.list}>
<NavigationLink
withSpaceAfter
@@ -152,6 +163,7 @@ export const RedesignNavigationPanel: React.FC<{
withSpaceAfter
to='/public/local'
iconComponent={FediIcon}
isActive={isFediverseFeedsLinkActive}
>
<FormattedMessage
id='tabs_bar.fediverse_feeds'
@@ -281,7 +293,7 @@ export const RedesignNavigationPanel: React.FC<{
)}
{!signedIn && (
<footer className={classes.footer} data-stuck={!isScrolledToBottom}>
<LoggedOutInfo />
{disabledAccountId ? <DisabledAccountBanner /> : <LoggedOutInfo />}
<NavigationFooterLinks
multiColumn={multiColumn}
siteName={siteName}
@@ -292,3 +304,19 @@ export const RedesignNavigationPanel: React.FC<{
</nav>
);
};
const TransientSingleColumnCallout: React.FC = () => (
<Callout className={classes.callout}>
<FormattedMessage
id='navigation_bar.opened_in_single_column_layout'
defaultMessage='Posts, profiles, and other pages are opened in the single-column layout by default.'
/>
<br />
<a href={`/deck${location.pathname}`}>
<FormattedMessage
id='navigation_bar.advanced_interface'
defaultMessage='Open in advanced web interface'
/>
</a>
</Callout>
);

View File

@@ -1,4 +1,5 @@
@use '@/styles/mastodon/mixins';
@use '@/styles/mastodon/variables';
@use './navigation_link.module.scss' as navigation-link;
.root {
@@ -26,6 +27,10 @@
@include mixins.type-meta;
color: var(--color-text-secondary);
@media (width < variables.$mobile-menu-breakpoint) {
min-height: 40px;
}
}
.toggleIcon {

View File

@@ -1,14 +1,48 @@
import { useEffect } from 'react';
import { useCallback, useEffect } from 'react';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router-dom';
import { WarningIcon } from '@phosphor-icons/react';
import { openModal } from '@/mastodon/actions/modal';
import { fetchServer } from '@/mastodon/actions/server';
import { Button } from '@/mastodon/components/button/redesign';
import { Callout } from '@/mastodon/components/callout/redesign';
import { Skeleton } from '@/mastodon/components/skeleton';
import {
disabledAccountId,
domain,
movedToAccountId,
} from '@/mastodon/initial_state';
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
import classes from './logged_out_info.module.scss';
const NavigationFooterLayout: React.FC<{
isLoading?: boolean;
description: React.ReactNode;
children: React.ReactNode;
}> = ({ description, isLoading, children }) => (
<>
<div className={classes.description}>
{isLoading ? (
<>
<Skeleton width='100%' />
<br />
<Skeleton width='100%' />
<br />
<Skeleton width='70%' />
</>
) : (
description
)}
</div>
<div className={classes.buttons}>{children}</div>
</>
);
export const LoggedOutInfo: React.FC = () => {
const dispatch = useAppDispatch();
const { item: serverItem, isLoading } = useAppSelector(
@@ -20,31 +54,93 @@ export const LoggedOutInfo: React.FC = () => {
}, [dispatch]);
return (
<>
<p className={classes.description}>
{isLoading ? (
<>
<Skeleton width='100%' />
<br />
<Skeleton width='100%' />
<br />
<Skeleton width='70%' />
</>
) : (
serverItem?.description
)}
</p>
<div className={classes.buttons}>
<Button as='a' href='/auth/sign_up' variant='solid'>
<FormattedMessage
id='server_banner.create_account'
defaultMessage='Create an account'
/>
</Button>
<Button as='a' href='/auth/sign_in'>
<FormattedMessage id='server_banner.log_in' defaultMessage='Log in' />
</Button>
</div>
</>
<NavigationFooterLayout
description={<p>{serverItem?.description ?? ''}</p>}
isLoading={isLoading}
>
<Button as='a' href='/auth/sign_up' variant='solid'>
<FormattedMessage
id='server_banner.create_account'
defaultMessage='Create an account'
/>
</Button>
<Button as='a' href='/auth/sign_in'>
<FormattedMessage id='server_banner.log_in' defaultMessage='Log in' />
</Button>
</NavigationFooterLayout>
);
};
export const DisabledAccountBanner: React.FC = () => {
const disabledAccount = useAppSelector((state) =>
disabledAccountId ? state.accounts.get(disabledAccountId) : undefined,
);
const movedToAccount = useAppSelector((state) =>
movedToAccountId ? state.accounts.get(movedToAccountId) : undefined,
);
const dispatch = useAppDispatch();
const handleLogOutClick = useCallback(
(e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
dispatch(openModal({ modalType: 'CONFIRM_LOG_OUT', modalProps: {} }));
return false;
},
[dispatch],
);
const disabledAccountLink = (
<Link to={`/@${disabledAccount?.acct}`}>
{disabledAccount?.acct}@{domain}
</Link>
);
return (
<NavigationFooterLayout
description={
<Callout icon={WarningIcon}>
{movedToAccount ? (
<FormattedMessage
id='moved_to_account_banner.text'
defaultMessage='Your account {disabledAccount} is currently disabled because you moved to {movedToAccount}.'
values={{
disabledAccount: disabledAccountLink,
movedToAccount: (
<Link to={`/@${movedToAccount.acct}`}>
{movedToAccount.acct.includes('@')
? movedToAccount.acct
: `${movedToAccount.acct}@${domain}`}
</Link>
),
}}
/>
) : (
<FormattedMessage
id='disabled_account_banner.text'
defaultMessage='Your account {disabledAccount} is currently disabled.'
values={{
disabledAccount: disabledAccountLink,
}}
/>
)}
</Callout>
}
>
<Button as='a' href='/auth/edit' variant='solid'>
<FormattedMessage
id='disabled_account_banner.account_settings'
defaultMessage='Account settings'
/>
</Button>
<Button onClick={handleLogOutClick}>
<FormattedMessage
id='confirmations.logout.confirm'
defaultMessage='Log out'
/>
</Button>
</NavigationFooterLayout>
);
};

View File

@@ -54,3 +54,7 @@
gap: var(--space-1);
margin-bottom: var(--space-2);
}
.callout {
margin-inline: var(--space-4);
}

View File

@@ -1048,6 +1048,7 @@
"navigation_bar.more": "More",
"navigation_bar.mutes": "Muted users",
"navigation_bar.opened_in_classic_interface": "Posts, accounts, and other specific pages are opened by default in the classic web interface.",
"navigation_bar.opened_in_single_column_layout": "Posts, profiles, and other pages are opened in the single-column layout by default.",
"navigation_bar.powered_by_mastodon": "powered by {logo}Mastodon",
"navigation_bar.preferences": "Preferences",
"navigation_bar.privacy_and_reach": "Privacy and reach",