From 67229bed3b466bbf2b1d242cd0fc335914eb2f1e Mon Sep 17 00:00:00 2001 From: lidaixingchen <3506962253@qq.com> Date: Sun, 31 May 2026 23:52:53 +0800 Subject: [PATCH 01/67] =?UTF-8?q?feat(poc):=20=E5=88=9D=E5=A7=8B=E5=8C=96?= =?UTF-8?q?=20Vue3=20+=20TS=20+=20Vite=20=E4=BE=9D=E8=B5=96=E9=AA=8C?= =?UTF-8?q?=E8=AF=81=E9=A1=B9=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增完整的 PoC 项目,包含: 1. 基础项目配置(tsconfig、vite.config、package.json) 2. 核心依赖集成:Reka UI、vee-validate+zod、v-calendar 3. 5个组件Demo:Dialog、Select、表单验证、日历、Combobox 4. 全局样式和静态资源 5. 更新根目录gitignore规则 --- .gitignore | 14 +- pnpm-lock.yaml | 960 +++++++++++++++++++++++- poc/.gitignore | 24 + poc/README.md | 5 + poc/index.html | 13 + poc/package.json | 29 + poc/public/favicon.svg | 1 + poc/public/icons.svg | 24 + poc/src/App.vue | 235 ++++++ poc/src/assets/hero.png | Bin 0 -> 13057 bytes poc/src/assets/vite.svg | 1 + poc/src/assets/vue.svg | 1 + poc/src/components/HelloWorld.vue | 95 +++ poc/src/components/RekaComboboxDemo.vue | 409 ++++++++++ poc/src/components/RekaDialogDemo.vue | 121 +++ poc/src/components/RekaSelectDemo.vue | 239 ++++++ poc/src/components/VCalendarDemo.vue | 158 ++++ poc/src/components/VeeValidateDemo.vue | 203 +++++ poc/src/main.ts | 5 + poc/src/style.css | 296 ++++++++ poc/tsconfig.app.json | 14 + poc/tsconfig.json | 7 + poc/tsconfig.node.json | 24 + poc/vite.config.ts | 7 + 24 files changed, 2878 insertions(+), 7 deletions(-) create mode 100644 poc/.gitignore create mode 100644 poc/README.md create mode 100644 poc/index.html create mode 100644 poc/package.json create mode 100644 poc/public/favicon.svg create mode 100644 poc/public/icons.svg create mode 100644 poc/src/App.vue create mode 100644 poc/src/assets/hero.png create mode 100644 poc/src/assets/vite.svg create mode 100644 poc/src/assets/vue.svg create mode 100644 poc/src/components/HelloWorld.vue create mode 100644 poc/src/components/RekaComboboxDemo.vue create mode 100644 poc/src/components/RekaDialogDemo.vue create mode 100644 poc/src/components/RekaSelectDemo.vue create mode 100644 poc/src/components/VCalendarDemo.vue create mode 100644 poc/src/components/VeeValidateDemo.vue create mode 100644 poc/src/main.ts create mode 100644 poc/src/style.css create mode 100644 poc/tsconfig.app.json create mode 100644 poc/tsconfig.json create mode 100644 poc/tsconfig.node.json create mode 100644 poc/vite.config.ts diff --git a/.gitignore b/.gitignore index 3722413..80eb33d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,29 +4,38 @@ node_modules # Build outputs dist +dist-ssr .next out build +.codegraph # IDE .vscode .idea +.trae +.cursor *.swp *.swo # OS .DS_Store Thumbs.db +Desktop.ini # Environment .env .env.local .env.*.local +*.local # Logs *.log npm-debug.log* +yarn-debug.log* +yarn-error.log* pnpm-debug.log* +lerna-debug.log* # Testing coverage @@ -36,9 +45,12 @@ coverage .turbo *.tsbuildinfo +# Next.js +next-env.d.ts + # Misc .vercel # Development docs (internal only) DEVELOPMENT.md -DEV_*.md \ No newline at end of file +DEV_*.md diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3ee46cc..927f536 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -304,6 +304,52 @@ importers: specifier: ^1.3.1 version: 1.6.1(@types/node@20.19.25)(jsdom@24.1.3) + poc: + dependencies: + '@popperjs/core': + specifier: ^2.11.8 + version: 2.11.8 + '@vee-validate/zod': + specifier: ^4.15.1 + version: 4.15.1(vue@3.5.35)(zod@3.25.76) + lucide-vue-next: + specifier: ^1.0.0 + version: 1.0.0(vue@3.5.35) + reka-ui: + specifier: ^2.9.8 + version: 2.9.8(vue@3.5.35) + v-calendar: + specifier: ^3.1.2 + version: 3.1.2(@popperjs/core@2.11.8)(vue@3.5.35) + vee-validate: + specifier: ^4.15.1 + version: 4.15.1(vue@3.5.35) + vue: + specifier: ^3.5.34 + version: 3.5.35(typescript@6.0.3) + zod: + specifier: '3' + version: 3.25.76 + devDependencies: + '@types/node': + specifier: ^24.12.3 + version: 24.12.4 + '@vitejs/plugin-vue': + specifier: ^6.0.6 + version: 6.0.7(vite@8.0.14)(vue@3.5.35) + '@vue/tsconfig': + specifier: ^0.9.1 + version: 0.9.1(typescript@6.0.3)(vue@3.5.35) + typescript: + specifier: ~6.0.2 + version: 6.0.3 + vite: + specifier: ^8.0.12 + version: 8.0.14(@types/node@24.12.4) + vue-tsc: + specifier: ^3.2.8 + version: 3.3.3(typescript@6.0.3) + packages: /@adobe/css-tools@4.4.4: @@ -430,10 +476,18 @@ packages: resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} + /@babel/helper-string-parser@7.29.7: + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + /@babel/helper-validator-identifier@7.28.5: resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} + /@babel/helper-validator-identifier@7.29.7: + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + /@babel/helper-validator-option@7.27.1: resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} @@ -455,6 +509,13 @@ packages: '@babel/types': 7.28.5 dev: true + /@babel/parser@7.29.7: + resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + engines: {node: '>=6.0.0'} + hasBin: true + dependencies: + '@babel/types': 7.29.7 + /@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.5): resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} engines: {node: '>=6.9.0'} @@ -478,7 +539,6 @@ packages: /@babel/runtime@7.28.4: resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} engines: {node: '>=6.9.0'} - dev: true /@babel/template@7.27.2: resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} @@ -511,6 +571,13 @@ packages: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + /@babel/types@7.29.7: + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + /@bcoe/v8-coverage@0.2.3: resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} dev: true @@ -562,6 +629,15 @@ packages: resolution: {integrity: sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==} dev: false + /@emnapi/core@1.10.0: + resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + requiresBuild: true + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + dev: true + optional: true + /@emnapi/core@1.7.1: resolution: {integrity: sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==} requiresBuild: true @@ -571,6 +647,14 @@ packages: dev: true optional: true + /@emnapi/runtime@1.10.0: + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + requiresBuild: true + dependencies: + tslib: 2.8.1 + dev: true + optional: true + /@emnapi/runtime@1.7.1: resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==} requiresBuild: true @@ -586,6 +670,14 @@ packages: dev: true optional: true + /@emnapi/wasi-threads@1.2.1: + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + requiresBuild: true + dependencies: + tslib: 2.8.1 + dev: true + optional: true + /@esbuild/aix-ppc64@0.21.5: resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} @@ -1538,6 +1630,12 @@ packages: '@floating-ui/utils': 0.2.10 dev: false + /@floating-ui/core@1.7.5: + resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} + dependencies: + '@floating-ui/utils': 0.2.11 + dev: false + /@floating-ui/dom@1.7.4: resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==} dependencies: @@ -1545,6 +1643,13 @@ packages: '@floating-ui/utils': 0.2.10 dev: false + /@floating-ui/dom@1.7.6: + resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==} + dependencies: + '@floating-ui/core': 1.7.5 + '@floating-ui/utils': 0.2.11 + dev: false + /@floating-ui/react-dom@2.1.6(react-dom@19.2.0)(react@19.2.0): resolution: {integrity: sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==} peerDependencies: @@ -1560,6 +1665,21 @@ packages: resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} dev: false + /@floating-ui/utils@0.2.11: + resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} + dev: false + + /@floating-ui/vue@1.1.11(vue@3.5.35): + resolution: {integrity: sha512-HzHKCNVxnGS35r9fCHBc3+uCnjw9IWIlCPL683cGgM9Kgj2BiAl8x1mS7vtvP6F9S/e/q4O6MApwSHj8hNLGfw==} + dependencies: + '@floating-ui/dom': 1.7.6 + '@floating-ui/utils': 0.2.11 + vue-demi: 0.14.10(vue@3.5.35) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + dev: false + /@hookform/resolvers@5.4.0(react-hook-form@7.76.1): resolution: {integrity: sha512-EIsqr/t/qbinPIhGjMdtvutIN1Kk4uwbROE9/UQ93CAVGR7GkA7Y92+fX80OzXi/OB67jVFYwKGO1WzkxmkFZw==} peerDependencies: @@ -1844,6 +1964,18 @@ packages: engines: {node: '>=18'} dev: false + /@internationalized/date@3.12.2: + resolution: {integrity: sha512-FY1Y+H64NDs+HAF6omlnWxm3mEpfgaCSWtL5l551ZZfImA+kGjPFgrnJrGjH6lfmLL0g8Z/mBu1R3kufeCp6Jw==} + dependencies: + '@swc/helpers': 0.5.15 + dev: false + + /@internationalized/number@3.6.7: + resolution: {integrity: sha512-3ji1fcrT+FPAK86UqEhB/psHixYo6niWPJtt7+qRaYFynt/BaJG8GhAPimtWUpEiVSTq8ZM8L5psMxGquiB/Vg==} + dependencies: + '@swc/helpers': 0.5.15 + dev: false + /@isaacs/cliui@8.0.2: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -1889,7 +2021,6 @@ packages: /@jridgewell/sourcemap-codec@1.5.5: resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - dev: true /@jridgewell/trace-mapping@0.3.31: resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} @@ -1965,6 +2096,19 @@ packages: dev: true optional: true + /@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0): + resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} + requiresBuild: true + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@tybys/wasm-util': 0.10.1 + dev: true + optional: true + /@next/env@15.5.18: resolution: {integrity: sha512-hAV85Ckd9QR6RvH04MEKwsfLTksvFpO47j9xwtoIuvuPnlwecpSi+uZTtm8HirVbtlI2Fnz//xpcSTjFdyJk+g==} dev: false @@ -2089,6 +2233,10 @@ packages: engines: {node: '>=12.4.0'} dev: true + /@oxc-project/types@0.132.0: + resolution: {integrity: sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==} + dev: true + /@pkgjs/parseargs@0.11.0: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -2096,6 +2244,10 @@ packages: dev: true optional: true + /@popperjs/core@2.11.8: + resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} + dev: false + /@radix-ui/number@1.1.1: resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} dev: false @@ -3113,10 +3265,158 @@ packages: resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} dev: false + /@rolldown/binding-android-arm64@1.0.2: + resolution: {integrity: sha512-ZS4D1JPGn/MYQN/SYDWftIE/nVsM8j/AFOYEzAoOE2O3NktQOZru+/vYXGbR/qtdLdIfGCP0lcoJiYVzsEz+iQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@rolldown/binding-darwin-arm64@1.0.2: + resolution: {integrity: sha512-vdFA9+C/rekyGce7WqHs/xoT0ioZEWaOFyZLIV1mEeNFaFDUQrPIo8Vs2GvJ6eetb3rzDUtUBgzto3ExpXJB3w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@rolldown/binding-darwin-x64@1.0.2: + resolution: {integrity: sha512-BewSOwTHazv77DTYiAZXSqqKZ4KP/KonFisDMVU7PImxoWfB2aepnPhd2E4SWz3zDzYgDNbs6jBmTdgNnF02GA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@rolldown/binding-freebsd-x64@1.0.2: + resolution: {integrity: sha512-m41o7M0YWtUdqk61Tb+jnKb2rN++iRdIASlExkUoKfIAH30DOHCB8fVLzSUpbWHHU8esmEioY62PxzexE8MBuA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@rolldown/binding-linux-arm-gnueabihf@1.0.2: + resolution: {integrity: sha512-jcojB9H7W/jS29pMKWAK1N+fU99vXodHDTatS3b3y/XSOCiHo0kkA74pL3jJmkoQtYpOCxDvaKs1fo2Ij/1X5w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rolldown/binding-linux-arm64-gnu@1.0.2: + resolution: {integrity: sha512-1jn6qDU5iiOgFgygDzKUuKP0maTi0/f1+sBLgvij/76C77Nm3ts6ufz9Bjg5q5dduxiUIxtq86JIoBvo1xQ4Ig==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + requiresBuild: true + dev: true + optional: true + + /@rolldown/binding-linux-arm64-musl@1.0.2: + resolution: {integrity: sha512-QVLO/czFMdoMFSqlX3bcswcJNm/23r+qoa/jgtmFc/qEp6/jXmIkDjF/XIo8dPfGaiwy1xfQn8o77L79GeXFgw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + requiresBuild: true + dev: true + optional: true + + /@rolldown/binding-linux-ppc64-gnu@1.0.2: + resolution: {integrity: sha512-hgO5Abm0w5UL6FEa2iFnZqo2KlK7TQ5QhV5x09hujBf7t5KzHQ1VmfPuTpqRy/rNlSxua3eWH374xxiVrP+lcA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + requiresBuild: true + dev: true + optional: true + + /@rolldown/binding-linux-s390x-gnu@1.0.2: + resolution: {integrity: sha512-fy8rXxuYEu602abC8MUNaPjYLIFzReOaEIEMKMUa0rFEUxNpVXhs15KSSQ4qlqSaM7B6rcj9rDZgADh/IGDzLQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + requiresBuild: true + dev: true + optional: true + + /@rolldown/binding-linux-x64-gnu@1.0.2: + resolution: {integrity: sha512-0+bOkiQ779+r1WpoHOWHqncvyySci0vKph+myNDYb+im6meJAzHQXay6oEgnkHuUGouM1LKTZwqKpBow6Kj7CQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + requiresBuild: true + dev: true + optional: true + + /@rolldown/binding-linux-x64-musl@1.0.2: + resolution: {integrity: sha512-mjSkrzZK5Qsl0a9d1JgILOiuZOSDTVdKENcSXBoqbzSrspLR/4/IRVDo5wd2GgZjNss/viBFJdeq+j7qH2nypw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + requiresBuild: true + dev: true + optional: true + + /@rolldown/binding-openharmony-arm64@1.0.2: + resolution: {integrity: sha512-1v5vHasdfQAZoEHakBV72LIFAC9JjnymsiKxp+GEr/ma3+NJCPSaYK+qavInOovJkgwFrs7GccX2d6IgDA3Z5w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + requiresBuild: true + dev: true + optional: true + + /@rolldown/binding-wasm32-wasi@1.0.2: + resolution: {integrity: sha512-mb1VobWn6NheziTk5/WEaR6AKVbrwT5sOi6C7zk3gy/pD1qtJfU1j4PgTo2NJnOtbL9Dl3Aeei8w9jJ7qC2jZQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + requiresBuild: true + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + dev: true + optional: true + + /@rolldown/binding-win32-arm64-msvc@1.0.2: + resolution: {integrity: sha512-SqKonF56vA/L2yHwHYcEp2P34URpOZ7d1fS635cTkpDnUtEGdUbhI6NzsPdqeSWvAAeGDrxjWjNmibDIdFf9/A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@rolldown/binding-win32-x64-msvc@1.0.2: + resolution: {integrity: sha512-v7qRI7gXLRINcOGXt+7YmAZ6iFuyZVMIoXAxhd8oP+DR9dLfL9GfNIx7PLMxmhZdvq8waUJBQiWN9EKNy+TRBQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + /@rolldown/pluginutils@1.0.0-beta.27: resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} dev: true + /@rolldown/pluginutils@1.0.1: + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + dev: true + /@rollup/rollup-android-arm-eabi@4.53.3: resolution: {integrity: sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==} cpu: [arm] @@ -3315,6 +3615,19 @@ packages: tslib: 2.8.1 dev: false + /@tanstack/virtual-core@3.16.0: + resolution: {integrity: sha512-Er2N7q3WOiH6y2JLxsxNX+u2/sLqSsL0bxFgDjuiPiA7vKhZRm+IzcS17vRee3GNXr64UsesA5CAp9yTiIYw9A==} + dev: false + + /@tanstack/vue-virtual@3.13.26(vue@3.5.35): + resolution: {integrity: sha512-4TmREKi8rKiQC8E2XVEMMgzWbrgHNYolkBgYTXVK1kqXmXRGz6xPWgBq20GUYWUDDhit94+g0ricUQKpZhWRmg==} + peerDependencies: + vue: ^2.7.0 || ^3.0.0 + dependencies: + '@tanstack/virtual-core': 3.16.0 + vue: 3.5.35(typescript@6.0.3) + dev: false + /@testing-library/dom@10.4.1: resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} engines: {node: '>=18'} @@ -3452,6 +3765,10 @@ packages: '@types/node': 20.19.25 dev: true + /@types/lodash@4.17.24: + resolution: {integrity: sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==} + dev: false + /@types/mdast@4.0.4: resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} dependencies: @@ -3470,6 +3787,12 @@ packages: dependencies: undici-types: 6.21.0 + /@types/node@24.12.4: + resolution: {integrity: sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==} + dependencies: + undici-types: 7.16.0 + dev: true + /@types/react-dom@19.2.3(@types/react@19.2.7): resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} peerDependencies: @@ -3482,6 +3805,10 @@ packages: dependencies: csstype: 3.2.3 + /@types/resize-observer-browser@0.1.11: + resolution: {integrity: sha512-cNw5iH8JkMkb3QkCoe7DaZiawbDQEUX8t7iuQaRTyLOyQCR2h+ibBD4GJt7p5yhUHrlOeL7ZtbxNHeipqNsBzQ==} + dev: false + /@types/unist@2.0.11: resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} dev: false @@ -3490,6 +3817,10 @@ packages: resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} dev: false + /@types/web-bluetooth@0.0.21: + resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==} + dev: false + /@typescript-eslint/eslint-plugin@8.48.1(@typescript-eslint/parser@6.21.0)(eslint@8.57.1)(typescript@5.9.3): resolution: {integrity: sha512-X63hI1bxl5ohelzr0LY5coufyl0LJNthld+abwxpCoo6Gq+hSqhKwci7MUWkXo67mzgUK6YFByhmaHmUcuBJmA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3834,6 +4165,18 @@ packages: dev: true optional: true + /@vee-validate/zod@4.15.1(vue@3.5.35)(zod@3.25.76): + resolution: {integrity: sha512-329Z4TDBE5Vx0FdbA8S4eR9iGCFFUNGbxjpQ20ff5b5wGueScjocUIx9JHPa79LTG06RnlUR4XogQsjN4tecKA==} + peerDependencies: + zod: ^3.24.0 + dependencies: + type-fest: 4.41.0 + vee-validate: 4.15.1(vue@3.5.35) + zod: 3.25.76 + transitivePeerDependencies: + - vue + dev: false + /@vitejs/plugin-react@4.7.0(vite@7.2.6): resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} engines: {node: ^14.18.0 || >=16.0.0} @@ -3851,6 +4194,18 @@ packages: - supports-color dev: true + /@vitejs/plugin-vue@6.0.7(vite@8.0.14)(vue@3.5.35): + resolution: {integrity: sha512-km+p+XdSz9Sxm5rqUbqcSfZYaAniKxWBj1KURl+Jr7UaPvvX7BmaWMdP69I5rrFDeQGyxAG7NXdc57vz+snhWg==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + vue: ^3.2.25 + dependencies: + '@rolldown/pluginutils': 1.0.1 + vite: 8.0.14(@types/node@24.12.4) + vue: 3.5.35(typescript@6.0.3) + dev: true + /@vitest/coverage-v8@1.6.1(vitest@1.6.1): resolution: {integrity: sha512-6YeRZwuO4oTGKxD3bijok756oktHSIm3eczVVzNe3scqzuhLwltIF3S9ZL/vwOVIpURmU6SnZhziXXAfw8/Qlw==} peerDependencies: @@ -3913,6 +4268,163 @@ packages: pretty-format: 29.7.0 dev: true + /@volar/language-core@2.4.28: + resolution: {integrity: sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==} + dependencies: + '@volar/source-map': 2.4.28 + dev: true + + /@volar/source-map@2.4.28: + resolution: {integrity: sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==} + dev: true + + /@volar/typescript@2.4.28: + resolution: {integrity: sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==} + dependencies: + '@volar/language-core': 2.4.28 + path-browserify: 1.0.1 + vscode-uri: 3.1.0 + dev: true + + /@vue/compiler-core@3.5.35: + resolution: {integrity: sha512-BUmHaR1J+O+CKZ9uJucdVTEr1LHsdyvv7vG3eNRhK3CczEHeMd/LtsHAuD7PbrxvI2envCY2v7HI1vC1aBRzKw==} + dependencies: + '@babel/parser': 7.29.7 + '@vue/shared': 3.5.35 + entities: 7.0.1 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + /@vue/compiler-dom@3.5.35: + resolution: {integrity: sha512-k+bprkXxuqhVajgTx5mUHuir7TwQzUKOWR40ng1ncAqQRPnrLngGGgqVEEhOnTMlc8btHYVKmrP8s5Qyg0hvYA==} + dependencies: + '@vue/compiler-core': 3.5.35 + '@vue/shared': 3.5.35 + + /@vue/compiler-sfc@3.5.35: + resolution: {integrity: sha512-G5VPMcXTSywXBgtFOZOnHKBxKSrwXUcvY1iaF5/hRcy7t0J6CH/d8ha9F4nzi00Fax1eLV0QHM7v4mQu68jydw==} + dependencies: + '@babel/parser': 7.29.7 + '@vue/compiler-core': 3.5.35 + '@vue/compiler-dom': 3.5.35 + '@vue/compiler-ssr': 3.5.35 + '@vue/shared': 3.5.35 + estree-walker: 2.0.2 + magic-string: 0.30.21 + postcss: 8.5.15 + source-map-js: 1.2.1 + + /@vue/compiler-ssr@3.5.35: + resolution: {integrity: sha512-rGhAeXgdM7/ffTJGXT69rCCdTmjDewnFuUZfBQQHTdcEBeWdT5HCGY60y2ytLJr9/Dsu7IntUi5z/w0h6Rjnzw==} + dependencies: + '@vue/compiler-dom': 3.5.35 + '@vue/shared': 3.5.35 + + /@vue/devtools-api@7.7.9: + resolution: {integrity: sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==} + dependencies: + '@vue/devtools-kit': 7.7.9 + dev: false + + /@vue/devtools-kit@7.7.9: + resolution: {integrity: sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==} + dependencies: + '@vue/devtools-shared': 7.7.9 + birpc: 2.9.0 + hookable: 5.5.3 + mitt: 3.0.1 + perfect-debounce: 1.0.0 + speakingurl: 14.0.1 + superjson: 2.2.6 + dev: false + + /@vue/devtools-shared@7.7.9: + resolution: {integrity: sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==} + dependencies: + rfdc: 1.4.1 + dev: false + + /@vue/language-core@3.3.3: + resolution: {integrity: sha512-X6p+7nfY7vVT6dQwUJ+v0Jfq/lwIfhL2jMi91dQ3ln4hnlGXlxsDu/FNkeyHYgvYtyQy18ZX76IZy7X4diDbiQ==} + dependencies: + '@volar/language-core': 2.4.28 + '@vue/compiler-dom': 3.5.35 + '@vue/shared': 3.5.35 + alien-signals: 3.2.1 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + picomatch: 4.0.4 + dev: true + + /@vue/reactivity@3.5.35: + resolution: {integrity: sha512-tVc+SsHConvh/Lz64qq1pP3rYArBmK42xonovEcxY74SQtvctZodG/zhq54P5dr38cVuw25d27cPNRdlMidpGQ==} + dependencies: + '@vue/shared': 3.5.35 + + /@vue/runtime-core@3.5.35: + resolution: {integrity: sha512-A/xFNX9loIcWDygeQuNCfKuh0CoYBzxhqEMNah5TSFg9Z53DrFYEN2qi5CU9necjM1OWYegYREUTHmXTmhfXtg==} + dependencies: + '@vue/reactivity': 3.5.35 + '@vue/shared': 3.5.35 + + /@vue/runtime-dom@3.5.35: + resolution: {integrity: sha512-odrJ1C391dbGnyDRh8U+rnP7J2amIEzfmRk5vXy7xi3aZhEXofTvpi0T4HJb6jlNqQZTNPR5MPHSB3RHNkIORA==} + dependencies: + '@vue/reactivity': 3.5.35 + '@vue/runtime-core': 3.5.35 + '@vue/shared': 3.5.35 + csstype: 3.2.3 + + /@vue/server-renderer@3.5.35(vue@3.5.35): + resolution: {integrity: sha512-NkebSOYdB97wi8OQcO3HqzZSlymJi/aWsN/7h74OSVhRTm6qGs3Jp3e0rCXynmWwSlKeRrnlIug+ilYoHBmQDA==} + peerDependencies: + vue: 3.5.35 + dependencies: + '@vue/compiler-ssr': 3.5.35 + '@vue/shared': 3.5.35 + vue: 3.5.35(typescript@6.0.3) + + /@vue/shared@3.5.35: + resolution: {integrity: sha512-zSbjL7gRXwks2ZQLRGCajBtBXEOXW9Ddhn/HvSdrGkE2dqGnumzW8XtusRrxrE9LvqtiqDXQ+A60Hp6mvdYxfA==} + + /@vue/tsconfig@0.9.1(typescript@6.0.3)(vue@3.5.35): + resolution: {integrity: sha512-buvjm+9NzLCJL29KY1j1991YYJ5e6275OiK+G4jtmfIb+z4POywbdm0wXusT9adVWqe0xqg70TbI7+mRx4uU9w==} + peerDependencies: + typescript: '>= 5.8' + vue: ^3.4.0 + peerDependenciesMeta: + typescript: + optional: true + vue: + optional: true + dependencies: + typescript: 6.0.3 + vue: 3.5.35(typescript@6.0.3) + dev: true + + /@vueuse/core@14.3.0(vue@3.5.35): + resolution: {integrity: sha512-aHfz47g0ZhMtTVHmIzMVpJy8ePhhOy68GY5bv110+5DVtZ+W7BsOx+m61UNQqfrWyPztIHIanWa3E2tib3NFIw==} + peerDependencies: + vue: ^3.5.0 + dependencies: + '@types/web-bluetooth': 0.0.21 + '@vueuse/metadata': 14.3.0 + '@vueuse/shared': 14.3.0(vue@3.5.35) + vue: 3.5.35(typescript@6.0.3) + dev: false + + /@vueuse/metadata@14.3.0: + resolution: {integrity: sha512-BwxmbAzwAVF50+MW57GXOUEV61nFBGnlBvrTqj49PqWJu3uw7hdu72ztXeZ33RdZtDY6kO+bfCAE1PCn88Tktw==} + dev: false + + /@vueuse/shared@14.3.0(vue@3.5.35): + resolution: {integrity: sha512-bZpge9eSXwa4ToSiqJ7j6KRwhAsneMFoSz3LMWKQDkqimm3D/tbFlrklrs/IOqC8tEcYmXQZJ6N0UrjhBirVCg==} + peerDependencies: + vue: ^3.5.0 + dependencies: + vue: 3.5.35(typescript@6.0.3) + dev: false + /acorn-jsx@5.3.2(acorn@8.15.0): resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -3946,6 +4458,10 @@ packages: uri-js: 4.4.1 dev: true + /alien-signals@3.2.1: + resolution: {integrity: sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g==} + dev: true + /ansi-escapes@4.3.2: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} engines: {node: '>=8'} @@ -4193,6 +4709,10 @@ packages: engines: {node: '>=8'} dev: true + /birpc@2.9.0: + resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==} + dev: false + /bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} dependencies: @@ -4483,6 +5003,13 @@ packages: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} dev: true + /copy-anything@4.0.5: + resolution: {integrity: sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==} + engines: {node: '>=18'} + dependencies: + is-what: 5.5.0 + dev: false + /cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -4556,6 +5083,21 @@ packages: resolution: {integrity: sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==} dev: false + /date-fns-tz@2.0.1(date-fns@2.30.0): + resolution: {integrity: sha512-fJCG3Pwx8HUoLhkepdsP7Z5RsucUi+ZBOxyM5d0ZZ6c4SdYustq0VMmOu6Wf7bli+yS/Jwp91TOCqn9jMcVrUA==} + peerDependencies: + date-fns: 2.x + dependencies: + date-fns: 2.30.0 + dev: false + + /date-fns@2.30.0: + resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} + engines: {node: '>=0.11'} + dependencies: + '@babel/runtime': 7.28.4 + dev: false + /date-fns@4.1.0: resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} dev: false @@ -4627,6 +5169,10 @@ packages: object-keys: 1.1.1 dev: true + /defu@6.1.7: + resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} + dev: false + /delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -4640,8 +5186,6 @@ packages: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} requiresBuild: true - dev: false - optional: true /detect-node-es@1.1.0: resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} @@ -4728,6 +5272,10 @@ packages: engines: {node: '>=0.12'} dev: true + /entities@7.0.1: + resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} + engines: {node: '>=0.12'} + /es-abstract@1.24.0: resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==} engines: {node: '>= 0.4'} @@ -5339,6 +5887,9 @@ packages: '@types/unist': 3.0.3 dev: false + /estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + /estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} dependencies: @@ -5420,6 +5971,18 @@ packages: picomatch: 4.0.3 dev: true + /fdir@6.5.0(picomatch@4.0.4): + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + dependencies: + picomatch: 4.0.4 + dev: true + /file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -5772,6 +6335,10 @@ packages: '@types/hast': 3.0.4 dev: false + /hookable@5.5.3: + resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + dev: false + /html-encoding-sniffer@4.0.0: resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} engines: {node: '>=18'} @@ -6161,6 +6728,11 @@ packages: get-intrinsic: 1.3.0 dev: true + /is-what@5.5.0: + resolution: {integrity: sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==} + engines: {node: '>=18'} + dev: false + /isarray@2.0.5: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} dev: true @@ -6357,6 +6929,128 @@ packages: type-check: 0.4.0 dev: true + /lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + requiresBuild: true + dev: true + optional: true + + /lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + requiresBuild: true + dev: true + optional: true + + /lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + requiresBuild: true + dev: true + optional: true + + /lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + requiresBuild: true + dev: true + optional: true + + /lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + dev: true + /lilconfig@3.1.3: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} @@ -6390,6 +7084,10 @@ packages: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true + /lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + dev: false + /log-symbols@4.1.0: resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} engines: {node: '>=10'} @@ -6441,6 +7139,14 @@ packages: react: 19.2.0 dev: true + /lucide-vue-next@1.0.0(vue@3.5.35): + resolution: {integrity: sha512-V6SPvx1IHTj/UY+FrIYWV5faISsPSb8BnWSFDxAtezWKvWc9ZZ40PDrdu1/Qb5vg4lHWr1hs1BAMGVGm6V1Xdg==} + peerDependencies: + vue: '>=3.0.1' + dependencies: + vue: 3.5.35(typescript@6.0.3) + dev: false + /lz-string@1.5.0: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true @@ -6450,7 +7156,6 @@ packages: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - dev: true /magicast@0.3.5: resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} @@ -6934,6 +7639,10 @@ packages: engines: {node: '>=16 || 14 >=14.17'} dev: true + /mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + dev: false + /mlly@1.8.0: resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} dependencies: @@ -6946,6 +7655,10 @@ packages: /ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + /muggle-string@0.4.1: + resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} + dev: true + /mute-stream@1.0.0: resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -7132,6 +7845,10 @@ packages: es-object-atoms: 1.1.1 dev: true + /ohash@2.0.11: + resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + dev: false + /once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} dependencies: @@ -7260,6 +7977,10 @@ packages: entities: 6.0.1 dev: true + /path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + dev: true + /path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -7309,6 +8030,10 @@ packages: resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} dev: true + /perfect-debounce@1.0.0: + resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + dev: false + /picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -7322,6 +8047,11 @@ packages: engines: {node: '>=12'} dev: true + /picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + dev: true + /pify@2.3.0: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} @@ -7428,7 +8158,6 @@ packages: nanoid: 3.3.12 picocolors: 1.1.1 source-map-js: 1.2.1 - dev: true /postcss@8.5.6: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} @@ -7704,6 +8433,26 @@ packages: - supports-color dev: false + /reka-ui@2.9.8(vue@3.5.35): + resolution: {integrity: sha512-7dxaBJ6nQ0zOQZXPV45219tTEgZPstmihBLS9ABPhSiPiJ8SiF0sacfZHFaBptS0v9N4tzsevq+8MNBpE4p5JQ==} + peerDependencies: + vue: '>= 3.4.0' + dependencies: + '@floating-ui/dom': 1.7.4 + '@floating-ui/vue': 1.1.11(vue@3.5.35) + '@internationalized/date': 3.12.2 + '@internationalized/number': 3.6.7 + '@tanstack/vue-virtual': 3.13.26(vue@3.5.35) + '@vueuse/core': 14.3.0(vue@3.5.35) + '@vueuse/shared': 14.3.0(vue@3.5.35) + aria-hidden: 1.2.6 + defu: 6.1.7 + ohash: 2.0.11 + vue: 3.5.35(typescript@6.0.3) + transitivePeerDependencies: + - '@vue/composition-api' + dev: false + /remark-mdx@3.1.1: resolution: {integrity: sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==} dependencies: @@ -7792,6 +8541,10 @@ packages: engines: {iojs: '>=1.0.0', node: '>=0.10.0'} dev: true + /rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + dev: false + /rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} deprecated: Rimraf versions prior to v4 are no longer supported @@ -7807,6 +8560,31 @@ packages: glob: 10.5.0 dev: true + /rolldown@1.0.2: + resolution: {integrity: sha512-oZx5zVDtVB44AW3eaifgDml1gWRDZGvjcfdxonE4swNPG98PrrXjaO/KrnUjzlMnztCCRVlUueA1kCXhARGk6g==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + dependencies: + '@oxc-project/types': 0.132.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.2 + '@rolldown/binding-darwin-arm64': 1.0.2 + '@rolldown/binding-darwin-x64': 1.0.2 + '@rolldown/binding-freebsd-x64': 1.0.2 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.2 + '@rolldown/binding-linux-arm64-gnu': 1.0.2 + '@rolldown/binding-linux-arm64-musl': 1.0.2 + '@rolldown/binding-linux-ppc64-gnu': 1.0.2 + '@rolldown/binding-linux-s390x-gnu': 1.0.2 + '@rolldown/binding-linux-x64-gnu': 1.0.2 + '@rolldown/binding-linux-x64-musl': 1.0.2 + '@rolldown/binding-openharmony-arm64': 1.0.2 + '@rolldown/binding-wasm32-wasi': 1.0.2 + '@rolldown/binding-win32-arm64-msvc': 1.0.2 + '@rolldown/binding-win32-x64-msvc': 1.0.2 + dev: true + /rollup@4.53.3: resolution: {integrity: sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -8068,6 +8846,11 @@ packages: resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} dev: false + /speakingurl@14.0.1: + resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} + engines: {node: '>=0.10.0'} + dev: false + /stable-hash@0.0.5: resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} dev: true @@ -8282,6 +9065,13 @@ packages: ts-interface-checker: 0.1.13 dev: true + /superjson@2.2.6: + resolution: {integrity: sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==} + engines: {node: '>=16'} + dependencies: + copy-anything: 4.0.5 + dev: false + /supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -8375,6 +9165,14 @@ packages: picomatch: 4.0.3 dev: true + /tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + dev: true + /tinypool@0.8.4: resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==} engines: {node: '>=14.0.0'} @@ -8533,6 +9331,11 @@ packages: engines: {node: '>=10'} dev: false + /type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + dev: false + /typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} @@ -8584,6 +9387,11 @@ packages: hasBin: true dev: true + /typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + engines: {node: '>=14.17'} + hasBin: true + /ufo@1.6.1: resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} dev: true @@ -8601,6 +9409,10 @@ packages: /undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + /undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + dev: true + /unified@11.0.5: resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} dependencies: @@ -8755,6 +9567,32 @@ packages: /util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + /v-calendar@3.1.2(@popperjs/core@2.11.8)(vue@3.5.35): + resolution: {integrity: sha512-QDWrnp4PWCpzUblctgo4T558PrHgHzDtQnTeUNzKxfNf29FkCeFpwGd9bKjAqktaa2aJLcyRl45T5ln1ku34kg==} + peerDependencies: + '@popperjs/core': ^2.0.0 + vue: ^3.2.0 + dependencies: + '@popperjs/core': 2.11.8 + '@types/lodash': 4.17.24 + '@types/resize-observer-browser': 0.1.11 + date-fns: 2.30.0 + date-fns-tz: 2.0.1(date-fns@2.30.0) + lodash: 4.18.1 + vue: 3.5.35(typescript@6.0.3) + vue-screen-utils: 1.0.0-beta.13(vue@3.5.35) + dev: false + + /vee-validate@4.15.1(vue@3.5.35): + resolution: {integrity: sha512-DkFsiTwEKau8VIxyZBGdO6tOudD+QoUBPuHj3e6QFqmbfCRj1ArmYWue9lEp6jLSWBIw4XPlDLjFIZNLdRAMSg==} + peerDependencies: + vue: ^3.4.26 + dependencies: + '@vue/devtools-api': 7.7.9 + type-fest: 4.41.0 + vue: 3.5.35(typescript@6.0.3) + dev: false + /vfile-message@4.0.3: resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} dependencies: @@ -8881,6 +9719,59 @@ packages: fsevents: 2.3.3 dev: true + /vite@8.0.14(@types/node@24.12.4): + resolution: {integrity: sha512-s4BJJ+5y1pYL6Otw51FHhVJQhPnuRinKig64g/1+EUNaJsd3gCKdD31IPFvswUgW9/60QT9oFHbZHbQK5imcxw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.1.18 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + dependencies: + '@types/node': 24.12.4 + lightningcss: 1.32.0 + picomatch: 4.0.4 + postcss: 8.5.15 + rolldown: 1.0.2 + tinyglobby: 0.2.17 + optionalDependencies: + fsevents: 2.3.3 + dev: true + /vitest@1.6.1(@types/node@20.19.25)(jsdom@24.1.3): resolution: {integrity: sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==} engines: {node: ^18.0.0 || >=20.0.0} @@ -8939,6 +9830,59 @@ packages: - terser dev: true + /vscode-uri@3.1.0: + resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} + dev: true + + /vue-demi@0.14.10(vue@3.5.35): + resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + peerDependencies: + '@vue/composition-api': ^1.0.0-rc.1 + vue: ^3.0.0-0 || ^2.6.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + dependencies: + vue: 3.5.35(typescript@6.0.3) + dev: false + + /vue-screen-utils@1.0.0-beta.13(vue@3.5.35): + resolution: {integrity: sha512-EJ/8TANKhFj+LefDuOvZykwMr3rrLFPLNb++lNBqPOpVigT2ActRg6icH9RFQVm4nHwlHIHSGm5OY/Clar9yIg==} + peerDependencies: + vue: ^3.2.0 + dependencies: + vue: 3.5.35(typescript@6.0.3) + dev: false + + /vue-tsc@3.3.3(typescript@6.0.3): + resolution: {integrity: sha512-SWUEG7YRUeDJHT7Xsuhf02elYX2gxPzzAII7OxDAh4KNOr4QHQ0Lls0YfnaO5GNd560CwVa2HTfdqmA5MqvRqQ==} + hasBin: true + peerDependencies: + typescript: '>=5.0.0' + dependencies: + '@volar/typescript': 2.4.28 + '@vue/language-core': 3.3.3 + typescript: 6.0.3 + dev: true + + /vue@3.5.35(typescript@6.0.3): + resolution: {integrity: sha512-cx89fnr+0kVGHiNFG6y6s0bdjypJRFNZn6x3WPstNdQR1bi1mbB7h4v5IBGTsPJU3nK1+0Iqj3Zf+hZWMieR4Q==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@vue/compiler-dom': 3.5.35 + '@vue/compiler-sfc': 3.5.35 + '@vue/runtime-dom': 3.5.35 + '@vue/server-renderer': 3.5.35(vue@3.5.35) + '@vue/shared': 3.5.35 + typescript: 6.0.3 + /w3c-xmlserializer@5.0.0: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} @@ -9124,6 +10068,10 @@ packages: engines: {node: '>=18'} dev: false + /zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + dev: false + /zod@4.4.3: resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} dev: false diff --git a/poc/.gitignore b/poc/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/poc/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/poc/README.md b/poc/README.md new file mode 100644 index 0000000..33895ab --- /dev/null +++ b/poc/README.md @@ -0,0 +1,5 @@ +# Vue 3 + TypeScript + Vite + +This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 ` + + diff --git a/poc/package.json b/poc/package.json new file mode 100644 index 0000000..7bd4b54 --- /dev/null +++ b/poc/package.json @@ -0,0 +1,29 @@ +{ + "name": "poc", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vue-tsc -b && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@popperjs/core": "^2.11.8", + "@vee-validate/zod": "^4.15.1", + "lucide-vue-next": "^1.0.0", + "reka-ui": "^2.9.8", + "v-calendar": "^3.1.2", + "vee-validate": "^4.15.1", + "vue": "^3.5.34", + "zod": "3" + }, + "devDependencies": { + "@types/node": "^24.12.3", + "@vitejs/plugin-vue": "^6.0.6", + "@vue/tsconfig": "^0.9.1", + "typescript": "~6.0.2", + "vite": "^8.0.12", + "vue-tsc": "^3.2.8" + } +} diff --git a/poc/public/favicon.svg b/poc/public/favicon.svg new file mode 100644 index 0000000..6893eb1 --- /dev/null +++ b/poc/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/poc/public/icons.svg b/poc/public/icons.svg new file mode 100644 index 0000000..e952219 --- /dev/null +++ b/poc/public/icons.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/poc/src/App.vue b/poc/src/App.vue new file mode 100644 index 0000000..e502ae2 --- /dev/null +++ b/poc/src/App.vue @@ -0,0 +1,235 @@ + + + + + diff --git a/poc/src/assets/hero.png b/poc/src/assets/hero.png new file mode 100644 index 0000000000000000000000000000000000000000..02251f4b956c55af2d76fd0788124d7eee2b45eb GIT binary patch literal 13057 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 P00000NkvXXu0mjft=yBf literal 0 HcmV?d00001 diff --git a/poc/src/assets/vite.svg b/poc/src/assets/vite.svg new file mode 100644 index 0000000..5101b67 --- /dev/null +++ b/poc/src/assets/vite.svg @@ -0,0 +1 @@ +Vite diff --git a/poc/src/assets/vue.svg b/poc/src/assets/vue.svg new file mode 100644 index 0000000..770e9d3 --- /dev/null +++ b/poc/src/assets/vue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/poc/src/components/HelloWorld.vue b/poc/src/components/HelloWorld.vue new file mode 100644 index 0000000..c232865 --- /dev/null +++ b/poc/src/components/HelloWorld.vue @@ -0,0 +1,95 @@ + + + diff --git a/poc/src/components/RekaComboboxDemo.vue b/poc/src/components/RekaComboboxDemo.vue new file mode 100644 index 0000000..c7f4076 --- /dev/null +++ b/poc/src/components/RekaComboboxDemo.vue @@ -0,0 +1,409 @@ + + + + + diff --git a/poc/src/components/RekaDialogDemo.vue b/poc/src/components/RekaDialogDemo.vue new file mode 100644 index 0000000..ac9219f --- /dev/null +++ b/poc/src/components/RekaDialogDemo.vue @@ -0,0 +1,121 @@ + + + + + diff --git a/poc/src/components/RekaSelectDemo.vue b/poc/src/components/RekaSelectDemo.vue new file mode 100644 index 0000000..87f4d8b --- /dev/null +++ b/poc/src/components/RekaSelectDemo.vue @@ -0,0 +1,239 @@ + + + + + diff --git a/poc/src/components/VCalendarDemo.vue b/poc/src/components/VCalendarDemo.vue new file mode 100644 index 0000000..235bc7b --- /dev/null +++ b/poc/src/components/VCalendarDemo.vue @@ -0,0 +1,158 @@ + + + + + diff --git a/poc/src/components/VeeValidateDemo.vue b/poc/src/components/VeeValidateDemo.vue new file mode 100644 index 0000000..ceaf1e0 --- /dev/null +++ b/poc/src/components/VeeValidateDemo.vue @@ -0,0 +1,203 @@ + + + + + + + diff --git a/poc/src/main.ts b/poc/src/main.ts new file mode 100644 index 0000000..2425c0f --- /dev/null +++ b/poc/src/main.ts @@ -0,0 +1,5 @@ +import { createApp } from 'vue' +import './style.css' +import App from './App.vue' + +createApp(App).mount('#app') diff --git a/poc/src/style.css b/poc/src/style.css new file mode 100644 index 0000000..527d4fb --- /dev/null +++ b/poc/src/style.css @@ -0,0 +1,296 @@ +:root { + --text: #6b6375; + --text-h: #08060d; + --bg: #fff; + --border: #e5e4e7; + --code-bg: #f4f3ec; + --accent: #aa3bff; + --accent-bg: rgba(170, 59, 255, 0.1); + --accent-border: rgba(170, 59, 255, 0.5); + --social-bg: rgba(244, 243, 236, 0.5); + --shadow: + rgba(0, 0, 0, 0.1) 0 10px 15px -3px, rgba(0, 0, 0, 0.05) 0 4px 6px -2px; + + --sans: system-ui, 'Segoe UI', Roboto, sans-serif; + --heading: system-ui, 'Segoe UI', Roboto, sans-serif; + --mono: ui-monospace, Consolas, monospace; + + font: 18px/145% var(--sans); + letter-spacing: 0.18px; + color-scheme: light dark; + color: var(--text); + background: var(--bg); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + + @media (max-width: 1024px) { + font-size: 16px; + } +} + +@media (prefers-color-scheme: dark) { + :root { + --text: #9ca3af; + --text-h: #f3f4f6; + --bg: #16171d; + --border: #2e303a; + --code-bg: #1f2028; + --accent: #c084fc; + --accent-bg: rgba(192, 132, 252, 0.15); + --accent-border: rgba(192, 132, 252, 0.5); + --social-bg: rgba(47, 48, 58, 0.5); + --shadow: + rgba(0, 0, 0, 0.4) 0 10px 15px -3px, rgba(0, 0, 0, 0.25) 0 4px 6px -2px; + } + + #social .button-icon { + filter: invert(1) brightness(2); + } +} + +body { + margin: 0; +} + +h1, +h2 { + font-family: var(--heading); + font-weight: 500; + color: var(--text-h); +} + +h1 { + font-size: 56px; + letter-spacing: -1.68px; + margin: 32px 0; + @media (max-width: 1024px) { + font-size: 36px; + margin: 20px 0; + } +} +h2 { + font-size: 24px; + line-height: 118%; + letter-spacing: -0.24px; + margin: 0 0 8px; + @media (max-width: 1024px) { + font-size: 20px; + } +} +p { + margin: 0; +} + +code, +.counter { + font-family: var(--mono); + display: inline-flex; + border-radius: 4px; + color: var(--text-h); +} + +code { + font-size: 15px; + line-height: 135%; + padding: 4px 8px; + background: var(--code-bg); +} + +.counter { + font-size: 16px; + padding: 5px 10px; + border-radius: 5px; + color: var(--accent); + background: var(--accent-bg); + border: 2px solid transparent; + transition: border-color 0.3s; + margin-bottom: 24px; + + &:hover { + border-color: var(--accent-border); + } + &:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 2px; + } +} + +.hero { + position: relative; + + .base, + .framework, + .vite { + inset-inline: 0; + margin: 0 auto; + } + + .base { + width: 170px; + position: relative; + z-index: 0; + } + + .framework, + .vite { + position: absolute; + } + + .framework { + z-index: 1; + top: 34px; + height: 28px; + transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg) + scale(1.4); + } + + .vite { + z-index: 0; + top: 107px; + height: 26px; + width: auto; + transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg) + scale(0.8); + } +} + +#app { + width: 1126px; + max-width: 100%; + margin: 0 auto; + text-align: center; + border-inline: 1px solid var(--border); + min-height: 100svh; + display: flex; + flex-direction: column; + box-sizing: border-box; +} + +#center { + display: flex; + flex-direction: column; + gap: 25px; + place-content: center; + place-items: center; + flex-grow: 1; + + @media (max-width: 1024px) { + padding: 32px 20px 24px; + gap: 18px; + } +} + +#next-steps { + display: flex; + border-top: 1px solid var(--border); + text-align: left; + + & > div { + flex: 1 1 0; + padding: 32px; + @media (max-width: 1024px) { + padding: 24px 20px; + } + } + + .icon { + margin-bottom: 16px; + width: 22px; + height: 22px; + } + + @media (max-width: 1024px) { + flex-direction: column; + text-align: center; + } +} + +#docs { + border-right: 1px solid var(--border); + + @media (max-width: 1024px) { + border-right: none; + border-bottom: 1px solid var(--border); + } +} + +#next-steps ul { + list-style: none; + padding: 0; + display: flex; + gap: 8px; + margin: 32px 0 0; + + .logo { + height: 18px; + } + + a { + color: var(--text-h); + font-size: 16px; + border-radius: 6px; + background: var(--social-bg); + display: flex; + padding: 6px 12px; + align-items: center; + gap: 8px; + text-decoration: none; + transition: box-shadow 0.3s; + + &:hover { + box-shadow: var(--shadow); + } + .button-icon { + height: 18px; + width: 18px; + } + } + + @media (max-width: 1024px) { + margin-top: 20px; + flex-wrap: wrap; + justify-content: center; + + li { + flex: 1 1 calc(50% - 8px); + } + + a { + width: 100%; + justify-content: center; + box-sizing: border-box; + } + } +} + +#spacer { + height: 88px; + border-top: 1px solid var(--border); + @media (max-width: 1024px) { + height: 48px; + } +} + +.ticks { + position: relative; + width: 100%; + + &::before, + &::after { + content: ''; + position: absolute; + top: -4.5px; + border: 5px solid transparent; + } + + &::before { + left: 0; + border-left-color: var(--border); + } + &::after { + right: 0; + border-right-color: var(--border); + } +} diff --git a/poc/tsconfig.app.json b/poc/tsconfig.app.json new file mode 100644 index 0000000..5c750c5 --- /dev/null +++ b/poc/tsconfig.app.json @@ -0,0 +1,14 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "types": ["vite/client"], + + /* Linting */ + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"] +} diff --git a/poc/tsconfig.json b/poc/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/poc/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/poc/tsconfig.node.json b/poc/tsconfig.node.json new file mode 100644 index 0000000..d3c52ea --- /dev/null +++ b/poc/tsconfig.node.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "es2023", + "lib": ["ES2023"], + "module": "esnext", + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["vite.config.ts"] +} diff --git a/poc/vite.config.ts b/poc/vite.config.ts new file mode 100644 index 0000000..bbcf80c --- /dev/null +++ b/poc/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [vue()], +}) From 2dc2a56fbb41c8781caf5be8259038540b7e637e Mon Sep 17 00:00:00 2001 From: lidaixingchen <3506962253@qq.com> Date: Mon, 1 Jun 2026 00:25:02 +0800 Subject: [PATCH 02/67] =?UTF-8?q?chore:=20=E5=88=A0=E9=99=A4=E8=BF=87?= =?UTF-8?q?=E6=97=B6=E7=9A=84cursor=E8=A7=84=E5=88=99=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 移除了根目录的.cursorrules和子目录的.cursor/rules/brutxui.mdc两个规则配置文件 --- .cursor/rules/brutxui.mdc | 31 ------------------------------- .cursorrules | 22 ---------------------- 2 files changed, 53 deletions(-) delete mode 100644 .cursor/rules/brutxui.mdc delete mode 100644 .cursorrules diff --git a/.cursor/rules/brutxui.mdc b/.cursor/rules/brutxui.mdc deleted file mode 100644 index 02c5438..0000000 --- a/.cursor/rules/brutxui.mdc +++ /dev/null @@ -1,31 +0,0 @@ ---- -description: Custom styling, component blueprints, and architectural safety rules for BrutxUI components and CLI packages. -globs: packages/ui/src/components/**/*.tsx, packages/registry/registry.json, packages/cli/src/commands/**/*.ts ---- - -# BrutxUI AI & Coding Rules - -Always follow these rules when developing in this monorepo: - -## Core Visual System Rules -- **Outline Thickness:** Use `border-3 border-black` (or `dark:border-white`). Avoid soft outlines like `border-slate-100`. -- **Dropshadows:** Saturated, unblurred flat shadows only: - - `shadow-brutal` (which resolves to `box-shadow: 4px 4px 0px 0px #000`) - - `shadow-brutal-sm` (2px) - - `shadow-brutal-lg` (6px) -- **Angles:** Razor-sharp unrounded corners via `rounded-none`, or global parameter classes like `rounded-brutal`. -- **Interactive translation:** Active elements translate down and right: `active:translate-x-[2px] active:translate-y-[2px] active:shadow-[2px_2px_0px_0px_#000] transition-all`. -- **Palette Highlights:** Saturated neon yellow (`#FFE66D`), mint teal (`#4ECDC4`), and coral red (`#FF6B6B`). - -## Code Blueprints -- **Class Mergers:** Merge incoming classes using `cn` (from `@/lib/utils` or `../lib/utils`). -- **Variants:** Set styles through CVA. For atomic components (e.g. `button.tsx`), define variant properties for easy custom overriding. -- **Radix UI Accessibility:** Retain screen reader compatibility, ARIA tags, and outline highlights. - -## Security Controls -- **Path Sanitization:** When editing CLI scripts (e.g. `add.ts` or `init.ts`), always check that file destinations are validated inside safe workspaces using `isSafePath`. Normalize the paths and filter out traversing sequences (`..`). - -## Manifest Compiler -- When adding a component or layout block, describe it in `packages/registry/registry.json`. Execute the compiler and validator before finishing: - - `pnpm --filter brutx-registry build` - - `pnpm --filter brutx-registry validate` diff --git a/.cursorrules b/.cursorrules deleted file mode 100644 index ad2b89f..0000000 --- a/.cursorrules +++ /dev/null @@ -1,22 +0,0 @@ -# BrutxUI Cursor Rules - -Always adhere to these guidelines when authoring React components, layout blocks, or styling custom elements in the BrutxUI codebase: - -## Visual Style Guide -- **Borders:** Always use `border-3 border-black` (or `dark:border-white`). Never use default light slate/gray borders. -- **Shadows:** Hard offset shadows only. Use `shadow-brutal` (4px offset), `shadow-brutal-sm` (2px offset), or `shadow-brutal-lg` (6px offset). Never use standard blurred shadows (`shadow-md`, `shadow-lg`). -- **Radii:** Sharp 0px corners via `rounded-none`, or global parameter classes like `rounded-brutal`. -- **Transitions:** Buttons and interactive elements translate on press: `active:translate-x-[2px] active:translate-y-[2px] active:shadow-[2px_2px_0px_0px_#000] transition-all`. -- **Color Accents:** Use saturated high-contrast colors: Coral `#FF6B6B` (`bg-brutal-primary`), Mint Teal `#4ECDC4` (`bg-brutal-secondary`), Saturated Yellow `#FFE66D` (`bg-brutal-accent`). - -## Component Guidelines -- Use standard `class-variance-authority` (CVA) to define styling variants. -- Merge custom layout inputs using the local class merger helper `cn` from `@/lib/utils` or equivalent path. -- Follow Radix UI guidelines for modals, dialogs, popovers, and inputs to keep markup robust and fully accessible. -- Declare correct `displayName` for every component file. - -## Safe Path Resolving -- When authoring CLI tools, normalize all directory inputs. Check and reject any relative traversals (`..`) or absolute path structures that escape safe workspaces. - -## Registry Schema -- When creating a new component or pricing block, always append its configuration schema to `packages/registry/registry.json`. Then compile and validate schemas via `pnpm --filter brutx-registry build` and `pnpm --filter brutx-registry validate`. From 9b55bf66bb9db4a2b8fed78b5c6d5b7608233a44 Mon Sep 17 00:00:00 2001 From: lidaixingchen <3506962253@qq.com> Date: Mon, 1 Jun 2026 00:26:12 +0800 Subject: [PATCH 03/67] =?UTF-8?q?refactor(ui):=20=E5=B0=86=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E5=BA=93=E4=BB=8EReact=E8=BF=81=E7=A7=BB=E8=87=B3Vue?= =?UTF-8?q?=203?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 替换所有React组件为Vue单文件组件,重写导出逻辑 2. 更新依赖包名和配置,适配Vue开发环境 3. 删除废弃的React测试文件和配置 4. 新增Toast组合式函数和基础组件占位实现 5. 完善项目元信息描述 --- .gitignore | 1 + AGENTS.md | 328 ++ apps/docs/package.json | 2 +- package.json | 12 +- packages/ui/eslint.config.js | 29 + packages/ui/package.json | 110 +- packages/ui/postcss.config.js | 2 +- .../components/alert-dialog.test.tsx | 75 - .../__tests__/components/auth-card.test.tsx | 33 - .../src/__tests__/components/badge.test.tsx | 68 - .../components/brutalist-hero.test.tsx | 41 - .../src/__tests__/components/button.test.tsx | 94 - .../ui/src/__tests__/components/card.test.tsx | 64 - .../__tests__/components/checkbox.test.tsx | 47 - .../__tests__/components/combobox.test.tsx | 158 - .../src/__tests__/components/command.test.tsx | 179 - .../components/dashboard-shell.test.tsx | 33 - .../src/__tests__/components/dialog.test.tsx | 120 - .../components/dropdown-menu.test.tsx | 165 - .../__tests__/components/empty-state.test.tsx | 30 - .../ui/src/__tests__/components/form.test.tsx | 92 - .../src/__tests__/components/input.test.tsx | 51 - .../components/pricing-section.test.tsx | 23 - .../__tests__/components/progress.test.tsx | 20 - .../__tests__/components/radio-group.test.tsx | 57 - .../src/__tests__/components/select.test.tsx | 130 - .../src/__tests__/components/sheet.test.tsx | 61 - .../src/__tests__/components/slider.test.tsx | 25 - .../src/__tests__/components/switch.test.tsx | 47 - .../ui/src/__tests__/components/tabs.test.tsx | 106 - .../__tests__/components/textarea.test.tsx | 48 - .../src/__tests__/components/toast.test.tsx | 200 -- .../components/toggle-group.test.tsx | 54 - .../src/__tests__/components/toggle.test.tsx | 35 - .../src/__tests__/components/tooltip.test.tsx | 131 - .../components/waitlist-page.test.tsx | 29 - packages/ui/src/__tests__/exports.test.tsx | 30 - packages/ui/src/__tests__/lib/utils.test.ts | 53 - packages/ui/src/__tests__/setup.ts | 39 - packages/ui/src/calendar.ts | 4 +- packages/ui/src/components/Button.vue | 45 + packages/ui/src/components/Calendar.vue | 29 + packages/ui/src/components/SubmitButton.vue | 36 + packages/ui/src/components/alert-dialog.tsx | 124 - packages/ui/src/components/alert.tsx | 70 - packages/ui/src/components/auth-card.tsx | 130 - packages/ui/src/components/avatar.tsx | 77 - packages/ui/src/components/badge.tsx | 67 - packages/ui/src/components/brutalist-hero.tsx | 98 - .../{button.tsx => button-variants.ts} | 59 +- packages/ui/src/components/calendar.tsx | 250 -- packages/ui/src/components/card.tsx | 99 - packages/ui/src/components/checkbox.tsx | 37 - packages/ui/src/components/combobox.tsx | 199 -- packages/ui/src/components/command.tsx | 198 -- .../ui/src/components/dashboard-shell.tsx | 209 -- .../ui/src/components/dashboard-stats.tsx | 177 - packages/ui/src/components/dialog.tsx | 133 - packages/ui/src/components/dropdown-menu.tsx | 210 -- packages/ui/src/components/empty-state.tsx | 52 - packages/ui/src/components/form.tsx | 173 - packages/ui/src/components/input.tsx | 55 - packages/ui/src/components/label.tsx | 36 - packages/ui/src/components/pagination.tsx | 240 -- packages/ui/src/components/popover.tsx | 37 - .../ui/src/components/pricing-section.tsx | 147 - packages/ui/src/components/progress.tsx | 31 - packages/ui/src/components/radio-group.tsx | 50 - packages/ui/src/components/saas-pricing.tsx | 227 -- packages/ui/src/components/scroll-area.tsx | 47 - packages/ui/src/components/select.tsx | 172 - packages/ui/src/components/separator.tsx | 25 - packages/ui/src/components/sheet.tsx | 138 - packages/ui/src/components/skeleton.tsx | 159 - packages/ui/src/components/slider.tsx | 45 - packages/ui/src/components/spinner.tsx | 237 -- packages/ui/src/components/submit-button.tsx | 28 - packages/ui/src/components/switch.tsx | 37 - packages/ui/src/components/table.tsx | 134 - packages/ui/src/components/tabs.tsx | 62 - packages/ui/src/components/textarea.tsx | 55 - packages/ui/src/components/toast.tsx | 283 -- packages/ui/src/components/toggle-group.tsx | 61 - packages/ui/src/components/toggle.tsx | 58 - packages/ui/src/components/tooltip.tsx | 33 - packages/ui/src/components/waitlist-page.tsx | 92 - packages/ui/src/composables/useToast.ts | 15 + packages/ui/src/env.d.ts | 5 + packages/ui/src/hooks/index.ts | 5 +- packages/ui/src/index.ts | 47 +- packages/ui/src/lib/utils.test.ts | 21 + packages/ui/src/submit-button.ts | 2 +- packages/ui/tailwind.config.js | 84 +- packages/ui/tsconfig.json | 6 +- packages/ui/vite.config.ts | 58 + packages/ui/vitest.config.ts | 33 +- pnpm-lock.yaml | 2869 ++++++++--------- 97 files changed, 1985 insertions(+), 8947 deletions(-) create mode 100644 AGENTS.md create mode 100644 packages/ui/eslint.config.js delete mode 100644 packages/ui/src/__tests__/components/alert-dialog.test.tsx delete mode 100644 packages/ui/src/__tests__/components/auth-card.test.tsx delete mode 100644 packages/ui/src/__tests__/components/badge.test.tsx delete mode 100644 packages/ui/src/__tests__/components/brutalist-hero.test.tsx delete mode 100644 packages/ui/src/__tests__/components/button.test.tsx delete mode 100644 packages/ui/src/__tests__/components/card.test.tsx delete mode 100644 packages/ui/src/__tests__/components/checkbox.test.tsx delete mode 100644 packages/ui/src/__tests__/components/combobox.test.tsx delete mode 100644 packages/ui/src/__tests__/components/command.test.tsx delete mode 100644 packages/ui/src/__tests__/components/dashboard-shell.test.tsx delete mode 100644 packages/ui/src/__tests__/components/dialog.test.tsx delete mode 100644 packages/ui/src/__tests__/components/dropdown-menu.test.tsx delete mode 100644 packages/ui/src/__tests__/components/empty-state.test.tsx delete mode 100644 packages/ui/src/__tests__/components/form.test.tsx delete mode 100644 packages/ui/src/__tests__/components/input.test.tsx delete mode 100644 packages/ui/src/__tests__/components/pricing-section.test.tsx delete mode 100644 packages/ui/src/__tests__/components/progress.test.tsx delete mode 100644 packages/ui/src/__tests__/components/radio-group.test.tsx delete mode 100644 packages/ui/src/__tests__/components/select.test.tsx delete mode 100644 packages/ui/src/__tests__/components/sheet.test.tsx delete mode 100644 packages/ui/src/__tests__/components/slider.test.tsx delete mode 100644 packages/ui/src/__tests__/components/switch.test.tsx delete mode 100644 packages/ui/src/__tests__/components/tabs.test.tsx delete mode 100644 packages/ui/src/__tests__/components/textarea.test.tsx delete mode 100644 packages/ui/src/__tests__/components/toast.test.tsx delete mode 100644 packages/ui/src/__tests__/components/toggle-group.test.tsx delete mode 100644 packages/ui/src/__tests__/components/toggle.test.tsx delete mode 100644 packages/ui/src/__tests__/components/tooltip.test.tsx delete mode 100644 packages/ui/src/__tests__/components/waitlist-page.test.tsx delete mode 100644 packages/ui/src/__tests__/exports.test.tsx delete mode 100644 packages/ui/src/__tests__/lib/utils.test.ts delete mode 100644 packages/ui/src/__tests__/setup.ts create mode 100644 packages/ui/src/components/Button.vue create mode 100644 packages/ui/src/components/Calendar.vue create mode 100644 packages/ui/src/components/SubmitButton.vue delete mode 100644 packages/ui/src/components/alert-dialog.tsx delete mode 100644 packages/ui/src/components/alert.tsx delete mode 100644 packages/ui/src/components/auth-card.tsx delete mode 100644 packages/ui/src/components/avatar.tsx delete mode 100644 packages/ui/src/components/badge.tsx delete mode 100644 packages/ui/src/components/brutalist-hero.tsx rename packages/ui/src/components/{button.tsx => button-variants.ts} (71%) delete mode 100644 packages/ui/src/components/calendar.tsx delete mode 100644 packages/ui/src/components/card.tsx delete mode 100644 packages/ui/src/components/checkbox.tsx delete mode 100644 packages/ui/src/components/combobox.tsx delete mode 100644 packages/ui/src/components/command.tsx delete mode 100644 packages/ui/src/components/dashboard-shell.tsx delete mode 100644 packages/ui/src/components/dashboard-stats.tsx delete mode 100644 packages/ui/src/components/dialog.tsx delete mode 100644 packages/ui/src/components/dropdown-menu.tsx delete mode 100644 packages/ui/src/components/empty-state.tsx delete mode 100644 packages/ui/src/components/form.tsx delete mode 100644 packages/ui/src/components/input.tsx delete mode 100644 packages/ui/src/components/label.tsx delete mode 100644 packages/ui/src/components/pagination.tsx delete mode 100644 packages/ui/src/components/popover.tsx delete mode 100644 packages/ui/src/components/pricing-section.tsx delete mode 100644 packages/ui/src/components/progress.tsx delete mode 100644 packages/ui/src/components/radio-group.tsx delete mode 100644 packages/ui/src/components/saas-pricing.tsx delete mode 100644 packages/ui/src/components/scroll-area.tsx delete mode 100644 packages/ui/src/components/select.tsx delete mode 100644 packages/ui/src/components/separator.tsx delete mode 100644 packages/ui/src/components/sheet.tsx delete mode 100644 packages/ui/src/components/skeleton.tsx delete mode 100644 packages/ui/src/components/slider.tsx delete mode 100644 packages/ui/src/components/spinner.tsx delete mode 100644 packages/ui/src/components/submit-button.tsx delete mode 100644 packages/ui/src/components/switch.tsx delete mode 100644 packages/ui/src/components/table.tsx delete mode 100644 packages/ui/src/components/tabs.tsx delete mode 100644 packages/ui/src/components/textarea.tsx delete mode 100644 packages/ui/src/components/toast.tsx delete mode 100644 packages/ui/src/components/toggle-group.tsx delete mode 100644 packages/ui/src/components/toggle.tsx delete mode 100644 packages/ui/src/components/tooltip.tsx delete mode 100644 packages/ui/src/components/waitlist-page.tsx create mode 100644 packages/ui/src/composables/useToast.ts create mode 100644 packages/ui/src/env.d.ts create mode 100644 packages/ui/src/lib/utils.test.ts create mode 100644 packages/ui/vite.config.ts diff --git a/.gitignore b/.gitignore index 80eb33d..bedff81 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ build .idea .trae .cursor +.cursorrules *.swp *.swo diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..9bb7afa --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,328 @@ +# AGENTS.md — BrutxUI Vue 3 + +This file provides context and instructions for AI agents working on the BrutxUI Vue 3 codebase. + +--- + +## Project Overview + +BrutxUI Vue 3 is a Neo-Brutalist UI component library for **Vue 3** and **Tailwind CSS**. It is a Vue 3 port of the original React-based BrutxUI, distributed as a copy-paste-first component registry via a CLI tool. + +The monorepo is managed with **pnpm workspaces** and contains the following packages: + +| Package | Path | Description | +|---------|------|-------------| +| `brutx-ui-vue` | `packages/ui/` | Core Vue 3 component library | +| `brutx` (CLI) | `packages/cli/` | CLI tool for `init` and `add` commands | +| `brutx-registry` | `packages/registry/` | Compiled JSON component registry | +| `brutx-shared` | `packages/shared/` | Shared types and component metadata | +| `docs` | `apps/docs/` | Next.js 15 documentation site | + +--- + +## Tech Stack + +- **Framework:** Vue 3.5+ (Composition API with ` + + +``` + +### Variant Definitions + +Variants are defined in separate `.ts` files using CVA, colocated with the component: + +```ts +import { cva } from 'class-variance-authority' + +export const componentVariants = cva( + [ + 'base-classes-here', + ], + { + variants: { + variant: { default: '...', primary: '...' }, + size: { sm: '...', default: '...', lg: '...' }, + }, + defaultVariants: { + variant: 'default', + size: 'default', + }, + } +) +``` + +### Key Rules + +1. **Always use `cn()`** from `../lib/utils` (or `@/lib/utils`) to merge classes — never concatenate class strings directly. +2. **Always use CVA** for variant definitions — never inline conditional classes in templates. +3. **Always use `reka-ui`** primitives for accessible headless behavior (Dialog, Popover, Tooltip, etc.) — this is the Vue equivalent of Radix UI. +4. **Always use `defineProps()`** with TypeScript interface — do not use the runtime props declaration syntax. +5. **Always use `withDefaults()`** for prop defaults when using TypeScript interface syntax. +6. **Always use `computed()`** for dynamic class merging — never call `cn()` directly in the template. +7. **Always export new components** from `src/index.ts` — add both the component and its variants export. + +--- + +## Neo-Brutalist Visual System + +### Core Design Tokens (CSS Custom Properties) + +All visual tokens are defined in `src/styles.css` under `:root` and `.dark`: + +| Token | Default (Light) | Default (Dark) | Purpose | +|-------|-----------------|----------------|---------| +| `--brutal-border-width` | `3px` | `3px` | Border thickness | +| `--brutal-border-color` | `#000000` | `#ffffff` | Border color | +| `--brutal-shadow-offset-x` | `4px` | `4px` | Shadow X offset | +| `--brutal-shadow-offset-y` | `4px` | `4px` | Shadow Y offset | +| `--brutal-shadow-color` | `#000000` | `#ffffff` | Shadow color | +| `--brutal-radius` | `0px` | `0px` | Border radius | +| `--brutal-bg` | `#ffffff` | `#141414` | Background | +| `--brutal-fg` | `#000000` | `#ffffff` | Foreground | +| `--brutal-primary` | `#FF6B6B` | `#FF6B6B` | Primary accent (Coral) | +| `--brutal-secondary` | `#4ECDC4` | `#4ECDC4` | Secondary accent (Mint Teal) | +| `--brutal-accent` | `#FFE66D` | `#FFE66D` | Accent (Saturated Yellow) | +| `--brutal-destructive` | `#EF476F` | `#EF476F` | Destructive color | +| `--brutal-success` | `#7FB069` | `#7FB069` | Success color | +| `--brutal-muted` | `#f3f4f6` | `#1e1e1e` | Muted background | +| `--brutal-ring` | `#000000` | `#ffffff` | Focus ring color | +| `--brutal-pressed-offset` | `2px` | `2px` | Active press offset | + +### Theme Presets + +Three built-in presets override the CSS custom properties: + +1. **`.theme-classic`** — Deep black shadows, neon accents, sharp corners (default) +2. **`.theme-pastel`** — Softer colors, lighter contrast, `8px` corners, `2px` borders +3. **`.theme-mono`** — Grayscale colors, heavier `4px` borders, `5px` shadow offset + +### Tailwind Utility Classes + +These are defined in `tailwind.config.js` and `brutalism-plugin.js`: + +- **Borders:** `border-3`, `border-brutal`, `rounded-brutal` +- **Shadows:** `shadow-brutal` (4px), `shadow-brutal-sm` (2px), `shadow-brutal-lg` (6px), `shadow-brutal-xl` (8px) +- **Colors:** `bg-brutal-bg`, `text-brutal-fg`, `bg-brutal-primary`, `bg-brutal-secondary`, `bg-brutal-accent`, `bg-brutal-destructive`, `bg-brutal-success`, `bg-brutal-muted`, `ring-brutal-ring` +- **Plugin utilities:** `nb-border`, `nb-shadow`, `nb-press`, `nb-font`, `nb-hover`, `nb-active`, `nb-focus`, `nb-disabled` +- **Plugin components:** `nb-btn`, `nb-card`, `nb-input` + +### Visual Rules + +1. **Thick borders:** Always use `border-3 border-brutal` (or `border-brutal` which uses the CSS variable). Never use thin slate borders. +2. **Flat shadows:** Only hard offset shadows (`shadow-brutal*`). Never use blurred or spread shadows. +3. **Sharp corners:** Default to `rounded-brutal` (respects `--brutal-radius`). Never hardcode `rounded-md` or similar. +4. **Press feedback:** Buttons translate down on active: `active:translate-y-[var(--brutal-pressed-offset,2px)] active:shadow-none transition-all`. +5. **Hover feedback:** Elements lift on hover: `hover:shadow-brutal-lg hover:-translate-x-0.5 hover:-translate-y-0.5`. +6. **High-contrast accents:** Use the brutalism color palette via CSS variables — never hardcode arbitrary colors. + +--- + +## Import Conventions + +- **Path alias:** `@/` maps to `src/` (configured in both `tsconfig.json` and `vite.config.ts`) +- **Internal imports** within `packages/ui/src/` use relative paths (e.g., `../lib/utils`) +- **reka-ui imports:** Import from `reka-ui` directly (e.g., `import { Primitive } from 'reka-ui'`) +- **Icon imports:** Import from `lucide-vue-next` (e.g., `import { Loader2 } from 'lucide-vue-next'`) + +--- + +## Testing Conventions + +- **Framework:** Vitest with jsdom environment and `@vue/test-utils` +- **Test file location:** Colocated with source (e.g., `src/lib/utils.test.ts`) +- **Test pattern:** `src/**/*.{test,spec}.{ts,tsx}` +- **Globals:** Vitest globals are enabled (`globals: true` in vitest.config.ts) +- **Mount pattern:** + +```ts +import { mount } from '@vue/test-utils' +import Component from './Component.vue' + +describe('Component', () => { + it('renders', () => { + const wrapper = mount(Component, { + props: { variant: 'default' }, + }) + expect(wrapper.find('button').exists()).toBe(true) + }) +}) +``` + +--- + +## Build & Exports + +The UI package uses Vite library mode with multiple entry points: + +| Entry | Export Path | Contents | +|-------|-------------|----------| +| `index` | `brutx-ui-vue` | Main components and utilities | +| `calendar` | `brutx-ui-vue/calendar` | Calendar component + v-calendar | +| `submit-button` | `brutx-ui-vue/submit-button` | SubmitButton component | +| `hooks` | `brutx-ui-vue/hooks` | Composables/hooks | +| `brutalism-plugin` | `brutx-ui-vue/brutalism-plugin` | Tailwind CSS plugin | +| `styles.css` | `brutx-ui-vue/styles.css` | Global CSS with design tokens | + +Externalized dependencies (not bundled): `vue`, `tailwindcss`, `reka-ui`, `@vueuse/*`, `v-calendar`, `vee-validate`, `@vee-validate/*` + +Output formats: ESM (`.mjs`) and CJS (`.cjs`) with type declarations (`.d.ts`) + +--- + +## ESLint Rules + +- `vue/multi-word-component-names`: **off** — single-word component names are allowed +- `vue/max-attributes-per-line`: **off** +- `vue/html-indent`: **off** +- `@typescript-eslint/no-unused-vars`: **warn** (args prefixed with `_` are ignored) +- `@typescript-eslint/no-explicit-any`: **warn** + +--- + +## Security Requirements + +1. **Path safety in CLI:** When working on `packages/cli/`, always normalize paths and validate with `isSafePath` to prevent directory traversal. +2. **No duplicate CSS tokens:** When updating `styles.css`, ensure CSS custom properties are not duplicated. +3. **No hardcoded secrets:** Never expose or log secrets, API keys, or tokens. + +--- + +## Current Component Status + +The Vue 3 port is in progress. Currently implemented: + +| Component | File | Status | +|-----------|------|--------| +| Button | `src/components/Button.vue` | Implemented with full CVA variants | +| Calendar | `src/components/Calendar.vue` | Placeholder (Phase 3) | +| SubmitButton | `src/components/SubmitButton.vue` | Implemented | +| useToast | `src/composables/useToast.ts` | Implemented | + +Components from the original React version that need Vue 3 ports: + +Alert, AlertDialog, Avatar, Badge, Card, Checkbox, Combobox, Command, DashboardStats, Dialog, DropdownMenu, EmptyState, Form, Input, Label, Pagination, Popover, Progress, RadioGroup, ScrollArea, Select, Separator, Sheet, Skeleton, Slider, Spinner, Switch, Table, Tabs, Textarea, Toast, Toggle, ToggleGroup, Tooltip, SaaSPricing, BrutalistHero, PricingSection, AuthCard, DashboardShell, WaitlistPage + +--- + +## Code Style + +- **No comments** in code unless explicitly requested +- **No magic numbers** — use CSS custom properties or named constants +- **No hardcoded values** — reference design tokens via CSS variables +- **4-space indentation** in TypeScript and Vue SFC +- **Single quotes** for strings in TypeScript +- **PascalCase** for component file names (e.g., `Button.vue`, `SubmitButton.vue`) +- **kebab-case** for variant file names (e.g., `button-variants.ts`) +- **camelCase** for composable file names (e.g., `useToast.ts`) diff --git a/apps/docs/package.json b/apps/docs/package.json index 5bee4e1..537b16b 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -21,7 +21,7 @@ "@radix-ui/react-slider": "^1.3.6", "@radix-ui/react-toggle": "^1.1.10", "@radix-ui/react-toggle-group": "^1.1.11", - "brutx-ui": "workspace:*", + "brutx-ui-vue": "workspace:*", "next": "15.5.18", "next-themes": "^0.2.1", "react": "^19.0.0", diff --git a/package.json b/package.json index 22b2ce8..c936c7b 100644 --- a/package.json +++ b/package.json @@ -2,18 +2,18 @@ "name": "brutx-monorepo", "version": "1.0.0", "private": true, - "description": "Neo-Brutalism UI Library Monorepo", + "description": "Neo-Brutalism UI Library Monorepo (Vue 3)", "scripts": { "dev": "pnpm --filter docs dev", - "build": "pnpm --filter brutx-ui build && pnpm --filter docs build", - "build:ui": "pnpm --filter brutx-ui build", + "build": "pnpm --filter brutx-ui-vue build", + "build:ui": "pnpm --filter brutx-ui-vue build", "build:docs": "pnpm --filter docs build", "lint": "pnpm -r lint", "clean": "pnpm -r clean", "typecheck": "pnpm -r typecheck", - "test": "pnpm --filter brutx-ui test && pnpm --filter brutx test", - "test:watch": "pnpm --filter brutx-ui test:watch", - "test:coverage": "pnpm --filter brutx-ui test:coverage" + "test": "pnpm --filter brutx-ui-vue test", + "test:watch": "pnpm --filter brutx-ui-vue test:watch", + "test:coverage": "pnpm --filter brutx-ui-vue test:coverage" }, "devDependencies": { "typescript": "^5.7.2" diff --git a/packages/ui/eslint.config.js b/packages/ui/eslint.config.js new file mode 100644 index 0000000..91d5093 --- /dev/null +++ b/packages/ui/eslint.config.js @@ -0,0 +1,29 @@ +import js from '@eslint/js' +import tseslint from 'typescript-eslint' +import pluginVue from 'eslint-plugin-vue' + +export default tseslint.config( + { + ignores: ['**/dist/**', '**/node_modules/**', '**/*.d.ts', '**/brutalism-plugin.js'], + }, + js.configs.recommended, + ...tseslint.configs.recommended, + ...pluginVue.configs['flat/recommended'], + { + files: ['*.vue', '**/*.vue'], + languageOptions: { + parserOptions: { + parser: tseslint.parser, + }, + }, + }, + { + rules: { + 'vue/multi-word-component-names': 'off', + 'vue/max-attributes-per-line': 'off', + 'vue/html-indent': 'off', + '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }], + '@typescript-eslint/no-explicit-any': 'warn', + }, + } +) diff --git a/packages/ui/package.json b/packages/ui/package.json index 90d1d54..cdd3785 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,49 +1,34 @@ { - "name": "brutx-ui", - "version": "0.3.0", - "description": "A Neo-Brutalism styled React UI component library", - "main": "./dist/index.js", + "name": "brutx-ui-vue", + "version": "0.1.0", + "description": "A Neo-Brutalism styled Vue 3 UI component library", + "main": "./dist/index.cjs", "module": "./dist/index.mjs", "types": "./dist/index.d.ts", "exports": { ".": { "types": "./dist/index.d.ts", "import": "./dist/index.mjs", - "require": "./dist/index.js" + "require": "./dist/index.cjs" }, "./calendar": { "types": "./dist/calendar.d.ts", "import": "./dist/calendar.mjs", - "require": "./dist/calendar.js" + "require": "./dist/calendar.cjs" }, "./submit-button": { "types": "./dist/submit-button.d.ts", "import": "./dist/submit-button.mjs", - "require": "./dist/submit-button.js" - }, - "./button": { - "types": "./dist/index.d.ts", - "import": "./dist/index.mjs", - "require": "./dist/index.js" - }, - "./input": { - "types": "./dist/index.d.ts", - "import": "./dist/index.mjs", - "require": "./dist/index.js" - }, - "./card": { - "types": "./dist/index.d.ts", - "import": "./dist/index.mjs", - "require": "./dist/index.js" + "require": "./dist/submit-button.cjs" }, "./hooks": { "types": "./dist/hooks.d.ts", "import": "./dist/hooks.mjs", - "require": "./dist/hooks.js" + "require": "./dist/hooks.cjs" }, "./brutalism-plugin": { "import": "./dist/brutalism-plugin.mjs", - "require": "./dist/brutalism-plugin.js" + "require": "./dist/brutalism-plugin.cjs" }, "./styles.css": "./dist/styles.css" }, @@ -55,73 +40,56 @@ "*.css" ], "scripts": { - "build": "tsup", - "dev": "tsup --watch", + "build": "vite build", + "dev": "vite build --watch", "clean": "rimraf dist", - "typecheck": "tsc --noEmit", - "lint": "eslint src --ext .ts,.tsx", + "typecheck": "vue-tsc --noEmit", + "lint": "eslint src --ext .ts,.tsx,.vue", "test": "vitest run", "test:watch": "vitest", "test:coverage": "vitest run --coverage" }, "peerDependencies": { - "lucide-react": ">=0.300.0", - "react": ">=18.0.0", - "react-dom": ">=18.0.0", - "tailwindcss": ">=3.0.0" + "lucide-vue-next": ">=0.300.0", + "tailwindcss": ">=3.0.0", + "vue": ">=3.5.0" }, "dependencies": { - "@radix-ui/react-alert-dialog": "^1.1.15", - "@radix-ui/react-avatar": "^1.1.0", - "@radix-ui/react-checkbox": "^1.1.0", - "@radix-ui/react-dialog": "^1.1.0", - "@radix-ui/react-dropdown-menu": "^2.1.0", - "@radix-ui/react-label": "^2.1.8", - "@radix-ui/react-popover": "^1.1.0", - "@radix-ui/react-progress": "^1.1.8", - "@radix-ui/react-radio-group": "^1.3.8", - "@radix-ui/react-scroll-area": "^1.2.0", - "@radix-ui/react-select": "^2.1.0", - "@radix-ui/react-separator": "^1.1.0", - "@radix-ui/react-slider": "^1.3.6", - "@radix-ui/react-slot": "^1.1.0", - "@radix-ui/react-switch": "^1.1.0", - "@radix-ui/react-tabs": "^1.1.0", - "@radix-ui/react-toggle": "^1.1.10", - "@radix-ui/react-toggle-group": "^1.1.11", - "@radix-ui/react-tooltip": "^1.1.0", + "@vee-validate/zod": "^4.15.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", - "cmdk": "^1.0.0", - "react-day-picker": "^9.4.4", - "react-hook-form": "^7.76.1", + "reka-ui": "^2.9.0", "tailwind-merge": "^2.2.0", - "zod": "^4.4.3" + "v-calendar": "^3.1.0", + "vee-validate": "^4.15.0", + "zod": "^3.25.0" }, "devDependencies": { - "@testing-library/jest-dom": "^6.4.2", - "@testing-library/react": "^16.3.0", - "@testing-library/user-event": "^14.6.1", + "@eslint/js": "^10.0.1", + "@popperjs/core": "^2.11.8", "@types/node": "^20.17.15", - "@types/react": "^19.0.8", - "@types/react-dom": "^19.0.3", - "@vitejs/plugin-react": "^4.2.1", - "@vitest/coverage-v8": "^1.3.1", + "@vitejs/plugin-vue": "^5.2.0", + "@vitest/coverage-v8": "^3.0.0", + "@vue/test-utils": "^2.4.0", "autoprefixer": "^10.4.16", - "jsdom": "^24.0.0", - "lucide-react": "^0.555.0", + "eslint": "^10.4.1", + "eslint-plugin-vue": "^10.9.1", + "jsdom": "^25.0.0", + "lucide-vue-next": "^0.510.0", "postcss": "^8.4.33", - "react": "^19.0.0", - "react-dom": "^19.0.0", "rimraf": "^5.0.5", "tailwindcss": "^3.4.0", - "tsup": "^8.0.1", "typescript": "^5.7.2", - "vitest": "^1.3.1" + "typescript-eslint": "^8.60.0", + "vite": "^6.0.0", + "vite-plugin-dts": "^4.0.0", + "vitest": "^3.0.0", + "vue": "^3.5.0", + "vue-tsc": "^2.2.0" }, "keywords": [ - "react", - "react-components", + "vue", + "vue3", "ui", "ui-library", "ui-components", @@ -132,7 +100,7 @@ "design-system", "tailwindcss", "tailwind", - "radix-ui", + "reka-ui", "typescript", "frontend", "accessible", diff --git a/packages/ui/postcss.config.js b/packages/ui/postcss.config.js index 67cdf1a..fef1b22 100644 --- a/packages/ui/postcss.config.js +++ b/packages/ui/postcss.config.js @@ -3,4 +3,4 @@ module.exports = { tailwindcss: {}, autoprefixer: {}, }, -}; +} diff --git a/packages/ui/src/__tests__/components/alert-dialog.test.tsx b/packages/ui/src/__tests__/components/alert-dialog.test.tsx deleted file mode 100644 index 4d4de07..0000000 --- a/packages/ui/src/__tests__/components/alert-dialog.test.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { describe, it, expect, vi } from 'vitest'; -import { render, screen, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { - AlertDialog, - AlertDialogTrigger, - AlertDialogContent, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogAction, - AlertDialogCancel, -} from '../../components/alert-dialog'; -import { Button } from '../../components/button'; - -function TestAlertDialog({ - defaultOpen = false, - onOpenChange, -}: { - defaultOpen?: boolean; - onOpenChange?: (open: boolean) => void; -}) { - return ( - - - - - - - Test Alert - Alert description context - - - - - - - - - - - - ); -} - -describe('AlertDialog', () => { - it('is closed by default', () => { - render(); - expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument(); - }); - - it('opens when trigger is clicked', async () => { - render(); - await userEvent.click(screen.getByRole('button', { name: 'Open Alert' })); - expect(screen.getByRole('alertdialog')).toBeInTheDocument(); - expect(screen.getByText('Test Alert')).toBeInTheDocument(); - }); - - it('closes when Cancel is clicked', async () => { - render(); - expect(screen.getByRole('alertdialog')).toBeInTheDocument(); - - await userEvent.click(screen.getByRole('button', { name: 'Discard' })); - await waitFor(() => expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument()); - }); - - it('closes when Action is clicked', async () => { - render(); - expect(screen.getByRole('alertdialog')).toBeInTheDocument(); - - await userEvent.click(screen.getByRole('button', { name: 'Confirm Action' })); - await waitFor(() => expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument()); - }); -}); diff --git a/packages/ui/src/__tests__/components/auth-card.test.tsx b/packages/ui/src/__tests__/components/auth-card.test.tsx deleted file mode 100644 index e70beb6..0000000 --- a/packages/ui/src/__tests__/components/auth-card.test.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { describe, it, expect, vi } from 'vitest'; -import { render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { AuthCard } from '../../components/auth-card'; - -describe('AuthCard', () => { - it('renders login components, inputs and OAuth button rows', () => { - render(); - expect(screen.getByText('Login Gateway')).toBeInTheDocument(); - expect(screen.getByText('Demo description')).toBeInTheDocument(); - - expect(screen.getByLabelText('Email Address')).toBeInTheDocument(); - expect(screen.getByLabelText('Password')).toBeInTheDocument(); - - expect(screen.getByRole('button', { name: 'Google' })).toBeInTheDocument(); - expect(screen.getByRole('button', { name: 'GitHub' })).toBeInTheDocument(); - }); - - it('submits email/password credential input values correctly', async () => { - const onSubmit = vi.fn(); - render(); - - const emailInput = screen.getByLabelText('Email Address'); - const passwordInput = screen.getByLabelText('Password'); - const submitBtn = screen.getByRole('button', { name: 'Access Account console' }); - - await userEvent.type(emailInput, 'creator@brutxui.site'); - await userEvent.type(passwordInput, 'admin123'); - await userEvent.click(submitBtn); - - expect(onSubmit).toHaveBeenCalledOnce(); - }); -}); diff --git a/packages/ui/src/__tests__/components/badge.test.tsx b/packages/ui/src/__tests__/components/badge.test.tsx deleted file mode 100644 index b4b6cd2..0000000 --- a/packages/ui/src/__tests__/components/badge.test.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { render, screen } from '@testing-library/react'; -import { Badge } from '../../components/badge'; - -describe('Badge', () => { - it('renders correctly', () => { - render(New); - expect(screen.getByText('New')).toBeInTheDocument(); - }); - - it('renders with default variant', () => { - render(Default); - const badge = screen.getByTestId('badge'); - expect(badge).toHaveClass('bg-brutal-bg'); - }); - - it('renders with primary variant', () => { - render( - - Primary - - ); - expect(screen.getByTestId('badge')).toHaveClass('bg-brutal-primary'); - }); - - it('renders with secondary variant', () => { - render( - - Secondary - - ); - expect(screen.getByTestId('badge')).toHaveClass('bg-brutal-secondary'); - }); - - it('renders with accent variant', () => { - render( - - Accent - - ); - expect(screen.getByTestId('badge')).toHaveClass('bg-brutal-accent'); - }); - - it('renders with outline variant', () => { - render( - - Outline - - ); - expect(screen.getByTestId('badge')).toHaveClass('bg-transparent'); - }); - - it('applies custom className', () => { - render( - - Custom - - ); - expect(screen.getByTestId('badge')).toHaveClass('custom-badge'); - }); - - it('has neo-brutalism border styles', () => { - render(Styled); - const badge = screen.getByTestId('badge'); - expect(badge).toHaveClass('border-2'); - expect(badge).toHaveClass('border-brutal'); - }); -}); diff --git a/packages/ui/src/__tests__/components/brutalist-hero.test.tsx b/packages/ui/src/__tests__/components/brutalist-hero.test.tsx deleted file mode 100644 index 8ff26c3..0000000 --- a/packages/ui/src/__tests__/components/brutalist-hero.test.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { describe, it, expect, vi } from 'vitest'; -import { render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { BrutalistHero } from '../../components/brutalist-hero'; - -describe('BrutalistHero', () => { - it('renders heading, description text, and mock visual component', () => { - render( - - ); - expect(screen.getByText('Bold Hero Title')).toBeInTheDocument(); - expect(screen.getByText('Neon brutalist card subheadings.')).toBeInTheDocument(); - expect(screen.getByText('brutx-terminal')).toBeInTheDocument(); - }); - - it('fires primary and secondary button click callbacks correctly', async () => { - const onPrimary = vi.fn(); - const onSecondary = vi.fn(); - - render( - - ); - - const primaryBtn = screen.getByRole('button', { name: 'Join Priority' }); - const secondaryBtn = screen.getByRole('button', { name: 'Learn More' }); - - await userEvent.click(primaryBtn); - expect(onPrimary).toHaveBeenCalledOnce(); - - await userEvent.click(secondaryBtn); - expect(onSecondary).toHaveBeenCalledOnce(); - }); -}); diff --git a/packages/ui/src/__tests__/components/button.test.tsx b/packages/ui/src/__tests__/components/button.test.tsx deleted file mode 100644 index 0f2e80f..0000000 --- a/packages/ui/src/__tests__/components/button.test.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { describe, it, expect, vi } from 'vitest'; -import { render, screen, fireEvent } from '@testing-library/react'; -import { Button } from '../../components/button'; - -describe('Button', () => { - it('renders correctly', () => { - render(); - expect(screen.getByRole('button')).toHaveTextContent('Click me'); - }); - - it('handles click events', () => { - const handleClick = vi.fn(); - render(); - - fireEvent.click(screen.getByRole('button')); - expect(handleClick).toHaveBeenCalledTimes(1); - }); - - it('renders with default variant', () => { - render(); - const button = screen.getByRole('button'); - expect(button).toHaveClass('bg-brutal-bg'); - }); - - it('renders with primary variant', () => { - render(); - const button = screen.getByRole('button'); - expect(button).toHaveClass('bg-brutal-primary'); - }); - - it('renders with secondary variant', () => { - render(); - const button = screen.getByRole('button'); - expect(button).toHaveClass('bg-brutal-secondary'); - }); - - it('renders with accent variant', () => { - render(); - const button = screen.getByRole('button'); - expect(button).toHaveClass('bg-brutal-accent'); - }); - - it('renders with different sizes', () => { - const { rerender } = render(); - expect(screen.getByRole('button')).toHaveClass('h-9'); - - rerender(); - expect(screen.getByRole('button')).toHaveClass('h-12'); - - rerender(); - expect(screen.getByRole('button')).toHaveClass('h-16'); - }); - - it('renders as disabled', () => { - render(); - expect(screen.getByRole('button')).toBeDisabled(); - }); - - it('renders with custom className', () => { - render(); - expect(screen.getByRole('button')).toHaveClass('custom-class'); - }); - - it('renders as child component with asChild prop', () => { - render( - - ); - expect(screen.getByRole('link')).toHaveTextContent('Link Button'); - }); - - it('shows loading spinner and disables when loading=true', () => { - render(); - const button = screen.getByRole('button'); - expect(button).toBeDisabled(); - expect(button.querySelector('svg')).toBeInTheDocument(); - }); - - it('does not fire click when disabled', () => { - const handleClick = vi.fn(); - render(); - fireEvent.click(screen.getByRole('button')); - expect(handleClick).not.toHaveBeenCalled(); - }); - - it('ghost and link variants have no shadow or border', () => { - const { rerender } = render(); - expect(screen.getByRole('button')).toHaveClass('border-transparent'); - - rerender(); - expect(screen.getByRole('button')).toHaveClass('border-transparent'); - }); -}); diff --git a/packages/ui/src/__tests__/components/card.test.tsx b/packages/ui/src/__tests__/components/card.test.tsx deleted file mode 100644 index cdcd246..0000000 --- a/packages/ui/src/__tests__/components/card.test.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { render, screen } from '@testing-library/react'; -import { - Card, - CardHeader, - CardTitle, - CardDescription, - CardContent, - CardFooter, -} from '../../components/card'; - -describe('Card', () => { - it('renders Card correctly', () => { - render(Card Content); - expect(screen.getByTestId('card')).toHaveTextContent('Card Content'); - }); - - it('renders with neo-brutalism styles', () => { - render(Card); - const card = screen.getByTestId('card'); - expect(card).toHaveClass('border-3'); - expect(card).toHaveClass('border-brutal'); - }); - - it('renders full card structure', () => { - render( - - - Title - Description - - Content - Footer - - ); - - expect(screen.getByText('Title')).toBeInTheDocument(); - expect(screen.getByText('Description')).toBeInTheDocument(); - expect(screen.getByText('Content')).toBeInTheDocument(); - expect(screen.getByText('Footer')).toBeInTheDocument(); - }); - - it('applies custom className to Card', () => { - render( - - Content - - ); - expect(screen.getByTestId('card')).toHaveClass('custom-card'); - }); - - it('renders CardTitle with correct styles', () => { - render(Title); - const title = screen.getByTestId('title'); - expect(title).toHaveClass('font-black'); - expect(title).toHaveClass('text-2xl'); - }); - - it('renders CardDescription with correct styles', () => { - render(Description); - const desc = screen.getByTestId('desc'); - expect(desc).toHaveClass('text-gray-600'); - }); -}); diff --git a/packages/ui/src/__tests__/components/checkbox.test.tsx b/packages/ui/src/__tests__/components/checkbox.test.tsx deleted file mode 100644 index 68802f3..0000000 --- a/packages/ui/src/__tests__/components/checkbox.test.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { describe, it, expect, vi } from 'vitest'; -import { render, screen, fireEvent } from '@testing-library/react'; -import { Checkbox } from '../../components/checkbox'; - -describe('Checkbox', () => { - it('renders correctly', () => { - render(); - expect(screen.getByRole('checkbox')).toBeInTheDocument(); - }); - - it('handles check/uncheck', () => { - const onCheckedChange = vi.fn(); - render(); - - const checkbox = screen.getByRole('checkbox'); - fireEvent.click(checkbox); - - expect(onCheckedChange).toHaveBeenCalledWith(true); - }); - - it('renders as checked', () => { - render(); - expect(screen.getByRole('checkbox')).toHaveAttribute('data-state', 'checked'); - }); - - it('renders as unchecked', () => { - render(); - expect(screen.getByRole('checkbox')).toHaveAttribute('data-state', 'unchecked'); - }); - - it('renders as disabled', () => { - render(); - expect(screen.getByRole('checkbox')).toBeDisabled(); - }); - - it('applies custom className', () => { - render(); - expect(screen.getByRole('checkbox')).toHaveClass('custom-checkbox'); - }); - - it('has neo-brutalism styles', () => { - render(); - const checkbox = screen.getByRole('checkbox'); - expect(checkbox).toHaveClass('border-3'); - expect(checkbox).toHaveClass('border-brutal'); - }); -}); diff --git a/packages/ui/src/__tests__/components/combobox.test.tsx b/packages/ui/src/__tests__/components/combobox.test.tsx deleted file mode 100644 index 7f46af3..0000000 --- a/packages/ui/src/__tests__/components/combobox.test.tsx +++ /dev/null @@ -1,158 +0,0 @@ -import { describe, it, expect, vi } from 'vitest'; -import { render, screen, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { Combobox, ComboboxMulti } from '../../components/combobox'; - -const fruits = [ - { value: 'apple', label: 'Apple' }, - { value: 'banana', label: 'Banana' }, - { value: 'cherry', label: 'Cherry' }, - { value: 'disabled-item', label: 'Disabled Fruit', disabled: true }, -]; - -describe('Combobox', () => { - it('renders trigger with placeholder', () => { - render(); - expect(screen.getByText('Pick a fruit')).toBeInTheDocument(); - }); - - it('opens dropdown when trigger is clicked', async () => { - render(); - await userEvent.click(screen.getByRole('combobox')); - await waitFor(() => { - expect(screen.getByPlaceholderText('Search...')).toBeInTheDocument(); - }); - }); - - it('shows all options when opened', async () => { - render(); - await userEvent.click(screen.getByRole('combobox')); - await waitFor(() => { - expect(screen.getByText('Apple')).toBeInTheDocument(); - expect(screen.getByText('Banana')).toBeInTheDocument(); - expect(screen.getByText('Cherry')).toBeInTheDocument(); - }); - }); - - it('calls onValueChange when an option is selected', async () => { - const onValueChange = vi.fn(); - render(); - await userEvent.click(screen.getByRole('combobox')); - await waitFor(() => screen.getByText('Apple')); - await userEvent.click(screen.getByText('Apple')); - expect(onValueChange).toHaveBeenCalledWith('apple'); - }); - - it('displays selected value label in trigger', () => { - render(); - expect(screen.getByRole('combobox')).toHaveTextContent('Banana'); - }); - - it('deselects value when selected option is clicked again', async () => { - const onValueChange = vi.fn(); - render(); - await userEvent.click(screen.getByRole('combobox')); - await waitFor(() => screen.getByRole('option', { name: 'Apple' })); - await userEvent.click(screen.getByRole('option', { name: 'Apple' })); - expect(onValueChange).toHaveBeenCalledWith(''); - }); - - it('filters options by search input', async () => { - render(); - await userEvent.click(screen.getByRole('combobox')); - await waitFor(() => screen.getByPlaceholderText('Search...')); - await userEvent.type(screen.getByPlaceholderText('Search...'), 'ban'); - await waitFor(() => { - expect(screen.getByText('Banana')).toBeInTheDocument(); - expect(screen.queryByText('Apple')).not.toBeInTheDocument(); - }); - }); - - it('shows empty text when no results match', async () => { - render(); - await userEvent.click(screen.getByRole('combobox')); - await waitFor(() => screen.getByPlaceholderText('Search...')); - await userEvent.type(screen.getByPlaceholderText('Search...'), 'zzz'); - await waitFor(() => expect(screen.getByText('Nothing found')).toBeInTheDocument()); - }); - - it('is disabled when disabled prop is true', () => { - render(); - expect(screen.getByRole('combobox')).toBeDisabled(); - }); - - it('shows check icon next to selected option', async () => { - render(); - await userEvent.click(screen.getByRole('combobox')); - await waitFor(() => screen.getByRole('option', { name: 'Apple' })); - expect(screen.getByRole('option', { name: 'Apple' })).toHaveAttribute('aria-selected', 'true'); - const checkIcon = screen.getByRole('option', { name: 'Apple' }).querySelector('svg'); - expect(checkIcon).toHaveClass('opacity-100'); - }); -}); - -describe('ComboboxMulti', () => { - it('renders trigger with placeholder', () => { - render(); - expect(screen.getByText('Pick fruits')).toBeInTheDocument(); - }); - - it('adds a value when an option is clicked', async () => { - const onValueChange = vi.fn(); - render(); - await userEvent.click(screen.getByRole('combobox')); - await waitFor(() => screen.getByText('Apple')); - await userEvent.click(screen.getByText('Apple')); - expect(onValueChange).toHaveBeenCalledWith(['apple']); - }); - - it('removes a value when an already-selected option is clicked', async () => { - const onValueChange = vi.fn(); - render( - - ); - await userEvent.click(screen.getByRole('combobox')); - await waitFor(() => screen.getByText('Apple')); - await userEvent.click(screen.getByText('Apple')); - expect(onValueChange).toHaveBeenCalledWith(['banana']); - }); - - it('displays selected labels in trigger', () => { - render(); - expect(screen.getByRole('combobox')).toHaveTextContent('Apple, Cherry'); - }); - - it('shows count when selections exceed maxDisplay', () => { - render( - - ); - expect(screen.getByRole('combobox')).toHaveTextContent('3 selected'); - }); - - it('is disabled when disabled prop is true', () => { - render(); - expect(screen.getByRole('combobox')).toBeDisabled(); - }); - - it('can select multiple items', async () => { - const selected: string[] = []; - const onValueChange = vi.fn((v: string[]) => selected.push(...v)); - - const { rerender } = render( - - ); - await userEvent.click(screen.getByRole('combobox')); - await waitFor(() => screen.getByText('Apple')); - await userEvent.click(screen.getByText('Apple')); - expect(onValueChange).toHaveBeenLastCalledWith(['apple']); - - rerender(); - await userEvent.click(screen.getByText('Banana')); - expect(onValueChange).toHaveBeenLastCalledWith(['apple', 'banana']); - }); -}); diff --git a/packages/ui/src/__tests__/components/command.test.tsx b/packages/ui/src/__tests__/components/command.test.tsx deleted file mode 100644 index 1fc2277..0000000 --- a/packages/ui/src/__tests__/components/command.test.tsx +++ /dev/null @@ -1,179 +0,0 @@ -import { describe, it, expect, vi } from 'vitest'; -import { render, screen, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { - Command, - CommandInput, - CommandList, - CommandEmpty, - CommandGroup, - CommandItem, - CommandSeparator, - CommandShortcut, - CommandDialog, -} from '../../components/command'; - -function TestCommand({ - onSelect, -}: { - onSelect?: (value: string) => void; -}) { - return ( - - - - No results found. - - onSelect?.('copy')}> - Copy - - onSelect?.('paste')}> - Paste - - onSelect?.('disabled-item')}> - Disabled Action - - - - - onSelect?.('home')}> - Home - ⌘H - - - - - ); -} - -describe('Command', () => { - it('renders search input', () => { - render(); - expect(screen.getByPlaceholderText('Type a command...')).toBeInTheDocument(); - }); - - it('renders all items', () => { - render(); - expect(screen.getByText('Copy')).toBeInTheDocument(); - expect(screen.getByText('Paste')).toBeInTheDocument(); - expect(screen.getByText('Home')).toBeInTheDocument(); - }); - - it('renders group headings', () => { - render(); - expect(screen.getByText('Actions')).toBeInTheDocument(); - expect(screen.getByText('Navigation')).toBeInTheDocument(); - }); - - it('filters items as user types', async () => { - render(); - await userEvent.type(screen.getByPlaceholderText('Type a command...'), 'copy'); - await waitFor(() => { - expect(screen.getByText('Copy')).toBeInTheDocument(); - expect(screen.queryByText('Paste')).not.toBeInTheDocument(); - expect(screen.queryByText('Home')).not.toBeInTheDocument(); - }); - }); - - it('shows empty state when no results match', async () => { - render(); - await userEvent.type(screen.getByPlaceholderText('Type a command...'), 'zzz'); - await waitFor(() => expect(screen.getByText('No results found.')).toBeInTheDocument()); - }); - - it('calls onSelect with correct value when item is clicked', async () => { - const onSelect = vi.fn(); - render(); - await userEvent.click(screen.getByText('Copy')); - expect(onSelect).toHaveBeenCalledWith('copy'); - }); - - it('calls onSelect for paste item', async () => { - const onSelect = vi.fn(); - render(); - await userEvent.click(screen.getByText('Paste')); - expect(onSelect).toHaveBeenCalledWith('paste'); - }); - - it('disabled item does not fire onSelect when clicked', async () => { - const onSelect = vi.fn(); - render(); - await userEvent.click(screen.getByText('Disabled Action')); - expect(onSelect).not.toHaveBeenCalled(); - }); - - it('disabled item has data-disabled attribute', () => { - render(); - const disabledItem = screen.getByText('Disabled Action').closest('[data-slot="command-item"]'); - expect(disabledItem).toHaveAttribute('data-disabled', 'true'); - }); - - it('renders keyboard shortcut', () => { - render(); - expect(screen.getByText('⌘H')).toBeInTheDocument(); - }); -}); - -describe('CommandDialog', () => { - it('is closed by default', () => { - render( - - - - New File - - - ); - expect(screen.queryByPlaceholderText('Search...')).not.toBeInTheDocument(); - }); - - it('shows content when open=true', () => { - render( - - - - New File - - - ); - expect(screen.getByPlaceholderText('Search...')).toBeInTheDocument(); - expect(screen.getByText('New File')).toBeInTheDocument(); - }); - - it('renders default accessible title (screen-reader only)', () => { - render(); - expect(screen.getByText('My Palette')).toBeInTheDocument(); - }); - - it('calls onOpenChange when Escape is pressed', async () => { - const onOpenChange = vi.fn(); - render( - - - - New File - - - ); - await userEvent.keyboard('{Escape}'); - await waitFor(() => expect(onOpenChange).toHaveBeenCalledWith(false)); - }); - - it('filters items inside dialog', async () => { - render( - - - - Nothing. - Open File - Settings - - - ); - await userEvent.type(screen.getByPlaceholderText('Search commands'), 'open'); - await waitFor(() => { - expect(screen.getByText('Open File')).toBeInTheDocument(); - expect(screen.queryByText('Settings')).not.toBeInTheDocument(); - }); - }); -}); diff --git a/packages/ui/src/__tests__/components/dashboard-shell.test.tsx b/packages/ui/src/__tests__/components/dashboard-shell.test.tsx deleted file mode 100644 index aab86c7..0000000 --- a/packages/ui/src/__tests__/components/dashboard-shell.test.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { describe, it, expect, vi } from 'vitest'; -import { render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { DashboardShell } from '../../components/dashboard-shell'; - -describe('DashboardShell', () => { - it('renders sidebar, topbar, statistics cards, and receipts logs table', () => { - render(); - - expect(screen.getByText('BrutxConsole')).toBeInTheDocument(); - expect(screen.getByText('developer@my-saas.com')).toBeInTheDocument(); - - expect(screen.getByText('Overview')).toBeInTheDocument(); - expect(screen.getByText('Deployments')).toBeInTheDocument(); - - expect(screen.getByText('Monthly Revenue')).toBeInTheDocument(); - expect(screen.getByText('Active Licenses')).toBeInTheDocument(); - - expect(screen.getByText('Recent Transaction Ledgers')).toBeInTheDocument(); - expect(screen.getByText('Alpha Agency')).toBeInTheDocument(); - expect(screen.getByText('Delta Studio')).toBeInTheDocument(); - }); - - it('triggers signout callback triggers correctly', async () => { - const onSignOut = vi.fn(); - render(); - - const signOutBtn = screen.getByRole('button', { name: 'Sign Out' }); - await userEvent.click(signOutBtn); - - expect(onSignOut).toHaveBeenCalledOnce(); - }); -}); diff --git a/packages/ui/src/__tests__/components/dialog.test.tsx b/packages/ui/src/__tests__/components/dialog.test.tsx deleted file mode 100644 index ad7d457..0000000 --- a/packages/ui/src/__tests__/components/dialog.test.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import { describe, it, expect, vi } from 'vitest'; -import { render, screen, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { - Dialog, - DialogTrigger, - DialogContent, - DialogHeader, - DialogTitle, - DialogDescription, - DialogFooter, - DialogClose, -} from '../../components/dialog'; -import { Button } from '../../components/button'; - -function TestDialog({ - defaultOpen = false, - showCloseButton = true, - onOpenChange, -}: { - defaultOpen?: boolean; - showCloseButton?: boolean; - onOpenChange?: (open: boolean) => void; -}) { - return ( - - - - - - - Test Dialog - Dialog description - -

Dialog body content

- - - - - - -
-
- ); -} - -describe('Dialog', () => { - it('is closed by default', () => { - render(); - expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); - }); - - it('opens when trigger is clicked', async () => { - render(); - await userEvent.click(screen.getByRole('button', { name: 'Open Dialog' })); - expect(screen.getByRole('dialog')).toBeInTheDocument(); - expect(screen.getByText('Test Dialog')).toBeInTheDocument(); - }); - - it('renders title and description for accessibility', async () => { - render(); - expect(screen.getByText('Test Dialog')).toBeInTheDocument(); - expect(screen.getByText('Dialog description')).toBeInTheDocument(); - }); - - it('closes when close button is clicked', async () => { - render(); - await userEvent.click(screen.getByRole('button', { name: 'Open Dialog' })); - expect(screen.getByRole('dialog')).toBeInTheDocument(); - - const closeBtn = screen.getByRole('button', { name: 'Close' }); - await userEvent.click(closeBtn); - await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument()); - }); - - it('closes when DialogClose button is clicked', async () => { - render(); - await userEvent.click(screen.getByRole('button', { name: 'Open Dialog' })); - - await userEvent.click(screen.getByRole('button', { name: 'Cancel' })); - await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument()); - }); - - it('closes on Escape key press', async () => { - render(); - await userEvent.click(screen.getByRole('button', { name: 'Open Dialog' })); - expect(screen.getByRole('dialog')).toBeInTheDocument(); - - await userEvent.keyboard('{Escape}'); - await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument()); - }); - - it('calls onOpenChange when opening and closing', async () => { - const onOpenChange = vi.fn(); - render(); - - await userEvent.click(screen.getByRole('button', { name: 'Open Dialog' })); - expect(onOpenChange).toHaveBeenCalledWith(true); - - await userEvent.keyboard('{Escape}'); - await waitFor(() => expect(onOpenChange).toHaveBeenCalledWith(false)); - }); - - it('can be rendered open by default with defaultOpen', () => { - render(); - expect(screen.getByRole('dialog')).toBeInTheDocument(); - }); - - it('hides close button when showCloseButton=false', async () => { - render(); - expect(screen.queryByRole('button', { name: 'Close' })).not.toBeInTheDocument(); - }); - - it('has aria-labelledby pointing to the title', async () => { - render(); - const dialog = screen.getByRole('dialog'); - const titleId = screen.getByText('Test Dialog').id; - expect(dialog).toHaveAttribute('aria-labelledby', titleId); - }); -}); diff --git a/packages/ui/src/__tests__/components/dropdown-menu.test.tsx b/packages/ui/src/__tests__/components/dropdown-menu.test.tsx deleted file mode 100644 index 5e40c6d..0000000 --- a/packages/ui/src/__tests__/components/dropdown-menu.test.tsx +++ /dev/null @@ -1,165 +0,0 @@ -import { describe, it, expect, vi } from 'vitest'; -import { render, screen, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, - DropdownMenuCheckboxItem, - DropdownMenuRadioItem, - DropdownMenuRadioGroup, - DropdownMenuSeparator, - DropdownMenuLabel, -} from '../../components/dropdown-menu'; - -describe('DropdownMenu', () => { - it('is closed by default', () => { - render( - - Open - - Item 1 - - - ); - expect(screen.queryByText('Item 1')).not.toBeInTheDocument(); - }); - - it('opens when trigger is clicked', async () => { - render( - - Open - - Item 1 - - - ); - await userEvent.click(screen.getByText('Open')); - await waitFor(() => expect(screen.getByText('Item 1')).toBeInTheDocument()); - }); - - it('calls onClick handler when item is clicked', async () => { - const onClick = vi.fn(); - render( - - Open - - Item 1 - - - ); - await userEvent.click(screen.getByText('Open')); - await waitFor(() => screen.getByText('Item 1')); - await userEvent.click(screen.getByText('Item 1')); - expect(onClick).toHaveBeenCalledTimes(1); - }); - - it('closes after item is selected', async () => { - render( - - Open - - Item 1 - - - ); - await userEvent.click(screen.getByText('Open')); - await waitFor(() => screen.getByText('Item 1')); - await userEvent.click(screen.getByText('Item 1')); - await waitFor(() => expect(screen.queryByText('Item 1')).not.toBeInTheDocument()); - }); - - it('closes on Escape key', async () => { - render( - - Open - - Item 1 - - - ); - await userEvent.click(screen.getByText('Open')); - await waitFor(() => screen.getByText('Item 1')); - await userEvent.keyboard('{Escape}'); - await waitFor(() => expect(screen.queryByText('Item 1')).not.toBeInTheDocument()); - }); - - it('disabled item does not trigger onSelect', async () => { - const onSelect = vi.fn(); - render( - - Open - - - Disabled Item - - - - ); - await userEvent.click(screen.getByText('Open')); - await waitFor(() => screen.getByText('Disabled Item')); - await userEvent.click(screen.getByText('Disabled Item')); - expect(onSelect).not.toHaveBeenCalled(); - }); - - it('renders label and separator', async () => { - render( - - Open - - Group A - Item A - - Item B - - - ); - await userEvent.click(screen.getByText('Open')); - await waitFor(() => { - expect(screen.getByText('Group A')).toBeInTheDocument(); - expect(screen.getByText('Item A')).toBeInTheDocument(); - expect(screen.getByText('Item B')).toBeInTheDocument(); - }); - }); - - it('checkbox item toggles checked state', async () => { - let checked = false; - const onCheckedChange = vi.fn((v: boolean) => { - checked = v; - }); - render( - - Open - - - Checkbox Option - - - - ); - await userEvent.click(screen.getByText('Open')); - await waitFor(() => screen.getByText('Checkbox Option')); - await userEvent.click(screen.getByText('Checkbox Option')); - expect(onCheckedChange).toHaveBeenCalledWith(true); - }); - - it('radio group selects a single item', async () => { - const onValueChange = vi.fn(); - render( - - Open - - - Option A - Option B - - - - ); - await userEvent.click(screen.getByText('Open')); - await waitFor(() => screen.getByText('Option B')); - await userEvent.click(screen.getByText('Option B')); - expect(onValueChange).toHaveBeenCalledWith('b'); - }); -}); diff --git a/packages/ui/src/__tests__/components/empty-state.test.tsx b/packages/ui/src/__tests__/components/empty-state.test.tsx deleted file mode 100644 index 5a02490..0000000 --- a/packages/ui/src/__tests__/components/empty-state.test.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { describe, it, expect, vi } from 'vitest'; -import { render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { EmptyState } from '../../components/empty-state'; - -describe('EmptyState', () => { - it('renders state titles, desc tags, and icons', () => { - render( - - ); - - expect(screen.getByText('Workspace Empty')).toBeInTheDocument(); - expect(screen.getByText('Please initialize a cluster.')).toBeInTheDocument(); - expect(screen.getByRole('button', { name: 'Propose Deploy' })).toBeInTheDocument(); - }); - - it('triggers action click callback correctly', async () => { - const onClick = vi.fn(); - render(); - - const btn = screen.getByRole('button', { name: 'New Deployment' }); - await userEvent.click(btn); - - expect(onClick).toHaveBeenCalledOnce(); - }); -}); diff --git a/packages/ui/src/__tests__/components/form.test.tsx b/packages/ui/src/__tests__/components/form.test.tsx deleted file mode 100644 index bf42b5b..0000000 --- a/packages/ui/src/__tests__/components/form.test.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { describe, it, expect, vi } from 'vitest'; -import { render, screen, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { useForm } from 'react-hook-form'; -import { z } from 'zod'; -import { zodResolver } from '@hookform/resolvers/zod'; - -import { - Form, - FormField, - FormItem, - FormLabel, - FormControl, - FormDescription, - FormMessage, -} from '../../components/form'; -import { Input } from '../../components/input'; -import { Button } from '../../components/button'; - -const testSchema = z.object({ - username: z.string().min(3, { message: 'Username too short' }), -}); - -function TestForm({ onSubmit }: { onSubmit: (values: any) => void }) { - const form = useForm>({ - resolver: zodResolver(testSchema), - defaultValues: { - username: '', - }, - }); - - return ( -
- - ( - - Username - - - - Must be 3+ chars - - - )} - /> - - - - ); -} - -describe('Form', () => { - it('renders form inputs, labels and descriptions correctly', () => { - const onSubmit = vi.fn(); - render(); - - expect(screen.getByText('Username')).toBeInTheDocument(); - expect(screen.getByPlaceholderText('Type here')).toBeInTheDocument(); - expect(screen.getByText('Must be 3+ chars')).toBeInTheDocument(); - }); - - it('triggers validation errors on invalid submit', async () => { - const onSubmit = vi.fn(); - render(); - - await userEvent.click(screen.getByRole('button', { name: 'Submit' })); - - await waitFor(() => { - expect(screen.getByText('Username too short')).toBeInTheDocument(); - }); - expect(onSubmit).not.toHaveBeenCalled(); - }); - - it('submits successfully when form input is valid', async () => { - const onSubmit = vi.fn(); - render(); - - const input = screen.getByPlaceholderText('Type here'); - await userEvent.type(input, 'johndoe'); - await userEvent.click(screen.getByRole('button', { name: 'Submit' })); - - await waitFor(() => { - expect(onSubmit).toHaveBeenCalledWith( - expect.objectContaining({ username: 'johndoe' }), - expect.any(Object) - ); - }); - }); -}); diff --git a/packages/ui/src/__tests__/components/input.test.tsx b/packages/ui/src/__tests__/components/input.test.tsx deleted file mode 100644 index ad8a3c7..0000000 --- a/packages/ui/src/__tests__/components/input.test.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { describe, it, expect, vi } from 'vitest'; -import { render, screen, fireEvent } from '@testing-library/react'; -import { Input } from '../../components/input'; - -describe('Input', () => { - it('renders correctly', () => { - render(); - expect(screen.getByPlaceholderText('Enter text')).toBeInTheDocument(); - }); - - it('handles value changes', () => { - const handleChange = vi.fn(); - render(); - - const input = screen.getByRole('textbox'); - fireEvent.change(input, { target: { value: 'test' } }); - - expect(handleChange).toHaveBeenCalled(); - }); - - it('renders with neo-brutalism styles', () => { - render(); - const input = screen.getByTestId('input'); - expect(input).toHaveClass('border-3'); - expect(input).toHaveClass('border-brutal'); - }); - - it('renders as disabled', () => { - render(); - expect(screen.getByRole('textbox')).toBeDisabled(); - }); - - it('applies custom className', () => { - render(); - expect(screen.getByTestId('input')).toHaveClass('custom-input'); - }); - - it('renders different input types', () => { - const { rerender } = render(); - expect(screen.getByRole('textbox')).toHaveAttribute('type', 'email'); - - rerender(); - expect(document.querySelector('input[type="password"]')).toBeInTheDocument(); - }); - - it('forwards ref correctly', () => { - const ref = vi.fn(); - render(); - expect(ref).toHaveBeenCalled(); - }); -}); diff --git a/packages/ui/src/__tests__/components/pricing-section.test.tsx b/packages/ui/src/__tests__/components/pricing-section.test.tsx deleted file mode 100644 index 24a2e5b..0000000 --- a/packages/ui/src/__tests__/components/pricing-section.test.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { render, screen } from '@testing-library/react'; -import { PricingSection } from '../../components/pricing-section'; - -describe('PricingSection', () => { - it('renders plans correctly', () => { - render(); - expect(screen.getByText('Custom Plans Title')).toBeInTheDocument(); - - expect(screen.getByText('Indie Creator')).toBeInTheDocument(); - expect(screen.getByText('Pro Developer')).toBeInTheDocument(); - expect(screen.getByText('Team Studio')).toBeInTheDocument(); - - expect(screen.getByText('$9')).toBeInTheDocument(); - expect(screen.getByText('$29')).toBeInTheDocument(); - expect(screen.getByText('$99')).toBeInTheDocument(); - }); - - it('renders the popular plan badge badge layout', () => { - render(); - expect(screen.getByText('Most Popular Tier')).toBeInTheDocument(); - }); -}); diff --git a/packages/ui/src/__tests__/components/progress.test.tsx b/packages/ui/src/__tests__/components/progress.test.tsx deleted file mode 100644 index ab06f8e..0000000 --- a/packages/ui/src/__tests__/components/progress.test.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { render, screen } from '@testing-library/react'; -import { Progress } from '../../components/progress'; - -describe('Progress', () => { - it('renders with correct aria attributes', () => { - render(); - const progressbar = screen.getByRole('progressbar'); - expect(progressbar).toBeInTheDocument(); - expect(progressbar).toHaveAttribute('aria-valuenow', '45'); - expect(progressbar).toHaveAttribute('aria-valuemin', '0'); - expect(progressbar).toHaveAttribute('aria-valuemax', '100'); - }); - - it('handles custom max values or null/undefined correctly', () => { - render(); - const progressbar = screen.getByRole('progressbar'); - expect(progressbar).not.toHaveAttribute('aria-valuenow'); - }); -}); diff --git a/packages/ui/src/__tests__/components/radio-group.test.tsx b/packages/ui/src/__tests__/components/radio-group.test.tsx deleted file mode 100644 index 3e36baf..0000000 --- a/packages/ui/src/__tests__/components/radio-group.test.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { describe, it, expect, vi } from 'vitest'; -import { render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { RadioGroup, RadioGroupItem } from '../../components/radio-group'; -import { Label } from '../../components/label'; - -function TestRadioGroup({ onValueChange }: { onValueChange?: (val: string) => void }) { - return ( - -
- - -
-
- - -
-
- - -
-
- ); -} - -describe('RadioGroup', () => { - it('renders radio options correctly', () => { - render(); - expect(screen.getByLabelText('Apple')).toBeInTheDocument(); - expect(screen.getByLabelText('Orange')).toBeInTheDocument(); - expect(screen.getByLabelText('Banana')).toBeInTheDocument(); - }); - - it('sets default checked state correctly', () => { - render(); - expect(screen.getByLabelText('Apple')).toBeChecked(); - expect(screen.getByLabelText('Orange')).not.toBeChecked(); - }); - - it('changes value on clicking other enabled option', async () => { - const onValueChange = vi.fn(); - render(); - - await userEvent.click(screen.getByLabelText('Orange')); - expect(onValueChange).toHaveBeenCalledWith('orange'); - expect(screen.getByLabelText('Orange')).toBeChecked(); - }); - - it('does not select disabled radio option on click', async () => { - const onValueChange = vi.fn(); - render(); - - await userEvent.click(screen.getByLabelText('Banana')); - expect(onValueChange).not.toHaveBeenCalled(); - expect(screen.getByLabelText('Banana')).not.toBeChecked(); - }); -}); diff --git a/packages/ui/src/__tests__/components/select.test.tsx b/packages/ui/src/__tests__/components/select.test.tsx deleted file mode 100644 index ed66d81..0000000 --- a/packages/ui/src/__tests__/components/select.test.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import { describe, it, expect, vi } from 'vitest'; -import { render, screen, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, - SelectGroup, - SelectLabel, - SelectSeparator, -} from '../../components/select'; - -function TestSelect({ - value, - onValueChange, - defaultValue, - disabled = false, -}: { - value?: string; - onValueChange?: (v: string) => void; - defaultValue?: string; - disabled?: boolean; -}) { - return ( - - ); -} - -describe('Select', () => { - it('renders trigger with placeholder', () => { - render(); - expect(screen.getByRole('combobox')).toBeInTheDocument(); - expect(screen.getByText('Select a fruit')).toBeInTheDocument(); - }); - - it('opens dropdown when trigger is clicked', async () => { - render(); - await userEvent.click(screen.getByRole('combobox')); - await waitFor(() => expect(screen.getByRole('listbox')).toBeInTheDocument()); - }); - - it('shows options when open', async () => { - render(); - await userEvent.click(screen.getByRole('combobox')); - await waitFor(() => { - expect(screen.getByText('Apple')).toBeInTheDocument(); - expect(screen.getByText('Banana')).toBeInTheDocument(); - expect(screen.getByText('Cherry')).toBeInTheDocument(); - }); - }); - - it('calls onValueChange when an option is selected', async () => { - const onValueChange = vi.fn(); - render(); - await userEvent.click(screen.getByRole('combobox')); - await waitFor(() => screen.getByText('Apple')); - await userEvent.click(screen.getByText('Apple')); - expect(onValueChange).toHaveBeenCalledWith('apple'); - }); - - it('displays selected value in trigger', async () => { - render(); - expect(screen.getByRole('combobox')).toHaveTextContent('Banana'); - }); - - it('works as controlled component', async () => { - const { rerender } = render(); - expect(screen.getByRole('combobox')).toHaveTextContent('Apple'); - - rerender(); - expect(screen.getByRole('combobox')).toHaveTextContent('Cherry'); - }); - - it('is disabled when disabled prop is true', () => { - render(); - expect(screen.getByRole('combobox')).toBeDisabled(); - }); - - it('disabled item cannot be selected', async () => { - const onValueChange = vi.fn(); - render(); - await userEvent.click(screen.getByRole('combobox')); - await waitFor(() => screen.getByText('Disabled Item')); - - const disabledOption = screen.getByText('Disabled Item').closest('[data-disabled]'); - expect(disabledOption).toBeInTheDocument(); - expect(onValueChange).not.toHaveBeenCalled(); - }); - - it('closes on Escape key', async () => { - render(); - await userEvent.click(screen.getByRole('combobox')); - await waitFor(() => screen.getByRole('listbox')); - - await userEvent.keyboard('{Escape}'); - await waitFor(() => expect(screen.queryByRole('listbox')).not.toBeInTheDocument()); - }); - - it('has proper ARIA attributes on trigger', () => { - render(); - const trigger = screen.getByRole('combobox'); - expect(trigger).toHaveAttribute('aria-label', 'Pick a fruit'); - expect(trigger).toHaveAttribute('aria-expanded', 'false'); - }); - - it('updates aria-expanded when open', async () => { - render(); - const trigger = screen.getByRole('combobox'); - await userEvent.click(trigger); - await waitFor(() => expect(trigger).toHaveAttribute('aria-expanded', 'true')); - }); -}); diff --git a/packages/ui/src/__tests__/components/sheet.test.tsx b/packages/ui/src/__tests__/components/sheet.test.tsx deleted file mode 100644 index 3206119..0000000 --- a/packages/ui/src/__tests__/components/sheet.test.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { render, screen, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { - Sheet, - SheetTrigger, - SheetContent, - SheetHeader, - SheetTitle, - SheetDescription, - SheetFooter, - SheetClose, -} from '../../components/sheet'; -import { Button } from '../../components/button'; - -function TestSheet({ - side = 'right' as const, - defaultOpen = false, -}) { - return ( - - - - - - - Test Sheet - Sheet description context - -

Sheet body

- - - - - -
-
- ); -} - -describe('Sheet', () => { - it('is closed by default', () => { - render(); - expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); - }); - - it('opens when trigger is clicked', async () => { - render(); - await userEvent.click(screen.getByRole('button', { name: 'Open Drawer' })); - expect(screen.getByRole('dialog')).toBeInTheDocument(); - expect(screen.getByText('Test Sheet')).toBeInTheDocument(); - }); - - it('closes when close is clicked', async () => { - render(); - expect(screen.getByRole('dialog')).toBeInTheDocument(); - - await userEvent.click(screen.getByRole('button', { name: 'Close' })); - await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument()); - }); -}); diff --git a/packages/ui/src/__tests__/components/slider.test.tsx b/packages/ui/src/__tests__/components/slider.test.tsx deleted file mode 100644 index fb6e66d..0000000 --- a/packages/ui/src/__tests__/components/slider.test.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { render, screen } from '@testing-library/react'; -import { Slider } from '../../components/slider'; - -describe('Slider', () => { - it('renders with standard range slider accessibility role', () => { - render(); - const thumb = screen.getByRole('slider'); - expect(thumb).toBeInTheDocument(); - expect(thumb).toHaveAttribute('aria-valuenow', '50'); - }); - - it('applies custom max and min attributes correctly', () => { - render(); - const thumb = screen.getByRole('slider'); - expect(thumb).toHaveAttribute('aria-valuemin', '10'); - expect(thumb).toHaveAttribute('aria-valuemax', '90'); - }); - - it('respects disabled state properties', () => { - render(); - const thumb = screen.getByRole('slider'); - expect(thumb).toHaveAttribute('data-disabled'); - }); -}); diff --git a/packages/ui/src/__tests__/components/switch.test.tsx b/packages/ui/src/__tests__/components/switch.test.tsx deleted file mode 100644 index 5a0a65e..0000000 --- a/packages/ui/src/__tests__/components/switch.test.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { describe, it, expect, vi } from 'vitest'; -import { render, screen, fireEvent } from '@testing-library/react'; -import { Switch } from '../../components/switch'; - -describe('Switch', () => { - it('renders correctly', () => { - render(); - expect(screen.getByRole('switch')).toBeInTheDocument(); - }); - - it('handles toggle', () => { - const onCheckedChange = vi.fn(); - render(); - - const switchEl = screen.getByRole('switch'); - fireEvent.click(switchEl); - - expect(onCheckedChange).toHaveBeenCalledWith(true); - }); - - it('renders as checked', () => { - render(); - expect(screen.getByRole('switch')).toHaveAttribute('data-state', 'checked'); - }); - - it('renders as unchecked', () => { - render(); - expect(screen.getByRole('switch')).toHaveAttribute('data-state', 'unchecked'); - }); - - it('renders as disabled', () => { - render(); - expect(screen.getByRole('switch')).toBeDisabled(); - }); - - it('applies custom className', () => { - render(); - expect(screen.getByRole('switch')).toHaveClass('custom-switch'); - }); - - it('has neo-brutalism styles', () => { - render(); - const switchEl = screen.getByRole('switch'); - expect(switchEl).toHaveClass('border-3'); - expect(switchEl).toHaveClass('border-brutal'); - }); -}); diff --git a/packages/ui/src/__tests__/components/tabs.test.tsx b/packages/ui/src/__tests__/components/tabs.test.tsx deleted file mode 100644 index e84b074..0000000 --- a/packages/ui/src/__tests__/components/tabs.test.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import { describe, it, expect, vi } from 'vitest'; -import { render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { Tabs, TabsList, TabsTrigger, TabsContent } from '../../components/tabs'; - -function TestTabs({ - value, - onValueChange, - defaultValue = 'tab1', -}: { - value?: string; - onValueChange?: (v: string) => void; - defaultValue?: string; -}) { - return ( - - - Tab 1 - Tab 2 - - Tab 3 - - - Content 1 - Content 2 - Content 3 - - ); -} - -describe('Tabs', () => { - it('renders all tabs', () => { - render(); - expect(screen.getByRole('tab', { name: 'Tab 1' })).toBeInTheDocument(); - expect(screen.getByRole('tab', { name: 'Tab 2' })).toBeInTheDocument(); - expect(screen.getByRole('tab', { name: 'Tab 3' })).toBeInTheDocument(); - }); - - it('shows active tab content by default', () => { - render(); - expect(screen.getByText('Content 1')).toBeVisible(); - }); - - it('switches content when a tab is clicked', async () => { - render(); - await userEvent.click(screen.getByRole('tab', { name: 'Tab 2' })); - expect(screen.getByText('Content 2')).toBeVisible(); - }); - - it('calls onValueChange when a tab is clicked', async () => { - const onValueChange = vi.fn(); - render(); - await userEvent.click(screen.getByRole('tab', { name: 'Tab 2' })); - expect(onValueChange).toHaveBeenCalledWith('tab2'); - }); - - it('active tab has aria-selected=true', () => { - render(); - expect(screen.getByRole('tab', { name: 'Tab 2' })).toHaveAttribute('aria-selected', 'true'); - expect(screen.getByRole('tab', { name: 'Tab 1' })).toHaveAttribute('aria-selected', 'false'); - }); - - it('disabled tab is not selectable', async () => { - const onValueChange = vi.fn(); - render(); - await userEvent.click(screen.getByRole('tab', { name: 'Tab 3' })); - expect(onValueChange).not.toHaveBeenCalled(); - expect(screen.getByRole('tab', { name: 'Tab 1' })).toHaveAttribute('aria-selected', 'true'); - }); - - it('disabled tab has disabled attribute', () => { - render(); - expect(screen.getByRole('tab', { name: 'Tab 3' })).toBeDisabled(); - }); - - it('navigates with arrow keys', async () => { - render(); - const tab1 = screen.getByRole('tab', { name: 'Tab 1' }); - tab1.focus(); - - await userEvent.keyboard('{ArrowRight}'); - expect(screen.getByRole('tab', { name: 'Tab 2' })).toHaveFocus(); - }); - - it('works as controlled component', async () => { - const { rerender } = render(); - expect(screen.getByRole('tab', { name: 'Tab 1' })).toHaveAttribute('aria-selected', 'true'); - - rerender(); - expect(screen.getByRole('tab', { name: 'Tab 2' })).toHaveAttribute('aria-selected', 'true'); - }); - - it('each tab has correct aria-controls linking to panel', () => { - render(); - const tab1 = screen.getByRole('tab', { name: 'Tab 1' }); - const panelId = tab1.getAttribute('aria-controls'); - expect(panelId).toBeTruthy(); - expect(document.getElementById(panelId!)).toBeInTheDocument(); - }); - - it('tabpanel has correct role', () => { - render(); - const panels = screen.getAllByRole('tabpanel'); - expect(panels.length).toBeGreaterThanOrEqual(1); - }); -}); diff --git a/packages/ui/src/__tests__/components/textarea.test.tsx b/packages/ui/src/__tests__/components/textarea.test.tsx deleted file mode 100644 index a8e69f7..0000000 --- a/packages/ui/src/__tests__/components/textarea.test.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { describe, it, expect, vi } from 'vitest'; -import { render, screen, fireEvent } from '@testing-library/react'; -import { Textarea } from '../../components/textarea'; - -describe('Textarea', () => { - it('renders correctly', () => { - render( + + + +## 安装 + + + +## 用法 + +```vue + + +