UNPKG

@ithinkdt/naive

Version:

iThinkDT Naive UI

213 lines (191 loc) 6.92 kB
import { defineComponent, watch, nextTick, ref, reactive, inject, useAttrs, markRaw } from 'vue' import { RouterLink } from 'vue-router' import { NMenu, NScrollbar, NEllipsis } from 'ithinkdt-ui' import { cB, cM, CSS_MOUNT_ANCHOR_META_NAME, CSS_STYLE_PREFIX as p } from '@ithinkdt/core/cssr' import { IBookmark } from '../assets.jsx' const cls = `${p}-side-menu` export function mapModules(modules, defaultIcon, menuIconLoader) { return modules.map(({ key, type: _type, label, _path, path, hidden, icon: _icon = defaultIcon, children }) => { const it = reactive({ key, label, _type, _path, path, show: !hidden, icon: undefined, _icon, children: children?.length ? mapModules(children, undefined, menuIconLoader) : children, }) if (_icon && typeof _icon === 'string') { Promise.resolve(menuIconLoader(_icon)) .then((Icon) => { if (Icon) { it.icon = markRaw(Icon) } else { console.debug(`[theme] not found icon: ${_icon}`) } }) .catch((error) => { console.debug(`[theme] load icon: ${_icon} failured`, error) }) } else { it.icon = _icon } return it }) } const __v = Symbol() const __c = Symbol() export function renderLabel(m, collapsed = false) { if (!m[__v]) { m[__c] = !m._path && m._type === 'group' ? m.label : <RouterLink to={m._path ?? m.path}>{m.label}</RouterLink> m[__v] = ( <NEllipsis class={`${cls}__ellipsis`}> {{ default: () => m[__c], tooltip: () => m.label, }} </NEllipsis> ) } return collapsed ? m[__c] : m[__v] } export const DtSideMenu = defineComponent({ name: 'DtSideMenu', inheritAttrs: false, props: { full: { type: Boolean, required: false, default: true }, }, setup(props) { createStyle(cls) const auth = inject('__INJECTED_AUTH__') const theme = inject('__INJECTED_THEME__') const menus = ref([]) const menuRef = ref() function update(paths) { nextTick(() => { if (paths.at(-1)) menuRef.value?.showOption(paths.at(-1)?.key) }) } watch( [() => props.full, () => auth.menus], ([full, modules]) => { if (full) { menus.value = mapModules(modules, IBookmark, theme.menuIconLoader) } update(auth.activedMenuPath) }, { immediate: true }, ) watch( () => auth.activedMenuPath, (paths, _paths) => { if (paths[0] !== _paths?.[0]) { if (!props.full) { menus.value = mapModules(paths[0]?.children ?? [], IBookmark, theme.menuIconLoader) } update(paths) } }, { immediate: true }, ) function _renderLabel(m) { return renderLabel(m, !theme.isFixedSidebar && !!m.icon) } const attrs = useAttrs() return () => { return ( <NScrollbar> <NMenu {...attrs} ref={menuRef} class={{ [cls]: true, [`${cls}--dark`]: theme.isDark }} accordion={theme.accordionMenu} options={menus.value} mode="vertical" collapsed={!theme.isFixedSidebar} collapsedWidth={58} // iconSize={theme.isFixedSidebar ? undefined : 54} // renderIcon={renderIcon} renderLabel={_renderLabel} // eslint-disable-next-line unicorn/no-null value={auth.activedMenuPath.at(-1)?.key ?? null} rootIndent={16} dropdownProps={{ showArrow: true }} /> </NScrollbar> ) } }, }) let style function createStyle(cls) { if (!style) { style = cB( 'side-menu', { [`--${cls}__submenu-bg`]: 'rgba(0, 0, 0, 0.03)', }, [ cM('dark', { [`--${cls}__submenu-bg`]: 'rgba(0, 0, 0, 0.15)', }), // cE('icon', { // ...fullWH, // ...flexDirCol, // ...flexAlignCenter, // ...flexGap('4px'), // }), // cE('icon-wrapper', { // lineHeight: '0', // width: '24px', // height: '24px', // fontSize: '24px', // }), // cE('icon-name', { // display: 'inline-block', // width: '4em', // fontSize: '12px', // textAlign: 'center', // lineHeight: '1em', // transform: 'scale(calc(5 / 6))', // // position: 'absolute', // // bottom: '0', // }), // c('&.n-menu--collapsed', [ // c('.n-menu-item-content--selected::after', { // background: 'unset', // }), // ]), // c('& > div:first-child', { // marginTop: '0', // }), // c('.n-submenu-children', { // background: `var(--${cls}__submenu-bg)`, // }), // c('.n-menu-item-content::before', { // transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)', // left: '0', // right: '0', // top: '0', // bottom: '0', // borderRadius: '0', // }), // c('.n-menu-item-content--selected::after', { // content: `''`, // display: 'inline-block', // position: 'absolute', // right: '0', // width: '2.5px', // height: '100%', // background: 'var(--dt-primary-color-hover)', // }), ], ) style.mount({ id: cls, anchorMetaName: CSS_MOUNT_ANCHOR_META_NAME, }) } }