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 2 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
249 changes: 41 additions & 208 deletions src/components/AsideHeader/AsideHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,224 +1,57 @@
import React from 'react';
import {block} from '../utils/cn';
import React, {useCallback} 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 {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 controlMenuButtonIcon from '../../../assets/icons/control-menu-button.svg';
import headerDividerCollapsedIcon from '../../../assets/icons/divider-collapsed.svg';
import {Content} from '../Content';

import {AsideHeaderContextProvider} from './AsideHeaderContext';
import {
AsideHeaderGeneralProps,
AsideHeaderDefaultProps,
AsideHeaderInnerProps,
} from './asideHeaderTypes';

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;
}
import {FirstPanel} from './components';
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 {b} from './asideHeaderUtils';

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;

const size = compact ? ASIDE_HEADER_COMPACT_WIDTH : ASIDE_HEADER_EXPANDED_WIDTH;

return (
<AsideHeaderContextProvider value={{compact, size}}>
<div className={b({compact}, className)}>
<div className={b('pane-container')}>
{this.renderFirstPane(size)}
{this.renderSecondPane(size)}
</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>
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],
);

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

return (
<div className={b('footer')}>
{renderFooter?.({
size,
compact,
asideRef: this.asideRef,
})}
return (
<AsideHeaderContextProvider value={{...props, size, onItemClick}}>
<div className={b({compact}, className)}>
<div className={b('pane-container')}>
{/* First Panel */}
<FirstPanel />
{/* Second Panel */}
<Content
size={size}
renderContent={props.renderContent}
className={b('content')}
/>
</div>
</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);
};
}
</AsideHeaderContextProvider>
);
};
27 changes: 19 additions & 8 deletions src/components/AsideHeader/AsideHeaderContext.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,27 @@
import React from 'react';
import {ASIDE_HEADER_COMPACT_WIDTH} from '../constants';
import {MenuItem} from '../types';
import {AsideHeaderInnerProps} from './asideHeaderTypes';

interface AsideHeaderContextType {
compact: boolean;
export interface AsideHeaderContextType extends AsideHeaderInnerProps {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This context is not intended to drag all the props into it, since it is exported outside. Let's try to explicitly pass props whenever possible, or create a separate context for internal use

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 AsideHeaderContext = React.createContext<AsideHeaderContextType | undefined>(
undefined,
);

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 AsideHeaderContext.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
10 changes: 9 additions & 1 deletion src/components/AsideHeader/__stories__/moc.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
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 = {
onItemClick: () => {},
logo: {text: () => null},
size: ASIDE_HEADER_EXPANDED_WIDTH,
};

export const menuItemsShowcase: MenuItem[] = [
{
id: 'overview',
Expand Down
30 changes: 30 additions & 0 deletions src/components/AsideHeader/asideHeaderTypes.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {RenderContentType} from '../Content';
import {DrawerItemProps} from '../Drawer/Drawer';
import {LogoProps, MenuItem, SubheaderMenuItem} from '../types';

export interface AsideHeaderGeneralProps {
logo: LogoProps;
compact?: boolean;
Lunory marked this conversation as resolved.
Show resolved Hide resolved
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;
}

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

export type AsideHeaderInnerProps = AsideHeaderGeneralProps & AsideHeaderDefaultProps;
3 changes: 3 additions & 0 deletions src/components/AsideHeader/asideHeaderUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import {block} from '../utils/cn';

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