diff --git a/package-lock.json b/package-lock.json
index 8d3cb015..780dd381 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "frappe-ui-react",
- "version": "1.0.1",
+ "version": "1.0.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "frappe-ui-react",
- "version": "1.0.1",
+ "version": "1.0.2",
"workspaces": [
"packages/*"
],
@@ -132,7 +132,6 @@
"integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.3",
@@ -1767,7 +1766,6 @@
"version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
"integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -1821,6 +1819,60 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@base-ui/react": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@base-ui/react/-/react-1.0.0.tgz",
+ "integrity": "sha512-4USBWz++DUSLTuIYpbYkSgy1F9ZmNG9S/lXvlUN6qMK0P0RlW+6eQmDUB4DgZ7HVvtXl4pvi4z5J2fv6Z3+9hg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.28.4",
+ "@base-ui/utils": "0.2.3",
+ "@floating-ui/react-dom": "^2.1.6",
+ "@floating-ui/utils": "^0.2.10",
+ "reselect": "^5.1.1",
+ "tabbable": "^6.3.0",
+ "use-sync-external-store": "^1.6.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@types/react": "^17 || ^18 || ^19",
+ "react": "^17 || ^18 || ^19",
+ "react-dom": "^17 || ^18 || ^19"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@base-ui/utils": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/@base-ui/utils/-/utils-0.2.3.tgz",
+ "integrity": "sha512-/CguQ2PDaOzeVOkllQR8nocJ0FFIDqsWIcURsVmm53QGo8NhFNpePjNlyPIB41luxfOqnG7PU0xicMEw3ls7XQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.28.4",
+ "@floating-ui/utils": "^0.2.10",
+ "reselect": "^5.1.1",
+ "use-sync-external-store": "^1.6.0"
+ },
+ "peerDependencies": {
+ "@types/react": "^17 || ^18 || ^19",
+ "react": "^17 || ^18 || ^19",
+ "react-dom": "^17 || ^18 || ^19"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@bcoe/v8-coverage": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz",
@@ -1940,7 +1992,6 @@
}
],
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=18"
},
@@ -1964,7 +2015,6 @@
}
],
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -3411,666 +3461,6 @@
"url": "https://opencollective.com/popperjs"
}
},
- "node_modules/@radix-ui/primitive": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz",
- "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
- "license": "MIT"
- },
- "node_modules/@radix-ui/react-arrow": {
- "version": "1.1.7",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz",
- "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-primitive": "2.1.3"
- },
- "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-collection": {
- "version": "1.1.7",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz",
- "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-compose-refs": "1.1.2",
- "@radix-ui/react-context": "1.1.2",
- "@radix-ui/react-primitive": "2.1.3",
- "@radix-ui/react-slot": "1.2.3"
- },
- "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.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz",
- "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==",
- "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-dialog": {
- "version": "1.1.15",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz",
- "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/primitive": "1.1.3",
- "@radix-ui/react-compose-refs": "1.1.2",
- "@radix-ui/react-context": "1.1.2",
- "@radix-ui/react-dismissable-layer": "1.1.11",
- "@radix-ui/react-focus-guards": "1.1.3",
- "@radix-ui/react-focus-scope": "1.1.7",
- "@radix-ui/react-id": "1.1.1",
- "@radix-ui/react-portal": "1.1.9",
- "@radix-ui/react-presence": "1.1.5",
- "@radix-ui/react-primitive": "2.1.3",
- "@radix-ui/react-slot": "1.2.3",
- "@radix-ui/react-use-controllable-state": "1.2.2",
- "aria-hidden": "^1.2.4",
- "react-remove-scroll": "^2.6.3"
- },
- "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-direction": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz",
- "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==",
- "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-dismissable-layer": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz",
- "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/primitive": "1.1.3",
- "@radix-ui/react-compose-refs": "1.1.2",
- "@radix-ui/react-primitive": "2.1.3",
- "@radix-ui/react-use-callback-ref": "1.1.1",
- "@radix-ui/react-use-escape-keydown": "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-dropdown-menu": {
- "version": "2.1.16",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz",
- "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/primitive": "1.1.3",
- "@radix-ui/react-compose-refs": "1.1.2",
- "@radix-ui/react-context": "1.1.2",
- "@radix-ui/react-id": "1.1.1",
- "@radix-ui/react-menu": "2.1.16",
- "@radix-ui/react-primitive": "2.1.3",
- "@radix-ui/react-use-controllable-state": "1.2.2"
- },
- "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-focus-guards": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz",
- "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==",
- "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-focus-scope": {
- "version": "1.1.7",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz",
- "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-compose-refs": "1.1.2",
- "@radix-ui/react-primitive": "2.1.3",
- "@radix-ui/react-use-callback-ref": "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-id": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz",
- "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-use-layout-effect": "1.1.1"
- },
- "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-menu": {
- "version": "2.1.16",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz",
- "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/primitive": "1.1.3",
- "@radix-ui/react-collection": "1.1.7",
- "@radix-ui/react-compose-refs": "1.1.2",
- "@radix-ui/react-context": "1.1.2",
- "@radix-ui/react-direction": "1.1.1",
- "@radix-ui/react-dismissable-layer": "1.1.11",
- "@radix-ui/react-focus-guards": "1.1.3",
- "@radix-ui/react-focus-scope": "1.1.7",
- "@radix-ui/react-id": "1.1.1",
- "@radix-ui/react-popper": "1.2.8",
- "@radix-ui/react-portal": "1.1.9",
- "@radix-ui/react-presence": "1.1.5",
- "@radix-ui/react-primitive": "2.1.3",
- "@radix-ui/react-roving-focus": "1.1.11",
- "@radix-ui/react-slot": "1.2.3",
- "@radix-ui/react-use-callback-ref": "1.1.1",
- "aria-hidden": "^1.2.4",
- "react-remove-scroll": "^2.6.3"
- },
- "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-popper": {
- "version": "1.2.8",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz",
- "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==",
- "license": "MIT",
- "dependencies": {
- "@floating-ui/react-dom": "^2.0.0",
- "@radix-ui/react-arrow": "1.1.7",
- "@radix-ui/react-compose-refs": "1.1.2",
- "@radix-ui/react-context": "1.1.2",
- "@radix-ui/react-primitive": "2.1.3",
- "@radix-ui/react-use-callback-ref": "1.1.1",
- "@radix-ui/react-use-layout-effect": "1.1.1",
- "@radix-ui/react-use-rect": "1.1.1",
- "@radix-ui/react-use-size": "1.1.1",
- "@radix-ui/rect": "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-portal": {
- "version": "1.1.9",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz",
- "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-primitive": "2.1.3",
- "@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-presence": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz",
- "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-compose-refs": "1.1.2",
- "@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-primitive": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
- "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-slot": "1.2.3"
- },
- "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-roving-focus": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz",
- "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/primitive": "1.1.3",
- "@radix-ui/react-collection": "1.1.7",
- "@radix-ui/react-compose-refs": "1.1.2",
- "@radix-ui/react-context": "1.1.2",
- "@radix-ui/react-direction": "1.1.1",
- "@radix-ui/react-id": "1.1.1",
- "@radix-ui/react-primitive": "2.1.3",
- "@radix-ui/react-use-callback-ref": "1.1.1",
- "@radix-ui/react-use-controllable-state": "1.2.2"
- },
- "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.3",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
- "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
- "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-toast": {
- "version": "1.2.15",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.15.tgz",
- "integrity": "sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/primitive": "1.1.3",
- "@radix-ui/react-collection": "1.1.7",
- "@radix-ui/react-compose-refs": "1.1.2",
- "@radix-ui/react-context": "1.1.2",
- "@radix-ui/react-dismissable-layer": "1.1.11",
- "@radix-ui/react-portal": "1.1.9",
- "@radix-ui/react-presence": "1.1.5",
- "@radix-ui/react-primitive": "2.1.3",
- "@radix-ui/react-use-callback-ref": "1.1.1",
- "@radix-ui/react-use-controllable-state": "1.2.2",
- "@radix-ui/react-use-layout-effect": "1.1.1",
- "@radix-ui/react-visually-hidden": "1.2.3"
- },
- "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-tooltip": {
- "version": "1.2.8",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz",
- "integrity": "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/primitive": "1.1.3",
- "@radix-ui/react-compose-refs": "1.1.2",
- "@radix-ui/react-context": "1.1.2",
- "@radix-ui/react-dismissable-layer": "1.1.11",
- "@radix-ui/react-id": "1.1.1",
- "@radix-ui/react-popper": "1.2.8",
- "@radix-ui/react-portal": "1.1.9",
- "@radix-ui/react-presence": "1.1.5",
- "@radix-ui/react-primitive": "2.1.3",
- "@radix-ui/react-slot": "1.2.3",
- "@radix-ui/react-use-controllable-state": "1.2.2",
- "@radix-ui/react-visually-hidden": "1.2.3"
- },
- "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-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-controllable-state": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz",
- "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-use-effect-event": "0.0.2",
- "@radix-ui/react-use-layout-effect": "1.1.1"
- },
- "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-effect-event": {
- "version": "0.0.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz",
- "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-use-layout-effect": "1.1.1"
- },
- "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-escape-keydown": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz",
- "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-use-callback-ref": "1.1.1"
- },
- "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/@radix-ui/react-use-rect": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz",
- "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/rect": "1.1.1"
- },
- "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-size": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz",
- "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-use-layout-effect": "1.1.1"
- },
- "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-visually-hidden": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz",
- "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-primitive": "2.1.3"
- },
- "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/rect": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz",
- "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==",
- "license": "MIT"
- },
"node_modules/@react-aria/focus": {
"version": "3.21.2",
"resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.21.2.tgz",
@@ -5301,7 +4691,6 @@
"integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/code-frame": "^7.10.4",
"@babel/runtime": "^7.12.5",
@@ -5581,7 +4970,6 @@
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
"devOptional": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"csstype": "^3.0.2"
}
@@ -5590,9 +4978,8 @@
"version": "19.2.1",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.1.tgz",
"integrity": "sha512-/EEvYBdT3BflCWvTMO7YkYBHVE9Ci6XdqZciZANQgKpaiDRGOLIlRo91jbTNRQjgPFWVaRxcYc0luVNFitz57A==",
- "devOptional": true,
+ "dev": true,
"license": "MIT",
- "peer": true,
"peerDependencies": {
"@types/react": "^19.2.0"
}
@@ -5656,7 +5043,6 @@
"integrity": "sha512-hA8gxBq4ukonVXPy0OKhiaUh/68D0E88GSmtC1iAEnGaieuDi38LhS7jdCHRLi6ErJBNDGCzvh5EnzdPwUc0DA==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.46.0",
@@ -5697,7 +5083,6 @@
"integrity": "sha512-n1H6IcDhmmUEG7TNVSspGmiHHutt7iVKtZwRppD7e04wha5MrkV1h3pti9xQLcCMt6YWsncpoT0HMjkH1FNwWQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.46.0",
"@typescript-eslint/types": "8.46.0",
@@ -6222,7 +5607,6 @@
"integrity": "sha512-tJxiPrWmzH8a+w9nLKlQMzAKX/7VjFs50MWgcAj7p9XQ7AQ9/35fByFYptgPELyLw+0aixTnC4pUWV+APcZ/kw==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@testing-library/dom": "^10.4.0",
"@testing-library/user-event": "^14.6.1",
@@ -6360,7 +5744,6 @@
"integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@vitest/utils": "3.2.4",
"pathe": "^2.0.3",
@@ -6419,7 +5802,6 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
- "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -6546,18 +5928,6 @@
"sprintf-js": "~1.0.2"
}
},
- "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/aria-query": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
@@ -6952,7 +6322,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.8.9",
"caniuse-lite": "^1.0.30001746",
@@ -7766,12 +7135,6 @@
"node": ">=8"
}
},
- "node_modules/detect-node-es": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
- "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==",
- "license": "MIT"
- },
"node_modules/diff-sequences": {
"version": "29.6.3",
"dev": true,
@@ -8077,7 +7440,6 @@
"integrity": "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==",
"hasInstallScript": true,
"license": "MIT",
- "peer": true,
"bin": {
"esbuild": "bin/esbuild"
},
@@ -8119,7 +7481,6 @@
"integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"debug": "^4.3.4"
},
@@ -8156,7 +7517,6 @@
"integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -8860,15 +8220,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/get-nonce": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
- "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==",
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/get-package-type": {
"version": "0.1.0",
"dev": true,
@@ -9924,7 +9275,6 @@
"version": "30.2.0",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@jest/core": "30.2.0",
"@jest/types": "30.2.0",
@@ -10854,7 +10204,6 @@
"integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"cssstyle": "^4.2.1",
"data-urls": "^5.0.0",
@@ -12693,7 +12042,6 @@
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
"dev": true,
"license": "MIT",
- "peer": true,
"bin": {
"prettier": "bin-prettier.js"
},
@@ -12812,7 +12160,6 @@
"node_modules/quill": {
"version": "2.0.3",
"license": "BSD-3-Clause",
- "peer": true,
"dependencies": {
"eventemitter3": "^5.0.1",
"lodash-es": "^4.17.21",
@@ -12826,7 +12173,6 @@
"node_modules/quill-delta": {
"version": "5.1.0",
"license": "MIT",
- "peer": true,
"dependencies": {
"fast-diff": "^1.3.0",
"lodash.clonedeep": "^4.5.0",
@@ -12905,7 +12251,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -12946,7 +12291,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
"integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
@@ -13016,53 +12360,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/react-remove-scroll": {
- "version": "2.7.1",
- "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz",
- "integrity": "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==",
- "license": "MIT",
- "dependencies": {
- "react-remove-scroll-bar": "^2.3.7",
- "react-style-singleton": "^2.2.3",
- "tslib": "^2.1.0",
- "use-callback-ref": "^1.3.3",
- "use-sidecar": "^1.1.3"
- },
- "engines": {
- "node": ">=10"
- },
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/react-remove-scroll-bar": {
- "version": "2.3.8",
- "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz",
- "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==",
- "license": "MIT",
- "dependencies": {
- "react-style-singleton": "^2.2.2",
- "tslib": "^2.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
"node_modules/react-resizable": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/react-resizable/-/react-resizable-3.0.5.tgz",
@@ -13076,28 +12373,6 @@
"react": ">= 16.3"
}
},
- "node_modules/react-style-singleton": {
- "version": "2.2.3",
- "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
- "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==",
- "license": "MIT",
- "dependencies": {
- "get-nonce": "^1.0.0",
- "tslib": "^2.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
"node_modules/read-pkg": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
@@ -13256,6 +12531,12 @@
"node": ">=0.10.0"
}
},
+ "node_modules/reselect": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
+ "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==",
+ "license": "MIT"
+ },
"node_modules/resize-observer-polyfill": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
@@ -13374,7 +12655,6 @@
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.4.tgz",
"integrity": "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@types/estree": "1.0.8"
},
@@ -13911,7 +13191,6 @@
"integrity": "sha512-kfr6kxQAjA96ADlH6FMALJwJ+eM80UqXy106yVHNgdsAP/CdzkkicglRAhZAvUycXK9AeadF6KZ00CWLtVMN4w==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@storybook/global": "^5.0.0",
"@testing-library/jest-dom": "^6.6.3",
@@ -14330,9 +13609,9 @@
}
},
"node_modules/tabbable": {
- "version": "6.2.0",
- "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz",
- "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==",
+ "version": "6.4.0",
+ "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz",
+ "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==",
"license": "MIT"
},
"node_modules/tailwindcss": {
@@ -14719,7 +13998,6 @@
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"license": "Apache-2.0",
- "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -14925,49 +14203,6 @@
"punycode": "^2.1.0"
}
},
- "node_modules/use-callback-ref": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz",
- "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==",
- "license": "MIT",
- "dependencies": {
- "tslib": "^2.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/use-sidecar": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz",
- "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==",
- "license": "MIT",
- "dependencies": {
- "detect-node-es": "^1.1.0",
- "tslib": "^2.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
"node_modules/use-sync-external-store": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
@@ -15006,7 +14241,6 @@
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.11.tgz",
"integrity": "sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.5.0",
@@ -15148,7 +14382,6 @@
"integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@types/chai": "^5.2.2",
"@vitest/expect": "3.2.4",
@@ -15694,16 +14927,13 @@
},
"packages/frappe-ui-react": {
"name": "@rtcamp/frappe-ui-react",
- "version": "1.0.1",
+ "version": "1.0.2",
"license": "MIT",
"dependencies": {
+ "@base-ui/react": "^1.0.0",
"@floating-ui/react": "^0.27.13",
"@headlessui/react": "^2.2.6",
"@popperjs/core": "^2.11.8",
- "@radix-ui/react-dialog": "^1.1.14",
- "@radix-ui/react-dropdown-menu": "^2.1.15",
- "@radix-ui/react-toast": "^1.2.15",
- "@radix-ui/react-tooltip": "^1.2.7",
"@tailwindcss/vite": "^4.1.11",
"clsx": "^2.1.1",
"dayjs": "^1.11.13",
diff --git a/packages/frappe-ui-react/package.json b/packages/frappe-ui-react/package.json
index fd279ba5..8e6b7ecd 100644
--- a/packages/frappe-ui-react/package.json
+++ b/packages/frappe-ui-react/package.json
@@ -38,13 +38,10 @@
"publish:remote": "npm publish"
},
"dependencies": {
+ "@base-ui/react": "^1.0.0",
"@floating-ui/react": "^0.27.13",
"@headlessui/react": "^2.2.6",
"@popperjs/core": "^2.11.8",
- "@radix-ui/react-dialog": "^1.1.14",
- "@radix-ui/react-dropdown-menu": "^2.1.15",
- "@radix-ui/react-toast": "^1.2.15",
- "@radix-ui/react-tooltip": "^1.2.7",
"@tailwindcss/vite": "^4.1.11",
"clsx": "^2.1.1",
"dayjs": "^1.11.13",
diff --git a/packages/frappe-ui-react/src/components/dialog/dialog.css b/packages/frappe-ui-react/src/components/dialog/dialog.css
index 1d75aab6..6970a1aa 100644
--- a/packages/frappe-ui-react/src/components/dialog/dialog.css
+++ b/packages/frappe-ui-react/src/components/dialog/dialog.css
@@ -1,4 +1,4 @@
-@keyframes dialog-overlay-in {
+@keyframes dialog-backdrop-in {
from {
opacity: 0;
}
@@ -7,7 +7,7 @@
}
}
-@keyframes dialog-overlay-out {
+@keyframes dialog-backdrop-out {
from {
opacity: 1;
}
@@ -38,18 +38,18 @@
}
}
-.dialog-overlay[data-state='open'] {
- animation: dialog-overlay-in 100ms ease-out;
+.dialog-backdrop[data-open] {
+ animation: dialog-backdrop-in 100ms ease-out;
}
-.dialog-overlay[data-state='closed'] {
- animation: dialog-overlay-out 150ms ease-in;
+.dialog-backdrop[data-closed] {
+ animation: dialog-backdrop-out 150ms ease-in;
}
-.dialog-content[data-state='open'] {
+.dialog-content[data-open] {
animation: dialog-content-in 100ms ease-out;
}
-.dialog-content[data-state='closed'] {
+.dialog-content[data-closed] {
animation: dialog-content-out 150ms ease-in;
-}
\ No newline at end of file
+}
diff --git a/packages/frappe-ui-react/src/components/dialog/dialog.stories.tsx b/packages/frappe-ui-react/src/components/dialog/dialog.stories.tsx
index c79b7934..34deeb11 100644
--- a/packages/frappe-ui-react/src/components/dialog/dialog.stories.tsx
+++ b/packages/frappe-ui-react/src/components/dialog/dialog.stories.tsx
@@ -286,8 +286,8 @@ export const WithInteractiveComponents: Story = {
setAutocompleteValue(_value as AutocompleteOption)
}
/>
-
-
@@ -209,11 +204,11 @@ const Dialog = ({
)}
)}
-
-
-
-
-
+
+
+
+
+
);
};
diff --git a/packages/frappe-ui-react/src/components/dialog/types.ts b/packages/frappe-ui-react/src/components/dialog/types.ts
index 36da537e..2f423b48 100644
--- a/packages/frappe-ui-react/src/components/dialog/types.ts
+++ b/packages/frappe-ui-react/src/components/dialog/types.ts
@@ -15,7 +15,7 @@ export interface DialogActionButtonProps {
}
export interface DialogOptions {
- title?: (() => React.ReactNode) | string;
+ title?: (() => React.ReactElement) | string;
message?: string;
size?:
| "xs"
diff --git a/packages/frappe-ui-react/src/components/dropdown/dropdown.tsx b/packages/frappe-ui-react/src/components/dropdown/dropdown.tsx
index 7b8d753f..823e4fde 100644
--- a/packages/frappe-ui-react/src/components/dropdown/dropdown.tsx
+++ b/packages/frappe-ui-react/src/components/dropdown/dropdown.tsx
@@ -1,5 +1,5 @@
import React, { useMemo, useCallback } from "react";
-import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
+import { Menu } from "@base-ui/react/menu";
import { Button, ButtonProps } from "../button";
import { Switch } from "../switch";
@@ -10,10 +10,11 @@ import type {
DropdownOptions,
} from "./types";
import FeatherIcon, { type FeatherIconProps } from "../featherIcon";
+import clsx from "clsx";
const cssClasses = {
dropdownContent:
- "min-w-40 divide-y divide-outline-gray-modals rounded-lg bg-surface-modal shadow-2xl ring-black focus:outline-none dropdown-content border border-outline-gray-1 z-100",
+ "min-w-40 divide-y divide-outline-gray-modals rounded-lg bg-surface-modal shadow-2xl ring-black focus:outline-none dropdown-content border border-outline-gray-1",
groupContainer: "p-1.5",
groupLabel: "flex h-7 items-center px-2 text-sm font-medium text-ink-gray-7",
itemLabel: "whitespace-nowrap",
@@ -23,6 +24,7 @@ const cssClasses = {
"group flex h-7 w-full items-center rounded px-2 text-base focus:outline-none",
submenuTrigger:
"group flex h-7 w-full items-center rounded px-2 text-base text-ink-gray-6 focus:outline-none",
+ dropdownPositioner: "z-100",
};
const Dropdown: React.FC = ({
@@ -180,64 +182,70 @@ const Dropdown: React.FC = ({
);
} else if (item.submenu) {
return (
-
-
-
-
-
-
- {processOptionsIntoGroups(item.submenu).map((submenuGroup) => (
-
- {submenuGroup.group && !submenuGroup.hideLabel && (
-
- {submenuGroup.group}
-
- )}
- {submenuGroup.items.map((subItem) => (
- subItem.onClick?.()}
- >
- {renderDropdownItem(subItem)}
-
- ))}
-
- ))}
-
-
-
+ )}
+ className={clsx(
+ cssClasses.submenuTrigger,
+ getSubmenuBackgroundColor(item)
+ )}
+ >
+ {item.label}
+
+ }
+ nativeButton={true}
+ />
+
+
+
+ {processOptionsIntoGroups(item.submenu).map((submenuGroup) => (
+
+ {submenuGroup.group && !submenuGroup.hideLabel && (
+
+ {submenuGroup.group}
+
+ )}
+ {submenuGroup.items.map((subItem) => (
+ subItem.onClick?.()}
+ render={renderDropdownItem(subItem)}
+ nativeButton={
+ !subItem.switch &&
+ !subItem.submenu &&
+ !subItem.component
+ }
+ />
+ ))}
+
+ ))}
+
+
+
+
);
} else {
return (
@@ -245,6 +253,7 @@ const Dropdown: React.FC = ({
className={`${cssClasses.itemButton} ${getTextColor(
item
)} ${getSubmenuBackgroundColor(item)}`}
+ data-testid="dropdown-item-button"
>
{item.icon &&
(typeof item.icon === "string" ? (
@@ -262,49 +271,53 @@ const Dropdown: React.FC = ({
};
return (
-
-
- {children ? (
- React.cloneElement(children as React.ReactElement, { ...attrs })
- ) : (
-
- )}
-
+
+
+ {button?.label || "Options"}
+
+ )
+ }
+ />
-
-
- {groups.map((group) => (
-
- {group.group && !group.hideLabel && (
-
- {group.group}
-
- )}
- {group.items.map((item) => (
-
- !item.switch && item.onClick?.()}>
- {renderDropdownItem(item)}
-
-
- ))}
-
- ))}
-
-
-
+
+
+
+ {groups.map((group) => (
+
+ {group.group && !group.hideLabel && (
+
+ {group.group}
+
+ )}
+ {group.items.map((item) => (
+
+
!item.switch && item.onClick?.()}
+ render={renderDropdownItem(item)}
+ nativeButton={
+ !item.switch && !item.submenu && !item.component
+ }
+ />
+
+ ))}
+
+ ))}
+
+
+
+
);
};
diff --git a/packages/frappe-ui-react/src/components/dropdown/tests/dropdown.tsx b/packages/frappe-ui-react/src/components/dropdown/tests/dropdown.tsx
new file mode 100644
index 00000000..590e5ffb
--- /dev/null
+++ b/packages/frappe-ui-react/src/components/dropdown/tests/dropdown.tsx
@@ -0,0 +1,368 @@
+import React from "react";
+import { render, screen } from "@testing-library/react";
+import "@testing-library/jest-dom";
+import userEvent from "@testing-library/user-event";
+
+import Dropdown from "../dropdown";
+
+describe("Dropdown", () => {
+ it("renders dropdown with given items", async () => {
+ const user = userEvent.setup();
+ const items = [
+ { label: "Item 1", onClick: jest.fn() },
+ { label: "Item 2", onClick: jest.fn() },
+ ];
+
+ render();
+
+ const trigger = screen.getByTestId("dropdown-trigger");
+ await user.click(trigger);
+
+ // Wait for menu to open
+ expect(await screen.findByText("Item 1")).toBeInTheDocument();
+
+ const menuItems = screen.getAllByTestId("dropdown-item-button");
+ expect(menuItems).toHaveLength(2);
+ expect(menuItems[0]).toHaveTextContent("Item 1");
+ expect(menuItems[1]).toHaveTextContent("Item 2");
+ });
+
+ it("calls onClick handler when an item is clicked", async () => {
+ const user = userEvent.setup();
+ const onClickMock = jest.fn();
+ const items = [{ label: "Clickable Item", onClick: onClickMock }];
+
+ render();
+
+ await user.click(screen.getByTestId("dropdown-trigger"));
+
+ expect(await screen.findByText("Clickable Item")).toBeInTheDocument();
+
+ await user.click(screen.getByTestId("dropdown-item-button"));
+
+ expect(onClickMock).toHaveBeenCalledTimes(1);
+ });
+
+ it("renders submenu items correctly", async () => {
+ const user = userEvent.setup();
+ const items = [
+ {
+ label: "Parent Item",
+ submenu: [
+ { label: "Sub Item 1", onClick: jest.fn() },
+ { label: "Sub Item 2", onClick: jest.fn() },
+ ],
+ },
+ ];
+
+ render();
+
+ await user.click(screen.getByTestId("dropdown-trigger"));
+
+ expect(await screen.findByText("Parent Item")).toBeInTheDocument();
+
+ const submenuTrigger = screen.getByTestId("dropdown-submenu-trigger");
+ await user.click(submenuTrigger);
+
+ expect(await screen.findByText("Sub Item 1")).toBeInTheDocument();
+ expect(screen.getByText("Sub Item 2")).toBeInTheDocument();
+ });
+
+ it("renders default button label when no button prop provided", () => {
+ render();
+
+ expect(screen.getByTestId("dropdown-trigger")).toHaveTextContent("Options");
+ });
+
+ it("renders custom button label when button prop provided", () => {
+ render();
+
+ expect(screen.getByTestId("dropdown-trigger")).toHaveTextContent(
+ "Custom Label"
+ );
+ });
+
+ it("renders custom children as trigger", async () => {
+ const user = userEvent.setup();
+ const items = [{ label: "Item 1", onClick: jest.fn() }];
+
+ render(
+
+
+
+ );
+
+ const customTrigger = screen.getByTestId("custom-trigger");
+ expect(customTrigger).toHaveTextContent("Custom Trigger");
+
+ await user.click(customTrigger);
+ expect(await screen.findByText("Item 1")).toBeInTheDocument();
+ });
+
+ it("renders grouped items with group labels", async () => {
+ const user = userEvent.setup();
+ const items = [
+ {
+ group: "Group 1",
+ key: "group-1",
+ items: [
+ { label: "Group 1 Item 1", onClick: jest.fn() },
+ { label: "Group 1 Item 2", onClick: jest.fn() },
+ ],
+ },
+ {
+ group: "Group 2",
+ key: "group-2",
+ items: [{ label: "Group 2 Item 1", onClick: jest.fn() }],
+ },
+ ];
+
+ render();
+
+ await user.click(screen.getByTestId("dropdown-trigger"));
+
+ expect(await screen.findByText("Group 1")).toBeInTheDocument();
+ expect(screen.getByText("Group 2")).toBeInTheDocument();
+ expect(screen.getByText("Group 1 Item 1")).toBeInTheDocument();
+ expect(screen.getByText("Group 1 Item 2")).toBeInTheDocument();
+ expect(screen.getByText("Group 2 Item 1")).toBeInTheDocument();
+ });
+
+ it("hides group label when hideLabel is true", async () => {
+ const user = userEvent.setup();
+ const items = [
+ {
+ group: "Hidden Group",
+ key: "hidden-group",
+ hideLabel: true,
+ items: [{ label: "Hidden Group Item", onClick: jest.fn() }],
+ },
+ ];
+
+ render();
+
+ await user.click(screen.getByTestId("dropdown-trigger"));
+
+ expect(await screen.findByText("Hidden Group Item")).toBeInTheDocument();
+ expect(screen.queryByText("Hidden Group")).not.toBeInTheDocument();
+ });
+
+ it("filters items based on condition function", async () => {
+ const user = userEvent.setup();
+ const items = [
+ { label: "Visible Item", onClick: jest.fn(), condition: () => true },
+ { label: "Hidden Item", onClick: jest.fn(), condition: () => false },
+ ];
+
+ render();
+
+ await user.click(screen.getByTestId("dropdown-trigger"));
+
+ expect(await screen.findByText("Visible Item")).toBeInTheDocument();
+ expect(screen.queryByText("Hidden Item")).not.toBeInTheDocument();
+ });
+
+ it("renders items with red theme styling", async () => {
+ const user = userEvent.setup();
+ const items = [
+ { label: "Delete", onClick: jest.fn(), theme: "red" as const },
+ ];
+
+ render();
+
+ await user.click(screen.getByTestId("dropdown-trigger"));
+
+ const deleteButton = await screen.findByTestId("dropdown-item-button");
+ expect(deleteButton).toHaveTextContent("Delete");
+ expect(deleteButton).toHaveClass("text-ink-red-3");
+ });
+
+ it("renders items with icon as string", async () => {
+ const user = userEvent.setup();
+ const items = [{ label: "Edit", onClick: jest.fn(), icon: "edit" }];
+
+ render();
+
+ await user.click(screen.getByTestId("dropdown-trigger"));
+
+ expect(await screen.findByText("Edit")).toBeInTheDocument();
+ // Icon should be rendered as FeatherIcon
+ const itemButton = screen.getByTestId("dropdown-item-button");
+ expect(itemButton.querySelector("svg")).toBeInTheDocument();
+ });
+
+ it("renders items with icon as React element", async () => {
+ const user = userEvent.setup();
+ const CustomIcon = () => (
+ CustomIcon placeholder
+ );
+ const items = [
+ { label: "CustomIcon", onClick: jest.fn(), icon: },
+ ];
+
+ render();
+
+ await user.click(screen.getByTestId("dropdown-trigger"));
+
+ expect(await screen.findByText("CustomIcon")).toBeInTheDocument();
+ expect(screen.getByTestId("custom-icon")).toBeInTheDocument();
+ });
+
+ it("renders switch item and toggles value", async () => {
+ const user = userEvent.setup();
+ const onToggle = jest.fn();
+ const items = [
+ {
+ label: "Enable Feature",
+ onClick: onToggle,
+ switch: true,
+ switchValue: false,
+ },
+ ];
+
+ render();
+
+ await user.click(screen.getByTestId("dropdown-trigger"));
+
+ expect(await screen.findByText("Enable Feature")).toBeInTheDocument();
+
+ // Find and click the switch
+ const switchElement = screen.getByRole("switch");
+ await user.click(switchElement);
+
+ expect(onToggle).toHaveBeenCalledWith(true);
+ });
+
+ it("handles mixed items and groups", async () => {
+ const user = userEvent.setup();
+ const items = [
+ { label: "Standalone Item 1", onClick: jest.fn() },
+ {
+ group: "Actions",
+ key: "actions-group",
+ items: [
+ { label: "Action 1", onClick: jest.fn() },
+ { label: "Action 2", onClick: jest.fn() },
+ ],
+ },
+ { label: "Standalone Item 2", onClick: jest.fn() },
+ ];
+
+ render();
+
+ await user.click(screen.getByTestId("dropdown-trigger"));
+
+ expect(await screen.findByText("Standalone Item 1")).toBeInTheDocument();
+ expect(screen.getByText("Actions")).toBeInTheDocument();
+ expect(screen.getByText("Action 1")).toBeInTheDocument();
+ expect(screen.getByText("Action 2")).toBeInTheDocument();
+ expect(screen.getByText("Standalone Item 2")).toBeInTheDocument();
+ });
+
+ it("filters out null and undefined items", async () => {
+ const user = userEvent.setup();
+ const items = [
+ { label: "Valid Item", onClick: jest.fn() },
+ null,
+ undefined,
+ { label: "Another Valid Item", onClick: jest.fn() },
+ ];
+
+ // @ts-expect-error - testing null/undefined handling
+ render();
+
+ await user.click(screen.getByTestId("dropdown-trigger"));
+
+ expect(await screen.findByText("Valid Item")).toBeInTheDocument();
+ expect(screen.getByText("Another Valid Item")).toBeInTheDocument();
+
+ const menuItems = screen.getAllByTestId("dropdown-item-button");
+ expect(menuItems).toHaveLength(2);
+ });
+
+ it("renders with different placements", () => {
+ const { rerender } = render(
+
+ );
+ expect(screen.getByTestId("dropdown-trigger")).toBeInTheDocument();
+
+ rerender(
+
+ );
+ expect(screen.getByTestId("dropdown-trigger")).toBeInTheDocument();
+
+ rerender(
+
+ );
+ expect(screen.getByTestId("dropdown-trigger")).toBeInTheDocument();
+ });
+
+ it("renders custom component in dropdown item", async () => {
+ const user = userEvent.setup();
+ const CustomComponent = ({ active }: { active: boolean }) => (
+
+ Custom Content
+
+ );
+ const items = [{ label: "Custom", component: CustomComponent }];
+
+ render();
+
+ await user.click(screen.getByTestId("dropdown-trigger"));
+
+ expect(await screen.findByTestId("custom-component")).toBeInTheDocument();
+ expect(screen.getByText("Custom Content")).toBeInTheDocument();
+ });
+
+ it("renders empty dropdown when no options provided", async () => {
+ const user = userEvent.setup();
+
+ render();
+
+ const trigger = screen.getByTestId("dropdown-trigger");
+ await user.click(trigger);
+
+ // Dropdown should open but have no items
+ expect(
+ screen.queryByTestId("dropdown-item-button")
+ ).not.toBeInTheDocument();
+ });
+
+ it("renders items in groups filtered by condition", async () => {
+ const user = userEvent.setup();
+ const items = [
+ {
+ group: "Filtered Group",
+ key: "filtered-group",
+ items: [
+ {
+ label: "Visible in Group",
+ onClick: jest.fn(),
+ condition: () => true,
+ },
+ {
+ label: "Hidden in Group",
+ onClick: jest.fn(),
+ condition: () => false,
+ },
+ ],
+ },
+ ];
+
+ render();
+
+ await user.click(screen.getByTestId("dropdown-trigger"));
+
+ expect(await screen.findByText("Visible in Group")).toBeInTheDocument();
+ expect(screen.queryByText("Hidden in Group")).not.toBeInTheDocument();
+ });
+});
diff --git a/packages/frappe-ui-react/src/components/toast/tests/toast.tsx b/packages/frappe-ui-react/src/components/toast/tests/toast.tsx
new file mode 100644
index 00000000..ccfd7898
--- /dev/null
+++ b/packages/frappe-ui-react/src/components/toast/tests/toast.tsx
@@ -0,0 +1,401 @@
+import "@testing-library/jest-dom";
+import { screen, renderHook, act, waitFor } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import ToastProvider from "../toastProvider";
+import { useToasts } from "../useToast";
+
+const renderToastHook = () => {
+ return renderHook(() => useToasts(), {
+ wrapper: ToastProvider,
+ });
+};
+
+describe("Toast", () => {
+ describe("Basic Toast Types", () => {
+ it("renders success toast with message", async () => {
+ const { result } = renderToastHook();
+
+ act(() => {
+ result.current.success("Operation successful");
+ });
+
+ expect(
+ await screen.findByText("Operation successful")
+ ).toBeInTheDocument();
+ });
+
+ it("renders error toast with message", async () => {
+ const { result } = renderToastHook();
+
+ act(() => {
+ result.current.error("Operation failed");
+ });
+
+ expect(await screen.findByText("Operation failed")).toBeInTheDocument();
+ });
+
+ it("renders info toast with message", async () => {
+ const { result } = renderToastHook();
+
+ act(() => {
+ result.current.info("Here is some information");
+ });
+
+ expect(
+ await screen.findByText("Here is some information")
+ ).toBeInTheDocument();
+ });
+
+ it("renders warning toast with message", async () => {
+ const { result } = renderToastHook();
+
+ act(() => {
+ result.current.warning("This is a warning");
+ });
+
+ expect(await screen.findByText("This is a warning")).toBeInTheDocument();
+ });
+
+ it("renders multiple toasts simultaneously", async () => {
+ const { result } = renderToastHook();
+
+ act(() => {
+ result.current.success("Success message");
+ result.current.error("Error message");
+ result.current.info("Info message");
+ result.current.warning("Warning message");
+ });
+
+ expect(await screen.findByText("Success message")).toBeInTheDocument();
+ expect(screen.getByText("Error message")).toBeInTheDocument();
+ expect(screen.getByText("Info message")).toBeInTheDocument();
+ expect(screen.getByText("Warning message")).toBeInTheDocument();
+ });
+ });
+
+ describe("Toast create method", () => {
+ it("creates toast using create method with type", async () => {
+ const { result } = renderToastHook();
+
+ act(() => {
+ result.current.create({
+ message: "Custom toast message",
+ type: "success",
+ });
+ });
+
+ expect(
+ await screen.findByText("Custom toast message")
+ ).toBeInTheDocument();
+ });
+
+ it("creates toast with custom id", async () => {
+ const { result } = renderToastHook();
+
+ let toastId = "";
+ act(() => {
+ toastId = result.current.create({
+ message: "Toast with custom ID",
+ type: "info",
+ id: "custom-toast-id",
+ });
+ });
+
+ expect(
+ await screen.findByText("Toast with custom ID")
+ ).toBeInTheDocument();
+ expect(toastId).toBe("custom-toast-id");
+ });
+
+ it("returns a toast id when creating a toast", async () => {
+ const { result } = renderToastHook();
+
+ let toastId = "";
+
+ act(() => {
+ toastId = result.current.success("Test toast");
+ });
+
+ expect(toastId).toBeDefined();
+ expect(typeof toastId).toBe("string");
+ });
+ });
+
+ describe("Toast removal", () => {
+ it("removes a specific toast by id", async () => {
+ const { result } = renderToastHook();
+
+ let toastId = "";
+
+ act(() => {
+ toastId = result.current.success("Toast to be removed");
+ });
+
+ expect(
+ await screen.findByText("Toast to be removed")
+ ).toBeInTheDocument();
+
+ act(() => {
+ result.current.remove(toastId);
+ });
+
+ await waitFor(() => {
+ expect(
+ screen.queryByText("Toast to be removed")
+ ).not.toBeInTheDocument();
+ });
+ });
+
+ it("removes all toasts with removeAll", async () => {
+ const { result } = renderToastHook();
+
+ act(() => {
+ result.current.success("First toast");
+ result.current.error("Second toast");
+ result.current.info("Third toast");
+ });
+
+ expect(await screen.findByText("First toast")).toBeInTheDocument();
+ expect(screen.getByText("Second toast")).toBeInTheDocument();
+ expect(screen.getByText("Third toast")).toBeInTheDocument();
+
+ act(() => {
+ result.current.removeAll();
+ });
+
+ await waitFor(() => {
+ expect(screen.queryByText("First toast")).not.toBeInTheDocument();
+ expect(screen.queryByText("Second toast")).not.toBeInTheDocument();
+ expect(screen.queryByText("Third toast")).not.toBeInTheDocument();
+ });
+ });
+ });
+
+ describe("Toast with action", () => {
+ it("renders toast with action button", async () => {
+ const { result } = renderToastHook();
+
+ const onClickMock = jest.fn();
+
+ act(() => {
+ result.current.create({
+ message: "Toast with action",
+ type: "info",
+ action: {
+ label: "Undo",
+ onClick: onClickMock,
+ },
+ });
+ });
+
+ expect(await screen.findByText("Toast with action")).toBeInTheDocument();
+ expect(screen.getByText("Undo")).toBeInTheDocument();
+ });
+
+ it("calls action onClick when action button is clicked", async () => {
+ const user = userEvent.setup();
+ const { result } = renderToastHook();
+
+ const onClickMock = jest.fn();
+
+ act(() => {
+ result.current.create({
+ message: "Toast with clickable action",
+ type: "info",
+ action: {
+ label: "Click me",
+ onClick: onClickMock,
+ },
+ });
+ });
+
+ const actionButton = await screen.findByText("Click me");
+ await user.click(actionButton);
+
+ expect(onClickMock).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ describe("Toast closable option", () => {
+ it("renders close button when closable is true (default)", async () => {
+ const { result } = renderToastHook();
+
+ act(() => {
+ result.current.success("Closable toast");
+ });
+
+ expect(await screen.findByText("Closable toast")).toBeInTheDocument();
+ expect(screen.getByLabelText("Close")).toBeInTheDocument();
+ });
+
+ it("does not render close button when closable is false", async () => {
+ const { result } = renderToastHook();
+
+ act(() => {
+ result.current.create({
+ message: "Non-closable toast",
+ type: "info",
+ closable: false,
+ });
+ });
+
+ expect(await screen.findByText("Non-closable toast")).toBeInTheDocument();
+ expect(screen.queryByLabelText("Close")).not.toBeInTheDocument();
+ });
+
+ it("closes toast when close button is clicked", async () => {
+ const user = userEvent.setup();
+ const { result } = renderToastHook();
+
+ act(() => {
+ result.current.success("Toast to close");
+ });
+
+ expect(await screen.findByText("Toast to close")).toBeInTheDocument();
+
+ const closeButton = screen.getByLabelText("Close");
+ await user.click(closeButton);
+
+ await waitFor(() => {
+ expect(screen.queryByText("Toast to close")).not.toBeInTheDocument();
+ });
+ });
+ });
+
+ describe("Toast promise", () => {
+ it("shows loading state while promise is pending", async () => {
+ const { result } = renderToastHook();
+
+ const promise = new Promise((resolve) => {
+ setTimeout(() => resolve("Success!"), 100);
+ });
+
+ act(() => {
+ result.current.promise(promise, {
+ loading: "Loading...",
+ success: "Done!",
+ error: "Failed!",
+ });
+ });
+
+ expect(await screen.findByText("Loading...")).toBeInTheDocument();
+ });
+
+ it("shows success message when promise resolves", async () => {
+ const { result } = renderToastHook();
+
+ const promise = Promise.resolve("Success data");
+
+ act(() => {
+ result.current.promise(promise, {
+ loading: "Loading...",
+ success: "Operation completed!",
+ error: "Failed!",
+ });
+ });
+
+ expect(
+ await screen.findByText("Operation completed!")
+ ).toBeInTheDocument();
+ });
+
+ it("shows success message from function when promise resolves", async () => {
+ const { result } = renderToastHook();
+
+ const promise = Promise.resolve({ name: "John" });
+
+ await act(async () => {
+ await result.current.promise(promise, {
+ loading: "Loading...",
+ success: (data) => `Hello, ${data.name}!`,
+ error: "Failed!",
+ });
+ });
+
+ expect(await screen.findByText("Hello, John!")).toBeInTheDocument();
+ });
+
+ it("shows error message when promise rejects", async () => {
+ const { result } = renderToastHook();
+
+ const promise = Promise.reject(new Error("Something went wrong"));
+
+ act(() => {
+ result.current
+ .promise(promise, {
+ loading: "Loading...",
+ success: "Done!",
+ error: "Operation failed!",
+ })
+ .catch(() => {}); // Catch to avoid unhandled rejection in test
+ });
+
+ expect(await screen.findByText("Operation failed!")).toBeInTheDocument();
+ });
+
+ it("shows error message from function when promise rejects", async () => {
+ const { result } = renderToastHook();
+
+ const promise = Promise.reject(new Error("Custom error message"));
+
+ act(() => {
+ result.current
+ .promise(promise, {
+ loading: "Loading...",
+ success: "Done!",
+ error: (err) => `Error: ${err.message}`,
+ })
+ .catch(() => {}); // Catch to avoid unhandled rejection in test
+ });
+
+ expect(
+ await screen.findByText("Error: Custom error message")
+ ).toBeInTheDocument();
+ });
+ });
+
+ describe("useToasts hook", () => {
+ it("throws error when used outside ToastProvider", () => {
+ const consoleError = jest
+ .spyOn(console, "error")
+ .mockImplementation(() => {});
+
+ expect(() => {
+ renderHook(() => useToasts());
+ }).toThrow("useToasts must be used within a ToastsProvider");
+
+ consoleError.mockRestore();
+ });
+ });
+
+ describe("Toast HTML sanitization", () => {
+ it("renders allowed HTML tags in message", async () => {
+ const { result } = renderToastHook();
+
+ act(() => {
+ result.current.create({
+ message: "This is important and urgent",
+ type: "info",
+ });
+ });
+
+ expect(await screen.findByText("important")).toBe("STRONG");
+ expect(screen.getByText("urgent")).toBe("EM");
+ });
+
+ it("renders link tags in message", async () => {
+ const { result } = renderToastHook();
+
+ act(() => {
+ result.current.create({
+ message: 'Click here for more info',
+ type: "info",
+ });
+ });
+
+ const linkElement = await screen.findByText("here");
+ expect(linkElement.tagName).toBe("A");
+ expect(linkElement).toHaveAttribute("href", "https://example.com");
+ });
+ });
+});
diff --git a/packages/frappe-ui-react/src/components/toast/toast.stories.tsx b/packages/frappe-ui-react/src/components/toast/toast.stories.tsx
index c0056e7b..6107ee5b 100644
--- a/packages/frappe-ui-react/src/components/toast/toast.stories.tsx
+++ b/packages/frappe-ui-react/src/components/toast/toast.stories.tsx
@@ -1,4 +1,4 @@
-import { Meta } from "@storybook/react-vite/*";
+import type { Meta } from "@storybook/react-vite/*";
import { useToasts } from "./useToast";
import ToastProvider from "./toastProvider";
@@ -7,7 +7,7 @@ import { Button } from "../button";
export default {
title: "Components/Toast",
parameters: { docs: { source: { type: "code" } }, layout: "centered" },
- tags: ["autodocs"],
+ tags: ["autodocs"],
decorators: [
(Story) => (
diff --git a/packages/frappe-ui-react/src/components/toast/toast.tsx b/packages/frappe-ui-react/src/components/toast/toast.tsx
index cdbfd117..f1c7c20f 100644
--- a/packages/frappe-ui-react/src/components/toast/toast.tsx
+++ b/packages/frappe-ui-react/src/components/toast/toast.tsx
@@ -1,21 +1,17 @@
import React, { useMemo } from "react";
-import * as Toast from "@radix-ui/react-toast";
+import { Toast } from "@base-ui/react/toast";
import { CircleCheck, AlertTriangle, Info, X } from "lucide-react";
import type { ToastProps } from "./types";
-const ToastComponent: React.FC = ({
- open,
- onOpenChange,
- message,
- type = "success",
- icon,
- closable = true,
- duration = 5000,
- action,
-}) => {
+const ToastComponent: React.FC = ({ toast }) => {
+ const icon = toast.data?.icon;
+ const type = toast.type || "success";
+ const closable = toast.data?.closable ?? true;
+
const iconComponent = useMemo(() => {
if (icon) return icon;
+
switch (type) {
case "success":
return (
@@ -26,115 +22,81 @@ const ToastComponent: React.FC = ({
);
case "error":
- return ;
+ return (
+
+ );
default:
return null;
}
}, [icon, type]);
- const handleAction = () => {
- action?.onClick?.();
- };
-
return (
-
- {iconComponent}
-
- {message && (
-
+
+
+ {iconComponent}
+
+
+
+
+
+
+ {closable && (
+
+
+
)}
-
-
- {action && (
-
- {action.label}
-
- )}
- {closable && (
-
-
-
- )}
-
+
diff --git a/packages/frappe-ui-react/src/components/toast/toastProvider.tsx b/packages/frappe-ui-react/src/components/toast/toastProvider.tsx
index f8b7d16a..b5645dba 100644
--- a/packages/frappe-ui-react/src/components/toast/toastProvider.tsx
+++ b/packages/frappe-ui-react/src/components/toast/toastProvider.tsx
@@ -1,58 +1,72 @@
-import React, { ReactNode, useCallback, useMemo, useState } from "react";
-import { ToastProvider, ToastViewport } from "@radix-ui/react-toast";
+import React, { ReactNode, useCallback, useMemo } from "react";
+import { Toast, type ToastObject } from "@base-ui/react/toast";
import DOMPurify from "dompurify";
import LoadingIndicator from "../loadingIndicator";
-import { ToastProps, ToastOptions, ToastPromiseOptions } from "./types";
-import { ToastContext } from "./context";
+import type {
+ ToastDataInternal,
+ ToastOptions,
+ ToastPromiseOptions,
+} from "./types";
+import { ToastAPI, ToastContext } from "./context";
import ToastComponent from "./toast";
interface ToastsProviderProps {
children: ReactNode;
}
+
+// Simple counter for generating unique toast IDs.
let toastIdCounter = 0;
-const ToastsProvider: React.FC
= ({ children }) => {
- const [toasts, setToasts] = useState([]);
- const create = useCallback((options: ToastOptions): string => {
- const id = `toast-${toastIdCounter++}`;
- const durationInMs =
- options.duration != null ? options.duration * 1000 : 5000;
+const ToastsProvider: React.FC = ({ children }) => {
+ const toastManager = Toast.createToastManager();
- const sanitizedMessage = DOMPurify.sanitize(options.message, {
- ALLOWED_TAGS: ["a", "em", "strong", "i", "b", "u"],
- });
+ return (
+
+ {children}
+
+ );
+};
- const toastItem: ToastProps = {
- id: options?.id || id,
- open: true,
- message: sanitizedMessage,
- type: options.type || "info",
- duration: durationInMs,
- action: options.action,
- icon: options.icon,
- closable: options.closable ?? true,
- onOpenChange: () => null,
- };
+const ToastContextProvider: React.FC = ({ children }) => {
+ const toastManager = Toast.useToastManager();
- setToasts((prev) => [...prev, toastItem]);
- return toastItem.id;
- }, []);
+ const create = useCallback(
+ (options: ToastOptions): string => {
+ const id = `toast-${toastIdCounter++}`;
+ const durationInMs =
+ options.closable === false
+ ? 0
+ : options.duration
+ ? options.duration * 1000
+ : 5000;
- const remove = useCallback((id: string) => {
- setToasts((prev) => prev.filter((t) => t.id !== id));
- }, []);
+ const sanitizedMessage = DOMPurify.sanitize(options.message, {
+ ALLOWED_TAGS: ["a", "em", "strong", "i", "b", "u"],
+ });
- const removeAll = useCallback(() => {
- setToasts([]);
- }, []);
+ return toastManager.add({
+ id: options?.id || id,
+ timeout: durationInMs,
+ description: ,
+ type: options.type || "info",
+ actionProps: {
+ children: options.action?.label,
+ onClick: options.action?.onClick,
+ },
+ data: {
+ icon: options.icon,
+ closable: options.closable,
+ },
+ });
+ },
+ [toastManager]
+ );
- const updateToastInState = useCallback(
- (id: string, updates: Partial>) => {
- setToasts((prev) =>
- prev.map((t) => (t.id === id ? { ...t, ...updates, open: true } : t))
- );
+ const remove = useCallback(
+ (id: string) => {
+ toastManager.close(id);
},
- []
+ [toastManager]
);
const promise = useCallback(
@@ -60,53 +74,49 @@ const ToastsProvider: React.FC = ({ children }) => {
promiseToResolve: Promise,
options: ToastPromiseOptions
): Promise => {
- const loadingDurationInSeconds = options.duration ?? 0;
- const toastId = create({
- message: options.loading,
- type: "info",
- icon: ,
- duration: loadingDurationInSeconds,
- closable: false,
- });
-
- try {
- const data = await promiseToResolve;
- const successMessage =
- typeof options.success === "function"
- ? options.success(data)
- : options.success;
- const successToastDurationInSeconds =
- options.successDuration ?? options.duration ?? 5;
-
- updateToastInState(toastId, {
- message: successMessage,
+ return toastManager.promise(promiseToResolve, {
+ loading: {
+ description: options.loading,
+ type: "info",
+ timeout: 0,
+ data: {
+ icon: ,
+ closable: false,
+ },
+ },
+ success: (data) => ({
+ description:
+ typeof options.success === "function"
+ ? options.success(data)
+ : options.success,
type: "success",
- duration: successToastDurationInSeconds * 1000,
- icon: undefined,
- closable: true,
- });
- return data;
- } catch (error) {
- const errorMessage =
- typeof options.error === "function"
- ? options.error(error as TError)
- : options.error;
- const errorToastDurationInSeconds =
- options.errorDuration ?? options.duration ?? 5;
-
- updateToastInState(toastId, {
- message: errorMessage,
+ timeout: (options.successDuration ?? options.duration ?? 5) * 1000,
+ data: {
+ icon: undefined,
+ },
+ }),
+ error: (error) => ({
+ description:
+ typeof options.error === "function"
+ ? options.error(error as TError)
+ : options.error,
type: "error",
- duration: errorToastDurationInSeconds * 1000,
- icon: undefined,
- closable: true,
- });
- throw error;
- }
+ timeout: (options.errorDuration ?? options.duration ?? 5) * 1000,
+ data: {
+ icon: undefined,
+ },
+ }),
+ });
},
- [create, updateToastInState]
+ [toastManager]
);
+ const removeAll = useCallback(() => {
+ toastManager.toasts.forEach((toast: ToastObject) =>
+ toastManager.close(toast.id)
+ );
+ }, [toastManager]);
+
const success = useCallback(
(message: string, options: Omit = {}) =>
create({ message, type: "success", ...options }),
@@ -128,7 +138,7 @@ const ToastsProvider: React.FC = ({ children }) => {
[create]
);
- const api = useMemo(
+ const api: ToastAPI = useMemo(
() => ({
create,
remove,
@@ -141,23 +151,18 @@ const ToastsProvider: React.FC = ({ children }) => {
}),
[create, remove, removeAll, promise, success, error, warning, info]
);
+
return (
-
-
- {children}
- {toasts.map((t) => (
- {
- if (!isOpen) {
- remove(t.id);
- }
- }}
- />
- ))}
-
-
-
+
+ {children}
+
+
+ {toastManager.toasts.map((toast: ToastObject) => (
+
+ ))}
+
+
+
);
};
diff --git a/packages/frappe-ui-react/src/components/toast/types.ts b/packages/frappe-ui-react/src/components/toast/types.ts
index 06173d02..564eb81a 100644
--- a/packages/frappe-ui-react/src/components/toast/types.ts
+++ b/packages/frappe-ui-react/src/components/toast/types.ts
@@ -1,3 +1,4 @@
+import type { ToastObject } from "@base-ui/react";
import type { ReactNode } from "react";
export interface ToastActionProps {
@@ -7,15 +8,7 @@ export interface ToastActionProps {
}
export interface ToastProps {
- open: boolean;
- onOpenChange: (open: boolean) => void;
- message: string;
- type?: ToastType;
- icon?: ReactNode;
- closable?: boolean;
- duration?: number;
- action?: ToastActionProps;
- id: string;
+ toast: ToastObject;
}
export type ToastType = "success" | "warning" | "error" | "info";
@@ -26,8 +19,14 @@ export interface ToastActionProps {
altText?: string;
}
-export interface ToastOptions
- extends Omit, "open" | "message"> {
+export interface ToastOptions {
+ onOpenChange?: (open: boolean) => void;
+ type?: ToastType;
+ icon?: ReactNode;
+ closable?: boolean;
+ duration?: number;
+ action?: ToastActionProps;
+ id?: string;
message: string;
}
@@ -39,3 +38,8 @@ export interface ToastPromiseOptions {
errorDuration?: number;
duration?: number;
}
+
+export interface ToastDataInternal {
+ icon?: ReactNode;
+ closable?: boolean;
+}
diff --git a/packages/frappe-ui-react/src/components/tooltip/tests/tooltip.tsx b/packages/frappe-ui-react/src/components/tooltip/tests/tooltip.tsx
new file mode 100644
index 00000000..cd0ed544
--- /dev/null
+++ b/packages/frappe-ui-react/src/components/tooltip/tests/tooltip.tsx
@@ -0,0 +1,242 @@
+import React from "react";
+import { render, screen, waitFor } from "@testing-library/react";
+import "@testing-library/jest-dom";
+import userEvent from "@testing-library/user-event";
+
+import Tooltip from "../tooltip";
+import { Button } from "../../button";
+
+describe("Tooltip", () => {
+ describe("Basic Rendering", () => {
+ it("renders children without tooltip initially", () => {
+ render(
+
+
+
+ );
+
+ expect(screen.getByText("Hover me")).toBeInTheDocument();
+ expect(screen.queryByText("Tooltip text")).not.toBeInTheDocument();
+ });
+
+ it("shows tooltip on hover", async () => {
+ const user = userEvent.setup();
+
+ render(
+
+
+
+ );
+
+ const trigger = screen.getByText("Hover me");
+ await user.hover(trigger);
+
+ expect(await screen.findByText("Tooltip text")).toBeInTheDocument();
+ });
+
+ it("hides tooltip on mouse leave", async () => {
+ const user = userEvent.setup();
+
+ render(
+
+
+
+ );
+
+ const trigger = screen.getByText("Hover me");
+ await user.hover(trigger);
+
+ expect(await screen.findByText("Tooltip text")).toBeInTheDocument();
+
+ await user.unhover(trigger);
+
+ await waitFor(() => {
+ expect(screen.queryByText("Tooltip text")).not.toBeInTheDocument();
+ });
+ });
+ });
+
+ describe("Text Prop", () => {
+ it("renders tooltip with text content", async () => {
+ const user = userEvent.setup();
+
+ render(
+
+
+
+ );
+
+ await user.hover(screen.getByText("Trigger"));
+
+ expect(
+ await screen.findByText("This is tooltip text")
+ ).toBeInTheDocument();
+ });
+
+ it("renders empty tooltip when text is empty string", async () => {
+ const user = userEvent.setup();
+
+ render(
+
+
+
+ );
+
+ await user.hover(screen.getByText("Trigger"));
+
+ await waitFor(() => {
+ expect(screen.queryByTestId("tooltip-popup")).not.toBeInTheDocument();
+ });
+ });
+ });
+
+ describe("Body Prop", () => {
+ it("renders custom body content", async () => {
+ const user = userEvent.setup();
+
+ render(
+ Custom content }
+ hoverDelay={0}
+ >
+
+
+ );
+
+ await user.hover(screen.getByText("Trigger"));
+
+ expect(await screen.findByTestId("custom-body")).toBeInTheDocument();
+ expect(screen.getByText("Custom content")).toBeInTheDocument();
+ });
+
+ it("body takes precedence over text", async () => {
+ const user = userEvent.setup();
+
+ render(
+ Body content}
+ hoverDelay={0}
+ >
+
+
+ );
+
+ await user.hover(screen.getByText("Trigger"));
+
+ expect(await screen.findByText("Body content")).toBeInTheDocument();
+ expect(screen.queryByText("Text content")).not.toBeInTheDocument();
+ });
+
+ it("renders complex custom body content", async () => {
+ const user = userEvent.setup();
+
+ render(
+
+ Title
+ Description text
+
+ }
+ hoverDelay={0}
+ >
+
+
+ );
+
+ await user.hover(screen.getByText("Trigger"));
+
+ expect(await screen.findByText("Title")).toBeInTheDocument();
+ expect(screen.getByText("Description text")).toBeInTheDocument();
+ });
+ });
+
+ describe("Different Trigger Elements", () => {
+ it("works with button trigger", async () => {
+ const user = userEvent.setup();
+
+ render(
+
+
+
+ );
+
+ await user.hover(screen.getByText("Button Trigger"));
+
+ expect(await screen.findByText("Button tooltip")).toBeInTheDocument();
+ });
+
+ it("works with span trigger", async () => {
+ const user = userEvent.setup();
+
+ render(
+
+ Span Trigger
+
+ );
+
+ await user.hover(screen.getByText("Span Trigger"));
+
+ expect(await screen.findByText("Span tooltip")).toBeInTheDocument();
+ });
+
+ it("works with div trigger", async () => {
+ const user = userEvent.setup();
+
+ render(
+
+ Div Trigger
+
+ );
+
+ await user.hover(screen.getByText("Div Trigger"));
+
+ expect(await screen.findByText("Div tooltip")).toBeInTheDocument();
+ });
+ });
+
+ describe("Edge Cases", () => {
+ it("handles no text and no body gracefully", async () => {
+ const user = userEvent.setup();
+
+ render(
+
+
+
+ );
+
+ await user.hover(screen.getByText("Trigger"));
+
+ // Should not crash, trigger should still be rendered
+ expect(screen.getByText("Trigger")).toBeInTheDocument();
+
+ waitFor(() => {
+ expect(screen.queryByTestId("tooltip-popup")).not.toBeInTheDocument();
+ });
+ });
+
+ it("renders multiple tooltips independently", async () => {
+ const user = userEvent.setup();
+
+ render(
+ <>
+
+
+
+
+
+
+ >
+ );
+
+ await user.hover(screen.getByText("First"));
+ expect(await screen.findByText("First tooltip")).toBeInTheDocument();
+
+ await user.unhover(screen.getByText("First"));
+ await user.hover(screen.getByText("Second"));
+
+ expect(await screen.findByText("Second tooltip")).toBeInTheDocument();
+ });
+ });
+});
diff --git a/packages/frappe-ui-react/src/components/tooltip/tooltip.stories.tsx b/packages/frappe-ui-react/src/components/tooltip/tooltip.stories.tsx
index 7881dd41..1410b9c4 100644
--- a/packages/frappe-ui-react/src/components/tooltip/tooltip.stories.tsx
+++ b/packages/frappe-ui-react/src/components/tooltip/tooltip.stories.tsx
@@ -49,7 +49,11 @@ type Story = StoryObj;
export const WithText: Story = {
render: (args) => {
return (
-
+
);
diff --git a/packages/frappe-ui-react/src/components/tooltip/tooltip.tsx b/packages/frappe-ui-react/src/components/tooltip/tooltip.tsx
index df93d77c..0eb9b47b 100644
--- a/packages/frappe-ui-react/src/components/tooltip/tooltip.tsx
+++ b/packages/frappe-ui-react/src/components/tooltip/tooltip.tsx
@@ -1,13 +1,7 @@
import React, { useMemo } from "react";
import { TooltipProps } from "./types";
-import {
- TooltipProvider,
- Root,
- TooltipPortal,
- TooltipContent,
- TooltipTrigger,
- TooltipArrow,
-} from "@radix-ui/react-tooltip";
+import { Tooltip } from "@base-ui/react/tooltip";
+import clsx from "clsx";
const TooltipComponent: React.FC = ({
children,
@@ -41,23 +35,32 @@ const TooltipComponent: React.FC = ({
}
return (
-
-
- {children}
-
+
+
+
+
{tooltipContent && (
-
- {tooltipContent}
-
-
+
+
+ {tooltipContent}
+
+
+
+
+
)}
-
-
-
+
+
+
);
};