Compare commits
13
Commits
develop
...
64e54ea472
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64e54ea472 | ||
|
|
2b53dd0a11 | ||
|
|
b56fe10cbe | ||
|
|
0beb12e6ad | ||
|
|
34e9896e15 | ||
|
|
67b96442a3 | ||
|
|
dcab297355 | ||
|
|
cc8111275e | ||
|
|
952f30ea19 | ||
|
|
e399f22007 | ||
|
|
703c608ac9 | ||
|
|
78f0f6d50a | ||
|
|
ac1249084a |
No files matched your search
Generated
+737
-14
@@ -9,6 +9,8 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@azure/msal-browser": "^5.21.0",
|
"@azure/msal-browser": "^5.21.0",
|
||||||
|
"@heroui/react": "^3.2.4",
|
||||||
|
"@heroui/styles": "^3.2.4",
|
||||||
"@react-oauth/google": "^0.13.5",
|
"@react-oauth/google": "^0.13.5",
|
||||||
"@tailwindcss/postcss": "^4.3.3",
|
"@tailwindcss/postcss": "^4.3.3",
|
||||||
"@tanstack/react-query": "^5.101.4",
|
"@tanstack/react-query": "^5.101.4",
|
||||||
@@ -35,6 +37,50 @@
|
|||||||
"typescript-eslint": "^8.65.0"
|
"typescript-eslint": "^8.65.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@adobe/react-spectrum": {
|
||||||
|
"version": "3.47.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.5.tgz",
|
||||||
|
"integrity": "sha512-iTbAwwMijVnQjwjwbvkWn82AyRAbFNSJzt/VawWp1vXRIxVIO9COZ1pPG5Fj88xXgqlG8maVcv58p74vqqy5HA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@internationalized/date": "^3.12.4",
|
||||||
|
"@react-types/shared": "^3.36.1",
|
||||||
|
"@spectrum-icons/ui": "^3.7.2",
|
||||||
|
"@spectrum-icons/workflow": "^4.3.2",
|
||||||
|
"@swc/helpers": "^0.5.0",
|
||||||
|
"client-only": "^0.0.1",
|
||||||
|
"clsx": "^2.0.0",
|
||||||
|
"react-aria": "3.52.1",
|
||||||
|
"react-aria-components": "1.21.1",
|
||||||
|
"react-stately": "3.50.0",
|
||||||
|
"react-transition-group": "^4.4.5",
|
||||||
|
"use-sync-external-store": "^1.6.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
|
||||||
|
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@adobe/react-spectrum-ui": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@adobe/react-spectrum-ui/-/react-spectrum-ui-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-wcrbEE2O/9WnEn6avBnaVRRx88S5PLFsPLr4wffzlbMfXeQsy+RMQwaJd3cbzrn18/j04Isit7f7Emfn0dhrJA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@adobe/react-spectrum-workflow": {
|
||||||
|
"version": "2.3.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@adobe/react-spectrum-workflow/-/react-spectrum-workflow-2.3.5.tgz",
|
||||||
|
"integrity": "sha512-b53VIPwPWKb/T5gzE3qs+QlGP5gVrw/LnWV3xMksDU+CRl3rzOKUwxIGiZO8ICyYh1WiyqY4myGlPU/nAynBUg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@alloc/quick-lru": {
|
"node_modules/@alloc/quick-lru": {
|
||||||
"version": "5.2.0",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
|
||||||
@@ -99,7 +145,6 @@
|
|||||||
"integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
|
"integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.29.7",
|
"@babel/code-frame": "^7.29.7",
|
||||||
"@babel/generator": "^7.29.7",
|
"@babel/generator": "^7.29.7",
|
||||||
@@ -261,6 +306,15 @@
|
|||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@babel/runtime": {
|
||||||
|
"version": "7.29.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz",
|
||||||
|
"integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/template": {
|
"node_modules/@babel/template": {
|
||||||
"version": "7.29.7",
|
"version": "7.29.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
|
||||||
@@ -447,6 +501,101 @@
|
|||||||
"node": "^20.19.0 || ^22.13.0 || >=24"
|
"node": "^20.19.0 || ^22.13.0 || >=24"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@formatjs/ecma402-abstract": {
|
||||||
|
"version": "2.3.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.6.tgz",
|
||||||
|
"integrity": "sha512-HJnTFeRM2kVFVr5gr5kH1XP6K0JcJtE7Lzvtr3FS/so5f1kpsqqqxy5JF+FRaO6H2qmcMfAUIox7AJteieRtVw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/fast-memoize": "2.2.7",
|
||||||
|
"@formatjs/intl-localematcher": "0.6.2",
|
||||||
|
"decimal.js": "^10.4.3",
|
||||||
|
"tslib": "^2.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/fast-memoize": {
|
||||||
|
"version": "2.2.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.7.tgz",
|
||||||
|
"integrity": "sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/icu-messageformat-parser": {
|
||||||
|
"version": "2.11.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.11.4.tgz",
|
||||||
|
"integrity": "sha512-7kR78cRrPNB4fjGFZg3Rmj5aah8rQj9KPzuLsmcSn4ipLXQvC04keycTI1F7kJYDwIXtT2+7IDEto842CfZBtw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/ecma402-abstract": "2.3.6",
|
||||||
|
"@formatjs/icu-skeleton-parser": "1.8.16",
|
||||||
|
"tslib": "^2.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/icu-skeleton-parser": {
|
||||||
|
"version": "1.8.16",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.16.tgz",
|
||||||
|
"integrity": "sha512-H13E9Xl+PxBd8D5/6TVUluSpxGNvFSlN/b3coUp0e0JpuWXXnQDiavIpY3NnvSp4xhEMoXyyBvVfdFX8jglOHQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/ecma402-abstract": "2.3.6",
|
||||||
|
"tslib": "^2.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/intl-localematcher": {
|
||||||
|
"version": "0.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.6.2.tgz",
|
||||||
|
"integrity": "sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@heroui/react": {
|
||||||
|
"version": "3.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@heroui/react/-/react-3.2.4.tgz",
|
||||||
|
"integrity": "sha512-ZXCTyz1G7bgCdHVxRPy4vIRI/wdb0ZvzEUJGG7CAEp0zqQsKrTH1o9i3Ns8yM6EidtAyfMosULN29gFA3wegBA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@heroui/styles": "3.2.4",
|
||||||
|
"@radix-ui/react-avatar": "1.1.11",
|
||||||
|
"@react-stately/utils": "3.12.1",
|
||||||
|
"@react-types/color": "3.2.0",
|
||||||
|
"@react-types/shared": "3.36.1",
|
||||||
|
"input-otp": "1.4.2",
|
||||||
|
"tailwind-merge": "3.4.0",
|
||||||
|
"tailwind-variants": "3.3.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@react-aria/i18n": "^3.13.1",
|
||||||
|
"@react-aria/ssr": "^3.10.1",
|
||||||
|
"@react-aria/utils": "^3.34.1",
|
||||||
|
"react": ">=19.0.0",
|
||||||
|
"react-aria": "^3.51.0",
|
||||||
|
"react-aria-components": "^1.20.0",
|
||||||
|
"react-dom": ">=19.0.0",
|
||||||
|
"tailwindcss": ">=4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@heroui/styles": {
|
||||||
|
"version": "3.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@heroui/styles/-/styles-3.2.4.tgz",
|
||||||
|
"integrity": "sha512-tjiT7VFUSjw7TA6ZjeT+D3oX62+D27l7dhPwW+emwPnkdkRoKALFJEPBsxpmEI/x18uNkKFIoTk85bICyOO8Vg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"tailwind-variants": "3.3.1",
|
||||||
|
"tw-animate-css": "1.4.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"tailwindcss": ">=4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@humanfs/core": {
|
"node_modules/@humanfs/core": {
|
||||||
"version": "0.19.2",
|
"version": "0.19.2",
|
||||||
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
|
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
|
||||||
@@ -1014,6 +1163,44 @@
|
|||||||
"url": "https://opencollective.com/libvips"
|
"url": "https://opencollective.com/libvips"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@internationalized/date": {
|
||||||
|
"version": "3.12.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.12.4.tgz",
|
||||||
|
"integrity": "sha512-M1dEn4c1U1HsSlaVR8upZtSqvXrTkHDfv18H01uCSJyjVLDxnBR38v/fMxecmlwXKR4i9HeZcmgQAPE6A+aGJQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@swc/helpers": "^0.5.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@internationalized/message": {
|
||||||
|
"version": "3.1.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@internationalized/message/-/message-3.1.10.tgz",
|
||||||
|
"integrity": "sha512-nc0Or6EdWHqZRcsXb6P9hBIpLsfSl/ILh0rk5h/OVBpzmhdExXtPy2cQtWsq8XKRBpRHwDNnAHt4OpolcB7dog==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@swc/helpers": "^0.5.0",
|
||||||
|
"intl-messageformat": "^10.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@internationalized/number": {
|
||||||
|
"version": "3.6.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.8.tgz",
|
||||||
|
"integrity": "sha512-8UmMFia46DUt+k97zKd9fKWXcWHR+k8ae3eYzILETuT2KbIvLyOfac7zesw+sJdRAAZ7Q9pM1Mk22aXp2LD0Ig==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@swc/helpers": "^0.5.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@internationalized/string": {
|
||||||
|
"version": "3.2.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@internationalized/string/-/string-3.2.10.tgz",
|
||||||
|
"integrity": "sha512-PDx6//vHSpRnHfxqMqto11zQvhsaU74O3mKv2F/0eicGZcl9NLjQmGlbHz/LsJh5tLKp4A4L7ZVTzN1/MmMTvA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@swc/helpers": "^0.5.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@jridgewell/gen-mapping": {
|
"node_modules/@jridgewell/gen-mapping": {
|
||||||
"version": "0.3.13",
|
"version": "0.3.13",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||||
@@ -1193,6 +1380,218 @@
|
|||||||
"node": ">= 10"
|
"node": ">= 10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-avatar": {
|
||||||
|
"version": "1.1.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.11.tgz",
|
||||||
|
"integrity": "sha512-0Qk603AHGV28BOBO34p7IgD5m+V5Sg/YovfayABkoDDBM5d3NCx0Mp4gGrjzLGes1jV5eNOE1r3itqOR33VC6Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-context": "1.1.3",
|
||||||
|
"@radix-ui/react-primitive": "2.1.4",
|
||||||
|
"@radix-ui/react-use-callback-ref": "1.1.1",
|
||||||
|
"@radix-ui/react-use-is-hydrated": "0.1.0",
|
||||||
|
"@radix-ui/react-use-layout-effect": "1.1.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-compose-refs": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-context": {
|
||||||
|
"version": "1.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.3.tgz",
|
||||||
|
"integrity": "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-primitive": {
|
||||||
|
"version": "2.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz",
|
||||||
|
"integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-slot": "1.2.4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-slot": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-use-callback-ref": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-use-is-hydrated": {
|
||||||
|
"version": "0.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.0.tgz",
|
||||||
|
"integrity": "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"use-sync-external-store": "^1.5.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-use-layout-effect": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-aria/color": {
|
||||||
|
"version": "3.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-aria/color/-/color-3.2.1.tgz",
|
||||||
|
"integrity": "sha512-v/pZh1yBNeayQ5Kio0xRIX0gBf6uu+KjCRW4QSydTyRpmhvk6sMzHT9i9/vIMdaDHaYHBXlXF+Tp/mvS9CG4Zw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@swc/helpers": "^0.5.0",
|
||||||
|
"react-aria": "^3.48.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
|
||||||
|
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-aria/i18n": {
|
||||||
|
"version": "3.13.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-aria/i18n/-/i18n-3.13.1.tgz",
|
||||||
|
"integrity": "sha512-z56ZYcbfpNmMyiGLhyEjytpmEfoTlBaksk84q4kds3HvNkf7QWKj+DJVfVDrJX+c1LyuBsszLSX7yxJRiHsYKQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@internationalized/date": "^3.12.1",
|
||||||
|
"@internationalized/message": "^3.1.9",
|
||||||
|
"@internationalized/string": "^3.2.8",
|
||||||
|
"@swc/helpers": "^0.5.0",
|
||||||
|
"react-aria": "^3.48.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
|
||||||
|
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-aria/ssr": {
|
||||||
|
"version": "3.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.10.1.tgz",
|
||||||
|
"integrity": "sha512-jn038/ZYmu6DpfXJ6r2U9zFFppjbc9wnApPJSCxao2RZVEqep4YyoniHSy8qv6V21/xyS4IV7W9a+X2jOjSuag==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@swc/helpers": "^0.5.0",
|
||||||
|
"react-aria": "^3.48.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
|
||||||
|
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-aria/utils": {
|
||||||
|
"version": "3.34.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.34.1.tgz",
|
||||||
|
"integrity": "sha512-H6+rGZL+0f58bBNaUMfctEnT+NogqwAk+nHiB8sR3K+YlQ37GTuCijy2U/pPvQtFMS5mURrjZeBH5JNNXsx14A==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@swc/helpers": "^0.5.0",
|
||||||
|
"react-aria": "^3.48.0",
|
||||||
|
"react-stately": "^3.46.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
|
||||||
|
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@react-oauth/google": {
|
"node_modules/@react-oauth/google": {
|
||||||
"version": "0.13.5",
|
"version": "0.13.5",
|
||||||
"resolved": "https://registry.npmjs.org/@react-oauth/google/-/google-0.13.5.tgz",
|
"resolved": "https://registry.npmjs.org/@react-oauth/google/-/google-0.13.5.tgz",
|
||||||
@@ -1203,6 +1602,120 @@
|
|||||||
"react-dom": ">=16.8.0"
|
"react-dom": ">=16.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@react-spectrum/color": {
|
||||||
|
"version": "3.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-spectrum/color/-/color-3.2.1.tgz",
|
||||||
|
"integrity": "sha512-FbB+GaVufsN+obNJmUAe8XGZTeJjEWvEf/k/7nxDxoDCBdg/o6eEPONAZyID5Dj1f8BZKNym8VURxDZx7IIyuQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@adobe/react-spectrum": "^3.47.0",
|
||||||
|
"@swc/helpers": "^0.5.0",
|
||||||
|
"react-stately": "^3.46.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
|
||||||
|
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-spectrum/provider": {
|
||||||
|
"version": "3.11.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-spectrum/provider/-/provider-3.11.1.tgz",
|
||||||
|
"integrity": "sha512-TsoNdVdmlQ7L+75ILq5Yb3+wp/I1AtIeat0o+Y+ZBxP+TtWpwT1ZtCB5l3cplFVzHzOpZlzO0VaDrDP9ElGYDw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@adobe/react-spectrum": "^3.47.0",
|
||||||
|
"@swc/helpers": "^0.5.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
|
||||||
|
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-stately/color": {
|
||||||
|
"version": "3.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-stately/color/-/color-3.10.1.tgz",
|
||||||
|
"integrity": "sha512-v4YlLa/oRQpPa/M9Yq1e0Bl8Z7xzGZdNDAIbfIMZuiJtacaOKriPW+AOgjjT/Ab0fNX2YDbwIYvkiMOJBnTi5A==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@swc/helpers": "^0.5.0",
|
||||||
|
"react-stately": "^3.46.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
|
||||||
|
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-stately/utils": {
|
||||||
|
"version": "3.12.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.12.1.tgz",
|
||||||
|
"integrity": "sha512-NqKfzrknpfwiewx7R2vk1P+CneClInPDsIhw15+jOcUYSEfej0nta4cJywuKQJ2gsPwqX/ojDNixedCve9FWGw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@swc/helpers": "^0.5.0",
|
||||||
|
"react-stately": "^3.46.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
|
||||||
|
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-types/color": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-types/color/-/color-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-beV3vz80nzZ1EuYUM7296Kyi3AHcMrbQw0qub/9yzHWVTKKc5sy/e4dCMKcWL/ArkeAyc7jDOiui190RQ4l0Fw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@react-aria/color": "^3.2.0",
|
||||||
|
"@react-spectrum/color": "^3.2.0",
|
||||||
|
"@react-stately/color": "^3.10.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@react-spectrum/provider": "^3.0.0",
|
||||||
|
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
|
||||||
|
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-types/shared": {
|
||||||
|
"version": "3.36.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.36.1.tgz",
|
||||||
|
"integrity": "sha512-AzsuD9OfxTOZMMvTRhlN3oHBwOmFN7tDh27LzqmHt4+uOgPhJT7ZM7/kVs/8/o0WxayMUIk3hBmCFRHv1FUoag==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@spectrum-icons/ui": {
|
||||||
|
"version": "3.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@spectrum-icons/ui/-/ui-3.7.2.tgz",
|
||||||
|
"integrity": "sha512-7IuSHO+Ck8gJUIGIXqDcDIU4R8RHRUZhlfz8AwN1rSdCEvdgp6DWYN0LDTMrWh7TN/H5hrJ7oDO/gUpR3TnBjw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@adobe/react-spectrum-ui": "1.2.1",
|
||||||
|
"@babel/runtime": "^7.24.4",
|
||||||
|
"@swc/helpers": "^0.5.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@adobe/react-spectrum": "^3.47.0",
|
||||||
|
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
|
||||||
|
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@spectrum-icons/workflow": {
|
||||||
|
"version": "4.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@spectrum-icons/workflow/-/workflow-4.3.2.tgz",
|
||||||
|
"integrity": "sha512-UkVg8Kp9rInjfqtG27in9p7RWfN9JFSvZr7ZOpN2n9jkrQ1y88hZpK8dKce8lgVb7jALXtsi2p0rjyaWzO0QsA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@adobe/react-spectrum-workflow": "2.3.5",
|
||||||
|
"@swc/helpers": "^0.5.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@adobe/react-spectrum": "^3.47.0",
|
||||||
|
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
|
||||||
|
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@swc/helpers": {
|
"node_modules/@swc/helpers": {
|
||||||
"version": "0.5.23",
|
"version": "0.5.23",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz",
|
||||||
@@ -1531,7 +2044,6 @@
|
|||||||
"integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==",
|
"integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.2.2"
|
"csstype": "^3.2.2"
|
||||||
}
|
}
|
||||||
@@ -1540,7 +2052,7 @@
|
|||||||
"version": "19.2.4",
|
"version": "19.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.4.tgz",
|
||||||
"integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==",
|
"integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@types/react": "^19.2.0"
|
"@types/react": "^19.2.0"
|
||||||
@@ -1591,7 +2103,6 @@
|
|||||||
"integrity": "sha512-fUBfTuuEulWqX6V8+O3PtScV01tzYYRUDTAirHFKoRAt7nOzoGiPt0M/bB47wWNy0coOOcgEwAMUtBpykMxl6w==",
|
"integrity": "sha512-fUBfTuuEulWqX6V8+O3PtScV01tzYYRUDTAirHFKoRAt7nOzoGiPt0M/bB47wWNy0coOOcgEwAMUtBpykMxl6w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.67.0",
|
"@typescript-eslint/scope-manager": "8.67.0",
|
||||||
"@typescript-eslint/types": "8.67.0",
|
"@typescript-eslint/types": "8.67.0",
|
||||||
@@ -1796,7 +2307,6 @@
|
|||||||
"integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==",
|
"integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@@ -1843,6 +2353,18 @@
|
|||||||
"url": "https://github.com/sponsors/epoberezkin"
|
"url": "https://github.com/sponsors/epoberezkin"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/aria-hidden": {
|
||||||
|
"version": "1.2.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz",
|
||||||
|
"integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/asynckit": {
|
"node_modules/asynckit": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
@@ -1916,7 +2438,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"baseline-browser-mapping": "^2.11.12",
|
"baseline-browser-mapping": "^2.11.12",
|
||||||
"caniuse-lite": "^1.0.30001809",
|
"caniuse-lite": "^1.0.30001809",
|
||||||
@@ -1970,6 +2491,15 @@
|
|||||||
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
|
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/clsx": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/combined-stream": {
|
"node_modules/combined-stream": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
@@ -2008,7 +2538,6 @@
|
|||||||
"version": "3.2.3",
|
"version": "3.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||||
"devOptional": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
@@ -2028,6 +2557,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/decimal.js": {
|
||||||
|
"version": "10.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
|
||||||
|
"integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
"node_modules/deep-is": {
|
"node_modules/deep-is": {
|
||||||
"version": "0.1.4",
|
"version": "0.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
|
||||||
@@ -2053,6 +2589,16 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dom-helpers": {
|
||||||
|
"version": "5.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
|
||||||
|
"integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.8.7",
|
||||||
|
"csstype": "^3.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/dunder-proto": {
|
"node_modules/dunder-proto": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||||
@@ -2161,7 +2707,6 @@
|
|||||||
"integrity": "sha512-5KeEOJZBfEVA47boFiBsf+6MmmJpffM7qEBg4pLla2e4nlKgdKlqCW0oSLOGsT8Wl5uCGJptLV1bkaiShj90Gw==",
|
"integrity": "sha512-5KeEOJZBfEVA47boFiBsf+6MmmJpffM7qEBg4pLla2e4nlKgdKlqCW0oSLOGsT8Wl5uCGJptLV1bkaiShj90Gw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
],
|
],
|
||||||
@@ -2656,6 +3201,29 @@
|
|||||||
"node": ">=0.8.19"
|
"node": ">=0.8.19"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/input-otp": {
|
||||||
|
"version": "1.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/input-otp/-/input-otp-1.4.2.tgz",
|
||||||
|
"integrity": "sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/intl-messageformat": {
|
||||||
|
"version": "10.7.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.18.tgz",
|
||||||
|
"integrity": "sha512-m3Ofv/X/tV8Y3tHXLohcuVuhWKo7BBq62cqY15etqmLxg2DZ34AGGgQDeR+SCta2+zICb1NX83af0GJmbQ1++g==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/ecma402-abstract": "2.3.6",
|
||||||
|
"@formatjs/fast-memoize": "2.2.7",
|
||||||
|
"@formatjs/icu-messageformat-parser": "2.11.4",
|
||||||
|
"tslib": "^2.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/is-extglob": {
|
"node_modules/is-extglob": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||||
@@ -2699,7 +3267,6 @@
|
|||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/jsesc": {
|
"node_modules/jsesc": {
|
||||||
@@ -3038,6 +3605,18 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/loose-envify": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"loose-envify": "cli.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/lru-cache": {
|
"node_modules/lru-cache": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
||||||
@@ -3225,6 +3804,15 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/object-assign": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/optionator": {
|
"node_modules/optionator": {
|
||||||
"version": "0.9.4",
|
"version": "0.9.4",
|
||||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
||||||
@@ -3307,7 +3895,6 @@
|
|||||||
"integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
|
"integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -3353,6 +3940,17 @@
|
|||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/prop-types": {
|
||||||
|
"version": "15.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||||
|
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"loose-envify": "^1.4.0",
|
||||||
|
"object-assign": "^4.1.1",
|
||||||
|
"react-is": "^16.13.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/proxy-from-env": {
|
"node_modules/proxy-from-env": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
|
||||||
@@ -3377,17 +3975,55 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz",
|
||||||
"integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==",
|
"integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-aria": {
|
||||||
|
"version": "3.52.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-aria/-/react-aria-3.52.1.tgz",
|
||||||
|
"integrity": "sha512-fdZZruC9/x/joCg0mhKGs5aHpwrXLCSZ4GOJmhhYyiE0ffyEsk9MLFt9LCOCF9tn8ErTD+UDQP4oDUSlZkmpCg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@internationalized/date": "^3.12.4",
|
||||||
|
"@internationalized/number": "^3.6.8",
|
||||||
|
"@internationalized/string": "^3.2.10",
|
||||||
|
"@react-types/shared": "^3.36.1",
|
||||||
|
"@swc/helpers": "^0.5.0",
|
||||||
|
"aria-hidden": "^1.2.3",
|
||||||
|
"clsx": "^2.0.0",
|
||||||
|
"react-stately": "3.50.0",
|
||||||
|
"use-sync-external-store": "^1.6.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
|
||||||
|
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-aria-components": {
|
||||||
|
"version": "1.21.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-aria-components/-/react-aria-components-1.21.1.tgz",
|
||||||
|
"integrity": "sha512-J88WflYY0z+EhLX0ce+GjFlQ3ag3ubIgfUTjpKSrBQBu92Xtl4Ub9o118HItUddtbk1Ido0jzyPy94/klZy1hg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@internationalized/date": "^3.12.4",
|
||||||
|
"@internationalized/string": "^3.2.10",
|
||||||
|
"@react-types/shared": "^3.36.1",
|
||||||
|
"@swc/helpers": "^0.5.0",
|
||||||
|
"client-only": "^0.0.1",
|
||||||
|
"react-aria": "3.52.1",
|
||||||
|
"react-stately": "3.50.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
|
||||||
|
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-dom": {
|
"node_modules/react-dom": {
|
||||||
"version": "19.2.8",
|
"version": "19.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz",
|
||||||
"integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==",
|
"integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"scheduler": "^0.27.0"
|
"scheduler": "^0.27.0"
|
||||||
},
|
},
|
||||||
@@ -3404,6 +4040,45 @@
|
|||||||
"react": "*"
|
"react": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-is": {
|
||||||
|
"version": "16.13.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/react-stately": {
|
||||||
|
"version": "3.50.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-stately/-/react-stately-3.50.0.tgz",
|
||||||
|
"integrity": "sha512-TnckvpDQGU0672wEaLZqdzvhuSeXWjgW6vijMWxiKOxc8CosQUfPGISdcZyfHqjX3XMjEtRxj4w9HumvX4h4mw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@internationalized/date": "^3.12.4",
|
||||||
|
"@internationalized/number": "^3.6.8",
|
||||||
|
"@internationalized/string": "^3.2.10",
|
||||||
|
"@react-types/shared": "^3.36.1",
|
||||||
|
"@swc/helpers": "^0.5.0",
|
||||||
|
"use-sync-external-store": "^1.6.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-transition-group": {
|
||||||
|
"version": "4.4.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
|
||||||
|
"integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.5.5",
|
||||||
|
"dom-helpers": "^5.0.1",
|
||||||
|
"loose-envify": "^1.4.0",
|
||||||
|
"prop-types": "^15.6.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.6.0",
|
||||||
|
"react-dom": ">=16.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/scheduler": {
|
"node_modules/scheduler": {
|
||||||
"version": "0.27.0",
|
"version": "0.27.0",
|
||||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
|
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
|
||||||
@@ -3554,6 +4229,38 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tailwind-merge": {
|
||||||
|
"version": "3.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz",
|
||||||
|
"integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/dcastil"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tailwind-variants": {
|
||||||
|
"version": "3.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-3.3.1.tgz",
|
||||||
|
"integrity": "sha512-4pAvwUtM4HKBiRZftncAbpn6V9Hhwoa5Fl7O2u5zbp7Z5Cvu+/o/6+176WY3WCEES209543quG8zFIcXCsc5Jw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.9.x",
|
||||||
|
"pnpm": ">=7.x"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"tailwind-merge": ">=3.0.0",
|
||||||
|
"tailwindcss": "*"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"tailwind-merge": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"tailwindcss": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tailwindcss": {
|
"node_modules/tailwindcss": {
|
||||||
"version": "4.3.3",
|
"version": "4.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz",
|
||||||
@@ -3609,6 +4316,15 @@
|
|||||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
"license": "0BSD"
|
"license": "0BSD"
|
||||||
},
|
},
|
||||||
|
"node_modules/tw-animate-css": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/Wombosvideo"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/type-check": {
|
"node_modules/type-check": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||||
@@ -3628,7 +4344,6 @@
|
|||||||
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
|
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
@@ -3709,6 +4424,15 @@
|
|||||||
"punycode": "^2.1.0"
|
"punycode": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/use-sync-external-store": {
|
||||||
|
"version": "1.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.7.0.tgz",
|
||||||
|
"integrity": "sha512-6L+EeigHMQhdaIPNIFUKwfWJSwWFQ8gJbJ2DLOs5sDIegTwR9fRxvnM3uciHKjIZhFz+KAv2emhWMRvDmMcY8A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/which": {
|
"node_modules/which": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||||
@@ -3761,7 +4485,6 @@
|
|||||||
"integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
|
"integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,8 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@azure/msal-browser": "^5.21.0",
|
"@azure/msal-browser": "^5.21.0",
|
||||||
|
"@heroui/react": "^3.2.4",
|
||||||
|
"@heroui/styles": "^3.2.4",
|
||||||
"@react-oauth/google": "^0.13.5",
|
"@react-oauth/google": "^0.13.5",
|
||||||
"@tailwindcss/postcss": "^4.3.3",
|
"@tailwindcss/postcss": "^4.3.3",
|
||||||
"@tanstack/react-query": "^5.101.4",
|
"@tanstack/react-query": "^5.101.4",
|
||||||
|
|||||||
@@ -279,8 +279,8 @@ export default function ComponentsContent() {
|
|||||||
<Row>
|
<Row>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label="Recordar sesión"
|
label="Recordar sesión"
|
||||||
checked={checked}
|
isSelected={checked}
|
||||||
onChange={(e) => setChecked(e.target.checked)}
|
onChange={(isSelected) => setChecked(isSelected)}
|
||||||
/>
|
/>
|
||||||
<Checkbox label="Deshabilitado" disabled />
|
<Checkbox label="Deshabilitado" disabled />
|
||||||
<Checkbox
|
<Checkbox
|
||||||
@@ -302,8 +302,8 @@ export default function ComponentsContent() {
|
|||||||
<CodeBlock
|
<CodeBlock
|
||||||
code={`<Checkbox
|
code={`<Checkbox
|
||||||
label="Recordar sesión"
|
label="Recordar sesión"
|
||||||
checked={checked}
|
isSelected={checked}
|
||||||
onChange={e => setChecked(e.target.checked)}
|
onChange={(isSelected) => setChecked(isSelected)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Switch
|
<Switch
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
@import "@heroui/styles";
|
||||||
|
|
||||||
@custom-variant dark (&:is(.dark, .dark *));
|
@custom-variant dark (&:is(.dark, .dark *));
|
||||||
|
|
||||||
|
|||||||
+67
-68
@@ -1,18 +1,49 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { Chip as HeroUIChip, ChipProps as HeroUIChipProps } from '@heroui/react';
|
||||||
|
import { tv } from '@heroui/styles';
|
||||||
import { FiX } from 'react-icons/fi';
|
import { FiX } from 'react-icons/fi';
|
||||||
|
|
||||||
export interface BadgeProps {
|
// 1. Organizamos los estilos con 'tv' usando variantes estándar
|
||||||
children: React.ReactNode;
|
const badgeStyles = tv({
|
||||||
variant?:
|
base: "inline-flex items-center select-none border transition-colors",
|
||||||
| 'default'
|
variants: {
|
||||||
| 'primary'
|
customVariant: {
|
||||||
| 'secondary'
|
default: 'bg-slate-100 text-slate-800 border-slate-200 dark:bg-slate-800/80 dark:text-slate-200 dark:border-slate-700',
|
||||||
| 'success'
|
primary: 'bg-cyan-50 text-cyan-700 border-cyan-200 dark:bg-cyan-950/60 dark:text-cyan-300 dark:border-cyan-500/30',
|
||||||
| 'warning'
|
secondary: 'bg-purple-50 text-purple-700 border-purple-200 dark:bg-purple-950/60 dark:text-purple-300 dark:border-purple-500/30',
|
||||||
| 'danger'
|
success: 'bg-emerald-50 text-emerald-700 border-emerald-200 dark:bg-emerald-950/60 dark:text-emerald-300 dark:border-emerald-500/30',
|
||||||
| 'info'
|
warning: 'bg-amber-50 text-amber-700 border-amber-200 dark:bg-amber-950/60 dark:text-amber-300 dark:border-amber-500/30',
|
||||||
| 'outline';
|
danger: 'bg-rose-50 text-rose-700 border-rose-200 dark:bg-rose-950/60 dark:text-rose-300 dark:border-rose-500/30',
|
||||||
|
info: 'bg-sky-50 text-sky-700 border-sky-200 dark:bg-sky-950/60 dark:text-sky-300 dark:border-sky-500/30',
|
||||||
|
outline: 'bg-transparent text-slate-700 border-slate-300 dark:text-slate-300 dark:border-slate-700',
|
||||||
|
},
|
||||||
|
customSize: {
|
||||||
|
sm: 'text-[10px] px-2 py-0.5 gap-1 rounded-md font-semibold h-auto min-w-min',
|
||||||
|
md: 'text-xs px-2.5 py-1 gap-1.5 rounded-lg font-medium h-auto min-w-min',
|
||||||
|
lg: 'text-sm px-3 py-1.5 gap-2 rounded-xl font-medium h-auto min-w-min',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
customVariant: "default",
|
||||||
|
customSize: "md",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mapeo auxiliar de colores para los dots según la variante
|
||||||
|
const dotColorMap: Record<NonNullable<BadgeProps['variant']>, string> = {
|
||||||
|
default: 'bg-slate-500',
|
||||||
|
primary: 'bg-cyan-500',
|
||||||
|
secondary: 'bg-purple-500',
|
||||||
|
success: 'bg-emerald-500',
|
||||||
|
warning: 'bg-amber-500',
|
||||||
|
danger: 'bg-rose-500',
|
||||||
|
info: 'bg-sky-500',
|
||||||
|
outline: 'bg-slate-400',
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface BadgeProps extends Omit<HeroUIChipProps, 'variant' | 'size' | 'color' | 'className' | 'children'> {
|
||||||
|
variant?: 'default' | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'outline';
|
||||||
size?: 'sm' | 'md' | 'lg';
|
size?: 'sm' | 'md' | 'lg';
|
||||||
dot?: boolean;
|
dot?: boolean;
|
||||||
dotPulse?: boolean;
|
dotPulse?: boolean;
|
||||||
@@ -20,50 +51,12 @@ export interface BadgeProps {
|
|||||||
removable?: boolean;
|
removable?: boolean;
|
||||||
onRemove?: () => void;
|
onRemove?: () => void;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
children?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const variantStyles: Record<NonNullable<BadgeProps['variant']>, { badge: string; dot: string }> = {
|
export const Badge = React.forwardRef<HTMLSpanElement, BadgeProps>(
|
||||||
default: {
|
(
|
||||||
badge: 'bg-slate-100 text-slate-800 border-slate-200 dark:bg-slate-800/80 dark:text-slate-200 dark:border-slate-700',
|
{
|
||||||
dot: 'bg-slate-500',
|
|
||||||
},
|
|
||||||
primary: {
|
|
||||||
badge: 'bg-cyan-50 text-cyan-700 border-cyan-200 dark:bg-cyan-950/60 dark:text-cyan-300 dark:border-cyan-500/30',
|
|
||||||
dot: 'bg-cyan-500',
|
|
||||||
},
|
|
||||||
secondary: {
|
|
||||||
badge: 'bg-purple-50 text-purple-700 border-purple-200 dark:bg-purple-950/60 dark:text-purple-300 dark:border-purple-500/30',
|
|
||||||
dot: 'bg-purple-500',
|
|
||||||
},
|
|
||||||
success: {
|
|
||||||
badge: 'bg-emerald-50 text-emerald-700 border-emerald-200 dark:bg-emerald-950/60 dark:text-emerald-300 dark:border-emerald-500/30',
|
|
||||||
dot: 'bg-emerald-500',
|
|
||||||
},
|
|
||||||
warning: {
|
|
||||||
badge: 'bg-amber-50 text-amber-700 border-amber-200 dark:bg-amber-950/60 dark:text-amber-300 dark:border-amber-500/30',
|
|
||||||
dot: 'bg-amber-500',
|
|
||||||
},
|
|
||||||
danger: {
|
|
||||||
badge: 'bg-rose-50 text-rose-700 border-rose-200 dark:bg-rose-950/60 dark:text-rose-300 dark:border-rose-500/30',
|
|
||||||
dot: 'bg-rose-500',
|
|
||||||
},
|
|
||||||
info: {
|
|
||||||
badge: 'bg-sky-50 text-sky-700 border-sky-200 dark:bg-sky-950/60 dark:text-sky-300 dark:border-sky-500/30',
|
|
||||||
dot: 'bg-sky-500',
|
|
||||||
},
|
|
||||||
outline: {
|
|
||||||
badge: 'bg-transparent text-slate-700 border-slate-300 dark:text-slate-300 dark:border-slate-700',
|
|
||||||
dot: 'bg-slate-400',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const sizeStyles = {
|
|
||||||
sm: 'text-[10px] px-2 py-0.5 gap-1 rounded-md font-semibold',
|
|
||||||
md: 'text-xs px-2.5 py-1 gap-1.5 rounded-lg font-medium',
|
|
||||||
lg: 'text-sm px-3 py-1.5 gap-2 rounded-xl font-medium',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Badge: React.FC<BadgeProps> = ({
|
|
||||||
children,
|
children,
|
||||||
variant = 'default',
|
variant = 'default',
|
||||||
size = 'md',
|
size = 'md',
|
||||||
@@ -73,27 +66,30 @@ export const Badge: React.FC<BadgeProps> = ({
|
|||||||
removable = false,
|
removable = false,
|
||||||
onRemove,
|
onRemove,
|
||||||
className = '',
|
className = '',
|
||||||
}) => {
|
...props
|
||||||
const currentVariant = variantStyles[variant];
|
},
|
||||||
|
ref
|
||||||
|
) => {
|
||||||
|
const dotColor = dotColorMap[variant];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span
|
<HeroUIChip
|
||||||
className={`
|
ref={ref}
|
||||||
inline-flex items-center select-none border transition-colors
|
// En HeroUI v3 se usa className en singular (soporta string directo desde tv)
|
||||||
${currentVariant.badge}
|
className={badgeStyles({
|
||||||
${sizeStyles[size]}
|
customVariant: variant,
|
||||||
${className}
|
customSize: size,
|
||||||
`}
|
className
|
||||||
|
})}
|
||||||
|
{...props}
|
||||||
>
|
>
|
||||||
{/* Optional Dot indicator */}
|
{/* Optional Dot indicator */}
|
||||||
{dot && (
|
{dot && (
|
||||||
<span className="relative flex h-1.5 w-1.5 shrink-0">
|
<span className="relative flex h-1.5 w-1.5 shrink-0">
|
||||||
{dotPulse && (
|
{dotPulse && (
|
||||||
<span
|
<span className={`animate-ping absolute inline-flex h-full w-full rounded-full opacity-75 ${dotColor}`} />
|
||||||
className={`animate-ping absolute inline-flex h-full w-full rounded-full opacity-75 ${currentVariant.dot}`}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
<span className={`relative inline-flex rounded-full h-1.5 w-1.5 ${currentVariant.dot}`} />
|
<span className={`relative inline-flex rounded-full h-1.5 w-1.5 ${dotColor}`} />
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -117,6 +113,9 @@ export const Badge: React.FC<BadgeProps> = ({
|
|||||||
<FiX className="text-xs" />
|
<FiX className="text-xs" />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</span>
|
</HeroUIChip>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
Badge.displayName = 'Badge';
|
||||||
@@ -1,8 +1,43 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { Button as HeroUIButton, ButtonProps as HeroUIButtonProps } from '@heroui/react';
|
||||||
|
import { tv } from '@heroui/styles';
|
||||||
import { Spinner } from './Spinner';
|
import { Spinner } from './Spinner';
|
||||||
|
|
||||||
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
const customButtonStyles = tv({
|
||||||
|
base: "relative inline-flex items-center justify-center font-medium select-none transition-all duration-200 ease-out focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-slate-950 active:scale-[0.98]",
|
||||||
|
variants: {
|
||||||
|
customVariant: {
|
||||||
|
primary: "bg-cyan-500 hover:bg-cyan-400 text-slate-950 font-semibold shadow-md shadow-cyan-500/20 hover:shadow-cyan-500/30 focus-visible:ring-cyan-500 dark:bg-cyan-500 dark:hover:bg-cyan-400 dark:text-slate-950 dark:shadow-cyan-500/30",
|
||||||
|
secondary: "bg-slate-100 hover:bg-slate-200 text-slate-800 border border-slate-200 shadow-sm focus-visible:ring-slate-400 dark:bg-slate-800 dark:hover:bg-slate-700 dark:text-slate-100 dark:border-slate-700 dark:shadow-none dark:focus-visible:ring-slate-500",
|
||||||
|
outline: "bg-transparent border border-slate-300 text-slate-700 hover:bg-slate-100 hover:border-slate-400 focus-visible:ring-slate-400 dark:border-slate-700 dark:text-slate-200 dark:hover:bg-slate-800/80 dark:hover:border-slate-600 dark:focus-visible:ring-slate-500",
|
||||||
|
ghost: "bg-transparent text-slate-600 hover:bg-slate-100 hover:text-slate-900 focus-visible:ring-slate-400 dark:text-slate-300 dark:hover:bg-slate-800/70 dark:hover:text-white dark:focus-visible:ring-slate-600",
|
||||||
|
danger: "bg-rose-600 hover:bg-rose-500 text-white font-medium shadow-md shadow-rose-600/25 focus-visible:ring-rose-500 dark:bg-rose-600 dark:hover:bg-rose-500 dark:text-white",
|
||||||
|
success: "bg-emerald-600 hover:bg-emerald-500 text-white font-medium shadow-md shadow-emerald-600/25 focus-visible:ring-emerald-500 dark:bg-emerald-500 dark:hover:bg-emerald-400 dark:text-slate-950",
|
||||||
|
gradient: "bg-gradient-to-r from-cyan-500 via-sky-500 to-blue-600 hover:from-cyan-400 hover:via-sky-400 hover:to-blue-500 text-white font-semibold shadow-lg shadow-cyan-500/25 hover:shadow-cyan-500/40 focus-visible:ring-cyan-500 dark:from-cyan-500 dark:to-blue-600",
|
||||||
|
violet: "bg-violet-600 hover:bg-violet-500 active:bg-violet-700 text-white font-bold shadow-lg shadow-violet-600/25 hover:shadow-violet-500/30 hover:-translate-y-0.5 focus-visible:ring-violet-500",
|
||||||
|
},
|
||||||
|
customSize: {
|
||||||
|
xs: "text-[11px] px-2.5 py-1 rounded-lg gap-1.5 h-auto min-w-min",
|
||||||
|
sm: "text-xs px-3 py-1.5 rounded-xl gap-2 h-auto min-w-min",
|
||||||
|
md: "text-sm px-4 py-2.5 rounded-xl gap-2 h-auto min-w-min",
|
||||||
|
lg: "text-base px-6 py-3.5 rounded-2xl gap-2.5 h-auto min-w-min",
|
||||||
|
icon: "p-2.5 rounded-xl justify-center items-center h-auto min-w-min",
|
||||||
|
},
|
||||||
|
fullWidth: {
|
||||||
|
true: "w-full",
|
||||||
|
},
|
||||||
|
isDisabled: {
|
||||||
|
true: "opacity-50 cursor-not-allowed pointer-events-none active:scale-100",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
customVariant: "primary",
|
||||||
|
customSize: "md",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export interface ButtonProps extends Omit<HeroUIButtonProps, 'variant' | 'size'> {
|
||||||
variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger' | 'success' | 'gradient' | 'violet';
|
variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger' | 'success' | 'gradient' | 'violet';
|
||||||
size?: 'xs' | 'sm' | 'md' | 'lg' | 'icon';
|
size?: 'xs' | 'sm' | 'md' | 'lg' | 'icon';
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
@@ -10,35 +45,11 @@ export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElemen
|
|||||||
leftIcon?: React.ReactNode;
|
leftIcon?: React.ReactNode;
|
||||||
rightIcon?: React.ReactNode;
|
rightIcon?: React.ReactNode;
|
||||||
fullWidth?: boolean;
|
fullWidth?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
className?: string;
|
||||||
|
children?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const variantStyles: Record<NonNullable<ButtonProps['variant']>, string> = {
|
|
||||||
primary:
|
|
||||||
'bg-cyan-500 hover:bg-cyan-400 text-slate-950 font-semibold shadow-md shadow-cyan-500/20 hover:shadow-cyan-500/30 focus-visible:ring-cyan-500 dark:bg-cyan-500 dark:hover:bg-cyan-400 dark:text-slate-950 dark:shadow-cyan-500/30',
|
|
||||||
secondary:
|
|
||||||
'bg-slate-100 hover:bg-slate-200 text-slate-800 border border-slate-200 shadow-sm focus-visible:ring-slate-400 dark:bg-slate-800 dark:hover:bg-slate-700 dark:text-slate-100 dark:border-slate-700 dark:shadow-none dark:focus-visible:ring-slate-500',
|
|
||||||
outline:
|
|
||||||
'bg-transparent border border-slate-300 text-slate-700 hover:bg-slate-100 hover:border-slate-400 focus-visible:ring-slate-400 dark:border-slate-700 dark:text-slate-200 dark:hover:bg-slate-800/80 dark:hover:border-slate-600 dark:focus-visible:ring-slate-500',
|
|
||||||
ghost:
|
|
||||||
'bg-transparent text-slate-600 hover:bg-slate-100 hover:text-slate-900 focus-visible:ring-slate-400 dark:text-slate-300 dark:hover:bg-slate-800/70 dark:hover:text-white dark:focus-visible:ring-slate-600',
|
|
||||||
danger:
|
|
||||||
'bg-rose-600 hover:bg-rose-500 text-white font-medium shadow-md shadow-rose-600/25 focus-visible:ring-rose-500 dark:bg-rose-600 dark:hover:bg-rose-500 dark:text-white',
|
|
||||||
success:
|
|
||||||
'bg-emerald-600 hover:bg-emerald-500 text-white font-medium shadow-md shadow-emerald-600/25 focus-visible:ring-emerald-500 dark:bg-emerald-500 dark:hover:bg-emerald-400 dark:text-slate-950',
|
|
||||||
gradient:
|
|
||||||
'bg-gradient-to-r from-cyan-500 via-sky-500 to-blue-600 hover:from-cyan-400 hover:via-sky-400 hover:to-blue-500 text-white font-semibold shadow-lg shadow-cyan-500/25 hover:shadow-cyan-500/40 focus-visible:ring-cyan-500 dark:from-cyan-500 dark:to-blue-600',
|
|
||||||
violet:
|
|
||||||
'bg-violet-600 hover:bg-violet-500 active:bg-violet-700 text-white font-bold shadow-lg shadow-violet-600/25 hover:shadow-violet-500/30 hover:-translate-y-0.5 focus-visible:ring-violet-500',
|
|
||||||
};
|
|
||||||
|
|
||||||
const sizeStyles: Record<NonNullable<ButtonProps['size']>, string> = {
|
|
||||||
xs: 'text-[11px] px-2.5 py-1 rounded-lg gap-1.5',
|
|
||||||
sm: 'text-xs px-3 py-1.5 rounded-xl gap-2',
|
|
||||||
md: 'text-sm px-4 py-2.5 rounded-xl gap-2',
|
|
||||||
lg: 'text-base px-6 py-3.5 rounded-2xl gap-2.5',
|
|
||||||
icon: 'p-2.5 rounded-xl justify-center items-center',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
@@ -50,30 +61,27 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|||||||
rightIcon,
|
rightIcon,
|
||||||
fullWidth = false,
|
fullWidth = false,
|
||||||
disabled,
|
disabled,
|
||||||
|
isDisabled,
|
||||||
className = '',
|
className = '',
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
},
|
},
|
||||||
ref
|
ref
|
||||||
) => {
|
) => {
|
||||||
const isInteractiveDisabled = disabled || isLoading;
|
const actuallyDisabled = disabled || isDisabled || isLoading;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<HeroUIButton
|
||||||
ref={ref}
|
ref={ref}
|
||||||
disabled={isInteractiveDisabled}
|
isDisabled={actuallyDisabled}
|
||||||
className={`
|
className={customButtonStyles({
|
||||||
relative inline-flex items-center justify-center font-medium select-none
|
customVariant: variant,
|
||||||
transition-all duration-200 ease-out
|
customSize: size,
|
||||||
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2
|
fullWidth,
|
||||||
focus-visible:ring-offset-white dark:focus-visible:ring-offset-slate-950
|
isDisabled: actuallyDisabled,
|
||||||
active:scale-[0.98]
|
className
|
||||||
disabled:opacity-50 disabled:cursor-not-allowed disabled:pointer-events-none disabled:active:scale-100
|
})}
|
||||||
${fullWidth ? 'w-full' : ''}
|
variant={undefined}
|
||||||
${variantStyles[variant]}
|
|
||||||
${sizeStyles[size]}
|
|
||||||
${className}
|
|
||||||
`}
|
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
@@ -91,7 +99,7 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|||||||
{rightIcon && <span className="shrink-0">{rightIcon}</span>}
|
{rightIcon && <span className="shrink-0">{rightIcon}</span>}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</button>
|
</HeroUIButton>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
+126
-76
@@ -1,147 +1,197 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
export interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
|
import React from "react";
|
||||||
variant?: 'default' | 'elevated' | 'glass' | 'outline' | 'interactive';
|
import { Card as HeroUICard } from "@heroui/react";
|
||||||
padding?: 'none' | 'sm' | 'md' | 'lg';
|
|
||||||
|
export type CardVariant =
|
||||||
|
| "default"
|
||||||
|
| "elevated"
|
||||||
|
| "glass"
|
||||||
|
| "outline"
|
||||||
|
| "interactive";
|
||||||
|
|
||||||
|
export type CardPadding = "none" | "sm" | "md" | "lg";
|
||||||
|
|
||||||
|
export interface CardProps
|
||||||
|
extends Omit<
|
||||||
|
React.ComponentProps<typeof HeroUICard>,
|
||||||
|
"variant" | "className"
|
||||||
|
> {
|
||||||
|
variant?: CardVariant;
|
||||||
|
padding?: CardPadding;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const variantStyles: Record<NonNullable<CardProps['variant']>, string> = {
|
const variantStyles: Record<CardVariant, string> = {
|
||||||
default:
|
default:
|
||||||
'bg-white dark:bg-slate-900/90 border border-slate-200 dark:border-slate-800/80 shadow-sm',
|
"bg-white dark:bg-slate-900/90 border border-slate-200 dark:border-slate-800/80 shadow-sm",
|
||||||
|
|
||||||
elevated:
|
elevated:
|
||||||
'bg-white dark:bg-slate-900 border border-slate-100 dark:border-slate-800 shadow-xl shadow-slate-200/50 dark:shadow-black/40',
|
"bg-white dark:bg-slate-900 border border-slate-100 dark:border-slate-800 shadow-xl shadow-slate-200/50 dark:shadow-black/40",
|
||||||
|
|
||||||
glass:
|
glass:
|
||||||
'bg-white/80 dark:bg-slate-900/60 backdrop-blur-xl border border-slate-200/70 dark:border-slate-800/70 shadow-lg',
|
"bg-white/80 dark:bg-slate-900/60 backdrop-blur-xl border border-slate-200/70 dark:border-slate-800/70 shadow-lg",
|
||||||
|
|
||||||
outline:
|
outline:
|
||||||
'bg-transparent border border-slate-300 dark:border-slate-800 shadow-none',
|
"bg-transparent border border-slate-300 dark:border-slate-800 shadow-none",
|
||||||
|
|
||||||
interactive:
|
interactive:
|
||||||
'bg-white dark:bg-slate-900/90 border border-slate-200 dark:border-slate-800 shadow-sm hover:shadow-xl hover:shadow-cyan-500/10 hover:border-cyan-500/50 dark:hover:border-cyan-500/40 cursor-pointer transform hover:-translate-y-1 transition-all duration-300 ease-out',
|
"bg-white dark:bg-slate-900/90 border border-slate-200 dark:border-slate-800 shadow-sm " +
|
||||||
|
"hover:shadow-xl hover:shadow-cyan-500/10 " +
|
||||||
|
"hover:border-cyan-500/50 dark:hover:border-cyan-500/40 " +
|
||||||
|
"cursor-pointer transform hover:-translate-y-1 " +
|
||||||
|
"transition-all duration-300 ease-out",
|
||||||
};
|
};
|
||||||
|
|
||||||
const paddingStyles: Record<NonNullable<CardProps['padding']>, string> = {
|
const paddingStyles: Record<CardPadding, string> = {
|
||||||
none: 'p-0',
|
none: "p-0",
|
||||||
sm: 'p-4',
|
sm: "p-4",
|
||||||
md: 'p-6',
|
md: "p-6",
|
||||||
lg: 'p-8',
|
lg: "p-8",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Card = React.forwardRef<HTMLDivElement, CardProps>(
|
export const Card = ({
|
||||||
(
|
variant = "default",
|
||||||
{
|
padding = "none",
|
||||||
variant = 'default',
|
className = "",
|
||||||
padding = 'none',
|
|
||||||
className = '',
|
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
},
|
}: CardProps) => {
|
||||||
ref
|
|
||||||
) => {
|
|
||||||
return (
|
return (
|
||||||
<div
|
<HeroUICard
|
||||||
ref={ref}
|
|
||||||
className={`
|
|
||||||
rounded-2xl overflow-hidden transition-colors
|
|
||||||
${variantStyles[variant]}
|
|
||||||
${paddingStyles[padding]}
|
|
||||||
${className}
|
|
||||||
`}
|
|
||||||
{...props}
|
{...props}
|
||||||
|
className={[
|
||||||
|
"rounded-2xl overflow-hidden transition-colors",
|
||||||
|
variantStyles[variant],
|
||||||
|
paddingStyles[padding],
|
||||||
|
className,
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(" ")}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</HeroUICard>
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
Card.displayName = 'Card';
|
export interface CardHeaderProps
|
||||||
|
extends React.ComponentProps<typeof HeroUICard.Header> {
|
||||||
export interface CardHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
||||||
bordered?: boolean;
|
bordered?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CardHeader: React.FC<CardHeaderProps> = ({
|
export const CardHeader = ({
|
||||||
bordered = false,
|
bordered = false,
|
||||||
className = '',
|
className = "",
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}) => {
|
}: CardHeaderProps) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<HeroUICard.Header
|
||||||
className={`
|
|
||||||
p-5 sm:p-6 flex flex-col space-y-1.5
|
|
||||||
${bordered ? 'border-b border-slate-100 dark:border-slate-800/80' : ''}
|
|
||||||
${className}
|
|
||||||
`}
|
|
||||||
{...props}
|
{...props}
|
||||||
|
className={[
|
||||||
|
"p-5 sm:p-6 flex flex-col space-y-1.5",
|
||||||
|
bordered
|
||||||
|
? "border-b border-slate-100 dark:border-slate-800/80"
|
||||||
|
: "",
|
||||||
|
className,
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(" ")}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</HeroUICard.Header>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CardTitle: React.FC<React.HTMLAttributes<HTMLHeadingElement>> = ({
|
export const CardTitle = ({
|
||||||
className = '',
|
className = "",
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}) => {
|
}: React.ComponentProps<typeof HeroUICard.Title>) => {
|
||||||
return (
|
return (
|
||||||
<h3
|
<HeroUICard.Title
|
||||||
className={`text-lg sm:text-xl font-bold tracking-tight text-slate-900 dark:text-white transition-colors ${className}`}
|
|
||||||
{...props}
|
{...props}
|
||||||
|
className={[
|
||||||
|
"text-lg sm:text-xl font-bold tracking-tight",
|
||||||
|
"text-slate-900 dark:text-white",
|
||||||
|
"transition-colors",
|
||||||
|
className,
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(" ")}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</h3>
|
</HeroUICard.Title>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CardDescription: React.FC<React.HTMLAttributes<HTMLParagraphElement>> = ({
|
export const CardDescription = ({
|
||||||
className = '',
|
className = "",
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}) => {
|
}: React.ComponentProps<typeof HeroUICard.Description>) => {
|
||||||
return (
|
return (
|
||||||
<p
|
<HeroUICard.Description
|
||||||
className={`text-xs sm:text-sm text-slate-500 dark:text-slate-400 leading-relaxed ${className}`}
|
|
||||||
{...props}
|
{...props}
|
||||||
|
className={[
|
||||||
|
"text-xs sm:text-sm",
|
||||||
|
"text-slate-500 dark:text-slate-400",
|
||||||
|
"leading-relaxed",
|
||||||
|
className,
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(" ")}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</p>
|
</HeroUICard.Description>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CardContent: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({
|
export const CardContent = ({
|
||||||
className = '',
|
className = "",
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}) => {
|
}: React.ComponentProps<typeof HeroUICard.Content>) => {
|
||||||
return (
|
return (
|
||||||
<div className={`p-5 sm:p-6 ${className}`} {...props}>
|
<HeroUICard.Content
|
||||||
|
{...props}
|
||||||
|
className={[
|
||||||
|
"p-5 sm:p-6",
|
||||||
|
className,
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(" ")}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</HeroUICard.Content>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface CardFooterProps extends React.HTMLAttributes<HTMLDivElement> {
|
export interface CardFooterProps
|
||||||
|
extends React.ComponentProps<typeof HeroUICard.Footer> {
|
||||||
bordered?: boolean;
|
bordered?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CardFooter: React.FC<CardFooterProps> = ({
|
export const CardFooter = ({
|
||||||
bordered = false,
|
bordered = false,
|
||||||
className = '',
|
className = "",
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}) => {
|
}: CardFooterProps) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<HeroUICard.Footer
|
||||||
className={`
|
|
||||||
p-5 sm:p-6 flex items-center justify-between
|
|
||||||
${bordered ? 'border-t border-slate-100 dark:border-slate-800/80' : ''}
|
|
||||||
${className}
|
|
||||||
`}
|
|
||||||
{...props}
|
{...props}
|
||||||
|
className={[
|
||||||
|
"p-5 sm:p-6 flex items-center justify-between",
|
||||||
|
bordered
|
||||||
|
? "border-t border-slate-100 dark:border-slate-800/80"
|
||||||
|
: "",
|
||||||
|
className,
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(" ")}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</HeroUICard.Footer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
+112
-76
@@ -1,112 +1,148 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import React, { useEffect, useRef } from 'react';
|
|
||||||
import { FiCheck, FiMinus } from 'react-icons/fi';
|
|
||||||
|
|
||||||
export interface CheckboxProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type'> {
|
import React from "react";
|
||||||
|
import {
|
||||||
|
Checkbox as HeroUICheckbox,
|
||||||
|
Description,
|
||||||
|
FieldError,
|
||||||
|
} from "@heroui/react";
|
||||||
|
|
||||||
|
import { FiCheck, FiMinus } from "react-icons/fi";
|
||||||
|
|
||||||
|
export interface CheckboxProps
|
||||||
|
extends Omit<
|
||||||
|
React.ComponentProps<typeof HeroUICheckbox>,
|
||||||
|
"children" | "defaultSelected" | "isDisabled" | "isInvalid" | "isIndeterminate"
|
||||||
|
> {
|
||||||
label?: React.ReactNode;
|
label?: React.ReactNode;
|
||||||
description?: string;
|
description?: string;
|
||||||
errorMessage?: string;
|
errorMessage?: string;
|
||||||
|
checked?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
indeterminate?: boolean;
|
indeterminate?: boolean;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
|
export const Checkbox = ({
|
||||||
(
|
|
||||||
{
|
|
||||||
label,
|
label,
|
||||||
description,
|
description,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
indeterminate = false,
|
indeterminate = false,
|
||||||
checked,
|
checked,
|
||||||
disabled,
|
isSelected,
|
||||||
className = '',
|
disabled = false,
|
||||||
|
className = "",
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
|
onChange,
|
||||||
...props
|
...props
|
||||||
},
|
}: CheckboxProps) => {
|
||||||
forwardedRef
|
|
||||||
) => {
|
|
||||||
const internalRef = useRef<HTMLInputElement>(null);
|
|
||||||
const resolvedRef = (forwardedRef as React.RefObject<HTMLInputElement>) || internalRef;
|
|
||||||
const checkboxId = id || name || (typeof label === 'string' ? label.toLowerCase().replace(/\s+/g, '-') : undefined);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (resolvedRef.current) {
|
|
||||||
resolvedRef.current.indeterminate = indeterminate;
|
|
||||||
}
|
|
||||||
}, [indeterminate, resolvedRef]);
|
|
||||||
|
|
||||||
const isChecked = Boolean(checked);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`flex flex-col space-y-1 ${className}`}>
|
<HeroUICheckbox
|
||||||
<label
|
|
||||||
htmlFor={checkboxId}
|
|
||||||
className={`inline-flex items-start gap-2.5 select-none cursor-pointer group ${
|
|
||||||
disabled ? 'opacity-50 !cursor-not-allowed' : ''
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{/* Custom Styled Box */}
|
|
||||||
<div className="relative flex items-center justify-center mt-0.5 shrink-0">
|
|
||||||
<input
|
|
||||||
ref={resolvedRef}
|
|
||||||
id={checkboxId}
|
|
||||||
name={name}
|
|
||||||
type="checkbox"
|
|
||||||
checked={checked}
|
|
||||||
disabled={disabled}
|
|
||||||
className="peer sr-only"
|
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
id={id}
|
||||||
|
name={name}
|
||||||
<div
|
isSelected={checked}
|
||||||
className={`
|
isDisabled={disabled}
|
||||||
w-4 h-4 rounded-md flex items-center justify-center transition-all duration-200
|
isIndeterminate={indeterminate}
|
||||||
border
|
isInvalid={Boolean(errorMessage)}
|
||||||
peer-focus-visible:ring-2 peer-focus-visible:ring-offset-2
|
onChange={onChange}
|
||||||
peer-focus-visible:ring-cyan-500 peer-focus-visible:ring-offset-white dark:peer-focus-visible:ring-offset-slate-950
|
className={className}
|
||||||
${
|
|
||||||
isChecked || indeterminate
|
|
||||||
? 'bg-cyan-500 border-cyan-500 text-slate-950 shadow-sm shadow-cyan-500/20'
|
|
||||||
: 'bg-white dark:bg-slate-900 border-slate-300 dark:border-slate-700 text-transparent group-hover:border-slate-400 dark:group-hover:border-slate-500'
|
|
||||||
}
|
|
||||||
${
|
|
||||||
errorMessage
|
|
||||||
? '!border-rose-500 !bg-rose-50 dark:!bg-rose-950/30'
|
|
||||||
: ''
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
>
|
>
|
||||||
{indeterminate ? (
|
<HeroUICheckbox.Content className="items-start gap-2.5">
|
||||||
<FiMinus className="text-xs stroke-3" />
|
<HeroUICheckbox.Control
|
||||||
) : isChecked ? (
|
className={[
|
||||||
<FiCheck className="text-xs stroke-3" />
|
"mt-0.5 shrink-0 size-4 rounded-md",
|
||||||
) : null}
|
"border",
|
||||||
</div>
|
"transition-all duration-200",
|
||||||
</div>
|
|
||||||
|
// Estado normal
|
||||||
|
"bg-white dark:bg-slate-900",
|
||||||
|
"border-slate-300 dark:border-slate-700",
|
||||||
|
|
||||||
|
// Hover
|
||||||
|
"group-hover:border-slate-400",
|
||||||
|
"dark:group-hover:border-slate-500",
|
||||||
|
|
||||||
|
// Focus
|
||||||
|
"data-[focus-visible=true]:ring-2",
|
||||||
|
"data-[focus-visible=true]:ring-cyan-500",
|
||||||
|
"data-[focus-visible=true]:ring-offset-2",
|
||||||
|
"data-[focus-visible=true]:ring-offset-white",
|
||||||
|
"dark:data-[focus-visible=true]:ring-offset-slate-950",
|
||||||
|
|
||||||
|
// Seleccionado
|
||||||
|
"data-[selected=true]:bg-cyan-500",
|
||||||
|
"data-[selected=true]:border-cyan-500",
|
||||||
|
"data-[selected=true]:text-slate-950",
|
||||||
|
"data-[selected=true]:shadow-sm",
|
||||||
|
"data-[selected=true]:shadow-cyan-500/20",
|
||||||
|
|
||||||
|
// Indeterminado
|
||||||
|
"data-[indeterminate=true]:bg-cyan-500",
|
||||||
|
"data-[indeterminate=true]:border-cyan-500",
|
||||||
|
"data-[indeterminate=true]:text-slate-950",
|
||||||
|
|
||||||
|
// Error
|
||||||
|
errorMessage
|
||||||
|
? "data-[invalid=true]:border-rose-500"
|
||||||
|
: "",
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(" ")}
|
||||||
|
>
|
||||||
|
<HeroUICheckbox.Indicator>
|
||||||
|
{({ isSelected, isIndeterminate }) => {
|
||||||
|
if (isIndeterminate) {
|
||||||
|
return <FiMinus className="text-xs stroke-3" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSelected) {
|
||||||
|
return <FiCheck className="text-xs stroke-3" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}}
|
||||||
|
</HeroUICheckbox.Indicator>
|
||||||
|
</HeroUICheckbox.Control>
|
||||||
|
|
||||||
{/* Label and Sub-description */}
|
|
||||||
{(label || description) && (
|
{(label || description) && (
|
||||||
<div className="flex flex-col text-left">
|
<div className="flex flex-col text-left">
|
||||||
{label && (
|
{label && (
|
||||||
<span className="text-xs sm:text-sm font-medium text-slate-800 dark:text-slate-200 group-hover:text-slate-950 dark:group-hover:text-white transition-colors">
|
<span
|
||||||
|
className="
|
||||||
|
text-xs sm:text-sm font-medium
|
||||||
|
text-slate-800 dark:text-slate-200
|
||||||
|
group-hover:text-slate-950
|
||||||
|
dark:group-hover:text-white
|
||||||
|
transition-colors
|
||||||
|
"
|
||||||
|
>
|
||||||
{label}
|
{label}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{description && (
|
{description && (
|
||||||
<span className="text-xs text-slate-500 dark:text-slate-400">
|
<Description
|
||||||
|
className="
|
||||||
|
text-xs
|
||||||
|
text-slate-500 dark:text-slate-400
|
||||||
|
"
|
||||||
|
>
|
||||||
{description}
|
{description}
|
||||||
</span>
|
</Description>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</label>
|
</HeroUICheckbox.Content>
|
||||||
|
|
||||||
{errorMessage && (
|
{errorMessage && (
|
||||||
<p className="text-xs text-rose-600 dark:text-rose-400 pl-6.5">{errorMessage}</p>
|
<FieldError className="pl-6.5 text-xs">
|
||||||
|
{errorMessage}
|
||||||
|
</FieldError>
|
||||||
)}
|
)}
|
||||||
</div>
|
</HeroUICheckbox>
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
Checkbox.displayName = 'Checkbox';
|
Checkbox.displayName = "Checkbox";
|
||||||
+317
-251
@@ -1,289 +1,355 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { useState, useRef, useEffect, useCallback } from "react";
|
|
||||||
import { createPortal } from "react-dom";
|
|
||||||
import { FiCalendar, FiChevronLeft, FiChevronRight, FiX } from "react-icons/fi";
|
|
||||||
|
|
||||||
interface DatePickerProps {
|
import React, { useMemo } from "react";
|
||||||
|
import {
|
||||||
|
DatePicker as HeroUIDatePicker,
|
||||||
|
DateField as HeroUIDateField,
|
||||||
|
Calendar as HeroUICalendar,
|
||||||
|
Label,
|
||||||
|
FieldError,
|
||||||
|
} from "@heroui/react";
|
||||||
|
|
||||||
|
import type { DateValue } from "@internationalized/date";
|
||||||
|
import { parseDate as parseInternationalDate } from "@internationalized/date";
|
||||||
|
|
||||||
|
import {
|
||||||
|
FiCalendar,
|
||||||
|
FiChevronLeft,
|
||||||
|
FiChevronRight,
|
||||||
|
FiX,
|
||||||
|
} from "react-icons/fi";
|
||||||
|
|
||||||
|
export interface DatePickerProps {
|
||||||
label?: string;
|
label?: string;
|
||||||
value?: string; // "YYYY-MM-DD"
|
value?: string; //Fecha en formato YYYY-MM-DD o undefined cuando se limpia
|
||||||
onChange: (value: string | undefined) => void;
|
onChange: (value: string | undefined) => void; //Devuelve la fecha en formato YYYY-MM-DD o undefined cuando se limpia.
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
maxDate?: Date;
|
maxDate?: Date; //Fecha máxima permitida.
|
||||||
|
className?: string;
|
||||||
|
errorMessage?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MONTHS = [
|
function toCalendarDate(dateStr?: string): DateValue | undefined {
|
||||||
"Enero","Febrero","Marzo","Abril","Mayo","Junio",
|
if (!dateStr) {
|
||||||
"Julio","Agosto","Septiembre","Octubre","Noviembre","Diciembre",
|
return undefined;
|
||||||
];
|
|
||||||
const DAYS = ["Do","Lu","Ma","Mi","Ju","Vi","Sa"];
|
|
||||||
const DROPDOWN_H = 320; // px estimado del dropdown
|
|
||||||
|
|
||||||
function parseDate(value?: string): Date | undefined {
|
|
||||||
if (!value) return undefined;
|
|
||||||
const d = new Date(value + "T12:00:00");
|
|
||||||
return isNaN(d.getTime()) ? undefined : d;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatDisplay(value?: string): string {
|
try {
|
||||||
const d = parseDate(value);
|
return parseInternationalDate(dateStr);
|
||||||
if (!d) return "";
|
} catch {
|
||||||
return d.toLocaleDateString("es-MX", { day: "numeric", month: "long", year: "numeric" });
|
return undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DatePicker({
|
function toDateString(date: DateValue): string {
|
||||||
|
const yyyy = date.year;
|
||||||
|
const mm = String(date.month).padStart(2, "0");
|
||||||
|
const dd = String(date.day).padStart(2, "0");
|
||||||
|
|
||||||
|
return `${yyyy}-${mm}-${dd}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DatePicker = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
DatePickerProps
|
||||||
|
>(
|
||||||
|
(
|
||||||
|
{
|
||||||
label,
|
label,
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
placeholder = "Selecciona una fecha",
|
placeholder,
|
||||||
required,
|
required = false,
|
||||||
maxDate = new Date(),
|
maxDate,
|
||||||
}: DatePickerProps) {
|
className = "",
|
||||||
const selected = parseDate(value);
|
errorMessage,
|
||||||
const [open, setOpen] = useState(false);
|
},
|
||||||
const [view, setView] = useState<"days" | "months" | "years">("days");
|
ref
|
||||||
const [cursor, setCursor] = useState(() => {
|
) => {
|
||||||
const d = selected ?? new Date();
|
const dateValue = useMemo(
|
||||||
return { month: d.getMonth(), year: d.getFullYear() };
|
() => toCalendarDate(value),
|
||||||
});
|
[value]
|
||||||
const [pos, setPos] = useState({ top: 0, left: 0, width: 0, openUp: false });
|
);
|
||||||
|
|
||||||
const triggerRef = useRef<HTMLButtonElement>(null);
|
const maxDateValue = useMemo<DateValue | undefined>(() => {
|
||||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
if (!maxDate) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
const calcPos = useCallback(() => {
|
try {
|
||||||
if (!triggerRef.current) return;
|
const yyyy = maxDate.getFullYear();
|
||||||
const r = triggerRef.current.getBoundingClientRect();
|
const mm = String(maxDate.getMonth() + 1).padStart(2, "0");
|
||||||
const spaceBelow = window.innerHeight - r.bottom;
|
const dd = String(maxDate.getDate()).padStart(2, "0");
|
||||||
const openUp = spaceBelow < DROPDOWN_H + 8 && r.top > DROPDOWN_H + 8;
|
|
||||||
setPos({
|
|
||||||
top: openUp ? r.top - 8 : r.bottom + 8,
|
|
||||||
left: r.left,
|
|
||||||
width: r.width,
|
|
||||||
openUp,
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const openPicker = () => {
|
return parseInternationalDate(`${yyyy}-${mm}-${dd}`);
|
||||||
if (selected) setCursor({ month: selected.getMonth(), year: selected.getFullYear() });
|
} catch {
|
||||||
setView("days");
|
return undefined;
|
||||||
calcPos();
|
}
|
||||||
setOpen(true);
|
}, [maxDate]);
|
||||||
|
|
||||||
|
const handleChange = (newDate: DateValue | null) => {
|
||||||
|
if (newDate) {
|
||||||
|
onChange(toDateString(newDate));
|
||||||
|
} else {
|
||||||
|
onChange(undefined);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const handleClear = () => {
|
||||||
if (!open) return;
|
onChange(undefined);
|
||||||
const onScroll = () => calcPos();
|
|
||||||
const onResize = () => calcPos();
|
|
||||||
const onMouse = (e: MouseEvent) => {
|
|
||||||
if (
|
|
||||||
triggerRef.current?.contains(e.target as Node) ||
|
|
||||||
dropdownRef.current?.contains(e.target as Node)
|
|
||||||
) return;
|
|
||||||
setOpen(false);
|
|
||||||
};
|
};
|
||||||
window.addEventListener("scroll", onScroll, true);
|
|
||||||
window.addEventListener("resize", onResize);
|
|
||||||
document.addEventListener("mousedown", onMouse);
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener("scroll", onScroll, true);
|
|
||||||
window.removeEventListener("resize", onResize);
|
|
||||||
document.removeEventListener("mousedown", onMouse);
|
|
||||||
};
|
|
||||||
}, [open, calcPos]);
|
|
||||||
|
|
||||||
const selectDay = (day: number) => {
|
|
||||||
const d = new Date(cursor.year, cursor.month, day);
|
|
||||||
const iso = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
||||||
onChange(iso);
|
|
||||||
setOpen(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const daysInMonth = new Date(cursor.year, cursor.month + 1, 0).getDate();
|
|
||||||
const firstDow = new Date(cursor.year, cursor.month, 1).getDay();
|
|
||||||
const today = new Date();
|
|
||||||
|
|
||||||
const isDisabled = (day: number) => new Date(cursor.year, cursor.month, day) > maxDate;
|
|
||||||
const isSelected = (day: number) =>
|
|
||||||
selected?.getFullYear() === cursor.year &&
|
|
||||||
selected?.getMonth() === cursor.month &&
|
|
||||||
selected?.getDate() === day;
|
|
||||||
const isToday = (day: number) =>
|
|
||||||
today.getFullYear() === cursor.year &&
|
|
||||||
today.getMonth() === cursor.month &&
|
|
||||||
today.getDate() === day;
|
|
||||||
|
|
||||||
const yearRange = Array.from({ length: 101 }, (_, i) => today.getFullYear() - 100 + i).reverse();
|
|
||||||
|
|
||||||
const dropdown = open ? (
|
|
||||||
<div
|
|
||||||
ref={dropdownRef}
|
|
||||||
style={{
|
|
||||||
position: "fixed",
|
|
||||||
top: pos.openUp ? "auto" : pos.top,
|
|
||||||
bottom: pos.openUp ? window.innerHeight - pos.top : "auto",
|
|
||||||
left: pos.left,
|
|
||||||
width: Math.max(pos.width, 288),
|
|
||||||
zIndex: 9999,
|
|
||||||
}}
|
|
||||||
className="rounded-2xl shadow-2xl border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-900 overflow-hidden"
|
|
||||||
>
|
|
||||||
{/* Header */}
|
|
||||||
<div className="flex items-center justify-between px-4 py-3 border-b border-slate-100 dark:border-slate-800">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => view === "days" && setCursor(c => {
|
|
||||||
const d = new Date(c.year, c.month - 1, 1);
|
|
||||||
return { month: d.getMonth(), year: d.getFullYear() };
|
|
||||||
})}
|
|
||||||
className="p-1.5 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 transition text-slate-500 dark:text-slate-400"
|
|
||||||
>
|
|
||||||
<FiChevronLeft />
|
|
||||||
</button>
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setView(v => v === "months" ? "days" : "months")}
|
|
||||||
className="text-sm font-semibold text-slate-800 dark:text-slate-100 hover:text-violet-600 dark:hover:text-violet-400 transition"
|
|
||||||
>
|
|
||||||
{MONTHS[cursor.month]}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setView(v => v === "years" ? "days" : "years")}
|
|
||||||
className="text-sm font-semibold text-slate-800 dark:text-slate-100 hover:text-violet-600 dark:hover:text-violet-400 transition"
|
|
||||||
>
|
|
||||||
{cursor.year}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => view === "days" && setCursor(c => {
|
|
||||||
const d = new Date(c.year, c.month + 1, 1);
|
|
||||||
return { month: d.getMonth(), year: d.getFullYear() };
|
|
||||||
})}
|
|
||||||
className="p-1.5 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 transition text-slate-500 dark:text-slate-400"
|
|
||||||
>
|
|
||||||
<FiChevronRight />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Days */}
|
|
||||||
{view === "days" && (
|
|
||||||
<div className="p-3">
|
|
||||||
<div className="grid grid-cols-7 mb-1">
|
|
||||||
{DAYS.map(d => (
|
|
||||||
<div key={d} className="text-center text-[10px] font-semibold text-slate-400 dark:text-slate-500 py-1">{d}</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-7 gap-y-0.5">
|
|
||||||
{Array.from({ length: firstDow }).map((_, i) => <div key={`e-${i}`} />)}
|
|
||||||
{Array.from({ length: daysInMonth }, (_, i) => i + 1).map(day => (
|
|
||||||
<button
|
|
||||||
key={day}
|
|
||||||
type="button"
|
|
||||||
disabled={isDisabled(day)}
|
|
||||||
onClick={() => selectDay(day)}
|
|
||||||
className={`
|
|
||||||
aspect-square w-full flex items-center justify-center rounded-lg text-sm transition-all
|
|
||||||
${isSelected(day)
|
|
||||||
? "bg-violet-600 text-white font-semibold"
|
|
||||||
: isToday(day)
|
|
||||||
? "border border-violet-400 text-violet-600 dark:text-violet-400 font-semibold"
|
|
||||||
: "text-slate-700 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-800"}
|
|
||||||
${isDisabled(day) ? "opacity-30 cursor-not-allowed" : ""}
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
{day}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Months */}
|
|
||||||
{view === "months" && (
|
|
||||||
<div className="grid grid-cols-3 gap-2 p-3">
|
|
||||||
{MONTHS.map((m, i) => (
|
|
||||||
<button
|
|
||||||
key={m}
|
|
||||||
type="button"
|
|
||||||
onClick={() => { setCursor(c => ({ ...c, month: i })); setView("days"); }}
|
|
||||||
className={`py-2 rounded-xl text-sm font-medium transition ${
|
|
||||||
cursor.month === i
|
|
||||||
? "bg-violet-600 text-white"
|
|
||||||
: "text-slate-700 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-800"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{m.slice(0, 3)}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Years */}
|
|
||||||
{view === "years" && (
|
|
||||||
<div className="h-52 overflow-y-auto p-2 grid grid-cols-3 gap-1 content-start">
|
|
||||||
{yearRange.map(y => (
|
|
||||||
<button
|
|
||||||
key={y}
|
|
||||||
type="button"
|
|
||||||
onClick={() => { setCursor(c => ({ ...c, year: y })); setView("days"); }}
|
|
||||||
className={`py-2 rounded-xl text-sm font-medium transition ${
|
|
||||||
cursor.year === y
|
|
||||||
? "bg-violet-600 text-white"
|
|
||||||
: "text-slate-700 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-800"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{y}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
) : null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full space-y-1.5">
|
<HeroUIDatePicker.Root
|
||||||
|
ref={ref}
|
||||||
|
value={dateValue ?? null}
|
||||||
|
onChange={handleChange}
|
||||||
|
maxValue={maxDateValue}
|
||||||
|
isRequired={required}
|
||||||
|
isInvalid={Boolean(errorMessage)}
|
||||||
|
className={`w-full flex flex-col gap-1.5 ${className}`}
|
||||||
|
>
|
||||||
|
{/* Label */}
|
||||||
{label && (
|
{label && (
|
||||||
<label className="block text-xs font-semibold text-slate-700 dark:text-slate-300">
|
<Label className="block text-xs font-semibold text-slate-700 dark:text-slate-300">
|
||||||
{label}
|
{label}
|
||||||
{required && <span className="ml-1 text-rose-500 font-bold">*</span>}
|
|
||||||
</label>
|
{required && (
|
||||||
|
<span className="ml-1 font-bold text-rose-500">
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</Label>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="relative flex items-center">
|
{/* Campo de fecha */}
|
||||||
<div className="absolute left-3.5 pointer-events-none text-slate-400 dark:text-slate-500">
|
<HeroUIDateField.Group
|
||||||
<FiCalendar className="text-base" />
|
className={[
|
||||||
</div>
|
"bg-white dark:bg-slate-900/90",
|
||||||
<button
|
"border border-slate-300 dark:border-slate-800",
|
||||||
ref={triggerRef}
|
"hover:border-slate-400 dark:hover:border-slate-700",
|
||||||
type="button"
|
"data-[focus-within=true]:border-cyan-500",
|
||||||
onClick={openPicker}
|
"data-[focus-within=true]:ring-4",
|
||||||
className={`
|
"data-[focus-within=true]:ring-cyan-500/15",
|
||||||
w-full pl-10 pr-10 py-2.5 text-sm rounded-xl text-left transition-all duration-200 outline-none
|
"rounded-xl flex items-center shadow-sm",
|
||||||
bg-white dark:bg-slate-900/90
|
"transition-all duration-200 h-10",
|
||||||
border border-slate-300 dark:border-slate-800
|
errorMessage
|
||||||
hover:border-slate-400 dark:hover:border-slate-700
|
? "border-rose-500 dark:border-rose-500"
|
||||||
focus:border-cyan-500 focus:ring-4 focus:ring-cyan-500/15
|
: "",
|
||||||
dark:focus:border-cyan-500 dark:focus:ring-cyan-500/25
|
].join(" ")}
|
||||||
${value ? "text-slate-900 dark:text-slate-100" : "text-slate-400 dark:text-slate-500"}
|
|
||||||
`}
|
|
||||||
>
|
>
|
||||||
{value ? formatDisplay(value) : placeholder}
|
{/* Icono izquierdo */}
|
||||||
</button>
|
<HeroUIDateField.Prefix
|
||||||
|
className="pl-3.5 pr-1.5 text-slate-400 dark:text-slate-500"
|
||||||
|
>
|
||||||
|
<FiCalendar className="text-base" />
|
||||||
|
</HeroUIDateField.Prefix>
|
||||||
|
|
||||||
|
{/* Input segmentado */}
|
||||||
|
<HeroUIDateField.Input
|
||||||
|
className="flex flex-1 items-center text-sm text-slate-900 outline-none dark:text-slate-100"
|
||||||
|
>
|
||||||
|
{(segment) => (
|
||||||
|
<HeroUIDateField.Segment
|
||||||
|
segment={segment}
|
||||||
|
className={[
|
||||||
|
"min-w-5 rounded px-0.5 text-center outline-none",
|
||||||
|
"data-[placeholder=true]:text-slate-400",
|
||||||
|
"dark:data-[placeholder=true]:text-slate-500",
|
||||||
|
"focus:bg-cyan-500/20",
|
||||||
|
"focus:text-cyan-700",
|
||||||
|
"dark:focus:text-cyan-400",
|
||||||
|
].join(" ")}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</HeroUIDateField.Input>
|
||||||
|
|
||||||
|
{!value && placeholder && (
|
||||||
|
<span className="pointer-events-none absolute ml-10 text-sm text-slate-400 dark:text-slate-500">
|
||||||
|
{placeholder}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<HeroUIDateField.Suffix className="flex items-center">
|
||||||
|
{/* Botón limpiar */}
|
||||||
{value && (
|
{value && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => onChange(undefined)}
|
onClick={handleClear}
|
||||||
className="absolute right-3 p-1 rounded-md text-slate-400 hover:text-slate-700 dark:hover:text-slate-200 hover:bg-slate-100 dark:hover:bg-slate-800 transition"
|
aria-label="Limpiar fecha"
|
||||||
|
className={[
|
||||||
|
"mr-1 rounded-md p-1",
|
||||||
|
"text-slate-400",
|
||||||
|
"hover:bg-slate-100 hover:text-slate-700",
|
||||||
|
"dark:hover:bg-slate-800 dark:hover:text-slate-200",
|
||||||
|
"outline-none transition",
|
||||||
|
].join(" ")}
|
||||||
>
|
>
|
||||||
<FiX className="text-sm" />
|
<FiX className="text-sm" />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
|
|
||||||
{typeof window !== "undefined" && createPortal(dropdown, document.body)}
|
{/* Botón calendario */}
|
||||||
|
<HeroUIDatePicker.Trigger
|
||||||
|
className={[
|
||||||
|
"rounded-r-xl pl-1.5 pr-3",
|
||||||
|
"text-slate-400",
|
||||||
|
"hover:text-slate-700",
|
||||||
|
"dark:hover:text-slate-200",
|
||||||
|
"outline-none",
|
||||||
|
"focus-visible:text-cyan-500",
|
||||||
|
].join(" ")}
|
||||||
|
>
|
||||||
|
<HeroUIDatePicker.TriggerIndicator />
|
||||||
|
</HeroUIDatePicker.Trigger>
|
||||||
|
</HeroUIDateField.Suffix>
|
||||||
|
</HeroUIDateField.Group>
|
||||||
|
|
||||||
|
{/* Error */}
|
||||||
|
{errorMessage && (
|
||||||
|
<FieldError className="text-xs text-rose-500">
|
||||||
|
{errorMessage}
|
||||||
|
</FieldError>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Calendario */}
|
||||||
|
<HeroUIDatePicker.Popover
|
||||||
|
className={[
|
||||||
|
"min-w-[288px]",
|
||||||
|
"overflow-hidden rounded-2xl",
|
||||||
|
"border border-slate-200 dark:border-slate-700",
|
||||||
|
"bg-white dark:bg-slate-900",
|
||||||
|
"shadow-2xl",
|
||||||
|
"outline-none",
|
||||||
|
].join(" ")}
|
||||||
|
>
|
||||||
|
<HeroUICalendar.Root aria-label={label ?? "Seleccionar fecha"}>
|
||||||
|
{/* Header */}
|
||||||
|
<HeroUICalendar.Header
|
||||||
|
className={[
|
||||||
|
"flex items-center justify-between",
|
||||||
|
"border-b border-slate-100 dark:border-slate-800",
|
||||||
|
"px-4 py-3",
|
||||||
|
].join(" ")}
|
||||||
|
>
|
||||||
|
{/* Mes anterior */}
|
||||||
|
<HeroUICalendar.NavButton
|
||||||
|
slot="previous"
|
||||||
|
className={[
|
||||||
|
"rounded-lg p-1.5",
|
||||||
|
"text-slate-500 dark:text-slate-400",
|
||||||
|
"hover:bg-slate-100 dark:hover:bg-slate-800",
|
||||||
|
"transition",
|
||||||
|
"outline-none",
|
||||||
|
"focus-visible:ring-2 focus-visible:ring-cyan-500",
|
||||||
|
].join(" ")}
|
||||||
|
>
|
||||||
|
<FiChevronLeft className="text-lg" />
|
||||||
|
</HeroUICalendar.NavButton>
|
||||||
|
|
||||||
|
{/* Mes / año */}
|
||||||
|
<HeroUICalendar.Heading
|
||||||
|
className={[
|
||||||
|
"text-sm font-semibold",
|
||||||
|
"text-slate-800 dark:text-slate-100",
|
||||||
|
].join(" ")}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Mes siguiente */}
|
||||||
|
<HeroUICalendar.NavButton
|
||||||
|
slot="next"
|
||||||
|
className={[
|
||||||
|
"rounded-lg p-1.5",
|
||||||
|
"text-slate-500 dark:text-slate-400",
|
||||||
|
"hover:bg-slate-100 dark:hover:bg-slate-800",
|
||||||
|
"transition",
|
||||||
|
"outline-none",
|
||||||
|
"focus-visible:ring-2 focus-visible:ring-cyan-500",
|
||||||
|
].join(" ")}
|
||||||
|
>
|
||||||
|
<FiChevronRight className="text-lg" />
|
||||||
|
</HeroUICalendar.NavButton>
|
||||||
|
</HeroUICalendar.Header>
|
||||||
|
|
||||||
|
{/* Grid del calendario */}
|
||||||
|
<div className="p-3">
|
||||||
|
<HeroUICalendar.Grid className="w-full border-collapse">
|
||||||
|
{/* Días de la semana */}
|
||||||
|
<HeroUICalendar.GridHeader>
|
||||||
|
{(day) => (
|
||||||
|
<HeroUICalendar.HeaderCell
|
||||||
|
className={[
|
||||||
|
"pb-2 text-center",
|
||||||
|
"text-[10px] font-semibold",
|
||||||
|
"text-slate-400 dark:text-slate-500",
|
||||||
|
].join(" ")}
|
||||||
|
>
|
||||||
|
{day}
|
||||||
|
</HeroUICalendar.HeaderCell>
|
||||||
|
)}
|
||||||
|
</HeroUICalendar.GridHeader>
|
||||||
|
|
||||||
|
{/* Días */}
|
||||||
|
<HeroUICalendar.GridBody>
|
||||||
|
{(date) => (
|
||||||
|
<HeroUICalendar.Cell
|
||||||
|
date={date}
|
||||||
|
className="p-0.5 outline-none"
|
||||||
|
>
|
||||||
|
{({
|
||||||
|
formattedDate,
|
||||||
|
isSelected,
|
||||||
|
isDisabled,
|
||||||
|
isToday,
|
||||||
|
}) => (
|
||||||
|
<HeroUICalendar.CellIndicator
|
||||||
|
className={[
|
||||||
|
"flex aspect-square w-full min-w-8",
|
||||||
|
"items-center justify-center",
|
||||||
|
"rounded-lg text-sm transition-all",
|
||||||
|
"outline-none",
|
||||||
|
"focus-visible:ring-2",
|
||||||
|
"focus-visible:ring-cyan-500",
|
||||||
|
"focus-visible:ring-offset-1",
|
||||||
|
"focus-visible:ring-offset-white",
|
||||||
|
"dark:focus-visible:ring-offset-slate-900",
|
||||||
|
|
||||||
|
isSelected
|
||||||
|
? "bg-violet-600 font-semibold text-white shadow-sm"
|
||||||
|
: isToday
|
||||||
|
? [
|
||||||
|
"border border-violet-400",
|
||||||
|
"font-semibold text-violet-600",
|
||||||
|
"hover:bg-slate-100",
|
||||||
|
"dark:text-violet-400",
|
||||||
|
"dark:hover:bg-slate-800",
|
||||||
|
].join(" ")
|
||||||
|
: [
|
||||||
|
"text-slate-700",
|
||||||
|
"hover:bg-slate-100",
|
||||||
|
"dark:text-slate-300",
|
||||||
|
"dark:hover:bg-slate-800",
|
||||||
|
].join(" "),
|
||||||
|
|
||||||
|
isDisabled
|
||||||
|
? "cursor-not-allowed opacity-30 hover:bg-transparent"
|
||||||
|
: "",
|
||||||
|
].join(" ")}
|
||||||
|
>
|
||||||
|
{formattedDate}
|
||||||
|
</HeroUICalendar.CellIndicator>
|
||||||
|
)}
|
||||||
|
</HeroUICalendar.Cell>
|
||||||
|
)}
|
||||||
|
</HeroUICalendar.GridBody>
|
||||||
|
</HeroUICalendar.Grid>
|
||||||
</div>
|
</div>
|
||||||
|
</HeroUICalendar.Root>
|
||||||
|
</HeroUIDatePicker.Popover>
|
||||||
|
</HeroUIDatePicker.Root>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
DatePicker.displayName = "DatePicker";
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import React, { useState, useRef, useEffect } from "react";
|
import React, { useState, useRef, useEffect } from "react";
|
||||||
import { FiCheck, FiChevronDown, FiSearch, FiX } from "react-icons/fi";
|
import { FiCheck, FiChevronDown, FiSearch, FiX } from "react-icons/fi";
|
||||||
|
import { Popover as HeroUIPopover } from "@heroui/react";
|
||||||
import { useTheme } from "../../context/ThemeContext";
|
import { useTheme } from "../../context/ThemeContext";
|
||||||
|
|
||||||
export interface DropdownOption {
|
export interface DropdownOption {
|
||||||
@@ -27,7 +28,6 @@ export const Dropdown: React.FC<DropdownProps> = ({
|
|||||||
const { isDark } = useTheme();
|
const { isDark } = useTheme();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
|
||||||
const searchRef = useRef<HTMLInputElement>(null);
|
const searchRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
const filtered = options.filter((o) =>
|
const filtered = options.filter((o) =>
|
||||||
@@ -35,24 +35,18 @@ export const Dropdown: React.FC<DropdownProps> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handler = (e: MouseEvent) => {
|
if (!open) {
|
||||||
if (ref.current && !ref.current.contains(e.target as Node)) {
|
|
||||||
setOpen(false);
|
|
||||||
setSearch("");
|
setSearch("");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
};
|
const timer = setTimeout(() => searchRef.current?.focus(), 50);
|
||||||
document.addEventListener("mousedown", handler);
|
return () => clearTimeout(timer);
|
||||||
return () => document.removeEventListener("mousedown", handler);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (open) setTimeout(() => searchRef.current?.focus(), 50);
|
|
||||||
}, [open]);
|
}, [open]);
|
||||||
|
|
||||||
const toggle = (val: string) =>
|
const toggle = (val: string) =>
|
||||||
onChange(value.includes(val) ? value.filter((v) => v !== val) : [...value, val]);
|
onChange(value.includes(val) ? value.filter((v) => v !== val) : [...value, val]);
|
||||||
|
|
||||||
const removeTag = (val: string, e: React.MouseEvent) => {
|
const removeTag = (val: string, e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onChange(value.filter((v) => v !== val));
|
onChange(value.filter((v) => v !== val));
|
||||||
};
|
};
|
||||||
@@ -71,13 +65,13 @@ export const Dropdown: React.FC<DropdownProps> = ({
|
|||||||
const divider = isDark ? "bg-slate-700" : "bg-slate-100";
|
const divider = isDark ? "bg-slate-700" : "bg-slate-100";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={ref} className={`relative ${className}`}>
|
<div className={`relative ${className}`}>
|
||||||
|
<HeroUIPopover.Root isOpen={open} onOpenChange={setOpen}>
|
||||||
{/* Trigger */}
|
<HeroUIPopover.Trigger className="block w-full">
|
||||||
<div
|
<button
|
||||||
onClick={() => setOpen((p) => !p)}
|
type="button"
|
||||||
className={`flex items-center gap-2 min-h-[46px] w-full px-4 py-2 cursor-pointer border transition-colors duration-150 ${bg} ${borderColor} ${hoverBorder} ${
|
className={`flex items-center gap-2 min-h-11.5 w-full px-4 py-2 cursor-pointer border transition-colors duration-150 outline-none ${bg} ${borderColor} ${hoverBorder} ${
|
||||||
open ? "rounded-t-xl border-b-0" : "rounded-xl"
|
open ? "rounded-t-xl rounded-b-none! border-b-0!" : "rounded-xl"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex-1 flex items-center gap-1.5 min-w-0 overflow-hidden">
|
<div className="flex-1 flex items-center gap-1.5 min-w-0 overflow-hidden">
|
||||||
@@ -88,35 +82,54 @@ export const Dropdown: React.FC<DropdownProps> = ({
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{value.slice(0, 1).map((v) => (
|
{value.slice(0, 1).map((v) => (
|
||||||
<span key={v} className={`inline-flex items-center gap-1 px-2 py-0.5 rounded-lg text-xs font-semibold shrink-0 ${
|
<span
|
||||||
|
key={v}
|
||||||
|
className={`inline-flex items-center gap-1 px-2 py-0.5 rounded-lg text-xs font-semibold shrink-0 ${
|
||||||
isDark ? "bg-violet-600/25 text-violet-300" : "bg-violet-100 text-violet-700"
|
isDark ? "bg-violet-600/25 text-violet-300" : "bg-violet-100 text-violet-700"
|
||||||
}`}>
|
}`}
|
||||||
|
>
|
||||||
{labelOf(v)}
|
{labelOf(v)}
|
||||||
<button type="button" onClick={(e) => removeTag(v, e)} className="hover:opacity-70">
|
<div
|
||||||
|
onClick={(e) => removeTag(v, e as any)}
|
||||||
|
className="hover:opacity-70 ml-1 cursor-pointer"
|
||||||
|
>
|
||||||
<FiX className="text-[10px]" />
|
<FiX className="text-[10px]" />
|
||||||
</button>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
{value.length > 1 && (
|
{value.length > 1 && (
|
||||||
<span className={`text-xs font-semibold px-2 py-0.5 rounded-lg shrink-0 ${
|
<span
|
||||||
|
className={`text-xs font-semibold px-2 py-0.5 rounded-lg shrink-0 ${
|
||||||
isDark ? "bg-slate-700 text-slate-400" : "bg-slate-100 text-slate-500"
|
isDark ? "bg-slate-700 text-slate-400" : "bg-slate-100 text-slate-500"
|
||||||
}`}>
|
}`}
|
||||||
+{value.length - 2} más
|
>
|
||||||
|
+{value.length - 1} más
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<FiChevronDown className={`shrink-0 text-sm transition-transform duration-200 ${open ? "rotate-180" : ""} ${isDark ? "text-slate-500" : "text-slate-400"}`} />
|
<FiChevronDown
|
||||||
</div>
|
className={`shrink-0 text-sm transition-transform duration-200 ${
|
||||||
|
open ? "rotate-180" : ""
|
||||||
|
} ${isDark ? "text-slate-500" : "text-slate-400"}`}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</HeroUIPopover.Trigger>
|
||||||
|
|
||||||
{/* Menu — absolute, sin border-top para no doblar la línea */}
|
<HeroUIPopover.Content
|
||||||
{open && (
|
offset={0}
|
||||||
<div className={`absolute left-0 top-full z-50 w-full rounded-b-xl border border-t-0 shadow-lg overflow-hidden ${bg} ${borderColor}`}>
|
placement="bottom start"
|
||||||
|
className={`w-(--trigger-width) rounded-b-xl border shadow-lg overflow-hidden p-0 m-0 ${bg} ${borderColor} rounded-t-none! border-t-0!`}
|
||||||
{/* Search */}
|
>
|
||||||
|
<HeroUIPopover.Dialog className="w-full flex flex-col p-0 outline-none">
|
||||||
<div className={`h-px ${divider}`} />
|
<div className={`h-px ${divider}`} />
|
||||||
<div className={`flex items-center gap-2 px-3 py-2 mx-3 my-2 rounded-lg border ${isDark ? "border-slate-700 bg-slate-900/50" : "border-slate-200 bg-slate-50"}`}>
|
|
||||||
|
<div
|
||||||
|
className={`flex items-center gap-2 px-3 py-2 mx-3 my-2 rounded-lg border ${
|
||||||
|
isDark ? "border-slate-700 bg-slate-900/50" : "border-slate-200 bg-slate-50"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
<FiSearch className={`shrink-0 text-sm ${isDark ? "text-slate-500" : "text-slate-400"}`} />
|
<FiSearch className={`shrink-0 text-sm ${isDark ? "text-slate-500" : "text-slate-400"}`} />
|
||||||
<input
|
<input
|
||||||
ref={searchRef}
|
ref={searchRef}
|
||||||
@@ -129,7 +142,11 @@ export const Dropdown: React.FC<DropdownProps> = ({
|
|||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
{search && (
|
{search && (
|
||||||
<button onClick={() => setSearch("")} className={isDark ? "text-slate-500 hover:text-slate-300" : "text-slate-400 hover:text-slate-600"}>
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setSearch("")}
|
||||||
|
className={isDark ? "text-slate-500 hover:text-slate-300" : "text-slate-400 hover:text-slate-600"}
|
||||||
|
>
|
||||||
<FiX className="text-xs" />
|
<FiX className="text-xs" />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
@@ -137,8 +154,7 @@ export const Dropdown: React.FC<DropdownProps> = ({
|
|||||||
|
|
||||||
<div className={`h-px ${divider}`} />
|
<div className={`h-px ${divider}`} />
|
||||||
|
|
||||||
{/* Options */}
|
<div className="max-h-52 overflow-y-auto py-1 w-full">
|
||||||
<div className="max-h-52 overflow-y-auto py-1">
|
|
||||||
{filtered.length === 0 ? (
|
{filtered.length === 0 ? (
|
||||||
<p className={`px-4 py-3 text-xs text-center ${isDark ? "text-slate-600" : "text-slate-400"}`}>
|
<p className={`px-4 py-3 text-xs text-center ${isDark ? "text-slate-600" : "text-slate-400"}`}>
|
||||||
Sin resultados
|
Sin resultados
|
||||||
@@ -151,24 +167,40 @@ export const Dropdown: React.FC<DropdownProps> = ({
|
|||||||
key={opt.value}
|
key={opt.value}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => toggle(opt.value)}
|
onClick={() => toggle(opt.value)}
|
||||||
className={`w-full flex items-center gap-3 px-4 py-3 text-sm transition-colors text-left ${
|
className={`w-full flex items-center gap-3 px-4 py-3 text-sm transition-colors text-left outline-none ${
|
||||||
isActive
|
isActive
|
||||||
? isDark ? "bg-violet-600/15 text-violet-300" : "bg-violet-50 text-violet-700"
|
? isDark
|
||||||
: isDark ? "text-slate-300 hover:bg-slate-700/50" : "text-slate-700 hover:bg-slate-50"
|
? "bg-violet-600/15 text-violet-300"
|
||||||
|
: "bg-violet-50 text-violet-700"
|
||||||
|
: isDark
|
||||||
|
? "text-slate-300 hover:bg-slate-700/50"
|
||||||
|
: "text-slate-700 hover:bg-slate-50"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={`w-4 h-4 rounded flex items-center justify-center border shrink-0 transition-colors ${
|
||||||
|
isActive
|
||||||
|
? "bg-violet-600 border-violet-600"
|
||||||
|
: isDark
|
||||||
|
? "border-slate-600"
|
||||||
|
: "border-slate-300"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<span className={`w-4 h-4 rounded flex items-center justify-center border shrink-0 transition-colors ${
|
|
||||||
isActive ? "bg-violet-600 border-violet-600" : isDark ? "border-slate-600" : "border-slate-300"
|
|
||||||
}`}>
|
|
||||||
{isActive && <FiCheck className="text-white text-[10px]" />}
|
{isActive && <FiCheck className="text-white text-[10px]" />}
|
||||||
</span>
|
</span>
|
||||||
<span className="flex-1 font-medium">{opt.label}</span>
|
<span className="flex-1 font-medium">{opt.label}</span>
|
||||||
{opt.count !== undefined && (
|
{opt.count !== undefined && (
|
||||||
<span className={`text-[11px] px-1.5 py-0.5 rounded font-mono shrink-0 ${
|
<span
|
||||||
|
className={`text-[11px] px-1.5 py-0.5 rounded font-mono shrink-0 ${
|
||||||
isActive
|
isActive
|
||||||
? isDark ? "bg-violet-500/30 text-violet-300" : "bg-violet-100 text-violet-600"
|
? isDark
|
||||||
: isDark ? "bg-slate-700 text-slate-500" : "bg-slate-100 text-slate-400"
|
? "bg-violet-500/30 text-violet-300"
|
||||||
}`}>
|
: "bg-violet-100 text-violet-600"
|
||||||
|
: isDark
|
||||||
|
? "bg-slate-700 text-slate-500"
|
||||||
|
: "bg-slate-100 text-slate-400"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
{opt.count}
|
{opt.count}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -180,19 +212,23 @@ export const Dropdown: React.FC<DropdownProps> = ({
|
|||||||
|
|
||||||
{value.length > 0 && (
|
{value.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<div className={`h-px ${divider}`} />
|
<div className={`h-px w-full ${divider}`} />
|
||||||
<div className="px-4 py-2.5 flex justify-end">
|
<div className="px-4 py-2.5 flex justify-end w-full">
|
||||||
<button
|
<button
|
||||||
|
type="button"
|
||||||
onClick={() => onChange([])}
|
onClick={() => onChange([])}
|
||||||
className={`text-xs font-medium transition-colors ${isDark ? "text-slate-500 hover:text-slate-300" : "text-slate-400 hover:text-slate-600"}`}
|
className={`text-xs font-medium transition-colors outline-none ${
|
||||||
|
isDark ? "text-slate-500 hover:text-slate-300" : "text-slate-400 hover:text-slate-600"
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
Limpiar selección
|
Limpiar selección
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</HeroUIPopover.Dialog>
|
||||||
)}
|
</HeroUIPopover.Content>
|
||||||
|
</HeroUIPopover.Root>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useRef, useState } from "react";
|
import { useRef, useState } from "react";
|
||||||
import { FiUpload, FiX } from "react-icons/fi";
|
import { FiUpload, FiX } from "react-icons/fi";
|
||||||
|
import { Button, CloseButton, Label } from "@heroui/react";
|
||||||
import { useTheme } from "@/context/ThemeContext";
|
import { useTheme } from "@/context/ThemeContext";
|
||||||
|
|
||||||
export interface ImageUploadProps {
|
export interface ImageUploadProps {
|
||||||
@@ -11,8 +13,15 @@ export interface ImageUploadProps {
|
|||||||
hint?: string;
|
hint?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ImageUpload({ label, value, onChange, aspectRatio = "square", hint }: ImageUploadProps) {
|
export function ImageUpload({
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
aspectRatio = "square",
|
||||||
|
hint,
|
||||||
|
}: ImageUploadProps) {
|
||||||
const { isDark } = useTheme();
|
const { isDark } = useTheme();
|
||||||
|
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
const [dragging, setDragging] = useState(false);
|
const [dragging, setDragging] = useState(false);
|
||||||
|
|
||||||
@@ -20,34 +29,118 @@ export function ImageUpload({ label, value, onChange, aspectRatio = "square", hi
|
|||||||
|
|
||||||
const handleFile = (file: File) => {
|
const handleFile = (file: File) => {
|
||||||
if (!file.type.startsWith("image/")) return;
|
if (!file.type.startsWith("image/")) return;
|
||||||
|
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = (e) => onChange(e.target?.result as string);
|
|
||||||
|
reader.onload = (event) => {
|
||||||
|
const result = event.target?.result;
|
||||||
|
|
||||||
|
if (typeof result === "string") {
|
||||||
|
onChange(result);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDrop = (e: React.DragEvent) => {
|
const handleDrop = (event: React.DragEvent<HTMLDivElement>) => {
|
||||||
e.preventDefault();
|
event.preventDefault();
|
||||||
setDragging(false);
|
setDragging(false);
|
||||||
const file = e.dataTransfer.files[0];
|
|
||||||
if (file) handleFile(file);
|
const file = event.dataTransfer.files[0];
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
handleFile(file);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const border = isDark ? "border-slate-700" : "border-slate-200";
|
const borderColor = isDark
|
||||||
const bg = isDark ? "bg-slate-800/50" : "bg-slate-50";
|
? "border-slate-700"
|
||||||
const active = dragging ? (isDark ? "border-violet-500 bg-violet-500/10" : "border-violet-400 bg-violet-50") : "";
|
: "border-slate-200";
|
||||||
const muted = isDark ? "text-slate-400" : "text-slate-500";
|
|
||||||
|
const backgroundColor = isDark
|
||||||
|
? "bg-slate-800/50"
|
||||||
|
: "bg-slate-50";
|
||||||
|
|
||||||
|
const dragActiveStyles = dragging
|
||||||
|
? isDark
|
||||||
|
? "border-violet-500 bg-violet-500/10"
|
||||||
|
: "border-violet-400 bg-violet-50"
|
||||||
|
: "";
|
||||||
|
|
||||||
|
const mutedColor = isDark
|
||||||
|
? "text-slate-400"
|
||||||
|
: "text-slate-500";
|
||||||
|
|
||||||
|
const labelColor = isDark
|
||||||
|
? "text-slate-300"
|
||||||
|
: "text-slate-700";
|
||||||
|
|
||||||
|
const pickerSize = isBanner
|
||||||
|
? "h-32 w-full"
|
||||||
|
: "h-28 w-28";
|
||||||
|
|
||||||
|
const openFilePicker = () => {
|
||||||
|
inputRef.current?.click();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputChange = (
|
||||||
|
event: React.ChangeEvent<HTMLInputElement>
|
||||||
|
) => {
|
||||||
|
const file = event.target.files?.[0];
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
handleFile(file);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemove = () => {
|
||||||
|
onChange(undefined);
|
||||||
|
|
||||||
|
// Permite volver a seleccionar la misma imagen
|
||||||
|
// después de eliminarla.
|
||||||
|
if (inputRef.current) {
|
||||||
|
inputRef.current.value = "";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<label className={`block text-sm font-medium mb-1.5 ${isDark ? "text-slate-300" : "text-slate-700"}`}>
|
<Label
|
||||||
|
className={`mb-1.5 block text-sm font-medium ${labelColor}`}
|
||||||
|
>
|
||||||
{label}
|
{label}
|
||||||
</label>
|
</Label>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={`relative rounded-xl border-2 border-dashed transition-all duration-200 overflow-hidden cursor-pointer ${border} ${bg} ${active} ${isBanner ? "h-32" : "h-28 w-28"}`}
|
role="button"
|
||||||
onClick={() => inputRef.current?.click()}
|
tabIndex={0}
|
||||||
onDragOver={(e) => { e.preventDefault(); setDragging(true); }}
|
aria-label={`Seleccionar imagen para ${label}`}
|
||||||
onDragLeave={() => setDragging(false)}
|
className={[
|
||||||
|
"relative cursor-pointer overflow-hidden rounded-xl",
|
||||||
|
"border-2 border-dashed",
|
||||||
|
"transition-all duration-200",
|
||||||
|
"focus-visible:outline-none focus-visible:ring-2",
|
||||||
|
"focus-visible:ring-violet-500",
|
||||||
|
borderColor,
|
||||||
|
backgroundColor,
|
||||||
|
dragActiveStyles,
|
||||||
|
pickerSize,
|
||||||
|
].join(" ")}
|
||||||
|
onClick={openFilePicker}
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
if (event.key === "Enter" || event.key === " ") {
|
||||||
|
event.preventDefault();
|
||||||
|
openFilePicker();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onDragOver={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
setDragging(true);
|
||||||
|
}}
|
||||||
|
onDragLeave={() => {
|
||||||
|
setDragging(false);
|
||||||
|
}}
|
||||||
onDrop={handleDrop}
|
onDrop={handleDrop}
|
||||||
>
|
>
|
||||||
{value ? (
|
{value ? (
|
||||||
@@ -55,34 +148,55 @@ export function ImageUpload({ label, value, onChange, aspectRatio = "square", hi
|
|||||||
<img
|
<img
|
||||||
src={value}
|
src={value}
|
||||||
alt={label}
|
alt={label}
|
||||||
className="w-full h-full object-cover"
|
className="h-full w-full object-cover"
|
||||||
/>
|
/>
|
||||||
<button
|
|
||||||
type="button"
|
<div
|
||||||
onClick={(e) => { e.stopPropagation(); onChange(undefined); }}
|
className="absolute right-2 top-2"
|
||||||
className="absolute top-2 right-2 p-1 rounded-full bg-black/60 text-white hover:bg-black/80 transition"
|
onClick={(event) => event.stopPropagation()}
|
||||||
|
>
|
||||||
|
<CloseButton
|
||||||
|
aria-label="Eliminar imagen"
|
||||||
|
onPress={handleRemove}
|
||||||
|
className="rounded-full bg-black/60 text-white hover:bg-black/80"
|
||||||
>
|
>
|
||||||
<FiX className="text-xs" />
|
<FiX className="text-xs" />
|
||||||
</button>
|
</CloseButton>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col items-center justify-center h-full gap-1.5 px-4 text-center">
|
<div className="flex h-full flex-col items-center justify-center gap-1.5 px-4 text-center">
|
||||||
<FiUpload className={`text-xl ${muted}`} />
|
<FiUpload className={`text-xl ${mutedColor}`} />
|
||||||
<p className={`text-xs ${muted}`}>
|
|
||||||
|
<p className={`text-xs ${mutedColor}`}>
|
||||||
{dragging ? "Suelta aquí" : "Click o arrastra"}
|
{dragging ? "Suelta aquí" : "Click o arrastra"}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onPress={openFilePicker}
|
||||||
|
className="mt-1"
|
||||||
|
>
|
||||||
|
Seleccionar
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{hint && <p className={`text-[11px] mt-1 ${muted}`}>{hint}</p>}
|
{hint && (
|
||||||
|
<p className={`mt-1 text-[11px] ${mutedColor}`}>
|
||||||
|
{hint}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
<input
|
<input
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
type="file"
|
type="file"
|
||||||
accept="image/*"
|
accept="image/*"
|
||||||
className="hidden"
|
className="hidden"
|
||||||
onChange={(e) => { const f = e.target.files?.[0]; if (f) handleFile(f); }}
|
onChange={handleInputChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
+79
-85
@@ -1,8 +1,9 @@
|
|||||||
"use client";
|
import React, { useState } from "react";
|
||||||
import React, { useState } from 'react';
|
|
||||||
import { FiEye, FiEyeOff, FiX, FiAlertCircle } from 'react-icons/fi';
|
|
||||||
|
|
||||||
export interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'> {
|
import { Input as HeroInput, TextField, Label, Description, FieldError, InputGroup } from "@heroui/react";
|
||||||
|
import { FiEye, FiEyeOff, FiX, FiAlertCircle } from "react-icons/fi";
|
||||||
|
|
||||||
|
export interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "size"> {
|
||||||
label?: string;
|
label?: string;
|
||||||
helperText?: string;
|
helperText?: string;
|
||||||
errorMessage?: string;
|
errorMessage?: string;
|
||||||
@@ -10,28 +11,28 @@ export interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElem
|
|||||||
rightIcon?: React.ReactNode;
|
rightIcon?: React.ReactNode;
|
||||||
isClearable?: boolean;
|
isClearable?: boolean;
|
||||||
onClear?: () => void;
|
onClear?: () => void;
|
||||||
sizeVariant?: 'sm' | 'md' | 'lg';
|
sizeVariant?: "sm" | "md" | "lg";
|
||||||
fullWidth?: boolean;
|
fullWidth?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sizeStyles = {
|
const sizeStyles = {
|
||||||
sm: {
|
sm: {
|
||||||
input: 'py-2 text-xs rounded-lg',
|
input: "py-2 text-xs rounded-lg",
|
||||||
icon: 'text-sm',
|
icon: "text-sm",
|
||||||
leftPad: 'pl-8',
|
leftPad: "pl-8",
|
||||||
rightPad: 'pr-8',
|
rightPad: "pr-8",
|
||||||
},
|
},
|
||||||
md: {
|
md: {
|
||||||
input: 'py-2.5 text-sm rounded-xl',
|
input: "py-2.5 text-sm rounded-xl",
|
||||||
icon: 'text-base',
|
icon: "text-base",
|
||||||
leftPad: 'pl-10',
|
leftPad: "pl-10",
|
||||||
rightPad: 'pr-10',
|
rightPad: "pr-10",
|
||||||
},
|
},
|
||||||
lg: {
|
lg: {
|
||||||
input: 'py-3.5 text-base rounded-2xl',
|
input: "py-3.5 text-base rounded-2xl",
|
||||||
icon: 'text-lg',
|
icon: "text-lg",
|
||||||
leftPad: 'pl-12',
|
leftPad: "pl-12",
|
||||||
rightPad: 'pr-12',
|
rightPad: "pr-12",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -45,65 +46,72 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|||||||
rightIcon,
|
rightIcon,
|
||||||
isClearable = false,
|
isClearable = false,
|
||||||
onClear,
|
onClear,
|
||||||
sizeVariant = 'md',
|
sizeVariant = "md",
|
||||||
fullWidth = true,
|
fullWidth = true,
|
||||||
type = 'text',
|
type = "text",
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
disabled,
|
disabled,
|
||||||
required,
|
required,
|
||||||
className = '',
|
className = "",
|
||||||
value,
|
value,
|
||||||
...props
|
...props
|
||||||
},
|
},
|
||||||
ref
|
ref,
|
||||||
) => {
|
) => {
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
const inputId = id || name || (label ? label.toLowerCase().replace(/\s+/g, '-') : undefined);
|
const inputId = id || name || (label ? label.toLowerCase().replace(/\s+/g, "-") : undefined);
|
||||||
|
const isPasswordType = type === "password";
|
||||||
const isPasswordType = type === 'password';
|
const computedType = isPasswordType ? (showPassword ? "text" : "password") : type;
|
||||||
const computedType = isPasswordType ? (showPassword ? 'text' : 'password') : type;
|
|
||||||
const hasError = Boolean(errorMessage);
|
const hasError = Boolean(errorMessage);
|
||||||
const hasValue = value !== undefined && value !== '' && value !== null;
|
const hasValue = value !== undefined && value !== "" && value !== null;
|
||||||
|
|
||||||
const currentSize = sizeStyles[sizeVariant];
|
const currentSize = sizeStyles[sizeVariant];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${fullWidth ? 'w-full' : 'inline-block'} space-y-1.5`}>
|
<TextField
|
||||||
|
id={`${inputId}-field`}
|
||||||
|
isInvalid={hasError}
|
||||||
|
isDisabled={disabled}
|
||||||
|
isRequired={required}
|
||||||
|
className={`${fullWidth ? "w-full" : "inline-block"} space-y-1.5`}>
|
||||||
|
|
||||||
{/* Label */}
|
{/* Label */}
|
||||||
{label && (
|
{label && (
|
||||||
<div className="flex items-center justify-between">
|
<Label
|
||||||
<label
|
|
||||||
htmlFor={inputId}
|
htmlFor={inputId}
|
||||||
className={`block text-xs font-semibold select-none transition-colors ${
|
className={`block text-xs font-semibold select-none transition-colors ${
|
||||||
hasError
|
hasError ? "text-rose-600 dark:text-rose-400" : "text-slate-700 dark:text-slate-300"
|
||||||
? 'text-rose-600 dark:text-rose-400'
|
}`}>
|
||||||
: 'text-slate-700 dark:text-slate-300'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{label}
|
{label}
|
||||||
{required && <span className="ml-1 text-rose-500 font-bold">*</span>}
|
{required}
|
||||||
</label>
|
</Label>
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Input Wrapper */}
|
{/* Input Wrapper */}
|
||||||
<div className="relative flex items-center">
|
<div className="relative flex items-center w-full">
|
||||||
|
<InputGroup
|
||||||
|
className={`
|
||||||
|
flex items-center w-full overflow-hidden transition-all duration-200 outline-none
|
||||||
|
bg-white border border-slate-300 hover:border-slate-400
|
||||||
|
focus-within:border-cyan-500 focus-within:ring-4 focus-within:ring-cyan-500/15
|
||||||
|
dark:bg-slate-900/90 dark:border-slate-800 dark:hover:border-slate-700
|
||||||
|
dark:focus-within:border-cyan-500 dark:focus-within:ring-cyan-500/25
|
||||||
|
${disabled ? "opacity-60 bg-slate-100 dark:bg-slate-950 cursor-not-allowed" : ""}
|
||||||
|
${hasError ? "border-rose-500! ring-rose-500/20! focus-within:border-rose-500! focus-within:ring-rose-500/20!" : ""}
|
||||||
|
${currentSize.input} `}>
|
||||||
|
|
||||||
{/* Left Icon */}
|
{/* Left Icon */}
|
||||||
{leftIcon && (
|
{leftIcon && (
|
||||||
<div
|
<InputGroup.Prefix
|
||||||
className={`absolute left-3.5 pointer-events-none flex items-center justify-center transition-colors ${
|
className={`flex items-center justify-center pl-3.5 pr-2 shrink-0 pointer-events-none transition-colors ${
|
||||||
hasError
|
hasError ? "text-rose-500" : "text-slate-400 dark:text-slate-500"
|
||||||
? 'text-rose-500'
|
} ${currentSize.icon}`}>
|
||||||
: 'text-slate-400 dark:text-slate-500'
|
|
||||||
} ${currentSize.icon}`}
|
|
||||||
>
|
|
||||||
{leftIcon}
|
{leftIcon}
|
||||||
</div>
|
</InputGroup.Prefix>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Core Input Element */}
|
{/* Core Input Element */}
|
||||||
<input
|
<HeroInput
|
||||||
ref={ref}
|
ref={ref}
|
||||||
id={inputId}
|
id={inputId}
|
||||||
name={name}
|
name={name}
|
||||||
@@ -112,78 +120,64 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
required={required}
|
required={required}
|
||||||
className={`
|
className={`
|
||||||
w-full px-3.5 font-normal transition-all duration-200 outline-none
|
flex-1 w-full bg-transparent border-none outline-none focus:ring-0 p-0 m-0 shadow-none text-slate-900 placeholder:text-slate-400
|
||||||
bg-white text-slate-900 placeholder:text-slate-400
|
dark:text-slate-100 dark:placeholder:text-slate-500
|
||||||
border border-slate-300
|
${hasError ? "text-rose-900 dark:text-rose-200" : ""}
|
||||||
hover:border-slate-400
|
${!leftIcon ? "pl-3.5" : ""}
|
||||||
focus:border-cyan-500 focus:ring-4 focus:ring-cyan-500/15
|
${!rightIcon && !isClearable && !isPasswordType ? "pr-3.5" : ""}
|
||||||
dark:bg-slate-900/90 dark:text-slate-100 dark:placeholder:text-slate-500
|
|
||||||
dark:border-slate-800 dark:hover:border-slate-700
|
|
||||||
dark:focus:border-cyan-500 dark:focus:ring-cyan-500/25
|
|
||||||
disabled:opacity-60 disabled:bg-slate-100 disabled:dark:bg-slate-950 disabled:cursor-not-allowed
|
|
||||||
${leftIcon ? currentSize.leftPad : ''}
|
|
||||||
${isPasswordType || isClearable || rightIcon ? currentSize.rightPad : ''}
|
|
||||||
${
|
|
||||||
hasError
|
|
||||||
? '!border-rose-500 !ring-rose-500/20 text-rose-900 dark:text-rose-200 focus:!border-rose-500 focus:!ring-rose-500/20'
|
|
||||||
: ''
|
|
||||||
}
|
|
||||||
${currentSize.input}
|
|
||||||
${className}
|
${className}
|
||||||
`}
|
`}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Right Action Icons (Clear, Password Toggle, Custom RightIcon) */}
|
{/* Right Action Icons */}
|
||||||
<div className="absolute right-3 flex items-center gap-1.5 text-slate-400 dark:text-slate-500">
|
<InputGroup.Suffix className="flex items-center gap-1.5 pr-3 pl-1 shrink-0 text-slate-400 dark:text-slate-500">
|
||||||
|
|
||||||
|
{/* Clear */}
|
||||||
{isClearable && hasValue && !disabled && (
|
{isClearable && hasValue && !disabled && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
onClick={onClear}
|
onClick={onClear}
|
||||||
aria-label="Limpiar campo"
|
className="p-1 rounded-md hover:bg-slate-100 hover:text-slate-700 dark:hover:bg-slate-800 dark:hover:text-slate-200 transition-colors">
|
||||||
className="p-1 rounded-md hover:bg-slate-100 hover:text-slate-700 dark:hover:bg-slate-800 dark:hover:text-slate-200 transition-colors"
|
|
||||||
>
|
|
||||||
<FiX className="text-sm" />
|
<FiX className="text-sm" />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Password Toggle */}
|
||||||
{isPasswordType && !disabled && (
|
{isPasswordType && !disabled && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
onClick={() => setShowPassword((prev) => !prev)}
|
onClick={() => setShowPassword((prev) => !prev)}
|
||||||
aria-label={showPassword ? 'Ocultar contraseña' : 'Ver contraseña'}
|
className="p-1 rounded-md hover:bg-slate-100 hover:text-slate-700 dark:hover:bg-slate-800 dark:hover:text-slate-200 transition-colors">
|
||||||
className="p-1 rounded-md hover:bg-slate-100 hover:text-slate-700 dark:hover:bg-slate-800 dark:hover:text-slate-200 transition-colors"
|
{showPassword ? <FiEyeOff className={currentSize.icon} /> : <FiEye className={currentSize.icon} />}
|
||||||
>
|
|
||||||
{showPassword ? (
|
|
||||||
<FiEyeOff className={currentSize.icon} />
|
|
||||||
) : (
|
|
||||||
<FiEye className={currentSize.icon} />
|
|
||||||
)}
|
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Custom Right Icon */}
|
||||||
{!isPasswordType && rightIcon && (
|
{!isPasswordType && rightIcon && (
|
||||||
<div className={`pointer-events-none flex items-center justify-center ${currentSize.icon}`}>
|
<div className={`pointer-events-none flex items-center justify-center ${currentSize.icon}`}>
|
||||||
{rightIcon}
|
{rightIcon}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</InputGroup.Suffix>
|
||||||
|
</InputGroup>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Error or Helper Message */}
|
{/* Error or Helper Message */}
|
||||||
{hasError ? (
|
{hasError ? (
|
||||||
<p className="flex items-center gap-1.5 text-xs text-rose-600 dark:text-rose-400 animate-fadeIn">
|
<FieldError className="flex items-center gap-1.5 text-xs text-rose-600 dark:text-rose-400 animate-fadeIn">
|
||||||
<FiAlertCircle className="shrink-0 text-xs" />
|
<FiAlertCircle className="shrink-0 text-xs" />
|
||||||
|
|
||||||
<span>{errorMessage}</span>
|
<span>{errorMessage}</span>
|
||||||
</p>
|
</FieldError>
|
||||||
) : helperText ? (
|
) : helperText ? (
|
||||||
<p className="text-xs text-slate-500 dark:text-slate-400">{helperText}</p>
|
<Description className="text-xs text-slate-500 dark:text-slate-400">{helperText}</Description>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</TextField>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
Input.displayName = 'Input';
|
Input.displayName = "Input";
|
||||||
+44
-69
@@ -1,13 +1,15 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import React, { useEffect } from 'react';
|
|
||||||
import { FiX } from 'react-icons/fi';
|
import React from "react";
|
||||||
|
import { Modal as HeroModal } from "@heroui/react";
|
||||||
|
import { FiX } from "react-icons/fi";
|
||||||
|
|
||||||
export interface ModalProps {
|
export interface ModalProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
title?: React.ReactNode;
|
title?: React.ReactNode;
|
||||||
description?: React.ReactNode;
|
description?: React.ReactNode;
|
||||||
size?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'full';
|
size?: "sm" | "md" | "lg" | "xl" | "2xl" | "full";
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
footer?: React.ReactNode;
|
footer?: React.ReactNode;
|
||||||
closeOnOverlayClick?: boolean;
|
closeOnOverlayClick?: boolean;
|
||||||
@@ -16,12 +18,12 @@ export interface ModalProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sizeStyles = {
|
const sizeStyles = {
|
||||||
sm: 'max-w-md',
|
sm: "max-w-md",
|
||||||
md: 'max-w-lg',
|
md: "max-w-lg",
|
||||||
lg: 'max-w-2xl',
|
lg: "max-w-2xl",
|
||||||
xl: 'max-w-4xl',
|
xl: "max-w-4xl",
|
||||||
'2xl': 'max-w-6xl',
|
"2xl": "max-w-6xl",
|
||||||
full: 'max-w-[95vw] h-[90vh]',
|
full: "max-w-[95vw] h-[90vh]",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Modal: React.FC<ModalProps> = ({
|
export const Modal: React.FC<ModalProps> = ({
|
||||||
@@ -29,101 +31,74 @@ export const Modal: React.FC<ModalProps> = ({
|
|||||||
onClose,
|
onClose,
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
size = 'md',
|
size = "md",
|
||||||
children,
|
children,
|
||||||
footer,
|
footer,
|
||||||
closeOnOverlayClick = true,
|
closeOnOverlayClick = true,
|
||||||
showCloseButton = true,
|
showCloseButton = true,
|
||||||
className = '',
|
className = "",
|
||||||
}) => {
|
}) => {
|
||||||
// ESC key handler and body scroll lock
|
return (
|
||||||
useEffect(() => {
|
<HeroModal>
|
||||||
if (!isOpen) return;
|
<HeroModal.Backdrop
|
||||||
|
isOpen={isOpen}
|
||||||
const handleKeyDown = (e: KeyboardEvent) => {
|
isDismissable={closeOnOverlayClick}
|
||||||
if (e.key === 'Escape') {
|
onOpenChange={(open) => {
|
||||||
|
if (!open) {
|
||||||
onClose();
|
onClose();
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const originalOverflow = document.body.style.overflow;
|
|
||||||
document.body.style.overflow = 'hidden';
|
|
||||||
window.addEventListener('keydown', handleKeyDown);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
document.body.style.overflow = originalOverflow;
|
|
||||||
window.removeEventListener('keydown', handleKeyDown);
|
|
||||||
};
|
|
||||||
}, [isOpen, onClose]);
|
|
||||||
|
|
||||||
if (!isOpen) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
role="dialog"
|
|
||||||
aria-modal="true"
|
|
||||||
className="fixed inset-0 z-50 flex items-center justify-center p-4 sm:p-6 overflow-y-auto"
|
|
||||||
>
|
|
||||||
{/* Backdrop */}
|
|
||||||
<div
|
|
||||||
onClick={() => {
|
|
||||||
if (closeOnOverlayClick) onClose();
|
|
||||||
}}
|
}}
|
||||||
className="fixed inset-0 bg-slate-950/70 backdrop-blur-md transition-opacity animate-fadeIn"
|
variant="blur"
|
||||||
/>
|
className="fixed inset-0 bg-slate-950/70 backdrop-blur-md transition-opacity animate-fadeIn">
|
||||||
|
<HeroModal.Container placement="center" scroll="inside" size="md" className="p-4 sm:p-6">
|
||||||
{/* Modal Dialog Card */}
|
<HeroModal.Dialog
|
||||||
<div
|
aria-modal="true"
|
||||||
className={`
|
className={`
|
||||||
relative w-full rounded-3xl overflow-hidden shadow-2xl z-10
|
relative w-full rounded-3xl overflow-hidden shadow-2xl z-10 p-0
|
||||||
bg-white text-slate-900 border border-slate-200
|
bg-white text-slate-900 border border-slate-200
|
||||||
dark:bg-slate-900/95 dark:text-slate-100 dark:border-slate-800
|
dark:bg-slate-900/95 dark:text-slate-100 dark:border-slate-800
|
||||||
transition-all transform animate-scaleUp
|
transition-all transform animate-scaleUp
|
||||||
${sizeStyles[size]}
|
${sizeStyles[size]}
|
||||||
${className}
|
${className}
|
||||||
`}
|
`}>
|
||||||
>
|
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
{(title || description || showCloseButton) && (
|
{(title || description || showCloseButton) && (
|
||||||
<div className="flex items-start justify-between p-6 pb-4 border-b border-slate-100 dark:border-slate-800/80">
|
<HeroModal.Header className="flex items-start justify-between p-6 pb-4 border-b border-slate-100 dark:border-slate-800/80">
|
||||||
<div className="space-y-1 pr-6">
|
<div className="space-y-1 pr-6">
|
||||||
{title && (
|
{title && (
|
||||||
<h2 className="text-xl font-bold text-slate-900 dark:text-white tracking-tight">
|
<HeroModal.Heading className="text-xl font-bold text-slate-900 dark:text-white tracking-tight">
|
||||||
{title}
|
{title}
|
||||||
</h2>
|
</HeroModal.Heading>
|
||||||
)}
|
)}
|
||||||
{description && (
|
{description && (
|
||||||
<p className="text-xs sm:text-sm text-slate-500 dark:text-slate-400">
|
<p className="text-xs sm:text-sm text-slate-500 dark:text-slate-400">{description}</p>
|
||||||
{description}
|
|
||||||
</p>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{showCloseButton && (
|
{showCloseButton && (
|
||||||
<button
|
<HeroModal.CloseTrigger
|
||||||
type="button"
|
|
||||||
onClick={onClose}
|
|
||||||
aria-label="Cerrar modal"
|
|
||||||
className="p-2 rounded-xl text-slate-400 hover:text-slate-700 hover:bg-slate-100 dark:hover:text-slate-200 dark:hover:bg-slate-800 transition-colors"
|
className="p-2 rounded-xl text-slate-400 hover:text-slate-700 hover:bg-slate-100 dark:hover:text-slate-200 dark:hover:bg-slate-800 transition-colors"
|
||||||
>
|
onPress={onClose}
|
||||||
|
aria-label="Cerrar modal">
|
||||||
<FiX className="text-lg" />
|
<FiX className="text-lg" />
|
||||||
</button>
|
</HeroModal.CloseTrigger>
|
||||||
)}
|
)}
|
||||||
</div>
|
</HeroModal.Header>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Body Content */}
|
{/* Body Content */}
|
||||||
<div className="p-6 overflow-y-auto max-h-[calc(85vh-160px)]">
|
<HeroModal.Body className="p-6 overflow-y-auto max-h-[calc(85vh-160px)]">{children}</HeroModal.Body>
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
{footer && (
|
{footer && (
|
||||||
<div className="p-6 pt-4 border-t border-slate-100 dark:border-slate-800/80 bg-slate-50/50 dark:bg-slate-950/40 flex items-center justify-end gap-3">
|
<HeroModal.Footer className="p-6 pt-4 border-t border-slate-100 dark:border-slate-800/80 bg-slate-50/50 dark:bg-slate-950/40 flex items-center justify-end gap-3">
|
||||||
{footer}
|
{footer}
|
||||||
</div>
|
</HeroModal.Footer>
|
||||||
)}
|
)}
|
||||||
</div>
|
</HeroModal.Dialog>
|
||||||
</div>
|
</HeroModal.Container>
|
||||||
|
</HeroModal.Backdrop>
|
||||||
|
</HeroModal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
+136
-101
@@ -1,6 +1,9 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import React from 'react';
|
|
||||||
import { FiChevronDown, FiAlertCircle } from 'react-icons/fi';
|
import React from "react";
|
||||||
|
import { Select as HeroSelect, Label, Description, FieldError, ListBox, ListBoxItem } from "@heroui/react";
|
||||||
|
import type { Key } from "@heroui/react";
|
||||||
|
import { FiChevronDown, FiAlertCircle } from "react-icons/fi";
|
||||||
|
|
||||||
export interface SelectOption {
|
export interface SelectOption {
|
||||||
value: string | number;
|
value: string | number;
|
||||||
@@ -8,158 +11,190 @@ export interface SelectOption {
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SelectProps extends Omit<React.SelectHTMLAttributes<HTMLSelectElement>, 'size'> {
|
export interface SelectProps {
|
||||||
label?: string;
|
label?: string;
|
||||||
helperText?: string;
|
helperText?: string;
|
||||||
errorMessage?: string;
|
errorMessage?: string;
|
||||||
leftIcon?: React.ReactNode;
|
leftIcon?: React.ReactNode;
|
||||||
options?: SelectOption[];
|
options?: SelectOption[];
|
||||||
sizeVariant?: 'sm' | 'md' | 'lg';
|
sizeVariant?: "sm" | "md" | "lg";
|
||||||
fullWidth?: boolean;
|
fullWidth?: boolean;
|
||||||
|
id?: string;
|
||||||
|
name?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
required?: boolean;
|
||||||
|
value?: string | number;
|
||||||
|
defaultValue?: string | number;
|
||||||
|
onChange?: (value: string | null) => void;
|
||||||
|
className?: string;
|
||||||
|
children?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sizeStyles = {
|
const sizeStyles = {
|
||||||
sm: {
|
sm: {
|
||||||
select: 'py-2 text-xs rounded-lg',
|
select: "py-2 text-xs rounded-lg",
|
||||||
icon: 'text-sm',
|
icon: "text-sm",
|
||||||
leftPad: 'pl-8',
|
leftPad: "pl-8",
|
||||||
rightPad: 'pr-8',
|
rightPad: "pr-8",
|
||||||
},
|
},
|
||||||
md: {
|
md: {
|
||||||
select: 'py-2.5 text-sm rounded-xl',
|
select: "py-2.5 text-sm rounded-xl",
|
||||||
icon: 'text-base',
|
icon: "text-base",
|
||||||
leftPad: 'pl-10',
|
leftPad: "pl-10",
|
||||||
rightPad: 'pr-10',
|
rightPad: "pr-10",
|
||||||
},
|
},
|
||||||
lg: {
|
lg: {
|
||||||
select: 'py-3.5 text-base rounded-2xl',
|
select: "py-3.5 text-base rounded-2xl",
|
||||||
icon: 'text-lg',
|
icon: "text-lg",
|
||||||
leftPad: 'pl-12',
|
leftPad: "pl-12",
|
||||||
rightPad: 'pr-12',
|
rightPad: "pr-12",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Select = React.forwardRef<HTMLSelectElement, SelectProps>(
|
export const Select = React.forwardRef<HTMLDivElement, SelectProps>(
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
label,
|
label,
|
||||||
helperText,
|
helperText,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
leftIcon,
|
leftIcon,
|
||||||
options,
|
options = [],
|
||||||
sizeVariant = 'md',
|
sizeVariant = "md",
|
||||||
fullWidth = true,
|
fullWidth = true,
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
disabled,
|
disabled,
|
||||||
required,
|
required,
|
||||||
className = '',
|
className = "",
|
||||||
|
value,
|
||||||
|
defaultValue,
|
||||||
|
onChange,
|
||||||
children,
|
children,
|
||||||
...props
|
|
||||||
},
|
},
|
||||||
ref
|
ref,
|
||||||
) => {
|
) => {
|
||||||
const selectId = id || name || (label ? label.toLowerCase().replace(/\s+/g, '-') : undefined);
|
const selectId = id || name || (label ? label.toLowerCase().replace(/\s+/g, "-") : undefined);
|
||||||
|
|
||||||
const hasError = Boolean(errorMessage);
|
const hasError = Boolean(errorMessage);
|
||||||
const currentSize = sizeStyles[sizeVariant];
|
const currentSize = sizeStyles[sizeVariant];
|
||||||
|
|
||||||
|
const selectedKey = value !== undefined && value !== null && value !== "" ? String(value) : undefined;
|
||||||
|
|
||||||
|
const defaultSelectedKey =
|
||||||
|
defaultValue !== undefined && defaultValue !== null && defaultValue !== "" ? String(defaultValue) : undefined;
|
||||||
|
|
||||||
|
const handleSelectionChange = (key: Key | null) => {
|
||||||
|
if (!onChange) return;
|
||||||
|
onChange(key !== null ? String(key) : null);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${fullWidth ? 'w-full' : 'inline-block'} space-y-1.5`}>
|
<div className={`${fullWidth ? "w-full" : "inline-block"} space-y-1.5`}>
|
||||||
{/* Label */}
|
<HeroSelect
|
||||||
{label && (
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<label
|
|
||||||
htmlFor={selectId}
|
|
||||||
className={`block text-xs font-semibold select-none transition-colors ${
|
|
||||||
hasError
|
|
||||||
? 'text-rose-600 dark:text-rose-400'
|
|
||||||
: 'text-slate-700 dark:text-slate-300'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
{required && <span className="ml-1 text-rose-500 font-bold">*</span>}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Select Wrapper */}
|
|
||||||
<div className="relative flex items-center">
|
|
||||||
{/* Left Icon */}
|
|
||||||
{leftIcon && (
|
|
||||||
<div
|
|
||||||
className={`absolute left-3.5 pointer-events-none flex items-center justify-center transition-colors ${
|
|
||||||
hasError
|
|
||||||
? 'text-rose-500'
|
|
||||||
: 'text-slate-400 dark:text-slate-500'
|
|
||||||
} ${currentSize.icon}`}
|
|
||||||
>
|
|
||||||
{leftIcon}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Core Select Element */}
|
|
||||||
<select
|
|
||||||
ref={ref}
|
ref={ref}
|
||||||
id={selectId}
|
id={selectId}
|
||||||
name={name}
|
name={name}
|
||||||
disabled={disabled}
|
selectedKey={selectedKey}
|
||||||
required={required}
|
defaultSelectedKey={defaultSelectedKey}
|
||||||
|
onSelectionChange={handleSelectionChange}
|
||||||
|
isDisabled={disabled}
|
||||||
|
isRequired={required}
|
||||||
|
isInvalid={hasError}
|
||||||
|
className="w-full">
|
||||||
|
{label && (
|
||||||
|
<Label
|
||||||
|
className={`block text-xs font-semibold select-none transition-colors ${
|
||||||
|
hasError ? "text-rose-600 dark:text-rose-400" : "text-slate-700 dark:text-slate-300"
|
||||||
|
}`}>
|
||||||
|
{label}
|
||||||
|
{required && <span className="ml-1 text-rose-500 font-bold">*</span>}
|
||||||
|
</Label>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<HeroSelect.Trigger
|
||||||
className={`
|
className={`
|
||||||
w-full px-3.5 font-normal transition-all duration-200 outline-none appearance-none cursor-pointer
|
relative flex w-full items-center
|
||||||
|
font-normal transition-all duration-200
|
||||||
|
outline-none appearance-none cursor-pointer
|
||||||
bg-white text-slate-900
|
bg-white text-slate-900
|
||||||
border border-slate-300
|
border border-slate-300
|
||||||
hover:border-slate-400
|
hover:border-slate-400
|
||||||
focus:border-cyan-500 focus:ring-4 focus:ring-cyan-500/15
|
focus:border-cyan-500
|
||||||
dark:bg-slate-900/90 dark:text-slate-100
|
focus:ring-4
|
||||||
dark:border-slate-800 dark:hover:border-slate-700
|
focus:ring-cyan-500/15
|
||||||
dark:focus:border-cyan-500 dark:focus:ring-cyan-500/25
|
dark:bg-slate-900/90
|
||||||
disabled:opacity-60 disabled:bg-slate-100 disabled:dark:bg-slate-950 disabled:cursor-not-allowed
|
dark:text-slate-100
|
||||||
${leftIcon ? currentSize.leftPad : ''}
|
dark:border-slate-800
|
||||||
${currentSize.rightPad}
|
dark:hover:border-slate-700
|
||||||
${
|
dark:focus:border-cyan-500
|
||||||
hasError
|
dark:focus:ring-cyan-500/25
|
||||||
? '!border-rose-500 !ring-rose-500/20 text-rose-900 dark:text-rose-200 focus:!border-rose-500 focus:!ring-rose-500/20'
|
disabled:opacity-60
|
||||||
: ''
|
disabled:bg-slate-100
|
||||||
}
|
disabled:dark:bg-slate-950
|
||||||
|
disabled:cursor-not-allowed
|
||||||
${currentSize.select}
|
${currentSize.select}
|
||||||
|
${leftIcon ? currentSize.leftPad : ""}
|
||||||
|
${currentSize.rightPad}
|
||||||
|
${hasError ? "border-rose-500! ring-rose-500/20! text-rose-900 dark:text-rose-200" : ""}
|
||||||
${className}
|
${className}
|
||||||
`}
|
`}>
|
||||||
{...props}
|
{leftIcon && (
|
||||||
>
|
<span
|
||||||
{options
|
className={`
|
||||||
? options.map((opt) => (
|
absolute left-3.5
|
||||||
<option
|
pointer-events-none
|
||||||
key={opt.value}
|
flex items-center justify-center
|
||||||
value={opt.value}
|
transition-colors
|
||||||
disabled={opt.disabled}
|
${hasError ? "text-rose-500" : "text-slate-400 dark:text-slate-500"}
|
||||||
className="bg-white text-slate-900 dark:bg-slate-900 dark:text-slate-100"
|
${currentSize.icon}
|
||||||
>
|
`}>
|
||||||
{opt.label}
|
{leftIcon}
|
||||||
</option>
|
</span>
|
||||||
))
|
)}
|
||||||
: children}
|
|
||||||
</select>
|
|
||||||
|
|
||||||
{/* Right Chevron Icon */}
|
<HeroSelect.Value className="w-full text-left" />
|
||||||
<div
|
|
||||||
className={`absolute right-3.5 pointer-events-none flex items-center justify-center text-slate-400 dark:text-slate-500 ${currentSize.icon}`}
|
<HeroSelect.Indicator
|
||||||
>
|
className={`
|
||||||
|
absolute right-3.5
|
||||||
|
pointer-events-none
|
||||||
|
flex items-center justify-center
|
||||||
|
text-slate-400 dark:text-slate-500
|
||||||
|
${currentSize.icon}
|
||||||
|
`}>
|
||||||
<FiChevronDown />
|
<FiChevronDown />
|
||||||
</div>
|
</HeroSelect.Indicator>
|
||||||
</div>
|
</HeroSelect.Trigger>
|
||||||
|
|
||||||
|
<HeroSelect.Popover className="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-xl shadow-xl overflow-hidden z-50 min-w-(--trigger-width)">
|
||||||
|
<ListBox className="p-1 outline-none">
|
||||||
|
{options.length > 0
|
||||||
|
? options.map((option) => (
|
||||||
|
<ListBoxItem
|
||||||
|
key={String(option.value)}
|
||||||
|
id={String(option.value)}
|
||||||
|
textValue={option.label}
|
||||||
|
isDisabled={option.disabled}
|
||||||
|
className="px-3 py-2 text-sm rounded-lg cursor-pointer hover:bg-slate-100 dark:hover:bg-slate-800 data-[selected=true]:bg-cyan-500/10 data-[selected=true]:text-cyan-600 dark:data-[selected=true]:text-cyan-400 outline-none transition-colors">
|
||||||
|
{option.label}
|
||||||
|
</ListBoxItem>
|
||||||
|
))
|
||||||
|
: (children as any)}
|
||||||
|
</ListBox>
|
||||||
|
</HeroSelect.Popover>
|
||||||
|
|
||||||
{/* Error or Helper Message */}
|
|
||||||
{hasError ? (
|
{hasError ? (
|
||||||
<p className="flex items-center gap-1.5 text-xs text-rose-600 dark:text-rose-400 animate-fadeIn">
|
<FieldError className="flex items-center gap-1.5 text-xs text-rose-600 dark:text-rose-400">
|
||||||
<FiAlertCircle className="shrink-0 text-xs" />
|
<FiAlertCircle className="shrink-0 text-xs" />
|
||||||
<span>{errorMessage}</span>
|
<span>{errorMessage}</span>
|
||||||
</p>
|
</FieldError>
|
||||||
) : helperText ? (
|
) : helperText ? (
|
||||||
<p className="text-xs text-slate-500 dark:text-slate-400">{helperText}</p>
|
<Description className="text-xs text-slate-500 dark:text-slate-400">{helperText}</Description>
|
||||||
) : null}
|
) : null}
|
||||||
|
</HeroSelect>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
Select.displayName = 'Select';
|
Select.displayName = "Select";
|
||||||
@@ -1,45 +1,41 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import React from 'react';
|
|
||||||
|
import React from "react";
|
||||||
|
import { Spinner as HeroSpinner } from "@heroui/react";
|
||||||
|
|
||||||
export interface SpinnerProps {
|
export interface SpinnerProps {
|
||||||
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
size?: "xs" | "sm" | "md" | "lg" | "xl";
|
||||||
color?: 'cyan' | 'blue' | 'white' | 'slate' | 'current';
|
color?: "cyan" | "blue" | "white" | "slate" | "current";
|
||||||
className?: string;
|
className?: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sizeClasses = {
|
const sizeClasses = {
|
||||||
xs: 'w-3.5 h-3.5 border-2',
|
xs: "w-3.5 h-3.5",
|
||||||
sm: 'w-4 h-4 border-2',
|
sm: "w-4 h-4",
|
||||||
md: 'w-6 h-6 border-2',
|
md: "w-6 h-6",
|
||||||
lg: 'w-8 h-8 border-3',
|
lg: "w-8 h-8",
|
||||||
xl: 'w-12 h-12 border-4',
|
xl: "w-12 h-12",
|
||||||
};
|
};
|
||||||
|
|
||||||
const colorClasses = {
|
const colorClasses = {
|
||||||
cyan: 'border-cyan-500/20 border-t-cyan-500 dark:border-cyan-400/20 dark:border-t-cyan-400',
|
cyan: "text-cyan-500 dark:text-cyan-400",
|
||||||
blue: 'border-blue-500/20 border-t-blue-500 dark:border-blue-400/20 dark:border-t-blue-400',
|
blue: "text-blue-500 dark:text-blue-400",
|
||||||
white: 'border-white/30 border-t-white',
|
white: "text-white",
|
||||||
slate: 'border-slate-300 border-t-slate-700 dark:border-slate-700 dark:border-t-slate-200',
|
slate: "text-slate-700 dark:text-slate-200",
|
||||||
current: 'border-current/20 border-t-current',
|
current: "text-current",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Spinner: React.FC<SpinnerProps> = ({
|
export const Spinner: React.FC<SpinnerProps> = ({
|
||||||
size = 'md',
|
size = "md",
|
||||||
color = 'cyan',
|
color = "cyan",
|
||||||
className = '',
|
className = "",
|
||||||
label = 'Cargando...',
|
label = "Cargando...",
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<HeroSpinner
|
||||||
role="status"
|
|
||||||
aria-label={label}
|
aria-label={label}
|
||||||
className={`inline-flex items-center justify-center ${className}`}
|
className={`inline-block animate-spin ${sizeClasses[size]} ${colorClasses[color]} ${className}`}
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={`rounded-full animate-spin ${sizeClasses[size]} ${colorClasses[color]}`}
|
|
||||||
/>
|
/>
|
||||||
<span className="sr-only">{label}</span>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -1,5 +1,11 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import React from 'react';
|
|
||||||
|
import React from "react";
|
||||||
|
import {
|
||||||
|
Switch as HeroSwitch,
|
||||||
|
Label,
|
||||||
|
Description,
|
||||||
|
} from "@heroui/react";
|
||||||
|
|
||||||
export interface SwitchProps {
|
export interface SwitchProps {
|
||||||
label?: React.ReactNode;
|
label?: React.ReactNode;
|
||||||
@@ -7,7 +13,7 @@ export interface SwitchProps {
|
|||||||
checked?: boolean;
|
checked?: boolean;
|
||||||
onChange?: (checked: boolean) => void;
|
onChange?: (checked: boolean) => void;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
size?: 'sm' | 'md' | 'lg';
|
size?: "sm" | "md" | "lg";
|
||||||
checkedIcon?: React.ReactNode;
|
checkedIcon?: React.ReactNode;
|
||||||
uncheckedIcon?: React.ReactNode;
|
uncheckedIcon?: React.ReactNode;
|
||||||
id?: string;
|
id?: string;
|
||||||
@@ -16,21 +22,21 @@ export interface SwitchProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const trackSizes = {
|
const trackSizes = {
|
||||||
sm: 'w-8 h-4.5',
|
sm: "w-8 h-[18px]",
|
||||||
md: 'w-11 h-6',
|
md: "w-11 h-6",
|
||||||
lg: 'w-14 h-7.5',
|
lg: "w-14 h-[30px]",
|
||||||
};
|
};
|
||||||
|
|
||||||
const thumbSizes = {
|
const thumbSizes = {
|
||||||
sm: 'w-3.5 h-3.5',
|
sm: "size-3.5",
|
||||||
md: 'w-5 h-5',
|
md: "size-5",
|
||||||
lg: 'w-6.5 h-6.5',
|
lg: "size-[26px]",
|
||||||
};
|
};
|
||||||
|
|
||||||
const translateSizes = {
|
const translateSizes = {
|
||||||
sm: 'translate-x-3.5',
|
sm: "translate-x-3.5",
|
||||||
md: 'translate-x-5',
|
md: "translate-x-5",
|
||||||
lg: 'translate-x-6.5',
|
lg: "translate-x-[26px]",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Switch: React.FC<SwitchProps> = ({
|
export const Switch: React.FC<SwitchProps> = ({
|
||||||
@@ -39,85 +45,100 @@ export const Switch: React.FC<SwitchProps> = ({
|
|||||||
checked = false,
|
checked = false,
|
||||||
onChange,
|
onChange,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
size = 'md',
|
size = "md",
|
||||||
checkedIcon,
|
checkedIcon,
|
||||||
uncheckedIcon,
|
uncheckedIcon,
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
className = '',
|
className = "",
|
||||||
}) => {
|
}) => {
|
||||||
const switchId = id || name || (typeof label === 'string' ? label.toLowerCase().replace(/\s+/g, '-') : undefined);
|
const switchId =
|
||||||
|
id ||
|
||||||
const handleClick = () => {
|
name ||
|
||||||
if (!disabled && onChange) {
|
(typeof label === "string"
|
||||||
onChange(!checked);
|
? label.toLowerCase().replace(/\s+/g, "-")
|
||||||
}
|
: undefined);
|
||||||
};
|
|
||||||
|
|
||||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
||||||
if (e.key === ' ' || e.key === 'Enter') {
|
|
||||||
e.preventDefault();
|
|
||||||
handleClick();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`inline-flex items-start gap-3 select-none ${className}`}>
|
<HeroSwitch
|
||||||
{/* Switch Button */}
|
|
||||||
<button
|
|
||||||
id={switchId}
|
id={switchId}
|
||||||
type="button"
|
name={name}
|
||||||
role="switch"
|
isSelected={checked}
|
||||||
aria-checked={checked}
|
isDisabled={disabled}
|
||||||
disabled={disabled}
|
size={size}
|
||||||
onClick={handleClick}
|
className={className}
|
||||||
onKeyDown={handleKeyDown}
|
onChange={(e: any) => {
|
||||||
|
if (!onChange || disabled) return;
|
||||||
|
const val = typeof e === "boolean" ? e : Boolean(e?.target?.checked);
|
||||||
|
onChange(val);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<HeroSwitch.Content>
|
||||||
|
<HeroSwitch.Control
|
||||||
className={`
|
className={`
|
||||||
relative shrink-0 inline-flex items-center rounded-full p-0.5 cursor-pointer transition-colors duration-200 ease-in-out
|
|
||||||
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2
|
|
||||||
focus-visible:ring-cyan-500 focus-visible:ring-offset-white dark:focus-visible:ring-offset-slate-950
|
|
||||||
disabled:opacity-50 disabled:cursor-not-allowed
|
|
||||||
${trackSizes[size]}
|
${trackSizes[size]}
|
||||||
|
relative shrink-0 inline-flex items-center rounded-full p-0.5
|
||||||
|
transition-colors duration-200 ease-in-out cursor-pointer
|
||||||
|
border-0 m-0 box-border outline-none
|
||||||
|
|
||||||
${
|
${
|
||||||
checked
|
checked
|
||||||
? 'bg-cyan-500 dark:bg-cyan-500'
|
? "bg-cyan-500"
|
||||||
: 'bg-slate-300 dark:bg-slate-700 hover:bg-slate-400 dark:hover:bg-slate-600'
|
: "bg-slate-300 hover:bg-slate-400 dark:bg-slate-700 dark:hover:bg-slate-600"
|
||||||
|
}
|
||||||
|
|
||||||
|
${disabled ? "cursor-not-allowed opacity-50" : ""}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<HeroSwitch.Thumb
|
||||||
|
className={`
|
||||||
|
${thumbSizes[size]}
|
||||||
|
${checked ? translateSizes[size] : "translate-x-0"}
|
||||||
|
|
||||||
|
border-0 m-0 pointer-events-none shrink-0 flex items-center justify-center
|
||||||
|
rounded-full bg-white text-slate-800 shadow-md
|
||||||
|
transition-transform duration-200 ease-in-out
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{(checkedIcon || uncheckedIcon) && (
|
||||||
|
<HeroSwitch.Icon>
|
||||||
|
{checked ? checkedIcon : uncheckedIcon}
|
||||||
|
</HeroSwitch.Icon>
|
||||||
|
)}
|
||||||
|
</HeroSwitch.Thumb>
|
||||||
|
</HeroSwitch.Control>
|
||||||
|
|
||||||
|
{/* TEXTOS */}
|
||||||
|
{(label || description) && (
|
||||||
|
<div className="flex flex-col text-left">
|
||||||
|
{label && (
|
||||||
|
<Label
|
||||||
|
className={`
|
||||||
|
text-xs sm:text-sm font-medium transition-colors cursor-pointer
|
||||||
|
${
|
||||||
|
disabled
|
||||||
|
? "text-slate-400 dark:text-slate-600 cursor-not-allowed"
|
||||||
|
: "text-slate-800 hover:text-slate-950 dark:text-slate-200 dark:hover:text-white"
|
||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
{/* Thumb */}
|
{label}
|
||||||
<span
|
</Label>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{description && (
|
||||||
|
<Description
|
||||||
className={`
|
className={`
|
||||||
pointer-events-none flex items-center justify-center rounded-full bg-white shadow-md transform transition-transform duration-200 ease-in-out text-slate-800
|
text-xs text-slate-500 dark:text-slate-400 cursor-pointer
|
||||||
${thumbSizes[size]}
|
${disabled ? "cursor-not-allowed" : ""}
|
||||||
${checked ? translateSizes[size] : 'translate-x-0'}
|
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
{checked ? checkedIcon : uncheckedIcon}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{/* Label and description */}
|
|
||||||
{(label || description) && (
|
|
||||||
<div className="flex flex-col text-left cursor-pointer" onClick={handleClick}>
|
|
||||||
{label && (
|
|
||||||
<span
|
|
||||||
className={`text-xs sm:text-sm font-medium transition-colors ${
|
|
||||||
disabled
|
|
||||||
? 'text-slate-400 dark:text-slate-600'
|
|
||||||
: 'text-slate-800 dark:text-slate-200 hover:text-slate-950 dark:hover:text-white'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{description && (
|
|
||||||
<span className="text-xs text-slate-500 dark:text-slate-400">
|
|
||||||
{description}
|
{description}
|
||||||
</span>
|
</Description>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</HeroSwitch.Content>
|
||||||
|
</HeroSwitch>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
Reference in new issue
Block a user