@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 @@
|
|
|
1
|
+
export * from './LinkItemHeader';
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
@use '@sbercloud/figma-tokens-web/build/scss/components/styles-tokens-element' as ste;
|
|
2
|
+
|
|
3
|
+
.button {
|
|
4
|
+
background-color: transparent;
|
|
5
|
+
border: none;
|
|
6
|
+
box-shadow: none;
|
|
7
|
+
padding: 0;
|
|
8
|
+
|
|
9
|
+
&:active,
|
|
10
|
+
&:focus {
|
|
11
|
+
outline: none;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.item {
|
|
16
|
+
position: relative;
|
|
17
|
+
text-decoration: none;
|
|
18
|
+
color: ste.$sys-graphite-accent-default;
|
|
19
|
+
transition: color 0.3s ease-in-out;
|
|
20
|
+
cursor: pointer;
|
|
21
|
+
|
|
22
|
+
&:hover {
|
|
23
|
+
color: ste.$sys-graphite-accent-hovered;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.hovered {
|
|
28
|
+
&::after {
|
|
29
|
+
content: '';
|
|
30
|
+
|
|
31
|
+
position: absolute;
|
|
32
|
+
bottom: -20px;
|
|
33
|
+
left: 0;
|
|
34
|
+
|
|
35
|
+
width: 100%;
|
|
36
|
+
height: 2px;
|
|
37
|
+
|
|
38
|
+
opacity: 0;
|
|
39
|
+
background: ste.$sys-primary-accent-default;
|
|
40
|
+
|
|
41
|
+
transition: opacity 0.3s ease-in-out;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
&:hover {
|
|
45
|
+
&::after {
|
|
46
|
+
opacity: 1;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.active {
|
|
52
|
+
&::after {
|
|
53
|
+
content: '';
|
|
54
|
+
|
|
55
|
+
position: absolute;
|
|
56
|
+
bottom: -20px;
|
|
57
|
+
left: 0;
|
|
58
|
+
|
|
59
|
+
width: 100%;
|
|
60
|
+
height: 2px;
|
|
61
|
+
|
|
62
|
+
opacity: 1;
|
|
63
|
+
background: ste.$sys-primary-accent-default;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { CloudFullLogoSVG } from '@cloud-ru/uikit-product-icons';
|
|
2
|
+
import { Typography } from '@snack-uikit/typography';
|
|
3
|
+
|
|
4
|
+
import { AdditionalLogoText, Logo } from '../../components/SiteHeaderBasic/SiteHeaderBasic';
|
|
5
|
+
import styles from './styles.module.scss';
|
|
6
|
+
|
|
7
|
+
export type LogoContentProps = {
|
|
8
|
+
additionalLogoText?: AdditionalLogoText;
|
|
9
|
+
logo: Logo;
|
|
10
|
+
isMobile?: boolean;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function LogoContent({ additionalLogoText, isMobile, logo }: LogoContentProps) {
|
|
14
|
+
const heightIconSizeProp = isMobile ? 102 : 130;
|
|
15
|
+
return (
|
|
16
|
+
<div className={styles.logoContentContainer}>
|
|
17
|
+
<a href={logo.logoLink} onClick={logo.onClick} className={styles.logoLink}>
|
|
18
|
+
<CloudFullLogoSVG size={heightIconSizeProp} width='60px' />
|
|
19
|
+
</a>
|
|
20
|
+
{additionalLogoText && (
|
|
21
|
+
<div className={styles.subLogo}>
|
|
22
|
+
<Typography className={styles.slash} size='m' purpose='title' family='sans'>
|
|
23
|
+
/
|
|
24
|
+
</Typography>
|
|
25
|
+
<a href={additionalLogoText.link} onClick={additionalLogoText.onClick} className={styles.link}>
|
|
26
|
+
<Typography size='m' purpose='title' family='sans'>
|
|
27
|
+
{additionalLogoText.text}
|
|
28
|
+
</Typography>
|
|
29
|
+
</a>
|
|
30
|
+
</div>
|
|
31
|
+
)}
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './LogoContent';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
@use '@sbercloud/figma-tokens-web/build/scss/components/styles-tokens-element' as ste;
|
|
2
|
+
|
|
3
|
+
.logoContentContainer {
|
|
4
|
+
display: flex;
|
|
5
|
+
flex-direction: row;
|
|
6
|
+
align-items: center;
|
|
7
|
+
gap: 10px;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.link {
|
|
11
|
+
text-decoration: none;
|
|
12
|
+
display: flex;
|
|
13
|
+
align-items: center;
|
|
14
|
+
color: ste.$sys-graphite-accent-default;
|
|
15
|
+
|
|
16
|
+
&:hover {
|
|
17
|
+
color: ste.$sys-primary-accent-hovered;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
&:active {
|
|
21
|
+
color: ste.$sys-primary-accent-pressed;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.slash {
|
|
26
|
+
color: ste.$sys-neutral-text-disabled;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.logoLink {
|
|
30
|
+
display: flex;
|
|
31
|
+
align-items: center;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.subLogo {
|
|
35
|
+
display: flex;
|
|
36
|
+
flex-direction: row;
|
|
37
|
+
align-items: center;
|
|
38
|
+
gap: 8px;
|
|
39
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import { MobileModalCustom } from '@cloud-ru/uikit-product-mobile-modal';
|
|
4
|
+
|
|
5
|
+
import styles from './styles.module.scss';
|
|
6
|
+
|
|
7
|
+
type MobileMenuProps = {
|
|
8
|
+
mobileMenuOpen: boolean;
|
|
9
|
+
mobileMenuContent: ReactNode;
|
|
10
|
+
mobileConsultationButton?: ReactNode;
|
|
11
|
+
onClickForCloseMobileMenu: () => void;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function MobileMenu({
|
|
15
|
+
mobileMenuOpen,
|
|
16
|
+
onClickForCloseMobileMenu,
|
|
17
|
+
mobileMenuContent,
|
|
18
|
+
mobileConsultationButton,
|
|
19
|
+
}: MobileMenuProps) {
|
|
20
|
+
return (
|
|
21
|
+
<MobileModalCustom open={mobileMenuOpen} onClose={onClickForCloseMobileMenu} className={styles.mobileMenu}>
|
|
22
|
+
<MobileModalCustom.Header title='Меню' align='center' />
|
|
23
|
+
<MobileModalCustom.Body content={mobileMenuContent} />
|
|
24
|
+
{mobileConsultationButton && <MobileModalCustom.Footer actions={mobileConsultationButton} />}
|
|
25
|
+
</MobileModalCustom>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './MobileMenu';
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import cn from 'classnames';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
|
|
4
|
+
import { MoreSVG } from '@cloud-ru/uikit-product-icons';
|
|
5
|
+
import { Dropdown } from '@snack-uikit/dropdown';
|
|
6
|
+
|
|
7
|
+
import { LinkItem } from '../../components/HeaderItems/types';
|
|
8
|
+
import { LinkItemHeader } from '../LinkItemHeader';
|
|
9
|
+
import styles from './styles.module.scss';
|
|
10
|
+
|
|
11
|
+
type MoreButtonProps = {
|
|
12
|
+
linkItemsArray: LinkItem[];
|
|
13
|
+
activeItemId?: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function MoreButton({ linkItemsArray, activeItemId }: MoreButtonProps) {
|
|
17
|
+
const [open, setOpen] = useState(false);
|
|
18
|
+
const onClickOpen = () => {
|
|
19
|
+
setOpen(prev => !prev);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<Dropdown
|
|
24
|
+
placement='bottom-start'
|
|
25
|
+
open={open}
|
|
26
|
+
outsideClick={() => {
|
|
27
|
+
onClickOpen();
|
|
28
|
+
return false;
|
|
29
|
+
}}
|
|
30
|
+
content={
|
|
31
|
+
<div className={styles.tagRowDropListScroll}>
|
|
32
|
+
{linkItemsArray.map(item => (
|
|
33
|
+
<div
|
|
34
|
+
key={item.id}
|
|
35
|
+
className={cn(styles.rowLinkMore, {
|
|
36
|
+
[styles.hovered]: !item.href,
|
|
37
|
+
[styles.active]: item.id === activeItemId && !item.href,
|
|
38
|
+
})}
|
|
39
|
+
>
|
|
40
|
+
<LinkItemHeader
|
|
41
|
+
label={item.label}
|
|
42
|
+
onClick={() => {
|
|
43
|
+
item.onClick && item.onClick();
|
|
44
|
+
onClickOpen();
|
|
45
|
+
}}
|
|
46
|
+
href={item.href}
|
|
47
|
+
withoutHover
|
|
48
|
+
/>
|
|
49
|
+
</div>
|
|
50
|
+
))}
|
|
51
|
+
</div>
|
|
52
|
+
}
|
|
53
|
+
>
|
|
54
|
+
<button className={styles.button} onClick={onClickOpen}>
|
|
55
|
+
<MoreSVG />
|
|
56
|
+
</button>
|
|
57
|
+
</Dropdown>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './MoreButton';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
@use '@sbercloud/figma-tokens-web/build/scss/components/styles-tokens-element' as ste;
|
|
2
|
+
|
|
3
|
+
.tagRowDropListScroll {
|
|
4
|
+
display: flex;
|
|
5
|
+
flex-direction: column;
|
|
6
|
+
align-items: start;
|
|
7
|
+
width: 140px;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.rowLinkMore {
|
|
11
|
+
padding: 8px 10px;
|
|
12
|
+
border-left: 2px solid transparent;
|
|
13
|
+
transition: color 0.3s ease-in-out;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.hovered {
|
|
17
|
+
&:hover {
|
|
18
|
+
border-left: 2px solid ste.$sys-primary-accent-default;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.button {
|
|
23
|
+
background-color: transparent;
|
|
24
|
+
border: none;
|
|
25
|
+
box-shadow: none;
|
|
26
|
+
padding: 0;
|
|
27
|
+
display: flex;
|
|
28
|
+
align-items: center;
|
|
29
|
+
cursor: pointer;
|
|
30
|
+
|
|
31
|
+
&:active,
|
|
32
|
+
&:focus {
|
|
33
|
+
outline: none;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.active {
|
|
38
|
+
border-left: 2px solid ste.$sys-primary-accent-default;
|
|
39
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import cn from 'classnames';
|
|
2
|
+
|
|
3
|
+
import { Avatar } from '@snack-uikit/avatar';
|
|
4
|
+
import { Typography } from '@snack-uikit/typography';
|
|
5
|
+
|
|
6
|
+
import styles from './styles.module.scss';
|
|
7
|
+
|
|
8
|
+
type UserInfoProps = {
|
|
9
|
+
userName: string;
|
|
10
|
+
lastName: string;
|
|
11
|
+
className?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function UserInfo({ userName, lastName, className }: UserInfoProps) {
|
|
15
|
+
const fullName = `${lastName} ${userName}`;
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<div className={cn(styles.root, className)}>
|
|
19
|
+
<Avatar name={fullName} size='xs' showTwoSymbols appearance='green' />
|
|
20
|
+
<Typography tag='div' size='s' purpose='body' family='sans' className={styles.text}>
|
|
21
|
+
{fullName}
|
|
22
|
+
</Typography>
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './UserInfo';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
@use '@sbercloud/figma-tokens-web/build/scss/components/styles-tokens-element'
|
|
2
|
+
as ste;
|
|
3
|
+
|
|
4
|
+
.root {
|
|
5
|
+
display: grid;
|
|
6
|
+
grid-template-columns: 24px 188px;
|
|
7
|
+
align-items: center;
|
|
8
|
+
gap: 8px;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.text {
|
|
12
|
+
text-overflow: ellipsis;
|
|
13
|
+
overflow: hidden;
|
|
14
|
+
white-space: nowrap;
|
|
15
|
+
color: ste.$sys-neutral-text-main;
|
|
16
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { RefObject, useEffect, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
export const useHeaderPosition = (isModalMenuOpen?: boolean, refHeader?: RefObject<HTMLDivElement>) => {
|
|
4
|
+
const [showHeader, setShowHeader] = useState(true);
|
|
5
|
+
const [headerPosition, setHeaderPosition] = useState(0);
|
|
6
|
+
const [headerHeight, setHeaderHeight] = useState(0);
|
|
7
|
+
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
const targetScroll = () => {
|
|
10
|
+
const lastScrollPos = window.scrollY;
|
|
11
|
+
|
|
12
|
+
if (headerPosition !== 0) {
|
|
13
|
+
if (!isModalMenuOpen && lastScrollPos > headerPosition && showHeader && lastScrollPos > 150) {
|
|
14
|
+
setShowHeader(false);
|
|
15
|
+
setHeaderPosition(lastScrollPos);
|
|
16
|
+
} else if (lastScrollPos < headerPosition && !showHeader) {
|
|
17
|
+
setShowHeader(true);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
setHeaderPosition(lastScrollPos);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const updateHeaderHeight = () => {
|
|
25
|
+
if (refHeader && refHeader.current) {
|
|
26
|
+
const height = refHeader.current.offsetHeight;
|
|
27
|
+
setHeaderHeight(Math.floor(height));
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const handleResize = () => {
|
|
32
|
+
updateHeaderHeight();
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
updateHeaderHeight();
|
|
36
|
+
targetScroll();
|
|
37
|
+
|
|
38
|
+
window.addEventListener('scroll', targetScroll);
|
|
39
|
+
window.addEventListener('scroll', handleResize);
|
|
40
|
+
|
|
41
|
+
return () => {
|
|
42
|
+
window.removeEventListener('scroll', targetScroll);
|
|
43
|
+
window.removeEventListener('scroll', handleResize);
|
|
44
|
+
};
|
|
45
|
+
}, [showHeader, headerPosition, isModalMenuOpen, refHeader]);
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
showHeader,
|
|
49
|
+
headerPosition,
|
|
50
|
+
headerHeight,
|
|
51
|
+
};
|
|
52
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
export const useResizeObserver = (element: HTMLElement | null) => {
|
|
4
|
+
const [width, setWidth] = useState<number>(0);
|
|
5
|
+
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
if (!element) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
12
|
+
if (element.offsetWidth !== width) {
|
|
13
|
+
setWidth(element.offsetWidth);
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
resizeObserver.observe(element);
|
|
18
|
+
|
|
19
|
+
return () => resizeObserver.disconnect();
|
|
20
|
+
}, [element, width]);
|
|
21
|
+
|
|
22
|
+
return { width };
|
|
23
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './components';
|