@cloud-ru/uikit-product-site-header 0.4.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +358 -0
- package/LICENSE +201 -0
- package/README.md +8 -0
- package/package.json +55 -0
- package/src/components/HeaderItems/HeaderItem.tsx +123 -0
- package/src/components/HeaderItems/index.ts +2 -0
- package/src/components/HeaderItems/styles.module.scss +46 -0
- package/src/components/HeaderItems/types.ts +12 -0
- package/src/components/SiteHeaderBasic/SiteHeaderBasic.tsx +133 -0
- package/src/components/SiteHeaderBasic/index.ts +1 -0
- package/src/components/SiteHeaderBasic/styles.module.scss +72 -0
- package/src/components/SubHeader/SubHeader.tsx +51 -0
- package/src/components/SubHeader/index.ts +1 -0
- package/src/components/SubHeader/styles.module.scss +37 -0
- package/src/components/UserDetailsDropdown/UserDetailsDropdown.tsx +46 -0
- package/src/components/UserDetailsDropdown/index.ts +1 -0
- package/src/components/UserDetailsDropdown/styles.module.scss +14 -0
- package/src/components/UserDetailsInline/UserDetailsInline.tsx +34 -0
- package/src/components/UserDetailsInline/index.ts +1 -0
- package/src/components/UserDetailsInline/styles.module.scss +9 -0
- package/src/components/index.ts +5 -0
- package/src/helperComponents/ButtonBurger/ButtonBurger.tsx +16 -0
- package/src/helperComponents/ButtonBurger/index.ts +1 -0
- package/src/helperComponents/ButtonBurger/styles.module.scss +21 -0
- package/src/helperComponents/LinkItemHeader/LinkItemHeader.tsx +36 -0
- package/src/helperComponents/LinkItemHeader/index.ts +1 -0
- package/src/helperComponents/LinkItemHeader/styles.module.scss +65 -0
- package/src/helperComponents/LogoContent/LogoContent.tsx +34 -0
- package/src/helperComponents/LogoContent/index.ts +1 -0
- package/src/helperComponents/LogoContent/styles.module.scss +39 -0
- package/src/helperComponents/MobileMenu/MobileMenu.tsx +27 -0
- package/src/helperComponents/MobileMenu/index.ts +1 -0
- package/src/helperComponents/MobileMenu/styles.module.scss +8 -0
- package/src/helperComponents/MoreButton/MoreButton.tsx +59 -0
- package/src/helperComponents/MoreButton/index.ts +1 -0
- package/src/helperComponents/MoreButton/styles.module.scss +39 -0
- package/src/helperComponents/UserInfo/UserInfo.tsx +25 -0
- package/src/helperComponents/UserInfo/index.ts +1 -0
- package/src/helperComponents/UserInfo/styles.module.scss +16 -0
- package/src/helperComponents/index.ts +5 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useHeaderPosition.ts +52 -0
- package/src/hooks/useResizeObserver.ts +23 -0
- package/src/index.ts +1 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import cn from 'classnames';
|
|
2
|
+
import { useEffect, useRef, useState } from 'react';
|
|
3
|
+
|
|
4
|
+
import { LinkItemHeader, MoreButton } from '../../helperComponents';
|
|
5
|
+
import { useResizeObserver } from '../../hooks';
|
|
6
|
+
import styles from './styles.module.scss';
|
|
7
|
+
import { LinkItem } from './types';
|
|
8
|
+
|
|
9
|
+
export type HeaderItemProps = {
|
|
10
|
+
/** Список элементов для пунктов меню хэдера */
|
|
11
|
+
linkItems?: LinkItem[];
|
|
12
|
+
/** Флаг является ли сейчас мобильная версия */
|
|
13
|
+
isMobileTabletView?: boolean;
|
|
14
|
+
/** Id активного элемента списка меню */
|
|
15
|
+
activeLinkItemId?: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const GAP_ITEMS = 22;
|
|
19
|
+
|
|
20
|
+
const WIDTH_MORE_BUTTON = 24;
|
|
21
|
+
|
|
22
|
+
export function HeaderItems({ linkItems, isMobileTabletView, activeLinkItemId }: HeaderItemProps) {
|
|
23
|
+
const [visibleItems, setVisibleItems] = useState<LinkItem[]>([]);
|
|
24
|
+
const [hiddenItems, setHiddenItems] = useState<LinkItem[]>([]);
|
|
25
|
+
|
|
26
|
+
const hiddenRowElementRef = useRef<HTMLDivElement>(null);
|
|
27
|
+
|
|
28
|
+
const [firstItemElement, setFirstItemElement] = useState<HTMLElement | null>(null);
|
|
29
|
+
const itemsMapRef = useRef(new Map<LinkItem, HTMLElement | null>());
|
|
30
|
+
|
|
31
|
+
const { width: maxWidth } = useResizeObserver(hiddenRowElementRef.current);
|
|
32
|
+
const { width: firstVisibleItemWidth } = useResizeObserver(firstItemElement);
|
|
33
|
+
|
|
34
|
+
function setItemElement(item: LinkItem, index: number) {
|
|
35
|
+
return (itemElement: HTMLElement | null) => {
|
|
36
|
+
if (index === 0 && itemElement) {
|
|
37
|
+
setFirstItemElement(itemElement);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (itemElement === null) {
|
|
41
|
+
itemsMapRef.current.delete(item);
|
|
42
|
+
} else {
|
|
43
|
+
itemsMapRef.current.set(item, itemElement);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (maxWidth < 1) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const newVisibleItems: LinkItem[] = [];
|
|
54
|
+
const newHiddenItems: LinkItem[] = [];
|
|
55
|
+
let isHidden = false;
|
|
56
|
+
let indexElement = 0;
|
|
57
|
+
|
|
58
|
+
let currentRowWidth = 0;
|
|
59
|
+
|
|
60
|
+
const size = itemsMapRef.current.size;
|
|
61
|
+
|
|
62
|
+
itemsMapRef.current.forEach((itemElement, itemRowItem) => {
|
|
63
|
+
const itemWidth = itemElement?.offsetWidth || 0;
|
|
64
|
+
const isLastElement = indexElement + 1 === size;
|
|
65
|
+
const sumRowWidth = currentRowWidth + itemWidth + (isLastElement ? 0 : GAP_ITEMS);
|
|
66
|
+
|
|
67
|
+
if (isHidden) {
|
|
68
|
+
newHiddenItems.push(itemRowItem);
|
|
69
|
+
} else {
|
|
70
|
+
if (maxWidth > sumRowWidth + (isLastElement ? 0 : WIDTH_MORE_BUTTON)) {
|
|
71
|
+
newVisibleItems.push(itemRowItem);
|
|
72
|
+
currentRowWidth = sumRowWidth;
|
|
73
|
+
} else {
|
|
74
|
+
newHiddenItems.push(itemRowItem);
|
|
75
|
+
isHidden = true;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
indexElement++;
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
setVisibleItems(newVisibleItems);
|
|
82
|
+
setHiddenItems(newHiddenItems);
|
|
83
|
+
}, [linkItems, maxWidth, firstVisibleItemWidth]);
|
|
84
|
+
|
|
85
|
+
if (!linkItems || isMobileTabletView) return null;
|
|
86
|
+
|
|
87
|
+
const isVisibleEmpty = visibleItems.length === 0;
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<div className={styles.wrapper}>
|
|
91
|
+
<div className={cn(styles.linkItemsContainer, styles.hiddenRow)} ref={hiddenRowElementRef}>
|
|
92
|
+
{linkItems.map((linkItem, index) => (
|
|
93
|
+
<div key={linkItem.id} className={styles.lastItemContainer} ref={setItemElement(linkItem, index)}>
|
|
94
|
+
<LinkItemHeader
|
|
95
|
+
label={linkItem.label}
|
|
96
|
+
target={linkItem.target}
|
|
97
|
+
href={linkItem.href}
|
|
98
|
+
onClick={linkItem.onClick}
|
|
99
|
+
/>
|
|
100
|
+
</div>
|
|
101
|
+
))}
|
|
102
|
+
</div>
|
|
103
|
+
<div
|
|
104
|
+
className={cn(styles.linkItemsContainer, {
|
|
105
|
+
[styles.linkItemsFirstViewContainer]: isVisibleEmpty,
|
|
106
|
+
})}
|
|
107
|
+
>
|
|
108
|
+
{/*Добавлено для сайта, так как используется next и приходит html без расчета js*/}
|
|
109
|
+
{(isVisibleEmpty ? linkItems : visibleItems).map(linkItem => (
|
|
110
|
+
<div key={linkItem.id} className={styles.lastItemContainer}>
|
|
111
|
+
<LinkItemHeader
|
|
112
|
+
label={linkItem.label}
|
|
113
|
+
href={linkItem.href}
|
|
114
|
+
onClick={linkItem.onClick}
|
|
115
|
+
active={activeLinkItemId === linkItem.id}
|
|
116
|
+
/>
|
|
117
|
+
</div>
|
|
118
|
+
))}
|
|
119
|
+
{hiddenItems.length > 0 && <MoreButton linkItemsArray={hiddenItems} activeItemId={activeLinkItemId} />}
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
.wrapper {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: row;
|
|
4
|
+
justify-content: start;
|
|
5
|
+
gap: 22px;
|
|
6
|
+
flex-wrap: nowrap;
|
|
7
|
+
min-height: 24px;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.linkItemsContainer {
|
|
11
|
+
display: flex;
|
|
12
|
+
flex-direction: row;
|
|
13
|
+
align-items: center;
|
|
14
|
+
flex-wrap: wrap;
|
|
15
|
+
gap: 22px;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.lastItemContainer {
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-direction: row;
|
|
21
|
+
align-items: center;
|
|
22
|
+
flex-wrap: nowrap;
|
|
23
|
+
gap: 22px;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.hiddenRow {
|
|
27
|
+
position: absolute;
|
|
28
|
+
top: 0;
|
|
29
|
+
left: 0;
|
|
30
|
+
|
|
31
|
+
display: flex;
|
|
32
|
+
width: 100%;
|
|
33
|
+
|
|
34
|
+
visibility: hidden;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.linkItemsFirstViewContainer {
|
|
38
|
+
display: flex;
|
|
39
|
+
flex-direction: row;
|
|
40
|
+
overflow: hidden;
|
|
41
|
+
height: 24px;
|
|
42
|
+
flex-grow: 1;
|
|
43
|
+
align-items: center;
|
|
44
|
+
flex-wrap: wrap;
|
|
45
|
+
gap: 22px;
|
|
46
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type LinkItem = {
|
|
2
|
+
/** id Элемента */
|
|
3
|
+
id: string;
|
|
4
|
+
/** Текст элемента */
|
|
5
|
+
label: string;
|
|
6
|
+
/** Хэндлер клика элемента */
|
|
7
|
+
onClick?: () => void;
|
|
8
|
+
/** Ссылка элемента */
|
|
9
|
+
href?: string;
|
|
10
|
+
/** target для ссылки элемента */
|
|
11
|
+
target?: string;
|
|
12
|
+
};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import cn from 'classnames';
|
|
2
|
+
import { MouseEvent, ReactNode, useRef } from 'react';
|
|
3
|
+
|
|
4
|
+
import { Layout } from '@cloud-ru/uikit-product-site-layout';
|
|
5
|
+
import { extractSupportProps, WithLayoutType, WithSupportProps } from '@cloud-ru/uikit-product-utils';
|
|
6
|
+
|
|
7
|
+
import { ButtonBurger, LogoContent, MobileMenu } from '../../helperComponents';
|
|
8
|
+
import { useHeaderPosition } from '../../hooks';
|
|
9
|
+
import styles from './styles.module.scss';
|
|
10
|
+
|
|
11
|
+
export type AdditionalLogoText = {
|
|
12
|
+
/** Дополнительный текст Логотипа */
|
|
13
|
+
text?: string;
|
|
14
|
+
/** Переход по ссылке по дополнительному тексту Логотипа */
|
|
15
|
+
link?: string;
|
|
16
|
+
/** Коллбэк по клику на дополнительный текс Логотипа */
|
|
17
|
+
onClick?(event?: MouseEvent<HTMLAnchorElement>): void;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type Logo = {
|
|
21
|
+
/** Переход по ссылке Логотипа */
|
|
22
|
+
logoLink: string;
|
|
23
|
+
/** Коллбэк по клику на Логотип */
|
|
24
|
+
onClick?(event?: MouseEvent<HTMLAnchorElement>): void;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type HeaderProps = WithSupportProps<
|
|
28
|
+
WithLayoutType<{
|
|
29
|
+
/** Настройки текста справа Логотипа */
|
|
30
|
+
additionalLogoText?: AdditionalLogoText;
|
|
31
|
+
/** className root блока */
|
|
32
|
+
className?: string;
|
|
33
|
+
/** className основного хедера */
|
|
34
|
+
mainHeaderClassName?: string;
|
|
35
|
+
/** максимальная ширина контейнера */
|
|
36
|
+
maxWidth?: number;
|
|
37
|
+
/** Флаг открытия мобильного меню */
|
|
38
|
+
mobileMenuOpen: boolean;
|
|
39
|
+
/** Настройки Логотипа */
|
|
40
|
+
logo: Logo;
|
|
41
|
+
/** Функция изменения флаг открытия мобильного меню */
|
|
42
|
+
onSetMobileMenuOpen(open: boolean): void;
|
|
43
|
+
/** Контент посередине (между логотипом и правым блоком) */
|
|
44
|
+
middleContent?: ReactNode;
|
|
45
|
+
/** Контент занимающий всю возможную ширину хэдера */
|
|
46
|
+
fullWidthContent?: ReactNode;
|
|
47
|
+
/** Контент справа (левее бургера) */
|
|
48
|
+
rightContent?: ReactNode;
|
|
49
|
+
/** Контент сверху над хэдером, для инфостроки */
|
|
50
|
+
subHeader?: ReactNode;
|
|
51
|
+
/** Контент мобильной версии меню */
|
|
52
|
+
mobileMenuContent?: ReactNode;
|
|
53
|
+
/** Нижний контент кнопок мобильной версии меню */
|
|
54
|
+
mobileConsultationButton?: ReactNode;
|
|
55
|
+
}>
|
|
56
|
+
>;
|
|
57
|
+
|
|
58
|
+
const HEIGHT_SUBHEADER = 25;
|
|
59
|
+
|
|
60
|
+
export function SiteHeaderBasic({
|
|
61
|
+
className,
|
|
62
|
+
maxWidth,
|
|
63
|
+
additionalLogoText,
|
|
64
|
+
middleContent,
|
|
65
|
+
rightContent,
|
|
66
|
+
mobileMenuContent,
|
|
67
|
+
subHeader,
|
|
68
|
+
fullWidthContent,
|
|
69
|
+
layoutType,
|
|
70
|
+
mobileConsultationButton,
|
|
71
|
+
mobileMenuOpen,
|
|
72
|
+
onSetMobileMenuOpen,
|
|
73
|
+
mainHeaderClassName,
|
|
74
|
+
logo,
|
|
75
|
+
...rest
|
|
76
|
+
}: HeaderProps) {
|
|
77
|
+
const refHeader = useRef<HTMLDivElement>(null);
|
|
78
|
+
const { showHeader, headerHeight } = useHeaderPosition(mobileMenuOpen, refHeader);
|
|
79
|
+
|
|
80
|
+
const isMobileTabletView = layoutType === 'mobile' || layoutType === 'tablet';
|
|
81
|
+
const isMobile = layoutType === 'mobile';
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<Layout.Header
|
|
85
|
+
style={{
|
|
86
|
+
transform: `translateY(-${!showHeader ? headerHeight + HEIGHT_SUBHEADER : 0}px)`,
|
|
87
|
+
}}
|
|
88
|
+
className={cn(styles.root, className)}
|
|
89
|
+
data-attr='layout-header'
|
|
90
|
+
{...extractSupportProps(rest)}
|
|
91
|
+
>
|
|
92
|
+
{subHeader}
|
|
93
|
+
<div
|
|
94
|
+
ref={refHeader}
|
|
95
|
+
className={cn(styles.headerMaster, styles.dividerHeader, mainHeaderClassName)}
|
|
96
|
+
data-layout-type={layoutType}
|
|
97
|
+
>
|
|
98
|
+
<div
|
|
99
|
+
className={styles.headerPartsContainer}
|
|
100
|
+
style={{
|
|
101
|
+
maxWidth,
|
|
102
|
+
}}
|
|
103
|
+
>
|
|
104
|
+
{fullWidthContent ?? (
|
|
105
|
+
<>
|
|
106
|
+
<div className={styles.leftPart} data-layout-type={layoutType}>
|
|
107
|
+
<LogoContent additionalLogoText={additionalLogoText} isMobile={isMobile} logo={logo} />
|
|
108
|
+
</div>
|
|
109
|
+
<div className={styles.middlePart}>{middleContent}</div>
|
|
110
|
+
<div className={styles.rightPart}>
|
|
111
|
+
{rightContent}
|
|
112
|
+
{mobileMenuContent && isMobileTabletView && (
|
|
113
|
+
<>
|
|
114
|
+
<ButtonBurger
|
|
115
|
+
mobileMenuOpen={mobileMenuOpen}
|
|
116
|
+
onClick={() => onSetMobileMenuOpen(!mobileMenuOpen)}
|
|
117
|
+
/>
|
|
118
|
+
<MobileMenu
|
|
119
|
+
mobileConsultationButton={mobileConsultationButton}
|
|
120
|
+
mobileMenuContent={mobileMenuContent}
|
|
121
|
+
mobileMenuOpen={mobileMenuOpen}
|
|
122
|
+
onClickForCloseMobileMenu={() => onSetMobileMenuOpen(false)}
|
|
123
|
+
/>
|
|
124
|
+
</>
|
|
125
|
+
)}
|
|
126
|
+
</div>
|
|
127
|
+
</>
|
|
128
|
+
)}
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
</Layout.Header>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './SiteHeaderBasic';
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
@use '@sbercloud/figma-tokens-cloud-platform/build/scss/styles-theme-variables' as ste;
|
|
2
|
+
|
|
3
|
+
.root {
|
|
4
|
+
position: sticky;
|
|
5
|
+
top: 0;
|
|
6
|
+
transition:
|
|
7
|
+
box-shadow 0.3s ease-in-out,
|
|
8
|
+
transform 0.3s ease-in-out;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.headerMaster {
|
|
12
|
+
width: 100%;
|
|
13
|
+
background: ste.$sys-neutral-background1-level;
|
|
14
|
+
padding: 0 32px;
|
|
15
|
+
|
|
16
|
+
&[data-layout-type='mobile'] {
|
|
17
|
+
padding: 0 16px;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.dividerHeader {
|
|
22
|
+
box-sizing: border-box;
|
|
23
|
+
border-bottom: 1px solid ste.$sys-neutral-decor-default;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.leftPart {
|
|
27
|
+
display: flex;
|
|
28
|
+
flex-direction: row;
|
|
29
|
+
align-items: center;
|
|
30
|
+
margin-right: 40px;
|
|
31
|
+
|
|
32
|
+
&[data-layout-type='mobile'] {
|
|
33
|
+
margin-right: 0;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.middlePart {
|
|
38
|
+
position: relative;
|
|
39
|
+
display: flex;
|
|
40
|
+
flex-direction: row;
|
|
41
|
+
align-items: center;
|
|
42
|
+
flex-grow: 1;
|
|
43
|
+
justify-content: start;
|
|
44
|
+
gap: 40px;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.linkItemsContainer {
|
|
48
|
+
display: flex;
|
|
49
|
+
flex-direction: row;
|
|
50
|
+
align-items: center;
|
|
51
|
+
gap: 20px;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.rightPart {
|
|
55
|
+
display: flex;
|
|
56
|
+
flex-direction: row;
|
|
57
|
+
align-items: center;
|
|
58
|
+
gap: 12px;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.headerPartsContainer {
|
|
62
|
+
max-width: 1216px;
|
|
63
|
+
width: 100%;
|
|
64
|
+
display: flex;
|
|
65
|
+
align-items: center;
|
|
66
|
+
flex-direction: row;
|
|
67
|
+
justify-content: space-between;
|
|
68
|
+
height: 60px;
|
|
69
|
+
box-sizing: border-box;
|
|
70
|
+
margin: 0 auto;
|
|
71
|
+
overflow: hidden;
|
|
72
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import cn from 'classnames';
|
|
2
|
+
|
|
3
|
+
import { WithLayoutType } from '@cloud-ru/uikit-product-utils';
|
|
4
|
+
import { AlertTop, AlertTopProps } from '@snack-uikit/alert';
|
|
5
|
+
|
|
6
|
+
import styles from './styles.module.scss';
|
|
7
|
+
|
|
8
|
+
type BannerInfo = {
|
|
9
|
+
/** Цвет фона SubHeader */
|
|
10
|
+
color: 'yellow' | 'blue' | 'green';
|
|
11
|
+
/** Ссылка на текст SubHeader */
|
|
12
|
+
link?: string;
|
|
13
|
+
/** Текст SubHeader, может передаваться без ссылки */
|
|
14
|
+
title: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const APPEARANCE_ALERT: Record<BannerInfo['color'], AlertTopProps['appearance']> = {
|
|
18
|
+
green: 'success',
|
|
19
|
+
yellow: 'warning',
|
|
20
|
+
blue: 'info',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
type SubHeaderProps = WithLayoutType<{
|
|
24
|
+
/** Объект для отображения данных на баннере */
|
|
25
|
+
bannerInfo: BannerInfo;
|
|
26
|
+
/** Функция закрытия SubHeader */
|
|
27
|
+
onCloseSubHeader?(): void;
|
|
28
|
+
}>;
|
|
29
|
+
|
|
30
|
+
export function SubHeader({ bannerInfo, onCloseSubHeader, layoutType }: SubHeaderProps) {
|
|
31
|
+
return (
|
|
32
|
+
<div className={cn(styles.root, styles[bannerInfo.color])} data-layout-type={layoutType}>
|
|
33
|
+
<AlertTop
|
|
34
|
+
className={styles.subHeaderContainer}
|
|
35
|
+
title={!bannerInfo.link ? bannerInfo.title : undefined}
|
|
36
|
+
link={
|
|
37
|
+
bannerInfo.link
|
|
38
|
+
? {
|
|
39
|
+
text: bannerInfo.title,
|
|
40
|
+
href: bannerInfo.link,
|
|
41
|
+
}
|
|
42
|
+
: undefined
|
|
43
|
+
}
|
|
44
|
+
description=''
|
|
45
|
+
icon={false}
|
|
46
|
+
appearance={APPEARANCE_ALERT[bannerInfo.color]}
|
|
47
|
+
onClose={onCloseSubHeader}
|
|
48
|
+
/>
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './SubHeader';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
@use '@sbercloud/figma-tokens-web/build/scss/styles-theme-variables' as ste;
|
|
2
|
+
|
|
3
|
+
.root {
|
|
4
|
+
background: ste.$sys-neutral-background1-level;
|
|
5
|
+
padding: 0 16px;
|
|
6
|
+
|
|
7
|
+
&[data-layout-type='mobile'] {
|
|
8
|
+
padding: 0;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.subHeaderContainer {
|
|
13
|
+
max-width: 1248px;
|
|
14
|
+
margin: 0 auto;
|
|
15
|
+
height: 36px;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.closeIcon {
|
|
19
|
+
cursor: pointer;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.green {
|
|
23
|
+
background-color: ste.$sys-green-accent-default;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.yellow {
|
|
27
|
+
background-color: ste.$sys-yellow-accent-default;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.blue {
|
|
31
|
+
background-color: ste.$sys-blue-accent-default;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.link {
|
|
35
|
+
text-decoration: underline;
|
|
36
|
+
color: ste.$sys-graphite-text-main;
|
|
37
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Avatar } from '@snack-uikit/avatar';
|
|
2
|
+
import { Dropdown } from '@snack-uikit/dropdown';
|
|
3
|
+
|
|
4
|
+
import { UserDetailsInline } from '../UserDetailsInline';
|
|
5
|
+
import styles from './styles.module.scss';
|
|
6
|
+
|
|
7
|
+
export type UserDetailsDropdownProps = {
|
|
8
|
+
userName: string;
|
|
9
|
+
lastName: string;
|
|
10
|
+
onClickExit: () => void;
|
|
11
|
+
onClickDropdownContent?: () => void;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function UserDetailsDropdown({
|
|
15
|
+
userName,
|
|
16
|
+
lastName,
|
|
17
|
+
onClickExit,
|
|
18
|
+
onClickDropdownContent,
|
|
19
|
+
}: UserDetailsDropdownProps) {
|
|
20
|
+
return (
|
|
21
|
+
<Dropdown
|
|
22
|
+
offset={8}
|
|
23
|
+
placement='bottom-end'
|
|
24
|
+
onOpenChange={onClickDropdownContent}
|
|
25
|
+
content={
|
|
26
|
+
<div className={styles.wrapper}>
|
|
27
|
+
<UserDetailsInline
|
|
28
|
+
className={styles.container}
|
|
29
|
+
userName={userName}
|
|
30
|
+
lastName={lastName}
|
|
31
|
+
onClickExit={onClickExit}
|
|
32
|
+
withDivider
|
|
33
|
+
/>
|
|
34
|
+
</div>
|
|
35
|
+
}
|
|
36
|
+
>
|
|
37
|
+
<Avatar
|
|
38
|
+
name={`${userName} ${lastName}`}
|
|
39
|
+
size='s'
|
|
40
|
+
showTwoSymbols
|
|
41
|
+
className={styles.authAvatar}
|
|
42
|
+
appearance='green'
|
|
43
|
+
/>
|
|
44
|
+
</Dropdown>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './UserDetailsDropdown';
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import cn from 'classnames';
|
|
2
|
+
|
|
3
|
+
import { ExitSVG } from '@cloud-ru/uikit-product-icons';
|
|
4
|
+
import { ButtonFunction } from '@snack-uikit/button';
|
|
5
|
+
import { Divider } from '@snack-uikit/divider';
|
|
6
|
+
|
|
7
|
+
import { UserInfo } from '../../helperComponents/UserInfo';
|
|
8
|
+
import styles from './styles.module.scss';
|
|
9
|
+
|
|
10
|
+
export type UserDetailsInlineProps = {
|
|
11
|
+
userName: string;
|
|
12
|
+
lastName: string;
|
|
13
|
+
className?: string;
|
|
14
|
+
onClickExit: () => void;
|
|
15
|
+
withDivider?: boolean;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function UserDetailsInline({ userName, lastName, onClickExit, withDivider, className }: UserDetailsInlineProps) {
|
|
19
|
+
return (
|
|
20
|
+
<div className={cn(styles.userAuthContent, className)}>
|
|
21
|
+
<UserInfo userName={userName} lastName={lastName} />
|
|
22
|
+
{withDivider && <Divider />}
|
|
23
|
+
<ButtonFunction
|
|
24
|
+
label='Выйти из аккаунта'
|
|
25
|
+
size='xs'
|
|
26
|
+
appearance='neutral'
|
|
27
|
+
icon={<ExitSVG size={24} />}
|
|
28
|
+
iconPosition='before'
|
|
29
|
+
className={styles.buttonExit}
|
|
30
|
+
onClick={onClickExit}
|
|
31
|
+
/>
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './UserDetailsInline';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { BurgerSVG, CloseSVG } from '@cloud-ru/uikit-product-icons';
|
|
2
|
+
|
|
3
|
+
import styles from './styles.module.scss';
|
|
4
|
+
|
|
5
|
+
type ButtonBurgerProps = {
|
|
6
|
+
mobileMenuOpen: boolean;
|
|
7
|
+
onClick: () => void;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function ButtonBurger({ mobileMenuOpen, onClick }: ButtonBurgerProps) {
|
|
11
|
+
return (
|
|
12
|
+
<button className={styles.buttonBurger} onClick={onClick}>
|
|
13
|
+
{mobileMenuOpen ? <CloseSVG /> : <BurgerSVG />}
|
|
14
|
+
</button>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './ButtonBurger';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
@use '@sbercloud/figma-tokens-web/build/scss/components/styles-tokens-element' as ste;
|
|
2
|
+
|
|
3
|
+
.buttonBurger {
|
|
4
|
+
background-color: transparent;
|
|
5
|
+
cursor: pointer;
|
|
6
|
+
box-shadow: none;
|
|
7
|
+
padding: 0;
|
|
8
|
+
height: 32px;
|
|
9
|
+
width: 32px;
|
|
10
|
+
display: flex;
|
|
11
|
+
align-items: center;
|
|
12
|
+
justify-content: center;
|
|
13
|
+
box-sizing: border-box;
|
|
14
|
+
border: 2px solid ste.$sys-graphite-decor-default;
|
|
15
|
+
color: ste.$sys-graphite-accent-default;
|
|
16
|
+
|
|
17
|
+
&:active,
|
|
18
|
+
&:focus {
|
|
19
|
+
outline: none;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import cn from 'classnames';
|
|
2
|
+
|
|
3
|
+
import { Typography } from '@snack-uikit/typography';
|
|
4
|
+
|
|
5
|
+
import styles from './styles.module.scss';
|
|
6
|
+
|
|
7
|
+
type LinkItemHeaderProps = {
|
|
8
|
+
href?: string;
|
|
9
|
+
target?: string;
|
|
10
|
+
label: string;
|
|
11
|
+
withoutHover?: boolean;
|
|
12
|
+
active?: boolean;
|
|
13
|
+
onClick?: () => void;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function LinkItemHeader({ href, label, onClick, withoutHover, target, active }: LinkItemHeaderProps) {
|
|
17
|
+
if (href) {
|
|
18
|
+
return (
|
|
19
|
+
<a href={href} target={target} onClick={onClick} className={styles.item}>
|
|
20
|
+
<Typography.SansTitleS>{label}</Typography.SansTitleS>
|
|
21
|
+
</a>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<button
|
|
27
|
+
onClick={onClick}
|
|
28
|
+
className={cn(styles.item, styles.button, {
|
|
29
|
+
[styles.active]: active,
|
|
30
|
+
[styles.hovered]: !withoutHover && !active,
|
|
31
|
+
})}
|
|
32
|
+
>
|
|
33
|
+
<Typography.SansTitleS>{label}</Typography.SansTitleS>
|
|
34
|
+
</button>
|
|
35
|
+
);
|
|
36
|
+
}
|