-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(AsideHeader): asideHeader rewrited to hooks (#102)
* refactor(AsideHeader): asideHeader rewrited to hooks * chore: fixed ts issues * chore: fixed review comments * chore: files naming fixed * chore: review comments
- Loading branch information
Showing
14 changed files
with
302 additions
and
222 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}; | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.