UNPKG

nishant-design-system

Version:
299 lines (276 loc) 7.17 kB
// @flow strict import * as React from 'react'; import type {ColorTypes} from '../../types/typography'; import classify from '../../utils/classify'; import {Button} from '../Button'; import type {IconProps} from '../Icon'; import {Icon} from '../Icon'; import type {TextProps} from '../Text'; import { ButtonTextSmall, SubTitleExtraSmall, SubTitleMedium, TEXT_COLORS, } from '../Text'; import css from './SubMenu.module.css'; type ClassNames = $ReadOnly<{wrapper?: string}>; export const SubMenuItemIcon = ({ className, ...props }: IconProps): React.Node => ( <Icon {...props} size="small" color={TEXT_COLORS.inverseSecondary} className={classify(css.menuIcon, className)} /> ); SubMenuItemIcon.displayName = 'SubMenuItemIcon'; export const SubMenuItemText = ({ children, className, ...props }: TextProps): React.Node => ( <ButtonTextSmall {...props} className={classify(css.subMenuItemText, className)} color={TEXT_COLORS.inversePrimary} > {children} </ButtonTextSmall> ); SubMenuItemText.displayName = 'SubMenuItemText'; export type SubMenuItemActionProps = { children?: React.Node, color?: ColorTypes, className?: string, ... }; export const SubMenuItemAction = ({ children, className, ...props }: SubMenuItemActionProps): React.Node => ( <div {...props} className={classify(css.subMenuItemAction, className)}> {children} </div> ); SubMenuItemAction.displayName = 'SubMenuItemAction'; export type SubMenuItemProps = { classNames?: ClassNames, children?: React.Node, selectedMenuKey?: string, disabled?: boolean, onChange?: (selectedMenuKey: string) => mixed, menuKey: string, ... }; export const SubMenuItem: React$AbstractComponent< SubMenuItemProps, HTMLElement, > = React.forwardRef( ( { children, selectedMenuKey, disabled, classNames, onChange, menuKey, ...props }: SubMenuItemProps, ref, ): React.Node => { const onChangeHandler = () => { if (!disabled) { onChange && onChange(menuKey); } }; const onKeyDownHandler = (e) => { if (e.key === 'Enter') { onChangeHandler(); } }; const selected = selectedMenuKey === menuKey; const getNamedComp = (comp: string) => { const childrenArray = React.Children.toArray(children); if (childrenArray.length) { const nodes: React.Node[] = []; for (const child of childrenArray) { if (child?.type?.displayName === comp) { nodes.push( React.cloneElement(child, { selected, disabled, }), ); } } return nodes.length > 1 ? nodes : nodes[0]; } return null; }; return ( <div {...props} className={classify( css.menuItem, { [css.selected]: selected, [css.disabled]: disabled, }, classNames?.wrapper, )} onKeyDown={onKeyDownHandler} onClick={onChangeHandler} ref={ref} tabIndex={disabled ? '-1' : 0} > <div className={css.menuIconName}> {getNamedComp('SubMenuItemIcon')} {getNamedComp('SubMenuItemText')} </div> {getNamedComp('SubMenuItemAction')} </div> ); }, ); type SubMenuGroupClassNames = $ReadOnly<{ wrapper?: string, groupHeader?: string, menuGroup?: string, }>; export type SubMenuGroupProps = { children?: React.Node, groupTitle?: string, collapsible?: boolean, disabled?: boolean, onChange?: (value: string) => mixed, selectedMenuKey?: string, classNames?: SubMenuGroupClassNames, }; export const SubMenuGroup = ({ children, groupTitle, collapsible = false, disabled = false, onChange, selectedMenuKey, classNames, }: SubMenuGroupProps): React.Node => { const [collapsed, setCollapsed] = React.useState(false); const collapseHandler = () => { collapsible && setCollapsed(!collapsed); }; const onChangeHandler = (value) => { if (onChange) { onChange(value); } }; const onKeyDownHandlerHeader = (e) => { if (e.key === 'Enter') { collapseHandler(); } }; const childrenWithProps = React.Children.map(children, (child) => { if (React.isValidElement(child)) { const {disabled: disabledChild} = child.props; return React.cloneElement(child, { selectedMenuKey, onChange: onChangeHandler, disabled: disabledChild || disabled, }); } return child; }); return ( <div className={classify(css.subMenuGroupWrapper, classNames?.wrapper)}> <div className={classify( css.subMenuGroupHeader, {[css.collapsible]: collapsible}, classNames?.groupHeader, )} onClick={collapseHandler} onKeyDown={onKeyDownHandlerHeader} tabIndex={collapsible ? 0 : -1} > <SubTitleExtraSmall color={TEXT_COLORS.inverseSecondary} className={css.groupTitle} > {groupTitle} </SubTitleExtraSmall> {collapsible && ( <Icon color={TEXT_COLORS.inverseSecondary} name={collapsed ? 'chevron-down' : 'chevron-up'} size="small" /> )} </div> <div className={classify( css.subMenuGroup, { [css.collapsed]: collapsible && collapsed, }, classNames?.menuGroup, )} > {childrenWithProps} </div> </div> ); }; export type SubMenuProps = { classNames?: ClassNames, title: string, children?: React.Node, onClose?: () => mixed, showClose?: boolean, }; export const SubMenu: React$AbstractComponent<SubMenuProps, HTMLDivElement> = React.forwardRef<SubMenuProps, HTMLDivElement>( ( {classNames, children, title, onClose, showClose = false}: SubMenuProps, ref, ): React.Node => { const childrenWithProps = React.Children.map(children, (child) => { if (React.isValidElement(child)) { return <div className={css.menuChildWrap}>{child}</div>; } }); return ( <div className={classify(css.subMenuWrapper, classNames?.wrapper)} ref={ref} > <div className={css.subMenuTitleAndButton}> <SubTitleMedium className={css.subMenuHeader} color={TEXT_COLORS.inversePrimary} > {title} </SubTitleMedium> {showClose && ( <Button ariaLabel="Icon Button" iconRightName="close" onClick={onClose} size="small" type="ghost" actionType="button" classNames={{ wrapper: css.subMenuCloseButton, icon: css.subMenuCloseIcon, }} /> )} </div> {childrenWithProps} </div> ); }, ); SubMenu.displayName = 'SubMenu';