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

refactor(AsideHeader): asideHeader rewrited to hooks #102

Merged
merged 5 commits into from
Sep 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
242 changes: 38 additions & 204 deletions src/components/AsideHeader/AsideHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,224 +1,58 @@
import React from 'react';
import {block} from '../utils/cn';
import React, {useCallback, useMemo} from 'react';

import {MenuItem, LogoProps, SubheaderMenuItem} from '../types';
import {MenuItem} from '../types';

import {ASIDE_HEADER_COMPACT_WIDTH, ASIDE_HEADER_EXPANDED_WIDTH} from '../constants';

import {Button, Icon} from '@gravity-ui/uikit';
import {Content} from '../Content';

import {Drawer, DrawerItem, DrawerItemProps} from '../Drawer/Drawer';
import {Logo} from '../Logo/Logo';
import {CompositeBar} from '../CompositeBar/CompositeBar';
import {Content, RenderContentType} from '../Content';
import {fakeDisplayName} from '../helpers';
import i18n from './i18n';
import {AsideHeaderContextProvider, AsideHeaderInnerContextProvider} from './AsideHeaderContext';
import {AsideHeaderGeneralProps, AsideHeaderDefaultProps, AsideHeaderInnerProps} from './types';

import controlMenuButtonIcon from '../../../assets/icons/control-menu-button.svg';
import headerDividerCollapsedIcon from '../../../assets/icons/divider-collapsed.svg';

import {AsideHeaderContextProvider} from './AsideHeaderContext';
import {FirstPanel} from './components';
import {b} from './utils';
Copy link
Collaborator

Choose a reason for hiding this comment

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

Let's move the imports above the style imports


import './AsideHeader.scss';

// TODO: remove temporary fix for expand button
const NotIcon = fakeDisplayName('NotIcon', Icon);

const b = block('aside-header');

interface AsideHeaderGeneralProps {
logo: LogoProps;
compact: boolean;
multipleTooltip?: boolean;
className?: string;
collapseTitle?: string;
expandTitle?: string;
menuMoreTitle?: string;
renderContent?: RenderContentType;
renderFooter?: (data: {
size: number;
compact: boolean;
asideRef: React.RefObject<HTMLDivElement>;
}) => React.ReactNode;
onClosePanel?: () => void;
onChangeCompact?: (compact: boolean) => void;
}

interface AsideHeaderDefaultProps {
panelItems: DrawerItemProps[];
subheaderItems: SubheaderMenuItem[];
menuItems: MenuItem[];
headerDecoration: boolean;
}

export interface AsideHeaderProps
extends AsideHeaderGeneralProps,
Partial<AsideHeaderDefaultProps> {}

type AsideHeaderInnerProps = AsideHeaderGeneralProps & AsideHeaderDefaultProps;

export class AsideHeader extends React.Component<AsideHeaderInnerProps> {
static defaultProps: AsideHeaderDefaultProps = {
panelItems: [],
subheaderItems: [],
menuItems: [],
headerDecoration: true,
};

asideRef = React.createRef<HTMLDivElement>();

render() {
const {className, compact} = this.props;
export const AsideHeader = (props: AsideHeaderInnerProps) => {
const {className, compact, onClosePanel} = props;

const size = compact ? ASIDE_HEADER_COMPACT_WIDTH : ASIDE_HEADER_EXPANDED_WIDTH;

const onItemClick = useCallback(
(
item: MenuItem,
collapsed: boolean,
event: React.MouseEvent<HTMLDivElement, MouseEvent>,
) => {
onClosePanel?.();
item.onItemClick?.(item, collapsed, event);
},
[onClosePanel],
);

const size = compact ? ASIDE_HEADER_COMPACT_WIDTH : ASIDE_HEADER_EXPANDED_WIDTH;
const asideHeaderContextValue = useMemo(() => ({size, compact}), [compact, size]);

return (
<AsideHeaderContextProvider value={{compact, size}}>
return (
<AsideHeaderContextProvider value={asideHeaderContextValue}>
<AsideHeaderInnerContextProvider value={{...props, size, onItemClick}}>
<div className={b({compact}, className)}>
<div className={b('pane-container')}>
{this.renderFirstPane(size)}
{this.renderSecondPane(size)}
{/* First Panel */}
<FirstPanel />
{/* Second Panel */}
<Content
size={size}
renderContent={props.renderContent}
className={b('content')}
/>
</div>
</div>
</AsideHeaderContextProvider>
);
}

private renderFirstPane = (size: number) => {
const {menuItems, panelItems, headerDecoration, multipleTooltip, menuMoreTitle} =
this.props;

return (
<React.Fragment>
<div className={b('aside')} style={{width: size}}>
<div className={b('aside-popup-anchor')} ref={this.asideRef} />
<div className={b('aside-content', {['with-decoration']: headerDecoration})}>
{this.renderHeader()}
{menuItems?.length ? (
<CompositeBar
type="menu"
items={menuItems}
menuMoreTitle={menuMoreTitle ?? i18n('label_more')}
onItemClick={this.onItemClick}
multipleTooltip={multipleTooltip}
/>
) : (
<div className={b('menu-items')} />
)}
{this.renderFooter(size)}
{this.renderCollapseButton()}
</div>
</div>

{panelItems && this.renderPanels(size)}
</React.Fragment>
);
};

private renderSecondPane = (size: number) => {
return (
<Content
size={size}
renderContent={this.props.renderContent}
className={b('content')}
/>
);
};

private renderLogo = () => <Logo {...this.props.logo} onClick={this.onLogoClick} />;

private renderHeader = () => (
<div className={b('header', {['with-decoration']: this.props.headerDecoration})}>
{this.renderLogo()}

<CompositeBar
type="subheader"
items={this.props.subheaderItems}
onItemClick={this.onItemClick}
/>

<Icon
data={headerDividerCollapsedIcon}
className={b('header-divider')}
width={ASIDE_HEADER_COMPACT_WIDTH}
height="29"
/>
</div>
</AsideHeaderInnerContextProvider>
</AsideHeaderContextProvider>
);

private renderFooter = (size: number) => {
const {renderFooter, compact} = this.props;

return (
<div className={b('footer')}>
{renderFooter?.({
size,
compact,
asideRef: this.asideRef,
})}
</div>
);
};

private renderPanels = (size: number) => {
const {panelItems} = this.props;

return (
<Drawer
className={b('panels')}
onVeilClick={this.onCloseDrawer}
onEscape={this.onCloseDrawer}
style={{left: size}}
>
{panelItems.map((item) => (
<DrawerItem key={item.id} {...item} />
))}
</Drawer>
);
};

private renderCollapseButton = () => {
const {expandTitle, collapseTitle, compact} = this.props;
const buttonTitle = compact
? expandTitle || i18n('button_expand')
: collapseTitle || i18n('button_collapse');

return (
<Button
className={b('collapse-button', {compact})}
view="flat"
onClick={this.onCollapseButtonClick}
title={buttonTitle}
>
<NotIcon
data={controlMenuButtonIcon}
className={b('collapse-icon')}
width="16"
height="10"
/>
</Button>
);
};

private onCollapseButtonClick = () => {
this.props.onChangeCompact?.(!this.props.compact);
};

private onCloseDrawer = () => {
this.props.onClosePanel?.();
};

private onItemClick = (
item: MenuItem,
collapsed: boolean,
event: React.MouseEvent<HTMLDivElement, MouseEvent>,
) => {
this.props.onClosePanel?.();
item.onItemClick?.(item, collapsed, event);
};

private onLogoClick = (event: React.MouseEvent<HTMLElement, MouseEvent>) => {
this.props.onClosePanel?.();
this.props.logo.onClick?.(event);
};
}
};
50 changes: 42 additions & 8 deletions src/components/AsideHeader/AsideHeaderContext.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,50 @@
import React from 'react';
import {ASIDE_HEADER_COMPACT_WIDTH} from '../constants';
import {MenuItem} from '../types';
import {AsideHeaderInnerProps} from './types';

interface AsideHeaderContextType {
compact: boolean;
export interface AsideHeaderInnerContextType extends AsideHeaderInnerProps {
size: number;
onItemClick: (
item: MenuItem,
collapsed: boolean,
event: React.MouseEvent<HTMLDivElement, MouseEvent>,
) => void;
}

export const AsideHeaderContext = React.createContext<AsideHeaderContextType>({
compact: false,
size: ASIDE_HEADER_COMPACT_WIDTH,
});
export const AsideHeaderInnerContext = React.createContext<AsideHeaderInnerContextType | undefined>(
undefined,
);
AsideHeaderInnerContext.displayName = 'AsideHeaderInnerContext';

export const AsideHeaderInnerContextProvider = AsideHeaderInnerContext.Provider;

export const useAsideHeaderInnerContext = (): AsideHeaderInnerContextType => {
const contextValue = React.useContext(AsideHeaderInnerContext);
if (contextValue === undefined) {
throw new Error(`AsideHeaderInnerContext is not initialized.
Please check if you wrapped your component with AsideHeaderInnerContext.Provider`);
}
return contextValue;
};

export interface AsideHeaderContextType {
compact?: boolean;
size: number;
}

export const AsideHeaderContext = React.createContext<AsideHeaderContextType | undefined>(
undefined,
);
AsideHeaderContext.displayName = 'AsideHeaderContext';

export const AsideHeaderContextProvider = AsideHeaderContext.Provider;

export const useAsideHeaderContext = () => React.useContext(AsideHeaderContext);
export const useAsideHeaderContext = (): AsideHeaderContextType => {
const contextValue = React.useContext(AsideHeaderContext);
if (contextValue === undefined) {
throw new Error(`AsideHeaderContext is not initialized.
Please check if you wrapped your component with AsideHeader
Context.Provider`);
}
return contextValue;
};
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,11 @@
const [headerDecoration, setHeaderDecoration] = React.useState<string>(BOOLEAN_OPTIONS.Yes);
const [isModalOpen, setIsModalOpen] = React.useState<boolean>(false);

const navRef = React.useRef<AsideHeader>(null);

const openModalSubscriber = (callback: OpenModalSubscriber) => {
// @ts-ignore
eventBroker.subscribe((data: EventBrokerData<{layersCount: number}>) => {
if (data?.eventId === 'layerschange') {
callback(isModalOpen && data?.meta?.layersCount !== 0);

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

View workflow job for this annotation

GitHub Actions / Verify Files

Expected return with your callback function
}
});
};
Expand All @@ -61,7 +59,6 @@
</div>
</Modal>
<AsideHeader
ref={navRef}
logo={{
text: 'Service',
icon: logoIcon,
Expand Down Expand Up @@ -123,7 +120,7 @@
]}
compact={compact}
multipleTooltip={multipleTooltip}
renderFooter={({compact}) => (

Check warning on line 123 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 Expand Up @@ -240,7 +237,9 @@
},
]}
onClosePanel={() => setVisiblePanel(undefined)}
onChangeCompact={setCompact}
onChangeCompact={(v) => {
setCompact(v);
}}
/>
</div>
);
Expand Down
8 changes: 7 additions & 1 deletion src/components/AsideHeader/__stories__/moc.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import React from 'react';
import {Gear, Plus} from '@gravity-ui/icons';

import {MenuItem} from 'src/components/types';
import {MenuItem} from '../../types';
import {ASIDE_HEADER_EXPANDED_WIDTH} from '../../constants';
import {AsideHeaderContextType} from '../AsideHeaderContext';

function renderTag(tag: string) {
return <div className="composite-bar-showcase__tag">{tag.toUpperCase()}</div>;
}

export const EMPTY_CONTEXT_VALUE: AsideHeaderContextType = {
size: ASIDE_HEADER_EXPANDED_WIDTH,
};

export const menuItemsShowcase: MenuItem[] = [
{
id: 'overview',
Expand Down
Loading
Loading