From 20a38685940a8f05cefce2bc53880190308b8cc1 Mon Sep 17 00:00:00 2001 From: Vben Date: Sun, 25 Aug 2024 23:40:52 +0800 Subject: [PATCH] feat: add modal and drawer components and examples (#4229) * feat: add modal component * feat: add drawer component * feat: apply new modal and drawer components to the layout * chore: typo * feat: add some unit tests --- .vscode/settings.json | 3 +- apps/backend-mock/utils/jwt-utils.ts | 2 +- cspell.json | 6 +- .../src/{ => __tests__}/hash.test.ts | 2 +- .../src/{ => __tests__}/path.test.ts | 2 +- .../default-loading-antd.html | 9 +- packages/@core/base/icons/src/lucide.ts | 3 + .../{ => __tests__}/storage-manager.test.ts | 2 +- .../src/color/{ => __tests__}/convert.test.ts | 2 +- .../src/utils/{ => __tests__}/diff.test.ts | 2 +- .../src/utils/{ => __tests__}/dom.test.ts | 2 +- .../utils/{ => __tests__}/inference.test.ts | 57 ++++- .../src/utils/{ => __tests__}/letter.test.ts | 41 +++- .../src/utils/{ => __tests__}/tree.test.ts | 2 +- .../src/utils/{ => __tests__}/unique.test.ts | 2 +- .../update-css-variables.test.ts | 2 +- .../src/utils/{ => __tests__}/window.test.ts | 2 +- .../@core/base/shared/src/utils/inference.ts | 37 ++- .../@core/base/shared/src/utils/letter.ts | 17 +- .../src/{ => __tests__}/use-sortable.test.ts | 3 +- packages/@core/composables/src/index.ts | 1 + .../composables/src/use-priority-value.ts | 47 ++++ .../@core/preferences/src/use-preferences.ts | 9 - .../@core/ui-kit/popup-ui/build.config.ts | 21 ++ packages/@core/ui-kit/popup-ui/package.json | 47 ++++ .../@core/ui-kit/popup-ui/postcss.config.mjs | 1 + .../src/drawer/__tests__/drawer-api.test.ts | 113 +++++++++ .../ui-kit/popup-ui/src/drawer/drawer-api.ts | 123 ++++++++++ .../ui-kit/popup-ui/src/drawer/drawer.ts | 93 +++++++ .../ui-kit/popup-ui/src/drawer/drawer.vue | 141 +++++++++++ .../@core/ui-kit/popup-ui/src/drawer/index.ts | 3 + .../ui-kit/popup-ui/src/drawer/use-drawer.ts | 105 ++++++++ packages/@core/ui-kit/popup-ui/src/index.ts | 2 + .../src/modal/__tests__/modal-api.test.ts | 112 +++++++++ .../@core/ui-kit/popup-ui/src/modal/index.ts | 3 + .../ui-kit/popup-ui/src/modal/modal-api.ts | 134 ++++++++++ .../@core/ui-kit/popup-ui/src/modal/modal.ts | 123 ++++++++++ .../@core/ui-kit/popup-ui/src/modal/modal.vue | 231 ++++++++++++++++++ .../popup-ui/src/modal/use-modal-draggable.ts | 148 +++++++++++ .../ui-kit/popup-ui/src/modal/use-modal.ts | 101 ++++++++ .../@core/ui-kit/popup-ui/tailwind.config.mjs | 1 + packages/@core/ui-kit/popup-ui/tsconfig.json | 6 + .../components/alert-dialog/alert-dialog.vue | 62 ----- .../src/components/alert-dialog/index.ts | 1 - .../ui-kit/shadcn-ui/src/components/index.ts | 3 - .../shadcn-ui/src/components/sheet/index.ts | 1 - .../shadcn-ui/src/components/sheet/sheet.vue | 113 --------- .../shadcn-ui/src/components/spinner/index.ts | 1 + .../src/components/spinner/loading.vue | 137 +++++++++++ .../src/components/spinner/spinner.vue | 32 ++- .../ui/alert-dialog/AlertDialog.vue | 19 -- .../ui/alert-dialog/AlertDialogAction.vue | 28 --- .../ui/alert-dialog/AlertDialogCancel.vue | 30 --- .../ui/alert-dialog/AlertDialogContent.vue | 46 ---- .../alert-dialog/AlertDialogDescription.vue | 29 --- .../ui/alert-dialog/AlertDialogFooter.vue | 22 -- .../ui/alert-dialog/AlertDialogHeader.vue | 17 -- .../ui/alert-dialog/AlertDialogTitle.vue | 26 -- .../ui/alert-dialog/AlertDialogTrigger.vue | 11 - .../src/components/ui/alert-dialog/index.ts | 9 - .../components/ui/dialog/DialogContent.vue | 21 +- .../src/components/ui/sheet/sheet.ts | 6 +- packages/effects/common-ui/package.json | 1 + .../ellipsis-text/ellipsis-text.vue | 70 +++--- .../effects/common-ui/src/components/index.ts | 1 + .../ui/authentication/login-expired-modal.vue | 63 ++--- packages/effects/layouts/package.json | 1 + .../widgets/global-search/global-search.vue | 121 +++++---- .../widgets/global-search/search-panel.vue | 6 +- .../widgets/lock-screen/lock-screen-modal.vue | 100 ++++---- .../blocks/shortcut-keys/global.vue | 6 +- ...ences-sheet.vue => preferences-drawer.vue} | 34 +-- .../src/widgets/preferences/preferences.vue | 30 ++- .../widgets/user-dropdown/user-dropdown.vue | 73 +++--- .../{ => __tests__}/find-menu-by-path.test.ts | 2 +- .../{ => __tests__}/generate-menus.test.ts | 2 +- .../generate-routes-frontend.test.ts | 2 +- .../merge-route-modules.test.ts | 4 +- playground/src/locales/langs/en-US.json | 6 + playground/src/locales/langs/zh-CN.json | 6 + .../src/router/routes/modules/examples.ts | 20 +- .../examples/drawer/auto-height-demo.vue | 40 +++ .../src/views/examples/drawer/base-demo.vue | 32 +++ .../views/examples/drawer/dynamic-demo.vue | 31 +++ .../src/views/examples/drawer/index.vue | 90 +++++++ .../examples/drawer/shared-data-demo.vue | 29 +++ .../src/views/examples/ellipsis/index.vue | 2 +- .../views/examples/modal/auto-height-demo.vue | 40 +++ .../src/views/examples/modal/base-demo.vue | 28 +++ .../src/views/examples/modal/drag-demo.vue | 19 ++ .../src/views/examples/modal/dynamic-demo.vue | 41 ++++ playground/src/views/examples/modal/index.vue | 104 ++++++++ .../views/examples/modal/shared-data-demo.vue | 29 +++ pnpm-lock.yaml | 28 ++- scripts/vsh/src/check-circular/index.ts | 1 + vben-admin.code-workspace | 4 + 96 files changed, 2700 insertions(+), 743 deletions(-) rename internal/node-utils/src/{ => __tests__}/hash.test.ts (97%) rename internal/node-utils/src/{ => __tests__}/path.test.ts (98%) rename packages/@core/base/shared/src/cache/{ => __tests__}/storage-manager.test.ts (98%) rename packages/@core/base/shared/src/color/{ => __tests__}/convert.test.ts (98%) rename packages/@core/base/shared/src/utils/{ => __tests__}/diff.test.ts (98%) rename packages/@core/base/shared/src/utils/{ => __tests__}/dom.test.ts (97%) rename packages/@core/base/shared/src/utils/{ => __tests__}/inference.test.ts (63%) rename packages/@core/base/shared/src/utils/{ => __tests__}/letter.test.ts (65%) rename packages/@core/base/shared/src/utils/{ => __tests__}/tree.test.ts (98%) rename packages/@core/base/shared/src/utils/{ => __tests__}/unique.test.ts (97%) rename packages/@core/base/shared/src/utils/{ => __tests__}/update-css-variables.test.ts (94%) rename packages/@core/base/shared/src/utils/{ => __tests__}/window.test.ts (89%) rename packages/@core/composables/src/{ => __tests__}/use-sortable.test.ts (92%) create mode 100644 packages/@core/composables/src/use-priority-value.ts create mode 100644 packages/@core/ui-kit/popup-ui/build.config.ts create mode 100644 packages/@core/ui-kit/popup-ui/package.json create mode 100644 packages/@core/ui-kit/popup-ui/postcss.config.mjs create mode 100644 packages/@core/ui-kit/popup-ui/src/drawer/__tests__/drawer-api.test.ts create mode 100644 packages/@core/ui-kit/popup-ui/src/drawer/drawer-api.ts create mode 100644 packages/@core/ui-kit/popup-ui/src/drawer/drawer.ts create mode 100644 packages/@core/ui-kit/popup-ui/src/drawer/drawer.vue create mode 100644 packages/@core/ui-kit/popup-ui/src/drawer/index.ts create mode 100644 packages/@core/ui-kit/popup-ui/src/drawer/use-drawer.ts create mode 100644 packages/@core/ui-kit/popup-ui/src/index.ts create mode 100644 packages/@core/ui-kit/popup-ui/src/modal/__tests__/modal-api.test.ts create mode 100644 packages/@core/ui-kit/popup-ui/src/modal/index.ts create mode 100644 packages/@core/ui-kit/popup-ui/src/modal/modal-api.ts create mode 100644 packages/@core/ui-kit/popup-ui/src/modal/modal.ts create mode 100644 packages/@core/ui-kit/popup-ui/src/modal/modal.vue create mode 100644 packages/@core/ui-kit/popup-ui/src/modal/use-modal-draggable.ts create mode 100644 packages/@core/ui-kit/popup-ui/src/modal/use-modal.ts create mode 100644 packages/@core/ui-kit/popup-ui/tailwind.config.mjs create mode 100644 packages/@core/ui-kit/popup-ui/tsconfig.json delete mode 100644 packages/@core/ui-kit/shadcn-ui/src/components/alert-dialog/alert-dialog.vue delete mode 100644 packages/@core/ui-kit/shadcn-ui/src/components/alert-dialog/index.ts delete mode 100644 packages/@core/ui-kit/shadcn-ui/src/components/sheet/index.ts delete mode 100644 packages/@core/ui-kit/shadcn-ui/src/components/sheet/sheet.vue create mode 100644 packages/@core/ui-kit/shadcn-ui/src/components/spinner/loading.vue delete mode 100644 packages/@core/ui-kit/shadcn-ui/src/components/ui/alert-dialog/AlertDialog.vue delete mode 100644 packages/@core/ui-kit/shadcn-ui/src/components/ui/alert-dialog/AlertDialogAction.vue delete mode 100644 packages/@core/ui-kit/shadcn-ui/src/components/ui/alert-dialog/AlertDialogCancel.vue delete mode 100644 packages/@core/ui-kit/shadcn-ui/src/components/ui/alert-dialog/AlertDialogContent.vue delete mode 100644 packages/@core/ui-kit/shadcn-ui/src/components/ui/alert-dialog/AlertDialogDescription.vue delete mode 100644 packages/@core/ui-kit/shadcn-ui/src/components/ui/alert-dialog/AlertDialogFooter.vue delete mode 100644 packages/@core/ui-kit/shadcn-ui/src/components/ui/alert-dialog/AlertDialogHeader.vue delete mode 100644 packages/@core/ui-kit/shadcn-ui/src/components/ui/alert-dialog/AlertDialogTitle.vue delete mode 100644 packages/@core/ui-kit/shadcn-ui/src/components/ui/alert-dialog/AlertDialogTrigger.vue delete mode 100644 packages/@core/ui-kit/shadcn-ui/src/components/ui/alert-dialog/index.ts rename packages/effects/layouts/src/widgets/preferences/{preferences-sheet.vue => preferences-drawer.vue} (94%) rename packages/utils/src/helpers/{ => __tests__}/find-menu-by-path.test.ts (96%) rename packages/utils/src/helpers/{ => __tests__}/generate-menus.test.ts (98%) rename packages/utils/src/helpers/{ => __tests__}/generate-routes-frontend.test.ts (98%) rename packages/utils/src/helpers/{ => __tests__}/merge-route-modules.test.ts (93%) create mode 100644 playground/src/views/examples/drawer/auto-height-demo.vue create mode 100644 playground/src/views/examples/drawer/base-demo.vue create mode 100644 playground/src/views/examples/drawer/dynamic-demo.vue create mode 100644 playground/src/views/examples/drawer/index.vue create mode 100644 playground/src/views/examples/drawer/shared-data-demo.vue create mode 100644 playground/src/views/examples/modal/auto-height-demo.vue create mode 100644 playground/src/views/examples/modal/base-demo.vue create mode 100644 playground/src/views/examples/modal/drag-demo.vue create mode 100644 playground/src/views/examples/modal/dynamic-demo.vue create mode 100644 playground/src/views/examples/modal/index.vue create mode 100644 playground/src/views/examples/modal/shared-data-demo.vue diff --git a/.vscode/settings.json b/.vscode/settings.json index cafc2857..66f3fdaa 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -168,9 +168,10 @@ "i18n-ally.localesPaths": [ "packages/locales/src/langs", - "playground/src/langs", + "playground/src/locales/langs", "apps/*/src/locales/langs" ], + "i18n-ally.pathMatcher": "{locale}.json", "i18n-ally.enabledParsers": ["json", "ts", "js", "yaml"], "i18n-ally.sourceLanguage": "en", "i18n-ally.displayLanguage": "zh-CN", diff --git a/apps/backend-mock/utils/jwt-utils.ts b/apps/backend-mock/utils/jwt-utils.ts index 0ee13da6..8cfc6843 100644 --- a/apps/backend-mock/utils/jwt-utils.ts +++ b/apps/backend-mock/utils/jwt-utils.ts @@ -14,7 +14,7 @@ export interface UserPayload extends UserInfo { } export function generateAccessToken(user: UserInfo) { - return jwt.sign(user, ACCESS_TOKEN_SECRET, { expiresIn: '1d' }); + return jwt.sign(user, ACCESS_TOKEN_SECRET, { expiresIn: '7d' }); } export function generateRefreshToken(user: UserInfo) { diff --git a/cspell.json b/cspell.json index 92cf497a..60020248 100644 --- a/cspell.json +++ b/cspell.json @@ -37,6 +37,7 @@ "astro", "ui-kit", "styl", + "vnode", "nocheck", "prefixs", "vitepress", @@ -53,6 +54,9 @@ "**/*-dist/**", "**/icons/**", "pnpm-lock.yaml", - "**/*.log" + "**/*.log", + "**/*.test.ts", + "**/*.spec.ts", + "**/__tests__/**" ] } diff --git a/internal/node-utils/src/hash.test.ts b/internal/node-utils/src/__tests__/hash.test.ts similarity index 97% rename from internal/node-utils/src/hash.test.ts rename to internal/node-utils/src/__tests__/hash.test.ts index 8b302f8f..38513060 100644 --- a/internal/node-utils/src/hash.test.ts +++ b/internal/node-utils/src/__tests__/hash.test.ts @@ -2,7 +2,7 @@ import { createHash } from 'node:crypto'; import { describe, expect, it } from 'vitest'; -import { generatorContentHash } from './hash'; +import { generatorContentHash } from '../hash'; describe('generatorContentHash', () => { it('should generate an MD5 hash for the content', () => { diff --git a/internal/node-utils/src/path.test.ts b/internal/node-utils/src/__tests__/path.test.ts similarity index 98% rename from internal/node-utils/src/path.test.ts rename to internal/node-utils/src/__tests__/path.test.ts index 4b550b4f..3bab5a16 100644 --- a/internal/node-utils/src/path.test.ts +++ b/internal/node-utils/src/__tests__/path.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest'; -import { toPosixPath } from './path'; +import { toPosixPath } from '../path'; describe('toPosixPath', () => { // 测试 Windows 风格路径到 POSIX 风格路径的转换 diff --git a/internal/vite-config/src/plugins/inject-app-loading/default-loading-antd.html b/internal/vite-config/src/plugins/inject-app-loading/default-loading-antd.html index 459f863f..20a21fb7 100644 --- a/internal/vite-config/src/plugins/inject-app-loading/default-loading-antd.html +++ b/internal/vite-config/src/plugins/inject-app-loading/default-loading-antd.html @@ -34,13 +34,6 @@ transition: all 0.6s ease-out; } - .loading .dots { - display: flex; - align-items: center; - justify-content: center; - padding: 98px; - } - .loading .title { margin-top: 36px; font-size: 30px; @@ -109,6 +102,6 @@ }
- +
<%= VITE_APP_TITLE %>
diff --git a/packages/@core/base/icons/src/lucide.ts b/packages/@core/base/icons/src/lucide.ts index 02d96f17..22de4a15 100644 --- a/packages/@core/base/icons/src/lucide.ts +++ b/packages/@core/base/icons/src/lucide.ts @@ -20,12 +20,14 @@ export { CornerDownLeft, Disc as IconDefault, Ellipsis, + Expand, ExternalLink, Eye, EyeOff, FoldHorizontal, Fullscreen, Github, + Info, InspectionPanel, Languages, LoaderCircle, @@ -46,6 +48,7 @@ export { Search, SearchX, Settings, + Shrink, Sun, SunMoon, SwatchBook, diff --git a/packages/@core/base/shared/src/cache/storage-manager.test.ts b/packages/@core/base/shared/src/cache/__tests__/storage-manager.test.ts similarity index 98% rename from packages/@core/base/shared/src/cache/storage-manager.test.ts rename to packages/@core/base/shared/src/cache/__tests__/storage-manager.test.ts index 915af72f..a5abe5a7 100644 --- a/packages/@core/base/shared/src/cache/storage-manager.test.ts +++ b/packages/@core/base/shared/src/cache/__tests__/storage-manager.test.ts @@ -1,6 +1,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { StorageManager } from './storage-manager'; +import { StorageManager } from '../storage-manager'; describe('storageManager', () => { let storageManager: StorageManager; diff --git a/packages/@core/base/shared/src/color/convert.test.ts b/packages/@core/base/shared/src/color/__tests__/convert.test.ts similarity index 98% rename from packages/@core/base/shared/src/color/convert.test.ts rename to packages/@core/base/shared/src/color/__tests__/convert.test.ts index ee174439..fc4256c5 100644 --- a/packages/@core/base/shared/src/color/convert.test.ts +++ b/packages/@core/base/shared/src/color/__tests__/convert.test.ts @@ -5,7 +5,7 @@ import { convertToHslCssVar, convertToRgb, isValidColor, -} from './convert'; +} from '../convert'; describe('color conversion functions', () => { it('should correctly convert color to HSL format', () => { diff --git a/packages/@core/base/shared/src/utils/diff.test.ts b/packages/@core/base/shared/src/utils/__tests__/diff.test.ts similarity index 98% rename from packages/@core/base/shared/src/utils/diff.test.ts rename to packages/@core/base/shared/src/utils/__tests__/diff.test.ts index c858a3bf..cb3227b5 100644 --- a/packages/@core/base/shared/src/utils/diff.test.ts +++ b/packages/@core/base/shared/src/utils/__tests__/diff.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { diff } from './diff'; +import { diff } from '../diff'; describe('diff function', () => { it('should return an empty object when comparing identical objects', () => { diff --git a/packages/@core/base/shared/src/utils/dom.test.ts b/packages/@core/base/shared/src/utils/__tests__/dom.test.ts similarity index 97% rename from packages/@core/base/shared/src/utils/dom.test.ts rename to packages/@core/base/shared/src/utils/__tests__/dom.test.ts index 66e6466b..c3c558fc 100644 --- a/packages/@core/base/shared/src/utils/dom.test.ts +++ b/packages/@core/base/shared/src/utils/__tests__/dom.test.ts @@ -1,6 +1,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { getElementVisibleRect } from './dom'; // 假设函数位于 utils.ts 中 +import { getElementVisibleRect } from '../dom'; // 假设函数位于 utils.ts 中 describe('getElementVisibleRect', () => { // 设置浏览器视口尺寸的 mock diff --git a/packages/@core/base/shared/src/utils/inference.test.ts b/packages/@core/base/shared/src/utils/__tests__/inference.test.ts similarity index 63% rename from packages/@core/base/shared/src/utils/inference.test.ts rename to packages/@core/base/shared/src/utils/__tests__/inference.test.ts index 3d613d9a..bfbcf0a1 100644 --- a/packages/@core/base/shared/src/utils/inference.test.ts +++ b/packages/@core/base/shared/src/utils/__tests__/inference.test.ts @@ -1,12 +1,13 @@ import { describe, expect, it } from 'vitest'; import { + getFirstNonNullOrUndefined, isEmpty, isHttpUrl, isObject, isUndefined, isWindow, -} from './inference'; +} from '../inference'; describe('isHttpUrl', () => { it("should return true when given 'http://example.com'", () => { @@ -103,7 +104,6 @@ describe('isObject', () => { it('should return false for non-objects', () => { expect(isObject(null)).toBe(false); - expect(isObject()).toBe(false); expect(isObject(42)).toBe(false); expect(isObject('string')).toBe(false); expect(isObject(true)).toBe(false); @@ -112,3 +112,56 @@ describe('isObject', () => { expect(isObject(/regex/)).toBe(true); }); }); + +describe('getFirstNonNullOrUndefined', () => { + describe('getFirstNonNullOrUndefined', () => { + it('should return the first non-null and non-undefined value for a number array', () => { + expect(getFirstNonNullOrUndefined(undefined, null, 0, 42)).toBe( + 0, + ); + expect(getFirstNonNullOrUndefined(null, undefined, 42, 123)).toBe( + 42, + ); + }); + + it('should return the first non-null and non-undefined value for a string array', () => { + expect( + getFirstNonNullOrUndefined(undefined, null, '', 'hello'), + ).toBe(''); + expect( + getFirstNonNullOrUndefined(null, undefined, 'test', 'world'), + ).toBe('test'); + }); + + it('should return undefined if all values are null or undefined', () => { + expect(getFirstNonNullOrUndefined(undefined, null)).toBeUndefined(); + expect(getFirstNonNullOrUndefined(null)).toBeUndefined(); + }); + + it('should work with a single value', () => { + expect(getFirstNonNullOrUndefined(42)).toBe(42); + expect(getFirstNonNullOrUndefined()).toBeUndefined(); + expect(getFirstNonNullOrUndefined(null)).toBeUndefined(); + }); + + it('should handle mixed types correctly', () => { + expect( + getFirstNonNullOrUndefined( + undefined, + null, + 'test', + 123, + { key: 'value' }, + ), + ).toBe('test'); + expect( + getFirstNonNullOrUndefined( + null, + undefined, + [1, 2, 3], + 'string', + ), + ).toEqual([1, 2, 3]); + }); + }); +}); diff --git a/packages/@core/base/shared/src/utils/letter.test.ts b/packages/@core/base/shared/src/utils/__tests__/letter.test.ts similarity index 65% rename from packages/@core/base/shared/src/utils/letter.test.ts rename to packages/@core/base/shared/src/utils/__tests__/letter.test.ts index 6f650bdb..2e4037a2 100644 --- a/packages/@core/base/shared/src/utils/letter.test.ts +++ b/packages/@core/base/shared/src/utils/__tests__/letter.test.ts @@ -2,9 +2,10 @@ import { describe, expect, it } from 'vitest'; import { capitalizeFirstLetter, + kebabToCamelCase, toCamelCase, toLowerCaseFirstLetter, -} from './letter'; +} from '../letter'; // 编写测试用例 describe('capitalizeFirstLetter', () => { @@ -76,3 +77,41 @@ describe('toCamelCase', () => { expect(toCamelCase('Child', 'Parent')).toBe('ParentChild'); }); }); + +describe('kebabToCamelCase', () => { + it('should convert kebab-case to camelCase correctly', () => { + expect(kebabToCamelCase('my-component-name')).toBe('myComponentName'); + }); + + it('should handle multiple consecutive hyphens', () => { + expect(kebabToCamelCase('my--component--name')).toBe('myComponentName'); + }); + + it('should trim leading and trailing hyphens', () => { + expect(kebabToCamelCase('-my-component-name-')).toBe('myComponentName'); + }); + + it('should preserve the case of the first word', () => { + expect(kebabToCamelCase('My-component-name')).toBe('MyComponentName'); + }); + + it('should convert a single word correctly', () => { + expect(kebabToCamelCase('component')).toBe('component'); + }); + + it('should return an empty string if input is empty', () => { + expect(kebabToCamelCase('')).toBe(''); + }); + + it('should handle strings with no hyphens', () => { + expect(kebabToCamelCase('mycomponentname')).toBe('mycomponentname'); + }); + + it('should handle strings with only hyphens', () => { + expect(kebabToCamelCase('---')).toBe(''); + }); + + it('should handle mixed case inputs', () => { + expect(kebabToCamelCase('my-Component-Name')).toBe('myComponentName'); + }); +}); diff --git a/packages/@core/base/shared/src/utils/tree.test.ts b/packages/@core/base/shared/src/utils/__tests__/tree.test.ts similarity index 98% rename from packages/@core/base/shared/src/utils/tree.test.ts rename to packages/@core/base/shared/src/utils/__tests__/tree.test.ts index f1000d6e..afe43cc5 100644 --- a/packages/@core/base/shared/src/utils/tree.test.ts +++ b/packages/@core/base/shared/src/utils/__tests__/tree.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { filterTree, mapTree, traverseTreeValues } from './tree'; +import { filterTree, mapTree, traverseTreeValues } from '../tree'; describe('traverseTreeValues', () => { interface Node { diff --git a/packages/@core/base/shared/src/utils/unique.test.ts b/packages/@core/base/shared/src/utils/__tests__/unique.test.ts similarity index 97% rename from packages/@core/base/shared/src/utils/unique.test.ts rename to packages/@core/base/shared/src/utils/__tests__/unique.test.ts index 50f48bbf..c5ebcacc 100644 --- a/packages/@core/base/shared/src/utils/unique.test.ts +++ b/packages/@core/base/shared/src/utils/__tests__/unique.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { uniqueByField } from './unique'; +import { uniqueByField } from '../unique'; describe('uniqueByField', () => { it('should return an array with unique items based on id field', () => { diff --git a/packages/@core/base/shared/src/utils/update-css-variables.test.ts b/packages/@core/base/shared/src/utils/__tests__/update-css-variables.test.ts similarity index 94% rename from packages/@core/base/shared/src/utils/update-css-variables.test.ts rename to packages/@core/base/shared/src/utils/__tests__/update-css-variables.test.ts index 8b5be0d0..4a9cdadc 100644 --- a/packages/@core/base/shared/src/utils/update-css-variables.test.ts +++ b/packages/@core/base/shared/src/utils/__tests__/update-css-variables.test.ts @@ -1,6 +1,6 @@ import { expect, it } from 'vitest'; -import { updateCSSVariables } from './update-css-variables'; +import { updateCSSVariables } from '../update-css-variables'; it('updateCSSVariables should update CSS variables in :root selector', () => { // 模拟初始的内联样式表内容 diff --git a/packages/@core/base/shared/src/utils/window.test.ts b/packages/@core/base/shared/src/utils/__tests__/window.test.ts similarity index 89% rename from packages/@core/base/shared/src/utils/window.test.ts rename to packages/@core/base/shared/src/utils/__tests__/window.test.ts index 8152dcb2..69ad4f7d 100644 --- a/packages/@core/base/shared/src/utils/window.test.ts +++ b/packages/@core/base/shared/src/utils/__tests__/window.test.ts @@ -1,6 +1,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { openWindow } from './window'; // 假设你的函数在 'openWindow' 文件中 +import { openWindow } from '../window'; // 假设你的函数在 'openWindow' 文件中 describe('openWindow', () => { // 保存原始的 window.open 函数 diff --git a/packages/@core/base/shared/src/utils/inference.ts b/packages/@core/base/shared/src/utils/inference.ts index 4e33ba2c..89cd355e 100644 --- a/packages/@core/base/shared/src/utils/inference.ts +++ b/packages/@core/base/shared/src/utils/inference.ts @@ -24,7 +24,7 @@ function isUndefined(value?: unknown): value is undefined { * @param {T} value 要检查的值。 * @returns {boolean} 如果值为空,返回true,否则返回false。 */ -function isEmpty(value: T): value is T { +function isEmpty(value?: T): value is T { if (value === null || value === undefined) { return true; } @@ -105,7 +105,42 @@ function isNumber(value: any): value is number { return typeof value === 'number' && Number.isFinite(value); } +/** + * Returns the first value in the provided list that is neither `null` nor `undefined`. + * + * This function iterates over the input values and returns the first one that is + * not strictly equal to `null` or `undefined`. If all values are either `null` or + * `undefined`, it returns `undefined`. + * + * @template T - The type of the input values. + * @param {...(T | null | undefined)[]} values - A list of values to evaluate. + * @returns {T | undefined} - The first value that is not `null` or `undefined`, or `undefined` if none are found. + * + * @example + * // Returns 42 because it is the first non-null, non-undefined value. + * getFirstNonNullOrUndefined(undefined, null, 42, 'hello'); // 42 + * + * @example + * // Returns 'hello' because it is the first non-null, non-undefined value. + * getFirstNonNullOrUndefined(null, undefined, 'hello', 123); // 'hello' + * + * @example + * // Returns undefined because all values are either null or undefined. + * getFirstNonNullOrUndefined(undefined, null); // undefined + */ +function getFirstNonNullOrUndefined( + ...values: (null | T | undefined)[] +): T | undefined { + for (const value of values) { + if (value !== undefined && value !== null) { + return value; + } + } + return undefined; +} + export { + getFirstNonNullOrUndefined, isEmpty, isFunction, isHttpUrl, diff --git a/packages/@core/base/shared/src/utils/letter.ts b/packages/@core/base/shared/src/utils/letter.ts index 713c459c..65a1c229 100644 --- a/packages/@core/base/shared/src/utils/letter.ts +++ b/packages/@core/base/shared/src/utils/letter.ts @@ -29,4 +29,19 @@ function toCamelCase(key: string, parentKey: string): string { return parentKey + key.charAt(0).toUpperCase() + key.slice(1); } -export { capitalizeFirstLetter, toCamelCase, toLowerCaseFirstLetter }; +function kebabToCamelCase(str: string): string { + return str + .split('-') + .filter(Boolean) + .map((word, index) => + index === 0 ? word : word.charAt(0).toUpperCase() + word.slice(1), + ) + .join(''); +} + +export { + capitalizeFirstLetter, + kebabToCamelCase, + toCamelCase, + toLowerCaseFirstLetter, +}; diff --git a/packages/@core/composables/src/use-sortable.test.ts b/packages/@core/composables/src/__tests__/use-sortable.test.ts similarity index 92% rename from packages/@core/composables/src/use-sortable.test.ts rename to packages/@core/composables/src/__tests__/use-sortable.test.ts index 70aa2234..e7ba1f13 100644 --- a/packages/@core/composables/src/use-sortable.test.ts +++ b/packages/@core/composables/src/__tests__/use-sortable.test.ts @@ -2,7 +2,7 @@ import type { SortableOptions } from 'sortablejs'; import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { useSortable } from './use-sortable'; +import { useSortable } from '../use-sortable'; describe('useSortable', () => { beforeEach(() => { @@ -30,7 +30,6 @@ describe('useSortable', () => { // Import sortablejs to access the mocked create function const Sortable = await import( - // @ts-expect-error - This is a dynamic import 'sortablejs/modular/sortable.complete.esm.js' ); diff --git a/packages/@core/composables/src/index.ts b/packages/@core/composables/src/index.ts index d0015f15..a41b2199 100644 --- a/packages/@core/composables/src/index.ts +++ b/packages/@core/composables/src/index.ts @@ -1,5 +1,6 @@ export * from './use-content-style'; export * from './use-namespace'; +export * from './use-priority-value'; export * from './use-sortable'; export { useEmitAsProps, diff --git a/packages/@core/composables/src/use-priority-value.ts b/packages/@core/composables/src/use-priority-value.ts new file mode 100644 index 00000000..1704e5e0 --- /dev/null +++ b/packages/@core/composables/src/use-priority-value.ts @@ -0,0 +1,47 @@ +import type { Ref } from 'vue'; +import { computed, getCurrentInstance, useAttrs, useSlots } from 'vue'; + +import { + getFirstNonNullOrUndefined, + kebabToCamelCase, +} from '@vben-core/shared'; + +/** + * 依次从插槽、attrs、props、state 中获取值 + * @param key + * @param props + * @param state + */ +export function usePriorityValue< + T extends Record, + S extends Record, + K extends keyof T = keyof T, +>(key: K, props: T, state: Readonly>> | undefined) { + const instance = getCurrentInstance(); + const slots = useSlots(); + const attrs = useAttrs() as T; + + const value = computed((): T[K] => { + // props不管有没有传,都会有默认值,会影响这里的顺序, + // 通过判断原始props是否有值来判断是否传入 + const rawProps = (instance?.vnode?.props || {}) as T; + + const standardRwaProps = {} as T; + + for (const [key, value] of Object.entries(rawProps)) { + standardRwaProps[kebabToCamelCase(key) as K] = value; + } + const propsKey = + standardRwaProps?.[key] === undefined ? undefined : props[key]; + + // slot可以关闭 + return getFirstNonNullOrUndefined( + slots[key as string], + attrs[key], + propsKey, + state?.value?.[key as keyof S], + ) as T[K]; + }); + + return value; +} diff --git a/packages/@core/preferences/src/use-preferences.ts b/packages/@core/preferences/src/use-preferences.ts index b13b327b..03e959fb 100644 --- a/packages/@core/preferences/src/use-preferences.ts +++ b/packages/@core/preferences/src/use-preferences.ts @@ -149,14 +149,6 @@ function usePreferences() { return enable && globalLockScreen; }); - /** - * @zh_CN 是否启用全局偏好设置快捷键 - */ - const globalPreferencesShortcutKey = computed(() => { - const { enable, globalPreferences } = shortcutKeysPreferences.value; - return enable && globalPreferences; - }); - return { authPanelCenter, authPanelLeft, @@ -165,7 +157,6 @@ function usePreferences() { diffPreference, globalLockScreenShortcutKey, globalLogoutShortcutKey, - globalPreferencesShortcutKey, globalSearchShortcutKey, isDark, isFullContent, diff --git a/packages/@core/ui-kit/popup-ui/build.config.ts b/packages/@core/ui-kit/popup-ui/build.config.ts new file mode 100644 index 00000000..18eaa604 --- /dev/null +++ b/packages/@core/ui-kit/popup-ui/build.config.ts @@ -0,0 +1,21 @@ +import { defineBuildConfig } from 'unbuild'; + +export default defineBuildConfig({ + clean: true, + declaration: true, + entries: [ + { + builder: 'mkdist', + input: './src', + loaders: ['vue'], + pattern: ['**/*.vue'], + }, + { + builder: 'mkdist', + format: 'esm', + input: './src', + loaders: ['js'], + pattern: ['**/*.ts'], + }, + ], +}); diff --git a/packages/@core/ui-kit/popup-ui/package.json b/packages/@core/ui-kit/popup-ui/package.json new file mode 100644 index 00000000..5f536288 --- /dev/null +++ b/packages/@core/ui-kit/popup-ui/package.json @@ -0,0 +1,47 @@ +{ + "name": "@vben-core/popup-ui", + "version": "5.1.1", + "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": "packages/@vben-core/uikit/popup-ui" + }, + "license": "MIT", + "type": "module", + "scripts": { + "build": "pnpm unbuild", + "prepublishOnly": "npm run build" + }, + "files": [ + "dist" + ], + "sideEffects": [ + "**/*.css" + ], + "main": "./dist/index.mjs", + "module": "./dist/index.mjs", + "exports": { + ".": { + "types": "./src/index.ts", + "development": "./src/index.ts", + "default": "./dist/index.mjs" + } + }, + "publishConfig": { + "exports": { + ".": { + "default": "./dist/index.mjs" + } + } + }, + "dependencies": { + "@vben-core/composables": "workspace:*", + "@vben-core/icons": "workspace:*", + "@vben-core/shadcn-ui": "workspace:*", + "@vben-core/shared": "workspace:*", + "@vueuse/core": "^11.0.1", + "vue": "^3.4.38" + } +} diff --git a/packages/@core/ui-kit/popup-ui/postcss.config.mjs b/packages/@core/ui-kit/popup-ui/postcss.config.mjs new file mode 100644 index 00000000..3d807045 --- /dev/null +++ b/packages/@core/ui-kit/popup-ui/postcss.config.mjs @@ -0,0 +1 @@ +export { default } from '@vben/tailwind-config/postcss'; diff --git a/packages/@core/ui-kit/popup-ui/src/drawer/__tests__/drawer-api.test.ts b/packages/@core/ui-kit/popup-ui/src/drawer/__tests__/drawer-api.test.ts new file mode 100644 index 00000000..2aebcc09 --- /dev/null +++ b/packages/@core/ui-kit/popup-ui/src/drawer/__tests__/drawer-api.test.ts @@ -0,0 +1,113 @@ +import type { DrawerState } from '../drawer'; + +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import { DrawerApi } from '../drawer-api'; + +// 模拟 Store 类 +vi.mock('@vben-core/shared', () => { + return { + isFunction: (fn: any) => typeof fn === 'function', + Store: class { + private _state: DrawerState; + private options: any; + + constructor(initialState: DrawerState, options: any) { + this._state = initialState; + this.options = options; + } + + batch(cb: () => void) { + cb(); + } + + setState(fn: (prev: DrawerState) => DrawerState) { + this._state = fn(this._state); + this.options.onUpdate(); + } + + get state() { + return this._state; + } + }, + }; +}); + +describe('drawerApi', () => { + let drawerApi: DrawerApi; + let drawerState: DrawerState; + + beforeEach(() => { + drawerApi = new DrawerApi(); + drawerState = drawerApi.store.state; + }); + + it('should initialize with default state', () => { + expect(drawerState.isOpen).toBe(false); + expect(drawerState.cancelText).toBe('取消'); + expect(drawerState.confirmText).toBe('确定'); + }); + + it('should open the drawer', () => { + drawerApi.open(); + expect(drawerApi.store.state.isOpen).toBe(true); + }); + + it('should close the drawer if onBeforeClose allows it', () => { + drawerApi.open(); + drawerApi.close(); + expect(drawerApi.store.state.isOpen).toBe(false); + }); + + it('should not close the drawer if onBeforeClose returns false', () => { + const onBeforeClose = vi.fn(() => false); + const drawerApiWithHook = new DrawerApi({ onBeforeClose }); + drawerApiWithHook.open(); + drawerApiWithHook.close(); + expect(drawerApiWithHook.store.state.isOpen).toBe(true); + expect(onBeforeClose).toHaveBeenCalled(); + }); + + it('should trigger onCancel and keep drawer open if onCancel is provided', () => { + const onCancel = vi.fn(); + const drawerApiWithHook = new DrawerApi({ onCancel }); + drawerApiWithHook.open(); + drawerApiWithHook.onCancel(); + expect(onCancel).toHaveBeenCalled(); + expect(drawerApiWithHook.store.state.isOpen).toBe(true); // 关闭逻辑不在 onCancel 内 + }); + + it('should update shared data correctly', () => { + const testData = { key: 'value' }; + drawerApi.setData(testData); + expect(drawerApi.getData()).toEqual(testData); + }); + + it('should set state correctly using an object', () => { + drawerApi.setState({ title: 'New Title' }); + expect(drawerApi.store.state.title).toBe('New Title'); + }); + + it('should set state correctly using a function', () => { + drawerApi.setState((prev) => ({ ...prev, confirmText: 'Yes' })); + expect(drawerApi.store.state.confirmText).toBe('Yes'); + }); + + it('should call onOpenChange when state changes', () => { + const onOpenChange = vi.fn(); + const drawerApiWithHook = new DrawerApi({ onOpenChange }); + drawerApiWithHook.open(); + expect(onOpenChange).toHaveBeenCalledWith(true); + }); + + it('should batch state updates', () => { + const batchSpy = vi.spyOn(drawerApi.store, 'batch'); + drawerApi.batchStore(() => { + drawerApi.setState({ title: 'Batch Title' }); + drawerApi.setState({ confirmText: 'Batch Confirm' }); + }); + expect(batchSpy).toHaveBeenCalled(); + expect(drawerApi.store.state.title).toBe('Batch Title'); + expect(drawerApi.store.state.confirmText).toBe('Batch Confirm'); + }); +}); diff --git a/packages/@core/ui-kit/popup-ui/src/drawer/drawer-api.ts b/packages/@core/ui-kit/popup-ui/src/drawer/drawer-api.ts new file mode 100644 index 00000000..11fa8369 --- /dev/null +++ b/packages/@core/ui-kit/popup-ui/src/drawer/drawer-api.ts @@ -0,0 +1,123 @@ +import type { DrawerApiOptions, DrawerState } from './drawer'; + +import { isFunction, Store } from '@vben-core/shared'; + +export class DrawerApi { + private api: Pick< + DrawerApiOptions, + 'onBeforeClose' | 'onCancel' | 'onConfirm' | 'onOpenChange' + >; + // private prevState!: DrawerState; + private state!: DrawerState; + + // 共享数据 + public sharedData: Record<'payload', any> = { + payload: {}, + }; + + public store: Store; + + constructor(options: DrawerApiOptions = {}) { + const { + connectedComponent: _, + onBeforeClose, + onCancel, + onConfirm, + onOpenChange, + ...storeState + } = options; + + const defaultState: DrawerState = { + cancelText: '取消', + closable: true, + confirmLoading: false, + confirmText: '确定', + footer: true, + isOpen: false, + loading: false, + modal: true, + sharedData: {}, + title: '', + }; + + this.store = new Store( + { + ...defaultState, + ...storeState, + }, + { + onUpdate: () => { + const state = this.store.state; + if (state?.isOpen === this.state?.isOpen) { + this.state = state; + } else { + this.state = state; + this.api.onOpenChange?.(!!state?.isOpen); + } + }, + }, + ); + + this.api = { + onBeforeClose, + onCancel, + onConfirm, + onOpenChange, + }; + } + + // 如果需要多次更新状态,可以使用 batch 方法 + batchStore(cb: () => void) { + this.store.batch(cb); + } + + /** + * 关闭弹窗 + */ + close() { + // 通过 onBeforeClose 钩子函数来判断是否允许关闭弹窗 + // 如果 onBeforeClose 返回 false,则不关闭弹窗 + const allowClose = this.api.onBeforeClose?.() ?? true; + if (allowClose) { + this.store.setState((prev) => ({ ...prev, isOpen: false })); + } + } + + getData>() { + return (this.sharedData?.payload ?? {}) as T; + } + + /** + * 取消操作 + */ + onCancel() { + this.api.onCancel?.(); + } + + /** + * 确认操作 + */ + onConfirm() { + this.api.onConfirm?.(); + } + + open() { + this.store.setState((prev) => ({ ...prev, isOpen: true })); + } + + setData(payload: T) { + this.sharedData.payload = payload; + } + + setState( + stateOrFn: + | ((prev: DrawerState) => Partial) + | Partial, + ) { + if (isFunction(stateOrFn)) { + this.store.setState(stateOrFn); + } else { + this.store.setState((prev) => ({ ...prev, ...stateOrFn })); + } + } +} diff --git a/packages/@core/ui-kit/popup-ui/src/drawer/drawer.ts b/packages/@core/ui-kit/popup-ui/src/drawer/drawer.ts new file mode 100644 index 00000000..1d62c511 --- /dev/null +++ b/packages/@core/ui-kit/popup-ui/src/drawer/drawer.ts @@ -0,0 +1,93 @@ +import type { DrawerApi } from './drawer-api'; + +import type { Component, Ref } from 'vue'; + +export interface DrawerProps { + /** + * 取消按钮文字 + */ + cancelText?: string; + + /** + * 是否显示右上角的关闭按钮 + * @default true + */ + closable?: boolean; + /** + * 确定按钮 loading + * @default false + */ + confirmLoading?: boolean; + /** + * 确定按钮文字 + */ + confirmText?: string; + /** + * 弹窗描述 + */ + description?: string; + /** + * 是否显示底部 + * @default true + */ + footer?: boolean; + /** + * 弹窗是否显示 + * @default false + */ + loading?: boolean; + /** + * 是否显示遮罩 + * @default true + */ + modal?: boolean; + /** + * 弹窗标题 + */ + title?: string; + /** + * 弹窗标题提示 + */ + titleTooltip?: string; +} + +export interface DrawerState extends DrawerProps { + /** 弹窗打开状态 */ + isOpen?: boolean; + /** + * 共享数据 + */ + sharedData?: Record; +} + +export type ExtendedDrawerApi = { + useStore: >( + selector?: (state: NoInfer) => T, + ) => Readonly>; +} & DrawerApi; + +export interface DrawerApiOptions extends DrawerState { + /** + * 独立的弹窗组件 + */ + connectedComponent?: Component; + /** + * 关闭前的回调,返回 false 可以阻止关闭 + * @returns + */ + onBeforeClose?: () => void; + /** + * 点击取消按钮的回调 + */ + onCancel?: () => void; + /** + * 点击确定按钮的回调 + */ + onConfirm?: () => void; + /** + * 弹窗状态变化回调 + * @param isOpen + * @returns + */ + onOpenChange?: (isOpen: boolean) => void; +} diff --git a/packages/@core/ui-kit/popup-ui/src/drawer/drawer.vue b/packages/@core/ui-kit/popup-ui/src/drawer/drawer.vue new file mode 100644 index 00000000..81f42630 --- /dev/null +++ b/packages/@core/ui-kit/popup-ui/src/drawer/drawer.vue @@ -0,0 +1,141 @@ + + diff --git a/packages/@core/ui-kit/popup-ui/src/drawer/index.ts b/packages/@core/ui-kit/popup-ui/src/drawer/index.ts new file mode 100644 index 00000000..f2dd8396 --- /dev/null +++ b/packages/@core/ui-kit/popup-ui/src/drawer/index.ts @@ -0,0 +1,3 @@ +export type * from './drawer'; +export { default as VbenDrawer } from './drawer.vue'; +export { useVbenDrawer } from './use-drawer'; diff --git a/packages/@core/ui-kit/popup-ui/src/drawer/use-drawer.ts b/packages/@core/ui-kit/popup-ui/src/drawer/use-drawer.ts new file mode 100644 index 00000000..c8040e3d --- /dev/null +++ b/packages/@core/ui-kit/popup-ui/src/drawer/use-drawer.ts @@ -0,0 +1,105 @@ +import type { + DrawerApiOptions, + DrawerProps, + ExtendedDrawerApi, +} from './drawer'; + +import { defineComponent, h, inject, nextTick, provide, reactive } from 'vue'; + +import { useStore } from '@vben-core/shared'; + +import VbenDrawer from './drawer.vue'; +import { DrawerApi } from './drawer-api'; + +const USER_DRAWER_INJECT_KEY = Symbol('VBEN_DRAWER_INJECT'); + +export function useVbenDrawer< + TParentDrawerProps extends DrawerProps = DrawerProps, +>(options: DrawerApiOptions = {}) { + // Drawer一般会抽离出来,所以如果有传入 connectedComponent,则表示为外部调用,与内部组件进行连接 + // 外部的Drawer通过provide/inject传递api + + const { connectedComponent } = options; + if (connectedComponent) { + const extendedApi = reactive({}); + const Drawer = defineComponent( + (props: TParentDrawerProps, { attrs, slots }) => { + provide(USER_DRAWER_INJECT_KEY, { + extendApi(api: ExtendedDrawerApi) { + // 不能直接给 reactive 赋值,会丢失响应 + // 不能用 Object.assign,会丢失 api 的原型函数 + Object.setPrototypeOf(extendedApi, api); + }, + options, + }); + checkProps(extendedApi as ExtendedDrawerApi, { + ...props, + ...attrs, + ...slots, + }); + return () => h(connectedComponent, { ...props, ...attrs }, slots); + }, + { + inheritAttrs: false, + name: 'VbenParentDrawer', + }, + ); + return [Drawer, extendedApi as ExtendedDrawerApi] as const; + } + + const injectData = inject(USER_DRAWER_INJECT_KEY, {}); + + const mergedOptions = { + ...injectData.options, + ...options, + } as DrawerApiOptions; + + // mergedOptions.onOpenChange = (isOpen: boolean) => { + // options.onOpenChange?.(isOpen); + // injectData.options?.onOpenChange?.(isOpen); + // }; + const api = new DrawerApi(mergedOptions); + + const extendedApi: ExtendedDrawerApi = api as never; + + extendedApi.useStore = (selector) => { + return useStore(api.store, selector); + }; + + const Drawer = defineComponent( + (props: DrawerProps, { attrs, slots }) => { + return () => + h(VbenDrawer, { ...props, ...attrs, drawerApi: extendedApi }, slots); + }, + { + inheritAttrs: false, + name: 'VbenDrawer', + }, + ); + injectData.extendApi?.(extendedApi); + return [Drawer, extendedApi] as const; +} + +async function checkProps(api: ExtendedDrawerApi, attrs: Record) { + if (!attrs || Object.keys(attrs).length === 0) { + return; + } + await nextTick(); + + const state = api?.store?.state; + + if (!state) { + return; + } + + const stateKeys = new Set(Object.keys(state)); + + for (const attr of Object.keys(attrs)) { + if (stateKeys.has(attr)) { + // connectedComponent存在时,不要传入Drawer的props,会造成复杂度提升,如果你需要修改Drawer的props,请使用 useVbenDrawer 或者api + console.warn( + `[Vben Drawer]: When 'connectedComponent' exists, do not set props or slots '${attr}', which will increase complexity. If you need to modify the props of Drawer, please use useVbenDrawer or api.`, + ); + } + } +} diff --git a/packages/@core/ui-kit/popup-ui/src/index.ts b/packages/@core/ui-kit/popup-ui/src/index.ts new file mode 100644 index 00000000..56e7ade5 --- /dev/null +++ b/packages/@core/ui-kit/popup-ui/src/index.ts @@ -0,0 +1,2 @@ +export * from './drawer'; +export * from './modal'; diff --git a/packages/@core/ui-kit/popup-ui/src/modal/__tests__/modal-api.test.ts b/packages/@core/ui-kit/popup-ui/src/modal/__tests__/modal-api.test.ts new file mode 100644 index 00000000..fa10d882 --- /dev/null +++ b/packages/@core/ui-kit/popup-ui/src/modal/__tests__/modal-api.test.ts @@ -0,0 +1,112 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import { ModalApi } from '../modal-api'; // 假设 ModalApi 位于同一目录 +import type { ModalState } from '../modal'; + +vi.mock('@vben-core/shared', () => { + return { + isFunction: (fn: any) => typeof fn === 'function', + Store: class { + private _state: ModalState; + private options: any; + + constructor(initialState: ModalState, options: any) { + this._state = initialState; + this.options = options; + } + + batch(cb: () => void) { + cb(); + } + + setState(fn: (prev: ModalState) => ModalState) { + this._state = fn(this._state); + this.options.onUpdate(); + } + + get state() { + return this._state; + } + }, + }; +}); + +describe('modalApi', () => { + let modalApi: ModalApi; + // 使用 modalState 而不是 state + let modalState: ModalState; + + beforeEach(() => { + modalApi = new ModalApi(); + // 获取 modalApi 内的 state + modalState = modalApi.store.state; + }); + + it('should initialize with default state', () => { + expect(modalState.isOpen).toBe(false); + expect(modalState.cancelText).toBe('取消'); + expect(modalState.confirmText).toBe('确定'); + }); + + it('should open the modal', () => { + modalApi.open(); + expect(modalApi.store.state.isOpen).toBe(true); + }); + + it('should close the modal if onBeforeClose allows it', () => { + modalApi.close(); + expect(modalApi.store.state.isOpen).toBe(false); + }); + + it('should not close the modal if onBeforeClose returns false', () => { + const onBeforeClose = vi.fn(() => false); + const modalApiWithHook = new ModalApi({ onBeforeClose }); + modalApiWithHook.open(); + modalApiWithHook.close(); + expect(modalApiWithHook.store.state.isOpen).toBe(true); + expect(onBeforeClose).toHaveBeenCalled(); + }); + + it('should trigger onCancel and close the modal if no onCancel hook is provided', () => { + const onCancel = vi.fn(); + const modalApiWithHook = new ModalApi({ onCancel }); + modalApiWithHook.open(); + modalApiWithHook.onCancel(); + expect(onCancel).toHaveBeenCalled(); + expect(modalApiWithHook.store.state.isOpen).toBe(true); + }); + + it('should update shared data correctly', () => { + const testData = { key: 'value' }; + modalApi.setData(testData); + expect(modalApi.getData()).toEqual(testData); + }); + + it('should set state correctly using an object', () => { + modalApi.setState({ title: 'New Title' }); + expect(modalApi.store.state.title).toBe('New Title'); + }); + + it('should set state correctly using a function', () => { + modalApi.setState((prev) => ({ ...prev, confirmText: 'Yes' })); + expect(modalApi.store.state.confirmText).toBe('Yes'); + }); + + it('should call onOpenChange when state changes', () => { + const onOpenChange = vi.fn(); + const modalApiWithHook = new ModalApi({ onOpenChange }); + modalApiWithHook.open(); + expect(onOpenChange).toHaveBeenCalledWith(true); + }); + + it('should batch state updates', () => { + const batchSpy = vi.spyOn(modalApi.store, 'batch'); + modalApi.batchStore(() => { + modalApi.setState({ title: 'Batch Title' }); + modalApi.setState({ confirmText: 'Batch Confirm' }); + }); + expect(batchSpy).toHaveBeenCalled(); + expect(modalApi.store.state.title).toBe('Batch Title'); + expect(modalApi.store.state.confirmText).toBe('Batch Confirm'); + }); +}); diff --git a/packages/@core/ui-kit/popup-ui/src/modal/index.ts b/packages/@core/ui-kit/popup-ui/src/modal/index.ts new file mode 100644 index 00000000..c8d3498c --- /dev/null +++ b/packages/@core/ui-kit/popup-ui/src/modal/index.ts @@ -0,0 +1,3 @@ +export type * from './modal'; +export { default as VbenModal } from './modal.vue'; +export { useVbenModal } from './use-modal'; diff --git a/packages/@core/ui-kit/popup-ui/src/modal/modal-api.ts b/packages/@core/ui-kit/popup-ui/src/modal/modal-api.ts new file mode 100644 index 00000000..179e2562 --- /dev/null +++ b/packages/@core/ui-kit/popup-ui/src/modal/modal-api.ts @@ -0,0 +1,134 @@ +import type { ModalApiOptions, ModalState } from './modal'; + +import { isFunction, Store } from '@vben-core/shared'; + +export class ModalApi { + private api: Pick< + ModalApiOptions, + 'onBeforeClose' | 'onCancel' | 'onConfirm' | 'onOpenChange' + >; + // private prevState!: ModalState; + private state!: ModalState; + + // 共享数据 + public sharedData: Record<'payload', any> = { + payload: {}, + }; + + public store: Store; + + constructor(options: ModalApiOptions = {}) { + const { + connectedComponent: _, + onBeforeClose, + onCancel, + onConfirm, + onOpenChange, + ...storeState + } = options; + + const defaultState: ModalState = { + cancelText: '取消', + centered: false, + closeOnClickModal: true, + closeOnPressEscape: true, + confirmLoading: false, + confirmText: '确定', + draggable: false, + footer: true, + fullscreen: false, + fullscreenButton: true, + isOpen: false, + loading: false, + modal: true, + sharedData: {}, + title: '', + }; + + this.store = new Store( + { + ...defaultState, + ...storeState, + }, + { + onUpdate: () => { + const state = this.store.state; + + // 每次更新状态时,都会调用 onOpenChange 回调函数 + if (state?.isOpen === this.state?.isOpen) { + this.state = state; + } else { + this.state = state; + this.api.onOpenChange?.(!!state?.isOpen); + } + }, + }, + ); + + this.api = { + onBeforeClose, + onCancel, + onConfirm, + onOpenChange, + }; + } + + // 如果需要多次更新状态,可以使用 batch 方法 + batchStore(cb: () => void) { + this.store.batch(cb); + } + + /** + * 关闭弹窗 + */ + close() { + // 通过 onBeforeClose 钩子函数来判断是否允许关闭弹窗 + // 如果 onBeforeClose 返回 false,则不关闭弹窗 + const allowClose = this.api.onBeforeClose?.() ?? true; + if (allowClose) { + this.store.setState((prev) => ({ ...prev, isOpen: false })); + } + } + + getData>() { + return (this.sharedData?.payload ?? {}) as T; + } + + /** + * 取消操作 + */ + onCancel() { + if (this.api.onCancel) { + this.api.onCancel?.(); + } else { + this.close(); + } + } + + /** + * 确认操作 + */ + onConfirm() { + this.api.onConfirm?.(); + } + + open() { + this.store.setState((prev) => ({ ...prev, isOpen: true })); + } + + setData(payload: T) { + this.sharedData.payload = payload; + } + + setState( + stateOrFn: + | ((prev: ModalState) => Partial) + | Partial, + ) { + if (isFunction(stateOrFn)) { + this.store.setState(stateOrFn); + } else { + this.store.setState((prev) => ({ ...prev, ...stateOrFn })); + } + } +} diff --git a/packages/@core/ui-kit/popup-ui/src/modal/modal.ts b/packages/@core/ui-kit/popup-ui/src/modal/modal.ts new file mode 100644 index 00000000..88b8aab3 --- /dev/null +++ b/packages/@core/ui-kit/popup-ui/src/modal/modal.ts @@ -0,0 +1,123 @@ +import type { ModalApi } from './modal-api'; + +import type { Component, Ref } from 'vue'; + +export interface ModalProps { + /** + * 取消按钮文字 + */ + cancelText?: string; + /** + * 是否居中 + * @default false + */ + centered?: boolean; + /** + * 是否显示右上角的关闭按钮 + * @default true + */ + closable?: boolean; + /** + * 点击弹窗遮罩是否关闭弹窗 + * @default true + */ + closeOnClickModal?: boolean; + /** + * 按下 ESC 键是否关闭弹窗 + * @default true + */ + closeOnPressEscape?: boolean; + /** + * 确定按钮 loading + * @default false + */ + confirmLoading?: boolean; + /** + * 确定按钮文字 + */ + confirmText?: string; + /** + * 弹窗描述 + */ + description?: string; + /** + * 是否可拖拽 + * @default false + */ + draggable?: boolean; + /** + * 是否显示底部 + * @default true + */ + footer?: boolean; + /** + * 是否全屏 + * @default false + */ + fullscreen?: boolean; + /** + * 是否显示全屏按钮 + * @default true + */ + fullscreenButton?: boolean; + /** + * 弹窗是否显示 + * @default false + */ + loading?: boolean; + + /** + * 是否显示遮罩 + * @default true + */ + modal?: boolean; + /** + * 弹窗标题 + */ + title?: string; + /** + * 弹窗标题提示 + */ + titleTooltip?: string; +} + +export interface ModalState extends ModalProps { + /** 弹窗打开状态 */ + isOpen?: boolean; + /** + * 共享数据 + */ + sharedData?: Record; +} + +export type ExtendedModalApi = { + useStore: >( + selector?: (state: NoInfer) => T, + ) => Readonly>; +} & ModalApi; + +export interface ModalApiOptions extends ModalState { + /** + * 独立的弹窗组件 + */ + connectedComponent?: Component; + /** + * 关闭前的回调,返回 false 可以阻止关闭 + * @returns + */ + onBeforeClose?: () => void; + /** + * 点击取消按钮的回调 + */ + onCancel?: () => void; + /** + * 点击确定按钮的回调 + */ + onConfirm?: () => void; + /** + * 弹窗状态变化回调 + * @param isOpen + * @returns + */ + onOpenChange?: (isOpen: boolean) => void; +} diff --git a/packages/@core/ui-kit/popup-ui/src/modal/modal.vue b/packages/@core/ui-kit/popup-ui/src/modal/modal.vue new file mode 100644 index 00000000..57d27ef9 --- /dev/null +++ b/packages/@core/ui-kit/popup-ui/src/modal/modal.vue @@ -0,0 +1,231 @@ + + diff --git a/packages/@core/ui-kit/popup-ui/src/modal/use-modal-draggable.ts b/packages/@core/ui-kit/popup-ui/src/modal/use-modal-draggable.ts new file mode 100644 index 00000000..60ea1324 --- /dev/null +++ b/packages/@core/ui-kit/popup-ui/src/modal/use-modal-draggable.ts @@ -0,0 +1,148 @@ +/** + * @copy https://github.com/element-plus/element-plus/blob/dev/packages/hooks/use-draggable/index.ts + * 调整部分细节 + */ + +import { onBeforeUnmount, onMounted, ref, watchEffect } from 'vue'; +import type { ComputedRef, Ref } from 'vue'; + +import { unrefElement } from '@vueuse/core'; + +export function useModalDraggable( + targetRef: Ref, + dragRef: Ref, + draggable: ComputedRef, +) { + let transform = { + offsetX: 0, + offsetY: 0, + }; + + const dragging = ref(false); + + // let isFirstDrag = true; + // let initialX = 0; + // let initialY = 0; + const onMousedown = (e: MouseEvent) => { + const downX = e.clientX; + const downY = e.clientY; + + if (!targetRef.value) { + return; + } + + // if (isFirstDrag) { + // const { x, y } = getInitialTransform(targetRef.value); + // initialX = x; + // initialY = y; + // } + + const targetRect = targetRef.value.getBoundingClientRect(); + + const { offsetX, offsetY } = transform; + const targetLeft = targetRect.left; + const targetTop = targetRect.top; + const targetWidth = targetRect.width; + const targetHeight = targetRect.height; + const docElement = document.documentElement; + const clientWidth = docElement.clientWidth; + const clientHeight = docElement.clientHeight; + + const minLeft = -targetLeft + offsetX; + const minTop = -targetTop + offsetY; + const maxLeft = clientWidth - targetLeft - targetWidth + offsetX; + const maxTop = clientHeight - targetTop - targetHeight + offsetY; + + const onMousemove = (e: MouseEvent) => { + let moveX = offsetX + e.clientX - downX; + let moveY = offsetY + e.clientY - downY; + // const x = isFirstDrag ? initialX : 0; + // const y = isFirstDrag ? initialY : 0; + moveX = Math.min(Math.max(moveX, minLeft), maxLeft); + // + x; + moveY = Math.min(Math.max(moveY, minTop), maxTop); + // + y; + + transform = { + offsetX: moveX, + offsetY: moveY, + }; + + if (targetRef.value) { + targetRef.value.style.transform = `translate(${moveX}px, ${moveY}px)`; + dragging.value = true; + } + }; + + const onMouseup = () => { + // isFirstDrag = false; + dragging.value = false; + document.removeEventListener('mousemove', onMousemove); + document.removeEventListener('mouseup', onMouseup); + }; + + document.addEventListener('mousemove', onMousemove); + document.addEventListener('mouseup', onMouseup); + }; + + const onDraggable = () => { + const dragDom = unrefElement(dragRef); + if (dragDom && targetRef.value) { + dragDom.addEventListener('mousedown', onMousedown); + } + }; + + const offDraggable = () => { + const dragDom = unrefElement(dragRef); + if (dragDom && targetRef.value) { + dragDom.removeEventListener('mousedown', onMousedown); + } + }; + + const resetPosition = () => { + transform = { + offsetX: 0, + offsetY: 0, + }; + const target = unrefElement(targetRef); + if (target) { + target.style.transform = 'none'; + } + }; + + onMounted(() => { + watchEffect(() => { + if (draggable.value) { + onDraggable(); + } else { + offDraggable(); + } + }); + }); + + onBeforeUnmount(() => { + offDraggable(); + }); + + return { + dragging, + resetPosition, + }; +} + +// function getInitialTransform(target: HTMLElement) { +// let x = 0; +// let y = 0; +// const transformValue = window.getComputedStyle(target)?.transform; +// if (transformValue) { +// const match = transformValue.match(/matrix\(([^)]+)\)/); +// if (match) { +// const values = match[1]?.split(', ') ?? []; +// // 获取 translateX 值 +// x = Number.parseFloat(`${values[4]}`); +// // 获取 translateY 值 +// y = Number.parseFloat(`${values[5]}`); +// } +// } +// return { x, y }; +// } diff --git a/packages/@core/ui-kit/popup-ui/src/modal/use-modal.ts b/packages/@core/ui-kit/popup-ui/src/modal/use-modal.ts new file mode 100644 index 00000000..cbce9c52 --- /dev/null +++ b/packages/@core/ui-kit/popup-ui/src/modal/use-modal.ts @@ -0,0 +1,101 @@ +import type { ExtendedModalApi, ModalApiOptions, ModalProps } from './modal'; + +import { defineComponent, h, inject, nextTick, provide, reactive } from 'vue'; + +import { useStore } from '@vben-core/shared'; + +import VbenModal from './modal.vue'; +import { ModalApi } from './modal-api'; + +const USER_MODAL_INJECT_KEY = Symbol('VBEN_MODAL_INJECT'); + +export function useVbenModal( + options: ModalApiOptions = {}, +) { + // Modal一般会抽离出来,所以如果有传入 connectedComponent,则表示为外部调用,与内部组件进行连接 + // 外部的Modal通过provide/inject传递api + + const { connectedComponent } = options; + if (connectedComponent) { + const extendedApi = reactive({}); + const Modal = defineComponent( + (props: TParentModalProps, { attrs, slots }) => { + provide(USER_MODAL_INJECT_KEY, { + extendApi(api: ExtendedModalApi) { + // 不能直接给 reactive 赋值,会丢失响应 + // 不能用 Object.assign,会丢失 api 的原型函数 + Object.setPrototypeOf(extendedApi, api); + }, + options, + }); + checkProps(extendedApi as ExtendedModalApi, { + ...props, + ...attrs, + ...slots, + }); + return () => h(connectedComponent, { ...props, ...attrs }, slots); + }, + { + inheritAttrs: false, + name: 'VbenParentModal', + }, + ); + return [Modal, extendedApi as ExtendedModalApi] as const; + } + + const injectData = inject(USER_MODAL_INJECT_KEY, {}); + + const mergedOptions = { + ...injectData.options, + ...options, + } as ModalApiOptions; + + // mergedOptions.onOpenChange = (isOpen: boolean) => { + // options.onOpenChange?.(isOpen); + // injectData.options?.onOpenChange?.(isOpen); + // }; + const api = new ModalApi(mergedOptions); + + const extendedApi: ExtendedModalApi = api as never; + + extendedApi.useStore = (selector) => { + return useStore(api.store, selector); + }; + + const Modal = defineComponent( + (props: ModalProps, { attrs, slots }) => { + return () => + h(VbenModal, { ...props, ...attrs, modalApi: extendedApi }, slots); + }, + { + inheritAttrs: false, + name: 'VbenModal', + }, + ); + injectData.extendApi?.(extendedApi); + return [Modal, extendedApi] as const; +} + +async function checkProps(api: ExtendedModalApi, attrs: Record) { + if (!attrs || Object.keys(attrs).length === 0) { + return; + } + await nextTick(); + + const state = api?.store?.state; + + if (!state) { + return; + } + + const stateKeys = new Set(Object.keys(state)); + + for (const attr of Object.keys(attrs)) { + if (stateKeys.has(attr)) { + // connectedComponent存在时,不要传入Modal的props,会造成复杂度提升,如果你需要修改Modal的props,请使用 useModal 或者api + console.warn( + `[Vben Modal]: When 'connectedComponent' exists, do not set props or slots '${attr}', which will increase complexity. If you need to modify the props of Modal, please use useModal or api.`, + ); + } + } +} diff --git a/packages/@core/ui-kit/popup-ui/tailwind.config.mjs b/packages/@core/ui-kit/popup-ui/tailwind.config.mjs new file mode 100644 index 00000000..f17f556f --- /dev/null +++ b/packages/@core/ui-kit/popup-ui/tailwind.config.mjs @@ -0,0 +1 @@ +export { default } from '@vben/tailwind-config'; diff --git a/packages/@core/ui-kit/popup-ui/tsconfig.json b/packages/@core/ui-kit/popup-ui/tsconfig.json new file mode 100644 index 00000000..ce1a891f --- /dev/null +++ b/packages/@core/ui-kit/popup-ui/tsconfig.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@vben/tsconfig/web.json", + "include": ["src"], + "exclude": ["node_modules"] +} diff --git a/packages/@core/ui-kit/shadcn-ui/src/components/alert-dialog/alert-dialog.vue b/packages/@core/ui-kit/shadcn-ui/src/components/alert-dialog/alert-dialog.vue deleted file mode 100644 index 3cc31f66..00000000 --- a/packages/@core/ui-kit/shadcn-ui/src/components/alert-dialog/alert-dialog.vue +++ /dev/null @@ -1,62 +0,0 @@ - - - diff --git a/packages/@core/ui-kit/shadcn-ui/src/components/alert-dialog/index.ts b/packages/@core/ui-kit/shadcn-ui/src/components/alert-dialog/index.ts deleted file mode 100644 index 8837d665..00000000 --- a/packages/@core/ui-kit/shadcn-ui/src/components/alert-dialog/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as VbenAlertDialog } from './alert-dialog.vue'; diff --git a/packages/@core/ui-kit/shadcn-ui/src/components/index.ts b/packages/@core/ui-kit/shadcn-ui/src/components/index.ts index 73fe0c90..d3ccfa4c 100644 --- a/packages/@core/ui-kit/shadcn-ui/src/components/index.ts +++ b/packages/@core/ui-kit/shadcn-ui/src/components/index.ts @@ -1,4 +1,3 @@ -export * from './alert-dialog'; export * from './avatar'; export * from './back-top'; export * from './breadcrumb'; @@ -20,11 +19,9 @@ export * from './popover'; export * from './render-content'; export * from './scrollbar'; export * from './segmented'; -export * from './sheet'; export * from './spinner'; export * from './swap'; export * from './tooltip'; -export * from './ui/alert-dialog'; export * from './ui/avatar'; export * from './ui/badge'; export * from './ui/breadcrumb'; diff --git a/packages/@core/ui-kit/shadcn-ui/src/components/sheet/index.ts b/packages/@core/ui-kit/shadcn-ui/src/components/sheet/index.ts deleted file mode 100644 index 8eb932ac..00000000 --- a/packages/@core/ui-kit/shadcn-ui/src/components/sheet/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as VbenSheet } from './sheet.vue'; diff --git a/packages/@core/ui-kit/shadcn-ui/src/components/sheet/sheet.vue b/packages/@core/ui-kit/shadcn-ui/src/components/sheet/sheet.vue deleted file mode 100644 index 43bd2fe7..00000000 --- a/packages/@core/ui-kit/shadcn-ui/src/components/sheet/sheet.vue +++ /dev/null @@ -1,113 +0,0 @@ - - - diff --git a/packages/@core/ui-kit/shadcn-ui/src/components/spinner/index.ts b/packages/@core/ui-kit/shadcn-ui/src/components/spinner/index.ts index a26467a5..4bba7405 100644 --- a/packages/@core/ui-kit/shadcn-ui/src/components/spinner/index.ts +++ b/packages/@core/ui-kit/shadcn-ui/src/components/spinner/index.ts @@ -1 +1,2 @@ +export { default as VbenLoading } from './loading.vue'; export { default as VbenSpinner } from './spinner.vue'; diff --git a/packages/@core/ui-kit/shadcn-ui/src/components/spinner/loading.vue b/packages/@core/ui-kit/shadcn-ui/src/components/spinner/loading.vue new file mode 100644 index 00000000..cdf9c1c2 --- /dev/null +++ b/packages/@core/ui-kit/shadcn-ui/src/components/spinner/loading.vue @@ -0,0 +1,137 @@ + + + + + diff --git a/packages/@core/ui-kit/shadcn-ui/src/components/spinner/spinner.vue b/packages/@core/ui-kit/shadcn-ui/src/components/spinner/spinner.vue index 318945c2..0ac6d52e 100644 --- a/packages/@core/ui-kit/shadcn-ui/src/components/spinner/spinner.vue +++ b/packages/@core/ui-kit/shadcn-ui/src/components/spinner/spinner.vue @@ -1,7 +1,10 @@ - - diff --git a/packages/@core/ui-kit/shadcn-ui/src/components/ui/alert-dialog/AlertDialogAction.vue b/packages/@core/ui-kit/shadcn-ui/src/components/ui/alert-dialog/AlertDialogAction.vue deleted file mode 100644 index 52f05d6e..00000000 --- a/packages/@core/ui-kit/shadcn-ui/src/components/ui/alert-dialog/AlertDialogAction.vue +++ /dev/null @@ -1,28 +0,0 @@ - - - diff --git a/packages/@core/ui-kit/shadcn-ui/src/components/ui/alert-dialog/AlertDialogCancel.vue b/packages/@core/ui-kit/shadcn-ui/src/components/ui/alert-dialog/AlertDialogCancel.vue deleted file mode 100644 index 088113f8..00000000 --- a/packages/@core/ui-kit/shadcn-ui/src/components/ui/alert-dialog/AlertDialogCancel.vue +++ /dev/null @@ -1,30 +0,0 @@ - - - diff --git a/packages/@core/ui-kit/shadcn-ui/src/components/ui/alert-dialog/AlertDialogContent.vue b/packages/@core/ui-kit/shadcn-ui/src/components/ui/alert-dialog/AlertDialogContent.vue deleted file mode 100644 index e986c0fa..00000000 --- a/packages/@core/ui-kit/shadcn-ui/src/components/ui/alert-dialog/AlertDialogContent.vue +++ /dev/null @@ -1,46 +0,0 @@ - - - diff --git a/packages/@core/ui-kit/shadcn-ui/src/components/ui/alert-dialog/AlertDialogDescription.vue b/packages/@core/ui-kit/shadcn-ui/src/components/ui/alert-dialog/AlertDialogDescription.vue deleted file mode 100644 index c057ebfe..00000000 --- a/packages/@core/ui-kit/shadcn-ui/src/components/ui/alert-dialog/AlertDialogDescription.vue +++ /dev/null @@ -1,29 +0,0 @@ - - - diff --git a/packages/@core/ui-kit/shadcn-ui/src/components/ui/alert-dialog/AlertDialogFooter.vue b/packages/@core/ui-kit/shadcn-ui/src/components/ui/alert-dialog/AlertDialogFooter.vue deleted file mode 100644 index 3d9ae39d..00000000 --- a/packages/@core/ui-kit/shadcn-ui/src/components/ui/alert-dialog/AlertDialogFooter.vue +++ /dev/null @@ -1,22 +0,0 @@ - - - diff --git a/packages/@core/ui-kit/shadcn-ui/src/components/ui/alert-dialog/AlertDialogHeader.vue b/packages/@core/ui-kit/shadcn-ui/src/components/ui/alert-dialog/AlertDialogHeader.vue deleted file mode 100644 index db8dc643..00000000 --- a/packages/@core/ui-kit/shadcn-ui/src/components/ui/alert-dialog/AlertDialogHeader.vue +++ /dev/null @@ -1,17 +0,0 @@ - - - diff --git a/packages/@core/ui-kit/shadcn-ui/src/components/ui/alert-dialog/AlertDialogTitle.vue b/packages/@core/ui-kit/shadcn-ui/src/components/ui/alert-dialog/AlertDialogTitle.vue deleted file mode 100644 index 711247e9..00000000 --- a/packages/@core/ui-kit/shadcn-ui/src/components/ui/alert-dialog/AlertDialogTitle.vue +++ /dev/null @@ -1,26 +0,0 @@ - - - diff --git a/packages/@core/ui-kit/shadcn-ui/src/components/ui/alert-dialog/AlertDialogTrigger.vue b/packages/@core/ui-kit/shadcn-ui/src/components/ui/alert-dialog/AlertDialogTrigger.vue deleted file mode 100644 index 32726258..00000000 --- a/packages/@core/ui-kit/shadcn-ui/src/components/ui/alert-dialog/AlertDialogTrigger.vue +++ /dev/null @@ -1,11 +0,0 @@ - - - diff --git a/packages/@core/ui-kit/shadcn-ui/src/components/ui/alert-dialog/index.ts b/packages/@core/ui-kit/shadcn-ui/src/components/ui/alert-dialog/index.ts deleted file mode 100644 index 88770f1e..00000000 --- a/packages/@core/ui-kit/shadcn-ui/src/components/ui/alert-dialog/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -export { default as AlertDialog } from './AlertDialog.vue'; -export { default as AlertDialogAction } from './AlertDialogAction.vue'; -export { default as AlertDialogCancel } from './AlertDialogCancel.vue'; -export { default as AlertDialogContent } from './AlertDialogContent.vue'; -export { default as AlertDialogDescription } from './AlertDialogDescription.vue'; -export { default as AlertDialogFooter } from './AlertDialogFooter.vue'; -export { default as AlertDialogHeader } from './AlertDialogHeader.vue'; -export { default as AlertDialogTitle } from './AlertDialogTitle.vue'; -export { default as AlertDialogTrigger } from './AlertDialogTrigger.vue'; diff --git a/packages/@core/ui-kit/shadcn-ui/src/components/ui/dialog/DialogContent.vue b/packages/@core/ui-kit/shadcn-ui/src/components/ui/dialog/DialogContent.vue index 823afeb5..f40543b6 100644 --- a/packages/@core/ui-kit/shadcn-ui/src/components/ui/dialog/DialogContent.vue +++ b/packages/@core/ui-kit/shadcn-ui/src/components/ui/dialog/DialogContent.vue @@ -1,5 +1,5 @@