Browse Source

perf: improve overall theme color matching

vince 8 months ago
parent
commit
f95d9aa609
39 changed files with 525 additions and 843 deletions
  1. 1 1
      apps/web-antd/src/router/routes/modules/vben.ts
  2. 65 22
      internal/tailwind-config/src/index.ts
  3. 7 7
      packages/@core/forward/preferences/src/preferences.ts
  4. 0 0
      packages/@core/hooks/build.config.ts
  5. 5 1
      packages/@core/hooks/package.json
  6. 1 0
      packages/@core/hooks/src/index.ts
  7. 45 0
      packages/@core/hooks/src/use-content-height.ts
  8. 0 0
      packages/@core/hooks/src/use-namespace.ts
  9. 0 0
      packages/@core/hooks/src/use-sortable.test.ts
  10. 0 0
      packages/@core/hooks/src/use-sortable.ts
  11. 0 0
      packages/@core/hooks/tsconfig.json
  12. 15 0
      packages/@core/shared/constants/src/global.ts
  13. 2 22
      packages/@core/shared/constants/src/index.ts
  14. 22 0
      packages/@core/shared/constants/src/vben.ts
  15. 14 1
      packages/@core/shared/design/src/design-tokens/dark/index.css
  16. 84 6
      packages/@core/shared/design/src/design-tokens/default/index.css
  17. 1 0
      packages/@core/ui-kit/layout-ui/package.json
  18. 3 19
      packages/@core/ui-kit/layout-ui/src/components/layout-content.vue
  19. 36 27
      packages/@core/ui-kit/layout-ui/src/components/layout-sidebar.vue
  20. 2 2
      packages/@core/ui-kit/layout-ui/src/components/widgets/sidebar-collapse-button.vue
  21. 2 2
      packages/@core/ui-kit/layout-ui/src/components/widgets/sidebar-fixed-button.vue
  22. 1 15
      packages/@core/ui-kit/layout-ui/src/vben-layout.ts
  23. 1 19
      packages/@core/ui-kit/layout-ui/src/vben-layout.vue
  24. 14 13
      packages/@core/ui-kit/menu-ui/src/components/menu.vue
  25. 16 12
      packages/@core/ui-kit/menu-ui/src/components/normal-menu/normal-menu.vue
  26. 0 506
      packages/@core/ui-kit/menu-ui/src/styles/index.scss
  27. 1 2
      packages/@core/ui-kit/shadcn-ui/src/components/logo/logo.vue
  28. 1 1
      packages/@core/ui-kit/shadcn-ui/src/components/menu-badge/menu-badge.vue
  29. 119 119
      packages/@core/ui-kit/tabs-ui/src/components/tabs-chrome/tabs.vue
  30. 6 18
      packages/@core/ui-kit/tabs-ui/src/components/tabs/tabs.vue
  31. 1 0
      packages/effects/layouts/package.json
  32. 4 0
      packages/effects/layouts/src/authentication/form.vue
  33. 3 0
      packages/effects/layouts/src/authentication/toolbar.vue
  34. 3 1
      packages/effects/layouts/src/basic/content/content.vue
  35. 4 4
      packages/effects/layouts/src/basic/content/use-content-spinner.ts
  36. 6 2
      packages/effects/layouts/src/basic/copyright/copyright.vue
  37. 35 17
      pnpm-lock.yaml
  38. 1 0
      pnpm-workspace.yaml
  39. 4 4
      vben-admin.code-workspace

+ 1 - 1
apps/web-antd/src/router/routes/modules/vben.ts

@@ -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',

+ 65 - 22
internal/tailwind-config/src/index.ts

@@ -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))`,
   };
 }

+ 7 - 7
packages/@core/forward/preferences/src/preferences.ts

@@ -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);
   }

+ 0 - 0
packages/@core/shared/hooks/build.config.ts → packages/@core/hooks/build.config.ts


+ 5 - 1
packages/@core/shared/hooks/package.json → packages/@core/hooks/package.json

@@ -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"

+ 1 - 0
packages/@core/shared/hooks/src/index.ts → packages/@core/hooks/src/index.ts

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

+ 45 - 0
packages/@core/hooks/src/use-content-height.ts

@@ -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 };

+ 0 - 0
packages/@core/shared/hooks/src/use-namespace.ts → packages/@core/hooks/src/use-namespace.ts


+ 0 - 0
packages/@core/shared/hooks/src/use-sortable.test.ts → packages/@core/hooks/src/use-sortable.test.ts


+ 0 - 0
packages/@core/shared/hooks/src/use-sortable.ts → packages/@core/hooks/src/use-sortable.ts


+ 0 - 0
packages/@core/shared/hooks/tsconfig.json → packages/@core/hooks/tsconfig.json


+ 15 - 0
packages/@core/shared/constants/src/global.ts

@@ -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 };

+ 2 - 22
packages/@core/shared/constants/src/index.ts

@@ -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';

+ 22 - 0
packages/@core/shared/constants/src/vben.ts

@@ -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 };

+ 14 - 1
packages/@core/shared/design/src/design-tokens/dark/index.css

@@ -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%;

+ 84 - 6
packages/@core/shared/design/src/design-tokens/default/index.css

@@ -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%;
 }

+ 1 - 0
packages/@core/ui-kit/layout-ui/package.json

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

+ 3 - 19
packages/@core/ui-kit/layout-ui/src/components/layout-content.vue

@@ -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>

+ 36 - 27
packages/@core/ui-kit/layout-ui/src/components/layout-sidebar.vue

@@ -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>

+ 2 - 2
packages/@core/ui-kit/layout-ui/src/components/widgets/sidebar-collapse-button.vue

@@ -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" />

+ 2 - 2
packages/@core/ui-kit/layout-ui/src/components/widgets/sidebar-fixed-button.vue

@@ -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" />

+ 1 - 15
packages/@core/ui-kit/layout-ui/src/vben-layout.ts

@@ -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

+ 1 - 19
packages/@core/ui-kit/layout-ui/src/vben-layout.vue

@@ -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>

+ 14 - 13
packages/@core/ui-kit/menu-ui/src/components/menu.vue

@@ -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;

+ 16 - 12
packages/@core/ui-kit/menu-ui/src/components/normal-menu/normal-menu.vue

@@ -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));
     }
 

+ 0 - 506
packages/@core/ui-kit/menu-ui/src/styles/index.scss

@@ -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);
-    }
-  }
-}

+ 1 - 2
packages/@core/ui-kit/shadcn-ui/src/components/logo/logo.vue

@@ -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>

+ 1 - 1
packages/@core/ui-kit/shadcn-ui/src/components/menu-badge/menu-badge.vue

@@ -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

+ 119 - 119
packages/@core/ui-kit/tabs-ui/src/components/tabs-chrome/tabs.vue

@@ -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"
-                >
-                  <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"
+                <!-- 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 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;
         }
       }
     }

+ 6 - 18
packages/@core/ui-kit/tabs-ui/src/components/tabs/tabs.vue

@@ -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>

+ 1 - 0
packages/effects/layouts/package.json

@@ -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:*",

+ 4 - 0
packages/effects/layouts/src/authentication/form.vue

@@ -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"
     >

+ 3 - 0
packages/effects/layouts/src/authentication/toolbar.vue

@@ -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>

+ 3 - 1
packages/effects/layouts/src/basic/content/content.vue

@@ -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 }">

+ 4 - 4
packages/effects/layouts/src/basic/content/use-content-spinner.ts

@@ -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;
   });
 

+ 6 - 2
packages/effects/layouts/src/basic/copyright/copyright.vue

@@ -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"
     >

+ 35 - 17
pnpm-lock.yaml

@@ -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:

+ 1 - 0
pnpm-workspace.yaml

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

+ 4 - 4
vben-admin.code-workspace

@@ -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",