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

@ -12,7 +12,7 @@ const routes: RouteRecordRaw[] = [
badgeType: 'dot',
icon: VBEN_LOGO_URL,
order: 9999,
title: 'Vben Admin',
title: 'Vben',
},
name: 'AboutLayout',
path: '/vben-admin',

View File

@ -27,6 +27,8 @@ packages.forEach((pkg) => {
const shadcnUiColors = {
accent: {
DEFAULT: 'hsl(var(--accent))',
dark: 'hsl(var(--accent-dark))',
'dark-hover': 'hsl(var(--accent-dark-hover))',
foreground: 'hsl(var(--accent-foreground))',
hover: 'hsl(var(--accent-hover))',
},
@ -34,17 +36,23 @@ const shadcnUiColors = {
DEFAULT: 'hsl(var(--background))',
content: 'hsl(var(--background-content))',
},
border: 'hsl(var(--border))',
border: {
DEFAULT: 'hsl(var(--border))',
dark: 'hsl(var(--border-dark))',
},
card: {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))',
},
destructive: {
...createColorsPattern('destructive'),
...createColorsPalette('destructive'),
DEFAULT: 'hsl(var(--destructive))',
},
foreground: 'hsl(var(--foreground))',
foreground: {
DEFAULT: 'hsl(var(--foreground))',
dark: 'hsl(var(--foreground-dark))',
},
input: {
DEFAULT: 'hsl(var(--input))',
@ -59,7 +67,7 @@ const shadcnUiColors = {
foreground: 'hsl(var(--popover-foreground))',
},
primary: {
...createColorsPattern('primary'),
...createColorsPalette('primary'),
DEFAULT: 'hsl(var(--primary))',
},
@ -76,7 +84,7 @@ const customColors = {
DEFAULT: 'hsl(var(--authentication))',
},
green: {
...createColorsPattern('green'),
...createColorsPalette('green'),
foreground: 'hsl(var(--success-foreground))',
},
heavy: {
@ -88,19 +96,19 @@ const customColors = {
},
overlay: 'hsl(var(--overlay))',
red: {
...createColorsPattern('red'),
...createColorsPalette('red'),
foreground: 'hsl(var(--destructive-foreground))',
},
success: {
...createColorsPattern('success'),
...createColorsPalette('success'),
DEFAULT: 'hsl(var(--success))',
},
warning: {
...createColorsPattern('warning'),
...createColorsPalette('warning'),
DEFAULT: 'hsl(var(--warning))',
},
yellow: {
...createColorsPattern('yellow'),
...createColorsPalette('yellow'),
foreground: 'hsl(var(--warning-foreground))',
},
};
@ -189,7 +197,31 @@ export default {
},
} as Config;
function createColorsPattern(name: string) {
function createColorsPalette(name: string) {
// backgroundLightest: '#EFF6FF', // Tailwind CSS 默认的 `blue-50`
// backgroundLighter: '#DBEAFE', // Tailwind CSS 默认的 `blue-100`
// backgroundLight: '#BFDBFE', // Tailwind CSS 默认的 `blue-200`
// borderLight: '#93C5FD', // Tailwind CSS 默认的 `blue-300`
// border: '#60A5FA', // Tailwind CSS 默认的 `blue-400`
// main: '#3B82F6', // Tailwind CSS 默认的 `blue-500`
// hover: '#2563EB', // Tailwind CSS 默认的 `blue-600`
// active: '#1D4ED8', // Tailwind CSS 默认的 `blue-700`
// backgroundDark: '#1E40AF', // Tailwind CSS 默认的 `blue-800`
// backgroundDarker: '#1E3A8A', // Tailwind CSS 默认的 `blue-900`
// backgroundDarkest: '#172554', // Tailwind CSS 默认的 `blue-950`
// • backgroundLightest (#EFF6FF): 适用于最浅的背景色,可能用于非常轻微的阴影或卡片的背景。
// • backgroundLighter (#DBEAFE): 适用于略浅的背景色,通常用于次要背景或略浅的区域。
// • backgroundLight (#BFDBFE): 适用于浅色背景,可能用于输入框或表单区域的背景。
// • borderLight (#93C5FD): 适用于浅色边框,可能用于输入框或卡片的边框。
// • border (#60A5FA): 适用于普通边框,可能用于按钮或卡片的边框。
// • main (#3B82F6): 适用于主要的主题色,通常用于按钮、链接或主要的强调色。
// • hover (#2563EB): 适用于鼠标悬停状态下的颜色,例如按钮悬停时的背景色或边框色。
// • active (#1D4ED8): 适用于激活状态下的颜色,例如按钮按下时的背景色或边框色。
// • backgroundDark (#1E40AF): 适用于深色背景,可能用于主要按钮或深色卡片背景。
// • backgroundDarker (#1E3A8A): 适用于更深的背景,通常用于头部导航栏或页脚。
// • backgroundDarkest (#172554): 适用于最深的背景,可能用于非常深色的区域或极端对比色。
return {
50: `hsl(var(--${name}-50))`,
100: `hsl(var(--${name}-100))`,
@ -199,18 +231,29 @@ function createColorsPattern(name: string) {
500: `hsl(var(--${name}-500))`,
600: `hsl(var(--${name}-600))`,
700: `hsl(var(--${name}-700))`,
800: `hsl(var(--${name}-800))`,
900: `hsl(var(--${name}-900))`,
950: `hsl(var(--${name}-950))`,
active: `hsl(var(--${name}-600))`,
background: `hsl(var(--${name}-50))`,
'background-hover': `hsl(var(--${name}-100))`,
border: `hsl(var(--${name}-200))`,
'border-hover': `hsl(var(--${name}-300))`,
// 800: `hsl(var(--${name}-800))`,
// 900: `hsl(var(--${name}-900))`,
// 950: `hsl(var(--${name}-950))`,
// 激活状态下的颜色,适用于按钮按下时的背景色或边框色。
active: `hsl(var(--${name}-700))`,
// 浅色背景,适用于输入框或表单区域的背景。
'background-light': `hsl(var(--${name}-200))`,
// 适用于略浅的背景色,通常用于次要背景或略浅的区域。
'background-lighter': `hsl(var(--${name}-100))`,
// 最浅的背景色,适用于非常轻微的阴影或卡片的背景。
'background-lightest': `hsl(var(--${name}-50))`,
// 适用于普通边框,可能用于按钮或卡片的边框。
border: `hsl(var(--${name}-400))`,
// 浅色边框,适用于输入框或卡片的边框。
'border-light': `hsl(var(--${name}-300))`,
foreground: `hsl(var(--${name}-foreground))`,
hover: `hsl(var(--${name}-400))`,
text: `hsl(var(--${name}-800))`,
'text-active': `hsl(var(--${name}-900))`,
'text-hover': `hsl(var(--${name}-700))`,
// 鼠标悬停状态下的颜色,适用于按钮悬停时的背景色或边框色。
hover: `hsl(var(--${name}-600))`,
// 主色文本
text: `hsl(var(--${name}-500))`,
// 主色文本激活态
'text-active': `hsl(var(--${name}-700))`,
// 主色文本悬浮态
'text-hover': `hsl(var(--${name}-600))`,
};
}

View File

@ -174,18 +174,18 @@ class PreferenceManager {
if (colorPrimary) {
document.documentElement.style.setProperty(
'--primary',
colorVariables['--primary-600'],
colorVariables['--primary-500'],
);
}
if (colorVariables['--green-600']) {
colorVariables['--success'] = colorVariables['--green-600'];
if (colorVariables['--green-500']) {
colorVariables['--success'] = colorVariables['--green-500'];
}
if (colorVariables['--yellow-600']) {
colorVariables['--warning'] = colorVariables['--yellow-600'];
if (colorVariables['--yellow-500']) {
colorVariables['--warning'] = colorVariables['--yellow-500'];
}
if (colorVariables['--red-600']) {
colorVariables['--destructive'] = colorVariables['--red-600'];
if (colorVariables['--red-500']) {
colorVariables['--destructive'] = colorVariables['--red-500'];
}
updateCSSVariables(colorVariables);
}

View File

@ -36,8 +36,12 @@
}
},
"dependencies": {
"@vben-core/constants": "workspace:*",
"@vben-core/toolkit": "workspace:*",
"@vueuse/core": "^10.11.0",
"radix-vue": "^1.9.1",
"sortablejs": "^1.15.2"
"sortablejs": "^1.15.2",
"vue": "^3.4.31"
},
"devDependencies": {
"@types/sortablejs": "^1.15.8"

View File

@ -1,3 +1,4 @@
export * from './use-content-height';
export * from './use-namespace';
export * from './use-sortable';
export {

View File

@ -0,0 +1,45 @@
import { computed, onMounted, ref, watch } from 'vue';
import { CSS_VARIABLE_LAYOUT_CONTENT_HEIGHT } from '@vben-core/constants';
import { getElementVisibleHeight } from '@vben-core/toolkit';
import { useCssVar, useDebounceFn, useWindowSize } from '@vueuse/core';
/**
* @zh_CN ()
*/
function useContentHeight() {
const contentHeight = useCssVar(CSS_VARIABLE_LAYOUT_CONTENT_HEIGHT);
const contentStyles = computed(() => {
return {
height: `var(${CSS_VARIABLE_LAYOUT_CONTENT_HEIGHT})`,
};
});
return { contentHeight, contentStyles };
}
/**
* @zh_CN
*/
function useContentHeightListener() {
const contentElement = ref<HTMLDivElement | null>(null);
const { height, width } = useWindowSize();
const contentHeight = useCssVar(CSS_VARIABLE_LAYOUT_CONTENT_HEIGHT);
const debouncedCalcHeight = useDebounceFn(() => {
contentHeight.value = `${getElementVisibleHeight(contentElement.value)}px`;
}, 200);
watch([height, width], () => {
debouncedCalcHeight();
});
onMounted(() => {
debouncedCalcHeight();
});
return { contentElement };
}
export { useContentHeight, useContentHeightListener };

View File

@ -0,0 +1,15 @@
// --vben-content-client-height
/**
* @zh_CN CSS
* @en_US CSS variable prefix
*/
const CSS_VARIABLE_PREFIX = '--vben';
/**
* @zh_CN css变量
* @en_US Layout content height
*/
const CSS_VARIABLE_LAYOUT_CONTENT_HEIGHT = `${CSS_VARIABLE_PREFIX}-content-height`;
export { CSS_VARIABLE_LAYOUT_CONTENT_HEIGHT, CSS_VARIABLE_PREFIX };

View File

@ -1,22 +1,2 @@
/**
* @zh_CN GITHUB
*/
const VBEN_GITHUB_URL = 'https://github.com/vbenjs/vue-vben-admin';
/**
* @zh_CN
*/
const VBEN_DOC_URL = 'https://doc.vben.pro';
/**
* @zh_CN Vben Logo
*/
const VBEN_LOGO_URL =
'https://cdn.jsdelivr.net/npm/@vbenjs/static-source@0.1.3/source/logo-v1.webp';
/**
* @zh_CN Vben Admin
*/
const VBEN_PREVIEW_URL = 'https://vben.pro';
export { VBEN_DOC_URL, VBEN_GITHUB_URL, VBEN_LOGO_URL, VBEN_PREVIEW_URL };
export * from './global';
export * from './vben';

View File

@ -0,0 +1,22 @@
/**
* @zh_CN GITHUB
*/
const VBEN_GITHUB_URL = 'https://github.com/vbenjs/vue-vben-admin';
/**
* @zh_CN
*/
const VBEN_DOC_URL = 'https://doc.vben.pro';
/**
* @zh_CN Vben Logo
*/
const VBEN_LOGO_URL =
'https://cdn.jsdelivr.net/npm/@vbenjs/static-source@0.1.3/source/logo-v1.webp';
/**
* @zh_CN Vben Admin
*/
const VBEN_PREVIEW_URL = 'https://vben.pro';
export { VBEN_DOC_URL, VBEN_GITHUB_URL, VBEN_LOGO_URL, VBEN_PREVIEW_URL };

View File

@ -56,7 +56,7 @@
--heavy-foreground: var(--accent-foreground);
/* Default border color */
--border: 215 27.9% 16.9%;
--border: 240 3.7% 15.9%;
/* Border color for inputs such as <Input />, <Select />, <Textarea /> */
--input: 0deg 0% 100% / 10%;
@ -90,6 +90,7 @@
:root.dark[data-theme='violet'] {
--background: 224 71.4% 4.1%;
--background-content: var(--background);
--foreground: 210 20% 98%;
--card: 224 71.4% 4.1%;
--card-foreground: 210 20% 98%;
@ -111,6 +112,7 @@
:root.dark[data-theme='pink'] {
--background: 20 14.3% 4.1%;
--background-content: var(--background);
--foreground: 0 0% 95%;
--card: 24 9.8% 10%;
--card-foreground: 0 0% 95%;
@ -132,6 +134,7 @@
:root.dark[data-theme='rose'] {
--background: 0 0% 3.9%;
--background-content: var(--background);
--foreground: 0 0% 98%;
--card: 0 0% 3.9%;
--card-foreground: 0 0% 98%;
@ -153,6 +156,7 @@
:root.dark[data-theme='sky-blue'] {
--background: 222.2 84% 4.9%;
--background-content: var(--background);
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
@ -174,6 +178,7 @@
:root.dark[data-theme='deep-blue'] {
--background: 222.2 84% 4.9%;
--background-content: var(--background);
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
@ -195,6 +200,7 @@
:root.dark[data-theme='green'] {
--background: 20 14.3% 4.1%;
--background-content: var(--background);
--foreground: 0 0% 95%;
--card: 24 9.8% 10%;
--card-foreground: 0 0% 95%;
@ -216,6 +222,7 @@
:root.dark[data-theme='deep-green'] {
--background: 20 14.3% 4.1%;
--background-content: var(--background);
--foreground: 0 0% 95%;
--card: 24 9.8% 10%;
--card-foreground: 0 0% 95%;
@ -237,6 +244,7 @@
:root.dark[data-theme='orange'] {
--background: 20 14.3% 4.1%;
--background-content: var(--background);
--foreground: 60 9.1% 97.8%;
--card: 20 14.3% 4.1%;
--card-foreground: 60 9.1% 97.8%;
@ -258,6 +266,7 @@
:root.dark[data-theme='yellow'] {
--background: 20 14.3% 4.1%;
--background-content: var(--background);
--foreground: 60 9.1% 97.8%;
--card: 20 14.3% 4.1%;
--card-foreground: 60 9.1% 97.8%;
@ -279,6 +288,7 @@
:root.dark[data-theme='zinc'] {
--background: 240 10% 3.9%;
--background-content: var(--background);
--foreground: 0 0% 98%;
--card: 240 10% 3.9%;
--card-foreground: 0 0% 98%;
@ -300,6 +310,7 @@
:root.dark[data-theme='neutral'] {
--background: 0 0% 3.9%;
--background-content: var(--background);
--foreground: 0 0% 98%;
--card: 0 0% 3.9%;
--card-foreground: 0 0% 98%;
@ -321,6 +332,7 @@
:root.dark[data-theme='slate'] {
--background: 222.2 84% 4.9%;
--background-content: var(--background);
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
@ -342,6 +354,7 @@
:root.dark[data-theme='gray'] {
--background: 224 71.4% 4.1%;
--background-content: var(--background);
--foreground: 210 20% 98%;
--card: 224 71.4% 4.1%;
--card-foreground: 210 20% 98%;

View File

@ -86,20 +86,20 @@
--authentication: 231deg 61% 44%;
/* 用于浅色主题下一些暗色主题的颜色 */
--dark-foreground: 220 13% 91%;
--dark-border: 0deg 0% 100% / 10%;
--dark-accent: 0deg 0% 100% / 8%;
--dark-accent-hover: 0deg 0% 100% / 12%;
--accent-dark-hover: 0deg 0% 100% / 12%;
--foreground-dark: 220 13% 91%;
--accent-dark: 0deg 0% 100% / 8%;
--border-dark: 240 3.7% 15.9%;
/* =============component & UI============= */
/* menu */
--menu: 0deg 0% 100%;
--menu-deep: 0deg 0% 95%;
--menu-deep: 210 11.11% 96.47%;
/* menu-dark */
--menu-dark: 222.34deg 10.43% 12.27%;
--menu-dark-deep: 223deg 11% 10%;
--menu-dark-deep: 220deg 13.06% 9%;
accent-color: var(--primary);
color-scheme: light;
@ -124,6 +124,12 @@
--border: 220 13% 91%;
--input: 220 13% 91%;
--ring: 262.1 83.3% 57.8%;
/* menu-dark */
--menu-dark: 224 71.4% 4.1%;
--menu-dark-deep: 224 71.4% 4.1%;
--border-dark: 215 27.9% 16.9%;
--foreground-dark: 210 20% 98%;
}
:root[data-theme='pink'] {
@ -145,6 +151,12 @@
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--ring: 346.8 77.2% 49.8%;
/* menu-dark */
--menu-dark: 20 14.3% 4.1%;
--menu-dark-deep: 20 14.3% 4.1%;
--border-dark: 240 3.7% 15.9%;
--foreground-dark: 0 0% 95%;
}
:root[data-theme='rose'] {
@ -166,6 +178,12 @@
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--ring: 346.8 77.2% 49.8%;
/* menu-dark */
--menu-dark: 0 0% 3.9%;
--menu-dark-deep: 0 0% 3.9%;
--border-dark: 0 0% 14.9%;
--foreground-dark: 0 0% 98%;
}
:root[data-theme='sky-blue'] {
@ -187,6 +205,12 @@
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 221.2 83.2% 53.3%;
/* menu-dark */
--menu-dark: 222.2 84% 4.9%;
--menu-dark-deep: 222.2 84% 4.9%;
--border-dark: 217.2 32.6% 17.5%;
--foreground-dark: 210 40% 98%;
}
:root[data-theme='deep-blue'] {
@ -208,6 +232,12 @@
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 221.2 83.2% 53.3%;
/* menu-dark */
--menu-dark: 222.2 84% 4.9%;
--menu-dark-deep: 222.2 84% 4.9%;
--border-dark: 217.2 32.6% 17.5%;
--foreground-dark: 210 40% 98%;
}
:root[data-theme='green'] {
@ -229,6 +259,12 @@
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--ring: 142.1 76.2% 36.3%;
/* menu-dark */
--menu-dark: 20 14.3% 4.1%;
--menu-dark-deep: 20 14.3% 4.1%;
--border-dark: 240 3.7% 15.9%;
--foreground-dark: 0 0% 95%;
}
:root[data-theme='deep-green'] {
@ -250,6 +286,12 @@
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--ring: 142.1 76.2% 36.3%;
/* menu-dark */
--menu-dark: 20 14.3% 4.1%;
--menu-dark-deep: 20 14.3% 4.1%;
--border-dark: 240 3.7% 15.9%;
--foreground-dark: 0 0% 95%;
}
:root[data-theme='orange'] {
@ -271,6 +313,12 @@
--border: 20 5.9% 90%;
--input: 20 5.9% 90%;
--ring: 24.6 95% 53.1%;
/* menu-dark */
--menu-dark: 20 14.3% 4.1%;
--menu-dark-deep: 20 14.3% 4.1%;
--border-dark: 12 6.5% 15.1%;
--foreground-dark: 60 9.1% 97.8%;
}
:root[data-theme='yellow'] {
@ -292,6 +340,12 @@
--border: 20 5.9% 90%;
--input: 20 5.9% 90%;
--ring: 20 14.3% 4.1%;
/* menu-dark */
--menu-dark: 20 14.3% 4.1%;
--menu-dark-deep: 20 14.3% 4.1%;
--border-dark: 12 6.5% 15.1%;
--foreground-dark: 60 9.1% 97.8%;
}
:root[data-theme='zinc'] {
@ -313,6 +367,12 @@
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--ring: 240 5.9% 10%;
/* menu-dark */
--menu-dark: 240 10% 3.9%;
--menu-dark-deep: 240 10% 3.9%;
--border-dark: 240 3.7% 15.9%;
--foreground-dark: 0 0% 98%;
}
:root[data-theme='neutral'] {
@ -334,6 +394,12 @@
--border: 0 0% 89.8%;
--input: 0 0% 89.8%;
--ring: 0 0% 3.9%;
/* menu-dark */
--menu-dark: 0 0% 3.9%;
--menu-dark-deep: 0 0% 3.9%;
--border-dark: 0 0% 14.9%;
--foreground-dark: 0 0% 98%;
}
:root[data-theme='slate'] {
@ -355,6 +421,12 @@
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 222.2 84% 4.9%;
/* menu-dark */
--menu-dark: 222.2 84% 4.9%;
--menu-dark-deep: 222.2 84% 4.9%;
--border-dark: 217.2 32.6% 17.5%;
--foreground-dark: 210 40% 98%;
}
:root[data-theme='gray'] {
@ -376,4 +448,10 @@
--border: 220 13% 91%;
--input: 220 13% 91%;
--ring: 224 71.4% 4.1%;
/* menu-dark */
--menu-dark: 224 71.4% 4.1%;
--menu-dark-deep: 224 71.4% 4.1%;
--border-dark: 215 27.9% 16.9%;
--foreground-dark: 210 20% 98%;
}

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>

View File

@ -38,6 +38,7 @@
},
"dependencies": {
"@vben-core/helpers": "workspace:*",
"@vben-core/hooks": "workspace:*",
"@vben-core/icons": "workspace:*",
"@vben-core/layout-ui": "workspace:*",
"@vben-core/locales": "workspace:*",

View File

@ -8,14 +8,17 @@ defineOptions({
name: 'AuthenticationFormView',
});
</script>
<template>
<div
class="flex-col-center bg-background-content relative px-6 py-10 lg:flex-initial lg:px-8"
>
<!-- Toolbar Slot -->
<slot name="toolbar">
<Toolbar />
</slot>
<!-- Router View with Transition and KeepAlive -->
<RouterView v-slot="{ Component, route }">
<Transition appear mode="out-in" name="slide-right">
<KeepAlive :include="['Login']">
@ -28,6 +31,7 @@ defineOptions({
</Transition>
</RouterView>
<!-- Footer Copyright -->
<div
class="text-muted-foreground absolute bottom-3 flex text-center text-xs"
>

View File

@ -10,14 +10,17 @@ defineOptions({
name: 'AuthenticationToolbar',
});
</script>
<template>
<div
class="flex-center bg-background dark:bg-accent absolute right-2 top-4 rounded-3xl px-3 py-1"
>
<!-- Only show on medium and larger screens -->
<div class="hidden md:flex">
<AuthenticationColorToggle />
<AuthenticationLayoutToggle />
</div>
<!-- Always show Language and Theme toggles -->
<LanguageToggle />
<ThemeToggle />
</div>

View File

@ -1,6 +1,7 @@
<script lang="ts" setup>
import type { RouteLocationNormalizedLoaded } from 'vue-router';
import { useContentHeight } from '@vben-core/hooks';
import { preferences, usePreferences } from '@vben-core/preferences';
import { Spinner } from '@vben-core/shadcn-ui';
import { storeToRefs, useCoreTabbarStore } from '@vben-core/stores';
@ -13,6 +14,7 @@ defineOptions({ name: 'LayoutContent' });
const tabbarStore = useCoreTabbarStore();
const { keepAlive } = usePreferences();
const { spinning } = useContentSpinner();
const { contentStyles } = useContentHeight();
const { getCachedTabs, getExcludeCachedTabs, renderRouteView } =
storeToRefs(tabbarStore);
@ -47,7 +49,7 @@ function getTransitionName(route: RouteLocationNormalizedLoaded) {
<Spinner
v-if="preferences.transition.loading"
:spinning="spinning"
class="h-[var(--vben-content-client-height)]"
:style="contentStyles"
/>
<IFrameRouterView />
<RouterView v-slot="{ Component, route }">

View File

@ -7,9 +7,10 @@ function useContentSpinner() {
const spinning = ref(false);
const startTime = ref(0);
const router = useRouter();
const minShowTime = 500;
const minShowTime = 500; // 最小显示时间
const enableLoading = computed(() => preferences.transition.loading);
// 结束加载动画
const onEnd = () => {
if (!enableLoading.value) {
return;
@ -24,6 +25,7 @@ function useContentSpinner() {
}
};
// 路由前置守卫
router.beforeEach((to) => {
if (to.meta.loaded || !enableLoading.value || to.meta.iframeSrc) {
return true;
@ -33,14 +35,12 @@ function useContentSpinner() {
return true;
});
// 路由后置守卫
router.afterEach((to) => {
if (to.meta.loaded || !enableLoading.value || to.meta.iframeSrc) {
return true;
}
// 关闭加载动画
onEnd();
return true;
});

View File

@ -22,19 +22,23 @@ withDefaults(defineProps<Props>(), {
<template>
<div class="text-md flex-center">
<!-- ICP Link -->
<a
v-if="icp"
:href="icpLink || 'javascript:void 0'"
:href="icpLink || 'javascript:void(0)'"
class="hover:text-primary-hover"
target="_blank"
>
{{ icp }}
</a>
<!-- Copyright Text -->
Copyright © {{ date }}
<!-- Company Link -->
<a
v-if="companyName"
:href="companySiteLink || 'javascript:void 0'"
:href="companySiteLink || 'javascript:void(0)'"
class="hover:text-primary-hover mx-1"
target="_blank"
>

View File

@ -634,6 +634,31 @@ importers:
specifier: ^4.4.0
version: 4.4.0(vue@3.4.31(typescript@5.5.3))
packages/@core/hooks:
dependencies:
'@vben-core/constants':
specifier: workspace:*
version: link:../shared/constants
'@vben-core/toolkit':
specifier: workspace:*
version: link:../shared/toolkit
'@vueuse/core':
specifier: ^10.11.0
version: 10.11.0(vue@3.4.31(typescript@5.5.3))
radix-vue:
specifier: ^1.9.1
version: 1.9.1(vue@3.4.31(typescript@5.5.3))
sortablejs:
specifier: ^1.15.2
version: 1.15.2
vue:
specifier: ^3.4.31
version: 3.4.31(typescript@5.5.3)
devDependencies:
'@types/sortablejs':
specifier: ^1.15.8
version: 1.15.8
packages/@core/locales:
dependencies:
'@intlify/core-base':
@ -657,19 +682,6 @@ importers:
specifier: ^2.0.0
version: 2.0.0
packages/@core/shared/hooks:
dependencies:
radix-vue:
specifier: ^1.9.1
version: 1.9.1(vue@3.4.31(typescript@5.5.3))
sortablejs:
specifier: ^1.15.2
version: 1.15.2
devDependencies:
'@types/sortablejs':
specifier: ^1.15.8
version: 1.15.8
packages/@core/shared/icons:
dependencies:
'@iconify/vue':
@ -724,6 +736,9 @@ importers:
packages/@core/ui-kit/layout-ui:
dependencies:
'@vben-core/hooks':
specifier: workspace:*
version: link:../../hooks
'@vben-core/icons':
specifier: workspace:*
version: link:../../shared/icons
@ -747,7 +762,7 @@ importers:
dependencies:
'@vben-core/hooks':
specifier: workspace:*
version: link:../../shared/hooks
version: link:../../hooks
'@vben-core/icons':
specifier: workspace:*
version: link:../../shared/icons
@ -801,7 +816,7 @@ importers:
dependencies:
'@vben-core/hooks':
specifier: workspace:*
version: link:../../shared/hooks
version: link:../../hooks
'@vben-core/icons':
specifier: workspace:*
version: link:../../shared/icons
@ -861,7 +876,7 @@ importers:
dependencies:
'@vben-core/hooks':
specifier: workspace:*
version: link:../../@core/shared/hooks
version: link:../../@core/hooks
'@vben-core/icons':
specifier: workspace:*
version: link:../../@core/shared/icons
@ -899,6 +914,9 @@ importers:
'@vben-core/helpers':
specifier: workspace:*
version: link:../../@core/forward/helpers
'@vben-core/hooks':
specifier: workspace:*
version: link:../../@core/hooks
'@vben-core/icons':
specifier: workspace:*
version: link:../../@core/shared/icons
@ -943,7 +961,7 @@ importers:
dependencies:
'@vben-core/hooks':
specifier: workspace:*
version: link:../@core/shared/hooks
version: link:../@core/hooks
packages/icons:
dependencies:

View File

@ -7,6 +7,7 @@ packages:
- "packages/@core/forward/*"
- "packages/@core/helpers"
- "packages/@core/locales"
- "packages/@core/hooks"
- "packages/effects/*"
- "apps/*"
- "scripts/*"

View File

@ -60,6 +60,10 @@
"name": "@vben-core/stores",
"path": "packages/@core/forward/stores",
},
{
"name": "@vben-core/hooks",
"path": "packages/@core/hooks",
},
{
"name": "@vben-core/locales",
"path": "packages/@core/locales",
@ -72,10 +76,6 @@
"name": "@vben-core/design",
"path": "packages/@core/shared/design",
},
{
"name": "@vben-core/hooks",
"path": "packages/@core/shared/hooks",
},
{
"name": "@vben-core/icons",
"path": "packages/@core/shared/icons",