menu.vue 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860
  1. <script lang="ts" setup>
  2. import type { UseResizeObserverReturn } from '@vueuse/core';
  3. import type { SetupContext, VNodeArrayChildren } from 'vue';
  4. import type {
  5. MenuItemClicked,
  6. MenuItemRegistered,
  7. MenuProps,
  8. MenuProvider,
  9. } from '../types';
  10. import {
  11. computed,
  12. nextTick,
  13. reactive,
  14. ref,
  15. toRef,
  16. useSlots,
  17. watch,
  18. watchEffect,
  19. } from 'vue';
  20. import { useNamespace } from '@vben-core/composables';
  21. import { Ellipsis } from '@vben-core/icons';
  22. import { isHttpUrl } from '@vben-core/shared/utils';
  23. import { useResizeObserver } from '@vueuse/core';
  24. import {
  25. createMenuContext,
  26. createSubMenuContext,
  27. useMenuStyle,
  28. } from '../hooks';
  29. import { flattedChildren } from '../utils';
  30. import SubMenu from './sub-menu.vue';
  31. interface Props extends MenuProps {}
  32. defineOptions({ name: 'Menu' });
  33. const props = withDefaults(defineProps<Props>(), {
  34. accordion: true,
  35. collapse: false,
  36. mode: 'vertical',
  37. rounded: true,
  38. theme: 'dark',
  39. });
  40. const emit = defineEmits<{
  41. close: [string, string[]];
  42. open: [string, string[]];
  43. select: [string, string[]];
  44. }>();
  45. const { b, is } = useNamespace('menu');
  46. const menuStyle = useMenuStyle();
  47. const slots: SetupContext['slots'] = useSlots();
  48. const menu = ref<HTMLUListElement>();
  49. const sliceIndex = ref(-1);
  50. const openedMenus = ref<MenuProvider['openedMenus']>(
  51. props.defaultOpeneds && !props.collapse ? [...props.defaultOpeneds] : [],
  52. );
  53. const activePath = ref<MenuProvider['activePath']>(props.defaultActive);
  54. const items = ref<MenuProvider['items']>({});
  55. const subMenus = ref<MenuProvider['subMenus']>({});
  56. const mouseInChild = ref(false);
  57. const isMenuPopup = computed<MenuProvider['isMenuPopup']>(() => {
  58. return (
  59. props.mode === 'horizontal' || (props.mode === 'vertical' && props.collapse)
  60. );
  61. });
  62. const getSlot = computed(() => {
  63. // 更新插槽内容
  64. const defaultSlots: VNodeArrayChildren = slots.default?.() ?? [];
  65. const originalSlot = flattedChildren(defaultSlots) as VNodeArrayChildren;
  66. const slotDefault =
  67. sliceIndex.value === -1
  68. ? originalSlot
  69. : originalSlot.slice(0, sliceIndex.value);
  70. const slotMore =
  71. sliceIndex.value === -1 ? [] : originalSlot.slice(sliceIndex.value);
  72. return { showSlotMore: slotMore.length > 0, slotDefault, slotMore };
  73. });
  74. watch(
  75. () => props.collapse,
  76. (value) => {
  77. if (value) openedMenus.value = [];
  78. },
  79. );
  80. watch(items.value, initMenu);
  81. watch(
  82. () => props.defaultActive,
  83. (currentActive = '') => {
  84. if (!items.value[currentActive]) {
  85. activePath.value = '';
  86. }
  87. updateActiveName(currentActive);
  88. },
  89. );
  90. let resizeStopper: UseResizeObserverReturn['stop'];
  91. watchEffect(() => {
  92. if (props.mode === 'horizontal') {
  93. resizeStopper = useResizeObserver(menu, handleResize).stop;
  94. } else {
  95. resizeStopper?.();
  96. }
  97. });
  98. // 注入上下文
  99. createMenuContext(
  100. reactive({
  101. activePath,
  102. addMenuItem,
  103. addSubMenu,
  104. closeMenu,
  105. handleMenuItemClick,
  106. handleSubMenuClick,
  107. isMenuPopup,
  108. openedMenus,
  109. openMenu,
  110. props,
  111. removeMenuItem,
  112. removeSubMenu,
  113. subMenus,
  114. theme: toRef(props, 'theme'),
  115. items,
  116. }),
  117. );
  118. createSubMenuContext({
  119. addSubMenu,
  120. level: 1,
  121. mouseInChild,
  122. removeSubMenu,
  123. });
  124. function calcMenuItemWidth(menuItem: HTMLElement) {
  125. const computedStyle = getComputedStyle(menuItem);
  126. const marginLeft = Number.parseInt(computedStyle.marginLeft, 10);
  127. const marginRight = Number.parseInt(computedStyle.marginRight, 10);
  128. return menuItem.offsetWidth + marginLeft + marginRight || 0;
  129. }
  130. function calcSliceIndex() {
  131. if (!menu.value) {
  132. return -1;
  133. }
  134. const items = [...(menu.value?.childNodes ?? [])].filter(
  135. (item) =>
  136. // remove comment type node #12634
  137. item.nodeName !== '#comment' &&
  138. (item.nodeName !== '#text' || item.nodeValue),
  139. ) as HTMLElement[];
  140. const moreItemWidth = 46;
  141. const computedMenuStyle = getComputedStyle(menu?.value);
  142. const paddingLeft = Number.parseInt(computedMenuStyle.paddingLeft, 10);
  143. const paddingRight = Number.parseInt(computedMenuStyle.paddingRight, 10);
  144. const menuWidth = menu.value?.clientWidth - paddingLeft - paddingRight;
  145. let calcWidth = 0;
  146. let sliceIndex = 0;
  147. items.forEach((item, index) => {
  148. calcWidth += calcMenuItemWidth(item);
  149. if (calcWidth <= menuWidth - moreItemWidth) {
  150. sliceIndex = index + 1;
  151. }
  152. });
  153. return sliceIndex === items.length ? -1 : sliceIndex;
  154. }
  155. function debounce(fn: () => void, wait = 33.34) {
  156. let timer: null | ReturnType<typeof setTimeout>;
  157. return () => {
  158. timer && clearTimeout(timer);
  159. timer = setTimeout(() => {
  160. fn();
  161. }, wait);
  162. };
  163. }
  164. let isFirstTimeRender = true;
  165. function handleResize() {
  166. if (sliceIndex.value === calcSliceIndex()) {
  167. return;
  168. }
  169. const callback = () => {
  170. sliceIndex.value = -1;
  171. nextTick(() => {
  172. sliceIndex.value = calcSliceIndex();
  173. });
  174. };
  175. callback();
  176. // // execute callback directly when first time resize to avoid shaking
  177. isFirstTimeRender ? callback() : debounce(callback)();
  178. isFirstTimeRender = false;
  179. }
  180. function getActivePaths() {
  181. const activeItem = activePath.value && items.value[activePath.value];
  182. if (!activeItem || props.mode === 'horizontal' || props.collapse) {
  183. return [];
  184. }
  185. return activeItem.parentPaths;
  186. }
  187. // 默认展开菜单
  188. function initMenu() {
  189. const parentPaths = getActivePaths();
  190. // 展开该菜单项的路径上所有子菜单
  191. // expand all subMenus of the menu item
  192. parentPaths.forEach((path) => {
  193. const subMenu = subMenus.value[path];
  194. subMenu && openMenu(path, subMenu.parentPaths);
  195. });
  196. }
  197. function updateActiveName(val: string) {
  198. const itemsInData = items.value;
  199. const item =
  200. itemsInData[val] ||
  201. (activePath.value && itemsInData[activePath.value]) ||
  202. itemsInData[props.defaultActive || ''];
  203. activePath.value = item ? item.path : val;
  204. }
  205. function handleMenuItemClick(data: MenuItemClicked) {
  206. const { collapse, mode } = props;
  207. if (mode === 'horizontal' || collapse) {
  208. openedMenus.value = [];
  209. }
  210. const { parentPaths, path } = data;
  211. if (!path || !parentPaths) {
  212. return;
  213. }
  214. if (!isHttpUrl(path)) {
  215. activePath.value = path;
  216. }
  217. emit('select', path, parentPaths);
  218. }
  219. function handleSubMenuClick({ parentPaths, path }: MenuItemRegistered) {
  220. const isOpened = openedMenus.value.includes(path);
  221. if (isOpened) {
  222. closeMenu(path, parentPaths);
  223. } else {
  224. openMenu(path, parentPaths);
  225. }
  226. }
  227. function close(path: string) {
  228. const i = openedMenus.value.indexOf(path);
  229. if (i !== -1) {
  230. openedMenus.value.splice(i, 1);
  231. }
  232. }
  233. /**
  234. * 关闭、折叠菜单
  235. */
  236. function closeMenu(path: string, parentPaths: string[]) {
  237. if (props.accordion) {
  238. openedMenus.value = subMenus.value[path]?.parentPaths ?? [];
  239. }
  240. close(path);
  241. emit('close', path, parentPaths);
  242. }
  243. /**
  244. * 点击展开菜单
  245. */
  246. function openMenu(path: string, parentPaths: string[]) {
  247. if (openedMenus.value.includes(path)) {
  248. return;
  249. }
  250. // 手风琴模式菜单
  251. if (props.accordion) {
  252. const activeParentPaths = getActivePaths();
  253. if (activeParentPaths.includes(path)) {
  254. parentPaths = activeParentPaths;
  255. }
  256. openedMenus.value = openedMenus.value.filter((path: string) =>
  257. parentPaths.includes(path),
  258. );
  259. }
  260. openedMenus.value.push(path);
  261. emit('open', path, parentPaths);
  262. }
  263. function addMenuItem(item: MenuItemRegistered) {
  264. items.value[item.path] = item;
  265. }
  266. function addSubMenu(subMenu: MenuItemRegistered) {
  267. subMenus.value[subMenu.path] = subMenu;
  268. }
  269. function removeSubMenu(subMenu: MenuItemRegistered) {
  270. Reflect.deleteProperty(subMenus.value, subMenu.path);
  271. }
  272. function removeMenuItem(item: MenuItemRegistered) {
  273. Reflect.deleteProperty(items.value, item.path);
  274. }
  275. </script>
  276. <template>
  277. <ul
  278. ref="menu"
  279. :class="[
  280. theme,
  281. b(),
  282. is(mode, true),
  283. is(theme, true),
  284. is('rounded', rounded),
  285. is('collapse', collapse),
  286. is('menu-align', mode === 'horizontal'),
  287. ]"
  288. :style="menuStyle"
  289. role="menu"
  290. >
  291. <template v-if="mode === 'horizontal' && getSlot.showSlotMore">
  292. <template v-for="item in getSlot.slotDefault" :key="item.key">
  293. <component :is="item" />
  294. </template>
  295. <SubMenu is-sub-menu-more path="sub-menu-more">
  296. <template #title>
  297. <Ellipsis class="size-4" />
  298. </template>
  299. <template v-for="item in getSlot.slotMore" :key="item.key">
  300. <component :is="item" />
  301. </template>
  302. </SubMenu>
  303. </template>
  304. <template v-else>
  305. <slot></slot>
  306. </template>
  307. </ul>
  308. </template>
  309. <style lang="scss">
  310. $namespace: vben;
  311. @mixin menu-item-active {
  312. color: var(--menu-item-active-color);
  313. text-decoration: none;
  314. cursor: pointer;
  315. background: var(--menu-item-active-background-color);
  316. }
  317. @mixin menu-item {
  318. position: relative;
  319. display: flex;
  320. // gap: 12px;
  321. align-items: center;
  322. height: var(--menu-item-height);
  323. padding: var(--menu-item-padding-y) var(--menu-item-padding-x);
  324. margin: 0 var(--menu-item-margin-x) var(--menu-item-margin-y)
  325. var(--menu-item-margin-x);
  326. font-size: var(--menu-font-size);
  327. color: var(--menu-item-color);
  328. text-decoration: none;
  329. white-space: nowrap;
  330. list-style: none;
  331. cursor: pointer;
  332. background: var(--menu-item-background-color);
  333. border: none;
  334. border-radius: var(--menu-item-radius);
  335. transition:
  336. background 0.15s ease,
  337. color 0.15s ease,
  338. padding 0.15s ease,
  339. border-color 0.15s ease;
  340. &.is-disabled {
  341. cursor: not-allowed;
  342. background: none !important;
  343. opacity: 0.25;
  344. }
  345. .#{$namespace}-menu__icon {
  346. transition: transform 0.25s;
  347. }
  348. &:hover {
  349. .#{$namespace}-menu__icon {
  350. transform: scale(1.2);
  351. }
  352. }
  353. &:hover,
  354. &:focus {
  355. outline: none;
  356. }
  357. * {
  358. vertical-align: bottom;
  359. }
  360. }
  361. @mixin menu-title {
  362. max-width: var(--menu-title-width);
  363. overflow: hidden;
  364. text-overflow: ellipsis;
  365. white-space: nowrap;
  366. opacity: 1;
  367. }
  368. .is-menu-align {
  369. justify-content: var(--menu-align, start);
  370. }
  371. .#{$namespace}-menu__popup-container,
  372. .#{$namespace}-menu {
  373. --menu-title-width: 140px;
  374. --menu-item-icon-size: 16px;
  375. --menu-item-height: 38px;
  376. --menu-item-padding-y: 21px;
  377. --menu-item-padding-x: 12px;
  378. --menu-item-popup-padding-y: 20px;
  379. --menu-item-popup-padding-x: 12px;
  380. --menu-item-margin-y: 2px;
  381. --menu-item-margin-x: 0px;
  382. --menu-item-collapse-padding-y: 23.5px;
  383. --menu-item-collapse-padding-x: 0px;
  384. --menu-item-collapse-margin-y: 4px;
  385. --menu-item-collapse-margin-x: 0px;
  386. --menu-item-radius: 0px;
  387. --menu-item-indent: 16px;
  388. --menu-font-size: 14px;
  389. &.is-dark {
  390. --menu-background-color: hsl(var(--menu));
  391. // --menu-submenu-opened-background-color: hsl(var(--menu-opened-dark));
  392. --menu-item-background-color: var(--menu-background-color);
  393. --menu-item-color: hsl(var(--foreground) / 80%);
  394. --menu-item-hover-color: hsl(var(--accent-foreground));
  395. --menu-item-hover-background-color: hsl(var(--accent));
  396. --menu-item-active-color: hsl(var(--accent-foreground));
  397. --menu-item-active-background-color: hsl(var(--accent));
  398. --menu-submenu-hover-color: hsl(var(--foreground));
  399. --menu-submenu-hover-background-color: hsl(var(--accent));
  400. --menu-submenu-active-color: hsl(var(--foreground));
  401. --menu-submenu-active-background-color: transparent;
  402. --menu-submenu-background-color: var(--menu-background-color);
  403. }
  404. &.is-light {
  405. --menu-background-color: hsl(var(--menu));
  406. // --menu-submenu-opened-background-color: hsl(var(--menu-opened));
  407. --menu-item-background-color: var(--menu-background-color);
  408. --menu-item-color: hsl(var(--foreground));
  409. --menu-item-hover-color: var(--menu-item-color);
  410. --menu-item-hover-background-color: hsl(var(--accent));
  411. --menu-item-active-color: hsl(var(--primary));
  412. --menu-item-active-background-color: hsl(var(--primary) / 15%);
  413. --menu-submenu-hover-color: hsl(var(--primary));
  414. --menu-submenu-hover-background-color: hsl(var(--accent));
  415. --menu-submenu-active-color: hsl(var(--primary));
  416. --menu-submenu-active-background-color: transparent;
  417. --menu-submenu-background-color: var(--menu-background-color);
  418. }
  419. &.is-rounded {
  420. --menu-item-margin-x: 8px;
  421. --menu-item-collapse-margin-x: 6px;
  422. --menu-item-radius: 8px;
  423. }
  424. &.is-horizontal:not(.is-rounded) {
  425. --menu-item-height: 40px;
  426. --menu-item-radius: 6px;
  427. }
  428. &.is-horizontal.is-rounded {
  429. --menu-item-height: 40px;
  430. --menu-item-radius: 6px;
  431. --menu-item-padding-x: 12px;
  432. }
  433. // .vben-menu__popup,
  434. &.is-horizontal {
  435. --menu-item-padding-y: 0px;
  436. --menu-item-padding-x: 10px;
  437. --menu-item-margin-y: 0px;
  438. --menu-item-margin-x: 1px;
  439. --menu-background-color: transparent;
  440. &.is-dark {
  441. --menu-item-hover-color: hsl(var(--accent-foreground));
  442. --menu-item-hover-background-color: hsl(var(--accent));
  443. --menu-item-active-color: hsl(var(--accent-foreground));
  444. --menu-item-active-background-color: hsl(var(--accent));
  445. --menu-submenu-active-color: hsl(var(--foreground));
  446. --menu-submenu-active-background-color: hsl(var(--accent));
  447. --menu-submenu-hover-color: hsl(var(--accent-foreground));
  448. --menu-submenu-hover-background-color: hsl(var(--accent));
  449. }
  450. &.is-light {
  451. --menu-item-active-color: hsl(var(--primary));
  452. --menu-item-active-background-color: hsl(var(--primary) / 15%);
  453. --menu-item-hover-background-color: hsl(var(--accent));
  454. --menu-item-hover-color: hsl(var(--primary));
  455. --menu-submenu-active-color: hsl(var(--primary));
  456. --menu-submenu-active-background-color: hsl(var(--primary) / 15%);
  457. --menu-submenu-hover-color: hsl(var(--primary));
  458. --menu-submenu-hover-background-color: hsl(var(--accent));
  459. }
  460. }
  461. }
  462. .#{$namespace}-menu {
  463. position: relative;
  464. box-sizing: border-box;
  465. padding-left: 0;
  466. margin: 0;
  467. list-style: none;
  468. background: hsl(var(--menu-background-color));
  469. // 垂直菜单
  470. &.is-vertical {
  471. &:not(.#{$namespace}-menu.is-collapse) {
  472. & .#{$namespace}-menu-item,
  473. & .#{$namespace}-sub-menu-content,
  474. & .#{$namespace}-menu-item-group__title {
  475. padding-left: calc(
  476. var(--menu-item-indent) + var(--menu-level) * var(--menu-item-indent)
  477. );
  478. white-space: nowrap;
  479. }
  480. & > .#{$namespace}-sub-menu {
  481. & > .#{$namespace}-menu {
  482. & > .#{$namespace}-menu-item {
  483. padding-left: calc(
  484. 0px + var(--menu-item-indent) + var(--menu-level) *
  485. var(--menu-item-indent)
  486. );
  487. }
  488. }
  489. & > .#{$namespace}-sub-menu-content {
  490. padding-left: calc(var(--menu-item-indent) - 8px);
  491. }
  492. }
  493. & > .#{$namespace}-menu-item {
  494. padding-left: calc(var(--menu-item-indent) - 8px);
  495. }
  496. }
  497. }
  498. &.is-horizontal {
  499. display: flex;
  500. flex-wrap: nowrap;
  501. max-width: 100%;
  502. height: var(--height-horizontal-height);
  503. border-right: none;
  504. .#{$namespace}-menu-item {
  505. display: inline-flex;
  506. align-items: center;
  507. justify-content: center;
  508. height: var(--menu-item-height);
  509. padding-right: calc(var(--menu-item-padding-x) + 6px);
  510. margin: 0;
  511. margin-right: 2px;
  512. // border-bottom: 2px solid transparent;
  513. border-radius: var(--menu-item-radius);
  514. }
  515. & > .#{$namespace}-sub-menu {
  516. height: var(--menu-item-height);
  517. margin-right: 2px;
  518. &:focus,
  519. &:hover {
  520. outline: none;
  521. }
  522. & .#{$namespace}-sub-menu-content {
  523. height: 100%;
  524. padding-right: 40px;
  525. // border-bottom: 2px solid transparent;
  526. border-radius: var(--menu-item-radius);
  527. }
  528. }
  529. & .#{$namespace}-menu-item:not(.is-disabled):hover,
  530. & .#{$namespace}-menu-item:not(.is-disabled):focus {
  531. outline: none;
  532. }
  533. & > .#{$namespace}-menu-item.is-active {
  534. color: var(--menu-item-active-color);
  535. }
  536. // &.is-light {
  537. // & > .#{$namespace}-sub-menu {
  538. // &.is-active {
  539. // border-bottom: 2px solid var(--menu-item-active-color);
  540. // }
  541. // &:not(.is-active) .#{$namespace}-sub-menu-content {
  542. // &:hover {
  543. // border-bottom: 2px solid var(--menu-item-active-color);
  544. // }
  545. // }
  546. // }
  547. // & > .#{$namespace}-menu-item.is-active {
  548. // border-bottom: 2px solid var(--menu-item-active-color);
  549. // }
  550. // & .#{$namespace}-menu-item:not(.is-disabled):hover,
  551. // & .#{$namespace}-menu-item:not(.is-disabled):focus {
  552. // border-bottom: 2px solid var(--menu-item-active-color);
  553. // }
  554. // }
  555. }
  556. // 折叠菜单
  557. &.is-collapse {
  558. .#{$namespace}-menu__icon {
  559. margin-right: 0;
  560. }
  561. .#{$namespace}-sub-menu__icon-arrow {
  562. display: none;
  563. }
  564. .#{$namespace}-sub-menu-content,
  565. .#{$namespace}-menu-item {
  566. display: flex;
  567. align-items: center;
  568. justify-content: center;
  569. padding: var(--menu-item-collapse-padding-y)
  570. var(--menu-item-collapse-padding-x);
  571. margin: var(--menu-item-collapse-margin-y)
  572. var(--menu-item-collapse-margin-x);
  573. transition: all 0.3s;
  574. &.is-active {
  575. background: var(--menu-item-active-background-color) !important;
  576. border-radius: var(--menu-item-radius);
  577. }
  578. }
  579. &.is-light {
  580. .#{$namespace}-sub-menu-content,
  581. .#{$namespace}-menu-item {
  582. &.is-active {
  583. // color: hsl(var(--primary-foreground)) !important;
  584. background: var(--menu-item-active-background-color) !important;
  585. }
  586. }
  587. }
  588. &.is-rounded {
  589. .#{$namespace}-sub-menu-content,
  590. .#{$namespace}-menu-item {
  591. &.is-collapse-show-title {
  592. // padding: 32px 0 !important;
  593. margin: 4px 8px !important;
  594. }
  595. }
  596. }
  597. }
  598. &__popup-container {
  599. max-width: 240px;
  600. height: unset;
  601. padding: 0;
  602. background: var(--menu-background-color);
  603. }
  604. &__popup {
  605. padding: 10px 0;
  606. border-radius: var(--menu-item-radius);
  607. .#{$namespace}-sub-menu-content,
  608. .#{$namespace}-menu-item {
  609. padding: var(--menu-item-popup-padding-y) var(--menu-item-popup-padding-x);
  610. }
  611. }
  612. &__icon {
  613. flex-shrink: 0;
  614. width: var(--menu-item-icon-size);
  615. height: var(--menu-item-icon-size);
  616. margin-right: 8px;
  617. text-align: center;
  618. vertical-align: middle;
  619. }
  620. }
  621. .#{$namespace}-menu-item {
  622. fill: var(--menu-item-color);
  623. @include menu-item;
  624. &.is-active {
  625. fill: var(--menu-item-active-color);
  626. @include menu-item-active;
  627. }
  628. &__content {
  629. display: inline-flex;
  630. align-items: center;
  631. width: 100%;
  632. height: var(--menu-item-height);
  633. span {
  634. @include menu-title;
  635. }
  636. }
  637. &.is-collapse-show-title {
  638. padding: 32px 0 !important;
  639. // margin: 4px 8px !important;
  640. .#{$namespace}-menu-tooltip__trigger {
  641. flex-direction: column;
  642. }
  643. .#{$namespace}-menu__icon {
  644. display: block;
  645. font-size: 20px !important;
  646. transition: all 0.25s ease;
  647. }
  648. .#{$namespace}-menu__name {
  649. display: inline-flex;
  650. margin-top: 8px;
  651. margin-bottom: 0;
  652. font-size: 12px;
  653. font-weight: 400;
  654. line-height: normal;
  655. transition: all 0.25s ease;
  656. }
  657. }
  658. &:not(.is-active):hover {
  659. color: var(--menu-item-hover-color);
  660. text-decoration: none;
  661. cursor: pointer;
  662. background: var(--menu-item-hover-background-color) !important;
  663. }
  664. .#{$namespace}-menu-tooltip__trigger {
  665. position: absolute;
  666. top: 0;
  667. left: 0;
  668. box-sizing: border-box;
  669. display: inline-flex;
  670. align-items: center;
  671. justify-content: center;
  672. width: 100%;
  673. height: 100%;
  674. padding: 0 var(--menu-item-padding-x);
  675. font-size: var(--menu-font-size);
  676. line-height: var(--menu-item-height);
  677. }
  678. }
  679. .#{$namespace}-sub-menu {
  680. padding-left: 0;
  681. margin: 0;
  682. list-style: none;
  683. background: var(--menu-submenu-background-color);
  684. fill: var(--menu-item-color);
  685. &.is-active {
  686. div[data-state='open'] > .#{$namespace}-sub-menu-content,
  687. > .#{$namespace}-sub-menu-content {
  688. // font-weight: 500;
  689. color: var(--menu-submenu-active-color);
  690. text-decoration: none;
  691. cursor: pointer;
  692. background: var(--menu-submenu-active-background-color);
  693. fill: var(--menu-submenu-active-color);
  694. }
  695. }
  696. }
  697. .#{$namespace}-sub-menu-content {
  698. height: var(--menu-item-height);
  699. @include menu-item;
  700. &__icon-arrow {
  701. position: absolute;
  702. top: 50%;
  703. right: 10px;
  704. width: inherit;
  705. margin-top: -8px;
  706. margin-right: 0;
  707. // font-size: 16px;
  708. font-weight: normal;
  709. opacity: 1;
  710. transition: transform 0.25s ease;
  711. }
  712. &__title {
  713. @include menu-title;
  714. }
  715. &.is-collapse-show-title {
  716. flex-direction: column;
  717. padding: 32px 0 !important;
  718. // margin: 4px 8px !important;
  719. .#{$namespace}-menu__icon {
  720. display: block;
  721. font-size: 20px !important;
  722. transition: all 0.25s ease;
  723. }
  724. .#{$namespace}-sub-menu-content__title {
  725. display: inline-flex;
  726. flex-shrink: 0;
  727. margin-top: 8px;
  728. margin-bottom: 0;
  729. font-size: 12px;
  730. font-weight: 400;
  731. line-height: normal;
  732. transition: all 0.25s ease;
  733. }
  734. }
  735. &.is-more {
  736. padding-right: 12px !important;
  737. }
  738. // &:not(.is-active):hover {
  739. &:hover {
  740. color: var(--menu-submenu-hover-color);
  741. text-decoration: none;
  742. cursor: pointer;
  743. background: var(--menu-submenu-hover-background-color) !important;
  744. // svg {
  745. // fill: var(--menu-submenu-hover-color);
  746. // }
  747. }
  748. }
  749. </style>