menu.vue 21 KB

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