diff --git a/.vscode/settings.json b/.vscode/settings.json index 5640c7928..44a3a8ddc 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,5 @@ { - "tailwindCSS.experimental.configFile": "packages/@core/base/design/src/css/global.css", + "tailwindCSS.experimental.configFile": "internal/tailwind-config/src/theme.css", "tailwindCSS.lint.suggestCanonicalClasses": "ignore", // workbench "workbench.list.smoothScrolling": true, @@ -43,6 +43,7 @@ "oxc.fmt.configPath": "oxfmt.config.ts", "eslint.useFlatConfig": true, "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit", "source.fixAll.oxc": "explicit", "source.fixAll.stylelint": "explicit", "source.organizeImports": "never" diff --git a/apps/web-antd/src/adapter/component/index.ts b/apps/web-antd/src/adapter/component/index.ts index 80d990b76..61760514a 100644 --- a/apps/web-antd/src/adapter/component/index.ts +++ b/apps/web-antd/src/adapter/component/index.ts @@ -14,6 +14,7 @@ import type { import type { Component, Ref } from 'vue'; import type { BaseFormComponentType } from '@vben/common-ui'; +import type { Sortable } from '@vben/hooks'; import type { Recordable } from '@vben/types'; import { @@ -21,6 +22,9 @@ import { defineAsyncComponent, defineComponent, h, + nextTick, + onMounted, + onUnmounted, ref, render, unref, @@ -33,6 +37,7 @@ import { IconPicker, VCropper, } from '@vben/common-ui'; +import { useSortable } from '@vben/hooks'; import { IconifyIcon } from '@vben/icons'; import { $t } from '@vben/locales'; import { isEmpty } from '@vben/utils'; @@ -126,260 +131,261 @@ const withDefaultPlaceholder = ( }); }; -const withPreviewUpload = () => { - // 检查是否为图片文件的辅助函数 - const isImageFile = (file: UploadFile): boolean => { - const imageExtensions = new Set([ - 'bmp', - 'gif', - 'jpeg', - 'jpg', - 'png', - 'svg', - 'webp', - ]); - if (file.url) { - try { - const pathname = new URL(file.url, 'http://localhost').pathname; - const ext = pathname.split('.').pop()?.toLowerCase(); - return ext ? imageExtensions.has(ext) : false; - } catch { - const ext = file.url?.split('.').pop()?.toLowerCase(); - return ext ? imageExtensions.has(ext) : false; - } - } - if (!file.type) { - const ext = file.name?.split('.').pop()?.toLowerCase(); - return ext ? imageExtensions.has(ext) : false; +const IMAGE_EXTENSIONS = new Set([ + 'bmp', + 'gif', + 'jpeg', + 'jpg', + 'png', + 'svg', + 'webp', +]); + +/** + * 检查是否为图片文件 + */ +function isImageFile(file: UploadFile): boolean { + if (file.url) { + try { + const pathname = new URL(file.url, 'http://localhost').pathname; + const ext = pathname.split('.').pop()?.toLowerCase(); + return ext ? IMAGE_EXTENSIONS.has(ext) : false; + } catch { + const ext = file.url?.split('.').pop()?.toLowerCase(); + return ext ? IMAGE_EXTENSIONS.has(ext) : false; } - return file.type.startsWith('image/'); + } + if (!file.type) { + const ext = file.name?.split('.').pop()?.toLowerCase(); + return ext ? IMAGE_EXTENSIONS.has(ext) : false; + } + return file.type.startsWith('image/'); +} + +/** + * 创建默认的上传按钮插槽 + */ +function createDefaultUploadSlots(listType: string, placeholder: string) { + if (listType === 'picture-card') { + return { default: () => placeholder }; + } + return { + default: () => + h( + Button, + { + icon: h(IconifyIcon, { + icon: 'ant-design:upload-outlined', + class: 'mb-1 size-4', + }), + }, + () => placeholder, + ), }; - // 创建默认的上传按钮插槽 - const createDefaultSlotsWithUpload = ( - listType: string, - placeholder: string, - ) => { - switch (listType) { - case 'picture-card': { - return { - default: () => placeholder, - }; - } - default: { - return { - default: () => - h( - Button, - { - icon: h(IconifyIcon, { - icon: 'ant-design:upload-outlined', - class: 'mb-1 size-4', - }), +} + +/** + * 获取文件的 Base64 + */ +function getBase64(file: File): Promise { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.readAsDataURL(file); + reader.addEventListener('load', () => resolve(reader.result as string)); + reader.addEventListener('error', reject); + }); +} + +/** + * 预览图片 + */ +async function previewImage( + file: UploadFile, + visible: Ref, + fileList: Ref, +) { + // 非图片文件直接打开链接 + if (!isImageFile(file)) { + const url = file.url || file.preview; + if (url) { + window.open(url, '_blank'); + } else { + message.error($t('ui.formRules.previewWarning')); + } + return; + } + + const [ImageComponent, PreviewGroupComponent] = await Promise.all([ + Image, + PreviewGroup, + ]); + + // 过滤图片文件并生成预览 + const imageFiles = (unref(fileList) || []).filter((f) => isImageFile(f)); + + for (const imgFile of imageFiles) { + if (!imgFile.url && !imgFile.preview && imgFile.originFileObj) { + imgFile.preview = await getBase64(imgFile.originFileObj); + } + } + + const container = document.createElement('div'); + document.body.append(container); + let isUnmounted = false; + + const currentIndex = imageFiles.findIndex((f) => f.uid === file.uid); + + const PreviewWrapper = { + setup() { + return () => { + if (isUnmounted) return null; + return h( + PreviewGroupComponent, + { + class: 'hidden', + preview: { + visible: visible.value, + current: currentIndex, + onVisibleChange: (value: boolean) => { + visible.value = value; + if (!value) { + setTimeout(() => { + if (!isUnmounted && container) { + isUnmounted = true; + render(null, container); + container.remove(); + } + }, 300); + } }, - () => placeholder, + }, + }, + () => + imageFiles.map((imgFile) => + h(ImageComponent, { + key: imgFile.uid, + src: imgFile.url || imgFile.preview, + }), ), - }; - } - } + ); + }; + }, }; - // 构建预览图片组 - const previewImage = async ( - file: UploadFile, - visible: Ref, - fileList: Ref, - ) => { - // 如果当前文件不是图片,直接打开 - if (!isImageFile(file)) { - if (file.url) { - window.open(file.url, '_blank'); - } else if (file.preview) { - window.open(file.preview, '_blank'); - } else { - message.error($t('ui.formRules.previewWarning')); - } - return; - } - // 对于图片文件,继续使用预览组 - const [ImageComponent, PreviewGroupComponent] = await Promise.all([ - Image, - PreviewGroup, - ]); - - const getBase64 = (file: File) => { - return new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.readAsDataURL(file); - reader.addEventListener('load', () => resolve(reader.result)); - reader.addEventListener('error', (error) => reject(error)); - }); - }; - // 从fileList中过滤出所有图片文件 - const imageFiles = (unref(fileList) || []).filter((element) => - isImageFile(element), - ); - - // 为所有没有预览地址的图片生成预览 - for (const imgFile of imageFiles) { - if (!imgFile.url && !imgFile.preview && imgFile.originFileObj) { - imgFile.preview = (await getBase64(imgFile.originFileObj)) as string; - } - } - const container: HTMLElement | null = document.createElement('div'); + render(h(PreviewWrapper), container); +} + +/** + * 图片裁剪操作 + */ +function cropImage(file: File, aspectRatio: string | undefined) { + return new Promise((resolve, reject) => { + const container = document.createElement('div'); document.body.append(container); - // 用于追踪组件是否已卸载 let isUnmounted = false; + let objectUrl: null | string = null; + + const open = ref(true); + const cropperRef = ref | null>(null); + + const closeModal = () => { + open.value = false; + setTimeout(() => { + if (!isUnmounted && container) { + if (objectUrl) { + URL.revokeObjectURL(objectUrl); + } + isUnmounted = true; + render(null, container); + container.remove(); + } + }, 300); + }; - const PreviewWrapper = { + const CropperWrapper = { setup() { return () => { if (isUnmounted) return null; + if (!objectUrl) { + objectUrl = URL.createObjectURL(file); + } return h( - PreviewGroupComponent, + Modal, { - class: 'hidden', - preview: { - visible: visible.value, - // 设置初始显示的图片索引 - current: imageFiles.findIndex((f) => f.uid === file.uid), - onVisibleChange: (value: boolean) => { - visible.value = value; - if (!value) { - // 延迟清理,确保动画完成 - setTimeout(() => { - if (!isUnmounted && container) { - isUnmounted = true; - render(null, container); - container.remove(); - } - }, 300); + open: open.value, + title: h('div', {}, [ + $t('ui.crop.title'), + h( + 'span', + { + class: `${aspectRatio ? '' : 'hidden'} ml-2 text-sm text-gray-400 font-normal`, + }, + $t('ui.crop.titleTip', [aspectRatio]), + ), + ]), + centered: true, + width: 548, + keyboard: false, + maskClosable: false, + closable: false, + cancelText: $t('common.cancel'), + okText: $t('ui.crop.confirm'), + destroyOnClose: true, + onOk: async () => { + const cropper = cropperRef.value; + if (!cropper) { + reject(new Error('Cropper not found')); + closeModal(); + return; + } + try { + const dataUrl = await cropper.getCropImage(); + if (dataUrl) { + resolve(dataUrl); + } else { + reject(new Error($t('ui.crop.errorTip'))); } - }, + } catch { + reject(new Error($t('ui.crop.errorTip'))); + } finally { + closeModal(); + } + }, + onCancel() { + resolve(''); + closeModal(); }, }, () => - // 渲染所有图片文件 - imageFiles.map((imgFile) => - h(ImageComponent, { - key: imgFile.uid, - src: imgFile.url || imgFile.preview, - }), - ), + h(VCropper, { + ref: (ref: any) => (cropperRef.value = ref), + img: objectUrl as string, + aspectRatio, + }), ); }; }, }; - render(h(PreviewWrapper), container); - }; - - // 图片裁剪操作 - const cropImage = (file: File, aspectRatio: string | undefined) => { - return new Promise((resolve, reject) => { - const container: HTMLElement | null = document.createElement('div'); - document.body.append(container); - - // 用于追踪组件是否已卸载 - let isUnmounted = false; - let objectUrl: null | string = null; - - const open = ref(true); - const cropperRef = ref | null>(null); - - const closeModal = () => { - open.value = false; - // 延迟清理,确保动画完成 - setTimeout(() => { - if (!isUnmounted && container) { - if (objectUrl) { - URL.revokeObjectURL(objectUrl); - } - isUnmounted = true; - render(null, container); - container.remove(); - } - }, 300); - }; - - const CropperWrapper = { - setup() { - return () => { - if (isUnmounted) return null; - if (!objectUrl) { - objectUrl = URL.createObjectURL(file); - } - return h( - Modal, - { - open: open.value, - title: h('div', {}, [ - $t('ui.crop.title'), - h( - 'span', - { - class: `${aspectRatio ? '' : 'hidden'} ml-2 text-sm text-gray-400 font-normal`, - }, - $t('ui.crop.titleTip', [aspectRatio]), - ), - ]), - centered: true, - width: 548, - keyboard: false, - maskClosable: false, - closable: false, - cancelText: $t('common.cancel'), - okText: $t('ui.crop.confirm'), - destroyOnClose: true, - onOk: async () => { - const cropper = cropperRef.value; - if (!cropper) { - reject(new Error('Cropper not found')); - closeModal(); - return; - } - try { - const dataUrl = await cropper.getCropImage(); - resolve(dataUrl); - } catch { - reject(new Error($t('ui.crop.errorTip'))); - } finally { - closeModal(); - } - }, - onCancel() { - resolve(''); - closeModal(); - }, - }, - () => - h(VCropper, { - ref: (ref: any) => (cropperRef.value = ref), - img: objectUrl as string, - aspectRatio, - }), - ); - }; - }, - }; - - render(h(CropperWrapper), container); - }); - }; + render(h(CropperWrapper), container); + }); +} +/** + * 带预览功能的上传组件 + */ +const withPreviewUpload = () => { return defineComponent({ name: Upload.name, emits: ['update:modelValue'], - setup: ( + setup( props: any, { attrs, slots, emit }: { attrs: any; emit: any; slots: any }, - ) => { + ) { const previewVisible = ref(false); - - const placeholder = attrs?.placeholder || $t(`ui.placeholder.upload`); - + const placeholder = attrs?.placeholder || $t('ui.placeholder.upload'); const listType = attrs?.listType || attrs?.['list-type'] || 'text'; - const fileList = ref( attrs?.fileList || attrs?.['file-list'] || [], ); @@ -393,12 +399,14 @@ const withPreviewUpload = () => { file: UploadFile, originFileList: Array, ) => { + // 文件大小限制 if (maxSize.value && (file.size || 0) / 1024 / 1024 > maxSize.value) { message.error($t('ui.formRules.sizeLimit', [maxSize.value])); file.status = 'removed'; return false; } - // 多选或者非图片不唤起裁剪框 + + // 图片裁剪处理 if ( attrs.crop && !attrs.multiple && @@ -406,14 +414,11 @@ const withPreviewUpload = () => { isImageFile(file) ) { file.status = 'removed'; - // antd Upload组件问题 file参数获取的是UploadFile类型对象无法取到File类型 所以通过originFileList[0]获取 const blob = await cropImage(originFileList[0], aspectRatio.value); - return new Promise((resolve, reject) => { - if (!blob) { - return reject(new Error($t('ui.crop.errorTip'))); - } - resolve(blob); - }); + if (!blob) { + throw new Error($t('ui.crop.errorTip')); + } + return blob; } return attrs.beforeUpload?.(file) ?? true; @@ -421,12 +426,9 @@ const withPreviewUpload = () => { const handleChange = (event: UploadChangeParam) => { try { - // 行内写法 handleChange: (event) => {} attrs.handleChange?.(event); - // template写法 @handle-change="(event) => {}" attrs.onHandleChange?.(event); } catch (error) { - // Avoid breaking internal v-model sync on user handler errors console.error(error); } fileList.value = event.fileList.filter( @@ -443,21 +445,88 @@ const withPreviewUpload = () => { await previewImage(file, previewVisible, fileList); }; - const renderUploadButton = (): any => { - const isDisabled = attrs.disabled; - - // 如果禁用,不渲染上传按钮 - if (isDisabled) { - return null; - } - - // 否则渲染默认上传按钮 + const renderUploadButton = () => { + if (attrs.disabled) return null; return isEmpty(slots) - ? createDefaultSlotsWithUpload(listType, placeholder) + ? createDefaultUploadSlots(listType, placeholder) : slots; }; - // 可以监听到表单API设置的值 + // 拖拽排序 + const draggable = computed( + () => (attrs.draggable ?? false) && !attrs.disabled, + ); + const uploadId = `upload-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`; + const sortableInstance = ref(null); + + const styleId = `upload-drag-style-${uploadId}`; + + function injectDragStyle() { + if (!document.querySelector(`[id="${styleId}"]`)) { + const style = document.createElement('style'); + style.id = styleId; + style.textContent = ` + [data-upload-id="${uploadId}"] .ant-upload-list-item { cursor: move; } + [data-upload-id="${uploadId}"] .ant-upload-list-item:hover { box-shadow: 0 2px 8px rgba(0,0,0,0.15); } + `; + document.head.append(style); + } + } + + function removeDragStyle() { + document.querySelector(`[id="${styleId}"]`)?.remove(); + } + + async function initSortable(retryCount = 0) { + if (!draggable.value) return; + + injectDragStyle(); + await nextTick(); + await new Promise((resolve) => setTimeout(resolve, 100)); + + const container = document.querySelector( + `[data-upload-id="${uploadId}"] .ant-upload-list`, + ) as HTMLElement; + + if (!container) { + if (retryCount < 5) { + setTimeout(() => initSortable(retryCount + 1), 200); + } + return; + } + + const { initializeSortable } = useSortable(container, { + animation: 300, + delay: 400, + delayOnTouchOnly: true, + filter: + '.ant-upload-select, .ant-upload-list-item-error, .ant-upload-list-item-uploading', + onEnd: (evt) => { + const { oldIndex, newIndex } = evt; + if ( + oldIndex === undefined || + newIndex === undefined || + oldIndex === newIndex + ) { + return; + } + + const list = [...(fileList.value || [])]; + const [movedItem] = list.splice(oldIndex, 1); + if (movedItem) { + list.splice(newIndex, 0, movedItem); + fileList.value = list; + } + + attrs.onDragSort?.(oldIndex, newIndex); + emit('update:modelValue', fileList.value); + }, + }); + + sortableInstance.value = await initializeSortable(); + } + + // 监听表单值变化 watch( () => attrs.modelValue, (res) => { @@ -465,18 +534,28 @@ const withPreviewUpload = () => { }, ); + onMounted(initSortable); + onUnmounted(() => { + sortableInstance.value?.destroy(); + removeDragStyle(); + }); + return () => h( - Upload, - { - ...props, - ...attrs, - fileList: fileList.value, - beforeUpload: handleBeforeUpload, - onChange: handleChange, - onPreview: handlePreview, - }, - renderUploadButton(), + 'div', + { 'data-upload-id': uploadId, class: 'w-full' }, + h( + Upload, + { + ...props, + ...attrs, + fileList: fileList.value, + beforeUpload: handleBeforeUpload, + onChange: handleChange, + onPreview: handlePreview, + }, + renderUploadButton() as any, + ), ); }, }); diff --git a/cspell.json b/cspell.json index f1071a848..c53d1cc51 100644 --- a/cspell.json +++ b/cspell.json @@ -60,6 +60,7 @@ "tabler", "taze", "tdesign", + "tsdown", "tsgolint", "turborepo", "ui-kit", diff --git a/docs/package.json b/docs/package.json index bee65c99a..c418d797f 100644 --- a/docs/package.json +++ b/docs/package.json @@ -28,6 +28,7 @@ "devDependencies": { "@nolebase/vitepress-plugin-git-changelog": "catalog:", "@tailwindcss/vite": "catalog:", + "@vben/tailwind-config": "workspace:*", "@vben/vite-config": "workspace:*", "@vite-pwa/vitepress": "catalog:", "vitepress": "catalog:", diff --git a/docs/src/en/guide/project/tailwindcss.md b/docs/src/en/guide/project/tailwindcss.md index 98e27ba7c..131fdcbb7 100644 --- a/docs/src/en/guide/project/tailwindcss.md +++ b/docs/src/en/guide/project/tailwindcss.md @@ -6,7 +6,7 @@ The project no longer maintains Tailwind through `tailwind.config.*` files. Theme definitions and scan scope are now managed through CSS and the shared Vite configuration. -- Theme entry: `packages/@core/base/design/src/css/global.css` +- Theme entry: `internal/tailwind-config/src/theme.css` - Vite integration: `internal/vite-config` In `global.css`, you will see the Tailwind CSS v4 directives currently used by the project, such as: @@ -21,7 +21,7 @@ In `global.css`, you will see the Tailwind CSS v4 directives currently used by t The project does not decide whether Tailwind CSS is enabled based on whether a package contains `tailwind.config.mjs`. -Apps and packages share `@vben/vite-config`, which integrates `@tailwindcss/vite`. The Tailwind scan scope is managed centrally in `packages/@core/base/design/src/css/global.css`. +Apps and packages share `@vben/vite-config`, which integrates `@tailwindcss/vite`. The Tailwind scan scope is managed centrally in `@vben/tailwind-config`, backed by `internal/tailwind-config/src/theme.css`. ::: tip Notes on using Tailwind CSS in packages @@ -35,4 +35,4 @@ The project applies a shared handling layer for `@apply` inside Vue single-file - `internal/vite-config/src/plugins/tailwind-reference.ts` -When component styles use `@apply`, the required `@reference` is injected automatically in most cases. +When component styles use `@apply`, `@reference "@vben/tailwind-config/theme"` is injected automatically in most cases. diff --git a/docs/src/guide/project/tailwindcss.md b/docs/src/guide/project/tailwindcss.md index c0f6e7edd..c51bd75a5 100644 --- a/docs/src/guide/project/tailwindcss.md +++ b/docs/src/guide/project/tailwindcss.md @@ -6,7 +6,7 @@ 项目当前不再通过 `tailwind.config.*` 文件维护 Tailwind 配置,主题与扫描范围都统一放在 CSS 与共享 Vite 配置中。 -- 主题入口:`packages/@core/base/design/src/css/global.css` +- 主题入口:`internal/tailwind-config/src/theme.css` - Vite 集成:`internal/vite-config` 在 `global.css` 中你会看到当前项目使用的 Tailwind CSS v4 指令,例如: @@ -21,7 +21,7 @@ 当前项目不会根据某个包下是否存在 `tailwind.config.mjs` 来决定是否启用 Tailwind CSS。 -应用和包统一复用 `@vben/vite-config`,并由该配置接入 `@tailwindcss/vite`。Tailwind 的扫描范围则统一在 `packages/@core/base/design/src/css/global.css` 中维护。 +应用和包统一复用 `@vben/vite-config`,并由该配置接入 `@tailwindcss/vite`。Tailwind 的扫描范围则统一在 `@vben/tailwind-config` 对应的 `internal/tailwind-config/src/theme.css` 中维护。 ::: tip 包使用 Tailwind CSS 的说明 @@ -35,4 +35,4 @@ - `internal/vite-config/src/plugins/tailwind-reference.ts` -当组件样式中使用 `@apply` 时,会自动注入对应的 `@reference`,一般不需要手动补充。 +当组件样式中使用 `@apply` 时,会自动注入 `@reference "@vben/tailwind-config/theme"`,一般不需要手动补充。 diff --git a/internal/lint-configs/eslint-config/build.config.ts b/internal/lint-configs/eslint-config/build.config.ts deleted file mode 100644 index 97e572c56..000000000 --- a/internal/lint-configs/eslint-config/build.config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineBuildConfig } from 'unbuild'; - -export default defineBuildConfig({ - clean: true, - declaration: true, - entries: ['src/index'], -}); diff --git a/internal/lint-configs/eslint-config/package.json b/internal/lint-configs/eslint-config/package.json index db7f699ac..c0ce7615f 100644 --- a/internal/lint-configs/eslint-config/package.json +++ b/internal/lint-configs/eslint-config/package.json @@ -12,7 +12,7 @@ "license": "MIT", "type": "module", "scripts": { - "stub": "pnpm unbuild --stub" + "stub": "pnpm exec tsdown" }, "files": [ "dist" @@ -27,12 +27,10 @@ } }, "dependencies": { - "@vben/oxlint-config": "workspace:*" - }, - "devDependencies": { "@eslint/js": "catalog:", "@typescript-eslint/eslint-plugin": "catalog:", "@typescript-eslint/parser": "catalog:", + "@vben/oxlint-config": "workspace:*", "eslint": "catalog:", "eslint-plugin-jsonc": "catalog:", "eslint-plugin-n": "catalog:", diff --git a/internal/lint-configs/eslint-config/src/configs/node.ts b/internal/lint-configs/eslint-config/src/configs/node.ts index 65f81fd53..95d998673 100644 --- a/internal/lint-configs/eslint-config/src/configs/node.ts +++ b/internal/lint-configs/eslint-config/src/configs/node.ts @@ -17,7 +17,8 @@ export async function node(): Promise { 'error', { allowModules: [ - 'unbuild', + 'tsdown', + 'unplugin-vue', '@vben/vite-config', 'vitest', 'vite', diff --git a/internal/lint-configs/eslint-config/tsdown.config.ts b/internal/lint-configs/eslint-config/tsdown.config.ts new file mode 100644 index 000000000..a7a8ce27f --- /dev/null +++ b/internal/lint-configs/eslint-config/tsdown.config.ts @@ -0,0 +1,16 @@ +import { defineConfig } from 'tsdown'; + +export default defineConfig({ + clean: true, + deps: { + skipNodeModulesBundle: true, + }, + dts: { + resolver: 'tsc', + }, + entry: ['src/index.ts'], + format: ['esm'], + outExtensions: () => ({ + dts: '.d.ts', + }), +}); diff --git a/internal/lint-configs/oxfmt-config/build.config.ts b/internal/lint-configs/oxfmt-config/build.config.ts deleted file mode 100644 index 97e572c56..000000000 --- a/internal/lint-configs/oxfmt-config/build.config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineBuildConfig } from 'unbuild'; - -export default defineBuildConfig({ - clean: true, - declaration: true, - entries: ['src/index'], -}); diff --git a/internal/lint-configs/oxfmt-config/package.json b/internal/lint-configs/oxfmt-config/package.json index 3e016797e..c239c026b 100644 --- a/internal/lint-configs/oxfmt-config/package.json +++ b/internal/lint-configs/oxfmt-config/package.json @@ -12,7 +12,7 @@ "license": "MIT", "type": "module", "scripts": { - "stub": "pnpm unbuild --stub" + "stub": "pnpm exec tsdown" }, "files": [ "dist" diff --git a/internal/lint-configs/oxfmt-config/src/index.ts b/internal/lint-configs/oxfmt-config/src/index.ts index 5f88c1809..edce2504a 100644 --- a/internal/lint-configs/oxfmt-config/src/index.ts +++ b/internal/lint-configs/oxfmt-config/src/index.ts @@ -2,7 +2,7 @@ import { defineConfig as defineOxfmtConfig } from 'oxfmt'; type OxfmtConfig = Parameters[0]; -const oxfmtConfig = defineOxfmtConfig({ +const oxfmtConfig: OxfmtConfig = defineOxfmtConfig({ printWidth: 80, proseWrap: 'never', semi: true, @@ -28,7 +28,7 @@ const oxfmtConfig = defineOxfmtConfig({ ], }); -function defineConfig(config: OxfmtConfig = {}) { +function defineConfig(config: OxfmtConfig = {}): OxfmtConfig { return defineOxfmtConfig({ ...oxfmtConfig, ...config, diff --git a/internal/lint-configs/oxfmt-config/tsdown.config.ts b/internal/lint-configs/oxfmt-config/tsdown.config.ts new file mode 100644 index 000000000..c4f51c5ee --- /dev/null +++ b/internal/lint-configs/oxfmt-config/tsdown.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from 'tsdown'; + +export default defineConfig({ + clean: true, + dts: true, + entry: ['src/index.ts'], + format: ['esm'], + outExtensions: () => ({ + dts: '.d.ts', + }), +}); diff --git a/internal/lint-configs/oxlint-config/build.config.ts b/internal/lint-configs/oxlint-config/build.config.ts deleted file mode 100644 index 97e572c56..000000000 --- a/internal/lint-configs/oxlint-config/build.config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineBuildConfig } from 'unbuild'; - -export default defineBuildConfig({ - clean: true, - declaration: true, - entries: ['src/index'], -}); diff --git a/internal/lint-configs/oxlint-config/package.json b/internal/lint-configs/oxlint-config/package.json index 05d3bedfe..bc20762fe 100644 --- a/internal/lint-configs/oxlint-config/package.json +++ b/internal/lint-configs/oxlint-config/package.json @@ -12,7 +12,7 @@ "license": "MIT", "type": "module", "scripts": { - "stub": "pnpm unbuild --stub" + "stub": "pnpm exec tsdown" }, "files": [ "dist" diff --git a/internal/lint-configs/oxlint-config/src/configs/tailwindcss.ts b/internal/lint-configs/oxlint-config/src/configs/tailwindcss.ts index c24e5c865..5e7bff5ab 100644 --- a/internal/lint-configs/oxlint-config/src/configs/tailwindcss.ts +++ b/internal/lint-configs/oxlint-config/src/configs/tailwindcss.ts @@ -14,7 +14,7 @@ const selectors = [ ]; const settings = { - entryPoint: 'packages/@core/base/design/src/css/global.css', + entryPoint: 'internal/tailwind-config/src/theme.css', selectors, }; diff --git a/internal/lint-configs/oxlint-config/tsdown.config.ts b/internal/lint-configs/oxlint-config/tsdown.config.ts new file mode 100644 index 000000000..c4f51c5ee --- /dev/null +++ b/internal/lint-configs/oxlint-config/tsdown.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from 'tsdown'; + +export default defineConfig({ + clean: true, + dts: true, + entry: ['src/index.ts'], + format: ['esm'], + outExtensions: () => ({ + dts: '.d.ts', + }), +}); diff --git a/internal/node-utils/build.config.ts b/internal/node-utils/build.config.ts deleted file mode 100644 index 97e572c56..000000000 --- a/internal/node-utils/build.config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineBuildConfig } from 'unbuild'; - -export default defineBuildConfig({ - clean: true, - declaration: true, - entries: ['src/index'], -}); diff --git a/internal/node-utils/package.json b/internal/node-utils/package.json index 40a017f14..35892a118 100644 --- a/internal/node-utils/package.json +++ b/internal/node-utils/package.json @@ -12,7 +12,7 @@ "license": "MIT", "type": "module", "scripts": { - "stub": "pnpm unbuild --stub" + "stub": "node ./scripts/build.mjs" }, "files": [ "dist" @@ -22,7 +22,7 @@ "types": "./dist/index.d.ts", "exports": { ".": { - "types": "./src/index.ts", + "types": "./dist/index.d.ts", "import": "./dist/index.mjs", "default": "./dist/index.mjs" } diff --git a/internal/node-utils/scripts/build.mjs b/internal/node-utils/scripts/build.mjs new file mode 100644 index 000000000..65a64e9c4 --- /dev/null +++ b/internal/node-utils/scripts/build.mjs @@ -0,0 +1,40 @@ +import { spawnSync } from 'node:child_process'; + +const pnpmCommand = + process.env.npm_execpath && process.env.npm_execpath.endsWith('.cjs') + ? [process.execPath, process.env.npm_execpath] + : ['pnpm']; + +const steps = [ + ['exec', 'tsdown', '--no-dts'], + [ + 'exec', + 'tsc', + '-p', + 'tsconfig.build.json', + '--emitDeclarationOnly', + '--declaration', + '--outDir', + 'dist', + ], +]; + +for (const args of steps) { + const [command, ...commandArgs] = pnpmCommand; + let cmd = command; + if (cmd.includes(' ')) { + cmd = `"${command}"`; + } + const result = spawnSync(cmd, [...commandArgs, ...args], { + shell: true, + stdio: 'inherit', + }); + + if (result.error) { + throw result.error; + } + + if (result.status !== 0) { + process.exit(result.status ?? 1); + } +} diff --git a/internal/node-utils/src/date.ts b/internal/node-utils/src/date.ts index d36572d97..7b11583f3 100644 --- a/internal/node-utils/src/date.ts +++ b/internal/node-utils/src/date.ts @@ -1,6 +1,6 @@ import dayjs from 'dayjs'; -import timezone from 'dayjs/plugin/timezone'; -import utc from 'dayjs/plugin/utc'; +import timezone from 'dayjs/plugin/timezone.js'; +import utc from 'dayjs/plugin/utc.js'; dayjs.extend(utc); dayjs.extend(timezone); diff --git a/internal/node-utils/src/monorepo.ts b/internal/node-utils/src/monorepo.ts index b6373e78b..33246c683 100644 --- a/internal/node-utils/src/monorepo.ts +++ b/internal/node-utils/src/monorepo.ts @@ -1,21 +1,31 @@ -import { dirname } from 'node:path'; +import type { Package } from '@manypkg/get-packages'; -import { - getPackages as getPackagesFunc, - getPackagesSync as getPackagesSyncFunc, -} from '@manypkg/get-packages'; -import { findUpSync } from 'find-up'; +import { existsSync } from 'node:fs'; +import { dirname, join, resolve } from 'node:path'; + +import * as manypkg from '@manypkg/get-packages'; +const { getPackages: getPackagesFunc, getPackagesSync: getPackagesSyncFunc } = + manypkg; /** * 查找大仓的根目录 * @param cwd */ function findMonorepoRoot(cwd: string = process.cwd()) { - const lockFile = findUpSync('pnpm-lock.yaml', { - cwd, - type: 'file', - }); - return dirname(lockFile || ''); + let currentDir = resolve(cwd); + + while (true) { + if (existsSync(join(currentDir, 'pnpm-lock.yaml'))) { + return currentDir; + } + + const parentDir = dirname(currentDir); + if (parentDir === currentDir) { + return ''; + } + + currentDir = parentDir; + } } /** @@ -40,7 +50,7 @@ async function getPackages() { */ async function getPackage(pkgName: string) { const { packages } = await getPackages(); - return packages.find((pkg) => pkg.packageJson.name === pkgName); + return packages.find((pkg: Package) => pkg.packageJson.name === pkgName); } export { findMonorepoRoot, getPackage, getPackages, getPackagesSync }; diff --git a/internal/node-utils/tsconfig.build.json b/internal/node-utils/tsconfig.build.json new file mode 100644 index 000000000..b62e0ab6b --- /dev/null +++ b/internal/node-utils/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": false + }, + "exclude": ["node_modules", "src/__tests__"] +} diff --git a/internal/node-utils/tsdown.config.ts b/internal/node-utils/tsdown.config.ts new file mode 100644 index 000000000..4a607e00c --- /dev/null +++ b/internal/node-utils/tsdown.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'tsdown'; + +export default defineConfig({ + clean: false, + deps: { + skipNodeModulesBundle: true, + }, + entry: ['src/index.ts'], + format: ['esm'], +}); diff --git a/internal/tailwind-config/package.json b/internal/tailwind-config/package.json new file mode 100644 index 000000000..6701907c7 --- /dev/null +++ b/internal/tailwind-config/package.json @@ -0,0 +1,38 @@ +{ + "name": "@vben/tailwind-config", + "version": "5.7.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", + "files": [ + "src" + ], + "sideEffects": [ + "**/*.css" + ], + "main": "./src/index.ts", + "module": "./src/index.ts", + "types": "./src/index.ts", + "exports": { + ".": { + "types": "./src/index.ts", + "default": "./src/index.ts" + }, + "./theme": { + "default": "./src/theme.css" + } + }, + "dependencies": { + "@iconify/tailwind4": "catalog:", + "@tailwindcss/typography": "catalog:", + "tailwindcss": "catalog:", + "tw-animate-css": "catalog:" + } +} diff --git a/internal/tailwind-config/src/index.ts b/internal/tailwind-config/src/index.ts new file mode 100644 index 000000000..5b1d5b4cf --- /dev/null +++ b/internal/tailwind-config/src/index.ts @@ -0,0 +1 @@ +import './theme.css'; diff --git a/internal/tailwind-config/src/theme.css b/internal/tailwind-config/src/theme.css new file mode 100644 index 000000000..c212dc66e --- /dev/null +++ b/internal/tailwind-config/src/theme.css @@ -0,0 +1,569 @@ +@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 *)); + +/* Explicitly pin Tailwind v4 dynamic spacing for classes like w-150/h-55. */ +@theme { + --spacing: 0.25rem; +} + +@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 outline-ring/50; + + box-sizing: border-box; + border-style: solid; + border-width: 0; + } + + html { + @apply text-foreground bg-background font-sans; + + scroll-behavior: smooth; + font-size: var(--font-size-base, 16px); + font-variation-settings: normal; + font-synthesis-weight: none; + line-height: 1.15; + text-rendering: optimizelegibility; + text-size-adjust: 100%; + -webkit-tap-highlight-color: transparent; + } + + #app, + body, + html { + @apply size-full; + } + + body { + min-height: 100vh; + } + + a, + a:active, + a:hover, + a:link, + a:visited { + @apply no-underline; + } + + ::view-transition-new(root), + ::view-transition-old(root) { + @apply animate-none mix-blend-normal; + } + + ::view-transition-old(root) { + @apply z-1; + } + + ::view-transition-new(root) { + @apply z-2147483646; + } + + html.dark::view-transition-old(root) { + @apply z-2147483646; + } + + html.dark::view-transition-new(root) { + @apply z-1; + } + + input::placeholder, + textarea::placeholder { + @apply opacity-100; + } + + input[type='number']::-webkit-inner-spin-button, + input[type='number']::-webkit-outer-spin-button { + @apply m-0 appearance-none; + } + + /* Only adjust scrollbar for non-macOS */ + html:not([data-platform='macOs']) { + ::-webkit-scrollbar { + @apply h-2.5 w-2.5; + } + + ::-webkit-scrollbar-thumb { + @apply bg-border rounded-sm border-none; + } + + ::-webkit-scrollbar-track { + @apply rounded-sm border-none bg-transparent shadow-none; + } + + ::-webkit-scrollbar-button { + @apply hidden; + } + } +} + +/* Custom utilities (v4 @utility syntax) */ +@utility flex-center { + display: flex; + align-items: center; + justify-content: 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-1; +} + +.outline-box::after { + @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-2; +} + +.outline-box.outline-box-active::after { + display: none; +} + +.outline-box:not(.outline-box-active):hover::after { + @apply outline-primary top-0 left-0 h-full w-full p-1 opacity-100; +} + +.vben-link { + @apply text-primary hover:text-primary-hover active:text-primary-active cursor-pointer; +} + +.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 { + @apply invert; +} + +html.grayscale-mode { + @apply grayscale; +} diff --git a/internal/vite-config/build.config.ts b/internal/vite-config/build.config.ts deleted file mode 100644 index 97e572c56..000000000 --- a/internal/vite-config/build.config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineBuildConfig } from 'unbuild'; - -export default defineBuildConfig({ - clean: true, - declaration: true, - entries: ['src/index'], -}); diff --git a/internal/vite-config/package.json b/internal/vite-config/package.json index d8e87d11b..b420f14fa 100644 --- a/internal/vite-config/package.json +++ b/internal/vite-config/package.json @@ -12,7 +12,7 @@ "license": "MIT", "type": "module", "scripts": { - "stub": "pnpm unbuild --stub" + "stub": "pnpm exec tsdown" }, "files": [ "dist" @@ -30,6 +30,7 @@ "@intlify/unplugin-vue-i18n": "catalog:", "@jspm/generator": "catalog:", "@tailwindcss/vite": "catalog:", + "@vben/node-utils": "workspace:*", "archiver": "catalog:", "cheerio": "catalog:", "get-port": "catalog:", @@ -43,18 +44,16 @@ "@pnpm/workspace.read-manifest": "catalog:", "@types/archiver": "catalog:", "@types/html-minifier-terser": "catalog:", - "@vben/node-utils": "workspace:*", "@vitejs/plugin-vue": "catalog:", "@vitejs/plugin-vue-jsx": "catalog:", "dayjs": "catalog:", "dotenv": "catalog:", - "rollup": "catalog:", "rollup-plugin-visualizer": "catalog:", "sass": "catalog:", "sass-embedded": "catalog:", + "unplugin-dts": "catalog:", "vite": "catalog:", "vite-plugin-compression": "catalog:", - "vite-plugin-dts": "catalog:", "vite-plugin-html": "catalog:", "vite-plugin-lazy-import": "catalog:" } diff --git a/internal/vite-config/src/plugins/index.ts b/internal/vite-config/src/plugins/index.ts index 114d4d837..9d3ba3f1c 100644 --- a/internal/vite-config/src/plugins/index.ts +++ b/internal/vite-config/src/plugins/index.ts @@ -12,8 +12,8 @@ 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'; +import viteDtsPlugin from 'unplugin-dts/vite'; import viteCompressPlugin from 'vite-plugin-compression'; -import viteDtsPlugin from 'vite-plugin-dts'; import { createHtmlPlugin as viteHtmlPlugin } from 'vite-plugin-html'; import { VitePWA } from 'vite-plugin-pwa'; import viteVueDevTools from 'vite-plugin-vue-devtools'; @@ -231,12 +231,13 @@ async function loadLibraryPlugins( // 单独取,否则commonOptions拿不到 const isBuild = options.isBuild; const { dts, ...commonOptions } = options; + const dtsOptions = typeof dts === 'object' ? dts : undefined; const commonPlugins = await loadCommonPlugins(commonOptions); return await loadConditionPlugins([ ...commonPlugins, { condition: isBuild && !!dts, - plugins: () => [viteDtsPlugin({ logLevel: 'error' })], + plugins: () => [viteDtsPlugin(dtsOptions)], }, ]); } diff --git a/internal/vite-config/src/plugins/tailwind-reference.ts b/internal/vite-config/src/plugins/tailwind-reference.ts index 8c3185125..86460494c 100644 --- a/internal/vite-config/src/plugins/tailwind-reference.ts +++ b/internal/vite-config/src/plugins/tailwind-reference.ts @@ -1,6 +1,6 @@ import type { Plugin } from 'vite'; -const REFERENCE_LINE = '@reference "@vben-core/design/theme";\n'; +const REFERENCE_LINE = '@reference "@vben/tailwind-config/theme";\n'; /** * Auto-inject @reference into Vue SFC