@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.
Files changed (44) hide show
  1. package/CHANGELOG.md +358 -0
  2. package/LICENSE +201 -0
  3. package/README.md +8 -0
  4. package/package.json +55 -0
  5. package/src/components/HeaderItems/HeaderItem.tsx +123 -0
  6. package/src/components/HeaderItems/index.ts +2 -0
  7. package/src/components/HeaderItems/styles.module.scss +46 -0
  8. package/src/components/HeaderItems/types.ts +12 -0
  9. package/src/components/SiteHeaderBasic/SiteHeaderBasic.tsx +133 -0
  10. package/src/components/SiteHeaderBasic/index.ts +1 -0
  11. package/src/components/SiteHeaderBasic/styles.module.scss +72 -0
  12. package/src/components/SubHeader/SubHeader.tsx +51 -0
  13. package/src/components/SubHeader/index.ts +1 -0
  14. package/src/components/SubHeader/styles.module.scss +37 -0
  15. package/src/components/UserDetailsDropdown/UserDetailsDropdown.tsx +46 -0
  16. package/src/components/UserDetailsDropdown/index.ts +1 -0
  17. package/src/components/UserDetailsDropdown/styles.module.scss +14 -0
  18. package/src/components/UserDetailsInline/UserDetailsInline.tsx +34 -0
  19. package/src/components/UserDetailsInline/index.ts +1 -0
  20. package/src/components/UserDetailsInline/styles.module.scss +9 -0
  21. package/src/components/index.ts +5 -0
  22. package/src/helperComponents/ButtonBurger/ButtonBurger.tsx +16 -0
  23. package/src/helperComponents/ButtonBurger/index.ts +1 -0
  24. package/src/helperComponents/ButtonBurger/styles.module.scss +21 -0
  25. package/src/helperComponents/LinkItemHeader/LinkItemHeader.tsx +36 -0
  26. package/src/helperComponents/LinkItemHeader/index.ts +1 -0
  27. package/src/helperComponents/LinkItemHeader/styles.module.scss +65 -0
  28. package/src/helperComponents/LogoContent/LogoContent.tsx +34 -0
  29. package/src/helperComponents/LogoContent/index.ts +1 -0
  30. package/src/helperComponents/LogoContent/styles.module.scss +39 -0
  31. package/src/helperComponents/MobileMenu/MobileMenu.tsx +27 -0
  32. package/src/helperComponents/MobileMenu/index.ts +1 -0
  33. package/src/helperComponents/MobileMenu/styles.module.scss +8 -0
  34. package/src/helperComponents/MoreButton/MoreButton.tsx +59 -0
  35. package/src/helperComponents/MoreButton/index.ts +1 -0
  36. package/src/helperComponents/MoreButton/styles.module.scss +39 -0
  37. package/src/helperComponents/UserInfo/UserInfo.tsx +25 -0
  38. package/src/helperComponents/UserInfo/index.ts +1 -0
  39. package/src/helperComponents/UserInfo/styles.module.scss +16 -0
  40. package/src/helperComponents/index.ts +5 -0
  41. package/src/hooks/index.ts +2 -0
  42. package/src/hooks/useHeaderPosition.ts +52 -0
  43. package/src/hooks/useResizeObserver.ts +23 -0
  44. 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,8 @@
1
+ .mobileMenu {
2
+ scrollbar-width: none;
3
+ -ms-overflow-style: none;
4
+
5
+ &::-webkit-scrollbar {
6
+ display: none;
7
+ }
8
+ }
@@ -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,5 @@
1
+ export * from './MoreButton';
2
+ export * from './LinkItemHeader';
3
+ export * from './ButtonBurger';
4
+ export * from './LogoContent';
5
+ export * from './MobileMenu';
@@ -0,0 +1,2 @@
1
+ export * from './useResizeObserver';
2
+ export * from './useHeaderPosition';
@@ -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';