perf: improve overall theme color matching

This commit is contained in:
vince
2024-07-15 23:53:58 +08:00
parent caf1fc4375
commit f95d9aa609
39 changed files with 525 additions and 843 deletions

View File

@@ -37,6 +37,7 @@
}
},
"dependencies": {
"@vben-core/hooks": "workspace:*",
"@vben-core/icons": "workspace:*",
"@vben-core/shadcn-ui": "workspace:*",
"@vben-core/toolkit": "workspace:*",

View File

@@ -2,11 +2,9 @@
import type { ContentCompactType } from '@vben-core/typings';
import type { CSSProperties } from 'vue';
import { computed, onMounted, ref, watch } from 'vue';
import { computed } from 'vue';
import { getElementVisibleHeight } from '@vben-core/toolkit';
import { useCssVar, useDebounceFn, useWindowSize } from '@vueuse/core';
import { useContentHeightListener } from '@vben-core/hooks';
interface Props {
/**
@@ -56,13 +54,7 @@ const props = withDefaults(defineProps<Props>(), {
paddingTop: 16,
});
const contentElement = ref<HTMLDivElement | null>();
const { height, width } = useWindowSize();
const contentClientHeight = useCssVar('--vben-content-client-height');
const debouncedCalcHeight = useDebounceFn(() => {
contentClientHeight.value = `${getElementVisibleHeight(contentElement.value)}px`;
}, 200);
const { contentElement } = useContentHeightListener();
const style = computed((): CSSProperties => {
const {
@@ -88,14 +80,6 @@ const style = computed((): CSSProperties => {
paddingTop: `${paddingTop}px`,
};
});
watch([height, width], () => {
debouncedCalcHeight();
});
onMounted(() => {
debouncedCalcHeight();
});
</script>
<template>

View File

@@ -7,10 +7,6 @@ import { VbenScrollbar } from '@vben-core/shadcn-ui';
import { SidebarCollapseButton, SidebarFixedButton } from './widgets';
interface Props {
/**
* 背景颜色
*/
backgroundColor: string;
/**
* 折叠区域高度
* @default 32
@@ -26,10 +22,6 @@ interface Props {
* @default true
*/
domVisible?: boolean;
/**
* 扩展区域背景颜色
*/
extraBackgroundColor: string;
/**
* 扩展区域宽度
* @default 180
@@ -113,15 +105,15 @@ const slots = useSlots();
const asideRef = shallowRef<HTMLDivElement | null>();
const hiddenSideStyle = computed((): CSSProperties => {
return calcMenuWidthStyle(true);
});
const hiddenSideStyle = computed((): CSSProperties => calcMenuWidthStyle(true));
const isDark = computed(() => props.theme === 'dark');
const style = computed((): CSSProperties => {
const { isSidebarMixed, paddingTop, theme, zIndex } = props;
const { isSidebarMixed, paddingTop, zIndex } = props;
return {
'--scroll-shadow': theme === 'dark' ? 'var(--menu-dark)' : 'var(--menu)',
'--scroll-shadow': isDark.value ? 'var(--menu-dark)' : 'var(--menu)',
...calcMenuWidthStyle(false),
paddingTop: `${paddingTop}px`,
zIndex,
@@ -130,9 +122,14 @@ const style = computed((): CSSProperties => {
});
const extraStyle = computed((): CSSProperties => {
const { extraBackgroundColor, extraWidth, show, width, zIndex } = props;
const { extraWidth, show, width, zIndex } = props;
const backgroundColor = isDark.value
? 'hsl(var(--menu-dark))'
: 'hsl(var(--menu))';
return {
backgroundColor: extraBackgroundColor,
backgroundColor,
left: `${width}px`,
width: extraVisible.value && show ? `${extraWidth}px` : 0,
zIndex,
@@ -196,14 +193,7 @@ watchEffect(() => {
});
function calcMenuWidthStyle(isHiddenDom: boolean): CSSProperties {
const {
backgroundColor,
extraWidth,
fixedExtra,
isSidebarMixed,
show,
width,
} = props;
const { extraWidth, fixedExtra, isSidebarMixed, show, width } = props;
let widthValue = `${width + (isSidebarMixed && fixedExtra && extraVisible.value ? extraWidth : 0)}px`;
@@ -213,6 +203,18 @@ function calcMenuWidthStyle(isHiddenDom: boolean): CSSProperties {
widthValue = `${collapseWidth}px`;
}
let backgroundColor = '';
if (isDark.value) {
backgroundColor = isSidebarMixed
? 'hsl(var(--menu-dark-deep))'
: 'hsl(var(--menu-dark))';
} else {
backgroundColor = isSidebarMixed
? 'hsl(var(--menu-deep))'
: 'hsl(var(--menu))';
}
return {
...(widthValue === '0px' ? { overflow: 'hidden' } : {}),
backgroundColor,
@@ -254,8 +256,9 @@ function handleMouseleave() {
class="h-full transition-all duration-200"
></div>
<aside
:data-theme="theme"
:style="style"
class="border-border fixed left-0 top-0 h-full border-r transition-all duration-200"
class="data-[theme=dark]:border-border-dark border-border fixed left-0 top-0 h-full border-r transition-all duration-200"
@mouseenter="handleMouseenter"
@mouseleave="handleMouseleave"
>
@@ -280,8 +283,9 @@ function handleMouseleave() {
<div
v-if="isSidebarMixed"
ref="asideRef"
:data-theme="theme"
:style="extraStyle"
class="fixed top-0 h-full overflow-hidden transition-all duration-200"
class="data-[theme=dark]:border-border-dark border-border fixed top-0 h-full overflow-hidden border-x transition-all duration-200"
>
<SidebarCollapseButton
v-if="isSidebarMixed && expandOnHover"
@@ -294,10 +298,15 @@ function handleMouseleave() {
v-model:expand-on-hover="expandOnHover"
:theme="theme"
/>
<div v-if="!extraCollapse" :style="extraTitleStyle">
<div v-if="!extraCollapse" :style="extraTitleStyle" class="pl-2">
<slot name="extra-title"></slot>
</div>
<VbenScrollbar :style="extraContentStyle" class="py-4" shadow>
<VbenScrollbar
:data-theme="theme"
:style="extraContentStyle"
class="data-[theme=dark]:border-border-dark border-border border-t py-2"
shadow
>
<slot name="extra"></slot>
</VbenScrollbar>
</div>

View File

@@ -5,7 +5,7 @@ interface Props {
theme: string;
}
withDefaults(defineProps<Props>(), {});
defineProps<Props>();
const collapsed = defineModel<boolean>('collapsed');
@@ -17,7 +17,7 @@ function handleCollapsed() {
<template>
<div
:data-theme="theme"
class="flex-center hover:text-foreground text-foreground/60 hover:bg-accent-hover bg-accent absolute bottom-2 left-3 z-10 cursor-pointer rounded-sm p-1 transition-all duration-300 data-[theme=dark]:bg-[hsl(var(--dark-accent))] data-[theme=dark]:text-[hsl(var(--dark-foreground)/60%)] data-[theme=dark]:hover:bg-[hsl(var(--dark-accent-hover))] data-[theme=dark]:hover:text-[hsl(var(--dark-foreground))]"
class="flex-center hover:text-foreground text-foreground/60 hover:bg-accent-hover bg-accent data-[theme=dark]:hover:bg-accent-dark-hover data-[theme=dark]:bg-accent-dark data-[theme=dark]:text-foreground-dark/60 data-[theme=dark]:hover:text-foreground-dark absolute bottom-2 left-3 z-10 cursor-pointer rounded-sm p-1 transition-all duration-300"
@click.stop="handleCollapsed"
>
<MdiMenuClose v-if="collapsed" />

View File

@@ -5,7 +5,7 @@ interface Props {
theme: string;
}
withDefaults(defineProps<Props>(), {});
defineProps<Props>();
const expandOnHover = defineModel<boolean>('expandOnHover');
@@ -17,7 +17,7 @@ function toggleFixed() {
<template>
<div
:data-theme="theme"
class="flex-center hover:text-foreground text-foreground/60 hover:bg-accent-hover bg-accent absolute bottom-2 right-3 z-10 cursor-pointer rounded-sm p-1 transition-all duration-300 data-[theme=dark]:bg-[hsl(var(--dark-accent))] data-[theme=dark]:text-[hsl(var(--dark-foreground)/60%)] data-[theme=dark]:hover:bg-[hsl(var(--dark-accent-hover))] data-[theme=dark]:hover:text-[hsl(var(--dark-foreground))]"
class="flex-center hover:text-foreground text-foreground/60 hover:bg-accent-hover bg-accent data-[theme=dark]:hover:bg-accent-dark-hover data-[theme=dark]:bg-accent-dark data-[theme=dark]:text-foreground-dark/60 data-[theme=dark]:hover:text-foreground-dark absolute bottom-2 right-3 z-10 cursor-pointer rounded-sm p-1 transition-all duration-300"
@click="toggleFixed"
>
<MdiPinOff v-if="!expandOnHover" />

View File

@@ -41,11 +41,6 @@ interface VbenLayoutProps {
* @default 16
*/
contentPaddingTop?: number;
/**
* footer背景颜色
* @default #fff
*/
footerBackgroundColor?: string;
/**
* footer 是否可见
* @default false
@@ -61,11 +56,7 @@ interface VbenLayoutProps {
* @default 32
*/
footerHeight?: number;
/**
* 背景颜色
* @default #fff
*/
headerBackgroundColor?: string;
/**
* header高度
* @default 48
@@ -157,11 +148,6 @@ interface VbenLayoutProps {
* @default 210
*/
sidebarWidth?: number;
/**
* footer背景颜色
* @default #fff
*/
tabbarBackgroundColor?: string;
/**
* tab是否可见
* @default true

View File

@@ -205,25 +205,7 @@ const showSidebar = computed(() => {
const sidebarFace = computed(() => {
const { sidebarSemiDark, sidebarTheme } = props;
const isDark = sidebarTheme === 'dark' || sidebarSemiDark;
let backgroundColor = '';
let extraBackgroundColor = '';
if (isDark) {
backgroundColor = isSidebarMixedNav.value
? 'hsl(var(--menu-dark-deep))'
: 'hsl(var(--menu-dark))';
} else {
backgroundColor = isSidebarMixedNav.value
? 'hsl(var(--menu-deep))'
: 'hsl(var(--menu))';
}
extraBackgroundColor = isDark ? 'hsl(var(--menu-dark))' : 'hsl(var(--menu))';
return {
backgroundColor,
extraBackgroundColor,
theme: isDark ? 'dark' : 'light',
};
});
@@ -476,9 +458,9 @@ function handleOpenMenu() {
:mixed-width="sidebarMixedWidth"
:padding-top="sidePaddingTop"
:show="showSidebar"
:theme="sidebarFace.theme"
:width="getSidebarWidth"
:z-index="sidebarZIndex"
v-bind="sidebarFace"
@leave="() => emit('sideMouseLeave')"
>
<template v-if="isSideMode && !isMixedNav" #logo>

View File

@@ -423,9 +423,9 @@ $namespace: vben;
--menu-title-width: 140px;
--menu-item-icon-width: 20px;
--menu-item-height: 38px;
--menu-item-padding-y: 26px;
--menu-item-padding-y: 22px;
--menu-item-padding-x: 12px;
--menu-item-popup-padding-y: 22px;
--menu-item-popup-padding-y: 20px;
--menu-item-popup-padding-x: 12px;
--menu-item-margin-y: 4px;
--menu-item-margin-x: 0px;
@@ -443,14 +443,14 @@ $namespace: vben;
--menu-background-color: hsl(var(--menu-dark));
// --menu-submenu-opened-background-color: hsl(var(--menu-opened-dark));
--menu-item-background-color: var(--menu-background-color);
--menu-item-color: hsl(var(--dark-foreground) / 80%);
--menu-item-color: hsl(var(--foreground-dark) / 80%);
--menu-item-hover-color: hsl(var(--primary-foreground));
--menu-item-hover-background-color: hsl(var(--menu-dark-background));
--menu-item-active-color: hsl(var(--primary-foreground));
--menu-item-active-background-color: hsl(var(--primary));
--menu-submenu-hover-color: hsl(var(--dark-foreground));
--menu-item-active-color: hsl(var(--foreground-dark));
--menu-item-active-background-color: hsl(var(--menu-dark-background));
--menu-submenu-hover-color: hsl(var(--foreground-dark));
--menu-submenu-hover-background-color: hsl(var(--menu-dark-background));
--menu-submenu-active-color: hsl(var(--dark-foreground));
--menu-submenu-active-color: hsl(var(--foreground-dark));
--menu-submenu-active-background-color: transparent;
--menu-submenu-background-color: var(--menu-background-color);
}
@@ -462,8 +462,8 @@ $namespace: vben;
--menu-item-color: hsl(var(--foreground));
--menu-item-hover-color: var(--menu-item-color);
--menu-item-hover-background-color: hsl(var(--menu-light-background));
--menu-item-active-color: hsl(var(--primary-foreground));
--menu-item-active-background-color: hsl(var(--primary));
--menu-item-active-color: hsl(var(--primary));
--menu-item-active-background-color: hsl(var(--primary) / 15%);
--menu-submenu-hover-color: hsl(var(--primary));
--menu-submenu-hover-background-color: hsl(var(--menu-light-background));
--menu-submenu-active-color: hsl(var(--primary));
@@ -512,10 +512,10 @@ $namespace: vben;
--menu-item-active-background-color: hsl(var(--menu-light-background));
--menu-item-hover-background-color: hsl(var(--menu-light-background));
--menu-item-hover-color: hsl(var(--primary));
--menu-submenu-active-color: hsl(var(--primary));
--menu-submenu-active-background-color: hsl(var(--primary) / 15%);
--menu-submenu-hover-color: hsl(var(--primary));
--menu-submenu-hover-background-color: hsl(var(--menu-light-background));
--menu-submenu-active-color: hsl(var(--foreground));
--menu-submenu-active-background-color: hsl(var(--menu-light-background));
}
}
}
@@ -666,7 +666,7 @@ $namespace: vben;
.#{$namespace}-sub-menu-content,
.#{$namespace}-menu-item {
&.is-active {
color: hsl(var(--primary-foreground)) !important;
// color: hsl(var(--primary-foreground)) !important;
background: var(--menu-item-active-background-color) !important;
}
}
@@ -788,6 +788,7 @@ $namespace: vben;
&.is-active {
div[data-state='open'] > .#{$namespace}-sub-menu-content,
> .#{$namespace}-sub-menu-content {
font-weight: 500;
color: var(--menu-submenu-active-color);
text-decoration: none;
cursor: pointer;
@@ -806,7 +807,7 @@ $namespace: vben;
&__icon-arrow {
position: absolute;
top: 50%;
right: 6px;
right: 10px;
width: inherit;
margin-top: -8px;
margin-right: 0;

View File

@@ -63,7 +63,7 @@ $namespace: vben;
.#{$namespace}-normal-menu {
--menu-item-margin-y: 4px;
--menu-item-margin-x: 0px;
--menu-item-padding-y: 11px;
--menu-item-padding-y: 8px;
--menu-item-padding-x: 0px;
--menu-item-radius: 0px;
--menu-dark-background: 0deg 0% 100% / 10%;
@@ -77,12 +77,21 @@ $namespace: vben;
&.is-dark {
.#{$namespace}-normal-menu__item {
color: hsl(var(--dark-foreground) / 80%);
color: hsl(var(--foreground-dark) / 80%);
&:not(.is-active):hover {
color: hsl(var(--primary-foreground));
background-color: hsl(var(--menu-dark-background));
}
&.is-active {
background-color: hsl(var(--menu-dark-background));
.#{$namespace}-normal-menu__name,
.#{$namespace}-normal-menu__icon {
color: hsl(var(--primary-foreground));
}
}
}
}
@@ -115,26 +124,21 @@ $namespace: vben;
border-radius: var(--menu-item-radius);
transition:
background 0.15s ease,
// color 0.15s ease,
padding 0.15s ease,
border-color 0.15s ease;
&.is-active {
font-weight: 700;
color: hsl(var(--primary-foreground));
background-color: hsl(var(--primary));
.#{$namespace}-normal-menu__name {
color: hsl(var(--primary-foreground));
}
@apply text-primary bg-primary/20;
.#{$namespace}-normal-menu__name,
.#{$namespace}-normal-menu__icon {
color: hsl(var(--primary-foreground));
@apply text-primary font-semibold;
}
}
&:not(.is-active):hover {
color: hsl(var(--foreground));
@apply text-foreground;
background-color: hsl(var(--menu-dark-background));
}

View File

@@ -1,506 +0,0 @@
$namespace: vben;
.#{$namespace}-menu__popup-container,
.#{$namespace}-menu {
--menu-title-width: 140px;
--menu-item-icon-width: 20px;
--menu-item-height: 38px;
--menu-item-padding-y: 26px;
--menu-item-padding-x: 12px;
--menu-item-popup-padding-y: 22px;
--menu-item-popup-padding-x: 12px;
--menu-item-margin-y: 4px;
--menu-item-margin-x: 0px;
--menu-item-collapse-padding-y: 25px;
--menu-item-collapse-padding-x: 0px;
--menu-item-collapse-margin-y: 4px;
--menu-item-collapse-margin-x: 0px;
--menu-item-radius: 0px;
--menu-item-indent: 16px;
--menu-font-size: 14px;
--menu-dark-background: 0deg 0% 100% / 10%;
--menu-light-background: 192deg 1% 93%;
&.is-dark {
--menu-background-color: hsl(var(--menu-dark));
// --menu-submenu-opened-background-color: hsl(var(--menu-opened-dark));
--menu-item-background-color: var(--menu-background-color);
--menu-item-color: hsl(var(--dark-foreground) / 80%);
--menu-item-hover-color: hsl(var(--primary-foreground));
--menu-item-hover-background-color: hsl(var(--menu-dark-background));
--menu-item-active-color: hsl(var(--primary-foreground));
--menu-item-active-background-color: hsl(var(--primary));
--menu-submenu-hover-color: hsl(var(--dark-foreground));
--menu-submenu-hover-background-color: hsl(var(--menu-dark-background));
--menu-submenu-active-color: hsl(var(--dark-foreground));
--menu-submenu-active-background-color: transparent;
--menu-submenu-background-color: var(--menu-background-color);
}
&.is-light {
--menu-background-color: hsl(var(--menu));
// --menu-submenu-opened-background-color: hsl(var(--menu-opened));
--menu-item-background-color: var(--menu-background-color);
--menu-item-color: hsl(var(--foreground));
--menu-item-hover-color: var(--menu-item-color);
--menu-item-hover-background-color: hsl(var(--menu-light-background));
--menu-item-active-color: hsl(var(--primary-foreground));
--menu-item-active-background-color: hsl(var(--primary));
--menu-submenu-hover-color: hsl(var(--primary));
--menu-submenu-hover-background-color: hsl(var(--menu-light-background));
--menu-submenu-active-color: hsl(var(--primary));
--menu-submenu-active-background-color: transparent;
--menu-submenu-background-color: var(--menu-background-color);
}
&.is-rounded {
--menu-item-margin-x: 8px;
--menu-item-collapse-margin-x: 6px;
--menu-item-radius: 6px;
}
&.is-horizontal:not(.is-rounded) {
--menu-item-height: 60px;
--menu-item-radius: 0px;
}
&.is-horizontal.is-rounded {
--menu-item-height: 40px;
--menu-item-radius: 6px;
--menu-item-padding-x: 12px;
}
// .vben-menu__popup,
&.is-horizontal {
--menu-item-padding-y: 0px;
--menu-item-padding-x: 10px;
--menu-item-margin-y: 0px;
--menu-item-margin-x: 1px;
--menu-background-color: transparent;
&.is-dark {
--menu-item-hover-color: var(--foreground);
--menu-item-hover-background-color: hsl(var(--menu-dark-background));
--menu-item-active-color: hsl(var(--foreground));
--menu-item-active-background-color: hsl(var(--menu-dark-background));
--menu-submenu-active-color: hsl(var(--foreground));
--menu-submenu-active-background-color: hsl(var(--menu-dark-background));
--menu-submenu-hover-color: hsl(var(--foreground));
--menu-submenu-hover-background-color: hsl(var(--menu-dark-background));
}
&.is-light {
--menu-item-active-color: hsl(var(--foreground));
--menu-item-active-background-color: hsl(var(--menu-light-background));
--menu-item-hover-background-color: hsl(var(--menu-light-background));
--menu-item-hover-color: hsl(var(--primary));
--menu-submenu-hover-color: hsl(var(--primary));
--menu-submenu-hover-background-color: hsl(var(--menu-light-background));
--menu-submenu-active-color: hsl(var(--foreground));
--menu-submenu-active-background-color: hsl(var(--menu-light-background));
}
}
}
@mixin menu-item-active {
color: var(--menu-item-active-color);
text-decoration: none;
cursor: pointer;
background: var(--menu-item-active-background-color);
}
@mixin menu-item {
position: relative;
display: flex;
// gap: 12px;
align-items: center;
height: var(--menu-item-height);
padding: var(--menu-item-padding-y) var(--menu-item-padding-x);
margin: 0 var(--menu-item-margin-x) var(--menu-item-margin-y)
var(--menu-item-margin-x);
font-size: var(--menu-font-size);
color: var(--menu-item-color);
text-decoration: none;
white-space: nowrap;
list-style: none;
cursor: pointer;
background: var(--menu-item-background-color);
border: none;
border-radius: var(--menu-item-radius);
transition:
background 0.15s ease,
color 0.15s ease,
padding 0.15s ease,
border-color 0.15s ease;
&.is-disabled {
cursor: not-allowed;
background: none !important;
opacity: 0.25;
}
.#{$namespace}-menu__icon {
transition: transform 0.25s;
}
&:hover {
.#{$namespace}-menu__icon {
transform: scale(1.3);
}
}
&:hover,
&:focus {
outline: none;
}
* {
vertical-align: bottom;
}
}
@mixin menu-title {
max-width: var(--menu-title-width);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
opacity: 1;
}
.#{$namespace}-menu {
position: relative;
box-sizing: border-box;
padding-left: 0;
margin: 0;
list-style: none;
background: hsl(var(--menu-background-color));
// 垂直菜单
&.is-vertical {
&:not(.#{$namespace}-menu.is-collapse) {
& .#{$namespace}-menu-item,
& .#{$namespace}-sub-menu-content,
& .#{$namespace}-menu-item-group__title {
padding-left: calc(
var(--menu-item-indent) + var(--menu-level) * var(--menu-item-indent)
);
white-space: nowrap;
}
& > .#{$namespace}-sub-menu {
// .#{$namespace}-menu {
// background: var(--menu-submenu-opened-background-color);
// .#{$namespace}-sub-menu,
// .#{$namespace}-menu-item:not(.is-active),
// .#{$namespace}-sub-menu-content:not(.is-active) {
// background: var(--menu-submenu-opened-background-color);
// }
// }
& > .#{$namespace}-menu {
& > .#{$namespace}-menu-item {
padding-left: calc(
0px + var(--menu-item-indent) + var(--menu-level) *
var(--menu-item-indent)
);
}
}
& > .#{$namespace}-sub-menu-content {
padding-left: calc(var(--menu-item-indent) - 8px);
}
}
& > .#{$namespace}-menu-item {
padding-left: calc(var(--menu-item-indent) - 8px);
}
}
}
&.is-horizontal {
display: flex;
flex-wrap: nowrap;
max-width: 100%;
height: var(--height-horizontal-height);
border-right: none;
.#{$namespace}-menu-item {
display: inline-flex;
align-items: center;
justify-content: center;
height: var(--menu-item-height);
padding-right: calc(var(--menu-item-padding-x) + 6px);
margin: 0;
margin-right: 2px;
// border-bottom: 2px solid transparent;
border-radius: var(--menu-item-radius);
}
& > .#{$namespace}-sub-menu {
height: var(--menu-item-height);
margin-right: 2px;
&:focus,
&:hover {
outline: none;
}
& .#{$namespace}-sub-menu-content {
height: 100%;
padding-right: 40px;
// border-bottom: 2px solid transparent;
border-radius: var(--menu-item-radius);
}
}
& .#{$namespace}-menu-item:not(.is-disabled):hover,
& .#{$namespace}-menu-item:not(.is-disabled):focus {
outline: none;
}
& > .#{$namespace}-menu-item.is-active {
color: var(--menu-item-active-color);
}
// &.is-light {
// & > .#{$namespace}-sub-menu {
// &.is-active {
// border-bottom: 2px solid var(--menu-item-active-color);
// }
// &:not(.is-active) .#{$namespace}-sub-menu-content {
// &:hover {
// border-bottom: 2px solid var(--menu-item-active-color);
// }
// }
// }
// & > .#{$namespace}-menu-item.is-active {
// border-bottom: 2px solid var(--menu-item-active-color);
// }
// & .#{$namespace}-menu-item:not(.is-disabled):hover,
// & .#{$namespace}-menu-item:not(.is-disabled):focus {
// border-bottom: 2px solid var(--menu-item-active-color);
// }
// }
}
// 折叠菜单
&.is-collapse {
.#{$namespace}-menu__icon {
margin-right: 0;
}
.#{$namespace}-sub-menu__icon-arrow {
display: none;
}
.#{$namespace}-sub-menu-content,
.#{$namespace}-menu-item {
display: flex;
align-items: center;
justify-content: center;
padding: var(--menu-item-collapse-padding-y)
var(--menu-item-collapse-padding-x);
margin: var(--menu-item-collapse-margin-y)
var(--menu-item-collapse-margin-x);
transition: all 0.3s;
&.is-active {
background: var(--menu-item-active-background-color) !important;
border-radius: var(--menu-item-radius);
}
}
&.is-light {
.#{$namespace}-sub-menu-content,
.#{$namespace}-menu-item {
&.is-active {
color: hsl(var(--primary-foreground)) !important;
background: var(--menu-item-active-background-color) !important;
}
}
}
&.is-rounded {
.#{$namespace}-sub-menu-content,
.#{$namespace}-menu-item {
&.is-collapse-show-title {
// padding: 32px 0 !important;
margin: 4px 8px !important;
}
}
}
}
&__popup-container {
max-width: 240px;
height: unset;
padding: 0;
background: var(--menu-background-color);
}
&__popup {
padding: 4px 0;
border-radius: var(--menu-item-radius);
.#{$namespace}-sub-menu-content,
.#{$namespace}-menu-item {
padding: var(--menu-item-popup-padding-y) var(--menu-item-popup-padding-x);
}
}
&__icon {
flex-shrink: 0;
// width: var(--menu-item-icon-width);
max-height: var(--menu-item-icon-width);
margin-right: 12px;
font-size: 20px;
text-align: center;
vertical-align: middle;
}
}
.#{$namespace}-menu-item {
fill: var(--menu-item-color);
stroke: var(--menu-item-color);
@include menu-item;
&.is-active {
fill: var(--menu-item-active-color);
stroke: var(--menu-item-active-color);
@include menu-item-active;
}
&__content {
display: inline-flex;
align-items: center;
width: 100%;
height: var(--menu-item-height);
}
&.is-collapse-show-title {
padding: 32px 0 !important;
// margin: 4px 8px !important;
.#{$namespace}-menu-tooltip__trigger {
flex-direction: column;
}
.#{$namespace}-menu__icon {
display: block;
font-size: 20px !important;
transition: all 0.25s ease;
}
.#{$namespace}-menu__name {
display: inline-flex;
margin-top: 8px;
margin-bottom: 0;
font-size: 12px;
font-weight: 400;
line-height: normal;
transition: all 0.25s ease;
}
}
&:not(.is-active):hover {
color: var(--menu-item-hover-color);
text-decoration: none;
cursor: pointer;
background: var(--menu-item-hover-background-color) !important;
}
.#{$namespace}-menu-tooltip__trigger {
position: absolute;
top: 0;
left: 0;
box-sizing: border-box;
display: inline-flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
padding: 0 var(--menu-item-padding-x);
font-size: var(--menu-font-size);
line-height: var(--menu-item-height);
}
}
.#{$namespace}-sub-menu {
padding-left: 0;
margin: 0;
list-style: none;
background: var(--menu-submenu-background-color);
fill: var(--menu-item-color);
stroke: var(--menu-item-color);
&.is-active {
div[data-state='open'] > .#{$namespace}-sub-menu-content,
> .#{$namespace}-sub-menu-content {
color: var(--menu-submenu-active-color);
text-decoration: none;
cursor: pointer;
background: var(--menu-submenu-active-background-color);
fill: var(--menu-submenu-active-color);
stroke: var(--menu-submenu-active-color);
}
}
}
.#{$namespace}-sub-menu-content {
height: var(--menu-item-height);
@include menu-item;
&__icon-arrow {
position: absolute;
top: 50%;
right: 6px;
width: inherit;
margin-top: -8px;
margin-right: 0;
font-size: 16px;
font-weight: normal;
opacity: 1;
transition: transform 0.25s ease;
}
&__title {
@include menu-title;
}
&.is-collapse-show-title {
flex-direction: column;
padding: 32px 0 !important;
// margin: 4px 8px !important;
.#{$namespace}-menu__icon {
display: block;
font-size: 20px !important;
transition: all 0.25s ease;
}
.#{$namespace}-sub-menu-content__title {
display: inline-flex;
flex-shrink: 0;
margin-top: 8px;
margin-bottom: 0;
font-size: 12px;
font-weight: 400;
line-height: normal;
transition: all 0.25s ease;
}
}
&.is-more {
padding-right: 12px !important;
}
// &:not(.is-active):hover {
&:hover {
color: var(--menu-submenu-hover-color);
text-decoration: none;
cursor: pointer;
background: var(--menu-submenu-hover-background-color) !important;
svg {
fill: var(--menu-submenu-hover-color);
}
}
}

View File

@@ -66,10 +66,9 @@ const logoClass = computed(() => {
/>
<span
v-if="!collapse"
class="text-primary truncate text-nowrap group-[.dark]:text-[hsl(var(--dark-foreground))]"
class="text-primary group-[.dark]:text-foreground-dark truncate text-nowrap"
>
{{ text }}
<!-- <span class="text-primary ml-1 align-super text-[smaller]">Pro</span> -->
</span>
</a>
</div>

View File

@@ -43,7 +43,7 @@ const badgeStyle = computed(() => {
});
</script>
<template>
<span v-if="isDot || badge" :class="$attrs.class" class="absolute right-5">
<span v-if="isDot || badge" :class="$attrs.class" class="absolute right-6">
<BadgeDot v-if="isDot" :dot-class="badgeClass" :dot-style="badgeStyle" />
<div
v-else

View File

@@ -6,7 +6,7 @@ import type { TabConfig, TabsProps } from '../../types';
import { computed, nextTick, onMounted, ref, watch } from 'vue';
import { IcRoundClose, MdiPin } from '@vben-core/icons';
import { VbenContextMenu, VbenIcon } from '@vben-core/shadcn-ui';
import { VbenContextMenu, VbenIcon, VbenScrollbar } from '@vben-core/shadcn-ui';
interface Props extends TabsProps {}
@@ -21,7 +21,7 @@ const props = withDefaults(defineProps<Props>(), {
contextMenus: () => [],
gap: 7,
maxWidth: 150,
minWidth: 40,
minWidth: 80,
tabs: () => [],
});
@@ -40,20 +40,20 @@ const style = computed(() => {
});
const layout = () => {
const { gap, maxWidth, minWidth, tabs } = props;
const { maxWidth, minWidth } = props;
if (!contentRef.value) {
return Math.max(maxWidth, minWidth);
}
const contentWidth = contentRef.value.clientWidth - gap * 3;
let width = contentWidth / tabs.length;
width += gap * 2;
if (width > maxWidth) {
width = maxWidth;
}
if (width < minWidth) {
width = minWidth;
}
tabWidth.value = width;
// const contentWidth = contentRef.value.clientWidth - gap * 3;
// let width = contentWidth / tabs.length;
// width += gap * 2;
// if (width > maxWidth) {
// width = maxWidth;
// }
// if (width < minWidth) {
// width = minWidth;
// }
tabWidth.value = maxWidth;
};
const tabsView = computed((): TabConfig[] => {
@@ -95,121 +95,115 @@ function handleUnpinTab(tab: TabConfig) {
<template>
<div :style="style" class="tabs-chrome size-full flex-1 overflow-hidden pt-1">
<!-- footer -> 4px -->
<div
ref="contentRef"
:class="contentClass"
class="relative h-full overflow-hidden"
>
<TransitionGroup name="slide-down">
<div
v-for="(tab, i) in tabsView"
:key="tab.key"
ref="tabRef"
:class="[
{ 'is-active': tab.key === active, dragable: !tab.affixTab },
]"
:data-index="i"
:style="{
width: `${tabWidth}px`,
left: `${(tabWidth - gap * 2) * i}px`,
}"
class="tabs-chrome__item group absolute flex h-full select-none items-center transition-all"
@click="active = tab.key"
>
<VbenContextMenu
:handler-data="tab"
:menus="contextMenus"
:modal="false"
item-class="pr-6"
<VbenScrollbar class="h-full" horizontal>
<!-- footer -> 4px -->
<div
ref="contentRef"
:class="contentClass"
class="relative !flex h-full w-max"
>
<TransitionGroup name="slide-down">
<div
v-for="(tab, i) in tabsView"
:key="tab.key"
ref="tabRef"
:class="[
{ 'is-active': tab.key === active, dragable: !tab.affixTab },
]"
:data-index="i"
:style="{
width: `${tabWidth}px`,
left: `${(tabWidth - gap * 2) * i}px`,
}"
class="tabs-chrome__item group absolute flex h-full select-none items-center transition-all"
@click="active = tab.key"
>
<div class="size-full">
<!-- divider -->
<div
v-if="i !== 0"
class="tabs-chrome__divider bg-foreground/80 absolute left-[var(--gap)] top-1/2 z-0 h-4 w-[1px] translate-y-[-50%] transition-all"
></div>
<!-- background -->
<div
class="tabs-chrome__background absolute z-[1] size-full px-[calc(var(--gap)-1px)] py-0 transition-opacity duration-150"
>
<VbenContextMenu
:handler-data="tab"
:menus="contextMenus"
:modal="false"
item-class="pr-6"
>
<div class="size-full">
<!-- divider -->
<div
class="tabs-chrome__background-content h-full rounded-tl-[var(--gap)] rounded-tr-[var(--gap)] duration-150"
v-if="i !== 0 && tab.key !== active"
class="tabs-chrome__divider bg-foreground/80 absolute left-[var(--gap)] top-1/2 z-0 h-4 w-[1px] translate-y-[-50%] transition-all"
></div>
<svg
class="tabs-chrome__background-before absolute bottom-[-1px] left-[-1px] fill-transparent transition-all duration-150"
height="7"
width="7"
<!-- background -->
<div
class="tabs-chrome__background absolute z-[1] size-full px-[calc(var(--gap)-1px)] py-0 transition-opacity duration-150"
>
<path d="M 0 7 A 7 7 0 0 0 7 0 L 7 7 Z" />
</svg>
<svg
class="tabs-chrome__background-after absolute bottom-[-1px] right-[-1px] fill-transparent transition-all duration-150"
height="7"
width="7"
>
<path d="M 0 0 A 7 7 0 0 0 7 7 L 0 7 Z" />
</svg>
</div>
<div
class="tabs-chrome__background-content h-full rounded-tl-[var(--gap)] rounded-tr-[var(--gap)] duration-150"
></div>
<svg
class="tabs-chrome__background-before absolute bottom-[-1px] left-[-1px] fill-transparent transition-all duration-150"
height="7"
width="7"
>
<path d="M 0 7 A 7 7 0 0 0 7 0 L 7 7 Z" />
</svg>
<svg
class="tabs-chrome__background-after absolute bottom-[-1px] right-[-1px] fill-transparent transition-all duration-150"
height="7"
width="7"
>
<path d="M 0 0 A 7 7 0 0 0 7 7 L 0 7 Z" />
</svg>
</div>
<!-- extra -->
<div
class="tabs-chrome__extra absolute right-[calc(var(--gap)*2)] top-1/2 z-[3] size-4 translate-y-[-50%]"
>
<!-- <div
<!-- extra -->
<div
class="tabs-chrome__extra absolute right-[calc(var(--gap)*1.5)] top-1/2 z-[3] size-4 translate-y-[-50%]"
>
<!-- <div
class="tabs-chrome__extra absolute right-[calc(var(--gap)*2)] top-1/2 z-[3] size-4 translate-y-[-50%] opacity-0 transition-opacity group-hover:opacity-100"
> -->
<!-- close-icon -->
<IcRoundClose
v-show="!tab.affixTab && tabsView.length > 1 && tab.closable"
class="hover:bg-accent stroke-accent-foreground/80 hover:stroke-accent-foreground group-[.is-active]:text-primary mt-[2px] size-3 cursor-pointer rounded-full transition-all"
@click.stop="handleClose(tab.key)"
/>
<MdiPin
v-show="tab.affixTab && tabsView.length > 1 && tab.closable"
class="hover:bg-accent hover:stroke-accent-foreground group-[.is-active]:text-primary mt-[2px] size-3.5 cursor-pointer rounded-full transition-all"
@click.stop="handleUnpinTab(tab)"
/>
</div>
<!-- close-icon -->
<IcRoundClose
v-show="
!tab.affixTab && tabsView.length > 1 && tab.closable
"
class="hover:bg-accent stroke-accent-foreground/80 hover:stroke-accent-foreground group-[.is-active]:text-primary mt-[2px] size-3 cursor-pointer rounded-full transition-all"
@click.stop="handleClose(tab.key)"
/>
<MdiPin
v-show="tab.affixTab && tabsView.length > 1 && tab.closable"
class="hover:bg-accent hover:stroke-accent-foreground group-[.is-active]:text-primary mt-[2px] size-3.5 cursor-pointer rounded-full transition-all"
@click.stop="handleUnpinTab(tab)"
/>
</div>
<!-- tab-item-main -->
<div
class="tabs-chrome__item-main group-[.is-active]:text-primary text-accent-foreground absolute left-0 right-0 z-[2] mx-[calc(var(--gap)*2)] my-0 flex h-full items-center overflow-hidden rounded-tl-[5px] rounded-tr-[5px] pr-4 duration-150 group-hover:pr-3 group-[.is-active]:font-semibold"
>
<VbenIcon
v-if="showIcon"
:icon="tab.icon"
class="ml-[var(--gap)] flex size-4 items-center overflow-hidden"
fallback
/>
<span
class="tabs-chrome__label ml-[var(--gap)] flex-1 overflow-hidden whitespace-nowrap"
<!-- tab-item-main -->
<div
class="tabs-chrome__item-main group-[.is-active]:text-primary text-accent-foreground absolute left-0 right-0 z-[2] mx-[calc(var(--gap)*2)] my-0 flex h-full items-center overflow-hidden rounded-tl-[5px] rounded-tr-[5px] pr-4 duration-150 group-hover:pr-3"
>
{{ tab.title }}
</span>
<VbenIcon
v-if="showIcon"
:icon="tab.icon"
class="ml-[var(--gap)] flex size-4 items-center overflow-hidden"
fallback
/>
<span
class="tabs-chrome__label ml-[var(--gap)] flex-1 overflow-hidden whitespace-nowrap"
>
{{ tab.title }}
</span>
</div>
</div>
</div>
</VbenContextMenu>
</div>
</TransitionGroup>
</div>
<!-- footer -->
<div class="bg-background h-1"></div>
</VbenContextMenu>
</div>
</TransitionGroup>
</div>
<!-- footer -->
<div class="bg-background h-1"></div>
</VbenScrollbar>
</div>
</template>
<style scoped>
/* html.dark {
.tabs-chrome {
.is-active {
.tabs-chrome__item-main {
@apply text-accent-foreground;
}
}
}
} */
.tabs-chrome {
.dragging {
.tabs-chrome__item-main {
@@ -222,7 +216,7 @@ function handleUnpinTab(tab: TabConfig) {
}
&__item {
&:hover {
&:hover:not(.is-active) {
& + .tabs-chrome__item {
.tabs-chrome__divider {
@apply opacity-0;
@@ -235,12 +229,12 @@ function handleUnpinTab(tab: TabConfig) {
.tabs-chrome__background {
&-content {
@apply bg-heavy;
@apply bg-primary/10 mx-1 rounded-md pb-2;
}
&-before,
&-after {
@apply fill-heavy;
@apply fill-primary/0;
}
}
}
@@ -248,16 +242,22 @@ function handleUnpinTab(tab: TabConfig) {
&.is-active {
@apply z-[2];
& + .tabs-chrome__item {
.tabs-chrome__divider {
@apply opacity-0 !important;
}
}
.tabs-chrome__background {
@apply opacity-100;
&-content {
@apply bg-background-content;
@apply bg-primary/15;
}
&-before,
&-after {
@apply fill-background-content;
@apply fill-primary/15;
}
}
}

View File

@@ -26,14 +26,14 @@ const active = defineModel<string>('active');
const typeWithClass = computed(() => {
const typeClasses: Record<string, { content: string }> = {
brisk: {
content: `h-full after:content-[''] after:absolute after:bottom-0 after:left-0 after:w-full after:h-[1.5px] after:bg-primary after:scale-x-0 after:transition-[transform] after:ease-out after:duration-300 hover:after:scale-x-100 after:origin-left [&.is-active]:after:scale-x-100`,
content: `h-full after:content-[''] after:absolute after:bottom-0 after:left-0 after:w-full after:h-[1.5px] after:bg-primary after:scale-x-0 after:transition-[transform] after:ease-out after:duration-300 hover:after:scale-x-100 after:origin-left [&.is-active]:after:scale-x-100 border-l border-border`,
},
card: {
content:
'h-[calc(100%-6px)] rounded-md mr-2 border-border [&.is-active]:border-primary border transition-all',
'h-[calc(100%-6px)] rounded-md ml-2 border border-border transition-all',
},
plain: {
content: 'h-full',
content: 'h-full border-l border-border',
},
};
@@ -77,7 +77,7 @@ function handleUnpinTab(tab: TabConfig) {
:key="tab.key"
:class="[
{
'tabs-item is-active bg-background-content': tab.key === active,
'is-active bg-primary/15': tab.key === active,
dragable: !tab.affixTab,
},
typeWithClass.content,
@@ -110,14 +110,14 @@ function handleUnpinTab(tab: TabConfig) {
/>
<MdiPin
v-show="tab.affixTab && tabsView.length > 1 && tab.closable"
class="hover:bg-accent hover:stroke-accent-foreground group-[.is-active]:text-primary mt-[2px] size-3.5 cursor-pointer rounded-full transition-all"
class="hover:bg-heavy hover:stroke-accent-foreground group-[.is-active]:text-primary mt-[2px] size-3.5 cursor-pointer rounded-full transition-all"
@click.stop="handleUnpinTab(tab)"
/>
</div>
<!-- tab-item-main -->
<div
class="tabs-item__main group-[.is-active]:text-primary text-accent-foreground mx-3 mr-4 flex h-full items-center overflow-hidden rounded-tl-[5px] rounded-tr-[5px] pr-3 transition-all duration-300"
class="group-[.is-active]:text-primary text-accent-foreground mx-3 mr-4 flex h-full items-center overflow-hidden rounded-tl-[5px] rounded-tr-[5px] pr-3 transition-all duration-300"
>
<!-- <div
class="mx-3 ml-3 mr-2 flex h-full items-center overflow-hidden rounded-tl-[5px] rounded-tr-[5px] transition-all duration-300 group-hover:mr-2 group-hover:pr-4 group-[.is-active]:pr-4"
@@ -141,15 +141,3 @@ function handleUnpinTab(tab: TabConfig) {
</VbenScrollbar>
</div>
</template>
<style scoped>
/* html.dark {
.tabs-item {
&.is-active {
.tabs-item__main {
@apply text-accent-foreground;
}
}
}
} */
</style>