Pull request #71: feat(TEAMMSBMOB-15864): добавлен дродаун для профиля

Merge in MCB_FE/mcb-platform-monorepo from story/TEAMMSBMOB-15365 to develop

* commit '7899c9bdbae229812553003a7dba0afad2d35551':
This commit is contained in:
Ильдар Смышляев
2025-07-21 13:52:14 +03:00
10 changed files with 188 additions and 3 deletions
@@ -0,0 +1,2 @@
export * from './localization';
export * from './paths';
@@ -0,0 +1,10 @@
const LOCALIZATION = {
USER_PROFILE: 'Профиль пользователя',
ORGANIZATION: 'Организация',
ORGANIZATIONS: 'Организации',
AGREEMENTS: 'Соглашения',
EXIT: 'Выйти',
SETTINGS: 'Настройки',
};
export { LOCALIZATION };
@@ -0,0 +1,6 @@
enum PATHS {
PROFILE = '/profile',
ORGANIZATIONS = '/organizations',
}
export { PATHS };
@@ -0,0 +1 @@
export { ProfileDropdown } from './ui';
@@ -0,0 +1,73 @@
import styled from '@emotion/styled';
import { Dropdown } from '@fractal-ui/composites';
import { Divider } from '@fractal-ui/core';
import { Avatar } from '@fractal-ui/extended';
import { ExitIcon } from '@fractal-ui/library';
import { DEFAULT_ANIMATION_DURATION_MS, MEDIA } from '@msb/shared';
const ProfileDropdown = styled(Dropdown)`
padding: 24px;
min-width: 280px;
max-width: 320px;
border-radius: 16px;
`;
const Body = styled.div`
display: flex;
flex-direction: column;
gap: 16px;
align-items: start;
@media ${MEDIA.mobile} {
padding-top: 24px;
}
`;
const Wrapper = styled.div`
position: relative;
`;
const ChildrenWrapper = styled.div<{ $isDropdownOpened: boolean }>(({ theme, $isDropdownOpened }) => ({
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
svg: {
transition: `${DEFAULT_ANIMATION_DURATION_MS}ms`,
color: $isDropdownOpened && theme.colors.text.primary,
},
}));
const Button = styled.div`
display: flex;
gap: 4px;
align-items: center;
cursor: pointer;
svg {
width: 20px;
height: 20px;
}
`;
const ButtonsGroup = styled.div`
display: flex;
flex-direction: column;
gap: 24px;
`;
const LogoutIcon = styled(ExitIcon)(({ theme }) => ({
'&&&': {
color: theme.colors.text.error,
},
}));
const ProfileInfo = styled(Avatar)`
cursor: pointer;
`;
const ProfileDivider = styled(Divider)(({ theme }) => ({
borderColor: theme.colors.bg.four,
}));
export { ProfileDropdown, Body, Wrapper, ChildrenWrapper, Button, ButtonsGroup, LogoutIcon, ProfileInfo, ProfileDivider };
@@ -0,0 +1,86 @@
import { useRef, useState, type ReactNode } from 'react';
import { BriefcaseIcon, DocEditIcon, SettingsIcon } from '@fractal-ui/library';
import { Drawer, PopupContainer } from '@fractal-ui/overlays';
import { Text } from '@fractal-ui/styling';
import { MEDIA, useMediaQuery } from '@msb/shared';
import { useHistory } from 'react-router-dom';
import { LOCALIZATION, PATHS } from '../constants';
import * as S from './ProfileDropdown.styles';
interface Props {
children: ReactNode;
}
const ProfileDropdown = ({ children }: Props) => {
const history = useHistory();
const handleOpenPage = (url: string) => {
history.push(url);
};
const [isDropdownOpened, setIsDropdownOpened] = useState(false);
const handleOpenDropdown = () => {
setIsDropdownOpened(true);
};
const handleCloseDropdown = () => {
setIsDropdownOpened(false);
};
const dropdownRef = useRef<HTMLDivElement>(null);
const isDesktop = useMediaQuery(MEDIA.desktop);
const DropdownContent = (
<S.Body>
<S.ProfileInfo showCaption alt="Иванов С.В." src="" variant="cyan" onClick={() => handleOpenPage(PATHS.PROFILE)} />
<S.ProfileDivider width="100%" />
<S.ButtonsGroup>
<S.Button onClick={() => handleOpenPage(PATHS.ORGANIZATIONS)}>
<BriefcaseIcon size="S" />
<Text.P2>{LOCALIZATION.ORGANIZATION}</Text.P2>
</S.Button>
<S.Button>
<DocEditIcon size="S" />
<Text.P2>{LOCALIZATION.AGREEMENTS}</Text.P2>
</S.Button>
<S.Button>
<SettingsIcon size="S" />
<Text.P2>{LOCALIZATION.SETTINGS}</Text.P2>
</S.Button>
</S.ButtonsGroup>
<S.ProfileDivider width="100%" />
<S.Button>
<S.LogoutIcon size="S" />
<Text.P2 color="text.error">{LOCALIZATION.EXIT}</Text.P2>
</S.Button>
</S.Body>
);
return (
<S.Wrapper ref={dropdownRef}>
<S.ChildrenWrapper $isDropdownOpened={isDropdownOpened} onClick={handleOpenDropdown}>
{children}
</S.ChildrenWrapper>
{isDesktop ? (
<PopupContainer
disableAutowidth
anchorEl={dropdownRef}
isOpen={isDropdownOpened}
offsetX={8}
offsetY={12}
position="bottom left"
onClose={handleCloseDropdown}
>
<S.ProfileDropdown dataName="profile-dropdown">{DropdownContent}</S.ProfileDropdown>
</PopupContainer>
) : (
<Drawer header={LOCALIZATION.USER_PROFILE} isOpen={isDropdownOpened} onClose={handleCloseDropdown}>
{DropdownContent}
</Drawer>
)}
</S.Wrapper>
);
};
export { ProfileDropdown };
@@ -0,0 +1 @@
export { ProfileDropdown } from './ProfileDropdown';
+1
View File
@@ -1 +1,2 @@
export { PageNavigation } from './PageNavigation';
export { ProfileDropdown } from './ProfileDropdown';
@@ -5,6 +5,7 @@ import { NotificationIcon, SearchIcon, UserProfileIcon } from '@fractal-ui/libra
import gpbIcon from '@msb/shared/assets/gpb_flavor.png';
import gpbMsbLogo from '../../../assets/logo_pic_out.svg';
import * as S from './HeaderMenu.styles';
import { ProfileDropdown } from '@/features/ProfileDropdown/ui';
interface Props {
organizationsSlot: ReactNode;
@@ -23,7 +24,10 @@ const HeaderMenu = ({ organizationsSlot }: Props): ReactElement => (
<S.ProfileGroup>
{organizationsSlot}
<NotificationIcon />
<UserProfileIcon />
<ProfileDropdown>
<UserProfileIcon />
</ProfileDropdown>
</S.ProfileGroup>
</S.Box>
</S.Header>
@@ -2,6 +2,7 @@ import type { ReactNode, ReactElement } from 'react';
import { InputSearch } from '@fractal-ui/composites';
import { UserProfileIcon } from '@fractal-ui/library';
import * as S from './HeaderMenu.styles';
import { ProfileDropdown } from '@/features/ProfileDropdown';
interface Props {
organizationsSlot: ReactNode;
@@ -10,9 +11,9 @@ interface Props {
const HeaderMobileMenu = ({ organizationsSlot }: Props): ReactElement => (
<S.Header>
<S.MobileBox>
<div>
<ProfileDropdown>
<UserProfileIcon />
</div>
</ProfileDropdown>
<S.SearchInputBox>
<InputSearch name="search" options={[]} placeholder="Поиск" />
</S.SearchInputBox>