diff --git a/packages/eui/changelogs/upcoming/7759.md b/packages/eui/changelogs/upcoming/7759.md new file mode 100644 index 00000000000..b87098a3008 --- /dev/null +++ b/packages/eui/changelogs/upcoming/7759.md @@ -0,0 +1 @@ +- `EuiFlyoutResizable` now respects `size` prop updates, allowing for controlled `size` usage diff --git a/packages/eui/src/components/flyout/flyout_resizable.spec.tsx b/packages/eui/src/components/flyout/flyout_resizable.spec.tsx index fb28eb6fef4..3b01819bd66 100644 --- a/packages/eui/src/components/flyout/flyout_resizable.spec.tsx +++ b/packages/eui/src/components/flyout/flyout_resizable.spec.tsx @@ -10,7 +10,7 @@ /// /// -import React from 'react'; +import React, { useState, useCallback } from 'react'; import { EuiFlyoutResizable } from './flyout_resizable'; @@ -196,6 +196,65 @@ describe('EuiFlyoutResizable', () => { expect(onResize.lastCall.args).to.eql([600]); }); }); + + it('responds to `size` prop updates after user resize', () => { + let onResizeCalls = 0; + const TestComponent = () => { + const [size, setSize] = useState(400); + const onResize = useCallback((width: number) => { + setSize(width); + onResizeCalls++; + }, []); + + return ( + + + + + + ); + }; + cy.mount(); + cy.get('.euiFlyout').should('have.css', 'inline-size', '400px'); + + // User resizing + cy.get('[data-test-subj="euiResizableButton"]').focus(); + cy.realPress('ArrowLeft').then(() => { + onResizeCalls = 0; // Reset resize calls. Cypress is flaky here so we shouldn't directly assert on the number of calls + }); + cy.get('.euiFlyout').should('have.css', 'inline-size', '410px'); + + // Consumer resizing + cy.wait(100); // Wait a tick for flyout to finish rerendering + cy.get('[data-test-subj="resetSize"]').realClick(); + cy.get('.euiFlyout') + .should('have.css', 'inline-size', '400px') + .then(() => { + expect(onResizeCalls).to.eql(0); + }); + + cy.wait(100); // Wait a tick for flyout to finish rerendering + cy.get('[data-test-subj="setSize"]').realClick(); + cy.get('.euiFlyout') + .should('have.css', 'inline-size', '200px') + .then(() => { + expect(onResizeCalls).to.eql(0); + }); + + cy.wait(100); // Wait a tick for flyout to finish rerendering + cy.get('[data-test-subj="setSizeKey"]').realClick(); + cy.get('.euiFlyout') + .should('have.css', 'inline-size', '384px') + .then(() => { + expect(onResizeCalls).to.eql(0); + }); + }); }); describe('push flyouts', () => { diff --git a/packages/eui/src/components/flyout/flyout_resizable.tsx b/packages/eui/src/components/flyout/flyout_resizable.tsx index bdbc63fd854..a91d4b20c54 100644 --- a/packages/eui/src/components/flyout/flyout_resizable.tsx +++ b/packages/eui/src/components/flyout/flyout_resizable.tsx @@ -65,12 +65,20 @@ export const EuiFlyoutResizable = forwardRef( // Must use state for the flyout ref in order for the useEffect to be correctly called after render const [flyoutRef, setFlyoutRef] = useState(null); const setRefs = useCombinedRefs([setFlyoutRef, ref]); + + useEffect(() => { + if (!flyoutWidth && flyoutRef) { + setCallOnResize(false); // Don't call `onResize` for non-user width changes + setFlyoutWidth(getFlyoutMinMaxWidth(flyoutRef.offsetWidth)); + } + }, [flyoutWidth, flyoutRef, getFlyoutMinMaxWidth]); + + // Update flyout width when consumers pass in a new `size` useEffect(() => { - setCallOnResize(false); // Don't call `onResize` for non-user width changes - setFlyoutWidth( - flyoutRef ? getFlyoutMinMaxWidth(flyoutRef.offsetWidth) : 0 - ); - }, [flyoutRef, getFlyoutMinMaxWidth, size]); + setCallOnResize(false); + // For string `size`s, resetting flyoutWidth to 0 will trigger the above useEffect's recalculation + setFlyoutWidth(typeof size === 'number' ? getFlyoutMinMaxWidth(size) : 0); + }, [size, getFlyoutMinMaxWidth]); // Initial numbers to calculate from, on resize drag start const initialWidth = useRef(0);