Skip to content

Commit

Permalink
feat(main-nav): Main nav refactoring, change links order (#20275)
Browse files Browse the repository at this point in the history
* feat(main-nav): change links order in main nav

* feat(main-nav): add the position property to order links in the main nav

* feat(main-nav): refactor the sorting of the nav links

* feat(main-nav): add useCollator and format message to sort by name the links
  • Loading branch information
simotae14 committed May 13, 2024
1 parent 8c5105d commit c734c14
Show file tree
Hide file tree
Showing 9 changed files with 77 additions and 93 deletions.
1 change: 1 addition & 0 deletions packages/core/admin/admin/src/StrapiApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ interface MenuItem {
Component: React.LazyExoticComponent<React.ComponentType>;
exact?: boolean;
lockIcon?: boolean;
position?: number;
}

interface StrapiAppSettingLink extends Omit<MenuItem, 'icon' | 'notificationCount'> {
Expand Down
145 changes: 55 additions & 90 deletions packages/core/admin/admin/src/components/LeftMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,45 @@
import * as React from 'react';

import { Divider, Flex, FlexComponent } from '@strapi/design-system';
import { Feather, Lock, House } from '@strapi/icons';
import { Divider, Flex, FlexComponent, useCollator } from '@strapi/design-system';
import { Lock } from '@strapi/icons';
import { useIntl } from 'react-intl';
import { useLocation } from 'react-router-dom';
import { styled } from 'styled-components';

import { useAuth } from '../features/Auth';
import { useTracking } from '../features/Tracking';
import { Menu } from '../hooks/useMenu';
import { Menu, MenuItem } from '../hooks/useMenu';
import { getDisplayName } from '../utils/users';

import { MainNav } from './MainNav/MainNav';
import { NavBrand } from './MainNav/NavBrand';
import { NavLink } from './MainNav/NavLink';
import { NavUser } from './MainNav/NavUser';

const NewNavLinkBadge = styled(NavLink.Badge)`
const sortLinks = (links: MenuItem[]) => {
return links.sort((a, b) => {
// if no position is defined, we put the link in the position of the external plugins, before the plugins list
const positionA = a.position ?? 6;
const positionB = b.position ?? 6;

if (positionA < positionB) {
return -1;
} else {
return 1;
}
});
};

const NavLinkBadgeCounter = styled(NavLink.Badge)`
span {
color: ${({ theme }) => theme.colors.neutral0};
}
`;

const NavLinkBadgeLock = styled(NavLink.Badge)`
background-color: transparent;
`;

const NavListWrapper = styled<FlexComponent<'ul'>>(Flex)`
overflow-y: auto;
`;
Expand All @@ -30,10 +48,13 @@ interface LeftMenuProps extends Pick<Menu, 'generalSectionLinks' | 'pluginsSecti

const LeftMenu = ({ generalSectionLinks, pluginsSectionLinks }: LeftMenuProps) => {
const user = useAuth('AuthenticatedApp', (state) => state.user);
const { formatMessage } = useIntl();
const { trackUsage } = useTracking();
const { pathname } = useLocation();
const userDisplayName = getDisplayName(user);
const { formatMessage, locale } = useIntl();
const formatter = useCollator(locale, {
sensitivity: 'base',
});

const initials = userDisplayName
.split(' ')
Expand All @@ -45,115 +66,59 @@ const LeftMenu = ({ generalSectionLinks, pluginsSectionLinks }: LeftMenuProps) =
trackUsage('willNavigate', { from: pathname, to: destination });
};

const listLinksAlphabeticallySorted = [...pluginsSectionLinks, ...generalSectionLinks].sort(
(a, b) => formatter.compare(formatMessage(a.intlLabel), formatMessage(b.intlLabel))
);
const listLinks = sortLinks(listLinksAlphabeticallySorted);

return (
<MainNav>
<NavBrand />

<Divider />

<NavListWrapper tag="ul" gap={3} direction="column" flex={1} paddingTop={3} paddingBottom={3}>
<Flex tag="li">
<NavLink.Tooltip label={formatMessage({ id: 'global.home', defaultMessage: 'Home' })}>
<NavLink.Link
to="/"
onClick={() => handleClickOnLink('/')}
aria-label={formatMessage({ id: 'global.home', defaultMessage: 'Home' })}
>
<NavLink.Icon label={formatMessage({ id: 'global.home', defaultMessage: 'Home' })}>
<House width="2rem" height="2rem" fill="neutral500" />
</NavLink.Icon>
</NavLink.Link>
</NavLink.Tooltip>
</Flex>
<Flex tag="li">
<NavLink.Tooltip
label={formatMessage({
id: 'global.content-manager',
defaultMessage: 'Content Manager',
})}
>
<NavLink.Link
to="/content-manager"
onClick={() => handleClickOnLink('/content-manager')}
aria-label={formatMessage({
id: 'global.content-manager',
defaultMessage: 'Content Manager',
})}
>
<NavLink.Icon
label={formatMessage({
id: 'global.content-manager',
defaultMessage: 'Content Manager',
})}
>
<Feather width="2rem" height="2rem" fill="neutral500" />
</NavLink.Icon>
</NavLink.Link>
</NavLink.Tooltip>
</Flex>
{pluginsSectionLinks.length > 0
? pluginsSectionLinks.map((link) => {
if (link.to === 'content-manager') {
return null;
}

{listLinks.length > 0
? listLinks.map((link) => {
const LinkIcon = link.icon;
const badgeContent = link?.lockIcon ? <Lock /> : undefined;
const badgeContentLock = link?.lockIcon ? <Lock /> : undefined;

const labelValue = formatMessage(link.intlLabel);
return (
<Flex tag="li" key={link.to}>
<NavLink.Tooltip label={labelValue}>
<NavLink.Link
to={link.to}
onClick={() => handleClickOnLink(link.to)}
aria-label={labelValue}
>
<NavLink.Icon label={labelValue}>
<LinkIcon width="2rem" height="2rem" fill="neutral500" />
</NavLink.Icon>
{badgeContent && (
<NavLink.Badge
label="locked"
background="transparent"
textColor="neutral500"
>
{badgeContent}
</NavLink.Badge>
)}
</NavLink.Link>
</NavLink.Tooltip>
</Flex>
);
})
: null}
{generalSectionLinks.length > 0
? generalSectionLinks.map((link) => {
const LinkIcon = link.icon;

const badgeContent =
const badgeContentNumeric =
link.notificationsCount && link.notificationsCount > 0
? link.notificationsCount.toString()
: undefined;

const labelValue = formatMessage(link.intlLabel);

return (
<Flex tag="li" key={link.to}>
<NavLink.Tooltip label={labelValue}>
<NavLink.Link
aria-label={labelValue}
to={link.to}
onClick={() => handleClickOnLink(link.to)}
aria-label={labelValue}
>
<NavLink.Icon label={labelValue}>
<LinkIcon width="2rem" height="2rem" fill="neutral500" />
</NavLink.Icon>
{badgeContent && (
<NewNavLinkBadge label={badgeContent} backgroundColor="primary600">
{badgeContent}
</NewNavLinkBadge>
)}
{badgeContentLock ? (
<NavLinkBadgeLock
label="locked"
textColor="neutral500"
paddingLeft={0}
paddingRight={0}
>
{badgeContentLock}
</NavLinkBadgeLock>
) : badgeContentNumeric ? (
<NavLinkBadgeCounter
label={badgeContentNumeric}
backgroundColor="primary600"
width="2.3rem"
color="neutral0"
>
{badgeContentNumeric}
</NavLinkBadgeCounter>
) : null}
</NavLink.Link>
</NavLink.Tooltip>
</Flex>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ const BadgeImpl = ({ children, label, ...props }: NavLink.NavBadgeProps) => {
return (
<CustomBadge
position="absolute"
width="2.3rem"
top="-0.8rem"
left="1.7rem"
aria-label={label}
Expand Down
17 changes: 15 additions & 2 deletions packages/core/admin/admin/src/hooks/useMenu.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react';

import { Cog, PuzzlePiece, ShoppingCart } from '@strapi/icons';
import { Cog, PuzzlePiece, ShoppingCart, House } from '@strapi/icons';
import cloneDeep from 'lodash/cloneDeep';
import { useSelector } from 'react-redux';

Expand All @@ -12,7 +12,7 @@ import { selectAdminPermissions } from '../selectors';
* useMenu
* -----------------------------------------------------------------------------------------------*/

type MenuItem = Omit<StrapiAppContextValue['menu'][number], 'Component'>;
export type MenuItem = Omit<StrapiAppContextValue['menu'][number], 'Component'>;

export interface Menu {
generalSectionLinks: MenuItem[];
Expand All @@ -26,6 +26,16 @@ const useMenu = (shouldUpdateStrapi: boolean) => {
const permissions = useSelector(selectAdminPermissions);
const [menuWithUserPermissions, setMenuWithUserPermissions] = React.useState<Menu>({
generalSectionLinks: [
{
icon: House,
intlLabel: {
id: 'global.home',
defaultMessage: 'Home',
},
to: '/',
permissions: [],
position: 0,
},
{
icon: PuzzlePiece,
intlLabel: {
Expand All @@ -34,6 +44,7 @@ const useMenu = (shouldUpdateStrapi: boolean) => {
},
to: '/list-plugins',
permissions: permissions.marketplace?.main ?? [],
position: 7,
},
{
icon: ShoppingCart,
Expand All @@ -43,6 +54,7 @@ const useMenu = (shouldUpdateStrapi: boolean) => {
},
to: '/marketplace',
permissions: permissions.marketplace?.main ?? [],
position: 8,
},
{
icon: Cog,
Expand All @@ -55,6 +67,7 @@ const useMenu = (shouldUpdateStrapi: boolean) => {
// using the settings menu
permissions: [],
notificationsCount: 0,
position: 10,
},
],
pluginsSectionLinks: [],
Expand Down
1 change: 1 addition & 0 deletions packages/core/content-manager/admin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export default {
},
permissions: [],
Component: () => import('./layout').then((mod) => ({ default: mod.Layout })),
position: 1,
});

app.registerPlugin(cm.config);
Expand Down
2 changes: 2 additions & 0 deletions packages/core/content-releases/admin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const admin: Plugin.Config.AdminInput = {
},
Component: () => import('./pages/App').then((mod) => ({ default: mod.App })),
permissions: PERMISSIONS.main,
position: 2,
});

/**
Expand Down Expand Up @@ -77,6 +78,7 @@ const admin: Plugin.Config.AdminInput = {
return { default: PurchaseContentReleases };
},
lockIcon: true,
position: 2,
});
}
},
Expand Down
1 change: 1 addition & 0 deletions packages/core/content-type-builder/admin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export default {
},
permissions: PERMISSIONS.main,
Component: () => import('./pages/App'),
position: 5,
});

app.registerPlugin({
Expand Down
1 change: 1 addition & 0 deletions packages/core/upload/admin/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export default {
},
permissions: PERMISSIONS.main,
Component: () => import('./pages/App'),
position: 4,
});

app.addSettingsLink('global', {
Expand Down
1 change: 1 addition & 0 deletions packages/plugins/documentation/admin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export default {
const { App } = await import('./pages/App');
return App;
},
position: 9,
});

app.addMiddlewares([() => api.middleware]);
Expand Down

0 comments on commit c734c14

Please sign in to comment.