diff --git a/src/components/AsideHeader/AsideHeader.scss b/src/components/AsideHeader/AsideHeader.scss index b24daa7..44a8a2f 100644 --- a/src/components/AsideHeader/AsideHeader.scss +++ b/src/components/AsideHeader/AsideHeader.scss @@ -2,10 +2,6 @@ $block: '.#{variables.$ns}aside-header'; -.g-root { - --gn-aside-top-panel-height: 0px; -} - #{$block} { $class: &; --gn-aside-header-min-width: 56px; @@ -24,7 +20,7 @@ $block: '.#{variables.$ns}aside-header'; &__aside { position: sticky; - top: var(--gn-aside-top-panel-height); + top: var(--gn-top-alert-height, 0); left: 0; height: 100vh; width: inherit; @@ -35,8 +31,8 @@ $block: '.#{variables.$ns}aside-header'; var(--gn-aside-header-background-color, var(--_--background-color)) ); z-index: var(--gn-aside-header-z-index, 100); - max-height: calc(100vh - var(--gn-aside-top-panel-height)); - margin-top: var(--gn-aside-top-panel-height); + max-height: calc(100vh - var(--gn-top-alert-height, 0)); + margin-top: var(--gn-top-alert-height, 0); box-sizing: border-box; @@ -205,9 +201,9 @@ $block: '.#{variables.$ns}aside-header'; &__panels { z-index: var(--gn-aside-header-panel-z-index, 98); position: fixed; - inset: var(--gn-aside-top-panel-height) 0 0; + inset: var(--gn-top-alert-height, 0) 0 0; overflow: auto; - max-height: calc(100vh - var(--gn-aside-top-panel-height)); + max-height: calc(100vh - var(--gn-top-alert-height, 0)); } &__panel { @@ -222,16 +218,7 @@ $block: '.#{variables.$ns}aside-header'; flex-direction: row; } - &__pane-top-divider { - height: 1px; - background-color: var( - --gn-aside-header-divider-horizontal-color, - var(--_--horizontal-divider-line-color) - ); - margin-top: -1px; - } - - &__pane-top { + &__top-alert { position: fixed; z-index: var(--gn-aside-header-pane-top-z-index, 98); top: 0; @@ -239,21 +226,9 @@ $block: '.#{variables.$ns}aside-header'; width: 100%; } - &__pane-top-alert { - &_centered { - display: flex; - justify-content: space-around; - } - - &_dense { - padding-top: var(--g-spacing-2); - padding-bottom: var(--g-spacing-2); - } - } - &__content { width: calc(100% - var(--gn-aside-header-size)); z-index: var(--gn-aside-header-content-z-index, 95); - margin-top: var(--gn-aside-top-panel-height); + margin-top: var(--gn-top-alert-height, 0); } } diff --git a/src/components/AsideHeader/README.md b/src/components/AsideHeader/README.md index bee887d..f40f5ab 100644 --- a/src/components/AsideHeader/README.md +++ b/src/components/AsideHeader/README.md @@ -173,7 +173,7 @@ export const Aside: FC = () => { Top Alert can be useful for displaying important information that users need to know. This alert is often appeared in all pages like call to action or warning. -You can customize the inner content, make alert closeable if necessary. For reading top alert height see value from CSS variable `--gn-aside-top-panel-height`. +You can customize the inner content, make alert closeable if necessary. For reading top alert height see value from CSS variable `--gn-top-alert-height`. | Name | Description | Type | Default | | :-------------- | :----------------------------------------------------------------- | :------------------------------------------------------------------------------------------------: | :---------: | @@ -199,7 +199,7 @@ You can customize the inner content, make alert closeable if necessary. For read | `--gn-aside-header-expanded-background-color` | Expanded navigation background color | `--g-color-base-background` | | `--gn-aside-header-divider-horizontal-color` | All horizontal divider line color | `--g-color-line-generic` | | `--gn-aside-header-divider-vertical-color` | Vertical divider line color between `AsideHeader` and content | `--g-color-line-generic` | -| `--gn-aside-top-panel-height` | **Read only**.`AsideHeader` top alert height | 0px | +| `--gn-top-alert-height` | **Read only**.`AsideHeader` top alert height | 0px | | `--gn-aside-header-padding-top` | Navigation top padding. May be helpful when logo and subheader items hide | | | Item | | | | `--gn-aside-header-general-item-icon-color` | Icon color for Subheader and Footer items | `--g-color-text-primary` | diff --git a/src/components/AsideHeader/__stories__/AsideHeaderShowcase.tsx b/src/components/AsideHeader/__stories__/AsideHeaderShowcase.tsx index 27a125b..61e7977 100644 --- a/src/components/AsideHeader/__stories__/AsideHeaderShowcase.tsx +++ b/src/components/AsideHeader/__stories__/AsideHeaderShowcase.tsx @@ -11,7 +11,7 @@ import { eventBroker, } from '@gravity-ui/uikit'; -import {AsideHeader, AsideHeaderTopAlertProps, FooterItem} from '../..'; +import {AsideHeader, FooterItem, TopAlertProps} from '../..'; import {ASIDE_HEADER_ICON_SIZE} from '../../constants'; import {MenuItem, OpenModalSubscriber} from '../../types'; import {cn} from '../../utils/cn'; @@ -39,7 +39,7 @@ enum Panel { interface AsideHeaderShowcaseProps { multipleTooltip?: boolean; initialCompact?: boolean; - topAlert?: AsideHeaderTopAlertProps; + topAlert?: TopAlertProps; customBackground?: React.ReactNode; customBackgroundClassName?: string; headerDecoration?: boolean; diff --git a/src/components/AsideHeader/components/PageLayout/PageLayout.tsx b/src/components/AsideHeader/components/PageLayout/PageLayout.tsx index 576c228..9932223 100644 --- a/src/components/AsideHeader/components/PageLayout/PageLayout.tsx +++ b/src/components/AsideHeader/components/PageLayout/PageLayout.tsx @@ -8,8 +8,8 @@ import {b} from '../../utils'; import '../../AsideHeader.scss'; -const TopPanel = React.lazy(() => - import('../TopPanel').then((module) => ({default: module.TopPanel})), +const TopAlert = React.lazy(() => + import('../../../TopAlert').then((module) => ({default: module.TopAlert})), ); export interface PageLayoutProps extends PropsWithChildren {} @@ -28,7 +28,7 @@ const Layout = ({compact, className, children, topAlert}: PageLayoutProps) => { > {topAlert && ( - + )}
{children}
diff --git a/src/components/AsideHeader/components/TopPanel.tsx b/src/components/AsideHeader/components/TopPanel.tsx deleted file mode 100644 index 0adafd5..0000000 --- a/src/components/AsideHeader/components/TopPanel.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import React from 'react'; - -import {Alert} from '@gravity-ui/uikit'; - -import {AsideHeaderTopAlertProps} from '../../types'; -import {useAsideHeaderTopPanel} from '../useAsideHeaderTopPanel'; -import {b} from '../utils'; - -type Props = { - topAlert?: AsideHeaderTopAlertProps; -}; - -export const TopPanel = ({topAlert}: Props) => { - const {topRef, updateTopSize} = useAsideHeaderTopPanel({topAlert}); - - const [opened, setOpened] = React.useState(true); - - const handleClose = React.useCallback(() => { - setOpened(false); - topAlert?.onCloseTopAlert?.(); - }, [topAlert]); - - React.useEffect(() => { - if (!opened) { - updateTopSize(); - } - }, [opened, updateTopSize]); - - if (!topAlert || !topAlert.message) { - return null; - } - - return ( -
- {opened && ( - - -
-
- )} -
- ); -}; diff --git a/src/components/AsideHeader/components/index.ts b/src/components/AsideHeader/components/index.ts index 90c9941..6b3499c 100644 --- a/src/components/AsideHeader/components/index.ts +++ b/src/components/AsideHeader/components/index.ts @@ -1,2 +1 @@ export {FirstPanel} from './FirstPanel'; -export {TopPanel} from './TopPanel'; diff --git a/src/components/AsideHeader/types.tsx b/src/components/AsideHeader/types.tsx index fb1c5d0..a0f2310 100644 --- a/src/components/AsideHeader/types.tsx +++ b/src/components/AsideHeader/types.tsx @@ -2,20 +2,14 @@ import {QAProps} from '@gravity-ui/uikit'; import {RenderContentType} from '../Content'; import {DrawerItemProps} from '../Drawer/Drawer'; -import { - AsideHeaderTopAlertProps, - LogoProps, - MenuItem, - OpenModalSubscriber, - SubheaderMenuItem, -} from '../types'; +import {LogoProps, MenuItem, OpenModalSubscriber, SubheaderMenuItem, TopAlertProps} from '../types'; import {AsideHeaderContextType} from './AsideHeaderContext'; export interface LayoutProps { compact: boolean; className?: string; - topAlert?: AsideHeaderTopAlertProps; + topAlert?: TopAlertProps; } export interface AsideHeaderGeneralProps extends QAProps { @@ -25,7 +19,7 @@ export interface AsideHeaderGeneralProps extends QAProps { collapseTitle?: string; expandTitle?: string; menuMoreTitle?: string; - topAlert?: AsideHeaderTopAlertProps; + topAlert?: TopAlertProps; customBackground?: React.ReactNode; customBackgroundClassName?: string; hideCollapseButton?: boolean; diff --git a/src/components/AsideHeader/useAsideHeaderTopPanel.tsx b/src/components/AsideHeader/useAsideHeaderTopPanel.tsx deleted file mode 100644 index 85fb21a..0000000 --- a/src/components/AsideHeader/useAsideHeaderTopPanel.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import React from 'react'; - -import debounceFn from 'lodash/debounce'; - -import {AsideHeaderTopAlertProps} from '../types'; - -type AsideHeaderTopPanel = { - topRef: React.RefObject; - updateTopSize: () => void; -}; - -const G_ROOT_CLASS_NAME = 'g-root'; - -const useRefHeight = (ref: React.RefObject) => { - const [topHeight, setTopHeight] = React.useState(0); - React.useEffect(() => { - if (ref.current) { - const {current} = ref; - setTopHeight(current.clientHeight); - } - }, [ref]); - return topHeight; -}; - -export const useAsideHeaderTopPanel = ({ - topAlert, -}: { - topAlert?: AsideHeaderTopAlertProps; -}): AsideHeaderTopPanel => { - const topRef = React.useRef(null); - const topHeight = useRefHeight(topRef); - - const setAsideTopPanelHeight = React.useCallback((clientHeight: number) => { - const gRootElement = document - .getElementsByClassName(G_ROOT_CLASS_NAME) - .item(0) as HTMLElement | null; - gRootElement?.style.setProperty('--gn-aside-top-panel-height', clientHeight + 'px'); - }, []); - - const updateTopSize = React.useCallback(() => { - if (topRef.current) { - setAsideTopPanelHeight(topRef.current?.clientHeight || 0); - } - }, [topRef, setAsideTopPanelHeight]); - - React.useLayoutEffect(() => { - const updateTopSizeDebounce = debounceFn(updateTopSize, 200, {leading: true}); - - if (topAlert) { - window.addEventListener('resize', updateTopSizeDebounce); - updateTopSizeDebounce(); - } - return () => { - window.removeEventListener('resize', updateTopSizeDebounce); - setAsideTopPanelHeight(0); - }; - }, [topAlert, topHeight, topRef, updateTopSize, setAsideTopPanelHeight]); - - return { - topRef, - updateTopSize, - }; -}; diff --git a/src/components/MobileHeader/MobileHeader.scss b/src/components/MobileHeader/MobileHeader.scss index 3f375a0..b9fd687 100644 --- a/src/components/MobileHeader/MobileHeader.scss +++ b/src/components/MobileHeader/MobileHeader.scss @@ -9,11 +9,14 @@ $block: '.#{variables.$ns}mobile-header'; background-color: var(--g-color-base-background); - &__header { - background-color: var(--g-color-base-background); - border-bottom: 1px solid var(--g-color-line-generic); + &__top { position: sticky; top: 0; + background-color: var(--g-color-base-background); + } + + &__header { + border-bottom: 1px solid var(--g-color-line-generic); padding: 0 10px; box-sizing: border-box; display: flex; diff --git a/src/components/MobileHeader/MobileHeader.tsx b/src/components/MobileHeader/MobileHeader.tsx index a0b4c86..03d319b 100644 --- a/src/components/MobileHeader/MobileHeader.tsx +++ b/src/components/MobileHeader/MobileHeader.tsx @@ -1,10 +1,10 @@ -import React, {useCallback, useEffect, useMemo, useState} from 'react'; +import React, {Suspense, useCallback, useEffect, useMemo, useState} from 'react'; import {useForwardRef} from '../../hooks/useForwardRef'; import {Content, RenderContentType} from '../Content'; import {Drawer, DrawerItem, DrawerItemProps} from '../Drawer/Drawer'; import {MobileLogo} from '../MobileLogo'; -import {LogoProps} from '../types'; +import {LogoProps, TopAlertProps} from '../types'; import {block} from '../utils/cn'; import {Burger} from './Burger/Burger'; @@ -25,6 +25,10 @@ import {MobileHeaderEvent, MobileHeaderEventOptions, MobileMenuItem} from './typ import './MobileHeader.scss'; +const TopAlert = React.lazy(() => + import('../TopAlert').then((module) => ({default: module.TopAlert})), +); + const b = block('mobile-header'); type PanelName = DrawerItemProps['id'] | null; @@ -44,6 +48,7 @@ export interface MobileHeaderProps { burgerCloseTitle?: string; burgerOpenTitle?: string; panelItems?: PanelItem[]; + topAlert?: TopAlertProps; renderContent?: RenderContentType; sideItemRenderContent?: RenderContentType; onEvent?: (itemName: string, eventName: MobileHeaderEvent) => void; @@ -67,6 +72,7 @@ export const MobileHeader = React.forwardRef( className, contentClassName, overlapPanel, + topAlert, }, ref, ): React.ReactElement => { @@ -261,24 +267,31 @@ export const MobileHeader = React.forwardRef( return (
-
- onPanelToggle(BURGER_PANEL_ITEM_ID)} - className={b('burger')} - closeTitle={burgerCloseTitle} - openTitle={burgerOpenTitle} - /> - +
+ {topAlert && ( + + + + )} +
+ onPanelToggle(BURGER_PANEL_ITEM_ID)} + className={b('burger')} + closeTitle={burgerCloseTitle} + openTitle={burgerOpenTitle} + /> + -
{sideItemRenderContent?.({size})}
-
+
{sideItemRenderContent?.({size})}
+
+
{[burgerPanelItem, ...panelItems].map((item) => ( { + const {alertRef, updateTopSize} = useTopAlertHeight({alert}); + + const [opened, setOpened] = React.useState(true); + + const handleClose = React.useCallback(() => { + setOpened(false); + alert?.onCloseTopAlert?.(); + }, [alert]); + + React.useEffect(() => { + if (!opened) { + updateTopSize(); + } + }, [opened, updateTopSize]); + + if (!alert || !alert.message) { + return null; + } + + return ( +
+ {opened && ( + + {alert.message} + + ) : ( + alert.message + ) + } + actions={alert.actions} + onClose={alert.closable ? handleClose : undefined} + /> + )} +
+ ); +}; diff --git a/src/components/TopAlert/index.ts b/src/components/TopAlert/index.ts new file mode 100644 index 0000000..f2f13ad --- /dev/null +++ b/src/components/TopAlert/index.ts @@ -0,0 +1 @@ +export {TopAlert} from './TopAlert'; diff --git a/src/components/TopAlert/useTopAlertHeight.ts b/src/components/TopAlert/useTopAlertHeight.ts new file mode 100644 index 0000000..0a99ba2 --- /dev/null +++ b/src/components/TopAlert/useTopAlertHeight.ts @@ -0,0 +1,45 @@ +import React from 'react'; + +import debounceFn from 'lodash/debounce'; + +import {TopAlertProps} from '../types'; + +type TopAlertHeightControls = { + alertRef: React.RefObject; + updateTopSize: () => void; +}; + +const G_ROOT_CLASS_NAME = 'g-root'; + +export const useTopAlertHeight = ({alert}: {alert?: TopAlertProps}): TopAlertHeightControls => { + const alertRef = React.useRef(null); + + const setAsideTopPanelHeight = React.useCallback((clientHeight: number) => { + const gRootElement = document + .getElementsByClassName(G_ROOT_CLASS_NAME) + .item(0) as HTMLElement | null; + gRootElement?.style.setProperty('--gn-top-alert-height', clientHeight + 'px'); + }, []); + + const updateTopSize = React.useCallback(() => { + setAsideTopPanelHeight(alertRef.current?.clientHeight || 0); + }, [setAsideTopPanelHeight]); + + React.useLayoutEffect(() => { + const updateTopSizeDebounce = debounceFn(updateTopSize, 200, {leading: true}); + + if (alert) { + window.addEventListener('resize', updateTopSizeDebounce); + updateTopSizeDebounce(); + } + return () => { + window.removeEventListener('resize', updateTopSizeDebounce); + setAsideTopPanelHeight(0); + }; + }, [alert, alertRef, updateTopSize, setAsideTopPanelHeight]); + + return { + alertRef, + updateTopSize, + }; +}; diff --git a/src/components/types.ts b/src/components/types.ts index 30c7a79..028187f 100644 --- a/src/components/types.ts +++ b/src/components/types.ts @@ -74,7 +74,8 @@ export interface LogoProps { 'aria-labelledby'?: string; } -export type AsideHeaderTopAlertProps = { +export interface TopAlertProps { + align?: AlertProps['align']; message: AlertProps['message']; title?: AlertProps['title']; icon?: AlertProps['icon']; @@ -85,4 +86,4 @@ export type AsideHeaderTopAlertProps = { centered?: boolean; dense?: boolean; onCloseTopAlert?: () => void; -}; +}