mirror of
https://github.com/mastodon/mastodon.git
synced 2026-09-12 00:46:13 -05:00
Redesign: Change mobile account menu (#40428)
This commit is contained in:
@@ -22,3 +22,10 @@
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
|
||||
.profileMenuItem {
|
||||
--list-item-padding: 0;
|
||||
--list-item-gap: 8px;
|
||||
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
GearIcon,
|
||||
CirclesFourIcon,
|
||||
HeartIcon,
|
||||
BookmarkSimpleIcon,
|
||||
UsersThreeIcon,
|
||||
ProhibitIcon,
|
||||
GavelIcon,
|
||||
@@ -17,7 +18,14 @@ import {
|
||||
|
||||
import { openModal } from '@/mastodon/actions/modal';
|
||||
import { Account } from '@/mastodon/components/account';
|
||||
import { Avatar } from '@/mastodon/components/avatar';
|
||||
import { IconButton } from '@/mastodon/components/button/redesign';
|
||||
import { DisplayName } from '@/mastodon/components/display_name';
|
||||
import { useAccountHandle } from '@/mastodon/components/display_name/default';
|
||||
import {
|
||||
ListItemContent,
|
||||
ListItemWrapper,
|
||||
} from '@/mastodon/components/list_item';
|
||||
import {
|
||||
Menu,
|
||||
MenuItem,
|
||||
@@ -37,6 +45,44 @@ import { useAppDispatch } from '@/mastodon/store';
|
||||
import classes from './account_card_and_menu.module.scss';
|
||||
|
||||
export const NavigationAccountCardAndMenu: React.FC = () => {
|
||||
const { accountId } = useIdentity();
|
||||
|
||||
if (!accountId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<Account
|
||||
id={accountId}
|
||||
minimal
|
||||
withBorder={false}
|
||||
withMenu={false}
|
||||
size={32}
|
||||
/>
|
||||
<Menu type='navigation'>
|
||||
<MenuTrigger
|
||||
as={IconButton}
|
||||
icon={DotsThreeIcon}
|
||||
variant='ghost'
|
||||
size='sm'
|
||||
>
|
||||
<FormattedMessage
|
||||
id='tabs_bar.account_settings'
|
||||
defaultMessage='Account settings'
|
||||
/>
|
||||
</MenuTrigger>
|
||||
<MenuList placement='top' offset={8}>
|
||||
<AccountMenuItems />
|
||||
</MenuList>
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const AccountMenuItems: React.FC<{
|
||||
context?: 'default' | 'mobile';
|
||||
}> = ({ context = 'default' }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { accountId, permissions } = useIdentity();
|
||||
const account = useAccount(accountId);
|
||||
@@ -55,109 +101,121 @@ export const NavigationAccountCardAndMenu: React.FC = () => {
|
||||
const accountBasePath = `/@${account?.acct}`;
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<Account
|
||||
id={accountId}
|
||||
minimal
|
||||
withBorder={false}
|
||||
withMenu={false}
|
||||
size={32}
|
||||
/>
|
||||
<Menu type='navigation'>
|
||||
<MenuTrigger
|
||||
as={IconButton}
|
||||
icon={DotsThreeIcon}
|
||||
variant='ghost'
|
||||
size='sm'
|
||||
>
|
||||
<FormattedMessage id='tabs_bar.more' defaultMessage='More' />
|
||||
</MenuTrigger>
|
||||
<MenuList placement='top' offset={8}>
|
||||
<MenuItemLink to='/profile/edit' icon={UserIcon}>
|
||||
<FormattedMessage
|
||||
id='account.edit_profile'
|
||||
defaultMessage='Edit profile'
|
||||
/>
|
||||
</MenuItemLink>
|
||||
<MenuItemLink as='a' href='/settings/preferences' icon={GearIcon}>
|
||||
<FormattedMessage
|
||||
id='tabs_bar.settings'
|
||||
defaultMessage='Settings'
|
||||
/>
|
||||
</MenuItemLink>
|
||||
<>
|
||||
{context === 'mobile' && <ProfileMenuItem />}
|
||||
|
||||
<MenuItemLink to='/profile/edit' icon={UserIcon}>
|
||||
<FormattedMessage
|
||||
id='account.edit_profile'
|
||||
defaultMessage='Edit profile'
|
||||
/>
|
||||
</MenuItemLink>
|
||||
|
||||
<MenuItemLink as='a' href='/settings/preferences' icon={GearIcon}>
|
||||
<FormattedMessage id='tabs_bar.settings' defaultMessage='Settings' />
|
||||
</MenuItemLink>
|
||||
|
||||
<MenuItemDivider />
|
||||
|
||||
<MenuItemLink
|
||||
to={`${accountBasePath}/collections`}
|
||||
icon={CirclesFourIcon}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='navigation_bar.collections'
|
||||
defaultMessage='Collections'
|
||||
/>
|
||||
</MenuItemLink>
|
||||
|
||||
<MenuItemLink to='/favourites' icon={HeartIcon}>
|
||||
<FormattedMessage
|
||||
id='navigation_bar.liked_posts'
|
||||
defaultMessage='Liked Posts'
|
||||
/>
|
||||
</MenuItemLink>
|
||||
|
||||
{context === 'mobile' && (
|
||||
<MenuItemLink to='/bookmarks' icon={BookmarkSimpleIcon}>
|
||||
<FormattedMessage
|
||||
id='navigation_bar.saved_posts'
|
||||
defaultMessage='Saved Posts'
|
||||
/>
|
||||
</MenuItemLink>
|
||||
)}
|
||||
|
||||
<MenuItemDivider />
|
||||
|
||||
<MenuItemLink as='a' href='/relationships' icon={UsersThreeIcon}>
|
||||
<FormattedMessage
|
||||
id='navigation_bar.followers_and_following'
|
||||
defaultMessage='Followers & Following'
|
||||
/>
|
||||
</MenuItemLink>
|
||||
|
||||
<MenuItemLink to='/blocks' icon={ProhibitIcon}>
|
||||
<FormattedMessage
|
||||
id='navigation_bar.blocked_accounts'
|
||||
defaultMessage='Blocked accounts'
|
||||
/>
|
||||
</MenuItemLink>
|
||||
|
||||
{(isManager || isAdmin) && (
|
||||
<>
|
||||
<MenuItemDivider />
|
||||
|
||||
<MenuItemLink
|
||||
to={`${accountBasePath}/collections`}
|
||||
icon={CirclesFourIcon}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='navigation_bar.collections'
|
||||
defaultMessage='Collections'
|
||||
/>
|
||||
</MenuItemLink>
|
||||
<MenuItemLink to='/favourites' icon={HeartIcon}>
|
||||
<FormattedMessage
|
||||
id='navigation_bar.liked_posts'
|
||||
defaultMessage='Liked Posts'
|
||||
/>
|
||||
</MenuItemLink>
|
||||
|
||||
<MenuItemDivider />
|
||||
|
||||
<MenuItemLink as='a' href='/relationships' icon={UsersThreeIcon}>
|
||||
<FormattedMessage
|
||||
id='navigation_bar.followers_and_following'
|
||||
defaultMessage='Followers & Following'
|
||||
/>
|
||||
</MenuItemLink>
|
||||
|
||||
<MenuItemLink to='/blocks' icon={ProhibitIcon}>
|
||||
<FormattedMessage
|
||||
id='navigation_bar.blocked_accounts'
|
||||
defaultMessage='Blocked accounts'
|
||||
/>
|
||||
</MenuItemLink>
|
||||
|
||||
{(isManager || isAdmin) && (
|
||||
<>
|
||||
<MenuItemDivider />
|
||||
|
||||
{isAdmin && (
|
||||
<MenuItemLink as='a' href='/admin/dashboard' icon={GavelIcon}>
|
||||
<FormattedMessage
|
||||
id='navigation_bar.administration'
|
||||
defaultMessage='Administration'
|
||||
/>
|
||||
</MenuItemLink>
|
||||
)}
|
||||
|
||||
{isManager && (
|
||||
<MenuItemLink
|
||||
as='a'
|
||||
href='/admin/reports'
|
||||
icon={ShieldStarIcon}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='navigation_bar.moderation'
|
||||
defaultMessage='Moderation'
|
||||
/>
|
||||
</MenuItemLink>
|
||||
)}
|
||||
</>
|
||||
{isAdmin && (
|
||||
<MenuItemLink as='a' href='/admin/dashboard' icon={GavelIcon}>
|
||||
<FormattedMessage
|
||||
id='navigation_bar.administration'
|
||||
defaultMessage='Administration'
|
||||
/>
|
||||
</MenuItemLink>
|
||||
)}
|
||||
|
||||
<MenuItemDivider />
|
||||
{isManager && (
|
||||
<MenuItemLink as='a' href='/admin/reports' icon={ShieldStarIcon}>
|
||||
<FormattedMessage
|
||||
id='navigation_bar.moderation'
|
||||
defaultMessage='Moderation'
|
||||
/>
|
||||
</MenuItemLink>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
<MenuItem onClick={confirmLogout} icon={SignOutIcon}>
|
||||
<FormattedMessage
|
||||
id='navigation_bar.sign_out'
|
||||
defaultMessage='Sign out'
|
||||
/>
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
</div>
|
||||
<MenuItemDivider />
|
||||
|
||||
<MenuItem onClick={confirmLogout} icon={SignOutIcon}>
|
||||
<FormattedMessage
|
||||
id='navigation_bar.sign_out'
|
||||
defaultMessage='Sign out'
|
||||
/>
|
||||
</MenuItem>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const ProfileMenuItem: React.FC = () => {
|
||||
const { accountId } = useIdentity();
|
||||
const account = useAccount(accountId);
|
||||
const handle = useAccountHandle(account);
|
||||
|
||||
if (!accountId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const accountBasePath = `/@${account?.acct}`;
|
||||
|
||||
return (
|
||||
<MenuItemLink to={accountBasePath}>
|
||||
<ListItemWrapper
|
||||
icon={<Avatar account={account} size={40} />}
|
||||
className={classes.profileMenuItem}
|
||||
>
|
||||
<ListItemContent subtitle={handle}>
|
||||
<DisplayName variant='simple' account={account} />
|
||||
</ListItemContent>
|
||||
</ListItemWrapper>
|
||||
</MenuItemLink>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -72,6 +72,10 @@ function useFollowedHashtags() {
|
||||
|
||||
export const RedesignNavigationPanel: React.FC<{
|
||||
siteName?: string;
|
||||
/**
|
||||
* In 'slide-out' mode (used on smaller viewport sizes), some
|
||||
* menu items are hidden and the design is tweaked slightly
|
||||
*/
|
||||
mode?: 'static' | 'slide-out';
|
||||
}> = ({ siteName, mode = 'static' }) => {
|
||||
const intl = useIntl();
|
||||
@@ -204,38 +208,45 @@ export const RedesignNavigationPanel: React.FC<{
|
||||
)}
|
||||
</ul>
|
||||
<footer className={classes.footer} data-stuck={!isScrolledToBottom}>
|
||||
<ul className={classes.footerNav}>
|
||||
<NavigationLink
|
||||
stacked
|
||||
to='/notifications'
|
||||
iconComponent={BellIcon}
|
||||
badgeCount={notificationsCount}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='tabs_bar.notifications'
|
||||
defaultMessage='Notifications'
|
||||
/>
|
||||
</NavigationLink>
|
||||
<NavigationLink
|
||||
stacked
|
||||
to='/conversations'
|
||||
iconComponent={ChatCircleIcon}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='tabs_bar.messages'
|
||||
defaultMessage='Messages'
|
||||
description='Message refers to a direct message. For languages where this is confusing, "chat" or "direct message" can be used.'
|
||||
/>
|
||||
</NavigationLink>
|
||||
<NavigationLink
|
||||
stacked
|
||||
to='/bookmarks'
|
||||
iconComponent={BookmarkSimpleIcon}
|
||||
>
|
||||
<FormattedMessage id='tabs_bar.saved' defaultMessage='Saved' />
|
||||
</NavigationLink>
|
||||
</ul>
|
||||
<NavigationAccountCardAndMenu />
|
||||
{mode !== 'slide-out' && (
|
||||
<>
|
||||
<ul className={classes.footerNav}>
|
||||
<NavigationLink
|
||||
stacked
|
||||
to='/notifications'
|
||||
iconComponent={BellIcon}
|
||||
badgeCount={notificationsCount}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='tabs_bar.notifications'
|
||||
defaultMessage='Notifications'
|
||||
/>
|
||||
</NavigationLink>
|
||||
<NavigationLink
|
||||
stacked
|
||||
to='/conversations'
|
||||
iconComponent={ChatCircleIcon}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='tabs_bar.messages'
|
||||
defaultMessage='Messages'
|
||||
description='Message refers to a direct message. For languages where this is confusing, "chat" or "direct message" can be used.'
|
||||
/>
|
||||
</NavigationLink>
|
||||
<NavigationLink
|
||||
stacked
|
||||
to='/bookmarks'
|
||||
iconComponent={BookmarkSimpleIcon}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='tabs_bar.saved'
|
||||
defaultMessage='Saved'
|
||||
/>
|
||||
</NavigationLink>
|
||||
</ul>
|
||||
<NavigationAccountCardAndMenu />
|
||||
</>
|
||||
)}
|
||||
<NavigationFooterLinks siteName={siteName} />
|
||||
</footer>
|
||||
</>
|
||||
|
||||
@@ -15,6 +15,7 @@ import { useDrag } from '@use-gesture/react';
|
||||
|
||||
import { closeNavigation, openNavigation } from '@/mastodon/actions/navigation';
|
||||
import { Avatar } from '@/mastodon/components/avatar';
|
||||
import { Menu, MenuList, MenuTrigger } from '@/mastodon/components/menu';
|
||||
import { FOCUS_TARGET } from '@/mastodon/components/navigation_focus_target';
|
||||
import { ComposeRedesignButton } from '@/mastodon/features/compose/redesign/trigger';
|
||||
import { useAccount } from '@/mastodon/hooks/useAccount';
|
||||
@@ -23,8 +24,9 @@ import { selectUnreadNotificationGroupsCount } from '@/mastodon/selectors/notifi
|
||||
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
||||
|
||||
import { RedesignNavigationPanel } from '.';
|
||||
import { AccountMenuItems } from './account_card_and_menu';
|
||||
import classes from './mobile_nav.module.scss';
|
||||
import { MobileNavLink } from './navigation_link';
|
||||
import { MobileNavLink, MobileNavProfileButton } from './navigation_link';
|
||||
|
||||
export const RedesignMobileNavigation: React.FC = () => {
|
||||
const { accountId, signedIn } = useIdentity();
|
||||
@@ -71,14 +73,26 @@ export const RedesignMobileNavigation: React.FC = () => {
|
||||
defaultMessage='Notifications'
|
||||
/>
|
||||
</MobileNavLink>
|
||||
<MobileNavLink
|
||||
to={`/@${account?.acct}`}
|
||||
customIcon={
|
||||
<Avatar size={24} account={account} className={classes.avatar} />
|
||||
}
|
||||
>
|
||||
<FormattedMessage id='tabs_bar.profile' defaultMessage='Profile' />
|
||||
</MobileNavLink>
|
||||
<Menu>
|
||||
<MenuTrigger
|
||||
as={MobileNavProfileButton}
|
||||
avatar={
|
||||
<Avatar
|
||||
size={24}
|
||||
account={account}
|
||||
className={classes.avatar}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='tabs_bar.account_settings'
|
||||
defaultMessage='Account settings'
|
||||
/>
|
||||
</MenuTrigger>
|
||||
<MenuList placement='top-end' offset={8}>
|
||||
<AccountMenuItems context='mobile' />
|
||||
</MenuList>
|
||||
</Menu>
|
||||
</ul>
|
||||
<ComposeRedesignButton inline />
|
||||
</nav>
|
||||
|
||||
@@ -76,6 +76,14 @@
|
||||
justify-content: center;
|
||||
gap: 0;
|
||||
padding-block: var(--space-sm);
|
||||
transition:
|
||||
background-color 200ms,
|
||||
color 200ms;
|
||||
|
||||
&[aria-expanded='true'] {
|
||||
color: var(--color-text-inverted);
|
||||
background-color: var(--color-bg-inverted);
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
|
||||
@@ -48,6 +48,7 @@ export const NavigationLink: React.FC<NavigationLinkProps> = ({
|
||||
>
|
||||
<Comp
|
||||
{...otherProps}
|
||||
type={as === 'button' ? 'button' : undefined}
|
||||
className={classNames(classes.link, stacked && classes.linkStacked)}
|
||||
>
|
||||
{IconComp && (
|
||||
@@ -74,20 +75,11 @@ export const NavigationLink: React.FC<NavigationLinkProps> = ({
|
||||
type MobileNavLink = NavLinkProps & {
|
||||
withDot?: boolean;
|
||||
children: ReactNode;
|
||||
} & (
|
||||
| {
|
||||
iconComponent: Icon | React.FC<SVGProps<SVGSVGElement>>;
|
||||
customIcon?: never;
|
||||
}
|
||||
| {
|
||||
customIcon: ReactNode;
|
||||
iconComponent?: never;
|
||||
}
|
||||
);
|
||||
iconComponent: Icon | React.FC<SVGProps<SVGSVGElement>>;
|
||||
};
|
||||
|
||||
export const MobileNavLink: React.FC<MobileNavLink> = ({
|
||||
iconComponent: IconComp,
|
||||
customIcon,
|
||||
withDot,
|
||||
children,
|
||||
...otherProps
|
||||
@@ -105,14 +97,31 @@ export const MobileNavLink: React.FC<MobileNavLink> = ({
|
||||
<span
|
||||
className={classNames(classes.icon, withDot && classes.iconWithDot)}
|
||||
>
|
||||
{IconComp ? (
|
||||
<IconComp size={24} weight={isActive ? 'fill' : undefined} />
|
||||
) : (
|
||||
customIcon
|
||||
)}
|
||||
<IconComp size={24} weight={isActive ? 'fill' : undefined} />
|
||||
</span>
|
||||
<span className='sr-only'>{children}</span>
|
||||
</NavLink>
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
export const MobileNavProfileButton: React.FC<
|
||||
{ avatar?: ReactNode } & Pick<MobileNavLink, 'children' | 'withDot'>
|
||||
> = ({ children, avatar, withDot, ...otherProps }) => {
|
||||
return (
|
||||
<li>
|
||||
<button
|
||||
type='button'
|
||||
{...otherProps}
|
||||
className={classNames(classes.link, classes.linkMobile)}
|
||||
>
|
||||
<span
|
||||
className={classNames(classes.icon, withDot && classes.iconWithDot)}
|
||||
>
|
||||
{avatar}
|
||||
</span>
|
||||
<span className='sr-only'>{children}</span>
|
||||
</button>
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1040,6 +1040,7 @@
|
||||
"navigation_bar.powered_by_mastodon": "powered by {logo}Mastodon",
|
||||
"navigation_bar.preferences": "Preferences",
|
||||
"navigation_bar.privacy_and_reach": "Privacy and reach",
|
||||
"navigation_bar.saved_posts": "Saved Posts",
|
||||
"navigation_bar.search": "Search",
|
||||
"navigation_bar.search_trends": "Search / Trending",
|
||||
"navigation_bar.sign_out": "Sign out",
|
||||
@@ -1445,6 +1446,7 @@
|
||||
"subscribed_languages.save": "Save changes",
|
||||
"subscribed_languages.target": "Change subscribed languages for {target}",
|
||||
"tab_bar.messages": "Messages",
|
||||
"tabs_bar.account_settings": "Account settings",
|
||||
"tabs_bar.create_custom_feed": "Create",
|
||||
"tabs_bar.custom_feeds": "Custom Feeds",
|
||||
"tabs_bar.custom_feeds_empty": "You have no custom feeds yet.",
|
||||
@@ -1455,9 +1457,7 @@
|
||||
"tabs_bar.home": "Home",
|
||||
"tabs_bar.menu": "Menu",
|
||||
"tabs_bar.messages": "Messages",
|
||||
"tabs_bar.more": "More",
|
||||
"tabs_bar.notifications": "Notifications",
|
||||
"tabs_bar.profile": "Profile",
|
||||
"tabs_bar.publish": "New Post",
|
||||
"tabs_bar.quick_links": "Quick links",
|
||||
"tabs_bar.saved": "Saved",
|
||||
|
||||
Reference in New Issue
Block a user