diff --git a/docs/manifest.json b/docs/manifest.json
index 7bb1e847ce03fd..86a889406ce919 100644
--- a/docs/manifest.json
+++ b/docs/manifest.json
@@ -761,12 +761,6 @@
"markdown_source": "../packages/components/src/drop-zone/README.md",
"parent": "components"
},
- {
- "title": "DropdownMenuV2Ariakit",
- "slug": "dropdown-menu-v2-ariakit",
- "markdown_source": "../packages/components/src/dropdown-menu-v2-ariakit/README.md",
- "parent": "components"
- },
{
"title": "DropdownMenu",
"slug": "dropdown-menu",
diff --git a/package-lock.json b/package-lock.json
index c52010a3347fc1..5861648b6f55df 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7541,85 +7541,6 @@
"@babel/runtime": "^7.13.10"
}
},
- "node_modules/@radix-ui/react-arrow": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.0.2.tgz",
- "integrity": "sha512-fqYwhhI9IarZ0ll2cUSfKuXHlJK0qE4AfnRrPBbRwEH/4mGQn04/QFGomLi8TXWIdv9WJk//KgGm+aDxVIr1wA==",
- "dependencies": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/react-primitive": "1.0.2"
- },
- "peerDependencies": {
- "react": "^16.8 || ^17.0 || ^18.0",
- "react-dom": "^16.8 || ^17.0 || ^18.0"
- }
- },
- "node_modules/@radix-ui/react-arrow/node_modules/@radix-ui/react-primitive": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.2.tgz",
- "integrity": "sha512-zY6G5Qq4R8diFPNwtyoLRZBxzu1Z+SXMlfYpChN7Dv8gvmx9X3qhDqiLWvKseKVJMuedFeU/Sa0Sy/Ia+t06Dw==",
- "dependencies": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/react-slot": "1.0.1"
- },
- "peerDependencies": {
- "react": "^16.8 || ^17.0 || ^18.0",
- "react-dom": "^16.8 || ^17.0 || ^18.0"
- }
- },
- "node_modules/@radix-ui/react-arrow/node_modules/@radix-ui/react-slot": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.1.tgz",
- "integrity": "sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==",
- "dependencies": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/react-compose-refs": "1.0.0"
- },
- "peerDependencies": {
- "react": "^16.8 || ^17.0 || ^18.0"
- }
- },
- "node_modules/@radix-ui/react-collection": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.0.2.tgz",
- "integrity": "sha512-s8WdQQ6wNXpaxdZ308KSr8fEWGrg4un8i4r/w7fhiS4ElRNjk5rRcl0/C6TANG2LvLOGIxtzo/jAg6Qf73TEBw==",
- "dependencies": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/react-compose-refs": "1.0.0",
- "@radix-ui/react-context": "1.0.0",
- "@radix-ui/react-primitive": "1.0.2",
- "@radix-ui/react-slot": "1.0.1"
- },
- "peerDependencies": {
- "react": "^16.8 || ^17.0 || ^18.0",
- "react-dom": "^16.8 || ^17.0 || ^18.0"
- }
- },
- "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-primitive": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.2.tgz",
- "integrity": "sha512-zY6G5Qq4R8diFPNwtyoLRZBxzu1Z+SXMlfYpChN7Dv8gvmx9X3qhDqiLWvKseKVJMuedFeU/Sa0Sy/Ia+t06Dw==",
- "dependencies": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/react-slot": "1.0.1"
- },
- "peerDependencies": {
- "react": "^16.8 || ^17.0 || ^18.0",
- "react-dom": "^16.8 || ^17.0 || ^18.0"
- }
- },
- "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.1.tgz",
- "integrity": "sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==",
- "dependencies": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/react-compose-refs": "1.0.0"
- },
- "peerDependencies": {
- "react": "^16.8 || ^17.0 || ^18.0"
- }
- },
"node_modules/@radix-ui/react-compose-refs": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz",
@@ -7668,17 +7589,6 @@
"react-dom": "^16.8 || ^17.0 || ^18.0"
}
},
- "node_modules/@radix-ui/react-direction": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.0.0.tgz",
- "integrity": "sha512-2HV05lGUgYcA6xgLQ4BKPDmtL+QbIZYH5fCOTAOOcJ5O0QbWS3i9lKaurLzliYUDhORI2Qr3pyjhJh44lKA3rQ==",
- "dependencies": {
- "@babel/runtime": "^7.13.10"
- },
- "peerDependencies": {
- "react": "^16.8 || ^17.0 || ^18.0"
- }
- },
"node_modules/@radix-ui/react-dismissable-layer": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.0.tgz",
@@ -7696,50 +7606,6 @@
"react-dom": "^16.8 || ^17.0 || ^18.0"
}
},
- "node_modules/@radix-ui/react-dropdown-menu": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.0.4.tgz",
- "integrity": "sha512-y6AT9+MydyXcByivdK1+QpjWoKaC7MLjkS/cH1Q3keEyMvDkiY85m8o2Bi6+Z1PPUlCsMULopxagQOSfN0wahg==",
- "dependencies": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/primitive": "1.0.0",
- "@radix-ui/react-compose-refs": "1.0.0",
- "@radix-ui/react-context": "1.0.0",
- "@radix-ui/react-id": "1.0.0",
- "@radix-ui/react-menu": "2.0.4",
- "@radix-ui/react-primitive": "1.0.2",
- "@radix-ui/react-use-controllable-state": "1.0.0"
- },
- "peerDependencies": {
- "react": "^16.8 || ^17.0 || ^18.0",
- "react-dom": "^16.8 || ^17.0 || ^18.0"
- }
- },
- "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-primitive": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.2.tgz",
- "integrity": "sha512-zY6G5Qq4R8diFPNwtyoLRZBxzu1Z+SXMlfYpChN7Dv8gvmx9X3qhDqiLWvKseKVJMuedFeU/Sa0Sy/Ia+t06Dw==",
- "dependencies": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/react-slot": "1.0.1"
- },
- "peerDependencies": {
- "react": "^16.8 || ^17.0 || ^18.0",
- "react-dom": "^16.8 || ^17.0 || ^18.0"
- }
- },
- "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-slot": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.1.tgz",
- "integrity": "sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==",
- "dependencies": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/react-compose-refs": "1.0.0"
- },
- "peerDependencies": {
- "react": "^16.8 || ^17.0 || ^18.0"
- }
- },
"node_modules/@radix-ui/react-focus-guards": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.0.tgz",
@@ -7778,215 +7644,6 @@
"react": "^16.8 || ^17.0 || ^18.0"
}
},
- "node_modules/@radix-ui/react-menu": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.0.4.tgz",
- "integrity": "sha512-mzKR47tZ1t193trEqlQoJvzY4u9vYfVH16ryBrVrCAGZzkgyWnMQYEZdUkM7y8ak9mrkKtJiqB47TlEnubeOFQ==",
- "dependencies": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/primitive": "1.0.0",
- "@radix-ui/react-collection": "1.0.2",
- "@radix-ui/react-compose-refs": "1.0.0",
- "@radix-ui/react-context": "1.0.0",
- "@radix-ui/react-direction": "1.0.0",
- "@radix-ui/react-dismissable-layer": "1.0.3",
- "@radix-ui/react-focus-guards": "1.0.0",
- "@radix-ui/react-focus-scope": "1.0.2",
- "@radix-ui/react-id": "1.0.0",
- "@radix-ui/react-popper": "1.1.1",
- "@radix-ui/react-portal": "1.0.2",
- "@radix-ui/react-presence": "1.0.0",
- "@radix-ui/react-primitive": "1.0.2",
- "@radix-ui/react-roving-focus": "1.0.3",
- "@radix-ui/react-slot": "1.0.1",
- "@radix-ui/react-use-callback-ref": "1.0.0",
- "aria-hidden": "^1.1.1",
- "react-remove-scroll": "2.5.5"
- },
- "peerDependencies": {
- "react": "^16.8 || ^17.0 || ^18.0",
- "react-dom": "^16.8 || ^17.0 || ^18.0"
- }
- },
- "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-dismissable-layer": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.3.tgz",
- "integrity": "sha512-nXZOvFjOuHS1ovumntGV7NNoLaEp9JEvTht3MBjP44NSW5hUKj/8OnfN3+8WmB+CEhN44XaGhpHoSsUIEl5P7Q==",
- "dependencies": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/primitive": "1.0.0",
- "@radix-ui/react-compose-refs": "1.0.0",
- "@radix-ui/react-primitive": "1.0.2",
- "@radix-ui/react-use-callback-ref": "1.0.0",
- "@radix-ui/react-use-escape-keydown": "1.0.2"
- },
- "peerDependencies": {
- "react": "^16.8 || ^17.0 || ^18.0",
- "react-dom": "^16.8 || ^17.0 || ^18.0"
- }
- },
- "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-focus-scope": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.2.tgz",
- "integrity": "sha512-spwXlNTfeIprt+kaEWE/qYuYT3ZAqJiAGjN/JgdvgVDTu8yc+HuX+WOWXrKliKnLnwck0F6JDkqIERncnih+4A==",
- "dependencies": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/react-compose-refs": "1.0.0",
- "@radix-ui/react-primitive": "1.0.2",
- "@radix-ui/react-use-callback-ref": "1.0.0"
- },
- "peerDependencies": {
- "react": "^16.8 || ^17.0 || ^18.0",
- "react-dom": "^16.8 || ^17.0 || ^18.0"
- }
- },
- "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-portal": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.2.tgz",
- "integrity": "sha512-swu32idoCW7KA2VEiUZGBSu9nB6qwGdV6k6HYhUoOo3M1FFpD+VgLzUqtt3mwL1ssz7r2x8MggpLSQach2Xy/Q==",
- "dependencies": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/react-primitive": "1.0.2"
- },
- "peerDependencies": {
- "react": "^16.8 || ^17.0 || ^18.0",
- "react-dom": "^16.8 || ^17.0 || ^18.0"
- }
- },
- "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-primitive": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.2.tgz",
- "integrity": "sha512-zY6G5Qq4R8diFPNwtyoLRZBxzu1Z+SXMlfYpChN7Dv8gvmx9X3qhDqiLWvKseKVJMuedFeU/Sa0Sy/Ia+t06Dw==",
- "dependencies": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/react-slot": "1.0.1"
- },
- "peerDependencies": {
- "react": "^16.8 || ^17.0 || ^18.0",
- "react-dom": "^16.8 || ^17.0 || ^18.0"
- }
- },
- "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.1.tgz",
- "integrity": "sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==",
- "dependencies": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/react-compose-refs": "1.0.0"
- },
- "peerDependencies": {
- "react": "^16.8 || ^17.0 || ^18.0"
- }
- },
- "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-escape-keydown": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.2.tgz",
- "integrity": "sha512-DXGim3x74WgUv+iMNCF+cAo8xUHHeqvjx8zs7trKf+FkQKPQXLk2sX7Gx1ysH7Q76xCpZuxIJE7HLPxRE+Q+GA==",
- "dependencies": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/react-use-callback-ref": "1.0.0"
- },
- "peerDependencies": {
- "react": "^16.8 || ^17.0 || ^18.0"
- }
- },
- "node_modules/@radix-ui/react-menu/node_modules/react-remove-scroll": {
- "version": "2.5.5",
- "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz",
- "integrity": "sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==",
- "dependencies": {
- "react-remove-scroll-bar": "^2.3.3",
- "react-style-singleton": "^2.2.1",
- "tslib": "^2.1.0",
- "use-callback-ref": "^1.3.0",
- "use-sidecar": "^1.1.2"
- },
- "engines": {
- "node": ">=10"
- },
- "peerDependencies": {
- "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-popper": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.1.1.tgz",
- "integrity": "sha512-keYDcdMPNMjSC8zTsZ8wezUMiWM9Yj14wtF3s0PTIs9srnEPC9Kt2Gny1T3T81mmSeyDjZxsD9N5WCwNNb712w==",
- "dependencies": {
- "@babel/runtime": "^7.13.10",
- "@floating-ui/react-dom": "0.7.2",
- "@radix-ui/react-arrow": "1.0.2",
- "@radix-ui/react-compose-refs": "1.0.0",
- "@radix-ui/react-context": "1.0.0",
- "@radix-ui/react-primitive": "1.0.2",
- "@radix-ui/react-use-callback-ref": "1.0.0",
- "@radix-ui/react-use-layout-effect": "1.0.0",
- "@radix-ui/react-use-rect": "1.0.0",
- "@radix-ui/react-use-size": "1.0.0",
- "@radix-ui/rect": "1.0.0"
- },
- "peerDependencies": {
- "react": "^16.8 || ^17.0 || ^18.0",
- "react-dom": "^16.8 || ^17.0 || ^18.0"
- }
- },
- "node_modules/@radix-ui/react-popper/node_modules/@floating-ui/core": {
- "version": "0.7.3",
- "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-0.7.3.tgz",
- "integrity": "sha512-buc8BXHmG9l82+OQXOFU3Kr2XQx9ys01U/Q9HMIrZ300iLc8HLMgh7dcCqgYzAzf4BkoQvDcXf5Y+CuEZ5JBYg=="
- },
- "node_modules/@radix-ui/react-popper/node_modules/@floating-ui/dom": {
- "version": "0.5.4",
- "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-0.5.4.tgz",
- "integrity": "sha512-419BMceRLq0RrmTSDxn8hf9R3VCJv2K9PUfugh5JyEFmdjzDo+e8U5EdR8nzKq8Yj1htzLm3b6eQEEam3/rrtg==",
- "dependencies": {
- "@floating-ui/core": "^0.7.3"
- }
- },
- "node_modules/@radix-ui/react-popper/node_modules/@floating-ui/react-dom": {
- "version": "0.7.2",
- "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-0.7.2.tgz",
- "integrity": "sha512-1T0sJcpHgX/u4I1OzIEhlcrvkUN8ln39nz7fMoE/2HDHrPiMFoOGR7++GYyfUmIQHkkrTinaeQsO3XWubjSvGg==",
- "dependencies": {
- "@floating-ui/dom": "^0.5.3",
- "use-isomorphic-layout-effect": "^1.1.1"
- },
- "peerDependencies": {
- "react": ">=16.8.0",
- "react-dom": ">=16.8.0"
- }
- },
- "node_modules/@radix-ui/react-popper/node_modules/@radix-ui/react-primitive": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.2.tgz",
- "integrity": "sha512-zY6G5Qq4R8diFPNwtyoLRZBxzu1Z+SXMlfYpChN7Dv8gvmx9X3qhDqiLWvKseKVJMuedFeU/Sa0Sy/Ia+t06Dw==",
- "dependencies": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/react-slot": "1.0.1"
- },
- "peerDependencies": {
- "react": "^16.8 || ^17.0 || ^18.0",
- "react-dom": "^16.8 || ^17.0 || ^18.0"
- }
- },
- "node_modules/@radix-ui/react-popper/node_modules/@radix-ui/react-slot": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.1.tgz",
- "integrity": "sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==",
- "dependencies": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/react-compose-refs": "1.0.0"
- },
- "peerDependencies": {
- "react": "^16.8 || ^17.0 || ^18.0"
- }
- },
"node_modules/@radix-ui/react-portal": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.0.tgz",
@@ -8027,52 +7684,6 @@
"react-dom": "^16.8 || ^17.0 || ^18.0"
}
},
- "node_modules/@radix-ui/react-roving-focus": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.3.tgz",
- "integrity": "sha512-stjCkIoMe6h+1fWtXlA6cRfikdBzCLp3SnVk7c48cv/uy3DTGoXhN76YaOYUJuy3aEDvDIKwKR5KSmvrtPvQPQ==",
- "dependencies": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/primitive": "1.0.0",
- "@radix-ui/react-collection": "1.0.2",
- "@radix-ui/react-compose-refs": "1.0.0",
- "@radix-ui/react-context": "1.0.0",
- "@radix-ui/react-direction": "1.0.0",
- "@radix-ui/react-id": "1.0.0",
- "@radix-ui/react-primitive": "1.0.2",
- "@radix-ui/react-use-callback-ref": "1.0.0",
- "@radix-ui/react-use-controllable-state": "1.0.0"
- },
- "peerDependencies": {
- "react": "^16.8 || ^17.0 || ^18.0",
- "react-dom": "^16.8 || ^17.0 || ^18.0"
- }
- },
- "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-primitive": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.2.tgz",
- "integrity": "sha512-zY6G5Qq4R8diFPNwtyoLRZBxzu1Z+SXMlfYpChN7Dv8gvmx9X3qhDqiLWvKseKVJMuedFeU/Sa0Sy/Ia+t06Dw==",
- "dependencies": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/react-slot": "1.0.1"
- },
- "peerDependencies": {
- "react": "^16.8 || ^17.0 || ^18.0",
- "react-dom": "^16.8 || ^17.0 || ^18.0"
- }
- },
- "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-slot": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.1.tgz",
- "integrity": "sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==",
- "dependencies": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/react-compose-refs": "1.0.0"
- },
- "peerDependencies": {
- "react": "^16.8 || ^17.0 || ^18.0"
- }
- },
"node_modules/@radix-ui/react-select": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-1.2.2.tgz",
@@ -8657,30 +8268,6 @@
}
}
},
- "node_modules/@radix-ui/react-use-rect": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.0.0.tgz",
- "integrity": "sha512-TB7pID8NRMEHxb/qQJpvSt3hQU4sqNPM1VCTjTRjEOa7cEop/QMuq8S6fb/5Tsz64kqSvB9WnwsDHtjnrM9qew==",
- "dependencies": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/rect": "1.0.0"
- },
- "peerDependencies": {
- "react": "^16.8 || ^17.0 || ^18.0"
- }
- },
- "node_modules/@radix-ui/react-use-size": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.0.0.tgz",
- "integrity": "sha512-imZ3aYcoYCKhhgNpkNDh/aTiU05qw9hX+HHI1QDBTyIlcFjgeFlKKySNGMwTp7nYFLQg/j0VA2FmCY4WPDDHMg==",
- "dependencies": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/react-use-layout-effect": "1.0.0"
- },
- "peerDependencies": {
- "react": "^16.8 || ^17.0 || ^18.0"
- }
- },
"node_modules/@radix-ui/react-visually-hidden": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.0.3.tgz",
@@ -8766,14 +8353,6 @@
}
}
},
- "node_modules/@radix-ui/rect": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.0.0.tgz",
- "integrity": "sha512-d0O68AYy/9oeEy1DdC07bz1/ZXX+DqCskRd3i4JzLSTXwefzaepQrKjXC7aNM8lTHjFLDO0pDgaEiQ7jEk+HVg==",
- "dependencies": {
- "@babel/runtime": "^7.13.10"
- }
- },
"node_modules/@react-native-clipboard/clipboard": {
"version": "1.11.2",
"resolved": "https://registry.npmjs.org/@react-native-clipboard/clipboard/-/clipboard-1.11.2.tgz",
@@ -51620,19 +51199,6 @@
}
}
},
- "node_modules/use-isomorphic-layout-effect": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz",
- "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==",
- "peerDependencies": {
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
"node_modules/use-latest-callback": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/use-latest-callback/-/use-latest-callback-0.1.6.tgz",
@@ -54726,7 +54292,6 @@
"@emotion/styled": "^11.6.0",
"@emotion/utils": "^1.0.0",
"@floating-ui/react-dom": "^2.0.1",
- "@radix-ui/react-dropdown-menu": "2.0.4",
"@types/gradient-parser": "0.1.3",
"@types/highlight-words-core": "1.2.1",
"@use-gesture/react": "^10.2.24",
@@ -62037,67 +61602,6 @@
"@babel/runtime": "^7.13.10"
}
},
- "@radix-ui/react-arrow": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.0.2.tgz",
- "integrity": "sha512-fqYwhhI9IarZ0ll2cUSfKuXHlJK0qE4AfnRrPBbRwEH/4mGQn04/QFGomLi8TXWIdv9WJk//KgGm+aDxVIr1wA==",
- "requires": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/react-primitive": "1.0.2"
- },
- "dependencies": {
- "@radix-ui/react-primitive": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.2.tgz",
- "integrity": "sha512-zY6G5Qq4R8diFPNwtyoLRZBxzu1Z+SXMlfYpChN7Dv8gvmx9X3qhDqiLWvKseKVJMuedFeU/Sa0Sy/Ia+t06Dw==",
- "requires": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/react-slot": "1.0.1"
- }
- },
- "@radix-ui/react-slot": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.1.tgz",
- "integrity": "sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==",
- "requires": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/react-compose-refs": "1.0.0"
- }
- }
- }
- },
- "@radix-ui/react-collection": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.0.2.tgz",
- "integrity": "sha512-s8WdQQ6wNXpaxdZ308KSr8fEWGrg4un8i4r/w7fhiS4ElRNjk5rRcl0/C6TANG2LvLOGIxtzo/jAg6Qf73TEBw==",
- "requires": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/react-compose-refs": "1.0.0",
- "@radix-ui/react-context": "1.0.0",
- "@radix-ui/react-primitive": "1.0.2",
- "@radix-ui/react-slot": "1.0.1"
- },
- "dependencies": {
- "@radix-ui/react-primitive": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.2.tgz",
- "integrity": "sha512-zY6G5Qq4R8diFPNwtyoLRZBxzu1Z+SXMlfYpChN7Dv8gvmx9X3qhDqiLWvKseKVJMuedFeU/Sa0Sy/Ia+t06Dw==",
- "requires": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/react-slot": "1.0.1"
- }
- },
- "@radix-ui/react-slot": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.1.tgz",
- "integrity": "sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==",
- "requires": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/react-compose-refs": "1.0.0"
- }
- }
- }
- },
"@radix-ui/react-compose-refs": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz",
@@ -62136,14 +61640,6 @@
"react-remove-scroll": "2.5.4"
}
},
- "@radix-ui/react-direction": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.0.0.tgz",
- "integrity": "sha512-2HV05lGUgYcA6xgLQ4BKPDmtL+QbIZYH5fCOTAOOcJ5O0QbWS3i9lKaurLzliYUDhORI2Qr3pyjhJh44lKA3rQ==",
- "requires": {
- "@babel/runtime": "^7.13.10"
- }
- },
"@radix-ui/react-dismissable-layer": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.0.tgz",
@@ -62157,41 +61653,6 @@
"@radix-ui/react-use-escape-keydown": "1.0.0"
}
},
- "@radix-ui/react-dropdown-menu": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.0.4.tgz",
- "integrity": "sha512-y6AT9+MydyXcByivdK1+QpjWoKaC7MLjkS/cH1Q3keEyMvDkiY85m8o2Bi6+Z1PPUlCsMULopxagQOSfN0wahg==",
- "requires": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/primitive": "1.0.0",
- "@radix-ui/react-compose-refs": "1.0.0",
- "@radix-ui/react-context": "1.0.0",
- "@radix-ui/react-id": "1.0.0",
- "@radix-ui/react-menu": "2.0.4",
- "@radix-ui/react-primitive": "1.0.2",
- "@radix-ui/react-use-controllable-state": "1.0.0"
- },
- "dependencies": {
- "@radix-ui/react-primitive": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.2.tgz",
- "integrity": "sha512-zY6G5Qq4R8diFPNwtyoLRZBxzu1Z+SXMlfYpChN7Dv8gvmx9X3qhDqiLWvKseKVJMuedFeU/Sa0Sy/Ia+t06Dw==",
- "requires": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/react-slot": "1.0.1"
- }
- },
- "@radix-ui/react-slot": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.1.tgz",
- "integrity": "sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==",
- "requires": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/react-compose-refs": "1.0.0"
- }
- }
- }
- },
"@radix-ui/react-focus-guards": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.0.tgz",
@@ -62220,166 +61681,6 @@
"@radix-ui/react-use-layout-effect": "1.0.0"
}
},
- "@radix-ui/react-menu": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.0.4.tgz",
- "integrity": "sha512-mzKR47tZ1t193trEqlQoJvzY4u9vYfVH16ryBrVrCAGZzkgyWnMQYEZdUkM7y8ak9mrkKtJiqB47TlEnubeOFQ==",
- "requires": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/primitive": "1.0.0",
- "@radix-ui/react-collection": "1.0.2",
- "@radix-ui/react-compose-refs": "1.0.0",
- "@radix-ui/react-context": "1.0.0",
- "@radix-ui/react-direction": "1.0.0",
- "@radix-ui/react-dismissable-layer": "1.0.3",
- "@radix-ui/react-focus-guards": "1.0.0",
- "@radix-ui/react-focus-scope": "1.0.2",
- "@radix-ui/react-id": "1.0.0",
- "@radix-ui/react-popper": "1.1.1",
- "@radix-ui/react-portal": "1.0.2",
- "@radix-ui/react-presence": "1.0.0",
- "@radix-ui/react-primitive": "1.0.2",
- "@radix-ui/react-roving-focus": "1.0.3",
- "@radix-ui/react-slot": "1.0.1",
- "@radix-ui/react-use-callback-ref": "1.0.0",
- "aria-hidden": "^1.1.1",
- "react-remove-scroll": "2.5.5"
- },
- "dependencies": {
- "@radix-ui/react-dismissable-layer": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.3.tgz",
- "integrity": "sha512-nXZOvFjOuHS1ovumntGV7NNoLaEp9JEvTht3MBjP44NSW5hUKj/8OnfN3+8WmB+CEhN44XaGhpHoSsUIEl5P7Q==",
- "requires": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/primitive": "1.0.0",
- "@radix-ui/react-compose-refs": "1.0.0",
- "@radix-ui/react-primitive": "1.0.2",
- "@radix-ui/react-use-callback-ref": "1.0.0",
- "@radix-ui/react-use-escape-keydown": "1.0.2"
- }
- },
- "@radix-ui/react-focus-scope": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.2.tgz",
- "integrity": "sha512-spwXlNTfeIprt+kaEWE/qYuYT3ZAqJiAGjN/JgdvgVDTu8yc+HuX+WOWXrKliKnLnwck0F6JDkqIERncnih+4A==",
- "requires": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/react-compose-refs": "1.0.0",
- "@radix-ui/react-primitive": "1.0.2",
- "@radix-ui/react-use-callback-ref": "1.0.0"
- }
- },
- "@radix-ui/react-portal": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.2.tgz",
- "integrity": "sha512-swu32idoCW7KA2VEiUZGBSu9nB6qwGdV6k6HYhUoOo3M1FFpD+VgLzUqtt3mwL1ssz7r2x8MggpLSQach2Xy/Q==",
- "requires": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/react-primitive": "1.0.2"
- }
- },
- "@radix-ui/react-primitive": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.2.tgz",
- "integrity": "sha512-zY6G5Qq4R8diFPNwtyoLRZBxzu1Z+SXMlfYpChN7Dv8gvmx9X3qhDqiLWvKseKVJMuedFeU/Sa0Sy/Ia+t06Dw==",
- "requires": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/react-slot": "1.0.1"
- }
- },
- "@radix-ui/react-slot": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.1.tgz",
- "integrity": "sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==",
- "requires": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/react-compose-refs": "1.0.0"
- }
- },
- "@radix-ui/react-use-escape-keydown": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.2.tgz",
- "integrity": "sha512-DXGim3x74WgUv+iMNCF+cAo8xUHHeqvjx8zs7trKf+FkQKPQXLk2sX7Gx1ysH7Q76xCpZuxIJE7HLPxRE+Q+GA==",
- "requires": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/react-use-callback-ref": "1.0.0"
- }
- },
- "react-remove-scroll": {
- "version": "2.5.5",
- "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz",
- "integrity": "sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==",
- "requires": {
- "react-remove-scroll-bar": "^2.3.3",
- "react-style-singleton": "^2.2.1",
- "tslib": "^2.1.0",
- "use-callback-ref": "^1.3.0",
- "use-sidecar": "^1.1.2"
- }
- }
- }
- },
- "@radix-ui/react-popper": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.1.1.tgz",
- "integrity": "sha512-keYDcdMPNMjSC8zTsZ8wezUMiWM9Yj14wtF3s0PTIs9srnEPC9Kt2Gny1T3T81mmSeyDjZxsD9N5WCwNNb712w==",
- "requires": {
- "@babel/runtime": "^7.13.10",
- "@floating-ui/react-dom": "0.7.2",
- "@radix-ui/react-arrow": "1.0.2",
- "@radix-ui/react-compose-refs": "1.0.0",
- "@radix-ui/react-context": "1.0.0",
- "@radix-ui/react-primitive": "1.0.2",
- "@radix-ui/react-use-callback-ref": "1.0.0",
- "@radix-ui/react-use-layout-effect": "1.0.0",
- "@radix-ui/react-use-rect": "1.0.0",
- "@radix-ui/react-use-size": "1.0.0",
- "@radix-ui/rect": "1.0.0"
- },
- "dependencies": {
- "@floating-ui/core": {
- "version": "0.7.3",
- "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-0.7.3.tgz",
- "integrity": "sha512-buc8BXHmG9l82+OQXOFU3Kr2XQx9ys01U/Q9HMIrZ300iLc8HLMgh7dcCqgYzAzf4BkoQvDcXf5Y+CuEZ5JBYg=="
- },
- "@floating-ui/dom": {
- "version": "0.5.4",
- "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-0.5.4.tgz",
- "integrity": "sha512-419BMceRLq0RrmTSDxn8hf9R3VCJv2K9PUfugh5JyEFmdjzDo+e8U5EdR8nzKq8Yj1htzLm3b6eQEEam3/rrtg==",
- "requires": {
- "@floating-ui/core": "^0.7.3"
- }
- },
- "@floating-ui/react-dom": {
- "version": "0.7.2",
- "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-0.7.2.tgz",
- "integrity": "sha512-1T0sJcpHgX/u4I1OzIEhlcrvkUN8ln39nz7fMoE/2HDHrPiMFoOGR7++GYyfUmIQHkkrTinaeQsO3XWubjSvGg==",
- "requires": {
- "@floating-ui/dom": "^0.5.3",
- "use-isomorphic-layout-effect": "^1.1.1"
- }
- },
- "@radix-ui/react-primitive": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.2.tgz",
- "integrity": "sha512-zY6G5Qq4R8diFPNwtyoLRZBxzu1Z+SXMlfYpChN7Dv8gvmx9X3qhDqiLWvKseKVJMuedFeU/Sa0Sy/Ia+t06Dw==",
- "requires": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/react-slot": "1.0.1"
- }
- },
- "@radix-ui/react-slot": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.1.tgz",
- "integrity": "sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==",
- "requires": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/react-compose-refs": "1.0.0"
- }
- }
- }
- },
"@radix-ui/react-portal": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.0.tgz",
@@ -62408,43 +61709,6 @@
"@radix-ui/react-slot": "1.0.0"
}
},
- "@radix-ui/react-roving-focus": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.3.tgz",
- "integrity": "sha512-stjCkIoMe6h+1fWtXlA6cRfikdBzCLp3SnVk7c48cv/uy3DTGoXhN76YaOYUJuy3aEDvDIKwKR5KSmvrtPvQPQ==",
- "requires": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/primitive": "1.0.0",
- "@radix-ui/react-collection": "1.0.2",
- "@radix-ui/react-compose-refs": "1.0.0",
- "@radix-ui/react-context": "1.0.0",
- "@radix-ui/react-direction": "1.0.0",
- "@radix-ui/react-id": "1.0.0",
- "@radix-ui/react-primitive": "1.0.2",
- "@radix-ui/react-use-callback-ref": "1.0.0",
- "@radix-ui/react-use-controllable-state": "1.0.0"
- },
- "dependencies": {
- "@radix-ui/react-primitive": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.2.tgz",
- "integrity": "sha512-zY6G5Qq4R8diFPNwtyoLRZBxzu1Z+SXMlfYpChN7Dv8gvmx9X3qhDqiLWvKseKVJMuedFeU/Sa0Sy/Ia+t06Dw==",
- "requires": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/react-slot": "1.0.1"
- }
- },
- "@radix-ui/react-slot": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.1.tgz",
- "integrity": "sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==",
- "requires": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/react-compose-refs": "1.0.0"
- }
- }
- }
- },
"@radix-ui/react-select": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-1.2.2.tgz",
@@ -62771,24 +62035,6 @@
"@babel/runtime": "^7.13.10"
}
},
- "@radix-ui/react-use-rect": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.0.0.tgz",
- "integrity": "sha512-TB7pID8NRMEHxb/qQJpvSt3hQU4sqNPM1VCTjTRjEOa7cEop/QMuq8S6fb/5Tsz64kqSvB9WnwsDHtjnrM9qew==",
- "requires": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/rect": "1.0.0"
- }
- },
- "@radix-ui/react-use-size": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.0.0.tgz",
- "integrity": "sha512-imZ3aYcoYCKhhgNpkNDh/aTiU05qw9hX+HHI1QDBTyIlcFjgeFlKKySNGMwTp7nYFLQg/j0VA2FmCY4WPDDHMg==",
- "requires": {
- "@babel/runtime": "^7.13.10",
- "@radix-ui/react-use-layout-effect": "1.0.0"
- }
- },
"@radix-ui/react-visually-hidden": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.0.3.tgz",
@@ -62830,14 +62076,6 @@
}
}
},
- "@radix-ui/rect": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.0.0.tgz",
- "integrity": "sha512-d0O68AYy/9oeEy1DdC07bz1/ZXX+DqCskRd3i4JzLSTXwefzaepQrKjXC7aNM8lTHjFLDO0pDgaEiQ7jEk+HVg==",
- "requires": {
- "@babel/runtime": "^7.13.10"
- }
- },
"@react-native-clipboard/clipboard": {
"version": "1.11.2",
"resolved": "https://registry.npmjs.org/@react-native-clipboard/clipboard/-/clipboard-1.11.2.tgz",
@@ -70161,7 +69399,6 @@
"@emotion/styled": "^11.6.0",
"@emotion/utils": "^1.0.0",
"@floating-ui/react-dom": "^2.0.1",
- "@radix-ui/react-dropdown-menu": "2.0.4",
"@types/gradient-parser": "0.1.3",
"@types/highlight-words-core": "1.2.1",
"@use-gesture/react": "^10.2.24",
@@ -96891,11 +96128,6 @@
"tslib": "^2.0.0"
}
},
- "use-isomorphic-layout-effect": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz",
- "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA=="
- },
"use-latest-callback": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/use-latest-callback/-/use-latest-callback-0.1.6.tgz",
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index ecce61cb91509c..a0a0b36eeb1e28 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -21,6 +21,7 @@
### Experimental
+- `DropdownMenuV2`: remove temporary radix UI based implementation ([#55626](https://github.com/WordPress/gutenberg/pull/55626)).
- `Tabs`: do not render hidden content ([#57046](https://github.com/WordPress/gutenberg/pull/57046)).
- `Tabs`: improve hover and text alignment styles ([#57275](https://github.com/WordPress/gutenberg/pull/57275)).
- `Tabs`: make sure `Tab`s are associated to the right `TabPanel`s, regardless of the order they're rendered in ([#57033](https://github.com/WordPress/gutenberg/pull/57033)).
diff --git a/packages/components/package.json b/packages/components/package.json
index b7581679d90947..79e961dc64da43 100644
--- a/packages/components/package.json
+++ b/packages/components/package.json
@@ -39,7 +39,6 @@
"@emotion/styled": "^11.6.0",
"@emotion/utils": "^1.0.0",
"@floating-ui/react-dom": "^2.0.1",
- "@radix-ui/react-dropdown-menu": "2.0.4",
"@types/gradient-parser": "0.1.3",
"@types/highlight-words-core": "1.2.1",
"@use-gesture/react": "^10.2.24",
diff --git a/packages/components/src/dropdown-menu-v2-ariakit/README.md b/packages/components/src/dropdown-menu-v2-ariakit/README.md
deleted file mode 100644
index 2902b541169766..00000000000000
--- a/packages/components/src/dropdown-menu-v2-ariakit/README.md
+++ /dev/null
@@ -1,331 +0,0 @@
-# `DropdownMenu` (v2)
-
-
-This feature is still experimental. “Experimental” means this is an early implementation subject to drastic and breaking changes.
-
-
-`DropdownMenu` displays a menu to the user (such as a set of actions or functions) triggered by a button.
-
-
-## Design guidelines
-
-### Usage
-
-#### When to use a DropdownMenu
-
-Use a DropdownMenu when you want users to:
-
-- Choose an action or change a setting from a list, AND
-- Only see the available choices contextually.
-
-`DropdownMenu` is a React component to render an expandable menu of buttons. It is similar in purpose to a `` element, with the distinction that it does not maintain a value. Instead, each option behaves as an action button.
-
-If you need to display all the available options at all times, consider using a Toolbar instead. Use a `DropdownMenu` to display a list of actions after the user interacts with a button.
-
-**Do**
-Use a `DropdownMenu` to display a list of actions after the user interacts with an icon.
-
-**Don’t** use a `DropdownMenu` for important actions that should always be visible. Use a `Toolbar` instead.
-
-**Don’t**
-Don’t use a `DropdownMenu` for frequently used actions. Use a `Toolbar` instead.
-
-#### Behavior
-
-Generally, the parent button should indicate that interacting with it will show a `DropdownMenu`.
-
-The parent button should retain the same visual styling regardless of whether the `DropdownMenu` is displayed or not.
-
-#### Placement
-
-The `DropdownMenu` should typically appear directly below, or below and to the left of, the parent button. If there isn’t enough space below to display the full `DropdownMenu`, it can be displayed instead above the parent button.
-
-## Development guidelines
-
-This component is still highly experimental, and it's not normally accessible to consumers of the `@wordpress/components` package.
-
-The component exposes a set of components that are meant to be used in combination with each other in order to implement a `DropdownMenu` correctly.
-
-### `DropdownMenu`
-
-The root component, used to specify the menu's trigger and its contents.
-
-#### Props
-
-The component accepts the following props:
-
-##### `trigger`: `React.ReactNode`
-
-The trigger button
-
-- Required: yes
-
-##### `children`: `React.ReactNode`
-
-The contents of the dropdown
-
-- Required: yes
-
-##### `defaultOpen`: `boolean`
-
-The open state of the dropdown menu when it is initially rendered. Use when not wanting to control its open state.
-
-- Required: no
-- Default: `false`
-
-##### `open`: `boolean`
-
-The controlled open state of the dropdown menu. Must be used in conjunction with `onOpenChange`.
-
-- Required: no
-
-##### `onOpenChange`: `(open: boolean) => void`
-
-Event handler called when the open state of the dropdown menu changes.
-
-- Required: no
-
-##### `modal`: `boolean`
-
-The modality of the dropdown menu. When set to true, interaction with outside elements will be disabled and only menu content will be visible to screen readers.
-
-- Required: no
-- Default: `true`
-
-##### `placement`: ``'top' | 'top-start' | 'top-end' | 'right' | 'right-start' | 'right-end' | 'bottom' | 'bottom-start' | 'bottom-end' | 'left' | 'left-start' | 'left-end'`
-
-The placement of the dropdown menu popover.
-
-- Required: no
-- Default: `'bottom-start'` for root-level menus, `'right-start'` for nested menus
-
-##### `gutter`: `number`
-
-The distance in pixels from the trigger.
-
-- Required: no
-- Default: `8` for root-level menus, `16` for nested menus
-
-##### `shift`: `number`
-
-The skidding of the popover along the anchor element. Can be set to negative values to make the popover shift to the opposite side.
-
-- Required: no
-- Default: `0` for root-level menus, `-8` for nested menus
-
-### `DropdownMenuItem`
-
-Used to render a menu item.
-
-#### Props
-
-The component accepts the following props:
-
-##### `children`: `React.ReactNode`
-
-The contents of the item
-
-- Required: yes
-
-##### `prefix`: `React.ReactNode`
-
-The contents of the item's prefix.
-
-- Required: no
-
-##### `suffix`: `React.ReactNode`
-
-The contents of the item's suffix.
-
-- Required: no
-
-##### `hideOnClick`: `boolean`
-
-Whether to hide the dropdown menu when the menu item is clicked.
-
-- Required: no
-- Default: `true`
-
-##### `disabled`: `boolean`
-
-Determines if the element is disabled.
-
-- Required: no
-- Default: `false`
-
-### `DropdownMenuCheckboxItem`
-
-Used to render a checkbox item.
-
-#### Props
-
-The component accepts the following props:
-
-##### `children`: `React.ReactNode`
-
-The contents of the item
-
-- Required: yes
-
-##### `suffix`: `React.ReactNode`
-
-The contents of the item's suffix.
-
-- Required: no
-
-##### `hideOnClick`: `boolean`
-
-Whether to hide the dropdown menu when the menu item is clicked.
-
-- Required: no
-- Default: `false`
-
-##### `disabled`: `boolean`
-
-Determines if the element is disabled.
-
-- Required: no
-- Default: `false`
-
-##### `name`: `string`
-
-The checkbox item's name.
-
-- Required: yes
-
-##### `value`: `string`
-
-The checkbox item's value, useful when using multiple checkbox items
- associated to the same `name`.
-
-- Required: no
-
-##### `checked`: `boolean`
-
-The checkbox item's value, useful when using multiple checkbox items
- associated to the same `name`.
-
-- Required: no
-
-##### `defaultChecked`: `boolean`
-
-The checked state of the checkbox menu item when it is initially rendered. Use when not wanting to control its checked state.
-
-- Required: no
-
-##### `onChange`: `( event: React.ChangeEvent< HTMLInputElement > ) => void;`
-
-Event handler called when the checked state of the checkbox menu item changes.
-
-- Required: no
-
-### `DropdownMenuRadioItem`
-
-Used to render a radio item.
-
-#### Props
-
-The component accepts the following props:
-
-##### `children`: `React.ReactNode`
-
-The contents of the item
-
-- Required: yes
-
-##### `suffix`: `React.ReactNode`
-
-The contents of the item's suffix.
-
-- Required: no
-
-##### `hideOnClick`: `boolean`
-
-Whether to hide the dropdown menu when the menu item is clicked.
-
-- Required: no
-- Default: `false`
-
-##### `disabled`: `boolean`
-
-Determines if the element is disabled.
-
-- Required: no
-- Default: `false`
-
-##### `name`: `string`
-
-The radio item's name.
-
-- Required: yes
-
-##### `value`: `string | number`
-
-The radio item's value.
-
-- Required: yes
-
-##### `checked`: `boolean`
-
-The checkbox item's value, useful when using multiple checkbox items
- associated to the same `name`.
-
-- Required: no
-
-##### `defaultChecked`: `boolean`
-
-The checked state of the radio menu item when it is initially rendered. Use when not wanting to control its checked state.
-
-- Required: no
-
-##### `onChange`: `( event: React.ChangeEvent< HTMLInputElement > ) => void;`
-
-Event handler called when the checked radio menu item changes.
-
-- Required: no
-
-### `DropdownMenuItemLabel`
-
-Used to render the menu item's label.
-
-#### Props
-
-The component accepts the following props:
-
-##### `children`: `React.ReactNode`
-
-The label contents.
-
-- Required: yes
-
-### `DropdownMenuItemHelpText`
-
-Used to render the menu item's help text.
-
-#### Props
-
-The component accepts the following props:
-
-##### `children`: `React.ReactNode`
-
-The help text contents.
-
-- Required: yes
-
-### `DropdownMenuGroup`
-
-Used to group menu items.
-
-#### Props
-
-The component accepts the following props:
-
-##### `children`: `React.ReactNode`
-
-The contents of the group.
-
-- Required: yes
-
-### `DropdownMenuSeparatorProps`
-
-Used to render a visual separator.
diff --git a/packages/components/src/dropdown-menu-v2-ariakit/index.tsx b/packages/components/src/dropdown-menu-v2-ariakit/index.tsx
deleted file mode 100644
index 37d4a1f9cfcc5e..00000000000000
--- a/packages/components/src/dropdown-menu-v2-ariakit/index.tsx
+++ /dev/null
@@ -1,383 +0,0 @@
-/**
- * External dependencies
- */
-// eslint-disable-next-line no-restricted-imports
-import * as Ariakit from '@ariakit/react';
-
-/**
- * WordPress dependencies
- */
-import {
- forwardRef,
- createContext,
- useContext,
- useMemo,
- cloneElement,
- isValidElement,
- useCallback,
-} from '@wordpress/element';
-import { isRTL } from '@wordpress/i18n';
-import { check, chevronRightSmall } from '@wordpress/icons';
-import { SVG, Circle } from '@wordpress/primitives';
-
-/**
- * Internal dependencies
- */
-import { useContextSystem, contextConnect } from '../context';
-import type { WordPressComponentProps } from '../context';
-import Icon from '../icon';
-import type {
- DropdownMenuContext as DropdownMenuContextType,
- DropdownMenuProps,
- DropdownMenuGroupProps,
- DropdownMenuItemProps,
- DropdownMenuCheckboxItemProps,
- DropdownMenuRadioItemProps,
- DropdownMenuSeparatorProps,
-} from './types';
-import * as Styled from './styles';
-
-export const DropdownMenuContext = createContext<
- DropdownMenuContextType | undefined
->( undefined );
-
-export const DropdownMenuItem = forwardRef<
- HTMLDivElement,
- WordPressComponentProps< DropdownMenuItemProps, 'div', false >
->( function DropdownMenuItem(
- { prefix, suffix, children, hideOnClick = true, ...props },
- ref
-) {
- const dropdownMenuContext = useContext( DropdownMenuContext );
-
- return (
-
- { prefix }
-
-
-
- { children }
-
-
- { suffix && (
-
- { suffix }
-
- ) }
-
-
- );
-} );
-
-export const DropdownMenuCheckboxItem = forwardRef<
- HTMLDivElement,
- WordPressComponentProps< DropdownMenuCheckboxItemProps, 'div', false >
->( function DropdownMenuCheckboxItem(
- { suffix, children, hideOnClick = false, ...props },
- ref
-) {
- const dropdownMenuContext = useContext( DropdownMenuContext );
-
- return (
-
- }
- // Override some ariakit inline styles
- style={ { width: 'auto', height: 'auto' } }
- >
-
-
-
-
-
- { children }
-
-
- { suffix && (
-
- { suffix }
-
- ) }
-
-
- );
-} );
-
-const radioCheck = (
-
-
-
-);
-
-export const DropdownMenuRadioItem = forwardRef<
- HTMLDivElement,
- WordPressComponentProps< DropdownMenuRadioItemProps, 'div', false >
->( function DropdownMenuRadioItem(
- { suffix, children, hideOnClick = false, ...props },
- ref
-) {
- const dropdownMenuContext = useContext( DropdownMenuContext );
-
- return (
-
- }
- // Override some ariakit inline styles
- style={ { width: 'auto', height: 'auto' } }
- >
-
-
-
-
-
- { children }
-
-
- { suffix && (
-
- { suffix }
-
- ) }
-
-
- );
-} );
-
-export const DropdownMenuGroup = forwardRef<
- HTMLDivElement,
- WordPressComponentProps< DropdownMenuGroupProps, 'div', false >
->( function DropdownMenuGroup( props, ref ) {
- const dropdownMenuContext = useContext( DropdownMenuContext );
- return (
-
- );
-} );
-
-const UnconnectedDropdownMenu = (
- props: WordPressComponentProps< DropdownMenuProps, 'div', false >,
- ref: React.ForwardedRef< HTMLDivElement >
-) => {
- const {
- // Store props
- open,
- defaultOpen = false,
- onOpenChange,
- placement,
-
- // Menu trigger props
- trigger,
-
- // Menu props
- gutter,
- children,
- shift,
- modal = true,
-
- // From internal components context
- variant,
-
- // Rest
- ...otherProps
- } = useContextSystem<
- typeof props & Pick< DropdownMenuContextType, 'variant' >
- >( props, 'DropdownMenu' );
-
- const parentContext = useContext( DropdownMenuContext );
-
- const computedDirection = isRTL() ? 'rtl' : 'ltr';
-
- // If an explicit value for the `placement` prop is not passed,
- // apply a default placement of `bottom-start` for the root dropdown,
- // and of `right-start` for nested dropdowns.
- let computedPlacement =
- props.placement ??
- ( parentContext?.store ? 'right-start' : 'bottom-start' );
- // Swap left/right in case of RTL direction
- if ( computedDirection === 'rtl' ) {
- if ( /right/.test( computedPlacement ) ) {
- computedPlacement = computedPlacement.replace(
- 'right',
- 'left'
- ) as typeof computedPlacement;
- } else if ( /left/.test( computedPlacement ) ) {
- computedPlacement = computedPlacement.replace(
- 'left',
- 'right'
- ) as typeof computedPlacement;
- }
- }
-
- const dropdownMenuStore = Ariakit.useMenuStore( {
- parent: parentContext?.store,
- open,
- defaultOpen,
- placement: computedPlacement,
- focusLoop: true,
- setOpen( willBeOpen ) {
- onOpenChange?.( willBeOpen );
- },
- rtl: computedDirection === 'rtl',
- } );
-
- const contextValue = useMemo(
- () => ( { store: dropdownMenuStore, variant } ),
- [ dropdownMenuStore, variant ]
- );
-
- // Extract the side from the applied placement — useful for animations.
- const appliedPlacementSide = dropdownMenuStore
- .useState( 'placement' )
- .split( '-' )[ 0 ];
-
- if (
- dropdownMenuStore.parent &&
- ! ( isValidElement( trigger ) && DropdownMenuItem === trigger.type )
- ) {
- // eslint-disable-next-line no-console
- console.warn(
- 'For nested DropdownMenus, the `trigger` should always be a `DropdownMenuItem`.'
- );
- }
-
- const hideOnEscape = useCallback(
- ( event: React.KeyboardEvent< Element > ) => {
- // Pressing Escape can cause unexpected consequences (ie. exiting
- // full screen mode on MacOs, close parent modals...).
- event.preventDefault();
- // Returning `true` causes the menu to hide.
- return true;
- },
- []
- );
-
- const wrapperProps = useMemo(
- () => ( {
- dir: computedDirection,
- style: {
- direction:
- computedDirection as React.CSSProperties[ 'direction' ],
- },
- } ),
- [ computedDirection ]
- );
-
- return (
- <>
- { /* Menu trigger */ }
-
- { trigger.props.suffix }
-
- >
- ),
- } )
- : trigger
- }
- />
-
- { /* Menu popover */ }
-
-
- { children }
-
-
- >
- );
-};
-export const DropdownMenu = contextConnect(
- UnconnectedDropdownMenu,
- 'DropdownMenu'
-);
-
-export const DropdownMenuSeparator = forwardRef<
- HTMLHRElement,
- WordPressComponentProps< DropdownMenuSeparatorProps, 'hr', false >
->( function DropdownMenuSeparator( props, ref ) {
- const dropdownMenuContext = useContext( DropdownMenuContext );
- return (
-
- );
-} );
-
-export const DropdownMenuItemLabel = forwardRef<
- HTMLSpanElement,
- WordPressComponentProps< { children: React.ReactNode }, 'span', true >
->( function DropdownMenuItemLabel( props, ref ) {
- return (
-
- );
-} );
-
-export const DropdownMenuItemHelpText = forwardRef<
- HTMLSpanElement,
- WordPressComponentProps< { children: React.ReactNode }, 'span', true >
->( function DropdownMenuItemHelpText( props, ref ) {
- return (
-
- );
-} );
diff --git a/packages/components/src/dropdown-menu-v2-ariakit/stories/index.story.tsx b/packages/components/src/dropdown-menu-v2-ariakit/stories/index.story.tsx
deleted file mode 100644
index fcc8f76294bc6b..00000000000000
--- a/packages/components/src/dropdown-menu-v2-ariakit/stories/index.story.tsx
+++ /dev/null
@@ -1,617 +0,0 @@
-/**
- * External dependencies
- */
-import type { Meta, StoryFn } from '@storybook/react';
-import { css } from '@emotion/react';
-
-/**
- * WordPress dependencies
- */
-import { customLink, formatCapitalize } from '@wordpress/icons';
-import { useState, useMemo, useContext } from '@wordpress/element';
-
-/**
- * Internal dependencies
- */
-import { useCx } from '../../utils';
-import {
- DropdownMenu,
- DropdownMenuItem,
- DropdownMenuCheckboxItem,
- DropdownMenuGroup,
- DropdownMenuSeparator,
- DropdownMenuContext,
- DropdownMenuRadioItem,
- DropdownMenuItemLabel,
- DropdownMenuItemHelpText,
-} from '..';
-import Icon from '../../icon';
-import Button from '../../button';
-import Modal from '../../modal';
-import { createSlotFill, Provider as SlotFillProvider } from '../../slot-fill';
-import { ContextSystemProvider } from '../../context';
-
-const meta: Meta< typeof DropdownMenu > = {
- title: 'Components (Experimental)/DropdownMenu v2 ariakit',
- component: DropdownMenu,
- subcomponents: {
- // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170
- DropdownMenuItem,
- // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170
- DropdownMenuCheckboxItem,
- // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170
- DropdownMenuGroup,
- // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170
- DropdownMenuSeparator,
- // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170
- DropdownMenuContext,
- // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170
- DropdownMenuRadioItem,
- // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170
- DropdownMenuItemLabel,
- // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170
- DropdownMenuItemHelpText,
- },
- argTypes: {
- children: { control: { type: null } },
- trigger: { control: { type: null } },
- },
- parameters: {
- actions: { argTypesRegex: '^on.*' },
- controls: { expanded: true },
- docs: {
- canvas: { sourceState: 'shown' },
- source: { excludeDecorators: true },
- },
- },
- decorators: [
- // Layout wrapper
- ( Story ) => (
-
-
-
- ),
- ],
-};
-export default meta;
-
-export const Default: StoryFn< typeof DropdownMenu > = ( props ) => (
-
-
- Label
-
-
- Label
- Help text
-
-
- Label
-
- The menu item help text is automatically truncated when there
- are more than two lines of text
-
-
-
- Label
-
- This item doesn't close the menu on click
-
-
- Disabled item
-
-
- }
- >
- With prefix
-
- With suffix
- }
- suffix="⌥⌘T"
- >
-
- Disabled with prefix and suffix
-
-
- And help text
-
-
-
-
-);
-Default.args = {
- trigger: (
-
- Open menu
-
- ),
-};
-
-export const WithSubmenu: StoryFn< typeof DropdownMenu > = ( props ) => (
-
- Level 1 item
-
-
- Submenu trigger item with a long label
-
-
- }
- >
-
- Level 2 item
-
-
- Level 2 item
-
-
-
- Submenu trigger
-
-
- }
- >
-
- Level 3 item
-
-
- Level 3 item
-
-
-
-
-);
-WithSubmenu.args = {
- ...Default.args,
-};
-
-export const WithCheckboxes: StoryFn< typeof DropdownMenu > = ( props ) => {
- const [ isAChecked, setAChecked ] = useState( false );
- const [ isBChecked, setBChecked ] = useState( true );
- const [ multipleCheckboxesValue, setMultipleCheckboxesValue ] = useState<
- string[]
- >( [ 'b' ] );
-
- const onMultipleCheckboxesCheckedChange: React.ComponentProps<
- typeof DropdownMenuCheckboxItem
- >[ 'onChange' ] = ( e ) => {
- setMultipleCheckboxesValue( ( prevValues ) => {
- if ( prevValues.includes( e.target.value ) ) {
- return prevValues.filter( ( val ) => val !== e.target.value );
- }
- return [ ...prevValues, e.target.value ];
- } );
- };
-
- return (
-
-
-
-
- Checkbox item A
-
-
- Uncontrolled
-
-
-
-
- Checkbox item B
-
-
- Uncontrolled, initially checked
-
-
-
-
-
- setAChecked( e.target.checked ) }
- >
-
- Checkbox item A
-
-
- Controlled
-
-
- setBChecked( e.target.checked ) }
- >
-
- Checkbox item B
-
-
- Controlled, initially checked
-
-
-
-
-
-
-
- Checkbox item A
-
-
- Uncontrolled, multiple selection
-
-
-
-
- Checkbox item B
-
-
- Uncontrolled, multiple selection, initially checked
-
-
-
-
-
-
-
- Checkbox item A
-
-
- Controlled, multiple selection
-
-
-
-
- Checkbox item B
-
-
- Controlled, multiple selection, initially checked
-
-
-
-
- );
-};
-WithCheckboxes.args = {
- ...Default.args,
-};
-
-export const WithRadios: StoryFn< typeof DropdownMenu > = ( props ) => {
- const [ radioValue, setRadioValue ] = useState( 'two' );
- const onRadioChange: React.ComponentProps<
- typeof DropdownMenuRadioItem
- >[ 'onChange' ] = ( e ) => setRadioValue( e.target.value );
-
- return (
-
-
-
- Radio item 1
-
- Uncontrolled
-
-
-
- Radio item 2
-
- Uncontrolled, initially checked
-
-
-
-
-
-
- Radio item 1
-
- Controlled
-
-
-
- Radio item 2
-
- Controlled, initially checked
-
-
-
-
- );
-};
-WithRadios.args = {
- ...Default.args,
-};
-
-const modalOnTopOfDropdown = css`
- && {
- z-index: 1000000;
- }
-`;
-
-// For more examples with `Modal`, check https://ariakit.org/examples/menu-wordpress-modal
-export const WithModals: StoryFn< typeof DropdownMenu > = ( props ) => {
- const [ isOuterModalOpen, setOuterModalOpen ] = useState( false );
- const [ isInnerModalOpen, setInnerModalOpen ] = useState( false );
-
- const cx = useCx();
- const modalOverlayClassName = cx( modalOnTopOfDropdown );
-
- return (
- <>
-
- setOuterModalOpen( true ) }
- hideOnClick={ false }
- >
-
- Open outer modal
-
-
- setInnerModalOpen( true ) }
- hideOnClick={ false }
- >
-
- Open inner modal
-
-
- { isInnerModalOpen && (
- setInnerModalOpen( false ) }
- overlayClassName={ modalOverlayClassName }
- >
- Modal's contents
- setInnerModalOpen( false ) }>
- Close
-
-
- ) }
-
- { isOuterModalOpen && (
- setOuterModalOpen( false ) }
- overlayClassName={ modalOverlayClassName }
- >
- Modal's contents
- setOuterModalOpen( false ) }>
- Close
-
-
- ) }
- >
- );
-};
-WithModals.args = {
- ...Default.args,
-};
-
-const ExampleSlotFill = createSlotFill( 'Example' );
-
-const Slot = () => {
- const dropdownMenuContext = useContext( DropdownMenuContext );
-
- // Forwarding the content of the slot so that it can be used by the fill
- const fillProps = useMemo(
- () => ( {
- forwardedContext: [
- [
- DropdownMenuContext.Provider,
- { value: dropdownMenuContext },
- ],
- ],
- } ),
- [ dropdownMenuContext ]
- );
-
- return (
-
- );
-};
-
-type ForwardedContextTuple< P = {} > = [
- React.ComponentType< React.PropsWithChildren< P > >,
- P,
-];
-
-const Fill = ( { children }: { children: React.ReactNode } ) => {
- const innerMarkup = <>{ children }>;
-
- return (
-
- { ( fillProps: { forwardedContext?: ForwardedContextTuple[] } ) => {
- const { forwardedContext = [] } = fillProps;
-
- return forwardedContext.reduce(
- ( inner: JSX.Element, [ Provider, props ] ) => (
- { inner }
- ),
- innerMarkup
- );
- } }
-
- );
-};
-
-export const WithSlotFill: StoryFn< typeof DropdownMenu > = ( props ) => {
- return (
-
-
-
- Item
-
-
-
-
-
-
-
- Item from fill
-
-
-
-
- Submenu from fill
-
-
- }
- >
-
-
- Submenu item from fill
-
-
-
-
-
- );
-};
-WithSlotFill.args = {
- ...Default.args,
-};
-
-const toolbarVariantContextValue = {
- DropdownMenu: {
- variant: 'toolbar',
- },
-};
-export const ToolbarVariant: StoryFn< typeof DropdownMenu > = ( props ) => (
- // TODO: add toolbar
-
-
-
- Level 1 item
-
-
- Level 1 item
-
-
-
-
- Submenu trigger
-
-
- }
- >
-
- Level 2 item
-
-
-
-
-);
-ToolbarVariant.args = {
- ...Default.args,
-};
-
-export const InsideModal: StoryFn< typeof DropdownMenu > = ( props ) => {
- const [ isModalOpen, setModalOpen ] = useState( false );
- return (
- <>
- setModalOpen( true ) }
- __next40pxDefaultSize
- variant="secondary"
- >
- Open modal
-
- { isModalOpen && (
- setModalOpen( false ) }>
-
-
-
- Level 1 item
-
-
-
-
- Level 1 item
-
-
-
-
-
- Submenu trigger
-
-
- }
- >
-
-
- Level 2 item
-
-
-
-
- setModalOpen( false ) }>
- Close modal
-
-
- ) }
- >
- );
-};
-InsideModal.args = {
- ...Default.args,
-};
-InsideModal.parameters = {
- docs: {
- source: { type: 'code' },
- },
-};
diff --git a/packages/components/src/dropdown-menu-v2-ariakit/styles.ts b/packages/components/src/dropdown-menu-v2-ariakit/styles.ts
deleted file mode 100644
index ec6b2cb74d2172..00000000000000
--- a/packages/components/src/dropdown-menu-v2-ariakit/styles.ts
+++ /dev/null
@@ -1,346 +0,0 @@
-/**
- * External dependencies
- */
-// eslint-disable-next-line no-restricted-imports
-import * as Ariakit from '@ariakit/react';
-import { css, keyframes } from '@emotion/react';
-import styled from '@emotion/styled';
-
-/**
- * Internal dependencies
- */
-import { COLORS, font, rtl, CONFIG } from '../utils';
-import { space } from '../utils/space';
-import Icon from '../icon';
-import { Truncate } from '../truncate';
-import type { DropdownMenuContext } from './types';
-
-const ANIMATION_PARAMS = {
- SLIDE_AMOUNT: '2px',
- DURATION: '400ms',
- EASING: 'cubic-bezier( 0.16, 1, 0.3, 1 )',
-};
-
-const CONTENT_WRAPPER_PADDING = space( 1 );
-const ITEM_PADDING_BLOCK = space( 2 );
-const ITEM_PADDING_INLINE = space( 3 );
-
-// TODO:
-// - those values are different from saved variables?
-// - should bring this into the config, and make themeable
-// - border color and divider color are different?
-const DEFAULT_BORDER_COLOR = COLORS.gray[ 300 ];
-const DIVIDER_COLOR = COLORS.gray[ 200 ];
-const TOOLBAR_VARIANT_BORDER_COLOR = COLORS.gray[ '900' ];
-const DEFAULT_BOX_SHADOW = `0 0 0 ${ CONFIG.borderWidth } ${ DEFAULT_BORDER_COLOR }, ${ CONFIG.popoverShadow }`;
-const TOOLBAR_VARIANT_BOX_SHADOW = `0 0 0 ${ CONFIG.borderWidth } ${ TOOLBAR_VARIANT_BORDER_COLOR }`;
-
-const slideUpAndFade = keyframes( {
- '0%': {
- opacity: 0,
- transform: `translateY(${ ANIMATION_PARAMS.SLIDE_AMOUNT })`,
- },
- '100%': { opacity: 1, transform: 'translateY(0)' },
-} );
-
-const slideRightAndFade = keyframes( {
- '0%': {
- opacity: 0,
- transform: `translateX(-${ ANIMATION_PARAMS.SLIDE_AMOUNT })`,
- },
- '100%': { opacity: 1, transform: 'translateX(0)' },
-} );
-
-const slideDownAndFade = keyframes( {
- '0%': {
- opacity: 0,
- transform: `translateY(-${ ANIMATION_PARAMS.SLIDE_AMOUNT })`,
- },
- '100%': { opacity: 1, transform: 'translateY(0)' },
-} );
-
-const slideLeftAndFade = keyframes( {
- '0%': {
- opacity: 0,
- transform: `translateX(${ ANIMATION_PARAMS.SLIDE_AMOUNT })`,
- },
- '100%': { opacity: 1, transform: 'translateX(0)' },
-} );
-
-export const DropdownMenu = styled( Ariakit.Menu )<
- Pick< DropdownMenuContext, 'variant' >
->`
- position: relative;
- /* Same as popover component */
- /* TODO: is there a way to read the sass variable? */
- z-index: 1000000;
-
- display: grid;
- grid-template-columns: minmax( 0, max-content ) 1fr;
- grid-template-rows: auto;
-
- box-sizing: border-box;
- min-width: 160px;
- max-width: 320px;
- max-height: var( --popover-available-height );
- padding: ${ CONTENT_WRAPPER_PADDING };
-
- background-color: ${ COLORS.ui.background };
- border-radius: 4px;
- ${ ( props ) => css`
- box-shadow: ${ props.variant === 'toolbar'
- ? TOOLBAR_VARIANT_BOX_SHADOW
- : DEFAULT_BOX_SHADOW };
- ` }
-
- overscroll-behavior: contain;
- overflow: auto;
-
- /* Only visible in Windows High Contrast mode */
- outline: 2px solid transparent !important;
-
- /* Animation */
- animation-duration: ${ ANIMATION_PARAMS.DURATION };
- animation-timing-function: ${ ANIMATION_PARAMS.EASING };
- will-change: transform, opacity;
- /* Default animation.*/
- animation-name: ${ slideDownAndFade };
-
- &[data-side='right'] {
- animation-name: ${ slideLeftAndFade };
- }
- &[data-side='bottom'] {
- animation-name: ${ slideUpAndFade };
- }
- &[data-side='left'] {
- animation-name: ${ slideRightAndFade };
- }
- @media ( prefers-reduced-motion ) {
- animation-duration: 0s;
- }
-`;
-
-const baseItem = css`
- all: unset;
-
- position: relative;
- min-height: ${ space( 10 ) };
- box-sizing: border-box;
-
- /* Occupy the width of all grid columns (ie. full width) */
- grid-column: 1 / -1;
-
- /*
- * Define a grid layout which inherits the same columns configuration
- * from the parent layout (ie. subgrid).
- */
- display: grid;
- grid-template-columns: subgrid;
- align-items: center;
-
- font-size: ${ font( 'default.fontSize' ) };
- font-family: inherit;
- font-weight: normal;
- line-height: 20px;
-
- color: ${ COLORS.gray[ 900 ] };
- border-radius: ${ CONFIG.radiusBlockUi };
-
- padding-block: ${ ITEM_PADDING_BLOCK };
- padding-inline: ${ ITEM_PADDING_INLINE };
-
- /*
- * Make sure that, when an item is scrolled into view (eg. while using the
- * keyboard to move focus), the whole item comes into view
- */
- scroll-margin: ${ CONTENT_WRAPPER_PADDING };
-
- user-select: none;
- outline: none;
-
- &[aria-disabled='true'] {
- color: ${ COLORS.ui.textDisabled };
- cursor: not-allowed;
- }
-
- /* Hover */
- &[data-active-item]:not( [data-focus-visible] ):not(
- [aria-disabled='true']
- ) {
- background-color: ${ COLORS.theme.accent };
- color: ${ COLORS.white };
- }
-
- /* Keyboard focus (focus-visible) */
- &[data-focus-visible] {
- box-shadow: 0 0 0 1.5px var( --wp-admin-theme-color );
-
- /* Only visible in Windows High Contrast mode */
- outline: 2px solid transparent;
- }
-
- /* Active (ie. pressed, mouse down) */
- &:active,
- &[data-active] {
- /* TODO: should there be a visual active state? */
- }
-
- /* When the item is the trigger of an open submenu */
- ${ DropdownMenu }:not(:focus) &:not(:focus)[aria-expanded="true"] {
- background-color: ${ COLORS.gray[ 100 ] };
- color: ${ COLORS.gray[ 900 ] };
- }
-
- svg {
- fill: currentColor;
- }
-`;
-
-export const DropdownMenuItem = styled( Ariakit.MenuItem )`
- ${ baseItem };
-`;
-
-export const DropdownMenuCheckboxItem = styled( Ariakit.MenuItemCheckbox )`
- ${ baseItem };
-`;
-
-export const DropdownMenuRadioItem = styled( Ariakit.MenuItemRadio )`
- ${ baseItem };
-`;
-
-export const ItemPrefixWrapper = styled.span`
- /* Always occupy the first column, even when auto-collapsing */
- grid-column: 1;
-
- /*
- * Even when the item is not checked, occupy the same screen space to avoid
- * the space collapside when no items are checked.
- */
- ${ DropdownMenuCheckboxItem } > &,
- ${ DropdownMenuRadioItem } > & {
- /* Same width as the check icons */
- min-width: ${ space( 6 ) };
- }
-
- ${ DropdownMenuCheckboxItem } > &,
- ${ DropdownMenuRadioItem } > &,
- &:not( :empty ) {
- margin-inline-end: ${ space( 2 ) };
- }
-
- display: flex;
- align-items: center;
- justify-content: center;
-
- color: ${ COLORS.gray[ '700' ] };
-
- /*
- * When the parent menu item is active, except when it's a non-focused/hovered
- * submenu trigger (in that case, color should not be inherited)
- */
- [data-active-item]:not( [data-focus-visible] ) > &,
- /* When the parent menu item is disabled */
- [aria-disabled='true'] > & {
- color: inherit;
- }
-`;
-
-export const DropdownMenuItemContentWrapper = styled.div`
- /*
- * Always occupy the second column, since the first column
- * is taken by the prefix wrapper (when displayed).
- */
- grid-column: 2;
-
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: ${ space( 3 ) };
-
- pointer-events: none;
-`;
-
-export const DropdownMenuItemChildrenWrapper = styled.div`
- flex: 1;
-
- display: inline-flex;
- flex-direction: column;
- gap: ${ space( 1 ) };
-`;
-
-export const ItemSuffixWrapper = styled.span`
- flex: 0 1 fit-content;
- min-width: 0;
- width: fit-content;
-
- display: flex;
- align-items: center;
- justify-content: center;
- gap: ${ space( 3 ) };
-
- color: ${ COLORS.gray[ '700' ] };
-
- /*
- * When the parent menu item is active, except when it's a non-focused/hovered
- * submenu trigger (in that case, color should not be inherited)
- */
- [data-active-item]:not( [data-focus-visible] ) *:not(${ DropdownMenu }) &,
- /* When the parent menu item is disabled */
- [aria-disabled='true'] *:not(${ DropdownMenu }) & {
- color: inherit;
- }
-`;
-
-export const DropdownMenuGroup = styled( Ariakit.MenuGroup )`
- /* Ignore this element when calculating the layout. Useful for subgrid */
- display: contents;
-`;
-
-export const DropdownMenuSeparator = styled( Ariakit.MenuSeparator )<
- Pick< DropdownMenuContext, 'variant' >
->`
- /* Occupy the width of all grid columns (ie. full width) */
- grid-column: 1 / -1;
-
- border: none;
- height: ${ CONFIG.borderWidth };
- background-color: ${ ( props ) =>
- props.variant === 'toolbar'
- ? TOOLBAR_VARIANT_BORDER_COLOR
- : DIVIDER_COLOR };
- /* Align with menu items' content */
- margin-block: ${ space( 2 ) };
- margin-inline: ${ ITEM_PADDING_INLINE };
-
- /* Only visible in Windows High Contrast mode */
- outline: 2px solid transparent;
-`;
-
-export const SubmenuChevronIcon = styled( Icon )`
- width: ${ space( 1.5 ) };
- ${ rtl(
- {
- transform: `scaleX(1)`,
- },
- {
- transform: `scaleX(-1)`,
- }
- ) };
-`;
-
-export const DropdownMenuItemLabel = styled( Truncate )`
- font-size: ${ font( 'default.fontSize' ) };
- line-height: 20px;
- color: inherit;
-`;
-
-export const DropdownMenuItemHelpText = styled( Truncate )`
- font-size: ${ font( 'helpText.fontSize' ) };
- line-height: 16px;
- color: ${ COLORS.gray[ '700' ] };
-
- [data-active-item]:not( [data-focus-visible] ) *:not( ${ DropdownMenu } ) &,
- [aria-disabled='true'] *:not( ${ DropdownMenu } ) & {
- color: inherit;
- }
-`;
diff --git a/packages/components/src/dropdown-menu-v2-ariakit/test/index.tsx b/packages/components/src/dropdown-menu-v2-ariakit/test/index.tsx
deleted file mode 100644
index f58639a545a056..00000000000000
--- a/packages/components/src/dropdown-menu-v2-ariakit/test/index.tsx
+++ /dev/null
@@ -1,1108 +0,0 @@
-/**
- * External dependencies
- */
-import { render, screen, waitFor } from '@testing-library/react';
-import { press, click, hover, type } from '@ariakit/test';
-
-/**
- * WordPress dependencies
- */
-import { useState } from '@wordpress/element';
-
-/**
- * Internal dependencies
- */
-import {
- DropdownMenu,
- DropdownMenuCheckboxItem,
- DropdownMenuItem,
- DropdownMenuRadioItem,
- DropdownMenuSeparator,
- DropdownMenuGroup,
-} from '..';
-
-const delay = ( delayInMs: number ) => {
- return new Promise( ( resolve ) => setTimeout( resolve, delayInMs ) );
-};
-
-describe( 'DropdownMenu', () => {
- // See https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/
- it( 'should follow the WAI-ARIA spec', async () => {
- render(
- Open dropdown }>
- Dropdown menu item
-
- Dropdown submenu
- }
- >
- Dropdown submenu item 1
- Dropdown submenu item 2
-
-
- );
-
- const toggleButton = screen.getByRole( 'button', {
- name: 'Open dropdown',
- } );
-
- expect( toggleButton ).toHaveAttribute( 'aria-haspopup', 'menu' );
- expect( toggleButton ).toHaveAttribute( 'aria-expanded', 'false' );
-
- await click( toggleButton );
-
- expect( toggleButton ).toHaveAttribute( 'aria-expanded', 'true' );
-
- expect(
- screen.getByRole( 'menu', { name: toggleButton.textContent ?? '' } )
- ).toHaveFocus();
- expect( screen.getByRole( 'separator' ) ).toHaveAttribute(
- 'aria-orientation',
- 'horizontal'
- );
- expect( screen.getAllByRole( 'menuitem' ) ).toHaveLength( 2 );
-
- const submenuTrigger = screen.getByRole( 'menuitem', {
- name: 'Dropdown submenu',
- } );
- expect( submenuTrigger ).toHaveAttribute( 'aria-haspopup', 'menu' );
- expect( submenuTrigger ).toHaveAttribute( 'aria-expanded', 'false' );
-
- await hover( submenuTrigger );
-
- // Wait for the open animation after hovering
- await waitFor( () =>
- expect(
- screen.getByRole( 'menu', {
- name: submenuTrigger.textContent ?? '',
- } )
- ).toBeVisible()
- );
-
- expect( submenuTrigger ).toHaveAttribute( 'aria-expanded', 'true' );
- expect( submenuTrigger ).toHaveAttribute(
- 'aria-controls',
- screen.getAllByRole( 'menu' )[ 1 ].id
- );
- } );
-
- describe( 'pointer and keyboard interactions', () => {
- it( 'should open and focus the menu when clicking the trigger', async () => {
- render(
- Open dropdown }>
- Dropdown menu item
-
- );
-
- const toggleButton = screen.getByRole( 'button', {
- name: 'Open dropdown',
- } );
-
- // DropdownMenu closed
- expect( screen.queryByRole( 'menu' ) ).not.toBeInTheDocument();
-
- // Click to open the menu
- await click( toggleButton );
-
- // DropdownMenu open, focus is on the menu wrapper
- expect( screen.getByRole( 'menu' ) ).toHaveFocus();
- } );
-
- it( 'should open and focus the first item when pressing the arrow down key on the trigger', async () => {
- render(
- Open dropdown }>
- First item
- Second item
- Third item
-
- );
-
- const toggleButton = screen.getByRole( 'button', {
- name: 'Open dropdown',
- } );
-
- // Move focus on the toggle
- await press.Tab();
-
- expect( toggleButton ).toHaveFocus();
-
- // DropdownMenu closed
- expect( screen.queryByRole( 'menuitem' ) ).not.toBeInTheDocument();
-
- await press.ArrowDown();
-
- // DropdownMenu open, focus is on the first focusable item
- // (disabled items are still focusable and accessible)
- expect(
- screen.getByRole( 'menuitem', { name: 'First item' } )
- ).toHaveFocus();
- } );
-
- it( 'should open and focus the first item when pressing the space key on the trigger', async () => {
- render(
- Open dropdown }>
- First item
- Second item
- Third item
-
- );
-
- const toggleButton = screen.getByRole( 'button', {
- name: 'Open dropdown',
- } );
-
- // Move focus on the toggle
- await press.Tab();
-
- expect( toggleButton ).toHaveFocus();
-
- // DropdownMenu closed
- expect( screen.queryByRole( 'menuitem' ) ).not.toBeInTheDocument();
-
- await press.Space();
-
- // DropdownMenu open, focus is on the first focusable item
- // (disabled items are still focusable and accessible
- expect(
- screen.getByRole( 'menuitem', { name: 'First item' } )
- ).toHaveFocus();
- } );
-
- it( 'should close when pressing the escape key', async () => {
- render(
- Open dropdown }>
- Dropdown menu item
-
- );
-
- const trigger = screen.getByRole( 'button', {
- name: 'Open dropdown',
- } );
-
- await click( trigger );
-
- // Focuses menu on mouse click, focuses first item on keyboard press
- // Can be changed with a custom useEffect
- expect( screen.getByRole( 'menu' ) ).toHaveFocus();
-
- // Pressing esc will close the menu and move focus to the toggle
- await press.Escape();
-
- expect( screen.queryByRole( 'menu' ) ).not.toBeInTheDocument();
-
- await waitFor( () =>
- expect(
- screen.getByRole( 'button', { name: 'Open dropdown' } )
- ).toHaveFocus()
- );
- } );
-
- it( 'should close when clicking outside of the content', async () => {
- render(
- Open dropdown }
- >
- Dropdown menu item
-
- );
-
- expect( screen.getByRole( 'menu' ) ).toBeInTheDocument();
-
- // Click on the body (ie. outside of the dropdown menu)
- await click( document.body );
-
- expect( screen.queryByRole( 'menu' ) ).not.toBeInTheDocument();
- } );
-
- it( 'should close when clicking on a menu item', async () => {
- render(
- Open dropdown }
- >
- Dropdown menu item
-
- );
-
- expect( screen.getByRole( 'menu' ) ).toBeInTheDocument();
-
- // Clicking a menu item will close the menu
- await click( screen.getByRole( 'menuitem' ) );
-
- expect( screen.queryByRole( 'menu' ) ).not.toBeInTheDocument();
- } );
-
- it( 'should not close when clicking on a menu item when the `hideOnClick` prop is set to `false`', async () => {
- render(
- Open dropdown }
- >
-
- Dropdown menu item
-
-
- );
-
- expect( screen.getByRole( 'menu' ) ).toBeVisible();
-
- // Clicking a menu item will close the menu
- await click( screen.getByRole( 'menuitem' ) );
-
- expect( screen.getByRole( 'menu' ) ).toBeVisible();
- } );
-
- it( 'should not close when clicking on a disabled menu item', async () => {
- render(
- Open dropdown }
- >
-
- Dropdown menu item
-
-
- );
-
- expect( screen.getByRole( 'menu' ) ).toBeInTheDocument();
-
- // Clicking a disabled menu item won't close the menu
- await click( screen.getByRole( 'menuitem' ) );
-
- expect( screen.getByRole( 'menu' ) ).toBeInTheDocument();
- } );
-
- it( 'should reveal submenu content when hovering over the submenu trigger', async () => {
- render(
- Open dropdown }
- >
- Dropdown menu item 1
- Dropdown menu item 2
-
- Dropdown submenu
-
- }
- >
-
- Dropdown submenu item 1
-
-
- Dropdown submenu item 2
-
-
- Dropdown menu item 3
-
- );
-
- // Before hover, submenu items are not rendered
- expect(
- screen.queryByRole( 'menuitem', {
- name: 'Dropdown submenu item 1',
- } )
- ).not.toBeInTheDocument();
-
- await hover(
- screen.getByRole( 'menuitem', { name: 'Dropdown submenu' } )
- );
-
- // After hover, submenu items are rendered
- // Reason for `findByRole`: due to the animation, we've got to wait
- // a short amount of time for the submenu to appear
- await screen.findByRole( 'menuitem', {
- name: 'Dropdown submenu item 1',
- } );
- } );
-
- it( 'should navigate menu items and subitems using the arrow, spacebar and enter keys', async () => {
- render(
- Open dropdown }
- >
- Dropdown menu item 1
- Dropdown menu item 2
-
- Dropdown submenu
-
- }
- >
-
- Dropdown submenu item 1
-
-
- Dropdown submenu item 2
-
-
- Dropdown menu item 3
-
- );
-
- // The menu is focused automatically when `defaultOpen` is set.
- await waitFor( () =>
- expect( screen.getByRole( 'menu' ) ).toHaveFocus()
- );
-
- // Arrow up/down selects menu items
- // The selection wraps around from last to first and viceversa
- await press.ArrowDown();
- expect(
- screen.getByRole( 'menuitem', { name: 'Dropdown menu item 1' } )
- ).toHaveFocus();
-
- await press.ArrowDown();
- expect(
- screen.getByRole( 'menuitem', { name: 'Dropdown menu item 2' } )
- ).toHaveFocus();
-
- await press.ArrowDown();
- expect(
- screen.getByRole( 'menuitem', { name: 'Dropdown submenu' } )
- ).toHaveFocus();
-
- await press.ArrowDown();
- expect(
- screen.getByRole( 'menuitem', { name: 'Dropdown menu item 3' } )
- ).toHaveFocus();
-
- await press.ArrowDown();
- expect(
- screen.getByRole( 'menuitem', { name: 'Dropdown menu item 1' } )
- ).toHaveFocus();
-
- await press.ArrowUp();
- expect(
- screen.getByRole( 'menuitem', { name: 'Dropdown menu item 3' } )
- ).toHaveFocus();
-
- await press.ArrowUp();
- expect(
- screen.getByRole( 'menuitem', { name: 'Dropdown submenu' } )
- ).toHaveFocus();
-
- // Arrow right/left can be used to enter/leave submenus
- await press.ArrowRight();
- expect(
- screen.getByRole( 'menuitem', {
- name: 'Dropdown submenu item 1',
- } )
- ).toHaveFocus();
-
- await press.ArrowDown();
- expect(
- screen.getByRole( 'menuitem', {
- name: 'Dropdown submenu item 2',
- } )
- ).toHaveFocus();
-
- await press.ArrowLeft();
- expect(
- screen.getByRole( 'menuitem', {
- name: 'Dropdown submenu',
- } )
- ).toHaveFocus();
-
- // Spacebar or enter key can also be used to enter a submenu
- await press.Enter();
- expect(
- screen.getByRole( 'menuitem', {
- name: 'Dropdown submenu item 1',
- } )
- ).toHaveFocus();
-
- await press.ArrowLeft();
- expect(
- screen.getByRole( 'menuitem', {
- name: 'Dropdown submenu',
- } )
- ).toHaveFocus();
-
- await press.Space();
- expect(
- screen.getByRole( 'menuitem', {
- name: 'Dropdown submenu item 1',
- } )
- ).toHaveFocus();
-
- await press.ArrowLeft();
- expect(
- screen.getByRole( 'menuitem', {
- name: 'Dropdown submenu',
- } )
- ).toHaveFocus();
- } );
-
- it( 'should check radio items and keep the menu open when clicking (controlled)', async () => {
- const onRadioValueChangeSpy = jest.fn();
-
- const ControlledRadioGroup = () => {
- const [ radioValue, setRadioValue ] = useState( 'two' );
- const onRadioChange: React.ComponentProps<
- typeof DropdownMenuRadioItem
- >[ 'onChange' ] = ( e ) => {
- onRadioValueChangeSpy( e.target.value );
- setRadioValue( e.target.value );
- };
- return (
- Open dropdown }>
-
-
- Radio item one
-
-
- Radio item two
-
-
-
- );
- };
-
- render( );
-
- // Open dropdown
- await click(
- screen.getByRole( 'button', { name: 'Open dropdown' } )
- );
-
- // No radios should be checked at this point
- expect( screen.getAllByRole( 'menuitemradio' ) ).toHaveLength( 2 );
- expect(
- screen.getByRole( 'menuitemradio', { name: 'Radio item one' } )
- ).not.toBeChecked();
- expect(
- screen.getByRole( 'menuitemradio', { name: 'Radio item two' } )
- ).not.toBeChecked();
-
- // Click first radio item, make sure that the callback fires
- await click(
- screen.getByRole( 'menuitemradio', { name: 'Radio item one' } )
- );
- expect( onRadioValueChangeSpy ).toHaveBeenCalledTimes( 1 );
- expect( onRadioValueChangeSpy ).toHaveBeenLastCalledWith(
- 'radio-one'
- );
-
- // Make sure that first radio is checked
- expect(
- screen.getByRole( 'menuitemradio', { name: 'Radio item one' } )
- ).toBeChecked();
- expect(
- screen.getByRole( 'menuitemradio', { name: 'Radio item two' } )
- ).not.toBeChecked();
-
- // Click second radio item, make sure that the callback fires
- await click(
- screen.getByRole( 'menuitemradio', { name: 'Radio item two' } )
- );
- expect( onRadioValueChangeSpy ).toHaveBeenCalledTimes( 2 );
- expect( onRadioValueChangeSpy ).toHaveBeenLastCalledWith(
- 'radio-two'
- );
-
- // Make sure that second radio is selected
- expect(
- screen.getByRole( 'menuitemradio', { name: 'Radio item one' } )
- ).not.toBeChecked();
- expect(
- screen.getByRole( 'menuitemradio', { name: 'Radio item two' } )
- ).toBeChecked();
- } );
-
- it( 'should check radio items and keep the menu open when clicking (uncontrolled)', async () => {
- const onRadioValueChangeSpy = jest.fn();
- render(
- Open dropdown }>
-
-
- onRadioValueChangeSpy( e.target.value )
- }
- >
- Radio item one
-
-
- onRadioValueChangeSpy( e.target.value )
- }
- >
- Radio item two
-
-
-
- );
-
- // Open dropdown
- await click(
- screen.getByRole( 'button', { name: 'Open dropdown' } )
- );
-
- // Radio item two should be checked (`defaultChecked` prop)
- expect( screen.getAllByRole( 'menuitemradio' ) ).toHaveLength( 2 );
- expect(
- screen.getByRole( 'menuitemradio', { name: 'Radio item one' } )
- ).not.toBeChecked();
- expect(
- screen.getByRole( 'menuitemradio', { name: 'Radio item two' } )
- ).toBeChecked();
-
- // Click first radio item, make sure that the callback fires
- await click(
- screen.getByRole( 'menuitemradio', { name: 'Radio item one' } )
- );
- expect( onRadioValueChangeSpy ).toHaveBeenCalledTimes( 1 );
- expect( onRadioValueChangeSpy ).toHaveBeenLastCalledWith(
- 'radio-one'
- );
-
- // Make sure that first radio is checked
- expect(
- screen.getByRole( 'menuitemradio', { name: 'Radio item one' } )
- ).toBeChecked();
- expect(
- screen.getByRole( 'menuitemradio', { name: 'Radio item two' } )
- ).not.toBeChecked();
-
- // Click second radio item, make sure that the callback fires
- await click(
- screen.getByRole( 'menuitemradio', { name: 'Radio item two' } )
- );
- expect( onRadioValueChangeSpy ).toHaveBeenCalledTimes( 2 );
- expect( onRadioValueChangeSpy ).toHaveBeenLastCalledWith(
- 'radio-two'
- );
-
- // Make sure that second radio is selected
- expect(
- screen.getByRole( 'menuitemradio', { name: 'Radio item one' } )
- ).not.toBeChecked();
- expect(
- screen.getByRole( 'menuitemradio', { name: 'Radio item two' } )
- ).toBeChecked();
- } );
-
- it( 'should check checkbox items and keep the menu open when clicking (controlled)', async () => {
- const onCheckboxValueChangeSpy = jest.fn();
-
- const ControlledRadioGroup = () => {
- const [ itemOneChecked, setItemOneChecked ] =
- useState< boolean >();
- const [ itemTwoChecked, setItemTwoChecked ] =
- useState< boolean >();
-
- return (
- Open dropdown }>
- {
- onCheckboxValueChangeSpy(
- e.target.name,
- e.target.value,
- e.target.checked
- );
- setItemOneChecked( e.target.checked );
- } }
- >
- Checkbox item one
-
-
- {
- onCheckboxValueChangeSpy(
- e.target.name,
- e.target.value,
- e.target.checked
- );
- setItemTwoChecked( e.target.checked );
- } }
- >
- Checkbox item two
-
-
- );
- };
-
- render( );
-
- // Open dropdown
- await click(
- screen.getByRole( 'button', { name: 'Open dropdown' } )
- );
-
- // No checkboxes should be checked at this point
- expect( screen.getAllByRole( 'menuitemcheckbox' ) ).toHaveLength(
- 2
- );
- expect(
- screen.getByRole( 'menuitemcheckbox', {
- name: 'Checkbox item one',
- } )
- ).not.toBeChecked();
- expect(
- screen.getByRole( 'menuitemcheckbox', {
- name: 'Checkbox item two',
- } )
- ).not.toBeChecked();
-
- // Click first checkbox item, make sure that the callback fires
- await click(
- screen.getByRole( 'menuitemcheckbox', {
- name: 'Checkbox item one',
- } )
- );
- expect( onCheckboxValueChangeSpy ).toHaveBeenCalledTimes( 1 );
- expect( onCheckboxValueChangeSpy ).toHaveBeenLastCalledWith(
- 'item-one',
- 'item-one-value',
- true
- );
-
- // Make sure that first checkbox is checked
- expect(
- screen.getByRole( 'menuitemcheckbox', {
- name: 'Checkbox item one',
- } )
- ).toBeChecked();
-
- // Click second checkbox item, make sure that the callback fires
- await click(
- screen.getByRole( 'menuitemcheckbox', {
- name: 'Checkbox item two',
- } )
- );
- expect( onCheckboxValueChangeSpy ).toHaveBeenCalledTimes( 2 );
- expect( onCheckboxValueChangeSpy ).toHaveBeenLastCalledWith(
- 'item-two',
- 'item-two-value',
- true
- );
-
- // Make sure that second checkbox is selected
- expect(
- screen.getByRole( 'menuitemcheckbox', {
- name: 'Checkbox item two',
- } )
- ).toBeChecked();
-
- // Click second checkbox item, make sure that the callback fires
- await click(
- screen.getByRole( 'menuitemcheckbox', {
- name: 'Checkbox item two',
- } )
- );
- expect( onCheckboxValueChangeSpy ).toHaveBeenCalledTimes( 3 );
- expect( onCheckboxValueChangeSpy ).toHaveBeenLastCalledWith(
- 'item-two',
- 'item-two-value',
- false
- );
-
- // Make sure that second checkbox is unselected
- expect(
- screen.getByRole( 'menuitemcheckbox', {
- name: 'Checkbox item two',
- } )
- ).not.toBeChecked();
- } );
-
- it( 'should check checkbox items and keep the menu open when clicking (uncontrolled)', async () => {
- const onCheckboxValueChangeSpy = jest.fn();
-
- render(
- Open dropdown }>
- {
- onCheckboxValueChangeSpy(
- e.target.name,
- e.target.value,
- e.target.checked
- );
- } }
- >
- Checkbox item one
-
-
- {
- onCheckboxValueChangeSpy(
- e.target.name,
- e.target.value,
- e.target.checked
- );
- } }
- >
- Checkbox item two
-
-
- );
-
- // Open dropdown
- await click(
- screen.getByRole( 'button', { name: 'Open dropdown' } )
- );
-
- // Checkbox item two should be checked (`defaultChecked`)
- expect( screen.getAllByRole( 'menuitemcheckbox' ) ).toHaveLength(
- 2
- );
- expect(
- screen.getByRole( 'menuitemcheckbox', {
- name: 'Checkbox item one',
- } )
- ).not.toBeChecked();
- expect(
- screen.getByRole( 'menuitemcheckbox', {
- name: 'Checkbox item two',
- } )
- ).toBeChecked();
-
- // Click first checkbox item, make sure that the callback fires
- await click(
- screen.getByRole( 'menuitemcheckbox', {
- name: 'Checkbox item one',
- } )
- );
- expect( onCheckboxValueChangeSpy ).toHaveBeenCalledTimes( 1 );
- expect( onCheckboxValueChangeSpy ).toHaveBeenLastCalledWith(
- 'item-one',
- 'item-one-value',
- true
- );
-
- // Make sure that first checkbox is checked
- expect(
- screen.getByRole( 'menuitemcheckbox', {
- name: 'Checkbox item one',
- } )
- ).toBeChecked();
-
- // Click second checkbox item, make sure that the callback fires
- await click(
- screen.getByRole( 'menuitemcheckbox', {
- name: 'Checkbox item two',
- } )
- );
- expect( onCheckboxValueChangeSpy ).toHaveBeenCalledTimes( 2 );
- expect( onCheckboxValueChangeSpy ).toHaveBeenLastCalledWith(
- 'item-two',
- 'item-two-value',
- false
- );
-
- // Make sure that second checkbox is unchecked
- expect(
- screen.getByRole( 'menuitemcheckbox', {
- name: 'Checkbox item two',
- } )
- ).not.toBeChecked();
-
- // Click second checkbox item, make sure that the callback fires
- await click(
- screen.getByRole( 'menuitemcheckbox', {
- name: 'Checkbox item two',
- } )
- );
- expect( onCheckboxValueChangeSpy ).toHaveBeenCalledTimes( 3 );
- expect( onCheckboxValueChangeSpy ).toHaveBeenLastCalledWith(
- 'item-two',
- 'item-two-value',
- true
- );
-
- // Make sure that second checkbox is unselected
- expect(
- screen.getByRole( 'menuitemcheckbox', {
- name: 'Checkbox item two',
- } )
- ).toBeChecked();
- } );
- } );
-
- describe( 'modality', () => {
- it( 'should be modal by default', async () => {
- render(
- <>
- Open dropdown }>
- Dropdown menu item
-
- Button outside of dropdown
- >
- );
-
- // Click to open the menu
- await click(
- screen.getByRole( 'button', {
- name: 'Open dropdown',
- } )
- );
-
- // DropdownMenu open, focus is on the menu wrapper
- expect( screen.getByRole( 'menu' ) ).toHaveFocus();
-
- expect(
- screen.queryByRole( 'button', {
- name: 'Button outside of dropdown',
- } )
- ).not.toBeInTheDocument();
- } );
-
- it( 'should not be modal when the `modal` prop is set to `false`', async () => {
- render(
- <>
- Open dropdown }
- modal={ false }
- >
- Dropdown menu item
-
- Button outside of dropdown
- >
- );
-
- // Click to open the menu
- await click(
- screen.getByRole( 'button', {
- name: 'Open dropdown',
- } )
- );
-
- // DropdownMenu open, focus is on the menu wrapper
- expect( screen.getByRole( 'menu' ) ).toHaveFocus();
-
- // DropdownMenu is not modal, therefore the outer button is part of the
- // accessibility tree and can be found.
- const outerButton = screen.getByRole( 'button', {
- name: 'Button outside of dropdown',
- } );
-
- // The outer button can be focused by pressing tab. Doing so will cause
- // the DropdownMenu to close.
- await press.Tab();
- expect( outerButton ).toBeInTheDocument();
- expect( screen.queryByRole( 'menu' ) ).not.toBeInTheDocument();
- } );
- } );
-
- describe( 'items prefix and suffix', () => {
- it( 'should display a prefix on regular items', async () => {
- render(
- Open dropdown }>
- Item prefix> }>
- Dropdown menu item
-
-
- );
-
- // Click to open the menu
- await click(
- screen.getByRole( 'button', {
- name: 'Open dropdown',
- } )
- );
-
- // The contents of the prefix are rendered before the item's children
- expect(
- screen.getByRole( 'menuitem', {
- name: 'Item prefix Dropdown menu item',
- } )
- ).toBeInTheDocument();
- } );
-
- it( 'should display a suffix on regular items', async () => {
- render(
- Open dropdown }>
- Item suffix> }>
- Dropdown menu item
-
-
- );
-
- // Click to open the menu
- await click(
- screen.getByRole( 'button', {
- name: 'Open dropdown',
- } )
- );
-
- // The contents of the suffix are rendered after the item's children
- expect(
- screen.getByRole( 'menuitem', {
- name: 'Dropdown menu item Item suffix',
- } )
- ).toBeInTheDocument();
- } );
-
- it( 'should display a suffix on radio items', async () => {
- render(
- Open dropdown }>
-
- Radio item one
-
-
- );
-
- // Click to open the menu
- await click(
- screen.getByRole( 'button', {
- name: 'Open dropdown',
- } )
- );
-
- // The contents of the suffix are rendered after the item's children
- expect(
- screen.getByRole( 'menuitemradio', {
- name: 'Radio item one Radio suffix',
- } )
- ).toBeInTheDocument();
- } );
-
- it( 'should display a suffix on checkbox items', async () => {
- render(
- Open dropdown }>
-
- Checkbox item one
-
-
- );
-
- // Click to open the menu
- await click(
- screen.getByRole( 'button', {
- name: 'Open dropdown',
- } )
- );
-
- // The contents of the suffix are rendered after the item's children
- expect(
- screen.getByRole( 'menuitemcheckbox', {
- name: 'Checkbox item one Checkbox suffix',
- } )
- ).toBeInTheDocument();
- } );
- } );
-
- describe( 'typeahead', () => {
- it( 'should highlight matching item', async () => {
- render(
- Open dropdown }>
- One
- Two
-
- );
-
- // Click to open the menu
- await click(
- screen.getByRole( 'button', {
- name: 'Open dropdown',
- } )
- );
- expect( screen.getByRole( 'menu' ) ).toHaveFocus();
-
- // Type "tw", it should match and focus the item with content "Two"
- await type( 'tw' );
- expect(
- screen.getByRole( 'menuitem', { name: 'Two' } )
- ).toHaveFocus();
-
- // Wait for the typeahead timer to reset and interpret
- // the next keystrokes as a new search
- await delay( 500 );
-
- // Type "on", it should match and focus the item with content "One"
- await type( 'on' );
- expect(
- screen.getByRole( 'menuitem', { name: 'One' } )
- ).toHaveFocus();
- } );
-
- it( 'should keep previous focus when no matches are found', async () => {
- render(
- Open dropdown }>
- One
- Two
-
- );
-
- // Click to open the menu
- await click(
- screen.getByRole( 'button', {
- name: 'Open dropdown',
- } )
- );
- expect( screen.getByRole( 'menu' ) ).toHaveFocus();
-
- // Type a string that doesn't match any items. Focus shouldn't move.
- await type( 'abc' );
- expect( screen.getByRole( 'menu' ) ).toHaveFocus();
-
- // Wait for the typeahead timer to reset and interpret
- // the next keystrokes as a new search
- await delay( 500 );
-
- // Type "on", it should match and focus the item with content "One"
- await type( 'on' );
- expect(
- screen.getByRole( 'menuitem', { name: 'One' } )
- ).toHaveFocus();
-
- // Wait for the typeahead timer to reset and interpret
- // the next keystrokes as a new search
- await delay( 500 );
-
- // Type a string that doesn't match any items. Focus shouldn't move.
- await type( 'abc' );
- expect(
- screen.getByRole( 'menuitem', { name: 'One' } )
- ).toHaveFocus();
-
- // Wait for the typeahead timer to reset and interpret
- // the next keystrokes as a new search
- await delay( 500 );
-
- // Type "tw", it should match and focus the item with content "Two"
- await type( 'tw' );
- expect(
- screen.getByRole( 'menuitem', { name: 'Two' } )
- ).toHaveFocus();
- } );
- } );
-} );
diff --git a/packages/components/src/dropdown-menu-v2-ariakit/types.ts b/packages/components/src/dropdown-menu-v2-ariakit/types.ts
deleted file mode 100644
index 478b89c67f136a..00000000000000
--- a/packages/components/src/dropdown-menu-v2-ariakit/types.ts
+++ /dev/null
@@ -1,179 +0,0 @@
-/**
- * External dependencies
- */
-// eslint-disable-next-line no-restricted-imports
-import type * as Ariakit from '@ariakit/react';
-import type { Placement } from '@floating-ui/react-dom';
-
-export interface DropdownMenuContext {
- /**
- * The ariakit store shared across all DropdownMenu subcomponents.
- */
- store: Ariakit.MenuStore;
- /**
- * The variant used by the underlying menu popover.
- */
- variant?: 'toolbar';
-}
-
-export interface DropdownMenuProps {
- /**
- * The trigger button.
- */
- trigger: React.ReactElement;
- /**
- * The contents of the dropdown.
- */
- children?: React.ReactNode;
- /**
- * The open state of the dropdown menu when it is initially rendered. Use when
- * not wanting to control its open state.
- *
- * @default false
- */
- defaultOpen?: boolean;
- /**
- * The controlled open state of the dropdown menu. Must be used in conjunction
- * with `onOpenChange`.
- */
- open?: boolean;
- /**
- * Event handler called when the open state of the dropdown menu changes.
- */
- onOpenChange?: ( open: boolean ) => void;
- /**
- * The modality of the dropdown menu. When set to true, interaction with
- * outside elements will be disabled and only menu content will be visible to
- * screen readers.
- *
- * @default true
- */
- modal?: boolean;
- /**
- * The placement of the dropdown menu popover.
- *
- * @default 'bottom-start' for root-level menus, 'right-start' for nested menus
- */
- placement?: Placement;
- /**
- * The distance between the popover and the anchor element.
- *
- * @default 8 for root-level menus, 16 for nested menus
- */
- gutter?: number;
- /**
- * The skidding of the popover along the anchor element. Can be set to
- * negative values to make the popover shift to the opposite side.
- *
- * @default 0 for root-level menus, -8 for nested menus
- */
- shift?: number;
- /**
- * Determines whether the menu popover will be hidden when the user presses
- * the Escape key.
- *
- * @default `( event ) => { event.preventDefault(); return true; }`
- */
- hideOnEscape?:
- | boolean
- | ( (
- event: KeyboardEvent | React.KeyboardEvent< Element >
- ) => boolean );
-}
-
-export interface DropdownMenuGroupProps {
- /**
- * The contents of the dropdown menu group.
- */
- children: React.ReactNode;
-}
-
-export interface DropdownMenuItemProps {
- /**
- * The contents of the menu item.
- */
- children: React.ReactNode;
- /**
- * The contents of the menu item's prefix.
- */
- prefix?: React.ReactNode;
- /**
- * The contents of the menu item's suffix.
- */
- suffix?: React.ReactNode;
- /**
- * Whether to hide the parent menu when the item is clicked.
- *
- * @default true
- */
- hideOnClick?: boolean;
- /**
- * Determines if the element is disabled.
- */
- disabled?: boolean;
-}
-
-export interface DropdownMenuCheckboxItemProps
- extends Omit< DropdownMenuItemProps, 'prefix' | 'hideOnClick' > {
- /**
- * Whether to hide the dropdown menu when the item is clicked.
- *
- * @default false
- */
- hideOnClick?: boolean;
- /**
- * The checkbox menu item's name.
- */
- name: string;
- /**
- * The checkbox item's value, useful when using multiple checkbox menu items
- * associated to the same `name`.
- */
- value?: string;
- /**
- * The controlled checked state of the checkbox menu item.
- */
- checked?: boolean;
- /**
- * The checked state of the checkbox menu item when it is initially rendered.
- * Use when not wanting to control its checked state.
- */
- defaultChecked?: boolean;
- /**
- * Event handler called when the checked state of the checkbox menu item changes.
- */
- onChange?: ( event: React.ChangeEvent< HTMLInputElement > ) => void;
-}
-
-export interface DropdownMenuRadioItemProps
- extends Omit< DropdownMenuItemProps, 'prefix' | 'hideOnClick' > {
- /**
- * Whether to hide the dropdown menu when the item is clicked.
- *
- * @default false
- */
- hideOnClick?: boolean;
- /**
- * The radio item's name.
- */
- name: string;
- /**
- * The radio item's value.
- */
- value: string | number;
- /**
- * The controlled checked state of the radio menu item.
- */
- checked?: boolean;
- /**
- * The checked state of the radio menu item when it is initially rendered.
- * Use when not wanting to control its checked state.
- */
- defaultChecked?: boolean;
- /**
- * Event handler called when the checked radio menu item changes.
- */
- onChange?: ( event: React.ChangeEvent< HTMLInputElement > ) => void;
-}
-
-export interface DropdownMenuSeparatorProps {}
diff --git a/packages/components/src/dropdown-menu-v2/README.md b/packages/components/src/dropdown-menu-v2/README.md
index 910690280015f5..2902b541169766 100644
--- a/packages/components/src/dropdown-menu-v2/README.md
+++ b/packages/components/src/dropdown-menu-v2/README.md
@@ -68,13 +68,14 @@ The contents of the dropdown
##### `defaultOpen`: `boolean`
-The open state of the dropdown menu when it is initially rendered. Use when you do not need to control its open state.
+The open state of the dropdown menu when it is initially rendered. Use when not wanting to control its open state.
- Required: no
+- Default: `false`
##### `open`: `boolean`
-The controlled open state of the dropdown menu. Must be used in conjunction with `onOpenChange`
+The controlled open state of the dropdown menu. Must be used in conjunction with `onOpenChange`.
- Required: no
@@ -91,33 +92,26 @@ The modality of the dropdown menu. When set to true, interaction with outside el
- Required: no
- Default: `true`
-##### `side`: `"bottom" | "left" | "right" | "top"`
+##### `placement`: ``'top' | 'top-start' | 'top-end' | 'right' | 'right-start' | 'right-end' | 'bottom' | 'bottom-start' | 'bottom-end' | 'left' | 'left-start' | 'left-end'`
-The preferred side of the trigger to render against when open. Will be reversed when collisions occur and avoidCollisions is enabled.
+The placement of the dropdown menu popover.
- Required: no
-- Default: `"bottom"`
+- Default: `'bottom-start'` for root-level menus, `'right-start'` for nested menus
-##### `sideOffset`: `number`
+##### `gutter`: `number`
The distance in pixels from the trigger.
- Required: no
-- Default: `0`
+- Default: `8` for root-level menus, `16` for nested menus
-##### `align`: `"end" | "start" | "center"`
+##### `shift`: `number`
-The preferred alignment against the trigger. May change when collisions occur.
+The skidding of the popover along the anchor element. Can be set to negative values to make the popover shift to the opposite side.
- Required: no
-- Default: `"start"`
-
-##### `alignOffset`: `number`
-
-An offset in pixels from the "start" or "end" alignment options.
-
-- Required: no
-- Default: `0`
+- Default: `0` for root-level menus, `-8` for nested menus
### `DropdownMenuItem`
@@ -133,23 +127,6 @@ The contents of the item
- Required: yes
-##### `disabled`: `boolean`
-
-- Required: no
-- Default: `false`
-
-##### `onSelect`: `(event: Event) => void`
-
-Event handler called when the user selects an item (via mouse or keyboard). Calling `event.preventDefault` in this handler will prevent the dropdown menu from closing when selecting that item.
-
-- Required: no
-
-##### `textValue`: `string`
-
-Optional text used for typeahead purposes. By default the typeahead behavior will use the `.textContent` of the item. Use this when the content is complex, or you have non-textual content inside.
-
-- Required: no
-
##### `prefix`: `React.ReactNode`
The contents of the item's prefix.
@@ -162,58 +139,23 @@ The contents of the item's suffix.
- Required: no
-### `DropdownSubMenu`
-
-Used to render a nested submenu.
-
-#### Props
-
-The component accepts the following props:
-##### `trigger`: `React.ReactNode`
-
-The contents rendered inside the trigger. The trigger should be an instance of the `DropdownSubMenuTrigger` component.
+##### `hideOnClick`: `boolean`
-- Required: yes
-
-##### `children`: `React.ReactNode`
-
-The contents of the dropdown
-
-- Required: yes
-
-##### `defaultOpen`: `boolean`
-
-The open state of the dropdown menu when it is initially rendered. Use when you do not need to control its open state.
-
-- Required: no
-
-##### `open`: `boolean`
-
-The controlled open state of the dropdown menu. Must be used in conjunction with `onOpenChange`
-
-- Required: no
-
-##### `onOpenChange`: `(open: boolean) => void`
-
-Event handler called when the open state of the dropdown menu changes.
+Whether to hide the dropdown menu when the menu item is clicked.
- Required: no
+- Default: `true`
##### `disabled`: `boolean`
-When `true`, prevents the user from interacting with the item.
-
-- Required: no
-
-##### `textValue`: `string`
-
-Optional text used for typeahead purposes for the trigger. By default the typeahead behavior will use the `.textContent` of the trigger. Use this when the content is complex, or you have non-textual content inside.
+Determines if the element is disabled.
- Required: no
+- Default: `false`
-### `DropdownSubMenuTrigger`
+### `DropdownMenuCheckboxItem`
-Used to render a submenu trigger.
+Used to render a checkbox item.
#### Props
@@ -225,73 +167,61 @@ The contents of the item
- Required: yes
-##### `prefix`: `React.ReactNode`
-
-The contents of the item's prefix.
-
-- Required: no
-
##### `suffix`: `React.ReactNode`
The contents of the item's suffix.
-- Default: a chevron icon
-- Required: The standard chevron icon for a submenu trigger
-
-### `DropdownMenuCheckboxItem`
-
-Used to render a checkbox item.
-
-#### Props
-
-The component accepts the following props:
+- Required: no
-##### `children`: `React.ReactNode`
+##### `hideOnClick`: `boolean`
-The contents of the checkbox item
+Whether to hide the dropdown menu when the menu item is clicked.
-- Required: yes
+- Required: no
+- Default: `false`
-##### `checked`: `boolean`
+##### `disabled`: `boolean`
-The controlled checked state of the item. Must be used in conjunction with `onCheckedChange`.
+Determines if the element is disabled.
- Required: no
- Default: `false`
-##### `onCheckedChange`: `(checked: boolean) => void)`
+##### `name`: `string`
-Event handler called when the checked state changes.
+The checkbox item's name.
-- Required: no
+- Required: yes
-##### `disabled`: `boolean`
+##### `value`: `string`
-When `true`, prevents the user from interacting with the item.
+The checkbox item's value, useful when using multiple checkbox items
+ associated to the same `name`.
- Required: no
-##### `onSelect`: `(event: Event) => void`
+##### `checked`: `boolean`
-Event handler called when the user selects an item (via mouse or keyboard). Calling `event.preventDefault` in this handler will prevent the dropdown menu from closing when selecting that item.
+The checkbox item's value, useful when using multiple checkbox items
+ associated to the same `name`.
- Required: no
-##### `textValue`: `string`
+##### `defaultChecked`: `boolean`
-Optional text used for typeahead purposes. By default the typeahead behavior will use the `.textContent` of the item. Use this when the content is complex, or you have non-textual content inside.
+The checked state of the checkbox menu item when it is initially rendered. Use when not wanting to control its checked state.
- Required: no
-##### `suffix`: `React.ReactNode`
+##### `onChange`: `( event: React.ChangeEvent< HTMLInputElement > ) => void;`
-The contents of the checkbox item's suffix.
+Event handler called when the checked state of the checkbox menu item changes.
- Required: no
-### `DropdownMenuRadioGroup`
+### `DropdownMenuRadioItem`
-Used to render a radio group.
+Used to render a radio item.
#### Props
@@ -299,69 +229,78 @@ The component accepts the following props:
##### `children`: `React.ReactNode`
-The contents of the radio group
+The contents of the item
- Required: yes
-##### `value`: `string`
+##### `suffix`: `React.ReactNode`
-The value of the selected item in the group.
+The contents of the item's suffix.
- Required: no
-##### `onValueChange`: `(value: string) => void`
+##### `hideOnClick`: `boolean`
-Event handler called when the value changes.
+Whether to hide the dropdown menu when the menu item is clicked.
- Required: no
+- Default: `false`
-### `DropdownMenuRadioItem`
-
-Used to render a radio item.
+##### `disabled`: `boolean`
-#### Props
+Determines if the element is disabled.
-The component accepts the following props:
+- Required: no
+- Default: `false`
-##### `children`: `React.ReactNode`
+##### `name`: `string`
-The contents of the item.
+The radio item's name.
- Required: yes
-##### `value`: `string`
+##### `value`: `string | number`
-The unique value of the item.
+The radio item's value.
- Required: yes
-##### `disabled`: `boolean`
+##### `checked`: `boolean`
-When `true`, prevents the user from interacting with the item.
+The checkbox item's value, useful when using multiple checkbox items
+ associated to the same `name`.
- Required: no
-##### `onSelect`: `(event: Event) => void`
+##### `defaultChecked`: `boolean`
-Event handler called when the user selects an item (via mouse or keyboard). Calling `event.preventDefault` in this handler will prevent the dropdown menu from closing when selecting that item.
+The checked state of the radio menu item when it is initially rendered. Use when not wanting to control its checked state.
- Required: no
-##### `textValue`: `string`
+##### `onChange`: `( event: React.ChangeEvent< HTMLInputElement > ) => void;`
-Optional text used for typeahead purposes. By default the typeahead behavior will use the `.textContent` of the item. Use this when the content is complex, or you have non-textual content inside.
+Event handler called when the checked radio menu item changes.
- Required: no
-##### `suffix`: `React.ReactNode
+### `DropdownMenuItemLabel`
-The contents of the radio item's suffix.
+Used to render the menu item's label.
-- Required: no
+#### Props
-### `DropdownMenuLabel`
+The component accepts the following props:
-Used to render a group label.
+##### `children`: `React.ReactNode`
+
+The label contents.
+
+- Required: yes
+
+### `DropdownMenuItemHelpText`
+
+Used to render the menu item's help text.
#### Props
@@ -369,7 +308,7 @@ The component accepts the following props:
##### `children`: `React.ReactNode`
-The contents of the group.
+The help text contents.
- Required: yes
diff --git a/packages/components/src/dropdown-menu-v2/index.tsx b/packages/components/src/dropdown-menu-v2/index.tsx
index 085a1f2c144cce..37d4a1f9cfcc5e 100644
--- a/packages/components/src/dropdown-menu-v2/index.tsx
+++ b/packages/components/src/dropdown-menu-v2/index.tsx
@@ -1,7 +1,8 @@
/**
* External dependencies
*/
-import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
+// eslint-disable-next-line no-restricted-imports
+import * as Ariakit from '@ariakit/react';
/**
* WordPress dependencies
@@ -11,283 +12,372 @@ import {
createContext,
useContext,
useMemo,
+ cloneElement,
+ isValidElement,
+ useCallback,
} from '@wordpress/element';
import { isRTL } from '@wordpress/i18n';
-import { check, chevronRightSmall, lineSolid } from '@wordpress/icons';
+import { check, chevronRightSmall } from '@wordpress/icons';
import { SVG, Circle } from '@wordpress/primitives';
/**
* Internal dependencies
*/
-import { useContextSystem, contextConnectWithoutRef } from '../context';
-import { useSlot } from '../slot-fill';
+import { useContextSystem, contextConnect } from '../context';
+import type { WordPressComponentProps } from '../context';
import Icon from '../icon';
-import { SLOT_NAME as POPOVER_DEFAULT_SLOT_NAME } from '../popover';
-import * as DropdownMenuStyled from './styles';
import type {
+ DropdownMenuContext as DropdownMenuContextType,
DropdownMenuProps,
- DropdownSubMenuProps,
- DropdownMenuItemProps,
- DropdownMenuLabelProps,
DropdownMenuGroupProps,
+ DropdownMenuItemProps,
DropdownMenuCheckboxItemProps,
- DropdownMenuRadioGroupProps,
DropdownMenuRadioItemProps,
DropdownMenuSeparatorProps,
- DropdownSubMenuTriggerProps,
- DropdownMenuInternalContext,
- DropdownMenuPrivateContext as DropdownMenuPrivateContextType,
} from './types';
+import * as Styled from './styles';
-// Menu content's side padding + 4px
-const SUB_MENU_OFFSET_SIDE = 16;
-// Opposite amount of the top padding of the menu item
-const SUB_MENU_OFFSET_ALIGN = -8;
+export const DropdownMenuContext = createContext<
+ DropdownMenuContextType | undefined
+>( undefined );
-const DropdownMenuPrivateContext =
- createContext< DropdownMenuPrivateContextType >( {
- variant: undefined,
- portalContainer: null,
- } );
+export const DropdownMenuItem = forwardRef<
+ HTMLDivElement,
+ WordPressComponentProps< DropdownMenuItemProps, 'div', false >
+>( function DropdownMenuItem(
+ { prefix, suffix, children, hideOnClick = true, ...props },
+ ref
+) {
+ const dropdownMenuContext = useContext( DropdownMenuContext );
+
+ return (
+
+ { prefix }
+
+
+
+ { children }
+
+
+ { suffix && (
+
+ { suffix }
+
+ ) }
+
+
+ );
+} );
+
+export const DropdownMenuCheckboxItem = forwardRef<
+ HTMLDivElement,
+ WordPressComponentProps< DropdownMenuCheckboxItemProps, 'div', false >
+>( function DropdownMenuCheckboxItem(
+ { suffix, children, hideOnClick = false, ...props },
+ ref
+) {
+ const dropdownMenuContext = useContext( DropdownMenuContext );
+
+ return (
+
+ }
+ // Override some ariakit inline styles
+ style={ { width: 'auto', height: 'auto' } }
+ >
+
+
+
+
+
+ { children }
+
+
+ { suffix && (
+
+ { suffix }
+
+ ) }
+
+
+ );
+} );
+
+const radioCheck = (
+
+
+
+);
+
+export const DropdownMenuRadioItem = forwardRef<
+ HTMLDivElement,
+ WordPressComponentProps< DropdownMenuRadioItemProps, 'div', false >
+>( function DropdownMenuRadioItem(
+ { suffix, children, hideOnClick = false, ...props },
+ ref
+) {
+ const dropdownMenuContext = useContext( DropdownMenuContext );
+
+ return (
+
+ }
+ // Override some ariakit inline styles
+ style={ { width: 'auto', height: 'auto' } }
+ >
+
+
+
+
+
+ { children }
+
-const UnconnectedDropdownMenu = ( props: DropdownMenuProps ) => {
+ { suffix && (
+
+ { suffix }
+
+ ) }
+
+
+ );
+} );
+
+export const DropdownMenuGroup = forwardRef<
+ HTMLDivElement,
+ WordPressComponentProps< DropdownMenuGroupProps, 'div', false >
+>( function DropdownMenuGroup( props, ref ) {
+ const dropdownMenuContext = useContext( DropdownMenuContext );
+ return (
+
+ );
+} );
+
+const UnconnectedDropdownMenu = (
+ props: WordPressComponentProps< DropdownMenuProps, 'div', false >,
+ ref: React.ForwardedRef< HTMLDivElement >
+) => {
const {
- // Root props
- defaultOpen,
+ // Store props
open,
+ defaultOpen = false,
onOpenChange,
- modal = true,
- // Content positioning props
- side = 'bottom',
- sideOffset = 0,
- align = 'center',
- alignOffset = 0,
- // Render props
- children,
+ placement,
+
+ // Menu trigger props
trigger,
+ // Menu props
+ gutter,
+ children,
+ shift,
+ modal = true,
+
// From internal components context
variant,
+
+ // Rest
+ ...otherProps
} = useContextSystem<
- // Adding `className` to the context type to avoid a TS error
- DropdownMenuProps & DropdownMenuInternalContext & { className?: string }
+ typeof props & Pick< DropdownMenuContextType, 'variant' >
>( props, 'DropdownMenu' );
- // Render the portal in the default slot used by the legacy Popover component.
- const slot = useSlot( POPOVER_DEFAULT_SLOT_NAME );
- const portalContainer = slot.ref?.current;
+ const parentContext = useContext( DropdownMenuContext );
- const privateContextValue = useMemo(
- () => ( {
- variant,
- portalContainer,
- } ),
- [ variant, portalContainer ]
- );
+ const computedDirection = isRTL() ? 'rtl' : 'ltr';
- return (
-
-
- { trigger }
-
-
-
-
- { children }
-
-
-
-
+ // If an explicit value for the `placement` prop is not passed,
+ // apply a default placement of `bottom-start` for the root dropdown,
+ // and of `right-start` for nested dropdowns.
+ let computedPlacement =
+ props.placement ??
+ ( parentContext?.store ? 'right-start' : 'bottom-start' );
+ // Swap left/right in case of RTL direction
+ if ( computedDirection === 'rtl' ) {
+ if ( /right/.test( computedPlacement ) ) {
+ computedPlacement = computedPlacement.replace(
+ 'right',
+ 'left'
+ ) as typeof computedPlacement;
+ } else if ( /left/.test( computedPlacement ) ) {
+ computedPlacement = computedPlacement.replace(
+ 'left',
+ 'right'
+ ) as typeof computedPlacement;
+ }
+ }
+
+ const dropdownMenuStore = Ariakit.useMenuStore( {
+ parent: parentContext?.store,
+ open,
+ defaultOpen,
+ placement: computedPlacement,
+ focusLoop: true,
+ setOpen( willBeOpen ) {
+ onOpenChange?.( willBeOpen );
+ },
+ rtl: computedDirection === 'rtl',
+ } );
+
+ const contextValue = useMemo(
+ () => ( { store: dropdownMenuStore, variant } ),
+ [ dropdownMenuStore, variant ]
);
-};
-/**
- * `DropdownMenu` displays a menu to the user (such as a set of actions
- * or functions) triggered by a button.
- */
-export const DropdownMenu = contextConnectWithoutRef(
- UnconnectedDropdownMenu,
- 'DropdownMenu'
-);
+ // Extract the side from the applied placement — useful for animations.
+ const appliedPlacementSide = dropdownMenuStore
+ .useState( 'placement' )
+ .split( '-' )[ 0 ];
-export const DropdownSubMenuTrigger = ( {
- prefix,
- suffix = (
-
- ),
- children,
-}: DropdownSubMenuTriggerProps ) => {
- return (
- <>
- { prefix && (
-
- { prefix }
-
- ) }
- { children }
- { suffix && (
-
- { suffix }
-
- ) }
- >
+ if (
+ dropdownMenuStore.parent &&
+ ! ( isValidElement( trigger ) && DropdownMenuItem === trigger.type )
+ ) {
+ // eslint-disable-next-line no-console
+ console.warn(
+ 'For nested DropdownMenus, the `trigger` should always be a `DropdownMenuItem`.'
+ );
+ }
+
+ const hideOnEscape = useCallback(
+ ( event: React.KeyboardEvent< Element > ) => {
+ // Pressing Escape can cause unexpected consequences (ie. exiting
+ // full screen mode on MacOs, close parent modals...).
+ event.preventDefault();
+ // Returning `true` causes the menu to hide.
+ return true;
+ },
+ []
);
-};
-export const DropdownSubMenu = ( {
- // Sub props
- defaultOpen,
- open,
- onOpenChange,
- // Sub trigger props
- disabled,
- textValue,
- // Render props
- children,
- trigger,
-}: DropdownSubMenuProps ) => {
- const { variant, portalContainer } = useContext(
- DropdownMenuPrivateContext
+ const wrapperProps = useMemo(
+ () => ( {
+ dir: computedDirection,
+ style: {
+ direction:
+ computedDirection as React.CSSProperties[ 'direction' ],
+ },
+ } ),
+ [ computedDirection ]
);
return (
-
-
+ { /* Menu trigger */ }
+
+ { trigger.props.suffix }
+
+ >
+ ),
+ } )
+ : trigger
+ }
+ />
+
+ { /* Menu popover */ }
+
- { trigger }
-
-
-
+
{ children }
-
-
-
+
+
+ >
);
};
-
-export const DropdownMenuLabel = ( props: DropdownMenuLabelProps ) => (
-
-);
-
-export const DropdownMenuGroup = ( props: DropdownMenuGroupProps ) => (
-
-);
-
-export const DropdownMenuItem = forwardRef(
- (
- { children, prefix, suffix, ...props }: DropdownMenuItemProps,
- forwardedRef: React.ForwardedRef< any >
- ) => {
- return (
-
- { prefix && (
-
- { prefix }
-
- ) }
- { children }
- { suffix && (
-
- { suffix }
-
- ) }
-
- );
- }
+export const DropdownMenu = contextConnect(
+ UnconnectedDropdownMenu,
+ 'DropdownMenu'
);
-export const DropdownMenuCheckboxItem = ( {
- children,
- checked = false,
- suffix,
- ...props
-}: DropdownMenuCheckboxItemProps ) => {
+export const DropdownMenuSeparator = forwardRef<
+ HTMLHRElement,
+ WordPressComponentProps< DropdownMenuSeparatorProps, 'hr', false >
+>( function DropdownMenuSeparator( props, ref ) {
+ const dropdownMenuContext = useContext( DropdownMenuContext );
return (
-
-
-
- { ( checked === 'indeterminate' || checked === true ) && (
-
- ) }
-
-
- { children }
- { suffix && (
-
- { suffix }
-
- ) }
-
+
);
-};
-
-export const DropdownMenuRadioGroup = (
- props: DropdownMenuRadioGroupProps
-) => ;
-
-const radioDot = (
-
-
-
-);
+} );
-export const DropdownMenuRadioItem = ( {
- children,
- suffix,
- ...props
-}: DropdownMenuRadioItemProps ) => {
+export const DropdownMenuItemLabel = forwardRef<
+ HTMLSpanElement,
+ WordPressComponentProps< { children: React.ReactNode }, 'span', true >
+>( function DropdownMenuItemLabel( props, ref ) {
return (
-
-
-
-
-
-
- { children }
- { suffix && (
-
- { suffix }
-
- ) }
-
+
);
-};
+} );
-export const DropdownMenuSeparator = ( props: DropdownMenuSeparatorProps ) => {
- const { variant } = useContext( DropdownMenuPrivateContext );
- return ;
-};
+export const DropdownMenuItemHelpText = forwardRef<
+ HTMLSpanElement,
+ WordPressComponentProps< { children: React.ReactNode }, 'span', true >
+>( function DropdownMenuItemHelpText( props, ref ) {
+ return (
+
+ );
+} );
diff --git a/packages/components/src/dropdown-menu-v2/stories/index.story.tsx b/packages/components/src/dropdown-menu-v2/stories/index.story.tsx
index 9c76a6f6f0f72f..343da0b7839d34 100644
--- a/packages/components/src/dropdown-menu-v2/stories/index.story.tsx
+++ b/packages/components/src/dropdown-menu-v2/stories/index.story.tsx
@@ -2,60 +2,55 @@
* External dependencies
*/
import type { Meta, StoryFn } from '@storybook/react';
-import styled from '@emotion/styled';
+import { css } from '@emotion/react';
+
+/**
+ * WordPress dependencies
+ */
+import { customLink, formatCapitalize } from '@wordpress/icons';
+import { useState, useMemo, useContext } from '@wordpress/element';
/**
* Internal dependencies
*/
-import { COLORS } from '../../utils';
+import { useCx } from '../../utils';
import {
DropdownMenu,
DropdownMenuItem,
- DropdownSubMenu,
- DropdownMenuSeparator,
DropdownMenuCheckboxItem,
DropdownMenuGroup,
- DropdownMenuLabel,
- DropdownMenuRadioGroup,
+ DropdownMenuSeparator,
+ DropdownMenuContext,
DropdownMenuRadioItem,
- DropdownSubMenuTrigger,
+ DropdownMenuItemLabel,
+ DropdownMenuItemHelpText,
} from '..';
-import Button from '../../button';
-
-/**
- * WordPress dependencies
- */
-import { useState } from '@wordpress/element';
-import { menu, wordpress } from '@wordpress/icons';
-
-/**
- * Internal dependencies
- */
import Icon from '../../icon';
+import Button from '../../button';
+import Modal from '../../modal';
+import { createSlotFill, Provider as SlotFillProvider } from '../../slot-fill';
import { ContextSystemProvider } from '../../context';
const meta: Meta< typeof DropdownMenu > = {
- title: 'Components (Experimental)/DropdownMenu v2',
+ title: 'Components (Experimental)/DropdownMenu V2',
component: DropdownMenu,
subcomponents: {
// @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170
DropdownMenuItem,
// @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170
- DropdownSubMenu,
- // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170
- DropdownSubMenuTrigger,
- // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170
- DropdownMenuSeparator,
- // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170
DropdownMenuCheckboxItem,
// @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170
DropdownMenuGroup,
// @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170
- DropdownMenuLabel,
+ DropdownMenuSeparator,
// @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170
- DropdownMenuRadioGroup,
+ DropdownMenuContext,
// @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170
DropdownMenuRadioItem,
+ // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170
+ DropdownMenuItemLabel,
+ // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170
+ DropdownMenuItemHelpText,
},
argTypes: {
children: { control: { type: null } },
@@ -84,126 +79,447 @@ const meta: Meta< typeof DropdownMenu > = {
};
export default meta;
-const ItemHelpText = styled.span`
- font-size: 12px;
- color: ${ COLORS.gray[ '700' ] };
-
- /* "> * > &" syntax is to target only immediate parent menu item */
- [data-highlighted] > * > &,
- [data-state='open'] > * > &,
- [data-disabled] > * & {
- color: inherit;
- }
-`;
-
-const CheckboxItemsGroup = () => {
- const [ itemOneChecked, setItemOneChecked ] = useState( true );
- const [ itemTwoChecked, setItemTwoChecked ] = useState( false );
-
- return (
+export const Default: StoryFn< typeof DropdownMenu > = ( props ) => (
+
+
+ Label
+
+
+ Label
+ Help text
+
+
+ Label
+
+ The menu item help text is automatically truncated when there
+ are more than two lines of text
+
+
+
+ Label
+
+ This item doesn't close the menu on click
+
+
+ Disabled item
+
- Checkbox group label
- ⌘+B }
+ }
>
- Checkbox item one
-
-
- With prefix
+
+ With suffix
+ }
+ suffix="⌥⌘T"
>
- Checkbox item two
-
+
+ Disabled with prefix and suffix
+
+
+ And help text
+
+
+
+);
+Default.args = {
+ trigger: (
+
+ Open menu
+
+ ),
+};
+
+export const WithSubmenu: StoryFn< typeof DropdownMenu > = ( props ) => (
+
+ Level 1 item
+
+
+ Submenu trigger item with a long label
+
+
+ }
+ >
+
+ Level 2 item
+
+
+ Level 2 item
+
+
+
+ Submenu trigger
+
+
+ }
+ >
+
+ Level 3 item
+
+
+ Level 3 item
+
+
+
+
+);
+WithSubmenu.args = {
+ ...Default.args,
+};
+
+export const WithCheckboxes: StoryFn< typeof DropdownMenu > = ( props ) => {
+ const [ isAChecked, setAChecked ] = useState( false );
+ const [ isBChecked, setBChecked ] = useState( true );
+ const [ multipleCheckboxesValue, setMultipleCheckboxesValue ] = useState<
+ string[]
+ >( [ 'b' ] );
+
+ const onMultipleCheckboxesCheckedChange: React.ComponentProps<
+ typeof DropdownMenuCheckboxItem
+ >[ 'onChange' ] = ( e ) => {
+ setMultipleCheckboxesValue( ( prevValues ) => {
+ if ( prevValues.includes( e.target.value ) ) {
+ return prevValues.filter( ( val ) => val !== e.target.value );
+ }
+ return [ ...prevValues, e.target.value ];
+ } );
+ };
+
+ return (
+
+
+
+
+ Checkbox item A
+
+
+ Uncontrolled
+
+
+
+
+ Checkbox item B
+
+
+ Uncontrolled, initially checked
+
+
+
+
+
+ setAChecked( e.target.checked ) }
+ >
+
+ Checkbox item A
+
+
+ Controlled
+
+
+ setBChecked( e.target.checked ) }
+ >
+
+ Checkbox item B
+
+
+ Controlled, initially checked
+
+
+
+
+
+
+
+ Checkbox item A
+
+
+ Uncontrolled, multiple selection
+
+
+
+
+ Checkbox item B
+
+
+ Uncontrolled, multiple selection, initially checked
+
+
+
+
+
+
+
+ Checkbox item A
+
+
+ Controlled, multiple selection
+
+
+
+
+ Checkbox item B
+
+
+ Controlled, multiple selection, initially checked
+
+
+
+
);
};
+WithCheckboxes.args = {
+ ...Default.args,
+};
-const RadioItemsGroup = () => {
- const [ radioValue, setRadioValue ] = useState( 'radio-one' );
+export const WithRadios: StoryFn< typeof DropdownMenu > = ( props ) => {
+ const [ radioValue, setRadioValue ] = useState( 'two' );
+ const onRadioChange: React.ComponentProps<
+ typeof DropdownMenuRadioItem
+ >[ 'onChange' ] = ( e ) => setRadioValue( e.target.value );
return (
-
- Radio group label
-
- Radio item one
-
-
- Radio item two
-
-
+
+
+
+ Radio item 1
+
+ Uncontrolled
+
+
+
+ Radio item 2
+
+ Uncontrolled, initially checked
+
+
+
+
+
+
+ Radio item 1
+
+ Controlled
+
+
+
+ Radio item 2
+
+ Controlled, initially checked
+
+
+
+
);
};
+WithRadios.args = {
+ ...Default.args,
+};
-const Template: StoryFn< typeof DropdownMenu > = ( props ) => (
-
-);
-export const Default = Template.bind( {} );
-Default.args = {
- trigger: ,
- sideOffset: 12,
- children: (
+const modalOnTopOfDropdown = css`
+ && {
+ z-index: 1000000;
+ }
+`;
+
+// For more examples with `Modal`, check https://ariakit.org/examples/menu-wordpress-modal
+export const WithModals: StoryFn< typeof DropdownMenu > = ( props ) => {
+ const [ isOuterModalOpen, setOuterModalOpen ] = useState( false );
+ const [ isInnerModalOpen, setInnerModalOpen ] = useState( false );
+
+ const cx = useCx();
+ const modalOverlayClassName = cx( modalOnTopOfDropdown );
+
+ return (
<>
-
- Menu item
+
}
+ onClick={ () => setOuterModalOpen( true ) }
+ hideOnClick={ false }
>
- Menu item with prefix
-
- ⌥⌘T }>
- Menu item with suffix
+
+ Open outer modal
+
- Disabled menu item
- Submenu
- }
+ setInnerModalOpen( true ) }
+ hideOnClick={ false }
>
- ⌘+S }>
- Submenu item with suffix
-
-
-
- Submenu item
-
- With additional custom text
-
-
-
-
-
- Second level submenu
-
- }
+
+ Open inner modal
+
+
+ { isInnerModalOpen && (
+ setInnerModalOpen( false ) }
+ overlayClassName={ modalOverlayClassName }
>
- Submenu item
- Submenu item
-
-
-
+ Modal's contents
+ setInnerModalOpen( false ) }>
+ Close
+
+
+ ) }
+
+ { isOuterModalOpen && (
+ setOuterModalOpen( false ) }
+ overlayClassName={ modalOverlayClassName }
+ >
+ Modal's contents
+ setOuterModalOpen( false ) }>
+ Close
+
+
+ ) }
+ >
+ );
+};
+WithModals.args = {
+ ...Default.args,
+};
-
+const ExampleSlotFill = createSlotFill( 'Example' );
-
+const Slot = () => {
+ const dropdownMenuContext = useContext( DropdownMenuContext );
-
+ // Forwarding the content of the slot so that it can be used by the fill
+ const fillProps = useMemo(
+ () => ( {
+ forwardedContext: [
+ [
+ DropdownMenuContext.Provider,
+ { value: dropdownMenuContext },
+ ],
+ ],
+ } ),
+ [ dropdownMenuContext ]
+ );
-
- >
- ),
+ return (
+
+ );
+};
+
+type ForwardedContextTuple< P = {} > = [
+ React.ComponentType< React.PropsWithChildren< P > >,
+ P,
+];
+
+const Fill = ( { children }: { children: React.ReactNode } ) => {
+ const innerMarkup = <>{ children }>;
+
+ return (
+
+ { ( fillProps: { forwardedContext?: ForwardedContextTuple[] } ) => {
+ const { forwardedContext = [] } = fillProps;
+
+ return forwardedContext.reduce(
+ ( inner: JSX.Element, [ Provider, props ] ) => (
+ { inner }
+ ),
+ innerMarkup
+ );
+ } }
+
+ );
+};
+
+export const WithSlotFill: StoryFn< typeof DropdownMenu > = ( props ) => {
+ return (
+
+
+
+ Item
+
+
+
+
+
+
+
+ Item from fill
+
+
+
+
+ Submenu from fill
+
+
+ }
+ >
+
+
+ Submenu item from fill
+
+
+
+
+
+ );
+};
+WithSlotFill.args = {
+ ...Default.args,
};
const toolbarVariantContextValue = {
@@ -212,10 +528,90 @@ const toolbarVariantContextValue = {
},
};
export const ToolbarVariant: StoryFn< typeof DropdownMenu > = ( props ) => (
+ // TODO: add toolbar
-
+
+
+ Level 1 item
+
+
+ Level 1 item
+
+
+
+
+ Submenu trigger
+
+
+ }
+ >
+
+ Level 2 item
+
+
+
);
ToolbarVariant.args = {
...Default.args,
};
+
+export const InsideModal: StoryFn< typeof DropdownMenu > = ( props ) => {
+ const [ isModalOpen, setModalOpen ] = useState( false );
+ return (
+ <>
+ setModalOpen( true ) }
+ __next40pxDefaultSize
+ variant="secondary"
+ >
+ Open modal
+
+ { isModalOpen && (
+ setModalOpen( false ) }>
+
+
+
+ Level 1 item
+
+
+
+
+ Level 1 item
+
+
+
+
+
+ Submenu trigger
+
+
+ }
+ >
+
+
+ Level 2 item
+
+
+
+
+ setModalOpen( false ) }>
+ Close modal
+
+
+ ) }
+ >
+ );
+};
+InsideModal.args = {
+ ...Default.args,
+};
+InsideModal.parameters = {
+ docs: {
+ source: { type: 'code' },
+ },
+};
diff --git a/packages/components/src/dropdown-menu-v2/styles.ts b/packages/components/src/dropdown-menu-v2/styles.ts
index cdeeed077b5f3a..ec6b2cb74d2172 100644
--- a/packages/components/src/dropdown-menu-v2/styles.ts
+++ b/packages/components/src/dropdown-menu-v2/styles.ts
@@ -1,9 +1,10 @@
/**
* External dependencies
*/
-import styled from '@emotion/styled';
+// eslint-disable-next-line no-restricted-imports
+import * as Ariakit from '@ariakit/react';
import { css, keyframes } from '@emotion/react';
-import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
+import styled from '@emotion/styled';
/**
* Internal dependencies
@@ -11,7 +12,8 @@ import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
import { COLORS, font, rtl, CONFIG } from '../utils';
import { space } from '../utils/space';
import Icon from '../icon';
-import type { DropdownMenuInternalContext } from './types';
+import { Truncate } from '../truncate';
+import type { DropdownMenuContext } from './types';
const ANIMATION_PARAMS = {
SLIDE_AMOUNT: '2px',
@@ -19,13 +21,16 @@ const ANIMATION_PARAMS = {
EASING: 'cubic-bezier( 0.16, 1, 0.3, 1 )',
};
-const CONTENT_WRAPPER_PADDING = space( 2 );
-const ITEM_PREFIX_WIDTH = space( 7 );
-const ITEM_PADDING_INLINE_START = space( 2 );
-const ITEM_PADDING_INLINE_END = space( 2.5 );
+const CONTENT_WRAPPER_PADDING = space( 1 );
+const ITEM_PADDING_BLOCK = space( 2 );
+const ITEM_PADDING_INLINE = space( 3 );
-// TODO: should bring this into the config, and make themeable
-const DEFAULT_BORDER_COLOR = COLORS.ui.borderDisabled;
+// TODO:
+// - those values are different from saved variables?
+// - should bring this into the config, and make themeable
+// - border color and divider color are different?
+const DEFAULT_BORDER_COLOR = COLORS.gray[ 300 ];
+const DIVIDER_COLOR = COLORS.gray[ 200 ];
const TOOLBAR_VARIANT_BORDER_COLOR = COLORS.gray[ '900' ];
const DEFAULT_BOX_SHADOW = `0 0 0 ${ CONFIG.borderWidth } ${ DEFAULT_BORDER_COLOR }, ${ CONFIG.popoverShadow }`;
const TOOLBAR_VARIANT_BOX_SHADOW = `0 0 0 ${ CONFIG.borderWidth } ${ TOOLBAR_VARIANT_BORDER_COLOR }`;
@@ -62,217 +67,280 @@ const slideLeftAndFade = keyframes( {
'100%': { opacity: 1, transform: 'translateX(0)' },
} );
-const baseContent = (
- variant: DropdownMenuInternalContext[ 'variant' ]
-) => css`
- min-width: 220px;
- background-color: ${ COLORS.ui.background };
- border-radius: ${ CONFIG.radiusBlockUi };
+export const DropdownMenu = styled( Ariakit.Menu )<
+ Pick< DropdownMenuContext, 'variant' >
+>`
+ position: relative;
+ /* Same as popover component */
+ /* TODO: is there a way to read the sass variable? */
+ z-index: 1000000;
+
+ display: grid;
+ grid-template-columns: minmax( 0, max-content ) 1fr;
+ grid-template-rows: auto;
+
+ box-sizing: border-box;
+ min-width: 160px;
+ max-width: 320px;
+ max-height: var( --popover-available-height );
padding: ${ CONTENT_WRAPPER_PADDING };
- box-shadow: ${ variant === 'toolbar'
- ? TOOLBAR_VARIANT_BOX_SHADOW
- : DEFAULT_BOX_SHADOW };
+
+ background-color: ${ COLORS.ui.background };
+ border-radius: 4px;
+ ${ ( props ) => css`
+ box-shadow: ${ props.variant === 'toolbar'
+ ? TOOLBAR_VARIANT_BOX_SHADOW
+ : DEFAULT_BOX_SHADOW };
+ ` }
+
+ overscroll-behavior: contain;
+ overflow: auto;
+
+ /* Only visible in Windows High Contrast mode */
+ outline: 2px solid transparent !important;
+
+ /* Animation */
animation-duration: ${ ANIMATION_PARAMS.DURATION };
animation-timing-function: ${ ANIMATION_PARAMS.EASING };
will-change: transform, opacity;
-
- &[data-side='top'] {
- animation-name: ${ slideDownAndFade };
- }
+ /* Default animation.*/
+ animation-name: ${ slideDownAndFade };
&[data-side='right'] {
animation-name: ${ slideLeftAndFade };
}
-
&[data-side='bottom'] {
animation-name: ${ slideUpAndFade };
}
-
&[data-side='left'] {
animation-name: ${ slideRightAndFade };
}
-
@media ( prefers-reduced-motion ) {
animation-duration: 0s;
}
`;
-const itemPrefix = css`
- width: ${ ITEM_PREFIX_WIDTH };
- display: inline-flex;
- align-items: center;
- justify-content: center;
- /* Prefixes don't get affected by the item's inline start padding */
- margin-inline-start: calc( -1 * ${ ITEM_PADDING_INLINE_START } );
- /*
- Negative margin allows the suffix to be as tall as the whole item
- (incl. padding) before increasing the items' height. This can be useful,
- e.g., when using icons that are bigger than 20x20 px
- */
- margin-top: ${ space( -2 ) };
- margin-bottom: ${ space( -2 ) };
-`;
-
-const itemSuffix = css`
- width: max-content;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- /* Push prefix to the inline-end of the item */
- margin-inline-start: auto;
- /* Minimum space between the item's content and suffix */
- padding-inline-start: ${ space( 6 ) };
- /*
- Negative margin allows the suffix to be as tall as the whole item
- (incl. padding) before increasing the items' height. This can be useful,
- e.g., when using icons that are bigger than 20x20 px
- */
- margin-top: ${ space( -2 ) };
- margin-bottom: ${ space( -2 ) };
-
- /*
- Override color in normal conditions, but inherit the item's color
- for altered conditions.
-
- TODO:
- - For now, used opacity like for disabled item, which makes it work
- regardless of the theme
- - how do we translate this for themes? Should we have a new variable
- for "secondary" text?
- */
- opacity: 0.6;
+const baseItem = css`
+ all: unset;
- [data-highlighted] > &,
- [data-state='open'] > &,
- [data-disabled] > & {
- opacity: 1;
- }
-`;
+ position: relative;
+ min-height: ${ space( 10 ) };
+ box-sizing: border-box;
-export const ItemPrefixWrapper = styled.span`
- ${ itemPrefix }
-`;
+ /* Occupy the width of all grid columns (ie. full width) */
+ grid-column: 1 / -1;
-export const ItemSuffixWrapper = styled.span`
- ${ itemSuffix }
-`;
+ /*
+ * Define a grid layout which inherits the same columns configuration
+ * from the parent layout (ie. subgrid).
+ */
+ display: grid;
+ grid-template-columns: subgrid;
+ align-items: center;
-const baseItem = css`
- all: unset;
font-size: ${ font( 'default.fontSize' ) };
font-family: inherit;
font-weight: normal;
line-height: 20px;
+
color: ${ COLORS.gray[ 900 ] };
border-radius: ${ CONFIG.radiusBlockUi };
- display: flex;
- align-items: center;
- padding: ${ space( 2 ) } ${ ITEM_PADDING_INLINE_END } ${ space( 2 ) }
- ${ ITEM_PADDING_INLINE_START };
- position: relative;
+
+ padding-block: ${ ITEM_PADDING_BLOCK };
+ padding-inline: ${ ITEM_PADDING_INLINE };
+
+ /*
+ * Make sure that, when an item is scrolled into view (eg. while using the
+ * keyboard to move focus), the whole item comes into view
+ */
+ scroll-margin: ${ CONTENT_WRAPPER_PADDING };
+
user-select: none;
outline: none;
- &[data-disabled] {
- /*
- TODO:
- - we need a disabled color in the Theme variables
- - design specs use opacity instead of setting a new text color
- */
- opacity: 0.5;
- pointer-events: none;
+ &[aria-disabled='true'] {
+ color: ${ COLORS.ui.textDisabled };
+ cursor: not-allowed;
+ }
+
+ /* Hover */
+ &[data-active-item]:not( [data-focus-visible] ):not(
+ [aria-disabled='true']
+ ) {
+ background-color: ${ COLORS.theme.accent };
+ color: ${ COLORS.white };
}
- /* Hover and Focus styles */
- &[data-highlighted] {
- /* TODO: reconcile with global focus styles */
- background-color: ${ COLORS.gray[ '100' ] };
+ /* Keyboard focus (focus-visible) */
+ &[data-focus-visible] {
+ box-shadow: 0 0 0 1.5px var( --wp-admin-theme-color );
/* Only visible in Windows High Contrast mode */
outline: 2px solid transparent;
}
- svg {
- fill: currentColor;
+ /* Active (ie. pressed, mouse down) */
+ &:active,
+ &[data-active] {
+ /* TODO: should there be a visual active state? */
+ }
+
+ /* When the item is the trigger of an open submenu */
+ ${ DropdownMenu }:not(:focus) &:not(:focus)[aria-expanded="true"] {
+ background-color: ${ COLORS.gray[ 100 ] };
+ color: ${ COLORS.gray[ 900 ] };
}
- &:not( :has( ${ ItemPrefixWrapper } ) ) {
- padding-inline-start: ${ ITEM_PREFIX_WIDTH };
+ svg {
+ fill: currentColor;
}
`;
-export const Content = styled( DropdownMenu.Content )<
- Pick< DropdownMenuInternalContext, 'variant' >
->`
- ${ ( props ) => baseContent( props.variant ) }
+export const DropdownMenuItem = styled( Ariakit.MenuItem )`
+ ${ baseItem };
`;
-export const SubContent = styled( DropdownMenu.SubContent )<
- Pick< DropdownMenuInternalContext, 'variant' >
->`
- ${ ( props ) => baseContent( props.variant ) }
+
+export const DropdownMenuCheckboxItem = styled( Ariakit.MenuItemCheckbox )`
+ ${ baseItem };
`;
-export const Item = styled( DropdownMenu.Item )`
- ${ baseItem }
+export const DropdownMenuRadioItem = styled( Ariakit.MenuItemRadio )`
+ ${ baseItem };
`;
-export const CheckboxItem = styled( DropdownMenu.CheckboxItem )`
- ${ baseItem }
+
+export const ItemPrefixWrapper = styled.span`
+ /* Always occupy the first column, even when auto-collapsing */
+ grid-column: 1;
+
+ /*
+ * Even when the item is not checked, occupy the same screen space to avoid
+ * the space collapside when no items are checked.
+ */
+ ${ DropdownMenuCheckboxItem } > &,
+ ${ DropdownMenuRadioItem } > & {
+ /* Same width as the check icons */
+ min-width: ${ space( 6 ) };
+ }
+
+ ${ DropdownMenuCheckboxItem } > &,
+ ${ DropdownMenuRadioItem } > &,
+ &:not( :empty ) {
+ margin-inline-end: ${ space( 2 ) };
+ }
+
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ color: ${ COLORS.gray[ '700' ] };
+
+ /*
+ * When the parent menu item is active, except when it's a non-focused/hovered
+ * submenu trigger (in that case, color should not be inherited)
+ */
+ [data-active-item]:not( [data-focus-visible] ) > &,
+ /* When the parent menu item is disabled */
+ [aria-disabled='true'] > & {
+ color: inherit;
+ }
`;
-export const RadioItem = styled( DropdownMenu.RadioItem )`
- ${ baseItem }
+
+export const DropdownMenuItemContentWrapper = styled.div`
+ /*
+ * Always occupy the second column, since the first column
+ * is taken by the prefix wrapper (when displayed).
+ */
+ grid-column: 2;
+
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: ${ space( 3 ) };
+
+ pointer-events: none;
`;
-export const SubTrigger = styled( DropdownMenu.SubTrigger )`
- ${ baseItem }
- &[data-state='open'] {
- background-color: ${ COLORS.gray[ '100' ] };
- }
+export const DropdownMenuItemChildrenWrapper = styled.div`
+ flex: 1;
+
+ display: inline-flex;
+ flex-direction: column;
+ gap: ${ space( 1 ) };
`;
-export const Label = styled( DropdownMenu.Label )`
- box-sizing: border-box;
+export const ItemSuffixWrapper = styled.span`
+ flex: 0 1 fit-content;
+ min-width: 0;
+ width: fit-content;
+
display: flex;
align-items: center;
- min-height: ${ space( 8 ) };
-
- padding: ${ space( 2 ) } ${ ITEM_PADDING_INLINE_END } ${ space( 2 ) }
- ${ ITEM_PREFIX_WIDTH };
- /* TODO: color doesn't match available UI variables */
- color: ${ COLORS.gray[ 700 ] };
-
- /* TODO: font size doesn't match available ones via "font" utils */
- font-size: 11px;
- line-height: 1.4;
- font-weight: 500;
- text-transform: uppercase;
+ justify-content: center;
+ gap: ${ space( 3 ) };
+
+ color: ${ COLORS.gray[ '700' ] };
+
+ /*
+ * When the parent menu item is active, except when it's a non-focused/hovered
+ * submenu trigger (in that case, color should not be inherited)
+ */
+ [data-active-item]:not( [data-focus-visible] ) *:not(${ DropdownMenu }) &,
+ /* When the parent menu item is disabled */
+ [aria-disabled='true'] *:not(${ DropdownMenu }) & {
+ color: inherit;
+ }
+`;
+
+export const DropdownMenuGroup = styled( Ariakit.MenuGroup )`
+ /* Ignore this element when calculating the layout. Useful for subgrid */
+ display: contents;
`;
-export const Separator = styled( DropdownMenu.Separator )<
- Pick< DropdownMenuInternalContext, 'variant' >
+export const DropdownMenuSeparator = styled( Ariakit.MenuSeparator )<
+ Pick< DropdownMenuContext, 'variant' >
>`
+ /* Occupy the width of all grid columns (ie. full width) */
+ grid-column: 1 / -1;
+
+ border: none;
height: ${ CONFIG.borderWidth };
- /* TODO: doesn't match border color from variables */
background-color: ${ ( props ) =>
props.variant === 'toolbar'
? TOOLBAR_VARIANT_BORDER_COLOR
- : DEFAULT_BORDER_COLOR };
- /* Negative horizontal margin to make separator go from side to side */
- margin: ${ space( 2 ) } calc( -1 * ${ CONTENT_WRAPPER_PADDING } );
-`;
+ : DIVIDER_COLOR };
+ /* Align with menu items' content */
+ margin-block: ${ space( 2 ) };
+ margin-inline: ${ ITEM_PADDING_INLINE };
-export const ItemIndicator = styled( DropdownMenu.ItemIndicator )`
- display: inline-flex;
- align-items: center;
- justify-content: center;
+ /* Only visible in Windows High Contrast mode */
+ outline: 2px solid transparent;
`;
-export const SubmenuRtlChevronIcon = styled( Icon )`
+export const SubmenuChevronIcon = styled( Icon )`
+ width: ${ space( 1.5 ) };
${ rtl(
{
- transform: `scaleX(1) translateX(${ space( 2 ) })`,
+ transform: `scaleX(1)`,
},
{
- transform: `scaleX(-1) translateX(${ space( 2 ) })`,
+ transform: `scaleX(-1)`,
}
- ) }
+ ) };
+`;
+
+export const DropdownMenuItemLabel = styled( Truncate )`
+ font-size: ${ font( 'default.fontSize' ) };
+ line-height: 20px;
+ color: inherit;
+`;
+
+export const DropdownMenuItemHelpText = styled( Truncate )`
+ font-size: ${ font( 'helpText.fontSize' ) };
+ line-height: 16px;
+ color: ${ COLORS.gray[ '700' ] };
+
+ [data-active-item]:not( [data-focus-visible] ) *:not( ${ DropdownMenu } ) &,
+ [aria-disabled='true'] *:not( ${ DropdownMenu } ) & {
+ color: inherit;
+ }
`;
diff --git a/packages/components/src/dropdown-menu-v2/test/index.tsx b/packages/components/src/dropdown-menu-v2/test/index.tsx
index 3138c74557ae1a..f58639a545a056 100644
--- a/packages/components/src/dropdown-menu-v2/test/index.tsx
+++ b/packages/components/src/dropdown-menu-v2/test/index.tsx
@@ -2,10 +2,7 @@
* External dependencies
*/
import { render, screen, waitFor } from '@testing-library/react';
-import {
- default as userEvent,
- PointerEventsCheckLevel,
-} from '@testing-library/user-event';
+import { press, click, hover, type } from '@ariakit/test';
/**
* WordPress dependencies
@@ -19,12 +16,9 @@ import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuItem,
- DropdownMenuLabel,
- DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
- DropdownSubMenu,
- DropdownSubMenuTrigger,
+ DropdownMenuGroup,
} from '..';
const delay = ( delayInMs: number ) => {
@@ -34,23 +28,18 @@ const delay = ( delayInMs: number ) => {
describe( 'DropdownMenu', () => {
// See https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/
it( 'should follow the WAI-ARIA spec', async () => {
- // Radio and Checkbox items'
- const user = userEvent.setup();
-
render(
Open dropdown }>
Dropdown menu item
-
- Dropdown submenu
-
+ Dropdown submenu
}
>
Dropdown submenu item 1
Dropdown submenu item 2
-
+
);
@@ -61,11 +50,13 @@ describe( 'DropdownMenu', () => {
expect( toggleButton ).toHaveAttribute( 'aria-haspopup', 'menu' );
expect( toggleButton ).toHaveAttribute( 'aria-expanded', 'false' );
- await user.click( toggleButton );
+ await click( toggleButton );
expect( toggleButton ).toHaveAttribute( 'aria-expanded', 'true' );
- expect( screen.getByRole( 'menu' ) ).toHaveFocus();
+ expect(
+ screen.getByRole( 'menu', { name: toggleButton.textContent ?? '' } )
+ ).toHaveFocus();
expect( screen.getByRole( 'separator' ) ).toHaveAttribute(
'aria-orientation',
'horizontal'
@@ -78,11 +69,15 @@ describe( 'DropdownMenu', () => {
expect( submenuTrigger ).toHaveAttribute( 'aria-haspopup', 'menu' );
expect( submenuTrigger ).toHaveAttribute( 'aria-expanded', 'false' );
- await user.hover( submenuTrigger );
+ await hover( submenuTrigger );
// Wait for the open animation after hovering
await waitFor( () =>
- expect( screen.getAllByRole( 'menu' ) ).toHaveLength( 2 )
+ expect(
+ screen.getByRole( 'menu', {
+ name: submenuTrigger.textContent ?? '',
+ } )
+ ).toBeVisible()
);
expect( submenuTrigger ).toHaveAttribute( 'aria-expanded', 'true' );
@@ -93,9 +88,7 @@ describe( 'DropdownMenu', () => {
} );
describe( 'pointer and keyboard interactions', () => {
- it( 'should open when clicking the trigger', async () => {
- const user = userEvent.setup();
-
+ it( 'should open and focus the menu when clicking the trigger', async () => {
render(
Open dropdown }>
Dropdown menu item
@@ -106,24 +99,52 @@ describe( 'DropdownMenu', () => {
name: 'Open dropdown',
} );
- // DropdownMenu closed, the content is not displayed
+ // DropdownMenu closed
expect( screen.queryByRole( 'menu' ) ).not.toBeInTheDocument();
- expect( screen.queryByRole( 'menuitem' ) ).not.toBeInTheDocument();
// Click to open the menu
- await user.click( toggleButton );
+ await click( toggleButton );
- // DropdownMenu open, the content is displayed
- expect( screen.getByRole( 'menu' ) ).toBeInTheDocument();
- expect( screen.getByRole( 'menuitem' ) ).toBeInTheDocument();
+ // DropdownMenu open, focus is on the menu wrapper
+ expect( screen.getByRole( 'menu' ) ).toHaveFocus();
} );
- it( 'should open when pressing the arrow down key on the trigger', async () => {
- const user = userEvent.setup();
+ it( 'should open and focus the first item when pressing the arrow down key on the trigger', async () => {
+ render(
+ Open dropdown }>
+ First item
+ Second item
+ Third item
+
+ );
+
+ const toggleButton = screen.getByRole( 'button', {
+ name: 'Open dropdown',
+ } );
+
+ // Move focus on the toggle
+ await press.Tab();
+
+ expect( toggleButton ).toHaveFocus();
+
+ // DropdownMenu closed
+ expect( screen.queryByRole( 'menuitem' ) ).not.toBeInTheDocument();
+
+ await press.ArrowDown();
+
+ // DropdownMenu open, focus is on the first focusable item
+ // (disabled items are still focusable and accessible)
+ expect(
+ screen.getByRole( 'menuitem', { name: 'First item' } )
+ ).toHaveFocus();
+ } );
+ it( 'should open and focus the first item when pressing the space key on the trigger', async () => {
render(
Open dropdown }>
- Dropdown menu item
+ First item
+ Second item
+ Third item
);
@@ -132,50 +153,52 @@ describe( 'DropdownMenu', () => {
} );
// Move focus on the toggle
- await user.keyboard( '{Tab}' );
+ await press.Tab();
expect( toggleButton ).toHaveFocus();
- // DropdownMenu closed, the content is not displayed
+ // DropdownMenu closed
expect( screen.queryByRole( 'menuitem' ) ).not.toBeInTheDocument();
- await user.keyboard( '{ArrowDown}' );
+ await press.Space();
- // DropdownMenu open, the content is displayed
- expect( screen.getByRole( 'menuitem' ) ).toBeInTheDocument();
+ // DropdownMenu open, focus is on the first focusable item
+ // (disabled items are still focusable and accessible
+ expect(
+ screen.getByRole( 'menuitem', { name: 'First item' } )
+ ).toHaveFocus();
} );
it( 'should close when pressing the escape key', async () => {
- const user = userEvent.setup();
-
render(
- Open dropdown }
- >
+ Open dropdown }>
Dropdown menu item
);
- // The menu is focused automatically when `defaultOpen` is set.
+ const trigger = screen.getByRole( 'button', {
+ name: 'Open dropdown',
+ } );
+
+ await click( trigger );
+
+ // Focuses menu on mouse click, focuses first item on keyboard press
+ // Can be changed with a custom useEffect
expect( screen.getByRole( 'menu' ) ).toHaveFocus();
// Pressing esc will close the menu and move focus to the toggle
- await user.keyboard( '{Escape}' );
+ await press.Escape();
expect( screen.queryByRole( 'menu' ) ).not.toBeInTheDocument();
- expect(
- screen.getByRole( 'button', { name: 'Open dropdown' } )
- ).toHaveFocus();
+
+ await waitFor( () =>
+ expect(
+ screen.getByRole( 'button', { name: 'Open dropdown' } )
+ ).toHaveFocus()
+ );
} );
it( 'should close when clicking outside of the content', async () => {
- const user = userEvent.setup( {
- // Disabling this check otherwise testing-library would complain
- // when clicking on document.body to close the dropdown menu.
- pointerEventsCheck: PointerEventsCheckLevel.Never,
- } );
-
render(
{
expect( screen.getByRole( 'menu' ) ).toBeInTheDocument();
// Click on the body (ie. outside of the dropdown menu)
- await user.click( document.body );
+ await click( document.body );
expect( screen.queryByRole( 'menu' ) ).not.toBeInTheDocument();
} );
it( 'should close when clicking on a menu item', async () => {
- const user = userEvent.setup();
-
render(
{
expect( screen.getByRole( 'menu' ) ).toBeInTheDocument();
// Clicking a menu item will close the menu
- await user.click( screen.getByRole( 'menuitem' ) );
+ await click( screen.getByRole( 'menuitem' ) );
expect( screen.queryByRole( 'menu' ) ).not.toBeInTheDocument();
} );
- it( 'should not close when clicking on a disabled menu item', async () => {
- const user = userEvent.setup( {
- // Disabling this check otherwise testing-library would complain
- // when clicking on a disabled element with pointer-events: none
- pointerEventsCheck: PointerEventsCheckLevel.Never,
- } );
+ it( 'should not close when clicking on a menu item when the `hideOnClick` prop is set to `false`', async () => {
+ render(
+ Open dropdown }
+ >
+
+ Dropdown menu item
+
+
+ );
+
+ expect( screen.getByRole( 'menu' ) ).toBeVisible();
+
+ // Clicking a menu item will close the menu
+ await click( screen.getByRole( 'menuitem' ) );
+
+ expect( screen.getByRole( 'menu' ) ).toBeVisible();
+ } );
+ it( 'should not close when clicking on a disabled menu item', async () => {
render(
{
expect( screen.getByRole( 'menu' ) ).toBeInTheDocument();
// Clicking a disabled menu item won't close the menu
- await user.click( screen.getByRole( 'menuitem' ) );
+ await click( screen.getByRole( 'menuitem' ) );
expect( screen.getByRole( 'menu' ) ).toBeInTheDocument();
} );
it( 'should reveal submenu content when hovering over the submenu trigger', async () => {
- const user = userEvent.setup();
-
render(
{
>
Dropdown menu item 1
Dropdown menu item 2
-
+
Dropdown submenu
-
+
}
>
@@ -262,7 +295,7 @@ describe( 'DropdownMenu', () => {
Dropdown submenu item 2
-
+
Dropdown menu item 3
);
@@ -274,7 +307,7 @@ describe( 'DropdownMenu', () => {
} )
).not.toBeInTheDocument();
- await user.hover(
+ await hover(
screen.getByRole( 'menuitem', { name: 'Dropdown submenu' } )
);
@@ -287,8 +320,6 @@ describe( 'DropdownMenu', () => {
} );
it( 'should navigate menu items and subitems using the arrow, spacebar and enter keys', async () => {
- const user = userEvent.setup();
-
render(
{
>
Dropdown menu item 1
Dropdown menu item 2
-
+
Dropdown submenu
-
+
}
>
@@ -309,67 +340,69 @@ describe( 'DropdownMenu', () => {
Dropdown submenu item 2
-
+
Dropdown menu item 3
);
// The menu is focused automatically when `defaultOpen` is set.
- expect( screen.getByRole( 'menu' ) ).toHaveFocus();
+ await waitFor( () =>
+ expect( screen.getByRole( 'menu' ) ).toHaveFocus()
+ );
// Arrow up/down selects menu items
// The selection wraps around from last to first and viceversa
- await user.keyboard( '{ArrowDown}' );
+ await press.ArrowDown();
expect(
screen.getByRole( 'menuitem', { name: 'Dropdown menu item 1' } )
).toHaveFocus();
- await user.keyboard( '{ArrowDown}' );
+ await press.ArrowDown();
expect(
screen.getByRole( 'menuitem', { name: 'Dropdown menu item 2' } )
).toHaveFocus();
- await user.keyboard( '{ArrowDown}' );
+ await press.ArrowDown();
expect(
screen.getByRole( 'menuitem', { name: 'Dropdown submenu' } )
).toHaveFocus();
- await user.keyboard( '{ArrowDown}' );
+ await press.ArrowDown();
expect(
screen.getByRole( 'menuitem', { name: 'Dropdown menu item 3' } )
).toHaveFocus();
- await user.keyboard( '{ArrowDown}' );
+ await press.ArrowDown();
expect(
screen.getByRole( 'menuitem', { name: 'Dropdown menu item 1' } )
).toHaveFocus();
- await user.keyboard( '{ArrowUp}' );
+ await press.ArrowUp();
expect(
screen.getByRole( 'menuitem', { name: 'Dropdown menu item 3' } )
).toHaveFocus();
- await user.keyboard( '{ArrowUp}' );
+ await press.ArrowUp();
expect(
screen.getByRole( 'menuitem', { name: 'Dropdown submenu' } )
).toHaveFocus();
// Arrow right/left can be used to enter/leave submenus
- await user.keyboard( '{ArrowRight}' );
+ await press.ArrowRight();
expect(
screen.getByRole( 'menuitem', {
name: 'Dropdown submenu item 1',
} )
).toHaveFocus();
- await user.keyboard( '{ArrowDown}' );
+ await press.ArrowDown();
expect(
screen.getByRole( 'menuitem', {
name: 'Dropdown submenu item 2',
} )
).toHaveFocus();
- await user.keyboard( '{ArrowLeft}' );
+ await press.ArrowLeft();
expect(
screen.getByRole( 'menuitem', {
name: 'Dropdown submenu',
@@ -377,28 +410,28 @@ describe( 'DropdownMenu', () => {
).toHaveFocus();
// Spacebar or enter key can also be used to enter a submenu
- await user.keyboard( '{Enter}' );
+ await press.Enter();
expect(
screen.getByRole( 'menuitem', {
name: 'Dropdown submenu item 1',
} )
).toHaveFocus();
- await user.keyboard( '{ArrowLeft}' );
+ await press.ArrowLeft();
expect(
screen.getByRole( 'menuitem', {
name: 'Dropdown submenu',
} )
).toHaveFocus();
- await user.keyboard( '{Spacebar}' );
+ await press.Space();
expect(
screen.getByRole( 'menuitem', {
name: 'Dropdown submenu item 1',
} )
).toHaveFocus();
- await user.keyboard( '{ArrowLeft}' );
+ await press.ArrowLeft();
expect(
screen.getByRole( 'menuitem', {
name: 'Dropdown submenu',
@@ -406,32 +439,37 @@ describe( 'DropdownMenu', () => {
).toHaveFocus();
} );
- it( 'should check menu radio items', async () => {
- const user = userEvent.setup();
-
+ it( 'should check radio items and keep the menu open when clicking (controlled)', async () => {
const onRadioValueChangeSpy = jest.fn();
const ControlledRadioGroup = () => {
- const [ radioValue, setRadioValue ] = useState< string >();
+ const [ radioValue, setRadioValue ] = useState( 'two' );
+ const onRadioChange: React.ComponentProps<
+ typeof DropdownMenuRadioItem
+ >[ 'onChange' ] = ( e ) => {
+ onRadioValueChangeSpy( e.target.value );
+ setRadioValue( e.target.value );
+ };
return (
Open dropdown }>
- {
- onRadioValueChangeSpy( value );
- setRadioValue( value );
- } }
- >
-
- Radio group label
-
-
+
+
Radio item one
-
+
Radio item two
-
+
);
};
@@ -439,7 +477,7 @@ describe( 'DropdownMenu', () => {
render( );
// Open dropdown
- await user.click(
+ await click(
screen.getByRole( 'button', { name: 'Open dropdown' } )
);
@@ -453,7 +491,7 @@ describe( 'DropdownMenu', () => {
).not.toBeChecked();
// Click first radio item, make sure that the callback fires
- await user.click(
+ await click(
screen.getByRole( 'menuitemradio', { name: 'Radio item one' } )
);
expect( onRadioValueChangeSpy ).toHaveBeenCalledTimes( 1 );
@@ -461,11 +499,6 @@ describe( 'DropdownMenu', () => {
'radio-one'
);
- // Open dropdown
- await user.click(
- screen.getByRole( 'button', { name: 'Open dropdown' } )
- );
-
// Make sure that first radio is checked
expect(
screen.getByRole( 'menuitemradio', { name: 'Radio item one' } )
@@ -475,7 +508,7 @@ describe( 'DropdownMenu', () => {
).not.toBeChecked();
// Click second radio item, make sure that the callback fires
- await user.click(
+ await click(
screen.getByRole( 'menuitemradio', { name: 'Radio item two' } )
);
expect( onRadioValueChangeSpy ).toHaveBeenCalledTimes( 2 );
@@ -483,11 +516,83 @@ describe( 'DropdownMenu', () => {
'radio-two'
);
+ // Make sure that second radio is selected
+ expect(
+ screen.getByRole( 'menuitemradio', { name: 'Radio item one' } )
+ ).not.toBeChecked();
+ expect(
+ screen.getByRole( 'menuitemradio', { name: 'Radio item two' } )
+ ).toBeChecked();
+ } );
+
+ it( 'should check radio items and keep the menu open when clicking (uncontrolled)', async () => {
+ const onRadioValueChangeSpy = jest.fn();
+ render(
+ Open dropdown }>
+
+
+ onRadioValueChangeSpy( e.target.value )
+ }
+ >
+ Radio item one
+
+
+ onRadioValueChangeSpy( e.target.value )
+ }
+ >
+ Radio item two
+
+
+
+ );
+
// Open dropdown
- await user.click(
+ await click(
screen.getByRole( 'button', { name: 'Open dropdown' } )
);
+ // Radio item two should be checked (`defaultChecked` prop)
+ expect( screen.getAllByRole( 'menuitemradio' ) ).toHaveLength( 2 );
+ expect(
+ screen.getByRole( 'menuitemradio', { name: 'Radio item one' } )
+ ).not.toBeChecked();
+ expect(
+ screen.getByRole( 'menuitemradio', { name: 'Radio item two' } )
+ ).toBeChecked();
+
+ // Click first radio item, make sure that the callback fires
+ await click(
+ screen.getByRole( 'menuitemradio', { name: 'Radio item one' } )
+ );
+ expect( onRadioValueChangeSpy ).toHaveBeenCalledTimes( 1 );
+ expect( onRadioValueChangeSpy ).toHaveBeenLastCalledWith(
+ 'radio-one'
+ );
+
+ // Make sure that first radio is checked
+ expect(
+ screen.getByRole( 'menuitemradio', { name: 'Radio item one' } )
+ ).toBeChecked();
+ expect(
+ screen.getByRole( 'menuitemradio', { name: 'Radio item two' } )
+ ).not.toBeChecked();
+
+ // Click second radio item, make sure that the callback fires
+ await click(
+ screen.getByRole( 'menuitemradio', { name: 'Radio item two' } )
+ );
+ expect( onRadioValueChangeSpy ).toHaveBeenCalledTimes( 2 );
+ expect( onRadioValueChangeSpy ).toHaveBeenLastCalledWith(
+ 'radio-two'
+ );
+
// Make sure that second radio is selected
expect(
screen.getByRole( 'menuitemradio', { name: 'Radio item one' } )
@@ -497,9 +602,7 @@ describe( 'DropdownMenu', () => {
).toBeChecked();
} );
- it( 'should check menu checkbox items', async () => {
- const user = userEvent.setup();
-
+ it( 'should check checkbox items and keep the menu open when clicking (controlled)', async () => {
const onCheckboxValueChangeSpy = jest.fn();
const ControlledRadioGroup = () => {
@@ -507,26 +610,36 @@ describe( 'DropdownMenu', () => {
useState< boolean >();
const [ itemTwoChecked, setItemTwoChecked ] =
useState< boolean >();
+
return (
Open dropdown }>
-
- Checkbox group label
-
{
- setItemOneChecked( checked );
- onCheckboxValueChangeSpy( 'item-one', checked );
+ onChange={ ( e ) => {
+ onCheckboxValueChangeSpy(
+ e.target.name,
+ e.target.value,
+ e.target.checked
+ );
+ setItemOneChecked( e.target.checked );
} }
>
Checkbox item one
{
- setItemTwoChecked( checked );
- onCheckboxValueChangeSpy( 'item-two', checked );
+ onChange={ ( e ) => {
+ onCheckboxValueChangeSpy(
+ e.target.name,
+ e.target.value,
+ e.target.checked
+ );
+ setItemTwoChecked( e.target.checked );
} }
>
Checkbox item two
@@ -538,7 +651,7 @@ describe( 'DropdownMenu', () => {
render( );
// Open dropdown
- await user.click(
+ await click(
screen.getByRole( 'button', { name: 'Open dropdown' } )
);
@@ -558,7 +671,7 @@ describe( 'DropdownMenu', () => {
).not.toBeChecked();
// Click first checkbox item, make sure that the callback fires
- await user.click(
+ await click(
screen.getByRole( 'menuitemcheckbox', {
name: 'Checkbox item one',
} )
@@ -566,14 +679,10 @@ describe( 'DropdownMenu', () => {
expect( onCheckboxValueChangeSpy ).toHaveBeenCalledTimes( 1 );
expect( onCheckboxValueChangeSpy ).toHaveBeenLastCalledWith(
'item-one',
+ 'item-one-value',
true
);
- // Open dropdown
- await user.click(
- screen.getByRole( 'button', { name: 'Open dropdown' } )
- );
-
// Make sure that first checkbox is checked
expect(
screen.getByRole( 'menuitemcheckbox', {
@@ -582,7 +691,7 @@ describe( 'DropdownMenu', () => {
).toBeChecked();
// Click second checkbox item, make sure that the callback fires
- await user.click(
+ await click(
screen.getByRole( 'menuitemcheckbox', {
name: 'Checkbox item two',
} )
@@ -590,14 +699,10 @@ describe( 'DropdownMenu', () => {
expect( onCheckboxValueChangeSpy ).toHaveBeenCalledTimes( 2 );
expect( onCheckboxValueChangeSpy ).toHaveBeenLastCalledWith(
'item-two',
+ 'item-two-value',
true
);
- // Open dropdown
- await user.click(
- screen.getByRole( 'button', { name: 'Open dropdown' } )
- );
-
// Make sure that second checkbox is selected
expect(
screen.getByRole( 'menuitemcheckbox', {
@@ -606,7 +711,7 @@ describe( 'DropdownMenu', () => {
).toBeChecked();
// Click second checkbox item, make sure that the callback fires
- await user.click(
+ await click(
screen.getByRole( 'menuitemcheckbox', {
name: 'Checkbox item two',
} )
@@ -614,27 +719,203 @@ describe( 'DropdownMenu', () => {
expect( onCheckboxValueChangeSpy ).toHaveBeenCalledTimes( 3 );
expect( onCheckboxValueChangeSpy ).toHaveBeenLastCalledWith(
'item-two',
+ 'item-two-value',
false
);
+ // Make sure that second checkbox is unselected
+ expect(
+ screen.getByRole( 'menuitemcheckbox', {
+ name: 'Checkbox item two',
+ } )
+ ).not.toBeChecked();
+ } );
+
+ it( 'should check checkbox items and keep the menu open when clicking (uncontrolled)', async () => {
+ const onCheckboxValueChangeSpy = jest.fn();
+
+ render(
+ Open dropdown }>
+ {
+ onCheckboxValueChangeSpy(
+ e.target.name,
+ e.target.value,
+ e.target.checked
+ );
+ } }
+ >
+ Checkbox item one
+
+
+ {
+ onCheckboxValueChangeSpy(
+ e.target.name,
+ e.target.value,
+ e.target.checked
+ );
+ } }
+ >
+ Checkbox item two
+
+
+ );
+
// Open dropdown
- await user.click(
+ await click(
screen.getByRole( 'button', { name: 'Open dropdown' } )
);
- // Make sure that second checkbox is unselected
+ // Checkbox item two should be checked (`defaultChecked`)
+ expect( screen.getAllByRole( 'menuitemcheckbox' ) ).toHaveLength(
+ 2
+ );
+ expect(
+ screen.getByRole( 'menuitemcheckbox', {
+ name: 'Checkbox item one',
+ } )
+ ).not.toBeChecked();
+ expect(
+ screen.getByRole( 'menuitemcheckbox', {
+ name: 'Checkbox item two',
+ } )
+ ).toBeChecked();
+
+ // Click first checkbox item, make sure that the callback fires
+ await click(
+ screen.getByRole( 'menuitemcheckbox', {
+ name: 'Checkbox item one',
+ } )
+ );
+ expect( onCheckboxValueChangeSpy ).toHaveBeenCalledTimes( 1 );
+ expect( onCheckboxValueChangeSpy ).toHaveBeenLastCalledWith(
+ 'item-one',
+ 'item-one-value',
+ true
+ );
+
+ // Make sure that first checkbox is checked
+ expect(
+ screen.getByRole( 'menuitemcheckbox', {
+ name: 'Checkbox item one',
+ } )
+ ).toBeChecked();
+
+ // Click second checkbox item, make sure that the callback fires
+ await click(
+ screen.getByRole( 'menuitemcheckbox', {
+ name: 'Checkbox item two',
+ } )
+ );
+ expect( onCheckboxValueChangeSpy ).toHaveBeenCalledTimes( 2 );
+ expect( onCheckboxValueChangeSpy ).toHaveBeenLastCalledWith(
+ 'item-two',
+ 'item-two-value',
+ false
+ );
+
+ // Make sure that second checkbox is unchecked
expect(
screen.getByRole( 'menuitemcheckbox', {
name: 'Checkbox item two',
} )
).not.toBeChecked();
+
+ // Click second checkbox item, make sure that the callback fires
+ await click(
+ screen.getByRole( 'menuitemcheckbox', {
+ name: 'Checkbox item two',
+ } )
+ );
+ expect( onCheckboxValueChangeSpy ).toHaveBeenCalledTimes( 3 );
+ expect( onCheckboxValueChangeSpy ).toHaveBeenLastCalledWith(
+ 'item-two',
+ 'item-two-value',
+ true
+ );
+
+ // Make sure that second checkbox is unselected
+ expect(
+ screen.getByRole( 'menuitemcheckbox', {
+ name: 'Checkbox item two',
+ } )
+ ).toBeChecked();
+ } );
+ } );
+
+ describe( 'modality', () => {
+ it( 'should be modal by default', async () => {
+ render(
+ <>
+ Open dropdown }>
+ Dropdown menu item
+
+ Button outside of dropdown
+ >
+ );
+
+ // Click to open the menu
+ await click(
+ screen.getByRole( 'button', {
+ name: 'Open dropdown',
+ } )
+ );
+
+ // DropdownMenu open, focus is on the menu wrapper
+ expect( screen.getByRole( 'menu' ) ).toHaveFocus();
+
+ expect(
+ screen.queryByRole( 'button', {
+ name: 'Button outside of dropdown',
+ } )
+ ).not.toBeInTheDocument();
+ } );
+
+ it( 'should not be modal when the `modal` prop is set to `false`', async () => {
+ render(
+ <>
+ Open dropdown }
+ modal={ false }
+ >
+ Dropdown menu item
+
+ Button outside of dropdown
+ >
+ );
+
+ // Click to open the menu
+ await click(
+ screen.getByRole( 'button', {
+ name: 'Open dropdown',
+ } )
+ );
+
+ // DropdownMenu open, focus is on the menu wrapper
+ expect( screen.getByRole( 'menu' ) ).toHaveFocus();
+
+ // DropdownMenu is not modal, therefore the outer button is part of the
+ // accessibility tree and can be found.
+ const outerButton = screen.getByRole( 'button', {
+ name: 'Button outside of dropdown',
+ } );
+
+ // The outer button can be focused by pressing tab. Doing so will cause
+ // the DropdownMenu to close.
+ await press.Tab();
+ expect( outerButton ).toBeInTheDocument();
+ expect( screen.queryByRole( 'menu' ) ).not.toBeInTheDocument();
} );
} );
describe( 'items prefix and suffix', () => {
it( 'should display a prefix on regular items', async () => {
- const user = userEvent.setup();
-
render(
Open dropdown }>
Item prefix> }>
@@ -644,7 +925,7 @@ describe( 'DropdownMenu', () => {
);
// Click to open the menu
- await user.click(
+ await click(
screen.getByRole( 'button', {
name: 'Open dropdown',
} )
@@ -659,8 +940,6 @@ describe( 'DropdownMenu', () => {
} );
it( 'should display a suffix on regular items', async () => {
- const user = userEvent.setup();
-
render(
Open dropdown }>
Item suffix> }>
@@ -670,7 +949,7 @@ describe( 'DropdownMenu', () => {
);
// Click to open the menu
- await user.click(
+ await click(
screen.getByRole( 'button', {
name: 'Open dropdown',
} )
@@ -685,23 +964,20 @@ describe( 'DropdownMenu', () => {
} );
it( 'should display a suffix on radio items', async () => {
- const user = userEvent.setup();
-
render(
Open dropdown }>
-
-
- Radio item one
-
-
+
+ Radio item one
+
);
// Click to open the menu
- await user.click(
+ await click(
screen.getByRole( 'button', {
name: 'Open dropdown',
} )
@@ -716,18 +992,20 @@ describe( 'DropdownMenu', () => {
} );
it( 'should display a suffix on checkbox items', async () => {
- const user = userEvent.setup();
-
render(
Open dropdown }>
-
+
Checkbox item one
);
// Click to open the menu
- await user.click(
+ await click(
screen.getByRole( 'button', {
name: 'Open dropdown',
} )
@@ -744,8 +1022,6 @@ describe( 'DropdownMenu', () => {
describe( 'typeahead', () => {
it( 'should highlight matching item', async () => {
- const user = userEvent.setup();
-
render(
Open dropdown }>
One
@@ -754,60 +1030,76 @@ describe( 'DropdownMenu', () => {
);
// Click to open the menu
- await user.click(
+ await click(
screen.getByRole( 'button', {
name: 'Open dropdown',
} )
);
- expect( screen.getByRole( 'menu' ) ).toBeInTheDocument();
+ expect( screen.getByRole( 'menu' ) ).toHaveFocus();
// Type "tw", it should match and focus the item with content "Two"
- await user.keyboard( 'tw' );
+ await type( 'tw' );
expect(
screen.getByRole( 'menuitem', { name: 'Two' } )
).toHaveFocus();
// Wait for the typeahead timer to reset and interpret
// the next keystrokes as a new search
- await delay( 1000 );
+ await delay( 500 );
// Type "on", it should match and focus the item with content "One"
- await user.keyboard( 'on' );
+ await type( 'on' );
expect(
screen.getByRole( 'menuitem', { name: 'One' } )
).toHaveFocus();
} );
- it( 'should use the textValue prop if specificied', async () => {
- const user = userEvent.setup();
-
+ it( 'should keep previous focus when no matches are found', async () => {
render(
Open dropdown }>
One
- Two
+ Two
);
// Click to open the menu
- await user.click(
+ await click(
screen.getByRole( 'button', {
name: 'Open dropdown',
} )
);
- expect( screen.getByRole( 'menu' ) ).toBeInTheDocument();
+ expect( screen.getByRole( 'menu' ) ).toHaveFocus();
- // Type "tw", it should not match the item with content "Two" because it
- // that item specifies the "textValue" prop. Therefore, the menu container
- // retains focus.
- await user.keyboard( 'tw' );
+ // Type a string that doesn't match any items. Focus shouldn't move.
+ await type( 'abc' );
expect( screen.getByRole( 'menu' ) ).toHaveFocus();
// Wait for the typeahead timer to reset and interpret
// the next keystrokes as a new search
- await delay( 1000 );
+ await delay( 500 );
+
+ // Type "on", it should match and focus the item with content "One"
+ await type( 'on' );
+ expect(
+ screen.getByRole( 'menuitem', { name: 'One' } )
+ ).toHaveFocus();
+
+ // Wait for the typeahead timer to reset and interpret
+ // the next keystrokes as a new search
+ await delay( 500 );
+
+ // Type a string that doesn't match any items. Focus shouldn't move.
+ await type( 'abc' );
+ expect(
+ screen.getByRole( 'menuitem', { name: 'One' } )
+ ).toHaveFocus();
- // Type "fo", it should match and focus the item with textValue "Four"
- await user.keyboard( 'fo' );
+ // Wait for the typeahead timer to reset and interpret
+ // the next keystrokes as a new search
+ await delay( 500 );
+
+ // Type "tw", it should match and focus the item with content "Two"
+ await type( 'tw' );
expect(
screen.getByRole( 'menuitem', { name: 'Two' } )
).toHaveFocus();
diff --git a/packages/components/src/dropdown-menu-v2/types.ts b/packages/components/src/dropdown-menu-v2/types.ts
index c06bb5aac626a7..478b89c67f136a 100644
--- a/packages/components/src/dropdown-menu-v2/types.ts
+++ b/packages/components/src/dropdown-menu-v2/types.ts
@@ -1,24 +1,46 @@
/**
* External dependencies
*/
-import type * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
+// eslint-disable-next-line no-restricted-imports
+import type * as Ariakit from '@ariakit/react';
+import type { Placement } from '@floating-ui/react-dom';
-export type DropdownMenuProps = {
+export interface DropdownMenuContext {
+ /**
+ * The ariakit store shared across all DropdownMenu subcomponents.
+ */
+ store: Ariakit.MenuStore;
+ /**
+ * The variant used by the underlying menu popover.
+ */
+ variant?: 'toolbar';
+}
+
+export interface DropdownMenuProps {
+ /**
+ * The trigger button.
+ */
+ trigger: React.ReactElement;
+ /**
+ * The contents of the dropdown.
+ */
+ children?: React.ReactNode;
/**
* The open state of the dropdown menu when it is initially rendered. Use when
- * you do not need to control its open state.
+ * not wanting to control its open state.
*
+ * @default false
*/
- defaultOpen?: DropdownMenuPrimitive.DropdownMenuProps[ 'defaultOpen' ];
+ defaultOpen?: boolean;
/**
* The controlled open state of the dropdown menu. Must be used in conjunction
* with `onOpenChange`.
*/
- open?: DropdownMenuPrimitive.DropdownMenuProps[ 'open' ];
+ open?: boolean;
/**
* Event handler called when the open state of the dropdown menu changes.
*/
- onOpenChange?: DropdownMenuPrimitive.DropdownMenuProps[ 'onOpenChange' ];
+ onOpenChange?: ( open: boolean ) => void;
/**
* The modality of the dropdown menu. When set to true, interaction with
* outside elements will be disabled and only menu content will be visible to
@@ -26,240 +48,132 @@ export type DropdownMenuProps = {
*
* @default true
*/
- modal?: DropdownMenuPrimitive.DropdownMenuProps[ 'modal' ];
+ modal?: boolean;
/**
- * The preferred side of the trigger to render against when open.
- * Will be reversed when collisions occur and avoidCollisions is enabled.
+ * The placement of the dropdown menu popover.
*
- * @default 'bottom'
+ * @default 'bottom-start' for root-level menus, 'right-start' for nested menus
*/
- side?: DropdownMenuPrimitive.DropdownMenuContentProps[ 'side' ];
+ placement?: Placement;
/**
- * The distance in pixels from the trigger.
+ * The distance between the popover and the anchor element.
*
- * @default 0
+ * @default 8 for root-level menus, 16 for nested menus
*/
- sideOffset?: DropdownMenuPrimitive.DropdownMenuContentProps[ 'sideOffset' ];
+ gutter?: number;
/**
- * The preferred alignment against the trigger.
- * May change when collisions occur.
+ * The skidding of the popover along the anchor element. Can be set to
+ * negative values to make the popover shift to the opposite side.
*
- * @default 'start'
+ * @default 0 for root-level menus, -8 for nested menus
*/
- align?: DropdownMenuPrimitive.DropdownMenuContentProps[ 'align' ];
+ shift?: number;
/**
- * An offset in pixels from the "start" or "end" alignment options.
+ * Determines whether the menu popover will be hidden when the user presses
+ * the Escape key.
*
- * @default 0
- */
- alignOffset?: DropdownMenuPrimitive.DropdownMenuContentProps[ 'alignOffset' ];
- /**
- * The trigger button.
- */
- trigger: React.ReactNode;
+ * @default `( event ) => { event.preventDefault(); return true; }`
+ */
+ hideOnEscape?:
+ | boolean
+ | ( (
+ event: KeyboardEvent | React.KeyboardEvent< Element >
+ ) => boolean );
+}
+
+export interface DropdownMenuGroupProps {
/**
- * The contents of the dropdown
+ * The contents of the dropdown menu group.
*/
children: React.ReactNode;
-};
+}
-export type DropdownSubMenuTriggerProps = {
+export interface DropdownMenuItemProps {
/**
- * The contents of the item.
+ * The contents of the menu item.
*/
children: React.ReactNode;
/**
- * The contents of the item's prefix.
+ * The contents of the menu item's prefix.
*/
prefix?: React.ReactNode;
/**
- * The contents of the item's suffix.
- *
- * @default The standard chevron icon for a submenu trigger.
+ * The contents of the menu item's suffix.
*/
suffix?: React.ReactNode;
-};
-
-export type DropdownSubMenuProps = {
/**
- * The open state of the submenu when it is initially rendered. Use when you
- * do not need to control its open state.
- */
- defaultOpen?: DropdownMenuPrimitive.DropdownMenuSubProps[ 'defaultOpen' ];
- /**
- * The controlled open state of the submenu. Must be used in conjunction with
- * `onOpenChange`.
- */
- open?: DropdownMenuPrimitive.DropdownMenuSubProps[ 'open' ];
- /**
- * Event handler called when the open state of the submenu changes.
- */
- onOpenChange?: DropdownMenuPrimitive.DropdownMenuSubProps[ 'onOpenChange' ];
- /**
- * When `true`, prevents the user from interacting with the item.
- */
- disabled?: DropdownMenuPrimitive.DropdownMenuSubTriggerProps[ 'disabled' ];
- /**
- * Optional text used for typeahead purposes for the trigger. By default the typeahead
- * behavior will use the `.textContent` of the trigger. Use this when the content
- * is complex, or you have non-textual content inside.
- */
- textValue?: DropdownMenuPrimitive.DropdownMenuSubTriggerProps[ 'textValue' ];
- /**
- * The contents rendered inside the trigger. The trigger should be
- * an instance of the `DropdownSubMenuTriggerProps` component.
+ * Whether to hide the parent menu when the item is clicked.
+ *
+ * @default true
*/
- trigger: React.ReactNode;
+ hideOnClick?: boolean;
/**
- * The contents of the dropdown sub menu
+ * Determines if the element is disabled.
*/
- children: React.ReactNode;
-};
+ disabled?: boolean;
+}
-export type DropdownMenuItemProps = {
+export interface DropdownMenuCheckboxItemProps
+ extends Omit< DropdownMenuItemProps, 'prefix' | 'hideOnClick' > {
/**
- * When true, prevents the user from interacting with the item.
+ * Whether to hide the dropdown menu when the item is clicked.
*
* @default false
*/
- disabled?: DropdownMenuPrimitive.DropdownMenuItemProps[ 'disabled' ];
+ hideOnClick?: boolean;
/**
- * Event handler called when the user selects an item (via mouse or keyboard).
- * Calling `event.preventDefault` in this handler will prevent the dropdown
- * menu from closing when selecting that item.
+ * The checkbox menu item's name.
*/
- onSelect?: DropdownMenuPrimitive.DropdownMenuItemProps[ 'onSelect' ];
+ name: string;
/**
- * Optional text used for typeahead purposes. By default the typeahead
- * behavior will use the `.textContent` of the item. Use this when the content
- * is complex, or you have non-textual content inside.
+ * The checkbox item's value, useful when using multiple checkbox menu items
+ * associated to the same `name`.
*/
- textValue?: DropdownMenuPrimitive.DropdownMenuItemProps[ 'textValue' ];
+ value?: string;
/**
- * The contents of the item
+ * The controlled checked state of the checkbox menu item.
*/
- children: React.ReactNode;
+ checked?: boolean;
/**
- * The contents of the item's prefix
+ * The checked state of the checkbox menu item when it is initially rendered.
+ * Use when not wanting to control its checked state.
*/
- prefix?: React.ReactNode;
+ defaultChecked?: boolean;
/**
- * The contents of the item's suffix
+ * Event handler called when the checked state of the checkbox menu item changes.
*/
- suffix?: React.ReactNode;
-};
+ onChange?: ( event: React.ChangeEvent< HTMLInputElement > ) => void;
+}
-export type DropdownMenuCheckboxItemProps = {
+export interface DropdownMenuRadioItemProps
+ extends Omit< DropdownMenuItemProps, 'prefix' | 'hideOnClick' > {
/**
- * The controlled checked state of the item.
- * Must be used in conjunction with `onCheckedChange`.
+ * Whether to hide the dropdown menu when the item is clicked.
*
* @default false
*/
- checked?: DropdownMenuPrimitive.DropdownMenuCheckboxItemProps[ 'checked' ];
- /**
- * Event handler called when the checked state changes.
- */
- onCheckedChange?: DropdownMenuPrimitive.DropdownMenuCheckboxItemProps[ 'onCheckedChange' ];
+ hideOnClick?: boolean;
/**
- * When `true`, prevents the user from interacting with the item.
+ * The radio item's name.
*/
- disabled?: DropdownMenuPrimitive.DropdownMenuCheckboxItemProps[ 'disabled' ];
+ name: string;
/**
- * Event handler called when the user selects an item (via mouse or keyboard).
- * Calling `event.preventDefault` in this handler will prevent the dropdown
- * menu from closing when selecting that item.
+ * The radio item's value.
*/
- onSelect?: DropdownMenuPrimitive.DropdownMenuCheckboxItemProps[ 'onSelect' ];
+ value: string | number;
/**
- * Optional text used for typeahead purposes. By default the typeahead
- * behavior will use the `.textContent` of the item. Use this when the content
- * is complex, or you have non-textual content inside.
+ * The controlled checked state of the radio menu item.
*/
- textValue?: DropdownMenuPrimitive.DropdownMenuCheckboxItemProps[ 'textValue' ];
+ checked?: boolean;
/**
- * The contents of the checkbox item
+ * The checked state of the radio menu item when it is initially rendered.
+ * Use when not wanting to control its checked state.
*/
- children: React.ReactNode;
+ defaultChecked?: boolean;
/**
- * The contents of the checkbox item's suffix
+ * Event handler called when the checked radio menu item changes.
*/
- suffix?: React.ReactNode;
-};
-
-export type DropdownMenuRadioGroupProps = {
- /**
- * The value of the selected item in the group.
- */
- value?: DropdownMenuPrimitive.DropdownMenuRadioGroupProps[ 'value' ];
- /**
- * Event handler called when the value changes.
- */
- onValueChange?: DropdownMenuPrimitive.DropdownMenuRadioGroupProps[ 'onValueChange' ];
- /**
- * The contents of the radio group
- */
- children: React.ReactNode;
-};
-
-export type DropdownMenuRadioItemProps = {
- /**
- * The unique value of the item.
- */
- value: DropdownMenuPrimitive.DropdownMenuRadioItemProps[ 'value' ];
- /**
- * When `true`, prevents the user from interacting with the item.
- */
- disabled?: DropdownMenuPrimitive.DropdownMenuRadioItemProps[ 'disabled' ];
- /**
- * Event handler called when the user selects an item (via mouse or keyboard).
- * Calling `event.preventDefault` in this handler will prevent the dropdown
- * menu from closing when selecting that item.
- */
- onSelect?: DropdownMenuPrimitive.DropdownMenuRadioItemProps[ 'onSelect' ];
- /**
- * Optional text used for typeahead purposes. By default the typeahead
- * behavior will use the `.textContent` of the item. Use this when the content
- * is complex, or you have non-textual content inside.
- */
- textValue?: DropdownMenuPrimitive.DropdownMenuRadioItemProps[ 'textValue' ];
- /**
- * The contents of the radio item
- */
- children: React.ReactNode;
- /**
- * The contents of the radio item's suffix
- */
- suffix?: React.ReactNode;
-};
-
-export type DropdownMenuLabelProps = {
- /**
- * The contents of the label
- */
- children: React.ReactNode;
-};
-
-export type DropdownMenuGroupProps = {
- /**
- * The contents of the group
- */
- children: React.ReactNode;
-};
-
-export type DropdownMenuSeparatorProps = {};
-
-export type DropdownMenuInternalContext = {
- /**
- * This variant can be used to change the appearance of the component in
- * specific contexts, ie. when rendered inside the `Toolbar` component.
- */
- variant?: 'toolbar';
-};
+ onChange?: ( event: React.ChangeEvent< HTMLInputElement > ) => void;
+}
-export type DropdownMenuPrivateContext = Pick<
- DropdownMenuInternalContext,
- 'variant'
-> & {
- portalContainer?: HTMLElement | null;
-};
+export interface DropdownMenuSeparatorProps {}
diff --git a/packages/components/src/private-apis.ts b/packages/components/src/private-apis.ts
index ba0048407574e7..6694c1d30bdce8 100644
--- a/packages/components/src/private-apis.ts
+++ b/packages/components/src/private-apis.ts
@@ -19,26 +19,14 @@ import { default as ProgressBar } from './progress-bar';
import { createPrivateSlotFill } from './slot-fill';
import {
DropdownMenu as DropdownMenuV2,
- DropdownMenuCheckboxItem as DropdownMenuCheckboxItemV2,
DropdownMenuGroup as DropdownMenuGroupV2,
DropdownMenuItem as DropdownMenuItemV2,
- DropdownMenuLabel as DropdownMenuLabelV2,
- DropdownMenuRadioGroup as DropdownMenuRadioGroupV2,
+ DropdownMenuCheckboxItem as DropdownMenuCheckboxItemV2,
DropdownMenuRadioItem as DropdownMenuRadioItemV2,
DropdownMenuSeparator as DropdownMenuSeparatorV2,
- DropdownSubMenu as DropdownSubMenuV2,
- DropdownSubMenuTrigger as DropdownSubMenuTriggerV2,
+ DropdownMenuItemLabel as DropdownMenuItemLabelV2,
+ DropdownMenuItemHelpText as DropdownMenuItemHelpTextV2,
} from './dropdown-menu-v2';
-import {
- DropdownMenu as DropdownMenuV2Ariakit,
- DropdownMenuGroup as DropdownMenuGroupV2Ariakit,
- DropdownMenuItem as DropdownMenuItemV2Ariakit,
- DropdownMenuCheckboxItem as DropdownMenuCheckboxItemV2Ariakit,
- DropdownMenuRadioItem as DropdownMenuRadioItemV2Ariakit,
- DropdownMenuSeparator as DropdownMenuSeparatorV2Ariakit,
- DropdownMenuItemLabel as DropdownMenuItemLabelV2Ariakit,
- DropdownMenuItemHelpText as DropdownMenuItemHelpTextV2Ariakit,
-} from './dropdown-menu-v2-ariakit';
import { ComponentsContext } from './context/context-system-provider';
import Theme from './theme';
import Tabs from './tabs';
@@ -56,26 +44,16 @@ lock( privateApis, {
__experimentalPopoverLegacyPositionToPlacement,
createPrivateSlotFill,
ComponentsContext,
+ ProgressBar,
+ Tabs,
+ Theme,
DropdownMenuV2,
- DropdownMenuCheckboxItemV2,
DropdownMenuGroupV2,
DropdownMenuItemV2,
- DropdownMenuLabelV2,
- DropdownMenuRadioGroupV2,
+ DropdownMenuCheckboxItemV2,
DropdownMenuRadioItemV2,
DropdownMenuSeparatorV2,
- DropdownSubMenuV2,
- DropdownSubMenuTriggerV2,
- ProgressBar,
- Tabs,
- Theme,
- DropdownMenuV2Ariakit,
- DropdownMenuGroupV2Ariakit,
- DropdownMenuItemV2Ariakit,
- DropdownMenuCheckboxItemV2Ariakit,
- DropdownMenuRadioItemV2Ariakit,
- DropdownMenuSeparatorV2Ariakit,
- DropdownMenuItemLabelV2Ariakit,
- DropdownMenuItemHelpTextV2Ariakit,
+ DropdownMenuItemLabelV2,
+ DropdownMenuItemHelpTextV2,
kebabCase,
} );
diff --git a/packages/dataviews/src/add-filter.js b/packages/dataviews/src/add-filter.js
index eb244bff4782b5..acc4c865340143 100644
--- a/packages/dataviews/src/add-filter.js
+++ b/packages/dataviews/src/add-filter.js
@@ -17,12 +17,12 @@ import { LAYOUT_LIST, OPERATORS } from './constants';
import { DropdownMenuRadioItemCustom } from './dropdown-menu-helper';
const {
- DropdownMenuV2Ariakit: DropdownMenu,
- DropdownMenuGroupV2Ariakit: DropdownMenuGroup,
- DropdownMenuItemV2Ariakit: DropdownMenuItem,
- DropdownMenuRadioItemV2Ariakit: DropdownMenuRadioItem,
- DropdownMenuSeparatorV2Ariakit: DropdownMenuSeparator,
- DropdownMenuItemLabelV2Ariakit: DropdownMenuItemLabel,
+ DropdownMenuV2: DropdownMenu,
+ DropdownMenuGroupV2: DropdownMenuGroup,
+ DropdownMenuItemV2: DropdownMenuItem,
+ DropdownMenuRadioItemV2: DropdownMenuRadioItem,
+ DropdownMenuSeparatorV2: DropdownMenuSeparator,
+ DropdownMenuItemLabelV2: DropdownMenuItemLabel,
} = unlock( componentsPrivateApis );
function WithSeparators( { children } ) {
diff --git a/packages/dataviews/src/dropdown-menu-helper.js b/packages/dataviews/src/dropdown-menu-helper.js
index ab59e7c764ae52..2fd6ae18a4536e 100644
--- a/packages/dataviews/src/dropdown-menu-helper.js
+++ b/packages/dataviews/src/dropdown-menu-helper.js
@@ -13,7 +13,7 @@ import { SVG, Circle } from '@wordpress/primitives';
*/
import { unlock } from './lock-unlock';
-const { DropdownMenuItemV2Ariakit: DropdownMenuItem } = unlock(
+const { DropdownMenuItemV2: DropdownMenuItem } = unlock(
componentsPrivateApis
);
diff --git a/packages/dataviews/src/filter-summary.js b/packages/dataviews/src/filter-summary.js
index 99eb796f0a8bfd..e255561660648c 100644
--- a/packages/dataviews/src/filter-summary.js
+++ b/packages/dataviews/src/filter-summary.js
@@ -18,11 +18,11 @@ import { unlock } from './lock-unlock';
import { DropdownMenuRadioItemCustom } from './dropdown-menu-helper';
const {
- DropdownMenuV2Ariakit: DropdownMenu,
- DropdownMenuGroupV2Ariakit: DropdownMenuGroup,
- DropdownMenuItemV2Ariakit: DropdownMenuItem,
- DropdownMenuSeparatorV2Ariakit: DropdownMenuSeparator,
- DropdownMenuItemLabelV2Ariakit: DropdownMenuItemLabel,
+ DropdownMenuV2: DropdownMenu,
+ DropdownMenuGroupV2: DropdownMenuGroup,
+ DropdownMenuItemV2: DropdownMenuItem,
+ DropdownMenuSeparatorV2: DropdownMenuSeparator,
+ DropdownMenuItemLabelV2: DropdownMenuItemLabel,
} = unlock( componentsPrivateApis );
const FilterText = ( { activeElement, filterInView, filter } ) => {
diff --git a/packages/dataviews/src/item-actions.js b/packages/dataviews/src/item-actions.js
index 17690f0064112a..ff584c11f8150b 100644
--- a/packages/dataviews/src/item-actions.js
+++ b/packages/dataviews/src/item-actions.js
@@ -17,10 +17,10 @@ import { moreVertical } from '@wordpress/icons';
import { unlock } from './lock-unlock';
const {
- DropdownMenuV2Ariakit: DropdownMenu,
- DropdownMenuGroupV2Ariakit: DropdownMenuGroup,
- DropdownMenuItemV2Ariakit: DropdownMenuItem,
- DropdownMenuItemLabelV2Ariakit: DropdownMenuItemLabel,
+ DropdownMenuV2: DropdownMenu,
+ DropdownMenuGroupV2: DropdownMenuGroup,
+ DropdownMenuItemV2: DropdownMenuItem,
+ DropdownMenuItemLabelV2: DropdownMenuItemLabel,
} = unlock( componentsPrivateApis );
function ButtonTrigger( { action, onClick } ) {
diff --git a/packages/dataviews/src/view-actions.js b/packages/dataviews/src/view-actions.js
index b7ae1da63c1eca..458032d68ac70c 100644
--- a/packages/dataviews/src/view-actions.js
+++ b/packages/dataviews/src/view-actions.js
@@ -15,11 +15,11 @@ import { VIEW_LAYOUTS, LAYOUT_TABLE, SORTING_DIRECTIONS } from './constants';
import { DropdownMenuRadioItemCustom } from './dropdown-menu-helper';
const {
- DropdownMenuV2Ariakit: DropdownMenu,
- DropdownMenuGroupV2Ariakit: DropdownMenuGroup,
- DropdownMenuItemV2Ariakit: DropdownMenuItem,
- DropdownMenuCheckboxItemV2Ariakit: DropdownMenuCheckboxItem,
- DropdownMenuItemLabelV2Ariakit: DropdownMenuItemLabel,
+ DropdownMenuV2: DropdownMenu,
+ DropdownMenuGroupV2: DropdownMenuGroup,
+ DropdownMenuItemV2: DropdownMenuItem,
+ DropdownMenuCheckboxItemV2: DropdownMenuCheckboxItem,
+ DropdownMenuItemLabelV2: DropdownMenuItemLabel,
} = unlock( componentsPrivateApis );
function ViewTypeMenu( { view, onChangeView, supportedLayouts } ) {
diff --git a/packages/dataviews/src/view-table.js b/packages/dataviews/src/view-table.js
index 775bae97af4e6f..200da945a101a1 100644
--- a/packages/dataviews/src/view-table.js
+++ b/packages/dataviews/src/view-table.js
@@ -20,11 +20,11 @@ import { ENUMERATION_TYPE, OPERATORS, SORTING_DIRECTIONS } from './constants';
import { DropdownMenuRadioItemCustom } from './dropdown-menu-helper';
const {
- DropdownMenuV2Ariakit: DropdownMenu,
- DropdownMenuGroupV2Ariakit: DropdownMenuGroup,
- DropdownMenuItemV2Ariakit: DropdownMenuItem,
- DropdownMenuSeparatorV2Ariakit: DropdownMenuSeparator,
- DropdownMenuItemLabelV2Ariakit: DropdownMenuItemLabel,
+ DropdownMenuV2: DropdownMenu,
+ DropdownMenuGroupV2: DropdownMenuGroup,
+ DropdownMenuItemV2: DropdownMenuItem,
+ DropdownMenuSeparatorV2: DropdownMenuSeparator,
+ DropdownMenuItemLabelV2: DropdownMenuItemLabel,
} = unlock( componentsPrivateApis );
const sortArrows = { asc: '↑', desc: '↓' };