menu.vue 21 KB

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