From fcb06786f956148e15bcb7fdef1a4952673cb9cc Mon Sep 17 00:00:00 2001 From: Jan Paul Posma Date: Wed, 8 Aug 2018 11:10:46 -0700 Subject: [PATCH] Add `renderMenuWrapper` prop To make it possible to render the menu in a portal while keeping `refs.menu` on the actual menu element, so that scrolling items into view keeps working. Test plan: Added unit test; verified that this works with a portal in an actual production application. --- README.md | 18 ++++++++++++++++++ lib/Autocomplete.js | 13 ++++++++++++- lib/__tests__/Autocomplete-test.js | 17 +++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e78523da..b7d4a8ea 100644 --- a/README.md +++ b/README.md @@ -167,6 +167,24 @@ and keyboard navigation logic will break. `styles` will contain { top, left, minWidth } which are the coordinates of the top-left corner and the width of the dropdown menu. +#### `renderMenuWrapper: Function` (optional) +Default value: +```jsx +function(menu) { + return menu; +} +``` + +Arguments: `menu: React element` + +Invoked to generate a wrapper around the menu component. Use this if you +want to create a React Portal around the menu, e.g.: +```jsx +function(menu) { + return createPortal(menu, document.body); +} +``` + #### `selectOnBlur: Boolean` (optional) Default value: `false` diff --git a/lib/Autocomplete.js b/lib/Autocomplete.js index 1658c8bb..de2f2224 100644 --- a/lib/Autocomplete.js +++ b/lib/Autocomplete.js @@ -95,6 +95,14 @@ class Autocomplete extends React.Component { * and the width of the dropdown menu. */ renderMenu: PropTypes.func, + /** + * Arguments: `menu: React element` + * + * Invoked to generate a wrapper around the menu component. Use this if you + * want to create a React Portal around the menu, e.g.: + * `(menu) => createPortal(menu, document.body)` + */ + renderMenuWrapper: PropTypes.func, /** * Styles that are applied to the dropdown menu in the default `renderMenu` * implementation. If you override `renderMenu` and you want to use @@ -175,6 +183,9 @@ class Autocomplete extends React.Component { renderMenu(items, value, style) { return
}, + renderMenuWrapper(menu) { + return menu + }, menuStyle: { borderRadius: '3px', boxShadow: '0 2px 12px rgba(0, 0, 0, 0.1)', @@ -586,7 +597,7 @@ class Autocomplete extends React.Component { onClick: this.composeEventHandlers(this.handleInputClick, inputProps.onClick), value: this.props.value, })} - {open && this.renderMenu()} + {open && this.props.renderMenuWrapper(this.renderMenu())} {this.props.debug && (
             {JSON.stringify(this._debugStates.slice(Math.max(0, this._debugStates.length - 5), this._debugStates.length), null, 2)}
diff --git a/lib/__tests__/Autocomplete-test.js b/lib/__tests__/Autocomplete-test.js
index ed5b79fb..da3643a4 100644
--- a/lib/__tests__/Autocomplete-test.js
+++ b/lib/__tests__/Autocomplete-test.js
@@ -723,6 +723,23 @@ describe('Autocomplete#renderMenu', () => {
   })
 })
 
+describe('Autocomplete#renderMenuWrapper', () => {
+  it('should be invoked in `render` to create a wrapper around the menu', () => {
+    class MenuWrapper extends React.Component {
+      render() {
+        return this.props.children
+      }
+    }
+    const tree = mount(AutocompleteComponentJSX({
+      renderMenuWrapper(menu) {
+        return {menu}
+      }
+    }))
+    tree.setState({ isOpen: true })
+    expect(tree.find(MenuWrapper).length).toBe(1)
+  })
+})
+
 describe('Autocomplete isItemSelectable', () => {
   const autocompleteWrapper = mount(AutocompleteComponentJSX({
     open: true,