@ithinkdt/naive
Version:
iThinkDT Naive UI
213 lines (191 loc) • 6.92 kB
JSX
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,
})
}
}