/
kaws
/
ui-kit-ce
Обзор
Документация
Войти
/
kaws
/
ui-kit-ce
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
develop
packages/bar/examples/NestedMenuExample.tsx
515 строк
14 KB
Строганов Федор Юрьевич
docs: доработана документация Storybook
04 мар 2025, 16:28
04 мар 2025, 16:28
28abce0
Код
Авторство
О чём код?
import React from 'react' import { Bar, BarButton, BarDate, BarDivider, BarMenuItem, BarSearch, SubBar, clsx, createUseStyles, } from '@v-uik/base' import { CustomBarMenuItem } from './CustomBarMenuItem' const data: NestedItem[] = [ { title: 'Menu 1', index: 1, }, { title: 'Menu 2', index: 2, children: [ { title: 'Menu 3', index: 3, children: [ { title: 'Menu 4', index: 4, }, { title: 'Menu 5', index: 5, children: [ { title: 'Menu 6', index: 16, }, { title: 'Menu 7', index: 17, }, { title: 'Menu 8', index: 18, }, ], }, ], }, { title: 'Menu 9', index: 6, }, ], }, { title: 'Very very very long menu label', index: 15, children: [ { title: 'Menu 11', index: 7, }, { title: 'Menu 12', index: 8, }, { title: 'Menu 13', index: 14, children: [ { title: 'Menu 14', index: 9, }, { title: 'Menu 15', index: 10, children: [ { title: 'Menu 16', index: 11, children: [ { title: 'Menu 17', index: 12, }, { title: 'Menu 18', index: 13, }, ], }, ], }, { title: 'Menu 19', index: 19, }, { title: 'Menu 20', index: 20, }, ], }, ], }, { title: 'Menu 21', index: 21, children: [ { title: 'Menu 22', index: 22, }, ], }, { title: 'Menu 23', index: 23, children: [ { title: 'Menu 24', index: 24, }, { title: 'Menu 25', index: 25, }, ], }, ] const allItemsWithChildren = [2, 3, 5, 15, 14, 10, 11, 21, 23] type NestedItem = { index: number title: string children?: NestedItem[] } const useStyles = createUseStyles((theme) => ({ elementWithActiveChild: { backgroundColor: theme.sys.color.inverseOnBackgroundOverlaySelect, }, elementTextActiveChild: { color: theme.sys.color.inverseOnBackgroundHigh, }, })) interface IconProps extends React.SVGAttributes<SVGElement> {} const svgPath = 'M20,2 C21.6568542,2 23,3.34314575 23,5 L23,20 C23,21.6568542 21.6568542,23 20,23 L5,23 C3.34314575,23 2,21.6568542 2,20 L2,5 C2,3.34314575 3.34314575,2 5,2 L20,2 Z M20,3.5 L5,3.5 C4.17157288,3.5 3.5,4.17157288 3.5,5 L3.5,5 L3.5,20 C3.5,20.8284271 4.17157288,21.5 5,21.5 L5,21.5 L20,21.5 C20.8284271,21.5 21.5,20.8284271 21.5,20 L21.5,20 L21.5,5 C21.5,4.17157288 20.8284271,3.5 20,3.5 L20,3.5 Z M11,2 L14,2 L14,3.5 L11,3.5 L11,2 Z M2,11 L3.5,11 L3.5,14 L2,14 L2,11 Z M11,21.5 L14,21.5 L14,23 L11,23 L11,21.5 Z M21.5,11 L23,11 L23,14 L21.5,14 L21.5,11 Z' // eslint-disable-line max-len const Icon: React.FC<IconProps> = ({ width = 24, height = 24, fill = 'currentColor', ...props }) => { return ( <svg width={width} height={height} fill={fill} {...props} viewBox="0 0 25 25" > <path d={svgPath} fillRule="evenodd" /> </svg> ) } Icon.displayName = 'Icon' const menuSvgPath = 'M1.5 1.5H6V6H1.5V1.5ZM1.5 9.75H6V14.25H1.5V9.75ZM14.25 1.5H9.75V6H14.25V1.5ZM9.75 9.75H14.25V14.25H9.75V9.75ZM22.5 1.5H18V6H22.5V1.5ZM18 9.75H22.5V14.25H18V9.75ZM6 18H1.5V22.5H6V18ZM9.75 18H14.25V22.5H9.75V18ZM22.5 18H18V22.5H22.5V18Z' // eslint-disable-line max-len const IconMenu: React.FC<IconProps> = ({ width = 24, height = 24, fill = 'currentColor', ...props }) => { return ( <svg width={width} height={height} fill={fill} {...props} viewBox="0 0 24 24" > <path d={menuSvgPath} fillRule="evenodd" /> </svg> ) } IconMenu.displayName = 'IconMenu' const historySvgPath = 'M12 22.5C17.799 22.5 22.5 17.799 22.5 12C22.5 6.20101 17.799 1.5 12 1.5C6.20101 1.5 1.5 6.20101 1.5 12C1.5 17.799 6.20101 22.5 12 22.5ZM12 21C16.9706 21 21 16.9706 21 12C21 7.02944 16.9706 3 12 3C7.02944 3 3 7.02944 3 12C3 16.9706 7.02944 21 12 21ZM10.5 4.5H12V12.0043L16.2383 16.2426L15.1776 17.3033L11.3743 13.5H10.5V4.5Z' export const HistoryIcon: React.FC<IconProps> = ({ width = 24, height = 24, fill = 'currentColor', ...props }) => { return ( <svg width={width} height={height} fill={fill} {...props} viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" > <path fillRule="evenodd" clipRule="evenodd" d={historySvgPath} /> </svg> ) } HistoryIcon.displayName = 'HistoryIcon' const expandSubBarSvgPath = 'M3 4.5H21V6H3V4.5ZM7.5 9H21V10.5H7.5V9ZM21 13.5H7.5V15H21V13.5ZM3 18H21V19.5H3V18ZM6 12L3 9V15L6 12Z' const ExpandSubBarIcon: React.FC<IconProps> = ({ width = 24, height = 24, fill = 'currentColor', ...props }) => { return ( <svg width={width} height={height} fill={fill} {...props} viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" > <path fillRule="evenodd" clipRule="evenodd" d={expandSubBarSvgPath} /> </svg> ) } ExpandSubBarIcon.displayName = 'ExpandSubBarIcon' const PlatformVFirstSvgPath = 'M95.5976 11.9639C95.5976 16.3867 98.4236 19.0169 102.663 19.0169C106.958 19.0169 109.728 16.1139 109.728 12.0029C109.728 7.59959 106.902 4.96931 102.663 4.94983C98.3672 4.94983 95.5976 7.85288 95.5976 11.9639ZM98.0847 11.9834C98.0847 9.1388 99.8179 7.20991 102.644 7.19044C105.658 7.20991 107.241 9.31414 107.241 11.9834C107.241 14.8475 105.489 16.7763 102.682 16.7763C99.6673 16.7568 98.0847 14.6526 98.0847 11.9834ZM40.5077 14.5552C44.37 14.5552 46.4424 12.9576 46.4424 9.78175C46.4424 6.70335 44.6715 5.16415 40.5077 5.16415H36.118V18.8026H38.473V14.5552H40.5077ZM40.715 7.40475C43.0134 7.40475 43.9933 8.04772 43.9933 9.72329C43.9933 11.5742 42.8628 12.3146 40.6773 12.3146H38.473V7.40475H40.715ZM51.389 5.16415H49.0339V18.8026H58.5484V16.562H51.389V5.16415ZM64.7429 5.16415H67.4182L72.8442 18.8026H70.3384L69.076 15.5684H63.0284L61.785 18.8026H59.3921L64.7429 5.16415ZM66.0427 7.73598L63.8762 13.3277H68.2095L66.0427 7.73598ZM83.0788 5.16415H71.9065V7.40475H76.2774V18.8026H78.6325V7.40475H83.0788V5.16415ZM87.4138 11.243H93.8573V13.4836H87.4138V18.8026H85.0589V5.16415H94.1588V7.40475H87.4138V11.243ZM123.181 18.8026H120.355L117.529 14.4968C117.152 14.5357 116.775 14.5552 116.361 14.5552H114.552V18.8026H112.197V5.16415H116.361C120.525 5.16415 122.296 6.70335 122.296 9.78175C122.296 11.8275 121.448 13.2108 119.809 13.9512L123.181 18.8026ZM119.846 9.72329C119.846 8.04772 118.867 7.40475 116.568 7.40475H114.552V12.3146H116.53C118.716 12.3146 119.846 11.5742 119.846 9.72329ZM137.577 18.8026H139.932V5.16415H137.144L132.81 12.5874L128.439 5.16415H125.463V18.8026H127.818V8.65169L132.151 15.8021H133.338L137.577 8.55429V18.8026ZM159.789 5.16415L154.438 18.8026H152.102L146.676 5.16415H149.201L153.308 15.8021L157.396 5.16415H159.789Z' const PlatformVSecondSvgPath = 'M13.6403 5.51965L11.5864 3.39556L19.0646 0L21.1185 2.12404L21.1186 2.12391L23.1726 4.248L19.8891 11.9815L17.8351 9.8574L21.1185 2.12414L13.6403 5.51965ZM11.5862 3.39563L9.53222 5.51972L2.05407 2.1242L5.33746 9.85747L3.28348 11.9816L0 4.24807L2.05398 2.12398L2.05404 2.12412L4.10797 7.04252e-05L11.5862 3.39563ZM17.8349 14.106L19.8889 11.9819L23.1723 19.7154L21.1184 21.8395L21.1183 21.8394L19.0644 23.9633L11.5862 20.5679L13.6402 18.4438L21.1183 21.8393L17.8349 14.106ZM3.28358 11.9815L5.33756 14.1056L2.05419 21.8389L2.05408 21.8391L0.0001013 19.715L3.28358 11.9815ZM2.05419 21.8389L4.10816 23.9631L11.5864 20.5674L9.5324 18.4434L2.05419 21.8389Z' const LogoPlatformV: React.FC<IconProps> = ({ width = 160, height = 24, fill = 'currentColor', ...props }) => { return ( <svg width={width} height={height} fill={fill} {...props} viewBox="0 0 160 24" xmlns="http://www.w3.org/2000/svg" > <path fillRule="evenodd" clipRule="evenodd" d={PlatformVFirstSvgPath} /> <path fillRule="evenodd" clipRule="evenodd" d={PlatformVSecondSvgPath} /> </svg> ) } LogoPlatformV.displayName = 'LogoPlatformV' const ExpandAllTreeIcon: React.FC<IconProps> = ({ width = 24, height = 24, fill = 'currentColor', ...props }) => { return ( <svg width={width} height={height} fill={fill} {...props} viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" > <path d="M19.5 19.5H15V21H19.5V19.5Z" fill="white" fillOpacity="0.87" /> <path d="M21 13.5H15V15H21V13.5Z" fill="white" fillOpacity="0.87" /> <path d="M22.5 7.5H15V9H22.5V7.5Z" fill="white" fillOpacity="0.87" /> <path d="M12.75 3H11.25V21H12.75V3Z" fill="white" fillOpacity="0.87" /> <path d="M7.9395 2.96926L5.25 5.43676L2.559 2.96851L1.5 4.02976L5.25 7.50001L9 4.02976L7.9395 2.96926Z" fill="white" fillOpacity="0.87" /> </svg> ) } ExpandAllTreeIcon.displayName = 'ExpandAllTreeIcon' export const NestedMenuExample = (): JSX.Element => { const classesList = useStyles() const [subBarExpanded, setSubBarExpanded] = React.useState(true) const [historyExpanded, setHistoryExpanded] = React.useState(false) const [selected, setSelected] = React.useState<number | null>(null) const [expanded, setExpanded] = React.useState<number[]>([]) const [historyStack, setHistoryStack] = React.useState< Array<{ /** * */ title: string callback(): void }> >([]) const handleClickItem = (item: NestedItem) => () => { if (!subBarExpanded) { setSubBarExpanded(true) } if (!item.children && !historyStack.find((el) => el.title === item.title)) { setHistoryStack((old) => [ { title: item.title, callback: () => { setSelected(item.index) }, }, ...old, ]) } if (expanded.includes(item.index)) { setExpanded((old) => old.filter((el) => el !== item.index)) } else { setExpanded((old) => [...old, item.index]) } if (item.children) { setSelected(item.children[0].index) } else { setSelected(item.index) } } const recursiveFindSelectedChild = ( items: NestedItem[] | undefined ): NestedItem | undefined => { if (!items) { return } return items.find((item) => { if (item.index === selected) { return true } if (item.children) { return recursiveFindSelectedChild(item.children) } else { return false } }) } const renderMenuItems = (data: NestedItem[], index = 0) => data.map((item) => { const elementWithActiveChild = recursiveFindSelectedChild(item.children) const classes = { menuItem: clsx(undefined, { [classesList.elementWithActiveChild ?? '']: !!elementWithActiveChild, }), text: clsx(undefined, { [classesList.elementTextActiveChild ?? '']: !!elementWithActiveChild, }), } const itemExpand = expanded.includes(item.index) return ( <CustomBarMenuItem key={item.index} expanded={itemExpand} classes={classes} icon={index === 0 && <Icon />} count={index} selected={selected === item.index} items={ item.children && renderMenuItems(item.children, Number(index + 1)) } onClick={handleClickItem(item)} > {subBarExpanded && ( <div style={{ whiteSpace: 'pre-wrap', maxWidth: 150 }}> {item.title} </div> )} </CustomBarMenuItem> ) }) return ( <div style={{ height: '80vh' }}> <div style={{ position: 'absolute', inset: '10px 6px' }}> <Bar style={{ position: 'absolute', }} kind="dark" direction="horizontal" expanded={false} > <BarDate style={{ marginLeft: 'auto' }} /> <BarDivider /> <BarButton icon={<IconMenu />} /> </Bar> <SubBar style={{ position: 'absolute', top: 48, }} direction="vertical" kind="dark" expanded={subBarExpanded} > <div style={{ marginBottom: 2, display: 'flex', justifyContent: 'space-between', alignItems: 'center', }} > <LogoPlatformV style={{ margin: '12px 16px', fill: 'white', }} /> {subBarExpanded && ( <BarButton icon={<ExpandAllTreeIcon />} onClick={() => setExpanded(allItemsWithChildren)} /> )} </div> <div style={{ overflow: 'auto' }}> {renderMenuItems(data)} <BarDivider /> <CustomBarMenuItem expanded={historyExpanded} items={ historyStack.length ? ( <> {historyStack.map((item, index) => ( <CustomBarMenuItem key={index} count={1} onClick={item.callback} > {item.title} </CustomBarMenuItem> ))} </> ) : null } icon={<HistoryIcon />} onClick={() => setHistoryExpanded((old) => !old)} > History </CustomBarMenuItem> </div> <BarDate style={{ marginTop: 'auto' }} /> <BarSearch style={{ marginBottom: 5 }} inputProps={{ placeholder: 'Search' }} /> <BarMenuItem icon={<ExpandSubBarIcon />} style={{ marginBottom: 10 }} onClick={() => { setSubBarExpanded((old) => !old) setExpanded([]) }} > Collapse </BarMenuItem> </SubBar> </div> </div> ) }