1
1
mirror of https://github.com/neosubhamoy/neodlp.git synced 2026-02-04 14:12:22 +05:30

feat: added color scheme options and bumped up to shadcn 3.5

This commit is contained in:
2025-11-13 15:22:58 +05:30
Verified
parent defdfd6fd1
commit 6028037e74
69 changed files with 4250 additions and 3255 deletions

273
package-lock.json generated
View File

@@ -36,7 +36,7 @@
"@radix-ui/react-toggle": "^1.1.10", "@radix-ui/react-toggle": "^1.1.10",
"@radix-ui/react-toggle-group": "^1.1.11", "@radix-ui/react-toggle-group": "^1.1.11",
"@radix-ui/react-tooltip": "^1.2.8", "@radix-ui/react-tooltip": "^1.2.8",
"@tanstack/react-query": "^5.90.7", "@tanstack/react-query": "^5.90.8",
"@tanstack/react-query-devtools": "^5.90.2", "@tanstack/react-query-devtools": "^5.90.2",
"@tauri-apps/api": "^2.9.0", "@tauri-apps/api": "^2.9.0",
"@tauri-apps/plugin-clipboard-manager": "^2.3.2", "@tauri-apps/plugin-clipboard-manager": "^2.3.2",
@@ -55,7 +55,7 @@
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"embla-carousel-react": "^8.6.0", "embla-carousel-react": "^8.6.0",
"input-otp": "^1.4.2", "input-otp": "^1.4.2",
"lucide-react": "^0.552.0", "lucide-react": "^0.553.0",
"next-themes": "^0.4.6", "next-themes": "^0.4.6",
"react": "^19.2.0", "react": "^19.2.0",
"react-day-picker": "^9.11.1", "react-day-picker": "^9.11.1",
@@ -63,9 +63,9 @@
"react-hook-form": "^7.66.0", "react-hook-form": "^7.66.0",
"react-resizable-panels": "^3.0.6", "react-resizable-panels": "^3.0.6",
"react-router-dom": "^7.9.5", "react-router-dom": "^7.9.5",
"recharts": "^3.3.0", "recharts": "^3.4.1",
"sonner": "^2.0.7", "sonner": "^2.0.7",
"tailwind-merge": "^3.3.1", "tailwind-merge": "^3.4.0",
"ulid": "^3.0.1", "ulid": "^3.0.1",
"vaul": "^1.1.2", "vaul": "^1.1.2",
"zod": "^4.1.12", "zod": "^4.1.12",
@@ -74,16 +74,16 @@
"devDependencies": { "devDependencies": {
"@tailwindcss/postcss": "^4.1.17", "@tailwindcss/postcss": "^4.1.17",
"@tailwindcss/vite": "^4.1.17", "@tailwindcss/vite": "^4.1.17",
"@tauri-apps/cli": "^2.9.3", "@tauri-apps/cli": "^2.9.4",
"@types/node": "^24.10.0", "@types/node": "^24.10.1",
"@types/react": "^19.2.2", "@types/react": "^19.2.3",
"@types/react-dom": "^19.2.2", "@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.0", "@vitejs/plugin-react": "^5.1.1",
"postcss": "^8.5.6", "postcss": "^8.5.6",
"tailwindcss": "^4.1.17", "tailwindcss": "^4.1.17",
"tw-animate-css": "^1.4.0", "tw-animate-css": "^1.4.0",
"typescript": "~5.9.3", "typescript": "~5.9.3",
"vite": "^7.2.1" "vite": "^7.2.2"
} }
}, },
"node_modules/@alloc/quick-lru": { "node_modules/@alloc/quick-lru": {
@@ -125,22 +125,22 @@
} }
}, },
"node_modules/@babel/core": { "node_modules/@babel/core": {
"version": "7.28.4", "version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz",
"integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.27.1", "@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.3", "@babel/generator": "^7.28.5",
"@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-compilation-targets": "^7.27.2",
"@babel/helper-module-transforms": "^7.28.3", "@babel/helper-module-transforms": "^7.28.3",
"@babel/helpers": "^7.28.4", "@babel/helpers": "^7.28.4",
"@babel/parser": "^7.28.4", "@babel/parser": "^7.28.5",
"@babel/template": "^7.27.2", "@babel/template": "^7.27.2",
"@babel/traverse": "^7.28.4", "@babel/traverse": "^7.28.5",
"@babel/types": "^7.28.4", "@babel/types": "^7.28.5",
"@jridgewell/remapping": "^2.3.5", "@jridgewell/remapping": "^2.3.5",
"convert-source-map": "^2.0.0", "convert-source-map": "^2.0.0",
"debug": "^4.1.0", "debug": "^4.1.0",
@@ -157,14 +157,14 @@
} }
}, },
"node_modules/@babel/generator": { "node_modules/@babel/generator": {
"version": "7.28.3", "version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz",
"integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/parser": "^7.28.3", "@babel/parser": "^7.28.5",
"@babel/types": "^7.28.2", "@babel/types": "^7.28.5",
"@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/gen-mapping": "^0.3.12",
"@jridgewell/trace-mapping": "^0.3.28", "@jridgewell/trace-mapping": "^0.3.28",
"jsesc": "^3.0.2" "jsesc": "^3.0.2"
@@ -253,9 +253,9 @@
} }
}, },
"node_modules/@babel/helper-validator-identifier": { "node_modules/@babel/helper-validator-identifier": {
"version": "7.27.1", "version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
"integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -287,13 +287,13 @@
} }
}, },
"node_modules/@babel/parser": { "node_modules/@babel/parser": {
"version": "7.28.4", "version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
"integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/types": "^7.28.4" "@babel/types": "^7.28.5"
}, },
"bin": { "bin": {
"parser": "bin/babel-parser.js" "parser": "bin/babel-parser.js"
@@ -350,18 +350,18 @@
} }
}, },
"node_modules/@babel/traverse": { "node_modules/@babel/traverse": {
"version": "7.28.4", "version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz",
"integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.27.1", "@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.3", "@babel/generator": "^7.28.5",
"@babel/helper-globals": "^7.28.0", "@babel/helper-globals": "^7.28.0",
"@babel/parser": "^7.28.4", "@babel/parser": "^7.28.5",
"@babel/template": "^7.27.2", "@babel/template": "^7.27.2",
"@babel/types": "^7.28.4", "@babel/types": "^7.28.5",
"debug": "^4.3.1" "debug": "^4.3.1"
}, },
"engines": { "engines": {
@@ -369,14 +369,14 @@
} }
}, },
"node_modules/@babel/types": { "node_modules/@babel/types": {
"version": "7.28.4", "version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
"integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-string-parser": "^7.27.1", "@babel/helper-string-parser": "^7.27.1",
"@babel/helper-validator-identifier": "^7.27.1" "@babel/helper-validator-identifier": "^7.28.5"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@@ -2574,14 +2574,14 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@reduxjs/toolkit": { "node_modules/@reduxjs/toolkit": {
"version": "2.8.2", "version": "2.10.1",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.8.2.tgz", "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.10.1.tgz",
"integrity": "sha512-MYlOhQ0sLdw4ud48FoC5w0dH9VfWQjtCjreKwYTT3l+r427qYC5Y8PihNutepr8XrNaBUDQo9khWUwQxZaqt5A==", "integrity": "sha512-/U17EXQ9Do9Yx4DlNGU6eVNfZvFJfYpUtRRdLf19PbPjdWBxNlxGZXywQZ1p1Nz8nMkWplTI7iD/23m07nolDA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@standard-schema/spec": "^1.0.0", "@standard-schema/spec": "^1.0.0",
"@standard-schema/utils": "^0.3.0", "@standard-schema/utils": "^0.3.0",
"immer": "^10.0.3", "immer": "^10.2.0",
"redux": "^5.0.1", "redux": "^5.0.1",
"redux-thunk": "^3.1.0", "redux-thunk": "^3.1.0",
"reselect": "^5.1.0" "reselect": "^5.1.0"
@@ -2600,9 +2600,9 @@
} }
}, },
"node_modules/@rolldown/pluginutils": { "node_modules/@rolldown/pluginutils": {
"version": "1.0.0-beta.43", "version": "1.0.0-beta.47",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.43.tgz", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.47.tgz",
"integrity": "sha512-5Uxg7fQUCmfhax7FJke2+8B6cqgeUJUD9o2uXIKXhD+mG0mL6NObmVoi9wXEU1tY89mZKgAYA6fTbftx3q2ZPQ==", "integrity": "sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
@@ -3185,9 +3185,9 @@
} }
}, },
"node_modules/@tanstack/query-core": { "node_modules/@tanstack/query-core": {
"version": "5.90.7", "version": "5.90.8",
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.7.tgz", "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.8.tgz",
"integrity": "sha512-6PN65csiuTNfBMXqQUxQhCNdtm1rV+9kC9YwWAIKcaxAauq3Wu7p18j3gQY3YIBJU70jT/wzCCZ2uqto/vQgiQ==", "integrity": "sha512-4E0RP/0GJCxSNiRF2kAqE/LQkTJVlL/QNU7gIJSptaseV9HP6kOuA+N11y4bZKZxa3QopK3ZuewwutHx6DqDXQ==",
"license": "MIT", "license": "MIT",
"funding": { "funding": {
"type": "github", "type": "github",
@@ -3205,13 +3205,13 @@
} }
}, },
"node_modules/@tanstack/react-query": { "node_modules/@tanstack/react-query": {
"version": "5.90.7", "version": "5.90.8",
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.7.tgz", "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.8.tgz",
"integrity": "sha512-wAHc/cgKzW7LZNFloThyHnV/AX9gTg3w5yAv0gvQHPZoCnepwqCMtzbuPbb2UvfvO32XZ46e8bPOYbfZhzVnnQ==", "integrity": "sha512-/3b9QGzkf4rE5/miL6tyhldQRlLXzMHcySOm/2Tm2OLEFE9P1ImkH0+OviDBSvyAvtAOJocar5xhd7vxdLi3aQ==",
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
"@tanstack/query-core": "5.90.7" "@tanstack/query-core": "5.90.8"
}, },
"funding": { "funding": {
"type": "github", "type": "github",
@@ -3249,9 +3249,9 @@
} }
}, },
"node_modules/@tauri-apps/cli": { "node_modules/@tauri-apps/cli": {
"version": "2.9.3", "version": "2.9.4",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.9.3.tgz", "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.9.4.tgz",
"integrity": "sha512-BQ7iLUXTQcyG1PpzLWeVSmBCedYDpnA/6Cm/kRFGtqjTf/eVUlyYO5S2ee07tLum3nWwDBWTGFZeruO8yEukfA==", "integrity": "sha512-pvylWC9QckrOS9ATWXIXcgu7g2hKK5xTL5ZQyZU/U0n9l88SEFGcWgLQNa8WZmd+wWIOWhkxOFcOl3i6ubDNNw==",
"dev": true, "dev": true,
"license": "Apache-2.0 OR MIT", "license": "Apache-2.0 OR MIT",
"bin": { "bin": {
@@ -3265,23 +3265,23 @@
"url": "https://opencollective.com/tauri" "url": "https://opencollective.com/tauri"
}, },
"optionalDependencies": { "optionalDependencies": {
"@tauri-apps/cli-darwin-arm64": "2.9.3", "@tauri-apps/cli-darwin-arm64": "2.9.4",
"@tauri-apps/cli-darwin-x64": "2.9.3", "@tauri-apps/cli-darwin-x64": "2.9.4",
"@tauri-apps/cli-linux-arm-gnueabihf": "2.9.3", "@tauri-apps/cli-linux-arm-gnueabihf": "2.9.4",
"@tauri-apps/cli-linux-arm64-gnu": "2.9.3", "@tauri-apps/cli-linux-arm64-gnu": "2.9.4",
"@tauri-apps/cli-linux-arm64-musl": "2.9.3", "@tauri-apps/cli-linux-arm64-musl": "2.9.4",
"@tauri-apps/cli-linux-riscv64-gnu": "2.9.3", "@tauri-apps/cli-linux-riscv64-gnu": "2.9.4",
"@tauri-apps/cli-linux-x64-gnu": "2.9.3", "@tauri-apps/cli-linux-x64-gnu": "2.9.4",
"@tauri-apps/cli-linux-x64-musl": "2.9.3", "@tauri-apps/cli-linux-x64-musl": "2.9.4",
"@tauri-apps/cli-win32-arm64-msvc": "2.9.3", "@tauri-apps/cli-win32-arm64-msvc": "2.9.4",
"@tauri-apps/cli-win32-ia32-msvc": "2.9.3", "@tauri-apps/cli-win32-ia32-msvc": "2.9.4",
"@tauri-apps/cli-win32-x64-msvc": "2.9.3" "@tauri-apps/cli-win32-x64-msvc": "2.9.4"
} }
}, },
"node_modules/@tauri-apps/cli-darwin-arm64": { "node_modules/@tauri-apps/cli-darwin-arm64": {
"version": "2.9.3", "version": "2.9.4",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.9.3.tgz", "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.9.4.tgz",
"integrity": "sha512-W8FQXZXQmQ0Fmj9UJXNrm2mLdIaLLriKVY7o/FzmizyIKTPIvHjfZALTNybbpTQRbJvKoGHLrW1DNzAWVDWJYg==", "integrity": "sha512-9rHkMVtbMhe0AliVbrGpzMahOBg3rwV46JYRELxR9SN6iu1dvPOaMaiC4cP6M/aD1424ziXnnMdYU06RAH8oIw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -3296,9 +3296,9 @@
} }
}, },
"node_modules/@tauri-apps/cli-darwin-x64": { "node_modules/@tauri-apps/cli-darwin-x64": {
"version": "2.9.3", "version": "2.9.4",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.9.3.tgz", "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.9.4.tgz",
"integrity": "sha512-zDwu40rlshijt3TU6aRvzPUyVpapsx1sNfOlreDMTaMelQLHl6YoQzSRpLHYwrHrhimxyX2uDqnKIiuGel0Lhg==", "integrity": "sha512-VT9ymNuT06f5TLjCZW2hfSxbVtZDhORk7CDUDYiq5TiSYQdxkl8MVBy0CCFFcOk4QAkUmqmVUA9r3YZ/N/vPRQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -3313,9 +3313,9 @@
} }
}, },
"node_modules/@tauri-apps/cli-linux-arm-gnueabihf": { "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": {
"version": "2.9.3", "version": "2.9.4",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.9.3.tgz", "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.9.4.tgz",
"integrity": "sha512-+Oc2OfcTRwYtW93VJqd/HOk77buORwC9IToj/qsEvM7bTMq6Kda4alpZprzwrCHYANSw+zD8PgjJdljTpe4p+g==", "integrity": "sha512-tTWkEPig+2z3Rk0zqZYfjUYcgD+aSm72wdrIhdYobxbQZOBw0zfn50YtWv+av7bm0SHvv75f0l7JuwgZM1HFow==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -3330,9 +3330,9 @@
} }
}, },
"node_modules/@tauri-apps/cli-linux-arm64-gnu": { "node_modules/@tauri-apps/cli-linux-arm64-gnu": {
"version": "2.9.3", "version": "2.9.4",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.9.3.tgz", "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.9.4.tgz",
"integrity": "sha512-59GqU/J1n9wFyAtleoQOaU0oVIo+kwQynEw4meFDoKRXszKGor6lTsbsS3r0QKLSPbc0o/yYGJhqqCtkYjb/eg==", "integrity": "sha512-ql6vJ611qoqRYHxkKPnb2vHa27U+YRKRmIpLMMBeZnfFtZ938eao7402AQCH1mO2+/8ioUhbpy9R/ZcLTXVmkg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -3347,9 +3347,9 @@
} }
}, },
"node_modules/@tauri-apps/cli-linux-arm64-musl": { "node_modules/@tauri-apps/cli-linux-arm64-musl": {
"version": "2.9.3", "version": "2.9.4",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.9.3.tgz", "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.9.4.tgz",
"integrity": "sha512-fzvG+jEn5/iYGNH6Z2IRMheYFC4pJdXa19BR9fFm6Bdn2cuajRLDKdUcEME/DCtwqclphXtFZTrT4oezY5vI/A==", "integrity": "sha512-vg7yNn7ICTi6hRrcA/6ff2UpZQP7un3xe3SEld5QM0prgridbKAiXGaCKr3BnUBx/rGXegQlD/wiLcWdiiraSw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -3364,9 +3364,9 @@
} }
}, },
"node_modules/@tauri-apps/cli-linux-riscv64-gnu": { "node_modules/@tauri-apps/cli-linux-riscv64-gnu": {
"version": "2.9.3", "version": "2.9.4",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.9.3.tgz", "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.9.4.tgz",
"integrity": "sha512-qV8DZXI/fZwawk6T3Th1g6smiNC2KeQTk7XFgKvqZ6btC01z3UTsQmNGvI602zwm3Ld1TBZb4+rEWu2QmQimmw==", "integrity": "sha512-l8L+3VxNk6yv5T/Z/gv5ysngmIpsai40B9p6NQQyqYqxImqYX37pqREoEBl1YwG7szGnDibpWhidPrWKR59OJA==",
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
@@ -3381,9 +3381,9 @@
} }
}, },
"node_modules/@tauri-apps/cli-linux-x64-gnu": { "node_modules/@tauri-apps/cli-linux-x64-gnu": {
"version": "2.9.3", "version": "2.9.4",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.9.3.tgz", "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.9.4.tgz",
"integrity": "sha512-tquyEONCNRfqEBWEe4eAHnxFN5yY5lFkCuD4w79XLIovUxVftQ684+xLp7zkhntkt4y20SMj2AgJa/+MOlx4Kg==", "integrity": "sha512-PepPhCXc/xVvE3foykNho46OmCyx47E/aG676vKTVp+mqin5d+IBqDL6wDKiGNT5OTTxKEyNlCQ81Xs2BQhhqA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -3398,9 +3398,9 @@
} }
}, },
"node_modules/@tauri-apps/cli-linux-x64-musl": { "node_modules/@tauri-apps/cli-linux-x64-musl": {
"version": "2.9.3", "version": "2.9.4",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.9.3.tgz", "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.9.4.tgz",
"integrity": "sha512-v2cBIB/6ji8DL+aiL5QUykU3ZO8OoJGyx50/qv2HQVzkf85KdaYSis3D/oVRemN/pcDz+vyCnnL3XnzFnDl4JQ==", "integrity": "sha512-zcd1QVffh5tZs1u1SCKUV/V7RRynebgYUNWHuV0FsIF1MjnULUChEXhAhug7usCDq4GZReMJOoXa6rukEozWIw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -3415,9 +3415,9 @@
} }
}, },
"node_modules/@tauri-apps/cli-win32-arm64-msvc": { "node_modules/@tauri-apps/cli-win32-arm64-msvc": {
"version": "2.9.3", "version": "2.9.4",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.9.3.tgz", "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.9.4.tgz",
"integrity": "sha512-ZGvBy7nvrHPbE0HeKp/ioaiw8bNgAHxWnb7JRZ4/G0A+oFj0SeSFxl9k5uU6FKnM7bHM23Gd1oeaDex9g5Fceg==", "integrity": "sha512-/7ZhnP6PY04bEob23q8MH/EoDISdmR1wuNm0k9d5HV7TDMd2GGCDa8dPXA4vJuglJKXIfXqxFmZ4L+J+MO42+w==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -3432,9 +3432,9 @@
} }
}, },
"node_modules/@tauri-apps/cli-win32-ia32-msvc": { "node_modules/@tauri-apps/cli-win32-ia32-msvc": {
"version": "2.9.3", "version": "2.9.4",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.9.3.tgz", "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.9.4.tgz",
"integrity": "sha512-UsgIwOnpCoY9NK9/65QiwgmWVIE80LE7SwRYVblGtmlY9RYfsYvpbItwsovA/AcHMTiO+OCvS/q9yLeqS3m6Sg==", "integrity": "sha512-1LmAfaC4Cq+3O1Ir1ksdhczhdtFSTIV51tbAGtbV/mr348O+M52A/xwCCXQank0OcdBxy5BctqkMtuZnQvA8uQ==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@@ -3449,9 +3449,9 @@
} }
}, },
"node_modules/@tauri-apps/cli-win32-x64-msvc": { "node_modules/@tauri-apps/cli-win32-x64-msvc": {
"version": "2.9.3", "version": "2.9.4",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.9.3.tgz", "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.9.4.tgz",
"integrity": "sha512-fmw7NrrHE5m49idCvJAx9T9bsupjdJ0a3p3DPCNCZRGANU6R1tA1L+KTlVuUtdAldX2NqU/9UPo2SCslYKgJHQ==", "integrity": "sha512-EdYd4c9wGvtPB95kqtEyY+bUR+k4kRw3IA30mAQ1jPH6z57AftT8q84qwv0RDp6kkEqOBKxeInKfqi4BESYuqg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -3601,9 +3601,9 @@
} }
}, },
"node_modules/@types/d3-array": { "node_modules/@types/d3-array": {
"version": "3.2.1", "version": "3.2.2",
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz",
"integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==", "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/d3-color": { "node_modules/@types/d3-color": {
@@ -3671,9 +3671,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "24.10.0", "version": "24.10.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.0.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz",
"integrity": "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==", "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
@@ -3682,9 +3682,9 @@
} }
}, },
"node_modules/@types/react": { "node_modules/@types/react": {
"version": "19.2.2", "version": "19.2.3",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.3.tgz",
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", "integrity": "sha512-k5dJVszUiNr1DSe8Cs+knKR6IrqhqdhpUwzqhkS8ecQTSf3THNtbfIp/umqHMpX2bv+9dkx3fwDv/86LcSfvSg==",
"devOptional": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
@@ -3693,9 +3693,9 @@
} }
}, },
"node_modules/@types/react-dom": { "node_modules/@types/react-dom": {
"version": "19.2.2", "version": "19.2.3",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.2.tgz", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
"integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==", "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
"devOptional": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
@@ -3710,16 +3710,16 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@vitejs/plugin-react": { "node_modules/@vitejs/plugin-react": {
"version": "5.1.0", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.0.tgz", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.1.tgz",
"integrity": "sha512-4LuWrg7EKWgQaMJfnN+wcmbAW+VSsCmqGohftWjuct47bv8uE4n/nPpq4XjJPsxgq00GGG5J8dvBczp8uxScew==", "integrity": "sha512-WQfkSw0QbQ5aJ2CHYw23ZGkqnRwqKHD/KYsMeTkZzPT4Jcf0DcBxBtwMJxnu6E7oxw5+JC6ZAiePgh28uJ1HBA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/core": "^7.28.4", "@babel/core": "^7.28.5",
"@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-self": "^7.27.1",
"@babel/plugin-transform-react-jsx-source": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1",
"@rolldown/pluginutils": "1.0.0-beta.43", "@rolldown/pluginutils": "1.0.0-beta.47",
"@types/babel__core": "^7.20.5", "@types/babel__core": "^7.20.5",
"react-refresh": "^0.18.0" "react-refresh": "^0.18.0"
}, },
@@ -4085,9 +4085,9 @@
} }
}, },
"node_modules/es-toolkit": { "node_modules/es-toolkit": {
"version": "1.39.9", "version": "1.41.0",
"resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.39.9.tgz", "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.41.0.tgz",
"integrity": "sha512-9OtbkZmTA2Qc9groyA1PUNeb6knVTkvB2RSdr/LcJXDL8IdEakaxwXLHXa7VX/Wj0GmdMJPR3WhnPGhiP3E+qg==", "integrity": "sha512-bDd3oRmbVgqZCJS6WmeQieOrzpl3URcWBUVDXxOELlUW2FuW+0glPOz1n0KnRie+PdyvUZcXz2sOn00c6pPRIA==",
"license": "MIT", "license": "MIT",
"workspaces": [ "workspaces": [
"docs", "docs",
@@ -4212,9 +4212,9 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/immer": { "node_modules/immer": {
"version": "10.1.1", "version": "10.2.0",
"resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz",
"integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", "integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==",
"license": "MIT", "license": "MIT",
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
@@ -4555,9 +4555,9 @@
} }
}, },
"node_modules/lucide-react": { "node_modules/lucide-react": {
"version": "0.552.0", "version": "0.553.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.552.0.tgz", "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.553.0.tgz",
"integrity": "sha512-g9WCjmfwqbexSnZE+2cl21PCfXOcqnGeWeMTNAOGEfpPbm/ZF4YIq77Z8qWrxbu660EKuLB4nSLggoKnCb+isw==", "integrity": "sha512-BRgX5zrWmNy/lkVAe0dXBgd7XQdZ3HTf+Hwe3c9WK6dqgnj9h+hxV+MDncM88xDWlCq27+TKvHGE70ViODNILw==",
"license": "ISC", "license": "ISC",
"peerDependencies": { "peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
@@ -4886,10 +4886,13 @@
} }
}, },
"node_modules/recharts": { "node_modules/recharts": {
"version": "3.3.0", "version": "3.4.1",
"resolved": "https://registry.npmjs.org/recharts/-/recharts-3.3.0.tgz", "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.4.1.tgz",
"integrity": "sha512-Vi0qmTB0iz1+/Cz9o5B7irVyUjX2ynvEgImbgMt/3sKRREcUM07QiYjS1QpAVrkmVlXqy5gykq4nGWMz9AS4Rg==", "integrity": "sha512-35kYg6JoOgwq8sE4rhYkVWwa6aAIgOtT+Ob0gitnShjwUwZmhrmy7Jco/5kJNF4PnLXgt9Hwq+geEMS+WrjU1g==",
"license": "MIT", "license": "MIT",
"workspaces": [
"www"
],
"dependencies": { "dependencies": {
"@reduxjs/toolkit": "1.x.x || 2.x.x", "@reduxjs/toolkit": "1.x.x || 2.x.x",
"clsx": "^2.1.1", "clsx": "^2.1.1",
@@ -5017,9 +5020,9 @@
} }
}, },
"node_modules/tailwind-merge": { "node_modules/tailwind-merge": {
"version": "3.3.1", "version": "3.4.0",
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.1.tgz", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz",
"integrity": "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==", "integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==",
"license": "MIT", "license": "MIT",
"funding": { "funding": {
"type": "github", "type": "github",
@@ -5235,9 +5238,9 @@
} }
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "7.2.1", "version": "7.2.2",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.2.1.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.2.tgz",
"integrity": "sha512-qTl3VF7BvOupTR85Zc561sPEgxyUSNSvTQ9fit7DEMP7yPgvvIGm5Zfa1dOM+kOwWGNviK9uFM9ra77+OjK7lQ==", "integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,

View File

@@ -39,7 +39,7 @@
"@radix-ui/react-toggle": "^1.1.10", "@radix-ui/react-toggle": "^1.1.10",
"@radix-ui/react-toggle-group": "^1.1.11", "@radix-ui/react-toggle-group": "^1.1.11",
"@radix-ui/react-tooltip": "^1.2.8", "@radix-ui/react-tooltip": "^1.2.8",
"@tanstack/react-query": "^5.90.7", "@tanstack/react-query": "^5.90.8",
"@tanstack/react-query-devtools": "^5.90.2", "@tanstack/react-query-devtools": "^5.90.2",
"@tauri-apps/api": "^2.9.0", "@tauri-apps/api": "^2.9.0",
"@tauri-apps/plugin-clipboard-manager": "^2.3.2", "@tauri-apps/plugin-clipboard-manager": "^2.3.2",
@@ -58,7 +58,7 @@
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"embla-carousel-react": "^8.6.0", "embla-carousel-react": "^8.6.0",
"input-otp": "^1.4.2", "input-otp": "^1.4.2",
"lucide-react": "^0.552.0", "lucide-react": "^0.553.0",
"next-themes": "^0.4.6", "next-themes": "^0.4.6",
"react": "^19.2.0", "react": "^19.2.0",
"react-day-picker": "^9.11.1", "react-day-picker": "^9.11.1",
@@ -66,9 +66,9 @@
"react-hook-form": "^7.66.0", "react-hook-form": "^7.66.0",
"react-resizable-panels": "^3.0.6", "react-resizable-panels": "^3.0.6",
"react-router-dom": "^7.9.5", "react-router-dom": "^7.9.5",
"recharts": "^3.3.0", "recharts": "^3.4.1",
"sonner": "^2.0.7", "sonner": "^2.0.7",
"tailwind-merge": "^3.3.1", "tailwind-merge": "^3.4.0",
"ulid": "^3.0.1", "ulid": "^3.0.1",
"vaul": "^1.1.2", "vaul": "^1.1.2",
"zod": "^4.1.12", "zod": "^4.1.12",
@@ -77,15 +77,15 @@
"devDependencies": { "devDependencies": {
"@tailwindcss/postcss": "^4.1.17", "@tailwindcss/postcss": "^4.1.17",
"@tailwindcss/vite": "^4.1.17", "@tailwindcss/vite": "^4.1.17",
"@tauri-apps/cli": "^2.9.3", "@tauri-apps/cli": "^2.9.4",
"@types/node": "^24.10.0", "@types/node": "^24.10.1",
"@types/react": "^19.2.2", "@types/react": "^19.2.3",
"@types/react-dom": "^19.2.2", "@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.0", "@vitejs/plugin-react": "^5.1.1",
"postcss": "^8.5.6", "postcss": "^8.5.6",
"tailwindcss": "^4.1.17", "tailwindcss": "^4.1.17",
"tw-animate-css": "^1.4.0", "tw-animate-css": "^1.4.0",
"typescript": "~5.9.3", "typescript": "~5.9.3",
"vite": "^7.2.1" "vite": "^7.2.2"
} }
} }

157
src-tauri/Cargo.lock generated
View File

@@ -197,7 +197,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn 2.0.110",
] ]
[[package]] [[package]]
@@ -232,7 +232,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn 2.0.110",
] ]
[[package]] [[package]]
@@ -475,9 +475,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.44" version = "1.2.45"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3" checksum = "35900b6c8d709fb1d854671ae27aeaa9eec2f8b01b364e1619a40da3e6fe2afe"
dependencies = [ dependencies = [
"find-msvc-tools", "find-msvc-tools",
"shlex", "shlex",
@@ -699,9 +699,9 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
[[package]] [[package]]
name = "crypto-common" name = "crypto-common"
version = "0.1.6" version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
dependencies = [ dependencies = [
"generic-array", "generic-array",
"typenum", "typenum",
@@ -731,7 +731,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
dependencies = [ dependencies = [
"quote", "quote",
"syn 2.0.108", "syn 2.0.110",
] ]
[[package]] [[package]]
@@ -741,7 +741,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501"
dependencies = [ dependencies = [
"quote", "quote",
"syn 2.0.108", "syn 2.0.110",
] ]
[[package]] [[package]]
@@ -765,7 +765,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"strsim", "strsim",
"syn 2.0.108", "syn 2.0.110",
] ]
[[package]] [[package]]
@@ -776,7 +776,7 @@ checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81"
dependencies = [ dependencies = [
"darling_core", "darling_core",
"quote", "quote",
"syn 2.0.108", "syn 2.0.110",
] ]
[[package]] [[package]]
@@ -814,7 +814,7 @@ checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn 2.0.110",
] ]
[[package]] [[package]]
@@ -827,7 +827,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"rustc_version", "rustc_version",
"syn 2.0.108", "syn 2.0.110",
] ]
[[package]] [[package]]
@@ -898,7 +898,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn 2.0.110",
] ]
[[package]] [[package]]
@@ -930,7 +930,7 @@ checksum = "788160fb30de9cdd857af31c6a2675904b16ece8fc2737b2c7127ba368c9d0f4"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn 2.0.110",
] ]
[[package]] [[package]]
@@ -1043,7 +1043,7 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn 2.0.110",
] ]
[[package]] [[package]]
@@ -1054,9 +1054,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]] [[package]]
name = "erased-serde" name = "erased-serde"
version = "0.4.8" version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "259d404d09818dec19332e31d94558aeb442fea04c817006456c24b5460bbd4b" checksum = "89e8918065695684b2b0702da20382d5ae6065cf3327bc2d6436bd49a71ce9f3"
dependencies = [ dependencies = [
"serde", "serde",
"serde_core", "serde_core",
@@ -1134,7 +1134,7 @@ checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn 2.0.110",
] ]
[[package]] [[package]]
@@ -1250,7 +1250,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn 2.0.110",
] ]
[[package]] [[package]]
@@ -1349,7 +1349,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn 2.0.110",
] ]
[[package]] [[package]]
@@ -1491,9 +1491,9 @@ dependencies = [
[[package]] [[package]]
name = "generic-array" name = "generic-array"
version = "0.14.9" version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [ dependencies = [
"typenum", "typenum",
"version_check", "version_check",
@@ -1613,7 +1613,7 @@ dependencies = [
"proc-macro-error", "proc-macro-error",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn 2.0.110",
] ]
[[package]] [[package]]
@@ -1692,7 +1692,7 @@ dependencies = [
"proc-macro-error", "proc-macro-error",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn 2.0.110",
] ]
[[package]] [[package]]
@@ -1862,9 +1862,9 @@ checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "1.7.0" version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" checksum = "1744436df46f0bde35af3eda22aeaba453aada65d8f1c171cd8a5f59030bd69f"
dependencies = [ dependencies = [
"atomic-waker", "atomic-waker",
"bytes", "bytes",
@@ -2436,7 +2436,7 @@ checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn 2.0.110",
] ]
[[package]] [[package]]
@@ -2679,9 +2679,9 @@ dependencies = [
[[package]] [[package]]
name = "num-bigint-dig" name = "num-bigint-dig"
version = "0.8.5" version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82c79c15c05d4bf82b6f5ef163104cc81a760d8e874d38ac50ab67c8877b647b" checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7"
dependencies = [ dependencies = [
"lazy_static", "lazy_static",
"libm", "libm",
@@ -2748,7 +2748,7 @@ dependencies = [
"proc-macro-crate 3.4.0", "proc-macro-crate 3.4.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn 2.0.110",
] ]
[[package]] [[package]]
@@ -3047,9 +3047,9 @@ dependencies = [
[[package]] [[package]]
name = "openssl" name = "openssl"
version = "0.10.74" version = "0.10.75"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24ad14dd45412269e1a30f52ad8f0664f0f4f4a89ee8fe28c3b3527021ebb654" checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328"
dependencies = [ dependencies = [
"bitflags 2.10.0", "bitflags 2.10.0",
"cfg-if", "cfg-if",
@@ -3068,7 +3068,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn 2.0.110",
] ]
[[package]] [[package]]
@@ -3079,9 +3079,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
[[package]] [[package]]
name = "openssl-sys" name = "openssl-sys"
version = "0.9.110" version = "0.9.111"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a9f0075ba3c21b09f8e8b2026584b1d18d49388648f2fbbf3c97ea8deced8e2" checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321"
dependencies = [ dependencies = [
"cc", "cc",
"libc", "libc",
@@ -3330,7 +3330,7 @@ dependencies = [
"phf_shared 0.11.3", "phf_shared 0.11.3",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn 2.0.110",
] ]
[[package]] [[package]]
@@ -3418,7 +3418,7 @@ checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"indexmap 2.12.0", "indexmap 2.12.0",
"quick-xml 0.38.3", "quick-xml 0.38.4",
"serde", "serde",
"time", "time",
] ]
@@ -3587,9 +3587,9 @@ dependencies = [
[[package]] [[package]]
name = "quick-xml" name = "quick-xml"
version = "0.38.3" version = "0.38.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42a232e7487fc2ef313d96dde7948e7a3c05101870d8985e4fd8d26aedd27b89" checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@@ -3651,9 +3651,9 @@ dependencies = [
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.41" version = "1.0.42"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@@ -3817,7 +3817,7 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn 2.0.110",
] ]
[[package]] [[package]]
@@ -4090,9 +4090,9 @@ dependencies = [
[[package]] [[package]]
name = "schemars" name = "schemars"
version = "1.0.5" version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1317c3bf3e7df961da95b0a56a172a02abead31276215a0497241a7624b487ce" checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289"
dependencies = [ dependencies = [
"dyn-clone", "dyn-clone",
"ref-cast", "ref-cast",
@@ -4109,7 +4109,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"serde_derive_internals", "serde_derive_internals",
"syn 2.0.108", "syn 2.0.110",
] ]
[[package]] [[package]]
@@ -4214,7 +4214,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn 2.0.110",
] ]
[[package]] [[package]]
@@ -4225,7 +4225,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn 2.0.110",
] ]
[[package]] [[package]]
@@ -4249,7 +4249,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn 2.0.110",
] ]
[[package]] [[package]]
@@ -4294,7 +4294,7 @@ dependencies = [
"indexmap 1.9.3", "indexmap 1.9.3",
"indexmap 2.12.0", "indexmap 2.12.0",
"schemars 0.9.0", "schemars 0.9.0",
"schemars 1.0.5", "schemars 1.1.0",
"serde_core", "serde_core",
"serde_json", "serde_json",
"serde_with_macros", "serde_with_macros",
@@ -4310,7 +4310,7 @@ dependencies = [
"darling", "darling",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn 2.0.110",
] ]
[[package]] [[package]]
@@ -4332,7 +4332,7 @@ checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn 2.0.110",
] ]
[[package]] [[package]]
@@ -4593,7 +4593,7 @@ dependencies = [
"quote", "quote",
"sqlx-core", "sqlx-core",
"sqlx-macros-core", "sqlx-macros-core",
"syn 2.0.108", "syn 2.0.110",
] ]
[[package]] [[package]]
@@ -4616,7 +4616,7 @@ dependencies = [
"sqlx-mysql", "sqlx-mysql",
"sqlx-postgres", "sqlx-postgres",
"sqlx-sqlite", "sqlx-sqlite",
"syn 2.0.108", "syn 2.0.110",
"tokio", "tokio",
"url", "url",
] ]
@@ -4820,9 +4820,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.108" version = "2.0.110"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -4846,7 +4846,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn 2.0.110",
] ]
[[package]] [[package]]
@@ -4940,7 +4940,7 @@ checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn 2.0.110",
] ]
[[package]] [[package]]
@@ -5051,7 +5051,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"sha2", "sha2",
"syn 2.0.108", "syn 2.0.110",
"tauri-utils", "tauri-utils",
"thiserror 2.0.17", "thiserror 2.0.17",
"time", "time",
@@ -5069,7 +5069,7 @@ dependencies = [
"heck 0.5.0", "heck 0.5.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn 2.0.110",
"tauri-codegen", "tauri-codegen",
"tauri-utils", "tauri-utils",
] ]
@@ -5394,10 +5394,11 @@ dependencies = [
[[package]] [[package]]
name = "tauri-winres" name = "tauri-winres"
version = "0.3.3" version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd21509dd1fa9bd355dc29894a6ff10635880732396aa38c0066c1e6c1ab8074" checksum = "1087b111fe2b005e42dbdc1990fc18593234238d47453b0c99b7de1c9ab2c1e0"
dependencies = [ dependencies = [
"dunce",
"embed-resource", "embed-resource",
"toml 0.9.8", "toml 0.9.8",
] ]
@@ -5464,7 +5465,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn 2.0.110",
] ]
[[package]] [[package]]
@@ -5475,7 +5476,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn 2.0.110",
] ]
[[package]] [[package]]
@@ -5574,7 +5575,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn 2.0.110",
] ]
[[package]] [[package]]
@@ -5794,7 +5795,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn 2.0.110",
] ]
[[package]] [[package]]
@@ -6152,7 +6153,7 @@ dependencies = [
"bumpalo", "bumpalo",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn 2.0.110",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@@ -6346,7 +6347,7 @@ checksum = "1d228f15bba3b9d56dde8bddbee66fa24545bd17b48d5128ccf4a8742b18e431"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn 2.0.110",
] ]
[[package]] [[package]]
@@ -6362,9 +6363,9 @@ dependencies = [
[[package]] [[package]]
name = "weezl" name = "weezl"
version = "0.1.10" version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
[[package]] [[package]]
name = "whoami" name = "whoami"
@@ -6489,7 +6490,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn 2.0.110",
] ]
[[package]] [[package]]
@@ -6500,7 +6501,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn 2.0.110",
] ]
[[package]] [[package]]
@@ -7058,7 +7059,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn 2.0.110",
"synstructure", "synstructure",
] ]
@@ -7106,7 +7107,7 @@ dependencies = [
"proc-macro-crate 3.4.0", "proc-macro-crate 3.4.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn 2.0.110",
"zbus_names", "zbus_names",
"zvariant", "zvariant",
"zvariant_utils", "zvariant_utils",
@@ -7141,7 +7142,7 @@ checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn 2.0.110",
] ]
[[package]] [[package]]
@@ -7161,7 +7162,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn 2.0.110",
"synstructure", "synstructure",
] ]
@@ -7201,7 +7202,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn 2.0.110",
] ]
[[package]] [[package]]
@@ -7255,7 +7256,7 @@ dependencies = [
"proc-macro-crate 3.4.0", "proc-macro-crate 3.4.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.108", "syn 2.0.110",
"zvariant_utils", "zvariant_utils",
] ]
@@ -7268,6 +7269,6 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"serde", "serde",
"syn 2.0.108", "syn 2.0.110",
"winnow 0.7.13", "winnow 0.7.13",
] ]

View File

@@ -58,6 +58,7 @@ export default function App({ children }: { children: React.ReactNode }) {
const YTDLP_AUTO_UPDATE = useSettingsPageStatesStore(state => state.settings.ytdlp_auto_update); const YTDLP_AUTO_UPDATE = useSettingsPageStatesStore(state => state.settings.ytdlp_auto_update);
const YTDLP_UPDATE_CHANNEL = useSettingsPageStatesStore(state => state.settings.ytdlp_update_channel); const YTDLP_UPDATE_CHANNEL = useSettingsPageStatesStore(state => state.settings.ytdlp_update_channel);
const APP_THEME = useSettingsPageStatesStore(state => state.settings.theme); const APP_THEME = useSettingsPageStatesStore(state => state.settings.theme);
const APP_COLOR_SCHEME = useSettingsPageStatesStore(state => state.settings.color_scheme);
const MAX_PARALLEL_DOWNLOADS = useSettingsPageStatesStore(state => state.settings.max_parallel_downloads); const MAX_PARALLEL_DOWNLOADS = useSettingsPageStatesStore(state => state.settings.max_parallel_downloads);
const MAX_RETRIES = useSettingsPageStatesStore(state => state.settings.max_retries); const MAX_RETRIES = useSettingsPageStatesStore(state => state.settings.max_retries);
const DOWNLOAD_DIR = useSettingsPageStatesStore(state => state.settings.download_dir); const DOWNLOAD_DIR = useSettingsPageStatesStore(state => state.settings.download_dir);
@@ -1178,7 +1179,7 @@ export default function App({ children }: { children: React.ReactNode }) {
return ( return (
<AppContext.Provider value={{ fetchVideoMetadata, startDownload, pauseDownload, resumeDownload, cancelDownload }}> <AppContext.Provider value={{ fetchVideoMetadata, startDownload, pauseDownload, resumeDownload, cancelDownload }}>
<ThemeProvider defaultTheme={APP_THEME || "system"} storageKey="vite-ui-theme"> <ThemeProvider defaultTheme={APP_THEME || "system"} defaultColorScheme={APP_COLOR_SCHEME || "default"}>
<TooltipProvider delayDuration={1000}> <TooltipProvider delayDuration={1000}>
{children} {children}
<Sonner closeButton /> <Sonner closeButton />

View File

@@ -57,8 +57,8 @@ const FormatSelectionGroupItem = React.forwardRef<
<RadioGroupPrimitive.Item <RadioGroupPrimitive.Item
ref={ref} ref={ref}
className={cn( className={cn(
"relative w-full rounded-lg border-2 border-border bg-card px-3 py-2 shadow-sm transition-all", "relative w-full rounded-lg border-2 border-border bg-background px-3 py-2 shadow-sm transition-all",
"data-[state=checked]:border-primary data-[state=checked]:border-2 data-[state=checked]:bg-muted/70", "data-[state=checked]:border-primary data-[state=checked]:border-2 data-[state=checked]:bg-primary/10",
"hover:bg-muted/70", "hover:bg-muted/70",
"disabled:cursor-not-allowed disabled:opacity-50", "disabled:cursor-not-allowed disabled:opacity-50",
className className

View File

@@ -23,7 +23,7 @@ export const SlidingButton = ({
return ( return (
<Tag <Tag
className={cn( className={cn(
"px-4 py-2 rounded-md bg-black dark:bg-white dark:text-black text-white text-center relative overflow-hidden cursor-pointer flex justify-center", "px-4 py-2 rounded-md bg-primary text-primary-foreground text-center relative overflow-hidden cursor-pointer flex justify-center",
`group/sliding-button`, `group/sliding-button`,
className className
)} )}
@@ -41,7 +41,7 @@ export const SlidingButton = ({
</span> </span>
<div <div
className={cn( className={cn(
'flex items-center justify-center absolute inset-0 transition duration-500 text-white z-20', 'flex items-center justify-center absolute inset-0 transition duration-500 text-primary-foreground z-20',
`-translate-x-60 group-hover/sliding-button:translate-x-0` `-translate-x-60 group-hover/sliding-button:translate-x-0`
)} )}
> >

View File

@@ -2,7 +2,7 @@ import { useLocation } from "react-router-dom";
import { isActive } from "@/utils"; import { isActive } from "@/utils";
import { config } from "@/config"; import { config } from "@/config";
import { useSettingsPageStatesStore } from "@/services/store"; import { useSettingsPageStatesStore } from "@/services/store";
import { Github, Globe } from "lucide-react"; import { Github, Globe, Heart } from "lucide-react";
export default function Footer() { export default function Footer() {
const location = useLocation(); const location = useLocation();
@@ -15,7 +15,7 @@ export default function Footer() {
<div className="flex items-center justify-between p-4 border-t border-border"> <div className="flex items-center justify-between p-4 border-t border-border">
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<span className="text-sm">{config.appName} v{appVersion} - &copy; {new Date().getFullYear()} &nbsp;|&nbsp; <a href={'https://github.com/' + config.appRepo + '/blob/main/LICENSE'} target="_blank">MIT License</a></span> <span className="text-sm">{config.appName} v{appVersion} - &copy; {new Date().getFullYear()} &nbsp;|&nbsp; <a href={'https://github.com/' + config.appRepo + '/blob/main/LICENSE'} target="_blank">MIT License</a></span>
<span className="text-xs text-muted-foreground">Made with by <a href={config.appAuthorUrl} target="_blank">{config.appAuthor}</a></span> <span className="text-xs text-muted-foreground">Made with <Heart className="inline size-3 mb-0.5 fill-primary stroke-primary"/> by <a href={config.appAuthorUrl} target="_blank">{config.appAuthor}</a></span>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<a href={config.appHomepage} target="_blank" className="text-sm text-muted-foreground hover:text-foreground" title="Homepage"> <a href={config.appHomepage} target="_blank" className="text-sm text-muted-foreground hover:text-foreground" title="Homepage">

View File

@@ -6,8 +6,8 @@ export function NeoDlpLogo({ className }: { className?: string }) {
<rect x="355" y="222" width="313" height="346" rx="25" fill="#FAFAFA"/> <rect x="355" y="222" width="313" height="346" rx="25" fill="#FAFAFA"/>
<defs> <defs>
<linearGradient id="paint0_linear_10_2" x1="129.5" y1="148.5" x2="921" y2="863" gradientUnits="userSpaceOnUse"> <linearGradient id="paint0_linear_10_2" x1="129.5" y1="148.5" x2="921" y2="863" gradientUnits="userSpaceOnUse">
<stop stopColor="#4444FF"/> <stop stopColor="var(--logo-stop-color-1)"/>
<stop offset="1" stopColor="#FF43D0"/> <stop offset="1" stopColor="var(--logo-stop-color-2)"/>
</linearGradient> </linearGradient>
</defs> </defs>
</svg> </svg>

View File

@@ -15,7 +15,7 @@ export default function Navbar() {
<nav className="flex justify-between items-center py-3 px-4 sticky top-0 backdrop-blur supports-backdrop-filter:bg-background/60 border-b z-50"> <nav className="flex justify-between items-center py-3 px-4 sticky top-0 backdrop-blur supports-backdrop-filter:bg-background/60 border-b z-50">
<div className="flex justify-center"> <div className="flex justify-center">
<SidebarTrigger /> <SidebarTrigger />
<h1 className="text-lg text-primary font-semibold ml-4">{getRouteName(location.pathname)}</h1> <h1 className="text-lg font-semibold ml-4">{getRouteName(location.pathname)}</h1>
</div> </div>
<div className="flex justify-center items-center"> <div className="flex justify-center items-center">
<Dialog> <Dialog>

View File

@@ -77,7 +77,7 @@ export function AppSidebar() {
<SidebarMenuButton size="lg" asChild> <SidebarMenuButton size="lg" asChild>
<a href="#"> <a href="#">
<div className="flex aspect-square size-8 items-center justify-center rounded-lg"> <div className="flex aspect-square size-8 items-center justify-center rounded-lg">
<NeoDlpLogo className="size-full rounded-lg border border-border" /> <NeoDlpLogo className="size-full rounded-md border border-border [--logo-stop-color-1:#4444FF] [--logo-stop-color-2:#FF43D0] customscheme:[--logo-stop-color-1:var(--color-chart-5)] customscheme:[--logo-stop-color-2:var(--color-chart-1)]" />
</div> </div>
<div className="grid flex-1 text-left text-sm leading-tight"> <div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-semibold">Neo Downloader Plus</span> <span className="truncate font-semibold">Neo Downloader Plus</span>
@@ -105,7 +105,7 @@ export function AppSidebar() {
asChild asChild
> >
<Link to={item.url}> <Link to={item.url}>
<item.icon /> <item.icon className="stroke-primary" />
<span>{item.title}</span> <span>{item.title}</span>
{item.title === "Library" && ongoingDownloads.length > 0 && showBadge && ( {item.title === "Library" && ongoingDownloads.length > 0 && showBadge && (
<Badge className="absolute right-2 inset-y-auto rounded-full font-bold bg-foreground/80">{ongoingDownloads.length}</Badge> <Badge className="absolute right-2 inset-y-auto rounded-full font-bold bg-foreground/80">{ongoingDownloads.length}</Badge>
@@ -124,10 +124,12 @@ export function AppSidebar() {
asChild asChild
> >
<Link to={item.url}> <Link to={item.url}>
<item.icon /> <item.icon className="stroke-primary" />
<span>{item.title}</span> <span>{item.title}</span>
{item.title === "Library" && ongoingDownloads.length > 0 && showBadge && ( {item.title === "Library" && ongoingDownloads.length > 0 && showBadge && (
<Badge className="absolute right-2 inset-y-auto h-5 min-w-5 rounded-full px-1 font-mono tabular-nums">{ongoingDownloads.length}</Badge> <Badge className="absolute right-2 inset-y-auto h-5 min-w-5 rounded-full px-1 font-mono tabular-nums flex items-center justify-center">
<span className="mt-0.5">{ongoingDownloads.length}</span>
</Badge>
)} )}
</Link> </Link>
</SidebarMenuButton> </SidebarMenuButton>
@@ -196,7 +198,7 @@ export function AppSidebar() {
asChild asChild
> >
<Link to={item.url}> <Link to={item.url}>
<item.icon /> <item.icon className="stroke-primary" />
<span>{item.title}</span> <span>{item.title}</span>
</Link> </Link>
</SidebarMenuButton> </SidebarMenuButton>
@@ -211,7 +213,7 @@ export function AppSidebar() {
asChild asChild
> >
<Link to={item.url}> <Link to={item.url}>
<item.icon /> <item.icon className="stroke-primary" />
<span>{item.title}</span> <span>{item.title}</span>
</Link> </Link>
</SidebarMenuButton> </SidebarMenuButton>

View File

@@ -1,64 +1,55 @@
import * as React from "react" import * as React from "react"
import * as AccordionPrimitive from "@radix-ui/react-accordion" import * as AccordionPrimitive from "@radix-ui/react-accordion"
import { ChevronDownIcon } from "lucide-react" import { ChevronDown } from "lucide-react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
function Accordion({ const Accordion = AccordionPrimitive.Root
...props
}: React.ComponentProps<typeof AccordionPrimitive.Root>) {
return <AccordionPrimitive.Root data-slot="accordion" {...props} />
}
function AccordionItem({ const AccordionItem = React.forwardRef<
className, React.ElementRef<typeof AccordionPrimitive.Item>,
...props React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
}: React.ComponentProps<typeof AccordionPrimitive.Item>) { >(({ className, ...props }, ref) => (
return (
<AccordionPrimitive.Item <AccordionPrimitive.Item
data-slot="accordion-item" ref={ref}
className={cn("border-b last:border-b-0", className)} className={cn("border-b", className)}
{...props} {...props}
/> />
) ))
} AccordionItem.displayName = "AccordionItem"
function AccordionTrigger({ const AccordionTrigger = React.forwardRef<
className, React.ElementRef<typeof AccordionPrimitive.Trigger>,
children, React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
...props >(({ className, children, ...props }, ref) => (
}: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
return (
<AccordionPrimitive.Header className="flex"> <AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger <AccordionPrimitive.Trigger
data-slot="accordion-trigger" ref={ref}
className={cn( className={cn(
"focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180", "flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all hover:underline text-left [&[data-state=open]>svg]:rotate-180",
className className
)} )}
{...props} {...props}
> >
{children} {children}
<ChevronDownIcon className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" /> <ChevronDown className="h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200" />
</AccordionPrimitive.Trigger> </AccordionPrimitive.Trigger>
</AccordionPrimitive.Header> </AccordionPrimitive.Header>
) ))
} AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
function AccordionContent({ const AccordionContent = React.forwardRef<
className, React.ElementRef<typeof AccordionPrimitive.Content>,
children, React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
...props >(({ className, children, ...props }, ref) => (
}: React.ComponentProps<typeof AccordionPrimitive.Content>) {
return (
<AccordionPrimitive.Content <AccordionPrimitive.Content
data-slot="accordion-content" ref={ref}
className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm" className="overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
{...props} {...props}
> >
<div className={cn("pt-0 pb-4", className)}>{children}</div> <div className={cn("pb-4 pt-0", className)}>{children}</div>
</AccordionPrimitive.Content> </AccordionPrimitive.Content>
) ))
} AccordionContent.displayName = AccordionPrimitive.Content.displayName
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }

View File

@@ -1,146 +1,128 @@
"use client"
import * as React from "react" import * as React from "react"
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
import { buttonVariants } from "@/components/ui/button" import { buttonVariants } from "@/components/ui/button"
function AlertDialog({ const AlertDialog = AlertDialogPrimitive.Root
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />
}
function AlertDialogTrigger({ const AlertDialogTrigger = AlertDialogPrimitive.Trigger
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) {
return (
<AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
)
}
function AlertDialogPortal({ const AlertDialogPortal = AlertDialogPrimitive.Portal
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) {
return (
<AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
)
}
function AlertDialogOverlay({ const AlertDialogOverlay = React.forwardRef<
className, React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
...props React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
}: React.ComponentProps<typeof AlertDialogPrimitive.Overlay>) { >(({ className, ...props }, ref) => (
return (
<AlertDialogPrimitive.Overlay <AlertDialogPrimitive.Overlay
data-slot="alert-dialog-overlay"
className={cn( className={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50", "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className className
)} )}
{...props} {...props}
ref={ref}
/> />
) ))
} AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
function AlertDialogContent({ const AlertDialogContent = React.forwardRef<
className, React.ElementRef<typeof AlertDialogPrimitive.Content>,
...props React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
}: React.ComponentProps<typeof AlertDialogPrimitive.Content>) { >(({ className, ...props }, ref) => (
return (
<AlertDialogPortal> <AlertDialogPortal>
<AlertDialogOverlay /> <AlertDialogOverlay />
<AlertDialogPrimitive.Content <AlertDialogPrimitive.Content
data-slot="alert-dialog-content" ref={ref}
className={cn( className={cn(
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg", "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 sm:rounded-lg",
className className
)} )}
{...props} {...props}
/> />
</AlertDialogPortal> </AlertDialogPortal>
) ))
} AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
function AlertDialogHeader({ const AlertDialogHeader = ({
className, className,
...props ...props
}: React.ComponentProps<"div">) { }: React.HTMLAttributes<HTMLDivElement>) => (
return (
<div <div
data-slot="alert-dialog-header"
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
{...props}
/>
)
}
function AlertDialogFooter({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-dialog-footer"
className={cn( className={cn(
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", "flex flex-col space-y-2 text-center sm:text-left",
className className
)} )}
{...props} {...props}
/> />
) )
} AlertDialogHeader.displayName = "AlertDialogHeader"
function AlertDialogTitle({ const AlertDialogFooter = ({
className, className,
...props ...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Title>) { }: React.HTMLAttributes<HTMLDivElement>) => (
return ( <div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
AlertDialogFooter.displayName = "AlertDialogFooter"
const AlertDialogTitle = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Title <AlertDialogPrimitive.Title
data-slot="alert-dialog-title" ref={ref}
className={cn("text-lg font-semibold", className)} className={cn("text-lg font-semibold", className)}
{...props} {...props}
/> />
) ))
} AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
function AlertDialogDescription({ const AlertDialogDescription = React.forwardRef<
className, React.ElementRef<typeof AlertDialogPrimitive.Description>,
...props React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
}: React.ComponentProps<typeof AlertDialogPrimitive.Description>) { >(({ className, ...props }, ref) => (
return (
<AlertDialogPrimitive.Description <AlertDialogPrimitive.Description
data-slot="alert-dialog-description" ref={ref}
className={cn("text-muted-foreground text-sm", className)} className={cn("text-sm text-muted-foreground", className)}
{...props} {...props}
/> />
) ))
} AlertDialogDescription.displayName =
AlertDialogPrimitive.Description.displayName
function AlertDialogAction({ const AlertDialogAction = React.forwardRef<
className, React.ElementRef<typeof AlertDialogPrimitive.Action>,
...props React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
}: React.ComponentProps<typeof AlertDialogPrimitive.Action>) { >(({ className, ...props }, ref) => (
return (
<AlertDialogPrimitive.Action <AlertDialogPrimitive.Action
ref={ref}
className={cn(buttonVariants(), className)} className={cn(buttonVariants(), className)}
{...props} {...props}
/> />
) ))
} AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
function AlertDialogCancel({ const AlertDialogCancel = React.forwardRef<
className, React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
...props React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
}: React.ComponentProps<typeof AlertDialogPrimitive.Cancel>) { >(({ className, ...props }, ref) => (
return (
<AlertDialogPrimitive.Cancel <AlertDialogPrimitive.Cancel
className={cn(buttonVariants({ variant: "outline" }), className)} ref={ref}
className={cn(
buttonVariants({ variant: "outline" }),
"mt-2 sm:mt-0",
className
)}
{...props} {...props}
/> />
) ))
} AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
export { export {
AlertDialog, AlertDialog,

View File

@@ -4,13 +4,13 @@ import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const alertVariants = cva( const alertVariants = cva(
"relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current", "relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7",
{ {
variants: { variants: {
variant: { variant: {
default: "bg-card text-card-foreground", default: "bg-background text-foreground",
destructive: destructive:
"text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90", "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
}, },
}, },
defaultVariants: { defaultVariants: {
@@ -19,48 +19,41 @@ const alertVariants = cva(
} }
) )
function Alert({ const Alert = React.forwardRef<
className, HTMLDivElement,
variant, React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
...props >(({ className, variant, ...props }, ref) => (
}: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
return (
<div <div
data-slot="alert" ref={ref}
role="alert" role="alert"
className={cn(alertVariants({ variant }), className)} className={cn(alertVariants({ variant }), className)}
{...props} {...props}
/> />
) ))
} Alert.displayName = "Alert"
function AlertTitle({ className, ...props }: React.ComponentProps<"div">) { const AlertTitle = React.forwardRef<
return ( HTMLParagraphElement,
<div React.HTMLAttributes<HTMLHeadingElement>
data-slot="alert-title" >(({ className, ...props }, ref) => (
className={cn( <h5
"col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight", ref={ref}
className className={cn("mb-1 font-medium leading-none tracking-tight", className)}
)}
{...props} {...props}
/> />
) ))
} AlertTitle.displayName = "AlertTitle"
function AlertDescription({ const AlertDescription = React.forwardRef<
className, HTMLParagraphElement,
...props React.HTMLAttributes<HTMLParagraphElement>
}: React.ComponentProps<"div">) { >(({ className, ...props }, ref) => (
return (
<div <div
data-slot="alert-description" ref={ref}
className={cn( className={cn("text-sm [&_p]:leading-relaxed", className)}
"text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed",
className
)}
{...props} {...props}
/> />
) ))
} AlertDescription.displayName = "AlertDescription"
export { Alert, AlertTitle, AlertDescription } export { Alert, AlertTitle, AlertDescription }

View File

@@ -1,9 +1,5 @@
import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio" import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
function AspectRatio({ const AspectRatio = AspectRatioPrimitive.Root
...props
}: React.ComponentProps<typeof AspectRatioPrimitive.Root>) {
return <AspectRatioPrimitive.Root data-slot="aspect-ratio" {...props} />
}
export { AspectRatio } export { AspectRatio }

View File

@@ -5,49 +5,46 @@ import * as AvatarPrimitive from "@radix-ui/react-avatar"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
function Avatar({ const Avatar = React.forwardRef<
className, React.ElementRef<typeof AvatarPrimitive.Root>,
...props React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
}: React.ComponentProps<typeof AvatarPrimitive.Root>) { >(({ className, ...props }, ref) => (
return (
<AvatarPrimitive.Root <AvatarPrimitive.Root
data-slot="avatar" ref={ref}
className={cn( className={cn(
"relative flex size-8 shrink-0 overflow-hidden rounded-full", "relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
className className
)} )}
{...props} {...props}
/> />
) ))
} Avatar.displayName = AvatarPrimitive.Root.displayName
function AvatarImage({ const AvatarImage = React.forwardRef<
className, React.ElementRef<typeof AvatarPrimitive.Image>,
...props React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
}: React.ComponentProps<typeof AvatarPrimitive.Image>) { >(({ className, ...props }, ref) => (
return (
<AvatarPrimitive.Image <AvatarPrimitive.Image
data-slot="avatar-image" ref={ref}
className={cn("aspect-square size-full", className)} className={cn("aspect-square h-full w-full", className)}
{...props} {...props}
/> />
) ))
} AvatarImage.displayName = AvatarPrimitive.Image.displayName
function AvatarFallback({ const AvatarFallback = React.forwardRef<
className, React.ElementRef<typeof AvatarPrimitive.Fallback>,
...props React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) { >(({ className, ...props }, ref) => (
return (
<AvatarPrimitive.Fallback <AvatarPrimitive.Fallback
data-slot="avatar-fallback" ref={ref}
className={cn( className={cn(
"bg-muted flex size-full items-center justify-center rounded-full", "flex h-full w-full items-center justify-center rounded-full bg-muted",
className className
)} )}
{...props} {...props}
/> />
) ))
} AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
export { Avatar, AvatarImage, AvatarFallback } export { Avatar, AvatarImage, AvatarFallback }

View File

@@ -1,22 +1,20 @@
import * as React from "react" import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority" import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const badgeVariants = cva( const badgeVariants = cva(
"inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden", "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
{ {
variants: { variants: {
variant: { variant: {
default: default:
"border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90", "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
secondary: secondary:
"border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90", "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive: destructive:
"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
outline: outline: "text-foreground",
"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
}, },
}, },
defaultVariants: { defaultVariants: {
@@ -25,21 +23,13 @@ const badgeVariants = cva(
} }
) )
function Badge({ export interface BadgeProps
className, extends React.HTMLAttributes<HTMLDivElement>,
variant, VariantProps<typeof badgeVariants> {}
asChild = false,
...props
}: React.ComponentProps<"span"> &
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
const Comp = asChild ? Slot : "span"
function Badge({ className, variant, ...props }: BadgeProps) {
return ( return (
<Comp <div className={cn(badgeVariants({ variant }), className)} {...props} />
data-slot="badge"
className={cn(badgeVariants({ variant }), className)}
{...props}
/>
) )
} }

View File

@@ -4,99 +4,105 @@ import { ChevronRight, MoreHorizontal } from "lucide-react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
function Breadcrumb({ ...props }: React.ComponentProps<"nav">) { const Breadcrumb = React.forwardRef<
return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} /> HTMLElement,
React.ComponentPropsWithoutRef<"nav"> & {
separator?: React.ReactNode
} }
>(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />)
Breadcrumb.displayName = "Breadcrumb"
function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) { const BreadcrumbList = React.forwardRef<
return ( HTMLOListElement,
React.ComponentPropsWithoutRef<"ol">
>(({ className, ...props }, ref) => (
<ol <ol
data-slot="breadcrumb-list" ref={ref}
className={cn( className={cn(
"text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5", "flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5",
className className
)} )}
{...props} {...props}
/> />
) ))
} BreadcrumbList.displayName = "BreadcrumbList"
function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) { const BreadcrumbItem = React.forwardRef<
return ( HTMLLIElement,
React.ComponentPropsWithoutRef<"li">
>(({ className, ...props }, ref) => (
<li <li
data-slot="breadcrumb-item" ref={ref}
className={cn("inline-flex items-center gap-1.5", className)} className={cn("inline-flex items-center gap-1.5", className)}
{...props} {...props}
/> />
) ))
} BreadcrumbItem.displayName = "BreadcrumbItem"
function BreadcrumbLink({ const BreadcrumbLink = React.forwardRef<
asChild, HTMLAnchorElement,
className, React.ComponentPropsWithoutRef<"a"> & {
...props
}: React.ComponentProps<"a"> & {
asChild?: boolean asChild?: boolean
}) { }
>(({ asChild, className, ...props }, ref) => {
const Comp = asChild ? Slot : "a" const Comp = asChild ? Slot : "a"
return ( return (
<Comp <Comp
data-slot="breadcrumb-link" ref={ref}
className={cn("hover:text-foreground transition-colors", className)} className={cn("transition-colors hover:text-foreground", className)}
{...props} {...props}
/> />
) )
} })
BreadcrumbLink.displayName = "BreadcrumbLink"
function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) { const BreadcrumbPage = React.forwardRef<
return ( HTMLSpanElement,
React.ComponentPropsWithoutRef<"span">
>(({ className, ...props }, ref) => (
<span <span
data-slot="breadcrumb-page" ref={ref}
role="link" role="link"
aria-disabled="true" aria-disabled="true"
aria-current="page" aria-current="page"
className={cn("text-foreground font-normal", className)} className={cn("font-normal text-foreground", className)}
{...props} {...props}
/> />
) ))
} BreadcrumbPage.displayName = "BreadcrumbPage"
function BreadcrumbSeparator({ const BreadcrumbSeparator = ({
children, children,
className, className,
...props ...props
}: React.ComponentProps<"li">) { }: React.ComponentProps<"li">) => (
return (
<li <li
data-slot="breadcrumb-separator"
role="presentation" role="presentation"
aria-hidden="true" aria-hidden="true"
className={cn("[&>svg]:size-3.5", className)} className={cn("[&>svg]:w-3.5 [&>svg]:h-3.5", className)}
{...props} {...props}
> >
{children ?? <ChevronRight />} {children ?? <ChevronRight />}
</li> </li>
) )
} BreadcrumbSeparator.displayName = "BreadcrumbSeparator"
function BreadcrumbEllipsis({ const BreadcrumbEllipsis = ({
className, className,
...props ...props
}: React.ComponentProps<"span">) { }: React.ComponentProps<"span">) => (
return (
<span <span
data-slot="breadcrumb-ellipsis"
role="presentation" role="presentation"
aria-hidden="true" aria-hidden="true"
className={cn("flex size-9 items-center justify-center", className)} className={cn("flex h-9 w-9 items-center justify-center", className)}
{...props} {...props}
> >
<MoreHorizontal className="size-4" /> <MoreHorizontal className="h-4 w-4" />
<span className="sr-only">More</span> <span className="sr-only">More</span>
</span> </span>
) )
} BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"
export { export {
Breadcrumb, Breadcrumb,

View File

@@ -0,0 +1,83 @@
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
import { Separator } from "@/components/ui/separator"
const buttonGroupVariants = cva(
"flex w-fit items-stretch has-[>[data-slot=button-group]]:gap-2 [&>*]:focus-visible:relative [&>*]:focus-visible:z-10 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1",
{
variants: {
orientation: {
horizontal:
"[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none",
vertical:
"flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none",
},
},
defaultVariants: {
orientation: "horizontal",
},
}
)
function ButtonGroup({
className,
orientation,
...props
}: React.ComponentProps<"div"> & VariantProps<typeof buttonGroupVariants>) {
return (
<div
role="group"
data-slot="button-group"
data-orientation={orientation}
className={cn(buttonGroupVariants({ orientation }), className)}
{...props}
/>
)
}
function ButtonGroupText({
className,
asChild = false,
...props
}: React.ComponentProps<"div"> & {
asChild?: boolean
}) {
const Comp = asChild ? Slot : "div"
return (
<Comp
className={cn(
"bg-muted shadow-xs flex items-center gap-2 rounded-md border px-4 text-sm font-medium [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none",
className
)}
{...props}
/>
)
}
function ButtonGroupSeparator({
className,
orientation = "vertical",
...props
}: React.ComponentProps<typeof Separator>) {
return (
<Separator
data-slot="button-group-separator"
orientation={orientation}
className={cn(
"bg-input relative !m-0 self-stretch data-[orientation=vertical]:h-auto",
className
)}
{...props}
/>
)
}
export {
ButtonGroup,
ButtonGroupSeparator,
ButtonGroupText,
buttonGroupVariants,
}

View File

@@ -5,27 +5,28 @@ import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const buttonVariants = cva( const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{ {
variants: { variants: {
variant: { variant: {
default: default:
"bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", "bg-primary text-primary-foreground shadow hover:bg-primary/90",
destructive: destructive:
"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
outline: outline:
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
secondary: secondary:
"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
ghost: ghost: "hover:bg-accent hover:text-accent-foreground",
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline", link: "text-primary underline-offset-4 hover:underline",
}, },
size: { size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3", default: "h-9 px-4 py-2",
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", sm: "h-8 rounded-md px-3 text-xs",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4", lg: "h-10 rounded-md px-8",
icon: "size-9", icon: "h-9 w-9",
"icon-sm": "size-8",
"icon-lg": "size-10",
}, },
}, },
defaultVariants: { defaultVariants: {
@@ -35,25 +36,24 @@ const buttonVariants = cva(
} }
) )
function Button({ export interface ButtonProps
className, extends React.ButtonHTMLAttributes<HTMLButtonElement>,
variant, VariantProps<typeof buttonVariants> {
size,
asChild = false,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean asChild?: boolean
}) { }
const Comp = asChild ? Slot : "button"
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return ( return (
<Comp <Comp
data-slot="button"
className={cn(buttonVariants({ variant, size, className }))} className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props} {...props}
/> />
) )
} }
)
Button.displayName = "Button"
export { Button, buttonVariants } export { Button, buttonVariants }

View File

@@ -1,3 +1,5 @@
"use client"
import * as React from "react" import * as React from "react"
import { import {
ChevronDownIcon, ChevronDownIcon,
@@ -27,7 +29,7 @@ function Calendar({
<DayPicker <DayPicker
showOutsideDays={showOutsideDays} showOutsideDays={showOutsideDays}
className={cn( className={cn(
"bg-background group/calendar p-3 [--cell-size:--spacing(8)] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent", "bg-background group/calendar p-3 [--cell-size:2rem] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`, String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`, String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
className className
@@ -41,69 +43,72 @@ function Calendar({
classNames={{ classNames={{
root: cn("w-fit", defaultClassNames.root), root: cn("w-fit", defaultClassNames.root),
months: cn( months: cn(
"flex gap-4 flex-col md:flex-row relative", "relative flex flex-col gap-4 md:flex-row",
defaultClassNames.months defaultClassNames.months
), ),
month: cn("flex flex-col w-full gap-4", defaultClassNames.month), month: cn("flex w-full flex-col gap-4", defaultClassNames.month),
nav: cn( nav: cn(
"flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between", "absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1",
defaultClassNames.nav defaultClassNames.nav
), ),
button_previous: cn( button_previous: cn(
buttonVariants({ variant: buttonVariant }), buttonVariants({ variant: buttonVariant }),
"size-(--cell-size) aria-disabled:opacity-50 p-0 select-none", "h-[--cell-size] w-[--cell-size] select-none p-0 aria-disabled:opacity-50",
defaultClassNames.button_previous defaultClassNames.button_previous
), ),
button_next: cn( button_next: cn(
buttonVariants({ variant: buttonVariant }), buttonVariants({ variant: buttonVariant }),
"size-(--cell-size) aria-disabled:opacity-50 p-0 select-none", "h-[--cell-size] w-[--cell-size] select-none p-0 aria-disabled:opacity-50",
defaultClassNames.button_next defaultClassNames.button_next
), ),
month_caption: cn( month_caption: cn(
"flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)", "flex h-[--cell-size] w-full items-center justify-center px-[--cell-size]",
defaultClassNames.month_caption defaultClassNames.month_caption
), ),
dropdowns: cn( dropdowns: cn(
"w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5", "flex h-[--cell-size] w-full items-center justify-center gap-1.5 text-sm font-medium",
defaultClassNames.dropdowns defaultClassNames.dropdowns
), ),
dropdown_root: cn( dropdown_root: cn(
"relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md", "has-focus:border-ring border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] relative rounded-md border",
defaultClassNames.dropdown_root defaultClassNames.dropdown_root
), ),
dropdown: cn("absolute inset-0 opacity-0", defaultClassNames.dropdown), dropdown: cn(
"bg-popover absolute inset-0 opacity-0",
defaultClassNames.dropdown
),
caption_label: cn( caption_label: cn(
"select-none font-medium", "select-none font-medium",
captionLayout === "label" captionLayout === "label"
? "text-sm" ? "text-sm"
: "rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5", : "[&>svg]:text-muted-foreground flex h-8 items-center gap-1 rounded-md pl-2 pr-1 text-sm [&>svg]:size-3.5",
defaultClassNames.caption_label defaultClassNames.caption_label
), ),
table: "w-full border-collapse", table: "w-full border-collapse",
weekdays: cn("flex", defaultClassNames.weekdays), weekdays: cn("flex", defaultClassNames.weekdays),
weekday: cn( weekday: cn(
"text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none", "text-muted-foreground flex-1 select-none rounded-md text-[0.8rem] font-normal",
defaultClassNames.weekday defaultClassNames.weekday
), ),
week: cn("flex w-full mt-2", defaultClassNames.week), week: cn("mt-2 flex w-full", defaultClassNames.week),
week_number_header: cn( week_number_header: cn(
"select-none w-(--cell-size)", "w-[--cell-size] select-none",
defaultClassNames.week_number_header defaultClassNames.week_number_header
), ),
week_number: cn( week_number: cn(
"text-[0.8rem] select-none text-muted-foreground", "text-muted-foreground select-none text-[0.8rem]",
defaultClassNames.week_number defaultClassNames.week_number
), ),
day: cn( day: cn(
"relative w-full h-full p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none", "group/day relative aspect-square h-full w-full select-none p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md",
defaultClassNames.day defaultClassNames.day
), ),
range_start: cn( range_start: cn(
"rounded-l-md bg-accent", "bg-accent rounded-l-md",
defaultClassNames.range_start defaultClassNames.range_start
), ),
range_middle: cn("rounded-none", defaultClassNames.range_middle), range_middle: cn("rounded-none", defaultClassNames.range_middle),
range_end: cn("rounded-r-md bg-accent", defaultClassNames.range_end), range_end: cn("bg-accent rounded-r-md", defaultClassNames.range_end),
today: cn( today: cn(
"bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none", "bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none",
defaultClassNames.today defaultClassNames.today
@@ -154,7 +159,7 @@ function Calendar({
WeekNumber: ({ children, ...props }) => { WeekNumber: ({ children, ...props }) => {
return ( return (
<td {...props}> <td {...props}>
<div className="flex size-(--cell-size) items-center justify-center text-center"> <div className="flex size-[--cell-size] items-center justify-center text-center">
{children} {children}
</div> </div>
</td> </td>
@@ -196,7 +201,7 @@ function CalendarDayButton({
data-range-end={modifiers.range_end} data-range-end={modifiers.range_end}
data-range-middle={modifiers.range_middle} data-range-middle={modifiers.range_middle}
className={cn( className={cn(
"data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 dark:hover:text-accent-foreground flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] data-[range-end=true]:rounded-md data-[range-end=true]:rounded-r-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md data-[range-start=true]:rounded-l-md [&>span]:text-xs [&>span]:opacity-70", "data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 flex aspect-square h-auto w-full min-w-[--cell-size] flex-col gap-1 font-normal leading-none data-[range-end=true]:rounded-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] [&>span]:text-xs [&>span]:opacity-70",
defaultClassNames.day, defaultClassNames.day,
className className
)} )}

View File

@@ -2,91 +2,75 @@ import * as React from "react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
function Card({ className, ...props }: React.ComponentProps<"div">) { const Card = React.forwardRef<
return ( HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div <div
data-slot="card" ref={ref}
className={cn( className={cn(
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm", "rounded-xl border bg-card text-card-foreground shadow",
className className
)} )}
{...props} {...props}
/> />
) ))
} Card.displayName = "Card"
function CardHeader({ className, ...props }: React.ComponentProps<"div">) { const CardHeader = React.forwardRef<
return ( HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div <div
data-slot="card-header" ref={ref}
className={cn( className={cn("flex flex-col space-y-1.5 p-6", className)}
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
className
)}
{...props} {...props}
/> />
) ))
} CardHeader.displayName = "CardHeader"
function CardTitle({ className, ...props }: React.ComponentProps<"div">) { const CardTitle = React.forwardRef<
return ( HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div <div
data-slot="card-title" ref={ref}
className={cn("leading-none font-semibold", className)} className={cn("font-semibold leading-none tracking-tight", className)}
{...props} {...props}
/> />
) ))
} CardTitle.displayName = "CardTitle"
function CardDescription({ className, ...props }: React.ComponentProps<"div">) { const CardDescription = React.forwardRef<
return ( HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div <div
data-slot="card-description" ref={ref}
className={cn("text-muted-foreground text-sm", className)} className={cn("text-sm text-muted-foreground", className)}
{...props} {...props}
/> />
) ))
} CardDescription.displayName = "CardDescription"
function CardAction({ className, ...props }: React.ComponentProps<"div">) { const CardContent = React.forwardRef<
return ( HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
))
CardContent.displayName = "CardContent"
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div <div
data-slot="card-action" ref={ref}
className={cn( className={cn("flex items-center p-6 pt-0", className)}
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
className
)}
{...props} {...props}
/> />
) ))
} CardFooter.displayName = "CardFooter"
function CardContent({ className, ...props }: React.ComponentProps<"div">) { export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
return (
<div
data-slot="card-content"
className={cn("px-6", className)}
{...props}
/>
)
}
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-footer"
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
{...props}
/>
)
}
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardAction,
CardDescription,
CardContent,
}

View File

@@ -1,5 +1,3 @@
"use client"
import * as React from "react" import * as React from "react"
import useEmblaCarousel, { import useEmblaCarousel, {
type UseEmblaCarouselType, type UseEmblaCarouselType,
@@ -42,7 +40,12 @@ function useCarousel() {
return context return context
} }
function Carousel({ const Carousel = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & CarouselProps
>(
(
{
orientation = "horizontal", orientation = "horizontal",
opts, opts,
setApi, setApi,
@@ -50,7 +53,9 @@ function Carousel({
className, className,
children, children,
...props ...props
}: React.ComponentProps<"div"> & CarouselProps) { },
ref
) => {
const [carouselRef, api] = useEmblaCarousel( const [carouselRef, api] = useEmblaCarousel(
{ {
...opts, ...opts,
@@ -62,7 +67,10 @@ function Carousel({
const [canScrollNext, setCanScrollNext] = React.useState(false) const [canScrollNext, setCanScrollNext] = React.useState(false)
const onSelect = React.useCallback((api: CarouselApi) => { const onSelect = React.useCallback((api: CarouselApi) => {
if (!api) return if (!api) {
return
}
setCanScrollPrev(api.canScrollPrev()) setCanScrollPrev(api.canScrollPrev())
setCanScrollNext(api.canScrollNext()) setCanScrollNext(api.canScrollNext())
}, []) }, [])
@@ -89,12 +97,18 @@ function Carousel({
) )
React.useEffect(() => { React.useEffect(() => {
if (!api || !setApi) return if (!api || !setApi) {
return
}
setApi(api) setApi(api)
}, [api, setApi]) }, [api, setApi])
React.useEffect(() => { React.useEffect(() => {
if (!api) return if (!api) {
return
}
onSelect(api) onSelect(api)
api.on("reInit", onSelect) api.on("reInit", onSelect)
api.on("select", onSelect) api.on("select", onSelect)
@@ -119,11 +133,11 @@ function Carousel({
}} }}
> >
<div <div
ref={ref}
onKeyDownCapture={handleKeyDown} onKeyDownCapture={handleKeyDown}
className={cn("relative", className)} className={cn("relative", className)}
role="region" role="region"
aria-roledescription="carousel" aria-roledescription="carousel"
data-slot="carousel"
{...props} {...props}
> >
{children} {children}
@@ -131,17 +145,19 @@ function Carousel({
</CarouselContext.Provider> </CarouselContext.Provider>
) )
} }
)
Carousel.displayName = "Carousel"
function CarouselContent({ className, ...props }: React.ComponentProps<"div">) { const CarouselContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const { carouselRef, orientation } = useCarousel() const { carouselRef, orientation } = useCarousel()
return ( return (
<div ref={carouselRef} className="overflow-hidden">
<div <div
ref={carouselRef} ref={ref}
className="overflow-hidden"
data-slot="carousel-content"
>
<div
className={cn( className={cn(
"flex", "flex",
orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col", orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
@@ -151,16 +167,20 @@ function CarouselContent({ className, ...props }: React.ComponentProps<"div">) {
/> />
</div> </div>
) )
} })
CarouselContent.displayName = "CarouselContent"
function CarouselItem({ className, ...props }: React.ComponentProps<"div">) { const CarouselItem = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const { orientation } = useCarousel() const { orientation } = useCarousel()
return ( return (
<div <div
ref={ref}
role="group" role="group"
aria-roledescription="slide" aria-roledescription="slide"
data-slot="carousel-item"
className={cn( className={cn(
"min-w-0 shrink-0 grow-0 basis-full", "min-w-0 shrink-0 grow-0 basis-full",
orientation === "horizontal" ? "pl-4" : "pt-4", orientation === "horizontal" ? "pl-4" : "pt-4",
@@ -169,25 +189,24 @@ function CarouselItem({ className, ...props }: React.ComponentProps<"div">) {
{...props} {...props}
/> />
) )
} })
CarouselItem.displayName = "CarouselItem"
function CarouselPrevious({ const CarouselPrevious = React.forwardRef<
className, HTMLButtonElement,
variant = "outline", React.ComponentProps<typeof Button>
size = "icon", >(({ className, variant = "outline", size = "icon", ...props }, ref) => {
...props
}: React.ComponentProps<typeof Button>) {
const { orientation, scrollPrev, canScrollPrev } = useCarousel() const { orientation, scrollPrev, canScrollPrev } = useCarousel()
return ( return (
<Button <Button
data-slot="carousel-previous" ref={ref}
variant={variant} variant={variant}
size={size} size={size}
className={cn( className={cn(
"absolute size-8 rounded-full", "absolute h-8 w-8 rounded-full",
orientation === "horizontal" orientation === "horizontal"
? "top-1/2 -left-12 -translate-y-1/2" ? "-left-12 top-1/2 -translate-y-1/2"
: "-top-12 left-1/2 -translate-x-1/2 rotate-90", : "-top-12 left-1/2 -translate-x-1/2 rotate-90",
className className
)} )}
@@ -195,29 +214,28 @@ function CarouselPrevious({
onClick={scrollPrev} onClick={scrollPrev}
{...props} {...props}
> >
<ArrowLeft /> <ArrowLeft className="h-4 w-4" />
<span className="sr-only">Previous slide</span> <span className="sr-only">Previous slide</span>
</Button> </Button>
) )
} })
CarouselPrevious.displayName = "CarouselPrevious"
function CarouselNext({ const CarouselNext = React.forwardRef<
className, HTMLButtonElement,
variant = "outline", React.ComponentProps<typeof Button>
size = "icon", >(({ className, variant = "outline", size = "icon", ...props }, ref) => {
...props
}: React.ComponentProps<typeof Button>) {
const { orientation, scrollNext, canScrollNext } = useCarousel() const { orientation, scrollNext, canScrollNext } = useCarousel()
return ( return (
<Button <Button
data-slot="carousel-next" ref={ref}
variant={variant} variant={variant}
size={size} size={size}
className={cn( className={cn(
"absolute size-8 rounded-full", "absolute h-8 w-8 rounded-full",
orientation === "horizontal" orientation === "horizontal"
? "top-1/2 -right-12 -translate-y-1/2" ? "-right-12 top-1/2 -translate-y-1/2"
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90", : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
className className
)} )}
@@ -225,11 +243,12 @@ function CarouselNext({
onClick={scrollNext} onClick={scrollNext}
{...props} {...props}
> >
<ArrowRight /> <ArrowRight className="h-4 w-4" />
<span className="sr-only">Next slide</span> <span className="sr-only">Next slide</span>
</Button> </Button>
) )
} })
CarouselNext.displayName = "CarouselNext"
export { export {
type CarouselApi, type CarouselApi,

View File

@@ -1,32 +1,28 @@
"use client"
import * as React from "react" import * as React from "react"
import * as CheckboxPrimitive from "@radix-ui/react-checkbox" import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
import { CheckIcon } from "lucide-react" import { Check } from "lucide-react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
function Checkbox({ const Checkbox = React.forwardRef<
className, React.ElementRef<typeof CheckboxPrimitive.Root>,
...props React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
}: React.ComponentProps<typeof CheckboxPrimitive.Root>) { >(({ className, ...props }, ref) => (
return (
<CheckboxPrimitive.Root <CheckboxPrimitive.Root
data-slot="checkbox" ref={ref}
className={cn( className={cn(
"peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50", "grid place-content-center peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
className className
)} )}
{...props} {...props}
> >
<CheckboxPrimitive.Indicator <CheckboxPrimitive.Indicator
data-slot="checkbox-indicator" className={cn("grid place-content-center text-current")}
className="flex items-center justify-center text-current transition-none"
> >
<CheckIcon className="size-3.5" /> <Check className="h-4 w-4" />
</CheckboxPrimitive.Indicator> </CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root> </CheckboxPrimitive.Root>
) ))
} Checkbox.displayName = CheckboxPrimitive.Root.displayName
export { Checkbox } export { Checkbox }

View File

@@ -1,31 +1,11 @@
"use client"
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible" import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
function Collapsible({ const Collapsible = CollapsiblePrimitive.Root
...props
}: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {
return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />
}
function CollapsibleTrigger({ const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
...props
}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) {
return (
<CollapsiblePrimitive.CollapsibleTrigger
data-slot="collapsible-trigger"
{...props}
/>
)
}
function CollapsibleContent({ const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
...props
}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) {
return (
<CollapsiblePrimitive.CollapsibleContent
data-slot="collapsible-content"
{...props}
/>
)
}
export { Collapsible, CollapsibleTrigger, CollapsibleContent } export { Collapsible, CollapsibleTrigger, CollapsibleContent }

View File

@@ -1,58 +1,33 @@
"use client" "use client"
import * as React from "react" import * as React from "react"
import { type DialogProps } from "@radix-ui/react-dialog"
import { Command as CommandPrimitive } from "cmdk" import { Command as CommandPrimitive } from "cmdk"
import { SearchIcon } from "lucide-react" import { Search } from "lucide-react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
import { import { Dialog, DialogContent } from "@/components/ui/dialog"
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
function Command({ const Command = React.forwardRef<
className, React.ElementRef<typeof CommandPrimitive>,
...props React.ComponentPropsWithoutRef<typeof CommandPrimitive>
}: React.ComponentProps<typeof CommandPrimitive>) { >(({ className, ...props }, ref) => (
return (
<CommandPrimitive <CommandPrimitive
data-slot="command" ref={ref}
className={cn( className={cn(
"bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md", "flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
className className
)} )}
{...props} {...props}
/> />
) ))
} Command.displayName = CommandPrimitive.displayName
function CommandDialog({ const CommandDialog = ({ children, ...props }: DialogProps) => {
title = "Command Palette",
description = "Search for a command to run...",
children,
className,
showCloseButton = true,
...props
}: React.ComponentProps<typeof Dialog> & {
title?: string
description?: string
className?: string
showCloseButton?: boolean
}) {
return ( return (
<Dialog {...props}> <Dialog {...props}>
<DialogHeader className="sr-only"> <DialogContent className="overflow-hidden p-0">
<DialogTitle>{title}</DialogTitle> <Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
<DialogDescription>{description}</DialogDescription>
</DialogHeader>
<DialogContent
className={cn("overflow-hidden p-0", className)}
showCloseButton={showCloseButton}
>
<Command className="[&_[cmdk-group-heading]]:text-muted-foreground **:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
{children} {children}
</Command> </Command>
</DialogContent> </DialogContent>
@@ -60,116 +35,110 @@ function CommandDialog({
) )
} }
function CommandInput({ const CommandInput = React.forwardRef<
className, React.ElementRef<typeof CommandPrimitive.Input>,
...props React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
}: React.ComponentProps<typeof CommandPrimitive.Input>) { >(({ className, ...props }, ref) => (
return ( <div className="flex items-center border-b px-3" cmdk-input-wrapper="">
<div <Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
data-slot="command-input-wrapper"
className="flex h-9 items-center gap-2 border-b px-3"
>
<SearchIcon className="size-4 shrink-0 opacity-50" />
<CommandPrimitive.Input <CommandPrimitive.Input
data-slot="command-input" ref={ref}
className={cn( className={cn(
"placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50", "flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
className className
)} )}
{...props} {...props}
/> />
</div> </div>
) ))
}
function CommandList({ CommandInput.displayName = CommandPrimitive.Input.displayName
className,
...props const CommandList = React.forwardRef<
}: React.ComponentProps<typeof CommandPrimitive.List>) { React.ElementRef<typeof CommandPrimitive.List>,
return ( React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
>(({ className, ...props }, ref) => (
<CommandPrimitive.List <CommandPrimitive.List
data-slot="command-list" ref={ref}
className={cn( className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
"max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto",
className
)}
{...props} {...props}
/> />
) ))
}
function CommandEmpty({ CommandList.displayName = CommandPrimitive.List.displayName
...props
}: React.ComponentProps<typeof CommandPrimitive.Empty>) { const CommandEmpty = React.forwardRef<
return ( React.ElementRef<typeof CommandPrimitive.Empty>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
>((props, ref) => (
<CommandPrimitive.Empty <CommandPrimitive.Empty
data-slot="command-empty" ref={ref}
className="py-6 text-center text-sm" className="py-6 text-center text-sm"
{...props} {...props}
/> />
) ))
}
function CommandGroup({ CommandEmpty.displayName = CommandPrimitive.Empty.displayName
className,
...props const CommandGroup = React.forwardRef<
}: React.ComponentProps<typeof CommandPrimitive.Group>) { React.ElementRef<typeof CommandPrimitive.Group>,
return ( React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Group <CommandPrimitive.Group
data-slot="command-group" ref={ref}
className={cn( className={cn(
"text-foreground [&_[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium", "overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
className className
)} )}
{...props} {...props}
/> />
) ))
}
function CommandSeparator({ CommandGroup.displayName = CommandPrimitive.Group.displayName
className,
...props const CommandSeparator = React.forwardRef<
}: React.ComponentProps<typeof CommandPrimitive.Separator>) { React.ElementRef<typeof CommandPrimitive.Separator>,
return ( React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Separator <CommandPrimitive.Separator
data-slot="command-separator" ref={ref}
className={cn("bg-border -mx-1 h-px", className)} className={cn("-mx-1 h-px bg-border", className)}
{...props} {...props}
/> />
) ))
} CommandSeparator.displayName = CommandPrimitive.Separator.displayName
function CommandItem({ const CommandItem = React.forwardRef<
className, React.ElementRef<typeof CommandPrimitive.Item>,
...props React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
}: React.ComponentProps<typeof CommandPrimitive.Item>) { >(({ className, ...props }, ref) => (
return (
<CommandPrimitive.Item <CommandPrimitive.Item
data-slot="command-item" ref={ref}
className={cn( className={cn(
"data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", "relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
className className
)} )}
{...props} {...props}
/> />
) ))
}
function CommandShortcut({ CommandItem.displayName = CommandPrimitive.Item.displayName
const CommandShortcut = ({
className, className,
...props ...props
}: React.ComponentProps<"span">) { }: React.HTMLAttributes<HTMLSpanElement>) => {
return ( return (
<span <span
data-slot="command-shortcut"
className={cn( className={cn(
"text-muted-foreground ml-auto text-xs tracking-widest", "ml-auto text-xs tracking-widest text-muted-foreground",
className className
)} )}
{...props} {...props}
/> />
) )
} }
CommandShortcut.displayName = "CommandShortcut"
export { export {
Command, Command,

View File

@@ -1,237 +1,183 @@
"use client"
import * as React from "react" import * as React from "react"
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu" import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react" import { Check, ChevronRight, Circle } from "lucide-react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
function ContextMenu({ const ContextMenu = ContextMenuPrimitive.Root
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Root>) {
return <ContextMenuPrimitive.Root data-slot="context-menu" {...props} />
}
function ContextMenuTrigger({ const ContextMenuTrigger = ContextMenuPrimitive.Trigger
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Trigger>) {
return (
<ContextMenuPrimitive.Trigger data-slot="context-menu-trigger" {...props} />
)
}
function ContextMenuGroup({ const ContextMenuGroup = ContextMenuPrimitive.Group
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Group>) {
return (
<ContextMenuPrimitive.Group data-slot="context-menu-group" {...props} />
)
}
function ContextMenuPortal({ const ContextMenuPortal = ContextMenuPrimitive.Portal
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Portal>) {
return (
<ContextMenuPrimitive.Portal data-slot="context-menu-portal" {...props} />
)
}
function ContextMenuSub({ const ContextMenuSub = ContextMenuPrimitive.Sub
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Sub>) {
return <ContextMenuPrimitive.Sub data-slot="context-menu-sub" {...props} />
}
function ContextMenuRadioGroup({ const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.RadioGroup>) {
return (
<ContextMenuPrimitive.RadioGroup
data-slot="context-menu-radio-group"
{...props}
/>
)
}
function ContextMenuSubTrigger({ const ContextMenuSubTrigger = React.forwardRef<
className, React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
inset, React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {
children,
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.SubTrigger> & {
inset?: boolean inset?: boolean
}) { }
return ( >(({ className, inset, children, ...props }, ref) => (
<ContextMenuPrimitive.SubTrigger <ContextMenuPrimitive.SubTrigger
data-slot="context-menu-sub-trigger" ref={ref}
data-inset={inset}
className={cn( className={cn(
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
inset && "pl-8",
className className
)} )}
{...props} {...props}
> >
{children} {children}
<ChevronRightIcon className="ml-auto" /> <ChevronRight className="ml-auto h-4 w-4" />
</ContextMenuPrimitive.SubTrigger> </ContextMenuPrimitive.SubTrigger>
) ))
} ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName
function ContextMenuSubContent({ const ContextMenuSubContent = React.forwardRef<
className, React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
...props React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>
}: React.ComponentProps<typeof ContextMenuPrimitive.SubContent>) { >(({ className, ...props }, ref) => (
return (
<ContextMenuPrimitive.SubContent <ContextMenuPrimitive.SubContent
data-slot="context-menu-sub-content" ref={ref}
className={cn( className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg", "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-context-menu-content-transform-origin]",
className className
)} )}
{...props} {...props}
/> />
) ))
} ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName
function ContextMenuContent({ const ContextMenuContent = React.forwardRef<
className, React.ElementRef<typeof ContextMenuPrimitive.Content>,
...props React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content>
}: React.ComponentProps<typeof ContextMenuPrimitive.Content>) { >(({ className, ...props }, ref) => (
return (
<ContextMenuPrimitive.Portal> <ContextMenuPrimitive.Portal>
<ContextMenuPrimitive.Content <ContextMenuPrimitive.Content
data-slot="context-menu-content" ref={ref}
className={cn( className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-context-menu-content-available-height) min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md", "z-50 max-h-[--radix-context-menu-content-available-height] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-context-menu-content-transform-origin]",
className className
)} )}
{...props} {...props}
/> />
</ContextMenuPrimitive.Portal> </ContextMenuPrimitive.Portal>
) ))
} ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName
function ContextMenuItem({ const ContextMenuItem = React.forwardRef<
className, React.ElementRef<typeof ContextMenuPrimitive.Item>,
inset, React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
variant = "default",
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Item> & {
inset?: boolean inset?: boolean
variant?: "default" | "destructive" }
}) { >(({ className, inset, ...props }, ref) => (
return (
<ContextMenuPrimitive.Item <ContextMenuPrimitive.Item
data-slot="context-menu-item" ref={ref}
data-inset={inset}
data-variant={variant}
className={cn( className={cn(
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
inset && "pl-8",
className className
)} )}
{...props} {...props}
/> />
) ))
} ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName
function ContextMenuCheckboxItem({ const ContextMenuCheckboxItem = React.forwardRef<
className, React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
children, React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem>
checked, >(({ className, children, checked, ...props }, ref) => (
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.CheckboxItem>) {
return (
<ContextMenuPrimitive.CheckboxItem <ContextMenuPrimitive.CheckboxItem
data-slot="context-menu-checkbox-item" ref={ref}
className={cn( className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className className
)} )}
checked={checked} checked={checked}
{...props} {...props}
> >
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center"> <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<ContextMenuPrimitive.ItemIndicator> <ContextMenuPrimitive.ItemIndicator>
<CheckIcon className="size-4" /> <Check className="h-4 w-4" />
</ContextMenuPrimitive.ItemIndicator> </ContextMenuPrimitive.ItemIndicator>
</span> </span>
{children} {children}
</ContextMenuPrimitive.CheckboxItem> </ContextMenuPrimitive.CheckboxItem>
) ))
} ContextMenuCheckboxItem.displayName =
ContextMenuPrimitive.CheckboxItem.displayName
function ContextMenuRadioItem({ const ContextMenuRadioItem = React.forwardRef<
className, React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,
children, React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem>
...props >(({ className, children, ...props }, ref) => (
}: React.ComponentProps<typeof ContextMenuPrimitive.RadioItem>) {
return (
<ContextMenuPrimitive.RadioItem <ContextMenuPrimitive.RadioItem
data-slot="context-menu-radio-item" ref={ref}
className={cn( className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className className
)} )}
{...props} {...props}
> >
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center"> <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<ContextMenuPrimitive.ItemIndicator> <ContextMenuPrimitive.ItemIndicator>
<CircleIcon className="size-2 fill-current" /> <Circle className="h-4 w-4 fill-current" />
</ContextMenuPrimitive.ItemIndicator> </ContextMenuPrimitive.ItemIndicator>
</span> </span>
{children} {children}
</ContextMenuPrimitive.RadioItem> </ContextMenuPrimitive.RadioItem>
) ))
} ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName
function ContextMenuLabel({ const ContextMenuLabel = React.forwardRef<
className, React.ElementRef<typeof ContextMenuPrimitive.Label>,
inset, React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Label> & {
inset?: boolean inset?: boolean
}) { }
return ( >(({ className, inset, ...props }, ref) => (
<ContextMenuPrimitive.Label <ContextMenuPrimitive.Label
data-slot="context-menu-label" ref={ref}
data-inset={inset}
className={cn( className={cn(
"text-foreground px-2 py-1.5 text-sm font-medium data-[inset]:pl-8", "px-2 py-1.5 text-sm font-semibold text-foreground",
inset && "pl-8",
className className
)} )}
{...props} {...props}
/> />
) ))
} ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName
function ContextMenuSeparator({ const ContextMenuSeparator = React.forwardRef<
className, React.ElementRef<typeof ContextMenuPrimitive.Separator>,
...props React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>
}: React.ComponentProps<typeof ContextMenuPrimitive.Separator>) { >(({ className, ...props }, ref) => (
return (
<ContextMenuPrimitive.Separator <ContextMenuPrimitive.Separator
data-slot="context-menu-separator" ref={ref}
className={cn("bg-border -mx-1 my-1 h-px", className)} className={cn("-mx-1 my-1 h-px bg-border", className)}
{...props} {...props}
/> />
) ))
} ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName
function ContextMenuShortcut({ const ContextMenuShortcut = ({
className, className,
...props ...props
}: React.ComponentProps<"span">) { }: React.HTMLAttributes<HTMLSpanElement>) => {
return ( return (
<span <span
data-slot="context-menu-shortcut"
className={cn( className={cn(
"text-muted-foreground ml-auto text-xs tracking-widest", "ml-auto text-xs tracking-widest text-muted-foreground",
className className
)} )}
{...props} {...props}
/> />
) )
} }
ContextMenuShortcut.displayName = "ContextMenuShortcut"
export { export {
ContextMenu, ContextMenu,

View File

@@ -1,141 +1,122 @@
"use client"
import * as React from "react" import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog" import * as DialogPrimitive from "@radix-ui/react-dialog"
import { XIcon } from "lucide-react" import { X } from "lucide-react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
function Dialog({ const Dialog = DialogPrimitive.Root
...props
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
return <DialogPrimitive.Root data-slot="dialog" {...props} />
}
function DialogTrigger({ const DialogTrigger = DialogPrimitive.Trigger
...props
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
}
function DialogPortal({ const DialogPortal = DialogPrimitive.Portal
...props
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
}
function DialogClose({ const DialogClose = DialogPrimitive.Close
...props
}: React.ComponentProps<typeof DialogPrimitive.Close>) {
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
}
function DialogOverlay({ const DialogOverlay = React.forwardRef<
className, React.ElementRef<typeof DialogPrimitive.Overlay>,
...props React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) { >(({ className, ...props }, ref) => (
return (
<DialogPrimitive.Overlay <DialogPrimitive.Overlay
data-slot="dialog-overlay" ref={ref}
className={cn( className={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50", "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className className
)} )}
{...props} {...props}
/> />
) ))
} DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
function DialogContent({ const DialogContent = React.forwardRef<
className, React.ElementRef<typeof DialogPrimitive.Content>,
children, React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
showCloseButton = true, >(({ className, children, ...props }, ref) => (
...props <DialogPortal>
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
showCloseButton?: boolean
}) {
return (
<DialogPortal data-slot="dialog-portal">
<DialogOverlay /> <DialogOverlay />
<DialogPrimitive.Content <DialogPrimitive.Content
data-slot="dialog-content" ref={ref}
className={cn( className={cn(
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg", "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 sm:rounded-lg",
className className
)} )}
{...props} {...props}
> >
{children} {children}
{showCloseButton && ( <DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<DialogPrimitive.Close <X className="h-4 w-4" />
data-slot="dialog-close"
className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
>
<XIcon />
<span className="sr-only">Close</span> <span className="sr-only">Close</span>
</DialogPrimitive.Close> </DialogPrimitive.Close>
)}
</DialogPrimitive.Content> </DialogPrimitive.Content>
</DialogPortal> </DialogPortal>
) ))
} DialogContent.displayName = DialogPrimitive.Content.displayName
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) { const DialogHeader = ({
return ( className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div <div
data-slot="dialog-header"
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
{...props}
/>
)
}
function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="dialog-footer"
className={cn( className={cn(
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", "flex flex-col space-y-1.5 text-center sm:text-left",
className className
)} )}
{...props} {...props}
/> />
) )
} DialogHeader.displayName = "DialogHeader"
function DialogTitle({ const DialogFooter = ({
className, className,
...props ...props
}: React.ComponentProps<typeof DialogPrimitive.Title>) { }: React.HTMLAttributes<HTMLDivElement>) => (
return ( <div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
DialogFooter.displayName = "DialogFooter"
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title <DialogPrimitive.Title
data-slot="dialog-title" ref={ref}
className={cn("text-lg leading-none font-semibold", className)} className={cn(
"text-lg font-semibold leading-none tracking-tight",
className
)}
{...props} {...props}
/> />
) ))
} DialogTitle.displayName = DialogPrimitive.Title.displayName
function DialogDescription({ const DialogDescription = React.forwardRef<
className, React.ElementRef<typeof DialogPrimitive.Description>,
...props React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
}: React.ComponentProps<typeof DialogPrimitive.Description>) { >(({ className, ...props }, ref) => (
return (
<DialogPrimitive.Description <DialogPrimitive.Description
data-slot="dialog-description" ref={ref}
className={cn("text-muted-foreground text-sm", className)} className={cn("text-sm text-muted-foreground", className)}
{...props} {...props}
/> />
) ))
} DialogDescription.displayName = DialogPrimitive.Description.displayName
export { export {
Dialog, Dialog,
DialogPortal,
DialogOverlay,
DialogTrigger,
DialogClose, DialogClose,
DialogContent, DialogContent,
DialogDescription,
DialogFooter,
DialogHeader, DialogHeader,
DialogOverlay, DialogFooter,
DialogPortal,
DialogTitle, DialogTitle,
DialogTrigger, DialogDescription,
} }

View File

@@ -3,121 +3,104 @@ import { Drawer as DrawerPrimitive } from "vaul"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
function Drawer({ const Drawer = ({
shouldScaleBackground = true,
...props ...props
}: React.ComponentProps<typeof DrawerPrimitive.Root>) { }: React.ComponentProps<typeof DrawerPrimitive.Root>) => (
return <DrawerPrimitive.Root data-slot="drawer" {...props} /> <DrawerPrimitive.Root
} shouldScaleBackground={shouldScaleBackground}
function DrawerTrigger({
...props
}: React.ComponentProps<typeof DrawerPrimitive.Trigger>) {
return <DrawerPrimitive.Trigger data-slot="drawer-trigger" {...props} />
}
function DrawerPortal({
...props
}: React.ComponentProps<typeof DrawerPrimitive.Portal>) {
return <DrawerPrimitive.Portal data-slot="drawer-portal" {...props} />
}
function DrawerClose({
...props
}: React.ComponentProps<typeof DrawerPrimitive.Close>) {
return <DrawerPrimitive.Close data-slot="drawer-close" {...props} />
}
function DrawerOverlay({
className,
...props
}: React.ComponentProps<typeof DrawerPrimitive.Overlay>) {
return (
<DrawerPrimitive.Overlay
data-slot="drawer-overlay"
className={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
className
)}
{...props} {...props}
/> />
) )
} Drawer.displayName = "Drawer"
function DrawerContent({ const DrawerTrigger = DrawerPrimitive.Trigger
className,
children, const DrawerPortal = DrawerPrimitive.Portal
...props
}: React.ComponentProps<typeof DrawerPrimitive.Content>) { const DrawerClose = DrawerPrimitive.Close
return (
<DrawerPortal data-slot="drawer-portal"> const DrawerOverlay = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DrawerPrimitive.Overlay
ref={ref}
className={cn("fixed inset-0 z-50 bg-black/80", className)}
{...props}
/>
))
DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName
const DrawerContent = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DrawerPortal>
<DrawerOverlay /> <DrawerOverlay />
<DrawerPrimitive.Content <DrawerPrimitive.Content
data-slot="drawer-content" ref={ref}
className={cn( className={cn(
"group/drawer-content bg-background fixed z-50 flex h-auto flex-col", "fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background",
"data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-lg data-[vaul-drawer-direction=top]:border-b",
"data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-lg data-[vaul-drawer-direction=bottom]:border-t",
"data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:border-l data-[vaul-drawer-direction=right]:sm:max-w-sm",
"data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:border-r data-[vaul-drawer-direction=left]:sm:max-w-sm",
className className
)} )}
{...props} {...props}
> >
<div className="bg-muted mx-auto mt-4 hidden h-2 w-[100px] shrink-0 rounded-full group-data-[vaul-drawer-direction=bottom]/drawer-content:block" /> <div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
{children} {children}
</DrawerPrimitive.Content> </DrawerPrimitive.Content>
</DrawerPortal> </DrawerPortal>
) ))
} DrawerContent.displayName = "DrawerContent"
function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) { const DrawerHeader = ({
return ( className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div <div
data-slot="drawer-header" className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)}
className={cn(
"flex flex-col gap-0.5 p-4 group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center group-data-[vaul-drawer-direction=top]/drawer-content:text-center md:gap-1.5 md:text-left",
className
)}
{...props} {...props}
/> />
) )
} DrawerHeader.displayName = "DrawerHeader"
function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) { const DrawerFooter = ({
return ( className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div <div
data-slot="drawer-footer"
className={cn("mt-auto flex flex-col gap-2 p-4", className)} className={cn("mt-auto flex flex-col gap-2 p-4", className)}
{...props} {...props}
/> />
) )
} DrawerFooter.displayName = "DrawerFooter"
function DrawerTitle({ const DrawerTitle = React.forwardRef<
className, React.ElementRef<typeof DrawerPrimitive.Title>,
...props React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
}: React.ComponentProps<typeof DrawerPrimitive.Title>) { >(({ className, ...props }, ref) => (
return (
<DrawerPrimitive.Title <DrawerPrimitive.Title
data-slot="drawer-title" ref={ref}
className={cn("text-foreground font-semibold", className)} className={cn(
"text-lg font-semibold leading-none tracking-tight",
className
)}
{...props} {...props}
/> />
) ))
} DrawerTitle.displayName = DrawerPrimitive.Title.displayName
function DrawerDescription({ const DrawerDescription = React.forwardRef<
className, React.ElementRef<typeof DrawerPrimitive.Description>,
...props React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
}: React.ComponentProps<typeof DrawerPrimitive.Description>) { >(({ className, ...props }, ref) => (
return (
<DrawerPrimitive.Description <DrawerPrimitive.Description
data-slot="drawer-description" ref={ref}
className={cn("text-muted-foreground text-sm", className)} className={cn("text-sm text-muted-foreground", className)}
{...props} {...props}
/> />
) ))
} DrawerDescription.displayName = DrawerPrimitive.Description.displayName
export { export {
Drawer, Drawer,

View File

@@ -2,256 +2,200 @@
import * as React from "react" import * as React from "react"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react" import { Check, ChevronRight, Circle } from "lucide-react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
function DropdownMenu({ const DropdownMenu = DropdownMenuPrimitive.Root
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />
}
function DropdownMenuPortal({ const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
return (
<DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
)
}
function DropdownMenuTrigger({ const DropdownMenuGroup = DropdownMenuPrimitive.Group
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) { const DropdownMenuPortal = DropdownMenuPrimitive.Portal
return (
<DropdownMenuPrimitive.Trigger const DropdownMenuSub = DropdownMenuPrimitive.Sub
data-slot="dropdown-menu-trigger"
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean
}
>(({ className, inset, children, ...props }, ref) => (
<DropdownMenuPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
inset && "pl-8",
className
)}
{...props}
>
{children}
<ChevronRight className="ml-auto" />
</DropdownMenuPrimitive.SubTrigger>
))
DropdownMenuSubTrigger.displayName =
DropdownMenuPrimitive.SubTrigger.displayName
const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]",
className
)}
{...props} {...props}
/> />
) ))
} DropdownMenuSubContent.displayName =
DropdownMenuPrimitive.SubContent.displayName
function DropdownMenuContent({ const DropdownMenuContent = React.forwardRef<
className, React.ElementRef<typeof DropdownMenuPrimitive.Content>,
sideOffset = 4, React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
...props >(({ className, sideOffset = 4, ...props }, ref) => (
}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
return (
<DropdownMenuPrimitive.Portal> <DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content <DropdownMenuPrimitive.Content
data-slot="dropdown-menu-content" ref={ref}
sideOffset={sideOffset} sideOffset={sideOffset}
className={cn( className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md", "z-50 max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]",
className className
)} )}
{...props} {...props}
/> />
</DropdownMenuPrimitive.Portal> </DropdownMenuPrimitive.Portal>
) ))
} DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
function DropdownMenuGroup({ const DropdownMenuItem = React.forwardRef<
...props React.ElementRef<typeof DropdownMenuPrimitive.Item>,
}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) { React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
return (
<DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
)
}
function DropdownMenuItem({
className,
inset,
variant = "default",
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean inset?: boolean
variant?: "default" | "destructive" }
}) { >(({ className, inset, ...props }, ref) => (
return (
<DropdownMenuPrimitive.Item <DropdownMenuPrimitive.Item
data-slot="dropdown-menu-item" ref={ref}
data-inset={inset}
data-variant={variant}
className={cn( className={cn(
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", "relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0",
inset && "pl-8",
className className
)} )}
{...props} {...props}
/> />
) ))
} DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
function DropdownMenuCheckboxItem({ const DropdownMenuCheckboxItem = React.forwardRef<
className, React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
children, React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
checked, >(({ className, children, checked, ...props }, ref) => (
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
return (
<DropdownMenuPrimitive.CheckboxItem <DropdownMenuPrimitive.CheckboxItem
data-slot="dropdown-menu-checkbox-item" ref={ref}
className={cn( className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className className
)} )}
checked={checked} checked={checked}
{...props} {...props}
> >
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center"> <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator> <DropdownMenuPrimitive.ItemIndicator>
<CheckIcon className="size-4" /> <Check className="h-4 w-4" />
</DropdownMenuPrimitive.ItemIndicator> </DropdownMenuPrimitive.ItemIndicator>
</span> </span>
{children} {children}
</DropdownMenuPrimitive.CheckboxItem> </DropdownMenuPrimitive.CheckboxItem>
) ))
} DropdownMenuCheckboxItem.displayName =
DropdownMenuPrimitive.CheckboxItem.displayName
function DropdownMenuRadioGroup({ const DropdownMenuRadioItem = React.forwardRef<
...props React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) { React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
return ( >(({ className, children, ...props }, ref) => (
<DropdownMenuPrimitive.RadioGroup
data-slot="dropdown-menu-radio-group"
{...props}
/>
)
}
function DropdownMenuRadioItem({
className,
children,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
return (
<DropdownMenuPrimitive.RadioItem <DropdownMenuPrimitive.RadioItem
data-slot="dropdown-menu-radio-item" ref={ref}
className={cn( className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className className
)} )}
{...props} {...props}
> >
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center"> <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator> <DropdownMenuPrimitive.ItemIndicator>
<CircleIcon className="size-2 fill-current" /> <Circle className="h-2 w-2 fill-current" />
</DropdownMenuPrimitive.ItemIndicator> </DropdownMenuPrimitive.ItemIndicator>
</span> </span>
{children} {children}
</DropdownMenuPrimitive.RadioItem> </DropdownMenuPrimitive.RadioItem>
) ))
} DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
function DropdownMenuLabel({ const DropdownMenuLabel = React.forwardRef<
className, React.ElementRef<typeof DropdownMenuPrimitive.Label>,
inset, React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean inset?: boolean
}) { }
return ( >(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label <DropdownMenuPrimitive.Label
data-slot="dropdown-menu-label" ref={ref}
data-inset={inset}
className={cn( className={cn(
"px-2 py-1.5 text-sm font-medium data-[inset]:pl-8", "px-2 py-1.5 text-sm font-semibold",
inset && "pl-8",
className className
)} )}
{...props} {...props}
/> />
) ))
} DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
function DropdownMenuSeparator({ const DropdownMenuSeparator = React.forwardRef<
className, React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
...props React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) { >(({ className, ...props }, ref) => (
return (
<DropdownMenuPrimitive.Separator <DropdownMenuPrimitive.Separator
data-slot="dropdown-menu-separator" ref={ref}
className={cn("bg-border -mx-1 my-1 h-px", className)} className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props} {...props}
/> />
) ))
} DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
function DropdownMenuShortcut({ const DropdownMenuShortcut = ({
className, className,
...props ...props
}: React.ComponentProps<"span">) { }: React.HTMLAttributes<HTMLSpanElement>) => {
return ( return (
<span <span
data-slot="dropdown-menu-shortcut" className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
className={cn(
"text-muted-foreground ml-auto text-xs tracking-widest",
className
)}
{...props}
/>
)
}
function DropdownMenuSub({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />
}
function DropdownMenuSubTrigger({
className,
inset,
children,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean
}) {
return (
<DropdownMenuPrimitive.SubTrigger
data-slot="dropdown-menu-sub-trigger"
data-inset={inset}
className={cn(
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8",
className
)}
{...props}
>
{children}
<ChevronRightIcon className="ml-auto size-4" />
</DropdownMenuPrimitive.SubTrigger>
)
}
function DropdownMenuSubContent({
className,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
return (
<DropdownMenuPrimitive.SubContent
data-slot="dropdown-menu-sub-content"
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
className
)}
{...props} {...props}
/> />
) )
} }
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
export { export {
DropdownMenu, DropdownMenu,
DropdownMenuPortal,
DropdownMenuTrigger, DropdownMenuTrigger,
DropdownMenuContent, DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuLabel,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuCheckboxItem, DropdownMenuCheckboxItem,
DropdownMenuRadioGroup,
DropdownMenuRadioItem, DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuShortcut, DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub, DropdownMenuSub,
DropdownMenuSubTrigger,
DropdownMenuSubContent, DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
} }

104
src/components/ui/empty.tsx Normal file
View File

@@ -0,0 +1,104 @@
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
function Empty({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="empty"
className={cn(
"flex min-w-0 flex-1 flex-col items-center justify-center gap-6 text-balance rounded-lg border-dashed p-6 text-center md:p-12",
className
)}
{...props}
/>
)
}
function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="empty-header"
className={cn(
"flex max-w-sm flex-col items-center gap-2 text-center",
className
)}
{...props}
/>
)
}
const emptyMediaVariants = cva(
"mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
{
variants: {
variant: {
default: "bg-transparent",
icon: "bg-muted text-foreground flex size-10 shrink-0 items-center justify-center rounded-lg [&_svg:not([class*='size-'])]:size-6",
},
},
defaultVariants: {
variant: "default",
},
}
)
function EmptyMedia({
className,
variant = "default",
...props
}: React.ComponentProps<"div"> & VariantProps<typeof emptyMediaVariants>) {
return (
<div
data-slot="empty-icon"
data-variant={variant}
className={cn(emptyMediaVariants({ variant, className }))}
{...props}
/>
)
}
function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="empty-title"
className={cn("text-lg font-medium tracking-tight", className)}
{...props}
/>
)
}
function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
return (
<div
data-slot="empty-description"
className={cn(
"text-muted-foreground [&>a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4",
className
)}
{...props}
/>
)
}
function EmptyContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="empty-content"
className={cn(
"flex w-full min-w-0 max-w-sm flex-col items-center gap-4 text-balance text-sm",
className
)}
{...props}
/>
)
}
export {
Empty,
EmptyHeader,
EmptyTitle,
EmptyDescription,
EmptyContent,
EmptyMedia,
}

244
src/components/ui/field.tsx Normal file
View File

@@ -0,0 +1,244 @@
"use client"
import { useMemo } from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
import { Label } from "@/components/ui/label"
import { Separator } from "@/components/ui/separator"
function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
return (
<fieldset
data-slot="field-set"
className={cn(
"flex flex-col gap-6",
"has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3",
className
)}
{...props}
/>
)
}
function FieldLegend({
className,
variant = "legend",
...props
}: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) {
return (
<legend
data-slot="field-legend"
data-variant={variant}
className={cn(
"mb-3 font-medium",
"data-[variant=legend]:text-base",
"data-[variant=label]:text-sm",
className
)}
{...props}
/>
)
}
function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="field-group"
className={cn(
"group/field-group @container/field-group flex w-full flex-col gap-7 data-[slot=checkbox-group]:gap-3 [&>[data-slot=field-group]]:gap-4",
className
)}
{...props}
/>
)
}
const fieldVariants = cva(
"group/field data-[invalid=true]:text-destructive flex w-full gap-3",
{
variants: {
orientation: {
vertical: ["flex-col [&>*]:w-full [&>.sr-only]:w-auto"],
horizontal: [
"flex-row items-center",
"[&>[data-slot=field-label]]:flex-auto",
"has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px has-[>[data-slot=field-content]]:items-start",
],
responsive: [
"@md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto flex-col [&>*]:w-full [&>.sr-only]:w-auto",
"@md/field-group:[&>[data-slot=field-label]]:flex-auto",
"@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
],
},
},
defaultVariants: {
orientation: "vertical",
},
}
)
function Field({
className,
orientation = "vertical",
...props
}: React.ComponentProps<"div"> & VariantProps<typeof fieldVariants>) {
return (
<div
role="group"
data-slot="field"
data-orientation={orientation}
className={cn(fieldVariants({ orientation }), className)}
{...props}
/>
)
}
function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="field-content"
className={cn(
"group/field-content flex flex-1 flex-col gap-1.5 leading-snug",
className
)}
{...props}
/>
)
}
function FieldLabel({
className,
...props
}: React.ComponentProps<typeof Label>) {
return (
<Label
data-slot="field-label"
className={cn(
"group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50",
"has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border [&>[data-slot=field]]:p-4",
"has-data-[state=checked]:bg-primary/5 has-data-[state=checked]:border-primary dark:has-data-[state=checked]:bg-primary/10",
className
)}
{...props}
/>
)
}
function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="field-label"
className={cn(
"flex w-fit items-center gap-2 text-sm font-medium leading-snug group-data-[disabled=true]/field:opacity-50",
className
)}
{...props}
/>
)
}
function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
return (
<p
data-slot="field-description"
className={cn(
"text-muted-foreground text-sm font-normal leading-normal group-has-[[data-orientation=horizontal]]/field:text-balance",
"nth-last-2:-mt-1 last:mt-0 [[data-variant=legend]+&]:-mt-1.5",
"[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4",
className
)}
{...props}
/>
)
}
function FieldSeparator({
children,
className,
...props
}: React.ComponentProps<"div"> & {
children?: React.ReactNode
}) {
return (
<div
data-slot="field-separator"
data-content={!!children}
className={cn(
"relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2",
className
)}
{...props}
>
<Separator className="absolute inset-0 top-1/2" />
{children && (
<span
className="bg-background text-muted-foreground relative mx-auto block w-fit px-2"
data-slot="field-separator-content"
>
{children}
</span>
)}
</div>
)
}
function FieldError({
className,
children,
errors,
...props
}: React.ComponentProps<"div"> & {
errors?: Array<{ message?: string } | undefined>
}) {
const content = useMemo(() => {
if (children) {
return children
}
if (!errors) {
return null
}
if (errors?.length === 1 && errors[0]?.message) {
return errors[0].message
}
return (
<ul className="ml-4 flex list-disc flex-col gap-1">
{errors.map(
(error, index) =>
error?.message && <li key={index}>{error.message}</li>
)}
</ul>
)
}, [children, errors])
if (!content) {
return null
}
return (
<div
role="alert"
data-slot="field-error"
className={cn("text-destructive text-sm font-normal", className)}
{...props}
>
{content}
</div>
)
}
export {
Field,
FieldLabel,
FieldDescription,
FieldError,
FieldGroup,
FieldLegend,
FieldSeparator,
FieldSet,
FieldContent,
FieldTitle,
}

View File

@@ -5,7 +5,6 @@ import {
Controller, Controller,
FormProvider, FormProvider,
useFormContext, useFormContext,
useFormState,
type ControllerProps, type ControllerProps,
type FieldPath, type FieldPath,
type FieldValues, type FieldValues,
@@ -18,18 +17,16 @@ const Form = FormProvider
type FormFieldContextValue< type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues, TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>, TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
> = { > = {
name: TName name: TName
} }
const FormFieldContext = React.createContext<FormFieldContextValue>( const FormFieldContext = React.createContext<FormFieldContextValue | null>(null)
{} as FormFieldContextValue
)
const FormField = < const FormField = <
TFieldValues extends FieldValues = FieldValues, TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>, TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
>({ >({
...props ...props
}: ControllerProps<TFieldValues, TName>) => { }: ControllerProps<TFieldValues, TName>) => {
@@ -43,14 +40,18 @@ const FormField = <
const useFormField = () => { const useFormField = () => {
const fieldContext = React.useContext(FormFieldContext) const fieldContext = React.useContext(FormFieldContext)
const itemContext = React.useContext(FormItemContext) const itemContext = React.useContext(FormItemContext)
const { getFieldState } = useFormContext() const { getFieldState, formState } = useFormContext()
const formState = useFormState({ name: fieldContext.name })
const fieldState = getFieldState(fieldContext.name, formState)
if (!fieldContext) { if (!fieldContext) {
throw new Error("useFormField should be used within <FormField>") throw new Error("useFormField should be used within <FormField>")
} }
if (!itemContext) {
throw new Error("useFormField should be used within <FormItem>")
}
const fieldState = getFieldState(fieldContext.name, formState)
const { id } = itemContext const { id } = itemContext
return { return {
@@ -67,47 +68,48 @@ type FormItemContextValue = {
id: string id: string
} }
const FormItemContext = React.createContext<FormItemContextValue>( const FormItemContext = React.createContext<FormItemContextValue | null>(null)
{} as FormItemContextValue
)
function FormItem({ className, ...props }: React.ComponentProps<"div">) { const FormItem = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const id = React.useId() const id = React.useId()
return ( return (
<FormItemContext.Provider value={{ id }}> <FormItemContext.Provider value={{ id }}>
<div <div ref={ref} className={cn("space-y-2", className)} {...props} />
data-slot="form-item"
className={cn("grid gap-2", className)}
{...props}
/>
</FormItemContext.Provider> </FormItemContext.Provider>
) )
} })
FormItem.displayName = "FormItem"
function FormLabel({ const FormLabel = React.forwardRef<
className, React.ElementRef<typeof LabelPrimitive.Root>,
...props React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
}: React.ComponentProps<typeof LabelPrimitive.Root>) { >(({ className, ...props }, ref) => {
const { error, formItemId } = useFormField() const { error, formItemId } = useFormField()
return ( return (
<Label <Label
data-slot="form-label" ref={ref}
data-error={!!error} className={cn(error && "text-destructive", className)}
className={cn("data-[error=true]:text-destructive", className)}
htmlFor={formItemId} htmlFor={formItemId}
{...props} {...props}
/> />
) )
} })
FormLabel.displayName = "FormLabel"
function FormControl({ ...props }: React.ComponentProps<typeof Slot>) { const FormControl = React.forwardRef<
React.ElementRef<typeof Slot>,
React.ComponentPropsWithoutRef<typeof Slot>
>(({ ...props }, ref) => {
const { error, formItemId, formDescriptionId, formMessageId } = useFormField() const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
return ( return (
<Slot <Slot
data-slot="form-control" ref={ref}
id={formItemId} id={formItemId}
aria-describedby={ aria-describedby={
!error !error
@@ -118,24 +120,32 @@ function FormControl({ ...props }: React.ComponentProps<typeof Slot>) {
{...props} {...props}
/> />
) )
} })
FormControl.displayName = "FormControl"
function FormDescription({ className, ...props }: React.ComponentProps<"p">) { const FormDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => {
const { formDescriptionId } = useFormField() const { formDescriptionId } = useFormField()
return ( return (
<p <p
data-slot="form-description" ref={ref}
id={formDescriptionId} id={formDescriptionId}
className={cn("text-muted-foreground text-sm", className)} className={cn("text-[0.8rem] text-muted-foreground", className)}
{...props} {...props}
/> />
) )
} })
FormDescription.displayName = "FormDescription"
function FormMessage({ className, ...props }: React.ComponentProps<"p">) { const FormMessage = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, children, ...props }, ref) => {
const { error, formMessageId } = useFormField() const { error, formMessageId } = useFormField()
const body = error ? String(error?.message ?? "") : props.children const body = error ? String(error?.message ?? "") : children
if (!body) { if (!body) {
return null return null
@@ -143,15 +153,16 @@ function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
return ( return (
<p <p
data-slot="form-message" ref={ref}
id={formMessageId} id={formMessageId}
className={cn("text-destructive text-sm", className)} className={cn("text-[0.8rem] font-medium text-destructive", className)}
{...props} {...props}
> >
{body} {body}
</p> </p>
) )
} })
FormMessage.displayName = "FormMessage"
export { export {
useFormField, useFormField,

View File

@@ -3,40 +3,25 @@ import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
function HoverCard({ const HoverCard = HoverCardPrimitive.Root
...props
}: React.ComponentProps<typeof HoverCardPrimitive.Root>) {
return <HoverCardPrimitive.Root data-slot="hover-card" {...props} />
}
function HoverCardTrigger({ const HoverCardTrigger = HoverCardPrimitive.Trigger
...props
}: React.ComponentProps<typeof HoverCardPrimitive.Trigger>) {
return (
<HoverCardPrimitive.Trigger data-slot="hover-card-trigger" {...props} />
)
}
function HoverCardContent({ const HoverCardContent = React.forwardRef<
className, React.ElementRef<typeof HoverCardPrimitive.Content>,
align = "center", React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>
sideOffset = 4, >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
...props
}: React.ComponentProps<typeof HoverCardPrimitive.Content>) {
return (
<HoverCardPrimitive.Portal data-slot="hover-card-portal">
<HoverCardPrimitive.Content <HoverCardPrimitive.Content
data-slot="hover-card-content" ref={ref}
align={align} align={align}
sideOffset={sideOffset} sideOffset={sideOffset}
className={cn( className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-64 origin-(--radix-hover-card-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden", "z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-hover-card-content-transform-origin]",
className className
)} )}
{...props} {...props}
/> />
</HoverCardPrimitive.Portal> ))
) HoverCardContent.displayName = HoverCardPrimitive.Content.displayName
}
export { HoverCard, HoverCardTrigger, HoverCardContent } export { HoverCard, HoverCardTrigger, HoverCardContent }

View File

@@ -0,0 +1,168 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Textarea } from "@/components/ui/textarea"
function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="input-group"
role="group"
className={cn(
"group/input-group border-input dark:bg-input/30 shadow-xs relative flex w-full items-center rounded-md border outline-none transition-[color,box-shadow]",
"h-9 has-[>textarea]:h-auto",
// Variants based on alignment.
"has-[>[data-align=inline-start]]:[&>input]:pl-2",
"has-[>[data-align=inline-end]]:[&>input]:pr-2",
"has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3",
"has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3",
// Focus state.
"has-[[data-slot=input-group-control]:focus-visible]:ring-ring has-[[data-slot=input-group-control]:focus-visible]:ring-1",
// Error state.
"has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[[data-slot][aria-invalid=true]]:border-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40",
className
)}
{...props}
/>
)
}
const inputGroupAddonVariants = cva(
"text-muted-foreground flex h-auto cursor-text select-none items-center justify-center gap-2 py-1.5 text-sm font-medium group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4",
{
variants: {
align: {
"inline-start":
"order-first pl-3 has-[>button]:ml-[-0.45rem] has-[>kbd]:ml-[-0.35rem]",
"inline-end":
"order-last pr-3 has-[>button]:mr-[-0.4rem] has-[>kbd]:mr-[-0.35rem]",
"block-start":
"[.border-b]:pb-3 order-first w-full justify-start px-3 pt-3 group-has-[>input]/input-group:pt-2.5",
"block-end":
"[.border-t]:pt-3 order-last w-full justify-start px-3 pb-3 group-has-[>input]/input-group:pb-2.5",
},
},
defaultVariants: {
align: "inline-start",
},
}
)
function InputGroupAddon({
className,
align = "inline-start",
...props
}: React.ComponentProps<"div"> & VariantProps<typeof inputGroupAddonVariants>) {
return (
<div
role="group"
data-slot="input-group-addon"
data-align={align}
className={cn(inputGroupAddonVariants({ align }), className)}
onClick={(e) => {
if ((e.target as HTMLElement).closest("button")) {
return
}
e.currentTarget.parentElement?.querySelector("input")?.focus()
}}
{...props}
/>
)
}
const inputGroupButtonVariants = cva(
"flex items-center gap-2 text-sm shadow-none",
{
variants: {
size: {
xs: "h-6 gap-1 rounded-[calc(var(--radius)-5px)] px-2 has-[>svg]:px-2 [&>svg:not([class*='size-'])]:size-3.5",
sm: "h-8 gap-1.5 rounded-md px-2.5 has-[>svg]:px-2.5",
"icon-xs":
"size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0",
"icon-sm": "size-8 p-0 has-[>svg]:p-0",
},
},
defaultVariants: {
size: "xs",
},
}
)
function InputGroupButton({
className,
type = "button",
variant = "ghost",
size = "xs",
...props
}: Omit<React.ComponentProps<typeof Button>, "size"> &
VariantProps<typeof inputGroupButtonVariants>) {
return (
<Button
type={type}
data-size={size}
variant={variant}
className={cn(inputGroupButtonVariants({ size }), className)}
{...props}
/>
)
}
function InputGroupText({ className, ...props }: React.ComponentProps<"span">) {
return (
<span
className={cn(
"text-muted-foreground flex items-center gap-2 text-sm [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none",
className
)}
{...props}
/>
)
}
function InputGroupInput({
className,
...props
}: React.ComponentProps<"input">) {
return (
<Input
data-slot="input-group-control"
className={cn(
"flex-1 rounded-none border-0 bg-transparent shadow-none focus-visible:ring-0 dark:bg-transparent",
className
)}
{...props}
/>
)
}
function InputGroupTextarea({
className,
...props
}: React.ComponentProps<"textarea">) {
return (
<Textarea
data-slot="input-group-control"
className={cn(
"flex-1 resize-none rounded-none border-0 bg-transparent py-3 shadow-none focus-visible:ring-0 dark:bg-transparent",
className
)}
{...props}
/>
)
}
export {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupText,
InputGroupInput,
InputGroupTextarea,
}

View File

@@ -1,57 +1,46 @@
"use client"
import * as React from "react" import * as React from "react"
import { OTPInput, OTPInputContext } from "input-otp" import { OTPInput, OTPInputContext } from "input-otp"
import { MinusIcon } from "lucide-react" import { Minus } from "lucide-react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
function InputOTP({ const InputOTP = React.forwardRef<
className, React.ElementRef<typeof OTPInput>,
containerClassName, React.ComponentPropsWithoutRef<typeof OTPInput>
...props >(({ className, containerClassName, ...props }, ref) => (
}: React.ComponentProps<typeof OTPInput> & {
containerClassName?: string
}) {
return (
<OTPInput <OTPInput
data-slot="input-otp" ref={ref}
containerClassName={cn( containerClassName={cn(
"flex items-center gap-2 has-disabled:opacity-50", "flex items-center gap-2 has-[:disabled]:opacity-50",
containerClassName containerClassName
)} )}
className={cn("disabled:cursor-not-allowed", className)} className={cn("disabled:cursor-not-allowed", className)}
{...props} {...props}
/> />
) ))
} InputOTP.displayName = "InputOTP"
function InputOTPGroup({ className, ...props }: React.ComponentProps<"div">) { const InputOTPGroup = React.forwardRef<
return ( React.ElementRef<"div">,
<div React.ComponentPropsWithoutRef<"div">
data-slot="input-otp-group" >(({ className, ...props }, ref) => (
className={cn("flex items-center", className)} <div ref={ref} className={cn("flex items-center", className)} {...props} />
{...props} ))
/> InputOTPGroup.displayName = "InputOTPGroup"
)
}
function InputOTPSlot({ const InputOTPSlot = React.forwardRef<
index, React.ElementRef<"div">,
className, React.ComponentPropsWithoutRef<"div"> & { index: number }
...props >(({ index, className, ...props }, ref) => {
}: React.ComponentProps<"div"> & {
index: number
}) {
const inputOTPContext = React.useContext(OTPInputContext) const inputOTPContext = React.useContext(OTPInputContext)
const { char, hasFakeCaret, isActive } = inputOTPContext?.slots[index] ?? {} const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index]
return ( return (
<div <div
data-slot="input-otp-slot" ref={ref}
data-active={isActive}
className={cn( className={cn(
"data-[active=true]:border-ring data-[active=true]:ring-ring/50 data-[active=true]:aria-invalid:ring-destructive/20 dark:data-[active=true]:aria-invalid:ring-destructive/40 aria-invalid:border-destructive data-[active=true]:aria-invalid:border-destructive dark:bg-input/30 border-input relative flex h-9 w-9 items-center justify-center border-y border-r text-sm shadow-xs transition-all outline-none first:rounded-l-md first:border-l last:rounded-r-md data-[active=true]:z-10 data-[active=true]:ring-[3px]", "relative flex h-9 w-9 items-center justify-center border-y border-r border-input text-sm shadow-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md",
isActive && "z-10 ring-1 ring-ring",
className className
)} )}
{...props} {...props}
@@ -59,19 +48,22 @@ function InputOTPSlot({
{char} {char}
{hasFakeCaret && ( {hasFakeCaret && (
<div className="pointer-events-none absolute inset-0 flex items-center justify-center"> <div className="pointer-events-none absolute inset-0 flex items-center justify-center">
<div className="animate-caret-blink bg-foreground h-4 w-px duration-1000" /> <div className="h-4 w-px animate-caret-blink bg-foreground duration-1000" />
</div> </div>
)} )}
</div> </div>
) )
} })
InputOTPSlot.displayName = "InputOTPSlot"
function InputOTPSeparator({ ...props }: React.ComponentProps<"div">) { const InputOTPSeparator = React.forwardRef<
return ( React.ElementRef<"div">,
<div data-slot="input-otp-separator" role="separator" {...props}> React.ComponentPropsWithoutRef<"div">
<MinusIcon /> >(({ ...props }, ref) => (
<div ref={ref} role="separator" {...props}>
<Minus />
</div> </div>
) ))
} InputOTPSeparator.displayName = "InputOTPSeparator"
export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator } export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }

View File

@@ -2,20 +2,21 @@ import * as React from "react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
function Input({ className, type, ...props }: React.ComponentProps<"input">) { const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
({ className, type, ...props }, ref) => {
return ( return (
<input <input
type={type} type={type}
data-slot="input"
className={cn( className={cn(
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
className className
)} )}
ref={ref}
{...props} {...props}
/> />
) )
} }
)
Input.displayName = "Input"
export { Input } export { Input }

193
src/components/ui/item.tsx Normal file
View File

@@ -0,0 +1,193 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
import { Separator } from "@/components/ui/separator"
function ItemGroup({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
role="list"
data-slot="item-group"
className={cn("group/item-group flex flex-col", className)}
{...props}
/>
)
}
function ItemSeparator({
className,
...props
}: React.ComponentProps<typeof Separator>) {
return (
<Separator
data-slot="item-separator"
orientation="horizontal"
className={cn("my-0", className)}
{...props}
/>
)
}
const itemVariants = cva(
"group/item [a]:hover:bg-accent/50 focus-visible:border-ring focus-visible:ring-ring/50 [a]:transition-colors flex flex-wrap items-center rounded-md border border-transparent text-sm outline-none transition-colors duration-100 focus-visible:ring-[3px]",
{
variants: {
variant: {
default: "bg-transparent",
outline: "border-border",
muted: "bg-muted/50",
},
size: {
default: "gap-4 p-4 ",
sm: "gap-2.5 px-4 py-3",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
function Item({
className,
variant = "default",
size = "default",
asChild = false,
...props
}: React.ComponentProps<"div"> &
VariantProps<typeof itemVariants> & { asChild?: boolean }) {
const Comp = asChild ? Slot : "div"
return (
<Comp
data-slot="item"
data-variant={variant}
data-size={size}
className={cn(itemVariants({ variant, size, className }))}
{...props}
/>
)
}
const itemMediaVariants = cva(
"flex shrink-0 items-center justify-center gap-2 group-has-[[data-slot=item-description]]/item:translate-y-0.5 group-has-[[data-slot=item-description]]/item:self-start [&_svg]:pointer-events-none",
{
variants: {
variant: {
default: "bg-transparent",
icon: "bg-muted size-8 rounded-sm border [&_svg:not([class*='size-'])]:size-4",
image:
"size-10 overflow-hidden rounded-sm [&_img]:size-full [&_img]:object-cover",
},
},
defaultVariants: {
variant: "default",
},
}
)
function ItemMedia({
className,
variant = "default",
...props
}: React.ComponentProps<"div"> & VariantProps<typeof itemMediaVariants>) {
return (
<div
data-slot="item-media"
data-variant={variant}
className={cn(itemMediaVariants({ variant, className }))}
{...props}
/>
)
}
function ItemContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="item-content"
className={cn(
"flex flex-1 flex-col gap-1 [&+[data-slot=item-content]]:flex-none",
className
)}
{...props}
/>
)
}
function ItemTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="item-title"
className={cn(
"flex w-fit items-center gap-2 text-sm font-medium leading-snug",
className
)}
{...props}
/>
)
}
function ItemDescription({ className, ...props }: React.ComponentProps<"p">) {
return (
<p
data-slot="item-description"
className={cn(
"text-muted-foreground line-clamp-2 text-balance text-sm font-normal leading-normal",
"[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4",
className
)}
{...props}
/>
)
}
function ItemActions({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="item-actions"
className={cn("flex items-center gap-2", className)}
{...props}
/>
)
}
function ItemHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="item-header"
className={cn(
"flex basis-full items-center justify-between gap-2",
className
)}
{...props}
/>
)
}
function ItemFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="item-footer"
className={cn(
"flex basis-full items-center justify-between gap-2",
className
)}
{...props}
/>
)
}
export {
Item,
ItemMedia,
ItemContent,
ItemActions,
ItemGroup,
ItemSeparator,
ItemTitle,
ItemDescription,
ItemHeader,
ItemFooter,
}

28
src/components/ui/kbd.tsx Normal file
View File

@@ -0,0 +1,28 @@
import { cn } from "@/lib/utils"
function Kbd({ className, ...props }: React.ComponentProps<"kbd">) {
return (
<kbd
data-slot="kbd"
className={cn(
"bg-muted text-muted-foreground pointer-events-none inline-flex h-5 w-fit min-w-5 select-none items-center justify-center gap-1 rounded-sm px-1 font-sans text-xs font-medium",
"[&_svg:not([class*='size-'])]:size-3",
"[[data-slot=tooltip-content]_&]:bg-background/20 [[data-slot=tooltip-content]_&]:text-background dark:[[data-slot=tooltip-content]_&]:bg-background/10",
className
)}
{...props}
/>
)
}
function KbdGroup({ className, ...props }: React.ComponentProps<"div">) {
return (
<kbd
data-slot="kbd-group"
className={cn("inline-flex items-center gap-1", className)}
{...props}
/>
)
}
export { Kbd, KbdGroup }

View File

@@ -2,23 +2,25 @@
import * as React from "react" import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label" import * as LabelPrimitive from "@radix-ui/react-label"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
function Label({ const labelVariants = cva(
className, "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
...props )
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
return ( const Label = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => (
<LabelPrimitive.Root <LabelPrimitive.Root
data-slot="label" ref={ref}
className={cn( className={cn(labelVariants(), className)}
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
className
)}
{...props} {...props}
/> />
) ))
} Label.displayName = LabelPrimitive.Root.displayName
export { Label } export { Label }

View File

@@ -1,211 +1,31 @@
import * as React from "react" import * as React from "react"
import * as MenubarPrimitive from "@radix-ui/react-menubar" import * as MenubarPrimitive from "@radix-ui/react-menubar"
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react" import { Check, ChevronRight, Circle } from "lucide-react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
function Menubar({
className,
...props
}: React.ComponentProps<typeof MenubarPrimitive.Root>) {
return (
<MenubarPrimitive.Root
data-slot="menubar"
className={cn(
"bg-background flex h-9 items-center gap-1 rounded-md border p-1 shadow-xs",
className
)}
{...props}
/>
)
}
function MenubarMenu({ function MenubarMenu({
...props ...props
}: React.ComponentProps<typeof MenubarPrimitive.Menu>) { }: React.ComponentProps<typeof MenubarPrimitive.Menu>) {
return <MenubarPrimitive.Menu data-slot="menubar-menu" {...props} /> return <MenubarPrimitive.Menu {...props} />
} }
function MenubarGroup({ function MenubarGroup({
...props ...props
}: React.ComponentProps<typeof MenubarPrimitive.Group>) { }: React.ComponentProps<typeof MenubarPrimitive.Group>) {
return <MenubarPrimitive.Group data-slot="menubar-group" {...props} /> return <MenubarPrimitive.Group {...props} />
} }
function MenubarPortal({ function MenubarPortal({
...props ...props
}: React.ComponentProps<typeof MenubarPrimitive.Portal>) { }: React.ComponentProps<typeof MenubarPrimitive.Portal>) {
return <MenubarPrimitive.Portal data-slot="menubar-portal" {...props} /> return <MenubarPrimitive.Portal {...props} />
} }
function MenubarRadioGroup({ function MenubarRadioGroup({
...props ...props
}: React.ComponentProps<typeof MenubarPrimitive.RadioGroup>) { }: React.ComponentProps<typeof MenubarPrimitive.RadioGroup>) {
return ( return <MenubarPrimitive.RadioGroup {...props} />
<MenubarPrimitive.RadioGroup data-slot="menubar-radio-group" {...props} />
)
}
function MenubarTrigger({
className,
...props
}: React.ComponentProps<typeof MenubarPrimitive.Trigger>) {
return (
<MenubarPrimitive.Trigger
data-slot="menubar-trigger"
className={cn(
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex items-center rounded-sm px-2 py-1 text-sm font-medium outline-hidden select-none",
className
)}
{...props}
/>
)
}
function MenubarContent({
className,
align = "start",
alignOffset = -4,
sideOffset = 8,
...props
}: React.ComponentProps<typeof MenubarPrimitive.Content>) {
return (
<MenubarPortal>
<MenubarPrimitive.Content
data-slot="menubar-content"
align={align}
alignOffset={alignOffset}
sideOffset={sideOffset}
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[12rem] origin-(--radix-menubar-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-md",
className
)}
{...props}
/>
</MenubarPortal>
)
}
function MenubarItem({
className,
inset,
variant = "default",
...props
}: React.ComponentProps<typeof MenubarPrimitive.Item> & {
inset?: boolean
variant?: "default" | "destructive"
}) {
return (
<MenubarPrimitive.Item
data-slot="menubar-item"
data-inset={inset}
data-variant={variant}
className={cn(
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
/>
)
}
function MenubarCheckboxItem({
className,
children,
checked,
...props
}: React.ComponentProps<typeof MenubarPrimitive.CheckboxItem>) {
return (
<MenubarPrimitive.CheckboxItem
data-slot="menubar-checkbox-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-xs py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
checked={checked}
{...props}
>
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<MenubarPrimitive.ItemIndicator>
<CheckIcon className="size-4" />
</MenubarPrimitive.ItemIndicator>
</span>
{children}
</MenubarPrimitive.CheckboxItem>
)
}
function MenubarRadioItem({
className,
children,
...props
}: React.ComponentProps<typeof MenubarPrimitive.RadioItem>) {
return (
<MenubarPrimitive.RadioItem
data-slot="menubar-radio-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-xs py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
>
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<MenubarPrimitive.ItemIndicator>
<CircleIcon className="size-2 fill-current" />
</MenubarPrimitive.ItemIndicator>
</span>
{children}
</MenubarPrimitive.RadioItem>
)
}
function MenubarLabel({
className,
inset,
...props
}: React.ComponentProps<typeof MenubarPrimitive.Label> & {
inset?: boolean
}) {
return (
<MenubarPrimitive.Label
data-slot="menubar-label"
data-inset={inset}
className={cn(
"px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
className
)}
{...props}
/>
)
}
function MenubarSeparator({
className,
...props
}: React.ComponentProps<typeof MenubarPrimitive.Separator>) {
return (
<MenubarPrimitive.Separator
data-slot="menubar-separator"
className={cn("bg-border -mx-1 my-1 h-px", className)}
{...props}
/>
)
}
function MenubarShortcut({
className,
...props
}: React.ComponentProps<"span">) {
return (
<span
data-slot="menubar-shortcut"
className={cn(
"text-muted-foreground ml-auto text-xs tracking-widest",
className
)}
{...props}
/>
)
} }
function MenubarSub({ function MenubarSub({
@@ -214,61 +34,221 @@ function MenubarSub({
return <MenubarPrimitive.Sub data-slot="menubar-sub" {...props} /> return <MenubarPrimitive.Sub data-slot="menubar-sub" {...props} />
} }
function MenubarSubTrigger({ const Menubar = React.forwardRef<
className, React.ElementRef<typeof MenubarPrimitive.Root>,
inset, React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Root>
children, >(({ className, ...props }, ref) => (
...props <MenubarPrimitive.Root
}: React.ComponentProps<typeof MenubarPrimitive.SubTrigger> & { ref={ref}
inset?: boolean
}) {
return (
<MenubarPrimitive.SubTrigger
data-slot="menubar-sub-trigger"
data-inset={inset}
className={cn( className={cn(
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-none select-none data-[inset]:pl-8", "flex h-9 items-center space-x-1 rounded-md border bg-background p-1 shadow-sm",
className
)}
{...props}
/>
))
Menubar.displayName = MenubarPrimitive.Root.displayName
const MenubarTrigger = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<MenubarPrimitive.Trigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center rounded-sm px-3 py-1 text-sm font-medium outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
className
)}
{...props}
/>
))
MenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName
const MenubarSubTrigger = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubTrigger> & {
inset?: boolean
}
>(({ className, inset, children, ...props }, ref) => (
<MenubarPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
inset && "pl-8",
className className
)} )}
{...props} {...props}
> >
{children} {children}
<ChevronRightIcon className="ml-auto h-4 w-4" /> <ChevronRight className="ml-auto h-4 w-4" />
</MenubarPrimitive.SubTrigger> </MenubarPrimitive.SubTrigger>
) ))
} MenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName
function MenubarSubContent({ const MenubarSubContent = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<MenubarPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-menubar-content-transform-origin]",
className
)}
{...props}
/>
))
MenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName
const MenubarContent = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Content>
>(
(
{ className, align = "start", alignOffset = -4, sideOffset = 8, ...props },
ref
) => (
<MenubarPrimitive.Portal>
<MenubarPrimitive.Content
ref={ref}
align={align}
alignOffset={alignOffset}
sideOffset={sideOffset}
className={cn(
"z-50 min-w-[12rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-menubar-content-transform-origin]",
className
)}
{...props}
/>
</MenubarPrimitive.Portal>
)
)
MenubarContent.displayName = MenubarPrimitive.Content.displayName
const MenubarItem = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Item> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<MenubarPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
inset && "pl-8",
className
)}
{...props}
/>
))
MenubarItem.displayName = MenubarPrimitive.Item.displayName
const MenubarCheckboxItem = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<MenubarPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<MenubarPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</MenubarPrimitive.ItemIndicator>
</span>
{children}
</MenubarPrimitive.CheckboxItem>
))
MenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName
const MenubarRadioItem = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<MenubarPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<MenubarPrimitive.ItemIndicator>
<Circle className="h-4 w-4 fill-current" />
</MenubarPrimitive.ItemIndicator>
</span>
{children}
</MenubarPrimitive.RadioItem>
))
MenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName
const MenubarLabel = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Label> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<MenubarPrimitive.Label
ref={ref}
className={cn(
"px-2 py-1.5 text-sm font-semibold",
inset && "pl-8",
className
)}
{...props}
/>
))
MenubarLabel.displayName = MenubarPrimitive.Label.displayName
const MenubarSeparator = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Separator>
>(({ className, ...props }, ref) => (
<MenubarPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
MenubarSeparator.displayName = MenubarPrimitive.Separator.displayName
const MenubarShortcut = ({
className, className,
...props ...props
}: React.ComponentProps<typeof MenubarPrimitive.SubContent>) { }: React.HTMLAttributes<HTMLSpanElement>) => {
return ( return (
<MenubarPrimitive.SubContent <span
data-slot="menubar-sub-content"
className={cn( className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-menubar-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg", "ml-auto text-xs tracking-widest text-muted-foreground",
className className
)} )}
{...props} {...props}
/> />
) )
} }
MenubarShortcut.displayname = "MenubarShortcut"
export { export {
Menubar, Menubar,
MenubarPortal,
MenubarMenu, MenubarMenu,
MenubarTrigger, MenubarTrigger,
MenubarContent, MenubarContent,
MenubarGroup, MenubarItem,
MenubarSeparator, MenubarSeparator,
MenubarLabel, MenubarLabel,
MenubarItem,
MenubarShortcut,
MenubarCheckboxItem, MenubarCheckboxItem,
MenubarRadioGroup, MenubarRadioGroup,
MenubarRadioItem, MenubarRadioItem,
MenubarSub, MenubarPortal,
MenubarSubTrigger,
MenubarSubContent, MenubarSubContent,
MenubarSubTrigger,
MenubarGroup,
MenubarSub,
MenubarShortcut,
} }

View File

@@ -1,161 +1,122 @@
import * as React from "react" import * as React from "react"
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu" import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
import { cva } from "class-variance-authority" import { cva } from "class-variance-authority"
import { ChevronDownIcon } from "lucide-react" import { ChevronDown } from "lucide-react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
function NavigationMenu({ const NavigationMenu = React.forwardRef<
className, React.ElementRef<typeof NavigationMenuPrimitive.Root>,
children, React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
viewport = true, >(({ className, children, ...props }, ref) => (
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Root> & {
viewport?: boolean
}) {
return (
<NavigationMenuPrimitive.Root <NavigationMenuPrimitive.Root
data-slot="navigation-menu" ref={ref}
data-viewport={viewport}
className={cn( className={cn(
"group/navigation-menu relative flex max-w-max flex-1 items-center justify-center", "relative z-10 flex max-w-max flex-1 items-center justify-center",
className className
)} )}
{...props} {...props}
> >
{children} {children}
{viewport && <NavigationMenuViewport />} <NavigationMenuViewport />
</NavigationMenuPrimitive.Root> </NavigationMenuPrimitive.Root>
) ))
} NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
function NavigationMenuList({ const NavigationMenuList = React.forwardRef<
className, React.ElementRef<typeof NavigationMenuPrimitive.List>,
...props React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
}: React.ComponentProps<typeof NavigationMenuPrimitive.List>) { >(({ className, ...props }, ref) => (
return (
<NavigationMenuPrimitive.List <NavigationMenuPrimitive.List
data-slot="navigation-menu-list" ref={ref}
className={cn( className={cn(
"group flex flex-1 list-none items-center justify-center gap-1", "group flex flex-1 list-none items-center justify-center space-x-1",
className className
)} )}
{...props} {...props}
/> />
) ))
} NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
function NavigationMenuItem({ const NavigationMenuItem = NavigationMenuPrimitive.Item
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Item>) {
return (
<NavigationMenuPrimitive.Item
data-slot="navigation-menu-item"
className={cn("relative", className)}
{...props}
/>
)
}
const navigationMenuTriggerStyle = cva( const navigationMenuTriggerStyle = cva(
"group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=open]:hover:bg-accent data-[state=open]:text-accent-foreground data-[state=open]:focus:bg-accent data-[state=open]:bg-accent/50 focus-visible:ring-ring/50 outline-none transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1" "group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[state=open]:text-accent-foreground data-[state=open]:bg-accent/50 data-[state=open]:hover:bg-accent data-[state=open]:focus:bg-accent"
) )
function NavigationMenuTrigger({ const NavigationMenuTrigger = React.forwardRef<
className, React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
children, React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
...props >(({ className, children, ...props }, ref) => (
}: React.ComponentProps<typeof NavigationMenuPrimitive.Trigger>) {
return (
<NavigationMenuPrimitive.Trigger <NavigationMenuPrimitive.Trigger
data-slot="navigation-menu-trigger" ref={ref}
className={cn(navigationMenuTriggerStyle(), "group", className)} className={cn(navigationMenuTriggerStyle(), "group", className)}
{...props} {...props}
> >
{children}{" "} {children}{" "}
<ChevronDownIcon <ChevronDown
className="relative top-[1px] ml-1 size-3 transition duration-300 group-data-[state=open]:rotate-180" className="relative top-[1px] ml-1 h-3 w-3 transition duration-300 group-data-[state=open]:rotate-180"
aria-hidden="true" aria-hidden="true"
/> />
</NavigationMenuPrimitive.Trigger> </NavigationMenuPrimitive.Trigger>
) ))
} NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
function NavigationMenuContent({ const NavigationMenuContent = React.forwardRef<
className, React.ElementRef<typeof NavigationMenuPrimitive.Content>,
...props React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
}: React.ComponentProps<typeof NavigationMenuPrimitive.Content>) { >(({ className, ...props }, ref) => (
return (
<NavigationMenuPrimitive.Content <NavigationMenuPrimitive.Content
data-slot="navigation-menu-content" ref={ref}
className={cn( className={cn(
"data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 top-0 left-0 w-full p-2 pr-2.5 md:absolute md:w-auto", "left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto ",
"group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:data-[state=open]:animate-in group-data-[viewport=false]/navigation-menu:data-[state=closed]:animate-out group-data-[viewport=false]/navigation-menu:data-[state=closed]:zoom-out-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:fade-in-0 group-data-[viewport=false]/navigation-menu:data-[state=closed]:fade-out-0 group-data-[viewport=false]/navigation-menu:top-full group-data-[viewport=false]/navigation-menu:mt-1.5 group-data-[viewport=false]/navigation-menu:overflow-hidden group-data-[viewport=false]/navigation-menu:rounded-md group-data-[viewport=false]/navigation-menu:border group-data-[viewport=false]/navigation-menu:shadow group-data-[viewport=false]/navigation-menu:duration-200 **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none",
className className
)} )}
{...props} {...props}
/> />
) ))
} NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
function NavigationMenuViewport({ const NavigationMenuLink = NavigationMenuPrimitive.Link
className,
...props const NavigationMenuViewport = React.forwardRef<
}: React.ComponentProps<typeof NavigationMenuPrimitive.Viewport>) { React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
return ( React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
<div >(({ className, ...props }, ref) => (
className={cn( <div className={cn("absolute left-0 top-full flex justify-center")}>
"absolute top-full left-0 isolate z-50 flex justify-center"
)}
>
<NavigationMenuPrimitive.Viewport <NavigationMenuPrimitive.Viewport
data-slot="navigation-menu-viewport"
className={cn( className={cn(
"origin-top-center bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border shadow md:w-[var(--radix-navigation-menu-viewport-width)]", "origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]",
className className
)} )}
ref={ref}
{...props} {...props}
/> />
</div> </div>
) ))
} NavigationMenuViewport.displayName =
NavigationMenuPrimitive.Viewport.displayName
function NavigationMenuLink({ const NavigationMenuIndicator = React.forwardRef<
className, React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
...props React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>
}: React.ComponentProps<typeof NavigationMenuPrimitive.Link>) { >(({ className, ...props }, ref) => (
return (
<NavigationMenuPrimitive.Link
data-slot="navigation-menu-link"
className={cn(
"data-[active=true]:focus:bg-accent data-[active=true]:hover:bg-accent data-[active=true]:bg-accent/50 data-[active=true]:text-accent-foreground hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus-visible:ring-ring/50 [&_svg:not([class*='text-'])]:text-muted-foreground flex flex-col gap-1 rounded-sm p-2 text-sm transition-all outline-none focus-visible:ring-[3px] focus-visible:outline-1 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
/>
)
}
function NavigationMenuIndicator({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Indicator>) {
return (
<NavigationMenuPrimitive.Indicator <NavigationMenuPrimitive.Indicator
data-slot="navigation-menu-indicator" ref={ref}
className={cn( className={cn(
"data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden", "top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in",
className className
)} )}
{...props} {...props}
> >
<div className="bg-border relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm shadow-md" /> <div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
</NavigationMenuPrimitive.Indicator> </NavigationMenuPrimitive.Indicator>
) ))
} NavigationMenuIndicator.displayName =
NavigationMenuPrimitive.Indicator.displayName
export { export {
navigationMenuTriggerStyle,
NavigationMenu, NavigationMenu,
NavigationMenuList, NavigationMenuList,
NavigationMenuItem, NavigationMenuItem,
@@ -164,5 +125,4 @@ export {
NavigationMenuLink, NavigationMenuLink,
NavigationMenuIndicator, NavigationMenuIndicator,
NavigationMenuViewport, NavigationMenuViewport,
navigationMenuTriggerStyle,
} }

View File

@@ -1,58 +1,52 @@
import * as React from "react" import * as React from "react"
import { import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react"
ChevronLeftIcon,
ChevronRightIcon,
MoreHorizontalIcon,
} from "lucide-react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
import { Button, buttonVariants } from "@/components/ui/button" import { ButtonProps, buttonVariants } from "@/components/ui/button"
function Pagination({ className, ...props }: React.ComponentProps<"nav">) { const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
return (
<nav <nav
role="navigation" role="navigation"
aria-label="pagination" aria-label="pagination"
data-slot="pagination"
className={cn("mx-auto flex w-full justify-center", className)} className={cn("mx-auto flex w-full justify-center", className)}
{...props} {...props}
/> />
) )
} Pagination.displayName = "Pagination"
function PaginationContent({ const PaginationContent = React.forwardRef<
className, HTMLUListElement,
...props React.ComponentProps<"ul">
}: React.ComponentProps<"ul">) { >(({ className, ...props }, ref) => (
return (
<ul <ul
data-slot="pagination-content" ref={ref}
className={cn("flex flex-row items-center gap-1", className)} className={cn("flex flex-row items-center gap-1", className)}
{...props} {...props}
/> />
) ))
} PaginationContent.displayName = "PaginationContent"
function PaginationItem({ ...props }: React.ComponentProps<"li">) { const PaginationItem = React.forwardRef<
return <li data-slot="pagination-item" {...props} /> HTMLLIElement,
} React.ComponentProps<"li">
>(({ className, ...props }, ref) => (
<li ref={ref} className={cn("", className)} {...props} />
))
PaginationItem.displayName = "PaginationItem"
type PaginationLinkProps = { type PaginationLinkProps = {
isActive?: boolean isActive?: boolean
} & Pick<React.ComponentProps<typeof Button>, "size"> & } & Pick<ButtonProps, "size"> &
React.ComponentProps<"a"> React.ComponentProps<"a">
function PaginationLink({ const PaginationLink = ({
className, className,
isActive, isActive,
size = "icon", size = "icon",
...props ...props
}: PaginationLinkProps) { }: PaginationLinkProps) => (
return (
<a <a
aria-current={isActive ? "page" : undefined} aria-current={isActive ? "page" : undefined}
data-slot="pagination-link"
data-active={isActive}
className={cn( className={cn(
buttonVariants({ buttonVariants({
variant: isActive ? "outline" : "ghost", variant: isActive ? "outline" : "ghost",
@@ -63,58 +57,54 @@ function PaginationLink({
{...props} {...props}
/> />
) )
} PaginationLink.displayName = "PaginationLink"
function PaginationPrevious({ const PaginationPrevious = ({
className, className,
...props ...props
}: React.ComponentProps<typeof PaginationLink>) { }: React.ComponentProps<typeof PaginationLink>) => (
return (
<PaginationLink <PaginationLink
aria-label="Go to previous page" aria-label="Go to previous page"
size="default" size="default"
className={cn("gap-1 px-2.5 sm:pl-2.5", className)} className={cn("gap-1 pl-2.5", className)}
{...props} {...props}
> >
<ChevronLeftIcon /> <ChevronLeft className="h-4 w-4" />
<span className="hidden sm:block">Previous</span> <span>Previous</span>
</PaginationLink> </PaginationLink>
) )
} PaginationPrevious.displayName = "PaginationPrevious"
function PaginationNext({ const PaginationNext = ({
className, className,
...props ...props
}: React.ComponentProps<typeof PaginationLink>) { }: React.ComponentProps<typeof PaginationLink>) => (
return (
<PaginationLink <PaginationLink
aria-label="Go to next page" aria-label="Go to next page"
size="default" size="default"
className={cn("gap-1 px-2.5 sm:pr-2.5", className)} className={cn("gap-1 pr-2.5", className)}
{...props} {...props}
> >
<span className="hidden sm:block">Next</span> <span>Next</span>
<ChevronRightIcon /> <ChevronRight className="h-4 w-4" />
</PaginationLink> </PaginationLink>
) )
} PaginationNext.displayName = "PaginationNext"
function PaginationEllipsis({ const PaginationEllipsis = ({
className, className,
...props ...props
}: React.ComponentProps<"span">) { }: React.ComponentProps<"span">) => (
return (
<span <span
aria-hidden aria-hidden
data-slot="pagination-ellipsis" className={cn("flex h-9 w-9 items-center justify-center", className)}
className={cn("flex size-9 items-center justify-center", className)}
{...props} {...props}
> >
<MoreHorizontalIcon className="size-4" /> <MoreHorizontal className="h-4 w-4" />
<span className="sr-only">More pages</span> <span className="sr-only">More pages</span>
</span> </span>
) )
} PaginationEllipsis.displayName = "PaginationEllipsis"
export { export {
Pagination, Pagination,

View File

@@ -1,48 +1,31 @@
"use client"
import * as React from "react" import * as React from "react"
import * as PopoverPrimitive from "@radix-ui/react-popover" import * as PopoverPrimitive from "@radix-ui/react-popover"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
function Popover({ const Popover = PopoverPrimitive.Root
...props
}: React.ComponentProps<typeof PopoverPrimitive.Root>) {
return <PopoverPrimitive.Root data-slot="popover" {...props} />
}
function PopoverTrigger({ const PopoverTrigger = PopoverPrimitive.Trigger
...props
}: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />
}
function PopoverContent({ const PopoverAnchor = PopoverPrimitive.Anchor
className,
align = "center", const PopoverContent = React.forwardRef<
sideOffset = 4, React.ElementRef<typeof PopoverPrimitive.Content>,
...props React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
}: React.ComponentProps<typeof PopoverPrimitive.Content>) { >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
return (
<PopoverPrimitive.Portal> <PopoverPrimitive.Portal>
<PopoverPrimitive.Content <PopoverPrimitive.Content
data-slot="popover-content" ref={ref}
align={align} align={align}
sideOffset={sideOffset} sideOffset={sideOffset}
className={cn( className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden", "z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-popover-content-transform-origin]",
className className
)} )}
{...props} {...props}
/> />
</PopoverPrimitive.Portal> </PopoverPrimitive.Portal>
) ))
} PopoverContent.displayName = PopoverPrimitive.Content.displayName
function PopoverAnchor({
...props
}: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />
}
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor } export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }

View File

@@ -1,29 +1,28 @@
"use client"
import * as React from "react" import * as React from "react"
import * as ProgressPrimitive from "@radix-ui/react-progress" import * as ProgressPrimitive from "@radix-ui/react-progress"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
function Progress({ const Progress = React.forwardRef<
className, React.ElementRef<typeof ProgressPrimitive.Root>,
value, React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
...props >(({ className, value, ...props }, ref) => (
}: React.ComponentProps<typeof ProgressPrimitive.Root>) {
return (
<ProgressPrimitive.Root <ProgressPrimitive.Root
data-slot="progress" ref={ref}
className={cn( className={cn(
"bg-primary/20 relative h-2 w-full overflow-hidden rounded-full", "relative h-2 w-full overflow-hidden rounded-full bg-primary/20",
className className
)} )}
{...props} {...props}
> >
<ProgressPrimitive.Indicator <ProgressPrimitive.Indicator
data-slot="progress-indicator" className="h-full w-full flex-1 bg-primary transition-all"
className="bg-primary h-full w-full flex-1 transition-all"
style={{ transform: `translateX(-${100 - (value || 0)}%)` }} style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
/> />
</ProgressPrimitive.Root> </ProgressPrimitive.Root>
) ))
} Progress.displayName = ProgressPrimitive.Root.displayName
export { Progress } export { Progress }

View File

@@ -1,45 +1,42 @@
"use client"
import * as React from "react" import * as React from "react"
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group" import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
import { CircleIcon } from "lucide-react" import { Circle } from "lucide-react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
function RadioGroup({ const RadioGroup = React.forwardRef<
className, React.ElementRef<typeof RadioGroupPrimitive.Root>,
...props React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
}: React.ComponentProps<typeof RadioGroupPrimitive.Root>) { >(({ className, ...props }, ref) => {
return ( return (
<RadioGroupPrimitive.Root <RadioGroupPrimitive.Root
data-slot="radio-group" className={cn("grid gap-2", className)}
className={cn("grid gap-3", className)}
{...props} {...props}
ref={ref}
/> />
) )
} })
RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
function RadioGroupItem({ const RadioGroupItem = React.forwardRef<
className, React.ElementRef<typeof RadioGroupPrimitive.Item>,
...props React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>
}: React.ComponentProps<typeof RadioGroupPrimitive.Item>) { >(({ className, ...props }, ref) => {
return ( return (
<RadioGroupPrimitive.Item <RadioGroupPrimitive.Item
data-slot="radio-group-item" ref={ref}
className={cn( className={cn(
"border-input text-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50", "aspect-square h-4 w-4 rounded-full border border-primary text-primary shadow focus:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
className className
)} )}
{...props} {...props}
> >
<RadioGroupPrimitive.Indicator <RadioGroupPrimitive.Indicator className="flex items-center justify-center">
data-slot="radio-group-indicator" <Circle className="h-3.5 w-3.5 fill-primary" />
className="relative flex items-center justify-center"
>
<CircleIcon className="fill-primary absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2" />
</RadioGroupPrimitive.Indicator> </RadioGroupPrimitive.Indicator>
</RadioGroupPrimitive.Item> </RadioGroupPrimitive.Item>
) )
} })
RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
export { RadioGroup, RadioGroupItem } export { RadioGroup, RadioGroupItem }

View File

@@ -1,16 +1,15 @@
import * as React from "react" "use client"
import { GripVerticalIcon } from "lucide-react"
import { GripVertical } from "lucide-react"
import * as ResizablePrimitive from "react-resizable-panels" import * as ResizablePrimitive from "react-resizable-panels"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
function ResizablePanelGroup({ const ResizablePanelGroup = ({
className, className,
...props ...props
}: React.ComponentProps<typeof ResizablePrimitive.PanelGroup>) { }: React.ComponentProps<typeof ResizablePrimitive.PanelGroup>) => (
return (
<ResizablePrimitive.PanelGroup <ResizablePrimitive.PanelGroup
data-slot="resizable-panel-group"
className={cn( className={cn(
"flex h-full w-full data-[panel-group-direction=vertical]:flex-col", "flex h-full w-full data-[panel-group-direction=vertical]:flex-col",
className className
@@ -18,37 +17,29 @@ function ResizablePanelGroup({
{...props} {...props}
/> />
) )
}
function ResizablePanel({ const ResizablePanel = ResizablePrimitive.Panel
...props
}: React.ComponentProps<typeof ResizablePrimitive.Panel>) {
return <ResizablePrimitive.Panel data-slot="resizable-panel" {...props} />
}
function ResizableHandle({ const ResizableHandle = ({
withHandle, withHandle,
className, className,
...props ...props
}: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & { }: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & {
withHandle?: boolean withHandle?: boolean
}) { }) => (
return (
<ResizablePrimitive.PanelResizeHandle <ResizablePrimitive.PanelResizeHandle
data-slot="resizable-handle"
className={cn( className={cn(
"bg-border focus-visible:ring-ring relative flex w-px items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-offset-1 focus-visible:outline-hidden data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:translate-x-0 data-[panel-group-direction=vertical]:after:-translate-y-1/2 [&[data-panel-group-direction=vertical]>div]:rotate-90", "relative flex w-px items-center justify-center bg-border after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 [&[data-panel-group-direction=vertical]>div]:rotate-90",
className className
)} )}
{...props} {...props}
> >
{withHandle && ( {withHandle && (
<div className="bg-border z-10 flex h-4 w-3 items-center justify-center rounded-xs border"> <div className="z-10 flex h-4 w-3 items-center justify-center rounded-sm border bg-border">
<GripVerticalIcon className="size-2.5" /> <GripVertical className="h-2.5 w-2.5" />
</div> </div>
)} )}
</ResizablePrimitive.PanelResizeHandle> </ResizablePrimitive.PanelResizeHandle>
) )
}
export { ResizablePanelGroup, ResizablePanel, ResizableHandle } export { ResizablePanelGroup, ResizablePanel, ResizableHandle }

View File

@@ -1,58 +1,46 @@
"use client"
import * as React from "react" import * as React from "react"
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area" import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
function ScrollArea({ const ScrollArea = React.forwardRef<
className, React.ElementRef<typeof ScrollAreaPrimitive.Root>,
children, React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
...props >(({ className, children, ...props }, ref) => (
}: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {
return (
<ScrollAreaPrimitive.Root <ScrollAreaPrimitive.Root
data-slot="scroll-area" ref={ref}
className={cn("relative", className)} className={cn("relative overflow-hidden", className)}
{...props} {...props}
> >
<ScrollAreaPrimitive.Viewport <ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
data-slot="scroll-area-viewport"
className="focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1"
>
{children} {children}
</ScrollAreaPrimitive.Viewport> </ScrollAreaPrimitive.Viewport>
<ScrollBar /> <ScrollBar />
<ScrollAreaPrimitive.Corner /> <ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root> </ScrollAreaPrimitive.Root>
) ))
} ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
function ScrollBar({ const ScrollBar = React.forwardRef<
className, React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
orientation = "vertical", React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
...props >(({ className, orientation = "vertical", ...props }, ref) => (
}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) {
return (
<ScrollAreaPrimitive.ScrollAreaScrollbar <ScrollAreaPrimitive.ScrollAreaScrollbar
data-slot="scroll-area-scrollbar" ref={ref}
orientation={orientation} orientation={orientation}
className={cn( className={cn(
"flex touch-none p-px transition-colors select-none", "flex touch-none select-none transition-colors",
orientation === "vertical" && orientation === "vertical" &&
"h-full w-2.5 border-l border-l-transparent", "h-full w-2.5 border-l border-l-transparent p-[1px]",
orientation === "horizontal" && orientation === "horizontal" &&
"h-2.5 flex-col border-t border-t-transparent", "h-2.5 flex-col border-t border-t-transparent p-[1px]",
className className
)} )}
{...props} {...props}
> >
<ScrollAreaPrimitive.ScrollAreaThumb <ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
data-slot="scroll-area-thumb"
className="bg-border relative flex-1 rounded-full"
/>
</ScrollAreaPrimitive.ScrollAreaScrollbar> </ScrollAreaPrimitive.ScrollAreaScrollbar>
) ))
} ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
export { ScrollArea, ScrollBar } export { ScrollArea, ScrollBar }

View File

@@ -1,65 +1,81 @@
"use client"
import * as React from "react" import * as React from "react"
import * as SelectPrimitive from "@radix-ui/react-select" import * as SelectPrimitive from "@radix-ui/react-select"
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react" import { Check, ChevronDown, ChevronUp } from "lucide-react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
function Select({ const Select = SelectPrimitive.Root
...props
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
return <SelectPrimitive.Root data-slot="select" {...props} />
}
function SelectGroup({ const SelectGroup = SelectPrimitive.Group
...props
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
return <SelectPrimitive.Group data-slot="select-group" {...props} />
}
function SelectValue({ const SelectValue = SelectPrimitive.Value
...props
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
return <SelectPrimitive.Value data-slot="select-value" {...props} />
}
function SelectTrigger({ const SelectTrigger = React.forwardRef<
className, React.ElementRef<typeof SelectPrimitive.Trigger>,
size = "default", React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
children, >(({ className, children, ...props }, ref) => (
...props
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
size?: "sm" | "default"
}) {
return (
<SelectPrimitive.Trigger <SelectPrimitive.Trigger
data-slot="select-trigger" ref={ref}
data-size={size}
className={cn( className={cn(
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", "flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
className className
)} )}
{...props} {...props}
> >
{children} {children}
<SelectPrimitive.Icon asChild> <SelectPrimitive.Icon asChild>
<ChevronDownIcon className="size-4 opacity-50" /> <ChevronDown className="h-4 w-4 opacity-50" />
</SelectPrimitive.Icon> </SelectPrimitive.Icon>
</SelectPrimitive.Trigger> </SelectPrimitive.Trigger>
) ))
} SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
function SelectContent({ const SelectScrollUpButton = React.forwardRef<
className, React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
children, React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
position = "popper", >(({ className, ...props }, ref) => (
...props <SelectPrimitive.ScrollUpButton
}: React.ComponentProps<typeof SelectPrimitive.Content>) { ref={ref}
return ( className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronUp className="h-4 w-4" />
</SelectPrimitive.ScrollUpButton>
))
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
const SelectScrollDownButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollDownButton
ref={ref}
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronDown className="h-4 w-4" />
</SelectPrimitive.ScrollDownButton>
))
SelectScrollDownButton.displayName =
SelectPrimitive.ScrollDownButton.displayName
const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, position = "popper", ...props }, ref) => (
<SelectPrimitive.Portal> <SelectPrimitive.Portal>
<SelectPrimitive.Content <SelectPrimitive.Content
data-slot="select-content" ref={ref}
className={cn( className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md", "relative z-50 max-h-[--radix-select-content-available-height] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-select-content-transform-origin]",
position === "popper" && position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className className
@@ -72,7 +88,7 @@ function SelectContent({
className={cn( className={cn(
"p-1", "p-1",
position === "popper" && position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1" "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
)} )}
> >
{children} {children}
@@ -80,104 +96,64 @@ function SelectContent({
<SelectScrollDownButton /> <SelectScrollDownButton />
</SelectPrimitive.Content> </SelectPrimitive.Content>
</SelectPrimitive.Portal> </SelectPrimitive.Portal>
) ))
} SelectContent.displayName = SelectPrimitive.Content.displayName
function SelectLabel({ const SelectLabel = React.forwardRef<
className, React.ElementRef<typeof SelectPrimitive.Label>,
...props React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
}: React.ComponentProps<typeof SelectPrimitive.Label>) { >(({ className, ...props }, ref) => (
return (
<SelectPrimitive.Label <SelectPrimitive.Label
data-slot="select-label" ref={ref}
className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)} className={cn("px-2 py-1.5 text-sm font-semibold", className)}
{...props} {...props}
/> />
) ))
} SelectLabel.displayName = SelectPrimitive.Label.displayName
function SelectItem({ const SelectItem = React.forwardRef<
className, React.ElementRef<typeof SelectPrimitive.Item>,
children, React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
...props >(({ className, children, ...props }, ref) => (
}: React.ComponentProps<typeof SelectPrimitive.Item>) {
return (
<SelectPrimitive.Item <SelectPrimitive.Item
data-slot="select-item" ref={ref}
className={cn( className={cn(
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2", "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className className
)} )}
{...props} {...props}
> >
<span className="absolute right-2 flex size-3.5 items-center justify-center"> <span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator> <SelectPrimitive.ItemIndicator>
<CheckIcon className="size-4" /> <Check className="h-4 w-4" />
</SelectPrimitive.ItemIndicator> </SelectPrimitive.ItemIndicator>
</span> </span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText> <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item> </SelectPrimitive.Item>
) ))
} SelectItem.displayName = SelectPrimitive.Item.displayName
function SelectSeparator({ const SelectSeparator = React.forwardRef<
className, React.ElementRef<typeof SelectPrimitive.Separator>,
...props React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
}: React.ComponentProps<typeof SelectPrimitive.Separator>) { >(({ className, ...props }, ref) => (
return (
<SelectPrimitive.Separator <SelectPrimitive.Separator
data-slot="select-separator" ref={ref}
className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)} className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props} {...props}
/> />
) ))
} SelectSeparator.displayName = SelectPrimitive.Separator.displayName
function SelectScrollUpButton({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
return (
<SelectPrimitive.ScrollUpButton
data-slot="select-scroll-up-button"
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronUpIcon className="size-4" />
</SelectPrimitive.ScrollUpButton>
)
}
function SelectScrollDownButton({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
return (
<SelectPrimitive.ScrollDownButton
data-slot="select-scroll-down-button"
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronDownIcon className="size-4" />
</SelectPrimitive.ScrollDownButton>
)
}
export { export {
Select, Select,
SelectContent,
SelectGroup, SelectGroup,
SelectItem,
SelectLabel,
SelectScrollDownButton,
SelectScrollUpButton,
SelectSeparator,
SelectTrigger,
SelectValue, SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator,
SelectScrollUpButton,
SelectScrollDownButton,
} }

View File

@@ -1,28 +1,29 @@
"use client"
import * as React from "react" import * as React from "react"
import * as SeparatorPrimitive from "@radix-ui/react-separator" import * as SeparatorPrimitive from "@radix-ui/react-separator"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
function Separator({ const Separator = React.forwardRef<
className, React.ElementRef<typeof SeparatorPrimitive.Root>,
orientation = "horizontal", React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
decorative = true, >(
...props (
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) { { className, orientation = "horizontal", decorative = true, ...props },
return ( ref
) => (
<SeparatorPrimitive.Root <SeparatorPrimitive.Root
data-slot="separator" ref={ref}
decorative={decorative} decorative={decorative}
orientation={orientation} orientation={orientation}
className={cn( className={cn(
"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px", "shrink-0 bg-border",
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
className className
)} )}
{...props} {...props}
/> />
) )
} )
Separator.displayName = SeparatorPrimitive.Root.displayName
export { Separator } export { Separator }

View File

@@ -1,132 +1,135 @@
"use client"
import * as React from "react" import * as React from "react"
import * as SheetPrimitive from "@radix-ui/react-dialog" import * as SheetPrimitive from "@radix-ui/react-dialog"
import { XIcon } from "lucide-react" import { cva, type VariantProps } from "class-variance-authority"
import { X } from "lucide-react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) { const Sheet = SheetPrimitive.Root
return <SheetPrimitive.Root data-slot="sheet" {...props} />
}
function SheetTrigger({ const SheetTrigger = SheetPrimitive.Trigger
...props
}: React.ComponentProps<typeof SheetPrimitive.Trigger>) {
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />
}
function SheetClose({ const SheetClose = SheetPrimitive.Close
...props
}: React.ComponentProps<typeof SheetPrimitive.Close>) {
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />
}
function SheetPortal({ const SheetPortal = SheetPrimitive.Portal
...props
}: React.ComponentProps<typeof SheetPrimitive.Portal>) {
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />
}
function SheetOverlay({ const SheetOverlay = React.forwardRef<
className, React.ElementRef<typeof SheetPrimitive.Overlay>,
...props React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
}: React.ComponentProps<typeof SheetPrimitive.Overlay>) { >(({ className, ...props }, ref) => (
return (
<SheetPrimitive.Overlay <SheetPrimitive.Overlay
data-slot="sheet-overlay"
className={cn( className={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50", "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className className
)} )}
{...props} {...props}
ref={ref}
/> />
) ))
} SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
function SheetContent({ const sheetVariants = cva(
className, "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out",
children, {
side = "right", variants: {
...props side: {
}: React.ComponentProps<typeof SheetPrimitive.Content> & { top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
side?: "top" | "right" | "bottom" | "left" bottom:
}) { "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
return ( left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
right:
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
},
},
defaultVariants: {
side: "right",
},
}
)
interface SheetContentProps
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
VariantProps<typeof sheetVariants> {}
const SheetContent = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Content>,
SheetContentProps
>(({ side = "right", className, children, ...props }, ref) => (
<SheetPortal> <SheetPortal>
<SheetOverlay /> <SheetOverlay />
<SheetPrimitive.Content <SheetPrimitive.Content
data-slot="sheet-content" ref={ref}
className={cn(sheetVariants({ side }), className)}
{...props}
>
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
{children}
</SheetPrimitive.Content>
</SheetPortal>
))
SheetContent.displayName = SheetPrimitive.Content.displayName
const SheetHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn( className={cn(
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500", "flex flex-col space-y-2 text-center sm:text-left",
side === "right" &&
"data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm",
side === "left" &&
"data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm",
side === "top" &&
"data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b",
side === "bottom" &&
"data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t",
className className
)} )}
{...props} {...props}
>
{children}
<SheetPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none">
<XIcon className="size-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
</SheetPrimitive.Content>
</SheetPortal>
)
}
function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="sheet-header"
className={cn("flex flex-col gap-1.5 p-4", className)}
{...props}
/> />
) )
} SheetHeader.displayName = "SheetHeader"
function SheetFooter({ className, ...props }: React.ComponentProps<"div">) { const SheetFooter = ({
return (
<div
data-slot="sheet-footer"
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
{...props}
/>
)
}
function SheetTitle({
className, className,
...props ...props
}: React.ComponentProps<typeof SheetPrimitive.Title>) { }: React.HTMLAttributes<HTMLDivElement>) => (
return ( <div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
SheetFooter.displayName = "SheetFooter"
const SheetTitle = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Title <SheetPrimitive.Title
data-slot="sheet-title" ref={ref}
className={cn("text-foreground font-semibold", className)} className={cn("text-lg font-semibold text-foreground", className)}
{...props} {...props}
/> />
) ))
} SheetTitle.displayName = SheetPrimitive.Title.displayName
function SheetDescription({ const SheetDescription = React.forwardRef<
className, React.ElementRef<typeof SheetPrimitive.Description>,
...props React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
}: React.ComponentProps<typeof SheetPrimitive.Description>) { >(({ className, ...props }, ref) => (
return (
<SheetPrimitive.Description <SheetPrimitive.Description
data-slot="sheet-description" ref={ref}
className={cn("text-muted-foreground text-sm", className)} className={cn("text-sm text-muted-foreground", className)}
{...props} {...props}
/> />
) ))
} SheetDescription.displayName = SheetPrimitive.Description.displayName
export { export {
Sheet, Sheet,
SheetPortal,
SheetOverlay,
SheetTrigger, SheetTrigger,
SheetClose, SheetClose,
SheetContent, SheetContent,

View File

@@ -1,10 +1,12 @@
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
function Skeleton({ className, ...props }: React.ComponentProps<"div">) { function Skeleton({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return ( return (
<div <div
data-slot="skeleton" className={cn("animate-pulse rounded-md bg-primary/10", className)}
className={cn("bg-accent animate-pulse rounded-md", className)}
{...props} {...props}
/> />
) )

View File

@@ -1,63 +1,26 @@
"use client"
import * as React from "react" import * as React from "react"
import * as SliderPrimitive from "@radix-ui/react-slider" import * as SliderPrimitive from "@radix-ui/react-slider"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
function Slider({ const Slider = React.forwardRef<
className, React.ElementRef<typeof SliderPrimitive.Root>,
defaultValue, React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>
value, >(({ className, ...props }, ref) => (
min = 0,
max = 100,
...props
}: React.ComponentProps<typeof SliderPrimitive.Root>) {
const _values = React.useMemo(
() =>
Array.isArray(value)
? value
: Array.isArray(defaultValue)
? defaultValue
: [min, max],
[value, defaultValue, min, max]
)
return (
<SliderPrimitive.Root <SliderPrimitive.Root
data-slot="slider" ref={ref}
defaultValue={defaultValue}
value={value}
min={min}
max={max}
className={cn( className={cn(
"relative flex w-full touch-none items-center select-none data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col", "relative flex w-full touch-none select-none items-center",
className className
)} )}
{...props} {...props}
> >
<SliderPrimitive.Track <SliderPrimitive.Track className="relative h-1.5 w-full grow overflow-hidden rounded-full bg-primary/20">
data-slot="slider-track" <SliderPrimitive.Range className="absolute h-full bg-primary" />
className={cn(
"bg-muted relative grow overflow-hidden rounded-full data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5"
)}
>
<SliderPrimitive.Range
data-slot="slider-range"
className={cn(
"bg-primary absolute data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full"
)}
/>
</SliderPrimitive.Track> </SliderPrimitive.Track>
{Array.from({ length: _values.length }, (_, index) => ( <SliderPrimitive.Thumb className="block h-4 w-4 rounded-full border border-primary/50 bg-background shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" />
<SliderPrimitive.Thumb
data-slot="slider-thumb"
key={index}
className="border-primary bg-background ring-ring/50 block size-4 shrink-0 rounded-full border shadow-sm transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50"
/>
))}
</SliderPrimitive.Root> </SliderPrimitive.Root>
) ))
} Slider.displayName = SliderPrimitive.Root.displayName
export { Slider } export { Slider }

View File

@@ -1,5 +1,16 @@
"use client"
import {
CircleCheckIcon,
InfoIcon,
Loader2Icon,
OctagonXIcon,
TriangleAlertIcon,
} from "lucide-react"
import { useTheme } from "next-themes" import { useTheme } from "next-themes"
import { Toaster as Sonner, ToasterProps } from "sonner" import { Toaster as Sonner } from "sonner"
type ToasterProps = React.ComponentProps<typeof Sonner>
const Toaster = ({ ...props }: ToasterProps) => { const Toaster = ({ ...props }: ToasterProps) => {
const { theme = "system" } = useTheme() const { theme = "system" } = useTheme()
@@ -8,17 +19,29 @@ const Toaster = ({ ...props }: ToasterProps) => {
<Sonner <Sonner
theme={theme as ToasterProps["theme"]} theme={theme as ToasterProps["theme"]}
className="toaster group" className="toaster group"
icons={{
success: <CircleCheckIcon className="size-4" />,
info: <InfoIcon className="size-4" />,
warning: <TriangleAlertIcon className="size-4" />,
error: <OctagonXIcon className="size-4" />,
loading: <Loader2Icon className="size-4 animate-spin" />,
}}
style={ style={
{ {
"--normal-bg": "var(--popover)", "--normal-bg": "var(--popover)",
"--normal-text": "var(--popover-foreground)", "--normal-text": "var(--popover-foreground)",
"--normal-border": "var(--border)", "--normal-border": "var(--border)",
"--border-radius": "var(--radius)",
} as React.CSSProperties } as React.CSSProperties
} }
toastOptions={{ toastOptions={{
classNames: { classNames: {
toast: "group", toast: "group toast",
icon: "group-data-[type=error]:!text-red-500 group-data-[type=success]:!text-green-500 group-data-[type=warning]:!text-amber-500 group-data-[type=info]:!text-sky-500", icon: "group-data-[type=error]:!text-red-500 group-data-[type=success]:!text-green-500 group-data-[type=warning]:!text-amber-500 group-data-[type=info]:!text-sky-500",
description: "group-[.toast]:!text-muted-foreground",
actionButton: "group-[.toast]:!bg-primary group-[.toast]:!text-primary-foreground",
cancelButton: "group-[.toast]:!bg-muted group-[.toast]:!text-muted-foreground",
closeButton: "hover:group-[.toast]:!bg-background hover:group-[.toast]:!text-foreground",
}, },
}} }}
{...props} {...props}

View File

@@ -0,0 +1,16 @@
import { Loader2Icon } from "lucide-react"
import { cn } from "@/lib/utils"
function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
return (
<Loader2Icon
role="status"
aria-label="Loading"
className={cn("size-4 animate-spin", className)}
{...props}
/>
)
}
export { Spinner }

View File

@@ -1,31 +1,27 @@
"use client"
import * as React from "react" import * as React from "react"
import * as SwitchPrimitive from "@radix-ui/react-switch" import * as SwitchPrimitives from "@radix-ui/react-switch"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
function Switch({ const Switch = React.forwardRef<
className, React.ElementRef<typeof SwitchPrimitives.Root>,
...props React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
}: React.ComponentProps<typeof SwitchPrimitive.Root>) { >(({ className, ...props }, ref) => (
return ( <SwitchPrimitives.Root
<SwitchPrimitive.Root
data-slot="switch"
className={cn( className={cn(
"peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50", "peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
className className
)} )}
{...props} {...props}
ref={ref}
> >
<SwitchPrimitive.Thumb <SwitchPrimitives.Thumb
data-slot="switch-thumb"
className={cn( className={cn(
"bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0" "pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0"
)} )}
/> />
</SwitchPrimitive.Root> </SwitchPrimitives.Root>
) ))
} Switch.displayName = SwitchPrimitives.Root.displayName
export { Switch } export { Switch }

View File

@@ -2,105 +2,111 @@ import * as React from "react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
function Table({ className, ...props }: React.ComponentProps<"table">) { const Table = React.forwardRef<
return ( HTMLTableElement,
<div React.HTMLAttributes<HTMLTableElement>
data-slot="table-container" >(({ className, ...props }, ref) => (
className="relative w-full overflow-x-auto" <div className="relative w-full overflow-auto">
>
<table <table
data-slot="table" ref={ref}
className={cn("w-full caption-bottom text-sm", className)} className={cn("w-full caption-bottom text-sm", className)}
{...props} {...props}
/> />
</div> </div>
) ))
} Table.displayName = "Table"
function TableHeader({ className, ...props }: React.ComponentProps<"thead">) { const TableHeader = React.forwardRef<
return ( HTMLTableSectionElement,
<thead React.HTMLAttributes<HTMLTableSectionElement>
data-slot="table-header" >(({ className, ...props }, ref) => (
className={cn("[&_tr]:border-b", className)} <thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
{...props} ))
/> TableHeader.displayName = "TableHeader"
)
}
function TableBody({ className, ...props }: React.ComponentProps<"tbody">) { const TableBody = React.forwardRef<
return ( HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tbody <tbody
data-slot="table-body" ref={ref}
className={cn("[&_tr:last-child]:border-0", className)} className={cn("[&_tr:last-child]:border-0", className)}
{...props} {...props}
/> />
) ))
} TableBody.displayName = "TableBody"
function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) { const TableFooter = React.forwardRef<
return ( HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tfoot <tfoot
data-slot="table-footer" ref={ref}
className={cn( className={cn(
"bg-muted/50 border-t font-medium [&>tr]:last:border-b-0", "border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
className className
)} )}
{...props} {...props}
/> />
) ))
} TableFooter.displayName = "TableFooter"
function TableRow({ className, ...props }: React.ComponentProps<"tr">) { const TableRow = React.forwardRef<
return ( HTMLTableRowElement,
React.HTMLAttributes<HTMLTableRowElement>
>(({ className, ...props }, ref) => (
<tr <tr
data-slot="table-row" ref={ref}
className={cn( className={cn(
"hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors", "border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
className className
)} )}
{...props} {...props}
/> />
) ))
} TableRow.displayName = "TableRow"
function TableHead({ className, ...props }: React.ComponentProps<"th">) { const TableHead = React.forwardRef<
return ( HTMLTableCellElement,
React.ThHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<th <th
data-slot="table-head" ref={ref}
className={cn( className={cn(
"text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]", "h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className className
)} )}
{...props} {...props}
/> />
) ))
} TableHead.displayName = "TableHead"
function TableCell({ className, ...props }: React.ComponentProps<"td">) { const TableCell = React.forwardRef<
return ( HTMLTableCellElement,
React.TdHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<td <td
data-slot="table-cell" ref={ref}
className={cn( className={cn(
"p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]", "p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className className
)} )}
{...props} {...props}
/> />
) ))
} TableCell.displayName = "TableCell"
function TableCaption({ const TableCaption = React.forwardRef<
className, HTMLTableCaptionElement,
...props React.HTMLAttributes<HTMLTableCaptionElement>
}: React.ComponentProps<"caption">) { >(({ className, ...props }, ref) => (
return (
<caption <caption
data-slot="table-caption" ref={ref}
className={cn("text-muted-foreground mt-4 text-sm", className)} className={cn("mt-4 text-sm text-muted-foreground", className)}
{...props} {...props}
/> />
) ))
} TableCaption.displayName = "TableCaption"
export { export {
Table, Table,

View File

@@ -1,66 +1,53 @@
"use client"
import * as React from "react" import * as React from "react"
import * as TabsPrimitive from "@radix-ui/react-tabs" import * as TabsPrimitive from "@radix-ui/react-tabs"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
function Tabs({ const Tabs = TabsPrimitive.Root
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Root>) {
return (
<TabsPrimitive.Root
data-slot="tabs"
className={cn("flex flex-col gap-2", className)}
{...props}
/>
)
}
function TabsList({ const TabsList = React.forwardRef<
className, React.ElementRef<typeof TabsPrimitive.List>,
...props React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
}: React.ComponentProps<typeof TabsPrimitive.List>) { >(({ className, ...props }, ref) => (
return (
<TabsPrimitive.List <TabsPrimitive.List
data-slot="tabs-list" ref={ref}
className={cn( className={cn(
"bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]", "inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground",
className className
)} )}
{...props} {...props}
/> />
) ))
} TabsList.displayName = TabsPrimitive.List.displayName
function TabsTrigger({ const TabsTrigger = React.forwardRef<
className, React.ElementRef<typeof TabsPrimitive.Trigger>,
...props React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
}: React.ComponentProps<typeof TabsPrimitive.Trigger>) { >(({ className, ...props }, ref) => (
return (
<TabsPrimitive.Trigger <TabsPrimitive.Trigger
data-slot="tabs-trigger" ref={ref}
className={cn( className={cn(
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", "inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow",
className className
)} )}
{...props} {...props}
/> />
) ))
} TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
function TabsContent({ const TabsContent = React.forwardRef<
className, React.ElementRef<typeof TabsPrimitive.Content>,
...props React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
}: React.ComponentProps<typeof TabsPrimitive.Content>) { >(({ className, ...props }, ref) => (
return (
<TabsPrimitive.Content <TabsPrimitive.Content
data-slot="tabs-content" ref={ref}
className={cn("flex-1 outline-none", className)} className={cn(
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
className
)}
{...props} {...props}
/> />
) ))
} TabsContent.displayName = TabsPrimitive.Content.displayName
export { Tabs, TabsList, TabsTrigger, TabsContent } export { Tabs, TabsList, TabsTrigger, TabsContent }

View File

@@ -2,17 +2,21 @@ import * as React from "react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) { const Textarea = React.forwardRef<
HTMLTextAreaElement,
React.ComponentProps<"textarea">
>(({ className, ...props }, ref) => {
return ( return (
<textarea <textarea
data-slot="textarea"
className={cn( className={cn(
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", "flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className className
)} )}
ref={ref}
{...props} {...props}
/> />
) )
} })
Textarea.displayName = "Textarea"
export { Textarea } export { Textarea }

View File

@@ -14,53 +14,39 @@ const ToggleGroupContext = React.createContext<
variant: "default", variant: "default",
}) })
function ToggleGroup({ const ToggleGroup = React.forwardRef<
className, React.ElementRef<typeof ToggleGroupPrimitive.Root>,
variant, React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root> &
size, VariantProps<typeof toggleVariants>
children, >(({ className, variant, size, children, ...props }, ref) => (
...props
}: React.ComponentProps<typeof ToggleGroupPrimitive.Root> &
VariantProps<typeof toggleVariants>) {
return (
<ToggleGroupPrimitive.Root <ToggleGroupPrimitive.Root
data-slot="toggle-group" ref={ref}
data-variant={variant} className={cn("flex items-center justify-center gap-1", className)}
data-size={size}
className={cn(
"group/toggle-group flex w-fit items-center rounded-md data-[variant=outline]:shadow-xs",
className
)}
{...props} {...props}
> >
<ToggleGroupContext.Provider value={{ variant, size }}> <ToggleGroupContext.Provider value={{ variant, size }}>
{children} {children}
</ToggleGroupContext.Provider> </ToggleGroupContext.Provider>
</ToggleGroupPrimitive.Root> </ToggleGroupPrimitive.Root>
) ))
}
function ToggleGroupItem({ ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName
className,
children, const ToggleGroupItem = React.forwardRef<
variant, React.ElementRef<typeof ToggleGroupPrimitive.Item>,
size, React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> &
...props VariantProps<typeof toggleVariants>
}: React.ComponentProps<typeof ToggleGroupPrimitive.Item> & >(({ className, children, variant, size, ...props }, ref) => {
VariantProps<typeof toggleVariants>) {
const context = React.useContext(ToggleGroupContext) const context = React.useContext(ToggleGroupContext)
return ( return (
<ToggleGroupPrimitive.Item <ToggleGroupPrimitive.Item
data-slot="toggle-group-item" ref={ref}
data-variant={context.variant || variant}
data-size={context.size || size}
className={cn( className={cn(
toggleVariants({ toggleVariants({
variant: context.variant || variant, variant: context.variant || variant,
size: context.size || size, size: context.size || size,
}), }),
"min-w-0 flex-1 shrink-0 rounded-none shadow-none first:rounded-l-md last:rounded-r-md focus:z-10 focus-visible:z-10 data-[variant=outline]:border-l-0 data-[variant=outline]:first:border-l",
className className
)} )}
{...props} {...props}
@@ -68,6 +54,8 @@ function ToggleGroupItem({
{children} {children}
</ToggleGroupPrimitive.Item> </ToggleGroupPrimitive.Item>
) )
} })
ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName
export { ToggleGroup, ToggleGroupItem } export { ToggleGroup, ToggleGroupItem }

View File

@@ -5,13 +5,13 @@ import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const toggleVariants = cva( const toggleVariants = cva(
"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium hover:bg-muted hover:text-muted-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] outline-none transition-[color,box-shadow] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive whitespace-nowrap", "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{ {
variants: { variants: {
variant: { variant: {
default: "bg-transparent", default: "bg-transparent",
outline: outline:
"border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground", "border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground",
}, },
size: { size: {
default: "h-9 px-2 min-w-9", default: "h-9 px-2 min-w-9",
@@ -26,20 +26,18 @@ const toggleVariants = cva(
} }
) )
function Toggle({ const Toggle = React.forwardRef<
className, React.ElementRef<typeof TogglePrimitive.Root>,
variant, React.ComponentPropsWithoutRef<typeof TogglePrimitive.Root> &
size, VariantProps<typeof toggleVariants>
...props >(({ className, variant, size, ...props }, ref) => (
}: React.ComponentProps<typeof TogglePrimitive.Root> &
VariantProps<typeof toggleVariants>) {
return (
<TogglePrimitive.Root <TogglePrimitive.Root
data-slot="toggle" ref={ref}
className={cn(toggleVariants({ variant, size, className }))} className={cn(toggleVariants({ variant, size, className }))}
{...props} {...props}
/> />
) ))
}
Toggle.displayName = TogglePrimitive.Root.displayName
export { Toggle, toggleVariants } export { Toggle, toggleVariants }

View File

@@ -1,59 +1,32 @@
"use client"
import * as React from "react" import * as React from "react"
import * as TooltipPrimitive from "@radix-ui/react-tooltip" import * as TooltipPrimitive from "@radix-ui/react-tooltip"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
function TooltipProvider({ const TooltipProvider = TooltipPrimitive.Provider
delayDuration = 0,
...props
}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
return (
<TooltipPrimitive.Provider
data-slot="tooltip-provider"
delayDuration={delayDuration}
{...props}
/>
)
}
function Tooltip({ const Tooltip = TooltipPrimitive.Root
...props
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
return (
<TooltipProvider>
<TooltipPrimitive.Root data-slot="tooltip" {...props} />
</TooltipProvider>
)
}
function TooltipTrigger({ const TooltipTrigger = TooltipPrimitive.Trigger
...props
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
}
function TooltipContent({ const TooltipContent = React.forwardRef<
className, React.ElementRef<typeof TooltipPrimitive.Content>,
sideOffset = 0, React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
children, >(({ className, sideOffset = 4, ...props }, ref) => (
...props
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
return (
<TooltipPrimitive.Portal> <TooltipPrimitive.Portal>
<TooltipPrimitive.Content <TooltipPrimitive.Content
data-slot="tooltip-content" ref={ref}
sideOffset={sideOffset} sideOffset={sideOffset}
className={cn( className={cn(
"bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance", "z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-tooltip-content-transform-origin]",
className className
)} )}
{...props} {...props}
> />
{children}
<TooltipPrimitive.Arrow className="bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
</TooltipPrimitive.Content>
</TooltipPrimitive.Portal> </TooltipPrimitive.Portal>
) ))
} TooltipContent.displayName = TooltipPrimitive.Content.displayName
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }

View File

@@ -1,7 +1,17 @@
@import "tailwindcss"; @import "tailwindcss";
@import "tw-animate-css"; @import "tw-animate-css";
@custom-variant dark (&:is(.dark *)); @custom-variant dark (&:is(.dark *, .blue-dark *, .green-dark *, .orange-dark *, .red-dark *, .rose-dark *, .violet-dark *, .yellow-dark *));
@custom-variant light (&:is(:not(.dark):not(.blue-dark):not(.green-dark):not(.orange-dark):not(.red-dark):not(.rose-dark):not(.violet-dark):not(.yellow-dark) *));
@custom-variant defaultscheme (&:is(:not(.blue):not(.blue-dark):not(.green):not(.green-dark):not(.orange):not(.orange-dark):not(.red):not(.red-dark):not(.rose):not(.rose-dark):not(.violet):not(.violet-dark):not(.yellow):not(.yellow-dark) *));
@custom-variant customscheme (&:is(.blue *, .blue-dark *, .green *, .green-dark *, .orange *, .orange-dark *, .red *, .red-dark *, .rose *, .rose-dark *, .violet *, .violet-dark *, .yellow *, .yellow-dark *));
@custom-variant blue (&:is(.blue *, .blue-dark *));
@custom-variant green (&:is(.green *, .green-dark *));
@custom-variant orange (&:is(.orange *, .orange-dark *));
@custom-variant red (&:is(.red *, .red-dark *));
@custom-variant rose (&:is(.rose *, .rose-dark *));
@custom-variant violet (&:is(.violet *, .violet-dark *));
@custom-variant yellow (&:is(.yellow *, .yellow-dark *));
@theme inline { @theme inline {
--radius-sm: calc(var(--radius) - 4px); --radius-sm: calc(var(--radius) - 4px);
@@ -40,6 +50,27 @@
--color-sidebar-border: var(--sidebar-border); --color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring); --color-sidebar-ring: var(--sidebar-ring);
--animate-accordion-down: accordion-down 0.2s ease-out;
--animate-accordion-up: accordion-up 0.2s ease-out;
@keyframes accordion-down {
from {
height: 0;
}
to {
height: var(--radix-accordion-content-height);
}
}
@keyframes accordion-up {
from {
height: var(--radix-accordion-content-height);
}
to {
height: 0;
}
}
@keyframes indeterminate-progress { @keyframes indeterminate-progress {
0% { 0% {
transform: translateX(0) scaleX(0); transform: translateX(0) scaleX(0);
@@ -54,7 +85,7 @@
} }
:root { :root {
--radius: 0.625rem; --radius: 0.65rem;
--background: oklch(1 0 0); --background: oklch(1 0 0);
--foreground: oklch(0.141 0.005 285.823); --foreground: oklch(0.141 0.005 285.823);
--card: oklch(1 0 0); --card: oklch(1 0 0);
@@ -122,6 +153,482 @@
--sidebar-ring: oklch(0.552 0.016 285.938); --sidebar-ring: oklch(0.552 0.016 285.938);
} }
.blue {
--background: oklch(1 0 0);
--foreground: oklch(0.141 0.005 285.823);
--card: oklch(1 0 0);
--card-foreground: oklch(0.141 0.005 285.823);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.141 0.005 285.823);
--primary: oklch(0.488 0.243 264.376);
--primary-foreground: oklch(0.97 0.014 254.604);
--secondary: oklch(0.967 0.001 286.375);
--secondary-foreground: oklch(0.21 0.006 285.885);
--muted: oklch(0.967 0.001 286.375);
--muted-foreground: oklch(0.552 0.016 285.938);
--accent: oklch(0.967 0.001 286.375);
--accent-foreground: oklch(0.21 0.006 285.885);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.92 0.004 286.32);
--input: oklch(0.92 0.004 286.32);
--ring: oklch(0.708 0 0);
--chart-1: oklch(0.809 0.105 251.813);
--chart-2: oklch(0.623 0.214 259.815);
--chart-3: oklch(0.546 0.245 262.881);
--chart-4: oklch(0.488 0.243 264.376);
--chart-5: oklch(0.424 0.199 265.638);
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.141 0.005 285.823);
--sidebar-primary: oklch(0.546 0.245 262.881);
--sidebar-primary-foreground: oklch(0.97 0.014 254.604);
--sidebar-accent: oklch(0.967 0.001 286.375);
--sidebar-accent-foreground: oklch(0.21 0.006 285.885);
--sidebar-border: oklch(0.92 0.004 286.32);
--sidebar-ring: oklch(0.708 0 0);
}
.blue-dark {
--background: oklch(0.141 0.005 285.823);
--foreground: oklch(0.985 0 0);
--card: oklch(0.21 0.006 285.885);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.21 0.006 285.885);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.488 0.243 264.376);
--primary-foreground: oklch(0.97 0.014 254.604);
--secondary: oklch(0.274 0.006 286.033);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.274 0.006 286.033);
--muted-foreground: oklch(0.705 0.015 286.067);
--accent: oklch(0.274 0.006 286.033);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.556 0 0);
--chart-1: oklch(0.809 0.105 251.813);
--chart-2: oklch(0.623 0.214 259.815);
--chart-3: oklch(0.546 0.245 262.881);
--chart-4: oklch(0.488 0.243 264.376);
--chart-5: oklch(0.424 0.199 265.638);
--sidebar: oklch(0.21 0.006 285.885);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.623 0.214 259.815);
--sidebar-primary-foreground: oklch(0.97 0.014 254.604);
--sidebar-accent: oklch(0.274 0.006 286.033);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.439 0 0);
}
.green {
--background: oklch(1 0 0);
--foreground: oklch(0.141 0.005 285.823);
--card: oklch(1 0 0);
--card-foreground: oklch(0.141 0.005 285.823);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.141 0.005 285.823);
--primary: oklch(0.648 0.2 131.684);
--primary-foreground: oklch(0.986 0.031 120.757);
--secondary: oklch(0.967 0.001 286.375);
--secondary-foreground: oklch(0.21 0.006 285.885);
--muted: oklch(0.967 0.001 286.375);
--muted-foreground: oklch(0.552 0.016 285.938);
--accent: oklch(0.967 0.001 286.375);
--accent-foreground: oklch(0.21 0.006 285.885);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.92 0.004 286.32);
--input: oklch(0.92 0.004 286.32);
--ring: oklch(0.841 0.238 128.85);
--chart-1: oklch(0.871 0.15 154.449);
--chart-2: oklch(0.723 0.219 149.579);
--chart-3: oklch(0.627 0.194 149.214);
--chart-4: oklch(0.527 0.154 150.069);
--chart-5: oklch(0.448 0.119 151.328);
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.141 0.005 285.823);
--sidebar-primary: oklch(0.648 0.2 131.684);
--sidebar-primary-foreground: oklch(0.986 0.031 120.757);
--sidebar-accent: oklch(0.967 0.001 286.375);
--sidebar-accent-foreground: oklch(0.21 0.006 285.885);
--sidebar-border: oklch(0.92 0.004 286.32);
--sidebar-ring: oklch(0.841 0.238 128.85);
}
.green-dark {
--background: oklch(0.141 0.005 285.823);
--foreground: oklch(0.985 0 0);
--card: oklch(0.21 0.006 285.885);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.21 0.006 285.885);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.648 0.2 131.684);
--primary-foreground: oklch(0.986 0.031 120.757);
--secondary: oklch(0.274 0.006 286.033);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.274 0.006 286.033);
--muted-foreground: oklch(0.705 0.015 286.067);
--accent: oklch(0.274 0.006 286.033);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.405 0.101 131.063);
--chart-1: oklch(0.871 0.15 154.449);
--chart-2: oklch(0.723 0.219 149.579);
--chart-3: oklch(0.627 0.194 149.214);
--chart-4: oklch(0.527 0.154 150.069);
--chart-5: oklch(0.448 0.119 151.328);
--sidebar: oklch(0.21 0.006 285.885);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.768 0.233 130.85);
--sidebar-primary-foreground: oklch(0.986 0.031 120.757);
--sidebar-accent: oklch(0.274 0.006 286.033);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.405 0.101 131.063);
}
.orange {
--background: oklch(1 0 0);
--foreground: oklch(0.141 0.005 285.823);
--card: oklch(1 0 0);
--card-foreground: oklch(0.141 0.005 285.823);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.141 0.005 285.823);
--primary: oklch(0.646 0.222 41.116);
--primary-foreground: oklch(0.98 0.016 73.684);
--secondary: oklch(0.967 0.001 286.375);
--secondary-foreground: oklch(0.21 0.006 285.885);
--muted: oklch(0.967 0.001 286.375);
--muted-foreground: oklch(0.552 0.016 285.938);
--accent: oklch(0.967 0.001 286.375);
--accent-foreground: oklch(0.21 0.006 285.885);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.92 0.004 286.32);
--input: oklch(0.92 0.004 286.32);
--ring: oklch(0.75 0.183 55.934);
--chart-1: oklch(0.837 0.128 66.29);
--chart-2: oklch(0.705 0.213 47.604);
--chart-3: oklch(0.646 0.222 41.116);
--chart-4: oklch(0.553 0.195 38.402);
--chart-5: oklch(0.47 0.157 37.304);
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.141 0.005 285.823);
--sidebar-primary: oklch(0.646 0.222 41.116);
--sidebar-primary-foreground: oklch(0.98 0.016 73.684);
--sidebar-accent: oklch(0.967 0.001 286.375);
--sidebar-accent-foreground: oklch(0.21 0.006 285.885);
--sidebar-border: oklch(0.92 0.004 286.32);
--sidebar-ring: oklch(0.75 0.183 55.934);
}
.orange-dark {
--background: oklch(0.141 0.005 285.823);
--foreground: oklch(0.985 0 0);
--card: oklch(0.21 0.006 285.885);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.21 0.006 285.885);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.705 0.213 47.604);
--primary-foreground: oklch(0.98 0.016 73.684);
--secondary: oklch(0.274 0.006 286.033);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.274 0.006 286.033);
--muted-foreground: oklch(0.705 0.015 286.067);
--accent: oklch(0.274 0.006 286.033);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.408 0.123 38.172);
--chart-1: oklch(0.837 0.128 66.29);
--chart-2: oklch(0.705 0.213 47.604);
--chart-3: oklch(0.646 0.222 41.116);
--chart-4: oklch(0.553 0.195 38.402);
--chart-5: oklch(0.47 0.157 37.304);
--sidebar: oklch(0.21 0.006 285.885);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.705 0.213 47.604);
--sidebar-primary-foreground: oklch(0.98 0.016 73.684);
--sidebar-accent: oklch(0.274 0.006 286.033);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.408 0.123 38.172);
}
.red {
--background: oklch(1 0 0);
--foreground: oklch(0.141 0.005 285.823);
--card: oklch(1 0 0);
--card-foreground: oklch(0.141 0.005 285.823);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.141 0.005 285.823);
--primary: oklch(0.577 0.245 27.325);
--primary-foreground: oklch(0.971 0.013 17.38);
--secondary: oklch(0.967 0.001 286.375);
--secondary-foreground: oklch(0.21 0.006 285.885);
--muted: oklch(0.967 0.001 286.375);
--muted-foreground: oklch(0.552 0.016 285.938);
--accent: oklch(0.967 0.001 286.375);
--accent-foreground: oklch(0.21 0.006 285.885);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.92 0.004 286.32);
--input: oklch(0.92 0.004 286.32);
--ring: oklch(0.704 0.191 22.216);
--chart-1: oklch(0.808 0.114 19.571);
--chart-2: oklch(0.637 0.237 25.331);
--chart-3: oklch(0.577 0.245 27.325);
--chart-4: oklch(0.505 0.213 27.518);
--chart-5: oklch(0.444 0.177 26.899);
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.141 0.005 285.823);
--sidebar-primary: oklch(0.577 0.245 27.325);
--sidebar-primary-foreground: oklch(0.971 0.013 17.38);
--sidebar-accent: oklch(0.967 0.001 286.375);
--sidebar-accent-foreground: oklch(0.21 0.006 285.885);
--sidebar-border: oklch(0.92 0.004 286.32);
--sidebar-ring: oklch(0.704 0.191 22.216);
}
.red-dark {
--background: oklch(0.141 0.005 285.823);
--foreground: oklch(0.985 0 0);
--card: oklch(0.21 0.006 285.885);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.21 0.006 285.885);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.637 0.237 25.331);
--primary-foreground: oklch(0.971 0.013 17.38);
--secondary: oklch(0.274 0.006 286.033);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.274 0.006 286.033);
--muted-foreground: oklch(0.705 0.015 286.067);
--accent: oklch(0.274 0.006 286.033);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.396 0.141 25.723);
--chart-1: oklch(0.808 0.114 19.571);
--chart-2: oklch(0.637 0.237 25.331);
--chart-3: oklch(0.577 0.245 27.325);
--chart-4: oklch(0.505 0.213 27.518);
--chart-5: oklch(0.444 0.177 26.899);
--sidebar: oklch(0.21 0.006 285.885);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.637 0.237 25.331);
--sidebar-primary-foreground: oklch(0.971 0.013 17.38);
--sidebar-accent: oklch(0.274 0.006 286.033);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.396 0.141 25.723);
}
.rose {
--background: oklch(1 0 0);
--foreground: oklch(0.141 0.005 285.823);
--card: oklch(1 0 0);
--card-foreground: oklch(0.141 0.005 285.823);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.141 0.005 285.823);
--primary: oklch(0.586 0.253 17.585);
--primary-foreground: oklch(0.969 0.015 12.422);
--secondary: oklch(0.967 0.001 286.375);
--secondary-foreground: oklch(0.21 0.006 285.885);
--muted: oklch(0.967 0.001 286.375);
--muted-foreground: oklch(0.552 0.016 285.938);
--accent: oklch(0.967 0.001 286.375);
--accent-foreground: oklch(0.21 0.006 285.885);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.92 0.004 286.32);
--input: oklch(0.92 0.004 286.32);
--ring: oklch(0.712 0.194 13.428);
--chart-1: oklch(0.81 0.117 11.638);
--chart-2: oklch(0.645 0.246 16.439);
--chart-3: oklch(0.586 0.253 17.585);
--chart-4: oklch(0.514 0.222 16.935);
--chart-5: oklch(0.455 0.188 13.697);
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.141 0.005 285.823);
--sidebar-primary: oklch(0.586 0.253 17.585);
--sidebar-primary-foreground: oklch(0.969 0.015 12.422);
--sidebar-accent: oklch(0.967 0.001 286.375);
--sidebar-accent-foreground: oklch(0.21 0.006 285.885);
--sidebar-border: oklch(0.92 0.004 286.32);
--sidebar-ring: oklch(0.712 0.194 13.428);
}
.rose-dark {
--background: oklch(0.141 0.005 285.823);
--foreground: oklch(0.985 0 0);
--card: oklch(0.21 0.006 285.885);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.21 0.006 285.885);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.645 0.246 16.439);
--primary-foreground: oklch(0.969 0.015 12.422);
--secondary: oklch(0.274 0.006 286.033);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.274 0.006 286.033);
--muted-foreground: oklch(0.705 0.015 286.067);
--accent: oklch(0.274 0.006 286.033);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.41 0.159 10.272);
--chart-1: oklch(0.81 0.117 11.638);
--chart-2: oklch(0.645 0.246 16.439);
--chart-3: oklch(0.586 0.253 17.585);
--chart-4: oklch(0.514 0.222 16.935);
--chart-5: oklch(0.455 0.188 13.697);
--sidebar: oklch(0.21 0.006 285.885);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.645 0.246 16.439);
--sidebar-primary-foreground: oklch(0.969 0.015 12.422);
--sidebar-accent: oklch(0.274 0.006 286.033);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.41 0.159 10.272);
}
.violet {
--background: oklch(1 0 0);
--foreground: oklch(0.141 0.005 285.823);
--card: oklch(1 0 0);
--card-foreground: oklch(0.141 0.005 285.823);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.141 0.005 285.823);
--primary: oklch(0.541 0.281 293.009);
--primary-foreground: oklch(0.969 0.016 293.756);
--secondary: oklch(0.967 0.001 286.375);
--secondary-foreground: oklch(0.21 0.006 285.885);
--muted: oklch(0.967 0.001 286.375);
--muted-foreground: oklch(0.552 0.016 285.938);
--accent: oklch(0.967 0.001 286.375);
--accent-foreground: oklch(0.21 0.006 285.885);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.92 0.004 286.32);
--input: oklch(0.92 0.004 286.32);
--ring: oklch(0.702 0.183 293.541);
--chart-1: oklch(0.811 0.111 293.571);
--chart-2: oklch(0.606 0.25 292.717);
--chart-3: oklch(0.541 0.281 293.009);
--chart-4: oklch(0.491 0.27 292.581);
--chart-5: oklch(0.432 0.232 292.759);
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.141 0.005 285.823);
--sidebar-primary: oklch(0.541 0.281 293.009);
--sidebar-primary-foreground: oklch(0.969 0.016 293.756);
--sidebar-accent: oklch(0.967 0.001 286.375);
--sidebar-accent-foreground: oklch(0.21 0.006 285.885);
--sidebar-border: oklch(0.92 0.004 286.32);
--sidebar-ring: oklch(0.702 0.183 293.541);
}
.violet-dark {
--background: oklch(0.141 0.005 285.823);
--foreground: oklch(0.985 0 0);
--card: oklch(0.21 0.006 285.885);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.21 0.006 285.885);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.606 0.25 292.717);
--primary-foreground: oklch(0.969 0.016 293.756);
--secondary: oklch(0.274 0.006 286.033);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.274 0.006 286.033);
--muted-foreground: oklch(0.705 0.015 286.067);
--accent: oklch(0.274 0.006 286.033);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.38 0.189 293.745);
--chart-1: oklch(0.811 0.111 293.571);
--chart-2: oklch(0.606 0.25 292.717);
--chart-3: oklch(0.541 0.281 293.009);
--chart-4: oklch(0.491 0.27 292.581);
--chart-5: oklch(0.432 0.232 292.759);
--sidebar: oklch(0.21 0.006 285.885);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.606 0.25 292.717);
--sidebar-primary-foreground: oklch(0.969 0.016 293.756);
--sidebar-accent: oklch(0.274 0.006 286.033);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.38 0.189 293.745);
}
.yellow {
--background: oklch(1 0 0);
--foreground: oklch(0.141 0.005 285.823);
--card: oklch(1 0 0);
--card-foreground: oklch(0.141 0.005 285.823);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.141 0.005 285.823);
--primary: oklch(0.852 0.199 91.936);
--primary-foreground: oklch(0.421 0.095 57.708);
--secondary: oklch(0.967 0.001 286.375);
--secondary-foreground: oklch(0.21 0.006 285.885);
--muted: oklch(0.967 0.001 286.375);
--muted-foreground: oklch(0.552 0.016 285.938);
--accent: oklch(0.967 0.001 286.375);
--accent-foreground: oklch(0.21 0.006 285.885);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.92 0.004 286.32);
--input: oklch(0.92 0.004 286.32);
--ring: oklch(0.852 0.199 91.936);
--chart-1: oklch(0.905 0.182 98.111);
--chart-2: oklch(0.795 0.184 86.047);
--chart-3: oklch(0.681 0.162 75.834);
--chart-4: oklch(0.554 0.135 66.442);
--chart-5: oklch(0.476 0.114 61.907);
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.141 0.005 285.823);
--sidebar-primary: oklch(0.681 0.162 75.834);
--sidebar-primary-foreground: oklch(0.987 0.026 102.212);
--sidebar-accent: oklch(0.967 0.001 286.375);
--sidebar-accent-foreground: oklch(0.21 0.006 285.885);
--sidebar-border: oklch(0.92 0.004 286.32);
--sidebar-ring: oklch(0.852 0.199 91.936);
}
.yellow-dark {
--background: oklch(0.141 0.005 285.823);
--foreground: oklch(0.985 0 0);
--card: oklch(0.21 0.006 285.885);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.21 0.006 285.885);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.795 0.184 86.047);
--primary-foreground: oklch(0.421 0.095 57.708);
--secondary: oklch(0.274 0.006 286.033);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.274 0.006 286.033);
--muted-foreground: oklch(0.705 0.015 286.067);
--accent: oklch(0.274 0.006 286.033);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.421 0.095 57.708);
--chart-1: oklch(0.905 0.182 98.111);
--chart-2: oklch(0.795 0.184 86.047);
--chart-3: oklch(0.681 0.162 75.834);
--chart-4: oklch(0.554 0.135 66.442);
--chart-5: oklch(0.476 0.114 61.907);
--sidebar: oklch(0.21 0.006 285.885);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.795 0.184 86.047);
--sidebar-primary-foreground: oklch(0.987 0.026 102.212);
--sidebar-accent: oklch(0.274 0.006 286.033);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.421 0.095 57.708);
}
@layer base { @layer base {
* { * {
@apply border-border outline-ring/50; @apply border-border outline-ring/50;

View File

@@ -12,7 +12,7 @@ import { determineFileType, fileFormatFilter, formatBitrate, formatDurationStrin
import { Calendar, Clock, DownloadCloud, Eye, Info, Loader2, Music, ThumbsUp, Video, File, ListVideo, PackageSearch, AlertCircleIcon, X, Settings2, Clipboard } from "lucide-react"; import { Calendar, Clock, DownloadCloud, Eye, Info, Loader2, Music, ThumbsUp, Video, File, ListVideo, PackageSearch, AlertCircleIcon, X, Settings2, Clipboard } from "lucide-react";
import { FormatSelectionGroup, FormatSelectionGroupItem } from "@/components/custom/formatSelectionGroup"; import { FormatSelectionGroup, FormatSelectionGroupItem } from "@/components/custom/formatSelectionGroup";
import { useEffect, useRef } from "react"; import { useEffect, useRef } from "react";
import { ToggleGroup, ToggleGroupItem } from "@/components/custom/legacyToggleGroup"; import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
import { VideoFormat } from "@/types/video"; import { VideoFormat } from "@/types/video";
// import { PlaylistToggleGroup, PlaylistToggleGroupItem } from "@/components/custom/playlistToggleGroup"; // import { PlaylistToggleGroup, PlaylistToggleGroupItem } from "@/components/custom/playlistToggleGroup";
import { PlaylistSelectionGroup, PlaylistSelectionGroupItem } from "@/components/custom/playlistSelectionGroup"; import { PlaylistSelectionGroup, PlaylistSelectionGroupItem } from "@/components/custom/playlistSelectionGroup";
@@ -360,7 +360,7 @@ export default function DownloaderPage() {
<div className="container mx-auto p-4 space-y-4 relative" ref={containerRef}> <div className="container mx-auto p-4 space-y-4 relative" ref={containerRef}>
<Card className="gap-4"> <Card className="gap-4">
<CardHeader> <CardHeader>
<CardTitle className="flex items-center"><PackageSearch className="size-5 mr-3" />{config.appName} Search</CardTitle> <CardTitle className="flex items-center"><PackageSearch className="size-5 mr-3 stroke-primary" />{config.appName} Search</CardTitle>
</CardHeader> </CardHeader>
<CardContent className="space-y-4"> <CardContent className="space-y-4">
<Form {...searchForm}> <Form {...searchForm}>
@@ -370,7 +370,7 @@ export default function DownloaderPage() {
name="url" name="url"
disabled={isMetadataLoading} disabled={isMetadataLoading}
render={({ field }) => ( render={({ field }) => (
<FormItem className="w-full"> <FormItem className="grow">
<FormControl> <FormControl>
<Input <Input
className="focus-visible:ring-0" className="focus-visible:ring-0"
@@ -396,6 +396,7 @@ export default function DownloaderPage() {
{!isMetadataLoading && !videoUrl && ( {!isMetadataLoading && !videoUrl && (
<Button <Button
type="button" type="button"
variant="outline"
size="icon" size="icon"
disabled={isMetadataLoading} disabled={isMetadataLoading}
onClick={async () => { onClick={async () => {
@@ -485,7 +486,7 @@ export default function DownloaderPage() {
<Info className="w-3 h-3 mr-2" /> <Info className="w-3 h-3 mr-2" />
<span className="text-xs">Extracted from {videoMetadata.extractor ? videoMetadata.extractor.charAt(0).toUpperCase() + videoMetadata.extractor.slice(1) : 'Unknown'}</span> <span className="text-xs">Extracted from {videoMetadata.extractor ? videoMetadata.extractor.charAt(0).toUpperCase() + videoMetadata.extractor.slice(1) : 'Unknown'}</span>
</div> </div>
<div className="spacer mb-10"></div> <div className="spacer mb-12"></div>
</div> </div>
</div> </div>
<div className="flex flex-col w-full pl-4"> <div className="flex flex-col w-full pl-4">
@@ -525,7 +526,7 @@ export default function DownloaderPage() {
<div className="flex gap-2 flex-wrap items-center"> <div className="flex gap-2 flex-wrap items-center">
{subtitleLanguages.map((lang) => ( {subtitleLanguages.map((lang) => (
<ToggleGroupItem <ToggleGroupItem
className="text-xs text-nowrap border-2 data-[state=on]:border-2 data-[state=on]:border-primary data-[state=on]:bg-muted/70 hover:bg-muted/70" className="text-xs text-nowrap border-2 data-[state=on]:border-2 data-[state=on]:border-primary data-[state=on]:bg-primary/10 hover:bg-muted/70"
value={lang.code} value={lang.code}
size="sm" size="sm"
aria-label={lang.lang} aria-label={lang.lang}
@@ -615,7 +616,7 @@ export default function DownloaderPage() {
</> </>
)} )}
</FormatSelectionGroup> </FormatSelectionGroup>
<div className="spacer mb-10"></div> <div className="spacer mb-12"></div>
</div> </div>
</TabsContent> </TabsContent>
<TabsContent value="combine"> <TabsContent value="combine">
@@ -632,7 +633,7 @@ export default function DownloaderPage() {
<div className="flex gap-2 flex-wrap items-center"> <div className="flex gap-2 flex-wrap items-center">
{subtitleLanguages.map((lang) => ( {subtitleLanguages.map((lang) => (
<ToggleGroupItem <ToggleGroupItem
className="text-xs text-nowrap border-2 data-[state=on]:border-2 data-[state=on]:border-primary data-[state=on]:bg-muted/70 hover:bg-muted/70" className="text-xs text-nowrap border-2 data-[state=on]:border-2 data-[state=on]:border-primary data-[state=on]:bg-primary/10 hover:bg-muted/70"
value={lang.code} value={lang.code}
size="sm" size="sm"
aria-label={lang.lang} aria-label={lang.lang}
@@ -696,14 +697,14 @@ export default function DownloaderPage() {
</FormatSelectionGroup> </FormatSelectionGroup>
{(!videoOnlyFormats || videoOnlyFormats.length === 0 || !audioOnlyFormats || audioOnlyFormats.length === 0) && ( {(!videoOnlyFormats || videoOnlyFormats.length === 0 || !audioOnlyFormats || audioOnlyFormats.length === 0) && (
<Alert> <Alert>
<AlertCircleIcon /> <AlertCircleIcon className="size-4 stroke-primary" />
<AlertTitle>Unable to use Combine Mode!</AlertTitle> <AlertTitle>Unable to use Combine Mode!</AlertTitle>
<AlertDescription> <AlertDescription>
Cannot use combine mode for this video as it does not have both audio and video formats available. Use Selective Mode or try another video. Cannot use combine mode for this video as it does not have both audio and video formats available. Use Selective Mode or try another video.
</AlertDescription> </AlertDescription>
</Alert> </Alert>
)} )}
<div className="spacer mb-10"></div> <div className="spacer mb-12"></div>
</div> </div>
</TabsContent> </TabsContent>
</Tabs> </Tabs>
@@ -758,7 +759,7 @@ export default function DownloaderPage() {
<Info className="w-3 h-3 mr-2" /> <Info className="w-3 h-3 mr-2" />
<span className="text-xs">Extracted from {videoMetadata.entries[0].extractor ? videoMetadata.entries[0].extractor.charAt(0).toUpperCase() + videoMetadata.entries[0].extractor.slice(1) : 'Unknown'}</span> <span className="text-xs">Extracted from {videoMetadata.entries[0].extractor ? videoMetadata.entries[0].extractor.charAt(0).toUpperCase() + videoMetadata.entries[0].extractor.slice(1) : 'Unknown'}</span>
</div> </div>
<div className="spacer mb-10"></div> <div className="spacer mb-12"></div>
</div> </div>
</div> </div>
<div className="flex flex-col w-full pl-4"> <div className="flex flex-col w-full pl-4">
@@ -792,7 +793,7 @@ export default function DownloaderPage() {
<div className="flex gap-2 flex-wrap items-center"> <div className="flex gap-2 flex-wrap items-center">
{subtitleLanguages.map((lang) => ( {subtitleLanguages.map((lang) => (
<ToggleGroupItem <ToggleGroupItem
className="text-xs text-nowrap border-2 data-[state=on]:border-2 data-[state=on]:border-primary data-[state=on]:bg-muted/70 hover:bg-muted/70" className="text-xs text-nowrap border-2 data-[state=on]:border-2 data-[state=on]:border-primary data-[state=on]:bg-primary/10 hover:bg-muted/70"
value={lang.code} value={lang.code}
size="sm" size="sm"
aria-label={lang.lang} aria-label={lang.lang}
@@ -878,7 +879,7 @@ export default function DownloaderPage() {
</> </>
)} )}
</FormatSelectionGroup> </FormatSelectionGroup>
<div className="spacer mb-10"></div> <div className="spacer mb-12"></div>
</div> </div>
</TabsContent> </TabsContent>
<TabsContent value="combine"> <TabsContent value="combine">
@@ -896,7 +897,7 @@ export default function DownloaderPage() {
<div className="flex gap-2 flex-wrap items-center"> <div className="flex gap-2 flex-wrap items-center">
{subtitleLanguages.map((lang) => ( {subtitleLanguages.map((lang) => (
<ToggleGroupItem <ToggleGroupItem
className="text-xs text-nowrap border-2 data-[state=on]:border-2 data-[state=on]:border-primary data-[state=on]:bg-muted/70 hover:bg-muted/70" className="text-xs text-nowrap border-2 data-[state=on]:border-2 data-[state=on]:border-primary data-[state=on]:bg-primary/10 hover:bg-muted/70"
value={lang.code} value={lang.code}
size="sm" size="sm"
aria-label={lang.lang} aria-label={lang.lang}
@@ -952,14 +953,14 @@ export default function DownloaderPage() {
</FormatSelectionGroup> </FormatSelectionGroup>
{(!videoOnlyFormats || videoOnlyFormats.length === 0 || !audioOnlyFormats || audioOnlyFormats.length === 0) && ( {(!videoOnlyFormats || videoOnlyFormats.length === 0 || !audioOnlyFormats || audioOnlyFormats.length === 0) && (
<Alert> <Alert>
<AlertCircleIcon /> <AlertCircleIcon className="size-4 stroke-primary" />
<AlertTitle>Unable to use Combine Mode!</AlertTitle> <AlertTitle>Unable to use Combine Mode!</AlertTitle>
<AlertDescription> <AlertDescription>
Cannot use combine mode for this video as it does not have both audio and video formats available. Use Selective Mode or try another video. Cannot use combine mode for this video as it does not have both audio and video formats available. Use Selective Mode or try another video.
</AlertDescription> </AlertDescription>
</Alert> </Alert>
)} )}
<div className="spacer mb-10"></div> <div className="spacer mb-12"></div>
</div> </div>
</TabsContent> </TabsContent>
</Tabs> </Tabs>
@@ -971,13 +972,13 @@ export default function DownloaderPage() {
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<div className="flex justify-center items-center p-3 rounded-md border border-border"> <div className="flex justify-center items-center p-3 rounded-md border border-border">
{selectedFormatFileType && (selectedFormatFileType === 'video' || selectedFormatFileType === 'video+audio') && ( {selectedFormatFileType && (selectedFormatFileType === 'video' || selectedFormatFileType === 'video+audio') && (
<Video className="w-4 h-4" /> <Video className="w-4 h-4 stroke-primary" />
)} )}
{selectedFormatFileType && selectedFormatFileType === 'audio' && ( {selectedFormatFileType && selectedFormatFileType === 'audio' && (
<Music className="w-4 h-4" /> <Music className="w-4 h-4 stroke-primary" />
)} )}
{(!selectedFormatFileType) || (selectedFormatFileType && selectedFormatFileType !== 'video' && selectedFormatFileType !== 'audio' && selectedFormatFileType !== 'video+audio') && ( {(!selectedFormatFileType) || (selectedFormatFileType && selectedFormatFileType !== 'video' && selectedFormatFileType !== 'audio' && selectedFormatFileType !== 'video+audio') && (
<File className="w-4 h-4" /> <File className="w-4 h-4 stroke-primary" />
)} )}
</div> </div>
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
@@ -1021,7 +1022,7 @@ export default function DownloaderPage() {
<TabsContent value="options"> <TabsContent value="options">
{useCustomCommands ? ( {useCustomCommands ? (
<Alert className="mt-2 mb-3"> <Alert className="mt-2 mb-3">
<AlertCircleIcon /> <AlertCircleIcon className="size-4 stroke-primary" />
<AlertTitle className="text-sm">Options Unavailable!</AlertTitle> <AlertTitle className="text-sm">Options Unavailable!</AlertTitle>
<AlertDescription className="text-xs"> <AlertDescription className="text-xs">
You cannot use these options when custom commands are enabled. To use these options, disable custom commands from Settings. You cannot use these options when custom commands are enabled. To use these options, disable custom commands from Settings.
@@ -1033,7 +1034,7 @@ export default function DownloaderPage() {
{(selectedFormatFileType && (selectedFormatFileType === 'video' || selectedFormatFileType === 'video+audio')) || activeDownloadModeTab === 'combine' ? ( {(selectedFormatFileType && (selectedFormatFileType === 'video' || selectedFormatFileType === 'video+audio')) || activeDownloadModeTab === 'combine' ? (
<RadioGroup <RadioGroup
orientation="horizontal" orientation="horizontal"
className="flex items-center gap-4 flex-wrap" className="flex items-center gap-4 flex-wrap my-2"
value={downloadConfiguration.output_format ?? 'auto'} value={downloadConfiguration.output_format ?? 'auto'}
onValueChange={(value) => setDownloadConfigurationKey('output_format', value)} onValueChange={(value) => setDownloadConfigurationKey('output_format', value)}
disabled={useCustomCommands} disabled={useCustomCommands}
@@ -1058,7 +1059,7 @@ export default function DownloaderPage() {
) : selectedFormatFileType && selectedFormatFileType === 'audio' ? ( ) : selectedFormatFileType && selectedFormatFileType === 'audio' ? (
<RadioGroup <RadioGroup
orientation="horizontal" orientation="horizontal"
className="flex items-center gap-4 flex-wrap" className="flex items-center gap-4 flex-wrap my-2"
value={downloadConfiguration.output_format ?? 'auto'} value={downloadConfiguration.output_format ?? 'auto'}
onValueChange={(value) => setDownloadConfigurationKey('output_format', value)} onValueChange={(value) => setDownloadConfigurationKey('output_format', value)}
disabled={useCustomCommands} disabled={useCustomCommands}
@@ -1083,7 +1084,7 @@ export default function DownloaderPage() {
) : ( ) : (
<RadioGroup <RadioGroup
orientation="horizontal" orientation="horizontal"
className="flex items-center gap-4 flex-wrap" className="flex items-center gap-4 flex-wrap my-2"
value={downloadConfiguration.output_format ?? 'auto'} value={downloadConfiguration.output_format ?? 'auto'}
onValueChange={(value) => setDownloadConfigurationKey('output_format', value)} onValueChange={(value) => setDownloadConfigurationKey('output_format', value)}
disabled={useCustomCommands} disabled={useCustomCommands}
@@ -1123,7 +1124,7 @@ export default function DownloaderPage() {
<Label className="text-xs my-3">Sponsorblock Mode</Label> <Label className="text-xs my-3">Sponsorblock Mode</Label>
<RadioGroup <RadioGroup
orientation="horizontal" orientation="horizontal"
className="flex items-center gap-4 flex-wrap" className="flex items-center gap-4 flex-wrap my-2"
value={downloadConfiguration.sponsorblock ?? 'auto'} value={downloadConfiguration.sponsorblock ?? 'auto'}
onValueChange={(value) => setDownloadConfigurationKey('sponsorblock', value)} onValueChange={(value) => setDownloadConfigurationKey('sponsorblock', value)}
disabled={useCustomCommands} disabled={useCustomCommands}
@@ -1167,7 +1168,7 @@ export default function DownloaderPage() {
<TabsContent value="commands"> <TabsContent value="commands">
{!useCustomCommands ? ( {!useCustomCommands ? (
<Alert className="mt-2 mb-3"> <Alert className="mt-2 mb-3">
<AlertCircleIcon /> <AlertCircleIcon className="size-4 stroke-primary" />
<AlertTitle className="text-sm">Enable Custom Commands!</AlertTitle> <AlertTitle className="text-sm">Enable Custom Commands!</AlertTitle>
<AlertDescription className="text-xs"> <AlertDescription className="text-xs">
To run custom commands for downloads, please enable it from the Settings. To run custom commands for downloads, please enable it from the Settings.
@@ -1181,7 +1182,7 @@ export default function DownloaderPage() {
) : ( ) : (
<RadioGroup <RadioGroup
orientation="vertical" orientation="vertical"
className="flex flex-col gap-2" className="flex flex-col gap-2 my-2"
disabled={!useCustomCommands} disabled={!useCustomCommands}
value={downloadConfiguration.custom_command} value={downloadConfiguration.custom_command}
onValueChange={(value) => setDownloadConfigurationKey('custom_command', value)} onValueChange={(value) => setDownloadConfigurationKey('custom_command', value)}

View File

@@ -158,7 +158,7 @@ export default function LibraryPage() {
<div className="w-full flex items-center justify-between mb-4"> <div className="w-full flex items-center justify-between mb-4">
<TabsList> <TabsList>
<TabsTrigger value="completed">Completed {completedDownloads.length > 0 && (`(${completedDownloads.length})`)}</TabsTrigger> <TabsTrigger value="completed">Completed {completedDownloads.length > 0 && (`(${completedDownloads.length})`)}</TabsTrigger>
<TabsTrigger value="incomplete">Incomplete {(incompleteDownloads.length > 0 && ongoingDownloads.length <= 0) && (`(${incompleteDownloads.length})`)} {ongoingDownloads.length > 0 && (<Badge className="h-4 min-w-4 rounded-full px-1 font-mono tabular-nums ml-1">{ongoingDownloads.length}</Badge>)}</TabsTrigger> <TabsTrigger value="incomplete">Incomplete {(incompleteDownloads.length > 0 && ongoingDownloads.length <= 0) && (`(${incompleteDownloads.length})`)} {ongoingDownloads.length > 0 && (<Badge className="h-4 min-w-4 rounded-full px-1 font-mono tabular-nums ml-1.5 mt-0.5">{ongoingDownloads.length}</Badge>)}</TabsTrigger>
</TabsList> </TabsList>
<AlertDialog> <AlertDialog>
<AlertDialogTrigger asChild> <AlertDialogTrigger asChild>
@@ -206,13 +206,13 @@ export default function LibraryPage() {
</AspectRatio> </AspectRatio>
<span className="w-full flex items-center justify-center text-xs border border-border py-1 px-2 rounded"> <span className="w-full flex items-center justify-center text-xs border border-border py-1 px-2 rounded">
{state.filetype && (state.filetype === 'video' || state.filetype === 'video+audio') && ( {state.filetype && (state.filetype === 'video' || state.filetype === 'video+audio') && (
<Video className="w-4 h-4 mr-2" /> <Video className="w-4 h-4 mr-2 stroke-primary" />
)} )}
{state.filetype && state.filetype === 'audio' && ( {state.filetype && state.filetype === 'audio' && (
<Music className="w-4 h-4 mr-2" /> <Music className="w-4 h-4 mr-2 stroke-primary" />
)} )}
{(!state.filetype) || (state.filetype && state.filetype !== 'video' && state.filetype !== 'audio' && state.filetype !== 'video+audio') && ( {(!state.filetype) || (state.filetype && state.filetype !== 'video' && state.filetype !== 'audio' && state.filetype !== 'video+audio') && (
<File className="w-4 h-4 mr-2" /> <File className="w-4 h-4 mr-2 stroke-primary" />
)} )}
{state.ext?.toUpperCase()} {state.resolution ? `(${state.resolution})` : null} {state.ext?.toUpperCase()} {state.resolution ? `(${state.resolution})` : null}
</span> </span>
@@ -220,7 +220,7 @@ export default function LibraryPage() {
<div className="w-full flex flex-col justify-between gap-2"> <div className="w-full flex flex-col justify-between gap-2">
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<h4 className="">{state.title}</h4> <h4 className="">{state.title}</h4>
<p className="text-xs text-muted-foreground">{state.channel ? state.channel : 'unknown'} {state.host ? `${state.host}` : 'unknown'}</p> <p className="text-xs text-muted-foreground">{state.channel ? state.channel : 'unknown'} {state.host ? <><span className="text-primary"></span> {state.host}</> : 'unknown'}</p>
<div className="flex items-center mt-1"> <div className="flex items-center mt-1">
<span className="text-xs text-muted-foreground flex items-center pr-3"><Clock className="w-4 h-4 mr-2"/> {state.duration_string ? formatDurationString(state.duration_string) : 'unknown'}</span> <span className="text-xs text-muted-foreground flex items-center pr-3"><Clock className="w-4 h-4 mr-2"/> {state.duration_string ? formatDurationString(state.duration_string) : 'unknown'}</span>
<Separator orientation="vertical" /> <Separator orientation="vertical" />
@@ -351,13 +351,13 @@ export default function LibraryPage() {
{state.ext && ( {state.ext && (
<span className="w-full flex items-center justify-center text-xs border border-border py-1 px-2 rounded"> <span className="w-full flex items-center justify-center text-xs border border-border py-1 px-2 rounded">
{state.filetype && (state.filetype === 'video' || state.filetype === 'video+audio') && ( {state.filetype && (state.filetype === 'video' || state.filetype === 'video+audio') && (
<Video className="w-4 h-4 mr-2" /> <Video className="w-4 h-4 mr-2 stroke-primary" />
)} )}
{state.filetype && state.filetype === 'audio' && ( {state.filetype && state.filetype === 'audio' && (
<Music className="w-4 h-4 mr-2" /> <Music className="w-4 h-4 mr-2 stroke-primary" />
)} )}
{(!state.filetype) || (state.filetype && state.filetype !== 'video' && state.filetype !== 'audio' && state.filetype !== 'video+audio') && ( {(!state.filetype) || (state.filetype && state.filetype !== 'video' && state.filetype !== 'audio' && state.filetype !== 'video+audio') && (
<File className="w-4 h-4 mr-2" /> <File className="w-4 h-4 mr-2 stroke-primary" />
)} )}
{state.ext.toUpperCase()} {state.resolution ? `(${state.resolution})` : null} {state.ext.toUpperCase()} {state.resolution ? `(${state.resolution})` : null}
</span> </span>
@@ -380,9 +380,9 @@ export default function LibraryPage() {
}</span> }</span>
</div> </div>
)} )}
<div className="text-xs text-muted-foreground">{ state.download_status && ( <div className="text-xs text-muted-foreground">
`${state.download_status === 'downloading' && state.status === 'finished' ? 'Processing' : state.download_status.charAt(0).toUpperCase() + state.download_status.slice(1)} ${debugMode && state.download_id ? `• ID: ${state.download_id.toUpperCase()}` : ""} ${state.download_status === 'downloading' && state.status !== 'finished' && state.speed ? `• Speed: ${formatSpeed(state.speed)}` : ""} ${state.download_status === 'downloading' && state.eta ? `• ETA: ${formatSecToTimeString(state.eta)}` : ""}` {state.download_status && state.download_status === 'downloading' && state.status === 'finished' ? 'Processing' : state.download_status.charAt(0).toUpperCase() + state.download_status.slice(1)} {debugMode && state.download_id ? <><span className="text-primary"></span> ID: {state.download_id.toUpperCase()}</> : ""} {state.download_status === 'downloading' && state.status !== 'finished' && state.speed ? <><span className="text-primary"></span> Speed: {formatSpeed(state.speed)}</> : ""} {state.download_status === 'downloading' && state.eta ? <><span className="text-primary"></span> ETA: {formatSecToTimeString(state.eta)}</> : ""}
)}</div> </div>
</div> </div>
<div className="w-full flex items-center gap-2 mt-2"> <div className="w-full flex items-center gap-2 mt-2">
{state.download_status === 'paused' ? ( {state.download_status === 'paused' ? (

View File

@@ -27,7 +27,7 @@ import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import * as fs from "@tauri-apps/plugin-fs"; import * as fs from "@tauri-apps/plugin-fs";
import { join } from "@tauri-apps/api/path"; import { join } from "@tauri-apps/api/path";
import { formatSpeed, generateID } from "@/utils"; import { formatSpeed, generateID } from "@/utils";
import { ToggleGroup, ToggleGroupItem } from "@/components/custom/legacyToggleGroup"; import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { isPermissionGranted, requestPermission } from '@tauri-apps/plugin-notification'; import { isPermissionGranted, requestPermission } from '@tauri-apps/plugin-notification';
@@ -94,6 +94,7 @@ export default function SettingsPage() {
const ytDlpUpdateChannel = useSettingsPageStatesStore(state => state.settings.ytdlp_update_channel); const ytDlpUpdateChannel = useSettingsPageStatesStore(state => state.settings.ytdlp_update_channel);
const ytDlpAutoUpdate = useSettingsPageStatesStore(state => state.settings.ytdlp_auto_update); const ytDlpAutoUpdate = useSettingsPageStatesStore(state => state.settings.ytdlp_auto_update);
const appTheme = useSettingsPageStatesStore(state => state.settings.theme); const appTheme = useSettingsPageStatesStore(state => state.settings.theme);
const appColorScheme = useSettingsPageStatesStore(state => state.settings.color_scheme);
const maxParallelDownloads = useSettingsPageStatesStore(state => state.settings.max_parallel_downloads); const maxParallelDownloads = useSettingsPageStatesStore(state => state.settings.max_parallel_downloads);
const maxRetries = useSettingsPageStatesStore(state => state.settings.max_retries); const maxRetries = useSettingsPageStatesStore(state => state.settings.max_retries);
const preferVideoOverPlaylist = useSettingsPageStatesStore(state => state.settings.prefer_video_over_playlist); const preferVideoOverPlaylist = useSettingsPageStatesStore(state => state.settings.prefer_video_over_playlist);
@@ -160,7 +161,18 @@ export default function SettingsPage() {
{ value: 'system', icon: Monitor, label: 'System' }, { value: 'system', icon: Monitor, label: 'System' },
]; ];
const sponsorblockCategories = [ const colorSchemeOptions: { value: string; label: string }[] = [
{ value: 'default', label: 'Default' },
{ value: 'blue', label: 'Blue' },
{ value: 'green', label: 'Green' },
{ value: 'orange', label: 'Orange' },
{ value: 'red', label: 'Red' },
{ value: 'rose', label: 'Rose' },
{ value: 'violet', label: 'Violet' },
{ value: 'yellow', label: 'Yellow' },
];
const sponsorblockCategories: { code: string; label: string }[] = [
{ code: 'sponsor', label: 'Sponsorship' }, { code: 'sponsor', label: 'Sponsorship' },
{ code: 'intro', label: 'Intro' }, { code: 'intro', label: 'Intro' },
{ code: 'outro', label: 'Outro' }, { code: 'outro', label: 'Outro' },
@@ -374,10 +386,10 @@ export default function SettingsPage() {
useEffect(() => { useEffect(() => {
const updateTheme = async () => { const updateTheme = async () => {
setTheme(appTheme); setTheme(appTheme, appColorScheme);
} }
updateTheme().catch(console.error); updateTheme().catch(console.error);
}, [appTheme]); }, [appTheme, appColorScheme]);
return ( return (
<div className="container mx-auto p-4 space-y-4 min-h-screen"> <div className="container mx-auto p-4 space-y-4 min-h-screen">
@@ -427,7 +439,7 @@ export default function SettingsPage() {
<Card className="p-4 space-y-4 my-4"> <Card className="p-4 space-y-4 my-4">
<div className="w-full flex gap-4 items-center justify-between"> <div className="w-full flex gap-4 items-center justify-between">
<div className="flex gap-4 items-center"> <div className="flex gap-4 items-center">
<div className="imgwrapper w-10 h-10 flex items-center justify-center bg-linear-65 from-[#FF43D0] to-[#4444FF] rounded-md overflow-hidden border border-border"> <div className="imgwrapper w-10 h-10 flex items-center justify-center bg-linear-65 from-[#FF43D0] to-[#4444FF] customscheme:from-chart-1 customscheme:to-chart-5 rounded-md overflow-hidden border border-border">
<Terminal className="size-5 text-white" /> <Terminal className="size-5 text-white" />
</div> </div>
<div className="flex flex-col"> <div className="flex flex-col">
@@ -624,6 +636,47 @@ export default function SettingsPage() {
))} ))}
</div> </div>
</div> </div>
<div className="app-color-scheme">
<h3 className="font-semibold">Color Scheme</h3>
<p className="text-xs text-muted-foreground mb-3">Choose app interface color scheme</p>
<ToggleGroup
type="single"
variant="outline"
className="flex flex-col items-start gap-2 mt-1"
value={appColorScheme}
onValueChange={(value) => saveSettingsKey('color_scheme', value)}
>
<div className="flex gap-2 flex-wrap items-center">
{colorSchemeOptions.map(({ value, label }) => (
<ToggleGroupItem
key={value}
className="text-xs text-nowrap border-2 data-[state=on]:border-2 data-[state=on]:border-primary data-[state=on]:bg-primary/10 hover:bg-muted/70"
size="sm"
value={value}
>
<span className="relative flex gap-1 items-center">
{
<span
className={cn(
'inline-block w-3 h-3 rounded-full border border-border',
value === 'default' && 'bg-neutral-900 dark:bg-neutral-100',
value === 'blue' && 'bg-blue-500',
value === 'green' && 'bg-green-500',
value === 'orange' && 'bg-orange-500',
value === 'red' && 'bg-red-500',
value === 'rose' && 'bg-rose-500',
value === 'violet' && 'bg-violet-500',
value === 'yellow' && 'bg-yellow-500',
)}
/>
}
{label}
</span>
</ToggleGroupItem>
))}
</div>
</ToggleGroup>
</div>
</TabsContent> </TabsContent>
<TabsContent key="folders" value="folders" className="flex flex-col gap-4 min-h-[425px]"> <TabsContent key="folders" value="folders" className="flex flex-col gap-4 min-h-[425px]">
<div className="download-dir"> <div className="download-dir">
@@ -1097,7 +1150,7 @@ export default function SettingsPage() {
{sponsorblockCategories.map((category) => ( {sponsorblockCategories.map((category) => (
category.code !== "poi_highlight" && ( category.code !== "poi_highlight" && (
<ToggleGroupItem <ToggleGroupItem
className="text-xs text-nowrap border-2 data-[state=on]:border-2 data-[state=on]:border-primary data-[state=on]:bg-muted/70 hover:bg-muted/70" className="text-xs text-nowrap border-2 data-[state=on]:border-2 data-[state=on]:border-primary data-[state=on]:bg-primary/10 hover:bg-muted/70"
value={category.code} value={category.code}
size="sm" size="sm"
aria-label={category.label} aria-label={category.label}
@@ -1143,7 +1196,7 @@ export default function SettingsPage() {
<div className="flex gap-2 flex-wrap items-center"> <div className="flex gap-2 flex-wrap items-center">
{sponsorblockCategories.map((category) => ( {sponsorblockCategories.map((category) => (
<ToggleGroupItem <ToggleGroupItem
className="text-xs text-nowrap border-2 data-[state=on]:border-2 data-[state=on]:border-primary data-[state=on]:bg-muted/70 hover:bg-muted/70" className="text-xs text-nowrap border-2 data-[state=on]:border-2 data-[state=on]:border-primary data-[state=on]:bg-primary/10 hover:bg-muted/70"
value={category.code} value={category.code}
size="sm" size="sm"
aria-label={category.label} aria-label={category.label}
@@ -1212,7 +1265,7 @@ export default function SettingsPage() {
<h3 className="font-semibold">Custom Commands</h3> <h3 className="font-semibold">Custom Commands</h3>
<p className="text-xs text-muted-foreground mb-3"> Run custom yt-dlp commands for your downloads</p> <p className="text-xs text-muted-foreground mb-3"> Run custom yt-dlp commands for your downloads</p>
<Alert className="mb-3"> <Alert className="mb-3">
<TriangleAlert /> <TriangleAlert className="size-4 stroke-primary" />
<AlertTitle className="text-sm">Most Settings will be Disabled!</AlertTitle> <AlertTitle className="text-sm">Most Settings will be Disabled!</AlertTitle>
<AlertDescription className="text-xs"> <AlertDescription className="text-xs">
This feature is intended for advanced users only. Turning it on will disable most other settings in the app. Make sure you know what you are doing before using this feature, otherwise things could break easily. This feature is intended for advanced users only. Turning it on will disable most other settings in the app. Make sure you know what you are doing before using this feature, otherwise things could break easily.
@@ -1240,7 +1293,7 @@ export default function SettingsPage() {
<FormItem className="w-full"> <FormItem className="w-full">
<FormControl> <FormControl>
<Textarea <Textarea
className="focus-visible:ring-0" className="focus-visible:ring-0 min-h-26"
placeholder="Enter yt-dlp command line arguments (no need to start with 'yt-dlp', already passed args: url, output paths, selected formats, selected subtitles, playlist item. also, bulk downloading is not supported)" placeholder="Enter yt-dlp command line arguments (no need to start with 'yt-dlp', already passed args: url, output paths, selected formats, selected subtitles, playlist item. also, bulk downloading is not supported)"
{...field} {...field}
/> />
@@ -1360,7 +1413,7 @@ export default function SettingsPage() {
<Card className="p-4 space-y-4 my-4"> <Card className="p-4 space-y-4 my-4">
<div className="w-full flex gap-4 items-center justify-between"> <div className="w-full flex gap-4 items-center justify-between">
<div className="flex gap-4 items-center"> <div className="flex gap-4 items-center">
<div className="imgwrapper w-10 h-10 flex items-center justify-center bg-linear-65 from-[#FF43D0] to-[#4444FF] rounded-md overflow-hidden border border-border"> <div className="imgwrapper w-10 h-10 flex items-center justify-center bg-linear-65 from-[#FF43D0] to-[#4444FF] customscheme:from-chart-1 customscheme:to-chart-5 rounded-md overflow-hidden border border-border">
<Radio className="size-5 text-white" /> <Radio className="size-5 text-white" />
</div> </div>
<div className="flex flex-col"> <div className="flex flex-col">
@@ -1435,7 +1488,7 @@ export default function SettingsPage() {
<div className="flex items-center gap-4 mb-4"> <div className="flex items-center gap-4 mb-4">
<SlidingButton <SlidingButton
slidingContent={ slidingContent={
<div className="flex items-center justify-center gap-2 text-white dark:text-black"> <div className="flex items-center justify-center gap-2 text-primary-foreground">
<ArrowRight className="size-4" /> <ArrowRight className="size-4" />
<span>Get Now</span> <span>Get Now</span>
</div> </div>
@@ -1443,7 +1496,7 @@ export default function SettingsPage() {
onClick={() => openLink('https://chromewebstore.google.com/detail/neo-downloader-plus/mehopeailfjmiloiiohgicphlcgpompf', 'chrome')} onClick={() => openLink('https://chromewebstore.google.com/detail/neo-downloader-plus/mehopeailfjmiloiiohgicphlcgpompf', 'chrome')}
> >
<span className="font-semibold flex items-center gap-2"> <span className="font-semibold flex items-center gap-2">
<svg className="size-4 fill-white dark:fill-black" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> <svg className="size-4 fill-primary-foreground" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path d="M0 256C0 209.4 12.5 165.6 34.3 127.1L144.1 318.3C166 357.5 207.9 384 256 384C270.3 384 283.1 381.7 296.8 377.4L220.5 509.6C95.9 492.3 0 385.3 0 256zM365.1 321.6C377.4 302.4 384 279.1 384 256C384 217.8 367.2 183.5 340.7 160H493.4C505.4 189.6 512 222.1 512 256C512 397.4 397.4 511.1 256 512L365.1 321.6zM477.8 128H256C193.1 128 142.3 172.1 130.5 230.7L54.2 98.5C101 38.5 174 0 256 0C350.8 0 433.5 51.5 477.8 128V128zM168 256C168 207.4 207.4 168 256 168C304.6 168 344 207.4 344 256C344 304.6 304.6 344 256 344C207.4 344 168 304.6 168 256z"/> <path d="M0 256C0 209.4 12.5 165.6 34.3 127.1L144.1 318.3C166 357.5 207.9 384 256 384C270.3 384 283.1 381.7 296.8 377.4L220.5 509.6C95.9 492.3 0 385.3 0 256zM365.1 321.6C377.4 302.4 384 279.1 384 256C384 217.8 367.2 183.5 340.7 160H493.4C505.4 189.6 512 222.1 512 256C512 397.4 397.4 511.1 256 512L365.1 321.6zM477.8 128H256C193.1 128 142.3 172.1 130.5 230.7L54.2 98.5C101 38.5 174 0 256 0C350.8 0 433.5 51.5 477.8 128V128zM168 256C168 207.4 207.4 168 256 168C304.6 168 344 207.4 344 256C344 304.6 304.6 344 256 344C207.4 344 168 304.6 168 256z"/>
</svg> </svg>
Get Chrome Extension Get Chrome Extension
@@ -1452,7 +1505,7 @@ export default function SettingsPage() {
</SlidingButton> </SlidingButton>
<SlidingButton <SlidingButton
slidingContent={ slidingContent={
<div className="flex items-center justify-center gap-2 text-white dark:text-black"> <div className="flex items-center justify-center gap-2 text-primary-foreground">
<ArrowRight className="size-4" /> <ArrowRight className="size-4" />
<span>Get Now</span> <span>Get Now</span>
</div> </div>
@@ -1460,7 +1513,7 @@ export default function SettingsPage() {
onClick={() => openLink('https://addons.mozilla.org/en-US/firefox/addon/neo-downloader-plus', 'firefox')} onClick={() => openLink('https://addons.mozilla.org/en-US/firefox/addon/neo-downloader-plus', 'firefox')}
> >
<span className="font-semibold flex items-center gap-2"> <span className="font-semibold flex items-center gap-2">
<svg className="size-4 fill-white dark:fill-black" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> <svg className="size-4 fill-primary-foreground" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path d="M130.2 127.5C130.4 127.6 130.3 127.6 130.2 127.5V127.5zM481.6 172.9C471 147.4 449.6 119.9 432.7 111.2C446.4 138.1 454.4 165 457.4 185.2C457.4 185.3 457.4 185.4 457.5 185.6C429.9 116.8 383.1 89.1 344.9 28.7C329.9 5.1 334 3.5 331.8 4.1L331.7 4.2C285 30.1 256.4 82.5 249.1 126.9C232.5 127.8 216.2 131.9 201.2 139C199.8 139.6 198.7 140.7 198.1 142C197.4 143.4 197.2 144.9 197.5 146.3C197.7 147.2 198.1 148 198.6 148.6C199.1 149.3 199.8 149.9 200.5 150.3C201.3 150.7 202.1 151 203 151.1C203.8 151.1 204.7 151 205.5 150.8L206 150.6C221.5 143.3 238.4 139.4 255.5 139.2C318.4 138.7 352.7 183.3 363.2 201.5C350.2 192.4 326.8 183.3 304.3 187.2C392.1 231.1 368.5 381.8 247 376.4C187.5 373.8 149.9 325.5 146.4 285.6C146.4 285.6 157.7 243.7 227 243.7C234.5 243.7 256 222.8 256.4 216.7C256.3 214.7 213.8 197.8 197.3 181.5C188.4 172.8 184.2 168.6 180.5 165.5C178.5 163.8 176.4 162.2 174.2 160.7C168.6 141.2 168.4 120.6 173.5 101.1C148.5 112.5 129 130.5 114.8 146.4H114.7C105 134.2 105.7 93.8 106.3 85.3C106.1 84.8 99 89 98.1 89.7C89.5 95.7 81.6 102.6 74.3 110.1C58 126.7 30.1 160.2 18.8 211.3C14.2 231.7 12 255.7 12 263.6C12 398.3 121.2 507.5 255.9 507.5C376.6 507.5 478.9 420.3 496.4 304.9C507.9 228.2 481.6 173.8 481.6 172.9z"/> <path d="M130.2 127.5C130.4 127.6 130.3 127.6 130.2 127.5V127.5zM481.6 172.9C471 147.4 449.6 119.9 432.7 111.2C446.4 138.1 454.4 165 457.4 185.2C457.4 185.3 457.4 185.4 457.5 185.6C429.9 116.8 383.1 89.1 344.9 28.7C329.9 5.1 334 3.5 331.8 4.1L331.7 4.2C285 30.1 256.4 82.5 249.1 126.9C232.5 127.8 216.2 131.9 201.2 139C199.8 139.6 198.7 140.7 198.1 142C197.4 143.4 197.2 144.9 197.5 146.3C197.7 147.2 198.1 148 198.6 148.6C199.1 149.3 199.8 149.9 200.5 150.3C201.3 150.7 202.1 151 203 151.1C203.8 151.1 204.7 151 205.5 150.8L206 150.6C221.5 143.3 238.4 139.4 255.5 139.2C318.4 138.7 352.7 183.3 363.2 201.5C350.2 192.4 326.8 183.3 304.3 187.2C392.1 231.1 368.5 381.8 247 376.4C187.5 373.8 149.9 325.5 146.4 285.6C146.4 285.6 157.7 243.7 227 243.7C234.5 243.7 256 222.8 256.4 216.7C256.3 214.7 213.8 197.8 197.3 181.5C188.4 172.8 184.2 168.6 180.5 165.5C178.5 163.8 176.4 162.2 174.2 160.7C168.6 141.2 168.4 120.6 173.5 101.1C148.5 112.5 129 130.5 114.8 146.4H114.7C105 134.2 105.7 93.8 106.3 85.3C106.1 84.8 99 89 98.1 89.7C89.5 95.7 81.6 102.6 74.3 110.1C58 126.7 30.1 160.2 18.8 211.3C14.2 231.7 12 255.7 12 263.6C12 398.3 121.2 507.5 255.9 507.5C376.6 507.5 478.9 420.3 496.4 304.9C507.9 228.2 481.6 173.8 481.6 172.9z"/>
</svg> </svg>
Get Firefox Extension Get Firefox Extension

View File

@@ -1,20 +1,25 @@
import { createContext, useContext, useEffect, useState } from "react" import { createContext, useContext, useEffect, useState } from "react"
type Theme = "dark" | "light" | "system" export type Theme = "dark" | "light" | "system"
export type ColorScheme = "default" | "blue" | "green" | "orange" | "red" | "rose" | "violet" | "yellow"
type ThemeProviderProps = { type ThemeProviderProps = {
children: React.ReactNode children: React.ReactNode
defaultTheme?: Theme defaultTheme?: Theme
storageKey?: string defaultColorScheme?: ColorScheme
themeStorageKey?: string
colorSchemeStorageKey?: string
} }
type ThemeProviderState = { type ThemeProviderState = {
theme: Theme theme: Theme
setTheme: (theme: Theme) => void colorScheme: ColorScheme
setTheme: (theme: Theme, colorScheme: ColorScheme) => void
} }
const initialState: ThemeProviderState = { const initialState: ThemeProviderState = {
theme: "system", theme: "system",
colorScheme: "default",
setTheme: () => null, setTheme: () => null,
} }
@@ -23,36 +28,56 @@ const ThemeProviderContext = createContext<ThemeProviderState>(initialState)
export function ThemeProvider({ export function ThemeProvider({
children, children,
defaultTheme = "system", defaultTheme = "system",
storageKey = "vite-ui-theme", defaultColorScheme = "default",
themeStorageKey = "vite-ui-theme",
colorSchemeStorageKey = "vite-ui-color-scheme",
...props ...props
}: ThemeProviderProps) { }: ThemeProviderProps) {
const [theme, setTheme] = useState<Theme>( const [theme, setTheme] = useState<Theme>(
() => (localStorage.getItem(storageKey) as Theme) || defaultTheme () => (localStorage.getItem(themeStorageKey) as Theme) || defaultTheme
)
const [colorScheme, setColorScheme] = useState<ColorScheme>(
() => (localStorage.getItem(colorSchemeStorageKey) as ColorScheme) || defaultColorScheme
) )
useEffect(() => { useEffect(() => {
const root = window.document.documentElement const root = window.document.documentElement
root.classList.remove("light", "dark") root.classList.remove("light", "dark", "blue", "blue-dark", "green", "green-dark", "orange", "orange-dark", "red", "red-dark", "rose", "rose-dark", "violet", "violet-dark", "yellow", "yellow-dark")
if (theme === "system") { if (theme === "system") {
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)") const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
.matches .matches
? colorScheme === "default"
? "dark" ? "dark"
: "light" : `${colorScheme}-dark`
: colorScheme === "default"
? "light"
: colorScheme
root.classList.add(systemTheme) root.classList.add(systemTheme)
return return
} }
root.classList.add(theme) if (theme === "dark") {
}, [theme]) root.classList.add(colorScheme === "default" ? "dark" : `${colorScheme}-dark`)
return
}
if (theme === "light") {
root.classList.add(colorScheme === "default" ? "light" : colorScheme)
return
}
}, [theme, colorScheme])
const value = { const value = {
theme, theme,
setTheme: (theme: Theme) => { colorScheme,
localStorage.setItem(storageKey, theme) setTheme: (theme: Theme, colorScheme: ColorScheme) => {
localStorage.setItem(themeStorageKey, theme)
localStorage.setItem(colorSchemeStorageKey, colorScheme)
setTheme(theme) setTheme(theme)
setColorScheme(colorScheme)
}, },
} }

View File

@@ -151,6 +151,7 @@ export const useSettingsPageStatesStore = create<SettingsPageStatesStore>((set)
ytdlp_update_channel: 'nightly', ytdlp_update_channel: 'nightly',
ytdlp_auto_update: true, ytdlp_auto_update: true,
theme: 'system', theme: 'system',
color_scheme: 'default',
download_dir: '', download_dir: '',
prefer_video_over_playlist: true, prefer_video_over_playlist: true,
strict_downloadablity_check: false, strict_downloadablity_check: false,
@@ -220,6 +221,7 @@ export const useSettingsPageStatesStore = create<SettingsPageStatesStore>((set)
ytdlp_update_channel: 'nightly', ytdlp_update_channel: 'nightly',
ytdlp_auto_update: true, ytdlp_auto_update: true,
theme: 'system', theme: 'system',
color_scheme: 'default',
download_dir: '', download_dir: '',
prefer_video_over_playlist: true, prefer_video_over_playlist: true,
strict_downloadablity_check: false, strict_downloadablity_check: false,

View File

@@ -1,3 +1,5 @@
import { ColorScheme, Theme } from "@/providers/themeProvider";
export interface SettingsTable { export interface SettingsTable {
key: string; key: string;
value: string; value: string;
@@ -12,7 +14,8 @@ export interface CustomCommand {
export interface Settings { export interface Settings {
ytdlp_update_channel: string; ytdlp_update_channel: string;
ytdlp_auto_update: boolean; ytdlp_auto_update: boolean;
theme: 'dark' | 'light' | 'system'; theme: Theme;
color_scheme: ColorScheme;
download_dir: string; download_dir: string;
max_parallel_downloads: number; max_parallel_downloads: number;
max_retries: number; max_retries: number;