menu.vue 22 KB

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