diff --git a/bun.lock b/bun.lock index ae67c8f..83385c2 100644 --- a/bun.lock +++ b/bun.lock @@ -6,9 +6,11 @@ "name": "person-panel", "dependencies": { "@libsql/client": "0.17.3", - "@module-federation/vite": "^1.15.5", + "@module-federation/vite": "1.15.5", "@nuxt/icon": "2.2.2", + "@types/lodash-es": "4.17.12", "bcryptjs": "3.0.3", + "bolt-ui": "workspace:*", "cache": "workspace:*", "common": "workspace:*", "croner": "10.0.1", @@ -17,6 +19,7 @@ "drizzle-pkg": "workspace:*", "drizzle-seed": "0.3.1", "drizzle-zod": "0.8.3", + "lodash-es": "4.18.1", "log4js": "6.9.1", "logger": "workspace:*", "mime": "4.1.0", @@ -34,12 +37,16 @@ "@types/multer": "2.1.0", "@types/node": "25.8.0", "drizzle-kit": "0.31.10", + "sass-embedded": "1.100.0", "tsconfig": "workspace:*", "tsx": "4.21.0", "typescript": "6.0.2", "vue3-toastify": "0.2.9", }, }, + "packages/bolt-ui": { + "name": "bolt-ui", + }, "packages/cache": { "name": "cache", "version": "0.1.0", @@ -120,6 +127,8 @@ "@bomb.sh/tab": ["@bomb.sh/tab@0.0.15", "https://registry.npmmirror.com/@bomb.sh/tab/-/tab-0.0.15.tgz", { "peerDependencies": { "cac": "^6.7.14", "citty": "^0.1.6 || ^0.2.0", "commander": "^13.1.0" }, "optionalPeers": ["cac", "citty", "commander"], "bin": { "tab": "dist/bin/cli.mjs" } }, "sha512-Y90ub44TAvbdO9P8mcD/XPyQjFhiR5xmd4Fk7JErmWmEWEUimNnjWiBrVZ16Tj3GA1rLZ+uvCN2V/pzLawv31g=="], + "@bufbuild/protobuf": ["@bufbuild/protobuf@2.12.0", "", {}, "sha512-B/XlCaFIP8LOwzo+bz5uFzATYokcwCKQcghqnlfwSmM5eX/qTkvDBnDPs+gXtX/RyjxJ4DRikECcPJbyALA8FA=="], + "@clack/core": ["@clack/core@1.3.1", "https://registry.npmmirror.com/@clack/core/-/core-1.3.1.tgz", { "dependencies": { "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" } }, "sha512-fT1qHVGAag4IEkrupZ6lRRbNCs1vS9P01KB/sG8zKgvUztbYtFBtQpjSITNwooDZ83tpsPzP0mRNs1/KVszCRA=="], "@clack/prompts": ["@clack/prompts@1.4.0", "https://registry.npmmirror.com/@clack/prompts/-/prompts-1.4.0.tgz", { "dependencies": { "@clack/core": "1.3.1", "fast-string-width": "^3.0.2", "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" } }, "sha512-S0My7XPGIgpRWMDG8uRqalbgT+a6FmCUdOW+HaIOVVpUPHOb7RrpvjTjiODadKp06fsrVDJZlIzc6yCTp4AnxA=="], @@ -590,6 +599,10 @@ "@types/jsesc": ["@types/jsesc@2.5.1", "https://registry.npmmirror.com/@types/jsesc/-/jsesc-2.5.1.tgz", {}, "sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw=="], + "@types/lodash": ["@types/lodash@4.17.24", "", {}, "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ=="], + + "@types/lodash-es": ["@types/lodash-es@4.17.12", "", { "dependencies": { "@types/lodash": "*" } }, "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ=="], + "@types/multer": ["@types/multer@2.1.0", "https://registry.npmmirror.com/@types/multer/-/multer-2.1.0.tgz", { "dependencies": { "@types/express": "*" } }, "sha512-zYZb0+nJhOHtPpGDb3vqPjwpdeGlGC157VpkqNQL+UU2qwoacoQ7MpsAmUptI/0Oa127X32JzWDqQVEXp2RcIA=="], "@types/node": ["@types/node@25.8.0", "https://registry.npmmirror.com/@types/node/-/node-25.8.0.tgz", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], @@ -720,6 +733,8 @@ "bl": ["bl@4.1.0", "https://registry.npmmirror.com/bl/-/bl-4.1.0.tgz", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], + "bolt-ui": ["bolt-ui@workspace:packages/bolt-ui"], + "boolbase": ["boolbase@1.0.0", "https://registry.npmmirror.com/boolbase/-/boolbase-1.0.0.tgz", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], "brace-expansion": ["brace-expansion@5.0.5", "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-5.0.5.tgz", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="], @@ -762,6 +777,8 @@ "color-name": ["color-name@1.1.4", "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + "colorjs.io": ["colorjs.io@0.5.2", "", {}, "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw=="], + "commander": ["commander@2.20.3", "https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], "common": ["common@workspace:packages/common"], @@ -1000,6 +1017,8 @@ "h3": ["h3@1.15.11", "https://registry.npmmirror.com/h3/-/h3-1.15.11.tgz", { "dependencies": { "cookie-es": "^1.2.3", "crossws": "^0.3.5", "defu": "^6.1.6", "destr": "^2.0.5", "iron-webcrypto": "^1.2.1", "node-mock-http": "^1.0.4", "radix3": "^1.1.2", "ufo": "^1.6.3", "uncrypto": "^0.1.3" } }, "sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg=="], + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + "hasown": ["hasown@2.0.2", "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], "homedir-polyfill": ["homedir-polyfill@1.0.3", "", { "dependencies": { "parse-passwd": "^1.0.0" } }, "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA=="], @@ -1024,6 +1043,8 @@ "image-meta": ["image-meta@0.2.2", "https://registry.npmmirror.com/image-meta/-/image-meta-0.2.2.tgz", {}, "sha512-3MOLanc3sb3LNGWQl1RlQlNWURE5g32aUphrDyFeCsxBTk08iE3VNe4CwsUZ0Qs1X+EfX0+r29Sxdpza4B+yRA=="], + "immutable": ["immutable@5.1.6", "", {}, "sha512-q1swsS8K7L8usSHuOqF2TAoCCkonYz0SG38wLAggaa4Wml70zixIvt2ql4coQ2C2B3hTjltJry4r6bULwgAXLQ=="], + "import-meta-resolve": ["import-meta-resolve@4.2.0", "https://registry.npmmirror.com/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="], "impound": ["impound@1.1.5", "https://registry.npmmirror.com/impound/-/impound-1.1.5.tgz", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.31", "es-module-lexer": "^2.0.0", "pathe": "^2.0.3", "unplugin": "^3.0.0", "unplugin-utils": "^0.3.1" } }, "sha512-5AUn+QE0UofqNHu5f2Skf6Svukdg4ehOIq8O0EtqIx4jta0CDZYBPqpIHt0zrlUTiFVYlLpeH39DoikXBjPKpA=="], @@ -1132,6 +1153,8 @@ "lodash": ["lodash@4.18.1", "https://registry.npmmirror.com/lodash/-/lodash-4.18.1.tgz", {}, "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q=="], + "lodash-es": ["lodash-es@4.18.1", "", {}, "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A=="], + "lodash.defaults": ["lodash.defaults@4.2.0", "https://registry.npmmirror.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz", {}, "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="], "lodash.isarguments": ["lodash.isarguments@3.1.0", "https://registry.npmmirror.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", {}, "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg=="], @@ -1446,10 +1469,52 @@ "run-parallel": ["run-parallel@1.2.0", "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], + "rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="], + "safe-buffer": ["safe-buffer@5.2.1", "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], "safer-buffer": ["safer-buffer@2.1.2", "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + "sass": ["sass@1.100.0", "", { "dependencies": { "chokidar": "^5.0.0", "immutable": "^5.1.5", "source-map-js": ">=0.6.2 <2.0.0" }, "optionalDependencies": { "@parcel/watcher": "^2.4.1" }, "bin": { "sass": "sass.js" } }, "sha512-B5j0rYMlinhhOo9tjQebMVVn0TfyXAF+wB3b2ggZUuJ/is/Y+7+JGjirAMxHZ9Z3hIP98NPfamlAkBHa1lAaXQ=="], + + "sass-embedded": ["sass-embedded@1.100.0", "", { "dependencies": { "@bufbuild/protobuf": "^2.5.0", "colorjs.io": "^0.5.0", "immutable": "^5.1.5", "rxjs": "^7.4.0", "supports-color": "^8.1.1", "sync-child-process": "^1.0.2", "varint": "^6.0.0" }, "optionalDependencies": { "sass-embedded-all-unknown": "1.100.0", "sass-embedded-android-arm": "1.100.0", "sass-embedded-android-arm64": "1.100.0", "sass-embedded-android-riscv64": "1.100.0", "sass-embedded-android-x64": "1.100.0", "sass-embedded-darwin-arm64": "1.100.0", "sass-embedded-darwin-x64": "1.100.0", "sass-embedded-linux-arm": "1.100.0", "sass-embedded-linux-arm64": "1.100.0", "sass-embedded-linux-musl-arm": "1.100.0", "sass-embedded-linux-musl-arm64": "1.100.0", "sass-embedded-linux-musl-riscv64": "1.100.0", "sass-embedded-linux-musl-x64": "1.100.0", "sass-embedded-linux-riscv64": "1.100.0", "sass-embedded-linux-x64": "1.100.0", "sass-embedded-unknown-all": "1.100.0", "sass-embedded-win32-arm64": "1.100.0", "sass-embedded-win32-x64": "1.100.0" }, "bin": { "sass": "dist/bin/sass.js" } }, "sha512-Ut8wlQSk19tm7jMK6mz6cF1+e+E7tUnW2tM02zQDPnOTcVbV8qCQG8UWxZkkNlY50+hV3hqP24OOkUlMz8xBpw=="], + + "sass-embedded-all-unknown": ["sass-embedded-all-unknown@1.100.0", "", { "dependencies": { "sass": "1.100.0" }, "cpu": [ "!arm", "!x64", "!arm64", ] }, "sha512-auFtXY/kwYILmSVjtBDwyj0axcLbYYiffOKWoaXHnI5bsYwiRbBh3EneR1rpbX2ZIZCrwX93i5pxKLTZF/662Q=="], + + "sass-embedded-android-arm": ["sass-embedded-android-arm@1.100.0", "", { "os": "android", "cpu": "arm" }, "sha512-70f3HgX2pFNmzpGQ86n5e6QfWn2fP4QUQGfFQK0P1XH73ZLIzLo2YqygrGKGKeeqtc5eU2Wl1/xQzhzuKnO4kw=="], + + "sass-embedded-android-arm64": ["sass-embedded-android-arm64@1.100.0", "", { "os": "android", "cpu": "arm64" }, "sha512-W+Ru9JwTnfU0UX3jSZcbqFdtKFMcYdfFwytc57h2DgnqCOIiAqI2E06mABZBZC+r3LwXCBuS5GbXAGeVgvVDkA=="], + + "sass-embedded-android-riscv64": ["sass-embedded-android-riscv64@1.100.0", "", { "os": "android", "cpu": "none" }, "sha512-icU3o0V/uCSytSpf+tX5Lf51BvyQEbLzDUJfUi9etSauYBGHpPKkdtdZH0si4v98phq11Kl8rSV1SggksxF1Hg=="], + + "sass-embedded-android-x64": ["sass-embedded-android-x64@1.100.0", "", { "os": "android", "cpu": "x64" }, "sha512-mevF9VQk6gEYByy8+jusaHGmd7Usb2ytX/DsEOd0JtOGCtcf1kh575xJ6OUBDIcJ15uLnbau/0iy1eP6WVBvWA=="], + + "sass-embedded-darwin-arm64": ["sass-embedded-darwin-arm64@1.100.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-1PVlYi61POo93IT/FfrG1mc1tAHxeSTyUALF2aOFmXGWjVXr3bQzEQiBGCOvQbj/ix+5hNyXFXcEMEyKvtUJJA=="], + + "sass-embedded-darwin-x64": ["sass-embedded-darwin-x64@1.100.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-x97o3JnGyImZNCIVs9wQHJUE5QCvmVIKaH1cwrz/5dK7OT1FpeNiW+u9TUomP9hG6Ekjd8EL8NBHpxTfIhdjmg=="], + + "sass-embedded-linux-arm": ["sass-embedded-linux-arm@1.100.0", "", { "os": "linux", "cpu": "arm" }, "sha512-9Ul7O1eKrc5YlhwWjkp8tZPSe3UEwSZ1uwUZOQom1HL0pRlBA6F/IlGZYFTLwnHMIP1fc77MMNaBRfc05mKMpw=="], + + "sass-embedded-linux-arm64": ["sass-embedded-linux-arm64@1.100.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-Dwjmj8Z6VRy7rAi53JAdEwIyUjpfl7PhpSc2/LpQPQx+aO5Dp7Spaipkax0ufJl1SoDUdchCsM4y/88YaluorQ=="], + + "sass-embedded-linux-musl-arm": ["sass-embedded-linux-musl-arm@1.100.0", "", { "os": "linux", "cpu": "arm" }, "sha512-sl0JgbGloPyJg66XXx5UDSDScZ0oU85DpMQU4JU/sCUCFj1Z8zZ69SJWKTCNE4/jwnce7WI2zPCV5AG+RHOZJw=="], + + "sass-embedded-linux-musl-arm64": ["sass-embedded-linux-musl-arm64@1.100.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-XpACJB2KjSLjf2e9uuvGVdOURsoNrFqgRiihhXyUHK9W0t3LIHb7z5MA/7XGPIT9bWSOO2zyw+rH/FHtDV/Yrg=="], + + "sass-embedded-linux-musl-riscv64": ["sass-embedded-linux-musl-riscv64@1.100.0", "", { "os": "linux", "cpu": "none" }, "sha512-ShvI0Kx04mwoCARwZ0UjiT97isQvzO80tAt91zmFyHLN9kelc/IrQi940farSm2xQVPCKdeVyeG0ekBsokSpYQ=="], + + "sass-embedded-linux-musl-x64": ["sass-embedded-linux-musl-x64@1.100.0", "", { "os": "linux", "cpu": "x64" }, "sha512-TDBCRWNuS4RDLQXvRc1gjZlWiWTWaWGp0Bwu/IKwJxov81lsvrCs3TihTyNXtW7V5aoN4Ky3r0QOkNb3mwmBnA=="], + + "sass-embedded-linux-riscv64": ["sass-embedded-linux-riscv64@1.100.0", "", { "os": "linux", "cpu": "none" }, "sha512-j4ENJGOheO+fm3j/yorLxCjBP6/XskrZx7dTLlT+lXYwN/qqCqoA/gsNLI0McS3DFM6GBwPiffzWsdWS8t6sEQ=="], + + "sass-embedded-linux-x64": ["sass-embedded-linux-x64@1.100.0", "", { "os": "linux", "cpu": "x64" }, "sha512-0vUSN8j0WGtCJIOPh//EmUvYGHW0QOe5iul8qyhPk50MAcw49MA0r34AhftjDdx94ILPF6vApFs0gwHPQRlpVA=="], + + "sass-embedded-unknown-all": ["sass-embedded-unknown-all@1.100.0", "", { "dependencies": { "sass": "1.100.0" }, "os": [ "!linux", "!win32", "!darwin", "!android", ] }, "sha512-c+naBgWId4MIpToXcI0DgqetjdAkwTTAxFAuOaBz7HUXLdyG1oZRrEvSsbe41nEdQOKH0vgofVFCeSQgoXOG9A=="], + + "sass-embedded-win32-arm64": ["sass-embedded-win32-arm64@1.100.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-iE+yxj+hUXwwbqpHkXxgAWTzeRfcWxJ7SSTQEPMk48lwq3oCrWLlz5sQuWHbuTK/i0GKQfROdP+hOmPi89yjUg=="], + + "sass-embedded-win32-x64": ["sass-embedded-win32-x64@1.100.0", "", { "os": "win32", "cpu": "x64" }, "sha512-qI4F8MI7/KYoy9NdjJfhSspG42WPkADSNDvwEV7qWvCSFC83koJssRsKO2/PfY+niZz6BG65Ic/D+A11h959hw=="], + "sax": ["sax@1.6.0", "https://registry.npmmirror.com/sax/-/sax-1.6.0.tgz", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="], "scule": ["scule@1.3.0", "https://registry.npmmirror.com/scule/-/scule-1.3.0.tgz", {}, "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g=="], @@ -1536,7 +1601,7 @@ "stylehacks": ["stylehacks@7.0.11", "https://registry.npmmirror.com/stylehacks/-/stylehacks-7.0.11.tgz", { "dependencies": { "browserslist": "^4.28.2", "postcss-selector-parser": "^7.1.1" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-iODNfhXVLqc5LADs+Y6Oh5wJuK5ZcHbVng8aiK3y9pjMQdc5hLrBW0eFU6FtnpNrE6PoEg/MmFTU4waotj5WNg=="], - "supports-color": ["supports-color@10.2.2", "https://registry.npmmirror.com/supports-color/-/supports-color-10.2.2.tgz", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="], + "supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], @@ -1544,6 +1609,10 @@ "svgo": ["svgo@4.0.1", "https://registry.npmmirror.com/svgo/-/svgo-4.0.1.tgz", { "dependencies": { "commander": "^11.1.0", "css-select": "^5.1.0", "css-tree": "^3.0.1", "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.1.1", "sax": "^1.5.0" }, "bin": "./bin/svgo.js" }, "sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w=="], + "sync-child-process": ["sync-child-process@1.0.2", "", { "dependencies": { "sync-message-port": "^1.0.0" } }, "sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA=="], + + "sync-message-port": ["sync-message-port@1.2.0", "", {}, "sha512-gAQ9qrUN/UCypHtGFbbe7Rc/f9bzO88IwrG8TDo/aMKAApKyD6E3W4Cm0EfhfBb6Z6SKt59tTCTfD+n1xmAvMg=="], + "tagged-tag": ["tagged-tag@1.0.0", "https://registry.npmmirror.com/tagged-tag/-/tagged-tag-1.0.0.tgz", {}, "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng=="], "tailwindcss": ["tailwindcss@4.3.0", "https://registry.npmmirror.com/tailwindcss/-/tailwindcss-4.3.0.tgz", {}, "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q=="], @@ -1640,6 +1709,8 @@ "util-deprecate": ["util-deprecate@1.0.2", "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + "varint": ["varint@6.0.0", "", {}, "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg=="], + "vite": ["vite@7.3.2", "https://registry.npmmirror.com/vite/-/vite-7.3.2.tgz", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.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" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg=="], "vite-dev-rpc": ["vite-dev-rpc@1.1.0", "https://registry.npmmirror.com/vite-dev-rpc/-/vite-dev-rpc-1.1.0.tgz", { "dependencies": { "birpc": "^2.4.0", "vite-hot-client": "^2.1.0" }, "peerDependencies": { "vite": "^2.9.0 || ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.1 || ^7.0.0-0" } }, "sha512-pKXZlgoXGoE8sEKiKJSng4hI1sQ4wi5YT24FCrwrLt6opmkjlqPPVmiPWWJn8M8byMxRGzp1CrFuqQs4M/Z39A=="], @@ -1764,6 +1835,8 @@ "@parcel/watcher-wasm/napi-wasm": ["napi-wasm@1.1.3", "https://registry.npmmirror.com/napi-wasm/-/napi-wasm-1.1.3.tgz", { "bundled": true }, "sha512-h/4nMGsHjZDCYmQVNODIrYACVJ+I9KItbG+0si6W/jSjdA9JbWDoU4LLeMXVcEQGHjttI2tuXqDrbGF7qkUHHg=="], + "@poppinss/dumper/supports-color": ["supports-color@10.2.2", "https://registry.npmmirror.com/supports-color/-/supports-color-10.2.2.tgz", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="], + "@rollup/plugin-commonjs/estree-walker": ["estree-walker@2.0.2", "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], "@rollup/plugin-inject/estree-walker": ["estree-walker@2.0.2", "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], diff --git a/nuxt.config.ts b/nuxt.config.ts index 8a7b683..bdf5695 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -1,10 +1,16 @@ import tailwindcss from '@tailwindcss/vite' -import { federation } from "@module-federation/vite"; +// import { federation } from "@module-federation/vite"; // https://nuxt.com/docs/api/configuration/nuxt-config export default defineNuxtConfig({ compatibilityDate: '2025-07-15', + modules: ['@nuxt/icon', 'bolt-ui/nuxt'], + + boltUi: { + importStyles: false, + }, + app: { head: { link: [ @@ -69,7 +75,6 @@ export default defineNuxtConfig({ } }, - modules: ['@nuxt/icon'], icon: { mode: 'css', cssLayer: 'base' diff --git a/package.json b/package.json index a5a74ca..24e1d56 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,9 @@ "@libsql/client": "0.17.3", "@module-federation/vite": "1.15.5", "@nuxt/icon": "2.2.2", + "@types/lodash-es": "4.17.12", "bcryptjs": "3.0.3", + "bolt-ui": "workspace:*", "cache": "workspace:*", "common": "workspace:*", "croner": "10.0.1", @@ -31,6 +33,7 @@ "drizzle-pkg": "workspace:*", "drizzle-seed": "0.3.1", "drizzle-zod": "0.8.3", + "lodash-es": "4.18.1", "log4js": "6.9.1", "logger": "workspace:*", "mime": "4.1.0", @@ -48,6 +51,7 @@ "@types/multer": "2.1.0", "@types/node": "25.8.0", "drizzle-kit": "0.31.10", + "sass-embedded": "1.100.0", "tsconfig": "workspace:*", "tsx": "4.21.0", "typescript": "6.0.2", diff --git a/packages/bolt-ui/components/Button/index.ts b/packages/bolt-ui/components/Button/index.ts new file mode 100644 index 0000000..2955940 --- /dev/null +++ b/packages/bolt-ui/components/Button/index.ts @@ -0,0 +1,5 @@ +import { withInstall } from 'bolt-ui/utils/vue/install' +import Button from './src/Button.vue' + +export const BoButton = withInstall(Button) +export default BoButton diff --git a/packages/bolt-ui/components/Button/src/Button.vue b/packages/bolt-ui/components/Button/src/Button.vue new file mode 100644 index 0000000..48bb5ef --- /dev/null +++ b/packages/bolt-ui/components/Button/src/Button.vue @@ -0,0 +1,23 @@ + + + diff --git a/packages/bolt-ui/components/Button/style/index.ts b/packages/bolt-ui/components/Button/style/index.ts new file mode 100644 index 0000000..4bf1d74 --- /dev/null +++ b/packages/bolt-ui/components/Button/style/index.ts @@ -0,0 +1 @@ +import 'bolt-ui/theme-chalk/src/button.scss' diff --git a/packages/bolt-ui/components/ConfigProvider/index.ts b/packages/bolt-ui/components/ConfigProvider/index.ts new file mode 100644 index 0000000..8688ea2 --- /dev/null +++ b/packages/bolt-ui/components/ConfigProvider/index.ts @@ -0,0 +1,5 @@ +import { withInstall } from 'bolt-ui/utils/vue/install' +import ConfigProvider from './src/ConfigProvider.vue' + +export const BoConfigProvider = withInstall(ConfigProvider) +export default BoConfigProvider diff --git a/packages/bolt-ui/components/ConfigProvider/src/ConfigProvider.vue b/packages/bolt-ui/components/ConfigProvider/src/ConfigProvider.vue new file mode 100644 index 0000000..8398667 --- /dev/null +++ b/packages/bolt-ui/components/ConfigProvider/src/ConfigProvider.vue @@ -0,0 +1,33 @@ + + + diff --git a/packages/bolt-ui/components/ConfigProvider/src/Token.ts b/packages/bolt-ui/components/ConfigProvider/src/Token.ts new file mode 100644 index 0000000..a389cf0 --- /dev/null +++ b/packages/bolt-ui/components/ConfigProvider/src/Token.ts @@ -0,0 +1,3 @@ +import { ComputedRef, InjectionKey } from 'vue' + +export const ConfigToken: InjectionKey> = Symbol('ConfigToken') diff --git a/packages/bolt-ui/components/ConfigProvider/src/useConfigProvider.ts b/packages/bolt-ui/components/ConfigProvider/src/useConfigProvider.ts new file mode 100644 index 0000000..5bcffc9 --- /dev/null +++ b/packages/bolt-ui/components/ConfigProvider/src/useConfigProvider.ts @@ -0,0 +1,10 @@ +import { inject } from 'vue' +import { ConfigToken } from './Token' + +export function useConfigProvider() { + const config = inject(ConfigToken) + if (!config) { + throw new Error('ConfigProvider not found') + } + return config +} diff --git a/packages/bolt-ui/components/ConfigProvider/style/index.ts b/packages/bolt-ui/components/ConfigProvider/style/index.ts new file mode 100644 index 0000000..e7f0809 --- /dev/null +++ b/packages/bolt-ui/components/ConfigProvider/style/index.ts @@ -0,0 +1 @@ +export {} diff --git a/packages/bolt-ui/components/index.ts b/packages/bolt-ui/components/index.ts new file mode 100644 index 0000000..f113dff --- /dev/null +++ b/packages/bolt-ui/components/index.ts @@ -0,0 +1,2 @@ +export * from './Button' +export * from './ConfigProvider' diff --git a/packages/bolt-ui/hooks/createContext/index.ts b/packages/bolt-ui/hooks/createContext/index.ts new file mode 100644 index 0000000..b807bb0 --- /dev/null +++ b/packages/bolt-ui/hooks/createContext/index.ts @@ -0,0 +1,67 @@ +import { getCurrentInstance, inject, provide } from 'vue' + +import type { InjectionKey } from 'vue' + +export interface CreateContextOptions { + /** 严格模式:未找到 Provider 且没有 defaultValue 时是否抛错,默认 true */ + strict?: boolean + /** 默认值工厂:当没有 Provider 时调用,用返回值兜底,避免共享状态 */ + defaultValue?: () => T + /** 自定义 InjectionKey,一般不需要传 */ + key?: InjectionKey +} + +/** + * 创建一套基于 provide/inject 的上下文工具。 + * + * 使用方式: + * const FooContext = createContext('Foo', { + * strict: true, + * defaultValue: () => ({ ... }) + * }) + * + * FooContext.provideContext(...) + * const ctx = FooContext.useContext() + */ +export const createContext = (name?: string, options: CreateContextOptions = {}) => { + const { strict = true, defaultValue, key } = options + const contextKey: InjectionKey = key ?? (Symbol(name ?? 'Context') as InjectionKey) + + const useContext = () => { + // 在 setup 之外调用没有意义,这里主动给出更友好的错误 + if (!getCurrentInstance()) { + throw new Error('useContext 只能在 setup 或生命周期钩子中使用') + } + + const ctx = inject(contextKey, undefined) + if (ctx !== undefined) { + return ctx + } + + if (defaultValue) { + // 每次调用 defaultValue 都返回一个“新”默认值,避免跨实例共享 + return defaultValue() + } + + if (strict) { + throw new Error( + `未找到上层 Provider,请确保已经调用 provideContext 或在正确的组件树中使用(${name ?? 'Context'})` + ) + } + + return undefined as unknown as T + } + + const provideContext = (context: T) => { + provide(contextKey, context) + } + + return { + /** 当前上下文使用的 InjectionKey */ + key: contextKey, + /** 在上层组件中调用,用于提供上下文 */ + provideContext, + /** 在子组件中调用,用于消费上下文,如果未找到会抛出错误 / 或返回默认值 */ + useContext + } +} diff --git a/packages/bolt-ui/hooks/index.ts b/packages/bolt-ui/hooks/index.ts new file mode 100644 index 0000000..5569b19 --- /dev/null +++ b/packages/bolt-ui/hooks/index.ts @@ -0,0 +1 @@ +export * from './useClickOutside' diff --git a/packages/bolt-ui/hooks/useClickOutside/index.ts b/packages/bolt-ui/hooks/useClickOutside/index.ts new file mode 100644 index 0000000..ae22329 --- /dev/null +++ b/packages/bolt-ui/hooks/useClickOutside/index.ts @@ -0,0 +1,38 @@ +import { onMounted, onUnmounted, Ref } from 'vue' + +export function useClickOutside( + elementRef: Ref | HTMLElement, + callback: (event: MouseEvent) => void, + options: { + ignore?: Ref[] // 需要忽略的元素 + } = {} +) { + const { ignore = [] } = options + + const handler = (event: MouseEvent) => { + const el = elementRef instanceof HTMLElement ? elementRef : elementRef.value + + if (!el) return + + // 检查点击是否在忽略的元素内 + const isIgnored = ignore.some((ref) => { + const element = ref?.value + return element && (element === event.target || element.contains(event.target as Node)) + }) + + if (isIgnored) return + + // 检查点击是否在目标元素外 + if (!(el === event.target || el.contains(event.target as Node))) { + callback(event) + } + } + + onMounted(() => { + document.addEventListener('click', handler, true) + }) + + onUnmounted(() => { + document.removeEventListener('click', handler, true) + }) +} diff --git a/packages/bolt-ui/index.ts b/packages/bolt-ui/index.ts new file mode 100644 index 0000000..ca23918 --- /dev/null +++ b/packages/bolt-ui/index.ts @@ -0,0 +1,2 @@ +export * from './components' +export * from './hooks' diff --git a/packages/bolt-ui/locales/index.ts b/packages/bolt-ui/locales/index.ts new file mode 100644 index 0000000..e5ad4e8 --- /dev/null +++ b/packages/bolt-ui/locales/index.ts @@ -0,0 +1,68 @@ +import { get } from 'lodash-es' + +import zh from './languages/zh.json' +import en from './languages/en.json' +import { reactive } from 'vue' + +const Languages = { + zh: zh, + en: en +} + +export type LanguagesType = keyof typeof Languages + +const LocaleState = reactive<{ + locale: LanguagesType +}>({ + locale: 'zh' +}) + +type FlattenObject = T extends object + ? { + [K in keyof T & (string | number)]: FlattenObject< + T[K], + Prefix extends '' ? `${K}` : `${Prefix}.${K}` + > + }[keyof T & (string | number)] + : Prefix + +type FlattenKeys = FlattenObject + +type TranslationKey = FlattenKeys + +function useLocale() { + function setLocale(locale: LanguagesType) { + LocaleState.locale = locale + } + + function getLocale(): string { + return LocaleState.locale + } + + function t(key: TranslationKey, replacements?: Record): string { + let text: string = + LocaleState.locale in Languages + ? get(Languages[LocaleState.locale], key) + : get(Languages['zh'], key) + if (!text) { + text = get(Languages['zh'], key) + if (!text) { + return key + } + } + if (replacements) { + Object.entries(replacements).forEach(([key, value]) => { + text = text.replace(new RegExp(`{${key}}`, 'g'), value) + }) + } + return text + } + + return { + setLocale, + getLocale, + t + } +} + +export { useLocale } diff --git a/packages/bolt-ui/locales/languages/en.json b/packages/bolt-ui/locales/languages/en.json new file mode 100644 index 0000000..371ccb3 --- /dev/null +++ b/packages/bolt-ui/locales/languages/en.json @@ -0,0 +1,11 @@ +{ + "input": { + "placeholder": "Please enter content" + }, + "select": { + "placeholder": "Please select" + }, + "empty": { + "description": "No Data" + } +} diff --git a/packages/bolt-ui/locales/languages/zh.json b/packages/bolt-ui/locales/languages/zh.json new file mode 100644 index 0000000..9868ad6 --- /dev/null +++ b/packages/bolt-ui/locales/languages/zh.json @@ -0,0 +1,11 @@ +{ + "input": { + "placeholder": "请输入内容" + }, + "select": { + "placeholder": "请选择" + }, + "empty": { + "description": "暂无数据" + } +} diff --git a/packages/bolt-ui/nuxt.ts b/packages/bolt-ui/nuxt.ts new file mode 100644 index 0000000..d55b219 --- /dev/null +++ b/packages/bolt-ui/nuxt.ts @@ -0,0 +1,136 @@ +import { + defineNuxtModule, + addComponent, + addImports, + createResolver, +} from '@nuxt/kit' +import { readdirSync, existsSync, readFileSync } from 'node:fs' +import { join } from 'node:path' + +export interface ModuleOptions { + /** + * Whether to import global base styles (CSS variables / design tokens). + * @default true + */ + importStyles?: boolean +} + +function kebabCase(str: string): string { + return str.replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^-/, '') +} + +// ── 从源码中提取 export function / export const 名称 ───────── +function extractExports(filePath: string): string[] { + try { + const content = readFileSync(filePath, 'utf-8') + const names: string[] = [] + const re = /export\s+(?:function|const|let|var)\s+(\w+)/g + let m: RegExpExecArray | null + while ((m = re.exec(content)) !== null) names.push(m[1] as any) + return names + } catch { + return [] + } +} + +function isComposable(name: string): boolean { + return name.startsWith('use') || name === 'createContext' +} + +// ── Vite 插件:自动注入组件样式 ──────────────────────────── +function BoltUiStylePlugin(styleMap: Record) { + return { + name: 'bolt-ui:style-inject', + enforce: 'post', + transform(code: string, id: string) { + if (!/\.(vue|tsx?|jsx?)$/.test(id)) return null + if (id.includes('node_modules')) return null + + const matchedStyles = Object.entries(styleMap) + .filter(([name]) => code.includes(name)) + .map(([, style]) => style) + + if (matchedStyles.length === 0) return null + + const imports = [...new Set(matchedStyles)] + .map(p => `import '${p}';`) + .join('\n') + + return { + code: imports + '\n' + code, + map: null, + } + }, + } +} + +export default defineNuxtModule({ + meta: { + name: 'bolt-ui', + configKey: 'boltUi', + }, + defaults: { + importStyles: true, + }, + setup(options, nuxt) { + const resolver = createResolver(import.meta.url) + + // ── CSS 变量层(design tokens)──────────────────────────── + if (options.importStyles) { + nuxt.options.css.push(resolver.resolve('./theme-chalk/src/theme/index.scss')) + } + + // ── 自动扫描组件目录 ──────────────────────────────────── + const componentsDir = resolver.resolve('./components') + const componentStyleMap: Record = {} + + for (const entry of readdirSync(componentsDir, { withFileTypes: true })) { + if (!entry.isDirectory()) continue + + const name = entry.name + const indexPath = join(componentsDir, name, 'index.ts') + if (!existsSync(indexPath)) continue + + // 注册组件 + addComponent({ + name: `Bo${name}`, + export: `Bo${name}`, + filePath: resolver.resolve(`./components/${name}/index.ts`), + }) + + // 构建样式映射(约定:theme-chalk/src/${kebab}.scss) + const scssPath = join(componentsDir, '..', 'theme-chalk/src', `${kebabCase(name)}.scss`) + if (existsSync(scssPath)) { + componentStyleMap[`Bo${name}`] = `bolt-ui/theme-chalk/src/${kebabCase(name)}.scss` + } + } + + // ── Vite 插件:按需注入组件样式 ───────────────────────── + nuxt.hook('vite:extendConfig', (config) => { + // @ts-ignore + config.plugins = config.plugins || [] + config.plugins.push(BoltUiStylePlugin(componentStyleMap) as any) + }) + + // ── 自动扫描 composable 目录 ────────────────────────── + for (const scanDir of ['hooks', 'utils', 'locales']) { + const absDir = resolver.resolve(`./${scanDir}`) + if (!existsSync(absDir)) continue + + const walk = (dir: string) => { + for (const entry of readdirSync(dir, { withFileTypes: true })) { + const fullPath = join(dir, entry.name) + if (entry.isDirectory()) { + walk(fullPath) + } else if (entry.name === 'index.ts') { + const names = extractExports(fullPath).filter(isComposable) + for (const name of names) { + addImports({ name, as: name, from: fullPath }) + } + } + } + } + walk(absDir) + } + }, +}) diff --git a/packages/bolt-ui/package.json b/packages/bolt-ui/package.json new file mode 100644 index 0000000..7155e2d --- /dev/null +++ b/packages/bolt-ui/package.json @@ -0,0 +1,8 @@ +{ + "name": "bolt-ui", + "type": "module", + "private": true, + "scripts": { + "build": "tsc -p ./tsconfig.json" + } +} diff --git a/packages/bolt-ui/resolver.ts b/packages/bolt-ui/resolver.ts new file mode 100644 index 0000000..84dc59d --- /dev/null +++ b/packages/bolt-ui/resolver.ts @@ -0,0 +1,67 @@ +const noStylesComponents: any[] = [] + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export default (options: any = {}): any => { + let optionsResolved: any + + async function resolveOptions() { + if (optionsResolved) return optionsResolved + optionsResolved = { + exclude: undefined, + noStylesComponents: options.noStylesComponents || [], + ...options + } + return optionsResolved + } + return { + type: 'component', + resolve: async (name: string) => { + const options = await resolveOptions() + + if ([...options.noStylesComponents, ...noStylesComponents].includes(name)) + return resolveComponent(name, { ...options, importStyle: false }) + else return resolveComponent(name, options) + } + } +} + +// function kebabCase(key: string) { +// const result = key.replace(/([A-Z])/g, ' $1').trim() +// return result.split(' ').join('-').toLowerCase() +// } + +function pascalCase(key: string): string { + // 第一步:将所有分隔符(-、_、空格)替换为空格,统一处理 + const replaced = key.replace(/[-_\s]/g, ' ') + // 第二步:处理可能的连续大写字母(如HTML),在大写字母前加空格(除了开头) + const spaced = replaced.replace(/([A-Z])/g, (_, p1, index) => { + return index === 0 ? p1 : ` ${p1}` + }) + // 第三步:去除首尾空格,并按空格拆分为单词数组 + const words = spaced.trim().split(/\s+/) + // 第四步:每个单词首字母大写,其余小写,然后拼接 + return words + .map((word) => { + if (word.length === 0) return '' + return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase() + }) + .join('') +} + +function resolveComponent(name: string, options: any) { + if (options.exclude && name.match(options.exclude)) return + + if (!name.match(/^Bo[A-Z]/)) return + + const partialName = pascalCase(name.slice(2)) + return { + name, + from: `${'bolt-ui'}`, + sideEffects: getSideEffects(partialName) + } +} + +function getSideEffects(dirName: string) { + const componentsFolder = 'bolt-ui/components' + return [`${componentsFolder}/${dirName}/style/index`, 'bolt-ui/theme-chalk/src/theme/index.scss'] +} diff --git a/packages/bolt-ui/theme-chalk/src/button.scss b/packages/bolt-ui/theme-chalk/src/button.scss new file mode 100644 index 0000000..e0a967f --- /dev/null +++ b/packages/bolt-ui/theme-chalk/src/button.scss @@ -0,0 +1,50 @@ +@use 'core/_base' as *; +@use 'sass:selector'; + +@include setNamespace('button'); + +#{b()} { + display: inline-block; + white-space: nowrap; + cursor: pointer; + background: #fff; + // border: 1px solid #dcdfe6; + // border-color: #dcdfe6; + border-radius: 8px; + border: none; + padding: 0 20px; + height: 40px; + line-height: 40px; + font-size: 14px; + + &#{m('primary')} { + background: var(--color-primary); + color: var(--color-primary-content); + } + &#{m('secondary')} { + background: var(--color-secondary); + color: var(--color-secondary-content); + } + &#{m('tertiary')} { + background: #f8f9fa; + color: #212529; + } + &#{m('danger')} { + background: #dc3545; + color: #fff; + } + &#{m('warning')} { + background: #ffc107; + color: #212529; + } + + &:hover { + opacity: 0.9; + } + &:active { + opacity: 0.8; + } + &:disabled { + opacity: 0.5; + } +} diff --git a/packages/bolt-ui/theme-chalk/src/card.scss b/packages/bolt-ui/theme-chalk/src/card.scss new file mode 100644 index 0000000..85b30f0 --- /dev/null +++ b/packages/bolt-ui/theme-chalk/src/card.scss @@ -0,0 +1,11 @@ +@use 'core/_base' as *; +@use 'sass:selector'; + +@include setNamespace('card'); + +#{b()} { + background-color: #fff; + border-radius: 4px; + padding: 10px; + box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1); +} diff --git a/packages/bolt-ui/theme-chalk/src/core/_base.scss b/packages/bolt-ui/theme-chalk/src/core/_base.scss new file mode 100644 index 0000000..98f7ccc --- /dev/null +++ b/packages/bolt-ui/theme-chalk/src/core/_base.scss @@ -0,0 +1,44 @@ +$prefix: 'bo'; +$namespace: ''; + +@mixin setNamespace($nc) { + $namespace: $nc !global; +} + +@function b() { + @return '.#{$prefix}-#{$namespace}'; +} + +@function e($element) { + $block: b(); + @return '#{$block}__#{$element}'; +} + +@function m($modifier) { + $block: b(); + @return '#{$block}--#{$modifier}'; +} + +@function bm($blockSuffix, $modifier) { + $block: b(); + @return '#{$block}-#{$blockSuffix}--#{$modifier}'; +} + +@function em($element, $modifier) { + $block: b(); + @return '#{$block}__#{$element}--#{$modifier}'; +} + +@function be($blockSuffix, $element) { + $block: b(); + @return '#{$block}-#{$blockSuffix}__#{$element}'; +} + +@function bem($blockSuffix, $element, $modifier) { + $block: b(); + @return '#{$block}-#{$blockSuffix}__#{$element}--#{$modifier}'; +} + +@function is($state) { + @return '.is-#{$state}'; +} diff --git a/packages/bolt-ui/theme-chalk/src/dropdown.scss b/packages/bolt-ui/theme-chalk/src/dropdown.scss new file mode 100644 index 0000000..4f92481 --- /dev/null +++ b/packages/bolt-ui/theme-chalk/src/dropdown.scss @@ -0,0 +1,51 @@ +@use 'core/_base' as *; +@use 'sass:selector'; + +@include setNamespace('dropdown'); + +#{b()} { + position: relative; + + #{e('content')} { + position: absolute; + top: 100%; + left: 0; + z-index: 9999; + display: grid; + grid-template-rows: 1fr; + transition: grid-template-rows 0.1s ease-out; + overflow: hidden; + min-width: 80px; + &#{is('hidden')} { + grid-template-rows: 0fr; + } + #{e('wrapper')} { + min-height: 0; + #{e('list')} { + box-sizing: border-box; + background-color: #fff; + border: 1px solid #dcdfe6; + border-radius: 4px; + + #{e('item')} { + padding: 10px; + font-size: 14px; + line-height: 1; + display: flex; + justify-content: center; + cursor: pointer; + &:hover { + background-color: #f5f5f5; + } + } + + #{e('line')} { + width: 100%; + height: 1px; + background-color: #dcdfe6; + transform: scaleY(0.5); + } + } + } + } +} diff --git a/packages/bolt-ui/theme-chalk/src/empty.scss b/packages/bolt-ui/theme-chalk/src/empty.scss new file mode 100644 index 0000000..81d0228 --- /dev/null +++ b/packages/bolt-ui/theme-chalk/src/empty.scss @@ -0,0 +1,38 @@ +@use 'core/_base' as *; +@use 'sass:selector'; + +@include setNamespace('empty'); + +#{b()} { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 40px 20px; + text-align: center; + color: #909399; + font-size: 14px; + + #{e('image')} { + margin-bottom: 20px; + display: flex; + align-items: center; + justify-content: center; + + img { + display: block; + user-select: none; + } + } + + #{e('description')} { + margin-bottom: 20px; + color: #909399; + font-size: 14px; + line-height: 1.5; + } + + #{e('footer')} { + margin-top: 10px; + } +} diff --git a/packages/bolt-ui/theme-chalk/src/float.scss b/packages/bolt-ui/theme-chalk/src/float.scss new file mode 100644 index 0000000..17bb05f --- /dev/null +++ b/packages/bolt-ui/theme-chalk/src/float.scss @@ -0,0 +1,59 @@ +@use 'core/_base' as *; + +@include setNamespace('float'); + +#{b()} { + box-sizing: border-box; + margin: 0; + min-width: 0; + /* fixed + 块级 + 100% 会占满视口宽,getBoundingClientRect 近 innerWidth,clamp 会把 x 锁死 */ + width: max-content; + max-width: calc(100vw - 1rem); + border-radius: var(--radius-box, 0.5rem); + box-shadow: + 0 4px 6px -1px rgb(0 0 0 / 0.08), + 0 2px 4px -2px rgb(0 0 0 / 0.06); + background-color: var(--color-base-100, #fafafa); + border: var(--border, 1px) solid var(--color-base-300, oklch(92% 0.04 240)); + overflow: hidden; + + &#{is('dragging')} { + cursor: grabbing; + user-select: none; + touch-action: none; + } + + &#{is('no-drag')} { + #{e('handle')} { + cursor: default; + touch-action: auto; + } + } + + #{e('handle')} { + display: flex; + align-items: center; + justify-content: center; + min-height: 2rem; + cursor: grab; + touch-action: none; + flex-shrink: 0; + + &:active { + cursor: grabbing; + } + } + + #{e('handle-grip')} { + display: block; + width: 2rem; + height: 0.25rem; + border-radius: 999px; + opacity: 0.35; + } + + #{e('body')} { + box-sizing: border-box; + min-height: 0; + } +} diff --git a/packages/bolt-ui/theme-chalk/src/input.scss b/packages/bolt-ui/theme-chalk/src/input.scss new file mode 100644 index 0000000..7b0d232 --- /dev/null +++ b/packages/bolt-ui/theme-chalk/src/input.scss @@ -0,0 +1,94 @@ +@use 'core/_base' as *; +@use 'sass:selector'; + +@include setNamespace('input'); + +#{b()} { + position: relative; + display: inline-flex; + width: 100%; + box-sizing: border-box; + vertical-align: middle; + &#{m('mini')} #{e('wrapper')} { + height: 32px; + line-height: 32px; + font-size: 12px; + } + &#{m('small')} #{e('wrapper')} { + height: 36px; + line-height: 36px; + font-size: 14px; + } + &#{m('large')} #{e('wrapper')} { + height: 44px; + line-height: 44px; + font-size: 16px; + } + + #{e('wrapper')} { + width: 100%; + display: inline-flex; + font-size: 14px; + height: 40px; + line-height: 40px; + color: #606266; + transition: + border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1), + background-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1); + background-color: #e9e9e9; + border: 1px solid transparent; + border-radius: 4px; + padding: 0 15px; + box-sizing: border-box; + &:not(:disabled):has(#{e('inner')}:focus) { + border-color: #007bff; + box-shadow: 0 0 3px 2px rgba(0, 123, 255, 0.4); + background-color: #fff; + } + } + + #{e('inner')} { + -webkit-appearance: none; + background-image: none; + background: none; + border: none; + box-sizing: border-box; + line-height: inherit; + color: inherit; + font-size: inherit; + outline: none; + width: 100%; + } + + #{e('prefix')} { + margin-right: 8px; + font-size: 1.5em; + } + #{e('suffix')} { + margin-left: 8px; + font-size: 1.5em; + } + + &#{is('disabled')} { + #{e('wrapper')} { + // color: #ccc; + // background-color: #e9e9e9; + opacity: 0.6; + } + #{e('inner')} { + cursor: not-allowed; + } + } + &#{is('readonly')} { + #{e('inner')} { + cursor: default; + } + position: relative; + transition: width 0.2s ease-in-out; + &#{is('hover-show')}:hover #{e('wrapper')} { + position: absolute; + transform: translateY(-50%); + width: 300px; + } + } +} diff --git a/packages/bolt-ui/theme-chalk/src/select.scss b/packages/bolt-ui/theme-chalk/src/select.scss new file mode 100644 index 0000000..38274d0 --- /dev/null +++ b/packages/bolt-ui/theme-chalk/src/select.scss @@ -0,0 +1,144 @@ +@use 'core/_base' as *; +@use 'sass:selector'; + +@include setNamespace('select'); + +#{b()} { + position: relative; + display: inline-flex; + width: 100%; + box-sizing: border-box; + vertical-align: middle; + + &#{m('mini')} #{e('wrapper')} { + height: 32px; + line-height: 32px; + font-size: 12px; + } + &#{m('small')} #{e('wrapper')} { + height: 36px; + line-height: 36px; + font-size: 14px; + } + &#{m('large')} #{e('wrapper')} { + height: 44px; + line-height: 44px; + font-size: 16px; + } + + #{e('wrapper')} { + width: 100%; + display: inline-flex; + align-items: center; + justify-content: space-between; + font-size: 14px; + height: 40px; + line-height: 40px; + color: #606266; + transition: + border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1), + background-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1); + background-color: #e9e9e9; + border: 1px solid transparent; + border-radius: 4px; + padding: 0 15px; + box-sizing: border-box; + cursor: pointer; + + &:hover:not(:disabled) { + background-color: #fff; + } + + &:not(:disabled):has(#{e('inner')}:focus) { + border-color: #007bff; + box-shadow: 0 0 3px 2px rgba(0, 123, 255, 0.4); + background-color: #fff; + } + } + + #{e('inner')} { + flex: 1; + box-sizing: border-box; + line-height: inherit; + color: inherit; + font-size: inherit; + outline: none; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + #{e('suffix')} { + margin-left: 8px; + display: flex; + align-items: center; + color: #909399; + transition: transform 0.2s; + flex-shrink: 0; + } + + #{e('dropdown')} { + position: absolute; + top: calc(100% + 4px); + left: 0; + right: 0; + z-index: 99; + display: grid; + grid-template-rows: 1fr; + transition: grid-template-rows 0.1s ease-out; + overflow: hidden; + + &#{is('hidden')} { + grid-template-rows: 0fr; + } + + #{e('dropdown-wrapper')} { + min-height: 0; + #{e('dropdown-list')} { + box-sizing: border-box; + background-color: #fff; + border: 1px solid #dcdfe6; + border-radius: 4px; + box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); + max-height: 274px; + overflow-y: auto; + + #{e('dropdown-item')} { + padding: 10px 15px; + font-size: 14px; + line-height: 1.5; + display: flex; + align-items: center; + cursor: pointer; + color: #606266; + + &:hover { + background-color: #f5f7fa; + } + + &#{is('selected')} { + color: #007bff; + background-color: #ecf5ff; + } + } + + #{e('dropdown-line')} { + width: 100%; + height: 1px; + background-color: #dcdfe6; + transform: scaleY(0.5); + } + } + } + } + + &#{is('disabled')} { + #{e('wrapper')} { + opacity: 0.6; + cursor: not-allowed; + } + #{e('inner')} { + cursor: not-allowed; + } + } +} diff --git a/packages/bolt-ui/theme-chalk/src/theme/index.scss b/packages/bolt-ui/theme-chalk/src/theme/index.scss new file mode 100644 index 0000000..ca5dcce --- /dev/null +++ b/packages/bolt-ui/theme-chalk/src/theme/index.scss @@ -0,0 +1,38 @@ +:root { + --color-base-100: oklch(98% 0.02 240); + --color-base-200: oklch(95% 0.03 240); + --color-base-300: oklch(92% 0.04 240); + --color-base-content: oklch(20% 0.05 240); + --color-primary: oklch(55% 0.3 240); + --color-primary-content: oklch(98% 0.01 240); + --color-secondary: oklch(70% 0.25 200); + --color-secondary-content: oklch(98% 0.01 200); + --color-accent: oklch(65% 0.25 160); + --color-accent-content: oklch(98% 0.01 160); + --color-neutral: oklch(50% 0.05 240); + --color-neutral-content: oklch(98% 0.01 240); + --color-info: oklch(70% 0.2 220); + --color-info-content: oklch(98% 0.01 220); + --color-success: oklch(65% 0.25 140); + --color-success-content: oklch(98% 0.01 140); + --color-warning: oklch(80% 0.25 80); + --color-warning-content: oklch(20% 0.05 80); + --color-error: oklch(65% 0.3 30); + --color-error-content: oklch(98% 0.01 30); + + /* border radius */ + --radius-selector: 1rem; + --radius-field: 0.25rem; + --radius-box: 0.5rem; + + /* base sizes */ + --size-selector: 0.25rem; + --size-field: 0.25rem; + + /* border size */ + --border: 1px; + + /* effects */ + --depth: 1; + --noise: 0; +} diff --git a/packages/bolt-ui/tsconfig.json b/packages/bolt-ui/tsconfig.json new file mode 100644 index 0000000..4b43cf6 --- /dev/null +++ b/packages/bolt-ui/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "tsconfig/tsconfig.json", + "include": ["resolver.ts"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/bolt-ui/utils/hooks/use-namespace/index.ts b/packages/bolt-ui/utils/hooks/use-namespace/index.ts new file mode 100644 index 0000000..bf49490 --- /dev/null +++ b/packages/bolt-ui/utils/hooks/use-namespace/index.ts @@ -0,0 +1,127 @@ +import { computed, getCurrentInstance, inject, isRef, ref, unref } from 'vue' + +import type { InjectionKey, MaybeRef, Ref } from 'vue' + +export const defaultNamespace = 'bo' +const statePrefix = 'is-' + +const _bem = ( + namespace: string, + block: string, + blockSuffix: string, + element: string, + modifier: string +) => { + let cls = `${namespace}-${block}` + if (blockSuffix) { + cls += `-${blockSuffix}` + } + if (element) { + cls += `__${element}` + } + if (modifier) { + cls += `--${modifier}` + } + return cls +} + +export const namespaceContextKey: InjectionKey> = + Symbol('namespaceContextKey') + +/** + * 获取上下文的命名空间 + * @param namespaceOverrides 覆盖命令空间 + * @returns 覆盖后的命令空间 + */ +export const useGetDerivedNamespace = (namespaceOverrides?: Ref) => { + const derivedNamespace = + namespaceOverrides || + (getCurrentInstance() + ? inject(namespaceContextKey, ref(defaultNamespace)) + : ref(defaultNamespace)) + const namespace = computed(() => { + return unref(derivedNamespace) || defaultNamespace + }) + return namespace +} + +export const useNamespace = ( + block: MaybeRef, + namespaceOverrides?: Ref +) => { + const namespace = useGetDerivedNamespace(namespaceOverrides) + const getBlock = () => (isRef(block) ? block.value : block) + const b = (blockSuffix = '') => _bem(namespace.value, getBlock(), blockSuffix, '', '') // bo-button-test + const e = (element?: string) => + element ? _bem(namespace.value, getBlock(), '', element, '') : '' // bo-button__test + const m = (modifier?: string) => + modifier ? _bem(namespace.value, getBlock(), '', '', modifier) : '' // bo-button--test + const be = (blockSuffix?: string, element?: string) => + blockSuffix && element + ? _bem(namespace.value, getBlock(), blockSuffix, element, '') // bo-button-test__test + : '' + const em = (element?: string, modifier?: string) => + element && modifier + ? _bem(namespace.value, getBlock(), '', element, modifier) // bo-button__test--test + : '' + const bm = (blockSuffix?: string, modifier?: string) => + blockSuffix && modifier + ? _bem(namespace.value, getBlock(), blockSuffix, '', modifier) // bo-button-test--test + : '' + const bem = (blockSuffix?: string, element?: string, modifier?: string) => + blockSuffix && element && modifier + ? _bem(namespace.value, getBlock(), blockSuffix, element, modifier) // bo-button-test__test--test + : '' + const is: { + (name: string, state: boolean | undefined): string + (name: string): string + } = (name: string, ...args: [boolean | undefined] | []) => { + const state = args.length >= 1 ? args[0]! : true + return name && state ? `${statePrefix}${name}` : '' // is-test + } + + // for css var + // --bo-xxx: value; + const cssVar = (object: Record) => { + const styles: Record = {} + for (const key in object) { + if (object[key]) { + styles[`--${namespace.value}-${key}`] = object[key] // --bo-test: test + } + } + return styles + } + // with block + const cssVarBlock = (object: Record) => { + const styles: Record = {} + for (const key in object) { + if (object[key]) { + styles[`--${namespace.value}-${getBlock()}-${key}`] = object[key] // --bo-button-test: test + } + } + return styles + } + + const cssVarName = (name: string) => `--${namespace.value}-${name}` // --bo-test + const cssVarBlockName = (name: string) => `--${namespace.value}-${getBlock()}-${name}` // --bo-button-test + + return { + namespace, + // bo-button-test + b, + e, + m, + be, + em, + bm, + bem, + is, + // css + cssVar, + cssVarName, + cssVarBlock, + cssVarBlockName + } +} + +export type UseNamespaceReturn = ReturnType diff --git a/packages/bolt-ui/utils/vue/install.ts b/packages/bolt-ui/utils/vue/install.ts new file mode 100644 index 0000000..cfab85f --- /dev/null +++ b/packages/bolt-ui/utils/vue/install.ts @@ -0,0 +1,26 @@ +import type { App } from 'vue' +import type { SFCWithInstall } from './types' +import { NOOP } from './types' + +export const withNoopInstall = (component: T) => { + ;(component as SFCWithInstall).install = NOOP + + return component as SFCWithInstall +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const withInstall = >(main: T, extra?: E) => { + ;(main as SFCWithInstall).install = (app: App): void => { + for (const comp of [main, ...Object.values(extra ?? {})]) { + app.component(comp.name, comp) + } + } + + if (extra) { + for (const [key, comp] of Object.entries(extra)) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ;(main as any)[key] = comp + } + } + return main as SFCWithInstall & E +} diff --git a/packages/bolt-ui/utils/vue/types.ts b/packages/bolt-ui/utils/vue/types.ts new file mode 100644 index 0000000..93375fd --- /dev/null +++ b/packages/bolt-ui/utils/vue/types.ts @@ -0,0 +1,12 @@ +import type { AppContext, Plugin } from 'vue' +import type { Ref } from 'vue' + +export type SFCWithInstall = T & Plugin + +export type SFCInstallWithContext = SFCWithInstall & { + _context: AppContext | null +} + +export const NOOP = () => {} + +export type MaybeRef = T | Ref