diff --git a/packages/devui-theme/src/theme/theme-data.ts b/packages/devui-theme/src/theme/theme-data.ts index cd82585e08..60782dcf1e 100644 --- a/packages/devui-theme/src/theme/theme-data.ts +++ b/packages/devui-theme/src/theme/theme-data.ts @@ -136,7 +136,13 @@ export const devuiLightTheme: Theme = new Theme({ 'devui-z-index-dropdown': '1052', 'devui-z-index-modal': '1050', 'devui-z-index-drawer': '1040', - 'devui-z-index-framework': '1000' + 'devui-z-index-framework': '1000', + + // Menu + 'devui-menu-item': '#252b3a', + 'devui-menu-item-sub':'#6C6C6C', + 'devui-menu-item-hover': '#0f0f0f', + 'devui-menu-disabled': '#919191' }, isDark: false, }); @@ -281,6 +287,12 @@ export const devuiDarkTheme: Theme = new Theme({ 'devui-primary-bg': '#383D4F', 'devui-default-line': '#5e7ce0', 'devui-default-bg': '#383838', + + // Menu + 'devui-menu-item': '#dcdcdc', + 'devui-menu-item-sub':'#c6c6c6', + 'devui-menu-item-hover': '#fff', + 'devui-menu-disabled': '#919191' }, extends: 'devui-light-theme', isDark: true, diff --git a/packages/devui-vue/devui/menu/__tests__/menu.spec.ts b/packages/devui-vue/devui/menu/__tests__/menu.spec.ts new file mode 100644 index 0000000000..afca3042d9 --- /dev/null +++ b/packages/devui-vue/devui/menu/__tests__/menu.spec.ts @@ -0,0 +1,101 @@ +import { mount, shallowMount } from '@vue/test-utils'; +import { reactive, ref } from 'vue'; +import { Menu,SubMenu,MenuItem } from '../index'; + +const factory_Menu = (value: Record) => shallowMount(Menu, value); + +describe('menu test', () => { + it('menu - defaultMode test', async ()=>{ + // + const menu = factory_Menu({}); + expect(menu.classes().indexOf('vertical')).toBe(1); + }); + it('menu - dynamic - mode', async ()=>{ + const menu = factory_Menu({ + props: { + 'mode': 'horizontal' + } + }); + expect(menu.classes().indexOf('horizontal')).not.toBe(-1); + }); + + // 参数动态测试 - defaultSelectKeys + it('menu - dynamic attr - defaultSelectKeys ', async ()=>{ + // + const wrapper = mount({ + components: { + Menu, + SubMenu, + MenuItem + }, + template: ` + + + Test + + + + `, + setup(){ + const selectKeys = ref(['test']); + const clickHandle = () => { + selectKeys.value.pop(); + }; + return { + selectKeys, + clickHandle + }; + } + }); + expect(wrapper.find('li').classes().indexOf('devui-menu-item-select')).not.toBe(-1); + await wrapper.find('button').trigger('click'); + expect(wrapper.find('li').classes().indexOf('devui-menu-item-select')).toBe(-1); + }); + // 参数动态测试 - openKeys + it('menu - dynamic attr - openKeys', async () => { + // + const wrapper = mount({ + components: {Menu,SubMenu,MenuItem}, + template: ` + + + + SubMenu > Item 1 + + + SubMenu > Item 2 + + + + + SubMenu2 > Item 1 + + + SubMenu2 > Item 2 + + + + + `, + setup(){ + const defaultOpenKey = ref(['1']); + const change = ()=>{ + if (defaultOpenKey.value.length < 2){ + defaultOpenKey.value.push('2'); + } else { + defaultOpenKey.value.pop(); + } + }; + return { + defaultOpenKey, + change + }; + } + }); + console.log(wrapper.findAll('ul')[2].classes()); + await wrapper.find('button').trigger('click'); + console.log(wrapper.findAll('ul')[2].classes()); + await wrapper.find('button').trigger('click'); + console.log(wrapper.findAll('ul')[2].classes()); + }); +}); diff --git a/packages/devui-vue/devui/menu/index.ts b/packages/devui-vue/devui/menu/index.ts new file mode 100644 index 0000000000..6be639dbe3 --- /dev/null +++ b/packages/devui-vue/devui/menu/index.ts @@ -0,0 +1,17 @@ +import type { App } from 'vue'; +import MenuItem from './src/components/menu-item'; +import SubMenu from './src/components/sub-menu'; +import Menu from './src/menu'; + +export { Menu,SubMenu,MenuItem }; + +export default { + title: 'Menu 菜单', + category: '布局', + status: "90%", // TODO: 组件若开发完成则填入"100%",并删除该注释 + install(app: App): void { + app.component(Menu.name, Menu); + app.component(MenuItem.name, MenuItem); + app.component(SubMenu.name, SubMenu); + } +}; diff --git a/packages/devui-vue/devui/menu/src/components/menu-item.tsx b/packages/devui-vue/devui/menu/src/components/menu-item.tsx new file mode 100644 index 0000000000..a1a0c94ac4 --- /dev/null +++ b/packages/devui-vue/devui/menu/src/components/menu-item.tsx @@ -0,0 +1,175 @@ +import { changeKey } from '../composables/layer-composables'; +import { defineComponent, + getCurrentInstance, + onMounted, + ref, + Transition, + watch, + inject, + Ref, + reactive, + toRefs, +} from "vue"; +import { MenuItemProps, menuItemProps } from "../types/menu-item-types"; +import { useInitSelect } from '../composables/init-select'; +import { addActiveParent } from '../composables/add-active-parent'; + + +export default defineComponent({ + name: 'DMenuItem', + props: menuItemProps, + setup(props: MenuItemProps, ctx){ + const instance = getCurrentInstance(); + const key = String(instance?.vnode.key); + const mode = inject('mode') as Ref<('vertical' | 'horizontal')>; + const multiple = inject('multiple') as boolean; + const indent = inject('defaultIndent'); + const isCollapsed = inject('isCollapsed') as Ref; + const defaultSelectKey = (inject('defaultSelectKey') as string[]); + const {disabled} = toRefs(props); + const isSelect = ref(useInitSelect(defaultSelectKey, key, multiple, disabled)); + const isLayer1 = ref(true); + const rootMenuEmit = inject('rootMenuEmit') as (eventName: string, ...args: unknown[]) => void; + const classObject: Record = reactive({ + 'devui-menu-item': true, + 'devui-menu-item-isCollapsed': isCollapsed.value, + 'devui-isCollapsed-item': isCollapsed.value, + 'devui-menu-item-select': isSelect.value, + 'devui-menu-item-disabled': disabled.value + }); + const onClick = (e: MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + const ele = e.currentTarget as HTMLElement; + if (!props.disabled){ + if (!multiple){ + changeKey(ele,e); + ele.classList.add('devui-menu-item-select'); + } else { + if (ele.classList.contains('devui-menu-item-select')){ + ele.classList.remove('devui-menu-item-select'); + rootMenuEmit('deselect', {type: 'deselect', el: ele, e}); + return; + } else { + ele.classList.add('devui-menu-item-select'); + } + } + rootMenuEmit('select', {type: 'select', el: ele, e}); + } + if (mode.value === 'vertical'){ + const target = (e.currentTarget as HTMLElement); + addActiveParent(target); + } + if (mode.value === 'horizontal') { + const ul = ele.parentElement?.parentElement; + ul?.classList.add('devui-menu-active-parent'); + } + }; + const icons = {ctx.slots.icon?.()}; + const menuItems = ref(null); + // watchEffect(()=>{ + // pushItem({el: menuItems.value as HTMLElement | null}); + // },{flush:'post'}); + watch(disabled, ()=> {classObject['devui-menu-item-select'] = false;}); + watch(defaultSelectKey, (n)=>{ + isSelect.value = useInitSelect(n,key,multiple,disabled); + classObject['devui-menu-item-select'] = isSelect.value; + }); + onMounted(()=>{ + let oldPadding = ''; + const ele = (menuItems.value) as unknown as HTMLElement; + if (mode.value === 'vertical'){ + if (ele.parentElement?.parentElement?.classList.contains('devui-menu')){ + isLayer1.value = true; + if (isLayer1.value){ + ele.style.paddingRight = ``; + ele.style.paddingLeft = `${indent}px`; + } + watch(isCollapsed, (val)=>{ + if (val){ + if (ele.style.padding !== '0'){ + oldPadding = ele.style.padding; + } + setTimeout(() => { + ele.style.padding = '0'; + ele.style.width = ''; + ele.style.textAlign = `center`; + }, 300); + ele.style.display=`block`; + } else { + ele.style.padding = `${oldPadding}`; + ele.style.textAlign = ``; + ele.style.display=`flex`; + } + }); + } else { + isLayer1.value = false; + } + } + }); + return () => { + return ( + mode.value === 'vertical' ? +
+
  • + {ctx.slots.icon !== undefined && + icons + } + { props.href === '' ? + + + {ctx.slots.default?.()} + + + : + + + {ctx.slots.default?.()} + + + } +
  • +
    + : +
  • + {ctx.slots.icon !== undefined && + icons + } + { props.href === '' ? + + + {ctx.slots.default?.()} + + + : + + + {ctx.slots.default?.()} + + + } +
  • + ); + }; + }, +}); diff --git a/packages/devui-vue/devui/menu/src/components/sub-menu.tsx b/packages/devui-vue/devui/menu/src/components/sub-menu.tsx new file mode 100644 index 0000000000..22801fb8df --- /dev/null +++ b/packages/devui-vue/devui/menu/src/components/sub-menu.tsx @@ -0,0 +1,169 @@ +import { + ComponentInternalInstance, + defineComponent, + getCurrentInstance, + inject, + onMounted, + Ref, + ref, + watchEffect, + watch, +} from 'vue'; +import { addLayer, pushElement, changeKey,getLayer } from '../composables/layer-composables'; +import { SubMenuProps, subMenuProps } from '../types/sub-menu-types'; +export default defineComponent({ + name: 'DSubMenu', + props: subMenuProps, + setup(props: SubMenuProps, ctx){ + const isShow = ref(true); + const {vnode:{key}} = getCurrentInstance() as ComponentInternalInstance; + const key_ = String(key); + const isOpen = ref(false); + const defaultOpenKeys = (inject('openKeys') as string[]); + const indent = inject('defaultIndent'); + const isCollapsed = inject('isCollapsed') as Ref; + const mode = inject('mode') as Ref; + const parentEmit = inject('rootMenuEmit') as (eventName: 'submenu-change', ...args: any[]) => void; + if (key_ === 'null'){ + console.warn(`[devui][menu]: Key can not be null`); + } else { + if (defaultOpenKeys.includes(key_)){ + isOpen.value = true; + } else { + isOpen.value = false; + } + } + const clickHandle = (e: MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + if (mode.value === 'horizontal'){ + const ele = e.currentTarget as HTMLElement; + changeKey(ele, e); + ele.classList.add('devui-menu-item-select'); + } + if (!props.disable && mode.value !== 'horizontal'){ + const target = e.target as HTMLElement; + let cur = e.target as HTMLElement; + if (target.tagName === 'UL'){ + if (target.classList.contains('devui-submenu-open')){ + isOpen.value = !isOpen.value; + } else { + isOpen.value = isOpen.value; + } + } else { + while (cur && cur.tagName !== 'UL'){ + if (cur.tagName === 'LI'){ + break; + } + cur = cur.parentElement as HTMLElement; + } + if (cur.tagName === 'UL'){ + if (cur.classList.contains('devui-submenu-open')){ + isOpen.value = false; + } else { + isOpen.value = true; + } + } + } + parentEmit('submenu-change', {type: 'submenu-change', state: isOpen.value, el: cur}); + } + }; + const subMenu = ref(null); + let title = ref(null); + let oldPadding = ''; + const class_layer = ref(''); + watchEffect(()=>{ + title = title.value as any; + pushElement({el: subMenu.value} as any); + },{'flush':'post'}); + watch(defaultOpenKeys, (n,v)=>{ + if (n.includes(key_)){ + isOpen.value = true; + } else { + isOpen.value = false; + } + }); + onMounted(()=>{ + const el = title as unknown as HTMLElement; + const e = subMenu.value as unknown as HTMLElement; + addLayer(); + class_layer.value = `layer_${Array.from(e.classList).at(-1)?.replace('layer_','')}`; + watch(isCollapsed, (newValue)=>{ + const layer = Number(getLayer(e)); + if (!Number.isNaN(layer)){ + if (layer > 2){ + if (isCollapsed.value){ + isShow.value = false; + } else { + isShow.value = true; + } + } + } + if (newValue){ + if (el.style.padding !== '0'){ + oldPadding = el.style.padding; + } + setTimeout(() => { + el.style.padding = '0'; + el.style.width = ''; + el.style.textAlign = `center`; + }, 300); + el.style.display=`block`; + } else { + el.style.padding = `${oldPadding}`; + el.style.textAlign = ``; + el.style.display=`flex`; + } + }); + }); + return () => { + return ( +
      + { + props.title ? +
      + {ctx.slots?.icon?.()} + + {props.title} + + +
      + : +
      + + {ctx.slots.icon?.()} + +
      + } + {mode.value === 'horizontal' ? +
      + {ctx.slots.default?.()} +
      : + ctx.slots.default?.() + } +
    + ); + }; + } +}); diff --git a/packages/devui-vue/devui/menu/src/composables/add-active-parent.ts b/packages/devui-vue/devui/menu/src/composables/add-active-parent.ts new file mode 100644 index 0000000000..af250acf9f --- /dev/null +++ b/packages/devui-vue/devui/menu/src/composables/add-active-parent.ts @@ -0,0 +1,10 @@ +export function addActiveParent(ele: HTMLElement): HTMLElement{ + let cur = (ele.parentElement as HTMLElement); + while (!cur.classList.contains('devui-menu')){ + if (cur.firstElementChild?.tagName === 'DIV'){ + cur?.firstElementChild?.classList.add('devui-menu-active-parent'); + } + cur = cur.parentElement as HTMLElement; + } + return cur; +} diff --git a/packages/devui-vue/devui/menu/src/composables/init-select.ts b/packages/devui-vue/devui/menu/src/composables/init-select.ts new file mode 100644 index 0000000000..1b454b429c --- /dev/null +++ b/packages/devui-vue/devui/menu/src/composables/init-select.ts @@ -0,0 +1,20 @@ +import { Ref, ref } from "vue"; + +export function useInitSelect(defaultSelectKeys: string[], keys: string, isMutiple: boolean, disabled: Ref): +boolean{ + const isSelect = ref(false); + if(!isMutiple){ + if (defaultSelectKeys[0] === keys && !disabled.value){ + isSelect.value = true; + } else { + isSelect.value = false; + } + } else{ + if (defaultSelectKeys.includes(keys)){ + isSelect.value = true; + } else { + isSelect.value = false; + } + } + return isSelect.value; +} diff --git a/packages/devui-vue/devui/menu/src/composables/layer-composables.ts b/packages/devui-vue/devui/menu/src/composables/layer-composables.ts new file mode 100644 index 0000000000..e1feac8a44 --- /dev/null +++ b/packages/devui-vue/devui/menu/src/composables/layer-composables.ts @@ -0,0 +1,108 @@ +import { Ref,ref } from "vue"; +interface clickEvent extends MouseEvent{ + path?: HTMLElement[] | Element[]; +} +const elements: JSX.Element[] = []; +let parents: HTMLElement[] = []; +const defaultIndent: Ref = ref(24); +export function setDefaultIndent(indent: number): void{ + defaultIndent.value = indent; +} +export function pushElement(element: JSX.Element): void{ + elements.push(element); +} +export function addLayer(): void{ + parents = []; + elements.forEach((val)=>{ + parents.push( + ((val.el as HTMLElement).parentElement as HTMLElement)); + }); + // dfs + const stack = [...parents]; + const getLayerFromClass = (className: string) => /layer_(\d*)/gim.exec(className)?.[1]; + while (stack.length){ + const shiftItem = stack.shift() as HTMLElement; + if (shiftItem?.classList.contains('devui-menu')){ + const children = shiftItem.children; + stack.unshift(...Array.from(children) as HTMLElement[]); + continue; + } else { + if (shiftItem.tagName === 'DIV'){ + if (shiftItem.classList.contains('devui-menu-item-vertical-wrapper')){ + const parent = shiftItem.parentElement; + stack.unshift(...Array.from(shiftItem.children) as HTMLElement[]); + if (parent?.classList.contains('devui-menu')){ + shiftItem.classList.add('layer_1'); + } else { + let layer: string|number|undefined = getLayerFromClass((parent?.classList.value || '') as string); + layer = Number(layer); + shiftItem.classList.add(`layer_${layer}`); + } + } else { + const parent = shiftItem.parentElement; + let layer: string|number|undefined = getLayerFromClass((parent?.classList.value || '') as string); + layer = Number(layer); + shiftItem.classList.add(`layer_${layer}`); + shiftItem.style.paddingLeft = `${(layer === 2 ? 1 : layer-1)*defaultIndent.value}px`; + } + } + if (shiftItem.tagName === 'UL'){ + const parent = shiftItem.parentElement; + const children = shiftItem.children; + for (let i=0;i div.devui-submenu-title { + margin: 0 !important; + background: $devui-block; + } + } + + li.devui-isCollapsed-item { + padding: 0; + margin: auto; + } + + &.devui-menu-collapsed { + .devui-menu-icon { + margin: auto; + } + + & ul li { + display: none !important; + } + + .devui-submenu { + div.devui-submenu-title { + .devui-menu-icon { + margin: auto; + } + } + } + } + + .devui-menu-item-vertical-wrapper { + padding-left: 0 !important; + } + + .devui-menu-item { + width: 100%; + height: 40px; + flex-grow: 1; + line-height: 40px; + cursor: pointer; + color: $devui-menu-item; + background: $devui-block; + display: flex; + + span:nth-child(2) { + // flex: auto; + text-align: left; + } + } + + div { + display: flex; + flex: auto; + } + + .devui-menu-item::after { + display: block; + position: absolute; + right: 0; + top: 0; + transform: scaleX(0); + content: ''; + opacity: 1; + background: $devui-menu-item-selectBar; + } + + .devui-menu-item-select { + background: $devui-primary-bg !important; + position: relative; + + span, + a { + color: $devui-menu-item-select; + } + + &::after { + display: block; + position: absolute; + right: 0; + top: 0; + height: 100%; + width: 4px; + content: ''; + opacity: 1; + background: var(--devui-brand, #5e7ce0); + transform: scaleX(1); + } + } + + .devui-submenu > div:hover { + span.devui-submenu-title-content { + color: $devui-menu-item-select; + } + } + + .devui-menu-item:hover { + color: $devui-menu-item-select; + } + + li.devui-menu-item, + div.devui-submenu-title { + white-space: nowrap; + overflow: hidden; + + span:nth-child(2) { + overflow: hidden; + text-overflow: ellipsis; + + span { + overflow: hidden; + text-overflow: ellipsis; + } + } + } + + .devui-submenu-open { + & > div { + color: $devui-menu-item-select !important; + } + + height: auto; + + li, + ul { + opacity: 1; + visibility: visible; + } + + ul { + max-height: 100px; + overflow: hidden; + } + } + + .devui-submenu-close { + & > div { + color: $devui-menu-item; + } + + li { + height: 0; + opacity: 0; + visibility: hidden; + overflow-y: hidden; + } + + ul { + max-height: 0; + overflow: hidden; + } + } + + .devui-menu-item-isCollapsed { + width: fit-content; + + .devui-menu-icon { + margin: auto; + } + } + + // sub menu + ul.devui-submenu { + margin: 0; + padding: 0; + + div.devui-submenu-title { + display: flex; + cursor: pointer; + width: 100%; + height: 40px; + margin: 4px 0; + line-height: 40px; + padding-left: 18px; + align-items: center; + color: $devui-menu-item; + + &:nth-child(1) { + font-size: $devui-font-size-lg; + } + + span.devui-submenu-title-content { + font-size: $devui-font-size-lg; + flex: auto; + } + + span.devui-menu-icon { + text-align: center; + } + } + + .devui-menu-item { + display: flex; + + & > span { + flex: auto; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + transition: all 0.5s ease; + color: $devui-menu-item-sub; + } + } + + .devui-menu-item:hover { + & > span { + color: $devui-menu-item-select; + } + } + + .devui-menu-item-select { + * { + color: $devui-menu-item-select !important; + } + } + } + + ul li ~ ul > div { + margin-top: 0 !important; + } + + ul li { + background: $devui-area !important; + } +} diff --git a/packages/devui-vue/devui/menu/src/menu.tsx b/packages/devui-vue/devui/menu/src/menu.tsx new file mode 100644 index 0000000000..7117e5a73d --- /dev/null +++ b/packages/devui-vue/devui/menu/src/menu.tsx @@ -0,0 +1,48 @@ +import { + defineComponent, + provide, + ref, + computed, +} from 'vue'; +import { menuProps, MenuProps } from './types/menu-types'; +import './menu.scss'; +import {setDefaultIndent} from './composables/layer-composables'; + +export default defineComponent({ + name: 'DMenu', + props: menuProps, + emits: ['select','dselect', 'submenu-change'], + setup(props: MenuProps, ctx) { + const isCollapsed = computed(()=>props.collapsed); + const mode = computed(()=>props['mode']); + provide('isCollapsed',isCollapsed); + provide('defaultIndent', props['indentSize']); + provide('multiple', props['multiple']); + provide('openKeys', props.openKeys); + provide('defaultSelectKey',props.defaultSelectKeys); + provide('mode', mode); + provide('collapsedIndent', ref(props['collapsedIndent'])); + provide('rootMenuEmit', ctx.emit); + setDefaultIndent(props['indentSize']); + return () => { + return ( +
      + {ctx.slots.default?.()} +
    + ); + }; + } +}); diff --git a/packages/devui-vue/devui/menu/src/styles/clear.scss b/packages/devui-vue/devui/menu/src/styles/clear.scss new file mode 100644 index 0000000000..8884c8ef2d --- /dev/null +++ b/packages/devui-vue/devui/menu/src/styles/clear.scss @@ -0,0 +1,3 @@ +a { + text-decoration: none; +} diff --git a/packages/devui-vue/devui/menu/src/types/menu-item-types.ts b/packages/devui-vue/devui/menu/src/types/menu-item-types.ts new file mode 100644 index 0000000000..343e847f24 --- /dev/null +++ b/packages/devui-vue/devui/menu/src/types/menu-item-types.ts @@ -0,0 +1,14 @@ +import { ExtractPropTypes } from "vue"; + +export const menuItemProps = { + disabled: { + type: Boolean, + default: false, + }, + href:{ + type: String, + default: '', + } +} as const; + +export type MenuItemProps = ExtractPropTypes; diff --git a/packages/devui-vue/devui/menu/src/types/menu-types.ts b/packages/devui-vue/devui/menu/src/types/menu-types.ts new file mode 100644 index 0000000000..179a03c5da --- /dev/null +++ b/packages/devui-vue/devui/menu/src/types/menu-types.ts @@ -0,0 +1,40 @@ +import { ExtractPropTypes } from "vue"; + +export type menuMode = 'vertical' | 'horizontal'; + +export const menuProps = { + width:{ + type: String, + default: '', + }, + collapsed: { + type: Boolean, + default: false + }, + collapsedIndent: { + type: Number, + default: 24 + }, + indentSize: { + type: Number, + default: 24 + }, + multiple:{ + type: Boolean, + default: false, + }, + openKeys: { + type: Array, + default: [] + }, + defaultSelectKeys: { + type: Array, + default: [], + }, + mode: { + type: String as () => menuMode, + default: 'vertical' + } +} as const; + +export type MenuProps = ExtractPropTypes; diff --git a/packages/devui-vue/devui/menu/src/types/sub-menu-types.ts b/packages/devui-vue/devui/menu/src/types/sub-menu-types.ts new file mode 100644 index 0000000000..af4593c799 --- /dev/null +++ b/packages/devui-vue/devui/menu/src/types/sub-menu-types.ts @@ -0,0 +1,14 @@ +import { ExtractPropTypes } from "vue"; + +export const subMenuProps = { + title: { + type: String, + default: '' + }, + disable: { + type: Boolean, + default: false + } +} as const; + +export type SubMenuProps = ExtractPropTypes; diff --git a/packages/devui-vue/devui/style/core/_reset.scss b/packages/devui-vue/devui/style/core/_reset.scss index 3b10cdcd8c..7f24257299 100755 --- a/packages/devui-vue/devui/style/core/_reset.scss +++ b/packages/devui-vue/devui/style/core/_reset.scss @@ -39,6 +39,11 @@ ol { padding: 0; } +ul, +li { + list-style: none; +} + p { margin: 0; padding: 0; diff --git a/packages/devui-vue/devui/style/theme/_color.scss b/packages/devui-vue/devui/style/theme/_color.scss index 4db1ea0207..3b5b0cc096 100755 --- a/packages/devui-vue/devui/style/theme/_color.scss +++ b/packages/devui-vue/devui/style/theme/_color.scss @@ -105,3 +105,17 @@ $devui-primary-bg: var(--devui-primary-bg, #f2f5fc); // 主要底色 $devui-default-line: var(--devui-default-line, #5e7ce0);// 默认边框 $devui-default-bg: var(--devui-default-bg, #f3f6f8); // 默认底色 + +// Menu + +// color + +$devui-menu-item: var(--devui-menu-item); +$devui-menu-item-sub: var(--devui-menu-item-sub); +$devui-menu-item-disabled: var(--devui-menu-disabled); +$devui-menu-item-select: var(--devui-menu-item-hover); + +$devui-menu-item-selectBar: var(--devui-primary-hover, #5e7ce0); +$devui-menu-active-parent: var(--devui-icon-fill-active); +$devui-menu-bg: var(--devui-global-bg-normal); +$devui-menu-bg-level: var(--devui-global-bg); diff --git a/packages/devui-vue/docs/components/menu/index.md b/packages/devui-vue/docs/components/menu/index.md new file mode 100644 index 0000000000..567fd84034 --- /dev/null +++ b/packages/devui-vue/docs/components/menu/index.md @@ -0,0 +1,266 @@ +# Menu 菜单 + +Menu 组件通常用于导航. + +### 何时使用 + +在需要收纳,排列,展示一系列选项时. + +### 基本用法 + +:::demo + +```vue + +``` + +::: + +### 垂直菜单 + +垂直菜单一般在后台中较为广泛使用,子菜单可以嵌入菜单中 + +:::demo + +```vue + +``` + +::: + +### 默认展开 + +通过设置`open-keys`可以修改展开的子菜单项目. + +:::demo + +```vue + +``` + +::: + +### 收缩菜单 + +:::demo + +```vue + + Collapsed + + + + +``` + +::: + +### 取消多选 + +:::demo + +```vue + +``` + +::: + +### 响应式参数 + +例如,`width`, `open-keys`, `default-select-keys`等参数均为响应式 + +:::demo + +```vue + + + +``` + +::: + +## d-menu + +### d-menu 参数 + +| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | 全局配置项 | +| ------------------- | ---------- | ---------- | -------------------------- | ------------------------- | ---------- | +| width | String | '' | 用于控制菜单宽度 | [响应式参数](#响应式参数) | | +| collapsed | Boolean | false | 用于决定菜单是否收起 | [收缩面板](#收缩面板) | | +| collapsed-indent | Number | 24 | 收起时图表距离菜单的距离 | / | | +| indent-size | Number | 24 | 未收起时二级菜单的缩进大小 | / | | +| multiple | Boolean | false | 是否可以多选 | / | | +| mode | menuMode | 'vertical' | 菜单类型 | [基本用法](#基本用法) | | +| open-keys | Array | [] | 默认展开的子菜单 key 值 | [默认展](#默认展开) | | +| default-select-keys | Array | [] | 默认选择菜单项 key 值 | / | | + +### d-menu 事件 + +| 事件 | 类型 | 说明 | 跳转 Demo | +| -------------- | -------------------------------------------------------------------------- | ---------------------------------------------------- | --------- | +| select | ```(e: {type:'select', el: HTMLElement})=>void``` | 选中菜单项时触发该事件,被禁用的选项不会被触发 | | +| dselect | ```(e: {type: 'dselect', el: HTMLElement})=>void``` | 取消选中时触发该事件,如果菜单不是多选菜单不会被触发 | | +| submenu-change | ```(e: {type: 'submenu-change': el: HTMLElement: state: boolean})=>void``` | 子菜单状态被更改时会触发 | + +## d-menu-item + + +| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | 全局配置项 | +| :----: | :----: | :----: | :----: | :---------: | ---------- | +|disable|boolean|false|是否禁用||| +|key|string|''|菜单项的key值,需唯一||| +|href|string|''|单击菜单项后跳转的页面|[基本用法](#基本用法)| + +## d-sub-menu +| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | 全局配置项 | +| :----: | :----: | :----: | :----: | :---------: | ---------- | +|title|String|''|子菜单标题||| +|disable|boolean|false|是否禁用子菜单||| + +## 接口及其定义 + +``` typescript +export type menuMode = 'vertical' | 'horizontal'; +``` diff --git a/packages/devui-vue/docs/en-US/components/menu/index.md b/packages/devui-vue/docs/en-US/components/menu/index.md new file mode 100644 index 0000000000..ca9ef03f60 --- /dev/null +++ b/packages/devui-vue/docs/en-US/components/menu/index.md @@ -0,0 +1,264 @@ +# Menu + +Menu component is usually used for navigation + +### When to used. + +When it comes to packing, arranging, and displaying a range of options. + +### Basic Used + +:::demo + +```vue + +``` + +::: + +### vertical Menu + +vertical menu usually used in back-end. sub-menu can Embed in a menu + +:::demo + +```vue + +``` + +::: + +### default open + +Setting`open-keys`can change sub menu item opan state + +:::demo + +```vue + +``` + +::: + +### collapse menu + +To change `collapsed` can change menu collapsed status. + +:::demo + +```vue + + Collapsed + + + + +``` + +::: + +### cancel multi-selection + +:::demo + +```vue + +``` + +::: + +### dynamic argument + +For example, parameters such as 'width', 'open-keys', 'default-select-keys' can be modified dynamically + +:::demo + +```vue + + + +``` + +::: + +## d-menu + +### d-menu 参数 + +| argument | type | default value | introduce| Demo| global config | +| --- | --- | --- | --- | --- | --- | +| width | String | '' | controller menu width | [dynamic argument](#dynamic argument) | | +| collapsed | Boolean | false | controller menu collapsed status | [collapse menu](#collapse menu) | | +| collapsed-indent | Number | 24 | If menu is collapse icon sistance from the menu | / | | +| indent-size | indentSize | 24 | Not collapse sub menu indent size | / | | +| multiple | Boolean | false | can multiple if is true | / | | +| mode | menuMode | 'vertical' | menu types | [Basic Used](#Basic Used) | | +| open-keys | Array | [] | default open sub menu's key | [default open](#default open) | | +| default-select-keys | Array | [] | default select menu item's key | / | | + +### d-menu 事件 + +| event| type|introduce| Demo | +| :---: | :---: | :---: | :---: | :---: | :---: | +| select | `(e: {type:'select', el: HTMLElement})=>void` | select menu item will emit this event. if is disabled can't emit this event | +| dselect | `(e: {type: 'dselect', el: HTMLElement})=>void` | The event is triggered when unchecked, if the menu is not multi-select the menu is not triggered | | +| submenu-change | `(e: {type: 'submenu-change': el: HTMLElement: state: boolean})=>void` | Triggered when the submenu state is changed | + +## d-menu-item + +| argument | type | default value | introduce| Demo| global config | +| :---: | :---: | :---: | --- | --- | :---: | +| disable | boolean | false | Disabled this menu item. | | | +| key | string | '' | menu item's key. Must is unique | | | +| href | string | '' | click menu item will link to page url. | [基本用法](#基本用法) | + +## d-sub-menu + +| event| type|introduce| Demo | +| :---: | :---: | :---: | :---: | :---: | :---: | +| title | String | '' | sub menu's title | | | +| disable | boolean | false | disabled sub menu | | | + +## Interface & declare + +```typescript +export type menuMode = 'vertical' | 'horizontal'; +```