Skip to content

Commit

Permalink
refactor(AsideHeader): asideHeader rewrited to hooks (#102)
Browse files Browse the repository at this point in the history
* refactor(AsideHeader): asideHeader rewrited to hooks

* chore: fixed ts issues

* chore: fixed review comments

* chore: files naming fixed

* chore: review comments
  • Loading branch information
catakot authored Sep 20, 2023
1 parent f40ecea commit 7b06b60
Show file tree
Hide file tree
Showing 14 changed files with 302 additions and 222 deletions.
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';

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,8 +42,6 @@ export const AsideHeaderShowcase: FC<AsideHeaderShowcaseProps> = ({
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}>) => {
Expand All @@ -61,7 +59,6 @@ export const AsideHeaderShowcase: FC<AsideHeaderShowcaseProps> = ({
</div>
</Modal>
<AsideHeader
ref={navRef}
logo={{
text: 'Service',
icon: logoIcon,
Expand Down Expand Up @@ -240,7 +237,9 @@ export const AsideHeaderShowcase: FC<AsideHeaderShowcaseProps> = ({
},
]}
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

0 comments on commit 7b06b60

Please sign in to comment.