Browse Source

feat: migrate to Tailwind CSS v4 (#7614)

* chore: update deps

* feat: use jsonc/x language

* chore: update eslint 10.0

* fix: no-useless-assignment

* feat: add CLAUDE.md

* chore: ignore

* feat: claude

* fix: lint

* chore: suppot eslint v10

* fix: lint

* fix: lint

* fix: type check

* fix: unit test

* fix: Suggested fix

* fix: unit test

* chore: update stylelint v17

* chore: update all major deps

* fix:  echarts console warn

* chore: update vitest v4

* feat: add skills ignores

* chore: update deps

* chore: update deps

* fix: cspell

* chore: update deps

* chore: update tailwindcss v4

* chore: remove postcss config

* fix: no use catalog

* chore: tailwind v4 config

* fix: tailwindcss v4 sort

* feat: use eslint-plugin-better-tailwindcss

* fix: Interference between enforce-consistent-line-wrapping, jsx-curly-brace-presence and Prettier

* fix: Interference between enforce-consistent-line-wrapping, jsx-curly-brace-presence and Prettier

* fix(lint): resolve prettier and better-tailwindcss formatting conflicts

* fix(tailwind): update theme references and lint sources

* style(format): normalize apps docs and playground vue files

* style(format): normalize core ui-kit components

* style(format): normalize effects ui and layout components
pull/7622/head
xingyu 2 weeks ago
committed by GitHub
parent
commit
a4736a49f8
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 7
      .gitignore
  2. 1
      .npmrc
  3. 5
      .prettierignore
  4. 4
      .stylelintignore
  5. 10
      .vscode/settings.json
  6. 148
      CLAUDE.md
  7. 2
      apps/backend-mock/api/table/list.ts
  8. 1
      apps/web-antd/postcss.config.mjs
  9. 3
      apps/web-antd/src/store/auth.ts
  10. 4
      apps/web-antd/src/views/dashboard/analytics/index.vue
  11. 1
      apps/web-antd/tailwind.config.mjs
  12. 1
      apps/web-antdv-next/postcss.config.mjs
  13. 3
      apps/web-antdv-next/src/store/auth.ts
  14. 4
      apps/web-antdv-next/src/views/dashboard/analytics/index.vue
  15. 1
      apps/web-antdv-next/tailwind.config.mjs
  16. 1
      apps/web-ele/postcss.config.mjs
  17. 3
      apps/web-ele/src/store/auth.ts
  18. 4
      apps/web-ele/src/views/dashboard/analytics/index.vue
  19. 4
      apps/web-ele/src/views/demos/element/index.vue
  20. 1
      apps/web-ele/tailwind.config.mjs
  21. 1
      apps/web-naive/postcss.config.mjs
  22. 3
      apps/web-naive/src/store/auth.ts
  23. 4
      apps/web-naive/src/views/dashboard/analytics/index.vue
  24. 1
      apps/web-naive/tailwind.config.mjs
  25. 1
      apps/web-tdesign/postcss.config.mjs
  26. 3
      apps/web-tdesign/src/store/auth.ts
  27. 4
      apps/web-tdesign/src/views/dashboard/analytics/index.vue
  28. 6
      apps/web-tdesign/src/views/demos/tdesign/index.vue
  29. 1
      apps/web-tdesign/tailwind.config.mjs
  30. 3
      cspell.json
  31. 2
      docs/.vitepress/components/demo-preview.vue
  32. 8
      docs/.vitepress/components/preview-group.vue
  33. 8
      docs/.vitepress/config/shared.mts
  34. 2
      docs/.vitepress/theme/components/vben-contributors.vue
  35. 1
      docs/package.json
  36. 11
      docs/tailwind.config.mjs
  37. 7
      internal/lint-configs/eslint-config/package.json
  38. 3
      internal/lint-configs/eslint-config/src/configs/comments.ts
  39. 6
      internal/lint-configs/eslint-config/src/configs/ignores.ts
  40. 1
      internal/lint-configs/eslint-config/src/configs/index.ts
  41. 9
      internal/lint-configs/eslint-config/src/configs/jsonc.ts
  42. 1
      internal/lint-configs/eslint-config/src/configs/node.ts
  43. 53
      internal/lint-configs/eslint-config/src/configs/perfectionist.ts
  44. 7
      internal/lint-configs/eslint-config/src/configs/pnpm.ts
  45. 49
      internal/lint-configs/eslint-config/src/configs/tailwindcss.ts
  46. 2
      internal/lint-configs/eslint-config/src/configs/test.ts
  47. 2
      internal/lint-configs/eslint-config/src/configs/yaml.ts
  48. 6
      internal/lint-configs/eslint-config/src/custom-config.ts
  49. 2
      internal/lint-configs/eslint-config/src/index.ts
  50. 1
      internal/lint-configs/prettier-config/index.mjs
  51. 3
      internal/lint-configs/prettier-config/package.json
  52. 14
      internal/lint-configs/stylelint-config/index.mjs
  53. 10
      internal/tailwind-config/build.config.ts
  54. 67
      internal/tailwind-config/package.json
  55. 266
      internal/tailwind-config/src/index.ts
  56. 3
      internal/tailwind-config/src/module.d.ts
  57. 53
      internal/tailwind-config/src/plugins/entry.ts
  58. 15
      internal/tailwind-config/src/postcss.config.ts
  59. 6
      internal/tailwind-config/tsconfig.json
  60. 1
      internal/vite-config/package.json
  61. 4
      internal/vite-config/src/plugins/index.ts
  62. 40
      internal/vite-config/src/plugins/tailwind-reference.ts
  63. 4
      package.json
  64. 9
      packages/@core/base/design/package.json
  65. 477
      packages/@core/base/design/src/css/global.css
  66. 8
      packages/@core/base/design/src/css/nprogress.css
  67. 54
      packages/@core/base/shared/src/utils/__tests__/resources.test.ts
  68. 2
      packages/@core/preferences/src/config.ts
  69. 1
      packages/@core/ui-kit/form-ui/postcss.config.mjs
  70. 15
      packages/@core/ui-kit/form-ui/src/form-api.ts
  71. 4
      packages/@core/ui-kit/form-ui/src/form-render/form-field.vue
  72. 2
      packages/@core/ui-kit/form-ui/src/form-render/form.vue
  73. 1
      packages/@core/ui-kit/form-ui/tailwind.config.mjs
  74. 1
      packages/@core/ui-kit/layout-ui/postcss.config.mjs
  75. 4
      packages/@core/ui-kit/layout-ui/src/components/layout-sidebar.vue
  76. 2
      packages/@core/ui-kit/layout-ui/src/components/widgets/sidebar-collapse-button.vue
  77. 2
      packages/@core/ui-kit/layout-ui/src/components/widgets/sidebar-fixed-button.vue
  78. 4
      packages/@core/ui-kit/layout-ui/src/vben-layout.vue
  79. 1
      packages/@core/ui-kit/layout-ui/tailwind.config.mjs
  80. 1
      packages/@core/ui-kit/menu-ui/package.json
  81. 1
      packages/@core/ui-kit/menu-ui/postcss.config.mjs
  82. 2
      packages/@core/ui-kit/menu-ui/src/components/menu-badge-dot.vue
  83. 4
      packages/@core/ui-kit/menu-ui/src/components/normal-menu/normal-menu.vue
  84. 1
      packages/@core/ui-kit/menu-ui/tailwind.config.mjs
  85. 1
      packages/@core/ui-kit/popup-ui/postcss.config.mjs
  86. 2
      packages/@core/ui-kit/popup-ui/src/alert/alert.vue
  87. 17
      packages/@core/ui-kit/popup-ui/src/drawer/__tests__/drawer-api.test.ts
  88. 19
      packages/@core/ui-kit/popup-ui/src/drawer/drawer-api.ts
  89. 12
      packages/@core/ui-kit/popup-ui/src/drawer/drawer.vue
  90. 17
      packages/@core/ui-kit/popup-ui/src/modal/__tests__/modal-api.test.ts
  91. 18
      packages/@core/ui-kit/popup-ui/src/modal/modal-api.ts
  92. 9
      packages/@core/ui-kit/popup-ui/src/modal/modal.vue
  93. 1
      packages/@core/ui-kit/popup-ui/tailwind.config.mjs
  94. 3
      packages/@core/ui-kit/shadcn-ui/components.json
  95. 1
      packages/@core/ui-kit/shadcn-ui/package.json
  96. 1
      packages/@core/ui-kit/shadcn-ui/postcss.config.mjs
  97. 4
      packages/@core/ui-kit/shadcn-ui/src/components/avatar/avatar.vue
  98. 2
      packages/@core/ui-kit/shadcn-ui/src/components/back-top/back-top.vue
  99. 12
      packages/@core/ui-kit/shadcn-ui/src/components/breadcrumb/breadcrumb-background.vue
  100. 2
      packages/@core/ui-kit/shadcn-ui/src/components/button/button.vue

7
.gitignore

@ -50,3 +50,10 @@ vite.config.ts.*
*.sw?
.history
.cursor
# AI
.agent
.agents
.claude
.codex
skills-lock.json

1
.npmrc

@ -2,7 +2,6 @@ registry=https://registry.npmmirror.com
public-hoist-pattern[]=lefthook
public-hoist-pattern[]=eslint
public-hoist-pattern[]=prettier
public-hoist-pattern[]=prettier-plugin-tailwindcss
public-hoist-pattern[]=stylelint
public-hoist-pattern[]=*postcss*
public-hoist-pattern[]=@commitlint/*

5
.prettierignore

@ -1,6 +1,10 @@
dist
dev-dist
.local
.claude
.agent
.agents
.codex
.output.js
node_modules
.nvmrc
@ -16,3 +20,4 @@ CODEOWNERS
public
.npmrc
*-lock.yaml
skills-lock.json

4
.stylelintignore

@ -2,3 +2,7 @@ dist
public
__tests__
coverage
.codex
.claude
.agent
.agents

10
.vscode/settings.json

@ -1,5 +1,6 @@
{
"tailwindCSS.experimental.configFile": "internal/tailwind-config/src/index.ts",
"tailwindCSS.experimental.configFile": "packages/@core/base/design/src/css/global.css",
"tailwindCSS.lint.suggestCanonicalClasses": "ignore",
// workbench
"workbench.list.smoothScrolling": true,
"workbench.startupEditor": "newUntitledFile",
@ -31,6 +32,9 @@
"editor.autoClosingOvertype": "always",
"editor.autoClosingQuotes": "beforeWhitespace",
"editor.wordSeparators": "`~!@#%^&*()=+[{]}\\|;:'\",.<>/?",
"editor.quickSuggestions": {
"strings": "on"
},
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.fixAll.stylelint": "explicit",
@ -79,6 +83,7 @@
"files.insertFinalNewline": true,
"files.simpleDialog.enable": true,
"files.associations": {
"*.css": "tailwindcss",
"*.ejs": "html",
"*.art": "html",
"**/tsconfig.json": "jsonc",
@ -220,8 +225,7 @@
"*.env": "$(capture).env.*",
"README.md": "README*,CHANGELOG*,LICENSE,CNAME",
"package.json": "pnpm-lock.yaml,pnpm-workspace.yaml,.gitattributes,.gitignore,.gitpod.yml,.npmrc,.browserslistrc,.node-version,.git*,.tazerc.json",
"eslint.config.mjs": ".eslintignore,.prettierignore,.stylelintignore,.commitlintrc.*,.prettierrc.*,stylelint.config.*,.lintstagedrc.mjs,cspell.json,lefthook.yml",
"tailwind.config.mjs": "postcss.*"
"eslint.config.mjs": ".eslintignore,.prettierignore,.stylelintignore,.commitlintrc.*,.prettierrc.*,stylelint.config.*,.lintstagedrc.mjs,cspell.json,lefthook.yml"
},
"commentTranslate.hover.enabled": false,
"commentTranslate.multiLineMerge": true,

148
CLAUDE.md

@ -0,0 +1,148 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## 技术栈
1. 基于 **pnpm workspaces** + **Turborepo** 的 Vue 3 + TypeScript + Vite monorepo 项目。
2. 提供多个 UI 组件库版本(Ant Design Vue、Element Plus、Naive UI、TDesign),共享同一套使用tailwindcss+shadcn-vue的UI组件库核心框架。
3. 要求 Node ≥ 20.19.0,pnpm ≥ 10。
4. 使用 **prettier** + **eslint** + **stylelint** 进行代码检查和格式化。
5. 使用 **vitest** 进行单元测试。
6. 使用 **commitlint** 进行提交规范。
7. 使用 **czg** 进行提交规范。
8. 使用 **lefthook** 进行提交规范。
9. 使用 **vsh** 进行代码检查和格式化。
10. 使用 **turbo** 进行构建。
11. 使用 **vite** 进行开发。
12. 使用 **vue-tsc** 进行类型检查。
```bash
# 其他检查
pnpm check:circular # 循环依赖扫描
pnpm check:dep # depcheck 依赖检查
pnpm check:cspell # 拼写检查
# 清理
pnpm clean # 删除 dist、node_modules 等产物
pnpm reinstall # clean + 重新安装
# 交互式规范提交
pnpm commit # czg 提交向导
```
Turbo 任务通过 `dependsOn: ["^build"]` 级联,构建某个应用时会自动先构建其所有依赖包。
## Monorepo 目录结构
```text
apps/
backend-mock/ # 基于 Nitro 的 mock API 服务(h3 路由 + faker.js 数据)
web-antd/ # Ant Design Vue 应用
web-ele/ # Element Plus 应用
web-naive/ # Naive UI 应用
web-tdesign/ # TDesign Vue 应用
packages/
@core/ # 框架核心(不依赖具体 UI 库)
base/ # 共享工具、缓存、颜色处理、类型定义
composables/ # 核心 Vue composable
preferences/ # PreferenceManager 类(响应式、持久化配置)
ui-kit/ # UI 组件片段:form-ui、layout-ui、menu-ui、popup-ui、shadcn-ui、tabs-ui
effects/ # 高层模块,可依赖 @core 和 UI 库
access/ # 路由/菜单生成与权限指令
common-ui/ # 通用 UI 组件(ApiComponent、IconPicker、VCropper、Tippy 等)
hooks/ # useAppConfig 等
layouts/ # BasicLayout、登录页、各类 widgets
plugins/ # Motion 等插件
request/ # RequestClient(axios 封装 + 拦截器体系)
constants/ # 全局常量(LOGIN_PATH 等)
icons/ # Iconify 图标封装
locales/ # vue-i18n 初始化、loadLocalesMap 工具
preferences/ # 对外暴露 @core/preferences 的公共 API
stores/ # Pinia 全局 store:useAccessStore、useUserStore、useTabbarStore
styles/ # 全局 CSS / TailwindCSS 基础样式
types/ # 共享 TypeScript 类型
utils/ # 共享工具函数(mergeRouteModules、mapTree 等)
internal/
lint-configs/ # ESLint、Prettier、Stylelint、commitlint 配置包
node-utils/ # 构建时 Node 工具
tailwind-config/ # 共享 Tailwind 配置
tsconfig/ # 基础 tsconfig
vite-config/ # 共享 Vite 配置工厂 + 插件集合
scripts/
vsh/ # CLI 工具(lint、check-dep、check-circular、publint)
turbo-run/ # 交互式 turbo 运行器
playground/ # 组件演示场
docs/ # VitePress 文档
```
## 核心架构说明
### 应用启动流程
每个应用的 `src/main.ts` 调用 `bootstrap(namespace)`(位于 `src/bootstrap.ts`),依次执行:
1. 初始化**组件适配器**(`src/adapter/component/index.ts`)——将通用表单组件名映射到具体 UI 库的组件。
2. 调用 `initSetupVbenForm()`(`src/adapter/form.ts`)配置通用表单系统。
3. 依次初始化 i18n、Pinia stores、权限指令、Tippy、路由、MotionPlugin,最后挂载到 `#app`
### 偏好设置系统
`@vben/preferences` 导出单例 `preferences`(`PreferenceManager`)。它是响应式的,自动持久化到 localStorage(以应用 namespace 为前缀),并驱动主题 CSS 变量的更新。各应用在 `src/preferences.ts` 中调用 `defineOverridesPreferences()` 覆盖默认值,无需修改核心代码。
### 权限/访问系统
`@vben/access`(`packages/effects/access`)支持三种访问模式:
- **frontend**:根据用户角色过滤静态路由。
- **backend**:从接口(`getAllMenusApi`)获取菜单并动态注册路由。
- **mixed**:同时使用以上两种方式。
路由守卫(`src/router/guard.ts`)在登录后首次导航时调用 `generateAccess()`,将结果存入 `useAccessStore`,再重定向到目标页。`v-access` 指令和 `<AccessControl>` 组件用于按权限码或角色控制 UI 元素显示。
### 请求客户端
`@vben/request` 将 Axios 封装为 `RequestClient`。每个应用在 `src/api/request.ts` 中创建自己的实例,挂载以下拦截器:
- **请求拦截**:自动附加 Bearer Token 和 Accept-Language 头。
- **`defaultResponseInterceptor`**:解包 `{ code, data, message }` 响应格式。
- **`authenticateResponseInterceptor`**:处理 401,自动刷新 token 或跳转登录。
- **`errorMessageResponseInterceptor`**:调用 `message.error()` 显示错误。
在 API 文件中从 `#/api/request` 引入 `requestClient`(自动解包响应)或 `baseRequestClient`(原始响应)。
### 路由组织
- `src/router/routes/modules/*.ts`:需要权限验证的动态路由。
- `src/router/routes/core/`:始终可访问的路由(登录页、404 等)。
- `mergeRouteModules(import.meta.glob(...))` 用于聚合路由模块文件。
- 动态路由在运行时由权限系统注册。
### 适配器模式
每个 UI 库应用在 `src/adapter/` 下提供适配器,将 `@vben/common-ui` 的通用 form/modal/drawer 组件桥接到具体组件库。这是 `web-antd`、`web-ele` 等应用之间的主要差异所在。
### 全局 Pinia Store
- `useAccessStore`:token、路由、菜单、锁屏、登录过期状态。
- `useUserStore`:用户信息、角色、homePath。
- `useTabbarStore`:已打开标签页管理。
所有 store 通过 `@vben/stores``initStores(app, { namespace })` 统一初始化。
### Mock 后端
`apps/backend-mock` 是一个 Nitro 服务器,可单独启动:`pnpm -F @vben/backend-mock start`。Vite 开发服务器通过 `vite.config.ts`(位于 `internal/vite-config`)将 API 请求代理到该服务。
## 开发约定
- **路径别名**:`#/*` 指向各应用的 `./src/*`(在 `package.json#imports` 中定义)。
- **依赖版本管理**:内部包使用 `workspace:*`,第三方包使用 `catalog:`(版本集中在 `pnpm-workspace.yaml#catalog` 中管理)。
- **提交规范**:遵循 Conventional Commits(`feat`、`fix`、`chore`、`docs`、`refactor`、`perf`、`test`、`ci`、`style`、`types`、`revert`),由 lefthook + commitlint 强制执行。
- **pre-commit 钩子**(lefthook):自动对暂存文件执行 prettier + eslint + stylelint,推荐使用 `pnpm commit`(czg)提交。
- **新增页面**:在 `src/views/` 下创建 `.vue` 文件,在 `src/router/routes/modules/` 下添加路由模块;若使用 backend 模式,还需确保后端接口返回对应菜单数据。
- **国际化**:统一使用 `$t('key')`,locale 文件位于 `packages/locales/`,项目级国际化文件位于 `src/locales/langs`

2
apps/backend-mock/api/table/list.ts

@ -79,7 +79,7 @@ export default eventHandler(async (event) => {
const aValue = a[sortKey] as unknown;
const bValue = b[sortKey] as unknown;
let result = 0;
let result: number;
if (typeof aValue === 'number' && typeof bValue === 'number') {
result = aValue - bValue;

1
apps/web-antd/postcss.config.mjs

@ -1 +0,0 @@
export { default } from '@vben/tailwind-config/postcss';

3
apps/web-antd/src/store/auth.ts

@ -98,8 +98,7 @@ export const useAuthStore = defineStore('auth', () => {
}
async function fetchUserInfo() {
let userInfo: null | UserInfo = null;
userInfo = await getUserInfoApi();
const userInfo = await getUserInfoApi();
userStore.setUserInfo(userInfo);
return userInfo;
}

4
apps/web-antd/src/views/dashboard/analytics/index.vue

@ -76,10 +76,10 @@ const chartTabs: TabOption[] = [
</AnalysisChartsTabs>
<div class="mt-5 w-full md:flex">
<AnalysisChartCard class="mt-5 md:mr-4 md:mt-0 md:w-1/3" title="访问数量">
<AnalysisChartCard class="mt-5 md:mt-0 md:mr-4 md:w-1/3" title="访问数量">
<AnalyticsVisitsData />
</AnalysisChartCard>
<AnalysisChartCard class="mt-5 md:mr-4 md:mt-0 md:w-1/3" title="访问来源">
<AnalysisChartCard class="mt-5 md:mt-0 md:mr-4 md:w-1/3" title="访问来源">
<AnalyticsVisitsSource />
</AnalysisChartCard>
<AnalysisChartCard class="mt-5 md:mt-0 md:w-1/3" title="访问来源">

1
apps/web-antd/tailwind.config.mjs

@ -1 +0,0 @@
export { default } from '@vben/tailwind-config';

1
apps/web-antdv-next/postcss.config.mjs

@ -1 +0,0 @@
export { default } from '@vben/tailwind-config/postcss';

3
apps/web-antdv-next/src/store/auth.ts

@ -98,8 +98,7 @@ export const useAuthStore = defineStore('auth', () => {
}
async function fetchUserInfo() {
let userInfo: null | UserInfo = null;
userInfo = await getUserInfoApi();
const userInfo = await getUserInfoApi();
userStore.setUserInfo(userInfo);
return userInfo;
}

4
apps/web-antdv-next/src/views/dashboard/analytics/index.vue

@ -76,10 +76,10 @@ const chartTabs: TabOption[] = [
</AnalysisChartsTabs>
<div class="mt-5 w-full md:flex">
<AnalysisChartCard class="mt-5 md:mr-4 md:mt-0 md:w-1/3" title="访问数量">
<AnalysisChartCard class="mt-5 md:mt-0 md:mr-4 md:w-1/3" title="访问数量">
<AnalyticsVisitsData />
</AnalysisChartCard>
<AnalysisChartCard class="mt-5 md:mr-4 md:mt-0 md:w-1/3" title="访问来源">
<AnalysisChartCard class="mt-5 md:mt-0 md:mr-4 md:w-1/3" title="访问来源">
<AnalyticsVisitsSource />
</AnalysisChartCard>
<AnalysisChartCard class="mt-5 md:mt-0 md:w-1/3" title="访问来源">

1
apps/web-antdv-next/tailwind.config.mjs

@ -1 +0,0 @@
export { default } from '@vben/tailwind-config';

1
apps/web-ele/postcss.config.mjs

@ -1 +0,0 @@
export { default } from '@vben/tailwind-config/postcss';

3
apps/web-ele/src/store/auth.ts

@ -99,8 +99,7 @@ export const useAuthStore = defineStore('auth', () => {
}
async function fetchUserInfo() {
let userInfo: null | UserInfo = null;
userInfo = await getUserInfoApi();
const userInfo = await getUserInfoApi();
userStore.setUserInfo(userInfo);
return userInfo;
}

4
apps/web-ele/src/views/dashboard/analytics/index.vue

@ -76,10 +76,10 @@ const chartTabs: TabOption[] = [
</AnalysisChartsTabs>
<div class="mt-5 w-full md:flex">
<AnalysisChartCard class="mt-5 md:mr-4 md:mt-0 md:w-1/3" title="访问数量">
<AnalysisChartCard class="mt-5 md:mt-0 md:mr-4 md:w-1/3" title="访问数量">
<AnalyticsVisitsData />
</AnalysisChartCard>
<AnalysisChartCard class="mt-5 md:mr-4 md:mt-0 md:w-1/3" title="访问来源">
<AnalysisChartCard class="mt-5 md:mt-0 md:mr-4 md:w-1/3" title="访问来源">
<AnalyticsVisitsSource />
</AnalysisChartCard>
<AnalysisChartCard class="mt-5 md:mt-0 md:w-1/3" title="访问来源">

4
apps/web-ele/src/views/demos/element/index.vue

@ -102,9 +102,7 @@ const segmentedOptions = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
</ElCard>
<ElCard class="mb-5 w-80">
<template #header> V-Loading </template>
<div class="flex size-72 items-center justify-center" v-loading="true">
一些演示的内容
</div>
<div class="flex-center size-72" v-loading="true">一些演示的内容</div>
</ElCard>
<ElCard class="mb-5 w-80">
<ElTable :data="tableData" stripe>

1
apps/web-ele/tailwind.config.mjs

@ -1 +0,0 @@
export { default } from '@vben/tailwind-config';

1
apps/web-naive/postcss.config.mjs

@ -1 +0,0 @@
export { default } from '@vben/tailwind-config/postcss';

3
apps/web-naive/src/store/auth.ts

@ -99,8 +99,7 @@ export const useAuthStore = defineStore('auth', () => {
}
async function fetchUserInfo() {
let userInfo: null | UserInfo = null;
userInfo = await getUserInfoApi();
const userInfo = await getUserInfoApi();
userStore.setUserInfo(userInfo);
return userInfo;
}

4
apps/web-naive/src/views/dashboard/analytics/index.vue

@ -76,10 +76,10 @@ const chartTabs: TabOption[] = [
</AnalysisChartsTabs>
<div class="mt-5 w-full md:flex">
<AnalysisChartCard class="mt-5 md:mr-4 md:mt-0 md:w-1/3" title="访问数量">
<AnalysisChartCard class="mt-5 md:mt-0 md:mr-4 md:w-1/3" title="访问数量">
<AnalyticsVisitsData />
</AnalysisChartCard>
<AnalysisChartCard class="mt-5 md:mr-4 md:mt-0 md:w-1/3" title="访问来源">
<AnalysisChartCard class="mt-5 md:mt-0 md:mr-4 md:w-1/3" title="访问来源">
<AnalyticsVisitsSource />
</AnalysisChartCard>
<AnalysisChartCard class="mt-5 md:mt-0 md:w-1/3" title="访问来源">

1
apps/web-naive/tailwind.config.mjs

@ -1 +0,0 @@
export { default } from '@vben/tailwind-config';

1
apps/web-tdesign/postcss.config.mjs

@ -1 +0,0 @@
export { default } from '@vben/tailwind-config/postcss';

3
apps/web-tdesign/src/store/auth.ts

@ -97,8 +97,7 @@ export const useAuthStore = defineStore('auth', () => {
}
async function fetchUserInfo() {
let userInfo: null | UserInfo = null;
userInfo = await getUserInfoApi();
const userInfo = await getUserInfoApi();
userStore.setUserInfo(userInfo);
return userInfo;
}

4
apps/web-tdesign/src/views/dashboard/analytics/index.vue

@ -76,10 +76,10 @@ const chartTabs: TabOption[] = [
</AnalysisChartsTabs>
<div class="mt-5 w-full md:flex">
<AnalysisChartCard class="mt-5 md:mr-4 md:mt-0 md:w-1/3" title="访问数量">
<AnalysisChartCard class="mt-5 md:mt-0 md:mr-4 md:w-1/3" title="访问数量">
<AnalyticsVisitsData />
</AnalysisChartCard>
<AnalysisChartCard class="mt-5 md:mr-4 md:mt-0 md:w-1/3" title="访问来源">
<AnalysisChartCard class="mt-5 md:mt-0 md:mr-4 md:w-1/3" title="访问来源">
<AnalyticsVisitsSource />
</AnalysisChartCard>
<AnalysisChartCard class="mt-5 md:mt-0 md:w-1/3" title="访问来源">

6
apps/web-tdesign/src/views/demos/tdesign/index.vue

@ -38,7 +38,7 @@ function notify(type: NotificationType) {
description="支持多语言,主题功能集成切换等"
title="TDesign Vue组件使用演示"
>
<Card class="!mb-5" title="按钮">
<Card class="mb-5!" title="按钮">
<Space>
<Button>Default</Button>
<Button theme="primary"> Primary </Button>
@ -46,7 +46,7 @@ function notify(type: NotificationType) {
<Button theme="danger"> Error </Button>
</Space>
</Card>
<Card class="!mb-5" title="Message">
<Card class="mb-5!" title="Message">
<Space>
<Button @click="info"> 信息 </Button>
<Button theme="danger" @click="error"> 错误 </Button>
@ -55,7 +55,7 @@ function notify(type: NotificationType) {
</Space>
</Card>
<Card class="!mb-5" title="Notification">
<Card class="mb-5!" title="Notification">
<Space>
<Button @click="notify('info')"> 信息 </Button>
<Button theme="danger" @click="notify('error')"> 错误 </Button>

1
apps/web-tdesign/tailwind.config.mjs

@ -1 +0,0 @@
export { default } from '@vben/tailwind-config';

3
cspell.json

@ -13,6 +13,7 @@
"brotli",
"cascader",
"clsx",
"dedup",
"defu",
"demi",
"dotenv",
@ -41,6 +42,7 @@
"noreferrer",
"nprogress",
"nuxt",
"organisation",
"pinia",
"prefixs",
"publint",
@ -51,6 +53,7 @@
"sonner",
"sortablejs",
"styl",
"tabler",
"taze",
"tdesign",
"ui-kit",

2
docs/.vitepress/components/demo-preview.vue

@ -27,7 +27,7 @@ const parsedFiles = computed(() => {
<ClientOnly>
<slot v-if="parsedFiles.length > 0"></slot>
<div v-else class="text-sm text-destructive">
<span class="rounded-sm bg-destructive px-1 py-1 text-foreground">
<span class="rounded-sm bg-destructive p-1 text-foreground">
ERROR:
</span>
The preview directory does not exist. Please check the 'dir'

8
docs/.vitepress/components/preview-group.vue

@ -56,15 +56,15 @@ const toggleOpen = () => {
<TabsList class="relative flex">
<template v-if="open">
<TabsIndicator
class="absolute bottom-0 left-0 h-[2px] w-[--reka-tabs-indicator-size] translate-x-[--reka-tabs-indicator-position] rounded-full transition-[width,transform] duration-300"
class="absolute bottom-0 left-0 h-[2px] w-(--reka-tabs-indicator-size) translate-x-(--reka-tabs-indicator-position) rounded-full transition-[width,transform] duration-300"
>
<div class="size-full bg-[var(--vp-c-indigo-1)]"></div>
<div class="size-full bg-(--vp-c-indigo-1)"></div>
</TabsIndicator>
<TabsTrigger
v-for="(tab, index) in tabs"
:key="index"
:value="tab.label"
class="border-box px-4 py-3 text-foreground data-[state=active]:text-[var(--vp-c-indigo-1)]"
class="border-box px-4 py-3 text-foreground data-[state=active]:text-(--vp-c-indigo-1)"
tabindex="-1"
>
{{ tab.label }}
@ -92,7 +92,7 @@ const toggleOpen = () => {
</div>
<div
:class="`${open ? 'h-[unset] max-h-[80vh]' : 'h-0'}`"
class="block overflow-y-scroll bg-[var(--vp-code-block-bg)] transition-all duration-300"
class="block overflow-y-scroll bg-(--vp-code-block-bg) transition-all duration-300"
>
<TabsContent
v-for="tab in tabs"

8
docs/.vitepress/config/shared.mts

@ -12,7 +12,7 @@ import {
GitChangelog,
GitChangelogMarkdownSection,
} from '@nolebase/vitepress-plugin-git-changelog/vite';
import tailwind from 'tailwindcss';
import tailwindcss from '@tailwindcss/vite';
import { defineConfig, postcssIsolateStyles } from 'vitepress';
import {
groupIconMdPlugin,
@ -57,10 +57,7 @@ export const shared = defineConfig({
},
css: {
postcss: {
plugins: [
tailwind(),
postcssIsolateStyles({ includeFiles: [/vp-doc\.css/] }),
],
plugins: [postcssIsolateStyles({ includeFiles: [/vp-doc\.css/] })],
},
preprocessorOptions: {
scss: {
@ -72,6 +69,7 @@ export const shared = defineConfig({
stringify: true,
},
plugins: [
tailwindcss(),
GitChangelog({
mapAuthors: [
{

2
docs/.vitepress/theme/components/vben-contributors.vue

@ -1,7 +1,7 @@
<script setup lang="ts"></script>
<template>
<div class="vp-doc vben-contributors">
<div class="vben-contributors vp-doc">
<p>Contributors</p>
<a href="https://github.com/vbenjs/vue-vben-admin/graphs/contributors">
<img

1
docs/package.json

@ -27,6 +27,7 @@
},
"devDependencies": {
"@nolebase/vitepress-plugin-git-changelog": "catalog:",
"@tailwindcss/vite": "catalog:",
"@vben/vite-config": "workspace:*",
"@vite-pwa/vitepress": "catalog:",
"vitepress": "catalog:",

11
docs/tailwind.config.mjs

@ -1,11 +0,0 @@
import tailwindcssConfig from '@vben/tailwind-config';
export default {
...tailwindcssConfig,
content: [
...tailwindcssConfig.content,
'.vitepress/**/*.{js,mts,ts,vue}',
'src/demos/**/*.{js,mts,ts,vue}',
'src/**/*.md',
],
};

7
internal/lint-configs/eslint-config/package.json

@ -32,12 +32,13 @@
"eslint-plugin-import-x": "catalog:"
},
"devDependencies": {
"@eslint-community/eslint-plugin-eslint-comments": "catalog:",
"@eslint/js": "catalog:",
"@types/eslint": "catalog:",
"@typescript-eslint/eslint-plugin": "catalog:",
"@typescript-eslint/parser": "catalog:",
"@vitest/eslint-plugin": "catalog:",
"eslint": "catalog:",
"eslint-plugin-eslint-comments": "catalog:",
"eslint-plugin-better-tailwindcss": "catalog:",
"eslint-plugin-jsdoc": "catalog:",
"eslint-plugin-jsonc": "catalog:",
"eslint-plugin-n": "catalog:",
@ -48,11 +49,9 @@
"eslint-plugin-regexp": "catalog:",
"eslint-plugin-unicorn": "catalog:",
"eslint-plugin-unused-imports": "catalog:",
"eslint-plugin-vitest": "catalog:",
"eslint-plugin-vue": "catalog:",
"eslint-plugin-yml": "catalog:",
"globals": "catalog:",
"jsonc-eslint-parser": "catalog:",
"vue-eslint-parser": "catalog:",
"yaml-eslint-parser": "catalog:"
}

3
internal/lint-configs/eslint-config/src/configs/comments.ts

@ -4,8 +4,7 @@ import { interopDefault } from '../util';
export async function comments(): Promise<Linter.Config[]> {
const [pluginComments] = await Promise.all([
// @ts-expect-error - no types
interopDefault(import('eslint-plugin-eslint-comments')),
interopDefault(import('@eslint-community/eslint-plugin-eslint-comments')),
] as const);
return [

6
internal/lint-configs/eslint-config/src/configs/ignores.ts

@ -48,6 +48,12 @@ export async function ignores(): Promise<Linter.Config[]> {
'**/*.woff',
'**/.github',
'**/lefthook.yml',
'**/.agent/**',
'**/.agents/**',
'**/.codex/**',
'**/.claude/**',
'**/.cursor/**',
],
},
];

1
internal/lint-configs/eslint-config/src/configs/index.ts

@ -11,6 +11,7 @@ export * from './perfectionist';
export * from './pnpm';
export * from './prettier';
export * from './regexp';
export * from './tailwindcss';
export * from './test';
export * from './turbo';
export * from './typescript';

9
internal/lint-configs/eslint-config/src/configs/jsonc.ts

@ -3,17 +3,12 @@ import type { Linter } from 'eslint';
import { interopDefault } from '../util';
export async function jsonc(): Promise<Linter.Config[]> {
const [pluginJsonc, parserJsonc] = await Promise.all([
interopDefault(import('eslint-plugin-jsonc')),
interopDefault(import('jsonc-eslint-parser')),
] as const);
const pluginJsonc = await interopDefault(import('eslint-plugin-jsonc'));
return [
{
files: ['**/*.json', '**/*.json5', '**/*.jsonc', '*.code-workspace'],
languageOptions: {
parser: parserJsonc as any,
},
language: 'jsonc/x',
plugins: {
jsonc: pluginJsonc as any,
},

1
internal/lint-configs/eslint-config/src/configs/node.ts

@ -23,7 +23,6 @@ export async function node(): Promise<Linter.Config[]> {
'vitest',
'vite',
'@vue/test-utils',
'@vben/tailwind-config',
'@playwright/test',
],
},

53
internal/lint-configs/eslint-config/src/configs/perfectionist.ts

@ -21,41 +21,58 @@ export async function perfectionist(): Promise<Linter.Config[]> {
'perfectionist/sort-imports': [
'error',
{
customGroups: {
type: {
'vben-core-type': ['^@vben-core/.+'],
'vben-type': ['^@vben/.+'],
'vue-type': ['^vue$', '^vue-.+', '^@vue/.+'],
customGroups: [
{
selector: 'type',
groupName: 'vben-core-type',
elementNamePattern: '^@vben-core/.+',
},
{
selector: 'type',
groupName: 'vben-type',
elementNamePattern: '^@vben/.+',
},
value: {
vben: ['^@vben/.+'],
'vben-core': ['^@vben-core/.+'],
vue: ['^vue$', '^vue-.+', '^@vue/.+'],
{
selector: 'type',
groupName: 'vue-type',
elementNamePattern: ['^vue$', '^vue-.+', '^@vue/.+'],
},
{
groupName: 'vben',
elementNamePattern: '^@vben/.+',
},
{
groupName: 'vben-core',
elementNamePattern: '^@vben-core/.+',
},
{
groupName: 'vue',
elementNamePattern: ['^vue$', '^vue-.+', '^@vue/.+'],
},
],
environment: 'node',
groups: [
['external-type', 'builtin-type', 'type'],
['type-external', 'type-builtin', 'type-import'],
'vue-type',
'vben-type',
'vben-core-type',
['parent-type', 'sibling-type', 'index-type'],
['internal-type'],
'builtin',
['type-parent', 'type-sibling', 'type-index'],
['type-internal'],
'value-builtin',
'vue',
'vben',
'vben-core',
'external',
'internal',
['parent', 'sibling', 'index'],
'value-external',
'value-internal',
['value-parent', 'value-sibling', 'value-index'],
'side-effect',
'side-effect-style',
'style',
'object',
'ts-equals-import',
'unknown',
],
internalPattern: ['^#/.+'],
newlinesBetween: 'always',
newlinesBetween: 1,
order: 'asc',
type: 'natural',
},

7
internal/lint-configs/eslint-config/src/configs/pnpm.ts

@ -3,18 +3,15 @@ import type { Linter } from 'eslint';
import { interopDefault } from '../util';
export async function pnpm(): Promise<Linter.Config[]> {
const [pluginPnpm, parserPnpm, parserJsonc] = await Promise.all([
const [pluginPnpm, parserPnpm] = await Promise.all([
interopDefault(import('eslint-plugin-pnpm')),
interopDefault(import('yaml-eslint-parser')),
interopDefault(import('jsonc-eslint-parser')),
] as const);
return [
{
files: ['package.json', '**/package.json'],
languageOptions: {
parser: parserJsonc,
},
language: 'jsonc/x',
plugins: {
pnpm: pluginPnpm,
},

49
internal/lint-configs/eslint-config/src/configs/tailwindcss.ts

@ -0,0 +1,49 @@
import type { Linter } from 'eslint';
import { getDefaultSelectors } from 'eslint-plugin-better-tailwindcss/defaults';
import { SelectorKind } from 'eslint-plugin-better-tailwindcss/types';
import { interopDefault } from '../util';
export async function tailwindcss(): Promise<Linter.Config[]> {
const [pluginBetterTailwindcss] = await Promise.all([
interopDefault(import('eslint-plugin-better-tailwindcss')),
] as const);
return [
{
plugins: {
'better-tailwindcss': pluginBetterTailwindcss,
},
// shadcn-ui 内部组件是自动生成的,不做太多限制
ignores: ['packages/@core/ui-kit/shadcn-ui/**/**'],
settings: {
'better-tailwindcss': {
entryPoint: 'packages/@core/base/design/src/css/global.css',
selectors: [
...getDefaultSelectors(), // preserve default selectors
{
kind: SelectorKind.Attribute,
match: [{ type: 'objectValues' }],
name: '^classNames$',
},
],
},
},
rules: {
...pluginBetterTailwindcss.configs.recommended.rules,
'better-tailwindcss/enforce-consistent-class-order': [
'error',
{
detectComponentClasses: true,
unknownClassOrder: 'asc',
unknownClassPosition: 'start',
},
],
// Let Prettier own wrapping decisions to avoid ping-pong formatting.
'better-tailwindcss/enforce-consistent-line-wrapping': 'off',
'better-tailwindcss/no-unknown-classes': 'off',
},
},
];
}

2
internal/lint-configs/eslint-config/src/configs/test.ts

@ -4,7 +4,7 @@ import { interopDefault } from '../util';
export async function test(): Promise<Linter.Config[]> {
const [pluginTest, pluginNoOnlyTests] = await Promise.all([
interopDefault(import('eslint-plugin-vitest')),
interopDefault(import('@vitest/eslint-plugin')),
// @ts-expect-error - no types
interopDefault(import('eslint-plugin-no-only-tests')),
] as const);

2
internal/lint-configs/eslint-config/src/configs/yaml.ts

@ -12,7 +12,7 @@ export async function yaml(): Promise<Linter.Config[]> {
{
files: ['**/*.y?(a)ml'],
plugins: {
yaml: pluginYaml as any,
yaml: pluginYaml,
},
languageOptions: {
parser: parserYaml,

6
internal/lint-configs/eslint-config/src/custom-config.ts

@ -1,10 +1,6 @@
import type { Linter } from 'eslint';
const restrictedImportIgnores = [
'**/vite.config.mts',
'**/tailwind.config.mjs',
'**/postcss.config.mjs',
];
const restrictedImportIgnores = ['**/vite.config.mts'];
const customConfig: Linter.Config[] = [
// shadcn-ui 内部组件是自动生成的,不做太多限制

2
internal/lint-configs/eslint-config/src/index.ts

@ -14,6 +14,7 @@ import {
pnpm,
prettier,
regexp,
tailwindcss,
test,
turbo,
typescript,
@ -45,6 +46,7 @@ async function defineConfig(config: FlatConfig[] = []) {
perfectionist(),
comments(),
jsdoc(),
tailwindcss(),
unicorn(),
test(),
regexp(),

1
internal/lint-configs/prettier-config/index.mjs

@ -9,7 +9,6 @@ export default {
},
},
],
plugins: ['prettier-plugin-tailwindcss'],
printWidth: 80,
proseWrap: 'never',
semi: true,

3
internal/lint-configs/prettier-config/package.json

@ -22,7 +22,6 @@
}
},
"dependencies": {
"prettier": "catalog:",
"prettier-plugin-tailwindcss": "catalog:"
"prettier": "catalog:"
}
}

14
internal/lint-configs/stylelint-config/index.mjs

@ -67,6 +67,12 @@ export default {
'use',
'forward',
'return',
'reference',
'plugin',
'source',
'theme',
'utility',
'custom-variant',
],
},
],
@ -130,12 +136,18 @@ export default {
'use',
'forward',
'return',
'reference',
'plugin',
'source',
'theme',
'utility',
'custom-variant',
],
},
],
'scss/operator-no-newline-after': null,
'selector-class-pattern':
'^(?:(?:o|c|u|t|s|is|has|_|js|qa)-)?[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*(?:__[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*)?(?:--[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*)?(?:[.+])?$',
'^-?(?:(?:o|c|u|t|s|is|has|_|js|qa)-)?[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*(?:__[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*)?(?:--[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*)?(?:[.+])?$',
'selector-not-notation': null,
},

10
internal/tailwind-config/build.config.ts

@ -1,10 +0,0 @@
import { defineBuildConfig } from 'unbuild';
export default defineBuildConfig({
clean: true,
declaration: true,
entries: ['src/index', './src/postcss.config'],
rollup: {
emitCJS: true,
},
});

67
internal/tailwind-config/package.json

@ -1,67 +0,0 @@
{
"name": "@vben/tailwind-config",
"version": "5.6.0",
"private": true,
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {
"type": "git",
"url": "git+https://github.com/vbenjs/vue-vben-admin.git",
"directory": "internal/tailwind-config"
},
"license": "MIT",
"type": "module",
"scripts": {
"stub": "pnpm unbuild --stub"
},
"files": [
"dist"
],
"main": "./dist/index.mjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"typesVersions": {
"*": {
"*": [
"./dist/*",
"./*"
]
}
},
"exports": {
".": {
"types": "./src/index.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
},
"./postcss": {
"types": "./src/postcss.config.ts",
"import": "./dist/postcss.config.mjs",
"require": "./dist/postcss.config.cjs",
"default": "./dist/postcss.config.mjs"
},
"./*": "./*"
},
"peerDependencies": {
"tailwindcss": "^3.4.3"
},
"dependencies": {
"@iconify/json": "catalog:",
"@iconify/tailwind": "catalog:",
"@manypkg/get-packages": "catalog:",
"@tailwindcss/nesting": "catalog:",
"@tailwindcss/typography": "catalog:",
"autoprefixer": "catalog:",
"cssnano": "catalog:",
"jiti": "catalog:",
"postcss": "catalog:",
"postcss-antd-fixes": "catalog:",
"postcss-import": "catalog:",
"postcss-preset-env": "catalog:",
"tailwindcss": "catalog:",
"tailwindcss-animate": "catalog:"
},
"devDependencies": {
"@types/postcss-import": "catalog:"
}
}

266
internal/tailwind-config/src/index.ts

@ -1,266 +0,0 @@
import type { Config } from 'tailwindcss';
import path from 'node:path';
import { addDynamicIconSelectors } from '@iconify/tailwind';
import { getPackagesSync } from '@manypkg/get-packages';
import typographyPlugin from '@tailwindcss/typography';
import animate from 'tailwindcss-animate';
import { enterAnimationPlugin } from './plugins/entry';
// import defaultTheme from 'tailwindcss/defaultTheme';
const { packages } = getPackagesSync(process.cwd());
const tailwindPackages: string[] = [];
packages.forEach((pkg) => {
// apps目录下和 @vben-core/tailwind-ui 包需要使用到 tailwindcss ui
// if (fs.existsSync(path.join(pkg.dir, 'tailwind.config.mjs'))) {
tailwindPackages.push(pkg.dir);
// }
});
const shadcnUiColors = {
accent: {
DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))',
hover: 'hsl(var(--accent-hover))',
lighter: 'has(val(--accent-lighter))',
},
background: {
deep: 'hsl(var(--background-deep))',
DEFAULT: 'hsl(var(--background))',
},
border: {
DEFAULT: 'hsl(var(--border))',
},
card: {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))',
},
destructive: {
...createColorsPalette('destructive'),
DEFAULT: 'hsl(var(--destructive))',
},
foreground: {
DEFAULT: 'hsl(var(--foreground))',
},
input: {
background: 'hsl(var(--input-background))',
DEFAULT: 'hsl(var(--input))',
},
muted: {
DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))',
},
popover: {
DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))',
},
primary: {
...createColorsPalette('primary'),
DEFAULT: 'hsl(var(--primary))',
},
ring: 'hsl(var(--ring))',
secondary: {
DEFAULT: 'hsl(var(--secondary))',
desc: 'hsl(var(--secondary-desc))',
foreground: 'hsl(var(--secondary-foreground))',
},
};
const customColors = {
green: {
...createColorsPalette('green'),
foreground: 'hsl(var(--success-foreground))',
},
header: {
DEFAULT: 'hsl(var(--header))',
},
heavy: {
DEFAULT: 'hsl(var(--heavy))',
foreground: 'hsl(var(--heavy-foreground))',
},
main: {
DEFAULT: 'hsl(var(--main))',
},
overlay: {
content: 'hsl(var(--overlay-content))',
DEFAULT: 'hsl(var(--overlay))',
},
red: {
...createColorsPalette('red'),
foreground: 'hsl(var(--destructive-foreground))',
},
sidebar: {
deep: 'hsl(var(--sidebar-deep))',
DEFAULT: 'hsl(var(--sidebar))',
},
success: {
...createColorsPalette('success'),
DEFAULT: 'hsl(var(--success))',
},
warning: {
...createColorsPalette('warning'),
DEFAULT: 'hsl(var(--warning))',
},
yellow: {
...createColorsPalette('yellow'),
foreground: 'hsl(var(--warning-foreground))',
},
};
export default {
content: [
'./index.html',
...tailwindPackages.map((item) =>
path.join(item, 'src/**/*.{vue,js,ts,jsx,tsx,svelte,astro,html}'),
),
],
darkMode: 'selector',
plugins: [
animate,
typographyPlugin,
addDynamicIconSelectors(),
enterAnimationPlugin,
],
prefix: '',
theme: {
container: {
center: true,
padding: '2rem',
screens: {
'2xl': '1400px',
},
},
extend: {
animation: {
'accordion-down': 'accordion-down 0.2s ease-out',
'accordion-up': 'accordion-up 0.2s ease-out',
'collapsible-down': 'collapsible-down 0.2s ease-in-out',
'collapsible-up': 'collapsible-up 0.2s ease-in-out',
float: 'float 5s linear 0ms infinite',
},
animationDuration: {
'2000': '2000ms',
'3000': '3000ms',
},
borderRadius: {
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)',
xl: 'calc(var(--radius) + 4px)',
},
boxShadow: {
float: `0 6px 16px 0 rgb(0 0 0 / 8%),
0 3px 6px -4px rgb(0 0 0 / 12%),
0 9px 28px 8px rgb(0 0 0 / 5%)`,
},
colors: {
...customColors,
...shadcnUiColors,
},
fontFamily: {
sans: [
'var(--font-family)',
// ...defaultTheme.fontFamily.sans
],
},
keyframes: {
'accordion-down': {
from: { height: '0' },
to: { height: 'var(--reka-accordion-content-height)' },
},
'accordion-up': {
from: { height: 'var(--reka-accordion-content-height)' },
to: { height: '0' },
},
'collapsible-down': {
from: { height: '0' },
to: { height: 'var(--reka-collapsible-content-height)' },
},
'collapsible-up': {
from: { height: 'var(--reka-collapsible-content-height)' },
to: { height: '0' },
},
float: {
'0%': { transform: 'translateY(0)' },
'50%': { transform: 'translateY(-20px)' },
'100%': { transform: 'translateY(0)' },
},
},
zIndex: {
'100': '100',
'1000': '1000',
},
},
},
safelist: ['dark'],
} as Config;
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))`,
200: `hsl(var(--${name}-200))`,
300: `hsl(var(--${name}-300))`,
400: `hsl(var(--${name}-400))`,
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}-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}-600))`,
// 主色文本
text: `hsl(var(--${name}-500))`,
// 主色文本激活态
'text-active': `hsl(var(--${name}-700))`,
// 主色文本悬浮态
'text-hover': `hsl(var(--${name}-600))`,
};
}

3
internal/tailwind-config/src/module.d.ts

@ -1,3 +0,0 @@
declare module '@tailwindcss/nesting' {
export default any;
}

53
internal/tailwind-config/src/plugins/entry.ts

@ -1,53 +0,0 @@
import plugin from 'tailwindcss/plugin.js';
const enterAnimationPlugin = plugin(({ addUtilities }) => {
const maxChild = 5;
const utilities: Record<string, any> = {};
for (let i = 1; i <= maxChild; i++) {
const baseDelay = 0.1;
const delay = `${baseDelay * i}s`;
utilities[`.enter-x:nth-child(${i})`] = {
animation: `enter-x-animation 0.3s ease-in-out ${delay} forwards`,
opacity: '0',
transform: `translateX(50px)`,
};
utilities[`.enter-y:nth-child(${i})`] = {
animation: `enter-y-animation 0.3s ease-in-out ${delay} forwards`,
opacity: '0',
transform: `translateY(50px)`,
};
utilities[`.-enter-x:nth-child(${i})`] = {
animation: `enter-x-animation 0.3s ease-in-out ${delay} forwards`,
opacity: '0',
transform: `translateX(-50px)`,
};
utilities[`.-enter-y:nth-child(${i})`] = {
animation: `enter-y-animation 0.3s ease-in-out ${delay} forwards`,
opacity: '0',
transform: `translateY(-50px)`,
};
}
// 添加动画关键帧
addUtilities(utilities);
addUtilities({
'@keyframes enter-x-animation': {
to: {
opacity: '1',
transform: 'translateX(0)',
},
},
'@keyframes enter-y-animation': {
to: {
opacity: '1',
transform: 'translateY(0)',
},
},
});
});
export { enterAnimationPlugin };

15
internal/tailwind-config/src/postcss.config.ts

@ -1,15 +0,0 @@
import config from '.';
export default {
plugins: {
...(process.env.NODE_ENV === 'production' ? { cssnano: {} } : {}),
// Specifying the config is not necessary in most cases, but it is included
autoprefixer: {},
// 修复 element-plus 和 ant-design-vue 的样式和tailwindcss冲突问题
'postcss-antd-fixes': { prefixes: ['ant', 'el'] },
'postcss-import': {},
'postcss-preset-env': {},
tailwindcss: { config },
'tailwindcss/nesting': {},
},
};

6
internal/tailwind-config/tsconfig.json

@ -1,6 +0,0 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@vben/tsconfig/node.json",
"include": ["src"],
"exclude": ["node_modules"]
}

1
internal/vite-config/package.json

@ -29,6 +29,7 @@
"dependencies": {
"@intlify/unplugin-vue-i18n": "catalog:",
"@jspm/generator": "catalog:",
"@tailwindcss/vite": "catalog:",
"archiver": "catalog:",
"cheerio": "catalog:",
"get-port": "catalog:",

4
internal/vite-config/src/plugins/index.ts

@ -8,6 +8,7 @@ import type {
} from '../typing';
import viteVueI18nPlugin from '@intlify/unplugin-vue-i18n/vite';
import tailwindcss from '@tailwindcss/vite';
import viteVue from '@vitejs/plugin-vue';
import viteVueJsx from '@vitejs/plugin-vue-jsx';
import { visualizer as viteVisualizerPlugin } from 'rollup-plugin-visualizer';
@ -25,6 +26,7 @@ import { viteMetadataPlugin } from './inject-metadata';
import { viteLicensePlugin } from './license';
import { viteNitroMockPlugin } from './nitro-mock';
import { vitePrintPlugin } from './print';
import { viteTailwindReferencePlugin } from './tailwind-reference';
import { viteVxeTableImportsPlugin } from './vxe-table';
/**
@ -60,6 +62,8 @@ async function loadCommonPlugins(
},
}),
viteVueJsx(),
viteTailwindReferencePlugin(),
tailwindcss(),
],
},

40
internal/vite-config/src/plugins/tailwind-reference.ts

@ -0,0 +1,40 @@
import type { Plugin } from 'vite';
const REFERENCE_LINE = '@reference "@vben-core/design/theme";\n';
/**
* Auto-inject @reference into Vue SFC <style> blocks that use @apply.
*
* In Tailwind CSS v4, each Vue SFC <style scoped> block is processed as an
* independent CSS module. If a style block uses @apply with custom theme
* utilities (e.g. bg-primary, text-foreground), it needs access to the
* @theme definition via @reference. This plugin auto-injects it so
* individual components don't need to add it manually.
*/
export function viteTailwindReferencePlugin(): Plugin {
return {
enforce: 'pre',
name: 'vite:tailwind-reference',
transform(code, id) {
// Only process Vue SFC style blocks
if (!id.includes('.vue')) {
return null;
}
if (!id.includes('type=style')) {
return null;
}
// Skip if already has @reference
if (code.includes('@reference')) {
return null;
}
// Only inject if the style block uses @apply
if (!code.includes('@apply')) {
return null;
}
return {
code: REFERENCE_LINE + code,
map: null,
};
},
};
}

4
package.json

@ -72,7 +72,6 @@
"@vben/eslint-config": "workspace:*",
"@vben/prettier-config": "workspace:*",
"@vben/stylelint-config": "workspace:*",
"@vben/tailwind-config": "workspace:*",
"@vben/tsconfig": "workspace:*",
"@vben/turbo-run": "workspace:*",
"@vben/vite-config": "workspace:*",
@ -80,7 +79,6 @@
"@vitejs/plugin-vue": "catalog:",
"@vitejs/plugin-vue-jsx": "catalog:",
"@vue/test-utils": "catalog:",
"autoprefixer": "catalog:",
"cross-env": "catalog:",
"cspell": "catalog:",
"happy-dom": "catalog:",
@ -101,5 +99,5 @@
"node": ">=20.19.0",
"pnpm": ">=10.0.0"
},
"packageManager": "pnpm@10.28.2"
"packageManager": "pnpm@10.30.3"
}

9
packages/@core/base/design/package.json

@ -25,6 +25,9 @@
"development": "./src/scss-bem/bem.scss",
"default": "./dist/bem.scss"
},
"./theme": {
"default": "./src/css/global.css"
},
".": {
"types": "./src/index.ts",
"development": "./src/index.ts",
@ -37,5 +40,11 @@
"default": "./dist/index.mjs"
}
}
},
"dependencies": {
"@iconify/json": "catalog:",
"@iconify/tailwind4": "catalog:",
"@tailwindcss/typography": "catalog:",
"tw-animate-css": "catalog:"
}
}

477
packages/@core/base/design/src/css/global.css

@ -1,12 +1,294 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@import 'tailwindcss';
@import 'tw-animate-css';
@plugin '@tailwindcss/typography';
@plugin '@iconify/tailwind4';
/* Monorepo source detection: scan all packages and apps for utility classes */
@source '../../../../../../packages/';
@source '../../../../../../apps/';
@source '../../../../../../docs/';
@source '../../../../../../playground/';
/* Dark mode uses .dark class selector, not prefers-color-scheme */
@custom-variant dark (&:is(.dark *));
@theme inline {
/* Font */
--font-sans: var(--font-family);
/* Border Radius */
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
/* Box Shadow */
--shadow-float:
0 6px 16px 0 rgb(0 0 0 / 8%), 0 3px 6px -4px rgb(0 0 0 / 12%),
0 9px 28px 8px rgb(0 0 0 / 5%);
/* Animations */
--animate-accordion-down: accordion-down 0.2s ease-out;
--animate-accordion-up: accordion-up 0.2s ease-out;
--animate-collapsible-down: collapsible-down 0.2s ease-in-out;
--animate-collapsible-up: collapsible-up 0.2s ease-in-out;
--animate-float: float 5s linear 0ms infinite;
/* ===== Semantic Colors (shadcn-ui) ===== */
--color-background: hsl(var(--background));
--color-background-deep: hsl(var(--background-deep));
--color-foreground: hsl(var(--foreground));
--color-card: hsl(var(--card));
--color-card-foreground: hsl(var(--card-foreground));
--color-popover: hsl(var(--popover));
--color-popover-foreground: hsl(var(--popover-foreground));
--color-muted: hsl(var(--muted));
--color-muted-foreground: hsl(var(--muted-foreground));
--color-accent: hsl(var(--accent));
--color-accent-foreground: hsl(var(--accent-foreground));
--color-accent-hover: hsl(var(--accent-hover));
--color-accent-lighter: hsl(var(--accent-lighter));
--color-border: hsl(var(--border));
--color-input: hsl(var(--input));
--color-input-background: hsl(var(--input-background));
--color-ring: hsl(var(--ring));
--color-secondary: hsl(var(--secondary));
--color-secondary-desc: hsl(var(--secondary-desc));
--color-secondary-foreground: hsl(var(--secondary-foreground));
/* ===== Custom Semantic Colors ===== */
--color-header: hsl(var(--header));
--color-heavy: hsl(var(--heavy));
--color-heavy-foreground: hsl(var(--heavy-foreground));
--color-main: hsl(var(--main));
--color-overlay: hsl(var(--overlay));
--color-overlay-content: hsl(var(--overlay-content));
--color-sidebar: hsl(var(--sidebar));
--color-sidebar-deep: hsl(var(--sidebar-deep));
/* ===== Primary Palette ===== */
--color-primary: hsl(var(--primary));
--color-primary-foreground: hsl(var(--primary-foreground));
--color-primary-50: hsl(var(--primary-50));
--color-primary-100: hsl(var(--primary-100));
--color-primary-200: hsl(var(--primary-200));
--color-primary-300: hsl(var(--primary-300));
--color-primary-400: hsl(var(--primary-400));
--color-primary-500: hsl(var(--primary-500));
--color-primary-600: hsl(var(--primary-600));
--color-primary-700: hsl(var(--primary-700));
--color-primary-active: hsl(var(--primary-700));
--color-primary-background-light: hsl(var(--primary-200));
--color-primary-background-lighter: hsl(var(--primary-100));
--color-primary-background-lightest: hsl(var(--primary-50));
--color-primary-border: hsl(var(--primary-400));
--color-primary-border-light: hsl(var(--primary-300));
--color-primary-hover: hsl(var(--primary-600));
--color-primary-text: hsl(var(--primary-500));
--color-primary-text-active: hsl(var(--primary-700));
--color-primary-text-hover: hsl(var(--primary-600));
/* ===== Destructive Palette ===== */
--color-destructive: hsl(var(--destructive));
--color-destructive-foreground: hsl(var(--destructive-foreground));
--color-destructive-50: hsl(var(--destructive-50));
--color-destructive-100: hsl(var(--destructive-100));
--color-destructive-200: hsl(var(--destructive-200));
--color-destructive-300: hsl(var(--destructive-300));
--color-destructive-400: hsl(var(--destructive-400));
--color-destructive-500: hsl(var(--destructive-500));
--color-destructive-600: hsl(var(--destructive-600));
--color-destructive-700: hsl(var(--destructive-700));
--color-destructive-active: hsl(var(--destructive-700));
--color-destructive-background-light: hsl(var(--destructive-200));
--color-destructive-background-lighter: hsl(var(--destructive-100));
--color-destructive-background-lightest: hsl(var(--destructive-50));
--color-destructive-border: hsl(var(--destructive-400));
--color-destructive-border-light: hsl(var(--destructive-300));
--color-destructive-hover: hsl(var(--destructive-600));
--color-destructive-text: hsl(var(--destructive-500));
--color-destructive-text-active: hsl(var(--destructive-700));
--color-destructive-text-hover: hsl(var(--destructive-600));
/* ===== Success Palette ===== */
--color-success: hsl(var(--success));
--color-success-foreground: hsl(var(--success-foreground));
--color-success-50: hsl(var(--success-50));
--color-success-100: hsl(var(--success-100));
--color-success-200: hsl(var(--success-200));
--color-success-300: hsl(var(--success-300));
--color-success-400: hsl(var(--success-400));
--color-success-500: hsl(var(--success-500));
--color-success-600: hsl(var(--success-600));
--color-success-700: hsl(var(--success-700));
--color-success-active: hsl(var(--success-700));
--color-success-background-light: hsl(var(--success-200));
--color-success-background-lighter: hsl(var(--success-100));
--color-success-background-lightest: hsl(var(--success-50));
--color-success-border: hsl(var(--success-400));
--color-success-border-light: hsl(var(--success-300));
--color-success-hover: hsl(var(--success-600));
--color-success-text: hsl(var(--success-500));
--color-success-text-active: hsl(var(--success-700));
--color-success-text-hover: hsl(var(--success-600));
/* ===== Warning Palette ===== */
--color-warning: hsl(var(--warning));
--color-warning-foreground: hsl(var(--warning-foreground));
--color-warning-50: hsl(var(--warning-50));
--color-warning-100: hsl(var(--warning-100));
--color-warning-200: hsl(var(--warning-200));
--color-warning-300: hsl(var(--warning-300));
--color-warning-400: hsl(var(--warning-400));
--color-warning-500: hsl(var(--warning-500));
--color-warning-600: hsl(var(--warning-600));
--color-warning-700: hsl(var(--warning-700));
--color-warning-active: hsl(var(--warning-700));
--color-warning-background-light: hsl(var(--warning-200));
--color-warning-background-lighter: hsl(var(--warning-100));
--color-warning-background-lightest: hsl(var(--warning-50));
--color-warning-border: hsl(var(--warning-400));
--color-warning-border-light: hsl(var(--warning-300));
--color-warning-hover: hsl(var(--warning-600));
--color-warning-text: hsl(var(--warning-500));
--color-warning-text-active: hsl(var(--warning-700));
--color-warning-text-hover: hsl(var(--warning-600));
/* ===== Green Palette (alias for success shades) ===== */
--color-green-50: hsl(var(--green-50));
--color-green-100: hsl(var(--green-100));
--color-green-200: hsl(var(--green-200));
--color-green-300: hsl(var(--green-300));
--color-green-400: hsl(var(--green-400));
--color-green-500: hsl(var(--green-500));
--color-green-600: hsl(var(--green-600));
--color-green-700: hsl(var(--green-700));
--color-green-active: hsl(var(--green-700));
--color-green-background-light: hsl(var(--green-200));
--color-green-background-lighter: hsl(var(--green-100));
--color-green-background-lightest: hsl(var(--green-50));
--color-green-border: hsl(var(--green-400));
--color-green-border-light: hsl(var(--green-300));
--color-green-foreground: hsl(var(--success-foreground));
--color-green-hover: hsl(var(--green-600));
--color-green-text: hsl(var(--green-500));
--color-green-text-active: hsl(var(--green-700));
--color-green-text-hover: hsl(var(--green-600));
/* ===== Red Palette (alias for destructive shades) ===== */
--color-red-50: hsl(var(--red-50));
--color-red-100: hsl(var(--red-100));
--color-red-200: hsl(var(--red-200));
--color-red-300: hsl(var(--red-300));
--color-red-400: hsl(var(--red-400));
--color-red-500: hsl(var(--red-500));
--color-red-600: hsl(var(--red-600));
--color-red-700: hsl(var(--red-700));
--color-red-active: hsl(var(--red-700));
--color-red-background-light: hsl(var(--red-200));
--color-red-background-lighter: hsl(var(--red-100));
--color-red-background-lightest: hsl(var(--red-50));
--color-red-border: hsl(var(--red-400));
--color-red-border-light: hsl(var(--red-300));
--color-red-foreground: hsl(var(--destructive-foreground));
--color-red-hover: hsl(var(--red-600));
--color-red-text: hsl(var(--red-500));
--color-red-text-active: hsl(var(--red-700));
--color-red-text-hover: hsl(var(--red-600));
/* ===== Yellow Palette (alias for warning shades) ===== */
--color-yellow-50: hsl(var(--yellow-50));
--color-yellow-100: hsl(var(--yellow-100));
--color-yellow-200: hsl(var(--yellow-200));
--color-yellow-300: hsl(var(--yellow-300));
--color-yellow-400: hsl(var(--yellow-400));
--color-yellow-500: hsl(var(--yellow-500));
--color-yellow-600: hsl(var(--yellow-600));
--color-yellow-700: hsl(var(--yellow-700));
--color-yellow-active: hsl(var(--yellow-700));
--color-yellow-background-light: hsl(var(--yellow-200));
--color-yellow-background-lighter: hsl(var(--yellow-100));
--color-yellow-background-lightest: hsl(var(--yellow-50));
--color-yellow-border: hsl(var(--yellow-400));
--color-yellow-border-light: hsl(var(--yellow-300));
--color-yellow-foreground: hsl(var(--warning-foreground));
--color-yellow-hover: hsl(var(--yellow-600));
--color-yellow-text: hsl(var(--yellow-500));
--color-yellow-text-active: hsl(var(--yellow-700));
--color-yellow-text-hover: hsl(var(--yellow-600));
}
/* Keyframes */
@keyframes accordion-down {
from {
height: 0;
}
to {
height: var(--reka-accordion-content-height);
}
}
@keyframes accordion-up {
from {
height: var(--reka-accordion-content-height);
}
to {
height: 0;
}
}
@keyframes collapsible-down {
from {
height: 0;
}
to {
height: var(--reka-collapsible-content-height);
}
}
@keyframes collapsible-up {
from {
height: var(--reka-collapsible-content-height);
}
to {
height: 0;
}
}
@keyframes float {
0% {
transform: translateY(0);
}
50% {
transform: translateY(-20px);
}
100% {
transform: translateY(0);
}
}
/* Base styles */
@layer base {
*,
::after,
::before {
@apply border-border;
@apply border-border outline-ring/50;
box-sizing: border-box;
border-style: solid;
@ -24,29 +306,16 @@
text-rendering: optimizelegibility;
text-size-adjust: 100%;
-webkit-tap-highlight-color: transparent;
/* -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; */
}
#app,
body,
html {
@apply size-full;
/* scrollbar-gutter: stable; */
}
body {
min-height: 100vh;
/* pointer-events: auto !important; */
/* overflow: overlay; */
/* -webkit-font-smoothing: antialiased; */
/* -moz-osx-font-smoothing: grayscale; */
}
a,
@ -63,19 +332,19 @@
}
::view-transition-old(root) {
@apply z-[1];
@apply z-1;
}
::view-transition-new(root) {
@apply z-[2147483646];
@apply z-2147483646;
}
html.dark::view-transition-old(root) {
@apply z-[2147483646];
@apply z-2147483646;
}
html.dark::view-transition-new(root) {
@apply z-[1];
@apply z-1;
}
input::placeholder,
@ -83,18 +352,12 @@
@apply opacity-100;
}
/* input:-webkit-autofill {
@apply border-none;
box-shadow: 0 0 0 1000px transparent inset;
} */
input[type='number']::-webkit-inner-spin-button,
input[type='number']::-webkit-outer-spin-button {
@apply m-0 appearance-none;
}
/* 只有非mac下才进行调整,mac下使用默认滚动条 */
/* Only adjust scrollbar for non-macOS */
html:not([data-platform='macOs']) {
::-webkit-scrollbar {
@apply h-[10px] w-[10px];
@ -114,25 +377,31 @@
}
}
@layer components {
.flex-center {
@apply flex items-center justify-center;
/* Custom utilities (v4 @utility syntax) */
@utility flex-center {
display: flex;
align-items: center;
justify-content: center;
}
.flex-col-center {
@apply flex flex-col items-center justify-center;
@utility flex-col-center {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
/* Component styles (complex selectors, not convertible to @utility) */
.outline-box {
@apply outline-border relative cursor-pointer rounded-md p-1 outline outline-1;
@apply outline-border relative cursor-pointer rounded-md p-1 outline-1;
}
.outline-box::after {
@apply absolute left-1/2 top-1/2 z-20 h-0 w-[1px] rounded-sm opacity-0 outline outline-2 outline-transparent transition-all duration-300 content-[""];
@apply absolute top-1/2 left-1/2 z-20 h-0 w-px rounded-sm opacity-0 outline-2 outline-transparent transition-all duration-300 content-[""];
}
.outline-box.outline-box-active {
@apply outline-primary outline outline-2;
@apply outline-primary outline-2;
}
.outline-box.outline-box-active::after {
@ -140,7 +409,7 @@
}
.outline-box:not(.outline-box-active):hover::after {
@apply outline-primary left-0 top-0 h-full w-full p-1 opacity-100;
@apply outline-primary top-0 left-0 h-full w-full p-1 opacity-100;
}
.vben-link {
@ -150,6 +419,140 @@
.card-box {
@apply bg-card text-card-foreground border-border rounded-xl border;
}
/* Enter animations (converted from enterAnimationPlugin) */
@keyframes enter-x-animation {
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes enter-y-animation {
to {
opacity: 1;
transform: translateY(0);
}
}
.enter-x:nth-child(1) {
opacity: 0;
transform: translateX(50px);
animation: enter-x-animation 0.3s ease-in-out 0.1s forwards;
}
.enter-x:nth-child(2) {
opacity: 0;
transform: translateX(50px);
animation: enter-x-animation 0.3s ease-in-out 0.2s forwards;
}
.enter-x:nth-child(3) {
opacity: 0;
transform: translateX(50px);
animation: enter-x-animation 0.3s ease-in-out 0.3s forwards;
}
.enter-x:nth-child(4) {
opacity: 0;
transform: translateX(50px);
animation: enter-x-animation 0.3s ease-in-out 0.4s forwards;
}
.enter-x:nth-child(5) {
opacity: 0;
transform: translateX(50px);
animation: enter-x-animation 0.3s ease-in-out 0.5s forwards;
}
.enter-y:nth-child(1) {
opacity: 0;
transform: translateY(50px);
animation: enter-y-animation 0.3s ease-in-out 0.1s forwards;
}
.enter-y:nth-child(2) {
opacity: 0;
transform: translateY(50px);
animation: enter-y-animation 0.3s ease-in-out 0.2s forwards;
}
.enter-y:nth-child(3) {
opacity: 0;
transform: translateY(50px);
animation: enter-y-animation 0.3s ease-in-out 0.3s forwards;
}
.enter-y:nth-child(4) {
opacity: 0;
transform: translateY(50px);
animation: enter-y-animation 0.3s ease-in-out 0.4s forwards;
}
.enter-y:nth-child(5) {
opacity: 0;
transform: translateY(50px);
animation: enter-y-animation 0.3s ease-in-out 0.5s forwards;
}
.-enter-x:nth-child(1) {
opacity: 0;
transform: translateX(-50px);
animation: enter-x-animation 0.3s ease-in-out 0.1s forwards;
}
.-enter-x:nth-child(2) {
opacity: 0;
transform: translateX(-50px);
animation: enter-x-animation 0.3s ease-in-out 0.2s forwards;
}
.-enter-x:nth-child(3) {
opacity: 0;
transform: translateX(-50px);
animation: enter-x-animation 0.3s ease-in-out 0.3s forwards;
}
.-enter-x:nth-child(4) {
opacity: 0;
transform: translateX(-50px);
animation: enter-x-animation 0.3s ease-in-out 0.4s forwards;
}
.-enter-x:nth-child(5) {
opacity: 0;
transform: translateX(-50px);
animation: enter-x-animation 0.3s ease-in-out 0.5s forwards;
}
.-enter-y:nth-child(1) {
opacity: 0;
transform: translateY(-50px);
animation: enter-y-animation 0.3s ease-in-out 0.1s forwards;
}
.-enter-y:nth-child(2) {
opacity: 0;
transform: translateY(-50px);
animation: enter-y-animation 0.3s ease-in-out 0.2s forwards;
}
.-enter-y:nth-child(3) {
opacity: 0;
transform: translateY(-50px);
animation: enter-y-animation 0.3s ease-in-out 0.3s forwards;
}
.-enter-y:nth-child(4) {
opacity: 0;
transform: translateY(-50px);
animation: enter-y-animation 0.3s ease-in-out 0.4s forwards;
}
.-enter-y:nth-child(5) {
opacity: 0;
transform: translateY(-50px);
animation: enter-y-animation 0.3s ease-in-out 0.5s forwards;
}
html.invert-mode {

8
packages/@core/base/design/src/css/nprogress.css

@ -1,10 +1,12 @@
@reference "./global.css";
/* Make clicks pass-through */
#nprogress {
@apply pointer-events-none;
}
#nprogress .bar {
@apply bg-primary fixed left-0 top-0 z-[1031] h-[2px] w-full;
@apply bg-primary fixed top-0 left-0 z-1031 h-[2px] w-full;
}
/* Fancy blur effect */
@ -20,11 +22,11 @@
/* Remove these to get rid of the spinner */
#nprogress .spinner {
@apply fixed right-4 top-4 z-[1031] block;
@apply fixed top-4 right-4 z-1031 block;
}
#nprogress .spinner-icon {
@apply border-t-primary border-l-primary size-4 rounded-full border-[2px] border-solid border-transparent;
@apply border-t-primary border-l-primary size-4 rounded-full border-2 border-solid border-transparent;
animation: nprogress-spinner 400ms linear infinite;
}

54
packages/@core/base/shared/src/utils/__tests__/resources.test.ts

@ -1,10 +1,7 @@
import { beforeEach, describe, expect, it } from 'vitest';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { loadScript } from '../resources';
const testJsPath =
'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js';
describe('loadScript', () => {
beforeEach(() => {
// 每个测试前清空 head,保证环境干净
@ -12,18 +9,14 @@ describe('loadScript', () => {
});
it('should resolve when the script loads successfully', async () => {
const promise = loadScript(testJsPath);
// happy-dom v20+ auto-fires 'load' via handleDisabledFileLoadingAsSuccess
const promise = loadScript('/test-script.js');
// 此时脚本元素已被创建并插入
const script = document.querySelector(
`script[src="${testJsPath}"]`,
'script[src="/test-script.js"]',
) as HTMLScriptElement;
expect(script).toBeTruthy();
// 模拟加载成功
script.dispatchEvent(new Event('load'));
// 等待 promise resolve
await expect(promise).resolves.toBeUndefined();
});
@ -45,37 +38,42 @@ describe('loadScript', () => {
});
it('should reject when the script fails to load', async () => {
let capturedScript: HTMLScriptElement | null = null;
// 拦截 append,捕获 script 元素但不插入 DOM,
// 防止 happy-dom v20+ 自动触发 load 事件
const appendSpy = vi
.spyOn(document.head, 'append')
.mockImplementation((...nodes) => {
for (const node of nodes) {
if (node instanceof HTMLScriptElement) {
capturedScript = node;
}
}
});
const promise = loadScript('error.js');
const script = document.querySelector(
'script[src="error.js"]',
) as HTMLScriptElement;
expect(script).toBeTruthy();
appendSpy.mockRestore();
// 模拟加载失败
script.dispatchEvent(new Event('error'));
expect(capturedScript).toBeTruthy();
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
capturedScript!.dispatchEvent(new Event('error'));
await expect(promise).rejects.toThrow('Failed to load script: error.js');
});
it('should handle multiple concurrent calls and only insert one script tag', async () => {
const p1 = loadScript(testJsPath);
const p2 = loadScript(testJsPath);
const script = document.querySelector(
`script[src="${testJsPath}"]`,
) as HTMLScriptElement;
expect(script).toBeTruthy();
// 触发一次 load,两个 promise 都应该 resolve
script.dispatchEvent(new Event('load'));
const p1 = loadScript('/test-script.js');
const p2 = loadScript('/test-script.js');
// happy-dom v20+ auto-fires 'load',两个 promise 都应该 resolve
await expect(p1).resolves.toBeUndefined();
await expect(p2).resolves.toBeUndefined();
// 只插入一次
const scripts = document.head.querySelectorAll(
`script[src="${testJsPath}"]`,
'script[src="/test-script.js"]',
);
expect(scripts).toHaveLength(1);
});

2
packages/@core/preferences/src/config.ts

@ -20,8 +20,8 @@ const defaultPreferences: Preferences = {
defaultHomePath: '/analytics',
dynamicTitle: true,
enableCheckUpdates: true,
enablePreferences: true,
enableCopyPreferences: true,
enablePreferences: true,
enableRefreshToken: false,
enableStickyPreferencesNavigationBar: true,
isMobile: false,

1
packages/@core/ui-kit/form-ui/postcss.config.mjs

@ -1 +0,0 @@
export { default } from '@vben/tailwind-config/postcss';

15
packages/@core/ui-kit/form-ui/src/form-api.ts

@ -75,19 +75,16 @@ export class FormApi {
const defaultState = getDefaultState();
this.store = new Store<VbenFormProps>(
{
this.store = new Store<VbenFormProps>({
...defaultState,
...storeState,
},
{
onUpdate: () => {
});
this.store.subscribe((state) => {
this.prevState = this.state;
this.state = this.store.state;
this.state = state;
this.updateState();
},
},
);
});
this.state = this.store.state;
this.stateHandler = new StateHandler();

4
packages/@core/ui-kit/form-ui/src/form-render/form-field.vue

@ -308,7 +308,7 @@ onUnmounted(() => {
cn(
'flex leading-6',
{
'mr-2 flex-shrink-0 justify-end': !isVertical,
'mr-2 shrink-0 justify-end': !isVertical,
'mb-1 flex-row': isVertical,
},
labelClass,
@ -324,7 +324,7 @@ onUnmounted(() => {
<VbenRenderContent :content="label" />
</template>
</FormLabel>
<div class="flex-auto overflow-hidden p-[1px]">
<div class="flex-auto overflow-hidden p-px">
<div :class="cn('relative flex w-full items-center', wrapperClass)">
<FormControl :class="cn(controlClass)">
<slot

2
packages/@core/ui-kit/form-ui/src/form-render/form.vue

@ -157,7 +157,7 @@ const computedSchema = computed(
...schema.formFieldProps,
},
formItemClass: cn(
'flex-shrink-0',
'shrink-0',
{ hidden },
formItemClass,
resolvedSchemaFormItemClass,

1
packages/@core/ui-kit/form-ui/tailwind.config.mjs

@ -1 +0,0 @@
export { default } from '@vben/tailwind-config';

1
packages/@core/ui-kit/layout-ui/postcss.config.mjs

@ -1 +0,0 @@
export { default } from '@vben/tailwind-config/postcss';

4
packages/@core/ui-kit/layout-ui/src/components/layout-sidebar.vue

@ -307,7 +307,7 @@ onUnmounted(() => {
<aside
ref="asideRef"
:style="style"
class="fixed left-0 top-0 h-full transition-all duration-150"
class="fixed top-0 left-0 h-full transition-all duration-150"
@mouseenter="handleMouseenter"
@mouseleave="handleMouseleave"
>
@ -374,7 +374,7 @@ onUnmounted(() => {
<div
v-if="draggable"
ref="dragBarRef"
class="absolute inset-y-0 -right-[1px] z-1000 w-[2px] cursor-col-resize hover:bg-primary"
class="absolute inset-y-0 -right-px z-1000 w-[2px] cursor-col-resize hover:bg-primary"
@mousedown="handleDragSidebar"
></div>
</aside>

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

@ -10,7 +10,7 @@ function handleCollapsed() {
<template>
<div
class="flex-center absolute bottom-2 left-3 z-10 cursor-pointer rounded-sm bg-accent p-1 text-foreground/60 hover:bg-accent-hover hover:text-foreground"
class="absolute bottom-2 left-3 z-10 flex-center cursor-pointer rounded-sm bg-accent p-1 text-foreground/60 hover:bg-accent-hover hover:text-foreground"
@click.stop="handleCollapsed"
>
<ChevronsRight v-if="collapsed" class="size-4" />

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

@ -10,7 +10,7 @@ function toggleFixed() {
<template>
<div
class="flex-center absolute bottom-2 right-3 z-10 cursor-pointer rounded-sm bg-accent p-[5px] text-foreground/60 transition-all duration-300 hover:bg-accent-hover hover:text-foreground"
class="absolute right-3 bottom-2 z-10 flex-center cursor-pointer rounded-sm bg-accent p-[5px] text-foreground/60 transition-all duration-300 hover:bg-accent-hover hover:text-foreground"
@click="toggleFixed"
>
<PinOff v-if="!expandOnHover" class="size-3.5" />

4
packages/@core/ui-kit/layout-ui/src/vben-layout.vue

@ -275,7 +275,7 @@ const mainStyle = computed(() => {
// tabbar
const tabbarStyle = computed((): CSSProperties => {
let width = '';
let width: string;
let marginLeft = 0;
// tabbar 100%
@ -627,7 +627,7 @@ const idMainContent = ELEMENT_ID_MAIN_CONTENT;
<div
v-if="maskVisible"
:style="maskStyle"
class="fixed left-0 top-0 h-full w-full bg-overlay transition-[background-color] duration-200"
class="fixed top-0 left-0 size-full bg-overlay transition-[background-color] duration-200"
@click="handleClickMask"
></div>
</div>

1
packages/@core/ui-kit/layout-ui/tailwind.config.mjs

@ -1 +0,0 @@
export { default } from '@vben/tailwind-config';

1
packages/@core/ui-kit/menu-ui/package.json

@ -38,6 +38,7 @@
},
"dependencies": {
"@vben-core/composables": "workspace:*",
"@vben-core/design": "workspace:*",
"@vben-core/icons": "workspace:*",
"@vben-core/shadcn-ui": "workspace:*",
"@vben-core/shared": "workspace:*",

1
packages/@core/ui-kit/menu-ui/postcss.config.mjs

@ -1 +0,0 @@
export { default } from '@vben/tailwind-config/postcss';

2
packages/@core/ui-kit/menu-ui/src/components/menu-badge-dot.vue

@ -16,7 +16,7 @@ withDefaults(defineProps<Props>(), {
<span
:class="dotClass"
:style="dotStyle"
class="absolute inline-flex h-full w-full animate-ping rounded-full opacity-75"
class="absolute inline-flex size-full animate-ping rounded-full opacity-75"
>
</span>
<span

4
packages/@core/ui-kit/menu-ui/src/components/normal-menu/normal-menu.vue

@ -60,6 +60,8 @@ function menuIcon(menu: MenuRecordRaw) {
<style lang="scss" scoped>
$namespace: vben;
@reference "@vben-core/design/theme";
.#{$namespace}-normal-menu {
--menu-item-margin-y: 4px;
--menu-item-margin-x: 0px;
@ -129,7 +131,7 @@ $namespace: vben;
.#{$namespace}-normal-menu__name,
.#{$namespace}-normal-menu__icon {
@apply font-semibold text-primary-foreground;
@apply text-primary-foreground font-semibold;
}
}

1
packages/@core/ui-kit/menu-ui/tailwind.config.mjs

@ -1 +0,0 @@
export { default } from '@vben/tailwind-config';

1
packages/@core/ui-kit/popup-ui/postcss.config.mjs

@ -1 +0,0 @@
export { default } from '@vben/tailwind-config/postcss';

2
packages/@core/ui-kit/popup-ui/src/alert/alert.vue

@ -147,7 +147,7 @@ async function handleOpenChange(val: boolean) {
:class="
cn(
containerClass,
'left-0 right-0 mx-auto flex max-h-[80%] flex-col p-0 duration-300 sm:w-[520px] sm:max-w-[80%] sm:rounded-[var(--radius)]',
'inset-x-0 mx-auto flex max-h-[80%] flex-col p-0 duration-300 sm:w-[520px] sm:max-w-[80%] sm:rounded-(--radius)',
{
'border border-border': bordered,
'shadow-3xl': !bordered,

17
packages/@core/ui-kit/popup-ui/src/drawer/__tests__/drawer-api.test.ts

@ -13,21 +13,20 @@ vi.mock('@vben-core/shared/store', () => {
return this._state;
}
private _state: DrawerState;
private subscribers: Array<(state: DrawerState) => void> = [];
private options: any;
constructor(initialState: DrawerState, options: any) {
constructor(initialState: DrawerState) {
this._state = initialState;
this.options = options;
}
batch(cb: () => void) {
cb();
}
setState(fn: (prev: DrawerState) => DrawerState) {
this._state = fn(this._state);
this.options.onUpdate();
this.subscribers.forEach((sub) => sub(this._state));
}
subscribe(fn: (state: DrawerState) => void) {
this.subscribers.push(fn);
return { unsubscribe: () => {} };
}
},
};

19
packages/@core/ui-kit/popup-ui/src/drawer/drawer-api.ts

@ -56,23 +56,18 @@ export class DrawerApi {
title: '',
};
this.store = new Store<DrawerState>(
{
this.store = new Store<DrawerState>({
...defaultState,
...storeState,
},
{
onUpdate: () => {
const state = this.store.state;
if (state?.isOpen === this.state?.isOpen) {
this.state = state;
} else {
});
this.store.subscribe((state) => {
const prevIsOpen = this.state?.isOpen;
this.state = state;
if (state?.isOpen !== prevIsOpen) {
this.api.onOpenChange?.(!!state?.isOpen);
}
},
},
);
});
this.state = this.store.state;
this.api = {
onBeforeClose,

12
packages/@core/ui-kit/popup-ui/src/drawer/drawer.vue

@ -186,8 +186,8 @@ const getForceMount = computed(() => {
:append-to="getAppendTo"
:class="
cn('flex w-[520px] flex-col', drawerClass, {
'!w-full': isMobile || placement === 'bottom' || placement === 'top',
'max-h-[100vh]': placement === 'bottom' || placement === 'top',
'w-full!': isMobile || placement === 'bottom' || placement === 'top',
'max-h-screen': placement === 'bottom' || placement === 'top',
hidden: isClosed,
})
"
@ -210,7 +210,7 @@ const getForceMount = computed(() => {
v-if="showHeader"
:class="
cn(
'!flex flex-row items-center justify-between border-b px-6 py-5',
'flex! flex-row items-center justify-between border-b px-6 py-5',
headerClass,
{
'px-4 py-3': closable,
@ -224,7 +224,7 @@ const getForceMount = computed(() => {
v-if="closable && closeIconPlacement === 'left'"
as-child
:disabled="submitting"
class="ml-[2px] cursor-pointer rounded-full opacity-80 transition-opacity hover:opacity-100 focus:outline-none disabled:pointer-events-none data-[state=open]:bg-secondary"
class="ml-[2px] cursor-pointer rounded-full opacity-80 transition-opacity hover:opacity-100 focus:outline-hidden disabled:pointer-events-none data-[state=open]:bg-secondary"
>
<slot name="close-icon">
<VbenIconButton>
@ -234,7 +234,7 @@ const getForceMount = computed(() => {
</SheetClose>
<Separator
v-if="closable && closeIconPlacement === 'left'"
class="ml-1 mr-2 h-8"
class="mr-2 ml-1 h-8"
decorative
orientation="vertical"
/>
@ -265,7 +265,7 @@ const getForceMount = computed(() => {
v-if="closable && closeIconPlacement === 'right'"
as-child
:disabled="submitting"
class="ml-[2px] cursor-pointer rounded-full opacity-80 transition-opacity hover:opacity-100 focus:outline-none disabled:pointer-events-none data-[state=open]:bg-secondary"
class="ml-[2px] cursor-pointer rounded-full opacity-80 transition-opacity hover:opacity-100 focus:outline-hidden disabled:pointer-events-none data-[state=open]:bg-secondary"
>
<slot name="close-icon">
<VbenIconButton>

17
packages/@core/ui-kit/popup-ui/src/modal/__tests__/modal-api.test.ts

@ -12,21 +12,20 @@ vi.mock('@vben-core/shared/store', () => {
return this._state;
}
private _state: ModalState;
private subscribers: Array<(state: ModalState) => void> = [];
private options: any;
constructor(initialState: ModalState, options: any) {
constructor(initialState: ModalState) {
this._state = initialState;
this.options = options;
}
batch(cb: () => void) {
cb();
}
setState(fn: (prev: ModalState) => ModalState) {
this._state = fn(this._state);
this.options.onUpdate();
this.subscribers.forEach((sub) => sub(this._state));
}
subscribe(fn: (state: ModalState) => void) {
this.subscribers.push(fn);
return { unsubscribe: () => {} };
}
},
};

18
packages/@core/ui-kit/popup-ui/src/modal/modal-api.ts

@ -62,25 +62,19 @@ export class ModalApi {
animationType: 'slide',
};
this.store = new Store<ModalState>(
{
this.store = new Store<ModalState>({
...defaultState,
...storeState,
},
{
onUpdate: () => {
const state = this.store.state;
});
this.store.subscribe((state) => {
// 每次更新状态时,都会调用 onOpenChange 回调函数
if (state?.isOpen === this.state?.isOpen) {
this.state = state;
} else {
const prevIsOpen = this.state?.isOpen;
this.state = state;
if (state?.isOpen !== prevIsOpen) {
this.api.onOpenChange?.(!!state?.isOpen);
}
},
},
);
});
this.state = this.store.state;

9
packages/@core/ui-kit/popup-ui/src/modal/modal.vue

@ -240,14 +240,13 @@ function handleClosed() {
:append-to="getAppendTo"
:class="
cn(
'left-0 right-0 top-[10vh] mx-auto flex max-h-[80%] w-[520px] flex-col p-0',
shouldFullscreen ? 'sm:rounded-none' : 'sm:rounded-[var(--radius)]',
'inset-x-0 top-[10vh] mx-auto flex max-h-[80%] w-[520px] flex-col p-0',
shouldFullscreen ? 'sm:rounded-none' : 'sm:rounded-(--radius)',
modalClass,
{
'border border-border': bordered,
'shadow-3xl': !bordered,
'left-0 top-0 size-full max-h-full !translate-x-0 !translate-y-0':
shouldFullscreen,
'top-0 left-0 size-full max-h-full translate-0!': shouldFullscreen,
'top-1/2': centered && !shouldFullscreen,
'duration-300': !dragging,
hidden: isClosed,
@ -320,7 +319,7 @@ function handleClosed() {
<VbenLoading v-if="showLoading || submitting" spinning />
<VbenIconButton
v-if="fullscreenButton"
class="flex-center absolute right-10 top-3 hidden size-6 rounded-full px-1 text-lg text-foreground/80 opacity-70 transition-opacity hover:bg-accent hover:text-accent-foreground hover:opacity-100 focus:outline-none disabled:pointer-events-none sm:block"
class="absolute top-3 right-10 flex-center hidden size-6 rounded-full px-1 text-lg text-foreground/80 opacity-70 transition-opacity hover:bg-accent hover:text-accent-foreground hover:opacity-100 focus:outline-hidden disabled:pointer-events-none sm:block"
@click="handleFullscreen"
>
<Shrink v-if="fullscreen" class="size-3.5" />

1
packages/@core/ui-kit/popup-ui/tailwind.config.mjs

@ -1 +0,0 @@
export { default } from '@vben/tailwind-config';

3
packages/@core/ui-kit/shadcn-ui/components.json

@ -3,12 +3,11 @@
"style": "new-york",
"typescript": true,
"tailwind": {
"config": "tailwind.config.mjs",
"config": "",
"css": "src/assets/index.css",
"baseColor": "slate",
"cssVariables": true
},
"framework": "vite",
"aliases": {
"components": "@vben-core/shadcn-ui/components",
"utils": "@vben-core/shared/utils"

1
packages/@core/ui-kit/shadcn-ui/package.json

@ -41,6 +41,7 @@
},
"dependencies": {
"@vben-core/composables": "workspace:*",
"@vben-core/design": "workspace:*",
"@vben-core/icons": "workspace:*",
"@vben-core/shared": "workspace:*",
"@vben-core/typings": "workspace:*",

1
packages/@core/ui-kit/shadcn-ui/postcss.config.mjs

@ -1 +0,0 @@
export { default } from '@vben/tailwind-config/postcss';

4
packages/@core/ui-kit/shadcn-ui/src/components/avatar/avatar.vue

@ -60,7 +60,7 @@ const rootStyle = computed(() => {
<div
:class="props.class"
:style="rootStyle"
class="relative flex flex-shrink-0 items-center"
class="relative flex shrink-0 items-center"
>
<Avatar :class="props.class" class="size-full">
<AvatarImage :alt="alt" :src="src" :style="imageStyle" />
@ -69,7 +69,7 @@ const rootStyle = computed(() => {
<span
v-if="dot"
:class="dotClass"
class="absolute bottom-0 right-0 size-3 rounded-full border-2 border-background"
class="border-background absolute right-0 bottom-0 size-3 rounded-full border-2"
>
</span>
</div>

2
packages/@core/ui-kit/shadcn-ui/src/components/back-top/back-top.vue

@ -32,7 +32,7 @@ const { handleClick, visible } = useBackTop(props);
<VbenButton
v-if="visible"
:style="backTopStyle"
class="data z-popup fixed bottom-10 size-10 rounded-full bg-background shadow-float duration-500 hover:bg-heavy dark:bg-accent dark:hover:bg-heavy"
class="data z-popup bg-background shadow-float hover:bg-heavy dark:bg-accent dark:hover:bg-heavy fixed bottom-10 size-10 rounded-full duration-500"
size="icon"
variant="icon"
@click="handleClick"

12
packages/@core/ui-kit/shadcn-ui/src/components/breadcrumb/breadcrumb-background.vue

@ -33,11 +33,11 @@ function handleClick(index: number, path?: string) {
<VbenIcon
v-if="showIcon"
:icon="item.icon"
class="mr-1 size-4 flex-shrink-0"
class="mr-1 size-4 shrink-0"
/>
<span
:class="{
'font-normal text-foreground':
'text-foreground font-normal':
index === breadcrumbs.length - 1,
}"
>{{ item.title }}
@ -50,12 +50,14 @@ function handleClick(index: number, path?: string) {
</ul>
</template>
<style scoped>
@reference "@vben-core/design/theme";
li {
@apply h-7;
}
li a {
@apply relative mr-9 flex h-7 items-center bg-accent py-0 pl-[5px] pr-2 text-[13px] text-muted-foreground;
@apply bg-accent text-muted-foreground relative mr-9 flex h-7 items-center py-0 pr-2 pl-[5px] text-[13px];
}
li a > span {
@ -84,7 +86,7 @@ li:last-child a::after {
li a::before,
li a::after {
@apply absolute top-0 h-0 w-0 border-[.875rem] border-solid border-accent content-[''];
@apply border-accent absolute top-0 h-0 w-0 border-[.875rem] border-solid content-[''];
}
li a::before {
@ -92,7 +94,7 @@ li a::before {
}
li a::after {
@apply left-full border-transparent border-l-accent;
@apply border-l-accent left-full border-transparent;
}
li:not(:last-child) a:hover {

2
packages/@core/ui-kit/shadcn-ui/src/components/button/button.vue

@ -35,7 +35,7 @@ const isDisabled = computed(() => {
>
<LoaderCircle
v-if="loading"
class="text-md mr-2 size-4 flex-shrink-0 animate-spin"
class="text-md mr-2 size-4 shrink-0 animate-spin"
/>
<slot></slot>
</Primitive>

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save