Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: aside header top alert #118

Merged
merged 15 commits into from
Nov 23, 2023
Merged
26 changes: 25 additions & 1 deletion src/components/AsideHeader/AsideHeader.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

$block: '.#{variables.$ns}aside-header';

:root {
--gn-aside-top-panel-height: 0px;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is it not in the .g-root?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then we can not change this value from document object

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use the correct selector

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use the correct selector

Ok, changed to getElementsByClassName selector

}

.g-root {
--gn-aside-header-background-color: var(--g-color-base-warning-light);
--gn-aside-header-collapse-button-divider-line-color: var(
Expand Down Expand Up @@ -43,6 +47,7 @@ $block: '.#{variables.$ns}aside-header';
flex-direction: column;
background-color: var(--g-color-base-background);
z-index: 100;
max-height: calc(100vh - var(--gn-aside-top-panel-height));

box-sizing: border-box;

Expand Down Expand Up @@ -176,8 +181,9 @@ $block: '.#{variables.$ns}aside-header';
left: 0;
right: 0;
bottom: 0;
top: 0;
top: var(--gn-aside-top-panel-height);
overflow: auto;
max-height: calc(100vh - var(--gn-aside-top-panel-height));
}

&__panel {
Expand All @@ -192,6 +198,24 @@ $block: '.#{variables.$ns}aside-header';
flex-direction: row;
}

&__pane-top-divider {
height: 1px;
background-color: var(--gn-aside-header-collapse-button-divider-line-color);
margin-top: -1px;
}

&__pane-top-alert {
&_centered {
display: flex;
justify-content: space-around;
}

&_dense {
padding-top: var(--g-spacing-2);
padding-bottom: var(--g-spacing-2);
}
}

&_reverse #{$block}__pane-container {
flex-direction: row-reverse;
}
Expand Down
4 changes: 2 additions & 2 deletions src/components/AsideHeader/AsideHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ import {PageLayoutAside} from './components/PageLayout/PageLayoutAside';
* </PageLayout>
*/
export const AsideHeader = React.forwardRef<HTMLDivElement, AsideHeaderProps>(
({compact, className, ...props}, ref) => {
({compact, className, topAlert, ...props}, ref) => {
return (
<PageLayout compact={compact} className={className}>
<PageLayout compact={compact} className={className} topAlert={topAlert}>
<PageLayoutAside ref={ref} {...props} />
<PageLayout.Content renderContent={props.renderContent} />
</PageLayout>
Expand Down
20 changes: 20 additions & 0 deletions src/components/AsideHeader/__stories__/AsideHeader.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,23 @@ AdvancedUsage.args = {
multipleTooltip: false,
initialCompact: true,
};

const TopAlertTemplate: StoryFn = (args) => <AsideHeaderShowcase {...args} />;
export const HeaderAlert = TopAlertTemplate.bind({});
HeaderAlert.args = {
topAlert: {
title: 'Maintenance',
view: 'filled',
message: 'Scheduled maintenance is being performed',
closable: true,
},
};

export const HeaderAlertCentered = TopAlertTemplate.bind({});
HeaderAlertCentered.args = {
topAlert: {
view: 'filled',
message: 'Scheduled maintenance is being performed',
centered: true,
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ body {
&__settings-panel,
&__search-panel {
width: 300px;
height: 100%;
height: calc(100% - 40px);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix drawer padding at 20px for remove unexpected scroll

padding: 20px;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, {FC, useState} from 'react';
import React, {FC} from 'react';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe then we’ll remove FC import too?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed


import {
Button,
Expand All @@ -11,7 +11,7 @@
} from '@gravity-ui/uikit';
import {Bug, Gear, Magnifier} from '@gravity-ui/icons';

import {AsideHeader, FooterItem} from '../..';
import {AsideHeader, FooterItem, AsideHeaderTopAlertProps} from '../..';
import {cn} from '../../utils/cn';
import {menuItemsShowcase, text as placeholderText} from './moc';
import {MenuItem, OpenModalSubscriber} from '../../types';
Expand All @@ -38,11 +38,13 @@
interface AsideHeaderShowcaseProps {
multipleTooltip?: boolean;
initialCompact?: boolean;
topAlert?: AsideHeaderTopAlertProps;
}

export const AsideHeaderShowcase: FC<AsideHeaderShowcaseProps> = ({
multipleTooltip = false,
initialCompact = false,
topAlert,
}) => {
const ref = React.useRef<HTMLDivElement>(null);
const [popupVisible, setPopupVisible] = React.useState(false);
Expand All @@ -59,12 +61,12 @@
const openModalCount = data?.meta?.layers?.filter(
({type}) => type === 'modal',
).length;
callback(openModalCount !== 0);

Check warning on line 64 in src/components/AsideHeader/__stories__/AsideHeaderShowcase.tsx

View workflow job for this annotation

GitHub Actions / Verify Files

Expected return with your callback function
}
});
};

const [menuItems, setMenuItems] = useState<MenuItem[]>([
const [menuItems, setMenuItems] = React.useState<MenuItem[]>([
...menuItemsShowcase,
{
id: 'components',
Expand Down Expand Up @@ -138,7 +140,8 @@
compact={compact}
multipleTooltip={multipleTooltip}
openModalSubscriber={openModalSubscriber}
topAlert={topAlert}
renderFooter={({compact, asideRef}) => (

Check warning on line 144 in src/components/AsideHeader/__stories__/AsideHeaderShowcase.tsx

View workflow job for this annotation

GitHub Actions / Verify Files

'compact' is already declared in the upper scope
<React.Fragment>
<FooterItem
compact={compact}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, {PropsWithChildren, useMemo} from 'react';
import {AsideHeaderContextProvider, useAsideHeaderContext} from '../../AsideHeaderContext';
import {Content, ContentProps} from '../../../Content';
import {TopPanel} from '..';
import {ASIDE_HEADER_COMPACT_WIDTH, ASIDE_HEADER_EXPANDED_WIDTH} from '../../../constants';
import {LayoutProps} from '../../types';
import {b} from '../../utils';
Expand All @@ -11,7 +12,7 @@ export interface PageLayoutProps extends PropsWithChildren<LayoutProps> {
reverse?: boolean;
}

const Layout = ({compact, reverse, className, children}: PageLayoutProps) => {
const Layout = ({compact, reverse, className, children, topAlert}: PageLayoutProps) => {
const size = compact ? ASIDE_HEADER_COMPACT_WIDTH : ASIDE_HEADER_EXPANDED_WIDTH;
const asideHeaderContextValue = useMemo(() => ({size, compact}), [compact, size]);

Expand All @@ -23,6 +24,7 @@ const Layout = ({compact, reverse, className, children}: PageLayoutProps) => {
...({'--gn-aside-header-size': `${size}px`} as React.CSSProperties),
}}
>
{topAlert && <TopPanel topAlert={topAlert} />}
<div className={b('pane-container')}>{children}</div>
</div>
</AsideHeaderContextProvider>
Expand Down
55 changes: 55 additions & 0 deletions src/components/AsideHeader/components/TopPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React from 'react';
import {Alert} from '@gravity-ui/uikit';

import {b} from '../utils';
import {AsideHeaderTopAlertProps} from '../../types';
import {useAsideHeaderTopPanel} from '../useAsideHeaderTopPanel';

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 (
<div ref={topRef} className={b('pane-top', {opened})}>
{opened && (
<React.Fragment>
<Alert
className={b('pane-top-alert', {
centered: topAlert.centered,
dense: topAlert.dense,
})}
corners="square"
layout="horizontal"
theme={topAlert.theme || 'warning'}
icon={topAlert.icon}
title={topAlert.title}
message={topAlert.message}
actions={topAlert.actions}
onClose={topAlert.closable ? handleClose : undefined}
/>
<div className={b('pane-top-divider')}></div>
</React.Fragment>
)}
</div>
);
};
1 change: 1 addition & 0 deletions src/components/AsideHeader/components/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export {FirstPanel} from './FirstPanel';
export {TopPanel} from './TopPanel';
10 changes: 9 additions & 1 deletion src/components/AsideHeader/types.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import {RenderContentType} from '../Content';
import {DrawerItemProps} from '../Drawer/Drawer';
import {LogoProps, MenuItem, SubheaderMenuItem, OpenModalSubscriber} from '../types';
import {
LogoProps,
MenuItem,
SubheaderMenuItem,
OpenModalSubscriber,
AsideHeaderTopAlertProps,
} from '../types';
import {AsideHeaderContextType} from './AsideHeaderContext';

export interface LayoutProps {
compact: boolean;
className?: string;
topAlert?: AsideHeaderTopAlertProps;
}

export interface AsideHeaderGeneralProps {
Expand All @@ -16,6 +23,7 @@ export interface AsideHeaderGeneralProps {
collapseTitle?: string;
expandTitle?: string;
menuMoreTitle?: string;
topAlert?: AsideHeaderTopAlertProps;
renderContent?: RenderContentType;
renderFooter?: (data: {
size: number;
Expand Down
60 changes: 60 additions & 0 deletions src/components/AsideHeader/useAsideHeaderTopPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React from 'react';

import debounceFn from 'lodash/debounce';
import {AsideHeaderTopAlertProps} from '../types';

type AsideHeaderTopPanel = {
topRef: React.RefObject<HTMLDivElement>;
updateTopSize: () => void;
};

const useRefHeight = (ref: React.RefObject<HTMLDivElement>) => {
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<HTMLDivElement>(null);
const topHeight = useRefHeight(topRef);

const setAsideTopPanelHeight = React.useCallback((clientHeight: number) => {
document.documentElement.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);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

set top panel size to zero for correct destroy component behaviour with iframe switching at storybook

};
}, [topAlert, topHeight, topRef, updateTopSize]);

Check warning on line 54 in src/components/AsideHeader/useAsideHeaderTopPanel.tsx

View workflow job for this annotation

GitHub Actions / Verify Files

React Hook React.useLayoutEffect has a missing dependency: 'setAsideTopPanelHeight'. Either include it or remove the dependency array

return {
topRef,
updateTopSize,
};
};
11 changes: 7 additions & 4 deletions src/components/Content/Content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,14 @@ export const Content: React.FC<ContentProps> = ({
renderContent,
children,
}) => {
const style: React.CSSProperties = {
[cssSizeVariableName]: `${size}px`,
maxHeight: 'calc(100vh - var(--gn-aside-top-panel-height))',
overflowY: 'auto',
};

return (
<div
className={className}
style={{...({[cssSizeVariableName]: `${size}px`} as React.CSSProperties)}}
>
<div className={className} style={style}>
{typeof renderContent === 'function' ? (
<RenderContent size={size} renderContent={renderContent} />
) : (
Expand Down
15 changes: 14 additions & 1 deletion src/components/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import {IconProps} from '@gravity-ui/uikit';
import {IconProps, AlertProps} from '@gravity-ui/uikit';

import {ItemProps} from 'src/components/CompositeBar/Item/Item';

Expand Down Expand Up @@ -67,3 +67,16 @@ export interface LogoProps {
wrapper?: (node: React.ReactNode, compact: boolean) => React.ReactNode;
onClick?: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void;
}

export type AsideHeaderTopAlertProps = {
message: AlertProps['message'];
title?: AlertProps['title'];
icon?: AlertProps['icon'];
view?: AlertProps['view'];
theme?: AlertProps['theme'];
actions?: AlertProps['actions'];
closable?: boolean;
centered?: boolean;
dense?: boolean;
onCloseTopAlert?: () => void;
};
Loading